From 614b795c1eccece05abf02836d76c39ba6d27575 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 10 Mar 2020 21:08:47 +0100 Subject: Initial commit --- .editorconfig | 8 + .gitignore | 2 + LICENSE | 21 ++ README.md | 15 ++ include/wlhangul.h | 26 ++ main.c | 113 ++++++++ meson.build | 37 +++ protocol/input-method-unstable-v2.xml | 490 ++++++++++++++++++++++++++++++++++ protocol/meson.build | 29 ++ 9 files changed, 741 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 include/wlhangul.h create mode 100644 main.c create mode 100644 meson.build create mode 100644 protocol/input-method-unstable-v2.xml create mode 100644 protocol/meson.build diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2097786 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +trim_trailing_whitespace = true +indent_style = tab +indent_size = 4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f69e60 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/build/ +/build-*/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0243e5a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Simon Ser + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..03a0fd5 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# wlhangul + +A Hangul input method for Wayland. + +## Building + +Depends on Wayland and libhangul. + + meson build/ + ninja -C build/ + build/wlhangul + +## License + +MIT diff --git a/include/wlhangul.h b/include/wlhangul.h new file mode 100644 index 0000000..0d62431 --- /dev/null +++ b/include/wlhangul.h @@ -0,0 +1,26 @@ +#ifndef WLHANGUL_H +#define WLHANGUL_H + +#include +#include +#include + +struct wlhangul_state { + struct wl_display *display; + struct zwp_input_method_manager_v2 *input_method_manager; + + bool running; + + struct wl_list seats; +}; + +struct wlhangul_seat { + struct wl_list link; + struct wl_seat *wl_seat; + struct wlhangul_state *state; + + HangulInputContext *input_context; + struct zwp_input_method_v2 *input_method; +}; + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..a220e78 --- /dev/null +++ b/main.c @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include "wlhangul.h" +#include "input-method-unstable-v2-client-protocol.h" + +static void handle_activate(void *data, + struct zwp_input_method_v2 *input_method) { +} + +static void handle_deactivate(void *data, + struct zwp_input_method_v2 *input_method) { +} + +static void handle_surrounding_text(void *data, + struct zwp_input_method_v2 *input_method, const char *text, + uint32_t cursor, uint32_t anchor) { +} + +static void handle_text_change_cause(void *data, + struct zwp_input_method_v2 *input_method, uint32_t cause) { +} + +static void handle_content_type(void *data, + struct zwp_input_method_v2 *input_method, uint32_t hint, + uint32_t purpose) { +} + +static void handle_done(void *data, struct zwp_input_method_v2 *input_method) { + // TODO +} + +static void handle_unavailable(void *data, + struct zwp_input_method_v2 *input_method) { +} + +static const struct zwp_input_method_v2_listener input_method_listener = { + .activate = handle_activate, + .deactivate = handle_deactivate, + .surrounding_text = handle_surrounding_text, + .text_change_cause = handle_text_change_cause, + .content_type = handle_content_type, + .done = handle_done, + .unavailable = handle_unavailable, +}; + +static struct wlhangul_seat *create_seat(struct wlhangul_state *state, + struct wl_seat *wl_seat) { + struct wlhangul_seat *seat = calloc(1, sizeof(*seat)); + seat->wl_seat = wl_seat; + wl_list_insert(&state->seats, &seat->link); + return seat; +} + +static void registry_handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) { + struct wlhangul_state *state = data; + if (strcmp(interface, wl_seat_interface.name) == 0) { + struct wl_seat *seat = + wl_registry_bind(registry, name, &wl_seat_interface, 1); + create_seat(state, seat); + } else if (strcmp(interface, zwp_input_method_manager_v2_interface.name) == 0) { + state->input_method_manager = wl_registry_bind(registry, name, + &zwp_input_method_manager_v2_interface, 1); + } +} + +static void registry_handle_global_remove(void *data, + struct wl_registry *registry, uint32_t name) { + // TODO +} + +static const struct wl_registry_listener registry_listener = { + .global = registry_handle_global, + .global_remove = registry_handle_global_remove, +}; + +int main(int argc, char *argv[]) { + struct wlhangul_state state = {0}; + wl_list_init(&state.seats); + + state.display = wl_display_connect(NULL); + if (state.display == NULL) { + fprintf(stderr, "failed to connect to Wayland display\n"); + return 1; + } + + struct wl_registry *registry = wl_display_get_registry(state.display); + wl_registry_add_listener(registry, ®istry_listener, &state); + wl_display_roundtrip(state.display); + + if (state.input_method_manager == NULL) { + fprintf(stderr, "missing wl_seat or zwp_input_method_manager_v2\n"); + return 1; + } + + struct wlhangul_seat *seat; + wl_list_for_each(seat, &state.seats, link) { + seat->input_context = hangul_ic_new("2"); + seat->input_method = zwp_input_method_manager_v2_get_input_method( + state.input_method_manager, seat->wl_seat); + zwp_input_method_v2_add_listener(seat->input_method, + &input_method_listener, seat); + } + + state.running = true; + while (state.running && wl_display_dispatch(state.display) != -1) { + // This space is intentionally left blank + } + + return 0; +} diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..9b20f9a --- /dev/null +++ b/meson.build @@ -0,0 +1,37 @@ +project( + 'wlhangul', + 'c', + version: '0.0.0', + license: 'MIT', + meson_version: '>=0.51.0', + default_options: [ + 'c_std=c99', + 'warning_level=2', + 'werror=true', + ], +) + +cc = meson.get_compiler('c') + +add_project_arguments(cc.get_supported_arguments([ + '-Wno-missing-braces', + '-Wno-unused-parameter', +]), language: 'c') + +wlhangul_inc = include_directories('include') + +libhangul = dependency('libhangul') +wayland_client = dependency('wayland-client') +wayland_protocols = dependency('wayland-protocols') + +wlhangul_files = files('main.c') + +subdir('protocol') + +executable( + meson.project_name(), + wlhangul_files, + include_directories: wlhangul_inc, + dependencies: [libhangul, wayland_client], + install: true, +) diff --git a/protocol/input-method-unstable-v2.xml b/protocol/input-method-unstable-v2.xml new file mode 100644 index 0000000..9e5ee6b --- /dev/null +++ b/protocol/input-method-unstable-v2.xml @@ -0,0 +1,490 @@ + + + + + Copyright © 2008-2011 Kristian Høgsberg + Copyright © 2010-2011 Intel Corporation + Copyright © 2012-2013 Collabora, Ltd. + Copyright © 2012, 2013 Intel Corporation + Copyright © 2015, 2016 Jan Arne Petersen + Copyright © 2017, 2018 Red Hat, Inc. + Copyright © 2018 Purism SPC + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol allows applications to act as input methods for compositors. + + An input method context is used to manage the state of the input method. + + Text strings are UTF-8 encoded, their indices and lengths are in bytes. + + This document adheres to the RFC 2119 when using words like "must", + "should", "may", etc. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + An input method object allows for clients to compose text. + + The objects connects the client to a text input in an application, and + lets the client to serve as an input method for a seat. + + The zwp_input_method_v2 object can occupy two distinct states: active and + inactive. In the active state, the object is associated to and + communicates with a text input. In the inactive state, there is no + associated text input, and the only communication is with the compositor. + Initially, the input method is in the inactive state. + + Requests issued in the inactive state must be accepted by the compositor. + Because of the serial mechanism, and the state reset on activate event, + they will not have any effect on the state of the next text input. + + There must be no more than one input method object per seat. + + + + + Notification that a text input focused on this seat requested the input + method to be activated. + + This event serves the purpose of providing the compositor with an + active input method. + + This event resets all state associated with previous enable, disable, + surrounding_text, text_change_cause, and content_type events, as well + as the state associated with set_preedit_string, commit_string, and + delete_surrounding_text requests. In addition, it marks the + zwp_input_method_v2 object as active, and makes any existing + zwp_input_popup_surface_v2 objects visible. + + The surrounding_text, and content_type events must follow before the + next done event if the text input supports the respective + functionality. + + State set with this event is double-buffered. It will get applied on + the next zwp_input_method_v2.done event, and stay valid until changed. + + + + + + Notification that no focused text input currently needs an active + input method on this seat. + + This event marks the zwp_input_method_v2 object as inactive. The + compositor must make all existing zwp_input_popup_surface_v2 objects + invisible until the next activate event. + + State set with this event is double-buffered. It will get applied on + the next zwp_input_method_v2.done event, and stay valid until changed. + + + + + + Updates the surrounding plain text around the cursor, excluding the + preedit text. + + If any preedit text is present, it is replaced with the cursor for the + purpose of this event. + + The argument text is a buffer containing the preedit string, and must + include the cursor position, and the complete selection. It should + contain additional characters before and after these. There is a + maximum length of wayland messages, so text can not be longer than 4000 + bytes. + + cursor is the byte offset of the cursor within the text buffer. + + anchor is the byte offset of the selection anchor within the text + buffer. If there is no selected text, anchor must be the same as + cursor. + + If this event does not arrive before the first done event, the input + method may assume that the text input does not support this + functionality and ignore following surrounding_text events. + + Values set with this event are double-buffered. They will get applied + and set to initial values on the next zwp_input_method_v2.done + event. + + The initial state for affected fields is empty, meaning that the text + input does not support sending surrounding text. If the empty values + get applied, subsequent attempts to change them may have no effect. + + + + + + + + + Tells the input method why the text surrounding the cursor changed. + + Whenever the client detects an external change in text, cursor, or + anchor position, it must issue this request to the compositor. This + request is intended to give the input method a chance to update the + preedit text in an appropriate way, e.g. by removing it when the user + starts typing with a keyboard. + + cause describes the source of the change. + + The value set with this event is double-buffered. It will get applied + and set to its initial value on the next zwp_input_method_v2.done + event. + + The initial value of cause is input_method. + + + + + + + Indicates the content type and hint for the current + zwp_input_method_v2 instance. + + Values set with this event are double-buffered. They will get applied + on the next zwp_input_method_v2.done event. + + The initial value for hint is none, and the initial value for purpose + is normal. + + + + + + + + Atomically applies state changes recently sent to the client. + + The done event establishes and updates the state of the client, and + must be issued after any changes to apply them. + + Text input state (content purpose, content hint, surrounding text, and + change cause) is conceptually double-buffered within an input method + context. + + Events modify the pending state, as opposed to the current state in use + by the input method. A done event atomically applies all pending state, + replacing the current state. After done, the new pending state is as + documented for each related request. + + Events must be applied in the order of arrival. + + Neither current nor pending state are modified unless noted otherwise. + + + + + + Send the commit string text for insertion to the application. + + Inserts a string at current cursor position (see commit event + sequence). The string to commit could be either just a single character + after a key press or the result of some composing. + + The argument text is a buffer containing the string to insert. There is + a maximum length of wayland messages, so text can not be longer than + 4000 bytes. + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_text_input_v3.commit request. + + The initial value of text is an empty string. + + + + + + + Send the pre-edit string text to the application text input. + + Place a new composing text (pre-edit) at the current cursor position. + Any previously set composing text must be removed. Any previously + existing selected text must be removed. The cursor is moved to a new + position within the preedit string. + + The argument text is a buffer containing the preedit string. There is + a maximum length of wayland messages, so text can not be longer than + 4000 bytes. + + The arguments cursor_begin and cursor_end are counted in bytes relative + to the beginning of the submitted string buffer. Cursor should be + hidden by the text input when both are equal to -1. + + cursor_begin indicates the beginning of the cursor. cursor_end + indicates the end of the cursor. It may be equal or different than + cursor_begin. + + Values set with this event are double-buffered. They must be applied on + the next zwp_input_method_v2.commit event. + + The initial value of text is an empty string. The initial value of + cursor_begin, and cursor_end are both 0. + + + + + + + + + Remove the surrounding text. + + before_length and after_length are the number of bytes before and after + the current cursor index (excluding the preedit text) to delete. + + If any preedit text is present, it is replaced with the cursor for the + purpose of this event. In effect before_length is counted from the + beginning of preedit text, and after_length from its end (see commit + event sequence). + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_input_method_v2.commit request. + + The initial values of both before_length and after_length are 0. + + + + + + + + Apply state changes from commit_string, set_preedit_string and + delete_surrounding_text requests. + + The state relating to these events is double-buffered, and each one + modifies the pending state. This request replaces the current state + with the pending state. + + The connected text input is expected to proceed by evaluating the + changes in the following order: + + 1. Replace existing preedit string with the cursor. + 2. Delete requested surrounding text. + 3. Insert commit string with the cursor at its end. + 4. Calculate surrounding text to send. + 5. Insert new preedit text in cursor position. + 6. Place cursor inside preedit text. + + The serial number reflects the last state of the zwp_input_method_v2 + object known to the client. The value of the serial argument must be + equal to the number of done events already issued by that object. When + the compositor receives a commit request with a serial different than + the number of past done events, it must proceed as normal, except it + should not change the current state of the zwp_input_method_v2 object. + + + + + + + Creates a new zwp_input_popup_surface_v2 object wrapping a given + surface. + + The surface gets assigned the "input_popup" role. If the surface + already has an assigned role, the compositor must issue a protocol + error. + + + + + + + + Allow an input method to receive hardware keyboard input and process + key events to generate text events (with pre-edit) over the wire. This + allows input methods which compose multiple key events for inputting + text like it is done for CJK languages. + + The compositor should send all keyboard events on the seat to the grab + holder via the returned wl_keyboard object. Nevertheless, the + compositor may decide not to forward any particular event. The + compositor must not further process any event after it has been + forwarded to the grab holder. + + Releasing the resulting wl_keyboard object releases the grab. + + + + + + + The input method ceased to be available. + + The compositor must issue this event as the only event on the object if + there was another input_method object associated with the same seat at + the time of its creation. + + The compositor must issue this request when the object is no longer + useable, e.g. due to seat removal. + + The input method context becomes inert and should be destroyed after + deactivation is handled. Any further requests and events except for the + destroy request must be ignored. + + + + + + Destroys the zwp_text_input_v2 object and any associated child + objects, i.e. zwp_input_popup_surface_v2 and + zwp_input_method_keyboard_grab_v2. + + + + + + + This interface marks a surface as a popup for interacting with an input + method. + + The compositor should place it near the active text input area. It must + be visible if and only if the input method is in the active state. + + The client must not destroy the underlying wl_surface while the + zwp_input_popup_surface_v2 object exists. + + + + + Notify about the position of the area of the text input expressed as a + rectangle in surface local coordinates. + + This is a hint to the input method telling it the relative position of + the text being entered. + + + + + + + + + + + + + + The zwp_input_method_keyboard_grab_v2 interface represents an exclusive + grab of the wl_keyboard interface associated with the seat. + + + + + This event provides a file descriptor to the client which can be + memory-mapped to provide a keyboard mapping description. + + + + + + + + + A key was pressed or released. + The time argument is a timestamp with millisecond granularity, with an + undefined base. + + + + + + + + + + Notifies clients that the modifier and/or group state has changed, and + it should update its local state. + + + + + + + + + + + + + + + Informs the client about the keyboard's repeat rate and delay. + + This event is sent as soon as the zwp_input_method_keyboard_grab_v2 + object has been created, and is guaranteed to be received by the + client before any key press event. + + Negative values for either rate or delay are illegal. A rate of zero + will disable any repeating (regardless of the value of delay). + + This event can be sent later on as well with a new value if necessary, + so clients should continue listening for the event past the creation + of zwp_input_method_keyboard_grab_v2. + + + + + + + + + The input method manager allows the client to become the input method on + a chosen seat. + + No more than one input method must be associated with any seat at any + given time. + + + + + Request a new input zwp_input_method_v2 object associated with a given + seat. + + + + + + + + Destroys the zwp_input_method_manager_v2 object. + + The zwp_input_method_v2 objects originating from it remain valid. + + + + diff --git a/protocol/meson.build b/protocol/meson.build new file mode 100644 index 0000000..0a27675 --- /dev/null +++ b/protocol/meson.build @@ -0,0 +1,29 @@ +protocols_dir = wayland_protocols.get_variable(pkgconfig: 'pkgdatadir') + +wayland_scanner = dependency('wayland-scanner', native: true) +wayland_scanner_exe = find_program( + wayland_scanner.get_variable(pkgconfig: 'wayland_scanner'), + native: true, +) + +protocols = { + 'input-method-unstable-v2': 'input-method-unstable-v2.xml', +} + +foreach name, path : protocols + code = custom_target( + name.underscorify() + '_c', + input: path, + output: '@BASENAME@-protocol.c', + command: [wayland_scanner_exe, 'private-code', '@INPUT@', '@OUTPUT@'], + ) + wlhangul_files += code + + client_header = custom_target( + name.underscorify() + '_client_h', + input: path, + output: '@BASENAME@-client-protocol.h', + command: [wayland_scanner_exe, 'client-header', '@INPUT@', '@OUTPUT@'], + ) + wlhangul_files += client_header +endforeach -- cgit v1.2.3