Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/dotfiles
Path: blob/master/roles/users/files/irssi/scripts/adv_windowlist.pl
245 views
1
use strict;
2
use warnings;
3
4
our $VERSION = '1.8'; # 63feb35d8b0a8b6
5
our %IRSSI = (
6
authors => 'Nei',
7
contact => 'Nei @ [email protected]',
8
url => "http://anti.teamidiot.de/",
9
name => 'adv_windowlist',
10
description => 'Adds a permanent advanced window list on the right or in a status bar.',
11
sbitems => 'awl_shared',
12
license => 'GNU GPLv2 or later',
13
);
14
15
# UPGRADE NOTE
16
# ============
17
# for users of 0.7 or earlier series, please note that appearance
18
# settings have moved to /format, i.e. inside your theme!
19
# the fifo (screen) has been replaced by an external viewer script
20
21
# Usage
22
# =====
23
# copy the script to ~/.irssi/scripts/
24
#
25
# In irssi:
26
#
27
# /run adv_windowlist
28
#
29
# In your shell (for example a tmux split):
30
#
31
# perl ~/.irssi/scripts/adv_windowlist.pl
32
#
33
# To use sbar mode instead:
34
#
35
# /toggle awl_viewer
36
#
37
# Hint: to get rid of the old [Act:] display
38
# /statusbar window remove act
39
#
40
# to get it back:
41
# /statusbar window add -after lag -priority 10 act
42
43
# Options
44
# =======
45
# formats can be cleared with /format -delete
46
#
47
# /format awl_display_(no)key(_active|_visible) <string>
48
# * string : Format String for one window. The following $'s are expanded:
49
# $C : Name
50
# $N : Number of the Window
51
# $Q : meta-Keymap
52
# $H : Start hilighting
53
# $S : Stop hilighting
54
# /+++++++++++++++++++++++++++++++++,
55
# | **** I M P O R T A N T : **** |
56
# | |
57
# | don't forget to use $S if you |
58
# | used $H before! |
59
# | |
60
# '+++++++++++++++++++++++++++++++++/
61
# key : a key binding that goes to this window could be detected in /bind
62
# nokey : no such key binding was detected
63
# active : window would receive the input you are currently typing
64
# visible : window is also visible on screen but not active (a split window)
65
#
66
# /format awl_name_display <string>
67
# * string : Format String for window names
68
# $0 : name as formatted by the settings
69
#
70
# /format awl_display_header <string>
71
# * string : Format String for this header line. The following $'s are expanded:
72
# $C : network tag
73
#
74
# /format awl_separator(2) <string>
75
# * string : Character to use between the channel entries
76
# variant 2 can be used for alternating separators (only in status bar
77
# without block display)
78
#
79
# /format awl_abbrev_chars <string>
80
# * string : Character to use when shortening long names. The second character
81
# will be used if two blocks need to be filled.
82
#
83
# /format awl_title <string>
84
# * string : Text to display in the title string or title bar
85
#
86
# /format awl_viewer_item_bg <string>
87
# * string : Format String specifying the viewer's item background colour
88
#
89
# /set awl_prefer_name <ON|OFF>
90
# * this setting decides whether awl will use the active_name (OFF) or the
91
# window name as the name/caption in awl_display_*.
92
# That way you can rename windows using /window name myownname.
93
#
94
# /set awl_hide_empty <num>
95
# * if visible windows without items should be hidden from the window list
96
# set it to 0 to show all windows
97
# 1 to hide visible windows without items (negative exempt
98
# active window)
99
#
100
# /set awl_detach <list>
101
# * list of windows that should be hidden from the window list. you
102
# can also use /awl detach and /awl attach to manage this
103
# setting. an optional data_level can be specified with ",num"
104
#
105
# /set awl_detach_data <num>
106
# * num : hide the detached window if its data_level is below num
107
#
108
# /set awl_detach_aht <ON|OFF>
109
# * if enabled, also detach all windows listed in the
110
# activity_hide_targets setting
111
#
112
# /set awl_hide_data <num>
113
# * num : hide the window if its data_level is below num
114
# set it to 0 to basically disable this feature,
115
# 1 if you don't want windows without activity to be shown
116
# 2 to show only those windows with channel text or hilight
117
# 3 to show only windows with hilight (negative exempt active window)
118
#
119
# /set awl_hide_name_data <num>
120
# * num : hide the name of the window if its data_level is below num
121
# (only works in status bar without block display)
122
# you will want to change your formats to add $H...$S around $Q or $N
123
# if you plan to use this
124
#
125
# /set awl_maxlines <num>
126
# * num : number of lines to use for the window list (0 to disable, negative
127
# lock)
128
#
129
# /set awl_maxcolumns <num>
130
# * num : number of columns to use for the window list when using the
131
# tmux integration (0 to disable)
132
#
133
# /set awl_block <num>
134
# * num : width of a column in viewer mode (negative values = block
135
# display in status bar mode)
136
# /+++++++++++++++++++++++++++++++++,
137
# | ****** W A R N I N G ! ****** |
138
# | |
139
# | If your block display looks |
140
# | DISTORTED, you need to add the |
141
# | following line to your .theme |
142
# | file under |
143
# | abstracts = { : |
144
# | |
145
# | sb_act_none = "%K$*"; |
146
# | |
147
# '+++++++++++++++++++++++++++++++++/
148
#
149
# /set awl_sbar_maxlength <ON|OFF>
150
# * if you enable the maxlength setting, the block width will be used as a
151
# maximum length for the non-block status bar mode too.
152
#
153
# /set awl_height_adjust <num>
154
# * num : how many lines to leave empty in viewer mode
155
#
156
# /set awl_sort <-data_level|-last_line|refnum>
157
# * you can change the window sort order with this variable
158
# -data_level : sort windows with hilight first
159
# -last_line : sort windows in order of activity
160
# refnum : sort windows by window number
161
# active/server/tag : sort by server name
162
# lru : sort windows with the last recently used last
163
# "-" reverses the sort order
164
# typechecks are supported via ::, e.g. active::Query or active::Irc::Query
165
# undefinedness can be checked with ~, e.g. ~active
166
# string comparison can be done with =, e.g. name=(status)
167
# to make sort case insensitive, use #i, e.g. name#i
168
# any key in the window hash can be tested, e.g. active/chat_type=XMPP
169
# multiple criteria can be separated with , or +, e.g. -data_level+-last_line
170
#
171
# /set awl_placement <top|bottom>
172
# /set awl_position <num>
173
# * these settings correspond to /statusbar because awl will create
174
# status bars for you
175
# (see /help statusbar to learn more)
176
#
177
# /set awl_all_disable <ON|OFF>
178
# * if you set awl_all_disable to ON, awl will also remove the
179
# last status bar it created if it is empty.
180
# As you might guess, this only makes sense with awl_hide_data > 0 ;)
181
#
182
# /set awl_viewer <ON|OFF>
183
# * enable the external viewer script
184
#
185
# /set awl_viewer_launch <ON|OFF>
186
# * try to auto-launch the viewer under tmux or with a shell command
187
# /awl restart is required all auto-launch related settings to take
188
# effect
189
#
190
# /set awl_viewer_tmux_position <left|top|right|bottom|custom>
191
# * try to split in this direction when using tmux for the viewer
192
# custom : use custom_command setting
193
#
194
# /set awl_viewer_xwin_command <shell command>
195
# * custom command to run in order to start the viewer when irssi is
196
# running under X
197
# %A - gets replaced by the command to run the viewer
198
# %qA - additionally quote the command
199
#
200
# /set awl_viewer_custom_command <shell command>
201
# * custom command to run in order to start the viewer
202
#
203
# /set awl_viewer_launch_env <string>
204
# * specific environment settings for use on viewer auto-launch,
205
# without the AWL_ prefix
206
#
207
# /set awl_shared_sbar <left<right|OFF>
208
# * share a status bar for the first awl item, you will need to manually
209
# /statusbar window add -after lag -priority 10 awl_shared
210
# left : space in cells occupied on the left of status bar
211
# right : space occupied on the right
212
# Note: you need to replace "left" AND "right" with the appropriate numbers!
213
#
214
# /set awl_path <path>
215
# * path to the file which the viewer script reads
216
#
217
# /set fancy_abbrev <no|head|strict|fancy>
218
# * how to shorten too long names
219
# no : shorten in the middle
220
# head : always cut off the ends
221
# strict : shorten repeating substrings
222
# fancy : combination of no+strict
223
#
224
# /set awl_custom_xform <perl code>
225
# * specify a custom routine to transform window names
226
# example: s/^#// remove the #-mark of IRC channels
227
# the special flags $CHANNEL / $TAG / $QUERY / $NAME can be
228
# tested in conditionals
229
#
230
# /set awl_last_line_shade <timeout>
231
# * set timeout to shade activity base colours, to enable
232
# you also need to add +-last_line to awl_sort
233
# (requires 256 colour support)
234
#
235
# /set awl_no_mode_hint <ON|OFF>
236
# * whether to show the hint of running the viewer script in the
237
# status bar
238
#
239
# /set awl_mouse <ON|OFF>
240
# * enable the terminal mouse in irssi
241
# (use the awl-patched mouse.pl for gestures and commands if you need
242
# them and disable mouse_escape)
243
#
244
# /set awl_mouse_offset <num>
245
# * specifies where on the screen is the awl status bar
246
# (0 = on top/bottom, 1 = one additional line in between,
247
# e.g. prompt)
248
# you MUST set this correctly otherwise the mouse coordinates will
249
# be off
250
#
251
# /set mouse_scroll <num>
252
# * how many lines the mouse wheel scrolls
253
#
254
# /set mouse_escape <num>
255
# * seconds to disable the mouse, when not clicked on the windowlist
256
#
257
258
# Commands
259
# ========
260
# /awl detach <num>
261
# * hide the current window from the window list. num specifies the
262
# data_level (optional)
263
#
264
# /awl attach
265
# * unhide the current window from the window list
266
#
267
# /awl ack
268
# * change to the next window with activity, ignoring detached windows
269
#
270
# /awl redraw
271
# * redraws the windowlist. There may be occasions where the
272
# windowlist can get destroyed so you can use this command to
273
# force a redraw.
274
#
275
# /awl restart
276
# * restart the connection to the viewer script.
277
278
# Viewer script
279
# =============
280
# When run from the command line, adv_windowlist acts as the viewer
281
# script to be used together with the irssi script to display the
282
# window list in a sidebar/terminal of its own.
283
#
284
# One optional parameter is accepted, the awl_path
285
#
286
# The viewer can be configured by three environment variables:
287
#
288
# AWL_HI9=1
289
# * interpret %9 as high-intensity toggle instead of bold. This had
290
# been the default prior to version 0.9b8
291
#
292
# AWL_AUTOFOCUS=0
293
# * disable auto-focus behaviour when activating a window
294
#
295
# AWL_NOTITLE=1
296
# * disable the title bar
297
298
# Nei =^.^= ( [email protected] )
299
300
no warnings 'redefine';
301
use constant IN_IRSSI => __PACKAGE__ ne 'main' || $ENV{IRSSI_MOCK};
302
use constant SCRIPT_FILE => __FILE__;
303
no if !IN_IRSSI, strict => (qw(subs refs));
304
use if IN_IRSSI, Irssi => ();
305
use if IN_IRSSI, 'Irssi::TextUI' => ();
306
use v5.10;
307
use Encode;
308
use Storable ();
309
use IO::Socket::UNIX;
310
use List::Util qw(min max reduce);
311
use Hash::Util qw(lock_keys);
312
use Text::ParseWords qw(shellwords);
313
314
BEGIN {
315
if ($] < 5.012) {
316
*CORE::GLOBAL::length = *CORE::GLOBAL::length = sub (_) {
317
defined $_[0] ? CORE::length($_[0]) : undef
318
};
319
}
320
*Irssi::active_win = {}; # hide incorrect warning
321
}
322
323
unless (IN_IRSSI) {
324
local *_ = \@ARGV;
325
&AwlViewer::main;
326
exit;
327
}
328
329
330
use constant GLOB_QUEUE_TIMER => 100;
331
332
our $BLOCK_ALL; # localized blocker
333
my @actString; # status bar texts
334
my @win_items;
335
my $currentLines = 0;
336
my %awins;
337
my $globTime; # timer to limit remake calls
338
339
my %CHANGED;
340
my $VIEWER_MODE;
341
my $MOUSE_ON;
342
my %mouse_coords;
343
my %statusbars;
344
my %S; # settings
345
my $settings_str = '1';
346
my $window_sort_func;
347
my $custom_xform;
348
my ($sb_base_width, $sb_base_width_pre, $sb_base_width_post);
349
my $print_text_activity;
350
my $shade_line_timer;
351
my ($screenHeight, $screenWidth);
352
my %viewer;
353
354
my (%keymap, %nummap, %wnmap, %specialmap, %wnmap_exp, %custom_key_map);
355
my %banned_channels;
356
my %detach_map;
357
my %abbrev_cache;
358
359
use constant setc => 'awl';
360
361
sub set ($) {
362
setc . '_' . $_[0]
363
}
364
365
sub add_statusbar {
366
for (@_) {
367
# add subs
368
my $l = set $_;
369
{
370
my $close = $_;
371
no strict 'refs';
372
*{$l} = sub { awl($close, @_) };
373
}
374
Irssi::command("^statusbar $l reset");
375
Irssi::command("statusbar $l enable");
376
if (lc $S{placement} eq 'top') {
377
Irssi::command("statusbar $l placement top");
378
}
379
if (my $x = $S{position}) {
380
Irssi::command("statusbar $l position $x");
381
}
382
Irssi::command("statusbar $l add -priority 100 -alignment left barstart");
383
Irssi::command("statusbar $l add $l");
384
Irssi::command("statusbar $l add -priority 100 -alignment right barend");
385
Irssi::command("statusbar $l disable");
386
Irssi::statusbar_item_register($l, '$0', $l);
387
$statusbars{$_} = 1;
388
Irssi::command("statusbar $l enable");
389
}
390
}
391
392
sub remove_statusbar {
393
for (@_) {
394
my $l = set $_;
395
Irssi::command("statusbar $l disable");
396
Irssi::command("statusbar $l reset");
397
Irssi::statusbar_item_unregister($l);
398
{
399
no strict 'refs';
400
undef &{$l};
401
}
402
delete $statusbars{$_};
403
}
404
}
405
406
my $awl_shared_empty = sub {
407
return if $BLOCK_ALL;
408
my ($item, $get_size_only) = @_;
409
$item->default_handler($get_size_only, '', '', 0);
410
};
411
412
sub syncLines {
413
my $maxLines = $S{maxlines};
414
my $newLines = ($maxLines > 0 and @actString > $maxLines) ?
415
$maxLines :
416
($maxLines < 0) ?
417
-$maxLines :
418
@actString;
419
$currentLines = 1 if !$currentLines && $S{shared_sbar};
420
if ($S{shared_sbar} && !$statusbars{shared}) {
421
my $l = set 'shared';
422
{
423
no strict 'refs';
424
*{$l} = sub {
425
return if $BLOCK_ALL;
426
my ($item, $get_size_only) = @_;
427
428
my $text = $actString[0];
429
my $title = _get_format(set 'title');
430
if (length $title) {
431
$title =~ s{\\(.)|(.)}{
432
defined $2 ? quotemeta $2
433
: $1 eq 'V' ? '\u'
434
: $1 eq ':' ? quotemeta ':%n'
435
: $1 =~ /^[uUFQE]$/ ? "\\$1"
436
: quotemeta "\\$1"
437
}sge;
438
$title = eval qq{"$title"};
439
$title .= ' ';
440
}
441
my $pat = defined $text ? "{sb $title\$*}" : '{sb }';
442
$text //= '';
443
$item->default_handler($get_size_only, $pat, $text, 0);
444
};
445
}
446
$statusbars{shared} = 1;
447
remove_statusbar (0) if $statusbars{0};
448
}
449
elsif ($statusbars{shared} && !$S{shared_sbar}) {
450
add_statusbar (0) if $currentLines && $newLines;
451
delete $statusbars{shared};
452
my $l = set 'shared';
453
{
454
no strict 'refs';
455
*{$l} = $awl_shared_empty;
456
}
457
}
458
if ($currentLines == $newLines) { return; }
459
elsif ($newLines > $currentLines) {
460
add_statusbar ($currentLines .. ($newLines - 1));
461
}
462
else {
463
remove_statusbar (reverse ($newLines .. ($currentLines - 1)));
464
}
465
$currentLines = $newLines;
466
}
467
468
sub awl {
469
return if $BLOCK_ALL;
470
my ($line, $item, $get_size_only) = @_;
471
472
my $text = $actString[$line];
473
my $pat = defined $text ? '{sb $*}' : '{sb }';
474
$text //= '';
475
$item->default_handler($get_size_only, $pat, $text, 0);
476
}
477
478
# remove old statusbars
479
{ my %killBar;
480
sub get_old_status {
481
my ($textDest, $cont, $cont_stripped) = @_;
482
if ($textDest->{level} == 524288 and $textDest->{target} eq '' and !defined $textDest->{server}) {
483
my $name = quotemeta(set '');
484
if ($cont_stripped =~ m/^$name(\d+)\s/) { $killBar{$1} = 1; }
485
Irssi::signal_stop;
486
}
487
}
488
sub killOldStatus {
489
%killBar = ();
490
Irssi::signal_add_first('print text' => 'get_old_status');
491
Irssi::command('statusbar');
492
Irssi::signal_remove('print text' => 'get_old_status');
493
remove_statusbar(keys %killBar);
494
}
495
}
496
497
sub _add_map {
498
my ($type, $target, $map) = @_;
499
($type->{$target}) = sort { length $a <=> length $b || $a cmp $b }
500
$map, exists $type->{$target} ? $type->{$target} : ();
501
}
502
503
sub get_keymap {
504
my ($textDest, undef, $cont_stripped) = @_;
505
if ($textDest->{level} == 524288 and $textDest->{target} eq '' and !defined $textDest->{server}) {
506
my $one_meta_or_ctrl_key = qr/((?:meta-)*?)(?:(meta-|\^)(\S)|(\w+))/;
507
$cont_stripped = as_uni($cont_stripped);
508
if ($cont_stripped =~ m/((?:$one_meta_or_ctrl_key-)*$one_meta_or_ctrl_key)\s+(.*)$/) {
509
my ($combo, $command) = ($1, $10);
510
my $map = '';
511
while ($combo =~ s/(?:-|^)$one_meta_or_ctrl_key$//) {
512
my ($level, $ctl, $key, $nkey) = ($1, $2, $3, $4);
513
my $numlevel = ($level =~ y/-//);
514
$ctl = '' if !$ctl || $ctl ne '^';
515
$map = ('-' x ($numlevel%2)) . ('+' x ($numlevel/2)) .
516
$ctl . (defined $key ? $key : "\01$nkey\01") . $map;
517
}
518
for ($command) {
519
last unless length $map;
520
if (/^change_window (\d+)/i) {
521
_add_map(\%nummap, $1, $map);
522
}
523
elsif (/^(?:command window goto|change_window) (\S+)/i) {
524
my $window = $1;
525
if ($window !~ /\D/) {
526
_add_map(\%nummap, $window, $map);
527
}
528
elsif (lc $window eq 'active') {
529
_add_map(\%specialmap, '_active', $map);
530
}
531
else {
532
_add_map(\%wnmap, $window, $map);
533
}
534
}
535
elsif (/^(?:active_window|command ((awl )?ack))/i) {
536
_add_map(\%specialmap, '_active', $map);
537
$viewer{use_ack} = $1;
538
}
539
elsif (/^command window last/i) {
540
_add_map(\%specialmap, '_last', $map);
541
}
542
elsif (/^(?:upper_window|command window up)/i) {
543
_add_map(\%specialmap, '_up', $map);
544
}
545
elsif (/^(?:lower_window|command window down)/i) {
546
_add_map(\%specialmap, '_down', $map);
547
}
548
elsif (/^key\s+(\w+)/i) {
549
$custom_key_map{$1} = $map;
550
}
551
}
552
}
553
Irssi::signal_stop;
554
}
555
}
556
557
sub update_keymap {
558
%nummap = %wnmap = %specialmap = %custom_key_map = ();
559
Irssi::signal_remove('command bind' => 'watch_keymap');
560
Irssi::signal_add_first('print text' => 'get_keymap');
561
Irssi::command('bind');
562
Irssi::signal_remove('print text' => 'get_keymap');
563
for (keys %custom_key_map) {
564
if (exists $custom_key_map{$_} &&
565
$custom_key_map{$_} =~ s/\01(\w+)\01/exists $custom_key_map{$1} ? $custom_key_map{$1} : "\02"/ge) {
566
if ($custom_key_map{$_} =~ /\02/) {
567
delete $custom_key_map{$_};
568
}
569
else {
570
redo;
571
}
572
}
573
}
574
for my $keymap (\(%specialmap, %wnmap, %nummap)) {
575
for (keys %$keymap) {
576
if ($keymap->{$_} =~ s/\01(\w+)\01/exists $custom_key_map{$1} ? $custom_key_map{$1} : "\02"/ge) {
577
if ($keymap->{$_} =~ /\02/) {
578
delete $keymap->{$_};
579
}
580
}
581
}
582
}
583
Irssi::signal_add('command bind' => 'watch_keymap');
584
delete $viewer{client_keymap};
585
&wl_changed;
586
}
587
588
# watch keymap changes
589
sub watch_keymap {
590
Irssi::timeout_add_once(1000, 'update_keymap', undef);
591
}
592
593
{ my %strip_table = (
594
# fe-common::core::formats.c:format_expand_styles
595
# delete format_backs format_fores bold_fores other stuff
596
(map { $_ => '' } (split //, '04261537' . 'kbgcrmyw' . 'KBGCRMYW' . 'U9_8I:|FnN>#[' . 'pP')),
597
# escape
598
(map { $_ => $_ } (split //, '{}%')),
599
);
600
sub ir_strip_codes { # strip %codes
601
my $o = shift;
602
$o =~ s/(%(%|Z.{6}|z.{6}|X..|x..|.))/exists $strip_table{$2} ? $strip_table{$2} :
603
$2 =~ m{x(?:0[a-f]|[1-6][0-9a-z]|7[a-x])|z[0-9a-f]{6}}i ? '' : $1/gex;
604
$o
605
}
606
}
607
## ir_parse_special -- wrapper around parse_special
608
## $i - input format
609
## $args - array ref of arguments to format
610
## $win - different target window (default current window)
611
## $flags - different kind of escape flags (default 4|8)
612
## returns formatted str
613
sub ir_parse_special {
614
my $o;
615
my $i = shift;
616
my $args = shift // [];
617
y/ /\177/ for @$args; # hack to escape spaces
618
my $win = shift || Irssi::active_win;
619
my $flags = shift // 0x4|0x8;
620
my @cmd_args = ($i, (join ' ', @$args), $flags);
621
my $server = Irssi::active_server();
622
if (ref $win and ref $win->{active}) {
623
$o = $win->{active}->parse_special(@cmd_args);
624
}
625
elsif (ref $win and ref $win->{active_server}) {
626
$o = $win->{active_server}->parse_special(@cmd_args);
627
}
628
elsif (ref $server) {
629
$o = $server->parse_special(@cmd_args);
630
}
631
else {
632
$o = &Irssi::parse_special(@cmd_args);
633
}
634
$o =~ y/\177/ /;
635
$o
636
}
637
638
sub sb_format_expand { # Irssi::current_theme->format_expand wrapper
639
Irssi::current_theme->format_expand(
640
$_[0],
641
(
642
Irssi::EXPAND_FLAG_IGNORE_REPLACES
643
|
644
($_[1] ? 0 : Irssi::EXPAND_FLAG_IGNORE_EMPTY)
645
)
646
)
647
}
648
649
{ my $term_type = Irssi::version > 20040819 ? 'term_charset' : 'term_type';
650
if (Irssi->can('string_width')) {
651
*screen_length = sub { Irssi::string_width($_[0]) };
652
}
653
else {
654
local $@;
655
eval { require Text::CharWidth; };
656
unless ($@) {
657
*screen_length = sub { Text::CharWidth::mbswidth($_[0]) };
658
}
659
else {
660
my $err = $@; chomp $err; $err =~ s/\sat .* line \d+\.$//;
661
#Irssi::print("%_$IRSSI{name}: warning:%_ Text::CharWidth module failed to load. Length calculation may be off! Error was:");
662
print "%_$IRSSI{name}:%_ $err";
663
*screen_length = sub {
664
my $temp = shift;
665
if (lc Irssi::settings_get_str($term_type) eq 'utf-8') {
666
Encode::_utf8_on($temp);
667
}
668
length($temp)
669
};
670
}
671
}
672
sub as_uni {
673
no warnings 'utf8';
674
Encode::decode(Irssi::settings_get_str($term_type), $_[0], 0)
675
}
676
sub as_tc {
677
Encode::encode(Irssi::settings_get_str($term_type), $_[0], 0)
678
}
679
}
680
681
sub sb_length {
682
screen_length(ir_strip_codes($_[0]))
683
}
684
685
sub run_custom_xform {
686
local $@;
687
eval {
688
$custom_xform->()
689
};
690
if ($@) {
691
$@ =~ /^(.*)/;
692
print '%_'.(set 'custom_xform').'%_ died (disabling): '.$1;
693
$custom_xform = undef;
694
}
695
}
696
697
sub remove_uniform {
698
my $o = shift;
699
$o =~ s/^xmpp:(.*?[%@]).+\.[^.]+$/$1/ or
700
$o =~ s#^psyc://.+\.[^.]+/([@~].*)$#$1#;
701
if ($custom_xform) {
702
run_custom_xform() for $o;
703
}
704
$o
705
}
706
707
sub remove_uniform_vars {
708
my $win = shift;
709
my $name = __PACKAGE__ . '::custom_xform::' . $win->{active}{type}
710
if ref $win->{active} && $win->{active}{type};
711
no strict 'refs';
712
local ${$name} = 1 if $name;
713
remove_uniform(+shift);
714
}
715
716
sub lc1459 {
717
my $x = shift;
718
$x =~ y/][\\^/}{|~/;
719
lc $x
720
}
721
722
sub window_list {
723
my $i = 0;
724
map { $_->[1] } sort $window_sort_func map { [ $i++, $_ ] } Irssi::windows;
725
}
726
727
sub _calculate_abbrev {
728
my ($wins, $abbrevList) = @_;
729
if ($S{fancy_abbrev} !~ /^(no|off|head)/i) {
730
my @nameList = map { ref $_ ? remove_uniform_vars($_, as_uni($_->get_active_name) // '') : '' } @$wins;
731
for (my $i = 0; $i < @nameList - 1; ++$i) {
732
my ($x, $y) = ($nameList[$i], $nameList[$i + 1]);
733
s/^[+#!=]// for $x, $y;
734
my $res = exists $abbrev_cache{$x}{$y} ? $abbrev_cache{$x}{$y}
735
: $abbrev_cache{$x}{$y} = string_LCSS($x, $y);
736
if (defined $res) {
737
for ($nameList[$i], $nameList[$i + 1]) {
738
$abbrevList->{$_} //= int((index $_, $res) + (length $res) / 2);
739
}
740
}
741
}
742
}
743
}
744
745
my %act_last_line_shades = (
746
r => [qw[ 50 40 30 20 ]],
747
g => [qw[ 1O 1I 1C 16 ]],
748
y => [qw[ 5O 4I 3C 26 ]],
749
b => [qw[ 15 14 13 12 ]],
750
m => [qw[ 54 43 32 21 ]],
751
c => [qw[ 1S 1L 1E 17 ]],
752
w => [qw[ 7W 7T 7Q 3E ]],
753
K => [qw[ 7M 7K 27 7H ]],
754
R => [qw[ 60 50 40 30 ]],
755
G => [qw[ 1U 1O 1I 1C ]],
756
Y => [qw[ 6U 5O 4I 3C ]],
757
B => [qw[ 2B 2A 29 28 ]],
758
M => [qw[ 65 54 43 32 ]],
759
C => [qw[ 1Z 1S 1L 1E ]],
760
W => [qw[ 6Z 5S 7R 7O ]],
761
);
762
763
sub _format_display {
764
my (undef, $format, $cformat, $hilight, $name, $number, $key, $win) = @_;
765
if ($print_text_activity && $S{line_shade}) {
766
my @hilight_code = split /\177/, sb_format_expand("{$hilight \177}"), 2;
767
my $max_time = max(1, log($S{line_shade}) - log(1000));
768
my $time_delta = min(3, min($max_time, log(max(1, time - $win->{last_line}))) / $max_time * 3);
769
if ($hilight_code[0] =~ /%(.)/ && exists $act_last_line_shades{$1}) {
770
$hilight = 'sb_act_hilight_color %X'.$act_last_line_shades{$1}[$time_delta];
771
}
772
}
773
$cformat = '$0' unless length $cformat;
774
my %map = ('$C' => $cformat, '$N' => '$1', '$Q' => '$2');
775
$format =~ s<(\$.)><$map{$1}//$1>ge;
776
$format =~ s<\$H((?:\$.|[^\$])*?)\$S><{$hilight $1%n}>g;
777
my @ret = ir_parse_special(sb_format_expand($format), [$name, $number, $key], $win);
778
@ret
779
}
780
781
sub _get_format {
782
Irssi::current_theme->get_format(__PACKAGE__, @_)
783
}
784
785
sub _is_detached {
786
my ($win, $active_number) = @_;
787
my $level = $win->{data_level} // 0;
788
my $number = $win->{refnum};
789
my $name = lc1459( as_uni($win->{name}) );
790
my $active = lc1459( as_uni($win->get_active_name) // '' );
791
my $tag = $win->{active} && $win->{active}{server} ? lc1459( as_uni($win->{active}{server}{tag}) // '' ) : '';
792
my @cond = ($number);
793
push @cond, "$name" if length $name;
794
push @cond, "$tag/$active" if length $tag && length $active;
795
push @cond, "$active" if length $active;
796
push @cond, "$tag/*", "$tag/::all" if length $tag;
797
push @cond, "*", "::all";
798
for my $cond (@cond) {
799
if (exists $detach_map{ $cond }) {
800
my $dd = $detach_map{ $cond } // $S{detach_data};
801
return $win->{data_level} < abs $dd
802
&& ($number != $active_number || 0 <= $dd);
803
}
804
}
805
return;
806
}
807
808
sub _calculate_items {
809
my ($wins, $abbrevList) = @_;
810
811
my $display_header = _get_format(set 'display_header');
812
my $name_format = _get_format(set 'name_display');
813
my $abbrev_chars = as_uni(_get_format(set 'abbrev_chars'));
814
815
my %displays;
816
817
my $active = Irssi::active_win;
818
@win_items = ();
819
%keymap = (%nummap, %wnmap_exp);
820
821
my ($numPad, $keyPad) = (0, 0);
822
if ($VIEWER_MODE or $S{block} < 0) {
823
$numPad = length((sort { length $b <=> length $a } keys %keymap)[0]) // 0;
824
$keyPad = length((sort { length $b <=> length $a } values %keymap)[0]) // 0;
825
}
826
my $last_net;
827
my ($abbrev1, $abbrev2) = $abbrev_chars =~ /(\X)(.*)/;
828
my @abbrev_chars = ('~', "\x{301c}");
829
unless (defined $abbrev1 && screen_length(as_tc($abbrev1)) == 1) { $abbrev1 = $abbrev_chars[0] }
830
unless (length $abbrev2) {
831
$abbrev2 = $abbrev1;
832
if ($abbrev1 eq $abbrev_chars[0]) {
833
$abbrev2 = $abbrev_chars[1];
834
}
835
else {
836
$abbrev2 = $abbrev1;
837
}
838
}
839
if (screen_length(as_tc($abbrev2)) == 1) {
840
$abbrev2 x= 2;
841
}
842
while (screen_length(as_tc($abbrev2)) > 2) {
843
chop $abbrev2;
844
}
845
unless (screen_length(as_tc($abbrev2)) == 2) {
846
$abbrev2 = $abbrev_chars[1];
847
}
848
for my $win (@$wins) {
849
my $global_tag_header_mode;
850
851
next unless ref $win;
852
853
my $backup_win = Storable::dclone($win);
854
delete $backup_win->{active} unless ref $backup_win->{active};
855
856
$global_tag_header_mode =
857
$display_header && ($last_net // '') ne ($backup_win->{active}{server}{tag} // '');
858
859
if ($win->{data_level} < abs $S{hide_data}
860
&& ($win->{refnum} != $active->{refnum} || 0 <= $S{hide_data})) {
861
next; }
862
elsif (exists $awins{$win->{refnum}} && $S{hide_empty} && !$win->items
863
&& ($win->{refnum} != $active->{refnum} || 0 <= $S{hide_empty})) {
864
next; }
865
elsif (_is_detached($win, $active->{refnum})) {
866
next; }
867
868
my $colour = $win->{hilight_color} // '';
869
my $hilight = do {
870
if ($win->{data_level} == 0) { 'sb_act_none'; }
871
elsif ($win->{data_level} == 1) { 'sb_act_text'; }
872
elsif ($win->{data_level} == 2) { 'sb_act_msg'; }
873
elsif ($colour ne '') { "sb_act_hilight_color $colour"; }
874
elsif ($win->{data_level} == 3) { 'sb_act_hilight'; }
875
else { 'sb_act_special'; }
876
};
877
my $number = $win->{refnum};
878
879
my ($name, $display, $cdisplay);
880
if ($global_tag_header_mode) {
881
$display = $display_header;
882
$name = as_uni($backup_win->{active}{server}{tag}) // '';
883
if ($custom_xform) {
884
no strict 'refs';
885
local ${ __PACKAGE__ . '::custom_xform::TAG' } = 1;
886
run_custom_xform() for $name;
887
}
888
}
889
else {
890
my @display = ('display_nokey');
891
if (defined $keymap{$number} and $keymap{$number} ne '') {
892
unshift @display, map { (my $cpy = $_) =~ s/_no/_/; $cpy } @display;
893
}
894
if (exists $awins{$number}) {
895
unshift @display, map { my $cpy = $_; $cpy .= '_visible'; $cpy } @display;
896
}
897
if ($active->{refnum} == $number) {
898
unshift @display, map { my $cpy = $_; $cpy .= '_active'; $cpy }
899
grep { !/_visible$/ } @display;
900
}
901
$display = (grep { length $_ }
902
map { $displays{$_} //= _get_format(set $_) }
903
@display)[0];
904
$cdisplay = $name_format;
905
$name = as_uni($win->get_active_name) // '';
906
$name = '*' if $S{banned_on} and exists $banned_channels{lc1459($name)};
907
$name = remove_uniform_vars($win, $name) if $name ne '*';
908
if ($name ne '*' and $win->{name} ne '' and $S{prefer_name}) {
909
$name = as_uni($win->{name});
910
if ($custom_xform) {
911
no strict 'refs';
912
local ${ __PACKAGE__ . '::custom_xform::NAME' } = 1;
913
run_custom_xform() for $name;
914
}
915
}
916
917
if (!$VIEWER_MODE && $S{block} >= 0 && $S{hide_name}
918
&& $win->{data_level} < abs $S{hide_name}
919
&& ($win->{refnum} != $active->{refnum} || 0 <= $S{hide_name})) {
920
$name = '';
921
$cdisplay = '';
922
}
923
}
924
925
$display = "$display%n";
926
my $num_ent = (' 'x max(0,$numPad - length $number)) . $number;
927
my $key_ent = exists $keymap{$number} ? ((' 'x max(0,$keyPad - length $keymap{$number})) . $keymap{$number}) : ' 'x$keyPad;
928
if ($VIEWER_MODE or $S{sbar_maxlen} or $S{block} < 0) {
929
my $baseLength = sb_length(_format_display(
930
'', $display, $cdisplay, $hilight,
931
'x', # placeholder
932
$num_ent,
933
$key_ent,
934
$win)) - 1;
935
my $diff = (abs $S{block}) - (screen_length(as_tc($name)) + $baseLength);
936
if ($diff < 0) { # too long
937
my $screen_length = screen_length(as_tc($name));
938
if ((abs $diff) >= $screen_length) { $name = '' } # forget it
939
elsif ((abs $diff) + screen_length(as_tc(substr($name, 0, 1))) >= $screen_length) { $name = substr($name, 0, 1); }
940
else {
941
my $ulen = length $name;
942
my $middle2 = exists $abbrevList->{$name} ?
943
($S{fancy_strict}) ?
944
2* $abbrevList->{$name} :
945
(2*($abbrevList->{$name} + $ulen) / 3) :
946
($S{fancy_head}) ?
947
2*$ulen :
948
$ulen;
949
my $first = 1;
950
while (length $name > 1) {
951
my $cp = $middle2 >= 0 ? $middle2/2 : -1; # clearing position
952
my $rm = 2;
953
# if character at end is wider than 1 cell -> replace it with ~
954
if (screen_length(as_tc(substr $name, $cp, 1)) > 1) {
955
if ($first || $cp < 0) {
956
$rm = 1;
957
$first = undef;
958
}
959
}
960
elsif ($cp < 0) { # elsif at end -> replace last 2 characters
961
--$cp;
962
}
963
(substr $name, $cp, $rm) = $abbrev1;
964
if ($cp > -1 && $rm > 1) {
965
--$middle2;
966
}
967
my $sl = screen_length(as_tc($name));
968
if ($sl + $baseLength < abs $S{block}) {
969
(substr $name, ($middle2+1)/2, 1) = $abbrev2;
970
last;
971
}
972
elsif ($sl + $baseLength == abs $S{block}) {
973
last;
974
}
975
}
976
}
977
}
978
elsif ($VIEWER_MODE or $S{block} < 0) {
979
$name .= (' ' x $diff);
980
}
981
}
982
983
push @win_items, _format_display(
984
'', $display, $cdisplay, $hilight,
985
as_tc($name),
986
$num_ent,
987
as_tc($key_ent),
988
$win);
989
990
if ($global_tag_header_mode) {
991
$last_net = $backup_win->{active}{server}{tag};
992
redo;
993
}
994
995
$mouse_coords{refnum}{$#win_items} = $number;
996
}
997
}
998
999
sub _spread_items {
1000
my $width = $screenWidth - $sb_base_width - 1;
1001
my @separator = _get_format(set 'separator');
1002
if ($S{block} >= 0) {
1003
my $sep2 = _get_format(set 'separator2');
1004
push @separator, $sep2 if length $sep2 && $sep2 ne $separator[0];
1005
}
1006
$separator[0] .= '%n';
1007
my @sepLen = map { sb_length($_) } @separator;
1008
1009
@actString = ();
1010
my $curLine;
1011
my $curLen = 0;
1012
if ($S{shared_sbar}) {
1013
$curLen += $S{shared_sbar}[0] + 2;
1014
$width -= $S{shared_sbar}[2];
1015
}
1016
my $mouse_header_check = 0;
1017
for my $it (@win_items) {
1018
my $itemLen = sb_length($it);
1019
if ($curLen) {
1020
if ($curLen + $itemLen + $sepLen[$mouse_header_check % @sepLen] > $width) {
1021
$width += $S{shared_sbar}[2]
1022
if !@actString && $S{shared_sbar};
1023
push @actString, $curLine;
1024
$curLine = undef;
1025
$curLen = 0;
1026
}
1027
elsif (defined $curLine) {
1028
$curLine .= $separator[$mouse_header_check % @separator];
1029
$curLen += $sepLen[$mouse_header_check % @sepLen];
1030
}
1031
}
1032
$curLine .= $it;
1033
if (exists $mouse_coords{refnum}{$mouse_header_check}) {
1034
$mouse_coords{scalar @actString}{ $_ } = $mouse_coords{refnum}{$mouse_header_check}
1035
for $curLen .. $curLen + $itemLen - 1;
1036
}
1037
$curLen += $itemLen;
1038
}
1039
continue {
1040
++$mouse_header_check;
1041
}
1042
$curLen -= $S{shared_sbar}[0]
1043
if !@actString && $S{shared_sbar};
1044
push @actString, $curLine if $curLen;
1045
}
1046
1047
sub remake {
1048
my %abbrevList;
1049
my @wins = window_list();
1050
if ($VIEWER_MODE or $S{sbar_maxlen} or $S{block} < 0) {
1051
_calculate_abbrev(\@wins, \%abbrevList);
1052
}
1053
1054
%mouse_coords = ( refnum => +{} );
1055
_calculate_items(\@wins, \%abbrevList);
1056
1057
unless ($VIEWER_MODE) {
1058
_spread_items();
1059
1060
push @actString, undef unless @actString || $S{all_disable};
1061
}
1062
}
1063
1064
sub update_wl {
1065
return if $BLOCK_ALL;
1066
remake();
1067
1068
Irssi::statusbar_items_redraw(set $_) for keys %statusbars;
1069
1070
unless ($VIEWER_MODE) {
1071
Irssi::timeout_add_once(100, 'syncLines', undef);
1072
}
1073
else {
1074
syncViewer();
1075
}
1076
}
1077
1078
sub screenFullRedraw {
1079
my ($window) = @_;
1080
if (!ref $window or $window->{refnum} == Irssi::active_win->{refnum}) {
1081
$viewer{fullRedraw} = 1 if $viewer{client};
1082
$settings_str = '';
1083
&setup_changed;
1084
}
1085
}
1086
1087
sub restartViewerServer {
1088
if ($VIEWER_MODE) {
1089
stop_viewer();
1090
start_viewer();
1091
}
1092
}
1093
1094
sub _simple_quote {
1095
my @r = map {
1096
my $x = $_;
1097
$x =~ s/'/'"'"'/g;
1098
$x = "'$x'";
1099
} @_;
1100
wantarray ? @r : shift @r
1101
}
1102
1103
sub _viewer_command_replace_format {
1104
my ($ecmd, @args) = @_;
1105
my $file = _simple_quote(SCRIPT_FILE());
1106
my $path = _simple_quote($viewer{path});
1107
my @env;
1108
for my $env (shellwords($S{viewer_launch_env})) {
1109
if ($env =~ /^(\w+)(?:=(.*))$/) {
1110
push @env, "AWL_$1=$2"
1111
}
1112
}
1113
my $cmd = join ' ',
1114
(@env ? ('env', _simple_quote(@env)) : ()),
1115
'perl', $file, '-1', _simple_quote(@args), $path;
1116
$ecmd =~ s{%(%|\w+)}{
1117
my $sub = $1;
1118
if ($sub eq '%') {
1119
'%'
1120
}
1121
elsif ($sub =~ /^(q*)A(.*)/) {
1122
my $ret = $cmd;
1123
for (1..length $1) {
1124
$ret = _simple_quote($ret);
1125
}
1126
"$ret$2"
1127
}
1128
else {
1129
"%$sub"
1130
}
1131
}gex;
1132
$ecmd
1133
}
1134
1135
sub start_viewer {
1136
unlink $viewer{path} if -S $viewer{path} || -p _;
1137
1138
$viewer{server} = IO::Socket::UNIX->new(
1139
Type => SOCK_STREAM,
1140
Local => $viewer{path},
1141
Listen => 1
1142
);
1143
unless ($viewer{server}) {
1144
$viewer{msg} = "Viewer: $!";
1145
$viewer{retry} = Irssi::timeout_add_once(5000, 'retry_viewer', 1);
1146
return;
1147
}
1148
$viewer{server}->blocking(0);
1149
set_viewer_mode_hint();
1150
$viewer{server_tag} = Irssi::input_add($viewer{server}->fileno, INPUT_READ, 'vi_connected', undef);
1151
1152
if ($S{viewer_launch}) {
1153
if (length $ENV{TMUX_PANE} && length $ENV{TMUX} && lc $S{viewer_tmux_position} ne 'custom') {
1154
my $cmd = _viewer_command_replace_format('%qA', '-p', lc $S{viewer_tmux_position});
1155
Irssi::command("exec - tmux neww -d $cmd 2>&1 &");
1156
}
1157
elsif (length $ENV{WINDOWID} && length $ENV{DISPLAY} && length $S{viewer_xwin_command} && $S{viewer_xwin_command} =~ /\S/) {
1158
my $cmd = _viewer_command_replace_format($S{viewer_xwin_command});
1159
Irssi::command("exec - $cmd 2>&1 &");
1160
}
1161
elsif (length $S{viewer_custom_command} && $S{viewer_custom_command} =~ /\S/) {
1162
my $cmd = _viewer_command_replace_format($S{viewer_custom_command});
1163
Irssi::command("exec - $cmd 2>&1 &");
1164
}
1165
}
1166
}
1167
1168
sub set_viewer_mode_hint {
1169
return unless $viewer{server};
1170
if ($S{no_mode_hint}) {
1171
$viewer{msg} = undef;
1172
}
1173
else {
1174
my ($name) = __PACKAGE__ =~ /::([^:]+)$/;
1175
$viewer{msg} = "Run $name from the shell or switch to sbar mode";
1176
}
1177
}
1178
1179
sub retry_viewer {
1180
start_viewer();
1181
}
1182
1183
sub vi_close_client {
1184
Irssi::input_remove(delete $viewer{client_tag}) if exists $viewer{client_tag};
1185
$viewer{client}->close if $viewer{client};
1186
delete $viewer{client};
1187
delete $viewer{client_keymap};
1188
delete $viewer{client_settings};
1189
delete $viewer{client_env};
1190
delete $viewer{fullRedraw};
1191
}
1192
1193
sub vi_connected {
1194
vi_close_client();
1195
$viewer{client} = $viewer{server}->accept or return;
1196
$viewer{client}->blocking(0);
1197
$viewer{client_tag} = Irssi::input_add($viewer{client}->fileno, INPUT_READ, 'vi_clientinput', undef);
1198
syncViewer();
1199
}
1200
1201
use constant VIEWER_BLOCK_SIZE => 1024;
1202
sub vi_clientinput {
1203
if ($viewer{client}->read(my $buf, VIEWER_BLOCK_SIZE)) {
1204
$viewer{rcvbuf} .= $buf;
1205
if ($viewer{rcvbuf} =~ s/^(?:(active|\d+)|(last|up|down))\n//igm) {
1206
if (defined $2) {
1207
Irssi::command("window $2");
1208
}
1209
elsif (lc $1 eq 'active' && $viewer{use_ack}) {
1210
Irssi::command($viewer{use_ack});
1211
}
1212
else {
1213
Irssi::command("window goto $1");
1214
}
1215
}
1216
}
1217
else {
1218
vi_close_client();
1219
Irssi::timeout_add_once(100, 'syncViewer', undef);
1220
}
1221
}
1222
1223
sub stop_viewer {
1224
Irssi::timeout_remove(delete $viewer{retry}) if exists $viewer{retry};
1225
vi_close_client();
1226
Irssi::input_remove(delete $viewer{server_tag}) if exists $viewer{server_tag};
1227
return unless $viewer{server};
1228
$viewer{server}->close;
1229
delete $viewer{server};
1230
}
1231
sub _encode_var {
1232
my $str;
1233
while (@_) {
1234
my ($name, $var) = splice @_, 0, 2;
1235
my $type = ref $var ? $var =~ /HASH/ ? 'map' : $var =~ /ARRAY/ ? 'list' : '' : '';
1236
$str .= "\n\U$name$type\_begin\n";
1237
if ($type eq 'map') {
1238
no warnings 'numeric';
1239
$str .= " $_\n ${$var}{$_}\n" for sort { $a <=> $b || $a cmp $b } keys %$var;
1240
}
1241
elsif ($type eq 'list') {
1242
$str .= " $_\n" for @$var;
1243
}
1244
else {
1245
$str .= " $var\n";
1246
}
1247
$str .= "\U$name$type\_end\n";
1248
}
1249
$str
1250
}
1251
sub syncViewer {
1252
if ($viewer{client}) {
1253
@actString = ();
1254
if ($currentLines) {
1255
killOldStatus();
1256
$currentLines = 0;
1257
}
1258
my $str;
1259
unless ($viewer{client_keymap}) {
1260
$str .= _encode_var('key', +{ %nummap, %specialmap });
1261
$viewer{client_keymap} = 1;
1262
}
1263
unless ($viewer{client_settings}) {
1264
$str .= _encode_var(
1265
block => $S{block},
1266
ha => $S{height_adjust},
1267
mc => $S{maxcolumns},
1268
ml => $S{maxlines},
1269
);
1270
$viewer{client_settings} = 1;
1271
}
1272
unless ($viewer{client_env}) {
1273
$str .= _encode_var(irssienv => +{
1274
length $ENV{TMUX_PANE} && length $ENV{TMUX} ?
1275
(tmux_pane => $ENV{TMUX_PANE},
1276
tmux_srv => $ENV{TMUX}) : (),
1277
length $ENV{WINDOWID} ?
1278
(xwinid => $ENV{WINDOWID}) : (),
1279
});
1280
$viewer{client_env} = 1;
1281
}
1282
my $separator = _get_format(set 'separator');
1283
my $sepLen = sb_length($separator);
1284
my $item_bg = _get_format(set 'viewer_item_bg');
1285
my $title = _get_format(set 'title');
1286
if (length $title) {
1287
$title =~ s{\\(.)|(.)}{
1288
defined $2 ? quotemeta $2
1289
: $1 eq 'V' ? '\U'
1290
: $1 eq ':' ? quotemeta '%N'
1291
: $1 =~ /^[uUFQE]$/ ? "\\$1"
1292
: quotemeta "\\$1"
1293
}sge;
1294
$title = eval qq{"$title"};
1295
}
1296
$str .= _encode_var(redraw => 1) if delete $viewer{fullRedraw};
1297
$str .= _encode_var(separator => $separator,
1298
seplen => $sepLen,
1299
itembg => $item_bg,
1300
title => $title,
1301
mouse => $mouse_coords{refnum},
1302
key2 => \%wnmap_exp,
1303
win => \@win_items);
1304
1305
my $was = $viewer{client}->blocking(1);
1306
$viewer{client}->print($str);
1307
$viewer{client}->blocking($was);
1308
}
1309
elsif ($viewer{server}) {
1310
if (defined $viewer{msg}) {
1311
@actString = ((uc setc()).": $viewer{msg}");
1312
}
1313
else {
1314
@actString = ();
1315
}
1316
}
1317
elsif (defined $viewer{msg}) {
1318
@actString = ((uc setc()).": $viewer{msg}");
1319
}
1320
if (@actString) {
1321
Irssi::timeout_add_once(100, 'syncLines', undef);
1322
}
1323
elsif ($currentLines) {
1324
killOldStatus();
1325
$currentLines = 0;
1326
}
1327
}
1328
1329
sub reset_awl {
1330
Irssi::timeout_remove($shade_line_timer) if $shade_line_timer; $shade_line_timer = undef;
1331
my $was_sort = $S{sort} // '';
1332
my $was_xform = $S{xform} // '';
1333
my $was_shared = $S{shared_sbar};
1334
my $was_no_hint = $S{no_mode_hint};
1335
%S = (
1336
sort => Irssi::settings_get_str( set 'sort'),
1337
fancy_abbrev => Irssi::settings_get_str('fancy_abbrev'),
1338
xform => Irssi::settings_get_str( set 'custom_xform'),
1339
block => Irssi::settings_get_int( set 'block'),
1340
banned_on => Irssi::settings_get_bool('banned_channels_on'),
1341
prefer_name => Irssi::settings_get_bool(set 'prefer_name'),
1342
hide_data => Irssi::settings_get_int( set 'hide_data'),
1343
hide_name => Irssi::settings_get_int( set 'hide_name_data'),
1344
hide_empty => Irssi::settings_get_int( set 'hide_empty'),
1345
detach => Irssi::settings_get_str( set 'detach'),
1346
detach_data => Irssi::settings_get_int( set 'detach_data'),
1347
detach_aht => Irssi::settings_get_bool(set 'detach_aht'),
1348
sbar_maxlen => Irssi::settings_get_bool(set 'sbar_maxlength'),
1349
placement => Irssi::settings_get_str( set 'placement'),
1350
position => Irssi::settings_get_int( set 'position'),
1351
maxlines => Irssi::settings_get_int( set 'maxlines'),
1352
maxcolumns => Irssi::settings_get_int( set 'maxcolumns'),
1353
all_disable => Irssi::settings_get_bool(set 'all_disable'),
1354
height_adjust => Irssi::settings_get_int( set 'height_adjust'),
1355
mouse_offset => Irssi::settings_get_int( set 'mouse_offset'),
1356
mouse_scroll => Irssi::settings_get_int( 'mouse_scroll'),
1357
mouse_escape => Irssi::settings_get_int( 'mouse_escape'),
1358
line_shade => Irssi::settings_get_time(set 'last_line_shade'),
1359
no_mode_hint => Irssi::settings_get_bool(set 'no_mode_hint'),
1360
viewer_launch => Irssi::settings_get_bool(set 'viewer_launch'),
1361
viewer_launch_env => Irssi::settings_get_str(set 'viewer_launch_env'),
1362
viewer_xwin_command => Irssi::settings_get_str(set 'viewer_xwin_command'),
1363
viewer_custom_command => Irssi::settings_get_str(set 'viewer_custom_command'),
1364
viewer_tmux_position => Irssi::settings_get_str(set 'viewer_tmux_position'),
1365
);
1366
$S{fancy_strict} = $S{fancy_abbrev} =~ /^strict/i;
1367
$S{fancy_head} = $S{fancy_abbrev} =~ /^head/i;
1368
my $shared = Irssi::settings_get_str(set 'shared_sbar');
1369
if ($shared =~ /^(\d+)([<])(\d+)$/) {
1370
$S{shared_sbar} = [$1, $2, $3];
1371
}
1372
else {
1373
Irssi::settings_set_str(set 'shared_sbar', 'OFF');
1374
$S{shared_sbar} = undef;
1375
}
1376
lock_keys(%S);
1377
if ($was_sort ne $S{sort}) {
1378
$print_text_activity = undef;
1379
my @sort_order = grep { @$_ > 4 } map {
1380
s/^\s*//;
1381
my $reverse = s/^\W*\K[-!]//;
1382
my $undef_check = s/^\W*\K~// ? 1 : undef;
1383
my $equal_check = s/=(.*)\s?$// ? $1 : undef;
1384
s/\s*$//;
1385
my $ignore_case = s/#i$// ? 1 : undef;
1386
1387
$print_text_activity = 1 if $_ eq 'last_line';
1388
1389
my @path = split '/';
1390
my $class_check = @path && $path[-1] =~ s/(::.*)$// ? $1 : undef;
1391
my $lru = "@path" eq 'lru';
1392
1393
[ $reverse ? -1 : 1, $undef_check, $equal_check, $class_check, $ignore_case, $lru, @path ]
1394
} "$S{sort}," =~ /([^+,]*|[^+,]*=[^,]*?\s(?=\+)|[^+,]*=[^,]*)[+,]/g;
1395
$window_sort_func = sub {
1396
no warnings qw(numeric uninitialized);
1397
for my $so (@sort_order) {
1398
my @x = map {
1399
my $ret = 0;
1400
$_ = lc1459($_) if defined $_ && !ref $_ && $so->[4];
1401
$ret = $_ eq ($so->[4] ? lc1459($so->[2]) : $so->[2]) ? 1 : -1 if defined $so->[2];
1402
$ret = defined $_ ? ($ret || -3) : 3 if $so->[1];
1403
$ret = ref $_ && $_->isa('Irssi'.$so->[3]) ? 2 : ($ret || -2) if $so->[3];
1404
-$ret || $_
1405
}
1406
map {
1407
$so->[5] ? $_->[0] : reduce { return unless ref $a; $a->{$b} } $_->[1], @{$so}[6..$#$so]
1408
} $a, $b;
1409
return ((($x[0] <=> $x[1] || $x[0] cmp $x[1]) * $so->[0]) || next);
1410
}
1411
return ($a->[1]{refnum} <=> $b->[1]{refnum});
1412
};
1413
}
1414
if ($was_xform ne $S{xform}) {
1415
if ($S{xform} !~ /\S/) {
1416
$custom_xform = undef;
1417
}
1418
else {
1419
my $script_pkg = __PACKAGE__ . '::custom_xform';
1420
local $@;
1421
$custom_xform = eval qq{
1422
package $script_pkg;
1423
use strict;
1424
no warnings;
1425
our (\$QUERY, \$CHANNEL, \$TAG, \$NAME);
1426
return sub {
1427
# line 1 @{[ set 'custom_xform' ]}\n$S{xform}\n}};
1428
if ($@) {
1429
$@ =~ /^(.*)/;
1430
print '%_'.(set 'custom_xform').'%_ did not compile: '.$1;
1431
}
1432
}
1433
}
1434
1435
my $new_settings = join "\n", $VIEWER_MODE
1436
? ("\\", $S{block}, $S{height_adjust}, $S{maxlines}, $S{maxcolumns})
1437
: ("!", $S{placement}, $S{position});
1438
1439
my $first_viewer = $settings_str eq '1';
1440
if ($settings_str ne $new_settings) {
1441
@actString = ();
1442
%abbrev_cache = ();
1443
$currentLines = 0;
1444
killOldStatus();
1445
delete $viewer{client_settings};
1446
$settings_str = $new_settings;
1447
}
1448
1449
my $was_mouse_mode = $MOUSE_ON;
1450
if ($MOUSE_ON = Irssi::settings_get_bool(set 'mouse') and !$was_mouse_mode) {
1451
install_mouse();
1452
}
1453
elsif ($was_mouse_mode and !$MOUSE_ON) {
1454
uninstall_mouse();
1455
}
1456
1457
unless ($first_viewer) {
1458
my $path = Irssi::settings_get_str(set 'path');
1459
my $was_viewer_mode = $VIEWER_MODE;
1460
if ($was_viewer_mode &&
1461
defined $viewer{path} && $viewer{path} ne $path) {
1462
stop_viewer();
1463
$was_viewer_mode = 0;
1464
}
1465
elsif ($was_viewer_mode && $S{no_mode_hint} != $was_no_hint + 0) {
1466
set_viewer_mode_hint();
1467
}
1468
$viewer{path} = $path;
1469
if ($VIEWER_MODE = Irssi::settings_get_bool(set 'viewer') and !$was_viewer_mode) {
1470
start_viewer();
1471
}
1472
elsif ($was_viewer_mode and !$VIEWER_MODE) {
1473
stop_viewer();
1474
}
1475
}
1476
1477
%banned_channels = map { lc1459(as_uni($_)) => undef }
1478
split ' ', Irssi::settings_get_str('banned_channels');
1479
1480
%detach_map = ($S{detach_aht}
1481
? (map { ( lc1459(as_uni($_)) => undef ) }
1482
split ' ', Irssi::settings_get_str('activity_hide_targets')) : (),
1483
(map { my ($k, $v) = (split /(?:,(-?\d+))$/, $_)[0, 1];
1484
( lc1459(as_uni($k)) => $v ) }
1485
split ' ', $S{detach}));
1486
1487
my @sb_base = split /\177/, sb_format_expand("{sbstart}{sb \177}{sbend}"), 2;
1488
$sb_base_width_pre = sb_length($sb_base[0]);
1489
$sb_base_width_post = max 0, sb_length($sb_base[1])-1;
1490
$sb_base_width = $sb_base_width_pre + $sb_base_width_post;
1491
1492
if ($print_text_activity && $S{line_shade}) {
1493
$shade_line_timer = Irssi::timeout_add(max(10 * GLOB_QUEUE_TIMER, 100*$S{line_shade}**(1/3)), 'wl_changed', undef);
1494
}
1495
1496
$CHANGED{AWINS} = 1;
1497
}
1498
1499
sub hide_window {
1500
my ($data) = @_;
1501
my $ent;
1502
1503
$data =~ s/\s*$//;
1504
my $win = Irssi::active_win;
1505
my $number = $win->{refnum};
1506
my $name = as_uni($win->{name});
1507
my $active = as_uni($win->get_active_name) // '';
1508
my $tag = $win->{active} && $win->{active}{server} ? as_uni($win->{active}{server}{tag}) // '' : '';
1509
if (length $name) {
1510
$ent = "$name";
1511
}
1512
elsif (length $tag && length $active) {
1513
$ent = "$tag/$active";
1514
}
1515
else {
1516
$ent = "$number";
1517
}
1518
1519
my $found = 0;
1520
my @setting;
1521
for my $s (split ' ', $S{detach}) {
1522
my ($k, $v) = (split /(?:,(-?\d+))$/, $s)[0, 1];
1523
if (lc1459(as_uni($k)) eq lc1459($ent)) {
1524
unless ($found) {
1525
if ($data =~ /^(-?\d+)$/) {
1526
$ent .= ",$1";
1527
}
1528
if (defined $v && 0 == abs $v) {
1529
$win->print("Hiding window $ent");
1530
}
1531
push @setting, as_tc($ent);
1532
$found = 1;
1533
}
1534
}
1535
else {
1536
push @setting, defined $v ? "$k,$v" : $k;
1537
}
1538
}
1539
unless ($found) {
1540
$win->print("Hiding window $ent");
1541
if ($data =~ /^(-?\d+)$/) {
1542
$ent .= ",$1";
1543
}
1544
push @setting, as_tc($ent);
1545
}
1546
1547
if (@setting) {
1548
Irssi::command("^set ".(set 'detach')." @setting");
1549
} else {
1550
Irssi::command("^set -clear ".(set 'detach'));
1551
}
1552
}
1553
1554
sub unhide_window {
1555
my ($data, $server, $witem) = @_;
1556
my $win = Irssi::active_win;
1557
my $number = $win->{refnum};
1558
my $name = as_uni($win->{name});
1559
my $active = as_uni($win->get_active_name) // '';
1560
my $tag = $win->{active} && $win->{active}{server} ? as_uni($win->{active}{server}{tag}) // '' : '';
1561
1562
my %detach_aht;
1563
if ($S{detach_aht}) {
1564
%detach_aht = (map { ( lc1459(as_uni($_)) => undef ) }
1565
split ' ', Irssi::settings_get_str('activity_hide_targets'));
1566
}
1567
my @setting;
1568
my @kills = (length $name ? $name : undef,
1569
length $tag && length $active ? "$tag/$active" : undef,
1570
length $active ? $active : undef,
1571
$number);
1572
my @was_unhidden = (0) x @kills;
1573
for my $s (split ' ', $S{detach}) {
1574
my ($k, $v) = (split /(?:,(-?\d+))$/, $s)[0, 1];
1575
my $k2 = lc1459(as_uni($k));
1576
my $kill;
1577
for my $ki (0..$#kills) {
1578
if (defined $kills[$ki] && $k2 eq lc1459($kills[$ki])) {
1579
$kill = $ki;
1580
}
1581
}
1582
1583
if (defined $kill) {
1584
if (defined $v && 0 == abs $v) {
1585
$was_unhidden[$kill] = 1;
1586
push @setting, defined $v ? "$k,$v" : $k;
1587
} else {
1588
$win->print("Unhiding window $kills[$kill]");
1589
}
1590
}
1591
else {
1592
push @setting, defined $v ? "$k,$v" : $k;
1593
}
1594
}
1595
my @is_hidden = (defined $kills[0] && (exists $detach_map{"*"} || exists $detach_map{"::all"}),
1596
defined $kills[1] && (exists $detach_map{lc1459("$tag/*")} || exists $detach_map{lc1459("$tag/::all")}
1597
|| exists $detach_map{"*"} || exists $detach_map{"::all"}),
1598
defined $kills[2] && (exists $detach_map{"*"} || exists $detach_map{"::all"}),
1599
(exists $detach_map{"*"} || exists $detach_map{"::all"})
1600
);
1601
for my $ki (1, 2, 0, 3) {
1602
if ($is_hidden[$ki]) {
1603
unless ($was_unhidden[$ki]) {
1604
$win->print("Unhiding window $kills[$ki]");
1605
push @setting, "$kills[$ki],0";
1606
$was_unhidden[$ki] = 1;
1607
}
1608
last;
1609
}
1610
}
1611
my @is_hidden_aht = (defined $kills[0] && (exists $detach_aht{lc1459($name)}
1612
|| exists $detach_aht{"*"} || exists $detach_aht{"::all"}),
1613
defined $kills[1] && (exists $detach_aht{lc1459("$tag/$active")}
1614
|| exists $detach_aht{lc1459($active)}
1615
|| exists $detach_aht{lc1459("$tag/*")} || exists $detach_aht{lc1459("$tag/::all")}
1616
|| exists $detach_aht{"*"} || exists $detach_aht{"::all"}),
1617
defined $kills[2] && (exists $detach_aht{lc1459($active)}
1618
|| exists $detach_aht{"*"} || exists $detach_aht{"::all"}),
1619
(exists $detach_aht{$number} || exists $detach_aht{"*"} || exists $detach_aht{"::all"})
1620
);
1621
for my $ki (1, 2, 0, 3) {
1622
if ($is_hidden_aht[$ki]) {
1623
unless ($was_unhidden[$ki]) {
1624
$win->print("Unhiding window $kills[$ki], it is hidden because ".(set 'detach_aht')." is ON");
1625
push @setting, "$kills[$ki],0";
1626
$was_unhidden[$ki] = 1;
1627
}
1628
last;
1629
}
1630
}
1631
1632
if (@setting) {
1633
Irssi::command("^set ".(set 'detach')." @setting");
1634
} else {
1635
Irssi::command("^set -clear ".(set 'detach'));
1636
}
1637
}
1638
1639
sub ack_window {
1640
my ($data, $server, $witem) = @_;
1641
my $win = Irssi::active_win;
1642
my $number = $win->{refnum};
1643
if (grep { $_->{cmd} eq 'ack' } Irssi::commands) {
1644
my $Orig_Irssi_windows = \&Irssi::windows;
1645
local *Irssi::windows = sub () { grep { !_is_detached($_, $number) } $Orig_Irssi_windows->() };
1646
Irssi::command("ack" . (length $data ? " $data" : ""));
1647
} else {
1648
my $ignore_refnum = Irssi::settings_get_bool('active_window_ignore_refnum');
1649
my $max_win;
1650
my $max_act = 0;
1651
my $max_ref = 0;
1652
for my $rec (Irssi::windows) {
1653
next if _is_detached($rec, $number);
1654
1655
# ignore refnum
1656
if ($ignore_refnum &&
1657
$rec->{data_level} > 0 && $max_act < $rec->{data_level}) {
1658
$max_act = $rec->{data_level};
1659
$max_win = $rec;
1660
}
1661
1662
# windows with lower refnums break ties
1663
elsif (!$ignore_refnum &&
1664
$rec->{data_level} > 0 &&
1665
($rec->{data_level} > $max_act ||
1666
($rec->{data_level} == $max_act && $rec->{refnum} < $max_ref))) {
1667
$max_act = $rec->{data_level};
1668
$max_win = $rec;
1669
$max_ref = $rec->{refnum};
1670
}
1671
}
1672
$max_win->set_active if defined $max_win;
1673
}
1674
}
1675
1676
sub refnum_changed {
1677
my ($win, $old_refnum) = @_;
1678
my @old_setting = split ' ', $S{detach};
1679
my @setting = map {
1680
my ($k, $v) = (split /(?:,(-?\d+))$/, $_)[0, 1];
1681
if ($k eq $old_refnum) {
1682
$win->{refnum} . (defined $v ? ",$v" : "")
1683
}
1684
else {
1685
$_
1686
}
1687
} @old_setting;
1688
if ("@old_setting" ne "@setting") {
1689
$S{detach} = "@setting";
1690
Irssi::settings_set_str(set 'detach', "@setting");
1691
&setup_changed;
1692
}
1693
else {
1694
&wl_changed;
1695
}
1696
}
1697
1698
sub window_destroyed {
1699
my ($win) = @_;
1700
my @old_setting = split ' ', $S{detach};
1701
my @setting = grep {
1702
my ($k, $v) = (split /(?:,(-?\d+))$/, $_)[0, 1];
1703
if ($k eq $win->{refnum}) {
1704
0;
1705
}
1706
else {
1707
1;
1708
}
1709
} @old_setting;
1710
if ("@old_setting" ne "@setting") {
1711
$S{detach} = "@setting";
1712
Irssi::settings_set_str(set 'detach', "@setting");
1713
&setup_changed;
1714
}
1715
else {
1716
&awins_changed;
1717
}
1718
}
1719
1720
sub stop_mouse_tracking {
1721
print STDERR "\e[?1005l\e[?1000l";
1722
}
1723
sub start_mouse_tracking {
1724
print STDERR "\e[?1000h\e[?1005h";
1725
}
1726
sub install_mouse {
1727
Irssi::command_bind('mouse_xterm' => 'mouse_xterm');
1728
Irssi::command('^bind meta-[M command mouse_xterm');
1729
Irssi::signal_add_first('gui key pressed' => 'mouse_key_hook');
1730
start_mouse_tracking();
1731
}
1732
sub uninstall_mouse {
1733
stop_mouse_tracking();
1734
Irssi::signal_remove('gui key pressed' => 'mouse_key_hook');
1735
Irssi::command('^bind -delete meta-[M');
1736
Irssi::command_unbind('mouse_xterm' => 'mouse_xterm');
1737
}
1738
1739
sub awl_mouse_event {
1740
return if $VIEWER_MODE;
1741
if ((($_[0] == 3 and $_[3] == 0)
1742
|| $_[0] == 64 || $_[0] == 65) and
1743
$_[1] == $_[4] and $_[2] == $_[5]) {
1744
my $top = lc $S{placement} eq 'top';
1745
my ($pos, $line) = @_[1 .. 2];
1746
unless ($top) {
1747
$line -= $screenHeight;
1748
$line += $currentLines;
1749
$line += $S{mouse_offset};
1750
}
1751
else {
1752
$line -= $S{mouse_offset};
1753
}
1754
$pos -= $sb_base_width_pre;
1755
return if $line < 0 || $line >= $currentLines;
1756
if ($_[0] == 64) {
1757
Irssi::command('window up');
1758
}
1759
elsif ($_[0] == 65) {
1760
Irssi::command('window down');
1761
}
1762
elsif (exists $mouse_coords{$line}{$pos}) {
1763
my $win = $mouse_coords{$line}{$pos};
1764
Irssi::command('window ' . $win);
1765
}
1766
Irssi::signal_stop;
1767
}
1768
}
1769
1770
sub mouse_scroll_event {
1771
return unless $S{mouse_scroll};
1772
if (($_[3] == 64 or $_[3] == 65) and
1773
$_[0] == $_[3] and $_[1] == $_[4] and $_[2] == $_[5]) {
1774
my $cmd = 'scrollback goto ' . ($_[3] == 64 ? '-' : '+') . $S{mouse_scroll};
1775
Irssi::active_win->command($cmd);
1776
Irssi::signal_stop;
1777
}
1778
elsif ($_[0] == 64 or $_[0] == 65) {
1779
Irssi::signal_stop;
1780
}
1781
}
1782
1783
sub mouse_escape {
1784
return unless $S{mouse_escape} > 0;
1785
if ($_[0] == 3) {
1786
my $tm = $S{mouse_escape};
1787
$tm *= 1000 if $tm < 1000;
1788
stop_mouse_tracking();
1789
Irssi::timeout_add_once($tm, 'start_mouse_tracking', undef);
1790
Irssi::signal_stop;
1791
}
1792
}
1793
1794
sub UNLOAD {
1795
@actString = ();
1796
killOldStatus();
1797
stop_viewer() if $VIEWER_MODE;
1798
uninstall_mouse() if $MOUSE_ON;
1799
}
1800
1801
sub addPrintTextHook { # update on print text
1802
return unless defined $^S;
1803
return if $BLOCK_ALL;
1804
return unless $print_text_activity;
1805
return if $_[0]->{level} == 262144 and $_[0]->{target} eq ''
1806
and !defined($_[0]->{server});
1807
&wl_changed;
1808
}
1809
1810
sub block_event_window_change {
1811
Irssi::signal_stop;
1812
}
1813
1814
sub update_awins {
1815
my @wins = Irssi::windows;
1816
local $BLOCK_ALL = 1;
1817
Irssi::signal_add_first('window changed' => 'block_event_window_change');
1818
my $bwin =
1819
my $awin = Irssi::active_win;
1820
my $lwin;
1821
my $defer_irssi_broken_last;
1822
unless ($wins[0]{refnum} == $awin->{refnum}) {
1823
# special case: more than 1 last win, so /win last;
1824
# /win last doesn't come back to the current window. eg. after
1825
# connect & autojoin; we can't handle this situation, bail out
1826
$defer_irssi_broken_last = 1;
1827
}
1828
else {
1829
$awin->command('window last');
1830
$lwin = Irssi::active_win;
1831
$lwin->command('window last');
1832
$defer_irssi_broken_last = $lwin->{refnum} == $bwin->{refnum};
1833
}
1834
my $awin_counter = 0;
1835
Irssi::signal_remove('window changed' => 'block_event_window_change');
1836
unless ($defer_irssi_broken_last) {
1837
# we need to keep the fe-windows code running here
1838
Irssi::signal_add_priority('window changed' => 'block_event_window_change', -99);
1839
%awins = %wnmap_exp = ();
1840
do {
1841
Irssi::active_win->command('window up');
1842
$awin = Irssi::active_win;
1843
$awins{$awin->{refnum}} = undef;
1844
++$awin_counter;
1845
} until ($awin->{refnum} == $bwin->{refnum} || $awin_counter >= @wins);
1846
Irssi::signal_remove('window changed' => 'block_event_window_change');
1847
1848
Irssi::signal_add_first('window changed' => 'block_event_window_change');
1849
for my $key (keys %wnmap) {
1850
next unless Irssi::window_find_name($key) || Irssi::window_find_item($key);
1851
$awin->command("window goto $key");
1852
my $cwin = Irssi::active_win;
1853
$wnmap_exp{ $cwin->{refnum} } = $wnmap{$key};
1854
$cwin->command('window last')
1855
if $cwin->{refnum} != $awin->{refnum};
1856
}
1857
for my $win (reverse @wins) { # restore original window order
1858
Irssi::active_win->command('window '.$win->{refnum});
1859
}
1860
$awin->command('window '.$lwin->{refnum}); # restore last win
1861
Irssi::active_win->command('window last');
1862
Irssi::signal_remove('window changed' => 'block_event_window_change');
1863
}
1864
$CHANGED{WL} = 1;
1865
}
1866
1867
sub resizeTerm {
1868
if (defined (my $r = `stty size 2>/dev/null`)) {
1869
($screenHeight, $screenWidth) = split ' ', $r;
1870
$CHANGED{SETUP} = 1;
1871
}
1872
else {
1873
$CHANGED{SIZE} = 1;
1874
}
1875
}
1876
1877
sub awl_refresh {
1878
$globTime = undef;
1879
resizeTerm() if delete $CHANGED{SIZE};
1880
reset_awl() if delete $CHANGED{SETUP};
1881
update_awins() if delete $CHANGED{AWINS};
1882
update_wl() if delete $CHANGED{WL};
1883
}
1884
1885
sub termsize_changed { $CHANGED{SIZE} = 1; &queue_refresh; }
1886
sub setup_changed { $CHANGED{SETUP} = 1; &queue_refresh; }
1887
sub awins_changed { $CHANGED{AWINS} = 1; &queue_refresh; }
1888
sub wl_changed { $CHANGED{WL} = 1; &queue_refresh; }
1889
1890
sub window_changed {
1891
&awins_changed if $_[1];
1892
}
1893
1894
sub queue_refresh {
1895
return if $BLOCK_ALL;
1896
Irssi::timeout_remove($globTime)
1897
if defined $globTime; # delay the update further
1898
$globTime = Irssi::timeout_add_once(GLOB_QUEUE_TIMER, 'awl_refresh', undef);
1899
}
1900
1901
sub awl_init {
1902
termsize_changed();
1903
setup_changed();
1904
update_keymap();
1905
Irssi::timeout_remove($globTime)
1906
if defined $globTime;
1907
awl_refresh();
1908
termsize_changed();
1909
}
1910
1911
sub runsub {
1912
my $cmd = shift;
1913
sub {
1914
my ($data, $server, $item) = @_;
1915
Irssi::command_runsub($cmd, $data, $server, $item);
1916
};
1917
}
1918
1919
Irssi::signal_register({
1920
'gui mouse' => [qw/int int int int int int/],
1921
});
1922
{ my $broken_expandos = (Irssi::version >= 20081128 && Irssi::version < 20110210)
1923
? sub { my $x = shift; $x =~ s/\$\{cumode_space\}/ /; $x } : undef;
1924
Irssi::theme_register([
1925
map { $broken_expandos ? $broken_expandos->($_) : $_ }
1926
set 'display_nokey' => '$N${cumode_space}$H$C$S',
1927
set 'display_key' => '$Q${cumode_space}$H$C$S',
1928
set 'display_nokey_visible' => '%2$N${cumode_space}$H$C$S',
1929
set 'display_key_visible' => '%2$Q${cumode_space}$H$C$S',
1930
set 'display_nokey_active' => '%1$N${cumode_space}$H$C$S',
1931
set 'display_key_active' => '%1$Q${cumode_space}$H$C$S',
1932
set 'display_header' => '%8$C|${N}',
1933
set 'name_display' => '$0',
1934
set 'separator' => ' ',
1935
set 'separator2' => '',
1936
set 'abbrev_chars' => "~\x{301c}",
1937
set 'viewer_item_bg' => sb_format_expand('{sb_background}'),
1938
set 'title' => '\V'.setc().'\:',
1939
]);
1940
}
1941
Irssi::settings_add_bool(setc, set 'prefer_name', 0); #
1942
Irssi::settings_add_int( setc, set 'hide_empty', 0); #
1943
Irssi::settings_add_int( setc, set 'hide_data', 0); #
1944
Irssi::settings_add_str( setc, set 'detach', ''); #
1945
Irssi::settings_add_int( setc, set 'detach_data', -3); #
1946
Irssi::settings_add_bool(setc, set 'detach_aht', 0); #
1947
Irssi::settings_add_int( setc, set 'hide_name_data', 0); #
1948
Irssi::settings_add_int( setc, set 'maxlines', 9); #
1949
Irssi::settings_add_int( setc, set 'maxcolumns', 4); #
1950
Irssi::settings_add_int( setc, set 'block', 15); #
1951
Irssi::settings_add_bool(setc, set 'sbar_maxlength', 1); #
1952
Irssi::settings_add_int( setc, set 'height_adjust', 2); #
1953
Irssi::settings_add_str( setc, set 'sort', 'refnum'); #
1954
Irssi::settings_add_str( setc, set 'placement', 'bottom'); #
1955
Irssi::settings_add_int( setc, set 'position', 0); #
1956
Irssi::settings_add_bool(setc, set 'all_disable', 1); #
1957
Irssi::settings_add_bool(setc, set 'viewer', 1); #
1958
Irssi::settings_add_str( setc, set 'shared_sbar', 'OFF'); #
1959
Irssi::settings_add_bool(setc, set 'mouse', 0); #
1960
Irssi::settings_add_str( setc, set 'path', Irssi::get_irssi_dir . '/_windowlist'); #
1961
Irssi::settings_add_str( setc, set 'custom_xform', ''); #
1962
Irssi::settings_add_time(setc, set 'last_line_shade', '0'); #
1963
Irssi::settings_add_int( setc, set 'mouse_offset', 1); #
1964
Irssi::settings_add_int( setc, 'mouse_scroll', 3); #
1965
Irssi::settings_add_int( setc, 'mouse_escape', 1); #
1966
Irssi::settings_add_str( setc, 'banned_channels', '');
1967
Irssi::settings_add_bool(setc, 'banned_channels_on', 1);
1968
Irssi::settings_add_str( setc, 'fancy_abbrev', 'fancy'); #
1969
Irssi::settings_add_bool(setc, set 'no_mode_hint', 0); #
1970
Irssi::settings_add_bool(setc, set 'viewer_launch', 1); #
1971
Irssi::settings_add_str( setc, set 'viewer_launch_env', ''); #
1972
Irssi::settings_add_str( setc, set 'viewer_tmux_position', 'left'); #
1973
Irssi::settings_add_str( setc, set 'viewer_xwin_command', 'xterm +sb -e %A'); #
1974
Irssi::settings_add_str( setc, set 'viewer_custom_command', ''); #
1975
1976
Irssi::signal_add_last({
1977
'setup changed' => 'setup_changed',
1978
'print text' => 'addPrintTextHook',
1979
'terminal resized' => 'termsize_changed',
1980
'setup reread' => 'screenFullRedraw',
1981
'window hilight' => 'wl_changed',
1982
'command format' => 'wl_changed',
1983
});
1984
Irssi::signal_add({
1985
'window changed' => 'window_changed',
1986
'window item changed' => 'wl_changed',
1987
'window changed automatic' => 'window_changed',
1988
'window created' => 'awins_changed',
1989
'window destroyed' => 'window_destroyed',
1990
'window name changed' => 'wl_changed',
1991
'window refnum changed' => 'refnum_changed',
1992
});
1993
Irssi::signal_add_last('gui mouse' => 'mouse_escape');
1994
Irssi::signal_add_last('gui mouse' => 'mouse_scroll_event');
1995
Irssi::signal_add_last('gui mouse' => 'awl_mouse_event');
1996
Irssi::command_bind( setc() => runsub(setc()) );
1997
Irssi::command_bind( setc() . ' redraw' => 'screenFullRedraw' );
1998
Irssi::command_bind( setc() . ' restart' => 'restartViewerServer' );
1999
Irssi::command_bind( setc() . ' attach' => 'unhide_window' );
2000
Irssi::command_bind( setc() . ' detach' => 'hide_window' );
2001
Irssi::command_bind( setc() . ' ack' => 'ack_window' );
2002
2003
{
2004
my $l = set 'shared';
2005
{
2006
no strict 'refs';
2007
*{$l} = $awl_shared_empty;
2008
}
2009
Irssi::statusbar_item_register($l, '$0', $l);
2010
}
2011
2012
awl_init();
2013
2014
# Mouse script based on irssi mouse patch by mirage
2015
{ my $mouse_status = -1; # -1:off 0,1,2:filling mouse_combo
2016
my @mouse_combo; # 0:button 1:x 2:y
2017
my @mouse_previous; # previous contents of mouse_combo
2018
2019
sub mouse_xterm_off {
2020
$mouse_status = -1;
2021
}
2022
sub mouse_xterm {
2023
$mouse_status = 0;
2024
Irssi::timeout_add_once(10, 'mouse_xterm_off', undef);
2025
}
2026
2027
sub mouse_key_hook {
2028
my ($key) = @_;
2029
if ($mouse_status != -1) {
2030
if ($mouse_status == 0) {
2031
@mouse_previous = @mouse_combo;
2032
#if @mouse_combo && $mouse_combo[0] < 64;
2033
}
2034
$mouse_combo[$mouse_status] = $key - 32;
2035
$mouse_status++;
2036
if ($mouse_status == 3) {
2037
$mouse_status = -1;
2038
# match screen coordinates
2039
$mouse_combo[1]--;
2040
$mouse_combo[2]--;
2041
Irssi::signal_emit('gui mouse', @mouse_combo[0 .. 2], @mouse_previous[0 .. 2]);
2042
}
2043
Irssi::signal_stop;
2044
}
2045
}
2046
}
2047
2048
sub string_LCSS {
2049
my $str = join "\0", @_;
2050
(sort { length $b <=> length $a } $str =~ /(?=(.+).*\0.*\1)/g)[0]
2051
}
2052
2053
# workaround for issue #271
2054
{ package Irssi::Nick }
2055
2056
# workaround for issue #572
2057
@Irssi::UI::Exec::ISA = 'Irssi::Windowitem'
2058
if Irssi::version >= 20140822 && Irssi::version <= 20161101 && !@Irssi::UI::Exec::ISA;
2059
2060
UNITCHECK
2061
{ package AwlViewer;
2062
use strict;
2063
use warnings;
2064
no warnings 'redefine';
2065
use Encode;
2066
use IO::Socket::UNIX;
2067
use IO::Select;
2068
use List::Util qw(max);
2069
use constant BLOCK_SIZE => 1024;
2070
use constant RECONNECT_TIME => 5;
2071
2072
my $sockpath;
2073
2074
our $VERSION = '0.8';
2075
2076
our ($got_int, $resized, $timeout);
2077
2078
my %vars;
2079
my (%c2w, @seqlist);
2080
my %mouse_coords;
2081
my (@mouse, @last_mouse);
2082
my ($err, $sock, $loop);
2083
my ($keybuf, $rcvbuf);
2084
my @screen;
2085
my ($screenHeight, $screenWidth);
2086
my ($disp_update, $fs_open, $one_shot_integration, $one_shot_resize);
2087
my $integration_position;
2088
my $show_title_bar;
2089
2090
sub connect_it {
2091
$sock = IO::Socket::UNIX->new(
2092
Type => SOCK_STREAM,
2093
Peer => $sockpath,
2094
);
2095
unless ($sock) {
2096
$err = $!;
2097
return;
2098
}
2099
$sock->blocking(0);
2100
$loop->add($sock);
2101
}
2102
2103
sub remove_conn {
2104
my $fh = shift;
2105
$loop->remove($fh);
2106
$fh->close;
2107
$sock = undef;
2108
%vars = ();
2109
@screen = ();
2110
}
2111
2112
{ package Terminfo; # xterm
2113
sub civis { "\e[?25l" }
2114
sub sc { "\e7" }
2115
sub cup { "\e[" . ($_[0] + 1) . ';' . ($_[1] + 1) . 'H' }
2116
sub el { "\e[K" }
2117
sub rc { "\e8" }
2118
sub cnorm { "\e[?25h" }
2119
sub setab { "\e[4" . $_[0] . 'm' }
2120
sub setaf { "\e[3" . $_[0] . 'm' }
2121
sub setaf16 { "\e[9" . $_[0] . 'm' }
2122
sub setab16 { "\e[10" . $_[0] . 'm' }
2123
sub setaf256 { "\e[38;5;" . $_[0] . 'm' }
2124
sub setab256 { "\e[48;5;" . $_[0] . 'm' }
2125
sub sgr0 { "\e[0m" }
2126
sub bold { "\e[1m" }
2127
sub it { "\e[3m" }
2128
sub ul { "\e[4m" }
2129
sub blink { "\e[5m" }
2130
sub rev { "\e[7m" }
2131
sub op { "\e[39;49m" }
2132
sub exit_bold { "\e[22m" }
2133
sub exit_it { "\e[23m" }
2134
sub exit_ul { "\e[24m" }
2135
sub exit_blink { "\e[25m" }
2136
sub exit_rev { "\e[27m" }
2137
sub smcup { "\e[?1049h" }
2138
sub rmcup { "\e[?1049l" }
2139
sub smmouse { "\e[?1000h\e[?1005h" }
2140
sub rmmouse { "\e[?1005l\e[?1000l" }
2141
}
2142
2143
sub init {
2144
$sockpath = shift // "$ENV{HOME}/.irssi/_windowlist";
2145
STDOUT->autoflush(1);
2146
printf "\r%swaiting for %s...", Terminfo::sc, $::IRSSI{name};
2147
2148
`stty -icanon -echo`;
2149
2150
$loop = IO::Select->new;
2151
STDIN->blocking(0);
2152
$loop->add(\*STDIN);
2153
2154
$SIG{INT} = sub {
2155
$got_int = 1
2156
};
2157
$SIG{WINCH} = sub {
2158
$resized = 1
2159
};
2160
2161
$resized = 3;
2162
2163
$disp_update = 2;
2164
2165
$show_title_bar = 1;
2166
}
2167
2168
sub enter_fs {
2169
return if $fs_open;
2170
safe_print(Terminfo::rc, Terminfo::smcup, Terminfo::civis, Terminfo::smmouse);
2171
$fs_open = 1;
2172
}
2173
2174
sub leave_fs {
2175
return unless $fs_open;
2176
safe_print(Terminfo::rmmouse, Terminfo::cnorm, Terminfo::rmcup);
2177
safe_print(sprintf "\r%swaiting for %s...", Terminfo::sc, $::IRSSI{name}) if $_[0];
2178
2179
$fs_open = 0;
2180
}
2181
2182
sub end_prog {
2183
leave_fs();
2184
STDIN->blocking(1);
2185
`stty sane`;
2186
printf "\r%s%sthanks for using %s\n", Terminfo::rc, Terminfo::el, $::IRSSI{name};
2187
}
2188
2189
sub safe_print {
2190
my $st = STDIN->blocking(1);
2191
print @_;
2192
STDIN->blocking($st);
2193
}
2194
2195
sub safe_qx {
2196
my $st = STDIN->blocking(1);
2197
my $ret = `$_[0]`;
2198
STDIN->blocking($st);
2199
$ret
2200
}
2201
2202
sub safe_print_sock {
2203
return unless $sock;
2204
my $was = $sock->blocking(1);
2205
$sock->print(@_);
2206
$sock->blocking($was);
2207
}
2208
2209
sub process_recv {
2210
my $need = 0;
2211
while ($rcvbuf =~ s/\n(.+)_BEGIN\n((?: .*\n)*)\1_END\n//) {
2212
my $var = lc $1;
2213
my $data = $2;
2214
my @data = split "\n ", "\n$data ", -1;
2215
shift @data; pop @data;
2216
my $itembg = $vars{itembg};
2217
if ($var =~ s/list$//) {
2218
$vars{$var} = \@data;
2219
}
2220
elsif ($var =~ s/map$//) {
2221
$vars{$var} = +{ @data };
2222
}
2223
else {
2224
$vars{$var} = join "\n", @data;
2225
}
2226
$need = 1 if $var eq 'win';
2227
$need = 1 if $var eq 'redraw' && $vars{$var};
2228
if (($itembg//'') ne ($vars{itembg}//'')) {
2229
$need = $vars{redraw} = 1;
2230
}
2231
_build_keymap() if $var eq 'key2';
2232
}
2233
$need
2234
}
2235
2236
{ my %ansi_table;
2237
my ($i, $j, $k) = (0, 0, 0);
2238
my %term_state;
2239
sub reset_term_state { my %old_term = %term_state; %term_state = (); %old_term }
2240
sub set_term_state { my %old_term = %term_state; %term_state = @_; %old_term }
2241
%ansi_table = (
2242
# fe-common::core::formats.c:format_expand_styles
2243
(map { my $t = $i++; ($_ => sub { my $n = $term_state{hicolor} ? \&Terminfo::setab16 : \&Terminfo::setab;
2244
$n->($t) }) } (split //, '01234567' )),
2245
(map { my $t = $j++; ($_ => sub { my $n = $term_state{hicolor} ? \&Terminfo::setaf16 : \&Terminfo::setaf;
2246
$n->($t) }) } (split //, 'krgybmcw' )),
2247
(map { my $t = $k++; ($_ => sub { my $n = $term_state{hicolor} ? \&Terminfo::setaf : \&Terminfo::setaf16;
2248
$n->($t) }) } (split //, 'KRGYBMCW')),
2249
# reset
2250
n => sub { $term_state{hicolor} = 0; my $r = Terminfo::op;
2251
for (qw(blink rev bold)) {
2252
$r .= Terminfo->can("exit_$_")->() if delete $term_state{$_};
2253
}
2254
{
2255
local $ansi_table{n} = $ansi_table{N};
2256
$r .= formats_to_ansi_basic($vars{itembg});
2257
}
2258
$r
2259
},
2260
N => sub { reset_term_state(); Terminfo::sgr0 },
2261
# flash/bright
2262
F => sub { my $n = 'blink'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() },
2263
# reverse
2264
8 => sub { my $n = 'rev'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() },
2265
# bold
2266
"_" => sub { my $n = 'bold'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() },
2267
# underline
2268
U => sub { my $n = 'ul'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() },
2269
# italic
2270
I => sub { my $n = 'it'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() },
2271
# bold, used as colour modifier if AWL_HI9 is set
2272
9 => $ENV{AWL_HI9} ? sub { $term_state{hicolor} ^= 1; '' }
2273
: sub { my $n = 'bold'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() },
2274
# delete other stuff
2275
(map { $_ => sub { '' } } (split //, ':|>#[')),
2276
# escape
2277
(map { my $close = $_; $_ => sub { $close } } (split //, '{}%')),
2278
);
2279
for my $base (0 .. 15) {
2280
my $close = $base;
2281
my $idx = ($close&8) | ($close&4)>>2 | ($close&2) | ($close&1)<<2;
2282
$ansi_table{ (sprintf "x0%x", $close) } =
2283
$ansi_table{ (sprintf "x0%X", $close) } =
2284
sub { Terminfo::setab256($idx) };
2285
$ansi_table{ (sprintf "X0%x", $close) } =
2286
$ansi_table{ (sprintf "X0%X", $close) } =
2287
sub { Terminfo::setaf256($idx) };
2288
}
2289
for my $plane (1 .. 6) {
2290
for my $coord (0 .. 35) {
2291
my $close = 16 + ($plane-1) * 36 + $coord;
2292
my $ch = $coord < 10 ? $coord : chr( $coord - 10 + ord 'a' );
2293
$ansi_table{ "x$plane$ch" } =
2294
$ansi_table{ "x$plane\U$ch" } =
2295
sub { Terminfo::setab256($close) };
2296
$ansi_table{ "X$plane$ch" } =
2297
$ansi_table{ "X$plane\U$ch" } =
2298
sub { Terminfo::setaf256($close) };
2299
}
2300
}
2301
for my $gray (0 .. 23) {
2302
my $close = 232 + $gray;
2303
my $ch = chr( $gray + ord 'a' );
2304
$ansi_table{ "x7$ch" } =
2305
$ansi_table{ "x7\U$ch" } =
2306
sub { Terminfo::setab256($close) };
2307
$ansi_table{ "X7$ch" } =
2308
$ansi_table{ "X7\U$ch" } =
2309
sub { Terminfo::setaf256($close) };
2310
}
2311
sub formats_to_ansi_basic {
2312
my $o = shift;
2313
$o =~ s/(%(X..|x..|.))/exists $ansi_table{$2} ? $ansi_table{$2}->() : $1/gex;
2314
$o
2315
}
2316
}
2317
2318
sub _header {
2319
my $str = $vars{title} // uc ::setc();
2320
my $ccs = qr/%(?:X(?:[1-6][0-9A-Z]|7[A-X])|[0-9BCFGIKMNRUWY_])/i;
2321
(my $stripstr = $str) =~ s/($ccs)//g;
2322
my $space = int( ((abs $vars{block}) - length $stripstr) / (1 + length $stripstr));
2323
if ($space > 0) {
2324
my $ss = ' ' x $space;
2325
my @x = $str =~ /((?:$ccs)*\X(?:(?:$ccs)*$)?)/g;
2326
$str = join $ss, '', @x, '';
2327
}
2328
($stripstr = $str) =~ s/($ccs)//g;
2329
my $pad = max 0, (abs $vars{block}) - length $stripstr;
2330
$str = ' ' x ($pad/2) . $str . ' ' x ($pad/2 + $pad%2);
2331
$str
2332
}
2333
2334
sub _add_item {
2335
my ($i, $j, $c, $wi, $screen, $mouse) = @_;
2336
$screen->[$i][$j] = "%N%n$wi";
2337
if (exists $vars{mouse}{$c - 1}) {
2338
$mouse->[$i][$j] = $vars{mouse}{$c - 1};
2339
}
2340
}
2341
sub update_screen {
2342
$disp_update = 0;
2343
unless ($sock && exists $vars{seplen} && exists $vars{block}) {
2344
leave_fs(1);
2345
return;
2346
}
2347
enter_fs();
2348
@screen = () if delete $vars{redraw};
2349
%mouse_coords = ();
2350
my $ncols = ($vars{seplen} + abs $vars{block}) ?
2351
int( ($screenWidth + $vars{seplen}) / ($vars{seplen} + abs $vars{block}) ) : 0;
2352
my $xenl = ($vars{seplen} + abs $vars{block})
2353
&& $ncols > int( ($screenWidth + $vars{seplen} - 1) / ($vars{seplen} + abs $vars{block}) );
2354
my $nrows = $screenHeight - $vars{ha};
2355
my @wi = @{$vars{win}//[]};
2356
my $max_items = $ncols * $nrows;
2357
my $c = $show_title_bar ? 1 : 0;
2358
my $items = @wi + $c;
2359
my $titems = $items > $max_items ? $max_items : $items;
2360
my $i = 0;
2361
my $j = 0;
2362
my @new_screen;
2363
my @new_mouse;
2364
$new_screen[0][0] = _header() #. ' ' x $vars{seplen}
2365
if $show_title_bar;
2366
unless ($nrows > $ncols) { # line layout
2367
++$j if $show_title_bar;
2368
for my $wi (@wi) {
2369
if ($j >= $ncols) {
2370
$j = 0;
2371
++$i;
2372
}
2373
last if $i >= $nrows;
2374
_add_item($i, $j, $show_title_bar ? $c : $c + 1,
2375
$wi, \@new_screen, \@new_mouse);
2376
if ($c + 1 < $titems && $j + 1 < $ncols) {
2377
$new_screen[$i][$j] .= $vars{separator};
2378
}
2379
++$j;
2380
++$c;
2381
}
2382
}
2383
else { # column layout
2384
++$i if $show_title_bar;
2385
for my $wi (@wi) {
2386
if ($i >= $nrows) {
2387
$i = 0;
2388
++$j;
2389
}
2390
last if $j >= $ncols;
2391
_add_item($i, $j, $show_title_bar ? $c : $c + 1,
2392
$wi, \@new_screen, \@new_mouse);
2393
if ($c + $nrows < $titems) {
2394
$new_screen[$i][$j] .= $vars{separator};
2395
}
2396
++$i;
2397
++$c;
2398
}
2399
}
2400
my $step = $vars{seplen} + abs $vars{block};
2401
$i = 0;
2402
my $str = Terminfo::sc . Terminfo::sgr0;
2403
for (my $i = 0; $i < @new_screen; ++$i) {
2404
for (my $j = 0; $j < @{$new_screen[$i]}; ++$j) {
2405
if (defined $new_mouse[$i] && defined $new_mouse[$i][$j]) {
2406
my $from = $j * $step;
2407
$mouse_coords{$i}{$_} = $new_mouse[$i][$j]
2408
for $from .. $from + abs $vars{block};
2409
}
2410
next if defined $screen[$i] && defined $screen[$i][$j]
2411
&& $screen[$i][$j] eq $new_screen[$i][$j];
2412
$str .= Terminfo::cup($i, $j * $step)
2413
. formats_to_ansi_basic($new_screen[$i][$j])
2414
. Terminfo::sgr0;
2415
$str .= Terminfo::el if $j == $#{$new_screen[$i]} && (!$xenl || $j + 1 != $ncols);
2416
}
2417
}
2418
for (@new_screen .. $screenHeight - 1) {
2419
if (!@screen || defined $screen[$_]) {
2420
$str .= Terminfo::cup($_, 0) . Terminfo::sgr0 . Terminfo::el;
2421
}
2422
}
2423
$str .= Terminfo::rc;
2424
safe_print $str;
2425
@screen = @new_screen;
2426
}
2427
2428
sub handle_resize {
2429
if (defined (my $r = safe_qx('stty size'))) {
2430
($screenHeight, $screenWidth) = split ' ', $r;
2431
$resized = 0;
2432
@screen = ();
2433
$disp_update = 1;
2434
if ($one_shot_integration == 2) {
2435
$one_shot_resize--;
2436
}
2437
}
2438
else {
2439
}
2440
}
2441
2442
sub _build_keymap {
2443
%c2w = reverse( %{$vars{key}}, %{$vars{key2}} );
2444
if (!grep { /^[+-]./ } keys %c2w) {
2445
%c2w = (%c2w, map { ("-$_" => $c2w{$_}) } grep { !/^\^./ } keys %c2w);
2446
}
2447
%c2w = map {
2448
my $key = $_;
2449
s{^(-)?(\+)?(\^)?(.)}{
2450
join '', (
2451
($1 ? "\e" : ''),
2452
($2 ? "\e\e" : ''),
2453
($3 ? "$4"^"@" : $4)
2454
)
2455
}e;
2456
$_ => $c2w{$key}
2457
} keys %c2w;
2458
@seqlist = sort { length $b <=> length $a } keys %c2w;
2459
}
2460
2461
sub _match_tmux {
2462
length $ENV{TMUX} && exists $vars{irssienv}{tmux_srv} && length $vars{irssienv}{tmux_pane}
2463
&& $ENV{TMUX} eq $vars{irssienv}{tmux_srv}
2464
}
2465
2466
sub process_keys {
2467
Encode::_utf8_on($keybuf);
2468
my $win;
2469
my $use_mouse;
2470
my $maybe;
2471
KEY: while (length $keybuf && !$maybe) {
2472
$maybe = 0;
2473
if ($keybuf =~ s/^\e\[M(.)(.)(.)//) {
2474
@last_mouse = @mouse;# if @mouse && $mouse[0] < 64;
2475
@mouse = map { -32 + ord } ($1, $2, $3);
2476
$use_mouse = 1;
2477
next KEY;
2478
}
2479
for my $s (@seqlist) {
2480
if ($keybuf =~ s/^\Q$s//) {
2481
$win = $c2w{$s};
2482
$use_mouse = 0;
2483
next KEY;
2484
}
2485
elsif (length $keybuf < length $s && $s =~ /^\Q$keybuf/) {
2486
$maybe = 1;
2487
}
2488
}
2489
unless ($maybe) {
2490
substr $keybuf, 0, 1, '';
2491
}
2492
}
2493
if ($use_mouse && @mouse && @last_mouse &&
2494
$mouse[2] == $last_mouse[2] &&
2495
$mouse[1] == $last_mouse[1] &&
2496
($mouse[0] == 3 || $mouse[0] == 64 || $mouse[0] == 65)) {
2497
if ($mouse[0] == 64) {
2498
$win = 'up';
2499
}
2500
elsif ($mouse[0] == 65) {
2501
$win = 'down';
2502
}
2503
elsif (exists $mouse_coords{$mouse[2] - 1}{$mouse[1] - 1}) {
2504
$win = $mouse_coords{$mouse[2] - 1}{$mouse[1] - 1};
2505
}
2506
elsif ($mouse[2] == 1 && $mouse[1] <= abs $vars{block}) {
2507
$win = $last_mouse[0] != 0 ? 'last' : 'active';
2508
}
2509
else {
2510
}
2511
}
2512
if (defined $win) {
2513
$win =~ s/^_//;
2514
safe_print_sock("$win\n");
2515
if (!exists $ENV{AWL_AUTOFOCUS} || $ENV{AWL_AUTOFOCUS}) {
2516
if (_match_tmux()) {
2517
safe_qx("tmux selectp -t $vars{irssienv}{tmux_pane} 2>&1");
2518
}
2519
elsif (exists $vars{irssienv}{xwinid}) {
2520
safe_qx("wmctrl -ia $vars{irssienv}{xwinid} 2>/dev/null");
2521
}
2522
}
2523
}
2524
Encode::_utf8_off($keybuf);
2525
}
2526
2527
sub check_integration {
2528
return unless $vars{irssienv};
2529
return unless $sock && exists $vars{seplen} && exists $vars{block};
2530
if ($one_shot_integration == 1) {
2531
my $nrows = $screenHeight - $vars{ha};
2532
my $ncols = ($vars{seplen} + abs $vars{block}) ? int( ($screenWidth + $vars{seplen}) / ($vars{seplen} + abs $vars{block}) ) : 0;
2533
my $items = ($show_title_bar ? 1 : 0) + @{$vars{win}//[]};
2534
my $dcols_required = $nrows ? int($items/$nrows) + !!($items%$nrows) : 0;
2535
my $rows_required = $ncols ? int($items/$ncols) + !!($items%$ncols) : 0;
2536
$rows_required = abs $vars{ml}
2537
if ($vars{ml} < 0 || ($vars{ml} > 0 && $rows_required > $vars{ml}));
2538
$dcols_required = abs $vars{mc}
2539
if ($vars{mc} < 0 || ($vars{mc} > 0 && $dcols_required > $vars{mc}));
2540
my $rows = $rows_required + $vars{ha};
2541
my $cols = ($dcols_required * ($vars{seplen} + abs $vars{block})) - $vars{seplen};
2542
if (_match_tmux()) {
2543
# int( ($screenWidth + $vars{seplen}) / ($vars{seplen} + abs $vars{block}) );
2544
my ($pos_flag, $before);
2545
if ($integration_position eq 'left') {
2546
$pos_flag = 'h';
2547
$before = 1;
2548
}
2549
elsif ($integration_position eq 'top') {
2550
$pos_flag = 'v';
2551
$before = 1;
2552
}
2553
elsif ($integration_position eq 'right') {
2554
$pos_flag = 'h';
2555
}
2556
else {
2557
$pos_flag = 'v';
2558
}
2559
my @cmd = "joinp -d$pos_flag -s $ENV{TMUX_PANE} -t $vars{irssienv}{tmux_pane}";
2560
push @cmd, "swapp -d -t $ENV{TMUX_PANE} -s $vars{irssienv}{tmux_pane}"
2561
if $before;
2562
$cols = max($cols, 2);
2563
$rows = max($rows, 2);
2564
2565
safe_qx("tmux " . (join " \\\; ", @cmd) . " 2>&1");
2566
}
2567
else {
2568
$resized = 1;
2569
#safe_qx("resize -s $screenHeight $cols 2>&1")
2570
# if $cols > 0;
2571
}
2572
$one_shot_integration++;
2573
if ($resized == 1) {
2574
handle_resize();
2575
resize_integration();
2576
}
2577
}
2578
elsif ($one_shot_integration == 2) {
2579
resize_integration(1);
2580
}
2581
}
2582
2583
sub resize_integration {
2584
return unless $one_shot_integration;
2585
return unless ($one_shot_resize//0) < 0 || shift;
2586
return if ($one_shot_resize//0) > 0;
2587
2588
my $nrows = $screenHeight - $vars{ha};
2589
my $ncols = ($vars{seplen} + abs $vars{block}) ? int( ($screenWidth + $vars{seplen}) / ($vars{seplen} + abs $vars{block}) ) : 0;
2590
my $items = ($show_title_bar ? 1 : 0) + @{$vars{win}//[]};
2591
my $dcols_required = $nrows ? (int($items/$nrows) + !!($items%$nrows)) : 0;
2592
my $rows_required = $ncols ? int($items/$ncols) + !!($items%$ncols) : 0;
2593
$rows_required = abs $vars{ml}
2594
if ($vars{ml} < 0 || ($vars{ml} > 0 && $rows_required > $vars{ml}));
2595
$dcols_required = abs $vars{mc}
2596
if ($vars{mc} < 0 || ($vars{mc} > 0 && $dcols_required > $vars{mc}));
2597
my $rows = $rows_required + $vars{ha};
2598
my $cols = ($dcols_required * ($vars{seplen} + abs $vars{block})) - $vars{seplen};
2599
if (_match_tmux()) {
2600
my $pos_flag;
2601
my $before = 0;
2602
if ($integration_position eq 'left') {
2603
$pos_flag = 'h';
2604
$before = 1;
2605
}
2606
elsif ($integration_position eq 'top') {
2607
$pos_flag = 'v';
2608
$before = 1;
2609
}
2610
elsif ($integration_position eq 'right') {
2611
$pos_flag = 'h';
2612
}
2613
else {
2614
$pos_flag = 'v';
2615
}
2616
my @cmd;
2617
# hard tmux limits
2618
$cols = max($cols, 2);
2619
$rows = max($rows, 2);
2620
if ($pos_flag eq 'h' && $cols != $screenWidth) {
2621
my $change = $screenWidth - $cols;
2622
my $dir = ($before ^ ($change<0)) ? 'L' : 'R';
2623
push @cmd, "resizep -$dir -t $ENV{TMUX_PANE} @{[abs $change]}";
2624
#push @cmd, "resizep -x $cols -t $ENV{TMUX_PANE}";
2625
$one_shot_resize = 1;
2626
}
2627
if ($pos_flag eq 'v' && $rows != $screenHeight) {
2628
#push @cmd, "resizep -y $rows -t $ENV{TMUX_PANE}";
2629
my $change = $screenHeight - $rows;
2630
my $dir = ($before ^ ($change<0)) ? 'U' : 'D';
2631
push @cmd, "resizep -$dir -t $ENV{TMUX_PANE} @{[abs $change]}";
2632
$one_shot_resize = 1;
2633
}
2634
2635
safe_qx("tmux " . (join " \\\; ", @cmd) . " 2>&1")
2636
if @cmd;
2637
}
2638
else {
2639
$cols = max($cols, 1);
2640
$rows = max($rows, 1);
2641
unless ($nrows > $ncols) { # line layout
2642
if ($rows != $screenHeight) {
2643
safe_qx("resize -s $rows $screenWidth 2>&1");
2644
$one_shot_resize = 1;
2645
}
2646
}
2647
else {
2648
if ($cols != $screenWidth) {
2649
safe_qx("resize -s $screenHeight $cols 2>&1");
2650
$one_shot_resize = 1;
2651
}
2652
}
2653
}
2654
if ($resized == 1) {
2655
handle_resize();
2656
}
2657
}
2658
2659
sub init_integration {
2660
return unless $one_shot_integration;
2661
if (_match_tmux()) {
2662
}
2663
else {
2664
}
2665
safe_print("\e]2;".(uc ::setc())."\e\\");
2666
}
2667
2668
sub main {
2669
require Getopt::Std;
2670
my %opts;
2671
Getopt::Std::getopts('1p:', \%opts);
2672
my $one_shot = $opts{1};
2673
$integration_position = $opts{p};
2674
$one_shot_integration = 0+!!$one_shot;
2675
#shift if @_ && $_[0] eq '--';
2676
&init;
2677
$show_title_bar = 0 if $ENV{AWL_NOTITLE};
2678
init_integration();
2679
until ($got_int) {
2680
$timeout = undef;
2681
if ($resized) {
2682
if ($resized == 1) {
2683
$timeout = 1;
2684
$resized++;
2685
}
2686
else {
2687
handle_resize();
2688
resize_integration();
2689
}
2690
}
2691
unless ($sock || $timeout) {
2692
connect_it();
2693
}
2694
$timeout ||= RECONNECT_TIME unless $sock;
2695
update_screen() if $disp_update;
2696
SELECT: while (my @read = $loop->can_read($timeout)) {
2697
for my $fh (@read) {
2698
if ($fh == \*STDIN) {
2699
if (read STDIN, my $buf, BLOCK_SIZE) {
2700
do {
2701
$keybuf .= $buf;
2702
} while read STDIN, $buf, BLOCK_SIZE;
2703
}
2704
else {
2705
$got_int = 1;
2706
last SELECT;
2707
}
2708
}
2709
else {
2710
if ($fh->read(my $buf, BLOCK_SIZE)) {
2711
do {
2712
$rcvbuf .= $buf;
2713
} while $fh->read($buf, BLOCK_SIZE);
2714
}
2715
else {
2716
$disp_update = 1;
2717
remove_conn($fh);
2718
if ($one_shot) {
2719
$got_int = 1;
2720
last SELECT;
2721
}
2722
$timeout ||= RECONNECT_TIME;
2723
}
2724
}
2725
}
2726
$disp_update |= process_recv() if length $rcvbuf;
2727
process_keys() if length $keybuf;
2728
check_integration() if $one_shot;
2729
update_screen() if $disp_update;
2730
}
2731
continue {
2732
}
2733
}
2734
end_prog();
2735
}
2736
}
2737
2738
1;
2739
2740
# Changelog
2741
# =========
2742
# 1.8
2743
# - use string_width in Irssi 1.2.0
2744
#
2745
# 1.7
2746
# - fix crash on invalid /set awl_sort, introduced in 1.6, reported by
2747
# tpetazzoni
2748
# - delay viewer initialisation
2749
# - improve race condition on tmux resize integration
2750
#
2751
# 1.6
2752
# - add detach setting to hide windows
2753
# - fix race condition when loading the script, reported by madduck
2754
# - improve compatibility with irssi 1.2
2755
# - add special value lru to awl_sort to sort windows by usage
2756
#
2757
# 1.5
2758
# - improve compat. with sideways splits
2759
#
2760
# 1.4
2761
# - fix line wrapping in some themes, reported by justanotherbody
2762
# - fix named window key detection, reported by madduck
2763
# - make title (in viewer and shared_sbar) configurable
2764
#
2765
# 1.3
2766
# - workaround for irssi issue #572
2767
#
2768
# 1.2
2769
# - new format to choose abbreviation character
2770
#
2771
# 1.1
2772
# - infinite loop on shortening certain window names reported by Kalan
2773
#
2774
# 1.0
2775
# - new awl_viewer_launch setting and an array of related settings
2776
# - fixed regression bug /exec -interactive
2777
# - fixed some warnings in perl 5.10 reported by kl3
2778
# - workaround for crash due to infinite recursion in irssi's Perl
2779
# error handling
2780
#
2781
# 0.9
2782
# - fix endless loop in awin detection code!
2783
# - correct colour swap in awl_viewer
2784
# - fix passing of alternate socket path to the viewer
2785
# - potential undefinedness in mouse refnum hinted at by Canopus
2786
# - fixed regression bug /exec -interactive
2787
# - add case-insensitive modifier to awl_sort
2788
# - run custom_xform on awl_prefer_name also
2789
# - avoid inconsistent active window state after awin detection
2790
# reported by ss
2791
# - revert %9-hack in the viewer prompted by discussion with pierrot
2792
# - fix new warning in perl 5.22
2793
#
2794
# 0.8
2795
# - replace fifo mode with external viewer script
2796
# - remove bundled cpan modules
2797
# - work around bogus irssi warning
2798
# - improve mouse support
2799
# - workaround for broken cumode in irssi 0.8.15
2800
# - fix handling of non-meta windows (uninitialized warning)
2801
# - add 256 colour support, strip true colour codes
2802
# - fix totally bogus $N padding reported by Ed S.
2803
# - make /window goto #name mappings work but ignore non-existant ones
2804
# - improve incomplete reads reported by bcode
2805
# - fix single % in awl_viewer reported by bcode
2806
# - add support for key bindings by nike and ferret
2807
# - coerce utf8 key binds
2808
# - add settings: custom_xform, last_line_shade, hide_name_data
2809
# - abbreviations were broken in some cases
2810
# - fix some misuse of / as cmdchar in mouse script reported by bcode
2811
# - add shared status bar mode
2812
# - ${type} variables for custom_xform setting
2813
# - crash if custom_xform had runtime error
2814
# - update sorting documentation
2815
# - fix odd case in size calculation noted by lasers
2816
# - add missing font styles to the viewer reported by ishanyx
2817
# - add italic
2818
#
2819
# 0.7g
2820
# - remove screen support and replace it with fifo support
2821
# - add double-width support to the shortener
2822
# - correct documentation regarding $T vs. display_header
2823
# - add missing refresh for window item changed (thanks vague)
2824
# - add visible windows
2825
# - add exemptions for active window
2826
# - workaround for hiding the window changes from trackbar
2827
# - hack to force 16colours in screen mode
2828
# - remember last window (reported by earthnative)
2829
# - wrong window focus on new queries (reported by emsid)
2830
# - dataloss bug on trying to remember last window
2831
#
2832
# 0.6d+
2833
# - add support for network headers
2834
# - fixed regression bug /exec -interactive
2835
#
2836
# 0.6ca+
2837
# - add screen support (from nicklist.pl)
2838
# - names can now have a max length and window names can be used
2839
# - fixed a bug with block display in screen mode and status bar mode
2840
# - added space handling to ir_fe and removed it again
2841
# - now handling formats on my own
2842
# - started to work on $tag display
2843
# - added warning about missing sb_act_none abstract leading to
2844
# - display*active settings
2845
# - added warning about the bug in awl_display_(no)key_active settings
2846
# - mouse hack
2847
#
2848
# 0.5d
2849
# - add setting to also hide the last status bar if empty (awl_all_disable)
2850
# - reverted to old utf8 code to also calculate broken utf8 length correctly
2851
# - simplified dealing with status bars in wlreset
2852
# - added a little tweak for the renamed term_type somewhere after Irssi 0.8.9
2853
# - fixed bug in handling channel #$$
2854
# - reset background colour at the beginning of an entry
2855
#
2856
# 0.4d
2857
# - fixed order of disabling status bars
2858
# - several attempts at special chars, without any real success
2859
# and much more weird new bugs caused by this
2860
# - setting to specify sort order
2861
# - reduced timeout values
2862
# - added awl_hide_data
2863
# - make it so the dynamic sub is actually deleted
2864
# - fix a bug with removing of the last separator
2865
# - take into consideration parse_special
2866
#
2867
# 0.3b
2868
# - automatically kill old status bars
2869
# - reset on /reload
2870
# - position/placement settings
2871
#
2872
# 0.2
2873
# - automated retrieval of key bindings (thanks grep.pl authors)
2874
# - improved removing of status bars
2875
# - got rid of status chop
2876
#
2877
# 0.1
2878
# - Based on chanact.pl which was apparently based on lightbar.c and
2879
# nicklist.pl with various other ideas from random scripts.
2880
2881