From 9e53955336cb1c6f834cd5fb9fae924419923b02 Mon Sep 17 00:00:00 2001 From: DanyLE Date: Tue, 11 Mar 2025 00:01:47 +0100 Subject: [PATCH] Add support for wlr_virtual_keyboard_v1 protocol --- diyac.c | 102 ++++++++++++++++++++++++++---- diyac.h | 40 ++++++++---- seat.c | 189 +++++++++++++++++++++++++++++++++++++++++++------------- seat.h | 2 + 4 files changed, 267 insertions(+), 66 deletions(-) diff --git a/diyac.c b/diyac.c index a7c53a5..3812db5 100644 --- a/diyac.c +++ b/diyac.c @@ -4,6 +4,10 @@ #include #include #include +#include +#include +#include +#include #include "output.h" #include "xdg.h" #include "cursor.h" @@ -11,30 +15,77 @@ #include "layer.h" #include "session.h" +#define PROC_MON_TO 100 + +void help() +{ + printf("Usage: diyac [-x] [startup command]\n"); + printf("Options:\n"); + printf(" -x exit with the session\n"); +} + +/** + * session process monitor + */ +static int session_monitor(void *data) +{ + struct diyac_server *server = data; + int status; + pid_t pid; + if (server->session_pid > 0) + { + pid = waitpid(server->session_pid, &status, WNOHANG); + if (pid == -1) + { + wlr_log(WLR_ERROR, "session waitpid error: %s", strerror(errno)); + // try to kill it + (void)kill(server->session_pid, SIGKILL); + } + else if (pid == 0) + { + wl_event_source_timer_update(server->proc_mon, PROC_MON_TO); + return 0; + } + server->session_pid = -1; + } + wl_display_terminate(server->wl_display); + return 0; +} + int main(int argc, char *argv[]) { wlr_log_init(WLR_INFO, NULL); char *startup_cmd = NULL; + int exit_with_session = 0; int c; - while ((c = getopt(argc, argv, "s:h")) != -1) + while ((c = getopt(argc, argv, "xh")) != -1) { switch (c) { - case 's': - startup_cmd = optarg; + case 'x': + exit_with_session = 1; break; default: - printf("Usage: %s [-s startup command]\n", argv[0]); + printf("Usage: %s [-s] startup command]\n", argv[0]); return 0; } } + if (exit_with_session && optind == argc) + { + help(); + return 1; + } if (optind < argc) { - printf("Usage: %s [-s startup command]\n", argv[0]); - return 0; + if(optind != argc - 1) + { + help(); + return 1; + } + // the last argument is the startup command + startup_cmd = argv[optind]; } - struct diyac_server server = {0}; /* The Wayland display is managed by libwayland. It handles accepting * clients from the Unix socket, manging Wayland globals, and so on. */ @@ -147,7 +198,7 @@ int main(int argc, char *argv[]) * pointer, touch, and drawing tablet device. We also rig up a listener to * let us know when new input devices are available on the backend. */ - wl_list_init(&server.seat.keyboards); + wl_list_init(&server.seat.inputs); /* foreign toplevel manager for create shell panel for application control */ server.foreign_toplevel_manager = wlr_foreign_toplevel_manager_v1_create(server.wl_display); diyac_init_seat(&server); @@ -172,9 +223,11 @@ int main(int argc, char *argv[]) /* Set the WAYLAND_DISPLAY environment variable to our socket and run the * startup command if requested. */ setenv("WAYLAND_DISPLAY", socket, true); + server.session_pid = -1; if (startup_cmd) { - if (fork() == 0) + server.session_pid = fork(); + if (server.session_pid == 0) { execl("/bin/sh", "/bin/sh", "-c", startup_cmd, (void *)NULL); } @@ -185,14 +238,41 @@ int main(int argc, char *argv[]) * frame events at the refresh rate, and so on. */ wlr_log(WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s", socket); - wl_display_run(server.wl_display); + server.proc_mon = NULL; + if( startup_cmd && exit_with_session) + { + if (server.session_pid == -1) + { + wlr_log(WLR_ERROR, "Cannot run startup command: %s. Exit", strerror(errno)); + } + else + { + server.proc_mon = wl_event_loop_add_timer(server.wl_event_loop, session_monitor, &server); + wl_event_source_timer_update(server.proc_mon, PROC_MON_TO); + wl_display_run(server.wl_display); + } + } + else + { + wl_display_run(server.wl_display); + } /* Once wl_display_run returns, we destroy all clients then shut down the * server. */ + if(server.proc_mon) + { + wl_event_source_remove(server.proc_mon); + } wl_display_destroy_clients(server.wl_display); - wlr_scene_node_destroy(&server.scene->tree.node); + // wlr_scene_node_destroy(&server.scene->tree.node); wlr_xcursor_manager_destroy(server.seat.cursor_mgr); wlr_output_layout_destroy(server.output_layout); wl_display_destroy(server.wl_display); return 0; } + +/** + * @brief TODO + * reload configuration (keymap, etc.) when sighub is received + * + */ \ No newline at end of file diff --git a/diyac.h b/diyac.h index 726f11b..cf4d594 100644 --- a/diyac.h +++ b/diyac.h @@ -17,6 +17,7 @@ #include #include #include +#include #define LAYER_TREE_SZ 4 @@ -70,7 +71,12 @@ struct diyac_seat struct wl_listener request_cursor; struct wl_listener request_set_selection; struct wl_listener request_set_primary_selection; - struct wl_list keyboards; + // struct wl_list keyboards; + struct wl_list inputs; + + // virtual keyboard support + struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard_manager; + struct wl_listener virtual_keyboard_new; }; struct diyac_popup @@ -108,8 +114,8 @@ struct diyac_session_lock struct wlr_session_lock_v1* wlr_session_lock; struct wl_listener new_surface; - struct wl_listener unlock; - struct wl_listener destroy; + struct wl_listener unlock; + struct wl_listener destroy; //struct wl_listener new_output; }; @@ -149,6 +155,9 @@ struct diyac_server struct wl_listener new_output; struct diyac_session_lock * lock; + + struct wl_event_source* proc_mon; + pid_t session_pid; }; struct diyac_layer_surface @@ -167,14 +176,14 @@ struct diyac_layer_surface struct diyac_output_lock_handle { struct wlr_scene_tree *tree; - struct wlr_scene_rect *background; + struct wlr_scene_rect *background; struct wlr_session_lock_surface_v1 *surface; struct diyac_output * output; struct wl_listener surface_destroy; struct wl_listener surface_map; - struct wl_listener commit; + struct wl_listener commit; }; struct diyac_output @@ -243,15 +252,22 @@ struct diyac_view struct foreign_toplevel toplevel; }; -struct diyac_keyboard +struct diyac_input { - struct wl_list link; - struct diyac_server *server; - struct wlr_keyboard *wlr_keyboard; - - struct wl_listener modifiers; - struct wl_listener key; + struct wl_list link; /* seat.inputs */ + struct wlr_input_device *device; + struct diyac_seat *seat; struct wl_listener destroy; }; +struct diyac_keyboard +{ + struct diyac_input input; + struct wl_list link; + struct wlr_keyboard *wlr_keyboard; + struct wl_listener modifiers; + struct wl_listener key; + bool is_virtual; +}; + #endif \ No newline at end of file diff --git a/seat.c b/seat.c index abc0446..2e86afa 100644 --- a/seat.c +++ b/seat.c @@ -3,9 +3,39 @@ #include #include #include +#include #include "seat.h" #include "view.h" +static void configure_keyboard(struct diyac_seat* seat, struct diyac_input* input, bool force) +{ + struct wlr_input_device *device = input->device; + assert(device->type == WLR_INPUT_DEVICE_KEYBOARD); + struct diyac_keyboard *keyboard = (struct diyac_keyboard *)input; + struct wlr_keyboard *kb = wlr_keyboard_from_input_device(device); + + if(!keyboard->is_virtual || force) + { + /* + * Set layout based on environment variables XKB_DEFAULT_LAYOUT, + * XKB_DEFAULT_OPTIONS, and friends. + */ + struct xkb_rule_names rules = { 0 }; + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + struct xkb_keymap *keymap = xkb_map_new_from_names(context, &rules,XKB_KEYMAP_COMPILE_NO_FLAGS); + if (keymap) { + if (!wlr_keyboard_keymaps_match(kb->keymap, keymap)) { + wlr_keyboard_set_keymap(kb, keymap); + } + xkb_keymap_unref(keymap); + } else { + wlr_log(WLR_ERROR, "Failed to create xkb keymap"); + } + xkb_context_unref(context); + } + wlr_keyboard_set_repeat_info(kb, 25, 600); +} + static void keyboard_handle_modifiers( struct wl_listener *listener, void *data) @@ -20,9 +50,9 @@ static void keyboard_handle_modifiers( * same seat. You can swap out the underlying wlr_keyboard like this and * wlr_seat handles this transparently. */ - wlr_seat_set_keyboard(keyboard->server->seat.wlr_seat, keyboard->wlr_keyboard); + wlr_seat_set_keyboard(keyboard->input.seat->wlr_seat, keyboard->wlr_keyboard); /* Send modifiers to the client. */ - wlr_seat_keyboard_notify_modifiers(keyboard->server->seat.wlr_seat, + wlr_seat_keyboard_notify_modifiers(keyboard->input.seat->wlr_seat, &keyboard->wlr_keyboard->modifiers); } @@ -62,11 +92,11 @@ static void keyboard_handle_key( /* This event is raised when a key is pressed or released. */ struct diyac_keyboard *keyboard = wl_container_of(listener, keyboard, key); - struct diyac_server *server = keyboard->server; struct wlr_keyboard_key_event *event = data; - struct wlr_seat *seat = server->seat.wlr_seat; + struct wlr_seat *seat = keyboard->input.seat->wlr_seat; /* Translate libinput keycode -> xkbcommon */ + wlr_log(WLR_INFO, "receive keycode %d", event->keycode); uint32_t keycode = event->keycode + 8; /* Get a list of keysyms based on the keymap for this keyboard */ const xkb_keysym_t *syms; @@ -82,7 +112,7 @@ static void keyboard_handle_key( * process it as a compositor keybinding. */ for (int i = 0; i < nsyms; i++) { - handled = handle_keybinding(server, syms[i]); + handled = handle_keybinding(keyboard->input.seat->server, syms[i]); } } @@ -95,63 +125,102 @@ static void keyboard_handle_key( } } -static void keyboard_handle_destroy(struct wl_listener *listener, void *data) +static void input_handle_destroy(struct wl_listener *listener, void *data) { /* This event is raised by the keyboard base wlr_input_device to signal * the destruction of the wlr_keyboard. It will no longer receive events * and should be destroyed. */ - struct diyac_keyboard *keyboard = - wl_container_of(listener, keyboard, destroy); - wl_list_remove(&keyboard->modifiers.link); - wl_list_remove(&keyboard->key.link); - wl_list_remove(&keyboard->destroy.link); - wl_list_remove(&keyboard->link); - free(keyboard); + struct diyac_input *input = wl_container_of(listener, input, destroy); + wl_list_remove(&input->link); + wl_list_remove(&input->destroy.link); + + if(input->device->type == WLR_INPUT_DEVICE_KEYBOARD) + { + struct diyac_keyboard* keyboard = (struct diyac_keyboard*) input; + if(keyboard->is_virtual) + { + wlr_log(WLR_INFO, "Virtual keyboard destroyed"); + } + wl_list_remove(&keyboard->modifiers.link); + wl_list_remove(&keyboard->key.link); + } + free(input); } -static void server_new_keyboard(struct diyac_server *server, - struct wlr_input_device *device) +static struct diyac_input* server_new_keyboard(struct diyac_seat *seat, + struct wlr_input_device *device, bool is_virtual) { struct wlr_keyboard *wlr_keyboard = wlr_keyboard_from_input_device(device); struct diyac_keyboard *keyboard = calloc(1, sizeof(*keyboard)); - keyboard->server = server; keyboard->wlr_keyboard = wlr_keyboard; + keyboard->input.device = device; + keyboard->input.seat = seat; + keyboard->is_virtual = is_virtual; - /* We need to prepare an XKB keymap and assign it to the keyboard. This - * assumes the defaults (e.g. layout = "us"). */ - struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, NULL, - XKB_KEYMAP_COMPILE_NO_FLAGS); - - wlr_keyboard_set_keymap(wlr_keyboard, keymap); - xkb_keymap_unref(keymap); - xkb_context_unref(context); - wlr_keyboard_set_repeat_info(wlr_keyboard, 25, 600); + /** + * Force configure default keyboard keymap for all new + * keyboard, including virtual keyboard + */ + configure_keyboard(seat,&keyboard->input, true); /* Here we set up listeners for keyboard events. */ keyboard->modifiers.notify = keyboard_handle_modifiers; wl_signal_add(&wlr_keyboard->events.modifiers, &keyboard->modifiers); + keyboard->key.notify = keyboard_handle_key; wl_signal_add(&wlr_keyboard->events.key, &keyboard->key); - keyboard->destroy.notify = keyboard_handle_destroy; - wl_signal_add(&device->events.destroy, &keyboard->destroy); - wlr_seat_set_keyboard(server->seat.wlr_seat, keyboard->wlr_keyboard); + wlr_seat_set_keyboard(seat->wlr_seat, keyboard->wlr_keyboard); /* And add the keyboard to our list of keyboards */ - wl_list_insert(&server->seat.keyboards, &keyboard->link); + //wl_list_insert(&server->seat.keyboards, &keyboard->link); + return (struct diyac_input*) keyboard; } -static void server_new_pointer(struct diyac_server *server, +static struct diyac_input* server_new_pointer(struct diyac_seat *seat, struct wlr_input_device *device) { + struct diyac_input *input = calloc(1, sizeof(*input)); + input->device = device; /* We don't do anything special with pointers. All of our pointer handling * is proxied through wlr_cursor. On another compositor, you might take this * opportunity to do libinput configuration on the device to set * acceleration, etc. */ - wlr_cursor_attach_input_device(server->seat.cursor, device); + wlr_cursor_attach_input_device(seat->cursor, device); + return input; +} + +static void seat_add_input(struct diyac_seat* seat, struct diyac_input* input) +{ + input->seat = seat; + input->destroy.notify = input_handle_destroy; + wl_signal_add(&input->device->events.destroy, &input->destroy); + wl_list_insert(&seat->inputs, &input->link); + // update capability + + /* We need to let the wlr_seat know what our capabilities are, which is + * communiciated to the client. In Diyac we always have a cursor, even if + * there are no pointer devices, so we always include that capability. */ + uint32_t caps = 0; + + wl_list_for_each(input, &seat->inputs, link) { + switch (input->device->type) { + case WLR_INPUT_DEVICE_KEYBOARD: + caps |= WL_SEAT_CAPABILITY_KEYBOARD; + break; + case WLR_INPUT_DEVICE_POINTER: + caps |= WL_SEAT_CAPABILITY_POINTER; + break; + case WLR_INPUT_DEVICE_TOUCH: + caps |= WL_SEAT_CAPABILITY_TOUCH; + break; + default: + break; + } + } + wlr_seat_set_capabilities(seat->wlr_seat, caps); } static void server_new_input(struct wl_listener *listener, void *data) @@ -161,26 +230,21 @@ static void server_new_input(struct wl_listener *listener, void *data) struct diyac_seat *seat = wl_container_of(listener, seat, new_input); struct wlr_input_device *device = data; + struct diyac_input* input = NULL; + switch (device->type) { case WLR_INPUT_DEVICE_KEYBOARD: - server_new_keyboard(seat->server, device); + input = server_new_keyboard(seat, device, false); break; case WLR_INPUT_DEVICE_POINTER: - server_new_pointer(seat->server, device); + input = server_new_pointer(seat, device); break; default: break; + return; } - /* We need to let the wlr_seat know what our capabilities are, which is - * communiciated to the client. In Diyac we always have a cursor, even if - * there are no pointer devices, so we always include that capability. */ - uint32_t caps = WL_SEAT_CAPABILITY_POINTER; - if (!wl_list_empty(&seat->keyboards)) - { - caps |= WL_SEAT_CAPABILITY_KEYBOARD; - } - wlr_seat_set_capabilities(seat->wlr_seat, caps); + seat_add_input(seat, input); } static void seat_request_cursor(struct wl_listener *listener, void *data) @@ -234,6 +298,18 @@ static void request_set_primary_selection_notify(struct wl_listener *listener, v event->serial); } +static void new_virtual_keyboard(struct wl_listener *listener, void *data) +{ + wlr_log(WLR_INFO, "Create a new virtual keyboard with default layout"); + struct diyac_seat *seat = wl_container_of(listener, seat, virtual_keyboard_new); + struct wlr_virtual_keyboard_v1 *virtual_keyboard = data; + struct wlr_input_device *device = &virtual_keyboard->keyboard.base; + + struct diyac_input *input = server_new_keyboard(seat, device, true); + device->data = input; + seat_add_input(seat, input); +} + /* * Configures a seat, which is a single "seat" at which a user sits and * operates the computer. This conceptually includes up to one keyboard, @@ -261,6 +337,12 @@ void diyac_init_seat(struct diyac_server *server) request_set_primary_selection_notify; wl_signal_add(&server->seat.wlr_seat->events.request_set_primary_selection, &server->seat.request_set_primary_selection); + + // virtual keyboard support + server->seat.virtual_keyboard_manager = wlr_virtual_keyboard_manager_v1_create(server->wl_display); + wl_signal_add(&server->seat.virtual_keyboard_manager->events.new_virtual_keyboard, + &server->seat.virtual_keyboard_new); + server->seat.virtual_keyboard_new.notify = new_virtual_keyboard; } static void seat_focus(struct diyac_seat *seat, struct wlr_surface *surface, bool is_lock_surface) @@ -332,4 +414,25 @@ void diyac_seat_focus_layer(struct diyac_seat *seat, struct wlr_layer_surface_v1 { seat->focused_layer = layer; } +} + +void diyac_seat_configure(struct diyac_server* server) +{ + struct diyac_seat* seat = &server->seat; + struct diyac_input* input = NULL; + + wl_list_for_each(input, &seat->inputs, link) + { + switch (input->device->type) { + case WLR_INPUT_DEVICE_KEYBOARD: + configure_keyboard(seat, input, false); + break; + case WLR_INPUT_DEVICE_POINTER: + break; + case WLR_INPUT_DEVICE_TOUCH: + break; + default: + break; + } + } } \ No newline at end of file diff --git a/seat.h b/seat.h index 21d0527..b3c8211 100644 --- a/seat.h +++ b/seat.h @@ -6,4 +6,6 @@ void diyac_init_seat(struct diyac_server* server); void diyac_seat_focus_surface(struct diyac_seat *seat, struct wlr_surface *surface); void diyac_seat_focus_lock_surface(struct diyac_seat *seat, struct wlr_surface *surface); void diyac_seat_focus_layer(struct diyac_seat *seat, struct wlr_layer_surface_v1 *layer); + +void diyac_seat_configure(struct diyac_server* server); #endif \ No newline at end of file