diff --git a/bin/shutter b/bin/shutter index fa38a4ba..28c1aa13 100755 --- a/bin/shutter +++ b/bin/shutter @@ -655,7 +655,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 interactive 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 +1002,13 @@ sub STARTUP { $st->{_upload}->set_sensitive(FALSE); unless ($x11_supported) { + # on Wayland _select/_window use the interactive 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 +3132,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,8 +6190,14 @@ sub STARTUP { } } + #wayland: route selection/window/active to the compositor's interactive picker + if (!$x11_supported + && $data =~ /^(tray_)?(select|window|awindow)$/) { + + $screenshot = Shutter::Screenshot::Wayland::xdg_portal($screenshooter, 1); + #fullscreen screenshot - if ($data eq "full" || $data eq "tray_full") { + } elsif ($data eq "full" || $data eq "tray_full") { if ($x11_supported) { $screenshooter = Shutter::Screenshot::Workspace->new( @@ -8655,7 +8668,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 +8718,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/docs/superpowers/specs/2026-05-21-wayland-interactive-selection-design.md b/docs/superpowers/specs/2026-05-21-wayland-interactive-selection-design.md new file mode 100644 index 00000000..95b9af9d --- /dev/null +++ b/docs/superpowers/specs/2026-05-21-wayland-interactive-selection-design.md @@ -0,0 +1,63 @@ +# Selezione interattiva su Wayland (strada A) + +Data: 2026-05-21 +Branch: `feature/wayland-interactive-selection` + +## Problema + +Su Wayland Shutter cattura solo lo schermo intero (via `xdg-desktop-portal`). +I bottoni Selezione area, Finestra e Attiva sono disabilitati perché dipendono +da X11 (`Wnck`, `GdkX11`), non accessibile su Wayland per ragioni di sicurezza. +Riferimento upstream: issue #187. + +## Obiettivo + +Abilitare su Wayland i bottoni Selezione / Finestra / Attiva. Tutti invocano il +portal XDG in modalità **interattiva**: il compositor (GNOME) mostra il proprio +selettore nativo (area / finestra / schermo) e ritorna un'immagine già ritagliata, +che entra nel flusso post-cattura esistente di Shutter. + +## Componenti toccati + +### 1. `share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm` +- `xdg_portal($screenshooter, $interactive)`: nuovo secondo parametro. +- Opzioni portal: `{handle_token => $token, interactive => $interactive ? TRUE : FALSE}`. +- Chiamata full-screen attuale → passa `FALSE` (comportamento invariato). + +### 2. `bin/shutter` (~riga 4406) +- Logica che disabilita i bottoni su Wayland (`$x11_supported` falso): condizionare + così che Selezione, Finestra e Attiva restino **abilitati** anche su Wayland. +- Bottoni che richiedono geometria/regex predefinita restano fuori scope (vedi sotto). + +### 3. `bin/shutter` (~riga 6198, dispatch cattura) +- Quando modalità = select / window / active **e** sessione = Wayland → + chiamare `Shutter::Screenshot::Wayland::xdg_portal($s, TRUE)` invece del path X11. + +## Flusso + +1. Utente clicca un bottone di selezione. +2. Shutter chiama il portal in modalità interattiva. +3. GNOME mostra il selettore nativo; l'utente sceglie area/finestra/schermo. +4. Il portal ritorna l'URI del file (immagine già ritagliata). +5. Shutter carica il pixbuf e prosegue con il **post-processing esistente** + (salvataggio / editor / upload). Identico al full-screen Wayland già funzionante. + +## Gestione errori + +- Response `num != 0` dal portal = utente ha annullato nel selettore GNOME. + → Abort silenzioso, nessun dialog d'errore (cancellazione volontaria, non fallimento). +- Eccezioni DBus → comportamento attuale (`_error_text`), invariato. + +## Fuori scope + +- Cattura finestra per nome/regex (`--window=PATTERN`): il portal non accetta target. +- Selezione con coordinate predefinite (`-s=X,Y,W,H`): il portal non accetta geometria. +- Overlay di selezione nativo di Shutter su Wayland: resta inattivo; il selettore è + quello di GNOME. + +## Test (sessione Wayland) + +- Ogni bottone (Selezione / Finestra / Attiva) apre il selettore GNOME. +- L'immagine catturata viene caricata in Shutter e segue il flusso normale. +- Annullamento nel selettore non genera dialog d'errore. +- Regressione: full-screen su Wayland e tutta la cattura su Xorg restano invariati. diff --git a/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm b/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm index e6c5e16e..89238ec8 100644 --- a/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm +++ b/share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm @@ -8,6 +8,7 @@ package Shutter::Screenshot::Wayland; sub xdg_portal { my $screenshooter = shift; + my $interactive = shift; my $reactor = Net::DBus::Reactor->main; my $bus = Net::DBus->find; my $me = $bus->get_unique_name; @@ -31,7 +32,10 @@ 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}); + # interactive=true lets the compositor show its native picker (area/window/screen) + my %options = (handle_token => $token); + $options{interactive} = Net::DBus::dbus_boolean(1) if $interactive; + 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'); @@ -40,6 +44,8 @@ sub xdg_portal { $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; }