Implement wlr_output_management_unstable_v1
This commit is contained in:
		
				
					committed by
					
						
						Isaac Freund
					
				
			
			
				
	
			
			
			
						parent
						
							4b7246685f
						
					
				
				
					commit
					c3b8986054
				
			@ -78,6 +78,7 @@ active: bool = false,
 | 
			
		||||
 | 
			
		||||
// All listeners for this output, in alphabetical order
 | 
			
		||||
listen_destroy: c.wl_listener = undefined,
 | 
			
		||||
listen_enable: c.wl_listener = undefined,
 | 
			
		||||
listen_frame: c.wl_listener = undefined,
 | 
			
		||||
listen_mode: c.wl_listener = undefined,
 | 
			
		||||
 | 
			
		||||
@ -108,6 +109,9 @@ pub fn init(self: *Self, root: *Root, wlr_output: *c.wlr_output) !void {
 | 
			
		||||
    self.listen_destroy.notify = handleDestroy;
 | 
			
		||||
    c.wl_signal_add(&wlr_output.events.destroy, &self.listen_destroy);
 | 
			
		||||
 | 
			
		||||
    self.listen_enable.notify = handleEnable;
 | 
			
		||||
    c.wl_signal_add(&wlr_output.events.enable, &self.listen_enable);
 | 
			
		||||
 | 
			
		||||
    self.listen_frame.notify = handleFrame;
 | 
			
		||||
    c.wl_signal_add(&wlr_output.events.frame, &self.listen_frame);
 | 
			
		||||
 | 
			
		||||
@ -523,6 +527,7 @@ fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
 | 
			
		||||
 | 
			
		||||
    // Remove all listeners
 | 
			
		||||
    c.wl_list_remove(&self.listen_destroy.link);
 | 
			
		||||
    c.wl_list_remove(&self.listen_enable.link);
 | 
			
		||||
    c.wl_list_remove(&self.listen_frame.link);
 | 
			
		||||
    c.wl_list_remove(&self.listen_mode.link);
 | 
			
		||||
 | 
			
		||||
@ -534,6 +539,15 @@ fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
 | 
			
		||||
    util.gpa.destroy(node);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn handleEnable(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
 | 
			
		||||
    const self = @fieldParentPtr(Self, "listen_enable", listener.?);
 | 
			
		||||
 | 
			
		||||
    if (self.wlr_output.enabled and !self.active) {
 | 
			
		||||
        const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self);
 | 
			
		||||
        self.root.addOutput(node);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn handleFrame(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
 | 
			
		||||
    // This function is called every time an output is ready to display a frame,
 | 
			
		||||
    // generally at the output's refresh rate (e.g. 60Hz).
 | 
			
		||||
 | 
			
		||||
@ -28,15 +28,33 @@ const Output = @import("Output.zig");
 | 
			
		||||
const Root = @import("Root.zig");
 | 
			
		||||
const Server = @import("Server.zig");
 | 
			
		||||
 | 
			
		||||
// Minimum effective width/height for outputs.
 | 
			
		||||
// This is needed, to prevent integer overflows caused by the output effective
 | 
			
		||||
// resolution beeing too small to fit clients that can't get scaled more and
 | 
			
		||||
// thus will be bigger than the output resolution.
 | 
			
		||||
// The value is totally arbitrary and low enough, that it should never be
 | 
			
		||||
// encountered during normal usage.
 | 
			
		||||
const min_size = 50;
 | 
			
		||||
 | 
			
		||||
root: *Root,
 | 
			
		||||
 | 
			
		||||
listen_new_output: c.wl_listener = undefined,
 | 
			
		||||
listen_output_layout_change: c.wl_listener = undefined,
 | 
			
		||||
 | 
			
		||||
wlr_output_manager: *c.wlr_output_manager_v1,
 | 
			
		||||
listen_output_manager_apply: c.wl_listener = undefined,
 | 
			
		||||
listen_output_manager_test: c.wl_listener = undefined,
 | 
			
		||||
 | 
			
		||||
wlr_output_power_manager: *c.wlr_output_power_manager_v1,
 | 
			
		||||
listen_output_power_manager_set_mode: c.wl_listener = undefined,
 | 
			
		||||
 | 
			
		||||
/// True if and only if we are currently applying an output config
 | 
			
		||||
output_config_pending: bool = false,
 | 
			
		||||
 | 
			
		||||
pub fn init(self: *Self, server: *Server) !void {
 | 
			
		||||
    self.* = .{
 | 
			
		||||
        .wlr_output_manager = c.wlr_output_manager_v1_create(server.wl_display) orelse
 | 
			
		||||
            return error.OutOfMemory,
 | 
			
		||||
        .wlr_output_power_manager = c.wlr_output_power_manager_v1_create(server.wl_display) orelse
 | 
			
		||||
            return error.OutOfMemory,
 | 
			
		||||
        .root = &server.root,
 | 
			
		||||
@ -45,6 +63,16 @@ pub fn init(self: *Self, server: *Server) !void {
 | 
			
		||||
    self.listen_new_output.notify = handleNewOutput;
 | 
			
		||||
    c.wl_signal_add(&server.wlr_backend.events.new_output, &self.listen_new_output);
 | 
			
		||||
 | 
			
		||||
    // Set up wlr_output_management
 | 
			
		||||
    self.listen_output_manager_apply.notify = handleOutputManagerApply;
 | 
			
		||||
    c.wl_signal_add(&self.wlr_output_manager.events.apply, &self.listen_output_manager_apply);
 | 
			
		||||
    self.listen_output_manager_test.notify = handleOutputManagerTest;
 | 
			
		||||
    c.wl_signal_add(&self.wlr_output_manager.events.@"test", &self.listen_output_manager_test);
 | 
			
		||||
 | 
			
		||||
    // Listen for changes in the output layout to send them to the clients of wlr_output_manager
 | 
			
		||||
    self.listen_output_layout_change.notify = handleOutputLayoutChange;
 | 
			
		||||
    c.wl_signal_add(&self.root.wlr_output_layout.events.change, &self.listen_output_layout_change);
 | 
			
		||||
 | 
			
		||||
    // Set up output power manager
 | 
			
		||||
    self.listen_output_power_manager_set_mode.notify = handleOutputPowerManagementSetMode;
 | 
			
		||||
    c.wl_signal_add(&self.wlr_output_power_manager.events.set_mode, &self.listen_output_power_manager_set_mode);
 | 
			
		||||
@ -77,6 +105,50 @@ fn handleNewOutput(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void
 | 
			
		||||
    self.root.addOutput(node);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Sends the new output configuration to all clients of wlr_output_manager
 | 
			
		||||
fn handleOutputLayoutChange(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
 | 
			
		||||
    const self = @fieldParentPtr(Self, "listen_output_layout_change", listener.?);
 | 
			
		||||
    // Dont do anything if the layout change is coming from applying a config
 | 
			
		||||
    if (self.output_config_pending) return;
 | 
			
		||||
 | 
			
		||||
    const config = self.createOutputConfigurationFromCurrent() catch {
 | 
			
		||||
        log.err(.output_manager, "Could not create output configuration", .{});
 | 
			
		||||
        return;
 | 
			
		||||
    };
 | 
			
		||||
    c.wlr_output_manager_v1_set_configuration(self.wlr_output_manager, config);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn handleOutputManagerApply(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
 | 
			
		||||
    const self = @fieldParentPtr(Self, "listen_output_manager_apply", listener.?);
 | 
			
		||||
    const config = util.voidCast(c.wlr_output_configuration_v1, data.?);
 | 
			
		||||
    defer c.wlr_output_configuration_v1_destroy(config);
 | 
			
		||||
 | 
			
		||||
    if (self.applyOutputConfig(config)) {
 | 
			
		||||
        c.wlr_output_configuration_v1_send_succeeded(config);
 | 
			
		||||
    } else {
 | 
			
		||||
        c.wlr_output_configuration_v1_send_failed(config);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Now send the config that actually was applied
 | 
			
		||||
    const actualConfig = self.createOutputConfigurationFromCurrent() catch {
 | 
			
		||||
        log.err(.output_manager, "Could not create output configuration", .{});
 | 
			
		||||
        return;
 | 
			
		||||
    };
 | 
			
		||||
    c.wlr_output_manager_v1_set_configuration(self.wlr_output_manager, actualConfig);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn handleOutputManagerTest(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
 | 
			
		||||
    const self = @fieldParentPtr(Self, "listen_output_manager_test", listener.?);
 | 
			
		||||
    const config = util.voidCast(c.wlr_output_configuration_v1, data.?);
 | 
			
		||||
    defer c.wlr_output_configuration_v1_destroy(config);
 | 
			
		||||
 | 
			
		||||
    if (testOutputConfig(config, true)) {
 | 
			
		||||
        c.wlr_output_configuration_v1_send_succeeded(config);
 | 
			
		||||
    } else {
 | 
			
		||||
        c.wlr_output_configuration_v1_send_failed(config);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn handleOutputPowerManagementSetMode(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
 | 
			
		||||
    const self = @fieldParentPtr(Self, "listen_output_power_manager_set_mode", listener.?);
 | 
			
		||||
    const mode_event = util.voidCast(c.wlr_output_power_v1_set_mode_event, data.?);
 | 
			
		||||
@ -88,15 +160,148 @@ fn handleOutputPowerManagementSetMode(listener: ?*c.wl_listener, data: ?*c_void)
 | 
			
		||||
    log.debug(
 | 
			
		||||
        .output_manager,
 | 
			
		||||
        "{} dpms for output {}",
 | 
			
		||||
        .{log_text, wlr_output.name},
 | 
			
		||||
        .{ log_text, wlr_output.name },
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    c.wlr_output_enable(wlr_output, enable);
 | 
			
		||||
    if (!c.wlr_output_commit(wlr_output)) {
 | 
			
		||||
        log.err(
 | 
			
		||||
            .server,
 | 
			
		||||
            .output_manager,
 | 
			
		||||
            "wlr_output_commit failed for {}",
 | 
			
		||||
            .{wlr_output.name},
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Applies an output config
 | 
			
		||||
fn applyOutputConfig(self: *Self, config: *c.wlr_output_configuration_v1) bool {
 | 
			
		||||
    // We need to store whether a config is pending because we listen to wlr_output_layout.change
 | 
			
		||||
    // and this event can be triggered by applying the config
 | 
			
		||||
    self.output_config_pending = true;
 | 
			
		||||
    defer self.output_config_pending = false;
 | 
			
		||||
 | 
			
		||||
    // Test if the config should apply cleanly
 | 
			
		||||
    if (!testOutputConfig(config, false)) return false;
 | 
			
		||||
 | 
			
		||||
    const list_head: *c.wl_list = &config.heads;
 | 
			
		||||
    var it: *c.wl_list = list_head.next;
 | 
			
		||||
    while (it != list_head) : (it = it.next) {
 | 
			
		||||
        const head = @fieldParentPtr(c.wlr_output_configuration_head_v1, "link", it);
 | 
			
		||||
        const output = util.voidCast(Output, @as(*c.wlr_output, head.state.output).data.?);
 | 
			
		||||
        const disable = output.wlr_output.enabled and !head.state.enabled;
 | 
			
		||||
 | 
			
		||||
        // This commit will only fail due to runtime errors.
 | 
			
		||||
        // We choose to ignore this error
 | 
			
		||||
        if (!c.wlr_output_commit(output.wlr_output)) {
 | 
			
		||||
            log.err(.output_manager, "wlr_output_commit failed for {}", .{output.wlr_output.name});
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (output.wlr_output.enabled) {
 | 
			
		||||
            // Moves the output if it is already in the layout
 | 
			
		||||
            c.wlr_output_layout_add(self.root.wlr_output_layout, output.wlr_output, head.state.x, head.state.y);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (disable) {
 | 
			
		||||
            const node = @fieldParentPtr(std.TailQueue(Output).Node, "data", output);
 | 
			
		||||
            self.root.removeOutput(node);
 | 
			
		||||
            c.wlr_output_layout_remove(self.root.wlr_output_layout, output.wlr_output);
 | 
			
		||||
        }
 | 
			
		||||
        // Arrange layers to adjust the usable_box
 | 
			
		||||
        // We dont need to call arrangeViews() since arrangeLayers() will call
 | 
			
		||||
        // it for us because the usable_box changed
 | 
			
		||||
        output.arrangeLayers();
 | 
			
		||||
        self.root.startTransaction();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Tests the output configuration.
 | 
			
		||||
/// If rollback is false all changes are applied to the pending state of the affected outputs.
 | 
			
		||||
fn testOutputConfig(config: *c.wlr_output_configuration_v1, rollback: bool) bool {
 | 
			
		||||
    var ok = true;
 | 
			
		||||
    const list_head: *c.wl_list = &config.heads;
 | 
			
		||||
    var it: *c.wl_list = list_head.next;
 | 
			
		||||
    while (it != list_head) : (it = it.next) {
 | 
			
		||||
        const head = @fieldParentPtr(c.wlr_output_configuration_head_v1, "link", it);
 | 
			
		||||
        const wlr_output = @as(*c.wlr_output, head.state.output);
 | 
			
		||||
 | 
			
		||||
        const width = if (@as(?*c.wlr_output_mode, head.state.mode)) |m| m.width else head.state.custom_mode.width;
 | 
			
		||||
        const height = if (@as(?*c.wlr_output_mode, head.state.mode)) |m| m.height else head.state.custom_mode.height;
 | 
			
		||||
        const scale = head.state.scale;
 | 
			
		||||
 | 
			
		||||
        const too_small = (@intToFloat(f32, width) / scale < min_size) or
 | 
			
		||||
            (@intToFloat(f32, height) / scale < min_size);
 | 
			
		||||
 | 
			
		||||
        if (too_small) {
 | 
			
		||||
            log.info(
 | 
			
		||||
                .output_manager,
 | 
			
		||||
                "The requested output resolution {}x{} scaled with {} for {} would be too small.",
 | 
			
		||||
                .{ width, height, scale, wlr_output.name },
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        applyHeadToOutput(head, wlr_output);
 | 
			
		||||
        ok = ok and !too_small and c.wlr_output_test(wlr_output);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (rollback or !ok) {
 | 
			
		||||
        // Rollback all changes
 | 
			
		||||
        it = list_head.next;
 | 
			
		||||
        while (it != list_head) : (it = it.next) {
 | 
			
		||||
            const head = @fieldParentPtr(c.wlr_output_configuration_head_v1, "link", it);
 | 
			
		||||
            const wlr_output = @as(*c.wlr_output, head.state.output);
 | 
			
		||||
            c.wlr_output_rollback(wlr_output);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return ok;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn applyHeadToOutput(head: *c.wlr_output_configuration_head_v1, wlr_output: *c.wlr_output) void {
 | 
			
		||||
    c.wlr_output_enable(wlr_output, head.state.enabled);
 | 
			
		||||
    // The output must be enabled for the following properties to apply
 | 
			
		||||
    if (head.state.enabled) {
 | 
			
		||||
        // TODO(wlroots) Somehow on the drm backend setting the mode causes
 | 
			
		||||
        // the commit in the rendering loop to fail. The commit that
 | 
			
		||||
        // applies the mode works fine.
 | 
			
		||||
        // We can just ignore this because nothing bad happens but it
 | 
			
		||||
        // should be fixed in the future
 | 
			
		||||
        // See https://github.com/swaywm/wlroots/issues/2492
 | 
			
		||||
        if (head.state.mode != null) {
 | 
			
		||||
            c.wlr_output_set_mode(wlr_output, head.state.mode);
 | 
			
		||||
        } else {
 | 
			
		||||
            const custom_mode = &head.state.custom_mode;
 | 
			
		||||
            c.wlr_output_set_custom_mode(wlr_output, custom_mode.width, custom_mode.height, custom_mode.refresh);
 | 
			
		||||
        }
 | 
			
		||||
        // TODO(wlroots) Figure out if this conversion is needed or if that is a bug in wlroots
 | 
			
		||||
        c.wlr_output_set_scale(wlr_output, @floatCast(f32, head.state.scale));
 | 
			
		||||
        c.wlr_output_set_transform(wlr_output, head.state.transform);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Creates an wlr_output_configuration from the current configuration
 | 
			
		||||
fn createOutputConfigurationFromCurrent(self: *Self) !*c.wlr_output_configuration_v1 {
 | 
			
		||||
    var config = c.wlr_output_configuration_v1_create() orelse return error.OutOfMemory;
 | 
			
		||||
    errdefer c.wlr_output_configuration_v1_destroy(config);
 | 
			
		||||
 | 
			
		||||
    var it = self.root.all_outputs.first;
 | 
			
		||||
    while (it) |node| : (it = node.next) {
 | 
			
		||||
        try self.createHead(node.data, config);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return config;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn createHead(self: *Self, output: *Output, config: *c.wlr_output_configuration_v1) !void {
 | 
			
		||||
    const wlr_output = output.wlr_output;
 | 
			
		||||
    const head: *c.wlr_output_configuration_head_v1 = c.wlr_output_configuration_head_v1_create(config, wlr_output) orelse
 | 
			
		||||
        return error.OutOfMemory;
 | 
			
		||||
 | 
			
		||||
    // If the output is not part of the layout (and thus disabled) we dont care about the position
 | 
			
		||||
    const box = @as(?*c.wlr_box, c.wlr_output_layout_get_box(self.root.wlr_output_layout, wlr_output));
 | 
			
		||||
    if (box) |b| {
 | 
			
		||||
        head.state.x = b.x;
 | 
			
		||||
        head.state.y = b.y;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -52,7 +52,7 @@ const Impl = union(enum) {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const State = struct {
 | 
			
		||||
    /// The output-relative coordinates and dimensions of the view. The
 | 
			
		||||
    /// The output-relative effective coordinates and effective dimensions of the view. The
 | 
			
		||||
    /// surface itself may have other dimensions which are stored in the
 | 
			
		||||
    /// surface_box member.
 | 
			
		||||
    box: Box = Box{ .x = 0, .y = 0, .width = 0, .height = 0 },
 | 
			
		||||
 | 
			
		||||
@ -46,6 +46,7 @@ pub usingnamespace @cImport({
 | 
			
		||||
    @cInclude("wlr/types/wlr_matrix.h");
 | 
			
		||||
    @cInclude("wlr/types/wlr_output.h");
 | 
			
		||||
    @cInclude("wlr/types/wlr_output_layout.h");
 | 
			
		||||
    @cInclude("wlr/types/wlr_output_management_v1.h");
 | 
			
		||||
    @cInclude("wlr/types/wlr_output_power_management_v1.h");
 | 
			
		||||
    @cInclude("wlr/types/wlr_pointer.h");
 | 
			
		||||
    @cInclude("wlr/types/wlr_primary_selection.h");
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user