Skip to content

labwc sometimes fails to broadcast keyboard layout changes if an application is using a virtual keyboard #3113

@ArrayBolt3

Description

@ArrayBolt3

Problem description

I think this is probably a duplicate of #2723, but I'm not entirely sure. If it's not a duplicate, it's at least very likely related.

If:

  • an application that uses the virtual-keyboard-unstable-v1.xml protocol is running and has a virtual keyboard actively attached to the compositor, and
  • the compositor has not yet received a keystroke event originating from the machine's physical keyboard, and
  • the user (somehow) modifies ~/.config/labwc/environment and sets a different keyboard layout with the XKB_DEFAULT_LAYOUT option, and
  • the user then attempts to make that keyboard layout change take effect by running labwc --reconfigure,

then labwc will not broadcast the keyboard layout change to all running applications. The user must press a physical key on the keyboard in order to cause the new keyboard layout to be broadcast to all applications.

This sounds like a ridiculous edge case no one will ever hit, especially since pressing a single key on the system's physical keyboard solves the issue, but there is one instance in which this can pose a serious problem, and that is when you have a Wayland client running that grabs all keyboard input devices on the system and proxies them to the compositor via the virtual-keyboard-unstable-v1.xml protocol. kloak is one such application. Because it both provides a virtual keyboard to labwc, and grabs exclusive access to all input devices, labwc doesn't broadcast keyboard layout changes because of the virtual keyboard, and will never start broadcasting them because it no longer is receiving keystrokes from the physical keyboard.

Steps to reproduce

There's probably an eaasier way to reproduce this, but the way I came up with was to use a custom Wayland client. I wrote this initially trying to figure out where the bug was in kloak, before discovering the issue was in labwc.

  1. Create a directory at ~/virt-kb-test.
  2. Paste the following code into ~/virt-kb-test/kb_test.c:
#define _GNU_SOURCE

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/ioctl.h>
#include <linux/input.h>
#include <fcntl.h>
#include <sys/mman.h>

#include <wayland-client.h>
#include "virtual-keyboard.h"

struct disp_state {
  struct wl_display *display;
  struct wl_registry *registry;
  struct wl_seat *seat;
  struct wl_keyboard *kb;
  uint32_t seat_caps;
  bool seat_set;
  struct zwp_virtual_keyboard_manager_v1 *virt_kb_manager;
  struct zwp_virtual_keyboard_v1 *virt_kb;
  char *old_kb_map_shm;
  int32_t old_kb_map_shm_size;
};
static void registry_handle_global(void *data, struct wl_registry * registry,
  uint32_t name, const char * interface, uint32_t version);
static void registry_handle_global_remove(void *data,
  struct wl_registry * registry, uint32_t name);
static void seat_handle_name(void *data, struct wl_seat *seat,
  const char *name);
static void seat_handle_capabilities(void *data, struct wl_seat *seat,
  uint32_t capabilities);
static void kb_handle_keymap(void *data, struct wl_keyboard *kb,
  uint32_t format, int32_t fd, uint32_t size);
static void kb_handle_enter(void *data, struct wl_keyboard *kb,
  uint32_t serial, struct wl_surface *surface, struct wl_array *keys);
static void kb_handle_leave(void *data, struct wl_keyboard *kb,
  uint32_t serial, struct wl_surface *surface);
static void kb_handle_key(void *data, struct wl_keyboard *kb, uint32_t serial,
  uint32_t time, uint32_t key, uint32_t state);
static void kb_handle_modifiers(void *data, struct wl_keyboard *kb,
  uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched,
  uint32_t mods_locked, uint32_t group);
static void kb_handle_repeat_info(void *data, struct wl_keyboard *kb,
  int32_t rate, int32_t delay);
static const struct wl_registry_listener registry_listener = {
  .global = registry_handle_global,
  .global_remove = registry_handle_global_remove,
};
static const struct wl_seat_listener seat_listener = {
  .name = seat_handle_name,
  .capabilities = seat_handle_capabilities,
};
static const struct wl_keyboard_listener kb_listener = {
  .keymap = kb_handle_keymap,
  .enter = kb_handle_enter,
  .leave = kb_handle_leave,
  .key = kb_handle_key,
  .modifiers = kb_handle_modifiers,
  .repeat_info = kb_handle_repeat_info,
};

struct disp_state state = { 0 };

static void registry_handle_global(void *data, struct wl_registry * registry,
  uint32_t name, const char * interface, uint32_t version) {
  struct disp_state *param_state = data;

  if (strcmp(interface, wl_seat_interface.name) == 0) {
    if (!param_state->seat_set) {
      param_state->seat = wl_registry_bind(registry, name, &wl_seat_interface, 9);
      wl_seat_add_listener(param_state->seat, &seat_listener, &state);
      param_state->seat_set = true;
    } else {
      fprintf(stderr,
        "WARNING: Multiple seats detected, all but first will be ignored.\n");
    }
  } else if (strcmp(interface, zwp_virtual_keyboard_manager_v1_interface.name) == 0) {
    param_state->virt_kb_manager = wl_registry_bind(registry, name, &zwp_virtual_keyboard_manager_v1_interface, 1);
  }
}

static void registry_handle_global_remove(void *data,
  struct wl_registry * registry, uint32_t name) {
  ;
}

static void seat_handle_name(void *data, struct wl_seat *seat,
  const char *name) {
  ;
}

static void seat_handle_capabilities(void *data, struct wl_seat *seat,
  uint32_t capabilities) {
  struct disp_state *param_state = data;

  param_state->seat_caps = capabilities;
  if (capabilities | WL_SEAT_CAPABILITY_KEYBOARD) {
    param_state->kb = wl_seat_get_keyboard(seat);
    wl_keyboard_add_listener(param_state->kb, &kb_listener, param_state);
  } else {
    fprintf(stderr,
      "FATAL ERROR: No keyboard capability for seat, cannot continue.\n");
    exit(1);
  }
}

static void kb_handle_keymap(void *data, struct wl_keyboard *kb,
  uint32_t format, int32_t fd, uint32_t size) {
  fprintf(stderr, "Keymap changed!\n");
  if (state.virt_kb == NULL && state.virt_kb_manager != NULL) {
    state.virt_kb = zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(
      state.virt_kb_manager, state.seat);
  }
  char *kb_map_shm = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
  if (kb_map_shm == MAP_FAILED) {
    fprintf(stderr, "FATAL ERROR: Could nto mmap xkb layout!\n");
    exit(1);
  }

  if (state.old_kb_map_shm) {
    if (size == state.old_kb_map_shm_size && memcmp(state.old_kb_map_shm, kb_map_shm, size) == 0) {
      munmap(kb_map_shm, size);
      close(fd);
      return;
    } else {
      munmap(state.old_kb_map_shm, (size_t)(state.old_kb_map_shm_size));
    }
  }

  if (state.virt_kb != NULL) {
    zwp_virtual_keyboard_v1_keymap(state.virt_kb, format, fd, size);
  }
  state.old_kb_map_shm = kb_map_shm;
  state.old_kb_map_shm_size = (int32_t)(size);
  close(fd);
}

static void kb_handle_enter(void *data, struct wl_keyboard *kb,
  uint32_t serial, struct wl_surface *surface, struct wl_array *keys) {
  wl_array_release(keys);
}

static void kb_handle_leave(void *data, struct wl_keyboard *kb,
  uint32_t serial, struct wl_surface *surface) {
  ;
}

static void kb_handle_key(void *data, struct wl_keyboard *kb, uint32_t serial,
  uint32_t time, uint32_t key, uint32_t state) {
  ;
}

static void kb_handle_modifiers(void *data, struct wl_keyboard *kb,
  uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched,
  uint32_t mods_locked, uint32_t group) {
  ;
}

static void kb_handle_repeat_info(void *data, struct wl_keyboard *kb,
  int32_t rate, int32_t delay) {
  ;
}

int main(int argc, char **argv) {
  state.virt_kb = NULL;
  state.virt_kb_manager = NULL;
  state.display = wl_display_connect(NULL);
  if (!state.display) {
    fprintf(stderr, "FATAL ERROR: Could not get Wayland display!\n");
    exit(1);
  }
  state.registry = wl_display_get_registry(state.display);
  if (!state.registry) {
    fprintf(stderr, "FATAL ERROR: Could not get Wayland registry!\n");
    exit(1);
  }
  wl_registry_add_listener(state.registry, &registry_listener, &state);
  wl_display_roundtrip(state.display);
  while (wl_display_dispatch(state.display)) {
    ;
  }
}
  1. Preferably review the above code before running it since it's always a bad idea to trust random code from strangers on the Internet. :P
  2. Obtain virtual-keyboard-unstable-v1.xml from wlroots, i.e. from https://gitlab.freedesktop.org/wlroots/wlroots/-/blob/5adf325333602d5b1e7ccdeb122633bbc8040ace/protocol/virtual-keyboard-unstable-v1.xml
  3. Create virtual-keyboard.c: wayland-scanner private-code virtual-keyboard-unstable-v1.xml virtual-keyboard.c
  4. Create virtual-keyboard.h: wayland-scanner client-header virtual-keyboard-unstable-v1.xml virtual-keyboard.c
  5. Compile the client: gcc kb_test.c virtual-keyboard.c -lwayland-client
  6. Before executing the client, SSH into the machine.
  7. In the SSH console, run export LABWC_PID="$(pgrep labwc)" so you can reconfigure the compositor from here later.
  8. On the physical machine, ensure you are in the virt-kb-test directory, then run sleep 1; ./a.out. Do not touch the physical system's keyboard after starting the program, if you do you will have to stop and restart the program to reproduce the issue.
  9. Wait for a couple of "Keymap changed!" messages to be printed.
  10. In the SSH console, run mkdir -p ~/.config/labwc; cd ~/.config/labwc; vim environment, and set XKB_DEFAULT_LAYOUT to something other than whatever it is set to currently. (I've been switching between de and us for my testing.)
  11. In the SSH console, save and close the file, then run labwc --reconfigure. You should not see a new "Keymap changed!" line appear on the testing system.
  12. In the SSH console, change XKB_DEFAULT_LAYOUT in ~/.config/labwc/environment again, then run labwc --reconfigure again. You should still not see a new "Keymap changed!" line appear on the testing system.
  13. On the testing system, tap any key once. I usually just tap the left control key. You should immediately see a new "Keymap changed!" line appear.
  14. In the SSH console, change XKB_DEFAULT_LAYOUT in ~/.config/labwc/environment again, then run labwc --reconfigure again. This time, you should see a new "Keymap changed!" line appear on the testing system.

labwc build source

Release

labwc version

0.9.1

labwc environment

From a TTY or some display manager like lightdm

Distribution

Arch Linux

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions