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:
parent
386316bdbd
commit
760c88b094
2
deps/zig-wayland
vendored
2
deps/zig-wayland
vendored
@ -1 +1 @@
|
||||
Subproject commit 52326e7ee09d7acb6b55855f7a697af083ae973a
|
||||
Subproject commit d693b3704ee73762c71a68d634ef1a538c3307bd
|
2
deps/zig-wlroots
vendored
2
deps/zig-wlroots
vendored
@ -1 +1 @@
|
||||
Subproject commit 16d9039b5c345b2cc26118032261df9782e24946
|
||||
Subproject commit 10ddf02b8aff37dabd07cee8695366917527676e
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
114
river/View.zig
114
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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user