river: get rid of all server-created options

- Replace the layout option with new default-layout and output-layout
commands.
- Remove the ability to get/set the output title entirely.
This commit is contained in:
Isaac Freund 2021-04-26 16:35:26 +02:00
parent 84f5283889
commit a6f908d7eb
6 changed files with 94 additions and 56 deletions

View file

@ -72,6 +72,13 @@ over the Wayland protocol.
Bump the focused view to the top of the layout stack. If the top Bump the focused view to the top of the layout stack. If the top
view in the stack is already focused, bump the second view. view in the stack is already focused, bump the second view.
*default-layout* _namespace_
Set the layout namespace to be used by all outputs by default.
*output-layout* _namespace_
Set the layout namespace of currently focused output, overriding
the value set with *default-layout* if any.
## TAG MANAGEMENT ## TAG MANAGEMENT
Tags are similar to workspaces but more flexible. You can assign views multiple Tags are similar to workspaces but more flexible. You can assign views multiple
@ -283,17 +290,6 @@ the currently focused output may be targeted with the *-focused-output* flag.
*mod-option* [*-output* _output_name_|*-focused-output*] _name_ _value_ *mod-option* [*-output* _output_name_|*-focused-output*] _name_ _value_
Add _value_ to the value of the specified option. _value_ can be negative. Add _value_ to the value of the specified option. _value_ can be negative.
River declares certain default options which will always be available:
*layout* (string)
The layout namespace used to determine which layout should arrange this
output. If set to null or no layout with this namespace exists for this
output, the output will enter floating mode. Defaults to null.
*output_title* (string)
Changing this option changes the title of the wayland and X11 backend
outputs.
# EXAMPLES # EXAMPLES
Bind bemenu-run to Super+P in normal mode: Bind bemenu-run to Super+P in normal mode:

View file

@ -59,6 +59,11 @@ csd_filter: std.ArrayList([]const u8),
/// The selected focus_follows_cursor mode /// The selected focus_follows_cursor mode
focus_follows_cursor: FocusFollowsCursorMode = .disabled, focus_follows_cursor: FocusFollowsCursorMode = .disabled,
/// The default layout namespace for outputs which have never had a per-output
/// value set. Call Output.handleLayoutNamespaceChange() on setting this if
/// Output.layout_namespace is null.
default_layout_namespace: []const u8 = &[0]u8{},
opacity: struct { opacity: struct {
/// The opacity of focused views /// The opacity of focused views
focused: f32 = 1.0, focused: f32 = 1.0,
@ -119,4 +124,6 @@ pub fn deinit(self: *Self) void {
for (self.csd_filter.items) |s| util.gpa.free(s); for (self.csd_filter.items) |s| util.gpa.free(s);
self.csd_filter.deinit(); self.csd_filter.deinit();
util.gpa.free(self.default_layout_namespace);
} }

View file

@ -61,11 +61,9 @@ pub fn create(client: *wl.Client, version: u32, id: u32, output: *Output, namesp
// If the namespace matches that of the output, set the layout as // If the namespace matches that of the output, set the layout as
// the active one of the output and arrange it. // the active one of the output and arrange it.
if (output.layout_option.get().string) |current_layout| { if (mem.eql(u8, namespace, output.layoutNamespace())) {
if (mem.eql(u8, namespace, mem.span(current_layout))) { output.pending.layout = &node.data;
output.pending.layout = &node.data; output.arrangeViews();
output.arrangeViews();
}
} }
} }

View file

