Waybar: Feature request: keyboard layout

Created on 28 Oct 2018  Β·  27Comments  Β·  Source: Alexays/Waybar

Would be really nice to have the module showing current keyboard layout. Or maybe a help to write the custom module script with this capability.

enhancement

Most helpful comment

here's a version without jq, only unix utils, shows EN by default, and first two upper case letters otherwise

  "custom/lang": {
    "exec": "swaymsg -t get_inputs | grep layout_name | grep -v En | awk 'END{if(NR == 0){print \"EN\"}else{print toupper(substr($2,2,2))}}'",
    "interval": 1,
    "format": "{} ο„œ"
  },

All 27 comments

Create a custom module with something like this in a script and run it in the exec: ?
setxkbmap -query | grep layout | cut -f2 -d":" | tr -d '[:space:]'

@harishkrupo it doesn't work. It always shows us (my default layout) even when I use another layout.

okay, will need to check. How do you pick a different layout?

@harishkrupo If you're using Sway: https://github.com/swaywm/sway/wiki#input-configuration

I have opened a PR for this issue: https://github.com/Alexays/Waybar/pull/85

if somebody needs it:

"custom/lang": {
    "exec": "swaymsg -t get_inputs|grep -4 'AT T'|tail -1|sed 's/.*: //;s/\"//g'|cut -c 1-2|tr '[a-z]' '[A-Z]'",
    "interval": 1
},
  • Note: grep -4 'AT T' finds my particular keyboard, you probably want to change values in the script

this polls it every second, but ideally it should work as in pulseaudio module, to update whenever layout updates.
for that it needs some integration with wlroots/sway or something, and values like "name": "AT T" to identify which exact keyboard to use

@rvi64 Thanks!
Similar suggestions here:

There is an ongoing work to create a new protocol to get this information. See here
Unfortunately, I got busy with some other work . :(
I will try to start work on this again.

here's a version without jq, only unix utils, shows EN by default, and first two upper case letters otherwise

  "custom/lang": {
    "exec": "swaymsg -t get_inputs | grep layout_name | grep -v En | awk 'END{if(NR == 0){print \"EN\"}else{print toupper(substr($2,2,2))}}'",
    "interval": 1,
    "format": "{} ο„œ"
  },

here's a another version with only unix utils, shows proper language two letters shortcut

.config/waybar/modules/layout.sh

#!/bin/bash

KEYBOARD=$(swaymsg -pt get_config | awk '{if ($1 == "input") {name=$2}; if($1 == "xkb_layout") {print name; exit}}')

ACTIVE_LANGUAGE=$(swaymsg -t get_inputs | grep -5 $KEYBOARD | tail -1 | sed 's/.*"\(.*\)".*$/\1/gi')

MANPAGE=$(man --where xkeyboard-config)
case `file -bi $MANPAGE | grep -o "^[^/]*/[^;]*"` in
    'application/x-gzip') CAT='zcat';;
    'application/x-bzip2') CAT='bzcat';;
    'application/x-tar') CAT='tar -O -xf';;
    'application/x-xz') CAT='xzcat';;
    'text/plain') CAT='cat';;
esac

$CAT $MANPAGE | awk -F'[\t(]' -v var="$ACTIVE_LANGUAGE" 'index($0,var)>=1 {print $1; exit}'

.config/waybar/config

    "custom/layout": {
        "exec": "~/.config/waybar/modules/layout.sh",
        "interval": 1,
        "format": "{} ο„œ"
    }

EDIT: add support for other man formats. Sadly /usr/share/X11/xkb/rules/evdev.xml is not contain needed informations

gzip: skipping: /usr/share/man/man7/xkeyboard-config.7.bz2 unrecognized format

You assume that man page is compressed with gzip.

Also, don't you think, parsing man isn't the right way to do this? The right way is parsing /usr/share/X11/xkb/rules/evdev.xml I guess.

@eternal-sorrow
EDIT2: I change to script to not start every time

.config/waybar/modules/layout.sh

#!/bin/bash

KEYBOARD=$(swaymsg -pt get_config | awk '{if ($1 == "input") {name=$2}; if($1 == "xkb_layout") {print name; exit}}')

MANPAGE=$(man --where xkeyboard-config)
case `file -bi $MANPAGE | grep -o "^[^/]*/[^;]*"` in
    'application/x-gzip') CAT='zcat';;
    'application/x-bzip2') CAT='bzcat';;
    'application/x-tar') CAT='tar -O -xf';;
    'application/x-xz') CAT='xzcat';;
    'text/plain') CAT='cat';;
esac

