commit 9514c8d53e1965e3ed1fbe564c431f3c223b36c3 Author: Yuki Joou Date: Sun Nov 12 00:39:52 2023 +0100 Initial commit: added current work/poc diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..c4295e8 --- /dev/null +++ b/.clang-format @@ -0,0 +1,23 @@ +BasedOnStyle: Microsoft + +UseTab: ForIndentation +TabWidth: 4 +BreakBeforeBraces: Linux +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: WithoutElse +PointerAlignment: Left + +AlignAfterOpenBracket: Align +BinPackArguments: false +BinPackParameters: false + + +AlignConsecutiveAssignments: Consecutive +AlignConsecutiveBitFields: Consecutive +AlignConsecutiveDeclarations: Consecutive +AlignConsecutiveMacros: Consecutive +AlignEscapedNewlines: Right +AlignOperands: AlignAfterOperator +AlignTrailingComments: true + +ColumnLimit: 80 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d163863 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3d9a552 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.21) +project(fcitx5-ipa-sil) + +find_package(Fcitx5Core REQUIRED) +# Setup some compiler option that is generally useful and compatible with Fcitx 5 (C++17) +include("${FCITX_INSTALL_CMAKECONFIG_DIR}/Fcitx5Utils/Fcitx5CompilerSettings.cmake") + +add_subdirectory(src) diff --git a/README.md b/README.md new file mode 100644 index 0000000..731efca --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# fcitx5-ipa-sil + +This is an IME for [Fctix 5](https://fcitx-im.org/wiki/Fcitx_5) that brings support for the [SIL IPA](https://keyman.com/keyboards/sil_ipa) input method. + +This allows the user to type IPA characters fairly easly by inputing only ASCII. + +Here are a few examples of SIL to IPA conversions: + +| IPA | SIL | +|-----|------| +| ə | `e=` | +| ʃ | `s=` | +| ŋ | `n>` | +| a | `a` | + +**This is still a work-in-progress**! This is mostly a rough draft to allow me to follow my phonetics class on a computer. I didn't put much thought into getting it to work for other people's needs! Feel free to send me suggestions though \:) + +## Building & trying it out! + +```console + $ mkdir -p build/ + $ cd build/ + $ cmake ../ -DCMAKE_INSTALL_PREFIX=/usr + $ make + $ sudo make install +``` + +You can then restart fcitx, and add the "SIL IPA" input method to your available input methods! diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..af4a8e7 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,12 @@ +add_library(ipa-sil SHARED ipa-sil.cpp) +target_link_libraries(ipa-sil PRIVATE Fcitx5::Core) +set_target_properties(ipa-sil PROPERTIES PREFIX "") +install(TARGETS ipa-sil DESTINATION "${FCITX_INSTALL_LIBDIR}/fcitx5") + +# Addon config file +# We need additional layer of conversion because we want PROJECT_VERSION in it. +configure_file(ipa-sil-addon.conf.in ipa-sil-addon.conf) +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ipa-sil-addon.conf" RENAME ipa-sil.conf DESTINATION "${FCITX_INSTALL_PKGDATADIR}/addon") + +# Input Method registration file +install(FILES "ipa-sil.conf" DESTINATION "${FCITX_INSTALL_PKGDATADIR}/inputmethod") diff --git a/src/ipa-sil-addon.conf.in b/src/ipa-sil-addon.conf.in new file mode 100644 index 0000000..232f53f --- /dev/null +++ b/src/ipa-sil-addon.conf.in @@ -0,0 +1,8 @@ +[Addon] +Name=IPA SIL +Category=InputMethod +Version=@PROJECT_VERSION@ +Library=ipa-sil +Type=SharedLibrary +OnDemand=True +Configurable=True \ No newline at end of file diff --git a/src/ipa-sil.conf b/src/ipa-sil.conf new file mode 100644 index 0000000..3af7cf4 --- /dev/null +++ b/src/ipa-sil.conf @@ -0,0 +1,13 @@ +[InputMethod] +# Translatable name of the input method +Name=IPA SIL +# Icon name +Icon=fcitx-ipa-sil +# A short label that present the name of input method +Label=sil +# ISO 639 language code +LangCode=en_GB +# Match addon name +Addon=ipa-sil +# Whether this input method support customization +# Configurable=True \ No newline at end of file diff --git a/src/ipa-sil.cpp b/src/ipa-sil.cpp new file mode 100644 index 0000000..3f1badd --- /dev/null +++ b/src/ipa-sil.cpp @@ -0,0 +1,138 @@ +/* + * SPDX-FileCopyrightText: 2023 Yuki Joou + * + * SPDX-License-Identifier: BSD-3-Clause + * + */ +#include "ipa-sil.hpp" + +#include +#include + +std::optional getIPAForSIL(std::string baseCharacter, + fcitx::KeySym silModifier) +{ + // TODO: Use a proper data structure, like a hash map + // Even better: load that data off of a file! + if (baseCharacter == "s") { + if (silModifier == FcitxKey_equal) return "ʃ"; + } + + if (baseCharacter == "z") { + if (silModifier == FcitxKey_equal) return "ʒ"; + } + + if (baseCharacter == "t") { + if (silModifier == FcitxKey_equal) return "θ"; + } + + if (baseCharacter == "d") { + if (silModifier == FcitxKey_equal) return "ð"; + } + + if (baseCharacter == "n") { + if (silModifier == FcitxKey_greater) return "ŋ"; + } + + if (baseCharacter == "i") { + if (silModifier == FcitxKey_equal) return "ɪ"; + } + + if (baseCharacter == "e") { + if (silModifier == FcitxKey_equal) return "ə"; + } + + return {}; +} + +bool isSILModifier(fcitx::KeySym key) +{ + return key == FcitxKey_less || key == FcitxKey_greater || + key == FcitxKey_equal; +} + +void SILState::handleAlphaKey(fcitx::Key key) +{ + if (!isSILModifier(key.sym()) || !m_lastKey.has_value()) { + m_lastKey = key.toString(); + m_buffer.type(key.sym()); + return; + } + + if (isSILModifier(key.sym())) { + auto ipaChar = getIPAForSIL(*m_lastKey, key.sym()); + if (ipaChar) { + m_buffer.backspace(); + m_buffer.type(*ipaChar); + } + m_lastKey.reset(); + } +} + +void SILState::keyEvent(fcitx::KeyEvent& keyEvent) +{ + // If it's not a simple letter key, and we have no text input yet, let it + // through. + if (m_buffer.empty() && !keyEvent.key().isSimple()) return; + + if (keyEvent.key().check(FcitxKey_Return)) { + m_ic->commitString(m_buffer.userInput()); + reset(); + } else if (keyEvent.key().check(FcitxKey_space)) { + m_ic->commitString(m_buffer.userInput()); + m_ic->commitString(" "); + reset(); + } else if (keyEvent.key().check(FcitxKey_BackSpace)) + m_buffer.backspace(); + else if (keyEvent.key().isSimple()) + handleAlphaKey(keyEvent.key()); + // m_buffer.type(keyEvent.key().sym()); + updateUI(); + + keyEvent.filterAndAccept(); +} + +void SILState::updateUI() +{ + auto& inputPanel = m_ic->inputPanel(); + inputPanel.reset(); + + if (m_ic->capabilityFlags().test(fcitx::CapabilityFlag::Preedit)) { + fcitx::Text preedit(m_buffer.userInput(), + fcitx::TextFormatFlag::HighLight); + inputPanel.setClientPreedit(preedit); + } else { + fcitx::Text preedit(m_buffer.userInput()); + inputPanel.setPreedit(preedit); + } + m_ic->updateUserInterface(fcitx::UserInterfaceComponent::InputPanel); + m_ic->updatePreedit(); +} + +SILEngine::SILEngine(fcitx::Instance* instance) + : m_factory( + [this](fcitx::InputContext& ic) { return new SILState(this, &ic); }) +{ + instance->inputContextManager().registerProperty("silState", &m_factory); +} + +void SILEngine::keyEvent(const fcitx::InputMethodEntry&, + fcitx::KeyEvent& keyEvent) +{ + if (keyEvent.isRelease() || keyEvent.key().states()) { + return; + } + + auto ic = keyEvent.inputContext(); + auto* state = ic->propertyFor(&m_factory); + state->keyEvent(keyEvent); +} + +void SILEngine::reset(const fcitx::InputMethodEntry&, + fcitx::InputContextEvent& event) +{ + auto* state = event.inputContext()->propertyFor(&m_factory); + state->reset(); +} + +FCITX_ADDON_FACTORY(SILEngineFactory); diff --git a/src/ipa-sil.hpp b/src/ipa-sil.hpp new file mode 100644 index 0000000..91134d9 --- /dev/null +++ b/src/ipa-sil.hpp @@ -0,0 +1,68 @@ +/* + * SPDX-FileCopyrightText: 2023 Yuki Joou + * + * SPDX-License-Identifier: BSD-3-Clause + * + */ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +class SILEngine; + +class SILState : public fcitx::InputContextProperty +{ + public: + SILState(SILEngine* engine, fcitx::InputContext* ic) + : m_engine(engine), m_ic(ic) + { + } + + void handleAlphaKey(fcitx::Key); + void keyEvent(fcitx::KeyEvent& keyEvent); + + void updateUI(); + + void reset() + { + m_buffer.clear(); + updateUI(); + } + + private: + SILEngine* m_engine; + fcitx::InputContext* m_ic; + fcitx::InputBuffer m_buffer{{fcitx::InputBufferOption::NoOption}}; + + std::optional m_lastKey{}; +}; + +class SILEngine : public fcitx::InputMethodEngineV2 +{ + public: + SILEngine(fcitx::Instance*); + + void keyEvent(const fcitx::InputMethodEntry&, fcitx::KeyEvent&) override; + void reset(const fcitx::InputMethodEntry&, + fcitx::InputContextEvent&) override; + + private: + fcitx::FactoryFor m_factory; +}; + +class SILEngineFactory : public fcitx::AddonFactory +{ + fcitx::AddonInstance* create(fcitx::AddonManager* manager) override + { + FCITX_UNUSED(manager); + return new SILEngine(manager->instance()); + } +};