add support to protocol: output-power-manager + output-manager

This commit is contained in:
DanyLE
2025-07-09 20:32:33 +02:00
parent 194b7894cd
commit 9cc2408dc8
6 changed files with 779 additions and 99 deletions

680
output.c
View File

@ -3,15 +3,66 @@
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <wlr/backend/wayland.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_xdg_output_v1.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/util/log.h>
#include <assert.h>
#include "output.h"
#include "layer.h"
#include "node.h"
#include "view.h"
#include "session.h"
/*
* While an output layout change is in process, this counter is
* non-zero and causes change-events from the wlr_output_layout
* to be ignored (to prevent, for example, moving views in a
* transitory layout state). Once the counter reaches zero,
* do_output_layout_change() must be called explicitly.
*/
static int g_pending_output_layout_change = 0;
static void output_state_init(struct diyac_output *output)
{
wlr_output_state_init(&output->pending_state);
/*
* As there is no direct way to convert an existing output
* configuration to an output_state we first convert it
* to a temporary output-management config and then apply
* it to an empty wlr_output_state.
*/
struct wlr_output_configuration_v1 *backup_config =
wlr_output_configuration_v1_create();
struct wlr_output_configuration_head_v1 *backup_head =
wlr_output_configuration_head_v1_create(
backup_config, output->wlr_output);
wlr_output_head_v1_state_apply(&backup_head->state, &output->pending_state);
wlr_output_configuration_v1_destroy(backup_config);
}
static bool output_state_commit(struct diyac_output *output)
{
bool committed =
wlr_output_commit_state(output->wlr_output, &output->pending_state);
if (committed)
{
wlr_output_state_finish(&output->pending_state);
wlr_output_state_init(&output->pending_state);
}
else
{
wlr_log(WLR_ERROR, "Failed to commit frame");
}
return committed;
}
static void output_frame(struct wl_listener *listener, void *data)
{
(void) data;
(void)data;
/* This function is called every time an output is ready to display a frame,
* generally at the output's refresh rate (e.g. 60Hz). */
struct diyac_output *output = wl_container_of(listener, output, frame);
@ -28,21 +79,126 @@ static void output_frame(struct wl_listener *listener, void *data)
wlr_scene_output_send_frame_done(scene_output, &now);
}
static struct wlr_output_configuration_v1 *create_output_config(struct diyac_server *server)
{
struct wlr_output_configuration_v1 *config =
wlr_output_configuration_v1_create();
if (!config)
{
wlr_log(WLR_ERROR, "wlr_output_configuration_v1_create()");
return NULL;
}
struct diyac_output *output;
wl_list_for_each(output, &server->outputs, link)
{
struct wlr_output_configuration_head_v1 *head =
wlr_output_configuration_head_v1_create(config,
output->wlr_output);
if (!head)
{
wlr_log(WLR_ERROR,
"wlr_output_configuration_head_v1_create()");
wlr_output_configuration_v1_destroy(config);
return NULL;
}
struct wlr_box box;
wlr_output_layout_get_box(server->output_layout,
output->wlr_output, &box);
if (!wlr_box_empty(&box))
{
head->state.x = box.x;
head->state.y = box.y;
}
else
{
wlr_log(WLR_ERROR, "failed to get output layout box");
}
}
return config;
}
/* returns true if usable area changed */
static bool update_usable_area(struct diyac_output *output)
{
struct wlr_box old = output->usable_area;
diyac_layers_arrange(output);
return !wlr_box_equal(&old, &output->usable_area);
}
static void do_output_layout_change(struct diyac_server *server)
{
if (!g_pending_output_layout_change)
{
struct wlr_output_configuration_v1 *config =
create_output_config(server);
if (config)
{
wlr_output_manager_v1_set_configuration(
server->output_manager, config);
}
else
{
wlr_log(WLR_ERROR,
"wlr_output_manager_v1_set_configuration()");
}
struct diyac_output *output;
wl_list_for_each(output, &server->outputs, link)
{
diyac_output_update_usable_area(output);
}
wlr_cursor_move(server->seat.cursor, NULL, 0, 0);
}
}
static void output_request_state(struct wl_listener *listener, void *data)
{
/* This function is called when the backend requests a new state for
* the output. For example, Wayland and X11 backends request a new mode
* when the output window is resized. */
/* This ensures nested backends can be resized */
struct diyac_output *output = wl_container_of(listener, output, request_state);
const struct wlr_output_event_request_state *event = data;
wlr_output_commit_state(output->wlr_output, event->state);
/*
* If wlroots ever requests other state changes here we could
* restore more of ddc9047a67cd53b2948f71fde1bbe9118000dd3f.
*/
if (event->state->committed == WLR_OUTPUT_STATE_MODE)
{
/* Only the mode has changed */
switch (event->state->mode_type)
{
case WLR_OUTPUT_STATE_MODE_FIXED:
wlr_output_state_set_mode(&output->pending_state,
event->state->mode);
break;
case WLR_OUTPUT_STATE_MODE_CUSTOM:
wlr_output_state_set_custom_mode(&output->pending_state,
event->state->custom_mode.width,
event->state->custom_mode.height,
event->state->custom_mode.refresh);
break;
}
wlr_output_schedule_frame(output->wlr_output);
return;
}
/*
* Fallback path for everything that we didn't handle above.
* The commit will cause a black frame injection so this
* path causes flickering during resize of nested outputs.
*/
if (!wlr_output_commit_state(output->wlr_output, event->state))
{
wlr_log(WLR_ERROR, "Backend requested a new state that could not be applied");
}
}
static void output_destroy(struct wl_listener *listener, void *data)
{
(void)data;
struct diyac_output *output = wl_container_of(listener, output, destroy);
if(output->lock_handle)
if (output->lock_handle)
{
diyac_session_unlock_output(output);
}
@ -53,31 +209,141 @@ static void output_destroy(struct wl_listener *listener, void *data)
wlr_scene_node_destroy(&output->scenes.overlay->node);
wlr_scene_node_destroy(&output->scenes.session->node);
wl_list_remove(&output->frame.link);
wl_list_remove(&output->request_state.link);
wl_list_remove(&output->destroy.link);
wl_list_remove(&output->link);
struct diyac_server* server = output->server;
struct diyac_view * view;
wl_list_for_each(view, &server->views, link) {
if (view->output == output) {
struct diyac_server *server = output->server;
struct diyac_view *view;
wl_list_for_each(view, &server->views, link)
{
if (view->output == output)
{
/**
* TODO: testing this case
*/
* TODO: testing this case
*/
view->output = NULL;
if(&server->outputs != server->outputs.next)
if (&server->outputs != server->outputs.next)
{
view->output = wl_container_of(server->outputs.next, view->output, link);
diyac_view_update_geometry(view, false);
}
}
}
wlr_output_state_finish(&output->pending_state);
free(output);
}
void diyac_server_new_output(struct wl_listener *listener, void *data)
static bool output_test_auto(struct wlr_output *wlr_output, struct wlr_output_state *state,
bool is_client_request)
{
wlr_log(WLR_DEBUG, "testing modes for %s", wlr_output->name);
/*
* When a client requests a specific mode, test only that mode. Here
* we interpret a custom_mode of all zeroes as "none/any"; this is
* seen e.g. with kanshi configs containing no "mode" field. In
* theory, (state->committed & WLR_OUTPUT_STATE_MODE) should be zero
* in this case, but this is not seen in practice.
*
* If the wlr_output_state did not come from a client request, then
* ignore the mode/custom_mode fields which are not meaningful.
*/
if (is_client_request && (state->mode || state->custom_mode.width || state->custom_mode.height || state->custom_mode.refresh))
{
if (state->mode)
{
wlr_log(WLR_DEBUG, "testing requested mode %dx%d@%d",
state->mode->width, state->mode->height,
state->mode->refresh);
}
else
{
wlr_log(WLR_DEBUG, "testing custom mode %dx%d@%d",
state->custom_mode.width,
state->custom_mode.height,
state->custom_mode.refresh);
}
return wlr_output_test_state(wlr_output, state);
}
struct wlr_output_mode *preferred_mode =
wlr_output_preferred_mode(wlr_output);
if (preferred_mode)
{
wlr_log(WLR_DEBUG, "testing preferred mode %dx%d@%d",
preferred_mode->width, preferred_mode->height,
preferred_mode->refresh);
wlr_output_state_set_mode(state, preferred_mode);
if (wlr_output_test_state(wlr_output, state))
{
return true;
}
}
/*
* Sometimes the preferred mode is not available due to hardware
* constraints (e.g. GPU or cable bandwidth limitations). In these
* cases it's better to fallback to lower modes than to end up with
* a black screen. See sway@4cdc4ac6
*/
struct wlr_output_mode *mode;
wl_list_for_each(mode, &wlr_output->modes, link)
{
if (mode == preferred_mode)
{
continue;
}
wlr_log(WLR_DEBUG, "testing fallback mode %dx%d@%d",
mode->width, mode->height, mode->refresh);
wlr_output_state_set_mode(state, mode);
if (wlr_output_test_state(wlr_output, state))
{
return true;
}
}
/* Reset mode if none worked (we may still try to commit) */
wlr_output_state_set_mode(state, NULL);
return false;
}
static void add_output_to_layout(struct diyac_server *server, struct diyac_output *output)
{
struct wlr_output *wlr_output = output->wlr_output;
struct wlr_output_layout_output *layout_output =
wlr_output_layout_add_auto(server->output_layout, wlr_output);
if (!layout_output)
{
wlr_log(WLR_ERROR, "unable to add output to layout");
return;
}
if (!output->scene_output)
{
output->scene_output =
wlr_scene_output_create(server->scene, wlr_output);
if (!output->scene_output)
{
wlr_log(WLR_ERROR, "unable to create scene output");
return;
}
/*
* Note: wlr_scene_output_layout_add_output() is not
* safe to call twice, so we call it only when initially
* creating the scene_output.
*/
wlr_scene_output_layout_add_output(server->scene_layout,
layout_output, output->scene_output);
}
/* Create lock surface if needed */
if (server->lock)
{
diyac_session_lock_output(output);
}
}
static void diyac_server_new_output(struct wl_listener *listener, void *data)
{
/* This event is raised by the backend when a new output (aka a display or
* monitor) becomes available. */
@ -89,26 +355,6 @@ void diyac_server_new_output(struct wl_listener *listener, void *data)
* and our renderer. Must be done once, before commiting the output */
wlr_output_init_render(wlr_output, server->allocator, server->renderer);
/* The output may be disabled, switch it on. */
struct wlr_output_state state;
wlr_output_state_init(&state);
wlr_output_state_set_enabled(&state, true);
/* 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,
* refresh rate), and each monitor supports only a specific set of modes. We
* just pick the monitor's preferred mode, a more sophisticated compositor
* would let the user configure it. */
struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output);
if (mode != NULL)
{
wlr_output_state_set_mode(&state, mode);
}
/* Atomically applies the new output state. */
wlr_output_commit_state(wlr_output, &state);
wlr_output_state_finish(&state);
/* Allocates and configures our state for this output */
struct diyac_output *output = calloc(1, sizeof(*output));
output->wlr_output = wlr_output;
@ -118,6 +364,9 @@ void diyac_server_new_output(struct wl_listener *listener, void *data)
output->usable_area.y = 0;
output->usable_area.width = wlr_output->width;
output->usable_area.height = wlr_output->height;
output_state_init(output);
/* Sets up a listener for the frame event. */
output->frame.notify = output_frame;
wl_signal_add(&wlr_output->events.frame, &output->frame);
@ -156,10 +405,6 @@ void diyac_server_new_output(struct wl_listener *listener, void *data)
output->layer_tree[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY] = output->scenes.overlay;
output->lock_handle = NULL;
if(server->lock)
{
diyac_session_lock_output(output);
}
/*
* Set the z-positions to achieve the following order (from top to
* bottom):
@ -178,28 +423,25 @@ void diyac_server_new_output(struct wl_listener *listener, void *data)
wlr_scene_node_raise_to_top(&output->scenes.popup->node);
wlr_scene_node_raise_to_top(&output->scenes.session->node);
/* Adds this to the output layout. The add_auto function arranges outputs
* from left-to-right in the order they appear. A more sophisticated
* compositor would let the user configure the arrangement of outputs in the
* layout.
*
* The output layout utility automatically adds a wl_output global to the
* display, which Wayland clients can see to find out information about the
* output (such as DPI, scale factor, manufacturer, etc).
*/
struct wlr_output_layout_output *l_output = wlr_output_layout_add_auto(server->output_layout,
wlr_output);
struct wlr_scene_output *scene_output = wlr_scene_output_create(server->scene, wlr_output);
wlr_scene_output_layout_add_output(server->scene_layout, l_output, scene_output);
}
wlr_output_state_set_enabled(&output->pending_state, true);
/* returns true if usable area changed */
static bool update_usable_area(struct diyac_output *output)
{
struct wlr_box old = output->usable_area;
diyac_layers_arrange(output);
return !wlr_box_equal(&old, &output->usable_area);
if (!output_test_auto(wlr_output, &output->pending_state,
/* is_client_request */ false))
{
wlr_log(WLR_INFO, "mode test failed for output %s",
wlr_output->name);
/*
* Continue anyway. For some reason, the test fails when
* running nested, yet the following commit succeeds.
*/
}
output_state_commit(output);
wlr_output_effective_resolution(wlr_output,
&output->usable_area.width, &output->usable_area.height);
g_pending_output_layout_change++;
add_output_to_layout(server, output);
g_pending_output_layout_change--;
do_output_layout_change(server);
}
void diyac_output_update_usable_area(struct diyac_output *output)
@ -225,9 +467,9 @@ struct diyac_output *diyac_output_from_cursor(struct diyac_server *server)
return output->data;
}
void diyac_output_usable_area(struct diyac_output *output,struct wlr_box* area)
void diyac_output_usable_area(struct diyac_output *output, struct wlr_box *area)
{
if(!area | !output)
if (!area | !output)
{
return;
}
@ -237,22 +479,324 @@ void diyac_output_usable_area(struct diyac_output *output,struct wlr_box* area)
output->wlr_output, &ox, &oy);
box.x -= ox;
box.y -= oy;
memcpy(area,&box,sizeof(box));
memcpy(area, &box, sizeof(box));
}
void diyac_output_full_area(struct diyac_output *output,struct wlr_box* area)
void diyac_output_full_area(struct diyac_output *output, struct wlr_box *area)
{
if(!area | !output)
if (!area | !output)
{
return;
}
struct wlr_box box = {0};
wlr_output_effective_resolution(output->wlr_output,
&box.width, &box.height);
&box.width, &box.height);
double ox = 0, oy = 0;
wlr_output_layout_output_coords(output->server->output_layout,
output->wlr_output, &ox, &oy);
output->wlr_output, &ox, &oy);
box.x -= ox;
box.y -= oy;
memcpy(area,&box,sizeof(box));
memcpy(area, &box, sizeof(box));
}
static void diyac_output_power_manager_set_mode(struct wl_listener *listener, void *data)
{
struct diyac_server *server = wl_container_of(listener, server,
output_power_manager_set_mode);
struct wlr_output_power_v1_set_mode_event *event = data;
struct diyac_output *output = event->output->data;
assert(output);
switch (event->mode)
{
case ZWLR_OUTPUT_POWER_V1_MODE_OFF:
if (!event->output->enabled)
{
return;
}
wlr_output_state_set_enabled(&output->pending_state, false);
output_state_commit(output);
break;
case ZWLR_OUTPUT_POWER_V1_MODE_ON:
if (event->output->enabled)
{
return;
}
wlr_output_state_set_enabled(&output->pending_state, true);
output_state_commit(output);
/*
* Re-set the cursor image so that the cursor
* isn't invisible on the newly enabled output.
*/
// cursor_update_image(&server->seat);
break;
}
}
static void handle_output_layout_change(struct wl_listener *listener, void *data)
{
(void)data;
struct diyac_server *server =
wl_container_of(listener, server, output_layout_change);
do_output_layout_change(server);
}
static bool verify_output_config_v1(const struct wlr_output_configuration_v1 *config)
{
const char *err_msg = NULL;
struct wlr_output_configuration_head_v1 *head;
wl_list_for_each(head, &config->heads, link)
{
if (!head->state.enabled)
{
continue;
}
/* Handle custom modes */
if (!head->state.mode)
{
int32_t refresh = head->state.custom_mode.refresh;
if (wlr_output_is_wl(head->state.output) && refresh != 0)
{
/* Wayland backend does not support refresh rates */
err_msg = "Wayland backend refresh rates unsupported";
goto custom_mode_failed;
}
}
if (wlr_output_is_wl(head->state.output) && !head->state.adaptive_sync_enabled)
{
err_msg = "Wayland backend requires adaptive sync";
goto custom_mode_failed;
}
/*
* Ensure the new output state can be applied on
* its own and inform the client when it can not.
*
* Applying the changes may still fail later when
* getting mixed with wlr_output->pending which
* may contain further unrelated changes.
*/
struct wlr_output_state output_state;
wlr_output_state_init(&output_state);
wlr_output_head_v1_state_apply(&head->state, &output_state);
if (!output_test_auto(head->state.output, &output_state,
/* is_client_request */ true))
{
wlr_output_state_finish(&output_state);
return false;
}
wlr_output_state_finish(&output_state);
}
return true;
custom_mode_failed:
assert(err_msg);
wlr_log(WLR_INFO, "%s (%s: %dx%d@%d)",
err_msg,
head->state.output->name,
head->state.custom_mode.width,
head->state.custom_mode.height,
head->state.custom_mode.refresh);
return false;
}
void diyac_output_enable_adaptive_sync(struct diyac_output *output, bool enabled)
{
wlr_output_state_set_adaptive_sync_enabled(&output->pending_state, enabled);
if (!wlr_output_test_state(output->wlr_output, &output->pending_state))
{
wlr_output_state_set_adaptive_sync_enabled(&output->pending_state, false);
wlr_log(WLR_DEBUG,
"failed to enable adaptive sync for output %s",
output->wlr_output->name);
}
else
{
wlr_log(WLR_INFO, "adaptive sync %sabled for output %s",
enabled ? "en" : "dis", output->wlr_output->name);
}
}
static bool output_config_apply(struct diyac_server *server, struct wlr_output_configuration_v1 *config)
{
bool success = true;
g_pending_output_layout_change++;
struct wlr_output_configuration_head_v1 *head;
wl_list_for_each(head, &config->heads, link)
{
struct wlr_output *o = head->state.output;
struct diyac_output *output = o->data;
struct wlr_output_state *os = &output->pending_state;
bool output_enabled = head->state.enabled;
wlr_output_state_set_enabled(os, output_enabled);
if (output_enabled)
{
/* Output specific actions only */
if (head->state.mode)
{
wlr_output_state_set_mode(os, head->state.mode);
}
else
{
wlr_output_state_set_custom_mode(os,
head->state.custom_mode.width,
head->state.custom_mode.height,
head->state.custom_mode.refresh);
}
/*
* Try to ensure a valid mode. Ignore failures
* here and just check the commit below.
*/
(void)output_test_auto(o, os,
/* is_client_request */ true);
wlr_output_state_set_scale(os, head->state.scale);
wlr_output_state_set_transform(os, head->state.transform);
diyac_output_enable_adaptive_sync(output,
head->state.adaptive_sync_enabled);
}
if (!output_state_commit(output))
{
/*
* FIXME: This is only part of the story, we should revert
* all previously committed outputs as well here.
*
* See https://github.com/labwc/labwc/pull/1528
*/
wlr_log(WLR_INFO, "Output config commit failed: %s", o->name);
success = false;
break;
}
/*
* Add or remove output from layout only if the commit went
* through. Note that at startup, the output may have already
* been enabled but not yet been added to the layout.
*/
bool was_in_layout =
!!wlr_output_layout_get(server->output_layout, o);
if (output_enabled)
{
if (!was_in_layout)
{
add_output_to_layout(server, output);
}
struct wlr_box pos = {0};
wlr_output_layout_get_box(server->output_layout, o, &pos);
if (pos.x != head->state.x || pos.y != head->state.y)
{
/*
* This overrides the automatic layout
*
* wlr_output_layout_add() in fact means _move()
*/
wlr_output_layout_add(server->output_layout, o,
head->state.x, head->state.y);
}
}
else if (was_in_layout)
{
/*
* At time of writing, wlr_output_layout_remove()
* indirectly destroys the wlr_scene_output, but
* this behavior may change in future. To remove
* doubt and avoid either a leak or double-free,
* explicitly destroy the wlr_scene_output before
* calling wlr_output_layout_remove().
*/
wlr_scene_output_destroy(output->scene_output);
wlr_output_layout_remove(server->output_layout, o);
output->scene_output = NULL;
}
}
g_pending_output_layout_change--;
do_output_layout_change(server);
return success;
}
static void handle_output_manager_apply(struct wl_listener *listener, void *data)
{
struct diyac_server *server =
wl_container_of(listener, server, output_manager_apply);
struct wlr_output_configuration_v1 *config = data;
bool config_is_good = verify_output_config_v1(config);
if (config_is_good && output_config_apply(server, config))
{
wlr_output_configuration_v1_send_succeeded(config);
}
else
{
wlr_output_configuration_v1_send_failed(config);
}
wlr_output_configuration_v1_destroy(config);
/*
struct output *output;
wl_list_for_each(output, &server->outputs, link)
{
wlr_xcursor_manager_load(server->seat.xcursor_manager,
output->wlr_output->scale);
}
cursor_update_focus(server);
cursor_update_image(&server->seat);
*/
}
static void handle_output_manager_test(struct wl_listener *listener, void *data)
{
(void) listener;
struct wlr_output_configuration_v1 *config = data;
if (verify_output_config_v1(config))
{
wlr_output_configuration_v1_send_succeeded(config);
}
else
{
wlr_output_configuration_v1_send_failed(config);
}
wlr_output_configuration_v1_destroy(config);
}
void diyac_output_init(struct diyac_server *server)
{
/* Creates an output layout, which a wlroots utility for working with an
* arrangement of screens in a physical layout. */
server->output_layout = wlr_output_layout_create(server->wl_display);
/* Configure a listener to be notified when new outputs are available on the
* backend. */
wl_list_init(&server->outputs);
server->new_output.notify = diyac_server_new_output;
wl_signal_add(&server->backend->events.new_output, &server->new_output);
server->scene_layout = wlr_scene_attach_output_layout(server->scene, server->output_layout);
wlr_xdg_output_manager_v1_create(server->wl_display, server->output_layout);
server->output_layout_change.notify = handle_output_layout_change;
wl_signal_add(&server->output_layout->events.change,
&server->output_layout_change);
server->output_manager = wlr_output_manager_v1_create(server->wl_display);
server->output_manager_apply.notify = handle_output_manager_apply;
wl_signal_add(&server->output_manager->events.apply,
&server->output_manager_apply);
server->output_manager_test.notify = handle_output_manager_test;
wl_signal_add(&server->output_manager->events.test,
&server->output_manager_test);
server->output_power_manager =
wlr_output_power_manager_v1_create(server->wl_display);
server->output_power_manager_set_mode.notify =
diyac_output_power_manager_set_mode;
wl_signal_add(&server->output_power_manager->events.set_mode,
&server->output_power_manager_set_mode);
}