├── .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 |
--------------------------------------------------------------------------------