foreign-toplevel-management: basic implementation

We do no yet set the parent of toplevels. We also only honor activate
requests if the target view is already visible on the focused output.
This commit is contained in:
Isaac Freund 2020-12-24 01:59:30 +01:00
parent 386316bdbd
commit 760c88b094
No known key found for this signature in database
GPG key ID: 86DED400DDFD7A11
8 changed files with 142 additions and 26 deletions

2
deps/zig-wayland vendored

@ -1 +1 @@
Subproject commit 52326e7ee09d7acb6b55855f7a697af083ae973a
Subproject commit d693b3704ee73762c71a68d634ef1a538c3307bd

2
deps/zig-wlroots vendored

@ -1 +1 @@
Subproject commit 16d9039b5c345b2cc26118032261df9782e24946
Subproject commit 10ddf02b8aff37dabd07cee8695366917527676e

View file

@ -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);
}

View file

@ -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();

View file

@ -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();
}

View file

@ -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;
}

View file

@ -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();
}
}
}
}

View file

@ -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