render: use wlr_scene to render views
This commit is contained in:
parent
07294057cb
commit
4f0ce8fceb
@ -885,7 +885,7 @@ fn xwaylandOverrideRedirectSurfaceAt(lx: f64, ly: f64) ?SurfaceAtResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn surfaceAtFilter(view: *View, filter_tags: u32) bool {
|
fn surfaceAtFilter(view: *View, filter_tags: u32) bool {
|
||||||
return view.surface != null and view.current.tags & filter_tags != 0;
|
return view.tree.node.enabled and view.current.tags & filter_tags != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enterMode(self: *Self, mode: enum { move, resize }, view: *View) void {
|
pub fn enterMode(self: *Self, mode: enum { move, resize }, view: *View) void {
|
||||||
|
@ -64,6 +64,10 @@ layers: [4]std.TailQueue(LayerSurface) = [1]std.TailQueue(LayerSurface){.{}} **
|
|||||||
/// TODO: this should be part of the output's State
|
/// TODO: this should be part of the output's State
|
||||||
usable_box: wlr.Box,
|
usable_box: wlr.Box,
|
||||||
|
|
||||||
|
/// Scene node representing the entire output.
|
||||||
|
/// Position must be updated when the output is moved in the layout.
|
||||||
|
tree: *wlr.SceneTree,
|
||||||
|
|
||||||
/// The top of the stack is the "most important" view.
|
/// The top of the stack is the "most important" view.
|
||||||
views: ViewStack(View) = .{},
|
views: ViewStack(View) = .{},
|
||||||
|
|
||||||
@ -144,6 +148,7 @@ pub fn init(self: *Self, wlr_output: *wlr.Output) !void {
|
|||||||
|
|
||||||
self.* = .{
|
self.* = .{
|
||||||
.wlr_output = wlr_output,
|
.wlr_output = wlr_output,
|
||||||
|
.tree = try server.root.scene.tree.createSceneTree(),
|
||||||
.usable_box = undefined,
|
.usable_box = undefined,
|
||||||
};
|
};
|
||||||
wlr_output.data = @ptrToInt(self);
|
wlr_output.data = @ptrToInt(self);
|
||||||
@ -208,7 +213,7 @@ pub fn sendLayoutNameClear(self: Self) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn arrangeFilter(view: *View, filter_tags: u32) bool {
|
pub fn arrangeFilter(view: *View, filter_tags: u32) bool {
|
||||||
return view.surface != null and !view.pending.float and !view.pending.fullscreen and
|
return view.tree.node.enabled and !view.pending.float and !view.pending.fullscreen and
|
||||||
view.pending.tags & filter_tags != 0;
|
view.pending.tags & filter_tags != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ pub fn sendViewTags(self: Self) void {
|
|||||||
|
|
||||||
var it = self.output.views.first;
|
var it = self.output.views.first;
|
||||||
while (it) |node| : (it = node.next) {
|
while (it) |node| : (it = node.next) {
|
||||||
if (node.view.surface == null) continue;
|
if (!node.view.tree.node.enabled) continue;
|
||||||
view_tags.append(node.view.current.tags) catch {
|
view_tags.append(node.view.current.tags) catch {
|
||||||
self.output_status.postNoMemory();
|
self.output_status.postNoMemory();
|
||||||
log.err("out of memory", .{});
|
log.err("out of memory", .{});
|
||||||
|
@ -32,6 +32,8 @@ const ViewStack = @import("view_stack.zig").ViewStack;
|
|||||||
const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
|
const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
|
||||||
const DragIcon = @import("DragIcon.zig");
|
const DragIcon = @import("DragIcon.zig");
|
||||||
|
|
||||||
|
scene: *wlr.Scene,
|
||||||
|
|
||||||
new_output: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleNewOutput),
|
new_output: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleNewOutput),
|
||||||
|
|
||||||
output_layout: *wlr.OutputLayout,
|
output_layout: *wlr.OutputLayout,
|
||||||
@ -77,6 +79,11 @@ pub fn init(self: *Self) !void {
|
|||||||
const output_layout = try wlr.OutputLayout.create();
|
const output_layout = try wlr.OutputLayout.create();
|
||||||
errdefer output_layout.destroy();
|
errdefer output_layout.destroy();
|
||||||
|
|
||||||
|
const scene = try wlr.Scene.create();
|
||||||
|
errdefer scene.tree.node.destroy();
|
||||||
|
|
||||||
|
try scene.attachOutputLayout(output_layout);
|
||||||
|
|
||||||
_ = try wlr.XdgOutputManagerV1.create(server.wl_server, output_layout);
|
_ = try wlr.XdgOutputManagerV1.create(server.wl_server, output_layout);
|
||||||
|
|
||||||
const event_loop = server.wl_server.getEventLoop();
|
const event_loop = server.wl_server.getEventLoop();
|
||||||
@ -85,12 +92,14 @@ pub fn init(self: *Self) !void {
|
|||||||
|
|
||||||
const noop_wlr_output = try server.headless_backend.headlessAddOutput(1920, 1080);
|
const noop_wlr_output = try server.headless_backend.headlessAddOutput(1920, 1080);
|
||||||
self.* = .{
|
self.* = .{
|
||||||
|
.scene = scene,
|
||||||
.output_layout = output_layout,
|
.output_layout = output_layout,
|
||||||
.output_manager = try wlr.OutputManagerV1.create(server.wl_server),
|
.output_manager = try wlr.OutputManagerV1.create(server.wl_server),
|
||||||
.power_manager = try wlr.OutputPowerManagerV1.create(server.wl_server),
|
.power_manager = try wlr.OutputPowerManagerV1.create(server.wl_server),
|
||||||
.transaction_timer = transaction_timer,
|
.transaction_timer = transaction_timer,
|
||||||
.noop_output = .{
|
.noop_output = .{
|
||||||
.wlr_output = noop_wlr_output,
|
.wlr_output = noop_wlr_output,
|
||||||
|
.tree = try scene.tree.createSceneTree(),
|
||||||
.usable_box = .{ .x = 0, .y = 0, .width = 0, .height = 0 },
|
.usable_box = .{ .x = 0, .y = 0, .width = 0, .height = 0 },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -104,6 +113,7 @@ pub fn init(self: *Self) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *Self) void {
|
||||||
|
self.scene.tree.node.destroy();
|
||||||
self.output_layout.destroy();
|
self.output_layout.destroy();
|
||||||
self.transaction_timer.remove();
|
self.transaction_timer.remove();
|
||||||
}
|
}
|
||||||
@ -204,7 +214,11 @@ pub fn addOutput(self: *Self, output: *Output) void {
|
|||||||
// This aarranges outputs from left-to-right in the order they appear. The
|
// This aarranges outputs from left-to-right in the order they appear. The
|
||||||
// wlr-output-management protocol may be used to modify this arrangement.
|
// wlr-output-management protocol may be used to modify this arrangement.
|
||||||
// This also creates a wl_output global which is advertised to clients.
|
// This also creates a wl_output global which is advertised to clients.
|
||||||
self.output_layout.addAuto(node.data.wlr_output);
|
self.output_layout.addAuto(output.wlr_output);
|
||||||
|
|
||||||
|
const layout_output = self.output_layout.get(output.wlr_output).?;
|
||||||
|
output.tree.node.setEnabled(true);
|
||||||
|
output.tree.node.setPosition(layout_output.x, layout_output.y);
|
||||||
|
|
||||||
// If we previously had no real outputs, move focus from the noop output
|
// If we previously had no real outputs, move focus from the noop output
|
||||||
// to the new one.
|
// to the new one.
|
||||||
@ -271,7 +285,7 @@ pub fn startTransaction(self: *Self) void {
|
|||||||
while (view_it) |view_node| : (view_it = view_node.next) {
|
while (view_it) |view_node| : (view_it = view_node.next) {
|
||||||
const view = &view_node.view;
|
const view = &view_node.view;
|
||||||
|
|
||||||
if (view.surface == null) continue;
|
if (!view.tree.node.enabled) continue;
|
||||||
|
|
||||||
if (view.shouldTrackConfigure()) {
|
if (view.shouldTrackConfigure()) {
|
||||||
// Clear the serial in case this transaction is interrupting a prior one.
|
// Clear the serial in case this transaction is interrupting a prior one.
|
||||||
@ -366,7 +380,7 @@ fn commitTransaction(self: *Self) void {
|
|||||||
const view = &view_node.view;
|
const view = &view_node.view;
|
||||||
view_it = view_node.next;
|
view_it = view_node.next;
|
||||||
|
|
||||||
if (view.surface == null) {
|
if (!view.tree.node.enabled) {
|
||||||
view.dropSavedBuffers();
|
view.dropSavedBuffers();
|
||||||
view.output.views.remove(view_node);
|
view.output.views.remove(view_node);
|
||||||
if (view.destroying) view.destroy();
|
if (view.destroying) view.destroy();
|
||||||
@ -383,6 +397,8 @@ fn commitTransaction(self: *Self) void {
|
|||||||
if (view.pending.urgent and view_tags_changed) urgent_tags_dirty = true;
|
if (view.pending.urgent and view_tags_changed) urgent_tags_dirty = true;
|
||||||
view.current = view.pending;
|
view.current = view.pending;
|
||||||
|
|
||||||
|
view.tree.node.setPosition(view.current.box.x, view.current.box.y);
|
||||||
|
|
||||||
view.dropSavedBuffers();
|
view.dropSavedBuffers();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -459,10 +475,13 @@ fn processOutputConfig(
|
|||||||
if (head.state.enabled) {
|
if (head.state.enabled) {
|
||||||
// Just updates the output's position if it is already in the layout
|
// Just updates the output's position if it is already in the layout
|
||||||
self.output_layout.add(output.wlr_output, head.state.x, head.state.y);
|
self.output_layout.add(output.wlr_output, head.state.x, head.state.y);
|
||||||
|
output.tree.node.setEnabled(true);
|
||||||
|
output.tree.node.setPosition(head.state.x, head.state.y);
|
||||||
output.arrangeLayers(.mapped);
|
output.arrangeLayers(.mapped);
|
||||||
} else {
|
} else {
|
||||||
self.removeOutput(output);
|
self.removeOutput(output);
|
||||||
self.output_layout.remove(output.wlr_output);
|
self.output_layout.remove(output.wlr_output);
|
||||||
|
output.tree.node.setEnabled(false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
std.log.scoped(.output_manager).err("failed to apply config to output {s}", .{
|
std.log.scoped(.output_manager).err("failed to apply config to output {s}", .{
|
||||||
|
@ -210,7 +210,7 @@ pub fn focus(self: *Self, _target: ?*View) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn pendingFilter(view: *View, filter_tags: u32) bool {
|
fn pendingFilter(view: *View, filter_tags: u32) bool {
|
||||||
return view.surface != null and view.pending.tags & filter_tags != 0;
|
return view.tree.node.enabled and view.pending.tags & filter_tags != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Switch focus to the target, handling unfocus and input inhibition
|
/// Switch focus to the target, handling unfocus and input inhibition
|
||||||
@ -222,7 +222,7 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
|
|||||||
|
|
||||||
// Obtain the target surface
|
// Obtain the target surface
|
||||||
const target_surface = switch (new_focus) {
|
const target_surface = switch (new_focus) {
|
||||||
.view => |target_view| target_view.surface.?,
|
.view => |target_view| target_view.rootSurface(),
|
||||||
.xwayland_override_redirect => |target_or| target_or.xwayland_surface.surface,
|
.xwayland_override_redirect => |target_or| target_or.xwayland_surface.surface,
|
||||||
.layer => |target_layer| target_layer.wlr_layer_surface.surface,
|
.layer => |target_layer| target_layer.wlr_layer_surface.surface,
|
||||||
.lock_surface => |lock_surface| lock_surface.wlr_lock_surface.surface,
|
.lock_surface => |lock_surface| lock_surface.wlr_lock_surface.surface,
|
||||||
|
@ -48,9 +48,7 @@ const Impl = union(enum) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const State = struct {
|
const State = struct {
|
||||||
/// The output-relative effective coordinates and effective dimensions of the view. The
|
/// The output-relative coordinates of the view and dimensions requested by river.
|
||||||
/// surface itself may have other dimensions which are stored in the
|
|
||||||
/// surface_box member.
|
|
||||||
box: wlr.Box = wlr.Box{ .x = 0, .y = 0, .width = 0, .height = 0 },
|
box: wlr.Box = wlr.Box{ .x = 0, .y = 0, .width = 0, .height = 0 },
|
||||||
|
|
||||||
/// The tags of the view, as a bitmask
|
/// The tags of the view, as a bitmask
|
||||||
@ -78,8 +76,7 @@ impl: Impl,
|
|||||||
/// The output this view is currently associated with
|
/// The output this view is currently associated with
|
||||||
output: *Output,
|
output: *Output,
|
||||||
|
|
||||||
/// This is non-null exactly when the view is mapped
|
tree: *wlr.SceneTree,
|
||||||
surface: ?*wlr.Surface = null,
|
|
||||||
|
|
||||||
/// This indicates that the view should be destroyed when the current
|
/// This indicates that the view should be destroyed when the current
|
||||||
/// transaction completes. See View.destroy()
|
/// transaction completes. See View.destroy()
|
||||||
@ -116,7 +113,7 @@ draw_borders: bool = true,
|
|||||||
request_activate: wl.Listener(*wlr.XdgActivationV1.event.RequestActivate) =
|
request_activate: wl.Listener(*wlr.XdgActivationV1.event.RequestActivate) =
|
||||||
wl.Listener(*wlr.XdgActivationV1.event.RequestActivate).init(handleRequestActivate),
|
wl.Listener(*wlr.XdgActivationV1.event.RequestActivate).init(handleRequestActivate),
|
||||||
|
|
||||||
pub fn init(self: *Self, output: *Output, impl: Impl) void {
|
pub fn init(self: *Self, output: *Output, tree: *wlr.SceneTree, impl: Impl) void {
|
||||||
const initial_tags = blk: {
|
const initial_tags = blk: {
|
||||||
const tags = output.current.tags & server.config.spawn_tagmask;
|
const tags = output.current.tags & server.config.spawn_tagmask;
|
||||||
break :blk if (tags != 0) tags else output.current.tags;
|
break :blk if (tags != 0) tags else output.current.tags;
|
||||||
@ -125,6 +122,7 @@ pub fn init(self: *Self, output: *Output, impl: Impl) void {
|
|||||||
self.* = .{
|
self.* = .{
|
||||||
.impl = impl,
|
.impl = impl,
|
||||||
.output = output,
|
.output = output,
|
||||||
|
.tree = tree,
|
||||||
.current = .{ .tags = initial_tags },
|
.current = .{ .tags = initial_tags },
|
||||||
.pending = .{ .tags = initial_tags },
|
.pending = .{ .tags = initial_tags },
|
||||||
};
|
};
|
||||||
@ -134,7 +132,6 @@ pub fn init(self: *Self, output: *Output, impl: Impl) void {
|
|||||||
/// mark this view for destruction when the transaction completes. Otherwise
|
/// mark this view for destruction when the transaction completes. Otherwise
|
||||||
/// destroy immediately.
|
/// destroy immediately.
|
||||||
pub fn destroy(self: *Self) void {
|
pub fn destroy(self: *Self) void {
|
||||||
assert(self.surface == null);
|
|
||||||
self.destroying = true;
|
self.destroying = true;
|
||||||
|
|
||||||
// If there are still saved buffers, then this view needs to be kept
|
// If there are still saved buffers, then this view needs to be kept
|
||||||
@ -210,10 +207,19 @@ fn lastSetFullscreenState(self: Self) bool {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn rootSurface(self: Self) *wlr.Surface {
|
||||||
|
assert(!self.destroying);
|
||||||
|
return switch (self.impl) {
|
||||||
|
.xdg_toplevel => |xdg_toplevel| xdg_toplevel.rootSurface(),
|
||||||
|
.xwayland_view => |xwayland_view| xwayland_view.rootSurface(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn sendFrameDone(self: Self) void {
|
pub fn sendFrameDone(self: Self) void {
|
||||||
|
assert(!self.destroying);
|
||||||
var now: os.timespec = undefined;
|
var now: os.timespec = undefined;
|
||||||
os.clock_gettime(os.CLOCK.MONOTONIC, &now) catch @panic("CLOCK_MONOTONIC not supported");
|
os.clock_gettime(os.CLOCK.MONOTONIC, &now) catch @panic("CLOCK_MONOTONIC not supported");
|
||||||
self.surface.?.sendFrameDone(&now);
|
self.rootSurface().sendFrameDone(&now);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dropSavedBuffers(self: *Self) void {
|
pub fn dropSavedBuffers(self: *Self) void {
|
||||||
@ -267,12 +273,6 @@ pub fn sendToOutput(self: *Self, destination_output: *Output) void {
|
|||||||
destination_output.sendUrgentTags();
|
destination_output.sendUrgentTags();
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the view is mapped send enter/leave events
|
|
||||||
if (self.surface != null) {
|
|
||||||
self.sendLeave(self.output);
|
|
||||||
self.sendEnter(destination_output);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.output = destination_output;
|
self.output = destination_output;
|
||||||
|
|
||||||
var output_width: i32 = undefined;
|
var output_width: i32 = undefined;
|
||||||
@ -365,9 +365,8 @@ pub inline fn forEachSurface(
|
|||||||
.xdg_toplevel => |xdg_toplevel| {
|
.xdg_toplevel => |xdg_toplevel| {
|
||||||
xdg_toplevel.xdg_toplevel.base.forEachSurface(T, iterator, user_data);
|
xdg_toplevel.xdg_toplevel.base.forEachSurface(T, iterator, user_data);
|
||||||
},
|
},
|
||||||
.xwayland_view => {
|
.xwayland_view => |xwayland_view| {
|
||||||
assert(build_options.xwayland);
|
xwayland_view.xwayland_surface.surface.?.forEachSurface(T, iterator, user_data);
|
||||||
self.surface.?.forEachSurface(T, iterator, user_data);
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -489,9 +488,6 @@ pub fn unmap(self: *Self) void {
|
|||||||
|
|
||||||
if (self.saved_buffers.items.len == 0) self.saveBuffers();
|
if (self.saved_buffers.items.len == 0) self.saveBuffers();
|
||||||
|
|
||||||
assert(self.surface != null);
|
|
||||||
self.surface = null;
|
|
||||||
|
|
||||||
// Inform all seats that the view has been unmapped so they can handle focus
|
// Inform all seats that the view has been unmapped so they can handle focus
|
||||||
var it = server.input_manager.seats.first;
|
var it = server.input_manager.seats.first;
|
||||||
while (it) |seat_node| : (it = seat_node.next) seat_node.data.handleViewUnmap(self);
|
while (it) |seat_node| : (it = seat_node.next) seat_node.data.handleViewUnmap(self);
|
||||||
|
@ -62,7 +62,9 @@ pub fn create(output: *Output, xdg_toplevel: *wlr.XdgToplevel) error{OutOfMemory
|
|||||||
const node = try util.gpa.create(ViewStack(View).Node);
|
const node = try util.gpa.create(ViewStack(View).Node);
|
||||||
const view = &node.view;
|
const view = &node.view;
|
||||||
|
|
||||||
view.init(output, .{ .xdg_toplevel = .{
|
const tree = try output.tree.createSceneXdgSurface(xdg_toplevel.base);
|
||||||
|
|
||||||
|
view.init(output, tree, .{ .xdg_toplevel = .{
|
||||||
.view = view,
|
.view = view,
|
||||||
.xdg_toplevel = xdg_toplevel,
|
.xdg_toplevel = xdg_toplevel,
|
||||||
} });
|
} });
|
||||||
@ -99,6 +101,10 @@ pub fn lastSetFullscreenState(self: Self) bool {
|
|||||||
return self.xdg_toplevel.scheduled.fullscreen;
|
return self.xdg_toplevel.scheduled.fullscreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn rootSurface(self: Self) *wlr.Surface {
|
||||||
|
return self.xdg_toplevel.base.surface;
|
||||||
|
}
|
||||||
|
|
||||||
/// Close the view. This will lead to the unmap and destroy events being sent
|
/// Close the view. This will lead to the unmap and destroy events being sent
|
||||||
pub fn close(self: Self) void {
|
pub fn close(self: Self) void {
|
||||||
self.xdg_toplevel.sendClose();
|
self.xdg_toplevel.sendClose();
|
||||||
@ -190,9 +196,6 @@ fn handleMap(listener: *wl.Listener(void)) void {
|
|||||||
self.xdg_toplevel.scheduled.width = initial_box.width;
|
self.xdg_toplevel.scheduled.width = initial_box.width;
|
||||||
self.xdg_toplevel.scheduled.height = initial_box.height;
|
self.xdg_toplevel.scheduled.height = initial_box.height;
|
||||||
|
|
||||||
view.surface = self.xdg_toplevel.base.surface;
|
|
||||||
view.surface_box = initial_box;
|
|
||||||
|
|
||||||
// Also use the view's "natural" size as the initial regular dimensions,
|
// Also use the view's "natural" size as the initial regular dimensions,
|
||||||
// for the case that it does not get arranged by a lyaout.
|
// for the case that it does not get arranged by a lyaout.
|
||||||
view.pending.box = view.float_box;
|
view.pending.box = view.float_box;
|
||||||
@ -275,7 +278,10 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
|
|||||||
const self_tags_changed = view.pending.tags != view.current.tags;
|
const self_tags_changed = view.pending.tags != view.current.tags;
|
||||||
const urgent_tags_dirty = view.pending.urgent != view.current.urgent or
|
const urgent_tags_dirty = view.pending.urgent != view.current.urgent or
|
||||||
(view.pending.urgent and self_tags_changed);
|
(view.pending.urgent and self_tags_changed);
|
||||||
|
|
||||||
view.current = view.pending;
|
view.current = view.pending;
|
||||||
|
view.tree.node.setPosition(view.current.box.x, view.current.box.y);
|
||||||
|
|
||||||
if (self_tags_changed) view.output.sendViewTags();
|
if (self_tags_changed) view.output.sendViewTags();
|
||||||
if (urgent_tags_dirty) view.output.sendUrgentTags();
|
if (urgent_tags_dirty) view.output.sendUrgentTags();
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ fn handleUnmap(listener: *wl.Listener(*wlr.XwaylandSurface), _: *wlr.XwaylandSur
|
|||||||
focused.impl.xwayland_view.xwayland_surface.pid == self.xwayland_surface.pid and
|
focused.impl.xwayland_view.xwayland_surface.pid == self.xwayland_surface.pid and
|
||||||
seat.wlr_seat.keyboard_state.focused_surface == self.xwayland_surface.surface)
|
seat.wlr_seat.keyboard_state.focused_surface == self.xwayland_surface.surface)
|
||||||
{
|
{
|
||||||
seat.keyboardEnterOrLeave(focused.surface.?);
|
seat.keyboardEnterOrLeave(focused.rootSurface());
|
||||||
},
|
},
|
||||||
.xwayland_override_redirect => |focused| if (focused == self) seat.focus(null),
|
.xwayland_override_redirect => |focused| if (focused == self) seat.focus(null),
|
||||||
.layer, .lock_surface, .none => {},
|
.layer, .lock_surface, .none => {},
|
||||||
|
@ -67,7 +67,10 @@ pub fn create(output: *Output, xwayland_surface: *wlr.XwaylandSurface) error{Out
|
|||||||
const node = try util.gpa.create(ViewStack(View).Node);
|
const node = try util.gpa.create(ViewStack(View).Node);
|
||||||
const view = &node.view;
|
const view = &node.view;
|
||||||
|
|
||||||
view.init(output, .{ .xwayland_view = .{
|
// TODO actually render xwayland windows, not just an empty tree.
|
||||||
|
const tree = try output.tree.createSceneTree();
|
||||||
|
|
||||||
|
view.init(output, tree, .{ .xwayland_view = .{
|
||||||
.view = view,
|
.view = view,
|
||||||
.xwayland_surface = xwayland_surface,
|
.xwayland_surface = xwayland_surface,
|
||||||
} });
|
} });
|
||||||
@ -115,6 +118,11 @@ pub fn lastSetFullscreenState(self: Self) bool {
|
|||||||
return self.last_set_fullscreen_state;
|
return self.last_set_fullscreen_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn rootSurface(self: Self) *wlr.Surface {
|
||||||
|
// TODO This is probably not OK, understand when xwayland surfaces can be null.
|
||||||
|
return self.xwayland_surface.surface.?;
|
||||||
|
}
|
||||||
|
|
||||||
/// Close the view. This will lead to the unmap and destroy events being sent
|
/// Close the view. This will lead to the unmap and destroy events being sent
|
||||||
pub fn close(self: Self) void {
|
pub fn close(self: Self) void {
|
||||||
self.xwayland_surface.close();
|
self.xwayland_surface.close();
|
||||||
@ -198,14 +206,6 @@ pub fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface:
|
|||||||
xwayland_surface.events.request_fullscreen.add(&self.request_fullscreen);
|
xwayland_surface.events.request_fullscreen.add(&self.request_fullscreen);
|
||||||
xwayland_surface.events.request_minimize.add(&self.request_minimize);
|
xwayland_surface.events.request_minimize.add(&self.request_minimize);
|
||||||
|
|
||||||
view.surface = surface;
|
|
||||||
self.view.surface_box = .{
|
|
||||||
.x = 0,
|
|
||||||
.y = 0,
|
|
||||||
.width = surface.current.width,
|
|
||||||
.height = surface.current.height,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Use the view's "natural" size centered on the output as the default
|
// Use the view's "natural" size centered on the output as the default
|
||||||
// floating dimensions
|
// floating dimensions
|
||||||
view.float_box = .{
|
view.float_box = .{
|
||||||
@ -257,7 +257,7 @@ fn handleRequestConfigure(
|
|||||||
const self = @fieldParentPtr(Self, "request_configure", listener);
|
const self = @fieldParentPtr(Self, "request_configure", listener);
|
||||||
|
|
||||||
// If unmapped, let the client do whatever it wants
|
// If unmapped, let the client do whatever it wants
|
||||||
if (self.view.surface == null) {
|
if (!self.xwayland_surface.mapped) {
|
||||||
self.xwayland_surface.configure(event.x, event.y, event.width, event.height);
|
self.xwayland_surface.configure(event.x, event.y, event.width, event.height);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -70,5 +70,5 @@ pub fn focusView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn filter(view: *View, filter_tags: u32) bool {
|
fn filter(view: *View, filter_tags: u32) bool {
|
||||||
return view.surface != null and view.pending.tags & filter_tags != 0;
|
return view.tree.node.enabled and view.pending.tags & filter_tags != 0;
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,6 @@ pub fn swap(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn filter(view: *View, filter_tags: u32) bool {
|
fn filter(view: *View, filter_tags: u32) bool {
|
||||||
return view.surface != null and !view.pending.float and
|
return view.tree.node.enabled and !view.pending.float and
|
||||||
!view.pending.fullscreen and view.pending.tags & filter_tags != 0;
|
!view.pending.fullscreen and view.pending.tags & filter_tags != 0;
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,6 @@ pub fn zoom(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn filter(view: *View, filter_tags: u32) bool {
|
fn filter(view: *View, filter_tags: u32) bool {
|
||||||
return view.surface != null and !view.pending.float and
|
return view.tree.node.enabled and !view.pending.float and
|
||||||
!view.pending.fullscreen and view.pending.tags & filter_tags != 0;
|
!view.pending.fullscreen and view.pending.tags & filter_tags != 0;
|
||||||
}
|
}
|
||||||
|
401
river/render.zig
401
river/render.zig
@ -14,411 +14,22 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const build_options = @import("build_options");
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const os = std.os;
|
const os = std.os;
|
||||||
const wlr = @import("wlroots");
|
|
||||||
const wl = @import("wayland").server.wl;
|
|
||||||
const pixman = @import("pixman");
|
|
||||||
|
|
||||||
const server = &@import("main.zig").server;
|
const server = &@import("main.zig").server;
|
||||||
const util = @import("util.zig");
|
|
||||||
|
|
||||||
const LayerSurface = @import("LayerSurface.zig");
|
|
||||||
const Output = @import("Output.zig");
|
const Output = @import("Output.zig");
|
||||||
const Server = @import("Server.zig");
|
|
||||||
const View = @import("View.zig");
|
|
||||||
const ViewStack = @import("view_stack.zig").ViewStack;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.render);
|
const log = std.log.scoped(.render);
|
||||||
|
|
||||||
const SurfaceRenderData = struct {
|
|
||||||
output: *const Output,
|
|
||||||
|
|
||||||
/// In output layout coordinates relative to the output
|
|
||||||
output_x: i32,
|
|
||||||
output_y: i32,
|
|
||||||
|
|
||||||
when: *os.timespec,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The rendering order in this function must be kept in sync with Cursor.surfaceAt()
|
|
||||||
pub fn renderOutput(output: *Output) void {
|
pub fn renderOutput(output: *Output) void {
|
||||||
|
const scene_output = server.root.scene.getSceneOutput(output.wlr_output).?;
|
||||||
|
if (!scene_output.commit()) {
|
||||||
|
log.err("output commit failed for {s}", .{output.wlr_output.name});
|
||||||
|
}
|
||||||
|
|
||||||
var now: os.timespec = undefined;
|
var now: os.timespec = undefined;
|
||||||
os.clock_gettime(os.CLOCK.MONOTONIC, &now) catch @panic("CLOCK_MONOTONIC not supported");
|
os.clock_gettime(os.CLOCK.MONOTONIC, &now) catch @panic("CLOCK_MONOTONIC not supported");
|
||||||
|
scene_output.sendFrameDone(&now);
|
||||||
output.wlr_output.attachRender(null) catch {
|
|
||||||
log.err("failed to attach renderer", .{});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
server.renderer.begin(@intCast(u32, output.wlr_output.width), @intCast(u32, output.wlr_output.height));
|
|
||||||
|
|
||||||
// In order to avoid flashing a blank black screen as the session is locked
|
|
||||||
// continue to render the unlocked session until either a lock surface is
|
|
||||||
// created or waiting for lock surfaces times out.
|
|
||||||
if (server.lock_manager.state == .locked or
|
|
||||||
(server.lock_manager.state == .waiting_for_lock_surfaces and output.lock_surface != null) or
|
|
||||||
server.lock_manager.state == .waiting_for_blank)
|
|
||||||
{
|
|
||||||
server.renderer.clear(&[_]f32{ 0, 0, 0, 1 }); // solid black
|
|
||||||
|
|
||||||
// TODO: this isn't frame-perfect if the output mode is changed. We
|
|
||||||
// could possibly delay rendering new frames after the mode change
|
|
||||||
// until the surface commits a buffer of the correct size.
|
|
||||||
if (output.lock_surface) |lock_surface| {
|
|
||||||
var rdata = SurfaceRenderData{
|
|
||||||
.output = output,
|
|
||||||
.output_x = 0,
|
|
||||||
.output_y = 0,
|
|
||||||
.when = &now,
|
|
||||||
};
|
|
||||||
lock_surface.wlr_lock_surface.surface.forEachSurface(
|
|
||||||
*SurfaceRenderData,
|
|
||||||
renderSurfaceIterator,
|
|
||||||
&rdata,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderDragIcons(output, &now);
|
|
||||||
|
|
||||||
output.wlr_output.renderSoftwareCursors(null);
|
|
||||||
server.renderer.end();
|
|
||||||
output.wlr_output.commit() catch {
|
|
||||||
log.err("output commit failed for {s}", .{output.wlr_output.name});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (server.lock_manager.state == .locked) {
|
|
||||||
switch (output.lock_render_state) {
|
|
||||||
.unlocked, .pending_blank, .pending_lock_surface => unreachable,
|
|
||||||
.blanked, .lock_surface => {},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (output.lock_surface == null) {
|
|
||||||
output.lock_render_state = .pending_blank;
|
|
||||||
} else {
|
|
||||||
output.lock_render_state = .pending_lock_surface;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
output.lock_render_state = .unlocked;
|
|
||||||
|
|
||||||
// Find the first visible fullscreen view in the stack if there is one
|
|
||||||
var it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, renderFilter);
|
|
||||||
const fullscreen_view = while (it.next()) |view| {
|
|
||||||
if (view.current.fullscreen) break view;
|
|
||||||
} else null;
|
|
||||||
|
|
||||||
// If we have a fullscreen view to render, render it.
|
|
||||||
if (fullscreen_view) |view| {
|
|
||||||
// Always clear with solid black for fullscreen
|
|
||||||
server.renderer.clear(&[_]f32{ 0, 0, 0, 1 });
|
|
||||||
renderView(output, view, &now);
|
|
||||||
if (build_options.xwayland) renderXwaylandOverrideRedirect(output, &now);
|
|
||||||
} else {
|
|
||||||
// No fullscreen view, so render normal layers/views
|
|
||||||
server.renderer.clear(&server.config.background_color);
|
|
||||||
|
|
||||||
renderLayer(output, output.getLayer(.background).*, &now, .toplevels);
|
|
||||||
renderLayer(output, output.getLayer(.bottom).*, &now, .toplevels);
|
|
||||||
|
|
||||||
// The first view in the list is "on top" so always iterate in reverse.
|
|
||||||
|
|
||||||
// non-focused, non-floating views
|
|
||||||
it = ViewStack(View).iter(output.views.last, .reverse, output.current.tags, renderFilter);
|
|
||||||
while (it.next()) |view| {
|
|
||||||
if (view.current.focus != 0 or view.current.float) continue;
|
|
||||||
if (view.draw_borders) renderBorders(output, view);
|
|
||||||
renderView(output, view, &now);
|
|
||||||
}
|
|
||||||
|
|
||||||
// focused, non-floating views
|
|
||||||
it = ViewStack(View).iter(output.views.last, .reverse, output.current.tags, renderFilter);
|
|
||||||
while (it.next()) |view| {
|
|
||||||
if (view.current.focus == 0 or view.current.float) continue;
|
|
||||||
if (view.draw_borders) renderBorders(output, view);
|
|
||||||
renderView(output, view, &now);
|
|
||||||
}
|
|
||||||
|
|
||||||
// non-focused, floating views
|
|
||||||
it = ViewStack(View).iter(output.views.last, .reverse, output.current.tags, renderFilter);
|
|
||||||
while (it.next()) |view| {
|
|
||||||
if (view.current.focus != 0 or !view.current.float) continue;
|
|
||||||
if (view.draw_borders) renderBorders(output, view);
|
|
||||||
renderView(output, view, &now);
|
|
||||||
}
|
|
||||||
|
|
||||||
// focused, floating views
|
|
||||||
it = ViewStack(View).iter(output.views.last, .reverse, output.current.tags, renderFilter);
|
|
||||||
while (it.next()) |view| {
|
|
||||||
if (view.current.focus == 0 or !view.current.float) continue;
|
|
||||||
if (view.draw_borders) renderBorders(output, view);
|
|
||||||
renderView(output, view, &now);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (build_options.xwayland) renderXwaylandOverrideRedirect(output, &now);
|
|
||||||
|
|
||||||
renderLayer(output, output.getLayer(.top).*, &now, .toplevels);
|
|
||||||
|
|
||||||
renderLayer(output, output.getLayer(.background).*, &now, .popups);
|
|
||||||
renderLayer(output, output.getLayer(.bottom).*, &now, .popups);
|
|
||||||
renderLayer(output, output.getLayer(.top).*, &now, .popups);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The overlay layer is rendered in both fullscreen and normal cases
|
|
||||||
renderLayer(output, output.getLayer(.overlay).*, &now, .toplevels);
|
|
||||||
renderLayer(output, output.getLayer(.overlay).*, &now, .popups);
|
|
||||||
|
|
||||||
renderDragIcons(output, &now);
|
|
||||||
|
|
||||||
output.wlr_output.renderSoftwareCursors(null);
|
|
||||||
|
|
||||||
server.renderer.end();
|
|
||||||
|
|
||||||
output.wlr_output.commit() catch
|
|
||||||
log.err("output commit failed for {s}", .{output.wlr_output.name});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn renderFilter(view: *View, filter_tags: u32) bool {
|
|
||||||
// This check prevents a race condition when a frame is requested
|
|
||||||
// between mapping of a view and the first configure being handled.
|
|
||||||
if (view.current.box.width == 0 or view.current.box.height == 0)
|
|
||||||
return false;
|
|
||||||
return view.current.tags & filter_tags != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Render all surfaces on the passed layer
|
|
||||||
fn renderLayer(
|
|
||||||
output: *const Output,
|
|
||||||
layer: std.TailQueue(LayerSurface),
|
|
||||||
now: *os.timespec,
|
|
||||||
role: enum { toplevels, popups },
|
|
||||||
) void {
|
|
||||||
var it = layer.first;
|
|
||||||
while (it) |node| : (it = node.next) {
|
|
||||||
const layer_surface = &node.data;
|
|
||||||
var rdata = SurfaceRenderData{
|
|
||||||
.output = output,
|
|
||||||
.output_x = layer_surface.box.x,
|
|
||||||
.output_y = layer_surface.box.y,
|
|
||||||
.when = now,
|
|
||||||
};
|
|
||||||
switch (role) {
|
|
||||||
.toplevels => layer_surface.wlr_layer_surface.surface.forEachSurface(
|
|
||||||
*SurfaceRenderData,
|
|
||||||
renderSurfaceIterator,
|
|
||||||
&rdata,
|
|
||||||
),
|
|
||||||
.popups => layer_surface.wlr_layer_surface.forEachPopupSurface(
|
|
||||||
*SurfaceRenderData,
|
|
||||||
renderSurfaceIterator,
|
|
||||||
&rdata,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Render all surfaces in the view's surface tree, including subsurfaces and popups
|
|
||||||
fn renderView(output: *const Output, view: *View, now: *os.timespec) void {
|
|
||||||
// If we have saved buffers, we are in the middle of a transaction
|
|
||||||
// and need to render those buffers until the transaction is complete.
|
|
||||||
if (view.saved_buffers.items.len != 0) {
|
|
||||||
for (view.saved_buffers.items) |saved_buffer| {
|
|
||||||
const texture = saved_buffer.client_buffer.texture orelse continue;
|
|
||||||
renderTexture(
|
|
||||||
output,
|
|
||||||
texture,
|
|
||||||
.{
|
|
||||||
.x = saved_buffer.surface_box.x + view.current.box.x - view.saved_surface_box.x,
|
|
||||||
.y = saved_buffer.surface_box.y + view.current.box.y - view.saved_surface_box.y,
|
|
||||||
.width = saved_buffer.surface_box.width,
|
|
||||||
.height = saved_buffer.surface_box.height,
|
|
||||||
},
|
|
||||||
&saved_buffer.source_box,
|
|
||||||
saved_buffer.transform,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Since there are no stashed buffers, we are not in the middle of
|
|
||||||
// a transaction and may simply render the most recent buffers provided
|
|
||||||
// by the client.
|
|
||||||
var rdata = SurfaceRenderData{
|
|
||||||
.output = output,
|
|
||||||
.output_x = view.current.box.x - view.surface_box.x,
|
|
||||||
.output_y = view.current.box.y - view.surface_box.y,
|
|
||||||
.when = now,
|
|
||||||
};
|
|
||||||
view.forEachSurface(*SurfaceRenderData, renderSurfaceIterator, &rdata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn renderDragIcons(output: *const Output, now: *os.timespec) void {
|
|
||||||
var output_box: wlr.Box = undefined;
|
|
||||||
server.root.output_layout.getBox(output.wlr_output, &output_box);
|
|
||||||
|
|
||||||
var it = server.input_manager.seats.first;
|
|
||||||
while (it) |node| : (it = node.next) {
|
|
||||||
const icon = node.data.drag_icon orelse continue;
|
|
||||||
|
|
||||||
var lx: f64 = undefined;
|
|
||||||
var ly: f64 = undefined;
|
|
||||||
switch (icon.wlr_drag_icon.drag.grab_type) {
|
|
||||||
.keyboard_pointer => {
|
|
||||||
lx = icon.seat.cursor.wlr_cursor.x;
|
|
||||||
ly = icon.seat.cursor.wlr_cursor.y;
|
|
||||||
},
|
|
||||||
.keyboard_touch => {
|
|
||||||
const touch_id = icon.wlr_drag_icon.drag.touch_id;
|
|
||||||
const point = icon.seat.cursor.touch_points.get(touch_id) orelse continue;
|
|
||||||
lx = point.lx;
|
|
||||||
ly = point.ly;
|
|
||||||
},
|
|
||||||
.keyboard => unreachable,
|
|
||||||
}
|
|
||||||
|
|
||||||
var rdata = SurfaceRenderData{
|
|
||||||
.output = output,
|
|
||||||
.output_x = @floatToInt(i32, lx) + icon.sx - output_box.x,
|
|
||||||
.output_y = @floatToInt(i32, ly) + icon.sy - output_box.y,
|
|
||||||
.when = now,
|
|
||||||
};
|
|
||||||
icon.wlr_drag_icon.surface.forEachSurface(*SurfaceRenderData, renderSurfaceIterator, &rdata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Render all override redirect xwayland windows that appear on the output
|
|
||||||
fn renderXwaylandOverrideRedirect(output: *const Output, now: *os.timespec) void {
|
|
||||||
var output_box: wlr.Box = undefined;
|
|
||||||
server.root.output_layout.getBox(output.wlr_output, &output_box);
|
|
||||||
|
|
||||||
var it = server.root.xwayland_override_redirect_views.last;
|
|
||||||
while (it) |node| : (it = node.prev) {
|
|
||||||
const xwayland_surface = node.data.xwayland_surface;
|
|
||||||
|
|
||||||
var rdata = SurfaceRenderData{
|
|
||||||
.output = output,
|
|
||||||
.output_x = xwayland_surface.x - output_box.x,
|
|
||||||
.output_y = xwayland_surface.y - output_box.y,
|
|
||||||
.when = now,
|
|
||||||
};
|
|
||||||
xwayland_surface.surface.?.forEachSurface(*SurfaceRenderData, renderSurfaceIterator, &rdata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This function is passed to wlroots to render each surface during iteration
|
|
||||||
fn renderSurfaceIterator(
|
|
||||||
surface: *wlr.Surface,
|
|
||||||
surface_x: c_int,
|
|
||||||
surface_y: c_int,
|
|
||||||
rdata: *SurfaceRenderData,
|
|
||||||
) void {
|
|
||||||
const texture = surface.getTexture() orelse return;
|
|
||||||
|
|
||||||
var source_box: wlr.FBox = undefined;
|
|
||||||
surface.getBufferSourceBox(&source_box);
|
|
||||||
|
|
||||||
renderTexture(
|
|
||||||
rdata.output,
|
|
||||||
texture,
|
|
||||||
.{
|
|
||||||
.x = rdata.output_x + surface_x,
|
|
||||||
.y = rdata.output_y + surface_y,
|
|
||||||
.width = surface.current.width,
|
|
||||||
.height = surface.current.height,
|
|
||||||
},
|
|
||||||
&source_box,
|
|
||||||
surface.current.transform,
|
|
||||||
);
|
|
||||||
|
|
||||||
surface.sendFrameDone(rdata.when);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Render the given texture at the given box, taking the scale and transform
|
|
||||||
/// of the output into account.
|
|
||||||
fn renderTexture(
|
|
||||||
output: *const Output,
|
|
||||||
texture: *wlr.Texture,
|
|
||||||
dest_box: wlr.Box,
|
|
||||||
source_box: *const wlr.FBox,
|
|
||||||
transform: wl.Output.Transform,
|
|
||||||
) void {
|
|
||||||
var box = dest_box;
|
|
||||||
|
|
||||||
// Scale the box to the output's current scaling factor
|
|
||||||
scaleBox(&box, output.wlr_output.scale);
|
|
||||||
|
|
||||||
// wlr_matrix_project_box is a helper which takes a box with a desired
|
|
||||||
// x, y coordinates, width and height, and an output geometry, then
|
|
||||||
// prepares an orthographic projection and multiplies the necessary
|
|
||||||
// transforms to produce a model-view-projection matrix.
|
|
||||||
var matrix: [9]f32 = undefined;
|
|
||||||
const inverted = wlr.Output.transformInvert(transform);
|
|
||||||
wlr.matrix.projectBox(&matrix, &box, inverted, 0.0, &output.wlr_output.transform_matrix);
|
|
||||||
|
|
||||||
// This takes our matrix, the texture, and an alpha, and performs the actual
|
|
||||||
// rendering on the GPU.
|
|
||||||
server.renderer.renderSubtextureWithMatrix(texture, source_box, &matrix, 1.0) catch return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn renderBorders(output: *const Output, view: *View) void {
|
|
||||||
const config = &server.config;
|
|
||||||
const color = blk: {
|
|
||||||
if (view.current.urgent) break :blk &config.border_color_urgent;
|
|
||||||
if (view.current.focus != 0) break :blk &config.border_color_focused;
|
|
||||||
break :blk &config.border_color_unfocused;
|
|
||||||
};
|
|
||||||
const actual_box = if (view.saved_buffers.items.len != 0) view.saved_surface_box else view.surface_box;
|
|
||||||
|
|
||||||
var border: wlr.Box = undefined;
|
|
||||||
|
|
||||||
// left and right, covering the corners as well
|
|
||||||
border.y = view.current.box.y - config.border_width;
|
|
||||||
border.width = config.border_width;
|
|
||||||
border.height = actual_box.height + config.border_width * 2;
|
|
||||||
|
|
||||||
// left
|
|
||||||
border.x = view.current.box.x - config.border_width;
|
|
||||||
renderRect(output, border, color);
|
|
||||||
|
|
||||||
// right
|
|
||||||
border.x = view.current.box.x + actual_box.width;
|
|
||||||
renderRect(output, border, color);
|
|
||||||
|
|
||||||
// top and bottom
|
|
||||||
border.x = view.current.box.x;
|
|
||||||
border.width = actual_box.width;
|
|
||||||
border.height = config.border_width;
|
|
||||||
|
|
||||||
// top
|
|
||||||
border.y = view.current.box.y - config.border_width;
|
|
||||||
renderRect(output, border, color);
|
|
||||||
|
|
||||||
// bottom border
|
|
||||||
border.y = view.current.box.y + actual_box.height;
|
|
||||||
renderRect(output, border, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn renderRect(output: *const Output, box: wlr.Box, color: *const [4]f32) void {
|
|
||||||
var scaled = box;
|
|
||||||
scaleBox(&scaled, output.wlr_output.scale);
|
|
||||||
server.renderer.renderRect(&scaled, color, &output.wlr_output.transform_matrix);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Scale a wlr_box, taking the possibility of fractional scaling into account.
|
|
||||||
fn scaleBox(box: *wlr.Box, scale: f64) void {
|
|
||||||
box.width = scaleLength(box.width, box.x, scale);
|
|
||||||
box.height = scaleLength(box.height, box.y, scale);
|
|
||||||
box.x = @floatToInt(c_int, @round(@intToFloat(f64, box.x) * scale));
|
|
||||||
box.y = @floatToInt(c_int, @round(@intToFloat(f64, box.y) * scale));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Scales a width/height.
|
|
||||||
///
|
|
||||||
/// This might seem overly complex, but it needs to work for fractional scaling.
|
|
||||||
fn scaleLength(length: c_int, offset: c_int, scale: f64) c_int {
|
|
||||||
return @floatToInt(c_int, @round(@intToFloat(f64, offset + length) * scale) -
|
|
||||||
@round(@intToFloat(f64, offset) * scale));
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user