From 3b7cdfe87f685139266860d43f1c34afe5a7a727 Mon Sep 17 00:00:00 2001 From: Peter Joyce Date: Fri, 14 Aug 2026 15:22:50 +0100 Subject: [PATCH 01/20] wayland: per-monitor screenshot capture + redoshot support --- bin/shutter | 32 +++++- .../modules/Shutter/Screenshot/Wayland.pm | 100 +++++++++++++++++- 2 files changed, 125 insertions(+), 7 deletions(-) diff --git a/bin/shutter b/bin/shutter index 19733fb9..b41bdfff 100755 --- a/bin/shutter +++ b/bin/shutter @@ -969,6 +969,9 @@ sub STARTUP { #init menus if ($x11_supported){ $st->{_full}->set_menu(fct_ret_workspace_menu(TRUE)); + } elsif (Gtk3::Gdk::Screen::get_default->get_n_monitors > 1) { + #Wayland: offer per-monitor capture through the "full" dropdown + $st->{_full}->set_menu(fct_ret_workspace_menu(TRUE)); } $st->{_window}->set_menu(fct_ret_window_menu()); @@ -3133,7 +3136,7 @@ sub STARTUP { #unblock signal handler fct_control_signals('unblock'); return TRUE; - } elsif (!$x11_supported && $data ne "full" && $data ne "tray_full") { + } elsif (!$x11_supported && $data ne "full" && $data ne "tray_full" && $data !~ /^shutter_monitor_direct/ && $data !~ /^redoshot/) { my $sd = Shutter::App::SimpleDialogs->new; $sd->dlg_error_message($d->get("Can't take screenshots without X11 server"), $d->get("Failed")); fct_control_signals('unblock'); @@ -6189,7 +6192,11 @@ sub STARTUP { } #fullscreen screenshot - if ($data eq "full" || $data eq "tray_full") { + if ($data eq "full" || $data eq "tray_full" || $data =~ /^shutter_monitor_direct(\d+)/) { + + #on Wayland a single monitor can be requested via the "full" dropdown + my $monitor = ($data =~ /^shutter_monitor_direct(\d+)/) ? $1 : undef; + $data = "full" if defined $monitor; if ($x11_supported) { $screenshooter = Shutter::Screenshot::Workspace->new( @@ -6201,7 +6208,8 @@ sub STARTUP { $screenshot = $screenshooter->workspace(); } else { # TODO: support kwin directly, because it has more features than the xdg portal - $screenshot = Shutter::Screenshot::Wayland::xdg_portal($screenshooter); + $screenshooter = Shutter::Screenshot::Wayland->new($sc, $monitor); + $screenshot = $screenshooter->capture; } #window @@ -8447,6 +8455,24 @@ sub STARTUP { my $menu_wrksp = Gtk3::Menu->new; unless ($x11_supported) { + + #On Wayland we can't enumerate workspaces via libwnck, but we can still + #offer to capture a single monitor by cropping the portal screenshot. + my $gdk_screen = Gtk3::Gdk::Screen::get_default; + my $n_mons = $gdk_screen->get_n_monitors; + if ($n_mons > 1) { + for (my $i = 0 ; $i < $n_mons ; $i++) { + my $geo = $gdk_screen->get_monitor_geometry($i); + my $plug = eval { $gdk_screen->get_monitor_plug_name($i) }; + my $label = $plug + ? sprintf("%s (%dx%d)", $plug, $geo->{width}, $geo->{height}) + : sprintf($d->get("Monitor %d (%dx%d)"), $i + 1, $geo->{width}, $geo->{height}); + my $mon_item = Gtk3::MenuItem->new_with_label($label); + $mon_item->signal_connect('activate' => \&evt_take_screenshot, "shutter_monitor_direct$i"); + $menu_wrksp->append($mon_item); + } + $menu_wrksp->show_all; + } return $menu_wrksp; } diff --git a/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm b/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm index e6c5e16e..bbecd8e0 100644 --- a/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm +++ b/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm @@ -6,6 +6,52 @@ use Net::DBus::Reactor; package Shutter::Screenshot::Wayland; +use Shutter::Screenshot::History; + +#Object wrapper around the portal so full/monitor captures can be repeated +#(e.g. via the redoshot/F5 shortcut) just like the X11 screenshooters. +sub new { + my $class = shift; + my $self = { + _sc => shift, + _monitor => shift, #undef captures the whole desktop + }; + $self->{_gdk_screen} = Gtk3::Gdk::Screen::get_default(); + bless $self, $class; + return $self; +} + +sub capture { + my $self = shift; + + my $pixbuf = xdg_portal($self); + return $pixbuf unless ref($pixbuf) && $pixbuf->isa('Gtk3::Gdk::Pixbuf'); + + if (defined $self->{_monitor}) { + $pixbuf = crop_to_monitor($pixbuf, $self->{_gdk_screen}, $self->{_monitor}); + } + + #a history marker makes this capture repeatable through redoshot + $self->{_history} = Shutter::Screenshot::History->new($self->{_sc}); + return $pixbuf; +} + +sub redo_capture { + my $self = shift; + return 3 unless defined $self->{_history}; + return $self->capture; +} + +sub get_history { + my $self = shift; + return $self->{_history}; +} + +sub get_error_text { + my $self = shift; + return $self->{_error_text}; +} + sub xdg_portal { my $screenshooter = shift; my $reactor = Net::DBus::Reactor->main; @@ -15,6 +61,7 @@ sub xdg_portal { $me =~ s/^://g; my $pixbuf; + my $portal_error; eval { my $portal_service = $bus->get_service('org.freedesktop.portal.Desktop'); @@ -39,9 +86,16 @@ sub xdg_portal { } $reactor->run; $request->disconnect_from_signal(Response => $conn); - if ($num != 0) { - $screenshooter->{_error_text} = "Response $num from XDG portal"; - return 9; + + #a "return" here would only exit the eval and leave the caller with an + #undef pixbuf, so record the failure and bail out after the eval instead + if (!defined $num || $num != 0) { + $portal_error = "Response " . (defined $num ? $num : "timeout") . " from XDG portal"; + return; + } + unless (defined $output && defined $output->{uri}) { + $portal_error = "XDG portal returned no screenshot URI"; + return; } my $giofile = Glib::IO::File::new_for_uri($output->{uri}); print "xdg portal: got file ".$giofile->get_path."\n"; @@ -51,9 +105,47 @@ sub xdg_portal { if ($@) { $screenshooter->{_error_text} = $@; return 9; - }; + } + if (defined $portal_error) { + $screenshooter->{_error_text} = $portal_error; + return 9; + } return $pixbuf; } +#The XDG portal always returns the whole desktop spanning every monitor. Crop +#that pixbuf down to a single monitor's area so users can capture just one. +sub crop_to_monitor { + my ($pixbuf, $gdk_screen, $monitor) = @_; + + return $pixbuf unless defined $pixbuf && defined $gdk_screen && defined $monitor; + + my $geo = $gdk_screen->get_monitor_geometry($monitor); + return $pixbuf unless $geo; + + #Monitor geometry is in logical pixels while the portal captures device + #pixels; derive the scale from the full desktop size to stay correct on HiDPI. + my $screen_w = $gdk_screen->get_width || $pixbuf->get_width; + my $screen_h = $gdk_screen->get_height || $pixbuf->get_height; + my $scale_x = $pixbuf->get_width / $screen_w; + my $scale_y = $pixbuf->get_height / $screen_h; + + my $x = int($geo->{x} * $scale_x); + my $y = int($geo->{y} * $scale_y); + my $w = int($geo->{width} * $scale_x); + my $h = int($geo->{height} * $scale_y); + + #clamp to the captured area + $x = 0 if $x < 0; + $y = 0 if $y < 0; + $w = $pixbuf->get_width - $x if $x + $w > $pixbuf->get_width; + $h = $pixbuf->get_height - $y if $y + $h > $pixbuf->get_height; + return $pixbuf if $w <= 0 || $h <= 0; + + my $cropped = Gtk3::Gdk::Pixbuf->new('rgb', $pixbuf->get_has_alpha, 8, $w, $h); + $pixbuf->copy_area($x, $y, $w, $h, $cropped, 0, 0); + return $cropped; +} + 1; From a945665f2aeb4e2954d8b583f3f6ddee694fbbb3 Mon Sep 17 00:00:00 2001 From: Photon89 Date: Mon, 25 May 2026 17:07:23 +0200 Subject: [PATCH 02/20] Added Wayland support via interactive or non-interactive call --- bin/shutter | 505 ++++++++++-------- .../modules/Shutter/Screenshot/Wayland.pm | 23 +- 2 files changed, 297 insertions(+), 231 deletions(-) mode change 100755 => 100644 bin/shutter diff --git a/bin/shutter b/bin/shutter old mode 100755 new mode 100644 index 58a42940..ce75cdfa --- a/bin/shutter +++ b/bin/shutter @@ -396,7 +396,7 @@ my $zoom_active; my %accounts; #hash to store account infos my %settings; #hash to store settings my @supported_formats; #hash to store available supported file formats - + my %supported_targets; #hash to store available targets for xdg-desktop-portal sub STARTUP { # This is called by $app->run below if another Shutter instance is not running. @@ -590,6 +590,21 @@ sub STARTUP { if ($ENV{XDG_SESSION_TYPE} eq "wayland") { $x11_supported = 0; + # Get targets supported by xdg-desktop-portal and hash them into %supported_targets + # Possible targets are: + # 1: Screen (corresponds "full") + # 2: Window (corresponds "window") + # 4: Area (corresponds "select") + # 8: Active Window (corresponds "awindow") + # See: https://github.com/flatpak/xdg-desktop-portal/pull/1981 + my $raw = Net::DBus->session + ->get_service("org.freedesktop.portal.Desktop") + ->get_object("/org/freedesktop/portal/desktop") + ->as_interface("org.freedesktop.DBus.Properties") + ->Get("org.freedesktop.portal.Screenshot", "AvailableTargets"); + my $val = (ref($raw) && $raw->can('value')) ? $raw->value : $raw; + my $targets = (ref($val) eq 'ARRAY') ? $val : []; + %supported_targets = map { $_ => 1 } @$targets; } if ($x11_supported) { @@ -656,7 +671,9 @@ sub STARTUP { $sm->{_menuitem_iclipboard}->signal_connect('activate', \&fct_clipboard_import); unless ($x11_supported) { - for my $name ('selection', 'awindow', 'window', 'menu', 'tooltip') { + # on Wayland selection/window/awindow go through the XDG portal; + # only menu/tooltip capture stay disabled (not supported by the portal) + for my $name ('menu', 'tooltip') { $sm->{"_menuitem_$name"}->set_sensitive(FALSE); } } @@ -1007,11 +1024,13 @@ sub STARTUP { $st->{_upload}->set_sensitive(FALSE); unless ($x11_supported) { + # on Wayland _select/_window use the XDG portal; menu/tooltip stay disabled my $tooltip = $d->get("Can't take screenshots without X11 server"); - for my $name ('_select', '_window', '_menu', '_tooltip') { + for my $name ('_menu', '_tooltip') { $st->{$name}->set_sensitive(FALSE); $st->{$name}->set_tooltip_text($tooltip); } + # dropdown menus (workspace list / window list) still need X11 to enumerate for my $name ('_full', '_window') { $st->{$name}->set_arrow_tooltip_text($tooltip); } @@ -3152,7 +3171,10 @@ sub STARTUP { #unblock signal handler fct_control_signals('unblock'); return TRUE; - } elsif (!$x11_supported && $data ne "full" && $data ne "tray_full") { + } elsif (!$x11_supported + && $data !~ /^(tray_)?(full|select|window|awindow)$/) { + # full/select/window/awindow are served via the XDG portal on Wayland; + # everything else (menu, tooltip, ...) still needs X11 my $sd = Shutter::App::SimpleDialogs->new; $sd->dlg_error_message($d->get("Can't take screenshots without X11 server"), $d->get("Failed")); fct_control_signals('unblock'); @@ -6213,10 +6235,37 @@ sub STARTUP { } } - #fullscreen screenshot - if ($data eq "full" || $data eq "tray_full") { - - if ($x11_supported) { + #wayland: route selection/window/active to xdg-desktop-portal + if (!$x11_supported) { + # For some period of time desktops won't support non-interactive targets, we need to deal with it. + my $target; + my $interactive; + # For fullscreen capture just use plain xdg_portal + if ($data eq "full" || $data eq "tray_full") { + $screenshot = Shutter::Screenshot::Wayland::xdg_portal($screenshooter); + } else { + # For each non-trivial capture mode (window, active window, selection) define a target + # to try and call the non-interactive portal interface + if ($data eq "window" || $data eq "tray_window") { + $target=2; + } elsif ($data eq "awindow" || $data eq "tray_awindow") { + $target=8 + } elsif ($data eq "select" || $data eq "tray_select") { + $target=4; + } + # For the target chosen by user check if it is supported non-interactively. + # If unsupported, fall back to an interactive call which makes use of the + # DE's interactive tool. + if ($supported_targets{$target}) { + $interactive=0; + } else { + $interactive=1; + } + $screenshot = Shutter::Screenshot::Wayland::xdg_portal($screenshooter, $interactive, $target); + } + } else { + #fullscreen screenshot + if ($data eq "full" || $data eq "tray_full") { $screenshooter = Shutter::Screenshot::Workspace->new( $sc, $include_cursor, $delay_value, $notify_timeout_active->get_active, @@ -6224,302 +6273,298 @@ sub STARTUP { undef, undef, $current_monitor_active->get_active ); $screenshot = $screenshooter->workspace(); - } else { - # TODO: support kwin directly, because it has more features than the xdg portal - $screenshot = Shutter::Screenshot::Wayland::xdg_portal($screenshooter); - } - - #window - } elsif ($data eq "window" - || $data eq "tray_window" - || $data eq "awindow" - || $data eq "tray_awindow" - || $data eq "section" - || $data eq "tray_section" - || $data eq "menu" - || $data eq "tray_menu" - || $data eq "tooltip" - || $data eq "tray_tooltip") - { + #window + } elsif ($data eq "window" + || $data eq "tray_window" + || $data eq "awindow" + || $data eq "tray_awindow" + || $data eq "section" + || $data eq "tray_section" + || $data eq "menu" + || $data eq "tray_menu" + || $data eq "tooltip" + || $data eq "tray_tooltip") + { - #control some wm related settings - my $curr_value = fct_control_wm_settings('start'); + #control some wm related settings + my $curr_value = fct_control_wm_settings('start'); - if (defined $extra && $extra) { + if (defined $extra && $extra) { - $screenshooter = Shutter::Screenshot::WindowName->new( - $sc, $include_cursor, $delay_value, $notify_timeout_active->get_active, - $border_active->get_active, $winresize_active->get_active, $winresize_w->get_value, $winresize_h->get_value, - $hide_time->get_value, $data, $autoshape_active->get_active - ); + $screenshooter = Shutter::Screenshot::WindowName->new( + $sc, $include_cursor, $delay_value, $notify_timeout_active->get_active, + $border_active->get_active, $winresize_active->get_active, $winresize_w->get_value, $winresize_h->get_value, + $hide_time->get_value, $data, $autoshape_active->get_active + ); - $screenshot = $screenshooter->window_find_by_name($extra); + $screenshot = $screenshooter->window_find_by_name($extra); - } else { + } else { - $screenshooter = Shutter::Screenshot::Window->new( - $sc, $include_cursor, $delay_value, $notify_timeout_active->get_active, - $border_active->get_active, $winresize_active->get_active, $winresize_w->get_value, $winresize_h->get_value, - $hide_time->get_value, $data, $autoshape_active->get_active, $is_hidden, - $visible_windows_active->get_active, $menu_waround_active->get_active - ); + $screenshooter = Shutter::Screenshot::Window->new( + $sc, $include_cursor, $delay_value, $notify_timeout_active->get_active, + $border_active->get_active, $winresize_active->get_active, $winresize_w->get_value, $winresize_h->get_value, + $hide_time->get_value, $data, $autoshape_active->get_active, $is_hidden, + $visible_windows_active->get_active, $menu_waround_active->get_active + ); - $screenshot = $screenshooter->window(); + $screenshot = $screenshooter->window(); - } + } - #control some wm related settings - if (defined $curr_value && $curr_value != -1) { - fct_control_wm_settings('stop', $curr_value); - } + #control some wm related settings + if (defined $curr_value && $curr_value != -1) { + fct_control_wm_settings('stop', $curr_value); + } - #selection - } elsif ($data eq "select" || $data eq "tray_select") { + #selection + } elsif ($data eq "select" || $data eq "tray_select") { - if (defined $extra && $extra) { + if (defined $extra && $extra) { - my @coords = split(',', $extra); + my @coords = split(',', $extra); - $screenshooter = Shutter::Screenshot::SelectorAuto->new($sc, $include_cursor, $delay_value, $notify_timeout_active->get_active,); + $screenshooter = Shutter::Screenshot::SelectorAuto->new($sc, $include_cursor, $delay_value, $notify_timeout_active->get_active,); - $screenshot = $screenshooter->select_auto($coords[0], $coords[1], $coords[2], $coords[3]); + $screenshot = $screenshooter->select_auto($coords[0], $coords[1], $coords[2], $coords[3]); - } else { + } else { - $screenshooter = Shutter::Screenshot::SelectorAdvanced->new( - $sc, $include_cursor, $delay_value, $notify_timeout_active->get_active, - $zoom_active->get_active, $hide_time->get_value, $as_help_active->get_active, $asel_size3->get_value, - $asel_size4->get_value, $asel_size1->get_value, $asel_size2->get_value, $as_confirmation_necessary->get_active, - ); + $screenshooter = Shutter::Screenshot::SelectorAdvanced->new( + $sc, $include_cursor, $delay_value, $notify_timeout_active->get_active, + $zoom_active->get_active, $hide_time->get_value, $as_help_active->get_active, $asel_size3->get_value, + $asel_size4->get_value, $asel_size1->get_value, $asel_size2->get_value, $as_confirmation_necessary->get_active, + ); - $screenshot = $screenshooter->select_advanced(); + $screenshot = $screenshooter->select_advanced(); - } + } - #web - } elsif ($data eq "web" || $data eq "tray_web") { + #web + } elsif ($data eq "web" || $data eq "tray_web") { - my $website_width = 1024; - if ($combobox_web_width->get_active_text =~ /(\d+)/) { - $website_width = $1; - } + my $website_width = 1024; + if ($combobox_web_width->get_active_text =~ /(\d+)/) { + $website_width = $1; + } - print "\nvirtual website width: $website_width\n" - if $sc->get_debug; + print "\nvirtual website width: $website_width\n" + if $sc->get_debug; - #determine timeout - my $web_menu = $st->{_web}->get_menu; - my @timeouts = $web_menu->get_children; - my $timeout = undef; - foreach my $to (@timeouts) { - if ($to->get_active) { - $timeout = $to->get_name; - $timeout =~ /([0-9]+)/; - $timeout = $1; - print $timeout. "\n" if $sc->get_debug; + #determine timeout + my $web_menu = $st->{_web}->get_menu; + my @timeouts = $web_menu->get_children; + my $timeout = undef; + foreach my $to (@timeouts) { + if ($to->get_active) { + $timeout = $to->get_name; + $timeout =~ /([0-9]+)/; + $timeout = $1; + print $timeout. "\n" if $sc->get_debug; + } } - } - $screenshooter = Shutter::Screenshot::Web->new($sc, $timeout, $website_width); - $screenshot = $screenshooter->dlg_website($extra); + $screenshooter = Shutter::Screenshot::Web->new($sc, $timeout, $website_width); + $screenshot = $screenshooter->dlg_website($extra); - #window by xid - } elsif ($data =~ /^shutter_window_direct(.*)/) { + #window by xid + } elsif ($data =~ /^shutter_window_direct(.*)/) { - my $xid = $1; - print "Selected xid: $xid\n" if $sc->get_debug; + my $xid = $1; + print "Selected xid: $xid\n" if $sc->get_debug; - #control some wm related settings - my $curr_value = fct_control_wm_settings('start'); + #control some wm related settings + my $curr_value = fct_control_wm_settings('start'); - #change mode (imitating selecting a window by mouse) - $data = "window"; + #change mode (imitating selecting a window by mouse) + $data = "window"; - $screenshooter = Shutter::Screenshot::WindowXid->new( - $sc, $include_cursor, $delay_value, $notify_timeout_active->get_active, - $border_active->get_active, $winresize_active->get_active, $winresize_w->get_value, $winresize_h->get_value, - $hide_time->get_value, $data, $autoshape_active->get_active - ); + $screenshooter = Shutter::Screenshot::WindowXid->new( + $sc, $include_cursor, $delay_value, $notify_timeout_active->get_active, + $border_active->get_active, $winresize_active->get_active, $winresize_w->get_value, $winresize_h->get_value, + $hide_time->get_value, $data, $autoshape_active->get_active + ); - $screenshot = $screenshooter->window_by_xid($xid); + $screenshot = $screenshooter->window_by_xid($xid); - #control some wm related settings - if (defined $curr_value && $curr_value != -1) { - fct_control_wm_settings('stop', $curr_value); - } + #control some wm related settings + if (defined $curr_value && $curr_value != -1) { + fct_control_wm_settings('stop', $curr_value); + } - } elsif ($data =~ /^shutter_wrksp_direct/) { + } elsif ($data =~ /^shutter_wrksp_direct/) { - #we need to handle different wm, e.g. metacity, compiz here - my $selected_workspace = undef; - my $vpx = undef; - my $vpy = undef; + #we need to handle different wm, e.g. metacity, compiz here + my $selected_workspace = undef; + my $vpx = undef; + my $vpy = undef; - #compiz - if ($data =~ /compiz(\d*)x(\d*)/) { - $vpx = $1; - $vpy = $2; - print "Sel. Viewport: $vpx, $vpy\n" if $sc->get_debug; - - #metacity etc. - } elsif ($data =~ /shutter_wrksp_direct(.*)/) { - $selected_workspace = $1; - print "Sel. Workspace: $selected_workspace\n" - if $sc->get_debug; + #compiz + if ($data =~ /compiz(\d*)x(\d*)/) { + $vpx = $1; + $vpy = $2; + print "Sel. Viewport: $vpx, $vpy\n" if $sc->get_debug; - #all workspaces - } elsif ($data =~ /shutter_wrksp_all/) { - print "Capturing all workspaces\n" - if $sc->get_debug; - $selected_workspace = 'all'; - } + #metacity etc. + } elsif ($data =~ /shutter_wrksp_direct(.*)/) { + $selected_workspace = $1; + print "Sel. Workspace: $selected_workspace\n" + if $sc->get_debug; - $screenshooter = - Shutter::Screenshot::Workspace->new($sc, $include_cursor, $delay_value, $notify_timeout_active->get_active, $selected_workspace, $vpx, $vpy, $current_monitor_active->get_active); + #all workspaces + } elsif ($data =~ /shutter_wrksp_all/) { + print "Capturing all workspaces\n" + if $sc->get_debug; + $selected_workspace = 'all'; + } - if ($selected_workspace eq 'all') { - $screenshot = $screenshooter->workspaces(); - } else { - $screenshot = $screenshooter->workspace(); - } + $screenshooter = + Shutter::Screenshot::Workspace->new($sc, $include_cursor, $delay_value, $notify_timeout_active->get_active, $selected_workspace, $vpx, $vpy, $current_monitor_active->get_active); + + if ($selected_workspace eq 'all') { + $screenshot = $screenshooter->workspaces(); + } else { + $screenshot = $screenshooter->workspace(); + } - } elsif ($data eq "redoshot") { + } elsif ($data eq "redoshot") { - #~ my $key = fct_get_last_capture(); - #~ if(defined $key && exists $session_screens{$key}->{'history'} && defined $session_screens{$key}->{'history'}){ - #~ $screenshooter = $session_screens{$key}->{'history'}; - #~ $screenshot = $screenshooter->redo_capture; - #~ }else{ - #~ $screenshot = 3; - #~ } + #~ my $key = fct_get_last_capture(); + #~ if(defined $key && exists $session_screens{$key}->{'history'} && defined $session_screens{$key}->{'history'}){ + #~ $screenshooter = $session_screens{$key}->{'history'}; + #~ $screenshot = $screenshooter->redo_capture; + #~ }else{ + #~ $screenshot = 3; + #~ } - if ($screenshooter = fct_get_last_capture()) { + if ($screenshooter = fct_get_last_capture()) { - #we need to handle menu and tooltip in a special way - if ($screenshooter->can('get_mode')) { - if (my $mode = $screenshooter->get_mode) { + #we need to handle menu and tooltip in a special way + if ($screenshooter->can('get_mode')) { + if (my $mode = $screenshooter->get_mode) { - #control some wm related settings - my $curr_value = undef; - if (($mode eq "window" || $mode eq "tray_window" || $mode eq "awindow" || $mode eq "tray_awindow" || $mode eq "section" || $mode eq "tray_section")) { - $curr_value = fct_control_wm_settings('start'); - } + #control some wm related settings + my $curr_value = undef; + if (($mode eq "window" || $mode eq "tray_window" || $mode eq "awindow" || $mode eq "tray_awindow" || $mode eq "section" || $mode eq "tray_section")) { + $curr_value = fct_control_wm_settings('start'); + } - if ($mode eq "menu" || $mode eq "tray_menu") { - $st->{_menu}->signal_emit('clicked'); - return FALSE; - } elsif ($mode eq "tooltip" || $mode eq "tray_tooltip") { - $st->{_tooltip}->signal_emit('clicked'); - return FALSE; + if ($mode eq "menu" || $mode eq "tray_menu") { + $st->{_menu}->signal_emit('clicked'); + return FALSE; + } elsif ($mode eq "tooltip" || $mode eq "tray_tooltip") { + $st->{_tooltip}->signal_emit('clicked'); + return FALSE; + } else { + $screenshot = $screenshooter->redo_capture; + } + + #control some wm related settings + if (($mode eq "window" || $mode eq "tray_window" || $mode eq "awindow" || $mode eq "tray_awindow" || $mode eq "section" || $mode eq "tray_section")) { + if (defined $curr_value && $curr_value != -1) { + fct_control_wm_settings('stop', $curr_value); + } + } + + #window by xid } else { + + #control some wm related settings + my $curr_value = fct_control_wm_settings('start'); $screenshot = $screenshooter->redo_capture; - } - #control some wm related settings - if (($mode eq "window" || $mode eq "tray_window" || $mode eq "awindow" || $mode eq "tray_awindow" || $mode eq "section" || $mode eq "tray_section")) { + #control some wm related settings if (defined $curr_value && $curr_value != -1) { fct_control_wm_settings('stop', $curr_value); } } - - #window by xid } else { - - #control some wm related settings - my $curr_value = fct_control_wm_settings('start'); $screenshot = $screenshooter->redo_capture; - - #control some wm related settings - if (defined $curr_value && $curr_value != -1) { - fct_control_wm_settings('stop', $curr_value); - } } } else { - $screenshot = $screenshooter->redo_capture; + $screenshot = 3; } - } else { - $screenshot = 3; - } - } elsif ($data eq "redoshot_this") { + } elsif ($data eq "redoshot_this") { - #get current screenshot (current notebook page) - my $key = fct_get_current_file(); + #get current screenshot (current notebook page) + my $key = fct_get_current_file(); - #or get the selected screenshot in the view - unless (defined $key) { - $session_start_screen{'first_page'}->{'view'}->selected_foreach( - sub { - my ($view, $path) = @_; - my $iter = $session_start_screen{'first_page'}->{'model'}->get_iter($path); - if (defined $iter) { - $key = $session_start_screen{'first_page'}->{'model'}->get_value($iter, 2); - } - }, - undef - ); - } + #or get the selected screenshot in the view + unless (defined $key) { + $session_start_screen{'first_page'}->{'view'}->selected_foreach( + sub { + my ($view, $path) = @_; + my $iter = $session_start_screen{'first_page'}->{'model'}->get_iter($path); + if (defined $iter) { + $key = $session_start_screen{'first_page'}->{'model'}->get_value($iter, 2); + } + }, + undef + ); + } - if ( defined $key - && exists $session_screens{$key}->{'history'} - && defined $session_screens{$key}->{'history'}) - { - $screenshooter = $session_screens{$key}->{'history'}; + if ( defined $key + && exists $session_screens{$key}->{'history'} + && defined $session_screens{$key}->{'history'}) + { + $screenshooter = $session_screens{$key}->{'history'}; - #we need to handle menu and tooltip in a special way - if ($screenshooter->can('get_mode')) { - if (my $mode = $screenshooter->get_mode) { + #we need to handle menu and tooltip in a special way + if ($screenshooter->can('get_mode')) { + if (my $mode = $screenshooter->get_mode) { - #control some wm related settings - my $curr_value = undef; - if (($mode eq "window" || $mode eq "tray_window" || $mode eq "awindow" || $mode eq "tray_awindow" || $mode eq "section" || $mode eq "tray_section")) { - $curr_value = fct_control_wm_settings('start'); - } + #control some wm related settings + my $curr_value = undef; + if (($mode eq "window" || $mode eq "tray_window" || $mode eq "awindow" || $mode eq "tray_awindow" || $mode eq "section" || $mode eq "tray_section")) { + $curr_value = fct_control_wm_settings('start'); + } - if ($mode eq "menu" || $mode eq "tray_menu") { - $st->{_menu}->signal_emit('clicked'); - return FALSE; - } elsif ($mode eq "tooltip" || $mode eq "tray_tooltip") { - $st->{_tooltip}->signal_emit('clicked'); - return FALSE; + if ($mode eq "menu" || $mode eq "tray_menu") { + $st->{_menu}->signal_emit('clicked'); + return FALSE; + } elsif ($mode eq "tooltip" || $mode eq "tray_tooltip") { + $st->{_tooltip}->signal_emit('clicked'); + return FALSE; + } else { + $screenshot = $screenshooter->redo_capture; + } + + #control some wm related settings + if (($mode eq "window" || $mode eq "tray_window" || $mode eq "awindow" || $mode eq "tray_awindow" || $mode eq "section" || $mode eq "tray_section")) { + if (defined $curr_value && $curr_value != -1) { + fct_control_wm_settings('stop', $curr_value); + } + } + + #window by xid } else { + + #control some wm related settings + my $curr_value = fct_control_wm_settings('start'); $screenshot = $screenshooter->redo_capture; - } - #control some wm related settings - if (($mode eq "window" || $mode eq "tray_window" || $mode eq "awindow" || $mode eq "tray_awindow" || $mode eq "section" || $mode eq "tray_section")) { + #control some wm related settings if (defined $curr_value && $curr_value != -1) { fct_control_wm_settings('stop', $curr_value); } } - - #window by xid } else { - - #control some wm related settings - my $curr_value = fct_control_wm_settings('start'); $screenshot = $screenshooter->redo_capture; - - #control some wm related settings - if (defined $curr_value && $curr_value != -1) { - fct_control_wm_settings('stop', $curr_value); - } } } else { - $screenshot = $screenshooter->redo_capture; + $screenshot = 3; } - } else { - $screenshot = 3; - } - } else { + } else { - #show error dialog - my $response = $sd->dlg_error_message($d->get("Triggered invalid screenshot action."), $d->get("Error while taking the screenshot.")); + #show error dialog + my $response = $sd->dlg_error_message($d->get("Triggered invalid screenshot action."), $d->get("Error while taking the screenshot.")); - fct_show_status_message(1, $d->get("Error while taking the screenshot.")); - fct_control_main_window('show'); - return FALSE; + fct_show_status_message(1, $d->get("Error while taking the screenshot.")); + fct_control_main_window('show'); + return FALSE; + } } #screenshot was taken at this stage... @@ -8684,7 +8729,8 @@ sub STARTUP { #selection my $menuitem_select = Gtk3::ImageMenuItem->new_with_mnemonic($d->get('_Selection')); - $menuitem_select->set_sensitive($x11_supported); + # served via interactive XDG portal on Wayland too + $menuitem_select->set_sensitive(TRUE); eval { my $ccursor_pb = Gtk3::Gdk::Cursor::new('left_ptr')->get_image->scale_simple($shf->icon_size('menu'), 'bilinear'); $menuitem_select->set_image(Gtk3::Image->new_from_pixbuf($ccursor_pb)); @@ -8733,7 +8779,8 @@ sub STARTUP { #window my $menuitem_window = Gtk3::ImageMenuItem->new_with_mnemonic($d->get('Window _under Cursor')); - $menuitem_window->set_sensitive($x11_supported); + # served via interactive XDG portal on Wayland too + $menuitem_window->set_sensitive(TRUE); if ($traytheme->has_icon('preferences-system-windows')) { $menuitem_window->set_image(Gtk3::Image->new_from_icon_name('preferences-system-windows', 'menu')); } else { diff --git a/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm b/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm index e6c5e16e..b3be7c10 100644 --- a/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm +++ b/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm @@ -1,13 +1,19 @@ use utf8; use strict; use warnings; -use Net::DBus; use Net::DBus::Reactor; +use Net::DBus; package Shutter::Screenshot::Wayland; sub xdg_portal { my $screenshooter = shift; + my $interactive = shift; + my $target = shift; + + # Fall back to fullscreen + $target = 1 unless defined $target; + my $reactor = Net::DBus::Reactor->main; my $bus = Net::DBus->find; my $me = $bus->get_unique_name; @@ -31,15 +37,28 @@ sub xdg_portal { $token =~ s/\.//g; my $request = $portal_service->get_object("/org/freedesktop/portal/desktop/request/$me/$token", 'org.freedesktop.portal.Request'); my $conn = $request->connect_to_signal(Response => $cb); - my $request_path = $portal->Screenshot('', {handle_token=>$token}); + + my %options = (handle_token => $token); + + $options{interactive} = Net::DBus::dbus_boolean($interactive); + if ($interactive eq 0) { + $options{target} = Net::DBus::dbus_uint32($target); + } + + my $request_path = $portal->Screenshot('', \%options); + if ($request->get_object_path ne $request_path) { $request->disconnect_from_signal(Response => $conn); $request = $portal_service->get_object($request_path, 'org.freedesktop.portal.Request'); $conn = $request->connect_to_signal(Response => $cb); } + $reactor->run; + $request->disconnect_from_signal(Response => $conn); if ($num != 0) { + # portal Response: 1 = user cancelled -> treat as abort (code 5), not error + return 5 if $num == 1; $screenshooter->{_error_text} = "Response $num from XDG portal"; return 9; } From 90028fa268a13fbb05739ded7c417c4f7b12bd3f Mon Sep 17 00:00:00 2001 From: Photon89 Date: Mon, 25 May 2026 17:42:48 +0200 Subject: [PATCH 03/20] Fixed file permissions --- bin/shutter | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 bin/shutter diff --git a/bin/shutter b/bin/shutter old mode 100644 new mode 100755 From 953b9461d7d6e78c43641254fce00a229fcf6b8e Mon Sep 17 00:00:00 2001 From: Photon89 Date: Tue, 26 May 2026 08:28:25 +0200 Subject: [PATCH 04/20] Fixed getting available portal targets if target property undefined --- bin/shutter | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/bin/shutter b/bin/shutter index ce75cdfa..33eb8db5 100755 --- a/bin/shutter +++ b/bin/shutter @@ -597,14 +597,23 @@ sub STARTUP { # 4: Area (corresponds "select") # 8: Active Window (corresponds "awindow") # See: https://github.com/flatpak/xdg-desktop-portal/pull/1981 - my $raw = Net::DBus->session - ->get_service("org.freedesktop.portal.Desktop") - ->get_object("/org/freedesktop/portal/desktop") - ->as_interface("org.freedesktop.DBus.Properties") - ->Get("org.freedesktop.portal.Screenshot", "AvailableTargets"); - my $val = (ref($raw) && $raw->can('value')) ? $raw->value : $raw; - my $targets = (ref($val) eq 'ARRAY') ? $val : []; - %supported_targets = map { $_ => 1 } @$targets; + my $targets_raw; + eval { + $targets_raw = Net::DBus->session + ->get_service("org.freedesktop.portal.Desktop") + ->get_object("/org/freedesktop/portal/desktop") + ->as_interface("org.freedesktop.DBus.Properties") + ->Get("org.freedesktop.portal.Screenshot", "AvailableTargets"); + }; + if ($@) { + # Fehler aufgetreten (Eigenschaft existiert nicht) -> Hash leeren + %supported_targets = (); + } else { + # Eigenschaft existiert -> Werte normal verarbeiten + my $targets_val = (ref($targets_raw) && $targets_raw->can('value')) ? $targets_raw->value : $targets_raw; + my $targets = (ref($targets_val) eq 'ARRAY') ? $targets_val : []; + %supported_targets = map { $_ => 1 } @$targets; + } } if ($x11_supported) { From 6a091c2d631e86c75e14d4de313a09951fda1733 Mon Sep 17 00:00:00 2001 From: Photon89 Date: Tue, 26 May 2026 11:12:05 +0200 Subject: [PATCH 05/20] Fix retrieving ActiveTargets from portal --- bin/shutter | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/bin/shutter b/bin/shutter index 33eb8db5..46c4082d 100755 --- a/bin/shutter +++ b/bin/shutter @@ -605,14 +605,18 @@ sub STARTUP { ->as_interface("org.freedesktop.DBus.Properties") ->Get("org.freedesktop.portal.Screenshot", "AvailableTargets"); }; + if ($@) { - # Fehler aufgetreten (Eigenschaft existiert nicht) -> Hash leeren %supported_targets = (); } else { - # Eigenschaft existiert -> Werte normal verarbeiten - my $targets_val = (ref($targets_raw) && $targets_raw->can('value')) ? $targets_raw->value : $targets_raw; - my $targets = (ref($targets_val) eq 'ARRAY') ? $targets_val : []; - %supported_targets = map { $_ => 1 } @$targets; + if (defined $targets_raw) { + $supported_targets{'1'} = 1 if ($targets_raw & 1); + $supported_targets{'2'} = 1 if ($targets_raw & 2); + $supported_targets{'4'} = 1 if ($targets_raw & 4); + $supported_targets{'8'} = 1 if ($targets_raw & 8); + } else { + %supported_targets = (); + } } } From 49d2c4cf358b49a5d5daddbb69327d5204f1cae7 Mon Sep 17 00:00:00 2001 From: Photon89 Date: Wed, 27 May 2026 08:54:18 +0200 Subject: [PATCH 06/20] Update warning message --- bin/shutter | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/shutter b/bin/shutter index 46c4082d..7c3569ad 100755 --- a/bin/shutter +++ b/bin/shutter @@ -984,7 +984,8 @@ sub STARTUP { unless ($x11_supported) { my $wayland_warning = Gtk3::Label->new; $wayland_warning->set_line_wrap(TRUE); - $wayland_warning->set_markup($d->get("Wayland support is limited, for more advanced screenshots please switch back to Xorg. Click here for details.")); + $wayland_warning->set_markup($d->get("Wayland support is limited, for more advanced screenshots please switch back to Xorg. Click here for details. Selection and window +modes are currently experimental, your desktop environment might fall back to an interactive mode chooser or full screen capture.")); $vbox->pack_start($wayland_warning, FALSE, TRUE, 0); } From 6fd60c45c9d1eddad38e95d624586cee55923213 Mon Sep 17 00:00:00 2001 From: Photon89 Date: Wed, 5 Aug 2026 10:41:29 +0200 Subject: [PATCH 07/20] Disable settings in environments where they are unnecessary --- bin/shutter | 171 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 115 insertions(+), 56 deletions(-) diff --git a/bin/shutter b/bin/shutter index 7c3569ad..e44b8a86 100755 --- a/bin/shutter +++ b/bin/shutter @@ -1775,10 +1775,15 @@ modes are currently experimental, your desktop environment might fall back to an #-------------------------------------- $zoom_active = Gtk3::CheckButton->new_with_label($d->get("Enable zoom window")); - if (defined $settings_xml->{'general'}->{'zoom_active'}) { - $zoom_active->set_active($settings_xml->{'general'}->{'zoom_active'}); + if ($x11_supported) { + if (defined $settings_xml->{'general'}->{'zoom_active'}) { + $zoom_active->set_active($settings_xml->{'general'}->{'zoom_active'}); + } else { + $zoom_active->set_active(TRUE); + } } else { - $zoom_active->set_active(TRUE); + $zoom_active->set_active(FALSE); + $zoom_active->set_sensitive(FALSE); } $zoom_active->set_tooltip_text($d->get("Enable zoom window")); @@ -1801,25 +1806,32 @@ modes are currently experimental, your desktop environment might fall back to an my $asel_size_vlabel1 = Gtk3::Label->new($d->get("pixels")); my $asel_size_vlabel2 = Gtk3::Label->new($d->get("pixels")); - if (defined $settings_xml->{'general'}->{'asel_x'}) { - $asel_size3->set_value($settings_xml->{'general'}->{'asel_x'}); - } else { - $asel_size3->set_value(0); - } - if (defined $settings_xml->{'general'}->{'asel_y'}) { - $asel_size4->set_value($settings_xml->{'general'}->{'asel_y'}); - } else { - $asel_size4->set_value(0); - } - if (defined $settings_xml->{'general'}->{'asel_w'}) { - $asel_size1->set_value($settings_xml->{'general'}->{'asel_w'}); - } else { - $asel_size1->set_value(0); - } - if (defined $settings_xml->{'general'}->{'asel_h'}) { - $asel_size2->set_value($settings_xml->{'general'}->{'asel_h'}); + if ($x11_supported) { + if (defined $settings_xml->{'general'}->{'asel_x'}) { + $asel_size3->set_value($settings_xml->{'general'}->{'asel_x'}); + } else { + $asel_size3->set_value(0); + } + if (defined $settings_xml->{'general'}->{'asel_y'}) { + $asel_size4->set_value($settings_xml->{'general'}->{'asel_y'}); + } else { + $asel_size4->set_value(0); + } + if (defined $settings_xml->{'general'}->{'asel_w'}) { + $asel_size1->set_value($settings_xml->{'general'}->{'asel_w'}); + } else { + $asel_size1->set_value(0); + } + if (defined $settings_xml->{'general'}->{'asel_h'}) { + $asel_size2->set_value($settings_xml->{'general'}->{'asel_h'}); + } else { + $asel_size2->set_value(0); + } } else { - $asel_size2->set_value(0); + $asel_size1->set_sensitive(FALSE); + $asel_size2->set_sensitive(FALSE); + $asel_size3->set_sensitive(FALSE); + $asel_size4->set_sensitive(FALSE); } $asel_size_label1->set_tooltip_text($d->get("Start Selection Tool with a customized selection size")); @@ -1851,10 +1863,15 @@ modes are currently experimental, your desktop environment might fall back to an #-------------------------------------- $as_help_active = Gtk3::CheckButton->new_with_label($d->get("Show help text")); - if (defined $settings_xml->{'general'}->{'as_help_active'}) { - $as_help_active->set_active($settings_xml->{'general'}->{'as_help_active'}); + if ($x11_supported) { + if (defined $settings_xml->{'general'}->{'as_help_active'}) { + $as_help_active->set_active($settings_xml->{'general'}->{'as_help_active'}); + } else { + $as_help_active->set_active(TRUE); + } } else { - $as_help_active->set_active(TRUE); + $as_help_active->set_active(FALSE); + $as_help_active->set_sensitive(FALSE); } $as_help_active->set_tooltip_text($d->get("Enables the help text")); @@ -1868,10 +1885,15 @@ modes are currently experimental, your desktop environment might fall back to an #-------------------------------------- $as_confirmation_necessary = Gtk3::CheckButton->new_with_label($d->get("Confirmation necessary")); - if (defined $settings_xml->{'general'}->{'as_confirmation_necessary'}) { - $as_confirmation_necessary->set_active($settings_xml->{'general'}->{'as_confirmation_necessary'}); + if ($x11_supported) { + if (defined $settings_xml->{'general'}->{'as_confirmation_necessary'}) { + $as_confirmation_necessary->set_active($settings_xml->{'general'}->{'as_confirmation_necessary'}); + } else { + $as_confirmation_necessary->set_active(TRUE); + } } else { - $as_confirmation_necessary->set_active(TRUE); + $as_confirmation_necessary->set_active(FALSE); + $as_confirmation_necessary->set_sensitive(FALSE); } $as_confirmation_necessary->set_tooltip_text($d->get("Pressing the enter key or doubleclicking is necessary to take the screenshot")); @@ -1888,10 +1910,15 @@ modes are currently experimental, your desktop environment might fall back to an $border_box->pack_start($border_active, FALSE, TRUE, 12); - if (defined $settings_xml->{'general'}->{'border'}) { - $border_active->set_active($settings_xml->{'general'}->{'border'}); + if ($x11_supported) { + if (defined $settings_xml->{'general'}->{'border'}) { + $border_active->set_active($settings_xml->{'general'}->{'border'}); + } else { + $border_active->set_active(TRUE); + } } else { $border_active->set_active(TRUE); + $border_active->set_sensitive(FALSE); } #end - border @@ -1907,22 +1934,29 @@ modes are currently experimental, your desktop environment might fall back to an $winresize_h = Gtk3::SpinButton->new_with_range(0, $h, 1); my $winresize_vlabel = Gtk3::Label->new($d->get("pixels")); - if (defined $settings_xml->{'general'}->{'winresize_active'}) { - $winresize_active->set_active($settings_xml->{'general'}->{'winresize_active'}); - } else { - $winresize_active->set_active(FALSE); - } + if ($x11_supported) { + if (defined $settings_xml->{'general'}->{'winresize_active'}) { + $winresize_active->set_active($settings_xml->{'general'}->{'winresize_active'}); + } else { + $winresize_active->set_active(FALSE); + } - if (defined $settings_xml->{'general'}->{'winresize_w'}) { - $winresize_w->set_value($settings_xml->{'general'}->{'winresize_w'}); - } else { - $winresize_w->set_value(500); - } + if (defined $settings_xml->{'general'}->{'winresize_w'}) { + $winresize_w->set_value($settings_xml->{'general'}->{'winresize_w'}); + } else { + $winresize_w->set_value(500); + } - if (defined $settings_xml->{'general'}->{'winresize_h'}) { - $winresize_h->set_value($settings_xml->{'general'}->{'winresize_h'}); + if (defined $settings_xml->{'general'}->{'winresize_h'}) { + $winresize_h->set_value($settings_xml->{'general'}->{'winresize_h'}); + } else { + $winresize_h->set_value(500); + } } else { - $winresize_h->set_value(500); + $winresize_active->set_active(FALSE); + $winresize_active->set_sensitive(FALSE); + $winresize_h->set_sensitive(FALSE); + $winresize_w->set_sensitive(FALSE); } $winresize_active->set_tooltip_text($d->get( @@ -1954,10 +1988,15 @@ modes are currently experimental, your desktop environment might fall back to an #-------------------------------------- $autoshape_active = Gtk3::CheckButton->new_with_label($d->get("Force rounded window corners")); - if (defined $settings_xml->{'general'}->{'autoshape_active'}) { - $autoshape_active->set_active($settings_xml->{'general'}->{'autoshape_active'}); + if ($x11_supported) { + if (defined $settings_xml->{'general'}->{'autoshape_active'}) { + $autoshape_active->set_active($settings_xml->{'general'}->{'autoshape_active'}); + } else { + $autoshape_active->set_active(FALSE); + } } else { $autoshape_active->set_active(FALSE); + $autoshape_active->set_sensitive(FALSE); } $autoshape_active->set_tooltip_text( @@ -1984,10 +2023,15 @@ modes are currently experimental, your desktop environment might fall back to an $visible_windows_box->pack_start($visible_windows_active, FALSE, TRUE, 12); - if (defined $settings_xml->{'general'}->{'visible_windows'}) { - $visible_windows_active->set_active($settings_xml->{'general'}->{'visible_windows'}); + if ($x11_supported) { + if (defined $settings_xml->{'general'}->{'visible_windows'}) { + $visible_windows_active->set_active($settings_xml->{'general'}->{'visible_windows'}); + } else { + $visible_windows_active->set_active(FALSE); + } } else { $visible_windows_active->set_active(FALSE); + $visible_windows_active->set_sensitive(FALSE); } #end - visible windows only @@ -2004,10 +2048,15 @@ modes are currently experimental, your desktop environment might fall back to an 'menu_delay_changed' ); - if (defined $settings_xml->{'general'}->{'menu_delay'}) { - $menu_delay->set_value($settings_xml->{'general'}->{'menu_delay'}); + if ($x11_supported) { + if (defined $settings_xml->{'general'}->{'menu_delay'}) { + $menu_delay->set_value($settings_xml->{'general'}->{'menu_delay'}); + } else { + $menu_delay->set_value(10); + } } else { - $menu_delay->set_value(10); + $menu_delay->set_value(0); + $menu_delay->set_sensitive(FALSE); } $menu_delay->set_tooltip_text($d->get("Capture menu/tooltip after a delay of n seconds")); @@ -2030,10 +2079,15 @@ modes are currently experimental, your desktop environment might fall back to an $menu_waround_box->pack_start($menu_waround_active, FALSE, TRUE, 12); - if (defined $settings_xml->{'general'}->{'menu_waround'}) { - $menu_waround_active->set_active($settings_xml->{'general'}->{'menu_waround'}); + if ($x11_supported) { + if (defined $settings_xml->{'general'}->{'menu_waround'}) { + $menu_waround_active->set_active($settings_xml->{'general'}->{'menu_waround'}); + } else { + $menu_waround_active->set_active(TRUE); + } } else { - $menu_waround_active->set_active(TRUE); + $menu_waround_active->set_active(FALSE); + $menu_waround_active->set_sensitive(FALSE); } #end - menu/tooltip workaround @@ -2056,12 +2110,17 @@ modes are currently experimental, your desktop environment might fall back to an $combobox_web_width->insert_text(10, "2048"); my $web_width_vlabel = Gtk3::Label->new($d->get("pixels")); - if (defined $settings_xml->{'general'}->{'web_width'}) { - $combobox_web_width->set_active($settings_xml->{'general'}->{'web_width'}); + if ($gnome_web_photo) { + if (defined $settings_xml->{'general'}->{'web_width'}) { + $combobox_web_width->set_active($settings_xml->{'general'}->{'web_width'}); + } else { + $combobox_web_width->set_active(2); + } } else { - $combobox_web_width->set_active(2); + $combobox_web_width->set_active(FALSE); + $combobox_web_width->set_sensitive(FALSE); } - + $web_width_label->set_tooltip_text($d->get("Virtual browser width when taking a website screenshot")); $combobox_web_width->set_tooltip_text($d->get("Virtual browser width when taking a website screenshot")); $web_width_vlabel->set_tooltip_text($d->get("Virtual browser width when taking a website screenshot")); From 26960f9e5e943608c8bd443bd0588fa4fec5b59f Mon Sep 17 00:00:00 2001 From: Photon89 Date: Thu, 6 Aug 2026 08:33:42 +0200 Subject: [PATCH 08/20] Disable further settings in environments where they are unnecessary --- bin/shutter | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/bin/shutter b/bin/shutter index e44b8a86..e804e4af 100755 --- a/bin/shutter +++ b/bin/shutter @@ -1474,7 +1474,10 @@ modes are currently experimental, your desktop environment might fall back to an 'value-changed' => \&evt_value_changed, 'delay_status_changed' ); - + unless($x11_supported) { + $delay_status->set_sensitive(FALSE); + } + #delay settings dialog my $delay_label = Gtk3::Label->new($d->get("Capture after a delay of")); $delay = Gtk3::SpinButton->new_with_range(0, 99, 1); @@ -1484,10 +1487,15 @@ modes are currently experimental, your desktop environment might fall back to an 'delay_changed' ); - if (defined $settings_xml->{'general'}->{'delay'}) { - $delay->set_value($settings_xml->{'general'}->{'delay'}); + if($x11_supported) { + if (defined $settings_xml->{'general'}->{'delay'}) { + $delay->set_value($settings_xml->{'general'}->{'delay'}); + } else { + $delay->set_value(0); + } } else { $delay->set_value(0); + $delay->set_sensitive(FALSE); } $delay->set_tooltip_text($d->get("Wait n seconds before taking a screenshot")); @@ -1514,6 +1522,10 @@ modes are currently experimental, your desktop environment might fall back to an 'cursor_status_toggled' ); + unless($x11_supported) { + $cursor_status_active->set_sensitive(FALSE); + } + $cursor_active = Gtk3::CheckButton->new_with_label($d->get("Include cursor when taking a screenshot")); $cursor_active->set_tooltip_text($d->get("Include cursor when taking a screenshot")); $cursor_active->signal_connect( @@ -1523,12 +1535,16 @@ modes are currently experimental, your desktop environment might fall back to an $cursor_box->pack_start($cursor_active, FALSE, TRUE, 12); - if (defined $settings_xml->{'general'}->{'cursor'}) { - $cursor_active->set_active($settings_xml->{'general'}->{'cursor'}); + if($x11_supported) { + if (defined $settings_xml->{'general'}->{'cursor'}) { + $cursor_active->set_active($settings_xml->{'general'}->{'cursor'}); + } else { + $cursor_active->set_active(FALSE); + } } else { - $cursor_active->set_active(FALSE); + $cursor_active->set_active(TRUE); + $cursor_active->set_sensitive(FALSE); } - #end - cursor #-------------------------------------- From 4ab49350020a55f5d901c83f518f767e7eb775cf Mon Sep 17 00:00:00 2001 From: Photon89 Date: Thu, 6 Aug 2026 08:50:14 +0200 Subject: [PATCH 09/20] Fixed default value for include_cursor --- bin/shutter | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/shutter b/bin/shutter index e804e4af..18f0ad7e 100755 --- a/bin/shutter +++ b/bin/shutter @@ -1542,7 +1542,7 @@ modes are currently experimental, your desktop environment might fall back to an $cursor_active->set_active(FALSE); } } else { - $cursor_active->set_active(TRUE); + $cursor_active->set_active(FALSE); $cursor_active->set_sensitive(FALSE); } #end - cursor From 6c9222116a50774b715108884e310f4ba8dd739e Mon Sep 17 00:00:00 2001 From: Photon89 Date: Fri, 14 Aug 2026 16:48:52 +0200 Subject: [PATCH 10/20] Use names instead of numerals for targets --- bin/shutter | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bin/shutter b/bin/shutter index 18f0ad7e..7d8339a7 100755 --- a/bin/shutter +++ b/bin/shutter @@ -610,10 +610,10 @@ sub STARTUP { %supported_targets = (); } else { if (defined $targets_raw) { - $supported_targets{'1'} = 1 if ($targets_raw & 1); - $supported_targets{'2'} = 1 if ($targets_raw & 2); - $supported_targets{'4'} = 1 if ($targets_raw & 4); - $supported_targets{'8'} = 1 if ($targets_raw & 8); + $supported_targets{'full'} = 1 if ($targets_raw & 1); + $supported_targets{'window'} = 1 if ($targets_raw & 2); + $supported_targets{'select'} = 1 if ($targets_raw & 4); + $supported_targets{'awindow'} = 1 if ($targets_raw & 8); } else { %supported_targets = (); } @@ -6336,11 +6336,11 @@ modes are currently experimental, your desktop environment might fall back to an # For each non-trivial capture mode (window, active window, selection) define a target # to try and call the non-interactive portal interface if ($data eq "window" || $data eq "tray_window") { - $target=2; + $target="window"; } elsif ($data eq "awindow" || $data eq "tray_awindow") { - $target=8 + $target="awindow"; } elsif ($data eq "select" || $data eq "tray_select") { - $target=4; + $target="select"; } # For the target chosen by user check if it is supported non-interactively. # If unsupported, fall back to an interactive call which makes use of the From 5e437f363dca0a469499af691a41db3f4e19cfdd Mon Sep 17 00:00:00 2001 From: Photon89 Date: Fri, 14 Aug 2026 17:03:32 +0200 Subject: [PATCH 11/20] Use proper xdg-portal call for full screen capture; added comments --- bin/shutter | 41 +++++++++---------- .../modules/Shutter/Screenshot/Wayland.pm | 4 +- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/bin/shutter b/bin/shutter index 7d8339a7..55327033 100755 --- a/bin/shutter +++ b/bin/shutter @@ -6326,32 +6326,29 @@ modes are currently experimental, your desktop environment might fall back to an #wayland: route selection/window/active to xdg-desktop-portal if (!$x11_supported) { + # For some period of time desktops won't support non-interactive targets, we need to deal with it. - my $target; - my $interactive; - # For fullscreen capture just use plain xdg_portal + my $target; # capture mode sent to xdg-portal + my $interactive; # whether to use the desktop environment's GUI (only for the time till DEs support non-interactive calls) + if ($data eq "full" || $data eq "tray_full") { - $screenshot = Shutter::Screenshot::Wayland::xdg_portal($screenshooter); + $target="full"; + } elsif ($data eq "window" || $data eq "tray_window") { + $target="window"; + } elsif ($data eq "awindow" || $data eq "tray_awindow") { + $target="awindow"; + } elsif ($data eq "select" || $data eq "tray_select") { + $target="select"; + } + # For the target chosen by user check if it is supported non-interactively. + # If unsupported, fall back to an interactive call which makes use of the + # DE's interactive tool. + if ($supported_targets{$target}) { + $interactive=0; } else { - # For each non-trivial capture mode (window, active window, selection) define a target - # to try and call the non-interactive portal interface - if ($data eq "window" || $data eq "tray_window") { - $target="window"; - } elsif ($data eq "awindow" || $data eq "tray_awindow") { - $target="awindow"; - } elsif ($data eq "select" || $data eq "tray_select") { - $target="select"; - } - # For the target chosen by user check if it is supported non-interactively. - # If unsupported, fall back to an interactive call which makes use of the - # DE's interactive tool. - if ($supported_targets{$target}) { - $interactive=0; - } else { - $interactive=1; - } - $screenshot = Shutter::Screenshot::Wayland::xdg_portal($screenshooter, $interactive, $target); + $interactive=1; } + $screenshot = Shutter::Screenshot::Wayland::xdg_portal($screenshooter, $interactive, $target); } else { #fullscreen screenshot if ($data eq "full" || $data eq "tray_full") { diff --git a/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm b/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm index b3be7c10..9c9c4bb5 100644 --- a/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm +++ b/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm @@ -8,8 +8,8 @@ package Shutter::Screenshot::Wayland; sub xdg_portal { my $screenshooter = shift; - my $interactive = shift; - my $target = shift; + my $interactive = shift; # whether to use the desktop environment's GUI (only for the time till DEs support non-interactive calls) + my $target = shift; # screenshot mode (full, select, window, awindow) to send to xdg-portal # Fall back to fullscreen $target = 1 unless defined $target; From 1a31f9e70b224c523d7ecc363b92905f344edf56 Mon Sep 17 00:00:00 2001 From: Photon89 Date: Fri, 14 Aug 2026 17:20:22 +0200 Subject: [PATCH 12/20] Convert back to numerals in Wayland.pm --- .../modules/Shutter/Screenshot/Wayland.pm | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm b/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm index 9c9c4bb5..54acbbb9 100644 --- a/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm +++ b/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm @@ -14,6 +14,20 @@ sub xdg_portal { # Fall back to fullscreen $target = 1 unless defined $target; + # We need to go back to numerical values to pass them to xdg-portal + if ($target eq "full") { + $target = 1; + } elsif ($target eq "window") { + $target = 2; + } elsif ($target eq "select") { + $target = 4; + } elsif ($target eq "awindow") { + $target = 8; + } else { + $target = 1; + } + + my $reactor = Net::DBus::Reactor->main; my $bus = Net::DBus->find; my $me = $bus->get_unique_name; From e0d95fccc36b77b84dc11b2d64c9867f1d34896f Mon Sep 17 00:00:00 2001 From: Photon89 Date: Fri, 14 Aug 2026 18:03:29 +0200 Subject: [PATCH 13/20] Reverted to numerical target values --- bin/shutter | 32 ++++++------------- .../modules/Shutter/Screenshot/Wayland.pm | 14 -------- 2 files changed, 9 insertions(+), 37 deletions(-) diff --git a/bin/shutter b/bin/shutter index 55327033..c01804de 100755 --- a/bin/shutter +++ b/bin/shutter @@ -372,6 +372,7 @@ my $sm; my $sp; my $st; my $status; +my $supported_targets; my $thumbnail_active; my $thumbnail; my $trans_backg; @@ -396,7 +397,6 @@ my $zoom_active; my %accounts; #hash to store account infos my %settings; #hash to store settings my @supported_formats; #hash to store available supported file formats - my %supported_targets; #hash to store available targets for xdg-desktop-portal sub STARTUP { # This is called by $app->run below if another Shutter instance is not running. @@ -590,34 +590,20 @@ sub STARTUP { if ($ENV{XDG_SESSION_TYPE} eq "wayland") { $x11_supported = 0; - # Get targets supported by xdg-desktop-portal and hash them into %supported_targets + # Get targets supported by xdg-desktop-portal # Possible targets are: # 1: Screen (corresponds "full") # 2: Window (corresponds "window") # 4: Area (corresponds "select") # 8: Active Window (corresponds "awindow") # See: https://github.com/flatpak/xdg-desktop-portal/pull/1981 - my $targets_raw; eval { - $targets_raw = Net::DBus->session + $supported_targets = Net::DBus->session ->get_service("org.freedesktop.portal.Desktop") ->get_object("/org/freedesktop/portal/desktop") ->as_interface("org.freedesktop.DBus.Properties") ->Get("org.freedesktop.portal.Screenshot", "AvailableTargets"); }; - - if ($@) { - %supported_targets = (); - } else { - if (defined $targets_raw) { - $supported_targets{'full'} = 1 if ($targets_raw & 1); - $supported_targets{'window'} = 1 if ($targets_raw & 2); - $supported_targets{'select'} = 1 if ($targets_raw & 4); - $supported_targets{'awindow'} = 1 if ($targets_raw & 8); - } else { - %supported_targets = (); - } - } } if ($x11_supported) { @@ -6326,24 +6312,24 @@ modes are currently experimental, your desktop environment might fall back to an #wayland: route selection/window/active to xdg-desktop-portal if (!$x11_supported) { - + # For some period of time desktops won't support non-interactive targets, we need to deal with it. my $target; # capture mode sent to xdg-portal my $interactive; # whether to use the desktop environment's GUI (only for the time till DEs support non-interactive calls) if ($data eq "full" || $data eq "tray_full") { - $target="full"; + $target=1; } elsif ($data eq "window" || $data eq "tray_window") { - $target="window"; + $target=2; } elsif ($data eq "awindow" || $data eq "tray_awindow") { - $target="awindow"; + $target=8; } elsif ($data eq "select" || $data eq "tray_select") { - $target="select"; + $target=4; } # For the target chosen by user check if it is supported non-interactively. # If unsupported, fall back to an interactive call which makes use of the # DE's interactive tool. - if ($supported_targets{$target}) { + if ($supported_targets & $target) { $interactive=0; } else { $interactive=1; diff --git a/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm b/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm index 54acbbb9..9c9c4bb5 100644 --- a/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm +++ b/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm @@ -14,20 +14,6 @@ sub xdg_portal { # Fall back to fullscreen $target = 1 unless defined $target; - # We need to go back to numerical values to pass them to xdg-portal - if ($target eq "full") { - $target = 1; - } elsif ($target eq "window") { - $target = 2; - } elsif ($target eq "select") { - $target = 4; - } elsif ($target eq "awindow") { - $target = 8; - } else { - $target = 1; - } - - my $reactor = Net::DBus::Reactor->main; my $bus = Net::DBus->find; my $me = $bus->get_unique_name; From 48dfbff1629e2cd330896dbda382699cbde43559 Mon Sep 17 00:00:00 2001 From: Photon89 Date: Sat, 15 Aug 2026 11:58:09 +0200 Subject: [PATCH 14/20] Added capture mode dependent file names on Wayland --- bin/shutter | 48 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/bin/shutter b/bin/shutter index c01804de..ad3148ef 100755 --- a/bin/shutter +++ b/bin/shutter @@ -6690,7 +6690,7 @@ modes are currently experimental, your desktop environment might fall back to an } #..replace wildcards by values - $tmp_filename = &fct_parse_filename_wildcards($tmp_filename, $screenshooter, $screenshot); + $tmp_filename = &fct_parse_filename_wildcards($tmp_filename, $screenshooter, $screenshot, $data); #...and create an uri $giofile = Glib::IO::File::new_for_path($tmp_filename); @@ -6703,7 +6703,7 @@ modes are currently experimental, your desktop environment might fall back to an $filename_value =~ s/(\/|\#)/-/g; #..replace wildcards by values - $filename_value = &fct_parse_filename_wildcards($filename_value, $screenshooter, $screenshot); + $filename_value = &fct_parse_filename_wildcards($filename_value, $screenshooter, $screenshot, $data); #...and get next filename $giofile = fct_get_next_filename($filename_value, $folder, $filetype_value); @@ -8118,7 +8118,7 @@ modes are currently experimental, your desktop environment might fall back to an } sub fct_parse_filename_wildcards { - my ($filename_value, $screenshooter, $screenshot) = @_; + my ($filename_value, $screenshooter, $screenshot, $capture_mode) = @_; my $screenshot_name = $filename_value; @@ -8144,20 +8144,42 @@ modes are currently experimental, your desktop environment might fall back to an #set name #e.g. window or workspace name + my $resolved_action_name = undef; + if ($x11_supported) { + # Get action name from X11 screenshooter if available if (my $action_name = $screenshooter->get_action_name) { - utf8::decode $action_name; - $action_name =~ s/(\/|\#|\>|\<|\%|\*)/-/g; - $screenshot_name =~ s/\$name/$action_name/g; - - #no blanks (special wildcard) - $action_name =~ s/\ //g; - $screenshot_name =~ s/\$nb_name/$action_name/g; - } else { - $screenshot_name =~ s/(\$name|\$nb_name)/unknown/g; + $resolved_action_name = $action_name; } } else { - $screenshot_name =~ s/(\$name|\$nb_name)/unknown/g; + # Wayland fallback using predefined translations + if (defined $capture_mode) { + if ($capture_mode eq "full" || $capture_mode eq "tray_full") { + $resolved_action_name = $d->get("Workspaces"); + } elsif ($capture_mode eq "window" || $capture_mode eq "tray_window" || $capture_mode eq "awindow" || $capture_mode eq "tray_awindow") { + $resolved_action_name = $d->get("Window"); + } elsif ($capture_mode eq "select" || $capture_mode eq "tray_select") { + my $selection_text = $d->get("Selection"); + if (defined $swidth && defined $sheight) { + $resolved_action_name = "${selection_text}_${swidth}x${sheight}"; + } else { + $resolved_action_name = $selection_text; + } + } + } + } + + # Unified processing for both X11 and Wayland strings + if ($resolved_action_name) { + utf8::decode $resolved_action_name; + $resolved_action_name =~ s/(\/|\#|\>|\<|\%|\*)/-/g; + $screenshot_name =~ s/\$name/$resolved_action_name/g; + + #no blanks (special wildcard) + $resolved_action_name =~ s/\ //g; + $screenshot_name =~ s/\$nb_name/$resolved_action_name/g; + } else { + $screenshot_name =~ s/(\$name|\$nb_name)/unknown/g; } print "Parsed \$name: $screenshot_name\n" From 59fc94e32cd18c0803bcbee19a6f5d5b5831f26d Mon Sep 17 00:00:00 2001 From: Photon89 Date: Sat, 15 Aug 2026 12:21:48 +0200 Subject: [PATCH 15/20] Fix error if cannot be retrieved from xdg-portal --- bin/shutter | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bin/shutter b/bin/shutter index ad3148ef..7e75f0b3 100755 --- a/bin/shutter +++ b/bin/shutter @@ -6315,7 +6315,7 @@ modes are currently experimental, your desktop environment might fall back to an # For some period of time desktops won't support non-interactive targets, we need to deal with it. my $target; # capture mode sent to xdg-portal - my $interactive; # whether to use the desktop environment's GUI (only for the time till DEs support non-interactive calls) + my $interactive=1; # whether to use the desktop environment's GUI (only for the time till DEs support non-interactive calls) if ($data eq "full" || $data eq "tray_full") { $target=1; @@ -6329,10 +6329,10 @@ modes are currently experimental, your desktop environment might fall back to an # For the target chosen by user check if it is supported non-interactively. # If unsupported, fall back to an interactive call which makes use of the # DE's interactive tool. - if ($supported_targets & $target) { - $interactive=0; - } else { - $interactive=1; + if (defined $supported_targets) { + if ($supported_targets & $target) { + $interactive=0; + } } $screenshot = Shutter::Screenshot::Wayland::xdg_portal($screenshooter, $interactive, $target); } else { From a5c964053a84d2ad7a9499363e68f35ae5f4c94d Mon Sep 17 00:00:00 2001 From: Photon89 Date: Sat, 15 Aug 2026 15:42:34 +0200 Subject: [PATCH 16/20] Fall back to simple full screen for DE without non-interactive call support --- .../resources/modules/Shutter/Screenshot/Wayland.pm | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm b/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm index 9c9c4bb5..4018544c 100644 --- a/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm +++ b/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm @@ -40,10 +40,11 @@ sub xdg_portal { my %options = (handle_token => $token); - $options{interactive} = Net::DBus::dbus_boolean($interactive); - if ($interactive eq 0) { - $options{target} = Net::DBus::dbus_uint32($target); - } + # set the interactive flag unless xdg-portal doesn't support non-interactive calls and we want full-screen capture + $options{interactive} = Net::DBus::dbus_boolean($interactive) unless $interactive eq 1 && $target eq 1; + + # only define a target if xdg-portal supports non-interactive calls + $options{target} = Net::DBus::dbus_uint32($target) if $interactive ne 1; my $request_path = $portal->Screenshot('', \%options); From 8ee9ab84aff939361eb5613ddd6cb10115b97216 Mon Sep 17 00:00:00 2001 From: Photon89 Date: Tue, 18 Aug 2026 09:06:44 +0200 Subject: [PATCH 17/20] Fix crash due to uninitialized on Wayland --- bin/shutter | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bin/shutter b/bin/shutter index 46df8aa0..870ec63a 100755 --- a/bin/shutter +++ b/bin/shutter @@ -6333,13 +6333,13 @@ modes are currently experimental, your desktop environment might fall back to an } # screen mode from PR #828: 1 (full screen), 2 (current monitor) - my $screen_mode = (defined $monitor || $current_monitor_active->get_active) ? 2 : 1; - + my $is_current_monitor = (defined $current_monitor_active && $current_monitor_active->get_active) ? 1 : 0; + my $screen_mode = (defined $monitor || $is_current_monitor) ? 2 : 1; $screenshot = Shutter::Screenshot::Wayland::xdg_portal( $screenshooter, - interactive => $interactive, - target => $target, - screen_mode => $screen_mode + $interactive, + $target, + $screen_mode ); } else { #fullscreen screenshot From e4a4226fb97b04f5e1d2d6f5ad6849ae3b5037e3 Mon Sep 17 00:00:00 2001 From: Photon89 Date: Tue, 18 Aug 2026 11:04:11 +0200 Subject: [PATCH 18/20] Merged capture from #828 into xdg_portal in Wayland.pm --- bin/shutter | 11 ++--- .../modules/Shutter/Screenshot/Wayland.pm | 41 ++++++++----------- 2 files changed, 21 insertions(+), 31 deletions(-) diff --git a/bin/shutter b/bin/shutter index 870ec63a..4e016335 100755 --- a/bin/shutter +++ b/bin/shutter @@ -6332,15 +6332,12 @@ modes are currently experimental, your desktop environment might fall back to an } } - # screen mode from PR #828: 1 (full screen), 2 (current monitor) + # screen mode from PR #828: 1 (full screen), 2 (current monitor) my $is_current_monitor = (defined $current_monitor_active && $current_monitor_active->get_active) ? 1 : 0; my $screen_mode = (defined $monitor || $is_current_monitor) ? 2 : 1; - $screenshot = Shutter::Screenshot::Wayland::xdg_portal( - $screenshooter, - $interactive, - $target, - $screen_mode - ); + + $screenshooter = Shutter::Screenshot::Wayland->new($sc, $interactive, $target, $screen_mode, $monitor); + $screenshot = Shutter::Screenshot::Wayland::xdg_portal($screenshooter); } else { #fullscreen screenshot if ($data eq "full" || $data eq "tray_full" || $data =~ /^shutter_monitor_direct(\d+)/) { diff --git a/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm b/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm index da406081..26ea0995 100644 --- a/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm +++ b/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm @@ -13,29 +13,17 @@ use Shutter::Screenshot::History; sub new { my $class = shift; my $self = { - _sc => shift, - _monitor => shift, #undef captures the whole desktop - }; + _sc => shift, + _interactive => shift, # whether to use the desktop environment's GUI (only for the time till DEs support non-interactive calls) + _target => shift, # screenshot mode (full, select, window, awindow) to send to xdg-portal + _screen_mode => shift, # whether we are in a multimonitor setup + _monitor => shift, # undef captures the whole desktop + }; $self->{_gdk_screen} = Gtk3::Gdk::Screen::get_default(); bless $self, $class; return $self; } -sub capture { - my $self = shift; - - my $pixbuf = xdg_portal($self); - return $pixbuf unless ref($pixbuf) && $pixbuf->isa('Gtk3::Gdk::Pixbuf'); - - if (defined $self->{_monitor}) { - $pixbuf = crop_to_monitor($pixbuf, $self->{_gdk_screen}, $self->{_monitor}); - } - - #a history marker makes this capture repeatable through redoshot - $self->{_history} = Shutter::Screenshot::History->new($self->{_sc}); - return $pixbuf; -} - sub redo_capture { my $self = shift; return 3 unless defined $self->{_history}; @@ -53,12 +41,10 @@ sub get_error_text { } sub xdg_portal { + my $self = shift; my $screenshooter = shift; - my $interactive = shift; # whether to use the desktop environment's GUI (only for the time till DEs support non-interactive calls) - my $target = shift; # screenshot mode (full, select, window, awindow) to send to xdg-portal - # Fall back to fullscreen - $target = 1 unless defined $target; + $self->{_target} = 1 unless defined $self->{_target}; my $reactor = Net::DBus::Reactor->main; my $bus = Net::DBus->find; @@ -88,10 +74,10 @@ sub xdg_portal { my %options = (handle_token => $token); # set the interactive flag unless xdg-portal doesn't support non-interactive calls and we want full-screen capture - $options{interactive} = Net::DBus::dbus_boolean($interactive) unless $interactive eq 1 && $target eq 1; + $options{interactive} = Net::DBus::dbus_boolean($self->{_interactive}) unless $self->{_interactive} eq 1 && $self->{_target} eq 1; # only define a target if xdg-portal supports non-interactive calls - $options{target} = Net::DBus::dbus_uint32($target) if $interactive ne 1; + $options{target} = Net::DBus::dbus_uint32($self->{_target}) if $self->{_interactive} ne 1; my $request_path = $portal->Screenshot('', \%options); @@ -119,8 +105,15 @@ sub xdg_portal { my $giofile = Glib::IO::File::new_for_uri($output->{uri}); print "xdg portal: got file ".$giofile->get_path."\n"; $pixbuf = Gtk3::Gdk::Pixbuf->new_from_file($giofile->get_path); + $giofile->delete; }; + if (defined $self->{_monitor}) { + $pixbuf = crop_to_monitor($pixbuf, $self->{_gdk_screen}, $self->{_monitor}); + } + + #a history marker makes this capture repeatable through redoshot + $self->{_history} = Shutter::Screenshot::History->new($self->{_sc}); if ($@) { $screenshooter->{_error_text} = $@; return 9; From ca986237ee477ddba395b295f9a4f161fbd0b333 Mon Sep 17 00:00:00 2001 From: Photon89 Date: Wed, 19 Aug 2026 14:37:44 +0200 Subject: [PATCH 19/20] Moved screenshot name logic into Wayland.pm and added monitor specific names for multimonitor case --- bin/shutter | 23 ++--------------- .../modules/Shutter/Screenshot/Wayland.pm | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+), 21 deletions(-) mode change 100755 => 100644 bin/shutter diff --git a/bin/shutter b/bin/shutter old mode 100755 new mode 100644 index 4e016335..91b6da11 --- a/bin/shutter +++ b/bin/shutter @@ -8139,27 +8139,8 @@ modes are currently experimental, your desktop environment might fall back to an #e.g. window or workspace name my $resolved_action_name = undef; - if ($x11_supported) { - # Get action name from X11 screenshooter if available - if (my $action_name = $screenshooter->get_action_name) { - $resolved_action_name = $action_name; - } - } else { - # Wayland fallback using predefined translations - if (defined $capture_mode) { - if ($capture_mode eq "full" || $capture_mode eq "tray_full") { - $resolved_action_name = $d->get("Workspaces"); - } elsif ($capture_mode eq "window" || $capture_mode eq "tray_window" || $capture_mode eq "awindow" || $capture_mode eq "tray_awindow") { - $resolved_action_name = $d->get("Window"); - } elsif ($capture_mode eq "select" || $capture_mode eq "tray_select") { - my $selection_text = $d->get("Selection"); - if (defined $swidth && defined $sheight) { - $resolved_action_name = "${selection_text}_${swidth}x${sheight}"; - } else { - $resolved_action_name = $selection_text; - } - } - } + if (my $action_name = $screenshooter->get_action_name) { + $resolved_action_name = $action_name; } # Unified processing for both X11 and Wayland strings diff --git a/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm b/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm index 26ea0995..bbce9621 100644 --- a/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm +++ b/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm @@ -43,6 +43,7 @@ sub get_error_text { sub xdg_portal { my $self = shift; my $screenshooter = shift; + my $d = $self->{_sc}->get_gettext; # Fall back to fullscreen $self->{_target} = 1 unless defined $self->{_target}; @@ -123,6 +124,25 @@ sub xdg_portal { return 9; } + # get name + if ($self->{_target} eq 1) { + if (defined $self->{_monitor}) { + $self->{_action_name} = $self->{_gdk_screen}->get_monitor_plug_name($self->{_monitor}); + } else { + $self->{_action_name} = $d->get("Workspaces"); + } + } elsif ($self->{_target} eq 2 || $self->{_target} eq 8) { + $self->{_action_name} = $d->get("Window"); + } elsif ($self->{_target} eq 4) { + my $selection_text = $d->get("Selection"); + my $swidth = $pixbuf->get_width; + my $sheight = $pixbuf->get_height; + if (defined $swidth && defined $sheight) { + $self->{_action_name} = "${selection_text}_${swidth}x${sheight}"; + } else { + $self->{_action_name} = $selection_text; + } + } return $pixbuf; } @@ -160,4 +180,9 @@ sub crop_to_monitor { return $cropped; } +sub get_action_name { + my $self = shift; + return $self->{_action_name}; +} + 1; From 096fd0c9df3b63b8dbd7cadd6ceb30c7a36e8cce Mon Sep 17 00:00:00 2001 From: Photon89 Date: Wed, 19 Aug 2026 22:04:23 +0200 Subject: [PATCH 20/20] Disable window list on Wayland --- bin/shutter | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) mode change 100644 => 100755 bin/shutter diff --git a/bin/shutter b/bin/shutter old mode 100644 new mode 100755 index 91b6da11..e964ecf5 --- a/bin/shutter +++ b/bin/shutter @@ -988,11 +988,11 @@ modes are currently experimental, your desktop environment might fall back to an #init menus if ($x11_supported){ $st->{_full}->set_menu(fct_ret_workspace_menu(TRUE)); + $st->{_window}->set_menu(fct_ret_window_menu()); } elsif (Gtk3::Gdk::Screen::get_default->get_n_monitors > 1) { - #Wayland: offer per-monitor capture through the "full" dropdown + #Wayland: offer per-monitor capture through the "full" dropdown, no window list available $st->{_full}->set_menu(fct_ret_workspace_menu(TRUE)); } - $st->{_window}->set_menu(fct_ret_window_menu()); #and attach signal handlers $st->{_full}->signal_connect('show-menu' => sub { $st->{_full}->set_menu(fct_ret_workspace_menu(FALSE)) });