summaryrefslogtreecommitdiffstats
path: root/src/fe-common
diff options
context:
space:
mode:
Diffstat (limited to 'src/fe-common')
-rw-r--r--src/fe-common/Makefile.am3
-rw-r--r--src/fe-common/Makefile.in671
-rw-r--r--src/fe-common/core/Makefile.am73
-rw-r--r--src/fe-common/core/Makefile.in876
-rw-r--r--src/fe-common/core/chat-completion.c1330
-rw-r--r--src/fe-common/core/chat-completion.h16
-rw-r--r--src/fe-common/core/command-history.c496
-rw-r--r--src/fe-common/core/command-history.h53
-rw-r--r--src/fe-common/core/completion.c957
-rw-r--r--src/fe-common/core/completion.h18
-rw-r--r--src/fe-common/core/fe-capsicum.c63
-rw-r--r--src/fe-common/core/fe-capsicum.h7
-rw-r--r--src/fe-common/core/fe-channels.c674
-rw-r--r--src/fe-common/core/fe-channels.h16
-rw-r--r--src/fe-common/core/fe-common-core.c584
-rw-r--r--src/fe-common/core/fe-common-core.h13
-rw-r--r--src/fe-common/core/fe-core-commands.c376
-rw-r--r--src/fe-common/core/fe-core-commands.h9
-rw-r--r--src/fe-common/core/fe-exec.c682
-rw-r--r--src/fe-common/core/fe-exec.h52
-rw-r--r--src/fe-common/core/fe-expandos.c58
-rw-r--r--src/fe-common/core/fe-help.c273
-rw-r--r--src/fe-common/core/fe-ignore-messages.c155
-rw-r--r--src/fe-common/core/fe-ignore.c319
-rw-r--r--src/fe-common/core/fe-log.c817
-rw-r--r--src/fe-common/core/fe-messages.c858
-rw-r--r--src/fe-common/core/fe-messages.h12
-rw-r--r--src/fe-common/core/fe-modules.c294
-rw-r--r--src/fe-common/core/fe-queries.c399
-rw-r--r--src/fe-common/core/fe-queries.h13
-rw-r--r--src/fe-common/core/fe-recode.c208
-rw-r--r--src/fe-common/core/fe-recode.h7
-rw-r--r--src/fe-common/core/fe-server.c548
-rw-r--r--src/fe-common/core/fe-settings.c452
-rw-r--r--src/fe-common/core/fe-settings.h6
-rw-r--r--src/fe-common/core/fe-tls.c94
-rw-r--r--src/fe-common/core/fe-tls.h25
-rw-r--r--src/fe-common/core/fe-windows.c845
-rw-r--r--src/fe-common/core/fe-windows.h112
-rw-r--r--src/fe-common/core/formats.c1750
-rw-r--r--src/fe-common/core/formats.h184
-rw-r--r--src/fe-common/core/hilight-text.c821
-rw-r--r--src/fe-common/core/hilight-text.h48
-rw-r--r--src/fe-common/core/keyboard.c1007
-rw-r--r--src/fe-common/core/keyboard.h58
-rw-r--r--src/fe-common/core/meson.build99
-rw-r--r--src/fe-common/core/module-formats.c321
-rw-r--r--src/fe-common/core/module-formats.h283
-rw-r--r--src/fe-common/core/module.h35
-rw-r--r--src/fe-common/core/printtext.c566
-rw-r--r--src/fe-common/core/printtext.h45
-rw-r--r--src/fe-common/core/themes.c1488
-rw-r--r--src/fe-common/core/themes.h75
-rw-r--r--src/fe-common/core/window-activity.c166
-rw-r--r--src/fe-common/core/window-activity.h13
-rw-r--r--src/fe-common/core/window-commands.c962
-rw-r--r--src/fe-common/core/window-items.c355
-rw-r--r--src/fe-common/core/window-items.h34
-rw-r--r--src/fe-common/core/windows-layout.c278
-rw-r--r--src/fe-common/core/windows-layout.h11
-rw-r--r--src/fe-common/irc/Makefile.am42
-rw-r--r--src/fe-common/irc/Makefile.in929
-rw-r--r--src/fe-common/irc/dcc/Makefile.am24
-rw-r--r--src/fe-common/irc/dcc/Makefile.in750
-rw-r--r--src/fe-common/irc/dcc/fe-dcc-chat-messages.c164
-rw-r--r--src/fe-common/irc/dcc/fe-dcc-chat.c385
-rw-r--r--src/fe-common/irc/dcc/fe-dcc-get.c150
-rw-r--r--src/fe-common/irc/dcc/fe-dcc-send.c186
-rw-r--r--src/fe-common/irc/dcc/fe-dcc-server.c83
-rw-r--r--src/fe-common/irc/dcc/fe-dcc.c195
-rw-r--r--src/fe-common/irc/dcc/fe-dcc.h7
-rw-r--r--src/fe-common/irc/dcc/meson.build27
-rw-r--r--src/fe-common/irc/dcc/module-formats.c79
-rw-r--r--src/fe-common/irc/dcc/module-formats.h57
-rw-r--r--src/fe-common/irc/dcc/module.h4
-rw-r--r--src/fe-common/irc/fe-cap.c84
-rw-r--r--src/fe-common/irc/fe-common-irc.c134
-rw-r--r--src/fe-common/irc/fe-ctcp.c170
-rw-r--r--src/fe-common/irc/fe-events-numeric.c903
-rw-r--r--src/fe-common/irc/fe-events.c550
-rw-r--r--src/fe-common/irc/fe-irc-channels.c111
-rw-r--r--src/fe-common/irc/fe-irc-channels.h10
-rw-r--r--src/fe-common/irc/fe-irc-commands.c448
-rw-r--r--src/fe-common/irc/fe-irc-messages.c373
-rw-r--r--src/fe-common/irc/fe-irc-queries.c101
-rw-r--r--src/fe-common/irc/fe-irc-server.c201
-rw-r--r--src/fe-common/irc/fe-irc-server.h9
-rw-r--r--src/fe-common/irc/fe-ircnet.c249
-rw-r--r--src/fe-common/irc/fe-modes.c236
-rw-r--r--src/fe-common/irc/fe-netjoin.c515
-rw-r--r--src/fe-common/irc/fe-netsplit.c389
-rw-r--r--src/fe-common/irc/fe-sasl.c53
-rw-r--r--src/fe-common/irc/fe-whois.c456
-rw-r--r--src/fe-common/irc/irc-completion.c41
-rw-r--r--src/fe-common/irc/irc-modules.c4
-rw-r--r--src/fe-common/irc/meson.build44
-rw-r--r--src/fe-common/irc/module-formats.c184
-rw-r--r--src/fe-common/irc/module-formats.h154
-rw-r--r--src/fe-common/irc/module.h4
-rw-r--r--src/fe-common/irc/notifylist/Makefile.am18
-rw-r--r--src/fe-common/irc/notifylist/Makefile.in725
-rw-r--r--src/fe-common/irc/notifylist/fe-notifylist.c250
-rw-r--r--src/fe-common/irc/notifylist/meson.build21
-rw-r--r--src/fe-common/irc/notifylist/module-formats.c41
-rw-r--r--src/fe-common/irc/notifylist/module-formats.h18
-rw-r--r--src/fe-common/irc/notifylist/module.h4
-rw-r--r--src/fe-common/meson.build6
107 files changed, 30651 insertions, 0 deletions
diff --git a/src/fe-common/Makefile.am b/src/fe-common/Makefile.am
new file mode 100644
index 0000000..f4b78b3
--- /dev/null
+++ b/src/fe-common/Makefile.am
@@ -0,0 +1,3 @@
+SUBDIRS = core irc
+
+EXTRA_DIST = meson.build
diff --git a/src/fe-common/Makefile.in b/src/fe-common/Makefile.in
new file mode 100644
index 0000000..d1025a5
--- /dev/null
+++ b/src/fe-common/Makefile.in
@@ -0,0 +1,671 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/fe-common
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/glib-2.0.m4 \
+ $(top_srcdir)/m4/glibtests.m4 $(top_srcdir)/m4/libgcrypt.m4 \
+ $(top_srcdir)/m4/libotr.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
+ $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
+ $(top_srcdir)/m4/pkg.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/irssi-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHAT_MODULES = @CHAT_MODULES@
+COMMON_LIBS = @COMMON_LIBS@
+COMMON_NOUI_LIBS = @COMMON_NOUI_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+FUZZER_LIBS = @FUZZER_LIBS@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_GENMARSHAL = @GLIB_GENMARSHAL@
+GLIB_LIBS = @GLIB_LIBS@
+GLIB_MKENUMS = @GLIB_MKENUMS@
+GOBJECT_QUERY = @GOBJECT_QUERY@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
+LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
+LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBOTR_CFLAGS = @LIBOTR_CFLAGS@
+LIBOTR_LIBS = @LIBOTR_LIBS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENSSL_CFLAGS = @OPENSSL_CFLAGS@
+OPENSSL_LIBS = @OPENSSL_LIBS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+OTR_CFLAGS = @OTR_CFLAGS@
+OTR_LDFLAGS = @OTR_LDFLAGS@
+OTR_LINK_FLAGS = @OTR_LINK_FLAGS@
+OTR_LINK_LIBS = @OTR_LINK_LIBS@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PERL_CFLAGS = @PERL_CFLAGS@
+PERL_EXTRA_OPTS = @PERL_EXTRA_OPTS@
+PERL_FE_LINK_LIBS = @PERL_FE_LINK_LIBS@
+PERL_LDFLAGS = @PERL_LDFLAGS@
+PERL_LINK_FLAGS = @PERL_LINK_FLAGS@
+PERL_LINK_LIBS = @PERL_LINK_LIBS@
+PERL_MM_OPT = @PERL_MM_OPT@
+PERL_MM_PARAMS = @PERL_MM_PARAMS@
+PERL_STATIC_LIBS = @PERL_STATIC_LIBS@
+PERL_USE_LIB = @PERL_USE_LIB@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PROG_LIBS = @PROG_LIBS@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+TEXTUI_LIBS = @TEXTUI_LIBS@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+installed_test_metadir = @installed_test_metadir@
+installed_testdir = @installed_testdir@
+irc_MODULES = @irc_MODULES@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+otr_module_lib = @otr_module_lib@
+otr_static_lib = @otr_static_lib@
+pdfdir = @pdfdir@
+perl_module_fe_lib = @perl_module_fe_lib@
+perl_module_lib = @perl_module_lib@
+perl_static_fe_lib = @perl_static_fe_lib@
+perl_static_lib = @perl_static_lib@
+perlpath = @perlpath@
+pkgconfigdir = @pkgconfigdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sedpath = @sedpath@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = core irc
+EXTRA_DIST = meson.build
+all: all-recursive
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/fe-common/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/fe-common/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-am clean clean-generic clean-libtool cscopelist-am ctags \
+ ctags-am distclean distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \
+ ps ps-am tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/fe-common/core/Makefile.am b/src/fe-common/core/Makefile.am
new file mode 100644
index 0000000..01e934e
--- /dev/null
+++ b/src/fe-common/core/Makefile.am
@@ -0,0 +1,73 @@
+noinst_LIBRARIES = libfe_common_core.a
+
+AM_CPPFLAGS = \
+ -I$(top_builddir) \
+ $(GLIB_CFLAGS) \
+ -DHELPDIR=\""$(datadir)/irssi/help"\" \
+ -DTHEMESDIR=\""$(datadir)/irssi/themes"\"
+
+libfe_common_core_a_SOURCES = \
+ chat-completion.c \
+ command-history.c \
+ completion.c \
+ fe-channels.c \
+ fe-common-core.c \
+ fe-core-commands.c \
+ fe-exec.c \
+ fe-expandos.c \
+ fe-help.c \
+ fe-ignore.c \
+ fe-ignore-messages.c \
+ fe-log.c \
+ fe-messages.c \
+ fe-modules.c \
+ fe-queries.c \
+ fe-server.c \
+ fe-settings.c \
+ fe-tls.c \
+ formats.c \
+ hilight-text.c \
+ keyboard.c \
+ module-formats.c \
+ printtext.c \
+ fe-recode.c \
+ themes.c \
+ window-activity.c \
+ window-commands.c \
+ window-items.c \
+ windows-layout.c \
+ fe-windows.c
+
+if HAVE_CAPSICUM
+libfe_common_core_a_SOURCES += \
+ fe-capsicum.c
+endif
+
+pkginc_fe_common_coredir=$(pkgincludedir)/src/fe-common/core
+pkginc_fe_common_core_HEADERS = \
+ command-history.h \
+ chat-completion.h \
+ completion.h \
+ fe-capsicum.h \
+ fe-channels.h \
+ fe-common-core.h \
+ fe-core-commands.h \
+ fe-exec.h \
+ fe-messages.h \
+ fe-queries.h \
+ fe-settings.h \
+ fe-tls.h \
+ formats.h \
+ hilight-text.h \
+ keyboard.h \
+ module-formats.h \
+ module.h \
+ printtext.h \
+ fe-recode.h \
+ themes.h \
+ window-activity.h \
+ window-items.h \
+ windows-layout.h \
+ fe-windows.h
+
+EXTRA_DIST = meson.build
diff --git a/src/fe-common/core/Makefile.in b/src/fe-common/core/Makefile.in
new file mode 100644
index 0000000..c247da9
--- /dev/null
+++ b/src/fe-common/core/Makefile.in
@@ -0,0 +1,876 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@HAVE_CAPSICUM_TRUE@am__append_1 = \
+@HAVE_CAPSICUM_TRUE@ fe-capsicum.c
+
+subdir = src/fe-common/core
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/glib-2.0.m4 \
+ $(top_srcdir)/m4/glibtests.m4 $(top_srcdir)/m4/libgcrypt.m4 \
+ $(top_srcdir)/m4/libotr.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
+ $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
+ $(top_srcdir)/m4/pkg.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_fe_common_core_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/irssi-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+ARFLAGS = cru
+AM_V_AR = $(am__v_AR_@AM_V@)
+am__v_AR_ = $(am__v_AR_@AM_DEFAULT_V@)
+am__v_AR_0 = @echo " AR " $@;
+am__v_AR_1 =
+libfe_common_core_a_AR = $(AR) $(ARFLAGS)
+libfe_common_core_a_LIBADD =
+am__libfe_common_core_a_SOURCES_DIST = chat-completion.c \
+ command-history.c completion.c fe-channels.c fe-common-core.c \
+ fe-core-commands.c fe-exec.c fe-expandos.c fe-help.c \
+ fe-ignore.c fe-ignore-messages.c fe-log.c fe-messages.c \
+ fe-modules.c fe-queries.c fe-server.c fe-settings.c fe-tls.c \
+ formats.c hilight-text.c keyboard.c module-formats.c \
+ printtext.c fe-recode.c themes.c window-activity.c \
+ window-commands.c window-items.c windows-layout.c fe-windows.c \
+ fe-capsicum.c
+@HAVE_CAPSICUM_TRUE@am__objects_1 = fe-capsicum.$(OBJEXT)
+am_libfe_common_core_a_OBJECTS = chat-completion.$(OBJEXT) \
+ command-history.$(OBJEXT) completion.$(OBJEXT) \
+ fe-channels.$(OBJEXT) fe-common-core.$(OBJEXT) \
+ fe-core-commands.$(OBJEXT) fe-exec.$(OBJEXT) \
+ fe-expandos.$(OBJEXT) fe-help.$(OBJEXT) fe-ignore.$(OBJEXT) \
+ fe-ignore-messages.$(OBJEXT) fe-log.$(OBJEXT) \
+ fe-messages.$(OBJEXT) fe-modules.$(OBJEXT) \
+ fe-queries.$(OBJEXT) fe-server.$(OBJEXT) fe-settings.$(OBJEXT) \
+ fe-tls.$(OBJEXT) formats.$(OBJEXT) hilight-text.$(OBJEXT) \
+ keyboard.$(OBJEXT) module-formats.$(OBJEXT) \
+ printtext.$(OBJEXT) fe-recode.$(OBJEXT) themes.$(OBJEXT) \
+ window-activity.$(OBJEXT) window-commands.$(OBJEXT) \
+ window-items.$(OBJEXT) windows-layout.$(OBJEXT) \
+ fe-windows.$(OBJEXT) $(am__objects_1)
+libfe_common_core_a_OBJECTS = $(am_libfe_common_core_a_OBJECTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES =
+depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/chat-completion.Po \
+ ./$(DEPDIR)/command-history.Po ./$(DEPDIR)/completion.Po \
+ ./$(DEPDIR)/fe-capsicum.Po ./$(DEPDIR)/fe-channels.Po \
+ ./$(DEPDIR)/fe-common-core.Po ./$(DEPDIR)/fe-core-commands.Po \
+ ./$(DEPDIR)/fe-exec.Po ./$(DEPDIR)/fe-expandos.Po \
+ ./$(DEPDIR)/fe-help.Po ./$(DEPDIR)/fe-ignore-messages.Po \
+ ./$(DEPDIR)/fe-ignore.Po ./$(DEPDIR)/fe-log.Po \
+ ./$(DEPDIR)/fe-messages.Po ./$(DEPDIR)/fe-modules.Po \
+ ./$(DEPDIR)/fe-queries.Po ./$(DEPDIR)/fe-recode.Po \
+ ./$(DEPDIR)/fe-server.Po ./$(DEPDIR)/fe-settings.Po \
+ ./$(DEPDIR)/fe-tls.Po ./$(DEPDIR)/fe-windows.Po \
+ ./$(DEPDIR)/formats.Po ./$(DEPDIR)/hilight-text.Po \
+ ./$(DEPDIR)/keyboard.Po ./$(DEPDIR)/module-formats.Po \
+ ./$(DEPDIR)/printtext.Po ./$(DEPDIR)/themes.Po \
+ ./$(DEPDIR)/window-activity.Po ./$(DEPDIR)/window-commands.Po \
+ ./$(DEPDIR)/window-items.Po ./$(DEPDIR)/windows-layout.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libfe_common_core_a_SOURCES)
+DIST_SOURCES = $(am__libfe_common_core_a_SOURCES_DIST)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(pkginc_fe_common_coredir)"
+HEADERS = $(pkginc_fe_common_core_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in \
+ $(top_srcdir)/build-aux/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHAT_MODULES = @CHAT_MODULES@
+COMMON_LIBS = @COMMON_LIBS@
+COMMON_NOUI_LIBS = @COMMON_NOUI_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+FUZZER_LIBS = @FUZZER_LIBS@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_GENMARSHAL = @GLIB_GENMARSHAL@
+GLIB_LIBS = @GLIB_LIBS@
+GLIB_MKENUMS = @GLIB_MKENUMS@
+GOBJECT_QUERY = @GOBJECT_QUERY@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
+LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
+LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBOTR_CFLAGS = @LIBOTR_CFLAGS@
+LIBOTR_LIBS = @LIBOTR_LIBS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENSSL_CFLAGS = @OPENSSL_CFLAGS@
+OPENSSL_LIBS = @OPENSSL_LIBS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+OTR_CFLAGS = @OTR_CFLAGS@
+OTR_LDFLAGS = @OTR_LDFLAGS@
+OTR_LINK_FLAGS = @OTR_LINK_FLAGS@
+OTR_LINK_LIBS = @OTR_LINK_LIBS@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PERL_CFLAGS = @PERL_CFLAGS@
+PERL_EXTRA_OPTS = @PERL_EXTRA_OPTS@
+PERL_FE_LINK_LIBS = @PERL_FE_LINK_LIBS@
+PERL_LDFLAGS = @PERL_LDFLAGS@
+PERL_LINK_FLAGS = @PERL_LINK_FLAGS@
+PERL_LINK_LIBS = @PERL_LINK_LIBS@
+PERL_MM_OPT = @PERL_MM_OPT@
+PERL_MM_PARAMS = @PERL_MM_PARAMS@
+PERL_STATIC_LIBS = @PERL_STATIC_LIBS@
+PERL_USE_LIB = @PERL_USE_LIB@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PROG_LIBS = @PROG_LIBS@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+TEXTUI_LIBS = @TEXTUI_LIBS@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+installed_test_metadir = @installed_test_metadir@
+installed_testdir = @installed_testdir@
+irc_MODULES = @irc_MODULES@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+otr_module_lib = @otr_module_lib@
+otr_static_lib = @otr_static_lib@
+pdfdir = @pdfdir@
+perl_module_fe_lib = @perl_module_fe_lib@
+perl_module_lib = @perl_module_lib@
+perl_static_fe_lib = @perl_static_fe_lib@
+perl_static_lib = @perl_static_lib@
+perlpath = @perlpath@
+pkgconfigdir = @pkgconfigdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sedpath = @sedpath@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LIBRARIES = libfe_common_core.a
+AM_CPPFLAGS = \
+ -I$(top_builddir) \
+ $(GLIB_CFLAGS) \
+ -DHELPDIR=\""$(datadir)/irssi/help"\" \
+ -DTHEMESDIR=\""$(datadir)/irssi/themes"\"
+
+libfe_common_core_a_SOURCES = chat-completion.c command-history.c \
+ completion.c fe-channels.c fe-common-core.c fe-core-commands.c \
+ fe-exec.c fe-expandos.c fe-help.c fe-ignore.c \
+ fe-ignore-messages.c fe-log.c fe-messages.c fe-modules.c \
+ fe-queries.c fe-server.c fe-settings.c fe-tls.c formats.c \
+ hilight-text.c keyboard.c module-formats.c printtext.c \
+ fe-recode.c themes.c window-activity.c window-commands.c \
+ window-items.c windows-layout.c fe-windows.c $(am__append_1)
+pkginc_fe_common_coredir = $(pkgincludedir)/src/fe-common/core
+pkginc_fe_common_core_HEADERS = \
+ command-history.h \
+ chat-completion.h \
+ completion.h \
+ fe-capsicum.h \
+ fe-channels.h \
+ fe-common-core.h \
+ fe-core-commands.h \
+ fe-exec.h \
+ fe-messages.h \
+ fe-queries.h \
+ fe-settings.h \
+ fe-tls.h \
+ formats.h \
+ hilight-text.h \
+ keyboard.h \
+ module-formats.h \
+ module.h \
+ printtext.h \
+ fe-recode.h \
+ themes.h \
+ window-activity.h \
+ window-items.h \
+ windows-layout.h \
+ fe-windows.h
+
+EXTRA_DIST = meson.build
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/fe-common/core/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/fe-common/core/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+ -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+
+libfe_common_core.a: $(libfe_common_core_a_OBJECTS) $(libfe_common_core_a_DEPENDENCIES) $(EXTRA_libfe_common_core_a_DEPENDENCIES)
+ $(AM_V_at)-rm -f libfe_common_core.a
+ $(AM_V_AR)$(libfe_common_core_a_AR) libfe_common_core.a $(libfe_common_core_a_OBJECTS) $(libfe_common_core_a_LIBADD)
+ $(AM_V_at)$(RANLIB) libfe_common_core.a
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chat-completion.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/command-history.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/completion.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-capsicum.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-channels.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-common-core.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-core-commands.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-exec.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-expandos.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-help.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-ignore-messages.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-ignore.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-log.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-messages.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-modules.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-queries.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-recode.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-server.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-settings.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-tls.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-windows.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/formats.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hilight-text.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keyboard.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/module-formats.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/printtext.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/themes.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/window-activity.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/window-commands.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/window-items.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/windows-layout.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_fe_common_coreHEADERS: $(pkginc_fe_common_core_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_fe_common_core_HEADERS)'; test -n "$(pkginc_fe_common_coredir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_fe_common_coredir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_fe_common_coredir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_fe_common_coredir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_fe_common_coredir)" || exit $$?; \
+ done
+
+uninstall-pkginc_fe_common_coreHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_fe_common_core_HEADERS)'; test -n "$(pkginc_fe_common_coredir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_fe_common_coredir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(pkginc_fe_common_coredir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/chat-completion.Po
+ -rm -f ./$(DEPDIR)/command-history.Po
+ -rm -f ./$(DEPDIR)/completion.Po
+ -rm -f ./$(DEPDIR)/fe-capsicum.Po
+ -rm -f ./$(DEPDIR)/fe-channels.Po
+ -rm -f ./$(DEPDIR)/fe-common-core.Po
+ -rm -f ./$(DEPDIR)/fe-core-commands.Po
+ -rm -f ./$(DEPDIR)/fe-exec.Po
+ -rm -f ./$(DEPDIR)/fe-expandos.Po
+ -rm -f ./$(DEPDIR)/fe-help.Po
+ -rm -f ./$(DEPDIR)/fe-ignore-messages.Po
+ -rm -f ./$(DEPDIR)/fe-ignore.Po
+ -rm -f ./$(DEPDIR)/fe-log.Po
+ -rm -f ./$(DEPDIR)/fe-messages.Po
+ -rm -f ./$(DEPDIR)/fe-modules.Po
+ -rm -f ./$(DEPDIR)/fe-queries.Po
+ -rm -f ./$(DEPDIR)/fe-recode.Po
+ -rm -f ./$(DEPDIR)/fe-server.Po
+ -rm -f ./$(DEPDIR)/fe-settings.Po
+ -rm -f ./$(DEPDIR)/fe-tls.Po
+ -rm -f ./$(DEPDIR)/fe-windows.Po
+ -rm -f ./$(DEPDIR)/formats.Po
+ -rm -f ./$(DEPDIR)/hilight-text.Po
+ -rm -f ./$(DEPDIR)/keyboard.Po
+ -rm -f ./$(DEPDIR)/module-formats.Po
+ -rm -f ./$(DEPDIR)/printtext.Po
+ -rm -f ./$(DEPDIR)/themes.Po
+ -rm -f ./$(DEPDIR)/window-activity.Po
+ -rm -f ./$(DEPDIR)/window-commands.Po
+ -rm -f ./$(DEPDIR)/window-items.Po
+ -rm -f ./$(DEPDIR)/windows-layout.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-pkginc_fe_common_coreHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/chat-completion.Po
+ -rm -f ./$(DEPDIR)/command-history.Po
+ -rm -f ./$(DEPDIR)/completion.Po
+ -rm -f ./$(DEPDIR)/fe-capsicum.Po
+ -rm -f ./$(DEPDIR)/fe-channels.Po
+ -rm -f ./$(DEPDIR)/fe-common-core.Po
+ -rm -f ./$(DEPDIR)/fe-core-commands.Po
+ -rm -f ./$(DEPDIR)/fe-exec.Po
+ -rm -f ./$(DEPDIR)/fe-expandos.Po
+ -rm -f ./$(DEPDIR)/fe-help.Po
+ -rm -f ./$(DEPDIR)/fe-ignore-messages.Po
+ -rm -f ./$(DEPDIR)/fe-ignore.Po
+ -rm -f ./$(DEPDIR)/fe-log.Po
+ -rm -f ./$(DEPDIR)/fe-messages.Po
+ -rm -f ./$(DEPDIR)/fe-modules.Po
+ -rm -f ./$(DEPDIR)/fe-queries.Po
+ -rm -f ./$(DEPDIR)/fe-recode.Po
+ -rm -f ./$(DEPDIR)/fe-server.Po
+ -rm -f ./$(DEPDIR)/fe-settings.Po
+ -rm -f ./$(DEPDIR)/fe-tls.Po
+ -rm -f ./$(DEPDIR)/fe-windows.Po
+ -rm -f ./$(DEPDIR)/formats.Po
+ -rm -f ./$(DEPDIR)/hilight-text.Po
+ -rm -f ./$(DEPDIR)/keyboard.Po
+ -rm -f ./$(DEPDIR)/module-formats.Po
+ -rm -f ./$(DEPDIR)/printtext.Po
+ -rm -f ./$(DEPDIR)/themes.Po
+ -rm -f ./$(DEPDIR)/window-activity.Po
+ -rm -f ./$(DEPDIR)/window-commands.Po
+ -rm -f ./$(DEPDIR)/window-items.Po
+ -rm -f ./$(DEPDIR)/windows-layout.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-pkginc_fe_common_coreHEADERS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-pkginc_fe_common_coreHEADERS install-ps \
+ install-ps-am install-strip installcheck installcheck-am \
+ installdirs maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am uninstall-pkginc_fe_common_coreHEADERS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/fe-common/core/chat-completion.c b/src/fe-common/core/chat-completion.c
new file mode 100644
index 0000000..be16db7
--- /dev/null
+++ b/src/fe-common/core/chat-completion.c
@@ -0,0 +1,1330 @@
+/*
+ chat-completion.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/lib-config/iconfig.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/core/chatnets.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/servers-setup.h>
+#include <irssi/src/core/channels.h>
+#include <irssi/src/core/channels-setup.h>
+#include <irssi/src/core/queries.h>
+#include <irssi/src/core/nicklist.h>
+
+#include <irssi/src/fe-common/core/completion.h>
+#include <irssi/src/fe-common/core/chat-completion.h>
+#include <irssi/src/fe-common/core/window-items.h>
+
+enum {
+ COMPLETE_MCASE_NEVER = 0,
+ COMPLETE_MCASE_ALWAYS,
+ COMPLETE_MCASE_AUTO,
+};
+
+static int keep_privates_count, keep_publics_count;
+static int completion_lowercase;
+static char *completion_char, *cmdchars;
+static GSList *global_lastmsgs;
+static int completion_auto, completion_strict, completion_empty_line;
+static int completion_match_case;
+
+#define SERVER_LAST_MSG_ADD(server, nick) \
+ last_msg_add(&((MODULE_SERVER_REC *) MODULE_DATA(server))->lastmsgs, \
+ nick, TRUE, keep_privates_count)
+
+#define CHANNEL_LAST_MSG_ADD(channel, nick, own) \
+ last_msg_add(&((MODULE_CHANNEL_REC *) MODULE_DATA(channel))->lastmsgs, \
+ nick, own, keep_publics_count)
+
+static gboolean contains_uppercase(const char *s1)
+{
+ const char *ch;
+
+ for (ch = s1; *ch != '\0'; ch++) {
+ if (g_ascii_isupper(*ch))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static LAST_MSG_REC *last_msg_find(GSList *list, const char *nick)
+{
+ while (list != NULL) {
+ LAST_MSG_REC *rec = list->data;
+
+ if (g_ascii_strcasecmp(rec->nick, nick) == 0)
+ return rec;
+ list = list->next;
+ }
+
+ return NULL;
+}
+
+static void last_msg_dec_owns(GSList *list)
+{
+ LAST_MSG_REC *rec;
+
+ while (list != NULL) {
+ rec = list->data;
+ if (rec->own) rec->own--;
+
+ list = list->next;
+ }
+}
+
+static void last_msg_destroy(GSList **list, LAST_MSG_REC *rec)
+{
+ *list = g_slist_remove(*list, rec);
+
+ g_free(rec->nick);
+ g_free(rec);
+}
+
+static void last_msg_add(GSList **list, const char *nick, int own, int max)
+{
+ LAST_MSG_REC *rec;
+
+ if (max <= 0)
+ return;
+
+ rec = last_msg_find(*list, nick);
+ if (rec != NULL) {
+ /* msg already exists, update it */
+ *list = g_slist_remove(*list, rec);
+ if (own)
+ rec->own = max;
+ else if (rec->own)
+ rec->own--;
+ } else {
+ rec = g_new(LAST_MSG_REC, 1);
+ rec->nick = g_strdup(nick);
+
+ while ((int)g_slist_length(*list) >= max) {
+ last_msg_destroy(list, g_slist_last(*list)->data);
+ }
+
+ rec->own = own ? max : 0;
+ }
+ rec->time = time(NULL);
+
+ last_msg_dec_owns(*list);
+
+ *list = g_slist_prepend(*list, rec);
+}
+
+void completion_last_message_add(const char *nick)
+{
+ g_return_if_fail(nick != NULL);
+
+ last_msg_add(&global_lastmsgs, nick, TRUE, keep_privates_count);
+}
+
+void completion_last_message_remove(const char *nick)
+{
+ LAST_MSG_REC *rec;
+
+ g_return_if_fail(nick != NULL);
+
+ rec = last_msg_find(global_lastmsgs, nick);
+ if (rec != NULL) last_msg_destroy(&global_lastmsgs, rec);
+}
+
+void completion_last_message_rename(const char *oldnick, const char *newnick)
+{
+ LAST_MSG_REC *rec;
+
+ g_return_if_fail(oldnick != NULL);
+ g_return_if_fail(newnick != NULL);
+
+ rec = last_msg_find(global_lastmsgs, oldnick);
+ if (rec != NULL) {
+ g_free(rec->nick);
+ rec->nick = g_strdup(newnick);
+ }
+}
+
+static void sig_message_public(SERVER_REC *server, const char *msg,
+ const char *nick, const char *address,
+ const char *target)
+{
+ CHANNEL_REC *channel;
+ int own;
+ g_return_if_fail(nick != NULL);
+
+ channel = channel_find(server, target);
+ if (channel != NULL) {
+ own = nick_match_msg(channel, msg, server->nick);
+ CHANNEL_LAST_MSG_ADD(channel, nick, own);
+ }
+}
+
+static void sig_message_join(SERVER_REC *server, const char *channel,
+ const char *nick, const char *address)
+{
+ CHANNEL_REC *chanrec;
+ g_return_if_fail(nick != NULL);
+
+ chanrec = channel_find(server, channel);
+ if (chanrec != NULL)
+ CHANNEL_LAST_MSG_ADD(chanrec, nick, FALSE);
+}
+
+static void sig_message_private(SERVER_REC *server, const char *msg,
+ const char *nick, const char *address)
+{
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(nick != NULL);
+
+ SERVER_LAST_MSG_ADD(server, nick);
+}
+
+static void sig_message_own_public(SERVER_REC *server, const char *msg,
+ const char *target, const char *origtarget)
+{
+ CHANNEL_REC *channel;
+ NICK_REC *nick;
+ char *p, *msgnick;
+
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(msg != NULL);
+ if (target == NULL) return;
+
+ channel = channel_find(server, target);
+ if (channel == NULL)
+ return;
+
+ /* channel msg - if first word in line is nick,
+ add it to lastmsgs */
+ p = strchr(msg, ' ');
+ if (p != NULL && p != msg) {
+ msgnick = g_strndup(msg, (int) (p-msg));
+ nick = nicklist_find(channel, msgnick);
+ if (nick == NULL && msgnick[1] != '\0') {
+ /* probably ':' or ',' or some other
+ char after nick, try without it */
+ msgnick[strlen(msgnick)-1] = '\0';
+ nick = nicklist_find(channel, msgnick);
+ }
+ g_free(msgnick);
+ if (nick != NULL && nick != channel->ownnick)
+ CHANNEL_LAST_MSG_ADD(channel, nick->nick, TRUE);
+ }
+}
+
+static void sig_message_own_private(SERVER_REC *server, const char *msg,
+ const char *target, const char *origtarget)
+{
+ g_return_if_fail(server != NULL);
+
+ if (target != NULL && query_find(server, target) == NULL)
+ SERVER_LAST_MSG_ADD(server, target);
+}
+
+static void sig_nick_removed(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ MODULE_CHANNEL_REC *mchannel;
+ LAST_MSG_REC *rec;
+
+ mchannel = MODULE_DATA(channel);
+ rec = last_msg_find(mchannel->lastmsgs, nick->nick);
+ if (rec != NULL) last_msg_destroy(&mchannel->lastmsgs, rec);
+}
+
+static void sig_nick_changed(CHANNEL_REC *channel, NICK_REC *nick,
+ const char *oldnick)
+{
+ MODULE_CHANNEL_REC *mchannel;
+ LAST_MSG_REC *rec;
+
+ mchannel = MODULE_DATA(channel);
+ rec = last_msg_find(mchannel->lastmsgs, oldnick);
+ if (rec != NULL) {
+ g_free(rec->nick);
+ rec->nick = g_strdup(nick->nick);
+ }
+}
+
+static int last_msg_cmp(LAST_MSG_REC *m1, LAST_MSG_REC *m2)
+{
+ return m1->time < m2->time ? 1 : -1;
+}
+
+/* Complete /MSG from specified server, or from
+ global_lastmsgs if server is NULL */
+static void completion_msg_server(GSList **list, SERVER_REC *server,
+ const char *nick, const char *prefix)
+{
+ LAST_MSG_REC *msg;
+ GSList *tmp;
+ int len;
+
+ g_return_if_fail(nick != NULL);
+
+ len = strlen(nick);
+ tmp = server == NULL ? global_lastmsgs :
+ ((MODULE_SERVER_REC *) MODULE_DATA(server))->lastmsgs;
+ for (; tmp != NULL; tmp = tmp->next) {
+ LAST_MSG_REC *rec = tmp->data;
+
+ if (len != 0 && g_ascii_strncasecmp(rec->nick, nick, len) != 0)
+ continue;
+
+ msg = g_new(LAST_MSG_REC, 1);
+ msg->time = rec->time;
+ msg->nick = prefix == NULL || *prefix == '\0' ?
+ g_strdup(rec->nick) :
+ g_strconcat(prefix, " ", rec->nick, NULL);
+ *list = g_slist_insert_sorted(*list, msg,
+ (GCompareFunc) last_msg_cmp);
+ }
+}
+
+/* convert list of LAST_MSG_REC's to list of char* nicks. */
+static GList *convert_msglist(GSList *msglist)
+{
+ GList *list;
+
+ list = NULL;
+ while (msglist != NULL) {
+ LAST_MSG_REC *rec = msglist->data;
+
+ list = g_list_append(list, rec->nick);
+ msglist = g_slist_remove(msglist, rec);
+ g_free(rec);
+ }
+
+ return list;
+}
+
+/* Complete /MSG - if `find_server' is NULL, complete nicks from all servers */
+GList *completion_msg(SERVER_REC *win_server,
+ SERVER_REC *find_server,
+ const char *nick, const char *prefix)
+{
+ GSList *tmp, *list;
+ char *newprefix;
+
+ g_return_val_if_fail(nick != NULL, NULL);
+ if (servers == NULL) return NULL;
+
+ list = NULL;
+ if (find_server != NULL) {
+ completion_msg_server(&list, find_server, nick, prefix);
+ return convert_msglist(list);
+ }
+
+ completion_msg_server(&list, NULL, nick, prefix);
+ for (tmp = servers; tmp != NULL; tmp = tmp->next) {
+ SERVER_REC *rec = tmp->data;
+
+ if (servers->next == NULL && rec == win_server)
+ newprefix = g_strdup(prefix);
+ else {
+ newprefix = prefix == NULL ?
+ g_strdup_printf("-%s", rec->tag) :
+ g_strdup_printf("%s -%s", prefix, rec->tag);
+ }
+
+ completion_msg_server(&list, rec, nick, newprefix);
+ g_free_not_null(newprefix);
+ }
+
+ return convert_msglist(list);
+}
+
+static void complete_from_nicklist(GList **outlist, CHANNEL_REC *channel,
+ const char *nick, const char *suffix,
+ const int match_case)
+{
+ MODULE_CHANNEL_REC *mchannel;
+ GSList *tmp;
+ GList *ownlist;
+ char *str;
+ int len;
+
+ /* go through the last x nicks who have said something in the channel.
+ nicks of all the "own messages" are placed before others */
+ ownlist = NULL;
+ len = strlen(nick);
+ mchannel = MODULE_DATA(channel);
+ for (tmp = mchannel->lastmsgs; tmp != NULL; tmp = tmp->next) {
+ LAST_MSG_REC *rec = tmp->data;
+
+ if ((match_case ? strncmp(rec->nick, nick, len) :
+ g_ascii_strncasecmp(rec->nick, nick, len)) == 0 &&
+ (match_case ? i_list_find_string(*outlist, rec->nick) :
+ i_list_find_icase_string(*outlist, rec->nick)) == NULL) {
+ str = g_strconcat(rec->nick, suffix, NULL);
+ if (completion_lowercase) ascii_strdown(str);
+ if (rec->own)
+ ownlist = g_list_append(ownlist, str);
+ else
+ *outlist = g_list_append(*outlist, str);
+ }
+ }
+
+ *outlist = g_list_concat(ownlist, *outlist);
+}
+
+static GList *completion_nicks_nonstrict(CHANNEL_REC *channel,
+ const char *nick,
+ const char *suffix,
+ const int match_case)
+{
+ GSList *nicks, *tmp;
+ GList *list;
+ char *tnick, *str, *in, *out;
+ int len, str_len, tmplen;
+
+ g_return_val_if_fail(channel != NULL, NULL);
+
+ list = NULL;
+
+ /* get all nicks from current channel, strip non alnum chars,
+ compare again and add to completion list on matching */
+ len = strlen(nick);
+ nicks = nicklist_getnicks(channel);
+
+ str_len = 80; str = g_malloc(str_len+1);
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
+ NICK_REC *rec = tmp->data;
+
+ tmplen = strlen(rec->nick);
+ if (tmplen > str_len) {
+ str_len = tmplen*2;
+ str = g_realloc(str, str_len+1);
+ }
+
+ /* remove non alnum chars from nick */
+ in = rec->nick; out = str;
+ while (*in != '\0') {
+ if (i_isalnum(*in))
+ *out++ = *in;
+ in++;
+ }
+ *out = '\0';
+
+ /* add to list if 'cleaned' nick matches */
+ if ((match_case? strncmp(str, nick, len)
+ : g_ascii_strncasecmp(str, nick, len)) == 0) {
+ tnick = g_strconcat(rec->nick, suffix, NULL);
+ if (completion_lowercase)
+ ascii_strdown(tnick);
+
+ if (i_list_find_icase_string(list, tnick) == NULL)
+ list = g_list_append(list, tnick);
+ else
+ g_free(tnick);
+ }
+
+ }
+ g_free(str);
+ g_slist_free(nicks);
+
+ return list;
+}
+
+static GList *completion_channel_nicks(CHANNEL_REC *channel, const char *nick,
+ const char *suffix)
+{
+ GSList *nicks, *tmp;
+ GList *list;
+ char *str;
+ int len, match_case;
+
+ g_return_val_if_fail(channel != NULL, NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+ if (*nick == '\0') return NULL;
+
+ if (suffix != NULL && *suffix == '\0')
+ suffix = NULL;
+
+ match_case = completion_match_case == COMPLETE_MCASE_ALWAYS ||
+ (completion_match_case == COMPLETE_MCASE_AUTO && contains_uppercase(nick));
+
+ /* put first the nicks who have recently said something */
+ list = NULL;
+ complete_from_nicklist(&list, channel, nick, suffix, match_case);
+
+ /* and add the rest of the nicks too */
+ len = strlen(nick);
+ nicks = nicklist_getnicks(channel);
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
+ NICK_REC *rec = tmp->data;
+
+ if ((match_case? strncmp(rec->nick, nick, len)
+ : g_ascii_strncasecmp(rec->nick, nick, len)) == 0 &&
+ rec != channel->ownnick) {
+ str = g_strconcat(rec->nick, suffix, NULL);
+ if (completion_lowercase)
+ ascii_strdown(str);
+ if (i_list_find_icase_string(list, str) == NULL)
+ list = g_list_append(list, str);
+ else
+ g_free(str);
+ }
+ }
+ g_slist_free(nicks);
+
+ /* remove non alphanum chars from nick and search again in case
+ list is still NULL ("foo<tab>" would match "_foo_" f.e.) */
+ if (!completion_strict)
+ list = g_list_concat(list, completion_nicks_nonstrict(channel, nick, suffix, match_case));
+ return list;
+}
+
+/* append all strings in list2 to list1 that already aren't there and
+ free list2 */
+static GList *completion_joinlist(GList *list1, GList *list2)
+{
+ GList *old;
+
+ old = list2;
+ while (list2 != NULL) {
+ if (!i_list_find_icase_string(list1, list2->data))
+ list1 = g_list_append(list1, list2->data);
+ else
+ g_free(list2->data);
+
+ list2 = list2->next;
+ }
+
+ g_list_free(old);
+ return list1;
+}
+
+GList *completion_get_servertags(const char *word)
+{
+ GList *list;
+ GSList *tmp;
+ int len;
+
+ g_return_val_if_fail(word != NULL, NULL);
+
+ len = strlen(word);
+ list = NULL;
+
+ for (tmp = servers; tmp != NULL; tmp = tmp->next) {
+ SERVER_REC *rec = tmp->data;
+
+ if (g_ascii_strncasecmp(rec->tag, word, len) == 0) {
+ if (rec == active_win->active_server)
+ list = g_list_prepend(list, g_strdup(rec->tag));
+ else
+ list = g_list_append(list, g_strdup(rec->tag));
+ }
+
+ }
+
+ return list;
+}
+
+GList *completion_get_channels(SERVER_REC *server, const char *word)
+{
+ GList *list;
+ GSList *tmp;
+ int len;
+
+ g_return_val_if_fail(word != NULL, NULL);
+
+ len = strlen(word);
+ list = NULL;
+
+ /* first get the joined channels */
+ tmp = server == NULL ? NULL : server->channels;
+ for (; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_REC *rec = tmp->data;
+
+ if (g_ascii_strncasecmp(rec->visible_name, word, len) == 0)
+ list = g_list_append(list, g_strdup(rec->visible_name));
+ else if (g_ascii_strncasecmp(rec->name, word, len) == 0)
+ list = g_list_append(list, g_strdup(rec->name));
+ }
+
+ /* get channels from setup */
+ for (tmp = setupchannels; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_SETUP_REC *rec = tmp->data;
+
+ if (g_ascii_strncasecmp(rec->name, word, len) == 0 &&
+ i_list_find_icase_string(list, rec->name) == NULL)
+ list = g_list_append(list, g_strdup(rec->name));
+
+ }
+
+ return list;
+}
+
+GList *completion_get_aliases(const char *word)
+{
+ CONFIG_NODE *node;
+ GList *list;
+ GSList *tmp;
+ int len;
+
+ g_return_val_if_fail(word != NULL, NULL);
+
+ len = strlen(word);
+ list = NULL;
+
+ /* get the list of all aliases */
+ node = iconfig_node_traverse("aliases", FALSE);
+ tmp = node == NULL ? NULL : config_node_first(node->value);
+ for (; tmp != NULL; tmp = config_node_next(tmp)) {
+ node = tmp->data;
+
+ if (node->type != NODE_TYPE_KEY)
+ continue;
+
+ if (len != 0 && g_ascii_strncasecmp(node->key, word, len) != 0)
+ continue;
+
+ list = g_list_append(list, g_strdup(node->key));
+ }
+
+ return list;
+}
+
+static void complete_window_nicks(GList **list, WINDOW_REC *window,
+ const char *word, const char *nicksuffix)
+{
+ CHANNEL_REC *channel;
+ GList *tmplist;
+ GSList *tmp;
+
+ channel = CHANNEL(window->active);
+
+ /* first the active channel */
+ if (channel != NULL) {
+ tmplist = completion_channel_nicks(channel, word, nicksuffix);
+ *list = completion_joinlist(*list, tmplist);
+ }
+
+ if (nicksuffix != NULL) {
+ /* completing nick at the start of line - probably answering
+ to some other nick, don't even try to complete from
+ non-active channels */
+ return;
+ }
+
+ /* then the rest */
+ for (tmp = window->items; tmp != NULL; tmp = tmp->next) {
+ channel = CHANNEL(tmp->data);
+ if (channel != NULL && tmp->data != window->active) {
+ tmplist = completion_channel_nicks(channel, word,
+ nicksuffix);
+ *list = completion_joinlist(*list, tmplist);
+ }
+ }
+}
+
+/* Checks if a line is only nicks from autocompletion.
+ This lets us insert colons only at the beginning of a list
+ of nicks */
+static int only_nicks(const char *linestart)
+{
+ int i = 1;
+ char prev;
+
+ // at the beginning of the line
+ if (*linestart == '\0') {
+ return TRUE;
+ }
+
+ /* completion_char being a whole string introduces loads of edge cases
+ and can't be handled generally. Skip this case; we handled the
+ "beginning of line" case already */
+ if (completion_char[1] != '\0')
+ return FALSE;
+
+ /* This would make the completion char get inserted everywhere otherwise */
+ if (*completion_char == ' ')
+ return FALSE;
+
+ /* First ensure that the line is of the format "foo: bar: baz"
+ we check this by ensuring each space is preceded by a colon or
+ another space */
+ while (linestart[i] != '\0') {
+ if (linestart[i] == ' ') {
+ prev = linestart[i - 1];
+ if (prev != *completion_char && prev != ' ')
+ return FALSE;
+ }
+ i += 1;
+ }
+
+ /* There's an edge case here, if we're completing something
+ like `foo: bar ba<tab>`, then the `linestart` line will end
+ at "bar", and we'll miss the space. Ensure that the end
+ of the line is a colon followed by an optional series of spaces */
+ i -= 1;
+ while (i >= 0) {
+ if (linestart[i] == ' ') {
+ i--;
+ continue;
+ } else if (linestart[i] == *completion_char) {
+ return TRUE;
+ } else {
+ break;
+ }
+ }
+ return FALSE;
+}
+
+static void sig_complete_word(GList **list, WINDOW_REC *window,
+ const char *word, const char *linestart,
+ int *want_space)
+{
+ SERVER_REC *server;
+ CHANNEL_REC *channel;
+ QUERY_REC *query;
+ char *prefix;
+
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(window != NULL);
+ g_return_if_fail(word != NULL);
+ g_return_if_fail(linestart != NULL);
+
+ server = window->active_server;
+ if (server == NULL && servers != NULL)
+ server = servers->data;
+
+ if (server != NULL && server_ischannel(server, word)) {
+ /* probably completing a channel name */
+ *list = completion_get_channels(window->active_server, word);
+ if (*list != NULL) signal_stop();
+ return;
+ }
+
+ server = window->active_server;
+ if (server == NULL || !server->connected)
+ return;
+
+ if (*linestart == '\0' && *word == '\0') {
+ if (!completion_empty_line)
+ return;
+ /* pressed TAB at the start of line - add /MSG */
+ prefix = g_strdup_printf("%cmsg", *cmdchars);
+ *list = completion_msg(server, NULL, "", prefix);
+ if (*list == NULL)
+ *list = g_list_append(*list, g_strdup(prefix));
+ g_free(prefix);
+
+ signal_stop();
+ return;
+ }
+
+ channel = CHANNEL(window->active);
+ query = QUERY(window->active);
+ if (channel == NULL && query != NULL &&
+ g_ascii_strncasecmp(word, query->name, strlen(word)) == 0) {
+ /* completion in query */
+ *list = g_list_append(*list, g_strdup(query->name));
+ } else if (channel != NULL) {
+ /* nick completion .. we could also be completing a nick
+ after /MSG from nicks in channel */
+ const char *suffix = only_nicks(linestart) ? completion_char : NULL;
+ complete_window_nicks(list, window, word, suffix);
+ } else if (window->level & MSGLEVEL_MSGS) {
+ /* msgs window, complete /MSG nicks */
+ *list = g_list_concat(completion_msg(server, NULL, word, NULL), *list);
+ }
+
+ if (*list != NULL) signal_stop();
+}
+
+static SERVER_REC *line_get_server(const char *line)
+{
+ SERVER_REC *server;
+ char *tag, *ptr;
+
+ g_return_val_if_fail(line != NULL, NULL);
+ if (*line != '-') return NULL;
+
+ /* -option found - should be server tag */
+ tag = g_strdup(line+1);
+ ptr = strchr(tag, ' ');
+ if (ptr != NULL) *ptr = '\0';
+
+ server = server_find_tag(tag);
+
+ g_free(tag);
+ return server;
+}
+
+static void sig_complete_msg(GList **list, WINDOW_REC *window,
+ const char *word, const char *line,
+ int *want_space)
+{
+ SERVER_REC *server, *msgserver;
+
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+ g_return_if_fail(line != NULL);
+
+ server = window->active_server;
+ if (server == NULL || !server->connected)
+ return;
+
+ msgserver = line_get_server(line);
+ *list = completion_msg(server, msgserver, word, NULL);
+ if (CHANNEL(window->active) != NULL)
+ complete_window_nicks(list, window, word, NULL);
+ if (*list != NULL) signal_stop();
+}
+
+static void sig_erase_complete_msg(WINDOW_REC *window, const char *word,
+ const char *line)
+{
+ SERVER_REC *server;
+ MODULE_SERVER_REC *mserver;
+ GSList *tmp;
+
+ server = line_get_server(line);
+ if (server == NULL){
+ server = window->active_server;
+ if (server == NULL)
+ return;
+ }
+
+ if (*word == '\0')
+ return;
+
+ /* check from global list */
+ completion_last_message_remove(word);
+
+ /* check from server specific list */
+ if (server != NULL) {
+ mserver = MODULE_DATA(server);
+ for (tmp = mserver->lastmsgs; tmp != NULL; tmp = tmp->next) {
+ LAST_MSG_REC *rec = tmp->data;
+
+ if (g_ascii_strcasecmp(rec->nick, word) == 0) {
+ last_msg_destroy(&mserver->lastmsgs, rec);
+ break;
+ }
+ }
+
+ }
+}
+
+GList *completion_get_chatnets(const char *word)
+{
+ GList *list;
+ GSList *tmp;
+ int len;
+
+ g_return_val_if_fail(word != NULL, NULL);
+
+ len = strlen(word);
+ list = NULL;
+
+ for (tmp = chatnets; tmp != NULL; tmp = tmp->next) {
+ CHATNET_REC *rec = tmp->data;
+
+ if (g_ascii_strncasecmp(rec->name, word, len) == 0)
+ list = g_list_append(list, g_strdup(rec->name));
+ }
+
+ return list;
+}
+
+GList *completion_get_servers(const char *word)
+{
+ GList *list;
+ GSList *tmp;
+ int len;
+
+ g_return_val_if_fail(word != NULL, NULL);
+
+ len = strlen(word);
+ list = NULL;
+
+ for (tmp = setupservers; tmp != NULL; tmp = tmp->next) {
+ SERVER_SETUP_REC *rec = tmp->data;
+
+ if (g_ascii_strncasecmp(rec->address, word, len) == 0)
+ list = g_list_append(list, g_strdup(rec->address));
+ }
+
+ return list;
+}
+
+GList *completion_get_targets(const char *word)
+{
+ CONFIG_NODE *node;
+ GList *list;
+ GSList *tmp;
+ int len;
+
+ g_return_val_if_fail(word != NULL, NULL);
+
+ len = strlen(word);
+ list = NULL;
+
+ /* get the list of all conversion targets */
+ node = iconfig_node_traverse("conversions", FALSE);
+ tmp = node == NULL ? NULL : config_node_first(node->value);
+ for (; tmp != NULL; tmp = config_node_next(tmp)) {
+ node = tmp->data;
+
+ if (node->type != NODE_TYPE_KEY)
+ continue;
+
+ if (len != 0 && g_ascii_strncasecmp(node->key, word, len) != 0)
+ continue;
+
+ list = g_list_append(list, g_strdup(node->key));
+ }
+
+ return list;
+}
+
+static void sig_complete_connect(GList **list, WINDOW_REC *window,
+ const char *word, const char *line,
+ int *want_space)
+{
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+
+ *list = completion_get_chatnets(word);
+ *list = g_list_concat(*list, completion_get_servers(word));
+ if (*list != NULL) signal_stop();
+}
+
+static void sig_complete_tag(GList **list, WINDOW_REC *window,
+ const char *word, const char *line,
+ int *want_space)
+{
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+
+ *list = completion_get_servertags(word);
+ if (*list != NULL) signal_stop();
+}
+
+static void sig_complete_topic(GList **list, WINDOW_REC *window,
+ const char *word, const char *line,
+ int *want_space)
+{
+ const char *topic;
+
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+
+ if (*word == '\0' && IS_CHANNEL(window->active)) {
+ topic = CHANNEL(window->active)->topic;
+ if (topic != NULL) {
+ *list = g_list_append(NULL, g_strdup(topic));
+ signal_stop();
+ }
+ }
+}
+
+static void sig_complete_away(GList **list, WINDOW_REC *window,
+ const char *word, const char *line,
+ int *want_space)
+{
+ const char *reason;
+
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+
+ *want_space = FALSE;
+
+ if (*word == '\0' && window->active_server != NULL) {
+ reason = SERVER(window->active_server)->away_reason;
+ if (reason != NULL) {
+ *list = g_list_append(NULL, g_strdup(reason));
+ signal_stop();
+ }
+ }
+}
+
+static void sig_complete_unalias(GList **list, WINDOW_REC *window,
+ const char *word, const char *line,
+ int *want_space)
+{
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+
+ *list = completion_get_aliases(word);
+ if (*list != NULL) signal_stop();
+}
+
+static void sig_complete_alias(GList **list, WINDOW_REC *window,
+ const char *word, const char *line,
+ int *want_space)
+{
+ const char *definition;
+
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+ g_return_if_fail(line != NULL);
+
+ if (*line != '\0') {
+ if ((definition = alias_find(line)) != NULL) {
+ *list = g_list_append(NULL, g_strdup(definition));
+ signal_stop();
+ }
+ } else {
+ *list = completion_get_aliases(word);
+ if (*list != NULL) signal_stop();
+ }
+}
+
+static void sig_complete_window(GList **list, WINDOW_REC *window,
+ const char *word, const char *linestart,
+ int *want_space)
+{
+ WINDOW_REC *win;
+ WI_ITEM_REC *item;
+ GSList *tmp;
+ int len;
+
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+
+ len = strlen(word);
+
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ win = tmp->data;
+ item = win->active;
+
+ if (win->name != NULL && g_ascii_strncasecmp(win->name, word, len) == 0)
+ *list = g_list_append(*list, g_strdup(win->name));
+ if (item != NULL && g_ascii_strncasecmp(item->visible_name, word, len) == 0)
+ *list = g_list_append(*list, g_strdup(item->visible_name));
+ }
+
+ if (*list != NULL) signal_stop();
+}
+
+static void sig_complete_channel(GList **list, WINDOW_REC *window,
+ const char *word, const char *line,
+ int *want_space)
+{
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+
+ *list = completion_get_channels(NULL, word);
+ if (*list != NULL) signal_stop();
+}
+
+static void sig_complete_server(GList **list, WINDOW_REC *window,
+ const char *word, const char *line,
+ int *want_space)
+{
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+
+ *list = completion_get_servers(word);
+ if (*list != NULL) signal_stop();
+}
+
+static void sig_complete_target(GList **list, WINDOW_REC *window,
+ const char *word, const char *line,
+ int *want_space)
+{
+ const char *definition;
+
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+ g_return_if_fail(line != NULL);
+
+ if (*line != '\0') {
+ if ((definition = iconfig_get_str("conversions", line ,NULL)) != NULL) {
+ *list = g_list_append(NULL, g_strdup(definition));
+ signal_stop();
+ }
+ } else {
+ *list = completion_get_targets(word);
+ if (*list != NULL) signal_stop();
+ }
+}
+
+static void event_text(const char *data, SERVER_REC *server, WI_ITEM_REC *item);
+
+/* expand \n, \t and \\ */
+static char *expand_escapes(const char *line, SERVER_REC *server,
+ WI_ITEM_REC *item)
+{
+ char *ptr, *ret;
+ const char *prev;
+ int chr;
+
+ prev = line;
+ ret = ptr = g_malloc(strlen(line)+1);
+ for (; *line != '\0'; line++) {
+ if (*line != '\\') {
+ *ptr++ = *line;
+ continue;
+ }
+
+ line++;
+ if (*line == '\0') {
+ *ptr++ = '\\';
+ break;
+ }
+
+ chr = expand_escape(&line);
+ if (chr == '\r' || chr == '\n') {
+ /* newline .. we need to send another "send text"
+ event to handle it (or actually the text before
+ the newline..) */
+ if (prev != line) {
+ char *prev_line = g_strndup(prev, (line - prev) - 1);
+ event_text(prev_line, server, item);
+ g_free(prev_line);
+ prev = line + 1;
+ ptr = ret;
+ }
+ } else if (chr != -1) {
+ /* escaping went ok */
+ *ptr++ = chr;
+ } else {
+ /* unknown escape, add it as-is */
+ *ptr++ = '\\';
+ *ptr++ = *line;
+ }
+ }
+
+ *ptr = '\0';
+ return ret;
+}
+
+static char *auto_complete(CHANNEL_REC *channel, const char *line)
+{
+ GList *comp;
+ const char *p;
+ char *nick, *ret;
+
+ p = strstr(line, completion_char);
+ if (p == NULL)
+ return NULL;
+
+ nick = g_strndup(line, (int) (p-line));
+
+ ret = NULL;
+ if (nicklist_find(channel, nick) == NULL) {
+ /* not an exact match, use the first possible completion */
+ comp = completion_channel_nicks(channel, nick, NULL);
+ if (comp != NULL) {
+ ret = g_strconcat(comp->data, p, NULL);
+ g_list_foreach(comp, (GFunc) g_free, NULL);
+ g_list_free(comp);
+ }
+ }
+
+ g_free(nick);
+
+ return ret;
+}
+
+static void event_text(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ char *line, *str, *target;
+
+ g_return_if_fail(data != NULL);
+
+ if (item == NULL)
+ return;
+
+ if (*data == '\0') {
+ /* empty line, forget it. */
+ signal_stop();
+ return;
+ }
+
+ line = settings_get_bool("expand_escapes") ?
+ expand_escapes(data, server, item) : g_strdup(data);
+
+ /* check for automatic nick completion */
+ if (completion_auto && IS_CHANNEL(item)) {
+ str = auto_complete(CHANNEL(item), line);
+ if (str != NULL) {
+ g_free(line);
+ line = str;
+ }
+ }
+
+ /* the nick is quoted in case it contains '-' character. also
+ spaces should work too now :) The nick is also escaped in case
+ it contains '\' characters */
+ target = escape_string(window_item_get_target(item));
+ str = g_strdup_printf(IS_CHANNEL(item) ? "-channel \"%s\" %s" :
+ IS_QUERY(item) ? "-nick \"%s\" %s" : "%s %s",
+ target, line);
+ g_free(target);
+
+ signal_emit("command msg", 3, str, server, item);
+
+ g_free(str);
+ g_free(line);
+
+ signal_stop();
+}
+
+static void sig_server_disconnected(SERVER_REC *server)
+{
+ MODULE_SERVER_REC *mserver;
+
+ g_return_if_fail(server != NULL);
+
+ mserver = MODULE_DATA(server);
+ if (mserver == NULL)
+ return;
+
+ while (mserver->lastmsgs)
+ last_msg_destroy(&mserver->lastmsgs, mserver->lastmsgs->data);
+}
+
+static void sig_channel_destroyed(CHANNEL_REC *channel)
+{
+ MODULE_CHANNEL_REC *mchannel;
+
+ g_return_if_fail(channel != NULL);
+
+ mchannel = MODULE_DATA(channel);
+ while (mchannel->lastmsgs != NULL) {
+ last_msg_destroy(&mchannel->lastmsgs,
+ mchannel->lastmsgs->data);
+ }
+}
+
+static void read_settings(void)
+{
+ keep_privates_count = settings_get_int("completion_keep_privates");
+ keep_publics_count = settings_get_int("completion_keep_publics");
+ completion_lowercase = settings_get_bool("completion_nicks_lowercase");
+
+ completion_auto = settings_get_bool("completion_auto");
+ completion_strict = settings_get_bool("completion_strict");
+ completion_empty_line = settings_get_bool("completion_empty_line");
+
+ completion_match_case = settings_get_choice("completion_nicks_match_case");
+
+ g_free_not_null(completion_char);
+ completion_char = g_strdup(settings_get_str("completion_char"));
+
+ g_free_not_null(cmdchars);
+ cmdchars = g_strdup(settings_get_str("cmdchars"));
+
+ if (*completion_char == '\0') {
+ /* this would break.. */
+ completion_auto = FALSE;
+ }
+}
+
+void chat_completion_init(void)
+{
+ settings_add_str("completion", "completion_char", ":");
+ settings_add_bool("completion", "completion_auto", FALSE);
+ settings_add_int("completion", "completion_keep_publics", 50);
+ settings_add_int("completion", "completion_keep_privates", 10);
+ settings_add_bool("completion", "completion_nicks_lowercase", FALSE);
+ settings_add_bool("completion", "completion_strict", FALSE);
+ settings_add_bool("completion", "completion_empty_line", TRUE);
+ settings_add_choice("completion", "completion_nicks_match_case", COMPLETE_MCASE_AUTO, "never;always;auto");
+
+ settings_add_bool("lookandfeel", "expand_escapes", FALSE);
+
+ read_settings();
+ signal_add("complete word", (SIGNAL_FUNC) sig_complete_word);
+ signal_add("complete command msg", (SIGNAL_FUNC) sig_complete_msg);
+ signal_add("complete command query", (SIGNAL_FUNC) sig_complete_msg);
+ signal_add("complete command action", (SIGNAL_FUNC) sig_complete_msg);
+ signal_add("complete erase command msg", (SIGNAL_FUNC) sig_erase_complete_msg);
+ signal_add("complete erase command query", (SIGNAL_FUNC) sig_erase_complete_msg);
+ signal_add("complete erase command action", (SIGNAL_FUNC) sig_erase_complete_msg);
+ signal_add("complete command connect", (SIGNAL_FUNC) sig_complete_connect);
+ signal_add("complete command server", (SIGNAL_FUNC) sig_complete_connect);
+ signal_add("complete command disconnect", (SIGNAL_FUNC) sig_complete_tag);
+ signal_add("complete command reconnect", (SIGNAL_FUNC) sig_complete_tag);
+ signal_add("complete command window server", (SIGNAL_FUNC) sig_complete_tag);
+ signal_add("complete command topic", (SIGNAL_FUNC) sig_complete_topic);
+ signal_add("complete command away", (SIGNAL_FUNC) sig_complete_away);
+ signal_add("complete command unalias", (SIGNAL_FUNC) sig_complete_unalias);
+ signal_add("complete command alias", (SIGNAL_FUNC) sig_complete_alias);
+ signal_add("complete command window goto", (SIGNAL_FUNC) sig_complete_window);
+ signal_add("complete command window item move", (SIGNAL_FUNC) sig_complete_channel);
+ signal_add("complete command server add", (SIGNAL_FUNC) sig_complete_server);
+ signal_add("complete command server remove", (SIGNAL_FUNC) sig_complete_server);
+ signal_add("complete command recode remove", (SIGNAL_FUNC) sig_complete_target);
+ signal_add("message public", (SIGNAL_FUNC) sig_message_public);
+ signal_add("message join", (SIGNAL_FUNC) sig_message_join);
+ signal_add("message private", (SIGNAL_FUNC) sig_message_private);
+ signal_add("message own_public", (SIGNAL_FUNC) sig_message_own_public);
+ signal_add("message own_private", (SIGNAL_FUNC) sig_message_own_private);
+ signal_add("nicklist remove", (SIGNAL_FUNC) sig_nick_removed);
+ signal_add("nicklist changed", (SIGNAL_FUNC) sig_nick_changed);
+ signal_add("send text", (SIGNAL_FUNC) event_text);
+ signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+ signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void chat_completion_deinit(void)
+{
+ while (global_lastmsgs != NULL)
+ last_msg_destroy(&global_lastmsgs, global_lastmsgs->data);
+
+ signal_remove("complete word", (SIGNAL_FUNC) sig_complete_word);
+ signal_remove("complete command msg", (SIGNAL_FUNC) sig_complete_msg);
+ signal_remove("complete command query", (SIGNAL_FUNC) sig_complete_msg);
+ signal_remove("complete command action", (SIGNAL_FUNC) sig_complete_msg);
+ signal_remove("complete erase command msg", (SIGNAL_FUNC) sig_erase_complete_msg);
+ signal_remove("complete erase command query", (SIGNAL_FUNC) sig_erase_complete_msg);
+ signal_remove("complete erase command action", (SIGNAL_FUNC) sig_erase_complete_msg);
+ signal_remove("complete command connect", (SIGNAL_FUNC) sig_complete_connect);
+ signal_remove("complete command server", (SIGNAL_FUNC) sig_complete_connect);
+ signal_remove("complete command disconnect", (SIGNAL_FUNC) sig_complete_tag);
+ signal_remove("complete command reconnect", (SIGNAL_FUNC) sig_complete_tag);
+ signal_remove("complete command window server", (SIGNAL_FUNC) sig_complete_tag);
+ signal_remove("complete command topic", (SIGNAL_FUNC) sig_complete_topic);
+ signal_remove("complete command away", (SIGNAL_FUNC) sig_complete_away);
+ signal_remove("complete command unalias", (SIGNAL_FUNC) sig_complete_unalias);
+ signal_remove("complete command alias", (SIGNAL_FUNC) sig_complete_alias);
+ signal_remove("complete command window goto", (SIGNAL_FUNC) sig_complete_window);
+ signal_remove("complete command window item move", (SIGNAL_FUNC) sig_complete_channel);
+ signal_remove("complete command server add", (SIGNAL_FUNC) sig_complete_server);
+ signal_remove("complete command server remove", (SIGNAL_FUNC) sig_complete_server);
+ signal_remove("complete command recode remove", (SIGNAL_FUNC) sig_complete_target);
+ signal_remove("message public", (SIGNAL_FUNC) sig_message_public);
+ signal_remove("message join", (SIGNAL_FUNC) sig_message_join);
+ signal_remove("message private", (SIGNAL_FUNC) sig_message_private);
+ signal_remove("message own_public", (SIGNAL_FUNC) sig_message_own_public);
+ signal_remove("message own_private", (SIGNAL_FUNC) sig_message_own_private);
+ signal_remove("nicklist remove", (SIGNAL_FUNC) sig_nick_removed);
+ signal_remove("nicklist changed", (SIGNAL_FUNC) sig_nick_changed);
+ signal_remove("send text", (SIGNAL_FUNC) event_text);
+ signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+ signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+
+ g_free_not_null(completion_char);
+ g_free_not_null(cmdchars);
+}
diff --git a/src/fe-common/core/chat-completion.h b/src/fe-common/core/chat-completion.h
new file mode 100644
index 0000000..2976b96
--- /dev/null
+++ b/src/fe-common/core/chat-completion.h
@@ -0,0 +1,16 @@
+#ifndef IRSSI_FE_COMMON_CORE_CHAT_COMPLETION_H
+#define IRSSI_FE_COMMON_CORE_CHAT_COMPLETION_H
+
+GList *completion_get_chatnets(const char *word);
+GList *completion_get_servers(const char *word);
+GList *completion_get_servertags(const char *word);
+GList *completion_get_channels(SERVER_REC *server, const char *word);
+GList *completion_get_aliases(const char *word);
+GList *completion_msg(SERVER_REC *win_server, SERVER_REC *find_server,
+ const char *nick, const char *prefix);
+
+void completion_last_message_add(const char *nick);
+void completion_last_message_remove(const char *nick);
+void completion_last_message_rename(const char *oldnick, const char *newnick);
+
+#endif
diff --git a/src/fe-common/core/command-history.c b/src/fe-common/core/command-history.c
new file mode 100644
index 0000000..43d8635
--- /dev/null
+++ b/src/fe-common/core/command-history.c
@@ -0,0 +1,496 @@
+/*
+ command-history.c : irssi
+
+ Copyright (C) 1999 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/special-vars.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/fe-common/core/fe-windows.h>
+#include <irssi/src/fe-common/core/window-items.h>
+
+#include <irssi/src/fe-common/core/command-history.h>
+
+/* command history */
+static GList *history_entries;
+static HISTORY_REC *global_history;
+static int window_history;
+static GSList *histories;
+
+static HISTORY_ENTRY_REC *history_entry_new(HISTORY_REC *history, const char *text)
+{
+ HISTORY_ENTRY_REC *entry;
+
+ entry = g_new0(HISTORY_ENTRY_REC, 1);
+ entry->text = g_strdup(text);
+ entry->history = history;
+ entry->time = time(NULL);
+
+ return entry;
+}
+
+static void history_entry_destroy(HISTORY_ENTRY_REC *entry)
+{
+ g_free((char *)entry->text);
+ g_free(entry);
+}
+
+GList *command_history_list_last(HISTORY_REC *history)
+{
+ GList *link;
+
+ link = g_list_last(history_entries);
+ while (link != NULL && history != NULL && ((HISTORY_ENTRY_REC *)link->data)->history != history) {
+ link = link->prev;
+ }
+
+ return link;
+}
+
+GList *command_history_list_first(HISTORY_REC *history)
+{
+ GList *link;
+
+ link = history_entries;
+ while (link != NULL && history != NULL && ((HISTORY_ENTRY_REC *)link->data)->history != history) {
+ link = link->next;
+ }
+
+ return link;
+}
+
+GList *command_history_list_prev(HISTORY_REC *history, GList *pos)
+{
+ GList *link;
+
+ link = pos != NULL ? pos->prev : NULL;
+ while (link != NULL && history != NULL && ((HISTORY_ENTRY_REC *)link->data)->history != history) {
+ link = link->prev;
+ }
+
+ return link;
+}
+
+GList *command_history_list_next(HISTORY_REC *history, GList *pos)
+{
+ GList *link;
+
+ link = pos != NULL ? pos->next : NULL;
+ while (link != NULL && history != NULL && ((HISTORY_ENTRY_REC *)link->data)->history != history) {
+ link = link->next;
+ }
+
+ return link;
+}
+
+static void command_history_clear_pos_for_unlink_func(HISTORY_REC *history, GList* link)
+{
+ if (history->pos == link) {
+ history->pos = command_history_list_next(history, link);
+ history->redo = 1;
+ }
+}
+
+static void history_list_delete_link_and_destroy(GList *link)
+{
+ g_slist_foreach(histories,
+ (GFunc) command_history_clear_pos_for_unlink_func, link);
+ history_entry_destroy(link->data);
+ history_entries = g_list_delete_link(history_entries, link);
+}
+
+void command_history_add(HISTORY_REC *history, const char *text)
+{
+ GList *link;
+
+ g_return_if_fail(history != NULL);
+ g_return_if_fail(text != NULL);
+
+ link = command_history_list_last(history);
+ if (link != NULL && g_strcmp0(((HISTORY_ENTRY_REC *)link->data)->text, text) == 0)
+ return; /* same as previous entry */
+
+ if (settings_get_int("max_command_history") < 1 ||
+ history->lines < settings_get_int("max_command_history"))
+ history->lines++;
+ else {
+ link = command_history_list_first(history);
+ history_list_delete_link_and_destroy(link);
+ }
+
+ history_entries = g_list_append(history_entries, history_entry_new(history, text));
+}
+
+HISTORY_REC *command_history_find(HISTORY_REC *history)
+{
+ GSList *tmp;
+ tmp = g_slist_find(histories, history);
+
+ if (tmp == NULL)
+ return NULL;
+ else
+ return tmp->data;
+}
+
+HISTORY_REC *command_history_find_name(const char *name)
+{
+ GSList *tmp;
+
+ if (name == NULL)
+ return NULL;
+
+ for (tmp = histories; tmp != NULL; tmp = tmp->next) {
+ HISTORY_REC *rec = tmp->data;
+
+ if (rec->name != NULL &&
+ g_ascii_strcasecmp(rec->name, name) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static int history_entry_after_time_sort(const HISTORY_ENTRY_REC *a, const HISTORY_ENTRY_REC *b)
+{
+ return a->time == b->time ? 1 : a->time - b->time;
+}
+
+void command_history_load_entry(time_t history_time, HISTORY_REC *history, const char *text)
+{
+ HISTORY_ENTRY_REC *entry;
+
+ g_return_if_fail(history != NULL);
+ g_return_if_fail(text != NULL);
+
+ entry = g_new0(HISTORY_ENTRY_REC, 1);
+ entry->text = g_strdup(text);
+ entry->history = history;
+ entry->time = history_time;
+
+ history->lines++;
+
+ history_entries = g_list_insert_sorted(history_entries, entry, (GCompareFunc)history_entry_after_time_sort);
+}
+
+static int history_entry_find_func(const HISTORY_ENTRY_REC *data, const HISTORY_ENTRY_REC *user_data)
+{
+ if ((user_data->time == -1 || (data->time == user_data->time)) &&
+ (user_data->history == NULL || (data->history == user_data->history)) &&
+ g_strcmp0(data->text, user_data->text) == 0) {
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+gboolean command_history_delete_entry(time_t history_time, HISTORY_REC *history, const char *text)
+{
+ GList *link;
+ HISTORY_ENTRY_REC entry;
+
+ g_return_val_if_fail(history != NULL, FALSE);
+ g_return_val_if_fail(text != NULL, FALSE);
+
+ entry.text = text;
+ entry.history = history;
+ entry.time = history_time;
+
+ link = g_list_find_custom(history_entries, &entry, (GCompareFunc)history_entry_find_func);
+ if (link != NULL) {
+ ((HISTORY_ENTRY_REC *)link->data)->history->lines--;
+ history_list_delete_link_and_destroy(link);
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+HISTORY_REC *command_history_current(WINDOW_REC *window)
+{
+ HISTORY_REC *rec;
+
+ if (window == NULL)
+ return global_history;
+
+ rec = command_history_find_name(window->history_name);
+ if (rec != NULL)
+ return rec;
+
+ if (window_history)
+ return window->history;
+
+ return global_history;
+}
+
+static const char *command_history_prev_int(WINDOW_REC *window, const char *text, gboolean global)
+{
+ HISTORY_REC *history;
+ GList *pos;
+
+ history = command_history_current(window);
+ pos = history->pos;
+ history->redo = 0;
+
+ if (pos != NULL) {
+ /* don't go past the first entry (no wrap around) */
+ GList *prev = command_history_list_prev(global ? NULL : history, history->pos);
+ if (prev != NULL)
+ history->pos = prev;
+ } else {
+ history->pos = command_history_list_last(global ? NULL : history);
+ }
+
+ if (*text != '\0' &&
+ (pos == NULL || g_strcmp0(((HISTORY_ENTRY_REC *)pos->data)->text, text) != 0)) {
+ /* save the old entry to history */
+ command_history_add(history, text);
+ }
+
+ return history->pos == NULL ? text : ((HISTORY_ENTRY_REC *)history->pos->data)->text;
+}
+
+const char *command_history_prev(WINDOW_REC *window, const char *text)
+{
+ return command_history_prev_int(window, text, FALSE);
+}
+
+const char *command_global_history_prev(WINDOW_REC *window, const char *text)
+{
+ return command_history_prev_int(window, text, TRUE);
+}
+
+static const char *command_history_next_int(WINDOW_REC *window, const char *text, gboolean global)
+{
+ HISTORY_REC *history;
+ GList *pos;
+
+ history = command_history_current(window);
+ pos = history->pos;
+
+ if (!(history->redo) && pos != NULL)
+ history->pos = command_history_list_next(global ? NULL : history, history->pos);
+ history->redo = 0;
+
+ if (*text != '\0' &&
+ (pos == NULL || g_strcmp0(((HISTORY_ENTRY_REC *)pos->data)->text, text) != 0)) {
+ /* save the old entry to history */
+ command_history_add(history, text);
+ }
+ return history->pos == NULL ? "" : ((HISTORY_ENTRY_REC *)history->pos->data)->text;
+}
+
+const char *command_history_next(WINDOW_REC *window, const char *text)
+{
+ return command_history_next_int(window, text, FALSE);
+}
+
+const char *command_global_history_next(WINDOW_REC *window, const char *text)
+{
+ return command_history_next_int(window, text, TRUE);
+}
+
+const char *command_history_delete_current(WINDOW_REC *window, const char *text)
+{
+ HISTORY_REC *history;
+ GList *pos;
+
+ history = command_history_current(window);
+ pos = history->pos;
+
+ if (pos != NULL && g_strcmp0(((HISTORY_ENTRY_REC *)pos->data)->text, text) == 0) {
+ ((HISTORY_ENTRY_REC *)pos->data)->history->lines--;
+ history_list_delete_link_and_destroy(pos);
+ }
+
+ history->redo = 0;
+ return history->pos == NULL ? "" : ((HISTORY_ENTRY_REC *)history->pos->data)->text;
+}
+
+void command_history_clear_pos_func(HISTORY_REC *history, gpointer user_data)
+{
+ history->pos = NULL;
+}
+
+void command_history_clear_pos(WINDOW_REC *window)
+{
+ g_slist_foreach(histories,
+ (GFunc) command_history_clear_pos_func, NULL);
+}
+
+HISTORY_REC *command_history_create(const char *name)
+{
+ HISTORY_REC *rec;
+
+ rec = g_new0(HISTORY_REC, 1);
+
+ if (name != NULL)
+ rec->name = g_strdup(name);
+
+ histories = g_slist_append(histories, rec);
+
+ return rec;
+}
+
+void command_history_clear(HISTORY_REC *history)
+{
+ GList *link, *next;
+
+ g_return_if_fail(history != NULL);
+
+ command_history_clear_pos_func(history, NULL);
+ link = command_history_list_first(history);
+ while (link != NULL) {
+ next = command_history_list_next(history, link);
+ history_list_delete_link_and_destroy(link);
+ link = next;
+ }
+ history->lines = 0;
+}
+
+void command_history_destroy(HISTORY_REC *history)
+{
+ g_return_if_fail(history != NULL);
+
+ /* history->refcount should be 0 here, or somthing is wrong... */
+ g_return_if_fail(history->refcount == 0);
+
+ histories = g_slist_remove(histories, history);
+ command_history_clear(history);
+
+ g_free_not_null(history->name);
+ g_free(history);
+}
+
+void command_history_link(const char *name)
+{
+ HISTORY_REC *rec;
+ rec = command_history_find_name(name);
+
+ if (rec == NULL)
+ rec = command_history_create(name);
+
+ rec->refcount++;
+}
+
+void command_history_unlink(const char *name)
+{
+ HISTORY_REC *rec;
+ rec = command_history_find_name(name);
+
+ if (rec == NULL)
+ return;
+
+ if (--(rec->refcount) <= 0)
+ command_history_destroy(rec);
+}
+
+static void sig_window_created(WINDOW_REC *window, int automatic)
+{
+ window->history = command_history_create(NULL);
+}
+
+static void sig_window_destroyed(WINDOW_REC *window)
+{
+ command_history_unlink(window->history_name);
+ command_history_destroy(window->history);
+ g_free_not_null(window->history_name);
+}
+
+static void sig_window_history_cleared(WINDOW_REC *window, const char *name) {
+ HISTORY_REC *history;
+
+ if (name == NULL || *name == '\0') {
+ history = command_history_current(window);
+ } else {
+ history = command_history_find_name(name);
+ }
+
+ command_history_clear(history);
+}
+
+static void sig_window_history_changed(WINDOW_REC *window, const char *oldname)
+{
+ command_history_link(window->history_name);
+ command_history_unlink(oldname);
+}
+
+static char *special_history_func(const char *text, void *item, int *free_ret)
+{
+ WINDOW_REC *window;
+ HISTORY_REC *history;
+ GList *tmp;
+ char *findtext, *ret;
+
+ window = item == NULL ? active_win : window_item_window(item);
+
+ findtext = g_strdup_printf("*%s*", text);
+ ret = NULL;
+
+ history = command_history_current(window);
+ for (tmp = command_history_list_first(history); tmp != NULL; tmp = command_history_list_next(history, tmp)) {
+ const char *line = ((HISTORY_ENTRY_REC *)tmp->data)->text;
+
+ if (match_wildcards(findtext, line)) {
+ *free_ret = TRUE;
+ ret = g_strdup(line);
+ }
+ }
+ g_free(findtext);
+
+ return ret;
+}
+
+static void read_settings(void)
+{
+ window_history = settings_get_bool("window_history");
+}
+
+void command_history_init(void)
+{
+ settings_add_int("history", "max_command_history", 100);
+ settings_add_bool("history", "window_history", FALSE);
+
+ special_history_func_set(special_history_func);
+
+ history_entries = NULL;
+
+ global_history = command_history_create(NULL);
+
+ read_settings();
+ signal_add("window created", (SIGNAL_FUNC) sig_window_created);
+ signal_add("window destroyed", (SIGNAL_FUNC) sig_window_destroyed);
+ signal_add("window history changed", (SIGNAL_FUNC) sig_window_history_changed);
+ signal_add_last("window history cleared", (SIGNAL_FUNC) sig_window_history_cleared);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void command_history_deinit(void)
+{
+ signal_remove("window created", (SIGNAL_FUNC) sig_window_created);
+ signal_remove("window destroyed", (SIGNAL_FUNC) sig_window_destroyed);
+ signal_remove("window history changed", (SIGNAL_FUNC) sig_window_history_changed);
+ signal_remove("window history cleared", (SIGNAL_FUNC) sig_window_history_cleared);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+
+ command_history_destroy(global_history);
+
+ g_list_free_full(history_entries, (GDestroyNotify) history_entry_destroy);
+}
diff --git a/src/fe-common/core/command-history.h b/src/fe-common/core/command-history.h
new file mode 100644
index 0000000..f404264
--- /dev/null
+++ b/src/fe-common/core/command-history.h
@@ -0,0 +1,53 @@
+#ifndef IRSSI_FE_COMMON_CORE_COMMAND_HISTORY_H
+#define IRSSI_FE_COMMON_CORE_COMMAND_HISTORY_H
+
+#include <irssi/src/common.h>
+
+typedef struct {
+ char *name;
+
+ GList *pos;
+ int lines;
+
+ int refcount;
+ unsigned int redo:1;
+} HISTORY_REC;
+
+typedef struct {
+ const char *text;
+ HISTORY_REC *history;
+ time_t time;
+} HISTORY_ENTRY_REC;
+
+HISTORY_REC *command_history_find(HISTORY_REC *history);
+HISTORY_REC *command_history_find_name(const char *name);
+
+HISTORY_REC *command_history_current(WINDOW_REC *window);
+
+void command_history_init(void);
+void command_history_deinit(void);
+
+void command_history_add(HISTORY_REC *history, const char *text);
+void command_history_load_entry(time_t time, HISTORY_REC *history, const char *text);
+gboolean command_history_delete_entry(time_t history_time, HISTORY_REC *history, const char *text);
+
+GList *command_history_list_last(HISTORY_REC *history);
+GList *command_history_list_first(HISTORY_REC *history);
+GList *command_history_list_prev(HISTORY_REC *history, GList *pos);
+GList *command_history_list_next(HISTORY_REC *history, GList *pos);
+
+const char *command_history_prev(WINDOW_REC *window, const char *text);
+const char *command_history_next(WINDOW_REC *window, const char *text);
+const char *command_global_history_prev(WINDOW_REC *window, const char *text);
+const char *command_global_history_next(WINDOW_REC *window, const char *text);
+const char *command_history_delete_current(WINDOW_REC *window, const char *text);
+
+void command_history_clear_pos(WINDOW_REC *window);
+
+HISTORY_REC *command_history_create(const char *name);
+void command_history_clear(HISTORY_REC *history);
+void command_history_destroy(HISTORY_REC *history);
+void command_history_link(const char *name);
+void command_history_unlink(const char *name);
+
+#endif
diff --git a/src/fe-common/core/completion.c b/src/fe-common/core/completion.c
new file mode 100644
index 0000000..e2c4d2a
--- /dev/null
+++ b/src/fe-common/core/completion.c
@@ -0,0 +1,957 @@
+/*
+ completion.c : irssi
+
+ Copyright (C) 2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/core/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/lib-config/iconfig.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/fe-common/core/completion.h>
+#include <irssi/src/fe-common/core/printtext.h>
+
+static GList *complist; /* list of commands we're currently completing */
+static char *last_line;
+static int last_want_space, last_line_pos;
+
+#define isseparator_notspace(c) \
+ ((c) == ',')
+
+#define isseparator_space(c) \
+ ((c) == ' ')
+
+#define isseparator(c) \
+ (isseparator_space(c) || isseparator_notspace(c))
+
+void chat_completion_init(void);
+void chat_completion_deinit(void);
+
+static const char *completion_find(const char *key, int automatic)
+{
+ CONFIG_NODE *node;
+
+ node = iconfig_node_traverse("completions", FALSE);
+ if (node == NULL || node->type != NODE_TYPE_BLOCK)
+ return NULL;
+
+ node = iconfig_node_section(node, key, -1);
+ if (node == NULL)
+ return NULL;
+
+ if (automatic && !config_node_get_bool(node, "auto", FALSE))
+ return NULL;
+
+ return config_node_get_str(node, "value", NULL);
+}
+
+/* Return whole word at specified position in string */
+static char *get_word_at(const char *str, int pos, char **startpos)
+{
+ const char *start, *end;
+
+ g_return_val_if_fail(str != NULL, NULL);
+ g_return_val_if_fail(pos >= 0, NULL);
+
+ /* get previous word if char at `pos' is space */
+ start = str+pos;
+ while (start > str && isseparator(start[-1])) start--;
+
+ end = start;
+ while (start > str && !isseparator(start[-1])) start--;
+ while (*end != '\0' && !isseparator(*end)) end++;
+ while (*end != '\0' && isseparator_notspace(*end)) end++;
+
+ *startpos = (char *) start;
+ return g_strndup(start, (int) (end-start));
+}
+
+/* automatic word completion - called when space/enter is pressed */
+char *auto_word_complete(const char *line, int *pos)
+{
+ GString *result;
+ const char *replace;
+ char *word, *wordstart, *ret;
+ int startpos;
+
+ g_return_val_if_fail(line != NULL, NULL);
+ g_return_val_if_fail(pos != NULL, NULL);
+
+ word = get_word_at(line, *pos, &wordstart);
+ startpos = (int) (wordstart-line);
+
+ result = g_string_new(line);
+ g_string_erase(result, startpos, strlen(word));
+
+ /* check for words in autocompletion list */
+ replace = completion_find(word, TRUE);
+ if (replace == NULL || (!g_strcmp0(replace, word))) {
+ ret = NULL;
+ g_string_free(result, TRUE);
+ } else {
+ *pos = startpos+strlen(replace);
+
+ g_string_insert(result, startpos, replace);
+ ret = result->str;
+ g_string_free(result, FALSE);
+ }
+
+ g_free(word);
+ return ret;
+}
+
+static void free_completions(void)
+{
+ complist = g_list_first(complist);
+
+ g_list_foreach(complist, (GFunc) g_free, NULL);
+ g_list_free(complist);
+ complist = NULL;
+
+ g_free_and_null(last_line);
+}
+
+/* manual word completion - called when TAB is pressed */
+char *word_complete(WINDOW_REC *window, const char *line, int *pos, int erase, int backward)
+{
+ static int startpos = 0, wordlen = 0;
+ int old_startpos, old_wordlen;
+
+ GString *result;
+ const char *cmdchars;
+ char *word, *wordstart, *linestart, *ret, *data;
+ int continue_complete, want_space, expand_escapes;
+
+ g_return_val_if_fail(line != NULL, NULL);
+ g_return_val_if_fail(pos != NULL, NULL);
+
+ continue_complete = complist != NULL && *pos == last_line_pos &&
+ g_strcmp0(line, last_line) == 0;
+
+ if (erase && !continue_complete)
+ return NULL;
+
+ old_startpos = startpos;
+ old_wordlen = wordlen;
+
+ if (!erase && continue_complete) {
+ word = NULL;
+ linestart = NULL;
+ } else {
+ char* old_wordstart;
+
+ /* get the word we want to complete */
+ word = get_word_at(line, *pos, &wordstart);
+ old_wordstart = wordstart;
+
+ startpos = (int) (wordstart-line);
+ wordlen = strlen(word);
+
+ /* remove trailing spaces from linestart */
+ while (wordstart > line && isseparator_space(wordstart[-1]))
+ wordstart--;
+
+ /* unless everything was spaces */
+ if (old_wordstart > line && wordstart == line)
+ wordstart = old_wordstart - 1;
+
+ linestart = g_strndup(line, (int) (wordstart-line));
+
+ /* completions usually add space after the word, that makes
+ things a bit harder. When continuing a completion
+ "/msg nick1 "<tab> we have to cycle to nick2, etc.
+ BUT if we start completion with "/msg "<tab>, we don't
+ want to complete the /msg word, but instead complete empty
+ word with /msg being in linestart. */
+ if (!erase && *pos > 0 && isseparator_space(line[*pos-1]) &&
+ (*linestart == '\0' || !isseparator_space(wordstart[-1]))) {
+ char *old;
+
+ old = linestart;
+ /* we want to move word into linestart */
+ if (*linestart == '\0') {
+ linestart = g_strdup(word);
+ } else {
+ GString *str = g_string_new(linestart);
+ if (old_wordstart[-1] != str->str[str->len - 1]) {
+ /* do not accidentally duplicate the word separator */
+ g_string_append_c(str, old_wordstart[-1]);
+ }
+ g_string_append(str, word);
+ linestart = g_string_free(str, FALSE);
+ }
+ g_free(old);
+
+ g_free(word);
+ word = g_strdup("");
+
+ startpos = *linestart == '\0' ? 0 :
+ strlen(linestart)+1;
+ wordlen = 0;
+ }
+
+ }
+
+ if (erase) {
+ signal_emit("complete erase", 3, window, word, linestart);
+
+ /* jump to next completion */
+ startpos = old_startpos;
+ wordlen = old_wordlen;
+ }
+
+ if (continue_complete) {
+ /* complete from old list */
+ if (backward)
+ complist = complist->prev != NULL ? complist->prev :
+ g_list_last(complist);
+ else
+ complist = complist->next != NULL ? complist->next :
+ g_list_first(complist);
+ want_space = last_want_space;
+ } else {
+ int keep_word = settings_get_bool("completion_keep_word");
+ /* get new completion list */
+ free_completions();
+
+ want_space = TRUE;
+ signal_emit("complete word", 5, &complist, window, word, linestart, &want_space);
+ last_want_space = want_space;
+
+ if (complist != NULL) {
+ /* Remove all nulls (from the signal) before doing further processing */
+ complist = g_list_remove_all(g_list_first(complist), NULL);
+
+ if (keep_word) {
+ complist = g_list_append(complist, g_strdup(word));
+ }
+
+ if (backward) {
+ complist = g_list_last(complist);
+ if (keep_word) {
+ complist = complist->prev;
+ }
+ }
+ }
+ }
+
+ g_free(linestart);
+ g_free(word);
+
+ if (complist == NULL)
+ return NULL;
+
+ /* get the cmd char */
+ cmdchars = settings_get_str("cmdchars");
+
+ /* get the expand_escapes setting */
+ expand_escapes = settings_get_bool("expand_escapes");
+
+ /* escape if the word doesn't begin with '/' and expand_escapes are turned on */
+ data = strchr(cmdchars, *line) == NULL && expand_escapes ?
+ escape_string_backslashes(complist->data) : g_strdup(complist->data);
+
+ /* word completed */
+ *pos = startpos + strlen(data);
+
+ /* replace the word in line - we need to return
+ a full new line */
+ result = g_string_new(line);
+ g_string_erase(result, startpos, wordlen);
+ g_string_insert(result, startpos, data);
+
+ if (want_space) {
+ if (!isseparator(result->str[*pos]))
+ g_string_insert_c(result, *pos, ' ');
+ (*pos)++;
+ }
+
+ wordlen = strlen(data);
+ last_line_pos = *pos;
+ g_free_not_null(last_line);
+ last_line = g_strdup(result->str);
+
+ ret = result->str;
+ g_string_free(result, FALSE);
+
+ /* free the data */
+ g_free(data);
+
+ return ret;
+}
+
+#define IS_CURRENT_DIR(dir) \
+ ((dir)[0] == '.' && ((dir)[1] == '\0' || (dir)[1] == G_DIR_SEPARATOR))
+
+#define USE_DEFAULT_PATH(path, default_path) \
+ ((!g_path_is_absolute(path) || IS_CURRENT_DIR(path)) && \
+ default_path != NULL)
+
+static GList *list_add_file(GList *list, const char *name, const char *default_path)
+{
+ struct stat statbuf;
+ char *fname;
+
+ g_return_val_if_fail(name != NULL, NULL);
+
+ fname = convert_home(name);
+ if (USE_DEFAULT_PATH(fname, default_path)) {
+ g_free(fname);
+ fname = g_strconcat(default_path, G_DIR_SEPARATOR_S,
+ name, NULL);
+ }
+ if (stat(fname, &statbuf) == 0) {
+ list = g_list_append(list, !S_ISDIR(statbuf.st_mode) ? g_strdup(name) :
+ g_strconcat(name, G_DIR_SEPARATOR_S, NULL));
+ }
+
+ g_free(fname);
+ return list;
+}
+
+GList *filename_complete(const char *path, const char *default_path)
+{
+ GList *list;
+ DIR *dirp;
+ struct dirent *dp;
+ char *basename;
+ char *realpath, *dir, *name;
+ size_t len;
+
+ g_return_val_if_fail(path != NULL, NULL);
+
+ if (path[0] == '\0') {
+ return NULL;
+ }
+
+ list = NULL;
+
+ /* get directory part of the path - expand ~/ */
+ realpath = convert_home(path);
+ if (USE_DEFAULT_PATH(realpath, default_path)) {
+ g_free(realpath);
+ realpath = g_strconcat(default_path, G_DIR_SEPARATOR_S,
+ path, NULL);
+ }
+
+ /* open directory for reading */
+ dir = g_path_get_dirname(realpath);
+ dirp = opendir(dir);
+ g_free(dir);
+ g_free(realpath);
+
+ if (dirp == NULL)
+ return NULL;
+
+ dir = g_path_get_dirname(path);
+ if (*dir == G_DIR_SEPARATOR && dir[1] == '\0') {
+ /* completing file in root directory */
+ *dir = '\0';
+ } else if (IS_CURRENT_DIR(dir) && !IS_CURRENT_DIR(path)) {
+ /* completing file in default_path
+ (path not set, and leave it that way) */
+ g_free_and_null(dir);
+ }
+
+ len = strlen(path);
+ /* g_path_get_basename() returns the component before the last slash if
+ * the path ends with a directory separator, that's not what we want */
+ if (len > 0 && path[len - 1] == G_DIR_SEPARATOR) {
+ basename = g_strdup("");
+ } else {
+ basename = g_path_get_basename(path);
+ }
+ len = strlen(basename);
+
+ /* add all files in directory to completion list */
+ while ((dp = readdir(dirp)) != NULL) {
+ if (dp->d_name[0] == '.') {
+ if (dp->d_name[1] == '\0' ||
+ (dp->d_name[1] == '.' && dp->d_name[2] == '\0'))
+ continue; /* skip . and .. */
+
+ /* Skip the dotfiles unless the user explicitly asked us
+ * to do so. Basename might be './', beware of that */
+ if (basename[0] != '.' || basename[1] == '\0')
+ continue;
+ }
+
+ if (len == 0 || strncmp(dp->d_name, basename, len) == 0) {
+ name = dir == NULL ? g_strdup(dp->d_name) :
+ g_strdup_printf("%s"G_DIR_SEPARATOR_S"%s", dir, dp->d_name);
+ list = list_add_file(list, name, default_path);
+ g_free(name);
+ }
+ }
+ closedir(dirp);
+ g_free(basename);
+
+ g_free_not_null(dir);
+ return list;
+}
+
+static GList *completion_get_settings(const char *key, SettingType type)
+{
+ GList *complist;
+ GSList *tmp, *sets;
+ int len;
+
+ g_return_val_if_fail(key != NULL, NULL);
+
+ sets = settings_get_sorted();
+
+ len = strlen(key);
+ complist = NULL;
+ for (tmp = sets; tmp != NULL; tmp = tmp->next) {
+ SETTINGS_REC *rec = tmp->data;
+
+ if ((type == SETTING_TYPE_ANY || rec->type == type) && g_ascii_strncasecmp(rec->key, key, len) == 0)
+ complist = g_list_insert_sorted(complist, g_strdup(rec->key),
+ (GCompareFunc) i_istr_cmp);
+ }
+ g_slist_free(sets);
+ return complist;
+}
+
+static GList *completion_get_aliases(const char *alias, char cmdchar)
+{
+ CONFIG_NODE *node;
+ GList *complist;
+ GSList *tmp;
+ char *word;
+ int len;
+
+ g_return_val_if_fail(alias != NULL, NULL);
+
+ /* get list of aliases from mainconfig */
+ node = iconfig_node_traverse("aliases", FALSE);
+ tmp = node == NULL ? NULL : config_node_first(node->value);
+
+ len = strlen(alias);
+ complist = NULL;
+ for (; tmp != NULL; tmp = config_node_next(tmp)) {
+ CONFIG_NODE *node = tmp->data;
+
+ if (node->type != NODE_TYPE_KEY)
+ continue;
+
+ if (g_ascii_strncasecmp(node->key, alias, len) == 0) {
+ word = cmdchar == '\0' ? g_strdup(node->key) :
+ g_strdup_printf("%c%s", cmdchar, node->key);
+ /* add matching alias to completion list, aliases will
+ be appended after command completions and kept in
+ uppercase to show it's an alias */
+ if (i_list_find_icase_string(complist, word) == NULL)
+ complist =
+ g_list_insert_sorted(complist, word, (GCompareFunc) i_istr_cmp);
+ else
+ g_free(word);
+ }
+ }
+ return complist;
+}
+
+static GList *completion_get_commands(const char *cmd, char cmdchar)
+{
+ GList *complist;
+ GSList *tmp;
+ char *word;
+ int len;
+
+ g_return_val_if_fail(cmd != NULL, NULL);
+
+ len = strlen(cmd);
+ complist = NULL;
+ for (tmp = commands; tmp != NULL; tmp = tmp->next) {
+ COMMAND_REC *rec = tmp->data;
+
+ if (strchr(rec->cmd, ' ') != NULL)
+ continue;
+
+ if (g_ascii_strncasecmp(rec->cmd, cmd, len) == 0) {
+ word = cmdchar == '\0' ? g_strdup(rec->cmd) :
+ g_strdup_printf("%c%s", cmdchar, rec->cmd);
+ if (i_list_find_icase_string(complist, word) == NULL)
+ complist =
+ g_list_insert_sorted(complist, word, (GCompareFunc) i_istr_cmp);
+ else
+ g_free(word);
+ }
+ }
+ return complist;
+}
+
+static GList *completion_get_subcommands(const char *cmd)
+{
+ GList *complist;
+ GSList *tmp;
+ char *spacepos;
+ int len, skip;
+
+ g_return_val_if_fail(cmd != NULL, NULL);
+
+ /* get the number of chars to skip at the start of command. */
+ spacepos = strrchr(cmd, ' ');
+ skip = spacepos == NULL ? strlen(cmd)+1 :
+ ((int) (spacepos-cmd) + 1);
+
+ len = strlen(cmd);
+ complist = NULL;
+ for (tmp = commands; tmp != NULL; tmp = tmp->next) {
+ COMMAND_REC *rec = tmp->data;
+
+ if ((int)strlen(rec->cmd) < len)
+ continue;
+
+ if (strchr(rec->cmd+len, ' ') != NULL)
+ continue;
+
+ if (g_ascii_strncasecmp(rec->cmd, cmd, len) == 0)
+ complist = g_list_insert_sorted(complist, g_strdup(rec->cmd + skip),
+ (GCompareFunc) i_istr_cmp);
+ }
+ return complist;
+}
+
+static GList *completion_get_options(const char *cmd, const char *option)
+{
+ COMMAND_REC *rec;
+ GList *list;
+ char **tmp;
+ int len;
+
+ g_return_val_if_fail(cmd != NULL, NULL);
+ g_return_val_if_fail(option != NULL, NULL);
+
+ rec = command_find(cmd);
+ if (rec == NULL || rec->options == NULL) return NULL;
+
+ list = NULL;
+ len = strlen(option);
+ for (tmp = rec->options; *tmp != NULL; tmp++) {
+ const char *optname;
+ if (**tmp == '~')
+ continue; /* deprecated or hidden option */
+
+ optname = *tmp + iscmdtype(**tmp);
+
+ if (len == 0 || g_ascii_strncasecmp(optname, option, len) == 0)
+ list = g_list_append(list, g_strconcat("-", optname, NULL));
+ }
+
+ return list;
+}
+
+/* split the line to command and arguments */
+static char *line_get_command(const char *line, char **args, int aliases)
+{
+ const char *ptr, *cmdargs;
+ char *cmd, *checkcmd;
+
+ g_return_val_if_fail(line != NULL, NULL);
+ g_return_val_if_fail(args != NULL, NULL);
+
+ cmd = checkcmd = NULL; *args = "";
+ cmdargs = NULL; ptr = line;
+
+ do {
+ ptr = strchr(ptr, ' ');
+ if (ptr == NULL) {
+ checkcmd = g_strdup(line);
+ cmdargs = "";
+ } else {
+ checkcmd = g_strndup(line, (int) (ptr-line));
+
+ while (*ptr == ' ') ptr++;
+ cmdargs = ptr;
+ }
+
+ if (aliases ? !alias_find(checkcmd) :
+ !command_find(checkcmd)) {
+ /* not found, use the previous */
+ g_free(checkcmd);
+ break;
+ }
+
+ /* found, check if it has subcommands */
+ g_free_not_null(cmd);
+ if (!aliases)
+ cmd = checkcmd;
+ else {
+ cmd = g_strdup(alias_find(checkcmd));
+ g_free(checkcmd);
+ }
+ *args = (char *) cmdargs;
+ } while (ptr != NULL);
+
+ if (cmd != NULL)
+ ascii_strdown(cmd);
+ return cmd;
+}
+
+static char *expand_aliases(const char *line)
+{
+ char *cmd, *args, *ret;
+
+ g_return_val_if_fail(line != NULL, NULL);
+
+ cmd = line_get_command(line, &args, TRUE);
+ if (cmd == NULL) return g_strdup(line);
+ if (*args == '\0') return cmd;
+
+ ret = g_strconcat(cmd, " ", args, NULL);
+ g_free(cmd);
+ return ret;
+}
+
+static void sig_complete_word(GList **list, WINDOW_REC *window,
+ const char *word, const char *linestart,
+ int *want_space)
+{
+ const char *newword, *cmdchars;
+ char *signal, *cmd, *args, *line;
+
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+ g_return_if_fail(linestart != NULL);
+
+ /* check against "completion words" list */
+ newword = completion_find(word, FALSE);
+ if (newword != NULL) {
+ *list = g_list_append(*list, g_strdup(newword));
+
+ signal_stop();
+ return;
+ }
+
+ if (*linestart != '\0' && (*word == '/' || *word == '~')) {
+ /* quite likely filename completion */
+ *list = g_list_concat(*list, filename_complete(word, NULL));
+ if (*list != NULL) {
+ *want_space = FALSE;
+ signal_stop();
+ return;
+ }
+ }
+
+ /* command completion? */
+ cmdchars = settings_get_str("cmdchars");
+ if (*word != '\0' && ((*linestart == '\0' && strchr(cmdchars, *word)) ||
+ (*linestart != '\0' && linestart[1] == '\0' &&
+ strchr(cmdchars, *linestart)))) {
+ gboolean skip = *linestart == '\0' ? TRUE : FALSE;
+
+ /* complete /command */
+ *list = completion_get_commands(word + (skip ? 1 : 0),
+ skip ? *word : '\0');
+
+ /* complete aliases, too */
+ *list = g_list_concat(*list,
+ completion_get_aliases(word + (skip ? 1 : 0),
+ skip ? *word : '\0'));
+
+ if (*list != NULL) signal_stop();
+ return;
+ }
+
+ /* check only for /command completions from now on */
+ if (*linestart == '\0')
+ return;
+
+ cmdchars = strchr(cmdchars, *linestart);
+ if (cmdchars == NULL) return;
+
+ /* check if there's aliases */
+ line = linestart[1] == *cmdchars ? g_strdup(linestart+2) :
+ expand_aliases(linestart+1);
+
+ cmd = line_get_command(line, &args, FALSE);
+ if (cmd == NULL) {
+ g_free(line);
+ return;
+ }
+
+ /* we're completing -option? */
+ if (*word == '-') {
+ *list = completion_get_options(cmd, word+1);
+ if (*list != NULL) signal_stop();
+ g_free(cmd);
+ g_free(line);
+ return;
+ }
+
+ /* complete parameters */
+ signal = g_strconcat("complete command ", cmd, NULL);
+ signal_emit(signal, 5, list, window, word, args, want_space);
+
+ if (command_have_sub(line)) {
+ /* complete subcommand */
+ g_free(cmd);
+ cmd = g_strconcat(line, " ", word, NULL);
+ *list = g_list_concat(completion_get_subcommands(cmd), *list);
+ }
+
+ if (*list != NULL) signal_stop();
+ g_free(signal);
+ g_free(cmd);
+ g_free(line);
+}
+
+static void sig_complete_erase(WINDOW_REC *window, const char *word,
+ const char *linestart)
+{
+ const char *cmdchars;
+ char *line, *cmd, *args, *signal;
+
+ if (*linestart == '\0')
+ return;
+
+ /* we only want to check for commands */
+ cmdchars = settings_get_str("cmdchars");
+ cmdchars = strchr(cmdchars, *linestart);
+ if (cmdchars == NULL)
+ return;
+
+ /* check if there's aliases */
+ line = linestart[1] == *cmdchars ? g_strdup(linestart+2) :
+ expand_aliases(linestart+1);
+
+ cmd = line_get_command(line, &args, FALSE);
+ if (cmd == NULL) {
+ g_free(line);
+ return;
+ }
+
+ signal = g_strconcat("complete erase command ", cmd, NULL);
+ signal_emit(signal, 3, window, word, args);
+
+ g_free(signal);
+ g_free(cmd);
+ g_free(line);
+}
+
+static void sig_complete_set(GList **list, WINDOW_REC *window,
+ const char *word, const char *line, int *want_space)
+{
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+ g_return_if_fail(line != NULL);
+
+ if (*line == '\0' ||
+ !g_strcmp0("-clear", line) || !g_strcmp0("-default", line))
+ *list = completion_get_settings(word, SETTING_TYPE_ANY);
+ else if (*line != '\0' && *word == '\0') {
+ SETTINGS_REC *rec = settings_get_record(line);
+ if (rec != NULL) {
+ char *value = settings_get_print(rec);
+
+ /* show the current option first */
+ if (value != NULL)
+ *list = g_list_append(*list, value);
+
+ /* show the whole list of valid options */
+ if (rec->type == SETTING_TYPE_CHOICE) {
+ char **tmp;
+
+ for (tmp = rec->choices; *tmp; tmp++) {
+ if (g_ascii_strcasecmp(*tmp, value) != 0)
+ *list = g_list_append(*list, g_strdup(*tmp));
+ }
+ }
+ }
+ }
+
+ if (*list != NULL) signal_stop();
+}
+
+static void sig_complete_toggle(GList **list, WINDOW_REC *window,
+ const char *word, const char *line, int *want_space)
+{
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+ g_return_if_fail(line != NULL);
+
+ if (*line != '\0') return;
+
+ *list = completion_get_settings(word, SETTING_TYPE_BOOLEAN);
+ if (*list != NULL) signal_stop();
+}
+
+/* first argument of command is file name - complete it */
+static void sig_complete_filename(GList **list, WINDOW_REC *window,
+ const char *word, const char *line, int *want_space)
+{
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+ g_return_if_fail(line != NULL);
+
+ if (*line != '\0') return;
+
+ *list = filename_complete(word, NULL);
+ if (*list != NULL) {
+ *want_space = FALSE;
+ signal_stop();
+ }
+}
+
+/* first argument of command is .. command :) (/HELP command) */
+static void sig_complete_command(GList **list, WINDOW_REC *window,
+ const char *word, const char *line, int *want_space)
+{
+ char *cmd;
+
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+ g_return_if_fail(line != NULL);
+
+ if (*line == '\0') {
+ /* complete base command */
+ *list = completion_get_commands(word, '\0');
+ } else if (command_have_sub(line)) {
+ /* complete subcommand */
+ cmd = g_strconcat(line, " ", word, NULL);
+ *list = completion_get_subcommands(cmd);
+ g_free(cmd);
+ }
+
+ if (*list != NULL) signal_stop();
+}
+
+/* SYNTAX: COMPLETION [-auto] [-delete] <key> <value> */
+static void cmd_completion(const char *data)
+{
+ GHashTable *optlist;
+ CONFIG_NODE *node;
+ GSList *tmp;
+ char *key, *value;
+ void *free_arg;
+ int len;
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS |
+ PARAM_FLAG_GETREST,
+ "completion", &optlist, &key, &value))
+ return;
+
+ node = iconfig_node_traverse("completions", *value != '\0');
+ if (node != NULL && node->type != NODE_TYPE_BLOCK) {
+ /* FIXME: remove after 0.8.5 */
+ iconfig_node_remove(mainconfig->mainnode, node);
+ node = iconfig_node_traverse("completions", *value != '\0');
+ }
+
+ if (node == NULL || (node->value == NULL && *value == '\0')) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_NO_COMPLETIONS);
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ if (g_hash_table_lookup(optlist, "delete") != NULL && *key != '\0') {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_COMPLETION_REMOVED, key);
+
+ iconfig_set_str("completions", key, NULL);
+ signal_emit("completion removed", 1, key);
+ } else if (*key != '\0' && *value != '\0') {
+ int automatic = g_hash_table_lookup(optlist, "auto") != NULL;
+
+ node = iconfig_node_section(node, key, NODE_TYPE_BLOCK);
+ iconfig_node_set_str(node, "value", value);
+ if (automatic)
+ iconfig_node_set_bool(node, "auto", TRUE);
+ else
+ iconfig_node_set_str(node, "auto", NULL);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ TXT_COMPLETION_LINE,
+ key, value, automatic ? "yes" : "no");
+
+ signal_emit("completion added", 1, key);
+ } else {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ TXT_COMPLETION_HEADER);
+
+ len = strlen(key);
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+ node = tmp->data;
+
+ if (len == 0 ||
+ g_ascii_strncasecmp(node->key, key, len) == 0) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ TXT_COMPLETION_LINE, node->key,
+ config_node_get_str(node, "value", ""),
+ config_node_get_bool(node, "auto", FALSE) ? "yes" : "no");
+ }
+ }
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ TXT_COMPLETION_FOOTER);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+void completion_init(void)
+{
+ complist = NULL;
+ last_line = NULL; last_line_pos = -1;
+
+ chat_completion_init();
+
+ settings_add_bool("completion", "completion_keep_word", TRUE);
+ command_bind("completion", NULL, (SIGNAL_FUNC) cmd_completion);
+
+ signal_add_first("complete word", (SIGNAL_FUNC) sig_complete_word);
+ signal_add_first("complete erase", (SIGNAL_FUNC) sig_complete_erase);
+ signal_add("complete command set", (SIGNAL_FUNC) sig_complete_set);
+ signal_add("complete command toggle", (SIGNAL_FUNC) sig_complete_toggle);
+ signal_add("complete command load", (SIGNAL_FUNC) sig_complete_filename);
+ signal_add("complete command cat", (SIGNAL_FUNC) sig_complete_filename);
+ signal_add("complete command save", (SIGNAL_FUNC) sig_complete_filename);
+ signal_add("complete command reload", (SIGNAL_FUNC) sig_complete_filename);
+ signal_add("complete command rawlog open", (SIGNAL_FUNC) sig_complete_filename);
+ signal_add("complete command rawlog save", (SIGNAL_FUNC) sig_complete_filename);
+ signal_add("complete command help", (SIGNAL_FUNC) sig_complete_command);
+
+ command_set_options("completion", "auto delete");
+}
+
+void completion_deinit(void)
+{
+ free_completions();
+
+ chat_completion_deinit();
+
+ command_unbind("completion", (SIGNAL_FUNC) cmd_completion);
+
+ signal_remove("complete word", (SIGNAL_FUNC) sig_complete_word);
+ signal_remove("complete erase", (SIGNAL_FUNC) sig_complete_erase);
+ signal_remove("complete command set", (SIGNAL_FUNC) sig_complete_set);
+ signal_remove("complete command toggle", (SIGNAL_FUNC) sig_complete_toggle);
+ signal_remove("complete command load", (SIGNAL_FUNC) sig_complete_filename);
+ signal_remove("complete command cat", (SIGNAL_FUNC) sig_complete_filename);
+ signal_remove("complete command save", (SIGNAL_FUNC) sig_complete_filename);
+ signal_remove("complete command reload", (SIGNAL_FUNC) sig_complete_filename);
+ signal_remove("complete command rawlog open", (SIGNAL_FUNC) sig_complete_filename);
+ signal_remove("complete command rawlog save", (SIGNAL_FUNC) sig_complete_filename);
+ signal_remove("complete command help", (SIGNAL_FUNC) sig_complete_command);
+}
diff --git a/src/fe-common/core/completion.h b/src/fe-common/core/completion.h
new file mode 100644
index 0000000..d505aa8
--- /dev/null
+++ b/src/fe-common/core/completion.h
@@ -0,0 +1,18 @@
+#ifndef IRSSI_FE_COMMON_CORE_COMPLETION_H
+#define IRSSI_FE_COMMON_CORE_COMPLETION_H
+
+#include <irssi/src/fe-common/core/window-items.h>
+
+/* automatic word completion - called when space/enter is pressed */
+char *auto_word_complete(const char *line, int *pos);
+/* manual word completion - called when TAB is pressed. if erase is TRUE,
+ the word is removed from completion list entirely (if possible) and
+ next completion is used */
+char *word_complete(WINDOW_REC *window, const char *line, int *pos, int erase, int backward);
+
+GList *filename_complete(const char *path, const char *default_path);
+
+void completion_init(void);
+void completion_deinit(void);
+
+#endif
diff --git a/src/fe-common/core/fe-capsicum.c b/src/fe-common/core/fe-capsicum.c
new file mode 100644
index 0000000..d7ea95e
--- /dev/null
+++ b/src/fe-common/core/fe-capsicum.c
@@ -0,0 +1,63 @@
+/*
+ fe-capsicum.c : irssi
+
+ Copyright (C) 2017 Edward Tomasz Napierala <trasz@FreeBSD.org>
+
+ This software was developed by SRI International and the University of
+ Cambridge Computer Laboratory under DARPA/AFRL contract FA8750-10-C-0237
+ ("CTSRD"), as part of the DARPA CRASH research programme.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/core/fe-capsicum.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/fe-common/core/module-formats.h>
+#include <irssi/src/fe-common/core/printtext.h>
+#include <irssi/src/core/signals.h>
+
+static void capability_mode_enabled(void)
+{
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_CAPSICUM_ENABLED);
+}
+
+static void capability_mode_disabled(void)
+{
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_CAPSICUM_DISABLED);
+}
+
+static void capability_mode_failed(gchar *msg)
+{
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_CAPSICUM_FAILED, msg);
+}
+
+void fe_capsicum_init(void)
+{
+
+ signal_add("capability mode enabled", (SIGNAL_FUNC) capability_mode_enabled);
+ signal_add("capability mode disabled", (SIGNAL_FUNC) capability_mode_disabled);
+ signal_add("capability mode failed", (SIGNAL_FUNC) capability_mode_failed);
+}
+
+void fe_capsicum_deinit(void)
+{
+ signal_remove("capability mode enabled", (SIGNAL_FUNC) capability_mode_enabled);
+ signal_remove("capability mode disabled", (SIGNAL_FUNC) capability_mode_disabled);
+ signal_remove("capability mode failed", (SIGNAL_FUNC) capability_mode_failed);
+}
diff --git a/src/fe-common/core/fe-capsicum.h b/src/fe-common/core/fe-capsicum.h
new file mode 100644
index 0000000..c047129
--- /dev/null
+++ b/src/fe-common/core/fe-capsicum.h
@@ -0,0 +1,7 @@
+#ifndef IRSSI_FE_COMMON_CORE_FE_CAPSICUM_H
+#define IRSSI_FE_COMMON_CORE_FE_CAPSICUM_H
+
+void fe_capsicum_init(void);
+void fe_capsicum_deinit(void);
+
+#endif
diff --git a/src/fe-common/core/fe-channels.c b/src/fe-common/core/fe-channels.c
new file mode 100644
index 0000000..2d4e9ec
--- /dev/null
+++ b/src/fe-common/core/fe-channels.c
@@ -0,0 +1,674 @@
+/*
+ fe-channels.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/core/module-formats.h>
+#include <irssi/src/core/modules.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/special-vars.h>
+#include <irssi/src/core/utf8.h>
+
+#include <irssi/src/core/chat-protocols.h>
+#include <irssi/src/core/chatnets.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/channels.h>
+#include <irssi/src/core/channels-setup.h>
+#include <irssi/src/core/nicklist.h>
+
+#include <irssi/src/fe-common/core/fe-windows.h>
+#include <irssi/src/fe-common/core/fe-channels.h>
+#include <irssi/src/fe-common/core/window-items.h>
+#include <irssi/src/fe-common/core/printtext.h>
+
+static void signal_channel_created(CHANNEL_REC *channel, void *automatic)
+{
+ if (window_item_window(channel) == NULL) {
+ window_item_create((WI_ITEM_REC *) channel,
+ GPOINTER_TO_INT(automatic));
+ }
+}
+
+static void signal_channel_created_curwin(CHANNEL_REC *channel)
+{
+ g_return_if_fail(channel != NULL);
+
+ window_item_add(active_win, (WI_ITEM_REC *) channel, FALSE);
+}
+
+static void signal_channel_destroyed(CHANNEL_REC *channel)
+{
+ WINDOW_REC *window;
+
+ g_return_if_fail(channel != NULL);
+
+ window = window_item_window((WI_ITEM_REC *) channel);
+ if (window == NULL)
+ return;
+
+ window_item_destroy((WI_ITEM_REC *) channel);
+
+ if (channel->joined && !channel->left &&
+ !channel->server->disconnected) {
+ /* kicked out from channel */
+ window_bind_add(window, channel->server->tag,
+ channel->visible_name);
+ } else if (!channel->joined || channel->left)
+ window_auto_destroy(window);
+}
+
+static void sig_disconnected(SERVER_REC *server)
+{
+ WINDOW_REC *window;
+ GSList *tmp;
+
+ g_return_if_fail(IS_SERVER(server));
+
+ for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_REC *channel = tmp->data;
+
+ window = window_item_window((WI_ITEM_REC *) channel);
+ window_bind_add(window, server->tag, channel->name);
+ }
+}
+
+static void signal_window_item_changed(WINDOW_REC *window, WI_ITEM_REC *item)
+{
+ g_return_if_fail(window != NULL);
+ if (item == NULL) return;
+
+ if (g_slist_length(window->items) > 1 && IS_CHANNEL(item)) {
+ printformat(item->server, item->visible_name,
+ MSGLEVEL_CLIENTNOTICE,
+ TXT_TALKING_IN, item->visible_name);
+ signal_stop();
+ }
+}
+
+static void sig_channel_joined(CHANNEL_REC *channel)
+{
+ if (settings_get_bool("show_names_on_join") && !channel->session_rejoin) {
+ int limit = settings_get_int("show_names_on_join_limit");
+ int flags = CHANNEL_NICKLIST_FLAG_ALL;
+ if (limit > 0 && g_hash_table_size(channel->nicks) > limit) {
+ flags |= CHANNEL_NICKLIST_FLAG_COUNT;
+ }
+ fe_channels_nicklist(channel, flags);
+ }
+}
+
+/* SYNTAX: JOIN [-window] [-invite] [-<server tag>] <channels> [<keys>] */
+static void cmd_join(const char *data, SERVER_REC *server)
+{
+ WINDOW_REC *window;
+ CHANNEL_REC *channel;
+ GHashTable *optlist;
+ char *pdata;
+ int invite;
+ int samewindow;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |
+ PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST |
+ PARAM_FLAG_STRIP_TRAILING_WS,
+ "join", &optlist, &pdata))
+ return;
+
+ invite = g_hash_table_lookup(optlist, "invite") != NULL;
+ samewindow = g_hash_table_lookup(optlist, "window") != NULL;
+ if (!invite && *pdata == '\0')
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ /* -<server tag> */
+ server = cmd_options_get_server("join", optlist, server);
+
+ channel = channel_find(server, pdata);
+ if (channel != NULL) {
+ /* already joined to channel, set it active */
+ window = window_item_window(channel);
+ if (window != active_win)
+ window_set_active(window);
+
+ window_item_set_active(active_win, (WI_ITEM_REC *) channel);
+ }
+ else {
+ if (server == NULL || !server->connected)
+ cmd_param_error(CMDERR_NOT_CONNECTED);
+ if (invite) {
+ if (server->last_invite == NULL) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_NOT_INVITED);
+ signal_stop();
+ cmd_params_free(free_arg);
+ return;
+ }
+ pdata = server->last_invite;
+ }
+ if (samewindow)
+ signal_add("channel created",
+ (SIGNAL_FUNC) signal_channel_created_curwin);
+ server->channels_join(server, pdata, FALSE);
+ if (samewindow)
+ signal_remove("channel created",
+ (SIGNAL_FUNC) signal_channel_created_curwin);
+ }
+ cmd_params_free(free_arg);
+}
+
+static void cmd_channel_list_joined(void)
+{
+ CHANNEL_REC *channel;
+ GString *nicks;
+ GSList *nicklist, *tmp, *ntmp;
+
+ if (channels == NULL) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_NOT_IN_CHANNELS);
+ return;
+ }
+
+ /* print active channel */
+ channel = CHANNEL(active_win->active);
+ if (channel != NULL)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_CURRENT_CHANNEL, channel->visible_name);
+
+ /* print list of all channels, their modes, server tags and nicks */
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_CHANLIST_HEADER);
+ for (tmp = channels; tmp != NULL; tmp = tmp->next) {
+ channel = tmp->data;
+
+ nicklist = nicklist_getnicks(channel);
+ nicks = g_string_new(NULL);
+ for (ntmp = nicklist; ntmp != NULL; ntmp = ntmp->next) {
+ NICK_REC *rec = ntmp->data;
+
+ g_string_append_printf(nicks, "%s ", rec->nick);
+ }
+
+ if (nicks->len > 1) g_string_truncate(nicks, nicks->len-1);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_CHANLIST_LINE,
+ channel->visible_name, channel->mode,
+ channel->server->tag, nicks->str);
+
+ g_slist_free(nicklist);
+ g_string_free(nicks, TRUE);
+ }
+}
+
+/* SYNTAX: CHANNEL LIST */
+static void cmd_channel_list(void)
+{
+ GString *str;
+ GSList *tmp;
+
+ str = g_string_new(NULL);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_CHANSETUP_HEADER);
+ for (tmp = setupchannels; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_SETUP_REC *rec = tmp->data;
+
+ g_string_truncate(str, 0);
+ if (rec->autojoin)
+ g_string_append(str, "autojoin, ");
+ if (rec->botmasks != NULL && *rec->botmasks != '\0')
+ g_string_append_printf(str, "bots: %s, ", rec->botmasks);
+ if (rec->autosendcmd != NULL && *rec->autosendcmd != '\0')
+ g_string_append_printf(str, "botcmd: %s, ", rec->autosendcmd);
+
+ if (str->len > 2) g_string_truncate(str, str->len-2);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_CHANSETUP_LINE,
+ rec->name, rec->chatnet == NULL ? "" : rec->chatnet,
+ rec->password == NULL ? "" : rec->password, str->str);
+ }
+ g_string_free(str, TRUE);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_CHANSETUP_FOOTER);
+}
+
+static void cmd_channel(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ if (*data == '\0')
+ cmd_channel_list_joined();
+ else if (server != NULL && server_ischannel(server, data)) {
+ signal_emit("command join", 3, data, server, item);
+ } else {
+ command_runsub("channel", data, server, item);
+ }
+}
+
+static void cmd_channel_add_modify(const char *data, gboolean add)
+{
+ GHashTable *optlist;
+ CHATNET_REC *chatnetrec;
+ CHANNEL_SETUP_REC *rec;
+ char *botarg, *botcmdarg, *chatnet, *channel, *password;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_OPTIONS,
+ "channel add", &optlist, &channel, &chatnet, &password))
+ return;
+
+ if (*chatnet == '\0' || *channel == '\0') {
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+ }
+
+ chatnetrec = chatnet_find(chatnet);
+ if (chatnetrec == NULL) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_UNKNOWN_CHATNET, chatnet);
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ botarg = g_hash_table_lookup(optlist, "bots");
+ botcmdarg = g_hash_table_lookup(optlist, "botcmd");
+
+ rec = channel_setup_find(channel, chatnet);
+ if (rec == NULL) {
+ if (add == FALSE) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_CHANSETUP_NOT_FOUND, channel, chatnet);
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ rec = CHAT_PROTOCOL(chatnetrec)->create_channel_setup();
+ rec->name = g_strdup(channel);
+ rec->chatnet = g_strdup(chatnet);
+ } else {
+ if (g_hash_table_lookup(optlist, "bots")) g_free_and_null(rec->botmasks);
+ if (g_hash_table_lookup(optlist, "botcmd")) g_free_and_null(rec->autosendcmd);
+ if (*password != '\0') g_free_and_null(rec->password);
+ }
+ if (g_hash_table_lookup(optlist, "auto")) rec->autojoin = TRUE;
+ if (g_hash_table_lookup(optlist, "noauto")) rec->autojoin = FALSE;
+ if (botarg != NULL && *botarg != '\0') rec->botmasks = g_strdup(botarg);
+ if (botcmdarg != NULL && *botcmdarg != '\0') rec->autosendcmd = g_strdup(botcmdarg);
+ if (*password != '\0' && g_strcmp0(password, "-") != 0) rec->password = g_strdup(password);
+
+ signal_emit("channel add fill", 2, rec, optlist);
+
+ channel_setup_create(rec);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_CHANSETUP_ADDED, channel, chatnet);
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: CHANNEL ADD|MODIFY [-auto | -noauto] [-bots <masks>] [-botcmd <command>]
+ <channel> <network> [<password>] */
+static void cmd_channel_add(const char *data)
+{
+ cmd_channel_add_modify(data, TRUE);
+}
+
+static void cmd_channel_modify(const char *data)
+{
+ cmd_channel_add_modify(data, FALSE);
+}
+
+/* SYNTAX: CHANNEL REMOVE <channel> <network> */
+static void cmd_channel_remove(const char *data)
+{
+ CHANNEL_SETUP_REC *rec;
+ char *chatnet, *channel;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, 2, &channel, &chatnet))
+ return;
+ if (*chatnet == '\0' || *channel == '\0')
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ rec = channel_setup_find(channel, chatnet);
+ if (rec == NULL)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_CHANSETUP_NOT_FOUND, channel, chatnet);
+ else {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_CHANSETUP_REMOVED, channel, chatnet);
+ channel_setup_remove(rec);
+ }
+ cmd_params_free(free_arg);
+}
+
+static int get_nick_length(void *data)
+{
+ return string_width(((NICK_REC *) data)->nick, -1);
+}
+
+static void display_sorted_nicks(CHANNEL_REC *channel, GSList *nicklist)
+{
+ WINDOW_REC *window;
+ TEXT_DEST_REC dest;
+ GString *str;
+ GSList *tmp;
+ char *format, *stripped, *prefix_format;
+ char *aligned_nick, nickmode[2] = { 0, 0 };
+ int *columns, cols, rows, last_col_rows, col, row, max_width;
+ int item_extra, formatnum;
+
+ window = window_find_closest(channel->server, channel->visible_name,
+ MSGLEVEL_CLIENTCRAP);
+ max_width = window->width;
+
+ /* get the length of item extra stuff ("[ ] ") */
+ format = format_get_text(MODULE_NAME, NULL,
+ channel->server, channel->visible_name,
+ TXT_NAMES_NICK, " ", "");
+ stripped = strip_codes(format);
+ item_extra = strlen(stripped);
+ g_free(stripped);
+ g_free(format);
+
+ if (settings_get_int("names_max_width") > 0 &&
+ settings_get_int("names_max_width") < max_width)
+ max_width = settings_get_int("names_max_width");
+
+ /* remove width of the timestamp from max_width */
+ format_create_dest(&dest, channel->server, channel->visible_name,
+ MSGLEVEL_CLIENTCRAP, NULL);
+ format = format_get_line_start(current_theme, &dest, time(NULL));
+ if (format != NULL) {
+ stripped = strip_codes(format);
+ max_width -= strlen(stripped);
+ g_free(stripped);
+ g_free(format);
+ }
+
+ /* remove width of the prefix from max_width */
+ prefix_format = format_get_text(MODULE_NAME, NULL,
+ channel->server, channel->visible_name,
+ TXT_NAMES_PREFIX,
+ channel->visible_name);
+ if (prefix_format != NULL) {
+ stripped = strip_codes(prefix_format);
+ max_width -= strlen(stripped);
+ g_free(stripped);
+ }
+
+ if (max_width <= 0) {
+ /* we should always have at least some space .. if we
+ really don't, it won't show properly anyway. */
+ max_width = 10;
+ }
+
+ /* calculate columns */
+ cols = get_max_column_count(nicklist, get_nick_length, max_width,
+ settings_get_int("names_max_columns"),
+ item_extra, 3, &columns, &rows);
+ nicklist = columns_sort_list(nicklist, rows);
+
+ /* rows in last column */
+ last_col_rows = rows-(cols*rows-g_slist_length(nicklist));
+ if (last_col_rows == 0)
+ last_col_rows = rows;
+
+ str = g_string_new(prefix_format);
+
+ col = 0; row = 0;
+ for (tmp = nicklist; tmp != NULL; tmp = tmp->next) {
+ NICK_REC *rec = tmp->data;
+
+ if (rec->prefixes[0])
+ nickmode[0] = rec->prefixes[0];
+ else
+ nickmode[0] = ' ';
+
+ aligned_nick = get_alignment(rec->nick,
+ columns[col]-item_extra,
+ ALIGN_PAD, ' ');
+
+ formatnum = rec->op ? TXT_NAMES_NICK_OP :
+ rec->halfop ? TXT_NAMES_NICK_HALFOP :
+ rec->voice ? TXT_NAMES_NICK_VOICE :
+ TXT_NAMES_NICK;
+ format = format_get_text(MODULE_NAME, NULL,
+ channel->server,
+ channel->visible_name,
+ formatnum, nickmode, aligned_nick);
+ g_string_append(str, format);
+ g_free(aligned_nick);
+ g_free(format);
+
+ if (++col == cols) {
+ printtext(channel->server, channel->visible_name,
+ MSGLEVEL_CLIENTCRAP, "%s", str->str);
+ g_string_truncate(str, 0);
+ if (prefix_format != NULL)
+ g_string_assign(str, prefix_format);
+ col = 0; row++;
+
+ if (row == last_col_rows)
+ cols--;
+ }
+ }
+
+ if (prefix_format != NULL && str->len > strlen(prefix_format)) {
+ printtext(channel->server, channel->visible_name,
+ MSGLEVEL_CLIENTCRAP, "%s", str->str);
+ }
+
+ g_slist_free(nicklist);
+ g_string_free(str, TRUE);
+ g_free_not_null(columns);
+ g_free_not_null(prefix_format);
+}
+
+void fe_channels_nicklist(CHANNEL_REC *channel, int flags)
+{
+ NICK_REC *nick;
+ GSList *tmp, *nicklist, *sorted;
+ int nicks, normal, voices, halfops, ops;
+ const char *nick_flags;
+
+ nicks = normal = voices = halfops = ops = 0;
+ nicklist = nicklist_getnicks(channel);
+ sorted = NULL;
+ nick_flags = channel->server->get_nick_flags(channel->server);
+
+ /* filter (for flags) and count ops, halfops, voices */
+ for (tmp = nicklist; tmp != NULL; tmp = tmp->next) {
+ nick = tmp->data;
+
+ nicks++;
+ if (nick->op) {
+ ops++;
+ if ((flags & CHANNEL_NICKLIST_FLAG_OPS) == 0)
+ continue;
+ } else if (nick->halfop) {
+ halfops++;
+ if ((flags & CHANNEL_NICKLIST_FLAG_HALFOPS) == 0)
+ continue;
+ } else if (nick->voice) {
+ voices++;
+ if ((flags & CHANNEL_NICKLIST_FLAG_VOICES) == 0)
+ continue;
+ } else {
+ normal++;
+ if ((flags & CHANNEL_NICKLIST_FLAG_NORMAL) == 0)
+ continue;
+ }
+
+ sorted = g_slist_prepend(sorted, nick);
+ }
+ g_slist_free(nicklist);
+
+ /* sort the nicklist */
+ sorted = g_slist_sort_with_data(sorted, (GCompareDataFunc) nicklist_compare, (void *)nick_flags);
+
+ /* display the nicks */
+ if ((flags & CHANNEL_NICKLIST_FLAG_COUNT) == 0) {
+ printformat(channel->server, channel->visible_name,
+ MSGLEVEL_CLIENTCRAP, TXT_NAMES,
+ channel->visible_name,
+ nicks, ops, halfops, voices, normal);
+ display_sorted_nicks(channel, sorted);
+ }
+ g_slist_free(sorted);
+
+ printformat(channel->server, channel->visible_name,
+ MSGLEVEL_CLIENTNOTICE, TXT_ENDOFNAMES,
+ channel->visible_name, nicks, ops, halfops, voices, normal);
+}
+
+/* SYNTAX: NAMES [-count | -ops -halfops -voices -normal] [<channels> | **] */
+static void cmd_names(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ CHANNEL_REC *chanrec;
+ GHashTable *optlist;
+ GString *unknowns;
+ char *channel, **channels, **tmp;
+ int flags;
+ void *free_arg;
+
+ g_return_if_fail(data != NULL);
+ if (!IS_SERVER(server) || !server->connected)
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS,
+ "names", &optlist, &channel))
+ return;
+
+ if (g_strcmp0(channel, "*") == 0 || *channel == '\0') {
+ if (!IS_CHANNEL(item))
+ cmd_param_error(CMDERR_NOT_JOINED);
+
+ channel = CHANNEL(item)->name;
+ }
+
+ flags = 0;
+ if (g_hash_table_lookup(optlist, "ops") != NULL)
+ flags |= CHANNEL_NICKLIST_FLAG_OPS;
+ if (g_hash_table_lookup(optlist, "halfops") != NULL)
+ flags |= CHANNEL_NICKLIST_FLAG_HALFOPS;
+ if (g_hash_table_lookup(optlist, "voices") != NULL)
+ flags |= CHANNEL_NICKLIST_FLAG_VOICES;
+ if (g_hash_table_lookup(optlist, "normal") != NULL)
+ flags |= CHANNEL_NICKLIST_FLAG_NORMAL;
+ if (g_hash_table_lookup(optlist, "count") != NULL)
+ flags |= CHANNEL_NICKLIST_FLAG_COUNT;
+
+ if (flags == 0) flags = CHANNEL_NICKLIST_FLAG_ALL;
+
+ unknowns = g_string_new(NULL);
+
+ channels = g_strsplit(channel, ",", -1);
+ for (tmp = channels; *tmp != NULL; tmp++) {
+ chanrec = channel_find(server, *tmp);
+ if (chanrec == NULL)
+ g_string_append_printf(unknowns, "%s,", *tmp);
+ else {
+ fe_channels_nicklist(chanrec, flags);
+ signal_stop();
+ }
+ }
+ g_strfreev(channels);
+
+ if (unknowns->len > 1)
+ g_string_truncate(unknowns, unknowns->len-1);
+
+ if (unknowns->len > 0 && g_strcmp0(channel, unknowns->str) != 0)
+ signal_emit("command names", 3, unknowns->str, server, item);
+ g_string_free(unknowns, TRUE);
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: CYCLE [<channel>] [<message>] */
+static void cmd_cycle(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ CHANNEL_REC *chanrec;
+ char *channame, *msg, *joindata;
+ void *free_arg;
+
+ g_return_if_fail(data != NULL);
+ if (!IS_SERVER(server) || !server->connected)
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTCHAN,
+ item, &channame, &msg))
+ return;
+ if (*channame == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ chanrec = channel_find(server, channame);
+ if (chanrec == NULL) cmd_param_error(CMDERR_CHAN_NOT_FOUND);
+
+ joindata = chanrec->get_join_data(chanrec);
+ window_bind_add(window_item_window(chanrec),
+ chanrec->server->tag, chanrec->name);
+
+ /* FIXME: kludgy kludgy... */
+ signal_emit("command part", 3, data, server, item);
+
+ if (g_slist_find(channels, chanrec) != NULL) {
+ chanrec->left = TRUE;
+ channel_destroy(chanrec);
+ }
+
+ server->channels_join(server, joindata, FALSE);
+ g_free(joindata);
+
+ cmd_params_free(free_arg);
+}
+
+void fe_channels_init(void)
+{
+ settings_add_bool("lookandfeel", "autoclose_windows", TRUE);
+ settings_add_bool("lookandfeel", "show_names_on_join", TRUE);
+ settings_add_int("lookandfeel", "show_names_on_join_limit", 18);
+ settings_add_int("lookandfeel", "names_max_columns", 6);
+ settings_add_int("lookandfeel", "names_max_width", 0);
+
+ signal_add("channel created", (SIGNAL_FUNC) signal_channel_created);
+ signal_add("channel destroyed", (SIGNAL_FUNC) signal_channel_destroyed);
+ signal_add_last("window item changed", (SIGNAL_FUNC) signal_window_item_changed);
+ signal_add_last("server disconnected", (SIGNAL_FUNC) sig_disconnected);
+ signal_add_last("channel joined", (SIGNAL_FUNC) sig_channel_joined);
+
+ command_bind("join", NULL, (SIGNAL_FUNC) cmd_join);
+ command_bind("channel", NULL, (SIGNAL_FUNC) cmd_channel);
+ command_bind("channel add", NULL, (SIGNAL_FUNC) cmd_channel_add);
+ command_bind("channel modify", NULL, (SIGNAL_FUNC) cmd_channel_modify);
+ command_bind("channel remove", NULL, (SIGNAL_FUNC) cmd_channel_remove);
+ command_bind("channel list", NULL, (SIGNAL_FUNC) cmd_channel_list);
+ command_bind("names", NULL, (SIGNAL_FUNC) cmd_names);
+ command_bind("cycle", NULL, (SIGNAL_FUNC) cmd_cycle);
+
+ command_set_options("channel add", "auto noauto -bots -botcmd");
+ command_set_options("channel modify", "auto noauto -bots -botcmd");
+ command_set_options("names", "count ops halfops voices normal");
+ command_set_options("join", "invite window");
+}
+
+void fe_channels_deinit(void)
+{
+ signal_remove("channel created", (SIGNAL_FUNC) signal_channel_created);
+ signal_remove("channel destroyed", (SIGNAL_FUNC) signal_channel_destroyed);
+ signal_remove("window item changed", (SIGNAL_FUNC) signal_window_item_changed);
+ signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected);
+ signal_remove("channel joined", (SIGNAL_FUNC) sig_channel_joined);
+
+ command_unbind("join", (SIGNAL_FUNC) cmd_join);
+ command_unbind("channel", (SIGNAL_FUNC) cmd_channel);
+ command_unbind("channel add", (SIGNAL_FUNC) cmd_channel_add);
+ command_unbind("channel modify", (SIGNAL_FUNC) cmd_channel_modify);
+ command_unbind("channel remove", (SIGNAL_FUNC) cmd_channel_remove);
+ command_unbind("channel list", (SIGNAL_FUNC) cmd_channel_list);
+ command_unbind("names", (SIGNAL_FUNC) cmd_names);
+ command_unbind("cycle", (SIGNAL_FUNC) cmd_cycle);
+}
diff --git a/src/fe-common/core/fe-channels.h b/src/fe-common/core/fe-channels.h
new file mode 100644
index 0000000..34b24ac
--- /dev/null
+++ b/src/fe-common/core/fe-channels.h
@@ -0,0 +1,16 @@
+#ifndef IRSSI_FE_COMMON_CORE_FE_CHANNELS_H
+#define IRSSI_FE_COMMON_CORE_FE_CHANNELS_H
+
+#define CHANNEL_NICKLIST_FLAG_OPS 0x01
+#define CHANNEL_NICKLIST_FLAG_HALFOPS 0x02
+#define CHANNEL_NICKLIST_FLAG_VOICES 0x04
+#define CHANNEL_NICKLIST_FLAG_NORMAL 0x08
+#define CHANNEL_NICKLIST_FLAG_ALL 0x0f
+#define CHANNEL_NICKLIST_FLAG_COUNT 0x10
+
+void fe_channels_nicklist(CHANNEL_REC *channel, int flags);
+
+void fe_channels_init(void);
+void fe_channels_deinit(void);
+
+#endif
diff --git a/src/fe-common/core/fe-common-core.c b/src/fe-common/core/fe-common-core.c
new file mode 100644
index 0000000..9724354
--- /dev/null
+++ b/src/fe-common/core/fe-common-core.c
@@ -0,0 +1,584 @@
+/*
+ fe-common-core.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/core/module-formats.h>
+#include <irssi/src/core/args.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/channels.h>
+#include <irssi/src/core/servers-setup.h>
+
+#include <irssi/src/core/special-vars.h>
+#include <irssi/src/fe-common/core/fe-core-commands.h>
+#include <irssi/src/fe-common/core/fe-queries.h>
+#ifdef HAVE_CAPSICUM
+#include <irssi/src/fe-common/core/fe-capsicum.h>
+#endif
+#include <irssi/src/fe-common/core/hilight-text.h>
+#include <irssi/src/fe-common/core/command-history.h>
+#include <irssi/src/fe-common/core/completion.h>
+#include <irssi/src/fe-common/core/keyboard.h>
+#include <irssi/src/fe-common/core/printtext.h>
+#include <irssi/src/fe-common/core/formats.h>
+#include <irssi/src/fe-common/core/themes.h>
+#include <irssi/src/fe-common/core/fe-channels.h>
+#include <irssi/src/fe-common/core/fe-windows.h>
+#include <irssi/src/fe-common/core/window-activity.h>
+#include <irssi/src/fe-common/core/window-items.h>
+#include <irssi/src/fe-common/core/windows-layout.h>
+#include <irssi/src/fe-common/core/fe-recode.h>
+
+#include <signal.h>
+
+static char *autocon_server;
+static char *autocon_password;
+static int autocon_port;
+static int no_autoconnect;
+static char *cmdline_nick;
+static char *cmdline_hostname;
+GLogFunc logger_old;
+
+void fe_core_log_init(void);
+void fe_core_log_deinit(void);
+
+void fe_exec_init(void);
+void fe_exec_deinit(void);
+
+void fe_expandos_init(void);
+void fe_expandos_deinit(void);
+
+void fe_help_init(void);
+void fe_help_deinit(void);
+
+void fe_ignore_init(void);
+void fe_ignore_deinit(void);
+
+void fe_ignore_messages_init(void);
+void fe_ignore_messages_deinit(void);
+
+void fe_log_init(void);
+void fe_log_deinit(void);
+
+void fe_messages_init(void);
+void fe_messages_deinit(void);
+
+void fe_modules_init(void);
+void fe_modules_deinit(void);
+
+void fe_server_init(void);
+void fe_server_deinit(void);
+
+void fe_settings_init(void);
+void fe_settings_deinit(void);
+
+void fe_tls_init(void);
+void fe_tls_deinit(void);
+
+void window_commands_init(void);
+void window_commands_deinit(void);
+
+static void sig_setup_changed(void);
+
+static void sig_connected(SERVER_REC *server)
+{
+ MODULE_DATA_SET(server, g_new0(MODULE_SERVER_REC, 1));
+}
+
+static void sig_destroyed(SERVER_REC *server)
+{
+ void *data = MODULE_DATA(server);
+ g_free(data);
+ MODULE_DATA_UNSET(server);
+}
+
+static void sig_channel_created(CHANNEL_REC *channel)
+{
+ MODULE_DATA_SET(channel, g_new0(MODULE_CHANNEL_REC, 1));
+}
+
+static void sig_channel_destroyed(CHANNEL_REC *channel)
+{
+ void *data = MODULE_DATA(channel);
+
+ g_free(data);
+ MODULE_DATA_UNSET(channel);
+}
+
+void fe_common_core_register_options(void)
+{
+ static GOptionEntry options[] = {
+ { "connect", 'c', 0, G_OPTION_ARG_STRING, &autocon_server, "Automatically connect to server/network", "SERVER" },
+ { "password", 'w', 0, G_OPTION_ARG_STRING, &autocon_password, "Autoconnect password", "PASSWORD" },
+ { "port", 'p', 0, G_OPTION_ARG_INT, &autocon_port, "Autoconnect port", "PORT" },
+ { "noconnect", '!', 0, G_OPTION_ARG_NONE, &no_autoconnect, "Disable autoconnecting", NULL },
+ { "nick", 'n', 0, G_OPTION_ARG_STRING, &cmdline_nick, "Specify nick to use", NULL },
+ { "hostname", 'h', 0, G_OPTION_ARG_STRING, &cmdline_hostname, "Specify host name to use", NULL },
+ { NULL }
+ };
+
+ autocon_server = NULL;
+ autocon_password = NULL;
+ autocon_port = 0;
+ no_autoconnect = FALSE;
+ cmdline_nick = NULL;
+ cmdline_hostname = NULL;
+ args_register(options);
+}
+
+void fe_common_core_init(void)
+{
+ const char *str;
+
+ settings_add_bool("lookandfeel", "timestamps", TRUE);
+ settings_add_level("lookandfeel", "timestamp_level", "ALL");
+ settings_add_time("lookandfeel", "timestamp_timeout", "0");
+
+ settings_add_level("lookandfeel", "beep_msg_level", "");
+ settings_add_bool("lookandfeel", "beep_when_window_active", TRUE);
+ settings_add_bool("lookandfeel", "beep_when_away", TRUE);
+
+ settings_add_bool("lookandfeel", "hide_text_style", FALSE);
+ settings_add_bool("lookandfeel", "hide_colors", FALSE);
+ settings_add_bool("lookandfeel", "hide_server_tags", FALSE);
+
+ settings_add_bool("lookandfeel", "use_status_window", TRUE);
+ settings_add_bool("lookandfeel", "use_msgs_window", FALSE);
+ g_get_charset(&str);
+ settings_add_str("lookandfeel", "term_charset", str);
+ settings_add_str("lookandfeel", "glib_log_domains", "all");
+ themes_init();
+ theme_register(fecommon_core_formats);
+
+ command_history_init();
+ completion_init();
+ keyboard_init();
+ printtext_init();
+ formats_init();
+ fe_exec_init();
+ fe_expandos_init();
+ fe_help_init();
+ fe_ignore_init();
+ fe_log_init();
+ fe_modules_init();
+ fe_server_init();
+ fe_settings_init();
+ fe_tls_init();
+#ifdef HAVE_CAPSICUM
+ fe_capsicum_init();
+#endif
+ windows_init();
+ window_activity_init();
+ window_commands_init();
+ window_items_init();
+ windows_layout_init();
+ fe_core_commands_init();
+
+ fe_channels_init();
+ fe_queries_init();
+
+ fe_messages_init();
+ hilight_text_init();
+ fe_ignore_messages_init();
+ fe_recode_init();
+
+ settings_check();
+
+ signal_add_first("server connected", (SIGNAL_FUNC) sig_connected);
+ signal_add_last("server destroyed", (SIGNAL_FUNC) sig_destroyed);
+ signal_add_first("channel created", (SIGNAL_FUNC) sig_channel_created);
+ signal_add_last("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
+
+ module_register("core", "fe");
+}
+
+void fe_common_core_deinit(void)
+{
+ hilight_text_deinit();
+ command_history_deinit();
+ completion_deinit();
+ keyboard_deinit();
+ printtext_deinit();
+ formats_deinit();
+ fe_exec_deinit();
+ fe_expandos_deinit();
+ fe_help_deinit();
+ fe_ignore_deinit();
+ fe_log_deinit();
+ fe_modules_deinit();
+ fe_server_deinit();
+ fe_settings_deinit();
+ fe_tls_deinit();
+#ifdef HAVE_CAPSICUM
+ fe_capsicum_deinit();
+#endif
+ windows_deinit();
+ window_activity_deinit();
+ window_commands_deinit();
+ window_items_deinit();
+ windows_layout_deinit();
+ fe_core_commands_deinit();
+
+ fe_channels_deinit();
+ fe_queries_deinit();
+
+ fe_messages_deinit();
+ fe_ignore_messages_deinit();
+ fe_recode_deinit();
+
+ theme_unregister();
+ themes_deinit();
+
+ signal_remove("setup changed", (SIGNAL_FUNC) sig_setup_changed);
+ signal_remove("server connected", (SIGNAL_FUNC) sig_connected);
+ signal_remove("server destroyed", (SIGNAL_FUNC) sig_destroyed);
+ signal_remove("channel created", (SIGNAL_FUNC) sig_channel_created);
+ signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
+
+ g_log_set_default_handler(logger_old, NULL);
+}
+
+static gboolean glib_domain_wanted(const char *domain)
+{
+ const char *domains;
+ char *c, *cur;
+ int len = 0;
+ int print_it = 0; /* -1 for exclude, 0 for undecided, 1 for include */
+ int incl;
+
+ /* Go through each item in glib_log_domains setting to determine whether
+ * or not we want to print message from this domain */
+ domains = settings_get_str("glib_log_domains");
+ c = cur = (char *) domains;
+
+ do {
+ /* Advance through the string until we hit a space or the end */
+ while (*cur != '\0' && *cur != ' ') {
+ cur++;
+ len++;
+ }
+
+ /* Handle '-' prefix */
+ incl = 1;
+ if (*c == '-') {
+ incl = -1;
+ c++;
+ len--;
+ }
+
+ /* If we got a valid item, process it */
+ if (len > 0 && (!strncmp(domain, c, len) || !strncasecmp("all", c, len) ||
+ !strncmp("*", c, len)))
+ print_it = incl;
+
+ /* Go past any spaces towards the next item */
+ while (*cur == ' ')
+ cur++;
+
+ /* Move on beyond the item we just handled */
+ c = cur;
+ len = 0;
+ } while (*c != '\0' && print_it != -1);
+
+ return (print_it == 1);
+}
+
+static void i_log_func(const char *log_domain, GLogLevelFlags log_level, const char *message)
+{
+ const char *reason, *domain;
+
+ switch (log_level) {
+ case G_LOG_LEVEL_WARNING:
+ reason = "warning";
+ break;
+ case G_LOG_LEVEL_CRITICAL:
+ reason = "critical";
+ break;
+ case G_LOG_LEVEL_DEBUG:
+ reason = "debug";
+ break;
+ case G_LOG_LEVEL_MESSAGE:
+ reason = "message";
+ break;
+ case G_LOG_LEVEL_INFO:
+ reason = "info";
+ break;
+ default:
+ reason = "error";
+ break;
+ }
+
+ /* If log_domain parameter is NULL, GLib means to tell us that this is
+ * meant to be some nebulous "default" log domain name. */
+ domain = (log_domain ? log_domain : "default");
+
+ /* Only print the message if we decided to */
+ if (!glib_domain_wanted(domain))
+ return;
+
+ if (windows == NULL)
+ fprintf(stderr, "GLib (%s) %s: %s\n", domain, reason, message);
+ else {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_GLIB_ERROR, domain, reason,
+ message);
+ }
+}
+
+#define MSGS_WINDOW_LEVELS (MSGLEVEL_MSGS|MSGLEVEL_ACTIONS|MSGLEVEL_DCCMSGS)
+
+static void create_windows(void)
+{
+ WINDOW_REC *window;
+ int have_status = settings_get_bool("use_status_window");
+
+ window = window_find_name("(status)");
+ if (have_status) {
+ if (window == NULL) {
+ window = window_create(NULL, TRUE);
+ window_set_refnum(window, 1);
+ window_set_name(window, "(status)");
+ window_set_level(window, MSGLEVEL_ALL ^
+ (settings_get_bool("use_msgs_window") ?
+ MSGS_WINDOW_LEVELS : 0));
+ window_set_immortal(window, TRUE);
+ }
+ } else {
+ if (window != NULL) {
+ window_set_name(window, NULL);
+ window_set_level(window, 0);
+ window_set_immortal(window, FALSE);
+ }
+ }
+
+ window = window_find_name("(msgs)");
+ if (settings_get_bool("use_msgs_window")) {
+ if (window == NULL) {
+ window = window_create(NULL, TRUE);
+ window_set_refnum(window, have_status ? 2 : 1);
+ window_set_name(window, "(msgs)");
+ window_set_level(window, MSGS_WINDOW_LEVELS);
+ window_set_immortal(window, TRUE);
+ }
+ } else {
+ if (window != NULL) {
+ window_set_name(window, NULL);
+ window_set_level(window, 0);
+ window_set_immortal(window, FALSE);
+ }
+ }
+
+ if (windows == NULL) {
+ /* we have to have at least one window.. */
+ window = window_create(NULL, TRUE);
+ }
+}
+
+static void autoconnect_servers(void)
+{
+ GSList *tmp, *chatnets;
+ char *str;
+
+ if (autocon_server != NULL) {
+ /* connect to specified server */
+ if (autocon_password == NULL)
+ str = g_strdup_printf("%s %d", autocon_server, autocon_port);
+ else
+ str = g_strdup_printf("%s %d %s", autocon_server, autocon_port, autocon_password);
+
+ signal_emit("command connect", 1, str);
+ g_free(str);
+ return;
+ }
+
+ if (no_autoconnect) {
+ /* don't autoconnect */
+ return;
+ }
+
+ /* connect to autoconnect servers */
+ chatnets = NULL;
+ for (tmp = setupservers; tmp != NULL; tmp = tmp->next) {
+ SERVER_SETUP_REC *rec = tmp->data;
+
+ if (rec->autoconnect &&
+ (rec->chatnet == NULL ||
+ i_slist_find_icase_string(chatnets, rec->chatnet) == NULL)) {
+ if (rec->chatnet != NULL) {
+ chatnets = g_slist_append(chatnets, rec->chatnet);
+ str = g_strdup_printf("-network %s %s %d", rec->chatnet, rec->address, rec->port);
+ } else {
+ str = g_strdup_printf("%s %d", rec->address, rec->port);
+ }
+
+ signal_emit("command connect", 1, str);
+ g_free(str);
+ }
+ }
+
+ g_slist_free(chatnets);
+}
+
+static void sig_setup_changed(void)
+{
+ static int firsttime = TRUE;
+ static int status_window = FALSE, msgs_window = FALSE;
+ int changed = FALSE;
+
+ if (settings_get_bool("use_status_window") != status_window) {
+ status_window = !status_window;
+ changed = TRUE;
+ }
+ if (settings_get_bool("use_msgs_window") != msgs_window) {
+ msgs_window = !msgs_window;
+ changed = TRUE;
+ }
+
+ if (firsttime) {
+ firsttime = FALSE;
+ changed = TRUE;
+
+ windows_layout_restore();
+ if (windows != NULL)
+ return;
+ }
+
+ if (changed)
+ create_windows();
+}
+
+static void autorun_startup(void)
+{
+ char *path;
+ GIOChannel *handle;
+ GString *buf;
+ gsize tpos;
+
+ /* open ~/.irssi/startup and run all commands in it */
+ path = g_strdup_printf("%s/startup", get_irssi_dir());
+ handle = g_io_channel_new_file(path, "r", NULL);
+ g_free(path);
+ if (handle == NULL) {
+ /* file not found */
+ return;
+ }
+
+ g_io_channel_set_encoding(handle, NULL, NULL);
+ buf = g_string_sized_new(512);
+ while (g_io_channel_read_line_string(handle, buf, &tpos, NULL) == G_IO_STATUS_NORMAL) {
+ buf->str[tpos] = '\0';
+ if (buf->str[0] != '#') {
+ eval_special_string(buf->str, "",
+ active_win->active_server,
+ active_win->active);
+ }
+ }
+ g_string_free(buf, TRUE);
+
+ g_io_channel_unref(handle);
+}
+
+void fe_common_core_finish_init(void)
+{
+ int setup_changed;
+
+ signal_emit("irssi init read settings", 0);
+
+#ifdef SIGPIPE
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+ setup_changed = FALSE;
+ if (cmdline_nick != NULL && *cmdline_nick != '\0') {
+ /* override nick found from setup */
+ settings_set_str("nick", cmdline_nick);
+ setup_changed = TRUE;
+ }
+
+ if (cmdline_hostname != NULL) {
+ /* override host name found from setup */
+ settings_set_str("hostname", cmdline_hostname);
+ setup_changed = TRUE;
+ }
+
+ sig_setup_changed();
+ signal_add_first("setup changed", (SIGNAL_FUNC) sig_setup_changed);
+
+ /* _after_ windows are created.. */
+ logger_old = g_log_set_default_handler((GLogFunc) i_log_func, NULL);
+
+ if (setup_changed)
+ signal_emit("setup changed", 0);
+
+ autorun_startup();
+ signal_emit("module autoload", 0);
+ autoconnect_servers();
+}
+
+gboolean strarray_find_dest(char **array, const TEXT_DEST_REC *dest)
+{
+ WI_ITEM_REC *item;
+ int server_tag_len, channel_type, query_type;
+ char **tmp;
+
+ channel_type = module_get_uniq_id_str("WINDOW ITEM TYPE", "CHANNEL");
+ query_type = module_get_uniq_id_str("WINDOW ITEM TYPE", "QUERY");
+
+ g_return_val_if_fail(array != NULL, FALSE);
+ g_return_val_if_fail(dest != NULL, FALSE);
+ g_return_val_if_fail(dest->window != NULL, FALSE);
+
+ if (dest->target == NULL)
+ return dest->window->name != NULL &&
+ strarray_find(array, dest->window->name) != -1 ? TRUE : FALSE;
+
+ item = window_item_find_window(dest->window, dest->server, dest->target);
+
+ server_tag_len = dest->server_tag != NULL ? strlen(dest->server_tag) : 0;
+ for (tmp = array; *tmp != NULL; tmp++) {
+ char *str = *tmp;
+ if (*str == '\0') {
+ continue;
+ }
+
+ if (server_tag_len &&
+ g_ascii_strncasecmp(str, dest->server_tag, server_tag_len) == 0 &&
+ str[server_tag_len] == '/') {
+ str += server_tag_len + 1;
+ }
+
+ if (g_strcmp0(str, "*") == 0 || g_strcmp0(str, "::all") == 0) {
+ return TRUE;
+ } else if (g_ascii_strcasecmp(str, dest->target) == 0) {
+ return TRUE;
+ } else if (item != NULL && item->type == query_type &&
+ g_strcmp0(str, dest->target[0] == '=' ? "::dccqueries" :
+ "::queries") == 0) {
+ return TRUE;
+ } else if (item != NULL && item->type == channel_type &&
+ g_strcmp0(str, "::channels") == 0) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
diff --git a/src/fe-common/core/fe-common-core.h b/src/fe-common/core/fe-common-core.h
new file mode 100644
index 0000000..a9a74b0
--- /dev/null
+++ b/src/fe-common/core/fe-common-core.h
@@ -0,0 +1,13 @@
+#ifndef IRSSI_FE_COMMON_CORE_FE_COMMON_CORE_H
+#define IRSSI_FE_COMMON_CORE_FE_COMMON_CORE_H
+
+void fe_common_core_register_options(void);
+void fe_common_core_init(void);
+void fe_common_core_deinit(void);
+void fe_common_core_finish_init(void);
+
+/* Returns TRUE if "dest->target" or "dest->server_tag/dest->target" is found in
+ * array, otherwise FALSE. */
+gboolean strarray_find_dest(char **array, const TEXT_DEST_REC *dest);
+
+#endif
diff --git a/src/fe-common/core/fe-core-commands.c b/src/fe-common/core/fe-core-commands.c
new file mode 100644
index 0000000..207bcff
--- /dev/null
+++ b/src/fe-common/core/fe-core-commands.c
@@ -0,0 +1,376 @@
+/*
+ fe-core-commands.c : irssi
+
+ Copyright (C) 1999-2001 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include <irssi/src/core/core.h>
+#include "module.h"
+#include <irssi/src/fe-common/core/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/irssi-version.h>
+#include <irssi/src/core/servers.h>
+#ifdef HAVE_CAPSICUM
+#include <irssi/src/core/capsicum.h>
+#endif
+
+#include <irssi/src/fe-common/core/fe-windows.h>
+#include <irssi/src/fe-common/core/printtext.h>
+
+#define PASTE_CHECK_SPEED 200 /* 0.2 sec */
+
+static int ret_texts[] = {
+ TXT_OPTION_UNKNOWN,
+ TXT_OPTION_AMBIGUOUS,
+ TXT_OPTION_MISSING_ARG,
+ TXT_COMMAND_UNKNOWN,
+ TXT_COMMAND_AMBIGUOUS,
+ -1,
+ TXT_NOT_ENOUGH_PARAMS,
+ TXT_NOT_CONNECTED,
+ TXT_NOT_JOINED,
+ TXT_CHAN_NOT_FOUND,
+ TXT_CHAN_NOT_SYNCED,
+ TXT_ILLEGAL_PROTO,
+ TXT_NOT_GOOD_IDEA,
+ TXT_INVALID_TIME,
+ TXT_INVALID_CHARSET,
+ TXT_EVAL_MAX_RECURSE,
+ TXT_PROGRAM_NOT_FOUND,
+ TXT_NO_SERVER_DEFINED,
+};
+
+int command_hide_output;
+
+/* keep the whole command line here temporarily. we need it in
+ "default command" event handler, but there we don't know if the start of
+ the line had one or two command chars, and which one.. */
+static const char *current_cmdline;
+
+static gint64 time_command_last, time_command_now;
+static int last_command_cmd, command_cmd;
+
+/* SYNTAX: ECHO [-window <name>] [-level <level>] <text> */
+static void cmd_echo(const char *data, void *server, WI_ITEM_REC *item)
+{
+ WINDOW_REC *window;
+ GHashTable *optlist;
+ char *msg, *levelstr, *winname;
+ void *free_arg;
+ int level;
+
+ g_return_if_fail(data != NULL);
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |
+ PARAM_FLAG_GETREST, "echo", &optlist, &msg))
+ return;
+
+ levelstr = g_hash_table_lookup(optlist, "level");
+ level = levelstr == NULL ? 0 :
+ level2bits(g_hash_table_lookup(optlist, "level"), NULL);
+ if (level == 0) level = MSGLEVEL_CRAP;
+
+ winname = g_hash_table_lookup(optlist, "window");
+ window = winname == NULL ? NULL :
+ is_numeric(winname, '\0') ?
+ window_find_refnum(atoi(winname)) :
+ window_find_item(NULL, winname);
+ if (window == NULL) window = active_win;
+
+ printtext_window(window, level, "%s", msg);
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: VERSION */
+static void cmd_version(char *data)
+{
+ char time[10];
+
+ g_return_if_fail(data != NULL);
+
+ if (*data == '\0') {
+ g_snprintf(time, sizeof(time), "%04d", IRSSI_VERSION_TIME);
+ printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ "Client: "PACKAGE_TARNAME" " PACKAGE_VERSION" (%d %s)",
+ IRSSI_VERSION_DATE, time);
+ }
+}
+
+/* SYNTAX: CAT [-window] <file> [<seek position>] */
+static void cmd_cat(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ char *fname, *fposstr;
+ gboolean target;
+ GHashTable *optlist;
+ void *free_arg;
+ int fpos;
+ GIOChannel *handle;
+ GString *buf;
+ gsize tpos;
+#ifdef HAVE_CAPSICUM
+ int fd;
+#endif
+
+ g_return_if_fail(data != NULL);
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS,
+ "cat", &optlist, &fname, &fposstr))
+ return;
+
+ fname = convert_home(fname);
+ fpos = atoi(fposstr);
+
+#ifdef HAVE_CAPSICUM
+ fd = capsicum_open_wrapper(fname, O_RDONLY, 0);
+ if (fd > 0)
+ handle = g_io_channel_unix_new(fd);
+ else
+ handle = NULL;
+#else
+ handle = g_io_channel_new_file(fname, "r", NULL);
+#endif
+ g_free(fname);
+
+ if (handle == NULL) {
+ /* file not found */
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ "%s", g_strerror(errno));
+ return;
+ }
+
+ target = g_hash_table_lookup(optlist, "window") != NULL;
+
+ g_io_channel_set_encoding(handle, NULL, NULL);
+ g_io_channel_seek_position(handle, fpos, G_SEEK_SET, NULL);
+ buf = g_string_sized_new(512);
+ while (g_io_channel_read_line_string(handle, buf, &tpos, NULL) == G_IO_STATUS_NORMAL) {
+ buf->str[tpos] = '\0';
+ if (target)
+ printtext_window(active_win, MSGLEVEL_CLIENTCRAP | MSGLEVEL_NEVER, "%s",
+ buf->str);
+ else
+ printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP | MSGLEVEL_NEVER, "%s", buf->str);
+ }
+ g_string_free(buf, TRUE);
+ cmd_params_free(free_arg);
+
+ g_io_channel_unref(handle);
+}
+
+/* SYNTAX: BEEP */
+static void cmd_beep(void)
+{
+ signal_emit("beep", 0);
+}
+
+static void cmd_nick(const char *data, SERVER_REC *server)
+{
+ g_return_if_fail(data != NULL);
+
+ if (*data != '\0') return;
+ if (server == NULL || !server->connected)
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ /* display current nick */
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_YOUR_NICK, server->nick);
+ signal_stop();
+}
+
+/* SYNTAX: UPTIME */
+static void cmd_uptime(char *data)
+{
+ long uptime;
+
+ g_return_if_fail(data != NULL);
+
+ if (*data == '\0') {
+ uptime = (long)difftime(time(NULL), client_start_time);
+ printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ "Uptime: %ldd %ldh %ldm %lds",
+ uptime/3600/24, uptime/3600%24,
+ uptime/60%60, uptime%60);
+ }
+}
+
+static void sig_stop(void)
+{
+ signal_stop();
+}
+
+static void event_command(const char *data)
+{
+ const char *cmdchar;
+
+ /* save current command line */
+ current_cmdline = data;
+
+ /* for detecting if we're pasting text */
+ time_command_last = time_command_now;
+ last_command_cmd = command_cmd;
+
+ time_command_now = g_get_real_time();
+ command_cmd = *data != '\0' &&
+ strchr(settings_get_str("cmdchars"), *data) != NULL;
+
+ /* /^command hides the output of the command */
+ cmdchar = *data == '\0' ? NULL :
+ strchr(settings_get_str("cmdchars"), *data);
+ if (cmdchar != NULL && (data[1] == '^' ||
+ (data[1] == *cmdchar && data[2] == '^'))
+ && !command_hide_output++) {
+ signal_add_first("print starting", (SIGNAL_FUNC) sig_stop);
+ signal_add_first("print noformat", (SIGNAL_FUNC) sig_stop);
+ signal_add_first("print format", (SIGNAL_FUNC) sig_stop);
+ signal_add_first("print text", (SIGNAL_FUNC) sig_stop);
+ }
+}
+
+static void event_command_last(const char *data)
+{
+ if (command_hide_output && !--command_hide_output) {
+ signal_remove("print starting", (SIGNAL_FUNC) sig_stop);
+ signal_remove("print noformat", (SIGNAL_FUNC) sig_stop);
+ signal_remove("print format", (SIGNAL_FUNC) sig_stop);
+ signal_remove("print text", (SIGNAL_FUNC) sig_stop);
+ }
+}
+
+static void event_default_command(const char *data, void *server,
+ WI_ITEM_REC *item)
+{
+ const char *cmdchars, *ptr;
+ char *cmd, *p;
+ long diff;
+
+ cmdchars = settings_get_str("cmdchars");
+ signal_stop();
+
+ ptr = data;
+ while (*ptr != '\0' && *ptr != ' ') {
+ if (strchr(cmdchars, *ptr)) {
+ /* command character inside command .. we probably
+ want to send this text to channel. for example
+ when pasting a path /usr/bin/xxx. */
+ signal_emit("send text", 3, current_cmdline, server, item);
+ return;
+ }
+ ptr++;
+ }
+
+ /* maybe we're copy+pasting text? check how long it was since the
+ last line */
+ diff = time_command_now - time_command_last;
+ if (item != NULL && !last_command_cmd && diff < PASTE_CHECK_SPEED) {
+ signal_emit("send text", 3, current_cmdline, active_win->active_server, active_win->active);
+ command_cmd = FALSE;
+ return;
+ }
+
+ /* get the command part of the line, send "error command" signal */
+ cmd = g_strdup(data);
+ p = strchr(cmd, ' ');
+ if (p != NULL) *p = '\0';
+
+ signal_emit("error command", 2, GINT_TO_POINTER(CMDERR_UNKNOWN), cmd);
+
+ g_free(cmd);
+}
+
+static void event_cmderror(void *errorp, const char *arg)
+{
+ int error;
+
+ error = GPOINTER_TO_INT(errorp);
+ if (error == CMDERR_ERRNO) {
+ /* errno is special */
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "%s", g_strerror(errno));
+ } else {
+ /* others */
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, ret_texts[error + -CMDERR_OPTION_UNKNOWN], arg);
+ }
+}
+
+static void event_list_subcommands(const char *command)
+{
+ GSList *tmp;
+ GString *str;
+ int len;
+
+ str = g_string_new(NULL);
+
+ len = strlen(command);
+ for (tmp = commands; tmp != NULL; tmp = tmp->next) {
+ COMMAND_REC *rec = tmp->data;
+
+ if (g_ascii_strncasecmp(rec->cmd, command, len) == 0 &&
+ rec->cmd[len] == ' ' &&
+ strchr(rec->cmd+len+1, ' ') == NULL) {
+ g_string_append_printf(str, "%s ", rec->cmd+len+1);
+ }
+ }
+
+ if (str->len != 0) {
+ g_string_truncate(str, str->len-1);
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "%s", str->str);
+ }
+
+ g_string_free(str, TRUE);
+}
+
+void fe_core_commands_init(void)
+{
+ command_hide_output = 0;
+
+ command_cmd = FALSE;
+ time_command_now = 0;
+
+ command_bind("echo", NULL, (SIGNAL_FUNC) cmd_echo);
+ command_bind("version", NULL, (SIGNAL_FUNC) cmd_version);
+ command_bind("cat", NULL, (SIGNAL_FUNC) cmd_cat);
+ command_bind("beep", NULL, (SIGNAL_FUNC) cmd_beep);
+ command_bind("uptime", NULL, (SIGNAL_FUNC) cmd_uptime);
+ command_bind_first("nick", NULL, (SIGNAL_FUNC) cmd_nick);
+
+ signal_add("send command", (SIGNAL_FUNC) event_command);
+ signal_add_last("send command", (SIGNAL_FUNC) event_command_last);
+ signal_add_last("default command", (SIGNAL_FUNC) event_default_command);
+ signal_add("error command", (SIGNAL_FUNC) event_cmderror);
+ signal_add("list subcommands", (SIGNAL_FUNC) event_list_subcommands);
+
+ command_set_options("echo", "+level +window");
+ command_set_options("cat", "window");
+}
+
+void fe_core_commands_deinit(void)
+{
+ command_unbind("echo", (SIGNAL_FUNC) cmd_echo);
+ command_unbind("version", (SIGNAL_FUNC) cmd_version);
+ command_unbind("cat", (SIGNAL_FUNC) cmd_cat);
+ command_unbind("beep", (SIGNAL_FUNC) cmd_beep);
+ command_unbind("uptime", (SIGNAL_FUNC) cmd_uptime);
+ command_unbind("nick", (SIGNAL_FUNC) cmd_nick);
+
+ signal_remove("send command", (SIGNAL_FUNC) event_command);
+ signal_remove("send command", (SIGNAL_FUNC) event_command_last);
+ signal_remove("default command", (SIGNAL_FUNC) event_default_command);
+ signal_remove("error command", (SIGNAL_FUNC) event_cmderror);
+ signal_remove("list subcommands", (SIGNAL_FUNC) event_list_subcommands);
+}
diff --git a/src/fe-common/core/fe-core-commands.h b/src/fe-common/core/fe-core-commands.h
new file mode 100644
index 0000000..d711b5d
--- /dev/null
+++ b/src/fe-common/core/fe-core-commands.h
@@ -0,0 +1,9 @@
+#ifndef IRSSI_FE_COMMON_CORE_FE_CORE_COMMANDS_H
+#define IRSSI_FE_COMMON_CORE_FE_CORE_COMMANDS_H
+
+extern int command_hide_output;
+
+void fe_core_commands_init(void);
+void fe_core_commands_deinit(void);
+
+#endif
diff --git a/src/fe-common/core/fe-exec.c b/src/fe-common/core/fe-exec.c
new file mode 100644
index 0000000..0e929ba
--- /dev/null
+++ b/src/fe-common/core/fe-exec.c
@@ -0,0 +1,682 @@
+/*
+ fe-exec.c : irssi
+
+ Copyright (C) 2000-2001 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/modules.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/pidwait.h>
+#include <irssi/src/core/line-split.h>
+#include <irssi/src/core/network.h>
+#include <irssi/src/core/net-sendbuffer.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/levels.h>
+
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/channels.h>
+#include <irssi/src/core/queries.h>
+
+#include <irssi/src/fe-common/core/printtext.h>
+#include <irssi/src/fe-common/core/fe-exec.h>
+#include <irssi/src/fe-common/core/fe-windows.h>
+#include <irssi/src/fe-common/core/window-items.h>
+
+#include <signal.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+GSList *processes;
+static int signal_exec_input;
+
+static void exec_wi_destroy(EXEC_WI_REC *rec)
+{
+ g_return_if_fail(rec != NULL);
+
+ if (rec->destroying) return;
+ rec->destroying = TRUE;
+
+ rec->process->target_item = NULL;
+ if (window_item_window((WI_ITEM_REC *) rec) != NULL)
+ window_item_destroy((WI_ITEM_REC *) rec);
+
+ MODULE_DATA_DEINIT(rec);
+ g_free(rec->visible_name);
+ g_free(rec);
+}
+
+static const char *exec_get_target(WI_ITEM_REC *item)
+{
+ return ((EXEC_WI_REC *) item)->visible_name;
+}
+
+static EXEC_WI_REC *exec_wi_create(WINDOW_REC *window, PROCESS_REC *rec)
+{
+ EXEC_WI_REC *item;
+
+ g_return_val_if_fail(window != NULL, NULL);
+ g_return_val_if_fail(rec != NULL, NULL);
+
+ item = g_new0(EXEC_WI_REC, 1);
+ item->type = module_get_uniq_id_str("WINDOW ITEM TYPE", "EXEC");
+ item->destroy = (void (*) (WI_ITEM_REC *)) exec_wi_destroy;
+ item->get_target = exec_get_target;
+ item->visible_name = rec->name != NULL ? g_strdup(rec->name) :
+ g_strdup_printf("%%%d", rec->id);
+
+ item->createtime = time(NULL);
+ item->process = rec;
+
+ MODULE_DATA_INIT(item);
+ window_item_add(window, (WI_ITEM_REC *) item, FALSE);
+ return item;
+}
+
+static int process_get_new_id(void)
+{
+ PROCESS_REC *rec;
+ GSList *tmp;
+ int id;
+
+ id = 0;
+ tmp = processes;
+ while (tmp != NULL) {
+ rec = tmp->data;
+
+ if (id != rec->id) {
+ tmp = tmp->next;
+ continue;
+ }
+
+ id++;
+ tmp = processes;
+ }
+
+ return id;
+}
+
+static PROCESS_REC *process_find_pid(int pid)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(pid > 0, NULL);
+
+ for (tmp = processes; tmp != NULL; tmp = tmp->next) {
+ PROCESS_REC *rec = tmp->data;
+
+ if (rec->pid == pid)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static PROCESS_REC *process_find_id(int id, int verbose)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(id != -1, NULL);
+
+ for (tmp = processes; tmp != NULL; tmp = tmp->next) {
+ PROCESS_REC *rec = tmp->data;
+
+ if (rec->id == id)
+ return rec;
+ }
+
+ if (verbose) {
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ "Unknown process id: %d", id);
+ }
+
+ return NULL;
+}
+
+static PROCESS_REC *process_find(const char *name, int verbose)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(name != NULL, NULL);
+
+ if (*name == '%' && is_numeric(name+1, 0))
+ return process_find_id(atoi(name+1), verbose);
+
+ for (tmp = processes; tmp != NULL; tmp = tmp->next) {
+ PROCESS_REC *rec = tmp->data;
+
+ if (rec->name != NULL && g_strcmp0(rec->name, name) == 0)
+ return rec;
+ }
+
+ if (verbose) {
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ "Unknown process name: %s", name);
+ }
+
+ return NULL;
+}
+
+static void process_destroy(PROCESS_REC *rec, int status)
+{
+ processes = g_slist_remove(processes, rec);
+
+ signal_emit("exec remove", 2, rec, GINT_TO_POINTER(status));
+
+ if (rec->read_tag != -1)
+ g_source_remove(rec->read_tag);
+ if (rec->target_item != NULL)
+ exec_wi_destroy(rec->target_item);
+
+ line_split_free(rec->databuf);
+ g_io_channel_shutdown(rec->in, TRUE, NULL);
+ g_io_channel_unref(rec->in);
+ net_sendbuffer_destroy(rec->out, TRUE);
+
+ g_free_not_null(rec->name);
+ g_free_not_null(rec->target);
+ g_free_not_null(rec->target_server);
+ g_free(rec->args);
+ g_free(rec);
+}
+
+static void processes_killall(int signum)
+{
+ GSList *tmp;
+ int kill_ret;
+
+ for (tmp = processes; tmp != NULL; tmp = tmp->next) {
+ PROCESS_REC *rec = tmp->data;
+
+ kill_ret = kill(-rec->pid, signum);
+ if (kill_ret != 0)
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ "Error sending signal %d to pid %d: %s",
+ signum, rec->pid, g_strerror(errno));
+ }
+}
+
+static int signal_name_to_id(const char *name)
+{
+ /* check only the few most common signals, too much job to check
+ them all. if we sometimes want more, procps-sources/proc/sig.c
+ would be useful for copypasting */
+ if (g_ascii_strcasecmp(name, "hup") == 0)
+ return SIGHUP;
+ if (g_ascii_strcasecmp(name, "int") == 0)
+ return SIGINT;
+ if (g_ascii_strcasecmp(name, "term") == 0)
+ return SIGTERM;
+ if (g_ascii_strcasecmp(name, "kill") == 0)
+ return SIGKILL;
+ if (g_ascii_strcasecmp(name, "usr1") == 0)
+ return SIGUSR1;
+ if (g_ascii_strcasecmp(name, "usr2") == 0)
+ return SIGUSR2;
+ return -1;
+}
+
+/* `optlist' should contain only one unknown key - the server tag.
+ returns NULL if there was unknown -option */
+static int cmd_options_get_signal(const char *cmd,
+ GHashTable *optlist)
+{
+ GList *list;
+ char *signame;
+ int signum;
+
+ /* get all the options, then remove the known ones. there should
+ be only one left - the signal */
+ list = optlist_remove_known(cmd, optlist);
+
+ if (list == NULL)
+ return -1;
+
+ signame = list->data;
+ signum = -1;
+
+ signum = is_numeric(signame, 0) ? atol(signame) :
+ signal_name_to_id(signame);
+
+ if (signum == -1 || list->next != NULL) {
+ /* unknown option (not a signal) */
+ signal_emit("error command", 2,
+ GINT_TO_POINTER(CMDERR_OPTION_UNKNOWN),
+ signum == -1 ? list->data : list->next->data);
+ signal_stop();
+ return -2;
+ }
+
+ g_list_free(list);
+ return signum;
+}
+
+static void exec_show_list(void)
+{
+ GSList *tmp;
+
+ for (tmp = processes; tmp != NULL; tmp = tmp->next) {
+ PROCESS_REC *rec = tmp->data;
+
+ printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ "%d (%s): %s", rec->id, rec->name, rec->args);
+ }
+}
+
+static void process_exec(PROCESS_REC *rec, const char *cmd)
+{
+ const char *shell_args[4] = { FHS_PREFIX "/bin/sh", "-c", NULL, NULL };
+ char **args;
+ int in[2], out[2];
+ int n;
+
+ if (pipe(in) == -1)
+ return;
+ if (pipe(out) == -1)
+ return;
+
+ shell_args[2] = cmd;
+ rec->pid = fork();
+ if (rec->pid == -1) {
+ /* error */
+ close(in[0]); close(in[1]);
+ close(out[0]); close(out[1]);
+ return;
+ }
+
+ if (rec->pid != 0) {
+ /* parent process */
+ GIOChannel *outio = i_io_channel_new(in[1]);
+
+ rec->in = i_io_channel_new(out[0]);
+ rec->out = net_sendbuffer_create(outio, 0);
+
+ close(out[1]);
+ close(in[0]);
+ pidwait_add(rec->pid);
+ return;
+ }
+
+ /* child process, try to clean up everything */
+ setsid();
+
+#ifndef __ANDROID__
+ if (setuid(getuid()) != 0)
+ _exit(EXIT_FAILURE);
+
+ if (setgid(getgid()) != 0)
+ _exit(EXIT_FAILURE);
+#endif
+
+ signal(SIGINT, SIG_IGN);
+ signal(SIGQUIT, SIG_DFL);
+
+ putenv("TERM=tty");
+
+ /* set stdin, stdout and stderr */
+ dup2(in[0], STDIN_FILENO);
+ dup2(out[1], STDOUT_FILENO);
+ dup2(out[1], STDERR_FILENO);
+
+ /* don't let child see our files */
+ for (n = 3; n < 256; n++)
+ close(n);
+
+ if (rec->shell) {
+ execvp(shell_args[0], (char **) shell_args);
+
+ fprintf(stderr, "Exec: " FHS_PREFIX "/bin/sh: %s\n", g_strerror(errno));
+ } else {
+ args = g_strsplit(cmd, " ", -1);
+ execvp(args[0], args);
+
+ fprintf(stderr, "Exec: %s: %s\n", args[0], g_strerror(errno));
+ }
+
+ _exit(-1);
+}
+
+static void sig_exec_input_reader(PROCESS_REC *rec)
+{
+ char tmpbuf[512], *str;
+ int recvlen;
+ int ret;
+
+ g_return_if_fail(rec != NULL);
+
+ recvlen = net_receive(rec->in, tmpbuf, sizeof(tmpbuf));
+ do {
+ ret = line_split(tmpbuf, recvlen, &str, &rec->databuf);
+ if (ret == -1) {
+ /* link to terminal closed? */
+ g_source_remove(rec->read_tag);
+ rec->read_tag = -1;
+ break;
+ }
+
+ if (ret > 0) {
+ signal_emit_id(signal_exec_input, 2, rec, str);
+ if (recvlen > 0) recvlen = 0;
+ }
+ } while (ret > 0);
+}
+
+static void handle_exec(const char *args, GHashTable *optlist,
+ SERVER_REC *server, WI_ITEM_REC *item)
+{
+ PROCESS_REC *rec;
+ SERVER_REC *target_server;
+ char *target, *level;
+ int notice, signum, interactive, target_nick, target_channel, kill_ret;
+
+ /* check that there's no unknown options. we allowed them
+ because signals can be used as options, but there should be
+ only one unknown option: the signal name/number. */
+ signum = cmd_options_get_signal("exec", optlist);
+ if (signum == -2)
+ return;
+
+ if (*args == '\0') {
+ exec_show_list();
+ return;
+ }
+
+ target = NULL;
+ target_server = NULL;
+ notice = FALSE;
+
+ if (g_hash_table_lookup(optlist, "in") != NULL) {
+ rec = process_find(g_hash_table_lookup(optlist, "in"), TRUE);
+ if (rec != NULL) {
+ net_sendbuffer_send(rec->out, args, strlen(args));
+ net_sendbuffer_send(rec->out, "\n", 1);
+ }
+ return;
+ }
+
+ /* check if args is a process ID or name. if it's ID but not found,
+ complain about it and fail immediately */
+ rec = process_find(args, *args == '%');
+ if (*args == '%' && rec == NULL)
+ return;
+
+ /* common options */
+ target_channel = target_nick = FALSE;
+ if (g_hash_table_lookup(optlist, "out") != NULL) {
+ /* redirect output to active channel/query */
+ if (item == NULL)
+ cmd_return_error(CMDERR_NOT_JOINED);
+ target = (char *) window_item_get_target(item);
+ target_server = item->server;
+ target_channel = IS_CHANNEL(item);
+ target_nick = IS_QUERY(item);
+ } else if (g_hash_table_lookup(optlist, "msg") != NULL) {
+ /* redirect output to /msg <nick> */
+ target = g_hash_table_lookup(optlist, "msg");
+ target_server = server;
+ } else if (g_hash_table_lookup(optlist, "notice") != NULL) {
+ target = g_hash_table_lookup(optlist, "notice");
+ target_server = server;
+ notice = TRUE;
+ }
+
+ /* options that require process ID/name as argument */
+ if (rec == NULL &&
+ (signum != -1 || g_hash_table_lookup(optlist, "close") != NULL)) {
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ "Unknown process name: %s", args);
+ return;
+ }
+ if (g_hash_table_lookup(optlist, "close") != NULL) {
+ /* forcibly close the process */
+ process_destroy(rec, -1);
+ return;
+ }
+
+ if (signum != -1) {
+ /* send a signal to process group */
+ kill_ret = kill(-rec->pid, signum);
+ if (kill_ret != 0)
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ "Error sending signal %d to pid %d: %s",
+ signum, rec->pid, g_strerror(errno));
+ return;
+ }
+
+ interactive = g_hash_table_lookup(optlist, "interactive") != NULL;
+ if (*args == '%') {
+ /* do something to already existing process */
+ char *name;
+
+ if (target != NULL) {
+ /* redirect output to target */
+ g_free_and_null(rec->target);
+ rec->target = g_strdup(target);
+ rec->target_server = target_server == NULL ? NULL :
+ g_strdup(target_server->tag);
+ rec->notice = notice;
+ }
+
+ name = g_hash_table_lookup(optlist, "name");
+ if (name != NULL) {
+ /* change window name */
+ g_free_not_null(rec->name);
+ rec->name = *name == '\0' ? NULL : g_strdup(name);
+ } else if (target == NULL &&
+ (rec->target_item == NULL || interactive)) {
+ /* no parameters given,
+ redirect output to the active window */
+ g_free_and_null(rec->target);
+ rec->target_win = active_win;
+
+ if (rec->target_item != NULL)
+ exec_wi_destroy(rec->target_item);
+
+ if (interactive) {
+ rec->target_item =
+ exec_wi_create(active_win, rec);
+ }
+ }
+ return;
+ }
+
+ /* starting a new process */
+ rec = g_new0(PROCESS_REC, 1);
+ rec->pid = -1;
+ rec->shell = g_hash_table_lookup(optlist, "nosh") == NULL;
+
+ process_exec(rec, args);
+ if (rec->pid == -1) {
+ /* pipe() or fork() failed */
+ g_free(rec);
+ cmd_return_error(CMDERR_ERRNO);
+ }
+
+ rec->id = process_get_new_id();
+ rec->target = g_strdup(target);
+ rec->target_server = target_server == NULL ? NULL :
+ g_strdup(target_server->tag);
+ rec->target_win = active_win;
+ rec->target_channel = target_channel;
+ rec->target_nick = target_nick;
+ rec->args = g_strdup(args);
+ rec->notice = notice;
+ rec->silent = g_hash_table_lookup(optlist, "-") != NULL;
+ rec->quiet = g_hash_table_lookup(optlist, "quiet") != NULL;
+ rec->name = g_strdup(g_hash_table_lookup(optlist, "name"));
+
+ level = g_hash_table_lookup(optlist, "level");
+ rec->level = level == NULL ? MSGLEVEL_CLIENTCRAP : level2bits(level, NULL);
+
+ rec->read_tag =
+ i_input_add(rec->in, I_INPUT_READ, (GInputFunction) sig_exec_input_reader, rec);
+ processes = g_slist_append(processes, rec);
+
+ if (rec->target == NULL && interactive)
+ rec->target_item = exec_wi_create(active_win, rec);
+
+ signal_emit("exec new", 1, rec);
+}
+
+/* SYNTAX: EXEC [-] [-nosh] [-out | -msg <target> | -notice <target>]
+ [-name <name>] <cmd line>
+ EXEC -out | -window | -msg <target> | -notice <target> |
+ -close | -<signal> %<id>
+ EXEC -in %<id> <text to send to process> */
+static void cmd_exec(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ GHashTable *optlist;
+ char *args;
+ void *free_arg;
+
+ g_return_if_fail(data != NULL);
+
+ if (cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |
+ PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST,
+ "exec", &optlist, &args)) {
+ handle_exec(args, optlist, server, item);
+ cmd_params_free(free_arg);
+ }
+}
+
+static void sig_pidwait(void *pid, void *statusp)
+{
+ PROCESS_REC *rec;
+ char *str;
+ int status = GPOINTER_TO_INT(statusp);
+
+ rec = process_find_pid(GPOINTER_TO_INT(pid));
+ if (rec == NULL) return;
+
+ /* process exited - print the last line if
+ there wasn't a newline at end. */
+ if (line_split("\n", 1, &str, &rec->databuf) > 0 && *str != '\0')
+ signal_emit_id(signal_exec_input, 2, rec, str);
+
+ if (!rec->silent) {
+ if (WIFSIGNALED(status)) {
+ status = WTERMSIG(status);
+ printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ "process %d (%s) terminated with signal %d (%s)",
+ rec->id, rec->args,
+ status, g_strsignal(status));
+ } else {
+ status = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
+ printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ "process %d (%s) terminated with return code %d",
+ rec->id, rec->args, status);
+ }
+ }
+ process_destroy(rec, status);
+}
+
+static void sig_exec_input(PROCESS_REC *rec, const char *text)
+{
+ WI_ITEM_REC *item;
+ SERVER_REC *server;
+ char *str;
+
+ if (rec->quiet)
+ return;
+
+ item = NULL;
+ server = NULL;
+
+ if (rec->target != NULL) {
+ if (rec->target_server != NULL) {
+ server = server_find_tag(rec->target_server);
+ if (server == NULL) {
+ /* disconnected - target is lost */
+ return;
+ }
+ item = NULL;
+ } else {
+ item = window_item_find(NULL, rec->target);
+ server = item != NULL ? item->server :
+ active_win->active_server;
+ }
+
+ str = g_strconcat(rec->target_nick ? "-nick " :
+ rec->target_channel ? "-channel " : "",
+ rec->target, " ", *text == '\0' ? " " : text, NULL);
+ signal_emit(rec->notice ? "command notice" : "command msg",
+ 3, str, server, item);
+ g_free(str);
+ } else if (rec->target_item != NULL) {
+ printtext(NULL, rec->target_item->visible_name,
+ rec->level, "%s", text);
+ } else {
+ printtext_window(rec->target_win, rec->level, "%s", text);
+ }
+}
+
+static void sig_window_destroyed(WINDOW_REC *window)
+{
+ GSList *tmp;
+
+ /* window is being closed, if there's any /exec targets for it,
+ change them to active window. */
+ for (tmp = processes; tmp != NULL; tmp = tmp->next) {
+ PROCESS_REC *rec = tmp->data;
+
+ if (rec->target_win == window)
+ rec->target_win = active_win;
+ }
+}
+
+static void event_text(const char *data, SERVER_REC *server, EXEC_WI_REC *item)
+{
+ if (!IS_EXEC_WI(item))
+ return;
+
+ net_sendbuffer_send(item->process->out, data, strlen(data));
+ net_sendbuffer_send(item->process->out, "\n", 1);
+ signal_stop();
+}
+
+void fe_exec_init(void)
+{
+ command_bind("exec", NULL, (SIGNAL_FUNC) cmd_exec);
+ command_set_options("exec", "!- interactive nosh +name out +msg +notice +in window close +level quiet");
+
+ signal_exec_input = signal_get_uniq_id("exec input");
+ signal_add("pidwait", (SIGNAL_FUNC) sig_pidwait);
+ signal_add("exec input", (SIGNAL_FUNC) sig_exec_input);
+ signal_add("window destroyed", (SIGNAL_FUNC) sig_window_destroyed);
+ signal_add_first("send text", (SIGNAL_FUNC) event_text);
+}
+
+void fe_exec_deinit(void)
+{
+ if (processes != NULL) {
+ processes_killall(SIGTERM);
+ sleep(1);
+ processes_killall(SIGKILL);
+
+ while (processes != NULL)
+ process_destroy(processes->data, -1);
+ }
+
+ command_unbind("exec", (SIGNAL_FUNC) cmd_exec);
+
+ signal_remove("pidwait", (SIGNAL_FUNC) sig_pidwait);
+ signal_remove("exec input", (SIGNAL_FUNC) sig_exec_input);
+ signal_remove("window destroyed", (SIGNAL_FUNC) sig_window_destroyed);
+ signal_remove("send text", (SIGNAL_FUNC) event_text);
+}
diff --git a/src/fe-common/core/fe-exec.h b/src/fe-common/core/fe-exec.h
new file mode 100644
index 0000000..824d3d8
--- /dev/null
+++ b/src/fe-common/core/fe-exec.h
@@ -0,0 +1,52 @@
+#ifndef IRSSI_FE_COMMON_CORE_FE_EXEC_H
+#define IRSSI_FE_COMMON_CORE_FE_EXEC_H
+
+#include <irssi/src/fe-common/core/fe-windows.h>
+
+#define EXEC_WI(query) \
+ MODULE_CHECK_CAST_MODULE(query, EXEC_WI_REC, type, \
+ "WINDOW ITEM TYPE", "EXEC")
+
+#define IS_EXEC_WI(query) \
+ (EXEC_WI(query) ? TRUE : FALSE)
+
+typedef struct PROCESS_REC PROCESS_REC;
+
+#define STRUCT_SERVER_REC void
+typedef struct {
+#include <irssi/src/core/window-item-rec.h>
+ PROCESS_REC *process;
+ unsigned int destroying:1;
+} EXEC_WI_REC;
+
+struct PROCESS_REC {
+ int id;
+ char *name;
+ char *args;
+
+ int pid;
+ GIOChannel *in;
+ NET_SENDBUF_REC *out;
+ LINEBUF_REC *databuf;
+ int read_tag;
+
+ int level; /* what level to use when printing the text */
+ char *target; /* send text with /msg <target> ... */
+ char *target_server;
+ WINDOW_REC *target_win; /* print text to this window */
+ EXEC_WI_REC *target_item; /* print text to this exec window item */
+
+ unsigned int shell:1; /* start the program via /bin/sh */
+ unsigned int notice:1; /* send text with /notice, not /msg if target is set */
+ unsigned int silent:1; /* don't print "process exited with level xx" */
+ unsigned int quiet:1; /* don't print process output at all */
+ unsigned int target_channel:1; /* target is a channel */
+ unsigned int target_nick:1; /* target is a nick */
+};
+
+extern GSList *processes;
+
+void fe_exec_init(void);
+void fe_exec_deinit(void);
+
+#endif
diff --git a/src/fe-common/core/fe-expandos.c b/src/fe-common/core/fe-expandos.c
new file mode 100644
index 0000000..9a48b4b
--- /dev/null
+++ b/src/fe-common/core/fe-expandos.c
@@ -0,0 +1,58 @@
+/*
+ fe-expandos.c : irssi
+
+ Copyright (C) 2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/expandos.h>
+#include <irssi/src/fe-common/core/fe-windows.h>
+
+/* Window ref# */
+static char *expando_winref(SERVER_REC *server, void *item, int *free_ret)
+{
+ if (active_win == NULL)
+ return "";
+
+ *free_ret = TRUE;
+ return g_strdup_printf("%d", active_win->refnum);
+}
+
+/* Window name */
+static char *expando_winname(SERVER_REC *server, void *item, int *free_ret)
+{
+ if (active_win == NULL)
+ return "";
+
+ return active_win->name;
+}
+
+void fe_expandos_init(void)
+{
+ expando_create("winref", expando_winref,
+ "window changed", EXPANDO_ARG_NONE,
+ "window refnum changed", EXPANDO_ARG_WINDOW, NULL);
+ expando_create("winname", expando_winname,
+ "window changed", EXPANDO_ARG_NONE,
+ "window name changed", EXPANDO_ARG_WINDOW, NULL);
+}
+
+void fe_expandos_deinit(void)
+{
+ expando_destroy("winref", expando_winref);
+ expando_destroy("winname", expando_winname);
+}
diff --git a/src/fe-common/core/fe-help.c b/src/fe-common/core/fe-help.c
new file mode 100644
index 0000000..9c42806
--- /dev/null
+++ b/src/fe-common/core/fe-help.c
@@ -0,0 +1,273 @@
+/*
+ fe-help.c : irssi
+
+ Copyright (C) 1999-2001 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/fe-common/core/printtext.h>
+#include <irssi/src/fe-common/core/formats.h>
+
+static int commands_equal(COMMAND_REC *rec, COMMAND_REC *rec2)
+{
+ int i;
+
+ if (rec->category == NULL && rec2->category != NULL)
+ return -1;
+ if (rec2->category == NULL && rec->category != NULL)
+ return 1;
+ if (rec->category != NULL && rec2->category != NULL) {
+ i = g_strcmp0(rec->category, rec2->category);
+ if (i != 0)
+ return i;
+ }
+
+ return g_strcmp0(rec->cmd, rec2->cmd);
+}
+
+static int get_cmd_length(void *data)
+{
+ return strlen(((COMMAND_REC *) data)->cmd);
+}
+
+static void help_category(GSList *cmdlist, int items)
+{
+ WINDOW_REC *window;
+ TEXT_DEST_REC dest;
+ GString *str;
+ GSList *tmp;
+ int *columns, cols, rows, col, row, last_col_rows, max_width;
+ char *linebuf, *format, *stripped;
+
+ window = window_find_closest(NULL, NULL, MSGLEVEL_CLIENTCRAP);
+ max_width = window->width;
+
+ /* remove width of timestamp from max_width */
+ format_create_dest(&dest, NULL, NULL, MSGLEVEL_CLIENTCRAP, NULL);
+ format = format_get_line_start(current_theme, &dest, time(NULL));
+ if (format != NULL) {
+ stripped = strip_codes(format);
+ max_width -= strlen(stripped);
+ g_free(stripped);
+ g_free(format);
+ }
+
+ /* calculate columns */
+ cols = get_max_column_count(cmdlist, get_cmd_length,
+ max_width, 6, 1, 3, &columns, &rows);
+ cmdlist = columns_sort_list(cmdlist, rows);
+
+ /* if the screen is too narrow the window width may be not
+ enough for even 1 column */
+ if (cols == 1 && columns[0] > max_width)
+ max_width = columns[0];
+
+ /* rows in last column */
+ last_col_rows = rows-(cols*rows-g_slist_length(cmdlist));
+ if (last_col_rows == 0)
+ last_col_rows = rows;
+
+ str = g_string_new(NULL);
+ linebuf = g_malloc(max_width+1);
+
+ col = 0; row = 0;
+ for (tmp = cmdlist; tmp != NULL; tmp = tmp->next) {
+ COMMAND_REC *rec = tmp->data;
+
+ memset(linebuf, ' ', columns[col]);
+ linebuf[columns[col]] = '\0';
+ memcpy(linebuf, rec->cmd, strlen(rec->cmd));
+ g_string_append(str, linebuf);
+
+ if (++col == cols) {
+ printtext(NULL, NULL,
+ MSGLEVEL_CLIENTCRAP, "%s", str->str);
+ g_string_truncate(str, 0);
+ col = 0; row++;
+
+ if (row == last_col_rows)
+ cols--;
+ }
+ }
+ if (str->len != 0)
+ printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "%s", str->str);
+
+ g_slist_free(cmdlist);
+ g_string_free(str, TRUE);
+ g_free(columns);
+ g_free(linebuf);
+}
+
+static int show_help_file(const char *file)
+{
+ const char *helppath;
+ char *path, **paths, **tmp;
+ GIOChannel *handle;
+ GString *buf;
+ gsize tpos;
+
+ helppath = settings_get_str("help_path");
+
+ paths = g_strsplit(helppath, ":", -1);
+
+ handle = NULL;
+ for (tmp = paths; *tmp != NULL; tmp++) {
+ /* helpdir/command or helpdir/category/command */
+ path = g_strdup_printf("%s/%s", *tmp, file);
+ handle = g_io_channel_new_file(path, "r", NULL);
+ g_free(path);
+
+ if (handle != NULL)
+ break;
+
+ }
+
+ g_strfreev(paths);
+
+ if (handle == NULL)
+ return FALSE;
+
+ g_io_channel_set_encoding(handle, NULL, NULL);
+ buf = g_string_sized_new(512);
+ /* just print to screen whatever is in the file */
+ while (g_io_channel_read_line_string(handle, buf, &tpos, NULL) == G_IO_STATUS_NORMAL) {
+ buf->str[tpos] = '\0';
+ g_string_prepend(buf, "%|");
+ printtext_string(NULL, NULL, MSGLEVEL_CLIENTCRAP, buf->str);
+ }
+ g_string_free(buf, TRUE);
+
+ g_io_channel_unref(handle);
+ return TRUE;
+}
+
+static void show_help(const char *data)
+{
+ COMMAND_REC *rec, *last;
+ GSList *tmp, *cmdlist;
+ int items, findlen;
+ int header, found, fullmatch;
+
+ g_return_if_fail(data != NULL);
+
+ /* sort the commands list */
+ commands = g_slist_sort(commands, (GCompareFunc) commands_equal);
+
+ /* print command, sort by category */
+ cmdlist = NULL; last = NULL; header = FALSE; fullmatch = FALSE;
+ items = 0; findlen = strlen(data); found = FALSE;
+ for (tmp = commands; tmp != NULL; last = rec, tmp = tmp->next) {
+ rec = tmp->data;
+
+ if (last != NULL && rec->category != NULL &&
+ (last->category == NULL ||
+ g_strcmp0(rec->category, last->category) != 0)) {
+ /* category changed */
+ if (items > 0) {
+ if (!header) {
+ printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "Irssi commands:");
+ header = TRUE;
+ }
+ if (last->category != NULL) {
+ printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "");
+ printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "%s:", last->category);
+ }
+ help_category(cmdlist, items);
+ }
+
+ g_slist_free(cmdlist); cmdlist = NULL;
+ items = 0;
+ }
+
+ if (last != NULL && g_ascii_strcasecmp(rec->cmd, last->cmd) == 0)
+ continue; /* don't display same command twice */
+
+ if ((int)strlen(rec->cmd) >= findlen &&
+ g_ascii_strncasecmp(rec->cmd, data, findlen) == 0) {
+ if (rec->cmd[findlen] == '\0') {
+ fullmatch = TRUE;
+ found = TRUE;
+ break;
+ }
+ else if (strchr(rec->cmd+findlen+1, ' ') == NULL) {
+ /* not a subcommand (and matches the query) */
+ items++;
+ cmdlist = g_slist_append(cmdlist, rec);
+ found = TRUE;
+ }
+ }
+ }
+
+ if ((!found || fullmatch) && !show_help_file(data)) {
+ printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ "No help for %s", data);
+ }
+
+ if (*data != '\0' && data[strlen(data)-1] != ' ' &&
+ command_have_sub(data)) {
+ char *cmd;
+
+ cmd = g_strconcat(data, " ", NULL);
+ show_help(cmd);
+ g_free(cmd);
+ }
+
+ if (items != 0) {
+ /* display the last category */
+ if (!header) {
+ printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ "Irssi commands:");
+ header = TRUE;
+ }
+
+ if (last->category != NULL) {
+ printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "");
+ printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ "%s:", last->category);
+ }
+ help_category(cmdlist, items);
+ g_slist_free(cmdlist);
+ }
+}
+
+/* SYNTAX: HELP [<command>] */
+static void cmd_help(const char *data)
+{
+ char *cmd;
+
+ cmd = g_ascii_strdown(data, -1);
+ g_strchomp(cmd);
+ show_help(cmd);
+ g_free(cmd);
+}
+
+void fe_help_init(void)
+{
+ settings_add_str("misc", "help_path", HELPDIR);
+ command_bind("help", NULL, (SIGNAL_FUNC) cmd_help);
+}
+
+void fe_help_deinit(void)
+{
+ command_unbind("help", (SIGNAL_FUNC) cmd_help);
+}
diff --git a/src/fe-common/core/fe-ignore-messages.c b/src/fe-common/core/fe-ignore-messages.c
new file mode 100644
index 0000000..4f625de
--- /dev/null
+++ b/src/fe-common/core/fe-ignore-messages.c
@@ -0,0 +1,155 @@
+/*
+ fe-ignore-messages.c : irssi
+
+ Copyright (C) 2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/ignore.h>
+#include <irssi/src/core/servers.h>
+
+static void sig_message_public(SERVER_REC *server, const char *msg,
+ const char *nick, const char *address,
+ const char *target)
+{
+ if (ignore_check(server, nick, address, target, msg, MSGLEVEL_PUBLIC))
+ signal_stop();
+}
+
+static void sig_message_private(SERVER_REC *server, const char *msg,
+ const char *nick, const char *address)
+{
+ if (ignore_check(server, nick, address, NULL, msg, MSGLEVEL_MSGS))
+ signal_stop();
+}
+
+static void sig_message_join(SERVER_REC *server, const char *channel,
+ const char *nick, const char *address)
+{
+ if (ignore_check(server, nick, address, channel, NULL, MSGLEVEL_JOINS))
+ signal_stop();
+}
+
+static void sig_message_host_changed(SERVER_REC *server, const char *nick,
+ const char *address, const char *old_address)
+{
+ if (ignore_check(server, nick, address, NULL, NULL, MSGLEVEL_JOINS))
+ signal_stop();
+}
+
+static void sig_message_part(SERVER_REC *server, const char *channel,
+ const char *nick, const char *address,
+ const char *reason)
+{
+ if (ignore_check(server, nick, address, channel, NULL, MSGLEVEL_PARTS))
+ signal_stop();
+}
+
+static void sig_message_quit(SERVER_REC *server, const char *nick,
+ const char *address, const char *reason)
+{
+ if (ignore_check(server, nick, address, NULL, reason, MSGLEVEL_QUITS))
+ signal_stop();
+}
+
+static void sig_message_kick(SERVER_REC *server, const char *channel,
+ const char *nick, const char *kicker,
+ const char *address, const char *reason)
+{
+ /* never ignore if you were kicked */
+ if (g_ascii_strcasecmp(nick, server->nick) != 0 &&
+ ignore_check(server, kicker, address,
+ channel, reason, MSGLEVEL_KICKS))
+ signal_stop();
+}
+
+static void sig_message_nick(SERVER_REC *server, const char *newnick,
+ const char *oldnick, const char *address)
+{
+ if (ignore_check(server, oldnick, address,
+ NULL, NULL, MSGLEVEL_NICKS) ||
+ ignore_check(server, newnick, address,
+ NULL, NULL, MSGLEVEL_NICKS))
+ signal_stop();
+}
+
+static void sig_message_own_nick(SERVER_REC *server, const char *newnick,
+ const char *oldnick, const char *address)
+{
+ if (ignore_check(server, oldnick, address, NULL, NULL, MSGLEVEL_NICKS))
+ signal_stop();
+}
+
+static void sig_message_invite(SERVER_REC *server, const char *channel,
+ const char *nick, const char *address)
+{
+ if (*channel == '\0' ||
+ ignore_check(server, nick, address,
+ channel, NULL, MSGLEVEL_INVITES))
+ signal_stop();
+}
+
+static void sig_message_invite_other(SERVER_REC *server, const char *channel,
+ const char *invited, const char *nick, const char *address)
+{
+ if (ignore_check(server, nick, address,
+ channel, invited, MSGLEVEL_INVITES))
+ signal_stop();
+}
+
+static void sig_message_topic(SERVER_REC *server, const char *channel,
+ const char *topic,
+ const char *nick, const char *address)
+{
+ if (ignore_check(server, nick, address,
+ channel, topic, MSGLEVEL_TOPICS))
+ signal_stop();
+}
+
+void fe_ignore_messages_init(void)
+{
+ signal_add_first("message public", (SIGNAL_FUNC) sig_message_public);
+ signal_add_first("message private", (SIGNAL_FUNC) sig_message_private);
+ signal_add_first("message join", (SIGNAL_FUNC) sig_message_join);
+ signal_add_first("message host_changed", (SIGNAL_FUNC) sig_message_host_changed);
+ signal_add_first("message part", (SIGNAL_FUNC) sig_message_part);
+ signal_add_first("message quit", (SIGNAL_FUNC) sig_message_quit);
+ signal_add_first("message kick", (SIGNAL_FUNC) sig_message_kick);
+ signal_add_first("message nick", (SIGNAL_FUNC) sig_message_nick);
+ signal_add_first("message own_nick", (SIGNAL_FUNC) sig_message_own_nick);
+ signal_add_first("message invite", (SIGNAL_FUNC) sig_message_invite);
+ signal_add_first("message invite_other", (SIGNAL_FUNC) sig_message_invite_other);
+ signal_add_first("message topic", (SIGNAL_FUNC) sig_message_topic);
+}
+
+void fe_ignore_messages_deinit(void)
+{
+ signal_remove("message public", (SIGNAL_FUNC) sig_message_public);
+ signal_remove("message private", (SIGNAL_FUNC) sig_message_private);
+ signal_remove("message join", (SIGNAL_FUNC) sig_message_join);
+ signal_remove("message host_changed", (SIGNAL_FUNC) sig_message_host_changed);
+ signal_remove("message part", (SIGNAL_FUNC) sig_message_part);
+ signal_remove("message quit", (SIGNAL_FUNC) sig_message_quit);
+ signal_remove("message kick", (SIGNAL_FUNC) sig_message_kick);
+ signal_remove("message nick", (SIGNAL_FUNC) sig_message_nick);
+ signal_remove("message own_nick", (SIGNAL_FUNC) sig_message_own_nick);
+ signal_remove("message invite", (SIGNAL_FUNC) sig_message_invite);
+ signal_remove("message invite_other", (SIGNAL_FUNC) sig_message_invite_other);
+ signal_remove("message topic", (SIGNAL_FUNC) sig_message_topic);
+}
diff --git a/src/fe-common/core/fe-ignore.c b/src/fe-common/core/fe-ignore.c
new file mode 100644
index 0000000..3594b6f
--- /dev/null
+++ b/src/fe-common/core/fe-ignore.c
@@ -0,0 +1,319 @@
+/*
+ fe-ignore.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <time.h>
+#include <irssi/src/fe-common/core/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/misc.h>
+
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/ignore.h>
+#include <irssi/src/fe-common/core/printtext.h>
+
+static char *ignore_get_key(IGNORE_REC *rec)
+{
+ char *chans, *ret;
+
+ if (rec->channels == NULL)
+ return g_strdup(rec->mask != NULL ? rec->mask : "*" );
+
+ chans = g_strjoinv(",", rec->channels);
+ if (rec->mask == NULL) return chans;
+
+ ret = g_strdup_printf("%s %s", rec->mask, chans);
+ g_free(chans);
+ return ret;
+}
+
+static void ignore_print(int index, IGNORE_REC *rec)
+{
+ GString *options;
+ char *key, *levels;
+ struct tm ts;
+ char buf[20];
+
+ key = ignore_get_key(rec);
+ levels = bits2level(rec->level);
+
+ options = g_string_new(NULL);
+ if (rec->exception) g_string_append(options, "-except ");
+ if (rec->regexp) {
+ g_string_append(options, "-regexp ");
+ if (rec->pattern == NULL)
+ g_string_append(options, "[INVALID! -pattern missing] ");
+ else if (rec->preg == NULL)
+ g_string_append(options, "[INVALID!] ");
+ }
+ if (rec->fullword) g_string_append(options, "-full ");
+ if (rec->replies) g_string_append(options, "-replies ");
+ if (rec->servertag != NULL)
+ g_string_append_printf(options, "-network %s ", rec->servertag);
+ if (rec->pattern != NULL)
+ g_string_append_printf(options, "-pattern %s ", rec->pattern);
+ if (rec->unignore_time != 0) {
+ ts = *localtime(&rec->unignore_time);
+ strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &ts);
+ g_string_append_printf(options, "ignore ends: %s ", buf);
+ }
+
+ if (options->len > 1) g_string_truncate(options, options->len-1);
+
+ if (index >= 0) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ TXT_IGNORE_LINE, index, key != NULL ? key : "",
+ levels != NULL ? levels : "", options->str);
+ } else {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ options->len > 0 ? TXT_IGNORED_OPTIONS : TXT_IGNORED,
+ key != NULL ? key : "",
+ levels != NULL ? levels : "", options->str);
+ }
+ g_string_free(options, TRUE);
+ g_free(key);
+ g_free(levels);
+}
+
+static void cmd_ignore_show(void)
+{
+ GSList *tmp;
+ int index;
+
+ if (ignores == NULL) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ TXT_IGNORE_NO_IGNORES);
+ return;
+ }
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_IGNORE_HEADER);
+ index = 1;
+ for (tmp = ignores; tmp != NULL; tmp = tmp->next, index++) {
+ IGNORE_REC *rec = tmp->data;
+
+ ignore_print(index, rec);
+ }
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_IGNORE_FOOTER);
+}
+
+/* SYNTAX: IGNORE [-regexp | -full] [-pattern <pattern>] [-except] [-replies]
+ [-network <network>] [-channels <channel>] [-time <time>] <mask> [<levels>]
+ IGNORE [-regexp | -full] [-pattern <pattern>] [-except] [-replies]
+ [-network <network>] [-time <time>] <channels> [<levels>] */
+/* NOTE: -network replaces the old -ircnet flag. */
+static void cmd_ignore(const char *data)
+{
+ GHashTable *optlist;
+ IGNORE_REC *rec;
+ char *patternarg, *chanarg, *mask, *levels, *timestr, *servertag;
+ char **channels;
+ void *free_arg;
+ int new_ignore, msecs, level, flags;
+
+ if (*data == '\0') {
+ cmd_ignore_show();
+ return;
+ }
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS |
+ PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS,
+ "ignore", &optlist, &mask, &levels))
+ return;
+
+ patternarg = g_hash_table_lookup(optlist, "pattern");
+ chanarg = g_hash_table_lookup(optlist, "channels");
+ servertag = g_hash_table_lookup(optlist, "network");
+ /* Allow -ircnet for backwards compatibility */
+ if (!servertag)
+ servertag = g_hash_table_lookup(optlist, "ircnet");
+
+ if (*mask == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+ if (*levels == '\0') levels = "ALL";
+ level = level2bits(levels, NULL);
+
+ msecs = 0;
+ timestr = g_hash_table_lookup(optlist, "time");
+ if (timestr != NULL) {
+ if (!parse_time_interval(timestr, &msecs))
+ cmd_param_error(CMDERR_INVALID_TIME);
+ }
+
+ if (active_win->active_server != NULL &&
+ server_ischannel(active_win->active_server, mask)) {
+ chanarg = mask;
+ mask = NULL;
+ }
+ channels = (chanarg == NULL || *chanarg == '\0') ? NULL :
+ g_strsplit(chanarg, ",", -1);
+
+ flags = IGNORE_FIND_PATTERN;
+ if (level & MSGLEVEL_NO_ACT)
+ flags |= IGNORE_FIND_NOACT;
+ if (level & MSGLEVEL_HIDDEN)
+ flags |= IGNORE_FIND_HIDDEN;
+ if (level & MSGLEVEL_NOHILIGHT)
+ flags |= IGNORE_FIND_NOHILIGHT;
+
+ rec = ignore_find_full(servertag, mask, patternarg, channels, flags);
+ new_ignore = rec == NULL;
+
+ if (rec == NULL) {
+ rec = g_new0(IGNORE_REC, 1);
+
+ rec->mask = mask == NULL || *mask == '\0' ||
+ g_strcmp0(mask, "*") == 0 ? NULL : g_strdup(mask);
+ rec->channels = channels;
+ } else {
+ g_free_and_null(rec->pattern);
+ g_strfreev(channels);
+ }
+
+ rec->level = combine_level(rec->level, levels);
+
+ if (rec->level == MSGLEVEL_NO_ACT) {
+ /* If only NO_ACT was specified add all levels; it makes no
+ * sense on its own. */
+ rec->level |= MSGLEVEL_ALL;
+ }
+
+ if (rec->level == MSGLEVEL_HIDDEN) {
+ /* If only HIDDEN was specified add all levels; it makes no
+ * sense on its own. */
+ rec->level |= MSGLEVEL_ALL;
+ }
+
+ if (rec->level == MSGLEVEL_NOHILIGHT) {
+ /* If only NOHILIGHT was specified add all levels; it makes no
+ * sense on its own. */
+ rec->level |= MSGLEVEL_ALL;
+ }
+
+ if (new_ignore && rec->level == 0) {
+ /* tried to unignore levels from nonexisting ignore */
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_IGNORE_NOT_FOUND, rec->mask);
+ g_free(rec->mask);
+ g_strfreev(rec->channels);
+ g_free(rec);
+ cmd_params_free(free_arg);
+ return;
+ }
+ rec->servertag = (servertag == NULL || *servertag == '\0') ?
+ NULL : g_strdup(servertag);
+ rec->pattern = (patternarg == NULL || *patternarg == '\0') ?
+ NULL : g_strdup(patternarg);
+ rec->exception = g_hash_table_lookup(optlist, "except") != NULL;
+ rec->regexp = g_hash_table_lookup(optlist, "regexp") != NULL;
+ rec->fullword = g_hash_table_lookup(optlist, "full") != NULL;
+ rec->replies = g_hash_table_lookup(optlist, "replies") != NULL;
+ if (msecs != 0)
+ rec->unignore_time = time(NULL)+msecs/1000;
+
+ if (new_ignore)
+ ignore_add_rec(rec);
+ else
+ ignore_update_rec(rec);
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: UNIGNORE <id>|<mask> */
+static void cmd_unignore(const char *data)
+{
+ IGNORE_REC *rec;
+ GSList *tmp;
+ char *mask, *mask_orig;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, 1, &mask))
+ return;
+
+ if (*mask == '\0')
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ /* Save the mask string here since it might be modified in the code
+ * below and we need it to print meaningful error messages. */
+ mask_orig = mask;
+
+ if (is_numeric(mask, ' ')) {
+ /* with index number */
+ tmp = g_slist_nth(ignores, atoi(mask)-1);
+ rec = tmp == NULL ? NULL : tmp->data;
+ } else {
+ /* with mask */
+ const char *chans[2] = { "*", NULL };
+
+ if (active_win->active_server != NULL &&
+ server_ischannel(active_win->active_server, mask)) {
+ chans[0] = mask;
+ mask = NULL;
+ }
+ rec = ignore_find_full("*", mask, NULL, (char **) chans, 0);
+ if (rec == NULL) {
+ rec = ignore_find_full("*", mask, NULL, (char **) chans, IGNORE_FIND_NOACT);
+ }
+ }
+
+ if (rec != NULL) {
+ rec->level = 0;
+ ignore_update_rec(rec);
+ } else {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_IGNORE_NOT_FOUND, mask_orig);
+ }
+ cmd_params_free(free_arg);
+}
+
+static void sig_ignore_created(IGNORE_REC *rec)
+{
+ ignore_print(-1, rec);
+}
+
+static void sig_ignore_destroyed(IGNORE_REC *rec)
+{
+ char *key;
+
+ key = ignore_get_key(rec);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_UNIGNORED, key);
+ g_free(key);
+}
+
+void fe_ignore_init(void)
+{
+ command_bind("ignore", NULL, (SIGNAL_FUNC) cmd_ignore);
+ command_bind("unignore", NULL, (SIGNAL_FUNC) cmd_unignore);
+
+ signal_add("ignore destroyed", (SIGNAL_FUNC) sig_ignore_destroyed);
+ signal_add("ignore created", (SIGNAL_FUNC) sig_ignore_created);
+ signal_add("ignore changed", (SIGNAL_FUNC) sig_ignore_created);
+
+ command_set_options("ignore", "regexp full except replies -network -ircnet -time -pattern -channels");
+}
+
+void fe_ignore_deinit(void)
+{
+ command_unbind("ignore", (SIGNAL_FUNC) cmd_ignore);
+ command_unbind("unignore", (SIGNAL_FUNC) cmd_unignore);
+
+ signal_remove("ignore destroyed", (SIGNAL_FUNC) sig_ignore_destroyed);
+ signal_remove("ignore created", (SIGNAL_FUNC) sig_ignore_created);
+ signal_remove("ignore changed", (SIGNAL_FUNC) sig_ignore_created);
+}
diff --git a/src/fe-common/core/fe-log.c b/src/fe-common/core/fe-log.c
new file mode 100644
index 0000000..164036c
--- /dev/null
+++ b/src/fe-common/core/fe-log.c
@@ -0,0 +1,817 @@
+/*
+ fe-log.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/core/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/chat-protocols.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/channels.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/log.h>
+#include <irssi/src/core/special-vars.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/lib-config/iconfig.h>
+#ifdef HAVE_CAPSICUM
+#include <irssi/src/core/capsicum.h>
+#endif
+
+#include <irssi/src/fe-common/core/fe-windows.h>
+#include <irssi/src/fe-common/core/window-items.h>
+#include <irssi/src/fe-common/core/formats.h>
+#include <irssi/src/fe-common/core/themes.h>
+#include <irssi/src/fe-common/core/printtext.h>
+#include <irssi/src/fe-common/core/fe-common-core.h>
+
+#include <irssi/src/core/channels-setup.h>
+
+/* close autologs after 5 minutes of inactivity */
+#define AUTOLOG_INACTIVITY_CLOSE (60*5)
+
+static int autolog_level;
+static int log_server_time;
+static int autoremove_tag;
+static char *autolog_path;
+
+static THEME_REC *log_theme;
+static int skip_next_printtext;
+static char *log_theme_name;
+
+static char **autolog_ignore_targets;
+
+static char *log_colorizer_strip(const char *str)
+{
+ return strip_codes(str);
+}
+
+static void log_add_targets(LOG_REC *log, const char *targets, const char *tag)
+{
+ char **tmp, **items;
+
+ g_return_if_fail(log != NULL);
+ g_return_if_fail(targets != NULL);
+
+ items = g_strsplit(targets, " ", -1);
+
+ for (tmp = items; *tmp != NULL; tmp++)
+ log_item_add(log, LOG_ITEM_TARGET, *tmp, tag);
+
+ g_strfreev(items);
+}
+
+/* SYNTAX: LOG OPEN [-noopen] [-autoopen] [-window] [-<server tag>]
+ [-targets <targets>] [-colors]
+ <fname> [<levels>] */
+static void cmd_log_open(const char *data)
+{
+ SERVER_REC *server;
+ GHashTable *optlist;
+ char *targetarg, *fname, *levels, *servertag;
+ void *free_arg;
+ char window[MAX_INT_STRLEN];
+ LOG_REC *log;
+ int level;
+
+ if (!cmd_get_params(data, &free_arg,
+ 2 | PARAM_FLAG_GETREST | PARAM_FLAG_UNKNOWN_OPTIONS |
+ PARAM_FLAG_OPTIONS | PARAM_FLAG_STRIP_TRAILING_WS,
+ "log open", &optlist, &fname, &levels))
+ return;
+ if (*fname == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ level = level2bits(levels, NULL);
+ log = log_create_rec(fname, level != 0 ? level : MSGLEVEL_ALL);
+
+ /* -<server tag> */
+ server = cmd_options_get_server("log open", optlist, NULL);
+ servertag = server == NULL ? NULL : server->tag;
+
+ if (g_hash_table_lookup(optlist, "window")) {
+ /* log by window ref# */
+ targetarg = g_hash_table_lookup(optlist, "targets");
+ if (targetarg == NULL || !is_numeric(targetarg, '\0')) {
+ ltoa(window, active_win->refnum);
+ targetarg = window;
+ }
+ log_item_add(log, LOG_ITEM_WINDOW_REFNUM, targetarg, servertag);
+ } else {
+ targetarg = g_hash_table_lookup(optlist, "targets");
+ if (targetarg != NULL && *targetarg != '\0')
+ log_add_targets(log, targetarg, servertag);
+ else if (servertag != NULL)
+ log_add_targets(log, "*", servertag);
+ }
+
+ if (g_hash_table_lookup(optlist, "autoopen"))
+ log->autoopen = TRUE;
+
+ if (g_hash_table_lookup(optlist, "colors") == NULL)
+ log->colorizer = log_colorizer_strip;
+
+ log_update(log);
+
+ if (log->handle == -1 && g_hash_table_lookup(optlist, "noopen") == NULL) {
+ /* start logging */
+ if (log_start_logging(log)) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_OPENED, fname);
+ } else {
+ log_close(log);
+ }
+ }
+
+ cmd_params_free(free_arg);
+}
+
+static LOG_REC *log_find_from_data(const char *data)
+{
+ GSList *tmp;
+
+ if (!is_numeric(data, ' '))
+ return log_find(data);
+
+ /* with index number */
+ tmp = g_slist_nth(logs, atoi(data)-1);
+ return tmp == NULL ? NULL : tmp->data;
+}
+
+/* SYNTAX: LOG CLOSE <id>|<file> */
+static void cmd_log_close(const char *data)
+{
+ LOG_REC *log;
+
+ log = log_find_from_data(data);
+ if (log == NULL)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_LOG_NOT_OPEN, data);
+ else {
+ log_close(log);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_CLOSED, data);
+ }
+}
+
+/* SYNTAX: LOG START <id>|<file> */
+static void cmd_log_start(const char *data)
+{
+ LOG_REC *log;
+
+ log = log_find_from_data(data);
+ if (log != NULL) {
+ log_start_logging(log);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_OPENED, data);
+ }
+}
+
+/* SYNTAX: LOG STOP <id>|<file> */
+static void cmd_log_stop(const char *data)
+{
+ LOG_REC *log;
+
+ log = log_find_from_data(data);
+ if (log == NULL || log->handle == -1)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_LOG_NOT_OPEN, data);
+ else {
+ log_stop_logging(log);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_CLOSED, data);
+ }
+}
+
+static char *log_items_get_list(LOG_REC *log)
+{
+ GSList *tmp;
+ GString *str;
+ char *ret;
+ LOG_ITEM_REC *rec = NULL;
+
+ g_return_val_if_fail(log != NULL, NULL);
+ g_return_val_if_fail(log->items != NULL, NULL);
+
+ str = g_string_new(NULL);
+ for (tmp = log->items; tmp != NULL; tmp = tmp->next) {
+ rec = tmp->data;
+
+ g_string_append_printf(str, "%s, ", rec->name);
+ }
+ g_string_truncate(str, str->len-2);
+ if(rec->servertag != NULL)
+ g_string_append_printf(str, " (%s)", rec->servertag);
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+static void cmd_log_list(void)
+{
+ GSList *tmp;
+ char *levelstr, *items;
+ int index;
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_LOG_LIST_HEADER);
+ for (tmp = logs, index = 1; tmp != NULL; tmp = tmp->next, index++) {
+ LOG_REC *rec = tmp->data;
+
+ levelstr = bits2level(rec->level);
+ items = rec->items == NULL ? NULL : log_items_get_list(rec);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_LOG_LIST, index, rec->fname,
+ items != NULL ? items : "", levelstr, rec->autoopen ? " -autoopen" : "",
+ rec->handle != -1 ? " active" : "");
+
+ g_free_not_null(items);
+ g_free(levelstr);
+ }
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_LOG_LIST_FOOTER);
+}
+
+static void cmd_log(const char *data, SERVER_REC *server, void *item)
+{
+ if (*data == '\0')
+ cmd_log_list();
+ else
+ command_runsub("log", data, server, item);
+}
+
+static LOG_REC *logs_find_item(int type, const char *item, const char *servertag,
+ LOG_ITEM_REC **ret_item)
+{
+ LOG_ITEM_REC *logitem;
+ GSList *tmp;
+
+ for (tmp = logs; tmp != NULL; tmp = tmp->next) {
+ LOG_REC *log = tmp->data;
+
+ if (type == LOG_ITEM_TARGET && log->temp == 0) continue;
+ logitem = log_item_find(log, type, item, servertag);
+ if (logitem != NULL) {
+ if (ret_item != NULL) *ret_item = logitem;
+ return log;
+ }
+ }
+
+ return NULL;
+}
+
+/* SYNTAX: WINDOW LOG on|off|toggle [<filename>] */
+static void cmd_window_log(const char *data)
+{
+ LOG_REC *log;
+ char *set, *fname, window[MAX_INT_STRLEN];
+ void *free_arg;
+ int open_log, close_log;
+
+ if (!cmd_get_params(data, &free_arg, 2, &set, &fname))
+ return;
+
+ ltoa(window, active_win->refnum);
+ log = logs_find_item(LOG_ITEM_WINDOW_REFNUM, window, NULL, NULL);
+
+ open_log = close_log = FALSE;
+ if (g_ascii_strcasecmp(set, "ON") == 0)
+ open_log = TRUE;
+ else if (g_ascii_strcasecmp(set, "OFF") == 0) {
+ close_log = TRUE;
+ } else if (g_ascii_strcasecmp(set, "TOGGLE") == 0) {
+ open_log = log == NULL;
+ close_log = log != NULL;
+ } else {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_NOT_TOGGLE);
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ if (open_log && log == NULL) {
+ /* irc.log.<windowname> or irc.log.Window<ref#> */
+ fname = *fname != '\0' ? g_strdup(fname) :
+ g_strdup_printf("~/irc.log.%s%s",
+ active_win->name != NULL ? active_win->name : "Window",
+ active_win->name != NULL ? "" : window);
+ log = log_create_rec(fname, MSGLEVEL_ALL);
+ log->colorizer = log_colorizer_strip;
+ log_item_add(log, LOG_ITEM_WINDOW_REFNUM, window, NULL);
+ log_update(log);
+ g_free(fname);
+ }
+
+ if (open_log && log != NULL) {
+ log_start_logging(log);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_OPENED, log->fname);
+ } else if (close_log && log != NULL && log->handle != -1) {
+ log_stop_logging(log);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_CLOSED, log->fname);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+/* Create log file entry to window, but don't start logging */
+/* SYNTAX: WINDOW LOGFILE <file> */
+static void cmd_window_logfile(const char *data)
+{
+ LOG_REC *log;
+ char window[MAX_INT_STRLEN];
+ void *free_arg;
+ char *fname;
+
+ if (!cmd_get_params(data, &free_arg, 1, &fname)) {
+ return;
+ }
+
+ if (!fname || strlen(fname) == 0) {
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+ }
+
+ ltoa(window, active_win->refnum);
+ log = logs_find_item(LOG_ITEM_WINDOW_REFNUM, window, NULL, NULL);
+
+ if (log != NULL) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_WINDOWLOG_FILE_LOGGING);
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ log = log_create_rec(fname, MSGLEVEL_ALL);
+ log->colorizer = log_colorizer_strip;
+ log_item_add(log, LOG_ITEM_WINDOW_REFNUM, window, NULL);
+ log_update(log);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_WINDOWLOG_FILE, data);
+
+ cmd_params_free(free_arg);
+}
+
+/* window's refnum changed - update the logs to log the new window refnum */
+static void sig_window_refnum_changed(WINDOW_REC *window, gpointer old_refnum)
+{
+ char winnum[MAX_INT_STRLEN];
+ LOG_REC *log;
+ LOG_ITEM_REC *item;
+
+ ltoa(winnum, GPOINTER_TO_INT(old_refnum));
+ log = logs_find_item(LOG_ITEM_WINDOW_REFNUM, winnum, NULL, &item);
+
+ if (log != NULL) {
+ ltoa(winnum, window->refnum);
+
+ g_free(item->name);
+ item->name = g_strdup(winnum);
+ }
+}
+
+static void sig_server_disconnected(SERVER_REC *server)
+{
+ LOG_ITEM_REC *logitem;
+ GSList *tmp, *next;
+
+ for (tmp = logs; tmp != NULL; tmp = next) {
+ LOG_REC *log = tmp->data;
+ next = tmp->next;
+
+ if (!log->temp || log->items == NULL)
+ continue;
+
+ logitem = log->items->data;
+ if (logitem->type == LOG_ITEM_TARGET && logitem->servertag != NULL &&
+ g_ascii_strcasecmp(logitem->servertag, server->tag) == 0 &&
+ server_ischannel(
+ server, logitem->name)) /* kludge again.. so we won't close dcc chats */
+ log_close(log);
+ }
+}
+
+static void autologs_close_all(void)
+{
+ GSList *tmp, *next;
+
+ for (tmp = logs; tmp != NULL; tmp = next) {
+ LOG_REC *rec = tmp->data;
+
+ next = tmp->next;
+ if (rec->temp) log_close(rec);
+ }
+}
+
+/* '%' -> '%%', badness -> '_' */
+static char *escape_target(const char *target)
+{
+ char *str, *p;
+
+ p = str = g_malloc(strlen(target)*2+1);
+ while (*target != '\0') {
+ if (strchr("/\\|*?\"<>:", *target))
+ *p++ = '_';
+ else {
+ if (*target == '%')
+ *p++ = '%';
+ *p++ = *target;
+ }
+
+ target++;
+ }
+ *p = '\0';
+
+ return str;
+}
+
+static void autolog_open(SERVER_REC *server, const char *server_tag, const char *target)
+{
+ LOG_REC *log;
+ char *fname, *dir, *fixed_target, *params;
+
+ log = logs_find_item(LOG_ITEM_TARGET, target, server_tag, NULL);
+ if (log != NULL && !log->failed) {
+ log_start_logging(log);
+ return;
+ }
+
+ /* '/' -> '_' - don't even accidentally try to log to
+ #../../../file if you happen to join to such channel..
+ similar for some characters that are metacharacters
+ and/or illegal in Windows filenames.
+
+ '%' -> '%%' - so strftime() won't mess with them */
+ fixed_target = escape_target(target);
+ if (CHAT_PROTOCOL(server)->case_insensitive)
+ ascii_strdown(fixed_target);
+
+ /* $0 = target, $1 = server tag */
+ params = g_strconcat(fixed_target, " ", server_tag, NULL);
+ g_free(fixed_target);
+
+ fname = parse_special_string(autolog_path, server, NULL, params, NULL, 0);
+ g_free(params);
+
+ if (log_find(fname) == NULL) {
+ log = log_create_rec(fname, autolog_level);
+ if (!settings_get_bool("autolog_colors"))
+ log->colorizer = log_colorizer_strip;
+ log_item_add(log, LOG_ITEM_TARGET, target, server_tag);
+
+ dir = g_path_get_dirname(log->real_fname);
+#ifdef HAVE_CAPSICUM
+ capsicum_mkdir_with_parents_wrapper(dir, log_dir_create_mode);
+#else
+ g_mkdir_with_parents(dir, log_dir_create_mode);
+#endif
+ g_free(dir);
+
+ log->temp = TRUE;
+ log_update(log);
+ log_start_logging(log);
+ }
+ g_free(fname);
+}
+
+static void autolog_open_check(TEXT_DEST_REC *dest)
+{
+ const char *deftarget;
+ SERVER_REC *server = dest->server;
+ const char *server_tag = dest->server_tag;
+ const char *target = dest->target;
+ int level = dest->level;
+
+ /* FIXME: kind of a kludge, but we don't want to reopen logs when
+ we're parting the channel with /WINDOW CLOSE.. Maybe a small
+ timeout would be nice instead of immediately closing the log file
+ after "window item destroyed" */
+ if (level == MSGLEVEL_PARTS || (autolog_level & level) == 0 || target == NULL ||
+ *target == '\0')
+ return;
+
+ deftarget = server ? server->nick : "unknown";
+
+ /* log only channels that have been saved to the config */
+ if (settings_get_bool("autolog_only_saved_channels") && IS_CHANNEL(window_item_find(server, target))
+ && channel_setup_find(target, server_tag) == NULL)
+ return;
+
+ if (autolog_ignore_targets != NULL && strarray_find_dest(autolog_ignore_targets, dest))
+ return;
+
+ if (target != NULL)
+ autolog_open(server, server_tag, g_strcmp0(target, "*") ? target : deftarget);
+}
+
+static void log_single_line(WINDOW_REC *window, const char *server_tag, const char *target,
+ int level, time_t t, const char *text)
+{
+ char windownum[MAX_INT_STRLEN];
+ LOG_REC *log;
+
+ if (window != NULL) {
+ /* save to log created with /WINDOW LOG */
+ ltoa(windownum, window->refnum);
+ log = logs_find_item(LOG_ITEM_WINDOW_REFNUM, windownum, NULL, NULL);
+ if (log != NULL)
+ log_write_rec(log, text, level, t);
+ }
+
+ log_file_write(server_tag, target, level, t, text, FALSE);
+}
+
+static void log_line(TEXT_DEST_REC *dest, const char *text)
+{
+ char **lines, **tmp;
+ time_t t = (time_t) -1;
+
+ if (dest->level == MSGLEVEL_NEVER)
+ return;
+
+ /* let autolog open the log records */
+ autolog_open_check(dest);
+
+ if (logs == NULL)
+ return;
+
+ /* text may contain one or more lines, log wants to eat them one
+ line at a time */
+ lines = g_strsplit(text, "\n", -1);
+ if (log_server_time && dest->meta != NULL) {
+ char *val;
+ if ((val = g_hash_table_lookup(dest->meta, "time")) != NULL) {
+ GDateTime *time;
+ if ((time = g_date_time_new_from_iso8601(val, NULL)) != NULL) {
+ t = g_date_time_to_unix(time);
+ g_date_time_unref(time);
+ }
+ }
+ }
+ for (tmp = lines; *tmp != NULL; tmp++)
+ log_single_line(dest->window, dest->server_tag, dest->target, dest->level, t, *tmp);
+ g_strfreev(lines);
+}
+
+static void sig_printtext(TEXT_DEST_REC *dest, const char *text, const char *stripped)
+{
+ if (skip_next_printtext) {
+ skip_next_printtext = FALSE;
+ return;
+ }
+
+ log_line(dest, text);
+}
+
+static void sig_print_format(THEME_REC *theme, const char *module, TEXT_DEST_REC *dest,
+ void *formatnum, char **args)
+{
+ char *str, *linestart, *tmp;
+
+ if (log_theme == NULL) {
+ /* theme isn't loaded for some reason (/reload destroys it),
+ reload it. */
+ log_theme = theme_load(log_theme_name);
+ if (log_theme == NULL) return;
+ }
+
+ if (theme == log_theme)
+ return;
+
+ str = format_get_text_theme_charargs(log_theme, module, dest, GPOINTER_TO_INT(formatnum),
+ args);
+ if (str != NULL && *str != '\0') {
+ skip_next_printtext = TRUE;
+
+ /* add the line start format */
+ linestart = format_get_level_tag(log_theme, dest);
+ tmp = str;
+ str = format_add_linestart(tmp, linestart);
+ g_free_not_null(linestart);
+ g_free(tmp);
+
+ /* strip colors from text, log it. */
+ log_line(dest, str);
+ }
+ g_free(str);
+
+}
+
+static int sig_autoremove(void)
+{
+ SERVER_REC *server;
+ LOG_ITEM_REC *logitem;
+ GSList *tmp, *next;
+ time_t removetime;
+
+ removetime = time(NULL) - AUTOLOG_INACTIVITY_CLOSE;
+ for (tmp = logs; tmp != NULL; tmp = next) {
+ LOG_REC *log = tmp->data;
+
+ next = tmp->next;
+
+ if (!log->temp || log->last > removetime || log->items == NULL)
+ continue;
+
+ /* Close only logs with private messages */
+ logitem = log->items->data;
+ if (logitem->servertag == NULL)
+ continue;
+
+ server = server_find_tag(logitem->servertag);
+ if (logitem->type == LOG_ITEM_TARGET && server != NULL &&
+ !server_ischannel(server, logitem->name))
+ log_close(log);
+ }
+ return 1;
+}
+
+static void sig_window_item_remove(WINDOW_REC *window, WI_ITEM_REC *item)
+{
+ LOG_REC *log;
+
+ log = logs_find_item(LOG_ITEM_TARGET, item->visible_name,
+ item->server == NULL ? NULL : item->server->tag, NULL);
+ if (log != NULL && log->temp)
+ log_close(log);
+}
+
+static void sig_log_locked(LOG_REC *log)
+{
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_LOG_LOCKED, log->real_fname);
+}
+
+static void sig_log_create_failed(LOG_REC *log)
+{
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_LOG_CREATE_FAILED, log->real_fname,
+ g_strerror(errno));
+}
+
+static void sig_log_new(LOG_REC *log)
+{
+ if (!settings_get_bool("awaylog_colors") &&
+ g_strcmp0(log->fname, settings_get_str("awaylog_file")) == 0)
+ log->colorizer = log_colorizer_strip;
+}
+
+static void sig_log_config_read(LOG_REC *log, CONFIG_NODE *node)
+{
+ if (!config_node_get_bool(node, "colors", FALSE))
+ log->colorizer = log_colorizer_strip;
+}
+
+static void sig_log_config_save(LOG_REC *log, CONFIG_NODE *node)
+{
+ if (log->colorizer == NULL)
+ iconfig_node_set_bool(node, "colors", TRUE);
+ else
+ iconfig_node_set_str(node, "colors", NULL);
+}
+
+static void sig_awaylog_show(LOG_REC *log, gpointer pmsgs, gpointer pfilepos)
+{
+ char *str;
+ int msgs, filepos;
+
+ msgs = GPOINTER_TO_INT(pmsgs);
+ filepos = GPOINTER_TO_INT(pfilepos);
+
+ if (msgs == 0)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_NO_AWAY_MSGS, log->real_fname);
+ else {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_AWAY_MSGS, log->real_fname, msgs);
+
+ str = g_strdup_printf("\"%s\" %d", log->real_fname, filepos);
+ signal_emit("command cat", 1, str);
+ g_free(str);
+ }
+}
+
+static void sig_theme_destroyed(THEME_REC *theme)
+{
+ if (theme == log_theme)
+ log_theme = NULL;
+}
+
+static void read_settings(void)
+{
+ int old_autolog = autolog_level;
+
+ g_free_not_null(autolog_path);
+ autolog_path = g_strdup(settings_get_str("autolog_path"));
+
+ autolog_level = !settings_get_bool("autolog") ? 0 :
+ settings_get_level("autolog_level");
+
+ if (old_autolog && !autolog_level)
+ autologs_close_all();
+
+ /* write to log files with different theme? */
+ if (log_theme_name != NULL)
+ signal_remove("print format", (SIGNAL_FUNC) sig_print_format);
+
+ g_free_not_null(log_theme_name);
+ log_theme_name = g_strdup(settings_get_str("log_theme"));
+
+ if (*log_theme_name == '\0') {
+ g_free(log_theme_name);
+ log_theme_name = NULL;
+ }
+ else
+ signal_add("print format", (SIGNAL_FUNC) sig_print_format);
+
+ log_theme = log_theme_name == NULL ? NULL :
+ theme_load(log_theme_name);
+
+ if (autolog_ignore_targets != NULL)
+ g_strfreev(autolog_ignore_targets);
+
+ autolog_ignore_targets = g_strsplit(settings_get_str("autolog_ignore_targets"), " ", -1);
+
+ log_server_time = settings_get_choice("log_server_time");
+ if (log_server_time == 2) {
+ SETTINGS_REC *rec = settings_get_record("show_server_time");
+ if (rec != NULL)
+ log_server_time = settings_get_bool("show_server_time");
+ }
+}
+
+void fe_log_init(void)
+{
+ autoremove_tag = g_timeout_add(60000, (GSourceFunc) sig_autoremove, NULL);
+ skip_next_printtext = FALSE;
+
+ settings_add_bool("log", "awaylog_colors", TRUE);
+ settings_add_bool("log", "autolog", FALSE);
+ settings_add_bool("log", "autolog_colors", FALSE);
+ settings_add_bool("log", "autolog_only_saved_channels", FALSE);
+ settings_add_choice("log", "log_server_time", 2, "off;on;auto");
+ settings_add_str("log", "autolog_path", "~/irclogs/$tag/$0.log");
+ settings_add_level("log", "autolog_level", "all -crap -clientcrap -ctcps");
+ settings_add_str("log", "log_theme", "");
+ settings_add_str("log", "autolog_ignore_targets", "");
+
+ autolog_level = 0;
+ log_theme_name = NULL;
+ read_settings();
+
+ command_bind("log", NULL, (SIGNAL_FUNC) cmd_log);
+ command_bind("log open", NULL, (SIGNAL_FUNC) cmd_log_open);
+ command_bind("log close", NULL, (SIGNAL_FUNC) cmd_log_close);
+ command_bind("log start", NULL, (SIGNAL_FUNC) cmd_log_start);
+ command_bind("log stop", NULL, (SIGNAL_FUNC) cmd_log_stop);
+ command_bind("window log", NULL, (SIGNAL_FUNC) cmd_window_log);
+ command_bind("window logfile", NULL, (SIGNAL_FUNC) cmd_window_logfile);
+ signal_add_first("print text", (SIGNAL_FUNC) sig_printtext);
+ signal_add("window item remove", (SIGNAL_FUNC) sig_window_item_remove);
+ signal_add("window refnum changed", (SIGNAL_FUNC) sig_window_refnum_changed);
+ signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+ signal_add("log locked", (SIGNAL_FUNC) sig_log_locked);
+ signal_add("log create failed", (SIGNAL_FUNC) sig_log_create_failed);
+ signal_add("log new", (SIGNAL_FUNC) sig_log_new);
+ signal_add("log config read", (SIGNAL_FUNC) sig_log_config_read);
+ signal_add("log config save", (SIGNAL_FUNC) sig_log_config_save);
+ signal_add("awaylog show", (SIGNAL_FUNC) sig_awaylog_show);
+ signal_add("theme destroyed", (SIGNAL_FUNC) sig_theme_destroyed);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+
+ command_set_options("log open", "noopen autoopen -targets window colors");
+}
+
+void fe_log_deinit(void)
+{
+ g_source_remove(autoremove_tag);
+ if (log_theme_name != NULL)
+ signal_remove("print format", (SIGNAL_FUNC) sig_print_format);
+
+ command_unbind("log", (SIGNAL_FUNC) cmd_log);
+ command_unbind("log open", (SIGNAL_FUNC) cmd_log_open);
+ command_unbind("log close", (SIGNAL_FUNC) cmd_log_close);
+ command_unbind("log start", (SIGNAL_FUNC) cmd_log_start);
+ command_unbind("log stop", (SIGNAL_FUNC) cmd_log_stop);
+ command_unbind("window log", (SIGNAL_FUNC) cmd_window_log);
+ command_unbind("window logfile", (SIGNAL_FUNC) cmd_window_logfile);
+ signal_remove("print text", (SIGNAL_FUNC) sig_printtext);
+ signal_remove("window item remove", (SIGNAL_FUNC) sig_window_item_remove);
+ signal_remove("window refnum changed", (SIGNAL_FUNC) sig_window_refnum_changed);
+ signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+ signal_remove("log locked", (SIGNAL_FUNC) sig_log_locked);
+ signal_remove("log create failed", (SIGNAL_FUNC) sig_log_create_failed);
+ signal_remove("log new", (SIGNAL_FUNC) sig_log_new);
+ signal_remove("log config read", (SIGNAL_FUNC) sig_log_config_read);
+ signal_remove("log config save", (SIGNAL_FUNC) sig_log_config_save);
+ signal_remove("awaylog show", (SIGNAL_FUNC) sig_awaylog_show);
+ signal_remove("theme destroyed", (SIGNAL_FUNC) sig_theme_destroyed);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+
+ if (autolog_ignore_targets != NULL)
+ g_strfreev(autolog_ignore_targets);
+
+ g_free_not_null(autolog_path);
+ g_free_not_null(log_theme_name);
+}
diff --git a/src/fe-common/core/fe-messages.c b/src/fe-common/core/fe-messages.c
new file mode 100644
index 0000000..355d4fd
--- /dev/null
+++ b/src/fe-common/core/fe-messages.c
@@ -0,0 +1,858 @@
+/*
+ fe-messages.c : irssi
+
+ Copyright (C) 2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/core/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/special-vars.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/channels.h>
+#include <irssi/src/core/nicklist.h>
+#include <irssi/src/core/ignore.h>
+
+#include <irssi/src/fe-common/core/window-items.h>
+#include <irssi/src/fe-common/core/fe-queries.h>
+#include <irssi/src/fe-common/core/hilight-text.h>
+#include <irssi/src/fe-common/core/printtext.h>
+
+#define ishighalnum(c) ((unsigned char) (c) >= 128 || i_isalnum(c))
+#define isnickchar(a) \
+ (i_isalnum(a) || (a) == '`' || (a) == '-' || (a) == '_' || \
+ (a) == '[' || (a) == ']' || (a) == '{' || (a) == '}' || \
+ (a) == '|' || (a) == '\\' || (a) == '^')
+
+GHashTable *printnicks;
+
+/* convert _underlined_, /italics/, and *bold* words (and phrases) to use real
+ underlining or bolding */
+char *expand_emphasis(WI_ITEM_REC *item, const char *text)
+{
+ GString *str;
+ char *ret;
+ int pos;
+ int emphasis_italics;
+
+ g_return_val_if_fail(text != NULL, NULL);
+
+ emphasis_italics = settings_get_bool("emphasis_italics");
+
+ str = g_string_new(text);
+
+ for (pos = 0; pos < str->len; pos++) {
+ char type, *bgn, *end;
+
+ bgn = str->str + pos;
+
+ if (*bgn == '*')
+ type = 2; /* bold */
+ else if (*bgn == '/' && emphasis_italics)
+ type = 29; /* italics */
+ else if (*bgn == '_')
+ type = 31; /* underlined */
+ else
+ continue;
+
+ /* check that the beginning marker starts a word, and
+ that the matching end marker ends a word */
+ if ((pos > 0 && bgn[-1] != ' ') || !ishighalnum(bgn[1]))
+ continue;
+ if ((end = strchr(bgn+1, *bgn)) == NULL)
+ continue;
+ if (!ishighalnum(end[-1]) || ishighalnum(end[1]) ||
+ end[1] == type || end[1] == '*' || end[1] == '_' ||
+ /* special case for italics to not emphasise
+ common paths by skipping /.../.X */
+ (type == 29 && i_ispunct(end[1]) && ishighalnum(end[2])))
+ continue;
+
+ if (IS_CHANNEL(item)) {
+ /* check that this isn't a _nick_, we don't want to
+ use emphasis on them. */
+ int found;
+ char c;
+ char *end2;
+
+ /* check if _foo_ is a nick */
+ c = end[1];
+ end[1] = '\0';
+ found = nicklist_find(CHANNEL(item), bgn) != NULL;
+ end[1] = c;
+ if (found) continue;
+
+ /* check if the whole 'word' (e.g. "_foo_^") is a nick
+ in "_foo_^ ", end will be the second _, end2 the ^ */
+ end2 = end;
+ while (isnickchar(end2[1]))
+ end2++;
+ c = end2[1];
+ end2[1] = '\0';
+ found = nicklist_find(CHANNEL(item), bgn) != NULL;
+ end2[1] = c;
+ if (found) continue;
+ }
+
+ /* allow only *word* emphasis, not *multiple words* */
+ if (!settings_get_bool("emphasis_multiword")) {
+ char *c;
+ for (c = bgn+1; c != end; c++) {
+ if (!ishighalnum(*c))
+ break;
+ }
+ if (c != end) continue;
+ }
+
+ if (settings_get_bool("emphasis_replace")) {
+ *bgn = *end = type;
+ pos += (end-bgn);
+ } else {
+ g_string_insert_c(str, pos, type);
+ pos += (end - bgn) + 2;
+ g_string_insert_c(str, pos++, type);
+ }
+ }
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+static char *channel_get_nickmode_rec(NICK_REC *nickrec)
+{
+ char *emptystr;
+ char *nickmode;
+
+ if (!settings_get_bool("show_nickmode"))
+ return g_strdup("");
+
+ emptystr = settings_get_bool("show_nickmode_empty") ? " " : "";
+
+ if (nickrec == NULL || nickrec->prefixes[0] == '\0')
+ nickmode = g_strdup(emptystr);
+ else {
+ nickmode = g_malloc(2);
+ nickmode[0] = nickrec->prefixes[0];
+ nickmode[1] = '\0';
+ }
+ return nickmode;
+}
+
+char *channel_get_nickmode(CHANNEL_REC *channel, const char *nick)
+{
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ return channel_get_nickmode_rec(channel == NULL ? NULL :
+ nicklist_find(channel, nick));
+}
+
+static void sig_message_public(SERVER_REC *server, const char *msg,
+ const char *nick, const char *address,
+ const char *target, NICK_REC *nickrec)
+{
+ CHANNEL_REC *chanrec;
+ const char *printnick;
+ int for_me, print_channel, level;
+ char *nickmode, *color, *freemsg = NULL;
+ HILIGHT_REC *hilight;
+ TEXT_DEST_REC dest;
+
+ /* NOTE: this may return NULL if some channel is just closed with
+ /WINDOW CLOSE and server still sends the few last messages */
+ chanrec = channel_find(server, target);
+ if (nickrec == NULL && chanrec != NULL)
+ nickrec = nicklist_find(chanrec, nick);
+
+ for_me = !settings_get_bool("hilight_nick_matches") ? FALSE :
+ !settings_get_bool("hilight_nick_matches_everywhere") ?
+ nick_match_msg(chanrec, msg, server->nick) :
+ nick_match_msg_everywhere(chanrec, msg, server->nick);
+ hilight = for_me ? NULL :
+ hilight_match_nick(server, target, nick, address, MSGLEVEL_PUBLIC, msg);
+ color = (hilight == NULL) ? NULL : hilight_get_color(hilight);
+
+ print_channel = chanrec == NULL ||
+ !window_item_is_active((WI_ITEM_REC *) chanrec);
+ if (!print_channel && settings_get_bool("print_active_channel") &&
+ window_item_window((WI_ITEM_REC *) chanrec)->items->next != NULL)
+ print_channel = TRUE;
+
+ level = MSGLEVEL_PUBLIC;
+ if (for_me)
+ level |= MSGLEVEL_HILIGHT;
+
+ ignore_check_plus(server, nick, address, target, msg, &level, FALSE);
+ if (level & MSGLEVEL_NOHILIGHT) {
+ for_me = FALSE;
+ g_free_and_null(color);
+ level &= ~MSGLEVEL_HILIGHT;
+ }
+
+ if (settings_get_bool("emphasis"))
+ msg = freemsg = expand_emphasis((WI_ITEM_REC *) chanrec, msg);
+
+ /* get nick mode & nick what to print the msg with
+ (in case there's multiple identical nicks) */
+ nickmode = channel_get_nickmode_rec(nickrec);
+ printnick = nickrec == NULL ? nick :
+ g_hash_table_lookup(printnicks, nickrec);
+ if (printnick == NULL)
+ printnick = nick;
+
+ format_create_dest(&dest, server, target, level, NULL);
+ dest.address = address;
+ dest.nick = nick;
+ if (color != NULL) {
+ /* highlighted nick */
+ hilight_update_text_dest(&dest,hilight);
+ if (!print_channel) /* message to active channel in window */
+ printformat_dest(&dest, TXT_PUBMSG_HILIGHT, color,
+ printnick, msg, nickmode);
+ else /* message to not existing/active channel */
+ printformat_dest(&dest, TXT_PUBMSG_HILIGHT_CHANNEL,
+ color, printnick, target, msg,
+ nickmode);
+ } else {
+ if (!print_channel)
+ printformat_dest(&dest,
+ for_me ? TXT_PUBMSG_ME : TXT_PUBMSG,
+ printnick, msg, nickmode);
+ else
+ printformat_dest(&dest,
+ for_me ? TXT_PUBMSG_ME_CHANNEL :
+ TXT_PUBMSG_CHANNEL,
+ printnick, target, msg, nickmode);
+ }
+
+ g_free_not_null(nickmode);
+ g_free_not_null(freemsg);
+ g_free_not_null(color);
+}
+
+static void sig_message_private(SERVER_REC *server, const char *msg,
+ const char *nick, const char *address, const char *target)
+{
+ QUERY_REC *query;
+ char *freemsg = NULL;
+ int level = MSGLEVEL_MSGS;
+
+ /* own message returned by bouncer? */
+ int own = (!g_strcmp0(nick, server->nick));
+
+ query = query_find(server, own ? target : nick);
+
+ if (settings_get_bool("emphasis"))
+ msg = freemsg = expand_emphasis((WI_ITEM_REC *) query, msg);
+
+ ignore_check_plus(server, nick, address, NULL, msg, &level, FALSE);
+
+ if (own) {
+ printformat(server, target, level,
+ query == NULL ? TXT_OWN_MSG_PRIVATE :
+ TXT_OWN_MSG_PRIVATE_QUERY, target, msg, server->nick);
+ } else {
+ printformat(server, nick, level,
+ query == NULL ? TXT_MSG_PRIVATE :
+ TXT_MSG_PRIVATE_QUERY, nick, address, msg);
+ }
+
+ g_free_not_null(freemsg);
+}
+
+static void sig_message_own_public(SERVER_REC *server, const char *msg,
+ const char *target)
+{
+ WINDOW_REC *window;
+ CHANNEL_REC *channel;
+ char *nickmode;
+ char *freemsg = NULL;
+ int print_channel;
+ channel = channel_find(server, target);
+ if (channel != NULL)
+ target = channel->visible_name;
+
+ nickmode = channel_get_nickmode(channel, server->nick);
+
+ window = channel == NULL ? NULL :
+ window_item_window((WI_ITEM_REC *) channel);
+
+ print_channel = window == NULL ||
+ window->active != (WI_ITEM_REC *) channel;
+
+ if (!print_channel && settings_get_bool("print_active_channel") &&
+ window != NULL && g_slist_length(window->items) > 1)
+ print_channel = TRUE;
+
+ if (settings_get_bool("emphasis"))
+ msg = freemsg = expand_emphasis((WI_ITEM_REC *) channel, msg);
+
+ if (!print_channel) {
+ printformat(server, target, MSGLEVEL_PUBLIC | MSGLEVEL_NOHILIGHT | MSGLEVEL_NO_ACT,
+ TXT_OWN_MSG, server->nick, msg, nickmode);
+ } else {
+ printformat(server, target, MSGLEVEL_PUBLIC | MSGLEVEL_NOHILIGHT | MSGLEVEL_NO_ACT,
+ TXT_OWN_MSG_CHANNEL, server->nick, target, msg, nickmode);
+ }
+
+ g_free_not_null(nickmode);
+ g_free_not_null(freemsg);
+}
+
+static void sig_message_own_private(SERVER_REC *server, const char *msg,
+ const char *target, const char *origtarget)
+{
+ QUERY_REC *query;
+ char *freemsg = NULL;
+
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(msg != NULL);
+ if (target == NULL) {
+ /* this should only happen if some special target failed and
+ we should display some error message. currently the special
+ targets are only ',' and '.'. */
+ g_return_if_fail(g_strcmp0(origtarget, ",") == 0 ||
+ g_strcmp0(origtarget, ".") == 0);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ *origtarget == ',' ? TXT_NO_MSGS_GOT :
+ TXT_NO_MSGS_SENT);
+ signal_stop();
+ return;
+ }
+
+ query = privmsg_get_query(server, target, TRUE, MSGLEVEL_MSGS);
+
+ if (settings_get_bool("emphasis"))
+ msg = freemsg = expand_emphasis((WI_ITEM_REC *) query, msg);
+
+ printformat(server, target,
+ MSGLEVEL_MSGS | MSGLEVEL_NOHILIGHT | MSGLEVEL_NO_ACT,
+ query == NULL ? TXT_OWN_MSG_PRIVATE :
+ TXT_OWN_MSG_PRIVATE_QUERY, target, msg, server->nick);
+
+ g_free_not_null(freemsg);
+}
+
+static void sig_message_join(SERVER_REC *server, const char *channel,
+ const char *nick, const char *address,
+ const char *account, const char *realname)
+{
+ int level = MSGLEVEL_JOINS;
+
+ ignore_check_plus(server, nick, address, channel, NULL, &level, FALSE);
+
+ if (settings_get_bool("show_extended_join")) {
+ int txt;
+ if (*account == '\0') txt = TXT_JOIN;
+ else if (g_strcmp0("*", account) == 0) txt = TXT_JOIN_EXTENDED;
+ else txt = TXT_JOIN_EXTENDED_ACCOUNT;
+ printformat(server, channel, level,
+ txt, nick, address, channel, account, realname);
+ } else {
+ printformat(server, channel, level,
+ TXT_JOIN, nick, address, channel, account, realname);
+ }
+}
+
+static void sig_message_part(SERVER_REC *server, const char *channel,
+ const char *nick, const char *address,
+ const char *reason)
+{
+ int level = MSGLEVEL_PARTS;
+
+ ignore_check_plus(server, nick, address, channel, NULL, &level, FALSE);
+
+ printformat(server, channel, level,
+ TXT_PART, nick, address, channel, reason);
+}
+
+static void spread_server_message_to_windows(SERVER_REC *server, gboolean once,
+ gboolean in_query,
+ int base_level,
+ int txt, int txt_once,
+ const char *nick, const char *address,
+ const char *data,
+ const char *ignore_data
+ )
+{
+ WINDOW_REC *window;
+ GString *chans;
+ GSList *tmp, *windows;
+ char *print_channel;
+ int count, level = base_level;
+
+ if (ignore_check_plus(server, nick, address, NULL, ignore_data, &level, TRUE))
+ return;
+
+ print_channel = NULL;
+
+ count = 0; windows = NULL;
+ chans = g_string_new(NULL);
+ for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_REC *rec;
+ level = base_level;
+ rec = tmp->data;
+
+ if (!nicklist_find(rec, nick)) {
+ continue;
+ }
+
+ if (ignore_check_plus(server, nick, address, rec->visible_name,
+ ignore_data, &level, TRUE)) {
+ count++;
+ continue;
+ }
+
+ if (print_channel == NULL ||
+ active_win->active == (WI_ITEM_REC *) rec) {
+ print_channel = rec->visible_name;
+ }
+
+ if (once) {
+ g_string_append_printf(chans, "%s,", rec->visible_name);
+ } else {
+ window = window_item_window((WI_ITEM_REC *) rec);
+ if (g_slist_find(windows, window) == NULL) {
+ windows = g_slist_prepend(windows, window);
+ printformat(server, rec->visible_name,
+ level,
+ txt, nick, address, data,
+ rec->visible_name);
+ }
+ }
+ count++;
+ }
+ g_slist_free(windows);
+
+ if (!once && in_query) {
+ /* check if you had query with the nick and
+ display the change there too */
+ QUERY_REC *query = query_find(server, nick);
+ if (query != NULL) {
+ printformat(server, nick, level,
+ txt, nick, address, data, "");
+ }
+ }
+
+ if (once || count == 0) {
+ if (chans->len > 0) {
+ g_string_truncate(chans, chans->len-1);
+ }
+ printformat(server, print_channel, base_level,
+ count <= 1 ? txt : txt_once,
+ nick, address, data, chans->str);
+ }
+ g_string_free(chans, TRUE);
+}
+
+static void sig_message_host_changed(SERVER_REC *server, const char *nick,
+ const char *address, const char *old_address)
+{
+ spread_server_message_to_windows(
+ server,
+ settings_get_bool("show_quit_once"),
+ TRUE,
+ MSGLEVEL_JOINS,
+ TXT_HOST_CHANGED, TXT_HOST_CHANGED,
+ nick, address, old_address,
+ NULL
+ );
+}
+
+static void sig_message_account_changed(SERVER_REC *server, const char *nick,
+ const char *address, const char *account)
+{
+ gboolean logged_in;
+ int txt;
+
+ if (!settings_get_bool("show_account_notify"))
+ return;
+
+ logged_in = g_strcmp0("*", account) != 0;
+ txt = logged_in ? TXT_LOGGED_IN : TXT_LOGGED_OUT;
+
+ spread_server_message_to_windows(
+ server,
+ settings_get_bool("show_quit_once"),
+ TRUE,
+ MSGLEVEL_MODES,
+ txt, txt,
+ nick, address, account,
+ "account"
+ );
+}
+
+static void sig_message_quit(SERVER_REC *server, const char *nick,
+ const char *address, const char *reason)
+{
+ spread_server_message_to_windows(
+ server,
+ settings_get_bool("show_quit_once"),
+ TRUE,
+ MSGLEVEL_QUITS,
+ TXT_QUIT, TXT_QUIT_ONCE,
+ nick, address, reason,
+ reason
+ );
+}
+
+static void sig_message_kick(SERVER_REC *server, const char *channel,
+ const char *nick, const char *kicker,
+ const char *address, const char *reason)
+{
+ int level = MSGLEVEL_KICKS;
+
+ ignore_check_plus(server, kicker, address, channel, reason, &level, FALSE);
+
+ printformat(server, channel, level,
+ TXT_KICK, nick, channel, kicker, reason, address);
+}
+
+static void print_nick_change_channel(SERVER_REC *server, const char *channel,
+ const char *newnick, const char *oldnick,
+ const char *address,
+ int ownnick)
+{
+ int level;
+
+ level = MSGLEVEL_NICKS;
+ if (ownnick) level |= MSGLEVEL_NO_ACT;
+ if (ignore_check_plus(server, oldnick, address,
+ channel, newnick, &level, TRUE))
+ return;
+
+ printformat(server, channel, level,
+ ownnick ? TXT_YOUR_NICK_CHANGED : TXT_NICK_CHANGED,
+ oldnick, newnick, channel, address);
+}
+
+static void print_nick_change(SERVER_REC *server, const char *newnick,
+ const char *oldnick, const char *address,
+ int ownnick)
+{
+ GSList *tmp, *windows;
+ int msgprint;
+
+ msgprint = FALSE;
+
+ /* Print to each channel where the nick is.
+ Don't print more than once to the same window. */
+ windows = NULL;
+ for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_REC *channel = tmp->data;
+ WINDOW_REC *window =
+ window_item_window((WI_ITEM_REC *) channel);
+
+ if (nicklist_find(channel, newnick) == NULL ||
+ g_slist_find(windows, window) != NULL)
+ continue;
+
+ windows = g_slist_append(windows, window);
+ print_nick_change_channel(server, channel->visible_name,
+ newnick, oldnick, address, ownnick);
+ msgprint = TRUE;
+ }
+
+ g_slist_free(windows);
+
+ if (!msgprint && ownnick) {
+ printformat(server, NULL, MSGLEVEL_NICKS,
+ TXT_YOUR_NICK_CHANGED, oldnick, newnick, "",
+ address);
+ }
+}
+
+static void sig_message_nick(SERVER_REC *server, const char *newnick,
+ const char *oldnick, const char *address)
+{
+ print_nick_change(server, newnick, oldnick, address, FALSE);
+}
+
+static void sig_message_own_nick(SERVER_REC *server, const char *newnick,
+ const char *oldnick, const char *address)
+{
+ if (!settings_get_bool("show_own_nickchange_once"))
+ print_nick_change(server, newnick, oldnick, address, TRUE);
+ else {
+ printformat(server, NULL, MSGLEVEL_NICKS,
+ TXT_YOUR_NICK_CHANGED, oldnick, newnick, "",
+ address);
+ }
+}
+
+static void sig_message_invite(SERVER_REC *server, const char *channel,
+ const char *nick, const char *address)
+{
+ char *str;
+
+ str = show_lowascii(channel);
+ printformat(server, NULL, MSGLEVEL_INVITES,
+ TXT_INVITE, nick, str, address);
+ g_free(str);
+}
+
+static void sig_message_invite_other(SERVER_REC *server, const char *channel,
+ const char *invited, const char *nick, const char *address)
+{
+ char *str;
+ int level = MSGLEVEL_INVITES;
+
+ ignore_check_plus(server, nick, address, channel, invited, &level, FALSE);
+
+ str = show_lowascii(channel);
+ printformat(server, channel, level,
+ TXT_INVITE_OTHER, invited, nick, str, address);
+ g_free(str);
+}
+
+static void sig_message_topic(SERVER_REC *server, const char *channel,
+ const char *topic,
+ const char *nick, const char *address)
+{
+ int level = MSGLEVEL_TOPICS;
+
+ ignore_check_plus(server, nick, address, channel, topic, &level, FALSE);
+
+ printformat(server, channel, level,
+ *topic != '\0' ? TXT_NEW_TOPIC : TXT_TOPIC_UNSET,
+ nick, channel, topic, address);
+}
+
+static void sig_message_away_notify(SERVER_REC *server, const char *nick,
+ const char *addr, const char *awaymsg)
+{
+ int txt = *awaymsg == '\0' ? TXT_NOTIFY_UNAWAY_CHANNEL :
+ TXT_NOTIFY_AWAY_CHANNEL;
+
+ if (!settings_get_bool("away_notify_public"))
+ return;
+
+ spread_server_message_to_windows(server, FALSE,
+ FALSE,
+ MSGLEVEL_CRAP,
+ txt, txt,
+ nick, addr,
+ awaymsg,
+ awaymsg
+ );
+}
+
+static int printnick_exists(NICK_REC *first, NICK_REC *ignore,
+ const char *nick)
+{
+ char *printnick;
+
+ while (first != NULL) {
+ if (first != ignore) {
+ printnick = g_hash_table_lookup(printnicks, first);
+ if (printnick != NULL && g_strcmp0(printnick, nick) == 0)
+ return TRUE;
+ }
+
+ first = first->next;
+ }
+
+ return FALSE;
+}
+
+static NICK_REC *printnick_find_original(NICK_REC *nick)
+{
+ while (nick != NULL) {
+ if (g_hash_table_lookup(printnicks, nick) == NULL)
+ return nick;
+
+ nick = nick->next;
+ }
+
+ return NULL;
+}
+
+static void sig_nicklist_new(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ NICK_REC *firstnick;
+ GString *newnick;
+ char *nickhost, *p;
+ int n;
+
+ firstnick = g_hash_table_lookup(channel->nicks, nick->nick);
+ if (firstnick->next == NULL)
+ return;
+
+ if (nick == channel->ownnick) {
+ /* own nick is being added, might be a nick change and
+ someone else having the original nick already in use.. */
+ nick = printnick_find_original(firstnick->next);
+ if (nick == NULL)
+ return; /* nope, we have it */
+ }
+
+ if (nick->host == NULL)
+ return;
+
+ /* identical nick already exists, have to change it somehow.. */
+ p = strchr(nick->host, '@');
+ if (p == NULL) p = nick->host; else p++;
+
+ nickhost = g_strdup_printf("%s@%s", nick->nick, p);
+ p = strchr(nickhost+strlen(nick->nick), '.');
+ if (p != NULL) *p = '\0';
+
+ if (!printnick_exists(firstnick, nick, nickhost)) {
+ /* use nick@host */
+ g_hash_table_insert(printnicks, nick, nickhost);
+ return;
+ }
+
+ newnick = g_string_new(NULL);
+ n = 2;
+ do {
+ g_string_printf(newnick, "%s%d", nickhost, n);
+ n++;
+ } while (printnick_exists(firstnick, nick, newnick->str));
+
+ g_hash_table_insert(printnicks, nick, newnick->str);
+ g_string_free(newnick, FALSE);
+ g_free(nickhost);
+}
+
+static void sig_nicklist_remove(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ char *nickname;
+
+ nickname = g_hash_table_lookup(printnicks, nick);
+ if (nickname != NULL) {
+ g_free(nickname);
+ g_hash_table_remove(printnicks, nick);
+ }
+}
+
+static void sig_nicklist_changed(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ sig_nicklist_remove(channel, nick);
+ sig_nicklist_new(channel, nick);
+}
+
+static void sig_channel_joined(CHANNEL_REC *channel)
+{
+ NICK_REC *nick;
+ char *nickname;
+
+ /* channel->ownnick is set at this point - check if our own nick
+ has been changed, if it was set it back to the original nick and
+ change the previous original to something else */
+
+ nickname = g_hash_table_lookup(printnicks, channel->ownnick);
+ if (nickname == NULL)
+ return;
+
+ g_free(nickname);
+ g_hash_table_remove(printnicks, channel->ownnick);
+
+ /* our own nick is guaranteed to be the first in list */
+ nick = channel->ownnick->next;
+ while (nick != NULL) {
+ if (g_hash_table_lookup(printnicks, nick) == NULL) {
+ sig_nicklist_new(channel, nick);
+ break;
+ }
+ nick = nick->next;
+ }
+}
+
+static void i_hash_free_value(void *key, void *value)
+{
+ g_free(value);
+}
+
+void fe_messages_init(void)
+{
+ printnicks = g_hash_table_new((GHashFunc) g_direct_hash,
+ (GCompareFunc) g_direct_equal);
+
+ settings_add_bool("lookandfeel", "hilight_nick_matches", TRUE);
+ settings_add_bool("lookandfeel", "hilight_nick_matches_everywhere", FALSE);
+ settings_add_bool("lookandfeel", "emphasis", TRUE);
+ settings_add_bool("lookandfeel", "emphasis_replace", FALSE);
+ settings_add_bool("lookandfeel", "emphasis_multiword", FALSE);
+ settings_add_bool("lookandfeel", "emphasis_italics", FALSE);
+ settings_add_bool("lookandfeel", "show_nickmode", TRUE);
+ settings_add_bool("lookandfeel", "show_nickmode_empty", TRUE);
+ settings_add_bool("lookandfeel", "print_active_channel", FALSE);
+ settings_add_bool("lookandfeel", "show_quit_once", FALSE);
+ settings_add_bool("lookandfeel", "show_own_nickchange_once", FALSE);
+ settings_add_bool("lookandfeel", "away_notify_public", FALSE);
+ settings_add_bool("lookandfeel", "show_extended_join", FALSE);
+ settings_add_bool("lookandfeel", "show_account_notify", FALSE);
+
+ signal_add_last("message public", (SIGNAL_FUNC) sig_message_public);
+ signal_add_last("message private", (SIGNAL_FUNC) sig_message_private);
+ signal_add_last("message own_public", (SIGNAL_FUNC) sig_message_own_public);
+ signal_add_last("message own_private", (SIGNAL_FUNC) sig_message_own_private);
+ signal_add_last("message join", (SIGNAL_FUNC) sig_message_join);
+ signal_add_last("message host_changed", (SIGNAL_FUNC) sig_message_host_changed);
+ signal_add_last("message account_changed", (SIGNAL_FUNC) sig_message_account_changed);
+ signal_add_last("message part", (SIGNAL_FUNC) sig_message_part);
+ signal_add_last("message quit", (SIGNAL_FUNC) sig_message_quit);
+ signal_add_last("message kick", (SIGNAL_FUNC) sig_message_kick);
+ signal_add_last("message nick", (SIGNAL_FUNC) sig_message_nick);
+ signal_add_last("message own_nick", (SIGNAL_FUNC) sig_message_own_nick);
+ signal_add_last("message invite", (SIGNAL_FUNC) sig_message_invite);
+ signal_add_last("message invite_other", (SIGNAL_FUNC) sig_message_invite_other);
+ signal_add_last("message topic", (SIGNAL_FUNC) sig_message_topic);
+ signal_add_last("message away_notify", (SIGNAL_FUNC) sig_message_away_notify);
+
+ signal_add("nicklist new", (SIGNAL_FUNC) sig_nicklist_new);
+ signal_add("nicklist remove", (SIGNAL_FUNC) sig_nicklist_remove);
+ signal_add("nicklist changed", (SIGNAL_FUNC) sig_nicklist_changed);
+ signal_add("nicklist host changed", (SIGNAL_FUNC) sig_nicklist_new);
+ signal_add("channel joined", (SIGNAL_FUNC) sig_channel_joined);
+}
+
+void fe_messages_deinit(void)
+{
+ g_hash_table_foreach(printnicks, (GHFunc) i_hash_free_value, NULL);
+ g_hash_table_destroy(printnicks);
+
+ signal_remove("message public", (SIGNAL_FUNC) sig_message_public);
+ signal_remove("message private", (SIGNAL_FUNC) sig_message_private);
+ signal_remove("message own_public", (SIGNAL_FUNC) sig_message_own_public);
+ signal_remove("message own_private", (SIGNAL_FUNC) sig_message_own_private);
+ signal_remove("message join", (SIGNAL_FUNC) sig_message_join);
+ signal_remove("message host_changed", (SIGNAL_FUNC) sig_message_host_changed);
+ signal_remove("message account_changed", (SIGNAL_FUNC) sig_message_account_changed);
+ signal_remove("message part", (SIGNAL_FUNC) sig_message_part);
+ signal_remove("message quit", (SIGNAL_FUNC) sig_message_quit);
+ signal_remove("message kick", (SIGNAL_FUNC) sig_message_kick);
+ signal_remove("message nick", (SIGNAL_FUNC) sig_message_nick);
+ signal_remove("message own_nick", (SIGNAL_FUNC) sig_message_own_nick);
+ signal_remove("message invite_other", (SIGNAL_FUNC) sig_message_invite_other);
+ signal_remove("message invite", (SIGNAL_FUNC) sig_message_invite);
+ signal_remove("message topic", (SIGNAL_FUNC) sig_message_topic);
+ signal_remove("message away_notify", (SIGNAL_FUNC) sig_message_away_notify);
+
+ signal_remove("nicklist new", (SIGNAL_FUNC) sig_nicklist_new);
+ signal_remove("nicklist remove", (SIGNAL_FUNC) sig_nicklist_remove);
+ signal_remove("nicklist changed", (SIGNAL_FUNC) sig_nicklist_changed);
+ signal_remove("nicklist host changed", (SIGNAL_FUNC) sig_nicklist_new);
+ signal_remove("channel joined", (SIGNAL_FUNC) sig_channel_joined);
+}
diff --git a/src/fe-common/core/fe-messages.h b/src/fe-common/core/fe-messages.h
new file mode 100644
index 0000000..03f7da7
--- /dev/null
+++ b/src/fe-common/core/fe-messages.h
@@ -0,0 +1,12 @@
+#ifndef IRSSI_FE_COMMON_CORE_FE_MESSAGES_H
+#define IRSSI_FE_COMMON_CORE_FE_MESSAGES_H
+
+/* convert _underlined_ and *bold* words (and phrases) to use real
+ underlining or bolding */
+char *expand_emphasis(WI_ITEM_REC *item, const char *text);
+
+char *channel_get_nickmode(CHANNEL_REC *channel, const char *nick);
+
+extern GHashTable *printnicks;
+
+#endif
diff --git a/src/fe-common/core/fe-modules.c b/src/fe-common/core/fe-modules.c
new file mode 100644
index 0000000..955b9a9
--- /dev/null
+++ b/src/fe-common/core/fe-modules.c
@@ -0,0 +1,294 @@
+/*
+ fe-common-core.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/modules.h>
+#include <irssi/src/core/modules-load.h>
+#include <irssi/src/fe-common/core/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/chat-protocols.h>
+
+#include <irssi/src/fe-common/core/printtext.h>
+
+#ifdef HAVE_GMODULE
+
+static void sig_module_error(void *number, const char *data,
+ const char *rootmodule, const char *submodule)
+{
+ switch (GPOINTER_TO_INT(number)) {
+ case MODULE_ERROR_ALREADY_LOADED:
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_MODULE_ALREADY_LOADED, rootmodule, submodule);
+ break;
+ case MODULE_ERROR_LOAD:
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_MODULE_LOAD_ERROR, rootmodule, submodule, data);
+ break;
+ case MODULE_ERROR_VERSION_MISMATCH:
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_MODULE_VERSION_MISMATCH, rootmodule, submodule, data);
+ break;
+ case MODULE_ERROR_INVALID:
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_MODULE_INVALID, rootmodule, submodule);
+ break;
+ }
+}
+
+static void sig_module_loaded(MODULE_REC *module, MODULE_FILE_REC *file)
+{
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_MODULE_LOADED, module->name, file->name);
+}
+
+static void sig_module_unloaded(MODULE_REC *module, MODULE_FILE_REC *file)
+{
+ if (file != NULL && file->gmodule != NULL) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_MODULE_UNLOADED, module->name, file->name);
+ }
+}
+
+static int module_list_sub(MODULE_REC *module, int mark_type,
+ GString *submodules)
+{
+ GSList *tmp;
+ int all_dynamic, dynamic;
+
+ g_string_truncate(submodules, 0);
+
+ all_dynamic = -1;
+ for (tmp = module->files; tmp != NULL; tmp = tmp->next) {
+ MODULE_FILE_REC *file = tmp->data;
+
+ /* if there's dynamic and static modules mixed, we'll need
+ to specify them separately */
+ if (!mark_type) {
+ dynamic = file->gmodule != NULL;
+ if (all_dynamic != -1 && all_dynamic != dynamic) {
+ return module_list_sub(module, TRUE,
+ submodules);
+ }
+ all_dynamic = dynamic;
+ }
+
+ if (submodules->len > 0)
+ g_string_append_c(submodules, ' ');
+ g_string_append(submodules, file->name);
+ if (mark_type) {
+ g_string_append(submodules, file->gmodule == NULL ?
+ " (static)" : " (dynamic)");
+ }
+ }
+
+ return all_dynamic;
+}
+
+static void cmd_load_list(void)
+{
+ GSList *tmp;
+ GString *submodules;
+ const char *type;
+ int dynamic;
+
+ submodules = g_string_new(NULL);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_MODULE_HEADER);
+ for (tmp = modules; tmp != NULL; tmp = tmp->next) {
+ MODULE_REC *rec = tmp->data;
+
+ dynamic = module_list_sub(rec, FALSE, submodules);
+ type = dynamic == -1 ? "mixed" :
+ dynamic ? "dynamic" : "static";
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ TXT_MODULE_LINE, rec->name, type, submodules->str);
+ }
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_MODULE_FOOTER);
+
+ g_string_free(submodules, TRUE);
+}
+
+static char **module_prefixes_get(void)
+{
+ GSList *tmp;
+ char **list, *name;
+ int count;
+
+ list = g_new(char *, 3 + 3*g_slist_length(chat_protocols));
+ list[0] = "fe";
+ list[1] = "fe_common";
+
+ count = 2;
+ for (tmp = chat_protocols; tmp != NULL; tmp = tmp->next) {
+ CHAT_PROTOCOL_REC *rec = tmp->data;
+
+ name = g_ascii_strdown(rec->name, -1);
+
+ list[count++] = name;
+ list[count++] = g_strconcat("fe_", name, NULL);
+ list[count++] = g_strconcat("fe_common_", name, NULL);
+ }
+ list[count] = NULL;
+
+ return list;
+}
+
+static void module_prefixes_free(char **list)
+{
+ char **pos = list+2;
+
+ while (*pos != NULL) {
+ g_free(*pos);
+ pos++;
+ }
+ g_free(list);
+}
+
+/* SYNTAX: LOAD [-silent] <module> [<submodule>] */
+static void cmd_load(const char *data)
+{
+ char *rootmodule, *submodule;
+ char **module_prefixes;
+ void *free_arg;
+ gboolean silent;
+ GHashTable *optlist;
+
+ g_return_if_fail(data != NULL);
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS, "load", &optlist, &rootmodule,
+ &submodule))
+ return;
+
+ silent = g_hash_table_lookup(optlist, "silent") != NULL;
+
+ if (*rootmodule == '\0') {
+ if (!silent) {
+ cmd_load_list();
+ }
+ } else {
+ if (silent) {
+ signal_add_first("module error", (SIGNAL_FUNC) signal_stop);
+ signal_add_first("module loaded", (SIGNAL_FUNC) signal_stop);
+ }
+
+ module_prefixes = module_prefixes_get();
+ if (*submodule == '\0')
+ module_load(rootmodule, module_prefixes);
+ else {
+ module_load_sub(rootmodule, submodule,
+ module_prefixes);
+ }
+ module_prefixes_free(module_prefixes);
+
+ if (silent) {
+ signal_remove("module error", (SIGNAL_FUNC) signal_stop);
+ signal_remove("module loaded", (SIGNAL_FUNC) signal_stop);
+ }
+ }
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: UNLOAD <module> [<submodule>] */
+static void cmd_unload(const char *data)
+{
+ MODULE_REC *module;
+ MODULE_FILE_REC *file;
+ char *rootmodule, *submodule;
+ void *free_arg;
+ GSList *tmp;
+ int all_dynamic;
+
+ g_return_if_fail(data != NULL);
+
+ if (!cmd_get_params(data, &free_arg, 2 , &rootmodule, &submodule))
+ return;
+ if (*rootmodule == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ module = module_find(rootmodule);
+ if (module != NULL) {
+ if (*submodule == '\0') {
+ all_dynamic = 1;
+ for (tmp = module->files; tmp != NULL; tmp = tmp->next)
+ all_dynamic &= !MODULE_IS_STATIC((MODULE_FILE_REC*) tmp->data);
+ if (all_dynamic)
+ module_unload(module);
+ }
+ else {
+ file = module_file_find(module, submodule);
+ if (file != NULL) {
+ if (!MODULE_IS_STATIC(file))
+ module_file_unload(file);
+ }
+ else
+ module = NULL;
+ }
+ }
+
+ if (module == NULL) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_MODULE_NOT_LOADED, rootmodule, submodule);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+void fe_modules_init(void)
+{
+ signal_add("module error", (SIGNAL_FUNC) sig_module_error);
+ signal_add("module loaded", (SIGNAL_FUNC) sig_module_loaded);
+ signal_add("module unloaded", (SIGNAL_FUNC) sig_module_unloaded);
+
+ command_bind("load", NULL, (SIGNAL_FUNC) cmd_load);
+ command_bind("unload", NULL, (SIGNAL_FUNC) cmd_unload);
+ command_set_options("load", "silent");
+}
+
+void fe_modules_deinit(void)
+{
+ signal_remove("module error", (SIGNAL_FUNC) sig_module_error);
+ signal_remove("module loaded", (SIGNAL_FUNC) sig_module_loaded);
+ signal_remove("module unloaded", (SIGNAL_FUNC) sig_module_unloaded);
+
+ command_unbind("load", (SIGNAL_FUNC) cmd_load);
+ command_unbind("unload", (SIGNAL_FUNC) cmd_unload);
+}
+
+#else /* !HAVE_GMODULE */
+
+static void cmd_load(const char *data)
+{
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ "Dynamic modules loading not supported");
+}
+
+void fe_modules_init(void)
+{
+ command_bind("load", NULL, (SIGNAL_FUNC) cmd_load);
+}
+
+void fe_modules_deinit(void)
+{
+ command_unbind("load", (SIGNAL_FUNC) cmd_load);
+}
+#endif
diff --git a/src/fe-common/core/fe-queries.c b/src/fe-common/core/fe-queries.c
new file mode 100644
index 0000000..77f0747
--- /dev/null
+++ b/src/fe-common/core/fe-queries.c
@@ -0,0 +1,399 @@
+/*
+ fe-queries.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/core/module-formats.h>
+#include <irssi/src/core/modules.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/core/chat-protocols.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/queries.h>
+
+#include <irssi/src/fe-common/core/fe-core-commands.h>
+#include <irssi/src/fe-common/core/fe-windows.h>
+#include <irssi/src/fe-common/core/window-items.h>
+#include <irssi/src/fe-common/core/printtext.h>
+
+static int queryclose_tag, query_auto_close, querycreate_level;
+
+/* Return query where to put the private message. */
+QUERY_REC *privmsg_get_query(SERVER_REC *server, const char *nick,
+ int own, int level)
+{
+ QUERY_REC *query;
+
+ g_return_val_if_fail(IS_SERVER(server), NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ query = query_find(server, nick);
+ if (query == NULL && !command_hide_output &&
+ (querycreate_level & level) != 0 &&
+ (!own || settings_get_bool("autocreate_own_query"))) {
+ query = CHAT_PROTOCOL(server)->
+ query_create(server->tag, nick, TRUE);
+ }
+
+ return query;
+}
+
+static void signal_query_created(QUERY_REC *query, gpointer automatic)
+{
+ TEXT_DEST_REC dest;
+
+ g_return_if_fail(IS_QUERY(query));
+
+ if (window_item_window(query) == NULL) {
+ window_item_create((WI_ITEM_REC *) query,
+ GPOINTER_TO_INT(automatic));
+ }
+
+ format_create_dest_tag(&dest, query->server, query->server_tag,
+ query->name, MSGLEVEL_CLIENTNOTICE, NULL);
+ printformat_dest(&dest, TXT_QUERY_START,
+ query->name, query->server_tag);
+}
+
+static void signal_query_created_curwin(QUERY_REC *query)
+{
+ g_return_if_fail(IS_QUERY(query));
+
+ window_item_add(active_win, (WI_ITEM_REC *) query, FALSE);
+}
+
+static void signal_query_destroyed(QUERY_REC *query)
+{
+ WINDOW_REC *window;
+ TEXT_DEST_REC dest;
+
+ g_return_if_fail(IS_QUERY(query));
+
+ window = window_item_window((WI_ITEM_REC *) query);
+ if (window == NULL)
+ return;
+
+ format_create_dest_tag(&dest, query->server, query->server_tag,
+ query->name, MSGLEVEL_CLIENTNOTICE, NULL);
+ printformat_dest(&dest, TXT_QUERY_STOP, query->name);
+
+ window_item_destroy((WI_ITEM_REC *) query);
+
+ if (!query->unwanted)
+ window_auto_destroy(window);
+ else {
+ /* eg. connection lost to dcc chat */
+ window_bind_add(window, query->server_tag, query->name);
+ }
+}
+
+static void signal_query_server_changed(QUERY_REC *query)
+{
+ WINDOW_REC *window;
+
+ g_return_if_fail(query != NULL);
+
+ window = window_item_window((WI_ITEM_REC *) query);
+ if (window->active == (WI_ITEM_REC *) query)
+ window_change_server(window, query->server);
+}
+
+static void signal_query_nick_changed(QUERY_REC *query, const char *oldnick)
+{
+ TEXT_DEST_REC dest;
+
+ g_return_if_fail(query != NULL);
+
+ format_create_dest_tag(&dest, query->server, query->server_tag,
+ query->name, MSGLEVEL_NICKS, NULL);
+
+ /* don't print the nick change message if only the case was changed */
+ if (g_ascii_strcasecmp(query->name, oldnick) != 0) {
+ printformat_dest(&dest, TXT_NICK_CHANGED, oldnick,
+ query->name, query->name,
+ query->address == NULL ? "" : query->address);
+ }
+
+ signal_emit("window item changed", 2,
+ window_item_window((WI_ITEM_REC *) query), query);
+}
+
+static void signal_window_item_server_changed(WINDOW_REC *window,
+ QUERY_REC *query)
+{
+ if (IS_QUERY(query)) {
+ g_free_and_null(query->server_tag);
+ if (query->server != NULL)
+ query->server_tag = g_strdup(query->server->tag);
+ }
+}
+
+static void sig_server_connected(SERVER_REC *server)
+{
+ GSList *tmp;
+
+ if (!IS_SERVER(server))
+ return;
+
+ /* check if there's any queries without server */
+ for (tmp = queries; tmp != NULL; tmp = tmp->next) {
+ QUERY_REC *rec = tmp->data;
+
+ if (rec->server == NULL &&
+ (rec->server_tag == NULL ||
+ g_ascii_strcasecmp(rec->server_tag, server->tag) == 0)) {
+ window_item_change_server((WI_ITEM_REC *) rec, server);
+ server->queries = g_slist_append(server->queries, rec);
+ }
+ }
+}
+
+static void cmd_window_server(const char *data)
+{
+ SERVER_REC *server;
+ QUERY_REC *query;
+ TEXT_DEST_REC dest;
+
+ g_return_if_fail(data != NULL);
+
+ server = server_find_tag(data);
+ query = QUERY(active_win->active);
+ if (server == NULL || query == NULL)
+ return;
+
+ /* /WINDOW SERVER used in a query window */
+ format_create_dest_tag(&dest, query->server, query->server_tag,
+ query->name, MSGLEVEL_CLIENTNOTICE, NULL);
+ printformat_dest(&dest, TXT_QUERY_SERVER_CHANGED,
+ query->name, server->tag);
+
+ query_change_server(query, server);
+ signal_stop();
+}
+
+/* SYNTAX: UNQUERY [<nick>] */
+static void cmd_unquery(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ QUERY_REC *query;
+ char *nick;
+ void *free_arg;
+
+ g_return_if_fail(data != NULL);
+
+ if (!cmd_get_params(data, &free_arg, 1, &nick))
+ return;
+
+ if (*nick == '\0') {
+ /* remove current query */
+ query = QUERY(item);
+ } else {
+ query = query_find(server, nick);
+ if (query == NULL) {
+ printformat(server, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_NO_QUERY, nick);
+ }
+ }
+
+ if (query != NULL)
+ query_destroy(query);
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: QUERY [-window] [-<server tag>] <nick> [<message>] */
+static void cmd_query(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ GHashTable *optlist;
+ QUERY_REC *query;
+ char *nick, *msg;
+ void *free_arg;
+
+ g_return_if_fail(data != NULL);
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST |
+ PARAM_FLAG_OPTIONS | PARAM_FLAG_UNKNOWN_OPTIONS,
+ "query", &optlist, &nick, &msg))
+ return;
+
+ if (*nick == '\0') {
+ /* remove current query */
+ cmd_unquery("", server, item);
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ server = cmd_options_get_server("query", optlist, server);
+ if (server == NULL) {
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ if (*nick != '=' && (server == NULL || !server->connected))
+ cmd_param_error(CMDERR_NOT_CONNECTED);
+
+ if (g_hash_table_lookup(optlist, "window") != NULL) {
+ signal_add("query created",
+ (SIGNAL_FUNC) signal_query_created_curwin);
+ }
+
+ query = query_find(server, nick);
+ if (query == NULL)
+ query = CHAT_PROTOCOL(server)->
+ query_create(server->tag, nick, FALSE);
+ else {
+ /* query already exists, set it active */
+ WINDOW_REC *window = window_item_window(query);
+
+ if (window != active_win)
+ window_set_active(window);
+ window_item_set_active(active_win, (WI_ITEM_REC *) query);
+ }
+
+ if (g_hash_table_lookup(optlist, "window") != NULL) {
+ signal_remove("query created",
+ (SIGNAL_FUNC) signal_query_created_curwin);
+ }
+
+ if (*msg != '\0') {
+ msg = g_strdup_printf("-nick %s %s", nick, msg);
+ signal_emit("command msg", 3, msg, server, query);
+ g_free(msg);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+static void window_reset_query_timestamps(WINDOW_REC *window)
+{
+ GSList *tmp;
+
+ if (window == NULL)
+ return;
+
+ for (tmp = window->items; tmp != NULL; tmp = tmp->next) {
+ QUERY_REC *query = QUERY(tmp->data);
+
+ if (query != NULL)
+ query->last_unread_msg = time(NULL);
+ }
+}
+
+static void sig_window_changed(WINDOW_REC *window, WINDOW_REC *old_window)
+{
+ /* reset the queries last_unread_msg so query doesn't get closed
+ immediately after switched to the window, or after changed to
+ some other window from it */
+ window_reset_query_timestamps(window);
+ window_reset_query_timestamps(old_window);
+}
+
+static int sig_query_autoclose(void)
+{
+ WINDOW_REC *window;
+ GSList *tmp, *next;
+ time_t now;
+
+ now = time(NULL);
+ for (tmp = queries; tmp != NULL; tmp = next) {
+ QUERY_REC *rec = tmp->data;
+
+ next = tmp->next;
+ window = window_item_window((WI_ITEM_REC *) rec);
+ if (window != active_win && rec->data_level < DATA_LEVEL_MSG &&
+ now-rec->last_unread_msg > query_auto_close)
+ query_destroy(rec);
+ }
+ return 1;
+}
+
+static void sig_message_private(SERVER_REC *server, const char *msg,
+ const char *nick, const char *address, const char *target)
+{
+ QUERY_REC *query;
+
+ /* own message returned by bouncer? */
+ int own = (!g_strcmp0(nick, server->nick));
+
+ /* create query window if needed */
+ query = privmsg_get_query(server, own ? target : nick, FALSE, MSGLEVEL_MSGS);
+
+ /* reset the query's last_unread_msg timestamp */
+ if (query != NULL)
+ query->last_unread_msg = time(NULL);
+}
+
+static void read_settings(void)
+{
+ querycreate_level = settings_get_level("autocreate_query_level");
+ query_auto_close = settings_get_time("autoclose_query")/1000;
+ if (query_auto_close > 0 && queryclose_tag == -1)
+ queryclose_tag = g_timeout_add(5000, (GSourceFunc) sig_query_autoclose, NULL);
+ else if (query_auto_close <= 0 && queryclose_tag != -1) {
+ g_source_remove(queryclose_tag);
+ queryclose_tag = -1;
+ }
+}
+
+void fe_queries_init(void)
+{
+ settings_add_level("lookandfeel", "autocreate_query_level", "MSGS DCCMSGS");
+ settings_add_bool("lookandfeel", "autocreate_own_query", TRUE);
+ settings_add_time("lookandfeel", "autoclose_query", "0");
+
+ queryclose_tag = -1;
+ read_settings();
+
+ signal_add("query created", (SIGNAL_FUNC) signal_query_created);
+ signal_add("query destroyed", (SIGNAL_FUNC) signal_query_destroyed);
+ signal_add("query server changed", (SIGNAL_FUNC) signal_query_server_changed);
+ signal_add("query nick changed", (SIGNAL_FUNC) signal_query_nick_changed);
+ signal_add("window item server changed", (SIGNAL_FUNC) signal_window_item_server_changed);
+ signal_add("server connected", (SIGNAL_FUNC) sig_server_connected);
+ signal_add("window changed", (SIGNAL_FUNC) sig_window_changed);
+ signal_add_first("message private", (SIGNAL_FUNC) sig_message_private);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+
+ command_bind("query", NULL, (SIGNAL_FUNC) cmd_query);
+ command_bind("unquery", NULL, (SIGNAL_FUNC) cmd_unquery);
+ command_bind("window server", NULL, (SIGNAL_FUNC) cmd_window_server);
+
+ command_set_options("query", "window");
+}
+
+void fe_queries_deinit(void)
+{
+ if (queryclose_tag != -1) g_source_remove(queryclose_tag);
+
+ signal_remove("query created", (SIGNAL_FUNC) signal_query_created);
+ signal_remove("query destroyed", (SIGNAL_FUNC) signal_query_destroyed);
+ signal_remove("query server changed", (SIGNAL_FUNC) signal_query_server_changed);
+ signal_remove("query nick changed", (SIGNAL_FUNC) signal_query_nick_changed);
+ signal_remove("window item server changed", (SIGNAL_FUNC) signal_window_item_server_changed);
+ signal_remove("server connected", (SIGNAL_FUNC) sig_server_connected);
+ signal_remove("window changed", (SIGNAL_FUNC) sig_window_changed);
+ signal_remove("message private", (SIGNAL_FUNC) sig_message_private);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+
+ command_unbind("query", (SIGNAL_FUNC) cmd_query);
+ command_unbind("unquery", (SIGNAL_FUNC) cmd_unquery);
+ command_unbind("window server", (SIGNAL_FUNC) cmd_window_server);
+}
diff --git a/src/fe-common/core/fe-queries.h b/src/fe-common/core/fe-queries.h
new file mode 100644
index 0000000..30c45e4
--- /dev/null
+++ b/src/fe-common/core/fe-queries.h
@@ -0,0 +1,13 @@
+#ifndef IRSSI_FE_COMMON_CORE_FE_QUERIES_H
+#define IRSSI_FE_COMMON_CORE_FE_QUERIES_H
+
+#include <irssi/src/core/queries.h>
+
+/* Return query where to put the private message. */
+QUERY_REC *privmsg_get_query(SERVER_REC *server, const char *nick,
+ int own, int level);
+
+void fe_queries_init(void);
+void fe_queries_deinit(void);
+
+#endif
diff --git a/src/fe-common/core/fe-recode.c b/src/fe-common/core/fe-recode.c
new file mode 100644
index 0000000..5e7c1e4
--- /dev/null
+++ b/src/fe-common/core/fe-recode.c
@@ -0,0 +1,208 @@
+/*
+ fe-recode.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/modules.h>
+#include <irssi/src/fe-common/core/module-formats.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/lib-config/iconfig.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/fe-common/core/printtext.h>
+#include <irssi/src/fe-common/core/formats.h>
+#include <irssi/src/core/recode.h>
+
+static char *recode_fallback = NULL;
+static char *recode_out_default = NULL;
+static char *term_charset = NULL;
+
+static const char *fe_recode_get_target (WI_ITEM_REC *witem)
+{
+ if (witem && (witem->type == module_get_uniq_id_str("WINDOW ITEM TYPE", "QUERY")
+ || witem->type == module_get_uniq_id_str("WINDOW ITEM TYPE", "CHANNEL")))
+ return window_item_get_target(witem);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_NOT_CHANNEL_OR_QUERY);
+ return NULL;
+}
+
+static int fe_recode_compare_func (CONFIG_NODE *node1, CONFIG_NODE *node2)
+{
+ return g_strcmp0(node1->key, node2->key);
+}
+
+/* SYNTAX: RECODE */
+static void fe_recode_cmd (const char *data, SERVER_REC *server, WI_ITEM_REC *witem)
+{
+ if (*data)
+ command_runsub("recode", data, server, witem);
+ else {
+ CONFIG_NODE *conversions;
+ GSList *tmp;
+ GSList *sorted = NULL;
+
+ conversions = iconfig_node_traverse("conversions", FALSE);
+
+ for (tmp = conversions ? config_node_first(conversions->value) : NULL;
+ tmp != NULL;
+ tmp = config_node_next(tmp)) {
+ CONFIG_NODE *node = tmp->data;
+
+ if (node->type == NODE_TYPE_KEY)
+ sorted = g_slist_insert_sorted(sorted, node, (GCompareFunc) fe_recode_compare_func);
+ }
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_RECODE_HEADER);
+ for (tmp = sorted; tmp != NULL; tmp = tmp->next) {
+ CONFIG_NODE *node = tmp->data;
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_RECODE_LINE, node->key, node->value);
+ }
+
+ g_slist_free(sorted);
+ }
+}
+
+/* SYNTAX: RECODE ADD [[<tag>/]<target>] <charset> */
+static void fe_recode_add_cmd (const char *data, SERVER_REC *server, WI_ITEM_REC *witem)
+{
+ const char *first;
+ const char *second;
+ const char *target;
+ const char *charset;
+ void *free_arg;
+
+ if (! cmd_get_params(data, &free_arg, 2, &first, &second))
+ return;
+
+ if (! *first)
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ if (*second) {
+ target = first;
+ charset = second;
+ } else {
+ target = fe_recode_get_target(witem);
+ charset = first;
+ if (! target)
+ goto end;
+ }
+ if (is_valid_charset(charset)) {
+ iconfig_set_str("conversions", target, charset);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_CONVERSION_ADDED, target, charset);
+ } else
+ signal_emit("error command", 2, GINT_TO_POINTER(CMDERR_INVALID_CHARSET), charset);
+ end:
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: RECODE REMOVE [<target>] */
+static void fe_recode_remove_cmd (const char *data, SERVER_REC *server, WI_ITEM_REC *witem)
+{
+ const char *target;
+ void *free_arg;
+
+ if (! cmd_get_params(data, &free_arg, 1, &target))
+ return;
+
+ if (! *target) {
+ target = fe_recode_get_target(witem);
+ if (! target)
+ goto end;
+ }
+
+ if (iconfig_get_str("conversions", target, NULL) == NULL)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_CONVERSION_NOT_FOUND, target);
+ else {
+ iconfig_set_str("conversions", target, NULL);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_CONVERSION_REMOVED, target);
+ }
+
+ end:
+ cmd_params_free(free_arg);
+}
+
+static void read_settings(void)
+{
+ /* preserve the valid values */
+ char *old_term_charset = g_strdup(term_charset);
+ char *old_recode_fallback = g_strdup(recode_fallback);
+ char *old_recode_out_default = g_strdup(recode_out_default);
+
+ if (settings_get_bool("recode_transliterate")) {
+ /* check if transliterations are supported in this system */
+ if (!is_valid_charset("ASCII")) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_CONVERSION_NO_TRANSLITS);
+ settings_set_bool("recode_transliterate", FALSE);
+ }
+ }
+
+ if (recode_fallback)
+ g_free(recode_fallback);
+ recode_fallback = g_strdup(settings_get_str("recode_fallback"));
+ if (!is_valid_charset(recode_fallback)) {
+ signal_emit("error command", 2, GINT_TO_POINTER(CMDERR_INVALID_CHARSET), recode_fallback);
+ g_free(recode_fallback);
+ recode_fallback = is_valid_charset(old_recode_fallback) ? g_strdup(old_recode_fallback) : NULL;
+ settings_set_str("recode_fallback", recode_fallback);
+ }
+
+ if (term_charset)
+ g_free(term_charset);
+ term_charset = g_strdup(settings_get_str("term_charset"));
+ if (!is_valid_charset(term_charset)) {
+ g_free(term_charset);
+ term_charset = is_valid_charset(old_term_charset) ? g_strdup(old_term_charset) : NULL;
+ settings_set_str("term_charset", term_charset);
+ }
+ recode_update_charset();
+
+ if (recode_out_default)
+ g_free(recode_out_default);
+ recode_out_default = g_strdup(settings_get_str("recode_out_default_charset"));
+ if (recode_out_default != NULL && *recode_out_default != '\0' &&
+ !is_valid_charset(recode_out_default)) {
+ signal_emit("error command", 2, GINT_TO_POINTER(CMDERR_INVALID_CHARSET), recode_out_default);
+ g_free(recode_out_default);
+ recode_out_default = is_valid_charset(old_recode_out_default) ? g_strdup(old_recode_out_default) : NULL;
+ settings_set_str("recode_out_default_charset", recode_out_default);
+ }
+
+ g_free(old_term_charset);
+ g_free(old_recode_fallback);
+ g_free(old_recode_out_default);
+}
+
+void fe_recode_init (void)
+{
+ command_bind("recode", NULL, (SIGNAL_FUNC) fe_recode_cmd);
+ command_bind("recode add", NULL, (SIGNAL_FUNC) fe_recode_add_cmd);
+ command_bind("recode remove", NULL, (SIGNAL_FUNC) fe_recode_remove_cmd);
+ signal_add_first("setup changed", (SIGNAL_FUNC) read_settings);
+ read_settings();
+}
+
+void fe_recode_deinit (void)
+{
+ command_unbind("recode", (SIGNAL_FUNC) fe_recode_cmd);
+ command_unbind("recode add", (SIGNAL_FUNC) fe_recode_add_cmd);
+ command_unbind("recode remove", (SIGNAL_FUNC) fe_recode_remove_cmd);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
diff --git a/src/fe-common/core/fe-recode.h b/src/fe-common/core/fe-recode.h
new file mode 100644
index 0000000..50775f2
--- /dev/null
+++ b/src/fe-common/core/fe-recode.h
@@ -0,0 +1,7 @@
+#ifndef IRSSI_FE_COMMON_CORE_FE_RECODE_H
+#define IRSSI_FE_COMMON_CORE_FE_RECODE_H
+
+void fe_recode_init (void);
+void fe_recode_deinit (void);
+
+#endif /* IRSSI_FE_COMMON_CORE_FE_RECODE_H */
diff --git a/src/fe-common/core/fe-server.c b/src/fe-common/core/fe-server.c
new file mode 100644
index 0000000..ab76c36
--- /dev/null
+++ b/src/fe-common/core/fe-server.c
@@ -0,0 +1,548 @@
+/*
+ fe-server.c : irssi
+
+ Copyright (C) 1999-2001 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/network.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/core/chat-protocols.h>
+#include <irssi/src/core/chatnets.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/servers-setup.h>
+#include <irssi/src/core/servers-reconnect.h>
+
+#include <irssi/src/fe-common/core/module-formats.h>
+#include <irssi/src/fe-common/core/printtext.h>
+
+static void print_servers(void)
+{
+ GSList *tmp;
+
+ for (tmp = servers; tmp != NULL; tmp = tmp->next) {
+ SERVER_REC *rec = tmp->data;
+
+ printformat(NULL, NULL, MSGLEVEL_CRAP, TXT_SERVER_LIST,
+ rec->tag, rec->connrec->address, rec->connrec->port,
+ rec->connrec->chatnet == NULL ? "" : rec->connrec->chatnet, rec->connrec->nick);
+ }
+}
+
+static void print_lookup_servers(void)
+{
+ GSList *tmp;
+ for (tmp = lookup_servers; tmp != NULL; tmp = tmp->next) {
+ SERVER_REC *rec = tmp->data;
+
+ printformat(NULL, NULL, MSGLEVEL_CRAP, TXT_SERVER_LOOKUP_LIST,
+ rec->tag, rec->connrec->address, rec->connrec->port,
+ rec->connrec->chatnet == NULL ? "" : rec->connrec->chatnet, rec->connrec->nick);
+ }
+}
+
+static void print_reconnects(void)
+{
+ GSList *tmp;
+ char *tag, *next_connect;
+ int left;
+
+ for (tmp = reconnects; tmp != NULL; tmp = tmp->next) {
+ RECONNECT_REC *rec = tmp->data;
+ SERVER_CONNECT_REC *conn = rec->conn;
+
+ tag = g_strdup_printf("RECON-%d", rec->tag);
+ left = rec->next_connect-time(NULL);
+ next_connect = g_strdup_printf("%02d:%02d", left/60, left%60);
+ printformat(NULL, NULL, MSGLEVEL_CRAP, TXT_SERVER_RECONNECT_LIST,
+ tag, conn->address, conn->port,
+ conn->chatnet == NULL ? "" : conn->chatnet,
+ conn->nick, next_connect);
+ g_free(next_connect);
+ g_free(tag);
+ }
+}
+
+static SERVER_SETUP_REC *create_server_setup(GHashTable *optlist)
+{
+ CHAT_PROTOCOL_REC *rec;
+ SERVER_SETUP_REC *server;
+ char *chatnet;
+
+ rec = chat_protocol_find_net(optlist);
+ if (rec == NULL)
+ rec = chat_protocol_get_default();
+ else {
+ chatnet = g_hash_table_lookup(optlist, "network");
+ if (chatnet == NULL && g_hash_table_lookup(optlist, rec->chatnet) != NULL)
+ chatnet = rec->chatnet;
+ if (chatnet_find(chatnet) == NULL) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_UNKNOWN_CHATNET, chatnet);
+ return NULL;
+ }
+ }
+
+ server = rec->create_server_setup();
+ server->chat_type = rec->id;
+ server->tls_verify = TRUE;
+ return server;
+}
+
+static void cmd_server_add_modify(const char *data, gboolean add)
+{
+ GHashTable *optlist;
+ SERVER_SETUP_REC *rec, *tmp;
+ char *addr, *portstr, *password, *value, *chatnet, *old_chatnet;
+ void *free_arg;
+ gboolean newrec;
+ int port, old_port, add_port;
+
+ if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_OPTIONS,
+ "server add", &optlist, &addr, &portstr, &password))
+ return;
+
+ if (*addr == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ port = old_port = -1;
+
+ value = g_hash_table_lookup(optlist, "port");
+ if (value != NULL && *value != '\0')
+ port = add_port = atoi(value);
+ else if (g_hash_table_lookup(optlist, "tls") ||
+ g_hash_table_lookup(optlist, "ssl"))
+ add_port = DEFAULT_SERVER_ADD_TLS_PORT;
+ else
+ add_port = DEFAULT_SERVER_ADD_PORT;
+
+ if (*portstr != '\0')
+ old_port = atoi(portstr);
+
+ chatnet = g_hash_table_lookup(optlist, "network");
+
+ rec = server_setup_find(addr, old_port != -1 ? old_port : add_port, chatnet);
+ if (old_port == -1 && rec != NULL)
+ old_port = rec->port;
+
+ if (port == -1)
+ port = old_port != -1 ? old_port : add_port;
+
+ /* make sure the new port doesn't exist */
+ tmp = server_setup_find(addr, port, chatnet);
+ if (tmp != NULL && tmp->port == port)
+ rec = tmp;
+
+ if (rec == NULL || (rec->port != old_port && rec->port != port)) {
+ newrec = TRUE;
+ if (add == FALSE) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_SETUPSERVER_NOT_FOUND,
+ addr, old_port == -1 ? port : old_port);
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ rec = create_server_setup(optlist);
+ if (rec == NULL) {
+ cmd_params_free(free_arg);
+ return;
+ }
+ rec->address = g_strdup(addr);
+ rec->port = port;
+ } else {
+ newrec = FALSE;
+ old_chatnet = g_strdup(rec->chatnet);
+ old_port = rec->port;
+ rec->port = port;
+
+ if (*password != '\0') g_free_and_null(rec->password);
+ if (g_hash_table_lookup(optlist, "host")) {
+ g_free_and_null(rec->own_host);
+ rec->own_ip4 = rec->own_ip6 = NULL;
+ }
+ }
+
+ if (g_hash_table_lookup(optlist, "6"))
+ rec->family = AF_INET6;
+ else if (g_hash_table_lookup(optlist, "4"))
+ rec->family = AF_INET;
+
+ value = g_hash_table_lookup(optlist, "tls_cert");
+ if (value == NULL)
+ value = g_hash_table_lookup(optlist, "ssl_cert");
+ if (value != NULL && *value != '\0') {
+ rec->tls_cert = g_strdup(value);
+ if (newrec) {
+ /* convenience and backward compatibility, turn on tls if tls_cert is given
+ */
+ rec->use_tls = TRUE;
+ }
+ }
+
+ value = g_hash_table_lookup(optlist, "tls_pkey");
+ if (value == NULL)
+ value = g_hash_table_lookup(optlist, "ssl_pkey");
+ if (value != NULL && *value != '\0')
+ rec->tls_pkey = g_strdup(value);
+
+ value = g_hash_table_lookup(optlist, "tls_pass");
+ if (value == NULL)
+ value = g_hash_table_lookup(optlist, "ssl_pass");
+ if (value != NULL && *value != '\0')
+ rec->tls_pass = g_strdup(value);
+
+ value = g_hash_table_lookup(optlist, "tls_cafile");
+ if (value == NULL)
+ value = g_hash_table_lookup(optlist, "ssl_cafile");
+ if (value != NULL && *value != '\0')
+ rec->tls_cafile = g_strdup(value);
+ else if (value != NULL && *value == '\0')
+ g_free_and_null(rec->tls_cafile);
+
+ value = g_hash_table_lookup(optlist, "tls_capath");
+ if (value == NULL)
+ value = g_hash_table_lookup(optlist, "ssl_capath");
+ if (value != NULL && *value != '\0')
+ rec->tls_capath = g_strdup(value);
+ else if (value != NULL && *value == '\0')
+ g_free_and_null(rec->tls_capath);
+
+ value = g_hash_table_lookup(optlist, "tls_ciphers");
+ if (value == NULL)
+ value = g_hash_table_lookup(optlist, "ssl_ciphers");
+ if (value != NULL && *value != '\0')
+ rec->tls_ciphers = g_strdup(value);
+
+ value = g_hash_table_lookup(optlist, "tls_pinned_cert");
+ if (value == NULL)
+ value = g_hash_table_lookup(optlist, "ssl_pinned_cert");
+ if (value != NULL && *value != '\0')
+ rec->tls_pinned_cert = g_strdup(value);
+
+ value = g_hash_table_lookup(optlist, "tls_pinned_pubkey");
+ if (value == NULL)
+ value = g_hash_table_lookup(optlist, "ssl_pinned_pubkey");
+ if (value != NULL && *value != '\0')
+ rec->tls_pinned_pubkey = g_strdup(value);
+
+ if ((rec->tls_cafile != NULL && rec->tls_cafile[0] != '\0')
+ || (rec->tls_capath != NULL && rec->tls_capath[0] != '\0'))
+ rec->tls_verify = TRUE;
+
+ if (g_hash_table_lookup(optlist, "tls_verify") ||
+ g_hash_table_lookup(optlist, "ssl_verify")) {
+ rec->tls_verify = TRUE;
+ if (newrec) {
+ /* convenience and backward compatibility, turn on tls if tls_verify is
+ * given */
+ rec->use_tls = TRUE;
+ }
+ } else if (g_hash_table_lookup(optlist, "notls_verify") ||
+ g_hash_table_lookup(optlist, "nossl_verify")) {
+ rec->tls_verify = FALSE;
+ }
+
+ if (g_hash_table_lookup(optlist, "tls") || g_hash_table_lookup(optlist, "ssl"))
+ rec->use_tls = TRUE;
+ else if (g_hash_table_lookup(optlist, "notls") || g_hash_table_lookup(optlist, "nossl"))
+ rec->use_tls = FALSE;
+
+ if (g_hash_table_lookup(optlist, "auto")) rec->autoconnect = TRUE;
+ if (g_hash_table_lookup(optlist, "noauto")) rec->autoconnect = FALSE;
+ if (g_hash_table_lookup(optlist, "proxy")) rec->no_proxy = FALSE;
+ if (g_hash_table_lookup(optlist, "noproxy")) rec->no_proxy = TRUE;
+
+ if (*password != '\0' && g_strcmp0(password, "-") != 0) rec->password = g_strdup(password);
+ value = g_hash_table_lookup(optlist, "host");
+ if (value != NULL && *value != '\0') {
+ rec->own_host = g_strdup(value);
+ rec->own_ip4 = rec->own_ip6 = NULL;
+ }
+
+ signal_emit("server add fill", 3, rec, optlist, GINT_TO_POINTER(add));
+
+ if (newrec) {
+ server_setup_add(rec);
+ } else {
+ server_setup_modify(rec, old_port, old_chatnet);
+ g_free(old_chatnet);
+ }
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_SETUPSERVER_ADDED, addr, port);
+
+ cmd_params_free(free_arg);
+}
+
+static void cmd_server_add(const char *data)
+{
+ cmd_server_add_modify(data, TRUE);
+}
+
+static void cmd_server_modify(const char *data)
+{
+ cmd_server_add_modify(data, FALSE);
+}
+
+/* SYNTAX: SERVER REMOVE <address> [<port>] [<network>] */
+static void cmd_server_remove(const char *data)
+{
+ SERVER_SETUP_REC *rec;
+ char *addr, *port, *chatnet;
+ void *free_arg;
+ int portnum;
+
+ if (!cmd_get_params(data, &free_arg, 3, &addr, &port, &chatnet))
+ return;
+ if (*addr == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ if (*port == '\0') {
+ portnum = DEFAULT_SERVER_ADD_PORT;
+ if (*chatnet == '\0')
+ rec = server_setup_find(addr, -1, NULL);
+ else
+ rec = server_setup_find(addr, -1, chatnet);
+ }
+ else
+ {
+ portnum = atoi(port);
+ if (*chatnet == '\0')
+ rec = server_setup_find(addr, portnum, NULL);
+ else
+ rec = server_setup_find(addr, portnum, chatnet);
+ }
+
+ if (rec == NULL)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_SETUPSERVER_NOT_FOUND, addr,
+ portnum);
+ else {
+ portnum = rec->port;
+ server_setup_remove(rec);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_SETUPSERVER_REMOVED, addr,
+ portnum);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+static void cmd_server(const char *data)
+{
+ if (*data != '\0')
+ return;
+
+ if (servers == NULL && lookup_servers == NULL &&
+ reconnects == NULL) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_NO_CONNECTED_SERVERS);
+ } else {
+ print_servers();
+ print_lookup_servers();
+ print_reconnects();
+ }
+
+ signal_stop();
+}
+
+static void cmd_server_connect(const char *data)
+{
+ GHashTable *optlist;
+ char *addr;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS,
+ "connect", &optlist, &addr))
+ return;
+
+ if (*addr == '\0' || g_strcmp0(addr, "+") == 0)
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+ if (*addr == '+') window_create(NULL, FALSE);
+
+ cmd_params_free(free_arg);
+}
+
+static void server_command(const char *data, SERVER_REC *server,
+ WI_ITEM_REC *item)
+{
+ if (server == NULL) {
+ /* this command accepts non-connected server too */
+ server = active_win->connect_server;
+ }
+
+ signal_continue(3, data, server, item);
+}
+
+static void sig_server_looking(SERVER_REC *server)
+{
+ g_return_if_fail(server != NULL);
+
+ printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOOKING_UP, server->connrec->address);
+}
+
+static void sig_server_connecting(SERVER_REC *server, IPADDR *ip)
+{
+ char ipaddr[MAX_IP_LEN];
+
+ g_return_if_fail(server != NULL);
+
+ if (ip == NULL)
+ ipaddr[0] = '\0';
+ else
+ net_ip2host(ip, ipaddr);
+
+ printformat(server, NULL, MSGLEVEL_CLIENTNOTICE,
+ !server->connrec->reconnecting ?
+ TXT_CONNECTING : TXT_RECONNECTING,
+ server->connrec->address, ipaddr, server->connrec->port);
+}
+
+static void sig_server_connected(SERVER_REC *server)
+{
+ g_return_if_fail(server != NULL);
+
+ printformat(server, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_CONNECTION_ESTABLISHED, server->connrec->address);
+}
+
+static void sig_connect_failed(SERVER_REC *server, gchar *msg)
+{
+ g_return_if_fail(server != NULL);
+
+ if (msg == NULL) {
+ /* no message so this wasn't unexpected fail - send
+ connection_lost message instead */
+ printformat(server, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_CONNECTION_LOST, server->connrec->address);
+ } else {
+ printformat(server, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_CANT_CONNECT, server->connrec->address, server->connrec->port, msg);
+ }
+}
+
+static void sig_server_disconnected(SERVER_REC *server)
+{
+ g_return_if_fail(server != NULL);
+
+ printformat(server, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_CONNECTION_LOST, server->connrec->address);
+}
+
+static void sig_server_quit(SERVER_REC *server, const char *msg)
+{
+ g_return_if_fail(server != NULL);
+
+ printformat(server, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_SERVER_QUIT, server->connrec->address, msg);
+}
+
+static void sig_server_lag_disconnected(SERVER_REC *server)
+{
+ g_return_if_fail(server != NULL);
+
+ printformat(server, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_LAG_DISCONNECTED, server->connrec->address,
+ time(NULL)-(server->lag_sent / G_TIME_SPAN_SECOND));
+}
+
+static void sig_server_reconnect_removed(RECONNECT_REC *reconnect)
+{
+ g_return_if_fail(reconnect != NULL);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_RECONNECT_REMOVED, reconnect->conn->address, reconnect->conn->port,
+ reconnect->conn->chatnet == NULL ? "" : reconnect->conn->chatnet);
+}
+
+static void sig_server_reconnect_not_found(const char *tag)
+{
+ g_return_if_fail(tag != NULL);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_RECONNECT_NOT_FOUND, tag);
+}
+
+static void sig_chat_protocol_unknown(const char *protocol)
+{
+ g_return_if_fail(protocol != NULL);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_UNKNOWN_CHAT_PROTOCOL, protocol);
+}
+
+void fe_server_init(void)
+{
+ command_bind("server", NULL, (SIGNAL_FUNC) cmd_server);
+ command_bind("server connect", NULL, (SIGNAL_FUNC) cmd_server_connect);
+ command_bind("server add", NULL, (SIGNAL_FUNC) cmd_server_add);
+ command_bind("server modify", NULL, (SIGNAL_FUNC) cmd_server_modify);
+ command_bind("server remove", NULL, (SIGNAL_FUNC) cmd_server_remove);
+ command_bind_first("server", NULL, (SIGNAL_FUNC) server_command);
+ command_bind_first("disconnect", NULL, (SIGNAL_FUNC) server_command);
+
+ command_set_options(
+ "server add", "4 6 !! ~ssl ~nossl ~+ssl_cert ~+ssl_pkey ~+ssl_pass ~ssl_verify "
+ "~nossl_verify ~+ssl_cafile ~+ssl_capath ~+ssl_ciphers ~+ssl_fingerprint "
+ "tls notls +tls_cert +tls_pkey +tls_pass tls_verify notls_verify "
+ "+tls_cafile +tls_capath +tls_ciphers +tls_pinned_cert "
+ "+tls_pinned_pubkey auto noauto proxy noproxy -host -port noautosendcmd");
+ command_set_options(
+ "server modify",
+ "4 6 !! ~ssl ~nossl ~+ssl_cert ~+ssl_pkey ~+ssl_pass ~ssl_verify ~nossl_verify "
+ "~+ssl_cafile ~+ssl_capath ~+ssl_ciphers ~+ssl_fingerprint tls notls +tls_cert "
+ "+tls_pkey +tls_pass tls_verify notls_verify +tls_cafile +tls_capath +tls_ciphers "
+ "+tls_pinned_cert +tls_pinned_pubkey auto noauto proxy noproxy -host -port "
+ "noautosendcmd");
+
+ signal_add("server looking", (SIGNAL_FUNC) sig_server_looking);
+ signal_add("server connecting", (SIGNAL_FUNC) sig_server_connecting);
+ signal_add("server connected", (SIGNAL_FUNC) sig_server_connected);
+ signal_add("server connect failed", (SIGNAL_FUNC) sig_connect_failed);
+ signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+ signal_add("server quit", (SIGNAL_FUNC) sig_server_quit);
+
+ signal_add("server lag disconnect", (SIGNAL_FUNC) sig_server_lag_disconnected);
+ signal_add("server reconnect remove", (SIGNAL_FUNC) sig_server_reconnect_removed);
+ signal_add("server reconnect not found", (SIGNAL_FUNC) sig_server_reconnect_not_found);
+
+ signal_add("chat protocol unknown", (SIGNAL_FUNC) sig_chat_protocol_unknown);
+}
+
+void fe_server_deinit(void)
+{
+ command_unbind("server", (SIGNAL_FUNC) cmd_server);
+ command_unbind("server connect", (SIGNAL_FUNC) cmd_server_connect);
+ command_unbind("server add", (SIGNAL_FUNC) cmd_server_add);
+ command_unbind("server modify", (SIGNAL_FUNC) cmd_server_modify);
+ command_unbind("server remove", (SIGNAL_FUNC) cmd_server_remove);
+ command_unbind("server", (SIGNAL_FUNC) server_command);
+ command_unbind("disconnect", (SIGNAL_FUNC) server_command);
+
+ signal_remove("server looking", (SIGNAL_FUNC) sig_server_looking);
+ signal_remove("server connecting", (SIGNAL_FUNC) sig_server_connecting);
+ signal_remove("server connected", (SIGNAL_FUNC) sig_server_connected);
+ signal_remove("server connect failed", (SIGNAL_FUNC) sig_connect_failed);
+ signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+ signal_remove("server quit", (SIGNAL_FUNC) sig_server_quit);
+
+ signal_remove("server lag disconnect", (SIGNAL_FUNC) sig_server_lag_disconnected);
+ signal_remove("server reconnect remove", (SIGNAL_FUNC) sig_server_reconnect_removed);
+ signal_remove("server reconnect not found", (SIGNAL_FUNC) sig_server_reconnect_not_found);
+
+ signal_remove("chat protocol unknown", (SIGNAL_FUNC) sig_chat_protocol_unknown);
+}
diff --git a/src/fe-common/core/fe-settings.c b/src/fe-common/core/fe-settings.c
new file mode 100644
index 0000000..f686916
--- /dev/null
+++ b/src/fe-common/core/fe-settings.c
@@ -0,0 +1,452 @@
+/*
+ fe-settings.c : irssi
+
+ Copyright (C) 1999 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/core/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/lib-config/iconfig.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/fe-common/core/fe-settings.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/fe-common/core/printtext.h>
+#include <irssi/src/fe-common/core/keyboard.h>
+
+static void set_print(SETTINGS_REC *rec)
+{
+ char *value;
+
+ value = settings_get_print(rec);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_SET_ITEM,
+ rec->key, value);
+ g_free(value);
+}
+
+void fe_settings_set_print(const char *key)
+{
+ set_print(settings_get_record(key));
+}
+
+static void set_print_pattern(const char *pattern)
+{
+ GSList *sets, *tmp;
+ const char *last_section;
+
+ last_section = "";
+ sets = settings_get_sorted();
+ for (tmp = sets; tmp != NULL; tmp = tmp->next) {
+ SETTINGS_REC *rec = tmp->data;
+
+ if (stristr(rec->key, pattern) == NULL)
+ continue;
+ if (g_strcmp0(last_section, rec->section) != 0) {
+ /* print section */
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ TXT_SET_TITLE, rec->section);
+ last_section = rec->section;
+ }
+ set_print(rec);
+ }
+ g_slist_free(sets);
+}
+
+static void set_print_section(const char *pattern)
+{
+ GSList *sets, *tmp;
+ const char *last_section;
+
+ last_section = "";
+ sets = settings_get_sorted();
+ for (tmp = sets; tmp != NULL; tmp = tmp->next) {
+ SETTINGS_REC *rec = tmp->data;
+
+ if (stristr(rec->section, pattern) != NULL) {
+ if (g_strcmp0(last_section, rec->section) != 0) {
+ /* print section */
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ TXT_SET_TITLE, rec->section);
+ last_section = rec->section;
+ }
+ set_print(rec);
+ }
+ }
+ g_slist_free(sets);
+}
+
+static void set_boolean(const char *key, const char *value)
+{
+ char *stripped_value;
+
+ stripped_value = g_strdup(value);
+ g_strstrip(stripped_value);
+
+ if (g_ascii_strcasecmp(stripped_value, "ON") == 0)
+ settings_set_bool(key, TRUE);
+ else if (g_ascii_strcasecmp(stripped_value, "OFF") == 0)
+ settings_set_bool(key, FALSE);
+ else if (g_ascii_strcasecmp(stripped_value, "TOGGLE") == 0)
+ settings_set_bool(key, !settings_get_bool(key));
+ else
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_NOT_TOGGLE);
+
+ g_free(stripped_value);
+}
+
+static void set_int(const char *key, const char *value)
+{
+ char *endp;
+ long longval;
+ int error;
+
+ errno = 0;
+ longval = strtol(value, &endp, 10);
+ error = errno;
+ while (i_isspace(*endp))
+ endp++;
+ if (error != 0 || *endp != '\0' || longval < INT_MIN || longval > INT_MAX)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_INVALID_NUMBER);
+ else
+ settings_set_int(key, (int)longval);
+}
+
+static void set_choice(const char *key, const char *value)
+{
+ char *stripped_value;
+
+ stripped_value = g_strdup(value);
+ g_strstrip(stripped_value);
+
+ if (settings_set_choice(key, stripped_value) == FALSE) {
+ SETTINGS_REC *rec = settings_get_record(key);
+ char *msg = g_strjoinv(", ", rec->choices);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_INVALID_CHOICE, msg);
+ g_free(msg);
+ }
+
+ g_free(stripped_value);
+}
+
+/* SYNTAX: SET [-clear | -default | -section] [<key> [<value>]] */
+static void cmd_set(char *data)
+{
+ GHashTable *optlist;
+ char *key, *value;
+ void *free_arg;
+ int clear, set_default, list_section;
+ SETTINGS_REC *rec;
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST |
+ PARAM_FLAG_OPTIONS,
+ "set", &optlist, &key, &value))
+ return;
+
+ clear = g_hash_table_lookup(optlist, "clear") != NULL;
+ set_default = g_hash_table_lookup(optlist, "default") != NULL;
+ list_section = g_hash_table_lookup(optlist, "section") != NULL;
+
+ if (*key == '\0')
+ clear = set_default = list_section = FALSE;
+
+ if (list_section)
+ set_print_section(key);
+ else if (!(clear || set_default || *value != '\0'))
+ set_print_pattern(key);
+ else {
+ rec = settings_get_record(key);
+ if (rec != NULL) {
+ /* change the setting */
+ switch (rec->type) {
+ case SETTING_TYPE_BOOLEAN:
+ if (clear)
+ settings_set_bool(key, FALSE);
+ else if (set_default)
+ settings_set_bool(key, rec->default_value.v_bool);
+ else
+ set_boolean(key, value);
+ break;
+ case SETTING_TYPE_INT:
+ if (clear)
+ settings_set_int(key, 0);
+ else if (set_default)
+ settings_set_int(key, rec->default_value.v_int);
+ else
+ set_int(key, value);
+ break;
+ case SETTING_TYPE_CHOICE:
+ if (clear || set_default)
+ settings_set_choice(key, rec->choices[rec->default_value.v_int]);
+ else
+ set_choice(key, value);
+ break;
+ case SETTING_TYPE_STRING:
+ settings_set_str(key, clear ? "" :
+ set_default ? rec->default_value.v_string :
+ value);
+ break;
+ case SETTING_TYPE_TIME:
+ if (!settings_set_time(key, clear ? "0" :
+ set_default ? rec->default_value.v_string : value))
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_INVALID_TIME);
+ break;
+ case SETTING_TYPE_LEVEL:
+ if (!settings_set_level(key, clear ? "" :
+ set_default ? rec->default_value.v_string : value))
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_INVALID_LEVEL);
+ break;
+ case SETTING_TYPE_SIZE:
+ if (!settings_set_size(key, clear ? "0" :
+ set_default ? rec->default_value.v_string : value))
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_INVALID_SIZE);
+ break;
+ case SETTING_TYPE_ANY:
+ /* Unpossible! */
+ break;
+ }
+ signal_emit("setup changed", 0);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_SET_TITLE, rec->section);
+ set_print(rec);
+ } else
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_SET_UNKNOWN, key);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: TOGGLE <key> [on|off|toggle] */
+static void cmd_toggle(const char *data)
+{
+ char *key, *value;
+ void *free_arg;
+ int type;
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS, &key, &value))
+ return;
+
+ if (*key == '\0')
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ type = settings_get_type(key);
+ if (type == SETTING_TYPE_ANY)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_SET_UNKNOWN, key);
+ else if (type != SETTING_TYPE_BOOLEAN)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_SET_NOT_BOOLEAN, key);
+ else {
+ set_boolean(key, *value != '\0' ? value : "TOGGLE");
+ set_print(settings_get_record(key));
+ signal_emit("setup changed", 0);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+static int config_key_compare(CONFIG_NODE *node1, CONFIG_NODE *node2)
+{
+ return g_ascii_strcasecmp(node1->key, node2->key);
+}
+
+static void show_aliases(const char *alias)
+{
+ CONFIG_NODE *node;
+ GSList *tmp, *list;
+ int aliaslen;
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_ALIASLIST_HEADER);
+
+ node = iconfig_node_traverse("aliases", FALSE);
+ tmp = node == NULL ? NULL : config_node_first(node->value);
+
+ /* first get the list of aliases sorted */
+ list = NULL;
+ aliaslen = strlen(alias);
+ for (; tmp != NULL; tmp = config_node_next(tmp)) {
+ CONFIG_NODE *node = tmp->data;
+
+ if (node->type != NODE_TYPE_KEY)
+ continue;
+
+ if (aliaslen != 0 && g_ascii_strncasecmp(node->key, alias, aliaslen) != 0)
+ continue;
+
+ list = g_slist_insert_sorted(list, node, (GCompareFunc) config_key_compare);
+ }
+
+ /* print the aliases */
+ for (tmp = list; tmp != NULL; tmp = tmp->next) {
+ CONFIG_NODE *node = tmp->data;
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_ALIASLIST_LINE,
+ node->key, node->value);
+ }
+ g_slist_free(list);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_ALIASLIST_FOOTER);
+}
+
+static void alias_remove(const char *alias)
+{
+ if (iconfig_get_str("aliases", alias, NULL) == NULL)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_ALIAS_NOT_FOUND, alias);
+ else {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_ALIAS_REMOVED, alias);
+ iconfig_set_str("aliases", alias, NULL);
+
+ signal_emit("alias removed", 1, alias);
+ }
+}
+
+/* SYNTAX: ALIAS [[-]<alias> [<command>]] */
+static void cmd_alias(const char *data)
+{
+ char *alias, *value;
+ void *free_arg;
+
+ g_return_if_fail(data != NULL);
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, &alias, &value))
+ return;
+
+ if (*alias == '-') {
+ if (alias[1] != '\0') alias_remove(alias+1);
+ } else if (*alias == '\0' || *value == '\0')
+ show_aliases(alias);
+ else {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_ALIAS_ADDED, alias);
+ iconfig_set_str("aliases", alias, value);
+ signal_emit("alias added", 2, alias, value);
+ }
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: UNALIAS <alias> */
+static void cmd_unalias(const char *data)
+{
+ char *alias;
+ void *free_arg;
+
+ g_return_if_fail(data != NULL);
+
+ if (!cmd_get_params(data, &free_arg, 1, &alias))
+ return;
+ if (*alias == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ alias_remove(alias);
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: RELOAD [<file>] */
+static void cmd_reload(const char *data)
+{
+ const char *fname;
+
+ fname = *data == '\0' ? get_irssi_config() : data;
+
+ if (settings_reread(fname)) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_CONFIG_RELOADED, fname);
+ }
+}
+
+static void settings_save_fe(const char *fname)
+{
+ if (settings_save(fname, FALSE /* not autosaved */)) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_CONFIG_SAVED, fname);
+ }
+}
+
+static void settings_save_confirm(const char *line, char *fname)
+{
+ if (i_toupper(line[0]) == 'Y')
+ settings_save_fe(fname);
+ g_free(fname);
+}
+
+/* SYNTAX: SAVE [<file>] */
+static void cmd_save(const char *data)
+{
+ GHashTable *optlist;
+ char *format, *fname;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS,
+ "save", &optlist, &fname))
+ return;
+
+ if (*fname == '\0')
+ fname = mainconfig->fname;
+
+ if (!irssi_config_is_changed(fname))
+ settings_save_fe(fname);
+ else {
+ /* config file modified outside irssi */
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_CONFIG_MODIFIED, fname);
+
+ format = format_get_text(MODULE_NAME, NULL, NULL, NULL,
+ TXT_OVERWRITE_CONFIG);
+ keyboard_entry_redirect((SIGNAL_FUNC) settings_save_confirm,
+ format, 0, g_strdup(fname));
+ g_free(format);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+static void settings_clean_confirm(const char *line)
+{
+ if (i_toupper(line[0]) == 'Y')
+ settings_clean_invalid();
+}
+
+static void sig_settings_errors(const char *msg)
+{
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "%s", msg);
+ keyboard_entry_redirect((SIGNAL_FUNC) settings_clean_confirm,
+ "Remove unknown settings from config file (Y/n)?",
+ 0, NULL);
+}
+
+void fe_settings_init(void)
+{
+ command_bind("set", NULL, (SIGNAL_FUNC) cmd_set);
+ command_bind("toggle", NULL, (SIGNAL_FUNC) cmd_toggle);
+ command_bind("alias", NULL, (SIGNAL_FUNC) cmd_alias);
+ command_bind("unalias", NULL, (SIGNAL_FUNC) cmd_unalias);
+ command_bind("reload", NULL, (SIGNAL_FUNC) cmd_reload);
+ command_bind("save", NULL, (SIGNAL_FUNC) cmd_save);
+ command_set_options("set", "clear default section");
+
+ signal_add("settings errors", (SIGNAL_FUNC) sig_settings_errors);
+}
+
+void fe_settings_deinit(void)
+{
+ command_unbind("set", (SIGNAL_FUNC) cmd_set);
+ command_unbind("toggle", (SIGNAL_FUNC) cmd_toggle);
+ command_unbind("alias", (SIGNAL_FUNC) cmd_alias);
+ command_unbind("unalias", (SIGNAL_FUNC) cmd_unalias);
+ command_unbind("reload", (SIGNAL_FUNC) cmd_reload);
+ command_unbind("save", (SIGNAL_FUNC) cmd_save);
+
+ signal_remove("settings errors", (SIGNAL_FUNC) sig_settings_errors);
+}
diff --git a/src/fe-common/core/fe-settings.h b/src/fe-common/core/fe-settings.h
new file mode 100644
index 0000000..61797bc
--- /dev/null
+++ b/src/fe-common/core/fe-settings.h
@@ -0,0 +1,6 @@
+#ifndef IRSSI_FE_COMMON_CORE_FE_SETTINGS_H
+#define IRSSI_FE_COMMON_CORE_FE_SETTINGS_H
+
+void fe_settings_set_print(const char *key);
+
+#endif
diff --git a/src/fe-common/core/fe-tls.c b/src/fe-common/core/fe-tls.c
new file mode 100644
index 0000000..d72a13b
--- /dev/null
+++ b/src/fe-common/core/fe-tls.c
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2015 Alexander Færøy <ahf@irssi.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA
+ */
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/tls.h>
+
+#include <irssi/src/fe-common/core/module-formats.h>
+#include <irssi/src/fe-common/core/printtext.h>
+
+#include <irssi/src/fe-common/core/fe-tls.h>
+
+static void tls_handshake_finished(SERVER_REC *server, TLS_REC *tls)
+{
+ GSList *certs = NULL;
+ GSList *subject = NULL;
+ GSList *issuer = NULL;
+ GString *str = NULL;
+ TLS_CERT_ENTRY_REC *data = NULL;
+
+ if (! settings_get_bool("tls_verbose_connect"))
+ return;
+
+ printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_TLS_CERT_HEADER);
+
+ for (certs = tls->certs; certs != NULL; certs = certs->next) {
+ TLS_CERT_REC *tls_cert_rec = certs->data;
+ str = g_string_new(NULL);
+
+ for (subject = tls_cert_rec->subject; subject != NULL; subject = subject->next) {
+ data = subject->data;
+ g_string_append_printf(str, "%s: %s, ", data->name, data->value);
+ }
+
+ if (str->len > 1)
+ g_string_truncate(str, str->len - 2);
+
+ printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_TLS_CERT_SUBJECT, str->str);
+ g_string_free(str, TRUE);
+
+ str = g_string_new(NULL);
+
+ for (issuer = tls_cert_rec->issuer; issuer != NULL; issuer = issuer->next) {
+ data = issuer->data;
+ g_string_append_printf(str, "%s: %s, ", data->name, data->value);
+ }
+
+ if (str->len > 1)
+ g_string_truncate(str, str->len - 2);
+
+ printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_TLS_CERT_ISSUER, str->str);
+ g_string_free(str, TRUE);
+ }
+
+ printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_TLS_PROTOCOL_VERSION, tls->protocol_version, tls->cipher_size, tls->cipher);
+
+ if (tls->ephemeral_key_algorithm != NULL)
+ printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_TLS_EPHEMERAL_KEY, tls->ephemeral_key_size, tls->ephemeral_key_algorithm);
+ else
+ printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_TLS_EPHEMERAL_KEY_UNAVAILBLE);
+
+ printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_TLS_PUBKEY, tls->public_key_size, tls->public_key_algorithm, tls->not_before, tls->not_after);
+ printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_TLS_PUBKEY_FINGERPRINT, tls->public_key_fingerprint, tls->public_key_fingerprint_algorithm);
+ printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_TLS_CERT_FINGERPRINT, tls->certificate_fingerprint, tls->certificate_fingerprint_algorithm);
+}
+
+void fe_tls_init(void)
+{
+ settings_add_bool("lookandfeel", "tls_verbose_connect", TRUE);
+
+ signal_add("tls handshake finished", (SIGNAL_FUNC)tls_handshake_finished);
+}
+
+void fe_tls_deinit(void)
+{
+ signal_remove("tls handshake finished", (SIGNAL_FUNC)tls_handshake_finished);
+}
diff --git a/src/fe-common/core/fe-tls.h b/src/fe-common/core/fe-tls.h
new file mode 100644
index 0000000..b0a3ea9
--- /dev/null
+++ b/src/fe-common/core/fe-tls.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2015 Alexander Færøy <ahf@irssi.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA
+ */
+
+#ifndef IRSSI_FE_COMMON_CORE_FE_TLS_H
+#define IRSSI_FE_COMMON_CORE_FE_TLS_H
+
+void fe_tls_init(void);
+void fe_tls_deinit(void);
+
+#endif
diff --git a/src/fe-common/core/fe-windows.c b/src/fe-common/core/fe-windows.c
new file mode 100644
index 0000000..d575813
--- /dev/null
+++ b/src/fe-common/core/fe-windows.c
@@ -0,0 +1,845 @@
+/*
+ windows.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/core/module-formats.h>
+#include <irssi/src/core/modules.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/core/levels.h>
+
+#include <irssi/src/fe-common/core/printtext.h>
+#include <irssi/src/fe-common/core/fe-windows.h>
+#include <irssi/src/fe-common/core/window-items.h>
+
+GSList *windows; /* first in the list is the active window,
+ next is the last active, etc. */
+GSequence *windows_seq;
+WINDOW_REC *active_win;
+
+static int daytag;
+static int daycheck; /* 0 = don't check, 1 = time is 00:00, check,
+ 2 = time is 00:00, already checked */
+
+static int window_refnum_lookup(WINDOW_REC *window, void *refnum_p)
+{
+ int refnum = GPOINTER_TO_INT(refnum_p);
+ return window->refnum == refnum ? 0 : window->refnum < refnum ? -1 : 1;
+}
+
+static GSequenceIter *windows_seq_begin(void)
+{
+ return g_sequence_get_begin_iter(windows_seq);
+}
+
+static GSequenceIter *windows_seq_end(void)
+{
+ return g_sequence_get_end_iter(windows_seq);
+}
+
+static GSequenceIter *windows_seq_insert(WINDOW_REC *rec)
+{
+ return g_sequence_insert_sorted(windows_seq, rec, (GCompareDataFunc)window_refnum_cmp, NULL);
+}
+
+static GSequenceIter *windows_seq_refnum_lookup(int refnum)
+{
+ return g_sequence_lookup(windows_seq, GINT_TO_POINTER(refnum), (GCompareDataFunc)window_refnum_lookup, NULL);
+}
+
+static void windows_seq_changed(GSequenceIter *iter)
+{
+ g_sequence_sort_changed(iter, (GCompareDataFunc)window_refnum_cmp, NULL);
+}
+
+static GSequenceIter *windows_seq_window_lookup(WINDOW_REC *rec)
+{
+ return g_sequence_lookup(windows_seq, rec, (GCompareDataFunc)window_refnum_cmp, NULL);
+}
+
+/* search to the numerically right iterator of refnum */
+static GSequenceIter *windows_seq_refnum_search_right(int refnum)
+{
+ return g_sequence_search(windows_seq, GINT_TO_POINTER(refnum), (GCompareDataFunc)window_refnum_lookup, NULL);
+}
+
+/* we want to find the numerically left iterator of refnum, so we
+ search the right of the previous refnum. but we need to figure out
+ the case where the iterator is already at the beginning, i.e
+ iter->refnum >= refnum */
+static GSequenceIter *windows_seq_refnum_search_left(int refnum)
+{
+ GSequenceIter *iter = windows_seq_refnum_search_right(refnum - 1);
+ return iter == windows_seq_begin() ? NULL : g_sequence_iter_prev(iter);
+}
+
+static int window_get_new_refnum(void)
+{
+ WINDOW_REC *win;
+ GSequenceIter *iter, *end;
+ int refnum;
+
+ refnum = 1;
+ iter = windows_seq_begin();
+ end = windows_seq_end();
+
+ while (iter != end) {
+ win = g_sequence_get(iter);
+
+ if (refnum != win->refnum)
+ return refnum;
+
+ refnum++;
+ iter = g_sequence_iter_next(iter);
+ }
+
+ return refnum;
+}
+
+WINDOW_REC *window_create(WI_ITEM_REC *item, int automatic)
+{
+ WINDOW_REC *rec;
+
+ rec = g_new0(WINDOW_REC, 1);
+ rec->refnum = window_get_new_refnum();
+ rec->level = settings_get_level("window_default_level");
+
+ windows = g_slist_prepend(windows, rec);
+ windows_seq_insert(rec);
+ signal_emit("window created", 2, rec, GINT_TO_POINTER(automatic));
+
+ if (item != NULL) window_item_add(rec, item, automatic);
+ if (windows->next == NULL || !automatic || settings_get_bool("window_auto_change")) {
+ if (automatic && windows->next != NULL)
+ signal_emit("window changed automatic", 1, rec);
+ window_set_active(rec);
+ }
+ return rec;
+}
+
+static void window_set_refnum0(WINDOW_REC *window, int refnum)
+{
+ int old_refnum;
+
+ g_return_if_fail(window != NULL);
+ g_return_if_fail(refnum >= 1);
+ if (window->refnum == refnum) return;
+
+ old_refnum = window->refnum;
+ window->refnum = refnum;
+ signal_emit("window refnum changed", 2, window, GINT_TO_POINTER(old_refnum));
+}
+
+/* removed_refnum was removed from the windows list, pack the windows so
+ there won't be any holes. If there is any holes after removed_refnum,
+ leave the windows behind it alone. */
+static void windows_pack(int removed_refnum)
+{
+ WINDOW_REC *window;
+ int refnum;
+ GSequenceIter *iter, *end;
+
+ refnum = removed_refnum + 1;
+ end = windows_seq_end();
+ iter = windows_seq_refnum_lookup(refnum);
+ if (iter == NULL) return;
+
+ while (iter != end) {
+ window = g_sequence_get(iter);
+
+ if (window == NULL || window->sticky_refnum || window->refnum != refnum)
+ break;
+
+ window_set_refnum0(window, refnum - 1);
+ windows_seq_changed(iter);
+
+ refnum++;
+ iter = g_sequence_iter_next(iter);
+ }
+}
+
+void window_destroy(WINDOW_REC *window)
+{
+ GSequenceIter *iter;
+ g_return_if_fail(window != NULL);
+
+ if (window->destroying) return;
+ window->destroying = TRUE;
+ windows = g_slist_remove(windows, window);
+ iter = windows_seq_window_lookup(window);
+ if (iter != NULL) g_sequence_remove(iter);
+
+ if (active_win == window) {
+ active_win = NULL; /* it's corrupted */
+ if (windows != NULL)
+ window_set_active(windows->data);
+ }
+
+ while (window->items != NULL)
+ window_item_destroy(window->items->data);
+
+ if (settings_get_bool("windows_auto_renumber"))
+ windows_pack(window->refnum);
+
+ signal_emit("window destroyed", 1, window);
+
+ while (window->bound_items != NULL)
+ window_bind_destroy(window, window->bound_items->data);
+
+ g_free_not_null(window->hilight_color);
+ g_free_not_null(window->servertag);
+ g_free_not_null(window->theme_name);
+ g_free_not_null(window->name);
+ g_free(window);
+}
+
+void window_auto_destroy(WINDOW_REC *window)
+{
+ if (settings_get_bool("autoclose_windows") && windows->next != NULL &&
+ window->items == NULL && window->bound_items == NULL &&
+ window->level == 0 && !window->immortal)
+ window_destroy(window);
+}
+
+void window_set_active(WINDOW_REC *window)
+{
+ WINDOW_REC *old_window;
+
+ if (window == active_win)
+ return;
+
+ old_window = active_win;
+ active_win = window;
+ if (active_win != NULL) {
+ windows = g_slist_remove(windows, active_win);
+ windows = g_slist_prepend(windows, active_win);
+ }
+
+ if (active_win != NULL)
+ signal_emit("window changed", 2, active_win, old_window);
+}
+
+void window_change_server(WINDOW_REC *window, void *server)
+{
+ SERVER_REC *active, *connect;
+
+ if (server != NULL && SERVER(server)->disconnected)
+ return;
+
+ if (server == NULL) {
+ active = connect = NULL;
+ } else if (g_slist_find(servers, server) != NULL) {
+ active = server;
+ connect = NULL;
+ } else {
+ active = NULL;
+ connect = server;
+ }
+
+ if (window->connect_server != connect) {
+ window->connect_server = connect;
+ signal_emit("window connect changed", 2, window, connect);
+ }
+
+ if (window->active_server != active) {
+ window->active_server = active;
+ signal_emit("window server changed", 2, window, active);
+ }
+}
+
+void window_set_refnum(WINDOW_REC *window, int refnum)
+{
+ GSequenceIter *other_iter, *window_iter;
+ int old_refnum;
+
+ g_return_if_fail(window != NULL);
+ g_return_if_fail(refnum >= 1);
+ if (window->refnum == refnum) return;
+
+ other_iter = windows_seq_refnum_lookup(refnum);
+ window_iter = windows_seq_refnum_lookup(window->refnum);
+
+ if (other_iter != NULL) {
+ WINDOW_REC *rec = g_sequence_get(other_iter);
+
+ rec->refnum = window->refnum;
+ signal_emit("window refnum changed", 2, rec, GINT_TO_POINTER(refnum));
+ }
+
+ old_refnum = window->refnum;
+ window->refnum = refnum;
+ signal_emit("window refnum changed", 2, window, GINT_TO_POINTER(old_refnum));
+
+ if (window_iter != NULL && other_iter != NULL) {
+ g_sequence_swap(other_iter, window_iter);
+ } else {
+ windows_seq_changed(window_iter);
+ }
+}
+
+void window_set_name(WINDOW_REC *window, const char *name)
+{
+ g_free_not_null(window->name);
+ window->name = name == NULL || *name == '\0' ? NULL : g_strdup(name);
+
+ signal_emit("window name changed", 1, window);
+}
+
+void window_set_history(WINDOW_REC *window, const char *name)
+{
+ char *oldname;
+ oldname = window->history_name;
+
+ if (name == NULL || *name == '\0')
+ window->history_name = NULL;
+ else
+ window->history_name = g_strdup(name);
+
+ signal_emit("window history changed", 2, window, oldname);
+
+ g_free_not_null(oldname);
+}
+
+void window_clear_history(WINDOW_REC *window, const char *name)
+{
+ signal_emit("window history cleared", 2, window, name);
+}
+
+void window_set_level(WINDOW_REC *window, int level)
+{
+ g_return_if_fail(window != NULL);
+
+ window->level = level;
+ signal_emit("window level changed", 1, window);
+}
+
+void window_set_immortal(WINDOW_REC *window, int immortal)
+{
+ g_return_if_fail(window != NULL);
+
+ window->immortal = immortal;
+ signal_emit("window immortal changed", 1, window);
+}
+
+/* return active item's name, or if none is active, window's name */
+const char *window_get_active_name(WINDOW_REC *window)
+{
+ g_return_val_if_fail(window != NULL, NULL);
+
+ if (window->active != NULL)
+ return window->active->visible_name;
+
+ return window->name;
+}
+
+#define WINDOW_LEVEL_MATCH(window, server, level) \
+ (((window)->level & level) && \
+ (server == NULL || (window)->active_server == server))
+
+WINDOW_REC *window_find_level(void *server, int level)
+{
+ GSList *tmp;
+ WINDOW_REC *match;
+
+ match = NULL;
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ if (WINDOW_LEVEL_MATCH(rec, server, level)) {
+ /* prefer windows without any items */
+ if (rec->items == NULL)
+ return rec;
+
+ if (match == NULL)
+ match = rec;
+ else if (active_win == rec) {
+ /* prefer active window over others */
+ match = rec;
+ }
+ }
+ }
+
+ return match;
+}
+
+WINDOW_REC *window_find_closest(void *server, const char *name, int level)
+{
+ WINDOW_REC *window,*namewindow=NULL;
+ WI_ITEM_REC *item;
+ int i;
+
+ /* match by name */
+ item = name == NULL ? NULL :
+ window_item_find(server, name);
+ if (item != NULL) {
+ namewindow = window_item_window(item);
+ if (namewindow != NULL &&
+ ((namewindow->level & level) != 0 ||
+ !settings_get_bool("window_check_level_first"))) {
+ /* match, but if multiple windows have the same level
+ we could be choosing a bad one here, eg.
+ name=nick1 would get nick2's query instead of
+ generic msgs window.
+
+ And check for prefixed !channel name --Borys */
+ if (g_ascii_strcasecmp(name, item->visible_name) == 0 ||
+ g_ascii_strcasecmp(name, (char *) window_item_get_target((WI_ITEM_REC *) item)) == 0)
+ return namewindow;
+ }
+ }
+
+ /* prefer windows without items */
+ for (i = 0; i < 2; i++) {
+ /* match by level */
+ if (level != MSGLEVEL_HILIGHT)
+ level &= ~(MSGLEVEL_HILIGHT | MSGLEVEL_NOHILIGHT);
+ window = window_find_level(server, level);
+ if (window != NULL && (i == 1 || window->items == NULL))
+ return window;
+
+ /* match by level - ignore server */
+ window = window_find_level(NULL, level);
+ if (window != NULL && (i == 1 || window->items == NULL))
+ return window;
+ }
+
+ /* still return item's window if we didnt find anything */
+ if (namewindow != NULL) return namewindow;
+
+ /* fallback to active */
+ return active_win;
+}
+
+WINDOW_REC *window_find_refnum(int refnum)
+{
+ GSequenceIter *iter;
+
+ iter = windows_seq_refnum_lookup(refnum);
+ if (iter != NULL) {
+ WINDOW_REC *rec = g_sequence_get(iter);
+
+ return rec;
+ }
+
+ return NULL;
+}
+
+WINDOW_REC *window_find_name(const char *name)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(name != NULL, NULL);
+
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ if (rec->name != NULL &&
+ g_ascii_strcasecmp(rec->name, name) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+WINDOW_REC *window_find_item(SERVER_REC *server, const char *name)
+{
+ WINDOW_REC *rec;
+ WI_ITEM_REC *item;
+
+ g_return_val_if_fail(name != NULL, NULL);
+
+ rec = window_find_name(name);
+ if (rec != NULL) return rec;
+
+ item = server == NULL ? NULL :
+ window_item_find(server, name);
+ if (item == NULL) {
+ /* not found from the active server - any server? */
+ item = window_item_find(NULL, name);
+ }
+
+ if (item == NULL)
+ return NULL;
+
+ return window_item_window(item);
+}
+
+int window_refnum_prev(int refnum, int wrap)
+{
+ WINDOW_REC *rec;
+ GSequenceIter *iter, *end;
+
+ iter = windows_seq_refnum_search_left(refnum);
+ end = windows_seq_end();
+
+ if (iter != NULL) {
+ rec = g_sequence_get(iter);
+ return rec->refnum;
+ }
+
+ if (wrap) {
+ iter = g_sequence_iter_prev(end);
+ if (iter != end) {
+ rec = g_sequence_get(iter);
+ return rec->refnum;
+ }
+ }
+
+ return -1;
+}
+
+int window_refnum_next(int refnum, int wrap)
+{
+ WINDOW_REC *rec;
+ GSequenceIter *iter, *end;
+
+ iter = windows_seq_refnum_search_right(refnum);
+ end = windows_seq_end();
+
+ if (iter != end) {
+ rec = g_sequence_get(iter);
+ return rec->refnum;
+ }
+
+ if (wrap) {
+ iter = windows_seq_begin();
+ if (iter != end) {
+ rec = g_sequence_get(iter);
+ return rec->refnum;
+ }
+ }
+
+ return -1;
+}
+
+int windows_refnum_last(void)
+{
+ WINDOW_REC *rec;
+ GSequenceIter *end, *iter;
+
+ end = windows_seq_end();
+ iter = g_sequence_iter_prev(end);
+ if (iter != end) {
+ rec = g_sequence_get(iter);
+ return rec->refnum;
+ }
+
+ return -1;
+}
+
+int window_refnum_cmp(WINDOW_REC *w1, WINDOW_REC *w2)
+{
+ return w1 == w2 ? 0 : w1->refnum < w2->refnum ? -1 : 1;
+}
+
+GSList *windows_get_sorted(void)
+{
+ GSequenceIter *iter, *begin;
+ GSList *sorted;
+
+ sorted = NULL;
+ iter = windows_seq_end();
+ begin = windows_seq_begin();
+
+ while (iter != begin) {
+ WINDOW_REC *rec;
+
+ iter = g_sequence_iter_prev(iter);
+ rec = g_sequence_get(iter);
+
+ sorted = g_slist_prepend(sorted, rec);
+ }
+
+ return sorted;
+}
+
+/* Add a new bind to window - if duplicate is found it's returned */
+WINDOW_BIND_REC *window_bind_add(WINDOW_REC *window, const char *servertag,
+ const char *name)
+{
+ WINDOW_BIND_REC *rec;
+
+ g_return_val_if_fail(window != NULL, NULL);
+ g_return_val_if_fail(servertag != NULL, NULL);
+ g_return_val_if_fail(name != NULL, NULL);
+
+ rec = window_bind_find(window, servertag, name);
+ if (rec != NULL)
+ return rec;
+
+ rec = g_new0(WINDOW_BIND_REC, 1);
+ rec->name = g_strdup(name);
+ rec->servertag = g_strdup(servertag);
+
+ window->bound_items = g_slist_append(window->bound_items, rec);
+ return rec;
+}
+
+void window_bind_destroy(WINDOW_REC *window, WINDOW_BIND_REC *rec)
+{
+ g_return_if_fail(window != NULL);
+ g_return_if_fail(rec != NULL);
+
+ window->bound_items = g_slist_remove(window->bound_items, rec);
+
+ g_free(rec->servertag);
+ g_free(rec->name);
+ g_free(rec);
+}
+
+WINDOW_BIND_REC *window_bind_find(WINDOW_REC *window, const char *servertag,
+ const char *name)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(window != NULL, NULL);
+ g_return_val_if_fail(servertag != NULL, NULL);
+ g_return_val_if_fail(name != NULL, NULL);
+
+ for (tmp = window->bound_items; tmp != NULL; tmp = tmp->next) {
+ WINDOW_BIND_REC *rec = tmp->data;
+
+ if (g_ascii_strcasecmp(rec->name, name) == 0 &&
+ g_ascii_strcasecmp(rec->servertag, servertag) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+void window_bind_remove_unsticky(WINDOW_REC *window)
+{
+ GSList *tmp, *next;
+
+ for (tmp = window->bound_items; tmp != NULL; tmp = next) {
+ WINDOW_BIND_REC *rec = tmp->data;
+
+ next = tmp->next;
+ if (!rec->sticky)
+ window_bind_destroy(window, rec);
+ }
+}
+
+static void sig_server_connected(SERVER_REC *server)
+{
+ GSList *tmp;
+
+ g_return_if_fail(server != NULL);
+
+ /* Try to keep some server assigned to windows..
+ Also change active window's server if the window is empty */
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ if ((rec->servertag == NULL ||
+ g_ascii_strcasecmp(rec->servertag, server->tag) == 0) &&
+ (rec->active_server == NULL ||
+ (rec == active_win && rec->items == NULL)))
+ window_change_server(rec, server);
+ }
+}
+
+static void sig_server_disconnected(SERVER_REC *server)
+{
+ GSList *tmp;
+ SERVER_REC *new_server;
+
+ g_return_if_fail(server != NULL);
+
+ new_server = servers == NULL ? NULL : servers->data;
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ if (rec->active_server == server ||
+ rec->connect_server == server) {
+ window_change_server(rec, rec->servertag != NULL ?
+ NULL : new_server);
+ }
+ }
+}
+
+static void window_print_daychange(WINDOW_REC *window, time_t t)
+{
+ THEME_REC *theme;
+ TEXT_DEST_REC dest;
+ struct tm *tm;
+ char *format, str[256];
+ int ret;
+
+ theme = active_win->theme != NULL ? active_win->theme : current_theme;
+ format_create_dest(&dest, NULL, NULL, MSGLEVEL_NEVER, window);
+ format = format_get_text_theme(theme, MODULE_NAME, &dest,
+ TXT_DAYCHANGE);
+ tm = localtime(&t);
+ ret = strftime(str, sizeof(str), format, tm);
+ g_free(format);
+ if (ret <= 0) return;
+
+ printtext_string_window(window, MSGLEVEL_NEVER, str);
+}
+
+short color_24bit_256 (const unsigned char rgb[])
+{
+ static const int cstep_size = 40;
+ static const int cstep_start = 0x5f;
+
+ static const int gstep_size = 10;
+ static const int gstep_start = 0x08;
+
+ int dist[3] = {0};
+ int r[3], gr[3];
+
+ size_t i;
+
+ for (i = 0; i < 3; ++i) {
+ const int n = rgb[i];
+ gr[i] = -1;
+ if (n < cstep_start /2) {
+ r[i] = 0;
+ dist[i] = -cstep_size/2;
+ }
+ else {
+ r[i] = 1+((n-cstep_start + cstep_size /2)/cstep_size);
+ dist[i] = ((n-cstep_start + cstep_size /2)%cstep_size - cstep_size/2);
+ }
+ if (n < gstep_start /2) {
+ gr[i] = -1;
+ }
+ else {
+ gr[i] = ((n-gstep_start + gstep_size /2)/gstep_size);
+ }
+ }
+ if (r[0] == r[1] && r[1] == r[2] &&
+ 4*abs(dist[0]) < gstep_size && 4*abs(dist[1]) < gstep_size && 4*abs(dist[2]) < gstep_size) {
+ /* skip gray detection */
+ }
+ else {
+ const int j = r[1] == r[2] ? 0 : 1;
+ if ((r[0] == r[1] || r[j] == r[2]) && abs(r[j]-r[(j+1)%3]) <= 1) {
+ const int k = gr[1] == gr[2] ? 0 : 1;
+ if ((gr[0] == gr[1] || gr[k] == gr[2]) && abs(gr[k]-gr[(k+1)%3]) <= 2) {
+ if (gr[k] < 0) {
+ r[0] = r[1] = r[2] = 0;
+ }
+ else if (gr[k] > 23) {
+ r[0] = r[1] = r[2] = 5;
+ }
+ else {
+ r[0] = 6;
+ r[1] = (gr[k] / 6);
+ r[2] = gr[k]%6;
+ }
+ }
+ }
+ }
+ return 16 + r[0]*36 + r[1] * 6 + r[2];
+}
+
+static void sig_print_text(void)
+{
+ GSList *tmp;
+ time_t t;
+ struct tm *tm;
+
+ t = time(NULL);
+ tm = localtime(&t);
+ if (tm->tm_hour != 0 || tm->tm_min != 0)
+ return;
+
+ daycheck = 2;
+ signal_remove("print text", (SIGNAL_FUNC) sig_print_text);
+
+ /* day changed, print notice about it to every window */
+ for (tmp = windows; tmp != NULL; tmp = tmp->next)
+ window_print_daychange(tmp->data, t);
+}
+
+static int sig_check_daychange(void)
+{
+ time_t t;
+ struct tm *tm;
+
+ t = time(NULL);
+ tm = localtime(&t);
+
+ if (daycheck == 1 && tm->tm_hour == 0 && tm->tm_min == 0) {
+ sig_print_text();
+ return TRUE;
+ }
+
+ if (tm->tm_hour != 23 || tm->tm_min != 59) {
+ daycheck = 0;
+ return TRUE;
+ }
+
+ /* time is 23:59 */
+ if (daycheck == 0) {
+ daycheck = 1;
+ signal_add("print text", (SIGNAL_FUNC) sig_print_text);
+ }
+ return TRUE;
+}
+
+static void read_settings(void)
+{
+ if (daytag != -1) {
+ g_source_remove(daytag);
+ daytag = -1;
+ }
+
+ if (settings_get_bool("timestamps"))
+ daytag = g_timeout_add(30000, (GSourceFunc) sig_check_daychange, NULL);
+}
+
+void windows_init(void)
+{
+ active_win = NULL;
+ windows_seq = g_sequence_new(NULL);
+ daycheck = 0; daytag = -1;
+ settings_add_bool("lookandfeel", "window_auto_change", FALSE);
+ settings_add_bool("lookandfeel", "windows_auto_renumber", TRUE);
+ settings_add_bool("lookandfeel", "window_check_level_first", FALSE);
+ settings_add_level("lookandfeel", "window_default_level", "NONE");
+
+ read_settings();
+ signal_add("server looking", (SIGNAL_FUNC) sig_server_connected);
+ signal_add("server connected", (SIGNAL_FUNC) sig_server_connected);
+ signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+ signal_add("server connect failed", (SIGNAL_FUNC) sig_server_disconnected);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void windows_deinit(void)
+{
+ if (daytag != -1) g_source_remove(daytag);
+ if (daycheck == 1) signal_remove("print text", (SIGNAL_FUNC) sig_print_text);
+
+ signal_remove("server looking", (SIGNAL_FUNC) sig_server_connected);
+ signal_remove("server connected", (SIGNAL_FUNC) sig_server_connected);
+ signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+ signal_remove("server connect failed", (SIGNAL_FUNC) sig_server_disconnected);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+ g_sequence_free(windows_seq);
+ windows_seq = NULL;
+}
diff --git a/src/fe-common/core/fe-windows.h b/src/fe-common/core/fe-windows.h
new file mode 100644
index 0000000..d5c60c8
--- /dev/null
+++ b/src/fe-common/core/fe-windows.h
@@ -0,0 +1,112 @@
+#ifndef IRSSI_FE_COMMON_CORE_FE_WINDOWS_H
+#define IRSSI_FE_COMMON_CORE_FE_WINDOWS_H
+
+#include <irssi/src/core/window-item-def.h>
+#include <irssi/src/fe-common/core/command-history.h>
+
+enum {
+ DATA_LEVEL_NONE = 0,
+ DATA_LEVEL_TEXT,
+ DATA_LEVEL_MSG,
+ DATA_LEVEL_HILIGHT
+};
+
+enum {
+ MAIN_WINDOW_TYPE_NONE = -1,
+ MAIN_WINDOW_TYPE_DEFAULT = 0,
+ MAIN_WINDOW_TYPE_HIDDEN = 1,
+ MAIN_WINDOW_TYPE_SPLIT = 2,
+ MAIN_WINDOW_TYPE_RSPLIT = 3
+};
+
+typedef struct {
+ char *servertag;
+ char *name;
+ int type;
+ unsigned int sticky:1;
+} WINDOW_BIND_REC;
+
+struct _WINDOW_REC {
+ int refnum;
+ char *name;
+
+ int width, height;
+
+ GSList *items;
+ WI_ITEM_REC *active;
+ SERVER_REC *active_server;
+ SERVER_REC *connect_server;
+ char *servertag; /* active_server must be either NULL or have this tag (unless there's items in this window) */
+
+ int level; /* message level */
+ GSList *bound_items; /* list of WINDOW_BIND_RECs */
+
+ unsigned int immortal:1;
+ unsigned int sticky_refnum:1;
+ unsigned int destroying:1;
+
+ /* window-specific command line history */
+ HISTORY_REC *history;
+ char *history_name;
+
+ int data_level; /* current data level */
+ char *hilight_color; /* current hilight color in %format */
+
+ time_t last_timestamp; /* When was last timestamp printed */
+ time_t last_line; /* When was last line printed */
+
+ char *theme_name; /* active theme in window, NULL = default */
+ void *theme; /* THEME_REC */
+
+ void *gui_data;
+};
+
+extern GSList *windows;
+extern WINDOW_REC *active_win;
+
+WINDOW_REC *window_create(WI_ITEM_REC *item, int automatic);
+void window_destroy(WINDOW_REC *window);
+
+void window_auto_destroy(WINDOW_REC *window);
+
+void window_set_active(WINDOW_REC *window);
+void window_change_server(WINDOW_REC *window, void *server);
+
+void window_set_refnum(WINDOW_REC *window, int refnum);
+void window_set_name(WINDOW_REC *window, const char *name);
+void window_set_history(WINDOW_REC *window, const char *name);
+void window_clear_history(WINDOW_REC *window, const char *name);
+void window_set_level(WINDOW_REC *window, int level);
+void window_set_immortal(WINDOW_REC *window, int immortal);
+
+/* return active item's name, or if none is active, window's name */
+const char *window_get_active_name(WINDOW_REC *window);
+
+WINDOW_REC *window_find_level(void *server, int level);
+WINDOW_REC *window_find_closest(void *server, const char *name, int level);
+WINDOW_REC *window_find_refnum(int refnum);
+WINDOW_REC *window_find_name(const char *name);
+WINDOW_REC *window_find_item(SERVER_REC *server, const char *name);
+
+int window_refnum_prev(int refnum, int wrap);
+int window_refnum_next(int refnum, int wrap);
+int windows_refnum_last(void);
+
+int window_refnum_cmp(WINDOW_REC *w1, WINDOW_REC *w2);
+GSList *windows_get_sorted(void);
+
+/* Add a new bind to window - if duplicate is found it's returned */
+WINDOW_BIND_REC *window_bind_add(WINDOW_REC *window, const char *servertag,
+ const char *name);
+void window_bind_destroy(WINDOW_REC *window, WINDOW_BIND_REC *rec);
+
+WINDOW_BIND_REC *window_bind_find(WINDOW_REC *window, const char *servertag,
+ const char *name);
+void window_bind_remove_unsticky(WINDOW_REC *window);
+
+void windows_init(void);
+void windows_deinit(void);
+
+short color_24bit_256(const unsigned char rgb[]);
+
+#endif
diff --git a/src/fe-common/core/formats.c b/src/fe-common/core/formats.c
new file mode 100644
index 0000000..0c8b308
--- /dev/null
+++ b/src/fe-common/core/formats.c
@@ -0,0 +1,1750 @@
+/*
+ formats.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/core/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/special-vars.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/servers.h>
+
+#include <irssi/src/fe-common/core/fe-windows.h>
+#include <irssi/src/fe-common/core/window-items.h>
+#include <irssi/src/fe-common/core/formats.h>
+#include <irssi/src/fe-common/core/themes.h>
+#include <irssi/src/core/recode.h>
+#include <irssi/src/core/utf8.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/refstrings.h>
+
+static const char *format_backs = "04261537";
+static const char *format_fores = "kbgcrmyw";
+static const char *format_boldfores = "KBGCRMYW";
+static const char *ext_color_al = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+static int signal_gui_print_text;
+static int hide_text_style, hide_server_tags, hide_colors;
+
+static int timestamp_level;
+static int timestamp_timeout;
+
+static GHashTable *global_meta;
+
+int format_find_tag(const char *module, const char *tag)
+{
+ FORMAT_REC *formats;
+ int n;
+
+ formats = g_hash_table_lookup(default_formats, module);
+ if (formats == NULL)
+ return -1;
+
+ for (n = 0; formats[n].def != NULL; n++) {
+ if (formats[n].tag != NULL &&
+ g_ascii_strcasecmp(formats[n].tag, tag) == 0)
+ return n;
+ }
+
+ return -1;
+}
+
+static void format_expand_code(const char **format, GString *out, int *flags)
+{
+ int set;
+
+ if (flags == NULL) {
+ /* flags are being ignored - skip the code */
+ while (**format != ']' && **format != '\0')
+ (*format)++;
+ return;
+ }
+
+ set = TRUE;
+ (*format)++;
+ while (**format != ']' && **format != '\0') {
+ if (**format == '+')
+ set = TRUE;
+ else if (**format == '-')
+ set = FALSE;
+ else switch (**format) {
+ case 's':
+ case 'S':
+ *flags |= !set ? PRINT_FLAG_UNSET_LINE_START :
+ **format == 's' ? PRINT_FLAG_SET_LINE_START :
+ PRINT_FLAG_SET_LINE_START_IRSSI;
+ break;
+ case 't':
+ *flags |= set ? PRINT_FLAG_SET_TIMESTAMP :
+ PRINT_FLAG_UNSET_TIMESTAMP;
+ break;
+ case 'T':
+ *flags |= set ? PRINT_FLAG_SET_SERVERTAG :
+ PRINT_FLAG_UNSET_SERVERTAG;
+ break;
+ }
+
+ (*format)++;
+ }
+}
+
+void format_ext_color(GString *out, int bg, int color)
+{
+ g_string_append_c(out, 4);
+ if (bg && color < 0x10)
+ g_string_append_c(out, FORMAT_COLOR_NOCHANGE);
+ if (color < 0x10)
+ g_string_append_c(out, color+'0');
+ else {
+ if (color < 0x60)
+ g_string_append_c(out, bg ? FORMAT_COLOR_EXT1_BG
+ : FORMAT_COLOR_EXT1);
+ else if (color < 0xb0)
+ g_string_append_c(out, bg ? FORMAT_COLOR_EXT2_BG
+ : FORMAT_COLOR_EXT2);
+ else
+ g_string_append_c(out, bg ? FORMAT_COLOR_EXT3_BG
+ : FORMAT_COLOR_EXT3);
+ g_string_append_c(out, FORMAT_COLOR_NOCHANGE + ((color-0x10)%0x50));
+ }
+
+ if (!bg && color < 0x10)
+ g_string_append_c(out, FORMAT_COLOR_NOCHANGE);
+}
+
+static void format_ext_color_unexpand(GString *out, gboolean bg, int base, char color)
+{
+ unsigned char value = base + (unsigned char) color - FORMAT_COLOR_NOCHANGE - 0x10;
+
+ g_string_append_c(out, '%');
+ g_string_append_c(out, bg ? 'x' : 'X');
+ if (value > 214)
+ value += 10;
+ g_string_append_c(out, '1' + (value / 36));
+ g_string_append_c(out, ext_color_al[value % 36]);
+}
+
+#ifdef TERM_TRUECOLOR
+void unformat_24bit_color(char **ptr, int off, int *fgcolor, int *bgcolor, int *flags)
+{
+ unsigned int color;
+ unsigned char rgbx[4];
+ unsigned int i;
+ for (i = 0; i < 4; ++i) {
+ if ((*ptr)[i + off] == '\0')
+ return;
+ rgbx[i] = (*ptr)[i + off];
+ }
+ rgbx[3] -= 0x20;
+ *ptr += 4;
+ for (i = 0; i < 3; ++i) {
+ if (rgbx[3] & (0x10 << i))
+ rgbx[i] -= 0x20;
+ }
+ color = rgbx[0] << 16 | rgbx[1] << 8 | rgbx[2];
+ if (rgbx[3] & 0x1) {
+ *bgcolor = color;
+ *flags |= GUI_PRINT_FLAG_COLOR_24_BG;
+ }
+ else {
+ *fgcolor = color;
+ *flags |= GUI_PRINT_FLAG_COLOR_24_FG;
+ }
+}
+
+static void format_24bit_color_unexpand(GString *out, int off, const char **ptr)
+{
+ unsigned int color;
+ unsigned char rgbx[4];
+ unsigned int i;
+ for (i = 0; i < 4; ++i) {
+ if ((*ptr)[i + off] == '\0')
+ return;
+ rgbx[i] = (*ptr)[i + off];
+ }
+ rgbx[3] -= 0x20;
+ *ptr += 4;
+ g_string_append_c(out, '%');
+ for (i = 0; i < 3; ++i) {
+ if (rgbx[3] & (0x10 << i))
+ rgbx[i] -= 0x20;
+ }
+ color = rgbx[0] << 16 | rgbx[1] << 8 | rgbx[2];
+ g_string_append_c(out, rgbx[3] & 0x1 ? 'z' : 'Z');
+ g_string_append_printf(out, "%06X", color);
+}
+#endif
+
+void format_24bit_color(GString *out, int bg, unsigned int color)
+{
+ unsigned char rgb[] = { color >> 16, color >> 8, color };
+#ifdef TERM_TRUECOLOR
+ unsigned char x = bg ? 0x1 : 0;
+ unsigned int i;
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_COLOR_24);
+ for (i = 0; i < 3; ++i) {
+ if (rgb[i] > 0x20)
+ g_string_append_c(out, rgb[i]);
+ else {
+ g_string_append_c(out, 0x20 + rgb[i]);
+ x |= 0x10 << i;
+ }
+ }
+ g_string_append_c(out, 0x20 + x);
+#else /* !TERM_TRUECOLOR */
+ format_ext_color(out, bg, color_24bit_256(rgb));
+#endif /* TERM_TRUECOLOR */
+}
+
+int format_expand_styles(GString *out, const char **format, int *flags)
+{
+ int retval = 1;
+
+ char *p, fmt;
+
+ /* storage for numerical parsing code for %x/X formats. */
+ int tmp;
+ unsigned int tmp2;
+
+ fmt = **format;
+ switch (fmt) {
+ case '{':
+ case '}':
+ case '%':
+ /* escaped char */
+ g_string_append_c(out, fmt);
+ break;
+ case 'U':
+ /* Underline on/off */
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_STYLE_UNDERLINE);
+ break;
+ case '9':
+ case '_':
+ /* bold on/off */
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_STYLE_BOLD);
+ break;
+ case '8':
+ /* reverse */
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_STYLE_REVERSE);
+ break;
+ case 'I':
+ /* italic */
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_STYLE_ITALIC);
+ break;
+ case ':':
+ /* Newline */
+ g_string_append_c(out, '\n');
+ break;
+ case '|':
+ /* Indent here */
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_STYLE_INDENT);
+ break;
+ case 'F':
+ /* blink */
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_STYLE_BLINK);
+ break;
+ case 'n':
+ case 'N':
+ /* default color */
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_STYLE_DEFAULTS);
+ break;
+ case '>':
+ /* clear to end of line */
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_STYLE_CLRTOEOL);
+ break;
+ case '#':
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_STYLE_MONOSPACE);
+ break;
+ case '[':
+ /* code */
+ format_expand_code(format, out, flags);
+ if ((*format)[0] == '\0')
+ /* oops, reached end prematurely */
+ (*format)--;
+
+ break;
+ case 'x':
+ case 'X':
+ if ((*format)[1] < '0' || (*format)[1] > '7')
+ break;
+
+ tmp = 16 + ((*format)[1]-'0'-1)*36;
+ if (tmp > 231) {
+ if (!isalpha((*format)[2]))
+ break;
+
+ tmp += (*format)[2] >= 'a' ? (*format)[2] - 'a' : (*format)[2] - 'A';
+
+ if (tmp > 255)
+ break;
+ }
+ else if (tmp > 0) {
+ if (!isalnum((*format)[2]))
+ break;
+
+ if ((*format)[2] >= 'a')
+ tmp += 10 + (*format)[2] - 'a';
+ else if ((*format)[2] >= 'A')
+ tmp += 10 + (*format)[2] - 'A';
+ else
+ tmp += (*format)[2] - '0';
+ }
+ else {
+ if (!isxdigit((*format)[2]))
+ break;
+
+ tmp = g_ascii_xdigit_value((*format)[2]);
+ }
+
+ retval += 2;
+
+ format_ext_color(out, fmt == 'x', tmp);
+ break;
+ case 'z':
+ case 'Z':
+ tmp2 = 0;
+ for (tmp = 1; tmp < 7; ++tmp) {
+ if (!isxdigit((*format)[tmp])) {
+ tmp2 = UINT_MAX;
+ break;
+ }
+ tmp2 <<= 4;
+ tmp2 |= g_ascii_xdigit_value((*format)[tmp]);
+ }
+
+ if (tmp2 == UINT_MAX)
+ break;
+
+ retval += 6;
+
+ format_24bit_color(out, fmt == 'z', tmp2);
+ break;
+ case 'o':
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_COLOR_NOCHANGE);
+ g_string_append_c(out, (char) -1);
+ break;
+ case 'O':
+ g_string_append_c(out, 4);
+ g_string_append_c(out, (char) -1);
+ g_string_append_c(out, FORMAT_COLOR_NOCHANGE);
+ break;
+ default:
+ /* check if it's a background color */
+ p = strchr(format_backs, fmt);
+ if (p != NULL) {
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_COLOR_NOCHANGE);
+ g_string_append_c(out, (char) ((int) (p-format_backs)+'0'));
+ break;
+ }
+
+ /* check if it's a foreground color */
+ if (fmt == 'p') fmt = 'm';
+ p = strchr(format_fores, fmt);
+ if (p != NULL) {
+ g_string_append_c(out, 4);
+ g_string_append_c(out, (char) ((int) (p-format_fores)+'0'));
+ g_string_append_c(out, FORMAT_COLOR_NOCHANGE);
+ break;
+ }
+
+ /* check if it's a bold foreground color */
+ if (fmt == 'P') fmt = 'M';
+ p = strchr(format_boldfores, fmt);
+ if (p != NULL) {
+ g_string_append_c(out, 4);
+ g_string_append_c(out, (char) (8+(int) (p-format_boldfores)+'0'));
+ g_string_append_c(out, FORMAT_COLOR_NOCHANGE);
+ break;
+ }
+
+ return FALSE;
+ }
+
+ return retval;
+}
+
+void format_read_arglist(va_list va, FORMAT_REC *format,
+ char **arglist, int arglist_size,
+ char *buffer, int buffer_size)
+{
+ int num, len, bufpos;
+
+ g_return_if_fail(format->params < arglist_size);
+
+ bufpos = 0;
+ arglist[format->params] = NULL;
+ for (num = 0; num < format->params; num++) {
+ switch (format->paramtypes[num]) {
+ case FORMAT_STRING:
+ arglist[num] = (char *) va_arg(va, char *);
+ if (arglist[num] == NULL)
+ arglist[num] = "";
+ break;
+ case FORMAT_INT: {
+ int d = (int) va_arg(va, int);
+
+ if (bufpos >= buffer_size) {
+ arglist[num] = "";
+ break;
+ }
+
+ arglist[num] = buffer+bufpos;
+ len = g_snprintf(buffer+bufpos, buffer_size-bufpos,
+ "%d", d);
+ bufpos += len+1;
+ break;
+ }
+ case FORMAT_LONG: {
+ long l = (long) va_arg(va, long);
+
+ if (bufpos >= buffer_size) {
+ arglist[num] = "";
+ break;
+ }
+
+ arglist[num] = buffer+bufpos;
+ len = g_snprintf(buffer+bufpos, buffer_size-bufpos,
+ "%ld", l);
+ bufpos += len+1;
+ break;
+ }
+ case FORMAT_FLOAT: {
+ double f = (double) va_arg(va, double);
+
+ if (bufpos >= buffer_size) {
+ arglist[num] = "";
+ break;
+ }
+
+ arglist[num] = buffer+bufpos;
+ len = g_snprintf(buffer+bufpos, buffer_size-bufpos,
+ "%0.2f", f);
+ bufpos += len+1;
+ break;
+ }
+ }
+ }
+}
+
+void format_dest_meta_stash(TEXT_DEST_REC *dest, const char *meta_key, const char *meta_value)
+{
+ g_hash_table_replace(dest->meta, i_refstr_intern(meta_key), g_strdup(meta_value));
+}
+
+const char *format_dest_meta_stash_find(TEXT_DEST_REC *dest, const char *meta_key)
+{
+ return g_hash_table_lookup(dest->meta, meta_key);
+}
+
+void format_dest_meta_clear_all(TEXT_DEST_REC *dest)
+{
+ g_hash_table_remove_all(dest->meta);
+}
+
+static void clear_global_meta(WINDOW_REC *window, TEXT_DEST_REC *dest)
+{
+ if (dest != NULL && dest->meta == global_meta)
+ g_hash_table_remove_all(global_meta);
+}
+
+void format_create_dest_tag_meta(TEXT_DEST_REC *dest, void *server, const char *server_tag,
+ const char *target, int level, WINDOW_REC *window,
+ GHashTable *meta)
+{
+ memset(dest, 0, sizeof(TEXT_DEST_REC));
+
+ dest->server = server;
+ dest->server_tag = server != NULL ? SERVER(server)->tag : server_tag;
+ dest->target = target;
+ dest->level = level;
+ dest->window = window != NULL ? window :
+ window_find_closest(server, target, level);
+ dest->meta = meta != NULL ? meta : global_meta;
+}
+
+void format_create_dest_tag(TEXT_DEST_REC *dest, void *server, const char *server_tag,
+ const char *target, int level, WINDOW_REC *window)
+{
+ format_create_dest_tag_meta(dest, server, server_tag, target, level, window,
+ server != NULL ? SERVER(server)->current_incoming_meta : NULL);
+}
+
+void format_create_dest(TEXT_DEST_REC *dest, void *server, const char *target, int level,
+ WINDOW_REC *window)
+{
+ format_create_dest_tag(dest, server, NULL, target, level, window);
+}
+
+/* Return length of text part in string (ie. without % codes) */
+int format_get_length(const char *str)
+{
+ GString *tmp;
+ int len;
+ int utf8;
+ int adv = 0;
+
+ g_return_val_if_fail(str != NULL, 0);
+
+ utf8 = string_policy(str);
+
+ tmp = g_string_new(NULL);
+ len = 0;
+ while (*str != '\0') {
+ if (*str == '%' && str[1] != '\0') {
+ str++;
+ if (*str != '%') {
+ adv = format_expand_styles(tmp, &str, NULL);
+ str += adv;
+ if (adv)
+ continue;
+ }
+
+ /* %% or unknown %code, written as-is */
+ if (*str != '%')
+ len++;
+ }
+
+ len += string_advance(&str, utf8);
+ }
+
+ g_string_free(tmp, TRUE);
+ return len;
+}
+
+/* Return how many characters in `str' must be skipped before `len'
+ characters of text is skipped. Like strip_real_length(), except this
+ handles %codes. */
+int format_real_length(const char *str, int len)
+{
+ GString *tmp;
+ const char *start;
+ const char *oldstr;
+ int utf8;
+ int adv = 0;
+ g_return_val_if_fail(str != NULL, 0);
+ g_return_val_if_fail(len >= 0, 0);
+
+ utf8 = string_policy(str);
+
+ start = str;
+ tmp = g_string_new(NULL);
+ while (*str != '\0') {
+ oldstr = str;
+ if (*str == '%' && str[1] != '\0') {
+ str++;
+ if (*str != '%') {
+ adv = format_expand_styles(tmp, &str, NULL);
+ if (adv) {
+ str += adv;
+ continue;
+ }
+ /* discount for unknown % */
+ if (--len < 0) {
+ str = oldstr;
+ break;
+ }
+ oldstr = str;
+ }
+ }
+
+ len -= string_advance(&str, utf8);
+ if (len < 0) {
+ str = oldstr;
+ break;
+ }
+ }
+
+ g_string_free(tmp, TRUE);
+ return (int) (str-start);
+}
+
+char *format_string_expand(const char *text, int *flags)
+{
+ GString *out;
+ char code, *ret;
+ int adv;
+
+ g_return_val_if_fail(text != NULL, NULL);
+
+ out = g_string_new(NULL);
+
+ if (flags != NULL) *flags = 0;
+ code = 0;
+ while (*text != '\0') {
+ if (code == '%') {
+ /* color code */
+ adv = format_expand_styles(out, &text, flags);
+ if (!adv) {
+ g_string_append_c(out, '%');
+ g_string_append_c(out, '%');
+ g_string_append_c(out, *text);
+ } else {
+ text += adv - 1;
+ }
+ code = 0;
+ } else {
+ if (*text == '%')
+ code = *text;
+ else
+ g_string_append_c(out, *text);
+ }
+
+ text++;
+ }
+
+ ret = out->str;
+ g_string_free(out, FALSE);
+ return ret;
+}
+
+inline static void format_flag_unexpand(GString *out, char flag)
+{
+ g_string_append_c(out, '%');
+ g_string_append_c(out, flag);
+}
+
+char *format_string_unexpand(const char *text, int flags)
+{
+ GString *out;
+
+ g_return_val_if_fail(text != NULL, NULL);
+
+ out = g_string_sized_new(strlen(text));
+ while (*text != '\0') {
+ switch (*text) {
+ case '%':
+ g_string_append(out, "%%");
+ break;
+ case 4:
+ text++;
+ if (*text == '\0')
+ break;
+ switch (*text) {
+ case FORMAT_COLOR_EXT1:
+ format_ext_color_unexpand(out, FALSE, 0x10, *++text);
+ break;
+ case FORMAT_COLOR_EXT1_BG:
+ format_ext_color_unexpand(out, TRUE, 0x10, *++text);
+ break;
+ case FORMAT_COLOR_EXT2:
+ format_ext_color_unexpand(out, FALSE, 0x60, *++text);
+ break;
+ case FORMAT_COLOR_EXT2_BG:
+ format_ext_color_unexpand(out, TRUE, 0x60, *++text);
+ break;
+ case FORMAT_COLOR_EXT3:
+ format_ext_color_unexpand(out, FALSE, 0xb0, *++text);
+ break;
+ case FORMAT_COLOR_EXT3_BG:
+ format_ext_color_unexpand(out, TRUE, 0xb0, *++text);
+ break;
+#ifdef TERM_TRUECOLOR
+ case FORMAT_COLOR_24:
+ format_24bit_color_unexpand(out, 1, &text);
+ break;
+#endif
+ case FORMAT_STYLE_BLINK:
+ format_flag_unexpand(out, 'F');
+ break;
+ case FORMAT_STYLE_UNDERLINE:
+ format_flag_unexpand(out, 'U');
+ break;
+ case FORMAT_STYLE_BOLD:
+ format_flag_unexpand(out, '9');
+ break;
+ case FORMAT_STYLE_REVERSE:
+ format_flag_unexpand(out, '8');
+ break;
+ case FORMAT_STYLE_INDENT:
+ format_flag_unexpand(out, '|');
+ break;
+ case FORMAT_STYLE_ITALIC:
+ format_flag_unexpand(out, 'I');
+ break;
+ case FORMAT_STYLE_DEFAULTS:
+ format_flag_unexpand(out, 'N');
+ break;
+ case FORMAT_STYLE_CLRTOEOL:
+ format_flag_unexpand(out, '>');
+ break;
+ case FORMAT_STYLE_MONOSPACE:
+ format_flag_unexpand(out, '#');
+ break;
+ default:
+ if (*text != FORMAT_COLOR_NOCHANGE) {
+ unsigned int value = (unsigned char) *text - '0';
+
+ g_string_append_c(out, '%');
+ if (value < 8) {
+ g_string_append_c(out, format_fores[value]);
+ } else if (value < 16) {
+ g_string_append_c(out, format_boldfores[value - 8]);
+ } else {
+ g_string_append_c(out, 'O');
+ }
+ }
+ text++;
+ if (*text == '\0')
+ break;
+
+ if (*text != FORMAT_COLOR_NOCHANGE) {
+ unsigned int value = (unsigned char) *text - '0';
+
+ g_string_append_c(out, '%');
+ if (value < 8) {
+ g_string_append_c(out, format_backs[value]);
+ } else if (value < 16) {
+ g_string_append(out, "x0");
+ g_string_append_c(out, ext_color_al[value]);
+ } else {
+ g_string_append_c(out, 'o');
+ }
+ }
+ break;
+ }
+ break;
+ default:
+ g_string_append_c(out, *text);
+ break;
+ }
+ if (*text != '\0')
+ text++;
+ }
+
+ return g_string_free(out, FALSE);
+}
+
+static char *format_get_text_args(TEXT_DEST_REC *dest,
+ const char *text, char **arglist)
+{
+ GString *out;
+ char code, *ret;
+ int need_free;
+ int adv;
+
+ out = g_string_new(NULL);
+
+ code = 0;
+ while (*text != '\0') {
+ if (code == '%') {
+ /* color code */
+ adv = format_expand_styles(out, &text, &dest->flags);
+ if (!adv) {
+ g_string_append_c(out, '%');
+ g_string_append_c(out, '%');
+ g_string_append_c(out, *text);
+ } else {
+ text += adv - 1;
+ }
+ code = 0;
+ } else if (code == '$') {
+ /* argument */
+ char *ret;
+
+ ret = parse_special((char **) &text, dest->server,
+ dest->target == NULL ? NULL :
+ window_item_find(dest->server, dest->target),
+ arglist, &need_free, NULL, 0);
+
+ if (ret != NULL) {
+ /* string shouldn't end with \003 or it could
+ mess up the next one or two characters */
+ int diff;
+ int len = strlen(ret);
+ while (len > 0 && ret[len-1] == 3) len--;
+ diff = strlen(ret)-len;
+
+ g_string_append(out, ret);
+ if (diff > 0)
+ g_string_truncate(out, out->len-diff);
+ if (need_free) g_free(ret);
+ }
+ code = 0;
+ } else {
+ if (*text == '%' || *text == '$')
+ code = *text;
+ else
+ g_string_append_c(out, *text);
+ }
+
+ text++;
+ }
+
+ ret = out->str;
+ g_string_free(out, FALSE);
+ return ret;
+}
+
+char *format_get_text_theme(THEME_REC *theme, const char *module,
+ TEXT_DEST_REC *dest, int formatnum, ...)
+{
+ va_list va;
+ char *str;
+
+ if (theme == NULL)
+ theme = window_get_theme(dest->window);
+
+ va_start(va, formatnum);
+ str = format_get_text_theme_args(theme, module, dest, formatnum, va);
+ va_end(va);
+
+ return str;
+}
+
+char *format_get_text_theme_args(THEME_REC *theme, const char *module,
+ TEXT_DEST_REC *dest, int formatnum,
+ va_list va)
+{
+ char *arglist[MAX_FORMAT_PARAMS];
+ char buffer[DEFAULT_FORMAT_ARGLIST_SIZE];
+ FORMAT_REC *formats;
+
+ formats = g_hash_table_lookup(default_formats, module);
+ format_read_arglist(va, &formats[formatnum],
+ arglist, sizeof(arglist)/sizeof(char *),
+ buffer, sizeof(buffer));
+
+ return format_get_text_theme_charargs(theme, module, dest,
+ formatnum, arglist);
+}
+
+char *format_get_text_theme_charargs(THEME_REC *theme, const char *module,
+ TEXT_DEST_REC *dest, int formatnum,
+ char **args)
+{
+ MODULE_THEME_REC *module_theme;
+ char *text;
+
+ if (module == NULL)
+ return NULL;
+
+ module_theme = g_hash_table_lookup(theme->modules, module);
+ if (module_theme == NULL)
+ return NULL;
+
+ text = module_theme->expanded_formats[formatnum];
+ return format_get_text_args(dest, text, args);
+}
+
+char *format_get_text(const char *module, WINDOW_REC *window,
+ void *server, const char *target,
+ int formatnum, ...)
+{
+ TEXT_DEST_REC dest;
+ THEME_REC *theme;
+ va_list va;
+ char *str;
+
+ format_create_dest(&dest, server, target, 0, window);
+ theme = window_get_theme(dest.window);
+
+ va_start(va, formatnum);
+ str = format_get_text_theme_args(theme, module, &dest, formatnum, va);
+ va_end(va);
+
+ return str;
+}
+
+/* add `linestart' to start of each line in `text'. `text' may contain
+ multiple lines separated with \n. */
+char *format_add_linestart(const char *text, const char *linestart)
+{
+ GString *str;
+ char *ret;
+
+ if (linestart == NULL)
+ return g_strdup(text);
+
+ if (strchr(text, '\n') == NULL)
+ return g_strconcat(linestart, text, NULL);
+
+ str = g_string_new(linestart);
+ while (*text != '\0') {
+ g_string_append_c(str, *text);
+ if (*text == '\n')
+ g_string_append(str, linestart);
+ text++;
+ }
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+char *format_add_lineend(const char *text, const char *linestart)
+{
+ GString *str;
+ char *ret;
+
+ if (linestart == NULL)
+ return g_strdup(text);
+
+ if (strchr(text, '\n') == NULL)
+ return g_strconcat(text, linestart, NULL);
+
+ str = g_string_new(NULL);
+ while (*text != '\0') {
+ if (*text == '\n')
+ g_string_append(str, linestart);
+ g_string_append_c(str, *text);
+ text++;
+ }
+ g_string_append(str, linestart);
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+#define LINE_START_IRSSI_LEVEL \
+ (MSGLEVEL_CLIENTERROR | MSGLEVEL_CLIENTNOTICE)
+
+#define NOT_LINE_START_LEVEL \
+ (MSGLEVEL_NEVER | MSGLEVEL_LASTLOG | MSGLEVEL_CLIENTCRAP | \
+ MSGLEVEL_MSGS | MSGLEVEL_PUBLIC | MSGLEVEL_DCC | MSGLEVEL_DCCMSGS | \
+ MSGLEVEL_ACTIONS | MSGLEVEL_NOTICES | MSGLEVEL_SNOTES | MSGLEVEL_CTCPS)
+
+/* return the "-!- " text at the start of the line */
+char *format_get_level_tag(THEME_REC *theme, TEXT_DEST_REC *dest)
+{
+ int format;
+
+ /* check for flags if we want to override defaults */
+ if (dest->flags & PRINT_FLAG_UNSET_LINE_START)
+ return NULL;
+
+ if (dest->flags & PRINT_FLAG_SET_LINE_START)
+ format = TXT_LINE_START;
+ else if (dest->flags & PRINT_FLAG_SET_LINE_START_IRSSI)
+ format = TXT_LINE_START_IRSSI;
+ else {
+ /* use defaults */
+ if (dest->level & LINE_START_IRSSI_LEVEL)
+ format = TXT_LINE_START_IRSSI;
+ else if ((dest->level & NOT_LINE_START_LEVEL) == 0)
+ format = TXT_LINE_START;
+ else
+ return NULL;
+ }
+
+ return format_get_text_theme(theme, MODULE_NAME, dest, format);
+}
+
+static char *get_timestamp(THEME_REC *theme, TEXT_DEST_REC *dest, time_t t)
+{
+ char *format, str[256];
+ struct tm *tm;
+ int diff;
+
+ if ((timestamp_level & dest->level) == 0)
+ return NULL;
+
+ /* check for flags if we want to override defaults */
+ if (dest->flags & PRINT_FLAG_UNSET_TIMESTAMP)
+ return NULL;
+
+ if ((dest->flags & PRINT_FLAG_SET_TIMESTAMP) == 0 &&
+ (dest->level & (MSGLEVEL_NEVER|MSGLEVEL_LASTLOG)) != 0)
+ return NULL;
+
+
+ if (timestamp_timeout > 0) {
+ diff = t - dest->window->last_timestamp;
+ dest->window->last_timestamp = t;
+ if (diff < timestamp_timeout)
+ return NULL;
+ }
+
+ tm = localtime(&t);
+ format = format_get_text_theme(theme, MODULE_NAME, dest,
+ TXT_TIMESTAMP);
+ if (strftime(str, sizeof(str), format, tm) <= 0)
+ str[0] = '\0';
+ g_free(format);
+ return g_strdup(str);
+}
+
+static char *get_server_tag(THEME_REC *theme, TEXT_DEST_REC *dest)
+{
+ int count = 0;
+
+ if (dest->server_tag == NULL || hide_server_tags)
+ return NULL;
+
+ /* check for flags if we want to override defaults */
+ if (dest->flags & PRINT_FLAG_UNSET_SERVERTAG)
+ return NULL;
+
+ if ((dest->flags & PRINT_FLAG_SET_SERVERTAG) == 0) {
+ if (dest->window->active != NULL &&
+ dest->window->active->server == dest->server)
+ return NULL;
+
+ if (servers != NULL) {
+ count++;
+ if (servers->next != NULL)
+ count++;
+ }
+ if (count < 2 && lookup_servers != NULL) {
+ count++;
+ if (lookup_servers->next != NULL)
+ count++;
+ }
+
+ if (count < 2)
+ return NULL;
+ }
+
+ return format_get_text_theme(theme, MODULE_NAME, dest,
+ TXT_SERVERTAG, dest->server_tag);
+}
+
+char *format_get_line_start(THEME_REC *theme, TEXT_DEST_REC *dest, time_t t)
+{
+ char *timestamp, *servertag;
+ char *linestart;
+
+ timestamp = get_timestamp(theme, dest, t);
+ servertag = get_server_tag(theme, dest);
+
+ if (timestamp == NULL && servertag == NULL)
+ return NULL;
+
+ linestart = g_strconcat(timestamp != NULL ? timestamp : "",
+ servertag, NULL);
+
+ g_free_not_null(timestamp);
+ g_free_not_null(servertag);
+ return linestart;
+}
+
+void format_newline(TEXT_DEST_REC *dest)
+{
+ g_return_if_fail(dest != NULL);
+ g_return_if_fail(dest->window != NULL);
+
+ signal_emit_id(signal_gui_print_text, 6, dest->window, GINT_TO_POINTER(-1),
+ GINT_TO_POINTER(-1), GINT_TO_POINTER(GUI_PRINT_FLAG_NEWLINE), "", dest);
+}
+
+#ifndef TERM_TRUECOLOR
+inline static int color_24bit_256_int(unsigned int color)
+{
+ unsigned char rgb[] = { color >> 16, color >> 8, color };
+ return color_24bit_256(rgb);
+}
+#endif /* !TERM_TRUECOLOR */
+
+/* parse ANSI color string */
+static const char *get_ansi_color(THEME_REC *theme, const char *str,
+ int *fg_ret, int *bg_ret, int *flags_ret)
+{
+ static char ansitab[8] = { 0, 4, 2, 6, 1, 5, 3, 7 };
+ const char *start;
+ char *endptr;
+ int fg, bg, flags, i;
+ guint num, num2;
+
+ if (*str != '[')
+ return str;
+ start = str++;
+
+ fg = fg_ret == NULL || *fg_ret < 0 ? theme->default_color : *fg_ret;
+ bg = bg_ret == NULL || *bg_ret < 0 ? -1 : *bg_ret;
+ flags = flags_ret == NULL ? 0 : *flags_ret;
+
+ num = 0;
+ for (;; str++) {
+ if (*str == '\0') return start;
+
+ if (i_isdigit(*str)) {
+ if (!parse_uint(str, &endptr, 10, &num)) {
+ return start;
+ }
+ str = endptr;
+ }
+
+ if (*str != ';' && *str != 'm')
+ return start;
+
+ switch (num) {
+ case 0:
+ /* reset colors and attributes back to default */
+ fg = theme->default_color;
+ bg = -1;
+ flags &= ~(GUI_PRINT_FLAG_INDENT |
+ GUI_PRINT_FLAG_BOLD | GUI_PRINT_FLAG_ITALIC | GUI_PRINT_FLAG_UNDERLINE |
+ GUI_PRINT_FLAG_BLINK | GUI_PRINT_FLAG_REVERSE |
+ GUI_PRINT_FLAG_COLOR_24_FG | GUI_PRINT_FLAG_COLOR_24_BG);
+ break;
+ case 1:
+ /* hilight */
+ flags |= GUI_PRINT_FLAG_BOLD;
+ break;
+ case 22:
+ /* normal */
+ flags &= ~GUI_PRINT_FLAG_BOLD;
+ break;
+ case 3:
+ /* italic */
+ flags |= GUI_PRINT_FLAG_ITALIC;
+ break;
+ case 23:
+ /* not italic */
+ flags &= ~GUI_PRINT_FLAG_ITALIC;
+ break;
+ case 4:
+ /* underline */
+ flags |= GUI_PRINT_FLAG_UNDERLINE;
+ break;
+ case 24:
+ /* not underline */
+ flags &= ~GUI_PRINT_FLAG_UNDERLINE;
+ break;
+ case 5:
+ /* blink */
+ flags |= GUI_PRINT_FLAG_BLINK;
+ break;
+ case 25:
+ /* steady */
+ flags &= ~GUI_PRINT_FLAG_BLINK;
+ break;
+ case 7:
+ /* reverse */
+ flags |= GUI_PRINT_FLAG_REVERSE;
+ break;
+ case 27:
+ /* positive */
+ flags &= ~GUI_PRINT_FLAG_REVERSE;
+ break;
+ case 39:
+ /* reset fg */
+ flags &= ~GUI_PRINT_FLAG_COLOR_24_FG;
+ fg = theme->default_color;
+ break;
+ case 49:
+ /* reset bg */
+ bg = -1;
+ flags &= ~(GUI_PRINT_FLAG_COLOR_24_BG | GUI_PRINT_FLAG_INDENT);
+ break;
+ case 38:
+ case 48:
+ /* ANSI indexed color or RGB color */
+ if (*str != ';') break;
+ str++;
+
+ if (!parse_uint(str, &endptr, 10, &num2)) {
+ return start;
+ }
+ str = endptr;
+
+ if (*str == '\0') return start;
+
+ switch (num2) {
+ case 2:
+ /* RGB */
+ num2 = 0;
+
+ for (i = 0; i < 3; ++i) {
+ num2 <<= 8;
+
+ if (*str != ';' && *str != ':') {
+ i = -1;
+ break;
+ }
+ str++;
+ for (; i_isdigit(*str); str++)
+ num2 = (num2&~0xff) |
+ (((num2&0xff) * 10 + (*str-'0'))&0xff);
+
+ if (*str == '\0') return start;
+ }
+
+ if (i == -1) break;
+#ifdef TERM_TRUECOLOR
+ if (num == 38) {
+ flags |= GUI_PRINT_FLAG_COLOR_24_FG;
+ fg = num2;
+ } else if (num == 48) {
+ flags |= GUI_PRINT_FLAG_COLOR_24_BG;
+ bg = num2;
+ }
+#else /* !TERM_TRUECOLOR */
+ if (num == 38) {
+ flags &= ~GUI_PRINT_FLAG_COLOR_24_FG;
+ fg = color_24bit_256_int(num2);
+ } else if (num == 48) {
+ flags &= ~GUI_PRINT_FLAG_COLOR_24_BG;
+ bg = color_24bit_256_int(num2);
+ }
+#endif
+
+ break;
+ case 5:
+ /* indexed */
+ if (*str != ';') break;
+ str++;
+
+ if (!parse_uint(str, &endptr, 10, &num2)) {
+ return start;
+ }
+ str = endptr;
+
+ if (*str == '\0') return start;
+
+ if (num == 38) {
+ flags &= ~GUI_PRINT_FLAG_COLOR_24_FG;
+ fg = num2;
+ } else if (num == 48) {
+ flags &= ~GUI_PRINT_FLAG_COLOR_24_BG;
+ bg = num2;
+ }
+
+ break;
+ }
+ break;
+ default:
+ if (num >= 30 && num <= 37) {
+ flags &= ~GUI_PRINT_FLAG_COLOR_24_FG;
+ fg = ansitab[num-30];
+ } else if (num >= 40 && num <= 47) {
+ flags &= ~GUI_PRINT_FLAG_COLOR_24_BG;
+ bg = ansitab[num-40];
+ } else if (num >= 90 && num <= 97) {
+ flags &= ~GUI_PRINT_FLAG_COLOR_24_FG;
+ fg = 8 + ansitab[num-90];
+ } else if (num >= 100 && num <= 107) {
+ flags &= ~GUI_PRINT_FLAG_COLOR_24_BG;
+ bg = 8 + ansitab[num-100];
+ }
+ break;
+ }
+ num = 0;
+
+ if (*str == 'm') {
+ if (fg_ret != NULL) *fg_ret = fg;
+ if (bg_ret != NULL) *bg_ret = bg;
+ if (flags_ret != NULL) *flags_ret = flags;
+
+ str++;
+ break;
+ }
+ }
+
+ return str;
+}
+
+/* parse MIRC color string */
+static void get_mirc_color(const char **str, int *fg_ret, int *bg_ret)
+{
+ int fg, bg;
+
+ fg = fg_ret == NULL ? -1 : *fg_ret;
+ bg = bg_ret == NULL ? -1 : *bg_ret;
+
+ if (!i_isdigit(**str)) {
+ /* turn off color */
+ fg = -1;
+ bg = -1;
+ } else {
+ /* foreground color */
+ fg = **str-'0';
+ (*str)++;
+ if (i_isdigit(**str)) {
+ fg = fg*10 + (**str-'0');
+ (*str)++;
+ }
+
+ if ((*str)[0] == ',' && i_isdigit((*str)[1])) {
+ /* background color */
+ (*str)++;
+ bg = **str-'0';
+ (*str)++;
+ if (i_isdigit(**str)) {
+ bg = bg*10 + (**str-'0');
+ (*str)++;
+ }
+ }
+ }
+
+ if (fg_ret) *fg_ret = fg;
+ if (bg_ret) *bg_ret = bg;
+}
+
+#define IS_COLOR_CODE(c) \
+ ((c) == 2 || (c) == 3 || (c) == 4 || (c) == 6 || (c) == 7 || \
+ (c) == 15 || (c) == 17 || (c) == 22 || (c) == 27 || (c) == 29 || (c) == 31)
+
+/* Return how many characters in `str' must be skipped before `len'
+ characters of text is skipped. */
+int strip_real_length(const char *str, int len,
+ int *last_color_pos, int *last_color_len)
+{
+ const char *start = str;
+
+ if (last_color_pos != NULL)
+ *last_color_pos = -1;
+ if (last_color_len != NULL)
+ *last_color_len = -1;
+
+ while (*str != '\0') {
+ if (*str == 3) { /* mIRC color */
+ const char *mircstart = str;
+
+ if (last_color_pos != NULL)
+ *last_color_pos = (int) (str-start);
+ str++;
+ get_mirc_color(&str, NULL, NULL);
+ if (last_color_len != NULL)
+ *last_color_len = (int) (str-mircstart);
+
+ } else if (*str == 4 && str[1] != '\0') {
+ /* We expect 4 to indicate an internal Irssi color code. However 4
+ * also means hex color, an alternative to mIRC color codes. We
+ * don't support those. */
+#ifdef TERM_TRUECOLOR
+ if (str[1] == FORMAT_COLOR_24 && str[2] != '\0') {
+ if (str[3] == '\0') str++;
+ else if (str[4] == '\0') str += 2;
+ else if (str[5] == '\0') str += 3;
+ else {
+ if (last_color_pos != NULL)
+ *last_color_pos = (int) (str-start);
+ if (last_color_len != NULL)
+ *last_color_len = 6;
+ str+=4;
+ }
+ } else
+#endif
+ if (str[1] < FORMAT_STYLE_SPECIAL && str[2] != '\0') {
+ if (last_color_pos != NULL)
+ *last_color_pos = (int) (str-start);
+ if (last_color_len != NULL)
+ *last_color_len = 3;
+ str++;
+ } else if (str[1] == FORMAT_STYLE_DEFAULTS) {
+ if (last_color_pos != NULL)
+ *last_color_pos = (int) (str-start);
+ if (last_color_len != NULL)
+ *last_color_len = 2;
+ }
+ str += 2;
+ } else {
+ if (!IS_COLOR_CODE(*str)) {
+ if (len-- == 0)
+ break;
+ }
+ str++;
+ }
+ }
+
+ return (int) (str-start);
+}
+
+char *strip_codes(const char *input)
+{
+ const char *p;
+ char *str, *out;
+
+ out = str = g_strdup(input);
+ for (p = input; *p != '\0'; p++) {
+ if (*p == 3) {
+ p++;
+
+ /* mirc color */
+ get_mirc_color(&p, NULL, NULL);
+ p--;
+ continue;
+ }
+
+ if (*p == 4 && p[1] != '\0') {
+ if (p[1] >= FORMAT_STYLE_SPECIAL) {
+ p++;
+ continue;
+ }
+
+ /* irssi color */
+ if (p[2] != '\0') {
+#ifdef TERM_TRUECOLOR
+ if (p[1] == FORMAT_COLOR_24) {
+ if (p[3] == '\0') p += 2;
+ else if (p[4] == '\0') p += 3;
+ else if (p[5] == '\0') p += 4;
+ else p += 5;
+ } else
+#endif /* TERM_TRUECOLOR */
+ p += 2;
+ continue;
+ }
+ }
+
+ if (*p == 27 && p[1] != '\0') {
+ p++;
+ p = get_ansi_color(current_theme, p, NULL, NULL, NULL);
+ p--;
+ } else if (!IS_COLOR_CODE(*p))
+ *out++ = *p;
+ }
+
+ *out = '\0';
+ return str;
+}
+
+/* parse text string into GUI_PRINT_FLAG_* separated pieces and emit them to handler
+ handler is a SIGNAL_FUNC with the following arguments:
+
+ WINDOW_REC *window, void *fgcolor_int, void *bgcolor_int,
+ void *flags_int, const char *textpiece, TEXT_DEST_REC *dest
+
+ */
+void format_send_as_gui_flags(TEXT_DEST_REC *dest, const char *text, SIGNAL_FUNC handler)
+{
+ THEME_REC *theme;
+ char *dup, *str, *ptr, type;
+ int fgcolor, bgcolor;
+ int flags;
+
+ theme = window_get_theme(dest->window);
+
+ dup = str = g_strdup(text);
+
+ flags = 0; fgcolor = theme->default_color; bgcolor = -1;
+
+ if (*str == '\0') {
+ /* empty line, write line info only */
+ handler(dest->window, GINT_TO_POINTER(fgcolor), GINT_TO_POINTER(bgcolor),
+ GINT_TO_POINTER(flags), str, dest);
+ }
+
+ while (*str != '\0') {
+ type = '\0';
+ for (ptr = str; *ptr != '\0'; ptr++) {
+ if (IS_COLOR_CODE(*ptr) || *ptr == '\n') {
+ type = *ptr;
+ *ptr++ = '\0';
+ break;
+ }
+ }
+
+ if (type == 4 && *ptr == FORMAT_STYLE_CLRTOEOL) {
+ /* clear to end of line */
+ flags |= GUI_PRINT_FLAG_CLRTOEOL;
+ }
+
+ if (*str != '\0' || (flags & GUI_PRINT_FLAG_CLRTOEOL)) {
+ /* send the text to gui handler */
+ handler(dest->window, GINT_TO_POINTER(fgcolor), GINT_TO_POINTER(bgcolor),
+ GINT_TO_POINTER(flags), str, dest);
+ flags &= ~(GUI_PRINT_FLAG_INDENT|GUI_PRINT_FLAG_CLRTOEOL);
+ }
+
+ if (type == '\n') {
+ handler(dest->window, GINT_TO_POINTER(-1), GINT_TO_POINTER(-1),
+ GINT_TO_POINTER(GUI_PRINT_FLAG_NEWLINE), "", dest);
+ fgcolor = theme->default_color;
+ bgcolor = -1;
+ flags &= GUI_PRINT_FLAG_INDENT|GUI_PRINT_FLAG_MONOSPACE;
+ }
+
+ if (*ptr == '\0')
+ break;
+
+ switch (type)
+ {
+ case 2:
+ /* bold */
+ if (!hide_text_style)
+ flags ^= GUI_PRINT_FLAG_BOLD;
+ break;
+ case 3:
+ /* MIRC color */
+ get_mirc_color((const char **) &ptr,
+ hide_colors ? NULL : &fgcolor,
+ hide_colors ? NULL : &bgcolor);
+ if (!hide_colors)
+ flags |= GUI_PRINT_FLAG_MIRC_COLOR;
+ break;
+ case 4:
+ /* user specific colors */
+ flags &= ~GUI_PRINT_FLAG_MIRC_COLOR;
+ switch (*ptr) {
+ case FORMAT_STYLE_BLINK:
+ flags ^= GUI_PRINT_FLAG_BLINK;
+ break;
+ case FORMAT_STYLE_UNDERLINE:
+ flags ^= GUI_PRINT_FLAG_UNDERLINE;
+ break;
+ case FORMAT_STYLE_BOLD:
+ flags ^= GUI_PRINT_FLAG_BOLD;
+ break;
+ case FORMAT_STYLE_REVERSE:
+ flags ^= GUI_PRINT_FLAG_REVERSE;
+ break;
+ case FORMAT_STYLE_ITALIC:
+ flags ^= GUI_PRINT_FLAG_ITALIC;
+ break;
+ case FORMAT_STYLE_MONOSPACE:
+ flags ^= GUI_PRINT_FLAG_MONOSPACE;
+ break;
+ case FORMAT_STYLE_INDENT:
+ flags |= GUI_PRINT_FLAG_INDENT;
+ break;
+ case FORMAT_STYLE_DEFAULTS:
+ fgcolor = theme->default_color;
+ bgcolor = -1;
+ flags &= GUI_PRINT_FLAG_INDENT|GUI_PRINT_FLAG_MONOSPACE;
+ break;
+ case FORMAT_STYLE_CLRTOEOL:
+ break;
+ case FORMAT_COLOR_EXT1:
+ fgcolor = 0x10 + *++ptr - FORMAT_COLOR_NOCHANGE;
+ flags &= ~GUI_PRINT_FLAG_COLOR_24_FG;
+ break;
+ case FORMAT_COLOR_EXT1_BG:
+ bgcolor = 0x10 + *++ptr - FORMAT_COLOR_NOCHANGE;
+ flags &= ~GUI_PRINT_FLAG_COLOR_24_BG;
+ break;
+ case FORMAT_COLOR_EXT2:
+ fgcolor = 0x60 + *++ptr - FORMAT_COLOR_NOCHANGE;
+ flags &= ~GUI_PRINT_FLAG_COLOR_24_FG;
+ break;
+ case FORMAT_COLOR_EXT2_BG:
+ bgcolor = 0x60 + *++ptr - FORMAT_COLOR_NOCHANGE;
+ flags &= ~GUI_PRINT_FLAG_COLOR_24_BG;
+ break;
+ case FORMAT_COLOR_EXT3:
+ fgcolor = 0xb0 + *++ptr - FORMAT_COLOR_NOCHANGE;
+ flags &= ~GUI_PRINT_FLAG_COLOR_24_FG;
+ break;
+ case FORMAT_COLOR_EXT3_BG:
+ bgcolor = 0xb0 + *++ptr - FORMAT_COLOR_NOCHANGE;
+ flags &= ~GUI_PRINT_FLAG_COLOR_24_BG;
+ break;
+#ifdef TERM_TRUECOLOR
+ case FORMAT_COLOR_24:
+ unformat_24bit_color(&ptr, 1, &fgcolor, &bgcolor, &flags);
+ break;
+#endif
+ default:
+ if (*ptr != FORMAT_COLOR_NOCHANGE) {
+ flags &= ~GUI_PRINT_FLAG_COLOR_24_FG;
+ fgcolor = *ptr==(char)0xff ? -1 : (unsigned char) *ptr-'0';
+ }
+ if (ptr[1] == '\0')
+ break;
+
+ ptr++;
+ if (*ptr != FORMAT_COLOR_NOCHANGE) {
+ flags &= ~GUI_PRINT_FLAG_COLOR_24_BG;
+ bgcolor = *ptr==(char)0xff ? -1 : *ptr-'0';
+ }
+ }
+ if (*ptr == '\0')
+ break;
+
+ ptr++;
+ break;
+ case 6:
+ /* blink */
+ if (!hide_text_style)
+ flags ^= GUI_PRINT_FLAG_BLINK;
+ break;
+ case 15:
+ /* remove all styling */
+ fgcolor = theme->default_color;
+ bgcolor = -1;
+ flags &= GUI_PRINT_FLAG_INDENT|GUI_PRINT_FLAG_MONOSPACE;
+ break;
+ case 17:
+ if (!hide_text_style)
+ flags ^= GUI_PRINT_FLAG_MONOSPACE;
+ break;
+ case 22:
+ /* reverse */
+ if (!hide_text_style)
+ flags ^= GUI_PRINT_FLAG_REVERSE;
+ break;
+ case 29:
+ /* italic */
+ if (!hide_text_style)
+ flags ^= GUI_PRINT_FLAG_ITALIC;
+ break;
+ case 31:
+ /* underline */
+ if (!hide_text_style)
+ flags ^= GUI_PRINT_FLAG_UNDERLINE;
+ break;
+ case 27:
+ /* ansi color code */
+ ptr = (char *)
+ get_ansi_color(theme, ptr,
+ hide_colors ? NULL : &fgcolor,
+ hide_colors ? NULL : &bgcolor,
+ hide_colors ? NULL : &flags);
+ break;
+ }
+
+ str = ptr;
+ }
+
+ g_free(dup);
+}
+
+inline static void gui_print_text_emitter(WINDOW_REC *window, void *fgcolor_int, void *bgcolor_int,
+ void *flags_int, const char *textpiece,
+ TEXT_DEST_REC *dest)
+{
+ signal_emit_id(signal_gui_print_text, 6, window, fgcolor_int, bgcolor_int, flags_int,
+ textpiece, dest);
+}
+
+/* send a fully parsed text string for GUI to print */
+void format_send_to_gui(TEXT_DEST_REC *dest, const char *text)
+{
+ format_send_as_gui_flags(dest, text, (SIGNAL_FUNC) gui_print_text_emitter);
+}
+
+void format_gui_flags(GString *out, int *last_fg, int *last_bg, int *last_flags, int fg, int bg,
+ int flags)
+{
+ if (fg != *last_fg ||
+ (flags & GUI_PRINT_FLAG_COLOR_24_FG) != (*last_flags & GUI_PRINT_FLAG_COLOR_24_FG)) {
+ *last_fg = fg;
+
+#ifdef TERM_TRUECOLOR
+ if (flags & GUI_PRINT_FLAG_COLOR_24_FG) {
+ *last_flags |= GUI_PRINT_FLAG_COLOR_24_FG;
+ format_24bit_color(out, 0, fg);
+ } else {
+ *last_flags &= ~GUI_PRINT_FLAG_COLOR_24_FG;
+#endif
+ if (fg < 0) {
+ g_string_append_c(out, 4);
+ g_string_append_c(out, (char) -1);
+ g_string_append_c(out, FORMAT_COLOR_NOCHANGE);
+ } else {
+ format_ext_color(out, 0, fg);
+ }
+#ifdef TERM_TRUECOLOR
+ }
+#endif
+ }
+ if (bg != *last_bg ||
+ (flags & GUI_PRINT_FLAG_COLOR_24_BG) != (*last_flags & GUI_PRINT_FLAG_COLOR_24_BG)) {
+ *last_bg = bg;
+#ifdef TERM_TRUECOLOR
+ if (flags & GUI_PRINT_FLAG_COLOR_24_BG) {
+ *last_flags |= GUI_PRINT_FLAG_COLOR_24_BG;
+ format_24bit_color(out, 1, bg);
+ } else {
+ *last_flags &= ~GUI_PRINT_FLAG_COLOR_24_BG;
+#endif
+ if (bg < 0) {
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_COLOR_NOCHANGE);
+ g_string_append_c(out, (char) -1);
+ } else {
+ format_ext_color(out, 1, bg);
+ }
+#ifdef TERM_TRUECOLOR
+ }
+#endif
+ }
+
+ if ((flags & GUI_PRINT_FLAG_UNDERLINE) != (*last_flags & GUI_PRINT_FLAG_UNDERLINE)) {
+ *last_flags ^= GUI_PRINT_FLAG_UNDERLINE;
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_STYLE_UNDERLINE);
+ }
+ if ((flags & GUI_PRINT_FLAG_REVERSE) != (*last_flags & GUI_PRINT_FLAG_REVERSE)) {
+ *last_flags ^= GUI_PRINT_FLAG_REVERSE;
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_STYLE_REVERSE);
+ }
+ if ((flags & GUI_PRINT_FLAG_BLINK) != (*last_flags & GUI_PRINT_FLAG_BLINK)) {
+ *last_flags ^= GUI_PRINT_FLAG_BLINK;
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_STYLE_BLINK);
+ }
+ if ((flags & GUI_PRINT_FLAG_BOLD) != (*last_flags & GUI_PRINT_FLAG_BOLD)) {
+ *last_flags ^= GUI_PRINT_FLAG_BOLD;
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_STYLE_BOLD);
+ }
+ if ((flags & GUI_PRINT_FLAG_ITALIC) != (*last_flags & GUI_PRINT_FLAG_ITALIC)) {
+ *last_flags ^= GUI_PRINT_FLAG_ITALIC;
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_STYLE_ITALIC);
+ }
+ if ((flags & GUI_PRINT_FLAG_MONOSPACE) != (*last_flags & GUI_PRINT_FLAG_MONOSPACE)) {
+ *last_flags ^= GUI_PRINT_FLAG_MONOSPACE;
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_STYLE_MONOSPACE);
+ }
+ if (flags & GUI_PRINT_FLAG_INDENT) {
+ *last_flags ^= GUI_PRINT_FLAG_INDENT;
+ g_string_append_c(out, 4);
+ g_string_append_c(out, FORMAT_STYLE_INDENT);
+ }
+}
+
+static void read_settings(void)
+{
+ timestamp_level = settings_get_bool("timestamps") ? MSGLEVEL_ALL : 0;
+ if (timestamp_level > 0)
+ timestamp_level = settings_get_level("timestamp_level");
+ timestamp_timeout = settings_get_time("timestamp_timeout")/1000;
+
+ hide_server_tags = settings_get_bool("hide_server_tags");
+ hide_text_style = settings_get_bool("hide_text_style");
+ hide_colors = hide_text_style || settings_get_bool("hide_colors");
+}
+
+void formats_init(void)
+{
+ signal_gui_print_text = signal_get_uniq_id("gui print text");
+ global_meta =
+ g_hash_table_new_full(g_str_hash, (GEqualFunc) g_str_equal,
+ (GDestroyNotify) i_refstr_release, (GDestroyNotify) g_free);
+
+ read_settings();
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_add_last("gui print text finished", (SIGNAL_FUNC) clear_global_meta);
+}
+
+void formats_deinit(void)
+{
+ g_hash_table_destroy(global_meta);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_remove("gui print text finished", (SIGNAL_FUNC) clear_global_meta);
+}
diff --git a/src/fe-common/core/formats.h b/src/fe-common/core/formats.h
new file mode 100644
index 0000000..5666c73
--- /dev/null
+++ b/src/fe-common/core/formats.h
@@ -0,0 +1,184 @@
+#ifndef IRSSI_FE_COMMON_CORE_FORMATS_H
+#define IRSSI_FE_COMMON_CORE_FORMATS_H
+
+#include <irssi/src/core/signals.h>
+#include <irssi/src/fe-common/core/fe-windows.h>
+#include <irssi/src/fe-common/core/themes.h>
+
+#define GUI_PRINT_FLAG_BOLD 0x0001
+#define GUI_PRINT_FLAG_REVERSE 0x0002
+#define GUI_PRINT_FLAG_UNDERLINE 0x0004
+#define GUI_PRINT_FLAG_BLINK 0x0008
+#define GUI_PRINT_FLAG_MIRC_COLOR 0x0010
+#define GUI_PRINT_FLAG_INDENT 0x0020
+#define GUI_PRINT_FLAG_ITALIC 0x0040
+#define GUI_PRINT_FLAG_NEWLINE 0x0080
+#define GUI_PRINT_FLAG_CLRTOEOL 0x0100
+#define GUI_PRINT_FLAG_MONOSPACE 0x0200
+#define GUI_PRINT_FLAG_COLOR_24_FG 0x0400
+#define GUI_PRINT_FLAG_COLOR_24_BG 0x0800
+
+#define MAX_FORMAT_PARAMS 10
+#define DEFAULT_FORMAT_ARGLIST_SIZE 200
+
+enum {
+ FORMAT_STRING,
+ FORMAT_INT,
+ FORMAT_LONG,
+ FORMAT_FLOAT
+};
+
+struct _FORMAT_REC {
+ char *tag;
+ char *def;
+
+ int params;
+ int paramtypes[MAX_FORMAT_PARAMS];
+};
+
+/* clang-format off */
+#define PRINT_FLAG_SET_LINE_START 0x0001
+#define PRINT_FLAG_SET_LINE_START_IRSSI 0x0002
+#define PRINT_FLAG_UNSET_LINE_START 0x0040
+
+#define PRINT_FLAG_SET_TIMESTAMP 0x0004
+#define PRINT_FLAG_UNSET_TIMESTAMP 0x0008
+
+#define PRINT_FLAG_SET_SERVERTAG 0x0010
+#define PRINT_FLAG_UNSET_SERVERTAG 0x0020
+
+#define PRINT_FLAG_FORMAT 0x0080
+/* clang-format on */
+
+typedef struct _HILIGHT_REC HILIGHT_REC;
+
+typedef struct _TEXT_DEST_REC {
+ WINDOW_REC *window;
+ SERVER_REC *server;
+ const char *server_tag; /* if server is non-NULL, must be server->tag */
+ const char *target;
+ const char *nick;
+ const char *address;
+ int level;
+
+ int hilight_priority;
+ char *hilight_color;
+ int flags; /* PRINT_FLAG */
+ GHashTable *meta;
+} TEXT_DEST_REC;
+
+typedef struct _LINE_INFO_META_REC {
+ gint64 server_time;
+ GHashTable *hash;
+} LINE_INFO_META_REC;
+
+#define window_get_theme(window) \
+ (window != NULL && (window)->theme != NULL ? \
+ (window)->theme : current_theme)
+
+int format_find_tag(const char *module, const char *tag);
+
+/* Return length of text part in string (ie. without % codes) */
+int format_get_length(const char *str);
+/* Return how many characters in `str' must be skipped before `len'
+ characters of text is skipped. Like strip_real_length(), except this
+ handles %codes. */
+int format_real_length(const char *str, int len);
+
+char *format_string_expand(const char *text, int *flags);
+char *format_string_unexpand(const char *text, int flags);
+
+char *format_get_text(const char *module, WINDOW_REC *window,
+ void *server, const char *target,
+ int formatnum, ...);
+
+/* good size for buffer is DEFAULT_FORMAT_ARGLIST_SIZE */
+void format_read_arglist(va_list va, FORMAT_REC *format,
+ char **arglist, int arglist_size,
+ char *buffer, int buffer_size);
+char *format_get_text_theme(THEME_REC *theme, const char *module,
+ TEXT_DEST_REC *dest, int formatnum, ...);
+char *format_get_text_theme_args(THEME_REC *theme, const char *module,
+ TEXT_DEST_REC *dest, int formatnum,
+ va_list va);
+char *format_get_text_theme_charargs(THEME_REC *theme, const char *module,
+ TEXT_DEST_REC *dest, int formatnum,
+ char **args);
+
+/* add `linestart' to start/end of each line in `text'. `text' may contain
+ multiple lines separated with \n. */
+char *format_add_linestart(const char *text, const char *linestart);
+char *format_add_lineend(const char *text, const char *linestart);
+
+/* return the "-!- " text at the start of the line */
+char *format_get_level_tag(THEME_REC *theme, TEXT_DEST_REC *dest);
+
+/* return timestamp + server tag */
+char *format_get_line_start(THEME_REC *theme, TEXT_DEST_REC *dest, time_t t);
+
+
+/* "private" functions for printtext */
+void format_create_dest(TEXT_DEST_REC *dest,
+ void *server, const char *target,
+ int level, WINDOW_REC *window);
+void format_create_dest_tag(TEXT_DEST_REC *dest, void *server, const char *server_tag,
+ const char *target, int level, WINDOW_REC *window);
+
+void format_newline(TEXT_DEST_REC *dest);
+
+/* manipulate the meta table of a dest */
+void format_dest_meta_stash(TEXT_DEST_REC *dest, const char *meta_key, const char *meta_value);
+const char *format_dest_meta_stash_find(TEXT_DEST_REC *dest, const char *meta_key);
+void format_dest_meta_clear_all(TEXT_DEST_REC *dest);
+
+/* Return how many characters in `str' must be skipped before `len'
+ characters of text is skipped. */
+int strip_real_length(const char *str, int len,
+ int *last_color_pos, int *last_color_len);
+
+/* strip all color (etc.) codes from `input'.
+ Returns newly allocated string. */
+char *strip_codes(const char *input);
+
+/* send a fully parsed text string for GUI to print */
+void format_send_to_gui(TEXT_DEST_REC *dest, const char *text);
+/* parse text string into GUI_PRINT_FLAG_* separated pieces and emit them to handler
+ handler is a SIGNAL_FUNC with the following arguments:
+
+ WINDOW_REC *window, void *fgcolor_int, void *bgcolor_int,
+ void *flags_int, const char *textpiece, TEXT_DEST_REC *dest
+
+ */
+void format_send_as_gui_flags(TEXT_DEST_REC *dest, const char *text, SIGNAL_FUNC handler);
+
+#define FORMAT_COLOR_NOCHANGE ('0'-1) /* don't change this, at least hilighting depends this value */
+#define FORMAT_COLOR_EXT1 ('0'-2)
+#define FORMAT_COLOR_EXT2 ('0'-3)
+#define FORMAT_COLOR_EXT3 ('0'-4)
+#define FORMAT_COLOR_EXT1_BG ('0'-5)
+#define FORMAT_COLOR_EXT2_BG ('0'-9)
+#define FORMAT_COLOR_EXT3_BG ('0'-10)
+#ifdef TERM_TRUECOLOR
+#define FORMAT_COLOR_24 ('0'-13)
+#endif
+
+#define FORMAT_STYLE_SPECIAL 0x60
+#define FORMAT_STYLE_BLINK (0x01 + FORMAT_STYLE_SPECIAL)
+#define FORMAT_STYLE_UNDERLINE (0x02 + FORMAT_STYLE_SPECIAL)
+#define FORMAT_STYLE_BOLD (0x03 + FORMAT_STYLE_SPECIAL)
+#define FORMAT_STYLE_REVERSE (0x04 + FORMAT_STYLE_SPECIAL)
+#define FORMAT_STYLE_INDENT (0x05 + FORMAT_STYLE_SPECIAL)
+#define FORMAT_STYLE_ITALIC (0x06 + FORMAT_STYLE_SPECIAL)
+#define FORMAT_STYLE_DEFAULTS (0x07 + FORMAT_STYLE_SPECIAL)
+#define FORMAT_STYLE_CLRTOEOL (0x08 + FORMAT_STYLE_SPECIAL)
+#define FORMAT_STYLE_MONOSPACE (0x09 + FORMAT_STYLE_SPECIAL)
+int format_expand_styles(GString *out, const char **format, int *flags);
+void format_ext_color(GString *out, int bg, int color);
+void format_24bit_color(GString *out, int bg, unsigned int color);
+void format_gui_flags(GString *out, int *last_fg, int *last_bg, int *last_flags, int fg, int bg,
+ int flags);
+
+void formats_init(void);
+void formats_deinit(void);
+
+#endif
diff --git a/src/fe-common/core/hilight-text.c b/src/fe-common/core/hilight-text.c
new file mode 100644
index 0000000..66e2dfd
--- /dev/null
+++ b/src/fe-common/core/hilight-text.c
@@ -0,0 +1,821 @@
+/*
+ hilight-text.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/core/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/lib-config/iconfig.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/iregex.h>
+
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/channels.h>
+#include <irssi/src/core/nicklist.h>
+
+#include <irssi/src/fe-common/core/hilight-text.h>
+#include <irssi/src/core/nickmatch-cache.h>
+#include <irssi/src/fe-common/core/printtext.h>
+#include <irssi/src/fe-common/core/formats.h>
+
+static NICKMATCH_REC *nickmatch;
+static int never_hilight_level, default_hilight_level;
+GSList *hilights;
+
+static void reset_level_cache(void)
+{
+ GSList *tmp;
+
+ never_hilight_level = MSGLEVEL_ALL & ~default_hilight_level;
+ for (tmp = hilights; tmp != NULL; tmp = tmp->next) {
+ HILIGHT_REC *rec = tmp->data;
+
+ if (never_hilight_level & rec->level)
+ never_hilight_level &= ~rec->level;
+ }
+}
+
+static void reset_cache(void)
+{
+ reset_level_cache();
+ nickmatch_rebuild(nickmatch);
+}
+
+static void hilight_add_config(HILIGHT_REC *rec)
+{
+ CONFIG_NODE *node;
+
+ g_return_if_fail(rec != NULL);
+
+ node = iconfig_node_traverse("(hilights", TRUE);
+ node = iconfig_node_section(node, NULL, NODE_TYPE_BLOCK);
+
+ iconfig_node_set_str(node, "text", rec->text);
+ if (rec->level > 0) iconfig_node_set_int(node, "level", rec->level);
+ if (rec->color) iconfig_node_set_str(node, "color", rec->color);
+ if (rec->act_color) iconfig_node_set_str(node, "act_color", rec->act_color);
+ if (rec->priority > 0) iconfig_node_set_int(node, "priority", rec->priority);
+ iconfig_node_set_bool(node, "nick", rec->nick);
+ iconfig_node_set_bool(node, "word", rec->word);
+ if (rec->nickmask) iconfig_node_set_bool(node, "mask", TRUE);
+ if (rec->fullword) iconfig_node_set_bool(node, "fullword", TRUE);
+ if (rec->regexp) iconfig_node_set_bool(node, "regexp", TRUE);
+ if (rec->case_sensitive) iconfig_node_set_bool(node, "matchcase", TRUE);
+ if (rec->servertag) iconfig_node_set_str(node, "servertag", rec->servertag);
+
+ if (rec->channels != NULL && *rec->channels != NULL) {
+ node = iconfig_node_section(node, "channels", NODE_TYPE_LIST);
+ iconfig_node_add_list(node, rec->channels);
+ }
+}
+
+static void hilight_remove_config(HILIGHT_REC *rec)
+{
+ CONFIG_NODE *node;
+
+ g_return_if_fail(rec != NULL);
+
+ node = iconfig_node_traverse("hilights", FALSE);
+ if (node != NULL) iconfig_node_list_remove(node, g_slist_index(hilights, rec));
+}
+
+static void hilight_destroy(HILIGHT_REC *rec)
+{
+ g_return_if_fail(rec != NULL);
+
+ if (rec->preg != NULL) i_regex_unref(rec->preg);
+ if (rec->channels != NULL) g_strfreev(rec->channels);
+ g_free_not_null(rec->color);
+ g_free_not_null(rec->act_color);
+ g_free_not_null(rec->servertag);
+ g_free(rec->text);
+ g_free(rec);
+}
+
+static void hilights_destroy_all(void)
+{
+ g_slist_foreach(hilights, (GFunc) hilight_destroy, NULL);
+ g_slist_free(hilights);
+ hilights = NULL;
+}
+
+static void hilight_init_rec(HILIGHT_REC *rec)
+{
+ if (rec->preg != NULL)
+ i_regex_unref(rec->preg);
+
+ rec->preg = i_regex_new(rec->text, G_REGEX_OPTIMIZE | G_REGEX_CASELESS, 0, NULL);
+}
+
+void hilight_create(HILIGHT_REC *rec)
+{
+ if (g_slist_find(hilights, rec) != NULL) {
+ hilight_remove_config(rec);
+ hilights = g_slist_remove(hilights, rec);
+ }
+
+ hilights = g_slist_append(hilights, rec);
+ hilight_add_config(rec);
+
+ hilight_init_rec(rec);
+
+ signal_emit("hilight created", 1, rec);
+}
+
+void hilight_remove(HILIGHT_REC *rec)
+{
+ g_return_if_fail(rec != NULL);
+
+ hilight_remove_config(rec);
+ hilights = g_slist_remove(hilights, rec);
+
+ signal_emit("hilight destroyed", 1, rec);
+ hilight_destroy(rec);
+}
+
+static HILIGHT_REC *hilight_find(const char *text, char **channels)
+{
+ GSList *tmp;
+ char **chan;
+
+ g_return_val_if_fail(text != NULL, NULL);
+
+ for (tmp = hilights; tmp != NULL; tmp = tmp->next) {
+ HILIGHT_REC *rec = tmp->data;
+
+ if (g_ascii_strcasecmp(rec->text, text) != 0)
+ continue;
+
+ if ((channels == NULL && rec->channels == NULL))
+ return rec; /* no channels - ok */
+
+ if (channels != NULL && g_strcmp0(*channels, "*") == 0)
+ return rec; /* ignore channels */
+
+ if (channels == NULL || rec->channels == NULL)
+ continue; /* other doesn't have channels */
+
+ if (g_strv_length(channels) != g_strv_length(rec->channels))
+ continue; /* different amount of channels */
+
+ /* check that channels match */
+ for (chan = channels; *chan != NULL; chan++) {
+ if (strarray_find(rec->channels, *chan) == -1)
+ break;
+ }
+
+ if (*chan == NULL)
+ return rec; /* channels ok */
+ }
+
+ return NULL;
+}
+
+static gboolean hilight_match_text(HILIGHT_REC *rec, const char *text,
+ int *match_beg, int *match_end)
+{
+ gboolean ret = FALSE;
+
+ if (rec->regexp) {
+ if (rec->preg != NULL) {
+ MatchInfo *match;
+ i_regex_match(rec->preg, text, 0, &match);
+
+ if (i_match_info_matches(match))
+ ret = i_match_info_fetch_pos(match, 0, match_beg, match_end);
+
+ i_match_info_free(match);
+ }
+ } else {
+ char *match;
+
+ if (rec->case_sensitive) {
+ match = rec->fullword ?
+ strstr_full(text, rec->text) :
+ strstr(text, rec->text);
+ } else {
+ match = rec->fullword ?
+ stristr_full(text, rec->text) :
+ stristr(text, rec->text);
+ }
+ if (match != NULL) {
+ if (match_beg != NULL && match_end != NULL) {
+ *match_beg = (int) (match-text);
+ *match_end = *match_beg + strlen(rec->text);
+ }
+ ret = TRUE;
+ }
+ }
+
+ return ret;
+}
+
+#define hilight_match_level(rec, level) \
+ (level & (((rec)->level != 0 ? rec->level : default_hilight_level)))
+
+#define hilight_match_channel(rec, channel) \
+ ((rec)->channels == NULL || ((channel) != NULL && \
+ strarray_find((rec)->channels, (channel)) != -1))
+
+HILIGHT_REC *hilight_match(SERVER_REC *server, const char *channel,
+ const char *nick, const char *address,
+ int level, const char *str,
+ int *match_beg, int *match_end)
+{
+ GSList *tmp;
+ CHANNEL_REC *chanrec;
+ NICK_REC *nickrec;
+ HILIGHT_REC *tmprec;
+ int priority = -1;
+
+ g_return_val_if_fail(str != NULL, NULL);
+ tmprec = NULL;
+
+ if ((never_hilight_level & level) == level)
+ return NULL;
+
+ if (nick != NULL) {
+ /* check nick mask hilights */
+ chanrec = channel_find(server, channel);
+ nickrec = chanrec == NULL ? NULL :
+ nicklist_find(chanrec, nick);
+ if (nickrec != NULL) {
+ HILIGHT_REC *rec;
+
+ if (nickrec->host == NULL)
+ nicklist_set_host(chanrec, nickrec, address);
+
+ rec = nickmatch_find(nickmatch, nickrec);
+ if (rec != NULL && hilight_match_level(rec, level))
+ return rec;
+ }
+ }
+
+ for (tmp = hilights; tmp != NULL; tmp = tmp->next) {
+ HILIGHT_REC *rec = tmp->data;
+
+ if (rec->priority > priority && !rec->nickmask && hilight_match_level(rec, level) &&
+ hilight_match_channel(rec, channel) &&
+ (rec->servertag == NULL ||
+ (server != NULL && g_ascii_strcasecmp(rec->servertag, server->tag) == 0)) &&
+ hilight_match_text(rec, str, match_beg, match_end)) {
+ tmprec = rec;
+ priority = rec->priority;
+ }
+ }
+
+ return tmprec;
+}
+
+static char *hilight_get_act_color(HILIGHT_REC *rec)
+{
+ g_return_val_if_fail(rec != NULL, NULL);
+
+ return g_strdup(rec->act_color != NULL ? rec->act_color :
+ rec->color != NULL ? rec->color :
+ settings_get_str("hilight_act_color"));
+}
+
+char *hilight_get_color(HILIGHT_REC *rec)
+{
+ const char *color;
+
+ g_return_val_if_fail(rec != NULL, NULL);
+
+ color = rec->color != NULL ? rec->color :
+ settings_get_str("hilight_color");
+
+ return format_string_expand(color, NULL);
+}
+
+void hilight_update_text_dest(TEXT_DEST_REC *dest, HILIGHT_REC *rec)
+{
+ dest->level |= MSGLEVEL_HILIGHT;
+
+ if (rec->priority > 0)
+ dest->hilight_priority = rec->priority;
+
+ g_free_and_null(dest->hilight_color);
+ if (rec->act_color != NULL && g_strcmp0(rec->act_color, "%n") == 0)
+ dest->level |= MSGLEVEL_NO_ACT;
+ else
+ dest->hilight_color = hilight_get_act_color(rec);
+}
+
+static void hilight_print(int index, HILIGHT_REC *rec);
+
+static void sig_render_line_text(TEXT_DEST_REC *dest, GString *str, LINE_INFO_META_REC *meta)
+{
+ char *color, *tmp, *tmp2;
+
+ if (meta == NULL || meta->hash == NULL)
+ return;
+
+ color = g_hash_table_lookup(meta->hash, "hilight-color");
+
+ if ((tmp = g_hash_table_lookup(meta->hash, "hilight-line")) != NULL) {
+ /* hilight whole line */
+
+ tmp = strip_codes(str->str);
+
+ color = format_string_expand(
+ color != NULL ? color : settings_get_str("hilight_color"), NULL);
+
+ g_string_truncate(str, 0);
+ g_string_append(str, color);
+ g_string_append(str, tmp);
+
+ g_free(color);
+ g_free(tmp);
+ } else if ((tmp = g_hash_table_lookup(meta->hash, "hilight-start")) != NULL &&
+ (tmp2 = g_hash_table_lookup(meta->hash, "hilight-end")) != NULL) {
+ /* hilight part of the line */
+ int hilight_start, hilight_end;
+ int pos, color_pos, color_len;
+ char *middle;
+ GString *str2;
+
+ hilight_start = atoi(tmp);
+ hilight_end = atoi(tmp2);
+
+ /* start of the line */
+ pos = strip_real_length(str->str, hilight_start, NULL, NULL);
+
+ str2 = g_string_new_len(str->str, pos);
+
+ /* color */
+ color = format_string_expand(
+ color != NULL ? color : settings_get_str("hilight_color"), NULL);
+ g_string_append(str2, color);
+ g_free(color);
+
+ /* middle of the line, stripped */
+ middle = strip_codes(str->str + pos);
+ g_string_append_len(str2, middle, hilight_end - hilight_start);
+ g_free(middle);
+
+ /* end of the line */
+ pos = strip_real_length(str->str, hilight_end, &color_pos, &color_len);
+ if (color_pos > 0) {
+ g_string_append_len(str2, str->str + color_pos, color_len);
+ } else {
+ /* no colors in line, change back to default */
+ g_string_append_c(str2, 4);
+ g_string_append_c(str2, FORMAT_STYLE_DEFAULTS);
+ }
+ g_string_append(str2, str->str + pos);
+
+ g_string_assign(str, g_string_free(str2, FALSE));
+ }
+}
+
+static void sig_print_text(TEXT_DEST_REC *dest, const char *text,
+ const char *stripped)
+{
+ HILIGHT_REC *hilight;
+ char *color, *newstr;
+ int old_level, hilight_start, hilight_end, hilight_len;
+ int nick_match;
+
+ if (dest->level & MSGLEVEL_NOHILIGHT)
+ return;
+
+ hilight_start = hilight_end = 0;
+ hilight = hilight_match(dest->server, dest->target, dest->nick,
+ dest->address, dest->level, stripped,
+ &hilight_start, &hilight_end);
+
+ if (hilight == NULL)
+ return;
+
+ nick_match = hilight->nick && (dest->level & (MSGLEVEL_PUBLIC|MSGLEVEL_ACTIONS)) == MSGLEVEL_PUBLIC;
+
+ old_level = dest->level;
+ if (!nick_match || (dest->level & MSGLEVEL_HILIGHT)) {
+ /* Remove NO_ACT, this means explicitly defined hilights will bypass
+ * /IGNORE ... NO_ACT.
+ * (It's still possible to use /hilight -actcolor %n to hide
+ * hilight/beep).
+ */
+ dest->level &= ~MSGLEVEL_NO_ACT;
+ /* update the level / hilight info */
+ hilight_update_text_dest(dest, hilight);
+ }
+
+ if (nick_match)
+ return; /* fe-messages.c should have taken care of this */
+
+ if (old_level & MSGLEVEL_HILIGHT) {
+ /* nick is highlighted, just set priority */
+ return;
+ }
+
+ color = hilight_get_color(hilight);
+ hilight_len = hilight_end-hilight_start;
+
+ if (!hilight->word) {
+ /* hilight whole line */
+ char *tmp = strip_codes(text);
+ newstr = g_strconcat(color, tmp, NULL);
+ g_free(tmp);
+
+ format_dest_meta_stash(dest, "hilight-line", "\001");
+ } else {
+ /* hilight part of the line */
+ GString *str;
+ char *middle, *tmp;
+ int pos, color_pos, color_len;
+
+ /* start of the line */
+ pos = strip_real_length(text, hilight_start, NULL, NULL);
+ str = g_string_new_len(text, pos);
+
+ /* color */
+ g_string_append(str, color);
+
+ /* middle of the line, stripped */
+ middle = strip_codes(text + pos);
+ g_string_append_len(str, middle, hilight_len);
+ g_free(middle);
+
+ /* end of the line */
+ pos = strip_real_length(text, hilight_end,
+ &color_pos, &color_len);
+ if (color_pos > 0) {
+ g_string_append_len(str, text + color_pos, color_len);
+ } else {
+ /* no colors in line, change back to default */
+ g_string_append_c(str, 4);
+ g_string_append_c(str, FORMAT_STYLE_DEFAULTS);
+ }
+ g_string_append(str, text + pos);
+
+ newstr = str->str;
+ g_string_free(str, FALSE);
+
+ format_dest_meta_stash(dest, "hilight-start",
+ tmp = g_strdup_printf("%d", hilight_start));
+ g_free(tmp);
+ format_dest_meta_stash(dest, "hilight-end",
+ tmp = g_strdup_printf("%d", hilight_end));
+ g_free(tmp);
+ }
+ if (hilight->color != NULL)
+ format_dest_meta_stash(dest, "hilight-color", hilight->color);
+
+ signal_emit("print text", 3, dest, newstr, stripped);
+
+ g_free(color);
+ g_free(newstr);
+
+ signal_stop();
+}
+
+HILIGHT_REC *hilight_match_nick(SERVER_REC *server, const char *channel,
+ const char *nick, const char *address,
+ int level, const char *msg)
+{
+ HILIGHT_REC *rec;
+
+ rec = hilight_match(server, channel, nick, address,
+ level, msg, NULL, NULL);
+ return (rec == NULL || !rec->nick) ? NULL : rec;
+}
+
+static void read_hilight_config(void)
+{
+ CONFIG_NODE *node;
+ HILIGHT_REC *rec;
+ GSList *tmp;
+ char *text, *color, *servertag;
+
+ hilights_destroy_all();
+
+ node = iconfig_node_traverse("hilights", FALSE);
+ if (node == NULL) {
+ reset_cache();
+ return;
+ }
+
+ tmp = config_node_first(node->value);
+ for (; tmp != NULL; tmp = config_node_next(tmp)) {
+ node = tmp->data;
+
+ if (node->type != NODE_TYPE_BLOCK)
+ continue;
+
+ text = config_node_get_str(node, "text", NULL);
+ if (text == NULL || *text == '\0')
+ continue;
+
+ rec = g_new0(HILIGHT_REC, 1);
+ hilights = g_slist_append(hilights, rec);
+
+ rec->text = g_strdup(text);
+
+ color = config_node_get_str(node, "color", NULL);
+ rec->color = color == NULL || *color == '\0' ? NULL :
+ g_strdup(color);
+
+ color = config_node_get_str(node, "act_color", NULL);
+ rec->act_color = color == NULL || *color == '\0' ? NULL :
+ g_strdup(color);
+
+ rec->level = config_node_get_int(node, "level", 0);
+ rec->priority = config_node_get_int(node, "priority", 0);
+ rec->nick = config_node_get_bool(node, "nick", TRUE);
+ rec->word = config_node_get_bool(node, "word", TRUE);
+ rec->case_sensitive = config_node_get_bool(node, "matchcase", FALSE);
+
+ rec->nickmask = config_node_get_bool(node, "mask", FALSE);
+ rec->fullword = config_node_get_bool(node, "fullword", FALSE);
+ rec->regexp = config_node_get_bool(node, "regexp", FALSE);
+ servertag = config_node_get_str(node, "servertag", NULL);
+ rec->servertag = servertag == NULL || *servertag == '\0' ? NULL :
+ g_strdup(servertag);
+ hilight_init_rec(rec);
+
+ node = iconfig_node_section(node, "channels", -1);
+ if (node != NULL) rec->channels = config_node_get_list(node);
+ }
+
+ reset_cache();
+}
+
+static void hilight_print(int index, HILIGHT_REC *rec)
+{
+ char *chans, *levelstr;
+ GString *options;
+
+ options = g_string_new(NULL);
+
+ if (rec->nick && rec->word) { /* default case, no option */ }
+ else if (rec->nick)
+ g_string_append(options, "-nick ");
+ else if (rec->word)
+ g_string_append(options, "-word ");
+ else
+ g_string_append(options, "-line ");
+
+ if (rec->nickmask) g_string_append(options, "-mask ");
+ if (rec->fullword) g_string_append(options, "-full ");
+ if (rec->case_sensitive) g_string_append(options, "-matchcase ");
+ if (rec->regexp) {
+ g_string_append(options, "-regexp ");
+ if (rec->preg == NULL)
+ g_string_append(options, "[INVALID!] ");
+ }
+
+ if (rec->priority != 0)
+ g_string_append_printf(options, "-priority %d ", rec->priority);
+ if (rec->servertag != NULL)
+ g_string_append_printf(options, "-network %s ", rec->servertag);
+ if (rec->color != NULL)
+ g_string_append_printf(options, "-color %s ", rec->color);
+ if (rec->act_color != NULL)
+ g_string_append_printf(options, "-actcolor %s ", rec->act_color);
+
+ chans = rec->channels == NULL ? NULL :
+ g_strjoinv(",", rec->channels);
+ levelstr = rec->level == 0 ? NULL :
+ bits2level(rec->level);
+ if (levelstr != NULL)
+ levelstr = g_strconcat(levelstr, " ", NULL);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ TXT_HILIGHT_LINE, index, rec->text,
+ chans != NULL ? chans : "",
+ levelstr != NULL ? levelstr : "",
+ options->str);
+ g_free_not_null(chans);
+ g_free_not_null(levelstr);
+ g_string_free(options, TRUE);
+}
+
+static void cmd_hilight_show(void)
+{
+ GSList *tmp;
+ int index;
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_HILIGHT_HEADER);
+ index = 1;
+ for (tmp = hilights; tmp != NULL; tmp = tmp->next, index++) {
+ HILIGHT_REC *rec = tmp->data;
+
+ hilight_print(index, rec);
+ }
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_HILIGHT_FOOTER);
+}
+
+/* SYNTAX: HILIGHT [-nick | -word | -line] [-mask | -full | -matchcase | -regexp]
+ [-color <color>] [-actcolor <color>] [-level <level>]
+ [-network <network>] [-channels <channels>] <text> */
+static void cmd_hilight(const char *data)
+{
+ GHashTable *optlist;
+ HILIGHT_REC *rec;
+ char *colorarg, *actcolorarg, *levelarg, *priorityarg, *chanarg, *text, *servertag;
+ char **channels;
+ void *free_arg;
+
+ g_return_if_fail(data != NULL);
+
+ if (*data == '\0') {
+ cmd_hilight_show();
+ return;
+ }
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |
+ PARAM_FLAG_GETREST, "hilight", &optlist, &text))
+ return;
+
+ chanarg = g_hash_table_lookup(optlist, "channels");
+ levelarg = g_hash_table_lookup(optlist, "level");
+ priorityarg = g_hash_table_lookup(optlist, "priority");
+ colorarg = g_hash_table_lookup(optlist, "color");
+ actcolorarg = g_hash_table_lookup(optlist, "actcolor");
+ servertag = g_hash_table_lookup(optlist, "network");
+
+ if (*text == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ channels = (chanarg == NULL || *chanarg == '\0') ? NULL :
+ g_strsplit(chanarg, ",", -1);
+
+ rec = hilight_find(text, channels);
+ if (rec == NULL) {
+ rec = g_new0(HILIGHT_REC, 1);
+
+ /* default to nick/word hilighting */
+ rec->nick = TRUE;
+ rec->word = TRUE;
+
+ rec->text = g_strdup(text);
+ rec->channels = channels;
+ } else {
+ g_strfreev(channels);
+ }
+
+ rec->level = (levelarg == NULL || *levelarg == '\0') ? 0 :
+ level2bits(replace_chars(levelarg, ',', ' '), NULL);
+ rec->priority = priorityarg == NULL ? 0 : atoi(priorityarg);
+
+ if (g_hash_table_lookup(optlist, "line") != NULL) {
+ rec->word = FALSE;
+ rec->nick = FALSE;
+ }
+
+ if (g_hash_table_lookup(optlist, "word") != NULL) {
+ rec->word = TRUE;
+ rec->nick = FALSE;
+ }
+
+ if (g_hash_table_lookup(optlist, "nick") != NULL)
+ rec->nick = TRUE;
+
+ rec->nickmask = g_hash_table_lookup(optlist, "mask") != NULL;
+ rec->fullword = g_hash_table_lookup(optlist, "full") != NULL;
+ rec->regexp = g_hash_table_lookup(optlist, "regexp") != NULL;
+ rec->case_sensitive = g_hash_table_lookup(optlist, "matchcase") != NULL;
+
+ if (colorarg != NULL) {
+ g_free_and_null(rec->color);
+ if (*colorarg != '\0')
+ rec->color = g_strdup(colorarg);
+ }
+ if (actcolorarg != NULL) {
+ g_free_and_null(rec->act_color);
+ if (*actcolorarg != '\0')
+ rec->act_color = g_strdup(actcolorarg);
+ }
+ if (servertag != NULL) {
+ g_free_and_null(rec->servertag);
+ if (*servertag != '\0')
+ rec->servertag = g_strdup(servertag);
+ }
+
+ hilight_create(rec);
+
+ hilight_print(g_slist_index(hilights, rec)+1, rec);
+ cmd_params_free(free_arg);
+
+ reset_cache();
+}
+
+/* SYNTAX: DEHILIGHT <id>|<mask> */
+static void cmd_dehilight(const char *data)
+{
+ HILIGHT_REC *rec;
+ GSList *tmp;
+
+ if (is_numeric(data, ' ')) {
+ /* with index number */
+ tmp = g_slist_nth(hilights, atoi(data)-1);
+ rec = tmp == NULL ? NULL : tmp->data;
+ } else {
+ /* with mask */
+ char *chans[2] = { "*", NULL };
+ rec = hilight_find(data, chans);
+ }
+
+ if (rec == NULL)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_HILIGHT_NOT_FOUND, data);
+ else {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_HILIGHT_REMOVED, rec->text);
+ hilight_remove(rec);
+ reset_cache();
+ }
+}
+
+static void hilight_nick_cache(GHashTable *list, CHANNEL_REC *channel,
+ NICK_REC *nick)
+{
+ GSList *tmp;
+ HILIGHT_REC *match;
+ char *nickmask;
+ int len, best_match;
+ int priority = -1;
+
+ if (nick->host == NULL)
+ return; /* don't check until host is known */
+
+ nickmask = g_strconcat(nick->nick, "!", nick->host, NULL);
+
+ best_match = 0; match = NULL;
+ for (tmp = hilights; tmp != NULL; tmp = tmp->next) {
+ HILIGHT_REC *rec = tmp->data;
+
+ if (rec->priority > priority && rec->nickmask &&
+ hilight_match_channel(rec, channel->name) &&
+ match_wildcards(rec->text, nickmask)) {
+ len = strlen(rec->text);
+ if (best_match < len) {
+ priority = rec->priority;
+ best_match = len;
+ match = rec;
+ }
+ }
+ }
+ g_free_not_null(nickmask);
+
+ if (match != NULL)
+ g_hash_table_insert(list, nick, match);
+}
+
+static void read_settings(void)
+{
+ default_hilight_level = settings_get_level("hilight_level");
+ reset_level_cache();
+}
+
+void hilight_text_init(void)
+{
+ settings_add_str("lookandfeel", "hilight_color", "%Y");
+ settings_add_str("lookandfeel", "hilight_act_color", "%M");
+ settings_add_level("lookandfeel", "hilight_level", "PUBLIC DCCMSGS");
+
+ read_settings();
+
+ nickmatch = nickmatch_init(hilight_nick_cache, NULL);
+ read_hilight_config();
+
+ signal_add_first("print text", (SIGNAL_FUNC) sig_print_text);
+ signal_add("gui render line text", (SIGNAL_FUNC) sig_render_line_text);
+ signal_add("setup reread", (SIGNAL_FUNC) read_hilight_config);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+
+ command_bind("hilight", NULL, (SIGNAL_FUNC) cmd_hilight);
+ command_bind("dehilight", NULL, (SIGNAL_FUNC) cmd_dehilight);
+ command_set_options("hilight", "-color -actcolor -level -priority -network -channels nick word line mask full regexp matchcase");
+}
+
+void hilight_text_deinit(void)
+{
+ hilights_destroy_all();
+ nickmatch_deinit(nickmatch);
+
+ signal_remove("print text", (SIGNAL_FUNC) sig_print_text);
+ signal_remove("gui render line text", (SIGNAL_FUNC) sig_render_line_text);
+ signal_remove("setup reread", (SIGNAL_FUNC) read_hilight_config);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+
+ command_unbind("hilight", (SIGNAL_FUNC) cmd_hilight);
+ command_unbind("dehilight", (SIGNAL_FUNC) cmd_dehilight);
+}
diff --git a/src/fe-common/core/hilight-text.h b/src/fe-common/core/hilight-text.h
new file mode 100644
index 0000000..0dfe78f
--- /dev/null
+++ b/src/fe-common/core/hilight-text.h
@@ -0,0 +1,48 @@
+#ifndef IRSSI_FE_COMMON_CORE_HILIGHT_TEXT_H
+#define IRSSI_FE_COMMON_CORE_HILIGHT_TEXT_H
+
+#include <irssi/src/core/iregex.h>
+#include <irssi/src/fe-common/core/formats.h>
+
+struct _HILIGHT_REC {
+ char *text;
+
+ char **channels; /* if non-NULL, check the text only from these channels */
+ int level; /* match only messages with this level, 0=default */
+ char *color; /* if starts with number, \003 is automatically
+ inserted before it. */
+ char *act_color; /* color for window activity */
+ int priority;
+
+ unsigned int nick:1; /* hilight only nick if possible */
+ unsigned int word:1; /* hilight only word, not full line */
+
+ unsigned int nickmask:1; /* `text' is a nick mask */
+ unsigned int fullword:1; /* match `text' only for full words */
+ unsigned int regexp:1; /* `text' is a regular expression */
+ unsigned int case_sensitive:1;/* `text' must match case */
+ Regex *preg;
+ char *servertag;
+};
+
+extern GSList *hilights;
+
+HILIGHT_REC *hilight_match(SERVER_REC *server, const char *channel,
+ const char *nick, const char *address,
+ int level, const char *str,
+ int *match_beg, int *match_end);
+
+HILIGHT_REC *hilight_match_nick(SERVER_REC *server, const char *channel,
+ const char *nick, const char *address,
+ int level, const char *msg);
+
+char *hilight_get_color(HILIGHT_REC *rec);
+void hilight_update_text_dest(TEXT_DEST_REC *dest, HILIGHT_REC *rec);
+
+void hilight_create(HILIGHT_REC *rec);
+void hilight_remove(HILIGHT_REC *rec);
+
+void hilight_text_init(void);
+void hilight_text_deinit(void);
+
+#endif
diff --git a/src/fe-common/core/keyboard.c b/src/fe-common/core/keyboard.c
new file mode 100644
index 0000000..e0e061d
--- /dev/null
+++ b/src/fe-common/core/keyboard.c
@@ -0,0 +1,1007 @@
+/*
+ keyboard.c : irssi
+
+ Copyright (C) 1999-2001 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/core/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/lib-config/iconfig.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/fe-common/core/keyboard.h>
+#include <irssi/src/fe-common/core/fe-windows.h>
+#include <irssi/src/fe-common/core/printtext.h>
+
+#define MAX_EXPAND_RECURSION 100
+
+GSList *keyinfos;
+static GHashTable *keys, *default_keys;
+static int key_timeout;
+
+/* A cache of some sort for key presses that generate a single char only.
+ If the key isn't used, used_keys[key] is zero. */
+static char used_keys[256];
+
+/* Contains a list of all possible executable key bindings (not "key" keys).
+ Format is _always_ in key1-key2-key3 format and fully extracted, like
+ ^[-[-A, not meta-A */
+static GTree *key_states;
+static int key_config_frozen;
+
+struct _KEYBOARD_REC {
+ char *key_state; /* the ongoing key combo */
+ guint timer_tag; /* used to check when a pending combo has expired */
+ void *gui_data; /* GUI specific data sent in "key pressed" signal */
+};
+
+/* Creates a new "keyboard" - this is used only for keeping track of
+ key combo states and sending the gui_data parameter in "key pressed"
+ signal */
+KEYBOARD_REC *keyboard_create(void *data)
+{
+ KEYBOARD_REC *rec;
+
+ rec = g_new0(KEYBOARD_REC, 1);
+ rec->gui_data = data;
+ rec->timer_tag = 0;
+
+ signal_emit("keyboard created", 1, rec);
+ return rec;
+}
+
+/* Destroys a keyboard */
+void keyboard_destroy(KEYBOARD_REC *keyboard)
+{
+ if (keyboard->timer_tag > 0) {
+ g_source_remove(keyboard->timer_tag);
+ keyboard->timer_tag = 0;
+ }
+
+ signal_emit("keyboard destroyed", 1, keyboard);
+
+ g_free_not_null(keyboard->key_state);
+ g_free(keyboard);
+}
+
+static void key_destroy(KEY_REC *rec, GHashTable *hash)
+{
+ g_hash_table_remove(hash, rec->key);
+
+ g_free_not_null(rec->data);
+ g_free(rec->key);
+ g_free(rec);
+}
+
+static void key_default_add(const char *id, const char *key, const char *data)
+{
+ KEYINFO_REC *info;
+ KEY_REC *rec;
+
+ info = key_info_find(id);
+ if (info == NULL)
+ return;
+
+ rec = g_hash_table_lookup(default_keys, key);
+ if (rec != NULL) {
+ /* key already exists, replace */
+ rec->info->default_keys =
+ g_slist_remove(rec->info->default_keys, rec);
+ key_destroy(rec, default_keys);
+ }
+
+ rec = g_new0(KEY_REC, 1);
+ rec->key = g_strdup(key);
+ rec->info = info;
+ rec->data = g_strdup(data);
+ info->default_keys = g_slist_append(info->default_keys, rec);
+ g_hash_table_insert(default_keys, rec->key, rec);
+}
+
+static CONFIG_NODE *key_config_find(const char *key)
+{
+ CONFIG_NODE *node;
+ GSList *tmp;
+
+ /* remove old keyboard settings */
+ node = iconfig_node_traverse("(keyboard", TRUE);
+
+ tmp = config_node_first(node->value);
+ for (; tmp != NULL; tmp = config_node_next(tmp)) {
+ node = tmp->data;
+
+ if (g_strcmp0(config_node_get_str(node, "key", ""), key) == 0)
+ return node;
+ }
+
+ return NULL;
+}
+
+static void keyconfig_save(const char *id, const char *key, const char *data)
+{
+ CONFIG_NODE *node;
+
+ g_return_if_fail(id != NULL);
+ g_return_if_fail(key != NULL);
+
+ node = key_config_find(key);
+ if (node == NULL) {
+ node = iconfig_node_traverse("(keyboard", TRUE);
+ node = iconfig_node_section(node, NULL, NODE_TYPE_BLOCK);
+ }
+
+ iconfig_node_set_str(node, "key", key);
+ iconfig_node_set_str(node, "id", id);
+ iconfig_node_set_str(node, "data", data);
+}
+
+static void keyconfig_clear(const char *key)
+{
+ CONFIG_NODE *node;
+ KEY_REC *rec;
+
+ g_return_if_fail(key != NULL);
+
+ /* remove old keyboard settings */
+ node = key_config_find(key);
+ if (node != NULL) {
+ iconfig_node_remove(iconfig_node_traverse("(keyboard", FALSE),
+ node);
+ }
+ if ((rec = g_hash_table_lookup(default_keys, key)) != NULL) {
+ node = iconfig_node_traverse("(keyboard", TRUE);
+ node = iconfig_node_section(node, NULL, NODE_TYPE_BLOCK);
+ iconfig_node_set_str(node, "key", key);
+ }
+}
+
+KEYINFO_REC *key_info_find(const char *id)
+{
+ GSList *tmp;
+
+ for (tmp = keyinfos; tmp != NULL; tmp = tmp->next) {
+ KEYINFO_REC *rec = tmp->data;
+
+ if (g_ascii_strcasecmp(rec->id, id) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static int expand_key(const char *key, GSList **out, int *limit);
+
+#define expand_out_char(out, c) \
+ { \
+ GSList *tmp; \
+ for (tmp = out; tmp != NULL; tmp = tmp->next) \
+ g_string_append_c(tmp->data, c); \
+ }
+
+#define expand_out_free(out) \
+ { \
+ GSList *tmp; \
+ for (tmp = out; tmp != NULL; tmp = tmp->next) \
+ g_string_free(tmp->data, TRUE); \
+ g_slist_free(out); out = NULL; \
+ }
+
+static int expand_combo(const char *start, const char *end, GSList **out, int *limit)
+{
+ KEY_REC *rec;
+ KEYINFO_REC *info;
+ GSList *tmp, *tmp2, *list, *copy, *newout;
+ char *str, *p;
+
+ if ((*limit)-- < 0) {
+ return FALSE;
+ }
+
+ if (start == end) {
+ /* single key */
+ expand_out_char(*out, *start);
+ return TRUE;
+ }
+
+ info = key_info_find("key");
+ if (info == NULL)
+ return FALSE;
+
+ /* get list of all key combos that generate the named combo.. */
+ list = NULL;
+ str = g_strndup(start, (int) (end-start)+1);
+ for (tmp = info->keys; tmp != NULL; tmp = tmp->next) {
+ KEY_REC *rec = tmp->data;
+
+ if (g_strcmp0(rec->data, str) == 0)
+ list = g_slist_append(list, rec);
+ }
+
+ if (list == NULL) {
+ /* unknown keycombo - add it as-is, maybe the GUI will
+ feed it to us as such */
+ for (p = str; *p != '\0'; p++)
+ expand_out_char(*out, *p);
+ g_free(str);
+ return TRUE;
+ }
+ g_free(str);
+
+ if (list->next == NULL) {
+ /* only one way to generate the combo, good */
+ rec = list->data;
+ g_slist_free(list);
+ return expand_key(rec->key, out, limit);
+ }
+
+ /* multiple ways to generate the combo -
+ we'll need to include all of them in output */
+ newout = NULL;
+ for (tmp = list->next; tmp != NULL; tmp = tmp->next) {
+ KEY_REC *rec = tmp->data;
+
+ copy = NULL;
+ for (tmp2 = *out; tmp2 != NULL; tmp2 = tmp2->next) {
+ GString *str = tmp2->data;
+ copy = g_slist_append(copy, g_string_new(str->str));
+ }
+
+ if (!expand_key(rec->key, &copy, limit)) {
+ if (*limit < 0) {
+ return FALSE;
+ }
+
+ /* illegal key combo, remove from list */
+ expand_out_free(copy);
+ } else {
+ newout = g_slist_concat(newout, copy);
+ }
+ }
+
+ rec = list->data;
+ g_slist_free(list);
+ if (!expand_key(rec->key, out, limit)) {
+ if (*limit < 0) {
+ return FALSE;
+ }
+
+ /* illegal key combo, remove from list */
+ expand_out_free(*out);
+ }
+
+ *out = g_slist_concat(*out, newout);
+ return *out != NULL;
+}
+
+/* Expand key code - returns TRUE if successful. */
+static int expand_key(const char *key, GSList **out, int *limit)
+{
+ GSList *tmp;
+ const char *start;
+ int last_hyphen;
+
+ if ((*limit)-- < 0) {
+ return FALSE;
+ }
+
+ /* meta-^W^Gf -> ^[-^W-^G-f */
+ start = NULL; last_hyphen = TRUE;
+ for (; *key != '\0'; key++) {
+ if (start != NULL) {
+ if (i_isalnum(*key) || *key == '_') {
+ /* key combo continues */
+ continue;
+ }
+
+ if (!expand_combo(start, key-1, out, limit))
+ return FALSE;
+ expand_out_char(*out, '-');
+ start = NULL;
+ }
+
+ if (*key == '-') {
+ if (last_hyphen) {
+ expand_out_char(*out, '-');
+ expand_out_char(*out, '-');
+ }
+ last_hyphen = !last_hyphen;
+ } else if (*key == '^') {
+ expand_out_char(*out, '^');
+
+ /* ctrl-code */
+ if (key[1] != '\0' && key[1] != '-') {
+ key++;
+ expand_out_char(*out, *key);
+ }
+ else {
+ /* escaped syntax for ^, see gui-readline.c */
+ expand_out_char(*out, '-');
+ }
+
+ expand_out_char(*out, '-');
+ last_hyphen = FALSE; /* optional */
+ } else if (last_hyphen && i_isalpha(*key)) {
+ /* possibly beginning of keycombo */
+ start = key;
+ last_hyphen = FALSE;
+ } else if (g_utf8_validate(key, -1, NULL)) {
+ /* Assume we are looking at the start of a
+ * multibyte sequence we will receive as-is,
+ * so add it to the list as-is.
+ */
+ const char *p, *end = g_utf8_next_char(key);
+ for (p = key; p != end; p++)
+ expand_out_char(*out, *p);
+ expand_out_char(*out, '-');
+ /* The for loop skips past the remaining character.
+ * Nasty, I know...
+ */
+ key = end - 1;
+ last_hyphen = FALSE;
+ } else {
+ expand_out_char(*out, *key);
+ expand_out_char(*out, '-');
+ last_hyphen = FALSE; /* optional */
+ }
+ }
+
+ if (start != NULL)
+ return expand_combo(start, key-1, out, limit);
+
+ for (tmp = *out; tmp != NULL; tmp = tmp->next) {
+ GString *str = tmp->data;
+
+ g_string_truncate(str, str->len-1);
+ }
+
+ return TRUE;
+}
+
+static void key_states_scan_key(const char *key, KEY_REC *rec)
+{
+ GSList *tmp, *out;
+ int limit = MAX_EXPAND_RECURSION;
+
+ if (g_strcmp0(rec->info->id, "key") == 0)
+ return;
+
+ out = g_slist_append(NULL, g_string_new(NULL));
+ if (expand_key(key, &out, &limit)) {
+ for (tmp = out; tmp != NULL; tmp = tmp->next) {
+ GString *str = tmp->data;
+
+ if (str->str[1] == '-' || str->str[1] == '\0')
+ used_keys[(int)(unsigned char)str->str[0]] = 1;
+
+ g_tree_insert(key_states, g_strdup(str->str), rec);
+ }
+ }
+
+ expand_out_free(out);
+}
+
+static int key_state_destroy(char *key)
+{
+ g_free(key);
+ return FALSE;
+}
+
+/* Rescan all the key combos and figure out which characters are supposed
+ to be treated as characters and which as key combos.
+ Yes, this is pretty slow function... */
+static void key_states_rescan(void)
+{
+ GString *temp;
+
+ memset(used_keys, 0, sizeof(used_keys));
+
+ g_tree_foreach(key_states, (GTraverseFunc) key_state_destroy,
+ NULL);
+ g_tree_destroy(key_states);
+ key_states = g_tree_new((GCompareFunc) g_strcmp0);
+
+ temp = g_string_new(NULL);
+ g_hash_table_foreach(keys, (GHFunc) key_states_scan_key, temp);
+ g_string_free(temp, TRUE);
+}
+
+void key_configure_freeze(void)
+{
+ key_config_frozen++;
+}
+
+void key_configure_thaw(void)
+{
+ g_return_if_fail(key_config_frozen > 0);
+
+ if (--key_config_frozen == 0)
+ key_states_rescan();
+}
+
+static void key_configure_destroy(KEY_REC *rec)
+{
+ g_return_if_fail(rec != NULL);
+
+ rec->info->keys = g_slist_remove(rec->info->keys, rec);
+ g_hash_table_remove(keys, rec->key);
+
+ signal_emit("key destroyed", 1, rec);
+
+ if (!key_config_frozen)
+ key_states_rescan();
+
+ g_free_not_null(rec->data);
+ g_free(rec->key);
+ g_free(rec);
+}
+
+/* Configure new key */
+static void key_configure_create(const char *id, const char *key,
+ const char *data)
+{
+ KEYINFO_REC *info;
+ KEY_REC *rec;
+
+ g_return_if_fail(id != NULL);
+ g_return_if_fail(key != NULL && *key != '\0');
+
+ info = key_info_find(id);
+ if (info == NULL)
+ return;
+
+ rec = g_hash_table_lookup(keys, key);
+ if (rec != NULL)
+ key_configure_destroy(rec);
+
+ rec = g_new0(KEY_REC, 1);
+ rec->key = g_strdup(key);
+ rec->info = info;
+ rec->data = g_strdup(data);
+ info->keys = g_slist_append(info->keys, rec);
+ g_hash_table_insert(keys, rec->key, rec);
+
+ signal_emit("key created", 1, rec);
+
+ if (!key_config_frozen)
+ key_states_rescan();
+}
+
+/* Bind a key for function */
+void key_bind(const char *id, const char *description,
+ const char *key_default, const char *data, SIGNAL_FUNC func)
+{
+ KEYINFO_REC *info;
+ char *key;
+
+ g_return_if_fail(id != NULL);
+
+ /* create key info record */
+ info = key_info_find(id);
+ if (info == NULL) {
+ g_return_if_fail(func != NULL);
+
+ if (description == NULL)
+ g_warning("key_bind(%s) should have description!", id);
+ info = g_new0(KEYINFO_REC, 1);
+ info->id = g_strdup(id);
+ info->description = g_strdup(description);
+ keyinfos = g_slist_append(keyinfos, info);
+
+ /* add the signal */
+ key = g_strconcat("key ", id, NULL);
+ signal_add(key, func);
+ g_free(key);
+
+ signal_emit("keyinfo created", 1, info);
+ }
+
+ if (key_default != NULL && *key_default != '\0') {
+ key_default_add(id, key_default, data);
+ key_configure_create(id, key_default, data);
+ }
+}
+
+static void keyinfo_remove(KEYINFO_REC *info)
+{
+ g_return_if_fail(info != NULL);
+
+ keyinfos = g_slist_remove(keyinfos, info);
+ signal_emit("keyinfo destroyed", 1, info);
+
+ /* destroy all keys */
+ g_slist_foreach(info->keys, (GFunc) key_destroy, keys);
+ g_slist_foreach(info->default_keys, (GFunc) key_destroy, default_keys);
+
+ /* destroy key info */
+ g_slist_free(info->keys);
+ g_slist_free(info->default_keys);
+ g_free_not_null(info->description);
+ g_free(info->id);
+ g_free(info);
+}
+
+/* Unbind key */
+void key_unbind(const char *id, SIGNAL_FUNC func)
+{
+ KEYINFO_REC *info;
+ char *key;
+
+ g_return_if_fail(id != NULL);
+ g_return_if_fail(func != NULL);
+
+ /* remove keys */
+ info = key_info_find(id);
+ if (info != NULL)
+ keyinfo_remove(info);
+
+ /* remove signal */
+ key = g_strconcat("key ", id, NULL);
+ signal_remove(key, func);
+ g_free(key);
+}
+
+/* Configure new key */
+void key_configure_add(const char *id, const char *key, const char *data)
+{
+ g_return_if_fail(id != NULL);
+ g_return_if_fail(key != NULL && *key != '\0');
+
+ key_configure_create(id, key, data);
+ keyconfig_save(id, key, data);
+}
+
+/* Remove key */
+void key_configure_remove(const char *key)
+{
+ KEY_REC *rec;
+
+ g_return_if_fail(key != NULL);
+
+ keyconfig_clear(key);
+
+ rec = g_hash_table_lookup(keys, key);
+ if (rec == NULL) return;
+
+ key_configure_destroy(rec);
+}
+
+/* Reset key to default */
+void key_configure_reset(const char *key)
+{
+ KEY_REC *rec;
+ CONFIG_NODE *node;
+
+ g_return_if_fail(key != NULL);
+
+ node = key_config_find(key);
+ if (node != NULL) {
+ iconfig_node_remove(iconfig_node_traverse("(keyboard", FALSE), node);
+ }
+
+ if ((rec = g_hash_table_lookup(default_keys, key)) != NULL) {
+ key_configure_create(rec->info->id, rec->key, rec->data);
+ } else {
+ rec = g_hash_table_lookup(keys, key);
+ if (rec == NULL)
+ return;
+
+ key_configure_destroy(rec);
+ }
+}
+
+static int key_emit_signal(KEYBOARD_REC *keyboard, KEY_REC *key)
+{
+ int consumed;
+ char *str;
+
+ str = g_strconcat("key ", key->info->id, NULL);
+ consumed = signal_emit(str, 3, key->data, keyboard->gui_data, key->info);
+ g_free(str);
+
+ return consumed;
+}
+
+static int key_states_search(const unsigned char *combo,
+ const unsigned char *search)
+{
+ while (*search != '\0') {
+ if (*combo != *search)
+ return *search - *combo;
+ search++; combo++;
+ }
+
+ return 0;
+}
+
+static gboolean key_timeout_expired(KEYBOARD_REC *keyboard)
+{
+ KEY_REC *rec;
+
+ keyboard->timer_tag = 0;
+
+ /* So, the timeout has expired with the input queue full, let's see if
+ * what we've got is bound to some action. */
+ rec = g_tree_lookup(key_states, keyboard->key_state);
+ /* Drain the queue anyway. */
+ g_free_and_null(keyboard->key_state);
+
+ if (rec != NULL) {
+ (void)key_emit_signal(keyboard, rec);
+ }
+
+ return FALSE;
+}
+
+int key_pressed(KEYBOARD_REC *keyboard, const char *key)
+{
+ KEY_REC *rec;
+ char *combo;
+ int first_key, consumed;
+
+ g_return_val_if_fail(keyboard != NULL, FALSE);
+ g_return_val_if_fail(key != NULL && *key != '\0', FALSE);
+
+ if (keyboard->timer_tag > 0) {
+ g_source_remove(keyboard->timer_tag);
+ keyboard->timer_tag = 0;
+ }
+
+ if (keyboard->key_state == NULL && key[1] == '\0' &&
+ !used_keys[(int) (unsigned char) key[0]]) {
+ /* fast check - key not used */
+ return -1;
+ }
+
+ first_key = keyboard->key_state == NULL;
+ combo = keyboard->key_state == NULL ? g_strdup(key) :
+ g_strconcat(keyboard->key_state, "-", key, NULL);
+ g_free_and_null(keyboard->key_state);
+
+ rec = g_tree_search(key_states,
+ (GCompareFunc) key_states_search,
+ combo);
+ if (rec == NULL) {
+ /* unknown key combo, eat the invalid key
+ unless it was the first key pressed */
+ g_free(combo);
+ return first_key ? -1 : 1;
+ }
+
+ if (g_tree_lookup(key_states, combo) != rec) {
+ /* key combo continues.. */
+ keyboard->key_state = combo;
+ /* respect the timeout if specified by the user */
+ if (key_timeout > 0) {
+ keyboard->timer_tag =
+ g_timeout_add(key_timeout,
+ (GSourceFunc) key_timeout_expired,
+ keyboard);
+ }
+ return 0;
+ }
+
+ /* finished key combo, execute */
+ g_free(combo);
+ consumed = key_emit_signal(keyboard, rec);
+
+ /* never consume non-control characters */
+ return consumed ? 1 : -1;
+}
+
+void keyboard_entry_redirect(SIGNAL_FUNC func, const char *entry,
+ int flags, void *data)
+{
+ signal_emit("gui entry redirect", 4, func, entry,
+ GINT_TO_POINTER(flags), data);
+}
+
+static void sig_command(const char *data)
+{
+ const char *cmdchars;
+ char *str;
+
+ cmdchars = settings_get_str("cmdchars");
+ str = strchr(cmdchars, *data) != NULL ? g_strdup(data) :
+ g_strdup_printf("%c%s", *cmdchars, data);
+
+ signal_emit("send command", 3, str, active_win->active_server, active_win->active);
+
+ g_free(str);
+}
+
+static void sig_key(const char *data)
+{
+ /* we should never get here */
+}
+
+static void sig_multi(const char *data, void *gui_data)
+{
+ KEYINFO_REC *info;
+ char **list, **tmp, *p, *str;
+
+ list = g_strsplit(data, ";", -1);
+ for (tmp = list; *tmp != NULL; tmp++) {
+ p = strchr(*tmp, ' ');
+ if (p != NULL) *p++ = '\0'; else p = "";
+
+ info = key_info_find(*tmp);
+ if (info != NULL) {
+ str = g_strconcat("key ", info->id, NULL);
+ signal_emit(str, 3, p, gui_data, info);
+ g_free(str);
+ }
+ }
+ g_strfreev(list);
+}
+
+static void sig_nothing(const char *data)
+{
+}
+
+static void cmd_show_keys(const char *searchkey, int full)
+{
+ GSList *info, *key;
+ int len;
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_BIND_HEADER);
+
+ len = searchkey == NULL ? 0 : strlen(searchkey);
+ for (info = keyinfos; info != NULL; info = info->next) {
+ KEYINFO_REC *rec = info->data;
+
+ for (key = rec->keys; key != NULL; key = key->next) {
+ KEY_REC *rec = key->data;
+
+ if ((len == 0 || (full ? strncmp(rec->key, searchkey, len) == 0 :
+ strstr(rec->key, searchkey) != NULL)) &&
+ (!full || rec->key[len] == '\0')) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_BIND_LIST,
+ rec->key, rec->info->id, rec->data == NULL ? "" : rec->data);
+ }
+ }
+ }
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_BIND_FOOTER);
+}
+
+/* SYNTAX: BIND [-list] [-delete | -reset] [<key> [<command> [<data>]]] */
+static void cmd_bind(const char *data)
+{
+ GHashTable *optlist;
+ char *key, *id, *keydata;
+ void *free_arg;
+ int command_id;
+
+ if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
+ "bind", &optlist, &key, &id, &keydata))
+ return;
+
+ if (g_hash_table_lookup(optlist, "list")) {
+ GSList *tmp;
+
+ for (tmp = keyinfos; tmp != NULL; tmp = tmp->next) {
+ KEYINFO_REC *rec = tmp->data;
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_BIND_COMMAND_LIST,
+ rec->id, rec->description ? rec->description : "");
+ }
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ if (*key != '\0' && g_hash_table_lookup(optlist, "delete")) {
+ /* delete key */
+ key_configure_remove(key);
+ cmd_params_free(free_arg);
+ return;
+ } else if (*key != '\0' && g_hash_table_lookup(optlist, "reset")) {
+ /* reset key */
+ key_configure_reset(key);
+ cmd_show_keys(key, TRUE);
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ if (*id == '\0') {
+ /* show some/all keys */
+ cmd_show_keys(key, FALSE);
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ command_id = strchr(settings_get_str("cmdchars"), *id) != NULL;
+ if (command_id) {
+ /* using shortcut to command id */
+ keydata = g_strconcat(id+1, " ", keydata, NULL);
+ id = "command";
+ }
+
+ if (key_info_find(id) == NULL)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_BIND_UNKNOWN_ID, id);
+ else {
+ key_configure_add(id, key, keydata);
+ cmd_show_keys(key, TRUE);
+ }
+
+ if (command_id) g_free(keydata);
+ cmd_params_free(free_arg);
+}
+
+static GList *completion_get_keyinfos(const char *info)
+{
+ GList *list;
+ GSList *tmp;
+ int len;
+
+ list = NULL; len = strlen(info);
+ for (tmp = keyinfos; tmp != NULL; tmp = tmp->next) {
+ KEYINFO_REC *rec = tmp->data;
+
+ if (g_ascii_strncasecmp(rec->id, info, len) == 0)
+ list = g_list_append(list, g_strdup(rec->id));
+ }
+
+ return list;
+}
+
+static void sig_complete_bind(GList **list, WINDOW_REC *window,
+ const char *word, const char *line,
+ int *want_space)
+{
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+ g_return_if_fail(line != NULL);
+
+ if (*line == '\0' || strchr(line, ' ') != NULL)
+ return;
+
+ *list = completion_get_keyinfos(word);
+ if (*list != NULL) signal_stop();
+}
+
+static int key_destroy_hash(const char *key, KEY_REC *rec)
+{
+ rec->info->keys = g_slist_remove(rec->info->keys, rec);
+
+ g_free_not_null(rec->data);
+ g_free(rec->key);
+ g_free(rec);
+ return TRUE;
+}
+
+static void key_copy_default(const char *key, KEY_REC *orig)
+{
+ KEY_REC *rec;
+
+ rec = g_new0(KEY_REC, 1);
+ rec->key = g_strdup(orig->key);
+ rec->info = orig->info;
+ rec->data = g_strdup(orig->data);
+
+ rec->info->keys = g_slist_append(rec->info->keys, rec);
+ g_hash_table_insert(keys, rec->key, rec);
+}
+
+static void keyboard_reset_defaults(void)
+{
+ g_hash_table_foreach_remove(keys, (GHRFunc) key_destroy_hash, NULL);
+ g_hash_table_foreach(default_keys, (GHFunc) key_copy_default, NULL);
+}
+
+static void key_config_read(CONFIG_NODE *node)
+{
+ char *key, *id, *data;
+
+ g_return_if_fail(node != NULL);
+
+ key = config_node_get_str(node, "key", NULL);
+ id = config_node_get_str(node, "id", NULL);
+ data = config_node_get_str(node, "data", NULL);
+
+ if (key != NULL && id != NULL) {
+ key_configure_create(id, key, data);
+ } else if (key != NULL && id == NULL && data == NULL) {
+ KEY_REC *rec = g_hash_table_lookup(keys, key);
+ if (rec != NULL)
+ key_configure_destroy(rec);
+ }
+}
+
+static void read_keyboard_config(void)
+{
+ CONFIG_NODE *node;
+ GSList *tmp;
+
+ key_configure_freeze();
+
+ keyboard_reset_defaults();
+
+ node = iconfig_node_traverse("keyboard", FALSE);
+ if (node == NULL) {
+ key_configure_thaw();
+ return;
+ }
+
+ /* FIXME: backward "compatibility" - remove after irssi .99 */
+ if (node->type != NODE_TYPE_LIST) {
+ iconfig_node_remove(NULL, node);
+ key_configure_thaw();
+ return;
+ }
+
+ tmp = config_node_first(node->value);
+ for (; tmp != NULL; tmp = config_node_next(tmp))
+ key_config_read(tmp->data);
+
+ key_configure_thaw();
+
+ /* any positive value other than 0 enables the timeout (in ms). */
+ key_timeout = settings_get_int("key_timeout");
+}
+
+void keyboard_init(void)
+{
+ keys = g_hash_table_new((GHashFunc) g_str_hash,
+ (GCompareFunc) g_str_equal);
+ default_keys = g_hash_table_new((GHashFunc) g_str_hash,
+ (GCompareFunc) g_str_equal);
+ keyinfos = NULL;
+ key_states = g_tree_new((GCompareFunc) g_strcmp0);
+ key_config_frozen = 0;
+ memset(used_keys, 0, sizeof(used_keys));
+
+ settings_add_int("misc", "key_timeout", 0);
+
+ key_bind("command", "Run any command", NULL, NULL, (SIGNAL_FUNC) sig_command);
+ key_bind("key", "Specify name for key binding", NULL, NULL, (SIGNAL_FUNC) sig_key);
+ key_bind("multi", "Run multiple commands", NULL, NULL, (SIGNAL_FUNC) sig_multi);
+ key_bind("nothing", "Do nothing", NULL, NULL, (SIGNAL_FUNC) sig_nothing);
+
+ /* read the keyboard config when all key binds are known */
+ signal_add("irssi init read settings", (SIGNAL_FUNC) read_keyboard_config);
+ signal_add("setup reread", (SIGNAL_FUNC) read_keyboard_config);
+ signal_add("complete command bind", (SIGNAL_FUNC) sig_complete_bind);
+
+ command_bind("bind", NULL, (SIGNAL_FUNC) cmd_bind);
+ command_set_options("bind", "delete reset list");
+}
+
+void keyboard_deinit(void)
+{
+ key_unbind("command", (SIGNAL_FUNC) sig_command);
+ key_unbind("key", (SIGNAL_FUNC) sig_key);
+ key_unbind("multi", (SIGNAL_FUNC) sig_multi);
+ key_unbind("nothing", (SIGNAL_FUNC) sig_nothing);
+
+ while (keyinfos != NULL)
+ keyinfo_remove(keyinfos->data);
+ g_hash_table_destroy(keys);
+ g_hash_table_destroy(default_keys);
+
+ g_tree_foreach(key_states, (GTraverseFunc) key_state_destroy,
+ NULL);
+ g_tree_destroy(key_states);
+
+ signal_remove("irssi init read settings", (SIGNAL_FUNC) read_keyboard_config);
+ signal_remove("setup reread", (SIGNAL_FUNC) read_keyboard_config);
+ signal_remove("complete command bind", (SIGNAL_FUNC) sig_complete_bind);
+ command_unbind("bind", (SIGNAL_FUNC) cmd_bind);
+}
diff --git a/src/fe-common/core/keyboard.h b/src/fe-common/core/keyboard.h
new file mode 100644
index 0000000..b19536a
--- /dev/null
+++ b/src/fe-common/core/keyboard.h
@@ -0,0 +1,58 @@
+#ifndef IRSSI_FE_COMMON_CORE_KEYBOARD_H
+#define IRSSI_FE_COMMON_CORE_KEYBOARD_H
+
+#include <irssi/src/core/signals.h>
+
+typedef struct _KEYBOARD_REC KEYBOARD_REC;
+typedef struct _KEYINFO_REC KEYINFO_REC;
+typedef struct _KEY_REC KEY_REC;
+
+struct _KEYINFO_REC {
+ char *id;
+ char *description;
+
+ GSList *keys, *default_keys;
+};
+
+struct _KEY_REC {
+ KEYINFO_REC *info;
+
+ char *key;
+ char *data;
+};
+
+extern GSList *keyinfos;
+
+/* Creates a new "keyboard" - this is used only for keeping track of
+ key combo states and sending the gui_data parameter in "key pressed"
+ signal */
+KEYBOARD_REC *keyboard_create(void *gui_data);
+/* Destroys a keyboard */
+void keyboard_destroy(KEYBOARD_REC *keyboard);
+/* Returns 1 if key press was consumed, -1 if not, 0 if it's beginning of a
+ key combo. Control characters should be sent as "^@" .. "^_" instead of
+ #0..#31 chars, #127 should be sent as ^? */
+int key_pressed(KEYBOARD_REC *keyboard, const char *key);
+
+void key_bind(const char *id, const char *description,
+ const char *key_default, const char *data, SIGNAL_FUNC func);
+void key_unbind(const char *id, SIGNAL_FUNC func);
+
+void key_configure_freeze(void);
+void key_configure_thaw(void);
+
+void key_configure_add(const char *id, const char *key, const char *data);
+void key_configure_remove(const char *key);
+
+KEYINFO_REC *key_info_find(const char *id);
+
+#define ENTRY_REDIRECT_FLAG_HOTKEY 0x01
+#define ENTRY_REDIRECT_FLAG_HIDDEN 0x02
+
+void keyboard_entry_redirect(SIGNAL_FUNC func, const char *entry,
+ int flags, void *data);
+
+void keyboard_init(void);
+void keyboard_deinit(void);
+
+#endif
diff --git a/src/fe-common/core/meson.build b/src/fe-common/core/meson.build
new file mode 100644
index 0000000..73cb156
--- /dev/null
+++ b/src/fe-common/core/meson.build
@@ -0,0 +1,99 @@
+# this file is part of irssi
+
+if have_capsicum
+ fe_common_core_capsicum_source = files('fe-capsicum.c')
+else
+ fe_common_core_capsicum_source = []
+endif
+
+fe_common_core_sources = [
+ files(
+ 'chat-completion.c',
+ 'command-history.c',
+ 'completion.c',
+ 'fe-channels.c',
+ 'fe-common-core.c',
+ 'fe-core-commands.c',
+ 'fe-exec.c',
+ 'fe-expandos.c',
+ 'fe-help.c',
+ 'fe-ignore-messages.c',
+ 'fe-ignore.c',
+ 'fe-log.c',
+ 'fe-messages.c',
+ 'fe-modules.c',
+ 'fe-queries.c',
+ 'fe-recode.c',
+ 'fe-server.c',
+ 'fe-settings.c',
+ 'fe-tls.c',
+ 'fe-windows.c',
+ 'formats.c',
+ 'hilight-text.c',
+ 'keyboard.c',
+ 'module-formats.c',
+ 'printtext.c',
+ 'themes.c',
+ 'window-activity.c',
+ 'window-commands.c',
+ 'window-items.c',
+ 'windows-layout.c',
+ )
+ + fe_common_core_capsicum_source
+ + [
+ default_theme_h,
+ irssi_version_h,
+ ]
+]
+
+libfe_common_core_a = static_library('fe_common_core',
+ fe_common_core_sources,
+ include_directories : rootinc,
+ implicit_include_directories : false,
+ c_args : [
+ def_helpdir,
+ def_themesdir,
+ ],
+ dependencies : dep)
+
+if want_fuzzer
+ libfuzzer_fe_common_core_a = static_library('fuzzer_fe_common_core',
+ fe_common_core_sources,
+ include_directories : rootinc,
+ implicit_include_directories : false,
+ c_args : [
+ def_helpdir,
+ def_themesdir,
+ def_suppress_printf_fallback,
+ ],
+ dependencies : dep)
+endif
+
+install_headers(
+ files(
+ 'chat-completion.h',
+ 'command-history.h',
+ 'completion.h',
+ 'fe-capsicum.h',
+ 'fe-channels.h',
+ 'fe-common-core.h',
+ 'fe-core-commands.h',
+ 'fe-exec.h',
+ 'fe-messages.h',
+ 'fe-queries.h',
+ 'fe-recode.h',
+ 'fe-settings.h',
+ 'fe-tls.h',
+ 'fe-windows.h',
+ 'formats.h',
+ 'hilight-text.h',
+ 'keyboard.h',
+ 'module-formats.h',
+ 'module.h',
+ 'printtext.h',
+ 'themes.h',
+ 'window-activity.h',
+ 'window-items.h',
+ 'windows-layout.h',
+ ),
+ subdir : incdir / 'src' / 'fe-common' / 'core')
diff --git a/src/fe-common/core/module-formats.c b/src/fe-common/core/module-formats.c
new file mode 100644
index 0000000..c4d9be9
--- /dev/null
+++ b/src/fe-common/core/module-formats.c
@@ -0,0 +1,321 @@
+/*
+ module-formats.c : irssi
+
+ Copyright (C) 2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/core/formats.h>
+
+FORMAT_REC fecommon_core_formats[] = {
+ /* clang-format off */
+ { MODULE_NAME, "Core", 0 },
+
+ /* ---- */
+ { NULL, "Windows", 0 },
+
+ { "line_start", "{line_start}", 0 },
+ { "line_start_irssi", "{line_start}{hilight Irssi:} ", 0 },
+ { "timestamp", "{timestamp $Z} ", 0 },
+ { "servertag", "[$0] ", 1, { 0 } },
+ { "daychange", "Day changed to %%d %%b %%Y", 0 },
+ { "talking_with", "You are now talking with {nick $0}", 1, { 0 } },
+ { "refnum_too_low", "Window number must be greater than 1", 0 },
+ { "error_server_sticky", "Window's server is sticky and it cannot be changed without -unsticky option", 0 },
+ { "set_server_sticky", "Window's server set sticky", 1, { 0 } },
+ { "unset_server_sticky", "Window's server isn't sticky anymore", 0 },
+ { "window_name_not_unique", "Window names must be unique", 1, { 0 } },
+ { "window_level", "Window level is $0", 1, { 0 } },
+ { "window_set_immortal", "Window is immortal", 0 },
+ { "window_unset_immortal", "Window isn't immortal", 0 },
+ { "window_immortal_error", "Window is immortal, if you really want to close it, say /WINDOW IMMORTAL OFF", 0 },
+ { "windowlist_header", "%#Ref Name Active item Server Level", 0 },
+ { "windowlist_line", "%#$[4]0 %|$[20]1 $[15]2 $[15]3 $4", 5, { 1, 0, 0, 0, 0 } },
+ { "windowlist_footer", "", 0 },
+ { "windows_layout_saved", "Layout of windows is now remembered", 0 },
+ { "windows_layout_reset", "Layout of windows reset to defaults", 0 },
+ { "window_info_header", "", 0 },
+ { "window_info_footer", "", 0 },
+ { "window_info_refnum", "%#Window : {hilight #$0}", 1, { 1 } },
+ { "window_info_refnum_sticky", "%#Window : {hilight #$0 (sticky)}", 1, { 1 } },
+ { "window_info_name", "%#Name : $0", 1, { 0 } },
+ { "window_info_history", "%#History : $0", 1, { 0 } },
+ { "window_info_immortal", "%#Immortal: yes", 0 },
+ { "window_info_size", "%#Size : $0x$1", 2, { 1, 1 } },
+ { "window_info_level", "%#Level : $0", 1, { 0 } },
+ { "window_info_server", "%#Server : $0", 1, { 0 } },
+ { "window_info_server_sticky", "%#Server : $0 (sticky)", 1, { 0 } },
+ { "window_info_theme", "%#Theme : $0$1", 2, { 0, 0 } },
+ { "window_info_bound_items_header", "%#Bounds : {hilight Name Server tag}", 0 },
+ { "window_info_bound_item", "%# : $[!30]0 $[!15]1 $2", 3, { 0, 0, 0 } },
+ { "window_info_bound_items_footer", "", 0 },
+ { "window_info_items_header", "%#Items : {hilight Name Server tag}", 0 },
+ { "window_info_item", "%# $[7]0: $[!30]1 $2", 3, { 0, 0, 0 } },
+ { "window_info_items_footer", "", 0 },
+
+ /* ---- */
+ { NULL, "Server", 0 },
+
+ { "looking_up", "Looking up {server $0}", 1, { 0 } },
+ { "connecting", "Connecting to {server $0} [$1] port {hilight $2}", 3, { 0, 0, 1 } },
+ { "reconnecting", "Reconnecting to {server $0} [$1] port {hilight $2} - use /RMRECONNS to abort", 3, { 0, 0, 1 } },
+ { "connection_established", "Connection to {server $0} established", 1, { 0 } },
+ { "cant_connect", "Unable to connect server {server $0} port {hilight $1} {reason $2}", 3, { 0, 1, 0 } },
+ { "connection_lost", "Connection lost to {server $0}", 1, { 0 } },
+ { "lag_disconnected", "No PONG reply from server {server $0} in $1 seconds, disconnecting", 2, { 0, 1 } },
+ { "disconnected", "Disconnected from {server $0} {reason $1}", 2, { 0, 0 } },
+ { "server_quit", "Disconnecting from server {server $0}: {reason $1}", 2, { 0, 0 } },
+ { "server_changed", "Changed to {hilight $2} server {server $1}", 3, { 0, 0, 0 } },
+ { "unknown_server_tag", "Unknown server tag {server $0}", 1, { 0 } },
+ { "no_connected_servers", "Not connected to any servers", 0 },
+ { "server_list", "{server $0}: $1:$2 ($3)", 5, { 0, 0, 1, 0, 0 } },
+ { "server_lookup_list", "{server $0}: $1:$2 ($3) (connecting...)", 5, { 0, 0, 1, 0, 0 } },
+ { "server_reconnect_list", "{server $0}: $1:$2 ($3) ($5 left before reconnecting)", 6, { 0, 0, 1, 0, 0, 0 } },
+ { "server_reconnect_removed", "Removed reconnection to server {server $0} port {hilight $1}", 3, { 0, 1, 0 } },
+ { "server_reconnect_not_found", "Reconnection tag {server $0} not found", 1, { 0 } },
+ { "setupserver_added", "Server {server $0} saved", 2, { 0, 1 } },
+ { "setupserver_removed", "Server {server $0} {hilight $1} removed", 2, { 0, 1 } },
+ { "setupserver_not_found", "Server {server $0} {hilight $1} not found", 2, { 0, 1 } },
+ { "your_nick", "Your nickname is {nick $0}", 1, { 0 } },
+
+ /* ---- */
+ { NULL, "Channels", 0 },
+
+ { "join", "{channick_hilight $0} {chanhost_hilight $1} has joined {channel $2}", 5, { 0, 0, 0, 0, 0 } },
+ { "join_extended", "{channick_hilight $0} {chanhost_hilight $1} has joined {channel $2} {comment realname {reason $4}}", 5, { 0, 0, 0, 0, 0 } },
+ { "join_extended_account", "{channick_hilight $0} {chanhost_hilight $1} has joined {channel $2} {reason account {hilight $3}} {comment realname {reason $4}}", 5, { 0, 0, 0, 0, 0 } },
+ { "host_changed", "{channick_hilight $0} {chanhost_hilight $1} has changed host", 4, { 0, 0, 0, 0 } },
+ { "logged_out", "{channick $0} {chanhost $1} has logged out of their account", 4, { 0, 0, 0, 0 } },
+ { "logged_in", "{channick_hilight $0} {chanhost_hilight $1} has logged in to account {hilight $2}", 4, { 0, 0, 0, 0 } },
+ { "part", "{channick $0} {chanhost $1} has left {channel $2} {reason $3}", 4, { 0, 0, 0, 0 } },
+ { "kick", "{channick $0} was kicked from {channel $1} by {nick $2} {reason $3}", 5, { 0, 0, 0, 0, 0 } },
+ { "quit", "{channick $0} {chanhost $1} has quit {reason $2}", 4, { 0, 0, 0, 0 } },
+ { "quit_once", "{channel $3} {channick $0} {chanhost $1} has quit {reason $2}", 4, { 0, 0, 0, 0 } },
+ { "invite", "{nick $0} invites you to {channel $1}", 3, { 0, 0, 0 } },
+ { "not_invited", "You have not been invited to a channel!", 0 },
+ { "invite_other", "{nick $0} has been invited to {channel $2} by {channick_hilight $1}", 4, { 0, 0, 0, 0 } },
+ { "new_topic", "{nick $0} changed the topic of {channel $1} to: $2", 4, { 0, 0, 0, 0 } },
+ { "topic_unset", "Topic unset by {nick $0} on {channel $1}", 4, { 0, 0, 0, 0 } },
+ { "your_nick_changed", "You're now known as {nick $1}", 4, { 0, 0, 0, 0 } },
+ { "nick_changed", "{channick $0} is now known as {channick_hilight $1}", 4, { 0, 0, 0, 0 } },
+ { "notify_away_channel", "{channick $0} {chanhost $1} is now away: {reason $2}", 4, { 0, 0, 0, 0 } },
+ { "notify_unaway_channel", "{channick_hilight $0} {chanhost $1} is no longer away", 4, { 0, 0, 0, 0 } },
+ { "talking_in", "You are now talking in {channel $0}", 1, { 0 } },
+ { "not_in_channels", "You are not on any channels", 0 },
+ { "current_channel", "Current channel {channel $0}", 1, { 0 } },
+ { "names", "{names_users Users {names_channel $0}}", 6, { 0, 1, 1, 1, 1, 1 } },
+ { "names_prefix", "%#{names_prefix $0}", 1, { 0 } },
+ { "names_nick_op", "{names_nick_op $0 $1}", 2, { 0, 0 } },
+ { "names_nick_halfop", "{names_nick_halfop $0 $1}", 2, { 0, 0 } },
+ { "names_nick_voice", "{names_nick_voice $0 $1}", 2, { 0, 0 } },
+ { "names_nick", "{names_nick $0 $1}", 2, { 0, 0 } },
+ { "endofnames", "{channel $0}: Total of {hilight $1} nicks {comment {hilight $2} ops, {hilight $3} halfops, {hilight $4} voices, {hilight $5} normal}", 6, { 0, 1, 1, 1, 1, 1 } },
+ { "chanlist_header", "%#You are on the following channels:", 0 },
+ { "chanlist_line", "%#{channel $[-10]0} %|+$1 ($2): $3", 4, { 0, 0, 0, 0 } },
+ { "chansetup_not_found", "Channel {channel $0} not found", 2, { 0, 0 } },
+ { "chansetup_added", "Channel {channel $0} saved", 2, { 0, 0 } },
+ { "chansetup_removed", "Channel {channel $0} removed", 2, { 0, 0 } },
+ { "chansetup_header", "%#Channel Network Password Settings", 0 },
+ { "chansetup_line", "%#{channel $[15]0} %|$[10]1 $[10]2 $3", 4, { 0, 0, 0, 0 } },
+ { "chansetup_footer", "", 0 },
+
+ /* ---- */
+ { NULL, "Messages", 0 },
+
+ { "own_msg", "{ownmsgnick $2 {ownnick $0}}$1", 3, { 0, 0, 0 } },
+ { "own_msg_channel", "{ownmsgnick $3 {ownnick $0}{msgchannel $1}}$2", 4, { 0, 0, 0, 0 } },
+ { "own_msg_private", "{ownprivmsg msg $0}$1", 2, { 0, 0 } },
+ { "own_msg_private_query", "{ownprivmsgnick {ownprivnick $2}}$1", 3, { 0, 0, 0 } },
+ { "pubmsg_me", "{pubmsgmenick $2 {menick $0}}$1", 3, { 0, 0, 0 } },
+ { "pubmsg_me_channel", "{pubmsgmenick $3 {menick $0}{msgchannel $1}}$2", 4, { 0, 0, 0, 0 } },
+ { "pubmsg_hilight", "{pubmsghinick $0 $3 $1}$2", 4, { 0, 0, 0, 0 } },
+ { "pubmsg_hilight_channel", "{pubmsghinick $0 $4 $1{msgchannel $2}}$3", 5, { 0, 0, 0, 0, 0 } },
+ { "pubmsg", "{pubmsgnick $2 {pubnick $0}}$1", 3, { 0, 0, 0 } },
+ { "pubmsg_channel", "{pubmsgnick $3 {pubnick $0}{msgchannel $1}}$2", 4, { 0, 0, 0, 0 } },
+ { "msg_private", "{privmsg $0 $1}$2", 3, { 0, 0, 0 } },
+ { "msg_private_query", "{privmsgnick $0}$2", 3, { 0, 0, 0 } },
+ { "no_msgs_got", "You have not received a message from anyone yet", 0 },
+ { "no_msgs_sent", "You have not sent a message to anyone yet", 0 },
+
+ /* ---- */
+ { NULL, "Queries", 0 },
+
+ { "query_start", "Starting query in {server $1} with {nick $0}", 2, { 0, 0 } },
+ { "query_stop", "Closing query with {nick $0}", 1, { 0 } },
+ { "no_query", "No query with {nick $0}", 1, { 0 } },
+ { "query_server_changed", "Query with {nick $0} changed to server {server $1}", 2, { 0, 0 } },
+
+ /* ---- */
+ { NULL, "Highlighting", 0 },
+
+ { "hilight_header", "%#Highlights:", 0 },
+ { "hilight_line", "%#$[-4]0 $1 $2 $3$4", 5, { 1, 0, 0, 0, 0 } },
+ { "hilight_footer", "", 0 },
+ { "hilight_not_found", "Highlight not found: $0", 1, { 0 } },
+ { "hilight_removed", "Highlight removed: $0", 1, { 0 } },
+
+ /* ---- */
+ { NULL, "Aliases", 0 },
+
+ { "alias_added", "Alias $0 added", 1, { 0 } },
+ { "alias_removed", "Alias $0 removed", 1, { 0 } },
+ { "alias_not_found", "No such alias: $0", 1, { 0 } },
+ { "aliaslist_header", "%#Aliases:", 0 },
+ { "aliaslist_line", "%#$[10]0 $1", 2, { 0, 0 } },
+ { "aliaslist_footer", "", 0 },
+
+ /* ---- */
+ { NULL, "Logging", 0 },
+
+ { "log_opened", "Log file {hilight $0} opened", 1, { 0 } },
+ { "log_closed", "Log file {hilight $0} closed", 1, { 0 } },
+ { "log_create_failed", "Couldn't create log file {hilight $0}: $1", 2, { 0, 0 } },
+ { "log_locked", "Log file {hilight $0} is locked, probably by another running Irssi", 1, { 0 } },
+ { "log_not_open", "Log file {hilight $0} not open", 1, { 0 } },
+ { "log_started", "Started logging to file {hilight $0}", 1, { 0 } },
+ { "log_stopped", "Stopped logging to file {hilight $0}", 1, { 0 } },
+ { "log_list_header", "%#Logs:", 0 },
+ { "log_list", "%#$0 $1: $2 $3$4$5", 6, { 1, 0, 0, 0, 0, 0 } },
+ { "log_list_footer", "", 0 },
+ { "windowlog_file", "Window LOGFILE set to $0", 1, { 0 } },
+ { "windowlog_file_logging", "Can't change window's logfile while log is on", 0 },
+ { "no_away_msgs", "No new messages in awaylog", 1, { 0 } },
+ { "away_msgs", "{hilight $1} new messages in awaylog:", 2, { 0, 1 } },
+
+ /* ---- */
+ { NULL, "Modules", 0 },
+
+ { "module_header", "%#Module Type Submodules", 0, },
+ { "module_line", "%#$[!20]0 $[7]1 $2", 3, { 0, 0, 0 } },
+ { "module_footer", "", 0, },
+ { "module_already_loaded", "Module {hilight $0/$1} already loaded", 2, { 0, 0 } },
+ { "module_not_loaded", "Module {hilight $0/$1} is not loaded", 2, { 0, 0 } },
+ { "module_load_error", "Error loading module {hilight $0/$1}: $2", 3, { 0, 0, 0 } },
+ { "module_version_mismatch", "{hilight $0/$1} is ABI version $2 but Irssi is version $abiversion, cannot load", 3, { 0, 0, 0 } },
+ { "module_invalid", "{hilight $0/$1} isn't Irssi module", 2, { 0, 0 } },
+ { "module_loaded", "Loaded module {hilight $0/$1}", 2, { 0, 0 } },
+ { "module_unloaded", "Unloaded module {hilight $0/$1}", 2, { 0, 0 } },
+
+ /* ---- */
+ { NULL, "Commands", 0 },
+
+ { "command_unknown", "Unknown command: $0", 1, { 0 } },
+ { "command_ambiguous", "Ambiguous command: $0", 1, { 0 } },
+ { "option_unknown", "Unknown option: $0", 1, { 0 } },
+ { "option_ambiguous", "Ambiguous option: $0", 1, { 0 } },
+ { "option_missing_arg", "Missing required argument for: $0", 1, { 0 } },
+ { "not_enough_params", "Not enough parameters given", 0 },
+ { "not_connected", "Not connected to server", 0 },
+ { "not_joined", "Not joined to any channel", 0 },
+ { "chan_not_found", "Not joined to such channel", 0 },
+ { "chan_not_synced", "Channel not fully synchronized yet, try again after a while", 0 },
+ { "illegal_proto", "Command isn't designed for the chat protocol of the active server", 0 },
+ { "not_good_idea", "Doing this is not a good idea. Add -YES option to command if you really mean it", 0 },
+ { "invalid_number", "Invalid number", 0 },
+ { "invalid_time", "Invalid timestamp", 0 },
+ { "invalid_level", "Invalid message level", 0 },
+ { "invalid_size", "Invalid size", 0 },
+ { "invalid_charset", "Invalid charset: $0", 1, { 0 } },
+ { "invalid_choice", "Invalid choice, must be one of $0", 1, { 0 } },
+ { "eval_max_recurse", "/eval hit maximum recursion limit", 0 },
+ { "program_not_found", "Could not find file or file is not executable", 0 },
+ { "no_server_defined", "No servers defined for this network, see /help server for how to add one", 0 },
+
+ /* ---- */
+ { NULL, "Themes", 0 },
+
+ { "theme_saved", "Theme saved to $0", 1, { 0 } },
+ { "theme_save_failed", "Error saving theme to $0: $1", 2, { 0, 0 } },
+ { "theme_not_found", "Theme {hilight $0} not found", 1, { 0 } },
+ { "theme_changed", "Now using theme {hilight $0} ($1)", 2, { 0, 0 } },
+ { "window_theme", "Using theme {hilight $0} in this window", 2, { 0, 0 } },
+ { "window_theme_default", "No theme is set for this window", 0 },
+ { "window_theme_changed", "Now using theme {hilight $0} ($1) in this window", 2, { 0, 0 } },
+ { "window_theme_removed", "Removed theme from this window", 0 },
+ { "format_title", "%:[{hilight $0}] - [{hilight $1}]%:", 2, { 0, 0 } },
+ { "format_subtitle", "[{hilight $0}]", 1, { 0 } },
+ { "format_item", "$0 = $1", 2, { 0, 0 } },
+
+ /* ---- */
+ { NULL, "Ignores", 0 },
+
+ { "ignored", "Ignoring {hilight $1} from {nick $0}", 2, { 0, 0 } },
+ { "ignored_options", "Ignoring {hilight $1} from {nick $0} {comment $2}", 3, { 0, 0, 0 } },
+ { "unignored", "Unignored {nick $0}", 1, { 0 } },
+ { "ignore_not_found", "{nick $0} is not being ignored", 1, { 0 } },
+ { "ignore_no_ignores", "There are no ignores", 0 },
+ { "ignore_header", "%#Ignore List:", 0 },
+ { "ignore_line", "%#$[-4]0 $1: $2 $3 $4", 4, { 1, 0, 0, 0 } },
+ { "ignore_footer", "", 0 },
+
+ /* ---- */
+ { NULL, "Recode", 0 },
+
+ { "not_channel_or_query", "The current window is not a channel or query window", 0 },
+ { "conversion_added", "Added {hilight $0}/{hilight $1} to conversion database", 2, { FORMAT_STRING, FORMAT_STRING } },
+ { "conversion_removed", "Removed {hilight $0} from conversion database", 1, { FORMAT_STRING } },
+ { "conversion_not_found", "{hilight $0} not found in conversion database", 1, { FORMAT_STRING } },
+ { "conversion_no_translits", "Transliterations not supported in this system", 0 },
+ { "recode_header", "%#Target Character set", 0 },
+ { "recode_line", "%#%|$[!30]0 $1", 2, { FORMAT_STRING, FORMAT_STRING } },
+
+ /* ---- */
+ { NULL, "Misc", 0 },
+
+ { "unknown_chat_protocol", "Unknown chat protocol: $0", 1, { 0 } },
+ { "unknown_chatnet", "Unknown chat network: $0 (create it with /NETWORK ADD)", 1, { 0 } },
+ { "not_toggle", "Value must be either ON, OFF or TOGGLE", 0 },
+ { "perl_error", "Perl error: $0", 1, { 0 } },
+ { "bind_header", "%#Key Action", 0 },
+ { "bind_list", "%#$[!20]0 $1 $2", 3, { 0, 0, 0 } },
+ { "bind_command_list", "$[!30]0 $1", 2, { 0, 0 } },
+ { "bind_footer", "", 0 },
+ { "bind_unknown_id", "Unknown bind action: $0", 1, { 0 } },
+ { "config_saved", "Saved configuration to file $0", 1, { 0 } },
+ { "config_reloaded", "Reloaded configuration", 1, { 0 } },
+ { "config_modified", "Configuration file was modified since irssi was last started - do you want to overwrite the possible changes?", 1, { 0 } },
+ { "glib_error", "{error ($0) $1} $2", 3, { 0, 0, 0 } },
+ { "overwrite_config", "Overwrite config (y/N)?", 0 },
+ { "set_title", "[{hilight $0}]", 1, { 0 } },
+ { "set_item", "$[-!32]0 %_$1", 2, { 0, 0 } },
+ { "set_unknown", "Unknown setting $0", 1, { 0 } },
+ { "set_not_boolean", "Setting {hilight $0} isn't boolean, use /SET", 1, { 0 } },
+ { "no_completions", "There are no completions", 0 },
+ { "completion_removed", "Removed completion $0", 1, { 0 } },
+ { "completion_header", "%#Key Value Auto", 0 },
+ { "completion_line", "%#$[10]0 $[!40]1 $2", 3, { 0, 0, 0 } },
+ { "completion_footer", "", 0 },
+ { "capsicum_enabled", "Capability mode enabled", 0 },
+ { "capsicum_disabled", "Capability mode not enabled", 0 },
+ { "capsicum_failed", "Capability mode failed: $0", 1, { 0 } },
+
+ /* ---- */
+ { NULL, "TLS", 0 },
+
+ { "tls_ephemeral_key", "EDH Key: {hilight $0} bit {hilight $1}", 2, { 1, 0 } },
+ { "tls_ephemeral_key_unavailable", "EDH Key: {error N/A}", 0 },
+ { "tls_pubkey", "Public Key: {hilight $0} bit {hilight $1}, valid from {hilight $2} to {hilight $3}", 4, { 1, 0, 0, 0 } },
+ { "tls_cert_header", "Certificate Chain:", 0 },
+ { "tls_cert_subject", " Subject: {hilight $0}", 1, { 0 } },
+ { "tls_cert_issuer", " Issuer: {hilight $0}", 1, { 0 } },
+ { "tls_pubkey_fingerprint", "Public Key Fingerprint: {hilight $0} ({hilight $1})", 2, { 0, 0 } },
+ { "tls_cert_fingerprint", "Certificate Fingerprint: {hilight $0} ({hilight $1})", 2, { 0, 0 } },
+ { "tls_protocol_version", "Protocol: {hilight $0} ({hilight $1} bit, {hilight $2})", 3, { 0, 1, 0 } },
+
+ { NULL, NULL, 0 }
+ /* clang-format on */
+};
diff --git a/src/fe-common/core/module-formats.h b/src/fe-common/core/module-formats.h
new file mode 100644
index 0000000..1b1927b
--- /dev/null
+++ b/src/fe-common/core/module-formats.h
@@ -0,0 +1,283 @@
+#include <irssi/src/fe-common/core/formats.h>
+
+enum {
+ TXT_MODULE_NAME,
+
+ TXT_FILL_1,
+
+ TXT_LINE_START,
+ TXT_LINE_START_IRSSI,
+ TXT_TIMESTAMP,
+ TXT_SERVERTAG,
+ TXT_DAYCHANGE,
+ TXT_TALKING_WITH,
+ TXT_REFNUM_TOO_LOW,
+ TXT_ERROR_SERVER_STICKY,
+ TXT_SET_SERVER_STICKY,
+ TXT_UNSET_SERVER_STICKY,
+ TXT_WINDOW_NAME_NOT_UNIQUE,
+ TXT_WINDOW_LEVEL,
+ TXT_WINDOW_SET_IMMORTAL,
+ TXT_WINDOW_UNSET_IMMORTAL,
+ TXT_WINDOW_IMMORTAL_ERROR,
+ TXT_WINDOWLIST_HEADER,
+ TXT_WINDOWLIST_LINE,
+ TXT_WINDOWLIST_FOOTER,
+ TXT_WINDOWS_LAYOUT_SAVED,
+ TXT_WINDOWS_LAYOUT_RESET,
+ TXT_WINDOW_INFO_HEADER,
+ TXT_WINDOW_INFO_FOOTER,
+ TXT_WINDOW_INFO_REFNUM,
+ TXT_WINDOW_INFO_REFNUM_STICKY,
+ TXT_WINDOW_INFO_NAME,
+ TXT_WINDOW_INFO_HISTORY,
+ TXT_WINDOW_INFO_IMMORTAL,
+ TXT_WINDOW_INFO_SIZE,
+ TXT_WINDOW_INFO_LEVEL,
+ TXT_WINDOW_INFO_SERVER,
+ TXT_WINDOW_INFO_SERVER_STICKY,
+ TXT_WINDOW_INFO_THEME,
+ TXT_WINDOW_INFO_BOUND_ITEMS_HEADER,
+ TXT_WINDOW_INFO_BOUND_ITEM,
+ TXT_WINDOW_INFO_BOUND_ITEMS_FOOTER,
+ TXT_WINDOW_INFO_ITEMS_HEADER,
+ TXT_WINDOW_INFO_ITEM,
+ TXT_WINDOW_INFO_ITEMS_FOOTER,
+
+ TXT_FILL_2,
+
+ TXT_LOOKING_UP,
+ TXT_CONNECTING,
+ TXT_RECONNECTING,
+ TXT_CONNECTION_ESTABLISHED,
+ TXT_CANT_CONNECT,
+ TXT_CONNECTION_LOST,
+ TXT_LAG_DISCONNECTED,
+ TXT_DISCONNECTED,
+ TXT_SERVER_QUIT,
+ TXT_SERVER_CHANGED,
+ TXT_UNKNOWN_SERVER_TAG,
+ TXT_NO_CONNECTED_SERVERS,
+ TXT_SERVER_LIST,
+ TXT_SERVER_LOOKUP_LIST,
+ TXT_SERVER_RECONNECT_LIST,
+ TXT_RECONNECT_REMOVED,
+ TXT_RECONNECT_NOT_FOUND,
+ TXT_SETUPSERVER_ADDED,
+ TXT_SETUPSERVER_REMOVED,
+ TXT_SETUPSERVER_NOT_FOUND,
+ TXT_YOUR_NICK,
+
+ TXT_FILL_3,
+
+ TXT_JOIN,
+ TXT_JOIN_EXTENDED,
+ TXT_JOIN_EXTENDED_ACCOUNT,
+ TXT_HOST_CHANGED,
+ TXT_LOGGED_OUT,
+ TXT_LOGGED_IN,
+ TXT_PART,
+ TXT_KICK,
+ TXT_QUIT,
+ TXT_QUIT_ONCE,
+ TXT_INVITE,
+ TXT_NOT_INVITED,
+ TXT_INVITE_OTHER,
+ TXT_NEW_TOPIC,
+ TXT_TOPIC_UNSET,
+ TXT_YOUR_NICK_CHANGED,
+ TXT_NICK_CHANGED,
+ TXT_NOTIFY_AWAY_CHANNEL,
+ TXT_NOTIFY_UNAWAY_CHANNEL,
+ TXT_TALKING_IN,
+ TXT_NOT_IN_CHANNELS,
+ TXT_CURRENT_CHANNEL,
+ TXT_NAMES,
+ TXT_NAMES_PREFIX,
+ TXT_NAMES_NICK_OP,
+ TXT_NAMES_NICK_HALFOP,
+ TXT_NAMES_NICK_VOICE,
+ TXT_NAMES_NICK,
+ TXT_ENDOFNAMES,
+ TXT_CHANLIST_HEADER,
+ TXT_CHANLIST_LINE,
+ TXT_CHANSETUP_NOT_FOUND,
+ TXT_CHANSETUP_ADDED,
+ TXT_CHANSETUP_REMOVED,
+ TXT_CHANSETUP_HEADER,
+ TXT_CHANSETUP_LINE,
+ TXT_CHANSETUP_FOOTER,
+
+ TXT_FILL_4,
+
+ TXT_OWN_MSG,
+ TXT_OWN_MSG_CHANNEL,
+ TXT_OWN_MSG_PRIVATE,
+ TXT_OWN_MSG_PRIVATE_QUERY,
+ TXT_PUBMSG_ME,
+ TXT_PUBMSG_ME_CHANNEL,
+ TXT_PUBMSG_HILIGHT,
+ TXT_PUBMSG_HILIGHT_CHANNEL,
+ TXT_PUBMSG,
+ TXT_PUBMSG_CHANNEL,
+ TXT_MSG_PRIVATE,
+ TXT_MSG_PRIVATE_QUERY,
+ TXT_NO_MSGS_GOT,
+ TXT_NO_MSGS_SENT,
+
+ TXT_FILL_5,
+
+ TXT_QUERY_START,
+ TXT_QUERY_STOP,
+ TXT_NO_QUERY,
+ TXT_QUERY_SERVER_CHANGED,
+
+ TXT_FILL_6,
+
+ TXT_HILIGHT_HEADER,
+ TXT_HILIGHT_LINE,
+ TXT_HILIGHT_FOOTER,
+ TXT_HILIGHT_NOT_FOUND,
+ TXT_HILIGHT_REMOVED,
+
+ TXT_FILL_7,
+
+ TXT_ALIAS_ADDED,
+ TXT_ALIAS_REMOVED,
+ TXT_ALIAS_NOT_FOUND,
+ TXT_ALIASLIST_HEADER,
+ TXT_ALIASLIST_LINE,
+ TXT_ALIASLIST_FOOTER,
+
+ TXT_FILL_8,
+
+ TXT_LOG_OPENED,
+ TXT_LOG_CLOSED,
+ TXT_LOG_CREATE_FAILED,
+ TXT_LOG_LOCKED,
+ TXT_LOG_NOT_OPEN,
+ TXT_LOG_STARTED,
+ TXT_LOG_STOPPED,
+ TXT_LOG_LIST_HEADER,
+ TXT_LOG_LIST,
+ TXT_LOG_LIST_FOOTER,
+ TXT_WINDOWLOG_FILE,
+ TXT_WINDOWLOG_FILE_LOGGING,
+ TXT_LOG_NO_AWAY_MSGS,
+ TXT_LOG_AWAY_MSGS,
+
+ TXT_FILL_9,
+
+ TXT_MODULE_HEADER,
+ TXT_MODULE_LINE,
+ TXT_MODULE_FOOTER,
+ TXT_MODULE_ALREADY_LOADED,
+ TXT_MODULE_NOT_LOADED,
+ TXT_MODULE_LOAD_ERROR,
+ TXT_MODULE_VERSION_MISMATCH,
+ TXT_MODULE_INVALID,
+ TXT_MODULE_LOADED,
+ TXT_MODULE_UNLOADED,
+
+ TXT_FILL_10,
+
+ TXT_COMMAND_UNKNOWN,
+ TXT_COMMAND_AMBIGUOUS,
+ TXT_OPTION_UNKNOWN,
+ TXT_OPTION_AMBIGUOUS,
+ TXT_OPTION_MISSING_ARG,
+ TXT_NOT_ENOUGH_PARAMS,
+ TXT_NOT_CONNECTED,
+ TXT_NOT_JOINED,
+ TXT_CHAN_NOT_FOUND,
+ TXT_CHAN_NOT_SYNCED,
+ TXT_ILLEGAL_PROTO,
+ TXT_NOT_GOOD_IDEA,
+ TXT_INVALID_NUMBER,
+ TXT_INVALID_TIME,
+ TXT_INVALID_LEVEL,
+ TXT_INVALID_SIZE,
+ TXT_INVALID_CHARSET,
+ TXT_INVALID_CHOICE,
+ TXT_EVAL_MAX_RECURSE,
+ TXT_PROGRAM_NOT_FOUND,
+ TXT_NO_SERVER_DEFINED,
+
+ TXT_FILL_11,
+
+ TXT_THEME_SAVED,
+ TXT_THEME_SAVE_FAILED,
+ TXT_THEME_NOT_FOUND,
+ TXT_THEME_CHANGED,
+ TXT_WINDOW_THEME,
+ TXT_WINDOW_THEME_DEFAULT,
+ TXT_WINDOW_THEME_CHANGED,
+ TXT_WINDOW_THEME_REMOVED,
+ TXT_FORMAT_TITLE,
+ TXT_FORMAT_SUBTITLE,
+ TXT_FORMAT_ITEM,
+
+ TXT_FILL_12,
+
+ TXT_IGNORED,
+ TXT_IGNORED_OPTIONS,
+ TXT_UNIGNORED,
+ TXT_IGNORE_NOT_FOUND,
+ TXT_IGNORE_NO_IGNORES,
+ TXT_IGNORE_HEADER,
+ TXT_IGNORE_LINE,
+ TXT_IGNORE_FOOTER,
+
+ TXT_FILL_13,
+
+ TXT_NOT_CHANNEL_OR_QUERY,
+ TXT_CONVERSION_ADDED,
+ TXT_CONVERSION_REMOVED,
+ TXT_CONVERSION_NOT_FOUND,
+ TXT_CONVERSION_NO_TRANSLITS,
+ TXT_RECODE_HEADER,
+ TXT_RECODE_LINE,
+
+ TXT_FILL_14,
+
+ TXT_UNKNOWN_CHAT_PROTOCOL,
+ TXT_UNKNOWN_CHATNET,
+ TXT_NOT_TOGGLE,
+ TXT_PERL_ERROR,
+ TXT_BIND_HEADER,
+ TXT_BIND_LIST,
+ TXT_BIND_COMMAND_LIST,
+ TXT_BIND_FOOTER,
+ TXT_BIND_UNKNOWN_ID,
+ TXT_CONFIG_SAVED,
+ TXT_CONFIG_RELOADED,
+ TXT_CONFIG_MODIFIED,
+ TXT_GLIB_ERROR,
+ TXT_OVERWRITE_CONFIG,
+ TXT_SET_TITLE,
+ TXT_SET_ITEM,
+ TXT_SET_UNKNOWN,
+ TXT_SET_NOT_BOOLEAN,
+ TXT_NO_COMPLETIONS,
+ TXT_COMPLETION_REMOVED,
+ TXT_COMPLETION_HEADER,
+ TXT_COMPLETION_LINE,
+ TXT_COMPLETION_FOOTER,
+ TXT_CAPSICUM_ENABLED,
+ TXT_CAPSICUM_DISABLED,
+ TXT_CAPSICUM_FAILED,
+
+ TLS_FILL_15,
+
+ TXT_TLS_EPHEMERAL_KEY,
+ TXT_TLS_EPHEMERAL_KEY_UNAVAILBLE,
+ TXT_TLS_PUBKEY,
+ TXT_TLS_CERT_HEADER,
+ TXT_TLS_CERT_SUBJECT,
+ TXT_TLS_CERT_ISSUER,
+ TXT_TLS_PUBKEY_FINGERPRINT,
+ TXT_TLS_CERT_FINGERPRINT,
+ TXT_TLS_PROTOCOL_VERSION
+};
+
+extern FORMAT_REC fecommon_core_formats[];
diff --git a/src/fe-common/core/module.h b/src/fe-common/core/module.h
new file mode 100644
index 0000000..e629dc5
--- /dev/null
+++ b/src/fe-common/core/module.h
@@ -0,0 +1,35 @@
+#include <irssi/src/common.h>
+
+#define MODULE_NAME "fe-common/core"
+
+#include <irssi/src/core/utf8.h>
+typedef struct {
+ time_t time;
+ char *nick;
+
+ /* channel specific msg to/from me - this is actually a reference
+ count. it begins from `completion_keep_publics' and is decreased
+ every time some nick is added to lastmsgs list.
+
+ this is because of how the nick completion works. the same nick
+ is never in the lastmsgs list twice, but every time it's used
+ it's just moved to the beginning of the list. if this would be
+ just a boolean value the own-status would never be removed
+ from the nick if it didn't keep quiet for long enough.
+
+ so, the own-status is rememberd only for the last
+ `completion_keep_publics' lines */
+ int own;
+} LAST_MSG_REC;
+
+typedef struct {
+ /* /MSG completion: */
+ GSList *lastmsgs; /* list of nicks who sent you msg or
+ to who you send msg */
+} MODULE_SERVER_REC;
+
+typedef struct {
+ /* nick completion: */
+ GSList *lastmsgs; /* list of nicks who sent latest msgs and
+ list of nicks who you sent msgs to */
+} MODULE_CHANNEL_REC;
diff --git a/src/fe-common/core/printtext.c b/src/fe-common/core/printtext.c
new file mode 100644
index 0000000..1865268
--- /dev/null
+++ b/src/fe-common/core/printtext.c
@@ -0,0 +1,566 @@
+/*
+ printtext.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/core/module-formats.h>
+#include <irssi/src/core/modules.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/servers.h>
+
+#include <irssi/src/fe-common/core/themes.h>
+#include <irssi/src/fe-common/core/fe-windows.h>
+#include <irssi/src/fe-common/core/printtext.h>
+
+static int beep_msg_level, beep_msg_level_ignore, beep_when_away, beep_when_window_active;
+
+static int signal_gui_print_text_finished;
+static int signal_print_starting;
+static int signal_print_text;
+static int signal_print_format;
+static int signal_print_noformat;
+static int signal_window_hilight_check;
+
+static int sending_print_starting;
+
+static void print_line(TEXT_DEST_REC *dest, const char *text, int formatted);
+
+void printformat_module_dest_args(const char *module, TEXT_DEST_REC *dest,
+ int formatnum, va_list va)
+{
+ char *arglist[MAX_FORMAT_PARAMS];
+ char buffer[DEFAULT_FORMAT_ARGLIST_SIZE];
+ FORMAT_REC *formats;
+
+ formats = g_hash_table_lookup(default_formats, module);
+ format_read_arglist(va, &formats[formatnum],
+ arglist, sizeof(arglist)/sizeof(char *),
+ buffer, sizeof(buffer));
+
+ printformat_module_dest_charargs(module, dest, formatnum, arglist);
+}
+
+void printformat_module_dest_charargs(const char *module, TEXT_DEST_REC *dest,
+ int formatnum, char **arglist)
+{
+ THEME_REC *theme;
+
+ theme = window_get_theme(dest->window);
+
+ if (!sending_print_starting) {
+ sending_print_starting = TRUE;
+ signal_emit_id(signal_print_starting, 1, dest);
+ sending_print_starting = FALSE;
+ }
+
+ signal_emit_id(signal_print_format, 5, theme, module,
+ dest, GINT_TO_POINTER(formatnum), arglist);
+}
+
+void printformat_module_dest(const char *module, TEXT_DEST_REC *dest,
+ int formatnum, ...)
+{
+ va_list va;
+
+ va_start(va, formatnum);
+ printformat_module_dest_args(module, dest, formatnum, va);
+ va_end(va);
+}
+
+void printformat_module_args(const char *module, void *server,
+ const char *target, int level,
+ int formatnum, va_list va)
+{
+ TEXT_DEST_REC dest;
+
+ format_create_dest(&dest, server, target, level, NULL);
+ printformat_module_dest_args(module, &dest, formatnum, va);
+}
+
+void printformat_module(const char *module, void *server, const char *target,
+ int level, int formatnum, ...)
+{
+ va_list va;
+
+ va_start(va, formatnum);
+ printformat_module_args(module, server, target, level, formatnum, va);
+ va_end(va);
+}
+
+void printformat_module_window_args(const char *module, WINDOW_REC *window,
+ int level, int formatnum, va_list va)
+{
+ TEXT_DEST_REC dest;
+
+ format_create_dest(&dest, NULL, NULL, level, window);
+ printformat_module_dest_args(module, &dest, formatnum, va);
+}
+
+void printformat_module_window(const char *module, WINDOW_REC *window,
+ int level, int formatnum, ...)
+{
+ va_list va;
+
+ va_start(va, formatnum);
+ printformat_module_window_args(module, window, level, formatnum, va);
+ va_end(va);
+}
+
+void printformat_module_gui_args(const char *module, int formatnum, va_list va)
+{
+ TEXT_DEST_REC dest;
+ char *arglist[MAX_FORMAT_PARAMS];
+ char buffer[DEFAULT_FORMAT_ARGLIST_SIZE];
+ FORMAT_REC *formats;
+ char *str;
+
+ g_return_if_fail(module != NULL);
+
+ memset(&dest, 0, sizeof(dest));
+
+ formats = g_hash_table_lookup(default_formats, module);
+ format_read_arglist(va, &formats[formatnum],
+ arglist, sizeof(arglist)/sizeof(char *),
+ buffer, sizeof(buffer));
+
+ str = format_get_text_theme_charargs(window_get_theme(dest.window),
+ module, &dest,
+ formatnum, arglist);
+ if (*str != '\0') format_send_to_gui(&dest, str);
+ g_free(str);
+}
+
+void printformat_module_gui(const char *module, int formatnum, ...)
+{
+ va_list va;
+
+ va_start(va, formatnum);
+ printformat_module_gui_args(module, formatnum, va);
+ va_end(va);
+}
+
+/* append string to `out', expand newlines. */
+static void printtext_append_str(TEXT_DEST_REC *dest, GString *out,
+ const char *str)
+{
+ while (*str != '\0') {
+ if (*str != '\n')
+ g_string_append_c(out, *str);
+ else {
+ print_line(dest, out->str, 0);
+ g_string_truncate(out, 0);
+ }
+ str++;
+ }
+}
+
+static char *printtext_get_args(TEXT_DEST_REC *dest, const char *str,
+ va_list va)
+{
+ GString *out;
+ char *ret;
+ int adv;
+
+ out = g_string_new(NULL);
+ for (; *str != '\0'; str++) {
+ if (*str != '%') {
+ g_string_append_c(out, *str);
+ continue;
+ }
+
+ if (*++str == '\0')
+ break;
+
+ /* standard parameters */
+ switch (*str) {
+ case 's': {
+ char *s = (char *) va_arg(va, char *);
+ if (s && *s) printtext_append_str(dest, out, s);
+ break;
+ }
+ case 'd': {
+ int d = (int) va_arg(va, int);
+ g_string_append_printf(out, "%d", d);
+ break;
+ }
+ case 'f': {
+ double f = (double) va_arg(va, double);
+ g_string_append_printf(out, "%0.2f", f);
+ break;
+ }
+ case 'u': {
+ unsigned int d =
+ (unsigned int) va_arg(va, unsigned int);
+ g_string_append_printf(out, "%u", d);
+ break;
+ }
+ case 'l': {
+ long d = (long) va_arg(va, long);
+
+ if (*++str != 'd' && *str != 'u') {
+ g_string_append_printf(out, "%ld", d);
+ str--;
+ } else {
+ if (*str == 'd')
+ g_string_append_printf(out, "%ld", d);
+ else
+ g_string_append_printf(out, "%lu", d);
+ }
+ break;
+ }
+ default:
+ adv = format_expand_styles(out, &str, &dest->flags);
+ if (!adv) {
+ g_string_append_c(out, '%');
+ g_string_append_c(out, *str);
+ } else {
+ str += adv - 1;
+ }
+ break;
+ }
+ }
+
+ ret = out->str;
+ g_string_free(out, FALSE);
+ return ret;
+}
+
+static char *printtext_expand_formats(const char *str, int *flags)
+{
+ GString *out;
+ char *ret;
+ int adv;
+
+ out = g_string_new(NULL);
+ for (; *str != '\0'; str++) {
+ if (*str != '%') {
+ g_string_append_c(out, *str);
+ continue;
+ }
+
+ if (*++str == '\0')
+ break;
+
+ adv = format_expand_styles(out, &str, flags);
+ if (!adv) {
+ g_string_append_c(out, '%');
+ g_string_append_c(out, *str);
+ } else {
+ str += adv - 1;
+ }
+ }
+
+ ret = out->str;
+ g_string_free(out, FALSE);
+ return ret;
+}
+
+static void printtext_dest_args(TEXT_DEST_REC *dest, const char *text, va_list va)
+{
+ char *str;
+
+ if (!sending_print_starting) {
+ sending_print_starting = TRUE;
+ signal_emit_id(signal_print_starting, 1, dest);
+ sending_print_starting = FALSE;
+ }
+
+ str = printtext_get_args(dest, text, va);
+ print_line(dest, str, 0);
+ g_free(str);
+}
+
+void printtext_dest(TEXT_DEST_REC *dest, const char *text, ...)
+{
+ va_list va;
+
+ va_start(va, text);
+ printtext_dest_args(dest, text, va);
+ va_end(va);
+}
+
+/* Write text to target - convert color codes */
+void printtext(void *server, const char *target, int level, const char *text, ...)
+{
+ TEXT_DEST_REC dest;
+ va_list va;
+
+ g_return_if_fail(text != NULL);
+
+ format_create_dest(&dest, server, target, level, NULL);
+
+ va_start(va, text);
+ printtext_dest_args(&dest, text, va);
+ va_end(va);
+}
+
+/* Like printtext(), but don't handle %s etc. */
+void printtext_string(void *server, const char *target, int level, const char *text)
+{
+ TEXT_DEST_REC dest;
+ char *str;
+
+ g_return_if_fail(text != NULL);
+
+ format_create_dest(&dest, server, target, level, NULL);
+
+ if (!sending_print_starting) {
+ sending_print_starting = TRUE;
+ signal_emit_id(signal_print_starting, 1, &dest);
+ sending_print_starting = FALSE;
+ }
+
+ str = printtext_expand_formats(text, &dest.flags);
+ print_line(&dest, str, 0);
+ g_free(str);
+}
+
+/* Like printtext_window(), but don't handle %s etc. */
+void printtext_string_window(WINDOW_REC *window, int level, const char *text)
+{
+ TEXT_DEST_REC dest;
+ char *str;
+
+ g_return_if_fail(text != NULL);
+
+ format_create_dest(&dest, NULL, NULL, level,
+ window != NULL ? window : active_win);
+
+ if (!sending_print_starting) {
+ sending_print_starting = TRUE;
+ signal_emit_id(signal_print_starting, 1, &dest);
+ sending_print_starting = FALSE;
+ }
+
+ str = printtext_expand_formats(text, &dest.flags);
+ print_line(&dest, str, 0);
+ g_free(str);
+}
+
+void printtext_window(WINDOW_REC *window, int level, const char *text, ...)
+{
+ TEXT_DEST_REC dest;
+ va_list va;
+
+ g_return_if_fail(text != NULL);
+
+ format_create_dest(&dest, NULL, NULL, level,
+ window != NULL ? window : active_win);
+
+ va_start(va, text);
+ printtext_dest_args(&dest, text, va);
+ va_end(va);
+}
+
+void printtext_gui(const char *text)
+{
+ TEXT_DEST_REC dest;
+ char *str;
+
+ g_return_if_fail(text != NULL);
+
+ memset(&dest, 0, sizeof(dest));
+
+ str = printtext_expand_formats(text, &dest.flags);
+ format_send_to_gui(&dest, str);
+ g_free(str);
+}
+
+/* Like printtext_gui(), but don't expand % codes. */
+void printtext_gui_internal(const char *str)
+{
+ TEXT_DEST_REC dest;
+
+ g_return_if_fail(str != NULL);
+
+ memset(&dest, 0, sizeof(dest));
+
+ format_send_to_gui(&dest, str);
+}
+
+static void msg_beep_check(TEXT_DEST_REC *dest)
+{
+ if (dest->level != 0 && (dest->level & MSGLEVEL_NO_ACT) == 0 &&
+ (beep_msg_level & dest->level) &&
+ (beep_when_away || (dest->server != NULL &&
+ !dest->server->usermode_away)) &&
+ (beep_when_window_active || dest->window != active_win)) {
+ if (beep_msg_level_ignore & MSGLEVEL_HIDDEN) {
+ int cb_ignore = 0;
+ if (signal_emit_id(signal_window_hilight_check, 4, dest, NULL, NULL,
+ &cb_ignore),
+ cb_ignore) {
+ return;
+ }
+ } else if (beep_msg_level_ignore & dest->level) {
+ return;
+ }
+
+ signal_emit("beep", 0);
+ }
+}
+
+static void sig_print_text(TEXT_DEST_REC *dest, const char *text)
+{
+ THEME_REC *theme;
+ char *str, *tmp;
+
+ g_return_if_fail(dest != NULL);
+ g_return_if_fail(text != NULL);
+
+ if (dest->window == NULL) {
+ str = strip_codes(text);
+#ifndef SUPPRESS_PRINTF_FALLBACK
+ printf("## NO WINDOWS: %s\n", str);
+#endif
+ g_free(str);
+ return;
+ }
+
+ msg_beep_check(dest);
+
+ if ((dest->level & MSGLEVEL_NEVER) == 0)
+ dest->window->last_line = time(NULL);
+
+ /* add timestamp/server tag here - if it's done in print_line()
+ it would be written to log files too */
+ theme = window_get_theme(dest->window);
+ tmp = format_get_line_start(theme, dest, time(NULL));
+ str = !theme->info_eol ? format_add_linestart(text, tmp) :
+ format_add_lineend(text, tmp);
+
+ g_free_not_null(tmp);
+
+ format_send_to_gui(dest, str);
+ g_free(str);
+
+ signal_emit_id(signal_gui_print_text_finished, 2, dest->window, dest);
+}
+
+static void sig_print_format(THEME_REC *theme, const char *module, TEXT_DEST_REC *dest,
+ void *formatnump, char **arglist)
+{
+ int formatnum;
+ char *str;
+
+ formatnum = GPOINTER_TO_INT(formatnump);
+ str = format_get_text_theme_charargs(theme, module, dest, formatnum, arglist);
+ if (str != NULL && *str != '\0')
+ print_line(dest, str, 1);
+
+ g_free(str);
+}
+
+static void sig_print_noformat(TEXT_DEST_REC *dest, const char *text)
+{
+ THEME_REC *theme;
+ char *str, *tmp, *stripped;
+
+ theme = window_get_theme(dest->window);
+ tmp = format_get_level_tag(theme, dest);
+ str = !theme->info_eol ? format_add_linestart(text, tmp) : format_add_lineend(text, tmp);
+ g_free_not_null(tmp);
+
+ /* send both the formatted + stripped (for logging etc.) */
+ stripped = strip_codes(str);
+ signal_emit_id(signal_print_text, 3, dest, str, stripped);
+
+ g_free_and_null(dest->hilight_color);
+
+ g_free(str);
+ g_free(stripped);
+}
+
+static void print_line(TEXT_DEST_REC *dest, const char *text, int formatted)
+{
+ g_return_if_fail(dest != NULL);
+ g_return_if_fail(text != NULL);
+
+ if (!formatted)
+ signal_emit_id(signal_print_noformat, 2, dest, text);
+ else
+ sig_print_noformat(dest, text);
+}
+
+void printtext_multiline(void *server, const char *target, int level,
+ const char *format, const char *text)
+{
+ char **lines, **tmp;
+
+ g_return_if_fail(format != NULL);
+ g_return_if_fail(text != NULL);
+
+ lines = g_strsplit(text, "\n", -1);
+ for (tmp = lines; *tmp != NULL; tmp++)
+ printtext(NULL, NULL, level, format, *tmp);
+ g_strfreev(lines);
+}
+
+static void sig_gui_dialog(const char *type, const char *text)
+{
+ char *format;
+
+ if (g_ascii_strcasecmp(type, "warning") == 0)
+ format = "%_Warning:%_ %s";
+ else if (g_ascii_strcasecmp(type, "error") == 0)
+ format = "%_Error:%_ %s";
+ else
+ format = "%s";
+
+ printtext_multiline(NULL, NULL, MSGLEVEL_NEVER, format, text);
+}
+
+static void read_settings(void)
+{
+ beep_msg_level = settings_get_level("beep_msg_level");
+ beep_msg_level_ignore = settings_get_level_negative("beep_msg_level");
+ beep_when_away = settings_get_bool("beep_when_away");
+ beep_when_window_active = settings_get_bool("beep_when_window_active");
+}
+
+void printtext_init(void)
+{
+ sending_print_starting = FALSE;
+ signal_gui_print_text_finished = signal_get_uniq_id("gui print text finished");
+ signal_print_starting = signal_get_uniq_id("print starting");
+ signal_print_text = signal_get_uniq_id("print text");
+ signal_print_format = signal_get_uniq_id("print format");
+ signal_print_noformat = signal_get_uniq_id("print noformat");
+ signal_window_hilight_check = signal_get_uniq_id("window hilight check");
+
+ read_settings();
+ signal_add("print text", (SIGNAL_FUNC) sig_print_text);
+ signal_add("print format", (SIGNAL_FUNC) sig_print_format);
+ signal_add("print noformat", (SIGNAL_FUNC) sig_print_noformat);
+ signal_add("gui dialog", (SIGNAL_FUNC) sig_gui_dialog);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void printtext_deinit(void)
+{
+ signal_remove("print text", (SIGNAL_FUNC) sig_print_text);
+ signal_remove("print format", (SIGNAL_FUNC) sig_print_format);
+ signal_remove("print noformat", (SIGNAL_FUNC) sig_print_noformat);
+ signal_remove("gui dialog", (SIGNAL_FUNC) sig_gui_dialog);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
diff --git a/src/fe-common/core/printtext.h b/src/fe-common/core/printtext.h
new file mode 100644
index 0000000..c223a3e
--- /dev/null
+++ b/src/fe-common/core/printtext.h
@@ -0,0 +1,45 @@
+#ifndef IRSSI_FE_COMMON_CORE_PRINTTEXT_H
+#define IRSSI_FE_COMMON_CORE_PRINTTEXT_H
+
+#include <irssi/src/fe-common/core/fe-windows.h>
+#include <irssi/src/fe-common/core/formats.h>
+
+void printformat_module(const char *module, void *server, const char *target, int level, int formatnum, ...);
+void printformat_module_window(const char *module, WINDOW_REC *window, int level, int formatnum, ...);
+void printformat_module_dest(const char *module, TEXT_DEST_REC *dest, int formatnum, ...);
+
+void printformat_module_args(const char *module, void *server, const char *target, int level, int formatnum, va_list va);
+void printformat_module_window_args(const char *module, WINDOW_REC *window, int level, int formatnum, va_list va);
+void printformat_module_dest_args(const char *module, TEXT_DEST_REC *dest, int formatnum, va_list va);
+void printformat_module_dest_charargs(const char *module, TEXT_DEST_REC *dest, int formatnum, char **arglist);
+
+void printtext(void *server, const char *target, int level, const char *text, ...);
+void printtext_string(void *server, const char *target, int level, const char *text);
+void printtext_string_window(WINDOW_REC *window, int level, const char *text);
+void printtext_window(WINDOW_REC *window, int level, const char *text, ...);
+void printtext_multiline(void *server, const char *target, int level, const char *format, const char *text);
+void printtext_dest(TEXT_DEST_REC *dest, const char *text, ...);
+
+/* only GUI should call these - used for printing text to somewhere else
+ than windows */
+void printtext_gui(const char *text);
+void printtext_gui_internal(const char *str);
+void printformat_module_gui(const char *module, int formatnum, ...);
+void printformat_module_gui_args(const char *module, int formatnum, va_list va);
+
+void printtext_init(void);
+void printtext_deinit(void);
+
+/* printformat(...) = printformat_format(MODULE_NAME, ...)
+
+ Irssi requires a C99 pre-processor with __VA_ARGS__ support */
+# define printformat(server, target, level, formatnum, ...) \
+ printformat_module(MODULE_NAME, server, target, level, formatnum, ##__VA_ARGS__)
+# define printformat_window(window, level, formatnum, ...) \
+ printformat_module_window(MODULE_NAME, window, level, formatnum, ##__VA_ARGS__)
+# define printformat_dest(dest, formatnum, ...) \
+ printformat_module_dest(MODULE_NAME, dest, formatnum, ##__VA_ARGS__)
+# define printformat_gui(formatnum, ...) \
+ printformat_module_gui(MODULE_NAME, formatnum, ##__VA_ARGS__)
+
+#endif
diff --git a/src/fe-common/core/themes.c b/src/fe-common/core/themes.c
new file mode 100644
index 0000000..04f0047
--- /dev/null
+++ b/src/fe-common/core/themes.c
@@ -0,0 +1,1488 @@
+/*
+ themes.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/core/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/special-vars.h>
+#include <irssi/src/lib-config/iconfig.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/fe-common/core/themes.h>
+#include <irssi/src/fe-common/core/printtext.h>
+
+#include "default-theme.h"
+
+GSList *themes;
+THEME_REC *current_theme;
+GHashTable *default_formats;
+
+static int init_finished;
+static char *init_errors;
+static THEME_REC *internal_theme;
+
+static int theme_read(THEME_REC *theme, const char *path);
+
+THEME_REC *theme_create(const char *path, const char *name)
+{
+ THEME_REC *rec;
+
+ g_return_val_if_fail(path != NULL, NULL);
+ g_return_val_if_fail(name != NULL, NULL);
+
+ rec = g_new0(THEME_REC, 1);
+ rec->refcount = 1;
+ rec->path = g_strdup(path);
+ rec->name = g_strdup(name);
+ rec->abstracts = g_hash_table_new((GHashFunc) g_str_hash,
+ (GCompareFunc) g_str_equal);
+ rec->modules = g_hash_table_new((GHashFunc) i_istr_hash, (GCompareFunc) i_istr_equal);
+ themes = g_slist_append(themes, rec);
+ signal_emit("theme created", 1, rec);
+
+ return rec;
+}
+
+static void theme_abstract_destroy(char *key, char *value)
+{
+ g_free(key);
+ g_free(value);
+}
+
+static void theme_module_destroy(const char *key, MODULE_THEME_REC *rec)
+{
+ int n;
+
+ for (n = 0; n < rec->count; n++) {
+ g_free_not_null(rec->formats[n]);
+ g_free_not_null(rec->expanded_formats[n]);
+ }
+ g_free(rec->formats);
+ g_free(rec->expanded_formats);
+
+ g_free(rec->name);
+ g_free(rec);
+}
+
+static void theme_real_destroy(THEME_REC *rec)
+{
+ g_hash_table_foreach(rec->abstracts, (GHFunc) theme_abstract_destroy, NULL);
+ g_hash_table_destroy(rec->abstracts);
+ g_hash_table_foreach(rec->modules, (GHFunc) theme_module_destroy, NULL);
+ g_hash_table_destroy(rec->modules);
+
+ g_slist_foreach(rec->replace_values, (GFunc) g_free, NULL);
+ g_slist_free(rec->replace_values);
+
+ g_free(rec->path);
+ g_free(rec->name);
+ g_free(rec);
+}
+
+static void theme_unref(THEME_REC *rec)
+{
+ if (--rec->refcount == 0)
+ theme_real_destroy(rec);
+}
+
+void theme_destroy(THEME_REC *rec)
+{
+ themes = g_slist_remove(themes, rec);
+ signal_emit("theme destroyed", 1, rec);
+
+ theme_unref(rec);
+}
+
+static char *theme_replace_expand(THEME_REC *theme, int index,
+ theme_rm_col default_fg, theme_rm_col default_bg,
+ theme_rm_col *last_fg, theme_rm_col *last_bg,
+ char chr, int flags)
+{
+ GSList *rec;
+ char *ret, *abstract, data[2];
+
+ rec = g_slist_nth(theme->replace_values, index);
+ g_return_val_if_fail(rec != NULL, NULL);
+
+ data[0] = chr; data[1] = '\0';
+
+ abstract = rec->data;
+ abstract = theme_format_expand_data(theme, (const char **) &abstract,
+ default_fg, default_bg,
+ last_fg, last_bg, (flags | EXPAND_FLAG_IGNORE_REPLACES));
+ ret = parse_special_string(abstract, NULL, NULL, data, NULL,
+ PARSE_FLAG_ONLY_ARGS);
+ g_free(abstract);
+ return ret;
+}
+
+static const char *fgcolorformats = "nkrgybmpcwKRGYBMPCW";
+static const char *bgcolorformats = "n01234567";
+
+#define IS_FGCOLOR_FORMAT(c) \
+ ((c) != '\0' && strchr(fgcolorformats, c) != NULL)
+#define IS_BGCOLOR_FORMAT(c) \
+ ((c) != '\0' && strchr(bgcolorformats, c) != NULL)
+
+/* append "variable" part in $variable, ie. not the contents of the variable */
+static void theme_format_append_variable(GString *str, const char **format)
+{
+ const char *orig;
+ char *value, *args[1] = { NULL };
+ int free_ret;
+
+ orig = *format;
+ (*format)++;
+
+ value = parse_special((char **) format, NULL, NULL,
+ args, &free_ret, NULL, PARSE_FLAG_ONLY_ARGS);
+ if (free_ret) g_free(value);
+
+ if (**format != '\0')
+ (*format)++;
+
+ /* append the variable name */
+ value = g_strndup(orig, (int) (*format-orig));
+ g_string_append(str, value);
+ g_free(value);
+}
+
+static inline int chr_is_valid_rgb(const char format[])
+{
+ int tmp;
+ for (tmp = 1; tmp < 7; ++tmp) {
+ if (!isxdigit(format[tmp]))
+ return tmp;
+ }
+ return 0;
+}
+
+static inline int chr_is_valid_ext(const char format[])
+{
+ if (format[1] < '0' || format[1] > '7')
+ return 1;
+
+ if (format[1] == '7') {
+ if (!isalpha(format[2]) || format[2] == 'y' || format[2] == 'Y'
+ || format[2] =='z' || format[2] == 'Z')
+ return 2;
+ } else if (format[1] == '0') {
+ if (!isxdigit(format[2]))
+ return 2;
+ } else if (!isalnum(format[2]))
+ return 2;
+
+ return 0;
+}
+
+/* append next "item", either a character, $variable or %format */
+static void theme_format_append_next(THEME_REC *theme, GString *str,
+ const char **format,
+ theme_rm_col default_fg, theme_rm_col default_bg,
+ theme_rm_col *last_fg, theme_rm_col *last_bg,
+ int flags)
+{
+ int index;
+ unsigned char chr;
+ char *t;
+
+ chr = **format;
+ if ((chr == '$' || chr == '%') &&
+ (*format)[1] == '\0') {
+ /* last char, always append */
+ g_string_append_c(str, chr);
+ (*format)++;
+ return;
+ }
+
+ if (chr == '$') {
+ /* $variable .. we'll always need to skip this, since it
+ may contain characters that are in replace chars. */
+ theme_format_append_variable(str, format);
+ return;
+ }
+
+ if (**format == '%') {
+ /* format */
+ (*format)++;
+ if (**format != '{' && **format != '}') {
+ chr = **format;
+ if (**format == 'n') {
+ /* %n = change to default color */
+ g_string_append(str, "%n");
+
+ if (default_bg.m[0] != 'n') {
+ g_string_append_c(str, '%');
+ g_string_append(str, default_bg.m);
+ }
+ if (default_fg.m[0] != 'n') {
+ g_string_append_c(str, '%');
+ g_string_append(str, default_fg.m);
+ }
+
+ *last_fg = default_fg;
+ *last_bg = default_bg;
+ } else if (chr == 'z' || chr == 'Z') {
+ if (chr_is_valid_rgb(*format) == 0) {
+ t = chr == 'z' ? (*last_bg).m : (*last_fg).m;
+ strncpy(t, *format, 7);
+ t[7] = '\0';
+ g_string_append_c(str, '%');
+ g_string_append(str, t);
+ (*format)+=6;
+ } else {
+ g_string_append(str, "%%");
+ g_string_append_c(str, **format);
+ }
+ } else if (chr == 'x' || chr == 'X') {
+ if (chr_is_valid_ext(*format) == 0) {
+ t = chr == 'x' ? (*last_bg).m : (*last_fg).m;
+ strncpy(t, *format, 3);
+ t[3] = '\0';
+ g_string_append_c(str, '%');
+ g_string_append(str, t);
+ (*format)+=2;
+ } else {
+ g_string_append(str, "%%");
+ g_string_append_c(str, **format);
+ }
+ } else {
+ if (IS_FGCOLOR_FORMAT(chr)) {
+ (*last_fg).m[0] = chr;
+ (*last_fg).m[1] = '\0';
+ }
+ if (IS_BGCOLOR_FORMAT(chr)) {
+ (*last_bg).m[0] = chr;
+ (*last_bg).m[1] = '\0';
+ }
+ g_string_append_c(str, '%');
+ g_string_append_c(str, chr);
+ }
+ (*format)++;
+ return;
+ }
+
+ /* %{ or %} gives us { or } char - keep the % char
+ though to make sure {} isn't treated as abstract */
+ g_string_append_c(str, '%');
+ chr = **format;
+ }
+
+ index = (flags & EXPAND_FLAG_IGNORE_REPLACES) ? -1 :
+ theme->replace_keys[(int) (unsigned char) chr];
+ if (index == -1)
+ g_string_append_c(str, chr);
+ else {
+ char *value;
+
+ value = theme_replace_expand(theme, index,
+ default_fg, default_bg,
+ last_fg, last_bg, chr, flags);
+ g_string_append(str, value);
+ g_free(value);
+ }
+
+ (*format)++;
+}
+
+/* returns TRUE if data is empty, or the data is a $variable which is empty */
+static int data_is_empty(const char **data)
+{
+ /* since we don't know the real argument list, assume there's always
+ an argument in them */
+ static char *arglist[] = {
+ "x", "x", "x", "x", "x", "x","x", "x", "x", "x",
+ NULL
+ };
+ SERVER_REC *server;
+ const char *p;
+ char *ret;
+ int free_ret, empty;
+
+ p = *data;
+ while (*p == ' ') p++;
+
+ if (*p == '}') {
+ /* empty */
+ *data = p+1;
+ return TRUE;
+ }
+
+ if (*p != '$') {
+ /* not empty */
+ return FALSE;
+ }
+
+ /* variable - check if it's empty */
+ p++;
+
+ server = active_win == NULL ? NULL :
+ active_win->active_server != NULL ?
+ active_win->active_server : active_win->connect_server;
+
+ ret = parse_special((char **) &p, server,
+ active_win == NULL ? NULL : active_win->active,
+ arglist, &free_ret, NULL, 0);
+ p++;
+
+ while (*p == ' ') p++;
+ empty = *p == '}' && (ret == NULL || *ret == '\0');
+ if (free_ret) g_free(ret);
+
+ if (empty) {
+ /* empty */
+ *data = p+1;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* return "data" from {abstract data} string */
+char *theme_format_expand_get(THEME_REC *theme, const char **format)
+{
+ GString *str;
+ char *ret;
+ theme_rm_col dummy, reset;
+ int braces = 1; /* we start with one brace opened */
+ dummy.m[0] = '\0';
+ strcpy(reset.m, "n");
+
+ str = g_string_new(NULL);
+ while (**format != '\0' && braces != 0) {
+ if (**format == '{')
+ braces++;
+ else if (**format == '}')
+ braces--;
+ else if ((braces > 1) && (**format == ' ')) {
+ g_string_append(str, "\\x20");
+ (*format)++;
+ continue;
+ } else {
+ theme_format_append_next(theme, str, format,
+ reset, reset,
+ &dummy, &dummy,
+ EXPAND_FLAG_IGNORE_REPLACES);
+ continue;
+ }
+
+ if (braces == 0) {
+ (*format)++;
+ break;
+ }
+
+ g_string_append_c(str, **format);
+ (*format)++;
+ }
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+static char *theme_format_expand_data_rec(THEME_REC *theme, const char **format,
+ theme_rm_col default_fg, theme_rm_col default_bg,
+ theme_rm_col *save_last_fg, theme_rm_col *save_last_bg,
+ int flags, GTree *block_list);
+
+/* expand a single {abstract ...data... } */
+static char *theme_format_expand_abstract(THEME_REC *theme, const char **formatp,
+ theme_rm_col *last_fg, theme_rm_col *last_bg, int flags,
+ GTree *block_list)
+{
+ GString *str;
+ const char *p, *format;
+ char *abstract, *data, *ret, *blocking;
+ theme_rm_col default_fg, default_bg;
+ int len;
+
+ format = *formatp;
+ default_fg = *last_fg;
+ default_bg = *last_bg;
+
+ /* get abstract name first */
+ p = format;
+ while (*p != '\0' && *p != ' ' &&
+ *p != '{' && *p != '}') p++;
+ if (*p == '\0' || p == format)
+ return NULL; /* error */
+
+ len = (int) (p-format);
+ abstract = g_strndup(format, len);
+
+ /* skip the following space, if there's any more spaces they're
+ treated as arguments */
+ if (*p == ' ') {
+ len++;
+ if ((flags & EXPAND_FLAG_IGNORE_EMPTY) && data_is_empty(&p)) {
+ *formatp = p;
+ g_free(abstract);
+ return NULL;
+ }
+ }
+ *formatp = format+len;
+
+ if (block_list == NULL) {
+ block_list = g_tree_new_full((GCompareDataFunc) g_strcmp0, NULL, g_free, NULL);
+ } else {
+ g_tree_ref(block_list);
+ }
+
+ /* get the abstract data */
+ data = g_hash_table_lookup(theme->abstracts, abstract);
+ if (data == NULL || g_tree_lookup(block_list, abstract) != NULL) {
+ /* unknown abstract, just display the data */
+ data = "$0-";
+ g_free(abstract);
+ blocking = NULL;
+ } else {
+ blocking = abstract;
+ g_tree_insert(block_list, blocking, blocking);
+ }
+ abstract = g_strdup(data);
+
+ /* we'll need to get the data part. it may contain
+ more abstracts, they are _NOT_ expanded. */
+ data = theme_format_expand_get(theme, formatp);
+ len = strlen(data);
+
+ if (len > 1 && i_isdigit(data[len-1]) && data[len-2] == '$') {
+ /* ends with $<digit> .. this breaks things if next
+ character is digit or '-' */
+ char digit, *tmp;
+
+ tmp = data;
+ digit = tmp[len-1];
+ tmp[len-1] = '\0';
+
+ data = g_strdup_printf("%s{%c}", tmp, digit);
+ g_free(tmp);
+ }
+
+ ret = parse_special_string(abstract, NULL, NULL, data, NULL,
+ PARSE_FLAG_ONLY_ARGS);
+ g_free(abstract);
+ g_free(data);
+ str = g_string_new(NULL);
+ p = ret;
+ while (*p != '\0') {
+ if (*p == '\\' && p[1] != '\0') {
+ int chr;
+ p++;
+ chr = expand_escape(&p);
+ g_string_append_c(str, chr != -1 ? chr : *p);
+ } else
+ g_string_append_c(str, *p);
+ p++;
+ }
+ g_free(ret);
+ abstract = str->str;
+ g_string_free(str, FALSE);
+
+ /* abstract may itself contain abstracts or replaces */
+ p = abstract;
+ ret = theme_format_expand_data_rec(theme, &p, default_fg, default_bg, last_fg, last_bg,
+ flags | EXPAND_FLAG_LASTCOLOR_ARG, block_list);
+ g_free(abstract);
+ if (blocking != NULL) {
+ g_tree_remove(block_list, blocking);
+ }
+ g_tree_unref(block_list);
+ return ret;
+}
+
+static char *theme_format_expand_data_rec(THEME_REC *theme, const char **format,
+ theme_rm_col default_fg, theme_rm_col default_bg,
+ theme_rm_col *save_last_fg, theme_rm_col *save_last_bg,
+ int flags, GTree *block_list)
+{
+ GString *str;
+ char *ret, *abstract;
+ theme_rm_col last_fg, last_bg;
+ int recurse_flags;
+
+ last_fg = default_fg;
+ last_bg = default_bg;
+ recurse_flags = flags & EXPAND_FLAG_RECURSIVE_MASK;
+
+ str = g_string_new(NULL);
+ while (**format != '\0') {
+ if ((flags & EXPAND_FLAG_ROOT) == 0 && **format == '}') {
+ /* ignore } if we're expanding original string */
+ (*format)++;
+ break;
+ }
+
+ if (**format != '{') {
+ if ((flags & EXPAND_FLAG_LASTCOLOR_ARG) &&
+ **format == '$' && (*format)[1] == '0') {
+ /* save the color before $0 ..
+ this is for the %n replacing */
+ if (save_last_fg != NULL) {
+ *save_last_fg = last_fg;
+ save_last_fg = NULL;
+ }
+ if (save_last_bg != NULL) {
+ *save_last_bg = last_bg;
+ save_last_bg = NULL;
+ }
+ }
+
+ theme_format_append_next(theme, str, format,
+ default_fg, default_bg,
+ &last_fg, &last_bg,
+ recurse_flags);
+ continue;
+ }
+
+ (*format)++;
+ if (**format == '\0' || **format == '}')
+ break; /* error */
+
+ /* get a single {...} */
+ abstract = theme_format_expand_abstract(theme, format, &last_fg, &last_bg,
+ recurse_flags, block_list);
+ if (abstract != NULL) {
+ g_string_append(str, abstract);
+ g_free(abstract);
+ }
+ }
+
+ /* save the last color */
+ if (save_last_fg != NULL)
+ *save_last_fg = last_fg;
+ if (save_last_bg != NULL)
+ *save_last_bg = last_bg;
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+/* expand the data part in {abstract data} */
+char *theme_format_expand_data(THEME_REC *theme, const char **format, theme_rm_col default_fg,
+ theme_rm_col default_bg, theme_rm_col *save_last_fg,
+ theme_rm_col *save_last_bg, int flags)
+{
+ return theme_format_expand_data_rec(theme, format, default_fg, default_bg, save_last_bg,
+ save_last_bg, flags, NULL);
+}
+
+#define IS_OLD_FORMAT(code, last_fg, last_bg) \
+ (((code) == 'n' && (last_fg) == 'n' && (last_bg) == 'n') || \
+ ((code) != 'n' && ((code) == (last_fg) || (code) == (last_bg))))
+
+static char *theme_format_compress_colors(THEME_REC *theme, const char *format)
+{
+ GString *str;
+ char *ret;
+ char last_fg, last_bg;
+
+ str = g_string_new(NULL);
+
+ last_fg = last_bg = '\0';
+ while (*format != '\0') {
+ if (*format == '$') {
+ /* $variable, skrip it entirely */
+ theme_format_append_variable(str, &format);
+ last_fg = last_bg = '\0';
+ } else if (*format != '%') {
+ /* a normal character */
+ g_string_append_c(str, *format);
+ format++;
+ } else if (format[1] != '\0') {
+ /* %format */
+ format++;
+ if (IS_OLD_FORMAT(*format, last_fg, last_bg)) {
+ /* active color set again */
+ } else if (IS_FGCOLOR_FORMAT(*format) &&
+ format[1] == '%' &&
+ IS_FGCOLOR_FORMAT(format[2]) &&
+ (*format != 'n' || format[2] == 'n')) {
+ /* two fg colors in a row. bg colors are
+ so rare that we don't bother checking
+ them */
+ } else {
+ /* some format, add it */
+ g_string_append_c(str, '%');
+ g_string_append_c(str, *format);
+
+ if (IS_FGCOLOR_FORMAT(*format))
+ last_fg = *format;
+ else if (*format == 'Z' || *format == 'X')
+ last_fg = '\0';
+ if (IS_BGCOLOR_FORMAT(*format))
+ last_bg = *format;
+ else if (*format == 'z' || *format == 'x')
+ last_bg = '\0';
+ }
+ format++;
+ } else {
+ /* % at end of string */
+ format++;
+ g_string_append_c(str, '%');
+ g_string_append_c(str, '%');
+ }
+ }
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+char *theme_format_expand(THEME_REC *theme, const char *format)
+{
+ char *data, *ret;
+ theme_rm_col reset;
+ strcpy(reset.m, "n");
+
+ g_return_val_if_fail(theme != NULL, NULL);
+ g_return_val_if_fail(format != NULL, NULL);
+
+ data = theme_format_expand_data(theme, &format, reset, reset, NULL, NULL,
+ EXPAND_FLAG_ROOT);
+ ret = theme_format_compress_colors(theme, data);
+ g_free(data);
+ return ret;
+}
+
+static MODULE_THEME_REC *theme_module_create(THEME_REC *theme, const char *module)
+{
+ MODULE_THEME_REC *rec;
+ FORMAT_REC *formats;
+
+ rec = g_hash_table_lookup(theme->modules, module);
+ if (rec != NULL) return rec;
+
+ formats = g_hash_table_lookup(default_formats, module);
+ g_return_val_if_fail(formats != NULL, NULL);
+
+ rec = g_new0(MODULE_THEME_REC, 1);
+ rec->name = g_strdup(module);
+
+ for (rec->count = 0; formats[rec->count].def != NULL; rec->count++) ;
+ rec->formats = g_new0(char *, rec->count);
+ rec->expanded_formats = g_new0(char *, rec->count);
+
+ g_hash_table_insert(theme->modules, rec->name, rec);
+ return rec;
+}
+
+static void theme_read_replaces(CONFIG_REC *config, THEME_REC *theme)
+{
+ GSList *tmp;
+ CONFIG_NODE *node;
+ const char *p;
+ int index;
+
+ /* reset replace keys */
+ for (index = 0; index < 256; index++)
+ theme->replace_keys[index] = -1;
+ index = 0;
+
+ node = config_node_traverse(config, "replaces", FALSE);
+ if (node == NULL || node->type != NODE_TYPE_BLOCK) return;
+
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+ node = tmp->data;
+
+ if (node->key != NULL && node->value != NULL) {
+ for (p = node->key; *p != '\0'; p++)
+ theme->replace_keys[(int) (unsigned char) *p] = index;
+
+ theme->replace_values =
+ g_slist_append(theme->replace_values,
+ g_strdup(node->value));
+ index++;
+ }
+ }
+}
+
+static void theme_read_abstracts(CONFIG_REC *config, THEME_REC *theme)
+{
+ GSList *tmp;
+ CONFIG_NODE *node;
+ gpointer oldkey, oldvalue;
+
+ node = config_node_traverse(config, "abstracts", FALSE);
+ if (node == NULL || node->type != NODE_TYPE_BLOCK) return;
+
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+ node = tmp->data;
+
+ if (node->key == NULL || node->value == NULL)
+ continue;
+
+ if (g_hash_table_lookup_extended(theme->abstracts, node->key,
+ &oldkey, &oldvalue)) {
+ /* new values override old ones */
+ g_hash_table_remove(theme->abstracts, oldkey);
+ g_free(oldkey);
+ g_free(oldvalue);
+ }
+
+ g_hash_table_insert(theme->abstracts, g_strdup(node->key),
+ g_strdup(node->value));
+ }
+}
+
+static void theme_set_format(THEME_REC *theme, MODULE_THEME_REC *rec,
+ const char *module,
+ const char *key, const char *value)
+{
+ int num;
+
+ num = format_find_tag(module, key);
+ if (num != -1) {
+ rec->formats[num] = g_strdup(value);
+ rec->expanded_formats[num] = theme_format_expand(theme, value);
+ }
+}
+
+static void theme_read_formats(THEME_REC *theme, const char *module,
+ CONFIG_REC *config, MODULE_THEME_REC *rec)
+{
+ CONFIG_NODE *node;
+ GSList *tmp;
+
+ node = config_node_traverse(config, "formats", FALSE);
+ if (node == NULL) return;
+ node = config_node_section(config, node, module, -1);
+ if (node == NULL) return;
+
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+ node = tmp->data;
+
+ if (node->key != NULL && node->value != NULL) {
+ theme_set_format(theme, rec, module,
+ node->key, node->value);
+ }
+ }
+}
+
+static void theme_init_module(THEME_REC *theme, const char *module,
+ CONFIG_REC *config)
+{
+ MODULE_THEME_REC *rec;
+ FORMAT_REC *formats;
+ int n;
+
+ formats = g_hash_table_lookup(default_formats, module);
+ g_return_if_fail(formats != NULL);
+
+ rec = theme_module_create(theme, module);
+
+ if (config != NULL)
+ theme_read_formats(theme, module, config, rec);
+
+ /* expand the remaining formats */
+ for (n = 0; n < rec->count; n++) {
+ if (rec->expanded_formats[n] == NULL) {
+ rec->expanded_formats[n] =
+ theme_format_expand(theme, formats[n].def);
+ }
+ }
+}
+
+static void sig_print_errors(void)
+{
+ init_finished = TRUE;
+
+ if (init_errors != NULL) {
+ signal_emit("gui dialog", 2, "error", init_errors);
+ g_free(init_errors);
+ }
+}
+
+static void theme_read_module(THEME_REC *theme, const char *module)
+{
+ CONFIG_REC *config;
+
+ config = config_open(theme->path, -1);
+ if (config != NULL)
+ config_parse(config);
+
+ theme_init_module(theme, module, config);
+
+ if (config != NULL) config_close(config);
+}
+
+static void themes_read_module(const char *module)
+{
+ g_slist_foreach(themes, (GFunc) theme_read_module, (void *) module);
+}
+
+static void theme_remove_module(THEME_REC *theme, const char *module)
+{
+ MODULE_THEME_REC *rec;
+
+ rec = g_hash_table_lookup(theme->modules, module);
+ if (rec == NULL) return;
+
+ g_hash_table_remove(theme->modules, module);
+ theme_module_destroy(module, rec);
+}
+
+static void themes_remove_module(const char *module)
+{
+ g_slist_foreach(themes, (GFunc) theme_remove_module, (void *) module);
+}
+
+void theme_register_module(const char *module, FORMAT_REC *formats)
+{
+ if (g_hash_table_lookup(default_formats, module) != NULL)
+ return;
+
+ g_hash_table_insert(default_formats, g_strdup(module), formats);
+ themes_read_module(module);
+}
+
+void theme_unregister_module(const char *module)
+{
+ gpointer key, value;
+
+ if (default_formats == NULL)
+ return; /* already uninitialized */
+
+ if (!g_hash_table_lookup_extended(default_formats, module, &key, &value))
+ return;
+
+ g_hash_table_remove(default_formats, key);
+ g_free(key);
+
+ themes_remove_module(module);
+}
+
+void theme_set_default_abstract(const char *key, const char *value)
+{
+ gpointer oldkey, oldvalue;
+
+ if (g_hash_table_lookup_extended(internal_theme->abstracts, key,
+ &oldkey, &oldvalue)) {
+ /* new values override old ones */
+ g_hash_table_remove(internal_theme->abstracts, oldkey);
+ g_free(oldkey);
+ g_free(oldvalue);
+ }
+
+ g_hash_table_insert(internal_theme->abstracts,
+ g_strdup(key), g_strdup(value));
+}
+
+static THEME_REC *theme_find(const char *name)
+{
+ GSList *tmp;
+
+ for (tmp = themes; tmp != NULL; tmp = tmp->next) {
+ THEME_REC *rec = tmp->data;
+
+ if (g_ascii_strcasecmp(rec->name, name) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static void window_themes_update(void)
+{
+ GSList *tmp;
+
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ if (rec->theme_name != NULL)
+ rec->theme = theme_load(rec->theme_name);
+ }
+}
+
+THEME_REC *theme_load(const char *setname)
+{
+ THEME_REC *theme, *oldtheme;
+ struct stat statbuf;
+ char *fname, *name, *p;
+
+ name = g_strdup(setname);
+ p = strrchr(name, '.');
+ if (p != NULL && g_strcmp0(p, ".theme") == 0) {
+ /* remove the trailing .theme */
+ *p = '\0';
+ }
+
+ theme = theme_find(name);
+
+ /* check home dir */
+ fname = g_strdup_printf("%s/%s.theme", get_irssi_dir(), name);
+ if (stat(fname, &statbuf) != 0) {
+ /* check global config dir */
+ g_free(fname);
+ fname = g_strdup_printf(THEMESDIR"/%s.theme", name);
+ if (stat(fname, &statbuf) != 0) {
+ /* theme not found */
+ g_free(fname);
+ g_free(name);
+ return theme; /* use the one in memory if possible */
+ }
+ }
+
+ if (theme != NULL && theme->last_modify == statbuf.st_mtime) {
+ /* theme not modified, use the one already in memory */
+ g_free(fname);
+ g_free(name);
+ return theme;
+ }
+
+ oldtheme = theme;
+ theme = theme_create(fname, name);
+ theme->last_modify = statbuf.st_mtime;
+ if (!theme_read(theme, theme->path)) {
+ /* error reading .theme file */
+ theme_destroy(theme);
+ theme = NULL;
+ }
+
+ if (oldtheme != NULL && theme != NULL) {
+ theme_destroy(oldtheme);
+ if (current_theme == oldtheme)
+ current_theme = theme;
+ window_themes_update();
+ }
+
+ g_free(fname);
+ g_free(name);
+ return theme;
+}
+
+static void copy_abstract_hash(char *key, char *value, GHashTable *dest)
+{
+ g_hash_table_insert(dest, g_strdup(key), g_strdup(value));
+}
+
+static void theme_copy_abstracts(THEME_REC *dest, THEME_REC *src)
+{
+ g_hash_table_foreach(src->abstracts, (GHFunc) copy_abstract_hash,
+ dest->abstracts);
+}
+
+typedef struct {
+ THEME_REC *theme;
+ CONFIG_REC *config;
+} THEME_READ_REC;
+
+static void theme_read_modules(const char *module, void *value,
+ THEME_READ_REC *rec)
+{
+ theme_init_module(rec->theme, module, rec->config);
+}
+
+static void read_error(const char *str)
+{
+ char *old;
+
+ if (init_finished)
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "%s", str);
+ else if (init_errors == NULL)
+ init_errors = g_strdup(str);
+ else {
+ old = init_errors;
+ init_errors = g_strconcat(init_errors, "\n", str, NULL);
+ g_free(old);
+ }
+}
+
+static int theme_read(THEME_REC *theme, const char *path)
+{
+ CONFIG_REC *config;
+ THEME_READ_REC rec;
+ char *str;
+
+ config = config_open(path, -1) ;
+ if (config == NULL) {
+ /* didn't exist or no access? */
+ str = g_strdup_printf("Error reading theme file %s: %s",
+ path, g_strerror(errno));
+ read_error(str);
+ g_free(str);
+ return FALSE;
+ }
+
+ if (path == NULL)
+ config_parse_data(config, default_theme, "internal");
+ else
+ config_parse(config);
+
+ if (config_last_error(config) != NULL) {
+ str = g_strdup_printf("Ignored errors in theme %s:\n%s",
+ theme->name, config_last_error(config));
+ read_error(str);
+ g_free(str);
+ }
+
+ theme->default_color =
+ config_get_int(config, NULL, "default_color", -1);
+ theme->info_eol = config_get_bool(config, NULL, "info_eol", FALSE);
+
+ theme_read_replaces(config, theme);
+
+ if (path != NULL)
+ theme_copy_abstracts(theme, internal_theme);
+ theme_read_abstracts(config, theme);
+
+ rec.theme = theme;
+ rec.config = config;
+ g_hash_table_foreach(default_formats,
+ (GHFunc) theme_read_modules, &rec);
+ config_close(config);
+
+ return TRUE;
+}
+
+typedef struct {
+ char *name;
+ char *short_name;
+} THEME_SEARCH_REC;
+
+static int theme_search_equal(THEME_SEARCH_REC *r1, THEME_SEARCH_REC *r2)
+{
+ return g_ascii_strcasecmp(r1->short_name, r2->short_name);
+}
+
+static void theme_get_modules(char *module, FORMAT_REC *formats, GSList **list)
+{
+ THEME_SEARCH_REC *rec;
+
+ rec = g_new(THEME_SEARCH_REC, 1);
+ rec->name = module;
+ rec->short_name = strrchr(module, '/');
+ if (rec->short_name != NULL)
+ rec->short_name++; else rec->short_name = module;
+ *list = g_slist_insert_sorted(*list, rec, (GCompareFunc) theme_search_equal);
+}
+
+static GSList *get_sorted_modules(void)
+{
+ GSList *list;
+
+ list = NULL;
+ g_hash_table_foreach(default_formats, (GHFunc) theme_get_modules, &list);
+ return list;
+}
+
+static THEME_SEARCH_REC *theme_search(GSList *list, const char *module)
+{
+ THEME_SEARCH_REC *rec;
+
+ while (list != NULL) {
+ rec = list->data;
+
+ if (g_ascii_strcasecmp(rec->short_name, module) == 0)
+ return rec;
+ list = list->next;
+ }
+
+ return NULL;
+}
+
+static void theme_show(THEME_SEARCH_REC *rec, const char *key, const char *value, int reset)
+{
+ MODULE_THEME_REC *theme;
+ FORMAT_REC *formats;
+ const char *text, *last_title;
+ int n, first;
+
+ formats = g_hash_table_lookup(default_formats, rec->name);
+ theme = g_hash_table_lookup(current_theme->modules, rec->name);
+
+ last_title = NULL; first = TRUE;
+ for (n = 1; formats[n].def != NULL; n++) {
+ text = theme != NULL && theme->formats[n] != NULL ?
+ theme->formats[n] : formats[n].def;
+
+ if (formats[n].tag == NULL)
+ last_title = text;
+ else if ((value != NULL && key != NULL && g_ascii_strcasecmp(formats[n].tag, key) == 0) ||
+ (value == NULL && (key == NULL || stristr(formats[n].tag, key) != NULL))) {
+ if (first) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_FORMAT_TITLE, rec->short_name, formats[0].def);
+ first = FALSE;
+ }
+ if (last_title != NULL)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_FORMAT_SUBTITLE, last_title);
+ if (reset || value != NULL) {
+ theme = theme_module_create(current_theme, rec->name);
+ g_free_not_null(theme->formats[n]);
+ g_free_not_null(theme->expanded_formats[n]);
+
+ text = reset ? formats[n].def : value;
+ theme->formats[n] = reset ? NULL : g_strdup(value);
+ theme->expanded_formats[n] = theme_format_expand(current_theme, text);
+ }
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_FORMAT_ITEM, formats[n].tag, text);
+ last_title = NULL;
+ }
+ }
+}
+
+/* SYNTAX: FORMAT [-delete | -reset] [<module>] [<key> [<value>]] */
+static void cmd_format(const char *data)
+{
+ GHashTable *optlist;
+ GSList *tmp, *modules;
+ char *module, *key, *value;
+ void *free_arg;
+ int reset;
+
+ if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
+ "format", &optlist, &module, &key, &value))
+ return;
+
+ modules = get_sorted_modules();
+ if (*module == '\0')
+ module = NULL;
+ else if (theme_search(modules, module) == NULL) {
+ /* first argument isn't module.. */
+ cmd_params_free(free_arg);
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
+ "format", &optlist, &key, &value))
+ return;
+ module = NULL;
+ }
+
+ reset = FALSE;
+ if (*key == '\0') key = NULL;
+ if (g_hash_table_lookup(optlist, "reset"))
+ reset = TRUE;
+ else if (g_hash_table_lookup(optlist, "delete"))
+ value = "";
+ else if (*value == '\0')
+ value = NULL;
+
+ for (tmp = modules; tmp != NULL; tmp = tmp->next) {
+ THEME_SEARCH_REC *rec = tmp->data;
+
+ if (module == NULL || g_ascii_strcasecmp(rec->short_name, module) == 0)
+ theme_show(rec, key, value, reset);
+ }
+ g_slist_foreach(modules, (GFunc) g_free, NULL);
+ g_slist_free(modules);
+
+ cmd_params_free(free_arg);
+}
+
+typedef struct {
+ CONFIG_REC *config;
+ int save_all;
+} THEME_SAVE_REC;
+
+static void module_save(const char *module, MODULE_THEME_REC *rec,
+ THEME_SAVE_REC *data)
+{
+ CONFIG_NODE *fnode, *node;
+ FORMAT_REC *formats;
+ int n;
+
+ formats = g_hash_table_lookup(default_formats, rec->name);
+ if (formats == NULL) return;
+
+ fnode = config_node_traverse(data->config, "formats", TRUE);
+
+ node = config_node_section(data->config, fnode, rec->name, NODE_TYPE_BLOCK);
+ for (n = 1; formats[n].def != NULL; n++) {
+ if (rec->formats[n] != NULL) {
+ config_node_set_str(data->config, node, formats[n].tag,
+ rec->formats[n]);
+ } else if (data->save_all && formats[n].tag != NULL) {
+ config_node_set_str(data->config, node, formats[n].tag,
+ formats[n].def);
+ }
+ }
+
+ if (node->value == NULL) {
+ /* not modified, don't keep the empty section */
+ config_node_remove(data->config, fnode, node);
+ if (fnode->value == NULL) {
+ config_node_remove(data->config,
+ data->config->mainnode, fnode);
+ }
+ }
+}
+
+static void theme_save(THEME_REC *theme, int save_all)
+{
+ CONFIG_REC *config;
+ THEME_SAVE_REC data;
+ char *path;
+ char *basename;
+ int ok;
+
+ config = config_open(theme->path, -1);
+ if (config != NULL)
+ config_parse(config);
+ else {
+ if (g_ascii_strcasecmp(theme->name, "default") == 0) {
+ config = config_open(NULL, -1);
+ config_parse_data(config, default_theme, "internal");
+ config_change_file_name(config, theme->path, 0660);
+ } else {
+ config = config_open(theme->path, 0660);
+ if (config == NULL)
+ return;
+ config_parse(config);
+ }
+ }
+
+ data.config = config;
+ data.save_all = save_all;
+ g_hash_table_foreach(theme->modules, (GHFunc) module_save, &data);
+
+ basename = g_path_get_basename(theme->path);
+ /* always save the theme to ~/.irssi/ */
+ path = g_strdup_printf("%s/%s", get_irssi_dir(), basename);
+ ok = config_write(config, path, 0660) == 0;
+ g_free(basename);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ ok ? TXT_THEME_SAVED : TXT_THEME_SAVE_FAILED,
+ path, config_last_error(config));
+
+ g_free(path);
+ config_close(config);
+}
+
+/* save changed formats, -format saves all */
+static void cmd_save(const char *data)
+{
+ GSList *tmp;
+ GHashTable *optlist;
+ void *free_arg;
+ char *fname;
+ int saveall;
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS,
+ "save", &optlist, &fname))
+ return;
+
+ saveall = g_hash_table_lookup(optlist, "formats") != NULL;
+ for (tmp = themes; tmp != NULL; tmp = tmp->next) {
+ THEME_REC *theme = tmp->data;
+
+ theme_save(theme, saveall);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+static void complete_format_list(THEME_SEARCH_REC *rec, const char *key, GList **list)
+{
+ FORMAT_REC *formats;
+ int n, len;
+
+ formats = g_hash_table_lookup(default_formats, rec->name);
+
+ len = strlen(key);
+ for (n = 1; formats[n].def != NULL; n++) {
+ const char *item = formats[n].tag;
+
+ if (item != NULL && g_ascii_strncasecmp(item, key, len) == 0)
+ *list = g_list_append(*list, g_strdup(item));
+ }
+}
+
+static GList *completion_get_formats(const char *module, const char *key)
+{
+ GSList *modules, *tmp;
+ GList *list;
+
+ g_return_val_if_fail(key != NULL, NULL);
+
+ list = NULL;
+
+ modules = get_sorted_modules();
+ if (*module == '\0' || theme_search(modules, module) != NULL) {
+ for (tmp = modules; tmp != NULL; tmp = tmp->next) {
+ THEME_SEARCH_REC *rec = tmp->data;
+
+ if (*module == '\0' || g_ascii_strcasecmp(rec->short_name, module) == 0)
+ complete_format_list(rec, key, &list);
+ }
+ }
+ g_slist_foreach(modules, (GFunc) g_free, NULL);
+ g_slist_free(modules);
+
+ return list;
+}
+
+static void sig_complete_format(GList **list, WINDOW_REC *window,
+ const char *word, const char *line, int *want_space)
+{
+ const char *ptr;
+ int words;
+
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+ g_return_if_fail(line != NULL);
+
+ ptr = line;
+
+ words = 0;
+ if (*ptr != '\0') {
+ do {
+ ptr++;
+ words++;
+ ptr = strchr(ptr, ' ');
+ } while (ptr != NULL);
+ }
+
+ if (words > 2)
+ return;
+
+ *list = completion_get_formats(line, word);
+ if (*list != NULL) signal_stop();
+}
+
+static void change_theme(const char *name, int verbose)
+{
+ THEME_REC *rec;
+
+ rec = theme_load(name);
+ if (rec != NULL) {
+ current_theme = rec;
+ signal_emit("theme changed", 1, rec);
+
+ if (verbose) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_THEME_CHANGED,
+ rec->name, rec->path);
+ }
+ } else if (verbose) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_THEME_NOT_FOUND, name);
+ }
+}
+
+static void read_settings(void)
+{
+ const char *theme;
+ int len;
+
+ theme = settings_get_str("theme");
+ len = strlen(current_theme->name);
+ if (g_strcmp0(current_theme->name, theme) != 0 &&
+ (strncmp(current_theme->name, theme, len) != 0 ||
+ g_strcmp0(theme+len, ".theme") != 0))
+ change_theme(theme, TRUE);
+}
+
+void themes_reload(void)
+{
+ GSList *refs;
+ char *fname;
+
+ /* increase every theme's refcount, and destroy them. this way if
+ we want to use the theme before it's reloaded we don't crash. */
+ refs = NULL;
+ while (themes != NULL) {
+ THEME_REC *theme = themes->data;
+
+ refs = g_slist_prepend(refs, theme);
+
+ theme->refcount++;
+ theme_destroy(theme);
+ }
+
+ /* first there's default theme.. */
+ current_theme = theme_load("default");
+ if (current_theme == NULL) {
+ fname = g_strdup_printf("%s/default.theme", get_irssi_dir());
+ current_theme = theme_create(fname, "default");
+ current_theme->default_color = -1;
+ theme_read(current_theme, NULL);
+ g_free(fname);
+ }
+
+ window_themes_update();
+ change_theme(settings_get_str("theme"), FALSE);
+
+ while (refs != NULL) {
+ void *tmp = refs->data;
+ refs = g_slist_remove(refs, refs->data);
+ theme_unref(tmp);
+ }
+}
+
+static THEME_REC *read_internal_theme(void)
+{
+ CONFIG_REC *config;
+ THEME_REC *theme;
+
+ theme = theme_create("internal", "_internal");
+ theme->refcount++;
+ theme_destroy(theme);
+
+ config = config_open(NULL, -1);
+ config_parse_data(config, default_theme, "internal");
+ theme_read_abstracts(config, theme);
+ config_close(config);
+
+ return theme;
+}
+
+void themes_init(void)
+{
+ settings_add_str("lookandfeel", "theme", "default");
+
+ default_formats = g_hash_table_new((GHashFunc) g_str_hash,
+ (GCompareFunc) g_str_equal);
+ internal_theme = read_internal_theme();
+
+ init_finished = FALSE;
+ init_errors = NULL;
+
+ themes_reload();
+
+ command_bind("format", NULL, (SIGNAL_FUNC) cmd_format);
+ command_bind("save", NULL, (SIGNAL_FUNC) cmd_save);
+ signal_add("complete command format", (SIGNAL_FUNC) sig_complete_format);
+ signal_add("irssi init finished", (SIGNAL_FUNC) sig_print_errors);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_add("setup reread", (SIGNAL_FUNC) themes_reload);
+
+ command_set_options("format", "delete reset");
+ command_set_options("save", "formats");
+}
+
+void themes_deinit(void)
+{
+ while (themes != NULL)
+ theme_destroy(themes->data);
+ theme_destroy(internal_theme);
+
+ g_hash_table_destroy(default_formats);
+ default_formats = NULL;
+
+ command_unbind("format", (SIGNAL_FUNC) cmd_format);
+ command_unbind("save", (SIGNAL_FUNC) cmd_save);
+ signal_remove("complete command format", (SIGNAL_FUNC) sig_complete_format);
+ signal_remove("irssi init finished", (SIGNAL_FUNC) sig_print_errors);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_remove("setup reread", (SIGNAL_FUNC) themes_reload);
+}
diff --git a/src/fe-common/core/themes.h b/src/fe-common/core/themes.h
new file mode 100644
index 0000000..53426f3
--- /dev/null
+++ b/src/fe-common/core/themes.h
@@ -0,0 +1,75 @@
+#ifndef IRSSI_FE_COMMON_CORE_THEMES_H
+#define IRSSI_FE_COMMON_CORE_THEMES_H
+
+typedef struct {
+ char *name;
+
+ int count;
+ char **formats; /* in same order as in module's default formats */
+ char **expanded_formats; /* this contains the formats after
+ expanding {templates} */
+} MODULE_THEME_REC;
+
+typedef struct {
+ int refcount;
+
+ char *path;
+ char *name;
+ time_t last_modify;
+
+ int default_color; /* default color to use with text with default
+ background. default is -1 which means the
+ default color set by terminal */
+ unsigned int info_eol:1; /* show the timestamp/servertag at the
+ end of the line, not at beginning */
+
+ GHashTable *modules;
+
+ int replace_keys[256]; /* index to replace_values for each char */
+ GSList *replace_values;
+ GHashTable *abstracts;
+
+ void *gui_data;
+} THEME_REC;
+
+typedef struct _FORMAT_REC FORMAT_REC;
+
+extern GSList *themes;
+extern THEME_REC *current_theme;
+extern GHashTable *default_formats;
+
+THEME_REC *theme_create(const char *path, const char *name);
+void theme_destroy(THEME_REC *rec);
+
+THEME_REC *theme_load(const char *name);
+
+#define theme_register(formats) theme_register_module(MODULE_NAME, formats)
+#define theme_unregister() theme_unregister_module(MODULE_NAME)
+void theme_register_module(const char *module, FORMAT_REC *formats);
+void theme_unregister_module(const char *module);
+
+void theme_set_default_abstract(const char *key, const char *value);
+
+#define EXPAND_FLAG_IGNORE_REPLACES 0x01 /* don't use the character replaces when expanding */
+#define EXPAND_FLAG_IGNORE_EMPTY 0x02 /* if abstract's argument is empty, or the argument is a $variable that is empty, don't try to expand it (ie. {xx }, but not {xx}) */
+#define EXPAND_FLAG_RECURSIVE_MASK 0x0f
+/* private */
+#define EXPAND_FLAG_ROOT 0x10
+#define EXPAND_FLAG_LASTCOLOR_ARG 0x20
+
+typedef struct {
+ char m[8];
+} theme_rm_col;
+
+char *theme_format_expand(THEME_REC *theme, const char *format);
+char *theme_format_expand_data(THEME_REC *theme, const char **format,
+ theme_rm_col default_fg, theme_rm_col default_bg,
+ theme_rm_col *save_last_fg, theme_rm_col *save_last_bg,
+ int flags);
+
+void themes_reload(void);
+
+void themes_init(void);
+void themes_deinit(void);
+
+#endif
diff --git a/src/fe-common/core/window-activity.c b/src/fe-common/core/window-activity.c
new file mode 100644
index 0000000..7a2903f
--- /dev/null
+++ b/src/fe-common/core/window-activity.c
@@ -0,0 +1,166 @@
+/*
+ window-activity.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/channels.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/fe-common/core/fe-windows.h>
+#include <irssi/src/fe-common/core/window-items.h>
+#include <irssi/src/core/nicklist.h>
+#include <irssi/src/fe-common/core/hilight-text.h>
+#include <irssi/src/fe-common/core/formats.h>
+#include <irssi/src/fe-common/core/fe-common-core.h>
+
+static char **hide_targets;
+static int hide_level, msg_level, hilight_level, signal_window_hilight_check;
+
+void window_activity(WINDOW_REC *window, int data_level,
+ const char *hilight_color)
+{
+ int old_data_level;
+
+ old_data_level = window->data_level;
+ if (data_level == 0 || window->data_level < data_level) {
+ window->data_level = data_level;
+ g_free_not_null(window->hilight_color);
+ window->hilight_color = g_strdup(hilight_color);
+ signal_emit("window hilight", 1, window);
+ }
+
+ signal_emit("window activity", 2, window,
+ GINT_TO_POINTER(old_data_level));
+}
+
+void window_item_activity(WI_ITEM_REC *item, int data_level,
+ const char *hilight_color)
+{
+ int old_data_level;
+
+ old_data_level = item->data_level;
+ if (data_level == 0 || item->data_level < data_level) {
+ item->data_level = data_level;
+ g_free_not_null(item->hilight_color);
+ item->hilight_color = g_strdup(hilight_color);
+ signal_emit("window item hilight", 1, item);
+ }
+
+ signal_emit("window item activity", 2, item,
+ GINT_TO_POINTER(old_data_level));
+}
+
+static void sig_hilight_text(TEXT_DEST_REC *dest, const char *msg)
+{
+ WI_ITEM_REC *item;
+ int data_level;
+ int cb_ignore = 0;
+
+ if (dest->window == active_win || (dest->level & hide_level))
+ return;
+
+ if (dest->level & hilight_level) {
+ data_level = DATA_LEVEL_HILIGHT+dest->hilight_priority;
+ } else {
+ data_level = (dest->level & msg_level) ?
+ DATA_LEVEL_MSG : DATA_LEVEL_TEXT;
+ }
+
+ if (hide_targets != NULL && (dest->level & MSGLEVEL_HILIGHT) == 0) {
+ /* check for both target and tag/target */
+ if (strarray_find_dest(hide_targets, dest))
+ return;
+ }
+
+ /* we should ask the text view if this line is hidden */
+ signal_emit_id(signal_window_hilight_check, 4, dest, msg, &data_level, &cb_ignore);
+ if (cb_ignore) {
+ return;
+ }
+
+ if (dest->target != NULL) {
+ item = window_item_find(dest->server, dest->target);
+ if (item != NULL) {
+ window_item_activity(item, data_level,
+ dest->hilight_color);
+ }
+ }
+ window_activity(dest->window, data_level, dest->hilight_color);
+}
+
+static void sig_dehilight_window(WINDOW_REC *window)
+{
+ GSList *tmp;
+
+ g_return_if_fail(window != NULL);
+
+ if (window->data_level != 0) {
+ window_activity(window, 0, NULL);
+ for (tmp = window->items; tmp != NULL; tmp = tmp->next)
+ window_item_activity(tmp->data, 0, NULL);
+ }
+}
+
+static void read_settings(void)
+{
+ const char *targets;
+
+ if (hide_targets != NULL)
+ g_strfreev(hide_targets);
+
+ targets = settings_get_str("activity_hide_targets");
+ hide_targets = *targets == '\0' ? NULL :
+ g_strsplit(targets, " ", -1);
+
+ hide_level = MSGLEVEL_NEVER | MSGLEVEL_NO_ACT |
+ settings_get_level("activity_hide_level");
+ msg_level = settings_get_level("activity_msg_level");
+ hilight_level = MSGLEVEL_HILIGHT |
+ settings_get_level("activity_hilight_level");
+}
+
+void window_activity_init(void)
+{
+ settings_add_str("lookandfeel", "activity_hide_targets", "");
+ settings_add_level("lookandfeel", "activity_hide_level", "");
+ settings_add_level("lookandfeel", "activity_msg_level", "PUBLIC");
+ settings_add_level("lookandfeel", "activity_hilight_level", "MSGS DCCMSGS");
+ signal_window_hilight_check = signal_get_uniq_id("window hilight check");
+
+ read_settings();
+ signal_add("print text", (SIGNAL_FUNC) sig_hilight_text);
+ signal_add("window changed", (SIGNAL_FUNC) sig_dehilight_window);
+ signal_add("window dehilight", (SIGNAL_FUNC) sig_dehilight_window);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void window_activity_deinit(void)
+{
+ if (hide_targets != NULL)
+ g_strfreev(hide_targets);
+
+ signal_remove("print text", (SIGNAL_FUNC) sig_hilight_text);
+ signal_remove("window changed", (SIGNAL_FUNC) sig_dehilight_window);
+ signal_remove("window dehilight", (SIGNAL_FUNC) sig_dehilight_window);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
diff --git a/src/fe-common/core/window-activity.h b/src/fe-common/core/window-activity.h
new file mode 100644
index 0000000..42a4cda
--- /dev/null
+++ b/src/fe-common/core/window-activity.h
@@ -0,0 +1,13 @@
+#ifndef IRSSI_FE_COMMON_CORE_WINDOW_ACTIVITY_H
+#define IRSSI_FE_COMMON_CORE_WINDOW_ACTIVITY_H
+
+void window_activity(WINDOW_REC *window, int data_level,
+ const char *hilight_color);
+
+void window_item_activity(WI_ITEM_REC *item, int data_level,
+ const char *hilight_color);
+
+void window_activity_init(void);
+void window_activity_deinit(void);
+
+#endif
diff --git a/src/fe-common/core/window-commands.c b/src/fe-common/core/window-commands.c
new file mode 100644
index 0000000..6669ba4
--- /dev/null
+++ b/src/fe-common/core/window-commands.c
@@ -0,0 +1,962 @@
+/*
+ window-commands.c : irssi
+
+ Copyright (C) 2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/core/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/core/levels.h>
+
+#include <irssi/src/fe-common/core/themes.h>
+#include <irssi/src/fe-common/core/fe-windows.h>
+#include <irssi/src/fe-common/core/window-items.h>
+#include <irssi/src/fe-common/core/windows-layout.h>
+#include <irssi/src/fe-common/core/printtext.h>
+#include <irssi/src/fe-common/core/command-history.h>
+
+static void window_print_binds(WINDOW_REC *win)
+{
+ GSList *tmp;
+
+ printformat_window(win, MSGLEVEL_CLIENTCRAP,
+ TXT_WINDOW_INFO_BOUND_ITEMS_HEADER);
+ for (tmp = win->bound_items; tmp != NULL; tmp = tmp->next) {
+ WINDOW_BIND_REC *bind = tmp->data;
+
+ printformat_window(win, MSGLEVEL_CLIENTCRAP,
+ TXT_WINDOW_INFO_BOUND_ITEM,
+ bind->name, bind->servertag,
+ bind->sticky ? "sticky" : "");
+ }
+ printformat_window(win, MSGLEVEL_CLIENTCRAP,
+ TXT_WINDOW_INFO_BOUND_ITEMS_FOOTER);
+}
+
+static void window_print_items(WINDOW_REC *win)
+{
+ GSList *tmp;
+ const char *type;
+
+ printformat_window(win, MSGLEVEL_CLIENTCRAP,
+ TXT_WINDOW_INFO_ITEMS_HEADER);
+ for (tmp = win->items; tmp != NULL; tmp = tmp->next) {
+ WI_ITEM_REC *item = tmp->data;
+
+ type = module_find_id_str("WINDOW ITEM TYPE", item->type);
+ printformat_window(win, MSGLEVEL_CLIENTCRAP,
+ TXT_WINDOW_INFO_ITEM,
+ type == NULL ? "??" : type,
+ item->visible_name,
+ item->server == NULL ? "" :
+ item->server->tag);
+ }
+ printformat_window(win, MSGLEVEL_CLIENTCRAP,
+ TXT_WINDOW_INFO_ITEMS_FOOTER);
+}
+
+static void cmd_window_info(WINDOW_REC *win)
+{
+ char *levelstr;
+
+ printformat_window(win, MSGLEVEL_CLIENTCRAP,
+ TXT_WINDOW_INFO_HEADER);
+
+ /* Window reference number + sticky status */
+ if (!win->sticky_refnum) {
+ printformat_window(win, MSGLEVEL_CLIENTCRAP,
+ TXT_WINDOW_INFO_REFNUM, win->refnum);
+ } else {
+ printformat_window(win, MSGLEVEL_CLIENTCRAP,
+ TXT_WINDOW_INFO_REFNUM_STICKY, win->refnum);
+ }
+
+ /* Window name */
+ if (win->name != NULL) {
+ printformat_window(win, MSGLEVEL_CLIENTCRAP,
+ TXT_WINDOW_INFO_NAME, win->name);
+ }
+
+ /* Window width / height */
+ printformat_window(win, MSGLEVEL_CLIENTCRAP, TXT_WINDOW_INFO_SIZE,
+ win->width, win->height);
+
+ /* Window immortality */
+ if (win->immortal) {
+ printformat_window(win, MSGLEVEL_CLIENTCRAP,
+ TXT_WINDOW_INFO_IMMORTAL);
+ }
+
+ /* Window history name */
+ if (win->history_name != NULL) {
+ printformat_window(win, MSGLEVEL_CLIENTCRAP,
+ TXT_WINDOW_INFO_HISTORY, win->history_name);
+ }
+
+ /* Window level */
+ levelstr = win->level == 0 ?
+ g_strdup("NONE") : bits2level(win->level);
+ printformat_window(win, MSGLEVEL_CLIENTCRAP, TXT_WINDOW_INFO_LEVEL,
+ levelstr);
+ g_free(levelstr);
+
+ /* Active window server + sticky status */
+ if (win->servertag == NULL) {
+ printformat_window(win, MSGLEVEL_CLIENTCRAP,
+ TXT_WINDOW_INFO_SERVER,
+ win->active_server != NULL ?
+ win->active_server->tag : "NONE");
+ } else {
+ if (win->active_server != NULL &&
+ g_strcmp0(win->active_server->tag, win->servertag) != 0)
+ g_warning("Active server isn't the sticky server!");
+
+ printformat_window(win, MSGLEVEL_CLIENTCRAP,
+ TXT_WINDOW_INFO_SERVER_STICKY,
+ win->servertag);
+ }
+
+ /* Window theme + error status */
+ if (win->theme_name != NULL) {
+ printformat_window(win, MSGLEVEL_CLIENTCRAP,
+ TXT_WINDOW_INFO_THEME, win->theme_name,
+ win->theme != NULL ? "" : "(not loaded)");
+ }
+
+ /* Bound items in window */
+ if (win->bound_items != NULL)
+ window_print_binds(win);
+
+ /* Item */
+ if (win->items != NULL)
+ window_print_items(win);
+
+ signal_emit("window print info", 1, win);
+
+ printformat_window(win, MSGLEVEL_CLIENTCRAP,
+ TXT_WINDOW_INFO_FOOTER);
+}
+
+static void cmd_window(const char *data, void *server, WI_ITEM_REC *item)
+{
+ while (*data == ' ') data++;
+
+ if (*data == '\0')
+ cmd_window_info(active_win);
+ else if (is_numeric(data, 0))
+ signal_emit("command window refnum", 3, data, server, item);
+ else
+ command_runsub("window", data, server, item);
+}
+
+/* SYNTAX: WINDOW NEW [HIDDEN|SPLIT|-right SPLIT] */
+static void cmd_window_new(const char *data, void *server, WI_ITEM_REC *item)
+{
+ WINDOW_REC *window;
+ int type;
+
+ g_return_if_fail(data != NULL);
+
+ type = (g_ascii_strncasecmp(data, "hid", 3) == 0 || g_ascii_strcasecmp(data, "tab") == 0) ? MAIN_WINDOW_TYPE_HIDDEN :
+ g_ascii_strcasecmp(data, "split") == 0 ? MAIN_WINDOW_TYPE_SPLIT :
+ g_ascii_strncasecmp(data, "-r", 2) == 0 ? MAIN_WINDOW_TYPE_RSPLIT : MAIN_WINDOW_TYPE_DEFAULT;
+ signal_emit("gui window create override", 1, GINT_TO_POINTER(type));
+
+ window = window_create(NULL, FALSE);
+ window_change_server(window, server);
+}
+
+/* SYNTAX: WINDOW CLOSE [<first> [<last>]] */
+static void cmd_window_close(const char *data)
+{
+ GSList *tmp, *destroys;
+ char *first, *last;
+ int first_num, last_num;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, 2, &first, &last))
+ return;
+
+ if ((*first != '\0' && !is_numeric(first, '\0')) ||
+ ((*last != '\0') && !is_numeric(last, '\0'))) {
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ first_num = *first == '\0' ? active_win->refnum : atoi(first);
+ last_num = *last == '\0' ? first_num : atoi(last);
+
+ /* get list of windows to destroy */
+ destroys = NULL;
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ if (rec->refnum >= first_num && rec->refnum <= last_num)
+ destroys = g_slist_append(destroys, rec);
+ }
+
+ /* really destroy the windows */
+ while (destroys != NULL) {
+ WINDOW_REC *rec = destroys->data;
+
+ if (windows->next != NULL) {
+ if (!rec->immortal)
+ window_destroy(rec);
+ else {
+ printformat_window(rec, MSGLEVEL_CLIENTERROR,
+ TXT_WINDOW_IMMORTAL_ERROR);
+ }
+ }
+
+ destroys = g_slist_remove(destroys, rec);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: WINDOW REFNUM <number> */
+static void cmd_window_refnum(const char *data)
+{
+ WINDOW_REC *window;
+
+ if (!is_numeric(data, 0))
+ return;
+
+ window = window_find_refnum(atoi(data));
+ if (window != NULL)
+ window_set_active(window);
+}
+
+/**
+ * return the window with the highest activity
+ *
+ * If ignore_refnum is true, the most recently active window with the highest
+ * activity will be returned. If ignore_refnum is false, the refnum will be used
+ * to break ties between windows with equally high activity.
+ */
+static WINDOW_REC *window_highest_activity(WINDOW_REC *window,
+ int ignore_refnum)
+{
+ WINDOW_REC *rec, *max_win;
+ GSList *tmp;
+ int max_act, max_ref, through;
+
+ g_return_val_if_fail(window != NULL, NULL);
+
+ max_win = NULL; max_act = 0; max_ref = 0; through = FALSE;
+
+ tmp = g_slist_find(windows, window);
+ for (;; tmp = tmp->next) {
+ if (tmp == NULL) {
+ tmp = windows;
+ through = TRUE;
+ }
+
+ if (through && tmp->data == window)
+ break;
+
+ rec = tmp->data;
+
+ /* ignore refnum */
+ if (ignore_refnum &&
+ rec->data_level > 0 && max_act < rec->data_level) {
+ max_act = rec->data_level;
+ max_win = rec;
+ }
+
+ /* windows with lower refnums break ties */
+ else if (!ignore_refnum &&
+ rec->data_level > 0 &&
+ (rec->data_level > max_act ||
+ (rec->data_level == max_act && rec->refnum < max_ref))) {
+ max_act = rec->data_level;
+ max_win = rec;
+ max_ref = rec->refnum;
+ }
+ }
+
+ return max_win;
+}
+
+static inline int is_nearer(int r1, int r2)
+{
+ int a = r2 < active_win->refnum;
+ int b = r1 < r2;
+
+ if (r1 > active_win->refnum)
+ return a || b;
+ else
+ return a && b;
+}
+
+static WINDOW_REC *window_find_item_cycle(SERVER_REC *server, const char *name)
+{
+ WINDOW_REC *rec, *win;
+ GSList *tmp;
+
+ win = NULL;
+
+ tmp = g_slist_find(windows, active_win);
+ tmp = tmp->next;
+ for (;; tmp = tmp->next) {
+ if (tmp == NULL)
+ tmp = windows;
+
+ if (tmp->data == active_win)
+ break;
+
+ rec = tmp->data;
+
+ if (window_item_find_window(rec, server, name) != NULL &&
+ (win == NULL || is_nearer(rec->refnum, win->refnum))) {
+ win = rec;
+ if (server != NULL) break;
+ }
+ }
+
+ return win;
+}
+
+/* SYNTAX: WINDOW GOTO active|<number>|<name> */
+static void cmd_window_goto(const char *data)
+{
+ WINDOW_REC *window;
+ char *target;
+ void *free_arg;
+
+ g_return_if_fail(data != NULL);
+
+ if (is_numeric(data, 0)) {
+ cmd_window_refnum(data);
+ return;
+ }
+
+ if (!cmd_get_params(data, &free_arg, 1, &target))
+ return;
+
+ if (g_ascii_strcasecmp(target, "active") == 0)
+ window = window_highest_activity(active_win,
+ settings_get_bool("active_window_ignore_refnum"));
+ else {
+ window = window_find_name(target);
+ if (window == NULL && active_win->active_server != NULL)
+ window = window_find_item_cycle(active_win->active_server, target);
+ if (window == NULL)
+ window = window_find_item_cycle(NULL, target);
+ }
+
+ if (window != NULL)
+ window_set_active(window);
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: WINDOW NEXT */
+static void cmd_window_next(void)
+{
+ int num;
+
+ num = window_refnum_next(active_win->refnum, TRUE);
+ if (num < 1) num = windows_refnum_last();
+
+ window_set_active(window_find_refnum(num));
+}
+
+/* SYNTAX: WINDOW LAST */
+static void cmd_window_last(void)
+{
+ if (windows->next != NULL)
+ window_set_active(windows->next->data);
+}
+
+/* SYNTAX: WINDOW PREVIOUS */
+static void cmd_window_previous(void)
+{
+ int num;
+
+ num = window_refnum_prev(active_win->refnum, TRUE);
+ if (num < 1) num = window_refnum_next(0, TRUE);
+
+ window_set_active(window_find_refnum(num));
+}
+
+/* SYNTAX: WINDOW LEVEL [<levels>] */
+static void cmd_window_level(const char *data)
+{
+ char *level;
+
+ g_return_if_fail(data != NULL);
+
+ window_set_level(active_win, combine_level(active_win->level, data));
+
+ level = active_win->level == 0 ? g_strdup("NONE") :
+ bits2level(active_win->level);
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_WINDOW_LEVEL, level);
+ g_free(level);
+}
+
+/* SYNTAX: WINDOW IMMORTAL on|off|toggle */
+static void cmd_window_immortal(const char *data)
+{
+ int set;
+
+ if (*data == '\0')
+ set = active_win->immortal;
+ else if (g_ascii_strcasecmp(data, "ON") == 0)
+ set = TRUE;
+ else if (g_ascii_strcasecmp(data, "OFF") == 0)
+ set = FALSE;
+ else if (g_ascii_strcasecmp(data, "TOGGLE") == 0)
+ set = !active_win->immortal;
+ else {
+ printformat_window(active_win, MSGLEVEL_CLIENTERROR,
+ TXT_NOT_TOGGLE);
+ return;
+ }
+
+ if (set) {
+ window_set_immortal(active_win, TRUE);
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_WINDOW_SET_IMMORTAL);
+ } else {
+ window_set_immortal(active_win, FALSE);
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_WINDOW_UNSET_IMMORTAL);
+ }
+}
+
+/* SYNTAX: WINDOW SERVER [-sticky | -unsticky] <tag> */
+static void cmd_window_server(const char *data)
+{
+ GHashTable *optlist;
+ SERVER_REC *server;
+ char *tag;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS,
+ "window server", &optlist, &tag))
+ return;
+
+ if (*tag == '\0' && active_win->active_server != NULL &&
+ (g_hash_table_lookup(optlist, "sticky") != NULL ||
+ g_hash_table_lookup(optlist, "unsticky") != NULL)) {
+ tag = active_win->active_server->tag;
+ }
+
+ if (*tag == '\0')
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+ server = server_find_tag(tag);
+ if (server == NULL)
+ server = server_find_lookup_tag(tag);
+
+ if (g_hash_table_lookup(optlist, "unsticky") != NULL &&
+ active_win->servertag != NULL) {
+ g_free_and_null(active_win->servertag);
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_UNSET_SERVER_STICKY);
+ }
+
+ if (active_win->servertag != NULL &&
+ g_hash_table_lookup(optlist, "sticky") == NULL) {
+ printformat_window(active_win, MSGLEVEL_CLIENTERROR,
+ TXT_ERROR_SERVER_STICKY);
+ } else if (server == NULL) {
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_UNKNOWN_SERVER_TAG, tag);
+ } else if (active_win->active == NULL) {
+ window_change_server(active_win, server);
+ if (g_hash_table_lookup(optlist, "sticky") != NULL) {
+ g_free_not_null(active_win->servertag);
+ active_win->servertag = g_strdup(server->tag);
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_SET_SERVER_STICKY, server->tag);
+ }
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_SERVER_CHANGED,
+ server->tag, server->connrec->address,
+ server->connrec->chatnet == NULL ? "" :
+ server->connrec->chatnet);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+static void cmd_window_item(const char *data, void *server, WI_ITEM_REC *item)
+{
+ while (*data == ' ') data++;
+
+ if (is_numeric(data, '\0'))
+ signal_emit("command window item goto", 3, data, server, item);
+ else
+ command_runsub("window item", data, server, item);
+}
+
+/* SYNTAX: WINDOW ITEM PREV */
+static void cmd_window_item_prev(void)
+{
+ window_item_prev(active_win);
+}
+
+/* SYNTAX: WINDOW ITEM NEXT */
+static void cmd_window_item_next(void)
+{
+ window_item_next(active_win);
+}
+
+/* SYNTAX: WINDOW ITEM GOTO <number>|<name> */
+static void cmd_window_item_goto(const char *data, SERVER_REC *server)
+{
+ WI_ITEM_REC *item;
+ GSList *tmp;
+ void *free_arg;
+ char *target;
+
+ if (!cmd_get_params(data, &free_arg, 1, &target))
+ return;
+
+ if (is_numeric(target, '\0')) {
+ /* change to specified number */
+ tmp = g_slist_nth(active_win->items, atoi(target)-1);
+ item = tmp == NULL ? NULL : tmp->data;
+ } else {
+ item = window_item_find_window(active_win, server, target);
+ }
+
+ if (item != NULL)
+ window_item_set_active(active_win, item);
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: WINDOW ITEM MOVE <number>|<name> */
+static void cmd_window_item_move(const char *data, SERVER_REC *server,
+ WI_ITEM_REC *item)
+{
+ WINDOW_REC *window;
+ void *free_arg;
+ char *target;
+
+ if (!cmd_get_params(data, &free_arg, 1, &target))
+ return;
+
+ if (is_numeric(target, '\0')) {
+ /* move current window item to specified window */
+ window = window_find_refnum(atoi(target));
+ } else {
+ /* move specified window item to current window */
+ item = window_item_find(server, target);
+ window = active_win;
+ }
+ if (window != NULL && item != NULL)
+ window_item_set_active(window, item);
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: WINDOW NUMBER [-sticky] <number> */
+static void cmd_window_number(const char *data)
+{
+ GHashTable *optlist;
+ char *refnum;
+ void *free_arg;
+ int num;
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS,
+ "window number", &optlist, &refnum))
+ return;
+
+ if (*refnum == '\0')
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ num = atoi(refnum);
+ if (num < 1) {
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_REFNUM_TOO_LOW);
+ } else {
+ window_set_refnum(active_win, num);
+ active_win->sticky_refnum =
+ g_hash_table_lookup(optlist, "sticky") != NULL;
+ }
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: WINDOW NAME <name> */
+static void cmd_window_name(const char *data)
+{
+ WINDOW_REC *win;
+
+ win = window_find_name(data);
+ if (win == NULL || win == active_win)
+ window_set_name(active_win, data);
+ else if (active_win->name == NULL ||
+ g_strcmp0(active_win->name, data) != 0) {
+ printformat_window(active_win, MSGLEVEL_CLIENTERROR,
+ TXT_WINDOW_NAME_NOT_UNIQUE, data);
+ }
+}
+
+/* SYNTAX: WINDOW HISTORY [-clear] <name> */
+void cmd_window_history(const char *data)
+{
+ GHashTable *optlist;
+ char *name;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | PARAM_FLAG_STRIP_TRAILING_WS,
+ "window history", &optlist, &name))
+ return;
+
+ if (g_hash_table_lookup(optlist, "clear") != NULL) {
+ signal_continue(1, data);
+ window_clear_history(active_win, name);
+ } else {
+ window_set_history(active_win, name);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+/* we're moving the first window to last - move the first contiguous block
+ of refnums to left. Like if there's windows 1..5 and 7..10, move 1 to
+ 11, 2..5 to 1..4 and leave 7..10 alone */
+static void window_refnums_move_left(WINDOW_REC *move_window)
+{
+ WINDOW_REC *window;
+ int refnum, new_refnum;
+
+ new_refnum = windows_refnum_last();
+ for (refnum = move_window->refnum+1; refnum <= new_refnum; refnum++) {
+ window = window_find_refnum(refnum);
+ if (window == NULL) {
+ new_refnum++;
+ break;
+ }
+
+ window_set_refnum(window, refnum-1);
+ }
+
+ window_set_refnum(move_window, new_refnum);
+}
+
+/* we're moving the last window to first - make some space so we can use the
+ refnum 1 */
+static void window_refnums_move_right(WINDOW_REC *move_window)
+{
+ WINDOW_REC *window;
+ int refnum, new_refnum;
+
+ new_refnum = 1;
+ if (window_find_refnum(new_refnum) == NULL) {
+ window_set_refnum(move_window, new_refnum);
+ return;
+ }
+
+ /* find the first unused refnum, like if there's windows
+ 1..5 and 7..10, we only need to move 1..5 to 2..6 */
+ refnum = new_refnum;
+ while (move_window->refnum == refnum ||
+ window_find_refnum(refnum) != NULL) refnum++;
+ refnum--;
+
+ while (refnum >= new_refnum) {
+ window = window_find_refnum(refnum);
+ window_set_refnum(window, refnum+1);
+
+ refnum--;
+ }
+
+ window_set_refnum(move_window, new_refnum);
+}
+
+/* SYNTAX: WINDOW MOVE PREV */
+static void cmd_window_move_prev(void)
+{
+ int refnum;
+
+ refnum = window_refnum_prev(active_win->refnum, FALSE);
+ if (refnum != -1) {
+ window_set_refnum(active_win, refnum);
+ return;
+ }
+
+ window_refnums_move_left(active_win);
+}
+
+/* SYNTAX: WINDOW MOVE NEXT */
+static void cmd_window_move_next(void)
+{
+ int refnum;
+
+ refnum = window_refnum_next(active_win->refnum, FALSE);
+ if (refnum != -1) {
+ window_set_refnum(active_win, refnum);
+ return;
+ }
+
+ window_refnums_move_right(active_win);
+}
+
+static void active_window_move_to(int new_refnum)
+{
+ int refnum;
+
+ if (new_refnum > active_win->refnum) {
+ for (;;) {
+ refnum = window_refnum_next(active_win->refnum, FALSE);
+ if (refnum == -1 || refnum > new_refnum)
+ break;
+
+ window_set_refnum(active_win, refnum);
+ }
+ } else {
+ for (;;) {
+ refnum = window_refnum_prev(active_win->refnum, FALSE);
+ if (refnum == -1 || refnum < new_refnum)
+ break;
+
+ window_set_refnum(active_win, refnum);
+ }
+ }
+}
+
+/* SYNTAX: WINDOW MOVE FIRST */
+static void cmd_window_move_first(void)
+{
+ active_window_move_to(1);
+}
+
+/* SYNTAX: WINDOW MOVE LAST */
+static void cmd_window_move_last(void)
+{
+ active_window_move_to(windows_refnum_last());
+}
+
+/* SYNTAX: WINDOW MOVE <number>|<direction> */
+static void cmd_window_move(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ if (!is_numeric(data, 0)) {
+ command_runsub("window move", data, server, item);
+ return;
+ }
+
+ active_window_move_to(atoi(data));
+}
+
+/* SYNTAX: WINDOW LIST */
+static void cmd_window_list(void)
+{
+ GSList *tmp, *sorted;
+ char *levelstr;
+
+ sorted = windows_get_sorted();
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_WINDOWLIST_HEADER);
+ for (tmp = sorted; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ levelstr = bits2level(rec->level);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_WINDOWLIST_LINE,
+ rec->refnum, rec->name == NULL ? "" : rec->name,
+ rec->active == NULL ? "" : rec->active->visible_name,
+ rec->active_server == NULL ? "" : ((SERVER_REC *) rec->active_server)->tag,
+ levelstr);
+ g_free(levelstr);
+ }
+ g_slist_free(sorted);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_WINDOWLIST_FOOTER);
+}
+
+/* SYNTAX: WINDOW THEME [-delete] [<name>] */
+static void cmd_window_theme(const char *data)
+{
+ THEME_REC *theme;
+ GHashTable *optlist;
+ char *name;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS,
+ "window theme", &optlist, &name))
+ return;
+
+ if (g_hash_table_lookup(optlist, "delete") != NULL) {
+ g_free_and_null(active_win->theme_name);
+
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_WINDOW_THEME_REMOVED);
+ } else if (*name == '\0') {
+ if (active_win->theme == NULL) {
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_WINDOW_THEME_DEFAULT);
+ } else {
+ theme = active_win->theme;
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_WINDOW_THEME,
+ theme->name, theme->path);
+ }
+ } else {
+ g_free_not_null(active_win->theme_name);
+ active_win->theme_name = g_strdup(data);
+
+ active_win->theme = theme = theme_load(data);
+ if (theme != NULL) {
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_WINDOW_THEME_CHANGED,
+ theme->name, theme->path);
+ } else {
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_THEME_NOT_FOUND, data);
+ }
+ }
+
+ cmd_params_free(free_arg);
+}
+
+static void cmd_layout(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ command_runsub("layout", data, server, item);
+}
+
+/* SYNTAX: FOREACH WINDOW <command> */
+static void cmd_foreach_window(const char *data)
+{
+ WINDOW_REC *old;
+ GSList *list;
+ const char *cmdchars;
+ char *str;
+
+ cmdchars = settings_get_str("cmdchars");
+ str = strchr(cmdchars, *data) != NULL ? g_strdup(data) :
+ g_strdup_printf("%c%s", *cmdchars, data);
+
+ old = active_win;
+
+ list = g_slist_copy(windows);
+ while (list != NULL) {
+ WINDOW_REC *rec = list->data;
+
+ active_win = rec;
+ signal_emit("send command", 3, str, rec->active_server,
+ rec->active);
+ list = g_slist_remove(list, list->data);
+ }
+
+ if (g_slist_find(windows, old) != NULL)
+ active_win = old;
+
+ g_free(str);
+}
+
+static void cmd_window_default_command(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ if (!is_numeric(data, 0) ||
+ !settings_get_bool("window_number_commands")) {
+ return;
+ }
+ signal_emit("command window refnum", 3, data, server, item);
+ signal_stop();
+}
+
+void window_commands_init(void)
+{
+ settings_add_bool("lookandfeel", "active_window_ignore_refnum", TRUE);
+ settings_add_bool("misc", "window_number_commands", TRUE);
+
+ signal_add("default command", (SIGNAL_FUNC) cmd_window_default_command);
+
+ command_bind("window", NULL, (SIGNAL_FUNC) cmd_window);
+ command_bind("window new", NULL, (SIGNAL_FUNC) cmd_window_new);
+ command_bind("window close", NULL, (SIGNAL_FUNC) cmd_window_close);
+ command_bind("window kill", NULL, (SIGNAL_FUNC) cmd_window_close);
+ command_bind("window server", NULL, (SIGNAL_FUNC) cmd_window_server);
+ command_bind("window refnum", NULL, (SIGNAL_FUNC) cmd_window_refnum);
+ command_bind("window goto", NULL, (SIGNAL_FUNC) cmd_window_goto);
+ command_bind("window previous", NULL, (SIGNAL_FUNC) cmd_window_previous);
+ command_bind("window next", NULL, (SIGNAL_FUNC) cmd_window_next);
+ command_bind("window last", NULL, (SIGNAL_FUNC) cmd_window_last);
+ command_bind("window level", NULL, (SIGNAL_FUNC) cmd_window_level);
+ command_bind("window immortal", NULL, (SIGNAL_FUNC) cmd_window_immortal);
+ command_bind("window item", NULL, (SIGNAL_FUNC) cmd_window_item);
+ command_bind("window item prev", NULL, (SIGNAL_FUNC) cmd_window_item_prev);
+ command_bind("window item next", NULL, (SIGNAL_FUNC) cmd_window_item_next);
+ command_bind("window item goto", NULL, (SIGNAL_FUNC) cmd_window_item_goto);
+ command_bind("window item move", NULL, (SIGNAL_FUNC) cmd_window_item_move);
+ command_bind("window number", NULL, (SIGNAL_FUNC) cmd_window_number);
+ command_bind("window name", NULL, (SIGNAL_FUNC) cmd_window_name);
+ command_bind("window history", NULL, (SIGNAL_FUNC) cmd_window_history);
+ command_bind("window move", NULL, (SIGNAL_FUNC) cmd_window_move);
+ command_bind("window move prev", NULL, (SIGNAL_FUNC) cmd_window_move_prev);
+ command_bind("window move next", NULL, (SIGNAL_FUNC) cmd_window_move_next);
+ command_bind("window move first", NULL, (SIGNAL_FUNC) cmd_window_move_first);
+ command_bind("window move last", NULL, (SIGNAL_FUNC) cmd_window_move_last);
+ command_bind("window list", NULL, (SIGNAL_FUNC) cmd_window_list);
+ command_bind("window theme", NULL, (SIGNAL_FUNC) cmd_window_theme);
+ command_bind("layout", NULL, (SIGNAL_FUNC) cmd_layout);
+ /* SYNTAX: LAYOUT SAVE */
+ command_bind("layout save", NULL, (SIGNAL_FUNC) windows_layout_save);
+ /* SYNTAX: LAYOUT RESET */
+ command_bind("layout reset", NULL, (SIGNAL_FUNC) windows_layout_reset);
+ command_bind("foreach window", NULL, (SIGNAL_FUNC) cmd_foreach_window);
+
+ command_set_options("window number", "sticky");
+ command_set_options("window server", "sticky unsticky");
+ command_set_options("window theme", "delete");
+ command_set_options("window history", "clear");
+}
+
+void window_commands_deinit(void)
+{
+ command_unbind("window", (SIGNAL_FUNC) cmd_window);
+ command_unbind("window new", (SIGNAL_FUNC) cmd_window_new);
+ command_unbind("window close", (SIGNAL_FUNC) cmd_window_close);
+ command_unbind("window kill", (SIGNAL_FUNC) cmd_window_close);
+ command_unbind("window server", (SIGNAL_FUNC) cmd_window_server);
+ command_unbind("window refnum", (SIGNAL_FUNC) cmd_window_refnum);
+ command_unbind("window goto", (SIGNAL_FUNC) cmd_window_goto);
+ command_unbind("window previous", (SIGNAL_FUNC) cmd_window_previous);
+ command_unbind("window next", (SIGNAL_FUNC) cmd_window_next);
+ command_unbind("window last", (SIGNAL_FUNC) cmd_window_last);
+ command_unbind("window level", (SIGNAL_FUNC) cmd_window_level);
+ command_unbind("window immortal", (SIGNAL_FUNC) cmd_window_immortal);
+ command_unbind("window item", (SIGNAL_FUNC) cmd_window_item);
+ command_unbind("window item prev", (SIGNAL_FUNC) cmd_window_item_prev);
+ command_unbind("window item next", (SIGNAL_FUNC) cmd_window_item_next);
+ command_unbind("window item goto", (SIGNAL_FUNC) cmd_window_item_goto);
+ command_unbind("window item move", (SIGNAL_FUNC) cmd_window_item_move);
+ command_unbind("window number", (SIGNAL_FUNC) cmd_window_number);
+ command_unbind("window name", (SIGNAL_FUNC) cmd_window_name);
+ command_unbind("window history", (SIGNAL_FUNC) cmd_window_history);
+ command_unbind("window move", (SIGNAL_FUNC) cmd_window_move);
+ command_unbind("window move prev", (SIGNAL_FUNC) cmd_window_move_prev);
+ command_unbind("window move next", (SIGNAL_FUNC) cmd_window_move_next);
+ command_unbind("window move first", (SIGNAL_FUNC) cmd_window_move_first);
+ command_unbind("window move last", (SIGNAL_FUNC) cmd_window_move_last);
+ command_unbind("window list", (SIGNAL_FUNC) cmd_window_list);
+ command_unbind("window theme", (SIGNAL_FUNC) cmd_window_theme);
+ command_unbind("layout", (SIGNAL_FUNC) cmd_layout);
+ command_unbind("layout save", (SIGNAL_FUNC) windows_layout_save);
+ command_unbind("layout reset", (SIGNAL_FUNC) windows_layout_reset);
+ command_unbind("foreach window", (SIGNAL_FUNC) cmd_foreach_window);
+
+ signal_remove("default command", (SIGNAL_FUNC) cmd_window_default_command);
+}
diff --git a/src/fe-common/core/window-items.c b/src/fe-common/core/window-items.c
new file mode 100644
index 0000000..c60c796
--- /dev/null
+++ b/src/fe-common/core/window-items.c
@@ -0,0 +1,355 @@
+/*
+ window-items.c : irssi
+
+ Copyright (C) 2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/core/module-formats.h>
+#include <irssi/src/core/modules.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/channels.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/core/levels.h>
+
+#include <irssi/src/fe-common/core/fe-windows.h>
+#include <irssi/src/fe-common/core/window-items.h>
+#include <irssi/src/fe-common/core/printtext.h>
+
+static void window_item_add_signal(WINDOW_REC *window, WI_ITEM_REC *item, int automatic, int send_signal)
+{
+ g_return_if_fail(window != NULL);
+ g_return_if_fail(item != NULL);
+ g_return_if_fail(item->window == NULL);
+
+ item->window = window;
+
+ if (window->items == NULL) {
+ window->active = item;
+ window->active_server = item->server;
+ }
+
+ if (!automatic || settings_get_bool("window_auto_change")) {
+ if (automatic)
+ signal_emit("window changed automatic", 1, window);
+ window_set_active(window);
+ }
+
+ window->items = g_slist_append(window->items, item);
+ if (send_signal)
+ signal_emit("window item new", 2, window, item);
+
+ if (g_slist_length(window->items) == 1 ||
+ (!automatic && settings_get_bool("autofocus_new_items"))) {
+ window->active = NULL;
+ window_item_set_active(window, item);
+ }
+}
+
+void window_item_add(WINDOW_REC *window, WI_ITEM_REC *item, int automatic)
+{
+ window_item_add_signal(window, item, automatic, TRUE);
+}
+
+static void window_item_remove_signal(WI_ITEM_REC *item, int emit_signal)
+{
+ WINDOW_REC *window;
+
+ g_return_if_fail(item != NULL);
+
+ window = window_item_window(item);
+
+ if (window == NULL)
+ return;
+
+ item->window = NULL;
+ window->items = g_slist_remove(window->items, item);
+
+ if (window->active == item) {
+ window_item_set_active(window, window->items == NULL ? NULL :
+ window->items->data);
+ }
+
+ if (emit_signal)
+ signal_emit("window item remove", 2, window, item);
+}
+
+void window_item_remove(WI_ITEM_REC *item)
+{
+ window_item_remove_signal(item, TRUE);
+}
+
+void window_item_destroy(WI_ITEM_REC *item)
+{
+ window_item_remove(item);
+ item->destroy(item);
+}
+
+void window_item_change_server(WI_ITEM_REC *item, void *server)
+{
+ WINDOW_REC *window;
+
+ g_return_if_fail(item != NULL);
+
+ window = window_item_window(item);
+ item->server = server;
+
+ signal_emit("window item server changed", 2, window, item);
+ if (window->active == item) window_change_server(window, item->server);
+}
+
+void window_item_set_active(WINDOW_REC *window, WI_ITEM_REC *item)
+{
+ WINDOW_REC *old_window;
+
+ g_return_if_fail(window != NULL);
+
+ if (item != NULL) {
+ old_window = window_item_window(item);
+ if (old_window != window) {
+ /* move item to different window */
+ window_item_remove_signal(item, FALSE);
+ window_item_add_signal(window, item, FALSE, FALSE);
+ signal_emit("window item moved", 3, window, item, old_window);
+ }
+ }
+
+ if (window->active != item) {
+ window->active = item;
+ if (item != NULL && window->active_server != item->server)
+ window_change_server(window, item->server);
+ signal_emit("window item changed", 2, window, item);
+ }
+}
+
+/* Return TRUE if `item' is the active window item in the window.
+ `item' can be NULL. */
+int window_item_is_active(WI_ITEM_REC *item)
+{
+ WINDOW_REC *window;
+
+ if (item == NULL)
+ return FALSE;
+
+ window = window_item_window(item);
+ if (window == NULL)
+ return FALSE;
+
+ return window->active == item;
+}
+
+void window_item_prev(WINDOW_REC *window)
+{
+ WI_ITEM_REC *last;
+ GSList *tmp;
+
+ g_return_if_fail(window != NULL);
+
+ last = NULL;
+ for (tmp = window->items; tmp != NULL; tmp = tmp->next) {
+ WI_ITEM_REC *rec = tmp->data;
+
+ if (rec != window->active)
+ last = rec;
+ else {
+ /* current channel. did we find anything?
+ if not, go to the last channel */
+ if (last != NULL) break;
+ }
+ }
+
+ if (last != NULL)
+ window_item_set_active(window, last);
+}
+
+void window_item_next(WINDOW_REC *window)
+{
+ WI_ITEM_REC *next;
+ GSList *tmp;
+ int gone;
+
+ g_return_if_fail(window != NULL);
+
+ next = NULL; gone = FALSE;
+ for (tmp = window->items; tmp != NULL; tmp = tmp->next) {
+ WI_ITEM_REC *rec = tmp->data;
+
+ if (rec == window->active)
+ gone = TRUE;
+ else {
+ if (gone) {
+ /* found the next channel */
+ next = rec;
+ break;
+ }
+
+ if (next == NULL)
+ next = rec; /* fallback to first channel */
+ }
+ }
+
+ if (next != NULL)
+ window_item_set_active(window, next);
+}
+
+WI_ITEM_REC *window_item_find_window(WINDOW_REC *window,
+ void *server, const char *name)
+{
+ GSList *tmp;
+
+ for (tmp = window->items; tmp != NULL; tmp = tmp->next) {
+ WI_ITEM_REC *rec = tmp->data;
+
+ if ((server == NULL || rec->server == server) &&
+ (g_ascii_strcasecmp(name, rec->visible_name) == 0
+ || (rec->name && g_ascii_strcasecmp(name, rec->name) == 0)))
+ return rec;
+ }
+ return NULL;
+}
+
+/* Find wanted window item by name. `server' can be NULL. */
+WI_ITEM_REC *window_item_find(void *server, const char *name)
+{
+ WI_ITEM_REC *item;
+ GSList *tmp;
+
+ g_return_val_if_fail(name != NULL, NULL);
+
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ item = window_item_find_window(rec, server, name);
+ if (item != NULL) return item;
+ }
+
+ return NULL;
+}
+
+static int window_bind_has_sticky(WINDOW_REC *window)
+{
+ GSList *tmp;
+
+ for (tmp = window->bound_items; tmp != NULL; tmp = tmp->next) {
+ WINDOW_BIND_REC *rec = tmp->data;
+
+ if (rec->sticky)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void window_item_create(WI_ITEM_REC *item, int automatic)
+{
+ WINDOW_REC *window;
+ WINDOW_BIND_REC *bind;
+ GSList *tmp, *sorted;
+ int clear_waiting, reuse_unused_windows;
+
+ g_return_if_fail(item != NULL);
+
+ reuse_unused_windows = settings_get_bool("reuse_unused_windows");
+
+ clear_waiting = TRUE;
+ window = NULL;
+ sorted = windows_get_sorted();
+ for (tmp = sorted; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ /* is item bound to this window? */
+ if (item->server != NULL) {
+ bind = window_bind_find(rec, item->server->tag,
+ item->visible_name);
+ if (bind != NULL) {
+ if (!bind->sticky)
+ window_bind_destroy(rec, bind);
+ window = rec;
+ clear_waiting = FALSE;
+ break;
+ }
+ }
+
+ /* use this window IF:
+ - reuse_unused_windows is ON
+ - window has no existing items
+ - window has no name
+ - window has no sticky binds (/LAYOUT SAVEd)
+ - we already haven't found "good enough" window,
+ except if
+ - this is the active window
+ - old window had some temporary bounds and this
+ one doesn't
+ */
+ if (reuse_unused_windows && rec->items == NULL &&
+ rec->name == NULL && !window_bind_has_sticky(rec) &&
+ (window == NULL || rec == active_win ||
+ window->bound_items != NULL))
+ window = rec;
+ }
+ g_slist_free(sorted);
+
+ if (window == NULL && !settings_get_bool("autocreate_windows")) {
+ /* never create new windows automatically */
+ window = active_win;
+ }
+
+ if (window == NULL) {
+ /* create new window to use */
+ if (settings_get_bool("autocreate_split_windows")) {
+ signal_emit("gui window create override", 1,
+ GINT_TO_POINTER(MAIN_WINDOW_TYPE_SPLIT));
+ }
+ window = window_create(item, automatic);
+ } else {
+ /* use existing window */
+ window_item_add(window, item, automatic);
+ }
+
+ if (clear_waiting)
+ window_bind_remove_unsticky(window);
+}
+
+static void signal_window_item_changed(WINDOW_REC *window, WI_ITEM_REC *item)
+{
+ g_return_if_fail(window != NULL);
+
+ if (g_slist_length(window->items) > 1) {
+ /* default to printing "talking with ...",
+ you can override it it you wish */
+ printformat(item->server, item->visible_name,
+ MSGLEVEL_CLIENTNOTICE,
+ TXT_TALKING_WITH, item->visible_name);
+ }
+}
+
+void window_items_init(void)
+{
+ settings_add_bool("lookandfeel", "reuse_unused_windows", FALSE);
+ settings_add_bool("lookandfeel", "autocreate_windows", TRUE);
+ settings_add_bool("lookandfeel", "autocreate_split_windows", FALSE);
+ settings_add_bool("lookandfeel", "autofocus_new_items", TRUE);
+
+ signal_add_last("window item changed", (SIGNAL_FUNC) signal_window_item_changed);
+}
+
+void window_items_deinit(void)
+{
+ signal_remove("window item changed", (SIGNAL_FUNC) signal_window_item_changed);
+}
diff --git a/src/fe-common/core/window-items.h b/src/fe-common/core/window-items.h
new file mode 100644
index 0000000..5bfa92f
--- /dev/null
+++ b/src/fe-common/core/window-items.h
@@ -0,0 +1,34 @@
+#ifndef IRSSI_FE_COMMON_CORE_WINDOW_ITEMS_H
+#define IRSSI_FE_COMMON_CORE_WINDOW_ITEMS_H
+
+#include <irssi/src/fe-common/core/fe-windows.h>
+
+/* Add/remove/destroy window item from `window' */
+void window_item_add(WINDOW_REC *window, WI_ITEM_REC *item, int automatic);
+void window_item_remove(WI_ITEM_REC *item);
+void window_item_destroy(WI_ITEM_REC *item);
+
+/* Find a window for `item' and call window_item_add(). */
+void window_item_create(WI_ITEM_REC *item, int automatic);
+
+#define window_item_window(item) \
+ ((WINDOW_REC *) ((WI_ITEM_REC *) (item))->window)
+void window_item_change_server(WI_ITEM_REC *item, void *server);
+
+void window_item_set_active(WINDOW_REC *window, WI_ITEM_REC *item);
+/* Return TRUE if `item' is the active window item in the window.
+ `item' can be NULL. */
+int window_item_is_active(WI_ITEM_REC *item);
+
+void window_item_prev(WINDOW_REC *window);
+void window_item_next(WINDOW_REC *window);
+
+/* Find wanted window item by name. `server' can be NULL. */
+WI_ITEM_REC *window_item_find(void *server, const char *name);
+WI_ITEM_REC *window_item_find_window(WINDOW_REC *window,
+ void *server, const char *name);
+
+void window_items_init(void);
+void window_items_deinit(void);
+
+#endif
diff --git a/src/fe-common/core/windows-layout.c b/src/fe-common/core/windows-layout.c
new file mode 100644
index 0000000..fa209f4
--- /dev/null
+++ b/src/fe-common/core/windows-layout.c
@@ -0,0 +1,278 @@
+/*
+ windows-layout.c : irssi
+
+ Copyright (C) 2000-2001 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/lib-config/iconfig.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/core/chat-protocols.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/channels.h>
+#include <irssi/src/core/queries.h>
+
+#include <irssi/src/fe-common/core/module-formats.h>
+#include <irssi/src/fe-common/core/printtext.h>
+#include <irssi/src/fe-common/core/themes.h>
+#include <irssi/src/fe-common/core/fe-windows.h>
+#include <irssi/src/fe-common/core/window-items.h>
+
+static WINDOW_REC *restore_win;
+
+static void signal_query_created_curwin(QUERY_REC *query)
+{
+ g_return_if_fail(IS_QUERY(query));
+
+ window_item_add(restore_win, (WI_ITEM_REC *) query, TRUE);
+}
+
+static void sig_layout_restore_item(WINDOW_REC *window, const char *type,
+ CONFIG_NODE *node)
+{
+ char *name, *tag, *chat_type;
+
+ chat_type = config_node_get_str(node, "chat_type", NULL);
+ name = config_node_get_str(node, "name", NULL);
+ tag = config_node_get_str(node, "tag", NULL);
+
+ if (name == NULL || tag == NULL)
+ return;
+
+ if (g_ascii_strcasecmp(type, "CHANNEL") == 0) {
+ /* bind channel to window */
+ WINDOW_BIND_REC *rec = window_bind_add(window, tag, name);
+ rec->sticky = TRUE;
+ } else if (g_ascii_strcasecmp(type, "QUERY") == 0 && chat_type != NULL) {
+ CHAT_PROTOCOL_REC *protocol;
+ /* create query immediately */
+ signal_add("query created",
+ (SIGNAL_FUNC) signal_query_created_curwin);
+
+ restore_win = window;
+
+ protocol = chat_protocol_find(chat_type);
+ if (protocol == NULL || protocol->not_initialized) {
+ WINDOW_BIND_REC *rec = window_bind_add(window, tag, name);
+ rec->type = module_get_uniq_id_str("WINDOW ITEM TYPE", "QUERY");
+ } else if (protocol->query_create != NULL) {
+ protocol->query_create(tag, name, TRUE);
+ } else {
+ QUERY_REC *query;
+
+ query = g_new0(QUERY_REC, 1);
+ query->chat_type = chat_protocol_lookup(chat_type);
+ query->name = g_strdup(name);
+ query->server_tag = g_strdup(tag);
+ query_init(query, TRUE);
+ }
+
+ signal_remove("query created",
+ (SIGNAL_FUNC) signal_query_created_curwin);
+ }
+}
+
+static void window_add_items(WINDOW_REC *window, CONFIG_NODE *node)
+{
+ GSList *tmp;
+ char *type;
+
+ if (node == NULL)
+ return;
+
+ tmp = config_node_first(node->value);
+ for (; tmp != NULL; tmp = config_node_next(tmp)) {
+ CONFIG_NODE *node = tmp->data;
+
+ type = config_node_get_str(node, "type", NULL);
+ if (type != NULL) {
+ signal_emit("layout restore item", 3,
+ window, type, node);
+ }
+ }
+}
+
+void windows_layout_restore(void)
+{
+ signal_emit("layout restore", 0);
+}
+
+static void sig_layout_restore(void)
+{
+ WINDOW_REC *window;
+ CONFIG_NODE *node;
+ GSList *tmp;
+
+ node = iconfig_node_traverse("windows", FALSE);
+ if (node == NULL) return;
+
+ tmp = config_node_first(node->value);
+ for (; tmp != NULL; tmp = config_node_next(tmp)) {
+ CONFIG_NODE *node = tmp->data;
+
+ if (node->key == NULL) continue;
+ window = window_find_refnum(atoi(node->key));
+ if (window == NULL)
+ window = window_create(NULL, TRUE);
+
+ window_set_refnum(window, atoi(node->key));
+ window->sticky_refnum = config_node_get_bool(node, "sticky_refnum", FALSE);
+ window->immortal = config_node_get_bool(node, "immortal", FALSE);
+ window_set_name(window, config_node_get_str(node, "name", NULL));
+ window_set_history(window, config_node_get_str(node, "history_name", NULL));
+ window_set_level(window, level2bits(config_node_get_str(node, "level", ""), NULL));
+
+ window->servertag = g_strdup(config_node_get_str(node, "servertag", NULL));
+ window->theme_name = g_strdup(config_node_get_str(node, "theme", NULL));
+ if (window->theme_name != NULL)
+ window->theme = theme_load(window->theme_name);
+
+ window_add_items(window, iconfig_node_section(node, "items", -1));
+ signal_emit("layout restore window", 2, window, node);
+ }
+}
+
+static void sig_layout_save_item(WINDOW_REC *window, WI_ITEM_REC *item,
+ CONFIG_NODE *node)
+{
+ CONFIG_NODE *subnode;
+ CHAT_PROTOCOL_REC *proto;
+ const char *type;
+ WINDOW_BIND_REC *rec;
+
+ type = module_find_id_str("WINDOW ITEM TYPE", item->type);
+ if (type == NULL)
+ return;
+
+ subnode = iconfig_node_section(node, NULL, NODE_TYPE_BLOCK);
+
+ iconfig_node_set_str(subnode, "type", type);
+ proto = item->chat_type == 0 ? NULL :
+ chat_protocol_find_id(item->chat_type);
+ if (proto != NULL)
+ iconfig_node_set_str(subnode, "chat_type", proto->name);
+ iconfig_node_set_str(subnode, "name", item->visible_name);
+
+ if (item->server != NULL) {
+ iconfig_node_set_str(subnode, "tag", item->server->tag);
+ if (IS_CHANNEL(item)) {
+ rec = window_bind_add(window, item->server->tag, item->visible_name);
+ if (rec != NULL)
+ rec->sticky = TRUE;
+ }
+ } else if (IS_QUERY(item)) {
+ iconfig_node_set_str(subnode, "tag", QUERY(item)->server_tag);
+ }
+}
+
+static void window_save_items(WINDOW_REC *window, CONFIG_NODE *node)
+{
+ GSList *tmp;
+
+ node = iconfig_node_section(node, "items", NODE_TYPE_LIST);
+ for (tmp = window->items; tmp != NULL; tmp = tmp->next)
+ signal_emit("layout save item", 3, window, tmp->data, node);
+}
+
+static void window_save(WINDOW_REC *window, CONFIG_NODE *node)
+{
+ char refnum[MAX_INT_STRLEN];
+
+ ltoa(refnum, window->refnum);
+ node = iconfig_node_section(node, refnum, NODE_TYPE_BLOCK);
+
+ if (window->sticky_refnum)
+ iconfig_node_set_bool(node, "sticky_refnum", TRUE);
+
+ if (window->immortal)
+ iconfig_node_set_bool(node, "immortal", TRUE);
+
+ if (window->name != NULL)
+ iconfig_node_set_str(node, "name", window->name);
+
+ if (window->history_name != NULL)
+ iconfig_node_set_str(node, "history_name", window->history_name);
+
+ if (window->servertag != NULL)
+ iconfig_node_set_str(node, "servertag", window->servertag);
+ if (window->level != 0) {
+ char *level = bits2level(window->level);
+ iconfig_node_set_str(node, "level", level);
+ g_free(level);
+ }
+ if (window->theme_name != NULL)
+ iconfig_node_set_str(node, "theme", window->theme_name);
+
+ while (window->bound_items != NULL)
+ window_bind_destroy(window, window->bound_items->data);
+ if (window->items != NULL)
+ window_save_items(window, node);
+
+ signal_emit("layout save window", 2, window, node);
+}
+
+void windows_layout_save(void)
+{
+ CONFIG_NODE *node;
+ GSList *sorted;
+
+ iconfig_set_str(NULL, "windows", NULL);
+ node = iconfig_node_traverse("windows", TRUE);
+
+ sorted = windows_get_sorted();
+ g_slist_foreach(sorted, (GFunc) window_save, node);
+ g_slist_free(sorted);
+ signal_emit("layout save", 0);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_WINDOWS_LAYOUT_SAVED);
+}
+
+void windows_layout_reset(void)
+{
+ GSList *tmp;
+
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *window = tmp->data;
+ while (window->bound_items != NULL)
+ window_bind_destroy(window, window->bound_items->data);
+ }
+
+ iconfig_set_str(NULL, "windows", NULL);
+ signal_emit("layout reset", 0);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_WINDOWS_LAYOUT_RESET);
+}
+
+void windows_layout_init(void)
+{
+ signal_add("layout restore item", (SIGNAL_FUNC) sig_layout_restore_item);
+ signal_add("layout restore", (SIGNAL_FUNC) sig_layout_restore);
+ signal_add("layout save item", (SIGNAL_FUNC) sig_layout_save_item);
+}
+
+void windows_layout_deinit(void)
+{
+ signal_remove("layout restore item", (SIGNAL_FUNC) sig_layout_restore_item);
+ signal_remove("layout restore", (SIGNAL_FUNC) sig_layout_restore);
+ signal_remove("layout save item", (SIGNAL_FUNC) sig_layout_save_item);
+}
diff --git a/src/fe-common/core/windows-layout.h b/src/fe-common/core/windows-layout.h
new file mode 100644
index 0000000..706fb78
--- /dev/null
+++ b/src/fe-common/core/windows-layout.h
@@ -0,0 +1,11 @@
+#ifndef IRSSI_FE_COMMON_CORE_WINDOWS_LAYOUT_H
+#define IRSSI_FE_COMMON_CORE_WINDOWS_LAYOUT_H
+
+void windows_layout_restore(void);
+void windows_layout_save(void);
+void windows_layout_reset(void);
+
+void windows_layout_init(void);
+void windows_layout_deinit(void);
+
+#endif
diff --git a/src/fe-common/irc/Makefile.am b/src/fe-common/irc/Makefile.am
new file mode 100644
index 0000000..8a04bee
--- /dev/null
+++ b/src/fe-common/irc/Makefile.am
@@ -0,0 +1,42 @@
+SUBDIRS = dcc notifylist
+
+noinst_LIBRARIES = libfe_common_irc.a
+
+AM_CPPFLAGS = \
+ -I$(top_builddir) \
+ $(GLIB_CFLAGS) \
+ -DHELPDIR=\""$(datadir)/irssi/help"\" \
+ -DSYSCONFDIR=\""$(sysconfdir)"\"
+
+real_sources = \
+ fe-irc-channels.c \
+ fe-irc-commands.c \
+ fe-irc-messages.c \
+ fe-irc-queries.c \
+ fe-irc-server.c \
+ fe-ircnet.c \
+ fe-ctcp.c \
+ fe-events.c \
+ fe-events-numeric.c \
+ fe-modes.c \
+ fe-netjoin.c \
+ fe-netsplit.c \
+ fe-common-irc.c \
+ fe-whois.c \
+ fe-sasl.c \
+ fe-cap.c \
+ irc-completion.c \
+ module-formats.c
+
+libfe_common_irc_a_SOURCES = \
+ $(real_sources) \
+ irc-modules.c
+
+pkginc_fe_common_ircdir=$(pkgincludedir)/src/fe-common/irc
+pkginc_fe_common_irc_HEADERS = \
+ fe-irc-server.h \
+ fe-irc-channels.h \
+ module.h \
+ module-formats.h
+
+EXTRA_DIST = meson.build
diff --git a/src/fe-common/irc/Makefile.in b/src/fe-common/irc/Makefile.in
new file mode 100644
index 0000000..202076b
--- /dev/null
+++ b/src/fe-common/irc/Makefile.in
@@ -0,0 +1,929 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/fe-common/irc
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/glib-2.0.m4 \
+ $(top_srcdir)/m4/glibtests.m4 $(top_srcdir)/m4/libgcrypt.m4 \
+ $(top_srcdir)/m4/libotr.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
+ $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
+ $(top_srcdir)/m4/pkg.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_fe_common_irc_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/irssi-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+ARFLAGS = cru
+AM_V_AR = $(am__v_AR_@AM_V@)
+am__v_AR_ = $(am__v_AR_@AM_DEFAULT_V@)
+am__v_AR_0 = @echo " AR " $@;
+am__v_AR_1 =
+libfe_common_irc_a_AR = $(AR) $(ARFLAGS)
+libfe_common_irc_a_LIBADD =
+am__objects_1 = fe-irc-channels.$(OBJEXT) fe-irc-commands.$(OBJEXT) \
+ fe-irc-messages.$(OBJEXT) fe-irc-queries.$(OBJEXT) \
+ fe-irc-server.$(OBJEXT) fe-ircnet.$(OBJEXT) fe-ctcp.$(OBJEXT) \
+ fe-events.$(OBJEXT) fe-events-numeric.$(OBJEXT) \
+ fe-modes.$(OBJEXT) fe-netjoin.$(OBJEXT) fe-netsplit.$(OBJEXT) \
+ fe-common-irc.$(OBJEXT) fe-whois.$(OBJEXT) fe-sasl.$(OBJEXT) \
+ fe-cap.$(OBJEXT) irc-completion.$(OBJEXT) \
+ module-formats.$(OBJEXT)
+am_libfe_common_irc_a_OBJECTS = $(am__objects_1) irc-modules.$(OBJEXT)
+libfe_common_irc_a_OBJECTS = $(am_libfe_common_irc_a_OBJECTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES =
+depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/fe-cap.Po \
+ ./$(DEPDIR)/fe-common-irc.Po ./$(DEPDIR)/fe-ctcp.Po \
+ ./$(DEPDIR)/fe-events-numeric.Po ./$(DEPDIR)/fe-events.Po \
+ ./$(DEPDIR)/fe-irc-channels.Po ./$(DEPDIR)/fe-irc-commands.Po \
+ ./$(DEPDIR)/fe-irc-messages.Po ./$(DEPDIR)/fe-irc-queries.Po \
+ ./$(DEPDIR)/fe-irc-server.Po ./$(DEPDIR)/fe-ircnet.Po \
+ ./$(DEPDIR)/fe-modes.Po ./$(DEPDIR)/fe-netjoin.Po \
+ ./$(DEPDIR)/fe-netsplit.Po ./$(DEPDIR)/fe-sasl.Po \
+ ./$(DEPDIR)/fe-whois.Po ./$(DEPDIR)/irc-completion.Po \
+ ./$(DEPDIR)/irc-modules.Po ./$(DEPDIR)/module-formats.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libfe_common_irc_a_SOURCES)
+DIST_SOURCES = $(libfe_common_irc_a_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(pkginc_fe_common_ircdir)"
+HEADERS = $(pkginc_fe_common_irc_HEADERS)
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in \
+ $(top_srcdir)/build-aux/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHAT_MODULES = @CHAT_MODULES@
+COMMON_LIBS = @COMMON_LIBS@
+COMMON_NOUI_LIBS = @COMMON_NOUI_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+FUZZER_LIBS = @FUZZER_LIBS@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_GENMARSHAL = @GLIB_GENMARSHAL@
+GLIB_LIBS = @GLIB_LIBS@
+GLIB_MKENUMS = @GLIB_MKENUMS@
+GOBJECT_QUERY = @GOBJECT_QUERY@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
+LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
+LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBOTR_CFLAGS = @LIBOTR_CFLAGS@
+LIBOTR_LIBS = @LIBOTR_LIBS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENSSL_CFLAGS = @OPENSSL_CFLAGS@
+OPENSSL_LIBS = @OPENSSL_LIBS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+OTR_CFLAGS = @OTR_CFLAGS@
+OTR_LDFLAGS = @OTR_LDFLAGS@
+OTR_LINK_FLAGS = @OTR_LINK_FLAGS@
+OTR_LINK_LIBS = @OTR_LINK_LIBS@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PERL_CFLAGS = @PERL_CFLAGS@
+PERL_EXTRA_OPTS = @PERL_EXTRA_OPTS@
+PERL_FE_LINK_LIBS = @PERL_FE_LINK_LIBS@
+PERL_LDFLAGS = @PERL_LDFLAGS@
+PERL_LINK_FLAGS = @PERL_LINK_FLAGS@
+PERL_LINK_LIBS = @PERL_LINK_LIBS@
+PERL_MM_OPT = @PERL_MM_OPT@
+PERL_MM_PARAMS = @PERL_MM_PARAMS@
+PERL_STATIC_LIBS = @PERL_STATIC_LIBS@
+PERL_USE_LIB = @PERL_USE_LIB@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PROG_LIBS = @PROG_LIBS@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+TEXTUI_LIBS = @TEXTUI_LIBS@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+installed_test_metadir = @installed_test_metadir@
+installed_testdir = @installed_testdir@
+irc_MODULES = @irc_MODULES@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+otr_module_lib = @otr_module_lib@
+otr_static_lib = @otr_static_lib@
+pdfdir = @pdfdir@
+perl_module_fe_lib = @perl_module_fe_lib@
+perl_module_lib = @perl_module_lib@
+perl_static_fe_lib = @perl_static_fe_lib@
+perl_static_lib = @perl_static_lib@
+perlpath = @perlpath@
+pkgconfigdir = @pkgconfigdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sedpath = @sedpath@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = dcc notifylist
+noinst_LIBRARIES = libfe_common_irc.a
+AM_CPPFLAGS = \
+ -I$(top_builddir) \
+ $(GLIB_CFLAGS) \
+ -DHELPDIR=\""$(datadir)/irssi/help"\" \
+ -DSYSCONFDIR=\""$(sysconfdir)"\"
+
+real_sources = \
+ fe-irc-channels.c \
+ fe-irc-commands.c \
+ fe-irc-messages.c \
+ fe-irc-queries.c \
+ fe-irc-server.c \
+ fe-ircnet.c \
+ fe-ctcp.c \
+ fe-events.c \
+ fe-events-numeric.c \
+ fe-modes.c \
+ fe-netjoin.c \
+ fe-netsplit.c \
+ fe-common-irc.c \
+ fe-whois.c \
+ fe-sasl.c \
+ fe-cap.c \
+ irc-completion.c \
+ module-formats.c
+
+libfe_common_irc_a_SOURCES = \
+ $(real_sources) \
+ irc-modules.c
+
+pkginc_fe_common_ircdir = $(pkgincludedir)/src/fe-common/irc
+pkginc_fe_common_irc_HEADERS = \
+ fe-irc-server.h \
+ fe-irc-channels.h \
+ module.h \
+ module-formats.h
+
+EXTRA_DIST = meson.build
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/fe-common/irc/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/fe-common/irc/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+ -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+
+libfe_common_irc.a: $(libfe_common_irc_a_OBJECTS) $(libfe_common_irc_a_DEPENDENCIES) $(EXTRA_libfe_common_irc_a_DEPENDENCIES)
+ $(AM_V_at)-rm -f libfe_common_irc.a
+ $(AM_V_AR)$(libfe_common_irc_a_AR) libfe_common_irc.a $(libfe_common_irc_a_OBJECTS) $(libfe_common_irc_a_LIBADD)
+ $(AM_V_at)$(RANLIB) libfe_common_irc.a
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-cap.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-common-irc.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-ctcp.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-events-numeric.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-events.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-irc-channels.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-irc-commands.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-irc-messages.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-irc-queries.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-irc-server.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-ircnet.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-modes.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-netjoin.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-netsplit.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-sasl.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-whois.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/irc-completion.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/irc-modules.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/module-formats.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_fe_common_ircHEADERS: $(pkginc_fe_common_irc_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_fe_common_irc_HEADERS)'; test -n "$(pkginc_fe_common_ircdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_fe_common_ircdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_fe_common_ircdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_fe_common_ircdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_fe_common_ircdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_fe_common_ircHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_fe_common_irc_HEADERS)'; test -n "$(pkginc_fe_common_ircdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_fe_common_ircdir)'; $(am__uninstall_files_from_dir)
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(LIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(pkginc_fe_common_ircdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool clean-noinstLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/fe-cap.Po
+ -rm -f ./$(DEPDIR)/fe-common-irc.Po
+ -rm -f ./$(DEPDIR)/fe-ctcp.Po
+ -rm -f ./$(DEPDIR)/fe-events-numeric.Po
+ -rm -f ./$(DEPDIR)/fe-events.Po
+ -rm -f ./$(DEPDIR)/fe-irc-channels.Po
+ -rm -f ./$(DEPDIR)/fe-irc-commands.Po
+ -rm -f ./$(DEPDIR)/fe-irc-messages.Po
+ -rm -f ./$(DEPDIR)/fe-irc-queries.Po
+ -rm -f ./$(DEPDIR)/fe-irc-server.Po
+ -rm -f ./$(DEPDIR)/fe-ircnet.Po
+ -rm -f ./$(DEPDIR)/fe-modes.Po
+ -rm -f ./$(DEPDIR)/fe-netjoin.Po
+ -rm -f ./$(DEPDIR)/fe-netsplit.Po
+ -rm -f ./$(DEPDIR)/fe-sasl.Po
+ -rm -f ./$(DEPDIR)/fe-whois.Po
+ -rm -f ./$(DEPDIR)/irc-completion.Po
+ -rm -f ./$(DEPDIR)/irc-modules.Po
+ -rm -f ./$(DEPDIR)/module-formats.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am: install-pkginc_fe_common_ircHEADERS
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/fe-cap.Po
+ -rm -f ./$(DEPDIR)/fe-common-irc.Po
+ -rm -f ./$(DEPDIR)/fe-ctcp.Po
+ -rm -f ./$(DEPDIR)/fe-events-numeric.Po
+ -rm -f ./$(DEPDIR)/fe-events.Po
+ -rm -f ./$(DEPDIR)/fe-irc-channels.Po
+ -rm -f ./$(DEPDIR)/fe-irc-commands.Po
+ -rm -f ./$(DEPDIR)/fe-irc-messages.Po
+ -rm -f ./$(DEPDIR)/fe-irc-queries.Po
+ -rm -f ./$(DEPDIR)/fe-irc-server.Po
+ -rm -f ./$(DEPDIR)/fe-ircnet.Po
+ -rm -f ./$(DEPDIR)/fe-modes.Po
+ -rm -f ./$(DEPDIR)/fe-netjoin.Po
+ -rm -f ./$(DEPDIR)/fe-netsplit.Po
+ -rm -f ./$(DEPDIR)/fe-sasl.Po
+ -rm -f ./$(DEPDIR)/fe-whois.Po
+ -rm -f ./$(DEPDIR)/irc-completion.Po
+ -rm -f ./$(DEPDIR)/irc-modules.Po
+ -rm -f ./$(DEPDIR)/module-formats.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-pkginc_fe_common_ircHEADERS
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic clean-libtool \
+ clean-noinstLIBRARIES cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-pkginc_fe_common_ircHEADERS \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs installdirs-am maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-pkginc_fe_common_ircHEADERS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/fe-common/irc/dcc/Makefile.am b/src/fe-common/irc/dcc/Makefile.am
new file mode 100644
index 0000000..feee349
--- /dev/null
+++ b/src/fe-common/irc/dcc/Makefile.am
@@ -0,0 +1,24 @@
+noinst_LIBRARIES = libfe_irc_dcc.a
+
+AM_CPPFLAGS = \
+ -I$(top_builddir) \
+ $(GLIB_CFLAGS) \
+ -DHELPDIR=\""$(datadir)/irssi/help"\" \
+ -DSYSCONFDIR=\""$(sysconfdir)"\"
+
+libfe_irc_dcc_a_SOURCES = \
+ fe-dcc.c \
+ fe-dcc-chat.c \
+ fe-dcc-chat-messages.c \
+ fe-dcc-get.c \
+ fe-dcc-send.c \
+ module-formats.c \
+ fe-dcc-server.c
+
+pkginc_fe_common_irc_dccdir=$(pkgincludedir)/src/fe-common/irc/dcc
+pkginc_fe_common_irc_dcc_HEADERS = \
+ module.h \
+ module-formats.h \
+ fe-dcc.h
+
+EXTRA_DIST = meson.build
diff --git a/src/fe-common/irc/dcc/Makefile.in b/src/fe-common/irc/dcc/Makefile.in
new file mode 100644
index 0000000..f73bf3b
--- /dev/null
+++ b/src/fe-common/irc/dcc/Makefile.in
@@ -0,0 +1,750 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/fe-common/irc/dcc
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/glib-2.0.m4 \
+ $(top_srcdir)/m4/glibtests.m4 $(top_srcdir)/m4/libgcrypt.m4 \
+ $(top_srcdir)/m4/libotr.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
+ $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
+ $(top_srcdir)/m4/pkg.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am \
+ $(pkginc_fe_common_irc_dcc_HEADERS) $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/irssi-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+ARFLAGS = cru
+AM_V_AR = $(am__v_AR_@AM_V@)
+am__v_AR_ = $(am__v_AR_@AM_DEFAULT_V@)
+am__v_AR_0 = @echo " AR " $@;
+am__v_AR_1 =
+libfe_irc_dcc_a_AR = $(AR) $(ARFLAGS)
+libfe_irc_dcc_a_LIBADD =
+am_libfe_irc_dcc_a_OBJECTS = fe-dcc.$(OBJEXT) fe-dcc-chat.$(OBJEXT) \
+ fe-dcc-chat-messages.$(OBJEXT) fe-dcc-get.$(OBJEXT) \
+ fe-dcc-send.$(OBJEXT) module-formats.$(OBJEXT) \
+ fe-dcc-server.$(OBJEXT)
+libfe_irc_dcc_a_OBJECTS = $(am_libfe_irc_dcc_a_OBJECTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES =
+depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/fe-dcc-chat-messages.Po \
+ ./$(DEPDIR)/fe-dcc-chat.Po ./$(DEPDIR)/fe-dcc-get.Po \
+ ./$(DEPDIR)/fe-dcc-send.Po ./$(DEPDIR)/fe-dcc-server.Po \
+ ./$(DEPDIR)/fe-dcc.Po ./$(DEPDIR)/module-formats.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libfe_irc_dcc_a_SOURCES)
+DIST_SOURCES = $(libfe_irc_dcc_a_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(pkginc_fe_common_irc_dccdir)"
+HEADERS = $(pkginc_fe_common_irc_dcc_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in \
+ $(top_srcdir)/build-aux/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHAT_MODULES = @CHAT_MODULES@
+COMMON_LIBS = @COMMON_LIBS@
+COMMON_NOUI_LIBS = @COMMON_NOUI_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+FUZZER_LIBS = @FUZZER_LIBS@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_GENMARSHAL = @GLIB_GENMARSHAL@
+GLIB_LIBS = @GLIB_LIBS@
+GLIB_MKENUMS = @GLIB_MKENUMS@
+GOBJECT_QUERY = @GOBJECT_QUERY@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
+LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
+LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBOTR_CFLAGS = @LIBOTR_CFLAGS@
+LIBOTR_LIBS = @LIBOTR_LIBS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENSSL_CFLAGS = @OPENSSL_CFLAGS@
+OPENSSL_LIBS = @OPENSSL_LIBS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+OTR_CFLAGS = @OTR_CFLAGS@
+OTR_LDFLAGS = @OTR_LDFLAGS@
+OTR_LINK_FLAGS = @OTR_LINK_FLAGS@
+OTR_LINK_LIBS = @OTR_LINK_LIBS@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PERL_CFLAGS = @PERL_CFLAGS@
+PERL_EXTRA_OPTS = @PERL_EXTRA_OPTS@
+PERL_FE_LINK_LIBS = @PERL_FE_LINK_LIBS@
+PERL_LDFLAGS = @PERL_LDFLAGS@
+PERL_LINK_FLAGS = @PERL_LINK_FLAGS@
+PERL_LINK_LIBS = @PERL_LINK_LIBS@
+PERL_MM_OPT = @PERL_MM_OPT@
+PERL_MM_PARAMS = @PERL_MM_PARAMS@
+PERL_STATIC_LIBS = @PERL_STATIC_LIBS@
+PERL_USE_LIB = @PERL_USE_LIB@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PROG_LIBS = @PROG_LIBS@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+TEXTUI_LIBS = @TEXTUI_LIBS@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+installed_test_metadir = @installed_test_metadir@
+installed_testdir = @installed_testdir@
+irc_MODULES = @irc_MODULES@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+otr_module_lib = @otr_module_lib@
+otr_static_lib = @otr_static_lib@
+pdfdir = @pdfdir@
+perl_module_fe_lib = @perl_module_fe_lib@
+perl_module_lib = @perl_module_lib@
+perl_static_fe_lib = @perl_static_fe_lib@
+perl_static_lib = @perl_static_lib@
+perlpath = @perlpath@
+pkgconfigdir = @pkgconfigdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sedpath = @sedpath@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LIBRARIES = libfe_irc_dcc.a
+AM_CPPFLAGS = \
+ -I$(top_builddir) \
+ $(GLIB_CFLAGS) \
+ -DHELPDIR=\""$(datadir)/irssi/help"\" \
+ -DSYSCONFDIR=\""$(sysconfdir)"\"
+
+libfe_irc_dcc_a_SOURCES = \
+ fe-dcc.c \
+ fe-dcc-chat.c \
+ fe-dcc-chat-messages.c \
+ fe-dcc-get.c \
+ fe-dcc-send.c \
+ module-formats.c \
+ fe-dcc-server.c
+
+pkginc_fe_common_irc_dccdir = $(pkgincludedir)/src/fe-common/irc/dcc
+pkginc_fe_common_irc_dcc_HEADERS = \
+ module.h \
+ module-formats.h \
+ fe-dcc.h
+
+EXTRA_DIST = meson.build
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/fe-common/irc/dcc/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/fe-common/irc/dcc/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+ -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+
+libfe_irc_dcc.a: $(libfe_irc_dcc_a_OBJECTS) $(libfe_irc_dcc_a_DEPENDENCIES) $(EXTRA_libfe_irc_dcc_a_DEPENDENCIES)
+ $(AM_V_at)-rm -f libfe_irc_dcc.a
+ $(AM_V_AR)$(libfe_irc_dcc_a_AR) libfe_irc_dcc.a $(libfe_irc_dcc_a_OBJECTS) $(libfe_irc_dcc_a_LIBADD)
+ $(AM_V_at)$(RANLIB) libfe_irc_dcc.a
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-dcc-chat-messages.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-dcc-chat.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-dcc-get.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-dcc-send.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-dcc-server.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-dcc.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/module-formats.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_fe_common_irc_dccHEADERS: $(pkginc_fe_common_irc_dcc_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_fe_common_irc_dcc_HEADERS)'; test -n "$(pkginc_fe_common_irc_dccdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_fe_common_irc_dccdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_fe_common_irc_dccdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_fe_common_irc_dccdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_fe_common_irc_dccdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_fe_common_irc_dccHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_fe_common_irc_dcc_HEADERS)'; test -n "$(pkginc_fe_common_irc_dccdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_fe_common_irc_dccdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(pkginc_fe_common_irc_dccdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/fe-dcc-chat-messages.Po
+ -rm -f ./$(DEPDIR)/fe-dcc-chat.Po
+ -rm -f ./$(DEPDIR)/fe-dcc-get.Po
+ -rm -f ./$(DEPDIR)/fe-dcc-send.Po
+ -rm -f ./$(DEPDIR)/fe-dcc-server.Po
+ -rm -f ./$(DEPDIR)/fe-dcc.Po
+ -rm -f ./$(DEPDIR)/module-formats.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-pkginc_fe_common_irc_dccHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/fe-dcc-chat-messages.Po
+ -rm -f ./$(DEPDIR)/fe-dcc-chat.Po
+ -rm -f ./$(DEPDIR)/fe-dcc-get.Po
+ -rm -f ./$(DEPDIR)/fe-dcc-send.Po
+ -rm -f ./$(DEPDIR)/fe-dcc-server.Po
+ -rm -f ./$(DEPDIR)/fe-dcc.Po
+ -rm -f ./$(DEPDIR)/module-formats.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-pkginc_fe_common_irc_dccHEADERS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-pkginc_fe_common_irc_dccHEADERS \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-pkginc_fe_common_irc_dccHEADERS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/fe-common/irc/dcc/fe-dcc-chat-messages.c b/src/fe-common/irc/dcc/fe-dcc-chat-messages.c
new file mode 100644
index 0000000..8dd67f7
--- /dev/null
+++ b/src/fe-common/irc/dcc/fe-dcc-chat-messages.c
@@ -0,0 +1,164 @@
+/*
+ fe-dcc-chat-messages.c : irssi
+
+ Copyright (C) 2002 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/levels.h>
+
+#include <irssi/src/irc/core/irc-servers.h>
+#include <irssi/src/irc/core/irc-queries.h>
+#include <irssi/src/irc/dcc/dcc-chat.h>
+#include <irssi/src/core/ignore.h>
+
+#include <irssi/src/fe-common/irc/dcc/module-formats.h>
+#include <irssi/src/fe-common/core/printtext.h>
+
+static void sig_message_dcc_own(CHAT_DCC_REC *dcc, const char *msg)
+{
+ TEXT_DEST_REC dest;
+ QUERY_REC *query;
+ char *tag;
+
+ tag = g_strconcat("=", dcc->id, NULL);
+ query = query_find(NULL, tag);
+
+ format_create_dest_tag(&dest, dcc->server, dcc->servertag, tag,
+ MSGLEVEL_DCCMSGS | MSGLEVEL_NOHILIGHT |
+ MSGLEVEL_NO_ACT, NULL);
+
+ printformat_dest(&dest, query != NULL ? IRCTXT_OWN_DCC_QUERY :
+ IRCTXT_OWN_DCC, dcc->mynick, dcc->id, msg);
+ g_free(tag);
+}
+
+static void sig_message_dcc_own_action(CHAT_DCC_REC *dcc, const char *msg)
+{
+ TEXT_DEST_REC dest;
+ QUERY_REC *query;
+ char *tag;
+
+ tag = g_strconcat("=", dcc->id, NULL);
+ query = query_find(NULL, tag);
+
+ format_create_dest_tag(&dest, dcc->server, dcc->servertag, tag,
+ MSGLEVEL_DCCMSGS | MSGLEVEL_ACTIONS |
+ MSGLEVEL_NOHILIGHT | MSGLEVEL_NO_ACT, NULL);
+
+ printformat_dest(&dest, query != NULL ? IRCTXT_OWN_DCC_ACTION_QUERY :
+ IRCTXT_OWN_DCC_ACTION, dcc->mynick, dcc->id, msg);
+ g_free(tag);
+}
+
+static void sig_message_dcc_own_ctcp(CHAT_DCC_REC *dcc, const char *cmd,
+ const char *data)
+{
+ TEXT_DEST_REC dest;
+ char *tag;
+
+ tag = g_strconcat("=", dcc->id, NULL);
+
+ format_create_dest_tag(&dest, dcc->server, dcc->servertag, tag,
+ MSGLEVEL_DCC | MSGLEVEL_CTCPS |
+ MSGLEVEL_NOHILIGHT | MSGLEVEL_NO_ACT, NULL);
+
+ printformat_dest(&dest, IRCTXT_OWN_DCC_CTCP, dcc->id, cmd, data);
+ g_free(tag);
+}
+
+static void sig_message_dcc(CHAT_DCC_REC *dcc, const char *msg)
+{
+ TEXT_DEST_REC dest;
+ QUERY_REC *query;
+ char *tag;
+ int level = MSGLEVEL_DCCMSGS;
+
+ tag = g_strconcat("=", dcc->id, NULL);
+ query = query_find(NULL, tag);
+
+ ignore_check_plus(SERVER(dcc->server), tag, dcc->addrstr, NULL, msg,
+ &level, FALSE);
+
+ format_create_dest_tag(&dest, dcc->server, dcc->servertag, tag,
+ level, NULL);
+
+ printformat_dest(&dest, query != NULL ? IRCTXT_DCC_MSG_QUERY :
+ IRCTXT_DCC_MSG, dcc->id, msg);
+ g_free(tag);
+}
+
+static void sig_message_dcc_action(CHAT_DCC_REC *dcc, const char *msg)
+{
+ TEXT_DEST_REC dest;
+ QUERY_REC *query;
+ char *tag;
+ int level = MSGLEVEL_DCCMSGS | MSGLEVEL_ACTIONS;
+
+ tag = g_strconcat("=", dcc->id, NULL);
+ query = query_find(NULL, tag);
+
+ ignore_check_plus(SERVER(dcc->server), tag, dcc->addrstr, NULL, msg,
+ &level, FALSE);
+
+ format_create_dest_tag(&dest, dcc->server, dcc->servertag, tag,
+ level, NULL);
+
+ printformat_dest(&dest, query != NULL ? IRCTXT_ACTION_DCC_QUERY :
+ IRCTXT_ACTION_DCC, dcc->id, msg);
+ g_free(tag);
+}
+
+static void sig_message_dcc_ctcp(CHAT_DCC_REC *dcc, const char *cmd,
+ const char *data)
+{
+ TEXT_DEST_REC dest;
+ char *tag;
+ int level = MSGLEVEL_DCCMSGS | MSGLEVEL_CTCPS;
+
+ tag = g_strconcat("=", dcc->id, NULL);
+
+ ignore_check_plus(SERVER(dcc->server), tag, dcc->addrstr, NULL, cmd,
+ &level, FALSE);
+
+ format_create_dest_tag(&dest, dcc->server, dcc->servertag, tag,
+ level, NULL);
+
+ printformat_dest(&dest, IRCTXT_DCC_CTCP, dcc->id, cmd, data);
+ g_free(tag);
+}
+
+void fe_dcc_chat_messages_init(void)
+{
+ signal_add("message dcc own", (SIGNAL_FUNC) sig_message_dcc_own);
+ signal_add("message dcc own_action", (SIGNAL_FUNC) sig_message_dcc_own_action);
+ signal_add("message dcc own_ctcp", (SIGNAL_FUNC) sig_message_dcc_own_ctcp);
+ signal_add("message dcc", (SIGNAL_FUNC) sig_message_dcc);
+ signal_add("message dcc action", (SIGNAL_FUNC) sig_message_dcc_action);
+ signal_add("message dcc ctcp", (SIGNAL_FUNC) sig_message_dcc_ctcp);
+}
+
+void fe_dcc_chat_messages_deinit(void)
+{
+ signal_remove("message dcc own", (SIGNAL_FUNC) sig_message_dcc_own);
+ signal_remove("message dcc own_action", (SIGNAL_FUNC) sig_message_dcc_own_action);
+ signal_remove("message dcc own_ctcp", (SIGNAL_FUNC) sig_message_dcc_own_ctcp);
+ signal_remove("message dcc", (SIGNAL_FUNC) sig_message_dcc);
+ signal_remove("message dcc action", (SIGNAL_FUNC) sig_message_dcc_action);
+ signal_remove("message dcc ctcp", (SIGNAL_FUNC) sig_message_dcc_ctcp);
+}
diff --git a/src/fe-common/irc/dcc/fe-dcc-chat.c b/src/fe-common/irc/dcc/fe-dcc-chat.c
new file mode 100644
index 0000000..4099e2f
--- /dev/null
+++ b/src/fe-common/irc/dcc/fe-dcc-chat.c
@@ -0,0 +1,385 @@
+/*
+ fe-dcc-chat.c : irssi
+
+ Copyright (C) 1999-2002 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/misc.h>
+
+#include <irssi/src/irc/core/irc.h>
+#include <irssi/src/irc/core/irc-servers.h>
+#include <irssi/src/irc/core/irc-queries.h>
+#include <irssi/src/irc/dcc/dcc-chat.h>
+
+#include <irssi/src/fe-common/irc/dcc/module-formats.h>
+#include <irssi/src/fe-common/core/printtext.h>
+#include <irssi/src/fe-common/core/fe-messages.h>
+
+#include <irssi/src/fe-common/core/chat-completion.h>
+
+void fe_dcc_chat_messages_init(void);
+void fe_dcc_chat_messages_deinit(void);
+
+static void dcc_request(CHAT_DCC_REC *dcc)
+{
+ if (!IS_DCC_CHAT(dcc)) return;
+
+ printformat(dcc->server, NULL, MSGLEVEL_DCC,
+ server_ischannel(SERVER(dcc->server), dcc->target) ? IRCTXT_DCC_CHAT_CHANNEL :
+ IRCTXT_DCC_CHAT, dcc->id, dcc->addrstr,
+ dcc->port, dcc->target);
+}
+
+static void dcc_connected(CHAT_DCC_REC *dcc)
+{
+ char *sender;
+
+ if (!IS_DCC_CHAT(dcc)) return;
+
+ sender = g_strconcat("=", dcc->id, NULL);
+ printformat(dcc->server, NULL, MSGLEVEL_DCC,
+ IRCTXT_DCC_CHAT_CONNECTED,
+ dcc->id, dcc->addrstr, dcc->port);
+
+ if (query_find(NULL, sender) == NULL) {
+ int level = settings_get_level("autocreate_query_level");
+ int autocreate_dccquery = (level & MSGLEVEL_DCCMSGS) != 0;
+
+ if (!autocreate_dccquery)
+ completion_last_message_add(sender);
+ else
+ irc_query_create(dcc->servertag, sender, TRUE);
+ }
+ g_free(sender);
+}
+
+static void dcc_closed(CHAT_DCC_REC *dcc)
+{
+ char *sender;
+
+ if (!IS_DCC_CHAT(dcc)) return;
+
+ sender = g_strconcat("=", dcc->id, NULL);
+ printformat(dcc->server, NULL, MSGLEVEL_DCC,
+ IRCTXT_DCC_CHAT_DISCONNECTED, dcc->id);
+ g_free(sender);
+}
+
+static void dcc_chat_msg(CHAT_DCC_REC *dcc, const char *msg)
+{
+ QUERY_REC *query;
+ char *sender, *freemsg;
+
+ g_return_if_fail(IS_DCC_CHAT(dcc));
+ g_return_if_fail(msg != NULL);
+
+ sender = g_strconcat("=", dcc->id, NULL);
+ query = query_find(NULL, sender);
+
+ if (settings_get_bool("emphasis"))
+ msg = freemsg = expand_emphasis((WI_ITEM_REC *) query, msg);
+ else
+ freemsg = NULL;
+
+ if (query == NULL)
+ completion_last_message_add(sender);
+ signal_emit("message dcc", 2, dcc, msg);
+
+ g_free_not_null(freemsg);
+ g_free(sender);
+}
+
+static void dcc_chat_action(CHAT_DCC_REC *dcc, const char *msg)
+{
+ char *sender;
+
+ g_return_if_fail(IS_DCC_CHAT(dcc));
+ g_return_if_fail(msg != NULL);
+
+ sender = g_strconcat("=", dcc->id, NULL);
+ if (query_find(NULL, sender) == NULL)
+ completion_last_message_add(sender);
+
+ signal_emit("message dcc action", 2, dcc, msg);
+ g_free(sender);
+}
+
+static void dcc_chat_ctcp(CHAT_DCC_REC *dcc, const char *cmd, const char *data)
+{
+ g_return_if_fail(IS_DCC_CHAT(dcc));
+
+ signal_emit("message dcc ctcp", 3, dcc, cmd, data);
+}
+
+static void dcc_error_ctcp(const char *type, const char *data,
+ const char *nick, const char *addr,
+ const char *target)
+{
+ printformat(NULL, NULL, MSGLEVEL_DCC,
+ IRCTXT_DCC_INVALID_CTCP, type, nick, addr, target);
+}
+
+static void dcc_unknown_ctcp(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr,
+ const char *target, CHAT_DCC_REC *chat)
+{
+ char *type, *args;
+ void *free_arg;
+
+ g_return_if_fail(data != NULL);
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST,
+ &type, &args))
+ return;
+
+ printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_UNKNOWN_CTCP,
+ type, nick, args);
+ cmd_params_free(free_arg);
+}
+
+static void dcc_unknown_reply(IRC_SERVER_REC *server, const char *data,
+ const char *nick)
+{
+ char *type, *args;
+ void *free_arg;
+
+ g_return_if_fail(data != NULL);
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST,
+ &type, &args))
+ return;
+
+ printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_UNKNOWN_REPLY,
+ type, nick, args);
+ cmd_params_free(free_arg);
+}
+
+static void sig_dcc_destroyed(CHAT_DCC_REC *dcc)
+{
+ QUERY_REC *query;
+ char *nick;
+
+ if (!IS_DCC_CHAT(dcc)) return;
+
+ nick = g_strconcat("=", dcc->id, NULL);
+ query = query_find(NULL, nick);
+ if (query != NULL) {
+ /* DCC chat closed, close the query with it. */
+ if (dcc->connection_lost) query->unwanted = TRUE;
+ query_destroy(query);
+ } else {
+ /* remove nick from msg completion
+ since it won't work anymore */
+ completion_last_message_remove(nick);
+ }
+
+ g_free(nick);
+}
+
+static void sig_query_destroyed(QUERY_REC *query)
+{
+ CHAT_DCC_REC *dcc;
+
+ if (*query->name != '=')
+ return;
+
+ dcc = dcc_chat_find_id(query->name+1);
+ if (dcc != NULL && !dcc->destroyed) {
+ /* DCC query window closed, close the dcc chat too. */
+ dcc_close(DCC(dcc));
+ }
+}
+
+static void dcc_error_close_not_found(const char *type, const char *nick,
+ const char *fname)
+{
+ g_return_if_fail(type != NULL);
+ g_return_if_fail(nick != NULL);
+ if (g_ascii_strcasecmp(type, "CHAT") != 0) return;
+
+ printformat(NULL, NULL, MSGLEVEL_DCC,
+ IRCTXT_DCC_CHAT_NOT_FOUND, nick);
+}
+
+static void sig_dcc_list_print(CHAT_DCC_REC *dcc)
+{
+ if (!IS_DCC_CHAT(dcc)) return;
+
+ printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_LIST_LINE_CHAT,
+ dcc->id, "CHAT");
+}
+
+static void cmd_msg(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ CHAT_DCC_REC *dcc;
+ GHashTable *optlist;
+ char *text, *target;
+ void *free_arg;
+
+ g_return_if_fail(data != NULL);
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_UNKNOWN_OPTIONS |
+ PARAM_FLAG_OPTIONS | PARAM_FLAG_GETREST, "msg",
+ &optlist, &target, &text))
+ return;
+
+ /* handle only DCC messages */
+ if (g_strcmp0(target, "*") == 0)
+ dcc = item_get_dcc(item);
+ else if (*target == '=')
+ dcc = dcc_chat_find_id(target+1);
+ else
+ dcc = NULL;
+
+ if (dcc == NULL && *target == '=') {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ IRCTXT_DCC_CHAT_NOT_FOUND, target+1);
+ } else if (dcc != NULL) {
+ if (query_find(NULL, target) == NULL)
+ completion_last_message_add(target);
+
+ signal_emit("message dcc own", 2, dcc, text);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+static void cmd_me(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ CHAT_DCC_REC *dcc;
+
+ dcc = item_get_dcc(item);
+ if (dcc != NULL)
+ signal_emit("message dcc own_action", 2, dcc, data);
+}
+
+static void cmd_action(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ CHAT_DCC_REC *dcc;
+ char *target, *text;
+ void *free_arg;
+
+ g_return_if_fail(data != NULL);
+
+ if (*data != '=') {
+ /* handle only DCC actions */
+ return;
+ }
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST,
+ &target, &text))
+ return;
+ if (*target == '\0' || *text == '\0')
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ dcc = dcc_chat_find_id(target+1);
+ if (dcc == NULL || dcc->sendbuf == NULL) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ IRCTXT_DCC_CHAT_NOT_FOUND, target+1);
+ } else {
+ if (query_find(NULL, target) == NULL)
+ completion_last_message_add(target);
+
+ signal_emit("message dcc own_action", 2, dcc, text);
+ }
+ cmd_params_free(free_arg);
+}
+
+static void cmd_ctcp(const char *data, SERVER_REC *server)
+{
+ CHAT_DCC_REC *dcc;
+ char *target, *ctcpcmd, *ctcpdata;
+ void *free_arg;
+
+ g_return_if_fail(data != NULL);
+ if (server == NULL || !server->connected)
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST,
+ &target, &ctcpcmd, &ctcpdata))
+ return;
+ if (*target == '\0' || *ctcpcmd == '\0')
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ if (*target != '=') {
+ /* handle only DCC CTCPs */
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ dcc = dcc_chat_find_id(target+1);
+ if (dcc == NULL || dcc->sendbuf == NULL) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ IRCTXT_DCC_CHAT_NOT_FOUND, target+1);
+ } else {
+ ascii_strup(ctcpcmd);
+ signal_emit("message dcc own_ctcp", 3, dcc, ctcpcmd, ctcpdata);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+void fe_dcc_chat_init(void)
+{
+ fe_dcc_chat_messages_init();
+
+ signal_add("dcc request", (SIGNAL_FUNC) dcc_request);
+ signal_add("dcc connected", (SIGNAL_FUNC) dcc_connected);
+ signal_add("dcc closed", (SIGNAL_FUNC) dcc_closed);
+ signal_add("dcc chat message", (SIGNAL_FUNC) dcc_chat_msg);
+ signal_add("dcc ctcp action", (SIGNAL_FUNC) dcc_chat_action);
+ signal_add("default dcc ctcp", (SIGNAL_FUNC) dcc_chat_ctcp);
+ signal_add("dcc error ctcp", (SIGNAL_FUNC) dcc_error_ctcp);
+ signal_add("default ctcp msg dcc", (SIGNAL_FUNC) dcc_unknown_ctcp);
+ signal_add("default ctcp reply dcc", (SIGNAL_FUNC) dcc_unknown_reply);
+ signal_add("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed);
+ signal_add("query destroyed", (SIGNAL_FUNC) sig_query_destroyed);
+ signal_add("dcc list print", (SIGNAL_FUNC) sig_dcc_list_print);
+ command_bind("msg", NULL, (SIGNAL_FUNC) cmd_msg);
+ command_bind("me", NULL, (SIGNAL_FUNC) cmd_me);
+ command_bind("action", NULL, (SIGNAL_FUNC) cmd_action);
+ command_bind("ctcp", NULL, (SIGNAL_FUNC) cmd_ctcp);
+ signal_add("dcc error close not found", (SIGNAL_FUNC) dcc_error_close_not_found);
+}
+
+void fe_dcc_chat_deinit(void)
+{
+ fe_dcc_chat_messages_deinit();
+
+ signal_remove("dcc request", (SIGNAL_FUNC) dcc_request);
+ signal_remove("dcc connected", (SIGNAL_FUNC) dcc_connected);
+ signal_remove("dcc closed", (SIGNAL_FUNC) dcc_closed);
+ signal_remove("dcc chat message", (SIGNAL_FUNC) dcc_chat_msg);
+ signal_remove("dcc ctcp action", (SIGNAL_FUNC) dcc_chat_action);
+ signal_remove("default dcc ctcp", (SIGNAL_FUNC) dcc_chat_ctcp);
+ signal_remove("dcc error ctcp", (SIGNAL_FUNC) dcc_error_ctcp);
+ signal_remove("default ctcp msg dcc", (SIGNAL_FUNC) dcc_unknown_ctcp);
+ signal_remove("default ctcp reply dcc", (SIGNAL_FUNC) dcc_unknown_reply);
+ signal_remove("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed);
+ signal_remove("query destroyed", (SIGNAL_FUNC) sig_query_destroyed);
+ signal_remove("dcc list print", (SIGNAL_FUNC) sig_dcc_list_print);
+ command_unbind("msg", (SIGNAL_FUNC) cmd_msg);
+ command_unbind("me", (SIGNAL_FUNC) cmd_me);
+ command_unbind("action", (SIGNAL_FUNC) cmd_action);
+ command_unbind("ctcp", (SIGNAL_FUNC) cmd_ctcp);
+ signal_remove("dcc error close not found", (SIGNAL_FUNC) dcc_error_close_not_found);
+}
diff --git a/src/fe-common/irc/dcc/fe-dcc-get.c b/src/fe-common/irc/dcc/fe-dcc-get.c
new file mode 100644
index 0000000..e75640b
--- /dev/null
+++ b/src/fe-common/irc/dcc/fe-dcc-get.c
@@ -0,0 +1,150 @@
+/*
+ fe-dcc-get.c : irssi
+
+ Copyright (C) 1999-2001 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/servers.h>
+
+#include <irssi/src/irc/core/irc.h>
+#include <irssi/src/irc/dcc/dcc-file.h>
+#include <irssi/src/irc/dcc/dcc-get.h>
+
+#include <irssi/src/fe-common/irc/dcc/module-formats.h>
+#include <irssi/src/fe-common/core/printtext.h>
+
+#include <irssi/src/fe-common/irc/dcc/fe-dcc.h>
+
+static void dcc_request(GET_DCC_REC *dcc)
+{
+ char *sizestr;
+
+ if (!IS_DCC_GET(dcc)) return;
+
+ sizestr = dcc_get_size_str(dcc->size);
+
+ printformat(dcc->server, NULL, MSGLEVEL_DCC,
+ server_ischannel(SERVER(dcc->server), dcc->target) ? IRCTXT_DCC_SEND_CHANNEL :
+ IRCTXT_DCC_SEND, dcc->nick, dcc->addrstr,
+ dcc->port, dcc->arg, sizestr, dcc->target);
+
+ g_free(sizestr);
+}
+
+static void dcc_connected(GET_DCC_REC *dcc)
+{
+ if (!IS_DCC_GET(dcc)) return;
+
+ printformat(dcc->server, NULL, MSGLEVEL_DCC, IRCTXT_DCC_GET_CONNECTED,
+ dcc->arg, dcc->nick, dcc->addrstr, dcc->port);
+}
+
+static void dcc_closed(GET_DCC_REC *dcc)
+{
+ char *sizestr, timestr[20];
+ double kbs;
+ time_t secs;
+
+ if (!IS_DCC_GET(dcc)) return;
+
+ secs = dcc->starttime == 0 ? -1 : time(NULL)-dcc->starttime;
+ kbs = (double) (dcc->transfd-dcc->skipped) /
+ (secs == 0 ? 1 : secs) / 1024.0;
+
+ sizestr = dcc_get_size_str(dcc->transfd);
+ g_snprintf(timestr, sizeof(timestr), "%02d:%02d:%02d",
+ (int)(secs/3600), (int)((secs/60)%60), (int)(secs%60));
+
+ if (secs == -1) {
+ /* aborted */
+ printformat(dcc->server, NULL, MSGLEVEL_DCC,
+ IRCTXT_DCC_GET_ABORTED, dcc->arg, dcc->nick);
+ } else {
+ printformat(dcc->server, NULL, MSGLEVEL_DCC,
+ IRCTXT_DCC_GET_COMPLETE, dcc->arg, sizestr,
+ dcc->nick, timestr, kbs);
+ }
+
+ g_free(sizestr);
+}
+
+static void dcc_error_file_create(GET_DCC_REC *dcc, const char *fname,
+ const char *error)
+{
+ printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_CANT_CREATE,
+ fname, error);
+}
+
+
+static void dcc_error_get_not_found(const char *nick)
+{
+ g_return_if_fail(nick != NULL);
+
+ printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_GET_NOT_FOUND, nick);
+}
+
+static void dcc_error_close_not_found(const char *type, const char *nick,
+ const char *fname)
+{
+ g_return_if_fail(type != NULL);
+ g_return_if_fail(nick != NULL);
+ g_return_if_fail(fname != NULL);
+ if (g_ascii_strcasecmp(type, "GET") != 0) return;
+
+ if (fname == NULL || *fname == '\0') fname = "(ANY)";
+ printformat(NULL, NULL, MSGLEVEL_DCC,
+ IRCTXT_DCC_GET_NOT_FOUND, nick, fname);
+}
+
+static void dcc_error_write(GET_DCC_REC *dcc, const char *error)
+{
+ printformat(NULL, NULL, MSGLEVEL_DCC,
+ IRCTXT_DCC_GET_WRITE_ERROR, dcc->file, error);
+}
+
+static void sig_dcc_list_print(GET_DCC_REC *dcc)
+{
+ if (IS_DCC_GET(dcc))
+ dcc_list_print_file((FILE_DCC_REC *) dcc);
+}
+
+void fe_dcc_get_init(void)
+{
+ signal_add("dcc request", (SIGNAL_FUNC) dcc_request);
+ signal_add("dcc connected", (SIGNAL_FUNC) dcc_connected);
+ signal_add("dcc closed", (SIGNAL_FUNC) dcc_closed);
+ signal_add("dcc error file create", (SIGNAL_FUNC) dcc_error_file_create);
+ signal_add("dcc error get not found", (SIGNAL_FUNC) dcc_error_get_not_found);
+ signal_add("dcc error close not found", (SIGNAL_FUNC) dcc_error_close_not_found);
+ signal_add("dcc error write", (SIGNAL_FUNC) dcc_error_write);
+ signal_add("dcc list print", (SIGNAL_FUNC) sig_dcc_list_print);
+}
+
+void fe_dcc_get_deinit(void)
+{
+ signal_remove("dcc request", (SIGNAL_FUNC) dcc_request);
+ signal_remove("dcc connected", (SIGNAL_FUNC) dcc_connected);
+ signal_remove("dcc closed", (SIGNAL_FUNC) dcc_closed);
+ signal_remove("dcc error file create", (SIGNAL_FUNC) dcc_error_file_create);
+ signal_remove("dcc error get not found", (SIGNAL_FUNC) dcc_error_get_not_found);
+ signal_remove("dcc error close not found", (SIGNAL_FUNC) dcc_error_close_not_found);
+ signal_remove("dcc error write", (SIGNAL_FUNC) dcc_error_write);
+ signal_remove("dcc list print", (SIGNAL_FUNC) sig_dcc_list_print);
+}
diff --git a/src/fe-common/irc/dcc/fe-dcc-send.c b/src/fe-common/irc/dcc/fe-dcc-send.c
new file mode 100644
index 0000000..cf9b634
--- /dev/null
+++ b/src/fe-common/irc/dcc/fe-dcc-send.c
@@ -0,0 +1,186 @@
+/*
+ fe-dcc-send.c : irssi
+
+ Copyright (C) 1999-2001 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/irc/dcc/dcc-file.h>
+#include <irssi/src/irc/dcc/dcc-send.h>
+#include <irssi/src/irc/dcc/dcc-queue.h>
+
+#include <irssi/src/fe-common/irc/dcc/module-formats.h>
+#include <irssi/src/fe-common/core/printtext.h>
+#include <irssi/src/fe-common/core/completion.h>
+
+#include <irssi/src/fe-common/irc/dcc/fe-dcc.h>
+
+static void dcc_connected(SEND_DCC_REC *dcc)
+{
+ if (!IS_DCC_SEND(dcc)) return;
+
+ printformat(dcc->server, NULL, MSGLEVEL_DCC,
+ IRCTXT_DCC_SEND_CONNECTED,
+ dcc->arg, dcc->nick, dcc->addrstr, dcc->port);
+}
+
+static void dcc_closed(SEND_DCC_REC *dcc)
+{
+ char *sizestr, timestr[20];
+ double kbs;
+ time_t secs;
+
+ if (!IS_DCC_SEND(dcc)) return;
+
+ secs = dcc->starttime == 0 ? -1 : time(NULL)-dcc->starttime;
+ kbs = (double) (dcc->transfd-dcc->skipped) /
+ (secs == 0 ? 1 : secs) / 1024.0;
+
+ if (secs == -1) {
+ /* aborted */
+ printformat(dcc->server, NULL, MSGLEVEL_DCC,
+ IRCTXT_DCC_SEND_ABORTED,
+ dcc->arg, dcc->nick);
+ } else {
+ sizestr = dcc_get_size_str(dcc->transfd);
+ g_snprintf(timestr, sizeof(timestr), "%02d:%02d:%02d",
+ (int)(secs/3600), (int)((secs/60)%60),
+ (int)(secs%60));
+
+ printformat(dcc->server, NULL, MSGLEVEL_DCC,
+ IRCTXT_DCC_SEND_COMPLETE,
+ dcc->arg, sizestr, dcc->nick, timestr, kbs);
+
+ g_free(sizestr);
+ }
+}
+
+static void dcc_error_file_open(const char *nick, const char *fname,
+ void *error)
+{
+ g_return_if_fail(nick != NULL);
+ g_return_if_fail(fname != NULL);
+
+ printformat(NULL, NULL, MSGLEVEL_DCC,
+ IRCTXT_DCC_SEND_FILE_OPEN_ERROR, fname,
+ g_strerror(GPOINTER_TO_INT(error)));
+}
+
+static void dcc_error_send_exists(const char *nick, const char *fname)
+{
+ g_return_if_fail(nick != NULL);
+ g_return_if_fail(fname != NULL);
+
+ printformat(NULL, NULL, MSGLEVEL_DCC,
+ IRCTXT_DCC_SEND_EXISTS, fname, nick);
+}
+
+static void dcc_error_send_no_route(const char *nick, const char *fname)
+{
+ printformat(NULL, NULL, MSGLEVEL_DCC,
+ IRCTXT_DCC_SEND_NO_ROUTE, nick, fname);
+}
+
+static void dcc_error_close_not_found(const char *type, const char *nick,
+ const char *fname)
+{
+ g_return_if_fail(type != NULL);
+ g_return_if_fail(nick != NULL);
+ g_return_if_fail(fname != NULL);
+ if (g_ascii_strcasecmp(type, "SEND") != 0) return;
+
+ if (fname == NULL || *fname == '\0') fname = "(ANY)";
+ printformat(NULL, NULL, MSGLEVEL_DCC,
+ IRCTXT_DCC_SEND_NOT_FOUND, nick, fname);
+}
+
+static void sig_dcc_send_complete(GList **list, WINDOW_REC *window,
+ const char *word, const char *line,
+ int *want_space)
+{
+ char *path;
+
+ g_return_if_fail(list != NULL);
+ g_return_if_fail(word != NULL);
+ g_return_if_fail(line != NULL);
+
+ if (*line == '\0' || strchr(line, ' ') != NULL)
+ return;
+
+ /* completing filename parameter for /DCC SEND */
+ path = convert_home(settings_get_str("dcc_upload_path"));
+ if (*path == '\0') {
+ /* use the default path */
+ g_free_and_null(path);
+ }
+
+ *list = filename_complete(word, path);
+
+ if (*list != NULL) {
+ *want_space = FALSE;
+ signal_stop();
+ }
+}
+
+static void sig_dcc_list_print(SEND_DCC_REC *dcc)
+{
+ GSList *queue;
+
+ if (!IS_DCC_SEND(dcc))
+ return;
+
+ dcc_list_print_file((FILE_DCC_REC *) dcc);
+
+ queue = dcc_queue_get_queue(dcc->queue);
+ for (; queue != NULL; queue = queue->next) {
+ DCC_QUEUE_REC *rec = queue->data;
+
+ printformat(NULL, NULL, MSGLEVEL_DCC,
+ IRCTXT_DCC_LIST_LINE_QUEUED_SEND, rec->nick,
+ rec->servertag == NULL ? "" : rec->servertag,
+ rec->file);
+ }
+}
+
+void fe_dcc_send_init(void)
+{
+ signal_add("dcc connected", (SIGNAL_FUNC) dcc_connected);
+ signal_add("dcc closed", (SIGNAL_FUNC) dcc_closed);
+ signal_add("dcc error file open", (SIGNAL_FUNC) dcc_error_file_open);
+ signal_add("dcc error send exists", (SIGNAL_FUNC) dcc_error_send_exists);
+ signal_add("dcc error send no route", (SIGNAL_FUNC) dcc_error_send_no_route);
+ signal_add("dcc error close not found", (SIGNAL_FUNC) dcc_error_close_not_found);
+ signal_add("complete command dcc send", (SIGNAL_FUNC) sig_dcc_send_complete);
+ signal_add("dcc list print", (SIGNAL_FUNC) sig_dcc_list_print);
+}
+
+void fe_dcc_send_deinit(void)
+{
+ signal_remove("dcc connected", (SIGNAL_FUNC) dcc_connected);
+ signal_remove("dcc closed", (SIGNAL_FUNC) dcc_closed);
+ signal_remove("dcc error file open", (SIGNAL_FUNC) dcc_error_file_open);
+ signal_remove("dcc error send exists", (SIGNAL_FUNC) dcc_error_send_exists);
+ signal_remove("dcc error send no route", (SIGNAL_FUNC) dcc_error_send_no_route);
+ signal_remove("dcc error close not found", (SIGNAL_FUNC) dcc_error_close_not_found);
+ signal_remove("complete command dcc send", (SIGNAL_FUNC) sig_dcc_send_complete);
+ signal_remove("dcc list print", (SIGNAL_FUNC) sig_dcc_list_print);
+}
diff --git a/src/fe-common/irc/dcc/fe-dcc-server.c b/src/fe-common/irc/dcc/fe-dcc-server.c
new file mode 100644
index 0000000..eaab963
--- /dev/null
+++ b/src/fe-common/irc/dcc/fe-dcc-server.c
@@ -0,0 +1,83 @@
+/*
+ fe-dcc-server.c : irssi
+
+ Copyright (C) 2003 Mark Trumbull
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/network.h>
+#include <irssi/src/core/levels.h>
+
+#include <irssi/src/irc/dcc/dcc-server.h>
+
+#include <irssi/src/fe-common/irc/dcc/module-formats.h>
+#include <irssi/src/fe-common/core/printtext.h>
+#include <irssi/src/fe-common/core/themes.h>
+
+static void dcc_server_started(SERVER_DCC_REC *dcc)
+{
+ if (!IS_DCC_SERVER(dcc)) {
+ return;
+ }
+
+ printformat(dcc->server, NULL, MSGLEVEL_DCC,
+ IRCTXT_DCC_SERVER_STARTED, dcc->port);
+}
+
+static void dcc_closed(SERVER_DCC_REC *dcc)
+{
+ /* We don't want to print a msg if its just starting a chat/get */
+ /* and getting rid of the leftover SERVER_DCC_REC */
+ if (!IS_DCC_SERVER(dcc) || dcc->connection_established) {
+ return;
+ }
+
+ printformat(dcc->server, NULL, MSGLEVEL_DCC,
+ IRCTXT_DCC_SERVER_CLOSED, dcc->port);
+}
+
+static void sig_dcc_list_print(SERVER_DCC_REC *dcc)
+{
+ /* We don't want to print a msg if its just starting a chat/get */
+ /* and getting rid of the leftover SERVER_DCC_REC */
+ if (!IS_DCC_SERVER(dcc) || dcc->connection_established) {
+ return;
+ }
+
+ /* SERVER: Port(59) - Send(on) - Chat(on) - Fserve(on) */
+ printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_LIST_LINE_SERVER,
+ "SERVER", dcc->port, dcc->accept_send ? "on" : "off",
+ dcc->accept_chat ? "on" : "off",
+ dcc->accept_fserve ? "on" : "off");
+}
+
+void fe_dcc_server_init(void)
+{
+ signal_add("dcc server started", (SIGNAL_FUNC) dcc_server_started);
+ signal_add("dcc closed", (SIGNAL_FUNC) dcc_closed);
+ signal_add("dcc list print", (SIGNAL_FUNC) sig_dcc_list_print);
+}
+
+void fe_dcc_server_deinit(void)
+{
+ signal_remove("dcc server started", (SIGNAL_FUNC) dcc_server_started);
+ signal_remove("dcc closed", (SIGNAL_FUNC) dcc_closed);
+ signal_remove("dcc list print", (SIGNAL_FUNC) sig_dcc_list_print);
+}
+
diff --git a/src/fe-common/irc/dcc/fe-dcc.c b/src/fe-common/irc/dcc/fe-dcc.c
new file mode 100644
index 0000000..f5fb3b2
--- /dev/null
+++ b/src/fe-common/irc/dcc/fe-dcc.c
@@ -0,0 +1,195 @@
+/*
+ fe-dcc.c : irssi
+
+ Copyright (C) 1999-2001 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/network.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/irc/dcc/dcc-chat.h>
+#include <irssi/src/irc/dcc/dcc-file.h>
+#include <irssi/src/irc/dcc/dcc-get.h>
+#include <irssi/src/irc/dcc/dcc-send.h>
+
+#include <irssi/src/fe-common/irc/dcc/module-formats.h>
+#include <irssi/src/fe-common/core/printtext.h>
+#include <irssi/src/fe-common/core/themes.h>
+
+void fe_dcc_chat_init(void);
+void fe_dcc_chat_deinit(void);
+
+void fe_dcc_get_init(void);
+void fe_dcc_get_deinit(void);
+
+void fe_dcc_send_init(void);
+void fe_dcc_send_deinit(void);
+
+void fe_dcc_server_init(void);
+void fe_dcc_server_deinit(void);
+
+char *dcc_get_size_str(uoff_t size)
+{
+ if (size < 1024)
+ return g_strdup_printf("%"PRIuUOFF_T"B", size);
+ if (size < 1024*1024)
+ return g_strdup_printf("%"PRIuUOFF_T"kB", (size+1023) / 1024);
+ return g_strdup_printf("%"PRIuUOFF_T"MB", size / (1024*1024));
+}
+
+static void dcc_request(DCC_REC *dcc)
+{
+ char *service;
+
+ g_return_if_fail(dcc != NULL);
+
+ if (dcc->port < 1024) {
+ /* warn about connecting to lowports */
+ service = net_getservbyport(dcc->port);
+
+ printformat(dcc->server, NULL, MSGLEVEL_DCC,
+ IRCTXT_DCC_LOWPORT, dcc->port,
+ service != NULL ? service : "unknown");
+ }
+}
+
+static void dcc_rejected(DCC_REC *dcc)
+{
+ g_return_if_fail(dcc != NULL);
+
+ printformat(dcc->server, NULL, MSGLEVEL_DCC, IRCTXT_DCC_CLOSE,
+ dcc_type2str(dcc->type), dcc->nick, dcc->arg);
+}
+
+static void dcc_request_send(DCC_REC *dcc)
+{
+ g_return_if_fail(dcc != NULL);
+
+ printformat(dcc->server, NULL, MSGLEVEL_DCC, IRCTXT_DCC_REQUEST_SEND,
+ dcc_type2str(dcc->type), dcc->nick, dcc->arg);
+}
+
+static void dcc_error_connect(DCC_REC *dcc)
+{
+ g_return_if_fail(dcc != NULL);
+
+ printformat(dcc->server, NULL, MSGLEVEL_DCC,
+ IRCTXT_DCC_CONNECT_ERROR, dcc->addrstr, dcc->port);
+}
+
+static void dcc_error_unknown_type(const char *type)
+{
+ g_return_if_fail(type != NULL);
+
+ printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_UNKNOWN_TYPE, type);
+}
+
+void dcc_list_print_file(FILE_DCC_REC *dcc)
+{
+ time_t going, eta;
+ char *transfd_str, *size_str, etastr[20];
+ uoff_t bps;
+
+ going = time(NULL) - dcc->starttime;
+ if (going <= 0) going = 1;
+
+ transfd_str = dcc_get_size_str(dcc->transfd);
+ size_str = dcc_get_size_str(dcc->size);
+
+ bps = (dcc->transfd-dcc->skipped) / going;
+ if (bps == 0) {
+ strcpy(etastr, "(stalled)");
+ } else {
+ eta = (dcc->size - dcc->transfd) / bps;
+ g_snprintf(etastr, sizeof(etastr), "%02d:%02d:%02d",
+ (int)(eta/3600), (int)((eta/60)%60), (int)(eta%60));
+ }
+
+ printformat(NULL, NULL, MSGLEVEL_DCC,
+ IRCTXT_DCC_LIST_LINE_FILE,
+ dcc->nick, dcc_type2str(dcc->type),
+ transfd_str, size_str,
+ dcc->size == 0 ? 0 : (int)((double)dcc->transfd/(double)dcc->size*100.0),
+ (double)bps/1024.0, dcc->arg, etastr);
+
+ g_free(transfd_str);
+ g_free(size_str);
+}
+
+static void cmd_dcc_list(const char *data)
+{
+ GSList *tmp;
+
+ g_return_if_fail(data != NULL);
+
+ printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_LIST_HEADER);
+ for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next)
+ signal_emit("dcc list print", 1, tmp->data);
+ printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_LIST_FOOTER);
+}
+
+static void cmd_dcc(const char *data)
+{
+ if (*data == '\0') {
+ cmd_dcc_list(data);
+ signal_stop();
+ }
+}
+
+void fe_irc_dcc_init(void)
+{
+ fe_dcc_chat_init();
+ fe_dcc_get_init();
+ fe_dcc_send_init();
+ fe_dcc_server_init();
+
+ signal_add("dcc request", (SIGNAL_FUNC) dcc_request);
+ signal_add("dcc rejected", (SIGNAL_FUNC) dcc_rejected);
+ signal_add("dcc request send", (SIGNAL_FUNC) dcc_request_send);
+ signal_add("dcc error connect", (SIGNAL_FUNC) dcc_error_connect);
+ signal_add("dcc error unknown type", (SIGNAL_FUNC) dcc_error_unknown_type);
+ command_bind("dcc", NULL, (SIGNAL_FUNC) cmd_dcc);
+ command_bind("dcc list", NULL, (SIGNAL_FUNC) cmd_dcc_list);
+
+ theme_register(fecommon_irc_dcc_formats);
+ settings_check();
+ module_register("dcc", "fe-irc");
+}
+
+void fe_irc_dcc_deinit(void)
+{
+ fe_dcc_chat_deinit();
+ fe_dcc_get_deinit();
+ fe_dcc_send_deinit();
+ fe_dcc_server_deinit();
+
+ theme_unregister();
+
+ signal_remove("dcc request", (SIGNAL_FUNC) dcc_request);
+ signal_remove("dcc rejected", (SIGNAL_FUNC) dcc_rejected);
+ signal_remove("dcc request send", (SIGNAL_FUNC) dcc_request_send);
+ signal_remove("dcc error connect", (SIGNAL_FUNC) dcc_error_connect);
+ signal_remove("dcc error unknown type", (SIGNAL_FUNC) dcc_error_unknown_type);
+ command_unbind("dcc", (SIGNAL_FUNC) cmd_dcc);
+ command_unbind("dcc list", (SIGNAL_FUNC) cmd_dcc_list);
+}
+
+MODULE_ABICHECK(fe_irc_dcc)
diff --git a/src/fe-common/irc/dcc/fe-dcc.h b/src/fe-common/irc/dcc/fe-dcc.h
new file mode 100644
index 0000000..4d79296
--- /dev/null
+++ b/src/fe-common/irc/dcc/fe-dcc.h
@@ -0,0 +1,7 @@
+#ifndef IRSSI_FE_COMMON_IRC_DCC_FE_DCC_H
+#define IRSSI_FE_COMMON_IRC_DCC_FE_DCC_H
+
+char *dcc_get_size_str(uoff_t size);
+void dcc_list_print_file(FILE_DCC_REC *dcc);
+
+#endif
diff --git a/src/fe-common/irc/dcc/meson.build b/src/fe-common/irc/dcc/meson.build
new file mode 100644
index 0000000..296b1d6
--- /dev/null
+++ b/src/fe-common/irc/dcc/meson.build
@@ -0,0 +1,27 @@
+# this file is part of irssi
+
+libfe_irc_dcc_a = static_library('fe_irc_dcc',
+ files(
+ 'fe-dcc-chat-messages.c',
+ 'fe-dcc-chat.c',
+ 'fe-dcc-get.c',
+ 'fe-dcc-send.c',
+ 'fe-dcc-server.c',
+ 'fe-dcc.c',
+ 'module-formats.c',
+ ),
+ include_directories : rootinc,
+ implicit_include_directories : false,
+ c_args : [
+ def_helpdir,
+ def_sysconfdir,
+ ],
+ dependencies : dep)
+
+install_headers(
+ files(
+ 'fe-dcc.h',
+ 'module-formats.h',
+ 'module.h',
+ ),
+ subdir : incdir / 'src' / 'fe-common' / 'irc' / 'dcc')
diff --git a/src/fe-common/irc/dcc/module-formats.c b/src/fe-common/irc/dcc/module-formats.c
new file mode 100644
index 0000000..f973cd9
--- /dev/null
+++ b/src/fe-common/irc/dcc/module-formats.c
@@ -0,0 +1,79 @@
+/*
+ module-formats.c : irssi
+
+ Copyright (C) 2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/core/formats.h>
+
+FORMAT_REC fecommon_irc_dcc_formats[] = {
+ { MODULE_NAME, "IRC", 0 },
+
+ /* ---- */
+ { NULL, "DCC", 0 },
+
+ { "own_dcc", "{dccownmsg dcc {dccownnick $1}}$2", 3, { 0, 0, 0 } },
+ { "own_dcc_action", "{dccownaction_target $0 $1}$2", 3, { 0, 0, 0 } },
+ { "own_dcc_action_query", "{dccownaction $0}$2", 3, { 0, 0, 0 } },
+ { "own_dcc_ctcp", "{ownctcp ctcp $0}$1 $2", 3, { 0, 0, 0 } },
+ { "dcc_msg", "{dccmsg dcc $0}$1", 2, { 0, 0 } },
+ { "action_dcc", "{dccaction $0}$1", 2, { 0, 0 } },
+ { "action_dcc_query", "{dccaction $0}$1", 2, { 0, 0 } },
+ { "own_dcc_query", "{ownmsgnick {dccownquerynick $0}}$2", 3, { 0, 0, 0 } },
+ { "dcc_msg_query", "{privmsgnick $0}$1", 2, { 0, 0 } },
+ { "dcc_ctcp", "{dcc >>> DCC CTCP {hilight $1} received from {hilight $0}: $2}", 3, { 0, 0, 0 } },
+ { "dcc_chat", "{dcc DCC CHAT from {nick $0} [$1 port $2]}", 3, { 0, 0, 1 } },
+ { "dcc_chat_channel", "{dcc DCC CHAT from {nick $0} [$1 port $2] requested in channel {channel $3}}", 4, { 0, 0, 1, 0 } },
+ { "dcc_chat_not_found", "{dcc No DCC CHAT connection open to {nick $0}}", 1, { 0 } },
+ { "dcc_chat_connected", "{dcc DCC CHAT connection with {nick $0} [$1 port $2] established}", 3, { 0, 0, 1 } },
+ { "dcc_chat_disconnected", "{dcc DCC lost chat to {nick $0}}", 1, { 0 } },
+ { "dcc_send", "{dcc DCC SEND from {nick $0} [$1 port $2]: $3 [$4]}", 5, { 0, 0, 1, 0, 0 } },
+ { "dcc_send_channel", "{dcc DCC SEND from {nick $0} [$1 port $2]: $3 [$4 bytes] requested in channel {channel $5}}", 6, { 0, 0, 1, 0, 0, 0 } },
+ { "dcc_send_exists", "{dcc DCC already sending file {dccfile $0} for {nick $1}}", 2, { 0, 0 } },
+ { "dcc_send_no_route", "{dcc DCC route lost to nick {nick $0} when trying to send file {dccfile $1}}", 2, { 0, 0 } },
+ { "dcc_send_not_found", "{dcc DCC not sending file {dccfile $1} to {nick $0}}", 2, { 0, 0 } },
+ { "dcc_send_file_open_error", "{dcc DCC can't open file {dccfile $0}: $1}", 2, { 0, 0 } },
+ { "dcc_send_connected", "{dcc DCC sending file {dccfile $0} for {nick $1} [$2 port $3]}", 4, { 0, 0, 0, 1 } },
+ { "dcc_send_complete", "{dcc DCC sent file {dccfile $0} [{hilight $1}] for {nick $2} in {hilight $3} [{hilight $4kB/s}]}", 5, { 0, 0, 0, 0, 3 } },
+ { "dcc_send_aborted", "{dcc DCC aborted sending file {dccfile $0} for {nick $1}}", 2, { 0, 0 } },
+ { "dcc_get_not_found", "{dcc DCC no file offered by {nick $0}}", 1, { 0 } },
+ { "dcc_get_connected", "{dcc DCC receiving file {dccfile $0} from {nick $1} [$2 port $3]}", 4, { 0, 0, 0, 1 } },
+ { "dcc_get_complete", "{dcc DCC received file {dccfile $0} [$1] from {nick $2} in {hilight $3} [$4kB/s]}", 5, { 0, 0, 0, 0, 3 } },
+ { "dcc_get_aborted", "{dcc DCC aborted receiving file {dccfile $0} from {nick $1}}", 2, { 0, 0 } },
+ { "dcc_get_write_error", "{dcc DCC error writing to file {dccfile $0}: {comment $1}", 2, { 0, 0 } },
+ { "dcc_unknown_ctcp", "{dcc DCC unknown ctcp {hilight $0} from {nick $1} [$2]}", 3, { 0, 0, 0 } },
+ { "dcc_unknown_reply", "{dcc DCC unknown reply {hilight $0} from {nick $1} [$2]}", 3, { 0, 0, 0 } },
+ { "dcc_unknown_type", "{dcc DCC unknown type {hilight $0}}", 1, { 0 } },
+ { "dcc_invalid_ctcp", "{dcc DCC received CTCP {hilight $0} with invalid parameters from {nick $1}}", 4, { 0, 0, 0, 0 } },
+ { "dcc_connect_error", "{dcc DCC can't connect to {hilight $0} port {hilight $1}}", 2, { 0, 1 } },
+ { "dcc_cant_create", "{dcc DCC can't create file {dccfile $0}: $1}", 2, { 0, 0 } },
+ { "dcc_rejected", "{dcc DCC $0 was rejected by {nick $1} [{hilight $2}]}", 3, { 0, 0, 0 } },
+ { "dcc_request_send", "{dcc DCC $0 request sent to {nick $1}: $2", 3, { 0, 0, 0 } },
+ { "dcc_close", "{dcc DCC $0 close for {nick $1} [{hilight $2}]}", 3, { 0, 0, 0 } },
+ { "dcc_lowport", "{dcc Warning: Port sent with DCC request is a lowport ({hilight $0, $1}) - this isn't normal. It is possible the address/port is faked (or maybe someone is just trying to bypass firewall)}", 2, { 1, 0 } },
+ { "dcc_list_header", "{dcc DCC connections}", 0 },
+ { "dcc_list_line_chat", "{dcc $0 $1}", 2, { 0, 0 } },
+ { "dcc_list_line_file", "{dcc $0 $1: %|$2 of $3 ($4%%) - $5kB/s - ETA $7 - $6}", 8, { 0, 0, 0, 0, 1, 3, 0, 0 } },
+ { "dcc_list_line_queued_send", "{dcc - $0 $2 (queued)}", 3, { 0, 0, 0 } },
+ { "dcc_list_footer", "", 0 },
+ { "dcc_list_line_server", "{dcc $0: Port($1) - Send($2) - Chat($3) - Fserve($4)}", 5, { 0, 1, 0, 0, 0 } },
+ { "dcc_server_started", "{dcc DCC SERVER started on port {hilight $0}}", 1, { 1 } },
+ { "dcc_server_closed", "{dcc DCC SERVER on port {hilight $0} closed}", 1, { 1 } },
+
+ { NULL, NULL, 0 }
+};
diff --git a/src/fe-common/irc/dcc/module-formats.h b/src/fe-common/irc/dcc/module-formats.h
new file mode 100644
index 0000000..204c7dd
--- /dev/null
+++ b/src/fe-common/irc/dcc/module-formats.h
@@ -0,0 +1,57 @@
+#include <irssi/src/fe-common/core/formats.h>
+
+enum {
+ IRCTXT_MODULE_NAME,
+
+ IRCTXT_FILL_1,
+
+ IRCTXT_OWN_DCC,
+ IRCTXT_OWN_DCC_ACTION,
+ IRCTXT_OWN_DCC_ACTION_QUERY,
+ IRCTXT_OWN_DCC_CTCP,
+ IRCTXT_DCC_MSG,
+ IRCTXT_ACTION_DCC,
+ IRCTXT_ACTION_DCC_QUERY,
+ IRCTXT_OWN_DCC_QUERY,
+ IRCTXT_DCC_MSG_QUERY,
+ IRCTXT_DCC_CTCP,
+ IRCTXT_DCC_CHAT,
+ IRCTXT_DCC_CHAT_CHANNEL,
+ IRCTXT_DCC_CHAT_NOT_FOUND,
+ IRCTXT_DCC_CHAT_CONNECTED,
+ IRCTXT_DCC_CHAT_DISCONNECTED,
+ IRCTXT_DCC_SEND,
+ IRCTXT_DCC_SEND_CHANNEL,
+ IRCTXT_DCC_SEND_EXISTS,
+ IRCTXT_DCC_SEND_NO_ROUTE,
+ IRCTXT_DCC_SEND_NOT_FOUND,
+ IRCTXT_DCC_SEND_FILE_OPEN_ERROR,
+ IRCTXT_DCC_SEND_CONNECTED,
+ IRCTXT_DCC_SEND_COMPLETE,
+ IRCTXT_DCC_SEND_ABORTED,
+ IRCTXT_DCC_GET_NOT_FOUND,
+ IRCTXT_DCC_GET_CONNECTED,
+ IRCTXT_DCC_GET_COMPLETE,
+ IRCTXT_DCC_GET_ABORTED,
+ IRCTXT_DCC_GET_WRITE_ERROR,
+ IRCTXT_DCC_UNKNOWN_CTCP,
+ IRCTXT_DCC_UNKNOWN_REPLY,
+ IRCTXT_DCC_UNKNOWN_TYPE,
+ IRCTXT_DCC_INVALID_CTCP,
+ IRCTXT_DCC_CONNECT_ERROR,
+ IRCTXT_DCC_CANT_CREATE,
+ IRCTXT_DCC_REJECTED,
+ IRCTXT_DCC_REQUEST_SEND,
+ IRCTXT_DCC_CLOSE,
+ IRCTXT_DCC_LOWPORT,
+ IRCTXT_DCC_LIST_HEADER,
+ IRCTXT_DCC_LIST_LINE_CHAT,
+ IRCTXT_DCC_LIST_LINE_FILE,
+ IRCTXT_DCC_LIST_LINE_QUEUED_SEND,
+ IRCTXT_DCC_LIST_FOOTER,
+ IRCTXT_DCC_LIST_LINE_SERVER,
+ IRCTXT_DCC_SERVER_STARTED,
+ IRCTXT_DCC_SERVER_CLOSED
+};
+
+extern FORMAT_REC fecommon_irc_dcc_formats[];
diff --git a/src/fe-common/irc/dcc/module.h b/src/fe-common/irc/dcc/module.h
new file mode 100644
index 0000000..5493446
--- /dev/null
+++ b/src/fe-common/irc/dcc/module.h
@@ -0,0 +1,4 @@
+#include <irssi/src/common.h>
+#include <irssi/src/irc/core/irc.h>
+
+#define MODULE_NAME "fe-common/irc/dcc"
diff --git a/src/fe-common/irc/fe-cap.c b/src/fe-common/irc/fe-cap.c
new file mode 100644
index 0000000..a75f2bb
--- /dev/null
+++ b/src/fe-common/irc/fe-cap.c
@@ -0,0 +1,84 @@
+/*
+ fe-cap.c : irssi
+
+ Copyright (C) 2018 dequis
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/irc/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/misc.h>
+
+#include <irssi/src/irc/core/irc-servers.h>
+
+#include <irssi/src/fe-common/core/printtext.h>
+
+static const struct {
+ const char *command;
+ const int template;
+} fe_cap_messages[] = {
+ {"LS", IRCTXT_CAP_LS},
+ {"ACK", IRCTXT_CAP_ACK},
+ {"NAK", IRCTXT_CAP_NAK},
+ {"LIST", IRCTXT_CAP_LIST},
+ {"NEW", IRCTXT_CAP_NEW},
+ {"DEL", IRCTXT_CAP_DEL},
+};
+
+static void event_cap(IRC_SERVER_REC *server, char *args, char *nick, char *address)
+{
+ int i;
+ char *params, *evt, *list, *star;
+
+ params = event_get_params(args, 4, NULL, &evt, &star, &list);
+
+ if (params == NULL) {
+ return;
+ }
+
+ /* With multiline CAP LS, if the '*' parameter isn't present,
+ * adjust the parameter pointer to compensate for this */
+ if (strcmp(star, "*") != 0 && list[0] == '\0') {
+ list = star;
+ }
+
+ for (i = 0; i < G_N_ELEMENTS(fe_cap_messages); i++) {
+ if (!g_ascii_strcasecmp(evt, fe_cap_messages[i].command)) {
+ printformat(server, NULL, MSGLEVEL_CRAP, fe_cap_messages[i].template, list);
+ }
+ }
+
+ g_free(params);
+}
+
+static void sig_server_cap_req(IRC_SERVER_REC *server, char *caps)
+{
+ printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_CAP_REQ, caps);
+}
+
+void fe_cap_init(void)
+{
+ signal_add("event cap", (SIGNAL_FUNC) event_cap);
+ signal_add("server cap req", (SIGNAL_FUNC) sig_server_cap_req);
+}
+
+void fe_cap_deinit(void)
+{
+ signal_remove("event cap", (SIGNAL_FUNC) event_cap);
+ signal_remove("server cap req", (SIGNAL_FUNC) sig_server_cap_req);
+}
diff --git a/src/fe-common/irc/fe-common-irc.c b/src/fe-common/irc/fe-common-irc.c
new file mode 100644
index 0000000..a36d3ca
--- /dev/null
+++ b/src/fe-common/irc/fe-common-irc.c
@@ -0,0 +1,134 @@
+/*
+ fe-common-irc.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/modules.h>
+#include <irssi/src/fe-common/irc/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/lib-config/iconfig.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/fe-common/core/themes.h>
+#include <irssi/src/fe-common/irc/fe-irc-server.h>
+#include <irssi/src/fe-common/irc/fe-irc-channels.h>
+
+void fe_irc_modules_init(void);
+void fe_irc_modules_deinit(void);
+
+void fe_irc_queries_init(void);
+void fe_irc_queries_deinit(void);
+
+void fe_irc_layout_init(void);
+void fe_irc_layout_deinit(void);
+
+void fe_irc_messages_init(void);
+void fe_irc_messages_deinit(void);
+
+void fe_irc_commands_init(void);
+void fe_irc_commands_deinit(void);
+
+void fe_ircnet_init(void);
+void fe_ircnet_deinit(void);
+
+void fe_ctcp_init(void);
+void fe_ctcp_deinit(void);
+
+void fe_events_init(void);
+void fe_events_deinit(void);
+
+void fe_events_numeric_init(void);
+void fe_events_numeric_deinit(void);
+
+void fe_modes_init(void);
+void fe_modes_deinit(void);
+
+void fe_netsplit_init(void);
+void fe_netsplit_deinit(void);
+
+void fe_netjoin_init(void);
+void fe_netjoin_deinit(void);
+
+void fe_whois_init(void);
+void fe_whois_deinit(void);
+
+void fe_sasl_init(void);
+void fe_sasl_deinit(void);
+
+void fe_cap_init(void);
+void fe_cap_deinit(void);
+
+void irc_completion_init(void);
+void irc_completion_deinit(void);
+
+void fe_common_irc_init(void)
+{
+ settings_add_bool("lookandfeel", "show_away_once", TRUE);
+
+ theme_register(fecommon_irc_formats);
+
+ fe_irc_channels_init();
+ fe_irc_queries_init();
+ fe_irc_messages_init();
+ fe_irc_commands_init();
+ fe_ircnet_init();
+ fe_irc_server_init();
+ fe_ctcp_init();
+ fe_events_init();
+ fe_events_numeric_init();
+ fe_modes_init();
+ fe_netsplit_init();
+ fe_netjoin_init();
+ fe_whois_init();
+ fe_sasl_init();
+ fe_cap_init();
+ irc_completion_init();
+
+ settings_check();
+ module_register("irc", "fe-common");
+
+ fe_irc_modules_init();
+}
+
+void fe_common_irc_deinit(void)
+{
+ fe_irc_modules_deinit();
+
+ fe_irc_channels_deinit();
+ fe_irc_queries_deinit();
+ fe_irc_messages_deinit();
+ fe_irc_commands_deinit();
+ fe_ircnet_deinit();
+ fe_irc_server_deinit();
+ fe_ctcp_deinit();
+ fe_events_deinit();
+ fe_events_numeric_deinit();
+ fe_modes_deinit();
+ fe_netsplit_deinit();
+ fe_netjoin_deinit();
+ fe_whois_deinit();
+ fe_sasl_deinit();
+ fe_cap_deinit();
+ irc_completion_deinit();
+
+ theme_unregister();
+}
+
+MODULE_ABICHECK(fe_common_irc)
diff --git a/src/fe-common/irc/fe-ctcp.c b/src/fe-common/irc/fe-ctcp.c
new file mode 100644
index 0000000..1489396
--- /dev/null
+++ b/src/fe-common/irc/fe-ctcp.c
@@ -0,0 +1,170 @@
+/*
+ fe-ctcp.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/irc/module-formats.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/channels.h>
+#include <irssi/src/core/queries.h>
+#include <irssi/src/core/ignore.h>
+
+#include <irssi/src/fe-common/core/fe-windows.h>
+#include <irssi/src/fe-common/core/window-items.h>
+#include <irssi/src/fe-common/core/printtext.h>
+
+static void ctcp_default_msg(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr,
+ const char *target)
+{
+ const char *p;
+ char *cmd;
+
+ p = strchr(data, ' ');
+ if (p == NULL) {
+ cmd = g_strdup(data);
+ data = "";
+ } else {
+ cmd = g_strndup(data, (int) (p-data));
+ data = p+1;
+ }
+
+ printformat(server, server_ischannel(SERVER(server), target) ? target : nick, MSGLEVEL_CTCPS,
+ IRCTXT_CTCP_REQUESTED_UNKNOWN,
+ nick, addr, cmd, data, target);
+ g_free(cmd);
+}
+
+static void ctcp_ping_msg(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr,
+ const char *target)
+{
+ signal_emit("message irc ctcp", 6, server, "PING",
+ data, nick, addr, target);
+}
+
+static void ctcp_version_msg(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr,
+ const char *target)
+{
+ signal_emit("message irc ctcp", 6, server, "VERSION",
+ data, nick, addr, target);
+}
+
+static void ctcp_time_msg(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr,
+ const char *target)
+{
+ signal_emit("message irc ctcp", 6, server, "TIME",
+ data, nick, addr, target);
+}
+
+static void ctcp_userinfo_msg(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr,
+ const char *target)
+{
+ signal_emit("message irc ctcp", 6, server, "USERINFO",
+ data, nick, addr, target);
+}
+
+static void ctcp_clientinfo_msg(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr,
+ const char *target)
+{
+ signal_emit("message irc ctcp", 6, server, "CLIENTINFO",
+ data, nick, addr, target);
+}
+
+static void ctcp_default_reply(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr,
+ const char *target)
+{
+ const char *ctcpdata;
+ char *ctcp, *ptr;
+
+ g_return_if_fail(data != NULL);
+
+ ctcp = g_strdup(data);
+ ptr = strchr(ctcp, ' ');
+ if (ptr == NULL)
+ ctcpdata = "";
+ else {
+ *ptr = '\0';
+ ctcpdata = ptr+1;
+ }
+
+ printformat(server, server_ischannel(SERVER(server), target) ? target : nick, MSGLEVEL_CTCPS,
+ server_ischannel(SERVER(server), target) ? IRCTXT_CTCP_REPLY_CHANNEL :
+ IRCTXT_CTCP_REPLY, ctcp, nick, ctcpdata, target);
+ g_free(ctcp);
+}
+
+static void ctcp_ping_reply(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr,
+ const char *target)
+{
+ gint64 tv, tv2;
+ long usecs;
+
+ g_return_if_fail(data != NULL);
+
+ if (sscanf(data,
+ "%" G_GINT64_FORMAT " "
+ "%" G_GINT64_FORMAT,
+ &tv, &tv2) < 1) {
+ char *tmp = g_strconcat("PING ", data, NULL);
+ ctcp_default_reply(server, tmp, nick, addr, target);
+ g_free(tmp);
+ return;
+ }
+
+ tv2 += tv * G_TIME_SPAN_SECOND;
+ tv = g_get_real_time();
+ usecs = tv - tv2;
+ printformat(server, server_ischannel(SERVER(server), target) ? target : nick, MSGLEVEL_CTCPS,
+ IRCTXT_CTCP_PING_REPLY, nick, usecs / G_TIME_SPAN_SECOND, usecs % G_TIME_SPAN_SECOND);
+}
+
+void fe_ctcp_init(void)
+{
+ signal_add("default ctcp msg", (SIGNAL_FUNC) ctcp_default_msg);
+ signal_add("ctcp msg ping", (SIGNAL_FUNC) ctcp_ping_msg);
+ signal_add("ctcp msg version", (SIGNAL_FUNC) ctcp_version_msg);
+ signal_add("ctcp msg time", (SIGNAL_FUNC) ctcp_time_msg);
+ signal_add("ctcp msg userinfo", (SIGNAL_FUNC) ctcp_userinfo_msg);
+ signal_add("ctcp msg clientinfo", (SIGNAL_FUNC) ctcp_clientinfo_msg);
+ signal_add("default ctcp reply", (SIGNAL_FUNC) ctcp_default_reply);
+ signal_add("ctcp reply ping", (SIGNAL_FUNC) ctcp_ping_reply);
+}
+
+void fe_ctcp_deinit(void)
+{
+ signal_remove("default ctcp msg", (SIGNAL_FUNC) ctcp_default_msg);
+ signal_remove("ctcp msg ping", (SIGNAL_FUNC) ctcp_ping_msg);
+ signal_remove("ctcp msg version", (SIGNAL_FUNC) ctcp_version_msg);
+ signal_remove("ctcp msg time", (SIGNAL_FUNC) ctcp_time_msg);
+ signal_remove("ctcp msg userinfo", (SIGNAL_FUNC) ctcp_userinfo_msg);
+ signal_remove("ctcp msg clientinfo", (SIGNAL_FUNC) ctcp_clientinfo_msg);
+ signal_remove("default ctcp reply", (SIGNAL_FUNC) ctcp_default_reply);
+ signal_remove("ctcp reply ping", (SIGNAL_FUNC) ctcp_ping_reply);
+}
diff --git a/src/fe-common/irc/fe-events-numeric.c b/src/fe-common/irc/fe-events-numeric.c
new file mode 100644
index 0000000..34ba3fe
--- /dev/null
+++ b/src/fe-common/irc/fe-events-numeric.c
@@ -0,0 +1,903 @@
+/*
+ fe-events-numeric.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/irc/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/recode.h>
+
+#include <irssi/src/irc/core/irc-servers.h>
+#include <irssi/src/irc/core/irc-channels.h>
+#include <irssi/src/core/nicklist.h>
+#include <irssi/src/irc/core/mode-lists.h>
+
+#include <irssi/src/fe-common/core/module-formats.h>
+#include <irssi/src/fe-common/core/printtext.h>
+#include <irssi/src/fe-common/core/fe-channels.h>
+#include <irssi/src/fe-common/irc/fe-irc-server.h>
+
+static void print_event_received(IRC_SERVER_REC *server, const char *data,
+ const char *nick, int target_param);
+
+static char *last_away_nick = NULL;
+static char *last_away_msg = NULL;
+
+static void event_user_mode(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *mode;
+
+ g_return_if_fail(data != NULL);
+ g_return_if_fail(server != NULL);
+
+ params = event_get_params(data, 2, NULL, &mode);
+ printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_USER_MODE,
+ g_strchomp(mode));
+ g_free(params);
+}
+
+static void event_ison(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *online;
+
+ g_return_if_fail(data != NULL);
+ g_return_if_fail(server != NULL);
+
+ params = event_get_params(data, 2, NULL, &online);
+ printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_ONLINE, online);
+ g_free(params);
+}
+
+static void event_names_list(IRC_SERVER_REC *server, const char *data)
+{
+ IRC_CHANNEL_REC *chanrec;
+ char *params, *channel, *names;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 4, NULL, NULL, &channel, &names);
+
+ chanrec = irc_channel_find(server, channel);
+ if (chanrec == NULL || chanrec->names_got) {
+ printformat_module("fe-common/core", server, channel,
+ MSGLEVEL_CRAP, TXT_NAMES,
+ channel, 0, 0, 0, 0, 0);
+ printtext(server, channel, MSGLEVEL_CRAP, "%s", names);
+
+ }
+ g_free(params);
+}
+
+static void event_end_of_names(IRC_SERVER_REC *server, const char *data,
+ const char *nick)
+{
+ IRC_CHANNEL_REC *chanrec;
+ char *params, *channel;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, NULL, &channel);
+
+ chanrec = irc_channel_find(server, channel);
+ if (chanrec == NULL || chanrec->names_got)
+ print_event_received(server, data, nick, FALSE);
+ g_free(params);
+}
+
+static void event_who(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *nick, *channel, *user, *host, *stat, *realname, *hops;
+ char *serv, *recoded;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 8, NULL, &channel, &user,
+ &host, &serv, &nick, &stat, &realname);
+
+ /* split hops/realname */
+ hops = realname;
+ while (*realname != '\0' && *realname != ' ') realname++;
+ if (*realname == ' ')
+ *realname++ = '\0';
+
+ recoded = recode_in(SERVER(server), realname, nick);
+ printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_WHO,
+ channel, nick, stat, hops, user, host, recoded, serv);
+
+ g_free(params);
+ g_free(recoded);
+}
+
+static void event_end_of_who(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *channel;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, NULL, &channel);
+ printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_END_OF_WHO, channel);
+ g_free(params);
+}
+
+static void event_ban_list(IRC_SERVER_REC *server, const char *data)
+{
+ IRC_CHANNEL_REC *chanrec;
+ BAN_REC *banrec;
+ const char *channel;
+ char *params, *ban, *setby, *tims;
+ long secs;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 5, NULL, &channel,
+ &ban, &setby, &tims);
+ secs = *tims == '\0' ? 0 :
+ (long) (time(NULL) - atol(tims));
+
+ chanrec = irc_channel_find(server, channel);
+ banrec = chanrec == NULL ? NULL : banlist_find(chanrec->banlist, ban);
+
+ channel = get_visible_target(server, channel);
+ printformat(server, channel, MSGLEVEL_CRAP,
+ *setby == '\0' ? IRCTXT_BANLIST : IRCTXT_BANLIST_LONG,
+ banrec == NULL ? 0 : g_slist_index(chanrec->banlist, banrec)+1,
+ channel, ban, setby, secs);
+
+ g_free(params);
+}
+
+static void event_eban_list(IRC_SERVER_REC *server, const char *data)
+{
+ const char *channel;
+ char *params, *ban, *setby, *tims;
+ long secs;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 5, NULL, &channel,
+ &ban, &setby, &tims);
+ secs = *tims == '\0' ? 0 :
+ (long) (time(NULL) - atol(tims));
+
+ channel = get_visible_target(server, channel);
+ printformat(server, channel, MSGLEVEL_CRAP,
+ *setby == '\0' ? IRCTXT_EBANLIST : IRCTXT_EBANLIST_LONG,
+ channel, ban, setby, secs);
+
+ g_free(params);
+}
+
+static void event_silence_list(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *nick, *mask;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 3, NULL, &nick, &mask);
+ printformat(server, NULL, MSGLEVEL_CRAP,
+ IRCTXT_SILENCE_LINE, nick, mask);
+ g_free(params);
+}
+
+static void event_accept_list(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *accepted;
+
+ g_return_if_fail(data != NULL);
+ g_return_if_fail(server != NULL);
+
+ params = event_get_params(data, 2 | PARAM_FLAG_GETREST,
+ NULL, &accepted);
+ printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_ACCEPT_LIST, accepted);
+ g_free(params);
+}
+
+static void event_invite_list(IRC_SERVER_REC *server, const char *data)
+{
+ const char *channel;
+ char *params, *invite, *setby, *tims;
+ long secs;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 5, NULL, &channel, &invite,
+ &setby, &tims);
+ secs = *tims == '\0' ? 0 :
+ (long) (time(NULL) - atol(tims));
+
+ channel = get_visible_target(server, channel);
+ printformat(server, channel, MSGLEVEL_CRAP,
+ *setby == '\0' ? IRCTXT_INVITELIST : IRCTXT_INVITELIST_LONG,
+ channel, invite, setby, secs);
+ g_free(params);
+}
+
+static void event_nick_in_use(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *nick;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, NULL, &nick);
+ if (server->connected) {
+ printformat(server, NULL, MSGLEVEL_CRAP,
+ IRCTXT_NICK_IN_USE, nick);
+ }
+
+ g_free(params);
+}
+
+static void event_topic_get(IRC_SERVER_REC *server, const char *data)
+{
+ const char *channel;
+ char *params, *topic, *recoded;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 3, NULL, &channel, &topic);
+ recoded = recode_in(SERVER(server), topic, channel);
+ channel = get_visible_target(server, channel);
+ printformat(server, channel, MSGLEVEL_CRAP,
+ IRCTXT_TOPIC, channel, recoded);
+ g_free(params);
+ g_free(recoded);
+}
+
+static void event_topic_info(IRC_SERVER_REC *server, const char *data)
+{
+ const char *channel;
+ char *params, *timestr, *bynick, *byhost, *topictime;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 4, NULL, &channel,
+ &bynick, &topictime);
+
+ timestr = my_asctime((time_t) atol(topictime));
+
+ byhost = strchr(bynick, '!');
+ if (byhost != NULL)
+ *byhost++ = '\0';
+
+ channel = get_visible_target(server, channel);
+ printformat(server, channel, MSGLEVEL_CRAP, IRCTXT_TOPIC_INFO,
+ bynick, timestr, byhost == NULL ? "" : byhost);
+ g_free(timestr);
+ g_free(params);
+}
+
+static void event_channel_mode(IRC_SERVER_REC *server, const char *data)
+{
+ const char *channel;
+ char *params, *mode;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 3 | PARAM_FLAG_GETREST,
+ NULL, &channel, &mode);
+ channel = get_visible_target(server, channel);
+ printformat(server, channel, MSGLEVEL_CRAP,
+ IRCTXT_CHANNEL_MODE, channel, g_strchomp(mode));
+ g_free(params);
+}
+
+static void event_channel_created(IRC_SERVER_REC *server, const char *data)
+{
+ const char *channel;
+ char *params, *createtime, *timestr;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 3, NULL, &channel, &createtime);
+
+ timestr = my_asctime((time_t) atol(createtime));
+ channel = get_visible_target(server, channel);
+ printformat(server, channel, MSGLEVEL_CRAP,
+ IRCTXT_CHANNEL_CREATED, channel, timestr);
+ g_free(timestr);
+ g_free(params);
+}
+
+static void event_nowaway(IRC_SERVER_REC *server, const char *data)
+{
+ printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_AWAY);
+}
+
+static void event_unaway(IRC_SERVER_REC *server, const char *data)
+{
+ printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_UNAWAY);
+}
+
+static void event_away(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *nick, *awaymsg, *recoded;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 3, NULL, &nick, &awaymsg);
+ recoded = recode_in(SERVER(server), awaymsg, nick);
+ if (!settings_get_bool("show_away_once") ||
+ last_away_nick == NULL ||
+ g_ascii_strcasecmp(last_away_nick, nick) != 0 ||
+ last_away_msg == NULL ||
+ g_ascii_strcasecmp(last_away_msg, awaymsg) != 0) {
+ /* don't show the same away message
+ from the same nick all the time */
+ g_free_not_null(last_away_nick);
+ g_free_not_null(last_away_msg);
+ last_away_nick = g_strdup(nick);
+ last_away_msg = g_strdup(awaymsg);
+
+ printformat(server, nick, MSGLEVEL_CRAP,
+ IRCTXT_NICK_AWAY, nick, recoded);
+ }
+ g_free(params);
+ g_free(recoded);
+}
+
+static void event_userhost(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *hosts;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, NULL, &hosts);
+ printtext(server, NULL, MSGLEVEL_CRAP, "%s", hosts);
+ g_free(params);
+}
+
+static void event_sent_invite(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *nick, *channel;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 3, NULL, &nick, &channel);
+ printformat(server, nick, MSGLEVEL_CRAP,
+ IRCTXT_INVITING, nick, channel);
+ g_free(params);
+}
+
+static void event_chanserv_url(IRC_SERVER_REC *server, const char *data)
+{
+ const char *channel;
+ char *params, *url;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 3, NULL, &channel, &url);
+ channel = get_visible_target(server, channel);
+ printformat(server, channel, MSGLEVEL_CRAP,
+ IRCTXT_CHANNEL_URL, channel, url);
+ g_free(params);
+}
+
+static void event_target_unavailable(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr)
+{
+ IRC_CHANNEL_REC *chanrec;
+ char *params, *target;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, NULL, &target);
+ if (!server_ischannel(SERVER(server), target)) {
+ /* nick unavailable */
+ printformat(server, NULL, MSGLEVEL_CRAP,
+ IRCTXT_NICK_UNAVAILABLE, target);
+ } else {
+ chanrec = irc_channel_find(server, target);
+ if (chanrec != NULL && chanrec->joined) {
+ /* dalnet - can't change nick while being banned */
+ print_event_received(server, data, nick, FALSE);
+ } else {
+ /* channel is unavailable. */
+ printformat(server, NULL, MSGLEVEL_CRAP,
+ IRCTXT_JOINERROR_UNAVAIL, target);
+ }
+ }
+
+ g_free(params);
+}
+
+static void event_no_such_nick(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr)
+{
+ char *params, *unick;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, NULL, &unick);
+ if (!g_strcmp0(unick, "*"))
+ /* more information will be in the description,
+ * e.g. * :Target left IRC. Failed to deliver: [hi] */
+ print_event_received(server, data, nick, FALSE);
+ else
+ printformat(server, unick, MSGLEVEL_CRAP, IRCTXT_NO_SUCH_NICK, unick);
+ g_free(params);
+}
+
+static void event_no_such_channel(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *channel;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, NULL, &channel);
+ printformat(server, channel, MSGLEVEL_CRAP,
+ IRCTXT_NO_SUCH_CHANNEL, channel);
+ g_free(params);
+}
+
+static void cannot_join(IRC_SERVER_REC *server, const char *data, int format)
+{
+ char *params, *channel;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, NULL, &channel);
+ printformat(server, NULL, MSGLEVEL_CRAP, format, channel);
+ g_free(params);
+}
+
+static void event_too_many_channels(IRC_SERVER_REC *server, const char *data)
+{
+ cannot_join(server, data, IRCTXT_JOINERROR_TOOMANY);
+}
+
+static void event_duplicate_channel(IRC_SERVER_REC *server, const char *data,
+ const char *nick)
+{
+ char *params, *channel, *p;
+
+ g_return_if_fail(data != NULL);
+
+ /* this new addition to ircd breaks completely with older
+ "standards", "nick Duplicate ::!!channel ...." */
+ params = event_get_params(data, 3, NULL, NULL, &channel);
+ p = strchr(channel, ' ');
+ if (p != NULL) *p = '\0';
+
+ if (channel[0] == '!' && channel[1] == '!') {
+ printformat(server, NULL, MSGLEVEL_CRAP,
+ IRCTXT_JOINERROR_DUPLICATE, channel+1);
+ } else
+ print_event_received(server, data, nick, FALSE);
+
+ g_free(params);
+}
+
+static void event_channel_is_full(IRC_SERVER_REC *server, const char *data)
+{
+ cannot_join(server, data, IRCTXT_JOINERROR_FULL);
+}
+
+static void event_invite_only(IRC_SERVER_REC *server, const char *data)
+{
+ cannot_join(server, data, IRCTXT_JOINERROR_INVITE);
+}
+
+static void event_banned(IRC_SERVER_REC *server, const char *data)
+{
+ cannot_join(server, data, IRCTXT_JOINERROR_BANNED);
+}
+
+static void event_bad_channel_key(IRC_SERVER_REC *server, const char *data)
+{
+ cannot_join(server, data, IRCTXT_JOINERROR_BAD_KEY);
+}
+
+static void event_bad_channel_mask(IRC_SERVER_REC *server, const char *data)
+{
+ cannot_join(server, data, IRCTXT_JOINERROR_BAD_MASK);
+}
+
+static void event_477(IRC_SERVER_REC *server, const char *data, const char *nick)
+{
+ /* Numeric 477 can mean many things:
+ * modeless channel, cannot join/send to channel (+r/+R/+M).
+ * If we tried to join this channel, display the error in the
+ * status window. Otherwise display it in the channel window.
+ */
+ IRC_CHANNEL_REC *chanrec;
+ char *params, *channel;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, NULL, &channel);
+
+ chanrec = irc_channel_find(server, channel);
+ print_event_received(server, data, nick, chanrec == NULL || chanrec->joined);
+ g_free(params);
+}
+
+static void event_489(IRC_SERVER_REC *server, const char *data, const char *nick)
+{
+ /* Numeric 489 can mean one of two things things:
+ * cannot join to channel (secure only), or not chanop or voice.
+ * If we tried to join this channel, display the joinerror.
+ * Otherwise depending on the channel being joined or not
+ * display the error in the channel or status window.
+ */
+ IRC_CHANNEL_REC *chanrec;
+ char *params, *channel;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, NULL, &channel);
+
+ chanrec = irc_channel_find(server, channel);
+ if (chanrec != NULL && !chanrec->joined) {
+ cannot_join(server, data, IRCTXT_JOINERROR_SECURE_ONLY);
+ } else {
+ print_event_received(server, data, nick, chanrec == NULL || chanrec->joined);
+ }
+ g_free(params);
+}
+
+static void event_help(IRC_SERVER_REC *server, int formatnum, const char *data)
+{
+ /* Common handling for umerics 704 (RPL_HELPSTART), 705 (RPL_HELPTXT),
+ * and 706 (RPL_ENDOFHELP); sent as a reply to HELP or HELPOP command.
+ */
+ char *params, *topic, *help_text;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 3, NULL, &topic, &help_text);
+
+ g_return_if_fail(help_text != NULL);
+
+ if (help_text[0] == '\0') {
+ /* Empty lines can be used by servers for styling; and we need to replace
+ * them with something non-empty or they would be dropped when displayed.
+ */
+ help_text = " ";
+ }
+
+ printformat(server, NULL, MSGLEVEL_CRAP, formatnum, topic, help_text);
+ g_free(params);
+}
+
+static void event_helpstart(IRC_SERVER_REC *server, const char *data, const char *nick)
+{
+ /* Numeric 704 (RPL_HELPSTART) sent as a reply to HELP or HELPOP command.
+ */
+ event_help(server, IRCTXT_SERVER_HELP_START, data);
+}
+
+static void event_helptxt(IRC_SERVER_REC *server, const char *data, const char *nick)
+{
+ /* Numeric 705 (RPL_HELPTXT), sent as a reply to HELP or HELPOP command.
+ */
+ event_help(server, IRCTXT_SERVER_HELP_TXT, data);
+}
+
+static void event_endofhelp(IRC_SERVER_REC *server, const char *data, const char *nick)
+{
+ /* Numeric 706 (RPL_ENDOFHELP), sent as a reply to HELP or HELPOP command.
+ */
+ event_help(server, IRCTXT_SERVER_END_OF_HELP, data);
+}
+
+static void event_target_too_fast(IRC_SERVER_REC *server, const char *data,
+ const char *nick)
+{
+ /* Target change too fast, could be nick or channel.
+ * If we tried to join this channel, display the error in the
+ * status window. Otherwise display it in the channel window.
+ */
+ IRC_CHANNEL_REC *chanrec;
+ char *params, *channel;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, NULL, &channel);
+
+ chanrec = irc_channel_find(server, channel);
+ print_event_received(server, data, nick, chanrec == NULL || chanrec->joined);
+ g_free(params);
+}
+
+static void event_unknown_mode(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *mode;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, NULL, &mode);
+ printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_UNKNOWN_MODE, mode);
+ g_free(params);
+}
+
+static void event_numeric(IRC_SERVER_REC *server, const char *data,
+ const char *nick)
+{
+ data = strchr(data, ' ');
+ if (data != NULL)
+ print_event_received(server, data+1, nick, FALSE);
+}
+
+static void print_event_received(IRC_SERVER_REC *server, const char *data,
+ const char *nick, int target_param)
+{
+ char *target, *args, *ptr, *ptr2, *recoded;
+ int format;
+
+ g_return_if_fail(data != NULL);
+
+ /* first param is our nick, "*" or a channel */
+ ptr = strchr(data, ' ');
+ if (ptr == NULL)
+ return;
+ ptr++;
+
+ if (server_ischannel(SERVER(server), data)) /* directed at channel */
+ target = g_strndup(data, (int)(ptr - data - 1));
+ else if (!target_param || *ptr == ':' || (ptr2 = strchr(ptr, ' ')) == NULL)
+ target = NULL;
+ else {
+ /* target parameter expected and present */
+ target = g_strndup(ptr, (int) (ptr2-ptr));
+ }
+
+ /* param1 param2 ... :last parameter */
+ if (*ptr == ':') {
+ /* only one parameter */
+ args = g_strdup(ptr+1);
+ } else {
+ args = g_strdup(ptr);
+ ptr = strstr(args, " :");
+ if (ptr != NULL)
+ memmove(ptr+1, ptr+2, strlen(ptr+1));
+ }
+
+ recoded = recode_in(SERVER(server), args, NULL);
+ format = nick == NULL || server->real_address == NULL ||
+ g_strcmp0(nick, server->real_address) == 0 ?
+ IRCTXT_DEFAULT_EVENT : IRCTXT_DEFAULT_EVENT_SERVER;
+ printformat(server, target, MSGLEVEL_CRAP, format,
+ nick, recoded, current_server_event);
+
+ g_free(recoded);
+ g_free(args);
+ g_free(target);
+}
+
+static void event_received(IRC_SERVER_REC *server, const char *data,
+ const char *nick)
+{
+ print_event_received(server, data, nick, FALSE);
+}
+
+static void event_target_received(IRC_SERVER_REC *server, const char *data,
+ const char *nick)
+{
+ print_event_received(server, data, nick, TRUE);
+}
+
+static void event_motd(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr)
+{
+ /* don't ignore motd anymore after 3 seconds of connection time -
+ we might have called /MOTD */
+ if (settings_get_bool("skip_motd") && !server->motd_got)
+ return;
+
+ print_event_received(server, data, nick, FALSE);
+}
+
+static void sig_empty(void)
+{
+}
+
+void fe_events_numeric_init(void)
+{
+ last_away_nick = NULL;
+ last_away_msg = NULL;
+
+ /* clang-format off */
+ signal_add("event 221", (SIGNAL_FUNC) event_user_mode);
+ signal_add("event 303", (SIGNAL_FUNC) event_ison);
+ signal_add("event 353", (SIGNAL_FUNC) event_names_list);
+ signal_add_first("event 366", (SIGNAL_FUNC) event_end_of_names);
+ signal_add("event 352", (SIGNAL_FUNC) event_who);
+ signal_add("event 315", (SIGNAL_FUNC) event_end_of_who);
+ signal_add("event 271", (SIGNAL_FUNC) event_silence_list);
+ signal_add("event 272", (SIGNAL_FUNC) sig_empty);
+ signal_add("event 281", (SIGNAL_FUNC) event_accept_list);
+ signal_add("event 367", (SIGNAL_FUNC) event_ban_list);
+ signal_add("event 348", (SIGNAL_FUNC) event_eban_list);
+ signal_add("event 346", (SIGNAL_FUNC) event_invite_list);
+ signal_add("event 433", (SIGNAL_FUNC) event_nick_in_use);
+ signal_add("event 332", (SIGNAL_FUNC) event_topic_get);
+ signal_add("event 333", (SIGNAL_FUNC) event_topic_info);
+ signal_add("event 324", (SIGNAL_FUNC) event_channel_mode);
+ signal_add("event 329", (SIGNAL_FUNC) event_channel_created);
+ signal_add("event 306", (SIGNAL_FUNC) event_nowaway);
+ signal_add("event 305", (SIGNAL_FUNC) event_unaway);
+ signal_add("event 301", (SIGNAL_FUNC) event_away);
+ signal_add("event 328", (SIGNAL_FUNC) event_chanserv_url);
+ signal_add("event 302", (SIGNAL_FUNC) event_userhost);
+ signal_add("event 341", (SIGNAL_FUNC) event_sent_invite);
+
+ signal_add("event 437", (SIGNAL_FUNC) event_target_unavailable);
+ signal_add("event 401", (SIGNAL_FUNC) event_no_such_nick);
+ signal_add("event 403", (SIGNAL_FUNC) event_no_such_channel);
+ signal_add("event 405", (SIGNAL_FUNC) event_too_many_channels);
+ signal_add("event 407", (SIGNAL_FUNC) event_duplicate_channel);
+ signal_add("event 471", (SIGNAL_FUNC) event_channel_is_full);
+ signal_add("event 472", (SIGNAL_FUNC) event_unknown_mode);
+ signal_add("event 473", (SIGNAL_FUNC) event_invite_only);
+ signal_add("event 474", (SIGNAL_FUNC) event_banned);
+ signal_add("event 475", (SIGNAL_FUNC) event_bad_channel_key);
+ signal_add("event 476", (SIGNAL_FUNC) event_bad_channel_mask);
+ signal_add("event 477", (SIGNAL_FUNC) event_477);
+ signal_add("event 489", (SIGNAL_FUNC) event_489); /* cannot join to channel (secure only), or not chanop or voice. */
+ signal_add("event 375", (SIGNAL_FUNC) event_motd);
+ signal_add("event 376", (SIGNAL_FUNC) event_motd);
+ signal_add("event 372", (SIGNAL_FUNC) event_motd);
+ signal_add("event 422", (SIGNAL_FUNC) event_motd);
+ signal_add("event 439", (SIGNAL_FUNC) event_target_too_fast);
+ signal_add("event 704", (SIGNAL_FUNC) event_helpstart);
+ signal_add("event 705", (SIGNAL_FUNC) event_helptxt);
+ signal_add("event 706", (SIGNAL_FUNC) event_endofhelp);
+ signal_add("event 707", (SIGNAL_FUNC) event_target_too_fast);
+
+ signal_add("default event numeric", (SIGNAL_FUNC) event_numeric);
+ /* Because default event numeric only fires if there is no specific
+ * event, add all numerics with a handler elsewhere in irssi that
+ * should not be printed specially here.
+ */
+ signal_add("event 001", (SIGNAL_FUNC) event_received);
+ signal_add("event 004", (SIGNAL_FUNC) event_received);
+ signal_add("event 005", (SIGNAL_FUNC) event_received);
+ signal_add("event 254", (SIGNAL_FUNC) event_received);
+ signal_add("event 354", (SIGNAL_FUNC) event_received);
+ signal_add("event 364", (SIGNAL_FUNC) event_received);
+ signal_add("event 365", (SIGNAL_FUNC) event_received);
+ signal_add("event 381", (SIGNAL_FUNC) event_received);
+ signal_add("event 396", (SIGNAL_FUNC) event_received);
+ signal_add("event 421", (SIGNAL_FUNC) event_received);
+ signal_add("event 432", (SIGNAL_FUNC) event_received);
+ signal_add("event 436", (SIGNAL_FUNC) event_received);
+ signal_add("event 438", (SIGNAL_FUNC) event_received);
+ signal_add("event 465", (SIGNAL_FUNC) event_received);
+ signal_add("event 470", (SIGNAL_FUNC) event_received);
+ signal_add("event 479", (SIGNAL_FUNC) event_received);
+
+ signal_add("event 344", (SIGNAL_FUNC) event_target_received); /* reop list */
+ signal_add("event 345", (SIGNAL_FUNC) event_target_received); /* end of reop list */
+ signal_add("event 347", (SIGNAL_FUNC) event_target_received); /* end of invite exception list */
+ signal_add("event 349", (SIGNAL_FUNC) event_target_received); /* end of ban exception list */
+ signal_add("event 368", (SIGNAL_FUNC) event_target_received); /* end of ban list */
+ signal_add("event 386", (SIGNAL_FUNC) event_target_received); /* owner list; old rsa challenge (harmless) */
+ signal_add("event 387", (SIGNAL_FUNC) event_target_received); /* end of owner list */
+ signal_add("event 388", (SIGNAL_FUNC) event_target_received); /* protect list */
+ signal_add("event 389", (SIGNAL_FUNC) event_target_received); /* end of protect list */
+ signal_add("event 404", (SIGNAL_FUNC) event_target_received); /* cannot send to channel */
+ signal_add("event 408", (SIGNAL_FUNC) event_target_received); /* cannot send (+c) */
+ signal_add("event 442", (SIGNAL_FUNC) event_target_received); /* you're not on that channel */
+ signal_add("event 478", (SIGNAL_FUNC) event_target_received); /* ban list is full */
+ signal_add("event 482", (SIGNAL_FUNC) event_target_received); /* not chanop */
+ signal_add("event 486", (SIGNAL_FUNC) event_target_received); /* cannot /msg (+R) */
+ signal_add("event 494", (SIGNAL_FUNC) event_target_received); /* cannot /msg (own +R) */
+ signal_add("event 506", (SIGNAL_FUNC) event_target_received); /* cannot send (+R) */
+ signal_add("event 716", (SIGNAL_FUNC) event_target_received); /* cannot /msg (+g) */
+ signal_add("event 717", (SIGNAL_FUNC) event_target_received); /* +g notified */
+ signal_add("event 728", (SIGNAL_FUNC) event_target_received); /* quiet (or other) list */
+ signal_add("event 729", (SIGNAL_FUNC) event_target_received); /* end of quiet (or other) list */
+ /* clang-format on */
+}
+
+void fe_events_numeric_deinit(void)
+{
+ g_free_not_null(last_away_nick);
+ g_free_not_null(last_away_msg);
+
+ signal_remove("event 221", (SIGNAL_FUNC) event_user_mode);
+ signal_remove("event 303", (SIGNAL_FUNC) event_ison);
+ signal_remove("event 353", (SIGNAL_FUNC) event_names_list);
+ signal_remove("event 366", (SIGNAL_FUNC) event_end_of_names);
+ signal_remove("event 352", (SIGNAL_FUNC) event_who);
+ signal_remove("event 315", (SIGNAL_FUNC) event_end_of_who);
+ signal_remove("event 271", (SIGNAL_FUNC) event_silence_list);
+ signal_remove("event 272", (SIGNAL_FUNC) sig_empty);
+ signal_remove("event 281", (SIGNAL_FUNC) event_accept_list);
+ signal_remove("event 367", (SIGNAL_FUNC) event_ban_list);
+ signal_remove("event 348", (SIGNAL_FUNC) event_eban_list);
+ signal_remove("event 346", (SIGNAL_FUNC) event_invite_list);
+ signal_remove("event 433", (SIGNAL_FUNC) event_nick_in_use);
+ signal_remove("event 332", (SIGNAL_FUNC) event_topic_get);
+ signal_remove("event 333", (SIGNAL_FUNC) event_topic_info);
+ signal_remove("event 324", (SIGNAL_FUNC) event_channel_mode);
+ signal_remove("event 329", (SIGNAL_FUNC) event_channel_created);
+ signal_remove("event 306", (SIGNAL_FUNC) event_nowaway);
+ signal_remove("event 305", (SIGNAL_FUNC) event_unaway);
+ signal_remove("event 301", (SIGNAL_FUNC) event_away);
+ signal_remove("event 328", (SIGNAL_FUNC) event_chanserv_url);
+ signal_remove("event 302", (SIGNAL_FUNC) event_userhost);
+ signal_remove("event 341", (SIGNAL_FUNC) event_sent_invite);
+
+ signal_remove("event 437", (SIGNAL_FUNC) event_target_unavailable);
+ signal_remove("event 401", (SIGNAL_FUNC) event_no_such_nick);
+ signal_remove("event 403", (SIGNAL_FUNC) event_no_such_channel);
+ signal_remove("event 405", (SIGNAL_FUNC) event_too_many_channels);
+ signal_remove("event 407", (SIGNAL_FUNC) event_duplicate_channel);
+ signal_remove("event 471", (SIGNAL_FUNC) event_channel_is_full);
+ signal_remove("event 472", (SIGNAL_FUNC) event_unknown_mode);
+ signal_remove("event 473", (SIGNAL_FUNC) event_invite_only);
+ signal_remove("event 474", (SIGNAL_FUNC) event_banned);
+ signal_remove("event 475", (SIGNAL_FUNC) event_bad_channel_key);
+ signal_remove("event 476", (SIGNAL_FUNC) event_bad_channel_mask);
+ signal_remove("event 477", (SIGNAL_FUNC) event_477);
+ signal_remove("event 489", (SIGNAL_FUNC) event_489);
+ signal_remove("event 375", (SIGNAL_FUNC) event_motd);
+ signal_remove("event 376", (SIGNAL_FUNC) event_motd);
+ signal_remove("event 372", (SIGNAL_FUNC) event_motd);
+ signal_remove("event 422", (SIGNAL_FUNC) event_motd);
+ signal_remove("event 439", (SIGNAL_FUNC) event_target_too_fast);
+ signal_remove("event 704", (SIGNAL_FUNC) event_helpstart);
+ signal_remove("event 705", (SIGNAL_FUNC) event_helptxt);
+ signal_remove("event 706", (SIGNAL_FUNC) event_endofhelp);
+ signal_remove("event 707", (SIGNAL_FUNC) event_target_too_fast);
+
+ signal_remove("default event numeric", (SIGNAL_FUNC) event_numeric);
+ signal_remove("event 001", (SIGNAL_FUNC) event_received);
+ signal_remove("event 004", (SIGNAL_FUNC) event_received);
+ signal_remove("event 005", (SIGNAL_FUNC) event_received);
+ signal_remove("event 254", (SIGNAL_FUNC) event_received);
+ signal_remove("event 354", (SIGNAL_FUNC) event_received);
+ signal_remove("event 364", (SIGNAL_FUNC) event_received);
+ signal_remove("event 365", (SIGNAL_FUNC) event_received);
+ signal_remove("event 381", (SIGNAL_FUNC) event_received);
+ signal_remove("event 396", (SIGNAL_FUNC) event_received);
+ signal_remove("event 421", (SIGNAL_FUNC) event_received);
+ signal_remove("event 432", (SIGNAL_FUNC) event_received);
+ signal_remove("event 436", (SIGNAL_FUNC) event_received);
+ signal_remove("event 438", (SIGNAL_FUNC) event_received);
+ signal_remove("event 465", (SIGNAL_FUNC) event_received);
+ signal_remove("event 470", (SIGNAL_FUNC) event_received);
+ signal_remove("event 479", (SIGNAL_FUNC) event_received);
+
+ signal_remove("event 344", (SIGNAL_FUNC) event_target_received);
+ signal_remove("event 345", (SIGNAL_FUNC) event_target_received);
+ signal_remove("event 347", (SIGNAL_FUNC) event_target_received);
+ signal_remove("event 349", (SIGNAL_FUNC) event_target_received);
+ signal_remove("event 368", (SIGNAL_FUNC) event_target_received);
+ signal_remove("event 386", (SIGNAL_FUNC) event_target_received);
+ signal_remove("event 387", (SIGNAL_FUNC) event_target_received);
+ signal_remove("event 388", (SIGNAL_FUNC) event_target_received);
+ signal_remove("event 389", (SIGNAL_FUNC) event_target_received);
+ signal_remove("event 404", (SIGNAL_FUNC) event_target_received);
+ signal_remove("event 408", (SIGNAL_FUNC) event_target_received);
+ signal_remove("event 442", (SIGNAL_FUNC) event_target_received);
+ signal_remove("event 478", (SIGNAL_FUNC) event_target_received);
+ signal_remove("event 482", (SIGNAL_FUNC) event_target_received);
+ signal_remove("event 486", (SIGNAL_FUNC) event_target_received);
+ signal_remove("event 494", (SIGNAL_FUNC) event_target_received);
+ signal_remove("event 506", (SIGNAL_FUNC) event_target_received);
+ signal_remove("event 716", (SIGNAL_FUNC) event_target_received);
+ signal_remove("event 717", (SIGNAL_FUNC) event_target_received);
+ signal_remove("event 728", (SIGNAL_FUNC) event_target_received);
+ signal_remove("event 729", (SIGNAL_FUNC) event_target_received);
+}
diff --git a/src/fe-common/irc/fe-events.c b/src/fe-common/irc/fe-events.c
new file mode 100644
index 0000000..b6b3e75
--- /dev/null
+++ b/src/fe-common/irc/fe-events.c
@@ -0,0 +1,550 @@
+/*
+ fe-events.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/irc/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/irc/core/servers-redirect.h>
+#include <irssi/src/core/servers-reconnect.h>
+#include <irssi/src/core/queries.h>
+#include <irssi/src/core/ignore.h>
+#include <irssi/src/core/recode.h>
+
+#include <irssi/src/irc/core/irc-servers.h>
+#include <irssi/src/irc/core/irc-channels.h>
+#include <irssi/src/irc/core/irc-nicklist.h>
+#include <irssi/src/irc/core/irc-masks.h>
+
+#include <irssi/src/fe-common/core/printtext.h>
+#include <irssi/src/fe-common/core/fe-queries.h>
+#include <irssi/src/fe-common/core/fe-windows.h>
+#include <irssi/src/fe-common/irc/fe-irc-server.h>
+#include <irssi/src/fe-common/irc/fe-irc-channels.h>
+
+static void event_privmsg(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr)
+{
+ char *params, *target, *msg, *recoded;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &msg);
+ if (nick == NULL) nick = server->real_address;
+ if (addr == NULL) addr = "";
+
+ if (fe_channel_is_opchannel(server, target)) {
+ /* Hybrid 6 feature, send msg to all ops in channel */
+ const char *cleantarget = fe_channel_skip_prefix(server, target);
+ recoded = recode_in(SERVER(server), msg, cleantarget);
+
+ /* pass the original target to the signal, with the @+ here
+ * the other one is only needed for recode_in*/
+ signal_emit("message irc op_public", 5,
+ server, recoded, nick, addr, target);
+ } else {
+ recoded = recode_in(SERVER(server), msg, server_ischannel(SERVER(server), target) ? target : nick);
+ signal_emit(server_ischannel(SERVER(server), target) ?
+ "message public" : "message private", 5,
+ server, recoded, nick, addr,
+ get_visible_target(server, target));
+ }
+
+ g_free(params);
+ g_free(recoded);
+}
+
+static void ctcp_action(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr,
+ const char *target)
+{
+ char *recoded;
+
+ g_return_if_fail(data != NULL);
+ recoded = recode_in(SERVER(server), data, target);
+ signal_emit("message irc action", 5,
+ server, recoded, nick, addr,
+ get_visible_target(server, target));
+ g_free(recoded);
+}
+
+static void event_notice(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr)
+{
+ char *params, *target, *msg, *recoded;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &msg);
+ recoded = recode_in(SERVER(server), msg, target);
+ if (nick == NULL) {
+ nick = server->real_address == NULL ?
+ server->connrec->address :
+ server->real_address;
+ }
+
+ signal_emit("message irc notice", 5, server, recoded, nick, addr,
+ get_visible_target(server, target));
+ g_free(params);
+ g_free(recoded);
+}
+
+static void event_join(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr)
+{
+ char *params, *channel, *tmp, *account, *realname;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 3, &channel, &account, &realname);
+ tmp = strchr(channel, 7); /* ^G does something weird.. */
+ if (tmp != NULL) *tmp = '\0';
+
+ signal_emit("message join", 6, server,
+ get_visible_target(server, channel), nick, addr, account, realname);
+ g_free(params);
+}
+
+static void event_chghost(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr)
+{
+ char *params, *user, *host, *new_addr;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, &user, &host);
+ new_addr = g_strconcat(user, "@", host, NULL);
+
+ signal_emit("message host_changed", 4, server, nick, new_addr, addr);
+
+ g_free(new_addr);
+ g_free(params);
+}
+
+static void event_account(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr)
+{
+ char *params, *account;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 1, &account);
+
+ signal_emit("message account_changed", 4, server, nick, addr, account);
+
+ g_free(params);
+}
+
+static void event_part(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr)
+{
+ char *params, *channel, *reason, *recoded;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2 | PARAM_FLAG_GETREST,
+ &channel, &reason);
+ recoded = recode_in(SERVER(server), reason, channel);
+ signal_emit("message part", 5, server,
+ get_visible_target(server, channel), nick, addr, recoded);
+ g_free(params);
+ g_free(recoded);
+}
+
+static void event_quit(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr)
+{
+ char *recoded;
+
+ g_return_if_fail(data != NULL);
+
+ if (*data == ':') data++; /* quit message */
+ recoded = recode_in(SERVER(server), data, nick);
+ signal_emit("message quit", 4, server, nick, addr, recoded);
+ g_free(recoded);
+}
+
+static void event_kick(IRC_SERVER_REC *server, const char *data,
+ const char *kicker, const char *addr)
+{
+ char *params, *channel, *nick, *reason, *recoded;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 3 | PARAM_FLAG_GETREST,
+ &channel, &nick, &reason);
+ recoded = recode_in(SERVER(server), reason, channel);
+ signal_emit("message kick", 6,
+ server, get_visible_target(server, channel),
+ nick, kicker, addr, recoded);
+ g_free(params);
+ g_free(recoded);
+}
+
+static void event_kill(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr)
+{
+ char *params, *path, *reason;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2 | PARAM_FLAG_GETREST,
+ NULL, &path);
+ reason = strstr(path, " (");
+ if (reason == NULL || reason[strlen(reason)-1] != ')') {
+ /* weird server, maybe it didn't give path */
+ reason = path;
+ path = "";
+ } else {
+ /* reason inside (...) */
+ *reason = '\0';
+ reason += 2;
+ reason[strlen(reason)-1] = '\0';
+ }
+
+ if (addr != NULL) {
+ printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_KILL,
+ nick, addr, reason, path);
+ } else {
+ printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_KILL_SERVER,
+ nick, reason, path);
+ }
+
+ g_free(params);
+}
+
+static void event_nick(IRC_SERVER_REC *server, const char *data,
+ const char *sender, const char *addr)
+{
+ char *params, *newnick;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 1, &newnick);
+
+ /* NOTE: server->nick was already changed in irc/core/irc-nicklist.c */
+ signal_emit(g_ascii_strcasecmp(newnick, server->nick) == 0 ?
+ "message own_nick" : "message nick", 4,
+ server, newnick, sender, addr);
+
+ g_free(params);
+}
+
+static void event_mode(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr)
+{
+ char *params, *channel, *mode;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2 | PARAM_FLAG_GETREST,
+ &channel, &mode);
+
+ signal_emit("message irc mode", 5,
+ server, get_visible_target(server, channel),
+ nick, addr, g_strchomp(mode));
+ g_free(params);
+}
+
+static void event_away_notify(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr)
+{
+ char *params, *awaymsg;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 1 | PARAM_FLAG_GETREST,
+ &awaymsg);
+
+ signal_emit("message away_notify", 4,
+ server, nick, addr, awaymsg);
+ g_free(params);
+}
+
+static void event_pong(IRC_SERVER_REC *server, const char *data, const char *nick)
+{
+ char *params, *host, *reply;
+
+ g_return_if_fail(data != NULL);
+ if (nick == NULL) nick = server->real_address;
+
+ params = event_get_params(data, 2 | PARAM_FLAG_GETREST, &host, &reply);
+ printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_PONG, host, reply);
+ g_free(params);
+}
+
+static void event_invite(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr)
+{
+ char *params, *invited, *channel;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, &invited, &channel);
+ if (server->nick_comp_func(invited, server->nick) == 0) {
+ signal_emit("message invite", 4,
+ server, get_visible_target(server, channel), nick, addr);
+ } else {
+ signal_emit("message invite_other", 5,
+ server, get_visible_target(server, channel), invited, nick, addr);
+ }
+ g_free(params);
+}
+
+static void event_topic(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr)
+{
+ char *params, *channel, *topic, *recoded;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2 | PARAM_FLAG_GETREST,
+ &channel, &topic);
+ recoded = recode_in(SERVER(server), topic, channel);
+ signal_emit("message topic", 5, server,
+ get_visible_target(server, channel), recoded, nick, addr);
+ g_free(params);
+ g_free(recoded);
+}
+
+static void event_error(IRC_SERVER_REC *server, const char *data)
+{
+ g_return_if_fail(data != NULL);
+
+ if (*data == ':') data++;
+ printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_ERROR, data);
+}
+
+static void event_wallops(IRC_SERVER_REC *server, const char *data, const char *nick, const char *addr)
+{
+ g_return_if_fail(data != NULL);
+
+ if (*data == ':') data++;
+ if (ignore_check(SERVER(server), nick, addr, NULL, data, MSGLEVEL_WALLOPS))
+ return;
+
+ if (g_ascii_strncasecmp(data, "\001ACTION ", 8) != 0)
+ printformat(server, NULL, MSGLEVEL_WALLOPS, IRCTXT_WALLOPS, nick, data);
+ else {
+ /* Action in WALLOP */
+ int len;
+ char *tmp;
+
+ tmp = g_strdup(data+8);
+ len = strlen(tmp);
+ if (len >= 1 && tmp[len-1] == 1) tmp[len-1] = '\0';
+ printformat(server, NULL, MSGLEVEL_WALLOPS, IRCTXT_ACTION_WALLOPS, nick, tmp);
+ g_free(tmp);
+ }
+}
+
+static void event_silence(IRC_SERVER_REC *server, const char *data, const char *nick, const char *addr)
+{
+ g_return_if_fail(data != NULL);
+
+ g_return_if_fail(*data == '+' || *data == '-');
+
+ printformat(server, NULL, MSGLEVEL_CRAP, *data == '+' ? IRCTXT_SILENCED : IRCTXT_UNSILENCED, data+1);
+}
+
+static void channel_sync(CHANNEL_REC *channel)
+{
+ g_return_if_fail(channel != NULL);
+
+ printformat(channel->server, channel->visible_name,
+ MSGLEVEL_CLIENTNOTICE|MSGLEVEL_NO_ACT,
+ IRCTXT_CHANNEL_SYNCED, channel->visible_name,
+ (long) (time(NULL)-channel->createtime));
+}
+
+static void event_connected(IRC_SERVER_REC *server)
+{
+ const char *nick;
+
+ g_return_if_fail(server != NULL);
+
+ nick = server->connrec->nick;
+ if (g_ascii_strcasecmp(server->nick, nick) == 0)
+ return;
+
+ /* someone has our nick, find out who. */
+ server_redirect_event(server, "whois", 1, nick, TRUE, NULL,
+ "event 311", "nickfind event whois",
+ "", "event empty", NULL);
+ irc_send_cmdv(server, "WHOIS %s", nick);
+}
+
+static void event_nickfind_whois(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *nick, *user, *host, *realname;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 6, NULL, &nick, &user, &host, NULL, &realname);
+ printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_YOUR_NICK_OWNED, nick, user, host, realname);
+ g_free(params);
+}
+
+static void event_ban_type_changed(void *ban_typep)
+{
+ GString *str;
+ int ban_type;
+
+ ban_type = GPOINTER_TO_INT(ban_typep);
+
+ if (ban_type == 0) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ IRCTXT_BANTYPE, "Error, using Normal");
+ return;
+ }
+
+ if (ban_type == (IRC_MASK_USER|IRC_MASK_DOMAIN)) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ IRCTXT_BANTYPE, "Normal");
+ } else if (ban_type == (IRC_MASK_HOST|IRC_MASK_DOMAIN)) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ IRCTXT_BANTYPE, "Host");
+ } else if (ban_type == IRC_MASK_DOMAIN) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ IRCTXT_BANTYPE, "Domain");
+ } else {
+ str = g_string_new("Custom:");
+ if (ban_type & IRC_MASK_NICK)
+ g_string_append(str, " Nick");
+ if (ban_type & IRC_MASK_USER)
+ g_string_append(str, " User");
+ if (ban_type & IRC_MASK_HOST)
+ g_string_append(str, " Host");
+ if (ban_type & IRC_MASK_DOMAIN)
+ g_string_append(str, " Domain");
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ IRCTXT_BANTYPE, str->str);
+ g_string_free(str, TRUE);
+ }
+}
+
+static void sig_whois_event_not_found(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *nick;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, NULL, &nick);
+ printformat(server, nick, MSGLEVEL_CRAP, IRCTXT_WHOIS_NOT_FOUND, nick);
+ g_free(params);
+}
+
+static void sig_whowas_event_end(IRC_SERVER_REC *server, const char *data,
+ const char *sender, const char *addr)
+{
+ char *params, *nick;
+
+ g_return_if_fail(data != NULL);
+
+ if (server->whowas_found) {
+ signal_emit("event 369", 4, server, data, sender, addr);
+ return;
+ }
+
+ params = event_get_params(data, 2, NULL, &nick);
+ printformat(server, nick, MSGLEVEL_CRAP, IRCTXT_WHOIS_NOT_FOUND, nick);
+ g_free(params);
+}
+
+static void event_received(IRC_SERVER_REC *server, const char *data,
+ const char *nick, const char *addr)
+{
+ if (!i_isdigit(*data)) {
+ printtext(server, NULL, MSGLEVEL_CRAP, "%s", data);
+ return;
+ }
+
+ /* numeric event. */
+ signal_emit("default event numeric", 4, server, data, nick, addr);
+}
+
+void fe_events_init(void)
+{
+ signal_add("event privmsg", (SIGNAL_FUNC) event_privmsg);
+ signal_add("ctcp action", (SIGNAL_FUNC) ctcp_action);
+ signal_add("event notice", (SIGNAL_FUNC) event_notice);
+ signal_add("event join", (SIGNAL_FUNC) event_join);
+ signal_add("event chghost", (SIGNAL_FUNC) event_chghost);
+ signal_add("event account", (SIGNAL_FUNC) event_account);
+ signal_add("event part", (SIGNAL_FUNC) event_part);
+ signal_add("event quit", (SIGNAL_FUNC) event_quit);
+ signal_add("event kick", (SIGNAL_FUNC) event_kick);
+ signal_add("event kill", (SIGNAL_FUNC) event_kill);
+ signal_add("event nick", (SIGNAL_FUNC) event_nick);
+ signal_add("event mode", (SIGNAL_FUNC) event_mode);
+ signal_add("event pong", (SIGNAL_FUNC) event_pong);
+ signal_add("event invite", (SIGNAL_FUNC) event_invite);
+ signal_add("event topic", (SIGNAL_FUNC) event_topic);
+ signal_add("event error", (SIGNAL_FUNC) event_error);
+ signal_add("event wallops", (SIGNAL_FUNC) event_wallops);
+ signal_add("event silence", (SIGNAL_FUNC) event_silence);
+ signal_add("event away", (SIGNAL_FUNC) event_away_notify);
+
+ signal_add("default event", (SIGNAL_FUNC) event_received);
+
+ signal_add("channel sync", (SIGNAL_FUNC) channel_sync);
+ signal_add("event connected", (SIGNAL_FUNC) event_connected);
+ signal_add("nickfind event whois", (SIGNAL_FUNC) event_nickfind_whois);
+ signal_add("ban type changed", (SIGNAL_FUNC) event_ban_type_changed);
+ signal_add("whois event not found", (SIGNAL_FUNC) sig_whois_event_not_found);
+ signal_add("whowas event end", (SIGNAL_FUNC) sig_whowas_event_end);
+}
+
+void fe_events_deinit(void)
+{
+ signal_remove("event privmsg", (SIGNAL_FUNC) event_privmsg);
+ signal_remove("ctcp action", (SIGNAL_FUNC) ctcp_action);
+ signal_remove("event notice", (SIGNAL_FUNC) event_notice);
+ signal_remove("event join", (SIGNAL_FUNC) event_join);
+ signal_remove("event chghost", (SIGNAL_FUNC) event_chghost);
+ signal_remove("event account", (SIGNAL_FUNC) event_account);
+ signal_remove("event part", (SIGNAL_FUNC) event_part);
+ signal_remove("event quit", (SIGNAL_FUNC) event_quit);
+ signal_remove("event kick", (SIGNAL_FUNC) event_kick);
+ signal_remove("event kill", (SIGNAL_FUNC) event_kill);
+ signal_remove("event nick", (SIGNAL_FUNC) event_nick);
+ signal_remove("event mode", (SIGNAL_FUNC) event_mode);
+ signal_remove("event pong", (SIGNAL_FUNC) event_pong);
+ signal_remove("event invite", (SIGNAL_FUNC) event_invite);
+ signal_remove("event topic", (SIGNAL_FUNC) event_topic);
+ signal_remove("event error", (SIGNAL_FUNC) event_error);
+ signal_remove("event wallops", (SIGNAL_FUNC) event_wallops);
+ signal_remove("event silence", (SIGNAL_FUNC) event_silence);
+ signal_remove("event away", (SIGNAL_FUNC) event_away_notify);
+
+ signal_remove("default event", (SIGNAL_FUNC) event_received);
+
+ signal_remove("channel sync", (SIGNAL_FUNC) channel_sync);
+ signal_remove("event connected", (SIGNAL_FUNC) event_connected);
+ signal_remove("nickfind event whois", (SIGNAL_FUNC) event_nickfind_whois);
+ signal_remove("ban type changed", (SIGNAL_FUNC) event_ban_type_changed);
+ signal_remove("whois event not found", (SIGNAL_FUNC) sig_whois_event_not_found);
+ signal_remove("whowas event end", (SIGNAL_FUNC) sig_whowas_event_end);
+}
diff --git a/src/fe-common/irc/fe-irc-channels.c b/src/fe-common/irc/fe-irc-channels.c
new file mode 100644
index 0000000..93ff360
--- /dev/null
+++ b/src/fe-common/irc/fe-irc-channels.c
@@ -0,0 +1,111 @@
+/*
+ fe-irc-channels.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/irc/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/levels.h>
+
+#include <irssi/src/irc/core/irc-servers.h>
+#include <irssi/src/irc/core/irc-channels.h>
+#include <irssi/src/irc/core/channel-rejoin.h>
+
+#include <irssi/src/fe-common/core/printtext.h>
+#include <irssi/src/fe-common/core/fe-windows.h>
+#include <irssi/src/fe-common/core/window-items.h>
+
+int fe_channel_is_opchannel(IRC_SERVER_REC *server, const char *target)
+{
+ const char *statusmsg;
+
+ /* Quick check */
+ if (server == NULL || server->prefix[(int)(unsigned char)*target] == 0)
+ return FALSE;
+
+ statusmsg = g_hash_table_lookup(server->isupport, "statusmsg");
+ if (statusmsg == NULL)
+ statusmsg = "@";
+
+ return strchr(statusmsg, *target) != NULL;
+}
+
+const char *fe_channel_skip_prefix(IRC_SERVER_REC *server, const char *target)
+{
+ const char *statusmsg;
+
+ /* Quick check */
+ if (server == NULL || server->prefix[(int)(unsigned char)*target] == 0)
+ return target;
+
+ /* Exit early if target doesn't name a channel */
+ if (server_ischannel(SERVER(server), target) == FALSE)
+ return target;
+
+ statusmsg = g_hash_table_lookup(server->isupport, "statusmsg");
+
+ /* Hack: for bahamut 1.4 which sends neither STATUSMSG nor
+ * WALLCHOPS in 005 */
+ if (statusmsg == NULL)
+ statusmsg = "@";
+
+ /* Strip the leading statusmsg prefixes */
+ while (strchr(statusmsg, *target) != NULL) {
+ target++;
+ }
+
+ return target;
+}
+
+static void sig_channel_rejoin(SERVER_REC *server, REJOIN_REC *rec)
+{
+ g_return_if_fail(rec != NULL);
+
+ printformat(server, NULL, MSGLEVEL_CLIENTNOTICE,
+ IRCTXT_CHANNEL_REJOIN, rec->channel);
+}
+
+static void sig_event_forward(SERVER_REC *server, const char *data,
+ const char *nick)
+{
+ IRC_CHANNEL_REC *channel;
+ char *params, *from, *to;
+
+ params = event_get_params(data, 3, NULL, &from, &to);
+ if (from != NULL && to != NULL && server_ischannel(server, from) && server_ischannel(server, to)) {
+ channel = irc_channel_find(server, from);
+ if (channel != NULL && irc_channel_find(server, to) == NULL) {
+ window_bind_add(window_item_window(channel),
+ server->tag, to);
+ }
+ }
+ g_free(params);
+}
+
+void fe_irc_channels_init(void)
+{
+ signal_add("channel rejoin new", (SIGNAL_FUNC) sig_channel_rejoin);
+ signal_add_first("event 470", (SIGNAL_FUNC) sig_event_forward);
+}
+
+void fe_irc_channels_deinit(void)
+{
+ signal_remove("channel rejoin new", (SIGNAL_FUNC) sig_channel_rejoin);
+ signal_remove("event 470", (SIGNAL_FUNC) sig_event_forward);
+}
diff --git a/src/fe-common/irc/fe-irc-channels.h b/src/fe-common/irc/fe-irc-channels.h
new file mode 100644
index 0000000..a577010
--- /dev/null
+++ b/src/fe-common/irc/fe-irc-channels.h
@@ -0,0 +1,10 @@
+#ifndef IRSSI_FE_COMMON_IRC_FE_IRC_CHANNELS_H
+#define IRSSI_FE_COMMON_IRC_FE_IRC_CHANNELS_H
+
+int fe_channel_is_opchannel(IRC_SERVER_REC *server, const char *target);
+const char *fe_channel_skip_prefix(IRC_SERVER_REC *server, const char *target);
+
+void fe_irc_channels_init(void);
+void fe_irc_channels_deinit(void);
+
+#endif
diff --git a/src/fe-common/irc/fe-irc-commands.c b/src/fe-common/irc/fe-irc-commands.c
new file mode 100644
index 0000000..40ad36e
--- /dev/null
+++ b/src/fe-common/irc/fe-irc-commands.c
@@ -0,0 +1,448 @@
+/*
+ fe-irc-commands.c : irssi
+
+ Copyright (C) 1999-2001 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/irc/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/special-vars.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/irc/core/mode-lists.h>
+#include <irssi/src/core/nicklist.h>
+#include <irssi/src/irc/core/irc-commands.h>
+#include <irssi/src/irc/core/irc-servers.h>
+#include <irssi/src/irc/core/irc-channels.h>
+#include <irssi/src/irc/core/irc-queries.h>
+
+#include <irssi/src/fe-common/core/fe-queries.h>
+#include <irssi/src/fe-common/core/fe-windows.h>
+#include <irssi/src/fe-common/core/window-items.h>
+#include <irssi/src/fe-common/core/printtext.h>
+#include <irssi/src/fe-common/core/keyboard.h>
+
+/* SYNTAX: ME <message> */
+static void cmd_me(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item)
+{
+ const char *target;
+ char *subdata;
+ char **splitdata;
+ int n = 0;
+
+ CMD_IRC_SERVER(server);
+ if (!IS_IRC_ITEM(item))
+ return;
+
+ if (server == NULL || !server->connected)
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ target = window_item_get_target(item);
+ splitdata = irc_server_split_action(server, target, data);
+ while ((subdata = splitdata[n++])) {
+ irc_server_send_action(server, target, subdata);
+ signal_emit("message irc own_action", 3, server, subdata,
+ item->visible_name);
+ }
+ g_strfreev(splitdata);
+}
+
+/* SYNTAX: ACTION [-<server tag>] <target> <message> */
+static void cmd_action(const char *data, IRC_SERVER_REC *server)
+{
+ GHashTable *optlist;
+ const char *target, *text;
+ char *subtext;
+ char **splittexts;
+ int n = 0;
+ void *free_arg;
+
+ CMD_IRC_SERVER(server);
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS |
+ PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST,
+ "action", &optlist, &target, &text))
+ return;
+ if (*target == '\0' || *text == '\0')
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ server = IRC_SERVER(cmd_options_get_server("action", optlist, SERVER(server)));
+ if (server == NULL || !server->connected)
+ cmd_param_error(CMDERR_NOT_CONNECTED);
+
+ splittexts = irc_server_split_action(server, target, text);
+ while ((subtext = splittexts[n++])) {
+ irc_server_send_action(server, target, subtext);
+ signal_emit("message irc own_action", 3, server, subtext,
+ target);
+ }
+
+ g_strfreev(splittexts);
+ cmd_params_free(free_arg);
+}
+
+static void cmd_notice(const char *data, IRC_SERVER_REC *server,
+ WI_ITEM_REC *item)
+{
+ const char *target, *msg;
+ void *free_arg;
+
+ CMD_IRC_SERVER(server);
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST,
+ &target, &msg))
+ return;
+ if (g_strcmp0(target, "*") == 0)
+ target = item == NULL ? "" : window_item_get_target(item);
+
+ if (*target == '\0' || *msg == '\0')
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ signal_emit("message irc own_notice", 3, server, msg, target);
+
+ cmd_params_free(free_arg);
+}
+
+static void cmd_ctcp(const char *data, IRC_SERVER_REC *server,
+ WI_ITEM_REC *item)
+{
+ const char *target;
+ char *ctcpcmd, *ctcpdata;
+ void *free_arg;
+
+ CMD_IRC_SERVER(server);
+
+ if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST,
+ &target, &ctcpcmd, &ctcpdata))
+ return;
+ if (g_strcmp0(target, "*") == 0)
+ target = item == NULL ? "" : window_item_get_target(item);
+ if (*target == '\0' || *ctcpcmd == '\0')
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ if (*target == '=') {
+ /* don't handle DCC CTCPs */
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ ascii_strup(ctcpcmd);
+ signal_emit("message irc own_ctcp", 4,
+ server, ctcpcmd, ctcpdata, target);
+
+ cmd_params_free(free_arg);
+}
+
+static void cmd_nctcp(const char *data, IRC_SERVER_REC *server,
+ WI_ITEM_REC *item)
+{
+ const char *target, *text;
+ void *free_arg;
+
+ CMD_IRC_SERVER(server);
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST,
+ &target, &text))
+ return;
+ if (g_strcmp0(target, "*") == 0)
+ target = item == NULL ? "" : window_item_get_target(item);
+ if (*target == '\0' || *text == '\0')
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ signal_emit("message irc own_notice", 3, server, text, target);
+ cmd_params_free(free_arg);
+}
+
+static void cmd_wall(const char *data, IRC_SERVER_REC *server,
+ WI_ITEM_REC *item)
+{
+ IRC_CHANNEL_REC *chanrec;
+ const char *channame, *msg;
+ void *free_arg;
+
+ CMD_IRC_SERVER(server);
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTCHAN |
+ PARAM_FLAG_GETREST, item, &channame, &msg))
+ return;
+ if (*msg == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ chanrec = irc_channel_find(server, channame);
+ if (chanrec == NULL) cmd_param_error(CMDERR_CHAN_NOT_FOUND);
+
+ signal_emit("message irc own_wall", 3, server, msg,
+ chanrec->visible_name);
+
+ cmd_params_free(free_arg);
+}
+
+static void bans_ask_channel(const char *channel, IRC_SERVER_REC *server,
+ WI_ITEM_REC *item)
+{
+ GString *str;
+
+ str = g_string_new(NULL);
+ g_string_printf(str, "%s b", channel);
+ signal_emit("command mode", 3, str->str, server, item);
+ if (server->emode_known) {
+ g_string_printf(str, "%s e", channel);
+ signal_emit("command mode", 3, str->str, server, item);
+ }
+ g_string_free(str, TRUE);
+}
+
+static void bans_show_channel(IRC_CHANNEL_REC *channel, IRC_SERVER_REC *server)
+{
+ GSList *tmp;
+ int counter;
+
+ if (channel->banlist == NULL) {
+ printformat(server, channel->visible_name,
+ MSGLEVEL_CLIENTNOTICE,
+ IRCTXT_NO_BANS, channel->visible_name);
+ return;
+ }
+
+ /* show bans.. */
+ counter = 1;
+ for (tmp = channel->banlist; tmp != NULL; tmp = tmp->next) {
+ BAN_REC *rec = tmp->data;
+
+ printformat(server, channel->visible_name, MSGLEVEL_CRAP,
+ (rec->setby == NULL || *rec->setby == '\0') ?
+ IRCTXT_BANLIST : IRCTXT_BANLIST_LONG,
+ counter, channel->visible_name,
+ rec->ban, rec->setby,
+ (int) (time(NULL)-rec->time));
+ counter++;
+ }
+}
+
+/* SYNTAX: BAN [<channel>] [<nicks>] */
+static void cmd_ban(const char *data, IRC_SERVER_REC *server,
+ WI_ITEM_REC *item)
+{
+ IRC_CHANNEL_REC *chanrec;
+ char *channel, *nicks;
+ void *free_arg;
+
+ CMD_IRC_SERVER(server);
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTCHAN |
+ PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS,
+ item, &channel, &nicks))
+ return;
+
+ if (*nicks != '\0') {
+ /* setting ban - don't handle here */
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ /* display bans */
+ chanrec = IRC_CHANNEL(item);
+ if (chanrec == NULL && *channel == '\0')
+ cmd_param_error(CMDERR_NOT_JOINED);
+
+ if (*channel != '\0' && g_strcmp0(channel, "*") != 0)
+ chanrec = irc_channel_find(server, channel);
+
+ if (chanrec == NULL || !chanrec->synced) {
+ /* not joined to such channel or not yet synced,
+ ask ban lists from server */
+ bans_ask_channel(channel, server, item);
+ } else {
+ bans_show_channel(chanrec, server);
+ }
+
+ signal_stop();
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: VER [<nick> | <channel> | *] */
+static void cmd_ver(gchar *data, IRC_SERVER_REC *server, WI_ITEM_REC *item)
+{
+ char *str;
+
+ g_return_if_fail(data != NULL);
+
+ CMD_IRC_SERVER(server);
+ if (*data == '\0' && !IS_QUERY(item))
+ cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ str = g_strdup_printf("%s VERSION", *data == '\0' ?
+ window_item_get_target(item) : data);
+ signal_emit("command ctcp", 3, str, server, item);
+ g_free(str);
+}
+
+static void cmd_topic(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ CHANNEL_REC *channel;
+ char *timestr, *bynick, *byhost;
+
+ g_return_if_fail(data != NULL);
+
+ channel = *data != '\0' ? channel_find(server, data) : CHANNEL(item);
+ if (channel == NULL) return;
+
+ printformat(server, channel->visible_name, MSGLEVEL_CRAP,
+ (channel->topic == NULL || *channel->topic == '\0') ? IRCTXT_NO_TOPIC : IRCTXT_TOPIC,
+ channel->visible_name, channel->topic);
+
+ if (channel->topic_time > 0) {
+ byhost = strchr(channel->topic_by, '!');
+ if (byhost == NULL) {
+ bynick = g_strdup(channel->topic_by);
+ byhost = "";
+ } else {
+ bynick = g_strndup(channel->topic_by,
+ (int) (byhost-channel->topic_by));
+ byhost++;
+ }
+
+ timestr = my_asctime(channel->topic_time);
+ printformat(server, channel->visible_name, MSGLEVEL_CRAP,
+ IRCTXT_TOPIC_INFO, bynick, timestr, byhost);
+ g_free(timestr);
+ g_free(bynick);
+ }
+ signal_stop();
+}
+
+/* SYNTAX: TS */
+static void cmd_ts(const char *data)
+{
+ GSList *tmp;
+
+ g_return_if_fail(data != NULL);
+
+ for (tmp = channels; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_REC *rec = tmp->data;
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_TOPIC,
+ rec->visible_name,
+ rec->topic == NULL ? "" : rec->topic);
+ }
+}
+
+typedef struct {
+ char *server_tag;
+ char *nick;
+} OPER_PASS_REC;
+
+static void cmd_oper_got_pass(const char *password, OPER_PASS_REC *rec)
+{
+ SERVER_REC *server_rec = server_find_tag(rec->server_tag);
+ if (*password != '\0' && IS_IRC_SERVER(server_rec))
+ irc_send_cmdv((IRC_SERVER_REC *) server_rec, "OPER %s %s", rec->nick, password);
+ g_free(rec->nick);
+ g_free(rec->server_tag);
+ g_free(rec);
+}
+
+static void cmd_oper(const char *data, IRC_SERVER_REC *server)
+{
+ char *nick, *password, *format;
+ void *free_arg;
+
+ g_return_if_fail(data != NULL);
+ if (!IS_IRC_SERVER(server) || !server->connected)
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ if (!cmd_get_params(data, &free_arg, 2, &nick, &password))
+ return;
+ if (*password == '\0') {
+ /* password not given, ask it.
+ irc/core handles the /OPER when password is given */
+ OPER_PASS_REC *rec;
+
+ rec = g_new(OPER_PASS_REC, 1);
+ rec->server_tag = g_strdup(server->tag);
+ rec->nick = g_strdup(*nick != '\0' ? nick : server->nick);
+
+ format = format_get_text(MODULE_NAME, NULL, server, NULL,
+ IRCTXT_ASK_OPER_PASS);
+
+ keyboard_entry_redirect((SIGNAL_FUNC) cmd_oper_got_pass,
+ format,
+ ENTRY_REDIRECT_FLAG_HIDDEN, rec);
+ g_free(format);
+
+ signal_stop();
+ }
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: SETHOST <host> <password> (non-ircops)
+ SETHOST <ident> <host> (ircops) */
+static void cmd_sethost(const char *data, IRC_SERVER_REC *server)
+{
+ GSList *tmp;
+
+ g_return_if_fail(data != NULL);
+ if (!IS_IRC_SERVER(server) || !server->connected)
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ /* Save all the joined channels in server to window binds, since
+ the server will soon /PART + /JOIN us in all channels. */
+ for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_REC *channel = tmp->data;
+
+ window_bind_add(window_item_window(channel),
+ server->tag, channel->visible_name);
+ }
+
+ irc_send_cmdv(server, "SETHOST %s", data);
+}
+
+void fe_irc_commands_init(void)
+{
+ command_bind_irc_last("me", NULL, (SIGNAL_FUNC) cmd_me);
+ command_bind_irc_last("action", NULL, (SIGNAL_FUNC) cmd_action);
+ command_bind_irc("notice", NULL, (SIGNAL_FUNC) cmd_notice);
+ command_bind_irc("ctcp", NULL, (SIGNAL_FUNC) cmd_ctcp);
+ command_bind_irc("nctcp", NULL, (SIGNAL_FUNC) cmd_nctcp);
+ command_bind_irc("wall", NULL, (SIGNAL_FUNC) cmd_wall);
+ command_bind_irc("ban", NULL, (SIGNAL_FUNC) cmd_ban);
+ command_bind_irc("ver", NULL, (SIGNAL_FUNC) cmd_ver);
+ command_bind_irc("topic", NULL, (SIGNAL_FUNC) cmd_topic);
+ command_bind_irc("ts", NULL, (SIGNAL_FUNC) cmd_ts);
+ command_bind_irc("oper", NULL, (SIGNAL_FUNC) cmd_oper);
+ command_bind_irc("sethost", NULL, (SIGNAL_FUNC) cmd_sethost);
+}
+
+void fe_irc_commands_deinit(void)
+{
+ command_unbind("me", (SIGNAL_FUNC) cmd_me);
+ command_unbind("action", (SIGNAL_FUNC) cmd_action);
+ command_unbind("notice", (SIGNAL_FUNC) cmd_notice);
+ command_unbind("ctcp", (SIGNAL_FUNC) cmd_ctcp);
+ command_unbind("nctcp", (SIGNAL_FUNC) cmd_nctcp);
+ command_unbind("wall", (SIGNAL_FUNC) cmd_wall);
+ command_unbind("ban", (SIGNAL_FUNC) cmd_ban);
+ command_unbind("ver", (SIGNAL_FUNC) cmd_ver);
+ command_unbind("topic", (SIGNAL_FUNC) cmd_topic);
+ command_unbind("ts", (SIGNAL_FUNC) cmd_ts);
+ command_unbind("oper", (SIGNAL_FUNC) cmd_oper);
+ command_unbind("sethost", (SIGNAL_FUNC) cmd_sethost);
+}
diff --git a/src/fe-common/irc/fe-irc-messages.c b/src/fe-common/irc/fe-irc-messages.c
new file mode 100644
index 0000000..5d820bb
--- /dev/null
+++ b/src/fe-common/irc/fe-irc-messages.c
@@ -0,0 +1,373 @@
+/*
+ fe-irc-messages.c : irssi
+
+ Copyright (C) 2001 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/channels.h>
+#include <irssi/src/core/ignore.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/irc/core/irc-servers.h>
+#include <irssi/src/irc/core/irc-channels.h>
+#include <irssi/src/irc/core/irc-queries.h>
+
+#include <irssi/src/fe-common/core/module-formats.h>
+#include <irssi/src/fe-common/irc/module-formats.h>
+#include <irssi/src/fe-common/core/printtext.h>
+#include <irssi/src/fe-common/core/fe-messages.h>
+
+#include <irssi/src/fe-common/core/fe-queries.h>
+#include <irssi/src/fe-common/core/hilight-text.h>
+#include <irssi/src/fe-common/core/window-items.h>
+#include <irssi/src/fe-common/irc/fe-irc-channels.h>
+#include <irssi/src/fe-common/irc/fe-irc-server.h>
+
+static void sig_message_own_public(SERVER_REC *server, const char *msg,
+ const char *target, const char *origtarget)
+{
+ const char *oldtarget;
+ char *nickmode;
+
+ if (!IS_IRC_SERVER(server))
+ return;
+ oldtarget = target;
+ target = fe_channel_skip_prefix(IRC_SERVER(server), target);
+ if (target != oldtarget) {
+ /* Hybrid 6 / Bahamut feature, send msg to all
+ ops / ops+voices in channel */
+ nickmode = channel_get_nickmode(channel_find(server, target),
+ server->nick);
+
+ printformat_module("fe-common/core", server, target,
+ MSGLEVEL_PUBLIC | MSGLEVEL_NOHILIGHT |
+ MSGLEVEL_NO_ACT,
+ TXT_OWN_MSG_CHANNEL,
+ server->nick, oldtarget, msg, nickmode);
+ g_free(nickmode);
+ signal_stop();
+ }
+
+}
+
+/* received msg to all ops in channel.
+ TODO: this code is a duplication of sig_message_public */
+static void sig_message_irc_op_public(SERVER_REC *server, const char *msg,
+ const char *nick, const char *address,
+ const char *target)
+{
+ CHANNEL_REC *chanrec;
+ char *nickmode, *optarget, *prefix, *color, *freemsg = NULL;
+ const char *cleantarget;
+ int for_me, level;
+ HILIGHT_REC *hilight;
+ TEXT_DEST_REC dest;
+
+ /* only skip here so the difference can be stored in prefix */
+ cleantarget = fe_channel_skip_prefix(IRC_SERVER(server), target);
+ prefix = g_strndup(target, cleantarget - target);
+
+ /* and clean the rest here */
+ cleantarget = get_visible_target(IRC_SERVER(server), cleantarget);
+
+ chanrec = channel_find(server, cleantarget);
+
+ nickmode = channel_get_nickmode(chanrec, nick);
+
+ optarget = g_strconcat(prefix, cleantarget, NULL);
+
+ /* Check for hilights */
+ for_me = !settings_get_bool("hilight_nick_matches") ? FALSE :
+ !settings_get_bool("hilight_nick_matches_everywhere") ?
+ nick_match_msg(chanrec, msg, server->nick) :
+ nick_match_msg_everywhere(chanrec, msg, server->nick);
+ hilight = for_me ? NULL :
+ hilight_match_nick(server, cleantarget, nick, address, MSGLEVEL_PUBLIC, msg);
+ color = (hilight == NULL) ? NULL : hilight_get_color(hilight);
+
+ level = MSGLEVEL_PUBLIC;
+ if (for_me)
+ level |= MSGLEVEL_HILIGHT;
+
+ if (ignore_check_plus(server, nick, address, cleantarget, msg, &level, TRUE)) {
+ g_free(nickmode);
+ g_free(color);
+ g_free(optarget);
+ g_free(prefix);
+ return;
+ }
+
+ if (level & MSGLEVEL_NOHILIGHT) {
+ for_me = FALSE;
+ g_free_and_null(color);
+ level &= ~MSGLEVEL_HILIGHT;
+ }
+
+ if (settings_get_bool("emphasis"))
+ msg = freemsg = expand_emphasis((WI_ITEM_REC *) chanrec, msg);
+
+ if (color != NULL) {
+ format_create_dest(&dest, server, cleantarget, level, NULL);
+ dest.address = address;
+ dest.nick = nick;
+ hilight_update_text_dest(&dest,hilight);
+ printformat_module_dest("fe-common/core", &dest,
+ TXT_PUBMSG_HILIGHT_CHANNEL,
+ color, nick, optarget, msg, nickmode);
+ } else {
+ printformat_module("fe-common/core", server, cleantarget, level,
+ for_me ? TXT_PUBMSG_ME_CHANNEL : TXT_PUBMSG_CHANNEL,
+ nick, optarget, msg, nickmode);
+ }
+
+ g_free(nickmode);
+ g_free(freemsg);
+ g_free(color);
+ g_free(optarget);
+ g_free(prefix);
+}
+
+static void sig_message_own_wall(SERVER_REC *server, const char *msg,
+ const char *target)
+{
+ char *nickmode, *optarget;
+
+ nickmode = channel_get_nickmode(channel_find(server, target),
+ server->nick);
+
+ /* this is always @, skip_prefix is not needed here */
+ optarget = g_strconcat("@", target, NULL);
+ printformat_module("fe-common/core", server, target,
+ MSGLEVEL_PUBLIC | MSGLEVEL_NOHILIGHT |
+ MSGLEVEL_NO_ACT,
+ TXT_OWN_MSG_CHANNEL,
+ server->nick, optarget, msg, nickmode);
+ g_free(nickmode);
+ g_free(optarget);
+}
+
+static void sig_message_own_action(IRC_SERVER_REC *server, const char *msg,
+ const char *target)
+{
+ void *item;
+ const char *oldtarget;
+ char *freemsg = NULL;
+
+ oldtarget = target;
+ target = fe_channel_skip_prefix(IRC_SERVER(server), target);
+ if (server_ischannel(SERVER(server), target))
+ item = channel_find(SERVER(server), target);
+ else
+ item = irc_query_find(server, target);
+
+ if (settings_get_bool("emphasis"))
+ msg = freemsg = expand_emphasis(item, msg);
+
+ printformat(server, target,
+ MSGLEVEL_ACTIONS | MSGLEVEL_NOHILIGHT | MSGLEVEL_NO_ACT |
+ (server_ischannel(SERVER(server), target) ? MSGLEVEL_PUBLIC : MSGLEVEL_MSGS),
+ item != NULL && oldtarget == target ? IRCTXT_OWN_ACTION : IRCTXT_OWN_ACTION_TARGET,
+ server->nick, msg, oldtarget);
+ g_free_not_null(freemsg);
+}
+
+static void sig_message_irc_action(IRC_SERVER_REC *server, const char *msg,
+ const char *nick, const char *address,
+ const char *target)
+{
+ void *item;
+ const char *oldtarget;
+ char *freemsg = NULL;
+ int level;
+ int own = FALSE;
+
+ oldtarget = target;
+ target = fe_channel_skip_prefix(IRC_SERVER(server), target);
+
+ level = MSGLEVEL_ACTIONS |
+ (server_ischannel(SERVER(server), target) ? MSGLEVEL_PUBLIC : MSGLEVEL_MSGS);
+
+ if (ignore_check_plus(SERVER(server), nick, address, target, msg, &level, TRUE))
+ return;
+
+ if (server_ischannel(SERVER(server), target)) {
+ item = channel_find(SERVER(server), target);
+ } else {
+ own = (!g_strcmp0(nick, server->nick));
+ item = privmsg_get_query(SERVER(server), own ? target : nick, FALSE, level);
+ }
+
+ if (settings_get_bool("emphasis"))
+ msg = freemsg = expand_emphasis(item, msg);
+
+ if (server_ischannel(SERVER(server), target)) {
+ /* channel action */
+ if (window_item_is_active(item) && target == oldtarget) {
+ /* message to active channel in window */
+ printformat(server, target, level,
+ IRCTXT_ACTION_PUBLIC, nick, msg);
+ } else {
+ /* message to not existing/active channel, or to @/+ */
+ printformat(server, target, level,
+ IRCTXT_ACTION_PUBLIC_CHANNEL,
+ nick, oldtarget, msg);
+ }
+ } else {
+ if (own) {
+ /* own action bounced */
+ printformat(server, target,
+ MSGLEVEL_ACTIONS | MSGLEVEL_MSGS,
+ item != NULL && oldtarget == target ? IRCTXT_OWN_ACTION : IRCTXT_OWN_ACTION_TARGET,
+ server->nick, msg, oldtarget);
+ } else {
+ /* private action */
+ printformat(server, nick, MSGLEVEL_ACTIONS | MSGLEVEL_MSGS,
+ item == NULL ? IRCTXT_ACTION_PRIVATE :
+ IRCTXT_ACTION_PRIVATE_QUERY,
+ nick, address == NULL ? "" : address, msg);
+ }
+ }
+
+ g_free_not_null(freemsg);
+}
+
+static char *notice_channel_context(SERVER_REC *server, const char *msg)
+{
+ if (!settings_get_bool("notice_channel_context"))
+ return NULL;
+
+ if (*msg == '[') {
+ char *end, *channel;
+ end = strpbrk(msg, " ,]");
+ if (end != NULL && *end == ']') {
+ channel = g_strndup(msg + 1, end - msg - 1);
+ if (server_ischannel(server, channel)) {
+ return channel;
+ }
+ g_free(channel);
+ }
+ }
+ return NULL;
+}
+
+static void sig_message_own_notice(IRC_SERVER_REC *server, const char *msg, const char *target)
+{
+ char *channel;
+ /* check if this is a cnotice */
+ channel = notice_channel_context((SERVER_REC *) server, msg);
+ printformat(server, channel != NULL ? channel : fe_channel_skip_prefix(server, target),
+ MSGLEVEL_NOTICES | MSGLEVEL_NOHILIGHT | MSGLEVEL_NO_ACT, IRCTXT_OWN_NOTICE,
+ target, msg);
+ g_free(channel);
+}
+
+static void sig_message_irc_notice(SERVER_REC *server, const char *msg,
+ const char *nick, const char *address,
+ const char *target)
+{
+ const char *oldtarget;
+ int level = MSGLEVEL_NOTICES;
+
+ oldtarget = target;
+ target = fe_channel_skip_prefix(IRC_SERVER(server), target);
+
+ if (address == NULL || *address == '\0') {
+ level = MSGLEVEL_SNOTES;
+ /* notice from server */
+ if (!ignore_check_plus(server, nick, "",
+ target, msg, &level, TRUE)) {
+ printformat(server, target, level,
+ IRCTXT_NOTICE_SERVER, nick, msg);
+ }
+ return;
+ }
+
+ if (ignore_check_plus(server, nick, address,
+ server_ischannel(SERVER(server), target) ? target : NULL,
+ msg, &level, TRUE))
+ return;
+
+ if (server_ischannel(SERVER(server), target)) {
+ /* notice in some channel */
+ printformat(server, target, level,
+ IRCTXT_NOTICE_PUBLIC, nick, oldtarget, msg);
+ } else {
+ char *channel;
+ /* check if this is a cnotice */
+ channel = notice_channel_context(server, msg);
+
+ if (channel == NULL) {
+ /* private notice */
+ privmsg_get_query(SERVER(server), nick, FALSE, MSGLEVEL_NOTICES);
+ }
+ printformat(server, channel == NULL ? nick : channel, level, IRCTXT_NOTICE_PRIVATE,
+ nick, address, msg);
+
+ g_free(channel);
+ }
+}
+
+static void sig_message_own_ctcp(IRC_SERVER_REC *server, const char *cmd,
+ const char *data, const char *target)
+{
+ printformat(server, fe_channel_skip_prefix(server, target), MSGLEVEL_CTCPS |
+ MSGLEVEL_NOHILIGHT | MSGLEVEL_NO_ACT,
+ IRCTXT_OWN_CTCP, target, cmd, data);
+}
+
+static void sig_message_irc_ctcp(IRC_SERVER_REC *server, const char *cmd,
+ const char *data, const char *nick,
+ const char *addr, const char *target)
+{
+ const char *oldtarget;
+
+ oldtarget = target;
+ target = fe_channel_skip_prefix(server, target);
+ printformat(server, server_ischannel(SERVER(server), target) ? target : nick, MSGLEVEL_CTCPS,
+ IRCTXT_CTCP_REQUESTED, nick, addr, cmd, data, oldtarget);
+}
+
+void fe_irc_messages_init(void)
+{
+ settings_add_bool("misc", "notice_channel_context", TRUE);
+
+ signal_add_last("message own_public", (SIGNAL_FUNC) sig_message_own_public);
+ signal_add_last("message irc op_public", (SIGNAL_FUNC) sig_message_irc_op_public);
+ signal_add_last("message irc own_wall", (SIGNAL_FUNC) sig_message_own_wall);
+ signal_add_last("message irc own_action", (SIGNAL_FUNC) sig_message_own_action);
+ signal_add_last("message irc action", (SIGNAL_FUNC) sig_message_irc_action);
+ signal_add_last("message irc own_notice", (SIGNAL_FUNC) sig_message_own_notice);
+ signal_add_last("message irc notice", (SIGNAL_FUNC) sig_message_irc_notice);
+ signal_add_last("message irc own_ctcp", (SIGNAL_FUNC) sig_message_own_ctcp);
+ signal_add_last("message irc ctcp", (SIGNAL_FUNC) sig_message_irc_ctcp);
+}
+
+void fe_irc_messages_deinit(void)
+{
+ signal_remove("message own_public", (SIGNAL_FUNC) sig_message_own_public);
+ signal_remove("message irc op_public", (SIGNAL_FUNC) sig_message_irc_op_public);
+ signal_remove("message irc own_wall", (SIGNAL_FUNC) sig_message_own_wall);
+ signal_remove("message irc own_action", (SIGNAL_FUNC) sig_message_own_action);
+ signal_remove("message irc action", (SIGNAL_FUNC) sig_message_irc_action);
+ signal_remove("message irc own_notice", (SIGNAL_FUNC) sig_message_own_notice);
+ signal_remove("message irc notice", (SIGNAL_FUNC) sig_message_irc_notice);
+ signal_remove("message irc own_ctcp", (SIGNAL_FUNC) sig_message_own_ctcp);
+ signal_remove("message irc ctcp", (SIGNAL_FUNC) sig_message_irc_ctcp);
+}
diff --git a/src/fe-common/irc/fe-irc-queries.c b/src/fe-common/irc/fe-irc-queries.c
new file mode 100644
index 0000000..7a13542
--- /dev/null
+++ b/src/fe-common/irc/fe-irc-queries.c
@@ -0,0 +1,101 @@
+/*
+ fe-irc-queries.c : irssi
+
+ Copyright (C) 2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/queries.h>
+#include <irssi/src/core/nicklist.h>
+
+static QUERY_REC *query_find_address(SERVER_REC *server, const char *address)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(IS_SERVER(server), NULL);
+
+ for (tmp = server->queries; tmp != NULL; tmp = tmp->next) {
+ QUERY_REC *rec = tmp->data;
+
+ if (*rec->name != '=' && rec->address != NULL &&
+ g_ascii_strcasecmp(address, rec->address) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static int server_has_nick(SERVER_REC *server, const char *nick)
+{
+ GSList *tmp;
+
+ for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_REC *channel = tmp->data;
+
+ if (nicklist_find(channel, nick) != NULL)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void event_privmsg(SERVER_REC *server, const char *data,
+ const char *nick, const char *address)
+{
+ QUERY_REC *query;
+
+ g_return_if_fail(data != NULL);
+
+ if (nick == NULL || address == NULL || server_ischannel(server, data) ||
+ !settings_get_bool("query_track_nick_changes"))
+ return;
+
+ query = query_find(server, nick);
+ if (query == NULL) {
+ /* check if there's query with another nick from the same
+ address. it was probably a nick change or reconnect to
+ server, so rename the query. */
+ query = query_find_address(server, address);
+ if (query != NULL) {
+ /* make sure the old nick doesn't exist anymore */
+ if (!server_has_nick(server, query->name))
+ query_change_nick(query, nick);
+ }
+ } else {
+ /* process the changes to the query structure now, before the
+ * privmsg is dispatched. */
+ if (g_strcmp0(query->name, nick) != 0)
+ query_change_nick(query, nick);
+ if (address != NULL && g_strcmp0(query->address, address) != 0)
+ query_change_address(query, address);
+ }
+}
+
+void fe_irc_queries_init(void)
+{
+ settings_add_bool("lookandfeel", "query_track_nick_changes", TRUE);
+
+ signal_add_first("event privmsg", (SIGNAL_FUNC) event_privmsg);
+}
+
+void fe_irc_queries_deinit(void)
+{
+ signal_remove("event privmsg", (SIGNAL_FUNC) event_privmsg);
+}
diff --git a/src/fe-common/irc/fe-irc-server.c b/src/fe-common/irc/fe-irc-server.c
new file mode 100644
index 0000000..4aad7ee
--- /dev/null
+++ b/src/fe-common/irc/fe-irc-server.c
@@ -0,0 +1,201 @@
+/*
+ fe-irc-server.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/irc/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/misc.h>
+
+#include <irssi/src/core/servers-setup.h>
+
+#include <irssi/src/core/levels.h>
+#include <irssi/src/irc/core/irc-chatnets.h>
+#include <irssi/src/irc/core/irc-servers.h>
+#include <irssi/src/irc/core/irc-channels.h>
+#include <irssi/src/core/servers-reconnect.h>
+#include <irssi/src/irc/core/irc-servers-setup.h>
+
+#include <irssi/src/fe-common/core/fe-windows.h>
+#include <irssi/src/fe-common/core/printtext.h>
+
+const char *get_visible_target(IRC_SERVER_REC *server, const char *target)
+{
+ IRC_CHANNEL_REC *channel;
+
+ if (*target == '!') {
+ /* visible_name of !channels is different - don't bother
+ checking other types for now, they'll just slow up */
+ channel = irc_channel_find(server, target);
+ if (channel != NULL)
+ return channel->visible_name;
+ }
+
+ return target;
+}
+
+/* SYNTAX: SERVER ADD|MODIFY [-4 | -6] [-cap | -nocap] [-tls_cert <cert>] [-tls_pkey <pkey>]
+ [-tls_pass <password>] [-tls_verify] [-tls_cafile <cafile>]
+ [-tls_capath <capath>] [-tls_ciphers <list>] [-tls | -notls]
+ [-starttls | -nostarttls | -disallow_starttls | -nodisallow_starttls]
+ [-auto | -noauto] [-network <network>] [-host <hostname>]
+ [-cmdspeed <ms>] [-cmdmax <count>] [-port <port>] <address> [<port>
+ [<password>]] */
+/* NOTE: -network replaces the old -ircnet flag. */
+static void sig_server_add_fill(IRC_SERVER_SETUP_REC *rec,
+ GHashTable *optlist)
+{
+ IRC_CHATNET_REC *ircnet;
+ char *value;
+
+ value = g_hash_table_lookup(optlist, "network");
+ /* For backwards compatibility, also allow the old name 'ircnet'.
+ But of course only if -network was not given. */
+ if (!value)
+ value = g_hash_table_lookup(optlist, "ircnet");
+
+ if (value != NULL) {
+ g_free_and_null(rec->chatnet);
+ if (*value != '\0') {
+ ircnet = ircnet_find(value);
+ rec->chatnet = ircnet != NULL ?
+ g_strdup(ircnet->name) : g_strdup(value);
+ }
+ }
+
+ value = g_hash_table_lookup(optlist, "cmdspeed");
+ if (value != NULL && *value != '\0') rec->cmd_queue_speed = atoi(value);
+ value = g_hash_table_lookup(optlist, "cmdmax");
+ if (value != NULL && *value != '\0') rec->max_cmds_at_once = atoi(value);
+ value = g_hash_table_lookup(optlist, "querychans");
+ if (value != NULL && *value != '\0') rec->max_query_chans = atoi(value);
+ if (g_hash_table_lookup(optlist, "nodisallow_starttls") ||
+ g_hash_table_lookup(optlist, "nostarttls"))
+ rec->starttls = STARTTLS_NOTSET;
+ if (g_hash_table_lookup(optlist, "disallow_starttls"))
+ rec->starttls = STARTTLS_DISALLOW;
+ if (g_hash_table_lookup(optlist, "starttls")) {
+ rec->starttls = STARTTLS_ENABLED;
+ rec->use_tls = 0;
+ }
+ if (g_hash_table_lookup(optlist, "nocap"))
+ rec->no_cap = 1;
+ if (g_hash_table_lookup(optlist, "cap"))
+ rec->no_cap = 0;
+}
+
+static void sig_server_waiting_info(IRC_SERVER_REC *server, const char *version)
+{
+ if (!IS_IRC_SERVER(server))
+ return;
+
+ printformat(server, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_SERVER_WAITING_CAP_LS, server,
+ version);
+}
+
+/* SYNTAX: SERVER LIST */
+static void cmd_server_list(const char *data)
+{
+ GString *str;
+ GSList *tmp;
+
+ str = g_string_new(NULL);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_SETUPSERVER_HEADER);
+ for (tmp = setupservers; tmp != NULL; tmp = tmp->next) {
+ IRC_SERVER_SETUP_REC *rec = tmp->data;
+
+ if (!IS_IRC_SERVER_SETUP(rec))
+ continue;
+
+ g_string_truncate(str, 0);
+ if (rec->password != NULL)
+ g_string_append(str, "(pass), ");
+ if (rec->autoconnect)
+ g_string_append(str, "autoconnect, ");
+ if (rec->no_proxy)
+ g_string_append(str, "noproxy, ");
+ if (rec->no_cap)
+ g_string_append(str, "nocap, ");
+ if (rec->starttls == STARTTLS_DISALLOW)
+ g_string_append(str, "disallow_starttls, ");
+ if (rec->starttls == STARTTLS_ENABLED)
+ g_string_append(str, "starttls, ");
+ if (rec->use_tls)
+ g_string_append(str, "tls, ");
+ if (rec->tls_cert) {
+ g_string_append_printf(str, "tls_cert: %s, ", rec->tls_cert);
+ if (rec->tls_pkey)
+ g_string_append_printf(str, "tls_pkey: %s, ", rec->tls_pkey);
+ if (rec->tls_pass)
+ g_string_append_printf(str, "(pass), ");
+ }
+ if (!rec->tls_verify)
+ g_string_append(str, "notls_verify, ");
+ if (rec->tls_cafile)
+ g_string_append_printf(str, "tls_cafile: %s, ", rec->tls_cafile);
+ if (rec->tls_capath)
+ g_string_append_printf(str, "tls_capath: %s, ", rec->tls_capath);
+ if (rec->tls_ciphers)
+ g_string_append_printf(str, "tls_ciphers: %s, ", rec->tls_ciphers);
+ if (rec->tls_pinned_cert)
+ g_string_append_printf(str, "tls_pinned_cert: %s, ", rec->tls_pinned_cert);
+ if (rec->tls_pinned_pubkey)
+ g_string_append_printf(str, "tls_pinned_pubkey: %s, ",
+ rec->tls_pinned_pubkey);
+
+ if (rec->max_cmds_at_once > 0)
+ g_string_append_printf(str, "cmdmax: %d, ", rec->max_cmds_at_once);
+ if (rec->cmd_queue_speed > 0)
+ g_string_append_printf(str, "cmdspeed: %d, ", rec->cmd_queue_speed);
+ if (rec->max_query_chans > 0)
+ g_string_append_printf(str, "querychans: %d, ", rec->max_query_chans);
+ if (rec->own_host != NULL)
+ g_string_append_printf(str, "host: %s, ", rec->own_host);
+
+ if (str->len > 1) g_string_truncate(str, str->len-2);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_SETUPSERVER_LINE,
+ rec->address, rec->port,
+ rec->chatnet == NULL ? "" : rec->chatnet,
+ str->str);
+ }
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_SETUPSERVER_FOOTER);
+ g_string_free(str, TRUE);
+}
+
+void fe_irc_server_init(void)
+{
+ signal_add("server add fill", (SIGNAL_FUNC) sig_server_add_fill);
+ signal_add("server waiting cap ls", (SIGNAL_FUNC) sig_server_waiting_info);
+ command_bind("server list", NULL, (SIGNAL_FUNC) cmd_server_list);
+
+ command_set_options("server add",
+ "-ircnet -network -cmdspeed -cmdmax -querychans starttls "
+ "nostarttls disallow_starttls nodisallow_starttls cap nocap");
+ command_set_options("server modify",
+ "-ircnet -network -cmdspeed -cmdmax -querychans starttls nostarttls "
+ "disallow_starttls nodisallow_starttls cap nocap");
+}
+
+void fe_irc_server_deinit(void)
+{
+ signal_remove("server add fill", (SIGNAL_FUNC) sig_server_add_fill);
+ signal_remove("server waiting cap ls", (SIGNAL_FUNC) sig_server_waiting_info);
+ command_unbind("server list", (SIGNAL_FUNC) cmd_server_list);
+}
diff --git a/src/fe-common/irc/fe-irc-server.h b/src/fe-common/irc/fe-irc-server.h
new file mode 100644
index 0000000..28a709e
--- /dev/null
+++ b/src/fe-common/irc/fe-irc-server.h
@@ -0,0 +1,9 @@
+#ifndef IRSSI_FE_COMMON_IRC_FE_IRC_SERVER_H
+#define IRSSI_FE_COMMON_IRC_FE_IRC_SERVER_H
+
+const char *get_visible_target(IRC_SERVER_REC *server, const char *target);
+
+void fe_irc_server_init(void);
+void fe_irc_server_deinit(void);
+
+#endif
diff --git a/src/fe-common/irc/fe-ircnet.c b/src/fe-common/irc/fe-ircnet.c
new file mode 100644
index 0000000..3484d92
--- /dev/null
+++ b/src/fe-common/irc/fe-ircnet.c
@@ -0,0 +1,249 @@
+/*
+ fe-ircnet.c : irssi
+
+ Copyright (C) 2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/irc/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/chatnets.h>
+
+#include <irssi/src/irc/core/irc-servers.h>
+#include <irssi/src/irc/core/irc-chatnets.h>
+#include <irssi/src/fe-common/core/printtext.h>
+#include <irssi/src/core/servers-setup.h>
+#include <irssi/src/core/channels-setup.h>
+
+static void cmd_network_list(void)
+{
+ GString *str;
+ GSList *tmp;
+
+ str = g_string_new(NULL);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_NETWORK_HEADER);
+ for (tmp = chatnets; tmp != NULL; tmp = tmp->next) {
+ IRC_CHATNET_REC *rec = tmp->data;
+
+ if (!IS_IRCNET(rec))
+ continue;
+
+ g_string_truncate(str, 0);
+ if (rec->nick != NULL)
+ g_string_append_printf(str, "nick: %s, ", rec->nick);
+ if (rec->alternate_nick != NULL)
+ g_string_append_printf(str, "alternate_nick: %s, ", rec->alternate_nick);
+ if (rec->username != NULL)
+ g_string_append_printf(str, "username: %s, ", rec->username);
+ if (rec->realname != NULL)
+ g_string_append_printf(str, "realname: %s, ", rec->realname);
+ if (rec->own_host != NULL)
+ g_string_append_printf(str, "host: %s, ", rec->own_host);
+ if (rec->autosendcmd != NULL)
+ g_string_append_printf(str, "autosendcmd: %s, ", rec->autosendcmd);
+ if (rec->usermode != NULL)
+ g_string_append_printf(str, "usermode: %s, ", rec->usermode);
+ if (rec->sasl_mechanism != NULL)
+ g_string_append_printf(str, "sasl_mechanism: %s, ", rec->sasl_mechanism);
+ if (rec->sasl_username != NULL)
+ g_string_append_printf(str, "sasl_username: %s, ", rec->sasl_username);
+ if (rec->sasl_password != NULL)
+ g_string_append_printf(str, "sasl_password: (pass), ");
+ if (rec->cmd_queue_speed > 0)
+ g_string_append_printf(str, "cmdspeed: %d, ", rec->cmd_queue_speed);
+ if (rec->max_cmds_at_once > 0)
+ g_string_append_printf(str, "cmdmax: %d, ", rec->max_cmds_at_once);
+ if (rec->max_query_chans > 0)
+ g_string_append_printf(str, "querychans: %d, ", rec->max_query_chans);
+
+ if (rec->max_kicks > 0)
+ g_string_append_printf(str, "max_kicks: %d, ", rec->max_kicks);
+ if (rec->max_msgs > 0)
+ g_string_append_printf(str, "max_msgs: %d, ", rec->max_msgs);
+ if (rec->max_modes > 0)
+ g_string_append_printf(str, "max_modes: %d, ", rec->max_modes);
+ if (rec->max_whois > 0)
+ g_string_append_printf(str, "max_whois: %d, ", rec->max_whois);
+
+ if (str->len > 1) g_string_truncate(str, str->len-2);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ IRCTXT_NETWORK_LINE, rec->name, str->str);
+ }
+ g_string_free(str, TRUE);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_NETWORK_FOOTER);
+}
+
+static void cmd_network_add_modify(const char *data, gboolean add)
+{
+ GHashTable *optlist;
+ char *name, *value;
+ void *free_arg;
+ IRC_CHATNET_REC *rec;
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS,
+ "network add", &optlist, &name))
+ return;
+
+ if (*name == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ rec = ircnet_find(name);
+ if (rec == NULL) {
+ if (add == FALSE) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ IRCTXT_NETWORK_NOT_FOUND, name);
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ rec = g_new0(IRC_CHATNET_REC, 1);
+ rec->name = g_strdup(name);
+ } else {
+ if (g_hash_table_lookup(optlist, "nick")) g_free_and_null(rec->nick);
+ if (g_hash_table_lookup(optlist, "alternate_nick")) g_free_and_null(rec->alternate_nick);
+ if (g_hash_table_lookup(optlist, "user")) g_free_and_null(rec->username);
+ if (g_hash_table_lookup(optlist, "realname")) g_free_and_null(rec->realname);
+ if (g_hash_table_lookup(optlist, "host")) {
+ g_free_and_null(rec->own_host);
+ rec->own_ip4 = rec->own_ip6 = NULL;
+ }
+ if (g_hash_table_lookup(optlist, "usermode")) g_free_and_null(rec->usermode);
+ if (g_hash_table_lookup(optlist, "autosendcmd")) g_free_and_null(rec->autosendcmd);
+ if (g_hash_table_lookup(optlist, "sasl_mechanism")) g_free_and_null(rec->sasl_mechanism);
+ if (g_hash_table_lookup(optlist, "sasl_username")) g_free_and_null(rec->sasl_username);
+ if (g_hash_table_lookup(optlist, "sasl_password")) g_free_and_null(rec->sasl_password);
+ }
+
+ value = g_hash_table_lookup(optlist, "kicks");
+ if (value != NULL) rec->max_kicks = atoi(value);
+ value = g_hash_table_lookup(optlist, "msgs");
+ if (value != NULL) rec->max_msgs = atoi(value);
+ value = g_hash_table_lookup(optlist, "modes");
+ if (value != NULL) rec->max_modes = atoi(value);
+ value = g_hash_table_lookup(optlist, "whois");
+ if (value != NULL) rec->max_whois = atoi(value);
+
+ value = g_hash_table_lookup(optlist, "cmdspeed");
+ if (value != NULL) rec->cmd_queue_speed = atoi(value);
+ value = g_hash_table_lookup(optlist, "cmdmax");
+ if (value != NULL) rec->max_cmds_at_once = atoi(value);
+ value = g_hash_table_lookup(optlist, "querychans");
+ if (value != NULL) rec->max_query_chans = atoi(value);
+
+ value = g_hash_table_lookup(optlist, "nick");
+ if (value != NULL && *value != '\0') rec->nick = g_strdup(value);
+ value = g_hash_table_lookup(optlist, "alternate_nick");
+ if (value != NULL && *value != '\0') rec->alternate_nick = g_strdup(value);
+ value = g_hash_table_lookup(optlist, "user");
+ if (value != NULL && *value != '\0') rec->username = g_strdup(value);
+ value = g_hash_table_lookup(optlist, "realname");
+ if (value != NULL && *value != '\0') rec->realname = g_strdup(value);
+
+ value = g_hash_table_lookup(optlist, "host");
+ if (value != NULL && *value != '\0') {
+ rec->own_host = g_strdup(value);
+ rec->own_ip4 = rec->own_ip6 = NULL;
+ }
+
+ value = g_hash_table_lookup(optlist, "usermode");
+ if (value != NULL && *value != '\0') rec->usermode = g_strdup(value);
+ value = g_hash_table_lookup(optlist, "autosendcmd");
+ if (value != NULL && *value != '\0') rec->autosendcmd = g_strdup(value);
+
+ /* the validity of the parameters is checked in sig_server_setup_fill_chatnet */
+ value = g_hash_table_lookup(optlist, "sasl_mechanism");
+ if (value != NULL) rec->sasl_mechanism = *value != '\0' ? g_strdup(value) : NULL;
+ value = g_hash_table_lookup(optlist, "sasl_username");
+ if (value != NULL) rec->sasl_username = *value != '\0' ? g_strdup(value) : NULL;
+ value = g_hash_table_lookup(optlist, "sasl_password");
+ if (value != NULL) rec->sasl_password = *value != '\0' ? g_strdup(value) : NULL;
+
+ ircnet_create(rec);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_NETWORK_ADDED, name);
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: NETWORK ADD|MODIFY [-nick <nick>] [-alternate_nick <nick>] [-user <user>] [-realname <name>]
+ [-host <host>] [-usermode <mode>] [-autosendcmd <cmd>]
+ [-querychans <count>] [-whois <count>] [-msgs <count>]
+ [-kicks <count>] [-modes <count>] [-cmdspeed <ms>]
+ [-cmdmax <count>] [-sasl_mechanism <mechanism>]
+ [-sasl_username <username>] [-sasl_password <password>]
+ <name> */
+static void cmd_network_add(const char *data)
+{
+ cmd_network_add_modify(data, TRUE);
+}
+
+static void cmd_network_modify(const char *data)
+{
+ cmd_network_add_modify(data, FALSE);
+}
+
+/* SYNTAX: NETWORK REMOVE <network> */
+static void cmd_network_remove(const char *data)
+{
+ IRC_CHATNET_REC *rec;
+
+ if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ rec = ircnet_find(data);
+ if (rec == NULL)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_NETWORK_NOT_FOUND, data);
+ else {
+ server_setup_remove_chatnet(data);
+ channel_setup_remove_chatnet(data);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_NETWORK_REMOVED, data);
+ chatnet_remove(CHATNET(rec));
+ }
+}
+
+static void cmd_network(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ if (*data == '\0')
+ cmd_network_list();
+ else
+ command_runsub("network", data, server, item);
+}
+
+void fe_ircnet_init(void)
+{
+ command_bind("ircnet", NULL, (SIGNAL_FUNC) cmd_network);
+ command_bind("network", NULL, (SIGNAL_FUNC) cmd_network);
+ command_bind("network list", NULL, (SIGNAL_FUNC) cmd_network_list);
+ command_bind("network add", NULL, (SIGNAL_FUNC) cmd_network_add);
+ command_bind("network modify", NULL, (SIGNAL_FUNC) cmd_network_modify);
+ command_bind("network remove", NULL, (SIGNAL_FUNC) cmd_network_remove);
+
+ command_set_options("network add", "-kicks -msgs -modes -whois -cmdspeed "
+ "-cmdmax -nick -alternate_nick -user -realname -host -autosendcmd -querychans -usermode -sasl_mechanism -sasl_username -sasl_password");
+ command_set_options("network modify", "-kicks -msgs -modes -whois -cmdspeed "
+ "-cmdmax -nick -alternate_nick -user -realname -host -autosendcmd -querychans -usermode -sasl_mechanism -sasl_username -sasl_password");
+}
+
+void fe_ircnet_deinit(void)
+{
+ command_unbind("ircnet", (SIGNAL_FUNC) cmd_network);
+ command_unbind("network", (SIGNAL_FUNC) cmd_network);
+ command_unbind("network list", (SIGNAL_FUNC) cmd_network_list);
+ command_unbind("network add", (SIGNAL_FUNC) cmd_network_add);
+ command_unbind("network modify", (SIGNAL_FUNC) cmd_network_modify);
+ command_unbind("network remove", (SIGNAL_FUNC) cmd_network_remove);
+}
diff --git a/src/fe-common/irc/fe-modes.c b/src/fe-common/irc/fe-modes.c
new file mode 100644
index 0000000..623fca0
--- /dev/null
+++ b/src/fe-common/irc/fe-modes.c
@@ -0,0 +1,236 @@
+/*
+ fe-modes.c : irssi
+
+ Copyright (C) 2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/irc/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/irc/core/irc-servers.h>
+#include <irssi/src/irc/core/irc-channels.h>
+#include <irssi/src/irc/core/modes.h>
+#include <irssi/src/core/ignore.h>
+
+#include <irssi/src/fe-common/core/printtext.h>
+
+#define MODE_WAIT_TIME 3 /* how many seconds to wait for identical modes */
+
+typedef struct {
+ IRC_CHANNEL_REC *channel;
+ int level;
+ char *mode;
+ GSList *nicks;
+ time_t last_mode;
+} MODE_REC;
+
+static int mode_tag, group_multi_mode;
+static GSList *modes;
+
+static MODE_REC *mode_find_channel(IRC_CHANNEL_REC *channel)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(channel != NULL, NULL);
+
+ for (tmp = modes; tmp != NULL; tmp = tmp->next) {
+ MODE_REC *rec = tmp->data;
+
+ if (rec->channel == channel)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static void mode_destroy(MODE_REC *mode)
+{
+ g_return_if_fail(mode != NULL);
+
+ modes = g_slist_remove(modes, mode);
+ g_slist_foreach(mode->nicks, (GFunc) g_free, NULL);
+ g_slist_free(mode->nicks);
+ g_free(mode->mode);
+ g_free(mode);
+}
+
+static void print_mode(MODE_REC *rec)
+{
+ GSList *tmp;
+ char *nicks;
+
+ if (g_slist_find(channels, rec->channel) == NULL) {
+ /* channel was destroyed while we were waiting.. */
+ return;
+ }
+
+ tmp = modes; modes = NULL;
+
+ nicks = i_slist_to_string(rec->nicks, ", ");
+ printformat(rec->channel->server, rec->channel->visible_name, rec->level,
+ IRCTXT_CHANMODE_CHANGE, rec->channel->visible_name, rec->mode, nicks, "");
+ g_free(nicks);
+
+ modes = tmp;
+}
+
+/* something is going to be printed to screen, print our current netsplit
+ message before it. */
+static void sig_print_starting(void)
+{
+ while (modes != NULL) {
+ print_mode(modes->data);
+ mode_destroy(modes->data);
+ }
+
+ signal_remove("print starting", sig_print_starting);
+}
+
+static int sig_check_modes(void)
+{
+ GSList *tmp, *next;
+
+ if (modes == NULL)
+ return 1;
+
+ for (tmp = modes; tmp != NULL; tmp = next) {
+ MODE_REC *rec = tmp->data;
+
+ next = tmp->next;
+ if (time(NULL)-rec->last_mode >= MODE_WAIT_TIME) {
+ print_mode(rec);
+ mode_destroy(rec);
+ }
+ }
+
+ if (modes == NULL)
+ signal_remove("print starting", (SIGNAL_FUNC) sig_print_starting);
+ return 1;
+}
+
+static void msg_multi_mode(IRC_CHANNEL_REC *channel, int level, const char *sender,
+ const char *addr, const char *mode)
+{
+ MODE_REC *rec;
+
+ if (modes == NULL)
+ signal_add("print starting", (SIGNAL_FUNC) sig_print_starting);
+
+ rec = mode_find_channel(channel);
+ if (rec != NULL && g_strcmp0(rec->mode, mode) != 0) {
+ /* different mode than last time, show and remove the old */
+ print_mode(rec);
+ mode_destroy(rec);
+ rec = NULL;
+ }
+
+ if (rec == NULL) {
+ /* no previous mode, create new */
+ rec = g_new0(MODE_REC, 1);
+ modes = g_slist_append(modes, rec);
+
+ rec->level = level;
+ rec->channel = channel;
+ rec->mode = g_strdup(mode);
+ }
+ /* the levels (everything below MSGLEVEL_ALL) are combined (|)
+ whereas the flags (anything above) must all match (&) */
+ rec->level = ((rec->level | level) & MSGLEVEL_ALL) | (rec->level & level);
+ rec->nicks = g_slist_append(rec->nicks, g_strdup(sender));
+ rec->last_mode = time(NULL);
+
+ signal_stop();
+}
+
+/* FIXME: should be moved to fe-irc-messages.c.. */
+static void sig_message_mode(IRC_SERVER_REC *server, const char *channel,
+ const char *nick, const char *addr,
+ const char *mode)
+{
+ int level = MSGLEVEL_MODES;
+
+ if (nick == NULL) nick = server->real_address;
+
+ if (ignore_check_plus(SERVER(server), nick, addr, channel,
+ mode, &level, TRUE))
+ return;
+
+ if (!server_ischannel(SERVER(server), channel)) {
+ /* user mode change */
+ printformat(server, NULL, level,
+ IRCTXT_USERMODE_CHANGE, mode, channel);
+ } else if (addr == NULL) {
+ /* channel mode changed by server */
+ printformat(server, channel, level,
+ IRCTXT_SERVER_CHANMODE_CHANGE,
+ channel, mode, nick);
+ } else {
+ /* channel mode changed by normal user */
+ IRC_CHANNEL_REC *chanrec;
+
+ chanrec = !group_multi_mode ? NULL :
+ irc_channel_find(server, channel);
+
+ if (chanrec != NULL && g_ascii_strcasecmp(nick, server->nick) != 0)
+ msg_multi_mode(chanrec, level, nick, addr, mode);
+ else {
+ printformat(server, channel, level,
+ IRCTXT_CHANMODE_CHANGE,
+ channel, mode, nick, addr);
+ }
+ }
+}
+
+static void read_settings(void)
+{
+ int old_group;
+
+ old_group = group_multi_mode;
+ group_multi_mode = settings_get_bool("group_multi_mode");
+
+ if (old_group && !group_multi_mode) {
+ g_source_remove(mode_tag);
+ mode_tag = -1;
+ } else if (!old_group && group_multi_mode) {
+ mode_tag = g_timeout_add(1000, (GSourceFunc) sig_check_modes, NULL);
+ }
+}
+
+void fe_modes_init(void)
+{
+ settings_add_bool("misc", "group_multi_mode", TRUE);
+ mode_tag = -1;
+
+ read_settings();
+ signal_add("message irc mode", (SIGNAL_FUNC) sig_message_mode);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void fe_modes_deinit(void)
+{
+ if (mode_tag != -1)
+ g_source_remove(mode_tag);
+
+ signal_remove("message irc mode", (SIGNAL_FUNC) sig_message_mode);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+
+ signal_remove("print starting", (SIGNAL_FUNC) sig_print_starting);
+}
diff --git a/src/fe-common/irc/fe-netjoin.c b/src/fe-common/irc/fe-netjoin.c
new file mode 100644
index 0000000..ea41f7f
--- /dev/null
+++ b/src/fe-common/irc/fe-netjoin.c
@@ -0,0 +1,515 @@
+/*
+ fe-netjoin.c : irssi
+
+ Copyright (C) 2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/irc/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/irc/core/irc-servers.h>
+#include <irssi/src/irc/core/modes.h>
+#include <irssi/src/core/ignore.h>
+#include <irssi/src/irc/core/netsplit.h>
+
+#include <irssi/src/fe-common/core/printtext.h>
+
+#define NETJOIN_WAIT_TIME 5 /* how many seconds to wait for the netsplitted JOIN messages to stop */
+#define NETJOIN_MAX_WAIT 30 /* how many seconds to wait for nick to join to the rest of the channels she was before the netsplit */
+
+typedef struct {
+ char *nick;
+ GSList *old_channels;
+ GSList *now_channels;
+} NETJOIN_REC;
+
+typedef struct {
+ IRC_SERVER_REC *server;
+ time_t last_netjoin;
+
+ GSList *netjoins;
+} NETJOIN_SERVER_REC;
+
+typedef struct {
+ int count;
+ GString *nicks;
+} TEMP_PRINT_REC;
+
+static int join_tag;
+static int netjoin_max_nicks, hide_netsplit_quits;
+static int printing_joins;
+static GSList *joinservers;
+
+static NETJOIN_SERVER_REC *netjoin_find_server(IRC_SERVER_REC *server)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(server != NULL, NULL);
+
+ for (tmp = joinservers; tmp != NULL; tmp = tmp->next) {
+ NETJOIN_SERVER_REC *rec = tmp->data;
+
+ if (rec->server == server)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static NETJOIN_REC *netjoin_add(IRC_SERVER_REC *server, const char *nick,
+ GSList *channels)
+{
+ NETJOIN_REC *rec;
+ NETJOIN_SERVER_REC *srec;
+
+ g_return_val_if_fail(server != NULL, NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ rec = g_new0(NETJOIN_REC, 1);
+ rec->nick = g_strdup(nick);
+ while (channels != NULL) {
+ NETSPLIT_CHAN_REC *channel = channels->data;
+
+ rec->old_channels = g_slist_append(rec->old_channels,
+ g_strdup(channel->name));
+ channels = channels->next;
+ }
+
+ srec = netjoin_find_server(server);
+ if (srec == NULL) {
+ srec = g_new0(NETJOIN_SERVER_REC, 1);
+ srec->server = server;
+ joinservers = g_slist_append(joinservers, srec);
+ }
+
+ srec->last_netjoin = time(NULL);
+ srec->netjoins = g_slist_append(srec->netjoins, rec);
+ return rec;
+}
+
+static NETJOIN_REC *netjoin_find(IRC_SERVER_REC *server, const char *nick)
+{
+ NETJOIN_SERVER_REC *srec;
+ GSList *tmp;
+
+ g_return_val_if_fail(server != NULL, NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ srec = netjoin_find_server(server);
+ if (srec == NULL) return NULL;
+
+ for (tmp = srec->netjoins; tmp != NULL; tmp = tmp->next) {
+ NETJOIN_REC *rec = tmp->data;
+
+ if (g_ascii_strcasecmp(rec->nick, nick) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static void netjoin_remove(NETJOIN_SERVER_REC *server, NETJOIN_REC *rec)
+{
+ server->netjoins = g_slist_remove(server->netjoins, rec);
+
+ g_slist_foreach(rec->old_channels, (GFunc) g_free, NULL);
+ g_slist_foreach(rec->now_channels, (GFunc) g_free, NULL);
+ g_slist_free(rec->old_channels);
+ g_slist_free(rec->now_channels);
+
+ g_free(rec->nick);
+ g_free(rec);
+}
+
+static void netjoin_server_remove(NETJOIN_SERVER_REC *server)
+{
+ joinservers = g_slist_remove(joinservers, server);
+
+ while (server->netjoins != NULL)
+ netjoin_remove(server, server->netjoins->data);
+ g_free(server);
+}
+
+static void print_channel_netjoins(char *channel, TEMP_PRINT_REC *rec,
+ NETJOIN_SERVER_REC *server)
+{
+ if (rec->nicks->len > 0)
+ g_string_truncate(rec->nicks, rec->nicks->len-2);
+
+ printformat(server->server, channel, MSGLEVEL_JOINS,
+ rec->count > netjoin_max_nicks ?
+ IRCTXT_NETSPLIT_JOIN_MORE : IRCTXT_NETSPLIT_JOIN,
+ rec->nicks->str, rec->count-netjoin_max_nicks);
+
+ g_string_free(rec->nicks, TRUE);
+ g_free(rec);
+ g_free(channel);
+}
+
+static void print_netjoins(NETJOIN_SERVER_REC *server, const char *filter_channel)
+{
+ TEMP_PRINT_REC *temp;
+ GHashTable *channels;
+ GSList *tmp, *tmp2, *next, *next2, *old;
+
+ g_return_if_fail(server != NULL);
+
+ printing_joins = TRUE;
+
+ /* save nicks to string, clear now_channels and remove the same
+ channels from old_channels list */
+ channels = g_hash_table_new((GHashFunc) i_istr_hash, (GCompareFunc) i_istr_equal);
+ for (tmp = server->netjoins; tmp != NULL; tmp = next) {
+ NETJOIN_REC *rec = tmp->data;
+
+ next = g_slist_next(tmp);
+
+ for (tmp2 = rec->now_channels; tmp2 != NULL; tmp2 = next2) {
+ char *channel = tmp2->data;
+ char *realchannel = channel + 1;
+
+ next2 = g_slist_next(tmp2);
+
+ /* Filter the results by channel if asked to do so */
+ if (filter_channel != NULL &&
+ strcasecmp(realchannel, filter_channel) != 0)
+ continue;
+
+ temp = g_hash_table_lookup(channels, realchannel);
+ if (temp == NULL) {
+ temp = g_new0(TEMP_PRINT_REC, 1);
+ temp->nicks = g_string_new(NULL);
+ g_hash_table_insert(channels,
+ g_strdup(realchannel),
+ temp);
+ }
+
+ temp->count++;
+ if (temp->count <= netjoin_max_nicks) {
+ if (*channel != ' ')
+ g_string_append_c(temp->nicks,
+ *channel);
+ g_string_append_printf(temp->nicks, "%s, ",
+ rec->nick);
+ }
+
+ /* remove the channel from old_channels too */
+ old = i_slist_find_icase_string(rec->old_channels, realchannel);
+ if (old != NULL) {
+ void *data = old->data;
+ rec->old_channels =
+ g_slist_remove(rec->old_channels, data);
+ g_free(data);
+ }
+
+ /* drop tmp2 from the list */
+ rec->now_channels = g_slist_delete_link(rec->now_channels, tmp2);
+ g_free(channel);
+ }
+
+ if (rec->old_channels == NULL)
+ netjoin_remove(server, rec);
+ }
+
+ g_hash_table_foreach(channels, (GHFunc) print_channel_netjoins,
+ server);
+ g_hash_table_destroy(channels);
+
+ if (server->netjoins == NULL)
+ netjoin_server_remove(server);
+
+ printing_joins = FALSE;
+}
+
+/* something is going to be printed to screen, print our current netsplit
+ message before it. */
+static void sig_print_starting(TEXT_DEST_REC *dest)
+{
+ NETJOIN_SERVER_REC *rec;
+
+ if (printing_joins)
+ return;
+
+ if (!IS_IRC_SERVER(dest->server))
+ return;
+
+ rec = netjoin_find_server(IRC_SERVER(dest->server));
+ if (rec != NULL && rec->netjoins != NULL) {
+ /* if netjoins exists, the server rec should be
+ still valid. otherwise, calling server->ischannel
+ may not be safe. */
+ if (dest->target != NULL &&
+ !server_ischannel((SERVER_REC *) rec->server, dest->target))
+ return;
+
+ print_netjoins(rec, NULL);
+ }
+}
+
+static int sig_check_netjoins(void)
+{
+ GSList *tmp, *next;
+ int diff;
+ time_t now;
+
+ now = time(NULL);
+ /* first print all netjoins which haven't had any new joins
+ * for NETJOIN_WAIT_TIME; this may cause them to be removed
+ * (all users who rejoined, rejoined all channels) */
+ for (tmp = joinservers; tmp != NULL; tmp = next) {
+ NETJOIN_SERVER_REC *server = tmp->data;
+
+ next = tmp->next;
+ diff = now-server->last_netjoin;
+ if (diff <= NETJOIN_WAIT_TIME) {
+ /* wait for more JOINs */
+ continue;
+ }
+
+ if (server->netjoins != NULL)
+ print_netjoins(server, NULL);
+ }
+
+ /* now remove all netjoins which haven't had any new joins
+ * for NETJOIN_MAX_WAIT (user rejoined some but not all channels
+ * after split) */
+ for (tmp = joinservers; tmp != NULL; tmp = next) {
+ NETJOIN_SERVER_REC *server = tmp->data;
+
+ next = tmp->next;
+ diff = now-server->last_netjoin;
+ if (diff >= NETJOIN_MAX_WAIT) {
+ /* waited long enough, forget about the rest */
+ netjoin_server_remove(server);
+ }
+ }
+
+ if (joinservers == NULL) {
+ g_source_remove(join_tag);
+ signal_remove("print starting", (SIGNAL_FUNC) sig_print_starting);
+ join_tag = -1;
+ }
+ return 1;
+}
+
+static void msg_quit(IRC_SERVER_REC *server, const char *nick,
+ const char *address, const char *reason)
+{
+ if (IS_IRC_SERVER(server) && quitmsg_is_split(reason))
+ signal_stop();
+}
+
+static void msg_join(IRC_SERVER_REC *server, const char *channel,
+ const char *nick, const char *address)
+{
+ NETSPLIT_REC *split;
+ NETJOIN_REC *netjoin;
+ GSList *channels;
+ int rejoin = 1;
+
+ if (!IS_IRC_SERVER(server))
+ return;
+
+ if (ignore_check(SERVER(server), nick, address,
+ channel, NULL, MSGLEVEL_JOINS))
+ return;
+
+ split = netsplit_find(server, nick, address);
+ netjoin = netjoin_find(server, nick);
+ if (split == NULL && netjoin == NULL)
+ return;
+
+ /* if this was not a channel they split from, treat it normally */
+ if (netjoin != NULL) {
+ if (!i_slist_find_icase_string(netjoin->old_channels, channel))
+ return;
+ } else {
+ channels = split->channels;
+ while (channels != NULL) {
+ NETSPLIT_CHAN_REC *schannel = channels->data;
+
+ if (!strcasecmp(schannel->name, channel))
+ break;
+ channels = channels->next;
+ }
+ /* we still need to create a NETJOIN_REC now as the
+ * NETSPLIT_REC will be destroyed */
+ if (channels == NULL)
+ rejoin = 0;
+ }
+
+ if (join_tag == -1) {
+ join_tag = g_timeout_add(1000, (GSourceFunc)
+ sig_check_netjoins, NULL);
+ signal_add("print starting", (SIGNAL_FUNC) sig_print_starting);
+ }
+
+ if (netjoin == NULL)
+ netjoin = netjoin_add(server, nick, split->channels);
+
+ if (rejoin)
+ {
+ netjoin->now_channels = g_slist_append(netjoin->now_channels,
+ g_strconcat(" ", channel, NULL));
+ signal_stop();
+ }
+}
+
+static int netjoin_set_nickmode(IRC_SERVER_REC *server, NETJOIN_REC *rec,
+ const char *channel, char prefix)
+{
+ GSList *pos;
+ const char *flags;
+ char *found_chan = NULL;
+
+ for (pos = rec->now_channels; pos != NULL; pos = pos->next) {
+ char *chan = pos->data;
+ if (strcasecmp(chan+1, channel) == 0) {
+ found_chan = chan;
+ break;
+ }
+ }
+
+ if (found_chan == NULL)
+ return FALSE;
+
+ flags = server->get_nick_flags(SERVER(server));
+ while (*flags != '\0') {
+ if (found_chan[0] == *flags)
+ break;
+ if (prefix == *flags) {
+ found_chan[0] = prefix;
+ break;
+ }
+ flags++;
+ }
+ return TRUE;
+}
+
+static void msg_mode(IRC_SERVER_REC *server, const char *channel,
+ const char *sender, const char *addr, const char *data)
+{
+ NETJOIN_REC *rec;
+ char *params, *mode, *nicks;
+ char **nicklist, **nick, type, prefix;
+ int show;
+
+ g_return_if_fail(data != NULL);
+ if (!server_ischannel(SERVER(server), channel) || addr != NULL)
+ return;
+
+ params = event_get_params(data, 2 | PARAM_FLAG_GETREST,
+ &mode, &nicks);
+
+ /* parse server mode changes - hide operator status changes and
+ show them in the netjoin message instead as @ before the nick */
+ nick = nicklist = g_strsplit(nicks, " ", -1);
+
+ type = '+'; show = FALSE;
+ for (; *mode != '\0'; mode++) {
+ if (*mode == '+' || *mode == '-') {
+ type = *mode;
+ continue;
+ }
+
+ if (*nick != NULL && GET_MODE_PREFIX(server, *mode)) {
+ /* give/remove ops */
+ rec = netjoin_find(server, *nick);
+ prefix = GET_MODE_PREFIX(server, *mode);
+ if (rec == NULL || type != '+' || prefix == '\0' ||
+ !netjoin_set_nickmode(server, rec, channel, prefix))
+ show = TRUE;
+ nick++;
+ } else {
+ if (HAS_MODE_ARG(server, type, *mode) && *nick != NULL)
+ nick++;
+ show = TRUE;
+ }
+ }
+
+ if (!show) signal_stop();
+
+ g_strfreev(nicklist);
+ g_free(params);
+}
+
+static void read_settings(void)
+{
+ int old_hide;
+
+ old_hide = hide_netsplit_quits;
+ hide_netsplit_quits = settings_get_bool("hide_netsplit_quits");
+ netjoin_max_nicks = settings_get_int("netjoin_max_nicks");
+
+ if (old_hide && !hide_netsplit_quits) {
+ signal_remove("message quit", (SIGNAL_FUNC) msg_quit);
+ signal_remove("message join", (SIGNAL_FUNC) msg_join);
+ signal_remove("message irc mode", (SIGNAL_FUNC) msg_mode);
+ } else if (!old_hide && hide_netsplit_quits) {
+ signal_add("message quit", (SIGNAL_FUNC) msg_quit);
+ signal_add("message join", (SIGNAL_FUNC) msg_join);
+ signal_add("message irc mode", (SIGNAL_FUNC) msg_mode);
+ }
+}
+
+static void sig_server_disconnected(IRC_SERVER_REC *server)
+{
+ NETJOIN_SERVER_REC *netjoin_server;
+
+ g_return_if_fail(server != NULL);
+
+ if (!IS_IRC_SERVER(server))
+ return;
+
+ if ((netjoin_server = netjoin_find_server(server))) {
+ netjoin_server_remove(netjoin_server);
+ }
+}
+
+void fe_netjoin_init(void)
+{
+ settings_add_bool("misc", "hide_netsplit_quits", TRUE);
+ settings_add_int("misc", "netjoin_max_nicks", 10);
+
+ join_tag = -1;
+ printing_joins = FALSE;
+
+ read_settings();
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+}
+
+void fe_netjoin_deinit(void)
+{
+ while (joinservers != NULL)
+ netjoin_server_remove(joinservers->data);
+ if (join_tag != -1) {
+ g_source_remove(join_tag);
+ signal_remove("print starting", (SIGNAL_FUNC) sig_print_starting);
+ }
+
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected);
+
+ signal_remove("message quit", (SIGNAL_FUNC) msg_quit);
+ signal_remove("message join", (SIGNAL_FUNC) msg_join);
+ signal_remove("message irc mode", (SIGNAL_FUNC) msg_mode);
+}
diff --git a/src/fe-common/irc/fe-netsplit.c b/src/fe-common/irc/fe-netsplit.c
new file mode 100644
index 0000000..b76d4ce
--- /dev/null
+++ b/src/fe-common/irc/fe-netsplit.c
@@ -0,0 +1,389 @@
+/*
+ fe-netsplit.c : irssi
+
+ Copyright (C) 2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/irc/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/irc/core/irc-servers.h>
+#include <irssi/src/irc/core/irc-commands.h>
+#include <irssi/src/core/ignore.h>
+#include <irssi/src/irc/core/netsplit.h>
+
+#include <irssi/src/fe-common/core/printtext.h>
+
+#define SPLIT_WAIT_TIME 5 /* how many seconds to wait for the QUIT split messages to stop */
+
+static int split_tag;
+static int netsplit_max_nicks, netsplit_nicks_hide_threshold;
+static int printing_splits;
+
+static int get_last_split(IRC_SERVER_REC *server)
+{
+ GSList *tmp;
+ time_t last;
+
+ last = 0;
+ for (tmp = server->split_servers; tmp != NULL; tmp = tmp->next) {
+ NETSPLIT_SERVER_REC *rec = tmp->data;
+
+ if (rec->last > last) last = rec->last;
+ }
+
+ return last;
+}
+
+typedef struct {
+ char *name;
+ int nick_count, maxnickpos;
+ GString *nicks;
+} TEMP_SPLIT_CHAN_REC;
+
+typedef struct {
+ IRC_SERVER_REC *server_rec;
+ GSList *servers; /* if many servers splitted from the same one */
+ GSList *channels;
+} TEMP_SPLIT_REC;
+
+static GSList *get_source_servers(const char *server, GSList **servers)
+{
+ GSList *list, *next, *tmp;
+
+ list = NULL;
+ for (tmp = *servers; tmp != NULL; tmp = next) {
+ NETSPLIT_SERVER_REC *rec = tmp->data;
+ next = tmp->next;
+
+ if (g_ascii_strcasecmp(rec->server, server) == 0) {
+ rec->prints = 0;
+ list = g_slist_append(list, rec);
+ *servers = g_slist_remove(*servers, rec);
+ }
+ }
+
+ return list;
+}
+
+static TEMP_SPLIT_CHAN_REC *find_split_chan(TEMP_SPLIT_REC *rec,
+ const char *name)
+{
+ GSList *tmp;
+
+ for (tmp = rec->channels; tmp != NULL; tmp = tmp->next) {
+ TEMP_SPLIT_CHAN_REC *chanrec = tmp->data;
+
+ if (g_ascii_strcasecmp(chanrec->name, name) == 0)
+ return chanrec;
+ }
+
+ return NULL;
+}
+
+static void get_server_splits(void *key, NETSPLIT_REC *split,
+ TEMP_SPLIT_REC *rec)
+{
+ TEMP_SPLIT_CHAN_REC *chanrec;
+ GSList *tmp;
+
+ if (split->printed ||
+ g_slist_find(rec->servers, split->server) == NULL)
+ return;
+
+ split->printed = TRUE;
+ for (tmp = split->channels; tmp != NULL; tmp = tmp->next) {
+ NETSPLIT_CHAN_REC *splitchan = tmp->data;
+
+ if (ignore_check(SERVER(rec->server_rec), split->nick,
+ split->address, splitchan->name, "",
+ MSGLEVEL_QUITS))
+ continue;
+
+ chanrec = find_split_chan(rec, splitchan->name);
+ if (chanrec == NULL) {
+ chanrec = g_new0(TEMP_SPLIT_CHAN_REC, 1);
+ chanrec->name = splitchan->name;
+ chanrec->nicks = g_string_new(NULL);
+
+ rec->channels = g_slist_append(rec->channels, chanrec);
+ }
+
+ split->server->prints++;
+ chanrec->nick_count++;
+ if (netsplit_nicks_hide_threshold <= 0 ||
+ chanrec->nick_count <= netsplit_nicks_hide_threshold) {
+ if (splitchan->op)
+ g_string_append_c(chanrec->nicks, '@');
+ else if (splitchan->voice)
+ g_string_append_c(chanrec->nicks, '+');
+ g_string_append_printf(chanrec->nicks, "%s, ", split->nick);
+
+ if (chanrec->nick_count == netsplit_max_nicks)
+ chanrec->maxnickpos = chanrec->nicks->len;
+ }
+ }
+}
+
+static void print_server_splits(IRC_SERVER_REC *server, TEMP_SPLIT_REC *rec, const char *filter_channel)
+{
+ GString *destservers;
+ char *sourceserver;
+ GSList *tmp;
+
+ g_return_if_fail(rec->servers != NULL);
+
+ destservers = g_string_new(NULL);
+ for (tmp = rec->servers; tmp != NULL; tmp = tmp->next) {
+ NETSPLIT_SERVER_REC *rec = tmp->data;
+
+ if (rec->prints > 0) {
+ g_string_append_printf(destservers, "%s, ",
+ rec->destserver);
+ }
+ }
+ if (destservers->len == 0) {
+ /* no nicks to print in this server */
+ g_string_free(destservers, TRUE);
+ return;
+ }
+ g_string_truncate(destservers, destservers->len-2);
+
+ sourceserver = ((NETSPLIT_SERVER_REC *) (rec->servers->data))->server;
+ for (tmp = rec->channels; tmp != NULL; tmp = tmp->next) {
+ TEMP_SPLIT_CHAN_REC *chan = tmp->data;
+
+ if (filter_channel != NULL &&
+ strcasecmp(chan->name, filter_channel) != 0)
+ continue;
+
+ g_string_truncate(chan->nicks, chan->nicks->len-2);
+
+ if (netsplit_max_nicks > 0 &&
+ chan->nick_count > netsplit_max_nicks) {
+ g_string_truncate(chan->nicks, chan->maxnickpos);
+ printformat(server, chan->name, MSGLEVEL_QUITS,
+ IRCTXT_NETSPLIT_MORE, sourceserver,
+ destservers->str, chan->nicks->str,
+ chan->nick_count - netsplit_max_nicks);
+ } else {
+ printformat(server, chan->name, MSGLEVEL_QUITS,
+ IRCTXT_NETSPLIT, sourceserver,
+ destservers->str, chan->nicks->str);
+ }
+ }
+
+ g_string_free(destservers, TRUE);
+}
+
+static void temp_split_chan_free(TEMP_SPLIT_CHAN_REC *rec)
+{
+ g_string_free(rec->nicks, TRUE);
+ g_free(rec);
+}
+
+static void print_splits(IRC_SERVER_REC *server, const char *filter_channel)
+{
+ TEMP_SPLIT_REC temp;
+ GSList *servers;
+
+ printing_splits = TRUE;
+
+ servers = g_slist_copy(server->split_servers);
+ while (servers != NULL) {
+ NETSPLIT_SERVER_REC *sserver = servers->data;
+
+ /* get all the splitted servers that have the same
+ source server */
+ temp.servers = get_source_servers(sserver->server, &servers);
+ temp.server_rec = server;
+ temp.channels = NULL;
+
+ g_hash_table_foreach(server->splits,
+ (GHFunc) get_server_splits, &temp);
+ print_server_splits(server, &temp, filter_channel);
+
+ g_slist_foreach(temp.channels,
+ (GFunc) temp_split_chan_free, NULL);
+ g_slist_free(temp.servers);
+ g_slist_free(temp.channels);
+ }
+
+ printing_splits = FALSE;
+}
+
+static int check_server_splits(IRC_SERVER_REC *server)
+{
+ time_t last;
+
+ g_return_val_if_fail(IS_IRC_SERVER(server), FALSE);
+
+ last = get_last_split(server);
+ if (time(NULL)-last < SPLIT_WAIT_TIME)
+ return FALSE;
+
+ print_splits(server, NULL);
+ return TRUE;
+}
+
+/* something is going to be printed to screen, print our current netsplit
+ message before it. */
+static void sig_print_starting(TEXT_DEST_REC *dest)
+{
+ IRC_SERVER_REC *rec;
+
+ if (printing_splits)
+ return;
+
+ if (!IS_IRC_SERVER(dest->server))
+ return;
+
+ rec = IRC_SERVER(dest->server);
+ if (rec->split_servers != NULL) {
+ /* if split_servers exists, the server rec should be
+ still valid. otherwise, calling server->ischannel
+ may not be safe. */
+ if (dest->target != NULL && !server_ischannel((SERVER_REC *) rec, dest->target))
+ return;
+
+ print_splits(rec, NULL);
+ }
+}
+
+static int sig_check_splits(void)
+{
+ GSList *tmp;
+ int stop;
+
+ stop = TRUE;
+ for (tmp = servers; tmp != NULL; tmp = tmp->next) {
+ IRC_SERVER_REC *rec = tmp->data;
+
+ if (!IS_IRC_SERVER(rec))
+ continue;
+
+ if (rec->split_servers != NULL) {
+ if (!check_server_splits(rec))
+ stop = FALSE;
+ }
+ }
+
+ if (stop) {
+ g_source_remove(split_tag);
+ signal_remove("print starting", (SIGNAL_FUNC) sig_print_starting);
+ split_tag = -1;
+ }
+ return 1;
+}
+
+static void sig_netsplit_servers(void)
+{
+ if (settings_get_bool("hide_netsplit_quits") && split_tag == -1) {
+ split_tag = g_timeout_add(1000,
+ (GSourceFunc) sig_check_splits,
+ NULL);
+ signal_add("print starting", (SIGNAL_FUNC) sig_print_starting);
+ }
+}
+
+static int split_equal(NETSPLIT_REC *n1, NETSPLIT_REC *n2)
+{
+ return g_ascii_strcasecmp(n1->nick, n2->nick);
+}
+
+static void split_get(void *key, NETSPLIT_REC *rec, GSList **list)
+{
+ *list = g_slist_insert_sorted(*list, rec,
+ (GCompareFunc) split_equal);
+}
+
+static void split_print(NETSPLIT_REC *rec, SERVER_REC *server)
+{
+ NETSPLIT_CHAN_REC *chan;
+ char *chanstr;
+
+ chan = rec->channels->data;
+ chanstr = chan == NULL ?
+ g_strdup("") :
+ g_strconcat(chan->op ? "@" : (chan->voice ? "+" : ""), chan->name, NULL);
+
+ printformat(server, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_NETSPLITS_LINE,
+ rec->nick, chanstr, rec->server->server,
+ rec->server->destserver);
+
+ g_free(chanstr);
+}
+
+/* SYNTAX: NETSPLIT */
+static void cmd_netsplit(const char *data, IRC_SERVER_REC *server)
+{
+ GSList *list;
+
+ CMD_IRC_SERVER(server);
+
+ if (server->split_servers == NULL) {
+ printformat(server, NULL, MSGLEVEL_CLIENTNOTICE,
+ IRCTXT_NO_NETSPLITS);
+ return;
+ }
+
+ printformat(server, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_NETSPLITS_HEADER);
+
+ list = NULL;
+ g_hash_table_foreach(server->splits, (GHFunc) split_get, &list);
+ g_slist_foreach(list, (GFunc) split_print, server);
+ g_slist_free(list);
+
+ printformat(server, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_NETSPLITS_FOOTER);
+}
+
+static void read_settings(void)
+{
+ netsplit_max_nicks = settings_get_int("netsplit_max_nicks");
+ netsplit_nicks_hide_threshold =
+ settings_get_int("netsplit_nicks_hide_threshold");
+ if (netsplit_nicks_hide_threshold < netsplit_max_nicks)
+ netsplit_max_nicks = netsplit_nicks_hide_threshold;
+}
+
+void fe_netsplit_init(void)
+{
+ settings_add_int("misc", "netsplit_max_nicks", 10);
+ settings_add_int("misc", "netsplit_nicks_hide_threshold", 15);
+ split_tag = -1;
+ printing_splits = FALSE;
+
+ read_settings();
+ signal_add("netsplit new", (SIGNAL_FUNC) sig_netsplit_servers);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+ command_bind_irc("netsplit", NULL, (SIGNAL_FUNC) cmd_netsplit);
+}
+
+void fe_netsplit_deinit(void)
+{
+ if (split_tag != -1) {
+ g_source_remove(split_tag);
+ signal_remove("print starting", (SIGNAL_FUNC) sig_print_starting);
+ }
+
+ signal_remove("netsplit new", (SIGNAL_FUNC) sig_netsplit_servers);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+ command_unbind("netsplit", (SIGNAL_FUNC) cmd_netsplit);
+}
diff --git a/src/fe-common/irc/fe-sasl.c b/src/fe-common/irc/fe-sasl.c
new file mode 100644
index 0000000..72fec58
--- /dev/null
+++ b/src/fe-common/irc/fe-sasl.c
@@ -0,0 +1,53 @@
+/*
+ fe-sasl.c : irssi
+
+ Copyright (C) 2015-2017 The Lemon Man
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/irc/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/irc/core/sasl.h>
+
+#include <irssi/src/irc/core/irc-servers.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/fe-common/core/printtext.h>
+
+static void sig_sasl_success(IRC_SERVER_REC *server)
+{
+ printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_SASL_SUCCESS);
+}
+
+static void sig_sasl_failure(IRC_SERVER_REC *server, const char *reason)
+{
+ printformat(server, NULL, MSGLEVEL_CRAP, IRCTXT_SASL_ERROR, reason);
+}
+
+void fe_sasl_init(void)
+{
+ signal_add("server sasl success", (SIGNAL_FUNC) sig_sasl_success);
+ signal_add("server sasl failure", (SIGNAL_FUNC) sig_sasl_failure);
+}
+
+void fe_sasl_deinit(void)
+{
+ signal_remove("server sasl success", (SIGNAL_FUNC) sig_sasl_success);
+ signal_remove("server sasl failure", (SIGNAL_FUNC) sig_sasl_failure);
+}
diff --git a/src/fe-common/irc/fe-whois.c b/src/fe-common/irc/fe-whois.c
new file mode 100644
index 0000000..b0eeb19
--- /dev/null
+++ b/src/fe-common/irc/fe-whois.c
@@ -0,0 +1,456 @@
+/* Copyright (C) 1999-2004 Timo Sirainen */
+
+#include "module.h"
+#include <irssi/src/fe-common/irc/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/recode.h>
+
+#include <irssi/src/irc/core/irc-servers.h>
+
+#include <irssi/src/fe-common/core/printtext.h>
+
+static void event_whois(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *nick, *user, *host, *realname, *recoded;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 6, NULL, &nick, &user,
+ &host, NULL, &realname);
+ recoded = recode_in(SERVER(server), realname, nick);
+ printformat(server, nick, MSGLEVEL_CRAP,
+ IRCTXT_WHOIS, nick, user, host, recoded);
+ g_free(params);
+ g_free(recoded);
+}
+
+static void event_whois_special(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *nick, *str;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 3 | PARAM_FLAG_GETREST, NULL, &nick, &str);
+ printformat(server, nick, MSGLEVEL_CRAP,
+ IRCTXT_WHOIS_SPECIAL, nick, str);
+ g_free(params);
+}
+
+static void event_whois_idle(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *nick, *secstr, *signonstr, *rest, *timestr;
+ long days, hours, mins, secs;
+ time_t signon;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 5 | PARAM_FLAG_GETREST, NULL,
+ &nick, &secstr, &signonstr, &rest);
+
+ secs = atol(secstr);
+ signon = strstr(rest, "signon time") == NULL ? 0 :
+ (time_t) atol(signonstr);
+
+ days = secs/3600/24;
+ hours = (secs%(3600*24))/3600;
+ mins = (secs%3600)/60;
+ secs %= 60;
+
+ if (signon == 0)
+ printformat(server, nick, MSGLEVEL_CRAP, IRCTXT_WHOIS_IDLE,
+ nick, days, hours, mins, secs);
+ else {
+ timestr = my_asctime(signon);
+ printformat(server, nick, MSGLEVEL_CRAP,
+ IRCTXT_WHOIS_IDLE_SIGNON,
+ nick, days, hours, mins, secs, timestr);
+ g_free(timestr);
+ }
+ g_free(params);
+}
+
+static void event_whois_server(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *nick, *whoserver, *desc;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 4, NULL, &nick, &whoserver, &desc);
+ printformat(server, nick, MSGLEVEL_CRAP,
+ IRCTXT_WHOIS_SERVER, nick, whoserver, desc);
+ g_free(params);
+}
+
+static void event_whois_oper(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *nick, *type;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 3, NULL, &nick, &type);
+
+ /* Bugfix: http://bugs.irssi.org/?do=details&task_id=99
+ * Author: Geert Hauwaerts <geert@irssi.org>
+ * Date: Wed Sep 15 20:17:24 CEST 2004
+ */
+
+ if ((!strncmp(type, "is an ", 6)) || (!strncmp(type, "is a ", 5))) {
+ type += 5;
+ if (*type == ' ') type++;
+ }
+
+ if (*type == '\0')
+ type = "IRC Operator";
+
+ printformat(server, nick, MSGLEVEL_CRAP,
+ IRCTXT_WHOIS_OPER, nick, type);
+ g_free(params);
+}
+
+static void event_whois_modes(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *nick, *modes;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 3 | PARAM_FLAG_GETREST,
+ NULL, &nick, &modes);
+ if (!strncmp(modes, "is using modes ", 15))
+ modes += 15;
+ printformat(server, nick, MSGLEVEL_CRAP,
+ IRCTXT_WHOIS_MODES, nick, modes);
+ g_free(params);
+}
+
+static void event_whois_realhost(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *nick, *txt_real, *txt_hostname, *hostname;
+
+ g_return_if_fail(data != NULL);
+
+ /* <yournick> real hostname <nick> <hostname> */
+ params = event_get_params(data, 5, NULL, &nick, &txt_real,
+ &txt_hostname, &hostname);
+ if (g_strcmp0(txt_real, "real") != 0 ||
+ g_strcmp0(txt_hostname, "hostname") != 0) {
+ /* <yournick> <nick> :... from <hostname> */
+ g_free(params);
+ params = event_get_params(data, 3, NULL, &nick, &hostname);
+
+ hostname = strstr(hostname, "from ");
+ if (hostname != NULL) hostname += 5;
+ }
+
+ if (hostname != NULL) {
+ if (!strncmp(hostname, "*@", 2))
+ hostname += 2;
+ printformat(server, nick, MSGLEVEL_CRAP,
+ IRCTXT_WHOIS_REALHOST, nick, hostname, "");
+ } else {
+ event_whois_special(server, data);
+ }
+ g_free(params);
+}
+
+static void event_whois_usermode326(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *nick, *usermode;
+
+ g_return_if_fail(data != NULL);
+
+ /* <yournick> <nick> :has oper privs: <mode> */
+ params = event_get_params(data, 3, NULL, &nick, &usermode);
+ printformat(server, nick, MSGLEVEL_CRAP,
+ IRCTXT_WHOIS_USERMODE, nick, usermode);
+ g_free(params);
+}
+
+static void event_whois_realhost327(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *nick, *hostname, *ip, *text;
+
+ g_return_if_fail(data != NULL);
+
+ /* <yournick> <hostname> <ip> :Real hostname/IP */
+ params = event_get_params(data, 5, NULL, &nick, &hostname, &ip, &text);
+ if (*text != '\0') {
+ printformat(server, nick, MSGLEVEL_CRAP,
+ IRCTXT_WHOIS_REALHOST, nick, hostname, ip);
+ } else {
+ event_whois_special(server, data);
+ }
+ g_free(params);
+}
+
+static void event_whois_realhost338(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *nick, *arg1, *arg2, *arg3;
+
+ g_return_if_fail(data != NULL);
+
+ /*
+ * :<server> 338 <yournick> <nick> <user>@<host> <ip> :Actual user@host, actual IP
+ * (ircu) or
+ * :<server> 338 <yournick> <nick> <ip> :actually using host
+ * (ratbox)
+ */
+ params = event_get_params(data, 5, NULL, &nick, &arg1, &arg2, &arg3);
+ if (*arg3 != '\0') {
+ printformat(server, nick, MSGLEVEL_CRAP,
+ IRCTXT_WHOIS_REALHOST, nick, arg1, arg2);
+ } else if (*arg2 != '\0') {
+ printformat(server, nick, MSGLEVEL_CRAP,
+ IRCTXT_WHOIS_REALHOST, nick, arg1, "");
+ } else {
+ event_whois_special(server, data);
+ }
+ g_free(params);
+}
+
+static void event_whois_usermode(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *txt_usermodes, *nick, *usermode;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 4, NULL, &txt_usermodes,
+ &nick, &usermode);
+
+ if (g_strcmp0(txt_usermodes, "usermodes") == 0) {
+ /* <yournick> usermodes <nick> usermode */
+ printformat(server, nick, MSGLEVEL_CRAP,
+ IRCTXT_WHOIS_USERMODE, nick, usermode);
+ } else {
+ event_whois_special(server, data);
+ }
+ g_free(params);
+}
+
+static void hide_safe_channel_id(IRC_SERVER_REC *server, char *chans)
+{
+ const char *idchan, *nick_flags;
+ char *p, *dest, *end, id;
+ int count, length, chanstart;
+
+ if (!server->isupport_sent)
+ idchan = "!:5";
+ else {
+ idchan = g_hash_table_lookup(server->isupport, "IDCHAN");
+ if (idchan == NULL)
+ return;
+ }
+ nick_flags = server->get_nick_flags(SERVER(server));
+
+ while (*idchan != '\0') {
+ id = *idchan;
+ if (idchan[1] != ':')
+ return;
+
+ length = strtoul(idchan+2, &end, 10);
+ if (*end == ',')
+ end++;
+ else if (*end != '\0')
+ return;
+ idchan = end;
+
+ count = 0;
+ chanstart = TRUE;
+ for (dest = p = chans; *p != '\0'; p++) {
+ if (count > 0)
+ count--;
+ else {
+ if (*p == ' ')
+ chanstart = TRUE;
+ else {
+ if (chanstart && *p == id)
+ count = length;
+ chanstart = chanstart && strchr(nick_flags, *p);
+ }
+ *dest++ = *p;
+ }
+ }
+ *dest = '\0';
+ }
+}
+
+static void event_whois_channels(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *nick, *chans, *recoded;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 3, NULL, &nick, &chans);
+
+ /* sure - we COULD print the channel names as-is, but since the
+ colors, bolds, etc. are mostly just to fool people, I think we
+ should show the channel names as they REALLY are so they could
+ even be joined without any extra tricks. */
+ chans = show_lowascii(chans);
+ if (settings_get_bool("whois_hide_safe_channel_id"))
+ hide_safe_channel_id(server, chans);
+ recoded = recode_in(SERVER(server), chans, nick);
+ printformat(server, nick, MSGLEVEL_CRAP,
+ IRCTXT_WHOIS_CHANNELS, nick, recoded);
+ g_free(chans);
+
+ g_free(params);
+ g_free(recoded);
+}
+
+static void event_whois_away(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *nick, *awaymsg, *recoded;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 3, NULL, &nick, &awaymsg);
+ recoded = recode_in(SERVER(server), awaymsg, nick);
+ printformat(server, nick, MSGLEVEL_CRAP,
+ IRCTXT_WHOIS_AWAY, nick, recoded);
+ g_free(params);
+ g_free(recoded);
+}
+
+static void event_end_of_whois(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *nick;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, NULL, &nick);
+ if (server->whois_found) {
+ printformat(server, nick, MSGLEVEL_CRAP,
+ IRCTXT_END_OF_WHOIS, nick);
+ }
+ g_free(params);
+}
+
+static void event_whois_auth(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *nick, *text;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 3, NULL, &nick, &text);
+ printformat(server, nick, MSGLEVEL_CRAP,
+ IRCTXT_WHOIS_EXTRA, nick, text);
+ g_free(params);
+}
+
+static void event_whowas(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *nick, *user, *host, *realname, *recoded;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 6, NULL, &nick, &user,
+ &host, NULL, &realname);
+ recoded = recode_in(SERVER(server), realname, nick);
+ printformat(server, nick, MSGLEVEL_CRAP,
+ IRCTXT_WHOWAS, nick, user, host, recoded);
+ g_free(params);
+ g_free(recoded);
+}
+
+static void event_end_of_whowas(IRC_SERVER_REC *server, const char *data)
+{
+ char *params, *nick;
+
+ g_return_if_fail(data != NULL);
+
+ params = event_get_params(data, 2, NULL, &nick);
+ if (server->whowas_found) {
+ printformat(server, nick, MSGLEVEL_CRAP,
+ IRCTXT_END_OF_WHOWAS, nick);
+ }
+ g_free(params);
+}
+
+struct whois_event_table {
+ int num;
+ void (*func)(IRC_SERVER_REC *, const char *);
+};
+
+static struct whois_event_table events[] = {
+ { 312, event_whois_server },
+ { 326, event_whois_usermode326 },
+ { 327, event_whois_realhost327 },
+ { 338, event_whois_realhost338 },
+ { 379, event_whois_modes },
+ { 378, event_whois_realhost },
+ { 377, event_whois_usermode },
+ { 317, event_whois_idle },
+ { 330, event_whois_auth },
+ { 319, event_whois_channels },
+ { 0, NULL }
+};
+
+static void event_whois_default(IRC_SERVER_REC *server, const char *data)
+{
+ int i, num;
+
+ num = atoi(current_server_event);
+ for (i = 0; events[i].num != 0; i++) {
+ if (events[i].num == num) {
+ events[i].func(server, data);
+ return;
+ }
+ }
+
+ event_whois_special(server, data);
+}
+
+void fe_whois_init(void)
+{
+ settings_add_bool("lookandfeel", "whois_hide_safe_channel_id", TRUE);
+
+ signal_add("event 311", (SIGNAL_FUNC) event_whois);
+ signal_add("event 312", (SIGNAL_FUNC) event_whois_server);
+ /* readding this events fixes the printing of /whois -yes *
+ Bug http://bugs.irssi.org/?do=details&task_id=123 */
+ signal_add("event 317", (SIGNAL_FUNC) event_whois_idle);
+ signal_add("event 319", (SIGNAL_FUNC) event_whois_channels);
+ signal_add("event 313", (SIGNAL_FUNC) event_whois_oper);
+ signal_add("event 330", (SIGNAL_FUNC) event_whois_auth);
+ signal_add("whois account", (SIGNAL_FUNC) event_whois_auth);
+ signal_add("event 377", (SIGNAL_FUNC) event_whois_usermode);
+ signal_add("event 378", (SIGNAL_FUNC) event_whois_realhost);
+ signal_add("event 379", (SIGNAL_FUNC) event_whois_modes);
+ signal_add("event 327", (SIGNAL_FUNC) event_whois_realhost327);
+ signal_add("event 326", (SIGNAL_FUNC) event_whois_usermode326);
+ signal_add("event 338", (SIGNAL_FUNC) event_whois_realhost338);
+ signal_add("whois away", (SIGNAL_FUNC) event_whois_away);
+ signal_add("whois oper", (SIGNAL_FUNC) event_whois_oper);
+ signal_add("whowas away", (SIGNAL_FUNC) event_whois_away);
+ signal_add("whois default event", (SIGNAL_FUNC) event_whois_default);
+ signal_add("event 318", (SIGNAL_FUNC) event_end_of_whois);
+ signal_add("event 314", (SIGNAL_FUNC) event_whowas);
+ signal_add("event 369", (SIGNAL_FUNC) event_end_of_whowas);
+}
+
+void fe_whois_deinit(void)
+{
+ signal_remove("event 311", (SIGNAL_FUNC) event_whois);
+ signal_remove("event 312", (SIGNAL_FUNC) event_whois_server);
+ signal_remove("event 317", (SIGNAL_FUNC) event_whois_idle);
+ signal_remove("event 319", (SIGNAL_FUNC) event_whois_channels);
+ signal_remove("event 313", (SIGNAL_FUNC) event_whois_oper);
+ signal_remove("event 330", (SIGNAL_FUNC) event_whois_auth);
+ signal_remove("whois account", (SIGNAL_FUNC) event_whois_auth);
+ signal_remove("event 377", (SIGNAL_FUNC) event_whois_usermode);
+ signal_remove("event 378", (SIGNAL_FUNC) event_whois_realhost);
+ signal_remove("event 379", (SIGNAL_FUNC) event_whois_modes);
+ signal_remove("event 327", (SIGNAL_FUNC) event_whois_realhost327);
+ signal_remove("event 326", (SIGNAL_FUNC) event_whois_usermode326);
+ signal_remove("event 338", (SIGNAL_FUNC) event_whois_realhost338);
+ signal_remove("whois away", (SIGNAL_FUNC) event_whois_away);
+ signal_remove("whois oper", (SIGNAL_FUNC) event_whois_oper);
+ signal_remove("whowas away", (SIGNAL_FUNC) event_whois_away);
+ signal_remove("whois default event", (SIGNAL_FUNC) event_whois_default);
+ signal_remove("event 318", (SIGNAL_FUNC) event_end_of_whois);
+ signal_remove("event 314", (SIGNAL_FUNC) event_whowas);
+ signal_remove("event 369", (SIGNAL_FUNC) event_end_of_whowas);
+}
diff --git a/src/fe-common/irc/irc-completion.c b/src/fe-common/irc/irc-completion.c
new file mode 100644
index 0000000..71e2e0c
--- /dev/null
+++ b/src/fe-common/irc/irc-completion.c
@@ -0,0 +1,41 @@
+/*
+ irc-completion.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/fe-common/core/chat-completion.h>
+
+static void sig_complete_stats(GList **list, WINDOW_REC *window,
+ const char *word, const char *line,
+ int *want_space)
+{
+ *list = completion_get_servers(word);
+ if (*list != NULL) signal_stop();
+}
+
+void irc_completion_init(void)
+{
+ signal_add("complete command stats", (SIGNAL_FUNC) sig_complete_stats);
+}
+
+void irc_completion_deinit(void)
+{
+ signal_remove("complete command stats", (SIGNAL_FUNC) sig_complete_stats);
+}
diff --git a/src/fe-common/irc/irc-modules.c b/src/fe-common/irc/irc-modules.c
new file mode 100644
index 0000000..3bf1f32
--- /dev/null
+++ b/src/fe-common/irc/irc-modules.c
@@ -0,0 +1,4 @@
+void fe_irc_dcc_init(void);void fe_irc_notifylist_init(void);
+void fe_irc_notifylist_deinit(void);void fe_irc_dcc_deinit(void);
+void fe_irc_modules_init(void) { fe_irc_dcc_init(); fe_irc_notifylist_init(); }
+void fe_irc_modules_deinit(void) { fe_irc_notifylist_deinit(); fe_irc_dcc_deinit(); }
diff --git a/src/fe-common/irc/meson.build b/src/fe-common/irc/meson.build
new file mode 100644
index 0000000..1789133
--- /dev/null
+++ b/src/fe-common/irc/meson.build
@@ -0,0 +1,44 @@
+# this file is part of irssi
+
+libfe_common_irc_a = static_library('fe_common_irc',
+ files(
+ 'fe-cap.c',
+ 'fe-common-irc.c',
+ 'fe-ctcp.c',
+ 'fe-events-numeric.c',
+ 'fe-events.c',
+ 'fe-irc-channels.c',
+ 'fe-irc-commands.c',
+ 'fe-irc-messages.c',
+ 'fe-irc-queries.c',
+ 'fe-irc-server.c',
+ 'fe-ircnet.c',
+ 'fe-modes.c',
+ 'fe-netjoin.c',
+ 'fe-netsplit.c',
+ 'fe-sasl.c',
+ 'fe-whois.c',
+ 'irc-completion.c',
+ 'module-formats.c',
+
+ 'irc-modules.c',
+ ),
+ include_directories : rootinc,
+ implicit_include_directories : false,
+ c_args : [
+ def_helpdir,
+ def_themesdir,
+ ],
+ dependencies : dep)
+
+install_headers(
+ files(
+ 'fe-irc-channels.h',
+ 'fe-irc-server.h',
+ 'module-formats.h',
+ 'module.h',
+ ),
+ subdir : incdir / 'src' / 'fe-common' / 'irc')
+
+subdir('dcc')
+subdir('notifylist')
diff --git a/src/fe-common/irc/module-formats.c b/src/fe-common/irc/module-formats.c
new file mode 100644
index 0000000..0adb268
--- /dev/null
+++ b/src/fe-common/irc/module-formats.c
@@ -0,0 +1,184 @@
+/*
+ module-formats.c : irssi
+
+ Copyright (C) 2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/core/formats.h>
+
+/* clang-format off */
+FORMAT_REC fecommon_irc_formats[] = {
+ { MODULE_NAME, "IRC", 0 },
+
+ /* ---- */
+ { NULL, "Server", 0 },
+
+ { "netsplit", "{netsplit Netsplit} {server $0} <-> {server $1} quits: $2", 3, { 0, 0, 0 } },
+ { "netsplit_more", "{netsplit Netsplit} {server $0} <-> {server $1} quits: $2 (+$3 more, use /NETSPLIT to show all of them)", 4, { 0, 0, 0, 1 } },
+ { "netsplit_join", "{netjoin Netsplit} over, joins: $0", 1, { 0 } },
+ { "netsplit_join_more", "{netjoin Netsplit} over, joins: $0 (+$1 more)", 2, { 0, 1 } },
+ { "no_netsplits", "There are no net splits", 0 },
+ { "netsplits_header", "%#Nick Channel Server Split server", 0 },
+ { "netsplits_line", "%#$[9]0 $[10]1 $[20]2 $3", 4, { 0, 0, 0, 0 } },
+ { "netsplits_footer", "", 0 },
+ { "network_added", "Network $0 saved", 1, { 0 } },
+ { "network_removed", "Network $0 removed", 1, { 0 } },
+ { "network_not_found", "Network $0 not found", 1, { 0 } },
+ { "network_header", "%#Networks:", 0 },
+ { "network_line", "%#$0: $1", 2, { 0, 0 } },
+ { "network_footer", "", 0 },
+ { "setupserver_header", "%#Server Port Network Settings", 0 },
+ { "setupserver_line", "%#%|$[!20]0 $[5]1 $[10]2 $3", 4, { 0, 1, 0, 0 } },
+ { "setupserver_footer", "", 0 },
+ { "server_waiting_cap_ls", "Waiting for CAP LS response...", 2, { 0, 0 } },
+ { "sasl_success", "SASL authentication succeeded", 0 },
+ { "sasl_error", "Cannot authenticate via SASL ($0)", 1, { 0 } },
+ { "cap_req", "Capabilities requested: $0", 1, { 0 } },
+ { "cap_ls", "Capabilities supported: $0", 1, { 0 } },
+ { "cap_ack", "Capabilities acknowledged: $0", 1, { 0 } },
+ { "cap_nak", "Capabilities refused: $0", 1, { 0 } },
+ { "cap_list", "Capabilities currently enabled: $0", 1, { 0 } },
+ { "cap_new", "Capabilities now available: $0", 1, { 0 } },
+ { "cap_del", "Capabilities removed: $0", 1, { 0 } },
+
+ /* ---- */
+ { NULL, "Channels", 0 },
+
+ { "joinerror_toomany", "Cannot join to channel {channel $0} (You have joined to too many channels)", 1, { 0 } },
+ { "joinerror_full", "Cannot join to channel {channel $0} (Channel is full)", 1, { 0 } },
+ { "joinerror_invite", "Cannot join to channel {channel $0} (You must be invited)", 1, { 0 } },
+ { "joinerror_banned", "Cannot join to channel {channel $0} (You are banned)", 1, { 0 } },
+ { "joinerror_bad_key", "Cannot join to channel {channel $0} (Bad channel key)", 1, { 0 } },
+ { "joinerror_bad_mask", "Cannot join to channel {channel $0} (Bad channel mask)", 1, { 0 } },
+ { "joinerror_secure_only", "Cannot join to channel {channel $0} (Secure clients only)", 1, { 0 } },
+ { "joinerror_unavail", "Cannot join to channel {channel $0} (Channel is temporarily unavailable)", 1, { 0 } },
+ { "joinerror_duplicate", "Channel {channel $0} already exists - cannot create it", 1, { 0 } },
+ { "channel_rejoin", "Channel {channel $0} is temporarily unavailable, this is normally because of netsplits. Irssi will now automatically try to rejoin back to this channel until the join is successful. Use /RMREJOINS command if you wish to abort this.", 1, { 0 } },
+ { "inviting", "Inviting {nick $0} to {channel $1}", 2, { 0, 0 } },
+ { "channel_created", "Channel {channelhilight $0} created $1", 2, { 0, 0 } },
+ { "url", "Home page for {channelhilight $0}: $1", 2, { 0, 0 } },
+ { "topic", "Topic for {channelhilight $0}: $1", 2, { 0, 0 } },
+ { "no_topic", "No topic set for {channelhilight $0}", 1, { 0 } },
+ { "topic_info", "Topic set by {nick $0} {nickhost $2} {comment $1}", 3, { 0, 0, 0 } },
+ { "chanmode_change", "mode/{channelhilight $0} {mode $1} by {nick $2}", 4, { 0, 0, 0, 0 } },
+ { "server_chanmode_change", "{netsplit ServerMode}/{channelhilight $0} {mode $1} by {nick $2}", 3, { 0, 0, 0 } },
+ { "channel_mode", "mode/{channelhilight $0} {mode $1}", 2, { 0, 0 } },
+ { "bantype", "Ban type changed to {channel $0}", 1, { 0 } },
+ { "no_bans", "No bans in channel {channel $0}", 1, { 0 } },
+ { "banlist", "$0 - {channel $1}: ban {ban $2}", 3, { 1, 0, 0 } },
+ { "banlist_long", "$0 - {channel $1}: ban {ban $2} {comment by {nick $3}, $4 secs ago}", 5, { 1, 0, 0, 0, 1 } },
+ { "ebanlist", "{channel $0}: ban exception {ban $1}", 2, { 0, 0 } },
+ { "ebanlist_long", "{channel $0}: ban exception {ban $1} {comment by {nick $2}, $3 secs ago}", 4, { 0, 0, 0, 1 } },
+ { "no_invitelist", "Invite list is empty in channel {channel $0}", 1, { 0 } },
+ { "invitelist", "{channel $0}: invite {ban $1}", 2, { 0, 0 } },
+ { "invitelist_long", "{channel $0}: invite {ban $1} {comment by {nick $2}, $3 secs ago}", 4, { 0, 0, 0, 1 } },
+ { "no_such_channel", "{channel $0}: No such channel", 1, { 0 } },
+ { "channel_synced", "Join to {channel $0} was synced in {hilight $1} secs", 2, { 0, 2 } },
+ { "server_help_start", "$1", 2, { 0, 0 } },
+ { "server_help_txt", "$1", 2, { 0, 0 } },
+ { "server_end_of_help", "$1", 2, { 0, 0 } },
+
+ /* ---- */
+ { NULL, "Nick", 0 },
+
+ { "usermode_change", "Mode change {mode $0} for user {nick $1}", 2, { 0, 0 } },
+ { "user_mode", "Your user mode is {mode $0}", 1, { 0 } },
+ { "away", "You have been marked as being away", 0 },
+ { "unaway", "You are no longer marked as being away", 0 },
+ { "nick_away", "{nick $0} is away: $1", 2, { 0, 0 } },
+ { "no_such_nick", "{nick $0}: No such nick/channel", 1, { 0 } },
+ { "nick_in_use", "Nick {nick $0} is already in use", 1, { 0 } },
+ { "nick_unavailable", "Nick {nick $0} is temporarily unavailable", 1, { 0 } },
+ { "your_nick_owned", "Your nick is in use by {nick $3} {comment $1@$2}", 4, { 0, 0, 0, 0 } },
+
+ /* ---- */
+ { NULL, "Who queries", 0 },
+
+ { "whois", "{nick $0} {nickhost $1@$2}%:{whois ircname $3}", 4, { 0, 0, 0, 0 } },
+ { "whowas", "{nick $0} {nickhost $1@$2}%:{whois was $3}", 4, { 0, 0, 0, 0 } },
+ { "whois_idle", "{whois idle %|$1 days $2 hours $3 mins $4 secs}", 5, { 0, 1, 1, 1, 1 } },
+ { "whois_idle_signon", "{whois idle %|$1 days $2 hours $3 mins $4 secs {comment signon: $5}}", 6, { 0, 1, 1, 1, 1, 0 } },
+ { "whois_server", "{whois server %|$1 {comment $2}}", 3, { 0, 0, 0 } },
+ { "whois_oper", "{whois {hilight $1}}", 2, { 0, 0 } },
+ { "whois_modes", "{whois modes $1}", 2, { 0, 0 } },
+ { "whois_realhost", "{whois hostname $1-}", 3, { 0, 0, 0 } },
+ { "whois_usermode", "{whois usermode $1}", 2, { 0, 0 } },
+ { "whois_channels", "{whois channels %|$1}", 2, { 0, 0 } },
+ { "whois_away", "{whois away %|$1}", 2, { 0, 0 } },
+ { "whois_special", "{whois %|$1}", 2, { 0, 0 } },
+ { "whois_extra", "{whois account %|$1}", 2, { 0, 0 } },
+ { "end_of_whois", "End of WHOIS", 1, { 0 } },
+ { "end_of_whowas", "End of WHOWAS", 1, { 0 } },
+ { "whois_not_found", "There is no such nick $0", 1, { 0 } },
+ { "who", "%#{channelhilight $[-10]0} %|{nick $[!9]1} $[!3]2 $[!2]3 $4@$5 {comment {hilight $6}}", 8, { 0, 0, 0, 0, 0, 0, 0, 0 } },
+ { "end_of_who", "End of /WHO list", 1, { 0 } },
+
+ /* ---- */
+ { NULL, "Your messages", 0 },
+
+ { "own_notice", "{ownnotice notice $0}$1", 2, { 0, 0 } },
+ { "own_action", "{ownaction $0}$1", 3, { 0, 0, 0 } },
+ { "own_action_target", "{ownaction_target $0 $2}$1", 3, { 0, 0, 0 } },
+ { "own_ctcp", "{ownctcp ctcp $0}$1 $2", 3, { 0, 0, 0 } },
+
+ /* ---- */
+ { NULL, "Received messages", 0 },
+
+ { "notice_server", "{servernotice $0}$1", 2, { 0, 0 } },
+ { "notice_public", "{notice $0{pubnotice_channel $1}}$2", 3, { 0, 0, 0 } },
+ { "notice_private", "{notice $0{pvtnotice_host $1}}$2", 3, { 0, 0, 0 } },
+ { "action_private", "{pvtaction $0}$2", 3, { 0, 0, 0 } },
+ { "action_private_query", "{pvtaction_query $0}$2", 3, { 0, 0, 0 } },
+ { "action_public", "{pubaction $0}$1", 2, { 0, 0 } },
+ { "action_public_channel", "{pubaction $0{msgchannel $1}}$2", 3, { 0, 0, 0 } },
+
+ /* ---- */
+ { NULL, "CTCPs", 0 },
+
+ { "ctcp_reply", "CTCP {hilight $0} reply from {nick $1}: $2", 3, { 0, 0, 0 } },
+ { "ctcp_reply_channel", "CTCP {hilight $0} reply from {nick $1} in channel {channel $3}: $2", 4, { 0, 0, 0, 0 } },
+ { "ctcp_ping_reply", "CTCP {hilight PING} reply from {nick $0}: $1.$[-3.0]2 seconds", 3, { 0, 2, 2 } },
+ { "ctcp_requested", "{ctcp {hilight $0} {comment $1} requested CTCP {hilight $2} from {nick $4}}: $3", 5, { 0, 0, 0, 0, 0 } },
+ { "ctcp_requested_unknown", "{ctcp {hilight $0} {comment $1} requested unknown CTCP {hilight $2} from {nick $4}}: $3", 5, { 0, 0, 0, 0, 0 } },
+
+ /* ---- */
+ { NULL, "Other server events", 0 },
+
+ { "online", "Users online: {hilight $0}", 1, { 0 } },
+ { "pong", "PONG received from $0: $1", 2, { 0, 0 } },
+ { "wallops", "{wallop WALLOP {wallop_nick $0}} $1", 2, { 0, 0 } },
+ { "action_wallops", "{wallop WALLOP {wallop_action $0}} $1", 2, { 0, 0 } },
+ { "kill", "You were {error killed} by {nick $0} {nickhost $1} {reason $2} {comment Path: $3}", 4, { 0, 0, 0, 0 } },
+ { "kill_server", "You were {error killed} by {server $0} {reason $1} {comment Path: $2}", 3, { 0, 0, 0 } },
+ { "error", "{error ERROR} $0", 1, { 0 } },
+ { "unknown_mode", "Unknown mode character $0", 1, { 0 } },
+ { "default_event", "$1", 3, { 0, 0, 0 } },
+ { "default_event_server", "[$0] $1", 3, { 0, 0, 0 } },
+
+ /* ---- */
+ { NULL, "Misc", 0 },
+
+ { "silenced", "Silenced {nick $0}", 1, { 0 } },
+ { "unsilenced", "Unsilenced {nick $0}", 1, { 0 } },
+ { "silence_line", "{nick $0}: silence {ban $1}", 2, { 0, 0 } },
+ { "ask_oper_pass", "Operator password:", 0 },
+ { "accept_list", "Accepted users: {hilight $0}", 1, { 0 } },
+
+ { NULL, NULL, 0 }
+};
+/* clang-format on */
diff --git a/src/fe-common/irc/module-formats.h b/src/fe-common/irc/module-formats.h
new file mode 100644
index 0000000..a9d29cb
--- /dev/null
+++ b/src/fe-common/irc/module-formats.h
@@ -0,0 +1,154 @@
+#include <irssi/src/fe-common/core/formats.h>
+
+/* clang-format off */
+enum {
+ IRCTXT_MODULE_NAME,
+
+ IRCTXT_FILL_1,
+
+ IRCTXT_NETSPLIT,
+ IRCTXT_NETSPLIT_MORE,
+ IRCTXT_NETSPLIT_JOIN,
+ IRCTXT_NETSPLIT_JOIN_MORE,
+ IRCTXT_NO_NETSPLITS,
+ IRCTXT_NETSPLITS_HEADER,
+ IRCTXT_NETSPLITS_LINE,
+ IRCTXT_NETSPLITS_FOOTER,
+ IRCTXT_NETWORK_ADDED,
+ IRCTXT_NETWORK_REMOVED,
+ IRCTXT_NETWORK_NOT_FOUND,
+ IRCTXT_NETWORK_HEADER,
+ IRCTXT_NETWORK_LINE,
+ IRCTXT_NETWORK_FOOTER,
+ IRCTXT_SETUPSERVER_HEADER,
+ IRCTXT_SETUPSERVER_LINE,
+ IRCTXT_SETUPSERVER_FOOTER,
+ IRCTXT_SERVER_WAITING_CAP_LS,
+ IRCTXT_SASL_SUCCESS,
+ IRCTXT_SASL_ERROR,
+ IRCTXT_CAP_REQ,
+ IRCTXT_CAP_LS,
+ IRCTXT_CAP_ACK,
+ IRCTXT_CAP_NAK,
+ IRCTXT_CAP_LIST,
+ IRCTXT_CAP_NEW,
+ IRCTXT_CAP_DEL,
+
+ IRCTXT_FILL_2,
+
+ IRCTXT_JOINERROR_TOOMANY,
+ IRCTXT_JOINERROR_FULL,
+ IRCTXT_JOINERROR_INVITE,
+ IRCTXT_JOINERROR_BANNED,
+ IRCTXT_JOINERROR_BAD_KEY,
+ IRCTXT_JOINERROR_BAD_MASK,
+ IRCTXT_JOINERROR_SECURE_ONLY,
+ IRCTXT_JOINERROR_UNAVAIL,
+ IRCTXT_JOINERROR_DUPLICATE,
+ IRCTXT_CHANNEL_REJOIN,
+ IRCTXT_INVITING,
+ IRCTXT_CHANNEL_CREATED,
+ IRCTXT_CHANNEL_URL,
+ IRCTXT_TOPIC,
+ IRCTXT_NO_TOPIC,
+ IRCTXT_TOPIC_INFO,
+ IRCTXT_CHANMODE_CHANGE,
+ IRCTXT_SERVER_CHANMODE_CHANGE,
+ IRCTXT_CHANNEL_MODE,
+ IRCTXT_BANTYPE,
+ IRCTXT_NO_BANS,
+ IRCTXT_BANLIST,
+ IRCTXT_BANLIST_LONG,
+ IRCTXT_EBANLIST,
+ IRCTXT_EBANLIST_LONG,
+ IRCTXT_NO_INVITELIST,
+ IRCTXT_INVITELIST,
+ IRCTXT_INVITELIST_LONG,
+ IRCTXT_NO_SUCH_CHANNEL,
+ IRCTXT_CHANNEL_SYNCED,
+ IRCTXT_SERVER_HELP_START,
+ IRCTXT_SERVER_HELP_TXT,
+ IRCTXT_SERVER_END_OF_HELP,
+
+ IRCTXT_FILL_4,
+
+ IRCTXT_USERMODE_CHANGE,
+ IRCTXT_USER_MODE,
+ IRCTXT_AWAY,
+ IRCTXT_UNAWAY,
+ IRCTXT_NICK_AWAY,
+ IRCTXT_NO_SUCH_NICK,
+ IRCTXT_NICK_IN_USE,
+ IRCTXT_NICK_UNAVAILABLE,
+ IRCTXT_YOUR_NICK_OWNED,
+
+ IRCTXT_FILL_5,
+
+ IRCTXT_WHOIS,
+ IRCTXT_WHOWAS,
+ IRCTXT_WHOIS_IDLE,
+ IRCTXT_WHOIS_IDLE_SIGNON,
+ IRCTXT_WHOIS_SERVER,
+ IRCTXT_WHOIS_OPER,
+ IRCTXT_WHOIS_MODES,
+ IRCTXT_WHOIS_REALHOST,
+ IRCTXT_WHOIS_USERMODE,
+ IRCTXT_WHOIS_CHANNELS,
+ IRCTXT_WHOIS_AWAY,
+ IRCTXT_WHOIS_SPECIAL,
+ IRCTXT_WHOIS_EXTRA,
+ IRCTXT_END_OF_WHOIS,
+ IRCTXT_END_OF_WHOWAS,
+ IRCTXT_WHOIS_NOT_FOUND,
+ IRCTXT_WHO,
+ IRCTXT_END_OF_WHO,
+
+ IRCTXT_FILL_6,
+
+ IRCTXT_OWN_NOTICE,
+ IRCTXT_OWN_ACTION,
+ IRCTXT_OWN_ACTION_TARGET,
+ IRCTXT_OWN_CTCP,
+
+ IRCTXT_FILL_7,
+
+ IRCTXT_NOTICE_SERVER,
+ IRCTXT_NOTICE_PUBLIC,
+ IRCTXT_NOTICE_PRIVATE,
+ IRCTXT_ACTION_PRIVATE,
+ IRCTXT_ACTION_PRIVATE_QUERY,
+ IRCTXT_ACTION_PUBLIC,
+ IRCTXT_ACTION_PUBLIC_CHANNEL,
+
+ IRCTXT_FILL_8,
+
+ IRCTXT_CTCP_REPLY,
+ IRCTXT_CTCP_REPLY_CHANNEL,
+ IRCTXT_CTCP_PING_REPLY,
+ IRCTXT_CTCP_REQUESTED,
+ IRCTXT_CTCP_REQUESTED_UNKNOWN,
+
+ IRCTXT_FILL_10,
+
+ IRCTXT_ONLINE,
+ IRCTXT_PONG,
+ IRCTXT_WALLOPS,
+ IRCTXT_ACTION_WALLOPS,
+ IRCTXT_KILL,
+ IRCTXT_KILL_SERVER,
+ IRCTXT_ERROR,
+ IRCTXT_UNKNOWN_MODE,
+ IRCTXT_DEFAULT_EVENT,
+ IRCTXT_DEFAULT_EVENT_SERVER,
+
+ IRCTXT_FILL_11,
+
+ IRCTXT_SILENCED,
+ IRCTXT_UNSILENCED,
+ IRCTXT_SILENCE_LINE,
+ IRCTXT_ASK_OPER_PASS,
+ IRCTXT_ACCEPT_LIST
+};
+/* clang-format on */
+
+extern FORMAT_REC fecommon_irc_formats[];
diff --git a/src/fe-common/irc/module.h b/src/fe-common/irc/module.h
new file mode 100644
index 0000000..ad63c95
--- /dev/null
+++ b/src/fe-common/irc/module.h
@@ -0,0 +1,4 @@
+#include <irssi/src/common.h>
+#include <irssi/src/irc/core/irc.h>
+
+#define MODULE_NAME "fe-common/irc"
diff --git a/src/fe-common/irc/notifylist/Makefile.am b/src/fe-common/irc/notifylist/Makefile.am
new file mode 100644
index 0000000..5eecb5f
--- /dev/null
+++ b/src/fe-common/irc/notifylist/Makefile.am
@@ -0,0 +1,18 @@
+noinst_LIBRARIES = libfe_irc_notifylist.a
+
+AM_CPPFLAGS = \
+ -I$(top_builddir) \
+ $(GLIB_CFLAGS) \
+ -DHELPDIR=\""$(datadir)/irssi/help"\" \
+ -DSYSCONFDIR=\""$(sysconfdir)"\"
+
+libfe_irc_notifylist_a_SOURCES = \
+ fe-notifylist.c \
+ module-formats.c
+
+pkginc_fe_common_irc_notifylistdir=$(pkgincludedir)/src/fe-common/irc/notifylist
+pkginc_fe_common_irc_notifylist_HEADERS = \
+ module.h \
+ module-formats.h
+
+EXTRA_DIST = meson.build
diff --git a/src/fe-common/irc/notifylist/Makefile.in b/src/fe-common/irc/notifylist/Makefile.in
new file mode 100644
index 0000000..733e022
--- /dev/null
+++ b/src/fe-common/irc/notifylist/Makefile.in
@@ -0,0 +1,725 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/fe-common/irc/notifylist
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/glib-2.0.m4 \
+ $(top_srcdir)/m4/glibtests.m4 $(top_srcdir)/m4/libgcrypt.m4 \
+ $(top_srcdir)/m4/libotr.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
+ $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
+ $(top_srcdir)/m4/pkg.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am \
+ $(pkginc_fe_common_irc_notifylist_HEADERS) $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/irssi-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+ARFLAGS = cru
+AM_V_AR = $(am__v_AR_@AM_V@)
+am__v_AR_ = $(am__v_AR_@AM_DEFAULT_V@)
+am__v_AR_0 = @echo " AR " $@;
+am__v_AR_1 =
+libfe_irc_notifylist_a_AR = $(AR) $(ARFLAGS)
+libfe_irc_notifylist_a_LIBADD =
+am_libfe_irc_notifylist_a_OBJECTS = fe-notifylist.$(OBJEXT) \
+ module-formats.$(OBJEXT)
+libfe_irc_notifylist_a_OBJECTS = $(am_libfe_irc_notifylist_a_OBJECTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES =
+depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/fe-notifylist.Po \
+ ./$(DEPDIR)/module-formats.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libfe_irc_notifylist_a_SOURCES)
+DIST_SOURCES = $(libfe_irc_notifylist_a_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(pkginc_fe_common_irc_notifylistdir)"
+HEADERS = $(pkginc_fe_common_irc_notifylist_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in \
+ $(top_srcdir)/build-aux/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHAT_MODULES = @CHAT_MODULES@
+COMMON_LIBS = @COMMON_LIBS@
+COMMON_NOUI_LIBS = @COMMON_NOUI_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+FUZZER_LIBS = @FUZZER_LIBS@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_GENMARSHAL = @GLIB_GENMARSHAL@
+GLIB_LIBS = @GLIB_LIBS@
+GLIB_MKENUMS = @GLIB_MKENUMS@
+GOBJECT_QUERY = @GOBJECT_QUERY@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
+LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
+LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBOTR_CFLAGS = @LIBOTR_CFLAGS@
+LIBOTR_LIBS = @LIBOTR_LIBS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENSSL_CFLAGS = @OPENSSL_CFLAGS@
+OPENSSL_LIBS = @OPENSSL_LIBS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+OTR_CFLAGS = @OTR_CFLAGS@
+OTR_LDFLAGS = @OTR_LDFLAGS@
+OTR_LINK_FLAGS = @OTR_LINK_FLAGS@
+OTR_LINK_LIBS = @OTR_LINK_LIBS@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PERL_CFLAGS = @PERL_CFLAGS@
+PERL_EXTRA_OPTS = @PERL_EXTRA_OPTS@
+PERL_FE_LINK_LIBS = @PERL_FE_LINK_LIBS@
+PERL_LDFLAGS = @PERL_LDFLAGS@
+PERL_LINK_FLAGS = @PERL_LINK_FLAGS@
+PERL_LINK_LIBS = @PERL_LINK_LIBS@
+PERL_MM_OPT = @PERL_MM_OPT@
+PERL_MM_PARAMS = @PERL_MM_PARAMS@
+PERL_STATIC_LIBS = @PERL_STATIC_LIBS@
+PERL_USE_LIB = @PERL_USE_LIB@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PROG_LIBS = @PROG_LIBS@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+TEXTUI_LIBS = @TEXTUI_LIBS@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+installed_test_metadir = @installed_test_metadir@
+installed_testdir = @installed_testdir@
+irc_MODULES = @irc_MODULES@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+otr_module_lib = @otr_module_lib@
+otr_static_lib = @otr_static_lib@
+pdfdir = @pdfdir@
+perl_module_fe_lib = @perl_module_fe_lib@
+perl_module_lib = @perl_module_lib@
+perl_static_fe_lib = @perl_static_fe_lib@
+perl_static_lib = @perl_static_lib@
+perlpath = @perlpath@
+pkgconfigdir = @pkgconfigdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sedpath = @sedpath@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LIBRARIES = libfe_irc_notifylist.a
+AM_CPPFLAGS = \
+ -I$(top_builddir) \
+ $(GLIB_CFLAGS) \
+ -DHELPDIR=\""$(datadir)/irssi/help"\" \
+ -DSYSCONFDIR=\""$(sysconfdir)"\"
+
+libfe_irc_notifylist_a_SOURCES = \
+ fe-notifylist.c \
+ module-formats.c
+
+pkginc_fe_common_irc_notifylistdir = $(pkgincludedir)/src/fe-common/irc/notifylist
+pkginc_fe_common_irc_notifylist_HEADERS = \
+ module.h \
+ module-formats.h
+
+EXTRA_DIST = meson.build
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/fe-common/irc/notifylist/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/fe-common/irc/notifylist/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+ -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+
+libfe_irc_notifylist.a: $(libfe_irc_notifylist_a_OBJECTS) $(libfe_irc_notifylist_a_DEPENDENCIES) $(EXTRA_libfe_irc_notifylist_a_DEPENDENCIES)
+ $(AM_V_at)-rm -f libfe_irc_notifylist.a
+ $(AM_V_AR)$(libfe_irc_notifylist_a_AR) libfe_irc_notifylist.a $(libfe_irc_notifylist_a_OBJECTS) $(libfe_irc_notifylist_a_LIBADD)
+ $(AM_V_at)$(RANLIB) libfe_irc_notifylist.a
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fe-notifylist.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/module-formats.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_fe_common_irc_notifylistHEADERS: $(pkginc_fe_common_irc_notifylist_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_fe_common_irc_notifylist_HEADERS)'; test -n "$(pkginc_fe_common_irc_notifylistdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_fe_common_irc_notifylistdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_fe_common_irc_notifylistdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_fe_common_irc_notifylistdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_fe_common_irc_notifylistdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_fe_common_irc_notifylistHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_fe_common_irc_notifylist_HEADERS)'; test -n "$(pkginc_fe_common_irc_notifylistdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_fe_common_irc_notifylistdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(pkginc_fe_common_irc_notifylistdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/fe-notifylist.Po
+ -rm -f ./$(DEPDIR)/module-formats.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-pkginc_fe_common_irc_notifylistHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/fe-notifylist.Po
+ -rm -f ./$(DEPDIR)/module-formats.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-pkginc_fe_common_irc_notifylistHEADERS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-pkginc_fe_common_irc_notifylistHEADERS \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-pkginc_fe_common_irc_notifylistHEADERS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/fe-common/irc/notifylist/fe-notifylist.c b/src/fe-common/irc/notifylist/fe-notifylist.c
new file mode 100644
index 0000000..68b98a0
--- /dev/null
+++ b/src/fe-common/irc/notifylist/fe-notifylist.c
@@ -0,0 +1,250 @@
+/*
+ fe-notifylist.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/irc/notifylist/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/chatnets.h>
+#include <irssi/src/lib-config/iconfig.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/core/levels.h>
+#include <irssi/src/irc/core/irc-servers.h>
+#include <irssi/src/irc/core/irc-chatnets.h>
+#include <irssi/src/irc/notifylist/notifylist.h>
+
+#include <irssi/src/fe-common/core/themes.h>
+#include <irssi/src/fe-common/core/printtext.h>
+
+/* add the nick of a hostmask to list if it isn't there already */
+static GSList *mask_add_once(GSList *list, const char *mask)
+{
+ char *str, *ptr;
+
+ g_return_val_if_fail(mask != NULL, NULL);
+
+ ptr = strchr(mask, '!');
+ str = ptr == NULL ? g_strdup(mask) :
+ g_strndup(mask, (int) (ptr-mask));
+
+ if (i_slist_find_icase_string(list, str) == NULL)
+ return g_slist_append(list, str);
+
+ g_free(str);
+ return list;
+}
+
+/* search for online people, print them and update offline list */
+static void print_notify_onserver(IRC_SERVER_REC *server, GSList *nicks,
+ GSList **offline, const char *desc)
+{
+ GSList *tmp;
+ GString *str;
+
+ g_return_if_fail(IS_IRC_SERVER(server));
+ g_return_if_fail(offline != NULL);
+ g_return_if_fail(desc != NULL);
+
+ str = g_string_new(NULL);
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
+ char *nick = tmp->data;
+
+ if (!notifylist_ison_server(server, nick))
+ continue;
+
+ g_string_append_printf(str, "%s, ", nick);
+ *offline = g_slist_remove(*offline, nick);
+ }
+
+ if (str->len > 0) {
+ g_string_truncate(str, str->len-2);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_NOTIFY_ONLINE, desc, str->str);
+ }
+
+ g_string_free(str, TRUE);
+}
+
+/* show the notify list, displaying who is on which net */
+static void cmd_notify_show(void)
+{
+ GSList *nicks, *offline, *tmp;
+ IRC_SERVER_REC *server;
+
+ if (notifies == NULL) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ IRCTXT_NOTIFY_LIST_EMPTY);
+ return;
+ }
+
+ /* build a list containing only the nicks */
+ nicks = NULL;
+ for (tmp = notifies; tmp != NULL; tmp = tmp->next) {
+ NOTIFYLIST_REC *rec = tmp->data;
+
+ nicks = mask_add_once(nicks, rec->mask);
+ }
+ offline = g_slist_copy(nicks);
+
+ /* print the notifies on specific ircnets */
+ for (tmp = chatnets; tmp != NULL; tmp = tmp->next) {
+ IRC_CHATNET_REC *rec = tmp->data;
+
+ if (!IS_IRCNET(rec))
+ continue;
+
+ server = (IRC_SERVER_REC *) server_find_chatnet(rec->name);
+ if (!IS_IRC_SERVER(server))
+ continue;
+
+ print_notify_onserver(server, nicks, &offline, rec->name);
+ }
+
+ /* print the notifies on servers without a specified ircnet */
+ for (tmp = servers; tmp != NULL; tmp = tmp->next) {
+ server = tmp->data;
+
+ if (!IS_IRC_SERVER(server) || server->connrec->chatnet != NULL)
+ continue;
+ print_notify_onserver(server, nicks, &offline, server->tag);
+ }
+
+ /* print offline people */
+ if (offline != NULL) {
+ GString *str;
+
+ str = g_string_new(NULL);
+ for (tmp = offline; tmp != NULL; tmp = tmp->next)
+ g_string_append_printf(str, "%s, ", (char *) tmp->data);
+
+ g_string_truncate(str, str->len-2);
+ printformat(NULL,NULL, MSGLEVEL_CLIENTNOTICE, IRCTXT_NOTIFY_OFFLINE, str->str);
+ g_string_free(str, TRUE);
+
+ g_slist_free(offline);
+ }
+
+ g_slist_foreach(nicks, (GFunc) g_free, NULL);
+ g_slist_free(nicks);
+}
+
+static void notifylist_print(NOTIFYLIST_REC *rec)
+{
+ char *ircnets;
+
+ ircnets = rec->ircnets == NULL ? NULL :
+ g_strjoinv(",", rec->ircnets);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_NOTIFY_LIST,
+ rec->mask, ircnets != NULL ? ircnets : "",
+ rec->away_check ? "-away" : "");
+
+ g_free_not_null(ircnets);
+}
+
+static void cmd_notifylist_show(void)
+{
+ if (notifies == NULL) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ IRCTXT_NOTIFY_LIST_EMPTY);
+ } else {
+ g_slist_foreach(notifies, (GFunc) notifylist_print, NULL);
+ }
+}
+
+static void cmd_notify(const char *data)
+{
+ if (*data == '\0') {
+ cmd_notify_show();
+ signal_stop();
+ }
+
+ if (g_ascii_strncasecmp(data, "-list", 4) == 0) {
+ cmd_notifylist_show();
+ signal_stop();
+ }
+}
+
+static void notifylist_joined(IRC_SERVER_REC *server, const char *nick,
+ const char *username, const char *host,
+ const char *realname, const char *awaymsg)
+{
+ g_return_if_fail(nick != NULL);
+
+ printformat(server, nick, MSGLEVEL_CLIENTNOTICE,
+ IRCTXT_NOTIFY_JOIN, nick, username, host, realname,
+ server->connrec->chatnet == NULL ? "IRC" : server->connrec->chatnet);
+}
+
+static void notifylist_left(IRC_SERVER_REC *server, const char *nick,
+ const char *username, const char *host,
+ const char *realname, const char *awaymsg)
+{
+ g_return_if_fail(nick != NULL);
+
+ printformat(server, nick, MSGLEVEL_CLIENTNOTICE, IRCTXT_NOTIFY_PART,
+ nick, username, host, realname,
+ server->connrec->chatnet == NULL ? "IRC" : server->connrec->chatnet);
+}
+
+static void notifylist_away(IRC_SERVER_REC *server, const char *nick,
+ const char *username, const char *host,
+ const char *realname, const char *awaymsg)
+{
+ g_return_if_fail(nick != NULL);
+
+ if (awaymsg != NULL) {
+ printformat(server, nick, MSGLEVEL_CLIENTNOTICE,
+ IRCTXT_NOTIFY_AWAY, nick, username, host, realname, awaymsg,
+ server->connrec->chatnet == NULL ? "IRC" : server->connrec->chatnet);
+ } else {
+ printformat(server, nick, MSGLEVEL_CLIENTNOTICE,
+ IRCTXT_NOTIFY_UNAWAY, nick, username, host, realname,
+ server->connrec->chatnet == NULL ? "IRC" : server->connrec->chatnet);
+ }
+}
+
+void fe_irc_notifylist_init(void)
+{
+ theme_register(fecommon_irc_notifylist_formats);
+
+ command_bind("notify", NULL, (SIGNAL_FUNC) cmd_notify);
+ signal_add("notifylist joined", (SIGNAL_FUNC) notifylist_joined);
+ signal_add("notifylist left", (SIGNAL_FUNC) notifylist_left);
+ signal_add("notifylist away changed", (SIGNAL_FUNC) notifylist_away);
+
+ command_set_options("notify", "list");
+
+ settings_check();
+ module_register("notifylist", "fe-irc");
+}
+
+void fe_irc_notifylist_deinit(void)
+{
+ theme_unregister();
+
+ command_unbind("notify", (SIGNAL_FUNC) cmd_notify);
+ signal_remove("notifylist joined", (SIGNAL_FUNC) notifylist_joined);
+ signal_remove("notifylist left", (SIGNAL_FUNC) notifylist_left);
+ signal_remove("notifylist away changed", (SIGNAL_FUNC) notifylist_away);
+}
+
+MODULE_ABICHECK(fe_irc_notifylist)
diff --git a/src/fe-common/irc/notifylist/meson.build b/src/fe-common/irc/notifylist/meson.build
new file mode 100644
index 0000000..2e66c78
--- /dev/null
+++ b/src/fe-common/irc/notifylist/meson.build
@@ -0,0 +1,21 @@
+# this file is part of irssi
+
+libfe_irc_notifylist_a = static_library('fe_irc_notifylist',
+ files(
+ 'fe-notifylist.c',
+ 'module-formats.c',
+ ),
+ include_directories : rootinc,
+ implicit_include_directories : false,
+ c_args : [
+ def_helpdir,
+ def_sysconfdir,
+ ],
+ dependencies : dep)
+
+install_headers(
+ files(
+ 'module-formats.h',
+ 'module.h',
+ ),
+ subdir : incdir / 'src' / 'fe-common' / 'irc' / 'notifylist')
diff --git a/src/fe-common/irc/notifylist/module-formats.c b/src/fe-common/irc/notifylist/module-formats.c
new file mode 100644
index 0000000..12f41fb
--- /dev/null
+++ b/src/fe-common/irc/notifylist/module-formats.c
@@ -0,0 +1,41 @@
+/*
+ module-formats.c : irssi
+
+ Copyright (C) 2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/core/formats.h>
+
+FORMAT_REC fecommon_irc_notifylist_formats[] =
+{
+ { MODULE_NAME, "Notifylist", 0 },
+
+ /* ---- */
+ { NULL, "Notifylist", 0 },
+
+ { "notify_join", "{nick $0} [$1@$2] [{hilight $3}] has joined to $4", 5, { 0, 0, 0, 0, 0 } },
+ { "notify_part", "{nick $0} has left $4", 5, { 0, 0, 0, 0, 0 } },
+ { "notify_away", "{nick $0} [$5] [$1@$2] [{hilight $3}] is now away: $4", 6, { 0, 0, 0, 0, 0, 0 } },
+ { "notify_unaway", "{nick $0} [$4] [$1@$2] [{hilight $3}] is now unaway", 5, { 0, 0, 0, 0, 0 } },
+ { "notify_online", "On $0: {hilight $1}", 2, { 0, 0 } },
+ { "notify_offline", "Offline: $0", 1, { 0 } },
+ { "notify_list", "$0: $1 $2", 4, { 0, 0, 0, 0 } },
+ { "notify_list_empty", "The notify list is empty", 0 },
+
+ { NULL, NULL, 0 },
+};
diff --git a/src/fe-common/irc/notifylist/module-formats.h b/src/fe-common/irc/notifylist/module-formats.h
new file mode 100644
index 0000000..ff3085e
--- /dev/null
+++ b/src/fe-common/irc/notifylist/module-formats.h
@@ -0,0 +1,18 @@
+#include <irssi/src/fe-common/core/formats.h>
+
+enum {
+ IRCTXT_MODULE_NAME,
+
+ IRCTXT_FILL_1,
+
+ IRCTXT_NOTIFY_JOIN,
+ IRCTXT_NOTIFY_PART,
+ IRCTXT_NOTIFY_AWAY,
+ IRCTXT_NOTIFY_UNAWAY,
+ IRCTXT_NOTIFY_ONLINE,
+ IRCTXT_NOTIFY_OFFLINE,
+ IRCTXT_NOTIFY_LIST,
+ IRCTXT_NOTIFY_LIST_EMPTY
+};
+
+extern FORMAT_REC fecommon_irc_notifylist_formats[];
diff --git a/src/fe-common/irc/notifylist/module.h b/src/fe-common/irc/notifylist/module.h
new file mode 100644
index 0000000..0e5ca45
--- /dev/null
+++ b/src/fe-common/irc/notifylist/module.h
@@ -0,0 +1,4 @@
+#include <irssi/src/common.h>
+#include <irssi/src/irc/core/irc.h>
+
+#define MODULE_NAME "fe-common/irc/notifylist"
diff --git a/src/fe-common/meson.build b/src/fe-common/meson.build
new file mode 100644
index 0000000..05ab38a
--- /dev/null
+++ b/src/fe-common/meson.build
@@ -0,0 +1,6 @@
+# this file is part of irssi
+
+subdir('core')
+foreach s : chat_modules
+ subdir(s)
+endforeach