diff --git a/diyac.c b/diyac.c index 9825dfa..8576a6c 100644 --- a/diyac.c +++ b/diyac.c @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -75,7 +74,7 @@ int main(int argc, char *argv[]) switch (c) { case 'v': - if(log_level < WLR_DEBUG) + if (log_level < WLR_DEBUG) { log_level++; } @@ -95,7 +94,7 @@ int main(int argc, char *argv[]) } if (optind < argc) { - if(optind != argc - 1) + if (optind != argc - 1) { help(); return 1; @@ -167,15 +166,6 @@ int main(int argc, char *argv[]) */ wlr_primary_selection_v1_device_manager_create(server.wl_display); - /* 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); /* Create a scene graph. This is a wlroots abstraction that handles all * rendering and damage tracking. All the compositor author needs to do @@ -184,22 +174,22 @@ int main(int argc, char *argv[]) * necessary. */ server.scene = wlr_scene_create(); - 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); + diyac_output_init(&server); wlr_export_dmabuf_manager_v1_create(server.wl_display); wlr_data_control_manager_v1_create(server.wl_display); wlr_screencopy_manager_v1_create(server.wl_display); wlr_single_pixel_buffer_manager_v1_create(server.wl_display); - wlr_fractional_scale_manager_v1_create(server.wl_display,1); + wlr_fractional_scale_manager_v1_create(server.wl_display, 1); diya_init_idle_manager(server.wl_display); + /* Set up xdg-shell version 6 The xdg-shell is a Wayland protocol which is * used for application windows. For more detail on shells, refer to * https://drewdevault.com/2018/07/29/Wayland-shells.html. */ wl_list_init(&server.views); /** - * TODO: free these tree when finish - */ + * TODO: free these tree when finish + */ server.view_tree = wlr_scene_tree_create(&server.scene->tree); server.xdg_popup_tree = wlr_scene_tree_create(&server.scene->tree); @@ -263,7 +253,7 @@ int main(int argc, char *argv[]) wlr_log(WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s", socket); server.proc_mon = NULL; - if( startup_cmd && exit_with_session) + if (startup_cmd && exit_with_session) { if (server.session_pid == -1) { @@ -283,13 +273,17 @@ int main(int argc, char *argv[]) /* Once wl_display_run returns, we destroy all clients then shut down the * server. */ - if(server.proc_mon) + if (server.proc_mon) { wl_event_source_remove(server.proc_mon); } wl_list_remove(&server.new_xdg_toplevel.link); wl_list_remove(&server.new_layer_surface.link); wl_list_remove(&server.new_output.link); + wl_list_remove(&server.output_power_manager_set_mode.link); + wl_list_remove(&server.output_manager_apply.link); + wl_list_remove(&server.output_manager_test.link); + wl_list_remove(&server.output_layout_change.link); wl_display_destroy_clients(server.wl_display); // wlr_scene_node_destroy(&server.scene->tree.node); wlr_xcursor_manager_destroy(server.seat.cursor_mgr); @@ -301,5 +295,5 @@ int main(int argc, char *argv[]) /** * @brief TODO * reload configuration (keymap, etc.) when sighub is received - * + * */ \ No newline at end of file diff --git a/diyac.h b/diyac.h index 011b9fe..9fb622f 100644 --- a/diyac.h +++ b/diyac.h @@ -18,6 +18,8 @@ #include #include #include +#include +#include #define LAYER_TREE_SZ 4 @@ -110,13 +112,13 @@ struct diyac_node_descriptor struct diyac_session_lock { bool abandoned; - struct wlr_surface * focused; + struct wlr_surface *focused; - struct wlr_session_lock_v1* wlr_session_lock; + struct wlr_session_lock_v1 *wlr_session_lock; struct wl_listener new_surface; struct wl_listener unlock; struct wl_listener destroy; - //struct wl_listener new_output; + // struct wl_listener new_output; }; struct diyac_server @@ -136,7 +138,7 @@ struct diyac_server struct wl_listener new_layer_surface; struct wl_list views; - struct diyac_view * active_view; + struct diyac_view *active_view; struct wlr_foreign_toplevel_manager_v1 *foreign_toplevel_manager; /* * Popups need to be rendered above always-on-top views, so we reparent @@ -156,9 +158,17 @@ struct diyac_server struct wl_list outputs; struct wl_listener new_output; - struct diyac_session_lock * lock; + struct wlr_output_power_manager_v1 *output_power_manager; + struct wl_listener output_power_manager_set_mode; - struct wl_event_source* proc_mon; + struct wlr_output_manager_v1 *output_manager; + struct wl_listener output_manager_test; + struct wl_listener output_manager_apply; + struct wl_listener output_layout_change; + + struct diyac_session_lock *lock; + + struct wl_event_source *proc_mon; pid_t session_pid; }; @@ -181,7 +191,7 @@ struct diyac_output_lock_handle struct wlr_scene_rect *background; struct wlr_session_lock_surface_v1 *surface; - struct diyac_output * output; + struct diyac_output *output; struct wl_listener surface_destroy; struct wl_listener surface_map; @@ -197,13 +207,15 @@ struct diyac_output struct wl_listener request_state; struct wl_listener destroy; struct wlr_box usable_area; + struct wlr_output_state pending_state; + struct wlr_scene_output* scene_output; // layer output struct diyac_output_scenes scenes; // alias to diyac_output_scenes elements struct wlr_scene_tree *layer_tree[LAYER_TREE_SZ]; // lock handle - struct diyac_output_lock_handle * lock_handle; + struct diyac_output_lock_handle *lock_handle; }; struct foreign_toplevel @@ -227,7 +239,7 @@ struct diyac_view struct diyac_view_state state; struct diyac_view_state requested; uint32_t configuration_serial; - struct wl_event_source * configuration_timeout; + struct wl_event_source *configuration_timeout; /* * Geometry of the wlr_surface contained within the view, as * currently displayed. Should be kept in sync with the diff --git a/meson.build b/meson.build index 370ca86..264d92c 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project('diyac', ['c'], - version: '0.1.1', + version: '0.1.2', license: 'MIT', meson_version: '>=0.58.0', default_options: ['c_std=gnu11', 'warning_level=3']) @@ -34,6 +34,7 @@ wayland_targets=[] wl_protocols = [ wl_protocol_dir / 'stable/xdg-shell/xdg-shell', 'protocol/wlr-layer-shell-unstable-v1', + 'protocol/wlr-output-power-management-unstable-v1', ] foreach proto : wl_protocols diff --git a/output.c b/output.c index a54ac29..2daa304 100644 --- a/output.c +++ b/output.c @@ -3,15 +3,66 @@ #include #include #include +#include +#include +#include +#include +#include +#include #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); } \ No newline at end of file diff --git a/output.h b/output.h index e5afee8..a1648e8 100644 --- a/output.h +++ b/output.h @@ -2,9 +2,10 @@ #define DIYAC_OUTPUT_H #include "diyac.h" -void diyac_server_new_output(struct wl_listener *listener, void *data); void diyac_output_update_usable_area(struct diyac_output *output); struct diyac_output *diyac_output_from_cursor(struct diyac_server *server); void diyac_output_usable_area(struct diyac_output * output,struct wlr_box* box); void diyac_output_full_area(struct diyac_output *output,struct wlr_box* area); +void diyac_output_init(struct diyac_server * server); +void diyac_output_enable_adaptive_sync(struct diyac_output *output, bool enabled); #endif \ No newline at end of file diff --git a/protocol/wlr-output-power-management-unstable-v1.xml b/protocol/wlr-output-power-management-unstable-v1.xml new file mode 100644 index 0000000..20dbb77 --- /dev/null +++ b/protocol/wlr-output-power-management-unstable-v1.xml @@ -0,0 +1,128 @@ + + + + Copyright © 2019 Purism SPC + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol allows clients to control power management modes + of outputs that are currently part of the compositor space. The + intent is to allow special clients like desktop shells to power + down outputs when the system is idle. + + To modify outputs not currently part of the compositor space see + wlr-output-management. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + This interface is a manager that allows creating per-output power + management mode controls. + + + + + Create an output power management mode control that can be used to + adjust the power management mode for a given output. + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This object offers requests to set the power management mode of + an output. + + + + + + + + + + + + + + Set an output's power save mode to the given mode. The mode change + is effective immediately. If the output does not support the given + mode a failed event is sent. + + + + + + + Report the power management mode change of an output. + + The mode event is sent after an output changed its power + management mode. The reason can be a client using set_mode or the + compositor deciding to change an output's mode. + This event is also sent immediately when the object is created + so the client is informed about the current power management mode. + + + + + + + This event indicates that the output power management mode control + is no longer valid. This can happen for a number of reasons, + including: + - The output doesn't support power management + - Another client already has exclusive power management mode control + for this output + - The output disappeared + + Upon receiving this event, the client should destroy this object. + + + + + + Destroys the output power management mode control object. + + + +