Add river-xkb-bindings and implement Alt+T to open foot
This is the only keybind for now.
This commit is contained in:
parent
a69d647f0c
commit
2c18946703
7 changed files with 402 additions and 9 deletions
|
|
@ -14,13 +14,16 @@ pub fn build(b: *std.Build) void {
|
||||||
|
|
||||||
const scanner = Scanner.create(b, .{});
|
const scanner = Scanner.create(b, .{});
|
||||||
const wayland = b.createModule(.{ .root_source_file = scanner.result });
|
const wayland = b.createModule(.{ .root_source_file = scanner.result });
|
||||||
|
const xkbcommon = b.dependency("xkbcommon", .{}).module("xkbcommon");
|
||||||
|
|
||||||
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.generate("wl_compositor", 4);
|
scanner.generate("wl_compositor", 4);
|
||||||
scanner.generate("wl_shm", 1);
|
scanner.generate("wl_shm", 1);
|
||||||
scanner.generate("wl_output", 4);
|
scanner.generate("wl_output", 4);
|
||||||
scanner.generate("river_window_manager_v1", 1);
|
scanner.generate("river_window_manager_v1", 3);
|
||||||
|
scanner.generate("river_xkb_bindings_v1", 2);
|
||||||
|
|
||||||
const exe = b.addExecutable(.{
|
const exe = b.addExecutable(.{
|
||||||
.name = "beansprout",
|
.name = "beansprout",
|
||||||
|
|
@ -34,6 +37,7 @@ pub fn build(b: *std.Build) void {
|
||||||
exe.pie = pie;
|
exe.pie = pie;
|
||||||
|
|
||||||
exe.root_module.addImport("wayland", wayland);
|
exe.root_module.addImport("wayland", wayland);
|
||||||
|
exe.root_module.addImport("xkbcommon", xkbcommon);
|
||||||
|
|
||||||
exe.linkLibC();
|
exe.linkLibC();
|
||||||
exe.linkSystemLibrary("wayland-client");
|
exe.linkSystemLibrary("wayland-client");
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,10 @@
|
||||||
.url = "https://codeberg.org/ifreund/zig-wayland/archive/v0.4.0.tar.gz",
|
.url = "https://codeberg.org/ifreund/zig-wayland/archive/v0.4.0.tar.gz",
|
||||||
.hash = "wayland-0.4.0-lQa1khbMAQAsLS2eBR7M5lofyEGPIbu2iFDmoz8lPC27",
|
.hash = "wayland-0.4.0-lQa1khbMAQAsLS2eBR7M5lofyEGPIbu2iFDmoz8lPC27",
|
||||||
},
|
},
|
||||||
|
.xkbcommon = .{
|
||||||
|
.url = "https://codeberg.org/ifreund/zig-xkbcommon/archive/v0.3.0.tar.gz",
|
||||||
|
.hash = "xkbcommon-0.3.0-VDqIe3K9AQB2fG5ZeRcMC9i7kfrp5m2rWgLrmdNn9azr",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
.paths = .{
|
.paths = .{
|
||||||
|
|
|
||||||
273
protocol/river-xkb-bindings-v1.xml
Normal file
273
protocol/river-xkb-bindings-v1.xml
Normal file
|
|
@ -0,0 +1,273 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<protocol name="river_xkb_bindings_v1">
|
||||||
|
<copyright>
|
||||||
|
SPDX-FileCopyrightText: © 2025 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="define xkbcommon-based key bindings">
|
||||||
|
This protocol allows the river-window-management-v1 window manager to
|
||||||
|
define key bindings in terms of xkbcommon keysyms and other configurable
|
||||||
|
properties.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Warning! The protocol described in this file is currently in the testing
|
||||||
|
phase. Backward compatible changes may be added together with the
|
||||||
|
corresponding interface version bump. Backward incompatible changes can only
|
||||||
|
be done by creating a new major version of the extension.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<interface name="river_xkb_bindings_v1" version="2">
|
||||||
|
<description summary="xkbcommon bindings global interface">
|
||||||
|
This global interface should only be advertised to the client if the
|
||||||
|
river_window_manager_v1 global is also advertised.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<enum name="error" since="2">
|
||||||
|
<entry name="object_already_created" value="0" since="2"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor">
|
||||||
|
<description summary="destroy the river_xkb_bindings_v1 object">
|
||||||
|
This request indicates that the client will no longer use the
|
||||||
|
river_xkb_bindings_v1 object.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="get_xkb_binding">
|
||||||
|
<description summary="define a new xkbcommon key binding">
|
||||||
|
Define a key binding for the given seat in terms of an xkbcommon keysym
|
||||||
|
and other configurable properties.
|
||||||
|
|
||||||
|
The new key binding is not enabled until initial configuration is
|
||||||
|
completed and the enable request is made during a manage sequence.
|
||||||
|
</description>
|
||||||
|
<arg name="seat" type="object" interface="river_seat_v1"/>
|
||||||
|
<arg name="id" type="new_id" interface="river_xkb_binding_v1"/>
|
||||||
|
<arg name="keysym" type="uint" summary="an xkbcommon keysym"/>
|
||||||
|
<arg name="modifiers" type="uint" enum="river_seat_v1.modifiers"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="get_seat" since="2">
|
||||||
|
<description summary="manage seat-specific state">
|
||||||
|
Create an object to manage seat-specific xkb bindings state.
|
||||||
|
|
||||||
|
It is a protocol error to make this request more than once for a given
|
||||||
|
river_seat_v1 object.
|
||||||
|
</description>
|
||||||
|
<arg name="id" type="new_id" interface="river_xkb_bindings_seat_v1"/>
|
||||||
|
<arg name="seat" type="object" interface="river_seat_v1"/>
|
||||||
|
</request>
|
||||||
|
</interface>
|
||||||
|
|
||||||
|
<interface name="river_xkb_binding_v1" version="2">
|
||||||
|
<description summary="configure a xkb key binding, receive trigger events">
|
||||||
|
This object allows the window manager to configure a xkbcommon key binding
|
||||||
|
and receive events when the key binding is triggered.
|
||||||
|
|
||||||
|
The new key binding is not enabled until the enable request is made during
|
||||||
|
a manage sequence.
|
||||||
|
|
||||||
|
Normally, all key events are sent to the surface with keyboard focus by
|
||||||
|
the compositor. Key events that trigger a key binding are not sent to the
|
||||||
|
surface with keyboard focus.
|
||||||
|
|
||||||
|
If multiple key bindings would be triggered by a single physical key event
|
||||||
|
on the compositor side, it is compositor policy which key binding(s) will
|
||||||
|
receive press/release events or if all of the matched key bindings receive
|
||||||
|
press/release events.
|
||||||
|
|
||||||
|
Key bindings might be matched by the same physical key event due to shared
|
||||||
|
keysym and modifiers. The layout override feature may also cause the same
|
||||||
|
physical key event to trigger two key bindings with different keysyms and
|
||||||
|
different layout overrides configured.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor">
|
||||||
|
<description summary="destroy the xkb binding object">
|
||||||
|
This request indicates that the client will no longer use the xkb key
|
||||||
|
binding object and that it may be safely destroyed.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="set_layout_override">
|
||||||
|
<description summary="override currently active xkb layout">
|
||||||
|
Specify an xkb layout that should be used to translate key events for
|
||||||
|
the purpose of triggering this key binding irrespective of the currently
|
||||||
|
active xkb layout.
|
||||||
|
|
||||||
|
The layout argument is a 0-indexed xkbcommon layout number for the
|
||||||
|
keyboard that generated the key event.
|
||||||
|
|
||||||
|
If this request is never made, the currently active xkb layout of the
|
||||||
|
keyboard that generated the key event will be used.
|
||||||
|
|
||||||
|
This request modifies window management state and may only be made as
|
||||||
|
part of a manage sequence, see the river_window_manager_v1 description.
|
||||||
|
</description>
|
||||||
|
<arg name="layout" type="uint" summary="0-indexed xkbcommon layout"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="enable">
|
||||||
|
<description summary="enable the key binding">
|
||||||
|
This request should be made after all initial configuration has been
|
||||||
|
completed and the window manager wishes the key binding to be able to be
|
||||||
|
triggered.
|
||||||
|
|
||||||
|
This request modifies window management state and may only be made as
|
||||||
|
part of a manage sequence, see the river_window_manager_v1 description.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="disable">
|
||||||
|
<description summary="disable the key binding">
|
||||||
|
This request may be used to temporarily disable the key binding. It may
|
||||||
|
be later re-enabled with the enable request.
|
||||||
|
|
||||||
|
This request modifies window management state and may only be made as
|
||||||
|
part of a manage sequence, see the river_window_manager_v1 description.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<event name="pressed">
|
||||||
|
<description summary="the key triggering the binding has been pressed">
|
||||||
|
This event indicates that the physical key triggering the binding has
|
||||||
|
been pressed.
|
||||||
|
|
||||||
|
This event will be followed by a manage_start event after all other new
|
||||||
|
state has been sent by the server.
|
||||||
|
|
||||||
|
The compositor should wait for the manage sequence to complete before
|
||||||
|
processing further input events. This allows the window manager client
|
||||||
|
to, for example, modify key bindings and keyboard focus without racing
|
||||||
|
against future input events. The window manager should of course respond
|
||||||
|
as soon as possible as the capacity of the compositor to buffer incoming
|
||||||
|
input events is finite.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="released">
|
||||||
|
<description summary="the key triggering the binding has been released">
|
||||||
|
This event indicates that the physical key triggering the binding has
|
||||||
|
been released.
|
||||||
|
|
||||||
|
Releasing the modifiers for the binding without releasing the "main"
|
||||||
|
physical key that produces the bound keysym does not trigger the release
|
||||||
|
event. This event is sent when the "main" key is released, even if the
|
||||||
|
modifiers have changed since the pressed event.
|
||||||
|
|
||||||
|
This event will be followed by a manage_start event after all other new
|
||||||
|
state has been sent by the server.
|
||||||
|
|
||||||
|
The compositor should wait for the manage sequence to complete before
|
||||||
|
processing further input events. This allows the window manager client
|
||||||
|
to, for example, modify key bindings and keyboard focus without racing
|
||||||
|
against future input events. The window manager should of course respond
|
||||||
|
as soon as possible as the capacity of the compositor to buffer incoming
|
||||||
|
input events is finite.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="stop_repeat" since="2">
|
||||||
|
<description summary="repeating should be stopped">
|
||||||
|
This event indicates that repeating should be stopped for the binding if
|
||||||
|
the window manager has been repeating some action since the pressed
|
||||||
|
event.
|
||||||
|
|
||||||
|
This event is generally sent when some other (possible unbound) key is
|
||||||
|
pressed after the pressed event is sent and before the released event
|
||||||
|
is sent for this binding.
|
||||||
|
|
||||||
|
This event will be followed by a manage_start event after all other new
|
||||||
|
state has been sent by the server.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
</interface>
|
||||||
|
|
||||||
|
<interface name="river_xkb_bindings_seat_v1" version="2">
|
||||||
|
<description summary="xkb bindings seat">
|
||||||
|
This object manages xkb bindings state associated with a specific seat.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor" since="2">
|
||||||
|
<description summary="destroy the object">
|
||||||
|
This request indicates that the client will no longer use the object and
|
||||||
|
that it may be safely destroyed.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="ensure_next_key_eaten" since="2">
|
||||||
|
<description summary="ensure the next key press event is eaten">
|
||||||
|
Ensure that the next non-modifier key press and corresponding release
|
||||||
|
events for this seat are not sent to the currently focused surface.
|
||||||
|
|
||||||
|
If the next non-modifier key press triggers a binding, the
|
||||||
|
pressed/released events are sent to the river_xkb_binding_v1 object as
|
||||||
|
usual.
|
||||||
|
|
||||||
|
If the next non-modifier key press does not trigger a binding, the
|
||||||
|
ate_unbound_key event is sent instead.
|
||||||
|
|
||||||
|
Rationale: the window manager may wish to implement "chorded"
|
||||||
|
keybindings where triggering a binding activates a "submap" with a
|
||||||
|
different set of keybindings. Without a way to eat the next key
|
||||||
|
press event, there is no good way for the window manager to know that it
|
||||||
|
should error out and exit the submap when a key not bound in the submap
|
||||||
|
is pressed.
|
||||||
|
|
||||||
|
This request modifies window management state and may only be made as
|
||||||
|
part of a manage sequence, see the river_window_manager_v1 description.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="cancel_ensure_next_key_eaten" since="2">
|
||||||
|
<description summary="cancel an ensure_next_key_eaten request">
|
||||||
|
This requests cancels the effect of the latest ensure_next_key_eaten
|
||||||
|
request if no key has been eaten due to the request yet. This request
|
||||||
|
has no effect if a key has already been eaten or no
|
||||||
|
ensure_next_key_eaten was made.
|
||||||
|
|
||||||
|
Rationale: the window manager may wish cancel an uncompleted "chorded"
|
||||||
|
keybinding after a timeout of a few seconds. Note that since this
|
||||||
|
timeout use-case requires the window manager to trigger a manage sequence
|
||||||
|
with the river_window_manager_v1.manage_dirty request it is possible that
|
||||||
|
the ate_unbound_key key event may be sent before the window manager has
|
||||||
|
a chance to make the cancel_ensure_next_key_eaten request.
|
||||||
|
|
||||||
|
This request modifies window management state and may only be made as
|
||||||
|
part of a manage sequence, see the river_window_manager_v1 description.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<event name="ate_unbound_key" since="2">
|
||||||
|
<description summary="an unbound key press event was eaten">
|
||||||
|
An unbound key press event was eaten due to the ensure_next_key_eaten
|
||||||
|
request.
|
||||||
|
|
||||||
|
This event will be followed by a manage_start event after all other new
|
||||||
|
state has been sent by the server.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
</interface>
|
||||||
|
</protocol>
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2025 Ben Buhse <me@benbuhse.email>
|
// SPDX-FileCopyrightText: 2025-2026 Ben Buhse <me@benbuhse.email>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
|
@ -19,6 +19,7 @@ new_x: i32 = 0,
|
||||||
new_y: i32 = 0,
|
new_y: i32 = 0,
|
||||||
|
|
||||||
link: wl.list.Link,
|
link: wl.list.Link,
|
||||||
|
is_head: bool = false,
|
||||||
|
|
||||||
pub fn init(window: *Window, context: *Context, river_window_v1: *river.WindowV1) void {
|
pub fn init(window: *Window, context: *Context, river_window_v1: *river.WindowV1) void {
|
||||||
window.* = .{
|
window.* = .{
|
||||||
|
|
@ -47,7 +48,7 @@ fn windowListener(river_window_v1: *river.WindowV1, event: river.WindowV1.Event,
|
||||||
window.context.allocator.destroy(window);
|
window.context.allocator.destroy(window);
|
||||||
},
|
},
|
||||||
.dimensions => |ev| {
|
.dimensions => |ev| {
|
||||||
// Part of the protocol requires these are strictly greater-than zero
|
// The protocol requires these are strictly greather than zero.
|
||||||
assert(ev.width > 0 and ev.height > 0);
|
assert(ev.width > 0 and ev.height > 0);
|
||||||
window.width = @intCast(ev.width);
|
window.width = @intCast(ev.width);
|
||||||
window.height = @intCast(ev.height);
|
window.height = @intCast(ev.height);
|
||||||
|
|
@ -69,13 +70,16 @@ pub fn manage(window: *Window) void {
|
||||||
window.new_y = 0;
|
window.new_y = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Support multiple primaries
|
||||||
|
// TODO: Is this a valid way to check for the window's index?
|
||||||
|
|
||||||
// TODO: Remove this -- just fullscreen for now
|
// TODO: Remove this -- just fullscreen for now
|
||||||
if (window.width != window.context.wm.outputs.first().?.width or
|
if (window.width != window.context.wm.outputs.first().?.width or
|
||||||
window.height != window.context.wm.outputs.first().?.height)
|
window.height != window.context.wm.outputs.first().?.height)
|
||||||
{
|
{
|
||||||
window.width = @intCast(window.context.wm.outputs.first().?.width);
|
window.width = @intCast(window.context.wm.outputs.first().?.width);
|
||||||
window.height = @intCast(window.context.wm.outputs.first().?.height);
|
window.height = @intCast(window.context.wm.outputs.first().?.height);
|
||||||
log.debug("setting window width={d} and height={d}", .{ window.width, window.height });
|
log.debug("setting window width={d} and height={d} {}", .{ window.width, window.height, window.is_head });
|
||||||
}
|
}
|
||||||
window.window_v1.proposeDimensions(window.width, window.height);
|
window.window_v1.proposeDimensions(window.width, window.height);
|
||||||
// window.window_v1.setTiled(.{ .top = true, .bottom = true, .left = true, .right = true });
|
// window.window_v1.setTiled(.{ .top = true, .bottom = true, .left = true, .right = true });
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2025 Ben Buhse <me@benbuhse.email>
|
// SPDX-FileCopyrightText: 2025-2026 Ben Buhse <me@benbuhse.email>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
|
@ -12,6 +12,8 @@ seats: wl.list.Head(Seat, .link),
|
||||||
outputs: wl.list.Head(Output, .link),
|
outputs: wl.list.Head(Output, .link),
|
||||||
windows: wl.list.Head(Window, .link),
|
windows: wl.list.Head(Window, .link),
|
||||||
|
|
||||||
|
window_count: u8 = 0,
|
||||||
|
|
||||||
pub fn init(wm: *WindowManager, context: *Context, window_manager_v1: *river.WindowManagerV1) void {
|
pub fn init(wm: *WindowManager, context: *Context, window_manager_v1: *river.WindowManagerV1) void {
|
||||||
assert(wm == &context.wm);
|
assert(wm == &context.wm);
|
||||||
wm.* = .{
|
wm.* = .{
|
||||||
|
|
@ -38,6 +40,10 @@ fn windowManagerV1Listener(window_manager_v1: *river.WindowManagerV1, event: riv
|
||||||
std.posix.exit(1);
|
std.posix.exit(1);
|
||||||
},
|
},
|
||||||
.manage_start => {
|
.manage_start => {
|
||||||
|
if (!context.initialized) {
|
||||||
|
context.initialized = true;
|
||||||
|
context.xkb_bindings.addBinding(xkbcommon.Keysym.t, .{ .mod1 = true });
|
||||||
|
}
|
||||||
{
|
{
|
||||||
var it = wm.seats.iterator(.forward);
|
var it = wm.seats.iterator(.forward);
|
||||||
while (it.next()) |seat| {
|
while (it.next()) |seat| {
|
||||||
|
|
@ -96,6 +102,11 @@ fn windowManagerV1Listener(window_manager_v1: *river.WindowManagerV1, event: riv
|
||||||
var window = context.allocator.create(Window) catch @panic("out-of-memory; exiting.");
|
var window = context.allocator.create(Window) catch @panic("out-of-memory; exiting.");
|
||||||
window.init(context, ev.id);
|
window.init(context, ev.id);
|
||||||
wm.windows.append(window);
|
wm.windows.append(window);
|
||||||
|
wm.window_count += 1;
|
||||||
|
if (wm.window_count == 1) {
|
||||||
|
window.is_head = true;
|
||||||
|
}
|
||||||
|
log.debug("window_count = {d}", .{wm.window_count});
|
||||||
},
|
},
|
||||||
else => |ev| {
|
else => |ev| {
|
||||||
log.debug("unhandled event: {s}", .{@tagName(ev)});
|
log.debug("unhandled event: {s}", .{@tagName(ev)});
|
||||||
|
|
@ -110,10 +121,11 @@ const wayland = @import("wayland");
|
||||||
const wl = wayland.client.wl;
|
const wl = wayland.client.wl;
|
||||||
const river = wayland.client.river;
|
const river = wayland.client.river;
|
||||||
|
|
||||||
|
const xkbcommon = @import("xkbcommon");
|
||||||
|
|
||||||
const Context = @import("main.zig").Context;
|
const Context = @import("main.zig").Context;
|
||||||
const Output = @import("Output.zig");
|
const Output = @import("Output.zig");
|
||||||
|
const Seat = @import("Seat.zig");
|
||||||
const Window = @import("Window.zig");
|
const Window = @import("Window.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.WindowManager);
|
const log = std.log.scoped(.WindowManager);
|
||||||
|
|
||||||
const Seat = @import("Seat.zig");
|
|
||||||
|
|
|
||||||
87
src/XkbBindings.zig
Normal file
87
src/XkbBindings.zig
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
// SPDX-FileCopyrightText: 2025-2026 Ben Buhse <me@benbuhse.email>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
const XkbBindings = @This();
|
||||||
|
|
||||||
|
const XkbBinding = struct {
|
||||||
|
xkb_binding_v1: *river.XkbBindingV1,
|
||||||
|
link: wl.list.Link,
|
||||||
|
|
||||||
|
fn init(xkb_binding: *XkbBinding, xkb_binding_v1: *river.XkbBindingV1) void {
|
||||||
|
xkb_binding.* = .{
|
||||||
|
.xkb_binding_v1 = xkb_binding_v1,
|
||||||
|
.link = undefined, // Handled by the wl.list
|
||||||
|
};
|
||||||
|
|
||||||
|
xkb_binding.xkb_binding_v1.setListener(*XkbBinding, xkbBindingListener, xkb_binding);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn xkbBindingListener(xkb_binding_v1: *river.XkbBindingV1, event: river.XkbBindingV1.Event, xkb_binding: *XkbBinding) void {
|
||||||
|
assert(xkb_binding.xkb_binding_v1 == xkb_binding_v1);
|
||||||
|
switch (event) {
|
||||||
|
.pressed => {
|
||||||
|
var child = std.process.Child.init(&.{"foot"}, std.heap.c_allocator);
|
||||||
|
_ = child.spawn() catch |err| {
|
||||||
|
log.err("Failed to spawn foot: {}", .{err});
|
||||||
|
};
|
||||||
|
},
|
||||||
|
.released => {},
|
||||||
|
else => |ev| {
|
||||||
|
log.debug("unhandled event: {s}", .{@tagName(ev)});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
context: *Context,
|
||||||
|
|
||||||
|
xkb_bindings_v1: *river.XkbBindingsV1,
|
||||||
|
|
||||||
|
bindings: wl.list.Head(XkbBinding, .link),
|
||||||
|
|
||||||
|
xkb_bindings_seat_v1: ?*river.XkbBindingsSeatV1 = null,
|
||||||
|
|
||||||
|
pub fn init(xkb_bindings: *XkbBindings, context: *Context, xkb_bindings_v1: *river.XkbBindingsV1) void {
|
||||||
|
assert(xkb_bindings == &context.xkb_bindings);
|
||||||
|
xkb_bindings.* = .{
|
||||||
|
.context = context,
|
||||||
|
.xkb_bindings_v1 = xkb_bindings_v1,
|
||||||
|
.bindings = undefined, // we will initialize this shortly
|
||||||
|
};
|
||||||
|
|
||||||
|
xkb_bindings.bindings.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getSeat(xkb_bindings: *XkbBindings) *river.SeatV1 {
|
||||||
|
const seat = xkb_bindings.context.wm.seats.first() orelse @panic("No seat available");
|
||||||
|
return seat.seat_v1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addBinding(xkb_bindings: *XkbBindings, keysym: u32, modifiers: river.SeatV1.Modifiers) void {
|
||||||
|
const seat_v1 = xkb_bindings.getSeat();
|
||||||
|
const xkb_binding_v1 = xkb_bindings.xkb_bindings_v1.getXkbBinding(seat_v1, keysym, modifiers) catch |err| {
|
||||||
|
log.err("Failed to get xkb binding: {}", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
const xkb_binding = xkb_bindings.context.allocator.create(XkbBinding) catch @panic("out-of-memory");
|
||||||
|
xkb_binding.init(xkb_binding_v1);
|
||||||
|
xkb_bindings.bindings.append(xkb_binding);
|
||||||
|
|
||||||
|
xkb_binding_v1.enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
|
const wayland = @import("wayland");
|
||||||
|
const wl = wayland.client.wl;
|
||||||
|
const river = wayland.client.river;
|
||||||
|
|
||||||
|
const xkbcommon = @import("xkbcommon");
|
||||||
|
|
||||||
|
const Context = @import("main.zig").Context;
|
||||||
|
const Seat = @import("Seat.zig");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.XkbBindings);
|
||||||
13
src/main.zig
13
src/main.zig
|
|
@ -15,6 +15,7 @@ pub const Context = struct {
|
||||||
shm: ?*wl.Shm = null,
|
shm: ?*wl.Shm = null,
|
||||||
|
|
||||||
wm: WindowManager,
|
wm: WindowManager,
|
||||||
|
xkb_bindings: XkbBindings,
|
||||||
|
|
||||||
fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *Context) void {
|
fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *Context) void {
|
||||||
switch (event) {
|
switch (event) {
|
||||||
|
|
@ -31,11 +32,17 @@ pub const Context = struct {
|
||||||
std.posix.exit(1);
|
std.posix.exit(1);
|
||||||
};
|
};
|
||||||
} else if (mem.orderZ(u8, ev.interface, river.WindowManagerV1.interface.name) == .eq) {
|
} else if (mem.orderZ(u8, ev.interface, river.WindowManagerV1.interface.name) == .eq) {
|
||||||
const window_manager_v1 = registry.bind(ev.name, river.WindowManagerV1, 1) catch |e| {
|
const window_manager_v1 = registry.bind(ev.name, river.WindowManagerV1, 3) catch |e| {
|
||||||
log.err("Failed to bind to window_manager_v: {any}", .{@errorName(e)});
|
log.err("Failed to bind to window_manager_v1: {any}", .{@errorName(e)});
|
||||||
std.posix.exit(1);
|
std.posix.exit(1);
|
||||||
};
|
};
|
||||||
context.wm.init(context, window_manager_v1);
|
context.wm.init(context, window_manager_v1);
|
||||||
|
} else if (mem.orderZ(u8, ev.interface, river.XkbBindingsV1.interface.name) == .eq) {
|
||||||
|
const xkb_bindings_v1 = registry.bind(ev.name, river.XkbBindingsV1, 2) catch |e| {
|
||||||
|
log.err("Failed to bind to xkb_bindings_v1: {any}", .{@errorName(e)});
|
||||||
|
std.posix.exit(1);
|
||||||
|
};
|
||||||
|
context.xkb_bindings.init(context, xkb_bindings_v1);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// We don't need .global_remove
|
// We don't need .global_remove
|
||||||
|
|
@ -69,6 +76,7 @@ pub fn main() !void {
|
||||||
.display = wl_display,
|
.display = wl_display,
|
||||||
.registry = registry,
|
.registry = registry,
|
||||||
.wm = undefined,
|
.wm = undefined,
|
||||||
|
.xkb_bindings = undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.setListener(*Context, Context.registryListener, &context);
|
registry.setListener(*Context, Context.registryListener, &context);
|
||||||
|
|
@ -107,5 +115,6 @@ const river = wayland.client.river;
|
||||||
const wl = wayland.client.wl;
|
const wl = wayland.client.wl;
|
||||||
|
|
||||||
const WindowManager = @import("WindowManager.zig");
|
const WindowManager = @import("WindowManager.zig");
|
||||||
|
const XkbBindings = @import("XkbBindings.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.main);
|
const log = std.log.scoped(.main);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue