Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 29 additions & 3 deletions bin/shutter
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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(
Expand All @@ -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
Expand Down Expand Up @@ -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;
}

Expand Down
100 changes: 96 additions & 4 deletions share/shutter/resources/modules/Shutter/Screenshot/Wayland.pm
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resources/modules/Shutter/App/Common.pm uses a different (more modern) way to create a class and properties; shouldn't this do the same?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and perhaps this should inherit from Shutter/Screenshot/Main.pm like other screenshotters do? The previous code here is mostly a hack

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree this should likley go somewhere else, I needed to get my evidence collected and after failing to get x11 working on g50 and giving up on kde as well as alternatives I decided to add the functions I needed to Shutter.

this came about as I updated my desktop and was against a wall to get the evidence collected, did not take the time to review the whole project just added what I needed.

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;
Expand All @@ -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');
Expand All @@ -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";
Expand All @@ -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;
Loading