30 changed files with 6877 additions and 13 deletions
@ -0,0 +1,33 @@ |
|||||||
|
!Расширения Perl |
||||||
|
URxvt.perl-lib: /home/maks/.shellrc/etc/soft/urxvt/perl/ |
||||||
|
URxvt.perl-ext-common: tabbedex,readline,searchable-scrollback,matcher,font-size |
||||||
|
|
||||||
|
!Tabbedex |
||||||
|
URxvt.tabbed.autohide: true |
||||||
|
URxvt.keysym.Control-t: perl:tabbedex:new_tab |
||||||
|
URxvt.keysym.Control-Tab: perl:tabbedex:next_tab |
||||||
|
URxvt.keysym.Control-Shift-Tab: perl:tabbedex:prev_tab |
||||||
|
|
||||||
|
URxvt.tabbed.tabbar-fg: 2 |
||||||
|
URxvt.tabbed.tabbar-bg: 252 |
||||||
|
URxvt.tabbed.tab-fg: 0 |
||||||
|
URxvt.tabbed.tab-bg: 250 |
||||||
|
URxvt.tabbed.title-bg: 252 |
||||||
|
URxvt.tabbed.title-fg: 0 |
||||||
|
URxvt.tabbed.tabbar-timeouts: " " |
||||||
|
|
||||||
|
!Font-size |
||||||
|
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 |
||||||
|
|
||||||
|
!Matcher |
||||||
|
URxvt.url-launcher: chromium |
||||||
|
URxvt.matcher.button: 2 |
||||||
|
URxvt.colorUL: #09419b |
||||||
|
URxvt.underlineURLs: false |
||||||
|
URxvt.underlineColor: #09419b |
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,51 @@ |
|||||||
|
#! perl |
||||||
|
|
||||||
|
# Copyright (C) 2011 Ryan Kavanagh <ryanakca@kubuntu.org> |
||||||
|
# |
||||||
|
# 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, write to the Free Software Foundation, Inc., |
||||||
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||||
|
|
||||||
|
#:META:RESOURCE:%:string:the shell snippet to execute |
||||||
|
|
||||||
|
=head1 NAME |
||||||
|
|
||||||
|
bell-command - execute a command when the bell rings |
||||||
|
|
||||||
|
=head1 DESCRIPTION |
||||||
|
|
||||||
|
Runs the command specified by the C<URxvt.bell-command> resource when |
||||||
|
a bell event occurs. For example, the following pops up a notification |
||||||
|
bubble with the text "Beep, Beep" using notify-send: |
||||||
|
|
||||||
|
URxvt.bell-command: notify-send "Beep, Beep" |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
sub on_start { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
$self->{bell_cmd} = $self->x_resource ("bell-command"); |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_bell { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
if (defined $self->{bell_cmd}) { |
||||||
|
$self->exec_async ($self->{bell_cmd}); |
||||||
|
} |
||||||
|
|
||||||
|
() |
||||||
|
} |
@ -0,0 +1,37 @@ |
|||||||
|
#! perl |
||||||
|
|
||||||
|
=head1 NAME |
||||||
|
|
||||||
|
block-graphics-to-ascii - map block graphics to ascii characters |
||||||
|
|
||||||
|
=head1 DESCRIPTION |
||||||
|
|
||||||
|
A not very useful example of filtering all text output to the terminal |
||||||
|
by replacing all line-drawing characters (U+2500 .. U+259F) by a |
||||||
|
similar-looking ascii character. |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
# simple example that uses the add_lines hook to filter unicode and vt100 line/box graphics |
||||||
|
|
||||||
|
# ─━│┃┄┅┆┇┈┉┊┋┌┍┎┏┐┑┒┓└┕┖┗┘┙┚┛├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋╌╍╎╏ |
||||||
|
my $rep_unicode = "--||--||--||++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--||" |
||||||
|
# ═║╒╓╔╕╖╗╘╙╚╛╜╝╞╟╠╡╢╣╤╥╦╧╨╩╪╫╬╭╮╯╰╱╲ ╳╴╵╶╷╸╹╺╻╼╽╾╿▀▁▂▃▄▅▆▇█▉▊▋▌▍▎▏▐░▒▓▔▕▖▗▘▙▚▛▜▝▞▟ |
||||||
|
. "=|+++++++++++++++++++++++++++++++/\\X-|-|-|-|-|-|#____#######|||||###~###########"; |
||||||
|
|
||||||
|
# ↑↓→←█▚ ☃HIJKLMNOPQRSTUVWXYZ[\ ]^ ◆▒␉␌␍␊°±␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π≠£· |
||||||
|
my $rep_acs = "↑↓<>#\\☃HIJKLMNOPQRSTUVWXYZ[\\]^ ◆#␉␌␍␊°±␋+++++⎺⎻-⎼⎽++++!<>π≠£·"; |
||||||
|
|
||||||
|
sub on_add_lines { |
||||||
|
my ($self, $str) = @_; |
||||||
|
|
||||||
|
$str =~ s/([\x{2500}-\x{259f}])/substr $rep_unicode, (ord $1) - 0x2500, 1/ge; |
||||||
|
|
||||||
|
$str =~ s/([\x41-\x7e])/substr $rep_acs, (ord $1) - 0x41, 1/ge |
||||||
|
if $self->cur_charset eq "0"; |
||||||
|
|
||||||
|
$self->scr_add_lines ($str); |
||||||
|
|
||||||
|
1 |
||||||
|
} |
||||||
|
|
@ -0,0 +1,30 @@ |
|||||||
|
#! perl |
||||||
|
|
||||||
|
=head1 NAME |
||||||
|
|
||||||
|
clipboard-osc - implement the clipboard operating system command sequence |
||||||
|
|
||||||
|
=head1 SYNOPSIS |
||||||
|
|
||||||
|
urxvt -pe clipboard-osc |
||||||
|
|
||||||
|
=head1 DESCRIPTION |
||||||
|
|
||||||
|
This extension implements the clipboard;copy Perl OSC. |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
|
||||||
|
sub on_osc_seq_perl { |
||||||
|
my ($self, $osc, $resp) = @_; |
||||||
|
|
||||||
|
return unless $osc =~ s/^clipboard;([^;]+)//; |
||||||
|
|
||||||
|
if ($1 eq "copy") { |
||||||
|
my $text = $self->selection (); |
||||||
|
$self->selection ($text, 1); |
||||||
|
$self->selection_grab (urxvt::CurrentTime, 1); |
||||||
|
} |
||||||
|
|
||||||
|
1 |
||||||
|
} |
@ -0,0 +1,57 @@ |
|||||||
|
#! perl |
||||||
|
|
||||||
|
=head1 NAME |
||||||
|
|
||||||
|
confirm-paste - ask for confirmation before pasting multiline text |
||||||
|
|
||||||
|
=head1 DESCRIPTION |
||||||
|
|
||||||
|
Displays a confirmation dialog when a paste containing at least a full |
||||||
|
line is detected. |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
sub msg { |
||||||
|
my ($self, $msg) = @_; |
||||||
|
|
||||||
|
$self->{overlay} = $self->overlay (0, -1, $self->ncol, 2, urxvt::OVERLAY_RSTYLE, 0); |
||||||
|
$self->{overlay}->set (0, 0, $msg); |
||||||
|
} |
||||||
|
|
||||||
|
sub on_tt_paste { |
||||||
|
my ($self, $str) = @_; |
||||||
|
|
||||||
|
my $count = ($str =~ tr/\012\015//); |
||||||
|
|
||||||
|
return unless $count; |
||||||
|
|
||||||
|
$self->{paste} = \$str; |
||||||
|
$self->msg ("Paste of $count lines, continue? (y/n)"); |
||||||
|
my $preview = substr $self->locale_decode ($str), 0, $self->ncol; |
||||||
|
$preview =~ s/\n/\\n/g; |
||||||
|
$self->{overlay}->set (0, 1, $self->special_encode ($preview)); |
||||||
|
$self->enable (key_press => \&key_press); |
||||||
|
|
||||||
|
1 |
||||||
|
} |
||||||
|
|
||||||
|
sub leave { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
$self->{paste} = undef; |
||||||
|
delete $self->{overlay}; |
||||||
|
$self->disable ("key_press"); |
||||||
|
} |
||||||
|
|
||||||
|
sub key_press { |
||||||
|
my ($self, $event, $keysym, $string) = @_; |
||||||
|
|
||||||
|
if ($keysym == 121) { # y |
||||||
|
$self->tt_paste (${$self->{paste}}); |
||||||
|
$self->leave; |
||||||
|
} elsif ($keysym == 110) { # n |
||||||
|
$self->leave; |
||||||
|
} |
||||||
|
|
||||||
|
1 |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
#! perl |
||||||
|
|
||||||
|
=head1 NAME |
||||||
|
|
||||||
|
digital-clock - display a digital clock overlay |
||||||
|
|
||||||
|
=head1 DESCRIPTION |
||||||
|
|
||||||
|
Displays a digital clock using the built-in overlay. |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
sub on_start { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
$self->{overlay} = $self->overlay (-1, 0, 8, 1, urxvt::OVERLAY_RSTYLE, 0); |
||||||
|
$self->{timer} = urxvt::timer |
||||||
|
->new |
||||||
|
->start (1 + int urxvt::NOW) # make sure we update "on" the second |
||||||
|
->interval (1) |
||||||
|
->cb (sub { |
||||||
|
$self->{overlay}->set (0, 0, |
||||||
|
sprintf "%2d:%02d:%02d", (localtime urxvt::NOW)[2,1,0]); |
||||||
|
}); |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
|
@ -0,0 +1,119 @@ |
|||||||
|
#! perl |
||||||
|
|
||||||
|
=head1 NAME |
||||||
|
|
||||||
|
eval - evaluate arbitrary perl code using actions |
||||||
|
|
||||||
|
=head1 EXAMPLES |
||||||
|
|
||||||
|
URxvt.keysym.M-c: eval:selection_to_clipboard |
||||||
|
URxvt.keysym.M-v: eval:paste_clipboard |
||||||
|
URxvt.keysym.M-V: eval:paste_primary |
||||||
|
|
||||||
|
URxvt.keysym.M-Up: eval:scroll_up 1 |
||||||
|
URxvt.keysym.M-Down: eval:scroll_down 1 |
||||||
|
URxvt.keysym.M-Home: eval:scroll_to_top |
||||||
|
URxvt.keysym.M-End: eval:scroll_to_bottom |
||||||
|
|
||||||
|
=head1 DESCRIPTION |
||||||
|
|
||||||
|
Add support for evaluating arbitrary perl code using actions in keysym |
||||||
|
resources. If a keysym I<action> takes the form C<eval:STRING>, the |
||||||
|
specified B<STRING> is evaluated as a Perl expression. While the full |
||||||
|
urxvt API is available, the following methods are also provided for |
||||||
|
users' convenience, as they implement basic actions: |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
our ($self); |
||||||
|
|
||||||
|
=over 4 |
||||||
|
|
||||||
|
=item scroll_up $count |
||||||
|
|
||||||
|
=item scroll_up_pages $count |
||||||
|
|
||||||
|
=item scroll_down $count |
||||||
|
|
||||||
|
=item scroll_down_pages $count |
||||||
|
|
||||||
|
Scroll up or down by C<$count> lines or pages. |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
sub scroll_up ($) { |
||||||
|
my $lines = $_[0]; |
||||||
|
$self->view_start ($self->view_start - $lines); |
||||||
|
} |
||||||
|
|
||||||
|
sub scroll_up_pages ($) { |
||||||
|
my $lines = $_[0] * ($self->nrow - 1); |
||||||
|
$self->view_start ($self->view_start - $lines); |
||||||
|
} |
||||||
|
|
||||||
|
sub scroll_down ($) { |
||||||
|
my $lines = $_[0]; |
||||||
|
$self->view_start ($self->view_start + $lines); |
||||||
|
} |
||||||
|
|
||||||
|
sub scroll_down_pages ($) { |
||||||
|
my $lines = $_[0] * ($self->nrow - 1); |
||||||
|
$self->view_start ($self->view_start + $lines); |
||||||
|
} |
||||||
|
|
||||||
|
=item scroll_to_top |
||||||
|
|
||||||
|
=item scroll_to_bottom |
||||||
|
|
||||||
|
Scroll to the top or bottom of the scrollback. |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
sub scroll_to_top () { |
||||||
|
$self->view_start ($self->top_row); |
||||||
|
} |
||||||
|
|
||||||
|
sub scroll_to_bottom () { |
||||||
|
$self->view_start (0); |
||||||
|
} |
||||||
|
|
||||||
|
=item selection_to_clipboard |
||||||
|
|
||||||
|
Copy the selection to the CLIPBOARD. |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
sub selection_to_clipboard () { |
||||||
|
$self->selection ($self->selection, 1); |
||||||
|
$self->selection_grab (urxvt::CurrentTime, 1); |
||||||
|
} |
||||||
|
|
||||||
|
=item paste_primary |
||||||
|
|
||||||
|
=item paste_clipboard |
||||||
|
|
||||||
|
Paste the value of the PRIMARY or CLIPBOARD selection. |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
sub paste_primary () { |
||||||
|
$self->selection_request (urxvt::CurrentTime, 1); |
||||||
|
} |
||||||
|
|
||||||
|
sub paste_clipboard () { |
||||||
|
$self->selection_request (urxvt::CurrentTime, 3); |
||||||
|
} |
||||||
|
|
||||||
|
=back |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
sub on_action { |
||||||
|
my ($arg_self, $action) = @_; |
||||||
|
|
||||||
|
local $self = $arg_self; |
||||||
|
eval "#line 1 \"$action\"\n$action"; |
||||||
|
die $@ if $@; |
||||||
|
|
||||||
|
() |
||||||
|
} |
@ -0,0 +1,57 @@ |
|||||||
|
#! perl |
||||||
|
|
||||||
|
=head1 NAME |
||||||
|
|
||||||
|
example-refresh-hooks - example of how to use refresh hooks |
||||||
|
|
||||||
|
=head1 DESCRIPTION |
||||||
|
|
||||||
|
Displays a very simple digital clock in the upper right corner of the |
||||||
|
window. Illustrates overwriting the refresh callbacks to create your own |
||||||
|
overlays or changes. |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
sub on_init { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
# force a refresh every second |
||||||
|
$self->{digital_clock_refresh} = urxvt::timer |
||||||
|
->new |
||||||
|
->start (1 + int urxvt::NOW) |
||||||
|
->interval (1) |
||||||
|
->cb (sub { $self->want_refresh }); |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
# before refreshing: replace upper right with the clock display |
||||||
|
sub on_refresh_begin { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
my $time = sprintf "%2d:%02d:%02d", (localtime urxvt::NOW)[2, 1, 0]; |
||||||
|
my $xpos = $self->ncol - length $time; |
||||||
|
|
||||||
|
$xpos >= 0 |
||||||
|
or return; |
||||||
|
|
||||||
|
$self->{digital_clock_rend} = $self->ROW_r (0, [(urxvt::DEFAULT_RSTYLE) x length $time], $xpos); |
||||||
|
$self->{digital_clock_text} = $self->ROW_t (0, $time, $xpos); |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
# after refreshing: restore previous screen contents |
||||||
|
sub on_refresh_end { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
exists $self->{digital_clock_text} |
||||||
|
or return; |
||||||
|
|
||||||
|
$self->ROW_r (0, delete $self->{digital_clock_rend}); |
||||||
|
$self->ROW_t (0, delete $self->{digital_clock_text}); |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
|
@ -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: $! $?"; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,37 @@ |
|||||||
|
#! perl |
||||||
|
|
||||||
|
=head1 NAME |
||||||
|
|
||||||
|
keysym-list - implement the "list" keysym expansion. |
||||||
|
|
||||||
|
=head1 SYNOPSIS |
||||||
|
|
||||||
|
urxvt -pe keysym-list |
||||||
|
|
||||||
|
=head1 DESCRIPTION |
||||||
|
|
||||||
|
The "list" keysym expansion was formerly part of the rxvt-unicode core, |
||||||
|
and has been moved into this extension for backwards compatibility. You |
||||||
|
shouldn't use this extension except for compatibility with old |
||||||
|
configurations. |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
|
||||||
|
sub on_register_command { |
||||||
|
my ($self, $keysym, $state, $str) = @_; |
||||||
|
|
||||||
|
if ($str =~ /^list(.)/) { |
||||||
|
my @list = split /\Q$1/, $str; |
||||||
|
if (@list == 3 or @list == 4) { |
||||||
|
$self->register_command ($keysym++, $state, "string:$list[1]$_$list[3]") |
||||||
|
for split //, $list[2]; |
||||||
|
|
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
warn "unable to parse keysym '$str' as list, processing as normal keysym\n"; |
||||||
|
} |
||||||
|
|
||||||
|
() |
||||||
|
} |
@ -0,0 +1,85 @@ |
|||||||
|
#! perl |
||||||
|
|
||||||
|
#:META:RESOURCE:%.hotkey:string:activation hotkey keysym |
||||||
|
|
||||||
|
=head1 NAME |
||||||
|
|
||||||
|
kuake - kuake-like hotkey terminal |
||||||
|
|
||||||
|
=head1 EXAMPLES |
||||||
|
|
||||||
|
@@RXVT_NAME@@ -kuake-hotkey F10 |
||||||
|
|
||||||
|
URxvt.kuake.hotkey: F10 |
||||||
|
|
||||||
|
=head1 DESCRIPTION |
||||||
|
|
||||||
|
A very primitive quake-console-like extension. It was inspired by a |
||||||
|
description of how the programs C<kuake> and C<yakuake> work: Whenever the |
||||||
|
user presses a global accelerator key (by default C<F10>), the terminal |
||||||
|
will show or hide itself. Another press of the accelerator key will hide |
||||||
|
or show it again. |
||||||
|
|
||||||
|
Initially, the window will not be shown when using this extension. |
||||||
|
|
||||||
|
This is useful if you need a single terminal that is not using any desktop |
||||||
|
space most of the time but is quickly available at the press of a key. |
||||||
|
|
||||||
|
The accelerator key is grabbed regardless of any modifiers, so this |
||||||
|
extension will actually grab a physical key just for this function. |
||||||
|
|
||||||
|
If you want a quake-like animation, tell your window manager to do so |
||||||
|
(fvwm can do it). |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
sub on_start { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
$self->{key} = $self->{argv}[0] || $self->x_resource ("%.hotkey") || "F10"; |
||||||
|
|
||||||
|
$self->{keysym} = $self->XStringToKeysym ($self->{key}) |
||||||
|
or urxvt::fatal "cannot convert requested kuake wake-up key '$self->{key}' to keysym, unable to continue.\n"; |
||||||
|
|
||||||
|
$self->{keycode} = $self->XKeysymToKeycode ($self->{keysym}) |
||||||
|
or urxvt::fatal "cannot convert requested kuake wake-up key '$self->{key}' to keycode, unable to continue.\n"; |
||||||
|
|
||||||
|
$self->XGrabKey ($self->{keycode}, urxvt::AnyModifier, $self->DefaultRootWindow); |
||||||
|
|
||||||
|
$self->XUnmapWindow ($self->parent); |
||||||
|
|
||||||
|
$self->{unmap_me} = 1; |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_map_notify { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
# suppress initial map event |
||||||
|
$self->XUnmapWindow ($self->parent) |
||||||
|
if delete $self->{unmap_me}; |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_root_event { |
||||||
|
my ($self, $event) = @_; |
||||||
|
|
||||||
|
return unless $event->{type} == urxvt::KeyPress && $event->{keycode} == $self->{keycode}; |
||||||
|
|
||||||
|
$self->mapped |
||||||
|
? $self->XUnmapWindow ($self->parent) |
||||||
|
: $self->XMapWindow ($self->parent); |
||||||
|
|
||||||
|
1 |
||||||
|
} |
||||||
|
|
||||||
|
sub on_destroy { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
$self->XUngrabKey ($self->XKeysymToKeycode ($self->{keysym}), 0, $self->DefaultRootWindow) |
||||||
|
if $self->{keysym}; |
||||||
|
|
||||||
|
() |
||||||
|
} |
@ -0,0 +1,492 @@ |
|||||||
|
#! perl |
||||||
|
|
||||||
|
# Author: Tim Pope <rxvt-unicodeNOSPAM@tpope.org> |
||||||
|
# Bob Farrell <robertanthonyfarrell@gmail.com> |
||||||
|
# Emanuele Giaquinta |
||||||
|
|
||||||
|
#:META:RESOURCE:%.launcher:string:default launcher command |
||||||
|
#:META:RESOURCE:%.button:string:the mouse button used to activate a match |
||||||
|
#:META:RESOURCE:%.pattern.:string:extra pattern to match |
||||||
|
#:META:RESOURCE:%.launcher.:string:custom launcher for pattern |
||||||
|
#:META:RESOURCE:%.rend.:string:custom rendition for pattern |
||||||
|
|
||||||
|
=head1 NAME |
||||||
|
|
||||||
|
matcher - match strings in terminal output and change their rendition |
||||||
|
|
||||||
|
=head1 DESCRIPTION |
||||||
|
|
||||||
|
Uses per-line display filtering (C<on_line_update>) to underline text |
||||||
|
matching a certain pattern and make it clickable. When clicked with the |
||||||
|
mouse button specified in the C<matcher.button> resource (default 2, or |
||||||
|
middle), the program specified in the C<matcher.launcher> resource |
||||||
|
(default, the C<url-launcher> resource, C<sensible-browser>) will be started |
||||||
|
with the matched text as first argument. The default configuration is |
||||||
|
suitable for matching URLs and launching a web browser, like the |
||||||
|
former "mark-urls" extension. |
||||||
|
|
||||||
|
The default pattern to match URLs can be overridden with the |
||||||
|
C<matcher.pattern.0> resource, and additional patterns can be specified |
||||||
|
with numbered patterns, in a manner similar to the "selection" extension. |
||||||
|
The launcher can also be overridden on a per-pattern basis. |
||||||
|
|
||||||
|
It is possible to activate the most recently seen match or a list of matches |
||||||
|
from the keyboard. Simply bind a keysym to "matcher:last" or |
||||||
|
"matcher:list" as seen in the example below. |
||||||
|
|
||||||
|
The 'matcher:select' action enables a mode in which it is possible to |
||||||
|
iterate over the matches using the keyboard and either activate them |
||||||
|
or copy them to the clipboard. While the mode is active, normal terminal |
||||||
|
input/output is suspended and the following bindings are recognized: |
||||||
|
|
||||||
|
=over 4 |
||||||
|
|
||||||
|
=item C<Up> |
||||||
|
|
||||||
|
Search for a match upwards. |
||||||
|
|
||||||
|
=item C<Down> |
||||||
|
|
||||||
|
Search for a match downwards. |
||||||
|
|
||||||
|
=item C<Home> |
||||||
|
|
||||||
|
Jump to the topmost match. |
||||||
|
|
||||||
|
=item C<End> |
||||||
|
|
||||||
|
Jump to the bottommost match. |
||||||
|
|
||||||
|
=item C<Escape> |
||||||
|
|
||||||
|
Leave the mode and return to the point where search was started. |
||||||
|
|
||||||
|
=item C<Enter> |
||||||
|
|
||||||
|
Activate the current match. |
||||||
|
|
||||||
|
=item C<y> |
||||||
|
|
||||||
|
Copy the current match to the clipboard. |
||||||
|
|
||||||
|
=back |
||||||
|
|
||||||
|
Example: load and use the matcher extension with defaults. |
||||||
|
|
||||||
|
URxvt.perl-ext: default,matcher |
||||||
|
|
||||||
|
Example: use a custom configuration. |
||||||
|
|
||||||
|
URxvt.url-launcher: sensible-browser |
||||||
|
URxvt.keysym.C-Delete: matcher:last |
||||||
|
URxvt.keysym.M-Delete: matcher:list |
||||||
|
URxvt.matcher.button: 1 |
||||||
|
URxvt.matcher.pattern.1: \\bwww\\.[\\w-]+\\.[\\w./?&@#-]*[\\w/-] |
||||||
|
URxvt.matcher.pattern.2: \\B(/\\S+?):(\\d+)(?=:|$) |
||||||
|
URxvt.matcher.launcher.2: gvim +$2 $1 |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
my $url = |
||||||
|
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 matchlist_key_press { |
||||||
|
my ($self, $event, $keysym, $octets) = @_; |
||||||
|
|
||||||
|
delete $self->{overlay}; |
||||||
|
$self->disable ("key_press"); |
||||||
|
|
||||||
|
my $i = ($keysym == 96 ? 0 : $keysym - 48); |
||||||
|
if ($i >= 0 && $i < @{ $self->{matches} }) { |
||||||
|
my @exec = @{ $self->{matches}[$i] }; |
||||||
|
$self->exec_async (@exec[5 .. $#exec]); |
||||||
|
} |
||||||
|
|
||||||
|
1 |
||||||
|
} |
||||||
|
|
||||||
|
# backwards compat |
||||||
|
sub on_user_command { |
||||||
|
my ($self, $cmd) = @_; |
||||||
|
|
||||||
|
if ($cmd eq "matcher:list") { |
||||||
|
$self->matchlist; |
||||||
|
} elsif ($cmd eq "matcher:last") { |
||||||
|
$self->most_recent; |
||||||
|
} elsif ($cmd eq "matcher:select") { |
||||||
|
$self->select_enter; |
||||||
|
} elsif ($cmd eq "matcher") { |
||||||
|
# for backward compatibility |
||||||
|
$self->most_recent; |
||||||
|
} |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_action { |
||||||
|
my ($self, $action) = @_; |
||||||
|
|
||||||
|
if ($action eq "list") { |
||||||
|
$self->matchlist; |
||||||
|
} elsif ($action eq "last") { |
||||||
|
$self->most_recent; |
||||||
|
} elsif ($action eq "select") { |
||||||
|
$self->select_enter; |
||||||
|
} |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub matchlist { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
$self->{matches} = []; |
||||||
|
my $row = $self->nrow - 1; |
||||||
|
while ($row >= 0 && @{ $self->{matches} } < 10) { |
||||||
|
my $line = $self->line ($row); |
||||||
|
my @matches = $self->find_matches ($row); |
||||||
|
|
||||||
|
for (sort { $b->[0] <=> $a->[0] or $b->[1] <=> $a->[1] } @matches) { |
||||||
|
push @{ $self->{matches} }, $_; |
||||||
|
last if @{ $self->{matches} } == 10; |
||||||
|
} |
||||||
|
|
||||||
|
$row = $line->beg - 1; |
||||||
|
} |
||||||
|
|
||||||
|
return unless @{ $self->{matches} }; |
||||||
|
|
||||||
|
my $width = 0; |
||||||
|
|
||||||
|
my $i = 0; |
||||||
|
for my $match (@{ $self->{matches} }) { |
||||||
|
my $text = $match->[4]; |
||||||
|
my $w = $self->strwidth ("$i-$text"); |
||||||
|
|
||||||
|
$width = $w if $w > $width; |
||||||
|
$i++; |
||||||
|
} |
||||||
|
|
||||||
|
$width = $self->ncol - 2 if $width > $self->ncol - 2; |
||||||
|
|
||||||
|
$self->{overlay} = $self->overlay (0, 0, $width, scalar (@{ $self->{matches} }), urxvt::OVERLAY_RSTYLE, 2); |
||||||
|
my $i = 0; |
||||||
|
for my $match (@{ $self->{matches} }) { |
||||||
|
my $text = $match->[4]; |
||||||
|
|
||||||
|
$self->{overlay}->set (0, $i, "$i-$text"); |
||||||
|
$i++; |
||||||
|
} |
||||||
|
|
||||||
|
$self->enable (key_press => \&matchlist_key_press); |
||||||
|
} |
||||||
|
|
||||||
|
sub most_recent { |
||||||
|
my ($self) = shift; |
||||||
|
my $row = $self->nrow - 1; |
||||||
|
my @exec; |
||||||
|
while ($row >= $self->top_row) { |
||||||
|
my $line = $self->line ($row); |
||||||
|
@exec = $self->command_for($row); |
||||||
|
last if(@exec); |
||||||
|
|
||||||
|
$row = $line->beg - 1; |
||||||
|
} |
||||||
|
if(@exec) { |
||||||
|
return $self->exec_async (@exec); |
||||||
|
} |
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub my_resource { |
||||||
|
$_[0]->x_resource ("%.$_[1]") |
||||||
|
} |
||||||
|
|
||||||
|
# turn a rendition spec in the resource into a sub that implements it on $_ |
||||||
|
sub parse_rend { |
||||||
|
my ($self, $str) = @_; |
||||||
|
my ($mask, $fg, $bg, $failed) = $str ? urxvt::rend2mask($str) |
||||||
|
: (urxvt::RS_Uline, undef, undef, []); |
||||||
|
warn "Failed to parse rendition string: " . join(',', @$failed) if @$failed; |
||||||
|
my @rend; |
||||||
|
push @rend, sub { $_ |= $mask } if $mask; |
||||||
|
push @rend, sub { $_ = urxvt::SET_FGCOLOR($_, $fg) } if defined $fg; |
||||||
|
push @rend, sub { $_ = urxvt::SET_BGCOLOR($_, $bg) } if defined $bg; |
||||||
|
sub { |
||||||
|
for my $s ( @rend ) { &$s }; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
sub on_start { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
$self->{launcher} = $self->my_resource ("launcher") || $self->x_resource("url-launcher") || "sensible-browser"; |
||||||
|
|
||||||
|
$self->{button} = 2; |
||||||
|
$self->{state} = 0; |
||||||
|
if($self->{argv}[0] || $self->my_resource ("button")) { |
||||||
|
my @mods = split '', $self->{argv}[0] || $self->my_resource ("button"); |
||||||
|
for my $mod (@mods) { |
||||||
|
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("$mod is invalid in $self->{_name}<$self->{argv}[0]>\n"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
my @defaults = ($url); |
||||||
|
my @matchers; |
||||||
|
for (my $idx = 0; defined (my $res = $self->my_resource ("pattern.$idx") || $defaults[$idx]); $idx++) { |
||||||
|
$res = $self->locale_decode ($res); |
||||||
|
utf8::encode $res; |
||||||
|
my $launcher = $self->my_resource ("launcher.$idx"); |
||||||
|
$launcher =~ s/\$&|\$\{&\}/\${0}/g if $launcher; |
||||||
|
my $rend = $self->parse_rend($self->my_resource ("rend.$idx")); |
||||||
|
unshift @matchers, [qr($res)x,$launcher,$rend]; |
||||||
|
} |
||||||
|
$self->{matchers} = \@matchers; |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_line_update { |
||||||
|
my ($self, $row) = @_; |
||||||
|
|
||||||
|
# fetch the line that has changed |
||||||
|
my $line = $self->line ($row); |
||||||
|
my $text = $line->t; |
||||||
|
my $rend; |
||||||
|
|
||||||
|
# find all urls (if any) |
||||||
|
for my $matcher (@{$self->{matchers}}) { |
||||||
|
while ($text =~ /$matcher->[0]/g) { |
||||||
|
#print "$&\n"; |
||||||
|
$rend ||= $line->r; |
||||||
|
|
||||||
|
# mark all characters as underlined. we _must_ not toggle underline, |
||||||
|
# as we might get called on an already-marked url. |
||||||
|
&{$matcher->[2]} |
||||||
|
for @{$rend}[$-[0] .. $+[0] - 1]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$line->r ($rend) if $rend; |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub valid_button { |
||||||
|
my ($self, $event) = @_; |
||||||
|
my $mask = $self->ModLevel3Mask | $self->ModMetaMask |
||||||
|
| urxvt::ShiftMask | urxvt::ControlMask; |
||||||
|
return ($event->{button} == $self->{button} && |
||||||
|
($event->{state} & $mask) == $self->{state}); |
||||||
|
} |
||||||
|
|
||||||
|
sub find_matches { |
||||||
|
my ($self, $row, $col) = @_; |
||||||
|
my $line = $self->line ($row); |
||||||
|
my $text = $line->t; |
||||||
|
my $off = $line->offset_of ($row, $col) if defined $col; |
||||||
|
|
||||||
|
my @matches; |
||||||
|
for my $matcher (@{$self->{matchers}}) { |
||||||
|
my $launcher = $matcher->[1] || $self->{launcher}; |
||||||
|
while ($text =~ /$matcher->[0]/g) { |
||||||
|
my $match = substr $text, $-[0], $+[0] - $-[0]; |
||||||
|
my @begin = @-; |
||||||
|
my @end = @+; |
||||||
|
my @exec; |
||||||
|
|
||||||
|
if (!defined($off) || ($-[0] <= $off && $+[0] >= $off)) { |
||||||
|
if ($launcher !~ /\$/) { |
||||||
|
@exec = ($launcher, $match); |
||||||
|
} else { |
||||||
|
# It'd be nice to just access a list like ($&,$1,$2...), |
||||||
|
# but alas, m//g behaves differently in list context. |
||||||
|
@exec = map { s/\$(\d+)|\$\{(\d+)\}/ |
||||||
|
substr $text, $begin[$1 || $2], $end[$1 || $2] - $begin[$1 || $2] |
||||||
|
/egx; $_ } split /\s+/, $launcher; |
||||||
|
} |
||||||
|
|
||||||
|
push @matches, [ $line->coord_of ($begin[0]), $line->coord_of ($end[0]), $match, @exec ]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@matches; |
||||||
|
} |
||||||
|
|
||||||
|
sub command_for { |
||||||
|
my ($self, $row, $col) = @_; |
||||||
|
|
||||||
|
my @matches = $self->find_matches ($row, $col); |
||||||
|
if (@matches) { |
||||||
|
my @match = @{ $matches[0] }; |
||||||
|
return @match[5 .. $#match]; |
||||||
|
} |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_button_press { |
||||||
|
my ($self, $event) = @_; |
||||||
|
if($self->valid_button($event) |
||||||
|
&& (my @exec = $self->command_for($event->{row},$event->{col}))) { |
||||||
|
$self->{row} = $event->{row}; |
||||||
|
$self->{col} = $event->{col}; |
||||||
|
$self->{cmd} = \@exec; |
||||||
|
return 1; |
||||||
|
} else { |
||||||
|
delete $self->{row}; |
||||||
|
delete $self->{col}; |
||||||
|
delete $self->{cmd}; |
||||||
|
} |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_button_release { |
||||||
|
my ($self, $event) = @_; |
||||||
|
|
||||||
|
my $row = delete $self->{row}; |
||||||
|
my $col = delete $self->{col}; |
||||||
|
my $cmd = delete $self->{cmd}; |
||||||
|
|
||||||
|
return if !defined $row; |
||||||
|
|
||||||
|
if($row == $event->{row} && abs($col-$event->{col}) < 2 |
||||||
|
&& join("\x00", @$cmd) eq join("\x00", $self->command_for($row,$col))) { |
||||||
|
if($self->valid_button($event)) { |
||||||
|
|
||||||
|
$self->exec_async (@$cmd); |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
1; |
||||||
|
} |
||||||
|
|
||||||
|
sub select_enter { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
$self->{view_start} = $self->view_start; |
||||||
|
$self->{pty_ev_events} = $self->pty_ev_events (urxvt::EV_NONE); |
||||||
|
$self->{cur_row} = $self->nrow - 1; |
||||||
|
|
||||||
|
$self->enable ( |
||||||
|
key_press => \&select_key_press, |
||||||
|
refresh_begin => \&select_refresh, |
||||||
|
refresh_end => \&select_refresh, |
||||||
|
); |
||||||
|
|
||||||
|
$self->{overlay} = $self->overlay (0, -1, $self->ncol, 1, urxvt::OVERLAY_RSTYLE, 0); |
||||||
|
$self->{overlay}->set (0, 0, "match-select"); |
||||||
|
} |
||||||
|
|
||||||
|
sub select_leave { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
$self->disable ("key_press", "refresh_begin", "refresh_end"); |
||||||
|
$self->pty_ev_events ($self->{pty_ev_events}); |
||||||
|
|
||||||
|
delete $self->{overlay}; |
||||||
|
delete $self->{matches}; |
||||||
|
delete $self->{id}; |
||||||
|
} |
||||||
|
|
||||||
|
sub select_search { |
||||||
|
my ($self, $dir, $row) = @_; |
||||||
|
|
||||||
|
while ($self->nrow > $row && $row >= $self->top_row) { |
||||||
|
my $line = $self->line ($row) |
||||||
|
or last; |
||||||
|
|
||||||
|
my @matches = $self->find_matches ($row); |
||||||
|
if (@matches) { |
||||||
|
@matches = sort { $a->[0] <=> $b->[0] or $a->[1] <=> $b->[1] } @matches; |
||||||
|
$self->{matches} = \@matches; |
||||||
|
$self->{cur_row} = $row; |
||||||
|
$self->{id} = $dir < 0 ? @{ $self->{matches} } - 1 : 0; |
||||||
|
$self->view_start (List::Util::min 0, $row - ($self->nrow >> 1)); |
||||||
|
$self->want_refresh; |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
$row = $dir < 0 ? $line->beg - 1 : $line->end + 1; |
||||||
|
} |
||||||
|
|
||||||
|
$self->scr_bell; |
||||||
|
} |
||||||
|
|
||||||
|
sub select_refresh { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
return unless $self->{matches}; |
||||||
|
|
||||||
|
my $cur = $self->{matches}[$self->{id}]; |
||||||
|
$self->scr_xor_span (@$cur[0 .. 3], urxvt::RS_RVid); |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub select_key_press { |
||||||
|
my ($self, $event, $keysym, $string) = @_; |
||||||
|
|
||||||
|
if ($keysym == 0xff0d || $keysym == 0xff8d) { # enter |
||||||
|
if ($self->{matches}) { |
||||||
|
my @match = @{ $self->{matches}[$self->{id}] }; |
||||||
|
$self->exec_async (@match[5 .. $#match]); |
||||||
|
} |
||||||
|
$self->select_leave; |
||||||
|
} elsif ($keysym == 0x79) { # y |
||||||
|
if ($self->{matches}) { |
||||||
|
$self->selection ($self->{matches}[$self->{id}][4], 1); |
||||||
|
$self->selection_grab (urxvt::CurrentTime, 1); |
||||||
|
} |
||||||
|
$self->select_leave; |
||||||
|
} elsif ($keysym == 0xff1b) { # escape |
||||||
|
$self->view_start ($self->{view_start}); |
||||||
|
$self->select_leave; |
||||||
|
} elsif ($keysym == 0xff50) { # home |
||||||
|
$self->select_search (+1, $self->top_row) |
||||||
|
} elsif ($keysym == 0xff57) { # end |
||||||
|
$self->select_search (-1, $self->nrow - 1) |
||||||
|
} elsif ($keysym == 0xff52) { # up |
||||||
|
if ($self->{id} > 0) { |
||||||
|
$self->{id}--; |
||||||
|
$self->want_refresh; |
||||||
|
} else { |
||||||
|
my $line = $self->line ($self->{cur_row}); |
||||||
|
$self->select_search (-1, $line->beg - 1) |
||||||
|
if $line->beg > $self->top_row; |
||||||
|
} |
||||||
|
} elsif ($keysym == 0xff54) { # down |
||||||
|
if ($self->{id} < @{ $self->{matches} } - 1) { |
||||||
|
$self->{id}++; |
||||||
|
$self->want_refresh; |
||||||
|
} else { |
||||||
|
my $line = $self->line ($self->{cur_row}); |
||||||
|
$self->select_search (+1, $line->end + 1) |
||||||
|
if $line->end < $self->nrow; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
1 |
||||||
|
} |
||||||
|
|
||||||
|
# vim:set sw=3 sts=3 et: |
@ -0,0 +1,84 @@ |
|||||||
|
#! perl |
||||||
|
|
||||||
|
=head1 NAME |
||||||
|
|
||||||
|
option-popup - option menu (enabled by default) |
||||||
|
|
||||||
|
=head1 DESCRIPTION |
||||||
|
|
||||||
|
Binds a popup menu to Ctrl-Button2 that lets you toggle (some) options at |
||||||
|
runtime. |
||||||
|
|
||||||
|
Other extensions can extend this popup menu by pushing a code reference |
||||||
|
onto C<< @{ $term->{option_popup_hook} } >>, which gets called whenever |
||||||
|
the popup is being displayed. |
||||||
|
|
||||||
|
Its sole argument is the popup menu, which can be modified. It should |
||||||
|
either return nothing or a string, the initial boolean value and a code |
||||||
|
reference. The string will be used as button text and the code reference |
||||||
|
will be called when the toggle changes, with the new boolean value as |
||||||
|
first argument. |
||||||
|
|
||||||
|
The following will add an entry C<myoption> that changes |
||||||
|
C<< $self->{myoption} >>: |
||||||
|
|
||||||
|
push @{ $self->{term}{option_popup_hook} }, sub { |
||||||
|
("my option" => $myoption, sub { $self->{myoption} = $_[0] }) |
||||||
|
}; |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
sub on_start { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
$self->grab_button (2, urxvt::ControlMask); |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_button_press { |
||||||
|
my ($self, $event) = @_; |
||||||
|
|
||||||
|
if ($event->{button} == 2 && $event->{state} & urxvt::ControlMask) { |
||||||
|
my $popup = $self->popup ($event) |
||||||
|
or return 1; |
||||||
|
|
||||||
|
$popup->add_title ("Options"); |
||||||
|
$popup->add_separator; |
||||||
|
|
||||||
|
my %unsafe = map +($_ => 1), |
||||||
|
qw(borderLess console iconic loginShell reverseVideo |
||||||
|
scrollBar scrollBar_floating scrollBar_right |
||||||
|
secondaryScreen transparent utmpInhibit meta8 |
||||||
|
override_redirect); |
||||||
|
|
||||||
|
for my $name (sort keys %urxvt::OPTION) { |
||||||
|
next if $unsafe{$name}; |
||||||
|
|
||||||
|
my $optval = $urxvt::OPTION{$name}; |
||||||
|
|
||||||
|
$popup->add_toggle ($name => $self->option ($optval), |
||||||
|
sub { $self->option ($optval, $_[0]) }); |
||||||
|
} |
||||||
|
|
||||||
|
for my $hook (@{ $self->{term}{option_popup_hook} || [] }) { |
||||||
|
if (my ($name, $value, $cb) = $hook->($popup)) { |
||||||
|
$popup->add_toggle ($name => $value, sub { $cb->($_[0]) }); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
{ |
||||||
|
$popup->add_separator; |
||||||
|
my $locale = $self->locale; |
||||||
|
$locale =~ y/\x20-\x7e//cd; |
||||||
|
$popup->add_title ("Locale: $locale"); |
||||||
|
} |
||||||
|
|
||||||
|
$popup->show; |
||||||
|
|
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
@ -0,0 +1,73 @@ |
|||||||
|
#! perl |
||||||
|
|
||||||
|
=head1 NAME |
||||||
|
|
||||||
|
overlay-osc - implement OSC to manage overlays |
||||||
|
|
||||||
|
=head1 DESCRIPTION |
||||||
|
|
||||||
|
This extension implements some OSC commands to display timed popups on the |
||||||
|
screen - useful for status displays from within scripts. You have to read |
||||||
|
the sources for more info. |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
# allows programs to open popups |
||||||
|
# printf "\033]777;overlay;action;args\007" |
||||||
|
# |
||||||
|
# action "simple;<id>;<timeout>;<x>;<y>;<h|t>;<text>" |
||||||
|
# printf "\033]777;overlay;simple;ov1;5;0;0;t;test\007" |
||||||
|
# |
||||||
|
|
||||||
|
# action "timeout;<id>;<seconds>" |
||||||
|
# printf "\033]777;overlay;timeout;ov1;6\007" |
||||||
|
|
||||||
|
# action "destroy;<id>" |
||||||
|
# printf "\033]777;overlay;destroy;ov1\007" |
||||||
|
|
||||||
|
# TODO: |
||||||
|
## action "complex;<id>;<timeout>;<x>;<y>;<width>;<height>;<rstyle>;<border>" |
||||||
|
## action "set;<id>;<x>;<y>;<h|t>;<hextext>;<rendition...>" |
||||||
|
|
||||||
|
sub on_osc_seq_perl { |
||||||
|
my ($self, $osc, $resp) = @_; |
||||||
|
|
||||||
|
return unless $osc =~ s/^overlay;//; |
||||||
|
|
||||||
|
$osc =~ s/^([^;]+)+;// |
||||||
|
or return; |
||||||
|
|
||||||
|
if ($1 eq "timeout") { |
||||||
|
my ($id, $to) = split /;/, $osc, 2; |
||||||
|
my $ov = $self->{ov}{$id} |
||||||
|
or return; |
||||||
|
if (length $to) { |
||||||
|
$ov->{to}->start (urxvt::NOW + $to); |
||||||
|
} else { |
||||||
|
delete $ov->{to}; |
||||||
|
} |
||||||
|
|
||||||
|
} elsif ($1 eq "simple") { |
||||||
|
my ($id, $to, $x, $y, $t, $txt) = split /;/, $osc, 6; |
||||||
|
if ($t eq "h") { |
||||||
|
$txt = pack "H*", $txt; |
||||||
|
utf8::decode $txt; |
||||||
|
} |
||||||
|
$self->{ov}{$id} = { |
||||||
|
ov => $self->overlay_simple ($x, $y, $txt), |
||||||
|
to => urxvt::timer |
||||||
|
->new |
||||||
|
->start (urxvt::NOW + $to) |
||||||
|
->cb(sub { |
||||||
|
delete $self->{ov}{$id}; |
||||||
|
}), |
||||||
|
}; |
||||||
|
|
||||||
|
} elsif ($1 eq "destroy") { |
||||||
|
delete $self->{ov}{$osc}; |
||||||
|
} |
||||||
|
|
||||||
|
1 |
||||||
|
} |
||||||
|
|
||||||
|
|
@ -0,0 +1,92 @@ |
|||||||
|
#! perl |
||||||
|
|
||||||
|
=head1 NAME |
||||||
|
|
||||||
|
readline - improve readline editing (enabled by default) |
||||||
|
|
||||||
|
=head1 DESCRIPTION |
||||||
|
|
||||||
|
A support package that tries to make editing with readline easier. At |
||||||
|
the moment, it reacts to clicking shift-left mouse button by trying to |
||||||
|
move the text cursor to this position. It does so by generating as many |
||||||
|
cursor-left or cursor-right keypresses as required (this only works |
||||||
|
for programs that correctly support wide characters). |
||||||
|
|
||||||
|
To avoid too many false positives, this is only done when: |
||||||
|
|
||||||
|
=over 4 |
||||||
|
|
||||||
|
=item - the tty is in ICANON state. |
||||||
|
|
||||||
|
=item - the text cursor is visible. |
||||||
|
|
||||||
|
=item - the primary screen is currently being displayed. |
||||||
|
|
||||||
|
=item - the mouse is on the same (multi-row-) line as the text cursor. |
||||||
|
|
||||||
|
=back |
||||||
|
|
||||||
|
The normal selection mechanism isn't disabled, so quick successive clicks |
||||||
|
might interfere with selection creation in harmless ways. |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
use POSIX (); |
||||||
|
|
||||||
|
my $termios = new POSIX::Termios; |
||||||
|
|
||||||
|
sub on_init { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
$self->{enabled} = 1; |
||||||
|
|
||||||
|
push @{ $self->{term}{option_popup_hook} }, sub { |
||||||
|
("readline" => $self->{enabled}, sub { $self->{enabled} = shift }) |
||||||
|
}; |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_button_press { |
||||||
|
my ($self, $event) = @_; |
||||||
|
|
||||||
|
$self->current_screen || $self->hidden_cursor || !$self->{enabled} |
||||||
|
and return; |
||||||
|
|
||||||
|
my $mask = $self->ModLevel3Mask | $self->ModMetaMask |
||||||
|
| urxvt::ShiftMask | urxvt::ControlMask; |
||||||
|
|
||||||
|
($event->{state} & $mask) == urxvt::ShiftMask |
||||||
|
or return; |
||||||
|
|
||||||
|
$termios->getattr ($self->pty_fd) |
||||||
|
or return; |
||||||
|
|
||||||
|
$termios->getlflag & &POSIX::ICANON |
||||||
|
and return; |
||||||
|
|
||||||
|
my ($row, $col) = $self->screen_cur; |
||||||
|
my $line = $self->line ($row); |
||||||
|
my $cur = $line->offset_of ($row, $col); |
||||||
|
my $ofs = $line->offset_of ($event->{row}, $event->{col}); |
||||||
|
|
||||||
|
$ofs >= 0 && $ofs < $line->l |
||||||
|
or return; |
||||||
|
|
||||||
|
my $diff = $ofs - $cur; |
||||||
|
my $move; |
||||||
|
|
||||||
|
if ($diff < 0) { |
||||||
|
($ofs, $cur) = ($cur, $ofs); |
||||||
|
$move = "\x1b[D"; |
||||||
|
} else { |
||||||
|
$move = "\x1b[C"; |
||||||
|
} |
||||||
|
|
||||||
|
my $skipped = substr $line->t, $cur, $ofs - $cur; |
||||||
|
$skipped =~ s/\x{ffff}//g; |
||||||
|
|
||||||
|
$self->tt_write ($move x length $skipped); |
||||||
|
|
||||||
|
1 |
||||||
|
} |
@ -0,0 +1,132 @@ |
|||||||
|
#! perl |
||||||
|
|
||||||
|
#:META:RESOURCE:%.store:string:the command used to store the selection |
||||||
|
#:META:RESOURCE:%.fetch:string:the command used to fetch the selection |
||||||
|
|
||||||
|
=head1 NAME |
||||||
|
|
||||||
|
remote-clipboard - manage a shared and possibly remote clipboard |
||||||
|
|
||||||
|
=head1 DESCRIPTION |
||||||
|
|
||||||
|
Somewhat of a misnomer, this extension adds two menu entries to the |
||||||
|
selection popup that allows one to run external commands to store the |
||||||
|
selection somewhere and fetch it again. |
||||||
|
|
||||||
|
We use it to implement a "distributed selection mechanism", which just |
||||||
|
means that one command uploads the file to a remote server, and another |
||||||
|
reads it. |
||||||
|
|
||||||
|
The commands can be set using the C<URxvt.remote-selection.store> and |
||||||
|
C<URxvt.remote-selection.fetch> resources. The first should read the |
||||||
|
selection to store from STDIN (always in UTF-8), the second should provide |
||||||
|
the selection data on STDOUT (also in UTF-8). |
||||||
|
|
||||||
|
The defaults (which are likely useless to you) use rsh and cat: |
||||||
|
|
||||||
|
URxvt.remote-selection.store: rsh ruth 'cat >/tmp/distributed-selection' |
||||||
|
URxvt.remote-selection.fetch: rsh ruth 'cat /tmp/distributed-selection' |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
use Fcntl (); |
||||||
|
|
||||||
|
sub msg { |
||||||
|
my ($self, $msg) = @_; |
||||||
|
|
||||||
|
my $ov = $self->overlay (-1, 0, $self->strwidth ($msg), 1, urxvt::OVERLAY_RSTYLE, 0); |
||||||
|
$ov->set (0, 0, $msg); |
||||||
|
|
||||||
|
$self->{msg} = |
||||||
|
urxvt::timer |
||||||
|
->new |
||||||
|
->after (5) |
||||||
|
->cb (sub { delete $self->{msg}; undef $ov; }); |
||||||
|
} |
||||||
|
|
||||||
|
sub wait_pipe { |
||||||
|
my ($self, $fh, $pid, $msg) = @_; |
||||||
|
|
||||||
|
$self->msg ("waiting for selection process to finish..."); |
||||||
|
|
||||||
|
my $wait_pipe; $wait_pipe = urxvt::pw->new->start ($pid)->cb (sub { |
||||||
|
my ($undef, $status) = @_; |
||||||
|
undef $wait_pipe; |
||||||
|
close $fh; |
||||||
|
$status >>= 8; |
||||||
|
$self->msg ("$msg (status $status)"); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
sub store { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
my $txt = $self->selection; |
||||||
|
|
||||||
|
local %ENV = %{ $self->env }; |
||||||
|
if (my $pid = open my $fh, "|-:utf8", $self->{store_cmd}) { |
||||||
|
fcntl $fh, &Fcntl::F_SETFL, &Fcntl::O_NONBLOCK; |
||||||
|
$self->{iow} = urxvt::iow |
||||||
|
->new |
||||||
|
->fd (fileno $fh) |
||||||
|
->events (urxvt::EV_WRITE) |
||||||
|
->start |
||||||
|
->cb (sub { |
||||||
|
if (my $len = syswrite $fh, $txt) { |
||||||
|
substr $txt, 0, $len, ""; |
||||||
|
$self->msg ((length $txt) . " chars to go..."); |
||||||
|
} else { |
||||||
|
delete $self->{iow}; |
||||||
|
$self->wait_pipe ($fh, $pid, "selection stored"); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
sub fetch { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
my $txt; |
||||||
|
|
||||||
|
local %ENV = %{ $self->env }; |
||||||
|
if (my $pid = open my $fh, "-|:utf8", $self->{fetch_cmd}) { |
||||||
|
fcntl $fh, &Fcntl::F_SETFL, &Fcntl::O_NONBLOCK; |
||||||
|
$self->{iow} = urxvt::iow |
||||||
|
->new |
||||||
|
->fd (fileno $fh) |
||||||
|
->events (urxvt::EV_READ) |
||||||
|
->start |
||||||
|
->cb (sub { |
||||||
|
if (my $len = sysread $fh, $txt, 8192, length $txt) { |
||||||
|
$self->msg ((length $txt) . " chars read..."); |
||||||
|
} else { |
||||||
|
delete $self->{iow}; |
||||||
|
$self->selection_clear; |
||||||
|
$self->selection ($txt); |
||||||
|
$self->selection_grab (urxvt::CurrentTime); |
||||||
|
$self->msg ("selection fetched"); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
sub on_start { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
$self->{store_cmd} = $self->x_resource ("%.store") |
||||||
|
|| "rsh ruth 'cat >/tmp/distributed-selection'"; |
||||||
|
|
||||||
|
$self->{fetch_cmd} = $self->x_resource ("%.fetch") |
||||||
|
|| "rsh ruth 'cat /tmp/distributed-selection'"; |
||||||
|
|
||||||
|
push @{ $self->{term}{selection_popup_hook} }, sub { |
||||||
|
("selection => remote" => sub { $self->store }) |
||||||
|
}; |
||||||
|
push @{ $self->{term}{selection_popup_hook} }, sub { |
||||||
|
("remote => selection" => sub { $self->fetch }) |
||||||
|
}; |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
|
@ -0,0 +1,213 @@ |
|||||||
|
#! perl |
||||||
|
|
||||||
|
# this extension implements scrollback buffer search |
||||||
|
|
||||||
|
#:META:RESOURCE:%:string:activation hotkey keysym |
||||||
|
|
||||||
|
=head1 NAME |
||||||
|
|
||||||
|
searchable-scrollback - incremental scrollback search (enabled by default) |
||||||
|
|
||||||
|
=head1 DESCRIPTION |
||||||
|
|
||||||
|
Adds regex search functionality to the scrollback buffer, triggered by |
||||||
|
the C<searchable-scrollback:start> action (bound to C<M-s> by |
||||||
|
default). While in search mode, normal terminal input/output is |
||||||
|
suspended and a regex is displayed at the bottom of the screen. |
||||||
|
|
||||||
|
Inputting characters appends them to the regex and continues incremental |
||||||
|
search. C<BackSpace> removes a character from the regex, C<Up> and C<Down> |
||||||
|
search upwards/downwards in the scrollback buffer, C<End> jumps to the |
||||||
|
bottom. C<Escape> leaves search mode and returns to the point where search |
||||||
|
was started, while C<Enter> or C<Return> stay at the current position and |
||||||
|
additionally stores the first match in the current line into the primary |
||||||
|
selection if the C<Shift> modifier is active. |
||||||
|
|
||||||
|
The regex defaults to "(?i)", resulting in a case-insensitive search. To |
||||||
|
get a case-sensitive search you can delete this prefix using C<BackSpace> |
||||||
|
or simply use an uppercase character which removes the "(?i)" prefix. |
||||||
|
|
||||||
|
See L<perlre> for more info about perl regular expression syntax. |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
sub on_init { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
# only for backwards compatibility |
||||||
|
my $hotkey = $self->{argv}[0] |
||||||
|
|| $self->x_resource ("%") |
||||||
|
|| "M-s"; |
||||||
|
|
||||||
|
$self->bind_action ($hotkey, "%:start") |
||||||
|
or warn "unable to register '$hotkey' as scrollback search start hotkey\n"; |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_user_command { |
||||||
|
my ($self, $cmd) = @_; |
||||||
|
|
||||||
|
$cmd eq "searchable-scrollback:start" |
||||||
|
and $self->enter; |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_action { |
||||||
|
my ($self, $action) = @_; |
||||||
|
|
||||||
|
$action eq "start" |
||||||
|
and $self->enter; |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub msg { |
||||||
|
my ($self, $msg) = @_; |
||||||
|
|
||||||
|
$self->{overlay} = $self->overlay (0, -1, $self->ncol, 1, urxvt::OVERLAY_RSTYLE, 0); |
||||||
|
$self->{overlay}->set (0, 0, $self->special_encode ($msg)); |
||||||
|
} |
||||||
|
|
||||||
|
sub enter { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
return if $self->{overlay}; |
||||||
|
|
||||||
|
$self->{view_start} = $self->view_start; |
||||||
|
$self->{pty_ev_events} = $self->pty_ev_events (urxvt::EV_NONE); |
||||||
|
$self->{row} = $self->nrow - 1; |
||||||
|
$self->{search} = "(?i)"; |
||||||
|
|
||||||
|
$self->enable ( |
||||||
|
key_press => \&key_press, |
||||||
|
tt_write => \&tt_write, |
||||||
|
refresh_begin => \&refresh, |
||||||
|
refresh_end => \&refresh, |
||||||
|
); |
||||||
|
|
||||||
|
$self->{manpage_overlay} = $self->overlay (0, -2, $self->ncol, 1, urxvt::OVERLAY_RSTYLE, 0); |
||||||
|
$self->{manpage_overlay}->set (0, 0, "scrollback search, see the ${urxvt::RXVTNAME}perl manpage for details"); |
||||||
|
|
||||||
|
$self->idle; |
||||||
|
} |
||||||
|
|
||||||
|
sub leave { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
$self->disable ("key_press", "tt_write", "refresh_begin", "refresh_end"); |
||||||
|
$self->pty_ev_events ($self->{pty_ev_events}); |
||||||
|
|
||||||
|
delete $self->{manpage_overlay}; |
||||||
|
delete $self->{overlay}; |
||||||
|
delete $self->{search}; |
||||||
|
delete $self->{found}; |
||||||
|
} |
||||||
|
|
||||||
|
sub idle { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
$self->msg ("(escape cancels) /$self->{search}█"); |
||||||
|
} |
||||||
|
|
||||||
|
sub search { |
||||||
|
my ($self, $dir, $row) = @_; |
||||||
|
|
||||||
|
my $search = $self->special_encode ($self->{search}); |
||||||
|
|
||||||
|
no re 'eval'; # just to be sure |
||||||
|
if (my $re = eval { qr/$search/ }) { |
||||||
|
while ($self->nrow > $row && $row >= $self->top_row) { |
||||||
|
my $line = $self->line ($row) |
||||||
|
or last; |
||||||
|
|
||||||
|
my $text = $line->t; |
||||||
|
if ($text =~ /$re/g) { |
||||||
|
delete $self->{found}; |
||||||
|
|
||||||
|
do { |
||||||
|
push @{ $self->{found} }, [$line->coord_of ($-[0]), $line->coord_of ($+[0])]; |
||||||
|
} while $text =~ /$re/g; |
||||||
|
|
||||||
|
$self->{row} = $row; |
||||||
|
$self->view_start (List::Util::min 0, $row - ($self->nrow >> 1)); |
||||||
|
$self->want_refresh; |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
$row = $dir < 0 ? $line->beg - 1 : $line->end + 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$self->scr_bell; |
||||||
|
} |
||||||
|
|
||||||
|
sub refresh { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
return unless $self->{found}; |
||||||
|
|
||||||
|
my $xor = urxvt::RS_RVid | urxvt::RS_Blink; |
||||||
|
for (@{ $self->{found} }) { |
||||||
|
$self->scr_xor_span (@$_, $xor); |
||||||
|
$xor = urxvt::RS_RVid; |
||||||
|
} |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub key_press { |
||||||
|
my ($self, $event, $keysym, $string) = @_; |
||||||
|
|
||||||
|
delete $self->{manpage_overlay}; |
||||||
|
|
||||||
|
if ($keysym == 0xff0d || $keysym == 0xff8d) { # enter |
||||||
|
if ($self->{found} && $event->{state} & urxvt::ShiftMask) { |
||||||
|
my ($br, $bc, $er, $ec) = @{ $self->{found}[0] }; |
||||||
|
$self->selection_beg ($br, $bc); |
||||||
|
$self->selection_end ($er, $ec); |
||||||
|
$self->selection_make ($event->{time}); |
||||||
|
} |
||||||
|
$self->leave; |
||||||
|
} elsif ($keysym == 0xff1b) { # escape |
||||||
|
$self->view_start ($self->{view_start}); |
||||||
|
$self->leave; |
||||||
|
} elsif ($keysym == 0xff57) { # end |
||||||
|
$self->{row} = $self->nrow - 1; |
||||||
|
$self->view_start (0); |
||||||
|
} elsif ($keysym == 0xff52) { # up |
||||||
|
my $line = $self->line ($self->{row}); |
||||||
|
$self->search (-1, $line->beg - 1) |
||||||
|
if $line->beg > $self->top_row; |
||||||
|
} elsif ($keysym == 0xff54) { # down |
||||||
|
my $line = $self->line ($self->{row}); |
||||||
|
$self->search (+1, $line->end + 1) |
||||||
|
if $line->end < $self->nrow; |
||||||
|
} elsif ($keysym == 0xff08) { # backspace |
||||||
|
substr $self->{search}, -1, 1, ""; |
||||||
|
$self->search (+1, $self->{row}); |
||||||
|
$self->idle; |
||||||
|
} elsif ($string !~ /[\x00-\x1f\x80-\xaf]/) { |
||||||
|
return; # pass to tt_write |
||||||
|
} |
||||||
|
|
||||||
|
1 |
||||||
|
} |
||||||
|
|
||||||
|
sub tt_write { |
||||||
|
my ($self, $data) = @_; |
||||||
|
|
||||||
|
$self->{search} .= $self->locale_decode ($data); |
||||||
|
|
||||||
|
$self->{search} =~ s/^\(\?i\)// |
||||||
|
if $self->{search} =~ /^\(.*[[:upper:]]/; |
||||||
|
|
||||||
|
delete $self->{found}; |
||||||
|
$self->search (-1, $self->{row}); |
||||||
|
$self->idle; |
||||||
|
|
||||||
|
1 |
||||||
|
} |
||||||
|
|
||||||
|
|
@ -0,0 +1,196 @@ |
|||||||
|
#! perl |
||||||
|
|
||||||
|
#:META:RESOURCE:%.pattern-0:string:first selection pattern |
||||||
|
|
||||||
|
=head1 NAME |
||||||
|
|
||||||
|
selection - more intelligent selection (enabled by default) |
||||||
|
|
||||||
|
=head1 DESCRIPTION |
||||||
|
|
||||||
|
This extension tries to be more intelligent when the user extends |
||||||
|
selections (double-click and further clicks). Right now, it tries to |
||||||
|
select words, urls and complete shell-quoted arguments, which is very |
||||||
|
convenient, too, if your F<ls> supports C<--quoting-style=shell>. |
||||||
|
|
||||||
|
A double-click usually selects the word under the cursor, further clicks |
||||||
|
will enlarge the selection. |
||||||
|
|
||||||
|
The selection works by trying to match a number of regexes and displaying |
||||||
|
them in increasing order of length. You can add your own regexes by |
||||||
|
specifying resources of the form: |
||||||
|
|
||||||
|
URxvt.selection.pattern-0: perl-regex |
||||||
|
URxvt.selection.pattern-1: perl-regex |
||||||
|
... |
||||||
|
|
||||||
|
The index number (0, 1...) must not have any holes, and each regex must |
||||||
|
contain at least one pair of capturing parentheses, which will be used for |
||||||
|
the match. For example, the following adds a regex that matches everything |
||||||
|
between two vertical bars: |
||||||
|
|
||||||
|
URxvt.selection.pattern-0: \\|([^|]+)\\| |
||||||
|
|
||||||
|
Another example: Programs I use often output "absolute path: " at the |
||||||
|
beginning of a line when they process multiple files. The following |
||||||
|
pattern matches the filename (note, there is a single space at the very |
||||||
|
end): |
||||||
|
|
||||||
|
URxvt.selection.pattern-0: ^(/[^:]+):\ |
||||||
|
|
||||||
|
You can look at the source of the selection extension to see more |
||||||
|
interesting uses, such as parsing a line from beginning to end. |
||||||
|
|
||||||
|
This extension also offers the following actions: |
||||||
|
|
||||||
|
=over 4 |
||||||
|
|
||||||
|
=item rot13 |
||||||
|
|
||||||
|
Rot-13 the selection when activated. |
||||||
|
|
||||||
|
Example: |
||||||
|
|
||||||
|
URxvt.keysym.C-M-r: selection:rot13 |
||||||
|
|
||||||
|
=back |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
sub on_user_command { |
||||||
|
my ($self, $cmd) = @_; |
||||||
|
|
||||||
|
$cmd eq "selection:rot13" |
||||||
|
and $self->selection (map { y/A-Za-z/N-ZA-Mn-za-m/; $_ } $self->selection); |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_action { |
||||||
|
my ($self, $action) = @_; |
||||||
|
|
||||||
|
$action eq "rot13" |
||||||
|
and $self->selection (map { y/A-Za-z/N-ZA-Mn-za-m/; $_ } $self->selection); |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_init { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
if (defined (my $res = $self->resource ("cutchars"))) { |
||||||
|
$res = $self->locale_decode ($res); |
||||||
|
push @{ $self->{patterns} }, qr{\G [\Q$res\E[:space:]]* ([^\Q$res\E[:space:]]+) }x; |
||||||
|
} |
||||||
|
|
||||||
|
for (my $idx = 0; defined (my $res = $self->x_resource ("selection.pattern-$idx")); $idx++) { |
||||||
|
$res = $self->locale_decode ($res); |
||||||
|
push @{ $self->{patterns} }, qr/$res/; |
||||||
|
} |
||||||
|
|
||||||
|
$self->{enabled} = 1; |
||||||
|
|
||||||
|
push @{ $self->{term}{option_popup_hook} }, sub { |
||||||
|
("new selection" => $self->{enabled}, sub { $self->{enabled} = shift }) |
||||||
|
}; |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
# "find interesting things"-patterns |
||||||
|
my @mark_patterns = ( |
||||||
|
# qr{ ([[:word:]]+) }x, |
||||||
|
qr{ ([^[:space:]]+) }x, |
||||||
|
|
||||||
|
# common types of "parentheses" |
||||||
|
qr{ (?<![^[:space:]]) [`'] ([^`']+) [`'] (?![^[:space:]]) }x, |
||||||
|
qr{ (?<![^[:space:]]) ‘ ([^‘’]+) ’ (?![^[:space:]]) }x, |
||||||
|
qr{ (?<![^[:space:]]) “ ([^“”]+) ” (?![^[:space:]]) }x, |
||||||
|
|
||||||
|
qr{ (?<![^[:space:]]) (' [^[:space:]] [^']* ') }x, |
||||||
|
qr{ (' [^']* [^[:space:]] ') (?![^[:space:]]) }x, |
||||||
|
qr{ (?<![^[:space:]]) (` [^[:space:]] [^']* ') }x, |
||||||
|
qr{ (` [^']* [^[:space:]] ') (?![^[:space:]]) }x, |
||||||
|
qr{ (?<![^[:space:]]) (" [^[:space:]] [^"]* ") }x, |
||||||
|
qr{ (" [^"]* [^[:space:]] ") (?![^[:space:]]) }x, |
||||||
|
|
||||||
|
qr{ \{ ([^\{\}]+) \} }x, |
||||||
|
qr{ \( ([^\(\)]+) \) }x, |
||||||
|
qr{ \[ ([^\[\]]+) \] }x, |
||||||
|
qr{ \< ([^\<\>]+) \> }x, |
||||||
|
|
||||||
|
# urls, just a heuristic |
||||||
|
qr{( |
||||||
|
(?:https?://|ftp://|news://|mailto:|file://|\bwww\.)[ab-zA-Z0-9\-\@;\/?:&=%\$_.+!*\x27(),~#]+ |
||||||
|
[ab-zA-Z0-9\-\@;\/?:&=%\$_+*()~] # exclude some trailing characters (heuristic) |
||||||
|
)}x, |
||||||
|
|
||||||
|
# shell-like argument quoting, basically always matches |
||||||
|
qr{\G [\ \t|&;<>()]* ( |
||||||
|
(?: |
||||||
|
[^\\"'\ \t|&;<>()]+ |
||||||
|
| \\. |
||||||
|
| " (?: [^\\"]+ | \\. )* " |
||||||
|
| ' [^']* ' |
||||||
|
)+ |
||||||
|
)}x, |
||||||
|
); |
||||||
|
|
||||||
|
# "correct obvious? crap"-patterns |
||||||
|
my @simplify_patterns = ( |
||||||
|
qr{^"([^\\"'\ \t|&;<>()*?]+)"$}, # "simple" => simple |
||||||
|
qr{^(.*)[,\-]$}, # strip off trailing , and - |
||||||
|
); |
||||||
|
|
||||||
|
sub on_sel_extend { |
||||||
|
my ($self, $time) = @_; |
||||||
|
|
||||||
|
$self->{enabled} |
||||||
|
or return; |
||||||
|
|
||||||
|
my ($row, $col) = $self->selection_mark; |
||||||
|
my $line = $self->line ($row); |
||||||
|
my $text = $line->t; |
||||||
|
my $markofs = $line->offset_of ($row, $col); |
||||||
|
my $curlen = $line->offset_of ($self->selection_end) |
||||||
|
- $line->offset_of ($self->selection_beg); |
||||||
|
|
||||||
|
my @matches; |
||||||
|
|
||||||
|
if ($markofs < $line->l) { |
||||||
|
study $text; # _really_ helps, too :) |
||||||
|
|
||||||
|
for my $regex (@mark_patterns, @{ $self->{patterns} }) { |
||||||
|
while ($text =~ /$regex/g) { |
||||||
|
if ($-[1] <= $markofs and $markofs <= $+[1]) { |
||||||
|
my $ofs = $-[1]; |
||||||
|
my $match = $1; |
||||||
|
|
||||||
|
for my $regex (@simplify_patterns) { |
||||||
|
if ($match =~ $regex) { |
||||||
|
$match = $1; |
||||||
|
$ofs += $-[1]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
push @matches, [$ofs, length $match]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
# whole line |
||||||
|
push @matches, [0, ($line->end - $line->beg + 1) * $self->ncol]; |
||||||
|
|
||||||
|
for (sort { $a->[1] <=> $b->[1] or $b->[0] <=> $a->[0] } @matches) { |
||||||
|
my ($ofs, $len) = @$_; |
||||||
|
|
||||||
|
next if $len <= $curlen; |
||||||
|
|
||||||
|
$self->selection_beg ($line->coord_of ($ofs)); |
||||||
|
$self->selection_end ($line->coord_of ($ofs + $len)); |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
() |
||||||
|
} |
@ -0,0 +1,101 @@ |
|||||||
|
#! perl |
||||||
|
|
||||||
|
#:META:RESOURCE:%.:string:autotransform expression |
||||||
|
|
||||||
|
=head1 NAME |
||||||
|
|
||||||
|
selection-autotransform - automatically transform select text |
||||||
|
|
||||||
|
=head1 DESCRIPTION |
||||||
|
|
||||||
|
This selection allows you to do automatic transforms on a selection |
||||||
|
whenever a selection is made. |
||||||
|
|
||||||
|
It works by specifying perl snippets (most useful is a single C<s///> |
||||||
|
operator) that modify C<$_> as resources: |
||||||
|
|
||||||
|
URxvt.selection-autotransform.0: transform |
||||||
|
URxvt.selection-autotransform.1: transform |
||||||
|
... |
||||||
|
|
||||||
|
For example, the following will transform selections of the form |
||||||
|
C<filename:number>, often seen in compiler messages, into C<vi +$filename |
||||||
|
$word>: |
||||||
|
|
||||||
|
URxvt.selection-autotransform.0: s/^([^:[:space:]]+):(\\d+):?$/vi +$2 \\Q$1\\E\\x0d/ |
||||||
|
|
||||||
|
And this example matches the same,but replaces it with vi-commands you can |
||||||
|
paste directly into your (vi :) editor: |
||||||
|
|
||||||
|
URxvt.selection-autotransform.0: s/^([^:[:space:]]+(\\d+):?$/:e \\Q$1\\E\\x0d:$2\\x0d/ |
||||||
|
|
||||||
|
Of course, this can be modified to suit your needs and your editor :) |
||||||
|
|
||||||
|
To expand the example above to typical perl error messages ("XXX at |
||||||
|
FILENAME line YYY."), you need a slightly more elaborate solution: |
||||||
|
|
||||||
|
URxvt.selection.pattern-0: ( at .*? line \\d+[,.]) |
||||||
|
URxvt.selection-autotransform.0: s/^ at (.*?) line (\\d+)[,.]$/:e \\Q$1\E\\x0d:$2\\x0d/ |
||||||
|
|
||||||
|
The first line tells the selection code to treat the unchanging part of |
||||||
|
every error message as a selection pattern, and the second line transforms |
||||||
|
the message into vi commands to load the file. |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
sub msg { |
||||||
|
my ($self, $msg) = @_; |
||||||
|
|
||||||
|
my $overlay = $self->overlay (0, 0, $self->strwidth ($msg), 1); |
||||||
|
$overlay->set (0, 0, $msg); |
||||||
|
$self->{timer} = urxvt::timer->new->after (2)->cb (sub { |
||||||
|
delete $self->{timer}; |
||||||
|
undef $overlay; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
sub on_init { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
for (my $idx = 0; defined (my $res = $self->x_resource ("%.$idx")); $idx++) { |
||||||
|
$res = $self->locale_decode ($res); |
||||||
|
my $transform = eval "sub { $res }"; |
||||||
|
|
||||||
|
if ($transform) { |
||||||
|
push @{ $self->{transforms} }, $transform; |
||||||
|
} else { |
||||||
|
warn "$res: $@"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$self->{enabled} = 1; |
||||||
|
|
||||||
|
push @{ $self->{term}{option_popup_hook} }, sub { |
||||||
|
("autotransform" => $self->{enabled}, sub { $self->{enabled} = shift }) |
||||||
|
}; |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_sel_grab { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
$self->{enabled} |
||||||
|
or return; |
||||||
|
|
||||||
|
my $text = $self->selection; |
||||||
|
local $_ = $text; |
||||||
|
|
||||||
|
for my $transform (@{ $self->{transforms} }) { |
||||||
|
$transform->(); |
||||||
|
if ($text ne $_) { |
||||||
|
$self->selection ($_); |
||||||
|
s/[\x00-\x1f\x80-\x9f]/·/g; |
||||||
|
$self->msg ($self->special_encode ("auto-transformed to $_")); |
||||||
|
last; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
@ -0,0 +1,136 @@ |
|||||||
|
#! perl |
||||||
|
|
||||||
|
#:META:RESOURCE:%.cmd:string:the command to run create a new pastebin |
||||||
|
#:META:RESOURCE:%.url:string:the url template for new pastebins |
||||||
|
|
||||||
|
=head1 NAME |
||||||
|
|
||||||
|
selection-pastebin - automatic pastebin upload |
||||||
|
|
||||||
|
=head1 EXAMPLES |
||||||
|
|
||||||
|
URxvt.keysym.C-M-e: selection-pastebin:remote-pastebin |
||||||
|
|
||||||
|
=head1 DESCRIPTION |
||||||
|
|
||||||
|
This is a little rarely useful extension that uploads the selection as |
||||||
|
textfile to a remote site (or does other things). (The implementation is |
||||||
|
not currently secure for use in a multiuser environment as it writes to |
||||||
|
F</tmp> directly.). |
||||||
|
|
||||||
|
It listens to the C<selection-pastebin:remote-pastebin> action, which, |
||||||
|
when activated, runs a command with C<%> replaced by the name of the |
||||||
|
textfile. This command can be set via a resource: |
||||||
|
|
||||||
|
URxvt.selection-pastebin.cmd: rsync -apP % ruth:/var/www/www.ta-sa.org/files/txt/. |
||||||
|
|
||||||
|
And the default is likely not useful to anybody but the few people around |
||||||
|
here :) |
||||||
|
|
||||||
|
The name of the textfile is the hex encoded md5 sum of the selection, so |
||||||
|
the same content should lead to the same filename. |
||||||
|
|
||||||
|
After a successful upload the selection will be replaced by the text given |
||||||
|
in the C<selection-pastebin-url> resource (again, the % is the placeholder |
||||||
|
for the filename): |
||||||
|
|
||||||
|
URxvt.selection-pastebin.url: http://www.ta-sa.org/files/txt/% |
||||||
|
|
||||||
|
I<Note to xrdb users:> xrdb uses the C preprocessor, which might interpret |
||||||
|
the double C</> characters as comment start. Use C<\057\057> instead, |
||||||
|
which works regardless of whether xrdb is used to parse the resource file |
||||||
|
or not. |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
sub upload_paste { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
require Digest::MD5; |
||||||
|
|
||||||
|
my $txt = $self->selection; |
||||||
|
|
||||||
|
my $filename = $txt; |
||||||
|
utf8::encode $filename; |
||||||
|
$filename = Digest::MD5::md5_hex ($filename) . ".txt"; |
||||||
|
|
||||||
|
my $tmpfile = "/tmp/$filename"; |
||||||
|
|
||||||
|
my $msg = "uploaded as $filename"; |
||||||
|
|
||||||
|
if (open my $o, ">:utf8", $tmpfile) { |
||||||
|
chmod 0644, $tmpfile; |
||||||
|
print $o $txt; |
||||||
|
close $o; |
||||||
|
} else { |
||||||
|
$msg = "couldn't write $tmpfile: $!"; |
||||||
|
} |
||||||
|
|
||||||
|
my $cmd = $self->{pastebin_cmd}; |
||||||
|
$cmd =~ s/%/$tmpfile/; |
||||||
|
|
||||||
|
my $pid = $self->exec_async ($cmd); |
||||||
|
|
||||||
|
$self->{pw} = urxvt::pw->new->start ($pid)->cb (sub { |
||||||
|
my (undef, $status) = @_; |
||||||
|
|
||||||
|
delete $self->{pw}; |
||||||
|
|
||||||
|
if ($status) { |
||||||
|
$status >>= 8; |
||||||
|
$msg = "ERROR: command returned status $status"; |
||||||
|
} else { |
||||||
|
my $url = $self->{pastebin_url}; |
||||||
|
$url =~ s/%/$filename/; |
||||||
|
|
||||||
|
$self->selection ($url); |
||||||
|
} |
||||||
|
|
||||||
|
unlink $tmpfile; |
||||||
|
|
||||||
|
my $ov = $self->overlay (-1, 0, $self->strwidth ($msg), 1, urxvt::OVERLAY_RSTYLE, 0); |
||||||
|
$ov->set (0, 0, $msg); |
||||||
|
|
||||||
|
$self->{timer} = |
||||||
|
urxvt::timer |
||||||
|
->new |
||||||
|
->after (5) |
||||||
|
->cb (sub { delete $self->{timer}; undef $ov; }); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
sub on_start { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
$self->{pastebin_cmd} = $self->x_resource ("%.cmd") |
||||||
|
|| "rcp -p % ruth:/var/www/www.ta-sa.org/files/txt/"; |
||||||
|
|
||||||
|
$self->{pastebin_url} = $self->x_resource ("%.url") |
||||||
|
|| "http://www.ta-sa.org/files/txt/%"; |
||||||
|
|
||||||
|
push @{ $self->{term}{selection_popup_hook} }, sub { |
||||||
|
("pastebin upload" => sub { $self->upload_paste }) |
||||||
|
}; |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_user_command { |
||||||
|
my ($self, $cmd) = @_; |
||||||
|
|
||||||
|
if ($cmd eq "selection-pastebin:remote-pastebin") { |
||||||
|
$self->upload_paste; |
||||||
|
} |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_action { |
||||||
|
my ($self, $action) = @_; |
||||||
|
|
||||||
|
$action eq "remote-pastebin" |
||||||
|
and $self->upload_paste; |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
@ -0,0 +1,147 @@ |
|||||||
|
#! perl |
||||||
|
|
||||||
|
#:META:RESOURCE:url-launcher:string:shell command to use |
||||||
|
|
||||||
|
=head1 NAME |
||||||
|
|
||||||
|
selection-popup (enabled by default) |
||||||
|
|
||||||
|
=head1 DESCRIPTION |
||||||
|
|
||||||
|
Binds a popup menu to Ctrl-Button3 that lets you paste the X |
||||||
|
selections and either modify or use the internal selection text in |
||||||
|
various ways (such as uri unescaping, perl evaluation, web-browser |
||||||
|
starting etc.), depending on content. |
||||||
|
|
||||||
|
Other extensions can extend this popup menu by pushing a code reference |
||||||
|
onto C<< @{ $term->{selection_popup_hook} } >>, which gets called whenever |
||||||
|
the popup is being displayed. |
||||||
|
|
||||||
|
Its sole argument is the popup menu, which can be modified. The selection |
||||||
|
is in C<$_>, which can be used to decide whether to add something or not. |
||||||
|
It should either return nothing or a string and a code reference. The |
||||||
|
string will be used as button text and the code reference will be called |
||||||
|
when the button gets activated and should transform C<$_>. |
||||||
|
|
||||||
|
The following will add an entry C<a to b> that transforms all C<a>s in |
||||||
|
the selection to C<b>s, but only if the selection currently contains any |
||||||
|
C<a>s: |
||||||
|
|
||||||
|
push @{ $self->{term}{selection_popup_hook} }, sub { |
||||||
|
/a/ ? ("a to b" => sub { s/a/b/g } |
||||||
|
: () |
||||||
|
}; |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
sub msg { |
||||||
|
my ($self, $msg) = @_; |
||||||
|
|
||||||
|
my $overlay = $self->overlay (0, 0, $self->strwidth ($msg), 1); |
||||||
|
$overlay->set (0, 0, $msg); |
||||||
|
$self->{timer} = urxvt::timer->new->after (1)->cb (sub { |
||||||
|
delete $self->{timer}; |
||||||
|
undef $overlay; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
sub on_start { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
$self->{browser} = $self->x_resource ("url-launcher") || "sensible-browser"; |
||||||
|
|
||||||
|
$self->grab_button (3, urxvt::ControlMask); |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_button_press { |
||||||
|
my ($self, $event) = @_; |
||||||
|
|
||||||
|
if ($event->{button} == 3 && $event->{state} & urxvt::ControlMask) { |
||||||
|
my $popup = $self->popup ($event) |
||||||
|
or return 1; |
||||||
|
|
||||||
|
$popup->add_title ("Selection"); |
||||||
|
|
||||||
|
my $text = $self->selection; |
||||||
|
|
||||||
|
my $title = $text; |
||||||
|
$title =~ s/[\x00-\x1f\x80-\x9f]/·/g; |
||||||
|
substr $title, 40, -1, "..." if 40 < length $title; |
||||||
|
$popup->add_title ($title); |
||||||
|
$popup->add_separator; |
||||||
|
|
||||||
|
my $add_button = sub { |
||||||
|
my ($title, $cb) = @_; |
||||||
|
|
||||||
|
$popup->add_button ($title => sub { |
||||||
|
for ($text) { |
||||||
|
my $orig = $_; |
||||||
|
$cb->(); |
||||||
|
|
||||||
|
if ($orig ne $_) { |
||||||
|
$self->selection ($_); |
||||||
|
s/[\x00-\x1f\x80-\x9f]/·/g; |
||||||
|
$self->msg ($self->special_encode ($_)); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
for ($text) { |
||||||
|
/\n/ |
||||||
|
and $add_button->("paste primary selection" => sub { $self->selection_request (urxvt::CurrentTime, 1) }); |
||||||
|
|
||||||
|
/./ |
||||||
|
and $add_button->("paste clipboard selection" => sub { $self->selection_request (urxvt::CurrentTime, 3) }); |
||||||
|
|
||||||
|
/./ |
||||||
|
and $add_button->("copy selection to clipboard" => sub { $self->selection ($self->selection, 1); |
||||||
|
$self->selection_grab (urxvt::CurrentTime, 1) }); |
||||||
|
|
||||||
|
/./ |
||||||
|
and $add_button->("newlines to spaces" => sub { y/\n/ / }); |
||||||
|
|
||||||
|
/./ |
||||||
|
and $add_button->("rot13" => sub { y/A-Za-z/N-ZA-Mn-za-m/ }); |
||||||
|
|
||||||
|
/./ |
||||||
|
and $add_button->("eval perl expression" => sub { my $self = $self; no warnings; $_ = eval $_; $_ = "$@" if $@ }); |
||||||
|
|
||||||
|
/./ |
||||||
|
and $add_button->((sprintf "to unicode hex index (%x)", ord) => sub { $_ = sprintf "%x", ord }); |
||||||
|
|
||||||
|
/(\S+):(\d+):?/ |
||||||
|
and $add_button->("vi-commands to load '$1'" => sub { s/^(\S+):(\d+):?$/\x1b:e $1\x0d:$2\x0d/ }); |
||||||
|
|
||||||
|
/%[0-9a-fA-F]{2}/ && !/%[^0-9a-fA-F]/ && !/%.[^0-9a-fA-F]/ |
||||||
|
and $add_button->("uri unescape" => sub { s/%([0-9a-fA-F]{2})/chr hex $1/ge }); |
||||||
|
|
||||||
|
/[\\"'\ \t|&;<>()]/ |
||||||
|
and $add_button->("shell quote" => sub { $_ = "\Q$_" }); |
||||||
|
|
||||||
|
/^(https?|ftp|telnet|irc|news):\// |
||||||
|
and $add_button->("run $self->{browser}" => sub { $self->exec_async ($self->{browser}, $_) }); |
||||||
|
|
||||||
|
for my $hook (@{ $self->{term}{selection_popup_hook} || [] }) { |
||||||
|
if (my ($title, $cb) = $hook->($popup)) { |
||||||
|
$add_button->($title, $cb); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (/^\s*((?:0x)?\d+)\s*$/) { |
||||||
|
$popup->add_title (sprintf "%20s", eval $1); |
||||||
|
$popup->add_title (sprintf "%20s", sprintf "0x%x", eval $1); |
||||||
|
$popup->add_title (sprintf "%20s", sprintf "0%o", eval $1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$popup->show; |
||||||
|
|
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
@ -0,0 +1,27 @@ |
|||||||
|
#! perl -w |
||||||
|
|
||||||
|
=head1 NAME |
||||||
|
|
||||||
|
selection-to-clipboard - copy the selection to the clipboard each time a selection is made |
||||||
|
|
||||||
|
=head1 SYNOPSIS |
||||||
|
|
||||||
|
urxvt -pe selection-to-clipboard |
||||||
|
|
||||||
|
=head1 DESCRIPTION |
||||||
|
|
||||||
|
This very simple extension copies the selection to the clipboard every |
||||||
|
time a selection is made. This, in effect, synchronises the clipboard with |
||||||
|
the selection for selections done by rxvt-unicode. |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
sub on_sel_grab { |
||||||
|
my ($self, $time) = @_; |
||||||
|
|
||||||
|
$self->selection ($self->selection, 1); |
||||||
|
$self->selection_grab ($time, 1); |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
@ -0,0 +1,423 @@ |
|||||||
|
#! perl |
||||||
|
|
||||||
|
#:META:RESOURCE:tabbar-fg:colour:tab bar foreground colour |
||||||
|
#:META:RESOURCE:tabbar-bg:colour:tab bar background colour |
||||||
|
#:META:RESOURCE:tab-fg:colour:tab foreground colour |
||||||
|
#:META:RESOURCE:tab-bg:colour:tab background colour |
||||||
|
|
||||||
|
=head1 NAME |
||||||
|
|
||||||
|
tabbed - tabbed interface to urxvt |
||||||
|
|
||||||
|
=head1 DESCRIPTION |
||||||
|
|
||||||
|
This transforms the terminal into a tabbar with additional terminals, that |
||||||
|
is, it implements what is commonly referred to as "tabbed terminal". The topmost line |
||||||
|
displays a "[NEW]" button, which, when clicked, will add a new tab, followed by one |
||||||
|
button per tab. |
||||||
|
|
||||||
|
Clicking a button will activate that tab. Pressing B<Shift-Left> and |
||||||
|
B<Shift-Right> will switch to the tab left or right of the current one, |
||||||
|
while B<Shift-Down> creates a new tab. Pressing B<Ctrl-Left> and |
||||||
|
B<Ctrl-Right> will renumber the current tab by moving it to the left or |
||||||
|
to the right. |
||||||
|
|
||||||
|
The tabbar itself can be configured similarly to a normal terminal, but |
||||||
|
with a resource class of C<URxvt.tabbed>. In addition, it supports the |
||||||
|
following four resources (shown with defaults): |
||||||
|
|
||||||
|
URxvt.tabbed.tabbar-fg: <colour-index, default 3> |
||||||
|
URxvt.tabbed.tabbar-bg: <colour-index, default 0> |
||||||
|
URxvt.tabbed.tab-fg: <colour-index, default 0> |
||||||
|
URxvt.tabbed.tab-bg: <colour-index, default 1> |
||||||
|
|
||||||
|
See I<COLOR AND GRAPHICS> in the @@RXVT_NAME@@(1) manpage for valid |
||||||
|
indices. |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
sub refresh { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
my $ncol = $self->ncol; |
||||||
|
|
||||||
|
my $text = " " x $ncol; |
||||||
|
my $rend = [($self->{rs_tabbar}) x $ncol]; |
||||||
|
|
||||||
|
my @ofs; |
||||||
|
|
||||||
|
substr $text, 0, 7, "[NEW] |"; |
||||||
|
@$rend[0 .. 5] = ($self->{rs_tab}) x 6; |
||||||
|
push @ofs, [0, 6, sub { $_[0]->new_tab }]; |
||||||
|
|
||||||
|
my $ofs = 7; |
||||||
|
my $idx = 0; |
||||||
|
|
||||||
|
for my $tab (@{ $self->{tabs} }) { |
||||||
|
$idx++; |
||||||
|
|
||||||
|
my $act = $tab->{activity} && $tab != $self->{cur} |
||||||
|
? "*" : " "; |
||||||
|
|
||||||
|
my $txt = "$act$idx$act"; |
||||||
|
my $len = length $txt; |
||||||
|
|
||||||
|
substr $text, $ofs, $len + 1, "$txt|"; |
||||||
|
@$rend[$ofs .. $ofs + $len - 1] = ($self->{rs_tab}) x $len |
||||||
|
if $tab == $self->{cur}; |
||||||
|
|
||||||
|
push @ofs, [ $ofs, $ofs + $len, sub { $_[0]->make_current ($tab) } ]; |
||||||
|
|
||||||
|
$ofs += $len + 1; |
||||||
|
} |
||||||
|
|
||||||
|
$self->{tabofs} = \@ofs; |
||||||
|
|
||||||
|
$self->ROW_t (0, $text, 0, 0, $ncol); |
||||||
|
$self->ROW_r (0, $rend, 0, 0, $ncol); |
||||||
|
|
||||||
|
$self->want_refresh; |
||||||
|
} |
||||||
|
|
||||||
|
sub new_tab { |
||||||
|
my ($self, @argv) = @_; |
||||||
|
|
||||||
|
# save a backlink to us, make sure tabbed is inactive |
||||||
|
push @urxvt::TERM_INIT, sub { |
||||||
|
my ($term) = @_; |
||||||
|
$term->{parent} = $self; |
||||||
|
|
||||||
|
for (0 .. urxvt::NUM_RESOURCES - 1) { |
||||||
|
my $value = $self->{resource}[$_]; |
||||||
|
|
||||||
|
$term->resource ("+$_" => $value) |
||||||
|
if defined $value; |
||||||
|
} |
||||||
|
|
||||||
|
$term->resource (perl_ext_2 => $term->resource ("perl_ext_2") . ",-tabbed"); |
||||||
|
}; |
||||||
|
|
||||||
|
push @urxvt::TERM_EXT, urxvt::ext::tabbed::tab::; |
||||||
|
|
||||||
|
my $term = new urxvt::term |
||||||
|
$self->env, $urxvt::RXVTNAME, |
||||||
|
-embed => $self->parent, |
||||||
|
@argv, |
||||||
|
; |
||||||
|
} |
||||||
|
|
||||||
|
sub configure { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
my $tab = $self->{cur}; |
||||||
|
|
||||||
|
# this is an extremely dirty way to force a configurenotify, but who cares |
||||||
|
$tab->XMoveResizeWindow ( |
||||||
|
$tab->parent, |
||||||
|
0, $self->{tabheight} + 1, |
||||||
|
$self->width, $self->height - $self->{tabheight} |
||||||
|
); |
||||||
|
$tab->XMoveResizeWindow ( |
||||||
|
$tab->parent, |
||||||
|
0, $self->{tabheight}, |
||||||
|
$self->width, $self->height - $self->{tabheight} |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
sub on_resize_all_windows { |
||||||
|
my ($self, $width, $height) = @_; |
||||||
|
|
||||||
|
1 |
||||||
|
} |
||||||
|
|
||||||
|
sub copy_properties { |
||||||
|
my ($self) = @_; |
||||||
|
my $tab = $self->{cur}; |
||||||
|
|
||||||
|
my $wm_normal_hints = $self->XInternAtom ("WM_NORMAL_HINTS"); |
||||||
|
|
||||||
|
my $current = delete $self->{current_properties}; |
||||||
|
|
||||||
|
# pass 1: copy over properties different or nonexisting |
||||||
|
for my $atom ($tab->XListProperties ($tab->parent)) { |
||||||
|
my ($type, $format, $items) = $self->XGetWindowProperty ($tab->parent, $atom); |
||||||
|
|
||||||
|
# fix up size hints |
||||||
|
if ($atom == $wm_normal_hints) { |
||||||
|
my (@hints) = unpack "l!*", $items; |
||||||
|
|
||||||
|
$hints[$_] += $self->{tabheight} for (4, 6, 16); |
||||||
|
|
||||||
|
$items = pack "l!*", @hints; |
||||||
|
} |
||||||
|
|
||||||
|
my $cur = delete $current->{$atom}; |
||||||
|
|
||||||
|
# update if changed, we assume empty items and zero type and format will not happen |
||||||
|
$self->XChangeProperty ($self->parent, $atom, $type, $format, $items) |
||||||
|
if $cur->[0] != $type or $cur->[1] != $format or $cur->[2] ne $items; |
||||||
|
|
||||||
|
$self->{current_properties}{$atom} = [$type, $format, $items]; |
||||||
|
} |
||||||
|
|
||||||
|
# pass 2, delete all extraneous properties |
||||||
|
$self->XDeleteProperty ($self->parent, $_) for keys %$current; |
||||||
|
} |
||||||
|
|
||||||
|
sub make_current { |
||||||
|
my ($self, $tab) = @_; |
||||||
|
|
||||||
|
if (my $cur = $self->{cur}) { |
||||||
|
delete $cur->{activity}; |
||||||
|
$cur->XUnmapWindow ($cur->parent) if $cur->mapped; |
||||||
|
$cur->focus_out; |
||||||
|
} |
||||||
|
|
||||||
|
$self->{cur} = $tab; |
||||||
|
|
||||||
|
$self->configure; |
||||||
|
$self->copy_properties; |
||||||
|
|
||||||
|
$tab->focus_out; # just in case, should be a nop |
||||||
|
$tab->focus_in if $self->focus; |
||||||
|
|
||||||
|
$tab->XMapWindow ($tab->parent); |
||||||
|
delete $tab->{activity}; |
||||||
|
$self->refresh; |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_focus_in { |
||||||
|
my ($self, $event) = @_; |
||||||
|
|
||||||
|
$self->{cur}->focus_in; |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_focus_out { |
||||||
|
my ($self, $event) = @_; |
||||||
|
|
||||||
|
$self->{cur}->focus_out; |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_tt_write { |
||||||
|
my ($self, $octets) = @_; |
||||||
|
|
||||||
|
$self->{cur}->tt_write ($octets); |
||||||
|
|
||||||
|
1 |
||||||
|
} |
||||||
|
|
||||||
|
sub on_key_press { |
||||||
|
my ($self, $event) = @_; |
||||||
|
|
||||||
|
$self->{cur}->key_press ($event->{state}, $event->{keycode}, $event->{time}); |
||||||
|
|
||||||
|
1 |
||||||
|
} |
||||||
|
|
||||||
|
sub on_key_release { |
||||||
|
my ($self, $event) = @_; |
||||||
|
|
||||||
|
$self->{cur}->key_release ($event->{state}, $event->{keycode}, $event->{time}); |
||||||
|
|
||||||
|
1 |
||||||
|
} |
||||||
|
|
||||||
|
sub on_button_press { |
||||||
|
1 |
||||||
|
} |
||||||
|
|
||||||
|
sub on_button_release { |
||||||
|
my ($self, $event) = @_; |
||||||
|
|
||||||
|
if ($event->{row} == 0) { |
||||||
|
for my $button (@{ $self->{tabofs} }) { |
||||||
|
$button->[2]->($self, $event) |
||||||
|
if $event->{col} >= $button->[0] |
||||||
|
&& $event->{col} < $button->[1]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
1 |
||||||
|
} |
||||||
|
|
||||||
|
sub on_motion_notify { |
||||||
|
1 |
||||||
|
} |
||||||
|
|
||||||
|
sub on_init { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
$self->{resource} = [map $self->resource ("+$_"), 0 .. urxvt::NUM_RESOURCES - 1]; |
||||||
|
|
||||||
|
$self->resource (int_bwidth => 0); |
||||||
|
$self->resource (name => "URxvt.tabbed"); |
||||||
|
$self->resource (pty_fd => -1); |
||||||
|
|
||||||
|
$self->option ($urxvt::OPTION{scrollBar}, 0); |
||||||
|
|
||||||
|
my $fg = $self->x_resource ("tabbar-fg"); |
||||||
|
my $bg = $self->x_resource ("tabbar-bg"); |
||||||
|
my $tabfg = $self->x_resource ("tab-fg"); |
||||||
|
my $tabbg = $self->x_resource ("tab-bg"); |
||||||
|
|
||||||
|
defined $fg or $fg = 3; |
||||||
|
defined $bg or $bg = 0; |
||||||
|
defined $tabfg or $tabfg = 0; |
||||||
|
defined $tabbg or $tabbg = 1; |
||||||
|
|
||||||
|
$self->{rs_tabbar} = urxvt::SET_COLOR (urxvt::DEFAULT_RSTYLE, $fg + 2, $bg + 2); |
||||||
|
$self->{rs_tab} = urxvt::SET_COLOR (urxvt::DEFAULT_RSTYLE, $tabfg + 2, $tabbg + 2); |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_start { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
$self->{tabheight} = $self->int_bwidth + $self->fheight + $self->lineSpace; |
||||||
|
|
||||||
|
$self->cmd_parse ("\033[?25l"); |
||||||
|
|
||||||
|
my @argv = $self->argv; |
||||||
|
|
||||||
|
do { |
||||||
|
shift @argv; |
||||||
|
} while @argv && $argv[0] ne "-e"; |
||||||
|
|
||||||
|
$self->new_tab (@argv); |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_configure_notify { |
||||||
|
my ($self, $event) = @_; |
||||||
|
|
||||||
|
$self->configure; |
||||||
|
$self->refresh; |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_wm_delete_window { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
$_->destroy for @{ $self->{tabs} }; |
||||||
|
|
||||||
|
1 |
||||||
|
} |
||||||
|
|
||||||
|
sub tab_start { |
||||||
|
my ($self, $tab) = @_; |
||||||
|
|
||||||
|
$tab->XChangeInput ($tab->parent, urxvt::PropertyChangeMask); |
||||||
|
|
||||||
|
push @{ $self->{tabs} }, $tab; |
||||||
|
|
||||||
|
# $tab->{name} ||= scalar @{ $self->{tabs} }; |
||||||
|
$self->make_current ($tab); |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub tab_destroy { |
||||||
|
my ($self, $tab) = @_; |
||||||
|
|
||||||
|
$self->{tabs} = [ grep $_ != $tab, @{ $self->{tabs} } ]; |
||||||
|
|
||||||
|
if (@{ $self->{tabs} }) { |
||||||
|
if ($self->{cur} == $tab) { |
||||||
|
delete $self->{cur}; |
||||||
|
$self->make_current ($self->{tabs}[-1]); |
||||||
|
} else { |
||||||
|
$self->refresh; |
||||||
|
} |
||||||
|
} else { |
||||||
|
# delay destruction a tiny bit |
||||||
|
$self->{destroy} = urxvt::iw->new->start->cb (sub { $self->destroy }); |
||||||
|
} |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub tab_key_press { |
||||||
|
my ($self, $tab, $event, $keysym, $str) = @_; |
||||||
|
|
||||||
|
if ($event->{state} & urxvt::ShiftMask) { |
||||||
|
if ($keysym == 0xff51 || $keysym == 0xff53) { |
||||||
|
my ($idx) = grep $self->{tabs}[$_] == $tab, 0 .. $#{ $self->{tabs} }; |
||||||
|
|
||||||
|
--$idx if $keysym == 0xff51; |
||||||
|
++$idx if $keysym == 0xff53; |
||||||
|
|
||||||
|
$self->make_current ($self->{tabs}[$idx % @{ $self->{tabs}}]); |
||||||
|
|
||||||
|
return 1; |
||||||
|
} elsif ($keysym == 0xff54) { |
||||||
|
$self->new_tab; |
||||||
|
|
||||||
|
return 1; |
||||||
|
} |
||||||
|
} |
||||||
|
elsif ($event->{state} & urxvt::ControlMask) { |
||||||
|
if ($keysym == 0xff51 || $keysym == 0xff53) { |
||||||
|
my ($idx1) = grep $self->{tabs}[$_] == $tab, 0 .. $#{ $self->{tabs} }; |
||||||
|
my $idx2 = ($idx1 + ($keysym == 0xff51 ? -1 : +1)) % @{ $self->{tabs} }; |
||||||
|
|
||||||
|
($self->{tabs}[$idx1], $self->{tabs}[$idx2]) = |
||||||
|
($self->{tabs}[$idx2], $self->{tabs}[$idx1]); |
||||||
|
|
||||||
|
$self->make_current ($self->{tabs}[$idx2]); |
||||||
|
|
||||||
|
return 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub tab_property_notify { |
||||||
|
my ($self, $tab, $event) = @_; |
||||||
|
|
||||||
|
$self->copy_properties |
||||||
|
if $event->{window} == $tab->parent; |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub tab_activity { |
||||||
|
my ($self, $tab) = @_; |
||||||
|
|
||||||
|
$self->refresh; |
||||||
|
} |
||||||
|
|
||||||
|
package urxvt::ext::tabbed::tab; |
||||||
|
|
||||||
|
# helper extension implementing the subwindows of a tabbed terminal. |
||||||
|
# simply proxies all interesting calls back to the tabbed class. |
||||||
|
|
||||||
|
{ |
||||||
|
for my $hook (qw(start destroy key_press property_notify)) { |
||||||
|
eval qq{ |
||||||
|
sub on_$hook { |
||||||
|
my \$parent = \$_[0]{term}{parent} |
||||||
|
or return; |
||||||
|
\$parent->tab_$hook (\@_) |
||||||
|
} |
||||||
|
}; |
||||||
|
die if $@; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
sub on_add_lines { |
||||||
|
$_[0]->{activity}++ |
||||||
|
or $_[0]{term}{parent}->tab_activity ($_[0]); |
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
|
@ -0,0 +1,459 @@ |
|||||||
|
#! perl |
||||||
|
|
||||||
|
sub refresh { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
my $ncol = $self->ncol; |
||||||
|
|
||||||
|
my $text = ' ' x $ncol; |
||||||
|
my $rend = [($self->{rs_tabbar}) x $ncol]; |
||||||
|
|
||||||
|
my @ofs; |
||||||
|
my $ofs = 0; |
||||||
|
my $idx = 0; |
||||||
|
|
||||||
|
for my $tab (@{ $self->{tabs} }) { |
||||||
|
my $is_rename = defined $self->{tab_rename_started} && $self->{tab_rename_started} && $tab == $self->{cur}; |
||||||
|
|
||||||
|
my $txt = ' '.($tab->{manual_name} || $tab->{name}).' '; |
||||||
|
if ($is_rename) { |
||||||
|
$txt = ' '.$tab->{tab_new_name}.' '; |
||||||
|
$txt .= ' ' if $tab->{tab_new_pos} == length($tab->{tab_new_name}); |
||||||
|
} |
||||||
|
my $len = length($txt); |
||||||
|
|
||||||
|
unless ($idx++) { |
||||||
|
# first tab |
||||||
|
substr $text, $ofs, $len, $txt; |
||||||
|
if ($is_rename) { |
||||||
|
@$rend[$ofs .. $ofs + $len - 1] = ($self->{rs_tab}) x $len; |
||||||
|
@$rend[$ofs + $tab->{tab_new_pos} + 2] = $self->{rs_rename}; |
||||||
|
} elsif ($tab == $self->{cur}) { |
||||||
|
@$rend[$ofs .. $ofs + $len - 1] = ($self->{rs_tab}) x $len; |
||||||
|
} elsif ($tab->{activity} && $tab != $self->{cur}) { |
||||||
|
@$rend[$ofs .. $ofs + $len - 1] = ($self->{rs_tabsel}) x $len; |
||||||
|
} |
||||||
|
push @ofs, [ $ofs, $ofs + $len, sub { $_[0]->make_current($tab) } ]; |
||||||
|
$ofs += $len; |
||||||
|
} else { |
||||||
|
# other tabs |
||||||
|
substr $text, $ofs, $len + 1, '|'.$txt; |
||||||
|
@$rend[$ofs] = $self->{rs_tabdiv}; |
||||||
|
if ($tab == $self->{cur}) { |
||||||
|
@$rend[$ofs + 1 .. $ofs + $len] = ($self->{rs_tab}) x $len; |
||||||
|
} elsif ($tab->{activity} && $tab != $self->{cur}) { |
||||||
|
@$rend[$ofs + 1 .. $ofs + $len] = ($self->{rs_tabsel}) x $len; |
||||||
|
} |
||||||
|
push @ofs, [ $ofs + 1, $ofs + $len + 1, sub { $_[0]->make_current($tab) } ]; |
||||||
|
$ofs += $len + 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$self->{tabofs} = \@ofs; |
||||||
|
$self->ROW_t(0, $text, 0, 0, $ncol); |
||||||
|
$self->ROW_r(0, $rend, 0, 0, $ncol); |
||||||
|
$self->want_refresh; |
||||||
|
} |
||||||
|
|
||||||
|
sub new_tab { |
||||||
|
my ($self, @argv) = @_; |
||||||
|
|
||||||
|
my $offset = $self->fheight; |
||||||
|
|
||||||
|
# save a backlink to us, make sure tabbed is inactive |
||||||
|
push @urxvt::TERM_INIT, sub { |
||||||
|
my ($term) = @_; |
||||||
|
$term->{parent} = $self; |
||||||
|
for (0 .. urxvt::NUM_RESOURCES - 1) { |
||||||
|
my $value = $self->{resource}[$_]; |
||||||
|
$term->resource("+$_" => $value) if defined $value; |
||||||
|
} |
||||||
|
$term->resource(perl_ext_2 => $term->resource("perl_ext_2") . ",-tabbed_new"); |
||||||
|
}; |
||||||
|
|
||||||
|
push @urxvt::TERM_EXT, urxvt::ext::tabbed_new::tab::; |
||||||
|
|
||||||
|
my $term = new urxvt::term $self->env, $urxvt::RXVTNAME, -embed => $self->parent, @argv,; |
||||||
|
} |
||||||
|
|
||||||
|
sub configure { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
my $tab = $self->{cur}; |
||||||
|
|
||||||
|
# this is an extremely dirty way to force a configurenotify, but who cares |
||||||
|
$tab->XMoveResizeWindow($tab->parent, 0, $self->{tabheight} + 1, $self->width, $self->height - $self->{tabheight}); |
||||||
|
$tab->XMoveResizeWindow($tab->parent, 0, $self->{tabheight}, $self->width, $self->height - $self->{tabheight}); |
||||||
|
} |
||||||
|
|
||||||
|
sub on_resize_all_windows { |
||||||
|
my ($self, $width, $height) = @_; |
||||||
|
1; |
||||||
|
} |
||||||
|
|
||||||
|
sub copy_properties { |
||||||
|
my ($self) = @_; |
||||||
|
my $tab = $self->{cur}; |
||||||
|
|
||||||
|
my $wm_normal_hints = $self->XInternAtom("WM_NORMAL_HINTS"); |
||||||
|
|
||||||
|
my $current = delete $self->{current_properties}; |
||||||
|
|
||||||
|
# pass 1: copy over properties different or nonexisting |
||||||
|
for my $atom ($tab->XListProperties($tab->parent)) { |
||||||
|
my ($type, $format, $items) = $self->XGetWindowProperty($tab->parent, $atom); |
||||||
|
|
||||||
|
# fix up size hints |
||||||
|
if ($atom == $wm_normal_hints) { |
||||||
|
my (@hints) = unpack "l!*", $items; |
||||||
|
|
||||||
|
$hints[$_] += $self->{tabheight} for (4, 6, 16); |
||||||
|
|
||||||
|
$items = pack "l!*", @hints; |
||||||
|
} |
||||||
|
|
||||||
|
my $cur = delete $current->{$atom}; |
||||||
|
|
||||||
|
# update if changed, we assume empty items and zero type and format will not happen |
||||||
|
$self->XChangeProperty($self->parent, $atom, $type, $format, $items) |
||||||
|
if $cur->[0] != $type or $cur->[1] != $format or $cur->[2] ne $items; |
||||||
|
|
||||||
|
$self->{current_properties}{$atom} = [$type, $format, $items]; |
||||||
|
} |
||||||
|
|
||||||
|
# pass 2, delete all extraneous properties |
||||||
|
$self->XDeleteProperty($self->parent, $_) for keys %$current; |
||||||
|
} |
||||||
|
|
||||||
|
sub make_current { |
||||||
|
my ($self, $tab) = @_; |
||||||
|
|
||||||
|
if (my $cur = $self->{cur}) { |
||||||
|
delete $cur->{activity}; |
||||||
|
$cur->XUnmapWindow($cur->parent) if $cur->mapped; |
||||||
|
$cur->focus_out; |
||||||
|
} |
||||||
|
|
||||||
|
$self->{cur} = $tab; |
||||||
|
$self->configure; |
||||||
|
$self->copy_properties; |
||||||
|
$tab->focus_out; # just in case, should be a nop |
||||||
|
$tab->focus_in if $self->focus; |
||||||
|
$tab->XMapWindow($tab->parent); |
||||||
|
delete $tab->{activity}; |
||||||
|
$self->refresh; |
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
sub on_focus_in { |
||||||
|
my ($self, $event) = @_; |
||||||
|
$self->{cur}->focus_in; |
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
sub on_focus_out { |
||||||
|
my ($self, $event) = @_; |
||||||
|
$self->{cur}->focus_out; |
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
sub on_key_press { |
||||||
|
my ($self, $event) = @_; |
||||||
|
$self->{cur}->key_press($event->{state}, $event->{keycode}, $event->{time}); |
||||||
|
1; |
||||||
|
} |
||||||
|
|
||||||
|
sub on_key_release { |
||||||
|
my ($self, $event) = @_; |
||||||
|
$self->{cur}->key_release($event->{state}, $event->{keycode}, $event->{time}); |
||||||
|
1; |
||||||
|
} |
||||||
|
|
||||||
|
sub on_button_press { |
||||||
|
1; |
||||||
|
} |
||||||
|
|
||||||
|
sub on_button_release { |
||||||
|
my ($self, $event) = @_; |
||||||
|
if ($event->{row} == 0) { |
||||||
|
for my $button (@{ $self->{tabofs} }) { |
||||||
|
$button->[2]->($self, $event) if $event->{col} >= $button->[0] && $event->{col} < $button->[1]; |
||||||
|
} |
||||||
|
} |
||||||
|
1; |
||||||
|
} |
||||||
|
|
||||||
|
sub on_motion_notify { |
||||||
|
1; |
||||||
|
} |
||||||
|
|
||||||
|
sub on_init { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
$self->{resource} = [map $self->resource("+$_"), 0 .. urxvt::NUM_RESOURCES - 1]; |
||||||
|
|
||||||
|
$self->resource(int_bwidth => 0); |
||||||
|
$self->resource(name => "URxvt.tabbed_new"); |
||||||
|
$self->resource(pty_fd => -1); |
||||||
|
|
||||||
|
$self->option($urxvt::OPTION{scrollBar}, 0); |
||||||
|
|
||||||
|
my $renamebg = $self->x_resource("tabren-bg"); |
||||||
|
my $divfg = $self->x_resource("tabdiv-fg"); |
||||||
|
my $fg = $self->x_resource("tabbar-fg"); |
||||||
|
my $bg = $self->x_resource("tabbar-bg"); |
||||||
|
my $selfg = $self->x_resource("tabsel-fg"); |
||||||
|
my $selbg = $self->x_resource("tabsel-bg"); |
||||||
|
my $tabfg = $self->x_resource("tab-fg"); |
||||||
|
my $tabbg = $self->x_resource("tab-bg"); |
||||||
|
|
||||||
|
defined $renamebg or $renamebg = 11; |
||||||
|
defined $divfg or $divfg = 8; |
||||||
|
defined $fg or $fg = 0; |
||||||
|
defined $bg or $bg = 15; |
||||||
|
defined $tabfg or $tabfg = 1; |
||||||
|
defined $tabbg or $tabbg = 15; |
||||||
|
defined $selfg or $selfg = 0; |
||||||
|
defined $selbg or $selbg = 7; |
||||||
|
|
||||||
|
$self->{rs_tabbar} = urxvt::SET_COLOR(urxvt::DEFAULT_RSTYLE, $fg + 2, $bg + 2); |
||||||
|
$self->{rs_tabdiv} = urxvt::SET_COLOR(urxvt::DEFAULT_RSTYLE, $divfg + 2, $bg + 2); |
||||||
|
$self->{rs_tabsel} = urxvt::SET_COLOR(urxvt::DEFAULT_RSTYLE, $selfg + 2, $selbg + 2); |
||||||
|
$self->{rs_tab} = urxvt::SET_COLOR(urxvt::DEFAULT_RSTYLE, $tabfg + 2, $tabbg + 2); |
||||||
|
|
||||||
|
$self->{rs_rename} = urxvt::SET_COLOR(urxvt::DEFAULT_RSTYLE, $selfg + 2, $renamebg + 2); |
||||||
|
|
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
sub on_start { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
$self->{tabheight} = $self->int_bwidth + $self->fheight + $self->lineSpace; |
||||||
|
$self->cmd_parse("\033[?25l"); |
||||||
|
my @argv = $self->argv; |
||||||
|
|
||||||
|
do { shift @argv; } while @argv && $argv[0] ne "-e"; |
||||||
|
$self->new_tab(@argv); |
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
sub on_configure_notify { |
||||||
|
my ($self, $event) = @_; |
||||||
|
$self->configure; |
||||||
|
$self->refresh; |
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
sub on_wm_delete_window { |
||||||
|
my ($self) = @_; |
||||||
|
$_->destroy for @{ $self->{tabs} }; |
||||||
|
1; |
||||||
|
} |
||||||
|
|
||||||
|
sub tab_name { |
||||||
|
my ($self, $name) = @_; |
||||||
|
|
||||||
|
return unless $name =~ /\@/; |
||||||
|
my $tab = $self->{cur}; |
||||||
|
$tab->{name} = $name; |
||||||
|
$self->refresh; |
||||||
|
1; |
||||||
|
} |
||||||
|
|
||||||
|
sub tab_start { |
||||||
|
my ($self, $tab) = @_; |
||||||
|
$tab->XChangeInput($tab->parent, urxvt::PropertyChangeMask); |
||||||
|
push @{ $self->{tabs} }, $tab; |
||||||
|
$tab->{name} ||= 'tab '.scalar @{ $self->{tabs} }; |
||||||
|
$tab->{manual_name} = undef; |
||||||
|
$self->make_current($tab); |
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
sub tab_destroy { |
||||||
|
my ($self, $tab) = @_; |
||||||
|
$self->{tabs} = [ grep $_ != $tab, @{ $self->{tabs} } ]; |
||||||
|
if (@{ $self->{tabs} }) { |
||||||
|
if ($self->{cur} == $tab) { |
||||||
|
delete $self->{cur}; |
||||||
|
$self->make_current($self->{tabs}[-1]); |
||||||
|
} else { |
||||||
|
$self->refresh; |
||||||
|
} |
||||||
|
} else { |
||||||
|
# delay destruction a tiny bit |
||||||
|
$self->{destroy} = urxvt::iw->new->start->cb(sub { $self->destroy }); |
||||||
|
} |
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
sub tab_key_press { |
||||||
|
my ($self, $tab, $event, $keysum, $str) = @_; |
||||||
|
my $action; |
||||||
|
|
||||||
|
if ($event->{state} & urxvt::Mod4Mask) { |
||||||
|
if ($event->{state} & urxvt::ControlMask) { |
||||||
|
$action = 'move-left' if $keysum == 0xff51; # Mod4+Ctrl+Left |
||||||
|
$action = 'move-right' if $keysum == 0xff53; # Mod4+Ctrl+Right |
||||||
|
} else { |
||||||
|
$action = 'prev-tab' if $keysum == 0xff51; # Mod4+Left |
||||||
|
$action = 'next-tab' if $keysum == 0xff53; # Mod4+Right |
||||||
|
$action = 'new-tab' if $keysum == 0x74; # Mod4+T |
||||||
|
$action = 'rename-start' if $keysum == 0x6e; # Mod4+N |
||||||
|
} |
||||||
|
} elsif ($event->{state} & urxvt::ControlMask) { |
||||||
|
$action = 'next-tab' if $keysum == 0xff09; # Ctrl+Tab |
||||||
|
} elsif (defined $self->{tab_rename_started} && $self->{tab_rename_started}) { |
||||||
|
if ($keysum == 0xff1b) { # Esc |
||||||
|
$action = 'cancel-rename'; |
||||||
|
} elsif ($keysum == 0xff0d || $keysum == 0xff8d) { # Enter |
||||||
|
$action = 'confirm-rename'; |
||||||
|
} elsif ($keysum == 0xff51) { # Left |
||||||
|
if ($tab->{tab_new_pos} > 0) { |
||||||
|
$tab->{tab_new_pos}--; |
||||||
|
$self->refresh; |
||||||
|
} |
||||||
|
return 1; |
||||||
|
} elsif ($keysum == 0xff53) { # Right |
||||||
|
if ($tab->{tab_new_pos} < length($tab->{tab_new_name})) { |
||||||
|
$tab->{tab_new_pos}++; |
||||||
|
$self->refresh; |
||||||
|
} |
||||||
|
return 1; |
||||||
|
} elsif ($keysum == 0xff50) { # Home |
||||||
|
if ($tab->{tab_new_pos} > 0) { |
||||||
|
$tab->{tab_new_pos} = 0; |
||||||
|
$self->refresh; |
||||||
|
} |
||||||
|
return 1; |
||||||
|
} elsif ($keysum == 0xff57) { # End |
||||||
|
if ($tab->{tab_new_pos} < length($tab->{tab_new_name})) { |
||||||
|
$tab->{tab_new_pos} = length($tab->{tab_new_name}); |
||||||
|
$self->refresh; |
||||||
|
} |
||||||
|
return 1; |
||||||
|
} elsif ($keysum == 0xff08) { # Backspace |
||||||
|
if ($tab->{tab_new_pos} > 0) { |
||||||
|
my $name = ''; |
||||||
|
$name .= substr($tab->{tab_new_name}, 0, $tab->{tab_new_pos} - 1) if $tab->{tab_new_pos} > 1; |
||||||
|
$name .= substr($tab->{tab_new_name}, $tab->{tab_new_pos}, length($tab->{tab_new_name})) if $tab->{tab_new_pos} < length($tab->{tab_new_name}); |
||||||
|
$tab->{tab_new_name} = $name; |
||||||
|
$tab->{tab_new_pos}--; |
||||||
|
} |
||||||
|
$self->refresh; |
||||||
|
return 1; |
||||||
|
} elsif ($keysum == 0xffff) { # Delete |
||||||
|
if ($tab->{tab_new_pos} < length($tab->{tab_new_name})) { |
||||||
|
my $name = ''; |
||||||
|
$name .= substr($tab->{tab_new_name}, 0, $tab->{tab_new_pos}) if $tab->{tab_new_pos} > 0; |
||||||
|
$name .= substr($tab->{tab_new_name}, $tab->{tab_new_pos} + 1, length($tab->{tab_new_name})) if $tab->{tab_new_pos} < length($tab->{tab_new_name}) - 1; |
||||||
|
$tab->{tab_new_name} = $name; |
||||||
|
} |
||||||
|
$self->refresh; |
||||||
|
return 1; |
||||||
|
} else { |
||||||
|
if ($str =~ /^[\x20-\x7f]$/) { # printable symbols |
||||||
|
my $name = ''; |
||||||
|
$name .= substr($tab->{tab_new_name}, 0, $tab->{tab_new_pos}) if $tab->{tab_new_pos} > 0; |
||||||
|
$name .= $str; |
||||||
|
$name .= substr($tab->{tab_new_name}, $tab->{tab_new_pos}, length($tab->{tab_new_name})) if $tab->{tab_new_pos} < length($tab->{tab_new_name}); |
||||||
|
$tab->{tab_new_name} = $name; |
||||||
|
$tab->{tab_new_pos}++; |
||||||
|
} |
||||||
|
$self->refresh; |
||||||
|
return 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (defined $action) { |
||||||
|
if ($action eq 'next-tab' || $action eq 'prev-tab') { |
||||||
|
my ($idx) = grep $self->{tabs}[$_] == $tab, 0 .. $#{ $self->{tabs} }; |
||||||
|
--$idx if $action eq 'prev-tab'; |
||||||
|
++$idx if $action eq 'next-tab'; |
||||||
|
$self->make_current($self->{tabs}[$idx % @{ $self->{tabs}}]); |
||||||
|
return 1; |
||||||
|
} elsif ($action eq 'move-left' || $action eq 'move-right') { |
||||||
|
my ($idx1) = grep $self->{tabs}[$_] == $tab, 0 .. $#{ $self->{tabs} }; |
||||||
|
if (($action eq 'move-left' && $idx1 > 0) || ($action eq 'move-right' && $idx1 < $#{ $self->{tabs} })) { |
||||||
|
my $idx2 = ($idx1 + ($action eq 'move-left' ? -1 : +1)) % @{ $self->{tabs} }; |
||||||
|
($self->{tabs}[$idx1], $self->{tabs}[$idx2]) = ($self->{tabs}[$idx2], $self->{tabs}[$idx1]); |
||||||
|
$self->make_current($self->{tabs}[$idx2]); |
||||||
|
} |
||||||
|
return 1; |
||||||
|
} elsif ($action eq 'new-tab') { |
||||||
|
$self->new_tab; |
||||||
|
return 1; |
||||||
|
} elsif ($action eq 'rename-start') { |
||||||
|
$self->{tab_rename_started} = 1; |
||||||
|
$tab->{tab_new_name} = $tab->{manual_name} || ''; |
||||||
|
$tab->{tab_new_pos} = length($tab->{tab_new_name}); |
||||||
|
$self->refresh; |
||||||
|
return 1; |
||||||
|
} elsif ($action eq 'cancel-rename') { |
||||||
|
undef $self->{tab_rename_started}; |
||||||
|
undef $tab->{tab_new_name}; |
||||||
|
undef $tab->{tab_new_pos}; |
||||||
|
$self->refresh; |
||||||
|
return 1; |
||||||
|
} elsif ($action eq 'confirm-rename') { |
||||||
|
$tab->{manual_name} = $tab->{tab_new_name} || undef; |
||||||
|
undef $self->{tab_rename_started}; |
||||||
|
undef $tab->{tab_new_name}; |
||||||
|
undef $tab->{tab_new_pos}; |
||||||
|
$self->refresh; |
||||||
|
return 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
sub tab_property_notify { |
||||||
|
my ($self, $tab, $event) = @_; |
||||||
|
$self->copy_properties if $event->{window} == $tab->parent; |
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
sub tab_activity { |
||||||
|
my ($self, $tab) = @_; |
||||||
|
$self->refresh; |
||||||
|
} |
||||||
|
|
||||||
|
package urxvt::ext::tabbed_new::tab; |
||||||
|
|
||||||
|
# helper extension implementing the subwindows of a tabbed terminal. |
||||||
|
# simply proxies all interesting calls back to the tabbed class. |
||||||
|
|
||||||
|
{ |
||||||
|
for my $hook (qw(start destroy key_press property_notify)) { |
||||||
|
eval qq{ |
||||||
|
sub on_$hook \{ |
||||||
|
my \$parent = \$_[0]\{term\}\{parent\} or return; |
||||||
|
\$parent->tab_$hook(\@_); |
||||||
|
\} |
||||||
|
}; |
||||||
|
die if $@; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
sub on_add_lines { |
||||||
|
$_[0]->{activity}++ or $_[0]{term}{parent}->tab_activity($_[0]); |
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
sub on_osc_seq { |
||||||
|
my ($self, $seq, $cmd, $resp) = @_; |
||||||
|
return unless $seq == 0; |
||||||
|
|
||||||
|
my $parent = $self->{term}->{parent}; |
||||||
|
return unless $parent; |
||||||
|
|
||||||
|
my ($name, undef) = split /:\s/, $cmd, 2; |
||||||
|
return unless $name; |
||||||
|
|
||||||
|
$parent->tab_name($name); |
||||||
|
|
||||||
|
(); |
||||||
|
} |
||||||
|
|
@ -0,0 +1,139 @@ |
|||||||
|
#! perl |
||||||
|
|
||||||
|
# this extension implements popup-menu functionality for urxvt. it works |
||||||
|
# together with the urxvt::popup class - "no user serviceable parts inside". |
||||||
|
|
||||||
|
sub refresh { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
my $cmd = "\x1b[H"; |
||||||
|
|
||||||
|
my $row = 1; |
||||||
|
for my $item (@{ $self->{data}{item} }) { |
||||||
|
my $rend = "normal"; |
||||||
|
|
||||||
|
if ($row == $self->{hover}) { |
||||||
|
$rend = $self->{press} ? "active" : "hover"; |
||||||
|
} |
||||||
|
|
||||||
|
$cmd .= "$item->{rend}{$rend}\x1b[K"; |
||||||
|
$cmd .= $self->locale_encode ($item->{render}->($item)); |
||||||
|
$cmd .= "\015\012"; |
||||||
|
|
||||||
|
$row++; |
||||||
|
} |
||||||
|
|
||||||
|
$self->cmd_parse (substr $cmd, 0, -2); |
||||||
|
} |
||||||
|
|
||||||
|
sub on_motion_notify { |
||||||
|
my ($self, $event) = @_; |
||||||
|
|
||||||
|
delete $self->{hover}; |
||||||
|
|
||||||
|
my ($row, $col) = ($event->{row}, $event->{col}); |
||||||
|
if ($col >= 0 && $col < $self->ncol |
||||||
|
&& $row >= 0 && $row < @{ $self->{data}{item} }) { |
||||||
|
$self->{hover} = $event->{row} + 1; |
||||||
|
} |
||||||
|
$self->refresh; |
||||||
|
|
||||||
|
1 |
||||||
|
} |
||||||
|
|
||||||
|
sub on_button_press { |
||||||
|
my ($self, $event) = @_; |
||||||
|
|
||||||
|
$self->{press}[$event->{button}] = 1; |
||||||
|
$self->refresh; |
||||||
|
|
||||||
|
1 |
||||||
|
} |
||||||
|
|
||||||
|
sub on_button_release { |
||||||
|
my ($self, $event) = @_; |
||||||
|
|
||||||
|
$self->{press}[$event->{button}] = 0; |
||||||
|
|
||||||
|
my ($row, $col) = ($event->{row}, $event->{col}); |
||||||
|
if ($col >= 0 && $col < $self->ncol |
||||||
|
&& $row >= 0 && $row < @{ $self->{data}{item} }) { |
||||||
|
my $item = $self->{data}{item}[$row]; |
||||||
|
$item->{activate}->($event, $item); |
||||||
|
} |
||||||
|
|
||||||
|
$self->refresh; |
||||||
|
|
||||||
|
if ($event->{button} == $self->{data}{event}{button}) { |
||||||
|
$self->ungrab; |
||||||
|
$self->destroy; |
||||||
|
} |
||||||
|
|
||||||
|
1 |
||||||
|
} |
||||||
|
|
||||||
|
sub on_focus_out { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
delete $self->{hover}; |
||||||
|
$self->refresh; |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_init { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
my $data = $self->{data} = $urxvt::popup::self; |
||||||
|
|
||||||
|
$_->{width} = $self->strwidth ($_->{text}) |
||||||
|
for @{ $data->{item} }; |
||||||
|
|
||||||
|
$self->resource (title => "URxvt Popup Menu"); |
||||||
|
$self->resource (name => "URxvt.popup"); |
||||||
|
|
||||||
|
$self->resource ($_ => $data->{term}->resource ($_)) |
||||||
|
for qw(font boldFont italicFont boldItalicFont color+0 color+1); |
||||||
|
|
||||||
|
my $width = List::Util::max map $_->{width}, @{ $data->{item} }; |
||||||
|
my $height = @{ $data->{item} }; |
||||||
|
|
||||||
|
my $pos = ""; |
||||||
|
|
||||||
|
if ($data->{event}) { |
||||||
|
my $x = int List::Util::max 0, $data->{event}{x_root} - $width * $data->{term}->fwidth * 0.5; |
||||||
|
my $y = int List::Util::max 0, $data->{event}{y_root} - $data->{term}->fheight * 0.5; |
||||||
|
$pos = "+$x+$y"; |
||||||
|
} |
||||||
|
|
||||||
|
$self->resource (geometry => "${width}x${height}$pos"); |
||||||
|
|
||||||
|
$self->{term}{urxvt_popup_init_done} = 1; |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_start { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
$self->cmd_parse ("\x1b[?25l\x1b[?7l"); |
||||||
|
$self->refresh; |
||||||
|
|
||||||
|
# might fail, but try anyways |
||||||
|
$self->grab ($self->{data}{event}{time}, 1) |
||||||
|
and $self->allow_events_async; |
||||||
|
|
||||||
|
on_button_press $self, $self->{data}{event} if $self->{data}{event}{button}; |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_map_notify { |
||||||
|
my ($self, $event) = @_; |
||||||
|
|
||||||
|
# should definitely not fail |
||||||
|
$self->grab ($self->{data}{event}{time}, 1) |
||||||
|
and $self->allow_events_async; |
||||||
|
} |
||||||
|
|
||||||
|
|
@ -0,0 +1,90 @@ |
|||||||
|
#! perl |
||||||
|
|
||||||
|
=head1 NAME |
||||||
|
|
||||||
|
xim-onthespot - implement XIM "on-the-spot" behaviour |
||||||
|
|
||||||
|
=head1 DESCRIPTION |
||||||
|
|
||||||
|
This perl extension implements OnTheSpot editing. It does not work |
||||||
|
perfectly, and some input methods don't seem to work well with OnTheSpot |
||||||
|
editing in general, but it seems to work at least for SCIM and kinput2. |
||||||
|
|
||||||
|
You enable it by specifying this extension and a preedit style of |
||||||
|
C<OnTheSpot>, i.e.: |
||||||
|
|
||||||
|
urxvt -pt OnTheSpot -pe xim-onthespot |
||||||
|
|
||||||
|
=cut |
||||||
|
|
||||||
|
# |
||||||
|
# problems with this implementation include |
||||||
|
# |
||||||
|
# - primary, secondary, tertiary are NO different to other highlighting styles |
||||||
|
# - if rend values are missing, they are not being interpolated |
||||||
|
# |
||||||
|
|
||||||
|
my $SIZEOF_LONG = length pack "l!", 0; |
||||||
|
|
||||||
|
sub refresh { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
delete $self->{overlay}; |
||||||
|
|
||||||
|
my $text = $self->{text}; |
||||||
|
|
||||||
|
return unless length $text; |
||||||
|
|
||||||
|
my ($row, $col) = $self->screen_cur; |
||||||
|
|
||||||
|
my $idx = 0; |
||||||
|
|
||||||
|
my @rend = map { |
||||||
|
my $rstyle = $self->{caret} == $idx ? urxvt::OVERLAY_RSTYLE : $self->rstyle; |
||||||
|
|
||||||
|
$rstyle |= urxvt::RS_Uline if $_ & (urxvt::XIMUnderline | urxvt::XIMPrimary); |
||||||
|
$rstyle |= urxvt::RS_RVid if $_ & (urxvt::XIMReverse | urxvt::XIMSecondary); |
||||||
|
$rstyle |= urxvt::RS_Italic if $_ & (urxvt::XIMHighlight | urxvt::XIMTertiary); |
||||||
|
|
||||||
|
($rstyle) x ($self->strwidth (substr $text, $idx++, 1)) |
||||||
|
} unpack "l!*", $self->{rend}; |
||||||
|
|
||||||
|
if ($self->{caret} >= length $text) { |
||||||
|
$text .= " "; |
||||||
|
push @rend, urxvt::OVERLAY_RSTYLE; |
||||||
|
} |
||||||
|
|
||||||
|
$self->{overlay} = $self->overlay ($col, $row, $self->strwidth ($text), 1, $self->rstyle, 0); |
||||||
|
$self->{overlay}->set (0, 0, $self->special_encode ($text), \@rend); |
||||||
|
} |
||||||
|
|
||||||
|
sub on_xim_preedit_start { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_xim_preedit_done { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
delete $self->{overlay}; |
||||||
|
delete $self->{text}; |
||||||
|
delete $self->{rend}; |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
sub on_xim_preedit_draw { |
||||||
|
my ($self, $caret, $pos, $len, $feedback, $chars) = @_; |
||||||
|
|
||||||
|
$self->{caret} = $caret; |
||||||
|
|
||||||
|
substr $self->{rend}, $pos * $SIZEOF_LONG, $len * $SIZEOF_LONG, $feedback; |
||||||
|
substr $self->{text}, $pos , $len , $chars if defined $feedback || !defined $chars; |
||||||
|
|
||||||
|
$self->refresh; |
||||||
|
|
||||||
|
() |
||||||
|
} |
||||||
|
|
||||||
|
|
@ -0,0 +1,678 @@ |
|||||||
|
#! perl |
||||||
|
# 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/>. |
||||||
|
# |
||||||
|
## |
||||||
|
## Tabbed plugin for rxvt-unicode |
||||||
|
## Modified by Michal Nazarewicz (mina86/AT/mina86.com), StephenB |
||||||
|
## (mail4stb/AT/gmail.com), Steven Merrill |
||||||
|
## <steven dot merrill at gmail.com>, Mark Pustjens |
||||||
|
## <pustjens@dds.nl> and more... |
||||||
|
## |
||||||
|
## The following has been added: |
||||||
|
## |
||||||
|
## 1. Depending on time of last activity, activity character differs. |
||||||
|
## By default, after 4 seconds an asterisk becomes a plus sing, |
||||||
|
## after next 4 it becomes a colon, and finally, after another 8 |
||||||
|
## seconds it becomes a dot. This can be configured via |
||||||
|
## tabbar-timeouts resource. It's format is: |
||||||
|
## |
||||||
|
## ( <timeout> ":" <character> ":" )* <timeout> ":" <character> ":" |
||||||
|
## |
||||||
|
## where <timeout> is timeout in seconds and <character> is |
||||||
|
## a single activity character. |
||||||
|
## |
||||||
|
## 2. The "[NEW]" button can be disabled (who on Earth uses mouse to |
||||||
|
## create new tab anyways?) by setting new-button resource to yes. |
||||||
|
## |
||||||
|
## 3. If title resource is true, tab's title is displayed after last |
||||||
|
## button. This is handy if you have terminal with no window |
||||||
|
## decorations. Colours can be configured via title-fg and |
||||||
|
## title-bg. |
||||||
|
## |
||||||
|
## 4. Incorporated Alexey Semenko <asemenko at gmail.com> patch adding |
||||||
|
## autohide resource. If it's true tab bar is hidden if there is |
||||||
|
## no more then one tab opened. |
||||||
|
## |
||||||
|
## 5. Tabs are indexed in starting with zero hex. :] If you're such |
||||||
|
## a geek to use urxvt it shouldn't be a problem for you and it |
||||||
|
## saves few character when many tabs are opened. |
||||||
|
## |
||||||
|
## 6. As a minor modification: Final pipe character is removed (unless |
||||||
|
## title is displayed). This make tab bar look nicer. |
||||||
|
## |
||||||
|
## Added by StephenB: |
||||||
|
## |
||||||
|
## 7. Tabs can be named with Shift+Up (Enter to confirm, Escape to |
||||||
|
## cancel). |
||||||
|
## |
||||||
|
## 8. "[NEW]" button disabled by default. |
||||||
|
## |
||||||
|
## Added by Steven Merrill <steven dot merrill at gmail.com> |
||||||
|
## |
||||||
|
## 9. Ability to start a new tab or cycle through tabs via user |
||||||
|
## commands: tabbedex:(new|next|prev)_tab . |
||||||
|
## e.g. (in .Xdefaults) URxvt.keysym.M-t: perl:tabbedex:new_tab |
||||||
|
## (see the urxvt man file for more info about keysym) |
||||||
|
## |
||||||
|
## 10. Fix an issue whereby on_user_command would not properly get sent |
||||||
|
## to other extension packages if the mouse was not over the urxvt |
||||||
|
## window. |
||||||
|
## |
||||||
|
## Added by Thomas Jost: |
||||||
|
## |
||||||
|
## 11. Add several user commands: tabbedex:rename_tab, |
||||||
|
## tabbedex:move_tab_(left|right). |
||||||
|
## e.g. (see 9.) URxvt.keysym.C-S-Left: perl:tabbex:move_tab_left |
||||||
|
## |
||||||
|
## 12. Ability to disable the default keybindings using the |
||||||
|
## no-tabbedex-keys resource. |
||||||
|
## |
||||||
|
## Added by xanf (Illya Klymov): |
||||||
|
## |
||||||
|
## 13. Ability to display non-latin characters in tab title. |
||||||
|
## |
||||||
|
## Added by jpkotta: |
||||||
|
## |
||||||
|
## 14. Tabs inherit command line options. |
||||||
|
## |
||||||
|
## Added by Mark Pustjens <pustjens@dds.nl> |
||||||
|
## |
||||||
|
## 15. Resources are now read respecting the -name option. |
||||||
|
## |
||||||
|
## 16. Ability to prevent the last tab from closing. |
||||||
|
## Use the following in your ~/.Xdefaults to enable: |
||||||
|
## URXvt.tabbed.reopen-on-close: yes |
||||||
|
## |
||||||
|
|
||||||
|
use Encode qw(decode); |
||||||
|
|
||||||
|
sub update_autohide { |
||||||
|
my ($self, $reconfigure) = @_; |
||||||
|
my $oldh = $self->{tabheight}; |
||||||
|
if ($self->{autohide} && @{ $self->{tabs} } <= 1 && |
||||||
|
! (@{ $self->{tabs} } == 1 && $self->{tabs}[-1]->{name})) { |
||||||
|
$self->{tabheight} = 0; |
||||||
|
} else { |
||||||
|
$self->{tabheight} = $self->{maxtabheight}; |
||||||
|
} |
||||||
|
if ($reconfigure && $self->{tabheight} != $oldh) { |
||||||
|
$self->configure; |
||||||
|
$self->copy_properties; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
sub tab_activity_mark ($$) { |
||||||
|
my ($self, $tab) = @_; |
||||||
|
return ' ' unless defined $tab->{lastActivity}; |
||||||
|
return ' ' if $tab == $self->{cur}; |
||||||
|
if (defined $self->{timeouts}) { |
||||||
|
my $diff = int urxvt::NOW - $tab->{lastActivity}; |
||||||
|
for my $spec (@{ $self->{timeouts} }) { |
||||||
|
return $spec->[1] if $diff > $spec->[0]; |
||||||
|
} |
||||||
|
} |
||||||
|
'*'; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
sub refresh { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
# autohide makes it zero |
||||||
|
return unless $self->{tabheight}; |
||||||
|
|
||||||
|
my $ncol = $self->ncol; |
||||||
|
|
||||||
|
my $text = " " x $ncol; |
||||||
|
my $rend = [($self->{rs_tabbar}) x $ncol]; |
||||||
|
|
||||||
|
my ($ofs, $idx, @ofs) = (0, 0); |
||||||
|
|
||||||
|
if ($self->{new_button}) { |
||||||
|
substr $text, 0, 7, "[NEW] |"; |
||||||
|
@$rend[0 .. 5] = ($self->{rs_tab}) x 6; |
||||||
|
push @ofs, [0, 6, -1 ]; |
||||||
|
$ofs = 7; |
||||||
|
} |
||||||
|
|
||||||
|
for my $tab (@{ $self->{tabs} }) { |
||||||
|
my $name = $tab->{name} ? $tab->{name} : $idx; |
||||||
|
my $act = $self->tab_activity_mark($tab); |
||||||
|
my $txt = sprintf "%s%s%s", $act, $name, $act; |
||||||
|
my $len = length $txt; |
||||||
|
|
||||||
|
substr $text, $ofs, $len + 1, "$txt|"; |
||||||
|
@$rend[$ofs .. $ofs + $len - 1] = ($self->{rs_tab}) x $len |
||||||
|
if $tab == $self->{cur}; |
||||||
|
|
||||||
|
push @ofs, [ $ofs, $ofs + $len, $idx ]; |
||||||
|
++$idx; |
||||||
|
$ofs += $len + 1; |
||||||
|
} |
||||||
|
|
||||||
|
substr $text, --$ofs, 1, ' '; # remove last '|' |
||||||
|
|
||||||
|
if ($self->{tab_title} && $ofs + 3 < $ncol) { |
||||||
|
my $term = $self->{term}; |
||||||
|
my @str = $term->XGetWindowProperty($term->parent, $self->{tab_title}); |
||||||
|
if (@str && $str[2]) { |
||||||
|
my $str = '| ' . decode("utf8", $str[2]); |
||||||
|
my $len = length $str; |
||||||
|
$len = $ncol - $ofs if $ofs + $len > $ncol; |
||||||
|
substr $text, $ofs, $len, substr $str, 0, $len; |
||||||
|
@$rend[$ofs + 2 .. $ofs + $len - 1] = ($self->{rs_title}) x ($len - 2); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$self->{tabofs} = \@ofs; |
||||||
|
|
||||||
|
$self->ROW_t (0, $text, 0, 0, $ncol); |
||||||
|
$self->ROW_r (0, $rend, 0, 0, $ncol); |
||||||
|
|
||||||
|
$self->want_refresh; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
sub new_tab { |
||||||
|
my ($self, @argv) = @_; |
||||||
|
|
||||||
|
my $offset = $self->fheight; |
||||||
|
|
||||||
|
$self->{tabheight} = $self->{maxtabheight} |
||||||
|
unless $self->{autohide} && !(defined $self->{tabs} && @{ $self->{tabs} }); |
||||||
|
|
||||||
|
# save a backlink to us, make sure tabbedex is inactive |
||||||
|
push @urxvt::TERM_INIT, sub { |
||||||
|
my ($term) = @_; |
||||||
|
$term->{parent} = $self; |
||||||
|
|
||||||
|
for (0 .. urxvt::NUM_RESOURCES - 1) { |
||||||
|
my $value = $self->{resource}[$_]; |
||||||
|
|
||||||
|
$term->resource ("+$_" => $value) |
||||||
|
if defined $value; |
||||||
|
} |
||||||
|
|
||||||
|
foreach my $opt (keys %urxvt::OPTION) { |
||||||
|
my $value = $self->{option}{$opt}; |
||||||
|
$term->option($urxvt::OPTION{$opt}, $value); |
||||||
|
} |
||||||
|
|
||||||
|
$term->resource (perl_ext_2 => $term->resource ("perl_ext_2") . ",-tabbedex"); |
||||||
|
}; |
||||||
|
|
||||||
|
push @urxvt::TERM_EXT, urxvt::ext::tabbedex::tab::; |
||||||
|
|
||||||
|
my $term = new urxvt::term |
||||||
|
$self->env, $urxvt::RXVTNAME, |
||||||
|
-embed => $self->parent, |
||||||
|
@argv; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
sub configure { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
my $tab = $self->{cur}; |
||||||
|
|
||||||
|
# this is an extremely dirty way to force a configurenotify, but who cares |
||||||
|
$tab->XMoveResizeWindow ( |
||||||
|
$tab->parent, |
||||||
|
0, $self->{tabheight} + 1, |
||||||
|
$self->width, $self->height - $self->{tabheight} |
||||||
|
); |
||||||
|
$tab->XMoveResizeWindow ( |
||||||
|
$tab->parent, |
||||||
|
0, $self->{tabheight}, |
||||||
|
$self->width, $self->height - $self->{tabheight} |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
sub copy_properties { |
||||||
|
my ($self) = @_; |
||||||
|
my $tab = $self->{cur}; |
||||||
|
|
||||||
|
my $wm_normal_hints = $self->XInternAtom ("WM_NORMAL_HINTS"); |
||||||
|
|
||||||
|
my $current = delete $self->{current_properties}; |
||||||
|
|
||||||
|
# pass 1: copy over properties different or nonexisting |
||||||
|
for my $atom ($tab->XListProperties ($tab->parent)) { |
||||||
|
my ($type, $format, $items) = $self->XGetWindowProperty ($tab->parent, $atom); |
||||||
|
|
||||||
|
# fix up size hints |
||||||
|
if ($atom == $wm_normal_hints) { |
||||||
|
my (@hints) = unpack "l!*", $items; |
||||||
|
|
||||||
|
$hints[$_] += $self->{tabheight} for (4, 6, 16); |
||||||
|
|
||||||
|
$items = pack "l!*", @hints; |
||||||
|
} |
||||||
|
|
||||||
|
my $cur = delete $current->{$atom}; |
||||||
|
|
||||||
|
# update if changed, we assume empty items and zero type and format will not happen |
||||||
|
$self->XChangeProperty ($self->parent, $atom, $type, $format, $items) |
||||||
|
if $cur->[0] != $type or $cur->[1] != $format or $cur->[2] ne $items; |
||||||
|
|
||||||
|
$self->{current_properties}{$atom} = [$type, $format, $items]; |
||||||
|
} |
||||||
|
|
||||||
|
# pass 2, delete all extraneous properties |
||||||
|
$self->XDeleteProperty ($self->parent, $_) for keys %$current; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
sub my_resource { |
||||||
|
my $self = shift; |
||||||
|
$self->x_resource ("tabbed.$_[0]"); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
sub make_current { |
||||||
|
my ($self, $tab) = @_; |
||||||
|
|
||||||
|
if (my $cur = $self->{cur}) { |
||||||
|
delete $cur->{lastActivity}; |
||||||
|
$cur->XUnmapWindow ($cur->parent) if $cur->mapped; |
||||||
|
$cur->focus_out; |
||||||
|
} |
||||||
|
|
||||||
|
$self->{cur} = $tab; |
||||||
|
|
||||||
|
$self->configure; |
||||||
|
$self->copy_properties; |
||||||
|
|
||||||
|
$tab->focus_out; # just in case, should be a nop |
||||||
|
$tab->focus_in if $self->focus; |
||||||
|
|
||||||
|
$tab->XMapWindow ($tab->parent); |
||||||
|
delete $tab->{lastActivity}; |
||||||
|
$self->refresh; |
||||||
|
|
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
sub on_focus_in { |
||||||
|
my ($self, $event) = @_; |
||||||
|
$self->{cur}->focus_in; |
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
sub on_focus_out { |
||||||
|
my ($self, $event) = @_; |
||||||
|
$self->{cur}->focus_out; |
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
sub on_key_press { |
||||||
|
my ($self, $event) = @_; |
||||||
|
$self->{cur}->key_press ($event->{state}, $event->{keycode}, $event->{time}); |
||||||
|
1; |
||||||
|
} |
||||||
|
|
||||||
|
sub on_key_release { |
||||||
|
my ($self, $event) = @_; |
||||||
|
$self->{cur}->key_release ($event->{state}, $event->{keycode}, $event->{time}); |
||||||
|
1; |
||||||
|
} |
||||||
|
|
||||||
|
sub on_button_release { |
||||||
|
my ($self, $event) = @_; |
||||||
|
|
||||||
|
if ($event->{row} == 0) { |
||||||
|
my $col = $event->{col}; |
||||||
|
for my $button (@{ $self->{tabofs} }) { |
||||||
|
last if $col < $button->[0]; |
||||||
|
next unless $col <= $button->[1]; |
||||||
|
if ($button->[2] == -1) { |
||||||
|
$self->new_tab; |
||||||
|
} else { |
||||||
|
$self->make_current($self->{tabs}[$button->[2]]); |
||||||
|
} |
||||||
|
} |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
sub on_init { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
$self->{resource} = [map $self->resource ("+$_"), 0 .. urxvt::NUM_RESOURCES - 1]; |
||||||
|
|
||||||
|
$self->resource (int_bwidth => 0); |
||||||
|
$self->resource (pty_fd => -1); |
||||||
|
|
||||||
|
$self->{option} = {}; |
||||||
|
for my $key (keys %urxvt::OPTION) { |
||||||
|
$self->{option}{$key} = $self->option($urxvt::OPTION{$key}); |
||||||
|
} |
||||||
|
|
||||||
|
# this is for the tabs terminal; order is important |
||||||
|
$self->option ($urxvt::OPTION{scrollBar}, 0); |
||||||
|
|
||||||
|
my $fg = $self->my_resource ("tabbar-fg"); |
||||||
|
my $bg = $self->my_resource ("tabbar-bg"); |
||||||
|
my $tabfg = $self->my_resource ("tab-fg"); |
||||||
|
my $tabbg = $self->my_resource ("tab-bg"); |
||||||
|
my $titfg = $self->my_resource ("title-fg"); |
||||||
|
my $titbg = $self->my_resource ("title-bg"); |
||||||
|
|
||||||
|
defined $fg or $fg = 3; |
||||||
|
defined $bg or $bg = 0; |
||||||
|
defined $tabfg or $tabfg = 0; |
||||||
|
defined $tabbg or $tabbg = 1; |
||||||
|
defined $titfg or $titfg = 2; |
||||||
|
defined $titbg or $titbg = 0; |
||||||
|
|
||||||
|
$self->{rs_tabbar} = urxvt::SET_COLOR (urxvt::DEFAULT_RSTYLE, $fg + 2, $bg + 2); |
||||||
|
$self->{rs_tab} = urxvt::SET_COLOR (urxvt::DEFAULT_RSTYLE, $tabfg + 2, $tabbg + 2); |
||||||
|
$self->{rs_title} = urxvt::SET_COLOR (urxvt::DEFAULT_RSTYLE, $titfg + 2, $titbg + 2); |
||||||
|
|
||||||
|
|
||||||
|
my $timeouts = $self->my_resource ("tabbar-timeouts"); |
||||||
|
$timeouts = '16:.:8:::4:+' unless defined $timeouts; |
||||||
|
if ($timeouts ne '') { |
||||||
|
my @timeouts; |
||||||
|
while ($timeouts =~ /^(\d+):(.)(?::(.*))?$/) { |
||||||
|
push @timeouts, [ int $1, $2 ]; |
||||||
|
$timeouts = defined $3 ? $3 : ''; |
||||||
|
} |
||||||
|
if (@timeouts) { |
||||||
|
$self->{timeouts} = [ sort { $b->[0] <=> $a-> [0] } @timeouts ]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$self->{new_button} = |
||||||
|
($self->my_resource ('new-button') or 'false') !~ /^(?:false|0|no)/i; |
||||||
|
$self->{tab_title} = |
||||||
|
($self->my_resource ('title') or 'true') !~ /^(?:false|0|no)/i; |
||||||
|
$self->{autohide} = |
||||||
|
($self->my_resource ('autohide') or 'false') !~ /^(?:false|0|no)/i; |
||||||
|
$self->{no_default_keys} = |
||||||
|
($self->my_resource ('no-tabbedex-keys') or 'false') !~ /^(?:false|0|no)/i; |
||||||
|
$self->{reopen_on_close} = |
||||||
|
($self->my_resource ('reopen-on-close') or 'false') !~ /^(?:false|0|no)/i; |
||||||
|
|
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
sub on_start { |
||||||
|
my ($self) = @_; |
||||||
|
|
||||||
|
$self->{maxtabheight} = $self->int_bwidth + $self->fheight + $self->lineSpace; |
||||||
|
$self->{tabheight} = $self->{autohide} ? 0 : $self->{maxtabheight}; |
||||||
|
|
||||||
|
$self->{running_user_command} = 0; |
||||||
|
|
||||||
|
$self->cmd_parse ("\033[?25l"); |
||||||
|
|
||||||
|
my @argv = $self->argv; |
||||||
|
|
||||||
|
do { |
||||||
|
shift @argv; |
||||||
|
} while @argv && $argv[0] ne "-e"; |
||||||
|
|
||||||
|
if ($self->{tab_title}) { |
||||||
|
$self->{tab_title} = $self->{term}->XInternAtom("_NET_WM_NAME", 1); |
||||||
|
} |
||||||
|
|
||||||
|
$self->new_tab (@argv); |
||||||
|
|
||||||
|
if (defined $self->{timeouts}) { |
||||||
|
my $interval = ($self->{timeouts}[@{ $self->{timeouts} } - 1]->[0]); |
||||||
|
$interval = int($interval / 4); |
||||||
|
$self->{timer} = urxvt::timer->new |
||||||
|
->interval($interval < 1 ? 1 : $interval) |
||||||
|
->cb ( sub { $self->refresh; } ); |
||||||
|
} |
||||||
|
|
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
sub on_configure_notify { |
||||||
|
my ($self, $event) = @_; |
||||||
|
|
||||||
|
$self->configure; |
||||||
|
$self->refresh; |
||||||
|
|
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
sub on_user_command { |
||||||
|
my ($self, $event) = @_; |
||||||
|
|
||||||
|
$self->{cur}->{term}->{parent}->tab_user_command($self->{cur}, $event, 1); |
||||||
|
|
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
sub on_wm_delete_window { |
||||||
|
my ($self) = @_; |
||||||
|
$_->destroy for @{ $self->{tabs} }; |
||||||
|
1; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
sub tab_start { |
||||||
|
my ($self, $tab) = @_; |
||||||
|
|
||||||
|
$tab->XChangeInput ($tab->parent, urxvt::PropertyChangeMask); |
||||||
|
|
||||||
|
push @{ $self->{tabs} }, $tab; |
||||||
|
|
||||||
|
# $tab->{name} ||= scalar @{ $self->{tabs} }; |
||||||
|
$self->make_current ($tab); |
||||||
|
|
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
sub tab_destroy { |
||||||
|
my ($self, $tab) = @_; |
||||||
|
|
||||||
|
if ($self->{reopen_on_close} && $#{ $self->{tabs} } == 0) { |
||||||
|
$self->new_tab; |
||||||
|
$self->make_current ($self->{tabs}[-1]); |
||||||
|
} |
||||||
|
|
||||||
|
$self->{tabs} = [ grep $_ != $tab, @{ $self->{tabs} } ]; |
||||||
|
$self->update_autohide (); |
||||||
|
|
||||||
|
if (@{ $self->{tabs} }) { |
||||||
|
if ($self->{cur} == $tab) { |
||||||
|
delete $self->{cur}; |
||||||
|
$self->make_current ($self->{tabs}[-1]); |
||||||
|
} else { |
||||||
|
$self->refresh; |
||||||
|
} |
||||||
|
} else { |
||||||
|
# delay destruction a tiny bit |
||||||
|
$self->{destroy} = urxvt::iw->new->start->cb (sub { $self->destroy }); |
||||||
|
} |
||||||
|
|
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
sub tab_key_press { |
||||||
|
my ($self, $tab, $event, $keysym, $str) = @_; |
||||||
|
|
||||||
|
if ($tab->{is_inputting_name}) { |
||||||
|
if ($keysym == 0xff0d || $keysym == 0xff8d) { # enter |
||||||
|
$tab->{name} = $tab->{new_name}; |
||||||
|
$tab->{is_inputting_name} = 0; |
||||||
|
$self->update_autohide (1); |
||||||
|
} elsif ($keysym == 0xff1b) { # escape |
||||||
|
$tab->{name} = $tab->{old_name}; |
||||||
|
$tab->{is_inputting_name} = 0; |
||||||
|
$self->update_autohide (1); |
||||||
|
} elsif ($keysym == 0xff08) { # backspace |
||||||
|
substr $tab->{new_name}, -1, 1, ""; |
||||||
|
$tab->{name} = "$tab->{new_name}█"; |
||||||
|
} elsif ($str !~ /[\x00-\x1f\x80-\xaf]/) { |
||||||
|
$tab->{new_name} .= $str; |
||||||
|
$tab->{name} = "$tab->{new_name}█"; |
||||||
|
} |
||||||
|
$self->refresh; |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
return () if ($self->{no_default_keys}); |
||||||
|
|
||||||
|
if ($event->{state} & urxvt::ShiftMask) { |
||||||
|
if ($keysym == 0xff51 || $keysym == 0xff53) { |
||||||
|
if (@{ $self->{tabs} } > 1) { |
||||||
|
$self->change_tab($tab, $keysym - 0xff52); |
||||||
|
} |
||||||
|
return 1; |
||||||
|
|
||||||
|
} elsif ($keysym == 0xff54) { |
||||||
|
$self->new_tab; |
||||||
|
return 1; |
||||||
|
|
||||||
|
} elsif ($keysym == 0xff52) { |
||||||
|
$self->rename_tab($tab); |
||||||
|
return 1; |
||||||
|
} |
||||||
|
} elsif ($event->{state} & urxvt::ControlMask) { |
||||||
|
if ($keysym == 0xff51 || $keysym == 0xff53) { |
||||||
|
$self->move_tab($tab, $keysym - 0xff52); |
||||||
|
return 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
sub tab_property_notify { |
||||||
|
my ($self, $tab, $event) = @_; |
||||||
|
|
||||||
|
$self->copy_properties |
||||||
|
if $event->{window} == $tab->parent; |
||||||
|
|
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
sub tab_add_lines { |
||||||
|
my ($self, $tab) = @_; |
||||||
|
my $mark = $self->tab_activity_mark($tab); |
||||||
|
$tab->{lastActivity} = int urxvt::NOW; |
||||||
|
$self->refresh if $mark ne $self->tab_activity_mark($tab); |
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
sub tab_user_command { |
||||||
|
my ($self, $tab, $cmd, $proxy_events) = @_; |
||||||
|
|
||||||
|
if ($cmd eq 'tabbedex:new_tab') { |
||||||
|
$self->new_tab; |
||||||
|
} |
||||||
|
elsif ($cmd eq 'tabbedex:next_tab') { |
||||||
|
$self->change_tab($tab, 1); |
||||||
|
} |
||||||
|
elsif ($cmd eq 'tabbedex:prev_tab') { |
||||||
|
$self->change_tab($tab, -1); |
||||||
|
} |
||||||
|
elsif ($cmd eq 'tabbedex:move_tab_left') { |
||||||
|
$self->move_tab($tab, -1); |
||||||
|
} |
||||||
|
elsif ($cmd eq 'tabbedex:move_tab_right') { |
||||||
|
$self->move_tab($tab, 1); |
||||||
|
} |
||||||
|
elsif ($cmd eq 'tabbedex:rename_tab') { |
||||||
|
$self->rename_tab($tab); |
||||||
|
} |
||||||
|
else { |
||||||
|
# Proxy the user command through to the tab's term, while taking care not |
||||||
|
# to get caught in an infinite loop. |
||||||
|
if ($proxy_events && $self->{running_user_command} == 0) { |
||||||
|
$self->{running_user_command} = 1; |
||||||
|
urxvt::invoke($tab->{term}, 20, $cmd); |
||||||
|
$self->{running_user_command} = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
sub change_tab { |
||||||
|
my ($self, $tab, $direction) = @_; |
||||||
|
|
||||||
|
my $idx = 0; |
||||||
|
++$idx while $self->{tabs}[$idx] != $tab; |
||||||
|
$idx += $direction; |
||||||
|
$self->make_current ($self->{tabs}[$idx % @{ $self->{tabs}}]); |
||||||
|
|
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
sub move_tab { |
||||||
|
my ($self, $tab, $direction) = @_; |
||||||
|
|
||||||
|
if (@{ $self->{tabs} } > 1) { |
||||||
|
my $idx1 = 0; |
||||||
|
++$idx1 while $self->{tabs}[$idx1] != $tab; |
||||||
|
my $idx2 = ($idx1 + $direction) % @{ $self->{tabs} }; |
||||||
|
|
||||||
|
($self->{tabs}[$idx1], $self->{tabs}[$idx2]) = |
||||||
|
($self->{tabs}[$idx2], $self->{tabs}[$idx1]); |
||||||
|
$self->make_current ($self->{tabs}[$idx2]); |
||||||
|
} |
||||||
|
|
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
sub rename_tab { |
||||||
|
my ($self, $tab) = @_; |
||||||
|
|
||||||
|
$tab->{is_inputting_name} = 1; |
||||||
|
$tab->{old_name} = $tab->{name} ? $tab->{name} : ""; |
||||||
|
$tab->{new_name} = ""; |
||||||
|
$tab->{name} = "█"; |
||||||
|
$self->update_autohide (1); |
||||||
|
$self->refresh; |
||||||
|
|
||||||
|
(); |
||||||
|
} |
||||||
|
|
||||||
|
package urxvt::ext::tabbedex::tab; |
||||||
|
|
||||||
|
# helper extension implementing the subwindows of a tabbed terminal. |
||||||
|
# simply proxies all interesting calls back to the tabbedex class. |
||||||
|
|
||||||
|
{ |
||||||
|
for my $hook (qw(start destroy user_command key_press property_notify add_lines)) { |
||||||
|
eval qq{ |
||||||
|
sub on_$hook { |
||||||
|
my \$parent = \$_[0]{term}{parent} |
||||||
|
or return; |
||||||
|
\$parent->tab_$hook (\@_) |
||||||
|
} |
||||||
|
}; |
||||||
|
die if $@; |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue