├── VERSION ├── .envrc ├── .dockerignore ├── src ├── engine │ ├── candidate │ │ ├── src │ │ │ ├── lib.rs │ │ │ └── client.rs │ │ ├── Cargo.toml │ │ └── examples │ │ │ └── client.rs │ ├── core │ │ ├── examples │ │ │ ├── make_config.rs │ │ │ └── print_type_sizes.rs │ │ ├── tests │ │ │ ├── config.rs │ │ │ ├── latin.rs │ │ │ ├── emoji.rs │ │ │ ├── sebeolsik_3_91.rs │ │ │ ├── sebeolsik_3sin_1995.rs │ │ │ ├── sebeolsik_3sin_p2.rs │ │ │ ├── math.rs │ │ │ ├── sebeolsik_3_90.rs │ │ │ ├── shared.rs │ │ │ └── dubeolsik.rs │ │ ├── benches │ │ │ └── call_key.rs │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── os.rs │ │ │ └── config.rs │ ├── backends │ │ ├── latin │ │ │ ├── Cargo.toml │ │ │ ├── data │ │ │ │ ├── colemak.yaml │ │ │ │ ├── qwerty.yaml │ │ │ │ └── dvorak.yaml │ │ │ └── src │ │ │ │ └── lib.rs │ │ ├── emoji │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── lib.rs │ │ ├── math │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── lib.rs │ │ ├── hanja │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── lib.rs │ │ └── hangul │ │ │ ├── Cargo.toml │ │ │ ├── src │ │ │ ├── layout.rs │ │ │ └── lib.rs │ │ │ └── data │ │ │ ├── sebeolsik-3-91.yaml │ │ │ ├── sebeolsik-3-90.yaml │ │ │ ├── sebeolsik-3sin-1995.yaml │ │ │ ├── sebeolsik-3sin-p2.yaml │ │ │ └── dubeolsik.yaml │ ├── dict │ │ ├── Cargo.toml │ │ ├── src │ │ │ ├── math_symbol_key.rs │ │ │ └── lib.rs │ │ └── build.rs │ ├── backend │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── input_result.rs │ │ │ ├── lib.rs │ │ │ └── keymap.rs │ ├── capi │ │ ├── Cargo.toml │ │ ├── cbindgen-c.toml │ │ ├── cbindgen-cpp.toml │ │ ├── build.rs │ │ └── src │ │ │ └── lib.rs │ └── config │ │ └── Cargo.toml ├── frontends │ ├── qt5 │ │ ├── src │ │ │ ├── kime.json │ │ │ ├── kime-qt5.hpp │ │ │ ├── plugin.hpp │ │ │ ├── plugin.cc │ │ │ ├── input_context.hpp │ │ │ └── input_context.cc │ │ └── CMakeLists.txt │ ├── gtk3 │ │ ├── src │ │ │ ├── immodule.h │ │ │ ├── str_buf.h │ │ │ ├── str_buf.c │ │ │ └── gtk.c │ │ └── CMakeLists.txt │ ├── gtk4 │ │ ├── CMakeLists.txt │ │ └── src │ │ │ └── gtk4.c │ ├── qt6 │ │ └── CMakeLists.txt │ ├── wayland │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── main.rs │ │ │ └── lib.rs │ └── xim │ │ ├── Cargo.toml │ │ └── src │ │ ├── main.rs │ │ ├── pe_window │ │ └── bgra.rs │ │ └── pe_window.rs ├── tools │ ├── run_dir │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── version │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── log │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── check │ │ └── Cargo.toml │ ├── properties_writer │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── candidate-window │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── indicator │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ └── kime │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs └── CMakeLists.txt ├── rust-toolchain.toml ├── docs ├── assets │ ├── icon.xcf │ ├── kime-mono.ai │ ├── kime-roundy.ai │ ├── kime-roundy-default.png │ ├── kime-roundy-default.psd │ ├── kime-roundy-default-bluegrey.png │ ├── kime-roundy-default-bluegrey.psd │ ├── kime-roundy-default-without-text.png │ └── kime-roundy-default-without-text-bluegrey.png ├── CONFIGURATION.ko.md └── CONFIGURATION.md ├── .github ├── FUNDING.yml ├── pull_request_template.md └── workflows │ └── ci.yaml ├── ci ├── build_deb.sh └── build_zst.sh ├── res ├── icons │ └── 64x64 │ │ ├── kime-latin-black.png │ │ ├── kime-latin-white.png │ │ ├── kime-hangul-black.png │ │ └── kime-hangul-white.png ├── kime-xdg-autostart ├── kime.desktop └── default_config.yaml ├── scripts ├── tool.sh ├── generate_properties.sh ├── im_kime.conf ├── release-zst.sh ├── control.in ├── im_kime.rc ├── release-deb.sh ├── install.sh └── build.sh ├── .vscode ├── immodules.cache ├── tasks.json ├── settings.json └── launch.json ├── typos.toml ├── .gitignore ├── nix └── deps.nix ├── flake.nix ├── Cargo.toml ├── shell.nix ├── default.nix ├── flake.lock ├── NOTICE.md ├── README.ko.md ├── CODE_OF_CONDUCT.md └── README.md /VERSION: -------------------------------------------------------------------------------- 1 | 3.1.1 2 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | /target 2 | /build 3 | -------------------------------------------------------------------------------- /src/engine/candidate/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod client; 2 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | -------------------------------------------------------------------------------- /src/frontends/qt5/src/kime.json: -------------------------------------------------------------------------------- 1 | { "Keys" : ["kime"] } 2 | -------------------------------------------------------------------------------- /docs/assets/icon.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Riey/kime/HEAD/docs/assets/icon.xcf -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: Riey 4 | -------------------------------------------------------------------------------- /docs/assets/kime-mono.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Riey/kime/HEAD/docs/assets/kime-mono.ai -------------------------------------------------------------------------------- /ci/build_deb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ci/build_zst.sh 4 | scripts/release-deb.sh /opt/kime-out 5 | -------------------------------------------------------------------------------- /docs/assets/kime-roundy.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Riey/kime/HEAD/docs/assets/kime-roundy.ai -------------------------------------------------------------------------------- /ci/build_zst.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | scripts/build.sh -ar 4 | scripts/release-zst.sh /opt/kime-out 5 | -------------------------------------------------------------------------------- /docs/assets/kime-roundy-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Riey/kime/HEAD/docs/assets/kime-roundy-default.png -------------------------------------------------------------------------------- /docs/assets/kime-roundy-default.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Riey/kime/HEAD/docs/assets/kime-roundy-default.psd -------------------------------------------------------------------------------- /res/icons/64x64/kime-latin-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Riey/kime/HEAD/res/icons/64x64/kime-latin-black.png -------------------------------------------------------------------------------- /res/icons/64x64/kime-latin-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Riey/kime/HEAD/res/icons/64x64/kime-latin-white.png -------------------------------------------------------------------------------- /res/icons/64x64/kime-hangul-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Riey/kime/HEAD/res/icons/64x64/kime-hangul-black.png -------------------------------------------------------------------------------- /res/icons/64x64/kime-hangul-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Riey/kime/HEAD/res/icons/64x64/kime-hangul-white.png -------------------------------------------------------------------------------- /docs/assets/kime-roundy-default-bluegrey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Riey/kime/HEAD/docs/assets/kime-roundy-default-bluegrey.png -------------------------------------------------------------------------------- /docs/assets/kime-roundy-default-bluegrey.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Riey/kime/HEAD/docs/assets/kime-roundy-default-bluegrey.psd -------------------------------------------------------------------------------- /scripts/tool.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | cd $(readlink -f $(dirname $0))/.. 6 | 7 | KIME_OUT=$PWD/build/out 8 | 9 | -------------------------------------------------------------------------------- /docs/assets/kime-roundy-default-without-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Riey/kime/HEAD/docs/assets/kime-roundy-default-without-text.png -------------------------------------------------------------------------------- /.vscode/immodules.cache: -------------------------------------------------------------------------------- 1 | "/home/riey/repos/kime/build/out/libkime-gtk3.so" 2 | "kime" "Kime (Korean IME)" "kime" "/usr/share/locale" "ko:*" 3 | -------------------------------------------------------------------------------- /docs/assets/kime-roundy-default-without-text-bluegrey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Riey/kime/HEAD/docs/assets/kime-roundy-default-without-text-bluegrey.png -------------------------------------------------------------------------------- /res/kime-xdg-autostart: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | /usr/bin/kime "#@" 4 | if systemctl --user is-active --quiet app-kime@autostart.service; then 5 | sleep 7s 6 | fi 7 | -------------------------------------------------------------------------------- /scripts/generate_properties.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source $(dirname $0)/tool.sh 4 | 5 | cargo run -p properties_writer > .vscode/c_cpp_properties.json 6 | -------------------------------------------------------------------------------- /src/engine/core/examples/make_config.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | print!( 3 | "{}", 4 | serde_yaml::to_string(&kime_engine_core::RawConfig::default()).unwrap() 5 | ); 6 | } 7 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | 3 | ## Note 4 | 5 | ## Checklist 6 | 7 | - [ ] I have documented my changes properly to adequate places 8 | - [ ] I have updated the docs/CHANGELOG.md 9 | -------------------------------------------------------------------------------- /src/frontends/qt5/src/kime-qt5.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef DEBUG 4 | #include 5 | #define KIME_DEBUG QTextStream(stderr, QIODevice::WriteOnly) 6 | #endif 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /src/tools/run_dir/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kime-run-dir" 3 | version = "0.1.0" 4 | authors = ["Riey "] 5 | edition = "2021" 6 | license = "GPL-3.0-or-later" 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /scripts/im_kime.conf: -------------------------------------------------------------------------------- 1 | IM_CONFIG_SHORT="activate Kime" 2 | IM_CONFIG_LONG="Korean IME" 3 | 4 | package_auto () { 5 | package_status kime 6 | } 7 | 8 | package_menu () { 9 | package_status kime 10 | } 11 | 12 | -------------------------------------------------------------------------------- /src/frontends/gtk3/src/immodule.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | void register_module(GTypeModule *module); 8 | GType get_kime_ty(); 9 | -------------------------------------------------------------------------------- /typos.toml: -------------------------------------------------------------------------------- 1 | [files] 2 | extend-exclude = [ 3 | "src/engine/dict/data/Unihan_Readings.txt", 4 | "src/engine/dict/data/*.xml" 5 | ] 6 | 7 | [default.extend-words] 8 | quitted = "quitted" 9 | thi = "thi" 10 | alph = "alph" 11 | -------------------------------------------------------------------------------- /src/engine/core/tests/config.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn check_default_config() { 3 | assert_eq!( 4 | serde_yaml::to_string(&kime_engine_core::RawConfig::default()).unwrap(), 5 | include_str!("../../../../res/default_config.yaml") 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /scripts/release-zst.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source $(dirname $0)/tool.sh 4 | 5 | if [ -z "$1" ]; then 6 | echo "Usage: " 7 | exit 1 8 | fi 9 | 10 | tar -cvf - -C $KIME_OUT . | zstd -T0 -15 -o "${1}/kime.tar.zst" 11 | -------------------------------------------------------------------------------- /scripts/control.in: -------------------------------------------------------------------------------- 1 | Package: kime 2 | Version: %VER% 3 | Maintainer: Riey 4 | Description: Korean IME 5 | Homepage: https://github.com/Riey/kime 6 | Section: utils 7 | Priority: optional 8 | Architecture: %ARCH% 9 | Recommends: fonts-noto-cjk 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /build 3 | /cmake_build 4 | /cargo-timing* 5 | /.direnv 6 | /.idea 7 | /.cache 8 | /.ccls-cache 9 | /.vscode/c_cpp_properties.json 10 | compile_commands.json 11 | kime_engine.h 12 | kime_engine.hpp 13 | massif.out* 14 | flamegraph.svg 15 | 16 | result 17 | .DS_Store 18 | -------------------------------------------------------------------------------- /src/tools/version/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kime-version" 3 | version = "0.1.0" 4 | authors = ["Riey "] 5 | edition = "2021" 6 | license = "GPL-3.0-or-later" 7 | 8 | [dependencies] 9 | kime-engine-core = { path = "../../engine/core" } 10 | kime-log = { path = "../../tools/log" } 11 | -------------------------------------------------------------------------------- /src/engine/candidate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kime-engine-candidate" 3 | version = "0.1.0" 4 | authors = ["Riey "] 5 | edition = "2021" 6 | license = "GPL-3.0-or-later" 7 | 8 | [dev-dependencies] 9 | kime-engine-dict = { path = "../dict" } 10 | 11 | [dependencies] 12 | nix = "0.26.1" 13 | -------------------------------------------------------------------------------- /src/tools/log/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kime-log" 3 | version = "0.1.0" 4 | authors = ["Riey "] 5 | edition = "2021" 6 | license = "GPL-3.0-or-later" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | simplelog = "0.12" 12 | -------------------------------------------------------------------------------- /src/engine/core/tests/latin.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod shared; 3 | 4 | define_layout_test!("dubeolsik", LatinLayout::Qwerty, InputCategory::Latin); 5 | 6 | #[test] 7 | fn qwerty() { 8 | test_input(&[ 9 | (Key::normal(A), "", "PASS"), 10 | (Key::normal(S), "", "PASS"), 11 | (Key::shift(SemiColon), "", "PASS"), 12 | ]); 13 | } 14 | -------------------------------------------------------------------------------- /src/frontends/gtk3/src/str_buf.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef struct StrBuf { 6 | char *ptr; 7 | size_t len; 8 | size_t cap; 9 | } StrBuf; 10 | 11 | StrBuf str_buf_new(); 12 | void str_buf_delete(StrBuf *buf); 13 | void str_buf_set_str(StrBuf *buf, KimeRustStr s); 14 | void str_buf_set_ch(StrBuf *buf, uint32_t ch); 15 | -------------------------------------------------------------------------------- /scripts/im_kime.rc: -------------------------------------------------------------------------------- 1 | if [ "$IM_CONFIG_PHASE" = 2 ]; then 2 | # start kime daemon 3 | # it failed when other daemon is already running 4 | kime || true 5 | fi 6 | 7 | if [ "$IM_CONFIG_PHASE" = 1 ]; then 8 | XMODIFIERS="@im=kime" 9 | GTK_IM_MODULE=kime 10 | QT4_IM_MODULE=xim 11 | QT_IM_MODULE=kime 12 | CLUTTER_IM_MODULE=xim 13 | fi 14 | -------------------------------------------------------------------------------- /src/engine/backends/latin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kime-engine-backend-latin" 3 | version = "0.1.0" 4 | authors = ["Riey "] 5 | edition = "2021" 6 | license = "GPL-3.0-or-later" 7 | 8 | [dependencies] 9 | kime-engine-backend = { path = "../../backend" } 10 | serde = { version = "1.0.124", features = ["derive"] } 11 | serde_yaml = "0.9" 12 | -------------------------------------------------------------------------------- /src/engine/candidate/examples/client.rs: -------------------------------------------------------------------------------- 1 | use kime_engine_candidate::client::Client; 2 | 3 | fn main() { 4 | let candidate_list = kime_engine_dict::lookup("가").unwrap(); 5 | let client = Client::new(candidate_list).unwrap(); 6 | 7 | while !client.is_ready() {} 8 | 9 | if let Some(res) = client.close().unwrap() { 10 | println!("{:?}", res); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/engine/backends/emoji/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kime-engine-backend-emoji" 3 | version = "0.1.0" 4 | authors = ["Riey "] 5 | edition = "2021" 6 | license = "GPL-3.0-or-later" 7 | 8 | [dependencies] 9 | kime-engine-backend = { path = "../../backend" } 10 | kime-engine-backend-latin = { path = "../latin" } 11 | kime-engine-dict = { path = "../../dict" } 12 | -------------------------------------------------------------------------------- /src/engine/backends/math/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kime-engine-backend-math" 3 | version = "0.1.0" 4 | authors = ["Riey "] 5 | edition = "2021" 6 | license = "GPL-3.0-or-later" 7 | 8 | [dependencies] 9 | kime-engine-backend = { path = "../../backend" } 10 | kime-engine-backend-latin = { path = "../latin" } 11 | kime-engine-dict = { path = "../../dict" } 12 | -------------------------------------------------------------------------------- /src/engine/backends/hanja/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kime-engine-backend-hanja" 3 | version = "0.1.0" 4 | authors = ["Riey "] 5 | edition = "2021" 6 | license = "GPL-3.0-or-later" 7 | 8 | [dependencies] 9 | kime-engine-backend = { path = "../../backend" } 10 | kime-engine-dict = { path = "../../dict" } 11 | kime-engine-candidate = { path = "../../candidate" } 12 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": [ 3 | { 4 | "type": "shell", 5 | "command": [ 6 | "nix develop -c scripts/build.sh -ad || scripts/build.sh -ad" 7 | ], 8 | "problemMatcher": [ 9 | "$rustc" 10 | ], 11 | "group": "build", 12 | "label": "build all" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /src/tools/check/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kime-check" 3 | version = "0.1.0" 4 | authors = ["Riey "] 5 | edition = "2021" 6 | license = "GPL-3.0-or-later" 7 | 8 | [dependencies] 9 | ansi_term = "0.12.1" 10 | kime-engine-core = { path = "../../engine/core" } 11 | pad = "0.1.6" 12 | serde_yaml = "0.9" 13 | strum = { version = "0.24", features = ["derive"] } 14 | xdg = "2.2.0" 15 | -------------------------------------------------------------------------------- /src/engine/dict/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kime-engine-dict" 3 | version = "0.1.0" 4 | authors = ["Riey "] 5 | edition = "2021" 6 | license = "GPL-3.0-or-later" 7 | 8 | [build-dependencies] 9 | serde = {version = "1.0.118", features = ["derive"]} 10 | serde_json = "1.0" 11 | itertools = "0.13.0" 12 | quick-xml = { version = "0.27.1", features = ["encoding"] } 13 | unic = "0.9.0" 14 | -------------------------------------------------------------------------------- /src/tools/properties_writer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "properties_writer" 3 | version = "0.1.0" 4 | authors = ["Riey "] 5 | edition = "2021" 6 | license = "GPL-3.0-or-later" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | serde = { version = "1.0.130", features = ["derive"] } 12 | serde_json = "1.0.67" 13 | -------------------------------------------------------------------------------- /res/kime.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Exec=/usr/bin/kime-xdg-autostart 3 | Name=kime daemon 4 | Name[ko]=kime 데몬 5 | Comment=Start kime daemon 6 | Type=Application 7 | Terminal=false 8 | NoDisplay=true 9 | StartupNotify=false 10 | X-DBUS-StartupType=Unique 11 | X-GNOME-AutoRestart=false 12 | X-GNOME-Autostart-Notify=false 13 | X-KDE-StartupNotify=false 14 | X-KDE-Wayland-VirtualKeyboard=true 15 | Icon=kime-hangul-black 16 | -------------------------------------------------------------------------------- /src/engine/backend/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kime-engine-backend" 3 | version = "0.1.0" 4 | authors = ["Riey "] 5 | edition = "2021" 6 | license = "GPL-3.0-or-later" 7 | 8 | [dependencies] 9 | bitflags = "1.2.1" 10 | enum-map = "2" 11 | enumset = { version = "1", features = ["serde"] } 12 | serde = { version = "1", features = ["derive"] } 13 | strum = { version = "0.24", features = ["derive"] } 14 | -------------------------------------------------------------------------------- /src/engine/core/examples/print_type_sizes.rs: -------------------------------------------------------------------------------- 1 | use kime_engine_core::*; 2 | use std::mem::size_of; 3 | 4 | fn main() { 5 | println!("Engine: {}", size_of::()); 6 | println!("Config: {}", size_of::()); 7 | println!("Hotkey: {}", size_of::()); 8 | println!("Option: {}", size_of::>()); 9 | println!("KeyMap: {}", size_of::>()); 10 | } 11 | -------------------------------------------------------------------------------- /src/tools/candidate-window/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kime-candidate-window" 3 | version = "0.1.0" 4 | authors = ["Riey "] 5 | edition = "2021" 6 | license = "GPL-3.0-or-later" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | kime-engine-core = { path = "../../engine/core" } 12 | eframe = "0.20.1" 13 | egui = "0.20.1" 14 | -------------------------------------------------------------------------------- /src/tools/log/src/lib.rs: -------------------------------------------------------------------------------- 1 | use simplelog::*; 2 | 3 | pub use simplelog::LevelFilter; 4 | 5 | fn config() -> Config { 6 | ConfigBuilder::new() 7 | .set_level_padding(LevelPadding::Left) 8 | .set_time_level(LevelFilter::Trace) 9 | .build() 10 | } 11 | 12 | pub fn enable_logger(level: LevelFilter) -> bool { 13 | TermLogger::init(level, config(), TerminalMode::Stderr, ColorChoice::Auto).is_ok() 14 | } 15 | -------------------------------------------------------------------------------- /src/tools/indicator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kime-indicator" 3 | version = "0.1.0" 4 | authors = ["Riey "] 5 | edition = "2021" 6 | license = "GPL-3.0-or-later" 7 | 8 | [dependencies] 9 | kime-engine-core = { path = "../../engine/core" } 10 | kime-version = { path = "../version" } 11 | kime-run-dir = { path = "../run_dir" } 12 | 13 | anyhow = "1.0.38" 14 | pico-args = "0.5.0" 15 | log = "0.4.14" 16 | ksni = "0.2.0" 17 | -------------------------------------------------------------------------------- /src/engine/backend/src/input_result.rs: -------------------------------------------------------------------------------- 1 | bitflags::bitflags! { 2 | #[repr(transparent)] 3 | pub struct InputResult: u32 { 4 | const CONSUMED = 0b1; 5 | const LANGUAGE_CHANGED = 0b10; 6 | const HAS_PREEDIT = 0b100; 7 | const HAS_COMMIT = 0b1000; 8 | const NOT_READY = 0b10000; 9 | } 10 | } 11 | 12 | impl Default for InputResult { 13 | fn default() -> Self { 14 | Self::empty() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/frontends/gtk3/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(PkgConfig REQUIRED) 2 | pkg_check_modules(GTK3 gtk+-3.0) 3 | 4 | if(GTK3_FOUND) 5 | add_library(kime-gtk3 SHARED ./src/gtk.c ./src/immodule.c ./src/str_buf.c) 6 | 7 | target_include_directories(kime-gtk3 PRIVATE ${GTK3_INCLUDE_DIRS} ${KIME_INCLUDE}) 8 | target_link_directories(kime-gtk3 PRIVATE ${GTK3_LIBRARY_DIRS} ${KIME_LIB_DIRS}) 9 | target_link_libraries(kime-gtk3 ${GTK3_LIBRARIES} ${KIME_ENGINE}) 10 | endif() 11 | -------------------------------------------------------------------------------- /src/tools/kime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kime" 3 | version = "0.1.0" 4 | authors = ["Riey "] 5 | edition = "2021" 6 | license = "GPL-3.0-or-later" 7 | 8 | [dependencies] 9 | kime-engine-core = { path = "../../engine/core" } 10 | kime-version = { path = "../version" } 11 | kime-run-dir = { path = "../run_dir" } 12 | 13 | ctrlc = { version = "3.1.8", features = ["termination"] } 14 | daemonize = "0.5.0" 15 | log = "0.4.14" 16 | pico-args = "0.5.0" 17 | -------------------------------------------------------------------------------- /src/frontends/gtk4/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(PkgConfig REQUIRED) 2 | pkg_check_modules(GTK4 QUIET gtk4) 3 | 4 | if(GTK4_FOUND) 5 | add_library(kime-gtk4 SHARED src/gtk4.c ../gtk3/src/immodule.c ../gtk3/src/str_buf.c) 6 | 7 | target_include_directories(kime-gtk4 PRIVATE ${GTK4_INCLUDE_DIRS} ${KIME_INCLUDE}) 8 | target_link_directories(kime-gtk4 PRIVATE ${GTK4_LIBRARY_DIRS} ${KIME_LIB_DIRS}) 9 | target_link_libraries(kime-gtk4 PRIVATE ${GTK4_LIBRARIES} ${KIME_ENGINE}) 10 | endif() 11 | -------------------------------------------------------------------------------- /src/engine/backends/hangul/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kime-engine-backend-hangul" 3 | version = "0.1.0" 4 | authors = ["Riey "] 5 | edition = "2021" 6 | license = "GPL-3.0-or-later" 7 | 8 | [dependencies] 9 | kime-engine-backend = { path = "../../backend" } 10 | enumset = "1.0.6" 11 | num-derive = "0.3.3" 12 | num-traits = "0.2.14" 13 | serde = { version = "1.0.124", features = ["derive"] } 14 | serde_yaml = "0.9" 15 | 16 | [target.'cfg(unix)'.dependencies] 17 | xdg = "2.2.0" 18 | -------------------------------------------------------------------------------- /src/engine/capi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kime-engine-capi" 3 | description = "Kime engine library" 4 | version = "0.5.0" 5 | authors = ["Riey "] 6 | edition = "2021" 7 | license = "GPL-3.0-or-later" 8 | 9 | [lib] 10 | name = "kime_engine" 11 | crate-type = ["cdylib"] 12 | 13 | [dependencies] 14 | kime-engine-core = { path = "../core" } 15 | 16 | [build-dependencies] 17 | bindgen = { version = "0.69.4", default-features = false } 18 | cbindgen = { version = "0.26.0", default-features = false } 19 | -------------------------------------------------------------------------------- /nix/deps.nix: -------------------------------------------------------------------------------- 1 | { pkgs }: 2 | { 3 | kimeBuildInputs = with pkgs; [ 4 | dbus 5 | libdbusmenu 6 | 7 | xorg.libxcb 8 | libGL 9 | wayland 10 | libxkbcommon 11 | 12 | gtk3 13 | gtk4 14 | 15 | qt5.qtbase 16 | # qt6.qtbase 17 | ]; 18 | 19 | kimeNativeBuildInputs = with pkgs; [ 20 | python3 # xcb 0.9.0 21 | pkg-config 22 | llvmPackages_18.clang 23 | llvmPackages_18.libclang.lib 24 | llvmPackages_18.bintools 25 | rustc cargo 26 | cmake 27 | extra-cmake-modules 28 | ]; 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/engine/capi/cbindgen-c.toml: -------------------------------------------------------------------------------- 1 | autogen_warning = "/* DO NOT MODIFY THIS MANUALLY */" 2 | documentation = true 3 | pragma_once = true 4 | include_version = true 5 | line_length = 100 6 | tab_width = 2 7 | braces = "SameLine" 8 | language = "C" 9 | style = "both" 10 | 11 | [macro_expansion] 12 | bitflags = true 13 | 14 | [parse] 15 | parse_deps = true 16 | include = ["kime-engine-backend", "kime-engine-core", "kime-engine-config", "log"] 17 | 18 | [export] 19 | prefix = "Kime" 20 | 21 | [enum] 22 | enum_class = true 23 | 24 | [const] 25 | allow_static_const = true 26 | -------------------------------------------------------------------------------- /src/frontends/qt5/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20) 2 | 3 | set(CMAKE_AUTOMOC ON) 4 | set(CMAKE_AUTORCC ON) 5 | set(CMAKE_AUTOUIC ON) 6 | 7 | find_package(Qt5 5.1.0 QUIET COMPONENTS Gui QUIET) 8 | 9 | if(NOT Qt5_FOUND) 10 | return() 11 | endif() 12 | 13 | add_library(kime-qt5 SHARED src/plugin.cc src/input_context.cc) 14 | 15 | target_include_directories(kime-qt5 PRIVATE ${Qt5Gui_PRIVATE_INCLUDE_DIRS} ${KIME_INCLUDE}) 16 | target_link_directories(kime-qt5 PRIVATE ${KIME_LIB_DIRS}) 17 | target_link_libraries(kime-qt5 PRIVATE ${KIME_ENGINE} Qt5::Gui) 18 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Korean IME"; 3 | 4 | inputs = { 5 | nixpkgs.url = github:NixOS/nixpkgs; 6 | flake-utils.url = github:numtide/flake-utils; 7 | }; 8 | 9 | outputs = { self, nixpkgs, flake-utils }: 10 | flake-utils.lib.eachDefaultSystem 11 | (system: 12 | let pkgs = import nixpkgs { 13 | inherit system; 14 | }; in 15 | { 16 | devShells.default = import ./shell.nix { inherit pkgs; }; 17 | packages.default = import ./default.nix { inherit pkgs; }; 18 | } 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/tools/run_dir/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::PathBuf; 3 | 4 | pub fn get_run_dir() -> PathBuf { 5 | let path = get_run_dir_impl(); 6 | 7 | if !path.exists() { 8 | std::fs::create_dir(&path).ok(); 9 | } 10 | 11 | path 12 | } 13 | 14 | pub fn get_run_dir_impl() -> PathBuf { 15 | if let Ok(dir) = env::var("XDG_RUNTIME_DIR") { 16 | dir.into() 17 | } else if let Ok(uid) = env::var("UID") { 18 | PathBuf::from(format!("/tmp/kime-{}", uid)) 19 | } else { 20 | PathBuf::from("/tmp") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/engine/config/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kime-engine-config" 3 | version = "0.1.0" 4 | authors = ["Riey "] 5 | license = "GPL-3.0-or-later" 6 | edition = "2021" 7 | 8 | [features] 9 | config-serde = ["serde", "enumset/serde", "log/serde"] 10 | 11 | [dependencies] 12 | kime-engine-backend = { path = "../backend" } 13 | kime-engine-backend-hangul = { path = "../backends/hangul" } 14 | kime-engine-backend-latin = { path = "../backends/latin" } 15 | log = "0.4.14" 16 | serde = { version = "1.0.124", features = ["derive"], optional = true } 17 | enumset = "1.0.6" 18 | enum-map = "2" 19 | maplit = "1.0.2" 20 | -------------------------------------------------------------------------------- /src/engine/capi/cbindgen-cpp.toml: -------------------------------------------------------------------------------- 1 | autogen_warning = "/* DO NOT MODIFY THIS MANUALLY */" 2 | documentation = true 3 | pragma_once = true 4 | include_version = true 5 | line_length = 100 6 | tab_width = 2 7 | braces = "SameLine" 8 | language = "C++" 9 | namespace = "kime" 10 | style = "both" 11 | 12 | no_includes = true 13 | sys_includes = ["stdint.h", "stddef.h"] 14 | 15 | [macro_expansion] 16 | bitflags = true 17 | 18 | [parse] 19 | parse_deps = true 20 | include = ["kime-engine-backend", "kime-engine-core", "kime-engine-config", "log"] 21 | 22 | [const] 23 | allow_static_const = true 24 | allow_constexpr = true 25 | 26 | [export] 27 | # prefix = "Kime" 28 | -------------------------------------------------------------------------------- /src/frontends/qt6/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20) 2 | 3 | set(CMAKE_AUTOMOC ON) 4 | set(CMAKE_AUTORCC ON) 5 | set(CMAKE_AUTOUIC ON) 6 | 7 | find_package(Qt6 6.0.0 QUIET COMPONENTS Gui QUIET) 8 | find_package(Qt5 5.1.0 QUIET COMPONENTS Gui QUIET) 9 | 10 | if(NOT Qt6_FOUND) 11 | return() 12 | endif() 13 | 14 | add_library(kime-qt6 SHARED ../qt5/src/plugin.cc ../qt5/src/input_context.cc) 15 | 16 | target_include_directories(kime-qt6 PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS} ${Qt5Gui_PRIVATE_INCLUDE_DIRS} ${KIME_INCLUDE}) 17 | target_link_directories(kime-qt6 PRIVATE ${KIME_LIB_DIRS}) 18 | target_link_libraries(kime-qt6 PRIVATE ${KIME_ENGINE} Qt6::Gui) 19 | -------------------------------------------------------------------------------- /src/frontends/wayland/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kime-wayland" 3 | version = "0.1.0" 4 | authors = ["Riey "] 5 | edition = "2021" 6 | license = "GPL-3.0-or-later" 7 | 8 | [dependencies] 9 | kime-engine-core = { path = "../../engine/core" } 10 | kime-version = { path = "../../tools/version" } 11 | 12 | wayland-client = "0.29" 13 | wayland-protocols = { version = "0.29", features = [ 14 | "client", 15 | "unstable_protocols", 16 | ] } 17 | zwp-virtual-keyboard = "0.2.7" 18 | xkbcommon = { version = "0.7.0", features = ["wayland"] } 19 | 20 | libc = "0.2.82" 21 | log = "0.4.13" 22 | pico-args = "0.5.0" 23 | mio = { version = "0.7", features = ["os-ext"] } 24 | mio-timerfd = "0.2" 25 | -------------------------------------------------------------------------------- /src/engine/capi/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rerun-if-changed=./kime_engine.h"); 3 | println!("cargo:rerun-if-changed=./kime_engine.hpp"); 4 | println!("cargo:rerun-if-changed=../capi"); 5 | 6 | let c_binding = cbindgen::generate_with_config( 7 | "../capi", 8 | cbindgen::Config::from_file("../capi/cbindgen-c.toml").unwrap(), 9 | ) 10 | .unwrap(); 11 | 12 | c_binding.write_to_file("kime_engine.h"); 13 | 14 | let cpp_binding = cbindgen::generate_with_config( 15 | "../capi", 16 | cbindgen::Config::from_file("../capi/cbindgen-cpp.toml").unwrap(), 17 | ) 18 | .unwrap(); 19 | 20 | cpp_binding.write_to_file("kime_engine.hpp"); 21 | } 22 | -------------------------------------------------------------------------------- /src/frontends/qt5/src/plugin.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "kime-qt5.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | class KimePlatformInputContextPlugin : public QPlatformInputContextPlugin { 10 | Q_OBJECT 11 | Q_PLUGIN_METADATA(IID QPlatformInputContextFactoryInterface_iid FILE 12 | "kime.json") 13 | 14 | private: 15 | kime::InputEngine *engine = nullptr; 16 | kime::Config *config = nullptr; 17 | 18 | public: 19 | KimePlatformInputContextPlugin(); 20 | ~KimePlatformInputContextPlugin(); 21 | 22 | QPlatformInputContext *create(const QString &key, 23 | const QStringList ¶m_list) override; 24 | }; 25 | -------------------------------------------------------------------------------- /src/frontends/wayland/src/main.rs: -------------------------------------------------------------------------------- 1 | use wayland_client::{Display, GlobalManager}; 2 | 3 | fn main() { 4 | kime_version::cli_boilerplate!((),); 5 | 6 | let display = Display::connect_to_env().expect("Failed to connect wayland display"); 7 | let mut event_queue = display.create_event_queue(); 8 | let attached_display = display.attach(event_queue.token()); 9 | let globals = GlobalManager::new(&attached_display); 10 | 11 | event_queue.sync_roundtrip(&mut (), |_, _, _| ()).unwrap(); 12 | 13 | let result = kime_wayland::input_method_v2::run(&display, &mut event_queue, &globals); 14 | 15 | if let Err(_) = result { 16 | kime_wayland::input_method_v1::run(&display, &mut event_queue, &globals).unwrap(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "src/engine/capi", 5 | "src/engine/candidate", 6 | "src/engine/core", 7 | "src/engine/config", 8 | "src/engine/dict", 9 | 10 | "src/engine/backend", 11 | "src/engine/backends/emoji", 12 | "src/engine/backends/hangul", 13 | "src/engine/backends/hanja", 14 | "src/engine/backends/latin", 15 | "src/engine/backends/math", 16 | 17 | "src/frontends/wayland", 18 | "src/frontends/xim", 19 | 20 | "src/tools/candidate-window", 21 | "src/tools/check", 22 | "src/tools/indicator", 23 | "src/tools/kime", 24 | "src/tools/log", 25 | "src/tools/properties_writer", 26 | "src/tools/run_dir", 27 | "src/tools/version", 28 | ] 29 | 30 | [profile.release] 31 | lto = true 32 | -------------------------------------------------------------------------------- /src/frontends/gtk4/src/gtk4.c: -------------------------------------------------------------------------------- 1 | #include "../../gtk3/src/immodule.h" 2 | 3 | G_MODULE_EXPORT void g_io_module_load(GIOModule *module) { 4 | if (kime_api_version() != KimeKIME_API_VERSION) { 5 | return; 6 | } 7 | 8 | GTypeModule *type_module = G_TYPE_MODULE(module); 9 | g_type_module_use(type_module); 10 | 11 | if (!get_kime_ty()) { 12 | register_module(type_module); 13 | g_io_extension_point_implement(GTK_IM_MODULE_EXTENSION_POINT_NAME, 14 | get_kime_ty(), "kime", 50); 15 | } 16 | } 17 | 18 | G_MODULE_EXPORT char **g_io_module_query(void) { 19 | char *eps[] = { 20 | GTK_IM_MODULE_EXTENSION_POINT_NAME, 21 | NULL, 22 | }; 23 | return g_strdupv(eps); 24 | } 25 | 26 | G_MODULE_EXPORT void g_io_module_unload(GIOModule *module) {} 27 | -------------------------------------------------------------------------------- /src/frontends/xim/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kime-xim" 3 | description = "Kime XIM server" 4 | version = "0.3.0" 5 | authors = ["Riey "] 6 | edition = "2021" 7 | license = "GPL-3.0-or-later" 8 | 9 | [dependencies] 10 | kime-engine-core = { path = "../../engine/core" } 11 | kime-version = { path = "../../tools/version" } 12 | 13 | xim = { version = "0.2", default-features = false, features = ["x11rb-server"] } 14 | # xim = { path = "../../../../xim-rs", default-features = false, features = ["x11rb-server", "x11rb-xcb"] } 15 | 16 | ahash = "0.8" 17 | log = "0.4.11" 18 | x11rb = { version = "0.11.0", features = [ 19 | "render", 20 | "image", 21 | ], default-features = false } 22 | pico-args = "0.5.0" 23 | image = "0.24" 24 | imageproc = "0.23" 25 | rusttype = "0.9.2" 26 | -------------------------------------------------------------------------------- /src/frontends/gtk3/src/str_buf.c: -------------------------------------------------------------------------------- 1 | #include "./str_buf.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #define INIT_CAP 128 7 | 8 | StrBuf str_buf_new() { 9 | StrBuf buf; 10 | 11 | buf.ptr = (char *)calloc(INIT_CAP, 1); 12 | buf.len = 0; 13 | buf.cap = INIT_CAP; 14 | 15 | return buf; 16 | } 17 | 18 | void str_buf_delete(StrBuf *buf) { free(buf->ptr); } 19 | 20 | void str_buf_set_str(StrBuf *buf, KimeRustStr s) { 21 | if (s.len >= buf->cap) { 22 | buf->cap = s.len + 1; 23 | buf->ptr = realloc(buf->ptr, buf->cap); 24 | } 25 | 26 | memcpy(buf->ptr, s.ptr, s.len); 27 | buf->len = s.len; 28 | buf->ptr[s.len] = '\0'; 29 | } 30 | 31 | void str_buf_set_ch(StrBuf *buf, uint32_t ch) { 32 | gint len = g_unichar_to_utf8(ch, buf->ptr); 33 | buf->ptr[len] = '\0'; 34 | buf->len = len; 35 | } 36 | -------------------------------------------------------------------------------- /src/frontends/qt5/src/plugin.cc: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | #include "input_context.hpp" 3 | #include 4 | 5 | KimePlatformInputContextPlugin::KimePlatformInputContextPlugin() { 6 | if (kime::kime_api_version() != kime::KIME_API_VERSION) { 7 | throw "Kime Engine version is mismatched!\n"; 8 | } 9 | 10 | this->config = kime::kime_config_load(); 11 | this->engine = kime::kime_engine_new(this->config); 12 | } 13 | 14 | KimePlatformInputContextPlugin::~KimePlatformInputContextPlugin() { 15 | kime::kime_engine_delete(this->engine); 16 | kime::kime_config_delete(this->config); 17 | } 18 | 19 | QPlatformInputContext * 20 | KimePlatformInputContextPlugin::create(const QString &key, 21 | const QStringList ¶m_list) { 22 | return new KimeInputContext(this->engine, this->config); 23 | } 24 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs ? import { }, 3 | }: 4 | let 5 | deps = import ./nix/deps.nix { inherit pkgs; }; 6 | stdenv = pkgs.llvmPackages_18.stdenv; 7 | mkShell = (pkgs.mkShell.override { inherit stdenv; }); 8 | in 9 | pkgs.mkShell { 10 | name = "kime-shell"; 11 | dontUseCmakeConfigure = true; 12 | dontWrapQtApps = true; 13 | buildInputs = deps.kimeBuildInputs; 14 | nativeBuildInputs = deps.kimeNativeBuildInputs ++ (with pkgs; [ 15 | rustfmt 16 | pkgs.gedit 17 | llvmPackages_18.lldb 18 | ]); 19 | CMAKE_EXPORT_COMPILE_COMMANDS = 1; 20 | LIBCLANG_PATH = "${pkgs.llvmPackages_18.libclang.lib}/lib"; 21 | LD_LIBRARY_PATH = "./target/debug:${pkgs.wayland}/lib:${pkgs.libGL}/lib:${pkgs.libxkbcommon}/lib"; 22 | G_MESSAGES_DEBUG = "kime"; 23 | GTK_IM_MODULE = "kime"; 24 | GTK_IM_MODULE_FILE = builtins.toString ./.vscode/immodules.cache; 25 | RUST_BACKTRACE = 1; 26 | } 27 | 28 | -------------------------------------------------------------------------------- /scripts/release-deb.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | KIME_PREFIX=51_kime 4 | 5 | source $(dirname $0)/tool.sh 6 | 7 | if [ -z "$1" ]; then 8 | echo "Usage: " 9 | exit 1 10 | fi 11 | 12 | TARGET_PATH=$1 13 | TMP_PATH=$(mktemp -d) 14 | ARCH=$(dpkg --print-architecture) 15 | VER=$(cat ./VERSION) 16 | 17 | mkdir -pv $TMP_PATH/DEBIAN 18 | mkdir -pv $TMP_PATH/usr/share/im-config/data 19 | 20 | cat scripts/control.in | sed "s/%VER%/$VER/; s/%ARCH%/$ARCH/" > $TMP_PATH/DEBIAN/control 21 | cp scripts/im_kime.rc $TMP_PATH/usr/share/im-config/data/$KIME_PREFIX.rc 22 | cp scripts/im_kime.conf $TMP_PATH/usr/share/im-config/data/$KIME_PREFIX.conf 23 | 24 | KIME_INSTALL_HEADER=0 \ 25 | KIME_LIB_DIR=usr/lib/x86_64-linux-gnu \ 26 | KIME_QT5_DIR=usr/lib/x86_64-linux-gnu/qt5 \ 27 | KIME_QT6_DIR=usr/lib/x86_64-linux-gnu/qt6 \ 28 | scripts/install.sh $TMP_PATH 29 | 30 | dpkg-deb --root-owner-group --build $TMP_PATH "${TARGET_PATH}/kime_$ARCH.deb" 31 | -------------------------------------------------------------------------------- /src/engine/backends/hangul/src/layout.rs: -------------------------------------------------------------------------------- 1 | use crate::characters::KeyValue; 2 | use crate::Key; 3 | use kime_engine_backend::KeyMap; 4 | use std::collections::HashMap; 5 | 6 | #[derive(Clone, Default)] 7 | pub struct Layout { 8 | keymap: KeyMap, 9 | } 10 | 11 | impl Layout { 12 | pub fn from_items(items: HashMap) -> Self { 13 | let mut keymap = KeyMap::default(); 14 | 15 | for (key, value) in items { 16 | let value = match value.parse::() { 17 | Ok(value) => value, 18 | Err(_) => continue, 19 | }; 20 | 21 | keymap.insert(key, value); 22 | } 23 | 24 | Self { keymap } 25 | } 26 | 27 | pub fn load_from(content: &str) -> Result { 28 | Ok(Self::from_items(serde_yaml::from_str(content)?)) 29 | } 30 | 31 | #[inline] 32 | pub fn lookup_kv(&self, key: Key) -> Option { 33 | self.keymap.get(key) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/engine/dict/src/math_symbol_key.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{BitOr, BitOrAssign}; 2 | 3 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] 4 | pub struct Style(pub u8); 5 | 6 | impl BitOr for Style { 7 | type Output = Style; 8 | fn bitor(self, rhs: Style) -> Style { 9 | Style(self.0 | rhs.0) 10 | } 11 | } 12 | 13 | impl BitOrAssign for Style { 14 | fn bitor_assign(&mut self, rhs: Style) { 15 | *self = *self | rhs; 16 | } 17 | } 18 | 19 | impl Style { 20 | pub const NONE: Style = Style(0); 21 | pub const SF: Style = Style(1 << 0); 22 | pub const BF: Style = Style(1 << 1); 23 | pub const IT: Style = Style(1 << 2); 24 | pub const TT: Style = Style(1 << 3); 25 | pub const BB: Style = Style(1 << 4); 26 | pub const SCR: Style = Style(1 << 5); 27 | pub const CAL: Style = Style(1 << 6); 28 | pub const FRAK: Style = Style(1 << 7); 29 | } 30 | 31 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] 32 | pub struct SymbolKey<'a>(pub &'a str, pub Style); 33 | -------------------------------------------------------------------------------- /src/engine/core/tests/emoji.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod shared; 3 | 4 | define_layout_test!("dubeolsik", LatinLayout::Qwerty, InputCategory::Latin); 5 | 6 | use kime_engine_core::ModifierState; 7 | 8 | const EMOJI: Key = Key::new(E, ModifierState::CONTROL.union(ModifierState::ALT)); 9 | 10 | #[test] 11 | fn thinking() { 12 | test_input(&[ 13 | (EMOJI, "🏻(light skin tone)🏼(medium-light skin tone)🏽(medium skin tone)🏾(medium-dark skin tone)🏿(dark skin tone)", ""), 14 | (Key::normal(T), "t🏻(light skin tone)🏼(medium-light skin tone)🏽(medium skin tone)🏾(medium-dark skin tone)🏿(dark skin tone)", ""), 15 | (Key::normal(H), "th😃(grinning face with big eyes)😄(grinning face with smiling eyes)😁(beaming face with smiling eyes)😅(grinning face with sweat)🤣(rolling on the floor laughing)", ""), 16 | (Key::normal(I), "thi🤔(thinking face)🕧(twelve-thirty)🕜(one-thirty)🕝(two-thirty)🕞(three-thirty)", ""), 17 | (Key::normal(N), "thin🤔(thinking face)", ""), 18 | (Key::normal(K), "think🤔(thinking face)", ""), 19 | (Key::normal(Enter), "", "🤔"), 20 | ]); 21 | } 22 | -------------------------------------------------------------------------------- /src/engine/core/benches/call_key.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | use kime_engine_core::{Config, InputCategory, InputEngine, Key, KeyCode::*}; 3 | 4 | fn simple(c: &mut Criterion) { 5 | let config = Config::default(); 6 | 7 | c.bench_function("simple 100", |b| { 8 | let mut engine = InputEngine::new(&config); 9 | engine.set_input_category(InputCategory::Hangul); 10 | b.iter(|| { 11 | for _ in 0..100 { 12 | engine.press_key(Key::normal(A), &config); 13 | engine.press_key(Key::normal(Backspace), &config); 14 | } 15 | }) 16 | }); 17 | 18 | c.bench_function("simple 1000", |b| { 19 | let mut engine = InputEngine::new(&config); 20 | engine.set_input_category(InputCategory::Hangul); 21 | b.iter(|| { 22 | for _ in 0..1000 { 23 | engine.press_key(Key::normal(A), &config); 24 | engine.press_key(Key::normal(Backspace), &config); 25 | } 26 | }) 27 | }); 28 | } 29 | 30 | criterion_group!(benches, simple); 31 | criterion_main!(benches); 32 | -------------------------------------------------------------------------------- /src/engine/backends/latin/data/colemak.yaml: -------------------------------------------------------------------------------- 1 | Q: q 2 | S-Q: Q 3 | W: w 4 | S-W: W 5 | E: f 6 | S-E: F 7 | R: p 8 | S-R: P 9 | T: g 10 | S-T: G 11 | Y: j 12 | S-Y: J 13 | U: l 14 | S-U: L 15 | I: u 16 | S-I: U 17 | O: y 18 | S-O: Y 19 | P: ';' 20 | S-P: ':' 21 | A: a 22 | S-A: A 23 | S: r 24 | S-S: R 25 | D: s 26 | S-D: S 27 | F: t 28 | S-F: T 29 | G: d 30 | S-G: D 31 | H: h 32 | S-H: H 33 | J: n 34 | S-J: N 35 | K: e 36 | S-K: E 37 | L: i 38 | S-L: I 39 | SemiColon: o 40 | S-SemiColon: O 41 | Z: z 42 | S-Z: Z 43 | X: x 44 | S-X: X 45 | C: c 46 | S-C: C 47 | V: v 48 | S-V: V 49 | B: b 50 | S-B: B 51 | N: k 52 | S-N: K 53 | M: m 54 | S-M: M 55 | 56 | Grave: '`' 57 | S-Grave: '~' 58 | 1: 1 59 | 2: 2 60 | 3: 3 61 | 4: 4 62 | 5: 5 63 | 6: 6 64 | 7: 7 65 | 8: 8 66 | 9: 9 67 | 0: 0 68 | 69 | S-1: '!' 70 | S-2: '@' 71 | S-3: '#' 72 | S-4: '$' 73 | S-5: '%' 74 | S-6: '^' 75 | S-7: '&' 76 | S-8: '*' 77 | S-9: '(' 78 | S-0: ')' 79 | Minus: '-' 80 | S-Minus: _ 81 | Equal: '=' 82 | S-Equal: + 83 | Backslash: \ 84 | S-Backslash: '|' 85 | OpenBracket: '[' 86 | S-OpenBracket: '{' 87 | CloseBracket: ']' 88 | S-CloseBracket: '}' 89 | Quote: "'" 90 | S-Quote: '"' 91 | Comma: ',' 92 | S-Comma: '<' 93 | Period: '.' 94 | S-Period: '>' 95 | Slash: / 96 | S-Slash: '?' -------------------------------------------------------------------------------- /src/engine/backends/hangul/data/sebeolsik-3-91.yaml: -------------------------------------------------------------------------------- 1 | 1: $ㅎ 2 | S-1: $ㄲ 3 | 2: $ㅆ 4 | S-2: $ㄺ 5 | 3: $ㅂ 6 | S-3: $ㅈ 7 | S-4: $ㄿ 8 | S-5: $ㄾ 9 | Q: $ㅅ 10 | S-Q: $ㅍ 11 | W: $ㄹ 12 | S-W: $ㅌ 13 | S-E: $ㄵ 14 | S-R: $ㅀ 15 | S-T: $ㄽ 16 | A: $ㅇ 17 | S-A: $ㄷ 18 | S: $ㄴ 19 | S-S: $ㄶ 20 | S-D: $ㄼ 21 | S-F: $ㄻ 22 | Z: $ㅁ 23 | S-Z: $ㅊ 24 | X: $ㄱ 25 | S-X: $ㅄ 26 | S-C: $ㅋ 27 | S-V: $ㄳ 28 | 29 | 4: $ㅛ 30 | 5: $ㅠ 31 | 6: ㅑ 32 | 7: $ㅖ 33 | 8: $ㅢ 34 | 9: ㅜ 35 | Slash: ㅗ 36 | E: ㅕ 37 | R: $ㅐ 38 | T: $ㅓ 39 | D: $ㅣ 40 | F: $ㅏ 41 | G: ㅡ 42 | S-G: $ㅒ 43 | C: $ㅔ 44 | V: ㅗ 45 | B: ㅜ 46 | 47 | 0: ㅋ 48 | Y: ㄹ 49 | U: ㄷ 50 | I: ㅁ 51 | O: ㅊ 52 | P: ㅍ 53 | H: ㄴ 54 | J: ㅇ 55 | K: ㄱ 56 | L: ㅈ 57 | SemiColon: ㅂ 58 | Quote: ㅌ 59 | N: ㅅ 60 | M: ㅎ 61 | 62 | Grave: '*' 63 | S-Grave: ※ 64 | S-6: = 65 | S-7: “ 66 | S-8: ” 67 | S-9: "'" 68 | S-0: ~ 69 | Minus: ) 70 | S-Minus: ; 71 | Equal: '>' 72 | S-Equal: '+' 73 | Backslash: ':' 74 | S-Backslash: \ 75 | OpenBracket: ( 76 | S-OpenBracket: '%' 77 | CloseBracket: '<' 78 | S-CloseBracket: / 79 | S-Quote: · 80 | S-B: '?' 81 | S-N: '-' 82 | S-M: '"' 83 | S-Comma: ',' 84 | S-Period: '.' 85 | S-Slash: '!' 86 | 87 | S-Y: 5 88 | S-U: 6 89 | S-I: 7 90 | S-O: 8 91 | S-P: 9 92 | S-H: 0 93 | S-J: 1 94 | S-K: 2 95 | S-L: 3 96 | S-SemiColon: 4 97 | -------------------------------------------------------------------------------- /src/engine/backends/latin/data/qwerty.yaml: -------------------------------------------------------------------------------- 1 | Q: q 2 | S-Q: Q 3 | W: w 4 | S-W: W 5 | E: e 6 | S-E: E 7 | R: r 8 | S-R: R 9 | T: t 10 | S-T: T 11 | Y: y 12 | S-Y: Y 13 | U: u 14 | S-U: U 15 | I: i 16 | S-I: I 17 | O: o 18 | S-O: O 19 | P: p 20 | S-P: P 21 | A: a 22 | S-A: A 23 | S: s 24 | S-S: S 25 | D: d 26 | S-D: D 27 | F: f 28 | S-F: F 29 | G: g 30 | S-G: G 31 | H: h 32 | S-H: H 33 | J: j 34 | S-J: J 35 | K: k 36 | S-K: K 37 | L: l 38 | S-L: L 39 | Z: z 40 | S-Z: Z 41 | X: x 42 | S-X: X 43 | C: c 44 | S-C: C 45 | V: v 46 | S-V: V 47 | B: b 48 | S-B: B 49 | N: n 50 | S-N: N 51 | M: m 52 | S-M: M 53 | 54 | Grave: '`' 55 | S-Grave: '~' 56 | 1: 1 57 | 2: 2 58 | 3: 3 59 | 4: 4 60 | 5: 5 61 | 6: 6 62 | 7: 7 63 | 8: 8 64 | 9: 9 65 | 0: 0 66 | 67 | S-1: '!' 68 | S-2: '@' 69 | S-3: '#' 70 | S-4: '$' 71 | S-5: '%' 72 | S-6: '^' 73 | S-7: '&' 74 | S-8: '*' 75 | S-9: '(' 76 | S-0: ')' 77 | Minus: '-' 78 | S-Minus: _ 79 | Equal: '=' 80 | S-Equal: + 81 | Backslash: \ 82 | S-Backslash: '|' 83 | OpenBracket: '[' 84 | S-OpenBracket: '{' 85 | CloseBracket: ']' 86 | S-CloseBracket: '}' 87 | SemiColon: ; 88 | S-SemiColon: ':' 89 | Quote: "'" 90 | S-Quote: '"' 91 | Comma: ',' 92 | S-Comma: '<' 93 | Period: '.' 94 | S-Period: '>' 95 | Slash: / 96 | S-Slash: '?' 97 | -------------------------------------------------------------------------------- /src/engine/backends/hangul/data/sebeolsik-3-90.yaml: -------------------------------------------------------------------------------- 1 | 1: $ㅎ 2 | S-1: $ㅈ 3 | 2: $ㅆ 4 | 3: $ㅂ 5 | Q: $ㅅ 6 | S-Q: $ㅍ 7 | W: $ㄹ 8 | S-W: $ㅌ 9 | S-E: $ㅋ 10 | A: $ㅇ 11 | S-A: $ㄷ 12 | S: $ㄴ 13 | S-S: $ㄶ 14 | S-D: $ㄺ 15 | S-F: $ㄲ 16 | Z: $ㅁ 17 | S-Z: $ㅊ 18 | X: $ㄱ 19 | S-X: $ㅄ 20 | S-C: $ㄻ 21 | S-V: $ㅀ 22 | 23 | 4: $ㅛ 24 | 5: $ㅠ 25 | 6: ㅑ 26 | 7: $ㅖ 27 | 8: $ㅢ 28 | 9: ㅜ 29 | Slash: ㅗ 30 | E: ㅕ 31 | R: $ㅐ 32 | S-R: $ㅒ 33 | T: $ㅓ 34 | D: $ㅣ 35 | F: $ㅏ 36 | G: ㅡ 37 | C: $ㅔ 38 | V: ㅗ 39 | B: ㅜ 40 | 41 | 0: ㅋ 42 | Y: ㄹ 43 | U: ㄷ 44 | I: ㅁ 45 | O: ㅊ 46 | P: ㅍ 47 | H: ㄴ 48 | J: ㅇ 49 | K: ㄱ 50 | L: ㅈ 51 | SemiColon: ㅂ 52 | Quote: ㅌ 53 | N: ㅅ 54 | M: ㅎ 55 | 56 | Grave: '`' 57 | S-Grave: '~' 58 | S-2: '@' 59 | S-3: '#' 60 | S-4: '$' 61 | S-5: '%' 62 | S-6: '^' 63 | S-7: '&' 64 | S-8: '*' 65 | S-9: ( 66 | S-0: ) 67 | Minus: '-' 68 | S-Minus: _ 69 | Equal: '=' 70 | S-Equal: + 71 | Backslash: \ 72 | S-Backslash: '|' 73 | S-T: ; 74 | S-Y: '<' 75 | S-U: 7 76 | S-I: 8 77 | S-O: 9 78 | S-P: '>' 79 | OpenBracket: '[' 80 | S-OpenBracket: '{' 81 | CloseBracket: ']' 82 | S-CloseBracket: '}' 83 | S-G: / 84 | S-H: "'" 85 | S-J: 4 86 | S-K: 5 87 | S-L: 6 88 | S-SemiColon: ':' 89 | S-Quote: '"' 90 | S-B: '!' 91 | S-N: 0 92 | S-M: 1 93 | S-Comma: 2 94 | S-Period: 3 95 | S-Slash: '?' 96 | -------------------------------------------------------------------------------- /src/frontends/qt5/src/input_context.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "kime-qt5.hpp" 4 | 5 | #include 6 | #include 7 | 8 | class KimeEventFilter; 9 | 10 | class KimeInputContext : public QPlatformInputContext { 11 | Q_OBJECT 12 | 13 | public: 14 | KimeInputContext(kime::InputEngine *engine, const kime::Config *config); 15 | 16 | bool isValid() const override; 17 | Qt::LayoutDirection inputDirection() const override; 18 | 19 | void reset() override; 20 | void commit() override; 21 | void update(Qt::InputMethodQueries queries) override; 22 | void invokeAction(QInputMethod::Action action, int cursorPosition) override; 23 | bool filterEvent(const QEvent *event) override; 24 | void setFocusObject(QObject *object) override; 25 | 26 | private: 27 | void commit_str(kime::RustStr s); 28 | void preedit_str(kime::RustStr s); 29 | bool process_input_result(kime::InputResult ret); 30 | 31 | bool visible = false; 32 | bool engine_ready = true; 33 | QList attributes; 34 | const kime::Config *config = nullptr; 35 | kime::InputEngine *engine = nullptr; 36 | QObject *focus_object = nullptr; 37 | KimeEventFilter *filter = nullptr; 38 | }; 39 | -------------------------------------------------------------------------------- /src/engine/backends/latin/data/dvorak.yaml: -------------------------------------------------------------------------------- 1 | Q: "'" 2 | S-Q: '"' 3 | W: \, 4 | S-W: \< 5 | E: \. 6 | S-E: \> 7 | R: p 8 | S-R: P 9 | T: y 10 | S-T: Y 11 | Y: f 12 | S-Y: F 13 | U: g 14 | S-U: G 15 | I: c 16 | S-I: C 17 | O: r 18 | S-O: R 19 | P: l 20 | S-P: L 21 | A: a 22 | S-A: A 23 | S: o 24 | S-S: O 25 | D: e 26 | S-D: E 27 | F: u 28 | S-F: U 29 | G: i 30 | S-G: I 31 | H: d 32 | S-H: D 33 | J: h 34 | S-J: H 35 | K: t 36 | S-K: T 37 | L: n 38 | S-L: N 39 | Z: ':' 40 | S-Z: \; 41 | X: q 42 | S-X: Q 43 | C: j 44 | S-C: J 45 | V: k 46 | S-V: K 47 | B: x 48 | S-B: X 49 | N: b 50 | S-N: B 51 | M: m 52 | S-M: M 53 | 54 | Grave: '`' 55 | S-Grave: '~' 56 | 1: 1 57 | 2: 2 58 | 3: 3 59 | 4: 4 60 | 5: 5 61 | 6: 6 62 | 7: 7 63 | 8: 8 64 | 9: 9 65 | 0: 0 66 | 67 | S-1: '!' 68 | S-2: '@' 69 | S-3: '#' 70 | S-4: '$' 71 | S-5: '%' 72 | S-6: '^' 73 | S-7: '&' 74 | S-8: '*' 75 | S-9: '(' 76 | S-0: ')' 77 | Minus: '[' 78 | S-Minus: '{' 79 | Equal: ']' 80 | S-Equal: '}' 81 | Backslash: \ 82 | S-Backslash: '|' 83 | OpenBracket: '/' 84 | S-OpenBracket: '?' 85 | CloseBracket: '=' 86 | S-CloseBracket: '+' 87 | SemiColon: 's' 88 | S-SemiColon: 'S' 89 | Quote: '-' 90 | S-Quote: '_' 91 | Comma: 'w' 92 | S-Comma: 'W' 93 | Period: 'v' 94 | S-Period: 'V' 95 | Slash: z 96 | S-Slash: Z 97 | -------------------------------------------------------------------------------- /src/engine/core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kime-engine-core" 3 | version = "2.0.0" 4 | authors = ["Riey "] 5 | edition = "2021" 6 | license = "GPL-3.0-or-later" 7 | 8 | [dependencies] 9 | kime-engine-config = { path = "../config", features = ["config-serde"] } 10 | kime-engine-backend = { path = "../backend" } 11 | kime-engine-backend-hangul = { path = "../backends/hangul" } 12 | kime-engine-backend-hanja = { path = "../backends/hanja", optional = true } 13 | kime-engine-backend-latin = { path = "../backends/latin" } 14 | kime-engine-backend-math = { path = "../backends/math", optional = true } 15 | kime-engine-backend-emoji = { path = "../backends/emoji", optional = true } 16 | serde_yaml = "0.9" 17 | parking_lot = "0.12" 18 | fontdb = { version = "0.11.2", features = ["fontconfig"] } 19 | 20 | [target.'cfg(unix)'.dependencies] 21 | xdg = "2.2.0" 22 | kime-run-dir = { path = "../../tools/run_dir" } 23 | 24 | [dev-dependencies] 25 | criterion = "0.4" 26 | pretty_assertions = "1.0.0" 27 | 28 | [[bench]] 29 | name = "call_key" 30 | harness = false 31 | 32 | [features] 33 | default = ["hanja", "math", "emoji"] 34 | hanja = ["dep:kime-engine-backend-hanja"] 35 | math = ["dep:kime-engine-backend-math"] 36 | emoji = ["dep:kime-engine-backend-emoji"] 37 | -------------------------------------------------------------------------------- /src/engine/backends/hangul/data/sebeolsik-3sin-1995.yaml: -------------------------------------------------------------------------------- 1 | Q: $ㅅ$ㅢ 2 | S-Q: $ㅢ 3 | W: $ㄹ$ㅑ 4 | S-W: $ㅑ 5 | E: $ㅂ$ㅕ 6 | S-E: $ㅕ 7 | R: $ㄷ$ㅐ 8 | S-R: $ㅐ 9 | T: $ㅌ$ㅓ 10 | S-T: $ㅓ 11 | A: $ㅇ$ㅒ 12 | S-A: $ㅒ 13 | S: $ㄴ$ㅖ 14 | S-S: $ㅖ 15 | D: $ㅎ$ㅣ 16 | S-D: $ㅣ 17 | F: $ㅈ$ㅏ 18 | S-F: $ㅏ 19 | G: $ㅍ$ㅡ 20 | S-G: $ㅡ 21 | Z: $ㅁ$ㅠ 22 | S-Z: $ㅠ 23 | X: $ㄱ$ㅛ 24 | S-X: $ㅛ 25 | C: $ㅊ$ㅔ 26 | S-C: $ㅔ 27 | V: $ㅋ$ㅗ 28 | S-V: $ㅗ 29 | B: $ㅆ$ㅜ 30 | S-B: $ㅜ 31 | 32 | Y: ㄹ 33 | S-Y: ㄹ 34 | U: ㄷ 35 | S-U: ㄷ 36 | I: ㅁㅜ 37 | S-I: ㅜ 38 | O: ㅊㅜ 39 | S-O: ㅜ 40 | P: ㅍㅗ 41 | S-P: ㅗ 42 | H: ㄴ 43 | S-H: ㄴ 44 | J: ㅇ 45 | S-J: ; 46 | K: ㄱ 47 | S-K: "'" 48 | L: ㅈ 49 | S-L: ㅈ 50 | SemiColon: ㅂ 51 | S-SemiColon: ':' 52 | Quote: ㅌ 53 | S-Quote: '"' 54 | N: ㅅ 55 | S-N: ㅅ 56 | M: ㅎ 57 | S-M: / 58 | Slash: ㅋ 59 | S-Slash: '?' 60 | 61 | Grave: '`' 62 | S-Grave: '~' 63 | 1: 1 64 | 2: 2 65 | 3: 3 66 | 4: 4 67 | 5: 5 68 | 6: 6 69 | 7: 7 70 | 8: 8 71 | 9: 9 72 | 0: 0 73 | 74 | S-1: '!' 75 | S-2: '@' 76 | S-3: '#' 77 | S-4: '$' 78 | S-5: '%' 79 | S-6: '^' 80 | S-7: '&' 81 | S-8: '*' 82 | S-9: '(' 83 | S-0: ')' 84 | Minus: '-' 85 | S-Minus: _ 86 | Equal: '=' 87 | S-Equal: + 88 | Backslash: \ 89 | S-Backslash: '|' 90 | OpenBracket: '[' 91 | S-OpenBracket: '{' 92 | CloseBracket: ']' 93 | S-CloseBracket: '}' 94 | Comma: ',' 95 | S-Comma: '<' 96 | Period: '.' 97 | S-Period: '>' -------------------------------------------------------------------------------- /src/frontends/gtk3/src/gtk.c: -------------------------------------------------------------------------------- 1 | #include "./immodule.h" 2 | 3 | static const GtkIMContextInfo INFO = { 4 | .context_id = "kime", 5 | .context_name = "Kime (Korean IME)", 6 | .domain = "kime", 7 | .domain_dirname = "/usr/share/locale", 8 | .default_locales = "ko:*", 9 | }; 10 | static const GtkIMContextInfo *INFOS[] = {&INFO}; 11 | 12 | G_MODULE_EXPORT const gchar *g_module_check_init(GModule *module) { 13 | if (kime_api_version() == KimeKIME_API_VERSION) { 14 | return NULL; 15 | } else { 16 | return "Engine version mismatched"; 17 | } 18 | } 19 | 20 | G_MODULE_EXPORT void im_module_exit(void) {} 21 | 22 | G_MODULE_EXPORT void im_module_init(GTypeModule *type_module) { 23 | g_type_module_use(type_module); 24 | register_module(type_module); 25 | } 26 | 27 | G_MODULE_EXPORT void im_module_list(const GtkIMContextInfo ***contexts, 28 | int *n_contexts) { 29 | *contexts = INFOS; 30 | *n_contexts = G_N_ELEMENTS(INFOS); 31 | } 32 | 33 | G_MODULE_EXPORT GtkIMContext *im_module_create(const gchar *context_id) { 34 | if (g_strcmp0(context_id, "kime") == 0) { 35 | GType ty = get_kime_ty(); 36 | if (ty) { 37 | gpointer obj = g_object_new(ty, NULL); 38 | return GTK_IM_CONTEXT(obj); 39 | } 40 | } 41 | 42 | return NULL; 43 | } 44 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs ? import { }, 3 | debug ? false, 4 | }: 5 | let 6 | src = ./.; 7 | deps = import ./nix/deps.nix { inherit pkgs; }; 8 | kimeVersion = builtins.readFile ./VERSION; 9 | testArgs = if debug then "" else "--release"; 10 | inherit (pkgs) llvmPackages_18 rustPlatform qt5; 11 | in 12 | llvmPackages_18.stdenv.mkDerivation { 13 | name = "kime"; 14 | inherit src; 15 | buildInputs = deps.kimeBuildInputs; 16 | nativeBuildInputs = deps.kimeNativeBuildInputs ++ [ rustPlatform.cargoSetupHook ]; 17 | version = kimeVersion; 18 | cargoDeps = rustPlatform.fetchCargoTarball { 19 | inherit src; 20 | #hash = "0000000000000000000000000000000000000000000000000000"; 21 | hash = "sha256-2MG6xigiKdvQX8PR457d6AXswTRPRJBPERvZqemjv24="; 22 | }; 23 | LIBCLANG_PATH = "${llvmPackages_18.libclang.lib}/lib"; 24 | dontUseCmakeConfigure = true; 25 | dontWrapQtApps = true; 26 | buildPhase = if debug then "bash scripts/build.sh -ad" else "bash scripts/build.sh -ar"; 27 | installPhase = '' 28 | KIME_BIN_DIR=bin \ 29 | KIME_INSTALL_HEADER=1 \ 30 | KIME_INCLUDE_DIR=include \ 31 | KIME_ICON_DIR=share/icons \ 32 | KIME_LIB_DIR=lib \ 33 | KIME_DOC_DIR=share/doc/kime \ 34 | KIME_QT5_DIR=lib/qt-${qt5.qtbase.version} \ 35 | bash scripts/install.sh "$out" 36 | ''; 37 | doCheck = true; 38 | checkPhase = '' 39 | cargo test ${testArgs} 40 | ''; 41 | } 42 | 43 | -------------------------------------------------------------------------------- /src/tools/version/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[doc(hidden)] 2 | pub use kime_log; 3 | 4 | #[doc(hidden)] 5 | pub mod build { 6 | pub const VERSION: &str = include_str!("../../../../VERSION"); 7 | } 8 | 9 | #[macro_export] 10 | macro_rules! cli_boilerplate { 11 | ($ok:expr, $($help:expr,)*) => {{ 12 | let mut args = pico_args::Arguments::from_env(); 13 | 14 | if args.contains(["-h", "--help"]) { 15 | println!("-h or --help: show help"); 16 | println!("-v or --version: show version"); 17 | println!("--log : set logging level"); 18 | $( 19 | println!($help); 20 | )* 21 | return $ok; 22 | } 23 | 24 | if args.contains(["-v", "--version"]) { 25 | $crate::print_version!(); 26 | return $ok; 27 | } 28 | 29 | let log_level = args.opt_value_from_str("--log") 30 | .ok() 31 | .flatten() 32 | .unwrap_or_else(|| { 33 | kime_engine_core::load_raw_config_from_config_dir().log.global_level 34 | }); 35 | $crate::kime_log::enable_logger(log_level); 36 | 37 | args 38 | }}; 39 | } 40 | 41 | #[macro_export] 42 | macro_rules! print_version { 43 | () => { 44 | println!("kime {}", $crate::build::VERSION); 45 | println!("`{}` {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")); 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /src/engine/backends/hangul/data/sebeolsik-3sin-p2.yaml: -------------------------------------------------------------------------------- 1 | # 아래아를 비롯한 옛한글은 아직 지원하지 않습니다. 2 | 3 | Q: $ㅅ$ㅒ 4 | S-Q: $ㅒ 5 | W: $ㄹ$ㅑ 6 | S-W: $ㅑ 7 | E: $ㅂ$ㅐ 8 | S-E: $ㅐ 9 | R: $ㅌ$ㅓ 10 | S-R: $ㅓ 11 | T: $ㅋ$ㅕ 12 | S-T: $ㅕ 13 | A: $ㅇ$ㅠ 14 | S-A: $ㅠ 15 | S: $ㄴ$ㅖ 16 | S-S: $ㅖ 17 | D: $ㅎ$ㅣ 18 | S-D: $ㅣ 19 | F: $ㅍ$ㅏ 20 | S-F: $ㅏ 21 | G: $ㄷ$ㅡ 22 | S-G: $ㅡ 23 | Z: $ㅁㅢ 24 | S-Z: $ㅢ 25 | X: $ㅆ$ㅛ 26 | S-X: $ㅛ 27 | C: $ㄱ$ㅔ 28 | S-C: $ㅔ 29 | V: $ㅈ$ㅗ 30 | S-V: $ㅗ 31 | B: $ㅊ$ㅜ 32 | S-B: $ㅜ 33 | 34 | Y: ㄹ 35 | S-Y: × 36 | U: ㄷ 37 | S-U: ○ 38 | I: ㅁㅡ 39 | S-I: ㅡ 40 | O: ㅊㅜ 41 | S-O: ㅜ 42 | P: ㅍ 43 | S-P: ; 44 | H: ㄴ 45 | S-H: ㄴ 46 | J: ㅇ 47 | S-J: "'" 48 | K: ㄱ 49 | S-K: "\"" 50 | L: ㅈ 51 | S-L: · 52 | SemiColon: ㅂ 53 | S-SemiColon: ':' 54 | Quote: ㅌ 55 | S-Quote: / 56 | N: ㅅ 57 | S-N: ― 58 | M: ㅎ 59 | S-M: … 60 | Slash: ㅋㅗ 61 | S-Slash: '?' 62 | 63 | Grave: '`' 64 | S-Grave: '~' 65 | 1: 1 66 | 2: 2 67 | 3: 3 68 | 4: 4 69 | 5: 5 70 | 6: 6 71 | 7: 7 72 | 8: 8 73 | 9: 9 74 | 0: 0 75 | 76 | S-1: '!' 77 | S-2: '@' 78 | S-3: '#' 79 | S-4: '$' 80 | S-5: '%' 81 | S-6: '^' 82 | S-7: '&' 83 | S-8: '*' 84 | S-9: '(' 85 | S-0: ')' 86 | Minus: '-' 87 | S-Minus: _ 88 | Equal: '=' 89 | S-Equal: + 90 | Backslash: \ 91 | S-Backslash: '|' 92 | OpenBracket: '[' 93 | S-OpenBracket: '{' 94 | CloseBracket: ']' 95 | S-CloseBracket: '}' 96 | Comma: ',' 97 | S-Comma: '<' 98 | Period: '.' 99 | S-Period: '>' 100 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13.0) 2 | 3 | project(kime VERSION 2.0.0) 4 | 5 | option(USE_SYSTEM_ENGINE "Use system engine file" OFF) 6 | option(ENABLE_GTK3 "Enable GTK3 immodule" OFF) 7 | option(ENABLE_GTK4 "Enable GTK4 immodule" OFF) 8 | option(ENABLE_QT5 "Enable Qt5 immodule" OFF) 9 | option(ENABLE_QT6 "Enable Qt6 immodule" OFF) 10 | 11 | set(CMAKE_SKIP_RPATH TRUE) 12 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -fvisibility=hidden") 13 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fvisibility=hidden") 14 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DDEBUG") 15 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") 16 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 17 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 18 | set(KIME_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/engine/capi) 19 | if(${USE_SYSTEM_ENGINE}) 20 | set(KIME_LIB_DIRS "") 21 | else() 22 | set(KIME_LIB_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/../target/debug ${CMAKE_CURRENT_SOURCE_DIR}/../target/release) 23 | endif() 24 | set(KIME_ENGINE kime_engine) 25 | set(OpenGL_GL_PREFERENCE GLVND) 26 | 27 | include(GNUInstallDirs) 28 | 29 | if(${ENABLE_GTK3}) 30 | add_subdirectory(frontends/gtk3) 31 | endif() 32 | if(${ENABLE_GTK4}) 33 | add_subdirectory(frontends/gtk4) 34 | endif() 35 | 36 | if(${ENABLE_QT5}) 37 | add_subdirectory(frontends/qt5) 38 | endif() 39 | if(${ENABLE_QT6}) 40 | add_subdirectory(frontends/qt6) 41 | endif() 42 | -------------------------------------------------------------------------------- /src/engine/backends/hangul/data/dubeolsik.yaml: -------------------------------------------------------------------------------- 1 | Q: ㅂ$ㅂ 2 | S-Q: ㅃ 3 | 4 | W: ㅈ$ㅈ 5 | S-W: ㅉ 6 | 7 | E: ㄷ$ㄷ 8 | S-E: ㄸ 9 | 10 | R: ㄱ$ㄱ 11 | S-R: ㄲ$ㄲ 12 | 13 | T: ㅅ$ㅅ 14 | S-T: ㅆ$ㅆ 15 | 16 | Y: ㅛ 17 | S-Y: ㅛ 18 | 19 | U: ㅕ 20 | S-U: ㅕ 21 | 22 | I: ㅑ 23 | S-I: ㅑ 24 | 25 | O: ㅐ 26 | S-O: ㅒ 27 | 28 | P: ㅔ 29 | S-P: ㅖ 30 | 31 | A: ㅁ$ㅁ 32 | S-A: ㅁ$ㅁ 33 | 34 | S: ㄴ$ㄴ 35 | S-S: ㄴ$ㄴ 36 | 37 | D: ㅇ$ㅇ 38 | S-D: ㅇ$ㅇ 39 | 40 | F: ㄹ$ㄹ 41 | S-F: ㄹ$ㄹ 42 | 43 | G: ㅎ$ㅎ 44 | S-G: ㅎ$ㅎ 45 | 46 | H: ㅗ 47 | S-H: ㅗ 48 | 49 | J: ㅓ 50 | S-J: ㅓ 51 | 52 | K: ㅏ 53 | S-K: ㅏ 54 | 55 | L: ㅣ 56 | S-L: ㅣ 57 | 58 | Z: ㅋ$ㅋ 59 | S-Z: ㅋ$ㅋ 60 | 61 | X: ㅌ$ㅌ 62 | S-X: ㅌ$ㅌ 63 | 64 | C: ㅊ$ㅊ 65 | S-C: ㅊ$ㅊ 66 | 67 | V: ㅍ$ㅍ 68 | S-V: ㅍ$ㅍ 69 | 70 | B: ㅠ 71 | S-B: ㅠ 72 | 73 | N: ㅜ 74 | S-N: ㅜ 75 | 76 | M: ㅡ 77 | S-M: ㅡ 78 | 79 | Grave: '`' 80 | S-Grave: '~' 81 | 1: 1 82 | 2: 2 83 | 3: 3 84 | 4: 4 85 | 5: 5 86 | 6: 6 87 | 7: 7 88 | 8: 8 89 | 9: 9 90 | 0: 0 91 | 92 | S-1: '!' 93 | S-2: '@' 94 | S-3: '#' 95 | S-4: '$' 96 | S-5: '%' 97 | S-6: '^' 98 | S-7: '&' 99 | S-8: '*' 100 | S-9: '(' 101 | S-0: ')' 102 | Minus: '-' 103 | S-Minus: _ 104 | Equal: '=' 105 | S-Equal: + 106 | Backslash: \ 107 | S-Backslash: '|' 108 | OpenBracket: '[' 109 | S-OpenBracket: '{' 110 | CloseBracket: ']' 111 | S-CloseBracket: '}' 112 | SemiColon: ; 113 | S-SemiColon: ':' 114 | Quote: "'" 115 | S-Quote: '"' 116 | Comma: ',' 117 | S-Comma: '<' 118 | Period: '.' 119 | S-Period: '>' 120 | Slash: / 121 | S-Slash: '?' 122 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "build-script-build": "cpp", 4 | "qtglobal": "cpp", 5 | "*.tcc": "cpp", 6 | "chrono": "cpp", 7 | "string_view": "cpp", 8 | "variant": "cpp", 9 | "qkeyevent": "cpp", 10 | "deque": "cpp", 11 | "list": "cpp", 12 | "string": "cpp", 13 | "unordered_map": "cpp", 14 | "unordered_set": "cpp", 15 | "vector": "cpp", 16 | "system_error": "cpp", 17 | "cstdarg": "cpp", 18 | "cstdlib": "cpp", 19 | "cstdint": "cpp", 20 | "stdio.h": "c", 21 | "immodule.h": "c", 22 | "gtkimmodule.h": "c", 23 | "gtkx.h": "c", 24 | "kime_engine.h": "c", 25 | "eventi.h": "c", 26 | "str_buf.h": "c", 27 | "qapplication": "cpp", 28 | "qabstractnativeeventfilter": "cpp", 29 | "memory": "cpp", 30 | "qtextcharformat": "cpp", 31 | "qmouseevent": "cpp", 32 | "qcoreapplication": "cpp", 33 | "qmetaenum": "cpp", 34 | "random": "cpp", 35 | "optional": "cpp" 36 | }, 37 | "yaml.customTags": [ 38 | "!Mode scalar", 39 | "!Toggle sequence", 40 | "!Switch scalar", 41 | ], 42 | "C_Cpp.errorSquiggles": "Disabled", 43 | "cmake.sourceDirectory": "${workspaceFolder}/src", 44 | "rust-analyzer.procMacro.enable": true, 45 | "cmake.configureOnOpen": true, 46 | "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", 47 | "rust-analyzer.cargo.buildScripts.enable": true 48 | } -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'develop' 7 | - 'releases/*' 8 | pull_request: 9 | branches: 10 | - 'develop' 11 | 12 | env: 13 | CARGO_TERM_COLOR: always 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-24.04 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: cachix/install-nix-action@v18 22 | with: 23 | github_access_token: ${{ secrets.GITHUB_TOKEN }} 24 | - name: Caching cargo 25 | uses: actions/cache@v4 26 | with: 27 | path: | 28 | ~/.cargo/registry 29 | ~/.cargo/git 30 | target 31 | key: ${{ runner.os }}-nix-rustc-v1-1.55.0-${{ hashFiles('Cargo.lock') }} 32 | restore-keys: | 33 | ${{ runner.os }}-nix-rustc-v1-1.55.0- 34 | - run: nix develop -c bash scripts/build.sh -ad 35 | - run: nix develop -c cargo test 36 | 37 | cargo-deny: 38 | runs-on: ubuntu-24.04 39 | strategy: 40 | matrix: 41 | checks: 42 | - bans licenses sources 43 | 44 | steps: 45 | - uses: actions/checkout@v4 46 | - uses: EmbarkStudios/cargo-deny-action@v1 47 | with: 48 | command: check ${{ matrix.checks }} 49 | 50 | format: 51 | runs-on: ubuntu-24.04 52 | 53 | steps: 54 | - uses: actions/checkout@v4 55 | - name: Install Rustfmt 56 | run: rustup component add rustfmt 57 | - name: Check Rustfmt 58 | run: cargo fmt -- --check 59 | 60 | spell-check: 61 | runs-on: ubuntu-24.04 62 | steps: 63 | - uses: actions/checkout@v4 64 | - uses: crate-ci/typos@master 65 | -------------------------------------------------------------------------------- /src/engine/core/tests/sebeolsik_3_91.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod shared; 3 | 4 | define_layout_test!("sebeolsik-3-91"); 5 | 6 | #[test] 7 | fn hello() { 8 | test_input(&[ 9 | (Key::normal(J), "ㅇ", ""), 10 | (Key::normal(F), "아", ""), 11 | (Key::normal(S), "안", ""), 12 | (Key::normal(H), "ㄴ", "안"), 13 | (Key::normal(E), "녀", ""), 14 | (Key::normal(A), "녕", ""), 15 | ]); 16 | } 17 | 18 | #[test] 19 | fn switch_next() { 20 | test_input(&[ 21 | (Key::normal(S), "ㄴ", ""), 22 | (Key::normal(J), "ㅇ", "ㄴ"), 23 | (Key::normal(F), "아", ""), 24 | ]); 25 | } 26 | 27 | #[test] 28 | fn good() { 29 | test_input(&[ 30 | (Key::normal(K), "ㄱ", ""), 31 | (Key::normal(R), "개", ""), 32 | (Key::normal(K), "ㄱ", "개"), 33 | (Key::normal(K), "ㄲ", ""), 34 | (Key::normal(B), "꾸", ""), 35 | (Key::normal(W), "꿀", ""), 36 | ]); 37 | } 38 | 39 | // https://github.com/Riey/kime/issues/521 40 | #[test] 41 | fn issue_521() { 42 | test_input(&[ 43 | (Key::normal(J), "ㅇ", ""), 44 | (Key::normal(F), "아", ""), 45 | (Key::shift(F), "앎", ""), 46 | ]); 47 | } 48 | 49 | #[test] 50 | fn colon() { 51 | test_input(&[(Key::normal(Backslash), "", ":")]); 52 | } 53 | 54 | // https://github.com/Riey/kime/issues/520 55 | #[test] 56 | fn flexible_compose_check_jungseong() { 57 | test_input_with_addon( 58 | &[ 59 | (Key::normal(N), "ㅅ", ""), 60 | (Key::normal(F), "사", ""), 61 | (Key::normal(N), "ㅅ", "사"), 62 | ], 63 | Addon::FlexibleComposeOrder | Addon::ComposeChoseongSsang, 64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1731533236, 9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1747505133, 24 | "narHash": "sha256-ONJblEhnYJGctOd2z2AmHBp//OKajQL+yUDoFAWQcvo=", 25 | "owner": "NixOS", 26 | "repo": "nixpkgs", 27 | "rev": "c5cac24ea8edf485ff43144c8494c5309b613546", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "NixOS", 32 | "repo": "nixpkgs", 33 | "type": "github" 34 | } 35 | }, 36 | "root": { 37 | "inputs": { 38 | "flake-utils": "flake-utils", 39 | "nixpkgs": "nixpkgs" 40 | } 41 | }, 42 | "systems": { 43 | "locked": { 44 | "lastModified": 1681028828, 45 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 46 | "owner": "nix-systems", 47 | "repo": "default", 48 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 49 | "type": "github" 50 | }, 51 | "original": { 52 | "owner": "nix-systems", 53 | "repo": "default", 54 | "type": "github" 55 | } 56 | } 57 | }, 58 | "root": "root", 59 | "version": 7 60 | } 61 | -------------------------------------------------------------------------------- /src/engine/candidate/src/client.rs: -------------------------------------------------------------------------------- 1 | use nix::poll; 2 | use std::fmt; 3 | use std::io::{self, BufWriter, Write}; 4 | use std::os::unix::io::{AsRawFd, RawFd}; 5 | use std::process::{Child, Stdio}; 6 | 7 | pub const CANDIDATE_PROCESS_NAME: &str = "kime-candidate-window"; 8 | 9 | pub struct Client { 10 | child: Child, 11 | stdout_fd: RawFd, 12 | } 13 | 14 | impl Client { 15 | pub fn new(candidate_list: &[(&str, &str)]) -> io::Result { 16 | let mut child = std::process::Command::new(CANDIDATE_PROCESS_NAME) 17 | .stdin(Stdio::piped()) 18 | .stdout(Stdio::piped()) 19 | .stderr(Stdio::inherit()) 20 | .spawn()?; 21 | 22 | let mut stdin = BufWriter::new(child.stdin.take().unwrap()); 23 | 24 | for (key, value) in candidate_list { 25 | stdin.write_all(key.as_bytes())?; 26 | stdin.write_all(b"\n")?; 27 | stdin.write_all(value.as_bytes())?; 28 | stdin.write_all(b"\n")?; 29 | } 30 | 31 | stdin.flush()?; 32 | 33 | drop(stdin); 34 | 35 | let stdout_fd = child.stdout.as_ref().unwrap().as_raw_fd(); 36 | 37 | Ok(Self { stdout_fd, child }) 38 | } 39 | 40 | pub fn is_ready(&self) -> bool { 41 | let fds = &mut [poll::PollFd::new(self.stdout_fd, poll::PollFlags::POLLIN)]; 42 | poll::poll(fds, 200) == Ok(1) 43 | } 44 | 45 | pub fn close(mut self) -> io::Result> { 46 | if self.is_ready() { 47 | Ok(String::from_utf8(self.child.wait_with_output()?.stdout).ok()) 48 | } else { 49 | self.child.kill()?; 50 | Ok(None) 51 | } 52 | } 53 | } 54 | 55 | impl fmt::Debug for Client { 56 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 57 | write!(f, "CandidateClient") 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/frontends/wayland/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::time::Instant; 2 | 3 | pub mod input_method_v1; 4 | pub mod input_method_v2; 5 | 6 | #[derive(Clone, Copy)] 7 | pub struct RepeatInfo { 8 | /// The rate of repeating keys in characters per second 9 | rate: i32, 10 | /// Delay in milliseconds since key down until repeating starts 11 | delay: i32, 12 | } 13 | 14 | #[derive(Clone, Copy)] 15 | pub enum PressState { 16 | /// User is pressing no key, or user lifted last pressed key. But kime-wayland is ready for key 17 | /// long-press. 18 | NotPressing, 19 | /// User is pressing a key. 20 | Pressing { 21 | /// User started pressing a key at this moment. 22 | pressed_at: Instant, 23 | /// `false` if user just started pressing a key. Soon, key repeating will be begin. `true` 24 | /// if user have pressed a key for a long enough time, key repeating is happening right 25 | /// now. 26 | is_repeating: bool, 27 | 28 | /// Key code used by wayland 29 | key: u32, 30 | /// Timestamp with millisecond granularity used by wayland. Their base is undefined, so 31 | /// they can't be compared against system time (as obtained with clock_gettime or 32 | /// gettimeofday). They can be compared with each other though, and for instance be used to 33 | /// identify sequences of button presses as double or triple clicks. 34 | /// 35 | /// #### Reference 36 | /// - https://wayland.freedesktop.org/docs/html/ch04.html#sect-Protocol-Input 37 | wayland_time: u32, 38 | }, 39 | } 40 | 41 | impl PressState { 42 | pub fn is_pressing(&self, query_key: u32) -> bool { 43 | if let PressState::Pressing { key, .. } = self { 44 | *key == query_key 45 | } else { 46 | false 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/engine/backend/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod input_result; 2 | mod keycode; 3 | mod keymap; 4 | 5 | pub use keycode::{Key, KeyCode, ModifierState}; 6 | pub use keymap::KeyMap; 7 | 8 | pub use input_result::InputResult; 9 | 10 | pub trait InputEngineBackend { 11 | type ConfigData; 12 | 13 | /// Press key 14 | /// # Return 15 | /// `true` means key has handled 16 | fn press_key(&mut self, config: &Self::ConfigData, key: Key, commit_buf: &mut String) -> bool; 17 | /// Clear current preedit string this function may change commit string 18 | fn clear_preedit(&mut self, commit_buf: &mut String); 19 | /// Clear engine state 20 | fn reset(&mut self); 21 | /// Get preedit string 22 | fn preedit_str(&self, buf: &mut String); 23 | /// Is have preedit 24 | fn has_preedit(&self) -> bool; 25 | } 26 | 27 | pub enum InputEngineModeResult { 28 | Continue(T), 29 | ExitHandled(T), 30 | Exit, 31 | } 32 | 33 | pub trait InputEngineMode { 34 | type ConfigData; 35 | 36 | /// Press key 37 | /// # Return 38 | /// `Ok(true)` means key has handled 39 | fn press_key( 40 | &mut self, 41 | config: &Self::ConfigData, 42 | key: Key, 43 | commit_buf: &mut String, 44 | ) -> InputEngineModeResult; 45 | /// Clear current preedit string this function may change commit string 46 | fn clear_preedit(&mut self, commit_buf: &mut String) -> InputEngineModeResult<()>; 47 | /// Clear engine state 48 | fn reset(&mut self) -> InputEngineModeResult<()>; 49 | /// Get preedit string 50 | fn preedit_str(&self, buf: &mut String); 51 | /// Is have preedit 52 | fn has_preedit(&self) -> bool; 53 | /// Is now ready 54 | fn check_ready(&self) -> bool { 55 | true 56 | } 57 | /// End ready state 58 | #[allow(unused_variables)] 59 | fn end_ready(&mut self, commit_buf: &mut String) -> InputEngineModeResult<()> { 60 | InputEngineModeResult::Continue(()) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/engine/core/tests/sebeolsik_3sin_1995.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod shared; 3 | 4 | define_layout_test!("sebeolsik-3sin-1995"); 5 | 6 | #[test] 7 | fn simple() { 8 | test_input(&[(Key::normal(U), "ㄷ", ""), (Key::normal(Q), "듸", "")]); 9 | } 10 | 11 | #[test] 12 | fn mu() { 13 | test_input(&[(Key::normal(I), "ㅁ", ""), (Key::normal(I), "무", "")]); 14 | } 15 | 16 | #[test] 17 | fn hello() { 18 | test_input(&[ 19 | (Key::normal(J), "ㅇ", ""), 20 | (Key::normal(F), "아", ""), 21 | (Key::normal(S), "안", ""), 22 | (Key::normal(H), "ㄴ", "안"), 23 | (Key::normal(E), "녀", ""), 24 | (Key::normal(A), "녕", ""), 25 | (Key::normal(M), "ㅎ", "녕"), 26 | (Key::normal(F), "하", ""), 27 | (Key::normal(N), "ㅅ", "하"), 28 | (Key::normal(C), "세", ""), 29 | (Key::normal(J), "ㅇ", "세"), 30 | (Key::normal(X), "요", ""), 31 | ]); 32 | } 33 | 34 | // issue #295 35 | #[test] 36 | fn compose_jungseong() { 37 | test_input(&[ 38 | (Key::normal(J), "ㅇ", ""), 39 | (Key::normal(O), "우", ""), 40 | (Key::normal(C), "웨", ""), 41 | (Key::normal(S), "웬", ""), 42 | (Key::normal(J), "ㅇ", "웬"), 43 | (Key::normal(P), "오", ""), 44 | (Key::normal(D), "외", ""), 45 | ]); 46 | } 47 | 48 | #[test] 49 | fn dont_compose_jungseong() { 50 | test_input(&[ 51 | (Key::normal(J), "ㅇ", ""), 52 | (Key::normal(B), "우", ""), 53 | (Key::normal(C), "웇", ""), 54 | (Key::normal(J), "ㅇ", "웇"), 55 | (Key::normal(V), "오", ""), 56 | (Key::normal(D), "옿", ""), 57 | ]); 58 | } 59 | 60 | #[test] 61 | fn chocolate() { 62 | test_input(&[ 63 | (Key::normal(J), "ㅇ", ""), 64 | (Key::normal(O), "우", ""), 65 | (Key::normal(C), "웨", ""), 66 | (Key::normal(S), "웬", ""), 67 | (Key::normal(O), "ㅊ", "웬"), 68 | (Key::normal(V), "초", ""), 69 | (Key::normal(Slash), "ㅋ", "초"), 70 | (Key::normal(V), "코", ""), 71 | (Key::normal(W), "콜", ""), 72 | (Key::normal(Y), "ㄹ", "콜"), 73 | (Key::normal(D), "리", ""), 74 | (Key::normal(Q), "릿", ""), 75 | ]); 76 | } 77 | -------------------------------------------------------------------------------- /src/frontends/xim/src/main.rs: -------------------------------------------------------------------------------- 1 | use x11rb::{ 2 | connection::Connection, 3 | protocol::{ErrorKind, Event}, 4 | }; 5 | use xim::{x11rb::HasConnection, XimConnections}; 6 | 7 | mod handler; 8 | mod pe_window; 9 | 10 | fn main() { 11 | kime_version::cli_boilerplate!((),); 12 | 13 | let config = kime_engine_core::load_engine_config_from_config_dir().unwrap_or_default(); 14 | 15 | let (conn, screen_num) = 16 | x11rb::rust_connection::RustConnection::connect(None).expect("Connect X"); 17 | let mut server = xim::x11rb::X11rbServer::init(conn, screen_num, "kime", xim::ALL_LOCALES) 18 | .expect("Init XIM server"); 19 | let mut connections = XimConnections::new(); 20 | let mut handler = self::handler::KimeHandler::new(screen_num, config); 21 | 22 | loop { 23 | let e = server.conn().wait_for_event().expect("Wait event"); 24 | match server.filter_event(&e, &mut connections, &mut handler) { 25 | // event has filtered 26 | Ok(true) => {} 27 | // event hasn't filtered 28 | Ok(false) => match e { 29 | Event::Expose(e) => { 30 | handler.expose(e.window, server.conn()).unwrap(); 31 | server.conn().flush().expect("Flush connection"); 32 | } 33 | Event::ConfigureNotify(e) => { 34 | handler.configure_notify(e, server.conn()).unwrap(); 35 | server.conn().flush().expect("Flush connection"); 36 | } 37 | Event::UnmapNotify(..) => {} 38 | Event::DestroyNotify(..) => {} 39 | Event::MappingNotify(..) => {} 40 | Event::Error(x11rb::x11_utils::X11Error { 41 | error_kind: ErrorKind::RenderPicture, 42 | .. 43 | }) => {} 44 | e => { 45 | log::trace!("Unfiltered event: {:?}", e); 46 | } 47 | }, 48 | Err(err) => { 49 | // Don't stop server just logging 50 | log::error!("ServerError occurred while process event: {}", err); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/engine/backends/emoji/src/lib.rs: -------------------------------------------------------------------------------- 1 | use kime_engine_backend::{ 2 | InputEngineMode, 3 | InputEngineModeResult::{self, Continue, Exit, ExitHandled}, 4 | Key, KeyCode, 5 | }; 6 | use kime_engine_backend_latin::LatinData; 7 | 8 | #[derive(Clone)] 9 | pub struct EmojiMode { 10 | buf: String, 11 | } 12 | 13 | impl EmojiMode { 14 | pub fn new() -> Self { 15 | Self { 16 | buf: String::with_capacity(16), 17 | } 18 | } 19 | } 20 | 21 | impl InputEngineMode for EmojiMode { 22 | type ConfigData = LatinData; 23 | 24 | fn press_key( 25 | &mut self, 26 | config: &LatinData, 27 | key: Key, 28 | _commit_buf: &mut String, 29 | ) -> InputEngineModeResult { 30 | if key.code == KeyCode::Backspace { 31 | if self.buf.pop().is_some() { 32 | Continue(true) 33 | } else { 34 | Exit 35 | } 36 | } else if key == Key::normal(KeyCode::Space) { 37 | self.buf.push(' '); 38 | Continue(true) 39 | } else if let Some(ch) = config.lookup(key) { 40 | self.buf.push(ch); 41 | Continue(true) 42 | } else { 43 | Continue(false) 44 | } 45 | } 46 | 47 | fn clear_preedit(&mut self, commit_buf: &mut String) -> InputEngineModeResult<()> { 48 | if !self.buf.is_empty() { 49 | if let Some(anno) = kime_engine_dict::search_unicode_annotations(&self.buf).next() { 50 | commit_buf.push_str(anno.codepoint); 51 | } 52 | self.buf.clear(); 53 | } 54 | 55 | ExitHandled(()) 56 | } 57 | 58 | fn reset(&mut self) -> InputEngineModeResult<()> { 59 | self.buf.clear(); 60 | ExitHandled(()) 61 | } 62 | 63 | fn preedit_str(&self, buf: &mut String) { 64 | buf.push_str(&self.buf); 65 | for anno in kime_engine_dict::search_unicode_annotations(&self.buf).take(5) { 66 | buf.push_str(anno.codepoint); 67 | buf.push('('); 68 | buf.push_str(anno.tts); 69 | buf.push(')'); 70 | } 71 | } 72 | 73 | fn has_preedit(&self) -> bool { 74 | true 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/engine/backends/hanja/src/lib.rs: -------------------------------------------------------------------------------- 1 | use kime_engine_backend::{ 2 | InputEngineMode, 3 | InputEngineModeResult::{self, Continue, Exit, ExitHandled}, 4 | Key, 5 | }; 6 | 7 | use kime_engine_candidate::client::Client; 8 | 9 | #[derive(Debug)] 10 | pub struct HanjaMode { 11 | client: Option, 12 | } 13 | 14 | impl Default for HanjaMode { 15 | fn default() -> Self { 16 | Self::new() 17 | } 18 | } 19 | 20 | impl HanjaMode { 21 | pub fn new() -> Self { 22 | Self { client: None } 23 | } 24 | 25 | pub fn set_key(&mut self, key: &str) -> bool { 26 | if let Some(entries) = kime_engine_dict::lookup(key) { 27 | match Client::new(entries) { 28 | Ok(client) => { 29 | self.client = Some(client); 30 | true 31 | } 32 | Err(_err) => false, 33 | } 34 | } else { 35 | false 36 | } 37 | } 38 | } 39 | 40 | impl InputEngineMode for HanjaMode { 41 | type ConfigData = (); 42 | 43 | fn press_key(&mut self, _: &(), _: Key, _: &mut String) -> InputEngineModeResult { 44 | self.reset(); 45 | 46 | Exit 47 | } 48 | 49 | fn preedit_str(&self, _: &mut String) {} 50 | 51 | fn clear_preedit(&mut self, _: &mut String) -> InputEngineModeResult<()> { 52 | Continue(()) 53 | } 54 | 55 | fn reset(&mut self) -> InputEngineModeResult<()> { 56 | self.client.take().and_then(|c| c.close().ok()); 57 | 58 | ExitHandled(()) 59 | } 60 | 61 | fn has_preedit(&self) -> bool { 62 | true 63 | } 64 | 65 | fn check_ready(&self) -> bool { 66 | self.client.as_ref().map(Client::is_ready).unwrap_or(true) 67 | } 68 | 69 | fn end_ready(&mut self, commit_buf: &mut String) -> InputEngineModeResult<()> { 70 | match self.client.take() { 71 | Some(client) => { 72 | if let Ok(Some(res)) = client.close() { 73 | commit_buf.push_str(&res); 74 | ExitHandled(()) 75 | } else { 76 | Exit 77 | } 78 | } 79 | None => Exit, 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/engine/core/tests/sebeolsik_3sin_p2.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod shared; 3 | 4 | define_layout_test!("sebeolsik-3sin-p2"); 5 | 6 | #[test] 7 | fn simple() { 8 | test_input(&[(Key::normal(U), "ㄷ", ""), (Key::normal(Z), "듸", "")]); 9 | } 10 | 11 | #[test] 12 | fn cu() { 13 | test_input(&[(Key::normal(O), "ㅊ", ""), (Key::normal(O), "추", "")]); 14 | } 15 | 16 | #[test] 17 | fn double_jongseong() { 18 | test_input(&[(Key::normal(T), "ㅋ", ""), (Key::normal(T), "ㅋ", "ㅋ")]); 19 | } 20 | 21 | #[test] 22 | fn hello() { 23 | test_input(&[ 24 | (Key::normal(J), "ㅇ", ""), 25 | (Key::normal(F), "아", ""), 26 | (Key::normal(S), "안", ""), 27 | (Key::normal(H), "ㄴ", "안"), 28 | (Key::normal(T), "녀", ""), 29 | (Key::normal(A), "녕", ""), 30 | (Key::normal(M), "ㅎ", "녕"), 31 | (Key::normal(F), "하", ""), 32 | (Key::normal(N), "ㅅ", "하"), 33 | (Key::normal(C), "세", ""), 34 | (Key::normal(J), "ㅇ", "세"), 35 | (Key::normal(X), "요", ""), 36 | ]); 37 | } 38 | 39 | #[test] 40 | fn compose_jungseong() { 41 | test_input(&[ 42 | (Key::normal(J), "ㅇ", ""), 43 | (Key::normal(O), "우", ""), 44 | (Key::normal(C), "웨", ""), 45 | (Key::normal(S), "웬", ""), 46 | (Key::normal(J), "ㅇ", "웬"), 47 | (Key::normal(Slash), "오", ""), 48 | (Key::normal(D), "외", ""), 49 | ]); 50 | } 51 | 52 | #[test] 53 | fn dont_compose_jungseong() { 54 | test_input(&[ 55 | (Key::normal(J), "ㅇ", ""), 56 | (Key::normal(B), "우", ""), 57 | (Key::normal(B), "웇", ""), 58 | (Key::normal(J), "ㅇ", "웇"), 59 | (Key::normal(V), "오", ""), 60 | (Key::normal(D), "옿", ""), 61 | ]); 62 | } 63 | 64 | #[test] 65 | fn chocolate() { 66 | test_input(&[ 67 | (Key::normal(J), "ㅇ", ""), 68 | (Key::normal(O), "우", ""), 69 | (Key::normal(C), "웨", ""), 70 | (Key::normal(S), "웬", ""), 71 | (Key::normal(O), "ㅊ", "웬"), 72 | (Key::normal(V), "초", ""), 73 | (Key::normal(Slash), "ㅋ", "초"), 74 | (Key::normal(V), "코", ""), 75 | (Key::normal(W), "콜", ""), 76 | (Key::normal(Y), "ㄹ", "콜"), 77 | (Key::normal(D), "리", ""), 78 | (Key::normal(Q), "릿", ""), 79 | ]); 80 | } 81 | -------------------------------------------------------------------------------- /res/default_config.yaml: -------------------------------------------------------------------------------- 1 | daemon: 2 | modules: 3 | - Xim 4 | - Wayland 5 | - Indicator 6 | indicator: 7 | icon_color: Black 8 | log: 9 | global_level: DEBUG 10 | engine: 11 | translation_layer: null 12 | default_category: Latin 13 | global_category_state: false 14 | global_hotkeys: 15 | M-C-Backslash: 16 | behavior: !Mode Math 17 | result: ConsumeIfProcessed 18 | Super-Space: 19 | behavior: !Toggle 20 | - Hangul 21 | - Latin 22 | result: Consume 23 | M-C-E: 24 | behavior: !Mode Emoji 25 | result: ConsumeIfProcessed 26 | Esc: 27 | behavior: !Switch Latin 28 | result: Bypass 29 | Muhenkan: 30 | behavior: !Toggle 31 | - Hangul 32 | - Latin 33 | result: Consume 34 | AltR: 35 | behavior: !Toggle 36 | - Hangul 37 | - Latin 38 | result: Consume 39 | M-AltR: 40 | behavior: !Toggle 41 | - Hangul 42 | - Latin 43 | result: Consume 44 | Hangul: 45 | behavior: !Toggle 46 | - Hangul 47 | - Latin 48 | result: Consume 49 | category_hotkeys: 50 | Hangul: 51 | ControlR: 52 | behavior: !Mode Hanja 53 | result: Consume 54 | HangulHanja: 55 | behavior: !Mode Hanja 56 | result: Consume 57 | F9: 58 | behavior: !Mode Hanja 59 | result: ConsumeIfProcessed 60 | mode_hotkeys: 61 | Math: 62 | Enter: 63 | behavior: Commit 64 | result: ConsumeIfProcessed 65 | Tab: 66 | behavior: Commit 67 | result: ConsumeIfProcessed 68 | Hanja: 69 | Enter: 70 | behavior: Commit 71 | result: ConsumeIfProcessed 72 | Tab: 73 | behavior: Commit 74 | result: ConsumeIfProcessed 75 | Emoji: 76 | Enter: 77 | behavior: Commit 78 | result: ConsumeIfProcessed 79 | Tab: 80 | behavior: Commit 81 | result: ConsumeIfProcessed 82 | candidate_font: Noto Sans CJK KR 83 | xim_preedit_font: 84 | - Noto Sans CJK KR 85 | - 15.0 86 | latin: 87 | layout: Qwerty 88 | preferred_direct: true 89 | hangul: 90 | layout: dubeolsik 91 | word_commit: false 92 | preedit_johab: Needed 93 | addons: 94 | all: 95 | - ComposeChoseongSsang 96 | dubeolsik: 97 | - TreatJongseongAsChoseong 98 | -------------------------------------------------------------------------------- /src/engine/core/tests/math.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod shared; 3 | 4 | define_layout_test!("dubeolsik", LatinLayout::Qwerty, InputCategory::Latin); 5 | 6 | use kime_engine_core::ModifierState; 7 | 8 | const MATH: Key = Key::new(Backslash, ModifierState::CONTROL.union(ModifierState::ALT)); 9 | 10 | #[test] 11 | fn twice_backspace() { 12 | test_input(&[ 13 | (MATH, "", ""), 14 | (Key::normal(Backslash), "\\", ""), 15 | (Key::normal(Backslash), "", "\\"), 16 | ]); 17 | } 18 | 19 | #[test] 20 | fn pi() { 21 | test_input(&[ 22 | (MATH, "", ""), 23 | (Key::normal(Backslash), "\\", ""), 24 | (Key::normal(P), "\\p", ""), 25 | (Key::normal(I), "\\pi", ""), 26 | (Key::normal(Tab), "", "π"), 27 | (Key::normal(Backslash), "\\", ""), 28 | (Key::shift(P), "\\P", ""), 29 | (Key::normal(I), "\\Pi", ""), 30 | (Key::normal(Tab), "", "Π"), 31 | ]); 32 | } 33 | 34 | #[test] 35 | fn space() { 36 | test_input(&[ 37 | (MATH, "", ""), 38 | (Key::normal(Backslash), "\\", ""), 39 | (Key::shift(Comma), "\\<", ""), 40 | (Key::shift(Comma), "\\<<", ""), 41 | (Key::normal(Space), "", "⟪PASS"), 42 | ]); 43 | } 44 | 45 | #[test] 46 | fn backspace() { 47 | test_input(&[ 48 | (MATH, "", ""), 49 | (Key::normal(Backslash), "\\", ""), 50 | (Key::normal(P), "\\p", ""), 51 | (Key::normal(I), "\\pi", ""), 52 | (Key::normal(Backspace), "\\p", ""), 53 | (Key::normal(Backspace), "\\", ""), 54 | (Key::normal(Backspace), "", ""), 55 | ]) 56 | } 57 | 58 | // issue #379 59 | #[test] 60 | fn esc() { 61 | test_input(&[ 62 | (MATH, "", ""), 63 | (Key::normal(Esc), "", "PASS"), 64 | (Key::normal(Backslash), "", "PASS"), 65 | ]); 66 | } 67 | 68 | #[test] 69 | fn style() { 70 | test_input(&[ 71 | (MATH, "", ""), 72 | (Key::normal(Backslash), "\\", ""), 73 | (Key::normal(B), "\\b", ""), 74 | (Key::normal(F), "\\bf", ""), 75 | (Key::normal(I), "\\bfi", ""), 76 | (Key::normal(T), "\\bfit", ""), 77 | (Key::normal(Period), "\\bfit.", ""), 78 | (Key::normal(A), "\\bfit.a", ""), 79 | (Key::normal(L), "\\bfit.al", ""), 80 | (Key::normal(P), "\\bfit.alp", ""), 81 | (Key::normal(H), "\\bfit.alph", ""), 82 | (Key::normal(A), "\\bfit.alpha", ""), 83 | (Key::normal(Tab), "", "𝜶"), 84 | ]) 85 | } 86 | -------------------------------------------------------------------------------- /src/engine/backends/latin/src/lib.rs: -------------------------------------------------------------------------------- 1 | use kime_engine_backend::{InputEngineBackend, Key, KeyMap}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Serialize, Deserialize)] 5 | pub enum LatinLayout { 6 | Qwerty, 7 | Dvorak, 8 | Colemak, 9 | } 10 | 11 | impl Default for LatinLayout { 12 | fn default() -> Self { 13 | Self::Qwerty 14 | } 15 | } 16 | 17 | #[derive(Serialize, Deserialize)] 18 | #[serde(default)] 19 | pub struct LatinConfig { 20 | pub layout: LatinLayout, 21 | pub preferred_direct: bool, 22 | } 23 | 24 | impl Default for LatinConfig { 25 | fn default() -> Self { 26 | Self { 27 | layout: LatinLayout::Qwerty, 28 | preferred_direct: true, 29 | } 30 | } 31 | } 32 | 33 | pub struct LatinData { 34 | keymap: KeyMap, 35 | } 36 | 37 | impl LatinData { 38 | pub fn new(config: &LatinConfig) -> Self { 39 | Self { 40 | keymap: load_layout(config), 41 | } 42 | } 43 | 44 | #[inline] 45 | pub fn lookup(&self, key: Key) -> Option { 46 | self.keymap.get(key) 47 | } 48 | } 49 | 50 | fn load_layout(config: &LatinConfig) -> KeyMap { 51 | let layout = match config.layout { 52 | LatinLayout::Qwerty => include_str!("../data/qwerty.yaml"), 53 | LatinLayout::Dvorak => include_str!("../data/dvorak.yaml"), 54 | LatinLayout::Colemak => include_str!("../data/colemak.yaml"), 55 | }; 56 | serde_yaml::from_str(layout).unwrap_or_default() 57 | } 58 | 59 | #[derive(Clone)] 60 | pub struct LatinEngine { 61 | preferred_direct: bool, 62 | } 63 | 64 | impl LatinEngine { 65 | pub fn new(preferred_direct: bool) -> Self { 66 | Self { preferred_direct } 67 | } 68 | } 69 | 70 | impl InputEngineBackend for LatinEngine { 71 | type ConfigData = LatinData; 72 | 73 | fn press_key(&mut self, config: &LatinData, key: Key, commit_buf: &mut String) -> bool { 74 | if self.preferred_direct { 75 | false 76 | } else { 77 | if let Some(ch) = config.lookup(key) { 78 | commit_buf.push(ch); 79 | true 80 | } else { 81 | false 82 | } 83 | } 84 | } 85 | 86 | fn clear_preedit(&mut self, _commit_buf: &mut String) {} 87 | fn reset(&mut self) {} 88 | 89 | fn has_preedit(&self) -> bool { 90 | false 91 | } 92 | 93 | fn preedit_str(&self, _buf: &mut String) {} 94 | } 95 | -------------------------------------------------------------------------------- /NOTICE.md: -------------------------------------------------------------------------------- 1 | # kime - Korean IME 2 | 3 | Copyright (C) 2020-2021 Riey 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | 18 | # Unihan_Readings.txt 19 | 20 | Unicode Character Database 21 | © 2021 Unicode®, Inc. 22 | Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. 23 | For terms of use, see http://www.unicode.org/terms_of_use.html 24 | For documentation, see http://www.unicode.org/reports/tr38 25 | 26 | # (freq-)hanja.txt 27 | 28 | Copyright (c) 2005,2006 Choe Hwanjin 29 | All rights reserved. 30 | 31 | Redistribution and use in source and binary forms, with or without 32 | modification, are permitted provided that the following conditions are met: 33 | 34 | 1. Redistributions of source code must retain the above copyright notice, 35 | this list of conditions and the following disclaimer. 36 | 2. Redistributions in binary form must reproduce the above copyright notice, 37 | this list of conditions and the following disclaimer in the documentation 38 | and/or other materials provided with the distribution. 39 | 3. Neither the name of the author nor the names of its contributors 40 | may be used to endorse or promote products derived from this software 41 | without specific prior written permission. 42 | 43 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 44 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 45 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 46 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 47 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 48 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 49 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 50 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 51 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 52 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 53 | POSSIBILITY OF SUCH DAMAGE. 54 | 55 | -------------------------------------------------------------------------------- /src/engine/dict/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod math_symbol_key; 2 | mod dict { 3 | include!(concat!(env!("OUT_DIR"), "/dict.rs")); 4 | } 5 | 6 | pub use dict::UnicodeAnnotation; 7 | use math_symbol_key::*; 8 | 9 | #[cfg(test)] 10 | mod tests { 11 | #[test] 12 | fn simple() { 13 | assert_eq!(crate::lookup("가").unwrap()[0].0, "可"); 14 | } 15 | 16 | #[test] 17 | fn hanja_no_empty() { 18 | for (k, v) in crate::dict::HANJA_ENTRIES { 19 | assert!(!v.is_empty(), "With: ({}, {:?})", k, v); 20 | } 21 | } 22 | 23 | #[test] 24 | fn math_symbols() { 25 | use crate::lookup_math_symbol; 26 | use crate::math_symbol_key::*; 27 | 28 | assert_eq!(lookup_math_symbol("alpha", Style::NONE), Some("α")); 29 | assert_eq!(lookup_math_symbol("alpha", Style::BF), Some("𝛂")); 30 | assert_eq!(lookup_math_symbol("alpha", Style::IT), Some("𝛼")); 31 | assert_eq!( 32 | lookup_math_symbol("alpha", Style::BF | Style::IT), 33 | Some("𝜶") 34 | ); 35 | 36 | assert_eq!( 37 | lookup_math_symbol("R", Style::SF | Style::BF | Style::IT), 38 | Some("𝙍") 39 | ); 40 | assert_eq!(lookup_math_symbol("R", Style::TT), Some("𝚁")); 41 | assert_eq!(lookup_math_symbol("R", Style::BB), Some("ℝ")); 42 | assert_eq!(lookup_math_symbol("R", Style::SCR), Some("ℛ")); 43 | assert_eq!(lookup_math_symbol("R", Style::CAL), Some("𝓡")); 44 | assert_eq!(lookup_math_symbol("R", Style::FRAK), Some("ℜ")); 45 | } 46 | 47 | #[test] 48 | fn unicode() { 49 | assert_eq!( 50 | crate::search_unicode_annotations("thinkin") 51 | .next() 52 | .unwrap() 53 | .codepoint, 54 | "🤔" 55 | ); 56 | } 57 | } 58 | 59 | pub fn lookup(hangul: &str) -> Option<&'static [(&'static str, &'static str)]> { 60 | crate::dict::HANJA_ENTRIES 61 | .binary_search_by_key(&hangul, |(k, _)| *k) 62 | .ok() 63 | .map(|idx| crate::dict::HANJA_ENTRIES[idx].1) 64 | } 65 | 66 | pub fn lookup_math_symbol(keyword: &str, style: Style) -> Option<&'static str> { 67 | let key = SymbolKey(keyword, style); 68 | crate::dict::MATH_SYMBOL_ENTRIES 69 | .binary_search_by_key(&key, |(k, _)| *k) 70 | .ok() 71 | .map(|idx| crate::dict::MATH_SYMBOL_ENTRIES[idx].1) 72 | } 73 | 74 | pub fn search_unicode_annotations(keyword: &str) -> impl Iterator + '_ { 75 | crate::dict::UNICODE_ANNOTATIONS 76 | .iter() 77 | .copied() 78 | .filter(move |annotation| annotation.tts.contains(keyword)) 79 | } 80 | -------------------------------------------------------------------------------- /src/engine/core/src/os.rs: -------------------------------------------------------------------------------- 1 | use crate::InputCategory; 2 | use std::io; 3 | 4 | pub trait OsContext { 5 | fn read_global_hangul_state(&mut self) -> io::Result; 6 | fn update_layout_state(&mut self, category: InputCategory) -> io::Result<()>; 7 | } 8 | 9 | #[cfg(unix)] 10 | mod unix { 11 | use crate::InputCategory; 12 | use std::{ 13 | io::{self, Read, Write}, 14 | os::unix::net::UnixStream, 15 | path::PathBuf, 16 | time::Duration, 17 | }; 18 | 19 | pub struct OsContext { 20 | sock_path: PathBuf, 21 | } 22 | 23 | fn get_state_dir() -> PathBuf { 24 | let run_path = kime_run_dir::get_run_dir(); 25 | run_path.join("kime-indicator.sock") 26 | } 27 | 28 | impl Default for OsContext { 29 | fn default() -> Self { 30 | Self { 31 | sock_path: get_state_dir(), 32 | } 33 | } 34 | } 35 | 36 | impl super::OsContext for OsContext { 37 | fn read_global_hangul_state(&mut self) -> io::Result { 38 | let mut buf = [0; 1]; 39 | let mut client = UnixStream::connect(&self.sock_path)?; 40 | client.set_read_timeout(Some(Duration::from_secs(2))).ok(); 41 | client.set_write_timeout(Some(Duration::from_secs(2))).ok(); 42 | client.read_exact(&mut buf)?; 43 | match buf[0] { 44 | 1 => Ok(InputCategory::Hangul), 45 | _ => Ok(InputCategory::Latin), 46 | } 47 | } 48 | 49 | fn update_layout_state(&mut self, category: InputCategory) -> io::Result<()> { 50 | let category = match category { 51 | InputCategory::Hangul => 1, 52 | InputCategory::Latin => 0, 53 | }; 54 | 55 | let mut client = UnixStream::connect(&self.sock_path)?; 56 | client.set_read_timeout(Some(Duration::from_secs(2))).ok(); 57 | client.set_write_timeout(Some(Duration::from_secs(2))).ok(); 58 | client.write_all(&[category]) 59 | } 60 | } 61 | } 62 | 63 | mod fallback { 64 | use crate::InputCategory; 65 | use std::io; 66 | 67 | #[derive(Default)] 68 | pub struct OsContext; 69 | 70 | impl super::OsContext for OsContext { 71 | fn read_global_hangul_state(&mut self) -> io::Result { 72 | Err(io::Error::new(io::ErrorKind::Other, "Unsupported platform")) 73 | } 74 | 75 | fn update_layout_state(&mut self, _category: InputCategory) -> io::Result<()> { 76 | Err(io::Error::new(io::ErrorKind::Other, "Unsupported platform")) 77 | } 78 | } 79 | } 80 | 81 | #[cfg(unix)] 82 | use unix as imp; 83 | 84 | #[cfg(not(unix))] 85 | use fallback as imp; 86 | 87 | pub use imp::OsContext as DefaultOsContext; 88 | -------------------------------------------------------------------------------- /src/engine/backend/src/keymap.rs: -------------------------------------------------------------------------------- 1 | use crate::{Key, KeyCode, ModifierState}; 2 | use enum_map::EnumMap; 3 | use serde::{ 4 | de::{MapAccess, Visitor}, 5 | Deserialize, 6 | }; 7 | use std::{ 8 | fmt, 9 | iter::{FromIterator, IntoIterator}, 10 | marker::PhantomData, 11 | }; 12 | 13 | #[derive(Clone, Debug, PartialEq, Eq)] 14 | pub struct KeyMap { 15 | arr: EnumMap; 2]>, 16 | } 17 | 18 | impl Default for KeyMap { 19 | fn default() -> Self { 20 | Self::new() 21 | } 22 | } 23 | 24 | impl KeyMap { 25 | pub fn new() -> Self { 26 | Self { 27 | arr: EnumMap::default(), 28 | } 29 | } 30 | 31 | pub fn get(&self, key: Key) -> Option { 32 | if key.state.intersects(!ModifierState::SHIFT) { 33 | None 34 | } else { 35 | // SAFETY: key.state <= 0x1 36 | unsafe { *self.arr[key.code].get_unchecked(key.state.bits() as usize) } 37 | } 38 | } 39 | 40 | /// Key must don't have shift modifier 41 | pub fn insert(&mut self, key: Key, value: V) { 42 | self.arr[key.code][key.state.bits() as usize] = Some(value); 43 | } 44 | } 45 | 46 | impl FromIterator<(Key, V)> for KeyMap { 47 | fn from_iter>(iter: T) -> Self { 48 | let mut map = Self::new(); 49 | for item in iter { 50 | map.insert(item.0, item.1); 51 | } 52 | map 53 | } 54 | } 55 | 56 | struct KeyMapVisitor(PhantomData); 57 | 58 | impl<'de, V: Copy> Visitor<'de> for KeyMapVisitor 59 | where 60 | V: Deserialize<'de>, 61 | { 62 | type Value = KeyMap; 63 | 64 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 65 | formatter.write_str("KeyMap") 66 | } 67 | 68 | fn visit_map(self, mut map: A) -> Result 69 | where 70 | A: MapAccess<'de>, 71 | { 72 | let mut ret = KeyMap::new(); 73 | 74 | while let Some(entry) = map.next_entry()? { 75 | ret.insert(entry.0, entry.1); 76 | } 77 | 78 | Ok(ret) 79 | } 80 | } 81 | 82 | impl<'de, V: Copy> Deserialize<'de> for KeyMap 83 | where 84 | V: Deserialize<'de>, 85 | { 86 | fn deserialize(deserializer: D) -> Result 87 | where 88 | D: serde::Deserializer<'de>, 89 | { 90 | deserializer.deserialize_map(KeyMapVisitor(PhantomData)) 91 | } 92 | } 93 | 94 | #[cfg(test)] 95 | mod tests { 96 | use super::{Key, KeyCode, KeyMap}; 97 | 98 | #[test] 99 | fn insert() { 100 | let mut map = KeyMap::new(); 101 | map.insert(Key::normal(KeyCode::Backspace), 123); 102 | assert_eq!(map.get(Key::normal(KeyCode::Backspace)), Some(123)); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/tools/properties_writer/src/main.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::collections::HashSet; 3 | use std::process::Command; 4 | 5 | #[derive(Serialize, Deserialize)] 6 | #[serde(rename_all = "camelCase")] 7 | struct Root { 8 | configurations: Vec, 9 | version: u32, 10 | } 11 | 12 | #[derive(Serialize, Deserialize)] 13 | #[serde(rename_all = "camelCase")] 14 | struct Configuration { 15 | name: String, 16 | intelli_sense_mode: String, 17 | include_path: HashSet, 18 | compiler_path: String, 19 | c_standard: String, 20 | cpp_standard: String, 21 | browse: Browse, 22 | configuration_provider: String, 23 | } 24 | 25 | #[derive(Serialize, Deserialize)] 26 | #[serde(rename_all = "camelCase")] 27 | struct Browse { 28 | path: Vec, 29 | limit_symbols_to_included_headers: bool, 30 | database_filename: String, 31 | } 32 | 33 | fn main() { 34 | let cc = std::env::var("CC").unwrap_or("gcc".into()); 35 | 36 | let ret = Command::new("whereis").arg(cc).output().unwrap().stdout; 37 | let ret = String::from_utf8(ret).unwrap(); 38 | let cc_path = ret.split(' ').nth(1).unwrap_or("/usr/bin/gcc"); 39 | 40 | let ret = Command::new("pkg-config") 41 | .arg("--list-all") 42 | .output() 43 | .unwrap() 44 | .stdout; 45 | let ret = String::from_utf8(ret).unwrap(); 46 | 47 | let mut include_path = HashSet::new(); 48 | 49 | let packages = ret.lines().filter_map(|l| l.split(' ').next()); 50 | 51 | for package in packages { 52 | let ret = Command::new("pkg-config") 53 | .arg("--cflags-only-I") 54 | .arg(package) 55 | .output() 56 | .unwrap() 57 | .stdout; 58 | let ret = String::from_utf8(ret).unwrap(); 59 | let paths = ret.split(' '); 60 | 61 | for path in paths { 62 | if !path.starts_with("-I") { 63 | continue; 64 | } 65 | 66 | include_path.insert(path.split_at(2).1.trim_end_matches("\n").into()); 67 | } 68 | } 69 | 70 | include_path.insert("${workspaceFolder}".into()); 71 | include_path.insert("${workspaceFolder}/src/engine/capi".into()); 72 | 73 | let config = Configuration { 74 | name: "include paths".into(), 75 | intelli_sense_mode: "clang-x64".into(), 76 | include_path, 77 | compiler_path: cc_path.into(), 78 | c_standard: "c11".into(), 79 | cpp_standard: "c++17".into(), 80 | configuration_provider: "ms-vscode.cmake-tools".into(), 81 | browse: Browse { 82 | path: vec!["${workspaceFolder}/**".into()], 83 | limit_symbols_to_included_headers: true, 84 | database_filename: String::new(), 85 | }, 86 | }; 87 | 88 | let root = Root { 89 | version: 4, 90 | configurations: vec![config], 91 | }; 92 | 93 | println!("{}", serde_json::to_string_pretty(&root).unwrap()); 94 | } 95 | -------------------------------------------------------------------------------- /src/engine/core/tests/sebeolsik_3_90.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod shared; 3 | 4 | define_layout_test!("sebeolsik-3-90"); 5 | 6 | #[test] 7 | fn hello() { 8 | test_input(&[ 9 | (Key::normal(J), "ㅇ", ""), 10 | (Key::normal(F), "아", ""), 11 | (Key::normal(S), "안", ""), 12 | (Key::normal(H), "ㄴ", "안"), 13 | (Key::normal(E), "녀", ""), 14 | (Key::normal(A), "녕", ""), 15 | ]); 16 | } 17 | 18 | // issue #529 19 | #[test] 20 | fn dont_convert_jongseong() { 21 | test_input(&[ 22 | (Key::normal(K), "ㄱ", ""), 23 | (Key::normal(F), "가", ""), 24 | (Key::normal(Z), "감", ""), 25 | (Key::normal(Z), "ㅁ", "감"), 26 | (Key::normal(F), "ㅏ", "ㅁ"), 27 | ]); 28 | } 29 | 30 | // issue #263 31 | #[test] 32 | fn compose_choseong_ssang() { 33 | test_input(&[ 34 | (Key::normal(K), "ㄱ", ""), 35 | (Key::normal(F), "가", ""), 36 | (Key::normal(X), "각", ""), 37 | (Key::normal(K), "ㄱ", "각"), 38 | (Key::normal(D), "기", ""), 39 | ]); 40 | } 41 | 42 | #[test] 43 | fn switch_next() { 44 | test_input(&[ 45 | (Key::normal(S), "ㄴ", ""), 46 | (Key::normal(J), "ㅇ", "ㄴ"), 47 | (Key::normal(F), "아", ""), 48 | ]); 49 | } 50 | 51 | #[test] 52 | fn dont_change_jongseong() { 53 | test_input(&[ 54 | (Key::normal(J), "ㅇ", ""), 55 | (Key::normal(F), "아", ""), 56 | (Key::normal(S), "안", ""), 57 | (Key::normal(D), "ㅣ", "안"), 58 | ]); 59 | } 60 | 61 | #[test] 62 | fn flexible_compose_composable_jungseong1() { 63 | test_input_with_addon( 64 | &[(Key::normal(F), "ㅏ", ""), (Key::normal(V), "ㅘ", "")], 65 | Addon::FlexibleComposeOrder, 66 | ); 67 | } 68 | 69 | #[test] 70 | fn flexible_compose_composable_jungseong2() { 71 | test_input_with_addon( 72 | &[(Key::normal(F), "ㅏ", ""), (Key::normal(Slash), "ㅘ", "")], 73 | Addon::FlexibleComposeOrder, 74 | ); 75 | } 76 | 77 | #[test] 78 | fn flexible_compose_order_jong_cho() { 79 | test_input_with_addon( 80 | &[ 81 | (Key::normal(S), "ㄴ", ""), 82 | (Key::normal(J), "ᄋᅠᆫ", ""), 83 | (Key::normal(F), "안", ""), 84 | ], 85 | Addon::FlexibleComposeOrder, 86 | ); 87 | } 88 | 89 | #[test] 90 | fn flexible_compose_order_jong_jung() { 91 | test_input_with_addon( 92 | &[ 93 | (Key::normal(S), "ㄴ", ""), 94 | (Key::normal(F), "ᅟᅡᆫ", ""), 95 | (Key::normal(J), "안", ""), 96 | ], 97 | Addon::FlexibleComposeOrder, 98 | ); 99 | } 100 | 101 | #[test] 102 | fn s_number() { 103 | test_input(&[ 104 | (Key::shift(Two), "", "@"), 105 | (Key::shift(Three), "", "#"), 106 | (Key::shift(Four), "", "$"), 107 | (Key::shift(Five), "", "%"), 108 | (Key::shift(Six), "", "^"), 109 | (Key::shift(Seven), "", "&"), 110 | (Key::shift(Eight), "", "*"), 111 | (Key::shift(Nine), "", "("), 112 | (Key::shift(Zero), "", ")"), 113 | ]) 114 | } 115 | 116 | #[test] 117 | fn colon() { 118 | test_input(&[(Key::shift(SemiColon), "", ":")]); 119 | } 120 | -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source $(dirname $0)/tool.sh 4 | 5 | if [ -z "$1" ]; then 6 | echo "Usage: " 7 | exit 1 8 | fi 9 | 10 | if [ -z "$KIME_INSTALL_HEADER" ]; then 11 | KIME_INSTALL_HEADER=0 12 | fi 13 | 14 | if [ -z "$KIME_INSTALL_DOC" ]; then 15 | KIME_INSTALL_DOC=1 16 | fi 17 | 18 | PREFIX=$1 19 | 20 | if [ -z "$KIME_BIN_DIR" ]; then 21 | KIME_BIN_DIR=usr/bin 22 | fi 23 | 24 | if [ -z "$KIME_INCLUDE_DIR" ]; then 25 | KIME_INCLUDE_DIR=usr/include 26 | fi 27 | 28 | if [ -z "$KIME_DOC_DIR" ]; then 29 | KIME_DOC_DIR=usr/share/doc/kime 30 | fi 31 | 32 | if [ -z "$KIME_ICON_DIR" ]; then 33 | KIME_ICON_DIR=usr/share/icons 34 | fi 35 | 36 | if [ -z "$KIME_AUTOSTART_DIR" ]; then 37 | KIME_AUTOSTART_DIR=etc/xdg/autostart 38 | fi 39 | 40 | if [ -z "$KIME_DESKTOP_ENTRY_DIR" ]; then 41 | KIME_DESKTOP_ENTRY_DIR=usr/share/applications 42 | fi 43 | 44 | if [ -z "$KIME_LIB_DIR" ]; then 45 | KIME_LIB_DIR=usr/lib 46 | fi 47 | 48 | if [ -z "$KIME_GTK3_DIR" ]; then 49 | KIME_GTK3_DIR="$KIME_LIB_DIR/gtk-3.0/3.0.0/immodules" 50 | fi 51 | 52 | if [ -z "$KIME_GTK4_DIR" ]; then 53 | KIME_GTK4_DIR="$KIME_LIB_DIR/gtk-4.0/4.0.0/immodules" 54 | fi 55 | 56 | if [ -z "$KIME_QT5_DIR" ]; then 57 | KIME_QT5_DIR="$KIME_LIB_DIR/qt" 58 | fi 59 | 60 | if [ -z "$KIME_QT6_DIR" ]; then 61 | KIME_QT6_DIR="$KIME_LIB_DIR/qt6" 62 | fi 63 | 64 | install_if () { 65 | if [ -f $KIME_OUT/$1 ]; then 66 | install -Dm$2 $KIME_OUT/$1 $3 "$PREFIX/$4" 67 | else 68 | echo "SKIP $1" 69 | fi 70 | } 71 | 72 | install_bin () { 73 | install_if $1 755 -t "$KIME_BIN_DIR" 74 | } 75 | 76 | install_doc () { 77 | install -Dm644 $KIME_OUT/$1 -t "$PREFIX/$KIME_DOC_DIR" 78 | } 79 | 80 | install_bin kime-check 81 | install_bin kime-indicator 82 | install_bin kime-candidate-window 83 | install_bin kime-xim 84 | install_bin kime-wayland 85 | install_bin kime 86 | 87 | if [ "${KIME_INSTALL_HEADER}" -eq "1" ]; then 88 | install -Dm644 $KIME_OUT/kime_engine.h -t "$PREFIX/$KIME_INCLUDE_DIR" 89 | install -Dm644 $KIME_OUT/kime_engine.hpp -t "$PREFIX/$KIME_INCLUDE_DIR" 90 | fi 91 | 92 | if [ "${KIME_INSTALL_DOC}" -eq "1" ]; then 93 | install_doc default_config.yaml 94 | install_doc LICENSE 95 | install_doc NOTICE.md 96 | install_doc README.md 97 | install_doc README.ko.md 98 | install_doc CHANGELOG.md 99 | fi 100 | 101 | install -Dm644 $KIME_OUT/*.desktop -t "$PREFIX/$KIME_AUTOSTART_DIR" 102 | install -Dm644 $KIME_OUT/*.desktop -t "$PREFIX/$KIME_DESKTOP_ENTRY_DIR" 103 | install -Dm755 $KIME_OUT/kime-xdg-autostart -t "$PREFIX/$KIME_BIN_DIR" 104 | install -Dm644 $KIME_OUT/icons/64x64/*.png -t "$PREFIX/$KIME_ICON_DIR/hicolor/64x64/apps" 105 | install -Dm755 $KIME_OUT/libkime_engine.so -t "$PREFIX/$KIME_LIB_DIR" 106 | 107 | install_if libkime-gtk3.so 755 -T "$KIME_GTK3_DIR/im-kime.so" 108 | install_if libkime-gtk4.so 755 -t "$KIME_GTK4_DIR" 109 | install_if libkime-qt5.so 755 -T "$KIME_QT5_DIR/plugins/platforminputcontexts/libkimeplatforminputcontextplugin.so" 110 | install_if libkime-qt6.so 755 -T "$KIME_QT6_DIR/plugins/platforminputcontexts/libkimeplatforminputcontextplugin.so" 111 | -------------------------------------------------------------------------------- /src/engine/core/tests/shared.rs: -------------------------------------------------------------------------------- 1 | use kime_engine_core::{Config, EngineConfig, InputCategory, InputEngine, InputResult, Key}; 2 | use pretty_assertions::assert_eq; 3 | 4 | #[track_caller] 5 | pub fn test_input_impl(engine: EngineConfig, category: InputCategory, keys: &[(Key, &str, &str)]) { 6 | let config = Config::new(engine); 7 | let mut engine = InputEngine::new(&config); 8 | engine.set_input_category(category); 9 | 10 | macro_rules! test_preedit { 11 | ($text:expr) => {{ 12 | assert_eq!(engine.preedit_str(), $text); 13 | }}; 14 | } 15 | 16 | macro_rules! test_commit { 17 | ($text:expr) => {{ 18 | assert_eq!(engine.commit_str(), $text); 19 | }}; 20 | (@pass $text:expr) => {{ 21 | assert_eq!(format!("{}PASS", engine.commit_str()), $text); 22 | }}; 23 | } 24 | 25 | for (key, preedit, commit) in keys.iter().copied() { 26 | eprintln!("Key: {:?}", key); 27 | 28 | let ret = engine.press_key(key, &config); 29 | 30 | eprintln!("Ret: {:?}", ret); 31 | 32 | test_preedit!(preedit); 33 | 34 | if ret.contains(InputResult::HAS_COMMIT) { 35 | if ret.contains(InputResult::CONSUMED) { 36 | test_commit!(commit); 37 | } else { 38 | test_commit!(@pass commit); 39 | } 40 | } else if !ret.contains(InputResult::CONSUMED) { 41 | assert_eq!("PASS", commit); 42 | } 43 | 44 | engine.clear_commit(); 45 | } 46 | } 47 | 48 | #[allow(unused_macros)] 49 | macro_rules! define_layout_test { 50 | ($layout:expr, $latin_layout:expr, $category:expr) => { 51 | use kime_engine_backend_hangul::Addon; 52 | use kime_engine_backend_latin::LatinLayout; 53 | use kime_engine_core::{EngineConfig, EnumSet, Hotkey, InputCategory, Key, KeyCode::*}; 54 | use shared::test_input_impl; 55 | 56 | #[allow(dead_code)] 57 | fn default_config() -> EngineConfig { 58 | let mut config = EngineConfig::default(); 59 | config.hangul.layout = $layout.into(); 60 | config.latin.layout = $latin_layout; 61 | config 62 | } 63 | 64 | #[allow(dead_code)] 65 | #[track_caller] 66 | fn test_input(keys: &[(Key, &str, &str)]) { 67 | test_input_impl(default_config(), $category, keys); 68 | } 69 | 70 | #[allow(dead_code)] 71 | #[track_caller] 72 | fn test_word_input(keys: &[(Key, &str, &str)]) { 73 | let mut config = default_config(); 74 | config.hangul.word_commit = true; 75 | test_input_impl(config, $category, keys); 76 | } 77 | 78 | #[allow(dead_code)] 79 | #[track_caller] 80 | fn test_input_with_addon(keys: &[(Key, &str, &str)], addons: impl Into>) { 81 | let mut config = default_config(); 82 | config.hangul.addons.insert($layout.into(), addons.into()); 83 | test_input_impl(config, $category, keys); 84 | } 85 | 86 | #[allow(dead_code)] 87 | #[track_caller] 88 | fn test_input_with_hotkey(keys: &[(Key, &str, &str)], hotkeys: &[(Key, Hotkey)]) { 89 | let mut config = default_config(); 90 | config.global_hotkeys = hotkeys.iter().copied().collect(); 91 | test_input_impl(config, $category, keys); 92 | } 93 | }; 94 | ($layout:expr) => { 95 | define_layout_test!($layout, LatinLayout::Qwerty, InputCategory::Hangul); 96 | }; 97 | } 98 | -------------------------------------------------------------------------------- /src/frontends/xim/src/pe_window/bgra.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] 2 | pub struct Bgra(pub [u8; 4]); 3 | 4 | impl image::Pixel for Bgra { 5 | type Subpixel = u8; 6 | 7 | const CHANNEL_COUNT: u8 = 4; 8 | 9 | fn channels(&self) -> &[Self::Subpixel] { 10 | self.0.as_slice() 11 | } 12 | 13 | fn channels_mut(&mut self) -> &mut [Self::Subpixel] { 14 | self.0.as_mut_slice() 15 | } 16 | 17 | const COLOR_MODEL: &'static str = "BGRA"; 18 | 19 | fn channels4( 20 | &self, 21 | ) -> ( 22 | Self::Subpixel, 23 | Self::Subpixel, 24 | Self::Subpixel, 25 | Self::Subpixel, 26 | ) { 27 | (self.0[0], self.0[1], self.0[2], self.0[3]) 28 | } 29 | 30 | fn from_channels( 31 | a: Self::Subpixel, 32 | b: Self::Subpixel, 33 | c: Self::Subpixel, 34 | d: Self::Subpixel, 35 | ) -> Self { 36 | Self([a, b, c, d]) 37 | } 38 | 39 | fn from_slice(slice: &[Self::Subpixel]) -> &Self { 40 | unsafe { slice.as_ptr().cast::().as_ref().unwrap_unchecked() } 41 | } 42 | 43 | fn from_slice_mut(slice: &mut [Self::Subpixel]) -> &mut Self { 44 | unsafe { 45 | slice 46 | .as_mut_ptr() 47 | .cast::() 48 | .as_mut() 49 | .unwrap_unchecked() 50 | } 51 | } 52 | 53 | fn to_rgb(&self) -> image::Rgb { 54 | image::Rgb([self.0[2], self.0[1], self.0[0]]) 55 | } 56 | 57 | fn to_rgba(&self) -> image::Rgba { 58 | image::Rgba([self.0[2], self.0[1], self.0[0], self.0[3]]) 59 | } 60 | 61 | fn to_luma(&self) -> image::Luma { 62 | self.to_rgb().to_luma() 63 | } 64 | 65 | fn to_luma_alpha(&self) -> image::LumaA { 66 | self.to_rgba().to_luma_alpha() 67 | } 68 | 69 | fn map(&self, f: F) -> Self 70 | where 71 | F: FnMut(Self::Subpixel) -> Self::Subpixel, 72 | { 73 | Self(self.0.map(f)) 74 | } 75 | 76 | fn apply(&mut self, f: F) 77 | where 78 | F: FnMut(Self::Subpixel) -> Self::Subpixel, 79 | { 80 | self.0 = self.0.map(f); 81 | } 82 | 83 | fn map_with_alpha(&self, f: F, g: G) -> Self 84 | where 85 | F: FnMut(Self::Subpixel) -> Self::Subpixel, 86 | G: FnMut(Self::Subpixel) -> Self::Subpixel, 87 | { 88 | let mut ret = self.clone(); 89 | ret.apply_with_alpha(f, g); 90 | ret 91 | } 92 | 93 | fn apply_with_alpha(&mut self, mut f: F, mut g: G) 94 | where 95 | F: FnMut(Self::Subpixel) -> Self::Subpixel, 96 | G: FnMut(Self::Subpixel) -> Self::Subpixel, 97 | { 98 | for c in &mut self.0[..3] { 99 | *c = f(*c); 100 | } 101 | self.0[3] = g(self.0[3]); 102 | } 103 | 104 | fn map2(&self, other: &Self, f: F) -> Self 105 | where 106 | F: FnMut(Self::Subpixel, Self::Subpixel) -> Self::Subpixel, 107 | { 108 | let mut ret = self.clone(); 109 | ret.apply2(other, f); 110 | ret 111 | } 112 | 113 | fn apply2(&mut self, other: &Self, mut f: F) 114 | where 115 | F: FnMut(Self::Subpixel, Self::Subpixel) -> Self::Subpixel, 116 | { 117 | for (a, b) in self.0.iter_mut().zip(other.0.iter()) { 118 | *a = f(*a, *b); 119 | } 120 | } 121 | 122 | fn invert(&mut self) { 123 | self.apply_without_alpha(|c| u8::MAX - c); 124 | } 125 | 126 | fn blend(&mut self, other: &Self) { 127 | let mut rgba = self.to_rgba(); 128 | rgba.blend(&other.to_rgba()); 129 | let [r, g, b, a] = rgba.0; 130 | self.0 = [b, g, r, a]; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source $(dirname $0)/tool.sh 4 | 5 | mkdir -pv $KIME_OUT 6 | 7 | if [ -z "$KIME_MAKE_ARGS" ]; then 8 | KIME_MAKE_ARGS="-j4" 9 | fi 10 | 11 | if [ -z "$KIME_SKIP_ENGINE" ]; then 12 | KIME_SKIP_ENGINE=0 13 | fi 14 | 15 | if [ -z "$KIME_BUILD_CANDIDATE_WINDOW" ]; then 16 | KIME_BUILD_CANDIDATE_WINDOW=0 17 | fi 18 | 19 | if [ -z "$KIME_BUILD_CHECK" ]; then 20 | KIME_BUILD_CHECK=0 21 | fi 22 | 23 | if [ -z "$KIME_BUILD_XIM" ]; then 24 | KIME_BUILD_XIM=0 25 | fi 26 | 27 | if [ -z "$KIME_BUILD_WAYLAND" ]; then 28 | KIME_BUILD_WAYLAND=0 29 | fi 30 | 31 | if [ -z "$KIME_BUILD_INDICATOR" ]; then 32 | KIME_BUILD_INDICATOR=0 33 | fi 34 | 35 | set_release() { 36 | TARGET_DIR=./target/release 37 | _KIME_CARGO_ARGS="--release" 38 | _KIME_CMAKE_ARGS="-DCMAKE_BUILD_TYPE=Release" 39 | } 40 | 41 | set_debug() { 42 | TARGET_DIR=./target/debug 43 | _KIME_CARGO_ARGS="" 44 | _KIME_CMAKE_ARGS="-DCMAKE_BUILD_TYPE=Debug" 45 | } 46 | 47 | cargo_build() { 48 | cargo build $_KIME_CARGO_ARGS $KIME_CARGO_ARGS "$@" 49 | } 50 | 51 | set_release 52 | 53 | while getopts hrda opt; do 54 | case $opt in 55 | h) 56 | echo "build.sh" 57 | echo "Please set KIME_BUILD_{CHECK,INDICATOR,XIM,WAYLAND}, KIME_CMAKE_ARGS or use -a" 58 | echo "-h: help" 59 | echo "-r: release mode(default)" 60 | echo "-d: debug mode" 61 | echo "-a: all modules" 62 | exit 0 63 | ;; 64 | r) 65 | set_release 66 | ;; 67 | d) 68 | set_debug 69 | ;; 70 | a) 71 | KIME_BUILD_CHECK=1 72 | KIME_BUILD_CANDIDATE_WINDOW=1 73 | KIME_BUILD_INDICATOR=1 74 | KIME_BUILD_WAYLAND=1 75 | if (pkg-config --exists xcb && pkg-config --exists cairo); then 76 | KIME_BUILD_XIM=1 77 | fi 78 | KIME_BUILD_KIME=1 79 | KIME_CMAKE_ARGS="-DENABLE_GTK3=ON -DENABLE_GTK4=ON -DENABLE_QT5=ON -DENABLE_QT6=ON $KIME_CMAKE_ARGS" 80 | ;; 81 | esac 82 | done 83 | 84 | echo Build rust pkgs 85 | 86 | KIME_RUST_PKGS=() 87 | 88 | if [ "$KIME_SKIP_ENGINE" -eq "1" ]; then 89 | _KIME_CMAKE_ARGS="${_KIME_CMAKE_ARGS} -DUSE_SYSTEM_ENGINE=ON" 90 | KIME_BUILD_CHECK=0 91 | echo Use system engine 92 | else 93 | LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${PWD}/${TARGET_DIR}" 94 | KIME_RUST_PKGS+=("-pkime-engine-capi") 95 | fi 96 | 97 | if [ "$KIME_BUILD_CHECK" -eq "1" ]; then 98 | KIME_RUST_PKGS+=("-pkime-check") 99 | fi 100 | 101 | if [ "$KIME_BUILD_INDICATOR" -eq "1" ]; then 102 | KIME_RUST_PKGS+=("-pkime-indicator") 103 | fi 104 | 105 | if [ "$KIME_BUILD_CANDIDATE_WINDOW" -eq "1" ]; then 106 | KIME_RUST_PKGS+=("-pkime-candidate-window") 107 | fi 108 | 109 | if [ "$KIME_BUILD_XIM" -eq "1" ]; then 110 | KIME_RUST_PKGS+=("-pkime-xim") 111 | fi 112 | 113 | if [ "$KIME_BUILD_WAYLAND" -eq "1" ]; then 114 | KIME_RUST_PKGS+=("-pkime-wayland") 115 | fi 116 | 117 | if [ "$KIME_BUILD_KIME" -eq "1" ]; then 118 | KIME_RUST_PKGS+=("-pkime") 119 | fi 120 | 121 | cargo_build "${KIME_RUST_PKGS[@]}" 122 | 123 | cp $TARGET_DIR/libkime_engine.so $KIME_OUT || true 124 | cp $TARGET_DIR/kime-check $KIME_OUT || true 125 | cp $TARGET_DIR/kime-candidate-window $KIME_OUT || true 126 | cp $TARGET_DIR/kime-indicator $KIME_OUT || true 127 | cp $TARGET_DIR/kime-xim $KIME_OUT || true 128 | cp $TARGET_DIR/kime-wayland $KIME_OUT || true 129 | cp $TARGET_DIR/kime $KIME_OUT || true 130 | 131 | cp src/engine/capi/kime_engine.h $KIME_OUT 132 | cp src/engine/capi/kime_engine.hpp $KIME_OUT 133 | cp docs/CHANGELOG.md $KIME_OUT 134 | cp LICENSE $KIME_OUT 135 | cp NOTICE.md $KIME_OUT 136 | cp README.md $KIME_OUT 137 | cp README.ko.md $KIME_OUT 138 | cp -R res/* $KIME_OUT 139 | 140 | mkdir -pv build/cmake 141 | 142 | echo Build gtk qt immodules... 143 | 144 | cd build/cmake 145 | 146 | cmake ../../src $_KIME_CMAKE_ARGS $KIME_CMAKE_ARGS 147 | 148 | make $KIME_MAKE_ARGS 149 | 150 | cp lib/* $KIME_OUT || true 151 | 152 | -------------------------------------------------------------------------------- /src/tools/kime/src/main.rs: -------------------------------------------------------------------------------- 1 | use daemonize::Daemonize; 2 | use kime_engine_core::{load_raw_config_from_config_dir, DaemonModule as Module}; 3 | use std::sync::atomic::{AtomicBool, Ordering::SeqCst}; 4 | use std::{ 5 | env, io, 6 | process::{Command, Stdio}, 7 | }; 8 | use std::{fs::File, path::Path}; 9 | 10 | const fn process_name(module: Module) -> &'static str { 11 | match module { 12 | Module::Xim => "kime-xim", 13 | Module::Wayland => "kime-wayland", 14 | Module::Indicator => "kime-indicator", 15 | } 16 | } 17 | 18 | fn kill_daemon(pid: &Path) -> io::Result<()> { 19 | let pid = std::fs::read_to_string(pid)?; 20 | 21 | let ret = Command::new("kill") 22 | .arg(pid) 23 | .spawn()? 24 | .wait_with_output()? 25 | .status; 26 | 27 | if ret.success() { 28 | Ok(()) 29 | } else { 30 | log::error!("kill return: {}", ret); 31 | Err(io::Error::new(io::ErrorKind::Other, "kill command failed")) 32 | } 33 | } 34 | 35 | fn main() -> Result<(), ()> { 36 | let mut args = kime_version::cli_boilerplate!( 37 | Ok(()), 38 | "-k or --kill: kill daemon then exit", 39 | "-D or --no-daemon: don't start as daemon", 40 | ); 41 | 42 | let run_dir = kime_run_dir::get_run_dir(); 43 | let pid = run_dir.join("kime.pid"); 44 | 45 | if args.contains(["-k", "--kill"]) { 46 | return kill_daemon(&pid).map_err(|err| { 47 | log::error!("Can't kill daemon: {}", err); 48 | }); 49 | } 50 | 51 | if !args.contains(["-D", "--no-daemon"]) { 52 | let stderr = run_dir.join("kime.err"); 53 | let stderr_file = match File::create(stderr) { 54 | Ok(file) => file, 55 | Err(err) => { 56 | log::error!("Can't create stderr file: {}", err); 57 | return Err(()); 58 | } 59 | }; 60 | match Daemonize::new() 61 | .working_directory("/tmp") 62 | .stderr(stderr_file) 63 | .pid_file(&pid) 64 | .start() 65 | { 66 | Ok(_) => {} 67 | Err(err) => { 68 | log::error!("Can't daemonize kime: {}", err); 69 | return Err(()); 70 | } 71 | } 72 | } 73 | 74 | let config = load_raw_config_from_config_dir().daemon; 75 | 76 | static RUN: AtomicBool = AtomicBool::new(true); 77 | 78 | ctrlc::set_handler(|| { 79 | log::info!("Receive exit signal"); 80 | RUN.store(false, SeqCst); 81 | }) 82 | .expect("Set ctrlc handler"); 83 | 84 | log::info!("Initialized"); 85 | 86 | let mut processes = config 87 | .modules 88 | .iter() 89 | .filter_map(|module| { 90 | let name = process_name(module); 91 | match Command::new(name) 92 | .stdin(Stdio::null()) 93 | .stdout(Stdio::inherit()) 94 | .stderr(Stdio::inherit()) 95 | .spawn() 96 | { 97 | Ok(p) => Some((name, p, false)), 98 | Err(err) => { 99 | log::error!("Can't spawn {}: {}", name, err); 100 | None 101 | } 102 | } 103 | }) 104 | .collect::>(); 105 | 106 | while RUN.load(SeqCst) { 107 | // Remove finished process 108 | for (name, process, exited) in processes.iter_mut() { 109 | match process.try_wait().expect("Wait process") { 110 | Some(status) => { 111 | log::info!("Process {} has exit with {}", name, status); 112 | *exited = true; 113 | } 114 | None => {} 115 | } 116 | } 117 | 118 | processes.retain(|(_, _, exited)| !*exited); 119 | 120 | if processes.is_empty() { 121 | log::info!("All process has exited"); 122 | return Ok(()); 123 | } 124 | 125 | std::thread::sleep(std::time::Duration::from_secs(1)); 126 | } 127 | 128 | for (name, mut process, _) in processes { 129 | log::info!("KILL {}", name); 130 | process.kill().ok(); 131 | } 132 | 133 | Ok(()) 134 | } 135 | -------------------------------------------------------------------------------- /src/tools/indicator/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use kime_engine_core::{load_raw_config_from_config_dir, IconColor}; 3 | use ksni::menu::*; 4 | use std::net::Shutdown; 5 | use std::os::unix::net::{UnixListener, UnixStream}; 6 | use std::path::Path; 7 | use std::{ 8 | io::{Read, Write}, 9 | time::Duration, 10 | }; 11 | 12 | #[derive(Clone, Copy, Debug)] 13 | enum InputCategory { 14 | Latin, 15 | Hangul, 16 | } 17 | 18 | struct KimeTray { 19 | icon_name: &'static str, 20 | color: IconColor, 21 | } 22 | 23 | impl ksni::Tray for KimeTray { 24 | fn icon_name(&self) -> String { 25 | self.icon_name.into() 26 | } 27 | 28 | fn id(&self) -> String { 29 | "kime".into() 30 | } 31 | 32 | fn title(&self) -> String { 33 | self.id() 34 | } 35 | 36 | fn attention_icon_name(&self) -> String { 37 | self.icon_name.into() 38 | } 39 | fn menu(&self) -> Vec> { 40 | vec![StandardItem { 41 | label: "Exit".into(), 42 | icon_name: "application-exit".into(), 43 | activate: Box::new(|_| std::process::exit(0)), 44 | ..Default::default() 45 | } 46 | .into()] 47 | } 48 | } 49 | 50 | const fn icon_name(category: InputCategory, color: IconColor) -> &'static str { 51 | match (category, color) { 52 | (InputCategory::Latin, IconColor::Black) => "kime-latin-black", 53 | (InputCategory::Latin, IconColor::White) => "kime-latin-white", 54 | (InputCategory::Hangul, IconColor::Black) => "kime-hangul-black", 55 | (InputCategory::Hangul, IconColor::White) => "kime-hangul-white", 56 | } 57 | } 58 | 59 | impl KimeTray { 60 | pub fn new(color: IconColor) -> Self { 61 | Self { 62 | // Set init category Latin 63 | // TODO: should consider `default_category` config? 64 | icon_name: icon_name(InputCategory::Latin, color), 65 | color, 66 | } 67 | } 68 | pub fn update_with_bytes(&mut self, bytes: &[u8; 1]) { 69 | let category = match bytes[0] { 70 | 1 => InputCategory::Hangul, 71 | _ => InputCategory::Latin, 72 | }; 73 | 74 | self.update(category); 75 | } 76 | 77 | pub fn update(&mut self, category: InputCategory) { 78 | log::debug!("Update: {:?}", category); 79 | self.icon_name = icon_name(category, self.color); 80 | } 81 | } 82 | 83 | const EXIT_MESSAGE: &[u8; 1] = b"Z"; 84 | 85 | fn try_terminate_previous_server(file_path: &Path) -> Result<()> { 86 | let mut client = UnixStream::connect(file_path)?; 87 | 88 | client.write_all(EXIT_MESSAGE)?; 89 | 90 | Ok(()) 91 | } 92 | 93 | fn indicator_server(file_path: &Path, color: IconColor) -> Result<()> { 94 | let service = ksni::TrayService::new(KimeTray::new(color)); 95 | let handle = service.handle(); 96 | service.spawn(); 97 | 98 | if file_path.exists() { 99 | try_terminate_previous_server(file_path).ok(); 100 | std::fs::remove_file(file_path).ok(); 101 | } 102 | 103 | let listener = UnixListener::bind(file_path)?; 104 | 105 | let mut current_bytes = [0; 1]; 106 | let mut read_buf = [0; 1]; 107 | 108 | loop { 109 | let mut client = listener.accept()?.0; 110 | client.set_read_timeout(Some(Duration::from_secs(2))).ok(); 111 | client.set_write_timeout(Some(Duration::from_secs(2))).ok(); 112 | client.write_all(¤t_bytes).ok(); 113 | client.shutdown(Shutdown::Write).ok(); 114 | match client.read_exact(&mut read_buf) { 115 | Ok(_) => { 116 | if &read_buf == EXIT_MESSAGE { 117 | log::info!("Receive exit message"); 118 | return Ok(()); 119 | } 120 | 121 | current_bytes = read_buf; 122 | 123 | handle.update(|tray| { 124 | tray.update_with_bytes(¤t_bytes); 125 | }); 126 | } 127 | _ => {} 128 | } 129 | } 130 | } 131 | 132 | fn main() { 133 | kime_version::cli_boilerplate!((),); 134 | 135 | let config = load_raw_config_from_config_dir().indicator; 136 | let run_dir = kime_run_dir::get_run_dir(); 137 | let file_path = run_dir.join("kime-indicator.sock"); 138 | indicator_server(&file_path, config.icon_color).unwrap(); 139 | } 140 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "attach", 10 | "name": "Attach Gedit(Nix)", 11 | "program": "/nix/store/xayryd4aps8pw1v3xwq5g2pzw3lif6kk-gedit-40.1/bin/.gedit-wrapped" 12 | }, 13 | { 14 | "type": "lldb", 15 | "request": "launch", 16 | "name": "Launch indicator", 17 | "cargo": { 18 | "args": [ 19 | "build", 20 | "-p=kime-indicator" 21 | ] 22 | }, 23 | "program": "${cargo:program}", 24 | "args": [] 25 | }, 26 | { 27 | "type": "lldb", 28 | "request": "launch", 29 | "name": "Launch check", 30 | "cargo": { 31 | "args": [ 32 | "build", 33 | "-p=kime-check" 34 | ] 35 | }, 36 | "env": { 37 | "LD_LIBRARY_PATH": "${workspaceFolder}/target/debug" 38 | }, 39 | "program": "${cargo:program}", 40 | "args": [] 41 | }, 42 | { 43 | "type": "lldb", 44 | "request": "launch", 45 | "name": "Launch wayland", 46 | "cargo": { 47 | "args": [ 48 | "build", 49 | "-p=kime-wayland" 50 | ] 51 | }, 52 | "env": { 53 | "LD_LIBRARY_PATH": "${workspaceFolder}/target/debug" 54 | }, 55 | "program": "${cargo:program}", 56 | "args": [ 57 | "--verbose" 58 | ] 59 | }, 60 | { 61 | "type": "lldb", 62 | "request": "launch", 63 | "name": "Launch xim", 64 | "cargo": { 65 | "args": [ 66 | "build", 67 | "-p=kime-xim" 68 | ] 69 | }, 70 | "env": { 71 | "LD_LIBRARY_PATH": "${workspaceFolder}/target/debug" 72 | }, 73 | "program": "${cargo:program}", 74 | "args": [ 75 | "--verbose" 76 | ] 77 | }, 78 | { 79 | "type": "lldb", 80 | "request": "launch", 81 | "name": "Debug with firefox", 82 | "preLaunchTask": "build all", 83 | "env": { 84 | "GTK_IM_MODULE": "kime", 85 | "GTK_IM_MODULE_FILE": "${workspaceFolder}/.vscode/immodules.cache", 86 | "LD_LIBRARY_PATH": "${workspaceFolder}/target/debug", 87 | "G_MESSAGES_DEBUG": "kime", 88 | }, 89 | "program": "/usr/lib64/firefox/firefox", 90 | "args": [ 91 | "-g", 92 | "-new-instance", 93 | "-P", 94 | "kime-debug", 95 | "-new-window", 96 | "https://codepen.io/hpop/pen/drLiH" 97 | ] 98 | }, 99 | { 100 | "type": "lldb", 101 | "request": "launch", 102 | "name": "Debug with libreoffice-calc", 103 | "preLaunchTask": "build all", 104 | "env": { 105 | "GTK_IM_MODULE": "kime", 106 | "GTK_IM_MODULE_FILE": "${workspaceFolder}/.vscode/immodules.cache", 107 | "LD_LIBRARY_PATH": "${workspaceFolder}/target/debug", 108 | "G_MESSAGES_DEBUG": "kime" 109 | }, 110 | "program": "/usr/lib/libreoffice/program/soffice.bin", 111 | "args": [ 112 | "--calc" 113 | ] 114 | }, 115 | { 116 | "type": "lldb", 117 | "request": "launch", 118 | "name": "Debug with gedit", 119 | "preLaunchTask": "build all", 120 | "env": { 121 | "GTK_IM_MODULE": "kime", 122 | "GTK_IM_MODULE_FILE": "${workspaceFolder}/.vscode/immodules.cache", 123 | "LD_LIBRARY_PATH": "${workspaceFolder}/target/debug", 124 | "G_MESSAGES_DEBUG": "kime" 125 | }, 126 | "program": "gedit", 127 | "args": [] 128 | }, 129 | { 130 | "type": "lldb", 131 | "request": "launch", 132 | "name": "Debug with kate", 133 | "preLaunchTask": "build all", 134 | "env": { 135 | "QT_IM_MODULE": "kime", 136 | "LD_LIBRARY_PATH": "${workspaceFolder}/target/debug", 137 | }, 138 | "program": "kate", 139 | "args": [] 140 | } 141 | ] 142 | } -------------------------------------------------------------------------------- /src/frontends/qt5/src/input_context.cc: -------------------------------------------------------------------------------- 1 | #include "input_context.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | KimeInputContext::KimeInputContext(kime::InputEngine *engine, 9 | const kime::Config *config) { 10 | this->engine = engine; 11 | this->config = config; 12 | } 13 | 14 | void KimeInputContext::update(Qt::InputMethodQueries queries) {} 15 | 16 | void KimeInputContext::commit() { this->reset(); } 17 | 18 | void KimeInputContext::reset() { 19 | #ifdef DEBUG 20 | KIME_DEBUG << "reset" 21 | << "\n"; 22 | #endif 23 | kime::kime_engine_clear_preedit(this->engine); 24 | this->commit_str(kime::kime_engine_commit_str(this->engine)); 25 | kime::kime_engine_reset(this->engine); 26 | } 27 | 28 | void KimeInputContext::setFocusObject(QObject *object) { 29 | if (object) { 30 | kime::kime_engine_update_layout_state(this->engine); 31 | if (!this->engine_ready) { 32 | if (kime::kime_engine_check_ready(this->engine)) { 33 | kime::InputResult ret = kime::kime_engine_end_ready(this->engine); 34 | this->process_input_result(ret); 35 | this->engine_ready = true; 36 | } 37 | } 38 | } else if (this->focus_object && this->engine_ready) { 39 | this->reset(); 40 | } 41 | 42 | this->focus_object = object; 43 | } 44 | 45 | bool KimeInputContext::isValid() const { return true; } 46 | 47 | Qt::LayoutDirection KimeInputContext::inputDirection() const { 48 | return Qt::LayoutDirection::LeftToRight; 49 | } 50 | 51 | void KimeInputContext::invokeAction(QInputMethod::Action action, 52 | int cursorPosition) { 53 | #ifdef DEBUG 54 | KIME_DEBUG << "invokeAction: " << action << ", " << cursorPosition << "\n"; 55 | #endif 56 | } 57 | 58 | bool KimeInputContext::filterEvent(const QEvent *event) { 59 | if (event->type() != QEvent::KeyPress) { 60 | return false; 61 | } 62 | 63 | auto keyevent = static_cast(event); 64 | auto modifiers = keyevent->modifiers(); 65 | 66 | kime::ModifierState state = 0; 67 | 68 | bool numlock = modifiers.testFlag(Qt::KeyboardModifier::KeypadModifier); 69 | 70 | if (modifiers.testFlag(Qt::KeyboardModifier::ControlModifier)) { 71 | state |= kime::ModifierState_CONTROL; 72 | } 73 | 74 | if (modifiers.testFlag(Qt::KeyboardModifier::ShiftModifier)) { 75 | state |= kime::ModifierState_SHIFT; 76 | } 77 | 78 | if (modifiers.testFlag(Qt::KeyboardModifier::AltModifier)) { 79 | state |= kime::ModifierState_ALT; 80 | } 81 | 82 | if (modifiers.testFlag(Qt::KeyboardModifier::MetaModifier)) { 83 | state |= kime::ModifierState_SUPER; 84 | } 85 | 86 | kime::InputResult ret = kime_engine_press_key( 87 | this->engine, this->config, (uint16_t)keyevent->nativeScanCode(), numlock, state); 88 | 89 | return this->process_input_result(ret); 90 | } 91 | 92 | void KimeInputContext::preedit_str(kime::RustStr s) { 93 | this->focus_object = qApp->focusObject(); 94 | if (!this->focus_object) { 95 | return; 96 | } 97 | 98 | QTextCharFormat fmt; 99 | fmt.setFontUnderline(true); 100 | QString qs = QString::fromUtf8((const char *)(s.ptr), s.len); 101 | this->attributes.push_back(QInputMethodEvent::Attribute{ 102 | QInputMethodEvent::AttributeType::TextFormat, 103 | 0, static_cast(qs.length()), fmt 104 | }); 105 | QInputMethodEvent e(qs, this->attributes); 106 | this->attributes.clear(); 107 | QCoreApplication::sendEvent(this->focus_object, &e); 108 | } 109 | 110 | void KimeInputContext::commit_str(kime::RustStr s) { 111 | this->focus_object = qApp->focusObject(); 112 | if (!this->focus_object) { 113 | return; 114 | } 115 | 116 | QInputMethodEvent e; 117 | if (s.len) { 118 | e.setCommitString(QString::fromUtf8((const char *)(s.ptr), s.len)); 119 | } 120 | QCoreApplication::sendEvent(this->focus_object, &e); 121 | } 122 | 123 | bool KimeInputContext::process_input_result(kime::InputResult ret) { 124 | if (ret & kime::InputResult_LANGUAGE_CHANGED) { 125 | kime::kime_engine_update_layout_state(this->engine); 126 | } 127 | 128 | bool visible = !!(ret & kime::InputResult_HAS_PREEDIT); 129 | 130 | if (!visible) { 131 | // only send preedit when invisible 132 | // issue #425 133 | if (this->visible) { 134 | #ifdef DEBUG 135 | KIME_DEBUG << "Clear preedit\n"; 136 | #endif 137 | this->preedit_str(kime::kime_engine_preedit_str(this->engine)); 138 | } 139 | } 140 | 141 | if (ret & (kime::InputResult_HAS_COMMIT)) { 142 | #ifdef DEBUG 143 | KIME_DEBUG << "Commit\n"; 144 | #endif 145 | commit_str(kime::kime_engine_commit_str(this->engine)); 146 | kime::kime_engine_clear_commit(this->engine); 147 | } 148 | 149 | if (visible) { 150 | #ifdef DEBUG 151 | KIME_DEBUG << "Update preedit\n"; 152 | #endif 153 | this->preedit_str(kime::kime_engine_preedit_str(this->engine)); 154 | } 155 | 156 | this->visible = visible; 157 | 158 | return !!(ret & kime::InputResult_CONSUMED); 159 | } 160 | -------------------------------------------------------------------------------- /docs/CONFIGURATION.ko.md: -------------------------------------------------------------------------------- 1 | # config.yaml 2 | 3 | [English](CONFIGURATION.md), [한국어](CONFIGURATION.ko.md) 4 | 5 | `/usr/share/doc/kime/default_config.yaml`에 기본 설정 파일 샘플이 있습니다. 6 | [default_config.yaml](../res/default_config.yaml)에서 기본 설정 파일을 온라인으로 볼 수도 있습니다. 7 | 이 파일을 `/etc/xdg/kime/config.yaml`로 복사하여 전역 설정으로 사용하세요. 8 | `~/.config/kime/config.yaml`에 사용자마다 각각 적용되는 설정 파일을 만들 수도 있습니다. 9 | 10 | [`$XDG_CONFIG_DIR`이나 `$XDG_CONFIG_HOME`][xdg] 환경 변수를 이용해 설정 파일의 위치를 바꿀 수도 있습니다. kime는 `$XDG_CONFIG_DIR/kime/config.yaml`과 `$XDG_CONFIG_HOME/kime/config.yaml`에 있는 설정 파일도 읽으려고 시도할 것입니다. 11 | 12 | [xdg]: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html#introduction 13 | 14 | # log 15 | 16 | kime 프로그램들의 로그 레벨을 설정합니다 17 | 18 | `OFF`, `ERROR`, `WARN`, `INFO`, `DEBUG`, `TRACE` 중에서 선택해주세요. 19 | 20 | ## global_level 21 | 22 | 전역 로깅 레벨입니다 23 | 24 | # daemon 25 | 26 | `kime` 데몬의 설정입니다 27 | 28 | ## modules 29 | 30 | 데몬의 모듈 목록입니다 기본값은 *전부*입니다 31 | 32 | * Xim 33 | * Wayland 34 | * Indicator 35 | 36 | # indicator 37 | 38 | `kime-indicator`의 설정입니다 39 | 40 | ### icon_color 41 | 42 | indicator에서 사용할 아이콘의 색을 정합니다 43 | 44 | #### 가능한 값 45 | 46 | * Black 47 | * White 48 | 49 | | 기본값 |`Black`| 50 | |--------|-------| 51 | 52 | # engine 53 | 54 | `kime-engine`의 설정입니다 55 | 56 | ## translation_layer 57 | 58 | 키코드 번역 레이어를 추가합니다 특수한 키보드를 사용할때 유용합니다. 59 | 60 | | 기본값 |`null`| 61 | |--------|-------| 62 | 63 | ## default_category 64 | 65 | 입력기가 시작될때의 기본 언어를 설정합니다. `Latin`(로마자), `Hangul`(한글) 중에서 설정해주세요 66 | 67 | | 기본값 |`Latin`| 68 | |--------|-------| 69 | 70 | ## global_category_state 71 | 72 | 언어상태를 전역에서 설정합니다. 73 | 74 | | 기본값 |`false`| 75 | |--------|-------| 76 | 77 | 78 | ## hotkeys 79 | 80 | 엔진의 단축키를 설정합니다 형식은 `키: 내용` 입니다 81 | 82 | ### global_hotkeys 83 | 84 | 전역 단축키입니다 85 | 86 | ### category_hotkeys 87 | 88 | 언어별 단축키입니다 전역 단축키를 덮어씁니다 89 | 90 | ### mode_hotkeys 91 | 92 | 모드별 단축키입니다 전역과 언어별 단축키를 덮어씁니다 93 | 94 | ### 내용 95 | 96 | #### behavior 97 | 98 | ##### !Toggle [InputCategory, InputCategory] 99 | 100 | 왼쪽과 오른쪽의 상태를 바꿉니다 101 | 102 | ##### !Switch InputCategory 103 | 104 | 해당 언어로 바꿉니다 105 | 106 | ##### !Mode InputMode 107 | 108 | 해당 모드를 활성화합니다 109 | 110 | ##### Commit 111 | 112 | 현재 조합상태를 종료하고 커밋합니다 113 | 114 | ##### Ignore 115 | 116 | 아무 동작도 하지 않습니다 117 | 118 | #### result 119 | 120 | ##### Bypass 121 | 122 | 키를 계속 처리합니다 123 | 124 | ##### Consume 125 | 126 | 키 처리를 종료합니다 127 | 128 | ##### ConsumeIfProcessed 129 | 130 | 단축키가 실행됐을 경우에는 Consume처럼, 아닐때는 Bypass처럼 동작합니다. 131 | 132 | ## xim_preedit_font 133 | 134 | XIM에서 쓸 편집창 글꼴과 크기입니다. 135 | 136 | | 기본값 |`[D2Coding, 15.0]`| 137 | |--------|------------------| 138 | 139 | ## latin 140 | 141 | 로마자 입력기를 설정합니다. 142 | 143 | ### preferred_direct 144 | 145 | 될 수 있으면 키 이밴트를 외부에서 처리합니다. 146 | 147 | ### layout 148 | 149 | 로마자 자판을 설정합니다. 150 | 151 | | 기본값 |`Qwerty`| 152 | |--------|-------| 153 | 154 | ### 가능한 자판들 155 | 156 | * `Qwerty` 157 | * `Dvorak` 158 | * `Colemak` 159 | 160 | ## hangul 161 | 162 | 한글 입력기를 설정합니다. 163 | 164 | ### layout 165 | 166 | 한글 자판을 설정합니다. 167 | 168 | | 기본값 |`dubeolsik`| 169 | |--------|-------| 170 | 171 | ### 내장된 자판들 172 | 173 | * `direct` 174 | * `qwerty` 175 | * `colmak` 176 | * `dubeolsik`(두벌식) 177 | * `sebeolsik-3-90`(세벌식 390) 178 | * `sebeolsik-3-91`(세벌식 최종) 179 | * `sebeolsik-3sin-1995`(신세벌식 1995) 180 | * `sebeolsik-3sin-p2`(신세벌식 p2 *옛한글은 미구현*) 181 | 182 | `$XDG_CONFIG_HOME/kime/layouts/`에 위 목록에 없는 키보드 자판을 YAML 파일로 직접 만들 수도 있습니다. [dubeolsik.yaml]을 참고해 보세요. 183 | 184 | [dubeolsik.yaml]: ../src/engine/backends/hangul/data/dubeolsik.yaml 185 | 186 | ### preedit_johab 187 | 188 | 편집상태에 조합형을 어느정도로 사용할지 설정합니다. 189 | 190 | | default |`Needed`| 191 | |---------|-------| 192 | 193 | ### word_commit 194 | 195 | 커밋을 단어 단위로 합니다. 196 | 197 | | 기본값 |`false`| 198 | |--------|-------| 199 | 200 | ### addons 201 | 202 | 한글 자판의 추가 기능을 설정 합니다 203 | 204 | 형식은 `자판이름: [Addon]` 입니다 `all`은 모든 자판에 적용됩니다. 205 | 206 | #### 기본값 207 | 208 | ```yaml 209 | all: 210 | - ComposeChoseongSsang 211 | dubeolsik: 212 | - TreatJongseongAsChoseong 213 | ``` 214 | 215 | #### Addons 216 | 217 | ##### TreatJongseongAsChoseong 218 | 219 | 종성을 초성처럼 취급합니다. 220 | 221 | ```txt 222 | 간 + ㅏ = 가나 223 | 값 + ㅏ = 갑사 224 | ``` 225 | 226 | ##### TreatJongseongAsChoseongCompose 227 | 228 | 이전 종성과 현재 초성을 조합합니다. 229 | 230 | 참고로 이건 다른 애드온들에 따라 달라집니다 이 예제는 `ComposeChoseongSsang`이 켜져있어야 작동합니다 231 | 232 | ```txt 233 | 읅 + ㄱ = 을ㄲ 234 | 앇 + ㅅ = 악ㅆ 235 | ``` 236 | 237 | ##### FlexibleComposeOrder 238 | 239 | 초성, 중성, 종성의 순서를 바꿔도 조합이 되도록 합니다 오타 교정에 도움이 될 수 있습니다. 240 | 241 | ```txt 242 | ㅏ + ㄱ = 가 243 | ㅚ + ㄱ = 괴 244 | ㅏ + $ㅁ + ㅁ = 맘 245 | ``` 246 | 247 | ##### ComposeChoseongSsang 248 | 249 | 같은 자음을 두 번 누를 때 쌍자음을 합성합니다. 250 | 251 | ```txt 252 | ㄱ + ㄱ = ㄲ 253 | ㅅ + ㅅ = ㅆ 254 | ㄷ + ㄷ = ㄸ 255 | ㅂ + ㅂ = ㅃ 256 | ㅈ + ㅈ = ㅉ 257 | ``` 258 | 259 | ##### DecomposeChoseongSsang 260 | 261 | 쌍자음에 백스페이스를 누를 때 쌍자음을 분해시킵니다. (e.g. ㄲ -> ㄱ) 262 | 263 | ##### ComposeJungseongSsang 264 | 265 | ```txt 266 | ㅑ + ㅣ = ㅒ 267 | ㅕ + ㅣ = ㅖ 268 | ``` 269 | 270 | ##### DecomposeJungseongSsang 271 | 272 | ##### ComposeJongseongSsang 273 | 274 | ```txt 275 | ㄱ + ㄱ = ㄲ 276 | ㅅ + ㅅ = ㅆ 277 | ``` 278 | 279 | ##### DecomposeJongseongSsang 280 | -------------------------------------------------------------------------------- /src/engine/core/src/config.rs: -------------------------------------------------------------------------------- 1 | use crate::KeyMap; 2 | use fontdb::{Family, Query}; 3 | pub use kime_engine_config::*; 4 | use std::fs; 5 | 6 | /// Preprocessed engine config 7 | pub struct Config { 8 | pub translation_layer: Option>, 9 | pub default_category: InputCategory, 10 | pub global_category_state: bool, 11 | pub category_hotkeys: EnumMap>, 12 | pub mode_hotkeys: EnumMap>, 13 | pub candidate_font: (Vec, u32), 14 | pub xim_preedit_font: (Vec, u32, f32), 15 | pub hangul_data: HangulData, 16 | pub preferred_direct: bool, 17 | pub latin_data: LatinData, 18 | } 19 | 20 | impl Default for Config { 21 | fn default() -> Self { 22 | Self::new(EngineConfig::default()) 23 | } 24 | } 25 | 26 | impl Config { 27 | fn new_impl(mut engine: EngineConfig, hangul_data: HangulData) -> Self { 28 | let mut db = fontdb::Database::new(); 29 | db.load_system_fonts(); 30 | 31 | let load_font = |name| { 32 | db.query(&Query { 33 | families: &[Family::Name(name), Family::Name("D2Coding")], 34 | ..Default::default() 35 | }) 36 | .and_then(|id| db.with_face_data(id, |data, index| (data.to_vec(), index))) 37 | .unwrap_or_default() 38 | }; 39 | 40 | #[cfg(unix)] 41 | let translation_layer: Option> = engine 42 | .translation_layer 43 | .and_then(|f| { 44 | xdg::BaseDirectories::with_prefix("kime") 45 | .ok() 46 | .and_then(|d| d.find_config_file(f)) 47 | }) 48 | .as_ref() 49 | .and_then(|f| fs::read_to_string(f.as_path()).ok()) 50 | .as_ref() 51 | .and_then(|content| serde_yaml::from_str(content).ok()); 52 | 53 | #[cfg(not(unix))] 54 | let translation_layer = None; 55 | 56 | Self { 57 | translation_layer: translation_layer, 58 | default_category: engine.default_category, 59 | global_category_state: engine.global_category_state, 60 | category_hotkeys: enum_map! { 61 | cat => { 62 | if let Some(map) = engine.category_hotkeys.get_mut(&cat) { for (k, v) in engine.global_hotkeys.iter() { 63 | map.entry(*k).or_insert(*v); 64 | } 65 | map.iter().map(|(k, v)| (*k, *v)).collect() 66 | } else { 67 | engine.global_hotkeys.iter().map(|(k, v)| (*k, *v)).collect() 68 | } 69 | } 70 | }, 71 | mode_hotkeys: enum_map! { 72 | mode => { 73 | if let Some(map) = engine.mode_hotkeys.get_mut(&mode) { 74 | for (k, v) in engine.global_hotkeys.iter() { 75 | map.entry(*k).or_insert(*v); 76 | } 77 | map.iter().map(|(k, v)| (*k, *v)).collect() 78 | } else { 79 | engine.global_hotkeys.iter().map(|(k, v)| (*k, *v)).collect() 80 | } 81 | } 82 | }, 83 | xim_preedit_font: { 84 | let (font, index) = load_font(&engine.xim_preedit_font.0); 85 | (font, index, engine.xim_preedit_font.1) 86 | }, 87 | candidate_font: { 88 | let (font, index) = load_font(&engine.candidate_font); 89 | (font, index) 90 | }, 91 | preferred_direct: engine.latin.preferred_direct, 92 | latin_data: LatinData::new(&engine.latin), 93 | hangul_data, 94 | } 95 | } 96 | 97 | pub fn new(engine: EngineConfig) -> Self { 98 | let hangul_data = HangulData::new( 99 | &engine.hangul, 100 | kime_engine_backend_hangul::builtin_layouts(), 101 | ); 102 | 103 | Self::new_impl(engine, hangul_data) 104 | } 105 | 106 | #[cfg(unix)] 107 | pub fn from_engine_config_with_dir(engine: EngineConfig, dir: &xdg::BaseDirectories) -> Self { 108 | let hangul_data = HangulData::from_config_with_dir(&engine.hangul, dir); 109 | Self::new_impl(engine, hangul_data) 110 | } 111 | } 112 | 113 | #[cfg(unix)] 114 | pub fn load_raw_config_from_config_dir() -> RawConfig { 115 | let dir = xdg::BaseDirectories::with_prefix("kime").ok(); 116 | 117 | dir.and_then(|dir| dir.find_config_file("config.yaml")) 118 | .and_then(|config| serde_yaml::from_reader(std::fs::File::open(config).ok()?).ok()) 119 | .unwrap_or_default() 120 | } 121 | 122 | #[cfg(unix)] 123 | pub fn load_engine_config_from_config_dir() -> Option { 124 | let dir = xdg::BaseDirectories::with_prefix("kime").ok()?; 125 | let config: RawConfig = dir 126 | .find_config_file("config.yaml") 127 | .and_then(|config| serde_yaml::from_reader(std::fs::File::open(config).ok()?).ok()) 128 | .unwrap_or_default(); 129 | 130 | Some(Config::from_engine_config_with_dir(config.engine, &dir)) 131 | } 132 | -------------------------------------------------------------------------------- /src/engine/backends/math/src/lib.rs: -------------------------------------------------------------------------------- 1 | use kime_engine_backend::{ 2 | InputEngineMode, 3 | InputEngineModeResult::{self, Continue}, 4 | Key, KeyCode, 5 | }; 6 | use kime_engine_backend_latin::LatinData; 7 | use kime_engine_dict::math_symbol_key::*; 8 | 9 | #[cfg(test)] 10 | mod tests { 11 | #[test] 12 | fn test_parse_style() { 13 | use kime_engine_dict::math_symbol_key::*; 14 | 15 | assert_eq!(crate::parse_style("sf"), Style::SF); 16 | assert_eq!(crate::parse_style("bf"), Style::BF); 17 | assert_eq!(crate::parse_style("it"), Style::IT); 18 | assert_eq!(crate::parse_style("tt"), Style::TT); 19 | assert_eq!(crate::parse_style("bb"), Style::BB); 20 | assert_eq!(crate::parse_style("scr"), Style::SCR); 21 | assert_eq!(crate::parse_style("cal"), Style::CAL); 22 | assert_eq!(crate::parse_style("frak"), Style::FRAK); 23 | assert_eq!(crate::parse_style("fruk"), Style::NONE); 24 | assert_eq!(crate::parse_style("bfit"), Style::BF | Style::IT); 25 | assert_eq!( 26 | crate::parse_style("bfsfit"), 27 | Style::SF | Style::BF | Style::IT 28 | ); 29 | } 30 | } 31 | 32 | #[derive(Clone)] 33 | pub struct MathMode { 34 | math_mode: bool, 35 | buf: String, 36 | } 37 | 38 | impl MathMode { 39 | pub fn new() -> Self { 40 | Self { 41 | math_mode: false, 42 | buf: String::with_capacity(16), 43 | } 44 | } 45 | } 46 | 47 | fn parse_style(style_str: &str) -> Style { 48 | let mut buf: &str = style_str; 49 | let mut style = Style::NONE; 50 | 51 | loop { 52 | let style_new = if buf.is_empty() { 53 | return style; 54 | } else if let Some(_buf) = buf.strip_prefix("sf") { 55 | buf = _buf; 56 | Style::SF 57 | } else if let Some(_buf) = buf.strip_prefix("bf") { 58 | buf = _buf; 59 | Style::BF 60 | } else if let Some(_buf) = buf.strip_prefix("it") { 61 | buf = _buf; 62 | Style::IT 63 | } else if let Some(_buf) = buf.strip_prefix("tt") { 64 | buf = _buf; 65 | Style::TT 66 | } else if let Some(_buf) = buf.strip_prefix("bb") { 67 | buf = _buf; 68 | Style::BB 69 | } else if let Some(_buf) = buf.strip_prefix("scr") { 70 | buf = _buf; 71 | Style::SCR 72 | } else if let Some(_buf) = buf.strip_prefix("cal") { 73 | buf = _buf; 74 | Style::CAL 75 | } else if let Some(_buf) = buf.strip_prefix("frak") { 76 | buf = _buf; 77 | Style::FRAK 78 | } else { 79 | return Style::NONE; 80 | }; 81 | 82 | style |= style_new; 83 | } 84 | } 85 | 86 | impl InputEngineMode for MathMode { 87 | type ConfigData = LatinData; 88 | 89 | fn press_key( 90 | &mut self, 91 | config: &LatinData, 92 | key: Key, 93 | commit_buf: &mut String, 94 | ) -> InputEngineModeResult { 95 | if key == Key::normal(KeyCode::Backslash) { 96 | if self.math_mode { 97 | // double backslash 98 | self.math_mode = false; 99 | commit_buf.push('\\'); 100 | } else { 101 | self.math_mode = true; 102 | } 103 | 104 | return Continue(true); 105 | } 106 | 107 | if self.math_mode && key.code == KeyCode::Backspace { 108 | if self.buf.pop().is_none() { 109 | self.math_mode = false; 110 | } 111 | 112 | return Continue(true); 113 | } 114 | 115 | if let Some(ch) = config.lookup(key) { 116 | if self.math_mode { 117 | self.buf.push(ch); 118 | } else { 119 | commit_buf.push(ch); 120 | } 121 | 122 | Continue(true) 123 | } else { 124 | Continue(false) 125 | } 126 | } 127 | 128 | fn clear_preedit(&mut self, commit_buf: &mut String) -> InputEngineModeResult<()> { 129 | let mut iter = self.buf.split('.'); 130 | if let Some(first) = iter.next() { 131 | if let Some(second) = iter.next() { 132 | let style = parse_style(first); 133 | if let Some(symbol) = kime_engine_dict::lookup_math_symbol(&second, style) { 134 | commit_buf.push_str(symbol); 135 | } 136 | } else { 137 | if let Some(symbol) = kime_engine_dict::lookup_math_symbol(&first, Style::NONE) { 138 | commit_buf.push_str(symbol); 139 | } 140 | } 141 | } 142 | 143 | self.buf.clear(); 144 | self.math_mode = false; 145 | Continue(()) 146 | } 147 | 148 | fn reset(&mut self) -> InputEngineModeResult<()> { 149 | self.buf.clear(); 150 | self.math_mode = false; 151 | Continue(()) 152 | } 153 | 154 | fn preedit_str(&self, buf: &mut String) { 155 | if self.math_mode { 156 | buf.push('\\'); 157 | buf.push_str(&self.buf); 158 | } 159 | } 160 | 161 | fn has_preedit(&self) -> bool { 162 | self.math_mode 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /README.ko.md: -------------------------------------------------------------------------------- 1 | # kime 2 | 3 | [](https://github.com/Riey/kime) 4 | 5 | 한글 입력기 6 | 7 | ## 다른 언어로 보기 8 | 9 | [English](./README.md), [**한국어**](./README.ko.md) 10 | 11 | --- 12 | 13 | [build](https://github.com/Riey/kime/actions?query=workflow%3ACI) 14 | [discord](https://discord.gg/YPnEfZqC6y) 15 | [release version](https://github.com/Riey/kime/releases) 16 | [aur version](https://aur.archlinux.org/packages/kime/) 17 | [license](https://github.com/Riey/kime/blob/master/LICENSE) 18 | 19 | ## [Changelog](docs/CHANGELOG.md) 20 | 21 | ## kime을 써야 하는 이유? 22 | 23 | * 잘 테스트된 입력 엔진 24 | * 빠른 [속도](https://github.com/Riey/kime/wiki/Performance) 25 | * 적은 메모리 사용량 26 | * 대부분의 코드가 세그멘테이션 오류가 없는 Rust로 작성됨 27 | * 사용자 설정 자판 지원 28 | 29 | ## 궁금한 게 있으신가요? 30 | 31 | [디스코드](https://discord.gg/YPnEfZqC6y) 채널에 와서 연락하시거나 이슈를 올려주세요. 32 | 33 | ## 지원되는 프론트엔드 34 | 35 | - [x] XIM 36 | - [x] Wayland 37 | - [x] GTK3 38 | - [x] GTK4 39 | - [x] Qt5 40 | - [x] Qt6 41 | 42 | ## 설치 43 | 44 | ### NixOS 45 | 46 | 이 코드를 configuration.nix에 추가해주세요 47 | 48 | ```nix 49 | i18n = { 50 | defaultLocale = "en_US.UTF-8"; 51 | inputMethod = { 52 | enable = true; 53 | type = "kime"; 54 | kime.iconColor = "White"; 55 | }; 56 | }; 57 | }; 58 | ``` 59 | 60 | ### 아치 리눅스 61 | 62 | 최신 릴리스는 AUR의 [kime](https://aur.archlinux.org/packages/kime)에 있으며, 만약 소스에서 빌드하시려면 [kime-git](https://aur.archlinux.org/packages/kime-git)에서 설치할 수 있습니다. 63 | 64 | ### 데비안, 우분투 65 | 66 | [releases](https://github.com/Riey/kime/releases) 탭에 있는 .deb 파일을 설치할 수 있습니다. 67 | 68 | ### 페도라 69 | 70 | 비공식 패키지가 [Fedora Copr](https://copr.fedorainfracloud.org/coprs/toroidalfox/kime/) 에서 운영되고 있습니다. 71 | 72 | ```sh 73 | dnf copr enable toroidalfox/kime 74 | dnf install kime # 개발 버전은 `kime-git` 75 | ``` 76 | 77 | ### 젠투 78 | 79 | ```sh 80 | eselect repository add riey git https://github.com/Riey/overlay 81 | eselect repository enable riey 82 | emaint sync -r riey 83 | emerge -av kime 84 | ``` 85 | 86 | ### 소스에서 빌드하기 87 | 88 | ### 도커 89 | 90 | 도커를 쓰시는 경우엔 따로 의존성을 설치하지 않아도 되어서 편리합니다. 91 | 92 | ```sh 93 | git clone https://github.com/riey/kime 94 | cd kime 95 | 96 | export DOCKER_BUILDKIT=0 97 | export COMPOSE_DOCKER_CLI_BUILD=0 98 | # 도커 빌드시 Buildkit을 쓰지 않고 레거시 방식으로 빌드하기 위함 99 | 100 | docker build --file build-docker/<배포판 경로>/Dockerfile --tag kime-build:git . 101 | docker run --name kime kime-build:git 102 | docker cp kime:/opt/kime-out/kime.tar.xz . 103 | # deb 파일을 얻으시려면 대신 이 명령어를 실행하세요 104 | # docker cp kime:/opt/kime-out/kime_amd64.deb . 105 | ``` 106 | 107 | ### 직접 빌드 108 | 109 | 빌드하기 전에 **cargo** 및 아래 나열되어 있는 기타 종속성이 설치되어 있는지 확인하세요. 110 | 111 | ```sh 112 | git clone https://github.com/riey/kime 113 | cd kime 114 | 115 | scripts/build.sh -ar 116 | ``` 117 | 118 | 이제 모든 파일은 build/out 경로에 있습니다. 만약 수동 설치를 원하시면 쓰시면 됩니다. 119 | 120 | `scripts/install.sh ` 스크립트를 쓸 수도 있습니다. 패키징할 때 유용합니다. 121 | 122 | `scripts/release-deb.sh ` 스크립트를 사용하시면 `deb` 파일을 생성합니다. 123 | 124 | #### GTK 125 | 126 | 대부분 배포판들은 이걸 자동으로 해주므로 127 | 128 | 패키지로 설치하실 경우에는 필요 없을 수도 있습니다. 129 | 130 | ```sh 131 | # GTK3 설치 시 132 | sudo gtk-query-immodules-3.0 --update-cache 133 | # GTK4 설치 시 134 | sudo gio-querymodules /usr/lib/gtk-4.0/4.0.0/immodules 135 | ``` 136 | 137 | ## 개발 138 | 139 | ### C/C++ 140 | 141 | `./scripts/generate_properties.sh`을 실행해서 vscode에서 C/C++ 코드의 인텔리센스 기능을 사용할 수 있습니다. 142 | 143 | ## 설정 144 | 145 | ### 데비안 계열 146 | 147 | 언어 설정에서 입력기 `kime`를 선택해주세요. 148 | 149 | ### 그 외 150 | 151 | init 스크립트에 다음을 추가하세요. 152 | 153 | ```sh 154 | export GTK_IM_MODULE=kime 155 | export QT_IM_MODULE=kime 156 | export XMODIFIERS=@im=kime 157 | ``` 158 | 159 | 만약 X를 사용하신다면 .xprofile에 설정하시면 됩니다. 160 | 161 | ### 추가적인 서버를 실행 162 | 163 | kime은 kime 데몬을 위한 kime.desktop 파일을 /etc/xdg/autostart에 설치합니다 164 | 165 | 혹시 `i3`나 `sway`처럼 `시작 프로그램`을 지원하지 않는다면 해당 WM의 설정파일에서 `kime` 혹은 원하시는 서버 커맨드를 실행해주세요 166 | 167 | ### KDE Plasma Wayland 168 | 169 | 시스템 설정 > 입력과 출력 > 키보드 > 가상 키보드에서 `kime 데몬`을 선택해야 합니다. 170 | 이후에 로그아웃 후 재로그인을 하는 것을 권장합니다. 171 | 172 | ### Weston 173 | `~/.config/weston.ini`에 해당 내용이 있어야 합니다. 174 | ``` 175 | [input-method] 176 | path=/usr/bin/kime 177 | ``` 178 | 179 | ### Configuration 180 | 181 | 자세한 옵션은 [CONFIGURATION.md](docs/CONFIGURATION.ko.md)를 참고하세요. 182 | 183 | ## 종속성 목록 184 | 185 | ### 런타임 종속성 186 | 187 | 참고로 필요하신 종속성만 있으면 됩니다 188 | 예를 들어 qt6를 사용하지 않으신다면 필요하지 않습니다. 189 | 190 | * gtk3 191 | * gtk4 192 | * qt5 193 | * qt6 194 | * libdbus (indicator) 195 | * xcb (candidate) 196 | * fontconfig (xim) 197 | * freetype (xim) 198 | * libxkbcommon (wayland) 199 | 200 | ### 빌드타임 종속성 (바이너리 실행 시엔 필요 없습니다) 201 | 202 | #### 필수 203 | 204 | * cmake 205 | * cargo 206 | * libclang 207 | * pkg-config 208 | 209 | #### 선택적 210 | 211 | * gtk3 212 | * gtk4 213 | * qtbase5-private 214 | * qtbase6-private 215 | * libdbus 216 | * xcb 217 | * fontconfig 218 | * freetype 219 | * libxkbcommon 220 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /docs/CONFIGURATION.md: -------------------------------------------------------------------------------- 1 | # config.yaml 2 | 3 | [English](CONFIGURATION.md), [한국어](CONFIGURATION.ko.md) 4 | 5 | Sample config file is located at `/usr/share/doc/kime/default_config.yaml`. Check 6 | [default_config.yaml](../res/default_config.yaml) to see the default configuration 7 | file. Copy this file to `/etc/xdg/kime/config.yaml` for global default configuration. 8 | You may create per user file at `~/.config/kime/config.yaml`. 9 | 10 | You can also change the location of config file using [`$XDG_CONFIG_DIR` or 11 | `$XDG_CONFIG_HOME`][xdg] environment variable. kime will try to read 12 | `$XDG_CONFIG_DIR/kime/config.yaml` and `$XDG_CONFIG_HOME/kime/config.yaml` too. 13 | 14 | [xdg]: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html#introduction 15 | 16 | # log 17 | 18 | Set kime programs logging level 19 | 20 | Please select one of `OFF`, `ERROR`, `WARN`, `INFO`, `DEBUG`, `TRACE` 21 | 22 | ## global_level 23 | 24 | # daemon 25 | 26 | `kime` daemon setting 27 | 28 | ## modules 29 | 30 | List of daemon modules default is *all* 31 | 32 | * Xim 33 | * Wayland 34 | * Indicator 35 | 36 | # indicator 37 | 38 | `kime-indicator` setting 39 | 40 | ## icon_color 41 | 42 | Set icon color for indicator 43 | 44 | ### Possible values 45 | 46 | * Black 47 | * White 48 | 49 | | default |`Black`| 50 | |---------|-------| 51 | 52 | # engine 53 | 54 | `kime-engine` setting 55 | 56 | ## translation_layer 57 | 58 | Set keycode translation layer useful when you're using special keyboard. 59 | 60 | | default |`null`| 61 | |---------|-------| 62 | 63 | ## default_category 64 | 65 | Set default InputCategory when IME starts, please select between `Latin` and `Hangul` 66 | 67 | | default |`Latin`| 68 | |---------|-------| 69 | 70 | ## global_category_state 71 | 72 | Set category state globally 73 | 74 | | default |`false`| 75 | |---------|-------| 76 | 77 | ## hotkeys 78 | 79 | Set engine hotkey format is `Key: Content` 80 | 81 | ### global_hotkeys 82 | 83 | Global hotkey 84 | 85 | ### category_hotkeys 86 | 87 | Hotkey for specific category override global hotkey 88 | 89 | ### mode_hotkeys 90 | 91 | Hotkey for specific mode override global, category hotkey 92 | 93 | ### content 94 | 95 | #### behavior 96 | 97 | ##### !Toggle [InputCategory, InputCategory] 98 | 99 | Toggle Left and Right category 100 | 101 | ##### !Switch InputCategory 102 | 103 | Switch to specific category 104 | 105 | ##### !Mode InputMode 106 | 107 | Enable specific mode 108 | 109 | ##### Commit 110 | 111 | End current preedit state then commit 112 | 113 | ##### Ignore 114 | 115 | Do nothing 116 | 117 | #### result 118 | 119 | ##### Bypass 120 | 121 | Bypass key to continue key process 122 | 123 | ##### Consume 124 | 125 | Consume key to end key process 126 | 127 | ##### ConsumeIfProcessed 128 | 129 | When hotkey processed it act like Consume otherwise it act like Bypass 130 | 131 | ## xim_preedit_font 132 | 133 | Preedit window font name and size for XIM 134 | 135 | | default |`[D2Coding, 15.0]`| 136 | |---------|------------------| 137 | 138 | ## latin 139 | 140 | Set latin setting 141 | 142 | ### preferred_direct 143 | 144 | Handling key event by external as possible 145 | 146 | ### layout 147 | 148 | Set latin layout 149 | 150 | | default |`Qwerty`| 151 | |---------|--------| 152 | 153 | ### embedded layouts 154 | 155 | * `Qwerty` 156 | * `Dvorak` 157 | * `Colemak` 158 | 159 | ## hangul 160 | 161 | Set hangul setting 162 | 163 | ### preedit_johab 164 | 165 | Set preedit johab encoding level 166 | 167 | | default |`Needed`| 168 | |---------|-------| 169 | 170 | ### word_commit 171 | 172 | Let commit by word 173 | 174 | | default |`false`| 175 | |---------|-------| 176 | 177 | ### layout 178 | 179 | Set hangul layout 180 | 181 | | default |`dubeolsik`| 182 | |---------|-------| 183 | 184 | #### Embedded layouts 185 | 186 | * `direct` 187 | * `qwerty` 188 | * `colmak` 189 | * `dubeolsik`(두벌식) 190 | * `sebeolsik-3-90`(세벌식 390) 191 | * `sebeolsik-3-91`(세벌식 최종) 192 | * `sebeolsik-3sin-1995`(신세벌식 1995) 193 | * `sebeolsik-3sin-p2`(신세벌식 p2 *옛한글은 미구현*) 194 | 195 | Custom layout can be added by creating layout YAML files 196 | at `$XDG_CONFIG_HOME/kime/layouts/` directory. See [dubeolsik.yaml] for the 197 | structure of keyboard layout file. 198 | 199 | [dubeolsik.yaml]: ../src/engine/backends/hangul/data/dubeolsik.yaml 200 | 201 | ### layout_addons 202 | 203 | Adjust layout addons 204 | 205 | format is `layout_name: [Addon]`, `all` applies all layouts 206 | 207 | #### default 208 | 209 | ```yaml 210 | all: 211 | - ComposeChoseongSsang 212 | dubeolsik: 213 | - TreatJongseongAsChoseongg 214 | ``` 215 | 216 | #### Addons 217 | 218 | ##### TreatJongseongAsChoseong 219 | 220 | Treat jongseong as choseong 221 | 222 | ```txt 223 | 간 + ㅏ = 가나 224 | 값 + ㅏ = 갑사 225 | ``` 226 | 227 | ##### TreatJongseongAsChoseongCompose 228 | 229 | Compose previous jongseong and current choseong 230 | 231 | Note that it depends on other addons this example is only work when `ComposeChoseongSsang` is on 232 | 233 | ```txt 234 | 읅 + ㄱ = 을ㄲ 235 | 앇 + ㅅ = 악ㅆ 236 | ``` 237 | 238 | ##### FlexibleComposeOrder 239 | 240 | Compose choseong, jungseong, and jongseong even order is reversed it could be help for fix typo error. 241 | 242 | ```txt 243 | ㅏ + ㄱ = 가 244 | ㅚ + ㄱ = 괴 245 | ㅏ + $ㅁ + ㅁ = 맘 246 | ``` 247 | 248 | ##### ComposeChoseongSsang 249 | 250 | When you press same choseong it will be ssangjaum 251 | 252 | ```txt 253 | ㄱ + ㄱ = ㄲ 254 | ㅅ + ㅅ = ㅆ 255 | ㄷ + ㄷ = ㄸ 256 | ㅂ + ㅂ = ㅃ 257 | ㅈ + ㅈ = ㅉ 258 | ``` 259 | 260 | ##### DecomposeChoseongSsang 261 | 262 | Same as above but work on backspace(e.g. ㄲ -> ㄱ) 263 | 264 | ##### ComposeJungseongSsang 265 | 266 | ```txt 267 | ㅑ + ㅣ = ㅒ 268 | ㅕ + ㅣ = ㅖ 269 | ``` 270 | 271 | ##### DecomposeJungseongSsang 272 | 273 | ##### ComposeJongseongSsang 274 | 275 | ```txt 276 | ㄱ + ㄱ = ㄲ 277 | ㅅ + ㅅ = ㅆ 278 | ``` 279 | 280 | #### DecomposeJongseongSsang 281 | -------------------------------------------------------------------------------- /src/engine/core/tests/dubeolsik.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod shared; 3 | 4 | define_layout_test!("dubeolsik"); 5 | 6 | #[test] 7 | fn flexible_compose_order_addon() { 8 | test_input_with_addon( 9 | &[(Key::normal(K), "ㅏ", ""), (Key::normal(R), "가", "")], 10 | EnumSet::only(Addon::FlexibleComposeOrder), 11 | ); 12 | } 13 | 14 | #[test] 15 | fn strict_typing_order() { 16 | test_input(&[(Key::normal(K), "ㅏ", ""), (Key::normal(R), "ㄱ", "ㅏ")]) 17 | } 18 | 19 | #[test] 20 | fn treat_jongseong_as_choseong_compose_addon() { 21 | test_input_with_addon( 22 | &[ 23 | (Key::normal(D), "ㅇ", ""), 24 | (Key::normal(M), "으", ""), 25 | (Key::normal(F), "을", ""), 26 | (Key::normal(R), "읅", ""), 27 | (Key::normal(R), "ㄲ", "을"), 28 | ], 29 | Addon::ComposeChoseongSsang | Addon::TreatJongseongAsChoseongCompose, 30 | ); 31 | } 32 | 33 | #[test] 34 | fn word_hello() { 35 | test_word_input(&[ 36 | (Key::normal(D), "ㅇ", ""), 37 | (Key::normal(K), "아", ""), 38 | (Key::normal(S), "안", ""), 39 | (Key::normal(S), "안ㄴ", ""), 40 | (Key::normal(U), "안녀", ""), 41 | (Key::normal(D), "안녕", ""), 42 | (Key::normal(Esc), "", "안녕PASS"), 43 | ]) 44 | } 45 | 46 | #[test] 47 | fn esc() { 48 | test_input(&[ 49 | (Key::normal(R), "ㄱ", ""), 50 | (Key::normal(Esc), "", "ㄱPASS"), 51 | (Key::normal(R), "", "PASS"), 52 | ]); 53 | } 54 | 55 | // issue #410 56 | #[test] 57 | fn other_keys() { 58 | let pass_keys = [Esc, PageUp, PageDown, Home, End]; 59 | 60 | for key in pass_keys.iter().copied() { 61 | test_input(&[(Key::normal(R), "ㄱ", ""), (Key::normal(key), "", "ㄱPASS")]); 62 | } 63 | } 64 | 65 | // issue #373 66 | #[test] 67 | fn arrow() { 68 | test_input(&[ 69 | (Key::normal(R), "ㄱ", ""), 70 | (Key::normal(Left), "", "ㄱPASS"), 71 | ]); 72 | } 73 | 74 | // issue #418 75 | #[test] 76 | fn shift_bypass() { 77 | test_input(&[ 78 | (Key::normal(R), "ㄱ", ""), 79 | (Key::normal(Shift), "ㄱ", "PASS"), 80 | (Key::shift(Shift), "ㄱ", "PASS"), 81 | (Key::super_(Shift), "ㄱ", "PASS"), 82 | (Key::alt(Shift), "ㄱ", "PASS"), 83 | (Key::shift(O), "걔", ""), 84 | ]) 85 | } 86 | 87 | #[test] 88 | fn ctrl_w() { 89 | test_input(&[(Key::normal(R), "ㄱ", ""), (Key::ctrl(W), "", "ㄱPASS")]); 90 | } 91 | 92 | #[test] 93 | fn next_jaum() { 94 | test_input(&[ 95 | (Key::normal(D), "ㅇ", ""), 96 | (Key::normal(K), "아", ""), 97 | (Key::normal(D), "앙", ""), 98 | (Key::normal(E), "ㄷ", "앙"), 99 | ]) 100 | } 101 | 102 | #[test] 103 | fn next_ssangjaum() { 104 | test_input(&[ 105 | (Key::normal(A), "ㅁ", ""), 106 | (Key::normal(K), "마", ""), 107 | (Key::shift(T), "맜", ""), 108 | (Key::normal(K), "싸", "마"), 109 | ]) 110 | } 111 | 112 | #[test] 113 | fn not_com_moum_when_continue() { 114 | test_input(&[ 115 | (Key::normal(D), "ㅇ", ""), 116 | (Key::normal(H), "오", ""), 117 | (Key::normal(D), "옹", ""), 118 | (Key::normal(K), "아", "오"), 119 | ]); 120 | } 121 | 122 | #[test] 123 | fn com_moum() { 124 | test_input(&[ 125 | (Key::normal(D), "ㅇ", ""), 126 | (Key::normal(H), "오", ""), 127 | (Key::normal(L), "외", ""), 128 | (Key::normal(D), "욍", ""), 129 | (Key::normal(D), "ㅇ", "욍"), 130 | (Key::normal(K), "아", ""), 131 | (Key::normal(S), "안", ""), 132 | (Key::normal(G), "않", ""), 133 | (Key::normal(E), "ㄷ", "않"), 134 | ]); 135 | } 136 | 137 | #[test] 138 | fn number() { 139 | test_input(&[ 140 | (Key::normal(D), "ㅇ", ""), 141 | (Key::normal(H), "오", ""), 142 | (Key::normal(L), "외", ""), 143 | (Key::normal(D), "욍", ""), 144 | (Key::normal(D), "ㅇ", "욍"), 145 | (Key::normal(K), "아", ""), 146 | (Key::normal(S), "안", ""), 147 | (Key::normal(G), "않", ""), 148 | (Key::normal(E), "ㄷ", "않"), 149 | (Key::normal(One), "", "ㄷ1"), 150 | ]); 151 | } 152 | 153 | #[test] 154 | fn exclamation_mark() { 155 | test_input(&[(Key::shift(R), "ㄲ", ""), (Key::shift(One), "", "ㄲ!")]); 156 | } 157 | 158 | #[test] 159 | fn backspace() { 160 | test_input(&[ 161 | (Key::normal(R), "ㄱ", ""), 162 | (Key::normal(K), "가", ""), 163 | (Key::normal(D), "강", ""), 164 | (Key::normal(Backspace), "가", ""), 165 | (Key::normal(Q), "갑", ""), 166 | (Key::normal(T), "값", ""), 167 | (Key::normal(Backspace), "갑", ""), 168 | (Key::normal(Backspace), "가", ""), 169 | (Key::normal(Backspace), "ㄱ", ""), 170 | (Key::normal(Backspace), "", ""), 171 | (Key::normal(D), "ㅇ", ""), 172 | (Key::normal(H), "오", ""), 173 | (Key::normal(L), "외", ""), 174 | (Key::normal(Backspace), "오", ""), 175 | (Key::normal(Backspace), "ㅇ", ""), 176 | (Key::normal(Backspace), "", ""), 177 | (Key::normal(D), "ㅇ", ""), 178 | (Key::normal(H), "오", ""), 179 | (Key::normal(K), "와", ""), 180 | (Key::normal(Backspace), "오", ""), 181 | (Key::normal(Backspace), "ㅇ", ""), 182 | (Key::normal(Backspace), "", ""), 183 | (Key::normal(R), "ㄱ", ""), 184 | ]) 185 | } 186 | 187 | #[test] 188 | fn compose_jong() { 189 | test_input(&[ 190 | (Key::normal(D), "ㅇ", ""), 191 | (Key::normal(J), "어", ""), 192 | (Key::normal(Q), "업", ""), 193 | (Key::normal(T), "없", ""), 194 | ]) 195 | } 196 | 197 | #[test] 198 | fn backspace_moum_compose() { 199 | test_input(&[ 200 | (Key::normal(D), "ㅇ", ""), 201 | (Key::normal(H), "오", ""), 202 | (Key::normal(K), "와", ""), 203 | (Key::normal(Backspace), "오", ""), 204 | (Key::normal(Backspace), "ㅇ", ""), 205 | ]) 206 | } 207 | -------------------------------------------------------------------------------- /src/engine/backends/hangul/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod characters; 2 | mod layout; 3 | mod state; 4 | 5 | use std::{borrow::Cow, collections::BTreeMap}; 6 | 7 | use enumset::{EnumSet, EnumSetType}; 8 | use kime_engine_backend::{InputEngineBackend, Key, KeyCode}; 9 | use serde::{Deserialize, Serialize}; 10 | 11 | pub use layout::Layout; 12 | pub use state::HangulEngine; 13 | 14 | #[derive(Hash, Serialize, Deserialize, Debug, EnumSetType)] 15 | #[enumset(serialize_repr = "list")] 16 | pub enum Addon { 17 | ComposeChoseongSsang, 18 | ComposeJungseongSsang, 19 | ComposeJongseongSsang, 20 | DecomposeChoseongSsang, 21 | DecomposeJungseongSsang, 22 | DecomposeJongseongSsang, 23 | 24 | /// ㅏ + ㄱ = 가 25 | /// ㄱ + $ㄱ + ㅏ = 각 26 | FlexibleComposeOrder, 27 | 28 | /// 안 + ㅣ = 아니 29 | TreatJongseongAsChoseong, 30 | /// 읅 + ㄱ = 을ㄲ 31 | TreatJongseongAsChoseongCompose, 32 | } 33 | 34 | #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] 35 | pub enum PreeditJohabLevel { 36 | /// Always use johab encoding 37 | Always, 38 | /// Use johab encoding when it's needed 39 | Needed, 40 | /// Never use johab encoding 41 | Never, 42 | } 43 | 44 | impl Default for PreeditJohabLevel { 45 | fn default() -> Self { 46 | PreeditJohabLevel::Needed 47 | } 48 | } 49 | 50 | #[derive(Serialize, Deserialize)] 51 | #[serde(default)] 52 | pub struct HangulConfig { 53 | pub layout: String, 54 | pub word_commit: bool, 55 | pub preedit_johab: PreeditJohabLevel, 56 | pub addons: BTreeMap>, 57 | } 58 | 59 | impl Default for HangulConfig { 60 | fn default() -> Self { 61 | Self { 62 | layout: "dubeolsik".into(), 63 | word_commit: false, 64 | preedit_johab: PreeditJohabLevel::default(), 65 | addons: vec![ 66 | ("all".into(), Addon::ComposeChoseongSsang.into()), 67 | ("dubeolsik".into(), Addon::TreatJongseongAsChoseong.into()), 68 | ] 69 | .into_iter() 70 | .collect(), 71 | } 72 | } 73 | } 74 | 75 | pub const BUILTIN_LAYOUTS: &'static [(&'static str, &'static str)] = &[ 76 | ("dubeolsik", include_str!("../data/dubeolsik.yaml")), 77 | ( 78 | "sebeolsik-3-90", 79 | include_str!("../data/sebeolsik-3-90.yaml"), 80 | ), 81 | ( 82 | "sebeolsik-3-91", 83 | include_str!("../data/sebeolsik-3-91.yaml"), 84 | ), 85 | ( 86 | "sebeolsik-3sin-1995", 87 | include_str!("../data/sebeolsik-3sin-1995.yaml"), 88 | ), 89 | ( 90 | "sebeolsik-3sin-p2", 91 | include_str!("../data/sebeolsik-3sin-p2.yaml"), 92 | ), 93 | ]; 94 | 95 | pub struct HangulData { 96 | layout: Layout, 97 | addons: EnumSet, 98 | preedit_johab: PreeditJohabLevel, 99 | word_commit: bool, 100 | } 101 | 102 | impl Default for HangulData { 103 | fn default() -> Self { 104 | Self::new(&HangulConfig::default(), builtin_layouts()) 105 | } 106 | } 107 | 108 | impl HangulData { 109 | #[cfg(unix)] 110 | pub fn from_config_with_dir(config: &HangulConfig, dir: &xdg::BaseDirectories) -> Self { 111 | let custom_layouts = dir 112 | .list_config_files("layouts") 113 | .into_iter() 114 | .filter_map(|path| { 115 | let name = path.file_stem()?.to_str()?; 116 | 117 | Layout::load_from(std::fs::read_to_string(&path).ok()?.as_str()) 118 | .ok() 119 | .map(move |l| (name.to_string().into(), l)) 120 | }); 121 | 122 | Self::new(config, custom_layouts.chain(builtin_layouts())) 123 | } 124 | 125 | pub fn new( 126 | config: &HangulConfig, 127 | mut layouts: impl Iterator, Layout)>, 128 | ) -> Self { 129 | Self { 130 | layout: layouts 131 | .find_map(|(name, layout)| { 132 | if name == config.layout { 133 | Some(layout) 134 | } else { 135 | None 136 | } 137 | }) 138 | .unwrap_or_default(), 139 | addons: config.addons.get("all").copied().unwrap_or_default().union( 140 | config 141 | .addons 142 | .get(&config.layout) 143 | .copied() 144 | .unwrap_or_default(), 145 | ), 146 | preedit_johab: config.preedit_johab, 147 | word_commit: config.word_commit, 148 | } 149 | } 150 | 151 | pub const fn preedit_johab(&self) -> PreeditJohabLevel { 152 | self.preedit_johab 153 | } 154 | 155 | pub const fn word_commit(&self) -> bool { 156 | self.word_commit 157 | } 158 | } 159 | 160 | impl InputEngineBackend for HangulEngine { 161 | type ConfigData = HangulData; 162 | 163 | fn press_key(&mut self, config: &HangulData, key: Key, commit_buf: &mut String) -> bool { 164 | if key.code == KeyCode::Backspace { 165 | self.backspace(config.addons, commit_buf) 166 | } else if let Some(kv) = config.layout.lookup_kv(key) { 167 | self.key(kv, config.addons, commit_buf) 168 | } else { 169 | false 170 | } 171 | } 172 | 173 | #[inline] 174 | fn clear_preedit(&mut self, commit_buf: &mut String) { 175 | self.clear_preedit(commit_buf); 176 | } 177 | 178 | #[inline] 179 | fn reset(&mut self) { 180 | self.reset(); 181 | } 182 | 183 | #[inline] 184 | fn has_preedit(&self) -> bool { 185 | self.has_preedit() 186 | } 187 | 188 | fn preedit_str(&self, buf: &mut String) { 189 | self.preedit_str(buf); 190 | } 191 | } 192 | 193 | pub fn builtin_layouts() -> impl Iterator, Layout)> { 194 | BUILTIN_LAYOUTS 195 | .iter() 196 | .copied() 197 | .filter_map(|(name, layout)| Layout::load_from(layout).ok().map(|l| (name.into(), l))) 198 | } 199 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kime 2 | 3 | [](https://github.com/Riey/kime) 4 | 5 | Korean IME 6 | 7 | ## View in other languages 8 | 9 | [**English**](./README.md), [한국어](./README.ko.md) 10 | 11 | --- 12 | 13 | [build](https://github.com/Riey/kime/actions?query=workflow%3ACI) 14 | [discord](https://discord.gg/YPnEfZqC6y) 15 | [release version](https://github.com/Riey/kime/releases) 16 | [aur version](https://aur.archlinux.org/packages/kime-bin/) 17 | [license](https://github.com/Riey/kime/blob/master/LICENSE) 18 | 19 | ## [Changelog](docs/CHANGELOG.md) 20 | 21 | ## Why kime 22 | 23 | * Well tested input engine 24 | * Blazing [fast](https://github.com/Riey/kime/wiki/Performance) 25 | * Small memory footprint 26 | * Written in Rust, no segmentation fault 27 | * Custom layouts 28 | 29 | ## Have a question? 30 | 31 | Please contact us on [Discord](https://discord.gg/YPnEfZqC6y) or create github issue. 32 | 33 | ## Supported frontend 34 | 35 | - [x] XIM 36 | - [x] Wayland 37 | - [x] GTK3 38 | - [x] GTK4 39 | - [x] Qt5 40 | - [x] Qt6 41 | 42 | ## Installation 43 | 44 | ### NixOS 45 | 46 | Add this code to your configuration.nix 47 | 48 | ```nix 49 | i18n = { 50 | defaultLocale = "en_US.UTF-8"; 51 | inputMethod = { 52 | enable = true; 53 | type = "kime"; 54 | kime.iconColor = "White"; 55 | }; 56 | }; 57 | }; 58 | ``` 59 | 60 | ### Arch Linux 61 | 62 | Latest release of kime is available on [`kime` AUR package](https://aur.archlinux.org/packages/kime). 63 | 64 | Developing version is available on [`kime-git`](https://aur.archlinux.org/packages/kime-git). 65 | 66 | ### Debian, Ubuntu 67 | 68 | `.deb` package is available on github [releases](https://github.com/Riey/kime/releases) tab. 69 | 70 | ### Fedora 71 | 72 | The unofficial package is being maintained on [Fedora Copr](https://copr.fedorainfracloud.org/coprs/toroidalfox/kime/). 73 | 74 | ```sh 75 | dnf copr enable toroidalfox/kime 76 | dnf install kime # `kime-git` for bleeding edge 77 | ``` 78 | 79 | ### Gentoo 80 | 81 | ```sh 82 | eselect repository add riey git https://github.com/Riey/overlay 83 | eselect repository enable riey 84 | emaint sync -r riey 85 | emerge -av kime 86 | ``` 87 | 88 | ### Build from source 89 | 90 | #### Docker 91 | 92 | Building with docker does not requires any other dependencies. 93 | 94 | ```sh 95 | git clone https://github.com/riey/kime 96 | cd kime 97 | 98 | export DOCKER_BUILDKIT=0 99 | export COMPOSE_DOCKER_CLI_BUILD=0 100 | # This is Docker build image using legacy 101 | 102 | docker build --file build-docker//Dockerfile --tag kime-build:git . 103 | docker run --name kime kime-build:git 104 | docker cp kime:/opt/kime-out/kime.tar.zst . 105 | # if you want to build deb package try this command instead 106 | # docker cp kime:/opt/kime-out/kime_amd64.deb . 107 | ``` 108 | 109 | #### Manual build 110 | 111 | Make sure that **cargo** and other dependencies listed below are installed before build. 112 | 113 | ```sh 114 | git clone https://github.com/Riey/kime 115 | cd kime 116 | 117 | scripts/build.sh -ar 118 | ``` 119 | 120 | Every files needed for manual install is in `build/out` directory. 121 | 122 | `scripts/install.sh ` can be used for packaging. 123 | 124 | `scripts/release-deb.sh ` can be used for packaging `deb` package. 125 | 126 | #### GTK 127 | 128 | Typically, this step is not necessary when kime is installed from binary package because most Linux distros does these steps themselves. 129 | 130 | ```sh 131 | # for gtk3 132 | sudo gtk-query-immodules-3.0 --update-cache 133 | # for gtk4 134 | sudo gio-querymodules /usr/lib/gtk-4.0/4.0.0/immodules 135 | ``` 136 | 137 | ## Development 138 | 139 | ### C/C++ 140 | 141 | Run `./scripts/generate_properties.sh` for using intellisense C/C++ in vscode 142 | 143 | ## Configuration 144 | 145 | ### environment variables setup 146 | 147 | #### Debian-like 148 | 149 | Set input method as `kime` in language setting 150 | 151 | #### Others 152 | 153 | Append following lines to your init script 154 | 155 | ```sh 156 | export GTK_IM_MODULE=kime 157 | export QT_IM_MODULE=kime 158 | export XMODIFIERS=@im=kime 159 | ``` 160 | 161 | if you use X, append above lines to file `~/.xprofile` 162 | 163 | ### Start additional server 164 | 165 | kime.desktop file is installed in /etc/xdg/autostart when installing kime. 166 | 167 | ### KDE Plasma Wayland 168 | 169 | It is required to select `kime daemon` under System Settings > Input & Output > Keyboard > Virtual Keyboard. 170 | A logout and re-login is recommended afterwards. 171 | 172 | ### Weston 173 | 174 | It is required to have the following lines in `~/.config/weston.ini` 175 | ``` 176 | [input-method] 177 | path=/usr/bin/kime 178 | ``` 179 | 180 | ### Configuration 181 | 182 | Read [CONFIGURATION.md](docs/CONFIGURATION.md) for detail options. 183 | 184 | ## Dependencies 185 | 186 | ### Run time 187 | 188 | These dependencies are optional depending on your environments. For example, qt6 is not required when it is not used. 189 | 190 | * gtk3 191 | * gtk4 192 | * qt5 193 | * qt6 194 | * libdbus (indicator) 195 | * xcb (candidate) 196 | * fontconfig (xim) 197 | * freetype (xim) 198 | * libxkbcommon (wayland) 199 | 200 | ### Build time (you don't need this on running compiled binary) 201 | 202 | #### Required 203 | 204 | * cmake 205 | * cargo 206 | * libclang 207 | * pkg-config 208 | 209 | #### Optional 210 | 211 | * gtk3 212 | * gtk4 213 | * qtbase5-private 214 | * qtbase6-private 215 | * libdbus 216 | * xcb 217 | * fontconfig 218 | * freetype 219 | * libxkbcommon 220 | -------------------------------------------------------------------------------- /src/tools/candidate-window/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::BTreeMap, 3 | io::{self, BufRead, Stdout, Write}, 4 | }; 5 | 6 | use egui::Widget; 7 | 8 | const PAGE_SIZE: usize = 10; 9 | 10 | #[derive(Default)] 11 | struct KeyState { 12 | left: bool, 13 | right: bool, 14 | } 15 | 16 | struct CandidateApp { 17 | stdout: Stdout, 18 | key_state: KeyState, 19 | page_index: usize, 20 | max_page_index: usize, 21 | candidate_list: Vec<(String, String)>, 22 | } 23 | 24 | impl eframe::App for CandidateApp { 25 | fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { 26 | if ctx.input().key_down(egui::Key::Escape) || ctx.input().key_down(egui::Key::Q) { 27 | frame.close(); 28 | return; 29 | } 30 | 31 | macro_rules! num_hotkey { 32 | ($k:expr, $n:expr) => { 33 | if ctx.input().key_down($k) { 34 | self.page_index = $n; 35 | } 36 | }; 37 | } 38 | 39 | num_hotkey!(egui::Key::Num1, 0); 40 | num_hotkey!(egui::Key::Num2, 1); 41 | num_hotkey!(egui::Key::Num3, 2); 42 | num_hotkey!(egui::Key::Num4, 3); 43 | num_hotkey!(egui::Key::Num5, 4); 44 | num_hotkey!(egui::Key::Num6, 5); 45 | num_hotkey!(egui::Key::Num7, 6); 46 | num_hotkey!(egui::Key::Num8, 7); 47 | num_hotkey!(egui::Key::Num9, 8); 48 | num_hotkey!(egui::Key::Num0, 9); 49 | 50 | if ctx.input().key_down(egui::Key::ArrowLeft) || ctx.input().key_down(egui::Key::H) { 51 | if !self.key_state.left { 52 | self.page_index = self.page_index.saturating_sub(1); 53 | self.key_state.left = true; 54 | } 55 | } 56 | 57 | if ctx.input().key_released(egui::Key::ArrowLeft) || ctx.input().key_released(egui::Key::H) 58 | { 59 | self.key_state.left = false; 60 | } 61 | 62 | if ctx.input().key_down(egui::Key::ArrowRight) || ctx.input().key_down(egui::Key::L) { 63 | if !self.key_state.right { 64 | self.page_index = self.page_index.saturating_add(1).min(self.max_page_index); 65 | self.key_state.right = true; 66 | } 67 | } 68 | 69 | if ctx.input().key_released(egui::Key::ArrowRight) || ctx.input().key_released(egui::Key::L) 70 | { 71 | self.key_state.right = false; 72 | } 73 | 74 | egui::CentralPanel::default().show(ctx, |ui| { 75 | ui.vertical_centered(|ui| { 76 | let from = self.page_index * PAGE_SIZE; 77 | let to = (from + PAGE_SIZE).min(self.candidate_list.len()); 78 | 79 | for (key, value) in self.candidate_list[from..to].iter() { 80 | let quitted = ui 81 | .horizontal(|ui| { 82 | ui.colored_label(egui::Color32::LIGHT_BLUE, key); 83 | ui.separator(); 84 | if ui.button(value).clicked() { 85 | true 86 | } else { 87 | false 88 | } 89 | }) 90 | .inner; 91 | 92 | if quitted { 93 | self.stdout.write_all(key.as_bytes()).unwrap(); 94 | frame.close(); 95 | return; 96 | } 97 | } 98 | }); 99 | }); 100 | 101 | egui::TopBottomPanel::bottom("candidate-footer").show(ctx, |ui| { 102 | ui.horizontal(|ui| { 103 | for i in 0..self.max_page_index + 1 { 104 | if i == self.page_index { 105 | egui::Button::new( 106 | egui::RichText::new(format!("[{}]", i + 1)) 107 | .color(egui::Color32::YELLOW), 108 | ) 109 | .ui(ui); 110 | } else { 111 | if ui.button(format!("{}", i + 1)).clicked() { 112 | self.page_index = i; 113 | } 114 | }; 115 | } 116 | }); 117 | }); 118 | } 119 | } 120 | 121 | fn main() -> io::Result<()> { 122 | let mut buf = String::with_capacity(4096); 123 | let stdin = io::stdin(); 124 | let stdout = io::stdout(); 125 | 126 | let mut candidate_list = Vec::new(); 127 | let mut stdin_lock = stdin.lock(); 128 | 129 | macro_rules! read { 130 | ($ret:ident) => { 131 | let len = stdin_lock.read_line(&mut buf)?; 132 | if len == 0 { 133 | break; 134 | } 135 | let $ret = buf.trim_end_matches('\n').to_string(); 136 | buf.clear(); 137 | }; 138 | } 139 | 140 | loop { 141 | read!(key); 142 | read!(value); 143 | candidate_list.push((key, value)); 144 | } 145 | 146 | eframe::run_native( 147 | "kime-candidate", 148 | eframe::NativeOptions { 149 | always_on_top: true, 150 | decorated: false, 151 | icon_data: None, 152 | initial_window_size: Some(egui::vec2(400.0, 400.0)), 153 | ..Default::default() 154 | }, 155 | Box::new(|cc| { 156 | let config = kime_engine_core::load_engine_config_from_config_dir().unwrap_or_default(); 157 | let (font_bytes, _index) = config.candidate_font; 158 | let mut font_data = BTreeMap::<_, egui::FontData>::new(); 159 | let mut families = BTreeMap::new(); 160 | 161 | font_data.insert( 162 | "Font".to_string(), 163 | egui::FontData::from_owned(font_bytes.to_vec()), 164 | ); 165 | 166 | families.insert(egui::FontFamily::Proportional, vec!["Font".to_string()]); 167 | families.insert(egui::FontFamily::Monospace, vec!["Font".to_string()]); 168 | 169 | cc.egui_ctx.set_fonts(egui::FontDefinitions { 170 | font_data, 171 | families, 172 | }); 173 | 174 | Box::new(CandidateApp { 175 | stdout, 176 | page_index: 0, 177 | key_state: KeyState::default(), 178 | max_page_index: if candidate_list.len() % PAGE_SIZE == 0 { 179 | (candidate_list.len() / PAGE_SIZE) - 1 180 | } else { 181 | candidate_list.len() / PAGE_SIZE 182 | }, 183 | candidate_list, 184 | }) 185 | }), 186 | ); 187 | 188 | Ok(()) 189 | } 190 | -------------------------------------------------------------------------------- /src/frontends/xim/src/pe_window.rs: -------------------------------------------------------------------------------- 1 | mod bgra; 2 | 3 | use bgra::Bgra; 4 | use std::{num::NonZeroU32, sync::Arc}; 5 | 6 | use image::ImageBuffer; 7 | use rusttype::Font; 8 | use x11rb::{ 9 | connection::Connection, 10 | protocol::xproto::{ 11 | AtomEnum, ConfigureNotifyEvent, ConnectionExt as _, CreateGCAux, CreateWindowAux, 12 | EventMask, ExposeEvent, ImageFormat, PropMode, WindowClass, EXPOSE_EVENT, 13 | }, 14 | wrapper::ConnectionExt as _, 15 | }; 16 | 17 | pub struct PeWindow { 18 | preedit_window: NonZeroU32, 19 | preedit: String, 20 | gc: u32, 21 | text_pos: (u32, u32), 22 | text_scale: rusttype::Scale, 23 | font: Arc>, 24 | image_buffer: ImageBuffer>, 25 | } 26 | 27 | impl PeWindow { 28 | pub fn new( 29 | conn: &impl Connection, 30 | (font, font_size): (Arc>, f32), 31 | app_win: Option, 32 | spot_location: xim::Point, 33 | screen_num: usize, 34 | ) -> Result { 35 | let size = (font_size * 1.7) as u16; 36 | let size = (size, size); 37 | let gc = conn.generate_id()?; 38 | let preedit_window = conn.generate_id()?; 39 | 40 | let screen = &conn.setup().roots[screen_num]; 41 | let pos = find_position(conn, screen.root, app_win, spot_location)?; 42 | 43 | conn.create_window( 44 | screen.root_depth, 45 | preedit_window, 46 | screen.root, 47 | pos.0, 48 | pos.1, 49 | size.0, 50 | size.1, 51 | 0, 52 | WindowClass::INPUT_OUTPUT, 53 | 0, 54 | &CreateWindowAux::default() 55 | .background_pixel(x11rb::NONE) 56 | .border_pixel(x11rb::NONE) 57 | .override_redirect(1u32) 58 | .event_mask(EventMask::EXPOSURE | EventMask::STRUCTURE_NOTIFY), 59 | )? 60 | .check()?; 61 | 62 | conn.create_gc( 63 | gc, 64 | preedit_window, 65 | &CreateGCAux::new() 66 | .background(screen.white_pixel) 67 | .foreground(screen.black_pixel), 68 | )?; 69 | 70 | let window_type = conn 71 | .intern_atom(false, b"_NET_WM_WINDOW_TYPE\0")? 72 | .reply()? 73 | .atom; 74 | let popup = conn 75 | .intern_atom(false, b"_NET_WM_WINDOW_TYPE_DOCK\0")? 76 | .reply()? 77 | .atom; 78 | 79 | conn.change_property32( 80 | PropMode::REPLACE, 81 | preedit_window, 82 | window_type, 83 | AtomEnum::ATOM, 84 | &[popup], 85 | )?; 86 | 87 | conn.change_property8( 88 | PropMode::REPLACE, 89 | preedit_window, 90 | AtomEnum::WM_CLASS, 91 | AtomEnum::STRING, 92 | b"kime\0kime\0", 93 | )?; 94 | 95 | conn.map_window(preedit_window)?.check()?; 96 | 97 | conn.flush()?; 98 | 99 | Ok(Self { 100 | preedit_window: NonZeroU32::new(preedit_window).unwrap(), 101 | preedit: String::with_capacity(10), 102 | gc, 103 | font, 104 | text_pos: ((font_size * 0.36) as _, (font_size * 0.36) as _), 105 | text_scale: rusttype::Scale::uniform(font_size as f32), 106 | image_buffer: ImageBuffer::new(size.0 as _, size.1 as _), 107 | }) 108 | } 109 | 110 | pub fn clean(self, conn: &impl Connection) -> Result<(), xim::ServerError> { 111 | conn.destroy_window(self.preedit_window.get())? 112 | .ignore_error(); 113 | conn.flush()?; 114 | 115 | Ok(()) 116 | } 117 | 118 | pub fn window(&self) -> NonZeroU32 { 119 | self.preedit_window 120 | } 121 | 122 | fn redraw(&mut self, conn: &impl Connection) -> Result<(), xim::ServerError> { 123 | const BACKGROUND: Bgra = Bgra([255, 255, 255, 255]); 124 | const FOREGROUND: Bgra = Bgra([0, 0, 0, 255]); 125 | 126 | log::trace!("Redraw: {}", self.preedit); 127 | 128 | let rect = imageproc::rect::Rect::at(0, 0) 129 | .of_size(self.image_buffer.width(), self.image_buffer.height()); 130 | imageproc::drawing::draw_filled_rect_mut(&mut self.image_buffer, rect, BACKGROUND); 131 | imageproc::drawing::draw_text_mut( 132 | &mut self.image_buffer, 133 | FOREGROUND, 134 | self.text_pos.0 as i32, 135 | self.text_pos.1 as i32, 136 | self.text_scale, 137 | &self.font, 138 | &self.preedit, 139 | ); 140 | 141 | conn.put_image( 142 | ImageFormat::Z_PIXMAP, 143 | self.preedit_window.get(), 144 | self.gc, 145 | self.image_buffer.width() as _, 146 | self.image_buffer.height() as _, 147 | 0, 148 | 0, 149 | 0, 150 | 24, 151 | self.image_buffer.as_raw(), 152 | )?; 153 | 154 | Ok(()) 155 | } 156 | 157 | pub fn expose(&mut self, conn: &impl Connection) -> Result<(), xim::ServerError> { 158 | self.redraw(conn)?; 159 | Ok(()) 160 | } 161 | 162 | pub fn configure_notify( 163 | &mut self, 164 | e: ConfigureNotifyEvent, 165 | conn: &impl Connection, 166 | ) -> Result<(), xim::ServerError> { 167 | self.image_buffer = ImageBuffer::new(e.width as _, e.height as _); 168 | self.redraw(conn)?; 169 | Ok(()) 170 | } 171 | 172 | pub fn refresh(&self, conn: &impl Connection) -> Result<(), xim::ServerError> { 173 | conn.send_event( 174 | false, 175 | self.preedit_window.get(), 176 | EventMask::NO_EVENT, 177 | ExposeEvent { 178 | response_type: EXPOSE_EVENT, 179 | window: self.window().get(), 180 | width: 0, 181 | height: 0, 182 | x: 0, 183 | y: 0, 184 | sequence: 0, 185 | count: 0, 186 | }, 187 | )?; 188 | conn.flush()?; 189 | 190 | Ok(()) 191 | } 192 | 193 | pub fn set_preedit(&mut self, s: &str) { 194 | self.preedit.clear(); 195 | self.preedit.push_str(s); 196 | } 197 | } 198 | 199 | fn find_position( 200 | conn: &impl Connection, 201 | root: u32, 202 | app_win: Option, 203 | spot_location: xim::Point, 204 | ) -> Result<(i16, i16), xim::ServerError> { 205 | match app_win { 206 | Some(app_win) => { 207 | let offset = conn 208 | .translate_coordinates(app_win.get(), root, spot_location.x, spot_location.y)? 209 | .reply()?; 210 | 211 | Ok((offset.dst_x, offset.dst_y)) 212 | } 213 | _ => Ok((0, 0)), 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/engine/dict/build.rs: -------------------------------------------------------------------------------- 1 | #[path = "src/math_symbol_key.rs"] 2 | mod math_symbol_key; 3 | 4 | use itertools::Itertools; 5 | use math_symbol_key::*; 6 | use serde::{Deserialize, Deserializer}; 7 | use std::{ 8 | collections::BTreeMap, 9 | env, 10 | io::{BufWriter, Write}, 11 | mem, 12 | path::PathBuf, 13 | }; 14 | use unic::emoji::char::is_emoji; 15 | 16 | #[derive(Default, Debug, Clone, Copy)] 17 | struct HanjaEntry { 18 | hanja: &'static str, 19 | description: &'static str, 20 | } 21 | 22 | #[derive(Default, Debug, Clone)] 23 | struct UnicodeEntry { 24 | cp: String, 25 | description: String, 26 | tts: String, 27 | } 28 | 29 | impl<'de> Deserialize<'de> for Style { 30 | fn deserialize>(deserializer: D) -> Result { 31 | use serde::de::Error; 32 | 33 | let styles: Vec<&str> = Deserialize::deserialize(deserializer)?; 34 | let style = styles 35 | .into_iter() 36 | .map(|s| { 37 | Ok(match s { 38 | "sf" => Style::SF, 39 | "bf" => Style::BF, 40 | "it" => Style::IT, 41 | "tt" => Style::TT, 42 | "bb" => Style::BB, 43 | "scr" => Style::SCR, 44 | "cal" => Style::CAL, 45 | "frak" => Style::FRAK, 46 | _ => return Err(Error::custom("no matching style name")), 47 | }) 48 | }) 49 | .fold(Ok(Style::NONE), |sty1, sty2| Ok(sty1? | sty2?)); 50 | style 51 | } 52 | } 53 | 54 | #[derive(Deserialize)] 55 | struct StySymPair<'a> { 56 | style: Style, 57 | symbol: &'a str, 58 | } 59 | 60 | #[derive(Deserialize)] 61 | struct KeySymPair<'a> { 62 | keyword: &'a str, 63 | symbols: Vec>, 64 | } 65 | 66 | type Dict = BTreeMap<&'static str, Vec>; 67 | 68 | fn load_hanja_dict() -> Dict { 69 | let hanja_data = include_str!("data/hanja.txt"); 70 | let hanja_freq = include_str!("data/freq-hanja.txt"); 71 | 72 | let mut freq_dict: BTreeMap = BTreeMap::new(); 73 | 74 | for line in hanja_freq.lines() { 75 | match line.split(':').next_tuple() { 76 | Some((hanja, freq)) => { 77 | if let Some(hanja) = hanja.chars().next() { 78 | if let Ok(freq) = freq.parse() { 79 | freq_dict.insert(hanja, freq); 80 | } 81 | } 82 | } 83 | None => continue, 84 | } 85 | } 86 | 87 | let mut dict = Dict::new(); 88 | 89 | for line in hanja_data.lines() { 90 | if line.starts_with('#') { 91 | continue; 92 | } 93 | 94 | match line.split(':').next_tuple() { 95 | Some((hangul, hanja, description)) => { 96 | // skip unused hanja 97 | if description.is_empty() { 98 | continue; 99 | } 100 | 101 | dict.entry(hangul) 102 | .or_default() 103 | .push(HanjaEntry { hanja, description }); 104 | } 105 | None => continue, 106 | } 107 | } 108 | 109 | for (_, entries) in dict.iter_mut() { 110 | entries.sort_by_key(|e| { 111 | std::cmp::Reverse( 112 | e.hanja 113 | .chars() 114 | .map(|c| freq_dict.get(&c).map_or(0, |n| *n)) 115 | .sum::(), 116 | ) 117 | }) 118 | } 119 | 120 | dict 121 | } 122 | 123 | fn load_unicode_annotations() -> quick_xml::Result> { 124 | use quick_xml::{events::Event, Reader}; 125 | 126 | let mut out = Vec::with_capacity(512); 127 | let mut current_entry = UnicodeEntry::default(); 128 | 129 | let mut reader = Reader::from_str(include_str!("data/en.xml")); 130 | 131 | loop { 132 | match reader.read_event()? { 133 | Event::Start(start) if start.name().0 == b"annotation" => { 134 | let cp = start.attributes().next().unwrap()?; 135 | debug_assert_eq!(cp.key.0, b"cp"); 136 | let cp = cp.decode_and_unescape_value(&reader)?; 137 | if current_entry.cp != cp { 138 | if !current_entry.cp.is_empty() { 139 | out.push(mem::take(&mut current_entry)); 140 | } 141 | 142 | current_entry.cp = cp.into_owned(); 143 | current_entry.description = 144 | reader.read_text(start.to_end().name())?.into_owned(); 145 | } else { 146 | current_entry.tts = reader.read_text(start.to_end().name())?.into_owned(); 147 | } 148 | } 149 | Event::Eof => break, 150 | _ => {} 151 | } 152 | } 153 | 154 | out.push(mem::take(&mut current_entry)); 155 | Ok(out) 156 | } 157 | 158 | fn main() { 159 | let mut out = BufWriter::new( 160 | std::fs::File::create(PathBuf::from(env::var("OUT_DIR").unwrap()).join("dict.rs")).unwrap(), 161 | ); 162 | 163 | writeln!(out, "use crate::math_symbol_key::*;").unwrap(); 164 | writeln!( 165 | out, 166 | "pub static HANJA_ENTRIES: &[(&str, &[(&str, &str)])] = &[", 167 | ) 168 | .unwrap(); 169 | 170 | for (k, values) in load_hanja_dict() { 171 | write!(out, "(\"{}\", &[", k).unwrap(); 172 | for value in values { 173 | write!(out, "(\"{}\", \"{}\"),", value.hanja, value.description).unwrap(); 174 | } 175 | writeln!(out, "]),").unwrap(); 176 | } 177 | 178 | writeln!(out, "];").unwrap(); 179 | 180 | let symbol_map_data = include_str!("data/symbol_map.json"); 181 | let symbol_map_data: Vec = serde_json::from_str(symbol_map_data).unwrap(); 182 | let mut symbol_map: Vec<(SymbolKey, &str)> = Vec::new(); 183 | for key_sym_pair in &symbol_map_data { 184 | let keyword = &key_sym_pair.keyword; 185 | for sty_sym_pair in &key_sym_pair.symbols { 186 | let style = sty_sym_pair.style; 187 | let symbol = sty_sym_pair.symbol; 188 | symbol_map.push((SymbolKey(keyword, style), symbol)); 189 | } 190 | } 191 | symbol_map.sort_unstable_by_key(|pair| pair.0); 192 | 193 | writeln!( 194 | out, 195 | "pub static MATH_SYMBOL_ENTRIES: &[(SymbolKey, &str)] = &{:?};", 196 | symbol_map 197 | ) 198 | .unwrap(); 199 | 200 | writeln!(out, "#[derive(Clone, Copy, Debug)] pub struct UnicodeAnnotation {{ pub codepoint: &'static str, pub tts: &'static str, }}").unwrap(); 201 | writeln!( 202 | out, 203 | "pub static UNICODE_ANNOTATIONS: &[UnicodeAnnotation] = &[" 204 | ) 205 | .unwrap(); 206 | for entry in load_unicode_annotations().unwrap() { 207 | if !entry.cp.chars().any(|c| is_emoji(c)) { 208 | continue; 209 | } 210 | 211 | writeln!( 212 | out, 213 | "UnicodeAnnotation {{ codepoint: \"{}\", tts: \"{}\" }},", 214 | entry.cp, entry.tts 215 | ) 216 | .unwrap() 217 | } 218 | writeln!(out, "];").unwrap(); 219 | 220 | out.flush().unwrap(); 221 | } 222 | -------------------------------------------------------------------------------- /src/engine/capi/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::missing_safety_doc)] 2 | 3 | use kime_engine_core::{load_engine_config_from_config_dir, load_raw_config_from_config_dir}; 4 | 5 | pub use kime_engine_core::{ 6 | Config, DaemonConfig, DaemonModule, IconColor, IndicatorConfig, InputCategory, InputEngine, 7 | InputResult, LogConfig, ModifierState, 8 | }; 9 | 10 | pub const KIME_API_VERSION: usize = 7; 11 | 12 | #[repr(C)] 13 | pub struct RustStr { 14 | ptr: *const u8, 15 | len: usize, 16 | } 17 | 18 | impl RustStr { 19 | pub fn new(s: &str) -> Self { 20 | Self { 21 | ptr: s.as_ptr(), 22 | len: s.len(), 23 | } 24 | } 25 | } 26 | 27 | #[repr(C)] 28 | pub struct RustSlice { 29 | ptr: *const u8, 30 | len: usize, 31 | } 32 | 33 | impl RustSlice { 34 | pub fn new(s: &[u8]) -> Self { 35 | Self { 36 | ptr: s.as_ptr(), 37 | len: s.len(), 38 | } 39 | } 40 | } 41 | 42 | #[repr(C)] 43 | pub struct FontData { 44 | font_data: RustSlice, 45 | index: u32, 46 | size: f32, 47 | } 48 | 49 | /// Return API version 50 | #[no_mangle] 51 | pub extern "C" fn kime_api_version() -> usize { 52 | KIME_API_VERSION 53 | } 54 | 55 | /// Create new engine 56 | #[no_mangle] 57 | pub extern "C" fn kime_engine_new(config: &Config) -> *mut InputEngine { 58 | Box::into_raw(Box::new(InputEngine::new(config))) 59 | } 60 | 61 | /// Set hangul enable state 62 | #[no_mangle] 63 | pub extern "C" fn kime_engine_set_input_category( 64 | engine: &mut InputEngine, 65 | category: InputCategory, 66 | ) { 67 | engine.set_input_category(category); 68 | } 69 | 70 | /// Delete engine 71 | /// 72 | /// # Safety 73 | /// 74 | /// engine must be created by `kime_engine_new` and never call delete more than once 75 | #[no_mangle] 76 | pub unsafe extern "C" fn kime_engine_delete(engine: &mut InputEngine) { 77 | drop(Box::from_raw(engine)); 78 | } 79 | 80 | /// Check engine ready state 81 | #[no_mangle] 82 | pub unsafe extern "C" fn kime_engine_check_ready(engine: &InputEngine) -> bool { 83 | engine.check_ready() 84 | } 85 | 86 | /// End engine ready state 87 | #[no_mangle] 88 | pub unsafe extern "C" fn kime_engine_end_ready(engine: &mut InputEngine) -> InputResult { 89 | engine.end_ready() 90 | } 91 | 92 | /// Update layout state 93 | #[no_mangle] 94 | pub extern "C" fn kime_engine_update_layout_state(engine: &mut InputEngine) { 95 | engine.update_layout_state().ok(); 96 | } 97 | 98 | /// Get commit string of engine 99 | /// 100 | /// ## Return 101 | /// 102 | /// valid utf8 string 103 | #[no_mangle] 104 | pub extern "C" fn kime_engine_commit_str(engine: &InputEngine) -> RustStr { 105 | RustStr::new(engine.commit_str()) 106 | } 107 | 108 | /// Get preedit string of engine 109 | /// 110 | /// ## Return 111 | /// 112 | /// valid utf8 string 113 | #[no_mangle] 114 | pub extern "C" fn kime_engine_preedit_str(engine: &mut InputEngine) -> RustStr { 115 | RustStr::new(engine.preedit_str()) 116 | } 117 | 118 | /// Clear commit string 119 | #[no_mangle] 120 | pub extern "C" fn kime_engine_clear_commit(engine: &mut InputEngine) { 121 | engine.clear_commit(); 122 | } 123 | 124 | /// Clear preedit state this function may append to commit string 125 | #[no_mangle] 126 | pub extern "C" fn kime_engine_clear_preedit(engine: &mut InputEngine) { 127 | engine.clear_preedit(); 128 | } 129 | 130 | /// Clear preedit state this function must not append to commit string 131 | #[no_mangle] 132 | pub extern "C" fn kime_engine_remove_preedit(engine: &mut InputEngine) { 133 | engine.remove_preedit(); 134 | } 135 | 136 | /// Reset engine state 137 | #[no_mangle] 138 | pub extern "C" fn kime_engine_reset(engine: &mut InputEngine) { 139 | engine.reset(); 140 | } 141 | 142 | /// Press key when modifier state 143 | /// 144 | /// ## Return 145 | /// 146 | /// input result 147 | #[no_mangle] 148 | pub extern "C" fn kime_engine_press_key( 149 | engine: &mut InputEngine, 150 | config: &Config, 151 | hardware_code: u16, 152 | numlock: bool, 153 | state: ModifierState, 154 | ) -> InputResult { 155 | engine.press_key_code(hardware_code, state, numlock, config) 156 | } 157 | 158 | /// Load config from local file 159 | #[cfg(unix)] 160 | #[no_mangle] 161 | pub extern "C" fn kime_config_load() -> *mut Config { 162 | let config = load_engine_config_from_config_dir().unwrap_or_default(); 163 | Box::into_raw(Box::new(config)) 164 | } 165 | 166 | /// Create default config note that this function will not read config file 167 | #[no_mangle] 168 | pub extern "C" fn kime_config_default() -> *mut Config { 169 | Box::into_raw(Box::new(Config::default())) 170 | } 171 | 172 | /// Delete config 173 | #[no_mangle] 174 | pub unsafe extern "C" fn kime_config_delete(config: *mut Config) { 175 | drop(Box::from_raw(config)); 176 | } 177 | 178 | /// Get candidate_font config 179 | /// font_data only valid while config is live 180 | #[no_mangle] 181 | pub extern "C" fn kime_config_candidate_font(config: &Config) -> FontData { 182 | let (ref font, index) = config.candidate_font; 183 | 184 | FontData { 185 | font_data: RustSlice::new(font), 186 | index, 187 | size: 0., 188 | } 189 | } 190 | 191 | /// Get xim_preedit_font config 192 | /// font_data only valid while config is live 193 | #[no_mangle] 194 | pub extern "C" fn kime_config_xim_preedit_font(config: &Config) -> FontData { 195 | let (ref font, index, size) = config.xim_preedit_font; 196 | 197 | FontData { 198 | font_data: RustSlice::new(font), 199 | index, 200 | size, 201 | } 202 | } 203 | 204 | /// Load daemon config 205 | #[cfg(unix)] 206 | #[no_mangle] 207 | pub extern "C" fn kime_daemon_config_load() -> *mut DaemonConfig { 208 | Box::into_raw(Box::new(load_raw_config_from_config_dir().daemon)) 209 | } 210 | 211 | /// Get daemon `modules` 212 | #[no_mangle] 213 | pub extern "C" fn kime_daemon_config_modules(config: &DaemonConfig) -> u32 /* enumset doesn't have transparent yet -> EnumSet */ 214 | { 215 | config.modules.as_u32() 216 | } 217 | 218 | /// Get default daemon config 219 | #[no_mangle] 220 | pub extern "C" fn kime_daemon_config_default() -> *mut DaemonConfig { 221 | Box::into_raw(Box::new(DaemonConfig::default())) 222 | } 223 | 224 | /// Delete daemon config 225 | #[no_mangle] 226 | pub unsafe extern "C" fn kime_daemon_config_delete(config: *mut DaemonConfig) { 227 | drop(Box::from_raw(config)); 228 | } 229 | 230 | /// Load indicator config 231 | #[cfg(unix)] 232 | #[no_mangle] 233 | pub extern "C" fn kime_indicator_config_load() -> *mut IndicatorConfig { 234 | Box::into_raw(Box::new(load_raw_config_from_config_dir().indicator)) 235 | } 236 | 237 | /// Get default indicator config 238 | #[no_mangle] 239 | pub extern "C" fn kime_indicator_config_default() -> *mut IndicatorConfig { 240 | Box::into_raw(Box::new(IndicatorConfig::default())) 241 | } 242 | 243 | /// Delete indicator config 244 | #[no_mangle] 245 | pub unsafe extern "C" fn kime_indicator_config_delete(config: *mut IndicatorConfig) { 246 | drop(Box::from_raw(config)); 247 | } 248 | 249 | /// Get indicator `icon_color` 250 | #[no_mangle] 251 | pub extern "C" fn kime_indicator_config_icon_color(config: &IndicatorConfig) -> IconColor /* enumset doesn't have transparent yet -> EnumSet */ 252 | { 253 | config.icon_color 254 | } 255 | 256 | /// Load log config 257 | #[cfg(unix)] 258 | #[no_mangle] 259 | pub extern "C" fn kime_log_config_load() -> *mut LogConfig { 260 | Box::into_raw(Box::new(load_raw_config_from_config_dir().log)) 261 | } 262 | 263 | /// Get default log config 264 | #[no_mangle] 265 | pub extern "C" fn kime_log_config_default() -> *mut LogConfig { 266 | Box::into_raw(Box::new(LogConfig::default())) 267 | } 268 | 269 | /// Delete log config 270 | #[no_mangle] 271 | pub unsafe extern "C" fn kime_log_config_delete(config: *mut LogConfig) { 272 | drop(Box::from_raw(config)); 273 | } 274 | 275 | /// Get log `icon_color` 276 | #[no_mangle] 277 | pub extern "C" fn kime_log_config_global_level(config: &LogConfig) -> RustStr { 278 | RustStr::new(config.global_level.as_str()) 279 | } 280 | --------------------------------------------------------------------------------