river-status: implement protocol

This commit is contained in:
Isaac Freund 2020-06-04 16:56:58 +02:00
parent e8aaadb228
commit 5aa7fe8af8
No known key found for this signature in database
GPG key ID: 86DED400DDFD7A11
15 changed files with 385 additions and 28 deletions

View file

@ -80,6 +80,7 @@ fn addProtocolDeps(exe: *std.build.LibExeObjStep, protocol_step: *std.build.Step
exe.step.dependOn(protocol_step);
exe.addIncludeDir("protocol");
exe.addCSourceFile("protocol/river-control-unstable-v1-protocol.c", &[_][]const u8{"-std=c99"});
exe.addCSourceFile("protocol/river-status-unstable-v1-protocol.c", &[_][]const u8{"-std=c99"});
}
const ScanProtocolsStep = struct {

View file

@ -44,7 +44,7 @@ pub fn init(self: *Self, server: *Server) !void {
protocol_version,
self,
bind,
) orelse return error.CantCreateRiverWindowManagementGlobal;
) orelse return error.CantCreateWlGlobal;
self.listen_display_destroy.notify = handleDisplayDestroy;
c.wl_display_add_destroy_listener(server.wl_display, &self.listen_display_destroy);
@ -67,11 +67,7 @@ fn bind(wl_client: ?*c.wl_client, data: ?*c_void, version: u32, id: u32) callcon
c.wl_client_post_no_memory(wl_client);
return;
};
c.wl_resource_set_implementation(wl_resource, &implementation, self, resourceDestroy);
}
fn resourceDestroy(wl_resource: ?*c.wl_resource) callconv(.C) void {
// TODO
c.wl_resource_set_implementation(wl_resource, &implementation, self, null);
}
fn runCommand(

View file

@ -28,6 +28,7 @@ const Log = @import("log.zig").Log;
const Root = @import("Root.zig");
const View = @import("View.zig");
const ViewStack = @import("view_stack.zig").ViewStack;
const OutputStatus = @import("OutputStatus.zig");
root: *Root,
wlr_output: *c.wlr_output,
@ -55,6 +56,9 @@ master_factor: f64,
/// Current layout of the output.
layout: Layout,
/// List of status tracking objects relaying changes to this output to clients.
status_trackers: std.SinglyLinkedList(OutputStatus),
// All listeners for this output, in alphabetical order
listen_destroy: c.wl_listener,
listen_frame: c.wl_listener,
@ -133,6 +137,8 @@ pub fn init(self: *Self, root: *Root, wlr_output: *c.wlr_output) !void {
// LeftMaster is the default layout for all outputs
self.layout = Layout.LeftMaster;
self.status_trackers = std.SinglyLinkedList(OutputStatus).init();
// Set up listeners
self.listen_destroy.notify = handleDestroy;
c.wl_signal_add(&wlr_output.events.destroy, &self.listen_destroy);
@ -170,20 +176,6 @@ pub fn init(self: *Self, root: *Root, wlr_output: *c.wlr_output) !void {
}
}
pub fn deinit(self: *Self) void {
for (self.layers) |*layer| {
while (layer.pop()) |layer_surface_node| {
self.root.server.allocator.destroy(layer_surface_node);
}
}
while (self.views.first) |node| {
node.view.deinit();
self.views.remove(node);
self.root.server.allocator.destroy(node);
}
}
pub fn getRenderer(self: Self) *c.wlr_renderer {
return c.river_wlr_backend_get_renderer(self.wlr_output.backend);
}
@ -708,7 +700,7 @@ fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
while (seat_it) |seat_node| : (seat_it = seat_node.next) {
const seat = &seat_node.data;
if (seat.focused_output == self) {
seat.focused_output = fallback_output;
seat.focusOutput(self);
seat.focus(null);
}
}

77
river/OutputStatus.zig Normal file
View file

@ -0,0 +1,77 @@
// This file is part of river, a dynamic tiling wayland compositor.
//
// Copyright 2020 Isaac Freund
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const Self = @This();
const std = @import("std");
const c = @import("c.zig");
const Log = @import("log.zig").Log;
const Output = @import("Output.zig");
const View = @import("View.zig");
const ViewStack = @import("view_stack.zig").ViewStack;
const implementation = c.struct_zriver_output_status_v1_interface{
.destroy = destroy,
};
output: *Output,
wl_resource: *c.wl_resource,
pub fn init(self: *Self, output: *Output, wl_resource: *c.wl_resource) void {
self.output = output;
self.wl_resource = wl_resource;
c.wl_resource_set_implementation(wl_resource, &implementation, self, handleResourceDestroy);
// Send view/focused tags once on bind.
self.sendViewTags();
self.sendFocusedTags();
}
fn handleResourceDestroy(wl_resource: ?*c.wl_resource) callconv(.C) void {
const self = @ptrCast(*Self, @alignCast(@alignOf(*Self), c.wl_resource_get_user_data(wl_resource)));
const node = @fieldParentPtr(std.SinglyLinkedList(Self).Node, "data", self);
self.output.status_trackers.remove(node);
}
fn destroy(wl_client: ?*c.wl_client, wl_resource: ?*c.wl_resource) callconv(.C) void {
c.wl_resource_destroy(wl_resource);
}
/// Send the current tags of each view on the output to the client.
pub fn sendViewTags(self: Self) void {
var view_tags: c.wl_array = undefined;
c.wl_array_init(&view_tags);
var it = ViewStack(View).iterator(self.output.views.first, std.math.maxInt(u32));
while (it.next()) |node| {
const ptr = c.wl_array_add(&view_tags, @sizeOf(u32)) orelse {
c.wl_resource_post_no_memory(self.wl_resource);
Log.Error.log("out of memory", .{});
return;
};
const ptr_u32 = @ptrCast(*u32, @alignCast(@alignOf(u32), ptr));
ptr_u32.* = node.view.current_tags;
}
c.zriver_output_status_v1_send_view_tags(self.wl_resource, &view_tags);
}
/// Send the currently focused tags of the output to the client.
pub fn sendFocusedTags(self: Self) void {
c.zriver_output_status_v1_send_focused_tags(self.wl_resource, self.output.current_focused_tags);
}

View file

@ -104,7 +104,7 @@ pub fn addOutput(self: *Self, wlr_output: *c.wlr_output) void {
// TODO: move views from the noop output to the new one and focus(null)
var it = self.server.input_manager.seats.first;
while (it) |seat_node| : (it = seat_node.next) {
seat_node.data.focused_output = &self.outputs.first.?.data;
seat_node.data.focusOutput(&self.outputs.first.?.data);
}
}
}
@ -220,8 +220,12 @@ fn commitTransaction(self: *Self) void {
);
output.current_focused_tags = tags;
output.pending_focused_tags = null;
var it = output.status_trackers.first;
while (it) |node| : (it = node.next) node.data.sendFocusedTags();
}
var view_tags_changed = false;
var view_it = ViewStack(View).iterator(output.views.first, std.math.maxInt(u32));
while (view_it.next()) |view_node| {
const view = &view_node.view;
@ -236,10 +240,16 @@ fn commitTransaction(self: *Self) void {
if (view.pending_tags) |tags| {
view.current_tags = tags;
view.pending_tags = null;
view_tags_changed = true;
}
view.dropStashedBuffer();
}
if (view_tags_changed) {
var it = output.status_trackers.first;
while (it) |node| : (it = node.next) node.data.sendViewTags();
}
}
// Iterate over all seats and update focus

