archive

- Random tools & helpful resources for IRC
git clone git://git.acid.vegas/archive.git
Log | Files | Refs | Archive

awl.pl (78257B)

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