@ -81,6 +81,11 @@ layout_demand: ?LayoutDemand = null,
/// List of all layouts /// List of all layouts
layouts: std.TailQueue(Layout) = .{}, layouts: std.TailQueue(Layout) = .{},
/// The current layout namespace of the output. If null,
/// config.default_layout_namespace should be used instead.
/// Call handleLayoutNamespaceChange() after setting this.
layout_namespace: ?[]const u8 = null,
/// Determines where new views will be attached to the view stack. /// Determines where new views will be attached to the view stack.
attach_mode: AttachMode = .top, attach_mode: AttachMode = .top,
@ -95,11 +100,6 @@ enable: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleEnable),
frame: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleFrame), frame: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleFrame),
mode: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleMode), mode: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleMode),
layout_option: *OutputOption,
output_title: wl.Listener(*Option.Value) = wl.Listener(*Option.Value).init(handleTitleChange),
layout_change: wl.Listener(*Option.Value) = wl.Listener(*Option.Value).init(handleLayoutChange),
pub fn init(self: *Self, root: *Root, wlr_output: *wlr.Output) !void { pub fn init(self: *Self, root: *Root, wlr_output: *wlr.Output) !void {
// Some backends don't have modes. DRM+KMS does, and we need to set a mode // Some backends don't have modes. DRM+KMS does, and we need to set a mode
// before we can use the output. The mode is a tuple of (width, height, // before we can use the output. The mode is a tuple of (width, height,
@ -116,7 +116,6 @@ pub fn init(self: *Self, root: *Root, wlr_output: *wlr.Output) !void {
.root = root, .root = root,
.wlr_output = wlr_output, .wlr_output = wlr_output,
.usable_box = undefined, .usable_box = undefined,
.layout_option = undefined,
}; };
wlr_output.data = @ptrToInt(self); wlr_output.data = @ptrToInt(self);
@ -155,23 +154,7 @@ pub fn init(self: *Self, root: *Root, wlr_output: *wlr.Output) !void {
try options_manager.createOutputOptions(self); try options_manager.createOutputOptions(self);
errdefer options_manager.destroyOutputOptions(self); errdefer options_manager.destroyOutputOptions(self);
// Set the default title of this output self.setTitle();
var buf: ["river - ".len + wlr_output.name.len + 1]u8 = undefined;
const default_title = fmt.bufPrintZ(&buf, "river - {}", .{mem.spanZ(&wlr_output.name)}) catch unreachable;
self.setTitle(default_title);
const global_title_option = options_manager.getOption("output_title") orelse unreachable;
const title_option = global_title_option.getOutputOption(self).?;
title_option.set(.{ .string = default_title }) catch |err| switch (err) {
error.TypeMismatch => unreachable,
error.OutOfMemory => return err,
};
const global_layout_option = options_manager.getOption("layout") orelse unreachable;
self.layout_option = global_layout_option.getOutputOption(self).?;
self.layout_option.event.update.add(&self.layout_change);
title_option.event.update.add(&self.output_title);
} }
} }
@ -464,10 +447,10 @@ fn handleDestroy(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) v
self.frame.link.remove(); self.frame.link.remove();
self.mode.link.remove(); self.mode.link.remove();
// Cleanup the layout demand, if any
if (self.layout_demand) |demand| demand.deinit();
// Free all memory and clean up the wlr.Output // Free all memory and clean up the wlr.Output
if (self.layout_demand) |demand| demand.deinit();
if (self.layout_namespace) |namespace| util.gpa.free(namespace);
self.wlr_output.data = undefined; self.wlr_output.data = undefined;
const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self);
@ -506,7 +489,9 @@ pub fn getEffectiveResolution(self: *Self) struct { width: u32, height: u32 } {
}; };
} }
pub fn setTitle(self: *Self, title: [*:0]const u8) void { fn setTitle(self: Self) void {
var buf: ["river - ".len + self.wlr_output.name.len + 1]u8 = undefined;
const title = fmt.bufPrintZ(&buf, "river - {}", .{mem.spanZ(&self.wlr_output.name)}) catch unreachable;
if (self.wlr_output.isWl()) { if (self.wlr_output.isWl()) {
self.wlr_output.wlSetTitle(title); self.wlr_output.wlSetTitle(title);
} else if (wlr.config.has_x11_backend and self.wlr_output.isX11()) { } else if (wlr.config.has_x11_backend and self.wlr_output.isX11()) {
@ -514,21 +499,17 @@ pub fn setTitle(self: *Self, title: [*:0]const u8) void {
} }
} }
fn handleTitleChange(listener: *wl.Listener(*Option.Value), value: *Option.Value) void { pub fn handleLayoutNamespaceChange(self: *Self) void {
const self = @fieldParentPtr(Self, "output_title", listener);
if (value.string) |title| self.setTitle(title);
}
fn handleLayoutChange(listener: *wl.Listener(*Option.Value), value: *Option.Value) void {
const self = @fieldParentPtr(Self, "layout_change", listener);
// The user changed the layout namespace of this output. Try to find a // The user changed the layout namespace of this output. Try to find a
// matching layout. // matching layout.
self.pending.layout = if (value.string) |namespace| blk: { var it = self.layouts.first;
var layout_it = self.layouts.first; self.pending.layout = while (it) |node| : (it = node.next) {
break :blk while (layout_it) |node| : (layout_it = node.next) { if (mem.eql(u8, self.layoutNamespace(), node.data.namespace)) break &node.data;
if (mem.eql(u8, mem.span(namespace), node.data.namespace)) break &node.data;
} else null;
} else null; } else null;
self.arrangeViews(); self.arrangeViews();
self.root.startTransaction(); self.root.startTransaction();
} }
pub fn layoutNamespace(self: Self) []const u8 {
return self.layout_namespace orelse self.root.server.config.default_layout_namespace;
}

View file

@ -50,16 +50,18 @@ const str_to_impl_fn = [_]struct {
.{ .name = "close", .impl = @import("command/close.zig").close }, .{ .name = "close", .impl = @import("command/close.zig").close },
.{ .name = "csd-filter-add", .impl = @import("command/filter.zig").csdFilterAdd }, .{ .name = "csd-filter-add", .impl = @import("command/filter.zig").csdFilterAdd },
.{ .name = "declare-mode", .impl = @import("command/declare_mode.zig").declareMode }, .{ .name = "declare-mode", .impl = @import("command/declare_mode.zig").declareMode },
.{ .name = "default-layout", .impl = @import("command/layout.zig").defaultLayout },
.{ .name = "enter-mode", .impl = @import("command/enter_mode.zig").enterMode }, .{ .name = "enter-mode", .impl = @import("command/enter_mode.zig").enterMode },
.{ .name = "exit", .impl = @import("command/exit.zig").exit }, .{ .name = "exit", .impl = @import("command/exit.zig").exit },
.{ .name = "float-filter-add", .impl = @import("command/filter.zig").floatFilterAdd }, .{ .name = "float-filter-add", .impl = @import("command/filter.zig").floatFilterAdd },
.{ .name = "focus-output", .impl = @import("command/focus_output.zig").focusOutput },
.{ .name = "focus-follows-cursor", .impl = @import("command/focus_follows_cursor.zig").focusFollowsCursor }, .{ .name = "focus-follows-cursor", .impl = @import("command/focus_follows_cursor.zig").focusFollowsCursor },
.{ .name = "focus-output", .impl = @import("command/focus_output.zig").focusOutput },
.{ .name = "focus-view", .impl = @import("command/focus_view.zig").focusView }, .{ .name = "focus-view", .impl = @import("command/focus_view.zig").focusView },
.{ .name = "map", .impl = @import("command/map.zig").map }, .{ .name = "map", .impl = @import("command/map.zig").map },
.{ .name = "map-pointer", .impl = @import("command/map.zig").mapPointer }, .{ .name = "map-pointer", .impl = @import("command/map.zig").mapPointer },
.{ .name = "move", .impl = @import("command/move.zig").move }, .{ .name = "move", .impl = @import("command/move.zig").move },
.{ .name = "opacity", .impl = @import("command/opacity.zig").opacity }, .{ .name = "opacity", .impl = @import("command/opacity.zig").opacity },
.{ .name = "output-layout", .impl = @import("command/layout.zig").outputLayout },
.{ .name = "resize", .impl = @import("command/move.zig").resize }, .{ .name = "resize", .impl = @import("command/move.zig").resize },
.{ .name = "send-to-output", .impl = @import("command/send_to_output.zig").sendToOutput }, .{ .name = "send-to-output", .impl = @import("command/send_to_output.zig").sendToOutput },
.{ .name = "set-focused-tags", .impl = @import("command/tags.zig").setFocusedTags }, .{ .name = "set-focused-tags", .impl = @import("command/tags.zig").setFocusedTags },

54
river/command/layout.zig Normal file
View file

@ -0,0 +1,54 @@
// This file is part of river, a dynamic tiling wayland compositor.
//
// Copyright 2021 The River Developers
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const util = @import("../util.zig");
const Error = @import("../command.zig").Error;
const Seat = @import("../Seat.zig");
pub fn outputLayout(
allocator: *std.mem.Allocator,
seat: *Seat,
args: []const []const u8,
out: *?[]const u8,
) Error!void {
if (args.len < 2) return Error.NotEnoughArguments;
if (args.len > 2) return Error.TooManyArguments;
const output = seat.focused_output;
output.layout_namespace = try util.gpa.dupe(u8, args[1]);
output.handleLayoutNamespaceChange();
}
pub fn defaultLayout(
allocator: *std.mem.Allocator,
seat: *Seat,
args: []const []const u8,
out: *?[]const u8,
) Error!void {
if (args.len < 2) return Error.NotEnoughArguments;
if (args.len > 2) return Error.TooManyArguments;
const server = seat.input_manager.server;
server.config.default_layout_namespace = try util.gpa.dupe(u8, args[1]);
var it = server.root.all_outputs.first;
while (it) |node| : (it = node.next) {
const output = node.data;
if (output.layout_namespace == null) output.handleLayoutNamespaceChange();
}
}