summaryrefslogtreecommitdiffstats
path: root/urxvt
diff options
context:
space:
mode:
authorMatthew Lemon <lemon@x220.lan>2020-09-06 16:48:05 +0100
committerMatthew Lemon <lemon@x220.lan>2020-09-06 16:48:05 +0100
commite4640e28c8449eea66392052bfab57ce0cdd27f9 (patch)
tree2be81b55bc37da60599f4e6d434e74a57694962e /urxvt
first commit
Diffstat (limited to 'urxvt')
-rw-r--r--urxvt/ext/clipboard117
-rw-r--r--urxvt/ext/font-size471
-rw-r--r--urxvt/ext/fullscreen33
-rw-r--r--urxvt/ext/keyboard-select598
-rw-r--r--urxvt/ext/url-select410
-rw-r--r--urxvt/ext/vtwheel49
6 files changed, 1678 insertions, 0 deletions
diff --git a/urxvt/ext/clipboard b/urxvt/ext/clipboard
new file mode 100644
index 0000000..e5d2322
--- /dev/null
+++ b/urxvt/ext/clipboard
@@ -0,0 +1,117 @@
+#!/usr/bin/perl
+# Author: Bert Muennich
+# Website: http://www.github.com/muennich/urxvt-perls
+# License: GPLv2
+
+# Use keyboard shortcuts to copy the selection to the clipboard and to paste
+# the clipboard contents (optionally escaping all special characters).
+# Requires xsel to be installed!
+
+# Usage: put the following lines in your .Xdefaults/.Xresources:
+# URxvt.perl-ext-common: ...,clipboard
+# URxvt.keysym.M-c: perl:clipboard:copy
+# URxvt.keysym.M-v: perl:clipboard:paste
+# URxvt.keysym.M-C-v: perl:clipboard:paste_escaped
+
+# Options:
+# URxvt.clipboard.autocopy: If true, PRIMARY overwrites clipboard
+
+# You can also overwrite the system commands to use for copying/pasting.
+# The default ones are:
+# URxvt.clipboard.copycmd: xsel -ib
+# URxvt.clipboard.pastecmd: xsel -ob
+# If you prefer xclip, then put these lines in your .Xdefaults/.Xresources:
+# URxvt.clipboard.copycmd: xclip -i -selection clipboard
+# URxvt.clipboard.pastecmd: xclip -o -selection clipboard
+# On Mac OS X, put these lines in your .Xdefaults/.Xresources:
+# URxvt.clipboard.copycmd: pbcopy
+# URxvt.clipboard.pastecmd: pbpaste
+
+# The use of the functions should be self-explanatory!
+
+
+use strict;
+use warnings;
+
+sub on_start {
+ my ($self) = @_;
+
+ $self->{copy_cmd} = $self->x_resource('clipboard.copycmd') || 'xsel -ib';
+ $self->{paste_cmd} = $self->x_resource('clipboard.pastecmd') || 'xsel -ob';
+
+ if ($self->x_resource('clipboard.autocopy') eq 'true') {
+ $self->enable(sel_grab => \&sel_grab);
+ }
+
+ ()
+}
+
+sub copy {
+ my ($self) = @_;
+
+ if (open(CLIPBOARD, "| $self->{copy_cmd}")) {
+ my $sel = $self->selection();
+ utf8::encode($sel);
+ print CLIPBOARD $sel;
+ close(CLIPBOARD);
+ } else {
+ print STDERR "error running '$self->{copy_cmd}': $!\n";
+ }
+
+ ()
+}
+
+sub paste {
+ my ($self) = @_;
+
+ my $str = `$self->{paste_cmd}`;
+ if ($? == 0) {
+ $self->tt_paste($str);
+ } else {
+ print STDERR "error running '$self->{paste_cmd}': $!\n";
+ }
+
+ ()
+}
+
+sub paste_escaped {
+ my ($self) = @_;
+
+ my $str = `$self->{paste_cmd}`;
+ if ($? == 0) {
+ $str =~ s/([!#\$%&\*\(\) ='"\\\|\[\]`~,<>\?])/\\$1/g;
+ $self->tt_paste($str);
+ } else {
+ print STDERR "error running '$self->{paste_cmd}': $!\n";
+ }
+
+ ()
+}
+
+sub on_action {
+ my ($self, $action) = @_;
+
+ on_user_command($self, "clipboard:" . $action);
+}
+
+sub on_user_command {
+ my ($self, $cmd) = @_;
+
+ if ($cmd eq "clipboard:copy") {
+ $self->copy;
+ } elsif ($cmd eq "clipboard:paste") {
+ $self->paste;
+ } elsif ($cmd eq "clipboard:paste_escaped") {
+ $self->paste_escaped;
+ }
+
+ ()
+}
+
+sub sel_grab {
+ my ($self) = @_;
+
+ $self->copy;
+
+ ()
+}
diff --git a/urxvt/ext/font-size b/urxvt/ext/font-size
new file mode 100644
index 0000000..f6a33bb
--- /dev/null
+++ b/urxvt/ext/font-size
@@ -0,0 +1,471 @@
+#!/usr/bin/perl
+#
+# On-the-fly adjusting of the font size in urxvt
+#
+# Copyright (c) 2008 David O'Neill
+# 2012 Noah K. Tilton <noahktilton@gmail.com>
+# 2009-2012 Simon Lundström <simmel@soy.se>
+# 2012-2016 Jan Larres <jan@majutsushi.net>
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+# URL: https://github.com/majutsushi/urxvt-font-size
+#
+# Based on:
+# https://github.com/dave0/urxvt-font-size
+# https://github.com/noah/urxvt-font
+# https://github.com/simmel/urxvt-resize-font
+#
+
+#:META:X_RESOURCE:%.step:interger:font size increase/decrease step
+
+=head1 NAME
+
+font-size - interactive font size setter
+
+=head1 USAGE
+
+Put the font-size script into $HOME/.urxvt/ext/ and add it to the list
+of enabled perl-extensions in ~/.Xresources:
+
+ URxvt.perl-ext-common: ...,font-size
+
+Add some keybindings:
+
+ URxvt.keysym.C-Up: font-size:increase
+ URxvt.keysym.C-Down: font-size:decrease
+ URxvt.keysym.C-S-Up: font-size:incglobal
+ URxvt.keysym.C-S-Down: font-size:decglobal
+ URxvt.keysym.C-equal: font-size:reset
+ URxvt.keysym.C-slash: font-size:show
+
+Note that for urxvt versions older than 9.21 the resources have to look like this:
+
+ URxvt.keysym.C-Up: perl:font-size:increase
+ URxvt.keysym.C-Down: perl:font-size:decrease
+ URxvt.keysym.C-S-Up: perl:font-size:incglobal
+ URxvt.keysym.C-S-Down: perl:font-size:decglobal
+ URxvt.keysym.C-equal: perl:font-size:reset
+ URxvt.keysym.C-slash: perl:font-size:show
+
+Supported functions:
+
+=over 2
+
+=item * increase/decrease:
+
+ increase or decrease the font size of the current terminal.
+
+=item * incglobal/decglobal:
+
+ same as above and also adjust the X server values so all newly
+ started terminals will use the same fontsize.
+
+=item * incsave/decsave:
+
+ same as incglobal/decglobal and also modify the ~/.Xresources
+ file so the changed font sizes will persist over a restart of
+ the X server or a reboot.
+
+=item * reset:
+
+ reset the font size to the value of the resource when starting
+ the terminal.
+
+=item * show
+
+ show the current value of the 'font' resource in a popup.
+
+=back
+
+You can also change the step size that the script will use to increase
+the font size:
+
+ URxvt.font-size.step: 4
+
+The default step size is 1. This means that with this setting a
+size change sequence would be for example 8->12->16->20 instead of
+8->9->10->11->12 etc. Please note that many X11 fonts are only
+available in specific sizes, though, and odd sizes are often not
+available, resulting in an effective step size of 2 instead of 1
+in that case.
+=cut
+
+use strict;
+use warnings;
+
+my %escapecodes = (
+ "font" => 710,
+ "boldFont" => 711,
+ "italicFont" => 712,
+ "boldItalicFont" => 713
+);
+
+sub on_start
+{
+ my ($self) = @_;
+
+ $self->{step} = $self->x_resource("%.step") || 1;
+
+ foreach my $type (qw(font boldFont italicFont boldItalicFont)) {
+ $self->{$type} = $self->x_resource($type) || "undef";
+ }
+}
+
+# Needed for backwards compatibility with < 9.21
+sub on_user_command
+{
+ my ($self, $cmd) = @_;
+
+ my $step = $self->{step};
+
+ if ($cmd eq "font-size:increase") {
+ fonts_change_size($self, $step, 0);
+ } elsif ($cmd eq "font-size:decrease") {
+ fonts_change_size($self, -$step, 0);
+ } elsif ($cmd eq "font-size:incglobal") {
+ fonts_change_size($self, $step, 1);
+ } elsif ($cmd eq "font-size:decglobal") {
+ fonts_change_size($self, -$step, 1);
+ } elsif ($cmd eq "font-size:incsave") {
+ fonts_change_size($self, $step, 2);
+ } elsif ($cmd eq "font-size:decsave") {
+ fonts_change_size($self, -$step, 2);
+ } elsif ($cmd eq "font-size:reset") {
+ fonts_reset($self);
+ } elsif ($cmd eq "font-size:show") {
+ fonts_show($self);
+ }
+}
+
+sub on_action
+{
+ my ($self, $action) = @_;
+
+ my $step = $self->{step};
+
+ if ($action eq "increase") {
+ fonts_change_size($self, $step, 0);
+ } elsif ($action eq "decrease") {
+ fonts_change_size($self, -$step, 0);
+ } elsif ($action eq "incglobal") {
+ fonts_change_size($self, $step, 1);
+ } elsif ($action eq "decglobal") {
+ fonts_change_size($self, -$step, 1);
+ } elsif ($action eq "incsave") {
+ fonts_change_size($self, $step, 2);
+ } elsif ($action eq "decsave") {
+ fonts_change_size($self, -$step, 2);
+ } elsif ($action eq "reset") {
+ fonts_reset($self);
+ } elsif ($action eq "show") {
+ fonts_show($self);
+ }
+}
+
+sub fonts_change_size
+{
+ my ($term, $delta, $save) = @_;
+
+ my @newfonts = ();
+
+ my $curres = $term->resource('font');
+ if (!$curres) {
+ $term->scr_add_lines("\r\nWarning: No font configured, trying a default.\r\nPlease set a font with the 'URxvt.font' resource.");
+ $curres = "fixed";
+ }
+ my @curfonts = split(/\s*,\s*/, $curres);
+
+ my $basefont = shift(@curfonts);
+ my ($newbasefont, $newbasedelta, $newbasesize) = handle_font($term, $basefont, $delta, 0, 0);
+ push @newfonts, $newbasefont;
+
+ # Only adjust other fonts if base font changed
+ if ($newbasefont ne $basefont) {
+ foreach my $font (@curfonts) {
+ my ($newfont, $newdelta, $newsize) = handle_font($term, $font, $delta, $newbasedelta, $newbasesize);
+ push @newfonts, $newfont;
+ }
+ my $newres = join(",", @newfonts);
+ font_apply_new($term, $newres, "font", $save);
+
+ handle_type($term, "boldFont", $delta, $newbasedelta, $newbasesize, $save);
+ handle_type($term, "italicFont", $delta, $newbasedelta, $newbasesize, $save);
+ handle_type($term, "boldItalicFont", $delta, $newbasedelta, $newbasesize, $save);
+ }
+
+ if ($save > 1) {
+ # write the new values back to the file
+ my $xresources = readlink $ENV{"HOME"} . "/.Xresources";
+ system("xrdb -edit " . $xresources);
+ }
+}
+
+sub fonts_reset
+{
+ my ($term) = @_;
+
+ foreach my $type (qw(font boldFont italicFont boldItalicFont)) {
+ my $initial = $term->{$type};
+ if ($initial ne "undef") {
+ font_apply_new($term, $initial, $type, 0);
+ }
+ }
+}
+
+sub fonts_show
+{
+ my ($term) = @_;
+
+ my $out = $term->resource('font');
+ $out =~ s/\s*,\s*/\n/g;
+
+ $term->{'font-size'}{'overlay'} = {
+ overlay => $term->overlay_simple(0, -1, $out),
+ timer => urxvt::timer->new->start(urxvt::NOW + 5)->cb(
+ sub {
+ delete $term->{'font-size'}{'overlay'};
+ }
+ ),
+ };
+}
+
+sub handle_type
+{
+ my ($term, $type, $delta, $basedelta, $basesize, $save) = @_;
+
+ my $curres = $term->resource($type);
+ if (!$curres) {
+ return;
+ }
+ my @curfonts = split(/\s*,\s*/, $curres);
+ my @newfonts = ();
+
+ foreach my $font (@curfonts) {
+ my ($newfont, $newdelta, $newsize) = handle_font($term, $font, $delta, $basedelta, $basesize);
+ push @newfonts, $newfont;
+ }
+
+ my $newres = join(",", @newfonts);
+ font_apply_new($term, $newres, $type, $save);
+}
+
+sub handle_font
+{
+ my ($term, $font, $delta, $basedelta, $basesize) = @_;
+
+ my $newfont;
+ my $newdelta;
+ my $newsize;
+ my $prefix = 0;
+
+ if ($font =~ /^\s*x:/) {
+ $font =~ s/^\s*x://;
+ $prefix = 1;
+ }
+ if ($font =~ /^\s*(\[.*\])?xft:/) {
+ ($newfont, $newdelta, $newsize) = font_change_size_xft($term, $font, $delta, $basedelta, $basesize);
+ } elsif ($font =~ /^\s*-/) {
+ ($newfont, $newdelta, $newsize) = font_change_size_xlfd($term, $font, $delta, $basedelta, $basesize);
+ } else {
+ # check whether the font is a valid alias and if yes resolve it to the
+ # actual font
+ my $lsfinfo = `xlsfonts -l $font 2>/dev/null`;
+
+ if ($lsfinfo eq "") {
+ # not a valid alias, ring the bell if it is the base font and just
+ # return the current font
+ if ($basesize == 0) {
+ $term->scr_bell;
+ }
+ return ($font, $basedelta, $basesize);
+ }
+
+ my $fontinfo = (split(/\n/, $lsfinfo))[-1];
+ my ($fontfull) = ($fontinfo =~ /\s+([-a-z0-9]+$)/);
+ ($newfont, $newdelta, $newsize) = font_change_size_xlfd($term, $fontfull, $delta, $basedelta, $basesize);
+ }
+
+ # $term->scr_add_lines("\r\nNew font is $newfont\n");
+ if ($prefix) {
+ $newfont = "x:$newfont";
+ }
+ return ($newfont, $newdelta, $newsize);
+}
+
+sub font_change_size_xft
+{
+ my ($term, $fontstring, $delta, $basedelta, $basesize) = @_;
+
+ my @pieces = split(/:/, $fontstring);
+ my @resized = ();
+ my $size = 0;
+ my $new_size = 0;
+
+ foreach my $piece (@pieces) {
+ if ($piece =~ /^(?:(?:pixel)?size=|[^=-]+-)(\d+(\.\d*)?)$/) {
+ $size = $1;
+
+ if ($basedelta != 0) {
+ $new_size = $size + $basedelta;
+ } else {
+ $new_size = $size + $delta;
+ }
+
+ $piece =~ s/(=|-)$size/$1$new_size/;
+ }
+ push @resized, $piece;
+ }
+
+ my $resized_str = join(":", @resized);
+
+ # don't make fonts too small
+ if ($new_size >= 6) {
+ return ($resized_str, $new_size - $size, $new_size);
+ } else {
+ if ($basesize == 0) {
+ $term->scr_bell;
+ }
+ return ($fontstring, 0, $size);
+ }
+}
+
+sub font_change_size_xlfd
+{
+ my ($term, $fontstring, $delta, $basedelta, $basesize) = @_;
+
+ #-xos4-terminus-medium-r-normal-*-12-*-*-*-*-*-*-1
+
+ my @fields = qw(foundry family weight slant setwidth style pixelSize pointSize Xresolution Yresolution spacing averageWidth registry encoding);
+
+ my %font;
+ $fontstring =~ s/^-//; # Strip leading - before split
+ @font{@fields} = split(/-/, $fontstring);
+
+ if ($font{pixelSize} eq '*') {
+ $term->scr_add_lines("\r\nWarning: Font size undefined, assuming 12.\r\nPlease set the 'URxvt.font' resource to a font with a concrete size.");
+ $font{pixelSize} = '12'
+ }
+ if ($font{registry} eq '*') {
+ $font{registry} ='iso8859';
+ }
+
+ # Blank out the size for the pattern
+ my %pattern = %font;
+ $pattern{foundry} = '*';
+ $pattern{setwidth} = '*';
+ $pattern{pixelSize} = '*';
+ $pattern{pointSize} = '*';
+ # if ($basesize != 0) {
+ # $pattern{Xresolution} = '*';
+ # $pattern{Yresolution} = '*';
+ # }
+ $pattern{averageWidth} = '*';
+ # make sure there are no empty fields
+ foreach my $field (@fields) {
+ $pattern{$field} = '*' unless defined($pattern{$field});
+ }
+ my $new_fontstring = '-' . join('-', @pattern{@fields});
+
+ my @candidates;
+ # $term->scr_add_lines("\r\nPattern is $new_fontstring\n");
+ open(FOO, "xlsfonts -fn '$new_fontstring' | sort -u |") or die $!;
+ while (<FOO>) {
+ chomp;
+ s/^-//; # Strip leading '-' before split
+ my @fontdata = split(/-/, $_);
+
+ push @candidates, [$fontdata[6], "-$_"];
+ # $term->scr_add_lines("\r\npossibly $fontdata[6] $_\n");
+ }
+ close(FOO);
+
+ if (!@candidates) {
+ die "No possible fonts!";
+ }
+
+ if ($basesize != 0) {
+ # sort by font size, descending
+ @candidates = sort {$b->[0] <=> $a->[0]} @candidates;
+
+ # font is not the base font, so find the largest font that is at most
+ # as large as the base font. If the largest possible font is smaller
+ # than the base font bail and hope that a 0-size font can be found at
+ # the end of the function
+ if ($candidates[0]->[0] > $basesize) {
+ foreach my $candidate (@candidates) {
+ if ($candidate->[0] <= $basesize) {
+ return ($candidate->[1], $candidate->[0] - $font{pixelSize}, $candidate->[0]);
+ }
+ }
+ }
+ } elsif ($delta > 0) {
+ # sort by font size, ascending
+ @candidates = sort {$a->[0] <=> $b->[0]} @candidates;
+
+ foreach my $candidate (@candidates) {
+ if ($candidate->[0] >= $font{pixelSize} + $delta) {
+ return ($candidate->[1], $candidate->[0] - $font{pixelSize}, $candidate->[0]);
+ }
+ }
+ } elsif ($delta < 0) {
+ # sort by font size, descending
+ @candidates = sort {$b->[0] <=> $a->[0]} @candidates;
+
+ foreach my $candidate (@candidates) {
+ if ($candidate->[0] <= $font{pixelSize} + $delta && $candidate->[0] != 0) {
+ return ($candidate->[1], $candidate->[0] - $font{pixelSize}, $candidate->[0]);
+ }
+ }
+ }
+
+ # no fitting font available, check whether a 0-size font can be used to
+ # fit the size of the base font
+ @candidates = sort {$a->[0] <=> $b->[0]} @candidates;
+ if ($basesize != 0 && $candidates[0]->[0] == 0) {
+ return ($candidates[0]->[1], $basedelta, $basesize);
+ } else {
+ # if there is absolutely no smaller/larger font that can be used
+ # return the current one, and beep if this is the base font
+ if ($basesize == 0) {
+ $term->scr_bell;
+ }
+ return ("-$fontstring", 0, $font{pixelSize});
+ }
+}
+
+sub font_apply_new
+{
+ my ($term, $newfont, $type, $save) = @_;
+
+ # $term->scr_add_lines("\r\nnew font is $newfont\n");
+
+ $term->cmd_parse("\033]" . $escapecodes{$type} . ";" . $newfont . "\033\\");
+
+ # load the xrdb db
+ # system("xrdb -load " . X_RESOURCES);
+
+ if ($save > 0) {
+ # merge the new values
+ open(XRDB_MERGE, "| xrdb -merge") || die "can't fork: $!";
+ local $SIG{PIPE} = sub { die "xrdb pipe broken" };
+ print XRDB_MERGE "URxvt." . $type . ": " . $newfont;
+ close(XRDB_MERGE) || die "bad xrdb: $! $?";
+ }
+}
diff --git a/urxvt/ext/fullscreen b/urxvt/ext/fullscreen
new file mode 100644
index 0000000..086c2ea
--- /dev/null
+++ b/urxvt/ext/fullscreen
@@ -0,0 +1,33 @@
+#!/usr/bin/perl
+#
+# Toggle fullscreen
+#
+# URL: https://aur.archlinux.org/packages/urxvt-fullscreen
+#
+# Copyright (C) 2010-2011 Christopher Luna
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+use strict;
+use warnings;
+
+sub on_user_command
+{
+ my ($self, $cmd) = @_;
+
+ if ($cmd eq "fullscreen:switch") {
+ my $dummy = `wmctrl -r :ACTIVE: -b toggle,fullscreen,above`;
+ }
+}
diff --git a/urxvt/ext/keyboard-select b/urxvt/ext/keyboard-select
new file mode 100644
index 0000000..a6f89af
--- /dev/null
+++ b/urxvt/ext/keyboard-select
@@ -0,0 +1,598 @@
+#!/usr/bin/perl
+# Author: Bert Muennich
+# Website: http://www.github.com/muennich/urxvt-perls
+# License: GPLv2
+
+# Use keyboard shortcuts to select and copy text.
+
+# Usage: put the following lines in your .Xdefaults/.Xresources:
+# URxvt.perl-ext-common: ...,keyboard-select
+# URxvt.keysym.M-Escape: perl:keyboard-select:activate
+# The following line overwrites the default Meta-s binding and allows to
+# activate keyboard-select directly in backward search mode:
+# URxvt.keysym.M-s: perl:keyboard-select:search
+
+# Use Meta-Escape to activate selection mode, then use the following keys:
+# h/j/k/l: Move cursor left/down/up/right (also with arrow keys)
+# g/G/0/^/$/H/M/L/f/F/;/,/w/W/b/B/e/E: More vi-like cursor movement keys
+# '/'/?: Start forward/backward search
+# n/N: Repeat last search, N: in reverse direction
+# Ctrl-f/b: Scroll down/up one screen
+# Ctrl-d/u: Scroll down/up half a screen
+# v/V/Ctrl-v: Toggle normal/linewise/blockwise selection
+# y/Return: Copy selection to primary buffer, Return: quit afterwards
+# Y: Copy selected lines to primary buffer or cursor line and quit
+# q/Escape: Quit keyboard selection mode
+
+
+use strict;
+use warnings;
+
+sub on_start{
+ my ($self) = @_;
+
+ $self->{patterns}{'w'} = qr/\w[^\w\s]|\W\w|\s\S/;
+ $self->{patterns}{'W'} = qr/\s\S/;
+ $self->{patterns}{'b'} = qr/.*(?:\w[^\w\s]|\W\w|\s\S)/;
+ $self->{patterns}{'B'} = qr/.*\s\S/;
+ $self->{patterns}{'e'} = qr/[^\w\s](?=\w)|\w(?=\W)|\S(?=\s|$)/;
+ $self->{patterns}{'E'} = qr/\S(?=\s|$)/;
+
+ ()
+}
+
+
+sub on_action {
+ my ($self, $action) = @_;
+
+ on_user_command($self, "keyboard-select:" . $action);
+}
+
+
+sub on_user_command {
+ my ($self, $cmd) = @_;
+
+ if (not $self->{active}) {
+ if ($cmd eq 'keyboard-select:activate') {
+ activate($self);
+ } elsif ($cmd eq 'keyboard-select:search') {
+ activate($self, 1);
+ }
+ }
+
+ ()
+}
+
+
+sub key_press {
+ my ($self, $event, $keysym, $char) = @_;
+ my $key = chr($keysym);
+
+ if (lc($key) eq 'c' && $event->{state} & urxvt::ControlMask) {
+ deactivate($self);
+ } elsif ($self->{search}) {
+ if ($keysym == 0xff1b) {
+ if ($self->{search_mode}) {
+ deactivate($self);
+ } else {
+ $self->{search} = '';
+ status_area($self);
+ }
+ } elsif ($keysym == 0xff08) {
+ $self->{search} = substr($self->{search}, 0, -1);
+ if (not $self->{search} and $self->{search_mode}) {
+ deactivate($self);
+ } else {
+ status_area($self);
+ }
+ } elsif ($keysym == 0xff0d ||
+ (lc($key) eq 'm' && $event->{state} & urxvt::ControlMask)) {
+ my $txt = substr($self->{search}, 1);
+ if ($txt) {
+ $self->{pattern} = ($txt =~ m/[[:upper:]]/) ? qr/\Q$txt\E/ :
+ qr/\Q$txt\E/i;
+ } elsif ($self->{pattern}) {
+ delete $self->{pattern};
+ }
+ $self->{search} = '';
+ $self->screen_cur($self->{srhcr}, $self->{srhcc});
+ if (not find_next($self)) {
+ if ($self->{search_mode}) {
+ deactivate($self);
+ } else {
+ status_area($self);
+ }
+ }
+ } elsif (length($char) > 0) {
+ $self->{search} .= $self->locale_decode($char);
+ my $txt = substr($self->{search}, 1);
+ if ($txt) {
+ $self->{pattern} = ($txt =~ m/[[:upper:]]/) ? qr/\Q$txt\E/ :
+ qr/\Q$txt\E/i;
+ } elsif ($self->{pattern}) {
+ delete $self->{pattern};
+ }
+ $self->screen_cur($self->{srhcr}, $self->{srhcc});
+ find_next($self);
+ status_area($self);
+ }
+ } elsif ($self->{move_to}) {
+ if ($keysym == 0xff1b) {
+ $self->{move_to} = 0;
+ status_area($self);
+ } elsif (length($char) > 0) {
+ $self->{move_to} = 0;
+ $self->{patterns}{'f-1'} = qr/^.*\Q$key\E/;
+ $self->{patterns}{'f+1'} = qr/^.+?\Q$key\E/;
+ move_to($self, ';');
+ status_area($self);
+ }
+ } elsif ($keysym == 0xff1b || lc($key) eq 'q') {
+ deactivate($self);
+ } elsif (lc($key) eq 'y' || $keysym == 0xff0d ||
+ (lc($key) eq 'm' && $event->{state} & urxvt::ControlMask)) {
+ my $quit = 0;
+ if ($key eq 'Y' && $self->{select} ne 'l') {
+ $quit = !$self->{select};
+ toggle_select($self, 'l');
+ }
+ if ($self->{select}) {
+ my ($br, $bc, $er, $ec) = calc_span($self);
+ $ec = $self->line($er)->l if $self->{select} eq 'l';
+ $self->selection_beg($br, $bc);
+ $self->selection_end($er, $ec);
+ $self->selection_make($event->{time}, $self->{select} eq 'b');
+ if (lc($key) eq 'y') {
+ $self->selection_beg(1, 0);
+ $self->selection_end(1, 0);
+ $self->{select} = '';
+ status_area($self);
+ $self->want_refresh();
+ } else {
+ $quit = 1;
+ }
+ }
+ if ($quit) {
+ deactivate($self);
+ }
+ } elsif ($key eq 'V') {
+ toggle_select($self, 'l');
+ } elsif ($key eq 'v') {
+ if ($event->{state} & urxvt::ControlMask) {
+ toggle_select($self, 'b');
+ } else {
+ toggle_select($self, 'n');
+ }
+ } elsif ($key eq 'k' || $keysym == 0xff52) {
+ move_cursor($self, 'k');
+ } elsif ($key eq 'j' || $keysym == 0xff54) {
+ move_cursor($self, 'j');
+ } elsif ($key eq 'h' || $keysym == 0xff51) {
+ move_cursor($self, 'h');
+ } elsif ($key eq 'l' || $keysym == 0xff53) {
+ move_cursor($self, 'l');
+ } elsif ($keysym == 0xff57) {
+ move_cursor($self, '$');
+ } elsif ($keysym == 0xff50) {
+ move_cursor($self, '^');
+ } elsif ('gG0^$HML' =~ m/\Q$key\E/ ||
+ ('fbdu' =~ m/\Q$key\E/ && $event->{state} & urxvt::ControlMask)) {
+ move_cursor($self, $key);
+ } elsif (lc($key) eq 'f') {
+ $self->{move_to} = 1;
+ $self->{move_dir} = $key eq 'F' ? -1 : 1;
+ status_area($self, $key);
+ } elsif (';,wWbBeE' =~ m/\Q$key\E/) {
+ move_to($self, $key);
+ } elsif ($key eq '/' || $key eq '?') {
+ $self->{search} = $key;
+ $self->{search_dir} = $key eq '?' ? -1 : 1;
+ ($self->{srhcr}, $self->{srhcc}) = $self->screen_cur();
+ status_area($self);
+ } elsif (lc($key) eq 'n') {
+ find_next($self, $self->{search_dir} * ($key eq 'N' ? -1 : 1));
+ }
+
+ return 1;
+}
+
+
+sub move_cursor {
+ my ($self, $key) = @_;
+ my ($cr, $cc) = $self->screen_cur();
+ my $line = $self->line($cr);
+
+ if ($key eq 'k' && $line->beg > $self->top_row) {
+ $cr = $line->beg - 1;
+ } elsif ($key eq 'j' && $line->end < $self->nrow - 1) {
+ $cr = $line->end + 1;
+ } elsif ($key eq 'h' && $self->{offset} > 0) {
+ $self->{offset} = $line->offset_of($cr, $cc) - 1;
+ $self->{dollar} = 0;
+ } elsif ($key eq 'l' && $self->{offset} < $line->l - 1) {
+ ++$self->{offset};
+ } elsif ($key eq 'f' || $key eq 'd') {
+ my $vs = $self->view_start() +
+ ($key eq 'd' ? $self->nrow / 2 : $self->nrow - 1);
+ $vs = 0 if $vs > 0;
+ $cr += $vs - $self->view_start($vs);
+ } elsif ($key eq 'b' || $key eq 'u') {
+ my $vs = $self->view_start() -
+ ($key eq 'u' ? $self->nrow / 2 : $self->nrow - 1);
+ $vs = $self->top_row if $vs < $self->top_row;
+ $cr += $vs - $self->view_start($vs);
+ } elsif ($key eq 'g') {
+ ($cr, $self->{offset}) = ($self->top_row, 0);
+ $self->{dollar} = 0;
+ } elsif ($key eq 'G') {
+ ($cr, $self->{offset}) = ($self->nrow - 1, 0);
+ $self->{dollar} = 0;
+ } elsif ($key eq '0') {
+ $self->{offset} = 0;
+ $self->{dollar} = 0;
+ } elsif ($key eq '^') {
+ my $ltxt = $self->special_decode($line->t);
+ while ($ltxt =~ s/^( *)\t/$1 . " " x (8 - length($1) % 8)/e) {}
+ $self->{offset} = $ltxt =~ m/^ +/ ? $+[0] : 0;
+ $self->{dollar} = 0;
+ } elsif ($key eq '$') {
+ my $co = $line->offset_of($cr, $cc);
+ $self->{dollar} = $co + 1;
+ $self->{offset} = $line->l - 1;
+ } elsif ($key eq 'H') {
+ $cr = $self->view_start();
+ } elsif ($key eq 'M') {
+ $cr = $self->view_start() + $self->nrow / 2;
+ } elsif ($key eq 'L') {
+ $cr = $self->view_start() + $self->nrow - 1;
+ }
+
+ $line = $self->line($cr);
+ $cc = $self->{dollar} || $self->{offset} >= $line->l ? $line->l - 1 :
+ $self->{offset};
+ $self->screen_cur($line->coord_of($cc));
+
+ status_area($self);
+ $self->want_refresh();
+
+ ()
+}
+
+
+sub move_to {
+ my ($self, $key) = @_;
+ my ($cr, $cc) = $self->screen_cur();
+ my $line = $self->line($cr);
+ my $offset = $self->{offset};
+ my ($dir, $pattern);
+ my ($wrap, $found) = (0, 0);
+
+ if ($key eq ';' || $key eq ',') {
+ $dir = $self->{move_dir} * ($key eq ',' ? -1 : 1);
+ $pattern = $self->{patterns}{sprintf('f%+d', $dir)};
+ return if not $pattern;
+ } else {
+ if (lc($key) eq 'b') {
+ $dir = -1;
+ } else {
+ $dir = 1;
+ ++$offset if lc($key) eq 'e';
+ }
+ $pattern = $self->{patterns}{$key};
+ $wrap = 1;
+ }
+
+ if ($dir > 0) {
+ NEXTDOWN: my $text = substr($line->t, $offset);
+ if ($text =~ m/$pattern/) {
+ $offset += $+[0] - 1;
+ $found = 1;
+ } elsif ($wrap && $line->end + 1 < $self->nrow) {
+ $cr = $line->end + 1;
+ $line = $self->line($cr);
+ $offset = 0;
+ if (lc($key) eq 'e') {
+ goto NEXTDOWN;
+ } else {
+ $found = 1;
+ }
+ }
+ } elsif ($dir < 0) {
+ NEXTUP: my $text = substr($line->t, 0, $offset);
+ if ($text =~ m/$pattern/) {
+ $offset += $+[0] - length($text) - 1;
+ $found = 1;
+ } elsif ($wrap) {
+ if ($offset > 0) {
+ $offset = 0;
+ $found = 1;
+ } elsif ($line->beg > $self->top_row) {
+ $cr = $line->beg - 1;
+ $line = $self->line($cr);
+ $offset = $line->l;
+ goto NEXTUP;
+ }
+ }
+ }
+
+ if ($found) {
+ $self->{dollar} = 0;
+ $self->{offset} = $offset;
+ $self->screen_cur($line->coord_of($offset));
+ $self->want_refresh();
+ }
+
+ ()
+}
+
+
+sub find_next {
+ my ($self, $dir) = @_;
+
+ return if not $self->{pattern};
+ $dir = $self->{search_dir} if not $dir;
+
+ my ($cr, $cc) = $self->screen_cur();
+ my $line = $self->line($cr);
+ my $offset = $line->offset_of($cr, $cc);
+ my $text;
+ my $found = 0;
+
+ ++$offset if $dir > 0;
+
+ while (not $found) {
+ if ($dir > 0) {
+ $text = substr($line->t, $offset);
+ if ($text =~ m/$self->{pattern}/) {
+ $found = 1;
+ $offset += $-[0];
+ } else {
+ last if $line->end >= $self->nrow;
+ $line = $self->line($line->end + 1);
+ $offset = 0;
+ }
+ } else {
+ $text = substr($line->t, 0, $offset);
+ if ($text =~ m/$self->{pattern}/) {
+ $found = 1;
+ $offset = $-[0] while $text =~ m/$self->{pattern}/g;
+ } else {
+ last if $line->beg <= $self->top_row;
+ $line = $self->line($line->beg - 1);
+ $offset = $line->l;
+ }
+ }
+ }
+
+ if ($found) {
+ $self->{dollar} = 0;
+ $self->{offset} = $offset;
+ $self->screen_cur($line->coord_of($offset));
+ status_area($self);
+ $self->want_refresh();
+ }
+
+ return $found;
+}
+
+
+sub tt_write {
+ return 1;
+}
+
+
+sub refresh {
+ my ($self) = @_;
+ my ($cr, $cc) = $self->screen_cur();
+
+ # scroll the current cursor position into visible area
+ if ($cr < $self->view_start()) {
+ $self->view_start($cr);
+ } elsif ($cr >= $self->view_start() + $self->nrow) {
+ $self->view_start($cr - $self->nrow + 1);
+ }
+
+ if ($self->{select}) {
+ my ($hl, $reverse_cursor);
+ my ($br, $bc, $er, $ec) = calc_span($self);
+
+ if ($self->x_resource('highlightColor')) {
+ $hl = urxvt::RS_Sel;
+ $reverse_cursor = 0;
+ } else {
+ $hl = urxvt::RS_RVid;
+ $reverse_cursor = $self->{select} ne 'l';
+ }
+ if ($self->{select} eq 'b') {
+ my $co = $self->line($cr)->offset_of($cr, $cc);
+ my $dollar = $self->{dollar} && $co >= $self->{dollar} - 1;
+
+ my $r = $br;
+ while ($r <= $er) {
+ my $line = $self->line($r);
+ if ($bc < $line->l) {
+ $ec = $line->l if $dollar;
+ my ($br, $bc) = $line->coord_of($bc);
+ my ($er, $ec) = $line->coord_of($ec <= $line->l ? $ec : $line->l);
+ $self->scr_xor_span($br, $bc, $er, $ec, $hl);
+ } elsif ($r == $cr) {
+ $reverse_cursor = 0;
+ }
+ $r = $line->end + 1;
+ }
+ } else {
+ $self->scr_xor_span($br, $bc, $er, $ec, $hl);
+ }
+
+ if ($reverse_cursor) {
+ # make the cursor visible again
+ $self->scr_xor_span($cr, $cc, $cr, $cc + 1, $hl);
+ }
+ }
+
+ ()
+}
+
+
+sub activate {
+ my ($self, $search) = @_;
+
+ $self->{active} = 1;
+
+ $self->{select} = '';
+ $self->{dollar} = 0;
+ $self->{move_to} = 0;
+
+ if ($search) {
+ $self->{search} = '?';
+ $self->{search_dir} = -1;
+ $self->{search_mode} = 1;
+ } else {
+ $self->{search} = '';
+ $self->{search_mode} = 0;
+ }
+
+ ($self->{oldcr}, $self->{oldcc}) = $self->screen_cur();
+ ($self->{srhcr}, $self->{srhcc}) = $self->screen_cur();
+ $self->{old_view_start} = $self->view_start();
+ $self->{old_pty_ev_events} = $self->pty_ev_events(urxvt::EV_NONE);
+
+ my $line = $self->line($self->{oldcr});
+ $self->{offset} = $line->offset_of($self->{oldcr}, $self->{oldcc});
+
+ $self->selection_beg(1, 0);
+ $self->selection_end(1, 0);
+
+ $self->enable(
+ key_press => \&key_press,
+ refresh_begin => \&refresh,
+ refresh_end => \&refresh,
+ tt_write => \&tt_write,
+ );
+
+ if ($self->{offset} >= $line->l) {
+ $self->{offset} = $line->l > 0 ? $line->l - 1 : 0;
+ $self->screen_cur($line->coord_of($self->{offset}));
+ $self->want_refresh();
+ }
+
+ $self->{overlay_len} = 0;
+ status_area($self);
+
+ ()
+}
+
+
+sub deactivate {
+ my ($self) = @_;
+
+ $self->selection_beg(1, 0);
+ $self->selection_end(1, 0);
+
+ delete $self->{overlay} if $self->{overlay};
+
+ $self->disable("key_press", "refresh_begin", "refresh_end", "tt_write");
+ $self->screen_cur($self->{oldcr}, $self->{oldcc});
+ $self->view_start($self->{old_view_start});
+ $self->pty_ev_events($self->{old_pty_ev_events});
+
+ $self->want_refresh();
+
+ $self->{active} = 0;
+
+ ()
+}
+
+
+sub status_area {
+ my ($self, $extra) = @_;
+ my ($stat, $stat_len);
+
+ if ($self->{search}) {
+ $stat_len = $self->ncol;
+ $stat = $self->{search} . ' ' x ($stat_len - length($self->{search}));
+ } else {
+ if ($self->{select}) {
+ $stat = "-V" . ($self->{select} ne 'n' ? uc($self->{select}) : "") . "- ";
+ }
+
+ if ($self->top_row == 0) {
+ $stat .= "All";
+ } elsif ($self->view_start() == $self->top_row) {
+ $stat .= "Top";
+ } elsif ($self->view_start() == 0) {
+ $stat .= "Bot";
+ } else {
+ $stat .= sprintf("%2d%%",
+ ($self->top_row - $self->view_start) * 100 / $self->top_row);
+ }
+
+ $stat = "$extra $stat" if $extra;
+ $stat_len = length($stat);
+ }
+
+ if (!$self->{overlay} || $self->{overlay_len} != $stat_len) {
+ delete $self->{overlay} if $self->{overlay};
+ $self->{overlay} = $self->overlay(-1, -1, $stat_len, 1,
+ urxvt::OVERLAY_RSTYLE, 0);
+ $self->{overlay_len} = $stat_len;
+ }
+
+ $self->{overlay}->set(0, 0, $self->special_encode($stat));
+ $self->{overlay}->show();
+
+ ()
+}
+
+
+sub toggle_select {
+ my ($self, $mode) = @_;
+
+ if ($self->{select} eq $mode) {
+ $self->{select} = '';
+ } else {
+ if (not $self->{select}) {
+ ($self->{ar}, $self->{ac}) = $self->screen_cur();
+ }
+ $self->{select} = $mode;
+ }
+
+ status_area($self);
+ $self->want_refresh();
+
+ ()
+}
+
+
+sub calc_span {
+ my ($self) = @_;
+ my ($cr, $cc) = $self->screen_cur();
+ my ($br, $bc, $er, $ec);
+
+ if ($self->{select} eq 'b') {
+ $br = $self->line($cr)->beg;
+ $bc = $self->line($cr)->offset_of($cr, $cc);
+ $er = $self->line($self->{ar})->beg;
+ $ec = $self->line($self->{ar})->offset_of($self->{ar}, $self->{ac});
+ ($br, $er) = ($er, $br) if $br > $er;
+ ($bc, $ec) = ($ec, $bc) if $bc > $ec;
+ } else {
+ if ($cr < $self->{ar}) {
+ ($br, $bc, $er, $ec) = ($cr, $cc, $self->{ar}, $self->{ac});
+ } elsif ($cr > $self->{ar}) {
+ ($br, $bc, $er, $ec) = ($self->{ar}, $self->{ac}, $cr, $cc);
+ } else {
+ ($br, $er) = ($cr, $cr);
+ ($bc, $ec) = $cc < $self->{ac} ? ($cc, $self->{ac}) : ($self->{ac}, $cc);
+ }
+ }
+
+ if ($self->{select} eq 'l') {
+ ($br, $er) = ($self->line($br)->beg, $self->line($er)->end);
+ ($bc, $ec) = (0, $self->ncol);
+ } else {
+ ++$ec;
+ }
+
+ return ($br, $bc, $er, $ec);
+}
diff --git a/urxvt/ext/url-select b/urxvt/ext/url-select
new file mode 100644
index 0000000..1afbd70
--- /dev/null
+++ b/urxvt/ext/url-select
@@ -0,0 +1,410 @@
+#!/usr/bin/perl
+# Author: Bert Muennich
+# Website: http://www.github.com/muennich/urxvt-perls
+# Based on: http://www.jukie.net/~bart/blog/urxvt-url-yank
+# License: GPLv2
+
+# Use keyboard shortcuts to select URLs.
+# This should be used as a replacement for the default matcher extension,
+# it also makes URLs clickable with the middle mouse button.
+
+# Usage: put the following lines in your .Xdefaults/.Xresources:
+# URxvt.perl-ext-common: ...,url-select
+# URxvt.keysym.M-u: perl:url-select:select_next
+
+# Use Meta-u to activate URL selection mode, then use the following keys:
+# j/k: Select next downward/upward URL (also with arrow keys)
+# g/G: Select first/last URL (also with home/end key)
+# o/Return: Open selected URL in browser, Return: deactivate afterwards
+# y: Copy (yank) selected URL and deactivate selection mode
+# q/Escape: Deactivate URL selection mode
+
+# Options:
+# URxvt.url-select.autocopy: If true, selected URLs are copied to PRIMARY
+# URvxt.url-select.button: Mouse button to click-open URLs (default: 2)
+# URxvt.url-select.launcher: Browser/command to open selected URL with
+# URxvt.url-select.underline: If set to true, all URLs get underlined
+
+
+use strict;
+use warnings;
+
+# The custom rendition bit to use for marking the cell as being underlined
+# by us so we can unset it again after a line has changed.
+use constant UNDERLINED => 1<<3; # arbitrarily chosen in hope of no collision
+
+sub on_start {
+ my ($self) = @_;
+
+ # read resource settings
+ if ($self->x_resource('url-select.launcher')) {
+ @{$self->{browser}} = split /\s+/, $self->x_resource('url-select.launcher');
+ } else {
+ @{$self->{browser}} = ('x-www-browser');
+ }
+ if ($self->x_resource('url-select.underline') eq 'true') {
+ $self->enable(line_update => \&line_update);
+ }
+ if ($self->x_resource('url-select.autocopy') eq 'true') {
+ $self->{autocopy} = 1;
+ }
+
+ $self->{state} = 0;
+
+ for my $mod (split '', $self->x_resource("url-select.button") ||
+ $self->x_resource("matcher.button") || 2) {
+ if ($mod =~ /^\d+$/) {
+ $self->{button} = $mod;
+ } elsif ($mod eq "C") {
+ $self->{state} |= urxvt::ControlMask;
+ } elsif ($mod eq "S") {
+ $self->{state} |= urxvt::ShiftMask;
+ } elsif ($mod eq "M") {
+ $self->{state} |= $self->ModMetaMask;
+ } elsif ($mod ne "-" && $mod ne " ") {
+ warn("invalid button/modifier in $self->{_name}<$self->{argv}[0]>: $mod\n");
+ }
+ }
+
+ if ($self->x_resource('matcher.pattern')) {
+ @{$self->{pattern}} = ($self->x_resource('matcher.pattern'));
+ } elsif ($self->x_resource('matcher.pattern.0')) {
+ my $current = 0;
+
+ while (defined (my $res = $self->x_resource("matcher.pattern.$current"))) {
+ $res = $self->locale_decode($res);
+ utf8::encode $res;
+ push @{$self->{pattern}}, qr($res)x;
+ $current++;
+ }
+ } else {
+ @{$self->{pattern}} = qr{
+ (?:https?://|ftp://|news://|mailto:|file://|\bwww\.)
+ [\w\-\@;\/?:&=%\$.+!*\x27,~#]*
+ (
+ \([\w\-\@;\/?:&=%\$.+!*\x27,~#]*\) # Allow a pair of matched parentheses
+ | #
+ [\w\-\@;\/?:&=%\$+*~] # exclude some trailing characters (heuristic)
+ )+
+ }x;
+ }
+
+ ()
+}
+
+
+sub line_update {
+ my ($self, $row) = @_;
+
+ my $line = $self->line($row);
+ my $text = $line->t;
+ my $rend = $line->r;
+
+ # clear all underlines that were set by us
+ for (@$rend) {
+ if (urxvt::GET_CUSTOM($_) & UNDERLINED) {
+ $_ = urxvt::SET_CUSTOM($_, urxvt::GET_CUSTOM($_) & ~UNDERLINED) &
+ ~urxvt::RS_Uline;
+ }
+ }
+
+ for my $pattern (@{$self->{pattern}}) {
+ while ($text =~ /$pattern/g) {
+ my $url = $&;
+ my ($beg, $end) = ($-[0], $+[0] - 1);
+
+ for (@{$rend}[$beg .. $end]) {
+ unless ($_ & urxvt::RS_Uline) {
+ $_ = urxvt::SET_CUSTOM($_, urxvt::GET_CUSTOM($_) | UNDERLINED);
+ $_ |= urxvt::RS_Uline;
+ }
+ }
+ }
+ }
+
+ $line->r($rend);
+
+ ()
+}
+
+sub on_action {
+ my ($self, $action) = @_;
+
+ on_user_command($self, "url-select:" . $action);
+}
+
+
+sub on_user_command {
+ my ($self, $cmd) = @_;
+
+ if ($cmd eq 'url-select:select_next') {
+ if (not $self->{active}) {
+ activate($self);
+ }
+ select_next($self, -1);
+ }
+
+ ()
+}
+
+
+sub key_press {
+ my ($self, $event, $keysym) = @_;
+ my $char = chr($keysym);
+
+ if ($keysym == 0xff1b || lc($char) eq 'q' ||
+ (lc($char) eq 'c' && $event->{state} & urxvt::ControlMask)) {
+ deactivate($self);
+ } elsif ($keysym == 0xff0d || $char eq 'o' ||
+ (lc($char) eq 'm' && $event->{state} & urxvt::ControlMask)) {
+ $self->exec_async(@{$self->{browser}}, ${$self->{found}[$self->{n}]}[4]);
+ deactivate($self) unless $char eq 'o';
+ } elsif ($char eq 'y') {
+ my $found = $self->{found}[$self->{n}];
+ $self->selection_beg(${$found}[0], ${$found}[1]);
+ $self->selection_end(${$found}[2], ${$found}[3]);
+ $self->selection_make($event->{time});
+ $self->selection_beg(1, 0);
+ $self->selection_end(1, 0);
+ deactivate($self);
+ } elsif ($char eq 'k' || $keysym == 0xff52 || $keysym == 0xff51) {
+ select_next($self, -1, $event);
+ } elsif ($char eq 'j' || $keysym == 0xff54 || $keysym == 0xff53) {
+ select_next($self, 1, $event);
+ } elsif ($char eq 'g' || $keysym == 0xff50) {
+ $self->{row} = $self->top_row - 1;
+ delete $self->{found};
+ select_next($self, 1, $event);
+ } elsif ($char eq 'G' || $keysym == 0xff57) {
+ $self->{row} = $self->nrow;
+ delete $self->{found};
+ select_next($self, -1, $event);
+ }
+
+ return 1;
+}
+
+
+sub on_button_press {
+ my ($self, $event) = @_;
+
+ my $mask = $self->ModLevel3Mask | $self->ModMetaMask |
+ urxvt::ShiftMask | urxvt::ControlMask;
+
+ if ($event->{button} == $self->{button} && ($event->{state} & $mask) == $self->{state}) {
+ my $col = $event->{col};
+ my $row = $event->{row};
+ my $line = $self->line($row);
+ my $text = $line->t;
+
+ for my $pattern (@{$self->{pattern}}) {
+ while ($text =~ /$pattern/g) {
+ my ($url, $beg, $end) = ($&, $-[0], $+[0]);
+ --$end if $url =~ s/["')]$//;
+
+ if ($col >= $beg && $col <= $end) {
+ $self->{button_pressed} = 1;
+ $self->{button_col} = $col;
+ $self->{button_row} = $row;
+ $self->{button_url} = $url;
+ return 1;
+ }
+ }
+ }
+ }
+
+ ()
+}
+
+sub on_button_release {
+ my ($self, $event) = @_;
+
+ if ($self->{button_pressed} && $event->{button} == $self->{button}) {
+ my $col = $event->{col};
+ my $row = $event->{row};
+
+ $self->{button_pressed} = 0;
+
+ if ($col == $self->{button_col} && $row == $self->{button_row}) {
+ $self->exec_async(@{$self->{browser}}, $self->{button_url});
+ return 1;
+ }
+ }
+
+ ()
+}
+
+
+sub select_next {
+ # $dir < 0: up, > 0: down
+ my ($self, $dir, $event) = @_;
+ my $row = $self->{row};
+
+ if (($dir < 0 && $self->{n} > 0) ||
+ ($dir > 0 && $self->{n} < $#{ $self->{found} })) {
+ # another url on current line
+ $self->{n} += $dir;
+ hilight($self);
+ if ($self->{autocopy}) {
+ my $found = $self->{found}[$self->{n}];
+ $self->selection_beg(${$found}[0], ${$found}[1]);
+ $self->selection_end(${$found}[2], ${$found}[3]);
+ $self->selection_make($event->{time});
+ $self->selection_beg(1, 0);
+ $self->selection_end(1, 0);
+ }
+ return;
+ }
+
+ while (($dir < 0 && $row > $self->top_row) ||
+ ($dir > 0 && $row < $self->nrow - 1)) {
+ my $line = $self->line($row);
+ $row = ($dir < 0 ? $line->beg : $line->end) + $dir;
+ $line = $self->line($row);
+ my $text = $line->t;
+
+ for my $pattern (@{$self->{pattern}}) {
+ if ($text =~ /$pattern/g) {
+ delete $self->{found};
+
+ do {
+ my ($beg, $end) = ($-[0], $+[0]);
+ push @{$self->{found}}, [$line->coord_of($beg),
+ $line->coord_of($end), substr($text, $beg, $end - $beg)];
+ } while ($text =~ /$pattern/g);
+
+ $self->{row} = $row;
+ $self->{n} = $dir < 0 ? $#{$self->{found}} : 0;
+ hilight($self);
+ if ($self->{autocopy}) {
+ my $found = $self->{found}[$self->{n}];
+ $self->selection_beg(${$found}[0], ${$found}[1]);
+ $self->selection_end(${$found}[2], ${$found}[3]);
+ $self->selection_make($event->{time});
+ $self->selection_beg(1, 0);
+ $self->selection_end(1, 0);
+ }
+ return;
+ }
+ }
+ }
+
+ deactivate($self) unless $self->{found};
+
+ ()
+}
+
+
+sub hilight {
+ my ($self) = @_;
+
+ if ($self->{found}) {
+ if ($self->{row} < $self->view_start() ||
+ $self->{row} >= $self->view_start() + $self->nrow) {
+ # scroll selected url into visible area
+ my $top = $self->{row} - ($self->nrow >> 1);
+ $self->view_start($top < 0 ? $top : 0);
+ }
+
+ status_area($self);
+ $self->want_refresh();
+ }
+
+ ()
+}
+
+
+sub refresh {
+ my ($self) = @_;
+
+ if ($self->{found}) {
+ if ($self->x_resource('highlightColor')) {
+ $self->scr_xor_span(@{$self->{found}[$self->{n}]}[0 .. 3], urxvt::RS_Sel);
+ } else {
+ $self->scr_xor_span(@{$self->{found}[$self->{n}]}[0 .. 3], urxvt::RS_RVid);
+ }
+ }
+
+ ()
+}
+
+
+sub status_area {
+ my ($self) = @_;
+
+ my $row = $self->{row} < 0 ?
+ $self->{row} - $self->top_row : abs($self->top_row) + $self->{row};
+ my $text = sprintf("%d,%d ", $row + 1, $self->{n} + 1);
+
+ if ($self->top_row == 0) {
+ $text .= "All";
+ } elsif ($self->view_start() == $self->top_row) {
+ $text .= "Top";
+ } elsif ($self->view_start() == 0) {
+ $text .= "Bot";
+ } else {
+ $text .= sprintf("%2d%",
+ ($self->top_row - $self->view_start) * 100 / $self->top_row);
+ }
+
+ my $text_len = length($text);
+
+ if ($self->{overlay_len} != $text_len) {
+ delete $self->{overlay} if $self->{overlay};
+ $self->{overlay} = $self->overlay(-1, -1, $text_len, 1,
+ urxvt::OVERLAY_RSTYLE, 0);
+ $self->{overlay_len} = $text_len;
+ }
+
+ $self->{overlay}->set(0, 0, $self->special_encode($text));
+ $self->{overlay}->show();
+
+ ()
+}
+
+
+sub tt_write {
+ return 1;
+}
+
+
+sub activate {
+ my ($self) = @_;
+
+ $self->{active} = 1;
+
+ $self->{row} = $self->view_start() + $self->nrow;
+ $self->{n} = 0;
+ $self->{overlay_len} = 0;
+ $self->{button_pressed} = 0;
+
+ $self->{view_start} = $self->view_start();
+ $self->{pty_ev_events} = $self->pty_ev_events(urxvt::EV_NONE);
+
+ $self->enable(
+ key_press => \&key_press,
+ refresh_begin => \&refresh,
+ refresh_end => \&refresh,
+ tt_write => \&tt_write,
+ );
+
+ ()
+}
+
+
+sub deactivate {
+ my ($self) = @_;
+
+ $self->disable("key_press", "refresh_begin", "refresh_end", "tt_write");
+ $self->view_start($self->{view_start});
+ $self->pty_ev_events($self->{pty_ev_events});
+
+ delete $self->{overlay} if $self->{overlay};
+ delete $self->{found} if $self->{found};
+
+ $self->want_refresh();
+
+ $self->{active} = 0;
+
+ ()
+}
diff --git a/urxvt/ext/vtwheel b/urxvt/ext/vtwheel
new file mode 100644
index 0000000..7f51226
--- /dev/null
+++ b/urxvt/ext/vtwheel
@@ -0,0 +1,49 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+# Implements a scrollwheel just like in good old vt100's mices
+
+sub simulate_keypress {
+ my ($self, $type) = @_; #type: 0:up, 1:down
+
+ my $keycode_up = 111;
+ my $keycode_down = 116;
+
+ my $numlines = 3;
+
+ my $keycode = 0;
+ if ($type eq 0) {
+ $keycode = $keycode_up;
+ } elsif ($type eq 1) {
+ $keycode = $keycode_down;
+ } else {
+ return;
+ }
+
+ for (my $i = 0 ; $i ne $numlines ; $i++) {
+ $self->key_press(0,$keycode);
+ $self->key_release(0,$keycode);
+ }
+}
+
+sub on_button_release {
+ my ($self, $event) = @_;
+
+ #my $res_ss = $self->resource("secondaryScroll");
+ #warn("ressource ss is <$res_ss>");
+
+ !$self->current_screen and return ();
+
+ #warn("foo, event: <$event->{button}>\n");
+ if ($event->{button} eq "4") { # scroll up
+ $self->simulate_keypress(0);
+ return 1;
+ } elsif ($event->{button} eq "5") { # scroll down
+ $self->simulate_keypress(1);
+ return 1;
+ }
+
+ return ();
+}