View file

@ -27,9 +27,12 @@ const InputManager = @import("InputManager.zig");
const Keyboard = @import("Keyboard.zig");
const LayerSurface = @import("LayerSurface.zig");
const Output = @import("Output.zig");
const SeatStatus = @import("SeatStatus.zig");
const View = @import("View.zig");
const ViewStack = @import("view_stack.zig").ViewStack;
// TODO: remove none variant, unify focused_view and focused_layer fields
// with type ?FocusTarget
const FocusTarget = union(enum) {
view: *View,
layer: *LayerSurface,
@ -62,6 +65,9 @@ focus_stack: ViewStack(*View),
/// recieve focus.
focused_layer: ?*LayerSurface,
/// List of status tracking objects relaying changes to this seat to clients.
status_trackers: std.SinglyLinkedList(SeatStatus),
listen_request_set_selection: c.wl_listener,
pub fn init(self: *Self, input_manager: *InputManager, name: []const u8) !void {
@ -70,6 +76,7 @@ pub fn init(self: *Self, input_manager: *InputManager, name: []const u8) !void {
// This will be automatically destroyed when the display is destroyed
self.wlr_seat = c.wlr_seat_create(input_manager.server.wl_display, name.ptr) orelse
return error.CantCreateWlrSeat;
self.wlr_seat.data = self;
try self.cursor.init(self);
errdefer self.cursor.destroy();
@ -86,6 +93,8 @@ pub fn init(self: *Self, input_manager: *InputManager, name: []const u8) !void {
self.focused_layer = null;
self.status_trackers = std.SinglyLinkedList(SeatStatus).init();
self.listen_request_set_selection.notify = handleRequestSetSelection;
c.wl_signal_add(&self.wlr_seat.events.request_set_selection, &self.listen_request_set_selection);
}
@ -167,9 +176,7 @@ pub fn setFocusRaw(self: *Self, focus_target: FocusTarget) void {
.view => |target_view| target_view == self.focused_view,
.layer => |target_layer| target_layer == self.focused_layer,
.none => false,
}) {
return;
}
}) return;
// Obtain the target wlr_surface
const target_wlr_surface = switch (focus_target) {
@ -223,6 +230,23 @@ pub fn setFocusRaw(self: *Self, focus_target: FocusTarget) void {
);
}
}
// Inform any clients tracking status of the change
var it = self.status_trackers.first;
while (it) |node| : (it = node.next) node.data.sendFocusedView();
}
/// Focus the given output, notifying any listening clients of the change.
pub fn focusOutput(self: *Self, output: *Output) void {
const root = &self.input_manager.server.root;
var it = self.status_trackers.first;
while (it) |node| : (it = node.next) node.data.sendOutput(.unfocused);
self.focused_output = output;
it = self.status_trackers.first;
while (it) |node| : (it = node.next) node.data.sendOutput(.focused);
}
/// Handle the unmapping of a view, removing it from the focus stack and

