wayfire icon indicating copy to clipboard operation
wayfire copied to clipboard

Add IPC method to query keyboard layout

Open killown opened this issue 1 year ago • 2 comments

For example, swaymsg is currently the only tool that supports querying the keyboard layout. It likely uses IPC for this functionality. It would be a nice feature if Wayfire supported it in a similar way.

killown avatar Aug 01 '24 23:08 killown

since we can get keyboard info using ipc get option value ❯❯❯ wfctl get keyboard
Layout: {'default': 'us', 'result': 'ok', 'value': 'brc'}, Variant: {'default': '', 'result': 'ok', 'value': 'intl'}

it's outside wayfire scope to get keyboard layout from the system besides internal conf, so I am closing this enhancement request.

killown avatar Aug 26 '24 18:08 killown

I plan to add this anyway :) We want a way to get the current layout.

ammen99 avatar Aug 26 '24 18:08 ammen99

I really need to get the current layout to write a script to output the layout to the bar via the command-output widget. I found only one solution and it is not the best.

wayfire.ini [autostart] command_reset_layout_file = echo 0 > ~/.config/layout_indicator/current_layout_index [command] binding_change_layout_file = KEY_SPACE command_change_layout_file = ~/.config/layout_indicator/change_layout_index

wf-shell.ini [panel] command_output_2 = ~/.config/layout_indicator/show_layout command_output_period_2 = 1 command_output_icon_2 = keyboard

!/bin/bash ~/.config/layout_indicator/show-layout

INDEX=$(cat ~/.config/layout_indicator/current_layout_index 2>/dev/null || echo 0) LAYOUTS=$(crudini --get ~/.config/wayfire.ini input xkb_layout) IFS=',' read -ra LAYOUTS_ARRAY <<< "$LAYOUTS" echo "${LAYOUTS_ARRAY[$INDEX]}" | tr '[:lower:]' '[:upper:]'

~/.config/layout_indicator/change_layout_index

LAYOUTS=$(crudini --get ~/.config/wayfire.ini input xkb_layout 2>/dev/null) IFS=',' read -ra LAYOUTS_ARRAY <<< "$LAYOUTS" TOTAL_LAYOUTS=${#LAYOUTS_ARRAY[@]} # Общее количество раскладок CURRENT_INDEX=$(cat ~/.config/layout_indicator/current_layout_index 2>/dev/null || echo 0) NEW_INDEX=$(( (CURRENT_INDEX + 1) % TOTAL_LAYOUTS )) echo $NEW_INDEX > ~/.config/layout_indicator/current_layout_index setxkbmap ${LAYOUTS_ARRAY[$NEW_INDEX]}

mrfoggg avatar May 31 '25 18:05 mrfoggg

I really need to get the current layout to write a script to output the layout to the bar via the command-output widget. I found only one solution and it is not the best.

wayfire.ini [autostart] command_reset_layout_file = echo 0 > ~/.config/layout_indicator/current_layout_index [command] binding_change_layout_file = KEY_SPACE command_change_layout_file = ~/.config/layout_indicator/change_layout_index

wf-shell.ini [panel] command_output_2 = ~/.config/layout_indicator/show_layout command_output_period_2 = 1 command_output_icon_2 = keyboard

!/bin/bash ~/.config/layout_indicator/show-layout

INDEX=$(cat ~/.config/layout_indicator/current_layout_index 2>/dev/null || echo 0) LAYOUTS=$(crudini --get ~/.config/wayfire.ini input xkb_layout) IFS=',' read -ra LAYOUTS_ARRAY <<< "$LAYOUTS" echo "${LAYOUTS_ARRAY[$INDEX]}" | tr '[:lower:]' '[:upper:]'

~/.config/layout_indicator/change_layout_index

LAYOUTS=$(crudini --get ~/.config/wayfire.ini input xkb_layout 2>/dev/null) IFS=',' read -ra LAYOUTS_ARRAY <<< "$LAYOUTS" TOTAL_LAYOUTS=${#LAYOUTS_ARRAY[@]} # Общее количество раскладок CURRENT_INDEX=$(cat ~/.config/layout_indicator/current_layout_index 2>/dev/null || echo 0) NEW_INDEX=$(( (CURRENT_INDEX + 1) % TOTAL_LAYOUTS )) echo $NEW_INDEX > ~/.config/layout_indicator/current_layout_index setxkbmap ${LAYOUTS_ARRAY[$NEW_INDEX]}

what we have for now is:

pip install wayfire

Then:

from wayfire import WayfireSocket
sock = WayfireSocket()

In [4]: sock.get_option_value("input/xkb_layout")
Out[4]: {'result': 'ok', 'value': 'br', 'default': 'us'}

In [6]: sock.get_option_value("input/xkb_rules")
Out[6]: {'result': 'ok', 'value': 'evdev', 'default': 'evdev'}

In [7]: sock.get_option_value("input/xkb_model")
Out[7]: {'result': 'ok', 'value': 'br', 'default': ''}

killown avatar May 31 '25 21:05 killown

this is an interesting workaround for the time being, bind super+space for example as a normal binding which changes the keyboard layout globally .. not perfect but maybe it is enough for the moment (a proper ipc method will still be implemented for the next release ofc).

ammen99 avatar May 31 '25 21:05 ammen99

I’ll leave this example here for newcomers until we implement the proper method in the next release. This way, they can still bind the script to Super+Space.

from wayfire.ipc import WayfireSocket

def cycle_keyboard_layout(sock):
    """Cycles between 'br' and 'us' keyboard layouts"""
    current = sock.get_option_value("input/xkb_layout")["value"]
    new_layout = "us" if current == "br" else "br"
    sock.set_option_values({"input/xkb_layout": new_layout})
    print(f"Switched from {current} to {new_layout}")
    return new_layout

# Usage:
sock = WayfireSocket()
print("Current layout:", sock.get_option_value("input/xkb_layout")["value"])
cycle_keyboard_layout(sock)  # First switch
cycle_keyboard_layout(sock)  # Second switch (returns to original)

killown avatar May 31 '25 21:05 killown

@killown This script doesn't work. After I run it, I'm no longer able to switch the keyboard layout using the "Alt+Shift" shortcut as usual. I have to log out or reboot to restore normal behavior.

bluebyt avatar Jun 01 '25 14:06 bluebyt

@killown This script doesn't work. After I run it, I'm no longer able to switch the keyboard layout using the "Alt+Shift" shortcut as usual. I have to log out or reboot to restore normal behavior.

This script is like changing wayfire.ini [input] xkb_layout = us, without the need to touch the config file

killown avatar Jun 01 '25 15:06 killown

This work

https://github.com/user-attachments/assets/8d1c4b9c-5dc0-4ba4-8833-611671e8a34a

but there is a bug in wayfire or at least the lack of unset_option in ipc, if you set_option once and try to change in wayfire.ini, the config change won't take effect

killown avatar Jun 01 '25 15:06 killown

@killown you can add a custom binding from the script, so that users don't have to do anything but start the script :) just an idea

ammen99 avatar Jun 01 '25 15:06 ammen99

@killown This script doesn't work. After I run it, I'm no longer able to switch the keyboard layout using the "Alt+Shift" shortcut as usual. I have to log out or reboot to restore normal behavior.

you can use this also, so you don't have to log out or reboot to restore normal behavior

import os
import mmap

def cycle_keyboard_layout():
    config_path = os.path.expanduser("~/.config/wayfire.ini")
    
    with open(config_path, 'r+') as f:
        with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_WRITE) as mm:
            match = mm.find(b'xkb_layout')
            if match != -1:
                line_start = mm.rfind(b'\n', 0, match) + 1
                line_end = mm.find(b'\n', match)
                current = mm[line_start:line_end].decode().split('=')[1].strip()
                new_layout = "us" if current == "br" else "br"
                mm[line_start:line_end] = f"xkb_layout = {new_layout}".ljust(line_end - line_start).encode()
            else:
                new_layout = "us"
                mm.seek(0, 2)
                if mm.size() > 0 and mm[-1:] != b'\n':
                    mm.write(b'\n')
                mm.write(b'[input]\nxkb_layout = ' + new_layout.encode() + b'\n')
    
    return new_layout

cycle_keyboard_layout()

killown avatar Jun 01 '25 16:06 killown

Both script doesn't work for me.

The first one does the same thing, after running it, the function xkb_layout stop to work:

#!/usr/bin/python3
from wayfire.ipc import WayfireSocket

def cycle_keyboard_layout(sock):
    """Cycles between 'ca' and 'us' keyboard layouts"""
    current = sock.get_option_value("input/xkb_layout")["value"]
    new_layout = "us" if current == "ca" else "ca"
    sock.set_option_values({"input/xkb_layout": new_layout})
    print(f"Switched from {current} to {new_layout}")
    return new_layout

sock = WayfireSocket()
print("Current layout:", sock.get_option_value("input/xkb_layout")["value"])
cycle_keyboard_layout(sock)

https://github.com/user-attachments/assets/bcc2f260-815a-45a4-b4ba-60967247426f

The second one change the file wayfire.ini To this "xkb_layout = us" or this "xkb_layout = ca", but in order to have my keyboard to work for "ca", I need this line "xkb_layout = us, ca"

#!/usr/bin/python3
import os
import mmap

def cycle_keyboard_layout():
    config_path = os.path.expanduser("~/.config/wayfire.ini")
    
    with open(config_path, 'r+') as f:
        with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_WRITE) as mm:
            match = mm.find(b'xkb_layout')
            if match != -1:
                line_start = mm.rfind(b'\n', 0, match) + 1
                line_end = mm.find(b'\n', match)
                current = mm[line_start:line_end].decode().split('=')[1].strip()
                new_layout = "us" if current == "ca" else "ca"
                mm[line_start:line_end] = f"xkb_layout = {new_layout}".ljust(line_end - line_start).encode()
            else:
                new_layout = "us"
                mm.seek(0, 2)
                if mm.size() > 0 and mm[-1:] != b'\n':
                    mm.write(b'\n')
                mm.write(b'[input]\nxkb_layout = ' + new_layout.encode() + b'\n')
    
    return new_layout

cycle_keyboard_layout()

bluebyt avatar Jun 01 '25 19:06 bluebyt

@bluebyt @mrfoggg @killown check my latest wayfire and pywayfire PRs, you can get keyboard layout with the new methods. You can also watch for the keyboard-state-changed event, it will be emitted on every modifier however so it is a bit often. We don't emit lots of data so it should be fine however.

ammen99 avatar Jun 01 '25 20:06 ammen99

if you merge both prs, it will solve #2419

awesome ammen, thanks!

https://github.com/user-attachments/assets/8e5455e7-43a6-4ea2-b2bf-f27e22ba8c2d

killown avatar Jun 01 '25 20:06 killown