Make Root a toplevel struct
This commit is contained in:
parent
9ea26d2a30
commit
a73343c92f
@ -8,7 +8,7 @@ const render = @import("render.zig");
|
|||||||
const Box = @import("box.zig");
|
const Box = @import("box.zig");
|
||||||
const LayerSurface = @import("layer_surface.zig");
|
const LayerSurface = @import("layer_surface.zig");
|
||||||
const Log = @import("log.zig").Log;
|
const Log = @import("log.zig").Log;
|
||||||
const Root = @import("root.zig").Root;
|
const Root = @import("root.zig");
|
||||||
const View = @import("view.zig");
|
const View = @import("view.zig");
|
||||||
const ViewStack = @import("view_stack.zig").ViewStack;
|
const ViewStack = @import("view_stack.zig").ViewStack;
|
||||||
|
|
||||||
|
392
src/root.zig
392
src/root.zig
@ -1,224 +1,220 @@
|
|||||||
const std = @import("std");
|
const Self = @This();
|
||||||
const c = @import("c.zig");
|
|
||||||
const util = @import("util.zig");
|
const std = @import("std");
|
||||||
|
|
||||||
|
const c = @import("c.zig");
|
||||||
|
|
||||||
const Box = @import("box.zig").Box;
|
|
||||||
const Log = @import("log.zig").Log;
|
const Log = @import("log.zig").Log;
|
||||||
const Output = @import("output.zig");
|
const Output = @import("output.zig");
|
||||||
const Server = @import("server.zig");
|
const Server = @import("server.zig");
|
||||||
const Seat = @import("seat.zig").Seat;
|
|
||||||
const View = @import("view.zig");
|
const View = @import("view.zig");
|
||||||
const ViewStack = @import("view_stack.zig").ViewStack;
|
const ViewStack = @import("view_stack.zig").ViewStack;
|
||||||
|
|
||||||
/// Responsible for all windowing operations
|
/// Responsible for all windowing operations
|
||||||
pub const Root = struct {
|
server: *Server,
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
server: *Server,
|
wlr_output_layout: *c.wlr_output_layout,
|
||||||
|
outputs: std.TailQueue(Output),
|
||||||
|
|
||||||
wlr_output_layout: *c.wlr_output_layout,
|
/// This output is used internally when no real outputs are available.
|
||||||
outputs: std.TailQueue(Output),
|
/// It is not advertised to clients.
|
||||||
|
noop_output: Output,
|
||||||
|
|
||||||
/// This output is used internally when no real outputs are available.
|
/// Number of pending configures sent in the current transaction.
|
||||||
/// It is not advertised to clients.
|
/// A value of 0 means there is no current transaction.
|
||||||
noop_output: Output,
|
pending_configures: u32,
|
||||||
|
|
||||||
/// Number of pending configures sent in the current transaction.
|
/// Handles timeout of transactions
|
||||||
/// A value of 0 means there is no current transaction.
|
transaction_timer: ?*c.wl_event_source,
|
||||||
pending_configures: u32,
|
|
||||||
|
|
||||||
/// Handles timeout of transactions
|
pub fn init(self: *Self, server: *Server) !void {
|
||||||
transaction_timer: ?*c.wl_event_source,
|
self.server = server;
|
||||||
|
|
||||||
pub fn init(self: *Self, server: *Server) !void {
|
// Create an output layout, which a wlroots utility for working with an
|
||||||
self.server = server;
|
// arrangement of screens in a physical layout.
|
||||||
|
self.wlr_output_layout = c.wlr_output_layout_create() orelse
|
||||||
|
return error.CantCreateWlrOutputLayout;
|
||||||
|
errdefer c.wlr_output_layout_destroy(self.wlr_output_layout);
|
||||||
|
|
||||||
// Create an output layout, which a wlroots utility for working with an
|
self.outputs = std.TailQueue(Output).init();
|
||||||
// arrangement of screens in a physical layout.
|
|
||||||
self.wlr_output_layout = c.wlr_output_layout_create() orelse
|
|
||||||
return error.CantCreateWlrOutputLayout;
|
|
||||||
errdefer c.wlr_output_layout_destroy(self.wlr_output_layout);
|
|
||||||
|
|
||||||
self.outputs = std.TailQueue(Output).init();
|
const noop_wlr_output = c.river_wlr_noop_add_output(server.noop_backend) orelse
|
||||||
|
return error.CantAddNoopOutput;
|
||||||
|
try self.noop_output.init(self, noop_wlr_output);
|
||||||
|
|
||||||
const noop_wlr_output = c.river_wlr_noop_add_output(server.noop_backend) orelse
|
self.pending_configures = 0;
|
||||||
return error.CantAddNoopOutput;
|
|
||||||
try self.noop_output.init(self, noop_wlr_output);
|
|
||||||
|
|
||||||
self.pending_configures = 0;
|
self.transaction_timer = null;
|
||||||
|
}
|
||||||
|
|
||||||
self.transaction_timer = null;
|
pub fn deinit(self: *Self) void {
|
||||||
|
while (self.outputs.pop()) |output_node| {
|
||||||
|
output_node.data.deinit();
|
||||||
|
self.server.allocator.destroy(output_node);
|
||||||
}
|
}
|
||||||
|
c.wlr_output_layout_destroy(self.wlr_output_layout);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn addOutput(self: *Self, wlr_output: *c.wlr_output) void {
|
||||||
while (self.outputs.pop()) |output_node| {
|
// TODO: Handle failure
|
||||||
output_node.data.deinit();
|
const node = self.outputs.allocateNode(self.server.allocator) catch unreachable;
|
||||||
self.server.allocator.destroy(output_node);
|
node.data.init(self, wlr_output) catch unreachable;
|
||||||
}
|
self.outputs.append(node);
|
||||||
c.wlr_output_layout_destroy(self.wlr_output_layout);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn addOutput(self: *Self, wlr_output: *c.wlr_output) void {
|
// if we previously had no real outputs, move focus from the noop output
|
||||||
// TODO: Handle failure
|
// to the new one.
|
||||||
const node = self.outputs.allocateNode(self.server.allocator) catch unreachable;
|
if (self.outputs.len == 1) {
|
||||||
node.data.init(self, wlr_output) catch unreachable;
|
// TODO: move views from the noop output to the new one and focus(null)
|
||||||
self.outputs.append(node);
|
|
||||||
|
|
||||||
// if we previously had no real outputs, move focus from the noop output
|
|
||||||
// to the new one.
|
|
||||||
if (self.outputs.len == 1) {
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clear the current focus.
|
|
||||||
pub fn clearFocus(self: *Self) void {
|
|
||||||
if (self.focused_view) |view| {
|
|
||||||
_ = c.wlr_xdg_toplevel_set_activated(view.wlr_xdg_surface, false);
|
|
||||||
}
|
|
||||||
self.focused_view = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Arrange all views on all outputs and then start a transaction.
|
|
||||||
pub fn arrange(self: *Self) void {
|
|
||||||
var it = self.outputs.first;
|
|
||||||
while (it) |output_node| : (it = output_node.next) {
|
|
||||||
output_node.data.arrangeViews();
|
|
||||||
}
|
|
||||||
self.startTransaction();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Initiate an atomic change to the layout. This change will not be
|
|
||||||
/// applied until all affected clients ack a configure and commit a buffer.
|
|
||||||
fn startTransaction(self: *Self) void {
|
|
||||||
// If a new transaction is started while another is in progress, we need
|
|
||||||
// to reset the pending count to 0 and clear serials from the views
|
|
||||||
self.pending_configures = 0;
|
|
||||||
|
|
||||||
// Iterate over all views of all outputs
|
|
||||||
var output_it = self.outputs.first;
|
|
||||||
while (output_it) |node| : (output_it = node.next) {
|
|
||||||
const output = &node.data;
|
|
||||||
var view_it = ViewStack(View).iterator(output.views.first, 0xFFFFFFFF);
|
|
||||||
while (view_it.next()) |view_node| {
|
|
||||||
const view = &view_node.view;
|
|
||||||
// Clear the serial in case this transaction is interrupting a prior one.
|
|
||||||
view.pending_serial = null;
|
|
||||||
|
|
||||||
if (view.needsConfigure()) {
|
|
||||||
view.configure();
|
|
||||||
self.pending_configures += 1;
|
|
||||||
|
|
||||||
// We save the current buffer, so we can send an early
|
|
||||||
// frame done event to give the client a head start on
|
|
||||||
// redrawing.
|
|
||||||
view.sendFrameDone();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is a saved buffer present, then this transaction is interrupting
|
|
||||||
// a previous transaction and we should keep the old buffer.
|
|
||||||
if (view.stashed_buffer == null) {
|
|
||||||
view.stashBuffer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.pending_configures > 0) {
|
|
||||||
Log.Debug.log(
|
|
||||||
"Started transaction with {} pending configures.",
|
|
||||||
.{self.pending_configures},
|
|
||||||
);
|
|
||||||
|
|
||||||
// TODO: log failure to create timer and commit immediately
|
|
||||||
self.transaction_timer = c.wl_event_loop_add_timer(
|
|
||||||
self.server.wl_event_loop,
|
|
||||||
handleTimeout,
|
|
||||||
self,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Set timeout to 200ms
|
|
||||||
if (c.wl_event_source_timer_update(self.transaction_timer, 200) == -1) {
|
|
||||||
// TODO: handle failure
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.commitTransaction();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handleTimeout(data: ?*c_void) callconv(.C) c_int {
|
|
||||||
const root = @ptrCast(*Root, @alignCast(@alignOf(*Root), data));
|
|
||||||
|
|
||||||
Log.Error.log("Transaction timed out. Some imperfect frames may be shown.", .{});
|
|
||||||
|
|
||||||
root.commitTransaction();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn notifyConfigured(self: *Self) void {
|
|
||||||
self.pending_configures -= 1;
|
|
||||||
if (self.pending_configures == 0) {
|
|
||||||
// Stop the timer, as we didn't timeout
|
|
||||||
if (c.wl_event_source_timer_update(self.transaction_timer, 0) == -1) {
|
|
||||||
// TODO: handle failure
|
|
||||||
}
|
|
||||||
self.commitTransaction();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Apply the pending state and drop stashed buffers. This means that
|
|
||||||
/// the next frame drawn will be the post-transaction state of the
|
|
||||||
/// layout. Should only be called after all clients have configured for
|
|
||||||
/// the new layout. If called early imperfect frames may be drawn.
|
|
||||||
fn commitTransaction(self: *Self) void {
|
|
||||||
// TODO: apply damage properly
|
|
||||||
|
|
||||||
// Ensure this is set to 0 to avoid entering invalid state (e.g. if called due to timeout)
|
|
||||||
self.pending_configures = 0;
|
|
||||||
|
|
||||||
// Iterate over all views of all outputs
|
|
||||||
var output_it = self.outputs.first;
|
|
||||||
while (output_it) |output_node| : (output_it = output_node.next) {
|
|
||||||
const output = &output_node.data;
|
|
||||||
|
|
||||||
// If there were pending focused tags, make them the current focus
|
|
||||||
if (output.pending_focused_tags) |tags| {
|
|
||||||
Log.Debug.log(
|
|
||||||
"changing current focus: {b:0>10} to {b:0>10}",
|
|
||||||
.{ output.current_focused_tags, tags },
|
|
||||||
);
|
|
||||||
output.current_focused_tags = tags;
|
|
||||||
output.pending_focused_tags = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var view_it = ViewStack(View).iterator(output.views.first, 0xFFFFFFFF);
|
|
||||||
while (view_it.next()) |view_node| {
|
|
||||||
const view = &view_node.view;
|
|
||||||
// Ensure that all pending state is cleared
|
|
||||||
view.pending_serial = null;
|
|
||||||
if (view.pending_box) |state| {
|
|
||||||
view.current_box = state;
|
|
||||||
view.pending_box = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply possible pending tags
|
|
||||||
if (view.pending_tags) |tags| {
|
|
||||||
view.current_tags = tags;
|
|
||||||
view.pending_tags = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
view.dropStashedBuffer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate over all seats and update focus
|
|
||||||
var it = self.server.input_manager.seats.first;
|
var it = self.server.input_manager.seats.first;
|
||||||
while (it) |seat_node| : (it = seat_node.next) {
|
while (it) |seat_node| : (it = seat_node.next) {
|
||||||
seat_node.data.focus(null);
|
seat_node.data.focused_output = &self.outputs.first.?.data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
/// Clear the current focus.
|
||||||
|
pub fn clearFocus(self: *Self) void {
|
||||||
|
if (self.focused_view) |view| {
|
||||||
|
_ = c.wlr_xdg_toplevel_set_activated(view.wlr_xdg_surface, false);
|
||||||
|
}
|
||||||
|
self.focused_view = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Arrange all views on all outputs and then start a transaction.
|
||||||
|
pub fn arrange(self: *Self) void {
|
||||||
|
var it = self.outputs.first;
|
||||||
|
while (it) |output_node| : (it = output_node.next) {
|
||||||
|
output_node.data.arrangeViews();
|
||||||
|
}
|
||||||
|
self.startTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initiate an atomic change to the layout. This change will not be
|
||||||
|
/// applied until all affected clients ack a configure and commit a buffer.
|
||||||
|
fn startTransaction(self: *Self) void {
|
||||||
|
// If a new transaction is started while another is in progress, we need
|
||||||
|
// to reset the pending count to 0 and clear serials from the views
|
||||||
|
self.pending_configures = 0;
|
||||||
|
|
||||||
|
// Iterate over all views of all outputs
|
||||||
|
var output_it = self.outputs.first;
|
||||||
|
while (output_it) |node| : (output_it = node.next) {
|
||||||
|
const output = &node.data;
|
||||||
|
var view_it = ViewStack(View).iterator(output.views.first, 0xFFFFFFFF);
|
||||||
|
while (view_it.next()) |view_node| {
|
||||||
|
const view = &view_node.view;
|
||||||
|
// Clear the serial in case this transaction is interrupting a prior one.
|
||||||
|
view.pending_serial = null;
|
||||||
|
|
||||||
|
if (view.needsConfigure()) {
|
||||||
|
view.configure();
|
||||||
|
self.pending_configures += 1;
|
||||||
|
|
||||||
|
// We save the current buffer, so we can send an early
|
||||||
|
// frame done event to give the client a head start on
|
||||||
|
// redrawing.
|
||||||
|
view.sendFrameDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is a saved buffer present, then this transaction is interrupting
|
||||||
|
// a previous transaction and we should keep the old buffer.
|
||||||
|
if (view.stashed_buffer == null) {
|
||||||
|
view.stashBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.pending_configures > 0) {
|
||||||
|
Log.Debug.log(
|
||||||
|
"Started transaction with {} pending configures.",
|
||||||
|
.{self.pending_configures},
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: log failure to create timer and commit immediately
|
||||||
|
self.transaction_timer = c.wl_event_loop_add_timer(
|
||||||
|
self.server.wl_event_loop,
|
||||||
|
handleTimeout,
|
||||||
|
self,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set timeout to 200ms
|
||||||
|
if (c.wl_event_source_timer_update(self.transaction_timer, 200) == -1) {
|
||||||
|
// TODO: handle failure
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.commitTransaction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleTimeout(data: ?*c_void) callconv(.C) c_int {
|
||||||
|
const self = @ptrCast(*Self, @alignCast(@alignOf(*Self), data));
|
||||||
|
|
||||||
|
Log.Error.log("Transaction timed out. Some imperfect frames may be shown.", .{});
|
||||||
|
|
||||||
|
self.commitTransaction();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn notifyConfigured(self: *Self) void {
|
||||||
|
self.pending_configures -= 1;
|
||||||
|
if (self.pending_configures == 0) {
|
||||||
|
// Stop the timer, as we didn't timeout
|
||||||
|
if (c.wl_event_source_timer_update(self.transaction_timer, 0) == -1) {
|
||||||
|
// TODO: handle failure
|
||||||
|
}
|
||||||
|
self.commitTransaction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply the pending state and drop stashed buffers. This means that
|
||||||
|
/// the next frame drawn will be the post-transaction state of the
|
||||||
|
/// layout. Should only be called after all clients have configured for
|
||||||
|
/// the new layout. If called early imperfect frames may be drawn.
|
||||||
|
fn commitTransaction(self: *Self) void {
|
||||||
|
// TODO: apply damage properly
|
||||||
|
|
||||||
|
// Ensure this is set to 0 to avoid entering invalid state (e.g. if called due to timeout)
|
||||||
|
self.pending_configures = 0;
|
||||||
|
|
||||||
|
// Iterate over all views of all outputs
|
||||||
|
var output_it = self.outputs.first;
|
||||||
|
while (output_it) |output_node| : (output_it = output_node.next) {
|
||||||
|
const output = &output_node.data;
|
||||||
|
|
||||||
|
// If there were pending focused tags, make them the current focus
|
||||||
|
if (output.pending_focused_tags) |tags| {
|
||||||
|
Log.Debug.log(
|
||||||
|
"changing current focus: {b:0>10} to {b:0>10}",
|
||||||
|
.{ output.current_focused_tags, tags },
|
||||||
|
);
|
||||||
|
output.current_focused_tags = tags;
|
||||||
|
output.pending_focused_tags = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var view_it = ViewStack(View).iterator(output.views.first, 0xFFFFFFFF);
|
||||||
|
while (view_it.next()) |view_node| {
|
||||||
|
const view = &view_node.view;
|
||||||
|
// Ensure that all pending state is cleared
|
||||||
|
view.pending_serial = null;
|
||||||
|
if (view.pending_box) |state| {
|
||||||
|
view.current_box = state;
|
||||||
|
view.pending_box = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply possible pending tags
|
||||||
|
if (view.pending_tags) |tags| {
|
||||||
|
view.current_tags = tags;
|
||||||
|
view.pending_tags = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
view.dropStashedBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over all seats and update focus
|
||||||
|
var it = self.server.input_manager.seats.first;
|
||||||
|
while (it) |seat_node| : (it = seat_node.next) {
|
||||||
|
seat_node.data.focus(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -9,7 +9,7 @@ const DecorationManager = @import("decoration_manager.zig");
|
|||||||
const InputManager = @import("input_manager.zig");
|
const InputManager = @import("input_manager.zig");
|
||||||
const Log = @import("log.zig").Log;
|
const Log = @import("log.zig").Log;
|
||||||
const Output = @import("output.zig");
|
const Output = @import("output.zig");
|
||||||
const Root = @import("root.zig").Root;
|
const Root = @import("root.zig");
|
||||||
const View = @import("view.zig");
|
const View = @import("view.zig");
|
||||||
const ViewStack = @import("view_stack.zig").ViewStack;
|
const ViewStack = @import("view_stack.zig").ViewStack;
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ const c = @import("c.zig");
|
|||||||
const Box = @import("box.zig");
|
const Box = @import("box.zig");
|
||||||
const Log = @import("log.zig").Log;
|
const Log = @import("log.zig").Log;
|
||||||
const Output = @import("output.zig");
|
const Output = @import("output.zig");
|
||||||
const Root = @import("root.zig").Root;
|
const Root = @import("root.zig");
|
||||||
const ViewStack = @import("view_stack.zig").ViewStack;
|
const ViewStack = @import("view_stack.zig").ViewStack;
|
||||||
const XdgToplevel = @import("xdg_toplevel.zig");
|
const XdgToplevel = @import("xdg_toplevel.zig");
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user