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 {
|
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);
|
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,
|
xwayland: if (build_options.xwayland) *wlr.Xwayland else void,
|
||||||
new_xwayland_surface: if (build_options.xwayland) wl.Listener(*wlr.XwaylandSurface) else void,
|
new_xwayland_surface: if (build_options.xwayland) wl.Listener(*wlr.XwaylandSurface) else void,
|
||||||
|
|
||||||
|
foreign_toplevel_manager: *wlr.ForeignToplevelManagerV1,
|
||||||
|
|
||||||
decoration_manager: DecorationManager,
|
decoration_manager: DecorationManager,
|
||||||
input_manager: InputManager,
|
input_manager: InputManager,
|
||||||
output_manager: OutputManager,
|
output_manager: OutputManager,
|
||||||
@ -104,6 +106,8 @@ pub fn init(self: *Self) !void {
|
|||||||
self.xwayland.events.new_surface.add(&self.new_xwayland_surface);
|
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);
|
_ = try wlr.PrimarySelectionDeviceManagerV1.create(self.wl_server);
|
||||||
|
|
||||||
self.config = try Config.init();
|
self.config = try Config.init();
|
||||||
@ -135,8 +139,8 @@ pub fn deinit(self: *Self) void {
|
|||||||
|
|
||||||
self.root.deinit();
|
self.root.deinit();
|
||||||
|
|
||||||
self.wl_server.destroy();
|
|
||||||
self.noop_backend.destroy();
|
self.noop_backend.destroy();
|
||||||
|
self.wl_server.destroy();
|
||||||
|
|
||||||
self.input_manager.deinit();
|
self.input_manager.deinit();
|
||||||
self.config.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 Box = @import("Box.zig");
|
||||||
const Output = @import("Output.zig");
|
const Output = @import("Output.zig");
|
||||||
const Root = @import("Root.zig");
|
const Root = @import("Root.zig");
|
||||||
|
const Seat = @import("Seat.zig");
|
||||||
const ViewStack = @import("view_stack.zig").ViewStack;
|
const ViewStack = @import("view_stack.zig").ViewStack;
|
||||||
const XdgToplevel = @import("XdgToplevel.zig");
|
const XdgToplevel = @import("XdgToplevel.zig");
|
||||||
const XwaylandView = if (build_options.xwayland) @import("XwaylandView.zig") else @import("VoidView.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
|
/// The output this view is currently associated with
|
||||||
output: *Output,
|
output: *Output,
|
||||||
|
|
||||||
/// This is from the point where the view is mapped until the surface
|
/// This is non-null from the point where the view is mapped until the
|
||||||
/// is destroyed by wlroots.
|
/// surface is destroyed by wlroots.
|
||||||
surface: ?*wlr.Surface = null,
|
surface: ?*wlr.Surface = null,
|
||||||
|
|
||||||
/// This View struct outlasts the wlroots object it wraps. This bool is set to
|
/// 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,
|
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 {
|
pub fn init(self: *Self, output: *Output, tags: u32, surface: anytype) void {
|
||||||
self.* = .{
|
self.* = .{
|
||||||
.output = output,
|
.output = output,
|
||||||
@ -150,10 +157,19 @@ pub fn init(self: *Self, output: *Output, tags: u32, surface: anytype) void {
|
|||||||
pub fn destroy(self: *Self) void {
|
pub fn destroy(self: *Self) void {
|
||||||
self.dropSavedBuffers();
|
self.dropSavedBuffers();
|
||||||
self.saved_buffers.deinit();
|
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) {
|
switch (self.impl) {
|
||||||
.xdg_toplevel => |*xdg_toplevel| xdg_toplevel.deinit(),
|
.xdg_toplevel => |*xdg_toplevel| xdg_toplevel.deinit(),
|
||||||
.xwayland_view => |*xwayland_view| xwayland_view.deinit(),
|
.xwayland_view => |*xwayland_view| xwayland_view.deinit(),
|
||||||
}
|
}
|
||||||
|
|
||||||
const node = @fieldParentPtr(ViewStack(Self).Node, "view", self);
|
const node = @fieldParentPtr(ViewStack(Self).Node, "view", self);
|
||||||
self.output.views.remove(node);
|
self.output.views.remove(node);
|
||||||
util.gpa.destroy(node);
|
util.gpa.destroy(node);
|
||||||
@ -220,6 +236,10 @@ pub fn needsConfigure(self: Self) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn configure(self: Self) void {
|
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) {
|
switch (self.impl) {
|
||||||
.xdg_toplevel => |xdg_toplevel| xdg_toplevel.configure(),
|
.xdg_toplevel => |xdg_toplevel| xdg_toplevel.configure(),
|
||||||
.xwayland_view => |xwayland_view| xwayland_view.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.?.sendLeave(self.output.wlr_output);
|
||||||
self.surface.?.sendEnter(destination_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;
|
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.
|
/// Return the current title of the view if any.
|
||||||
pub fn getTitle(self: Self) [*:0]const u8 {
|
pub fn getTitle(self: Self) ?[*:0]const u8 {
|
||||||
return switch (self.impl) {
|
return switch (self.impl) {
|
||||||
.xdg_toplevel => |xdg_toplevel| xdg_toplevel.getTitle(),
|
.xdg_toplevel => |xdg_toplevel| xdg_toplevel.getTitle(),
|
||||||
.xwayland_view => |xwayland_view| xwayland_view.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
|
/// Clamp the width/height of the pending state to the constraints of the view
|
||||||
pub fn applyConstraints(self: *Self) void {
|
pub fn applyConstraints(self: *Self) void {
|
||||||
const constraints = self.getConstraints();
|
const constraints = self.getConstraints();
|
||||||
@ -383,6 +415,28 @@ pub fn map(self: *Self) void {
|
|||||||
|
|
||||||
log.debug(.server, "view '{}' mapped", .{self.getTitle()});
|
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
|
// Add the view to the stack of its output
|
||||||
const node = @fieldParentPtr(ViewStack(Self).Node, "view", self);
|
const node = @fieldParentPtr(ViewStack(Self).Node, "view", self);
|
||||||
self.output.views.attach(node, self.output.attach_mode);
|
self.output.views.attach(node, self.output.attach_mode);
|
||||||
@ -428,6 +482,28 @@ pub fn unmap(self: *Self) void {
|
|||||||
root.startTransaction();
|
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.
|
/// Change the opacity of a view by config.view_opacity_delta.
|
||||||
/// If the target opacity was reached, return true.
|
/// If the target opacity was reached, return true.
|
||||||
fn incrementOpacity(self: *Self) bool {
|
fn incrementOpacity(self: *Self) bool {
|
||||||
@ -491,3 +567,33 @@ pub fn commitOpacityTransition(self: *Self) void {
|
|||||||
self.attachOpacityTimer();
|
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;
|
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;
|
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.
|
/// Return the current title of the toplevel if any.
|
||||||
pub fn getTitle(self: Self) [*:0]const u8 {
|
pub fn getTitle(self: Self) ?[*:0]const u8 {
|
||||||
return self.xdg_surface.role_data.toplevel.title orelse "NULL";
|
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.
|
/// 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
|
/// Called when the client sets / updates its title
|
||||||
fn handleSetTitle(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurface) void {
|
fn handleSetTitle(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurface) void {
|
||||||
const self = @fieldParentPtr(Self, "set_title", listener);
|
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
|
/// Get the current title of the xwayland surface if any.
|
||||||
pub fn getTitle(self: Self) [*:0]const u8 {
|
pub fn getTitle(self: Self) ?[*:0]const u8 {
|
||||||
return self.xwayland_surface.title orelse "";
|
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
|
/// Return bounds on the dimensions of the view
|
||||||
|
Loading…
Reference in New Issue
Block a user