#! perl =head1 NAME ccx-osc-52 - Implement OSC 52 ; provide menu selector for paste options =head1 SYNOPSIS urxvt -pe ccx-osc-52 =head1 DESCRIPTION This extension implements OSC 52 clipboard requests. Instead of interacting with X11 selections directly it calls out to external program to provide a list of paste options from various sources and lets user pick one. This also prevents silent snooping of clipboard by terminal applications as every request needs to be manually acknowledged. Loosely based on: https://github.com/parantapa/dotfiles/blob/100cabd431e80b3788d03c3c798e79fcbd06d6f3/urxvt-perl/52-osc =cut use MIME::Base64; use Encode; use Data::Dumper; # quote string and transform special characters to backslashed form sub qquote { my ($to_quote) = @_; return Data::Dumper::qquote($to_quote); } sub dbg { my ($s) = @_; print STDERR "\n= $s =\n"; } sub clip_overlay_fill { my ($self) = @_; my $sel = $self->{clip_selected}; my $l = $self->{clip_label}; my $c = $self->{clip_content}; my $height = scalar @{$l}; print STDERR Data::Dumper::Dumper($l); my $rend_normal = urxvt::SET_BGCOLOR urxvt::DEFAULT_RSTYLE, 2; my $rend_l_sel = urxvt::SET_FGCOLOR $rend_normal, 5; # yellow my $rend_c_sel = urxvt::SET_FGCOLOR $rend_normal, 4; # green my @style_label; my @style_content; my @style_label_sel; my @style_content_sel; foreach my $i (0 .. 30) { push @style_label, $rend_normal; push @style_label_sel, $rend_l_sel; } foreach my $i (0 .. $self->ncol - 2 - 30) { push @style_content, $rend_normal; push @style_content_sel, $rend_c_sel; } foreach my $i (0 .. $height) { if ($i == $sel) { $self->{overlay}->set ( 0, $i+1, $self->special_encode($l->[$i]), \@style_label_sel); $self->{overlay}->set (30, $i+1, $self->special_encode($c->[$i]), \@style_content_sel); } else { $self->{overlay}->set ( 0, $i+1, $self->special_encode($l->[$i]), \@style_label); $self->{overlay}->set (30, $i+1, $self->special_encode($c->[$i]), \@style_content); } } } sub clip_menu { my ($self) = @_; my $height = scalar @{$self->{clip_label}}; dbg("height: $height"); my $rend_normal = urxvt::SET_BGCOLOR urxvt::DEFAULT_RSTYLE, 2; $self->{overlay} = $self->overlay (0, ($self->nrow - 3 - $height)/2, $self->ncol-2, $height+1, $rend_normal, 2); $self->{overlay}->set ( 0, 0, "Select clipboard to paste:"); $self->{overlay}->set (30, 0, "arrows, j, k: select; Esc: cancel; Enter: confirm"); clip_overlay_fill($self); $self->enable (key_press => \&key_press); } sub leave { my ($self) = @_; delete $self->{clip_selected}; delete $self->{clip_label}; delete $self->{clip_content}; delete $self->{clip_type}; delete $self->{overlay}; $self->disable ("key_press"); } sub clip_cursor_up { my ($self) = @_; my $sel = $self->{clip_selected}; $self->{clip_selected} = $sel > 0 ? $sel - 1 : 0; dbg("sel: $sel"); clip_overlay_fill($self); } sub clip_cursor_down { my ($self) = @_; my $sel = $self->{clip_selected}; my $height = scalar @{$self->{clip_label}}; $self->{clip_selected} = $sel < ($height - 1) ? $sel + 1 : $sel; dbg("sel: $sel"); clip_overlay_fill($self); } sub clip_paste_selected { my ($self) = @_; if($self->{clip_selected} < 0) { dbg("nothing selected"); return; } my $clip_data = $self->{clip_content}[$self->{clip_selected}]; my $clip = $self->{clip_type}; dbg(sprintf('paste: %s', qquote($clip_data))); Encode::_utf8_off($clip_data); # XXX $self->tt_write("\e]52;$clip;".encode_base64($clip_data, '')."\a"); } sub key_press { my ($self, $event, $keysym, $string) = @_; if ($keysym == 0x6b or $keysym == 0xff52) { # k or Up clip_cursor_up($self); } elsif ($keysym == 0x6a or $keysym == 0xff54) { # j or Down clip_cursor_down($self); } elsif ($keysym == 0xff1b) { # Escape dbg("Escape"); $self->leave; } elsif ($keysym == 0xff0d) { # Return dbg("Return"); clip_paste_selected($self); $self->leave; } 1 } sub on_osc_seq { my ($self, $op, $args) = @_; print STDERR "\n= 1 =\n"; return () unless $op eq 52; print STDERR "\n= 2 =\n"; my ($clip, $data) = split ';', $args, 2; if ($data eq '?') { print STDERR "\n= 3 =\n"; open(my $fh, "-|", "s6-sudo /run/inbox/xpra.ccx/run/exec/exec clip -0"); read($fh, my $clip_data, 8096); close($fh); print STDERR "\n"; my @clip_arr = split(/\0/, $clip_data); print STDERR scalar @clip_arr; print STDERR "\n"; print STDERR Data::Dumper::Dumper(@clip_arr); print STDERR "\n"; my @clip_label; my @clip_content; foreach my $i (0 .. $#clip_arr) { if($i % 2) { push @clip_content, $clip_arr[$i]; } else { push @clip_label, $clip_arr[$i]; } } $self->{clip_selected} = -1; $self->{clip_type} = $clip; $self->{clip_label} = \@clip_label; $self->{clip_content} = \@clip_content; clip_menu($self); } else { print STDERR "\n= 4 =\n"; my $data_decoded = decode_base64($data); Encode::_utf8_on($data_decoded); # XXX printf(STDERR "got clipboard set request: %s into %s (ignored)\n", qquote($data_decoded), qquote($clip)); #$self->selection($data_decoded, $clip =~ /c/); #$self->selection_grab(urxvt::CurrentTime, $clip =~ /c/); } () }