From 58dec1391f54fa26e1dd43c1e97c822f95c2103e Mon Sep 17 00:00:00 2001 From: Jason Francis Date: Sat, 7 Mar 2020 10:37:19 -0500 Subject: [PATCH] Port to GTK4 (WIP) --- README.md | 2 +- resources/head.ui | 402 ++++++++------------------ resources/meson.build | 4 +- resources/wdisplays.ui | 365 ++++++------------------ src/main.c | 620 ++++++++++++++++++++--------------------- src/meson.build | 12 +- src/overlay.c | 37 ++- src/wdisplays.h | 6 +- 8 files changed, 543 insertions(+), 905 deletions(-) diff --git a/README.md b/README.md index 167c121..3cb81df 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ setups. Build requirements are: - meson -- GTK+3 +- GTK4 - epoxy - wayland-client diff --git a/resources/head.ui b/resources/head.ui index 9329acd..c04b463 100644 --- a/resources/head.ui +++ b/resources/head.ui @@ -1,5 +1,4 @@ - @@ -7,23 +6,6 @@ 1 10 - - False - - - True - False - 10 - 10 - 10 - 10 - vertical - - - - - - 16383 1 @@ -51,430 +33,278 @@ 10 - True - False 8 8 8 8 8 16 - True + 1 _Enabled - True - True - False + 1 start - True - True - + 1 + + 1 + 0 + - - 1 - 0 - - True - False - True + 1 word-char end 0 + + 1 + 1 + - - 1 - 1 - - True - True + 1 start 9 scale_adjustment 2 1 - + + 1 + 3 + - - 1 - 3 - - True - False DPI _Scale - True + 1 scale 1 + + 0 + 3 + - - 0 - 3 - - True - False _Position - True + 1 pos_x 1 + + 0 + 4 + - - 0 - 4 - - True - False Description 1 + + 0 + 1 + - - 0 - 1 - - True - False - True + 1 word-char end 0 + + 1 + 2 + - - 1 - 2 - - True - False Physical Size 1 + + 0 + 2 + - - 0 - 2 - - True - False Si_ze - True + 1 width 1 + + 0 + 5 + - - 0 - 5 - - True - False start 8 - True - True + refresh + 1 9 - number refresh_adjustment 3 - True + 1 if-valid - - False - True - 0 - - True - False Hz - - False - True - 1 - + + 1 + 6 + - - 1 - 6 - - True - False _Refresh Rate - True + 1 1 + + 0 + 6 + - - 0 - 6 - - True - True - True - transforms - - - + 1 + 1 + + 1 + 7 + - - 1 - 7 - - True - False _Transform - True + 1 1 + + 0 + 7 + - - 0 - 7 - _Flipped - True - True - False + 1 start - True - True - + 1 + + 1 + 8 + - - 1 - 8 - - True - False 8 - True - True + 1 6 0 - number pos_x_adjustment - True + 1 if-valid + + 0 + 0 + - - 0 - 0 - - True - True + 1 6 0 - number pos_y_adjustment - True + 1 if-valid + + 2 + 0 + - - 2 - 0 - - True - True + width + 1 4 0 - number width_adjustment - True + 1 if-valid + + 0 + 1 + - - 0 - 1 - 20 - True - False × + + 1 + 1 + - - 1 - 1 - - True - True + height + 1 4 0 - number height_adjustment - True + 1 if-valid + + 2 + 1 + - - 2 - 1 - - True - True - True + 1 + 1 Select Mode Preset - 8 8 - modes - - - True - False - view-more-symbolic - - + view-more-symbolic + + 3 + 1 + - - 3 - 1 - - - - - - - - - - - 1 - 4 - 2 - - - - - - - - - - - False - rotate_button - - - True - False - 10 - 10 - 10 - 10 - vertical - - - True - True - True - transform.rotate_0 - Don't Rotate - - - False - True - 0 - - - - - True - True - True - transform.rotate_90 - Rotate 90° - - - False - True - 1 - - - - - True - True - True - transform.rotate_180 - Rotate 180° - - - False - True - 2 - - - - - True - True - True - transform.rotate_270 - Rotate 270° - - - False - True - 3 - + + 1 + 4 + 2 + diff --git a/resources/meson.build b/resources/meson.build index 97a99a6..e853324 100644 --- a/resources/meson.build +++ b/resources/meson.build @@ -1,9 +1,9 @@ gnome = import('gnome') resources = gnome.compile_resources( - 'waydisplay-resources', 'resources.xml', + 'wdisplays-resources', 'resources.xml', source_dir : '.', - c_name : 'waydisplay_resources') + c_name : 'wdisplays_resources') scour = find_program('scour', required: false) diff --git a/resources/wdisplays.ui b/resources/wdisplays.ui index 35a85c8..bcabd25 100644 --- a/resources/wdisplays.ui +++ b/resources/wdisplays.ui @@ -1,8 +1,6 @@ - - 1 10 @@ -11,143 +9,56 @@ 1 10 - - False - - - True - False - 10 - 10 - 10 - 10 - vertical - - - True - True - True - app.auto-apply - _Automatically Apply Changes - - - False - True - 0 - - - - - True - True - True - app.capture-screens - Show Screen Contents - - - False - True - 1 - - - - - True - True - True - app.show-overlay - Overlay Screen Names - - - False - True - 2 - - - - - - False wdisplays - - True - False - True - False vertical - False - True + 0 start error - True - False - - - - False + 1 + 0 + + 6 - end - - False - False - 0 - - + - False + 0 16 - True - False - True + 1 0 - - True - True - 2 - - - True - True - 0 - - - False - True - 0 - - - True - True + + 0 + 0 + 0 + 1 400 - True + 1 - True - True + 1 canvas_horiz canvas_vert 400 @@ -156,232 +67,136 @@ - - True - False - - True - False vertical - True - False center - 8 - 8 8 8 8 - True + 1 heads_stack - - False - True - 0 - - True - False crossfade - - False - True - 1 - - - False - False - - - True - True - 1 - - - -1 - - True - False - True - True - True + 0 + 1 + 1 + 0 - - True - - True - False crossfade - - True - False - wdisplays - False - True - - - True - False - expand + + title + + + wdisplays + 0 + 1 - - True - True - True - Zoom Out - + - - True - False + + 1 + 1 + Zoom Out + app.zoom-out zoom-out-symbolic + - - - - True - True - 0 - True - - - - - True - True - True - Zoom Reset - - - - - True - True - 1 - True - - - - - True - True - True - Zoom In - - - True - False - zoom-in-symbolic + + 1 + 1 + Zoom Reset + app.zoom-reset + - + + + 1 + 1 + Zoom In + app.zoom-in + zoom-in-symbolic + + + + - - True - True - 2 - True - - - - - - True - True - True - main_menu - - - True - False + + + 1 + 1 open-menu-symbolic - - end - 1 - - + - - title - - - True - False - - - True - False - Apply Changes? - - - - - _Apply - True - True - True - True - - - - - end - - - - - _Cancel - True - True - True - True - - - - 1 - - - - + apply - 1 - + + + + + Apply Changes? + + + + + _Apply + 1 + 1 + 1 + app.apply-changes + + + + + + _Cancel + 1 + 1 + 1 + app.cancel-changes + + + + + diff --git a/src/main.c b/src/main.c index 9b90550..b1110fe 100644 --- a/src/main.c +++ b/src/main.c @@ -2,7 +2,7 @@ /* Copyright (C) 2019 cyclopsian */ #include -#include +#include #include "wdisplays.h" #include "glviewport.h" @@ -20,22 +20,18 @@ __attribute__((noreturn)) void wd_fatal_error(int status, const char *message) { #define MAX_ZOOM 1000. #define CANVAS_MARGIN 40 +static const char *HEAD_PREFIX = "head"; static const char *MODE_PREFIX = "mode"; -static const char *TRANSFORM_PREFIX = "transform"; +static const char *ROTATE_PREFIX = "rotate"; static const char *APP_PREFIX = "app"; -#define NUM_ROTATIONS 4 -static const char *ROTATE_IDS[NUM_ROTATIONS] = { - "rotate_0", "rotate_90", "rotate_180", "rotate_270" -}; - -static int get_rotate_index(enum wl_output_transform transform) { +static int32_t get_rotate_value(enum wl_output_transform transform) { if (transform == WL_OUTPUT_TRANSFORM_90 || transform == WL_OUTPUT_TRANSFORM_FLIPPED_90) { - return 1; + return 90; } else if (transform == WL_OUTPUT_TRANSFORM_180 || transform == WL_OUTPUT_TRANSFORM_FLIPPED_180) { - return 2; + return 180; } else if (transform == WL_OUTPUT_TRANSFORM_270 || transform == WL_OUTPUT_TRANSFORM_FLIPPED_270) { - return 3; + return 270; } return 0; } @@ -45,6 +41,7 @@ static bool has_changes(const struct wd_state *state) { for (GList *form_iter = forms; form_iter != NULL; form_iter = form_iter->next) { GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form_iter->data), "builder")); const struct wd_head *head = g_object_get_data(G_OBJECT(form_iter->data), "head"); + GAction *rotate_action = G_ACTION(g_object_get_data(G_OBJECT(form_iter->data), "rotate_action")); if (head->enabled != gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "enabled")))) { return TRUE; } @@ -71,16 +68,8 @@ static bool has_changes(const struct wd_state *state) { if (r / 1000. != gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "refresh")))) { return TRUE; } - for (int i = 0; i < NUM_ROTATIONS; i++) { - GtkWidget *rotate = GTK_WIDGET(gtk_builder_get_object(builder, ROTATE_IDS[i])); - gboolean selected; - g_object_get(rotate, "active", &selected, NULL); - if (selected) { - if (i != get_rotate_index(head->transform)) { - return TRUE; - } - break; - } + if (g_variant_get_int32(g_action_get_state(rotate_action)) != get_rotate_value(head->transform)) { + return TRUE; } bool flipped = head->transform == WL_OUTPUT_TRANSFORM_FLIPPED || head->transform == WL_OUTPUT_TRANSFORM_FLIPPED_90 @@ -103,20 +92,13 @@ void fill_output_from_form(struct wd_head_config *output, GtkWidget *form) { output->width = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "width"))); output->height = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "height"))); output->refresh = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "refresh"))) * 1000.; + int32_t rotate = g_variant_get_int32(g_action_get_state(G_ACTION(g_object_get_data(G_OBJECT(form), "rotate_action")))); gboolean flipped = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "flipped"))); - for (int i = 0; i < NUM_ROTATIONS; i++) { - GtkWidget *rotate = GTK_WIDGET(gtk_builder_get_object(builder, ROTATE_IDS[i])); - gboolean selected; - g_object_get(rotate, "active", &selected, NULL); - if (selected) { - switch (i) { - case 0: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED : WL_OUTPUT_TRANSFORM_NORMAL; break; - case 1: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED_90 : WL_OUTPUT_TRANSFORM_90; break; - case 2: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED_180 : WL_OUTPUT_TRANSFORM_180; break; - case 3: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED_270 : WL_OUTPUT_TRANSFORM_270; break; - } - break; - } + switch (rotate) { + case 0: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED : WL_OUTPUT_TRANSFORM_NORMAL; break; + case 90: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED_90 : WL_OUTPUT_TRANSFORM_90; break; + case 180: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED_180 : WL_OUTPUT_TRANSFORM_180; break; + case 270: output->transform = flipped ? WL_OUTPUT_TRANSFORM_FLIPPED_270 : WL_OUTPUT_TRANSFORM_270; break; } } @@ -131,8 +113,8 @@ static gboolean send_apply(gpointer data) { wl_list_insert(outputs, &output->link); fill_output_from_form(output, GTK_WIDGET(form_iter->data)); } - GdkWindow *window = gtk_widget_get_window(state->stack); - GdkDisplay *display = gdk_window_get_display(window); + GdkSurface *surface = gtk_native_get_surface(gtk_widget_get_native(state->stack)); + GdkDisplay *display = gdk_surface_get_display(surface); struct wl_display *wl_display = gdk_wayland_display_get_wl_display(display); wd_apply_state(state, outputs, wl_display); state->apply_pending = FALSE; @@ -143,7 +125,7 @@ static void apply_state(struct wd_state *state) { gtk_stack_set_visible_child_name(GTK_STACK(state->header_stack), "title"); if (!state->autoapply) { gtk_style_context_add_class(gtk_widget_get_style_context(state->spinner), "visible"); - gtk_overlay_set_overlay_pass_through(GTK_OVERLAY(state->overlay), state->spinner, FALSE); + gtk_widget_set_can_target(state->spinner, TRUE); gtk_spinner_start(GTK_SPINNER(state->spinner)); gtk_widget_set_sensitive(state->stack_switcher, FALSE); @@ -273,15 +255,15 @@ static void update_cursor(struct wd_state *state) { break; } } - GdkWindow *window = gtk_widget_get_window(state->canvas); + GdkSurface *surface = gtk_native_get_surface(gtk_widget_get_native(state->canvas)); if (any_hovered) { - gdk_window_set_cursor(window, state->grab_cursor); + gdk_surface_set_cursor(surface, state->grab_cursor); } else if (state->clicked != NULL) { - gdk_window_set_cursor(window, state->grabbing_cursor); + gdk_surface_set_cursor(surface, state->grabbing_cursor); } else if (state->panning) { - gdk_window_set_cursor(window, state->move_cursor); + gdk_surface_set_cursor(surface, state->move_cursor); } else { - gdk_window_set_cursor(window, NULL); + gdk_surface_set_cursor(surface, NULL); } } @@ -294,15 +276,13 @@ static inline void flip_anim(uint64_t *timer, uint64_t tick) { } } -static void update_hovered(struct wd_state *state) { - GdkDisplay *display = gdk_display_get_default(); - GdkWindow *window = gtk_widget_get_window(state->canvas); +static void update_hovered(struct wd_state *state, + gdouble mouse_x, gdouble mouse_y) { if (!gtk_widget_get_realized(state->canvas)) { return; } GdkFrameClock *clock = gtk_widget_get_frame_clock(state->canvas); uint64_t tick = gdk_frame_clock_get_frame_time(clock); - g_autoptr(GList) seats = gdk_display_list_seats(display); bool any_hovered = FALSE; struct wd_render_head_data *render; wl_list_for_each(render, &state->render.heads, link) { @@ -315,18 +295,10 @@ static void update_hovered(struct wd_state *state) { render->hovered = TRUE; any_hovered = TRUE; } else if (state->clicked == NULL) { - for (GList *iter = seats; iter != NULL; iter = iter->next) { - double mouse_x; - double mouse_y; - - GdkDevice *pointer = gdk_seat_get_pointer(GDK_SEAT(iter->data)); - gdk_window_get_device_position_double(window, pointer, &mouse_x, &mouse_y, NULL); - if (mouse_x >= render->x1 && mouse_x < render->x2 && - mouse_y >= render->y1 && mouse_y < render->y2) { - render->hovered = TRUE; - any_hovered = TRUE; - break; - } + if (mouse_x >= render->x1 && mouse_x < render->x2 && + mouse_y >= render->y1 && mouse_y < render->y2) { + render->hovered = TRUE; + any_hovered = TRUE; } } if (init_hovered != render->hovered) { @@ -348,18 +320,8 @@ static inline void color_to_float_array(GtkStyleContext *ctx, } static unsigned form_get_rotation(GtkWidget *form) { - GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form), "builder")); - unsigned rot; - for (rot = 0; rot < NUM_ROTATIONS; rot++) { - GtkWidget *rotate = GTK_WIDGET(gtk_builder_get_object(builder, - ROTATE_IDS[rot])); - gboolean selected; - g_object_get(rotate, "active", &selected, NULL); - if (selected) { - return rot; - } - } - return -1; + int32_t rotate = g_variant_get_int32(g_action_get_state(G_ACTION(g_object_get_data(G_OBJECT(form), "rotate_action")))); + return rotate / 90; } #define SWAP(_type, _a, _b) { _type _tmp = (_a); (_a) = (_b); (_b) = _tmp; } @@ -443,67 +405,81 @@ static void update_sensitivity(GtkWidget *form) { } } -static void select_rotate_option(GtkWidget *form, GtkWidget *model_button) { - GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form), "builder")); +static void rotate_selected(GSimpleAction *action, GVariant *param, gpointer data) { + const struct wd_head *head = g_object_get_data(G_OBJECT(data), "head"); + GtkBuilder *builder = GTK_BUILDER(g_object_get_data(data, "builder")); GtkWidget *rotate_button = GTK_WIDGET(gtk_builder_get_object(builder, "rotate_button")); - for (int i = 0; i < NUM_ROTATIONS; i++) { - GtkWidget *rotate = GTK_WIDGET(gtk_builder_get_object(builder, ROTATE_IDS[i])); - gboolean selected = model_button == rotate; - g_object_set(rotate, "active", selected, NULL); - if (selected) { - g_autofree gchar *rotate_text = NULL; - g_object_get(rotate, "text", &rotate_text, NULL); - gtk_button_set_label(GTK_BUTTON(rotate_button), rotate_text); + GMenuModel *menu = gtk_menu_button_get_menu_model(GTK_MENU_BUTTON(rotate_button)); + int items = g_menu_model_get_n_items(menu); + for (int i = 0; i < items; i++) { + g_autoptr(GVariant) target = g_menu_model_get_item_attribute_value(menu, i, G_MENU_ATTRIBUTE_TARGET, NULL); + g_autoptr(GVariant) label = g_menu_model_get_item_attribute_value(menu, i, G_MENU_ATTRIBUTE_LABEL, NULL); + if (g_variant_get_int32(target) == g_variant_get_int32(param)) { + gtk_menu_button_set_label(GTK_MENU_BUTTON(rotate_button), g_variant_get_string(label, NULL)); + break; } } -} - -static void rotate_selected(GSimpleAction *action, GVariant *param, gpointer data) { - select_rotate_option(GTK_WIDGET(data), g_object_get_data(G_OBJECT(action), "widget")); - const struct wd_head *head = g_object_get_data(G_OBJECT(data), "head"); + g_simple_action_set_state(action, param); update_ui(head->state); } -static void select_mode_option(GtkWidget *form, int32_t w, int32_t h, int32_t r) { - GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form), "builder")); - GtkWidget *mode_box = GTK_WIDGET(gtk_builder_get_object(builder, "mode_box")); - g_autoptr(GList) children = gtk_container_get_children(GTK_CONTAINER(mode_box)); - for (GList *child = children; child != NULL; child = child->next) { - const struct wd_mode *mode = g_object_get_data(G_OBJECT(child->data), "mode"); - g_object_set(child->data, "active", w == mode->width && h == mode->height && r == mode->refresh, NULL); - } +static GVariant *create_mode_variant(int32_t w, int32_t h, int32_t r) { + GVariant * const children[] = { + g_variant_new_int32(w), + g_variant_new_int32(h), + g_variant_new_int32(r), + }; + return g_variant_new_tuple(children, G_N_ELEMENTS(children)); } -static void update_mode_entries(GtkWidget *form, int32_t w, int32_t h, int32_t r) { - GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form), "builder")); - GtkWidget *width = GTK_WIDGET(gtk_builder_get_object(builder, "width")); - GtkWidget *height = GTK_WIDGET(gtk_builder_get_object(builder, "height")); - GtkWidget *refresh = GTK_WIDGET(gtk_builder_get_object(builder, "refresh")); +struct vid_mode { + int32_t width; + int32_t height; + int32_t refresh; +}; - gtk_spin_button_set_value(GTK_SPIN_BUTTON(width), w); - gtk_spin_button_set_value(GTK_SPIN_BUTTON(height), h); - gtk_spin_button_set_value(GTK_SPIN_BUTTON(refresh), r / 1000.); +static void unpack_mode_variant(GVariant *value, struct vid_mode *mode) { + g_autoptr(GVariant) width = g_variant_get_child_value(value, 0); + mode->width = g_variant_get_int32(width); + g_autoptr(GVariant) height = g_variant_get_child_value(value, 1); + mode->height = g_variant_get_int32(height); + g_autoptr(GVariant) refresh = g_variant_get_child_value(value, 2); + mode->refresh = g_variant_get_int32(refresh); +} + +static void mode_spin_changed(GtkSpinButton *spin_button, gpointer data) { + GtkWidget *form = data; + const struct wd_head *head = g_object_get_data(G_OBJECT(form), "head"); + struct vid_mode mode; + GAction *mode_action = G_ACTION(g_object_get_data(G_OBJECT(form), "mode_action")); + GVariant *value = g_action_get_state(mode_action); + unpack_mode_variant(value, &mode); + if (strcmp(gtk_widget_get_name(GTK_WIDGET(spin_button)), "width") == 0) { + mode.width = gtk_spin_button_get_value(spin_button); + } else if (strcmp(gtk_widget_get_name(GTK_WIDGET(spin_button)), "height") == 0) { + mode.height = gtk_spin_button_get_value(spin_button); + } else if (strcmp(gtk_widget_get_name(GTK_WIDGET(spin_button)), "refresh") == 0) { + mode.refresh = gtk_spin_button_get_value(spin_button) * 1000.; + } + g_action_activate(mode_action, create_mode_variant(mode.width, mode.height, mode.refresh)); + update_ui(head->state); } static void mode_selected(GSimpleAction *action, GVariant *param, gpointer data) { GtkWidget *form = data; const struct wd_head *head = g_object_get_data(G_OBJECT(form), "head"); - const struct wd_mode *mode = g_object_get_data(G_OBJECT(action), "mode"); + GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form), "builder")); + struct vid_mode mode; + unpack_mode_variant(param, &mode); - update_mode_entries(form, mode->width, mode->height, mode->refresh); - select_mode_option(form, mode->width, mode->height, mode->refresh); + g_simple_action_set_state(action, param); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "width")), mode.width); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "height")), mode.height); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "refresh")), mode.refresh / 1000.); update_ui(head->state); } // END FORM CALLBACKS -static void clear_menu(GtkWidget *box, GActionMap *action_map) { - g_autoptr(GList) children = gtk_container_get_children(GTK_CONTAINER(box)); - for (GList *child = children; child != NULL; child = child->next) { - g_action_map_remove_action(action_map, strchr(gtk_actionable_get_action_name(GTK_ACTIONABLE(child->data)), '.') + 1); - gtk_container_remove(GTK_CONTAINER(box), GTK_WIDGET(child->data)); - } -} - static void update_head_form(GtkWidget *form, unsigned int fields) { GtkBuilder *builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form), "builder")); GtkWidget *description = GTK_WIDGET(gtk_builder_get_object(builder, "description")); @@ -512,12 +488,14 @@ static void update_head_form(GtkWidget *form, unsigned int fields) { GtkWidget *scale = GTK_WIDGET(gtk_builder_get_object(builder, "scale")); GtkWidget *pos_x = GTK_WIDGET(gtk_builder_get_object(builder, "pos_x")); GtkWidget *pos_y = GTK_WIDGET(gtk_builder_get_object(builder, "pos_y")); - GtkWidget *mode_box = GTK_WIDGET(gtk_builder_get_object(builder, "mode_box")); + GtkWidget *mode_button = GTK_WIDGET(gtk_builder_get_object(builder, "mode_button")); GtkWidget *flipped = GTK_WIDGET(gtk_builder_get_object(builder, "flipped")); const struct wd_head *head = g_object_get_data(G_OBJECT(form), "head"); + GAction *mode_action = G_ACTION(g_object_get_data(G_OBJECT(form), "mode_action")); + GAction *rotate_action = G_ACTION(g_object_get_data(G_OBJECT(form), "rotate_action")); if (fields & WD_FIELD_NAME) { - gtk_container_child_set(GTK_CONTAINER(head->state->stack), form, "title", head->name, NULL); + g_object_set(gtk_stack_get_page(GTK_STACK(head->state->stack), form), "title", head->name, NULL); } if (fields & WD_FIELD_DESCRIPTION) { gtk_label_set_text(GTK_LABEL(description), head->description); @@ -538,27 +516,17 @@ static void update_head_form(GtkWidget *form, unsigned int fields) { } if (fields & WD_FIELD_MODE) { - GActionMap *mode_actions = G_ACTION_MAP(g_object_get_data(G_OBJECT(form), "mode-group")); - clear_menu(mode_box, mode_actions); + GMenu *mode_menu = g_menu_new(); struct wd_mode *mode; + g_autofree gchar *action = g_strdup_printf("%s.%s", HEAD_PREFIX, MODE_PREFIX); wl_list_for_each(mode, &head->modes, link) { g_autofree gchar *name = g_strdup_printf("%d×%d@%0.3fHz", mode->width, mode->height, mode->refresh / 1000.); - GSimpleAction *action = g_simple_action_new(name, NULL); - g_action_map_add_action(G_ACTION_MAP(mode_actions), G_ACTION(action)); - g_signal_connect(action, "activate", G_CALLBACK(mode_selected), form); - g_object_set_data(G_OBJECT(action), "mode", mode); - g_object_unref(action); - - GtkWidget *button = gtk_model_button_new(); - g_autoptr(GString) prefixed_name = g_string_new(MODE_PREFIX); - g_string_append(prefixed_name, "."); - g_string_append(prefixed_name, name); - gtk_actionable_set_action_name(GTK_ACTIONABLE(button), prefixed_name->str); - g_object_set(button, "role", GTK_BUTTON_ROLE_RADIO, "text", name, NULL); - gtk_box_pack_start(GTK_BOX(mode_box), button, FALSE, FALSE, 0); - g_object_set_data(G_OBJECT(button), "mode", mode); - gtk_widget_show_all(button); + GMenuItem *item = g_menu_item_new(name, action); + g_menu_item_set_attribute_value(item, G_MENU_ATTRIBUTE_TARGET, + create_mode_variant(mode->width, mode->height, mode->refresh)); + g_menu_append_item(mode_menu, item); } + gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(mode_button), G_MENU_MODEL(mode_menu)); // Mode entries int w = head->custom_mode.width; int h = head->custom_mode.height; @@ -579,14 +547,12 @@ static void update_head_form(GtkWidget *form, unsigned int fields) { } } - update_mode_entries(form, w, h, r); - select_mode_option(form, w, h, r); - gtk_widget_show_all(mode_box); + g_action_change_state(mode_action, create_mode_variant(w, h, r)); } if (fields & WD_FIELD_TRANSFORM) { - int active_rotate = get_rotate_index(head->transform); - select_rotate_option(form, GTK_WIDGET(gtk_builder_get_object(builder, ROTATE_IDS[active_rotate]))); + int active_rotate = get_rotate_value(head->transform); + g_action_change_state(rotate_action, g_variant_new_int32(active_rotate)); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(flipped), head->transform == WL_OUTPUT_TRANSFORM_FLIPPED @@ -625,36 +591,48 @@ void wd_ui_reset_heads(struct wd_state *state) { GtkWidget *mode_button = GTK_WIDGET(gtk_builder_get_object(builder, "mode_button")); GtkWidget *rotate_button = GTK_WIDGET(gtk_builder_get_object(builder, "rotate_button")); - GSimpleActionGroup *mode_actions = g_simple_action_group_new(); - gtk_widget_insert_action_group(mode_button, MODE_PREFIX, G_ACTION_GROUP(mode_actions)); - g_object_set_data(G_OBJECT(form), "mode-group", mode_actions); - g_object_unref(mode_actions); + GSimpleActionGroup *head_actions = g_simple_action_group_new(); + gtk_widget_insert_action_group(mode_button, HEAD_PREFIX, G_ACTION_GROUP(head_actions)); + gtk_widget_insert_action_group(rotate_button, HEAD_PREFIX, G_ACTION_GROUP(head_actions)); - GSimpleActionGroup *transform_actions = g_simple_action_group_new(); - gtk_widget_insert_action_group(rotate_button, TRANSFORM_PREFIX, G_ACTION_GROUP(transform_actions)); - g_object_unref(transform_actions); + GMenu *rotate_menu = g_menu_new(); + g_menu_append(rotate_menu, "Don't Rotate", "head.rotate(0)"); + g_menu_append(rotate_menu, "Rotate 90°", "head.rotate(90)"); + g_menu_append(rotate_menu, "Rotate 180°", "head.rotate(180)"); + g_menu_append(rotate_menu, "Rotate 270°", "head.rotate(270)"); + gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(rotate_button), G_MENU_MODEL(rotate_menu)); - for (int i = 0; i < NUM_ROTATIONS; i++) { - GtkWidget *button = GTK_WIDGET(gtk_builder_get_object(builder, ROTATE_IDS[i])); - g_object_set(button, "role", GTK_BUTTON_ROLE_RADIO, NULL); - GSimpleAction *action = g_simple_action_new(ROTATE_IDS[i], NULL); - g_action_map_add_action(G_ACTION_MAP(transform_actions), G_ACTION(action)); - g_signal_connect(action, "activate", G_CALLBACK(rotate_selected), form); - g_object_set_data(G_OBJECT(action), "widget", button); - g_object_unref(action); - } + static const GVariantType * const mode_types[] = { + G_VARIANT_TYPE_INT32, + G_VARIANT_TYPE_INT32, + G_VARIANT_TYPE_INT32 + }; + GSimpleAction *action = g_simple_action_new_stateful("mode", + g_variant_type_new_tuple(mode_types, G_N_ELEMENTS(mode_types)), + create_mode_variant(0, 0, 0)); + g_action_map_add_action(G_ACTION_MAP(head_actions), G_ACTION(action)); + g_signal_connect(action, "change-state", G_CALLBACK(mode_selected), form); + g_object_set_data(G_OBJECT(form), "mode_action", action); + g_object_unref(action); + + action = g_simple_action_new_stateful(ROTATE_PREFIX, G_VARIANT_TYPE_INT32, + g_variant_new_int32(0)); + g_action_map_add_action(G_ACTION_MAP(head_actions), G_ACTION(action)); + g_signal_connect(action, "change-state", G_CALLBACK(rotate_selected), form); + g_object_set_data(G_OBJECT(form), "rotate_action", action); + g_object_unref(action); + + g_object_unref(head_actions); update_head_form(form, WD_FIELDS_ALL); - gtk_widget_show_all(form); - g_signal_connect_swapped(gtk_builder_get_object(builder, "enabled"), "toggled", G_CALLBACK(update_sensitivity), form); + g_signal_connect(gtk_builder_get_object(builder, "width"), "value-changed", G_CALLBACK(mode_spin_changed), form); + g_signal_connect(gtk_builder_get_object(builder, "height"), "value-changed", G_CALLBACK(mode_spin_changed), form); + g_signal_connect(gtk_builder_get_object(builder, "refresh"), "value-changed", G_CALLBACK(mode_spin_changed), form); g_signal_connect_swapped(gtk_builder_get_object(builder, "enabled"), "toggled", G_CALLBACK(update_ui), state); g_signal_connect_swapped(gtk_builder_get_object(builder, "scale"), "value-changed", G_CALLBACK(update_ui), state); g_signal_connect_swapped(gtk_builder_get_object(builder, "pos_x"), "value-changed", G_CALLBACK(update_ui), state); g_signal_connect_swapped(gtk_builder_get_object(builder, "pos_y"), "value-changed", G_CALLBACK(update_ui), state); - g_signal_connect_swapped(gtk_builder_get_object(builder, "width"), "value-changed", G_CALLBACK(update_ui), state); - g_signal_connect_swapped(gtk_builder_get_object(builder, "height"), "value-changed", G_CALLBACK(update_ui), state); - g_signal_connect_swapped(gtk_builder_get_object(builder, "refresh"), "value-changed", G_CALLBACK(update_ui), state); g_signal_connect_swapped(gtk_builder_get_object(builder, "flipped"), "toggled", G_CALLBACK(update_ui), state); } else { @@ -705,7 +683,7 @@ void wd_ui_reset_all(struct wd_state *state) { void wd_ui_apply_done(struct wd_state *state, struct wl_list *outputs) { gtk_style_context_remove_class(gtk_widget_get_style_context(state->spinner), "visible"); - gtk_overlay_set_overlay_pass_through(GTK_OVERLAY(state->overlay), state->spinner, TRUE); + gtk_widget_set_can_target(state->spinner, FALSE); gtk_spinner_stop(GTK_SPINNER(state->spinner)); gtk_widget_set_sensitive(state->stack_switcher, TRUE); @@ -786,15 +764,18 @@ static void zoom_to(struct wd_state *state, double zoom) { update_zoom(state); } -static void zoom_out(struct wd_state *state) { +static void zoom_out(GSimpleAction *action, GVariant *param, gpointer data) { + struct wd_state *state = data; zoom_to(state, state->zoom * 0.75); } -static void zoom_reset(struct wd_state *state) { +static void zoom_reset(GSimpleAction *action, GVariant *param, gpointer data) { + struct wd_state *state = data; zoom_to(state, DEFAULT_ZOOM); } -static void zoom_in(struct wd_state *state) { +static void zoom_in(GSimpleAction *action, GVariant *param, gpointer data) { + struct wd_state *state = data; zoom_to(state, state->zoom / 0.75); } @@ -923,82 +904,68 @@ static void set_clicked_head(struct wd_state *state, state->clicked = clicked; } -static gboolean canvas_click(GtkWidget *widget, GdkEvent *event, - gpointer data) { +static void canvas_drag_begin(GtkGestureDrag *drag, + gdouble mouse_x, gdouble mouse_y, gpointer data) { struct wd_state *state = data; - if (event->button.type == GDK_BUTTON_PRESS) { - if (event->button.button == 1) { + guint button = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(drag)); + + if (button == 1) { + struct wd_render_head_data *render; + state->clicked = NULL; + wl_list_for_each(render, &state->render.heads, link) { + if (mouse_x >= render->x1 && mouse_x < render->x2 && + mouse_y >= render->y1 && mouse_y < render->y2) { + set_clicked_head(state, render); + state->click_offset.x = mouse_x - render->x1; + state->click_offset.y = mouse_y - render->y1; + break; + } + } + if (state->clicked != NULL) { + wl_list_remove(&state->clicked->link); + wl_list_insert(&state->render.heads, &state->clicked->link); + struct wd_render_head_data *render; - state->clicked = NULL; wl_list_for_each(render, &state->render.heads, link) { - double mouse_x = event->button.x; - double mouse_y = event->button.y; - if (mouse_x >= render->x1 && mouse_x < render->x2 && - mouse_y >= render->y1 && mouse_y < render->y2) { - set_clicked_head(state, render); - state->click_offset.x = event->button.x - render->x1; - state->click_offset.y = event->button.y - render->y1; + render->updated_at = 0; + render->preview = TRUE; + } + gtk_gl_area_queue_render(GTK_GL_AREA(state->canvas)); + g_autoptr(GList) forms = gtk_container_get_children(GTK_CONTAINER(state->stack)); + for (GList *form_iter = forms; form_iter != NULL; form_iter = form_iter->next) { + const struct wd_head *other = g_object_get_data(G_OBJECT(form_iter->data), "head"); + if (state->clicked == other->render) { + gtk_stack_set_visible_child(GTK_STACK(state->stack), form_iter->data); break; } } - if (state->clicked != NULL) { - wl_list_remove(&state->clicked->link); - wl_list_insert(&state->render.heads, &state->clicked->link); - - struct wd_render_head_data *render; - wl_list_for_each(render, &state->render.heads, link) { - render->updated_at = 0; - render->preview = TRUE; - } - gtk_gl_area_queue_render(GTK_GL_AREA(state->canvas)); - g_autoptr(GList) forms = gtk_container_get_children(GTK_CONTAINER(state->stack)); - for (GList *form_iter = forms; form_iter != NULL; form_iter = form_iter->next) { - const struct wd_head *other = g_object_get_data(G_OBJECT(form_iter->data), "head"); - if (state->clicked == other->render) { - gtk_stack_set_visible_child(GTK_STACK(state->stack), form_iter->data); - break; - } - } - } - } else if (event->button.button == 2) { - state->panning = TRUE; - state->pan_last.x = event->button.x; - state->pan_last.y = event->button.y; } + } else if (button == 2) { + state->panning = TRUE; + state->pan_last.x = mouse_x; + state->pan_last.y = mouse_y; } - return TRUE; -} - -static gboolean canvas_release(GtkWidget *widget, GdkEvent *event, - gpointer data) { - struct wd_state *state = data; - if (event->button.button == 1) { - set_clicked_head(state, NULL); - } - if (event->button.button == 2) { - state->panning = FALSE; - } - update_cursor(state); - return TRUE; } #define SNAP_DIST 6. -static gboolean canvas_motion(GtkWidget *widget, GdkEvent *event, - gpointer data) { +static void canvas_drag_update(GtkGestureDrag *drag, + gdouble mouse_x, gdouble mouse_y, gpointer data) { struct wd_state *state = data; - if (event->motion.state & GDK_BUTTON2_MASK) { + guint button = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(drag)); + + if (button == 2) { GtkAdjustment *xadj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(state->scroller)); GtkAdjustment *yadj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(state->scroller)); - double delta_x = event->motion.x - state->pan_last.x; - double delta_y = event->motion.y - state->pan_last.y; + double delta_x = mouse_x - state->pan_last.x; + double delta_y = mouse_y - state->pan_last.y; gtk_adjustment_set_value(xadj, gtk_adjustment_get_value(xadj) + delta_x); gtk_adjustment_set_value(yadj, gtk_adjustment_get_value(yadj) + delta_y); - state->pan_last.x = event->motion.x; - state->pan_last.y = event->motion.y; + state->pan_last.x = mouse_x; + state->pan_last.y = mouse_y; queue_canvas_draw(state); } - if ((event->motion.state & GDK_BUTTON1_MASK) && state->clicked != NULL) { + if (button == 1 && state->clicked != NULL) { GtkWidget *form = NULL; g_autoptr(GList) forms = gtk_container_get_children(GTK_CONTAINER(state->stack)); for (GList *form_iter = forms; form_iter != NULL; form_iter = form_iter->next) { @@ -1024,9 +991,9 @@ static gboolean canvas_motion(GtkWidget *widget, GdkEvent *event, SWAP(int, size.x, size.y); } struct wd_point tl = { - .x = (event->motion.x - state->click_offset.x + .x = (mouse_x - state->click_offset.x + state->render.x_origin + state->render.scroll_x) / state->zoom, - .y = (event->motion.y - state->click_offset.y + .y = (mouse_y - state->click_offset.y + state->render.y_origin + state->render.scroll_y) / state->zoom }; const struct wd_point br = { @@ -1036,9 +1003,12 @@ static gboolean canvas_motion(GtkWidget *widget, GdkEvent *event, struct wd_point new_pos = tl; float snap = SNAP_DIST / state->zoom; + GdkEvent *event = gtk_get_current_event(); + GdkModifierType mod_state = gdk_event_get_modifier_state(event); + for (GList *form_iter = forms; form_iter != NULL; form_iter = form_iter->next) { const struct wd_head *other = g_object_get_data(G_OBJECT(form_iter->data), "head"); - if (other->render != state->clicked && !(event->motion.state & GDK_SHIFT_MASK)) { + if (other->render != state->clicked && !(mod_state & GDK_SHIFT_MASK)) { GtkBuilder *other_builder = GTK_BUILDER(g_object_get_data(G_OBJECT(form_iter->data), "builder")); double x1 = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(other_builder, "pos_x"))); double y1 = gtk_spin_button_get_value(GTK_SPIN_BUTTON(gtk_builder_get_object(other_builder, "pos_y"))); @@ -1088,96 +1058,87 @@ static gboolean canvas_motion(GtkWidget *widget, GdkEvent *event, gtk_spin_button_set_value(GTK_SPIN_BUTTON(pos_y), new_pos.y); } } - update_hovered(state); - return TRUE; } -static gboolean canvas_enter(GtkWidget *widget, GdkEvent *event, - gpointer data) { +static void canvas_drag_end(GtkGestureDrag *drag, + gdouble mouse_x, gdouble mouse_y, gpointer data) { struct wd_state *state = data; - if (!(event->crossing.state & GDK_BUTTON1_MASK)) { + guint button = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(drag)); + if (button == 1) { set_clicked_head(state, NULL); } - if (!(event->crossing.state & GDK_BUTTON2_MASK)) { + if (button == 2) { state->panning = FALSE; } update_cursor(state); - return TRUE; } -static gboolean canvas_leave(GtkWidget *widget, GdkEvent *event, - gpointer data) { +static void canvas_motion(GtkEventControllerMotion *controller, + gdouble mouse_x, gdouble mouse_y, gpointer data) { + struct wd_state *state = data; + update_hovered(state, mouse_x, mouse_y); +} + +static void canvas_enter(GtkEventControllerMotion *controller, + gdouble x, gdouble y, GdkCrossingMode crossing_mode, gpointer data) { + struct wd_state *state = data; + GdkEvent *event = gtk_get_current_event(); + GdkModifierType mod_state = gdk_event_get_modifier_state(event); + + if (!(mod_state & GDK_BUTTON1_MASK)) { + set_clicked_head(state, NULL); + } + if (!(mod_state & GDK_BUTTON2_MASK)) { + state->panning = FALSE; + } + update_cursor(state); +} + +static void canvas_leave(GtkEventControllerMotion *controller, + GdkCrossingMode crossing_mode, gpointer data) { struct wd_state *state = data; struct wd_render_head_data *render; wl_list_for_each(render, &state->render.heads, link) { render->hovered = FALSE; } update_tick_callback(state); - return TRUE; } -static gboolean canvas_scroll(GtkWidget *widget, GdkEvent *event, - gpointer data) { +static gboolean canvas_scroll(GtkEventControllerScroll *controller, + gdouble delta_x, gdouble delta_y, gpointer data) { struct wd_state *state = data; - if (event->scroll.state & GDK_CONTROL_MASK) { - switch (event->scroll.direction) { - case GDK_SCROLL_UP: - zoom_in(state); - break; - case GDK_SCROLL_DOWN: - zoom_out(state); - break; - case GDK_SCROLL_SMOOTH: - if (event->scroll.delta_y) - zoom_to(state, state->zoom * pow(0.75, event->scroll.delta_y)); - break; - default: - break; - } + GdkEvent *event = gtk_get_current_event(); + GdkModifierType mod_state = gdk_event_get_modifier_state(event); + + if (mod_state & GDK_CONTROL_MASK) { + if (delta_y) + zoom_to(state, state->zoom * pow(0.75, delta_y)); } else { GtkAdjustment *xadj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(state->scroller)); GtkAdjustment *yadj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(state->scroller)); double xstep = gtk_adjustment_get_step_increment(xadj); double ystep = gtk_adjustment_get_step_increment(yadj); - switch (event->scroll.direction) { - case GDK_SCROLL_UP: - gtk_adjustment_set_value(yadj, gtk_adjustment_get_value(yadj) - ystep); - break; - case GDK_SCROLL_DOWN: - gtk_adjustment_set_value(yadj, gtk_adjustment_get_value(yadj) + ystep); - break; - case GDK_SCROLL_LEFT: - gtk_adjustment_set_value(xadj, gtk_adjustment_get_value(xadj) - xstep); - break; - case GDK_SCROLL_RIGHT: - gtk_adjustment_set_value(xadj, gtk_adjustment_get_value(xadj) + xstep); - break; - case GDK_SCROLL_SMOOTH: - if (event->scroll.delta_x) - gtk_adjustment_set_value(xadj, gtk_adjustment_get_value(xadj) + xstep * event->scroll.delta_x); - if (event->scroll.delta_y) - gtk_adjustment_set_value(yadj, gtk_adjustment_get_value(yadj) + ystep * event->scroll.delta_y); - break; - default: - break; - } + if (delta_x) + gtk_adjustment_set_value(xadj, gtk_adjustment_get_value(xadj) + xstep * delta_x); + if (delta_y) + gtk_adjustment_set_value(yadj, gtk_adjustment_get_value(yadj) + ystep * delta_y); } - return FALSE; + return TRUE; } -static void canvas_resize(GtkWidget *widget, GdkRectangle *allocation, - gpointer data) { +static void canvas_resize(GtkWidget *widget, gint width, gint height, + gint baseline, gpointer data) { struct wd_state *state = data; update_scroll_size(state); } -static void cancel_changes(GtkButton *button, gpointer data) { +static void cancel_changes(GSimpleAction *action, GVariant *param, gpointer data) { struct wd_state *state = data; gtk_stack_set_visible_child_name(GTK_STACK(state->header_stack), "title"); wd_ui_reset_all(state); } -static void apply_changes(GtkButton *button, gpointer data) { +static void apply_changes(GSimpleAction *action, GVariant *param, gpointer data) { apply_state(data); } @@ -1195,8 +1156,8 @@ static void info_bar_animation_done(GObject *object, GParamSpec *pspec, gpointer static void auto_apply_selected(GSimpleAction *action, GVariant *param, gpointer data) { struct wd_state *state = data; - state->autoapply = !state->autoapply; - g_simple_action_set_state(action, g_variant_new_boolean(state->autoapply)); + state->autoapply = g_variant_get_boolean(param); + g_simple_action_set_state(action, param); } static gboolean redraw_canvas(GtkWidget *widget, GdkFrameClock *frame_clock, gpointer data) { @@ -1211,15 +1172,15 @@ static gboolean redraw_canvas(GtkWidget *widget, GdkFrameClock *frame_clock, gpo static void capture_selected(GSimpleAction *action, GVariant *param, gpointer data) { struct wd_state *state = data; - state->capture = !state->capture; - g_simple_action_set_state(action, g_variant_new_boolean(state->capture)); + state->capture = g_variant_get_boolean(param); + g_simple_action_set_state(action, param); 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)); + state->show_overlay = g_variant_get_boolean(param); + g_simple_action_set_state(action, param); struct wd_output *output; wl_list_for_each(output, &state->outputs, link) { @@ -1245,15 +1206,18 @@ static void activate(GtkApplication* app, gpointer user_data) { GtkCssProvider *css_provider = gtk_css_provider_new(); gtk_css_provider_load_from_resource(css_provider, "/style.css"); - gtk_style_context_add_provider_for_screen(gdk_screen_get_default(), GTK_STYLE_PROVIDER(css_provider), + gtk_style_context_add_provider_for_display(gdk_display, GTK_STYLE_PROVIDER(css_provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); - state->grab_cursor = gdk_cursor_new_from_name(gdk_display, "grab"); - state->grabbing_cursor = gdk_cursor_new_from_name(gdk_display, "grabbing"); - state->move_cursor = gdk_cursor_new_from_name(gdk_display, "move"); + GdkCursor *default_cursor = gdk_cursor_new_from_name("default", NULL); + state->grab_cursor = gdk_cursor_new_from_name("grab", default_cursor); + state->grabbing_cursor = gdk_cursor_new_from_name("grabbing", default_cursor); + state->move_cursor = gdk_cursor_new_from_name("move", default_cursor); + g_object_unref(default_cursor); GtkBuilder *builder = gtk_builder_new_from_resource("/wdisplays.ui"); GtkWidget *window = GTK_WIDGET(gtk_builder_get_object(builder, "heads_window")); + GtkWidget *paned = GTK_WIDGET(gtk_builder_get_object(builder, "paned")); state->header_stack = GTK_WIDGET(gtk_builder_get_object(builder, "header_stack")); state->stack_switcher = GTK_WIDGET(gtk_builder_get_object(builder, "heads_stack_switcher")); state->stack = GTK_WIDGET(gtk_builder_get_object(builder, "heads_stack")); @@ -1267,36 +1231,37 @@ static void activate(GtkApplication* app, gpointer user_data) { state->info_label = GTK_WIDGET(gtk_builder_get_object(builder, "heads_info_label")); state->menu_button = GTK_WIDGET(gtk_builder_get_object(builder, "menu_button")); - gtk_builder_add_callback_symbol(builder, "apply_changes", G_CALLBACK(apply_changes)); - gtk_builder_add_callback_symbol(builder, "cancel_changes", G_CALLBACK(cancel_changes)); - gtk_builder_add_callback_symbol(builder, "zoom_out", G_CALLBACK(zoom_out)); - gtk_builder_add_callback_symbol(builder, "zoom_reset", G_CALLBACK(zoom_reset)); - gtk_builder_add_callback_symbol(builder, "zoom_in", G_CALLBACK(zoom_in)); - gtk_builder_add_callback_symbol(builder, "info_response", G_CALLBACK(info_response)); - gtk_builder_add_callback_symbol(builder, "destroy", G_CALLBACK(cleanup)); - gtk_builder_connect_signals(builder, state); - gtk_box_set_homogeneous(GTK_BOX(gtk_builder_get_object(builder, "zoom_box")), FALSE); + g_signal_connect(window, "destroy", G_CALLBACK(cleanup), state); + + g_object_set(paned, "shrink-child1", FALSE, "shrink-child2", FALSE, NULL); state->canvas = wd_gl_viewport_new(); - gtk_container_add(GTK_CONTAINER(state->scroller), state->canvas); - gtk_widget_add_events(state->canvas, GDK_POINTER_MOTION_MASK - | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_SCROLL_MASK - | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK); + gtk_widget_set_hexpand(state->canvas, TRUE); + gtk_widget_set_vexpand(state->canvas, TRUE); g_signal_connect(state->canvas, "realize", G_CALLBACK(canvas_realize), state); g_signal_connect(state->canvas, "render", G_CALLBACK(canvas_render), state); g_signal_connect(state->canvas, "unrealize", G_CALLBACK(canvas_unrealize), state); - g_signal_connect(state->canvas, "button-press-event", G_CALLBACK(canvas_click), state); - g_signal_connect(state->canvas, "button-release-event", G_CALLBACK(canvas_release), state); - g_signal_connect(state->canvas, "enter-notify-event", G_CALLBACK(canvas_enter), state); - g_signal_connect(state->canvas, "leave-notify-event", G_CALLBACK(canvas_leave), state); - g_signal_connect(state->canvas, "motion-notify-event", G_CALLBACK(canvas_motion), state); - g_signal_connect(state->canvas, "scroll-event", G_CALLBACK(canvas_scroll), state); g_signal_connect(state->canvas, "size-allocate", G_CALLBACK(canvas_resize), state); - gtk_gl_area_set_use_es(GTK_GL_AREA(state->canvas), TRUE); gtk_gl_area_set_required_version(GTK_GL_AREA(state->canvas), 2, 0); - gtk_gl_area_set_has_alpha(GTK_GL_AREA(state->canvas), TRUE); + gtk_gl_area_set_use_es(GTK_GL_AREA(state->canvas), TRUE); gtk_gl_area_set_auto_render(GTK_GL_AREA(state->canvas), state->capture); + GtkGesture *canvas_drag_controller = gtk_gesture_drag_new(); + gtk_widget_add_controller(state->canvas, GTK_EVENT_CONTROLLER(canvas_drag_controller)); + GtkEventController *canvas_motion_controller = gtk_event_controller_motion_new(); + gtk_widget_add_controller(state->canvas, canvas_motion_controller); + GtkEventController *canvas_scroll_controller = gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES); + gtk_widget_add_controller(state->canvas, canvas_scroll_controller); + g_signal_connect(canvas_drag_controller, "drag-begin", G_CALLBACK(canvas_drag_begin), state); + g_signal_connect(canvas_drag_controller, "drag-update", G_CALLBACK(canvas_drag_update), state); + g_signal_connect(canvas_drag_controller, "drag-end", G_CALLBACK(canvas_drag_end), state); + g_signal_connect(canvas_motion_controller, "enter", G_CALLBACK(canvas_enter), state); + g_signal_connect(canvas_motion_controller, "leave", G_CALLBACK(canvas_leave), state); + g_signal_connect(canvas_motion_controller, "motion", G_CALLBACK(canvas_motion), state); + g_signal_connect(canvas_scroll_controller, "scroll", G_CALLBACK(canvas_scroll), state); + + gtk_container_add(GTK_CONTAINER(state->scroller), state->canvas); + GtkAdjustment *scroll_x_adj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(state->scroller)); GtkAdjustment *scroll_y_adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(state->scroller)); g_signal_connect_swapped(scroll_x_adj, "value-changed", G_CALLBACK(queue_canvas_draw), state); @@ -1305,24 +1270,51 @@ static void activate(GtkApplication* app, gpointer user_data) { update_zoom(state); GSimpleActionGroup *main_actions = g_simple_action_group_new(); - gtk_widget_insert_action_group(state->menu_button, APP_PREFIX, G_ACTION_GROUP(main_actions)); + gtk_widget_insert_action_group(window, APP_PREFIX, G_ACTION_GROUP(main_actions)); g_object_unref(main_actions); - GSimpleAction *autoapply_action = g_simple_action_new_stateful("auto-apply", NULL, + GSimpleAction *action = g_simple_action_new("apply-changes", NULL); + g_signal_connect(action, "activate", G_CALLBACK(apply_changes), state); + g_action_map_add_action(G_ACTION_MAP(main_actions), G_ACTION(action)); + + action = g_simple_action_new("cancel-changes", NULL); + g_signal_connect(action, "activate", G_CALLBACK(cancel_changes), state); + g_action_map_add_action(G_ACTION_MAP(main_actions), G_ACTION(action)); + + action = g_simple_action_new("zoom-out", NULL); + g_signal_connect(action, "activate", G_CALLBACK(zoom_out), state); + g_action_map_add_action(G_ACTION_MAP(main_actions), G_ACTION(action)); + + action = g_simple_action_new("zoom-reset", NULL); + g_signal_connect(action, "activate", G_CALLBACK(zoom_reset), state); + g_action_map_add_action(G_ACTION_MAP(main_actions), G_ACTION(action)); + + action = g_simple_action_new("zoom-in", NULL); + g_signal_connect(action, "activate", G_CALLBACK(zoom_in), state); + g_action_map_add_action(G_ACTION_MAP(main_actions), G_ACTION(action)); + + action = g_simple_action_new_stateful("auto-apply", NULL, g_variant_new_boolean(state->autoapply)); - g_signal_connect(autoapply_action, "activate", G_CALLBACK(auto_apply_selected), state); - g_action_map_add_action(G_ACTION_MAP(main_actions), G_ACTION(autoapply_action)); + g_signal_connect(action, "change-state", G_CALLBACK(auto_apply_selected), state); + g_action_map_add_action(G_ACTION_MAP(main_actions), G_ACTION(action)); GSimpleAction *capture_action = g_simple_action_new_stateful("capture-screens", NULL, g_variant_new_boolean(state->capture)); - g_signal_connect(capture_action, "activate", G_CALLBACK(capture_selected), state); + g_signal_connect(capture_action, "change-state", 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_signal_connect(overlay_action, "change-state", G_CALLBACK(overlay_selected), state); g_action_map_add_action(G_ACTION_MAP(main_actions), G_ACTION(overlay_action)); + GMenu *main_menu = g_menu_new(); + g_menu_append(main_menu, "_Automatically Apply Changes", "app.auto-apply"); + g_menu_append(main_menu, "_Show Screen Contents", "app.capture-screens"); + g_menu_append(main_menu, "_Overlay Screen Names", "app.show-overlay"); + gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(state->menu_button), G_MENU_MODEL(main_menu)); + + g_signal_connect(state->info_bar, "response", G_CALLBACK(info_response), state); /* 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); @@ -1357,14 +1349,14 @@ static void activate(GtkApplication* app, gpointer user_data) { g_signal_connect(gdk_display, "monitor-removed", G_CALLBACK(monitor_removed), state); gtk_application_add_window(app, GTK_WINDOW(window)); - gtk_widget_show_all(window); g_object_unref(builder); update_tick_callback(state); + + gtk_window_present(GTK_WINDOW(window)); } // END GLOBAL CALLBACKS int main(int argc, char *argv[]) { - g_setenv("GDK_GL", "gles", FALSE); GtkApplication *app = gtk_application_new(WDISPLAYS_APP_ID, G_APPLICATION_FLAGS_NONE); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); diff --git a/src/meson.build b/src/meson.build index 02139b4..87f2e67 100644 --- a/src/meson.build +++ b/src/meson.build @@ -2,10 +2,10 @@ cc = meson.get_compiler('c') m_dep = cc.find_library('m', required : false) rt_dep = cc.find_library('rt', required : false) -gdk = dependency('gdk-3.0') -gtk = dependency('gtk+-3.0') -assert(gdk.get_pkgconfig_variable('targets').split().contains('wayland'), 'Wayland GDK backend not present') +gtk = dependency('gtk4') +assert(gtk.get_pkgconfig_variable('targets').split().contains('wayland'), 'Wayland GDK backend not present') epoxy = dependency('epoxy') +gmodule_export = dependency('gmodule-export-2.0') configure_file(input: 'config.h.in', output: 'config.h', configuration: conf) @@ -16,7 +16,7 @@ executable( 'outputs.c', 'render.c', 'glviewport.c', - 'overlay.c', + #'overlay.c', resources, ], dependencies : [ @@ -25,7 +25,9 @@ executable( wayland_client, client_protos, epoxy, - gtk + gtk, + gmodule_export ], + c_args: ['-DGDK_DISABLE_DEPRECATED', '-DGTK_DISABLE_DEPRECATED'], install: true ) diff --git a/src/overlay.c b/src/overlay.c index 6ddf21c..4cd6b3f 100644 --- a/src/overlay.c +++ b/src/overlay.c @@ -7,7 +7,7 @@ #include #include -#include +#include #include "wdisplays.h" @@ -39,8 +39,8 @@ static inline int min(int a, int b) { static PangoLayout *create_text_layout(struct wd_head *head, PangoContext *pango, GtkStyleContext *style) { GtkStyleContext *desc_style = gtk_style_context_new(); - gtk_style_context_set_screen(desc_style, - gtk_style_context_get_screen(style)); + gtk_style_context_set_display(desc_style, + gtk_style_context_get_display(style)); GtkWidgetPath *desc_path = gtk_widget_path_copy( gtk_style_context_get_path(style)); gtk_widget_path_append_type(desc_path, G_TYPE_NONE); @@ -48,8 +48,7 @@ static PangoLayout *create_text_layout(struct wd_head *head, gtk_style_context_add_class(desc_style, "description"); double desc_font_size = 16.; - gtk_style_context_get(desc_style, GTK_STATE_FLAG_NORMAL, - "font-size", &desc_font_size, NULL); + gtk_style_context_get(desc_style, "font-size", &desc_font_size, NULL); g_autofree gchar *str = g_strdup_printf("%s\n%s", head->name, (int) (desc_font_size * PANGO_SCALE), head->description); @@ -70,7 +69,7 @@ static void resize(struct wd_output *output) { } uint32_t margin = min(screen_width, screen_height) * SCREEN_MARGIN_PERCENT; - GdkWindow *window = gtk_widget_get_window(output->overlay_window); + GdkSurface *surface = gtk_widget_get_surface(output->overlay_window); PangoContext *pango = gtk_widget_get_pango_context(output->overlay_window); GtkStyleContext *style_ctx = gtk_widget_get_style_context( output->overlay_window); @@ -83,7 +82,7 @@ static void resize(struct wd_output *output) { GtkBorder padding; - gtk_style_context_get_padding(style_ctx, GTK_STATE_FLAG_NORMAL, &padding); + gtk_style_context_get_padding(style_ctx, &padding); width = min(width, screen_width - margin * 2) + padding.left + padding.right; @@ -95,10 +94,10 @@ static void resize(struct wd_output *output) { 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); + struct wl_surface *wl_surface = gdk_wayland_surface_get_wl_surface(surface); + wl_surface_commit(wl_surface); - GdkDisplay *display = gdk_window_get_display(window); + GdkDisplay *display = gdk_surface_get_display(surface); wl_display_roundtrip(gdk_wayland_display_get_wl_display(display)); } @@ -110,22 +109,23 @@ void wd_redraw_overlay(struct wd_output *output) { } void window_realize(GtkWidget *widget, gpointer data) { - GdkWindow *window = gtk_widget_get_window(widget); - gdk_wayland_window_set_use_custom_surface(window); + //FIXME - custom surfaces in GTK4 wayland? + GdkSurface *surface = gtk_widget_get_surface(widget); + gdk_wayland_surface_set_use_custom_surface(surface); } void window_map(GtkWidget *widget, gpointer data) { struct wd_output *output = data; - GdkWindow *window = gtk_widget_get_window(widget); + GdkSurface *surface = gtk_widget_get_surface(widget); cairo_region_t *region = cairo_region_create(); - gdk_window_input_shape_combine_region(window, region, 0, 0); + gdk_surface_input_shape_combine_region(surface, region, 0, 0); cairo_region_destroy(region); - struct wl_surface *surface = gdk_wayland_window_get_wl_surface(window); + struct wl_surface *wl_surface = gdk_wayland_surface_get_wl_surface(surface); output->overlay_layer_surface = zwlr_layer_shell_v1_get_layer_surface( - output->state->layer_shell, surface, output->wl_output, + output->state->layer_shell, wl_surface, output->wl_output, ZWLR_LAYER_SHELL_V1_LAYER_TOP, "output-overlay"); zwlr_layer_surface_v1_add_listener(output->overlay_layer_surface, @@ -149,14 +149,14 @@ gboolean window_draw(GtkWidget *widget, cairo_t *cr, gpointer data) { GtkStyleContext *style_ctx = gtk_widget_get_style_context(widget); GdkRGBA fg; - gtk_style_context_get_color(style_ctx, GTK_STATE_FLAG_NORMAL, &fg); + gtk_style_context_get_color(style_ctx, &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); GtkBorder padding; - gtk_style_context_get_padding(style_ctx, GTK_STATE_FLAG_NORMAL, &padding); + gtk_style_context_get_padding(style_ctx, &padding); PangoContext *pango = gtk_widget_get_pango_context(widget); PangoLayout *layout = create_text_layout(head, pango, style_ctx); @@ -171,7 +171,6 @@ 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); diff --git a/src/wdisplays.h b/src/wdisplays.h index e408957..e9a5be3 100644 --- a/src/wdisplays.h +++ b/src/wdisplays.h @@ -335,16 +335,16 @@ 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); +static inline 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); +static inline void wd_redraw_overlay(struct wd_output *output) {} /* * Destroys the screen overlay on the given output. */ -void wd_destroy_overlay(struct wd_output *output); +static inline void wd_destroy_overlay(struct wd_output *output) {} #endif