1# This script adds a nicklist to the right of irssi
2# for documentation: see http://wouter.coekaerts.be/site/irssi/nicklist
3
4# Copyright (C) 2002-2007  Wouter Coekaerts <coekie@irssi.org>
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19
20use Irssi;
21use strict;
22use IO::Handle; # for (auto)flush
23use Fcntl; # for sysopen
24use vars qw($VERSION %IRSSI);
25$VERSION = '0.4.12';
26%IRSSI = (
27	authors     => 'Wouter Coekaerts',
28	contact     => 'coekie@irssi.org',
29	name        => 'nicklist',
30	description => 'draws a nicklist to another terminal, or at the right of your irssi in the same terminal',
31	license     => 'GPLv2',
32	url         => 'http://wouter.coekaerts.be/irssi',
33	changed     => '2018-10-02'
34);
35
36sub cmd_help {
37	print ( <<EOF
38Commands:
39NICKLIST HELP
40NICKLIST SCROLL <nr of lines>
41NICKLIST SCREEN
42NICKLIST FIFO
43NICKLIST ON
44NICKLIST OFF
45NICKLIST TOGGLE
46NICKLIST UPDATE
47
48For help see: http://wouter.coekaerts.be/site/irssi/nicklist
49
50in short:
51
521. FIFO MODE
53- in irssi: /NICKLIST FIFO (only the first time, to create the fifo)
54- in a shell, in a window where you want the nicklist: cat ~/.irssi/nicklistfifo
55- back in irssi:
56    /SET nicklist_heigth <height of nicklist>
57    /SET nicklist_width <width of nicklist>
58    /NICKLIST FIFO
59
602. SCREEN MODE
61- start irssi inside screen ("screen irssi")
62- /NICKLIST SCREEN
63EOF
64    );
65}
66
67my $prev_lines = 0;                  # number of lines in previous written nicklist
68my $scroll_pos = 0;                  # scrolling position
69my $cursor_line;                     # line the cursor is currently on
70my ($OFF, $SCREEN, $FIFO) = (0,1,2); # modes
71my $mode = $OFF;                     # current mode
72my $need_redraw = 0;                 # nicklist needs redrawing
73my $screen_resizing = 0;             # terminal is being resized
74my $active_channel;                  # (REC)
75
76my @nicklist=();                     # array of hashes, containing the internal nicklist of the active channel
77	# nick => realnick
78	# modeflag => '@', '%', '+', or other mode char
79	# modepos => number representing the position in which to sort nicks with that mode
80	# status => (not used yet...)
81	# text => text to be printed
82	# cmp => text used to compare (sort) nicks
83
84# 'cached' settings
85my ($screen_prefix, $irssi_width, %prefix_mode, @prefix_status, $height, $nicklist_width, $check_friends, @prefix_friends);
86
87sub read_settings {
88	($screen_prefix = Irssi::settings_get_str('nicklist_screen_prefix')) =~ s/\\e/\033/g;
89
90	($prefix_mode{'@'} = Irssi::settings_get_str('nicklist_prefix_mode_op')) =~ s/\\e/\033/g;
91	($prefix_mode{'%'} = Irssi::settings_get_str('nicklist_prefix_mode_halfop')) =~ s/\\e/\033/g;
92	($prefix_mode{'+'} = Irssi::settings_get_str('nicklist_prefix_mode_voice')) =~ s/\\e/\033/g;
93	($prefix_mode{' '} = Irssi::settings_get_str('nicklist_prefix_mode_normal')) =~ s/\\e/\033/g;
94
95	(my $prefix_mode_other = Irssi::settings_get_str('nicklist_prefix_mode_other')) =~ s/\\e/\033/g;
96	foreach my $p (split (/ /, $prefix_mode_other)) {
97		next if $p eq '';
98		if ($p !~ /(.)=(.*)/) {
99			Irssi::print("Could not parse nicklist_prefix_mode_other part '$p'. Expected space separated list of <mode character>=<prefix>");
100			last;
101		} else {
102			$prefix_mode{$1} = $2;
103		}
104	}
105
106	(my $prefix_friends = Irssi::settings_get_str('nicklist_prefix_friends')) =~ s/\\e/\033/g;
107	foreach my $p (split (/ /, $prefix_friends)) {
108		next if $p eq '';
109		if ($p !~ /(.+?)=(.*)/) {
110			Irssi::print("Could not parse nicklist_prefix_friends part '$p'. Expected space separated list of <flags>=<prefix>");
111			last;
112		} else {
113			push @prefix_friends, {'flags' => $1, 'prefix' => $2};
114		}
115	}
116
117	$check_friends = ($prefix_friends ne '');
118
119	if ($mode != $SCREEN) {
120		$height = Irssi::settings_get_int('nicklist_height');
121	}
122	my $new_nicklist_width = Irssi::settings_get_int('nicklist_width');
123	if ($new_nicklist_width != $nicklist_width && $mode == $SCREEN) {
124		sig_terminal_resized();
125	}
126	$nicklist_width = $new_nicklist_width;
127}
128
129sub update {
130	read_settings();
131	make_nicklist();
132}
133
134##################
135##### OUTPUT #####
136##################
137
138### on ###
139
140sub cmd_on {
141	if (uc(Irssi::settings_get_str('nicklist_automode')) eq 'SCREEN') {
142		cmd_screen_start();
143	} elsif (uc(Irssi::settings_get_str('nicklist_automode')) eq 'FIFO') {
144		cmd_fifo_start();
145	}
146}
147
148### off ###
149
150sub cmd_off {
151	if ($mode == $SCREEN) {
152		screen_stop();
153	} elsif ($mode == $FIFO) {
154		fifo_stop();
155	}
156}
157
158### toggle ###
159
160sub cmd_toggle {
161	if ($mode == $OFF) {
162		cmd_on();
163	} else {
164		cmd_off();
165	}
166}
167
168### fifo ###
169
170sub cmd_fifo_start {
171	read_settings();
172	my $path = Irssi::settings_get_str('nicklist_fifo_path');
173	unless (-p $path) { # not a pipe
174	    if (-e _) { # but a something else
175	        die "$0: $path exists and is not a pipe, please remove it\n";
176	    } else {
177	        require POSIX;
178	        POSIX::mkfifo($path, 0666) or die "can\'t mkfifo $path: $!";
179		Irssi::print("Fifo created. Start reading it (\"cat $path\") and try again.");
180		return;
181	    }
182	}
183	if (!sysopen(FIFO, $path, O_WRONLY | O_NONBLOCK)) { # or die "can't write $path: $!";
184		Irssi::print("Couldn\'t write to the fifo ($!). Please start reading the fifo (\"cat $path\") and try again.");
185		return;
186	}
187	FIFO->autoflush(1);
188	print FIFO "\033[2J\033[1;1H"; # erase screen & jump to 0,0
189	$cursor_line = 0;
190	if ($mode == $SCREEN) {
191		screen_stop();
192	}
193	$mode = $FIFO;
194	make_nicklist();
195}
196
197sub fifo_stop {
198	close FIFO;
199	$mode = $OFF;
200	Irssi::print("Fifo closed.");
201}
202
203### screen ###
204
205sub cmd_screen_start {
206	if (!defined($ENV{'STY'})) {
207		Irssi::print 'screen not detected, screen mode only works inside screen';
208		return;
209	}
210	read_settings();
211	if ($mode == $SCREEN) {return;}
212	if ($mode == $FIFO) {
213		fifo_stop();
214	}
215	$mode = $SCREEN;
216	Irssi::signal_add_last('gui print text finished', \&sig_gui_print_text_finished);
217	Irssi::signal_add_last('gui page scrolled', \&sig_page_scrolled);
218	Irssi::signal_add('terminal resized', \&sig_terminal_resized);
219	screen_size();
220	make_nicklist();
221}
222
223sub screen_stop {
224	$mode = $OFF;
225	Irssi::signal_remove('gui print text finished', \&sig_gui_print_text_finished);
226	Irssi::signal_remove('gui page scrolled', \&sig_page_scrolled);
227	Irssi::signal_remove('terminal resized', \&sig_terminal_resized);
228	system 'screen -x '.$ENV{'STY'}.' -X fit';
229	# we wait a second to make sure the fit command was processed
230	Irssi::timeout_add_once(1000, \&screen_size, []);
231}
232
233#sub screen_size_real {
234sub screen_size {
235	if ($mode != $SCREEN) {
236		return;
237	}
238	$screen_resizing = 1;
239	# fit screen
240	system 'screen -x '.$ENV{'STY'}.' -X fit';
241	# get size (from perldoc -q size)
242	my ($winsize, $row, $col, $xpixel, $ypixel);
243	eval 'use Term::ReadKey; ($col, $row, $xpixel, $ypixel) = GetTerminalSize';
244	#	require Term::ReadKey 'GetTerminalSize';
245	#	($col, $row, $xpixel, $ypixel) = Term::ReadKey::GetTerminalSize;
246	#};
247	if ($@) { # no Term::ReadKey, try the ugly way
248		eval {
249			require 'sys/ioctl.ph';
250			# without this reloading doesn't work. workaround for some unknown bug
251			do 'asm/ioctls.ph';
252		};
253
254		# ugly way not working, let's try something uglier, the dg-hack(tm) (constant for linux only?)
255		if($@) { no strict 'refs'; *TIOCGWINSZ = sub { return 0x5413 } }
256
257		unless (defined &TIOCGWINSZ) {
258			die "Term::ReadKey not found, and ioctl 'workaround' failed. Install the Term::ReadKey perl module to use screen mode.\n";
259		}
260		open(TTY, "+<","/dev/tty") or die "No tty: $!";
261		unless (ioctl(TTY, &TIOCGWINSZ, $winsize='')) {
262			die "Term::ReadKey not found, and ioctl 'workaround' failed ($!). Install the Term::ReadKey perl module to use screen mode.\n";
263		}
264		close(TTY);
265		($row, $col, $xpixel, $ypixel) = unpack('S4', $winsize);
266	}
267
268	# set screen width
269	$irssi_width = $col-$nicklist_width-1;
270	$height = $row-1;
271
272	system 'screen -x '.$ENV{'STY'}.' -X width -w ' . $irssi_width;
273	# wait another second for the resizing, and then redraw.
274	Irssi::timeout_add_once(1000,sub {$screen_resizing = 0; redraw()}, []);
275}
276
277sub sig_terminal_resized {
278	if ($screen_resizing) {
279		return;
280	}
281	$screen_resizing = 1;
282	Irssi::timeout_add_once(1000,\&screen_size,[]);
283}
284
285
286### both ###
287
288sub nicklist_write_start {
289	if ($mode == $SCREEN) {
290		print STDERR "\033P\0337\033\\"; # save cursor
291	}
292}
293
294sub nicklist_write_end {
295	if ($mode == $SCREEN) {
296		print STDERR "\033P\0338\033\\"; # restore cursor
297	}
298}
299
300sub nicklist_write_line {
301	my ($line, $data) = @_;
302	if ($mode == $SCREEN) {
303		print STDERR "\033P\033[" . ($line+1) . ';'. ($irssi_width+1) .'H'. $screen_prefix . $data . "\033\\";
304	} elsif ($mode == $FIFO) {
305		$data = "\033[m$data"; # reset color
306		if ($line == $cursor_line+1) {
307			$data = "\n$data"; # next line
308		} elsif ($line == $cursor_line) {
309			$data = "\033[1G".$data; # back to beginning of line
310		} else {
311			$data = "\033[".($line+1).";0H".$data; # jump
312		}
313		$cursor_line=$line;
314		print(FIFO $data) or fifo_stop();
315	}
316}
317
318sub calc_prefix_friends {
319	my ($nick) = @_;
320
321	return '' unless $check_friends
322		&& $nick->{'host'}
323		&& is_friend($active_channel->{'server'}->{'chatnet'}, $active_channel->{'name'}, $nick->{'nick'}, $nick->{'host'});
324
325	my $flags = get_flags($active_channel->{'server'}->{'chatnet'}, $active_channel->{'name'}, $nick->{'nick'}, $nick->{'host'});
326
327	my $prefix;
328	foreach my $prefix_friend (@prefix_friends) {
329		if ($prefix_friend->{'flags'} eq 'noflag') {
330			if ($flags eq '') {
331				$prefix = $prefix_friend->{'prefix'};
332				last;
333			}
334		} elsif (check_modes($flags, $prefix_friend->{'flags'})) {
335			$prefix = $prefix_friend->{'prefix'};
336		}
337	}
338
339	return $prefix ? $prefix : '';
340}
341
342# recalc the text of the nicklist item
343sub calc_text {
344	my ($nick) = @_;
345	my $tmp = $nicklist_width-3;
346	(my $text = $nick->{'nick'}) =~ s/^(.{$tmp})..+$/$1\033[34m~/; # strip nick if too long
347
348	my $prefix_mode = $prefix_mode{$nick->{'modeflag'}};
349	if (! defined($prefix_mode) ) {
350		$prefix_mode = $nick->{'modeflag'};
351	}
352
353	my $prefix_friends = calc_prefix_friends($nick);
354
355	$nick->{'text'} =
356		$prefix_mode .
357		$prefix_friends .
358		$text .
359		(' ' x ($nicklist_width-length($nick->{'nick'})-1)) .
360		"\033[m"; # reset
361	$nick->{'cmp'} = $nick->{'modepos'}.lc($nick->{'nick'});
362}
363
364# redraw the given nick (nr) if it is visible
365sub redraw_nick_nr {
366	my ($nr) = @_;
367	my $line = $nr - $scroll_pos;
368	if ($line >= 0 && $line < $height) {
369		nicklist_write_line($line, $nicklist[$nr]->{'text'});
370	}
371}
372
373# nick was inserted, redraw area if necessary
374sub draw_insert_nick_nr {
375	my ($nr) = @_;
376	my $line = $nr - $scroll_pos;
377	if ($line < 0) { # nick is inserted above visible area
378		$scroll_pos++; # 'scroll' down :)
379	} elsif ($line < $height) { # line is visible
380		if ($mode == $SCREEN) {
381			need_redraw();
382		} elsif ($mode == $FIFO) {
383			my $data = "\033[m\033[L". $nicklist[$nr]->{'text'}; # reset color & insert line & write nick
384			if ($line == $cursor_line) {
385				$data = "\033[1G".$data; # back to beginning of line
386			} else {
387				$data = "\033[".($line+1).";1H".$data; # jump
388			}
389			$cursor_line=$line;
390			print(FIFO $data) or fifo_stop();
391			if ($prev_lines < $height) {
392				$prev_lines++; # the nicklist has one line more
393			}
394		}
395	}
396}
397
398sub draw_remove_nick_nr {
399	my ($nr) = @_;
400	my $line = $nr - $scroll_pos;
401	if ($line < 0) { # nick removed above visible area
402		$scroll_pos--; # 'scroll' up :)
403	} elsif ($line < $height) { # line is visible
404		if ($mode == $SCREEN) {
405			need_redraw();
406		} elsif ($mode == $FIFO) {
407			#my $data = "\033[m\033[L[i$line]". $nicklist[$nr]->{'text'}; # reset color & insert line & write nick
408			my $data = "\033[M"; # delete line
409			if ($line != $cursor_line) {
410				$data = "\033[".($line+1)."d".$data; # jump
411			}
412			$cursor_line=$line;
413			print(FIFO $data) or fifo_stop();
414			if (@nicklist-$scroll_pos >= $height) {
415				redraw_nick_nr($scroll_pos+$height-1);
416			}
417		}
418	}
419}
420
421# redraw the whole nicklist
422sub redraw {
423	$need_redraw = 0;
424	#make_nicklist();
425	nicklist_write_start();
426	my $line = 0;
427	### draw nicklist ###
428	for (my $i=$scroll_pos;$line < $height && $i < @nicklist; $i++) {
429		nicklist_write_line($line++, $nicklist[$i]->{'text'});
430	}
431
432	### clean up other lines ###
433	my $real_lines = $line;
434	while($line < $prev_lines) {
435		nicklist_write_line($line++,' ' x $nicklist_width);
436	}
437	$prev_lines = $real_lines;
438	nicklist_write_end();
439}
440
441# redraw (with little delay to avoid redrawing to much)
442sub need_redraw {
443	if(!$need_redraw) {
444		$need_redraw = 1;
445		Irssi::timeout_add_once(10,\&redraw,[]);
446	}
447}
448
449sub sig_page_scrolled {
450	$prev_lines = $height; # we'll need to redraw everything if he scrolled up
451	need_redraw;
452}
453
454# redraw (with delay) if the window is visible (only in screen mode)
455sub sig_gui_print_text_finished {
456	if ($need_redraw) { # there's already a redraw 'queued'
457		return;
458	}
459	my $window = @_[0];
460	if ($window->{'refnum'} == Irssi::active_win->{'refnum'} || Irssi::settings_get_str('nicklist_screen_split_windows') eq '*') {
461		need_redraw;
462		return;
463	}
464	foreach my $win (split(/[ ,]/, Irssi::settings_get_str('nicklist_screen_split_windows'))) {
465		if ($window->{'refnum'} == $win || $window->{'name'} eq $win) {
466			need_redraw;
467			return;
468		}
469	}
470}
471
472###################
473##### FRIENDS #####
474###################
475
476# checks if $has_modes is in $need_modes, copied from trigger.pl
477sub check_modes {
478	my ($has_modes, $need_modes) = @_;
479	my $matches;
480	my $switch = 1; # if a '-' if found, will be 0 (meaning the modes should not be set)
481	foreach my $need_mode (split /&/,$need_modes) {
482		$matches = 0;
483		foreach my $char (split //,$need_mode) {
484			if ($char eq '-') {
485				$switch = 0;
486			} elsif ($char eq '+') {
487				$switch = 1;
488			} elsif ((index($has_modes,$char) != -1) == $switch) {
489				$matches = 1;
490				last;
491			}
492		}
493		if (!$matches) {
494			return 0;
495		}
496	}
497	return 1;
498}
499
500# get someones flags from people.pl or friends(_shasta).pl, copied from trigger.pl
501sub get_flags {
502	my ($chatnet, $channel, $nick, $address) = @_;
503	my $flags;
504	no strict 'refs';
505	if (%{ 'Irssi::Script::people::' }) {
506		if (defined ($channel)) {
507			$flags = (&{ 'Irssi::Script::people::find_local_flags' }($chatnet,$channel,$nick,$address));
508		} else {
509			$flags = (&{ 'Irssi::Script::people::find_global_flags' }($chatnet,$nick,$address));
510		}
511		$flags = join('',keys(%{$flags}));
512	} else {
513		my $shasta;
514		if (%{ 'Irssi::Script::friends_shasta::' }) {
515			$shasta = 'friends_shasta';
516		} elsif (defined &{ 'Irssi::Script::friends::get_idx' }) {
517			$shasta = 'friends';
518		}
519		if (!$shasta) {
520			return undef;
521		}
522		my $idx = (&{ 'Irssi::Script::'.$shasta.'::get_idx' }($nick,$address));
523		if ($idx == -1) {
524			return '';
525		}
526		$flags = (&{ 'Irssi::Script::'.$shasta.'::get_friends_flags' }($idx,undef));
527		if ($channel) {
528			$flags .= (&{ 'Irssi::Script::'.$shasta.'::get_friends_flags' }($idx,$channel));
529		}
530	}
531	return $flags;
532}
533
534sub is_friend {
535	my ($chatnet, $channel, $nick, $address) = @_;
536	no strict 'refs';
537	if (%{ 'Irssi::Script::people::' }) {
538		return (() != &{'Irssi::Script::people::find_users'}($chatnet, $nick, $address));
539		my $flags;
540		if (defined ($channel)) {
541			$flags = (&{ 'Irssi::Script::people::find_local_flags' }($chatnet,$channel,$nick,$address));
542		} else {
543			$flags = (&{ 'Irssi::Script::people::find_global_flags' }($chatnet,$nick,$address));
544		}
545		return ($flags ne ''); # TODO: test this
546	} else {
547		my $shasta;
548		if (%{ 'Irssi::Script::friends_shasta::' }) {
549			$shasta = 'friends_shasta';
550		} elsif (defined &{ 'Irssi::Script::friends::get_idx' }) {
551			$shasta = 'friends';
552		}
553		if (!$shasta) {
554			return undef;
555		}
556		my $idx = (&{ 'Irssi::Script::'.$shasta.'::get_idx' }($nick,$address));
557		return ($idx != -1);
558	}
559}
560
561####################
562##### NICKLIST #####
563####################
564
565# returns the position of the given nick(as string) in the (internal) nicklist
566sub find_nick {
567	my ($nick) = @_;
568	for (my $i=0;$i < @nicklist; $i++) {
569		if ($nicklist[$i]->{'nick'} eq $nick) {
570			return $i;
571		}
572	}
573	return -1;
574}
575
576# find position where nick should be inserted into the list
577sub find_insert_pos {
578	my ($cmp)= @_;
579	for (my $i=0;$i < @nicklist; $i++) {
580		if ($nicklist[$i]->{'cmp'} gt $cmp) {
581			return $i;
582		}
583	}
584	return scalar(@nicklist); #last
585}
586
587# make the (internal) nicklist (@nicklist)
588sub make_nicklist {
589	@nicklist = ();
590	$scroll_pos = 0;
591
592	### get & check channel ###
593	my $channel = Irssi::active_win->{active};
594
595	if (!$channel || (ref($channel) ne 'Irssi::Irc::Channel' && ref($channel) ne 'Irssi::Silc::Channel' && ref($channel) ne 'Irssi::Xmpp::Channel') || $channel->{'type'} ne 'CHANNEL' || ($channel->{chat_type} ne 'SILC' && !$channel->{'names_got'}) ) {
596		$active_channel = undef;
597		# no nicklist
598	} else {
599		$active_channel = $channel;
600		### make nicklist ###
601		foreach my $nick ($channel->nicks()) {
602			my $thisnick = {'nick' => $nick->{'nick'}};
603			recalc_nick($thisnick, $nick);
604			push @nicklist, $thisnick;
605		}
606		@nicklist = sort {$a->{'cmp'} cmp $b->{'cmp'}} @nicklist;
607	}
608	need_redraw();
609}
610
611# insert nick(as hash) into nicklist
612# pre: cmp has to be calculated
613sub insert_nick {
614	my ($nick) = @_;
615	my $nr = find_insert_pos($nick->{'cmp'});
616	splice @nicklist, $nr, 0, $nick;
617	draw_insert_nick_nr($nr);
618}
619
620# remove nick(as nr) from nicklist
621sub remove_nick {
622	my ($nr) = @_;
623	splice @nicklist, $nr, 1;
624	draw_remove_nick_nr($nr);
625}
626
627# update the mode and cmp of a nick, based on a nickrec from irssi
628sub recalc_nick {
629	my ($nick, $nickrec) = @_;
630	if (! $nickrec) {
631		$nickrec = $active_channel->nick_find($nick->{'nick'});
632	}
633
634	my $nickflags = $active_channel->{'server'}->get_nick_flags() . ' ';
635
636	my $flag = (
637		$nickrec->{'op'} ? '@' :
638		$nickrec->{'halfop'} ? '%' :
639		$nickrec->{'voice'} ? '+' :
640		' '
641	);
642
643	if ($nickrec->{'other'} && index($nickflags, $nick->{'other'}) < index($nickflags, $flag)) {
644		$flag = chr($nickrec->{'other'});
645	}
646
647	$nick->{'modepos'} = index($nickflags, $flag);
648	$nick->{'modeflag'} = $flag;
649
650	$nick->{'host'} = $nickrec->{'host'};
651	calc_text($nick);
652}
653
654###################
655##### ACTIONS #####
656###################
657
658# scroll the nicklist, arg = number of lines to scroll, positive = down, negative = up
659sub cmd_scroll {
660	my $channel = Irssi::active_win->{active};
661	if (!$channel || !$channel->can('nicks')) { # active window is not a channel
662		return;
663	}
664	my @nicks = $channel->nicks;
665	my $nick_count = scalar(@nicks)+0;
666
667	if (!$active_channel) { # not a channel active
668		return;
669	}
670	if (!$channel || $channel->{type} ne 'CHANNEL' || !$channel->{names_got} || $nick_count <= $height) {
671		return;
672	}
673	if ($nick_count <= Irssi::settings_get_int('nicklist_height')) {
674		return;
675	}
676	$scroll_pos += @_[0];
677
678	if ($scroll_pos > $nick_count - $height) {
679		$scroll_pos = $nick_count - $height;
680	}
681	if ($scroll_pos <= 0) {
682		$scroll_pos = 0;
683	}
684	need_redraw();
685}
686
687sub is_active_channel {
688	my ($server,$channel) = @_; # (channel as string)
689	return ($server && $server->{'tag'} eq $active_channel->{'server'}->{'tag'} && $server->channel_find($channel) && $active_channel && $server->channel_find($channel)->{'name'} eq $active_channel->{'name'});
690}
691
692sub sig_channel_wholist { # this is actualy a little late, when the names are received would be better
693	my ($channel) = @_;
694	if (Irssi::active_win->{'active'} && Irssi::active_win->{'active'}->{'name'} eq $channel->{'name'}) { # the channel joined is active
695		make_nicklist
696	}
697}
698
699sub sig_join {
700	my ($server,$channel,$nick,$address) = @_;
701	if (!is_active_channel($server,$channel)) {
702		return;
703	}
704	my $newnick = {'nick' => $nick};
705	recalc_nick($newnick);
706	insert_nick($newnick);
707}
708
709sub sig_kick {
710	my ($server, $channel, $nick, $kicker, $address, $reason) = @_;
711	if (!is_active_channel($server,$channel)) {
712		return;
713	}
714	my $nr = find_nick($nick);
715	if ($nr == -1) {
716		Irssi::print("nicklist warning: $nick was kicked from $channel, but not found in nicklist");
717	} else {
718		remove_nick($nr);
719	}
720}
721
722sub sig_part {
723	my ($server,$channel,$nick,$address, $reason) = @_;
724	if (!is_active_channel($server,$channel)) {
725		return;
726	}
727	my $nr = find_nick($nick);
728	if ($nr == -1) {
729		Irssi::print("nicklist warning: $nick has parted $channel, but was not found in nicklist");
730	} else {
731		remove_nick($nr);
732	}
733
734}
735
736sub sig_quit {
737	my ($server,$nick,$address, $reason) = @_;
738	if ($server->{'tag'} ne $active_channel->{'server'}->{'tag'}) {
739		return;
740	}
741	my $nr = find_nick($nick);
742	if ($nr != -1) {
743		remove_nick($nr);
744	}
745}
746
747sub sig_nick {
748	my ($server, $newnick, $oldnick, $address) = @_;
749	if ($server->{'tag'} ne $active_channel->{'server'}->{'tag'}) {
750		return;
751	}
752	my $nr = find_nick($oldnick);
753	if ($nr != -1) { # if nick was found (nickchange is in current channel)
754		my $nick = $nicklist[$nr];
755		remove_nick($nr);
756		$nick->{'nick'} = $newnick;
757		calc_text($nick);
758		insert_nick($nick);
759	}
760}
761
762sub sig_mode {
763	my ($channel, $nick, $setby, $mode, $type) = @_; # (nick and channel as rec)
764	if ($channel->{'server'}->{'tag'} ne $active_channel->{'server'}->{'tag'} || $channel->{'name'} ne $active_channel->{'name'}) {
765		return;
766	}
767	my $nr = find_nick($nick->{'nick'});
768	if ($nr == -1) {
769		Irssi::print("nicklist warning: $nick->{'nick'} had mode set on $channel->{'name'}, but was not found in nicklist");
770	} else {
771		my $nicklist_item = $nicklist[$nr];
772		remove_nick($nr);
773		recalc_nick($nicklist_item, $nick);
774		insert_nick($nicklist_item);
775	}
776}
777
778##### command binds #####
779Irssi::command_bind 'nicklist' => sub {
780    my ( $data, $server, $item ) = @_;
781    $data =~ s/\s+$//g;
782    Irssi::command_runsub ('nicklist', $data, $server, $item ) ;
783};
784Irssi::signal_add_first 'default command nicklist' => sub {
785	# gets triggered if called with unknown subcommand
786	cmd_help();
787};
788Irssi::command_bind('nicklist update',\&update);
789Irssi::command_bind('nicklist help',\&cmd_help);
790Irssi::command_bind('nicklist scroll',\&cmd_scroll);
791Irssi::command_bind('nicklist fifo',\&cmd_fifo_start);
792Irssi::command_bind('nicklist screen',\&cmd_screen_start);
793Irssi::command_bind('nicklist screensize',\&screen_size);
794Irssi::command_bind('nicklist on',\&cmd_on);
795Irssi::command_bind('nicklist off',\&cmd_off);
796Irssi::command_bind('nicklist toggle',\&cmd_toggle);
797
798##### signals #####
799Irssi::signal_add_last('window item changed', \&make_nicklist);
800Irssi::signal_add_last('window changed', \&make_nicklist);
801Irssi::signal_add_last('channel wholist', \&sig_channel_wholist);
802Irssi::signal_add_first('message join', \&sig_join); # first, to be before ignores
803Irssi::signal_add_first('message part', \&sig_part);
804Irssi::signal_add_first('message kick', \&sig_kick);
805Irssi::signal_add_first('message quit', \&sig_quit);
806Irssi::signal_add_first('message nick', \&sig_nick);
807Irssi::signal_add_first('message own_nick', \&sig_nick);
808Irssi::signal_add_first('nick mode changed', \&sig_mode);
809
810Irssi::signal_add('setup changed', \&read_settings);
811
812##### settings #####
813Irssi::settings_add_str('nicklist', 'nicklist_screen_prefix', '\e[m ');
814Irssi::settings_add_str('nicklist', 'nicklist_prefix_mode_op', '\e[32m@\e[39m');
815Irssi::settings_add_str('nicklist', 'nicklist_prefix_mode_halfop', '\e[34m%\e[39m');
816Irssi::settings_add_str('nicklist', 'nicklist_prefix_mode_voice', '\e[33m+\e[39m');
817Irssi::settings_add_str('nicklist', 'nicklist_prefix_mode_normal', ' ');
818Irssi::settings_add_str('nicklist', 'nicklist_prefix_mode_other', '&=\e[31m&\e[39m ~=\e[35m~\e[39m');
819Irssi::settings_add_str('nicklist', 'nicklist_prefix_friends', 'o=\e[32m v=\e[33m noflag=\e[1m');
820
821Irssi::settings_add_int('nicklist', 'nicklist_width',11);
822Irssi::settings_add_int('nicklist', 'nicklist_height',24);
823Irssi::settings_add_str('nicklist', 'nicklist_fifo_path', Irssi::get_irssi_dir . '/nicklistfifo');
824Irssi::settings_add_str('nicklist', 'nicklist_screen_split_windows', '');
825Irssi::settings_add_str('nicklist', 'nicklist_automode', '');
826
827read_settings();
828cmd_on();
829