From c96b2f257fb90b4a6bd349966454fd642a3e4cd3 Mon Sep 17 00:00:00 2001 From: Photon89 Date: Mon, 25 May 2026 17:07:23 +0200 Subject: [PATCH 01/15] 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 fa38a4ba..bbd390cd --- a/bin/shutter +++ b/bin/shutter @@ -395,7 +395,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. @@ -589,6 +589,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) { @@ -655,7 +670,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); } } @@ -1000,11 +1017,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); } @@ -3128,7 +3147,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'); @@ -6183,10 +6205,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, @@ -6194,302 +6243,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... @@ -8655,7 +8700,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)); @@ -8704,7 +8750,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 bb3b2d4d842afd5404568587909fb45f4fcd440d Mon Sep 17 00:00:00 2001 From: Photon89 Date: Mon, 25 May 2026 17:42:48 +0200 Subject: [PATCH 02/15] 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 c61459ba0ec95fcb515dbd27941c6d0b256f0ec5 Mon Sep 17 00:00:00 2001 From: Photon89 Date: Tue, 26 May 2026 08:28:25 +0200 Subject: [PATCH 03/15] 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 bbd390cd..dff97662 100755 --- a/bin/shutter +++ b/bin/shutter @@ -596,14 +596,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 ae11a3d20dc47201bec097ca3cb502907c98f0cb Mon Sep 17 00:00:00 2001 From: Photon89 Date: Tue, 26 May 2026 11:12:05 +0200 Subject: [PATCH 04/15] Fix retrieving ActiveTargets from portal --- bin/shutter | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/bin/shutter b/bin/shutter index dff97662..81c21495 100755 --- a/bin/shutter +++ b/bin/shutter @@ -604,14 +604,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 5338d99f7aacb6c2f71a0b08588b157c1c5869b0 Mon Sep 17 00:00:00 2001 From: Photon89 Date: Wed, 27 May 2026 08:54:18 +0200 Subject: [PATCH 05/15] Update warning message --- bin/shutter | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/shutter b/bin/shutter index 81c21495..952a95bc 100755 --- a/bin/shutter +++ b/bin/shutter @@ -977,7 +977,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 5b05eccb4e277f985011e570cddc59c24f158028 Mon Sep 17 00:00:00 2001 From: Photon89 Date: Wed, 5 Aug 2026 10:41:29 +0200 Subject: [PATCH 06/15] 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 952a95bc..dd113061 100755 --- a/bin/shutter +++ b/bin/shutter @@ -1752,10 +1752,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")); @@ -1778,25 +1783,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")); @@ -1828,10 +1840,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")); @@ -1845,10 +1862,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")); @@ -1865,10 +1887,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 @@ -1884,22 +1911,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( @@ -1931,10 +1965,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( @@ -1961,10 +2000,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 @@ -1981,10 +2025,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")); @@ -2007,10 +2056,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 @@ -2033,12 +2087,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 07ae64b09adb127d3cc7529f185c77e3b50fbc8a Mon Sep 17 00:00:00 2001 From: Photon89 Date: Thu, 6 Aug 2026 08:33:42 +0200 Subject: [PATCH 07/15] 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 dd113061..7935723c 100755 --- a/bin/shutter +++ b/bin/shutter @@ -1467,7 +1467,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); @@ -1477,10 +1480,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")); @@ -1507,6 +1515,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( @@ -1516,12 +1528,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 b42c47c9b2ab8f93e16213019531afa458b7e309 Mon Sep 17 00:00:00 2001 From: Photon89 Date: Thu, 6 Aug 2026 08:50:14 +0200 Subject: [PATCH 08/15] 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 7935723c..b69539b7 100755 --- a/bin/shutter +++ b/bin/shutter @@ -1535,7 +1535,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 c7529cfdab0013dd9300ef755fd71ef3bdd1202e Mon Sep 17 00:00:00 2001 From: Photon89 Date: Fri, 14 Aug 2026 16:48:52 +0200 Subject: [PATCH 09/15] 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 b69539b7..d17d8d6c 100755 --- a/bin/shutter +++ b/bin/shutter @@ -609,10 +609,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 = (); } @@ -6306,11 +6306,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 daca43f35aacbc2845ed2bf8697f84cb93556c54 Mon Sep 17 00:00:00 2001 From: Photon89 Date: Fri, 14 Aug 2026 17:03:32 +0200 Subject: [PATCH 10/15] 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 d17d8d6c..6eb78a82 100755 --- a/bin/shutter +++ b/bin/shutter @@ -6296,32 +6296,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 b0e3dd691397c1d37fde81b8b522cfe891f42f78 Mon Sep 17 00:00:00 2001 From: Photon89 Date: Fri, 14 Aug 2026 17:20:22 +0200 Subject: [PATCH 11/15] 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 8d48014666abb4c0c27269c2cfa6f1181b4f4ede Mon Sep 17 00:00:00 2001 From: Photon89 Date: Fri, 14 Aug 2026 18:03:29 +0200 Subject: [PATCH 12/15] 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 6eb78a82..a8e0e09f 100755 --- a/bin/shutter +++ b/bin/shutter @@ -371,6 +371,7 @@ my $sm; my $sp; my $st; my $status; +my $supported_targets; my $thumbnail_active; my $thumbnail; my $trans_backg; @@ -395,7 +396,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. @@ -589,34 +589,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) { @@ -6296,24 +6282,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 86022675db17be2d45a48d790ca6928f2832d5a6 Mon Sep 17 00:00:00 2001 From: Photon89 Date: Sat, 15 Aug 2026 11:58:09 +0200 Subject: [PATCH 13/15] 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 a8e0e09f..b0f6458a 100755 --- a/bin/shutter +++ b/bin/shutter @@ -6660,7 +6660,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); @@ -6673,7 +6673,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); @@ -8089,7 +8089,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; @@ -8115,20 +8115,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 f0ec8f8fdc91db9924fabb39f656fac4628e9eca Mon Sep 17 00:00:00 2001 From: Photon89 Date: Sat, 15 Aug 2026 12:21:48 +0200 Subject: [PATCH 14/15] 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 b0f6458a..c8d390e2 100755 --- a/bin/shutter +++ b/bin/shutter @@ -6285,7 +6285,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; @@ -6299,10 +6299,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 ccc99982976e613bb504e848c6c42ae5c7287bff Mon Sep 17 00:00:00 2001 From: Photon89 Date: Sat, 15 Aug 2026 15:42:34 +0200 Subject: [PATCH 15/15] 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);