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.