while true; do
    ACTIVE_LANGUAGE=$(swaymsg -t get_inputs | grep -5 $KEYBOARD | tail -1 | sed 's/.*"\(.*\)".*$/\1/gi')
    $CAT $MANPAGE | awk -F'[\t(]' -v var="$ACTIVE_LANGUAGE" 'index($0,var)>=1 {print $1; exit}'

    sleep 1
done

.config/waybar/config

    "custom/layout": {
        "exec": "~/.config/waybar/modules/layout.sh", // DO NOT RUN multiple times
        "format": "{} ο„œ"
    }

Here's my python version. You'll need the latest git version of acrisci/i3ipc-python installed.
Set KBD_NAME to the name of your keyboard device (see swaymsg -t get_inputs).

#!/usr/bin/env -S python3 -u

import time
import i3ipc

KBD_NAME = "1:1:AT_Translated_Set_2_keyboard"

sway = i3ipc.Connection()

while True:
    inputs = sway.get_inputs()
    keyboard = [i for i in inputs if i['identifier'] == KBD_NAME][0]
    layout = keyboard['xkb_active_layout_name']
    print(layout[:2].lower())
    time.sleep(1)

We can get updates from swaymsg -mrt subscribe (no "interval" setting needed) https://github.com/Alexays/Waybar/pull/85#issuecomment-560100037

Here's my python version. You'll need the latest git version of acrisci/i3ipc-python installed.
Set KBD_NAME to the name of your keyboard device (see swaymsg -t get_inputs).

#!/usr/bin/env -S python3 -u

import time
import i3ipc

KBD_NAME = "1:1:AT_Translated_Set_2_keyboard"

sway = i3ipc.Connection()

while True:
  inputs = sway.get_inputs()
  keyboard = [i for i in inputs if i['identifier'] == KBD_NAME][0]
  layout = keyboard['xkb_active_layout_name']
  print(layout[:2].lower())
  time.sleep(1)

Not work for me.

print inputs [<i3ipc.replies.InputReply object at 0x7f988d156dc0>, <i3ipc.replies.InputReply object at 0x7f988d1379d0>, <i3ipc.replies.InputReply object at 0x7f988d137b50>, <i3ipc.replies.InputReply object at 0x7f988cfc0910>, <i3ipc.replies.InputReply object at 0x7f988cd69d60>, <i3ipc.replies.InputReply object at 0x7f988cd69dc0>, <i3ipc.replies.InputReply object at 0x7f988cd69e20>, <i3ipc.replies.InputReply object at 0x7f988cd69e50>, <i3ipc.replies.InputReply object at 0x7f988cd69e80>, <i3ipc.replies.InputReply object at 0x7f988cd69eb0>]
Traceback (most recent call last):
  File "./layout.py", line 13, in <module>
    keyboard = [i for i in inputs if i['identifier'] == KBD_NAME][0]
  File "./layout.py", line 13, in <listcomp>
    keyboard = [i for i in inputs if i['identifier'] == KBD_NAME][0]
TypeError: 'InputReply' object is not subscriptable

@exdeniz This version is very outdated. It uses polling and old i3ipc API. Use that one https://github.com/Alexays/Waybar/pull/85#issuecomment-526235072 or one of others from that thread.

Sway IPC protocol now has subscribe message type. Here is my Waybar config entry that prints current layout:

"custom/layout": {
    "tooltip": false,
    "exec": "swaymsg -mrt subscribe '[\"input\"]' | jq -r --unbuffered \"select(.change == \\\"xkb_layout\\\") | .input | select(.identifier == \\\"1:1:AT_Translated_Set_2_keyboard\\\" and .type == \\\"keyboard\\\") | .xkb_active_layout_name | .[0:2] | ascii_upcase\""
},

However:

  • When killed, Waybar disowns sh process and it keeps running until you kill swaymsg -mrt subscribe ["input"] process.
  • Notice that "1:1:AT_Translated_Set_2_keyboard" - it seems that keyboard identification is a fundamental problem to wayland protocol. There seems to be no reliable way to differentiate between a real keyboard and "keyboards" that are actually power buttons and other special buttons.
  • Remember to install jq

I would be really happy to see this __inside__ Waybar.

@Hexawolf I created pull request with waybar module which shows keyboard layout. Yes it still use 1:1:AT_Translated_Set_2_keyboard but only on startup. Also it can show short layout names from /usr/share/X11/xkb/rules/evdev.xml.

One might want to use a more generic version while pull request is still not merged:

"custom/layout": {
    "tooltip": false,
    "exec": "swaymsg -mrt subscribe '[\"input\"]' | jq -r --unbuffered \"select(.change == \\\"xkb_lay
out\\\") | .input | select(.type == \\\"keyboard\\\") | .xkb_active_layout_name | .[0:2] | ascii_upcase\""
}

