diff --git a/README.md b/README.md index 2e3b74f..b1c710d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ # xkbhyprmodfix -Replace CapsLock with Hyper_L without keybinds assigned to Hyper clashing with other modifiers on Mod4. \ No newline at end of file +Replace CapsLock with Hyper_L without keybinds assigned to Hyper clashing with those on Super. + +Creates a new xkb keymap, "HyperCaps", identical to the user's current keyboard layout, except with CapsLock replaced with Hyper and Hyper_L moved to Mod3. The symbol file for the current layout and the xkb rules files in `/usr/share/X11/xkb` are cloned to `/usr/local/share/X11/xkb/`, edited, and then symlinked to. The original unmodified files are backed up. + +This is a workaround to desktop environments such as KDE overwriting any keyboard setup done with xinit configuration files. + +Must be run as root (to grant access to `/usr/share` and `/usr/local/share`). diff --git a/xkbhyprmodfix.sh b/xkbhyprmodfix.sh new file mode 100755 index 0000000..9b9b50b --- /dev/null +++ b/xkbhyprmodfix.sh @@ -0,0 +1,126 @@ +#!/bin/sh + +# test for root +[ $(id -u) = 0 ] || { printf "%s\n" "xkbhyprmodfix: must be run as root user"; exit 2; } + +rundate="$(date '+%s')" # UNIX timestamp for backups + +# get keyboard layout info +xkbinfo=$(setxkbmap -query) +layouts=$(echo "$xkbinfo" | grep "^layout:" | sed -E "s/layout:[[:space:]]+//g") +variants=$(echo "$xkbinfo" | grep "^variant:" | sed -E "s/variant:[[:space:]]+//g") + +# extract layout and variant settings if only one setting, otherwise ask the user to select which layout they want to modify +echo "$layouts" | grep -q "," || { layout="$layouts"; variant="$variants"; } +while ! [ "$layout" ]; do + layoutcount=$(echo "$layouts" | tr ',' ' ' | wc -w) + printf "%s " "Multiple keyboard layouts detected ($layouts). Select one to proceed (1-$layoutcount)" + read -r selection + case "$selection" in + ''|*[!0-9]*) + printf "%s\n" "Invalid selection '$selection'." + continue + ;; + esac + { [ "$selection" -gt 0 ] && [ "$selection" -le "$layoutcount" ]; } || { printf "%s\n" "Invalid selection '$selection'."; continue; } + layout=$(echo "$layouts" | cut -d"," -f"$selection") + variant=$(echo "$variants" | cut -d"," -f"$selection") +done + +# new keyboard will duplicate the existing one +# the default layout of that language is not a variant, meaning $variant will be empty, but is referred to as "basic" in the symbol file +# $variant is copied into a new variable which is set to "basic" if blank +# similarly, "basic" is a valid variant but is considered as no variant in the .lst files +# $variant is set to blank if it is "basic" +variant_inherit="$variant" +[ -n "$variant_inherit" ] || variant_inherit="basic" +[ "$variant" = "basic" ] && variant= + +xkbdir=/usr/share/X11/xkb +localxkbdir=/usr/local/share/X11/xkb_hypercaps +locallayout="$localxkbdir"/symbols/"$layout" + +[ -e "$localxkbdir" ] && { + printf "%s " "Filepath '$localxkbdir' exists. Files found at this path or its subdirectories may be overwritten. Continue? (y/n)" + read -r selection + [ "$selection" = "y" ] || [ "$selection" = "Y" ] || { printf "%s\n" "Exiting."; exit 2; } +} + +# make sure the layout exists in symbols and hypercaps layout is not already applied +[ -f "$xkbdir"/symbols/"$layout" ] || { printf "%s\n" "Error: $xkbdir/symbols/$layout not found. Exiting."; exit 2; } +grep -q 'xkb_symbols "hypercaps" {' "$xkbdir"/symbols/"$layout" && { printf "%s\n" "Error: hypercaps layout for layout '$layout' already exists."; exit 2; } + +# Get layout title +lstfile=$(wc -l "$xkbdir"/rules/*.lst | grep -v "total$" | sort -b -r -h | sed 's/^[[:space:]]\+[0-9]\+ //g; 1q') # Use the longest .lst file +[ -n "$variant" ] \ + && lstpattern="^[[:space:]]{2}${variant}[[:space:]]+$layout:[[:space:]]" \ + || lstpattern="^[[:space:]]{2}${layout}[[:space:]]{8,}" +layouttitle=$(grep -E "$lstpattern" "$lstfile" | head -n 1 | sed -E "s/$lstpattern//g") +[ -n "$layouttitle" ] || { printf "%s\n" "Warning: Keyboard layout name not found in '$lstfile'. Defaulting to '$layout (HyperCaps)' as name."; layouttitle="$layout ()"; } +hypercapstitle=$(echo "$layouttitle" | sed -E 's/\((.+)\)$/\(\1, HyperCaps\)/; s/\(\)/(HyperCaps)/') +echo "$hypercapstitle" | grep -q "HyperCaps)" || hypercapstitle="$layouttitle (HyperCaps)" + +# file setup +mkdir -p "$localxkbdir"/symbols +mkdir -p "$localxkbdir"/rules +cp -f "$xkbdir"/symbols/"$layout" "$locallayout" +cp -f -d -t "$localxkbdir"/rules/ "$xkbdir"/rules/base.xml "$xkbdir"/rules/base.lst \ + "$xkbdir"/rules/evdev.xml "$xkbdir"/rules/evdev.lst \ + "$xkbdir"/rules/xorg.xml "$xkbdir"/rules/xorg.lst + +# append new keyboard layout to copied symbol file if it does not already exist +grep -q 'xkb_symbols "hypercaps" {' "$locallayout" || \ + printf "\n%s\n%s\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s\n%s\n" \ + "partial alphanumeric_keys" \ + "xkb_symbols \"hypercaps\" {" \ + "include \"$layout($variant_inherit)\"" \ + "replace key { [ Super_L, Super_L ] };" \ + "name[Group1]=\"$hypercapstitle\";" \ + "key { [ Hyper_L ] };" \ + "modifier_map Mod3 { };" \ + "};" >> "$locallayout" + + +# modify copied rule files if layout is not already applied +grep -q "$hypercapstitle" "$localxkbdir"/rules/*.xml || \ + sed -i '/'"$layouttitle"'<\/description>/,/<\/layout>/{/<\/variantList>/i\ + \ + \ + hypercaps\ + '"$hypercapstitle"'\ + \ + + }' "$localxkbdir"/rules/base.xml "$localxkbdir"/rules/evdev.xml + +grep -q "$hypercapstitle" "$localxkbdir"/rules/*.xml || \ + sed -i '/'"$layouttitle"'<\/description>/,/<\/layout>/{/<\/configItem>/a\ + \ + \ + \ + hypercaps\ + '"$hypercapstitle"'\ + \ + \ + + }' "$localxkbdir"/rules/base.xml "$localxkbdir"/rules/evdev.xml + + +grep -q "hypercaps $layout: $hypercapstitle" "$localxkbdir"/rules/*.lst || \ + sed -i "/^! variant$/a \ \ hypercaps $layout: $hypercapstitle" "$localxkbdir"/rules/*.lst + +# +# danger zone +# + +# backup original files and alias modified copies +cp -d -f "$xkbdir"/symbols/"$layout" "$xkbdir"/symbols/"${layout}"."${rundate}".bak \ + && ln -s -f "$locallayout" "$xkbdir"/symbols/"$layout" \ + || { printf "%s\n" "Unable to apply modified symbol layout to $xkbdir/symbols/$layout"; exit 2; } + +for ext in xml lst; do + for file in base evdev xorg; do + cp -d -f "$xkbdir"/rules/"${file}"."${ext}" "$xkbdir"/rules/"${file}"."${rundate}"."${ext}" \ + && ln -s -f "$localxkbdir"/rules/"${file}"."${ext}" "$xkbdir"/rules/"${file}"."${ext}" \ + || { printf "%s\n" "Unable to replace rule file '${file}.${ext}"; exit 2; } + done +done