#! 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/);
}
()
}