dotfiles

*nix config files
git clone git://git.pyratebeard.net/dotfiles.git
Log | Files | Refs | README

adv_windowlist.pl (88055B)


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