Implement river-xkb-config-v1
This commit adds support for the river-xkb-config-v1 protocol. There's a new keyboard_layout block in config that can take options from xkeyboard-config(7).
This commit is contained in:
parent
799963ae42
commit
a1bd356943
12 changed files with 714 additions and 6 deletions
|
|
@ -41,6 +41,7 @@ pub fn build(b: *std.Build) !void {
|
||||||
scanner.addCustomProtocol(b.path("protocol/river-layer-shell-v1.xml"));
|
scanner.addCustomProtocol(b.path("protocol/river-layer-shell-v1.xml"));
|
||||||
scanner.addCustomProtocol(b.path("protocol/river-window-management-v1.xml"));
|
scanner.addCustomProtocol(b.path("protocol/river-window-management-v1.xml"));
|
||||||
scanner.addCustomProtocol(b.path("protocol/river-xkb-bindings-v1.xml"));
|
scanner.addCustomProtocol(b.path("protocol/river-xkb-bindings-v1.xml"));
|
||||||
|
scanner.addCustomProtocol(b.path("protocol/river-xkb-config-v1.xml"));
|
||||||
scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml"); // dep of wlr-layer-shell-unstable-v1
|
scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml"); // dep of wlr-layer-shell-unstable-v1
|
||||||
scanner.addCustomProtocol(b.path("protocol/wlr-layer-shell-unstable-v1.xml"));
|
scanner.addCustomProtocol(b.path("protocol/wlr-layer-shell-unstable-v1.xml"));
|
||||||
|
|
||||||
|
|
@ -52,6 +53,7 @@ pub fn build(b: *std.Build) !void {
|
||||||
scanner.generate("river_layer_shell_v1", 1);
|
scanner.generate("river_layer_shell_v1", 1);
|
||||||
scanner.generate("river_window_manager_v1", 4);
|
scanner.generate("river_window_manager_v1", 4);
|
||||||
scanner.generate("river_xkb_bindings_v1", 2);
|
scanner.generate("river_xkb_bindings_v1", 2);
|
||||||
|
scanner.generate("river_xkb_config_v1", 1);
|
||||||
scanner.generate("zwlr_layer_shell_v1", 3);
|
scanner.generate("zwlr_layer_shell_v1", 3);
|
||||||
|
|
||||||
const options = b.addOptions();
|
const options = b.addOptions();
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,9 @@ An example config can be found at [examples/config.kdl](../examples/config.kdl).
|
||||||
If the config file is missing or fails to load, beansprout falls back to its built-in
|
If the config file is missing or fails to load, beansprout falls back to its built-in
|
||||||
defaults. If no keybinds are configured, the following fallback keybinds are added:
|
defaults. If no keybinds are configured, the following fallback keybinds are added:
|
||||||
|
|
||||||
- `Ctrl+Alt Delete` — exit the River session
|
- `Ctrl+Alt Delete`: exit the River session
|
||||||
- `Super+Shift R` — reload config
|
- `Super+Shift R`: reload config
|
||||||
- `Super T` — spawn `foot`
|
- `Super T`: spawn `foot`
|
||||||
|
|
||||||
Similarly, if an individual node or block is invalid, it will
|
Similarly, if an individual node or block is invalid, it will
|
||||||
try to ignore the error and continue on.
|
try to ignore the error and continue on.
|
||||||
|
|
@ -201,6 +201,37 @@ tag_overlay {
|
||||||
| `square_inactive_border_color` | color | `0x6c7086` | Inactive tag square border |
|
| `square_inactive_border_color` | color | `0x6c7086` | Inactive tag square border |
|
||||||
| `square_inactive_occupied_color` | color | `0xcdd6f4` | Inactive tag occupied indicator |
|
| `square_inactive_occupied_color` | color | `0xcdd6f4` | Inactive tag occupied indicator |
|
||||||
|
|
||||||
|
## Keyboard Layout
|
||||||
|
|
||||||
|
Keyboard layout settings are placed inside a `keyboard_layout` block. These
|
||||||
|
configure the XKB keymap applied to all keyboard devices via the
|
||||||
|
`river-xkb-config` protocol. All fields are optional and default to
|
||||||
|
system/xkbcommon defaults.
|
||||||
|
|
||||||
|
```kdl
|
||||||
|
keyboard_layout {
|
||||||
|
layout "us"
|
||||||
|
variant "dvorak"
|
||||||
|
options "compose:rctrl"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Setting | Type | Default | Description |
|
||||||
|
|-----------|--------|----------------|--------------------------------------------------|
|
||||||
|
| `rules` | string | system default | XKB rules file (almost always `evdev`) |
|
||||||
|
| `model` | string | system default | Keyboard model (e.g., `pc104`, `pc105`) |
|
||||||
|
| `layout` | string | system default | Keyboard layout (e.g., `us`, `de`, `fr`) |
|
||||||
|
| `variant` | string | none | Layout variant (e.g., `dvorak`, `colemak`, `intl`)|
|
||||||
|
| `options` | string | none | XKB options (e.g., `compose:rctrl`, `caps:escape`)|
|
||||||
|
|
||||||
|
Multiple options can be separated by commas: `"compose:rctrl,caps:escape"`.
|
||||||
|
|
||||||
|
If the `keyboard_layout` block is removed from the config and reloaded, beansprout
|
||||||
|
will revert to the system/xkbcommon default keymap.
|
||||||
|
|
||||||
|
See `xkeyboard-config(7)` for a full list of available rules, models, layouts,
|
||||||
|
variants, and options.
|
||||||
|
|
||||||
## Keybinds
|
## Keybinds
|
||||||
|
|
||||||
Keyboard bindings are placed inside a `keybinds` block. Each binding has the
|
Keyboard bindings are placed inside a `keybinds` block. Each binding has the
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,14 @@ pointer_binds {
|
||||||
// tiled windows will automatically float if resized
|
// tiled windows will automatically float if resized
|
||||||
resize_window Mod4 BTN_RIGHT
|
resize_window Mod4 BTN_RIGHT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keyboard layout configuration
|
||||||
|
// All fields are optional and default to system/xkbcommon defaults
|
||||||
|
keyboard_layout {
|
||||||
|
layout "us"
|
||||||
|
// variant "dvorak"
|
||||||
|
options "compose:rctrl"
|
||||||
|
}
|
||||||
// Default input config for all devices
|
// Default input config for all devices
|
||||||
input {
|
input {
|
||||||
accel_profile "flat"
|
accel_profile "flat"
|
||||||
|
|
|
||||||
|
|
@ -216,6 +216,43 @@ default to 0.
|
||||||
|
|
||||||
*top*, *right*, *bottom*, *left*
|
*top*, *right*, *bottom*, *left*
|
||||||
|
|
||||||
|
# KEYBOARD LAYOUT
|
||||||
|
|
||||||
|
Keyboard layout settings are placed inside a *keyboard_layout* block. These
|
||||||
|
configure the XKB keymap applied to all keyboard devices via the
|
||||||
|
river-xkb-config protocol. All fields are optional and default to
|
||||||
|
system/xkbcommon defaults.
|
||||||
|
|
||||||
|
```
|
||||||
|
keyboard_layout {
|
||||||
|
layout "us"
|
||||||
|
variant "dvorak"
|
||||||
|
options "compose:rctrl"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
*rules* _rules_
|
||||||
|
XKB rules file. Almost always "evdev". (Default: system default)
|
||||||
|
|
||||||
|
*model* _model_
|
||||||
|
Keyboard model, e.g., "pc104", "pc105". (Default: system default)
|
||||||
|
|
||||||
|
*layout* _layout_
|
||||||
|
Keyboard layout, e.g., "us", "de", "fr". (Default: system default)
|
||||||
|
|
||||||
|
*variant* _variant_
|
||||||
|
Layout variant, e.g., "dvorak", "colemak", "intl".
|
||||||
|
|
||||||
|
*options* _options_
|
||||||
|
XKB options, e.g., "compose:rctrl", "caps:escape". Multiple options
|
||||||
|
can be separated by commas.
|
||||||
|
|
||||||
|
If the *keyboard_layout* block is removed from the config and reloaded,
|
||||||
|
beansprout will revert to the system/xkbcommon default keymap.
|
||||||
|
|
||||||
|
See *xkeyboard-config*(7) for a full list of available rules, models, layouts,
|
||||||
|
variants, and options.
|
||||||
|
|
||||||
# KEYBINDS
|
# KEYBINDS
|
||||||
|
|
||||||
Keyboard bindings are placed inside a *keybinds* block. Each binding has the
|
Keyboard bindings are placed inside a *keybinds* block. Each binding has the
|
||||||
|
|
@ -383,4 +420,4 @@ All libinput configuration options supported by river are also supported by
|
||||||
|
|
||||||
# SEE ALSO
|
# SEE ALSO
|
||||||
|
|
||||||
*beansprout*(1), *river*(1), *strftime*(3)
|
*beansprout*(1), *river*(1), *strftime*(3), *xkeyboard-config*(7)
|
||||||
|
|
|
||||||
275
protocol/river-xkb-config-v1.xml
Normal file
275
protocol/river-xkb-config-v1.xml
Normal file
|
|
@ -0,0 +1,275 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<protocol name="river_xkb_config_v1">
|
||||||
|
<copyright>
|
||||||
|
SPDX-FileCopyrightText: © 2026 Isaac Freund
|
||||||
|
SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to
|
||||||
|
deal in the Software without restriction, including without limitation the
|
||||||
|
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
IN THE SOFTWARE.
|
||||||
|
</copyright>
|
||||||
|
|
||||||
|
<description summary="configure xkbcommon keyboards">
|
||||||
|
This protocol allow a client to set the xkbcommon keymap of individual
|
||||||
|
keyboard input devices. It also allows switching between the layouts of a
|
||||||
|
keymap and toggling capslock/numlock state.
|
||||||
|
|
||||||
|
The key words "must", "must not", "required", "shall", "shall not",
|
||||||
|
"should", "should not", "recommended", "may", and "optional" in this
|
||||||
|
document are to be interpreted as described in IETF RFC 2119.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<interface name="river_xkb_config_v1" version="1">
|
||||||
|
<description summary="xkb config global interface">
|
||||||
|
Global interface for configuring xkb devices.
|
||||||
|
|
||||||
|
This global should only be advertised if river_input_manager_v1 is
|
||||||
|
advertised as well.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<enum name="error">
|
||||||
|
<entry name="invalid_destroy" value="0"/>
|
||||||
|
<entry name="invalid_format" value="1"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<request name="stop">
|
||||||
|
<description summary="stop sending events">
|
||||||
|
This request indicates that the client no longer wishes to receive
|
||||||
|
events on this object.
|
||||||
|
|
||||||
|
The Wayland protocol is asynchronous, which means the server may send
|
||||||
|
further events until the stop request is processed. The client must wait
|
||||||
|
for a river_xkb_config_v1.finished event before destroying this object.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<event name="finished">
|
||||||
|
<description summary="the server has finished with the object">
|
||||||
|
This event indicates that the server will send no further events on this
|
||||||
|
object. The client should destroy the object. See
|
||||||
|
river_xkb_config_v1.destroy for more information.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor">
|
||||||
|
<description summary="destroy the river_xkb_config_v1 object">
|
||||||
|
This request should be called after the finished event has been received
|
||||||
|
to complete destruction of the object.
|
||||||
|
|
||||||
|
It is a protocol error to make this request before the finished event
|
||||||
|
has been received.
|
||||||
|
|
||||||
|
If a client wishes to destroy this object it should send a
|
||||||
|
river_xkb_config_v1.stop request and wait for a
|
||||||
|
river_xkb_config_v1.finished event. Once the finished event is received
|
||||||
|
it is safe to destroy this object and any other objects created through
|
||||||
|
this interface.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<enum name="keymap_format">
|
||||||
|
<entry name="text_v1" value="1" summary="XKB_KEYMAP_FORMAT_TEXT_V1"/>
|
||||||
|
<entry name="text_v2" value="2" summary="XKB_KEYMAP_FORMAT_TEXT_V2"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<request name="create_keymap">
|
||||||
|
<description summary="create a keymap object">
|
||||||
|
The server must be able to mmap the fd with MAP_PRIVATE.
|
||||||
|
The server will fstat the fd to obtain the size of the keymap.
|
||||||
|
The client must not modify the contents of the fd after making this request.
|
||||||
|
The client should seal the fd with fcntl.
|
||||||
|
</description>
|
||||||
|
<arg name="id" type="new_id" interface="river_xkb_keymap_v1"/>
|
||||||
|
<arg name="fd" type="fd"/>
|
||||||
|
<arg name="format" type="uint" enum="keymap_format"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<event name="xkb_keyboard">
|
||||||
|
<description summary="new xkb keyboard">
|
||||||
|
A new xkbcommon keyboard has been created. Not every
|
||||||
|
river_input_device_v1 is necessarily an xkbcommon keyboard as well.
|
||||||
|
</description>
|
||||||
|
<arg name="id" type="new_id" interface="river_xkb_keyboard_v1"/>
|
||||||
|
</event>
|
||||||
|
</interface>
|
||||||
|
|
||||||
|
<interface name="river_xkb_keymap_v1" version="1">
|
||||||
|
<description summary="xkbcommon keymap">
|
||||||
|
This object is the result of attempting to create an xkbcommon keymap.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor">
|
||||||
|
<description summary="destroy the keymap object">
|
||||||
|
This request indicates that the client will no longer use the keymap
|
||||||
|
object and that it may be safely destroyed.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<event name="success">
|
||||||
|
<description summary="keymap creation succeeded">
|
||||||
|
The keymap object was successfully created and may be used with the
|
||||||
|
river_xkb_keyboard_v1.set_keymap request.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="failure">
|
||||||
|
<description summary="keymap creation failed">
|
||||||
|
The compositor failed to create a keymap from the given parameters.
|
||||||
|
|
||||||
|
It is a protocol error to use this keymap object with
|
||||||
|
river_xkb_keyboard_v1.set_keymap.
|
||||||
|
</description>
|
||||||
|
<arg name="error_msg" type="string"/>
|
||||||
|
</event>
|
||||||
|
</interface>
|
||||||
|
|
||||||
|
<interface name="river_xkb_keyboard_v1" version="1">
|
||||||
|
<description summary="xkbcommon keyboard device">
|
||||||
|
This object represent a physical keyboard which has its configuration and
|
||||||
|
state managed by xkbcommon.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<enum name="error">
|
||||||
|
<entry name="invalid_keymap" value="0"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor">
|
||||||
|
<description summary="destroy the xkb keyboard object">
|
||||||
|
This request indicates that the client will no longer use the keyboard
|
||||||
|
object and that it may be safely destroyed.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<event name="removed">
|
||||||
|
<description summary="the xkb keyboard is removed">
|
||||||
|
This event indicates that the xkb keyboard has been removed.
|
||||||
|
|
||||||
|
The server will send no further events on this object and ignore any
|
||||||
|
request (other than river_xkb_keyboard_v1.destroy) made after this event
|
||||||
|
is sent. The client should destroy this object with the
|
||||||
|
river_xkb_keyboard_v1.destroy request to free up resources.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="input_device">
|
||||||
|
<description summary="corresponding river input device">
|
||||||
|
The river_input_device_v1 corresponding to this xkb keyboard. This event
|
||||||
|
will always be the first event sent on the river_xkb_keyboard_v1 object,
|
||||||
|
and it will be sent exactly once.
|
||||||
|
</description>
|
||||||
|
<arg name="device" type="object" interface="river_input_device_v1"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="set_keymap">
|
||||||
|
<description summary="set the keymap">
|
||||||
|
Set the keymap for the keyboard.
|
||||||
|
|
||||||
|
It is a protocol error to pass a keymap object for which the
|
||||||
|
river_xkb_keymap_v1.success event was not received.
|
||||||
|
</description>
|
||||||
|
<arg name="keymap" type="object" interface="river_xkb_keymap_v1"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="set_layout_by_index">
|
||||||
|
<description summary="set the active layout by index">
|
||||||
|
Set the active layout for the keyboard's keymap. Has no effect if the
|
||||||
|
layout index is out of bounds for the current keymap.
|
||||||
|
</description>
|
||||||
|
<arg name="index" type="int"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="set_layout_by_name">
|
||||||
|
<description summary="set the active layout by name">
|
||||||
|
Set the active layout for the keyboard's keymap. Has no effect if there
|
||||||
|
is no layout with the give name for the keyboard's keymap.
|
||||||
|
</description>
|
||||||
|
<arg name="name" type="string"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<event name="layout">
|
||||||
|
<description summary="currently active layout">
|
||||||
|
The currently active layout index and name. The name arg may be null if
|
||||||
|
the active layout does not have a name.
|
||||||
|
|
||||||
|
This event is sent once when the river_xkb_keyboard_v1 is created and
|
||||||
|
again whenever the layout changes.
|
||||||
|
</description>
|
||||||
|
<arg name="index" type="uint"/>
|
||||||
|
<arg name="name" type="string" allow-null="true"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="capslock_enable">
|
||||||
|
<description summary="enable capslock">
|
||||||
|
Enable capslock for the keyboard.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="capslock_disable">
|
||||||
|
<description summary="disable capslock">
|
||||||
|
Disable capslock for the keyboard.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<event name="capslock_enabled">
|
||||||
|
<description summary="capslock is currently enabled">
|
||||||
|
Capslock is currently enabled for the keyboard.
|
||||||
|
|
||||||
|
This event is sent once when the river_xkb_keyboard_v1 is created and
|
||||||
|
again whenever the capslock state changes.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="capslock_disabled">
|
||||||
|
<description summary="capslock is currently disabled">
|
||||||
|
Capslock is currently disabled for the keyboard.
|
||||||
|
|
||||||
|
This event is sent once when the river_xkb_keyboard_v1 is created and
|
||||||
|
again whenever the capslock state changes.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="numlock_enable">
|
||||||
|
<description summary="enable numlock">
|
||||||
|
Enable numlock for the keyboard.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="numlock_disable">
|
||||||
|
<description summary="disable numlock">
|
||||||
|
Disable numlock for the keyboard.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<event name="numlock_enabled">
|
||||||
|
<description summary="numlock is currently enabled">
|
||||||
|
Numlock is currently enabled for the keyboard.
|
||||||
|
|
||||||
|
This event is sent once when the river_xkb_keyboard_v1 is created and
|
||||||
|
again whenever the numlock state changes.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="numlock_disabled">
|
||||||
|
<description summary="numlock is currently disabled">
|
||||||
|
Numlock is currently disabled for the keyboard.
|
||||||
|
|
||||||
|
This event is sent once when the river_xkb_keyboard_v1 is created and
|
||||||
|
again whenever the numlock state changes.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
</interface>
|
||||||
|
</protocol>
|
||||||
|
|
@ -282,7 +282,7 @@ fn draw(bar: *Bar) !void {
|
||||||
}
|
}
|
||||||
}.f;
|
}.f;
|
||||||
|
|
||||||
// Measure center first — needed to constrain left and right widths
|
// Measure center first; needed to constrain left and right widths
|
||||||
const center_codepoints = componentSlice(options.center, clock_codepoints, title_codepoints, wm_info_codepoints);
|
const center_codepoints = componentSlice(options.center, clock_codepoints, title_codepoints, wm_info_codepoints);
|
||||||
const center_width = try bar.textWidth(center_codepoints);
|
const center_width = try bar.textWidth(center_codepoints);
|
||||||
var center_x: i32 = @divFloor(buffer.width - center_width, 2);
|
var center_x: i32 = @divFloor(buffer.width - center_width, 2);
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,9 @@ tag_overlay_config: ?TagOverlayConfig = null,
|
||||||
/// Bar configuration. If null, no bar is created.
|
/// Bar configuration. If null, no bar is created.
|
||||||
bar_config: ?BarConfig = null,
|
bar_config: ?BarConfig = null,
|
||||||
|
|
||||||
|
/// Keyboard layout configuration
|
||||||
|
keyboard_layout: KeyboardLayoutConfig = .{},
|
||||||
|
|
||||||
/// Tag bind entries parsed from config (tag_bind nodes in keybinds block)
|
/// Tag bind entries parsed from config (tag_bind nodes in keybinds block)
|
||||||
tag_binds: std.ArrayList(Keybind) = .{},
|
tag_binds: std.ArrayList(Keybind) = .{},
|
||||||
// We use a hash map so that duplicate keybinds can be easily de-duplicated
|
// We use a hash map so that duplicate keybinds can be easily de-duplicated
|
||||||
|
|
@ -77,6 +80,7 @@ const NodeName = enum {
|
||||||
bar,
|
bar,
|
||||||
borders,
|
borders,
|
||||||
input,
|
input,
|
||||||
|
keyboard_layout,
|
||||||
keybinds,
|
keybinds,
|
||||||
pointer_binds,
|
pointer_binds,
|
||||||
tag_overlay,
|
tag_overlay,
|
||||||
|
|
@ -120,6 +124,11 @@ pub fn create() !*Config {
|
||||||
if (config.bar_config) |bc| {
|
if (config.bar_config) |bc| {
|
||||||
if (bc.fonts) |fonts| utils.gpa.free(fonts);
|
if (bc.fonts) |fonts| utils.gpa.free(fonts);
|
||||||
}
|
}
|
||||||
|
if (config.keyboard_layout.rules) |s| utils.gpa.free(s);
|
||||||
|
if (config.keyboard_layout.model) |s| utils.gpa.free(s);
|
||||||
|
if (config.keyboard_layout.layout) |s| utils.gpa.free(s);
|
||||||
|
if (config.keyboard_layout.variant) |s| utils.gpa.free(s);
|
||||||
|
if (config.keyboard_layout.options) |s| utils.gpa.free(s);
|
||||||
if (config.wallpaper_image_path) |path| {
|
if (config.wallpaper_image_path) |path| {
|
||||||
utils.gpa.free(path);
|
utils.gpa.free(path);
|
||||||
}
|
}
|
||||||
|
|
@ -167,6 +176,11 @@ pub fn destroy(config: *Config) void {
|
||||||
if (config.bar_config) |bc| {
|
if (config.bar_config) |bc| {
|
||||||
if (bc.fonts) |fonts| utils.gpa.free(fonts);
|
if (bc.fonts) |fonts| utils.gpa.free(fonts);
|
||||||
}
|
}
|
||||||
|
if (config.keyboard_layout.rules) |s| utils.gpa.free(s);
|
||||||
|
if (config.keyboard_layout.model) |s| utils.gpa.free(s);
|
||||||
|
if (config.keyboard_layout.layout) |s| utils.gpa.free(s);
|
||||||
|
if (config.keyboard_layout.variant) |s| utils.gpa.free(s);
|
||||||
|
if (config.keyboard_layout.options) |s| utils.gpa.free(s);
|
||||||
if (config.wallpaper_image_path) |path| {
|
if (config.wallpaper_image_path) |path| {
|
||||||
utils.gpa.free(path);
|
utils.gpa.free(path);
|
||||||
}
|
}
|
||||||
|
|
@ -305,6 +319,7 @@ fn load(config: *Config, reader: *Io.Reader) !void {
|
||||||
},
|
},
|
||||||
inline .bar,
|
inline .bar,
|
||||||
.borders,
|
.borders,
|
||||||
|
.keyboard_layout,
|
||||||
.keybinds,
|
.keybinds,
|
||||||
.pointer_binds,
|
.pointer_binds,
|
||||||
.tag_overlay,
|
.tag_overlay,
|
||||||
|
|
@ -320,6 +335,7 @@ fn load(config: *Config, reader: *Io.Reader) !void {
|
||||||
switch (child_block) {
|
switch (child_block) {
|
||||||
.bar => try BarConfig.load(config, &parser, hostname),
|
.bar => try BarConfig.load(config, &parser, hostname),
|
||||||
.borders => try border.load(config, &parser, hostname),
|
.borders => try border.load(config, &parser, hostname),
|
||||||
|
.keyboard_layout => try KeyboardLayoutConfig.load(config, &parser, hostname),
|
||||||
.keybinds => try keybind.load(config, &parser, hostname),
|
.keybinds => try keybind.load(config, &parser, hostname),
|
||||||
.pointer_binds => try pointer_bind.load(config, &parser, hostname),
|
.pointer_binds => try pointer_bind.load(config, &parser, hostname),
|
||||||
.input => {
|
.input => {
|
||||||
|
|
@ -375,6 +391,7 @@ const XkbBindings = @import("XkbBindings.zig");
|
||||||
|
|
||||||
const border = @import("config/border.zig");
|
const border = @import("config/border.zig");
|
||||||
const helpers = @import("config/helpers.zig");
|
const helpers = @import("config/helpers.zig");
|
||||||
|
const KeyboardLayoutConfig = @import("config/KeyboardLayoutConfig.zig");
|
||||||
const keybind = @import("config/keybind.zig");
|
const keybind = @import("config/keybind.zig");
|
||||||
const pointer_bind = @import("config/pointer_bind.zig");
|
const pointer_bind = @import("config/pointer_bind.zig");
|
||||||
const window_rule = @import("config/window_rule.zig");
|
const window_rule = @import("config/window_rule.zig");
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ river_layer_shell_v1: *river.LayerShellV1,
|
||||||
im: *InputManager,
|
im: *InputManager,
|
||||||
wm: *WindowManager,
|
wm: *WindowManager,
|
||||||
xkb_bindings: *XkbBindings,
|
xkb_bindings: *XkbBindings,
|
||||||
|
xkb_config: ?*XkbConfig,
|
||||||
|
|
||||||
/// Pool of Buffers used for rendering wallpapers
|
/// Pool of Buffers used for rendering wallpapers
|
||||||
buffer_pool: BufferPool = .{},
|
buffer_pool: BufferPool = .{},
|
||||||
|
|
@ -52,6 +53,7 @@ pub const Options = struct {
|
||||||
river_layer_shell_v1: *river.LayerShellV1,
|
river_layer_shell_v1: *river.LayerShellV1,
|
||||||
river_window_manager_v1: *river.WindowManagerV1,
|
river_window_manager_v1: *river.WindowManagerV1,
|
||||||
river_xkb_bindings_v1: *river.XkbBindingsV1,
|
river_xkb_bindings_v1: *river.XkbBindingsV1,
|
||||||
|
river_xkb_config_v1: *river.XkbConfigV1,
|
||||||
|
|
||||||
config: *Config,
|
config: *Config,
|
||||||
};
|
};
|
||||||
|
|
@ -66,6 +68,8 @@ pub fn create(options: Options) !*Context {
|
||||||
errdefer wm.destroy();
|
errdefer wm.destroy();
|
||||||
const xkb_bindings = try XkbBindings.create(context, options.river_xkb_bindings_v1);
|
const xkb_bindings = try XkbBindings.create(context, options.river_xkb_bindings_v1);
|
||||||
errdefer xkb_bindings.destroy();
|
errdefer xkb_bindings.destroy();
|
||||||
|
const xkb_config = try XkbConfig.create(context, options.river_xkb_config_v1);
|
||||||
|
errdefer xkb_config.destroy();
|
||||||
|
|
||||||
var env = try process.getEnvMap(utils.gpa);
|
var env = try process.getEnvMap(utils.gpa);
|
||||||
errdefer env.deinit();
|
errdefer env.deinit();
|
||||||
|
|
@ -85,6 +89,7 @@ pub fn create(options: Options) !*Context {
|
||||||
.im = im,
|
.im = im,
|
||||||
.wm = wm,
|
.wm = wm,
|
||||||
.xkb_bindings = xkb_bindings,
|
.xkb_bindings = xkb_bindings,
|
||||||
|
.xkb_config = xkb_config,
|
||||||
.config = options.config,
|
.config = options.config,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -96,6 +101,9 @@ pub fn destroy(context: *Context) void {
|
||||||
context.im.destroy();
|
context.im.destroy();
|
||||||
context.wm.destroy();
|
context.wm.destroy();
|
||||||
context.xkb_bindings.destroy();
|
context.xkb_bindings.destroy();
|
||||||
|
if (context.xkb_config) |xkb_config| {
|
||||||
|
xkb_config.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
if (context.wallpaper_image) |wallpaper_image| {
|
if (context.wallpaper_image) |wallpaper_image| {
|
||||||
wallpaper_image.destroy();
|
wallpaper_image.destroy();
|
||||||
|
|
@ -269,5 +277,6 @@ const TagOverlay = @import("TagOverlay.zig");
|
||||||
const Wallpaper = @import("Wallpaper.zig");
|
const Wallpaper = @import("Wallpaper.zig");
|
||||||
const WindowManager = @import("WindowManager.zig");
|
const WindowManager = @import("WindowManager.zig");
|
||||||
const XkbBindings = @import("XkbBindings.zig");
|
const XkbBindings = @import("XkbBindings.zig");
|
||||||
|
const XkbConfig = @import("XkbConfig.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.Context);
|
const log = std.log.scoped(.Context);
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,11 @@ fn initialize(wm: *WindowManager) void {
|
||||||
}
|
}
|
||||||
binding.enable();
|
binding.enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply keyboard layout from config
|
||||||
|
if (context.xkb_config) |xkb_config| {
|
||||||
|
xkb_config.applyKeyboardLayout();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn manage(wm: *WindowManager) void {
|
fn manage(wm: *WindowManager) void {
|
||||||
|
|
|
||||||
242
src/XkbConfig.zig
Normal file
242
src/XkbConfig.zig
Normal file
|
|
@ -0,0 +1,242 @@
|
||||||
|
// SPDX-FileCopyrightText: 2026 Ben Buhse <me@benbuhse.email>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
const XkbConfig = @This();
|
||||||
|
|
||||||
|
context: *Context,
|
||||||
|
|
||||||
|
xkb_config_v1: *river.XkbConfigV1,
|
||||||
|
|
||||||
|
/// The keymap object sent to the compositor, if any.
|
||||||
|
/// Only valid to use with set_keymap once xkb_keymap_confirmed is true.
|
||||||
|
xkb_keymap_v1: ?*river.XkbKeymapV1 = null,
|
||||||
|
/// True once xkb_keymap_v1 has received the success event.
|
||||||
|
xkb_keymap_confirmed: bool = false,
|
||||||
|
|
||||||
|
/// Tracked keyboards so we can apply keymaps to all of them.
|
||||||
|
keyboards: wl.list.Head(Keyboard, .link),
|
||||||
|
|
||||||
|
const Keyboard = struct {
|
||||||
|
xkb_keyboard_v1: *river.XkbKeyboardV1,
|
||||||
|
xkb_config: *XkbConfig,
|
||||||
|
link: wl.list.Link,
|
||||||
|
|
||||||
|
fn create(xkb_keyboard_v1: *river.XkbKeyboardV1, xkb_config: *XkbConfig) !*Keyboard {
|
||||||
|
const keyboard = try utils.gpa.create(Keyboard);
|
||||||
|
keyboard.* = .{
|
||||||
|
.xkb_keyboard_v1 = xkb_keyboard_v1,
|
||||||
|
.xkb_config = xkb_config,
|
||||||
|
.link = undefined,
|
||||||
|
};
|
||||||
|
xkb_keyboard_v1.setListener(*Keyboard, keyboardListener, keyboard);
|
||||||
|
xkb_config.keyboards.append(keyboard);
|
||||||
|
|
||||||
|
// Only apply if the keymap has been confirmed via the success event.
|
||||||
|
// Applying an unconfirmed keymap is a protocol error.
|
||||||
|
if (xkb_config.xkb_keymap_confirmed) {
|
||||||
|
xkb_keyboard_v1.setKeymap(xkb_config.xkb_keymap_v1.?);
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy(keyboard: *Keyboard) void {
|
||||||
|
keyboard.link.remove();
|
||||||
|
keyboard.xkb_keyboard_v1.destroy();
|
||||||
|
utils.gpa.destroy(keyboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn keyboardListener(_: *river.XkbKeyboardV1, event: river.XkbKeyboardV1.Event, keyboard: *Keyboard) void {
|
||||||
|
switch (event) {
|
||||||
|
.removed => keyboard.destroy(),
|
||||||
|
.input_device, .layout, .capslock_enabled, .capslock_disabled, .numlock_enabled, .numlock_disabled => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn create(context: *Context, xkb_config_v1: *river.XkbConfigV1) !*XkbConfig {
|
||||||
|
const xkb_config = try utils.gpa.create(XkbConfig);
|
||||||
|
errdefer utils.gpa.destroy(xkb_config);
|
||||||
|
|
||||||
|
xkb_config.* = .{
|
||||||
|
.context = context,
|
||||||
|
.xkb_config_v1 = xkb_config_v1,
|
||||||
|
.keyboards = undefined, // we will initialize this shortly
|
||||||
|
};
|
||||||
|
xkb_config.keyboards.init();
|
||||||
|
|
||||||
|
xkb_config_v1.setListener(*XkbConfig, riverXkbConfigV1Listener, xkb_config);
|
||||||
|
|
||||||
|
return xkb_config;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(xkb_config: *XkbConfig) void {
|
||||||
|
var it = xkb_config.keyboards.safeIterator(.forward);
|
||||||
|
while (it.next()) |keyboard| {
|
||||||
|
keyboard.destroy();
|
||||||
|
}
|
||||||
|
if (xkb_config.xkb_keymap_v1) |keymap_v1| {
|
||||||
|
keymap_v1.destroy();
|
||||||
|
}
|
||||||
|
xkb_config.xkb_config_v1.destroy();
|
||||||
|
utils.gpa.destroy(xkb_config);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn riverXkbConfigV1Listener(xkb_config_v1: *river.XkbConfigV1, event: river.XkbConfigV1.Event, xkb_config: *XkbConfig) void {
|
||||||
|
assert(xkb_config.xkb_config_v1 == xkb_config_v1);
|
||||||
|
switch (event) {
|
||||||
|
.finished => {
|
||||||
|
xkb_config.context.xkb_config = null;
|
||||||
|
xkb_config.destroy();
|
||||||
|
},
|
||||||
|
.xkb_keyboard => |ev| {
|
||||||
|
_ = Keyboard.create(ev.id, xkb_config) catch |e| {
|
||||||
|
log.err("Failed to create keyboard: {}", .{e});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a keymap from the config's keyboard_layout settings and send it
|
||||||
|
/// to the compositor. On success, apply it to all tracked keyboards.
|
||||||
|
pub fn applyKeyboardLayout(xkb_config: *XkbConfig) void {
|
||||||
|
const layout_config = xkb_config.context.config.keyboard_layout;
|
||||||
|
|
||||||
|
// If nothing is configured and we haven't previously applied a custom keymap,
|
||||||
|
// there's nothing to do. If we did previously apply one, fall through to apply
|
||||||
|
// the system default, reverting the custom keymap.
|
||||||
|
if (layout_config.rules == null and
|
||||||
|
layout_config.model == null and
|
||||||
|
layout_config.layout == null and
|
||||||
|
layout_config.variant == null and
|
||||||
|
layout_config.options == null and
|
||||||
|
!xkb_config.xkb_keymap_confirmed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert ?[]const u8 to ?[*:0]const u8 for xkbcommon
|
||||||
|
const rules_z = dupeZOrNull(layout_config.rules);
|
||||||
|
defer if (rules_z) |s| utils.gpa.free(s);
|
||||||
|
const model_z = dupeZOrNull(layout_config.model);
|
||||||
|
defer if (model_z) |s| utils.gpa.free(s);
|
||||||
|
const layout_z = dupeZOrNull(layout_config.layout);
|
||||||
|
defer if (layout_z) |s| utils.gpa.free(s);
|
||||||
|
const variant_z = dupeZOrNull(layout_config.variant);
|
||||||
|
defer if (variant_z) |s| utils.gpa.free(s);
|
||||||
|
const options_z = dupeZOrNull(layout_config.options);
|
||||||
|
defer if (options_z) |s| utils.gpa.free(s);
|
||||||
|
|
||||||
|
const names = xkbcommon.RuleNames{
|
||||||
|
.rules = if (rules_z) |s| s.ptr else null,
|
||||||
|
.model = if (model_z) |s| s.ptr else null,
|
||||||
|
.layout = if (layout_z) |s| s.ptr else null,
|
||||||
|
.variant = if (variant_z) |s| s.ptr else null,
|
||||||
|
.options = if (options_z) |s| s.ptr else null,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create xkb context and keymap
|
||||||
|
const xkb_context = xkbcommon.Context.new(.no_flags) orelse {
|
||||||
|
log.err("Failed to create XKB context", .{});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
defer xkb_context.unref();
|
||||||
|
|
||||||
|
const keymap = xkbcommon.Keymap.newFromNames(xkb_context, &names, .no_flags) orelse {
|
||||||
|
log.err("Invalid keyboard layout configuration", .{});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
defer keymap.unref();
|
||||||
|
|
||||||
|
// Serialize keymap to string
|
||||||
|
const keymap_str = keymap.getAsString(.text_v1) orelse {
|
||||||
|
log.err("Failed to serialize keymap", .{});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
const keymap_len = mem.len(keymap_str);
|
||||||
|
// Include the null terminator in the buffer sent to the compositor
|
||||||
|
const keymap_buf = keymap_str[0 .. keymap_len + 1];
|
||||||
|
defer utils.gpa.free(keymap_buf);
|
||||||
|
|
||||||
|
// Create memfd and write keymap
|
||||||
|
const fd = switch (builtin.target.os.tag) {
|
||||||
|
.linux => posix.memfd_createZ("beansprout-xkb-keymap", os.linux.MFD.CLOEXEC | os.linux.MFD.ALLOW_SEALING),
|
||||||
|
.freebsd => posix.memfd_createZ("beansprout-xkb-keymap", std.c.MFD.CLOEXEC | std.c.MFD.ALLOW_SEALING),
|
||||||
|
else => @compileError("target OS not supported"),
|
||||||
|
} catch |err| {
|
||||||
|
log.err("Failed to create memfd: {}", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
defer posix.close(fd);
|
||||||
|
|
||||||
|
_ = posix.write(fd, keymap_buf) catch |err| {
|
||||||
|
log.err("Failed to write keymap to fd: {}", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send to compositor
|
||||||
|
const keymap_v1 = xkb_config.xkb_config_v1.createKeymap(fd, .text_v1) catch |err| {
|
||||||
|
log.err("Failed to create keymap object: {}", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Destroy old keymap if any
|
||||||
|
if (xkb_config.xkb_keymap_v1) |old| {
|
||||||
|
old.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
xkb_config.xkb_keymap_confirmed = false;
|
||||||
|
keymap_v1.setListener(*XkbConfig, keymapListener, xkb_config);
|
||||||
|
xkb_config.xkb_keymap_v1 = keymap_v1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn keymapListener(_: *river.XkbKeymapV1, event: river.XkbKeymapV1.Event, xkb_config: *XkbConfig) void {
|
||||||
|
switch (event) {
|
||||||
|
.success => {
|
||||||
|
log.info("Keyboard layout applied successfully", .{});
|
||||||
|
xkb_config.xkb_keymap_confirmed = true;
|
||||||
|
// Apply to all tracked keyboards
|
||||||
|
var it = xkb_config.keyboards.iterator(.forward);
|
||||||
|
while (it.next()) |keyboard| {
|
||||||
|
keyboard.xkb_keyboard_v1.setKeymap(xkb_config.xkb_keymap_v1.?);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.failure => |ev| {
|
||||||
|
log.err("Failed to apply keyboard layout: {s}", .{ev.error_msg});
|
||||||
|
xkb_config.xkb_keymap_confirmed = false;
|
||||||
|
if (xkb_config.xkb_keymap_v1) |keymap| {
|
||||||
|
keymap.destroy();
|
||||||
|
xkb_config.xkb_keymap_v1 = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dupeZOrNull(opt: ?[]const u8) ?[:0]u8 {
|
||||||
|
const slice = opt orelse return null;
|
||||||
|
return utils.gpa.dupeZ(u8, slice) catch {
|
||||||
|
log.err("Out of memory", .{});
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const std = @import("std");
|
||||||
|
const mem = std.mem;
|
||||||
|
const os = std.os;
|
||||||
|
const posix = std.posix;
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
|
const wayland = @import("wayland");
|
||||||
|
const wl = wayland.client.wl;
|
||||||
|
const river = wayland.client.river;
|
||||||
|
|
||||||
|
const xkbcommon = @import("xkbcommon");
|
||||||
|
|
||||||
|
const utils = @import("utils.zig");
|
||||||
|
const Context = @import("Context.zig");
|
||||||
|
const Seat = @import("Seat.zig");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.XkbConfig);
|
||||||
75
src/config/KeyboardLayoutConfig.zig
Normal file
75
src/config/KeyboardLayoutConfig.zig
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
// SPDX-FileCopyrightText: 2026 Ben Buhse <me@benbuhse.email>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
const KeyboardLayoutConfig = @This();
|
||||||
|
|
||||||
|
const NodeName = enum {
|
||||||
|
rules,
|
||||||
|
model,
|
||||||
|
layout,
|
||||||
|
variant,
|
||||||
|
options,
|
||||||
|
};
|
||||||
|
|
||||||
|
rules: ?[]const u8 = null,
|
||||||
|
model: ?[]const u8 = null,
|
||||||
|
layout: ?[]const u8 = null,
|
||||||
|
variant: ?[]const u8 = null,
|
||||||
|
options: ?[]const u8 = null,
|
||||||
|
|
||||||
|
pub fn load(config: *Config, parser: *kdl.Parser, hostname: ?[]const u8) !void {
|
||||||
|
while (try parser.next()) |event| {
|
||||||
|
switch (event) {
|
||||||
|
.node => |node| {
|
||||||
|
const node_name = std.meta.stringToEnum(NodeName, node.name);
|
||||||
|
if (node_name) |name| {
|
||||||
|
if (!helpers.hostMatches(node, parser, hostname)) {
|
||||||
|
log.debug("Skipping \"keyboard_layout.{s}\" (host mismatch)", .{@tagName(name)});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const value = utils.stripQuotes(node.arg(parser, 0) orelse {
|
||||||
|
logWarnMissingNodeArg(name);
|
||||||
|
continue;
|
||||||
|
});
|
||||||
|
const duped = try utils.gpa.dupe(u8, value);
|
||||||
|
const target = switch (name) {
|
||||||
|
.rules => &config.keyboard_layout.rules,
|
||||||
|
.model => &config.keyboard_layout.model,
|
||||||
|
.layout => &config.keyboard_layout.layout,
|
||||||
|
.variant => &config.keyboard_layout.variant,
|
||||||
|
.options => &config.keyboard_layout.options,
|
||||||
|
};
|
||||||
|
if (target.*) |old| {
|
||||||
|
utils.gpa.free(old);
|
||||||
|
}
|
||||||
|
target.* = duped;
|
||||||
|
logDebugSettingNode(name, value);
|
||||||
|
} else {
|
||||||
|
helpers.logWarnInvalidNode(node.name);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.child_block_begin => try helpers.skipChildBlock(parser),
|
||||||
|
.child_block_end => return,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn logDebugSettingNode(node_name: NodeName, node_value: []const u8) void {
|
||||||
|
log.debug("Setting keyboard_layout.{s} to {s}", .{ @tagName(node_name), node_value });
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn logWarnMissingNodeArg(node_name: NodeName) void {
|
||||||
|
log.warn("\"keyboard_layout.{s}\" missing argument. Ignoring", .{@tagName(node_name)});
|
||||||
|
}
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const kdl = @import("kdl");
|
||||||
|
|
||||||
|
const utils = @import("../utils.zig");
|
||||||
|
const Config = @import("../Config.zig");
|
||||||
|
|
||||||
|
const helpers = @import("helpers.zig");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.config_keyboard_layout);
|
||||||
|
|
@ -9,6 +9,7 @@ const Globals = struct {
|
||||||
river_layer_shell_v1: ?*river.LayerShellV1 = null,
|
river_layer_shell_v1: ?*river.LayerShellV1 = null,
|
||||||
river_window_manager_v1: ?*river.WindowManagerV1 = null,
|
river_window_manager_v1: ?*river.WindowManagerV1 = null,
|
||||||
river_xkb_bindings_v1: ?*river.XkbBindingsV1 = null,
|
river_xkb_bindings_v1: ?*river.XkbBindingsV1 = null,
|
||||||
|
river_xkb_config_v1: ?*river.XkbConfigV1 = null,
|
||||||
|
|
||||||
wl_compositor: ?*wl.Compositor = null,
|
wl_compositor: ?*wl.Compositor = null,
|
||||||
wl_shm: ?*wl.Shm = null,
|
wl_shm: ?*wl.Shm = null,
|
||||||
|
|
@ -65,6 +66,7 @@ pub fn main() !void {
|
||||||
const river_layer_shell_v1 = globals.river_layer_shell_v1 orelse utils.interfaceNotAdvertised(river.LayerShellV1);
|
const river_layer_shell_v1 = globals.river_layer_shell_v1 orelse utils.interfaceNotAdvertised(river.LayerShellV1);
|
||||||
const river_window_manager_v1 = globals.river_window_manager_v1 orelse utils.interfaceNotAdvertised(river.WindowManagerV1);
|
const river_window_manager_v1 = globals.river_window_manager_v1 orelse utils.interfaceNotAdvertised(river.WindowManagerV1);
|
||||||
const river_xkb_bindings_v1 = globals.river_xkb_bindings_v1 orelse utils.interfaceNotAdvertised(river.XkbBindingsV1);
|
const river_xkb_bindings_v1 = globals.river_xkb_bindings_v1 orelse utils.interfaceNotAdvertised(river.XkbBindingsV1);
|
||||||
|
const river_xkb_config_v1 = globals.river_xkb_config_v1 orelse utils.interfaceNotAdvertised(river.XkbConfigV1);
|
||||||
|
|
||||||
const config = try Config.create();
|
const config = try Config.create();
|
||||||
defer config.destroy();
|
defer config.destroy();
|
||||||
|
|
@ -78,6 +80,7 @@ pub fn main() !void {
|
||||||
.river_layer_shell_v1 = river_layer_shell_v1,
|
.river_layer_shell_v1 = river_layer_shell_v1,
|
||||||
.river_window_manager_v1 = river_window_manager_v1,
|
.river_window_manager_v1 = river_window_manager_v1,
|
||||||
.river_xkb_bindings_v1 = river_xkb_bindings_v1,
|
.river_xkb_bindings_v1 = river_xkb_bindings_v1,
|
||||||
|
.river_xkb_config_v1 = river_xkb_config_v1,
|
||||||
.config = config,
|
.config = config,
|
||||||
});
|
});
|
||||||
defer context.destroy();
|
defer context.destroy();
|
||||||
|
|
@ -243,7 +246,7 @@ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *
|
||||||
};
|
};
|
||||||
} else if (mem.orderZ(u8, ev.interface, wl.Output.interface.name) == .eq) {
|
} else if (mem.orderZ(u8, ev.interface, wl.Output.interface.name) == .eq) {
|
||||||
if (ev.version < 4) utils.versionNotSupported(wl.Output, ev.version, 4);
|
if (ev.version < 4) utils.versionNotSupported(wl.Output, ev.version, 4);
|
||||||
// We don't bind wl_output until the river_output send its .wl_output event
|
// We don't bind wl_output until the river_output sends its .wl_output event
|
||||||
// This way, we don't miss the initial configuration events
|
// This way, we don't miss the initial configuration events
|
||||||
} else if (mem.orderZ(u8, ev.interface, wl.Shm.interface.name) == .eq) {
|
} else if (mem.orderZ(u8, ev.interface, wl.Shm.interface.name) == .eq) {
|
||||||
globals.wl_shm = registry.bind(ev.name, wl.Shm, 1) catch |e| {
|
globals.wl_shm = registry.bind(ev.name, wl.Shm, 1) catch |e| {
|
||||||
|
|
@ -270,6 +273,10 @@ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *
|
||||||
globals.river_xkb_bindings_v1 = registry.bind(ev.name, river.XkbBindingsV1, 2) catch |e| {
|
globals.river_xkb_bindings_v1 = registry.bind(ev.name, river.XkbBindingsV1, 2) catch |e| {
|
||||||
fatal("Failed to bind to river_xkb_bindings_v1: {any}", .{@errorName(e)});
|
fatal("Failed to bind to river_xkb_bindings_v1: {any}", .{@errorName(e)});
|
||||||
};
|
};
|
||||||
|
} else if (mem.orderZ(u8, ev.interface, river.XkbConfigV1.interface.name) == .eq) {
|
||||||
|
globals.river_xkb_config_v1 = registry.bind(ev.name, river.XkbConfigV1, 1) catch |e| {
|
||||||
|
fatal("Failed to bind to river_xkb_config_v1: {any}", .{@errorName(e)});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.global_remove => |_| {
|
.global_remove => |_| {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue