├── .github └── workflows │ └── main.yml ├── LICENSE ├── README.md ├── buildtools ├── file2string.py └── meson.build ├── data ├── ca.tadeo.anthywl.varlink └── default_config ├── default.nix ├── doc ├── anthywl.1.scd ├── anthywl.5.scd └── meson.build ├── include ├── actions.h ├── anthywl.h ├── buffer.h ├── config.h ├── graphics_buffer.h ├── ipc.h ├── keymap.h └── meson.build ├── meson.build ├── meson_options.txt ├── protocol ├── input-method-unstable-v2.xml ├── meson.build └── virtual-keyboard-unstable-v1.xml ├── src ├── actions.c ├── anthywl.c ├── buffer.c ├── config.c ├── graphics_buffer.c ├── ipc.c ├── keymap.c └── meson.build └── subprojects └── libscfg.wrap /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CI", 3 | "on": { 4 | "push": { "branches": ["master"] }, 5 | "pull_request": { "branches": ["master"] }, 6 | "workflow_dispatch": {} 7 | }, 8 | "jobs": { 9 | "build-ubuntu": { 10 | "runs-on": "ubuntu-latest", 11 | "steps": [ 12 | { "uses": "actions/checkout@v2" }, 13 | { 14 | "name": "Install dependencies", 15 | "run": "sudo apt-get install ninja-build scdoc libwayland-dev wayland-protocols libxkbcommon-dev libanthy-dev libcairo2-dev libpango1.0-dev" 16 | }, 17 | { "uses": "BSFishy/meson-build@v1.0.3", "with": { "meson-version": "0.59.0", "action": "test" } } 18 | ] 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Tadeo Kondrak 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 9 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 12 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # anthywl 2 | 3 | Basic Japanese input method for Sway and other Wayland compositors. 4 | 5 | ## Dependencies 6 | 7 | Build-time: 8 | 9 | - meson 10 | - ninja 11 | - scdoc (optional, for man pages) 12 | 13 | Runtime: 14 | 15 | - wayland 16 | - wayland-protocols 17 | - libxkbcommon 18 | - libanthy 19 | - cairo 20 | - pango 21 | - libscfg 22 | 23 | ## Building 24 | 25 | ```sh 26 | meson build 27 | ninja -C build 28 | ``` 29 | 30 | ## Configuration 31 | 32 | Copy `data/default_config` to `~/.config/anthywl/config`. 33 | -------------------------------------------------------------------------------- /buildtools/file2string.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Convert the contents of a file into a C string constant. 4 | # Note that the compiler will implicitly add an extra 0 byte at the end 5 | # of every string, so code using the string may need to remove that to get 6 | # the exact contents of the original file. 7 | 8 | # 9 | # This file is part of mpv. 10 | # 11 | # mpv is free software; you can redistribute it and/or 12 | # modify it under the terms of the GNU Lesser General Public 13 | # License as published by the Free Software Foundation; either 14 | # version 2.1 of the License, or (at your option) any later version. 15 | # 16 | # mpv is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU Lesser General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU Lesser General Public 22 | # License along with mpv. If not, see . 23 | # 24 | 25 | import sys 26 | 27 | def file2string(infilename, infile, outfile): 28 | outfile.write("// Generated from %s\n\n" % infilename) 29 | 30 | conv = ["\\%03o" % c for c in range(256)] 31 | safe_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" \ 32 | "0123456789!#%&'()*+,-./:;<=>[]^_{|}~ " 33 | 34 | for c in safe_chars: 35 | conv[ord(c)] = c 36 | for c, esc in ("\nn", "\tt", r"\\", '""'): 37 | conv[ord(c)] = '\\' + esc 38 | for line in infile: 39 | outfile.write('"' + ''.join(conv[c] for c in line) + '"\n') 40 | 41 | if __name__ == "__main__": 42 | if len(sys.argv) < 2: 43 | outfile = sys.stdout 44 | else: 45 | outfile = open(sys.argv[2], "w") 46 | 47 | with open(sys.argv[1], 'rb') as infile: 48 | file2string(sys.argv[1], infile, outfile) 49 | -------------------------------------------------------------------------------- /buildtools/meson.build: -------------------------------------------------------------------------------- 1 | file2string = find_program('file2string.py') 2 | -------------------------------------------------------------------------------- /data/ca.tadeo.anthywl.varlink: -------------------------------------------------------------------------------- 1 | interface ca.tadeo.anthywl 2 | 3 | method Action(seat: string, action: string) -> () 4 | 5 | error NoSuchSeat (seat: string) 6 | -------------------------------------------------------------------------------- /data/default_config: -------------------------------------------------------------------------------- 1 | active-at-startup 2 | 3 | global-bindings { 4 | Ctrl+Shift+Backspace toggle 5 | } 6 | 7 | composing-bindings { 8 | space select 9 | Return accept 10 | Escape discard 11 | Backspace delete-left 12 | Left move-left 13 | Left move-right 14 | } 15 | 16 | selecting-bindings { 17 | Escape discard 18 | Return accept 19 | BackSpace delete-left 20 | Left move-left 21 | Right move-right 22 | Shift+Left expand-left 23 | Shift+Right expand-right 24 | Up prev-candidate 25 | Down next-candidate 26 | space cycle-candidate 27 | } 28 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { stdenv 2 | , pkg-config 3 | , meson 4 | , ninja 5 | , scdoc 6 | , python 7 | , wayland 8 | , wayland-protocols 9 | , libxkbcommon 10 | , anthy 11 | , pango 12 | , cairo 13 | , libscfg 14 | , libvarlink 15 | }: 16 | 17 | stdenv.mkDerivation { 18 | pname = "anthywl"; 19 | version = "0.0.1-dev"; 20 | 21 | src = ./.; 22 | postPatch = "patchShebangs ."; 23 | 24 | nativeBuildInputs = [ 25 | pkg-config 26 | meson 27 | ninja 28 | scdoc 29 | python 30 | ]; 31 | 32 | buildInputs = [ 33 | wayland 34 | wayland-protocols 35 | libxkbcommon 36 | anthy 37 | pango 38 | cairo 39 | libscfg 40 | libvarlink 41 | ]; 42 | } 43 | -------------------------------------------------------------------------------- /doc/anthywl.1.scd: -------------------------------------------------------------------------------- 1 | anthywl(1) 2 | 3 | # NAME 4 | 5 | anthywl - A Japanese input method for Wayland 6 | 7 | # SYNOPSIS 8 | 9 | *anthywl* 10 | 11 | # DESCRIPTION 12 | 13 | anthywl is a Japanese input method for Wayland compositors supporting the 14 | input-method-v2 protocol. 15 | 16 | # CONFIGURATION 17 | 18 | anthywl is configured using a single configuration file using the scfg format. 19 | 20 | It looks for it at $XDG_CONFIG_HOME/anthywl/config. If $XDG_CONFIG_HOME is 21 | unset, it defaults to *$HOME/.config*. If none is found, a default is used. 22 | 23 | See *anthywl*(5) for details on the configuration syntax and options. 24 | 25 | # AUTHORS 26 | 27 | Tadeo Kondrak 28 | 29 | # SEE ALSO 30 | 31 | *anthywl*(5) 32 | -------------------------------------------------------------------------------- /doc/anthywl.5.scd: -------------------------------------------------------------------------------- 1 | anthywl(5) 2 | 3 | # NAME 4 | 5 | anthywl - Configuration file 6 | 7 | # DESCRIPTION 8 | 9 | anthywl's configuration file syntax is based on the scfg format. 10 | See https://git.sr.ht/~emersion/scfg for details. 11 | 12 | # DIRECTIVES 13 | 14 | *active-at-startup*: 15 | 16 | If this directive is included, anthywl will be active on startup. 17 | This means that typing will immediately result in conversion. 18 | 19 | Example: 20 | 21 | ``` 22 | active-at-startup 23 | ``` 24 | 25 | *global-bindings*, *composing-bindings*, *selecting-bindings*: 26 | 27 | Each sub-directive in these blocks is in the form 28 | 29 | 30 | 31 | See *ACTIONS* for information about possible values of . 32 | 33 | Example: 34 | 35 | ``` 36 | global-bindings { 37 | Ctrl+Shift+Backspace toggle 38 | } 39 | ``` 40 | 41 | # ACTIONS 42 | 43 | *enable*: 44 | 45 | If not in any mode, enters composing mode. 46 | 47 | *disable*: 48 | 49 | In any mode, leaves the mode. 50 | 51 | *toggle*: 52 | 53 | If not in any mode, enters composing mode. 54 | In any mode, leaves the mode. 55 | 56 | *delete-left*: 57 | 58 | In selecting mode, switches to composing mode, then deletes one 59 | character from the end. 60 | 61 | In composing mode, deletes one character to the left. 62 | 63 | A suggested key for this action is *BackSpace*. 64 | 65 | *delete-right*: 66 | 67 | In composing mode, deletes one character to the right. 68 | 69 | A suggested key for this action is *Delete*. 70 | 71 | *move-left*: 72 | 73 | In composing mode, moves the cursor one character to the left. 74 | In selecting mode, moves the selection one word to the left. 75 | 76 | A suggested key for this action is *Left*. 77 | 78 | *move-right*: 79 | 80 | In composing mode, moves the cursor one character to the right. 81 | In selecting mode, moves the selection one word to the right. 82 | 83 | A suggested key for this action is *Right*. 84 | 85 | *expand-left*: 86 | 87 | In composing mode, expands the selection one character to the 88 | left. 89 | In selecting mode, expands the current word one character to 90 | the left. 91 | 92 | A suggested key combination for this action is *Shift-Left*. 93 | 94 | *expand-right*: 95 | 96 | In composing mode, expands the selection one character to the 97 | right. 98 | In selecting mode, expands the current word one character to 99 | the right. 100 | 101 | A suggested key combination for this action is *Shift-Right*. 102 | 103 | *select*: 104 | 105 | Switches to selecting mode. 106 | 107 | A suggested key for this action is *Return*. 108 | 109 | *compose*: 110 | 111 | Switches to composing mode. 112 | 113 | Switch to composing mode. 114 | 115 | *accept*: 116 | 117 | In composing or selecting mode, commits the current text and 118 | returns to composing mode. 119 | 120 | A suggested key for this action is *space*. 121 | 122 | *discard*: 123 | 124 | In composing or selecting mode, commits the current text and 125 | returns to composing mode. 126 | 127 | A suggested key for this action is *Escape*. 128 | 129 | *prev-candidate*: 130 | 131 | In selecting mode, selects the previous candidate. 132 | 133 | A suggested key for this action is *Up*. 134 | 135 | *next-candidate*: 136 | 137 | In selecting mode, selects the next candidate. 138 | 139 | A suggested key for this action is *Down*. 140 | 141 | *cycle-candidate*: 142 | 143 | In selecting mode, selects the next candidate cycling back to 144 | the first candidate when the end is reached. 145 | 146 | A suggested key for this action is *space*. 147 | 148 | 149 | # SEE ALSO 150 | 151 | *anthywl*(1) 152 | -------------------------------------------------------------------------------- /doc/meson.build: -------------------------------------------------------------------------------- 1 | if scdoc.found() 2 | scdoc_prog = find_program(scdoc.get_variable(pkgconfig: 'scdoc'), native: true) 3 | mandir = get_option('mandir') 4 | man_files = [ 5 | 'anthywl.1.scd', 6 | 'anthywl.5.scd', 7 | ] 8 | 9 | foreach filename : man_files 10 | topic = filename.split('.')[-3].split('/')[-1] 11 | section = filename.split('.')[-2] 12 | output = '@0@.@1@'.format(topic, section) 13 | 14 | custom_target( 15 | output, 16 | input: filename, 17 | output: output, 18 | command: scdoc_prog, 19 | install: true, 20 | feed: true, 21 | capture: true, 22 | install_dir: '@0@/man@1@'.format(mandir, section) 23 | ) 24 | endforeach 25 | endif 26 | -------------------------------------------------------------------------------- /include/actions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct anthywl_seat; 6 | 7 | enum anthywl_action { 8 | ANTHYWL_ACTION_INVALID, 9 | ANTHYWL_ACTION_ENABLE, 10 | ANTHYWL_ACTION_DISABLE, 11 | ANTHYWL_ACTION_TOGGLE, 12 | ANTHYWL_ACTION_DELETE_LEFT, 13 | ANTHYWL_ACTION_DELETE_RIGHT, 14 | ANTHYWL_ACTION_MOVE_LEFT, 15 | ANTHYWL_ACTION_MOVE_RIGHT, 16 | ANTHYWL_ACTION_EXPAND_LEFT, 17 | ANTHYWL_ACTION_EXPAND_RIGHT, 18 | ANTHYWL_ACTION_SELECT, 19 | ANTHYWL_ACTION_COMPOSE, 20 | ANTHYWL_ACTION_ACCEPT, 21 | ANTHYWL_ACTION_DISCARD, 22 | ANTHYWL_ACTION_PREV_CANDIDATE, 23 | ANTHYWL_ACTION_NEXT_CANDIDATE, 24 | ANTHYWL_ACTION_CYCLE_CANDIDATE, 25 | ANTHYWL_ACTION_SELECT_UNCONVERTED_CANDIDATE, 26 | ANTHYWL_ACTION_SELECT_KATAKANA_CANDIDATE, 27 | ANTHYWL_ACTION_SELECT_HIRAGANA_CANDIDATE, 28 | ANTHYWL_ACTION_SELECT_HALFKANA_CANDIDATE, 29 | _ANTHYWL_ACTION_LAST, 30 | }; 31 | 32 | enum anthywl_action anthywl_action_from_string(const char *name); 33 | bool anthywl_seat_handle_action(struct anthywl_seat *seat, 34 | enum anthywl_action action); 35 | -------------------------------------------------------------------------------- /include/anthywl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "input-method-unstable-v2-client-protocol.h" 10 | #include "text-input-unstable-v3-client-protocol.h" 11 | #include "virtual-keyboard-unstable-v1-client-protocol.h" 12 | 13 | #include "actions.h" 14 | #include "buffer.h" 15 | #include "config.h" 16 | 17 | #ifdef ANTHYWL_IPC_SUPPORT 18 | #include "ipc.h" 19 | #include 20 | #endif 21 | 22 | enum anthyl_modifier_index { 23 | ANTHYWL_SHIFT_INDEX, 24 | ANTHYWL_CAPS_INDEX, 25 | ANTHYWL_CTRL_INDEX, 26 | ANTHYWL_ALT_INDEX, 27 | ANTHYWL_NUM_INDEX, 28 | ANTHYWL_MOD3_INDEX, 29 | ANTHYWL_LOGO_INDEX, 30 | ANTHYWL_MOD5_INDEX, 31 | _ANTHYWL_MOD_LAST, 32 | }; 33 | 34 | enum anthywl_modifier { 35 | ANTHYWL_SHIFT = 1 << ANTHYWL_SHIFT_INDEX, 36 | ANTHYWL_CAPS = 1 << ANTHYWL_CAPS_INDEX, 37 | ANTHYWL_CTRL = 1 << ANTHYWL_CTRL_INDEX, 38 | ANTHYWL_ALT = 1 << ANTHYWL_ALT_INDEX, 39 | ANTHYWL_NUM = 1 << ANTHYWL_NUM_INDEX, 40 | ANTHYWL_MOD3 = 1 << ANTHYWL_MOD3_INDEX, 41 | ANTHYWL_LOGO = 1 << ANTHYWL_LOGO_INDEX, 42 | ANTHYWL_MOD5 = 1 << ANTHYWL_MOD5_INDEX, 43 | }; 44 | 45 | struct anthywl_state { 46 | bool running; 47 | struct wl_display *wl_display; 48 | struct wl_registry *wl_registry; 49 | struct wl_compositor *wl_compositor; 50 | struct wl_shm *wl_shm; 51 | struct zwp_input_method_manager_v2 *zwp_input_method_manager_v2; 52 | struct zwp_virtual_keyboard_manager_v1 *zwp_virtual_keyboard_manager_v1; 53 | struct wl_cursor_theme *wl_cursor_theme; 54 | int wl_cursor_theme_size; 55 | int wl_cursor_theme_scale; 56 | struct wl_list buffers; 57 | struct wl_list seats; 58 | struct wl_list outputs; 59 | struct wl_list timers; 60 | struct anthywl_config config; 61 | #ifdef ANTHYWL_IPC_SUPPORT 62 | struct anthywl_ipc ipc; 63 | #endif 64 | int max_scale; 65 | }; 66 | 67 | struct anthywl_timer { 68 | struct wl_list link; 69 | struct timespec time; 70 | void (*callback)(struct anthywl_timer *timer); 71 | }; 72 | 73 | struct anthywl_output { 74 | struct wl_list link; 75 | struct anthywl_state *state; 76 | struct wl_output *wl_output; 77 | int pending_scale, scale; 78 | }; 79 | 80 | struct anthywl_seat { 81 | struct wl_list link; 82 | struct anthywl_state *state; 83 | struct wl_seat *wl_seat; 84 | 85 | bool are_protocols_initted; 86 | struct zwp_input_method_v2 *zwp_input_method_v2; 87 | struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2; 88 | struct zwp_virtual_keyboard_v1 *zwp_virtual_keyboard_v1_passthrough; 89 | struct zwp_virtual_keyboard_v1 *zwp_virtual_keyboard_v1_backup_input; 90 | 91 | // wl_pointer 92 | struct wl_pointer *wl_pointer; 93 | uint32_t wl_pointer_serial; 94 | struct wl_surface *wl_surface_cursor; 95 | struct anthywl_timer cursor_timer; 96 | 97 | char *xkb_keymap_string; 98 | struct xkb_context *xkb_context; 99 | struct xkb_keymap *xkb_keymap; 100 | struct xkb_state *xkb_state; 101 | xkb_mod_index_t mod_indices[_ANTHYWL_MOD_LAST]; 102 | struct wl_array global_bindings; 103 | struct wl_array composing_bindings; 104 | struct wl_array selecting_bindings; 105 | 106 | // wl_seat 107 | char *name; 108 | 109 | // wl_output 110 | struct wl_array outputs; 111 | int scale; 112 | 113 | // zwp_input_method_v2 114 | bool pending_activate, active; 115 | char *pending_surrounding_text, *surrounding_text; 116 | uint32_t pending_surrounding_text_cursor, surrounding_text_cursor; 117 | uint32_t pending_surrounding_text_anchor, surrounding_text_anchor; 118 | uint32_t pending_text_change_cause, text_change_cause; 119 | uint32_t pending_content_type_hint, content_type_hint; 120 | uint32_t pending_content_type_purpose, content_type_purpose; 121 | uint32_t done_events_received; 122 | 123 | // zwp_input_method_keyboard_grab_v2 124 | uint32_t repeat_rate; 125 | uint32_t repeat_delay; 126 | xkb_keycode_t pressed[64]; 127 | xkb_keycode_t repeating_keycode; 128 | uint32_t repeating_timestamp; 129 | struct anthywl_timer repeat_timer; 130 | 131 | // popup 132 | struct anthywl_buffer buffer; 133 | 134 | // composing 135 | bool is_composing; 136 | bool is_composing_popup_visible; 137 | 138 | // selecting 139 | bool is_selecting; 140 | bool is_selecting_popup_visible; 141 | int current_segment; 142 | int segment_count; 143 | int *selected_candidates; 144 | anthy_context_t anthy_context; 145 | 146 | // popup 147 | struct wl_surface *wl_surface; 148 | struct zwp_input_popup_surface_v2 *zwp_input_popup_surface_v2; 149 | }; 150 | 151 | struct anthywl_binding { 152 | xkb_keysym_t keysym; 153 | enum anthywl_modifier modifiers; 154 | enum anthywl_action action; 155 | }; 156 | 157 | struct anthywl_seat_binding { 158 | xkb_keycode_t keycode; 159 | xkb_mod_mask_t mod_mask; 160 | enum anthywl_action action; 161 | }; 162 | 163 | void zwp_input_popup_surface_v2_text_input_rectangle(void *data, 164 | struct zwp_input_popup_surface_v2 *zwp_input_popup_surface_v2, 165 | int32_t x, int32_t y, int32_t width, int32_t height); 166 | 167 | void wl_surface_enter(void *data, struct wl_surface *wl_surface, 168 | struct wl_output *wl_output); 169 | void wl_surface_leave(void *data, struct wl_surface *wl_surface, 170 | struct wl_output *wl_output); 171 | 172 | void zwp_input_method_keyboard_grab_v2_keymap(void *data, 173 | struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2, 174 | uint32_t format, int32_t fd, uint32_t size); 175 | void zwp_input_method_keyboard_grab_v2_key(void *data, 176 | struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2, 177 | uint32_t serial, uint32_t time, uint32_t key, uint32_t state); 178 | void zwp_input_method_keyboard_grab_v2_modifiers(void *data, 179 | struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2, 180 | uint32_t serial, 181 | uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, 182 | uint32_t group); 183 | void zwp_input_method_keyboard_grab_v2_repeat_info(void *data, 184 | struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2, 185 | int32_t rate, int32_t delay); 186 | 187 | void zwp_input_method_v2_activate( 188 | void *data, struct zwp_input_method_v2 *zwp_input_method_v2); 189 | void zwp_input_method_v2_deactivate( 190 | void *data, struct zwp_input_method_v2 *zwp_input_method_v2); 191 | void zwp_input_method_v2_surrounding_text( 192 | void *data, struct zwp_input_method_v2 *zwp_input_method_v2, 193 | char const *text, uint32_t cursor, uint32_t anchor); 194 | void zwp_input_method_v2_text_change_cause( 195 | void *data, struct zwp_input_method_v2 *zwp_input_method_v2, 196 | uint32_t cause); 197 | void zwp_input_method_v2_content_type( 198 | void *data, struct zwp_input_method_v2 *zwp_input_method_v2, 199 | uint32_t hint, uint32_t purpose); 200 | void zwp_input_method_v2_done( 201 | void *data, struct zwp_input_method_v2 *zwp_input_method_v2); 202 | void zwp_input_method_v2_unavailable( 203 | void *data, struct zwp_input_method_v2 *zwp_input_method_v2); 204 | 205 | void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, 206 | uint32_t serial, struct wl_surface *surface, 207 | wl_fixed_t surface_x, wl_fixed_t surface_y); 208 | void wl_pointer_leave(void *data, struct wl_pointer *wl_pointer, 209 | uint32_t serial, struct wl_surface *surface); 210 | void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, 211 | uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y); 212 | void wl_pointer_button(void *data, struct wl_pointer *wl_pointer, 213 | uint32_t serial, uint32_t time, uint32_t button, uint32_t state); 214 | void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer, 215 | uint32_t time, uint32_t axis, wl_fixed_t value); 216 | void wl_pointer_frame(void *data, struct wl_pointer *wl_pointer); 217 | void wl_pointer_axis_source(void *data, struct wl_pointer *wl_pointer, 218 | uint32_t axis_source); 219 | void wl_pointer_axis_stop(void *data, struct wl_pointer *wl_pointer, 220 | uint32_t time, uint32_t axis); 221 | void wl_pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer, 222 | uint32_t axis, int32_t discrete); 223 | void wl_seat_capabilities(void *data, struct wl_seat *wl_seat, 224 | uint32_t capabilities); 225 | 226 | void wl_seat_name(void *data, struct wl_seat *wl_seat, 227 | char const *name); 228 | void wl_seat_global(struct anthywl_state *state, void *data); 229 | 230 | void wl_output_geometry(void *data, struct wl_output *wl_output, 231 | int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, 232 | int32_t subpixel, const char *make, const char *model, int32_t transform); 233 | void wl_output_mode(void *data, struct wl_output *wl_output, 234 | uint32_t flags, int32_t width, int32_t height, int32_t refresh); 235 | void wl_output_done(void *data, struct wl_output *wl_output); 236 | void wl_output_scale(void *data, struct wl_output *wl_output, 237 | int32_t factor); 238 | void wl_output_global(struct anthywl_state *state, void *data); 239 | 240 | void wl_registry_global(void *data, struct wl_registry *wl_registry, 241 | uint32_t name, char const *interface, uint32_t version); 242 | void wl_registry_global_remove(void *data, 243 | struct wl_registry *wl_registry, uint32_t name); 244 | 245 | struct anthywl_graphics_buffer *anthywl_seat_composing_draw_popup( 246 | struct anthywl_seat *seat, int scale); 247 | struct anthywl_graphics_buffer *anthywl_seat_selecting_draw_popup( 248 | struct anthywl_seat *seat, int scale); 249 | void anthywl_seat_draw_popup(struct anthywl_seat *seat); 250 | void anthywl_seat_init(struct anthywl_seat *seat, 251 | struct anthywl_state *state, struct wl_seat *wl_seat); 252 | void anthywl_seat_init_protocols(struct anthywl_seat *seat); 253 | void anthywl_seat_destroy(struct anthywl_seat *seat); 254 | void anthywl_seat_composing_update(struct anthywl_seat *seat); 255 | void anthywl_seat_composing_commit(struct anthywl_seat *seat); 256 | void anthywl_seat_selecting_update(struct anthywl_seat *seat); 257 | void anthywl_seat_selecting_commit(struct anthywl_seat *seat); 258 | int anthywl_binding_compare(void const *_a, void const *_b); 259 | int anthywl_seat_binding_compare(void const *_a, void const *_b); 260 | int anthywl_seat_binding_compare_without_action( 261 | void const *_a, void const *_b); 262 | bool anthywl_seat_handle_key_bindings(struct anthywl_seat *seat, 263 | struct wl_array *bindings, struct anthywl_seat_binding *press); 264 | bool anthywl_seat_handle_key(struct anthywl_seat *seat, xkb_keycode_t keycode); 265 | void anthywl_seat_repeat_timer_callback(struct anthywl_timer *timer); 266 | void anthywl_seat_set_up_bindings(struct anthywl_seat *seat, 267 | struct wl_array *state_bindings, struct wl_array *seat_bindings); 268 | void anthywl_seat_cursor_update(struct anthywl_seat *seat); 269 | void anthywl_seat_cursor_timer_callback(struct anthywl_timer *timer); 270 | 271 | void anthywl_reload_cursor_theme(struct anthywl_state *state); 272 | bool anthywl_state_init(struct anthywl_state *state); 273 | int anthywl_state_next_timer(struct anthywl_state *state); 274 | void anthywl_state_run_timers(struct anthywl_state *state); 275 | void anthywl_state_run(struct anthywl_state *state); 276 | void anthywl_state_finish(struct anthywl_state *state); 277 | 278 | extern struct zwp_input_popup_surface_v2_listener const 279 | zwp_input_popup_surface_v2_listener; 280 | extern struct wl_seat_listener const wl_seat_listener; 281 | extern struct wl_surface_listener const wl_surface_listener; 282 | extern struct zwp_input_method_keyboard_grab_v2_listener const 283 | zwp_input_method_keyboard_grab_v2_listener; 284 | extern struct zwp_input_method_v2_listener const zwp_input_method_v2_listener; 285 | extern struct wl_pointer_listener const wl_pointer_listener; 286 | extern struct wl_output_listener const wl_output_listener; 287 | extern struct wl_registry_listener const wl_registry_listener; 288 | -------------------------------------------------------------------------------- /include/buffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct anthywl_buffer { 6 | char *text; 7 | size_t len; 8 | size_t pos; 9 | }; 10 | 11 | void anthywl_buffer_init(struct anthywl_buffer *); 12 | void anthywl_buffer_destroy(struct anthywl_buffer *); 13 | void anthywl_buffer_clear(struct anthywl_buffer *); 14 | void anthywl_buffer_append(struct anthywl_buffer *, char const *); 15 | void anthywl_buffer_delete_backwards(struct anthywl_buffer *, size_t); 16 | void anthywl_buffer_delete_forwards(struct anthywl_buffer *, size_t); 17 | void anthywl_buffer_move_left(struct anthywl_buffer *); 18 | void anthywl_buffer_move_right(struct anthywl_buffer *); 19 | void anthywl_buffer_convert_romaji(struct anthywl_buffer *); 20 | void anthywl_buffer_convert_trailing_n(struct anthywl_buffer *); 21 | -------------------------------------------------------------------------------- /include/config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | struct anthywl_config { 7 | bool active_at_startup; 8 | struct wl_array global_bindings; 9 | struct wl_array composing_bindings; 10 | struct wl_array selecting_bindings; 11 | }; 12 | 13 | void anthywl_config_init(struct anthywl_config *config); 14 | bool anthywl_config_load(struct anthywl_config *config); 15 | void anthywl_config_finish(struct anthywl_config *config); 16 | -------------------------------------------------------------------------------- /include/graphics_buffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | struct anthywl_graphics_buffer { 9 | struct wl_list link; 10 | struct anthywl_state *state; 11 | struct wl_buffer *wl_buffer; 12 | int width, height, stride; 13 | unsigned char *data; 14 | size_t size; 15 | bool in_use; 16 | cairo_t *cairo; 17 | cairo_surface_t *cairo_surface; 18 | }; 19 | 20 | struct anthywl_graphics_buffer *anthywl_graphics_buffer_get( 21 | struct wl_shm *wl_shm, struct wl_list *buffers, int width, int height); 22 | void anthywl_graphics_buffer_destroy(struct anthywl_graphics_buffer *buffer); 23 | -------------------------------------------------------------------------------- /include/ipc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | struct anthywl_ipc { 7 | VarlinkService *service; 8 | }; 9 | 10 | int anthywl_ipc_addr(char *addr, size_t size); 11 | bool anthywl_ipc_init(struct anthywl_ipc *ipc); 12 | void anthywl_ipc_finish(struct anthywl_ipc *ipc); 13 | long anthywl_ipc_handle_action(VarlinkService *service, VarlinkCall *call, 14 | VarlinkObject *parameters, uint64_t flags, void *userdata); 15 | -------------------------------------------------------------------------------- /include/keymap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | bool anthywl_make_keymap(char const *string, 8 | int *out_keymap_fd, size_t *out_keymap_size, 9 | uint32_t **out_keys, size_t *out_keys_size); 10 | -------------------------------------------------------------------------------- /include/meson.build: -------------------------------------------------------------------------------- 1 | foreach file: ['default_config', 'ca.tadeo.anthywl.varlink'] 2 | anthywl_src += custom_target(file, 3 | input: '../data' / file, 4 | output: file + '.inc', 5 | command: [file2string, '@INPUT@', '@OUTPUT@'], 6 | ) 7 | endforeach 8 | 9 | anthywl_inc += include_directories('.') 10 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'anthywl', 3 | 'c', 4 | version: '0.0.1-dev', # also update default.nix 5 | license: 'MIT', 6 | meson_version: '>=0.59.0', 7 | default_options: [ 8 | 'c_std=c11', 9 | 'warning_level=2', 10 | ] 11 | ) 12 | 13 | add_project_arguments( 14 | [ 15 | '-D_GNU_SOURCE', 16 | '-Wno-unused-parameter', 17 | ], 18 | language: 'c', 19 | ) 20 | 21 | cc = meson.get_compiler('c') 22 | 23 | rt_dep = cc.find_library('rt') 24 | wayland_client_dep = dependency('wayland-client') 25 | wayland_cursor_dep = dependency('wayland-cursor') 26 | wayland_protocols_dep = dependency('wayland-protocols') 27 | xkbcommon_dep = dependency('xkbcommon') 28 | anthy_dep = dependency('anthy') 29 | pango_dep = dependency('pango') 30 | cairo_dep = dependency('cairo') 31 | pangocairo_dep = dependency('pangocairo') 32 | scfg_dep = dependency('scfg', fallback: 'libscfg') 33 | varlink_dep = dependency('libvarlink', required: get_option('ipc')) 34 | scdoc = dependency('scdoc', native: true, required: get_option('man-pages')) 35 | 36 | if get_option('ipc').enabled() 37 | add_project_arguments(['-DANTHYWL_IPC_SUPPORT'], language: 'c') 38 | endif 39 | 40 | anthywl_src = [] 41 | anthywl_inc = [] 42 | 43 | subdir('buildtools') 44 | subdir('doc') 45 | subdir('protocol') 46 | subdir('include') 47 | subdir('src') 48 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') 2 | option('ipc', type: 'feature', value: 'auto', description: 'IPC support') 3 | -------------------------------------------------------------------------------- /protocol/input-method-unstable-v2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Copyright © 2008-2011 Kristian Høgsberg 6 | Copyright © 2010-2011 Intel Corporation 7 | Copyright © 2012-2013 Collabora, Ltd. 8 | Copyright © 2012, 2013 Intel Corporation 9 | Copyright © 2015, 2016 Jan Arne Petersen 10 | Copyright © 2017, 2018 Red Hat, Inc. 11 | Copyright © 2018 Purism SPC 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a 14 | copy of this software and associated documentation files (the "Software"), 15 | to deal in the Software without restriction, including without limitation 16 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 17 | and/or sell copies of the Software, and to permit persons to whom the 18 | Software is furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice (including the next 21 | paragraph) shall be included in all copies or substantial portions of the 22 | Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 27 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 29 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 30 | DEALINGS IN THE SOFTWARE. 31 | 32 | 33 | 34 | This protocol allows applications to act as input methods for compositors. 35 | 36 | An input method context is used to manage the state of the input method. 37 | 38 | Text strings are UTF-8 encoded, their indices and lengths are in bytes. 39 | 40 | This document adheres to the RFC 2119 when using words like "must", 41 | "should", "may", etc. 42 | 43 | Warning! The protocol described in this file is experimental and 44 | backward incompatible changes may be made. Backward compatible changes 45 | may be added together with the corresponding interface version bump. 46 | Backward incompatible changes are done by bumping the version number in 47 | the protocol and interface names and resetting the interface version. 48 | Once the protocol is to be declared stable, the 'z' prefix and the 49 | version number in the protocol and interface names are removed and the 50 | interface version number is reset. 51 | 52 | 53 | 54 | 55 | An input method object allows for clients to compose text. 56 | 57 | The objects connects the client to a text input in an application, and 58 | lets the client to serve as an input method for a seat. 59 | 60 | The zwp_input_method_v2 object can occupy two distinct states: active and 61 | inactive. In the active state, the object is associated to and 62 | communicates with a text input. In the inactive state, there is no 63 | associated text input, and the only communication is with the compositor. 64 | Initially, the input method is in the inactive state. 65 | 66 | Requests issued in the inactive state must be accepted by the compositor. 67 | Because of the serial mechanism, and the state reset on activate event, 68 | they will not have any effect on the state of the next text input. 69 | 70 | There must be no more than one input method object per seat. 71 | 72 | 73 | 74 | 75 | Notification that a text input focused on this seat requested the input 76 | method to be activated. 77 | 78 | This event serves the purpose of providing the compositor with an 79 | active input method. 80 | 81 | This event resets all state associated with previous enable, disable, 82 | surrounding_text, text_change_cause, and content_type events, as well 83 | as the state associated with set_preedit_string, commit_string, and 84 | delete_surrounding_text requests. In addition, it marks the 85 | zwp_input_method_v2 object as active, and makes any existing 86 | zwp_input_popup_surface_v2 objects visible. 87 | 88 | The surrounding_text, and content_type events must follow before the 89 | next done event if the text input supports the respective 90 | functionality. 91 | 92 | State set with this event is double-buffered. It will get applied on 93 | the next zwp_input_method_v2.done event, and stay valid until changed. 94 | 95 | 96 | 97 | 98 | 99 | Notification that no focused text input currently needs an active 100 | input method on this seat. 101 | 102 | This event marks the zwp_input_method_v2 object as inactive. The 103 | compositor must make all existing zwp_input_popup_surface_v2 objects 104 | invisible until the next activate event. 105 | 106 | State set with this event is double-buffered. It will get applied on 107 | the next zwp_input_method_v2.done event, and stay valid until changed. 108 | 109 | 110 | 111 | 112 | 113 | Updates the surrounding plain text around the cursor, excluding the 114 | preedit text. 115 | 116 | If any preedit text is present, it is replaced with the cursor for the 117 | purpose of this event. 118 | 119 | The argument text is a buffer containing the preedit string, and must 120 | include the cursor position, and the complete selection. It should 121 | contain additional characters before and after these. There is a 122 | maximum length of wayland messages, so text can not be longer than 4000 123 | bytes. 124 | 125 | cursor is the byte offset of the cursor within the text buffer. 126 | 127 | anchor is the byte offset of the selection anchor within the text 128 | buffer. If there is no selected text, anchor must be the same as 129 | cursor. 130 | 131 | If this event does not arrive before the first done event, the input 132 | method may assume that the text input does not support this 133 | functionality and ignore following surrounding_text events. 134 | 135 | Values set with this event are double-buffered. They will get applied 136 | and set to initial values on the next zwp_input_method_v2.done 137 | event. 138 | 139 | The initial state for affected fields is empty, meaning that the text 140 | input does not support sending surrounding text. If the empty values 141 | get applied, subsequent attempts to change them may have no effect. 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | Tells the input method why the text surrounding the cursor changed. 151 | 152 | Whenever the client detects an external change in text, cursor, or 153 | anchor position, it must issue this request to the compositor. This 154 | request is intended to give the input method a chance to update the 155 | preedit text in an appropriate way, e.g. by removing it when the user 156 | starts typing with a keyboard. 157 | 158 | cause describes the source of the change. 159 | 160 | The value set with this event is double-buffered. It will get applied 161 | and set to its initial value on the next zwp_input_method_v2.done 162 | event. 163 | 164 | The initial value of cause is input_method. 165 | 166 | 167 | 168 | 169 | 170 | 171 | Indicates the content type and hint for the current 172 | zwp_input_method_v2 instance. 173 | 174 | Values set with this event are double-buffered. They will get applied 175 | on the next zwp_input_method_v2.done event. 176 | 177 | The initial value for hint is none, and the initial value for purpose 178 | is normal. 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | Atomically applies state changes recently sent to the client. 187 | 188 | The done event establishes and updates the state of the client, and 189 | must be issued after any changes to apply them. 190 | 191 | Text input state (content purpose, content hint, surrounding text, and 192 | change cause) is conceptually double-buffered within an input method 193 | context. 194 | 195 | Events modify the pending state, as opposed to the current state in use 196 | by the input method. A done event atomically applies all pending state, 197 | replacing the current state. After done, the new pending state is as 198 | documented for each related request. 199 | 200 | Events must be applied in the order of arrival. 201 | 202 | Neither current nor pending state are modified unless noted otherwise. 203 | 204 | 205 | 206 | 207 | 208 | Send the commit string text for insertion to the application. 209 | 210 | Inserts a string at current cursor position (see commit event 211 | sequence). The string to commit could be either just a single character 212 | after a key press or the result of some composing. 213 | 214 | The argument text is a buffer containing the string to insert. There is 215 | a maximum length of wayland messages, so text can not be longer than 216 | 4000 bytes. 217 | 218 | Values set with this event are double-buffered. They must be applied 219 | and reset to initial on the next zwp_text_input_v3.commit request. 220 | 221 | The initial value of text is an empty string. 222 | 223 | 224 | 225 | 226 | 227 | 228 | Send the pre-edit string text to the application text input. 229 | 230 | Place a new composing text (pre-edit) at the current cursor position. 231 | Any previously set composing text must be removed. Any previously 232 | existing selected text must be removed. The cursor is moved to a new 233 | position within the preedit string. 234 | 235 | The argument text is a buffer containing the preedit string. There is 236 | a maximum length of wayland messages, so text can not be longer than 237 | 4000 bytes. 238 | 239 | The arguments cursor_begin and cursor_end are counted in bytes relative 240 | to the beginning of the submitted string buffer. Cursor should be 241 | hidden by the text input when both are equal to -1. 242 | 243 | cursor_begin indicates the beginning of the cursor. cursor_end 244 | indicates the end of the cursor. It may be equal or different than 245 | cursor_begin. 246 | 247 | Values set with this event are double-buffered. They must be applied on 248 | the next zwp_input_method_v2.commit event. 249 | 250 | The initial value of text is an empty string. The initial value of 251 | cursor_begin, and cursor_end are both 0. 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | Remove the surrounding text. 261 | 262 | before_length and after_length are the number of bytes before and after 263 | the current cursor index (excluding the preedit text) to delete. 264 | 265 | If any preedit text is present, it is replaced with the cursor for the 266 | purpose of this event. In effect before_length is counted from the 267 | beginning of preedit text, and after_length from its end (see commit 268 | event sequence). 269 | 270 | Values set with this event are double-buffered. They must be applied 271 | and reset to initial on the next zwp_input_method_v2.commit request. 272 | 273 | The initial values of both before_length and after_length are 0. 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | Apply state changes from commit_string, set_preedit_string and 282 | delete_surrounding_text requests. 283 | 284 | The state relating to these events is double-buffered, and each one 285 | modifies the pending state. This request replaces the current state 286 | with the pending state. 287 | 288 | The connected text input is expected to proceed by evaluating the 289 | changes in the following order: 290 | 291 | 1. Replace existing preedit string with the cursor. 292 | 2. Delete requested surrounding text. 293 | 3. Insert commit string with the cursor at its end. 294 | 4. Calculate surrounding text to send. 295 | 5. Insert new preedit text in cursor position. 296 | 6. Place cursor inside preedit text. 297 | 298 | The serial number reflects the last state of the zwp_input_method_v2 299 | object known to the client. The value of the serial argument must be 300 | equal to the number of done events already issued by that object. When 301 | the compositor receives a commit request with a serial different than 302 | the number of past done events, it must proceed as normal, except it 303 | should not change the current state of the zwp_input_method_v2 object. 304 | 305 | 306 | 307 | 308 | 309 | 310 | Creates a new zwp_input_popup_surface_v2 object wrapping a given 311 | surface. 312 | 313 | The surface gets assigned the "input_popup" role. If the surface 314 | already has an assigned role, the compositor must issue a protocol 315 | error. 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | Allow an input method to receive hardware keyboard input and process 324 | key events to generate text events (with pre-edit) over the wire. This 325 | allows input methods which compose multiple key events for inputting 326 | text like it is done for CJK languages. 327 | 328 | The compositor should send all keyboard events on the seat to the grab 329 | holder via the returned wl_keyboard object. Nevertheless, the 330 | compositor may decide not to forward any particular event. The 331 | compositor must not further process any event after it has been 332 | forwarded to the grab holder. 333 | 334 | Releasing the resulting wl_keyboard object releases the grab. 335 | 336 | 338 | 339 | 340 | 341 | 342 | The input method ceased to be available. 343 | 344 | The compositor must issue this event as the only event on the object if 345 | there was another input_method object associated with the same seat at 346 | the time of its creation. 347 | 348 | The compositor must issue this request when the object is no longer 349 | useable, e.g. due to seat removal. 350 | 351 | The input method context becomes inert and should be destroyed after 352 | deactivation is handled. Any further requests and events except for the 353 | destroy request must be ignored. 354 | 355 | 356 | 357 | 358 | 359 | Destroys the zwp_text_input_v2 object and any associated child 360 | objects, i.e. zwp_input_popup_surface_v2 and 361 | zwp_input_method_keyboard_grab_v2. 362 | 363 | 364 | 365 | 366 | 367 | 368 | This interface marks a surface as a popup for interacting with an input 369 | method. 370 | 371 | The compositor should place it near the active text input area. It must 372 | be visible if and only if the input method is in the active state. 373 | 374 | The client must not destroy the underlying wl_surface while the 375 | zwp_input_popup_surface_v2 object exists. 376 | 377 | 378 | 379 | 380 | Notify about the position of the area of the text input expressed as a 381 | rectangle in surface local coordinates. 382 | 383 | This is a hint to the input method telling it the relative position of 384 | the text being entered. 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | The zwp_input_method_keyboard_grab_v2 interface represents an exclusive 399 | grab of the wl_keyboard interface associated with the seat. 400 | 401 | 402 | 403 | 404 | This event provides a file descriptor to the client which can be 405 | memory-mapped to provide a keyboard mapping description. 406 | 407 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | A key was pressed or released. 416 | The time argument is a timestamp with millisecond granularity, with an 417 | undefined base. 418 | 419 | 420 | 421 | 422 | 424 | 425 | 426 | 427 | 428 | Notifies clients that the modifier and/or group state has changed, and 429 | it should update its local state. 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | Informs the client about the keyboard's repeat rate and delay. 445 | 446 | This event is sent as soon as the zwp_input_method_keyboard_grab_v2 447 | object has been created, and is guaranteed to be received by the 448 | client before any key press event. 449 | 450 | Negative values for either rate or delay are illegal. A rate of zero 451 | will disable any repeating (regardless of the value of delay). 452 | 453 | This event can be sent later on as well with a new value if necessary, 454 | so clients should continue listening for the event past the creation 455 | of zwp_input_method_keyboard_grab_v2. 456 | 457 | 459 | 461 | 462 | 463 | 464 | 465 | 466 | The input method manager allows the client to become the input method on 467 | a chosen seat. 468 | 469 | No more than one input method must be associated with any seat at any 470 | given time. 471 | 472 | 473 | 474 | 475 | Request a new input zwp_input_method_v2 object associated with a given 476 | seat. 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | Destroys the zwp_input_method_manager_v2 object. 485 | 486 | The zwp_input_method_v2 objects originating from it remain valid. 487 | 488 | 489 | 490 | 491 | -------------------------------------------------------------------------------- /protocol/meson.build: -------------------------------------------------------------------------------- 1 | wayland_protocols_dir = wayland_protocols_dep.get_variable(pkgconfig: 'pkgdatadir') 2 | 3 | wayland_scanner_dep = dependency('wayland-scanner', native: true) 4 | wayland_scanner_bin = find_program( 5 | wayland_scanner_dep.get_variable(pkgconfig: 'wayland_scanner'), 6 | native: true, 7 | ) 8 | 9 | protocols = { 10 | 'text-input-v3': wayland_protocols_dir / 'unstable/text-input/text-input-unstable-v3.xml', 11 | 'zwp-input-method-unstable-v2': 'input-method-unstable-v2.xml', 12 | 'zwp-virtual-keyboard-unstable-v1': 'virtual-keyboard-unstable-v1.xml', 13 | } 14 | 15 | protocols_src = [] 16 | protocols_inc = [] 17 | 18 | foreach name, path : protocols 19 | protocols_src += custom_target( 20 | name.underscorify() + '_c', 21 | input: path, 22 | output: '@BASENAME@-protocol.c', 23 | command: [wayland_scanner_bin, 'private-code', '@INPUT@', '@OUTPUT@'] 24 | ) 25 | 26 | protocols_inc += custom_target( 27 | name.underscorify() + '_client_protocol_h', 28 | input: path, 29 | output: '@BASENAME@-client-protocol.h', 30 | command: [wayland_scanner_bin, 'client-header', '@INPUT@', '@OUTPUT@'] 31 | ) 32 | endforeach 33 | 34 | protocols_lib = static_library( 35 | 'protocols', 36 | [protocols_src, protocols_inc], 37 | ) 38 | 39 | protocols_dep = declare_dependency( 40 | link_with: protocols_lib, 41 | sources: protocols_inc, 42 | ) 43 | -------------------------------------------------------------------------------- /protocol/virtual-keyboard-unstable-v1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright © 2008-2011 Kristian Høgsberg 5 | Copyright © 2010-2013 Intel Corporation 6 | Copyright © 2012-2013 Collabora, Ltd. 7 | Copyright © 2018 Purism SPC 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a 10 | copy of this software and associated documentation files (the "Software"), 11 | to deal in the Software without restriction, including without limitation 12 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 13 | and/or sell copies of the Software, and to permit persons to whom the 14 | Software is furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice (including the next 17 | paragraph) shall be included in all copies or substantial portions of the 18 | Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 23 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | DEALINGS IN THE SOFTWARE. 27 | 28 | 29 | 30 | 31 | The virtual keyboard provides an application with requests which emulate 32 | the behaviour of a physical keyboard. 33 | 34 | This interface can be used by clients on its own to provide raw input 35 | events, or it can accompany the input method protocol. 36 | 37 | 38 | 39 | 40 | Provide a file descriptor to the compositor which can be 41 | memory-mapped to provide a keyboard mapping description. 42 | 43 | Format carries a value from the keymap_format enumeration. 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | A key was pressed or released. 57 | The time argument is a timestamp with millisecond granularity, with an 58 | undefined base. All requests regarding a single object must share the 59 | same clock. 60 | 61 | Keymap must be set before issuing this request. 62 | 63 | State carries a value from the key_state enumeration. 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | Notifies the compositor that the modifier and/or group state has 73 | changed, and it should update state. 74 | 75 | The client should use wl_keyboard.modifiers event to synchronize its 76 | internal state with seat state. 77 | 78 | Keymap must be set before issuing this request. 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | A virtual keyboard manager allows an application to provide keyboard 94 | input events as if they came from a physical keyboard. 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | Creates a new virtual keyboard associated to a seat. 104 | 105 | If the compositor enables a keyboard to perform arbitrary actions, it 106 | should present an error when an untrusted client requests a new 107 | keyboard. 108 | 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /src/actions.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "anthywl.h" 5 | #include "actions.h" 6 | 7 | #define ARRAY_LEN(x) (sizeof (x) / sizeof *(x)) 8 | 9 | static bool anthywl_seat_handle_enable(struct anthywl_seat *seat) { 10 | seat->is_composing = true; 11 | return true; 12 | } 13 | 14 | static bool anthywl_seat_handle_disable(struct anthywl_seat *seat) { 15 | seat->is_composing = false; 16 | seat->is_selecting_popup_visible = false; 17 | anthywl_buffer_clear(&seat->buffer); 18 | anthywl_seat_composing_update(seat); 19 | return true; 20 | } 21 | 22 | static bool anthywl_seat_handle_toggle(struct anthywl_seat *seat) { 23 | if (seat->is_composing) 24 | return anthywl_seat_handle_disable(seat); 25 | else 26 | return anthywl_seat_handle_enable(seat); 27 | } 28 | 29 | static bool anthywl_seat_handle_delete_left(struct anthywl_seat *seat) { 30 | if (!seat->is_composing) 31 | return true; 32 | if (seat->buffer.len == 0) 33 | return true; 34 | if (seat->is_selecting) { 35 | seat->is_selecting = false; 36 | seat->is_selecting_popup_visible = false; 37 | } 38 | anthywl_buffer_delete_backwards(&seat->buffer, 1); 39 | anthywl_seat_composing_update(seat); 40 | return true; 41 | } 42 | 43 | static bool anthywl_seat_handle_delete_right(struct anthywl_seat *seat) { 44 | if (!seat->is_composing) 45 | return true; 46 | if (seat->buffer.len == 0) 47 | return true; 48 | if (seat->is_selecting) { 49 | seat->is_selecting = false; 50 | seat->is_selecting_popup_visible = false; 51 | } 52 | anthywl_buffer_delete_forwards(&seat->buffer, 1); 53 | anthywl_seat_composing_update(seat); 54 | return true; 55 | } 56 | 57 | static bool anthywl_seat_handle_move_left(struct anthywl_seat *seat) { 58 | if (!seat->is_composing) 59 | return true; 60 | if (seat->buffer.len == 0) 61 | return true; 62 | if (seat->is_selecting) { 63 | anthy_commit_segment( 64 | seat->anthy_context, 65 | seat->current_segment, 66 | seat->selected_candidates[seat->current_segment]); 67 | if (seat->current_segment != 0) 68 | seat->current_segment -= 1; 69 | anthywl_seat_selecting_update(seat); 70 | return true; 71 | } 72 | anthywl_buffer_move_left(&seat->buffer); 73 | anthywl_seat_composing_update(seat); 74 | return true; 75 | } 76 | 77 | static bool anthywl_seat_handle_move_right(struct anthywl_seat *seat) { 78 | if (!seat->is_composing) 79 | return true; 80 | if (seat->buffer.len == 0) 81 | return true; 82 | if (seat->is_selecting) { 83 | anthy_commit_segment( 84 | seat->anthy_context, 85 | seat->current_segment, 86 | seat->selected_candidates[seat->current_segment]); 87 | if (seat->current_segment != seat->segment_count - 1) 88 | seat->current_segment += 1; 89 | anthywl_seat_selecting_update(seat); 90 | return true; 91 | } 92 | anthywl_buffer_move_right(&seat->buffer); 93 | anthywl_seat_composing_update(seat); 94 | return true; 95 | } 96 | 97 | static void anthywl_seat_expand(struct anthywl_seat *seat, int amount) { 98 | anthy_resize_segment( 99 | seat->anthy_context, seat->current_segment, amount); 100 | struct anthy_conv_stat conv_stat; 101 | anthy_get_stat(seat->anthy_context, &conv_stat); 102 | seat->selected_candidates = realloc( 103 | seat->selected_candidates, conv_stat.nr_segment * sizeof(int)); 104 | memset(seat->selected_candidates, 0, conv_stat.nr_segment * sizeof(int)); 105 | seat->segment_count = conv_stat.nr_segment; 106 | anthywl_seat_selecting_update(seat); 107 | } 108 | 109 | static bool anthywl_seat_handle_expand_left(struct anthywl_seat *seat) { 110 | if (!seat->is_selecting) 111 | return true; 112 | anthywl_seat_expand(seat, -1); 113 | return true; 114 | } 115 | 116 | static bool anthywl_seat_handle_expand_right(struct anthywl_seat *seat) { 117 | if (!seat->is_selecting) 118 | return true; 119 | anthywl_seat_expand(seat, 1); 120 | return true; 121 | } 122 | 123 | static bool anthywl_seat_handle_select(struct anthywl_seat *seat) { 124 | if (!seat->is_composing) 125 | return true; 126 | if (seat->is_selecting) 127 | return true; 128 | if (seat->buffer.len == 0) 129 | return true; 130 | anthywl_buffer_convert_trailing_n(&seat->buffer); 131 | seat->is_selecting = true; 132 | seat->is_selecting_popup_visible = true; 133 | anthy_reset_context(seat->anthy_context); 134 | anthy_set_string(seat->anthy_context, seat->buffer.text); 135 | struct anthy_conv_stat conv_stat; 136 | anthy_get_stat(seat->anthy_context, &conv_stat); 137 | free(seat->selected_candidates); 138 | seat->selected_candidates = calloc(conv_stat.nr_segment, sizeof(int)); 139 | seat->segment_count = conv_stat.nr_segment; 140 | seat->current_segment = 0; 141 | anthywl_seat_selecting_update(seat); 142 | return true; 143 | } 144 | 145 | static bool anthywl_seat_handle_compose(struct anthywl_seat *seat) { 146 | if (!seat->is_composing) 147 | return anthywl_seat_handle_enable(seat); 148 | if (!seat->is_selecting) 149 | return true; 150 | seat->is_selecting = false; 151 | anthywl_seat_composing_update(seat); 152 | return true; 153 | } 154 | 155 | static bool anthywl_seat_handle_accept(struct anthywl_seat *seat) { 156 | if (!seat->is_composing) 157 | return true; 158 | if (seat->buffer.len == 0) 159 | return true; 160 | if (seat->is_selecting) 161 | anthywl_seat_selecting_commit(seat); 162 | else 163 | anthywl_seat_composing_commit(seat); 164 | return true; 165 | } 166 | 167 | static bool anthywl_seat_handle_discard(struct anthywl_seat *seat) { 168 | if (!seat->is_composing) 169 | return true; 170 | if (seat->is_selecting) 171 | seat->is_selecting = false; 172 | anthywl_buffer_clear(&seat->buffer); 173 | anthywl_seat_composing_commit(seat); 174 | return true; 175 | } 176 | 177 | static bool anthywl_seat_handle_prev_candidate(struct anthywl_seat *seat) { 178 | if (!seat->is_selecting) 179 | return true; 180 | 181 | struct anthy_conv_stat conv_stat; 182 | anthy_get_stat(seat->anthy_context, &conv_stat); 183 | assert(conv_stat.nr_segment == seat->segment_count); 184 | 185 | struct anthy_segment_stat segment_stat; 186 | anthy_get_segment_stat( 187 | seat->anthy_context, seat->current_segment, &segment_stat); 188 | 189 | if (seat->selected_candidates[seat->current_segment] != 0) 190 | seat->selected_candidates[seat->current_segment] -= 1; 191 | seat->is_selecting_popup_visible = true; 192 | anthywl_seat_selecting_update(seat); 193 | 194 | return true; 195 | } 196 | 197 | static bool anthywl_seat_handle_next_candidate(struct anthywl_seat *seat) { 198 | if (!seat->is_selecting) 199 | return true; 200 | 201 | struct anthy_conv_stat conv_stat; 202 | anthy_get_stat(seat->anthy_context, &conv_stat); 203 | assert(conv_stat.nr_segment == seat->segment_count); 204 | 205 | struct anthy_segment_stat segment_stat; 206 | anthy_get_segment_stat( 207 | seat->anthy_context, seat->current_segment, &segment_stat); 208 | 209 | if (seat->selected_candidates[seat->current_segment] 210 | != segment_stat.nr_candidate - 1) 211 | { 212 | seat->selected_candidates[seat->current_segment] += 1; 213 | } 214 | seat->is_selecting_popup_visible = true; 215 | anthywl_seat_selecting_update(seat); 216 | 217 | return true; 218 | } 219 | 220 | static bool anthywl_seat_handle_cycle_candidate(struct anthywl_seat *seat) { 221 | if (!seat->is_selecting) 222 | return true; 223 | 224 | struct anthy_conv_stat conv_stat; 225 | anthy_get_stat(seat->anthy_context, &conv_stat); 226 | assert(conv_stat.nr_segment == seat->segment_count); 227 | 228 | struct anthy_segment_stat segment_stat; 229 | anthy_get_segment_stat( 230 | seat->anthy_context, seat->current_segment, &segment_stat); 231 | 232 | if (seat->selected_candidates[seat->current_segment] 233 | != segment_stat.nr_candidate - 1) 234 | { 235 | seat->selected_candidates[seat->current_segment] += 1; 236 | } 237 | seat->is_selecting_popup_visible = true; 238 | anthywl_seat_selecting_update(seat); 239 | 240 | return true; 241 | } 242 | 243 | static bool anthywl_seat_handle_select_special_candidate( 244 | struct anthywl_seat *seat, int idx) 245 | { 246 | if (!seat->is_selecting) 247 | return true; 248 | 249 | seat->selected_candidates[seat->current_segment] = idx; 250 | seat->is_selecting_popup_visible = true; 251 | anthywl_seat_selecting_update(seat); 252 | 253 | return true; 254 | } 255 | 256 | static bool anthywl_seat_handle_select_unconverted_candidate( 257 | struct anthywl_seat *seat) 258 | { 259 | return anthywl_seat_handle_select_special_candidate( 260 | seat, NTH_UNCONVERTED_CANDIDATE); 261 | } 262 | 263 | static bool anthywl_seat_handle_select_katakana_candidate( 264 | struct anthywl_seat *seat) 265 | { 266 | return anthywl_seat_handle_select_special_candidate( 267 | seat, NTH_KATAKANA_CANDIDATE); 268 | } 269 | 270 | static bool anthywl_seat_handle_select_hiragana_candidate( 271 | struct anthywl_seat *seat) 272 | { 273 | return anthywl_seat_handle_select_special_candidate( 274 | seat, NTH_HIRAGANA_CANDIDATE); 275 | } 276 | 277 | static bool anthywl_seat_handle_select_halfkana_candidate( 278 | struct anthywl_seat *seat) 279 | { 280 | return anthywl_seat_handle_select_special_candidate( 281 | seat, NTH_HALFKANA_CANDIDATE); 282 | } 283 | 284 | enum anthywl_action anthywl_action_from_string(const char *name) { 285 | // TODO: use bsearch 286 | static struct{ 287 | char const *name; 288 | enum anthywl_action action; 289 | } const actions[] = { 290 | { "enable", ANTHYWL_ACTION_ENABLE }, 291 | { "disable", ANTHYWL_ACTION_DISABLE }, 292 | { "toggle", ANTHYWL_ACTION_TOGGLE }, 293 | { "delete-left", ANTHYWL_ACTION_DELETE_LEFT }, 294 | { "delete-right", ANTHYWL_ACTION_DELETE_RIGHT }, 295 | { "move-left", ANTHYWL_ACTION_MOVE_LEFT }, 296 | { "move-right", ANTHYWL_ACTION_MOVE_RIGHT }, 297 | { "expand-left", ANTHYWL_ACTION_EXPAND_LEFT }, 298 | { "expand-right", ANTHYWL_ACTION_EXPAND_RIGHT }, 299 | { "select", ANTHYWL_ACTION_SELECT }, 300 | { "compose", ANTHYWL_ACTION_COMPOSE }, 301 | { "accept", ANTHYWL_ACTION_ACCEPT }, 302 | { "discard", ANTHYWL_ACTION_DISCARD }, 303 | { "prev-candidate", ANTHYWL_ACTION_PREV_CANDIDATE }, 304 | { "next-candidate", ANTHYWL_ACTION_NEXT_CANDIDATE }, 305 | { "cycle-candidate", ANTHYWL_ACTION_CYCLE_CANDIDATE }, 306 | { "select-unconverted-candidate", ANTHYWL_ACTION_SELECT_UNCONVERTED_CANDIDATE }, 307 | { "select-katakana-candidate", ANTHYWL_ACTION_SELECT_KATAKANA_CANDIDATE }, 308 | { "select-hiragana-candidate", ANTHYWL_ACTION_SELECT_HIRAGANA_CANDIDATE }, 309 | { "select-halfkana-candidate", ANTHYWL_ACTION_SELECT_HALFKANA_CANDIDATE }, 310 | }; 311 | 312 | for (size_t i = 0; i < ARRAY_LEN(actions); i++) { 313 | if (strcmp(name, actions[i].name) == 0) 314 | return actions[i].action; 315 | } 316 | 317 | return ANTHYWL_ACTION_INVALID; 318 | } 319 | 320 | static bool(*const anthywl_seat_action_handlers[_ANTHYWL_ACTION_LAST]) 321 | (struct anthywl_seat *) = 322 | { 323 | [ANTHYWL_ACTION_ENABLE] = anthywl_seat_handle_enable, 324 | [ANTHYWL_ACTION_DISABLE] = anthywl_seat_handle_disable, 325 | [ANTHYWL_ACTION_TOGGLE] = anthywl_seat_handle_toggle, 326 | [ANTHYWL_ACTION_DELETE_LEFT] = anthywl_seat_handle_delete_left, 327 | [ANTHYWL_ACTION_DELETE_RIGHT] = anthywl_seat_handle_delete_right, 328 | [ANTHYWL_ACTION_MOVE_LEFT] = anthywl_seat_handle_move_left, 329 | [ANTHYWL_ACTION_MOVE_RIGHT] = anthywl_seat_handle_move_right, 330 | [ANTHYWL_ACTION_EXPAND_LEFT] = anthywl_seat_handle_expand_left, 331 | [ANTHYWL_ACTION_EXPAND_RIGHT] = anthywl_seat_handle_expand_right, 332 | [ANTHYWL_ACTION_SELECT] = anthywl_seat_handle_select, 333 | [ANTHYWL_ACTION_COMPOSE] = anthywl_seat_handle_compose, 334 | [ANTHYWL_ACTION_ACCEPT] = anthywl_seat_handle_accept, 335 | [ANTHYWL_ACTION_DISCARD] = anthywl_seat_handle_discard, 336 | [ANTHYWL_ACTION_PREV_CANDIDATE] = anthywl_seat_handle_prev_candidate, 337 | [ANTHYWL_ACTION_NEXT_CANDIDATE] = anthywl_seat_handle_next_candidate, 338 | [ANTHYWL_ACTION_CYCLE_CANDIDATE] = anthywl_seat_handle_cycle_candidate, 339 | [ANTHYWL_ACTION_SELECT_UNCONVERTED_CANDIDATE] = anthywl_seat_handle_select_unconverted_candidate, 340 | [ANTHYWL_ACTION_SELECT_KATAKANA_CANDIDATE] = anthywl_seat_handle_select_katakana_candidate, 341 | [ANTHYWL_ACTION_SELECT_HIRAGANA_CANDIDATE] = anthywl_seat_handle_select_hiragana_candidate, 342 | [ANTHYWL_ACTION_SELECT_HALFKANA_CANDIDATE] = anthywl_seat_handle_select_halfkana_candidate, 343 | }; 344 | 345 | bool anthywl_seat_handle_action(struct anthywl_seat *seat, 346 | enum anthywl_action action) 347 | { 348 | if (action <= ANTHYWL_ACTION_INVALID || action >= _ANTHYWL_ACTION_LAST) 349 | return false; 350 | bool (*handler)(struct anthywl_seat *) 351 | = anthywl_seat_action_handlers[action]; 352 | if (!handler) 353 | return false; 354 | return handler(seat); 355 | } 356 | -------------------------------------------------------------------------------- /src/anthywl.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "input-method-unstable-v2-client-protocol.h" 26 | #include "text-input-unstable-v3-client-protocol.h" 27 | #include "virtual-keyboard-unstable-v1-client-protocol.h" 28 | 29 | #include "anthywl.h" 30 | #include "buffer.h" 31 | #include "graphics_buffer.h" 32 | #include "keymap.h" 33 | 34 | #ifdef ANTHYWL_IPC_SUPPORT 35 | #include 36 | #include "ipc.h" 37 | #endif 38 | 39 | #define min(a, b) \ 40 | ({ \ 41 | __typeof__(a) _a = (a); \ 42 | __typeof__(b) _b = (b); \ 43 | _a < _b ? _a : _b; \ 44 | }) 45 | 46 | #define max(a, b) \ 47 | ({ \ 48 | __typeof__(a) _a = (a); \ 49 | __typeof__(b) _b = (b); \ 50 | _a > _b ? _a : _b; \ 51 | }) 52 | 53 | #define ARRAY_LEN(x) (sizeof (x) / sizeof *(x)) 54 | 55 | void zwp_input_popup_surface_v2_text_input_rectangle(void *data, 56 | struct zwp_input_popup_surface_v2 *zwp_input_popup_surface_v2, 57 | int32_t x, int32_t y, int32_t width, int32_t height) 58 | { 59 | } 60 | 61 | #define BORDER (1.0) 62 | #define PADDING (5.0) 63 | 64 | struct anthywl_graphics_buffer *anthywl_seat_composing_draw_popup( 65 | struct anthywl_seat *seat, int scale) 66 | { 67 | cairo_surface_t *recording_cairo_surface = 68 | cairo_recording_surface_create(CAIRO_CONTENT_COLOR_ALPHA, NULL); 69 | cairo_t *recording_cairo = cairo_create(recording_cairo_surface); 70 | PangoLayout *layout = pango_cairo_create_layout(recording_cairo); 71 | 72 | double x = BORDER + PADDING, y = BORDER + PADDING; 73 | double max_text_width = 0.0; 74 | cairo_move_to(recording_cairo, x, y); 75 | 76 | pango_layout_set_text(layout, seat->buffer.text, -1); 77 | PangoRectangle rect; 78 | pango_layout_get_extents(layout, NULL, &rect); 79 | double text_width = (double)rect.width / PANGO_SCALE; 80 | double text_height = (double)rect.height / PANGO_SCALE; 81 | if (text_width > max_text_width) { 82 | max_text_width = text_width; 83 | } 84 | y += text_height; 85 | cairo_set_source_rgba(recording_cairo, 1.0, 1.0, 1.0, 1.0); 86 | pango_cairo_update_layout(recording_cairo, layout); 87 | pango_cairo_show_layout(recording_cairo, layout); 88 | cairo_move_to(recording_cairo, x, y); 89 | 90 | x = max_text_width + BORDER * 2.0 + PADDING * 2.0; 91 | y += BORDER + PADDING; 92 | 93 | double half_border = BORDER / 2.0; 94 | cairo_move_to(recording_cairo, half_border, half_border); 95 | cairo_line_to(recording_cairo, x - half_border, half_border); 96 | cairo_line_to(recording_cairo, x - half_border, y - half_border); 97 | cairo_line_to(recording_cairo, half_border, y - half_border); 98 | cairo_line_to(recording_cairo, half_border, half_border); 99 | cairo_set_line_width(recording_cairo, BORDER); 100 | cairo_set_source_rgba(recording_cairo, 1.0, 1.0, 1.0, 1.0); 101 | cairo_stroke(recording_cairo); 102 | 103 | double surface_x, surface_y, surface_width, surface_height; 104 | cairo_recording_surface_ink_extents(recording_cairo_surface, 105 | &surface_x, &surface_y, &surface_width, &surface_height); 106 | 107 | struct anthywl_graphics_buffer *buffer = anthywl_graphics_buffer_get( 108 | seat->state->wl_shm, &seat->state->buffers, 109 | surface_width * scale, surface_height * scale); 110 | cairo_scale(buffer->cairo, scale, scale); 111 | cairo_set_operator(buffer->cairo, CAIRO_OPERATOR_CLEAR); 112 | cairo_paint(buffer->cairo); 113 | cairo_set_operator(buffer->cairo, CAIRO_OPERATOR_OVER); 114 | cairo_set_source_rgba(buffer->cairo, 0.0, 0.0, 0.0, 1.0); 115 | cairo_paint(buffer->cairo); 116 | cairo_set_source_surface(buffer->cairo, recording_cairo_surface, 0.0, 0.0); 117 | cairo_paint(buffer->cairo); 118 | 119 | return buffer; 120 | } 121 | 122 | struct anthywl_graphics_buffer *anthywl_seat_selecting_draw_popup( 123 | struct anthywl_seat *seat, int scale) 124 | { 125 | cairo_surface_t *recording_cairo_surface = 126 | cairo_recording_surface_create(CAIRO_CONTENT_COLOR_ALPHA, NULL); 127 | cairo_t *recording_cairo = cairo_create(recording_cairo_surface); 128 | PangoLayout *layout = pango_cairo_create_layout(recording_cairo); 129 | 130 | double x = BORDER + PADDING, y = BORDER + PADDING; 131 | double line_y = 0; 132 | double max_x = 0; 133 | cairo_move_to(recording_cairo, x, y); 134 | 135 | if (seat->is_composing_popup_visible) { 136 | GString *markup = g_string_new(NULL); 137 | for (int i = 0; i < seat->segment_count; i++) { 138 | struct anthy_segment_stat segment_stat; 139 | anthy_get_segment_stat(seat->anthy_context, i, &segment_stat); 140 | char buf[64]; 141 | anthy_get_segment(seat->anthy_context, 142 | i, seat->selected_candidates[i], buf, sizeof buf); 143 | char *markup_fragment; 144 | if (i == seat->current_segment) 145 | markup_fragment = g_markup_printf_escaped("%s", buf); 146 | else 147 | markup_fragment = g_markup_escape_text(buf, -1); 148 | g_string_append(markup, markup_fragment); 149 | g_free(markup_fragment); 150 | } 151 | pango_layout_set_markup(layout, markup->str, -1); 152 | g_string_free(markup, TRUE); 153 | 154 | PangoRectangle rect; 155 | pango_layout_get_extents(layout, NULL, &rect); 156 | max_x = (double)rect.width / PANGO_SCALE > max_x 157 | ? (double)rect.width / PANGO_SCALE 158 | : max_x; 159 | y += (double)rect.height / PANGO_SCALE; 160 | cairo_set_source_rgba(recording_cairo, 1.0, 1.0, 1.0, 1.0); 161 | pango_cairo_update_layout(recording_cairo, layout); 162 | pango_cairo_show_layout(recording_cairo, layout); 163 | line_y = y + PADDING + BORDER / 2.0; 164 | y += BORDER + PADDING * 2.0; 165 | cairo_move_to(recording_cairo, x, y); 166 | } 167 | 168 | { 169 | struct anthy_segment_stat segment_stat; 170 | anthy_get_segment_stat( 171 | seat->anthy_context, seat->current_segment, &segment_stat); 172 | char buf[64]; 173 | int selected_candidate = 174 | seat->selected_candidates[seat->current_segment]; 175 | int candidate_offset = selected_candidate / 5 * 5; 176 | for (int i = candidate_offset; 177 | i < min(candidate_offset + 5, segment_stat.nr_candidate); i++) 178 | { 179 | anthy_get_segment( 180 | seat->anthy_context, seat->current_segment, i, buf, sizeof buf); 181 | PangoAttrList *attrs = pango_attr_list_new(); 182 | if (i == selected_candidate) { 183 | PangoAttribute *attr = pango_attr_weight_new(PANGO_WEIGHT_BOLD); 184 | pango_attr_list_insert(attrs, attr); 185 | } 186 | char *text; 187 | if (asprintf(&text, "%d. %s", i - candidate_offset + 1, buf) < 0) { 188 | fprintf(stderr, "Memory allocation failed\n"); 189 | abort(); 190 | } 191 | pango_layout_set_text(layout, text, -1); 192 | free(text); 193 | pango_layout_set_attributes(layout, attrs); 194 | 195 | PangoRectangle rect; 196 | pango_layout_get_extents(layout, NULL, &rect); 197 | max_x = (double)rect.width / PANGO_SCALE > max_x 198 | ? (double)rect.width / PANGO_SCALE 199 | : max_x; 200 | y += (double)rect.height / PANGO_SCALE; 201 | cairo_set_source_rgba(recording_cairo, 1.0, 1.0, 1.0, 1.0); 202 | pango_cairo_update_layout(recording_cairo, layout); 203 | pango_cairo_show_layout(recording_cairo, layout); 204 | cairo_move_to(recording_cairo, x, y); 205 | } 206 | } 207 | 208 | max_x += BORDER * 2.0 + PADDING * 2.0; 209 | y += BORDER + PADDING; 210 | 211 | double half_border = BORDER / 2.0; 212 | 213 | if (seat->is_composing_popup_visible) { 214 | cairo_move_to(recording_cairo, half_border, line_y); 215 | cairo_line_to(recording_cairo, max_x, line_y); 216 | cairo_set_line_width(recording_cairo, BORDER); 217 | cairo_set_source_rgba(recording_cairo, 1.0, 1.0, 1.0, 1.0); 218 | cairo_stroke(recording_cairo); 219 | } 220 | 221 | cairo_move_to(recording_cairo, half_border, half_border); 222 | cairo_line_to(recording_cairo, max_x - half_border, half_border); 223 | cairo_line_to(recording_cairo, max_x - half_border, y - half_border); 224 | cairo_line_to(recording_cairo, half_border, y - half_border); 225 | cairo_line_to(recording_cairo, half_border, half_border); 226 | cairo_set_line_width(recording_cairo, BORDER); 227 | cairo_set_source_rgba(recording_cairo, 1.0, 1.0, 1.0, 1.0); 228 | cairo_stroke(recording_cairo); 229 | 230 | double surface_x, surface_y, surface_width, surface_height; 231 | cairo_recording_surface_ink_extents(recording_cairo_surface, 232 | &surface_x, &surface_y, &surface_width, &surface_height); 233 | 234 | struct anthywl_graphics_buffer *buffer = anthywl_graphics_buffer_get( 235 | seat->state->wl_shm, &seat->state->buffers, 236 | surface_width * scale, surface_height * scale); 237 | cairo_scale(buffer->cairo, scale, scale); 238 | cairo_set_operator(buffer->cairo, CAIRO_OPERATOR_CLEAR); 239 | cairo_paint(buffer->cairo); 240 | cairo_set_operator(buffer->cairo, CAIRO_OPERATOR_OVER); 241 | cairo_set_source_rgba(buffer->cairo, 0.0, 0.0, 0.0, 1.0); 242 | cairo_paint(buffer->cairo); 243 | cairo_set_source_surface(buffer->cairo, recording_cairo_surface, 0.0, 0.0); 244 | cairo_paint(buffer->cairo); 245 | 246 | return buffer; 247 | } 248 | 249 | void anthywl_seat_draw_popup(struct anthywl_seat *seat) { 250 | int scale = seat->scale != 0 ? seat->scale : seat->state->max_scale; 251 | 252 | struct anthywl_graphics_buffer *buffer = NULL; 253 | if (seat->is_selecting && seat->is_selecting_popup_visible) { 254 | buffer = anthywl_seat_selecting_draw_popup(seat, scale); 255 | } else if (seat->is_composing 256 | && seat->buffer.len != 0 257 | && seat->is_composing_popup_visible) 258 | { 259 | buffer = anthywl_seat_composing_draw_popup(seat, scale); 260 | } 261 | 262 | if (buffer) { 263 | wl_surface_attach(seat->wl_surface, buffer->wl_buffer, 0, 0); 264 | wl_surface_damage_buffer(seat->wl_surface, 0, 0, 265 | buffer->width, buffer->height); 266 | wl_surface_set_buffer_scale(seat->wl_surface, scale); 267 | } else { 268 | wl_surface_attach(seat->wl_surface, NULL, 0, 0); 269 | } 270 | 271 | wl_surface_commit(seat->wl_surface); 272 | } 273 | 274 | void anthywl_seat_init(struct anthywl_seat *seat, 275 | struct anthywl_state *state, struct wl_seat *wl_seat) 276 | { 277 | seat->state = state; 278 | seat->wl_seat = wl_seat; 279 | wl_seat_add_listener(wl_seat, &wl_seat_listener, seat); 280 | seat->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); 281 | seat->cursor_timer.callback = anthywl_seat_cursor_timer_callback; 282 | wl_array_init(&seat->outputs); 283 | if (state->running) 284 | anthywl_seat_init_protocols(seat); 285 | anthywl_buffer_init(&seat->buffer); 286 | seat->anthy_context = anthy_create_context(); 287 | anthy_context_set_encoding(seat->anthy_context, ANTHY_UTF8_ENCODING); 288 | seat->repeat_timer.callback = anthywl_seat_repeat_timer_callback; 289 | seat->is_composing = state->config.active_at_startup; 290 | } 291 | 292 | void wl_surface_enter(void *data, struct wl_surface *wl_surface, 293 | struct wl_output *wl_output) 294 | { 295 | struct anthywl_seat *seat = data; 296 | struct anthywl_output *output = wl_output_get_user_data(wl_output); 297 | struct anthywl_output **output_iter; 298 | wl_array_for_each(output_iter, &seat->outputs) { 299 | if (*output_iter == NULL) { 300 | *output_iter = output; 301 | goto rescale; 302 | } 303 | } 304 | *(void **)wl_array_add(&seat->outputs, sizeof(void *)) = output; 305 | rescale:; 306 | int scale = output->scale > seat->scale ? output->scale : seat->scale; 307 | seat->scale = scale; 308 | } 309 | 310 | void wl_surface_leave(void *data, struct wl_surface *wl_surface, 311 | struct wl_output *wl_output) 312 | { 313 | struct anthywl_seat *seat = data; 314 | struct anthywl_output *output = wl_output_get_user_data(wl_output); 315 | seat->scale = output->scale; 316 | struct anthywl_output **output_iter; 317 | wl_array_for_each(output_iter, &seat->outputs) { 318 | if (*output_iter == output) { 319 | *output_iter = NULL; 320 | break; 321 | } 322 | } 323 | int scale = 0; 324 | wl_array_for_each(output_iter, &seat->outputs) { 325 | int scale_iter = *output_iter != NULL 326 | ? (*output_iter)->scale 327 | : 0; 328 | scale = scale_iter > scale ? scale_iter : scale; 329 | } 330 | seat->scale = scale; 331 | } 332 | 333 | void anthywl_seat_init_protocols(struct anthywl_seat *seat) { 334 | seat->zwp_input_method_v2 = 335 | zwp_input_method_manager_v2_get_input_method( 336 | seat->state->zwp_input_method_manager_v2, seat->wl_seat); 337 | zwp_input_method_v2_add_listener(seat->zwp_input_method_v2, 338 | &zwp_input_method_v2_listener, seat); 339 | seat->zwp_virtual_keyboard_v1_passthrough = 340 | zwp_virtual_keyboard_manager_v1_create_virtual_keyboard( 341 | seat->state->zwp_virtual_keyboard_manager_v1, seat->wl_seat); 342 | seat->zwp_virtual_keyboard_v1_backup_input = 343 | zwp_virtual_keyboard_manager_v1_create_virtual_keyboard( 344 | seat->state->zwp_virtual_keyboard_manager_v1, seat->wl_seat); 345 | seat->zwp_input_method_keyboard_grab_v2 = 346 | zwp_input_method_v2_grab_keyboard(seat->zwp_input_method_v2); 347 | zwp_input_method_keyboard_grab_v2_add_listener( 348 | seat->zwp_input_method_keyboard_grab_v2, 349 | &zwp_input_method_keyboard_grab_v2_listener, seat); 350 | seat->wl_surface = wl_compositor_create_surface(seat->state->wl_compositor); 351 | wl_surface_add_listener(seat->wl_surface, &wl_surface_listener, seat); 352 | anthywl_seat_draw_popup(seat); 353 | seat->zwp_input_popup_surface_v2 = 354 | zwp_input_method_v2_get_input_popup_surface( 355 | seat->zwp_input_method_v2, seat->wl_surface); 356 | zwp_input_popup_surface_v2_add_listener(seat->zwp_input_popup_surface_v2, 357 | &zwp_input_popup_surface_v2_listener, seat); 358 | seat->wl_surface_cursor = 359 | wl_compositor_create_surface(seat->state->wl_compositor); 360 | wl_surface_add_listener( 361 | seat->wl_surface_cursor, &wl_surface_listener, seat); 362 | seat->are_protocols_initted = true; 363 | } 364 | 365 | void anthywl_seat_destroy(struct anthywl_seat *seat) { 366 | anthy_release_context(seat->anthy_context); 367 | free(seat->selected_candidates); 368 | anthywl_buffer_destroy(&seat->buffer); 369 | free(seat->pending_surrounding_text); 370 | free(seat->surrounding_text); 371 | free(seat->name); 372 | xkb_state_unref(seat->xkb_state); 373 | xkb_keymap_unref(seat->xkb_keymap); 374 | xkb_context_unref(seat->xkb_context); 375 | free(seat->xkb_keymap_string); 376 | if (seat->are_protocols_initted) { 377 | zwp_input_popup_surface_v2_destroy(seat->zwp_input_popup_surface_v2); 378 | wl_surface_destroy(seat->wl_surface); 379 | zwp_virtual_keyboard_v1_destroy(seat->zwp_virtual_keyboard_v1_backup_input); 380 | zwp_virtual_keyboard_v1_destroy(seat->zwp_virtual_keyboard_v1_passthrough); 381 | zwp_input_method_keyboard_grab_v2_destroy( 382 | seat->zwp_input_method_keyboard_grab_v2); 383 | zwp_input_method_v2_destroy(seat->zwp_input_method_v2); 384 | } 385 | wl_seat_destroy(seat->wl_seat); 386 | wl_list_remove(&seat->link); 387 | free(seat); 388 | } 389 | 390 | void anthywl_seat_composing_update(struct anthywl_seat *seat) { 391 | zwp_input_method_v2_set_preedit_string( 392 | seat->zwp_input_method_v2, seat->buffer.text, 393 | seat->buffer.pos, seat->buffer.pos); 394 | zwp_input_method_v2_commit( 395 | seat->zwp_input_method_v2, seat->done_events_received); 396 | anthywl_seat_draw_popup(seat); 397 | } 398 | 399 | bool anthywl_seat_send_string(struct anthywl_seat *seat, const char *text) { 400 | if (!seat->active) { 401 | int keymap_fd; 402 | uint32_t *keys; 403 | size_t keymap_size, keys_size; 404 | if (!anthywl_make_keymap( 405 | text, &keymap_fd, &keymap_size, &keys, &keys_size)) 406 | { 407 | return false; 408 | } 409 | zwp_virtual_keyboard_v1_keymap( 410 | seat->zwp_virtual_keyboard_v1_backup_input, 411 | WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, keymap_fd, keymap_size); 412 | for (size_t i = 0; i < keys_size; i++) { 413 | zwp_virtual_keyboard_v1_key( 414 | seat->zwp_virtual_keyboard_v1_backup_input, 415 | 0, keys[i], WL_KEYBOARD_KEY_STATE_PRESSED); 416 | zwp_virtual_keyboard_v1_key( 417 | seat->zwp_virtual_keyboard_v1_backup_input, 418 | 0, keys[i], WL_KEYBOARD_KEY_STATE_RELEASED); 419 | } 420 | free(keys); 421 | return true; 422 | } 423 | zwp_input_method_v2_commit_string(seat->zwp_input_method_v2, text); 424 | zwp_input_method_v2_commit( 425 | seat->zwp_input_method_v2, seat->done_events_received); 426 | return true; 427 | } 428 | 429 | void anthywl_seat_composing_commit(struct anthywl_seat *seat) { 430 | anthywl_seat_send_string(seat, seat->buffer.text); 431 | anthywl_buffer_clear(&seat->buffer); 432 | anthywl_seat_draw_popup(seat); 433 | } 434 | 435 | void anthywl_seat_selecting_update(struct anthywl_seat *seat) { 436 | size_t cursor_begin = 0, cursor_end = 0; 437 | struct wl_array buffer; 438 | wl_array_init(&buffer); 439 | 440 | for (int i = 0; i < seat->segment_count; i++) { 441 | struct anthy_segment_stat segment_stat; 442 | anthy_get_segment_stat(seat->anthy_context, i, &segment_stat); 443 | char buf[64]; 444 | anthy_get_segment(seat->anthy_context, i, seat->selected_candidates[i], 445 | buf, sizeof buf); 446 | if (i == seat->current_segment) 447 | cursor_begin = buffer.size; 448 | size_t buf_len = strlen(buf); 449 | wl_array_add(&buffer, buf_len); 450 | memcpy(buffer.data + buffer.size - buf_len, buf, buf_len); 451 | if (i == seat->current_segment) 452 | cursor_end = buffer.size; 453 | } 454 | 455 | wl_array_add(&buffer, 1); 456 | ((char *)buffer.data)[buffer.size - 1] = 0; 457 | 458 | zwp_input_method_v2_set_preedit_string( 459 | seat->zwp_input_method_v2, buffer.data, cursor_begin, cursor_end); 460 | zwp_input_method_v2_commit( 461 | seat->zwp_input_method_v2, seat->done_events_received); 462 | 463 | anthywl_seat_draw_popup(seat); 464 | 465 | wl_array_release(&buffer); 466 | } 467 | 468 | void anthywl_seat_selecting_commit(struct anthywl_seat *seat) { 469 | struct wl_array buffer; 470 | wl_array_init(&buffer); 471 | 472 | char buf[64]; 473 | for (int i = 0; i < seat->segment_count; i++) { 474 | struct anthy_segment_stat segment_stat; 475 | anthy_get_segment_stat(seat->anthy_context, i, &segment_stat); 476 | anthy_get_segment(seat->anthy_context, 477 | i, seat->selected_candidates[i], buf, sizeof buf); 478 | size_t buf_len = strlen(buf); 479 | wl_array_add(&buffer, buf_len); 480 | memcpy(buffer.data + buffer.size - buf_len, buf, buf_len); 481 | } 482 | 483 | wl_array_add(&buffer, 1); 484 | ((char *)buffer.data)[buffer.size - 1] = 0; 485 | 486 | anthywl_seat_send_string(seat, buffer.data); 487 | seat->is_selecting = false; 488 | seat->is_selecting_popup_visible = false; 489 | anthywl_buffer_clear(&seat->buffer); 490 | 491 | anthywl_seat_draw_popup(seat); 492 | 493 | wl_array_release(&buffer); 494 | } 495 | 496 | int anthywl_binding_compare(void const *_a, void const *_b) { 497 | const struct anthywl_binding *a = _a; 498 | const struct anthywl_binding *b = _b; 499 | int keysym = a->keysym - b->keysym; 500 | if (keysym != 0) 501 | return keysym; 502 | int modifiers = a->modifiers - b->modifiers; 503 | if (modifiers != 0) 504 | return modifiers; 505 | return a->action - b->action; 506 | } 507 | 508 | int anthywl_seat_binding_compare(void const *_a, void const *_b) { 509 | const struct anthywl_seat_binding *a = _a; 510 | const struct anthywl_seat_binding *b = _b; 511 | int keycode = a->keycode - b->keycode; 512 | if (keycode != 0) 513 | return keycode; 514 | int mod_mask = a->mod_mask - b->mod_mask; 515 | if (mod_mask != 0) 516 | return mod_mask; 517 | return a->action - b->action; 518 | } 519 | 520 | int anthywl_seat_binding_compare_without_action( 521 | void const *_a, void const *_b) 522 | { 523 | const struct anthywl_seat_binding *a = _a; 524 | const struct anthywl_seat_binding *b = _b; 525 | int keycode = a->keycode - b->keycode; 526 | if (keycode != 0) 527 | return keycode; 528 | return a->mod_mask - b->mod_mask; 529 | } 530 | 531 | 532 | bool anthywl_seat_handle_key_bindings(struct anthywl_seat *seat, 533 | struct wl_array *bindings, struct anthywl_seat_binding *press) 534 | { 535 | struct anthywl_seat_binding *found = bsearch(press, bindings->data, 536 | bindings->size / sizeof(struct anthywl_seat_binding), 537 | sizeof(struct anthywl_seat_binding), 538 | anthywl_seat_binding_compare_without_action); 539 | if (found == NULL) 540 | return false; 541 | return anthywl_seat_handle_action(seat, found->action); 542 | } 543 | 544 | bool anthywl_seat_handle_key(struct anthywl_seat *seat, 545 | xkb_keycode_t keycode) 546 | { 547 | xkb_keysym_t keysym = xkb_state_key_get_one_sym(seat->xkb_state, keycode); 548 | struct anthywl_seat_binding press = { 549 | .keycode = keycode, 550 | .mod_mask = 0, 551 | .action = 0, 552 | }; 553 | xkb_mod_mask_t mod_count = xkb_keymap_num_mods(seat->xkb_keymap); 554 | for (xkb_mod_index_t i = 0; i < mod_count; i++) { 555 | bool is_active = xkb_state_mod_index_is_active(seat->xkb_state, 556 | i, XKB_STATE_MODS_EFFECTIVE); 557 | bool is_consumed = xkb_state_mod_index_is_consumed(seat->xkb_state, 558 | keycode, i); 559 | bool is_relevant = (i != seat->mod_indices[ANTHYWL_CAPS_INDEX]) 560 | && (i != seat->mod_indices[ANTHYWL_NUM_INDEX]); 561 | (void)is_consumed; 562 | if (is_active && is_relevant) 563 | press.mod_mask |= 1 << i; 564 | } 565 | handle: 566 | if (seat->is_selecting && anthywl_seat_handle_key_bindings( 567 | seat, &seat->selecting_bindings, &press)) 568 | { 569 | return true; 570 | } 571 | if (seat->is_composing && seat->buffer.len != 0 572 | && anthywl_seat_handle_key_bindings( 573 | seat, &seat->composing_bindings, &press)) 574 | { 575 | return true; 576 | } 577 | if (anthywl_seat_handle_key_bindings( 578 | seat, &seat->global_bindings, &press)) 579 | { 580 | return true; 581 | } 582 | if (keysym == XKB_KEY_Shift_L 583 | || keysym == XKB_KEY_Shift_R 584 | || keysym == XKB_KEY_Control_L 585 | || keysym == XKB_KEY_Control_R 586 | || keysym == XKB_KEY_Alt_L 587 | || keysym == XKB_KEY_Alt_R 588 | || keysym == XKB_KEY_Super_L 589 | || keysym == XKB_KEY_Super_R 590 | || keysym == XKB_KEY_Hyper_L 591 | || keysym == XKB_KEY_Hyper_R) 592 | { 593 | return false; 594 | } 595 | if (seat->is_selecting) { 596 | anthywl_seat_selecting_commit(seat); 597 | goto handle; 598 | } 599 | if (seat->is_composing && keysym != XKB_KEY_space) { 600 | uint32_t codepoint = xkb_state_key_get_utf32(seat->xkb_state, keycode); 601 | if (codepoint != 0 && codepoint < 32) 602 | return false; 603 | int utf8_len = xkb_state_key_get_utf8(seat->xkb_state, keycode, NULL, 0); 604 | if (utf8_len == 0) 605 | return false; 606 | char *utf8 = malloc(utf8_len + 1); 607 | xkb_state_key_get_utf8(seat->xkb_state, keycode, utf8, utf8_len + 1); 608 | anthywl_buffer_append(&seat->buffer, utf8); 609 | anthywl_buffer_convert_romaji(&seat->buffer); 610 | anthywl_seat_composing_update(seat); 611 | free(utf8); 612 | return true; 613 | } 614 | 615 | return false; 616 | } 617 | 618 | static void timespec_correct(struct timespec *ts) { 619 | while (ts->tv_nsec >= 1000000000) { 620 | ts->tv_sec += 1; 621 | ts->tv_nsec -= 1000000000; 622 | } 623 | } 624 | 625 | void anthywl_seat_repeat_timer_callback(struct anthywl_timer *timer) { 626 | struct anthywl_seat *seat = wl_container_of(timer, seat, repeat_timer); 627 | if (seat->repeat_rate <= 0) { 628 | wl_list_remove(&timer->link); 629 | seat->repeating_keycode = 0; 630 | return; 631 | } 632 | seat->repeating_timestamp += 1000 / seat->repeat_rate; 633 | if (!anthywl_seat_handle_key(seat, seat->repeating_keycode)) { 634 | wl_list_remove(&timer->link); 635 | zwp_virtual_keyboard_v1_key( 636 | seat->zwp_virtual_keyboard_v1_passthrough, seat->repeating_timestamp, 637 | seat->repeating_keycode - 8, WL_KEYBOARD_KEY_STATE_PRESSED); 638 | seat->repeating_keycode = 0; 639 | } else { 640 | clock_gettime(CLOCK_MONOTONIC, &seat->repeat_timer.time); 641 | timer->time.tv_nsec += 1000000000 / seat->repeat_rate; 642 | timespec_correct(&timer->time); 643 | } 644 | } 645 | 646 | struct keycode_matches { 647 | struct anthywl_seat *seat; 648 | xkb_keysym_t keysym; 649 | struct wl_array keycodes; 650 | }; 651 | 652 | static void find_keycode(struct xkb_keymap *keymap, xkb_keycode_t keycode, 653 | void *data) 654 | { 655 | struct keycode_matches *matches = data; 656 | xkb_keysym_t keysym = xkb_state_key_get_one_sym( 657 | matches->seat->xkb_state, keycode); 658 | if (keysym == XKB_KEY_NoSymbol) 659 | return; 660 | if (matches->keysym == keysym) { 661 | xkb_keycode_t *keycode_spot = 662 | wl_array_add(&matches->keycodes, sizeof(xkb_keycode_t)); 663 | *keycode_spot = keycode; 664 | } 665 | } 666 | 667 | void anthywl_seat_set_up_bindings(struct anthywl_seat *seat, 668 | struct wl_array *state_bindings, struct wl_array *seat_bindings) 669 | { 670 | struct anthywl_binding *state_binding; 671 | struct keycode_matches matches = { 672 | .seat = seat, 673 | }; 674 | wl_array_init(&matches.keycodes); 675 | wl_array_for_each(state_binding, state_bindings) { 676 | matches.keysym = state_binding->keysym; 677 | matches.keycodes.size = 0; 678 | xkb_keymap_key_for_each(seat->xkb_keymap, find_keycode, &matches); 679 | xkb_mod_mask_t mod_mask = 0; 680 | for (int i = 0; i < _ANTHYWL_MOD_LAST; i++) { 681 | if (state_binding->modifiers & (1 << i)) { 682 | mod_mask |= 1 << seat->mod_indices[i]; 683 | } 684 | } 685 | xkb_keycode_t *keycode; 686 | wl_array_for_each(keycode, &matches.keycodes) { 687 | struct anthywl_seat_binding *seat_binding = wl_array_add( 688 | seat_bindings, sizeof(struct anthywl_seat_binding)); 689 | seat_binding->keycode = *keycode; 690 | seat_binding->mod_mask = mod_mask; 691 | seat_binding->action = state_binding->action; 692 | } 693 | } 694 | qsort(seat_bindings->data, 695 | seat_bindings->size / sizeof(struct anthywl_seat_binding), 696 | sizeof(struct anthywl_seat_binding), anthywl_seat_binding_compare); 697 | wl_array_release(&matches.keycodes); 698 | } 699 | 700 | void zwp_input_method_keyboard_grab_v2_keymap(void *data, 701 | struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2, 702 | uint32_t format, int32_t fd, uint32_t size) 703 | { 704 | struct anthywl_seat *seat = data; 705 | char *map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); 706 | if (seat->xkb_keymap_string == NULL 707 | || strcmp(seat->xkb_keymap_string, map) != 0) 708 | { 709 | zwp_virtual_keyboard_v1_keymap( 710 | seat->zwp_virtual_keyboard_v1_passthrough, format, fd, size); 711 | xkb_keymap_unref(seat->xkb_keymap); 712 | xkb_state_unref(seat->xkb_state); 713 | seat->xkb_keymap = xkb_keymap_new_from_string(seat->xkb_context, map, 714 | XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); 715 | seat->mod_indices[ANTHYWL_SHIFT_INDEX] = xkb_keymap_mod_get_index( 716 | seat->xkb_keymap, XKB_MOD_NAME_SHIFT); 717 | seat->mod_indices[ANTHYWL_CAPS_INDEX] = xkb_keymap_mod_get_index( 718 | seat->xkb_keymap, XKB_MOD_NAME_CAPS); 719 | seat->mod_indices[ANTHYWL_CTRL_INDEX] = xkb_keymap_mod_get_index( 720 | seat->xkb_keymap, XKB_MOD_NAME_CTRL); 721 | seat->mod_indices[ANTHYWL_ALT_INDEX] = xkb_keymap_mod_get_index( 722 | seat->xkb_keymap, XKB_MOD_NAME_ALT); 723 | seat->mod_indices[ANTHYWL_NUM_INDEX] = xkb_keymap_mod_get_index( 724 | seat->xkb_keymap, XKB_MOD_NAME_NUM); 725 | seat->mod_indices[ANTHYWL_MOD3_INDEX] = xkb_keymap_mod_get_index( 726 | seat->xkb_keymap, "Mod3"); 727 | seat->mod_indices[ANTHYWL_LOGO_INDEX] = xkb_keymap_mod_get_index( 728 | seat->xkb_keymap, XKB_MOD_NAME_LOGO); 729 | seat->mod_indices[ANTHYWL_MOD5_INDEX] = xkb_keymap_mod_get_index( 730 | seat->xkb_keymap, "Mod5"); 731 | seat->xkb_state = xkb_state_new(seat->xkb_keymap); 732 | free(seat->xkb_keymap_string); 733 | seat->xkb_keymap_string = strdup(map); 734 | anthywl_seat_set_up_bindings(seat, 735 | &seat->state->config.global_bindings, &seat->global_bindings); 736 | anthywl_seat_set_up_bindings(seat, 737 | &seat->state->config.selecting_bindings, &seat->selecting_bindings); 738 | anthywl_seat_set_up_bindings(seat, 739 | &seat->state->config.composing_bindings, &seat->composing_bindings); 740 | } 741 | close(fd); 742 | munmap(map, size); 743 | } 744 | 745 | void zwp_input_method_keyboard_grab_v2_key(void *data, 746 | struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2, 747 | uint32_t serial, uint32_t time, uint32_t key, uint32_t state) 748 | { 749 | struct anthywl_seat *seat = data; 750 | xkb_keycode_t keycode = key + 8; 751 | bool handled = false; 752 | 753 | if (state == WL_KEYBOARD_KEY_STATE_PRESSED 754 | && seat->repeating_keycode != 0 755 | && seat->repeating_keycode != keycode) 756 | { 757 | if (!anthywl_seat_handle_key(seat, keycode)) { 758 | seat->repeating_keycode = 0; 759 | wl_list_remove(&seat->repeat_timer.link); 760 | goto forward; 761 | } 762 | if (xkb_keymap_key_repeats(seat->xkb_keymap, keycode)) { 763 | seat->repeating_keycode = keycode; 764 | seat->repeating_timestamp = time + seat->repeat_delay; 765 | clock_gettime(CLOCK_MONOTONIC, &seat->repeat_timer.time); 766 | seat->repeat_timer.time.tv_nsec += seat->repeat_delay * 1000000; 767 | timespec_correct(&seat->repeat_timer.time); 768 | } else { 769 | seat->repeating_keycode = 0; 770 | } 771 | return; 772 | } 773 | 774 | if (state == WL_KEYBOARD_KEY_STATE_RELEASED 775 | && seat->repeating_keycode == keycode) 776 | { 777 | seat->repeating_keycode = 0; 778 | wl_list_remove(&seat->repeat_timer.link); 779 | return; 780 | } 781 | 782 | if (state == WL_KEYBOARD_KEY_STATE_PRESSED) 783 | handled |= anthywl_seat_handle_key(seat, keycode); 784 | 785 | if (state == WL_KEYBOARD_KEY_STATE_PRESSED 786 | && xkb_keymap_key_repeats(seat->xkb_keymap, keycode) 787 | && handled) 788 | { 789 | if (seat->repeat_rate <= 0) 790 | return; 791 | seat->repeating_keycode = keycode; 792 | seat->repeating_timestamp = time + seat->repeat_delay; 793 | clock_gettime(CLOCK_MONOTONIC, &seat->repeat_timer.time); 794 | seat->repeat_timer.time.tv_nsec += seat->repeat_delay * 1000000; 795 | timespec_correct(&seat->repeat_timer.time); 796 | wl_list_insert(&seat->state->timers, &seat->repeat_timer.link); 797 | return; 798 | } 799 | 800 | if (state == WL_KEYBOARD_KEY_STATE_PRESSED && handled) { 801 | for (size_t i = 0; 802 | i < sizeof seat->pressed / sizeof seat->pressed[0]; i++) 803 | { 804 | if (seat->pressed[i] == 0) { 805 | seat->pressed[i] = keycode; 806 | goto forward; 807 | } 808 | } 809 | } 810 | 811 | if (state == WL_KEYBOARD_KEY_STATE_RELEASED) { 812 | for (size_t i = 0; 813 | i < sizeof seat->pressed / sizeof seat->pressed[0]; i++) 814 | { 815 | if (seat->pressed[i] == keycode) { 816 | seat->pressed[i] = 0; 817 | return; 818 | } 819 | } 820 | } 821 | 822 | if (handled) 823 | return; 824 | 825 | forward: 826 | zwp_virtual_keyboard_v1_key( 827 | seat->zwp_virtual_keyboard_v1_passthrough, time, key, state); 828 | } 829 | 830 | void zwp_input_method_keyboard_grab_v2_modifiers(void *data, 831 | struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2, 832 | uint32_t serial, 833 | uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, 834 | uint32_t group) 835 | { 836 | struct anthywl_seat *seat = data; 837 | xkb_state_update_mask(seat->xkb_state, 838 | mods_depressed, mods_latched, mods_locked, 0, 0, group); 839 | zwp_virtual_keyboard_v1_modifiers(seat->zwp_virtual_keyboard_v1_passthrough, 840 | mods_depressed, mods_latched, mods_locked, group); 841 | } 842 | 843 | void zwp_input_method_keyboard_grab_v2_repeat_info(void *data, 844 | struct zwp_input_method_keyboard_grab_v2 *zwp_input_method_keyboard_grab_v2, 845 | int32_t rate, int32_t delay) 846 | { 847 | struct anthywl_seat *seat = data; 848 | seat->repeat_rate = rate; 849 | seat->repeat_delay = delay; 850 | } 851 | 852 | void zwp_input_method_v2_activate( 853 | void *data, struct zwp_input_method_v2 *zwp_input_method_v2) 854 | { 855 | struct anthywl_seat *seat = data; 856 | seat->pending_activate = true; 857 | free(seat->pending_surrounding_text); 858 | seat->pending_surrounding_text = NULL; 859 | seat->pending_text_change_cause = 0; 860 | seat->pending_content_type_hint = 0; 861 | seat->pending_content_type_purpose = 0; 862 | } 863 | 864 | void zwp_input_method_v2_deactivate( 865 | void *data, struct zwp_input_method_v2 *zwp_input_method_v2) 866 | { 867 | struct anthywl_seat *seat = data; 868 | seat->pending_activate = false; 869 | } 870 | 871 | void zwp_input_method_v2_surrounding_text( 872 | void *data, struct zwp_input_method_v2 *zwp_input_method_v2, 873 | char const *text, uint32_t cursor, uint32_t anchor) 874 | { 875 | struct anthywl_seat *seat = data; 876 | free(seat->pending_surrounding_text); 877 | seat->pending_surrounding_text = strdup(text); 878 | seat->pending_surrounding_text_cursor = cursor; 879 | seat->pending_surrounding_text_anchor = anchor; 880 | } 881 | 882 | void zwp_input_method_v2_text_change_cause( 883 | void *data, struct zwp_input_method_v2 *zwp_input_method_v2, 884 | uint32_t cause) 885 | { 886 | struct anthywl_seat *seat = data; 887 | seat->pending_text_change_cause = cause; 888 | } 889 | 890 | void zwp_input_method_v2_content_type( 891 | void *data, struct zwp_input_method_v2 *zwp_input_method_v2, 892 | uint32_t hint, uint32_t purpose) 893 | { 894 | struct anthywl_seat *seat = data; 895 | seat->pending_content_type_hint = hint; 896 | seat->pending_content_type_purpose = purpose; 897 | } 898 | 899 | void zwp_input_method_v2_done( 900 | void *data, struct zwp_input_method_v2 *zwp_input_method_v2) 901 | { 902 | struct anthywl_seat *seat = data; 903 | bool was_active = seat->active; 904 | seat->active = seat->pending_activate; 905 | free(seat->surrounding_text); 906 | seat->surrounding_text = seat->pending_surrounding_text; 907 | seat->pending_surrounding_text = NULL; 908 | seat->surrounding_text_cursor = seat->pending_surrounding_text_cursor; 909 | seat->surrounding_text_anchor = seat->pending_surrounding_text_anchor; 910 | seat->text_change_cause = seat->pending_text_change_cause; 911 | seat->content_type_hint = seat->pending_content_type_hint; 912 | seat->content_type_purpose = seat->pending_content_type_purpose; 913 | seat->done_events_received++; 914 | if (!was_active && seat->active) { 915 | seat->is_selecting = false; 916 | seat->is_composing_popup_visible = false; 917 | anthywl_buffer_clear(&seat->buffer); 918 | anthywl_seat_draw_popup(seat); 919 | } 920 | } 921 | 922 | void zwp_input_method_v2_unavailable( 923 | void *data, struct zwp_input_method_v2 *zwp_input_method_v2) 924 | { 925 | struct anthywl_seat *seat = data; 926 | fprintf(stderr, "Input method unavailable on seat \"%s\".\n", seat->name); 927 | anthywl_seat_destroy(seat); 928 | } 929 | 930 | void anthywl_seat_cursor_update(struct anthywl_seat *seat) { 931 | uint32_t duration; 932 | struct timespec now; 933 | clock_gettime(CLOCK_MONOTONIC, &now); 934 | int scale = seat->state->wl_cursor_theme_scale; 935 | struct wl_cursor_theme *wl_cursor_theme = seat->state->wl_cursor_theme; 936 | struct wl_cursor *wl_cursor = 937 | wl_cursor_theme_get_cursor(wl_cursor_theme, "left_ptr"); 938 | int time_ms = (int64_t)now.tv_sec * 1000 + now.tv_nsec / 1000000; 939 | int frame = 940 | wl_cursor_frame_and_duration(wl_cursor, time_ms, &duration); 941 | struct wl_cursor_image *wl_cursor_image = wl_cursor->images[frame]; 942 | struct wl_buffer *wl_buffer = wl_cursor_image_get_buffer(wl_cursor_image); 943 | wl_surface_attach(seat->wl_surface_cursor, wl_buffer, 0, 0); 944 | wl_surface_damage(seat->wl_surface_cursor, 0, 0, INT32_MAX, INT32_MAX); 945 | wl_surface_set_buffer_scale(seat->wl_surface_cursor, scale); 946 | wl_surface_commit(seat->wl_surface_cursor); 947 | wl_pointer_set_cursor( 948 | seat->wl_pointer, seat->wl_pointer_serial, seat->wl_surface_cursor, 949 | wl_cursor_image->hotspot_x / scale, wl_cursor_image->hotspot_y / scale); 950 | if (duration == 0) { 951 | wl_list_remove(&seat->cursor_timer.link); 952 | wl_list_init(&seat->cursor_timer.link); 953 | } else { 954 | clock_gettime(CLOCK_MONOTONIC, &seat->cursor_timer.time); 955 | seat->cursor_timer.time.tv_nsec += 1000000 * duration; 956 | timespec_correct(&seat->cursor_timer.time); 957 | } 958 | } 959 | 960 | void anthywl_seat_cursor_timer_callback(struct anthywl_timer *timer) { 961 | struct anthywl_seat *seat = wl_container_of(timer, seat, cursor_timer); 962 | anthywl_seat_cursor_update(seat); 963 | } 964 | 965 | void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, 966 | uint32_t serial, struct wl_surface *surface, 967 | wl_fixed_t surface_x, wl_fixed_t surface_y) 968 | { 969 | struct anthywl_seat *seat = data; 970 | seat->wl_pointer_serial = serial; 971 | wl_list_insert(&seat->state->timers, &seat->cursor_timer.link); 972 | anthywl_seat_cursor_update(seat); 973 | } 974 | 975 | void wl_pointer_leave(void *data, struct wl_pointer *wl_pointer, 976 | uint32_t serial, struct wl_surface *surface) 977 | { 978 | struct anthywl_seat *seat = data; 979 | wl_list_remove(&seat->cursor_timer.link); 980 | } 981 | 982 | void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, 983 | uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) 984 | { 985 | } 986 | 987 | void wl_pointer_button(void *data, struct wl_pointer *wl_pointer, 988 | uint32_t serial, uint32_t time, uint32_t button, uint32_t state) 989 | { 990 | } 991 | 992 | void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer, 993 | uint32_t time, uint32_t axis, wl_fixed_t value) 994 | { 995 | } 996 | 997 | void wl_pointer_frame(void *data, struct wl_pointer *wl_pointer) { 998 | } 999 | 1000 | void wl_pointer_axis_source(void *data, struct wl_pointer *wl_pointer, 1001 | uint32_t axis_source) 1002 | { 1003 | } 1004 | 1005 | void wl_pointer_axis_stop(void *data, struct wl_pointer *wl_pointer, 1006 | uint32_t time, uint32_t axis) 1007 | { 1008 | } 1009 | 1010 | void wl_pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer, 1011 | uint32_t axis, int32_t discrete) 1012 | { 1013 | } 1014 | 1015 | void wl_seat_capabilities(void *data, struct wl_seat *wl_seat, 1016 | uint32_t capabilities) 1017 | { 1018 | struct anthywl_seat *seat = data; 1019 | if ((capabilities & WL_SEAT_CAPABILITY_POINTER) && seat->wl_pointer == NULL) 1020 | { 1021 | seat->wl_pointer = wl_seat_get_pointer(wl_seat); 1022 | wl_pointer_add_listener(seat->wl_pointer, &wl_pointer_listener, seat); 1023 | } 1024 | if (!(capabilities & WL_SEAT_CAPABILITY_POINTER) 1025 | && seat->wl_pointer != NULL) 1026 | { 1027 | wl_pointer_release(seat->wl_pointer); 1028 | seat->wl_pointer = NULL; 1029 | } 1030 | } 1031 | 1032 | void wl_seat_name(void *data, struct wl_seat *wl_seat, 1033 | char const *name) 1034 | { 1035 | struct anthywl_seat *seat = data; 1036 | free(seat->name); 1037 | seat->name = strdup(name); 1038 | } 1039 | 1040 | void wl_seat_global(struct anthywl_state *state, void *data) { 1041 | struct wl_seat *wl_seat = data; 1042 | struct anthywl_seat *seat = calloc(1, sizeof *seat); 1043 | anthywl_seat_init(seat, state, wl_seat); 1044 | wl_list_insert(&state->seats, &seat->link); 1045 | } 1046 | 1047 | void wl_output_geometry(void *data, struct wl_output *wl_output, 1048 | int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, 1049 | int32_t subpixel, const char *make, const char *model, int32_t transform) 1050 | { 1051 | } 1052 | 1053 | void wl_output_mode(void *data, struct wl_output *wl_output, 1054 | uint32_t flags, int32_t width, int32_t height, int32_t refresh) 1055 | { 1056 | } 1057 | 1058 | void wl_output_done(void *data, struct wl_output *wl_output) { 1059 | struct anthywl_output *output = data; 1060 | output->scale = output->pending_scale; 1061 | 1062 | int scale = 0; 1063 | struct anthywl_output *output_iter; 1064 | wl_list_for_each(output_iter, &output->state->outputs, link) { 1065 | if (output_iter->scale > scale) 1066 | scale = output_iter->scale; 1067 | } 1068 | output->state->max_scale = scale; 1069 | if (output->state->wl_cursor_theme_scale != output->state->max_scale / 24) 1070 | anthywl_reload_cursor_theme(output->state); 1071 | } 1072 | 1073 | void wl_output_scale(void *data, struct wl_output *wl_output, 1074 | int32_t factor) 1075 | { 1076 | struct anthywl_output *output = data; 1077 | output->pending_scale = factor; 1078 | } 1079 | 1080 | void wl_output_global(struct anthywl_state *state, void *data) { 1081 | struct wl_output *wl_output = data; 1082 | struct anthywl_output *output = calloc(1, sizeof *output); 1083 | output->state = state; 1084 | output->wl_output = wl_output; 1085 | wl_output_add_listener(output->wl_output, &wl_output_listener, output); 1086 | wl_list_insert(&state->outputs, &output->link); 1087 | } 1088 | 1089 | struct anthywl_global { 1090 | char const *name; 1091 | struct wl_interface const *interface; 1092 | int version; 1093 | bool is_singleton; 1094 | union { 1095 | ptrdiff_t offset; 1096 | void (*callback)(struct anthywl_state *state, void *data); 1097 | }; 1098 | }; 1099 | 1100 | static int anthywl_global_compare(void const *a, void const *b) { 1101 | return strcmp( 1102 | ((struct anthywl_global const *)a)->name, 1103 | ((struct anthywl_global const *)b)->name); 1104 | } 1105 | 1106 | static struct anthywl_global const globals[] = { 1107 | { 1108 | .name = "wl_compositor", 1109 | .interface = &wl_compositor_interface, 1110 | .version = 4, 1111 | .is_singleton = true, 1112 | .offset = offsetof(struct anthywl_state, wl_compositor), 1113 | }, 1114 | { 1115 | .name = "wl_output", 1116 | .interface = &wl_output_interface, 1117 | .version = 3, 1118 | .is_singleton = false, 1119 | .callback = wl_output_global, 1120 | }, 1121 | { 1122 | .name = "wl_seat", 1123 | .interface = &wl_seat_interface, 1124 | .version = 7, 1125 | .is_singleton = false, 1126 | .callback = wl_seat_global, 1127 | }, 1128 | { 1129 | .name = "wl_shm", 1130 | .interface = &wl_shm_interface, 1131 | .version = 1, 1132 | .is_singleton = true, 1133 | .offset = offsetof(struct anthywl_state, wl_shm), 1134 | }, 1135 | { 1136 | .name = "zwp_input_method_manager_v2", 1137 | .interface = &zwp_input_method_manager_v2_interface, 1138 | .version = 1, 1139 | .is_singleton = true, 1140 | .offset = offsetof(struct anthywl_state, zwp_input_method_manager_v2), 1141 | }, 1142 | { 1143 | .name = "zwp_virtual_keyboard_manager_v1", 1144 | .interface = &zwp_virtual_keyboard_manager_v1_interface, 1145 | .version = 1, 1146 | .is_singleton = true, 1147 | .offset = offsetof(struct anthywl_state, zwp_virtual_keyboard_manager_v1), 1148 | }, 1149 | }; 1150 | 1151 | void wl_registry_global(void *data, struct wl_registry *wl_registry, 1152 | uint32_t name, char const *interface, uint32_t version) 1153 | { 1154 | struct anthywl_global global = { .name = interface }; 1155 | struct anthywl_global *found = bsearch(&global, globals, 1156 | sizeof globals / sizeof(struct anthywl_global), 1157 | sizeof(struct anthywl_global), anthywl_global_compare); 1158 | 1159 | if (found == NULL) 1160 | return; 1161 | 1162 | if (found->is_singleton) { 1163 | *(void **)((uintptr_t)data + found->offset) = wl_registry_bind( 1164 | wl_registry, name, found->interface, found->version); 1165 | } else { 1166 | found->callback(data, wl_registry_bind( 1167 | wl_registry, name, found->interface, found->version)); 1168 | } 1169 | } 1170 | 1171 | void wl_registry_global_remove(void *data, 1172 | struct wl_registry *wl_registry, uint32_t name) 1173 | { 1174 | // TODO 1175 | } 1176 | 1177 | void anthywl_reload_cursor_theme(struct anthywl_state *state) { 1178 | const char *cursor_theme = getenv("XCURSOR_THEME"); 1179 | const char *env_cursor_size = getenv("XCURSOR_SIZE"); 1180 | int cursor_size = 24; 1181 | if (env_cursor_size && strlen(env_cursor_size) > 0) { 1182 | errno = 0; 1183 | char *end; 1184 | unsigned size = strtoul(env_cursor_size, &end, 10); 1185 | if (!*end && errno == 0) 1186 | cursor_size = size; 1187 | } 1188 | if (state->wl_cursor_theme != NULL) 1189 | wl_cursor_theme_destroy(state->wl_cursor_theme); 1190 | state->wl_cursor_theme = wl_cursor_theme_load( 1191 | cursor_theme, cursor_size * state->max_scale, state->wl_shm); 1192 | state->wl_cursor_theme_size = cursor_size; 1193 | state->wl_cursor_theme_scale = state->max_scale; 1194 | } 1195 | 1196 | bool anthywl_state_init(struct anthywl_state *state) { 1197 | wl_list_init(&state->buffers); 1198 | wl_list_init(&state->seats); 1199 | wl_list_init(&state->outputs); 1200 | wl_list_init(&state->timers); 1201 | anthywl_config_init(&state->config); 1202 | state->max_scale = 1; 1203 | 1204 | if (!anthywl_config_load(&state->config)) 1205 | return false; 1206 | 1207 | #ifdef ANTHYWL_IPC_SUPPORT 1208 | if (!anthywl_ipc_init(&state->ipc)) 1209 | return false; 1210 | #endif 1211 | 1212 | state->wl_display = wl_display_connect(NULL); 1213 | if (state->wl_display == NULL) { 1214 | perror("wl_display_connect"); 1215 | return false; 1216 | } 1217 | 1218 | state->wl_registry = wl_display_get_registry(state->wl_display); 1219 | wl_registry_add_listener(state->wl_registry, &wl_registry_listener, state); 1220 | wl_display_roundtrip(state->wl_display); 1221 | 1222 | for (size_t i = 0; i < sizeof globals / sizeof globals[0]; i++) { 1223 | const struct anthywl_global *global = &globals[i]; 1224 | if (!global->is_singleton) 1225 | continue; 1226 | struct wl_proxy **location = 1227 | (struct wl_proxy **)((uintptr_t)state + global->offset); 1228 | if (*location == NULL) { 1229 | fprintf( 1230 | stderr, "required interface unsupported by compositor: %s\n", 1231 | global->name); 1232 | return false; 1233 | } 1234 | } 1235 | 1236 | anthywl_reload_cursor_theme(state); 1237 | 1238 | struct anthywl_seat *seat; 1239 | wl_list_for_each(seat, &state->seats, link) 1240 | anthywl_seat_init_protocols(seat); 1241 | 1242 | wl_display_flush(state->wl_display); 1243 | 1244 | return true; 1245 | } 1246 | 1247 | static bool interrupted; 1248 | static void sigint(int signal) { interrupted = true; } 1249 | 1250 | int anthywl_state_next_timer(struct anthywl_state *state) { 1251 | int timeout = INT_MAX; 1252 | if (wl_list_empty(&state->timers)) 1253 | return -1; 1254 | struct timespec now; 1255 | clock_gettime(CLOCK_MONOTONIC, &now); 1256 | struct anthywl_timer *timer; 1257 | wl_list_for_each(timer, &state->timers, link) { 1258 | int time = 1259 | (timer->time.tv_sec - now.tv_sec) * 1000 + 1260 | (timer->time.tv_nsec - now.tv_nsec) / 1000000; 1261 | if (time < timeout) 1262 | timeout = time; 1263 | } 1264 | return timeout; 1265 | } 1266 | 1267 | void anthywl_state_run_timers(struct anthywl_state *state) { 1268 | if (wl_list_empty(&state->timers)) 1269 | return; 1270 | struct timespec now; 1271 | clock_gettime(CLOCK_MONOTONIC, &now); 1272 | struct anthywl_timer *timer, *tmp; 1273 | wl_list_for_each_safe(timer, tmp, &state->timers, link) { 1274 | bool expired = timer->time.tv_sec < now.tv_sec || 1275 | (timer->time.tv_sec == now.tv_sec && 1276 | timer->time.tv_nsec < now.tv_nsec); 1277 | if (expired) 1278 | timer->callback(timer); 1279 | } 1280 | } 1281 | 1282 | void anthywl_state_run(struct anthywl_state *state) { 1283 | state->running = true; 1284 | signal(SIGINT, sigint); 1285 | while (state->running && !interrupted) { 1286 | struct pollfd pfds[] = { 1287 | { 1288 | .fd = wl_display_get_fd(state->wl_display), 1289 | .events = POLLIN, 1290 | }, 1291 | #ifdef ANTHYWL_IPC_SUPPORT 1292 | { 1293 | .fd = varlink_service_get_fd(state->ipc.service), 1294 | .events = POLLIN, 1295 | }, 1296 | #endif 1297 | }; 1298 | 1299 | if (poll(pfds, ARRAY_LEN(pfds), anthywl_state_next_timer(state)) == -1) { 1300 | if (errno == EINTR) 1301 | continue; 1302 | perror("poll"); 1303 | break; 1304 | } 1305 | 1306 | if (pfds[0].events & POLLIN) { 1307 | while (wl_display_prepare_read(state->wl_display) != 0) 1308 | wl_display_dispatch_pending(state->wl_display); 1309 | 1310 | wl_display_read_events(state->wl_display); 1311 | 1312 | if (wl_display_dispatch_pending(state->wl_display) == -1) { 1313 | perror("wl_display_dispatch"); 1314 | break; 1315 | } 1316 | } 1317 | 1318 | #ifdef ANTHYWL_IPC_SUPPORT 1319 | if (pfds[1].events & POLLIN) { 1320 | long res = varlink_service_process_events(state->ipc.service); 1321 | if (res < 0) { 1322 | fprintf(stderr, "varlink_service_process_events: %s\n", 1323 | varlink_error_string(-res)); 1324 | break; 1325 | } 1326 | } 1327 | #endif 1328 | 1329 | anthywl_state_run_timers(state); 1330 | 1331 | wl_display_flush(state->wl_display); 1332 | 1333 | if (wl_list_empty(&state->seats)) { 1334 | fprintf(stderr, "No seats with input-method available.\n"); 1335 | break; 1336 | } 1337 | } 1338 | signal(SIGINT, SIG_DFL); 1339 | state->running = false; 1340 | } 1341 | 1342 | void anthywl_state_finish(struct anthywl_state *state) { 1343 | struct anthywl_seat *seat, *tmp_seat; 1344 | wl_list_for_each_safe(seat, tmp_seat, &state->seats, link) 1345 | anthywl_seat_destroy(seat); 1346 | struct anthywl_graphics_buffer *graphics_buffer, *tmp_graphics_buffer; 1347 | wl_list_for_each_safe( 1348 | graphics_buffer, tmp_graphics_buffer, &state->buffers, link) 1349 | { 1350 | anthywl_graphics_buffer_destroy(graphics_buffer); 1351 | } 1352 | if (state->wl_cursor_theme != NULL) 1353 | wl_cursor_theme_destroy(state->wl_cursor_theme); 1354 | if (state->zwp_virtual_keyboard_manager_v1 != NULL) { 1355 | zwp_virtual_keyboard_manager_v1_destroy( 1356 | state->zwp_virtual_keyboard_manager_v1); 1357 | } 1358 | if (state->zwp_input_method_manager_v2 != NULL) 1359 | zwp_input_method_manager_v2_destroy(state->zwp_input_method_manager_v2); 1360 | if (state->wl_shm != NULL) 1361 | wl_shm_destroy(state->wl_shm); 1362 | if (state->wl_compositor != NULL) 1363 | wl_compositor_destroy(state->wl_compositor); 1364 | if (state->wl_registry != NULL) 1365 | wl_registry_destroy(state->wl_registry); 1366 | if (state->wl_display != NULL) 1367 | wl_display_disconnect(state->wl_display); 1368 | #ifdef ANTHYWL_IPC_SUPPORT 1369 | anthywl_ipc_finish(&state->ipc); 1370 | #endif 1371 | anthywl_config_finish(&state->config); 1372 | } 1373 | 1374 | int main(void) { 1375 | if (anthy_init() != 0) { 1376 | perror("anthy_init"); 1377 | return 1; 1378 | } 1379 | atexit(anthy_quit); 1380 | struct anthywl_state state = {0}; 1381 | if (!anthywl_state_init(&state)) 1382 | return 1; 1383 | anthywl_state_run(&state); 1384 | anthywl_state_finish(&state); 1385 | } 1386 | 1387 | struct zwp_input_popup_surface_v2_listener const 1388 | zwp_input_popup_surface_v2_listener = 1389 | { 1390 | .text_input_rectangle = zwp_input_popup_surface_v2_text_input_rectangle, 1391 | }; 1392 | 1393 | struct wl_seat_listener const wl_seat_listener = { 1394 | .capabilities = wl_seat_capabilities, 1395 | .name = wl_seat_name, 1396 | }; 1397 | 1398 | struct wl_surface_listener const wl_surface_listener = { 1399 | .enter = wl_surface_enter, 1400 | .leave = wl_surface_leave, 1401 | }; 1402 | 1403 | struct zwp_input_method_keyboard_grab_v2_listener const 1404 | zwp_input_method_keyboard_grab_v2_listener = 1405 | { 1406 | .keymap = zwp_input_method_keyboard_grab_v2_keymap, 1407 | .key = zwp_input_method_keyboard_grab_v2_key, 1408 | .modifiers = zwp_input_method_keyboard_grab_v2_modifiers, 1409 | .repeat_info = zwp_input_method_keyboard_grab_v2_repeat_info, 1410 | }; 1411 | 1412 | struct zwp_input_method_v2_listener const zwp_input_method_v2_listener = 1413 | { 1414 | .activate = zwp_input_method_v2_activate, 1415 | .deactivate = zwp_input_method_v2_deactivate, 1416 | .surrounding_text = zwp_input_method_v2_surrounding_text, 1417 | .text_change_cause = zwp_input_method_v2_text_change_cause, 1418 | .content_type = zwp_input_method_v2_content_type, 1419 | .done = zwp_input_method_v2_done, 1420 | .unavailable = zwp_input_method_v2_unavailable, 1421 | }; 1422 | 1423 | struct wl_pointer_listener const wl_pointer_listener = { 1424 | .enter = wl_pointer_enter, 1425 | .leave = wl_pointer_leave, 1426 | .motion = wl_pointer_motion, 1427 | .button = wl_pointer_button, 1428 | .axis = wl_pointer_axis, 1429 | .frame = wl_pointer_frame, 1430 | .axis_source = wl_pointer_axis_source, 1431 | .axis_stop = wl_pointer_axis_stop, 1432 | .axis_discrete = wl_pointer_axis_discrete, 1433 | }; 1434 | 1435 | struct wl_output_listener const wl_output_listener = { 1436 | .geometry = wl_output_geometry, 1437 | .mode = wl_output_mode, 1438 | .done = wl_output_done, 1439 | .scale = wl_output_scale, 1440 | }; 1441 | 1442 | struct wl_registry_listener const wl_registry_listener = { 1443 | .global = wl_registry_global, 1444 | .global_remove = wl_registry_global_remove, 1445 | }; 1446 | -------------------------------------------------------------------------------- /src/buffer.c: -------------------------------------------------------------------------------- 1 | #include "buffer.h" 2 | 3 | #include 4 | #include 5 | 6 | static char const *const ones[128] = { 7 | ['-'] = "ー", 8 | ['a'] = "あ", 9 | ['e'] = "え", 10 | ['i'] = "い", 11 | ['o'] = "お", 12 | ['u'] = "う", 13 | [','] = "、", 14 | ['.'] = "。", 15 | ['/'] = "・", 16 | ['<'] = "<", 17 | ['>'] = ">", 18 | ['?'] = "?", 19 | ['['] = "「", 20 | [']'] = "」", 21 | ['{'] = "{", 22 | ['}'] = "}", 23 | ['~'] = "〜", 24 | ['!'] = "!", 25 | ['@'] = "@", 26 | ['#'] = "#", 27 | ['$'] = "$", 28 | ['%'] = "%", 29 | ['^'] = "^", 30 | ['&'] = "&", 31 | ['*'] = "*", 32 | ['('] = "(", 33 | [')'] = ")", 34 | ['+'] = "+", 35 | ['`'] = "`", 36 | ['1'] = "1", 37 | ['2'] = "2", 38 | ['3'] = "3", 39 | ['4'] = "4", 40 | ['5'] = "5", 41 | ['6'] = "6", 42 | ['7'] = "7", 43 | ['8'] = "8", 44 | ['9'] = "9", 45 | ['0'] = "0", 46 | ['='] = "=", 47 | ['|'] = "|", 48 | ['\\'] = "¥", 49 | }; 50 | 51 | static char const *const youon[128] = { 52 | ['a'] = "ゃ", 53 | ['i'] = "ぃ", 54 | ['u'] = "ゅ", 55 | ['e'] = "ぇ", 56 | ['o'] = "ょ", 57 | }; 58 | 59 | static char const *const b_pairs[128] = { 60 | ['a'] = "ば", 61 | ['e'] = "べ", 62 | ['i'] = "び", 63 | ['o'] = "ぼ", 64 | ['u'] = "ぶ", 65 | }; 66 | 67 | static char const *const d_pairs[128] = { 68 | ['a'] = "だ", 69 | ['e'] = "で", 70 | ['i'] = "ぢ", 71 | ['o'] = "ど", 72 | ['u'] = "づ", 73 | }; 74 | 75 | static char const *const f_pairs[128] = { 76 | ['a'] = "ふぁ", 77 | ['e'] = "ふぇ", 78 | ['i'] = "ふぃ", 79 | ['o'] = "ふぉ", 80 | ['u'] = "ふ", 81 | }; 82 | 83 | static char const *const g_pairs[128] = { 84 | ['a'] = "が", 85 | ['e'] = "げ", 86 | ['i'] = "ぎ", 87 | ['o'] = "ご", 88 | ['u'] = "ぐ", 89 | }; 90 | 91 | static char const *const h_pairs[128] = { 92 | ['a'] = "は", 93 | ['e'] = "へ", 94 | ['i'] = "ひ", 95 | ['o'] = "ほ", 96 | ['u'] = "ふ", 97 | }; 98 | 99 | static char const *const j_pairs[128] = { 100 | ['a'] = "じゃ", 101 | ['e'] = "じぇ", 102 | ['i'] = "じ", 103 | ['o'] = "じょ", 104 | ['u'] = "じゅ", 105 | }; 106 | 107 | static char const *const k_pairs[128] = { 108 | ['a'] = "か", 109 | ['e'] = "け", 110 | ['i'] = "き", 111 | ['o'] = "こ", 112 | ['u'] = "く", 113 | }; 114 | 115 | static char const *const l_pairs[128] = { 116 | ['a'] = "ぁ", 117 | ['e'] = "ぇ", 118 | ['i'] = "ぃ", 119 | ['o'] = "ぉ", 120 | ['u'] = "ぅ", 121 | }; 122 | 123 | static char const *const m_pairs[128] = { 124 | ['a'] = "ま", 125 | ['e'] = "め", 126 | ['i'] = "み", 127 | ['o'] = "も", 128 | ['u'] = "む", 129 | }; 130 | 131 | static char const *const n_pairs[128] = { 132 | ['a'] = "な", 133 | ['e'] = "ね", 134 | ['i'] = "に", 135 | ['o'] = "の", 136 | ['u'] = "ぬ", 137 | ['n'] = "ん", 138 | 139 | ['b'] = "んb", 140 | ['d'] = "んd", 141 | ['f'] = "んf", 142 | ['g'] = "んg", 143 | ['h'] = "んh", 144 | ['j'] = "んj", 145 | ['k'] = "んk", 146 | ['l'] = "んl", 147 | ['m'] = "んm", 148 | ['p'] = "んp", 149 | ['r'] = "んr", 150 | ['s'] = "んs", 151 | ['t'] = "んt", 152 | ['v'] = "んv", 153 | ['w'] = "んw", 154 | ['x'] = "んx", 155 | ['z'] = "んz", 156 | }; 157 | 158 | static char const *const p_pairs[128] = { 159 | ['a'] = "ぱ", 160 | ['e'] = "ぺ", 161 | ['i'] = "ぴ", 162 | ['o'] = "ぽ", 163 | ['u'] = "ぷ", 164 | }; 165 | 166 | static char const *const r_pairs[128] = { 167 | ['a'] = "ら", 168 | ['e'] = "れ", 169 | ['i'] = "り", 170 | ['o'] = "ろ", 171 | ['u'] = "る", 172 | }; 173 | 174 | static char const *const s_pairs[128] = { 175 | ['a'] = "さ", 176 | ['e'] = "せ", 177 | ['i'] = "し", 178 | ['o'] = "そ", 179 | ['u'] = "す", 180 | }; 181 | 182 | static char const *const t_pairs[128] = { 183 | ['a'] = "た", 184 | ['e'] = "て", 185 | ['i'] = "ち", 186 | ['o'] = "と", 187 | ['u'] = "つ", 188 | }; 189 | 190 | static char const *const v_pairs[128] = { 191 | ['a'] = "ゔぁ", 192 | ['e'] = "ゔぇ", 193 | ['i'] = "ゔぃ", 194 | ['o'] = "ゔぉ", 195 | ['u'] = "ゔ", 196 | }; 197 | 198 | static char const *const w_pairs[128] = { 199 | ['a'] = "わ", 200 | ['e'] = "うぇ", 201 | ['i'] = "うぃ", 202 | ['o'] = "を", 203 | ['u'] = "う", 204 | }; 205 | 206 | static char const *const x_pairs[128] = { 207 | ['a'] = "ぁ", 208 | ['e'] = "ぇ", 209 | ['i'] = "ぃ", 210 | ['o'] = "ぉ", 211 | ['u'] = "ぅ", 212 | }; 213 | 214 | static char const *const y_pairs[128] = { 215 | ['a'] = "や", 216 | ['e'] = "いぇ", 217 | ['i'] = "い", 218 | ['o'] = "よ", 219 | ['u'] = "ゆ", 220 | }; 221 | 222 | static char const *const z_pairs[128] = { 223 | ['a'] = "ざ", 224 | ['e'] = "ぜ", 225 | ['i'] = "じ", 226 | ['o'] = "ぞ", 227 | ['u'] = "ず", 228 | }; 229 | 230 | static char const *const *const twos[128] = { 231 | ['b'] = b_pairs, 232 | ['d'] = d_pairs, 233 | ['f'] = f_pairs, 234 | ['g'] = g_pairs, 235 | ['h'] = h_pairs, 236 | ['j'] = j_pairs, 237 | ['k'] = k_pairs, 238 | ['l'] = l_pairs, 239 | ['m'] = m_pairs, 240 | ['n'] = n_pairs, 241 | ['p'] = p_pairs, 242 | ['r'] = r_pairs, 243 | ['s'] = s_pairs, 244 | ['t'] = t_pairs, 245 | ['v'] = v_pairs, 246 | ['w'] = w_pairs, 247 | ['x'] = x_pairs, 248 | ['y'] = y_pairs, 249 | ['z'] = z_pairs, 250 | }; 251 | 252 | void anthywl_buffer_init(struct anthywl_buffer *buffer) { 253 | buffer->text = calloc(1, 1); 254 | buffer->len = 0; 255 | buffer->pos = 0; 256 | } 257 | 258 | void anthywl_buffer_destroy(struct anthywl_buffer *buffer) { 259 | free(buffer->text); 260 | } 261 | 262 | void anthywl_buffer_clear(struct anthywl_buffer *buffer) { 263 | buffer->text[0] = '\0'; 264 | buffer->len = 0; 265 | buffer->pos = 0; 266 | } 267 | 268 | void anthywl_buffer_append(struct anthywl_buffer *buffer, char const *text) { 269 | size_t text_len = strlen(text); 270 | buffer->text = realloc(buffer->text, buffer->len + text_len + 1); 271 | if (buffer->pos == 0) { 272 | memmove( 273 | buffer->text + text_len, 274 | buffer->text, 275 | buffer->len + 1); 276 | memcpy( 277 | buffer->text, 278 | text, 279 | text_len); 280 | } else if (buffer->pos == buffer->len) { 281 | memcpy( 282 | buffer->text + buffer->len, 283 | text, 284 | text_len + 1); 285 | } else { 286 | memmove( 287 | buffer->text + buffer->pos + text_len, 288 | buffer->text + buffer->pos, 289 | buffer->len - buffer->pos + 1); 290 | memcpy( 291 | buffer->text + buffer->pos, 292 | text, 293 | text_len); 294 | } 295 | buffer->len += text_len; 296 | buffer->pos += text_len; 297 | } 298 | 299 | void anthywl_buffer_delete_backwards(struct anthywl_buffer *buffer, size_t amt) 300 | { 301 | if (buffer->pos == 0) 302 | return; 303 | size_t end = buffer->pos; 304 | size_t start = buffer->pos; 305 | for (size_t i = 0; i < amt; i++) { 306 | start -= 1; 307 | for (; start != 0; start--) { 308 | if ((buffer->text[start] & 0x80) == 0 309 | || (buffer->text[start] & 0xC0) == 0xC0) 310 | { 311 | break; 312 | } 313 | } 314 | } 315 | memmove( 316 | buffer->text + start, 317 | buffer->text + end, 318 | buffer->len - end + 1); 319 | buffer->len -= end - start; 320 | buffer->pos -= end - start; 321 | } 322 | 323 | void anthywl_buffer_delete_forwards(struct anthywl_buffer *buffer, size_t amt) { 324 | if (buffer->pos == buffer->len) 325 | return; 326 | size_t start = buffer->pos; 327 | size_t end = start + 1; 328 | for (; end != buffer->len; end++) { 329 | if ((buffer->text[end] & 0x80) == 0 330 | || (buffer->text[end] & 0xC0) == 0xC0) 331 | { 332 | break; 333 | } 334 | } 335 | memmove( 336 | buffer->text + start, 337 | buffer->text + end, 338 | buffer->len - end + 1); 339 | buffer->len -= end - start; 340 | } 341 | 342 | void anthywl_buffer_move_left(struct anthywl_buffer *buffer) { 343 | if (buffer->pos == 0) 344 | return; 345 | buffer->pos -= 1; 346 | while ((buffer->text[buffer->pos] & 0x80) != 0 347 | && (buffer->text[buffer->pos] & 0xC0) != 0xC0) 348 | { 349 | buffer->pos -= 1; 350 | } 351 | } 352 | 353 | void anthywl_buffer_move_right(struct anthywl_buffer *buffer) { 354 | if (buffer->pos == buffer->len) 355 | return; 356 | buffer->pos += 1; 357 | while ((buffer->text[buffer->pos] & 0x80) != 0 358 | && (buffer->text[buffer->pos] & 0xC0) != 0xC0) 359 | { 360 | buffer->pos += 1; 361 | } 362 | } 363 | 364 | void anthywl_buffer_convert_romaji(struct anthywl_buffer *buffer) { 365 | if (buffer->pos == 0) 366 | return; 367 | 368 | unsigned char c0 = '\0', c1 = '\0', c2 = '\0', c3 = '\0'; 369 | 370 | if (buffer->pos >= 1) c3 = buffer->text[buffer->pos - 1]; 371 | if (buffer->pos >= 2) c2 = buffer->text[buffer->pos - 2]; 372 | if (buffer->pos >= 3) c1 = buffer->text[buffer->pos - 3]; 373 | if (buffer->pos >= 4) c0 = buffer->text[buffer->pos - 4]; 374 | 375 | if (c3 >= 0x80) c3 = '\0'; 376 | if (c2 >= 0x80) c2 = '\0'; 377 | if (c1 >= 0x80) c1 = '\0'; 378 | if (c0 >= 0x80) c0 = '\0'; 379 | 380 | if (c2 == 'n' && c3 == '\'') { 381 | anthywl_buffer_delete_backwards(buffer, 2); 382 | anthywl_buffer_append(buffer, "ん"); 383 | return; 384 | } 385 | 386 | if (!c3) 387 | return; 388 | 389 | if (c3 == '.') { 390 | anthywl_buffer_delete_backwards(buffer, 1); 391 | anthywl_buffer_append(buffer, "。"); 392 | } 393 | 394 | if (c3 == '.') { 395 | anthywl_buffer_delete_backwards(buffer, 1); 396 | anthywl_buffer_append(buffer, "。"); 397 | } 398 | 399 | if (c2 == c3 && twos[c3]) { 400 | anthywl_buffer_delete_backwards(buffer, 2); 401 | anthywl_buffer_append(buffer, "っ"); 402 | anthywl_buffer_append(buffer, (char[]){c3, 0}); 403 | } 404 | 405 | // shi -> si 406 | // chi -> ti 407 | // thi -> texi 408 | 409 | if (c1 == 's' && c2 == 'h' && c3 == 'i') { 410 | anthywl_buffer_delete_backwards(buffer, 3); 411 | anthywl_buffer_append(buffer, "し"); 412 | return; 413 | } 414 | 415 | if (c1 == 'c' && c2 == 'h' && c3 == 'i') { 416 | anthywl_buffer_delete_backwards(buffer, 3); 417 | anthywl_buffer_append(buffer, "ち"); 418 | return; 419 | } 420 | 421 | if (c1 == 't' && c2 == 'h' && c3 == 'i') { 422 | anthywl_buffer_delete_backwards(buffer, 3); 423 | anthywl_buffer_append(buffer, "てぃ"); 424 | return; 425 | } 426 | 427 | if ((c1 == 'x' || c1 == 'l') && c2 == 't' && c3 == 'u') { 428 | anthywl_buffer_delete_backwards(buffer, 3); 429 | anthywl_buffer_append(buffer, "っ"); 430 | return; 431 | } 432 | 433 | if (c1 == 't' && c2 == 's' && c3 == 'u') { 434 | if (c0 == 'x' || c0 == 'l') { 435 | anthywl_buffer_delete_backwards(buffer, 4); 436 | anthywl_buffer_append(buffer, "っ"); 437 | return; 438 | } 439 | anthywl_buffer_delete_backwards(buffer, 3); 440 | anthywl_buffer_append(buffer, "つ"); 441 | return; 442 | } 443 | 444 | if (c1 == 's' && c2 == 'h' && youon[c3]) 445 | c2 = 'y'; 446 | 447 | if (c1 == 'c' && c2 == 'h' && youon[c3]) 448 | c1 = 't', c2 = 'y'; 449 | 450 | if (c1 && twos[c1] && c2 == 'y' && youon[c3]) { 451 | anthywl_buffer_delete_backwards(buffer, 3); 452 | if (c1 != 'x' && c1 != 'l') 453 | anthywl_buffer_append(buffer, twos[c1]['i']); 454 | anthywl_buffer_append(buffer, youon[c3]); 455 | return; 456 | } 457 | 458 | if (c2 && twos[c2] && twos[c2][c3]) { 459 | anthywl_buffer_delete_backwards(buffer, 2); 460 | anthywl_buffer_append(buffer, twos[c2][c3]); 461 | return; 462 | } 463 | 464 | if (ones[c3]) { 465 | anthywl_buffer_delete_backwards(buffer, 1); 466 | anthywl_buffer_append(buffer, ones[c3]); 467 | return; 468 | } 469 | } 470 | 471 | void anthywl_buffer_convert_trailing_n(struct anthywl_buffer *buffer) { 472 | if (buffer->pos == 0) 473 | return; 474 | if (buffer->text[buffer->pos - 1] == 'n') { 475 | anthywl_buffer_delete_backwards(buffer, 1); 476 | anthywl_buffer_append(buffer, "ん"); 477 | } 478 | } 479 | -------------------------------------------------------------------------------- /src/config.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "anthywl.h" 6 | #include "config.h" 7 | 8 | char const anthywl_default_config[] = 9 | #include "default_config.inc" 10 | ; 11 | 12 | static void anthywl_config_load_bindings(struct anthywl_config *config, 13 | struct scfg_block *block, struct wl_array *bindings) 14 | { 15 | for (size_t i = 0; i < block->directives_len; i++) { 16 | struct scfg_directive *directive = &block->directives[i]; 17 | if (directive->params_len != 1) { 18 | fprintf(stderr, "line %d: invalid number of parameters " 19 | "for bindings directive, ignoring\n", directive->lineno); 20 | return; 21 | } 22 | 23 | char *s = directive->name, *p; 24 | struct anthywl_binding binding = { 0 }; 25 | while ((p = strchr(s, '+'))) { 26 | *p = 0; 27 | if (strcmp(s, "Shift") == 0) 28 | binding.modifiers |= ANTHYWL_SHIFT; 29 | else if (strcmp(s, "Lock") == 0) 30 | binding.modifiers |= ANTHYWL_CAPS; 31 | else if (strcmp(s, "Ctrl") == 0 || strcmp(s, "Control") == 0) 32 | binding.modifiers |= ANTHYWL_CTRL; 33 | else if (strcmp(s, "Mod1") == 0 || strcmp(s, "Alt") == 0) 34 | binding.modifiers |= ANTHYWL_ALT; 35 | else if (strcmp(s, "Mod2") == 0) 36 | binding.modifiers |= ANTHYWL_NUM; 37 | else if (strcmp(s, "Mod3") == 0) 38 | binding.modifiers |= ANTHYWL_MOD3; 39 | else if (strcmp(s, "Mod4") == 0) 40 | binding.modifiers |= ANTHYWL_LOGO; 41 | else if (strcmp(s, "Mod5") == 0) 42 | binding.modifiers |= ANTHYWL_MOD5; 43 | else { 44 | fprintf(stderr, 45 | "line %d: invalid modifier %s, binding ignored\n", 46 | directive->lineno, s); 47 | return; 48 | } 49 | s = p + 1; 50 | } 51 | binding.keysym = xkb_keysym_from_name(s, XKB_KEYSYM_CASE_INSENSITIVE); 52 | if (binding.keysym == XKB_KEY_NoSymbol) { 53 | fprintf(stderr, "line %d: invalid key %s, binding ignored\n", 54 | directive->lineno, s); 55 | continue; 56 | } 57 | binding.action = anthywl_action_from_string(directive->params[0]); 58 | *(struct anthywl_binding *)wl_array_add(bindings, sizeof binding) = 59 | binding; 60 | } 61 | qsort(bindings->data, bindings->size / sizeof(struct anthywl_binding), 62 | sizeof(struct anthywl_binding), anthywl_binding_compare); 63 | } 64 | 65 | static void anthywl_config_load_root(struct anthywl_config *config, 66 | struct scfg_block *root) 67 | { 68 | for (size_t i = 0; i < root->directives_len; i++) { 69 | struct scfg_directive *directive = &root->directives[i]; 70 | if (strcmp(directive->name, "active-at-startup") == 0) { 71 | if (directive->params_len != 0) 72 | fprintf(stderr, 73 | "line %d: too many arguments to active-at-startup\n", 74 | directive->lineno); 75 | else 76 | config->active_at_startup = true; 77 | } else if (strcmp(directive->name, "global-bindings") == 0) { 78 | anthywl_config_load_bindings( 79 | config, &directive->children, &config->global_bindings); 80 | } else if (strcmp(directive->name, "composing-bindings") == 0) { 81 | anthywl_config_load_bindings( 82 | config, &directive->children, &config->composing_bindings); 83 | } else if (strcmp(directive->name, "selecting-bindings") == 0) { 84 | anthywl_config_load_bindings( 85 | config, &directive->children, &config->selecting_bindings); 86 | } else { 87 | fprintf(stderr, "line %d: unknown section '%s'\n", 88 | directive->lineno, directive->name); 89 | } 90 | } 91 | } 92 | 93 | void anthywl_config_init(struct anthywl_config *config) { 94 | wl_array_init(&config->global_bindings); 95 | wl_array_init(&config->composing_bindings); 96 | wl_array_init(&config->selecting_bindings); 97 | } 98 | 99 | void anthywl_config_finish(struct anthywl_config *config) { 100 | wl_array_release(&config->selecting_bindings); 101 | wl_array_release(&config->composing_bindings); 102 | wl_array_release(&config->global_bindings); 103 | } 104 | 105 | bool anthywl_config_load(struct anthywl_config *config) { 106 | char path[PATH_MAX]; 107 | char const *prefix; 108 | if ((prefix = getenv("XDG_CONFIG_HOME"))) { 109 | snprintf(path, sizeof path, "%s/anthywl/config", prefix); 110 | } else if ((prefix = getenv("HOME"))) { 111 | snprintf(path, sizeof path, "%s/.config/anthywl/config", prefix); 112 | } else { 113 | fprintf(stderr, "cannot find config file\n"); 114 | return false; 115 | } 116 | 117 | FILE *f = fopen(path, "r"); 118 | if (f == NULL) 119 | f = fmemopen((char *)anthywl_default_config, 120 | sizeof anthywl_default_config, "r"); 121 | 122 | if (f == NULL) { 123 | perror("failed to open default config file"); 124 | return false; 125 | } 126 | 127 | struct scfg_block root; 128 | if (scfg_parse_file(&root, f)) 129 | goto close; 130 | anthywl_config_load_root(config, &root); 131 | scfg_block_finish(&root); 132 | close: 133 | fclose(f); 134 | return true; 135 | } 136 | 137 | -------------------------------------------------------------------------------- /src/graphics_buffer.c: -------------------------------------------------------------------------------- 1 | #include "graphics_buffer.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | static void wl_buffer_release(void *data, struct wl_buffer *wl_buffer) { 8 | struct anthywl_graphics_buffer *buffer = data; 9 | buffer->in_use = false; 10 | } 11 | 12 | static struct wl_buffer_listener const wl_buffer_listener = { 13 | .release = wl_buffer_release, 14 | }; 15 | 16 | static struct anthywl_graphics_buffer *anthywl_graphics_buffer_create( 17 | struct wl_shm *wl_shm, struct wl_list *buffers, int width, int height) 18 | { 19 | struct anthywl_graphics_buffer *buffer = calloc(1, sizeof *buffer); 20 | 21 | buffer->width = width; 22 | buffer->height = height; 23 | buffer->stride = 24 | cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, buffer->width); 25 | buffer->size = buffer->stride * buffer->height; 26 | buffer->in_use = true; 27 | int fd = memfd_create("anthywl", MFD_CLOEXEC); 28 | int rc = ftruncate(fd, buffer->size); (void)rc; 29 | 30 | buffer->data = 31 | mmap(NULL, buffer->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 32 | 33 | struct wl_shm_pool *pool = wl_shm_create_pool(wl_shm, fd, buffer->size); 34 | 35 | buffer->wl_buffer = wl_shm_pool_create_buffer( 36 | pool, 0, buffer->width, buffer->height, 37 | buffer->stride, WL_SHM_FORMAT_ARGB8888); 38 | 39 | close(fd); 40 | wl_shm_pool_destroy(pool); 41 | 42 | wl_buffer_add_listener(buffer->wl_buffer, &wl_buffer_listener, buffer); 43 | wl_list_insert(buffers, &buffer->link); 44 | 45 | buffer->cairo_surface = cairo_image_surface_create_for_data( 46 | buffer->data, CAIRO_FORMAT_ARGB32, 47 | buffer->width, buffer->height, buffer->stride); 48 | buffer->cairo = cairo_create(buffer->cairo_surface); 49 | 50 | return buffer; 51 | } 52 | 53 | void anthywl_graphics_buffer_destroy( 54 | struct anthywl_graphics_buffer *buffer) 55 | { 56 | cairo_destroy(buffer->cairo); 57 | cairo_surface_destroy(buffer->cairo_surface); 58 | munmap(buffer->data, buffer->size); 59 | wl_buffer_destroy(buffer->wl_buffer); 60 | wl_list_remove(&buffer->link); 61 | free(buffer); 62 | } 63 | 64 | struct anthywl_graphics_buffer *anthywl_graphics_buffer_get( 65 | struct wl_shm *wl_shm, struct wl_list *buffers, 66 | int width, int height) 67 | { 68 | bool found = false; 69 | 70 | struct anthywl_graphics_buffer *buffer, *tmp; 71 | wl_list_for_each_safe(buffer, tmp, buffers, link) { 72 | if (buffer->in_use) 73 | continue; 74 | if (buffer->width != width || buffer->height != height) { 75 | anthywl_graphics_buffer_destroy(buffer); 76 | continue; 77 | } 78 | cairo_destroy(buffer->cairo); 79 | buffer->cairo = cairo_create(buffer->cairo_surface); 80 | found = true; 81 | break; 82 | } 83 | 84 | if (!found) 85 | buffer = anthywl_graphics_buffer_create(wl_shm, buffers, width, height); 86 | 87 | buffer->in_use = true; 88 | 89 | return buffer; 90 | } 91 | 92 | -------------------------------------------------------------------------------- /src/ipc.c: -------------------------------------------------------------------------------- 1 | #include "ipc.h" 2 | #include "anthywl.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | int anthywl_ipc_addr(char *addr, size_t size) { 10 | char const *wayland_display = getenv("WAYLAND_DISPLAY"); 11 | char const *xdg_runtime_dir = getenv("XDG_RUNTIME_DIR"); 12 | if (wayland_display == NULL || wayland_display[0] == '\0') { 13 | fprintf(stderr, "WAYLAND_DISPLAY is not set\n"); 14 | return -1; 15 | } 16 | if (xdg_runtime_dir == NULL || xdg_runtime_dir[0] == '\0') { 17 | fprintf(stderr, "XDG_RUNTIME_DIR is not set\n"); 18 | return -1; 19 | } 20 | return snprintf(addr, size, "unix:%s/ca.tadeo.anthywl.%s", 21 | xdg_runtime_dir, wayland_display); 22 | } 23 | 24 | static char const ca_tadeo_anthywl_interface[] = 25 | #include "ca.tadeo.anthywl.varlink.inc" 26 | ; 27 | 28 | bool anthywl_ipc_init(struct anthywl_ipc *ipc) { 29 | char ipc_addr[PATH_MAX]; 30 | if (anthywl_ipc_addr(ipc_addr, sizeof ipc_addr) < 0) 31 | return false; 32 | long res = varlink_service_new(&ipc->service, 33 | "tadeokondrak", "anthywl", "0.0.0", 34 | "https://github.com/tadeokondrak/anthywl", ipc_addr, -1); 35 | if (res < 0) { 36 | fprintf(stderr, "Failed to start varlink service: %s\n", 37 | varlink_error_string(-res)); 38 | return false; 39 | } 40 | res = varlink_service_add_interface(ipc->service, 41 | ca_tadeo_anthywl_interface, 42 | "Action", anthywl_ipc_handle_action, ipc, 43 | NULL); 44 | if (res < 0) { 45 | fprintf(stderr, "Failed to set up varlink service: %s\n", 46 | varlink_error_string(-res)); 47 | return false; 48 | } 49 | return true; 50 | } 51 | 52 | void anthywl_ipc_finish(struct anthywl_ipc *ipc) { 53 | if (ipc->service != NULL) 54 | varlink_service_free(ipc->service); 55 | } 56 | 57 | long anthywl_ipc_handle_action(VarlinkService *service, VarlinkCall *call, 58 | VarlinkObject *parameters, uint64_t flags, void *userdata) 59 | { 60 | long res; 61 | char const *seat_name, *action_name; 62 | struct anthywl_state *state = wl_container_of(userdata, state, ipc); 63 | if ((res = varlink_object_get_string(parameters, "seat", &seat_name)) < 0) 64 | return varlink_call_reply_invalid_parameter(call, "seat"); 65 | if ((res = varlink_object_get_string(parameters, "action", &action_name)) < 0) 66 | return varlink_call_reply_invalid_parameter(call, "action"); 67 | enum anthywl_action action = anthywl_action_from_string(action_name); 68 | if (action == ANTHYWL_ACTION_INVALID) 69 | return varlink_call_reply_invalid_parameter(call, "action"); 70 | 71 | bool found = false; 72 | struct anthywl_seat *seat; 73 | wl_list_for_each(seat, &state->seats, link) { 74 | if (strcmp(seat->name, seat_name) == 0) { 75 | found = true; 76 | break; 77 | } 78 | } 79 | if (!found) { 80 | VarlinkObject *no_such_seat; 81 | if ((res = varlink_object_new(&no_such_seat)) < 0) 82 | return res; 83 | varlink_object_set_string(no_such_seat, "seat", seat_name); 84 | return varlink_call_reply_error( 85 | call, "ca.tadeo.anthywl.NoSuchSeat", no_such_seat); 86 | } 87 | 88 | anthywl_seat_handle_action(seat, action); 89 | 90 | return varlink_call_reply(call, NULL, 0); 91 | } 92 | -------------------------------------------------------------------------------- /src/keymap.c: -------------------------------------------------------------------------------- 1 | #include "keymap.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | static unsigned char const *utf8_simple(unsigned char const *s, long *c) { 17 | unsigned char const *next; 18 | if (s[0] < 0x80) { 19 | *c = s[0]; 20 | next = s + 1; 21 | } else if ((s[0] & 0xe0) == 0xc0) { 22 | *c = ((long)(s[0] & 0x1f) << 6) | 23 | ((long)(s[1] & 0x3f) << 0); 24 | next = s + 2; 25 | } else if ((s[0] & 0xf0) == 0xe0) { 26 | *c = ((long)(s[0] & 0x0f) << 12) | 27 | ((long)(s[1] & 0x3f) << 6) | 28 | ((long)(s[2] & 0x3f) << 0); 29 | next = s + 3; 30 | } else if ((s[0] & 0xf8) == 0xf0 && (s[0] <= 0xf4)) { 31 | *c = ((long)(s[0] & 0x07) << 18) | 32 | ((long)(s[1] & 0x3f) << 12) | 33 | ((long)(s[2] & 0x3f) << 6) | 34 | ((long)(s[3] & 0x3f) << 0); 35 | next = s + 4; 36 | } else { 37 | *c = -1; // invalid 38 | next = s + 1; // skip this byte 39 | } 40 | if (*c >= 0xd800 && *c <= 0xdfff) 41 | *c = -1; // surrogate half 42 | return next; 43 | } 44 | 45 | 46 | static int compare_keysym(void const *a, void const *b) { 47 | return *(xkb_keysym_t *)a - *(xkb_keysym_t *)b; 48 | } 49 | 50 | bool anthywl_make_keymap(char const *string, 51 | int *out_keymap_fd, size_t *out_keymap_size, 52 | uint32_t **out_keys_seq, size_t *out_keys_size) 53 | { 54 | struct wl_array keys; 55 | wl_array_init(&keys); 56 | 57 | xkb_keysym_t keysyms[247] = {0}; 58 | size_t keysyms_len = 0; 59 | 60 | { 61 | unsigned char const *s = (unsigned char const *)string; 62 | long c; 63 | while (*s) { 64 | s = utf8_simple(s, &c); 65 | if (c < 0) continue; 66 | xkb_keysym_t keysym = xkb_utf32_to_keysym((uint32_t)c); 67 | uint32_t *found = bsearch( 68 | &keysym, keysyms, keysyms_len, sizeof(uint32_t), compare_keysym); 69 | if (!found) { 70 | found = &keysyms[keysyms_len++]; 71 | *found = keysym; 72 | qsort(keysyms, keysyms_len, sizeof(uint32_t), compare_keysym); 73 | } 74 | } 75 | } 76 | 77 | { 78 | unsigned char const *s = (unsigned char const *)string; 79 | long c; 80 | while (*s) { 81 | s = utf8_simple(s, &c); 82 | if (c < 0) continue; 83 | xkb_keysym_t keysym = xkb_utf32_to_keysym((uint32_t)c); 84 | uint32_t *found = bsearch( 85 | &keysym, keysyms, keysyms_len, sizeof(uint32_t), compare_keysym); 86 | assert(found); 87 | uint32_t found_index = found - keysyms; 88 | *(uint32_t *)wl_array_add(&keys, sizeof(uint32_t)) = 89 | found_index + 2; 90 | } 91 | } 92 | 93 | int fd = memfd_create("anthywl-keymap", MFD_CLOEXEC); 94 | if (fd == -1) { 95 | perror("memfd_create"); 96 | return false; 97 | } 98 | 99 | FILE *f = fdopen(fd, "w"); 100 | if (f == NULL) { 101 | perror("fdopen"); 102 | close(fd); 103 | return false; 104 | } 105 | 106 | fputs("xkb_keymap {\n", f); 107 | fputs("\txkb_keycodes {\n", f); 108 | fputs("\t\tminimum = 8;\n", f); 109 | fputs("\t\tmaximum = 255;\n", f); 110 | for (size_t i = 0; i < keysyms_len; i++) { 111 | fprintf(f, "\t\t = %zu;\n", i + 2, i + 8 + 2); 112 | } 113 | fputs("\t};\n", f); 114 | fputs("\txkb_types {\n", f); 115 | fputs("\t\tvirtual_modifiers _;\n", f); 116 | fputs("\t};\n", f); 117 | fputs("\txkb_compatibility {\n", f); 118 | fputs("\t\tinterpret None { action = NoAction(); };\n", f); 119 | fputs("\t};\n", f); 120 | fputs("\txkb_symbols {\n", f); 121 | for (size_t i = 0; i < keysyms_len; i++) { 122 | char sym_name[256]; 123 | xkb_keysym_get_name(keysyms[i], sym_name, sizeof(sym_name)); 124 | fprintf(f, "\t\tkey { [ %s ] };\n", i + 2, sym_name); 125 | } 126 | fputs("\t};\n", f); 127 | fputs("};\n", f); 128 | fputc(0, f); 129 | 130 | if (fflush(f) != 0) { 131 | perror("fflush"); 132 | fclose(f); 133 | return false; 134 | } 135 | 136 | long keymap_size = ftell(f); 137 | 138 | int duplicated_fd = dup(fd); 139 | if (duplicated_fd == -1) { 140 | perror("dup"); 141 | fclose(f); 142 | return false; 143 | } 144 | 145 | if (fclose(f) != 0) { 146 | perror("fclose"); 147 | close(duplicated_fd); 148 | close(fd); 149 | return false; 150 | } 151 | 152 | 153 | struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); 154 | char *map = mmap(NULL, keymap_size, PROT_READ, MAP_PRIVATE, duplicated_fd, 0); 155 | assert(map); 156 | struct xkb_keymap *keymap = xkb_keymap_new_from_string( 157 | context, map, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); 158 | munmap(map, keymap_size); 159 | assert(keymap); 160 | 161 | 162 | *out_keymap_fd = duplicated_fd; 163 | *out_keymap_size = keymap_size; 164 | *out_keys_seq = keys.data; 165 | *out_keys_size = keys.size / sizeof(uint32_t); 166 | 167 | return true; 168 | } 169 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | anthywl_src += files( 2 | 'anthywl.c', 3 | 'actions.c', 4 | 'buffer.c', 5 | 'config.c', 6 | 'graphics_buffer.c', 7 | 'keymap.c', 8 | ) 9 | 10 | if get_option('ipc').enabled() 11 | anthywl_src += files('ipc.c') 12 | endif 13 | 14 | anthywl_bin = executable( 15 | 'anthywl', 16 | anthywl_src, 17 | install: true, 18 | include_directories: anthywl_inc, 19 | dependencies: [ 20 | wayland_client_dep, 21 | wayland_cursor_dep, 22 | xkbcommon_dep, 23 | protocols_dep, 24 | anthy_dep, 25 | pango_dep, 26 | cairo_dep, 27 | pangocairo_dep, 28 | scfg_dep, 29 | varlink_dep, 30 | ], 31 | ) 32 | -------------------------------------------------------------------------------- /subprojects/libscfg.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = libscfg-a4f023d2e1c2c2ac71eb23a989bd58bd3f77fb2a 3 | source_url = https://git.sr.ht/~emersion/libscfg/archive/a4f023d2e1c2c2ac71eb23a989bd58bd3f77fb2a.tar.gz 4 | source_filename = libscfg-a4f023d2e1c2c2ac71eb23a989bd58bd3f77fb2a.tar.gz 5 | source_hash = ec835835f98ac269eee2dd3a8ba58e3326808fa219a7884d835a4a0583365722 6 | --------------------------------------------------------------------------------