diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build diff --git a/bottom-panel.c b/bottom-panel.c new file mode 100644 index 0000000..6f19d01 --- /dev/null +++ b/bottom-panel.c @@ -0,0 +1,157 @@ +#include +#include + +#include "wlchewing.h" +#include "buffer.h" +#include "bottom-panel.h" + +static void noop() { + // no-op +} + +static void layer_surface_configure(void *data, + struct zwlr_layer_surface_v1 *wlr_layer_surface, + uint32_t serial, uint32_t w, uint32_t h) { + printf("resize %d %d\n", w, h); + struct wlchewing_bottom_panel *panel = data; + panel->width = w; + panel->height = h; + zwlr_layer_surface_v1_ack_configure(wlr_layer_surface, serial); +} + +static void layer_surface_closed(void *data, + struct zwlr_layer_surface_v1 *wlr_layer_surface) { + struct wlchewing_bottom_panel *panel = data; + bottom_panel_destroy(panel); +} + +static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { + .configure = layer_surface_configure, + .closed = layer_surface_closed, +}; + +static void surface_enter(void *data, struct wl_surface *wl_surface, + struct wl_output *output) { + struct wlchewing_bottom_panel *panel = data; + panel->scale = *((uint32_t *)wl_output_get_user_data(output)); + printf("scale %d\n", panel->scale); +} + +static const struct wl_surface_listener surface_listener = { + .enter = surface_enter, + .leave = noop, +}; + +static void bottom_panel_configure(struct wlchewing_bottom_panel *panel, + struct wlchewing_state *state){ + printf("render scale %d\n", panel->scale); + struct wlchewing_buffer *buffer = buffer_new(state->shm, + panel->width, panel->height, panel->scale); + assert(buffer); + panel->layout = pango_cairo_create_layout(buffer->cairo); + pango_layout_set_text(panel->layout, "哈嘍 PangoCairo", -1); + int width, height; + pango_layout_get_size(panel->layout, &width, &height); + panel->height = height / PANGO_SCALE; + wl_surface_attach(panel->wl_surface, buffer->wl_buffer, 0, 0); + zwlr_layer_surface_v1_set_size(panel->layer_surface, 0, panel->height); + zwlr_layer_surface_v1_set_exclusive_zone(panel->layer_surface, panel->height); + wl_surface_commit(panel->wl_surface); + wl_display_roundtrip(state->display); + wl_surface_set_buffer_scale(panel->wl_surface, panel->scale); + buffer_destroy(buffer); +} + +static int render_cand(struct wlchewing_buffer *buffer, PangoLayout *layout, + const char *text, bool selected) { + pango_layout_set_text(layout, text, -1); + int width, height; + pango_layout_get_size(layout, &width, &height); + width /= PANGO_SCALE; + if (selected) { + cairo_set_source_rgba(buffer->cairo, 0.2, 0.2, 0.2, 0.9); + cairo_rectangle(buffer->cairo, 0, 0, width + 8, buffer->height); + cairo_fill(buffer->cairo); + + } + cairo_move_to(buffer->cairo, 4, 0); + cairo_set_source_rgba(buffer->cairo, 1, 1, 1, 0.9); + pango_cairo_show_layout(buffer->cairo, layout); + return width + 8; +} + +struct wlchewing_bottom_panel *bottom_panel_new( + struct wlchewing_state *state) { + struct wlchewing_bottom_panel *panel = calloc(1, + sizeof(struct wlchewing_bottom_panel)); + if (panel == NULL) { + wlchewing_err("Failed to calloc for bottom panel"); + return NULL; + } + panel->scale = 1; + panel->height = 1; + panel->width = 1; + panel->wl_surface = wl_compositor_create_surface(state->compositor); + assert(panel->wl_surface); + wl_surface_add_listener(panel->wl_surface, &surface_listener, panel); + panel->layer_surface = zwlr_layer_shell_v1_get_layer_surface( + state->layer_shell, panel->wl_surface, NULL, + ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "input-method-panel"); + assert(panel->layer_surface); + + zwlr_layer_surface_v1_add_listener(panel->layer_surface, + &layer_surface_listener, panel); + zwlr_layer_surface_v1_set_anchor(panel->layer_surface, + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT); + wl_surface_commit(panel->wl_surface); + wl_display_roundtrip(state->display); + + // set scale and height TODO: set font options there + bottom_panel_configure(panel, state); + + panel->buffer_pool = buffer_pool_new(state->shm, + panel->width, panel->height, panel->scale); + return panel; +} + +void bottom_panel_destroy(struct wlchewing_bottom_panel *panel) { + zwlr_layer_surface_v1_destroy(panel->layer_surface); + buffer_pool_destroy(panel->buffer_pool); + g_object_unref(panel->layout); + free(panel); +} + +void bottom_panel_render(struct wlchewing_bottom_panel *panel, + ChewingContext *ctx) { + int total = chewing_cand_TotalChoice(ctx); + assert(panel->selected_index < total); + + struct wlchewing_buffer *buffer = buffer_pool_get_buffer( + panel->buffer_pool); + cairo_t *cairo = buffer->cairo; + cairo_save(cairo); + cairo_set_source_rgba(cairo, 0, 0, 0, 0.9); + cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); + cairo_paint(cairo); + cairo_set_operator(cairo, CAIRO_OPERATOR_OVER); + printf("selected %d\n", panel->selected_index); + + int offset = 0, total_offset = 0; + offset = render_cand(buffer, panel->layout, + chewing_cand_string_by_index_static(ctx, + panel->selected_index), true); + for (int i = panel->selected_index + 1; i < total; i++) { + cairo_translate(cairo, offset, 0); + total_offset += offset; + offset = render_cand(buffer, panel->layout, + chewing_cand_string_by_index_static(ctx, i), false); + } + cairo_translate(cairo, -total_offset, 0); + cairo_restore(cairo); + wl_surface_attach(panel->wl_surface, buffer->wl_buffer, 0, 0); + wl_surface_damage_buffer(panel->wl_surface, 0, 0, + buffer->width * buffer->scale, buffer->height * buffer->scale); + wl_surface_commit(panel->wl_surface); +} diff --git a/bottom-panel.h b/bottom-panel.h new file mode 100644 index 0000000..d4f1e5d --- /dev/null +++ b/bottom-panel.h @@ -0,0 +1,27 @@ +#ifndef BOTTOM_PANEL_H +#define BOTTOM_PANEL_H + +#include +#include "wlr-layer-shell-unstable-v1-client-protocol.h" + +struct wlchewing_state; +struct wlchewing_buffer; + +struct wlchewing_bottom_panel { + struct zwlr_layer_surface_v1 *layer_surface; + struct wl_surface *wl_surface; + struct wl_list *buffer_pool; + uint32_t width, height; + int32_t scale; + PangoLayout *layout; + int selected_index; +}; + +struct wlchewing_bottom_panel *bottom_panel_new(struct wlchewing_state *state); + +void bottom_panel_destroy(struct wlchewing_bottom_panel *panel); + +void bottom_panel_render(struct wlchewing_bottom_panel *panel, + ChewingContext *ctx); + +#endif diff --git a/buffer.c b/buffer.c new file mode 100644 index 0000000..bc5d02e --- /dev/null +++ b/buffer.c @@ -0,0 +1,126 @@ +#include "wlchewing.h" +#include "buffer.h" + +#include +#include +#include +#include +#include + +static void handle_release(void *data, struct wl_buffer *wl_buffer) { + struct wlchewing_buffer *buffer = data; + buffer->available = true; +} + +static const struct wl_buffer_listener buffer_listener = { + .release = handle_release, +}; + +static void mktempname(char *template) { +} + +struct wlchewing_buffer *buffer_new(struct wl_shm *shm, + uint32_t width, uint32_t height, uint32_t scale) { + struct wlchewing_buffer *buffer = calloc(1, sizeof(struct wlchewing_buffer)); + if (buffer == NULL){ + wlchewing_err("Failed to calloc for wlchewing_buffer"); + return NULL; + } + buffer->width = width; + buffer->height = height; + buffer->scale = scale; + buffer->shm = shm; + + char *template=strdup("/wlchewing-XXXXXX"); + char *name=mktemp(template); //TODO + int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd < 0) { + wlchewing_err("Failed to shm_open"); + free(buffer); + return NULL; + } + shm_unlink(name); + free(name); + + off_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, + width * scale); + buffer->size = (height * scale) * stride; + int ret = ftruncate(fd, buffer->size); + if (ret == -1) { + wlchewing_err("Failed to ftruncate"); + free(buffer); + return NULL; + } + buffer->data = mmap(NULL, buffer->size, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + if (buffer->data == MAP_FAILED) { + wlchewing_err("Failed to mmap %ld", buffer->size); + close(fd); + free(buffer); + return NULL; + } + + struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, buffer->size); + buffer->wl_buffer = wl_shm_pool_create_buffer(pool, 0, width * scale, + height * scale, stride, WL_SHM_FORMAT_ARGB8888); + wl_buffer_add_listener(buffer->wl_buffer, &buffer_listener, buffer); + wl_shm_pool_destroy(pool); + close(fd); + + buffer->cairo_surface = cairo_image_surface_create_for_data( + buffer->data, CAIRO_FORMAT_ARGB32, width * scale, + height * scale, stride); + buffer->cairo = cairo_create(buffer->cairo_surface); + cairo_scale(buffer->cairo, scale, scale); + return buffer; +} + +void buffer_destroy(struct wlchewing_buffer *buffer) { + wl_buffer_destroy(buffer->wl_buffer); + cairo_destroy(buffer->cairo); + cairo_surface_destroy(buffer->cairo_surface); + munmap(buffer->data, buffer->size); + free(buffer); +} + +struct wl_list *buffer_pool_new(struct wl_shm *shm, + uint32_t width, uint32_t height, uint32_t scale) { + struct wl_list *pool = calloc(1, sizeof(struct wl_list)); + if (pool == NULL) { + wlchewing_err("Failed to calloc wl_list for buffer pool"); + return NULL; + } + wl_list_init(pool); + struct wlchewing_buffer *buffer = buffer_new(shm, width, height, scale); + buffer->available = true; + if (buffer == NULL) { + free(pool); + wlchewing_err("Failed to create first buffer for buffer pool"); + return NULL; + } + wl_list_insert(pool, &buffer->link); + return pool; +} + +struct wlchewing_buffer *buffer_pool_get_buffer(struct wl_list *pool) { + struct wlchewing_buffer *cur_buffer; + wl_list_for_each(cur_buffer, pool, link) { + if (cur_buffer->available) { + cur_buffer->available = false; + return cur_buffer; + } + } + printf("new buffer\n"); + struct wlchewing_buffer *new_buffer = buffer_new(cur_buffer->shm, + cur_buffer->width, cur_buffer->height, cur_buffer->scale); + wl_list_insert(&cur_buffer->link, &new_buffer->link); + return new_buffer; +} + +void buffer_pool_destroy(struct wl_list *pool) { + struct wlchewing_buffer *cur_buffer, *tmp; + wl_list_for_each_safe(cur_buffer, tmp, pool, link) { + buffer_destroy(cur_buffer); + } + free(pool); +} diff --git a/buffer.h b/buffer.h new file mode 100644 index 0000000..cf7370f --- /dev/null +++ b/buffer.h @@ -0,0 +1,35 @@ +#ifndef BUFFER_H +#define BUFFER_H + +#include +#include + +struct wlchewing_buffer { + uint32_t width, height; + int32_t scale; + off_t size; + + void *data; + struct wl_buffer *wl_buffer; + cairo_surface_t *cairo_surface; + cairo_t *cairo; + bool available; + + struct wl_list link; + struct wl_shm *shm; +}; + +struct wlchewing_buffer *buffer_new(struct wl_shm *shm, + uint32_t width, uint32_t height, uint32_t scale); + +void buffer_destroy(struct wlchewing_buffer *buffer); + + +struct wl_list *buffer_pool_new(struct wl_shm *shm, + uint32_t width, uint32_t height, uint32_t scale); + +struct wlchewing_buffer *buffer_pool_get_buffer(struct wl_list *pool); + +void buffer_pool_destroy(struct wl_list *pool); + +#endif diff --git a/config.c b/config.c new file mode 100644 index 0000000..e69de29 diff --git a/im.c b/im.c new file mode 100644 index 0000000..8b2a324 --- /dev/null +++ b/im.c @@ -0,0 +1,256 @@ +#include +#include + +#include "wlchewing.h" + +static void noop() { + // no-op +} + +static bool im_key_press(struct wlchewing_state *state, xkb_keysym_t keysym) { + // return false if unhandled, need forwarding + if (xkb_state_mod_name_is_active(state->xkb_state, XKB_MOD_NAME_CTRL, + XKB_STATE_MODS_EFFECTIVE) > 0) { + if (keysym == XKB_KEY_space) { + // Chinese / English(forwarding) toggle + state->forwarding = !state->forwarding; + chewing_Reset(state->chewing); + if (state->bottom_panel) { + bottom_panel_destroy(state->bottom_panel); + state->bottom_panel = NULL; + } + return true; + } + return false; + } + // TODO check other modifiers not used by us + if (state->forwarding) { + return false; + } + if (state->bottom_panel) { + switch(keysym){ + case XKB_KEY_KP_Enter: + case XKB_KEY_Return: + chewing_cand_choose_by_index(state->chewing, + state->bottom_panel->selected_index); + chewing_cand_close(state->chewing); + bottom_panel_destroy(state->bottom_panel); + state->bottom_panel = NULL; + break; + case XKB_KEY_Left: + if (state->bottom_panel->selected_index > 0) { + state->bottom_panel->selected_index--; + bottom_panel_render(state->bottom_panel, + state->chewing); + } + break; + case XKB_KEY_Right: + printf("chewing report %d\n", + chewing_cand_TotalChoice(state->chewing)); + if (state->bottom_panel->selected_index + < chewing_cand_TotalChoice( + state->chewing) - 1) { + state->bottom_panel->selected_index++; + bottom_panel_render(state->bottom_panel, + state->chewing); + } + break; + case XKB_KEY_Up: + chewing_cand_close(state->chewing); + bottom_panel_destroy(state->bottom_panel); + state->bottom_panel = NULL; + break; + case XKB_KEY_Down: + if (chewing_cand_list_has_next(state->chewing)) { + chewing_cand_list_next(state->chewing); + } else { + chewing_cand_list_first(state->chewing); + } + state->bottom_panel->selected_index = 0; + bottom_panel_render(state->bottom_panel, + state->chewing); + break; + case XKB_KEY_space: + default: + return false; + } + } else { + // TODO if chewing preedit is empty, forward bs, del, arrows... + switch(keysym){ + case XKB_KEY_BackSpace: + chewing_handle_Backspace(state->chewing); + break; + case XKB_KEY_Delete: + chewing_handle_Del(state->chewing); + break; + case XKB_KEY_KP_Enter: + case XKB_KEY_Return: + chewing_handle_Enter(state->chewing); + break; + case XKB_KEY_Left: + chewing_handle_Left(state->chewing); + break; + case XKB_KEY_Right: + chewing_handle_Right(state->chewing); + break; + case XKB_KEY_Down: + chewing_cand_open(state->chewing); + if (chewing_cand_TotalChoice(state->chewing)) { + state->bottom_panel = bottom_panel_new(state); + bottom_panel_render(state->bottom_panel, + state->chewing); + } + break; + default: + chewing_handle_Default(state->chewing, + (char)xkb_keysym_to_utf32(keysym)); + } + } + + char *preedit = chewing_buffer_String(state->chewing); + if (!strlen(preedit)) { + free(preedit); + preedit = strdup(chewing_bopomofo_String_static(state->chewing)); + } + else { + preedit = strcat(preedit, chewing_bopomofo_String_static( + state->chewing)); + } + int cursor_begin = chewing_cursor_Current(state->chewing); + zwp_input_method_v2_set_preedit_string(state->input_method, preedit, + cursor_begin, cursor_begin); + if (chewing_commit_Check(state->chewing)) { + zwp_input_method_v2_commit_string(state->input_method, + chewing_commit_String_static(state->chewing)); + } + zwp_input_method_v2_commit(state->input_method, state->serial); + return true; +} + +static void handle_key(void *data, struct zwp_input_method_keyboard_grab_v2 + *zwp_input_method_keyboard_grab_v2, uint32_t serial, uint32_t time, + uint32_t key, uint32_t key_state) { + struct wlchewing_state *state = data; + xkb_keysym_t keysym = xkb_state_key_get_one_sym(state->xkb_state, key + 8); + char keysym_name[64]; + xkb_keysym_get_name(keysym, keysym_name, sizeof(keysym_name)); + printf(" xkb translated to %s\n", keysym_name); + if (key_state == WL_KEYBOARD_KEY_STATE_PRESSED) { + if (!im_key_press(state, keysym)){ + wlchewing_err("Forwarding not yet implemeted"); + // forward, record that future release needs forwarding + } + } else { + // forward if needs forwarding + } +} + +static void handle_modifiers(void *data, struct zwp_input_method_keyboard_grab_v2 + *zwp_input_method_keyboard_grab_v2, uint32_t serial, + uint32_t mods_depressed, uint32_t mods_latched, + uint32_t mods_locked, uint32_t group) { + struct wlchewing_state *state = data; + xkb_state_update_mask(state->xkb_state, mods_depressed, mods_latched, + mods_locked, 0, 0, group); + // forward modifiers +} + +static void handle_keymap(void *data, struct zwp_input_method_keyboard_grab_v2 + *zwp_input_method_keyboard_grab_v2, uint32_t format, + int32_t fd, uint32_t size) { + struct wlchewing_state *state = data; + char *keymap_string = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + struct xkb_keymap *keymap = xkb_keymap_new_from_string( + state->xkb_context, keymap_string, XKB_KEYMAP_FORMAT_TEXT_V1, + XKB_KEYMAP_COMPILE_NO_FLAGS); + munmap(keymap_string, size); + close(fd); + xkb_state_unref(state->xkb_state); + state->xkb_state = xkb_state_new(keymap); + xkb_keymap_unref(keymap); + // forward keymap +} + +static const struct zwp_input_method_keyboard_grab_v2_listener grab_listener = { + .key = handle_key, + .modifiers = handle_modifiers, + .keymap = handle_keymap, + .repeat_info = noop, // TODO +}; + +static void handle_activate(void *data, + struct zwp_input_method_v2 *zwp_input_method_v2) { + struct wlchewing_state *state = data; + state->pending_activate = true; +} + +static void handle_deactivate(void *data, + struct zwp_input_method_v2 *zwp_input_method_v2) { + struct wlchewing_state *state = data; + state->pending_activate = false; +} + +static void handle_unavailable(void *data, + struct zwp_input_method_v2 *zwp_input_method_v2) { + struct wlchewing_state *state = data; + wlchewing_err("IM unavailable"); + im_destory(state); + exit(EXIT_FAILURE); +} + +static void handle_done(void *data, + struct zwp_input_method_v2 *zwp_input_method_v2) { + struct wlchewing_state *state = data; + state->serial++; + if (state->pending_activate && !state->activated) { + state->kb_grab = zwp_input_method_v2_grab_keyboard( + state->input_method); + zwp_input_method_keyboard_grab_v2_add_listener(state->kb_grab, + &grab_listener, state); + + if (!state->kb_grab) { + wlchewing_err("Failed to grab"); + exit(EXIT_FAILURE); + } + } else if (!state->pending_activate && state->activated) { + zwp_input_method_keyboard_grab_v2_release(state->kb_grab); + state->kb_grab = NULL; + if (state->bottom_panel) { + bottom_panel_destroy(state->bottom_panel); + state->bottom_panel = NULL; + } + chewing_Reset(state->chewing); + } + state->activated = state->pending_activate; +} + +static const struct zwp_input_method_v2_listener im_listener = { + .activate = handle_activate, + .deactivate = handle_deactivate, + .surrounding_text = noop, + .text_change_cause = noop, + .content_type = noop, + .done = handle_done, + .unavailable = handle_unavailable, +}; + +void im_setup(struct wlchewing_state *state) { + state->input_method = zwp_input_method_manager_v2_get_input_method( + state->input_method_manager, state->seat); + zwp_input_method_v2_add_listener(state->input_method, &im_listener, state); + + state->chewing = chewing_new(); + state->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + wl_display_roundtrip(state->display); +} + +void im_destory(struct wlchewing_state *state) { + chewing_delete(state->chewing); + xkb_state_unref(state->xkb_state); + xkb_context_unref(state->xkb_context); + zwp_input_method_v2_destroy(state->input_method); + if (state->bottom_panel) { + bottom_panel_destroy(state->bottom_panel); + state->bottom_panel = NULL; + } +} diff --git a/input-method-unstable-v2.xml b/input-method-unstable-v2.xml new file mode 100644 index 0000000..62be9d9 --- /dev/null +++ b/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/main.c b/main.c new file mode 100644 index 0000000..359114d --- /dev/null +++ b/main.c @@ -0,0 +1,88 @@ +#include + +#include "wlchewing.h" +#include "bottom-panel.h" + +static const struct wl_output_listener output_listener; + +static void handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) { + struct wlchewing_state *state = data; + 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); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + state->seat = wl_registry_bind(registry, name, &wl_seat_interface, version); + } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { + state->layer_shell = wl_registry_bind(registry, name, + &zwlr_layer_shell_v1_interface, 1); + } else if (strcmp(interface, wl_shm_interface.name) == 0) { + state->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); + } else if (strcmp(interface, wl_compositor_interface.name) == 0) { + state->compositor = wl_registry_bind(registry, name, + &wl_compositor_interface, 4); + } else if (strcmp(interface, wl_output_interface.name) == 0) { + struct wl_output *output = wl_registry_bind(registry, name, + &wl_output_interface, 3); + wl_output_add_listener(output, &output_listener, NULL); + } +} + +static void noop() { + // no-op +} + +static const struct wl_registry_listener registry_listener = { + .global = handle_global, + .global_remove = noop, +}; + +static void output_scale(void *data, struct wl_output *output, int32_t scale) { + int32_t *user_data = malloc(sizeof(int32_t)); + assert(user_data); + *user_data = scale; + wl_output_set_user_data(output, user_data); +} + +static const struct wl_output_listener output_listener = { + .scale = output_scale, + .geometry = noop, + .mode = noop, + .done = noop +}; + +int main(int argc, char **argv) { + struct wlchewing_state *state = calloc(1, + sizeof(struct wlchewing_state)); + if (state == NULL) { + wlchewing_err("Failed to calloc state"); + return EXIT_FAILURE; + } + state->display = wl_display_connect(NULL); + if (state->display == NULL) { + wlchewing_err("Failed to create display"); + return EXIT_FAILURE; + } + + struct wl_registry *registry = wl_display_get_registry(state->display); + wl_registry_add_listener(registry, ®istry_listener, state); + wl_display_dispatch(state->display); + wl_display_roundtrip(state->display); + + if (state->layer_shell == NULL || state->shm == NULL + || state->compositor == NULL + || state->input_method_manager == NULL) { + /* TODO for adding panel with input-method support + * currently only panel with wlr-layer-shell + */ + wlchewing_err("Required wayland interface not available"); + return EXIT_FAILURE; + } + + im_setup(state); + + while (wl_display_dispatch(state->display) != -1) { + // dispatch loop + } + return EXIT_SUCCESS; +} diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..a397c6e --- /dev/null +++ b/meson.build @@ -0,0 +1,33 @@ +project('wlchewing-experiments',['c']) + +wl_client = dependency('wayland-client') +wl_protocols = dependency('wayland-protocols') +cairo = dependency('cairo') +pangocairo = dependency('pangocairo') +chewing = dependency('chewing') +xkbcommon = dependency('xkbcommon') +cc = meson.get_compiler('c') +rt = cc.find_library('rt', required: false) +scanner = find_program('wayland-scanner') +scanner_private_code = generator(scanner, output: '@BASENAME@-protocol.c', arguments: ['private-code', '@INPUT@', '@OUTPUT@']) +scanner_client_header = generator(scanner, output: '@BASENAME@-client-protocol.h', arguments: ['client-header', '@INPUT@', '@OUTPUT@']) + +wl_protocols_dir = wl_protocols.get_pkgconfig_variable('pkgdatadir') +xdg_shell = wl_protocols_dir + '/stable/xdg-shell/xdg-shell.xml' +text_input_path = wl_protocols_dir + '/unstable/text-input/text-input-unstable-v3.xml' +protocols_src=[ + scanner_private_code.process('input-method-unstable-v2.xml'), + scanner_private_code.process(text_input_path), + scanner_private_code.process('wlr-layer-shell-unstable-v1.xml'), + scanner_private_code.process(xdg_shell) +] +protocols_headers=[ + scanner_client_header.process('input-method-unstable-v2.xml'), + scanner_client_header.process(text_input_path), + scanner_client_header.process('wlr-layer-shell-unstable-v1.xml'), + scanner_client_header.process(xdg_shell) +] +lib_protocols = static_library('protocols', protocols_src + protocols_headers, dependencies: wl_client) +protocols_dep = declare_dependency(link_with: lib_protocols, sources: protocols_headers) + +executable('wlchewing', ['main.c', 'config.c', 'bottom-panel.c', 'buffer.c', 'im.c'], dependencies: [chewing, xkbcommon, cairo, pangocairo, protocols_dep, rt], install: true) diff --git a/wlchewing.h b/wlchewing.h new file mode 100644 index 0000000..9c9961f --- /dev/null +++ b/wlchewing.h @@ -0,0 +1,45 @@ +#ifndef WLCHEWING_H +#define WLCHEWING_H + +#include +#include +#include +#include +#include +#include + +#include "bottom-panel.h" +#include "input-method-unstable-v2-client-protocol.h" +#include "text-input-unstable-v3-client-protocol.h" + +struct wlchewing_state { + struct wl_display *display; + struct wl_compositor *compositor; + struct wl_shm *shm; + struct wl_seat *seat; + + struct zwp_input_method_manager_v2 *input_method_manager; + struct zwp_input_method_v2 *input_method; + struct zwp_input_method_keyboard_grab_v2 *kb_grab; + bool pending_activate; + bool activated; + + struct zwlr_layer_shell_v1 *layer_shell; + struct wlchewing_bottom_panel *bottom_panel; + + ChewingContext *chewing; + bool forwarding; + + struct xkb_context *xkb_context; + struct xkb_state *xkb_state; + + int32_t serial; +}; + +void im_setup(struct wlchewing_state *state); + +void im_destory(struct wlchewing_state *state); + +#define wlchewing_err(fmt, ...) fprintf(stderr, "[%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__) + +#endif diff --git a/wlr-layer-shell-unstable-v1.xml b/wlr-layer-shell-unstable-v1.xml new file mode 100644 index 0000000..adc6a17 --- /dev/null +++ b/wlr-layer-shell-unstable-v1.xml @@ -0,0 +1,301 @@ + + + + Copyright © 2017 Drew DeVault + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + Clients can use this interface to assign the surface_layer role to + wl_surfaces. Such surfaces are assigned to a "layer" of the output and + rendered with a defined z-depth respective to each other. They may also be + anchored to the edges and corners of a screen and specify input handling + semantics. This interface should be suitable for the implementation of + many desktop shell components, and a broad number of other applications + that interact with the desktop. + + + + + Create a layer surface for an existing surface. This assigns the role of + layer_surface, or raises a protocol error if another role is already + assigned. + + Creating a layer surface from a wl_surface which has a buffer attached + or committed is a client error, and any attempts by a client to attach + or manipulate a buffer prior to the first layer_surface.configure call + must also be treated as errors. + + You may pass NULL for output to allow the compositor to decide which + output to use. Generally this will be the one that the user most + recently interacted with. + + Clients can specify a namespace that defines the purpose of the layer + surface. + + + + + + + + + + + + + + + + + These values indicate which layers a surface can be rendered in. They + are ordered by z depth, bottom-most first. Traditional shell surfaces + will typically be rendered between the bottom and top layers. + Fullscreen shell surfaces are typically rendered at the top layer. + Multiple surfaces can share a single layer, and ordering within a + single layer is undefined. + + + + + + + + + + + + An interface that may be implemented by a wl_surface, for surfaces that + are designed to be rendered as a layer of a stacked desktop-like + environment. + + Layer surface state (layer, size, anchor, exclusive zone, + margin, interactivity) is double-buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + + + + Sets the size of the surface in surface-local coordinates. The + compositor will display the surface centered with respect to its + anchors. + + If you pass 0 for either value, the compositor will assign it and + inform you of the assignment in the configure event. You must set your + anchor to opposite edges in the dimensions you omit; not doing so is a + protocol error. Both values are 0 by default. + + Size is double-buffered, see wl_surface.commit. + + + + + + + + Requests that the compositor anchor the surface to the specified edges + and corners. If two orthogonal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left corner of the output); otherwise the anchor point + will be centered on that edge, or in the center if none is specified. + + Anchor is double-buffered, see wl_surface.commit. + + + + + + + Requests that the compositor avoids occluding an area with other + surfaces. The compositor's use of this information is + implementation-dependent - do not assume that this region will not + actually be occluded. + + A positive value is only meaningful if the surface is anchored to one + edge or an edge and both perpendicular edges. If the surface is not + anchored, anchored to only two perpendicular edges (a corner), anchored + to only two parallel edges or anchored to all edges, a positive value + will be treated the same as zero. + + A positive zone is the distance from the edge in surface-local + coordinates to consider exclusive. + + Surfaces that do not wish to have an exclusive zone may instead specify + how they should interact with surfaces that do. If set to zero, the + surface indicates that it would like to be moved to avoid occluding + surfaces with a positive exclusive zone. If set to -1, the surface + indicates that it would not like to be moved to accommodate for other + surfaces, and the compositor should extend it all the way to the edges + it is anchored to. + + For example, a panel might set its exclusive zone to 10, so that + maximized shell surfaces are not shown on top of it. A notification + might set its exclusive zone to 0, so that it is moved to avoid + occluding the panel, but shell surfaces are shown underneath it. A + wallpaper or lock screen might set their exclusive zone to -1, so that + they stretch below or over the panel. + + The default value is 0. + + Exclusive zone is double-buffered, see wl_surface.commit. + + + + + + + Requests that the surface be placed some distance away from the anchor + point on the output, in surface-local coordinates. Setting this value + for edges you are not anchored to has no effect. + + The exclusive zone includes the margin. + + Margin is double-buffered, see wl_surface.commit. + + + + + + + + + + Set to 1 to request that the seat send keyboard events to this layer + surface. For layers below the shell surface layer, the seat will use + normal focus semantics. For layers above the shell surface layers, the + seat will always give exclusive keyboard focus to the top-most layer + which has keyboard interactivity set to true. + + Layer surfaces receive pointer, touch, and tablet events normally. If + you do not want to receive them, set the input region on your surface + to an empty region. + + Events is double-buffered, see wl_surface.commit. + + + + + + + This assigns an xdg_popup's parent to this layer_surface. This popup + should have been created via xdg_surface::get_popup with the parent set + to NULL, and this request must be invoked before committing the popup's + initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + + + + + + This request destroys the layer surface. + + + + + + The configure event asks the client to resize its surface. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + The client is free to dismiss all but the last configure event it + received. + + The width and height arguments specify the size of the window in + surface-local coordinates. + + The size is a hint, in the sense that the client is free to ignore it if + it doesn't resize, pick a smaller size (to satisfy aspect ratio or + resize in steps of NxM pixels). If the client picks a smaller size and + is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the + surface will be centered on this axis. + + If the width or height arguments are zero, it means the client should + decide its own window dimension. + + + + + + + + + The closed event is sent by the compositor when the surface will no + longer be shown. The output may have been destroyed or the user may + have asked for it to be removed. Further changes to the surface will be + ignored. The client should destroy the resource after receiving this + event, and create a new surface if they so choose. + + + + + + + + + + + + + + + + + + + + + Change the layer that the surface is rendered on. + + Layer is double-buffered, see wl_surface.commit. + + + + +