diff options
author | tdro <tdro@users.noreply.github.com> | 2022-03-14 22:57:45 -0400 |
---|---|---|
committer | tdro <tdro@users.noreply.github.com> | 2022-03-14 22:57:45 -0400 |
commit | 84fa56baf9a8b7c76e90f7013840f4c4a222957e (patch) | |
tree | 1a70523fb28f06d4691e109dafe3cfc149305832 | |
parent | 99a0494cc933de18caf1324925892265eab65eac (diff) | |
download | dotfiles-84fa56baf9a8b7c76e90f7013840f4c4a222957e.tar.gz dotfiles-84fa56baf9a8b7c76e90f7013840f4c4a222957e.tar.bz2 dotfiles-84fa56baf9a8b7c76e90f7013840f4c4a222957e.zip |
.config/nixpkgs/packages: Use patched rxvt-unicode
Add zeal and weston to package set.
-rw-r--r-- | .bash_profile | 1 | ||||
-rw-r--r-- | .config/X11/Xresources | 21 | ||||
-rw-r--r-- | .config/nixpkgs/config.nix | 4 | ||||
-rw-r--r-- | .config/nixpkgs/packages/rxvt-unicode/default.nix | 99 | ||||
-rw-r--r-- | .config/urxvt/ext/keyboard-select | 606 | ||||
-rw-r--r-- | .config/urxvt/ext/url-select | 408 |
6 files changed, 1129 insertions, 10 deletions
diff --git a/.bash_profile b/.bash_profile index 9581a74..7f0bc8c 100644 --- a/.bash_profile +++ b/.bash_profile @@ -67,6 +67,7 @@ export PYTHONSTARTUP="$XDG_CONFIG_HOME/python/pythonrc" export RECOLL_CONFDIR="$XDG_CONFIG_HOME/recoll" export RUSTUP_HOME="$XDG_DATA_HOME/rustup" export TEXINPUTS=$XDG_DATA_HOME/tex: +export URXVT_PERL_LIB="$XDG_CONFIG_HOME/urxvt/ext" export WEECHAT_HOME="$XDG_CONFIG_HOME/weechat" command -v rsvg-convert > /dev/null 2>&1 && \ diff --git a/.config/X11/Xresources b/.config/X11/Xresources index 2f5bcdd..d32e628 100644 --- a/.config/X11/Xresources +++ b/.config/X11/Xresources @@ -11,11 +11,6 @@ URxvt.internalBorder: 10 URxvt.geometry: 74x18 URxvt.cursorUnderline: 0 URxvt.cursorBlink: 1 -URxvt.cursorColor: #69AEFF -URxvt*font: xft:monospace:size=10,xft:DejaVuSansMono Nerd Font Mono:size=10 -URxvt.boldFont: xft:monospace:size=10,xft:DejaVuSansMono Nerd Font Mono:size=10 -URxvt.italicFont: xft:monospace:size=10,xft:DejaVuSansMono Nerd Font Mono:size=10 -URxvt.boldItalicFont: xft:monospace:size=10,xft:DejaVuSansMono Nerd Font Mono:size=10 URxvt.keysym.C-0: perl:font-size:reset URxvt.keysym.C-Up: perl:font-size:increase URxvt.keysym.C-Down: perl:font-size:decrease @@ -25,14 +20,17 @@ URxvt.scrollWithBuffer: true URxvt.scrollTtyKeypress: true URxvt.transparent: false URxvt.shading: 20 -URxvt.foreground: #ffffff -URxvt.background: #2c303c -URxvt.colorUL: #8fc1ff ! ----------------- ! Terminal Colors ! ----------------- +*colorUL: #8fc1ff +*cursorColor: #69aeff + +*foreground: #ffffff +*background: #2c303c + !black *color0: #2c303c *color8: #999999 @@ -69,12 +67,17 @@ URxvt.colorUL: #8fc1ff ! Font Settings ! ----------------- +*font: xft:monospace:size=10,xft:DejaVuSansMono Nerd Font Mono:size=10 +*boldFont: xft:monospace:size=10,xft:DejaVuSansMono Nerd Font Mono:size=10 +*italicFont: xft:monospace:size=10,xft:DejaVuSansMono Nerd Font Mono:size=10 +*boldItalicFont: xft:monospace:size=10,xft:DejaVuSansMono Nerd Font Mono:size=10 + Xft.autohint: 0 Xft.antialias: 1 Xft.hinting: 1 -Xft.hintstyle: hintslight Xft.dpi: 96 Xft.rgba: rgb +Xft.hintstyle: hintslight Xft.lcdfilter: lcddefault ! ----------------- diff --git a/.config/nixpkgs/config.nix b/.config/nixpkgs/config.nix index 9e5e462..0920b4c 100644 --- a/.config/nixpkgs/config.nix +++ b/.config/nixpkgs/config.nix @@ -66,6 +66,7 @@ in paths = [ (callPackage ./packages/chromexup/default.nix {}) (callPackage ./packages/quickemu/default.nix {}) + (callPackage ./packages/rxvt-unicode/default.nix {}) (callPackage ./packages/systemd2nix/default.nix {}) (callPackage ./packages/youtube-dl/default.nix {}) (unstable.pass.withExtensions (ext: with ext; [ pass-import pass-audit pass-otp ])) @@ -127,7 +128,6 @@ in ranger rclone ripgrep - rxvt-unicode sbcl silver-searcher sshfs @@ -228,6 +228,7 @@ in xsane yad zathura + zeal ]; }; @@ -248,6 +249,7 @@ in autocutsel glxinfo unclutter-xfixes + weston wmctrl xbindkeys xdotool diff --git a/.config/nixpkgs/packages/rxvt-unicode/default.nix b/.config/nixpkgs/packages/rxvt-unicode/default.nix new file mode 100644 index 0000000..24c9afb --- /dev/null +++ b/.config/nixpkgs/packages/rxvt-unicode/default.nix @@ -0,0 +1,99 @@ +{ lib, stdenv, fetchurl, fetchpatch, makeDesktopItem, libX11, libXt, libXft +, libXrender, ncurses, fontconfig, freetype, pkg-config, gdk-pixbuf, perl }: + +let + pname = "rxvt-unicode"; + version = "9.26"; + description = "A clone of the well-known terminal emulator rxvt"; + + desktopItem = makeDesktopItem { + name = pname; + exec = "urxvt"; + genericName = pname; + comment = description; + desktopName = "URxvt"; + icon = "utilities-terminal"; + categories = "System;TerminalEmulator;"; + }; + +in stdenv.mkDerivation { + + inherit pname version; + name = "${pname}-unwrapped-${version}"; + + src = fetchurl { + url = "http://dist.schmorp.de/rxvt-unicode/Attic/rxvt-unicode-${version}.tar.bz2"; + sha256 = "12y9p32q0v7n7rhjla0j2g9d5rj2dmwk20c9yhlssaaxlawiccb4"; + }; + + buildInputs = [ + fontconfig + freetype + gdk-pixbuf + libX11 + libXft + libXrender + libXt + ncurses + perl + pkg-config + ]; + + outputs = [ "out" "terminfo" ]; + + patches = [ + (fetchpatch { + name = "enable-wide-glyphs.patch"; + url = "https://raw.githubusercontent.com/owl4ce/nelumbonaceae/e6be9823ec9ab7c14a23f5a25dfb33ce705d9950/x11-terms/rxvt-unicode/files/enable-wide-glyphs.patch"; + sha256 = "0vfpidysc1kxr3zark2y1b8fxx7k2lck79ccb7b97mgz04mxchvd"; + }) + (fetchpatch { + name = "improve-font-rendering.patch"; + url = "https://raw.githubusercontent.com/owl4ce/nelumbonaceae/e6be9823ec9ab7c14a23f5a25dfb33ce705d9950/x11-terms/rxvt-unicode/files/improve-font-rendering.patch"; + sha256 = "0xkwvn204n679v4mgpw2dl4c30pdl622l6b2iw6sr0gijga8zqcd"; + }) + (fetchpatch { + name = "256-color-resources.patch"; + url = "https://raw.githubusercontent.com/NixOS/nixpkgs/2bb3a9da24ca60d9f5bed69f679a1ec50dbdf997/pkgs/applications/terminal-emulators/rxvt-unicode/patches/256-color-resources.patch"; + sha256 = "00yw67faybglypnvz59k7m3cp5bvdwaiq0771zfi1x7drz3xfql6"; + }) + (fetchpatch { + name = "fixed-layout-size.patch"; + url = "https://raw.githubusercontent.com/owl4ce/nelumbonaceae/b593a0eb7bb45a0b471c2ee2aa02b0e66776b9c0/x11-terms/rxvt-unicode/files/fixed-layout-size.patch"; + sha256 = "148vpln61zs3qv4sipcc5c7fiyc4d4q6k10r7bpr0k0q4pzshljh"; + }) + ]; + + configureFlags = [ + "--enable-perl" + "--enable-unicode3" + "--enable-256-color" + "--enable-wide-glyphs" + "--with-terminfo=$terminfo/share/terminfo" + ]; + + CFLAGS = [ "-I${freetype.dev}/include/freetype2" ]; + LDFLAGS = [ "-lfontconfig" "-lXrender" "-lpthread" ]; + + preConfigure = '' + mkdir -p $terminfo/share/terminfo + export TERMINFO=$terminfo/share/terminfo + mkdir -p $out/$(dirname ${perl.libPrefix}) + ln -s $out/lib/urxvt $out/${perl.libPrefix} + ''; + + postInstall = '' + mkdir -p $out/nix-support + echo "$terminfo" >> $out/nix-support/propagated-user-env-packages + cp -r ${desktopItem}/share/applications/ $out/share/ + ''; + + meta = { + inherit description; + license = lib.licenses.gpl3; + platforms = lib.platforms.unix; + maintainers = with lib.maintainers; [ rnhmjoj ]; + downloadPage = "http://dist.schmorp.de/rxvt-unicode/Attic/"; + homepage = "http://software.schmorp.de/pkg/rxvt-unicode.html"; + }; +} diff --git a/.config/urxvt/ext/keyboard-select b/.config/urxvt/ext/keyboard-select new file mode 100644 index 0000000..14405d4 --- /dev/null +++ b/.config/urxvt/ext/keyboard-select @@ -0,0 +1,606 @@ +#! perl -w +# 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 + +# Options: +# URxvt.keyboard-select.clipboard: If true, copy to clipboard too + + +use strict; + +sub on_start{ + my ($self) = @_; + + $self->{clipboard} = $self->x_resource_boolean('keyboard-select.clipboard'); + + $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 ($self->{clipboard}) { + $self->selection($self->selection(), 1); + $self->selection_grab($event->{time}, 1); + } + 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/.config/urxvt/ext/url-select b/.config/urxvt/ext/url-select new file mode 100644 index 0000000..34637bb --- /dev/null +++ b/.config/urxvt/ext/url-select @@ -0,0 +1,408 @@ +#! perl -w +# 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; + +# 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; + + () +} |