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