Skip to content

Commit 4c2e30e

Browse files
committed
Merge remote-tracking branch 'origin/main' into pyqt6-migration
2 parents 8f7bbb5 + b12c617 commit 4c2e30e

File tree

11 files changed

+495
-3
lines changed

11 files changed

+495
-3
lines changed

linux/appimage/apprun.sh

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,44 @@ appimage_python()
6262
exec "${APPDIR}/usr/bin/python" "$@"
6363
}
6464

65+
install_udev_rule()
66+
{
67+
# Pass through variables because pkexec doesn't pass through env
68+
local UDEV_RULE_FILE="$1"
69+
local USER="$2"
70+
if ! grep -q "^plover:" /etc/group; then
71+
groupadd plover
72+
fi
73+
# NOTE: this requires a reboot!
74+
if ! groups "$USER" | grep -qw "plover"; then
75+
usermod -aG plover "$USER"
76+
fi
77+
if [ ! -f "$UDEV_RULE_FILE" ]; then
78+
echo 'KERNEL=="uinput", GROUP="plover", MODE="0660", OPTIONS+="static_node=uinput"' > "$UDEV_RULE_FILE"
79+
chmod 644 "$UDEV_RULE_FILE"
80+
udevadm control --reload-rules
81+
udevadm trigger
82+
# Temporarily give the current user access
83+
# This is done because the groupadd does not take effect until next reboot
84+
# And this temporary solution works *until* the next reboot
85+
# FIXME if someone can find a better solution
86+
chown "${USER}:plover" /dev/uinput
87+
chmod 660 /dev/uinput
88+
fi
89+
}
90+
6591
appimage_launch()
6692
{
93+
# Install the udev rule required for uinput
94+
UDEV_RULE_FILE="/etc/udev/rules.d/99-plover-uinput.rules"
95+
# It's done like this to have the lowest possible number of pkexec calls
96+
# Each time it's called, the user gets shown a new password input dialog
97+
# FIXME if there is an easier way to do it
98+
if [ ! -f "$UDEV_RULE_FILE" ] || ! grep -q "^plover:" /etc/group || ! groups | grep -qw "plover"; then
99+
notify-send -t 10000 "Installing udev rules" "You will be prompted for your password"
100+
pkexec bash -c "$(declare -f install_udev_rule); install_udev_rule '$UDEV_RULE_FILE' '$USER'"
101+
notify-send -t 10000 "Successfully installed udev rules" "A reboot may be required for output to work"
102+
fi
67103
appimage_python -s -m plover.scripts.dist_main "$@"
68104
}
69105

news.d/feature/1679.linux.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added keyboard emulation and capture using uinput, compatible with X11, Wayland and anything else on linux and bsd.

plover/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ def _set(self, section, option, value):
338338
boolean_option('start_capitalized', False, OUTPUT_CONFIG_SECTION),
339339
int_option('undo_levels', DEFAULT_UNDO_LEVELS, MINIMUM_UNDO_LEVELS, None, OUTPUT_CONFIG_SECTION),
340340
int_option('time_between_key_presses', DEFAULT_TIME_BETWEEN_KEY_PRESSES, MINIMUM_TIME_BETWEEN_KEY_PRESSES, None, OUTPUT_CONFIG_SECTION),
341+
choice_option("keyboard_layout", ("qwerty", "qwertz", "colemak", "colemak-dh"), OUTPUT_CONFIG_SECTION),
341342
# Logging.
342343
path_option('log_file_name', expand_path('strokes.log'), LOGGING_CONFIG_SECTION, 'log_file'),
343344
boolean_option('enable_stroke_logging', False, LOGGING_CONFIG_SECTION),

plover/engine.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,9 @@ def _update(self, config_update=None, full=False, reset_machine=False):
212212
self._formatter.start_capitalized = config['start_capitalized']
213213
self._translator.set_min_undo_length(config['undo_levels'])
214214
self._keyboard_emulation.set_key_press_delay(config['time_between_key_presses'])
215+
# This only applies to UInput, because it emulates a physical keyboard and follows the layout set in software. Because there is no standard of defining it, the user has to do so manually if not using a QWERTY keyboard.
216+
if hasattr(self._keyboard_emulation, '_update_layout'):
217+
self._keyboard_emulation._update_layout(config["keyboard_layout"])
215218
# Update system.
216219
system_name = config['system_name']
217220
if system.NAME != system_name:

plover/gui_qt/config_window.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,15 @@ def __init__(self, engine):
392392
'programs time to process each key press.\n'
393393
'Setting the delay too high will negatively impact the\n'
394394
'performance of key stroke output.')),
395+
ConfigOption(_("Linux keyboard layout:"), "keyboard_layout",
396+
partial(ChoiceOption, choices={
397+
"qwerty": "qwerty",
398+
"qwertz": "qwertz",
399+
"colemak": "colemak",
400+
"colemak-dh": "colemak-dh",
401+
}),
402+
_("Set the keyboard layout configurad in your system.\n"
403+
"This only applies when using Linux/BSD and not using X11."))
395404
)),
396405
# i18n: Widget: “ConfigWindow”.
397406
(_('Plugins'), (

plover/gui_qt/main_window.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from PyQt6.QtWidgets import (
1010
QMainWindow,
1111
QMenu,
12+
QApplication,
1213
)
1314

1415
from plover import _, log
@@ -147,6 +148,9 @@ def set_visible(self, visible):
147148
else:
148149
self.showMinimized()
149150

151+
def _is_wayland(self):
152+
return "wayland" in QApplication.platformName().lower()
153+
150154
def _activate_dialog(self, name, args=(), manage_windows=False):
151155
if manage_windows:
152156
previous_window = wmctrl.GetForegroundWindow()
@@ -162,7 +166,10 @@ def on_finished():
162166
wmctrl.SetForegroundWindow(previous_window)
163167
dialog.finished.connect(on_finished)
164168
dialog.showNormal()
165-
dialog.activateWindow()
169+
if not self._is_wayland():
170+
# Otherwise gives this warning:
171+
# Qt: Wayland does not support QWindow::requestActivate()
172+
dialog.activateWindow()
166173
dialog.raise_()
167174

168175
def _add_translation(self, dictionary=None, manage_windows=False):
@@ -173,7 +180,8 @@ def _add_translation(self, dictionary=None, manage_windows=False):
173180

174181
def _focus(self):
175182
self.showNormal()
176-
self.activateWindow()
183+
if not self._is_wayland():
184+
self.activateWindow()
177185
self.raise_()
178186

179187
def _configure(self, manage_windows=False):
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import os
2+
3+
"""
4+
This value should be one of:
5+
- x11
6+
- wayland
7+
- tty
8+
"""
9+
DISPLAY_SERVER = os.environ.get("XDG_SESSION_TYPE", None)
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
1-
from .keyboardcontrol_x11 import KeyboardCapture, KeyboardEmulation # pylint: disable=unused-import
1+
from .display_server import DISPLAY_SERVER
2+
3+
if DISPLAY_SERVER == "x11":
4+
from .keyboardcontrol_x11 import KeyboardCapture, KeyboardEmulation # pylint: disable=unused-import
5+
else:
6+
from .keyboardcontrol_uinput import KeyboardCapture, KeyboardEmulation #pylint: disable=unused-import

0 commit comments

Comments
 (0)