commit faec083446e067dd4c6e1b944fb7306de9ee9ab6
parent 86231de2e6d8017596e443f8402ded96745c9a89
Author: Andrea Feletto <andrea@andreafeletto.com>
Date: Thu, 20 Jan 2022 01:52:09 +0100
handle tag click
Diffstat:
4 files changed, 236 insertions(+), 3 deletions(-)
diff --git a/build.zig b/build.zig
@@ -11,6 +11,7 @@ pub fn build(b: *std.build.Builder) void {
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");
+ scanner.addProtocolPath("protocol/river-control-unstable-v1.xml");
const wayland = Pkg{
.name = "wayland",
diff --git a/protocol/river-control-unstable-v1.xml b/protocol/river-control-unstable-v1.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="river_control_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_control_v1" version="1">
+ <description summary="run compositor commands">
+ This interface allows clients to run compositor commands and receive a
+ success/failure response with output or a failure message respectively.
+
+ Each command is built up in a series of add_argument requests and
+ executed with a run_command request. The first argument is the command
+ to be run.
+
+ A complete list of commands should be made available in the man page of
+ the compositor.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the river_control object">
+ This request indicates that the client will not use the
+ river_control object any more. Objects that have been created
+ through this instance are not affected.
+ </description>
+ </request>
+
+ <request name="add_argument">
+ <description summary="add an argument to the current command">
+ Arguments are stored by the server in the order they were sent until
+ the run_command request is made.
+ </description>
+ <arg name="argument" type="string" summary="the argument to add"/>
+ </request>
+
+ <request name="run_command">
+ <description summary="run the current command">
+ Execute the command built up using the add_argument request for the
+ given seat.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat"/>
+ <arg name="callback" type="new_id" interface="zriver_command_callback_v1"
+ summary="callback object"/>
+ </request>
+ </interface>
+
+ <interface name="zriver_command_callback_v1" version="1">
+ <description summary="callback object">
+ This object is created by the run_command request. Exactly one of the
+ success or failure events will be sent. This object will be destroyed
+ by the compositor after one of the events is sent.
+ </description>
+
+ <event name="success">
+ <description summary="command successful">
+ Sent when the command has been successfully received and executed by
+ the compositor. Some commands may produce output, in which case the
+ output argument will be a non-empty string.
+ </description>
+ <arg name="output" type="string" summary="the output of the command"/>
+ </event>
+
+ <event name="failure">
+ <description summary="command failed">
+ Sent when the command could not be carried out. This could be due to
+ sending a non-existent command, no command, not enough arguments, too
+ many arguments, invalid arguments, etc.
+ </description>
+ <arg name="failure_message" type="string"
+ summary="a message explaining why failure occurred"/>
+ </event>
+ </interface>
+</protocol>
diff --git a/src/tags.zig b/src/tags.zig
@@ -1,7 +1,10 @@
+const std = @import("std");
+
const zriver = @import("wayland").client.zriver;
const Output = @import("wayland.zig").Output;
const render = @import("render.zig");
+const Seat = @import("wayland.zig").Seat;
const State = @import("main.zig").State;
pub const Tag = struct {
@@ -17,9 +20,10 @@ pub const Tags = struct {
pub fn create(state: *State, output: *Output) !*Tags {
const self = try state.allocator.create(Tags);
+ const wayland = state.wayland;
self.output = output;
- self.outputStatus = try state.wayland.statusManager.getRiverOutputStatus(
+ self.outputStatus = try wayland.statusManager.getRiverOutputStatus(
output.wlOutput,
);
for (self.tags) |*tag, i| {
@@ -66,4 +70,24 @@ pub const Tags = struct {
}
}
}
+
+ pub fn handleClick(self: *Tags, x: u32, seat: *Seat) !void {
+ const state = self.output.state;
+ const control = state.wayland.control;
+
+ if (self.output.surface) |surface| {
+ const index = x / surface.height;
+ const payload = try std.fmt.allocPrintZ(
+ state.allocator,
+ "{d}",
+ .{ @as(u32, 1) << @intCast(u5, index) },
+ );
+ defer state.allocator.free(payload);
+
+ control.addArgument("set-focused-tags");
+ control.addArgument(payload);
+ const callback = try control.runCommand(seat.wlSeat);
+ _ = callback;
+ }
+ }
};
diff --git a/src/wayland.zig b/src/wayland.zig
@@ -18,14 +18,16 @@ pub const Wayland = struct {
registry: *wl.Registry,
outputs: ArrayList(*Output),
+ seats: ArrayList(*Seat),
compositor: *wl.Compositor,
subcompositor: *wl.Subcompositor,
shm: *wl.Shm,
layerShell: *zwlr.LayerShellV1,
statusManager: *zriver.StatusManagerV1,
+ control: *zriver.ControlV1,
- globalsRegistered: [5]bool,
+ globalsRegistered: [6]bool,
pub fn init(state: *State) !Wayland {
const display = try wl.Display.connect(null);
@@ -35,12 +37,14 @@ pub const Wayland = struct {
.display = display,
.registry = try display.getRegistry(),
.outputs = ArrayList(*Output).init(state.allocator),
+ .seats = ArrayList(*Seat).init(state.allocator),
.compositor = undefined,
.subcompositor = undefined,
.shm = undefined,
.layerShell = undefined,
.statusManager = undefined,
- .globalsRegistered = mem.zeroes([5]bool),
+ .control = undefined,
+ .globalsRegistered = mem.zeroes([6]bool),
};
}
@@ -76,9 +80,15 @@ pub const Wayland = struct {
} else if (strcmp(interface, zriver.StatusManagerV1.getInterface().name) == 0) {
wayland.statusManager = registry.bind(name, zriver.StatusManagerV1, 1) catch return;
wayland.globalsRegistered[4] = true;
+ } else if (strcmp(interface, zriver.ControlV1.getInterface().name) == 0) {
+ wayland.control = registry.bind(name, zriver.ControlV1, 1) catch return;
+ wayland.globalsRegistered[5] = 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;
+ } else if (strcmp(interface, wl.Seat.getInterface().name) == 0) {
+ const seat = Seat.create(state, registry, name) catch return;
+ wayland.seats.append(seat) catch return;
}
},
.global_remove => |data| {
@@ -89,9 +99,34 @@ pub const Wayland = struct {
break;
}
}
+ for (wayland.seats.items) |seat, i| {
+ if (seat.globalName == data.name) {
+ seat.destroy();
+ _ = wayland.seats.swapRemove(i);
+ break;
+ }
+ }
},
}
}
+
+ pub fn findSurface(self: *Wayland, wlSurface: ?*wl.Surface) ?*Surface {
+ if (wlSurface == null) {
+ return null;
+ }
+ for (self.outputs.items) |output| {
+ if (output.surface) |surface| {
+ if (
+ surface.backgroundSurface == wlSurface or
+ surface.tagsSurface == wlSurface or
+ surface.clockSurface == wlSurface
+ ) {
+ return surface;
+ }
+ }
+ }
+ return null;
+ }
};
pub const Output = struct {
@@ -143,6 +178,94 @@ pub const Output = struct {
}
};
+pub const Seat = struct {
+ state: *State,
+ wlSeat: *wl.Seat,
+ globalName: u32,
+
+ pointer: struct {
+ wlPointer: ?*wl.Pointer,
+ x: i32,
+ y: i32,
+ surface: ?*Surface,
+ },
+
+ pub fn create(state: *State, registry: *wl.Registry, name: u32) !*Seat {
+ const self = try state.allocator.create(Seat);
+ self.state = state;
+ self.wlSeat = try registry.bind(name, wl.Seat, 3);
+ self.globalName = name;
+
+ self.pointer.wlPointer = null;
+ self.pointer.surface = null;
+
+ self.wlSeat.setListener(*Seat, listener, self);
+ return self;
+ }
+
+ pub fn destroy(self: *Seat) void {
+ if (self.pointer.wlPointer) |wlPointer| {
+ wlPointer.release();
+ }
+ self.wlSeat.release();
+ self.state.allocator.destroy(self);
+ }
+
+ fn listener(wlSeat: *wl.Seat, event: wl.Seat.Event, seat: *Seat) void {
+ switch (event) {
+ .capabilities => |data| {
+ if (seat.pointer.wlPointer) |wlPointer| {
+ wlPointer.release();
+ seat.pointer.wlPointer = null;
+ }
+ if (data.capabilities.pointer) {
+ seat.pointer.wlPointer = wlSeat.getPointer() catch return;
+ seat.pointer.wlPointer.?.setListener(
+ *Seat,
+ pointerListener,
+ seat,
+ );
+ }
+ },
+ .name => {},
+ }
+ }
+
+ fn pointerListener(
+ _: *wl.Pointer,
+ event: wl.Pointer.Event,
+ seat: *Seat,
+ ) void {
+ switch (event) {
+ .enter => |data| {
+ seat.pointer.x = data.surface_x.toInt();
+ seat.pointer.y = data.surface_y.toInt();
+ const surface = seat.state.wayland.findSurface(data.surface);
+ seat.pointer.surface = surface;
+ },
+ .leave => |_| {
+ seat.pointer.surface = null;
+ },
+ .motion => |data| {
+ seat.pointer.x = data.surface_x.toInt();
+ seat.pointer.y = data.surface_y.toInt();
+ },
+ .button => |data| {
+ if (data.state != .pressed) return;
+ if (seat.pointer.surface) |surface| {
+ if (!surface.configured) return;
+
+ const x = @intCast(u32, seat.pointer.x);
+ if (x < surface.height * 9) {
+ surface.output.tags.handleClick(x, seat) catch return;
+ }
+ }
+ },
+ else => {},
+ }
+ }
+};
+
pub const Surface = struct {
output: *Output,