stevee

My wayland statusbar
git clone git://gtms.dev/stevee
Log | Files | Refs | Submodules | README | LICENSE

commit faec083446e067dd4c6e1b944fb7306de9ee9ab6
parent 86231de2e6d8017596e443f8402ded96745c9a89
Author: Andrea Feletto <andrea@andreafeletto.com>
Date:   Thu, 20 Jan 2022 01:52:09 +0100

handle tag click

Diffstat:
Mbuild.zig | 1+
Aprotocol/river-control-unstable-v1.xml | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/tags.zig | 26+++++++++++++++++++++++++-
Msrc/wayland.zig | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
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,