diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..468d90c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+*build*/
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..71cde2f
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,39 @@
+# SPDX-FileCopyrightText: © 2026 Isaac Freund
+# SPDX-License-Identifier: 0BSD
+
+project(
+ 'srwwm',
+ 'c',
+ meson_version: '>= 1.8.0',
+ version: '0.1',
+ default_options: [
+ 'c_std=c99',
+ 'warning_level=3',
+ ],
+)
+
+cc = meson.get_compiler('c')
+add_project_arguments(cc.get_supported_arguments([
+ '-D_POSIX_C_SOURCE=200809L',
+ '-Wno-unused-parameter',
+]), language: 'c')
+
+wayland_client = dependency('wayland-client')
+xkbcommon = dependency('xkbcommon')
+
+wl_mod = import('wayland')
+generated = wl_mod.scan_xml(
+ 'protocol/river-window-management-v1.xml',
+ 'protocol/river-xkb-bindings-v1.xml',
+)
+
+exe = executable(
+ 'srwwm',
+ 'src/main.c',
+ generated,
+ dependencies: [
+ wayland_client,
+ xkbcommon,
+ ],
+ install: true,
+)
diff --git a/protocol/river-window-management-v1.xml b/protocol/river-window-management-v1.xml
new file mode 100644
index 0000000..64bfa58
--- /dev/null
+++ b/protocol/river-window-management-v1.xml
@@ -0,0 +1,1854 @@
+
+
+
+ SPDX-FileCopyrightText: © 2024 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.
+
+
+
+ This protocol allows a single "window manager" client to determine the
+ window management policy of the compositor. State is globally
+ double-buffered allowing for frame perfect state changes involving multiple
+ windows.
+
+ 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.
+
+
+
+
+ This global interface should only be advertised to the window manager
+ process. Only one window management client may be active at a time. The
+ compositor should use the unavailable event if necessary to enforce this.
+
+ There are two disjoint categories of state managed by this protocol:
+
+ Window management state influences the communication between the
+ compositor and individual windows (e.g. xdg_toplevels). Window management
+ state includes window dimensions, fullscreen state, keyboard focus,
+ keyboard bindings, and more.
+
+ Rendering state only affects the rendered output of the compositor and
+ does not influence communication between the compositor and individual
+ windows. Rendering state includes the position and rendering order of
+ windows, shell surfaces, decoration surfaces, borders, and more.
+
+ Window management state may only be modified by the window manager as part
+ of a manage sequence. A manage sequence is started with the manage_start
+ event and ended with the manage_finish request. It is a protocol error to
+ modify window management state outside of a manage sequence.
+
+ A manage sequence is always followed by at least one render sequence. A
+ render sequence is started with the render_start event and ended with the
+ render_finish request.
+
+ Rendering state may be modified by the window manager during a manage
+ sequence or a render sequence. Regardless of when the rendering state is
+ modified, it is applied with the next render_finish request. It is a
+ protocol error to modify rendering state outside of a manage or render
+ sequence.
+
+ The server will start a manage sequence by sending new state and the
+ manage_start event as soon as possible whenever there is a change in state
+ that must be communicated with the window manager.
+
+ If the window manager client needs to ensure a manage sequence is started
+ due to a state change the compositor is not aware of, it may send the
+ manage_dirty request.
+
+ The server will start a render sequence by sending new state and the
+ render_start event as soon as possible whenever there is a change in
+ window dimensions that must be communicated with the window manager.
+ Multiple render sequences may be made consecutively without a manage
+ sequence in between, for example if a window independently changes its own
+ dimensions.
+
+ To summarize, the main loop of this protocol is as follows:
+
+ 1. The server sends events indicating all changes since the last
+ manage sequence followed by the manage_start event.
+
+ 2. The client sends requests modifying window management state or
+ rendering state (as defined above) followed by the manage_finish
+ request.
+
+ 3. The server sends new state to windows and waits for responses.
+
+ 4. The server sends new window dimensions to the client followed by the
+ render_start event.
+
+ 5. The client sends requests modifying rendering state (as defined above)
+ followed by the render_finish request.
+
+ 6. If window dimensions change, loop back to step 4.
+ If state that requires a manage sequence changes or if the client makes
+ a manage_dirty request, loop back to step 1.
+
+ For the purposes of frame perfection, the server may delay rendering new
+ state committed by the windows in step 3 until after step 5 is finished.
+
+ It is a protocol error for the client to make a manage_finish or
+ render_finish request that violates this ordering.
+
+
+
+
+
+
+
+
+
+
+ This event indicates that window management is not available to the
+ client, perhaps due to another window management client already running.
+ The circumstances causing this event to be sent are compositor policy.
+
+ If sent, this event is guaranteed to be the first and only event sent by
+ the server.
+
+ The server will send no further events on this object. The client should
+ destroy this object and all objects created through this interface.
+
+
+
+
+
+ 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_window_manager_v1.finished event before destroying this
+ object.
+
+
+
+
+
+ This event indicates that the server will send no further events on this
+ object. The client should destroy the object. See
+ river_window_manager_v1.destroy for more information.
+
+
+
+
+
+ This request should be called after the finished event has been received
+ to complete destruction of the object.
+
+ If a client wishes to destroy this object it should send a
+ river_window_manager_v1.stop request and wait for a
+ river_window_manager_v1.finished event. Once the finished event is
+ received it is safe to destroy this object and any other objects created
+ through this interface.
+
+
+
+
+
+ This event indicates that the server has sent events indicating all
+ state changes since the last manage sequence.
+
+ In response to this event, the client should make requests modifying
+ window management state as it chooses. Then, the client must make the
+ manage_finish request.
+
+ See the description of the river_window_manager_v1 interface for a
+ complete overview of the manage/render sequence loop.
+
+
+
+
+
+ This request indicates that the client has made all changes to window
+ management state it wishes to include in the current manage sequence and
+ that the server should atomically send these state changes to the
+ windows and continue with the manage sequence.
+
+ After sending this request, it is a protocol error for the client to
+ make further changes to window management state until the next
+ manage_start event is received.
+
+ See the description of the river_window_manager_v1 interface for a
+ complete overview of the manage/render sequence loop.
+
+
+
+
+
+ This request ensures a manage sequence is started and that a
+ manage_start event is sent by the server. If this request is made during
+ an ongoing manage sequence, a new manage sequence will be started as
+ soon as the current one is completed.
+
+ The client may want to use this request due to an internal state change
+ that the compositor is not aware of (e.g. a dbus event) which should
+ affect window management or rendering state.
+
+
+
+
+
+ This event indicates that the server has sent all river_node_v1.position
+ and river_window_v1.dimensions events necessary.
+
+ In response to this event, the client should make requests modifying
+ rendering state as it chooses. Then, the client must make the
+ render_finish request.
+
+ See the description of the river_window_manager_v1 interface for a
+ complete overview of the manage/render sequence loop.
+
+
+
+
+
+ This request indicates that the client has made all changes to rendering
+ state it wishes to include in the current manage sequence and that the
+ server should atomically apply and display these state changes to the
+ user.
+
+ After sending this request, it is a protocol error for the client to
+ make further changes to rendering state until the next manage_start or
+ render_start event is received, whichever comes first.
+
+ See the description of the river_window_manager_v1 interface for a
+ complete overview of the manage/render sequence loop.
+
+
+
+
+
+ This event indicates that the session has been locked.
+
+ The window manager may wish to restrict which key bindings are available
+ while locked or otherwise use this information.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+ This event indicates that the session has been unlocked.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+ A new window has been created.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+
+ A new logical output has been created, perhaps due to a new physical
+ monitor being plugged in or perhaps due to a change in configuration.
+
+ This event will be followed by river_output_v1.position and dimensions
+ events as well as a manage_start event after all other new state has
+ been sent by the server.
+
+
+
+
+
+
+ A new seat has been created.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+
+ Create a new shell surface for window manager UI and assign the
+ river_shell_surface_v1 role to the surface.
+
+ Providing a wl_surface which already has a role or already has a buffer
+ attached or committed is a protocol error.
+
+
+
+
+
+
+
+ End the current Wayland session and exit the compositor.
+ All Wayland clients running in the current session, including
+ the window manager, will be disconnected.
+
+ Window managers should only make this request if the user explicitly
+ asks to exit the Wayland session, not for example on normal window
+ manager termination.
+
+
+
+
+
+
+ This represents a logical window. For example, a window may correspond to
+ an xdg_toplevel or Xwayland window.
+
+ A newly created window will not be displayed until the window manager
+ makes a propose_dimensions or fullscreen request as part of a manage
+ sequence, the server replies with a dimensions event as part of a render
+ sequence, and that render sequence is finished.
+
+
+
+
+
+
+
+
+
+
+
+ This request indicates that the client will no longer use the window
+ object and that it may be safely destroyed.
+
+ This request should be made after the river_window_v1.closed event or
+ river_window_manager_v1.finished is received to complete destruction of
+ the window.
+
+
+
+
+
+ The window has been closed by the server, perhaps due to an
+ xdg_toplevel.close request or similar.
+
+ The server will send no further events on this object and ignore any
+ request other than river_window_v1.destroy made after this event is
+ sent. The client should destroy this object with the
+ river_window_v1.destroy request to free up resources.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+ Request that the window be closed. The window may ignore this request or
+ only close after some delay, perhaps opening a dialog asking the user to
+ save their work or similar.
+
+ The server will send a river_window_v1.closed event if/when the window
+ has been closed.
+
+ This request modifies window management state and may only be made as
+ part of a manage sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+ Get the node in the render list corresponding to the window.
+
+ It is a protocol error to make this request more than once for a single
+ window.
+
+
+
+
+
+
+ This event informs the window manager of the window's preferred min/max
+ dimensions. These preferences are a hint, and the window manager is free
+ to propose dimensions outside of these bounds.
+
+ All min/max width/height values must be strictly greater than or equal
+ to 0. A value of 0 indicates that the window has no preference for that
+ value.
+
+ The min_width/min_height must be strictly less than or equal to the
+ max_width/max_height.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+
+
+
+
+ This event indicates the dimensions of the window in the compositor's
+ logical coordinate space. The width and height must be strictly greater
+ than zero.
+
+ Note that the dimensions of a river_window_v1 refer to the dimensions of
+ the window content and are unaffected by the presence of borders or
+ decoration surfaces.
+
+ This event is sent as part of a render sequence before the render_start
+ event.
+
+ It may be sent due to a propose_dimensions or fullscreen request in a
+ previous manage sequence or because a window independently decides to
+ change its dimensions.
+
+ The window will not be displayed until the first dimensions event is
+ received and the render sequence is finished.
+
+
+
+
+
+
+
+ This request proposes dimensions for the window in the compositor's
+ logical coordinate space.
+
+ The width and height must be greater than or equal to zero. If the width
+ or height is zero the window will be allowed to decide its own
+ dimensions.
+
+ The window may not take the exact dimensions proposed. The actual
+ dimensions taken by the window will be sent in a subsequent
+ river_window_v1.dimensions event. For example, a terminal emulator may
+ only allow dimensions that are multiple of the cell size.
+
+ When a propose_dimensions request is made, the server must send a
+ dimensions event in response as soon as possible. It may not be possible
+ to send a dimensions event in the very next render sequence if, for
+ example, the window takes too long to respond to the proposed
+ dimensions. In this case, the server will send the dimensions event in a
+ future render sequence.
+
+ Note that the dimensions of a river_window_v1 refer to the dimensions of
+ the window content and are unaffected by the presence of borders or
+ decoration surfaces.
+
+ This request modifies window management state and may only be made as
+ part of a manage sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+
+
+ Request that the window be hidden. Has no effect if the window is
+ already hidden. Hides any window borders and decorations as well.
+
+ Newly created windows are considered shown unless explicitly hidden with
+ the hide request.
+
+ This request modifies rendering state and may only be made as part of a
+ render sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+ Request that the window be shown. Has no effect if the window is not
+ hidden. Does not guarantee that the window is visible as it may be
+ completely obscured by other windows placed above it for example.
+
+ Newly created windows are considered shown unless explicitly hidden with
+ the hide request.
+
+ This request modifies rendering state and may only be made as part of a
+ render sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+ The window set an application ID.
+
+ The app_id argument will be null if the window has never set an
+ application ID or if the window cleared its application ID. (Xwayland
+ windows may do this for example, though xdg-toplevels may not.)
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+
+ The window set a title.
+
+ The title argument will be null if the window has never set a title or
+ if the window cleared its title. (Xwayland windows may do this for
+ example, though xdg-toplevels may not.)
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+
+ The window set a parent window. If this event is never received or if
+ the parent argument is null then the window has no parent.
+
+ A surface with a parent set might be a dialog, file picker, or similar
+ for the parent window.
+
+ Child windows should generally be rendered directly above their parent.
+
+ The compositor must guarantee that there are no loops in the window
+ tree: a parent must not be the descendant of one of its children.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Information from the window about the supported and preferred client
+ side/server side decoration options.
+
+ This event may be sent multiple times over the lifetime of the window if
+ the window changes its preferences.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+
+ Tell the client to use client side decoration and draw its own title
+ bar, borders, etc.
+
+ This is the default if neither this request nor the use_ssd request is
+ ever made.
+
+ This request modifies window management state and may only be made as
+ part of a manage sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+ Tell the client to use server side decoration and not draw any client
+ side decorations.
+
+ This request will have no effect if the client only supports client side
+ decoration, see the decoration_hint event.
+
+ This request modifies window management state and may only be made as
+ part of a manage sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This request decorates the window with borders drawn by the compositor
+ on the specified edges of the window. Borders are drawn above the window
+ content.
+
+ Corners are drawn only between borders on adjacent edges. If e.g. the
+ left edge has a border and the top edge does not, the border drawn on
+ the left edge will not extend vertically beyond the top edge of the
+ window.
+
+ Borders are not drawn while the window is fullscreen.
+
+ The color is defined by four 32-bit RGBA values. Unless specified in
+ another protocol extension, the RGBA values use pre-multiplied alpha.
+
+ Setting the edges to none or the width to 0 disables the borders.
+ Setting a negative width is a protocol error.
+
+ This request completely overrides all previous set_borders requests.
+ Only the most recent set_borders request has an effect.
+
+ Note that the position/dimensions of a river_window_v1 refer to the
+ position/dimensions of the window content and are unaffected by the
+ presence of borders or decoration surfaces.
+
+ This request modifies rendering state and may only be made as part of a
+ render sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+
+
+
+
+
+
+ Inform the window that it is part of a tiled layout and adjacent to
+ other elements in the tiled layout on the given edges.
+
+ The window should use this information to change the style of its client
+ side decorations and avoid drawing e.g. drop shadows outside of the
+ window dimensions on the tiled edges.
+
+ Setting the edges argument to none informs the window that it is not
+ part of a tiled layout. If this request is never made, the window is
+ informed that it is not part of a tiled layout.
+
+ This request modifies window management state and may only be made as
+ part of a manage sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+
+ Create a decoration surface and assign the river_decoration_v1 role to
+ the surface. The created decoration is placed above the window in
+ rendering order, see the description of river_decoration_v1.
+
+ Providing a wl_surface which already has a role or already has a buffer
+ attached or committed is a protocol error.
+
+
+
+
+
+
+
+ Create a decoration surface and assign the river_decoration_v1 role to
+ the surface. The created decoration is placed below the window in
+ rendering order, see the description of river_decoration_v1.
+
+ Providing a wl_surface which already has a role or already has a buffer
+ attached or committed is a protocol error.
+
+
+
+
+
+
+
+ This event informs the window manager that the window has requested to
+ be interactively moved using the pointer. The seat argument indicates the
+ seat for the move.
+
+ The xdg-shell protocol for example allows windows to request that an
+ interactive move be started, perhaps when a client-side rendered
+ titlebar is dragged.
+
+ The window manager may use the river_seat_v1.op_start_pointer request to
+ interactively move the window or ignore this event entirely.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+
+ This event informs the window manager that the window has requested to
+ be interactively resized using the pointer. The seat argument indicates
+ the seat for the resize.
+
+ The edges argument indicates which edges the window has requested to be
+ resized from. The edges argument will never be none and will never have
+ both top and bottom or both left and right edges set.
+
+ The xdg-shell protocol for example allows windows to request that an
+ interactive resize be started, perhaps when the corner of client-side
+ rendered decorations is dragged.
+
+ The window manager may use the river_seat_v1.op_start_pointer request to
+ interactively resize the window or ignore this event entirely.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+
+
+ Inform the window that it is being resized. The window manager should
+ use this request to inform windows that are the target of an interactive
+ resize for example.
+
+ The window manager remains responsible for handling the position and
+ dimensions of the window while it is resizing.
+
+ This request modifies window management state and may only be made as
+ part of a manage sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+ Inform the window that it is no longer being resized. The window manager
+ should use this request to inform windows that are the target of an
+ interactive resize that the interactive resize has ended for example.
+
+ This request modifies window management state and may only be made as
+ part of a manage sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+
+
+
+
+
+
+
+ This request informs the window of the capabilities supported by the
+ window manager. If the window manager, for example, ignores requests to
+ be maximized from the window it should not tell the window that it
+ supports the maximize capability.
+
+ The window might use this information to, for example, only show a
+ maximize button if the window manager supports the maximize capability.
+
+ The window manager client should use this request to set capabilities
+ for all new windows. If this request is never made, the compositor will
+ inform windows that all capabilities are supported.
+
+ This request modifies window management state and may only be made as
+ part of a manage sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+
+ The xdg-shell protocol for example allows windows to request that a
+ window menu be shown, for example when the user right clicks on client
+ side window decorations.
+
+ A window menu might include options to maximize or minimize the window.
+
+ The window manager is free to ignore this request and decide what the
+ window menu contains if it does choose to show one.
+
+ The x and y arguments indicate where the window requested that the
+ window menu be shown.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+
+
+ The xdg-shell protocol for example allows windows to request to be
+ maximized.
+
+ The window manager is free to honor this request using
+ river_window_v1.inform_maximize or ignore it.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+ The xdg-shell protocol for example allows windows to request to be
+ unmaximized.
+
+ The window manager is free to honor this request using
+ river_window_v1.inform_unmaximized or ignore it.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+ Inform the window that it is maximized. The window might use this
+ information to adapt the style of its client-side window decorations for
+ example.
+
+ The window manager remains responsible for handling the position and
+ dimensions of the window while it is maximized.
+
+ This request modifies window management state and may only be made as
+ part of a manage sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+ Inform the window that it is unmaximized. The window might use this
+ information to adapt the style of its client-side window decorations for
+ example.
+
+ This request modifies window management state and may only be made as
+ part of a manage sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+ The xdg-shell protocol for example allows windows to request that they
+ be made fullscreen and allows them to provide an optional output hint.
+
+ If the output argument is null, the window has no preference and the
+ window manager should choose an output.
+
+ The window manager is free to honor this request using
+ river_window_v1.fullscreen or ignore it.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+
+ The xdg-shell protocol for example allows windows to request to exit
+ fullscreen.
+
+ The window manager is free to honor this request using
+ river_window_v1.exit_fullscreen or ignore it.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+ Inform the window that it is fullscreen. The window might use this
+ information to adapt the style of its client-side window decorations for
+ example.
+
+ This request does not affect the size/position of the window or cause it
+ to become the only window rendered, see the river_window_v1.fullscreen
+ and exit_fullscreen requests for that.
+
+ This request modifies window management state and may only be made as
+ part of a manage sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+ Inform the window that it is not fullscreen. The window might use this
+ information to adapt the style of its client-side window decorations for
+ example.
+
+ This request does not affect the size/position of the window or cause it
+ to become the only window rendered, see the river_window_v1.fullscreen
+ and exit_fullscreen requests for that.
+
+ This request modifies window management state and may only be made as
+ part of a manage sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+ Make the window fullscreen on the given output. If multiple windows are
+ fullscreen on the same output at the same time only the "top" window in
+ rendering order shall be displayed.
+
+ All river_shell_surface_v1 objects above the top fullscreen window in
+ the rendering order will continue to be rendered.
+
+ The compositor will handle the position and dimensions of the window
+ while it is fullscreen. The set_position and propose_dimensions requests
+ shall not affect the current position and dimensions of a fullscreen
+ window.
+
+ When a fullscreen request is made, the server must send a dimensions
+ event in response as soon as possible. It may not be possible to send a
+ dimensions event in the very next render sequence if, for example, the
+ window takes too long to respond. In this case, the server will send the
+ dimensions event in a future render sequence.
+
+ The compositor will clip window content, decoration surfaces, and
+ borders to the given output's dimensions while the window is fullscreen.
+ The effects of set_clip_box and set_content_clip_box are ignored while
+ the window is fullscreen.
+
+ If the output on which a window is currently fullscreen is removed, the
+ windowing state is modified as if there were an exit_fullscreen request
+ made in the same manage sequence as the river_output_v1.removed event.
+
+ This request does not inform the window that it is fullscreen, see the
+ river_window_v1.inform_fullscreen and inform_not_fullscreen requests.
+
+ This request modifies window management state and may only be made as
+ part of a manage sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+
+ Make the window not fullscreen.
+
+ The position and dimensions are undefined after this request is made
+ until a manage sequence in which the window manager makes the
+ propose_dimensions and set_position requests is completed.
+
+ The window manager should make propose_dimensions and set_position
+ requests in the same manage sequence as the exit_fullscreen request for
+ frame perfection.
+
+ This request does not inform the window that it is fullscreen, see the
+ river_window_v1.inform_fullscreen and inform_not_fullscreen requests.
+
+ This request modifies window management state and may only be made as
+ part of a manage sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+ The xdg-shell protocol for example allows windows to request to be
+ minimized.
+
+ The window manager is free to ignore this request, hide the window, or
+ do whatever else it chooses.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+ Clip the window, including borders and decoration surfaces, to the box
+ specified by the x, y, width, and height arguments. The x/y position of
+ the box is relative to the top left corner of the window.
+
+ The width and height arguments must be greater than or equal to 0.
+
+ Setting a clip box with 0 width or height disables clipping.
+
+ The clip box is ignored while the window is fullscreen.
+
+ Both set_clip_box and set_content_clip_box may be enabled simultaneously.
+
+ This request modifies rendering state and may only be made as part of a
+ render sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+
+
+
+
+ This event gives an unreliable PID of the process that created the
+ window. Obtaining this information is inherently racy due to PID reuse.
+ Therefore, this PID must not be used for anything security sensitive.
+
+ Note also that a single process may create multiple windows, so there is
+ not necessarily a 1-to-1 mapping from PID to window. Multiple windows
+ may have the same PID.
+
+ This event is sent once when the river_window_v1 is created and never
+ sent again.
+
+
+
+
+
+
+ Clip the content of the window, excluding borders and decoration
+ surfaces, to the box specified by the x, y, width, and height arguments.
+ The x/y position of the box is relative to the top left corner of the
+ window.
+
+ Borders drawn by the compositor (see set_borders) are placed around the
+ intersection of the window content (as defined by the dimensions event)
+ and the content clip box when content clipping is enabled.
+
+ The width and height arguments must be greater than or equal to 0.
+
+ Setting a box with 0 width or height disables content clipping.
+
+ The content clip box is ignored while the window is fullscreen.
+
+ Both set_clip_box and set_content_clip_box may be enabled simultaneously.
+
+ This request modifies rendering state and may only be made as part of a
+ render sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+
+
+
+
+ This event communicates the window's preferred presentation mode.
+
+ This event will be followed by a render_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+
+ The identifier is a string that contains up to 32 printable ASCII bytes.
+ The identifier must not be an empty string.
+
+ It is compositor policy how the identifier is generated, but the following
+ properties must be upheld:
+
+ 1. The identifier must uniquely identify the window. Two windows must not
+ share the same identifier.
+
+ 2. The identifier must not be reused. This avoids races around window
+ creation/destruction when identifiers are used in out-of-band IPC.
+
+ If the compositor implements the ext-foreign-toplevel-list-v1 protocol,
+ the river_window_v1.identifier event must match the corresponding
+ ext_foreign_toplevel_handle_v1.identifier event.
+
+ This event is sent once when the river_window_v1 is created and never
+ sent again.
+
+
+
+
+
+
+ Recommend that the window keep its dimensions within a given
+ maximum width/height. This recommendation is only a hint and the window
+ may ignore it.
+
+ Setting the width and height to 0 indicates that there are no bounds
+ and is equivalent to having never made this request.
+
+ Setting width or height to a negative value is a protocol error.
+
+ The server should communicate this hint to an xdg_toplevel window with
+ the xdg_toplevel.configure_bounds event for example.
+
+ This request modifies window management state and may only be made as
+ part of a manage sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+
+
+
+ The rendering order of windows with decorations is follows:
+
+ 1. Decorations created with get_decoration_below at the bottom
+ 2. Window content
+ 3. Borders configured with river_window_v1.set_borders
+ 4. Decorations created with get_decoration_above at the top
+
+ The relative ordering of decoration surfaces above/below a window is
+ undefined by this protocol and left up to the compositor.
+
+
+
+
+
+
+
+
+ This request indicates that the client will no longer use the decoration
+ object and that it may be safely destroyed.
+
+
+
+
+
+ This request sets the offset of the decoration surface from the top left
+ corner of the window.
+
+ If this request is never sent, the x and y offsets are undefined by this
+ protocol and left up to the compositor.
+
+ This request modifies rendering state and may only be made as part of a
+ render sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+
+
+ Synchronize application of the next wl_surface.commit request on the
+ decoration surface with rest of the state atomically applied with the
+ next river_window_manager_v1.render_finish request.
+
+ The client must make a wl_surface.commit request on the decoration
+ surface after this request and before the render_finish request, failure
+ to do so is a protocol error.
+
+ This request modifies rendering state and may only be made as part of a
+ render sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+
+ The window manager might use a shell surface to display a status bar,
+ background image, desktop notifications, launcher, desktop menu, or
+ whatever else it wants.
+
+
+
+
+
+
+
+
+
+ This request indicates that the client will no longer use the shell
+ surface object and that it may be safely destroyed.
+
+
+
+
+
+ Get the node in the render list corresponding to the shell surface.
+
+ It is a protocol error to make this request more than once for a single
+ shell surface.
+
+
+
+
+
+
+ Synchronize application of the next wl_surface.commit request on the
+ shell surface with rest of the rendering state atomically applied with
+ the next river_window_manager_v1.render_finish request.
+
+ The client must make a wl_surface.commit request on the shell surface
+ after this request and before the render_finish request, failure to do
+ so is a protocol error.
+
+ This request modifies rendering state and may only be made as part of a
+ render sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+
+ The render list is a list of nodes that determines the rendering order of
+ the compositor. Nodes may correspond to windows or shell surfaces. The
+ relative ordering of nodes may be changed with the place_above and
+ place_below requests, changing the rendering order.
+
+ The initial position of a node in the render list is undefined, the window
+ manager client must use the place_above or place_below request to
+ guarantee a specific rendering order.
+
+
+
+
+ This request indicates that the client will no longer use the node
+ object and that it may be safely destroyed.
+
+
+
+
+
+ Set the absolute position of the node in the compositor's logical
+ coordinate space. The x and y coordinates may be positive or negative.
+
+ Note that the position of a river_window_v1 refers to the position of
+ the window content and is unaffected by the presence of borders or
+ decoration surfaces.
+
+ If this request is never sent, the position of the node is undefined by
+ this protocol and left up to the compositor.
+
+ This request modifies rendering state and may only be made as part of a
+ render sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+
+
+ This request places the node above all other nodes in the compositor's
+ render list.
+
+ This request modifies rendering state and may only be made as part of a
+ render sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+ This request places the node below all other nodes in the compositor's
+ render list.
+
+ This request modifies rendering state and may only be made as part of a
+ render sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+ This request places the node directly above another node in the
+ compositor's render list.
+
+ Attempting to place a node above itself has no effect.
+
+ This request modifies rendering state and may only be made as part of a
+ render sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+
+ This request places the node directly below another node in the
+ compositor's render list.
+
+ Attempting to place a node below itself has no effect.
+
+ This request modifies rendering state and may only be made as part of a
+ render sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+
+
+ An area in the compositor's logical coordinate space that should be
+ treated as a single output for window management purposes. This area may
+ correspond to a single physical output or multiple physical outputs in the
+ case of mirroring or tiled monitors depending on the hardware and
+ compositor configuration.
+
+
+
+
+ This request indicates that the client will no longer use the output
+ object and that it may be safely destroyed.
+
+ This request should be made after the river_output_v1.removed event is
+ received to complete destruction of the output.
+
+
+
+
+
+ This event indicates that the logical output is no longer conceptually
+ part of window management space.
+
+ The server will send no further events on this object and ignore any
+ request (other than river_output_v1.destroy) made after this event is
+ sent. The client should destroy this object with the
+ river_output_v1.destroy request to free up resources.
+
+ This event may be sent because a corresponding physical output has been
+ physically unplugged or because some output configuration has changed.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+ The wl_output object corresponding to the river_output_v1. The argument
+ is the global name of the wl_output advertised with wl_registry.global.
+
+ It is guaranteed that the corresponding wl_output is advertised before
+ this event is sent.
+
+ This event is sent exactly once. The wl_output associated with a
+ river_output_v1 cannot change. It is guaranteed that there is a 1-to-1
+ mapping between wl_output and river_output_v1 objects.
+
+ The global_remove event for the corresponding wl_output may be sent
+ before the river_output_v1.remove event. This is due to the fact that
+ river_output_v1 state changes are synced to the river window management
+ manage sequence while changes to globals are not.
+
+ Rationale: The window manager may need information provided by the
+ wl_output interface such as the name/description. It also may need the
+ wl_output object to start screencopy for example.
+
+
+
+
+
+
+ This event indicates the position of the output in the compositor's
+ logical coordinate space. The x and y coordinates may be positive or
+ negative.
+
+ This event is sent once when the river_output_v1 is created and again
+ whenever the position changes.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+ The server must guarantee that the position and dimensions events do not
+ cause the areas of multiple logical outputs to overlap when the
+ corresponding manage_start event is received.
+
+
+
+
+
+
+
+ This event indicates the dimensions of the output in the compositor's
+ logical coordinate space. The width and height will always be strictly
+ greater than zero.
+
+ This event is sent once when the river_output_v1 is created and again
+ whenever the dimensions change.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+ The server must guarantee that the position and dimensions events do not
+ cause the areas of multiple logical outputs to overlap when the
+ corresponding manage_start event is received.
+
+
+
+
+
+
+
+
+
+
+
+
+ Output page-flips should be synchronized to the vertical blanking
+ period, eliminating tearing. This is the default presentation mode.
+
+
+
+
+ Output page-flips should not be synchronized to the vertical blanking
+ period, visual screen tearing may occur.
+
+
+
+
+
+
+ Set the preferred presentation mode of the output. The compositor should
+ always respect the preference of the window manager if possible. If this
+ request is never made, the preferred presentation mode is vsync.
+
+ This request modifies rendering state and may only be made as part of a
+ render sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+
+
+ This object represents a single user's collection of input devices. It
+ allows the window manager to route keyboard input to windows, get
+ high-level information about pointer input, define pointer bindings, etc.
+
+ For keyboard bindings, see the river-xkb-bindings-v1 protocol.
+
+ Since version 4: The cursor surface/shape set by the window manager on the
+ wl_pointer of this seat is used when no client has pointer focus, for
+ example during a pointer operation. Since the window manager is allowed to
+ set cursor surface/shape even when it does not have pointer focus, the
+ compositor must ignore the serial argument of wl_pointer.set_cursor and
+ wp_cursor_shape_device_v1.set_shape requests made by the window manager.
+
+ The most recent cursor surface/shape set by the window manager is
+ remembered by the compositor and restored whenever no client has pointer
+ focus. If the window manager never sets a cursor surface/shape, the
+ "default" shape is used.
+
+
+
+
+ This request indicates that the client will no longer use the seat
+ object and that it may be safely destroyed.
+
+ This request should be made after the river_seat_v1.removed event is
+ received to complete destruction of the seat.
+
+
+
+
+
+ This event indicates that seat is no longer in use and should be
+ destroyed.
+
+ The server will send no further events on this object and ignore any
+ request (other than river_seat_v1.destroy) made after this event is
+ sent. The client should destroy this object with the
+ river_seat_v1.destroy request to free up resources.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+ The wl_seat object corresponding to the river_seat_v1. The argument is
+ the global name of the wl_seat advertised with wl_registry.global.
+
+ It is guaranteed that the corresponding wl_seat is advertised before
+ this event is sent.
+
+ This event is sent exactly once. The wl_seat associated with a
+ river_seat_v1 cannot change. It is guaranteed that there is a 1-to-1
+ mapping between wl_seat and river_seat_v1 objects.
+
+ The global_remove event for the corresponding wl_seat may be sent before
+ the river_seat_v1.remove event. This is due to the fact that
+ river_seat_v1 state changes are synced to the river window management
+ manage sequence while changes to globals are not.
+
+ Rationale: The window manager may want to trigger window management
+ state changes based on normal input events received by its shell
+ surfaces for example.
+
+
+
+
+
+
+ Request that the compositor send keyboard input to the given window.
+
+ This request modifies window management state and may only be made as
+ part of a manage sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+
+ Request that the compositor send keyboard input to the given shell
+ surface.
+
+ This request modifies window management state and may only be made as
+ part of a manage sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+
+ Request that the compositor not send keyboard input to any client.
+
+ This request modifies window management state and may only be made as
+ part of a manage sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+ The seat's pointer entered the given window's area.
+
+ The area of a window is defined to include the area defined by the
+ window dimensions, borders configured using river_window_v1.set_borders,
+ and the input regions of decoration surfaces. In particular, it does not
+ include input regions of surfaces belonging to the window that extend
+ outside the window dimensions.
+
+ The pointer of a seat may only enter a single window at a time. When the
+ pointer moves between windows, the pointer_leave event for the old
+ window must be sent before the pointer_enter event for the new window.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+
+ The seat's pointer left the window for which pointer_enter was most
+ recently sent. See pointer_enter for details.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+ A window has been interacted with beyond the pointer merely passing over
+ it. This event might be sent due to a pointer button press or due to a
+ touch/tablet tool interaction with the window.
+
+ There are no guarantees regarding how this event is sent in relation to
+ the pointer_enter and pointer_leave events as the interaction may use
+ touch or tablet tool input.
+
+ Rationale: this event gives window managers necessary information to
+ determine when to send keyboard focus, raise a window that already has
+ keyboard focus, etc. Rather than expose all pointer, touch, and tablet
+ events to window managers, a policy over mechanism approach is taken.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+
+ A shell surface has been interacted with beyond the pointer merely
+ passing over it. This event might be sent due to a pointer button press
+ or due to a touch/tablet tool interaction with the shell_surface.
+
+ There are no guarantees regarding how this event is sent in relation to
+ the pointer_enter and pointer_leave events as the interaction may use
+ touch or tablet tool input.
+
+ Rationale: While the shell surface does receive all wl_pointer,
+ wl_touch, etc. input events for the surface directly, these events do
+ not necessarily trigger a manage sequence and therefore do not allow the
+ window manager to update focus or perform other actions in response to
+ the input in a race-free way.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+
+ Start an interactive pointer operation. During the operation, op_delta
+ events will be sent based on pointer input.
+
+ When all pointer buttons are released, the op_release event is sent.
+
+ The pointer operation continues until the op_end request is made during
+ a manage sequence and that manage sequence is finished.
+
+ The window manager may use this operation to implement interactive
+ move/resize of windows by setting the position of windows and proposing
+ dimensions based off of the op_delta events.
+
+ This request is ignored if an operation is already in progress.
+
+ The compositor must ensure that no client has pointer focus from this
+ seat during the pointer operation. This means that the window manager
+ has control over the pointer's cursor surface/shape during the pointer
+ operation. See the river_seat_v1 description.
+
+ This request modifies window management state and may only be made as
+ part of a manage sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+ This event indicates the total change in position since the start of the
+ operation of the pointer/touch point/etc.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+
+
+ The input driving the current interactive operation has been released.
+ For a pointer op for example, all pointer buttons have been released.
+
+ Depending on the op type, op_delta events may continue to be sent until
+ the op is ended with the op_end request.
+
+ This event is sent at most once during an interactive operation.
+
+ This event will be followed by a manage_start event after all other new
+ state has been sent by the server.
+
+
+
+
+
+ End an interactive operation.
+
+ This request is ignored if there is no operation in progress.
+
+ This request modifies window management state and may only be made as
+ part of a manage sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+ This enum is used to describe the keyboard modifiers that must be held
+ down to trigger a key binding or pointer binding.
+
+ Note that river and wlroots use the values 2 and 16 for capslock and
+ numlock internally. It doesn't make sense to use locked modifiers for
+ bindings however so these values are not included in this enum.
+
+
+
+
+
+
+
+
+
+
+
+
+ Define a pointer binding in terms of a pointer button, keyboard
+ modifiers, and other configurable properties.
+
+ The button argument is a Linux input event code defined in the
+ linux/input-event-codes.h header file (e.g. BTN_RIGHT).
+
+ The new pointer binding is not enabled until initial configuration is
+ completed and the enable request is made during a manage sequence.
+
+
+
+
+
+
+
+
+ Set the XCursor theme for the seat. This theme is used for cursors
+ rendered by the compositor, but not necessarily for cursors rendered by
+ clients.
+
+ Note: The window manager may also wish to set the XCURSOR_THEME and
+ XCURSOR_SIZE environment variable for programs it starts.
+
+
+
+
+
+
+
+ The current position of the pointer in the compositor's logical
+ coordinate space.
+
+ This state is special in that a change in pointer position alone must
+ not cause the compositor to start a manage sequence.
+
+ Assuming the seat has a pointer, this event must be sent in every manage
+ sequence unless there is no change in x/y position since the last time this
+ event was sent.
+
+
+
+
+
+
+
+ Warp the pointer to the given position in the compositor's logical
+ coordinate space.
+
+ If the given position is outside the bounds of all outputs, the pointer
+ will be warped to the closest point inside an output instead.
+
+ This request modifies window management state and may only be made as
+ part of a manage sequence, see the river_window_manager_v1 description.
+
+
+
+
+
+
+
+
+ This object allows the window manager to configure a pointer binding and
+ receive events when the binding is triggered.
+
+ The new pointer binding is not enabled until the enable request is made
+ during a manage sequence.
+
+ Normally, all pointer button events are sent to the surface with pointer
+ focus by the compositor. Pointer button events that trigger a pointer
+ binding are not sent to the surface with pointer focus.
+
+ If multiple pointer bindings would be triggered by a single physical
+ pointer event on the compositor side, it is compositor policy which
+ pointer binding(s) will receive press/release events or if all of the
+ matched pointer bindings receive press/release events.
+
+
+
+
+ This request indicates that the client will no longer use the pointer
+ binding object and that it may be safely destroyed.
+
+
+
+
+
+ This request should be made after all initial configuration has been
+ completed and the window manager wishes the pointer 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.
+
+
+
+
+
+ This request may be used to temporarily disable the pointer 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.
+
+
+
+
+
+ This event indicates that the pointer button 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.
+
+
+
+
+
+ This event indicates that the pointer button triggering the binding has
+ been released.
+
+ Releasing the modifiers for the binding without releasing the pointer
+ button does not trigger the release event. This event is sent when the
+ pointer button 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.
+
+
+
+
diff --git a/protocol/river-xkb-bindings-v1.xml b/protocol/river-xkb-bindings-v1.xml
new file mode 100644
index 0000000..de4f100
--- /dev/null
+++ b/protocol/river-xkb-bindings-v1.xml
@@ -0,0 +1,268 @@
+
+
+
+ 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.
+
+
+
+ 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.
+
+
+
+
+ This global interface should only be advertised to the client if the
+ river_window_manager_v1 global is also advertised.
+
+
+
+
+
+
+
+
+ This request indicates that the client will no longer use the
+ river_xkb_bindings_v1 object.
+
+
+
+
+
+ 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.
+
+
+
+
+
+
+
+
+
+ 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.
+
+
+
+
+
+
+
+
+ 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.
+
+
+
+
+ This request indicates that the client will no longer use the xkb key
+ binding object and that it may be safely destroyed.
+
+
+
+
+
+ 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.
+
+
+
+
+
+
+ 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.
+
+
+
+
+
+ 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.
+
+
+
+
+
+ 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.
+
+
+
+
+
+ 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.
+
+
+
+
+
+ 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.
+
+
+
+
+
+
+ This object manages xkb bindings state associated with a specific seat.
+
+
+
+
+ This request indicates that the client will no longer use the object and
+ that it may be safely destroyed.
+
+
+
+
+
+ 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.
+
+
+
+
+
+ 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.
+
+
+
+
+
+ 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.
+
+
+
+
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..0c5ce7c
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,702 @@
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+struct Output {
+ struct river_output_v1 *obj;
+ bool removed;
+ struct wl_list link; // WindowManager.outputs
+};
+
+struct Window {
+ struct river_window_v1 *obj;
+ struct river_node_v1 *node;
+
+ bool new;
+ bool closed;
+
+ int32_t x;
+ int32_t y;
+ int32_t width;
+ int32_t height;
+
+ struct Seat *pointer_move_requested;
+ struct Seat *pointer_resize_requested;
+ uint32_t pointer_resize_requested_edges;
+
+ struct wl_list link; // WindowManager.windows
+};
+
+enum Action {
+ ACTION_NONE,
+ ACTION_SPAWN_TERMINAL,
+ ACTION_CLOSE,
+ ACTION_FOCUS_NEXT,
+ ACTION_MOVE,
+ ACTION_RESIZE,
+ ACTION_EXIT,
+};
+
+struct XkbBinding {
+ struct river_xkb_binding_v1 *obj;
+ struct Seat *seat;
+ enum Action action;
+ struct wl_list link;
+};
+
+struct PointerBinding {
+ struct river_pointer_binding_v1 *obj;
+ struct Seat *seat;
+ enum Action action;
+ struct wl_list link;
+};
+
+enum SeatOp {
+ SEAT_OP_NONE,
+ SEAT_OP_MOVE,
+ SEAT_OP_RESIZE,
+};
+
+struct Seat {
+ struct river_seat_v1 *obj;
+ bool new;
+ bool removed;
+
+ struct Window *focused;
+ struct Window *hovered;
+ struct Window *interacted;
+
+ struct wl_list xkb_bindings; // XkbBinding
+ struct wl_list pointer_bindings; // PointerBinding
+ enum Action pending_action;
+
+ enum SeatOp op;
+ // For SEAT_OP_MOVE and SEAT_OP_RESIZE
+ struct Window *op_window;
+ int32_t op_start_x, op_start_y;
+ int32_t op_dx, op_dy;
+ bool op_release;
+ // For SEAT_OP_RESIZE only
+ int32_t op_start_width, op_start_height;
+ uint32_t op_edges;
+
+ struct wl_list link; // WindowManager.seats
+};
+
+struct WindowManager {
+ struct wl_list outputs; // Output
+ struct wl_list windows; // Window
+ struct wl_list seats; // Seat
+};
+
+struct WindowManager wm;
+
+struct river_window_manager_v1 *window_manager_v1;
+struct river_xkb_bindings_v1 *xkb_bindings_v1;
+
+static void output_handle_removed(void *data, struct river_output_v1 *obj) {
+ struct Output *output = data;
+ output->removed = true;
+}
+
+// Ignored events
+static void output_handle_wl_output(void *data, struct river_output_v1 *obj, uint32_t name) {}
+static void output_handle_position(void *data, struct river_output_v1 *obj, int32_t x, int32_t y) {}
+static void output_handle_dimensions(void *data, struct river_output_v1 *obj, int32_t width, int32_t height) {}
+
+const struct river_output_v1_listener river_output_listener = {
+ .removed = output_handle_removed,
+ .wl_output = output_handle_wl_output,
+ .position = output_handle_position,
+ .dimensions = output_handle_dimensions,
+};
+
+static void output_maybe_destroy(struct Output *output) {
+ if (!output->removed) {
+ return;
+ }
+ river_output_v1_destroy(output->obj);
+ wl_list_remove(&output->link);
+ free(output);
+}
+
+static void window_handle_closed(void *data, struct river_window_v1 *obj) {
+ struct Window *window = data;
+ window->closed = true;
+}
+
+static void window_handle_dimensions(
+ void *data, struct river_window_v1 *obj, int32_t width, int32_t height) {
+ struct Window *window = data;
+ window->width = width;
+ window->height = height;
+}
+
+static void window_handle_pointer_move_requested(
+ void *data, struct river_window_v1 *obj, struct river_seat_v1 *river_seat) {
+ struct Window *window = data;
+ window->pointer_move_requested = river_seat_v1_get_user_data(river_seat);
+}
+
+static void window_handle_pointer_resize_requested(
+ void *data, struct river_window_v1 *obj, struct river_seat_v1 *river_seat, uint32_t edges) {
+ struct Window *window = data;
+ window->pointer_resize_requested = river_seat_v1_get_user_data(river_seat);
+ window->pointer_resize_requested_edges = edges;
+}
+
+// Ignored events
+static void window_handle_dimensions_hint(void *data, struct river_window_v1 *obj, int32_t min_width, int32_t min_height, int32_t max_width, int32_t max_height) {}
+static void window_handle_app_id(void *data, struct river_window_v1 *obj, const char *app_id) {}
+static void window_handle_title(void *data, struct river_window_v1 *obj, const char *title) {}
+static void window_handle_parent(void *data, struct river_window_v1 *obj, struct river_window_v1 *parent) {}
+static void window_handle_decoration_hint(void *data, struct river_window_v1 *obj, uint32_t hint) {}
+static void window_handle_show_window_menu_requested(void *data, struct river_window_v1 *obj, int32_t x, int32_t y) {}
+static void window_handle_maximize_requested(void *data, struct river_window_v1 *obj) {}
+static void window_handle_unmaximize_requested(void *data, struct river_window_v1 *obj) {}
+static void window_handle_fullscreen_requested(void *data, struct river_window_v1 *obj, struct river_output_v1 *river_output) {}
+static void window_handle_exit_fullscreen_requested(void *data, struct river_window_v1 *obj) {}
+static void window_handle_minimize_requested(void *data, struct river_window_v1 *obj) {}
+static void window_handle_unreliable_pid(void *data, struct river_window_v1 *obj, int32_t unreliable_pid) {}
+static void window_handle_presentation_hint(void *data, struct river_window_v1 *obj, uint32_t hint) {}
+static void window_handle_identifier(void *data, struct river_window_v1 *obj, const char *indentifier) {}
+
+const struct river_window_v1_listener river_window_listener = {
+ .closed = window_handle_closed,
+ .dimensions_hint = window_handle_dimensions_hint,
+ .dimensions = window_handle_dimensions,
+ .app_id = window_handle_app_id,
+ .title = window_handle_title,
+ .parent = window_handle_parent,
+ .decoration_hint = window_handle_decoration_hint,
+ .pointer_move_requested = window_handle_pointer_move_requested,
+ .pointer_resize_requested = window_handle_pointer_resize_requested,
+ .show_window_menu_requested = window_handle_show_window_menu_requested,
+ .maximize_requested = window_handle_maximize_requested,
+ .unmaximize_requested = window_handle_unmaximize_requested,
+ .fullscreen_requested = window_handle_fullscreen_requested,
+ .exit_fullscreen_requested = window_handle_exit_fullscreen_requested,
+ .minimize_requested = window_handle_minimize_requested,
+ .unreliable_pid = window_handle_unreliable_pid,
+ .presentation_hint = window_handle_presentation_hint,
+ .identifier = window_handle_identifier,
+};
+
+static void window_maybe_destroy(struct Window *window) {
+ if (!window->closed) {
+ return;
+ }
+
+ struct Seat *seat;
+ wl_list_for_each(seat, &wm.seats, link) {
+ if (seat->focused == window) {
+ seat->focused = NULL;
+ }
+ if (seat->op_window == window) {
+ river_seat_v1_op_end(seat->obj);
+ seat->op = SEAT_OP_NONE;
+ seat->op_window = NULL;
+ }
+ }
+
+ river_window_v1_destroy(window->obj);
+ wl_list_remove(&window->link);
+ free(window);
+}
+
+static void window_set_position(struct Window *window, int32_t x, int32_t y) {
+ river_node_v1_set_position(window->node, x, y);
+ window->x = x;
+ window->y = y;
+}
+
+static void seat_pointer_move(struct Seat *seat, struct Window *window);
+static void seat_pointer_resize(struct Seat *seat, struct Window *window, uint32_t edges);
+
+static void window_manage(struct Window *window) {
+ if (window->new) {
+ window->new = false;
+ window_set_position(window, 0, 0);
+ river_window_v1_propose_dimensions(window->obj, 0, 0);
+ }
+ if (window->pointer_move_requested != NULL) {
+ seat_pointer_move(window->pointer_move_requested, window);
+ window->pointer_move_requested = NULL;
+ }
+ if (window->pointer_resize_requested != NULL) {
+ seat_pointer_resize(window->pointer_resize_requested, window,
+ window->pointer_resize_requested_edges);
+ window->pointer_resize_requested = NULL;
+ }
+}
+
+static void xkb_binding_handle_pressed(void *data, struct river_xkb_binding_v1 *obj) {
+ struct XkbBinding *binding = data;
+ binding->seat->pending_action = binding->action;
+}
+
+static void xkb_binding_handle_released(void *data, struct river_xkb_binding_v1 *obj) {}
+
+const struct river_xkb_binding_v1_listener river_xkb_binding_listener = {
+ .pressed = xkb_binding_handle_pressed,
+ .released = xkb_binding_handle_released,
+};
+
+static void xkb_binding_destroy(struct XkbBinding *binding) {
+ river_xkb_binding_v1_destroy(binding->obj);
+ wl_list_remove(&binding->link);
+ free(binding);
+}
+
+static void xkb_binding_create(
+ struct Seat *seat, uint32_t mods, xkb_keysym_t keysym, enum Action action) {
+ struct XkbBinding *binding = calloc(1, sizeof(struct XkbBinding));
+ binding->obj = river_xkb_bindings_v1_get_xkb_binding(xkb_bindings_v1, seat->obj, keysym, mods);
+ binding->seat = seat;
+ binding->action = action;
+
+ river_xkb_binding_v1_add_listener(binding->obj, &river_xkb_binding_listener, binding);
+ river_xkb_binding_v1_enable(binding->obj);
+
+ wl_list_insert(seat->xkb_bindings.prev, &binding->link);
+}
+
+static void pointer_binding_handle_pressed(void *data, struct river_pointer_binding_v1 *obj) {
+ struct PointerBinding *binding = data;
+ binding->seat->pending_action = binding->action;
+}
+
+static void pointer_binding_handle_released(void *data, struct river_pointer_binding_v1 *obj) {}
+
+const struct river_pointer_binding_v1_listener river_pointer_binding_listener = {
+ .pressed = pointer_binding_handle_pressed,
+ .released = pointer_binding_handle_released,
+};
+
+static void pointer_binding_destroy(struct PointerBinding *binding) {
+ river_pointer_binding_v1_destroy(binding->obj);
+ wl_list_remove(&binding->link);
+ free(binding);
+}
+
+static void pointer_binding_create(
+ struct Seat *seat, uint32_t mods, uint32_t button, enum Action action) {
+ struct PointerBinding *binding = calloc(1, sizeof(struct PointerBinding));
+ binding->obj = river_seat_v1_get_pointer_binding(seat->obj, button, mods);
+ binding->seat = seat;
+ binding->action = action;
+
+ river_pointer_binding_v1_add_listener(binding->obj, &river_pointer_binding_listener, binding);
+ river_pointer_binding_v1_enable(binding->obj);
+
+ wl_list_insert(seat->pointer_bindings.prev, &binding->link);
+}
+
+static void seat_handle_removed(void *data, struct river_seat_v1 *obj) {
+ struct Seat *seat = data;
+ seat->removed = true;
+}
+
+static void seat_handle_pointer_enter(
+ void *data, struct river_seat_v1 *obj, struct river_window_v1 *river_window) {
+ struct Seat *seat = data;
+ seat->hovered = river_window_v1_get_user_data(river_window);
+}
+
+static void seat_handle_pointer_leave(void *data, struct river_seat_v1 *obj) {
+ struct Seat *seat = data;
+ seat->hovered = NULL;
+}
+
+static void seat_handle_window_interaction(
+ void *data, struct river_seat_v1 *obj, struct river_window_v1 *river_window) {
+ struct Seat *seat = data;
+ seat->interacted = river_window_v1_get_user_data(river_window);
+}
+
+static void seat_handle_op_delta(void *data, struct river_seat_v1 *obj, int32_t dx, int32_t dy) {
+ struct Seat *seat = data;
+ seat->op_dx = dx;
+ seat->op_dy = dy;
+}
+
+static void seat_handle_op_release(void *data, struct river_seat_v1 *obj) {
+ struct Seat *seat = data;
+ seat->op_release = true;
+}
+
+// Ignored events
+static void seat_handle_wl_seat(void *data, struct river_seat_v1 *obj, uint32_t id) {}
+static void seat_handle_shell_surface_interaction(void *data, struct river_seat_v1 *obj, struct river_shell_surface_v1 *river_shell_surface) {}
+static void seat_handle_pointer_position(void *data, struct river_seat_v1 *obj, int32_t x, int32_t y) {}
+
+const struct river_seat_v1_listener river_seat_listener = {
+ .removed = seat_handle_removed,
+ .wl_seat = seat_handle_wl_seat,
+ .pointer_enter = seat_handle_pointer_enter,
+ .pointer_leave = seat_handle_pointer_leave,
+ .window_interaction = seat_handle_window_interaction,
+ .shell_surface_interaction = seat_handle_shell_surface_interaction,
+ .op_delta = seat_handle_op_delta,
+ .op_release = seat_handle_op_release,
+ .pointer_position = seat_handle_pointer_position,
+};
+
+static void seat_maybe_destroy(struct Seat *seat) {
+ if (!seat->removed) {
+ return;
+ }
+
+ struct XkbBinding *xkb_binding, *xkb_binding_tmp;
+ wl_list_for_each_safe(xkb_binding, xkb_binding_tmp, &seat->xkb_bindings, link) {
+ xkb_binding_destroy(xkb_binding);
+ }
+
+ struct PointerBinding *pointer_binding, *pointer_binding_tmp;
+ wl_list_for_each_safe(pointer_binding, pointer_binding_tmp, &seat->pointer_bindings, link) {
+ pointer_binding_destroy(pointer_binding);
+ }
+
+ river_seat_v1_destroy(seat->obj);
+ wl_list_remove(&seat->link);
+ free(seat);
+}
+
+static void seat_focus(struct Seat *seat, struct Window *window) {
+ // Focus the top window (if any) when there is no explicit target.
+ if (window == NULL && !wl_list_empty(&wm.windows)) {
+ window = wl_container_of(wm.windows.prev, window, link);
+ }
+
+ if (seat->focused == window) {
+ return;
+ }
+
+ if (window != NULL) {
+ river_seat_v1_focus_window(seat->obj, window->obj);
+ river_node_v1_place_top(window->node);
+ wl_list_remove(&window->link);
+ wl_list_insert(wm.windows.prev, &window->link);
+ } else {
+ river_seat_v1_clear_focus(seat->obj);
+ }
+
+ seat->focused = window;
+}
+
+static void seat_pointer_move(struct Seat *seat, struct Window *window) {
+ seat_focus(seat, window);
+ river_seat_v1_op_start_pointer(seat->obj);
+ seat->op = SEAT_OP_MOVE;
+ seat->op_window = window;
+ seat->op_start_x = window->x;
+ seat->op_start_y = window->y;
+ seat->op_dx = 0;
+ seat->op_dy = 0;
+}
+
+static void seat_pointer_resize(struct Seat *seat, struct Window *window, uint32_t edges) {
+ seat_focus(seat, window);
+ river_window_v1_inform_resize_start(window->obj);
+ river_seat_v1_op_start_pointer(seat->obj);
+ seat->op = SEAT_OP_RESIZE;
+ seat->op_window = window;
+ seat->op_edges = edges;
+ seat->op_start_x = window->x;
+ seat->op_start_y = window->y;
+ seat->op_start_width = window->width;
+ seat->op_start_height = window->height;
+ seat->op_dx = 0;
+ seat->op_dy = 0;
+}
+
+static void seat_action(struct Seat *seat, enum Action action) {
+ switch (action) {
+ case ACTION_NONE:
+ break;
+ case ACTION_SPAWN_TERMINAL:
+ if (fork() == 0) {
+ execlp("kitty", "kitty", (char *)0);
+ }
+ break;
+ case ACTION_CLOSE:
+ if (seat->focused != NULL) {
+ river_window_v1_close(seat->focused->obj);
+ }
+ break;
+ case ACTION_FOCUS_NEXT:
+ if (!wl_list_empty(&wm.windows)) {
+ // Focus the bottom window
+ struct Window *window = wl_container_of(wm.windows.next, window, link);
+ seat_focus(seat, window);
+ }
+ break;
+ case ACTION_MOVE:
+ if (seat->op == SEAT_OP_NONE && seat->hovered != NULL) {
+ seat_pointer_move(seat, seat->hovered);
+ }
+ break;
+ case ACTION_RESIZE:
+ if (seat->op == SEAT_OP_NONE && seat->hovered != NULL) {
+ seat_pointer_resize(seat, seat->hovered,
+ RIVER_WINDOW_V1_EDGES_BOTTOM | RIVER_WINDOW_V1_EDGES_RIGHT);
+ }
+ break;
+ case ACTION_EXIT:
+ river_window_manager_v1_exit_session(window_manager_v1);
+ break;
+ }
+}
+
+static void seat_manage(struct Seat *seat) {
+ if (seat->new) {
+ seat->new = false;
+ const uint32_t super = RIVER_SEAT_V1_MODIFIERS_MOD4;
+ xkb_binding_create(seat, super, XKB_KEY_space, ACTION_SPAWN_TERMINAL);
+ xkb_binding_create(seat, super, XKB_KEY_q, ACTION_CLOSE);
+ xkb_binding_create(seat, super, XKB_KEY_n, ACTION_FOCUS_NEXT);
+ xkb_binding_create(seat, super, XKB_KEY_Escape, ACTION_EXIT);
+ pointer_binding_create(seat, super, BTN_LEFT, ACTION_MOVE);
+ pointer_binding_create(seat, super, BTN_RIGHT, ACTION_RESIZE);
+ }
+
+ // If no window was interacted with in the current manage sequence,
+ // intentionally pass NULL to ensure the window on top has focus.
+ // This is necessary to handle new windows for example.
+ seat_focus(seat, seat->interacted);
+ seat->interacted = NULL;
+
+ seat_action(seat, seat->pending_action);
+ seat->pending_action = ACTION_NONE;
+
+ switch (seat->op) {
+ case SEAT_OP_NONE:
+ break;
+ case SEAT_OP_MOVE:
+ if (seat->op_release) {
+ river_seat_v1_op_end(seat->obj);
+ seat->op = SEAT_OP_NONE;
+ seat->op_window = NULL;
+ break;
+ }
+ break;
+ case SEAT_OP_RESIZE:
+ if (seat->op_release) {
+ river_window_v1_inform_resize_end(seat->op_window->obj);
+ river_seat_v1_op_end(seat->obj);
+ seat->op = SEAT_OP_NONE;
+ seat->op_window = NULL;
+ break;
+ }
+ int32_t width = seat->op_start_width;
+ int32_t height = seat->op_start_height;
+ if ((seat->op_edges & RIVER_WINDOW_V1_EDGES_LEFT) != 0) {
+ width -= seat->op_dx;
+ }
+ if ((seat->op_edges & RIVER_WINDOW_V1_EDGES_RIGHT) != 0) {
+ width += seat->op_dx;
+ }
+ if ((seat->op_edges & RIVER_WINDOW_V1_EDGES_TOP) != 0) {
+ height -= seat->op_dy;
+ }
+ if ((seat->op_edges & RIVER_WINDOW_V1_EDGES_BOTTOM) != 0) {
+ height += seat->op_dy;
+ }
+ river_window_v1_propose_dimensions(
+ seat->op_window->obj, width > 1 ? width : 1, height > 1 ? height : 1);
+ break;
+ }
+ seat->op_release = false;
+}
+
+static void seat_render(struct Seat *seat) {
+ switch (seat->op) {
+ case SEAT_OP_NONE:
+ break;
+ case SEAT_OP_MOVE:
+ window_set_position(
+ seat->op_window, seat->op_start_x + seat->op_dx, seat->op_start_y + seat->op_dy);
+ break;
+ case SEAT_OP_RESIZE:;
+ int32_t x = seat->op_start_x;
+ int32_t y = seat->op_start_y;
+ if ((seat->op_edges & RIVER_WINDOW_V1_EDGES_LEFT) != 0) {
+ x += seat->op_start_width - seat->op_window->width;
+ }
+ if ((seat->op_edges & RIVER_WINDOW_V1_EDGES_TOP) != 0) {
+ y += seat->op_start_height - seat->op_window->height;
+ }
+ window_set_position(seat->op_window, x, y);
+ break;
+ }
+}
+
+static void wm_handle_unavailable(void *data, struct river_window_manager_v1 *obj) {
+ fprintf(stderr, "error: another window manager is already running\n");
+ exit(1);
+}
+
+static void wm_handle_finished(void *data, struct river_window_manager_v1 *obj) {
+ exit(0);
+}
+
+static void wm_handle_manage_start(void *data, struct river_window_manager_v1 *obj) {
+ // Destroy closed windows and removed outputs/seats
+ struct Output *output, *output_tmp;
+ wl_list_for_each_safe(output, output_tmp, &wm.outputs, link) {
+ output_maybe_destroy(output);
+ }
+ struct Window *window, *window_tmp;
+ wl_list_for_each_safe(window, window_tmp, &wm.windows, link) {
+ window_maybe_destroy(window);
+ }
+ struct Seat *seat, *seat_tmp;
+ wl_list_for_each_safe(seat, seat_tmp, &wm.seats, link) {
+ seat_maybe_destroy(seat);
+ }
+
+ // Carry out window management policy
+ wl_list_for_each(window, &wm.windows, link) {
+ window_manage(window);
+ }
+ wl_list_for_each(seat, &wm.seats, link) {
+ seat_manage(seat);
+ }
+
+ river_window_manager_v1_manage_finish(window_manager_v1);
+}
+
+static void wm_handle_render_start(void *data, struct river_window_manager_v1 *obj) {
+ struct Seat *seat;
+ wl_list_for_each(seat, &wm.seats, link) {
+ seat_render(seat);
+ }
+
+ river_window_manager_v1_render_finish(window_manager_v1);
+}
+
+static void wm_handle_window(
+ void *data, struct river_window_manager_v1 *obj, struct river_window_v1 *river_window) {
+ struct Window *window = calloc(1, sizeof(struct Window));
+ window->obj = river_window;
+ window->node = river_window_v1_get_node(window->obj);
+ window->new = true;
+
+ river_window_v1_add_listener(window->obj, &river_window_listener, window);
+
+ wl_list_insert(wm.windows.prev, &window->link);
+}
+
+static void wm_handle_output(
+ void *data, struct river_window_manager_v1 *obj, struct river_output_v1 *river_output) {
+ struct Output *output = calloc(1, sizeof(struct Output));
+ output->obj = river_output;
+
+ river_output_v1_add_listener(output->obj, &river_output_listener, output);
+
+ wl_list_insert(wm.outputs.prev, &output->link);
+}
+
+static void wm_handle_seat(
+ void *data, struct river_window_manager_v1 *obj, struct river_seat_v1 *river_seat) {
+ struct Seat *seat = calloc(1, sizeof(struct Seat));
+ seat->obj = river_seat;
+ seat->new = true;
+ wl_list_init(&seat->xkb_bindings);
+ wl_list_init(&seat->pointer_bindings);
+
+ river_seat_v1_add_listener(seat->obj, &river_seat_listener, seat);
+
+ wl_list_insert(wm.seats.prev, &seat->link);
+}
+
+// Ignored events
+static void wm_handle_session_locked(void *data, struct river_window_manager_v1 *obj) {}
+static void wm_handle_session_unlocked(void *data, struct river_window_manager_v1 *obj) {}
+
+static const struct river_window_manager_v1_listener wm_listener = {
+ .unavailable = wm_handle_unavailable,
+ .finished = wm_handle_finished,
+ .manage_start = wm_handle_manage_start,
+ .render_start = wm_handle_render_start,
+ .session_locked = wm_handle_session_locked,
+ .session_unlocked = wm_handle_session_unlocked,
+ .window = wm_handle_window,
+ .output = wm_handle_output,
+ .seat = wm_handle_seat,
+};
+
+static void wm_init(void) {
+ wl_list_init(&wm.outputs);
+ wl_list_init(&wm.windows);
+ wl_list_init(&wm.seats);
+}
+
+static void handle_global(void *data, struct wl_registry *registry, uint32_t name,
+ const char *interface, uint32_t version) {
+ if (strcmp(interface, river_window_manager_v1_interface.name) == 0) {
+ if (version >= 4) {
+ window_manager_v1 = wl_registry_bind(registry, name, &river_window_manager_v1_interface, 4);
+ }
+ } else if (strcmp(interface, river_xkb_bindings_v1_interface.name) == 0) {
+ xkb_bindings_v1 = wl_registry_bind(registry, name, &river_xkb_bindings_v1_interface, 1);
+ }
+}
+
+static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) {}
+
+static const struct wl_registry_listener registry_listener = {
+ .global = handle_global,
+ .global_remove = handle_global_remove,
+};
+
+int main(void) {
+ struct wl_display *display = wl_display_connect(NULL);
+ if (display == NULL) {
+ fprintf(stderr, "failed to connect to Wayland server\n");
+ return 1;
+ }
+
+ // Avoid passing WAYLAND_DEBUG on to our children.
+ // It only matters if it's set when the display is created.
+ unsetenv("WAYLAND_DEBUG");
+
+ // Ensure children are automatically reaped.
+ signal(SIGCHLD, SIG_IGN);
+
+ struct wl_registry *registry = wl_display_get_registry(display);
+ wl_registry_add_listener(registry, ®istry_listener, NULL);
+ if (wl_display_roundtrip(display) < 0) {
+ fprintf(stderr, "roundtrip failed\n");
+ return 1;
+ }
+
+ if (window_manager_v1 == NULL || xkb_bindings_v1 == NULL) {
+ fprintf(stderr,
+ "river_window_manager_v1 or river_xkb_bindings_v1 "
+ "not supported by the Wayland server\n");
+ return 1;
+ }
+
+ wm_init();
+
+ river_window_manager_v1_add_listener(window_manager_v1, &wm_listener, NULL);
+
+ while (true) {
+ if (wl_display_dispatch(display) < 0) {
+ fprintf(stderr, "dispatch failed\n");
+ return 1;
+ }
+ }
+
+ return 0;
+}