Files
nwg-panel/nwg_panel/modules/keyboard_layout.py
2024-09-19 01:52:12 +02:00

208 lines
7.8 KiB
Python

#!/usr/bin/env python3
import json
import os
import gi
from nwg_panel.tools import check_key, update_image, eprint, hyprctl
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
from gi.repository import Gtk, Gdk
def on_enter_notify_event(widget, event):
widget.set_state_flags(Gtk.StateFlags.DROP_ACTIVE, clear=False)
widget.set_state_flags(Gtk.StateFlags.SELECTED, clear=False)
def on_leave_notify_event(widget, event):
widget.unset_state_flags(Gtk.StateFlags.DROP_ACTIVE)
widget.unset_state_flags(Gtk.StateFlags.SELECTED)
class KeyboardLayout(Gtk.EventBox):
def __init__(self, settings, icons_path):
self.settings = settings
self.icons_path = icons_path
Gtk.EventBox.__init__(self)
self.box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
self.add(self.box)
self.image = Gtk.Image()
self.label = Gtk.Label.new("")
self.icon_path = None
if os.getenv("SWAYSOCK"):
from i3ipc import Connection
self.i3 = Connection()
self.compositor = "sway"
elif os.getenv("HYPRLAND_INSTANCE_SIGNATURE"):
self.compositor = "Hyprland"
else:
self.compositor = ""
eprint("Neither sway nor Hyprland detected, this won't work")
if self.compositor:
self.keyboards = self.list_keyboards()
if self.keyboards:
self.keyboard_names = []
for k in self.keyboards:
if self.compositor == "Hyprland":
self.keyboard_names.append(k["name"])
# On sway some devices may be listed twice, let's add them just once
elif k.identifier not in self.keyboard_names:
self.keyboard_names.append(k.identifier)
self.kb_layouts = self.get_kb_layouts()
check_key(settings, "keyboard-device-sway", "")
check_key(settings, "keyboard-device-hyprland", "")
self.device_name = settings["keyboard-device-sway"] if self.compositor == "sway" else settings[
"keyboard-device-hyprland"]
check_key(settings, "root-css-name", "root-executor")
check_key(settings, "css-name", "")
check_key(settings, "icon-placement", "left")
check_key(settings, "icon-size", 16)
check_key(settings, "show-icon", True)
check_key(settings, "tooltip-text", "LMB: Next layout, RMB: Menu")
check_key(settings, "angle", 0.0)
self.label.set_angle(settings["angle"])
if settings["angle"] != 0.0:
self.box.set_orientation(Gtk.Orientation.VERTICAL)
update_image(self.image, "input-keyboard", self.settings["icon-size"], self.icons_path)
self.set_property("name", settings["root-css-name"])
if settings["css-name"]:
self.label.set_property("name", settings["css-name"])
else:
self.label.set_property("name", "executor-label")
if settings["tooltip-text"]:
self.set_tooltip_text(settings["tooltip-text"])
self.connect('button-release-event', self.on_button_release)
self.connect('enter-notify-event', on_enter_notify_event)
self.connect('leave-notify-event', on_leave_notify_event)
self.build_box()
label = self.get_current_layout()
if label:
self.label.set_text(label)
self.show_all()
else:
print("KeyboardLayout module: failed listing devices, won't create UI, sorry.")
def list_keyboards(self):
if self.compositor == "Hyprland":
o = hyprctl("j/devices")
devices = json.loads(o)
keyboards = devices["keyboards"] if "keyboards" in devices else []
else:
inputs = self.i3.get_inputs()
keyboards = []
for i in inputs:
if i.type == "keyboard":
keyboards.append(i)
return keyboards
def get_kb_layouts(self):
if self.compositor == "Hyprland":
o = hyprctl("j/getoption input:kb_layout")
option = json.loads(o)
if option and "str" in option:
return option["str"].split(",")
return []
elif self.compositor == "sway":
layout_names = []
if self.keyboards:
for k in self.keyboards:
for name in k.xkb_layout_names:
if name not in layout_names:
layout_names.append(name)
return layout_names
def get_current_layout(self):
if self.compositor == "Hyprland":
if self.device_name:
for k in self.keyboards:
if k["name"] == self.device_name:
return k["active_keymap"]
return "unknown"
else:
for k in self.keyboards:
if "keyboard" in k["name"]:
return k["active_keymap"]
return self.keyboards[0]["layout"]
elif self.compositor == "sway":
for k in self.keyboards:
if "keyboard" in k.identifier:
return k.xkb_active_layout_name
return self.keyboards[0].xkb_active_layout_name
return "unknown"
def refresh(self):
self.keyboards = self.list_keyboards()
label = self.get_current_layout()
if label:
self.label.set_text(label)
def build_box(self):
if self.settings["show-icon"] and self.settings["icon-placement"] == "left":
self.box.pack_start(self.image, False, False, 3)
self.box.pack_start(self.label, False, False, 3)
if self.settings["show-icon"] and self.settings["icon-placement"] != "left":
self.box.pack_start(self.image, False, False, 3)
def on_left_click(self):
if self.compositor == "Hyprland":
if self.device_name:
# apply to selected device
hyprctl(f"switchxkblayout {self.device_name} next")
else:
# apply to all devices
for name in self.keyboard_names:
hyprctl(f"switchxkblayout {name} next")
elif self.compositor == "sway":
# apply to all devices of type:keyboard
self.i3.command(f'input type:keyboard xkb_switch_layout next')
self.refresh()
def on_menu_item(self, item, idx):
if self.compositor == "Hyprland":
if self.device_name:
# apply to selected device
hyprctl(f'switchxkblayout {self.device_name} {idx}')
else:
# apply to all devices
for name in self.keyboard_names:
hyprctl(f'switchxkblayout {name} {idx}')
elif self.compositor == "sway":
# apply to all devices of type:keyboard
self.i3.command(f'input type:keyboard xkb_switch_layout {idx}')
self.refresh()
def on_right_click(self):
menu = Gtk.Menu()
for i in range(len(self.kb_layouts)):
item = Gtk.MenuItem.new_with_label(self.kb_layouts[i])
item.connect("activate", self.on_menu_item, i)
menu.append(item)
menu.set_reserve_toggle_size(False)
menu.show_all()
menu.popup_at_widget(self.label, Gdk.Gravity.STATIC, Gdk.Gravity.STATIC, None)
def on_button_release(self, widget, event):
if event.button == 1:
self.on_left_click()
elif event.button == 3:
self.on_right_click()