commit e2a1a91f361fcce9616d73210f877fa65845d821
parent 84b6cd7bc5158a00badaa0c79694d1b6b3d7a2a5
Author: Andrea Feletto <andrea@andreafeletto.com>
Date: Wed, 19 Jan 2022 16:29:21 +0100
black rectangle correctly rendered
Diffstat:
11 files changed, 951 insertions(+), 0 deletions(-)
diff --git a/.gitmodules b/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "deps/zig-wayland"]
+ path = deps/zig-wayland
+ url = https://github.com/ifreund/zig-wayland
+[submodule "deps/zig-pixman"]
+ path = deps/zig-pixman
+ url = https://github.com/ifreund/zig-pixman
diff --git a/build.zig b/build.zig
@@ -1,12 +1,40 @@
const std = @import("std");
+const Pkg = std.build.Pkg;
+
+const ScanProtocolsStep = @import("deps/zig-wayland/build.zig").ScanProtocolsStep;
pub fn build(b: *std.build.Builder) void {
const target = b.standardTargetOptions(.{});
const mode = b.standardReleaseOptions();
+ const scanner = ScanProtocolsStep.create(b);
+ scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml");
+ scanner.addProtocolPath("protocol/wlr-layer-shell-unstable-v1.xml");
+ scanner.addProtocolPath("protocol/river-status-unstable-v1.xml");
+
+ const wayland = Pkg{
+ .name = "wayland",
+ .path = .{ .generated = &scanner.result },
+ };
+ const pixman = Pkg{
+ .name = "pixman",
+ .path = .{ .path = "deps/zig-pixman/pixman.zig" },
+ };
+
const exe = b.addExecutable("levee", "src/main.zig");
exe.setTarget(target);
exe.setBuildMode(mode);
+
+ exe.linkLibC();
+
+ exe.addPackage(wayland);
+ exe.linkSystemLibrary("wayland-client");
+ exe.step.dependOn(&scanner.step);
+ scanner.addCSource(exe);
+
+ exe.addPackage(pixman);
+ exe.linkSystemLibrary("pixman-1");
+
exe.install();
const run_cmd = exe.run();
diff --git a/deps/zig-pixman b/deps/zig-pixman
@@ -0,0 +1 @@
+Subproject commit d381567de9b6e40dd7f4c6e0b5740f94ebd8c9d7
diff --git a/deps/zig-wayland b/deps/zig-wayland
@@ -0,0 +1 @@
+Subproject commit d4bf1dd7aef84731e9d7274a8146322956a8b4ab
diff --git a/protocol/river-status-unstable-v1.xml b/protocol/river-status-unstable-v1.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="river_status_unstable_v1">
+ <copyright>
+ Copyright 2020 The River Developers
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ </copyright>
+
+ <interface name="zriver_status_manager_v1" version="1">
+ <description summary="manage river status objects">
+ A global factory for objects that receive status information specific
+ to river. It could be used to implement, for example, a status bar.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the river_status_manager object">
+ This request indicates that the client will not use the
+ river_status_manager object any more. Objects that have been created
+ through this instance are not affected.
+ </description>
+ </request>
+
+ <request name="get_river_output_status">
+ <description summary="create an output status object">
+ This creates a new river_output_status object for the given wl_output.
+ </description>
+ <arg name="id" type="new_id" interface="zriver_output_status_v1"/>
+ <arg name="output" type="object" interface="wl_output"/>
+ </request>
+
+ <request name="get_river_seat_status">
+ <description summary="create a seat status object">
+ This creates a new river_seat_status object for the given wl_seat.
+ </description>
+ <arg name="id" type="new_id" interface="zriver_seat_status_v1"/>
+ <arg name="seat" type="object" interface="wl_seat"/>
+ </request>
+ </interface>
+
+ <interface name="zriver_output_status_v1" version="1">
+ <description summary="track output tags and focus">
+ This interface allows clients to receive information about the current
+ windowing state of an output.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the river_output_status object">
+ This request indicates that the client will not use the
+ river_output_status object any more.
+ </description>
+ </request>
+
+ <event name="focused_tags">
+ <description summary="focused tags of the output">
+ Sent once binding the interface and again whenever the tag focus of
+ the output changes.
+ </description>
+ <arg name="tags" type="uint" summary="32-bit bitfield"/>
+ </event>
+
+ <event name="view_tags">
+ <description summary="tag state of an output's views">
+ Sent once on binding the interface and again whenever the tag state
+ of the output changes.
+ </description>
+ <arg name="tags" type="array" summary="array of 32-bit bitfields"/>
+ </event>
+ </interface>
+
+ <interface name="zriver_seat_status_v1" version="1">
+ <description summary="track seat focus">
+ This interface allows clients to receive information about the current
+ focus of a seat. Note that (un)focused_output events will only be sent
+ if the client has bound the relevant wl_output globals.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the river_seat_status object">
+ This request indicates that the client will not use the
+ river_seat_status object any more.
+ </description>
+ </request>
+
+ <event name="focused_output">
+ <description summary="the seat focused an output">
+ Sent on binding the interface and again whenever an output gains focus.
+ </description>
+ <arg name="output" type="object" interface="wl_output"/>
+ </event>
+
+ <event name="unfocused_output">
+ <description summary="the seat unfocused an output">
+ Sent whenever an output loses focus.
+ </description>
+ <arg name="output" type="object" interface="wl_output"/>
+ </event>
+
+ <event name="focused_view">
+ <description summary="information on the focused view">
+ Sent once on binding the interface and again whenever the focused
+ view or a property thereof changes. The title may be an empty string
+ if no view is focused or the focused view did not set a title.
+ </description>
+ <arg name="title" type="string" summary="title of the focused view"/>
+ </event>
+ </interface>
+</protocol>
diff --git a/protocol/wlr-layer-shell-unstable-v1.xml b/protocol/wlr-layer-shell-unstable-v1.xml
@@ -0,0 +1,390 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wlr_layer_shell_unstable_v1">
+ <copyright>
+ Copyright © 2017 Drew DeVault
+
+ Permission to use, copy, modify, distribute, and sell this
+ software and its documentation for any purpose is hereby granted
+ without fee, provided that the above copyright notice appear in
+ all copies and that both that copyright notice and this permission
+ notice appear in supporting documentation, and that the name of
+ the copyright holders not be used in advertising or publicity
+ pertaining to distribution of the software without specific,
+ written prior permission. The copyright holders make no
+ representations about the suitability of this software for any
+ purpose. It is provided "as is" without express or implied
+ warranty.
+
+ THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ THIS SOFTWARE.
+ </copyright>
+
+ <interface name="zwlr_layer_shell_v1" version="4">
+ <description summary="create surfaces that are layers of the desktop">
+ Clients can use this interface to assign the surface_layer role to
+ wl_surfaces. Such surfaces are assigned to a "layer" of the output and
+ rendered with a defined z-depth respective to each other. They may also be
+ anchored to the edges and corners of a screen and specify input handling
+ semantics. This interface should be suitable for the implementation of
+ many desktop shell components, and a broad number of other applications
+ that interact with the desktop.
+ </description>
+
+ <request name="get_layer_surface">
+ <description summary="create a layer_surface from a surface">
+ Create a layer surface for an existing surface. This assigns the role of
+ layer_surface, or raises a protocol error if another role is already
+ assigned.
+
+ Creating a layer surface from a wl_surface which has a buffer attached
+ or committed is a client error, and any attempts by a client to attach
+ or manipulate a buffer prior to the first layer_surface.configure call
+ must also be treated as errors.
+
+ After creating a layer_surface object and setting it up, the client
+ must perform an initial commit without any buffer attached.
+ The compositor will reply with a layer_surface.configure event.
+ The client must acknowledge it and is then allowed to attach a buffer
+ to map the surface.
+
+ You may pass NULL for output to allow the compositor to decide which
+ output to use. Generally this will be the one that the user most
+ recently interacted with.
+
+ Clients can specify a namespace that defines the purpose of the layer
+ surface.
+ </description>
+ <arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/>
+ <arg name="surface" type="object" interface="wl_surface"/>
+ <arg name="output" type="object" interface="wl_output" allow-null="true"/>
+ <arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/>
+ <arg name="namespace" type="string" summary="namespace for the layer surface"/>
+ </request>
+
+ <enum name="error">
+ <entry name="role" value="0" summary="wl_surface has another role"/>
+ <entry name="invalid_layer" value="1" summary="layer value is invalid"/>
+ <entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/>
+ </enum>
+
+ <enum name="layer">
+ <description summary="available layers for surfaces">
+ These values indicate which layers a surface can be rendered in. They
+ are ordered by z depth, bottom-most first. Traditional shell surfaces
+ will typically be rendered between the bottom and top layers.
+ Fullscreen shell surfaces are typically rendered at the top layer.
+ Multiple surfaces can share a single layer, and ordering within a
+ single layer is undefined.
+ </description>
+
+ <entry name="background" value="0"/>
+ <entry name="bottom" value="1"/>
+ <entry name="top" value="2"/>
+ <entry name="overlay" value="3"/>
+ </enum>
+
+ <!-- Version 3 additions -->
+
+ <request name="destroy" type="destructor" since="3">
+ <description summary="destroy the layer_shell object">
+ This request indicates that the client will not use the layer_shell
+ object any more. Objects that have been created through this instance
+ are not affected.
+ </description>
+ </request>
+ </interface>
+
+ <interface name="zwlr_layer_surface_v1" version="4">
+ <description summary="layer metadata interface">
+ An interface that may be implemented by a wl_surface, for surfaces that
+ are designed to be rendered as a layer of a stacked desktop-like
+ environment.
+
+ Layer surface state (layer, size, anchor, exclusive zone,
+ margin, interactivity) is double-buffered, and will be applied at the
+ time wl_surface.commit of the corresponding wl_surface is called.
+
+ Attaching a null buffer to a layer surface unmaps it.
+
+ Unmapping a layer_surface means that the surface cannot be shown by the
+ compositor until it is explicitly mapped again. The layer_surface
+ returns to the state it had right after layer_shell.get_layer_surface.
+ The client can re-map the surface by performing a commit without any
+ buffer attached, waiting for a configure event and handling it as usual.
+ </description>
+
+ <request name="set_size">
+ <description summary="sets the size of the surface">
+ Sets the size of the surface in surface-local coordinates. The
+ compositor will display the surface centered with respect to its
+ anchors.
+
+ If you pass 0 for either value, the compositor will assign it and
+ inform you of the assignment in the configure event. You must set your
+ anchor to opposite edges in the dimensions you omit; not doing so is a
+ protocol error. Both values are 0 by default.
+
+ Size is double-buffered, see wl_surface.commit.
+ </description>
+ <arg name="width" type="uint"/>
+ <arg name="height" type="uint"/>
+ </request>
+
+ <request name="set_anchor">
+ <description summary="configures the anchor point of the surface">
+ Requests that the compositor anchor the surface to the specified edges
+ and corners. If two orthogonal edges are specified (e.g. 'top' and
+ 'left'), then the anchor point will be the intersection of the edges
+ (e.g. the top left corner of the output); otherwise the anchor point
+ will be centered on that edge, or in the center if none is specified.
+
+ Anchor is double-buffered, see wl_surface.commit.
+ </description>
+ <arg name="anchor" type="uint" enum="anchor"/>
+ </request>
+
+ <request name="set_exclusive_zone">
+ <description summary="configures the exclusive geometry of this surface">
+ Requests that the compositor avoids occluding an area with other
+ surfaces. The compositor's use of this information is
+ implementation-dependent - do not assume that this region will not
+ actually be occluded.
+
+ A positive value is only meaningful if the surface is anchored to one
+ edge or an edge and both perpendicular edges. If the surface is not
+ anchored, anchored to only two perpendicular edges (a corner), anchored
+ to only two parallel edges or anchored to all edges, a positive value
+ will be treated the same as zero.
+
+ A positive zone is the distance from the edge in surface-local
+ coordinates to consider exclusive.
+
+ Surfaces that do not wish to have an exclusive zone may instead specify
+ how they should interact with surfaces that do. If set to zero, the
+ surface indicates that it would like to be moved to avoid occluding
+ surfaces with a positive exclusive zone. If set to -1, the surface
+ indicates that it would not like to be moved to accommodate for other
+ surfaces, and the compositor should extend it all the way to the edges
+ it is anchored to.
+
+ For example, a panel might set its exclusive zone to 10, so that
+ maximized shell surfaces are not shown on top of it. A notification
+ might set its exclusive zone to 0, so that it is moved to avoid
+ occluding the panel, but shell surfaces are shown underneath it. A
+ wallpaper or lock screen might set their exclusive zone to -1, so that
+ they stretch below or over the panel.
+
+ The default value is 0.
+
+ Exclusive zone is double-buffered, see wl_surface.commit.
+ </description>
+ <arg name="zone" type="int"/>
+ </request>
+
+ <request name="set_margin">
+ <description summary="sets a margin from the anchor point">
+ Requests that the surface be placed some distance away from the anchor
+ point on the output, in surface-local coordinates. Setting this value
+ for edges you are not anchored to has no effect.
+
+ The exclusive zone includes the margin.
+
+ Margin is double-buffered, see wl_surface.commit.
+ </description>
+ <arg name="top" type="int"/>
+ <arg name="right" type="int"/>
+ <arg name="bottom" type="int"/>
+ <arg name="left" type="int"/>
+ </request>
+
+ <enum name="keyboard_interactivity">
+ <description summary="types of keyboard interaction possible for a layer shell surface">
+ Types of keyboard interaction possible for layer shell surfaces. The
+ rationale for this is twofold: (1) some applications are not interested
+ in keyboard events and not allowing them to be focused can improve the
+ desktop experience; (2) some applications will want to take exclusive
+ keyboard focus.
+ </description>
+
+ <entry name="none" value="0">
+ <description summary="no keyboard focus is possible">
+ This value indicates that this surface is not interested in keyboard
+ events and the compositor should never assign it the keyboard focus.
+
+ This is the default value, set for newly created layer shell surfaces.
+
+ This is useful for e.g. desktop widgets that display information or
+ only have interaction with non-keyboard input devices.
+ </description>
+ </entry>
+ <entry name="exclusive" value="1">
+ <description summary="request exclusive keyboard focus">
+ Request exclusive keyboard focus if this surface is above the shell surface layer.
+
+ For the top and overlay layers, the seat will always give
+ exclusive keyboard focus to the top-most layer which has keyboard
+ interactivity set to exclusive. If this layer contains multiple
+ surfaces with keyboard interactivity set to exclusive, the compositor
+ determines the one receiving keyboard events in an implementation-
+ defined manner. In this case, no guarantee is made when this surface
+ will receive keyboard focus (if ever).
+
+ For the bottom and background layers, the compositor is allowed to use
+ normal focus semantics.
+
+ This setting is mainly intended for applications that need to ensure
+ they receive all keyboard events, such as a lock screen or a password
+ prompt.
+ </description>
+ </entry>
+ <entry name="on_demand" value="2" since="4">
+ <description summary="request regular keyboard focus semantics">
+ This requests the compositor to allow this surface to be focused and
+ unfocused by the user in an implementation-defined manner. The user
+ should be able to unfocus this surface even regardless of the layer
+ it is on.
+
+ Typically, the compositor will want to use its normal mechanism to
+ manage keyboard focus between layer shell surfaces with this setting
+ and regular toplevels on the desktop layer (e.g. click to focus).
+ Nevertheless, it is possible for a compositor to require a special
+ interaction to focus or unfocus layer shell surfaces (e.g. requiring
+ a click even if focus follows the mouse normally, or providing a
+ keybinding to switch focus between layers).
+
+ This setting is mainly intended for desktop shell components (e.g.
+ panels) that allow keyboard interaction. Using this option can allow
+ implementing a desktop shell that can be fully usable without the
+ mouse.
+ </description>
+ </entry>
+ </enum>
+
+ <request name="set_keyboard_interactivity">
+ <description summary="requests keyboard events">
+ Set how keyboard events are delivered to this surface. By default,
+ layer shell surfaces do not receive keyboard events; this request can
+ be used to change this.
+
+ This setting is inherited by child surfaces set by the get_popup
+ request.
+
+ Layer surfaces receive pointer, touch, and tablet events normally. If
+ you do not want to receive them, set the input region on your surface
+ to an empty region.
+
+ Keyboard interactivity is double-buffered, see wl_surface.commit.
+ </description>
+ <arg name="keyboard_interactivity" type="uint" enum="keyboard_interactivity"/>
+ </request>
+
+ <request name="get_popup">
+ <description summary="assign this layer_surface as an xdg_popup parent">
+ This assigns an xdg_popup's parent to this layer_surface. This popup
+ should have been created via xdg_surface::get_popup with the parent set
+ to NULL, and this request must be invoked before committing the popup's
+ initial state.
+
+ See the documentation of xdg_popup for more details about what an
+ xdg_popup is and how it is used.
+ </description>
+ <arg name="popup" type="object" interface="xdg_popup"/>
+ </request>
+
+ <request name="ack_configure">
+ <description summary="ack a configure event">
+ When a configure event is received, if a client commits the
+ surface in response to the configure event, then the client
+ must make an ack_configure request sometime before the commit
+ request, passing along the serial of the configure event.
+
+ If the client receives multiple configure events before it
+ can respond to one, it only has to ack the last configure event.
+
+ A client is not required to commit immediately after sending
+ an ack_configure request - it may even ack_configure several times
+ before its next surface commit.
+
+ A client may send multiple ack_configure requests before committing, but
+ only the last request sent before a commit indicates which configure
+ event the client really is responding to.
+ </description>
+ <arg name="serial" type="uint" summary="the serial from the configure event"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the layer_surface">
+ This request destroys the layer surface.
+ </description>
+ </request>
+
+ <event name="configure">
+ <description summary="suggest a surface change">
+ The configure event asks the client to resize its surface.
+
+ Clients should arrange their surface for the new states, and then send
+ an ack_configure request with the serial sent in this configure event at
+ some point before committing the new surface.
+
+ The client is free to dismiss all but the last configure event it
+ received.
+
+ The width and height arguments specify the size of the window in
+ surface-local coordinates.
+
+ The size is a hint, in the sense that the client is free to ignore it if
+ it doesn't resize, pick a smaller size (to satisfy aspect ratio or
+ resize in steps of NxM pixels). If the client picks a smaller size and
+ is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the
+ surface will be centered on this axis.
+
+ If the width or height arguments are zero, it means the client should
+ decide its own window dimension.
+ </description>
+ <arg name="serial" type="uint"/>
+ <arg name="width" type="uint"/>
+ <arg name="height" type="uint"/>
+ </event>
+
+ <event name="closed">
+ <description summary="surface should be closed">
+ The closed event is sent by the compositor when the surface will no
+ longer be shown. The output may have been destroyed or the user may
+ have asked for it to be removed. Further changes to the surface will be
+ ignored. The client should destroy the resource after receiving this
+ event, and create a new surface if they so choose.
+ </description>
+ </event>
+
+ <enum name="error">
+ <entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/>
+ <entry name="invalid_size" value="1" summary="size is invalid"/>
+ <entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/>
+ <entry name="invalid_keyboard_interactivity" value="3" summary="keyboard interactivity is invalid"/>
+ </enum>
+
+ <enum name="anchor" bitfield="true">
+ <entry name="top" value="1" summary="the top edge of the anchor rectangle"/>
+ <entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/>
+ <entry name="left" value="4" summary="the left edge of the anchor rectangle"/>
+ <entry name="right" value="8" summary="the right edge of the anchor rectangle"/>
+ </enum>
+
+ <!-- Version 2 additions -->
+
+ <request name="set_layer" since="2">
+ <description summary="change the layer of the surface">
+ Change the layer that the surface is rendered on.
+
+ Layer is double-buffered, see wl_surface.commit.
+ </description>
+ <arg name="layer" type="uint" enum="zwlr_layer_shell_v1.layer" summary="layer to move this surface to"/>
+ </request>
+ </interface>
+</protocol>
diff --git a/src/event.zig b/src/event.zig
@@ -0,0 +1,84 @@
+const std = @import("std");
+const mem = std.mem;
+const os = std.os;
+const ArrayList = std.ArrayList;
+
+const wl = @import("wayland").client.wl;
+
+const State = @import("main.zig").State;
+
+pub const Loop = struct {
+ state: *State,
+
+ fds: [2]os.pollfd,
+ timers: ArrayList(*Timer),
+
+ pub fn init(state: *State) !Loop {
+ const tfd = os.linux.timerfd_create(
+ os.CLOCK.MONOTONIC,
+ os.linux.TFD.CLOEXEC,
+ );
+
+ return Loop{
+ .state = state,
+ .fds = .{
+ .{
+ .fd = state.wayland.display.getFd(),
+ .events = os.POLL.IN,
+ .revents = 0,
+ },
+ .{
+ .fd = @intCast(os.fd_t, tfd),
+ .events = os.POLL.IN,
+ .revents = 0,
+ },
+ },
+ .timers = ArrayList(*Timer).init(state.allocator),
+ };
+ }
+
+ pub fn run(self: *Loop) !void {
+ const display = self.state.wayland.display;
+
+ while (true) loop: {
+ while (true) {
+ const ret = try display.dispatchPending();
+ _ = try display.flush();
+ if (ret <= 0) break;
+ }
+ _ = try os.poll(&self.fds, -1);
+
+ for (self.fds) |fd| {
+ if (fd.revents & os.POLL.HUP != 0) {
+ break :loop;
+ }
+ if (fd.revents & os.POLL.ERR != 0) {
+ break :loop;
+ }
+ }
+
+ // wayland
+ if (self.fds[0].revents & os.POLL.IN != 0) {
+ _ = try display.dispatch();
+ }
+ if (self.fds[0].revents & os.POLL.OUT != 0) {
+ _ = try display.flush();
+ }
+
+ // timer
+ if (self.fds[1].revents & os.POLL.IN != 0) {
+ for (self.timers.items) |timer, i| {
+ const callback = timer.callback;
+ const payload = timer.payload;
+ _ = self.timers.swapRemove(i);
+ callback(payload);
+ }
+ }
+ }
+ }
+};
+
+pub const Timer = struct {
+ callback: fn (*anyopaque) void,
+ payload: *anyopaque,
+};
diff --git a/src/main.zig b/src/main.zig
@@ -1,4 +1,27 @@
const std = @import("std");
+const Loop = @import("event.zig").Loop;
+const Wayland = @import("wayland.zig").Wayland;
+
+pub const State = struct {
+ allocator: std.mem.Allocator,
+ wayland: Wayland,
+ loop: Loop,
+};
+
pub fn main() anyerror!void {
+ var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
+ defer arena.deinit();
+
+ std.log.info("initialization", .{});
+ var state: State = undefined;
+ state.allocator = arena.allocator();
+ state.wayland = try Wayland.init(&state);
+ state.loop = try Loop.init(&state);
+
+ std.log.info("wayland globals registration", .{});
+ try state.wayland.registerGlobals();
+
+ std.log.info("event loop start", .{});
+ try state.loop.run();
}
diff --git a/src/render.zig b/src/render.zig
@@ -0,0 +1,31 @@
+const pixman = @import("pixman");
+
+const Buffer = @import("shm.zig").Buffer;
+const Surface = @import("wayland.zig").Surface;
+
+pub fn renderBackground(surface: *Surface) !void {
+ const wlSurface = surface.backgroundSurface;
+
+ const buffer = try Buffer.nextBuffer(
+ &surface.backgroundBuffers,
+ surface.output.state.wayland.shm,
+ surface.width,
+ surface.height,
+ );
+ buffer.busy = true;
+
+ const area = [_]pixman.Rectangle16{
+ .{ .x = 0, .y = 0, .width = surface.width, .height = surface.height },
+ };
+ const color = pixman.Color{
+ .red = 0,
+ .green = 0,
+ .blue = 0,
+ .alpha = 0xffff,
+ };
+ _ = pixman.Image.fillRectangles(.src, buffer.pix.?, &color, 1, &area);
+
+ wlSurface.setBufferScale(surface.output.scale);
+ wlSurface.damageBuffer(0, 0, surface.width, surface.height);
+ wlSurface.attach(buffer.buffer, 0, 0);
+}
diff --git a/src/shm.zig b/src/shm.zig
@@ -0,0 +1,69 @@
+const std = @import("std");
+const mem = std.mem;
+const os = std.os;
+
+const wayland = @import("wayland").client;
+const wl = wayland.wl;
+
+const pixman = @import("pixman");
+
+pub const Buffer = struct {
+ data: ?[]align(4096) u8,
+ buffer: ?*wl.Buffer,
+ pix: ?*pixman.Image,
+
+ busy: bool,
+ width: u31,
+ height: u31,
+ size: u31,
+
+ pub fn init(self: *Buffer, shm: *wl.Shm, width: u31, height: u31) !void {
+ self.busy = true;
+ self.width = width;
+ self.height = height;
+
+ const fd = try os.memfd_create("levee-wayland-shm-buffer-pool", 0);
+ defer os.close(fd);
+
+ const stride = width * 4;
+ self.size = stride * height;
+ try os.ftruncate(fd, self.size);
+
+ self.data = try os.mmap(null, self.size, os.PROT.READ | os.PROT.WRITE, os.MAP.SHARED, fd, 0);
+ errdefer os.munmap(self.data.?);
+
+ const pool = try shm.createPool(fd, self.size);
+ defer pool.destroy();
+
+ self.buffer = try pool.createBuffer(0, width, height, stride, .argb8888);
+ errdefer self.buffer.?.destroy();
+ self.buffer.?.setListener(*Buffer, listener, self);
+
+ self.pix = pixman.Image.createBitsNoClear(.a8r8g8b8, width, height, @ptrCast([*c]u32, self.data.?), stride);
+ }
+
+ pub fn deinit(self: *Buffer) void {
+ if (self.pix) |pix| _ = pix.unref();
+ if (self.buffer) |buf| buf.destroy();
+ if (self.data) |data| os.munmap(data);
+ }
+
+ fn listener(_: *wl.Buffer, event: wl.Buffer.Event, buffer: *Buffer) void {
+ switch (event) {
+ .release => buffer.busy = false,
+ }
+ }
+
+ pub fn nextBuffer(pool: *[2]Buffer, shm: *wl.Shm, width: u16, height: u16) !*Buffer {
+ if (pool[0].busy and pool[1].busy) {
+ return error.NoAvailableBuffers;
+ }
+ const buffer = if (!pool[0].busy) &pool[0] else &pool[1];
+
+ if (buffer.width != width or buffer.height != height) {
+ buffer.deinit();
+ try buffer.init(shm, width, height);
+ }
+ return buffer;
+ }
+};
diff --git a/src/wayland.zig b/src/wayland.zig
@@ -0,0 +1,201 @@
+const std = @import("std");
+const mem = std.mem;
+const strcmp = std.cstr.cmp;
+const ArrayList = std.ArrayList;
+
+const wl = @import("wayland").client.wl;
+const zwlr = @import("wayland").client.zwlr;
+const zriver = @import("wayland").client.zriver;
+
+const Buffer = @import("shm.zig").Buffer;
+const render = @import("render.zig");
+const State = @import("main.zig").State;
+
+pub const Wayland = struct {
+ state: *State,
+ display: *wl.Display,
+ registry: *wl.Registry,
+
+ outputs: ArrayList(*Output),
+
+ compositor: *wl.Compositor,
+ shm: *wl.Shm,
+ layerShell: *zwlr.LayerShellV1,
+ statusManager: *zriver.StatusManagerV1,
+
+ globalsRegistered: [4]bool,
+
+ pub fn init(state: *State) !Wayland {
+ const display = try wl.Display.connect(null);
+
+ return Wayland{
+ .state = state,
+ .display = display,
+ .registry = try display.getRegistry(),
+ .outputs = ArrayList(*Output).init(state.allocator),
+ .compositor = undefined,
+ .shm = undefined,
+ .layerShell = undefined,
+ .statusManager = undefined,
+ .globalsRegistered = mem.zeroes([4]bool),
+ };
+ }
+
+ pub fn registerGlobals(self: *Wayland) !void {
+ self.registry.setListener(*State, registryListener, self.state);
+ _ = try self.display.roundtrip();
+
+ for (self.globalsRegistered) |globalRegistered| {
+ if (!globalRegistered) return error.UnsupportedGlobal;
+ }
+ }
+
+ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, state: *State) void {
+ const wayland = &state.wayland;
+
+ switch (event) {
+ .global => |global| {
+ const interface = global.interface;
+ const name = global.name;
+
+ if (strcmp(interface, wl.Compositor.getInterface().name) == 0) {
+ wayland.compositor = registry.bind(name, wl.Compositor, 4) catch return;
+ wayland.globalsRegistered[0] = true;
+ } else if (strcmp(interface, wl.Shm.getInterface().name) == 0) {
+ wayland.shm = registry.bind(name, wl.Shm, 1) catch return;
+ wayland.globalsRegistered[1] = true;
+ } else if (strcmp(interface, zwlr.LayerShellV1.getInterface().name) == 0) {
+ wayland.layerShell = registry.bind(name, zwlr.LayerShellV1, 1) catch return;
+ wayland.globalsRegistered[2] = true;
+ } else if (strcmp(interface, zriver.StatusManagerV1.getInterface().name) == 0) {
+ wayland.statusManager = registry.bind(name, zriver.StatusManagerV1, 1) catch return;
+ wayland.globalsRegistered[3] = true;
+ } else if (strcmp(interface, wl.Output.getInterface().name) == 0) {
+ const output = Output.create(state, registry, name) catch return;
+ wayland.outputs.append(output) catch return;
+ }
+ },
+ .global_remove => |data| {
+ for (wayland.outputs.items) |output, i| {
+ if (output.globalName == data.name) {
+ state.allocator.destroy(output);
+ _ = wayland.outputs.swapRemove(i);
+ break;
+ }
+ }
+ },
+ }
+ }
+};
+
+pub const Output = struct {
+ state: *State,
+ wlOutput: *wl.Output,
+ globalName: u32,
+ scale: i32,
+ surface: ?*Surface,
+
+ pub fn create(state: *State, registry: *wl.Registry, name: u32) !*Output {
+ const self = try state.allocator.create(Output);
+ self.state = state;
+ self.wlOutput = try registry.bind(name, wl.Output, 3);
+ self.globalName = name;
+ self.scale = 1;
+ self.surface = null;
+
+ self.wlOutput.setListener(*Output, listener, self);
+ return self;
+ }
+
+ fn listener(_: *wl.Output, event: wl.Output.Event, output: *Output) void {
+ switch (event) {
+ .scale => |scale| {
+ output.scale = scale.factor;
+ },
+ .geometry => {},
+ .mode => {},
+ .name => {},
+ .description => {},
+ .done => {
+ if (output.surface) |_| {} else {
+ output.surface = Surface.create(output) catch return;
+ }
+ },
+ }
+ }
+};
+
+pub const Surface = struct {
+ output: *Output,
+
+ backgroundSurface: *wl.Surface,
+ backgroundBuffers: [2]Buffer,
+
+ layerSurface: *zwlr.LayerSurfaceV1,
+
+ configured: bool,
+ width: u16,
+ height: u16,
+
+ pub fn create(output: *Output) !*Surface {
+ const state = output.state;
+
+ const self = try state.allocator.create(Surface);
+ self.output = output;
+ self.configured = false;
+
+ self.backgroundSurface = try state.wayland.compositor.createSurface();
+ self.backgroundBuffers = mem.zeroes([2]Buffer);
+
+ self.layerSurface = try state.wayland.layerShell.getLayerSurface(
+ self.backgroundSurface,
+ output.wlOutput,
+ .overlay,
+ "levee",
+ );
+
+ const height = 32;
+ self.layerSurface.setSize(0, height);
+ self.layerSurface.setAnchor(
+ .{ .top = true, .left = true, .right = true, .bottom = false },
+ );
+ self.layerSurface.setExclusiveZone(@intCast(i32, height));
+ self.layerSurface.setMargin(0, 0, 0, 0);
+ self.layerSurface.setListener(*Surface, layerSurfaceListener, self);
+
+ self.backgroundSurface.commit();
+
+ return self;
+ }
+
+ pub fn destroy(self: *Surface) void {
+ self.output.surface = null;
+ self.backgroundSurface.destroy();
+ self.layerSurface.destroy();
+ self.backgroundBuffers[0].deinit();
+ self.backgroundBuffers[1].deinit();
+ self.output.state.allocator.destroy(self);
+ }
+
+ fn layerSurfaceListener(
+ layerSurface: *zwlr.LayerSurfaceV1,
+ event: zwlr.LayerSurfaceV1.Event,
+ surface: *Surface,
+ ) void {
+ switch (event) {
+ .configure => |data| {
+ surface.configured = true;
+ surface.width = @intCast(u16, data.width);
+ surface.height = @intCast(u16, data.height);
+
+ layerSurface.ackConfigure(data.serial);
+
+ render.renderBackground(surface) catch return;
+ surface.backgroundSurface.commit();
+ },
+ .closed => {
+ surface.destroy();
+ },
+ }
+ }
+};