initial commit
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# SPDX-FileCopyrightText: © 2026 FireFly
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
tinyrwm/protocol/*.lua
|
||||
37
README.md
Normal file
37
README.md
Normal file
@@ -0,0 +1,37 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: © 2026 FireFly
|
||||
SPDX-License-Identifier: 0BSD
|
||||
-->
|
||||
|
||||
# tinyrwm.lua
|
||||
|
||||
Tiny river window manager implemented in Lua.
|
||||
|
||||
## Dependencies
|
||||
|
||||
System dependencies:
|
||||
- lua (5.4 tested)
|
||||
- luarocks
|
||||
- libwayland
|
||||
- libxkbcommon
|
||||
|
||||
The lua-ecosystem dependencies should be handled by luarocks.
|
||||
|
||||
## Building
|
||||
|
||||
To fetch lua dependencies, build, and install to `~/.luarocks/bin`, run:
|
||||
|
||||
```sh
|
||||
eval $(luarocks --path bin)
|
||||
luarocks --local make
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
Make sure libxkbcommon.so and libwayland-client.so are present in
|
||||
`LD_LIBRARY_PATH` (and `river` and `foot` in your `PATH`). You should be able
|
||||
to run river with the installed Lua tinyrwm with
|
||||
|
||||
```sh
|
||||
river -c ~/.luarocks/bin/tinyrwm
|
||||
```
|
||||
44
planar-dev-1.rockspec
Normal file
44
planar-dev-1.rockspec
Normal file
@@ -0,0 +1,44 @@
|
||||
-- SPDX-FileCopyrightText: © 2026 FireFly
|
||||
-- SPDX-License-Identifier: 0BSD
|
||||
|
||||
package = "planar"
|
||||
version = "dev-1"
|
||||
rockspec_format = "3.0"
|
||||
|
||||
source = {
|
||||
url = "",
|
||||
}
|
||||
|
||||
description = {
|
||||
summary = "stupid stupid",
|
||||
homepage = "",
|
||||
license = "0BSD",
|
||||
}
|
||||
|
||||
dependencies = {
|
||||
"lua == 5.4",
|
||||
"cffi-lua >= 0.2.4",
|
||||
"firefly/wau",
|
||||
"luaposix",
|
||||
}
|
||||
|
||||
external_dependencies = {
|
||||
-- runtime dependencies: ensure these are in your LD_LIBRARY_PATH
|
||||
-- XKBCOMMON = { library = "libxkbcommon.so" },
|
||||
-- WAYLAND = { library = "libwayland-client.so" },
|
||||
}
|
||||
|
||||
build = {
|
||||
type = "command",
|
||||
build_command = [[
|
||||
for f in planar/protocol/*.xml; do
|
||||
wau-scanner <$f >${f%%.xml}.lua
|
||||
done
|
||||
]],
|
||||
install_command = [[
|
||||
# mimic build.type == "builtin" behaviour
|
||||
install -Dm644 planar/xkbcommon.lua $(LUADIR)/planar/xkbcommon.lua
|
||||
install -Dm644 -t $(LUADIR)/planar/protocol planar/protocol/*.lua
|
||||
install -Dm755 planar.lua $(BINDIR)/planar
|
||||
]],
|
||||
}
|
||||
538
planar.lua
Executable file
538
planar.lua
Executable file
@@ -0,0 +1,538 @@
|
||||
#! /usr/bin/env lua
|
||||
-- SPDX-FileCopyrightText: © 2026 FireFly
|
||||
-- SPDX-License-Identifier: 0BSD
|
||||
|
||||
local wau = require("wau")
|
||||
local xkbcommon = require("planar.xkbcommon")
|
||||
local posix = require("posix")
|
||||
|
||||
wau:require("planar.protocol.river-window-management-v1")
|
||||
wau:require("planar.protocol.river-xkb-bindings-v1")
|
||||
|
||||
local globals = {}
|
||||
local required_globals = {
|
||||
["river_window_manager_v1"] = 4,
|
||||
["river_xkb_bindings_v1"] = 1,
|
||||
}
|
||||
|
||||
local Mods = wau.river_seat_v1.Modifiers
|
||||
|
||||
local xkb_bindings = {
|
||||
{"space", Mods.MOD1, "spawn-foot"},
|
||||
{"q", Mods.MOD1, "close"},
|
||||
{"r", Mods.MOD1, "reset-view"},
|
||||
{"Escape", Mods.MOD1, "exit"},
|
||||
}
|
||||
|
||||
local pointer_bindings = {
|
||||
{"left", Mods.MOD1, "move"},
|
||||
{"right", Mods.MOD1, "resize"},
|
||||
}
|
||||
|
||||
local wm = {
|
||||
outputs = {},
|
||||
seats = {},
|
||||
-- Windows are kept in rendering order; last window is topmost
|
||||
windows = {},
|
||||
cam = {x = 0, y = 0, dirty = false},
|
||||
}
|
||||
|
||||
local function table_index_of(tbl, sought)
|
||||
for i, v in ipairs(tbl) do
|
||||
if v == sought then return i end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
local function table_filter_inplace(tbl, pred)
|
||||
local removed = 0
|
||||
for i=1,#tbl do
|
||||
if pred(tbl[i]) then
|
||||
tbl[i - removed] = tbl[i]
|
||||
else
|
||||
removed = removed + 1
|
||||
end
|
||||
if removed > 0 then tbl[i] = nil end
|
||||
end
|
||||
return tbl
|
||||
end
|
||||
|
||||
|
||||
---- Output ---------------------------
|
||||
local Output = { mt = {}, listener = {} }
|
||||
Output.mt.__index = Output
|
||||
|
||||
function Output.create(obj)
|
||||
local output = { obj = obj }
|
||||
setmetatable(output, Output.mt)
|
||||
obj:set_user_data(output)
|
||||
obj:add_listener(Output.listener)
|
||||
return output
|
||||
end
|
||||
|
||||
function Output:maybe_destroy()
|
||||
if self.removed then
|
||||
self.obj:destroy()
|
||||
else
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
function Output.listener:removed()
|
||||
self:get_user_data().removed = true
|
||||
end
|
||||
|
||||
---- Window ---------------------------
|
||||
local Window = { mt = {}, listener = {} }
|
||||
Window.mt.__index = Window
|
||||
|
||||
function Window.create(obj)
|
||||
local window = {
|
||||
obj = obj,
|
||||
node = obj:get_node(),
|
||||
new = true,
|
||||
}
|
||||
setmetatable(window, Window.mt)
|
||||
obj:set_user_data(window)
|
||||
obj:add_listener(Window.listener)
|
||||
return window
|
||||
end
|
||||
|
||||
function Window:maybe_destroy()
|
||||
if self.closed then
|
||||
self.obj:destroy()
|
||||
self.node:destroy()
|
||||
else
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
function Window:manage()
|
||||
if self.new then
|
||||
for i = 1,#wm.outputs do
|
||||
local output = wm.outputs[i]
|
||||
for k,v in pairs(output) do print(k,v) end
|
||||
end
|
||||
|
||||
self.new = nil
|
||||
self:set_position(wm.cam.x, wm.cam.y)
|
||||
self.obj:propose_dimensions(0, 0)
|
||||
end
|
||||
|
||||
local move = self.pointer_move_requested
|
||||
if move ~= nil then
|
||||
self.pointer_move_requested = nil
|
||||
move.seat:pointer_move(self)
|
||||
end
|
||||
|
||||
local resize = self.pointer_resize_requested
|
||||
if resize ~= nil then
|
||||
self.pointer_resize_requested = nil
|
||||
resize.seat:pointer_resize(self, resize.edges)
|
||||
end
|
||||
end
|
||||
|
||||
function Window:set_position(x, y)
|
||||
self.node:set_position(x - wm.cam.x, y - wm.cam.y)
|
||||
self.x = x
|
||||
self.y = y
|
||||
end
|
||||
|
||||
function Window.listener:closed()
|
||||
self:get_user_data().closed = true
|
||||
end
|
||||
function Window.listener:dimensions(width, height)
|
||||
local window = self:get_user_data()
|
||||
window.width = width
|
||||
window.height = height
|
||||
end
|
||||
function Window.listener:pointer_move_requested(seat)
|
||||
self:get_user_data().pointer_move_requested = {
|
||||
seat = seat:get_user_data(),
|
||||
}
|
||||
end
|
||||
function Window.listener:pointer_resize_requested(seat, edges)
|
||||
local Edges = wau.river_window_v1.Edges
|
||||
self:get_user_data().pointer_resize_requested = {
|
||||
seat = seat:get_user_data(),
|
||||
edges = {
|
||||
left = (edges & Edges.LEFT) ~= 0,
|
||||
right = (edges & Edges.RIGHT) ~= 0,
|
||||
top = (edges & Edges.TOP) ~= 0,
|
||||
bottom = (edges & Edges.BOTTOM) ~= 0,
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
---- Seat -----------------------------
|
||||
local Seat = { mt = {}, listener = {} }
|
||||
Seat.mt.__index = Seat
|
||||
|
||||
function Seat.create(obj)
|
||||
local seat = {
|
||||
obj = obj,
|
||||
new = true,
|
||||
xkb_bindings = {},
|
||||
pointer_bindings = {},
|
||||
}
|
||||
setmetatable(seat, Seat.mt)
|
||||
obj:set_user_data(seat)
|
||||
obj:add_listener(Seat.listener)
|
||||
return seat
|
||||
end
|
||||
|
||||
function Seat:focus(window)
|
||||
if window == nil and #wm.windows > 0 then
|
||||
-- Fall back to topmost window
|
||||
window = wm.windows[#wm.windows]
|
||||
end
|
||||
|
||||
if window then
|
||||
if self.focused ~= window then
|
||||
self.obj:focus_window(window.obj)
|
||||
self.focused = window
|
||||
-- Move to top
|
||||
local i = table_index_of(wm.windows, window)
|
||||
table.remove(wm.windows, i)
|
||||
table.insert(wm.windows, window)
|
||||
window.node:place_top()
|
||||
end
|
||||
else
|
||||
self.obj:clear_focus()
|
||||
self.focused = nil
|
||||
end
|
||||
end
|
||||
|
||||
function Seat:pointer_move(window)
|
||||
if window then
|
||||
if self.op == nil then
|
||||
self:focus(window)
|
||||
self.obj:op_start_pointer()
|
||||
self.op = {
|
||||
type = "move",
|
||||
window = window,
|
||||
start = { x = window.x, y = window.y },
|
||||
dx = 0,
|
||||
dy = 0,
|
||||
}
|
||||
end
|
||||
else
|
||||
if self.op == nil then
|
||||
self:focus(window)
|
||||
self.obj:op_start_pointer()
|
||||
self.op = {
|
||||
type = "world_move",
|
||||
dx = 0,
|
||||
dy = 0,
|
||||
pdx = 0,
|
||||
pdy = 0
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Seat:pointer_resize(window, edges)
|
||||
if self.op == nil then
|
||||
self:focus(window)
|
||||
window.obj:inform_resize_start()
|
||||
self.obj:op_start_pointer()
|
||||
self.op = {
|
||||
type = "resize",
|
||||
window = window,
|
||||
edges = edges,
|
||||
start = {
|
||||
x = window.x,
|
||||
y = window.y,
|
||||
width = window.width,
|
||||
height = window.height,
|
||||
},
|
||||
dx = 0,
|
||||
dy = 0,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
function Seat:action(action)
|
||||
if action == "spawn-foot" then
|
||||
if posix.unistd.fork() == 0 then
|
||||
posix.unistd.execp("foot", {})
|
||||
end
|
||||
elseif action == "reset-view" then
|
||||
wm.cam.x = 0
|
||||
wm.cam.y = 0
|
||||
wm.cam.dirty = true;
|
||||
elseif action == "close" then
|
||||
if self.focused ~= nil then
|
||||
self.focused.obj:close()
|
||||
end
|
||||
elseif action == "focus-next" then
|
||||
self:focus(wm.windows[1])
|
||||
elseif action == "move" then
|
||||
-- if self.hovered ~= nil then
|
||||
self:pointer_move(self.hovered)
|
||||
-- end
|
||||
elseif action == "resize" then
|
||||
if self.hovered ~= nil then
|
||||
self:pointer_resize(self.hovered, { bottom = true, right = true })
|
||||
end
|
||||
elseif action == "exit" then
|
||||
globals["river_window_manager_v1"]:exit_session()
|
||||
else
|
||||
print("Seat:action: unimplemented", action)
|
||||
end
|
||||
end
|
||||
|
||||
function Seat:add_pointer_binding(button, mods, action)
|
||||
-- From /usr/include/linux/input-event-codes.h
|
||||
local button_code = ({ left = 0x110, right = 0x111 })[button]
|
||||
local obj = self.obj:get_pointer_binding(button_code, mods)
|
||||
local binding = { obj = obj }
|
||||
|
||||
obj:add_listener {
|
||||
["pressed"] = function (_)
|
||||
self.pending_action = action
|
||||
end,
|
||||
}
|
||||
obj:enable()
|
||||
table.insert(self.pointer_bindings, binding)
|
||||
end
|
||||
|
||||
function Seat:add_xkb_binding(key, mods, action)
|
||||
local keysym = xkbcommon.keysym(key)
|
||||
local obj = globals["river_xkb_bindings_v1"]:get_xkb_binding(
|
||||
self.obj, keysym, mods)
|
||||
local binding = { obj = obj }
|
||||
|
||||
obj:add_listener {
|
||||
["pressed"] = function (_)
|
||||
self.pending_action = action
|
||||
end,
|
||||
}
|
||||
obj:enable()
|
||||
table.insert(self.xkb_bindings, binding)
|
||||
end
|
||||
|
||||
function Seat:manage()
|
||||
if self.new then
|
||||
self.new = nil
|
||||
|
||||
for _, tbl in ipairs(xkb_bindings) do
|
||||
self:add_xkb_binding(table.unpack(tbl))
|
||||
end
|
||||
|
||||
for _, tbl in ipairs(pointer_bindings) do
|
||||
self:add_pointer_binding(table.unpack(tbl))
|
||||
end
|
||||
end
|
||||
|
||||
if self.focused and self.focused.closed then
|
||||
self.focused = nil
|
||||
end
|
||||
|
||||
self:focus(self.interacted)
|
||||
self.interacted = nil
|
||||
|
||||
if self.pending_action ~= nil then
|
||||
self:action(self.pending_action)
|
||||
self.pending_action = nil
|
||||
end
|
||||
|
||||
if self.op then
|
||||
local op, window = self.op, self.op.window
|
||||
local window = self.op.window
|
||||
|
||||
if window and window.closed then
|
||||
self.obj:op_end()
|
||||
self.op = nil
|
||||
|
||||
elseif self.op_release then
|
||||
if window and op.type == "resize" then
|
||||
window.obj:inform_resize_end()
|
||||
end
|
||||
self.obj:op_end()
|
||||
self.op = nil
|
||||
|
||||
elseif window and op.type == "resize" then
|
||||
local width = math.max(
|
||||
1,
|
||||
op.edges.left and (op.start.width - op.dx) or
|
||||
op.edges.right and (op.start.width + op.dx) or
|
||||
op.start.width
|
||||
)
|
||||
local height = math.max(
|
||||
1,
|
||||
op.edges.top and (op.start.height - op.dy) or
|
||||
op.edges.bottom and (op.start.height + op.dy) or
|
||||
op.start.height
|
||||
)
|
||||
window.obj:propose_dimensions(width, height)
|
||||
end
|
||||
end
|
||||
|
||||
self.op_release = nil
|
||||
end
|
||||
|
||||
function Seat:render()
|
||||
if self.op then
|
||||
local op, window = self.op, self.op.window
|
||||
if window and self.op.type == "move" then
|
||||
window:set_position(
|
||||
op.start.x + op.dx,
|
||||
op.start.y + op.dy
|
||||
)
|
||||
elseif window and self.op.type == "resize" then
|
||||
local x = op.edges.left
|
||||
and (op.start.x + (op.start.width - window.width))
|
||||
or op.start.x
|
||||
local y = op.edges.top
|
||||
and (op.start.y + (op.start.height - window.height))
|
||||
or op.start.y
|
||||
window:set_position(x, y)
|
||||
elseif self.op.type == "world_move" then
|
||||
local adx, ady = op.dx - op.pdx, op.dy - op.pdy
|
||||
|
||||
wm.cam.x = wm.cam.x - adx -- TODO: make sure should be minus
|
||||
wm.cam.y = wm.cam.y - ady
|
||||
|
||||
wm.cam.dirty = true;
|
||||
|
||||
op.pdx, op.pdy = op.dx, op.dy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Seat:maybe_destroy()
|
||||
if self.removed then
|
||||
for _, binding in ipairs(self.xkb_bindings) do
|
||||
binding.obj:destroy()
|
||||
end
|
||||
for _, binding in ipairs(self.pointer_bindings) do
|
||||
binding.obj:destroy()
|
||||
end
|
||||
self.obj:destroy()
|
||||
else
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
function Seat.listener.removed(self)
|
||||
self:get_user_data().removed = true
|
||||
end
|
||||
function Seat.listener.pointer_enter(self, window)
|
||||
self:get_user_data().hovered = window:get_user_data()
|
||||
end
|
||||
function Seat.listener.pointer_leave(self)
|
||||
self:get_user_data().hovered = nil
|
||||
end
|
||||
function Seat.listener.window_interaction(self, window)
|
||||
self:get_user_data().interacted = window:get_user_data()
|
||||
end
|
||||
function Seat.listener.op_delta(self, dx, dy)
|
||||
local seat = self:get_user_data()
|
||||
seat.op.dx = dx
|
||||
seat.op.dy = dy
|
||||
end
|
||||
function Seat.listener.op_release(self)
|
||||
self:get_user_data().op_release = true
|
||||
end
|
||||
|
||||
|
||||
---- wm -------------------------------
|
||||
local function wm_manage()
|
||||
table_filter_inplace(wm.outputs, Output.maybe_destroy)
|
||||
table_filter_inplace(wm.windows, Window.maybe_destroy)
|
||||
table_filter_inplace(wm.seats, Seat.maybe_destroy)
|
||||
|
||||
for _, window in ipairs(wm.windows) do
|
||||
window:manage()
|
||||
end
|
||||
|
||||
for _, seat in ipairs(wm.seats) do
|
||||
seat:manage()
|
||||
end
|
||||
|
||||
globals["river_window_manager_v1"]:manage_finish()
|
||||
end
|
||||
|
||||
local function wm_render()
|
||||
if wm.cam.dirty then
|
||||
for i = 1,#wm.windows do
|
||||
local window = wm.windows[i]
|
||||
|
||||
window.node:set_position(window.x - wm.cam.x, window.y - wm.cam.y);
|
||||
end
|
||||
wm.cam.dirty = false;
|
||||
end
|
||||
|
||||
for _, seat in ipairs(wm.seats) do
|
||||
seat:render()
|
||||
end
|
||||
|
||||
globals["river_window_manager_v1"]:render_finish()
|
||||
end
|
||||
|
||||
local wm_handlers = {
|
||||
["unavailable"] = function (self)
|
||||
io.stderr:write("another window manager is already running\n")
|
||||
os.exit(1)
|
||||
end,
|
||||
["finished"] = function (self)
|
||||
os.exit(0)
|
||||
end,
|
||||
["manage_start"] = wm_manage,
|
||||
["render_start"] = wm_render,
|
||||
["output"] = function (self, obj)
|
||||
table.insert(wm.outputs, Output.create(obj))
|
||||
end,
|
||||
["seat"] = function (self, obj)
|
||||
table.insert(wm.seats, Seat.create(obj))
|
||||
end,
|
||||
["window"] = function (self, obj)
|
||||
table.insert(wm.windows, Window.create(obj))
|
||||
end,
|
||||
}
|
||||
|
||||
|
||||
---- Entry point ----------------------
|
||||
display = wau.wl_display.connect()
|
||||
assert(display, "Failed to connect to wayland compositor")
|
||||
|
||||
-- Ensure we exit nonzero if an event handler errors
|
||||
local function handle_callback_error(proxy, name, func, err)
|
||||
io.stderr:write(("-- Error calling event handler for %s %q:")
|
||||
:format(tostring(proxy), name))
|
||||
io.stderr:write(("%s\n"):format(tostring(err)))
|
||||
os.exit(1)
|
||||
end
|
||||
wau.wl_proxy.set_error_callback(handle_callback_error)
|
||||
|
||||
-- Avoid passing WAYLAND_DEBUG to our children
|
||||
posix.stdlib.setenv("WAYLAND_DEBUG", nil)
|
||||
|
||||
-- Ensure children are automatically reaped
|
||||
posix.signal.signal(posix.signal.SIGCHLD, posix.signal.SIG_IGN)
|
||||
|
||||
local registry = display:get_registry()
|
||||
registry:add_listener {
|
||||
["global"] = function (self, name, iface, version)
|
||||
local required_version = required_globals[iface]
|
||||
if required_version ~= nil then
|
||||
assert(required_version <= version,
|
||||
("wayland compositor supported %s version too old (need %d, got %d)")
|
||||
:format(iface, required_version, version))
|
||||
globals[iface] = self:bind(name, wau[iface], required_version)
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
display:roundtrip()
|
||||
|
||||
for k in pairs(required_globals) do
|
||||
assert(globals[k] ~= nil, ("wayland compositor does not support %s"):format(k))
|
||||
end
|
||||
|
||||
globals["river_window_manager_v1"]:add_listener(wm_handlers)
|
||||
|
||||
while display:dispatch() do end
|
||||
|
||||
2107
planar/protocol/river-window-management-v1.lua
Normal file
2107
planar/protocol/river-window-management-v1.lua
Normal file
File diff suppressed because it is too large
Load Diff
1854
planar/protocol/river-window-management-v1.xml
Normal file
1854
planar/protocol/river-window-management-v1.xml
Normal file
File diff suppressed because it is too large
Load Diff
341
planar/protocol/river-xkb-bindings-v1.lua
Normal file
341
planar/protocol/river-xkb-bindings-v1.lua
Normal file
@@ -0,0 +1,341 @@
|
||||
-- Auto generated by the wau-scanner v0
|
||||
|
||||
--- river_xkb_bindings_v1
|
||||
-- @module river_xkb_bindings_v1
|
||||
|
||||
-- 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.
|
||||
return function(wau)
|
||||
|
||||
local interfaces = {
|
||||
"river_xkb_bindings_v1",
|
||||
"river_xkb_binding_v1",
|
||||
"river_xkb_bindings_seat_v1",
|
||||
}
|
||||
|
||||
for _, iface in ipairs(interfaces) do
|
||||
wau[iface] = wau.wl_interface.new()
|
||||
end
|
||||
|
||||
--- xkbcommon bindings global interface
|
||||
--
|
||||
-- This global interface should only be advertised to the client if the
|
||||
-- river_window_manager_v1 global is also advertised.
|
||||
-- @type river_xkb_bindings_v1
|
||||
wau.river_xkb_bindings_v1:init {
|
||||
name = "river_xkb_bindings_v1",
|
||||
version = 2,
|
||||
methods = {
|
||||
--- destroy the river_xkb_bindings_v1 object
|
||||
--
|
||||
-- This request indicates that the client will no longer use the
|
||||
-- river_xkb_bindings_v1 object.
|
||||
-- @function river_xkb_bindings_v1:destroy
|
||||
-- @treturn river_xkb_bindings_v1 self
|
||||
{
|
||||
name = "destroy",
|
||||
signature = "",
|
||||
types = { },
|
||||
type = "destructor"
|
||||
},
|
||||
--- 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.
|
||||
-- @function river_xkb_bindings_v1:get_xkb_binding
|
||||
-- @tparam river_seat_v1 seat
|
||||
-- @treturn river_xkb_binding_v1
|
||||
-- @tparam uint keysym an xkbcommon keysym
|
||||
-- @tparam uint modifiers
|
||||
{
|
||||
name = "get_xkb_binding",
|
||||
signature = "onuu",
|
||||
types = { wau.river_seat_v1, wau.river_xkb_binding_v1, 0, 0, },
|
||||
},
|
||||
--- 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.
|
||||
-- @function river_xkb_bindings_v1:get_seat
|
||||
-- @treturn river_xkb_bindings_seat_v1
|
||||
-- @tparam river_seat_v1 seat
|
||||
{
|
||||
name = "get_seat",
|
||||
signature = "2no",
|
||||
types = { wau.river_xkb_bindings_seat_v1, wau.river_seat_v1, },
|
||||
},
|
||||
},
|
||||
events = {
|
||||
},
|
||||
enums = {
|
||||
--- error
|
||||
-- @enum river_xkb_bindings_v1.Error
|
||||
-- @param OBJECT_ALREADY_CREATED 0
|
||||
["error"] = {
|
||||
["object_already_created"] = 0,
|
||||
},
|
||||
},
|
||||
methods_opcode = {
|
||||
["destroy"] = 0,
|
||||
["get_xkb_binding"] = 1,
|
||||
["get_seat"] = 2,
|
||||
},
|
||||
}
|
||||
|
||||
--- 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.
|
||||
-- @type river_xkb_binding_v1
|
||||
wau.river_xkb_binding_v1:init {
|
||||
name = "river_xkb_binding_v1",
|
||||
version = 2,
|
||||
methods = {
|
||||
--- 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.
|
||||
-- @function river_xkb_binding_v1:destroy
|
||||
-- @treturn river_xkb_binding_v1 self
|
||||
{
|
||||
name = "destroy",
|
||||
signature = "",
|
||||
types = { },
|
||||
type = "destructor"
|
||||
},
|
||||
--- 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.
|
||||
-- @function river_xkb_binding_v1:set_layout_override
|
||||
-- @tparam uint layout 0-indexed xkbcommon layout
|
||||
-- @treturn river_xkb_binding_v1 self
|
||||
{
|
||||
name = "set_layout_override",
|
||||
signature = "u",
|
||||
types = { 0, },
|
||||
},
|
||||
--- 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.
|
||||
-- @function river_xkb_binding_v1:enable
|
||||
-- @treturn river_xkb_binding_v1 self
|
||||
{
|
||||
name = "enable",
|
||||
signature = "",
|
||||
types = { },
|
||||
},
|
||||
--- 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.
|
||||
-- @function river_xkb_binding_v1:disable
|
||||
-- @treturn river_xkb_binding_v1 self
|
||||
{
|
||||
name = "disable",
|
||||
signature = "",
|
||||
types = { },
|
||||
},
|
||||
},
|
||||
events = {
|
||||
--- 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.
|
||||
-- @event river_xkb_binding_v1:pressed
|
||||
{
|
||||
name = "pressed",
|
||||
signature = "",
|
||||
types = { },
|
||||
},
|
||||
--- 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.
|
||||
-- @event river_xkb_binding_v1:released
|
||||
{
|
||||
name = "released",
|
||||
signature = "",
|
||||
types = { },
|
||||
},
|
||||
--- 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.
|
||||
-- @event river_xkb_binding_v1:stop_repeat
|
||||
{
|
||||
name = "stop_repeat",
|
||||
signature = "2",
|
||||
types = { },
|
||||
},
|
||||
},
|
||||
enums = {
|
||||
},
|
||||
methods_opcode = {
|
||||
["destroy"] = 0,
|
||||
["set_layout_override"] = 1,
|
||||
["enable"] = 2,
|
||||
["disable"] = 3,
|
||||
},
|
||||
}
|
||||
|
||||
--- xkb bindings seat
|
||||
--
|
||||
-- This object manages xkb bindings state associated with a specific seat.
|
||||
-- @type river_xkb_bindings_seat_v1
|
||||
wau.river_xkb_bindings_seat_v1:init {
|
||||
name = "river_xkb_bindings_seat_v1",
|
||||
version = 2,
|
||||
methods = {
|
||||
--- destroy the object
|
||||
--
|
||||
-- This request indicates that the client will no longer use the object and
|
||||
-- that it may be safely destroyed.
|
||||
-- @function river_xkb_bindings_seat_v1:destroy
|
||||
-- @treturn river_xkb_bindings_seat_v1 self
|
||||
{
|
||||
name = "destroy",
|
||||
signature = "2",
|
||||
types = { },
|
||||
type = "destructor"
|
||||
},
|
||||
--- 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.
|
||||
-- @function river_xkb_bindings_seat_v1:ensure_next_key_eaten
|
||||
-- @treturn river_xkb_bindings_seat_v1 self
|
||||
{
|
||||
name = "ensure_next_key_eaten",
|
||||
signature = "2",
|
||||
types = { },
|
||||
},
|
||||
--- 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.
|
||||
-- @function river_xkb_bindings_seat_v1:cancel_ensure_next_key_eaten
|
||||
-- @treturn river_xkb_bindings_seat_v1 self
|
||||
{
|
||||
name = "cancel_ensure_next_key_eaten",
|
||||
signature = "2",
|
||||
types = { },
|
||||
},
|
||||
},
|
||||
events = {
|
||||
--- 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.
|
||||
-- @event river_xkb_bindings_seat_v1:ate_unbound_key
|
||||
{
|
||||
name = "ate_unbound_key",
|
||||
signature = "2",
|
||||
types = { },
|
||||
},
|
||||
},
|
||||
enums = {
|
||||
},
|
||||
methods_opcode = {
|
||||
["destroy"] = 0,
|
||||
["ensure_next_key_eaten"] = 1,
|
||||
["cancel_ensure_next_key_eaten"] = 2,
|
||||
},
|
||||
}
|
||||
|
||||
end
|
||||
268
planar/protocol/river-xkb-bindings-v1.xml
Normal file
268
planar/protocol/river-xkb-bindings-v1.xml
Normal file
@@ -0,0 +1,268 @@
|
||||
<?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="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.
|
||||
</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>
|
||||
26
planar/xkbcommon.lua
Normal file
26
planar/xkbcommon.lua
Normal file
@@ -0,0 +1,26 @@
|
||||
-- SPDX-FileCopyrightText: © 2026 FireFly
|
||||
-- SPDX-License-Identifier: 0BSD
|
||||
|
||||
local ffi = require("cffi")
|
||||
|
||||
local M = {}
|
||||
|
||||
local raw = ffi.load("xkbcommon")
|
||||
|
||||
ffi.cdef [[
|
||||
typedef uint32_t xkb_keysym_t;
|
||||
|
||||
enum xkb_keysym_flags {
|
||||
XKB_KEYSYM_NO_FLAGS = 0,
|
||||
XKB_KEYSYM_CASE_INSENSITIVE = (1 << 0)
|
||||
};
|
||||
|
||||
xkb_keysym_t
|
||||
xkb_keysym_from_name(const char *name, enum xkb_keysym_flags flags);
|
||||
]]
|
||||
|
||||
function M.keysym(name, flags)
|
||||
return raw.xkb_keysym_from_name(name, flags or 0)
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user