81
river/SeatStatus.zig Normal file
View file

@ -0,0 +1,81 @@
// This file is part of river, a dynamic tiling wayland compositor.
//
// Copyright 2020 Isaac Freund
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const Self = @This();
const std = @import("std");
const c = @import("c.zig");
const Log = @import("log.zig").Log;
const Seat = @import("Seat.zig");
const Output = @import("Output.zig");
const View = @import("View.zig");
const FocusState = enum {
focused,
unfocused,
};
const implementation = c.struct_zriver_seat_status_v1_interface{
.destroy = destroy,
};
seat: *Seat,
wl_resource: *c.wl_resource,
pub fn init(self: *Self, seat: *Seat, wl_resource: *c.wl_resource) void {
self.seat = seat;
self.wl_resource = wl_resource;
c.wl_resource_set_implementation(wl_resource, &implementation, self, handleResourceDestroy);
// Send focused output/view once on bind
self.sendOutput(.focused);
self.sendFocusedView();
}
fn handleResourceDestroy(wl_resource: ?*c.wl_resource) callconv(.C) void {
const self = @ptrCast(*Self, @alignCast(@alignOf(*Self), c.wl_resource_get_user_data(wl_resource)));
const node = @fieldParentPtr(std.SinglyLinkedList(Self).Node, "data", self);
self.seat.status_trackers.remove(node);
}
fn destroy(wl_client: ?*c.wl_client, wl_resource: ?*c.wl_resource) callconv(.C) void {
c.wl_resource_destroy(wl_resource);
}
pub fn sendOutput(self: Self, state: FocusState) void {
const wl_client = c.wl_resource_get_client(self.wl_resource);
const output_resources = &self.seat.focused_output.wlr_output.resources;
var output_resource = c.wl_resource_from_link(output_resources.next);
while (c.wl_resource_get_link(output_resource) != output_resources) : (output_resource =
c.wl_resource_from_link(c.wl_resource_get_link(output_resource).*.next))
{
if (c.wl_resource_get_client(output_resource) == wl_client) switch (state) {
.focused => c.zriver_seat_status_v1_send_focused_output(self.wl_resource, output_resource),
.unfocused => c.zriver_seat_status_v1_send_unfocused_output(self.wl_resource, output_resource),
};
}
}
pub fn sendFocusedView(self: Self) void {
c.zriver_seat_status_v1_send_focused_view(self.wl_resource, if (self.seat.focused_view) |v|
v.getTitle()
else
"");
}

