Skip to content

Commit 41ae1e1

Browse files
committed
Create set-labwc-keymap script
1 parent 59d568f commit 41ae1e1

File tree

1 file changed

+254
-0
lines changed

1 file changed

+254
-0
lines changed

usr/bin/set-labwc-keymap

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
#!/bin/bash
2+
3+
## Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
4+
## See the file COPYING for copying conditions.
5+
6+
set -o errexit
7+
set -o nounset
8+
set -o errtrace
9+
set -o pipefail
10+
11+
true "$0: START"
12+
13+
xkb_variable_names=(
14+
'XKB_DEFAULT_LAYOUT'
15+
'XKB_DEFAULT_VARIANT'
16+
'XKB_DEFAULT_OPTIONS'
17+
)
18+
do_persist='false'
19+
20+
## Checks to see if all items in "check_str" are present in the output of a
21+
## command that lists valid items.
22+
is_layout_data_valid() {
23+
local valid_list_cmd check_str check_list check_item valid_item_list \
24+
valid_item is_item_valid
25+
26+
check_str="${1:-}"
27+
shift
28+
valid_list_cmd=( "$@" )
29+
if [ "${#valid_list_cmd[@]}" = '0' ] || [ -z "${valid_list_cmd[0]}" ]; then\
30+
return 1
31+
fi
32+
if [ -z "${check_str}" ]; then return 1; fi
33+
34+
readarray -t check_list < <(printf '%s\n' "${check_str}" | tr ',' '\n')
35+
readarray -t valid_item_list < <("${valid_list_cmd[@]}")
36+
37+
for check_item in "${check_list[@]}"; do
38+
if [ -z "${check_item}" ]; then continue; fi
39+
is_item_valid='false'
40+
for valid_item in "${valid_item_list[@]}"; do
41+
if [ "${check_item}" = "${valid_item}" ]; then
42+
is_item_valid='true'
43+
break
44+
fi
45+
done
46+
if [ "${is_item_valid}" = 'false' ]; then
47+
return 1
48+
fi
49+
done
50+
51+
return 0
52+
}
53+
54+
## Sets the XKB layout(s), variant(s), and option(s) in labwc, either for just
55+
## this session or persistently.
56+
set_labwc_keymap() {
57+
local labwc_config_path labwc_config_bak_path labwc_env_file_lines \
58+
did_replace_var var_idx conf_idx kb_layout_list kb_variant_list \
59+
kb_idx
60+
61+
labwc_config_path="${HOME}/.config/labwc/environment"
62+
labwc_config_bak_path=''
63+
labwc_env_file_lines=()
64+
args=( "$@" )
65+
if [ "${#args[@]}" != '3' ]; then
66+
printf '%s\n' "$0: ERROR: Argument handling failure!"
67+
return 1
68+
fi
69+
70+
## Ensure the user has no more than four keyboard layouts specified (this
71+
## is the maximum number supported by XKB under X11 according to
72+
## https://www.x.org/archive/X11R7.5/doc/input/XKB-Config.html, and it is
73+
## the maximum number labwc appears to support).
74+
readarray -t kb_layout_list < <(printf '%s\n' "${args[0]}" | tr ',' '\n')
75+
if (( ${#kb_layout_list[@]} > 4 )); then
76+
printf '%s\n' "$0: ERROR: Too many keyboard layouts specified, must specify 4 or less!"
77+
return 1
78+
fi
79+
80+
## Ensure the specified keyboard layout(s) are valid.
81+
if ! is_layout_data_valid "${args[0]}" \
82+
localectl list-x11-keymap-layouts ; then
83+
printf '%s\n' "$0: ERROR: Specified keyboard layout(s) are not all valid!"
84+
printf '%s\n' "$0: INFO: Run 'localectl list-x11-keymap-layouts' to get a list of valid layouts."
85+
return 1
86+
fi
87+
88+
## Ensure the specified keyboard layout variant(s) are valid.
89+
if [ -n "${args[1]:-}" ]; then
90+
readarray -t kb_variant_list < <(printf '%s\n' "${args[1]}" | tr ',' '\n')
91+
92+
if (( ${#kb_layout_list[@]} < ${#kb_variant_list[@]} )); then
93+
printf '%s\n' "$0: ERROR: Insufficient number of keyboard layouts specified for number of variants!"
94+
return 1
95+
fi
96+
97+
for kb_idx in "${!kb_layout_list[@]}"; do
98+
if [ -z "${kb_variant_list[kb_idx]}" ]; then continue; fi
99+
if ! is_layout_data_valid "${kb_variant_list[kb_idx]}" \
100+
localectl list-x11-keymap-variants "${kb_layout_list[kb_idx]}" ; then
101+
printf '%s\n' "$0: ERROR: Specified keyboard layout variant for layout ${kb_layout_list[kb_idx]} is not valid!"
102+
printf '%s\n' "$0: INFO: Run 'localectl list-x11-keymap-variants ${kb_layout_list[kb_idx]}' to get a list of valid layout variants for the ${kb_layout_list[kb_idx]} layout."
103+
return 1
104+
fi
105+
done
106+
fi
107+
108+
## Ensure the specified keyboard option(s) are valid.
109+
if [ -n "${args[2]:-}" ]; then
110+
if ! is_layout_data_valid "${args[2]}" \
111+
localectl list-x11-keymap-options ; then
112+
printf '%s\n' "$0: ERROR: Specified keyboard layout option(s) are not valid!"
113+
printf '%s\n' "$0: INFO: Run 'localectl list-x11-keymap-options' to get a list of valid layout options."
114+
return 1
115+
fi
116+
fi
117+
118+
## Ensure the labwc configuration directory exists.
119+
mkdir -p "$(dirname "${labwc_config_path}")" || {
120+
printf '%s\n' "$0: ERROR: Cannot create labwc config directory!"
121+
return 1
122+
}
123+
124+
## If labwc's environment config file exists, read it.
125+
if [ -f "${labwc_config_path}" ]; then
126+
if ! [ -r "${labwc_config_path}" ]; then
127+
printf '%s\n' "$0: ERROR: Cannot read existing labwc environment config!"
128+
return 1
129+
fi
130+
131+
readarray -t labwc_env_file_lines < "${labwc_config_path}"
132+
133+
## If we do not want the new configuration to be persistent, move the
134+
## existing configuration to a temporary backup location.
135+
if [ "${do_persist}" = 'false' ]; then
136+
labwc_config_bak_path="$(mktemp)"
137+
mv "${labwc_config_path}" "${labwc_config_bak_path}" || {
138+
printf '%s\n' "$0: ERROR: Cannot move existing labwc environment config to backup location!"
139+
return 1
140+
}
141+
fi
142+
fi
143+
144+
## Loop through the variables we want to set. For each variable in `args`,
145+
## the variable's name is specified in the corresponding element in
146+
## `xkb_variable_names`.
147+
for var_idx in "${!args[@]}"; do
148+
if (( var_idx >= 3 )); then break; fi
149+
150+
## Loop through the lines of the existing labwc environment configuration.
151+
## Change already-set keyboard-layout-related variables to new values,
152+
## append variables that we want to set but that aren't defined yet.
153+
## Remove duplicate variable assignments so that the variables we set
154+
## don't get overridden.
155+
did_replace_var='false'
156+
for conf_idx in "${!labwc_env_file_lines[@]}"; do
157+
## Note that we use a capturing group around leading whitespace - this
158+
## allows us to prepend ${BASH_REMATCH[1]} to the replacement line,
159+
## preserving the indentation.
160+
if [[ "${labwc_env_file_lines[conf_idx]}" \
161+
=~ ^([[:space:]]*)"${xkb_variable_names[var_idx]}=" ]]; then
162+
if [ "${did_replace_var}" = 'false' ]; then
163+
labwc_env_file_lines[conf_idx]="${BASH_REMATCH[1]}${xkb_variable_names[var_idx]}=${args[var_idx]}"
164+
did_replace_var='true'
165+
else
166+
unset "labwc_env_file_lines[${conf_idx}]"
167+
fi
168+
fi
169+
done
170+
171+
## Remove any holes from the `labwc_env_file_lines` array.
172+
labwc_env_file_lines=( "${labwc_env_file_lines[@]}" )
173+
174+
## If we replaced a variable assignment in the configuration file, skip
175+
## the rest of this loop iteration.
176+
if [ "${did_replace_var}" = 'true' ]; then
177+
continue
178+
fi
179+
180+
## Append the new environment variable to the configuration file's
181+
## contents.
182+
labwc_env_file_lines+=(
183+
"${xkb_variable_names[var_idx]}=${args[var_idx]}"
184+
)
185+
done
186+
187+
## Write the new config file contents and load them into labwc.
188+
printf '%s\n' "${labwc_env_file_lines[@]}" > "${labwc_config_path}" || {
189+
printf '%s\n' "$0: ERROR: Cannot write new labwc environment config!"
190+
return 1
191+
}
192+
labwc --reconfigure \
193+
|| printf '%s\n' "$0: WARNING: labwc reconfiguration failed!"
194+
195+
## If we do not want to persist the new configuration, put the old
196+
## configuration back (or just delete the new config file if there wasn't an
197+
## old config file).
198+
if [ -n "${labwc_config_bak_path}" ]; then
199+
mv "${labwc_config_bak_path}" "${labwc_config_path}" || {
200+
printf '%s\n' "$0: ERROR: Cannot move backup labwc environment config to original location!"
201+
return 1
202+
}
203+
elif [ "${do_persist}" = 'false' ]; then
204+
safe-rm "${labwc_config_path}" || {
205+
printf '%s\n' "$0: ERROR: Cannot remove temporary labwc environment config!"
206+
return 1
207+
}
208+
fi
209+
}
210+
211+
print_usage() {
212+
printf '%s\n' "$0: Set keyboard layout for labwc"
213+
printf '%s\n' "Usage: $0 [--help] [--persist] [layout [variant [option]]]"
214+
printf '%s\n' 'Options:'
215+
printf '%s\n' '--help Print this help message'
216+
printf '%s\n' '--persist Make layout change persistent'
217+
printf '\n'
218+
printf '%s\n' 'Examples:'
219+
printf '%s\n' ' set-labwc-keymap de'
220+
printf '%s\n' ' set-labwc-keymap --persist us colemak'
221+
printf '%s\n' ' set-labwc-keymap us,cz,de "" grp:alt_shift_toggle'
222+
}
223+
224+
if [ "${1:-}" = '--persist' ]; then
225+
do_persist='true'
226+
shift
227+
fi
228+
if [ "${1:-}" = '--help' ] || [ "${1:-}" = '-h' ]; then
229+
print_usage
230+
exit 0
231+
fi
232+
233+
args=( "$@" )
234+
## We must have at least one, but no more than three, arguments specifying the
235+
## keyboard layout(s).
236+
if [ "${#args[@]}" = '0' ] || [ -z "${args[0]:-}" ] \
237+
|| (( ${#args[@]} > 3 )); then
238+
print_usage
239+
exit 1
240+
fi
241+
242+
## If we have less than three arguments, populate the `args` array with empty
243+
## strings for the remaining arguments. This will make labwc unset the
244+
## corresponding XKB environment variables internally, allowing the user to
245+
## run something like `set-labwc-keymap us colemak`, then
246+
## `set-labwc-keymap us` and have their keyboard not stuck in the Colemak
247+
## layout after the second command.
248+
while (( ${#args[@]} < 3 )); do
249+
args+=( '' )
250+
done
251+
252+
set_labwc_keymap "${args[@]}" || exit
253+
254+
true "$0: END"

0 commit comments

Comments
 (0)