-
Notifications
You must be signed in to change notification settings - Fork 12
/
locale1-xkb-config
executable file
·150 lines (128 loc) · 4.86 KB
/
locale1-xkb-config
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#!/usr/bin/python3
"""
Sync Sway input configuration with org.freedesktop.locale1.
Usage:
Configure keyboard mappings with `localectl set-x11-keymap`.
Add `exec /path/to/script` to your Sway config.
See also:
https://www.freedesktop.org/software/systemd/man/org.freedesktop.locale1.html
Dependencies: dbus-next, i3ipc
"""
import argparse
import asyncio
import logging
from typing import Any, Dict
from dbus_next import BusType, DBusError, Variant
from dbus_next.aio import MessageBus
from i3ipc.aio import Connection
DEFAULT_DEVICE = 'type:keyboard'
LOG = logging.getLogger("sway.locale1")
LOCALE1_BUS_NAME = "org.freedesktop.locale1"
LOCALE1_OBJECT_PATH = "/org/freedesktop/locale1"
LOCALE1_INTERFACE = "org.freedesktop.locale1"
PROPERTIES_INTERFACE = "org.freedesktop.DBus.Properties"
PROPERTIES = {
'X11Layout': 'layout',
'X11Model': 'model',
'X11Variant': 'variant',
'X11Options': 'options'
}
class Locale1Client:
"""Handle org.freedesktop.locale1 updates and pass XKB configuration to Sway"""
layout: str = ''
model: str = ''
variant: str = ''
options: str = ''
def __init__(self,
bus: MessageBus,
conn: Connection,
device: str = DEFAULT_DEVICE):
self._bus = bus
self._conn = conn
self._proxy = None
self._device = device
async def connect(self):
"""asynchronous initialization code"""
introspection = await self._bus.introspect(LOCALE1_BUS_NAME,
LOCALE1_OBJECT_PATH)
self._proxy = self._bus.get_proxy_object(LOCALE1_BUS_NAME,
LOCALE1_OBJECT_PATH,
introspection)
self._proxy.get_interface(PROPERTIES_INTERFACE).on_properties_changed(
self.on_properties_changed)
locale1 = self._proxy.get_interface(LOCALE1_INTERFACE)
self.layout = await locale1.get_x11_layout()
self.model = await locale1.get_x11_model()
self.variant = await locale1.get_x11_variant()
self.options = await locale1.get_x11_options()
await self.update()
async def on_properties_changed(self,
interface: str,
changed: Dict[str, Any],
_invalidated=None):
"""Handle updates from localed"""
if interface != LOCALE1_INTERFACE:
return
apply = False
for name, value in changed.items():
if name not in PROPERTIES:
continue
if isinstance(value, Variant):
value = value.value
self.__dict__[PROPERTIES[name]] = value
apply = True
if apply:
await self.update()
async def update(self):
"""Pass the updated xkb configuration to Sway"""
LOG.info("xkb(%s): layout '%s' model '%s', variant '%s' options '%s'",
self._device, self.layout, self.model, self.variant,
self.options)
cmd = [f"input {self._device} xkb_variant ''"]
cmd.extend([
f"input {self._device} xkb_{name} '{self.__dict__[name]}'"
for name in PROPERTIES.values()
])
replies = await self._conn.command(', '.join(cmd))
for cmd, reply in zip(cmd, replies):
if reply.error is not None:
LOG.error("command '%s' failed: %s", cmd, reply.error)
async def main(args: argparse.Namespace):
"""Async entrypoint"""
try:
bus = await MessageBus(bus_type=BusType.SYSTEM).connect()
conn = await Connection(auto_reconnect=False).connect()
await Locale1Client(bus, conn, device=args.device).connect()
if not args.oneshot:
await conn.main()
except DBusError as exc:
LOG.error("DBus connection error: %s", exc)
except (ConnectionError, EOFError) as exc:
LOG.error("Sway IPC connection error: %s", exc)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Sync Sway input configuration with org.freedesktop.locale1"
)
parser.add_argument(
"-l",
"--loglevel",
choices=["critical", "error", "warning", "info", "debug"],
default="info",
dest="loglevel",
help="set logging level",
)
parser.add_argument(
"--device",
default=DEFAULT_DEVICE,
metavar='identifier',
nargs='?',
type=str,
help="control settings for a specific device "
"(see man sway-input; default: %(default)s)",
)
parser.add_argument("--oneshot",
action='store_true',
help="apply current settings and exit immediately")
args = parser.parse_args()
logging.basicConfig(level=args.loglevel.upper())
asyncio.run(main(args))