I just built a little bit on the previous answers and uses the following to show flags for layouts in waybar.

"custom/keyboard_layout": {
    "exec": "waybar_sway_keyboard_layout",
    "tooltip": false
}

waybar_sway_keyboard_layout

#!/bin/sh

DEFAULT_LAYOUT="English (US)"

# Kill old instances since waybar don't kill them when exited
[ "$(pgrep -a 'swaymsg' | grep -c '["input"]')" -gt 0 ] &&
  pgrep -a 'swaymsg' | grep '["input"]' | cut -d ' ' -f1 | xargs -r kill

# Sleep for a short while in order to prevent startup issues in waybar
sleep 0.5

keyboard_flag() {
  while read -r layout; do
    if [ "$layout" = "English (US)" ]; then
      layout_waybar="πŸ‡ΊπŸ‡Έ"
    elif [ "$layout" = "Swedish" ]; then
      layout_waybar="πŸ‡ΈπŸ‡ͺ"
    else
      layout_waybar="$layout"
    fi

    printf '%s\n' "$layout_waybar"
  done
}

echo "$DEFAULT_LAYOUT" | keyboard_flag
swaymsg -mrt subscribe '["input"]' | jq -r --unbuffered "select(.change == \"xkb_layout\") | .input | select(.type == \"keyboard\") | .xkb_active_layout_name" | keyboard_flag

I made a simple C program for displaying layouts using sway IPC subscription:
https://github.com/Hexawolf/swaylay

659 should solve this.

I've a custom python script to get the layout using i3ipc library (python-i3ipc). it subscribes to input changes to get notified when the layout changes without the need to poll in a loop.

xkblayout.py:

#!/usr/bin/env -S python3 -u
# vi:syntax=python


import os
import signal

import i3ipc

pri_layout_code = "EN"
sec_layout_code = "AR"
sec_layout_name = "arabic"
input_event_change = "xkb_layout"

sway_sock_path = os.environ["SWAYSOCK"]
sway = i3ipc.Connection(socket_path=sway_sock_path, auto_reconnect=False)


def on_posix_signal(sig, frame):
    sway.main_quit()


signal.signal(signal.SIGHUP, on_posix_signal)
signal.signal(signal.SIGINT, on_posix_signal)
signal.signal(signal.SIGQUIT, on_posix_signal)
signal.signal(signal.SIGTERM, on_posix_signal)

# print the primary layout as it's the default
print(pri_layout_code)


def on_input_change(conn, event):
    if event.change != input_event_change:
        return
    if event.input.xkb_active_layout_name.lower() == sec_layout_name:
        print(sec_layout_code)
    else:
        print(pri_layout_code)


def on_shutdown_or_reload(conn, event):
    conn.main_quit()


sway.on(i3ipc.Event.INPUT, on_input_change)
sway.on(i3ipc.Event.SHUTDOWN, on_shutdown_or_reload)
sway.on(i3ipc.Event.SHUTDOWN_EXIT, on_shutdown_or_reload)
sway.on(i3ipc.Event.SHUTDOWN_RESTART, on_shutdown_or_reload)
sway.on(i3ipc.Event.BARCONFIG_UPDATE, on_shutdown_or_reload)
sway.main()

make sure you make xkblayout.py executable

config:

.......
.......
"custom/xkblayout": {
        "exec": "path/to/xkblayout.py",
        "format": "ο„œ {}",
        "tooltip": false
    },
.......
.......

if I read correctly, #659 also interact just to input change

@krtko1 yes it does subscribe to input change events

I rewrote a jq version in sed and embedded it into the single 'exec' object, so it doesn't need any external script execuion:

"exec": "swaymsg --type get_inputs | grep \"xkb_active_layout_name\" | sed -u '1!d; s/^.*xkb_active_layout_name\": \"//; s/ (US)//; s/\",//' && swaymsg --type subscribe --monitor '[\"input\"]' | sed -u 's/^.*xkb_active_layout_name\": \"//; s/\",.*$//; s/ (US)//'",

FWIW those running Waybar in Sway can just use sway/language: https://github.com/Alexays/Waybar/blob/master/man/waybar-sway-language.5.scd

FWIW those running Waybar in Sway can just use sway/language: https://github.com/Alexays/Waybar/blob/master/man/waybar-sway-language.5.scd

Great, I just added it and it seems to work. Do you think it would be possible to add also the variant together with the layout name?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

pedrocr picture pedrocr  Β·  3Comments

cryptoluks picture cryptoluks  Β·  4Comments

ForTheReallys picture ForTheReallys  Β·  5Comments

Maclanky picture Maclanky  Β·  5Comments

ddorstijn picture ddorstijn  Β·  5Comments