Hi!

Using i3wm? Have a fancy keyboard with a nice CapsLock LED, but never actually press CapsLock (not as a remapped Ctrl)?

Me too! Let's convert it into a keyboard layout indicator then!

You can use method described below for things other than keyboard layout and for other LEDs. Happy hacking!

Requirements

  • i3 - targeted window manager, we will use its IPC to subscribe to window change event
  • perl with AnyEvent::I3 - to communicate with i3 IPC
  • bash with grep, sed and xargs utils
  • brightnessctl - to control LED
  • xkb-switch - to monitor keyboard layout change event
  • i3-subscribe.pl - perl script for i3 IPC (source)
  • toggle_capslock_led.sh - our bash script for toggling LED
  • i3ipc_capslock_led.sh - our bash script to start listening for window and layout changes

All scripts below are supposed to be put into ~/Scripts directory - change this path if you need to.

General method

We have a bash script, which checks current keyboard layout and turns the LED on when specified layout is active.

We call this script:

  • when the layout changes
  • when we go to other window (as each window has its own keyboard layout state)

This is the script (toggle_capslock_led.sh) with some comments inside:

#!/usr/bin/env bash
# Lights up capslock LED when specified layout is on.
# Works for LED on all connected keyboards (e.g. laptop's and external one).

# You can also set this to your specific input name, which can be found with: brightnessctl --list
# LED_NAMES="input7::capslock"
LED_NAMES=$(brightnessctl --list | grep '::capslock' | sed 's/Device .\(.*\). of.*/\1/g')

# The layout on which we want LED to be on.
# Get available layout names with: xkb-switch -l
ON_LAYOUT="us"
CURRENT_LAYOUT=$(xkb-switch -p)

VALUE=0
if [[ "$CURRENT_LAYOUT" == "$ON_LAYOUT" ]]; then
  VALUE=1
fi

echo "$LED_NAMES" | while read name; do
  brightnessctl --device="$name" set $VALUE > /dev/null
done

Talking to i3 IPC

Below is the i3-subscribe.pl script for talking to i3 with perl.

To run it you will need to install AnyEvent::I3 for perl.

If you are on NixOS - leave script as it is (it uses nix-shell with perl540Packages.AnyEventI3).

If you aren't on NixOS - replace first two lines with this one: #!/usr/bin/env perl.

#!/usr/bin/env nix-shell
#!nix-shell -i perl -p perl perl540Packages.AnyEventI3
# Subscribe to i3wm events via i3ipc.
# Source: https://faq.i3wm.org/question/5721/how-do-i-subscribe-to-i3-events-using-bash-easily.1.html
#
# Usage example for bash:
# i3subscribe window workspace | while read -r event; do
# ...
# done

BEGIN { $| = 1 } # flush \n

use strict;
use warnings;
use Data::Dumper;
use AnyEvent::I3;
use v5.10;

my $i3 = i3();
$i3->connect->recv or die "Error connecting to i3";

sub subscribe {
    my $ev = $_[0];
    my $dump = $_[1];
    if($i3->subscribe({
        $ev => sub {
            my ($msg) = @_;
            say "$ev:$msg->{'change'}";
            if($dump) {
                print Dumper($msg);
            }
        }
    })->recv->{success}) {
        say "Successfully subscribed to $ev-event";
    }
}

my $nextArg = shift;
if(!$nextArg) {
    say "Subscribe to i3-events";
    say "Usage:   $0 workspace|output|mode|window|barconfig_update|binding [dump]";
    say "Example: $0 workspace dump window binding dump";
    exit 1;
}
while($nextArg) {
    my $arg = $nextArg;
    $nextArg = shift;
    my $dump = 0;
    if($nextArg and $nextArg eq "dump") {
        $dump = 1;
        $nextArg = shift;
    }
    subscribe("$arg", $dump);
}
AE::cv->recv;

Toggle LED

This is i3ipc_capslock_led.sh which starts needed listeners - add this script to autostart and that's it:

#!/usr/bin/env bash
# Executes `toggle_capslock_led.sh` script on window change.
# Source: https://faq.i3wm.org/question/5721/how-do-i-subscribe-to-i3-events-using-bash-easily.1.html

# Run LED switcher on layout change
xkb-switch -W | xargs -I{} ~/Scripts/toggle_capslock_led.sh &

# Run LED switcher on window or workspace change
~/Scripts/i3-subscribe.pl window workspace | while read -r event; do
  ~/Scripts/toggle_capslock_led.sh
done

Bye!