diff --git a/build.zig b/build.zig
index 9e6de6b..bc405f3 100644
--- a/build.zig
+++ b/build.zig
@@ -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 {
diff --git a/river/Control.zig b/river/Control.zig
index 1c12b42..89b009f 100644
--- a/river/Control.zig
+++ b/river/Control.zig
@@ -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(
diff --git a/river/Output.zig b/river/Output.zig
index 2dae989..09e1168 100644
--- a/river/Output.zig
+++ b/river/Output.zig
@@ -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);
}
}
diff --git a/river/OutputStatus.zig b/river/OutputStatus.zig
new file mode 100644
index 0000000..b08c195
--- /dev/null
+++ b/river/OutputStatus.zig
@@ -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 .
+
+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);
+}
diff --git a/river/Root.zig b/river/Root.zig
index d411739..2b229b0 100644
--- a/river/Root.zig
+++ b/river/Root.zig
@@ -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
diff --git a/river/Seat.zig b/river/Seat.zig
index 82979fc..5fc1320 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -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
diff --git a/river/SeatStatus.zig b/river/SeatStatus.zig
new file mode 100644
index 0000000..3f50f90
--- /dev/null
+++ b/river/SeatStatus.zig
@@ -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 .
+
+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
+ "");
+}
diff --git a/river/Server.zig b/river/Server.zig
index f700746..b10918a 100644
--- a/river/Server.zig
+++ b/river/Server.zig
@@ -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
diff --git a/river/StatusManager.zig b/river/StatusManager.zig
new file mode 100644
index 0000000..3ea4207
--- /dev/null
+++ b/river/StatusManager.zig
@@ -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 .
+
+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);
+}
diff --git a/river/View.zig b/river/View.zig
index 8ab32cc..7929673 100644
--- a/river/View.zig
+++ b/river/View.zig
@@ -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;
diff --git a/river/VoidView.zig b/river/VoidView.zig
index 72c57a7..fca2321 100644
--- a/river/VoidView.zig
+++ b/river/VoidView.zig
@@ -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;
+}
diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig
index b3806db..88e7dce 100644
--- a/river/XdgToplevel.zig
+++ b/river/XdgToplevel.zig
@@ -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.?);
diff --git a/river/XwaylandView.zig b/river/XwaylandView.zig
index dbd35e4..681d9d1 100644
--- a/river/XwaylandView.zig
+++ b/river/XwaylandView.zig
@@ -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.?);
diff --git a/river/c.zig b/river/c.zig
index df4bcee..d5b5ddf 100644
--- a/river/c.zig
+++ b/river/c.zig
@@ -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
diff --git a/river/command/focus_output.zig b/river/command/focus_output.zig
index b11664f..fdb7095 100644
--- a/river/command/focus_output.zig
+++ b/river/command/focus_output.zig
@@ -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);
}