View file

@ -23,15 +23,16 @@ const std = @import("std");
const c = @import("c.zig");
const Config = @import("Config.zig");
const Control = @import("Control.zig");
const DecorationManager = @import("DecorationManager.zig");
const InputManager = @import("InputManager.zig");
const LayerSurface = @import("LayerSurface.zig");
const Log = @import("log.zig").Log;
const Output = @import("Output.zig");
const Root = @import("Root.zig");
const StatusManager = @import("StatusManager.zig");
const View = @import("View.zig");
const ViewStack = @import("view_stack.zig").ViewStack;
const Control = @import("Control.zig");
const XwaylandUnmanaged = @import("XwaylandUnmanaged.zig");
allocator: *std.mem.Allocator,
@ -57,6 +58,7 @@ input_manager: InputManager,
root: Root,
config: Config,
control: Control,
status_manager: StatusManager,
pub fn init(self: *Self, allocator: *std.mem.Allocator) !void {
self.allocator = allocator;
@ -123,6 +125,7 @@ pub fn init(self: *Self, allocator: *std.mem.Allocator) !void {
// Must be called after root is initialized
try self.input_manager.init(self);
try self.control.init(self);
try self.status_manager.init(self);
// These all free themselves when the wl_display is destroyed
_ = c.wlr_data_device_manager_create(self.wl_display) orelse

146
river/StatusManager.zig Normal file
View file

@ -0,0 +1,146 @@
// This file is part of river, a dynamic tiling wayland compositor.
//
// Copyright 2020 Isaac Freund
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const Self = @This();
const std = @import("std");
const c = @import("c.zig");
const Log = @import("log.zig").Log;
const Output = @import("Output.zig");
const OutputStatus = @import("OutputStatus.zig");
const Seat = @import("Seat.zig");
const SeatStatus = @import("SeatStatus.zig");
const Server = @import("Server.zig");
const protocol_version = 1;
const implementation = c.struct_zriver_status_manager_v1_interface{
.destroy = destroy,
.get_river_output_status = getRiverOutputStatus,
.get_river_seat_status = getRiverSeatStatus,
};
// TODO: remove this field, move allocator to util or something
server: *Server,
wl_global: *c.wl_global,
listen_display_destroy: c.wl_listener,
pub fn init(self: *Self, server: *Server) !void {
self.server = server;
self.wl_global = c.wl_global_create(
server.wl_display,
&c.zriver_status_manager_v1_interface,
protocol_version,
self,
bind,
) orelse return error.CantCreateWlGlobal;
self.listen_display_destroy.notify = handleDisplayDestroy;
c.wl_display_add_destroy_listener(server.wl_display, &self.listen_display_destroy);
}
fn handleDisplayDestroy(wl_listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
const self = @fieldParentPtr(Self, "listen_display_destroy", wl_listener.?);
c.wl_global_destroy(self.wl_global);
}
/// Called when a client binds our global
fn bind(wl_client: ?*c.wl_client, data: ?*c_void, version: u32, id: u32) callconv(.C) void {
const self = @ptrCast(*Self, @alignCast(@alignOf(*Self), data));
const wl_resource = c.wl_resource_create(
wl_client,
&c.zriver_status_manager_v1_interface,
@intCast(c_int, version),
id,
) orelse {
c.wl_client_post_no_memory(wl_client);
Log.Error.log("out of memory\n", .{});
return;
};
c.wl_resource_set_implementation(wl_resource, &implementation, self, null);
}
fn destroy(wl_client: ?*c.wl_client, wl_resource: ?*c.wl_resource) callconv(.C) void {}
fn getRiverOutputStatus(
wl_client: ?*c.wl_client,
wl_resource: ?*c.wl_resource,
new_id: u32,
output_wl_resource: ?*c.wl_resource,
) callconv(.C) void {
const self = @ptrCast(*Self, @alignCast(@alignOf(*Self), c.wl_resource_get_user_data(wl_resource)));
// This can be null if the output is inert, in which case we ignore the request
const wlr_output = c.wlr_output_from_resource(output_wl_resource) orelse return;
const output = @ptrCast(*Output, @alignCast(@alignOf(*Output), wlr_output.*.data));
const allocator = self.server.allocator;
const node = allocator.create(std.SinglyLinkedList(OutputStatus).Node) catch {
c.wl_client_post_no_memory(wl_client);
Log.Error.log("out of memory\n", .{});
return;
};
const output_status_resource = c.wl_resource_create(
wl_client,
&c.zriver_output_status_v1_interface,
protocol_version,
new_id,
) orelse {
c.wl_client_post_no_memory(wl_client);
Log.Error.log("out of memory\n", .{});
return;
};
node.data.init(output, output_status_resource);
output.status_trackers.prepend(node);
}
fn getRiverSeatStatus(
wl_client: ?*c.wl_client,
wl_resource: ?*c.wl_resource,
new_id: u32,
seat_wl_resource: ?*c.wl_resource,
) callconv(.C) void {
const self = @ptrCast(*Self, @alignCast(@alignOf(*Self), c.wl_resource_get_user_data(wl_resource)));
// This can be null if the seat is inert, in which case we ignore the request
const wlr_seat_client = c.wlr_seat_client_from_resource(wl_resource) orelse return;
const seat = @ptrCast(*Seat, @alignCast(@alignOf(*Seat), wlr_seat_client.*.seat.*.data));
const allocator = self.server.allocator;
const node = allocator.create(std.SinglyLinkedList(SeatStatus).Node) catch {
c.wl_client_post_no_memory(wl_client);
Log.Error.log("out of memory\n", .{});
return;
};
const seat_status_resource = c.wl_resource_create(
wl_client,
&c.zriver_seat_status_v1_interface,
protocol_version,
new_id,
) orelse {
c.wl_client_post_no_memory(wl_client);
Log.Error.log("out of memory\n", .{});
return;
};
node.data.init(seat, seat_status_resource);
seat.status_trackers.prepend(node);
}

View file

@ -223,6 +223,14 @@ pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surfa
};
}
/// Return the current title of the view. May be an empty string.
pub fn getTitle(self: Self) [*:0]const u8 {
return switch (self.impl) {
.xdg_toplevel => |xdg_toplevel| xdg_toplevel.getTitle(),
.xwayland_view => |xwayland_view| xwayland_view.getTitle(),
};
}
/// Called by the impl when the surface is ready to be displayed
pub fn map(self: *Self) void {
const root = self.output.root;

View file

@ -46,3 +46,7 @@ pub fn forEachSurface(
pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surface {
unreachable;
}
pub fn getTitle(self: Self) [*:0]const u8 {
unreachable;
}

View file

@ -100,6 +100,15 @@ pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surfa
);
}
/// Return the current title of the toplevel. May be an empty string.
pub fn getTitle(self: Self) [*:0]const u8 {
const wlr_xdg_toplevel: *c.wlr_xdg_toplevel = @field(
self.wlr_xdg_surface,
c.wlr_xdg_surface_union,
).toplevel;
return wlr_xdg_toplevel.title;
}
/// Called when the xdg surface is destroyed
fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
const self = @fieldParentPtr(Self, "listen_destroy", listener.?);

View file

@ -105,6 +105,11 @@ pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surfa
);
}
/// Get the current title of the xwayland surface. May be an empty string
pub fn getTitle(self: Self) [*:0]const u8 {
return self.wlr_xwayland_surface.title;
}
/// Called when the xwayland surface is destroyed
fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
const self = @fieldParentPtr(Self, "listen_destroy", listener.?);

View file

@ -51,6 +51,7 @@ pub usingnamespace @cImport({
@cInclude("include/bindings.h");
@cInclude("river-control-unstable-v1-protocol.h");
@cInclude("river-status-unstable-v1-protocol.h");
});
// These are needed because zig currently names translated anonymous unions

View file

@ -45,10 +45,10 @@ pub fn focusOutput(
// Focus the next/prev output in the list if there is one, else wrap
const focused_node = @fieldParentPtr(std.TailQueue(Output).Node, "data", seat.focused_output);
seat.focused_output = switch (direction) {
seat.focusOutput(switch (direction) {
.Next => if (focused_node.next) |node| &node.data else &root.outputs.first.?.data,
.Prev => if (focused_node.prev) |node| &node.data else &root.outputs.last.?.data,
};
});
seat.focus(null);
}