Skip to content

Commit

Permalink
Fix client side decorations (CSD)
Browse files Browse the repository at this point in the history
This fixes the rendering issues pqiv had on Wayland. Turns out GTK has a
model where if client side decorations are used, as is the case on
Wayland or if GTK_CSD=1 is set, the decorations are part of the
dimensions exposed to the program. So if you resize your window to (h0,w0),
GTK will come around and actually resize it to (h1,w1), and expose these
dimensions to the program. Worse, the program is able to make full use
of these dimensions, including some transparent area surrounding the
actual window. Drawing to coordinate (0,0) of a window will draw outside
the decorated area. There is no API to query the dimensions of the CSD.
GTK devs take the position that drawing straight to the window is a bad
idea anyways (but still provide an API to do that, which is weird), and
apparently do not intend to ever fix this (from my perspective) broken
interface where window decorations aren't transparent to application
developers but also cannot be queried..

The workaround pqiv goes for is to add a box to the window at startup,
and query its allocation once the window is realized, to measure the CSD
dimensions once. They are then added/subtracted as needed. It does this
once for simplicity, which has the downside that if the theme is changed
while pqiv is running, things will break. If anyone complains, it'll be
easy to change this later.
  • Loading branch information
phillipberndt committed Feb 27, 2024
1 parent 8af563e commit 10aad05
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 15 deletions.
3 changes: 2 additions & 1 deletion README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,8 @@ Changelog
pqiv 2.13 (dev)
* Fix `toggle_fullscreen(1/2)` behavior when already fullscreen
* Add `--font` to adjust info box font, use Pango for rendering (See #221)
* Prefer x11 over wayland GDK backend to avoid rendering issues
* Prefer x11 over Wayland GDK backend (it overall provides a better experience)
* Fix Client Side Decorations (CSD), e.g. in Wayland

pqiv 2.12
* Fix external image filters (Fixes #182)
Expand Down
55 changes: 41 additions & 14 deletions pqiv.c
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ gboolean current_image_drawn = FALSE;
GtkWindow *main_window;
gboolean main_window_visible = FALSE;

GtkWidget *main_window_box;
gint csd_left, csd_right, csd_top, csd_bottom, csd_height, csd_width;

// Detection of tiled WMs: They should ignore our resize events
gint requested_main_window_resize_pos_callback_id = -1;
gint requested_main_window_width = -1;
Expand Down Expand Up @@ -2108,8 +2111,8 @@ gboolean main_window_resize_callback(gpointer user_data) {/*{{{*/

// Resize if this has not worked before, but accept a slight deviation (might be round-off error)
if(main_window_width >= 0 && abs(main_window_width - new_window_width) + abs(main_window_height - new_window_height) > 1) {
requested_main_window_width = new_window_width;
requested_main_window_height = new_window_height;
requested_main_window_width = new_window_width + csd_width;
requested_main_window_height = new_window_height + csd_height;
gtk_window_resize(main_window, new_window_width / screen_scale_factor, new_window_height / screen_scale_factor);
}

Expand Down Expand Up @@ -4910,7 +4913,7 @@ gboolean window_draw_thumbnail_montage(cairo_t *cr_arg) {/*{{{*/
#endif

D_UNLOCK(file_tree);
return TRUE;
return FALSE;
}/*}}}*/
#endif
void window_clear_background_pixmap() {/*{{{*/
Expand Down Expand Up @@ -5069,6 +5072,15 @@ gboolean window_draw_callback(GtkWidget *widget, cairo_t *cr_arg, gpointer user_
// Resume the action queue
action_done();

// Clip shadow area out
if(!main_window_in_fullscreen) {
cairo_new_path(cr_arg);
cairo_rectangle(cr_arg, csd_left, csd_top, main_window_width, main_window_height);
cairo_close_path(cr_arg);
cairo_clip(cr_arg);
cairo_translate(cr_arg, csd_left, csd_top);
}

// We have different drawing modes. The default, below, is to draw a single
// image.
#ifndef CONFIGURED_WITHOUT_MONTAGE_MODE
Expand Down Expand Up @@ -5338,7 +5350,7 @@ gboolean window_draw_callback(GtkWidget *widget, cairo_t *cr_arg, gpointer user_
x2 = (pango_extents.x + pango_extents.width) / PANGO_SCALE;
y2 = (pango_extents.y + pango_extents.height) / PANGO_SCALE;

if(x2 > main_window_width - 10 * screen_scale_factor && !main_window_in_fullscreen) {
if(x2 > main_window_width - csd_width - 10 * screen_scale_factor && !main_window_in_fullscreen) {
cairo_restore(cr_arg);
cairo_save(cr_arg);
g_object_unref(pango_layout);
Expand Down Expand Up @@ -5371,12 +5383,7 @@ gboolean window_draw_callback(GtkWidget *widget, cairo_t *cr_arg, gpointer user_
}
#endif

// TODO Maybe this will need to be changed someday; the GDK Wayland backend
// currently does not draw window borders if the draw callback reports
// success. Anyway, it also draws the borders at the wrong place (well
// within the window rather than around it), so I'll leave things as they
// are for the time being.
return TRUE;
return FALSE;
}/*}}}*/
#if GTK_MAJOR_VERSION < 3
gboolean window_expose_callback(GtkWidget *widget, GdkEvent *event, gpointer user_data) {/*{{{*/
Expand Down Expand Up @@ -6724,7 +6731,7 @@ gboolean window_configure_callback(GtkWidget *widget, GdkEventConfigure *event,
requested_main_window_width = -1;
}

if(wm_ignores_size_requests || (main_window_width != event->width || main_window_height != event->height)) {
if(wm_ignores_size_requests || (main_window_width != event->width - csd_width || main_window_height != event->height - csd_height)) {
// Reset cached font size for info text
#ifndef CONFIGURED_WITHOUT_INFO_TEXT
current_info_text_cached_font_size = -1;
Expand All @@ -6736,8 +6743,8 @@ gboolean window_configure_callback(GtkWidget *widget, GdkEventConfigure *event,
main_window_height = screen_geometry.height;
}
else {
main_window_width = event->width;
main_window_height = event->height;
main_window_width = event->width - csd_width;
main_window_height = event->height - csd_height;
}

// If the fullscreen state just changed execute the post-change callbacks here
Expand All @@ -6752,9 +6759,10 @@ gboolean window_configure_callback(GtkWidget *widget, GdkEventConfigure *event,
}

// Rescale the image
if(main_window_width != event->width || main_window_height != event->height) {
if(main_window_width != event->width - csd_width || main_window_height != event->height - csd_height) {
set_scale_level_to_fit();
}
gdk_window_invalidate_rect(gtk_widget_get_window(GTK_WIDGET(main_window)), NULL, TRUE);
queue_draw();

// We need to redraw in old GTK versions to avoid artifacts
Expand Down Expand Up @@ -7275,6 +7283,21 @@ void window_screen_changed_callback(GtkWidget *widget, GdkScreen *previous_scree
#endif
}/*}}}*/
void window_realize_callback(GtkWidget *widget, gpointer user_data) {/*{{{*/
// Compute CSD dimensions
// If the theme changes while pqiv is running, this breaks, but that should rarely happen
GtkAllocation box_allocation, window_allocation;
gtk_widget_get_allocation(main_window_box, &box_allocation);
gtk_widget_get_allocation(GTK_WIDGET(main_window), &window_allocation);
csd_left = box_allocation.x;
csd_top = box_allocation.y;
csd_right = window_allocation.width - box_allocation.width - csd_left;
csd_bottom = window_allocation.height - box_allocation.height - csd_top;
csd_height = csd_top + csd_bottom;
csd_width = csd_left + csd_right;
gtk_widget_destroy(main_window_box);
main_window_box = NULL;

// Transition to fullscreen if that was requested
if(option_start_fullscreen) {
window_fullscreen();
}
Expand Down Expand Up @@ -7334,6 +7357,10 @@ void create_window() { /*{{{*/
g_signal_connect(main_window, "window-state-event", G_CALLBACK(window_state_callback), NULL);
g_signal_connect(main_window, "realize", G_CALLBACK(window_realize_callback), NULL);

// Not used for anything except determining CSD dimensions
main_window_box = GTK_WIDGET(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0));
gtk_container_add(GTK_CONTAINER(main_window), main_window_box);

gtk_widget_set_events(GTK_WIDGET(main_window),
GDK_EXPOSURE_MASK | GDK_SCROLL_MASK | GDK_BUTTON_MOTION_MASK |
GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
Expand Down

0 comments on commit 10aad05

Please sign in to comment.