diff --git a/meson.build b/meson.build index 6939ac4..565c4ef 100644 --- a/meson.build +++ b/meson.build @@ -69,6 +69,7 @@ endif executable('maomao', 'src/maomao.c', 'src/util.c', + 'src/mem.c', wayland_sources, dependencies : [ libm, diff --git a/src/ime.h b/src/ime.h new file mode 100644 index 0000000..bddec24 --- /dev/null +++ b/src/ime.h @@ -0,0 +1,821 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Based on labwc (https://github.com/labwc/labwc) */ + + +#ifndef LABWC_IME_H +#define LABWC_IME_H + +#include +#include +#include +#include "mem.h" + +#define LAB_SET_MAX_SIZE 16 + +#define SAME_CLIENT(wlr_obj1, wlr_obj2) \ + (wl_resource_get_client((wlr_obj1)->resource) \ + == wl_resource_get_client((wlr_obj2)->resource)) + +struct lab_set { + uint32_t values[LAB_SET_MAX_SIZE]; + int size; +}; + +/* + * The relay structure manages the relationship between text-inputs and + * input-method on a given seat. Multiple text-inputs may be bound to a relay, + * but at most one will be "active" (communicating with input-method) at a time. + * At most one input-method may be bound to the seat. When an input-method and + * an active text-input is present, the relay passes messages between them. + */ +struct input_method_relay { + struct wl_list text_inputs; /* struct text_input.link */ + struct wlr_input_method_v2 *input_method; + struct wlr_surface *focused_surface; + + struct lab_set forwarded_pressed_keys; + struct wlr_keyboard_modifiers forwarded_modifiers; + + /* + * Text-input which is enabled by the client and communicating with + * input-method. + * This must be NULL if input-method is not present. + * Its client must be the same as that of focused_surface. + */ + struct text_input *active_text_input; + + struct wl_list popups; /* input_method_popup.link */ + struct wlr_scene_tree *popup_tree; + + struct wl_listener new_text_input; + struct wl_listener new_input_method; + + struct wl_listener input_method_commit; + struct wl_listener input_method_grab_keyboard; + struct wl_listener input_method_destroy; + struct wl_listener input_method_new_popup_surface; + + struct wl_listener keyboard_grab_destroy; + struct wl_listener focused_surface_destroy; +}; + +struct input_method_popup { + struct wlr_input_popup_surface_v2 *popup_surface; + struct wlr_scene_tree *tree; + struct wlr_scene_tree *scene_surface; + struct input_method_relay *relay; + struct wl_list link; /* input_method_relay.popups */ + + struct wl_listener destroy; + struct wl_listener commit; +}; + +struct text_input { + struct input_method_relay *relay; + struct wlr_text_input_v3 *input; + struct wl_list link; + + struct wl_listener enable; + struct wl_listener commit; + struct wl_listener disable; + struct wl_listener destroy; +}; + +struct wlr_input_method_manager_v2 *input_method_manager; +struct wlr_text_input_manager_v3 *text_input_manager; +struct input_method_relay *input_method_relay; + +/* + * Forward key event to keyboard grab of the seat from the keyboard + * if the keyboard grab exists. + * Returns true if the key event was forwarded. + */ +bool input_method_keyboard_grab_forward_key(KeyboardGroup *keyboard, + struct wlr_keyboard_key_event *event); + +/* + * Forward modifier state to keyboard grab of the seat from the keyboard + * if the keyboard grab exists. + * Returns true if the modifier state was forwarded. + */ +bool input_method_keyboard_grab_forward_modifiers(KeyboardGroup *keyboard); + +struct input_method_relay *input_method_relay_create(); + +void input_method_relay_finish(struct input_method_relay *relay); + +/* Updates currently focused surface. Surface must belong to the same seat. */ +void input_method_relay_set_focus(struct input_method_relay *relay, + struct wlr_surface *surface); + + + +bool +lab_set_contains(struct lab_set *set, uint32_t value) +{ + for (int i = 0; i < set->size; ++i) { + if (set->values[i] == value) { + return true; + } + } + return false; +} + +void +lab_set_add(struct lab_set *set, uint32_t value) +{ + if (lab_set_contains(set, value)) { + return; + } + if (set->size >= LAB_SET_MAX_SIZE) { + wlr_log(WLR_ERROR, "lab_set size exceeded"); + return; + } + set->values[set->size++] = value; +} + +void +lab_set_remove(struct lab_set *set, uint32_t value) +{ + for (int i = 0; i < set->size; ++i) { + if (set->values[i] == value) { + --set->size; + set->values[i] = set->values[set->size]; + return; + } + } +} + + +Monitor * +output_from_wlr_output(struct wlr_output *wlr_output) +{ + Monitor *m; + wl_list_for_each(m, &mons, link) { + if (m->wlr_output == wlr_output) { + return m; + } + } + return NULL; +} + +Monitor * +output_nearest_to(int lx, int ly) +{ + double closest_x, closest_y; + wlr_output_layout_closest_point(output_layout, NULL, lx, ly, + &closest_x, &closest_y); + + return output_from_wlr_output( + wlr_output_layout_output_at(output_layout, + closest_x, closest_y)); +} + +bool +output_is_usable(Monitor *m) +{ + /* output_is_usable(NULL) is safe and returns false */ + return m && m->wlr_output->enabled; +} + + +static bool +is_keyboard_emulated_by_input_method(struct wlr_keyboard *keyboard, + struct wlr_input_method_v2 *input_method) +{ + if (!keyboard || !input_method) { + return false; + } + + struct wlr_virtual_keyboard_v1 *virtual_keyboard = + wlr_input_device_get_virtual_keyboard(&keyboard->base); + + return virtual_keyboard && SAME_CLIENT(virtual_keyboard, input_method); +} + +/* + * Get keyboard grab of the seat from keyboard if we should forward events + * to it. + */ +static struct wlr_input_method_keyboard_grab_v2 * +get_keyboard_grab(KeyboardGroup *keyboard) +{ + struct wlr_input_method_v2 *input_method = + input_method_relay->input_method; + if (!input_method || !input_method->keyboard_grab) { + return NULL; + } + + /* + * Input-methods often use virtual keyboard to send raw key signals + * instead of sending encoded text via set_preedit_string and + * commit_string. We should not forward those key events back to the + * input-method so key events don't loop between the compositor and + * the input-method. + */ + if (is_keyboard_emulated_by_input_method( + &keyboard->wlr_group->keyboard, input_method)) { + return NULL; + } + + return input_method->keyboard_grab; +} + +bool +input_method_keyboard_grab_forward_modifiers(KeyboardGroup *keyboard) +{ + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = + get_keyboard_grab(keyboard); + + struct wlr_keyboard_modifiers *forwarded_modifiers = + &input_method_relay->forwarded_modifiers; + struct wlr_keyboard_modifiers *modifiers = + &keyboard->wlr_group->keyboard.modifiers; + + if (forwarded_modifiers->depressed == modifiers->depressed + && forwarded_modifiers->latched == modifiers->latched + && forwarded_modifiers->locked == modifiers->locked + && forwarded_modifiers->group == modifiers->group) { + return false; + } + + if (keyboard_grab) { + *forwarded_modifiers = keyboard->wlr_group->keyboard.modifiers; + wlr_input_method_keyboard_grab_v2_set_keyboard(keyboard_grab, + &keyboard->wlr_group->keyboard); + wlr_input_method_keyboard_grab_v2_send_modifiers(keyboard_grab, + modifiers); + return true; + } else { + return false; + } +} + +bool +input_method_keyboard_grab_forward_key(KeyboardGroup *keyboard, + struct wlr_keyboard_key_event *event) +{ + /* + * We should not forward key-release events without corresponding + * key-press events forwarded + */ + struct lab_set *pressed_keys = + &input_method_relay->forwarded_pressed_keys; + if (event->state == WL_KEYBOARD_KEY_STATE_RELEASED + && !lab_set_contains(pressed_keys, event->keycode)) { + return false; + } + + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = + get_keyboard_grab(keyboard); + if (keyboard_grab) { + if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + lab_set_add(pressed_keys, event->keycode); + } else { + lab_set_remove(pressed_keys, event->keycode); + } + wlr_input_method_keyboard_grab_v2_set_keyboard(keyboard_grab, + &keyboard->wlr_group->keyboard); + wlr_input_method_keyboard_grab_v2_send_key(keyboard_grab, + event->time_msec, event->keycode, event->state); + return true; + } else { + return false; + } +} + +/* + * update_text_inputs_focused_surface() should be called beforehand to set + * right text-inputs to choose from. + */ +static struct text_input * +get_active_text_input(struct input_method_relay *relay) +{ + if (!relay->input_method) { + return NULL; + } + struct text_input *text_input; + wl_list_for_each(text_input, &relay->text_inputs, link) { + if (text_input->input->focused_surface + && text_input->input->current_enabled) { + return text_input; + } + } + return NULL; +} + +/* + * Updates active text-input and activates/deactivates the input-method if the + * value is changed. + */ +static void +update_active_text_input(struct input_method_relay *relay) +{ + struct text_input *active_text_input = get_active_text_input(relay); + + if (relay->input_method && relay->active_text_input + != active_text_input) { + if (active_text_input) { + wlr_input_method_v2_send_activate(relay->input_method); + } else { + wlr_input_method_v2_send_deactivate(relay->input_method); + } + wlr_input_method_v2_send_done(relay->input_method); + } + + relay->active_text_input = active_text_input; +} + +/* + * Updates focused surface of text-inputs and sends enter/leave events to + * the text-inputs whose focused surface is changed. + * When input-method is present, text-inputs whose client is the same as the + * relay's focused surface also have that focused surface. Clients can then + * send enable request on a text-input which has the focused surface to make + * the text-input active and start communicating with input-method. + */ +static void +update_text_inputs_focused_surface(struct input_method_relay *relay) +{ + struct text_input *text_input; + wl_list_for_each(text_input, &relay->text_inputs, link) { + struct wlr_text_input_v3 *input = text_input->input; + + struct wlr_surface *new_focused_surface; + if (relay->input_method && relay->focused_surface + && SAME_CLIENT(input, relay->focused_surface)) { + new_focused_surface = relay->focused_surface; + } else { + new_focused_surface = NULL; + } + + if (input->focused_surface == new_focused_surface) { + continue; + } + if (input->focused_surface) { + wlr_text_input_v3_send_leave(input); + } + if (new_focused_surface) { + wlr_text_input_v3_send_enter(input, new_focused_surface); + } + } +} + +static void +update_popup_position(struct input_method_popup *popup) +{ + struct input_method_relay *relay = popup->relay; + struct text_input *text_input = relay->active_text_input; + + if (!text_input || !relay->focused_surface + || !popup->popup_surface->surface->mapped) { + return; + } + + struct wlr_box cursor_rect; + struct wlr_xdg_surface *xdg_surface = + wlr_xdg_surface_try_from_wlr_surface(relay->focused_surface); + struct wlr_layer_surface_v1 *layer_surface = + wlr_layer_surface_v1_try_from_wlr_surface(relay->focused_surface); + + if ((text_input->input->current.features + & WLR_TEXT_INPUT_V3_FEATURE_CURSOR_RECTANGLE) + && (xdg_surface || layer_surface)) { + cursor_rect = text_input->input->current.cursor_rectangle; + + /* + * wlr_surface->data is: + * - for XDG surfaces: view->content_tree + * - for layer surfaces: lab_layer_surface->scene_layer_surface->tree + * - for layer popups: lab_layer_popup->scene_tree + */ + struct wlr_scene_tree *tree = relay->focused_surface->data; + int lx, ly; + wlr_scene_node_coords(&tree->node, &lx, &ly); + cursor_rect.x += lx; + cursor_rect.y += ly; + + if (xdg_surface) { + /* Take into account invisible xdg-shell CSD borders */ + cursor_rect.x -= xdg_surface->geometry.x; + cursor_rect.y -= xdg_surface->geometry.y; + } + } else { + cursor_rect = (struct wlr_box){0}; + } + + Monitor *output = + output_nearest_to(cursor_rect.x, cursor_rect.y); + if (!output_is_usable(output)) { + wlr_log(WLR_ERROR, + "Cannot position IME popup (unusable output)"); + return; + } + struct wlr_box output_box; + wlr_output_layout_get_box( + output_layout, output->wlr_output, &output_box); + + /* Use xdg-positioner utilities to position popup */ + struct wlr_xdg_positioner_rules rules = { + .anchor_rect = cursor_rect, + .anchor = XDG_POSITIONER_ANCHOR_BOTTOM_LEFT, + .gravity = XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT, + .size = { + .width = popup->popup_surface->surface->current.width, + .height = popup->popup_surface->surface->current.height, + }, + .constraint_adjustment = + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y + | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X, + }; + + struct wlr_box popup_box; + wlr_xdg_positioner_rules_get_geometry(&rules, &popup_box); + wlr_xdg_positioner_rules_unconstrain_box(&rules, &output_box, &popup_box); + + wlr_scene_node_set_position( + &popup->tree->node, popup_box.x, popup_box.y); + /* Make sure IME popups are always on top, above layer-shell surfaces */ + wlr_scene_node_raise_to_top(&relay->popup_tree->node); + + wlr_input_popup_surface_v2_send_text_input_rectangle( + popup->popup_surface, &(struct wlr_box){ + .x = cursor_rect.x - popup_box.x, + .y = cursor_rect.y - popup_box.y, + .width = cursor_rect.width, + .height = cursor_rect.height, + }); +} + +static void +update_popups_position(struct input_method_relay *relay) +{ + struct input_method_popup *popup; + wl_list_for_each(popup, &relay->popups, link) { + update_popup_position(popup); + } +} + +static void +handle_input_method_commit(struct wl_listener *listener, void *data) +{ + struct input_method_relay *relay = + wl_container_of(listener, relay, input_method_commit); + struct wlr_input_method_v2 *input_method = data; + assert(relay->input_method == input_method); + + struct text_input *text_input = relay->active_text_input; + if (!text_input) { + return; + } + + if (input_method->current.preedit.text) { + wlr_text_input_v3_send_preedit_string( + text_input->input, + input_method->current.preedit.text, + input_method->current.preedit.cursor_begin, + input_method->current.preedit.cursor_end); + } + if (input_method->current.commit_text) { + wlr_text_input_v3_send_commit_string( + text_input->input, + input_method->current.commit_text); + } + if (input_method->current.delete.before_length + || input_method->current.delete.after_length) { + wlr_text_input_v3_send_delete_surrounding_text( + text_input->input, + input_method->current.delete.before_length, + input_method->current.delete.after_length); + } + wlr_text_input_v3_send_done(text_input->input); +} + +static void +handle_keyboard_grab_destroy(struct wl_listener *listener, void *data) +{ + struct input_method_relay *relay = + wl_container_of(listener, relay, keyboard_grab_destroy); + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = data; + wl_list_remove(&relay->keyboard_grab_destroy.link); + + if (keyboard_grab->keyboard) { + /* Send modifier state to original client */ + wlr_seat_keyboard_notify_modifiers( + keyboard_grab->input_method->seat, + &keyboard_grab->keyboard->modifiers); + } +} + +static void +handle_input_method_grab_keyboard(struct wl_listener *listener, void *data) +{ + struct input_method_relay *relay = wl_container_of(listener, relay, + input_method_grab_keyboard); + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = data; + + struct wlr_keyboard *active_keyboard = + wlr_seat_get_keyboard(seat); + + if (!is_keyboard_emulated_by_input_method( + active_keyboard, relay->input_method)) { + /* Send modifier state to grab */ + wlr_input_method_keyboard_grab_v2_set_keyboard( + keyboard_grab, active_keyboard); + } + + relay->forwarded_pressed_keys = (struct lab_set){0}; + relay->forwarded_modifiers = (struct wlr_keyboard_modifiers){0}; + + relay->keyboard_grab_destroy.notify = handle_keyboard_grab_destroy; + wl_signal_add(&keyboard_grab->events.destroy, + &relay->keyboard_grab_destroy); +} + +static void +handle_input_method_destroy(struct wl_listener *listener, void *data) +{ + struct input_method_relay *relay = + wl_container_of(listener, relay, input_method_destroy); + assert(relay->input_method == data); + wl_list_remove(&relay->input_method_commit.link); + wl_list_remove(&relay->input_method_grab_keyboard.link); + wl_list_remove(&relay->input_method_new_popup_surface.link); + wl_list_remove(&relay->input_method_destroy.link); + relay->input_method = NULL; + + update_text_inputs_focused_surface(relay); + update_active_text_input(relay); +} + +static void +handle_popup_surface_destroy(struct wl_listener *listener, void *data) +{ + struct input_method_popup *popup = + wl_container_of(listener, popup, destroy); + wlr_scene_node_destroy(&popup->tree->node); + wl_list_remove(&popup->destroy.link); + wl_list_remove(&popup->commit.link); + wl_list_remove(&popup->link); + free(popup); +} + +static void +handle_popup_surface_commit(struct wl_listener *listener, void *data) +{ + struct input_method_popup *popup = + wl_container_of(listener, popup, commit); + update_popup_position(popup); +} + +static void +handle_input_method_new_popup_surface(struct wl_listener *listener, void *data) +{ + struct input_method_relay *relay = wl_container_of(listener, relay, + input_method_new_popup_surface); + + struct input_method_popup *popup = znew(*popup); + popup->popup_surface = data; + popup->relay = relay; + + popup->destroy.notify = handle_popup_surface_destroy; + wl_signal_add(&popup->popup_surface->events.destroy, &popup->destroy); + + popup->commit.notify = handle_popup_surface_commit; + wl_signal_add(&popup->popup_surface->surface->events.commit, + &popup->commit); + + // popup->tree = wlr_scene_subsurface_tree_create( + // relay->popup_tree, popup->popup_surface->surface); + // node_descriptor_create(&popup->tree->node, LAB_NODE_DESC_IME_POPUP, NULL); + + popup->tree = wlr_scene_tree_create(layers[LyrIMPopup]); + popup->scene_surface = wlr_scene_subsurface_tree_create(popup->tree,popup->popup_surface->surface); + //popup->scene_surface = &wlr_scene_subsurface_tree_create(popup->scene->parent,popup->popup_surface->surface)->node; + //popup->scene_surface->data = popup; + popup->scene_surface->node.data = popup; + + wl_list_insert(&relay->popups, &popup->link); + + update_popup_position(popup); +} + +static void +handle_new_input_method(struct wl_listener *listener, void *data) +{ + struct input_method_relay *relay = + wl_container_of(listener, relay, new_input_method); + struct wlr_input_method_v2 *input_method = data; + if (seat != input_method->seat) { + return; + } + + if (relay->input_method) { + wlr_log(WLR_INFO, + "Attempted to connect second input method to a seat"); + wlr_input_method_v2_send_unavailable(input_method); + return; + } + + relay->input_method = input_method; + + relay->input_method_commit.notify = handle_input_method_commit; + wl_signal_add(&relay->input_method->events.commit, + &relay->input_method_commit); + + relay->input_method_grab_keyboard.notify = + handle_input_method_grab_keyboard; + wl_signal_add(&relay->input_method->events.grab_keyboard, + &relay->input_method_grab_keyboard); + + relay->input_method_destroy.notify = handle_input_method_destroy; + wl_signal_add(&relay->input_method->events.destroy, + &relay->input_method_destroy); + + relay->input_method_new_popup_surface.notify = + handle_input_method_new_popup_surface; + wl_signal_add(&relay->input_method->events.new_popup_surface, + &relay->input_method_new_popup_surface); + + update_text_inputs_focused_surface(relay); + update_active_text_input(relay); +} + +/* Conveys state from active text-input to input-method */ +static void +send_state_to_input_method(struct input_method_relay *relay) +{ + assert(relay->active_text_input && relay->input_method); + + struct wlr_input_method_v2 *input_method = relay->input_method; + struct wlr_text_input_v3 *input = relay->active_text_input->input; + + /* TODO: only send each of those if they were modified */ + if (input->active_features + & WLR_TEXT_INPUT_V3_FEATURE_SURROUNDING_TEXT) { + wlr_input_method_v2_send_surrounding_text(input_method, + input->current.surrounding.text, + input->current.surrounding.cursor, + input->current.surrounding.anchor); + } + wlr_input_method_v2_send_text_change_cause(input_method, + input->current.text_change_cause); + if (input->active_features & WLR_TEXT_INPUT_V3_FEATURE_CONTENT_TYPE) { + wlr_input_method_v2_send_content_type(input_method, + input->current.content_type.hint, + input->current.content_type.purpose); + } + wlr_input_method_v2_send_done(input_method); +} + +static void +handle_text_input_enable(struct wl_listener *listener, void *data) +{ + struct text_input *text_input = + wl_container_of(listener, text_input, enable); + struct input_method_relay *relay = text_input->relay; + + update_active_text_input(relay); + if (relay->active_text_input == text_input) { + update_popups_position(relay); + send_state_to_input_method(relay); + } +} + +static void +handle_text_input_disable(struct wl_listener *listener, void *data) +{ + struct text_input *text_input = + wl_container_of(listener, text_input, disable); + /* + * When the focus is moved between surfaces from different clients and + * then the old client sends "disable" event, the relay ignores it and + * doesn't deactivate the input-method. + */ + update_active_text_input(text_input->relay); +} + +static void +handle_text_input_commit(struct wl_listener *listener, void *data) +{ + struct text_input *text_input = + wl_container_of(listener, text_input, commit); + struct input_method_relay *relay = text_input->relay; + + if (relay->active_text_input == text_input) { + update_popups_position(relay); + send_state_to_input_method(relay); + } +} + +static void +handle_text_input_destroy(struct wl_listener *listener, void *data) +{ + struct text_input *text_input = + wl_container_of(listener, text_input, destroy); + wl_list_remove(&text_input->enable.link); + wl_list_remove(&text_input->disable.link); + wl_list_remove(&text_input->commit.link); + wl_list_remove(&text_input->destroy.link); + wl_list_remove(&text_input->link); + update_active_text_input(text_input->relay); + free(text_input); +} + +static void +handle_new_text_input(struct wl_listener *listener, void *data) +{ + struct input_method_relay *relay = + wl_container_of(listener, relay, new_text_input); + struct wlr_text_input_v3 *wlr_text_input = data; + if (seat != wlr_text_input->seat) { + return; + } + + struct text_input *text_input = znew(*text_input); + text_input->input = wlr_text_input; + text_input->relay = relay; + wl_list_insert(&relay->text_inputs, &text_input->link); + + text_input->enable.notify = handle_text_input_enable; + wl_signal_add(&text_input->input->events.enable, &text_input->enable); + + text_input->disable.notify = handle_text_input_disable; + wl_signal_add(&text_input->input->events.disable, &text_input->disable); + + text_input->commit.notify = handle_text_input_commit; + wl_signal_add(&text_input->input->events.commit, &text_input->commit); + + text_input->destroy.notify = handle_text_input_destroy; + wl_signal_add(&text_input->input->events.destroy, &text_input->destroy); + + update_text_inputs_focused_surface(relay); +} + +/* + * Usually this function is not called because the client destroys the surface + * role (like xdg_toplevel) first and input_method_relay_set_focus() is called + * before wl_surface is destroyed. + */ +static void +handle_focused_surface_destroy(struct wl_listener *listener, void *data) +{ + struct input_method_relay *relay = + wl_container_of(listener, relay, focused_surface_destroy); + assert(relay->focused_surface == data); + + input_method_relay_set_focus(relay, NULL); +} + +struct input_method_relay * +input_method_relay_create() +{ + struct input_method_relay *relay = znew(*relay); + wl_list_init(&relay->text_inputs); + wl_list_init(&relay->popups); + relay->popup_tree = wlr_scene_tree_create(&scene->tree); + + relay->new_text_input.notify = handle_new_text_input; + wl_signal_add(&text_input_manager->events.text_input, + &relay->new_text_input); + + relay->new_input_method.notify = handle_new_input_method; + wl_signal_add(&input_method_manager->events.input_method, + &relay->new_input_method); + + relay->focused_surface_destroy.notify = handle_focused_surface_destroy; + + return relay; +} + +void +input_method_relay_finish(struct input_method_relay *relay) +{ + wl_list_remove(&relay->new_text_input.link); + wl_list_remove(&relay->new_input_method.link); + free(relay); +} + +void +input_method_relay_set_focus(struct input_method_relay *relay, + struct wlr_surface *surface) +{ + if (relay->focused_surface == surface) { + wlr_log(WLR_INFO, "The surface is already focused"); + return; + } + + if (relay->focused_surface) { + wl_list_remove(&relay->focused_surface_destroy.link); + } + relay->focused_surface = surface; + if (surface) { + wl_signal_add(&surface->events.destroy, + &relay->focused_surface_destroy); + } + + update_text_inputs_focused_surface(relay); + update_active_text_input(relay); +} + +#endif diff --git a/src/maomao.c b/src/maomao.c index 7268fa6..1204123 100644 --- a/src/maomao.c +++ b/src/maomao.c @@ -752,7 +752,7 @@ static struct wlr_xwayland *xwayland; /* attempt to encapsulate suck into one file */ #include "client.h" #ifdef IM -#include "text_input.h" +#include "ime.h" #endif struct NumTags { @@ -1772,10 +1772,6 @@ arrange(Monitor *m, bool want_animation) { m->pertag->ltidxs[m->pertag->curtag]->arrange(m, gappoh, 0); } -#ifdef IM - if (input_relay && input_relay->popup) - input_popup_update(input_relay->popup); -#endif motionnotify(0, NULL, 0, 0, 0, 0); checkidleinhibitor(NULL); } @@ -2407,8 +2403,7 @@ void cleanup(void) { #endif #ifdef IM - wl_list_remove(&input_relay->input_method_new.link); - wl_list_remove(&input_relay->text_input_new.link); + input_method_relay_finish(input_method_relay); #endif wl_display_destroy_clients(dpy); @@ -3511,7 +3506,7 @@ void focusclient(Client *c, int lift) { selmon->sel = NULL; // 这个很关键,因为很多地方用到当前窗口做计算,不重置成NULL就会到处有野指针 #ifdef IM - dwl_input_method_relay_set_focus(input_relay, NULL); + input_method_relay_set_focus(input_method_relay, NULL); #endif wlr_seat_keyboard_notify_clear_focus(seat); return; @@ -3524,11 +3519,7 @@ void focusclient(Client *c, int lift) { client_notify_enter(client_surface(c), wlr_seat_get_keyboard(seat)); #ifdef IM - struct wlr_keyboard *keyboard; - keyboard = wlr_seat_get_keyboard(seat); - uint32_t mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; - if (mods == 0) - dwl_input_method_relay_set_focus(input_relay, client_surface(c)); + input_method_relay_set_focus(input_method_relay, client_surface(c)); #endif /* Activate the new client */ client_activate_surface(client_surface(c), 1); @@ -3805,15 +3796,6 @@ void keypress(struct wl_listener *listener, void *data) { } } -#ifdef IM - if (!locked && event->state == WL_KEYBOARD_KEY_STATE_RELEASED && - (keycode == 133 || keycode == 37 || keycode == 64 || keycode == 50 || - keycode == 134 || keycode == 105 || keycode == 108 || keycode == 62) && - selmon && selmon->sel) { - dwl_input_method_relay_set_focus(input_relay, client_surface(selmon->sel)); - } -#endif - /* On _press_ if there is no active screen locker, * attempt to process a compositor keybinding. */ if (!locked && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { @@ -3852,26 +3834,12 @@ void keypress(struct wl_listener *listener, void *data) { if (hit_global) { return; } - -#ifdef IM - /* if there is a keyboard grab, we send the key there */ - struct wlr_input_method_keyboard_grab_v2 *kb_grab = - keyboard_get_im_grab(group); - if (kb_grab) { - wlr_input_method_keyboard_grab_v2_set_keyboard( - kb_grab, &(group->wlr_group->keyboard)); - wlr_input_method_keyboard_grab_v2_send_key(kb_grab, event->time_msec, - event->keycode, event->state); - wlr_log(WLR_DEBUG, "keypress send to IM:%u mods %u state %u", - event->keycode, mods, event->state); - return; - } -#endif - - wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); - /* Pass unhandled keycodes along to the client. */ - wlr_seat_keyboard_notify_key(seat, event->time_msec, event->keycode, + if (!input_method_keyboard_grab_forward_key(group, event)) { + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + /* Pass unhandled keycodes along to the client. */ + wlr_seat_keyboard_notify_key(seat, event->time_msec, event->keycode, event->state); + } } void keypressmod(struct wl_listener *listener, void *data) { @@ -3879,20 +3847,12 @@ void keypressmod(struct wl_listener *listener, void *data) { * pressed. We simply communicate this to the client. */ KeyboardGroup *group = wl_container_of(listener, group, modifiers); -#ifdef IM - struct wlr_input_method_keyboard_grab_v2 *kb_grab = - keyboard_get_im_grab(group); - if (kb_grab) { - wlr_input_method_keyboard_grab_v2_send_modifiers( - kb_grab, &group->wlr_group->keyboard.modifiers); - wlr_log(WLR_DEBUG, "keypressmod send to IM"); - return; - } -#endif - wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); - /* Send modifiers to the client. */ - wlr_seat_keyboard_notify_modifiers(seat, - &group->wlr_group->keyboard.modifiers); + if (!input_method_keyboard_grab_forward_modifiers(group)) { + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + /* Send modifiers to the client. */ + wlr_seat_keyboard_notify_modifiers(seat, + &group->wlr_group->keyboard.modifiers); + } } static bool scene_node_snapshot(struct wlr_scene_node *node, int lx, int ly, @@ -5606,8 +5566,8 @@ void setup(void) { input_method_manager = wlr_input_method_manager_v2_create(dpy); text_input_manager = wlr_text_input_manager_v3_create(dpy); - input_relay = calloc(1, sizeof(*input_relay)); - dwl_input_method_relay_init(input_relay); + input_method_relay = calloc(1, sizeof(*input_method_relay)); + input_method_relay = input_method_relay_create(); #endif wl_global_create(dpy, &zdwl_ipc_manager_v2_interface, 2, NULL, dwl_ipc_manager_bind); diff --git a/src/mem.c b/src/mem.c new file mode 100644 index 0000000..26a0a0d --- /dev/null +++ b/src/mem.c @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Based on labwc (https://github.com/labwc/labwc) */ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include "mem.h" + +static void +die_if_null(void *ptr) +{ + if (!ptr) { + perror("Failed to allocate memory"); + exit(EXIT_FAILURE); + } +} + +void * +xzalloc(size_t size) +{ + if (!size) { + return NULL; + } + void *ptr = calloc(1, size); + die_if_null(ptr); + return ptr; +} + +void * +xrealloc(void *ptr, size_t size) +{ + if (!size) { + free(ptr); + return NULL; + } + ptr = realloc(ptr, size); + die_if_null(ptr); + return ptr; +} + +char * +xstrdup(const char *str) +{ + assert(str); + char *copy = strdup(str); + die_if_null(copy); + return copy; +} diff --git a/src/mem.h b/src/mem.h new file mode 100644 index 0000000..7af481e --- /dev/null +++ b/src/mem.h @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Based on labwc (https://github.com/labwc/labwc) */ +#ifndef LABWC_MEM_H +#define LABWC_MEM_H + +#include + +/* + * As defined in busybox, weston, etc. + * Allocates zero-filled memory; calls exit() on error. + * Returns NULL only if (size == 0). + */ +void *xzalloc(size_t size); + +/* + * Type-safe macros in the style of C++ new/new[]. + * Both allocate zero-filled memory for object(s) the same size as + * , which may be either a type name or value expression. + * + * znew() allocates space for one object. + * znew_n() allocates space for an array of objects. + * + * Examples: + * struct wlr_box *box = znew(*box); + * char *buf = znew_n(char, 80); + */ +#define znew(expr) ((__typeof__(expr) *)xzalloc(sizeof(expr))) +#define znew_n(expr, n) ((__typeof__(expr) *)xzalloc((n) * sizeof(expr))) + +/* + * As defined in FreeBSD. + * Like realloc(), but calls exit() on error. + * Returns NULL only if (size == 0). + * Does NOT zero-fill memory. + */ +void *xrealloc(void *ptr, size_t size); + +/* malloc() is a specific case of realloc() */ +#define xmalloc(size) xrealloc(NULL, (size)) + +/* + * As defined in FreeBSD. + * Allocates a copy of ; calls exit() on error. + * Requires (str != NULL) and never returns NULL. + */ +char *xstrdup(const char *str); + +/* + * Same as ptr = xstrdup(str) but free + * before assigning the result. + */ +#define xstrdup_replace(ptr, str) do { \ + free(ptr); (ptr) = xstrdup(str); \ +} while (0) + +/* + * Frees memory pointed to by and sets to NULL. + * Does nothing if is already NULL. + */ +#define zfree(ptr) do { \ + free(ptr); (ptr) = NULL; \ +} while (0) + +#endif /* LABWC_MEM_H */ diff --git a/src/text_input.h b/src/text_input.h deleted file mode 100644 index 4c1c61e..0000000 --- a/src/text_input.h +++ /dev/null @@ -1,735 +0,0 @@ -#ifdef IM -#include -#include -#include - -#ifdef HANDWRITE -#include -#endif - -/** - * The relay structure manages the relationship between text-input and - * input_method interfaces on a given seat. Multiple text-input interfaces may - * be bound to a relay, but at most one will be focused (receiving events) at - * a time. At most one input-method interface may be bound to the seat. The - * relay manages life cycle of both sides. When both sides are present and - * focused, the relay passes messages between them. - * - * Text input focus is a subset of keyboard focus - if the text-input is - * in the focused state, wl_keyboard sent an enter as well. However, having - * wl_keyboard focused doesn't mean that text-input will be focused. - */ -// 这个relay这一层结构嵌套其实在dwl这里没啥用,暴露出结构成员定义作全局变量代码会更清晰,更符合dwl的风格 -// 但这个代码最初是日本人从sway那边抄过来的,sway的架构比较大,我猜会不会是考虑到会有两个input_method实例才这么定义 -// 为了考虑万一以后哪个版本要扩充,就保留外层的结构 -struct dwl_input_method_relay { - struct wl_list text_inputs; // dwl_text_input::link - struct wlr_input_method_v2 *input_method; // doesn't have to be present - - struct dwl_input_popup *popup; - - struct wl_listener text_input_new; - - struct wl_listener input_method_new; - struct wl_listener input_method_commit; - struct wl_listener input_method_destroy; - struct wl_listener input_method_new_popup_surface; - struct wl_listener input_method_grab_keyboard; - struct wl_listener input_method_keyboard_grab_destroy; -}; - -struct dwl_text_input { - struct dwl_input_method_relay *relay; - - struct wlr_text_input_v3 *input; - // The surface getting seat's focus. Stored for when text-input cannot - // be sent an enter event immediately after getting focus, e.g. when - // there's no input method available. Cleared once text-input is entered. - struct wlr_surface *pending_focused_surface; - - struct wl_list link; - - struct wl_listener pending_focused_surface_destroy; - - struct wl_listener text_input_enable; - //struct wl_listener text_input_commit; - //struct wl_listener text_input_disable; - struct wl_listener text_input_destroy; -}; - - -struct dwl_input_popup { - struct dwl_input_method_relay *relay; - struct wlr_input_popup_surface_v2 *popup_surface; - - //struct wlr_scene_node *scene; - struct wlr_scene_tree *scene; - //struct wlr_scene_node *scene_surface; - struct wlr_scene_tree *scene_surface; - - int x, y; - bool visible; - - - struct wl_listener popup_map; - struct wl_listener popup_unmap; - struct wl_listener popup_destroy; - //struct wl_listener popup_surface_commit; - //struct wl_listener focused_surface_unmap; -}; - - -void dwl_input_method_relay_init(struct dwl_input_method_relay *relay); -// Updates currently focused surface. Surface must belong to the same -// seat. -void dwl_input_method_relay_set_focus(struct dwl_input_method_relay *relay, - struct wlr_surface *surface); - -static void handle_im_grab_keyboard(struct wl_listener *listener, void *data); -static void handle_im_keyboard_grab_destroy(struct wl_listener *listener, - void *data); -static void input_popup_update(struct dwl_input_popup *popup); -static struct dwl_text_input *relay_get_focused_text_input(struct dwl_input_method_relay *relay); - -struct wlr_input_method_manager_v2 *input_method_manager; -struct wlr_text_input_manager_v3 *text_input_manager; -struct dwl_input_method_relay *input_relay; -// static int NO_printstatus=0; // i suspect that printstatus will make dwl hang with fcitx5 - -#ifdef HANDWRITE -static void receive_handwrite_text_from_handwrite_input_app(struct wl_client *client, struct wl_resource *resource, const char *text){ - struct dwl_text_input *text_input; - - wlr_log(WLR_INFO,"receive_handwrite_text_from_handwrite_input_app called with:%s",text); - text_input = relay_get_focused_text_input(input_relay); - - if (text_input){ - wlr_log(WLR_INFO,"receive_handwrite_text_from_handwrite_input_app will commit %s",text); - wlr_text_input_v3_send_commit_string(text_input->input,text); - wlr_text_input_v3_send_done(text_input->input); - }else wlr_log(WLR_INFO, "no focused text_input for handwrite"); -} - -const struct zwp_handwrite_v1_interface handwrite_interface_impl = { - .send_handwrite_text = receive_handwrite_text_from_handwrite_input_app, -}; - -// struct wl_client* handwrite_app=NULL;//TODO: multiple handwrite app instance? -// void zwp_handwrite_v1_handle_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id){ -// wlr_log(WLR_INFO,"zwp_handwrite_v1_handle_bind called"); -// handwrite_app = client; -// struct wl_resource *resource = wl_resource_create(client, &zwp_handwrite_v1_interface, zwp_handwrite_v1_interface.version, id); -// wl_resource_set_implementation(resource, &handwrite_interface_impl, NULL, NULL); -// } -#endif - - - -/** - * Get keyboard grab of the seat from sway_keyboard if we should forward events - * to it. - * - * Returns NULL if the keyboard is not grabbed by an input method, - * or if event is from virtual keyboard - */ -static struct wlr_input_method_keyboard_grab_v2 *keyboard_get_im_grab(KeyboardGroup* kb) { - struct wlr_input_method_v2 *input_method = input_relay->input_method; - - if (!input_method){ - wlr_log(WLR_DEBUG, "keypress keyboard_get_im_grab return NULL:no input_method"); - return NULL; - } else if (!input_method->keyboard_grab){ - wlr_log(WLR_DEBUG, "keypress keyboard_get_im_grab return NULL:no input_method->keyboard_grab"); - return NULL; - } else if (kb != kb_group) { - wlr_log(WLR_DEBUG, "keypress keyboard_get_im_grab return NULL:virtual_keyboard"); - return NULL; - } else return input_method->keyboard_grab; -} - -static void handle_im_grab_keyboard(struct wl_listener *listener, void *data) { - struct dwl_input_method_relay *relay = wl_container_of(listener, relay, - input_method_grab_keyboard); - //wl_container_of 宏的第二个参数sample, 这里是relay,无须是已经初始化的变量,只要是返回值类型的变量就可以了,这里就是dwl_input_method_relay类型的变量 - struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = data; - - // send modifier state to grab - struct wlr_keyboard *active_keyboard = wlr_seat_get_keyboard(seat); - if (active_keyboard){ - wlr_log(WLR_INFO,"im_grab_keyboard"); - wlr_input_method_keyboard_grab_v2_set_keyboard(keyboard_grab,active_keyboard); - wlr_input_method_keyboard_grab_v2_send_modifiers(keyboard_grab, &active_keyboard->modifiers); - } - else - wlr_log(WLR_INFO,"im_grab_keyboard but no active keyboard"); - - - wl_signal_add(&keyboard_grab->events.destroy, - &relay->input_method_keyboard_grab_destroy); - relay->input_method_keyboard_grab_destroy.notify = - handle_im_keyboard_grab_destroy; -} - -static void handle_im_keyboard_grab_destroy(struct wl_listener *listener, void *data) { - struct dwl_input_method_relay *relay = wl_container_of(listener, relay, - input_method_keyboard_grab_destroy); - struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = data; - wlr_log(WLR_DEBUG,"im_keyboard_grab_destroy"); - wl_list_remove(&relay->input_method_keyboard_grab_destroy.link); - if (keyboard_grab->keyboard) { - // send modifier state to original client - wlr_seat_keyboard_notify_modifiers(keyboard_grab->input_method->seat, - &keyboard_grab->keyboard->modifiers); - } -} - - -static struct dwl_text_input *relay_get_focusable_text_input( - struct dwl_input_method_relay *relay) { - struct dwl_text_input *text_input = NULL; - wl_list_for_each(text_input, &relay->text_inputs, link) { - if (text_input->pending_focused_surface) { - return text_input; - } - } - return NULL; -} - -static struct dwl_text_input *relay_get_focused_text_input( - struct dwl_input_method_relay *relay) { - struct dwl_text_input *text_input = NULL; - wl_list_for_each(text_input, &relay->text_inputs, link) { - if (text_input->input->focused_surface) { - return text_input; - } - } - return NULL; -} - -static void handle_im_commit(struct wl_listener *listener, void *data) { - struct wlr_input_method_v2 *context; - - struct dwl_input_method_relay *relay = wl_container_of(listener, relay, - input_method_commit); - - struct dwl_text_input *text_input = relay_get_focused_text_input(relay); - if (!text_input) { - return; - } - - context = data; - assert(context == relay->input_method); - if (context->current.preedit.text) { - wlr_text_input_v3_send_preedit_string(text_input->input, - context->current.preedit.text, - context->current.preedit.cursor_begin, - context->current.preedit.cursor_end); - wlr_log(WLR_DEBUG,"preedit_text: %s", context->current.preedit.text); - } - if (context->current.commit_text) { - wlr_text_input_v3_send_commit_string(text_input->input, - context->current.commit_text); - wlr_log(WLR_DEBUG,"commit_text: %s", context->current.commit_text); - } - if (context->current.delete.before_length - || context->current.delete.after_length) { - wlr_text_input_v3_send_delete_surrounding_text(text_input->input, - context->current.delete.before_length, - context->current.delete.after_length); - } - wlr_text_input_v3_send_done(text_input->input); -} - -static void text_input_set_pending_focused_surface( - struct dwl_text_input *text_input, struct wlr_surface *surface) { - wl_list_remove(&text_input->pending_focused_surface_destroy.link); - text_input->pending_focused_surface = surface; - - if (surface) { - wl_signal_add(&surface->events.destroy, - &text_input->pending_focused_surface_destroy); - } else { - wl_list_init(&text_input->pending_focused_surface_destroy.link); - } -} - -static void handle_im_destroy(struct wl_listener *listener, void *data) { - struct dwl_text_input *text_input; - - struct dwl_input_method_relay *relay = wl_container_of(listener, relay, - input_method_destroy); - struct wlr_input_method_v2 *context = data; - wlr_log(WLR_INFO,"IM destroy"); - assert(context == relay->input_method); - - wl_list_remove(&relay->input_method_commit.link); - wl_list_remove(&relay->input_method_grab_keyboard.link); - wl_list_remove(&relay->input_method_new_popup_surface.link); - wl_list_remove(&relay->input_method_destroy.link); - - relay->input_method = NULL; - - text_input = relay_get_focused_text_input(relay); - if (text_input) { - // keyboard focus is still there, so keep the surface at hand in case - // the input method returns - text_input_set_pending_focused_surface(text_input, - text_input->input->focused_surface); - wlr_text_input_v3_send_leave(text_input->input); - } -} - -static void relay_send_im_state(struct dwl_input_method_relay *relay, - struct wlr_text_input_v3 *input) { - struct wlr_input_method_v2 *input_method = relay->input_method; - if (!input_method) { - wlr_log(WLR_INFO, "Sending IM_DONE but im is gone"); - return; - } - // TODO: only send each of those if they were modified - wlr_input_method_v2_send_surrounding_text(input_method, - input->current.surrounding.text, input->current.surrounding.cursor, - input->current.surrounding.anchor); - wlr_input_method_v2_send_text_change_cause(input_method, - input->current.text_change_cause); - wlr_input_method_v2_send_content_type(input_method, - input->current.content_type.hint, input->current.content_type.purpose); - wlr_input_method_v2_send_done(input_method); - // TODO: pass intent, display popup size -} - -static void handle_text_input_enable(struct wl_listener *listener, void *data) { - struct dwl_text_input *text_input = wl_container_of(listener, text_input, - text_input_enable); - if (text_input->relay->input_method == NULL) { - wlr_log(WLR_INFO, "text_input_enable but input method is NULL"); - return; - } - - wlr_log(WLR_INFO,"text_input_enable"); -#ifdef XWAYLAND - wlr_input_method_v2_send_activate(text_input->relay->input_method); - wlr_log(WLR_INFO,"input_method activate for xwayland"); -#endif - - relay_send_im_state(text_input->relay, text_input->input); -} - -/* static void handle_text_input_commit(struct wl_listener *listener, */ -/* void *data) { */ -/* struct dwl_text_input *text_input = wl_container_of(listener, text_input, */ -/* text_input_commit); */ -/* if (!text_input->input->current_enabled) { */ -/* wlr_log(WLR_INFO, "text_input_commit but not enabled"); */ -/* return; */ -/* } */ -/* if (text_input->relay->input_method == NULL) { */ -/* wlr_log(WLR_INFO, "text_input_commit but input method is NULL"); */ -/* return; */ -/* } */ -/* wlr_log(WLR_DEBUG, "text_input_commit"); */ -/* relay_send_im_state(text_input->relay, text_input->input); */ -/* } */ - -static void relay_disable_text_input(struct dwl_input_method_relay *relay, - struct dwl_text_input *text_input) { - if (relay->input_method == NULL) { - wlr_log(WLR_INFO, "text_input_disable, but input method is NULL"); - return; - } - wlr_log(WLR_INFO,"text_input_disable"); - -#ifdef XWAYLAND - // https://gitee.com/guyuming76/dwl/commit/59328d6ecbbef1b1cd6e5ea8d90d78ccddd5c263 - wlr_input_method_v2_send_deactivate(relay->input_method); - wlr_log(WLR_INFO,"input_method deactivate for xwayland"); -#endif - //but if you keep the line above while remove the line below, input Chinese in geogebra(xwayland) won't work - relay_send_im_state(relay, text_input->input); -} - - -static void handle_text_input_destroy(struct wl_listener *listener, - void *data) { - struct dwl_text_input *text_input = wl_container_of(listener, text_input, - text_input_destroy); - - if (text_input->input->current_enabled) { - wlr_log(WLR_INFO,"text_input_destroy when still enabled"); - relay_disable_text_input(text_input->relay, text_input); - } - else { - wlr_log(WLR_INFO,"text_input_destroy"); - } - - text_input_set_pending_focused_surface(text_input, NULL); - //wl_list_remove(&text_input->text_input_commit.link); - wl_list_remove(&text_input->text_input_destroy.link); - //wl_list_remove(&text_input->text_input_disable.link); - wl_list_remove(&text_input->text_input_enable.link); - wl_list_remove(&text_input->link); - free(text_input); -} - -static void handle_pending_focused_surface_destroy(struct wl_listener *listener, - void *data) { - struct dwl_text_input *text_input = wl_container_of(listener, text_input, - pending_focused_surface_destroy); - struct wlr_surface *surface = data; - assert(text_input->pending_focused_surface == surface); - text_input->pending_focused_surface = NULL; - wl_list_remove(&text_input->pending_focused_surface_destroy.link); - wl_list_init(&text_input->pending_focused_surface_destroy.link); -} - - -static void relay_handle_text_input_new(struct wl_listener *listener, - void *data) { - struct dwl_input_method_relay *relay = wl_container_of(listener, relay, - text_input_new); - struct wlr_text_input_v3 *wlr_text_input = data; - if (seat != wlr_text_input->seat) { - return; - } - - struct dwl_text_input *input; - input = calloc(1, sizeof(*input)); - if (!input) { - wlr_log(WLR_ERROR, "dwl_text_input calloc failed"); - return; - } - wlr_log(WLR_INFO, "dwl_text_input calloc"); - input->input = wlr_text_input; - input->relay = relay; - - wl_list_insert(&relay->text_inputs, &input->link); - - input->text_input_enable.notify = handle_text_input_enable; - wl_signal_add(&wlr_text_input->events.enable, &input->text_input_enable); - - //input->text_input_commit.notify = handle_text_input_commit; - //wl_signal_add(&text_input->events.commit, &input->text_input_commit); - - /* input->text_input_disable.notify = handle_text_input_disable; */ - /* wl_signal_add(&text_input->events.disable, &input->text_input_disable); */ - - input->text_input_destroy.notify = handle_text_input_destroy; - wl_signal_add(&wlr_text_input->events.destroy, &input->text_input_destroy); - - input->pending_focused_surface_destroy.notify = - handle_pending_focused_surface_destroy; - wl_list_init(&input->pending_focused_surface_destroy.link); -} - - -static LayerSurface* layer_surface_from_wlr_layer_surface_v1( - struct wlr_layer_surface_v1* layer_surface) { - return layer_surface->data; -} - - -static void get_parent_and_output_box(struct wlr_surface *focused_surface, - struct wlr_box *parent, struct wlr_box *output_box) { - struct wlr_output *output; - struct wlr_box output_box_tmp; - struct wlr_layer_surface_v1 *layer_surface; - - if ((layer_surface=wlr_layer_surface_v1_try_from_wlr_surface(focused_surface))) { - LayerSurface* layer = - layer_surface_from_wlr_layer_surface_v1(layer_surface); - output = layer->layer_surface->output; - wlr_output_layout_get_box(output_layout, output,&output_box_tmp); - *parent = layer->geom; - parent->x += output_box_tmp.x; - parent->y += output_box_tmp.y; - wlr_log(WLR_INFO,"get_parent_and_output_box layersurface output_box_tmp->x %d y %d",output_box_tmp.x, output_box_tmp.y); - wlr_log(WLR_INFO,"get_parent_and_output_box layersurface parent->x %d y %d",parent->x,parent->y); - } else { - //Client *client = client_from_wlr_surface(focused_surface); - Client *client = NULL; - LayerSurface *l = NULL; - toplevel_from_wlr_surface(focused_surface, &client, &l); - - output = wlr_output_layout_output_at(output_layout, - client->geom.x, client->geom.y); - wlr_output_layout_get_box(output_layout, output,&output_box_tmp); - - parent->x = client->geom.x + client->bw; - parent->y = client->geom.y + client->bw; - parent->width = client->geom.width; - parent->height = client->geom.height; - wlr_log(WLR_INFO,"get_parent_and_output_box client output_box_tmp->x %d y %d",output_box_tmp.x, output_box_tmp.y); - wlr_log(WLR_INFO,"get_parent_and_output_box client client->geom.x %d y %d",client->geom.x,client->geom.y); - wlr_log(WLR_INFO,"get_parent_and_output_box client client->bw %d",client->bw); - wlr_log(WLR_INFO,"get_parent_and_output_box client parent->x %d y %d",parent->x,parent->y); - } - - //*output_box = output_box_tmp; - memcpy(output_box,&output_box_tmp,sizeof(struct wlr_box)); - wlr_log(WLR_INFO,"get_parent_and_output_box output_box x %d y %d width %d height %d",output_box->x,output_box->y,output_box->width,output_box->height); -} - -// 如果当前focused wlr_text_input_v3.features 满足 WLR_TEXT_INPUT_V3_FEATURE_CURSOR_RECTANGLE, 不含这个feature就弹出在父窗口左上角 -// 根据 wlr_text_input_v3.current.cursor_rectangle 计算出一个wlr_box -// 再调用 wlr_input_popup_surface_v2_send_text_input_rectangle 和 wlr_scene_node_set_position -static void input_popup_update(struct dwl_input_popup *popup) { - struct wlr_surface* focused_surface; - struct wlr_box output_box, parent, cursor; - int x1, x2, y1, y2, x, y, available_right, available_left, available_down, - available_up, popup_width, popup_height; - bool cursor_rect, x1_in_bounds, y1_in_bounds, x2_in_bounds, y2_in_bounds; - - struct dwl_text_input *text_input = - relay_get_focused_text_input(popup->relay); - if (!text_input|| !text_input->input->focused_surface) { - return; - } - - //TODO: https://gitlab.freedesktop.org/wlroots/wlroots/-/commit/743da5c0ae723098fe772aadb93810f60e700ab9 - - if (!popup->popup_surface->surface->mapped) { - return; - } - - cursor_rect = text_input->input->current.features - & WLR_TEXT_INPUT_V3_FEATURE_CURSOR_RECTANGLE; - - focused_surface = text_input->input->focused_surface; - cursor = text_input->input->current.cursor_rectangle; - - get_parent_and_output_box(focused_surface, &parent, &output_box); - - popup_width = popup->popup_surface->surface->current.width; - popup_height = popup->popup_surface->surface->current.height; - - if (!cursor_rect) { - cursor.x = 0; - cursor.y = 0; - cursor.width = parent.width; - cursor.height = parent.height; - wlr_log(WLR_INFO,"input_popup_update !cursor_rect"); - - popup->x=parent.x; - popup->y=parent.y; - popup->visible=true; - } - else { - wlr_log(WLR_INFO,"input_popup_update cursor x %d y %d width %d height %d",cursor.x,cursor.y,cursor.width,cursor.height); - - x1 = parent.x + cursor.x; - x2 = parent.x + cursor.x + cursor.width; - y1 = parent.y + cursor.y; - y2 = parent.y + cursor.y + cursor.height; - x = x1; - y = y2; - - wlr_log(WLR_INFO,"input_popup_update x1 %d x2 %d y1 %d y2 %d; x %d y %d",x1,x2,y1,y2,x,y); - available_right = output_box.x + output_box.width - x1; - available_left = x2 - output_box.x; - if (available_right < popup_width && available_left > available_right) { - x = x2 - popup_width; - wlr_log(WLR_INFO,"input_popup_update available_left %d popup_width %d available_right %d; x %d",available_left,popup_width,available_right,x); - } - - available_down = output_box.y + output_box.height - y2; - available_up = y1 - output_box.y; - if (available_down < popup_height && available_up > available_down) { - y = y1 - popup_height; - wlr_log(WLR_INFO,"input_popup_update available up %d popup_height %d available_down %d; y %d",available_up,popup_height,available_down,y); - } - - popup->x = x; - popup->y = y; - - // Hide popup if cursor position is completely out of bounds - x1_in_bounds = (cursor.x >= 0 && cursor.x < parent.width); - y1_in_bounds = (cursor.y >= 0 && cursor.y < parent.height); - x2_in_bounds = (cursor.x + cursor.width >= 0 - && cursor.x + cursor.width < parent.width); - y2_in_bounds = (cursor.y + cursor.height >= 0 - && cursor.y + cursor.height < parent.height); - popup->visible = - (x1_in_bounds && y1_in_bounds) || (x2_in_bounds && y2_in_bounds); - - struct wlr_box box = { - .x = x1 - x, - .y = y1 - y, - .width = cursor.width, - .height = cursor.height, - }; - wlr_input_popup_surface_v2_send_text_input_rectangle( - popup->popup_surface, &box); - wlr_log(WLR_INFO,"input_popup_update send_text_input_rect box.x %d box.y %d",box.x,box.y); - - } - - wlr_log(WLR_INFO,"input_popup_update x %d y %d visible %s",popup->x,popup->y,popup->visible?"true":"false"); - wlr_scene_node_set_position(&popup->scene->node, popup->x, popup->y); -} - -static void handle_im_popup_map(struct wl_listener *listener, void *data) { - struct dwl_input_popup *popup = - wl_container_of(listener, popup, popup_map); - - wlr_log(WLR_INFO, "IM_popup_map"); - - //popup->scene = &wlr_scene_tree_create(layers[LyrIMPopup])->node; - popup->scene = wlr_scene_tree_create(layers[LyrIMPopup]); - popup->scene_surface = wlr_scene_subsurface_tree_create(popup->scene,popup->popup_surface->surface); - //popup->scene_surface = &wlr_scene_subsurface_tree_create(popup->scene->parent,popup->popup_surface->surface)->node; - //popup->scene_surface->data = popup; - popup->scene_surface->node.data = popup; - - input_popup_update(popup); - - //wlr_scene_node_set_position(popup->scene, popup->x, popup->y); -} - -static void handle_im_popup_unmap(struct wl_listener *listener, void *data) { - struct dwl_input_popup *popup = - wl_container_of(listener, popup, popup_unmap); - //input_popup_update(popup); - - wlr_log(WLR_INFO,"im_popup_unmap"); - wlr_scene_node_destroy(&popup->scene->node); -} - -static void handle_im_popup_destroy(struct wl_listener *listener, void *data) { - struct dwl_input_method_relay *relay; - struct dwl_input_popup *popup = - wl_container_of(listener, popup, popup_destroy); - - wlr_log(WLR_INFO,"im_popup_destroy"); - - wl_list_remove(&popup->popup_destroy.link); - wl_list_remove(&popup->popup_unmap.link); - wl_list_remove(&popup->popup_map.link); - - relay=popup->relay; - free(popup->relay->popup); - relay->popup=NULL; -} - - -static void handle_im_new_popup_surface(struct wl_listener *listener, void *data) { - // struct dwl_text_input* text_input; - - struct dwl_input_method_relay *relay = wl_container_of(listener, relay, - input_method_new_popup_surface); - struct dwl_input_popup *popup = calloc(1, sizeof(*popup)); - - wlr_log(WLR_INFO, "IM_new_popup_surface"); - relay->popup = popup; - - popup->relay = relay; - popup->popup_surface = data; - popup->popup_surface->data = popup; - - - wl_signal_add(&popup->popup_surface->surface->events.map, &popup->popup_map); - popup->popup_map.notify = handle_im_popup_map; - - wl_signal_add(&popup->popup_surface->surface->events.unmap, &popup->popup_unmap); - popup->popup_unmap.notify = handle_im_popup_unmap; - - wl_signal_add( - &popup->popup_surface->events.destroy, &popup->popup_destroy); - popup->popup_destroy.notify = handle_im_popup_destroy; -} - - -static void relay_handle_input_method_new(struct wl_listener *listener, - void *data) { - struct dwl_text_input *text_input; - - struct dwl_input_method_relay *relay = wl_container_of(listener, relay, - input_method_new); - - struct wlr_input_method_v2 *input_method = data; - if (seat != input_method->seat) { - wlr_log(WLR_INFO,"input_method_new Seat does not match"); - return; - } - - if (relay->input_method != NULL) { - wlr_log(WLR_INFO, "input_method_new Attempted to connect second input method to a seat"); - wlr_input_method_v2_send_unavailable(input_method); - return; - } - - wlr_log(WLR_INFO,"input_method_new"); - - relay->input_method = input_method; - wl_signal_add(&relay->input_method->events.commit, - &relay->input_method_commit); - relay->input_method_commit.notify = handle_im_commit; - wl_signal_add(&relay->input_method->events.new_popup_surface, - &relay->input_method_new_popup_surface); - relay->input_method_new_popup_surface.notify = handle_im_new_popup_surface; - wl_signal_add(&relay->input_method->events.grab_keyboard, - &relay->input_method_grab_keyboard); - relay->input_method_grab_keyboard.notify = handle_im_grab_keyboard; - wl_signal_add(&relay->input_method->events.destroy, - &relay->input_method_destroy); - relay->input_method_destroy.notify = handle_im_destroy; - - wlr_input_method_v2_send_activate(relay->input_method); - - text_input = relay_get_focusable_text_input(relay); - if (text_input) { - wlr_text_input_v3_send_enter(text_input->input, - text_input->pending_focused_surface); - text_input_set_pending_focused_surface(text_input, NULL); - } -} - -void dwl_input_method_relay_init(struct dwl_input_method_relay *relay) { - wl_list_init(&relay->text_inputs); - - relay->popup=NULL; - - relay->text_input_new.notify = relay_handle_text_input_new; - wl_signal_add(&text_input_manager->events.text_input, - &relay->text_input_new); - - relay->input_method_new.notify = relay_handle_input_method_new; - wl_signal_add(&input_method_manager->events.input_method, - &relay->input_method_new); -} - -void dwl_input_method_relay_set_focus(struct dwl_input_method_relay *relay, - struct wlr_surface *surface) { - struct dwl_text_input *text_input; - wl_list_for_each(text_input, &relay->text_inputs, link) { - if (text_input->pending_focused_surface) { - assert(text_input->input->focused_surface == NULL); - if (surface != text_input->pending_focused_surface) { - text_input_set_pending_focused_surface(text_input, NULL); - } - } else if (text_input->input->focused_surface) { - assert(text_input->pending_focused_surface == NULL); - if (surface != text_input->input->focused_surface) { - relay_disable_text_input(relay, text_input); - wlr_text_input_v3_send_leave(text_input->input); - wlr_log(WLR_INFO, "wlr_text_input_send_leave"); - } else { - wlr_log(WLR_INFO, "IM relay set_focus already focused"); - continue; - } - } - - if (surface - && wl_resource_get_client(text_input->input->resource) - == wl_resource_get_client(surface->resource)) { - if (relay->input_method) { - wlr_text_input_v3_send_enter(text_input->input, surface); - wlr_log(WLR_INFO, "wlr_text_input_send_enter"); - if (relay->popup) input_popup_update(relay->popup); - } else { - text_input_set_pending_focused_surface(text_input, surface); - } - } - } -} - -#endif