summaryrefslogtreecommitdiffstats
path: root/src/fe-common/core
diff options
context:
space:
mode:
Diffstat (limited to 'src/fe-common/core')
-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
58 files changed, 20389 insertions, 0 deletions
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