initial commit
This commit is contained in:
+297
@@ -0,0 +1,297 @@
|
||||
//! A logical output, and the window management state that dwm keeps per
|
||||
//! monitor: the visible tag set, the layout, nmaster and mfact.
|
||||
//!
|
||||
//! The arrangement settings are stored per tag rather than per output, as
|
||||
//! dwm's pertag patch does, so switching tags restores the layout that tag was
|
||||
//! last arranged with.
|
||||
|
||||
const Output = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const wayland = @import("wayland");
|
||||
const wl = wayland.client.wl;
|
||||
const river = wayland.client.river;
|
||||
|
||||
const config = @import("config");
|
||||
const action = @import("action");
|
||||
|
||||
const Wm = @import("Wm.zig");
|
||||
const Window = @import("Window.zig");
|
||||
const shm = @import("shm.zig");
|
||||
const color = @import("color.zig");
|
||||
const layout = @import("layout.zig");
|
||||
const Box = layout.Box;
|
||||
|
||||
wm: *Wm,
|
||||
output: *river.OutputV1,
|
||||
layer_output: ?*river.LayerShellOutputV1 = null,
|
||||
|
||||
/// The global name of the corresponding wl_output, used to pair the two up.
|
||||
wl_output_name: u32 = 0,
|
||||
/// Owned by Wm.wl_outputs; holds the human readable output name.
|
||||
wl_output: ?*Wm.WlOutput = null,
|
||||
|
||||
/// Full output area in the compositor's logical coordinate space.
|
||||
box: Box = .{},
|
||||
/// The part of `box` not covered by layer-shell exclusive zones. Windows are
|
||||
/// laid out here so bars are not overlapped.
|
||||
usable: Box = .{},
|
||||
/// Whether `usable` has been reported; before that it tracks `box`.
|
||||
have_usable: bool = false,
|
||||
|
||||
tags: u32 = config.default_tags,
|
||||
prev_tags: u32 = config.default_tags,
|
||||
|
||||
/// One slot per tag, plus slot 0 for views of more than one tag. Indexed
|
||||
/// through `state()`, never directly.
|
||||
tag_state: [action.tag_count + 1]TagState = @splat(.{}),
|
||||
|
||||
removed: bool = false,
|
||||
|
||||
/// Written by the layout pass each manage sequence, read by the render pass.
|
||||
tabbar_box: ?Box = null,
|
||||
/// True when the current layout stacks windows, so only `stack_top` shows.
|
||||
stacked: bool = false,
|
||||
stack_top: ?*Window = null,
|
||||
|
||||
tabbar: TabBar,
|
||||
|
||||
pub fn create(wm: *Wm, output: *river.OutputV1) !*Output {
|
||||
const self = try wm.gpa.create(Output);
|
||||
self.* = .{
|
||||
.wm = wm,
|
||||
.output = output,
|
||||
.tabbar = .{ .wm = wm },
|
||||
};
|
||||
output.setListener(*Output, onEvent, self);
|
||||
|
||||
if (wm.layer_shell) |ls| {
|
||||
self.layer_output = ls.getOutput(output) catch null;
|
||||
if (self.layer_output) |lo| {
|
||||
lo.setListener(*Output, onLayerEvent, self);
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(self: *Output) void {
|
||||
const gpa = self.wm.gpa;
|
||||
self.tabbar.deinit();
|
||||
if (self.layer_output) |lo| lo.destroy();
|
||||
// The wl_output entry is owned by Wm; just break the back reference.
|
||||
if (self.wl_output) |entry| entry.output = null;
|
||||
self.output.destroy();
|
||||
gpa.destroy(self);
|
||||
}
|
||||
|
||||
pub fn displayName(self: *const Output) []const u8 {
|
||||
const entry = self.wl_output orelse return "?";
|
||||
return entry.name orelse "?";
|
||||
}
|
||||
|
||||
/// The area windows are laid out in.
|
||||
pub fn layoutArea(self: *const Output) Box {
|
||||
return if (self.have_usable) self.usable else self.box;
|
||||
}
|
||||
|
||||
/// How one tag is arranged. dwm's pertag patch keeps exactly these four.
|
||||
pub const TagState = struct {
|
||||
layout: action.Layout = config.default_layout,
|
||||
prev_layout: action.Layout = config.default_layout,
|
||||
nmaster: i32 = config.nmaster,
|
||||
mfact: f32 = config.mfact,
|
||||
};
|
||||
|
||||
/// The arrangement settings in force on this output right now, i.e. those of
|
||||
/// the tag being viewed. See `action.tagSlot` for how a view picks its slot.
|
||||
pub fn state(self: *Output) *TagState {
|
||||
return &self.tag_state[action.tagSlot(self.tags)];
|
||||
}
|
||||
|
||||
pub fn setLayout(self: *Output, mode: action.Layout) void {
|
||||
const st = self.state();
|
||||
if (mode == st.layout) return;
|
||||
st.prev_layout = st.layout;
|
||||
st.layout = mode;
|
||||
}
|
||||
|
||||
pub fn setTags(self: *Output, tags: u32) void {
|
||||
const masked = tags & action.all_tags;
|
||||
if (masked == 0 or masked == self.tags) return;
|
||||
self.prev_tags = self.tags;
|
||||
self.tags = masked;
|
||||
}
|
||||
|
||||
fn onEvent(_: *river.OutputV1, event: river.OutputV1.Event, self: *Output) void {
|
||||
switch (event) {
|
||||
.removed => {
|
||||
self.removed = true;
|
||||
self.wm.needsManage();
|
||||
},
|
||||
.position => |ev| {
|
||||
self.box.x = ev.x;
|
||||
self.box.y = ev.y;
|
||||
self.wm.needsManage();
|
||||
},
|
||||
.dimensions => |ev| {
|
||||
self.box.width = ev.width;
|
||||
self.box.height = ev.height;
|
||||
self.wm.needsManage();
|
||||
},
|
||||
.wl_output => |ev| {
|
||||
self.wl_output_name = ev.name;
|
||||
self.wm.attachWlOutput(self);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn onLayerEvent(_: *river.LayerShellOutputV1, event: river.LayerShellOutputV1.Event, self: *Output) void {
|
||||
switch (event) {
|
||||
.non_exclusive_area => |ev| {
|
||||
self.usable = .{ .x = ev.x, .y = ev.y, .width = ev.width, .height = ev.height };
|
||||
self.have_usable = true;
|
||||
self.wm.needsManage();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// The strip of solid colour blocks drawn above the windows in the tabbed
|
||||
/// layout. One block per window, the focused one highlighted; titles are not
|
||||
/// drawn here but are published over IPC for bars that want to render them.
|
||||
pub const TabBar = struct {
|
||||
wm: *Wm,
|
||||
|
||||
surface: ?*wl.Surface = null,
|
||||
shell: ?*river.ShellSurfaceV1 = null,
|
||||
node: ?*river.NodeV1 = null,
|
||||
pool: ?shm.Pool = null,
|
||||
|
||||
/// Currently mapped, i.e. showing a buffer.
|
||||
mapped: bool = false,
|
||||
/// Where the bar is, in global coordinates.
|
||||
box: Box = .{},
|
||||
/// Hit rectangles for the tabs currently drawn, in global coordinates,
|
||||
/// parallel to `windows`.
|
||||
rects: std.ArrayList(Box) = .empty,
|
||||
windows: std.ArrayList(*Window) = .empty,
|
||||
|
||||
pub fn deinit(self: *TabBar) void {
|
||||
const gpa = self.wm.gpa;
|
||||
self.rects.deinit(gpa);
|
||||
self.windows.deinit(gpa);
|
||||
if (self.pool) |*p| p.deinit();
|
||||
if (self.node) |n| n.destroy();
|
||||
if (self.shell) |s| s.destroy();
|
||||
if (self.surface) |s| s.destroy();
|
||||
}
|
||||
|
||||
fn ensureSurface(self: *TabBar) !void {
|
||||
if (self.surface != null) return;
|
||||
|
||||
const compositor = self.wm.compositor orelse return error.NoCompositor;
|
||||
const wm_proxy = self.wm.window_manager orelse return error.NoWindowManager;
|
||||
const wl_shm = self.wm.shm orelse return error.NoShm;
|
||||
|
||||
const surface = try compositor.createSurface();
|
||||
errdefer surface.destroy();
|
||||
|
||||
const shell = try wm_proxy.getShellSurface(surface);
|
||||
errdefer shell.destroy();
|
||||
|
||||
const node = try shell.getNode();
|
||||
|
||||
self.surface = surface;
|
||||
self.shell = shell;
|
||||
self.node = node;
|
||||
self.pool = shm.Pool.init(self.wm.gpa, wl_shm);
|
||||
}
|
||||
|
||||
/// Draw and place the bar. Must be called during a render sequence.
|
||||
pub fn show(self: *TabBar, box: Box, windows: []const *Window, focused: ?*Window) void {
|
||||
self.ensureSurface() catch |err| {
|
||||
std.log.err("tab bar: {s}", .{@errorName(err)});
|
||||
return;
|
||||
};
|
||||
if (box.width <= 0 or box.height <= 0 or windows.len == 0) {
|
||||
self.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
const gpa = self.wm.gpa;
|
||||
const pool = &self.pool.?;
|
||||
|
||||
const buffer = pool.acquire(box.width, box.height) catch |err| {
|
||||
std.log.err("tab bar buffer: {s}", .{@errorName(err)});
|
||||
return;
|
||||
};
|
||||
|
||||
// Recompute the hit rectangles, in buffer-local coordinates first.
|
||||
self.rects.clearRetainingCapacity();
|
||||
self.windows.clearRetainingCapacity();
|
||||
self.rects.ensureTotalCapacity(gpa, windows.len) catch return;
|
||||
self.windows.appendSlice(gpa, windows) catch return;
|
||||
self.rects.resize(gpa, windows.len) catch return;
|
||||
|
||||
const local: Box = .{ .x = 0, .y = 0, .width = box.width, .height = box.height };
|
||||
layout.tabRects(local, windows.len, self.rects.items);
|
||||
|
||||
const sep = color.toArgb8888(config.tab_separator);
|
||||
buffer.fill(local, sep);
|
||||
|
||||
for (self.rects.items, windows) |rect, win| {
|
||||
const is_focused = focused != null and focused.? == win;
|
||||
const argb = color.toArgb8888(if (is_focused) config.tab_focused else config.tab_normal);
|
||||
// Leave a one pixel separator on the right of every tab but the
|
||||
// last, which the background colour shows through.
|
||||
const inner: Box = .{
|
||||
.x = rect.x,
|
||||
.y = rect.y,
|
||||
.width = @max(0, rect.width - 1),
|
||||
.height = rect.height,
|
||||
};
|
||||
buffer.fill(inner, argb);
|
||||
}
|
||||
|
||||
// Translate the hit rectangles into global coordinates for click
|
||||
// handling, now that drawing is done.
|
||||
for (self.rects.items) |*rect| {
|
||||
rect.x += box.x;
|
||||
rect.y += box.y;
|
||||
}
|
||||
|
||||
const surface = self.surface.?;
|
||||
buffer.busy = true;
|
||||
surface.attach(buffer.wl_buffer, 0, 0);
|
||||
surface.damageBuffer(0, 0, box.width, box.height);
|
||||
self.shell.?.syncNextCommit();
|
||||
surface.commit();
|
||||
|
||||
self.node.?.setPosition(box.x, box.y);
|
||||
self.box = box;
|
||||
self.mapped = true;
|
||||
}
|
||||
|
||||
/// Unmap the bar. Must be called during a render sequence if it was
|
||||
/// previously shown.
|
||||
pub fn hide(self: *TabBar) void {
|
||||
if (!self.mapped) return;
|
||||
const surface = self.surface orelse return;
|
||||
surface.attach(null, 0, 0);
|
||||
self.shell.?.syncNextCommit();
|
||||
surface.commit();
|
||||
self.mapped = false;
|
||||
self.rects.clearRetainingCapacity();
|
||||
self.windows.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
/// Which window's tab covers this global coordinate, if any.
|
||||
pub fn windowAt(self: *const TabBar, x: i32, y: i32) ?*Window {
|
||||
if (!self.mapped) return null;
|
||||
for (self.rects.items, self.windows.items) |rect, win| {
|
||||
if (rect.contains(x, y)) return win;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user