aboutsummaryrefslogtreecommitdiff
path: root/content/posts/switch-virtual-keyboard.md
blob: b554757d13fdfeba9db9f196dcd89c20616b2f88 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
---
title: "Automatically Switching the Virtual Keyboard in KDE Plasma"
date: 2024-09-25T21:54:00-04:00
description: "A Bash script using D-Bus to switch the KDE Plasma Wayland virtual keyboard when entering and exiting tablet mode"
type: "post"
tags: ["obscure", "bash", "fish", "kde", "wayland", "dbus", "systemd", "linux"]
---


Welcome back to yet another episode of solving extremely obscure Linux problems that probably zero other people worldwide are suffering from! This time, the problem is that on my convertible laptop, I sometimes use both Fcitx 5 for typing Chinese and Maliit Keyboard as a touchscreen keyboard. In Wayland, both of these use the [same virtual keyboard protocol](https://wayland.app/protocols/virtual-keyboard-unstable-v1) and thus only one of them can be active at the same time. I'm not a Wayland expert so this actually might be a limitation in KWin, not in the Wayland protocol. (I have a friend who's a Wayland expert but that's because he lives there. Wayland the town, not the protocol.) Fortunately, I only use Fcitx 5 when the laptop is in its normal mode with the physical keyboard accessible, and Maliit Keyboard when the laptop is in tablet mode with the physical keyboard folded back. So there lies the problem: How do I switch the virtual keyboard when entering and exiting tablet mode?

It's easy to manually do this in the Plasma system settings app. Sure, Plasma is really nice and customizable and for the most part works really well on convertible laptops, but I'm over here tearing my hair out since there's no easy way to configure it from the command line or from a script! They do provide a CLI utility to edit Plasma config files, for instance, `kwriteconfig6 --file kwinrc --group Wayland --key InputMethod /usr/share/applications/fcitx5-wayland-launcher.desktop` to set the virtual keyboard to Fcitx 5, but Plasma (specifically KWin) doesn't recognize the config change. I suspected the system settings app does some additional D-Bus shenanigans, and after dredging through the output of `dbus-monitor`, I found I was right!

```
signal time=1727303907.040280 sender=:1.403 -> destination=(null destination) serial=113 path=/kwinrc; interface=org.kde.kconfig.notify; member=ConfigChanged
   array [
      dict entry(
         string "Wayland"
         array [
            array of bytes "InputMethod"
         ]
      )
   ]
```

Basically, that particular D-Bus signal tells KWin to peak at the new value of `InputMethod`. That translates into the command `busctl --user emit /kwinrc org.kde.kconfig.notify ConfigChanged "a{saay}" 1 Wayland 1 11 73 110 112 117 116 77 101 116 104 111 100`. The weird numbers are just `InputMethod` in ASCII as an array of bytes. Did you know D-Bus has its own [type and serialization system](https://dbus.freedesktop.org/doc/dbus-specification.html#type-system)? Well, I had to learn it for that command. It's also possible to detect entering and exiting tablet mode using D-Bus.

Alright, so now time for a Bash script, in `~/.config/autostart/switchvirtualkeyboard.sh`:
```bash
#!/bin/bash

switch() {
	echo "Starting $1"
	kwriteconfig6 --file kwinrc --group Wayland --key InputMethod "$1"
	busctl --user emit /kwinrc org.kde.kconfig.notify ConfigChanged "a{saay}" 1 Wayland 1 11 73 110 112 117 116 77 101 116 104 111 100
}

busctl --user monitor --match "type='signal',interface='org.kde.KWin.TabletModeManager',member='tabletModeChanged'" | \
while read -r line; do
	if [[ $line == *"true"* ]]; then
		switch /usr/share/applications/com.github.maliit.keyboard.desktop
	elif [[ $line == *"false"* ]]; then
		switch /usr/share/applications/fcitx5-wayland-launcher.desktop
	fi
done
```

Or alternatively, a Fish version of the same script:
```fish
function setvirtualkeyboard
    echo "Starting $argv[1]"
    kwriteconfig6 --file kwinrc --group Wayland --key InputMethod "$argv[1]"
    busctl --user emit /kwinrc org.kde.kconfig.notify ConfigChanged "a{saay}" 1 Wayland 1 11 73 110 112 117 116 77 101 116 104 111 100
end

function switchvirtualkeyboard
    busctl --user monitor --match "type='signal',interface='org.kde.KWin.TabletModeManager',member='tabletModeChanged'" | \
    while read -l line
        if string match -q "*true*" $line
            setvirtualkeyboard /usr/share/applications/com.github.maliit.keyboard.desktop
        else if string match -q "*false*" $line
            setvirtualkeyboard /usr/share/applications/fcitx5-wayland-launcher.desktop
        end
    end
end
```

To make Plasma start the Bash script at login, add a file `~/.config/autostart/switchvirtualkeyboard.desktop`. Internally, systemd-xdg-autostart-generator translates the `.desktop` file into a systemd service.
```ini
[Desktop Entry]
Exec=bash ~/.config/autostart/switchvirtualkeyboard.sh
Name=switchvirtualkeyboard
Type=Application
```

For the Fish version, you can directly write a systemd service and start and enable it:
```ini
[Unit]
Description=Switch the virtual keyboard when entering and exiting tablet mode

[Service]
ExecStart=fish -c switchvirtualkeyboard

[Install]
WantedBy=default.target
```

As you might expect from all these obscure Linux components, there's not much documentation about this stuff and the script was really tricky to debug. It works now, but it took me way more time to write this than to the amount of time it would hypothetically take me to manually switch the virtual keyboard in the settings app every time in the future. But hey, I got to learn some cool things about D-Bus and systemd!

Lastly, this is just one way that I found to accomplish this task, but I bet there are better ways. For instance, I considered writing a KWin script instead, but the API seemed a bit limited, since for instance, `callDBus` can only call D-Bus methods and can't send D-Bus signals. If you have any ideas for improvement, I'd love to hear it!