From 3d69ae73d3a4fd18bbe08bceec89f3c73bc34076 Mon Sep 17 00:00:00 2001 From: Jason Francis Date: Mon, 12 Aug 2019 18:38:46 -0400 Subject: [PATCH] Add screen number overlays --- protocol/meson.build | 4 +- protocol/wlr-layer-shell-unstable-v1.xml | 285 +++++++++++++++++++++++ resources/style.css | 11 +- resources/wdisplays.ui | 14 ++ src/main.c | 30 ++- src/meson.build | 1 + src/outputs.c | 102 ++++++-- src/overlay.c | 200 ++++++++++++++++ src/wdisplays.h | 32 ++- 9 files changed, 646 insertions(+), 33 deletions(-) create mode 100644 protocol/wlr-layer-shell-unstable-v1.xml create mode 100644 src/overlay.c diff --git a/protocol/meson.build b/protocol/meson.build index f12853d..f3a1af4 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -18,8 +18,10 @@ wayland_scanner_client = generator( client_protocols = [ [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'], + [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], ['wlr-output-management-unstable-v1.xml'], - ['wlr-screencopy-unstable-v1.xml'] + ['wlr-screencopy-unstable-v1.xml'], + ['wlr-layer-shell-unstable-v1.xml'] ] client_protos_src = [] diff --git a/protocol/wlr-layer-shell-unstable-v1.xml b/protocol/wlr-layer-shell-unstable-v1.xml new file mode 100644 index 0000000..216e0d9 --- /dev/null +++ b/protocol/wlr-layer-shell-unstable-v1.xml @@ -0,0 +1,285 @@ + + + + Copyright © 2017 Drew DeVault + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + Clients can use this interface to assign the surface_layer role to + wl_surfaces. Such surfaces are assigned to a "layer" of the output and + rendered with a defined z-depth respective to each other. They may also be + anchored to the edges and corners of a screen and specify input handling + semantics. This interface should be suitable for the implementation of + many desktop shell components, and a broad number of other applications + that interact with the desktop. + + + + + Create a layer surface for an existing surface. This assigns the role of + layer_surface, or raises a protocol error if another role is already + assigned. + + Creating a layer surface from a wl_surface which has a buffer attached + or committed is a client error, and any attempts by a client to attach + or manipulate a buffer prior to the first layer_surface.configure call + must also be treated as errors. + + You may pass NULL for output to allow the compositor to decide which + output to use. Generally this will be the one that the user most + recently interacted with. + + Clients can specify a namespace that defines the purpose of the layer + surface. + + + + + + + + + + + + + + + + + These values indicate which layers a surface can be rendered in. They + are ordered by z depth, bottom-most first. Traditional shell surfaces + will typically be rendered between the bottom and top layers. + Fullscreen shell surfaces are typically rendered at the top layer. + Multiple surfaces can share a single layer, and ordering within a + single layer is undefined. + + + + + + + + + + + + An interface that may be implemented by a wl_surface, for surfaces that + are designed to be rendered as a layer of a stacked desktop-like + environment. + + Layer surface state (size, anchor, exclusive zone, margin, interactivity) + is double-buffered, and will be applied at the time wl_surface.commit of + the corresponding wl_surface is called. + + + + + Sets the size of the surface in surface-local coordinates. The + compositor will display the surface centered with respect to its + anchors. + + If you pass 0 for either value, the compositor will assign it and + inform you of the assignment in the configure event. You must set your + anchor to opposite edges in the dimensions you omit; not doing so is a + protocol error. Both values are 0 by default. + + Size is double-buffered, see wl_surface.commit. + + + + + + + + Requests that the compositor anchor the surface to the specified edges + and corners. If two orthoginal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left corner of the output); otherwise the anchor point + will be centered on that edge, or in the center if none is specified. + + Anchor is double-buffered, see wl_surface.commit. + + + + + + + Requests that the compositor avoids occluding an area of the surface + with other surfaces. The compositor's use of this information is + implementation-dependent - do not assume that this region will not + actually be occluded. + + A positive value is only meaningful if the surface is anchored to an + edge, rather than a corner. The zone is the number of surface-local + coordinates from the edge that are considered exclusive. + + Surfaces that do not wish to have an exclusive zone may instead specify + how they should interact with surfaces that do. If set to zero, the + surface indicates that it would like to be moved to avoid occluding + surfaces with a positive excluzive zone. If set to -1, the surface + indicates that it would not like to be moved to accommodate for other + surfaces, and the compositor should extend it all the way to the edges + it is anchored to. + + For example, a panel might set its exclusive zone to 10, so that + maximized shell surfaces are not shown on top of it. A notification + might set its exclusive zone to 0, so that it is moved to avoid + occluding the panel, but shell surfaces are shown underneath it. A + wallpaper or lock screen might set their exclusive zone to -1, so that + they stretch below or over the panel. + + The default value is 0. + + Exclusive zone is double-buffered, see wl_surface.commit. + + + + + + + Requests that the surface be placed some distance away from the anchor + point on the output, in surface-local coordinates. Setting this value + for edges you are not anchored to has no effect. + + The exclusive zone includes the margin. + + Margin is double-buffered, see wl_surface.commit. + + + + + + + + + + Set to 1 to request that the seat send keyboard events to this layer + surface. For layers below the shell surface layer, the seat will use + normal focus semantics. For layers above the shell surface layers, the + seat will always give exclusive keyboard focus to the top-most layer + which has keyboard interactivity set to true. + + Layer surfaces receive pointer, touch, and tablet events normally. If + you do not want to receive them, set the input region on your surface + to an empty region. + + Events is double-buffered, see wl_surface.commit. + + + + + + + This assigns an xdg_popup's parent to this layer_surface. This popup + should have been created via xdg_surface::get_popup with the parent set + to NULL, and this request must be invoked before committing the popup's + initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + + + + + + This request destroys the layer surface. + + + + + + The configure event asks the client to resize its surface. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + The client is free to dismiss all but the last configure event it + received. + + The width and height arguments specify the size of the window in + surface-local coordinates. + + The size is a hint, in the sense that the client is free to ignore it if + it doesn't resize, pick a smaller size (to satisfy aspect ratio or + resize in steps of NxM pixels). If the client picks a smaller size and + is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the + surface will be centered on this axis. + + If the width or height arguments are zero, it means the client should + decide its own window dimension. + + + + + + + + + The closed event is sent by the compositor when the surface will no + longer be shown. The output may have been destroyed or the user may + have asked for it to be removed. Further changes to the surface will be + ignored. The client should destroy the resource after receiving this + event, and create a new surface if they so choose. + + + + + + + + + + + + + + + + + diff --git a/resources/style.css b/resources/style.css index f250845..f8ffc7b 100644 --- a/resources/style.css +++ b/resources/style.css @@ -8,12 +8,9 @@ spinner.visible { opacity: 1; } -.head { - border: 1px solid @borders; - border-radius: 1px; - background-color: @theme_bg_color; - transition: background-color 200ms ease-in-out; -} -.head:hover { +.output-overlay { background-color: @theme_selected_bg_color; + color: @theme_selected_fg_color; + border-radius: 8px; + opacity: 0.9; } diff --git a/resources/wdisplays.ui b/resources/wdisplays.ui index 8bf1c95..d2b2bd6 100644 --- a/resources/wdisplays.ui +++ b/resources/wdisplays.ui @@ -50,6 +50,20 @@ 1 + + + True + True + True + app.show-overlay + Show Numbers on Screens + + + False + True + 2 + + diff --git a/src/main.c b/src/main.c index 39053a0..e679ce8 100644 --- a/src/main.c +++ b/src/main.c @@ -708,7 +708,8 @@ static void cleanup(GtkWidget *window, gpointer data) { } static void monitor_added(GdkDisplay *display, GdkMonitor *monitor, gpointer data) { - wd_add_output(data, gdk_wayland_monitor_get_wl_output(monitor)); + struct wl_display *wl_display = gdk_wayland_display_get_wl_display(display); + wd_add_output(data, gdk_wayland_monitor_get_wl_output(monitor), wl_display); } static void monitor_removed(GdkDisplay *display, GdkMonitor *monitor, gpointer data) { @@ -1145,6 +1146,21 @@ static void capture_selected(GSimpleAction *action, GVariant *param, gpointer da update_tick_callback(state); } +static void overlay_selected(GSimpleAction *action, GVariant *param, gpointer data) { + struct wd_state *state = data; + state->show_overlay = !state->show_overlay; + g_simple_action_set_state(action, g_variant_new_boolean(state->show_overlay)); + + struct wd_output *output; + wl_list_for_each(output, &state->outputs, link) { + if (state->show_overlay) { + wd_create_overlay(output); + } else { + wd_destroy_overlay(output); + } + } +} + static void activate(GtkApplication* app, gpointer user_data) { GdkDisplay *gdk_display = gdk_display_get_default(); if (!GDK_IS_WAYLAND_DISPLAY(gdk_display)) { @@ -1229,6 +1245,11 @@ static void activate(GtkApplication* app, gpointer user_data) { g_signal_connect(capture_action, "activate", G_CALLBACK(capture_selected), state); g_action_map_add_action(G_ACTION_MAP(main_actions), G_ACTION(capture_action)); + GSimpleAction *overlay_action = g_simple_action_new_stateful("show-overlay", NULL, + g_variant_new_boolean(state->show_overlay)); + g_signal_connect(overlay_action, "activate", G_CALLBACK(overlay_selected), state); + g_action_map_add_action(G_ACTION_MAP(main_actions), G_ACTION(overlay_action)); + /* first child of GtkInfoBar is always GtkRevealer */ g_autoptr(GList) info_children = gtk_container_get_children(GTK_CONTAINER(state->info_bar)); g_signal_connect(info_children->data, "notify::child-revealed", G_CALLBACK(info_bar_animation_done), state); @@ -1247,11 +1268,16 @@ static void activate(GtkApplication* app, gpointer user_data) { g_simple_action_set_state(capture_action, g_variant_new_boolean(state->capture)); g_simple_action_set_enabled(capture_action, FALSE); } + if (state->layer_shell == NULL) { + state->show_overlay = false; + g_simple_action_set_state(overlay_action, g_variant_new_boolean(state->show_overlay)); + g_simple_action_set_enabled(overlay_action, FALSE); + } int n_monitors = gdk_display_get_n_monitors(gdk_display); for (int i = 0; i < n_monitors; i++) { GdkMonitor *monitor = gdk_display_get_monitor(gdk_display, i); - wd_add_output(state, gdk_wayland_monitor_get_wl_output(monitor)); + wd_add_output(state, gdk_wayland_monitor_get_wl_output(monitor), display); } g_signal_connect(gdk_display, "monitor-added", G_CALLBACK(monitor_added), state); diff --git a/src/meson.build b/src/meson.build index 56cbc7f..26cbd2f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -14,6 +14,7 @@ executable( 'outputs.c', 'render.c', 'glviewport.c', + 'overlay.c', resources, ], dependencies : [ diff --git a/src/outputs.c b/src/outputs.c index b4ecde1..522b619 100644 --- a/src/outputs.c +++ b/src/outputs.c @@ -1,4 +1,3 @@ - /* * Copyright (C) 2019 cyclopsian * Copyright (C) 2017-2019 emersion @@ -34,6 +33,7 @@ #include #include #include +#include #include #include @@ -44,6 +44,7 @@ #include "wlr-output-management-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" #include "wlr-screencopy-unstable-v1-client-protocol.h" +#include "wlr-layer-shell-unstable-v1-client-protocol.h" static void noop() { // This space is intentionally left blank @@ -171,32 +172,54 @@ static void wd_frame_destroy(struct wd_frame *frame) { free(frame); } +static int create_shm_file(size_t size, const char *fmt, ...) { + char *shm_name = NULL; + int fd = -1; + + va_list vl; + va_start(vl, fmt); + int result = vasprintf(&shm_name, fmt, vl); + va_end(vl); + + if (result == -1) { + fprintf(stderr, "asprintf: %s\n", strerror(errno)); + shm_name = NULL; + return -1; + } + + fd = shm_open(shm_name, O_CREAT | O_RDWR, 0); + if (fd == -1) { + fprintf(stderr, "shm_open: %s\n", strerror(errno)); + free(shm_name); + return -1; + } + shm_unlink(shm_name); + free(shm_name); + + if (ftruncate(fd, size) == -1) { + fprintf(stderr, "ftruncate: %s\n", strerror(errno)); + close(fd); + return -1; + } + return fd; +} + static void capture_buffer(void *data, struct zwlr_screencopy_frame_v1 *copy_frame, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) { struct wd_frame *frame = data; - char *shm_name = NULL; if (format != WL_SHM_FORMAT_ARGB8888 && format != WL_SHM_FORMAT_XRGB8888 && format != WL_SHM_FORMAT_ABGR8888 && format != WL_SHM_FORMAT_XBGR8888) { goto err; } - if (asprintf(&shm_name, "/wd-%s", frame->output->name) == -1) { - fprintf(stderr, "asprintf: %s\n", strerror(errno)); - shm_name = NULL; - goto err; - } - frame->capture_fd = shm_open(shm_name, O_CREAT | O_RDWR, 0); - if (frame->capture_fd == -1) { - fprintf(stderr, "shm_open: %s\n", strerror(errno)); - goto err; - } - shm_unlink(shm_name); - free(shm_name); - size_t size = stride * height; - ftruncate(frame->capture_fd, size); + frame->capture_fd = create_shm_file(size, "/wd-%s", frame->output->name); + if (frame->capture_fd == -1) { + goto err; + } + frame->pool = wl_shm_create_pool(frame->output->state->shm, frame->capture_fd, size); frame->buffer = wl_shm_pool_create_buffer(frame->pool, 0, @@ -210,9 +233,6 @@ static void capture_buffer(void *data, return; err: - if (shm_name != NULL) { - free(shm_name); - } wd_frame_destroy(frame); } @@ -302,6 +322,9 @@ static void wd_output_destroy(struct wd_output *output) { wl_list_for_each_safe(frame, frame_tmp, &output->frames, link) { wd_frame_destroy(frame); } + if (output->state->layer_shell != NULL) { + wd_destroy_overlay(output); + } zxdg_output_v1_destroy(output->xdg_output); free(output->name); free(output); @@ -452,8 +475,20 @@ static void head_handle_scale(void *data, static void head_handle_finished(void *data, struct zwlr_output_head_v1 *wlr_head) { struct wd_head *head = data; + struct wd_state *state = head->state; wl_list_remove(&head->link); wd_head_destroy(head); + + uint32_t counter = 0; + wl_list_for_each(head, &state->heads, link) { + if (head->id != counter) { + head->id = counter; + if (head->output != NULL) { + wd_redraw_overlay(head->output); + } + } + counter++; + } } static const struct zwlr_output_head_v1_listener head_listener = { @@ -478,6 +513,7 @@ static void output_manager_handle_head(void *data, head->state = state; head->wlr_head = wlr_head; head->scale = 1.0; + head->id = wl_list_length(&state->heads); wl_list_init(&head->modes); wl_list_insert(state->heads.prev, &head->link); @@ -513,6 +549,9 @@ static void registry_handle_global(void *data, struct wl_registry *registry, } else if(strcmp(interface, zwlr_screencopy_manager_v1_interface.name) == 0) { state->copy_manager = wl_registry_bind(registry, name, &zwlr_screencopy_manager_v1_interface, version); + } else if(strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { + state->layer_shell = wl_registry_bind(registry, name, + &zwlr_layer_shell_v1_interface, version); } else if(strcmp(interface, wl_shm_interface.name) == 0) { state->shm = wl_registry_bind(registry, name, &wl_shm_interface, version); } @@ -532,11 +571,12 @@ void wd_add_output_management_listener(struct wd_state *state, struct wl_display_roundtrip(display); } -static struct wd_head *wd_find_head(struct wd_state *state, +struct wd_head *wd_find_head(struct wd_state *state, struct wd_output *output) { struct wd_head *head; wl_list_for_each(head, &state->heads, link) { if (output->name != NULL && strcmp(output->name, head->name) == 0) { + head->output = output; return head; } } @@ -576,7 +616,14 @@ static void output_logical_size(void *data, struct zxdg_output_v1 *zxdg_output_v static void output_name(void *data, struct zxdg_output_v1 *zxdg_output_v1, const char *name) { struct wd_output *output = data; + if (output->name != NULL) { + free(output->name); + } output->name = strdup(name); + struct wd_head *head = wd_find_head(output->state, output); + if (head != NULL) { + wd_ui_reset_head(head, WD_FIELD_NAME); + } } static const struct zxdg_output_v1_listener output_listener = { @@ -587,7 +634,8 @@ static const struct zxdg_output_v1_listener output_listener = { .description = noop }; -void wd_add_output(struct wd_state *state, struct wl_output *wl_output) { +void wd_add_output(struct wd_state *state, struct wl_output *wl_output, + struct wl_display *display) { struct wd_output *output = calloc(1, sizeof(*output)); output->state = state; output->wl_output = wl_output; @@ -596,6 +644,10 @@ void wd_add_output(struct wd_state *state, struct wl_output *wl_output) { wl_list_init(&output->frames); zxdg_output_v1_add_listener(output->xdg_output, &output_listener, output); wl_list_insert(output->state->outputs.prev, &output->link); + if (state->layer_shell != NULL && state->show_overlay) { + wl_display_roundtrip(display); + wd_create_overlay(output); + } } void wd_remove_output(struct wd_state *state, struct wl_output *wl_output, @@ -634,6 +686,7 @@ struct wd_state *wd_state_create(void) { struct wd_state *state = calloc(1, sizeof(*state)); state->zoom = 1.; state->capture = true; + state->show_overlay = true; wl_list_init(&state->heads); wl_list_init(&state->outputs); wl_list_init(&state->render.heads); @@ -658,7 +711,12 @@ void wd_state_destroy(struct wd_state *state) { wl_list_for_each_safe(output, output_tmp, &state->outputs, link) { wd_output_destroy(output); } - zwlr_screencopy_manager_v1_destroy(state->copy_manager); + if (state->layer_shell != NULL) { + zwlr_layer_shell_v1_destroy(state->layer_shell); + } + if (state->copy_manager != NULL) { + zwlr_screencopy_manager_v1_destroy(state->copy_manager); + } zwlr_output_manager_v1_destroy(state->output_manager); zxdg_output_manager_v1_destroy(state->xdg_output_manager); wl_shm_destroy(state->shm); diff --git a/src/overlay.c b/src/overlay.c new file mode 100644 index 0000000..69150da --- /dev/null +++ b/src/overlay.c @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2019 cyclopsian + + * 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 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 X CONSORTIUM 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. + */ + +#define _GNU_SOURCE +#include +#include +#include + +#include +#include + +#include "wdisplays.h" + +#include "wlr-layer-shell-unstable-v1-client-protocol.h" + +#define TEXT_SIZE 128 +#define SCREEN_MARGIN_PERCENT 0.02 + +static void layer_surface_configure(void *data, + struct zwlr_layer_surface_v1 *surface, + uint32_t serial, uint32_t width, uint32_t height) { + struct wd_output *output = data; + gtk_widget_set_size_request(output->overlay_window, width, height); + zwlr_layer_surface_v1_ack_configure(surface, serial); +} + +static void layer_surface_closed(void *data, + struct zwlr_layer_surface_v1 *surface) { +} + +static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { + .configure = layer_surface_configure, + .closed = layer_surface_closed, +}; + +static inline int min(int a, int b) { + return a < b ? a : b; +} + +static char *make_overlay_string(struct wd_head *head) { + return g_strdup_printf("%d", head->id + 1); +} + +static PangoLayout *create_text_layout(struct wd_head *head, + PangoContext *pango) { + g_autofree gchar *str = make_overlay_string(head); + PangoLayout *layout = pango_layout_new(pango); + + PangoAttrList *attrs = pango_attr_list_new(); + pango_attr_list_insert(attrs, pango_attr_size_new(TEXT_SIZE * PANGO_SCALE)); + pango_layout_set_attributes(layout, attrs); + pango_attr_list_unref(attrs); + pango_layout_set_text(layout, str, -1); + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE); + return layout; +} + +static void resize(struct wd_output *output) { + struct wd_head *head = wd_find_head(output->state, output); + + uint32_t screen_width = head->custom_mode.width; + uint32_t screen_height = head->custom_mode.height; + if (head->mode != NULL) { + screen_width = head->mode->width; + screen_height = head->mode->height; + } + uint32_t margin = min(screen_width, screen_height) * SCREEN_MARGIN_PERCENT; + + GdkWindow *window = gtk_widget_get_window(output->overlay_window); + PangoContext *pango = gtk_widget_get_pango_context(output->overlay_window); + PangoLayout *layout = create_text_layout(head, pango); + + int width; + int height; + pango_layout_get_pixel_size(layout, &width, &height); + g_object_unref(layout); + width = min(width, screen_width - margin * 2); + height = min(height, screen_height - margin * 2); + + zwlr_layer_surface_v1_set_margin(output->overlay_layer_surface, + margin, margin, margin, margin); + zwlr_layer_surface_v1_set_size(output->overlay_layer_surface, + width, height); + + struct wl_surface *surface = gdk_wayland_window_get_wl_surface(window); + wl_surface_commit(surface); + + GdkDisplay *display = gdk_window_get_display(window); + wl_display_roundtrip(gdk_wayland_display_get_wl_display(display)); +} + +void wd_redraw_overlay(struct wd_output *output) { + if (output->overlay_window != NULL) { + resize(output); + gtk_widget_queue_draw(output->overlay_window); + } +} + +void window_realize(GtkWidget *widget, gpointer data) { + GdkWindow *window = gtk_widget_get_window(widget); + gdk_wayland_window_set_use_custom_surface(window); +} + +void window_map(GtkWidget *widget, gpointer data) { + struct wd_output *output = data; + + GdkWindow *window = gtk_widget_get_window(widget); + cairo_region_t *region = cairo_region_create(); + gdk_window_input_shape_combine_region(window, region, 0, 0); + cairo_region_destroy(region); + + struct wl_surface *surface = gdk_wayland_window_get_wl_surface(window); + + output->overlay_layer_surface = zwlr_layer_shell_v1_get_layer_surface( + output->state->layer_shell, surface, output->wl_output, + ZWLR_LAYER_SHELL_V1_LAYER_TOP, "output-overlay"); + + zwlr_layer_surface_v1_add_listener(output->overlay_layer_surface, + &layer_surface_listener, output); + + zwlr_layer_surface_v1_set_anchor(output->overlay_layer_surface, + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT); + + resize(output); +} + +void window_unmap(GtkWidget *widget, gpointer data) { + struct wd_output *output = data; + zwlr_layer_surface_v1_destroy(output->overlay_layer_surface); +} + +gboolean window_draw(GtkWidget *widget, cairo_t *cr, gpointer data) { + struct wd_output *output = data; + struct wd_head *head = wd_find_head(output->state, output); + + GtkStyleContext *style_ctx = gtk_widget_get_style_context(widget); + GdkRGBA fg; + gtk_style_context_get_color(style_ctx, GTK_STATE_FLAG_NORMAL, &fg); + + int width = gtk_widget_get_allocated_width(widget); + int height = gtk_widget_get_allocated_height(widget); + gtk_render_background(style_ctx, cr, 0, 0, width, height); + + PangoContext *pango = gtk_widget_get_pango_context(widget); + PangoLayout *layout = create_text_layout(head, pango); + + gdk_cairo_set_source_rgba(cr, &fg); + pango_cairo_show_layout(cr, layout); + g_object_unref(layout); + return TRUE; +} + +void wd_create_overlay(struct wd_output *output) { + output->overlay_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_decorated(GTK_WINDOW(output->overlay_window), FALSE); + gtk_window_set_resizable(GTK_WINDOW(output->overlay_window), FALSE); + gtk_widget_add_events(output->overlay_window, GDK_STRUCTURE_MASK); + + g_signal_connect(output->overlay_window, "realize", + G_CALLBACK(window_realize), output); + g_signal_connect(output->overlay_window, "map", + G_CALLBACK(window_map), output); + g_signal_connect(output->overlay_window, "unmap", + G_CALLBACK(window_unmap), output); + g_signal_connect(output->overlay_window, "draw", + G_CALLBACK(window_draw), output); + + GtkStyleContext *style_ctx = gtk_widget_get_style_context( + output->overlay_window); + gtk_style_context_add_class(style_ctx, "output-overlay"); + gtk_widget_show(output->overlay_window); +} + +void wd_destroy_overlay(struct wd_output *output) { + if (output->overlay_window != NULL) { + gtk_widget_destroy(output->overlay_window); + output->overlay_window = NULL; + } +} diff --git a/src/wdisplays.h b/src/wdisplays.h index a8fcd1e..feb5dd1 100644 --- a/src/wdisplays.h +++ b/src/wdisplays.h @@ -44,6 +44,8 @@ struct zwlr_output_head_v1; struct zwlr_output_manager_v1; struct zwlr_screencopy_manager_v1; struct zwlr_screencopy_frame_v1; +struct zwlr_layer_shell_v1; +struct zwlr_layer_surface_v1; struct _GtkWidget; typedef struct _GtkWidget GtkWidget; @@ -74,6 +76,8 @@ struct wd_output { char *name; struct wl_list frames; + GtkWidget *overlay_window; + struct zwlr_layer_surface_v1 *overlay_layer_surface; }; struct wd_frame { @@ -126,6 +130,7 @@ struct wd_head { struct wd_render_head_data *render; cairo_surface_t *surface; + uint32_t id; char *name, *description; int32_t phys_width, phys_height; // mm struct wl_list modes; @@ -191,6 +196,7 @@ struct wd_state { struct zxdg_output_manager_v1 *xdg_output_manager; struct zwlr_output_manager_v1 *output_manager; struct zwlr_screencopy_manager_v1 *copy_manager; + struct zwlr_layer_shell_v1 *layer_shell; struct wl_shm *shm; struct wl_list heads; struct wl_list outputs; @@ -199,6 +205,7 @@ struct wd_state { bool apply_pending; bool autoapply; bool capture; + bool show_overlay; double zoom; struct wd_render_head_data *clicked; @@ -249,7 +256,7 @@ void wd_fatal_error(int status, const char *message); /* * Add an output to the list of screen captured outputs. */ -void wd_add_output(struct wd_state *state, struct wl_output *wl_output); +void wd_add_output(struct wd_state *state, struct wl_output *wl_output, struct wl_display *display); /* * Remove an output from the list of screen captured outputs. @@ -261,6 +268,11 @@ void wd_remove_output(struct wd_state *state, struct wl_output *wl_output, struc * output is disabled. */ struct wd_output *wd_find_output(struct wd_state *state, struct wd_head *head); + +/* + * Finds the head associated with a given output. + */ +struct wd_head *wd_find_head(struct wd_state *state, struct wd_output *output); /* * Starts listening for output management events from the compositor. */ @@ -313,13 +325,31 @@ void wd_ui_show_error(struct wd_state *state, const char *message); * Compiles the GL shaders. */ struct wd_gl_data *wd_gl_setup(void); + /* * Renders the GL scene. */ void wd_gl_render(struct wd_gl_data *res, struct wd_render_data *info, uint64_t tick); + /* * Destroys the GL shaders. */ void wd_gl_cleanup(struct wd_gl_data *res); +/* + * Create an overlay on the screen that contains a textual description of the + * output. This is to help the user identify the outputs visually. + */ +void wd_create_overlay(struct wd_output *output); + +/* + * Forces redrawing of the screen overlay on the given output. + */ +void wd_redraw_overlay(struct wd_output *output); + +/* + * Destroys the screen overlay on the given output. + */ +void wd_destroy_overlay(struct wd_output *output); + #endif