aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
m---------deps/zig-wayland0
m---------deps/zig-wlroots0
-rw-r--r--river/SeatStatus.zig5
-rw-r--r--river/Server.zig6
-rw-r--r--river/View.zig114
-rw-r--r--river/VoidView.zig6
-rw-r--r--river/XdgToplevel.zig22
-rw-r--r--river/XwaylandView.zig11
8 files changed, 140 insertions, 24 deletions
diff --git a/deps/zig-wayland b/deps/zig-wayland
-Subproject 52326e7ee09d7acb6b55855f7a697af083ae973
+Subproject d693b3704ee73762c71a68d634ef1a538c3307b
diff --git a/deps/zig-wlroots b/deps/zig-wlroots
-Subproject 16d9039b5c345b2cc26118032261df9782e2494
+Subproject 10ddf02b8aff37dabd07cee8695366917527676
diff --git a/river/SeatStatus.zig b/river/SeatStatus.zig
index a6c0d46..c83a9db 100644
--- a/river/SeatStatus.zig
+++ b/river/SeatStatus.zig
@@ -65,6 +65,9 @@ pub fn sendOutput(self: Self, state: enum { focused, unfocused }) void {
}
pub fn sendFocusedView(self: Self) void {
- const title: [*:0]const u8 = if (self.seat.focused == .view) self.seat.focused.view.getTitle() else "";
+ const title: [*:0]const u8 = if (self.seat.focused == .view)
+ self.seat.focused.view.getTitle() orelse ""
+ else
+ "";
self.seat_status.sendFocusedView(title);
}
diff --git a/river/Server.zig b/river/Server.zig
index 1d91061..b4e5a20 100644
--- a/river/Server.zig
+++ b/river/Server.zig
@@ -56,6 +56,8 @@ new_layer_surface: wl.Listener(*wlr.LayerSurfaceV1),
xwayland: if (build_options.xwayland) *wlr.Xwayland else void,
new_xwayland_surface: if (build_options.xwayland) wl.Listener(*wlr.XwaylandSurface) else void,
+foreign_toplevel_manager: *wlr.ForeignToplevelManagerV1,
+
decoration_manager: DecorationManager,
input_manager: InputManager,
output_manager: OutputManager,
@@ -104,6 +106,8 @@ pub fn init(self: *Self) !void {
self.xwayland.events.new_surface.add(&self.new_xwayland_surface);
}
+ self.foreign_toplevel_manager = try wlr.ForeignToplevelManagerV1.create(self.wl_server);
+
_ = try wlr.PrimarySelectionDeviceManagerV1.create(self.wl_server);
self.config = try Config.init();
@@ -135,8 +139,8 @@ pub fn deinit(self: *Self) void {
self.root.deinit();
- self.wl_server.destroy();
self.noop_backend.destroy();
+ self.wl_server.destroy();
self.input_manager.deinit();
self.config.deinit();
diff --git a/river/View.zig b/river/View.zig
index ddde6a4..3003f88 100644
--- a/river/View.zig
+++ b/river/View.zig
@@ -29,6 +29,7 @@ const util = @import("util.zig");
const Box = @import("Box.zig");
const Output = @import("Output.zig");
const Root = @import("Root.zig");
+const Seat = @import("Seat.zig");
const ViewStack = @import("view_stack.zig").ViewStack;
const XdgToplevel = @import("XdgToplevel.zig");
const XwaylandView = if (build_options.xwayland) @import("XwaylandView.zig") else @import("VoidView.zig");
@@ -84,8 +85,8 @@ impl: Impl = undefined,
/// The output this view is currently associated with
output: *Output,
-/// This is from the point where the view is mapped until the surface
-/// is destroyed by wlroots.
+/// This is non-null from the point where the view is mapped until the
+/// surface is destroyed by wlroots.
surface: ?*wlr.Surface = null,
/// This View struct outlasts the wlroots object it wraps. This bool is set to
@@ -122,6 +123,12 @@ opacity_timer: ?*wl.EventSource = null,
draw_borders: bool = true,
+/// This is created when the view is mapped and destroyed with the view
+foreign_toplevel_handle: ?*wlr.ForeignToplevelHandleV1 = null,
+foreign_activate: wl.Listener(*wlr.ForeignToplevelHandleV1.event.Activated) = undefined,
+foreign_fullscreen: wl.Listener(*wlr.ForeignToplevelHandleV1.event.Fullscreen) = undefined,
+foreign_close: wl.Listener(*wlr.ForeignToplevelHandleV1) = undefined,
+
pub fn init(self: *Self, output: *Output, tags: u32, surface: anytype) void {
self.* = .{
.output = output,
@@ -150,10 +157,19 @@ pub fn init(self: *Self, output: *Output, tags: u32, surface: anytype) void {
pub fn destroy(self: *Self) void {
self.dropSavedBuffers();
self.saved_buffers.deinit();
+
+ if (self.foreign_toplevel_handle) |handle| {
+ self.foreign_activate.link.remove();
+ self.foreign_fullscreen.link.remove();
+ self.foreign_close.link.remove();
+ handle.destroy();
+ }
+
switch (self.impl) {
.xdg_toplevel => |*xdg_toplevel| xdg_toplevel.deinit(),
.xwayland_view => |*xwayland_view| xwayland_view.deinit(),
}
+
const node = @fieldParentPtr(ViewStack(Self).Node, "view", self);
self.output.views.remove(node);
util.gpa.destroy(node);
@@ -220,6 +236,10 @@ pub fn needsConfigure(self: Self) bool {
}
pub fn configure(self: Self) void {
+ if (self.foreign_toplevel_handle) |handle| {
+ handle.setActivated(self.pending.focus != 0);
+ handle.setFullscreen(self.pending.fullscreen);
+ }
switch (self.impl) {
.xdg_toplevel => |xdg_toplevel| xdg_toplevel.configure(),
.xwayland_view => |xwayland_view| xwayland_view.configure(),
@@ -293,6 +313,9 @@ pub fn sendToOutput(self: *Self, destination_output: *Output) void {
self.surface.?.sendLeave(self.output.wlr_output);
self.surface.?.sendEnter(destination_output.wlr_output);
+ self.foreign_toplevel_handle.?.outputLeave(self.output.wlr_output);
+ self.foreign_toplevel_handle.?.outputEnter(destination_output.wlr_output);
+
self.output = destination_output;
}
@@ -324,14 +347,23 @@ pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*wlr.Surface
};
}
-/// Return the current title of the view. May be an empty string.
-pub fn getTitle(self: Self) [*:0]const u8 {
+/// Return the current title of the view if any.
+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(),
};
}
+/// Return the current app_id of the view if any.
+pub fn getAppId(self: Self) ?[*:0]const u8 {
+ return switch (self.impl) {
+ .xdg_toplevel => |xdg_toplevel| xdg_toplevel.getAppId(),
+ // X11 clients don't have an app_id but the class serves a similar role
+ .xwayland_view => |xwayland_view| xwayland_view.getClass(),
+ };
+}
+
/// Clamp the width/height of the pending state to the constraints of the view
pub fn applyConstraints(self: *Self) void {
const constraints = self.getConstraints();
@@ -383,6 +415,28 @@ pub fn map(self: *Self) void {
log.debug(.server, "view '{}' mapped", .{self.getTitle()});
+ if (self.foreign_toplevel_handle == null) {
+ self.foreign_toplevel_handle = wlr.ForeignToplevelHandleV1.create(
+ root.server.foreign_toplevel_manager,
+ ) catch {
+ log.crit(.server, "out of memory", .{});
+ self.surface.?.resource.getClient().postNoMemory();
+ return;
+ };
+
+ self.foreign_activate.setNotify(handleForeignActivate);
+ self.foreign_toplevel_handle.?.events.request_activate.add(&self.foreign_activate);
+
+ self.foreign_fullscreen.setNotify(handleForeignFullscreen);
+ self.foreign_toplevel_handle.?.events.request_fullscreen.add(&self.foreign_fullscreen);
+
+ self.foreign_close.setNotify(handleForeignClose);
+ self.foreign_toplevel_handle.?.events.request_close.add(&self.foreign_close);
+
+ if (self.getTitle()) |s| self.foreign_toplevel_handle.?.setTitle(s);
+ if (self.getAppId()) |s| self.foreign_toplevel_handle.?.setAppId(s);
+ }
+
// Add the view to the stack of its output
const node = @fieldParentPtr(ViewStack(Self).Node, "view", self);
self.output.views.attach(node, self.output.attach_mode);
@@ -428,6 +482,28 @@ pub fn unmap(self: *Self) void {
root.startTransaction();
}
+pub fn notifyTitle(self: Self) void {
+ if (self.foreign_toplevel_handle) |handle| {
+ if (self.getTitle()) |s| handle.setTitle(s);
+ }
+ // Send title to all status listeners attached to a seat which focuses this view
+ var seat_it = self.output.root.server.input_manager.seats.first;
+ while (seat_it) |seat_node| : (seat_it = seat_node.next) {
+ if (seat_node.data.focused == .view and seat_node.data.focused.view == self) {
+ var client_it = seat_node.data.status_trackers.first;
+ while (client_it) |client_node| : (client_it = client_node.next) {
+ client_node.data.sendFocusedView();
+ }
+ }
+ }
+}
+
+pub fn notifyAppId(self: Self) void {
+ if (self.foreign_toplevel_handle) |handle| {
+ if (self.getAppId()) |s| handle.setAppId(s);
+ }
+}
+
/// Change the opacity of a view by config.view_opacity_delta.
/// If the target opacity was reached, return true.
fn incrementOpacity(self: *Self) bool {
@@ -491,3 +567,33 @@ pub fn commitOpacityTransition(self: *Self) void {
self.attachOpacityTimer();
}
}
+
+/// Only honors the request if the view is already visible on the seat's
+/// currently focused output. TODO: consider allowing this request to switch
+/// output/tag focus.
+fn handleForeignActivate(
+ listener: *wl.Listener(*wlr.ForeignToplevelHandleV1.event.Activated),
+ event: *wlr.ForeignToplevelHandleV1.event.Activated,
+) void {
+ const self = @fieldParentPtr(Self, "foreign_activate", listener);
+ const seat = @intToPtr(*Seat, event.seat.data);
+ seat.focus(self);
+ self.output.root.startTransaction();
+}
+
+fn handleForeignFullscreen(
+ listener: *wl.Listener(*wlr.ForeignToplevelHandleV1.event.Fullscreen),
+ event: *wlr.ForeignToplevelHandleV1.event.Fullscreen,
+) void {
+ const self = @fieldParentPtr(Self, "foreign_fullscreen", listener);
+ self.pending.fullscreen = event.fullscreen;
+ self.applyPending();
+}
+
+fn handleForeignClose(
+ listener: *wl.Listener(*wlr.ForeignToplevelHandleV1),
+ event: *wlr.ForeignToplevelHandleV1,
+) void {
+ const self = @fieldParentPtr(Self, "foreign_close", listener);
+ self.close();
+}
diff --git a/river/VoidView.zig b/river/VoidView.zig
index ff29d99..21192ca 100644
--- a/river/VoidView.zig
+++ b/river/VoidView.zig
@@ -52,7 +52,11 @@ pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*wlr.Surface
unreachable;
}
-pub fn getTitle(self: Self) [*:0]const u8 {
+pub fn getTitle(self: Self) ?[*:0]const u8 {
+ unreachable;
+}
+
+pub fn getClass(self: Self) ?[*:0]const u8 {
unreachable;
}
diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig
index 8017be9..689a4aa 100644
--- a/river/XdgToplevel.zig
+++ b/river/XdgToplevel.zig
@@ -123,9 +123,14 @@ pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*wlr.Surface
);
}
-/// Return the current title of the toplevel. May be an empty string.
-pub fn getTitle(self: Self) [*:0]const u8 {
- return self.xdg_surface.role_data.toplevel.title orelse "NULL";
+/// Return the current title of the toplevel if any.
+pub fn getTitle(self: Self) ?[*:0]const u8 {
+ return self.xdg_surface.role_data.toplevel.title;
+}
+
+/// Return the current app_id of the toplevel if any .
+pub fn getAppId(self: Self) ?[*:0]const u8 {
+ return self.xdg_surface.role_data.toplevel.app_id;
}
/// Return bounds on the dimensions of the toplevel.
@@ -310,15 +315,4 @@ fn handleRequestResize(listener: *wl.Listener(*wlr.XdgToplevel.event.Resize), ev
/// Called when the client sets / updates its title
fn handleSetTitle(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurface) void {
const self = @fieldParentPtr(Self, "set_title", listener);
-
- // Send title to all status listeners attached to a seat which focuses this view
- var seat_it = self.view.output.root.server.input_manager.seats.first;
- while (seat_it) |seat_node| : (seat_it = seat_node.next) {
- if (seat_node.data.focused == .view and seat_node.data.focused.view == self.view) {
- var client_it = seat_node.data.status_trackers.first;
- while (client_it) |client_node| : (client_it = client_node.next) {
- client_node.data.sendFocusedView();
- }
- }
- }
}
diff --git a/river/XwaylandView.zig b/river/XwaylandView.zig
index b118b82..f3f0474 100644
--- a/river/XwaylandView.zig
+++ b/river/XwaylandView.zig
@@ -121,9 +121,14 @@ pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*wlr.Surface
);
}
-/// Get the current title of the xwayland surface. May be an empty string
-pub fn getTitle(self: Self) [*:0]const u8 {
- return self.xwayland_surface.title orelse "";
+/// Get the current title of the xwayland surface if any.
+pub fn getTitle(self: Self) ?[*:0]const u8 {
+ return self.xwayland_surface.title;
+}
+
+/// Get the current class of the xwayland surface if any.
+pub fn getClass(self: Self) ?[*:0]const u8 {
+ return self.xwayland_surface.class;
}
/// Return bounds on the dimensions of the view