Skip to content

Instantly share code, notes, and snippets.

@oficsu
Last active December 18, 2024 11:35
Show Gist options
  • Save oficsu/0b2959f25da69815f642e6e7f6e65044 to your computer and use it in GitHub Desktop.
Save oficsu/0b2959f25da69815f642e6e7f6e65044 to your computer and use it in GitHub Desktop.
Enable only us keyboard layout in the KDE Plasma screenlocker and disable others
#!/bin/bash
# MIT License
#
# Copyright (c) 2024 Ofee Oficsu
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# inspired by:
# https://alexsorokin.ru/2019/01/plasma-lock-screen-kb-layout
# https://superuser.com/a/898991
# https://superuser.com/a/820619
# https://unix.stackexchange.com/a/368632
# https://gist.github.com/bojidar-bg/5dd246cd3c442893a333b36955ae0753
# https://discuss.kde.org/t/re-read-configuration-after-setting-change/15008
PLASMA_VERSION_MAJOR=$(kwin_x11 --version | awk '{print $2}' | awk -F '.' '{print $1}')
kwriteconfig() {
command "kwriteconfig$PLASMA_VERSION_MAJOR" "$@"
}
read-default-layout-list() {
grep -oP 'LayoutList=\K.*' "$HOME/.config/kxkbrc"
}
update-layouts() {
local list="$1"
kwriteconfig --file kxkbrc --group Layout --key LayoutList "$list"
}
apply-layouts() {
dbus-send --session --type=signal --reply-timeout=1000 \
--dest=org.kde.keyboard /Layouts \
org.kde.keyboard.reloadConfig
}
monitor-screensaver() {
local signal="type=signal"
local saver="path=/ScreenSaver"
local freedesktop_saver="interface=org.freedesktop.ScreenSaver"
local kde_saver="interface=org.kde.screensaver"
local layouts="interface=org.kde.KeyboardLayouts"
# well-documented way to catch screensaver state changes
local state="$signal,$saver,$freedesktop_saver,member=ActiveChanged"
# is not well documented, but it is triggered before the
# lockscreen appears, which hides flickering of the text
local about="$signal,$saver,$kde_saver,member=AboutToLock"
local layouts="$layouts,member=layoutListChanged"
dbus-monitor "$state" "$about" "$layouts"
}
is-ready-to-lockswitch() {
local prev="$1"
local line="$2"
grep -q "AboutToLock" <<< "$line" \
&& return 0
# on kwin_x11 sometimes windows (including screenlocker)
# are skipped, so do it second time anyway, just in case
# also would be helpful if 'AboutToLock' api was changed
grep -q "ActiveChanged" <<< "$prev" \
&& grep -q "boolean true" <<< "$line" \
&& return 0
return 1
}
is-ready-to-unlockswitch() {
local prev="$1"
local line="$2"
grep -q "ActiveChanged" <<< "$prev" \
&& grep -q "boolean false" <<< "$line"
}
request-layout-list() {
dbus-send --print-reply --session --reply-timeout=500 \
--dest=org.kde.keyboard /Layouts \
org.kde.KeyboardLayouts.getLayoutsList
}
has-us-layout() {
local stdin="$1"
echo "$stdin" | grep -q 'string "us"'
}
is-single-us-layout() {
local stdin=$(cat)
local layout_count="$(echo "$stdin" | grep struct -c)"
local is_us=$(echo "$stdin" | grep -q 'string "us"')
[ "$layout_count" == 1 ] \
&& has-us-layout "$stdin"
}
is-layout-list-changed() {
local line="$1"
local time="$2"
local lasttime="$3"
local state="$4"
grep -q "layoutListChanged" <<< "$line" \
&& [ "$time" -gt "$(($lasttime + 1))" ] \
&& [ "$state" == unlocked ] \
&& (request-layout-list | is-single-us-layout)
}
process() {
local default_layouts="$(read-default-layout-list)"
local state=unlocked
local time lasttime="$(date +%s)"
local line prev
while read -r line; do
time="$(date +%s)"
if is-ready-to-lockswitch "$prev" "$line"; then
default_layouts="$(read-default-layout-list)"
echo "locked, set layout to 'us'"
update-layouts "us"
apply-layouts
# apply-layouts is non-blocking, which can result in
# a race condition with the following update-layouts
sleep 1
# make sure default layouts outlive lockscreen crash
update-layouts "$default_layouts"
state=locked
elif is-ready-to-unlockswitch "$prev" "$line"; then
echo "unlocked, set layout to 'us,ru'"
update-layouts "$default_layouts"
apply-layouts
state=unlocked
elif is-layout-list-changed "$line" "$time" "$lasttime" "$state"; then
echo "xorg bug: layout list wasn't recovered for a window"
apply-layouts
lasttime="$(date +%s)"
fi
prev="$line"
done
}
monitor-screensaver | process
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment