diff options
Diffstat (limited to 'src')
145 files changed, 64236 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 00000000..78856692 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,14 @@ +## Process this file with automake to produce Makefile.in + +EXTRA_DIST = fe-text/fe-text.c \ + fe-text/README fe-text/fe-text.h version-script + +if DO_TEXT +text_fe = fe-text +endif + +if DO_GTK +gtk_fe = fe-gtk +endif + +SUBDIRS = pixmaps common $(gtk_fe) $(text_fe) diff --git a/src/common/Makefile.am b/src/common/Makefile.am new file mode 100644 index 00000000..6a35da91 --- /dev/null +++ b/src/common/Makefile.am @@ -0,0 +1,61 @@ +## Process this file with automake to produce Makefile.in + +noinst_LIBRARIES = libxchatcommon.a + +INCLUDES = $(COMMON_CFLAGS) + +EXTRA_DIST = \ + cfgfiles.h \ + chanopt.h \ + ctcp.h \ + dcc.h \ + fe.h \ + history.h \ + identd.c \ + ignore.h \ + inbound.h \ + inet.h \ + make-te.c \ + modes.h \ + msproxy.h \ + network.h \ + notify.h \ + outbound.h \ + plugin.h \ + plugin-timer.h \ + proto-irc.h \ + server.h \ + servlist.h \ + ssl.h \ + ssl.c \ + text.h \ + textenums.h \ + textevents.h \ + textevents.in \ + tree.h \ + url.h \ + userlist.h \ + util.h \ + xchat.h \ + xchatc.h \ + xchat-plugin.h + +if USE_OPENSSL +ssl_c = ssl.c +endif + +if USE_DBUS +dbusdir = dbus +libxchatcommon_a_LIBADD = \ + $(top_builddir)/src/common/dbus/dbus-*.$(OBJEXT) +endif +SUBDIRS = $(dbusdir) . + +libxchatcommon_a_SOURCES = cfgfiles.c chanopt.c ctcp.c dcc.c history.c ignore.c \ + inbound.c modes.c msproxy.c network.c notify.c outbound.c \ + plugin.c plugin-timer.c proto-irc.c server.c servlist.c $(ssl_c) \ + text.c tree.c url.c userlist.c util.c xchat.c +libxchatcommon_a_CFLAGS = $(LIBPROXY_CFLAGS) + +textevents: make-te + ./make-te < textevents.in > textevents.h 2> textenums.h diff --git a/src/common/cfgfiles.c b/src/common/cfgfiles.c new file mode 100644 index 00000000..8bb2a61b --- /dev/null +++ b/src/common/cfgfiles.c @@ -0,0 +1,1105 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "xchat.h" +#include "cfgfiles.h" +#include "util.h" +#include "fe.h" +#include "text.h" +#include "xchatc.h" + +#ifdef WIN32 +#define XCHAT_DIR "X-Chat 2" +#else +#define XCHAT_DIR ".xchat2" +#endif +#define DEF_FONT "Monospace 9" + +void +list_addentry (GSList ** list, char *cmd, char *name) +{ + struct popup *pop; + int cmd_len = 1, name_len; + + /* remove <2.8.0 stuff */ + if (!strcmp (cmd, "away") && !strcmp (name, "BACK")) + return; + + if (cmd) + cmd_len = strlen (cmd) + 1; + name_len = strlen (name) + 1; + + pop = malloc (sizeof (struct popup) + cmd_len + name_len); + pop->name = (char *) pop + sizeof (struct popup); + pop->cmd = pop->name + name_len; + + memcpy (pop->name, name, name_len); + if (cmd) + memcpy (pop->cmd, cmd, cmd_len); + else + pop->cmd[0] = 0; + + *list = g_slist_append (*list, pop); +} + +/* read it in from a buffer to our linked list */ + +static void +list_load_from_data (GSList ** list, char *ibuf, int size) +{ + char cmd[384]; + char name[128]; + char *buf; + int pnt = 0; + + cmd[0] = 0; + name[0] = 0; + + while (buf_get_line (ibuf, &buf, &pnt, size)) + { + if (*buf != '#') + { + if (!strncasecmp (buf, "NAME ", 5)) + { + safe_strcpy (name, buf + 5, sizeof (name)); + } + else if (!strncasecmp (buf, "CMD ", 4)) + { + safe_strcpy (cmd, buf + 4, sizeof (cmd)); + if (*name) + { + list_addentry (list, cmd, name); + cmd[0] = 0; + name[0] = 0; + } + } + } + } +} + +void +list_loadconf (char *file, GSList ** list, char *defaultconf) +{ + char filebuf[256]; + char *ibuf; + int fh; + struct stat st; + + snprintf (filebuf, sizeof (filebuf), "%s/%s", get_xdir_fs (), file); + fh = open (filebuf, O_RDONLY | OFLAGS); + if (fh == -1) + { + if (defaultconf) + list_load_from_data (list, defaultconf, strlen (defaultconf)); + return; + } + if (fstat (fh, &st) != 0) + { + perror ("fstat"); + abort (); + } + + ibuf = malloc (st.st_size); + read (fh, ibuf, st.st_size); + close (fh); + + list_load_from_data (list, ibuf, st.st_size); + + free (ibuf); +} + +void +list_free (GSList ** list) +{ + void *data; + while (*list) + { + data = (void *) (*list)->data; + free (data); + *list = g_slist_remove (*list, data); + } +} + +int +list_delentry (GSList ** list, char *name) +{ + struct popup *pop; + GSList *alist = *list; + + while (alist) + { + pop = (struct popup *) alist->data; + if (!strcasecmp (name, pop->name)) + { + *list = g_slist_remove (*list, pop); + free (pop); + return 1; + } + alist = alist->next; + } + return 0; +} + +char * +cfg_get_str (char *cfg, char *var, char *dest, int dest_len) +{ + while (1) + { + if (!strncasecmp (var, cfg, strlen (var))) + { + char *value, t; + cfg += strlen (var); + while (*cfg == ' ') + cfg++; + if (*cfg == '=') + cfg++; + while (*cfg == ' ') + cfg++; + /*while (*cfg == ' ' || *cfg == '=') + cfg++; */ + value = cfg; + while (*cfg != 0 && *cfg != '\n') + cfg++; + t = *cfg; + *cfg = 0; + safe_strcpy (dest, value, dest_len); + *cfg = t; + return cfg; + } + while (*cfg != 0 && *cfg != '\n') + cfg++; + if (*cfg == 0) + return 0; + cfg++; + if (*cfg == 0) + return 0; + } +} + +static int +cfg_put_str (int fh, char *var, char *value) +{ + char buf[512]; + int len; + + snprintf (buf, sizeof buf, "%s = %s\n", var, value); + len = strlen (buf); + return (write (fh, buf, len) == len); +} + +int +cfg_put_color (int fh, int r, int g, int b, char *var) +{ + char buf[400]; + int len; + + snprintf (buf, sizeof buf, "%s = %04x %04x %04x\n", var, r, g, b); + len = strlen (buf); + return (write (fh, buf, len) == len); +} + +int +cfg_put_int (int fh, int value, char *var) +{ + char buf[400]; + int len; + + if (value == -1) + value = 1; + + snprintf (buf, sizeof buf, "%s = %d\n", var, value); + len = strlen (buf); + return (write (fh, buf, len) == len); +} + +int +cfg_get_color (char *cfg, char *var, int *r, int *g, int *b) +{ + char str[128]; + + if (!cfg_get_str (cfg, var, str, sizeof (str))) + return 0; + + sscanf (str, "%04x %04x %04x", r, g, b); + return 1; +} + +int +cfg_get_int_with_result (char *cfg, char *var, int *result) +{ + char str[128]; + + if (!cfg_get_str (cfg, var, str, sizeof (str))) + { + *result = 0; + return 0; + } + + *result = 1; + return atoi (str); +} + +int +cfg_get_int (char *cfg, char *var) +{ + char str[128]; + + if (!cfg_get_str (cfg, var, str, sizeof (str))) + return 0; + + return atoi (str); +} + +char *xdir_fs = NULL; /* file system encoding */ +char *xdir_utf = NULL; /* utf-8 encoding */ + +#ifdef WIN32 + +#include <windows.h> + +static gboolean +get_reg_str (const char *sub, const char *name, char *out, DWORD len) +{ + HKEY hKey; + DWORD t; + + if (RegOpenKeyEx (HKEY_CURRENT_USER, sub, 0, KEY_READ, &hKey) == + ERROR_SUCCESS) + { + if (RegQueryValueEx (hKey, name, NULL, &t, out, &len) != ERROR_SUCCESS || + t != REG_SZ) + { + RegCloseKey (hKey); + return FALSE; + } + out[len-1] = 0; + RegCloseKey (hKey); + return TRUE; + } + + return FALSE; +} + +char * +get_xdir_fs (void) +{ + if (!xdir_fs) + { + char out[256]; + + if (!get_reg_str ("Software\\Microsoft\\Windows\\CurrentVersion\\" + "Explorer\\Shell Folders", "AppData", out, sizeof (out))) + return "./config"; + xdir_fs = g_strdup_printf ("%s\\" XCHAT_DIR, out); + } + return xdir_fs; +} + +#else + +char * +get_xdir_fs (void) +{ + if (!xdir_fs) + xdir_fs = g_strdup_printf ("%s/" XCHAT_DIR, g_get_home_dir ()); + + return xdir_fs; +} + +#endif /* !WIN32 */ + +char * +get_xdir_utf8 (void) +{ + if (!xdir_utf) /* never free this, keep it for program life time */ + xdir_utf = xchat_filename_to_utf8 (get_xdir_fs (), -1, 0, 0, 0); + + return xdir_utf; +} + +static void +check_prefs_dir (void) +{ + char *dir = get_xdir_fs (); + if (access (dir, F_OK) != 0) + { +#ifdef WIN32 + if (mkdir (dir) != 0) +#else + if (mkdir (dir, S_IRUSR | S_IWUSR | S_IXUSR) != 0) +#endif + fe_message (_("Cannot create ~/.xchat2"), FE_MSG_ERROR); + } +} + +static char * +default_file (void) +{ + static char *dfile = 0; + + if (!dfile) + { + dfile = malloc (strlen (get_xdir_fs ()) + 12); + sprintf (dfile, "%s/xchat.conf", get_xdir_fs ()); + } + return dfile; +} + +/* Keep these sorted!! */ + +const struct prefs vars[] = { + {"auto_save", P_OFFINT (autosave), TYPE_BOOL}, + {"auto_save_url", P_OFFINT (autosave_url), TYPE_BOOL}, + + {"away_auto_unmark", P_OFFINT (auto_unmark_away), TYPE_BOOL}, + {"away_reason", P_OFFSET (awayreason), TYPE_STR}, + {"away_show_message", P_OFFINT (show_away_message), TYPE_BOOL}, + {"away_show_once", P_OFFINT (show_away_once), TYPE_BOOL}, + {"away_size_max", P_OFFINT (away_size_max), TYPE_INT}, + {"away_timeout", P_OFFINT (away_timeout), TYPE_INT}, + {"away_track", P_OFFINT (away_track), TYPE_BOOL}, + + {"completion_amount", P_OFFINT (completion_amount), TYPE_INT}, + {"completion_auto", P_OFFINT (nickcompletion), TYPE_BOOL}, + {"completion_sort", P_OFFINT (completion_sort), TYPE_INT}, + {"completion_suffix", P_OFFSET (nick_suffix), TYPE_STR}, + + {"dcc_auto_chat", P_OFFINT (autodccchat), TYPE_INT}, + {"dcc_auto_resume", P_OFFINT (autoresume), TYPE_BOOL}, + {"dcc_auto_send", P_OFFINT (autodccsend), TYPE_INT}, + {"dcc_blocksize", P_OFFINT (dcc_blocksize), TYPE_INT}, + {"dcc_completed_dir", P_OFFSET (dcc_completed_dir), TYPE_STR}, + {"dcc_dir", P_OFFSET (dccdir), TYPE_STR}, + {"dcc_fast_send", P_OFFINT (fastdccsend), TYPE_BOOL}, + {"dcc_global_max_get_cps", P_OFFINT (dcc_global_max_get_cps), TYPE_INT}, + {"dcc_global_max_send_cps", P_OFFINT (dcc_global_max_send_cps), TYPE_INT}, + {"dcc_ip", P_OFFSET (dcc_ip_str), TYPE_STR}, + {"dcc_ip_from_server", P_OFFINT (ip_from_server), TYPE_BOOL}, + {"dcc_max_get_cps", P_OFFINT (dcc_max_get_cps), TYPE_INT}, + {"dcc_max_send_cps", P_OFFINT (dcc_max_send_cps), TYPE_INT}, + {"dcc_permissions", P_OFFINT (dccpermissions), TYPE_INT}, + {"dcc_port_first", P_OFFINT (first_dcc_send_port), TYPE_INT}, + {"dcc_port_last", P_OFFINT (last_dcc_send_port), TYPE_INT}, + {"dcc_remove", P_OFFINT (dcc_remove), TYPE_BOOL}, + {"dcc_save_nick", P_OFFINT (dccwithnick), TYPE_BOOL}, + {"dcc_send_fillspaces", P_OFFINT (dcc_send_fillspaces), TYPE_BOOL}, + {"dcc_stall_timeout", P_OFFINT (dccstalltimeout), TYPE_INT}, + {"dcc_timeout", P_OFFINT (dcctimeout), TYPE_INT}, + + {"dnsprogram", P_OFFSET (dnsprogram), TYPE_STR}, + + {"flood_ctcp_num", P_OFFINT (ctcp_number_limit), TYPE_INT}, + {"flood_ctcp_time", P_OFFINT (ctcp_time_limit), TYPE_INT}, + {"flood_msg_num", P_OFFINT (msg_number_limit), TYPE_INT}, + {"flood_msg_time", P_OFFINT (msg_time_limit), TYPE_INT}, + + {"gui_auto_open_chat", P_OFFINT (autoopendccchatwindow), TYPE_BOOL}, + {"gui_auto_open_dialog", P_OFFINT (autodialog), TYPE_BOOL}, + {"gui_auto_open_recv", P_OFFINT (autoopendccrecvwindow), TYPE_BOOL}, + {"gui_auto_open_send", P_OFFINT (autoopendccsendwindow), TYPE_BOOL}, + {"gui_dialog_height", P_OFFINT (dialog_height), TYPE_INT}, + {"gui_dialog_left", P_OFFINT (dialog_left), TYPE_INT}, + {"gui_dialog_top", P_OFFINT (dialog_top), TYPE_INT}, + {"gui_dialog_width", P_OFFINT (dialog_width), TYPE_INT}, + {"gui_hide_menu", P_OFFINT (hidemenu), TYPE_BOOL}, + {"gui_input_spell", P_OFFINT (gui_input_spell), TYPE_BOOL}, + {"gui_input_style", P_OFFINT (style_inputbox), TYPE_BOOL}, + {"gui_join_dialog", P_OFFINT (gui_join_dialog), TYPE_BOOL}, + {"gui_lagometer", P_OFFINT (lagometer), TYPE_INT}, + {"gui_mode_buttons", P_OFFINT (chanmodebuttons), TYPE_BOOL}, + {"gui_pane_left_size", P_OFFINT (gui_pane_left_size), TYPE_INT}, + {"gui_pane_right_size", P_OFFINT (gui_pane_right_size), TYPE_INT}, + {"gui_quit_dialog", P_OFFINT (gui_quit_dialog), TYPE_BOOL}, + {"gui_slist_fav", P_OFFINT (slist_fav), TYPE_INT}, + {"gui_slist_select", P_OFFINT (slist_select), TYPE_INT}, + {"gui_slist_skip", P_OFFINT (slist_skip), TYPE_BOOL}, + {"gui_throttlemeter", P_OFFINT (throttlemeter), TYPE_INT}, + {"gui_topicbar", P_OFFINT (topicbar), TYPE_BOOL}, + {"gui_tray", P_OFFINT (gui_tray), TYPE_BOOL}, + {"gui_tray_flags", P_OFFINT (gui_tray_flags), TYPE_INT}, + {"gui_tweaks", P_OFFINT (gui_tweaks), TYPE_INT}, + {"gui_ulist_buttons", P_OFFINT (userlistbuttons), TYPE_BOOL}, + {"gui_ulist_doubleclick", P_OFFSET (doubleclickuser), TYPE_STR}, + {"gui_ulist_hide", P_OFFINT (hideuserlist), TYPE_BOOL}, + {"gui_ulist_left", P_OFFINT (_gui_ulist_left), TYPE_BOOL}, /* obsolete */ + {"gui_ulist_pos", P_OFFINT (gui_ulist_pos), TYPE_INT}, + {"gui_ulist_resizable", P_OFFINT (paned_userlist), TYPE_BOOL}, + {"gui_ulist_show_hosts", P_OFFINT(showhostname_in_userlist), TYPE_BOOL}, + {"gui_ulist_sort", P_OFFINT (userlist_sort), TYPE_INT}, + {"gui_ulist_style", P_OFFINT (style_namelistgad), TYPE_BOOL}, + {"gui_url_mod", P_OFFINT (gui_url_mod), TYPE_INT}, + {"gui_usermenu", P_OFFINT (gui_usermenu), TYPE_BOOL}, + {"gui_win_height", P_OFFINT (mainwindow_height), TYPE_INT}, + {"gui_win_left", P_OFFINT (mainwindow_left), TYPE_INT}, + {"gui_win_save", P_OFFINT (mainwindow_save), TYPE_BOOL}, + {"gui_win_state", P_OFFINT (gui_win_state), TYPE_INT}, + {"gui_win_top", P_OFFINT (mainwindow_top), TYPE_INT}, + {"gui_win_width", P_OFFINT (mainwindow_width), TYPE_INT}, + +#ifdef WIN32 + {"identd", P_OFFINT (identd), TYPE_BOOL}, +#endif + {"input_balloon_chans", P_OFFINT (input_balloon_chans), TYPE_BOOL}, + {"input_balloon_hilight", P_OFFINT (input_balloon_hilight), TYPE_BOOL}, + {"input_balloon_priv", P_OFFINT (input_balloon_priv), TYPE_BOOL}, + {"input_balloon_time", P_OFFINT (input_balloon_time), TYPE_INT}, + {"input_beep_chans", P_OFFINT (input_beep_chans), TYPE_BOOL}, + {"input_beep_hilight", P_OFFINT (input_beep_hilight), TYPE_BOOL}, + {"input_beep_msg", P_OFFINT (input_beep_priv), TYPE_BOOL}, + {"input_command_char", P_OFFSET (cmdchar), TYPE_STR}, + {"input_filter_beep", P_OFFINT (filterbeep), TYPE_BOOL}, + {"input_flash_chans", P_OFFINT (input_flash_chans), TYPE_BOOL}, + {"input_flash_hilight", P_OFFINT (input_flash_hilight), TYPE_BOOL}, + {"input_flash_priv", P_OFFINT (input_flash_priv), TYPE_BOOL}, + {"input_perc_ascii", P_OFFINT (perc_ascii), TYPE_BOOL}, + {"input_perc_color", P_OFFINT (perc_color), TYPE_BOOL}, + {"input_tray_chans", P_OFFINT (input_tray_chans), TYPE_BOOL}, + {"input_tray_hilight", P_OFFINT (input_tray_hilight), TYPE_BOOL}, + {"input_tray_priv", P_OFFINT (input_tray_priv), TYPE_BOOL}, + + {"irc_auto_rejoin", P_OFFINT (autorejoin), TYPE_BOOL}, + {"irc_ban_type", P_OFFINT (bantype), TYPE_INT}, + {"irc_conf_mode", P_OFFINT (confmode), TYPE_BOOL}, + {"irc_extra_hilight", P_OFFSET (irc_extra_hilight), TYPE_STR}, + {"irc_hide_version", P_OFFINT (hidever), TYPE_BOOL}, + {"irc_id_ntext", P_OFFSET (irc_id_ntext), TYPE_STR}, + {"irc_id_ytext", P_OFFSET (irc_id_ytext), TYPE_STR}, + {"irc_invisible", P_OFFINT (invisible), TYPE_BOOL}, + {"irc_join_delay", P_OFFINT (irc_join_delay), TYPE_INT}, + {"irc_logging", P_OFFINT (logging), TYPE_BOOL}, + {"irc_logmask", P_OFFSET (logmask), TYPE_STR}, + {"irc_nick1", P_OFFSET (nick1), TYPE_STR}, + {"irc_nick2", P_OFFSET (nick2), TYPE_STR}, + {"irc_nick3", P_OFFSET (nick3), TYPE_STR}, + {"irc_nick_hilight", P_OFFSET (irc_nick_hilight), TYPE_STR}, + {"irc_no_hilight", P_OFFSET (irc_no_hilight), TYPE_STR}, + {"irc_part_reason", P_OFFSET (partreason), TYPE_STR}, + {"irc_quit_reason", P_OFFSET (quitreason), TYPE_STR}, + {"irc_raw_modes", P_OFFINT (raw_modes), TYPE_BOOL}, + {"irc_real_name", P_OFFSET (realname), TYPE_STR}, + {"irc_servernotice", P_OFFINT (servernotice), TYPE_BOOL}, + {"irc_skip_motd", P_OFFINT (skipmotd), TYPE_BOOL}, + {"irc_user_name", P_OFFSET (username), TYPE_STR}, + {"irc_wallops", P_OFFINT (wallops), TYPE_BOOL}, + {"irc_who_join", P_OFFINT (userhost), TYPE_BOOL}, + {"irc_whois_front", P_OFFINT (irc_whois_front), TYPE_BOOL}, + + {"net_auto_reconnect", P_OFFINT (autoreconnect), TYPE_BOOL}, + {"net_auto_reconnectonfail", P_OFFINT (autoreconnectonfail), TYPE_BOOL}, + {"net_bind_host", P_OFFSET (hostname), TYPE_STR}, + {"net_ping_timeout", P_OFFINT (pingtimeout), TYPE_INT}, + {"net_proxy_auth", P_OFFINT (proxy_auth), TYPE_BOOL}, + {"net_proxy_host", P_OFFSET (proxy_host), TYPE_STR}, + {"net_proxy_pass", P_OFFSET (proxy_pass), TYPE_STR}, + {"net_proxy_port", P_OFFINT (proxy_port), TYPE_INT}, + {"net_proxy_type", P_OFFINT (proxy_type), TYPE_INT}, + {"net_proxy_use", P_OFFINT (proxy_use), TYPE_INT}, + {"net_proxy_user", P_OFFSET (proxy_user), TYPE_STR}, + + {"net_reconnect_delay", P_OFFINT (recon_delay), TYPE_INT}, + {"net_throttle", P_OFFINT (throttle), TYPE_BOOL}, + + {"notify_timeout", P_OFFINT (notify_timeout), TYPE_INT}, + {"notify_whois_online", P_OFFINT (whois_on_notifyonline), TYPE_BOOL}, + + {"perl_warnings", P_OFFINT (perlwarnings), TYPE_BOOL}, + + {"sound_command", P_OFFSET (soundcmd), TYPE_STR}, + {"sound_dir", P_OFFSET (sounddir), TYPE_STR}, + {"stamp_log", P_OFFINT (timestamp_logs), TYPE_BOOL}, + {"stamp_log_format", P_OFFSET (timestamp_log_format), TYPE_STR}, + {"stamp_text", P_OFFINT (timestamp), TYPE_BOOL}, + {"stamp_text_format", P_OFFSET (stamp_format), TYPE_STR}, + + {"tab_chans", P_OFFINT (tabchannels), TYPE_BOOL}, + {"tab_dialogs", P_OFFINT (privmsgtab), TYPE_BOOL}, + {"tab_layout", P_OFFINT (tab_layout), TYPE_INT}, + {"tab_new_to_front", P_OFFINT (newtabstofront), TYPE_INT}, + {"tab_notices", P_OFFINT (notices_tabs), TYPE_BOOL}, + {"tab_pos", P_OFFINT (tab_pos), TYPE_INT}, + {"tab_position", P_OFFINT (_tabs_position), TYPE_INT}, /* obsolete */ + {"tab_server", P_OFFINT (use_server_tab), TYPE_BOOL}, + {"tab_small", P_OFFINT (tab_small), TYPE_INT}, + {"tab_sort", P_OFFINT (tab_sort), TYPE_BOOL}, + {"tab_trunc", P_OFFINT (truncchans), TYPE_INT}, + {"tab_utils", P_OFFINT (windows_as_tabs), TYPE_BOOL}, + + {"text_background", P_OFFSET (background), TYPE_STR}, + {"text_color_nicks", P_OFFINT (colorednicks), TYPE_BOOL}, + {"text_font", P_OFFSET (font_normal), TYPE_STR}, + {"text_indent", P_OFFINT (indent_nicks), TYPE_BOOL}, + {"text_max_indent", P_OFFINT (max_auto_indent), TYPE_INT}, + {"text_max_lines", P_OFFINT (max_lines), TYPE_INT}, + {"text_replay", P_OFFINT (text_replay), TYPE_BOOL}, + {"text_show_marker", P_OFFINT (show_marker), TYPE_BOOL}, + {"text_show_sep", P_OFFINT (show_separator), TYPE_BOOL}, + {"text_stripcolor", P_OFFINT (stripcolor), TYPE_BOOL}, + {"text_thin_sep", P_OFFINT (thin_separator), TYPE_BOOL}, + {"text_tint_blue", P_OFFINT (tint_blue), TYPE_INT}, + {"text_tint_green", P_OFFINT (tint_green), TYPE_INT}, + {"text_tint_red", P_OFFINT (tint_red), TYPE_INT}, + {"text_transparent", P_OFFINT (transparent), TYPE_BOOL}, + {"text_wordwrap", P_OFFINT (wordwrap), TYPE_BOOL}, + + {0, 0, 0}, +}; + +static char * +convert_with_fallback (const char *str, const char *fallback) +{ + char *utf; + + utf = g_locale_to_utf8 (str, -1, 0, 0, 0); + if (!utf) + { + /* this can happen if CHARSET envvar is set wrong */ + /* maybe it's already utf8 (breakage!) */ + if (!g_utf8_validate (str, -1, NULL)) + utf = g_strdup (fallback); + else + utf = g_strdup (str); + } + + return utf; +} + +void +load_config (void) +{ + struct stat st; + char *cfg, *sp; + const char *username, *realname; + int res, val, i, fh; + + check_prefs_dir (); + username = g_get_user_name (); + if (!username) + username = "root"; + + realname = g_get_real_name (); + if ((realname && realname[0] == 0) || !realname) + realname = username; + + username = convert_with_fallback (username, "username"); + realname = convert_with_fallback (realname, "realname"); + + memset (&prefs, 0, sizeof (struct xchatprefs)); + + /* put in default values, anything left out is automatically zero */ + prefs.local_ip = 0xffffffff; + prefs.irc_join_delay = 3; + prefs.show_marker = 1; + prefs.newtabstofront = 2; + prefs.completion_amount = 5; + prefs.away_timeout = 60; + prefs.away_size_max = 300; + prefs.away_track = 1; + prefs.timestamp_logs = 1; + prefs.truncchans = 20; + prefs.autoresume = 1; + prefs.show_away_once = 1; + prefs.indent_nicks = 1; + prefs.thin_separator = 1; + prefs._tabs_position = 2; /* 2 = left */ + prefs.fastdccsend = 1; + prefs.wordwrap = 1; + prefs.autosave = 1; + prefs.autodialog = 1; + prefs.gui_input_spell = 1; + prefs.autoreconnect = 1; + prefs.recon_delay = 10; + prefs.text_replay = 1; + prefs.tabchannels = 1; + prefs.tab_layout = 2; /* 0=Tabs 1=Reserved 2=Tree */ + prefs.tab_sort = 1; + prefs.paned_userlist = 1; + prefs.newtabstofront = 2; + prefs.use_server_tab = 1; + prefs.privmsgtab = 1; + /*prefs.style_inputbox = 1;*/ + prefs.dccpermissions = 0600; + prefs.max_lines = 500; + prefs.mainwindow_width = 640; + prefs.mainwindow_height = 400; + prefs.dialog_width = 500; + prefs.dialog_height = 256; + prefs.gui_join_dialog = 1; + prefs.gui_quit_dialog = 1; + prefs.dcctimeout = 180; + prefs.dccstalltimeout = 60; + prefs.notify_timeout = 15; + prefs.tint_red = + prefs.tint_green = + prefs.tint_blue = 195; + prefs.auto_indent = 1; + prefs.max_auto_indent = 256; + prefs.show_separator = 1; + prefs.dcc_blocksize = 1024; + prefs.throttle = 1; + /*FIXME*/ prefs.msg_time_limit = 30; + prefs.msg_number_limit = 5; + prefs.ctcp_time_limit = 30; + prefs.ctcp_number_limit = 5; + prefs.topicbar = 1; + prefs.lagometer = 1; + prefs.throttlemeter = 1; + prefs.autoopendccrecvwindow = 1; + prefs.autoopendccsendwindow = 1; + prefs.autoopendccchatwindow = 1; + prefs.userhost = 1; + prefs.gui_url_mod = 4; /* ctrl */ + prefs.gui_tray = 1; + prefs.gui_pane_left_size = 100; + prefs.gui_pane_right_size = 100; + prefs.mainwindow_save = 1; + prefs.bantype = 2; + prefs.input_balloon_time = 20; + prefs.input_flash_priv = prefs.input_flash_hilight = 1; + prefs.input_tray_priv = prefs.input_tray_hilight = 1; + prefs.autodccsend = 2; /* browse mode */ +#ifdef WIN32 + prefs.identd = 1; +#endif + strcpy (prefs.stamp_format, "[%H:%M] "); + strcpy (prefs.timestamp_log_format, "%b %d %H:%M:%S "); + strcpy (prefs.logmask, "%n-%c.log"); + strcpy (prefs.nick_suffix, ","); + strcpy (prefs.cmdchar, "/"); + strcpy (prefs.nick1, username); + strcpy (prefs.nick2, username); + strcat (prefs.nick2, "_"); + strcpy (prefs.nick3, username); + strcat (prefs.nick3, "__"); + strcpy (prefs.realname, realname); + strcpy (prefs.username, username); +#ifdef WIN32 + strcpy (prefs.sounddir, "./sounds"); + { + char out[256]; + + if (get_reg_str ("Software\\Microsoft\\Windows\\CurrentVersion\\" + "Explorer\\Shell Folders", "Personal", out, sizeof (out))) + snprintf (prefs.dccdir, sizeof (prefs.dccdir), "%s\\Downloads", out); + else + snprintf (prefs.dccdir, sizeof (prefs.dccdir), "%s\\Downloads", get_xdir_utf8 ()); + } +#else + snprintf (prefs.sounddir, sizeof (prefs.sounddir), "%s/sounds", get_xdir_utf8 ()); + snprintf (prefs.dccdir, sizeof (prefs.dccdir), "%s/downloads", get_xdir_utf8 ()); +#endif + strcpy (prefs.doubleclickuser, "QUOTE WHOIS %s %s"); + strcpy (prefs.awayreason, _("I'm busy")); + strcpy (prefs.quitreason, _("Leaving")); + strcpy (prefs.partreason, prefs.quitreason); + strcpy (prefs.font_normal, DEF_FONT); + strcpy (prefs.dnsprogram, "host"); + strcpy (prefs.irc_no_hilight, "NickServ,ChanServ"); + + g_free ((char *)username); + g_free ((char *)realname); + + fh = open (default_file (), OFLAGS | O_RDONLY); + if (fh != -1) + { + fstat (fh, &st); + cfg = malloc (st.st_size + 1); + cfg[0] = '\0'; + i = read (fh, cfg, st.st_size); + if (i >= 0) + cfg[i] = '\0'; /* make sure cfg is NULL terminated */ + close (fh); + i = 0; + do + { + switch (vars[i].type) + { + case TYPE_STR: + cfg_get_str (cfg, vars[i].name, (char *) &prefs + vars[i].offset, + vars[i].len); + break; + case TYPE_BOOL: + case TYPE_INT: + val = cfg_get_int_with_result (cfg, vars[i].name, &res); + if (res) + *((int *) &prefs + vars[i].offset) = val; + break; + } + i++; + } + while (vars[i].name); + + free (cfg); + + } else + { +#ifndef WIN32 +#ifndef __EMX__ + /* OS/2 uses UID 0 all the time */ + if (getuid () == 0) + fe_message (_("* Running IRC as root is stupid! You should\n" + " create a User Account and use that to login.\n"), FE_MSG_WARN|FE_MSG_WAIT); +#endif +#endif /* !WIN32 */ + + mkdir_utf8 (prefs.dccdir); + mkdir_utf8 (prefs.dcc_completed_dir); + } + if (prefs.mainwindow_height < 138) + prefs.mainwindow_height = 138; + if (prefs.mainwindow_width < 106) + prefs.mainwindow_width = 106; + + sp = strchr (prefs.username, ' '); + if (sp) + sp[0] = 0; /* spaces in username would break the login */ + + /* try to make sense of old ulist/tree position settings */ + if (prefs.gui_ulist_pos == 0) + { + prefs.gui_ulist_pos = 3; /* top right */ + if (prefs._gui_ulist_left) + prefs.gui_ulist_pos = 2; /* bottom left */ + + switch (prefs._tabs_position) + { + case 0: + prefs.tab_pos = 6; /* bottom */ + break; + case 1: + prefs.tab_pos = 5; /* top */ + break; + case 2: + prefs.tab_pos = 1; /* left */ + break; + case 3: + prefs.tab_pos = 4; /* right */ + break; + case 4: + prefs.tab_pos = 1; /* (hidden)left */ + break; + case 5: + if (prefs._gui_ulist_left) + { + prefs.tab_pos = 1; /* above ulist left */ + prefs.gui_ulist_pos = 2; + } + else + { + prefs.tab_pos = 3; /* above ulist right */ + prefs.gui_ulist_pos = 4; + } + break; + } + } +} + +int +save_config (void) +{ + int fh, i; + char *new_config, *config; + + check_prefs_dir (); + + config = default_file (); + new_config = malloc (strlen (config) + 5); + strcpy (new_config, config); + strcat (new_config, ".new"); + + fh = open (new_config, OFLAGS | O_TRUNC | O_WRONLY | O_CREAT, 0600); + if (fh == -1) + { + free (new_config); + return 0; + } + + if (!cfg_put_str (fh, "version", PACKAGE_VERSION)) + { + free (new_config); + return 0; + } + + i = 0; + do + { + switch (vars[i].type) + { + case TYPE_STR: + if (!cfg_put_str (fh, vars[i].name, (char *) &prefs + vars[i].offset)) + { + free (new_config); + return 0; + } + break; + case TYPE_INT: + case TYPE_BOOL: + if (!cfg_put_int (fh, *((int *) &prefs + vars[i].offset), vars[i].name)) + { + free (new_config); + return 0; + } + } + i++; + } + while (vars[i].name); + + if (close (fh) == -1) + { + free (new_config); + return 0; + } + +#ifdef WIN32 + unlink (config); /* win32 can't rename to an existing file */ +#endif + if (rename (new_config, config) == -1) + { + free (new_config); + return 0; + } + free (new_config); + + return 1; +} + +static void +set_showval (session *sess, const struct prefs *var, char *tbuf) +{ + int len, dots, j; + + len = strlen (var->name); + memcpy (tbuf, var->name, len); + dots = 29 - len; + if (dots < 0) + dots = 0; + tbuf[len++] = '\003'; + tbuf[len++] = '2'; + for (j=0;j<dots;j++) + tbuf[j + len] = '.'; + len += j; + switch (var->type) + { + case TYPE_STR: + sprintf (tbuf + len, "\0033:\017 %s\n", + (char *) &prefs + var->offset); + break; + case TYPE_INT: + sprintf (tbuf + len, "\0033:\017 %d\n", + *((int *) &prefs + var->offset)); + break; + case TYPE_BOOL: + if (*((int *) &prefs + var->offset)) + sprintf (tbuf + len, "\0033:\017 %s\n", "ON"); + else + sprintf (tbuf + len, "\0033:\017 %s\n", "OFF"); + break; + } + PrintText (sess, tbuf); +} + +static void +set_list (session * sess, char *tbuf) +{ + int i; + + i = 0; + do + { + set_showval (sess, &vars[i], tbuf); + i++; + } + while (vars[i].name); +} + +int +cfg_get_bool (char *var) +{ + int i = 0; + + do + { + if (!strcasecmp (var, vars[i].name)) + { + return *((int *) &prefs + vars[i].offset); + } + i++; + } + while (vars[i].name); + + return -1; +} + +int +cmd_set (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int wild = FALSE; + int or = FALSE; + int off = FALSE; + int quiet = FALSE; + int erase = FALSE; + int i = 0, finds = 0, found; + int idx = 2; + char *var, *val; + + if (strcasecmp (word[2], "-e") == 0) + { + idx++; + erase = TRUE; + } + + /* turn a bit OFF */ + if (strcasecmp (word[idx], "-off") == 0) + { + idx++; + off = TRUE; + } + + /* turn a bit ON */ + if (strcasecmp (word[idx], "-or") == 0 || strcasecmp (word[idx], "-on") == 0) + { + idx++; + or = TRUE; + } + + if (strcasecmp (word[idx], "-quiet") == 0) + { + idx++; + quiet = TRUE; + } + + var = word[idx]; + val = word_eol[idx+1]; + + if (!*var) + { + set_list (sess, tbuf); + return TRUE; + } + + if ((strchr (var, '*') || strchr (var, '?')) && !*val) + wild = TRUE; + + if (*val == '=') + val++; + + do + { + if (wild) + found = !match (var, vars[i].name); + else + found = strcasecmp (var, vars[i].name); + + if (found == 0) + { + finds++; + switch (vars[i].type) + { + case TYPE_STR: + if (erase || *val) + { + strncpy ((char *) &prefs + vars[i].offset, val, vars[i].len); + ((char *) &prefs)[vars[i].offset + vars[i].len - 1] = 0; + if (!quiet) + PrintTextf (sess, "%s set to: %s\n", var, (char *) &prefs + vars[i].offset); + } else + { + set_showval (sess, &vars[i], tbuf); + } + break; + case TYPE_INT: + case TYPE_BOOL: + if (*val) + { + if (vars[i].type == TYPE_BOOL) + { + if (atoi (val)) + *((int *) &prefs + vars[i].offset) = 1; + else + *((int *) &prefs + vars[i].offset) = 0; + if (!strcasecmp (val, "YES") || !strcasecmp (val, "ON")) + *((int *) &prefs + vars[i].offset) = 1; + if (!strcasecmp (val, "NO") || !strcasecmp (val, "OFF")) + *((int *) &prefs + vars[i].offset) = 0; + } else + { + if (or) + *((int *) &prefs + vars[i].offset) |= atoi (val); + else if (off) + *((int *) &prefs + vars[i].offset) &= ~(atoi (val)); + else + *((int *) &prefs + vars[i].offset) = atoi (val); + } + if (!quiet) + PrintTextf (sess, "%s set to: %d\n", var, + *((int *) &prefs + vars[i].offset)); + } else + { + set_showval (sess, &vars[i], tbuf); + } + break; + } + } + i++; + } + while (vars[i].name); + + if (!finds && !quiet) + PrintText (sess, "No such variable.\n"); + + return TRUE; +} + +int +xchat_open_file (char *file, int flags, int mode, int xof_flags) +{ + char buf[1024]; + + if (xof_flags & XOF_FULLPATH) + { + if (xof_flags & XOF_DOMODE) + return open (file, flags | OFLAGS, mode); + else + return open (file, flags | OFLAGS); + } + + snprintf (buf, sizeof (buf), "%s/%s", get_xdir_fs (), file); + if (xof_flags & XOF_DOMODE) + return open (buf, flags | OFLAGS, mode); + else + return open (buf, flags | OFLAGS); +} + +FILE * +xchat_fopen_file (const char *file, const char *mode, int xof_flags) +{ + char buf[1024]; + + if (xof_flags & XOF_FULLPATH) + return fopen (file, mode); + + snprintf (buf, sizeof (buf), "%s/%s", get_xdir_fs (), file); + return fopen (buf, mode); +} diff --git a/src/common/cfgfiles.h b/src/common/cfgfiles.h new file mode 100644 index 00000000..984b9472 --- /dev/null +++ b/src/common/cfgfiles.h @@ -0,0 +1,55 @@ +/* cfgfiles.h */ + +#ifndef XCHAT_CFGFILES_H +#define XCHAT_CFGFILES_H + +#include "xchat.h" + +extern char *xdir_fs; +extern char *xdir_utf; + +char *cfg_get_str (char *cfg, char *var, char *dest, int dest_len); +int cfg_get_bool (char *var); +int cfg_get_int_with_result (char *cfg, char *var, int *result); +int cfg_get_int (char *cfg, char *var); +int cfg_put_int (int fh, int value, char *var); +int cfg_get_color (char *cfg, char *var, int *r, int *g, int *b); +int cfg_put_color (int fh, int r, int g, int b, char *var); +char *get_xdir_fs (void); +char *get_xdir_utf8 (void); +void load_config (void); +int save_config (void); +void list_free (GSList ** list); +void list_loadconf (char *file, GSList ** list, char *defaultconf); +int list_delentry (GSList ** list, char *name); +void list_addentry (GSList ** list, char *cmd, char *name); +int cmd_set (session *sess, char *tbuf, char *word[], char *word_eol[]); +int xchat_open_file (char *file, int flags, int mode, int xof_flags); +FILE *xchat_fopen_file (const char *file, const char *mode, int xof_flags); +#define XOF_DOMODE 1 +#define XOF_FULLPATH 2 + +#define STRUCT_OFFSET_STR(type,field) \ +( (unsigned int) (((char *) (&(((type *) NULL)->field)))- ((char *) NULL)) ) + +#define STRUCT_OFFSET_INT(type,field) \ +( (unsigned int) (((int *) (&(((type *) NULL)->field)))- ((int *) NULL)) ) + +#define P_OFFSET(field) STRUCT_OFFSET_STR(struct xchatprefs, field),sizeof(prefs.field) +#define P_OFFSETNL(field) STRUCT_OFFSET_STR(struct xchatprefs, field) +#define P_OFFINT(field) STRUCT_OFFSET_INT(struct xchatprefs, field),0 +#define P_OFFINTNL(field) STRUCT_OFFSET_INT(struct xchatprefs, field) + +struct prefs +{ + char *name; + unsigned short offset; + unsigned short len; + unsigned short type; +}; + +#define TYPE_STR 0 +#define TYPE_INT 1 +#define TYPE_BOOL 2 + +#endif diff --git a/src/common/chanopt.c b/src/common/chanopt.c new file mode 100644 index 00000000..a4fd8faa --- /dev/null +++ b/src/common/chanopt.c @@ -0,0 +1,431 @@ +/* per-channel/dialog settings :: /CHANOPT */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> + +#include "xchat.h" + +#include "cfgfiles.h" +#include "server.h" +#include "text.h" +#include "util.h" +#include "xchatc.h" + + +static GSList *chanopt_list = NULL; +static gboolean chanopt_open = FALSE; +static gboolean chanopt_changed = FALSE; + + +typedef struct +{ + char *name; + char *alias; /* old names from 2.8.4 */ + int offset; +} channel_options; + +#define S_F(xx) STRUCT_OFFSET_STR(struct session,xx) + +static const channel_options chanopt[] = +{ + {"alert_beep", "BEEP", S_F(alert_beep)}, + {"alert_taskbar", NULL, S_F(alert_taskbar)}, + {"alert_tray", "TRAY", S_F(alert_tray)}, + + {"text_hidejoinpart", "CONFMODE", S_F(text_hidejoinpart)}, + {"text_logging", NULL, S_F(text_logging)}, + {"text_scrollback", NULL, S_F(text_scrollback)}, +}; + +#undef S_F + +static char * +chanopt_value (guint8 val) +{ + switch (val) + { + case SET_OFF: + return "OFF"; + case SET_ON: + return "ON"; + default: + return "{unset}"; + } +} + +/* handle the /CHANOPT command */ + +int +chanopt_command (session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int dots, i = 0, j, p = 0; + guint8 val; + int offset = 2; + char *find; + gboolean quiet = FALSE; + int newval = -1; + + if (!strcmp (word[2], "-quiet")) + { + quiet = TRUE; + offset++; + } + + find = word[offset++]; + + if (word[offset][0]) + { + if (!strcasecmp (word[offset], "ON")) + newval = 1; + else if (!strcasecmp (word[offset], "OFF")) + newval = 0; + else if (word[offset][0] == 'u') + newval = SET_DEFAULT; + else + newval = atoi (word[offset]); + } + + if (!quiet) + PrintTextf (sess, "\002Network\002: %s \002Channel\002: %s\n", + sess->server->network ? server_get_network (sess->server, TRUE) : _("<none>"), + sess->channel[0] ? sess->channel : _("<none>")); + + while (i < sizeof (chanopt) / sizeof (channel_options)) + { + if (find[0] == 0 || match (find, chanopt[i].name) || (chanopt[i].alias && match (find, chanopt[i].alias))) + { + if (newval != -1) /* set new value */ + { + *(guint8 *)G_STRUCT_MEMBER_P(sess, chanopt[i].offset) = newval; + } + + if (!quiet) /* print value */ + { + strcpy (tbuf, chanopt[i].name); + p = strlen (tbuf); + + tbuf[p++] = 3; + tbuf[p++] = '2'; + + dots = 20 - strlen (chanopt[i].name); + + for (j = 0; j < dots; j++) + tbuf[p++] = '.'; + tbuf[p++] = 0; + + val = G_STRUCT_MEMBER (guint8, sess, chanopt[i].offset); + PrintTextf (sess, "%s\0033:\017 %s", tbuf, chanopt_value (val)); + } + } + i++; + } + + return TRUE; +} + +/* is a per-channel setting set? Or is it UNSET and + * the global version is set? */ + +gboolean +chanopt_is_set (unsigned int global, guint8 per_chan_setting) +{ + if (per_chan_setting == SET_DEFAULT) + return global; + + return per_chan_setting; +} + +/* additive version */ + +gboolean +chanopt_is_set_a (unsigned int global, guint8 per_chan_setting) +{ + if (per_chan_setting == SET_DEFAULT) + return global; + + return per_chan_setting || global; +} + +/* === below is LOADING/SAVING stuff only === */ + +typedef struct +{ + /* Per-Channel Alerts */ + /* use a byte, because we need a pointer to each element */ + guint8 alert_beep; + guint8 alert_taskbar; + guint8 alert_tray; + + /* Per-Channel Settings */ + guint8 text_hidejoinpart; + guint8 text_logging; + guint8 text_scrollback; + + char *network; + char *channel; + +} chanopt_in_memory; + + +static chanopt_in_memory * +chanopt_find (char *network, char *channel, gboolean add_new) +{ + GSList *list; + chanopt_in_memory *co; + int i; + + for (list = chanopt_list; list; list = list->next) + { + co = list->data; + if (!strcasecmp (co->channel, channel) && + !strcasecmp (co->network, network)) + return co; + } + + if (!add_new) + return NULL; + + /* allocate a new one */ + co = g_malloc0 (sizeof (chanopt_in_memory)); + co->channel = g_strdup (channel); + co->network = g_strdup (network); + + /* set all values to SET_DEFAULT */ + i = 0; + while (i < sizeof (chanopt) / sizeof (channel_options)) + { + *(guint8 *)G_STRUCT_MEMBER_P(co, chanopt[i].offset) = SET_DEFAULT; + i++; + } + + chanopt_list = g_slist_prepend (chanopt_list, co); + chanopt_changed = TRUE; + + return co; +} + +static void +chanopt_add_opt (chanopt_in_memory *co, char *var, int new_value) +{ + int i; + + i = 0; + while (i < sizeof (chanopt) / sizeof (channel_options)) + { + if (!strcmp (var, chanopt[i].name)) + { + *(guint8 *)G_STRUCT_MEMBER_P(co, chanopt[i].offset) = new_value; + + } + i++; + } +} + +/* load chanopt.conf from disk into our chanopt_list GSList */ + +static void +chanopt_load_all (void) +{ + int fh; + char buf[256]; + char *eq; + char *network = NULL; + chanopt_in_memory *current = NULL; + + /* 1. load the old file into our GSList */ + fh = xchat_open_file ("chanopt.conf", O_RDONLY, 0, 0); + if (fh != -1) + { + while (waitline (fh, buf, sizeof buf, FALSE) != -1) + { + eq = strchr (buf, '='); + if (!eq) + continue; + eq[0] = 0; + + if (eq != buf && eq[-1] == ' ') + eq[-1] = 0; + + if (!strcmp (buf, "network")) + { + g_free (network); + network = g_strdup (eq + 2); + } + else if (!strcmp (buf, "channel")) + { + current = chanopt_find (network, eq + 2, TRUE); + chanopt_changed = FALSE; + } + else + { + if (current) + chanopt_add_opt (current, buf, atoi (eq + 2)); + } + + } + close (fh); + g_free (network); + } +} + +void +chanopt_load (session *sess) +{ + int i; + guint8 val; + chanopt_in_memory *co; + char *network; + + if (sess->channel[0] == 0) + return; + + network = server_get_network (sess->server, FALSE); + if (!network) + return; + + if (!chanopt_open) + { + chanopt_open = TRUE; + chanopt_load_all (); + } + + co = chanopt_find (network, sess->channel, FALSE); + if (!co) + return; + + /* fill in all the sess->xxxxx fields */ + i = 0; + while (i < sizeof (chanopt) / sizeof (channel_options)) + { + val = G_STRUCT_MEMBER(guint8, co, chanopt[i].offset); + *(guint8 *)G_STRUCT_MEMBER_P(sess, chanopt[i].offset) = val; + i++; + } +} + +void +chanopt_save (session *sess) +{ + int i; + guint8 vals; + guint8 valm; + chanopt_in_memory *co; + char *network; + + if (sess->channel[0] == 0) + return; + + network = server_get_network (sess->server, FALSE); + if (!network) + return; + + /* 2. reconcile sess with what we loaded from disk */ + + co = chanopt_find (network, sess->channel, TRUE); + + i = 0; + while (i < sizeof (chanopt) / sizeof (channel_options)) + { + vals = G_STRUCT_MEMBER(guint8, sess, chanopt[i].offset); + valm = G_STRUCT_MEMBER(guint8, co, chanopt[i].offset); + + if (vals != valm) + { + *(guint8 *)G_STRUCT_MEMBER_P(co, chanopt[i].offset) = vals; + chanopt_changed = TRUE; + } + + i++; + } +} + +static void +chanopt_save_one_channel (chanopt_in_memory *co, int fh) +{ + int i; + char buf[256]; + guint8 val; + + snprintf (buf, sizeof (buf), "%s = %s\n", "network", co->network); + write (fh, buf, strlen (buf)); + + snprintf (buf, sizeof (buf), "%s = %s\n", "channel", co->channel); + write (fh, buf, strlen (buf)); + + i = 0; + while (i < sizeof (chanopt) / sizeof (channel_options)) + { + val = G_STRUCT_MEMBER (guint8, co, chanopt[i].offset); + if (val != SET_DEFAULT) + { + snprintf (buf, sizeof (buf), "%s = %d\n", chanopt[i].name, val); + write (fh, buf, strlen (buf)); + } + i++; + } +} + +void +chanopt_save_all (void) +{ + int i; + int num_saved; + int fh; + GSList *list; + chanopt_in_memory *co; + guint8 val; + + if (!chanopt_list || !chanopt_changed) + { + return; + } + + fh = xchat_open_file ("chanopt.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); + if (fh == -1) + { + return; + } + + for (num_saved = 0, list = chanopt_list; list; list = list->next) + { + co = list->data; + + i = 0; + while (i < sizeof (chanopt) / sizeof (channel_options)) + { + val = G_STRUCT_MEMBER (guint8, co, chanopt[i].offset); + /* not using global/default setting, must save */ + if (val != SET_DEFAULT) + { + if (num_saved != 0) + write (fh, "\n", 1); + + chanopt_save_one_channel (co, fh); + num_saved++; + goto cont; + } + i++; + } + +cont: + g_free (co->network); + g_free (co->channel); + g_free (co); + } + + close (fh); + + /* we're quiting, no need to free */ + + /*g_slist_free (chanopt_list); + chanopt_list = NULL; + + chanopt_open = FALSE; + chanopt_changed = FALSE;*/ +} diff --git a/src/common/chanopt.h b/src/common/chanopt.h new file mode 100644 index 00000000..90ca86c3 --- /dev/null +++ b/src/common/chanopt.h @@ -0,0 +1,6 @@ +int chanopt_command (session *sess, char *tbuf, char *word[], char *word_eol[]); +gboolean chanopt_is_set (unsigned int global, guint8 per_chan_setting); +gboolean chanopt_is_set_a (unsigned int global, guint8 per_chan_setting); +void chanopt_save_all (void); +void chanopt_save (session *sess); +void chanopt_load (session *sess); diff --git a/src/common/ctcp.c b/src/common/ctcp.c new file mode 100644 index 00000000..574cda79 --- /dev/null +++ b/src/common/ctcp.c @@ -0,0 +1,191 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> + +#include "xchat.h" +#include "cfgfiles.h" +#include "util.h" +#include "modes.h" +#include "outbound.h" +#include "ignore.h" +#include "inbound.h" +#include "dcc.h" +#include "text.h" +#include "ctcp.h" +#include "server.h" +#include "xchatc.h" + + +static void +ctcp_reply (session *sess, char *nick, char *word[], char *word_eol[], + char *conf) +{ + char tbuf[4096]; /* can receive 2048 from IRC, so this is enough */ + + conf = strdup (conf); + /* process %C %B etc */ + check_special_chars (conf, TRUE); + auto_insert (tbuf, sizeof (tbuf), conf, word, word_eol, "", "", word_eol[5], + server_get_network (sess->server, TRUE), "", "", nick); + free (conf); + handle_command (sess, tbuf, FALSE); +} + +static int +ctcp_check (session *sess, char *nick, char *word[], char *word_eol[], + char *ctcp) +{ + int ret = 0; + char *po; + struct popup *pop; + GSList *list = ctcp_list; + + po = strchr (ctcp, '\001'); + if (po) + *po = 0; + + po = strchr (word_eol[5], '\001'); + if (po) + *po = 0; + + while (list) + { + pop = (struct popup *) list->data; + if (!strcasecmp (ctcp, pop->name)) + { + ctcp_reply (sess, nick, word, word_eol, pop->cmd); + ret = 1; + } + list = list->next; + } + return ret; +} + +void +ctcp_handle (session *sess, char *to, char *nick, char *ip, + char *msg, char *word[], char *word_eol[], int id) +{ + char *po; + session *chansess; + server *serv = sess->server; + char outbuf[1024]; + int ctcp_offset = 2; + + if (serv->have_idmsg && (word[4][1] == '+' || word[4][1] == '-') ) + ctcp_offset = 3; + + /* consider DCC to be different from other CTCPs */ + if (!strncasecmp (msg, "DCC", 3)) + { + /* but still let CTCP replies override it */ + if (!ctcp_check (sess, nick, word, word_eol, word[4] + ctcp_offset)) + { + if (!ignore_check (word[1], IG_DCC)) + handle_dcc (sess, nick, word, word_eol); + } + return; + } + + /* consider ACTION to be different from other CTCPs. Check + ignore as if it was a PRIV/CHAN. */ + if (!strncasecmp (msg, "ACTION ", 7)) + { + if (is_channel (serv, to)) + { + /* treat a channel action as a CHAN */ + if (ignore_check (word[1], IG_CHAN)) + return; + } else + { + /* treat a private action as a PRIV */ + if (ignore_check (word[1], IG_PRIV)) + return; + } + + /* but still let CTCP replies override it */ + if (ctcp_check (sess, nick, word, word_eol, word[4] + ctcp_offset)) + goto generic; + + inbound_action (sess, to, nick, ip, msg + 7, FALSE, id); + return; + } + + if (ignore_check (word[1], IG_CTCP)) + return; + + if (!strcasecmp (msg, "VERSION") && !prefs.hidever) + { + snprintf (outbuf, sizeof (outbuf), "VERSION xchat "PACKAGE_VERSION" %s", + get_cpu_str ()); + serv->p_nctcp (serv, nick, outbuf); + } + + if (!ctcp_check (sess, nick, word, word_eol, word[4] + ctcp_offset)) + { + if (!strncasecmp (msg, "SOUND", 5)) + { + po = strchr (word[5], '\001'); + if (po) + po[0] = 0; + + if (is_channel (sess->server, to)) + { + chansess = find_channel (sess->server, to); + if (!chansess) + chansess = sess; + + EMIT_SIGNAL (XP_TE_CTCPSNDC, chansess, word[5], + nick, to, NULL, 0); + } else + { + EMIT_SIGNAL (XP_TE_CTCPSND, sess->server->front_session, word[5], + nick, NULL, NULL, 0); + } + + /* don't let IRCers specify path */ +#ifdef WIN32 + if (strchr (word[5], '/') == NULL && strchr (word[5], '\\') == NULL) +#else + if (strchr (word[5], '/') == NULL) +#endif + sound_play (word[5], TRUE); + return; + } + } + +generic: + po = strchr (msg, '\001'); + if (po) + po[0] = 0; + + if (!is_channel (sess->server, to)) + { + EMIT_SIGNAL (XP_TE_CTCPGEN, sess->server->front_session, msg, nick, + NULL, NULL, 0); + } else + { + chansess = find_channel (sess->server, to); + if (!chansess) + chansess = sess; + EMIT_SIGNAL (XP_TE_CTCPGENC, chansess, msg, nick, to, NULL, 0); + } +} diff --git a/src/common/ctcp.h b/src/common/ctcp.h new file mode 100644 index 00000000..9acd80e3 --- /dev/null +++ b/src/common/ctcp.h @@ -0,0 +1,6 @@ +#ifndef XCHAT_CTCP_H +#define XCHAT_CTCP_H + +void ctcp_handle (session *sess, char *to, char *nick, char *ip, char *msg, char *word[], char *word_eol[], int id); + +#endif diff --git a/src/common/dbus/Makefile.am b/src/common/dbus/Makefile.am new file mode 100644 index 00000000..05ee9de6 --- /dev/null +++ b/src/common/dbus/Makefile.am @@ -0,0 +1,56 @@ +noinst_LIBRARIES = libxchatdbus.a +libxchatdbus_a_SOURCES = \ + dbus-plugin.c \ + dbus-plugin.h \ + dbus-client.c \ + dbus-client.h + +EXTRA_DIST = \ + remote-object.xml \ + apps_xchat_url_handler.schemas \ + marshallers.list \ + example.py \ + org.xchat.service.service.in \ + README + +BUILT_SOURCES = \ + marshallers.h \ + remote-object-glue.h + +CLEANFILES = $(BUILT_SOURCES) + +INCLUDES = $(COMMON_CFLAGS) $(DBUS_CFLAGS) + +noinst_PROGRAMS = example +example_SOURCES = example.c +example_LDADD = $(DBUS_LIBS) $(GLIB_LIBS) + +remote-object-glue.h: remote-object.xml + $(LIBTOOL) --mode=execute $(DBUS_BINDING_TOOL) --prefix=remote_object --mode=glib-server --output=$@ $< + +marshallers.h: marshallers.list + $(LIBTOOL) --mode=execute $(GLIB_GENMARSHAL) --header --body $< > $@ + +# Dbus service file +servicedir = $(DBUS_SERVICES_DIR) +service_in_files = org.xchat.service.service.in +service_DATA = $(service_in_files:.service.in=.service) + +# Rule to make the service file with bindir expanded +$(service_DATA): $(service_in_files) Makefile + @sed -e "s|\@bindir\@|$(bindir)|" $< > $@ + +if DO_GCONF +GCONF_SCHEMA_CONFIG_SOURCE = `$(GCONFTOOL) --get-default-source` +GCONF_SCHEMA_FILE_DIR = $(sysconfdir)/gconf/schemas +schemadir = $(GCONF_SCHEMA_FILE_DIR) +schema_DATA = apps_xchat_url_handler.schemas +install-data-local: + if test -z "$(DESTDIR)" ; then \ + for p in $(schema_DATA) ; do \ + GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE) $(GCONFTOOL) --makefile-install-rule $(srcdir)/$$p; \ + done \ + fi +else +install-data-local: +endif diff --git a/src/common/dbus/README b/src/common/dbus/README new file mode 100644 index 00000000..d61cf4e0 --- /dev/null +++ b/src/common/dbus/README @@ -0,0 +1,198 @@ +For more help you can see the xchat plugin interface documentation. +http://www.xchat.org/docs/plugin20.html +WARNING: The dbus interface may change in the future. + +You can use the "/org/xchat/Remote" object with interface "org.xchat.plugin", +but his context can be changed by other clients at any moment and +you may receive signal asked by other clients. So for more complex usage it's +better to get your own remote object. Using "Connect" method on interface +"org.xchat.connection" + +Available methods on org.xchat.connection interface: + +"Connect" + - Parameters: + - gchar*: filename + - gchar*: name + - gchar*: description + - gchar*: version + - Returns: + - gchar*: Your own object's path. + +"Disconnect" + No parameter, no return value. It frees your remote object. + +Available methods on org.xchat.plugin interface: + +"Command" + - Parameters: + - gchar*: the command name without the "/". (e.g. "nick pseudo") + +"Print" + - Parameters: + - gchar*: text to print on the xchat window. + +"FindContext" + - Parameters: + - gchar*: the server name. Can be NULL. + - gchar*: the channel name. Can be NULL. + - Returns: + - guint: context ID + +"GetContext" + - Returns: + - guint: current context's ID + +"SetContext" + - Parameters: + - guint: context ID to switch, returned by "FindContext" or "GetContext" + - Returns: + - gboolean: 1 for success, 0 for failure. + +"GetInfo" + - Parameters: + - gchar*: ID of the information you want. + - Returns: + - gchar*: information you requested. + +"GetPrefs" + - Parameters: + - gchar*: Setting name required. + - Returns: + - int: 0-Failed 1-Returned a string 2-Returned an Integer + 3-Returned a Boolean. + - gchar*: the information requested if it's a string. + - int: the information requested if it's a integer or boolean. + +"HookCommand" + - Parameters: + - gchar*: Name of the command (without the forward slash). + - int: Priority of this command. + - gchar*: String of text to display when the user executes /help + for this command. May be NULL if you're lazy. + - int: Value to returns when the command is catched. See XCHAT_EAT_*. + - Returns: + - guint: The ID of the hook. + +"HookServer" + - Parameters: + - gchar*: Name of the server event. + - int: Priority of this command. + - int: Value to returns when the command is catched. See XCHAT_EAT_*. + - Returns: + - guint: The ID of the hook. + +"HookPrint" + - Parameters: + - gchar*: Name of the print event. + - int: Priority of this command. + - int: Value to returns when the command is catched. See XCHAT_EAT_*. + - Returns: + - guint: The ID of the hook. + +"Unhook" + - Parameters: + - guint: ID of the hook to unhook. + (the return value of "HookCommand", "HookServer" or "HookPrint") + +"ListGet" + - Parameters: + - gchar*: The list name. + - Returns: + - guint: List ID. + +"ListNext" + - Parameters: + - guint: List ID returned by "ListGet". + - Returns: + - gboolean: says if there is no more item in the list. + +"ListStr" + - Parameters: + - guint: List ID returned by "ListGet". + - gchar*: Name of the information needed. + - Returns: + - gchar*: The information requested. +Warning: "context" attribut of "channels" list should be get with "ListInt" + +"ListInt" + - Parameters: + - guint: List ID returned by "ListGet". + - gchar*: Name of the information needed. + - Returns: + - guint: The information requested. + +"ListTime" + - Parameters: + - guint: List ID returned by "ListGet". + - gchar*: Name of the information needed. + - Returns: + - guint64: The information requested. + +"ListFields" + - Parameters: + - gchar*: The list name. + - Returns: + - gchar**: information names in this list. + +"ListFree" + - Parameters: + - guint: List ID returned by "ListGet". + +"EmitPrint" + - Parameters: + - gchar*: Text event to print. + - gchar**: NULL terminated array of string. + - Returns: + - gboolean: 1-Success 0-Failure. + +"Nickcmp" + - Parameters: + - gchar*: String to compare. + - gchar*: String to compare. + - Returns: + - int: An integer less than, equal to, or greater than zero if s1 is found, + respectively, to be less than, to match, or be greater than s2. + +"Strip" + - Parameters: + - gchar*: String to strip. + - int: Length of the string (or -1 for NULL terminated). + - int: Bit-field of flags: 0-Strip mIRC colors, 1-Strip text attributes. + - Returns: + - gchar*: striped string. + +"SendModes" + - Parameters: + - gchar**: NULL terminated array of targets (strings). The names of people + whom the action will be performed on. + - int: Maximum modes to send per line. + - gchar: Mode sign, '-' or '+'. + - gchar: Mode char, e.g. 'o' for Ops. + + +Available signals: + +"ServerSignal" + - Parameters: + - gchar**: word returned by xchat. + - gchar**: word_eol returned bu xchat. + - guint: the ID of the hook. (the return value of "HookServer"). + - guint: the ID of the context where the event come from. + +"CommandSignal" + - Parameters: + - gchar**: word returned by xchat. + - gchar**: word_eol returned bu xchat. + - guint: the ID of the hook. (the return value of "HookCommand"). + - guint: the ID of the context where the event come from. + +"PrintSignal" + - Parameters: + - gchar**: word returned by xchat. + - guint: the ID of the hook. (the return value of "HookPrint"). + - guint: the ID of the context where the event come from. + +"UnloadSignal" + emited when the user asks to unload your program. + Please exit(0); when received ! diff --git a/src/common/dbus/apps_xchat_url_handler.schemas b/src/common/dbus/apps_xchat_url_handler.schemas new file mode 100644 index 00000000..10ac948d --- /dev/null +++ b/src/common/dbus/apps_xchat_url_handler.schemas @@ -0,0 +1,37 @@ +<gconfschemafile> + <schemalist> + <schema> + <key>/schemas/desktop/gnome/url-handlers/irc/command</key> + <applyto>/desktop/gnome/url-handlers/irc/command</applyto> + <owner>xchat</owner> + <type>string</type> + <default>xchat --existing --url=%u</default> + <locale name="C"> + <short>The handler for "irc://" URLs</short> + </locale> + </schema> + + <schema> + <key>/schemas/desktop/gnome/url-handlers/irc/enabled</key> + <applyto>/desktop/gnome/url-handlers/irc/enabled</applyto> + <owner>xchat</owner> + <type>bool</type> + <default>true</default> + <locale name="C"> + <short>Set it at TRUE if you want it activated</short> + </locale> + </schema> + + <schema> + <key>/schemas/desktop/gnome/url-handlers/irc/needs_terminal</key> + <applyto>/desktop/gnome/url-handlers/irc/needs_terminal</applyto> + <owner>xchat</owner> + <type>bool</type> + <default>false</default> + <locale name="C"> + <short>Run xchat in a terminal?</short> + </locale> + </schema> + + </schemalist> +</gconfschemafile> diff --git a/src/common/dbus/dbus-client.c b/src/common/dbus/dbus-client.c new file mode 100644 index 00000000..ac6bf6dc --- /dev/null +++ b/src/common/dbus/dbus-client.c @@ -0,0 +1,118 @@ +/* dbus-client.c - XChat command-line options for D-Bus + * Copyright (C) 2006 Claessens Xavier + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + * Claessens Xavier + * xclaesse@gmail.com + */ + +#include <dbus/dbus-glib.h> +#include "dbus-client.h" +#include "../xchat.h" +#include "../xchatc.h" + +#define DBUS_SERVICE "org.xchat.service" +#define DBUS_REMOTE "/org/xchat/Remote" +#define DBUS_REMOTE_INTERFACE "org.xchat.plugin" + +static void +write_error (char *message, + GError **error) +{ + if (error == NULL || *error == NULL) { + return; + } + g_printerr ("%s: %s\n", message, (*error)->message); + g_clear_error (error); +} + +void +xchat_remote (void) +/* TODO: dbus_g_connection_unref (connection) are commented because it makes + * dbus to crash. Fixed in dbus >=0.70 ?!? + * https://launchpad.net/distros/ubuntu/+source/dbus/+bug/54375 + */ +{ + DBusGConnection *connection; + DBusGProxy *dbus = NULL; + DBusGProxy *remote_object = NULL; + gboolean xchat_running; + GError *error = NULL; + char *command = NULL; + + /* GnomeVFS >=2.15 uses D-Bus and threads, so threads should be + * initialised before opening for the first time a D-Bus connection */ + if (!g_thread_supported ()) { + g_thread_init (NULL); + } + dbus_g_thread_init (); + + /* if there is nothing to do, return now. */ + if (!arg_existing || !(arg_url || arg_command)) { + return; + } + + arg_dont_autoconnect = TRUE; + + connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error); + if (!connection) { + write_error (_("Couldn't connect to session bus"), &error); + return; + } + + /* Checks if xchat is already running */ + dbus = dbus_g_proxy_new_for_name (connection, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS); + if (!dbus_g_proxy_call (dbus, "NameHasOwner", &error, + G_TYPE_STRING, DBUS_SERVICE, + G_TYPE_INVALID, + G_TYPE_BOOLEAN, &xchat_running, + G_TYPE_INVALID)) { + write_error (_("Failed to complete NameHasOwner"), &error); + xchat_running = FALSE; + } + g_object_unref (dbus); + + if (!xchat_running) { + //dbus_g_connection_unref (connection); + return; + } + + remote_object = dbus_g_proxy_new_for_name (connection, + DBUS_SERVICE, + DBUS_REMOTE, + DBUS_REMOTE_INTERFACE); + + if (arg_url) { + command = g_strdup_printf ("url %s", arg_url); + } else if (arg_command) { + command = g_strdup (arg_command); + } + + if (command) { + if (!dbus_g_proxy_call (remote_object, "Command", + &error, + G_TYPE_STRING, command, + G_TYPE_INVALID,G_TYPE_INVALID)) { + write_error (_("Failed to complete Command"), &error); + } + g_free (command); + } + + exit (0); +} diff --git a/src/common/dbus/dbus-client.h b/src/common/dbus/dbus-client.h new file mode 100644 index 00000000..cafc2b71 --- /dev/null +++ b/src/common/dbus/dbus-client.h @@ -0,0 +1,27 @@ +/* dbus-client.h - XChat command-line options for D-Bus + * Copyright (C) 2006 Claessens Xavier + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + * Claessens Xavier + * xclaesse@gmail.com + */ + +#ifndef __DBUS_PLUGIN_H__ +#define __DBUS_PLUGIN_H__ + +void xchat_remote (void); + +#endif /* __DBUS_PLUGIN_H__ */ diff --git a/src/common/dbus/dbus-plugin.c b/src/common/dbus/dbus-plugin.c new file mode 100644 index 00000000..012812cc --- /dev/null +++ b/src/common/dbus/dbus-plugin.c @@ -0,0 +1,1087 @@ +/* dbus-plugin.c - xchat plugin for remote access using D-Bus + * Copyright (C) 2006 Claessens Xavier + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + * Claessens Xavier + * xclaesse@gmail.com + */ + +#define DBUS_API_SUBJECT_TO_CHANGE + +#include <config.h> +#include <dbus/dbus-glib.h> +#include <dbus/dbus-glib-lowlevel.h> +#include <glib/gi18n.h> +#include "../xchat-plugin.h" + +#define PNAME _("remote access") +#define PDESC _("plugin for remote access using DBUS") +#define PVERSION "" + +#define DBUS_SERVICE "org.xchat.service" +#define DBUS_OBJECT_PATH "/org/xchat" + +static xchat_plugin *ph; +static guint last_context_id = 0; +static GList *contexts = NULL; +static GHashTable *clients = NULL; +static DBusGConnection *connection; + +typedef struct RemoteObject RemoteObject; +typedef struct RemoteObjectClass RemoteObjectClass; + +GType Remote_object_get_type (void); + +struct RemoteObject +{ + GObject parent; + + guint last_hook_id; + guint last_list_id; + xchat_context *context; + char *dbus_path; + char *filename; + GHashTable *hooks; + GHashTable *lists; + void *handle; +}; + +struct RemoteObjectClass +{ + GObjectClass parent; +}; + +typedef struct +{ + guint id; + int return_value; + xchat_hook *hook; + RemoteObject *obj; +} HookInfo; + +typedef struct +{ + guint id; + xchat_context *context; +} ContextInfo; + +enum +{ + SERVER_SIGNAL, + COMMAND_SIGNAL, + PRINT_SIGNAL, + UNLOAD_SIGNAL, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +#define REMOTE_TYPE_OBJECT (remote_object_get_type ()) +#define REMOTE_OBJECT(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), REMOTE_TYPE_OBJECT, RemoteObject)) +#define REMOTE_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), REMOTE_TYPE_OBJECT, RemoteObjectClass)) +#define REMOTE_IS_OBJECT(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), REMOTE_TYPE_OBJECT)) +#define REMOTE_IS_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), REMOTE_TYPE_OBJECT)) +#define REMOTE_OBJECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), REMOTE_TYPE_OBJECT, RemoteObjectClass)) +#define REMOTE_OBJECT_ERROR (remote_object_error_quark ()) +#define REMOTE_TYPE_ERROR (remote_object_error_get_type ()) + +G_DEFINE_TYPE (RemoteObject, remote_object, G_TYPE_OBJECT) + +/* Available services */ + +static gboolean remote_object_connect (RemoteObject *obj, + const char *filename, + const char *name, + const char *desc, + const char *version, + DBusGMethodInvocation *context); + +static gboolean remote_object_disconnect (RemoteObject *obj, + DBusGMethodInvocation *context); + +static gboolean remote_object_command (RemoteObject *obj, + const char *command, + GError **error); + +static gboolean remote_object_print (RemoteObject *obj, + const char *text, + GError **error); + +static gboolean remote_object_find_context (RemoteObject *obj, + const char *server, + const char *channel, + guint *ret_id, + GError **error); + +static gboolean remote_object_get_context (RemoteObject *obj, + guint *ret_id, + GError **error); + +static gboolean remote_object_set_context (RemoteObject *obj, + guint id, + gboolean *ret, + GError **error); + +static gboolean remote_object_print (RemoteObject *obj, + const char *text, + GError **error); + +static gboolean remote_object_get_info (RemoteObject *obj, + const char *id, + char **ret_info, + GError **error); + +static gboolean remote_object_get_prefs (RemoteObject *obj, + const char *name, + int *ret_type, + char **ret_str, + int *ret_int, + GError **error); + +static gboolean remote_object_hook_command (RemoteObject *obj, + const char *name, + int pri, + const char *help_text, + int return_value, + guint *ret_id, + GError **error); + +static gboolean remote_object_hook_server (RemoteObject *obj, + const char *name, + int pri, + int return_value, + guint *ret_id, + GError **error); + +static gboolean remote_object_hook_print (RemoteObject *obj, + const char *name, + int pri, + int return_value, + guint *ret_id, + GError **error); + +static gboolean remote_object_unhook (RemoteObject *obj, + guint id, + GError **error); + +static gboolean remote_object_list_get (RemoteObject *obj, + const char *name, + guint *ret_id, + GError **error); + +static gboolean remote_object_list_next (RemoteObject *obj, + guint id, + gboolean *ret, + GError **error); + +static gboolean remote_object_list_str (RemoteObject *obj, + guint id, + const char *name, + char **ret_str, + GError **error); + +static gboolean remote_object_list_int (RemoteObject *obj, + guint id, + const char *name, + int *ret_int, + GError **error); + +static gboolean remote_object_list_time (RemoteObject *obj, + guint id, + const char *name, + guint64 *ret_time, + GError **error); + +static gboolean remote_object_list_fields (RemoteObject *obj, + const char *name, + char ***ret, + GError **error); + +static gboolean remote_object_list_free (RemoteObject *obj, + guint id, + GError **error); + +static gboolean remote_object_emit_print (RemoteObject *obj, + const char *event_name, + const char *args[], + gboolean *ret, + GError **error); + +static gboolean remote_object_nickcmp (RemoteObject *obj, + const char *nick1, + const char *nick2, + int *ret, + GError **error); + +static gboolean remote_object_strip (RemoteObject *obj, + const char *str, + int len, + int flag, + char **ret_str, + GError **error); + +static gboolean remote_object_send_modes (RemoteObject *obj, + const char *targets[], + int modes_per_line, + char sign, + char mode, + GError **error); + +#include "remote-object-glue.h" +#include "marshallers.h" + +/* Useful functions */ + +static char** build_list (char *word[]); +static guint context_list_find_id (xchat_context *context); +static xchat_context* context_list_find_context (guint id); + +/* Remote Object */ + +static void +hook_info_destroy (gpointer data) +{ + HookInfo *info = (HookInfo*)data; + + if (info == NULL) { + return; + } + xchat_unhook (ph, info->hook); + g_free (info); +} + +static void +list_info_destroy (gpointer data) +{ + xchat_list_free (ph, (xchat_list*)data); +} + +static void +remote_object_finalize (GObject *obj) +{ + RemoteObject *self = (RemoteObject*)obj; + + g_hash_table_destroy (self->lists); + g_hash_table_destroy (self->hooks); + g_free (self->dbus_path); + g_free (self->filename); + xchat_plugingui_remove (ph, self->handle); + + G_OBJECT_CLASS (remote_object_parent_class)->finalize (obj); +} + +static void +remote_object_init (RemoteObject *obj) +{ + obj->hooks = + g_hash_table_new_full (g_int_hash, + g_int_equal, + NULL, + hook_info_destroy); + + obj->lists = + g_hash_table_new_full (g_int_hash, + g_int_equal, + g_free, + list_info_destroy); + obj->dbus_path = NULL; + obj->filename = NULL; + obj->last_hook_id = 0; + obj->last_list_id = 0; + obj->context = xchat_get_context (ph); +} + +static void +remote_object_class_init (RemoteObjectClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = remote_object_finalize; + + signals[SERVER_SIGNAL] = + g_signal_new ("server_signal", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_user_marshal_VOID__POINTER_POINTER_UINT_UINT, + G_TYPE_NONE, + 4, G_TYPE_STRV, G_TYPE_STRV, G_TYPE_UINT, G_TYPE_UINT); + + signals[COMMAND_SIGNAL] = + g_signal_new ("command_signal", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_user_marshal_VOID__POINTER_POINTER_UINT_UINT, + G_TYPE_NONE, + 4, G_TYPE_STRV, G_TYPE_STRV, G_TYPE_UINT, G_TYPE_UINT); + + signals[PRINT_SIGNAL] = + g_signal_new ("print_signal", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_user_marshal_VOID__POINTER_POINTER_UINT_UINT, + G_TYPE_NONE, + 3, G_TYPE_STRV, G_TYPE_UINT, G_TYPE_UINT); + + signals[UNLOAD_SIGNAL] = + g_signal_new ("unload_signal", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); +} + +/* Implementation of services */ + +static gboolean +remote_object_connect (RemoteObject *obj, + const char *filename, + const char *name, + const char *desc, + const char *version, + DBusGMethodInvocation *context) +{ + static guint count = 0; + char *sender, *path; + RemoteObject *remote_object; + + sender = dbus_g_method_get_sender (context); + remote_object = g_hash_table_lookup (clients, sender); + if (remote_object != NULL) { + dbus_g_method_return (context, remote_object->dbus_path); + g_free (sender); + return TRUE; + } + path = g_strdup_printf (DBUS_OBJECT_PATH"/%d", count++); + remote_object = g_object_new (REMOTE_TYPE_OBJECT, NULL); + remote_object->dbus_path = path; + remote_object->filename = g_path_get_basename (filename); + remote_object->handle = xchat_plugingui_add (ph, + remote_object->filename, + name, + desc, + version, NULL); + dbus_g_connection_register_g_object (connection, + path, + G_OBJECT (remote_object)); + + g_hash_table_insert (clients, + sender, + remote_object); + + dbus_g_method_return (context, path); + return TRUE; +} + +static gboolean +remote_object_disconnect (RemoteObject *obj, + DBusGMethodInvocation *context) +{ + char *sender; + + sender = dbus_g_method_get_sender (context); + g_hash_table_remove (clients, sender); + g_free (sender); + + dbus_g_method_return (context); + return TRUE; +} + +static gboolean +remote_object_command (RemoteObject *obj, + const char *command, + GError **error) +{ + if (xchat_set_context (ph, obj->context)) { + xchat_command (ph, command); + } + return TRUE; +} + +static gboolean +remote_object_print (RemoteObject *obj, + const char *text, + GError **error) +{ + if (xchat_set_context (ph, obj->context)) { + xchat_print (ph, text); + } + return TRUE; +} + +static gboolean +remote_object_find_context (RemoteObject *obj, + const char *server, + const char *channel, + guint *ret_id, + GError **error) +{ + xchat_context *context; + + if (*server == '\0') { + server = NULL; + } + if (*channel == '\0') { + channel = NULL; + } + context = xchat_find_context (ph, server, channel); + *ret_id = context_list_find_id (context); + + return TRUE; +} + +static gboolean +remote_object_get_context (RemoteObject *obj, + guint *ret_id, + GError **error) +{ + *ret_id = context_list_find_id (obj->context); + return TRUE; +} + +static gboolean +remote_object_set_context (RemoteObject *obj, + guint id, + gboolean *ret, + GError **error) +{ + xchat_context *context; + + context = context_list_find_context (id); + if (context == NULL) { + *ret = FALSE; + return TRUE; + } + obj->context = context; + *ret = TRUE; + + return TRUE; +} + +static gboolean +remote_object_get_info (RemoteObject *obj, + const char *id, + char **ret_info, + GError **error) +{ + /* win_ptr is a GtkWindow* casted to char* and will crash + * D-Bus if we send it as a string */ + if (!xchat_set_context (ph, obj->context) || + g_str_equal (id, "win_ptr")) { + *ret_info = NULL; + return TRUE; + } + *ret_info = g_strdup (xchat_get_info (ph, id)); + return TRUE; +} + +static gboolean +remote_object_get_prefs (RemoteObject *obj, + const char *name, + int *ret_type, + char **ret_str, + int *ret_int, + GError **error) +{ + const char *str; + + if (!xchat_set_context (ph, obj->context)) { + *ret_type = 0; + return TRUE; + } + *ret_type = xchat_get_prefs (ph, name, &str, ret_int); + *ret_str = g_strdup (str); + + return TRUE; +} + +static int +server_hook_cb (char *word[], + char *word_eol[], + void *userdata) +{ + HookInfo *info = (HookInfo*)userdata; + char **arg1; + char **arg2; + + arg1 = build_list (word + 1); + arg2 = build_list (word_eol + 1); + info->obj->context = xchat_get_context (ph); + g_signal_emit (info->obj, + signals[SERVER_SIGNAL], + 0, + arg1, arg2, info->id, + context_list_find_id (info->obj->context)); + g_strfreev (arg1); + g_strfreev (arg2); + + return info->return_value; +} + +static int +command_hook_cb (char *word[], + char *word_eol[], + void *userdata) +{ + HookInfo *info = (HookInfo*)userdata; + char **arg1; + char **arg2; + + arg1 = build_list (word + 1); + arg2 = build_list (word_eol + 1); + info->obj->context = xchat_get_context (ph); + g_signal_emit (info->obj, + signals[COMMAND_SIGNAL], + 0, + arg1, arg2, info->id, + context_list_find_id (info->obj->context)); + g_strfreev (arg1); + g_strfreev (arg2); + + return info->return_value; +} + +static int +print_hook_cb (char *word[], + void *userdata) +{ + HookInfo *info = (HookInfo*)userdata; + char **arg1; + + arg1 = build_list (word + 1); + info->obj->context = xchat_get_context (ph); + g_signal_emit (info->obj, + signals[PRINT_SIGNAL], + 0, + arg1, info->id, + context_list_find_id (info->obj->context)); + g_strfreev (arg1); + + return info->return_value; +} + +static gboolean +remote_object_hook_command (RemoteObject *obj, + const char *name, + int priority, + const char *help_text, + int return_value, + guint *ret_id, + GError **error) +{ + HookInfo *info; + + info = g_new0 (HookInfo, 1); + info->obj = obj; + info->return_value = return_value; + info->id = ++obj->last_hook_id; + info->hook = xchat_hook_command (ph, + name, + priority, + command_hook_cb, + help_text, + info); + g_hash_table_insert (obj->hooks, &info->id, info); + *ret_id = info->id; + + return TRUE; +} + +static gboolean +remote_object_hook_server (RemoteObject *obj, + const char *name, + int priority, + int return_value, + guint *ret_id, + GError **error) +{ + HookInfo *info; + + info = g_new0 (HookInfo, 1); + info->obj = obj; + info->return_value = return_value; + info->id = ++obj->last_hook_id; + info->hook = xchat_hook_server (ph, + name, + priority, + server_hook_cb, + info); + g_hash_table_insert (obj->hooks, &info->id, info); + *ret_id = info->id; + + return TRUE; +} + +static gboolean +remote_object_hook_print (RemoteObject *obj, + const char *name, + int priority, + int return_value, + guint *ret_id, + GError **error) +{ + HookInfo *info; + + info = g_new0 (HookInfo, 1); + info->obj = obj; + info->return_value = return_value; + info->id = ++obj->last_hook_id; + info->hook = xchat_hook_print (ph, + name, + priority, + print_hook_cb, + info); + g_hash_table_insert (obj->hooks, &info->id, info); + *ret_id = info->id; + + return TRUE; +} + +static gboolean +remote_object_unhook (RemoteObject *obj, + guint id, + GError **error) +{ + g_hash_table_remove (obj->hooks, &id); + return TRUE; +} + +static gboolean +remote_object_list_get (RemoteObject *obj, + const char *name, + guint *ret_id, + GError **error) +{ + xchat_list *xlist; + guint *id; + + if (!xchat_set_context (ph, obj->context)) { + *ret_id = 0; + return TRUE; + } + xlist = xchat_list_get (ph, name); + if (xlist == NULL) { + *ret_id = 0; + return TRUE; + } + id = g_new (guint, 1); + *id = ++obj->last_list_id; + *ret_id = *id; + g_hash_table_insert (obj->lists, + id, + xlist); + + return TRUE; +} + +static gboolean +remote_object_list_next (RemoteObject *obj, + guint id, + gboolean *ret, + GError **error) +{ + xchat_list *xlist; + + xlist = g_hash_table_lookup (obj->lists, &id); + if (xlist == NULL) { + *ret = FALSE; + return TRUE; + } + *ret = xchat_list_next (ph, xlist); + + return TRUE; +} + +static gboolean +remote_object_list_str (RemoteObject *obj, + guint id, + const char *name, + char **ret_str, + GError **error) +{ + xchat_list *xlist; + + xlist = g_hash_table_lookup (obj->lists, &id); + if (xlist == NULL && !xchat_set_context (ph, obj->context)) { + *ret_str = NULL; + return TRUE; + } + if (g_str_equal (name, "context")) { + *ret_str = NULL; + return TRUE; + } + *ret_str = g_strdup (xchat_list_str (ph, xlist, name)); + + return TRUE; +} + +static gboolean +remote_object_list_int (RemoteObject *obj, + guint id, + const char *name, + int *ret_int, + GError **error) +{ + xchat_list *xlist; + + xlist = g_hash_table_lookup (obj->lists, &id); + if (xlist == NULL && !xchat_set_context (ph, obj->context)) { + *ret_int = -1; + return TRUE; + } + if (g_str_equal (name, "context")) { + xchat_context *context; + context = (xchat_context*)xchat_list_str (ph, xlist, name); + *ret_int = context_list_find_id (context); + } else { + *ret_int = xchat_list_int (ph, xlist, name); + } + + return TRUE; +} + +static gboolean +remote_object_list_time (RemoteObject *obj, + guint id, + const char *name, + guint64 *ret_time, + GError **error) +{ + xchat_list *xlist; + + xlist = g_hash_table_lookup (obj->lists, &id); + if (xlist == NULL) { + *ret_time = (guint64) -1; + return TRUE; + } + *ret_time = xchat_list_time (ph, xlist, name); + + return TRUE; +} + +static gboolean +remote_object_list_fields (RemoteObject *obj, + const char *name, + char ***ret, + GError **error) +{ + *ret = g_strdupv ((char**)xchat_list_fields (ph, name)); + if (*ret == NULL) { + *ret = g_new0 (char*, 1); + } + return TRUE; +} + +static gboolean +remote_object_list_free (RemoteObject *obj, + guint id, + GError **error) +{ + g_hash_table_remove (obj->lists, &id); + return TRUE; +} + +static gboolean +remote_object_emit_print (RemoteObject *obj, + const char *event_name, + const char *args[], + gboolean *ret, + GError **error) +{ + const char *argv[4] = {NULL, NULL, NULL, NULL}; + int i; + + for (i = 0; i < 4 && args[i] != NULL; i++) { + argv[i] = args[i]; + } + + *ret = xchat_set_context (ph, obj->context); + if (*ret) { + *ret = xchat_emit_print (ph, event_name, argv[0], argv[1], + argv[2], argv[3]); + } + + return TRUE; +} + +static gboolean +remote_object_nickcmp (RemoteObject *obj, + const char *nick1, + const char *nick2, + int *ret, + GError **error) +{ + xchat_set_context (ph, obj->context); + *ret = xchat_nickcmp (ph, nick1, nick2); + return TRUE; +} + +static gboolean +remote_object_strip (RemoteObject *obj, + const char *str, + int len, + int flag, + char **ret_str, + GError **error) +{ + *ret_str = xchat_strip (ph, str, len, flag); + return TRUE; +} + +static gboolean +remote_object_send_modes (RemoteObject *obj, + const char *targets[], + int modes_per_line, + char sign, + char mode, + GError **error) +{ + if (xchat_set_context (ph, obj->context)) { + xchat_send_modes (ph, targets, + g_strv_length ((char**)targets), + modes_per_line, + sign, mode); + } + return TRUE; +} + +/* DBUS stuffs */ + +static void +name_owner_changed (DBusGProxy *driver_proxy, + const char *name, + const char *old_owner, + const char *new_owner, + void *user_data) +{ + if (*new_owner == '\0') { + /* this name has vanished */ + g_hash_table_remove (clients, name); + } +} + +static gboolean +init_dbus (void) +{ + DBusGProxy *proxy; + RemoteObject *remote; + guint request_name_result; + GError *error = NULL; + + dbus_g_object_type_install_info (REMOTE_TYPE_OBJECT, + &dbus_glib_remote_object_object_info); + + connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error); + if (connection == NULL) { + xchat_printf (ph, _("Couldn't connect to session bus: %s\n"), + error->message); + g_error_free (error); + return FALSE; + } + + proxy = dbus_g_proxy_new_for_name (connection, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS); + + if (!dbus_g_proxy_call (proxy, "RequestName", &error, + G_TYPE_STRING, DBUS_SERVICE, + G_TYPE_UINT, DBUS_NAME_FLAG_ALLOW_REPLACEMENT, + G_TYPE_INVALID, + G_TYPE_UINT, &request_name_result, + G_TYPE_INVALID)) { + xchat_printf (ph, _("Failed to acquire %s: %s\n"), + DBUS_SERVICE, + error->message); + g_error_free (error); + + return FALSE; + } + + dbus_g_proxy_add_signal (proxy, "NameOwnerChanged", + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_INVALID); + dbus_g_proxy_connect_signal (proxy, "NameOwnerChanged", + G_CALLBACK (name_owner_changed), + NULL, NULL); + + remote = g_object_new (REMOTE_TYPE_OBJECT, NULL); + dbus_g_connection_register_g_object (connection, + DBUS_OBJECT_PATH"/Remote", + G_OBJECT (remote)); + + return TRUE; +} + +/* xchat_plugin stuffs */ + +static char** +build_list (char *word[]) +{ + guint i; + guint num = 0; + char **result; + + if (word == NULL) { + return NULL; + } + + while (word[num] && word[num][0]) { + num++; + } + + result = g_new0 (char*, num + 1); + for (i = 0; i < num; i++) { + result[i] = g_strdup (word[i]); + } + + return result; +} + +static guint +context_list_find_id (xchat_context *context) +{ + GList *l = NULL; + + for (l = contexts; l != NULL; l = l->next) { + if (((ContextInfo*)l->data)->context == context) { + return ((ContextInfo*)l->data)->id; + } + } + + return 0; +} + +static xchat_context* +context_list_find_context (guint id) +{ + GList *l = NULL; + + for (l = contexts; l != NULL; l = l->next) { + if (((ContextInfo*)l->data)->id == id) { + return ((ContextInfo*)l->data)->context; + } + } + + return NULL; +} + +static int +open_context_cb (char *word[], + void *userdata) +{ + ContextInfo *info; + + info = g_new0 (ContextInfo, 1); + info->id = ++last_context_id; + info->context = xchat_get_context (ph); + contexts = g_list_prepend (contexts, info); + + return XCHAT_EAT_NONE; +} + +static int +close_context_cb (char *word[], + void *userdata) +{ + GList *l; + xchat_context *context = xchat_get_context (ph); + + for (l = contexts; l != NULL; l = l->next) { + if (((ContextInfo*)l->data)->context == context) { + g_free (l->data); + contexts = g_list_delete_link (contexts, l); + break; + } + } + + return XCHAT_EAT_NONE; +} + +static gboolean +clients_find_filename_foreach (gpointer key, + gpointer value, + gpointer user_data) +{ + RemoteObject *obj = REMOTE_OBJECT (value); + return g_str_equal (obj->filename, (char*)user_data); +} + +static int +unload_plugin_cb (char *word[], char *word_eol[], void *userdata) +{ + RemoteObject *obj; + + obj = g_hash_table_find (clients, + clients_find_filename_foreach, + word[2]); + if (obj != NULL) { + g_signal_emit (obj, + signals[UNLOAD_SIGNAL], + 0); + return XCHAT_EAT_ALL; + } + + return XCHAT_EAT_NONE; +} + +int +dbus_plugin_init (xchat_plugin *plugin_handle, + char **plugin_name, + char **plugin_desc, + char **plugin_version, + char *arg) +{ + ph = plugin_handle; + *plugin_name = PNAME; + *plugin_desc = PDESC; + *plugin_version = PVERSION; + + if (init_dbus()) { + /*xchat_printf (ph, _("%s loaded successfully!\n"), PNAME);*/ + + clients = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); + + xchat_hook_print (ph, "Open Context", + XCHAT_PRI_NORM, + open_context_cb, + NULL); + + xchat_hook_print (ph, "Close Context", + XCHAT_PRI_NORM, + close_context_cb, + NULL); + + xchat_hook_command (ph, "unload", + XCHAT_PRI_HIGHEST, + unload_plugin_cb, NULL, NULL); + } + + return TRUE; +} diff --git a/src/common/dbus/dbus-plugin.h b/src/common/dbus/dbus-plugin.h new file mode 100644 index 00000000..bda8f61c --- /dev/null +++ b/src/common/dbus/dbus-plugin.h @@ -0,0 +1,31 @@ +/* dbus-plugin.c - xchat plugin for remote access using DBUS + * Copyright (C) 2006 Claessens Xavier + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + * Claessens Xavier + * xclaesse@gmail.com + */ + +#ifndef XCHAT_DBUS_PLUGIN_H +#define XCHAT_DBUS_PLUGIN_H + +int dbus_plugin_init (xchat_plugin *plugin_handle, + char **plugin_name, + char **plugin_desc, + char **plugin_version, + char *arg); + +#endif diff --git a/src/common/dbus/example.c b/src/common/dbus/example.c new file mode 100644 index 00000000..1d072785 --- /dev/null +++ b/src/common/dbus/example.c @@ -0,0 +1,201 @@ +/* example.c - program to demonstrate some D-BUS stuffs. + * Copyright (C) 2006 Claessens Xavier + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Claessens Xavier + * xclaesse@gmail.com + */ + +#include <config.h> +#include <dbus/dbus-glib.h> +#include <stdlib.h> +#include "marshallers.h" + +#define DBUS_SERVICE "org.xchat.service" +#define DBUS_REMOTE "/org/xchat/Remote" +#define DBUS_REMOTE_CONNECTION_INTERFACE "org.xchat.connection" +#define DBUS_REMOTE_PLUGIN_INTERFACE "org.xchat.plugin" + +guint command_id; +guint server_id; + +static void +write_error (char *message, + GError **error) +{ + if (error == NULL || *error == NULL) { + return; + } + g_printerr ("%s: %s\n", message, (*error)->message); + g_clear_error (error); +} + +static void +test_server_cb (DBusGProxy *proxy, + char *word[], + char *word_eol[], + guint hook_id, + guint context_id, + gpointer user_data) +{ + if (hook_id == server_id) { + g_print ("message: %s\n", word_eol[0]); + } +} + +static void +test_command_cb (DBusGProxy *proxy, + char *word[], + char *word_eol[], + guint hook_id, + guint context_id, + gpointer user_data) +{ + GError *error = NULL; + + if (hook_id == command_id) { + if (!dbus_g_proxy_call (proxy, "Unhook", + &error, + G_TYPE_UINT, hook_id, + G_TYPE_INVALID, G_TYPE_INVALID)) { + write_error ("Failed to complete unhook", &error); + } + /* Now if you write "/test blah" again in the xchat window + * you'll get a "Unknown command" error message */ + g_print ("test command received: %s\n", word_eol[1]); + if (!dbus_g_proxy_call (proxy, "Print", + &error, + G_TYPE_STRING, "test command succeed", + G_TYPE_INVALID, + G_TYPE_INVALID)) { + write_error ("Failed to complete Print", &error); + } + } +} + +static void +unload_cb (void) +{ + g_print ("Good bye !\n"); + exit (EXIT_SUCCESS); +} + +int +main (int argc, char **argv) +{ + DBusGConnection *connection; + DBusGProxy *remote_object; + GMainLoop *mainloop; + gchar *path; + GError *error = NULL; + + g_type_init (); + + connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error); + if (connection == NULL) { + write_error ("Couldn't connect to session bus", &error); + return EXIT_FAILURE; + } + + remote_object = dbus_g_proxy_new_for_name (connection, + DBUS_SERVICE, + DBUS_REMOTE, + DBUS_REMOTE_CONNECTION_INTERFACE); + if (!dbus_g_proxy_call (remote_object, "Connect", + &error, + G_TYPE_STRING, argv[0], + G_TYPE_STRING, "example", + G_TYPE_STRING, "Example of a D-Bus client", + G_TYPE_STRING, "1.0", + G_TYPE_INVALID, + G_TYPE_STRING, &path, G_TYPE_INVALID)) { + write_error ("Failed to complete Connect", &error); + return EXIT_FAILURE; + } + g_object_unref (remote_object); + + remote_object = dbus_g_proxy_new_for_name (connection, + DBUS_SERVICE, + path, + DBUS_REMOTE_PLUGIN_INTERFACE); + g_free (path); + + if (!dbus_g_proxy_call (remote_object, "HookCommand", + &error, + G_TYPE_STRING, "test", + G_TYPE_INT, 0, + G_TYPE_STRING, "Simple D-BUS example", + G_TYPE_INT, 1, G_TYPE_INVALID, + G_TYPE_UINT, &command_id, G_TYPE_INVALID)) { + write_error ("Failed to complete HookCommand", &error); + return EXIT_FAILURE; + } + g_print ("Command hook id=%d\n", command_id); + + if (!dbus_g_proxy_call (remote_object, "HookServer", + &error, + G_TYPE_STRING, "RAW LINE", + G_TYPE_INT, 0, + G_TYPE_INT, 0, G_TYPE_INVALID, + G_TYPE_UINT, &server_id, G_TYPE_INVALID)) { + write_error ("Failed to complete HookServer", &error); + return EXIT_FAILURE; + } + g_print ("Server hook id=%d\n", server_id); + + dbus_g_object_register_marshaller ( + g_cclosure_user_marshal_VOID__POINTER_POINTER_UINT_UINT, + G_TYPE_NONE, + G_TYPE_STRV, G_TYPE_STRV, G_TYPE_UINT, G_TYPE_UINT, + G_TYPE_INVALID); + + dbus_g_object_register_marshaller ( + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + G_TYPE_INVALID); + + dbus_g_proxy_add_signal (remote_object, "CommandSignal", + G_TYPE_STRV, + G_TYPE_STRV, + G_TYPE_UINT, + G_TYPE_UINT, + G_TYPE_INVALID); + dbus_g_proxy_connect_signal (remote_object, "CommandSignal", + G_CALLBACK (test_command_cb), + NULL, NULL); + + dbus_g_proxy_add_signal (remote_object, "ServerSignal", + G_TYPE_STRV, + G_TYPE_STRV, + G_TYPE_UINT, + G_TYPE_UINT, + G_TYPE_INVALID); + dbus_g_proxy_connect_signal (remote_object, "ServerSignal", + G_CALLBACK (test_server_cb), + NULL, NULL); + + dbus_g_proxy_add_signal (remote_object, "UnloadSignal", + G_TYPE_INVALID); + dbus_g_proxy_connect_signal (remote_object, "UnloadSignal", + G_CALLBACK (unload_cb), + NULL, NULL); + + /* Now you can write on the xchat windows: "/test arg1 arg2 ..." */ + mainloop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (mainloop); + + return EXIT_SUCCESS; +} diff --git a/src/common/dbus/example.py b/src/common/dbus/example.py new file mode 100644 index 00000000..08bfdac3 --- /dev/null +++ b/src/common/dbus/example.py @@ -0,0 +1,28 @@ +#! /usr/bin/python + +import dbus + +bus = dbus.SessionBus() +proxy = bus.get_object('org.xchat.service', '/org/xchat/Remote') +remote = dbus.Interface(proxy, 'org.xchat.connection') +path = remote.Connect ("example.py", + "Python example", + "Example of a D-Bus client written in python", + "1.0") +proxy = bus.get_object('org.xchat.service', path) +xchat = dbus.Interface(proxy, 'org.xchat.plugin') + +channels = xchat.ListGet ("channels") +while xchat.ListNext (channels): + name = xchat.ListStr (channels, "channel") + print "------- " + name + " -------" + xchat.SetContext (xchat.ListInt (channels, "context")) + xchat.EmitPrint ("Channel Message", ["John", "Hi there", "@"]) + users = xchat.ListGet ("users") + while xchat.ListNext (users): + print "Nick: " + xchat.ListStr (users, "nick") + xchat.ListFree (users) +xchat.ListFree (channels) + +print xchat.Strip ("\00312Blue\003 \002Bold!\002", -1, 1|2) + diff --git a/src/common/dbus/marshallers.list b/src/common/dbus/marshallers.list new file mode 100644 index 00000000..bc3c4ad5 --- /dev/null +++ b/src/common/dbus/marshallers.list @@ -0,0 +1 @@ +VOID:POINTER,POINTER,UINT,UINT diff --git a/src/common/dbus/org.xchat.service.service.in b/src/common/dbus/org.xchat.service.service.in new file mode 100644 index 00000000..19490121 --- /dev/null +++ b/src/common/dbus/org.xchat.service.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=org.xchat.service +Exec=@bindir@/xchat diff --git a/src/common/dbus/remote-object.xml b/src/common/dbus/remote-object.xml new file mode 100644 index 00000000..88a8ae7c --- /dev/null +++ b/src/common/dbus/remote-object.xml @@ -0,0 +1,142 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<node name="/"> + + <interface name="org.xchat.connection"> + <method name="Connect"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <arg type="s" name="filename" direction="in"/> + <arg type="s" name="name" direction="in"/> + <arg type="s" name="desc" direction="in"/> + <arg type="s" name="version" direction="in"/> + <arg type="s" name="path" direction="out"/> + </method> + <method name="Disconnect"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + </method> + </interface> + + <interface name="org.xchat.plugin"> + <method name="Command"> + <arg type="s" name="command" direction="in"/> + </method> + <method name="Print"> + <arg type="s" name="text" direction="in"/> + </method> + <method name="FindContext"> + <arg type="s" name="server" direction="in"/> + <arg type="s" name="channel" direction="in"/> + <arg type="u" name="ret_id" direction="out"/> + </method> + <method name="GetContext"> + <arg type="u" name="ret_id" direction="out"/> + </method> + <method name="SetContext"> + <arg type="u" name="id" direction="in"/> + <arg type="b" name="ret" direction="out"/> + </method> + <method name="GetInfo"> + <arg type="s" name="id" direction="in"/> + <arg type="s" name="ret_info" direction="out"/> + </method> + <method name="GetPrefs"> + <arg type="s" name="name" direction="in"/> + <arg type="i" name="ret_type" direction="out"/> + <arg type="s" name="ret_str" direction="out"/> + <arg type="i" name="ret_int" direction="out"/> + </method> + <method name="HookCommand"> + <arg type="s" name="name" direction="in"/> + <arg type="i" name="priority" direction="in"/> + <arg type="s" name="help_text" direction="in"/> + <arg type="i" name="return_value" direction="in"/> + <arg type="u" name="ret_id" direction="out"/> + </method> + <method name="HookServer"> + <arg type="s" name="name" direction="in"/> + <arg type="i" name="priority" direction="in"/> + <arg type="i" name="return_value" direction="in"/> + <arg type="u" name="ret_id" direction="out"/> + </method> + <method name="HookPrint"> + <arg type="s" name="name" direction="in"/> + <arg type="i" name="priority" direction="in"/> + <arg type="i" name="return_value" direction="in"/> + <arg type="u" name="ret_id" direction="out"/> + </method> + <method name="Unhook"> + <arg type="u" name="id" direction="in"/> + </method> + <method name="ListGet"> + <arg type="s" name="name" direction="in"/> + <arg type="u" name="ret_id" direction="out"/> + </method> + <method name="ListNext"> + <arg type="u" name="id" direction="in"/> + <arg type="b" name="ret" direction="out"/> + </method> + <method name="ListStr"> + <arg type="u" name="id" direction="in"/> + <arg type="s" name="name" direction="in"/> + <arg type="s" name="ret_str" direction="out"/> + </method> + <method name="ListInt"> + <arg type="u" name="id" direction="in"/> + <arg type="s" name="name" direction="in"/> + <arg type="i" name="ret_int" direction="out"/> + </method> + <method name="ListTime"> + <arg type="u" name="id" direction="in"/> + <arg type="s" name="name" direction="in"/> + <arg type="t" name="ret_time" direction="out"/> + </method> + <method name="ListFields"> + <arg type="s" name="name" direction="in"/> + <arg type="as" name="ret" direction="out"/> + </method> + <method name="ListFree"> + <arg type="u" name="id" direction="in"/> + </method> + <method name="EmitPrint"> + <arg type="s" name="event_name" direction="in"/> + <arg type="as" name="args" direction="in"/> + <arg type="b" name="ret" direction="out"/> + </method> + <method name="Nickcmp"> + <arg type="s" name="nick1" direction="in"/> + <arg type="s" name="nick2" direction="in"/> + <arg type="i" name="ret" direction="out"/> + </method> + <method name="Strip"> + <arg type="s" name="str" direction="in"/> + <arg type="i" name="len" direction="in"/> + <arg type="i" name="flag" direction="in"/> + <arg type="s" name="ret_str" direction="out"/> + </method> + <method name="SendModes"> + <arg type="as" name="targets" direction="in"/> + <arg type="i" name="modes_per_line" direction="in"/> + <arg type="y" name="sign" direction="in"/> + <arg type="y" name="mode" direction="in"/> + </method> + + <signal name="CommandSignal"> + <arg type="as" name="word"/> + <arg type="as" name="word_eol"/> + <arg type="u" name="hook_id"/> + <arg type="u" name="context_id"/> + </signal> + <signal name="ServerSignal"> + <arg type="as" name="word"/> + <arg type="as" name="word_eol"/> + <arg type="u" name="hook_id"/> + <arg type="u" name="context_id"/> + </signal> + <signal name="PrintSignal"> + <arg type="as" name="word"/> + <arg type="u" name="hook_id"/> + <arg type="u" name="context_id"/> + </signal> + <signal name="UnloadSignal"/> + </interface> +</node> diff --git a/src/common/dcc.c b/src/common/dcc.c new file mode 100644 index 00000000..8f289342 --- /dev/null +++ b/src/common/dcc.c @@ -0,0 +1,2587 @@ +/* X-Chat + * Copyright (C) 1998-2006 Peter Zelezny. + * Copyright (C) 2006 Damjan Jovanovic + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + * Wayne Conrad, 3 Apr 1999: Color-coded DCC file transfer status windows + * Bernhard Valenti <bernhard.valenti@gmx.net> 2000-11-21: Fixed DCC send behind nat + * + * 2001-03-08 Added support for getting "dcc_ip" config parameter. + * Jim Seymour (jseymour@LinxNet.com) + */ + +/* we only use 32 bits, but without this define, you get only 31! */ +#define _FILE_OFFSET_BITS 64 +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <errno.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> + +#define WANTSOCKET +#define WANTARPA +#define WANTDNS +#include "inet.h" + +#ifdef WIN32 +#include <windows.h> +#endif + +#include "xchat.h" +#include "util.h" +#include "fe.h" +#include "outbound.h" +#include "inbound.h" +#include "network.h" +#include "plugin.h" +#include "server.h" +#include "text.h" +#include "url.h" +#include "xchatc.h" + +#ifdef USE_DCC64 +#define BIG_STR_TO_INT(x) strtoull(x,NULL,10) +#else +#define BIG_STR_TO_INT(x) strtoul(x,NULL,10) +#endif + +static char *dcctypes[] = { "SEND", "RECV", "CHAT", "CHAT" }; + +struct dccstat_info dccstat[] = { + {N_("Waiting"), 1 /*black */ }, + {N_("Active"), 12 /*cyan */ }, + {N_("Failed"), 4 /*red */ }, + {N_("Done"), 3 /*green */ }, + {N_("Connect"), 1 /*black */ }, + {N_("Aborted"), 4 /*red */ }, +}; + +static int dcc_global_throttle; /* 0x1 = sends, 0x2 = gets */ +/*static*/ int dcc_sendcpssum, dcc_getcpssum; + +static struct DCC *new_dcc (void); +static void dcc_close (struct DCC *dcc, int dccstat, int destroy); +static gboolean dcc_send_data (GIOChannel *, GIOCondition, struct DCC *); +static gboolean dcc_read (GIOChannel *, GIOCondition, struct DCC *); +static gboolean dcc_read_ack (GIOChannel *source, GIOCondition condition, struct DCC *dcc); + +static int new_id() +{ + static int id = 0; + if (id == 0) + { + /* start the first ID at a random number for pseudo security */ + /* 1 - 255 */ + id = RAND_INT(255) + 1; + /* ignore overflows, since it can go to 2 billion */ + } + return id++; +} + +static double +timeval_diff (GTimeVal *greater, + GTimeVal *less) +{ + long usecdiff; + double result; + + result = greater->tv_sec - less->tv_sec; + usecdiff = (long) greater->tv_usec - less->tv_usec; + result += (double) usecdiff / 1000000; + + return result; +} + +static void +dcc_unthrottle (struct DCC *dcc) +{ + /* don't unthrottle here, but delegate to funcs */ + if (dcc->type == TYPE_RECV) + dcc_read (NULL, 0, dcc); + else + dcc_send_data (NULL, 0, dcc); +} + +static void +dcc_calc_cps (struct DCC *dcc) +{ + GTimeVal now; + int oldcps; + double timediff, startdiff; + int glob_throttle_bit, wasthrottled; + int *cpssum, glob_limit; + DCC_SIZE pos, posdiff; + + g_get_current_time (&now); + + /* the pos we use for sends is an average + between pos and ack */ + if (dcc->type == TYPE_SEND) + { + /* carefull to avoid 32bit overflow */ + pos = dcc->pos - ((dcc->pos - dcc->ack) / 2); + glob_throttle_bit = 0x1; + cpssum = &dcc_sendcpssum; + glob_limit = prefs.dcc_global_max_send_cps; + } + else + { + pos = dcc->pos; + glob_throttle_bit = 0x2; + cpssum = &dcc_getcpssum; + glob_limit = prefs.dcc_global_max_get_cps; + } + + if (!dcc->firstcpstv.tv_sec && !dcc->firstcpstv.tv_usec) + dcc->firstcpstv = now; + else + { + startdiff = timeval_diff (&now, &dcc->firstcpstv); + if (startdiff < 1) + startdiff = 1; + else if (startdiff > CPS_AVG_WINDOW) + startdiff = CPS_AVG_WINDOW; + + timediff = timeval_diff (&now, &dcc->lastcpstv); + if (timediff > startdiff) + timediff = startdiff = 1; + + posdiff = pos - dcc->lastcpspos; + oldcps = dcc->cps; + dcc->cps = ((double) posdiff / timediff) * (timediff / startdiff) + + (double) dcc->cps * (1.0 - (timediff / startdiff)); + + *cpssum += dcc->cps - oldcps; + } + + dcc->lastcpspos = pos; + dcc->lastcpstv = now; + + /* now check cps against set limits... */ + wasthrottled = dcc->throttled; + + /* check global limits first */ + dcc->throttled &= ~0x2; + if (glob_limit > 0 && *cpssum >= glob_limit) + { + dcc_global_throttle |= glob_throttle_bit; + if (dcc->maxcps >= 0) + dcc->throttled |= 0x2; + } + else + dcc_global_throttle &= ~glob_throttle_bit; + + /* now check per-connection limit */ + if (dcc->maxcps > 0 && dcc->cps > dcc->maxcps) + dcc->throttled |= 0x1; + else + dcc->throttled &= ~0x1; + + /* take action */ + if (wasthrottled && !dcc->throttled) + dcc_unthrottle (dcc); +} + +static void +dcc_remove_from_sum (struct DCC *dcc) +{ + if (dcc->dccstat != STAT_ACTIVE) + return; + if (dcc->type == TYPE_SEND) + dcc_sendcpssum -= dcc->cps; + else if (dcc->type == TYPE_RECV) + dcc_getcpssum -= dcc->cps; +} + +gboolean +is_dcc (struct DCC *dcc) +{ + GSList *list = dcc_list; + while (list) + { + if (list->data == dcc) + return TRUE; + list = list->next; + } + return FALSE; +} + +/* this is called from xchat.c:xchat_misc_checks() every 1 second. */ + +void +dcc_check_timeouts (void) +{ + struct DCC *dcc; + time_t tim = time (0); + GSList *next, *list = dcc_list; + + while (list) + { + dcc = (struct DCC *) list->data; + next = list->next; + + switch (dcc->dccstat) + { + case STAT_ACTIVE: + dcc_calc_cps (dcc); + fe_dcc_update (dcc); + + if (dcc->type == TYPE_SEND || dcc->type == TYPE_RECV) + { + if (prefs.dccstalltimeout > 0) + { + if (!dcc->throttled + && tim - dcc->lasttime > prefs.dccstalltimeout) + { + EMIT_SIGNAL (XP_TE_DCCSTALL, dcc->serv->front_session, + dcctypes[dcc->type], + file_part (dcc->file), dcc->nick, NULL, 0); + dcc_close (dcc, STAT_ABORTED, FALSE); + } + } + } + break; + case STAT_QUEUED: + if (dcc->type == TYPE_SEND || dcc->type == TYPE_CHATSEND) + { + if (tim - dcc->offertime > prefs.dcctimeout) + { + if (prefs.dcctimeout > 0) + { + EMIT_SIGNAL (XP_TE_DCCTOUT, dcc->serv->front_session, + dcctypes[dcc->type], + file_part (dcc->file), dcc->nick, NULL, 0); + dcc_close (dcc, STAT_ABORTED, FALSE); + } + } + } + break; + case STAT_DONE: + case STAT_FAILED: + case STAT_ABORTED: + if (prefs.dcc_remove) + dcc_close (dcc, 0, TRUE); + break; + } + list = next; + } +} + +static int +dcc_lookup_proxy (char *host, struct sockaddr_in *addr) +{ + struct hostent *h; + static char *cache_host = NULL; + static guint32 cache_addr; + + /* too lazy to thread this, so we cache results */ + if (cache_host) + { + if (strcmp (host, cache_host) == 0) + { + memcpy (&addr->sin_addr, &cache_addr, 4); + return TRUE; + } + free (cache_host); + cache_host = NULL; + } + + h = gethostbyname (host); + if (h != NULL && h->h_length == 4 && h->h_addr_list[0] != NULL) + { + memcpy (&addr->sin_addr, h->h_addr, 4); + memcpy (&cache_addr, h->h_addr, 4); + cache_host = strdup (host); + return TRUE; + } + + return FALSE; +} + +#define DCC_USE_PROXY() (prefs.proxy_host[0] && prefs.proxy_type>0 && prefs.proxy_type<5 && prefs.proxy_use!=1) + +static int +dcc_connect_sok (struct DCC *dcc) +{ + int sok; + struct sockaddr_in addr; + + sok = socket (AF_INET, SOCK_STREAM, 0); + if (sok == -1) + return -1; + + memset (&addr, 0, sizeof (addr)); + addr.sin_family = AF_INET; + if (DCC_USE_PROXY ()) + { + if (!dcc_lookup_proxy (prefs.proxy_host, &addr)) + { + closesocket (sok); + return -1; + } + addr.sin_port = htons (prefs.proxy_port); + } + else + { + addr.sin_port = htons (dcc->port); + addr.sin_addr.s_addr = htonl (dcc->addr); + } + + set_nonblocking (sok); + connect (sok, (struct sockaddr *) &addr, sizeof (addr)); + + return sok; +} + +static void +dcc_close (struct DCC *dcc, int dccstat, int destroy) +{ + if (dcc->wiotag) + { + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + } + + if (dcc->iotag) + { + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + } + + if (dcc->sok != -1) + { + closesocket (dcc->sok); + dcc->sok = -1; + } + + dcc_remove_from_sum (dcc); + + if (dcc->fp != -1) + { + close (dcc->fp); + dcc->fp = -1; + + if(dccstat == STAT_DONE) + { + /* if we just completed a dcc recieve, move the */ + /* completed file to the completed directory */ + if(dcc->type == TYPE_RECV) + { + /* mgl: change this to use destfile_fs for correctness and to */ + /* handle the case where dccwithnick is set */ + move_file_utf8 (prefs.dccdir, prefs.dcc_completed_dir, + file_part (dcc->destfile), prefs.dccpermissions); + } + + } + } + + dcc->dccstat = dccstat; + if (dcc->dccchat) + { + free (dcc->dccchat); + dcc->dccchat = NULL; + } + + if (destroy) + { + dcc_list = g_slist_remove (dcc_list, dcc); + fe_dcc_remove (dcc); + if (dcc->proxy) + free (dcc->proxy); + if (dcc->file) + free (dcc->file); + if (dcc->destfile) + g_free (dcc->destfile); + if (dcc->destfile_fs) + g_free (dcc->destfile_fs); + free (dcc->nick); + free (dcc); + return; + } + + fe_dcc_update (dcc); +} + +void +dcc_abort (session *sess, struct DCC *dcc) +{ + if (dcc) + { + switch (dcc->dccstat) + { + case STAT_QUEUED: + case STAT_CONNECTING: + case STAT_ACTIVE: + dcc_close (dcc, STAT_ABORTED, FALSE); + switch (dcc->type) + { + case TYPE_CHATSEND: + case TYPE_CHATRECV: + EMIT_SIGNAL (XP_TE_DCCCHATABORT, sess, dcc->nick, NULL, NULL, + NULL, 0); + break; + case TYPE_SEND: + EMIT_SIGNAL (XP_TE_DCCSENDABORT, sess, dcc->nick, + file_part (dcc->file), NULL, NULL, 0); + break; + case TYPE_RECV: + EMIT_SIGNAL (XP_TE_DCCRECVABORT, sess, dcc->nick, + dcc->file, NULL, NULL, 0); + } + break; + default: + dcc_close (dcc, 0, TRUE); + } + } +} + +void +dcc_notify_kill (struct server *serv) +{ + struct server *replaceserv = 0; + struct DCC *dcc; + GSList *list = dcc_list; + if (serv_list) + replaceserv = (struct server *) serv_list->data; + while (list) + { + dcc = (struct DCC *) list->data; + if (dcc->serv == serv) + dcc->serv = replaceserv; + list = list->next; + } +} + +struct DCC * +dcc_write_chat (char *nick, char *text) +{ + struct DCC *dcc; + int len; + + dcc = find_dcc (nick, "", TYPE_CHATRECV); + if (!dcc) + dcc = find_dcc (nick, "", TYPE_CHATSEND); + if (dcc && dcc->dccstat == STAT_ACTIVE) + { + len = strlen (text); + tcp_send_real (NULL, dcc->sok, dcc->serv->encoding, dcc->serv->using_irc, + text, len); + send (dcc->sok, "\n", 1, 0); + dcc->size += len; + fe_dcc_update (dcc); + return dcc; + } + return 0; +} + +/* returns: 0 - ok + 1 - the dcc is closed! */ + +static int +dcc_chat_line (struct DCC *dcc, char *line) +{ + session *sess; + char *word[PDIWORDS]; + char *po; + char *utf; + char *conv; + int ret, i; + int len; + gsize utf_len; + char portbuf[32]; + + len = strlen (line); + if (dcc->serv->using_cp1255) + len++; /* include the NUL terminator */ + + if (dcc->serv->using_irc) /* using "IRC" encoding (CP1252/UTF-8 hybrid) */ + utf = NULL; + else if (dcc->serv->encoding == NULL) /* system */ + utf = g_locale_to_utf8 (line, len, NULL, &utf_len, NULL); + else + utf = g_convert (line, len, "UTF-8", dcc->serv->encoding, 0, &utf_len, 0); + + if (utf) + { + line = utf; + len = utf_len; + } + + if (dcc->serv->using_cp1255 && len > 0) + len--; + + /* we really need valid UTF-8 now */ + conv = text_validate (&line, &len); + + sess = find_dialog (dcc->serv, dcc->nick); + if (!sess) + sess = dcc->serv->front_session; + + sprintf (portbuf, "%d", dcc->port); + + word[0] = "DCC Chat Text"; + word[1] = net_ip (dcc->addr); + word[2] = portbuf; + word[3] = dcc->nick; + word[4] = line; + for (i = 5; i < PDIWORDS; i++) + word[i] = "\000"; + + ret = plugin_emit_print (sess, word); + + /* did the plugin close it? */ + if (!g_slist_find (dcc_list, dcc)) + { + if (utf) + g_free (utf); + if (conv) + g_free (conv); + return 1; + } + + /* did the plugin eat the event? */ + if (ret) + { + if (utf) + g_free (utf); + if (conv) + g_free (conv); + return 0; + } + + url_check_line (line, len); + + if (line[0] == 1 && !strncasecmp (line + 1, "ACTION", 6)) + { + po = strchr (line + 8, '\001'); + if (po) + po[0] = 0; + inbound_action (sess, dcc->serv->nick, dcc->nick, "", line + 8, FALSE, FALSE); + } else + { + inbound_privmsg (dcc->serv, dcc->nick, "", line, FALSE); + } + if (utf) + g_free (utf); + if (conv) + g_free (conv); + return 0; +} + +static gboolean +dcc_read_chat (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + int i, len, dead; + char portbuf[32]; + char lbuf[2050]; + + while (1) + { + if (dcc->throttled) + { + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + return FALSE; + } + + if (!dcc->iotag) + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read_chat, dcc); + + len = recv (dcc->sok, lbuf, sizeof (lbuf) - 2, 0); + if (len < 1) + { + if (len < 0) + { + if (would_block ()) + return TRUE; + } + sprintf (portbuf, "%d", dcc->port); + EMIT_SIGNAL (XP_TE_DCCCHATF, dcc->serv->front_session, dcc->nick, + net_ip (dcc->addr), portbuf, + errorstring ((len < 0) ? sock_error () : 0), 0); + dcc_close (dcc, STAT_FAILED, FALSE); + return TRUE; + } + i = 0; + lbuf[len] = 0; + while (i < len) + { + switch (lbuf[i]) + { + case '\r': + break; + case '\n': + dcc->dccchat->linebuf[dcc->dccchat->pos] = 0; + dead = dcc_chat_line (dcc, dcc->dccchat->linebuf); + + if (dead || !dcc->dccchat) /* the dcc has been closed, don't use (DCC *)! */ + return TRUE; + + dcc->pos += dcc->dccchat->pos; + dcc->dccchat->pos = 0; + fe_dcc_update (dcc); + break; + default: + dcc->dccchat->linebuf[dcc->dccchat->pos] = lbuf[i]; + if (dcc->dccchat->pos < (sizeof (dcc->dccchat->linebuf) - 1)) + dcc->dccchat->pos++; + } + i++; + } + } +} + +static void +dcc_calc_average_cps (struct DCC *dcc) +{ + time_t sec; + + sec = time (0) - dcc->starttime; + if (sec < 1) + sec = 1; + if (dcc->type == TYPE_SEND) + dcc->cps = (dcc->ack - dcc->resumable) / sec; + else + dcc->cps = (dcc->pos - dcc->resumable) / sec; +} + +static void +dcc_send_ack (struct DCC *dcc) +{ + /* send in 32-bit big endian */ + guint32 pos = htonl (dcc->pos & 0xffffffff); + send (dcc->sok, (char *) &pos, 4, 0); +} + +static gboolean +dcc_read (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + char *old; + char buf[4096]; + int n; + gboolean need_ack = FALSE; + + if (dcc->fp == -1) + { + + /* try to create the download dir (even if it exists, no harm) */ + mkdir_utf8 (prefs.dccdir); + + if (dcc->resumable) + { + dcc->fp = open (dcc->destfile_fs, O_WRONLY | O_APPEND | OFLAGS); + dcc->pos = dcc->resumable; + dcc->ack = dcc->resumable; + } else + { + if (access (dcc->destfile_fs, F_OK) == 0) + { + n = 0; + do + { + n++; + snprintf (buf, sizeof (buf), "%s.%d", dcc->destfile_fs, n); + } + while (access (buf, F_OK) == 0); + + g_free (dcc->destfile_fs); + dcc->destfile_fs = g_strdup (buf); + + old = dcc->destfile; + dcc->destfile = xchat_filename_to_utf8 (buf, -1, 0, 0, 0); + + EMIT_SIGNAL (XP_TE_DCCRENAME, dcc->serv->front_session, + old, dcc->destfile, NULL, NULL, 0); + g_free (old); + } + dcc->fp = + open (dcc->destfile_fs, OFLAGS | O_TRUNC | O_WRONLY | O_CREAT, + prefs.dccpermissions); + } + } + if (dcc->fp == -1) + { + /* the last executed function is open(), errno should be valid */ + EMIT_SIGNAL (XP_TE_DCCFILEERR, dcc->serv->front_session, dcc->destfile, + errorstring (errno), NULL, NULL, 0); + dcc_close (dcc, STAT_FAILED, FALSE); + return TRUE; + } + while (1) + { + if (dcc->throttled) + { + if (need_ack) + dcc_send_ack (dcc); + + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + return FALSE; + } + + if (!dcc->iotag) + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read, dcc); + + n = recv (dcc->sok, buf, sizeof (buf), 0); + if (n < 1) + { + if (n < 0) + { + if (would_block ()) + { + if (need_ack) + dcc_send_ack (dcc); + return TRUE; + } + } + EMIT_SIGNAL (XP_TE_DCCRECVERR, dcc->serv->front_session, dcc->file, + dcc->destfile, dcc->nick, + errorstring ((n < 0) ? sock_error () : 0), 0); + /* send ack here? but the socket is dead */ + /*if (need_ack) + dcc_send_ack (dcc);*/ + dcc_close (dcc, STAT_FAILED, FALSE); + return TRUE; + } + + if (write (dcc->fp, buf, n) == -1) /* could be out of hdd space */ + { + EMIT_SIGNAL (XP_TE_DCCRECVERR, dcc->serv->front_session, dcc->file, + dcc->destfile, dcc->nick, errorstring (errno), 0); + if (need_ack) + dcc_send_ack (dcc); + dcc_close (dcc, STAT_FAILED, FALSE); + return TRUE; + } + + dcc->lasttime = time (0); + dcc->pos += n; + need_ack = TRUE; /* send ack when we're done recv()ing */ + + if (dcc->pos >= dcc->size) + { + dcc_send_ack (dcc); + dcc_close (dcc, STAT_DONE, FALSE); + dcc_calc_average_cps (dcc); /* this must be done _after_ dcc_close, or dcc_remove_from_sum will see the wrong value in dcc->cps */ + sprintf (buf, "%d", dcc->cps); + EMIT_SIGNAL (XP_TE_DCCRECVCOMP, dcc->serv->front_session, + dcc->file, dcc->destfile, dcc->nick, buf, 0); + return TRUE; + } + } +} + +static void +dcc_open_query (server *serv, char *nick) +{ + if (prefs.autodialog) + open_query (serv, nick, FALSE); +} + +static gboolean +dcc_did_connect (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + int er; + struct sockaddr_in addr; + +#ifdef WIN32 + if (condition & G_IO_ERR) + { + int len; + + /* find the last errno for this socket */ + len = sizeof (er); + getsockopt (dcc->sok, SOL_SOCKET, SO_ERROR, (char *)&er, &len); + EMIT_SIGNAL (XP_TE_DCCCONFAIL, dcc->serv->front_session, + dcctypes[dcc->type], dcc->nick, errorstring (er), + NULL, 0); + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return FALSE; + } + +#else + memset (&addr, 0, sizeof (addr)); + addr.sin_port = htons (dcc->port); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl (dcc->addr); + + /* check if it's already connected; This always fails on winXP */ + if (connect (dcc->sok, (struct sockaddr *) &addr, sizeof (addr)) != 0) + { + er = sock_error (); + if (er != EISCONN) + { + EMIT_SIGNAL (XP_TE_DCCCONFAIL, dcc->serv->front_session, + dcctypes[dcc->type], dcc->nick, errorstring (er), + NULL, 0); + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return FALSE; + } + } +#endif + + return TRUE; +} + +static gboolean +dcc_connect_finished (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + char host[128]; + + if (dcc->iotag) + { + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + } + + if (!dcc_did_connect (source, condition, dcc)) + return TRUE; + + dcc->dccstat = STAT_ACTIVE; + snprintf (host, sizeof host, "%s:%d", net_ip (dcc->addr), dcc->port); + + switch (dcc->type) + { + case TYPE_RECV: + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read, dcc); + EMIT_SIGNAL (XP_TE_DCCCONRECV, dcc->serv->front_session, + dcc->nick, host, dcc->file, NULL, 0); + break; + case TYPE_SEND: + /* passive send */ + dcc->fastsend = prefs.fastdccsend; + if (dcc->fastsend) + dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE, dcc_send_data, dcc); + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read_ack, dcc); + dcc_send_data (NULL, 0, (gpointer)dcc); + EMIT_SIGNAL (XP_TE_DCCCONSEND, dcc->serv->front_session, + dcc->nick, host, dcc->file, NULL, 0); + break; + case TYPE_CHATSEND: /* pchat */ + dcc_open_query (dcc->serv, dcc->nick); + case TYPE_CHATRECV: /* normal chat */ + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read_chat, dcc); + dcc->dccchat = malloc (sizeof (struct dcc_chat)); + dcc->dccchat->pos = 0; + EMIT_SIGNAL (XP_TE_DCCCONCHAT, dcc->serv->front_session, + dcc->nick, host, NULL, NULL, 0); + break; + } + dcc->starttime = time (0); + dcc->lasttime = dcc->starttime; + fe_dcc_update (dcc); + + return TRUE; +} + +static gboolean +read_proxy (struct DCC *dcc) +{ + struct proxy_state *proxy = dcc->proxy; + while (proxy->bufferused < proxy->buffersize) + { + int ret = recv (dcc->sok, &proxy->buffer[proxy->bufferused], + proxy->buffersize - proxy->bufferused, 0); + if (ret > 0) + proxy->bufferused += ret; + else + { + if (would_block ()) + return FALSE; + else + { + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + if (dcc->iotag) + { + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + } + return FALSE; + } + } + } + return TRUE; +} + +static gboolean +write_proxy (struct DCC *dcc) +{ + struct proxy_state *proxy = dcc->proxy; + while (proxy->bufferused < proxy->buffersize) + { + int ret = send (dcc->sok, &proxy->buffer[proxy->bufferused], + proxy->buffersize - proxy->bufferused, 0); + if (ret >= 0) + proxy->bufferused += ret; + else + { + if (would_block ()) + return FALSE; + else + { + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + if (dcc->wiotag) + { + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + } + return FALSE; + } + } + } + return TRUE; +} + +static gboolean +proxy_read_line (struct DCC *dcc) +{ + struct proxy_state *proxy = dcc->proxy; + while (1) + { + proxy->buffersize = proxy->bufferused + 1; + if (!read_proxy (dcc)) + return FALSE; + if (proxy->buffer[proxy->bufferused - 1] == '\n' + || proxy->bufferused == MAX_PROXY_BUFFER) + { + proxy->buffer[proxy->bufferused - 1] = 0; + return TRUE; + } + } +} + +static gboolean +dcc_wingate_proxy_traverse (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + struct proxy_state *proxy = dcc->proxy; + if (proxy->phase == 0) + { + proxy->buffersize = snprintf ((char*) proxy->buffer, MAX_PROXY_BUFFER, + "%s %d\r\n", net_ip(dcc->addr), + dcc->port); + proxy->bufferused = 0; + dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, + dcc_wingate_proxy_traverse, dcc); + ++proxy->phase; + } + if (proxy->phase == 1) + { + if (!read_proxy (dcc)) + return TRUE; + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + dcc_connect_finished (source, 0, dcc); + } + return TRUE; +} + +struct sock_connect +{ + char version; + char type; + guint16 port; + guint32 address; + char username[10]; +}; +static gboolean +dcc_socks_proxy_traverse (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + struct proxy_state *proxy = dcc->proxy; + + if (proxy->phase == 0) + { + struct sock_connect sc; + sc.version = 4; + sc.type = 1; + sc.port = htons (dcc->port); + sc.address = htonl (dcc->addr); + strncpy (sc.username, prefs.username, 9); + memcpy (proxy->buffer, &sc, sizeof (sc)); + proxy->buffersize = 8 + strlen (sc.username) + 1; + proxy->bufferused = 0; + dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, + dcc_socks_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 1) + { + if (!write_proxy (dcc)) + return TRUE; + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + proxy->bufferused = 0; + proxy->buffersize = 8; + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, + dcc_socks_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 2) + { + if (!read_proxy (dcc)) + return TRUE; + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + if (proxy->buffer[1] == 90) + dcc_connect_finished (source, 0, dcc); + else + { + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + } + } + + return TRUE; +} + +struct sock5_connect1 +{ + char version; + char nmethods; + char method; +}; +static gboolean +dcc_socks5_proxy_traverse (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + struct proxy_state *proxy = dcc->proxy; + int auth = prefs.proxy_auth && prefs.proxy_user[0] && prefs.proxy_pass[0]; + + if (proxy->phase == 0) + { + struct sock5_connect1 sc1; + + sc1.version = 5; + sc1.nmethods = 1; + sc1.method = 0; + if (auth) + sc1.method = 2; + memcpy (proxy->buffer, &sc1, 3); + proxy->buffersize = 3; + proxy->bufferused = 0; + dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, + dcc_socks5_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 1) + { + if (!write_proxy (dcc)) + return TRUE; + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + proxy->bufferused = 0; + proxy->buffersize = 2; + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, + dcc_socks5_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 2) + { + if (!read_proxy (dcc)) + return TRUE; + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + + /* did the server say no auth required? */ + if (proxy->buffer[0] == 5 && proxy->buffer[1] == 0) + auth = 0; + + /* Set up authentication I/O */ + if (auth) + { + int len_u=0, len_p=0; + + /* authentication sub-negotiation (RFC1929) */ + if ( proxy->buffer[0] != 5 || proxy->buffer[1] != 2 ) /* UPA not supported by server */ + { + PrintText (dcc->serv->front_session, "SOCKS\tServer doesn't support UPA authentication.\n"); + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return TRUE; + } + + memset (proxy->buffer, 0, MAX_PROXY_BUFFER); + + /* form the UPA request */ + len_u = strlen (prefs.proxy_user); + len_p = strlen (prefs.proxy_pass); + proxy->buffer[0] = 1; + proxy->buffer[1] = len_u; + memcpy (proxy->buffer + 2, prefs.proxy_user, len_u); + proxy->buffer[2 + len_u] = len_p; + memcpy (proxy->buffer + 3 + len_u, prefs.proxy_pass, len_p); + + proxy->buffersize = 3 + len_u + len_p; + proxy->bufferused = 0; + dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, + dcc_socks5_proxy_traverse, dcc); + ++proxy->phase; + } + else + { + if (proxy->buffer[0] != 5 || proxy->buffer[1] != 0) + { + PrintText (dcc->serv->front_session, "SOCKS\tAuthentication required but disabled in settings.\n"); + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return TRUE; + } + proxy->phase += 2; + } + } + + if (proxy->phase == 3) + { + if (!write_proxy (dcc)) + return TRUE; + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + proxy->buffersize = 2; + proxy->bufferused = 0; + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, + dcc_socks5_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 4) + { + if (!read_proxy (dcc)) + return TRUE; + if (dcc->iotag) + { + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + } + if (proxy->buffer[1] != 0) + { + PrintText (dcc->serv->front_session, "SOCKS\tAuthentication failed. " + "Is username and password correct?\n"); + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return TRUE; + } + ++proxy->phase; + } + + if (proxy->phase == 5) + { + proxy->buffer[0] = 5; /* version (socks 5) */ + proxy->buffer[1] = 1; /* command (connect) */ + proxy->buffer[2] = 0; /* reserved */ + proxy->buffer[3] = 1; /* address type (IPv4) */ + proxy->buffer[4] = (dcc->addr >> 24) & 0xFF; /* IP address */ + proxy->buffer[5] = (dcc->addr >> 16) & 0xFF; + proxy->buffer[6] = (dcc->addr >> 8) & 0xFF; + proxy->buffer[7] = (dcc->addr & 0xFF); + proxy->buffer[8] = (dcc->port >> 8) & 0xFF; /* port */ + proxy->buffer[9] = (dcc->port & 0xFF); + proxy->buffersize = 10; + proxy->bufferused = 0; + dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, + dcc_socks5_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 6) + { + if (!write_proxy (dcc)) + return TRUE; + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + proxy->buffersize = 4; + proxy->bufferused = 0; + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, + dcc_socks5_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 7) + { + if (!read_proxy (dcc)) + return TRUE; + if (proxy->buffer[0] != 5 || proxy->buffer[1] != 0) + { + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + if (proxy->buffer[1] == 2) + PrintText (dcc->serv->front_session, "SOCKS\tProxy refused to connect to host (not allowed).\n"); + else + PrintTextf (dcc->serv->front_session, "SOCKS\tProxy failed to connect to host (error %d).\n", proxy->buffer[1]); + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return TRUE; + } + switch (proxy->buffer[3]) + { + case 1: proxy->buffersize += 6; break; + case 3: proxy->buffersize += 1; break; + case 4: proxy->buffersize += 18; break; + }; + ++proxy->phase; + } + + if (proxy->phase == 8) + { + if (!read_proxy (dcc)) + return TRUE; + /* handle domain name case */ + if (proxy->buffer[3] == 3) + { + proxy->buffersize = 5 + proxy->buffer[4] + 2; + } + /* everything done? */ + if (proxy->bufferused == proxy->buffersize) + { + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + dcc_connect_finished (source, 0, dcc); + } + } + return TRUE; +} + +static gboolean +dcc_http_proxy_traverse (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + struct proxy_state *proxy = dcc->proxy; + + if (proxy->phase == 0) + { + char buf[256]; + char auth_data[128]; + char auth_data2[68]; + int n, n2; + + n = snprintf (buf, sizeof (buf), "CONNECT %s:%d HTTP/1.0\r\n", + net_ip(dcc->addr), dcc->port); + if (prefs.proxy_auth) + { + n2 = snprintf (auth_data2, sizeof (auth_data2), "%s:%s", + prefs.proxy_user, prefs.proxy_pass); + base64_encode (auth_data, auth_data2, n2); + n += snprintf (buf+n, sizeof (buf)-n, "Proxy-Authorization: Basic %s\r\n", auth_data); + } + n += snprintf (buf+n, sizeof (buf)-n, "\r\n"); + proxy->buffersize = n; + proxy->bufferused = 0; + memcpy (proxy->buffer, buf, proxy->buffersize); + dcc->wiotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, + dcc_http_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 1) + { + if (!write_proxy (dcc)) + return TRUE; + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + proxy->bufferused = 0; + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, + dcc_http_proxy_traverse, dcc); + ++proxy->phase; + } + + if (proxy->phase == 2) + { + if (!proxy_read_line (dcc)) + return TRUE; + /* "HTTP/1.0 200 OK" */ + if (proxy->bufferused < 12 || + memcmp (proxy->buffer, "HTTP/", 5) || memcmp (proxy->buffer + 9, "200", 3)) + { + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + PrintText (dcc->serv->front_session, proxy->buffer); + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return TRUE; + } + proxy->bufferused = 0; + ++proxy->phase; + } + + if (proxy->phase == 3) + { + while (1) + { + /* read until blank line */ + if (proxy_read_line (dcc)) + { + if (proxy->bufferused < 1 || + (proxy->bufferused == 2 && proxy->buffer[0] == '\r')) + { + break; + } + if (proxy->bufferused > 1) + PrintText (dcc->serv->front_session, proxy->buffer); + proxy->bufferused = 0; + } + else + return TRUE; + } + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + dcc_connect_finished (source, 0, dcc); + } + + return TRUE; +} + +static gboolean +dcc_proxy_connect (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + + if (!dcc_did_connect (source, condition, dcc)) + return TRUE; + + dcc->proxy = malloc (sizeof (struct proxy_state)); + if (!dcc->proxy) + { + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return TRUE; + } + memset (dcc->proxy, 0, sizeof (struct proxy_state)); + + switch (prefs.proxy_type) + { + case 1: return dcc_wingate_proxy_traverse (source, condition, dcc); + case 2: return dcc_socks_proxy_traverse (source, condition, dcc); + case 3: return dcc_socks5_proxy_traverse (source, condition, dcc); + case 4: return dcc_http_proxy_traverse (source, condition, dcc); + } + return TRUE; +} + +static int dcc_listen_init (struct DCC *, struct session *); + +static void +dcc_connect (struct DCC *dcc) +{ + int ret; + char tbuf[400]; + + if (dcc->dccstat == STAT_CONNECTING) + return; + dcc->dccstat = STAT_CONNECTING; + + if (dcc->pasvid && dcc->port == 0) + { + /* accepted a passive dcc send */ + ret = dcc_listen_init (dcc, dcc->serv->front_session); + if (!ret) + { + dcc_close (dcc, STAT_FAILED, FALSE); + return; + } + /* possible problems with filenames containing spaces? */ + if (dcc->type == TYPE_RECV) + snprintf (tbuf, sizeof (tbuf), strchr (dcc->file, ' ') ? + "DCC SEND \"%s\" %u %d %"DCC_SFMT" %d" : + "DCC SEND %s %u %d %"DCC_SFMT" %d", dcc->file, + dcc->addr, dcc->port, dcc->size, dcc->pasvid); + else + snprintf (tbuf, sizeof (tbuf), "DCC CHAT chat %u %d %d", + dcc->addr, dcc->port, dcc->pasvid); + dcc->serv->p_ctcp (dcc->serv, dcc->nick, tbuf); + } + else + { + dcc->sok = dcc_connect_sok (dcc); + if (dcc->sok == -1) + { + dcc->dccstat = STAT_FAILED; + fe_dcc_update (dcc); + return; + } + if (DCC_USE_PROXY ()) + dcc->iotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, dcc_proxy_connect, dcc); + else + dcc->iotag = fe_input_add (dcc->sok, FIA_WRITE|FIA_EX, dcc_connect_finished, dcc); + } + + fe_dcc_update (dcc); +} + +static gboolean +dcc_send_data (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + char *buf; + int len, sent, sok = dcc->sok; + + if (prefs.dcc_blocksize < 1) /* this is too little! */ + prefs.dcc_blocksize = 1024; + + if (prefs.dcc_blocksize > 102400) /* this is too much! */ + prefs.dcc_blocksize = 102400; + + if (dcc->throttled) + { + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + return FALSE; + } + + if (!dcc->fastsend) + { + if (dcc->ack < dcc->pos) + return TRUE; + } + else if (!dcc->wiotag) + dcc->wiotag = fe_input_add (sok, FIA_WRITE, dcc_send_data, dcc); + + buf = malloc (prefs.dcc_blocksize); + if (!buf) + return TRUE; + + lseek (dcc->fp, dcc->pos, SEEK_SET); + len = read (dcc->fp, buf, prefs.dcc_blocksize); + if (len < 1) + goto abortit; + sent = send (sok, buf, len, 0); + + if (sent < 0 && !(would_block ())) + { +abortit: + free (buf); + EMIT_SIGNAL (XP_TE_DCCSENDFAIL, dcc->serv->front_session, + file_part (dcc->file), dcc->nick, + errorstring (sock_error ()), NULL, 0); + dcc_close (dcc, STAT_FAILED, FALSE); + return TRUE; + } + if (sent > 0) + { + dcc->pos += sent; + dcc->lasttime = time (0); + } + + /* have we sent it all yet? */ + if (dcc->pos >= dcc->size) + { + /* it's all sent now, so remove the WRITE/SEND handler */ + if (dcc->wiotag) + { + fe_input_remove (dcc->wiotag); + dcc->wiotag = 0; + } + } + + free (buf); + + return TRUE; +} + +static gboolean +dcc_handle_new_ack (struct DCC *dcc) +{ + guint32 ack; + char buf[16]; + gboolean done = FALSE; + + memcpy (&ack, dcc->ack_buf, 4); + dcc->ack = ntohl (ack); + + /* this could mess up when xfering >32bit files */ + if (dcc->size <= 0xffffffff) + { + /* fix for BitchX */ + if (dcc->ack < dcc->resumable) + dcc->ackoffset = TRUE; + if (dcc->ackoffset) + dcc->ack += dcc->resumable; + } + + /* DCC complete check */ + if (dcc->pos >= dcc->size && dcc->ack >= (dcc->size & 0xffffffff)) + { + dcc->ack = dcc->size; /* force 100% ack for >4 GB */ + dcc_close (dcc, STAT_DONE, FALSE); + dcc_calc_average_cps (dcc); /* this must be done _after_ dcc_close, or dcc_remove_from_sum will see the wrong value in dcc->cps */ + sprintf (buf, "%d", dcc->cps); + EMIT_SIGNAL (XP_TE_DCCSENDCOMP, dcc->serv->front_session, + file_part (dcc->file), dcc->nick, buf, NULL, 0); + done = TRUE; + } + else if ((!dcc->fastsend) && (dcc->ack >= (dcc->pos & 0xffffffff))) + { + dcc_send_data (NULL, 0, (gpointer)dcc); + } + +#ifdef USE_DCC64 + /* take the top 32 of "bytes send" and bottom 32 of "ack" */ + dcc->ack = (dcc->pos & G_GINT64_CONSTANT (0xffffffff00000000)) | + (dcc->ack & 0xffffffff); + /* dcc->ack is only used for CPS and PERCENTAGE calcs from now on... */ +#endif + + return done; +} + +static gboolean +dcc_read_ack (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + int len; + + while (1) + { + /* try to fill up 4 bytes */ + len = recv (dcc->sok, dcc->ack_buf, 4 - dcc->ack_pos, 0); + if (len < 1) + { + if (len < 0) + { + if (would_block ()) /* ok - keep waiting */ + return TRUE; + } + EMIT_SIGNAL (XP_TE_DCCSENDFAIL, dcc->serv->front_session, + file_part (dcc->file), dcc->nick, + errorstring ((len < 0) ? sock_error () : 0), NULL, 0); + dcc_close (dcc, STAT_FAILED, FALSE); + return TRUE; + } + + dcc->ack_pos += len; + if (dcc->ack_pos >= 4) + { + dcc->ack_pos = 0; + if (dcc_handle_new_ack (dcc)) + return TRUE; + } + /* loop again until would_block() returns true */ + } +} + +static gboolean +dcc_accept (GIOChannel *source, GIOCondition condition, struct DCC *dcc) +{ + char host[128]; + struct sockaddr_in CAddr; + int sok; + socklen_t len; + + len = sizeof (CAddr); + sok = accept (dcc->sok, (struct sockaddr *) &CAddr, &len); + fe_input_remove (dcc->iotag); + dcc->iotag = 0; + closesocket (dcc->sok); + if (sok < 0) + { + dcc->sok = -1; + dcc_close (dcc, STAT_FAILED, FALSE); + return TRUE; + } + set_nonblocking (sok); + dcc->sok = sok; + dcc->addr = ntohl (CAddr.sin_addr.s_addr); + + if (dcc->pasvid) + return dcc_connect_finished (NULL, 0, dcc); + + dcc->dccstat = STAT_ACTIVE; + dcc->lasttime = dcc->starttime = time (0); + dcc->fastsend = prefs.fastdccsend; + + snprintf (host, sizeof (host), "%s:%d", net_ip (dcc->addr), dcc->port); + + switch (dcc->type) + { + case TYPE_SEND: + if (dcc->fastsend) + dcc->wiotag = fe_input_add (sok, FIA_WRITE, dcc_send_data, dcc); + dcc->iotag = fe_input_add (sok, FIA_READ|FIA_EX, dcc_read_ack, dcc); + dcc_send_data (NULL, 0, (gpointer)dcc); + EMIT_SIGNAL (XP_TE_DCCCONSEND, dcc->serv->front_session, + dcc->nick, host, dcc->file, NULL, 0); + break; + + case TYPE_CHATSEND: + dcc_open_query (dcc->serv, dcc->nick); + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_read_chat, dcc); + dcc->dccchat = malloc (sizeof (struct dcc_chat)); + dcc->dccchat->pos = 0; + EMIT_SIGNAL (XP_TE_DCCCONCHAT, dcc->serv->front_session, + dcc->nick, host, NULL, NULL, 0); + break; + } + + fe_dcc_update (dcc); + + return TRUE; +} + +guint32 +dcc_get_my_address (void) /* the address we'll tell the other person */ +{ + struct hostent *dns_query; + guint32 addr = 0; + + if (prefs.ip_from_server && prefs.dcc_ip) + addr = prefs.dcc_ip; + else if (prefs.dcc_ip_str[0]) + { + dns_query = gethostbyname ((const char *) prefs.dcc_ip_str); + + if (dns_query != NULL && + dns_query->h_length == 4 && + dns_query->h_addr_list[0] != NULL) + { + /*we're offered at least one IPv4 address: we take the first*/ + addr = *((guint32*) dns_query->h_addr_list[0]); + } + } + + return addr; +} + +static int +dcc_listen_init (struct DCC *dcc, session *sess) +{ + guint32 my_addr; + struct sockaddr_in SAddr; + int i, bindretval = -1; + socklen_t len; + + dcc->sok = socket (AF_INET, SOCK_STREAM, 0); + if (dcc->sok == -1) + return FALSE; + + memset (&SAddr, 0, sizeof (struct sockaddr_in)); + + len = sizeof (SAddr); + getsockname (dcc->serv->sok, (struct sockaddr *) &SAddr, &len); + + SAddr.sin_family = AF_INET; + + /*if local_ip is specified use that*/ + if (prefs.local_ip != 0xffffffff) + { + my_addr = prefs.local_ip; + SAddr.sin_addr.s_addr = prefs.local_ip; + } + /*otherwise use the default*/ + else + my_addr = SAddr.sin_addr.s_addr; + + /*if we have a valid portrange try to use that*/ + if (prefs.first_dcc_send_port > 0) + { + SAddr.sin_port = 0; + i = 0; + while ((prefs.last_dcc_send_port > ntohs(SAddr.sin_port)) && + (bindretval == -1)) + { + SAddr.sin_port = htons (prefs.first_dcc_send_port + i); + i++; + /*printf("Trying to bind against port: %d\n",ntohs(SAddr.sin_port));*/ + bindretval = bind (dcc->sok, (struct sockaddr *) &SAddr, sizeof (SAddr)); + } + + /* with a small port range, reUseAddr is needed */ + len = 1; + setsockopt (dcc->sok, SOL_SOCKET, SO_REUSEADDR, (char *) &len, sizeof (len)); + + } else + { + /* try random port */ + SAddr.sin_port = 0; + bindretval = bind (dcc->sok, (struct sockaddr *) &SAddr, sizeof (SAddr)); + } + + if (bindretval == -1) + { + /* failed to bind */ + PrintText (sess, "Failed to bind to any address or port.\n"); + return FALSE; + } + + len = sizeof (SAddr); + getsockname (dcc->sok, (struct sockaddr *) &SAddr, &len); + + dcc->port = ntohs (SAddr.sin_port); + + /*if we have a dcc_ip, we use that, so the remote client can connect*/ + /*else we try to take an address from dcc_ip_str*/ + /*if something goes wrong we tell the client to connect to our LAN ip*/ + dcc->addr = dcc_get_my_address (); + + /*if nothing else worked we use the address we bound to*/ + if (dcc->addr == 0) + dcc->addr = my_addr; + + dcc->addr = ntohl (dcc->addr); + + set_nonblocking (dcc->sok); + listen (dcc->sok, 1); + set_blocking (dcc->sok); + + dcc->iotag = fe_input_add (dcc->sok, FIA_READ|FIA_EX, dcc_accept, dcc); + + return TRUE; +} + +static struct session *dccsess; +static char *dccto; /* lame!! */ +static int dccmaxcps; +static int recursive = FALSE; + +static void +dcc_send_wild (char *file) +{ + dcc_send (dccsess, dccto, file, dccmaxcps, 0); +} + +void +dcc_send (struct session *sess, char *to, char *file, int maxcps, int passive) +{ + char outbuf[512]; + struct stat st; + struct DCC *dcc; + char *file_fs; + + /* this is utf8 */ + file = expand_homedir (file); + + if (!recursive && (strchr (file, '*') || strchr (file, '?'))) + { + char path[256]; + char wild[256]; + char *path_fs; /* local filesystem encoding */ + + safe_strcpy (wild, file_part (file), sizeof (wild)); + path_part (file, path, sizeof (path)); + if (path[0] != '/' || path[1] != '\0') + path[strlen (path) - 1] = 0; /* remove trailing slash */ + + dccsess = sess; + dccto = to; + dccmaxcps = maxcps; + + free (file); + + /* for_files() will use opendir, so we need local FS encoding */ + path_fs = xchat_filename_from_utf8 (path, -1, 0, 0, 0); + if (path_fs) + { + recursive = TRUE; + for_files (path_fs, wild, dcc_send_wild); + recursive = FALSE; + g_free (path_fs); + } + + return; + } + + dcc = new_dcc (); + if (!dcc) + return; + dcc->file = file; + dcc->maxcps = maxcps; + + /* get the local filesystem encoding */ + file_fs = xchat_filename_from_utf8 (file, -1, 0, 0, 0); + + if (stat (file_fs, &st) != -1) + { + +#ifndef USE_DCC64 + if (sizeof (st.st_size) > 4 && st.st_size > 4294967295U) + { + PrintText (sess, "Cannot send files larger than 4 GB.\n"); + goto xit; + } +#endif + + if (!(*file_part (file_fs)) || S_ISDIR (st.st_mode) || st.st_size < 1) + { + PrintText (sess, "Cannot send directories or empty files.\n"); + goto xit; + } + + dcc->starttime = dcc->offertime = time (0); + dcc->serv = sess->server; + dcc->dccstat = STAT_QUEUED; + dcc->size = st.st_size; + dcc->type = TYPE_SEND; + dcc->fp = open (file_fs, OFLAGS | O_RDONLY); + if (dcc->fp != -1) + { + g_free (file_fs); + if (passive || dcc_listen_init (dcc, sess)) + { + char havespaces = 0; + file = dcc->file; + while (*file) + { + if (*file == ' ') + { + if (prefs.dcc_send_fillspaces) + *file = '_'; + else + havespaces = 1; + } + file++; + } + dcc->nick = strdup (to); + if (prefs.autoopendccsendwindow) + { + if (fe_dcc_open_send_win (TRUE)) /* already open? add */ + fe_dcc_add (dcc); + } else + fe_dcc_add (dcc); + + if (passive) + { + dcc->pasvid = new_id(); + snprintf (outbuf, sizeof (outbuf), (havespaces) ? + "DCC SEND \"%s\" 199 0 %"DCC_SFMT" %d" : + "DCC SEND %s 199 0 %"DCC_SFMT" %d", + file_part (dcc->file), + dcc->size, dcc->pasvid); + } else + { + snprintf (outbuf, sizeof (outbuf), (havespaces) ? + "DCC SEND \"%s\" %u %d %"DCC_SFMT : + "DCC SEND %s %u %d %"DCC_SFMT, + file_part (dcc->file), dcc->addr, + dcc->port, dcc->size); + } + sess->server->p_ctcp (sess->server, to, outbuf); + + EMIT_SIGNAL (XP_TE_DCCOFFER, sess, file_part (dcc->file), + to, dcc->file, NULL, 0); + } else + { + dcc_close (dcc, 0, TRUE); + } + return; + } + } + PrintTextf (sess, _("Cannot access %s\n"), dcc->file); + PrintTextf (sess, "%s %d: %s\n", _("Error"), errno, errorstring (errno)); +xit: + g_free (file_fs); + dcc_close (dcc, 0, TRUE); +} + +static struct DCC * +find_dcc_from_id (int id, int type) +{ + struct DCC *dcc; + GSList *list = dcc_list; + while (list) + { + dcc = (struct DCC *) list->data; + if (dcc->pasvid == id && + dcc->dccstat == STAT_QUEUED && dcc->type == type) + return dcc; + list = list->next; + } + return 0; +} + +static struct DCC * +find_dcc_from_port (int port, int type) +{ + struct DCC *dcc; + GSList *list = dcc_list; + while (list) + { + dcc = (struct DCC *) list->data; + if (dcc->port == port && + dcc->dccstat == STAT_QUEUED && dcc->type == type) + return dcc; + list = list->next; + } + return 0; +} + +struct DCC * +find_dcc (char *nick, char *file, int type) +{ + GSList *list = dcc_list; + struct DCC *dcc; + while (list) + { + dcc = (struct DCC *) list->data; + if (nick == NULL || !rfc_casecmp (nick, dcc->nick)) + { + if (type == -1 || dcc->type == type) + { + if (!file[0]) + return dcc; + if (!strcasecmp (file, file_part (dcc->file))) + return dcc; + if (!strcasecmp (file, dcc->file)) + return dcc; + } + } + list = list->next; + } + return 0; +} + +/* called when we receive a NICK change from server */ + +void +dcc_change_nick (struct server *serv, char *oldnick, char *newnick) +{ + struct DCC *dcc; + GSList *list = dcc_list; + + while (list) + { + dcc = (struct DCC *) list->data; + if (dcc->serv == serv) + { + if (!serv->p_cmp (dcc->nick, oldnick)) + { + if (dcc->nick) + free (dcc->nick); + dcc->nick = strdup (newnick); + } + } + list = list->next; + } +} + +/* is the destination file the same? new_dcc is not opened yet */ + +static int +is_same_file (struct DCC *dcc, struct DCC *new_dcc) +{ + struct stat st_a, st_b; + + /* if it's the same filename, must be same */ + if (strcmp (dcc->destfile, new_dcc->destfile) == 0) + return TRUE; + + /* now handle case-insensitive Filesystems: HFS+, FAT */ +#ifdef WIN32 +#warning no win32 implementation - behaviour may be unreliable +#else + /* this fstat() shouldn't really fail */ + if ((dcc->fp == -1 ? stat (dcc->destfile_fs, &st_a) : fstat (dcc->fp, &st_a)) == -1) + return FALSE; + if (stat (new_dcc->destfile_fs, &st_b) == -1) + return FALSE; + + /* same inode, same device, same file! */ + if (st_a.st_ino == st_b.st_ino && + st_a.st_dev == st_b.st_dev) + return TRUE; +#endif + + return FALSE; +} + +static int +is_resumable (struct DCC *dcc) +{ + dcc->resumable = 0; + + /* Check the file size */ + if (access (dcc->destfile_fs, W_OK) == 0) + { + struct stat st; + + if (stat (dcc->destfile_fs, &st) != -1) + { + if (st.st_size < dcc->size) + { + dcc->resumable = st.st_size; + dcc->pos = st.st_size; + } + else + dcc->resume_error = 2; + } else + { + dcc->resume_errno = errno; + dcc->resume_error = 1; + } + } else + { + dcc->resume_errno = errno; + dcc->resume_error = 1; + } + + /* Now verify that this DCC is not already in progress from someone else */ + + if (dcc->resumable) + { + GSList *list = dcc_list; + struct DCC *d; + while (list) + { + d = list->data; + if (d->type == TYPE_RECV && d->dccstat != STAT_ABORTED && + d->dccstat != STAT_DONE && d->dccstat != STAT_FAILED) + { + if (d != dcc && is_same_file (d, dcc)) + { + dcc->resume_error = 3; /* dccgui.c uses it */ + dcc->resumable = 0; + dcc->pos = 0; + break; + } + } + list = list->next; + } + } + + return dcc->resumable; +} + +void +dcc_get (struct DCC *dcc) +{ + switch (dcc->dccstat) + { + case STAT_QUEUED: + if (dcc->type != TYPE_CHATSEND) + { + if (dcc->type == TYPE_RECV && prefs.autoresume && dcc->resumable) + { + dcc_resume (dcc); + } + else + { + dcc->resumable = 0; + dcc->pos = 0; + dcc_connect (dcc); + } + } + break; + case STAT_DONE: + case STAT_FAILED: + case STAT_ABORTED: + dcc_close (dcc, 0, TRUE); + break; + } +} + +/* for "Save As..." dialog */ + +void +dcc_get_with_destfile (struct DCC *dcc, char *file) +{ + g_free (dcc->destfile); + dcc->destfile = g_strdup (file); /* utf-8 */ + + /* get the local filesystem encoding */ + g_free (dcc->destfile_fs); + dcc->destfile_fs = xchat_filename_from_utf8 (dcc->destfile, -1, 0, 0, 0); + + /* since destfile changed, must check resumability again */ + is_resumable (dcc); + + dcc_get (dcc); +} + +void +dcc_get_nick (struct session *sess, char *nick) +{ + struct DCC *dcc; + GSList *list = dcc_list; + while (list) + { + dcc = (struct DCC *) list->data; + if (!sess->server->p_cmp (nick, dcc->nick)) + { + if (dcc->dccstat == STAT_QUEUED && dcc->type == TYPE_RECV) + { + dcc->resumable = 0; + dcc->pos = 0; + dcc->ack = 0; + dcc_connect (dcc); + return; + } + } + list = list->next; + } + if (sess) + EMIT_SIGNAL (XP_TE_DCCIVAL, sess, NULL, NULL, NULL, NULL, 0); +} + +static struct DCC * +new_dcc (void) +{ + struct DCC *dcc = malloc (sizeof (struct DCC)); + if (!dcc) + return 0; + memset (dcc, 0, sizeof (struct DCC)); + dcc->sok = -1; + dcc->fp = -1; + dcc_list = g_slist_prepend (dcc_list, dcc); + return (dcc); +} + +void +dcc_chat (struct session *sess, char *nick, int passive) +{ + char outbuf[512]; + struct DCC *dcc; + + dcc = find_dcc (nick, "", TYPE_CHATSEND); + if (dcc) + { + switch (dcc->dccstat) + { + case STAT_ACTIVE: + case STAT_QUEUED: + case STAT_CONNECTING: + EMIT_SIGNAL (XP_TE_DCCCHATREOFFER, sess, nick, NULL, NULL, NULL, 0); + return; + case STAT_ABORTED: + case STAT_FAILED: + dcc_close (dcc, 0, TRUE); + } + } + dcc = find_dcc (nick, "", TYPE_CHATRECV); + if (dcc) + { + switch (dcc->dccstat) + { + case STAT_QUEUED: + dcc_connect (dcc); + break; + case STAT_FAILED: + case STAT_ABORTED: + dcc_close (dcc, 0, TRUE); + } + return; + } + /* offer DCC CHAT */ + + dcc = new_dcc (); + if (!dcc) + return; + dcc->starttime = dcc->offertime = time (0); + dcc->serv = sess->server; + dcc->dccstat = STAT_QUEUED; + dcc->type = TYPE_CHATSEND; + dcc->nick = strdup (nick); + if (passive || dcc_listen_init (dcc, sess)) + { + if (prefs.autoopendccchatwindow) + { + if (fe_dcc_open_chat_win (TRUE)) /* already open? add only */ + fe_dcc_add (dcc); + } else + fe_dcc_add (dcc); + + if (passive) + { + dcc->pasvid = new_id (); + snprintf (outbuf, sizeof (outbuf), "DCC CHAT chat 199 %d %d", + dcc->port, dcc->pasvid); + } else + { + snprintf (outbuf, sizeof (outbuf), "DCC CHAT chat %u %d", + dcc->addr, dcc->port); + } + dcc->serv->p_ctcp (dcc->serv, nick, outbuf); + EMIT_SIGNAL (XP_TE_DCCCHATOFFERING, sess, nick, NULL, NULL, NULL, 0); + } else + { + dcc_close (dcc, 0, TRUE); + } +} + +static void +dcc_malformed (struct session *sess, char *nick, char *data) +{ + EMIT_SIGNAL (XP_TE_MALFORMED, sess, nick, data, NULL, NULL, 0); +} + +int +dcc_resume (struct DCC *dcc) +{ + char tbuf[500]; + + if (dcc->dccstat == STAT_QUEUED && dcc->resumable) + { + dcc->resume_sent = 1; + /* filename contains spaces? Quote them! */ + snprintf (tbuf, sizeof (tbuf) - 10, strchr (dcc->file, ' ') ? + "DCC RESUME \"%s\" %d %"DCC_SFMT : + "DCC RESUME %s %d %"DCC_SFMT, + dcc->file, dcc->port, dcc->resumable); + + if (dcc->pasvid) + sprintf (tbuf + strlen (tbuf), " %d", dcc->pasvid); + + dcc->serv->p_ctcp (dcc->serv, dcc->nick, tbuf); + return 1; + } + + return 0; +} + +static void +dcc_confirm_send (void *ud) +{ + struct DCC *dcc = (struct DCC *) ud; + dcc_get (dcc); +} + +static void +dcc_deny_send (void *ud) +{ + struct DCC *dcc = (struct DCC *) ud; + dcc_abort (dcc->serv->front_session, dcc); +} + +static void +dcc_confirm_chat (void *ud) +{ + struct DCC *dcc = (struct DCC *) ud; + dcc_connect (dcc); +} + +static void +dcc_deny_chat (void *ud) +{ + struct DCC *dcc = (struct DCC *) ud; + dcc_abort (dcc->serv->front_session, dcc); +} + +static struct DCC * +dcc_add_chat (session *sess, char *nick, int port, guint32 addr, int pasvid) +{ + struct DCC *dcc; + + dcc = new_dcc (); + if (dcc) + { + dcc->serv = sess->server; + dcc->type = TYPE_CHATRECV; + dcc->dccstat = STAT_QUEUED; + dcc->addr = addr; + dcc->port = port; + dcc->pasvid = pasvid; + dcc->nick = strdup (nick); + dcc->starttime = time (0); + + EMIT_SIGNAL (XP_TE_DCCCHATOFFER, sess->server->front_session, nick, + NULL, NULL, NULL, 0); + + if (prefs.autoopendccchatwindow) + { + if (fe_dcc_open_chat_win (TRUE)) /* already open? add only */ + fe_dcc_add (dcc); + } else + fe_dcc_add (dcc); + + if (prefs.autodccchat == 1) + dcc_connect (dcc); + else if (prefs.autodccchat == 2) + { + char buff[128]; + snprintf (buff, sizeof (buff), "%s is offering DCC Chat. Do you want to accept?", nick); + fe_confirm (buff, dcc_confirm_chat, dcc_deny_chat, dcc); + } + } + + return dcc; +} + +static struct DCC * +dcc_add_file (session *sess, char *file, DCC_SIZE size, int port, char *nick, guint32 addr, int pasvid) +{ + struct DCC *dcc; + char tbuf[512]; + + dcc = new_dcc (); + if (dcc) + { + dcc->file = strdup (file); + + dcc->destfile = g_malloc (strlen (prefs.dccdir) + strlen (nick) + + strlen (file) + 4); + + strcpy (dcc->destfile, prefs.dccdir); + if (prefs.dccdir[strlen (prefs.dccdir) - 1] != '/') + strcat (dcc->destfile, "/"); + if (prefs.dccwithnick) + { +#ifdef WIN32 + char *t = strlen (dcc->destfile) + dcc->destfile; + strcpy (t, nick); + while (*t) + { + if (*t == '\\' || *t == '|') + *t = '_'; + t++; + } +#else + strcat (dcc->destfile, nick); +#endif + strcat (dcc->destfile, "."); + } + strcat (dcc->destfile, file); + + /* get the local filesystem encoding */ + dcc->destfile_fs = xchat_filename_from_utf8 (dcc->destfile, -1, 0, 0, 0); + + dcc->resumable = 0; + dcc->pos = 0; + dcc->serv = sess->server; + dcc->type = TYPE_RECV; + dcc->dccstat = STAT_QUEUED; + dcc->addr = addr; + dcc->port = port; + dcc->pasvid = pasvid; + dcc->size = size; + dcc->nick = strdup (nick); + dcc->maxcps = prefs.dcc_max_get_cps; + + is_resumable (dcc); + + /* autodccsend is really autodccrecv.. right? */ + + if (prefs.autodccsend == 1) + { + dcc_get (dcc); + } + else if (prefs.autodccsend == 2) + { + snprintf (tbuf, sizeof (tbuf), _("%s is offering \"%s\". Do you want to accept?"), nick, file); + fe_confirm (tbuf, dcc_confirm_send, dcc_deny_send, dcc); + } + if (prefs.autoopendccrecvwindow) + { + if (fe_dcc_open_recv_win (TRUE)) /* was already open? just add*/ + fe_dcc_add (dcc); + } else + fe_dcc_add (dcc); + } + sprintf (tbuf, "%"DCC_SFMT, size); + snprintf (tbuf + 24, 300, "%s:%d", net_ip (addr), port); + EMIT_SIGNAL (XP_TE_DCCSENDOFFER, sess->server->front_session, nick, + file, tbuf, tbuf + 24, 0); + + return dcc; +} + +void +handle_dcc (struct session *sess, char *nick, char *word[], + char *word_eol[]) +{ + char tbuf[512]; + struct DCC *dcc; + char *type = word[5]; + int port, pasvid = 0; + guint32 addr; + DCC_SIZE size; + int psend = 0; + + if (!strcasecmp (type, "CHAT")) + { + port = atoi (word[8]); + addr = strtoul (word[7], NULL, 10); + + if (port == 0) + pasvid = atoi (word[9]); + else if (word[9][0] != 0) + { + pasvid = atoi (word[9]); + psend = 1; + } + + if (!addr /*|| (port < 1024 && port != 0)*/ + || port > 0xffff || (port == 0 && pasvid == 0)) + { + dcc_malformed (sess, nick, word_eol[4] + 2); + return; + } + + if (psend) + { + dcc = find_dcc_from_id (pasvid, TYPE_CHATSEND); + if (dcc) + { + dcc->addr = addr; + dcc->port = port; + dcc_connect (dcc); + } else + { + dcc_malformed (sess, nick, word_eol[4] + 2); + } + return; + } + + dcc = find_dcc (nick, "", TYPE_CHATSEND); + if (dcc) + dcc_close (dcc, 0, TRUE); + + dcc = find_dcc (nick, "", TYPE_CHATRECV); + if (dcc) + dcc_close (dcc, 0, TRUE); + + dcc_add_chat (sess, nick, port, addr, pasvid); + return; + } + + if (!strcasecmp (type, "Resume")) + { + port = atoi (word[7]); + + if (port == 0) + { /* PASSIVE */ + pasvid = atoi(word[9]); + dcc = find_dcc_from_id(pasvid, TYPE_SEND); + } else + { + dcc = find_dcc_from_port (port, TYPE_SEND); + } + if (!dcc) + dcc = find_dcc (nick, word[6], TYPE_SEND); + if (dcc) + { + size = BIG_STR_TO_INT (word[8]); + dcc->resumable = size; + if (dcc->resumable < dcc->size) + { + dcc->pos = dcc->resumable; + dcc->ack = dcc->resumable; + lseek (dcc->fp, dcc->pos, SEEK_SET); + + /* Checking if dcc is passive and if filename contains spaces */ + if (dcc->pasvid) + snprintf (tbuf, sizeof (tbuf), strchr (file_part (dcc->file), ' ') ? + "DCC ACCEPT \"%s\" %d %"DCC_SFMT" %d" : + "DCC ACCEPT %s %d %"DCC_SFMT" %d", + file_part (dcc->file), port, dcc->resumable, dcc->pasvid); + else + snprintf (tbuf, sizeof (tbuf), strchr (file_part (dcc->file), ' ') ? + "DCC ACCEPT \"%s\" %d %"DCC_SFMT : + "DCC ACCEPT %s %d %"DCC_SFMT, + file_part (dcc->file), port, dcc->resumable); + + dcc->serv->p_ctcp (dcc->serv, dcc->nick, tbuf); + } + sprintf (tbuf, "%"DCC_SFMT, dcc->pos); + EMIT_SIGNAL (XP_TE_DCCRESUMEREQUEST, sess, nick, + file_part (dcc->file), tbuf, NULL, 0); + } + return; + } + if (!strcasecmp (type, "Accept")) + { + port = atoi (word[7]); + dcc = find_dcc_from_port (port, TYPE_RECV); + if (dcc && dcc->dccstat == STAT_QUEUED) + { + dcc_connect (dcc); + } + return; + } + if (!strcasecmp (type, "SEND")) + { + char *file = file_part (word[6]); + + port = atoi (word[8]); + addr = strtoul (word[7], NULL, 10); + size = BIG_STR_TO_INT (word[9]); + + if (port == 0) /* Passive dcc requested */ + pasvid = atoi (word[10]); + else if (word[10][0] != 0) + { + /* Requesting passive dcc. + * Destination user of an active dcc is giving his + * TRUE address/port/pasvid data. + * This information will be used later to + * establish the connection to the user. + * We can recognize this type of dcc using word[10] + * because this field is always null (no pasvid) + * in normal dcc sends. + */ + pasvid = atoi (word[10]); + psend = 1; + } + + + if (!addr || !size /*|| (port < 1024 && port != 0)*/ + || port > 0xffff || (port == 0 && pasvid == 0)) + { + dcc_malformed (sess, nick, word_eol[4] + 2); + return; + } + + if (psend) + { + /* Third Step of Passive send. + * Connecting to the destination and finally + * sending file. + */ + dcc = find_dcc_from_id (pasvid, TYPE_SEND); + if (dcc) + { + dcc->addr = addr; + dcc->port = port; + dcc_connect (dcc); + } else + { + dcc_malformed (sess, nick, word_eol[4] + 2); + } + return; + } + + dcc_add_file (sess, file, size, port, nick, addr, pasvid); + + } else + { + EMIT_SIGNAL (XP_TE_DCCGENERICOFFER, sess->server->front_session, + word_eol[4] + 2, nick, NULL, NULL, 0); + } +} + +void +dcc_show_list (struct session *sess) +{ + int i = 0; + struct DCC *dcc; + GSList *list = dcc_list; + + EMIT_SIGNAL (XP_TE_DCCHEAD, sess, NULL, NULL, NULL, NULL, 0); + while (list) + { + dcc = (struct DCC *) list->data; + i++; + PrintTextf (sess, " %s %-10.10s %-7.7s %-7"DCC_SFMT" %-7"DCC_SFMT" %s\n", + dcctypes[dcc->type], dcc->nick, + _(dccstat[dcc->dccstat].name), dcc->size, dcc->pos, + file_part (dcc->file)); + list = list->next; + } + if (!i) + PrintText (sess, _("No active DCCs\n")); +} diff --git a/src/common/dcc.h b/src/common/dcc.h new file mode 100644 index 00000000..da4ce979 --- /dev/null +++ b/src/common/dcc.h @@ -0,0 +1,117 @@ +/* dcc.h */ + +#include <time.h> /* for time_t */ + +#ifndef XCHAT_DCC_H +#define XCHAT_DCC_H + +#define STAT_QUEUED 0 +#define STAT_ACTIVE 1 +#define STAT_FAILED 2 +#define STAT_DONE 3 +#define STAT_CONNECTING 4 +#define STAT_ABORTED 5 + +#define TYPE_SEND 0 +#define TYPE_RECV 1 +#define TYPE_CHATRECV 2 +#define TYPE_CHATSEND 3 + +#define CPS_AVG_WINDOW 10 + +/* can we do 64-bit dcc? */ +#if defined(G_GINT64_FORMAT) && defined(HAVE_STRTOULL) +#define USE_DCC64 +/* we really get only 63 bits, since st_size is signed */ +#define DCC_SIZE gint64 +#define DCC_SFMT G_GINT64_FORMAT +#else +#define DCC_SIZE unsigned int +#define DCC_SFMT "u" +#endif + +struct DCC +{ + struct server *serv; + struct dcc_chat *dccchat; + struct proxy_state *proxy; + guint32 addr; /* the 32bit IP number, host byte order */ + int fp; /* file pointer */ + int sok; + int iotag; /* reading io tag */ + int wiotag; /* writing/sending io tag */ + int port; + int pasvid; /* mIRC's passive DCC id */ + int cps; + int resume_error; + int resume_errno; + + GTimeVal lastcpstv, firstcpstv; + DCC_SIZE lastcpspos; + int maxcps; + + unsigned char ack_buf[4]; /* buffer for reading 4-byte ack */ + int ack_pos; + + DCC_SIZE size; + DCC_SIZE resumable; + DCC_SIZE ack; + DCC_SIZE pos; + time_t starttime; + time_t offertime; + time_t lasttime; + char *file; /* utf8 */ + char *destfile; /* utf8 */ + char *destfile_fs; /* local filesystem encoding */ + char *nick; + unsigned char type; /* 0 = SEND 1 = RECV 2 = CHAT */ + unsigned char dccstat; /* 0 = QUEUED 1 = ACTIVE 2 = FAILED 3 = DONE */ + unsigned int resume_sent:1; /* resume request sent */ + unsigned int fastsend:1; + unsigned int ackoffset:1; /* is reciever sending acks as an offset from */ + /* the resume point? */ + unsigned int throttled:2; /* 0x1 = per send/get throttle + 0x2 = global throttle */ +}; + +#define MAX_PROXY_BUFFER 1024 +struct proxy_state +{ + int phase; + unsigned char buffer[MAX_PROXY_BUFFER]; + int buffersize; + int bufferused; +}; + +struct dcc_chat +{ + char linebuf[2048]; + int pos; +}; + +struct dccstat_info +{ + char *name; /* Display name */ + int color; /* Display color (index into colors[] ) */ +}; + +extern struct dccstat_info dccstat[]; + +gboolean is_dcc (struct DCC *dcc); +void dcc_abort (session *sess, struct DCC *dcc); +void dcc_get (struct DCC *dcc); +int dcc_resume (struct DCC *dcc); +void dcc_check_timeouts (void); +void dcc_change_nick (server *serv, char *oldnick, char *newnick); +void dcc_notify_kill (struct server *serv); +struct DCC *dcc_write_chat (char *nick, char *text); +void dcc_send (struct session *sess, char *to, char *file, int maxcps, int passive); +struct DCC *find_dcc (char *nick, char *file, int type); +void dcc_get_nick (struct session *sess, char *nick); +void dcc_chat (session *sess, char *nick, int passive); +void handle_dcc (session *sess, char *nick, char *word[], char *word_eol[]); +void dcc_show_list (session *sess); +guint32 dcc_get_my_address (void); +void dcc_get_with_destfile (struct DCC *dcc, char *utf8file); + +#endif diff --git a/src/common/fe.h b/src/common/fe.h new file mode 100644 index 00000000..16526581 --- /dev/null +++ b/src/common/fe.h @@ -0,0 +1,162 @@ +#include "userlist.h" +#include "dcc.h" + +#ifndef XCHAT_FE_H +#define XCHAT_FE_H + +/* for storage of /menu entries */ +typedef struct +{ + gint32 pos; /* position */ + gint16 modifier; /* keybinding */ + gint16 root_offset; /* bytes to offset ->path */ + + char is_main; /* is part of the Main menu? (not a popup) */ + char state; /* state of toggle items */ + char markup; /* use pango markup? */ + char enable; /* enabled? sensitivity */ + + int key; + char *path; + char *label; + char *cmd; + char *ucmd; /* unselect command (toggles) */ + char *group; /* for radio items or NULL */ + char *icon; /* filename */ +} menu_entry; + +int fe_args (int argc, char *argv[]); +void fe_init (void); +void fe_main (void); +void fe_cleanup (void); +void fe_exit (void); +int fe_timeout_add (int interval, void *callback, void *userdata); +void fe_timeout_remove (int tag); +void fe_new_window (struct session *sess, int focus); +void fe_new_server (struct server *serv); +void fe_add_rawlog (struct server *serv, char *text, int len, int outbound); +#define FE_MSG_WAIT 1 +#define FE_MSG_INFO 2 +#define FE_MSG_WARN 4 +#define FE_MSG_ERROR 8 +#define FE_MSG_MARKUP 16 +void fe_message (char *msg, int flags); +#define FIA_READ 1 +#define FIA_WRITE 2 +#define FIA_EX 4 +#define FIA_FD 8 +int fe_input_add (int sok, int flags, void *func, void *data); +void fe_input_remove (int tag); +void fe_idle_add (void *func, void *data); +void fe_set_topic (struct session *sess, char *topic, char *stripped_topic); +void fe_set_hilight (struct session *sess); +void fe_set_tab_color (struct session *sess, int col); +void fe_flash_window (struct session *sess); +void fe_update_mode_buttons (struct session *sess, char mode, char sign); +void fe_update_channel_key (struct session *sess); +void fe_update_channel_limit (struct session *sess); +int fe_is_chanwindow (struct server *serv); +void fe_add_chan_list (struct server *serv, char *chan, char *users, + char *topic); +void fe_chan_list_end (struct server *serv); +int fe_is_banwindow (struct session *sess); +void fe_add_ban_list (struct session *sess, char *mask, char *who, char *when, int is_exemption); +void fe_ban_list_end (struct session *sess, int is_exemption); +void fe_notify_update (char *name); +void fe_notify_ask (char *name, char *networks); +void fe_text_clear (struct session *sess, int lines); +void fe_close_window (struct session *sess); +void fe_progressbar_start (struct session *sess); +void fe_progressbar_end (struct server *serv); +void fe_print_text (struct session *sess, char *text, time_t stamp); +void fe_userlist_insert (struct session *sess, struct User *newuser, int row, int sel); +int fe_userlist_remove (struct session *sess, struct User *user); +void fe_userlist_rehash (struct session *sess, struct User *user); +void fe_userlist_update (struct session *sess, struct User *user); +void fe_userlist_move (struct session *sess, struct User *user, int new_row); +void fe_userlist_numbers (struct session *sess); +void fe_userlist_clear (struct session *sess); +void fe_userlist_set_selected (struct session *sess); +void fe_uselect (session *sess, char *word[], int do_clear, int scroll_to); +void fe_dcc_add (struct DCC *dcc); +void fe_dcc_update (struct DCC *dcc); +void fe_dcc_remove (struct DCC *dcc); +int fe_dcc_open_recv_win (int passive); +int fe_dcc_open_send_win (int passive); +int fe_dcc_open_chat_win (int passive); +void fe_clear_channel (struct session *sess); +void fe_session_callback (struct session *sess); +void fe_server_callback (struct server *serv); +void fe_url_add (const char *text); +void fe_pluginlist_update (void); +void fe_buttons_update (struct session *sess); +void fe_dlgbuttons_update (struct session *sess); +void fe_dcc_send_filereq (struct session *sess, char *nick, int maxcps, int passive); +void fe_set_channel (struct session *sess); +void fe_set_title (struct session *sess); +void fe_set_nonchannel (struct session *sess, int state); +void fe_set_nick (struct server *serv, char *newnick); +void fe_ignore_update (int level); +void fe_beep (void); +void fe_lastlog (session *sess, session *lastlog_sess, char *sstr, gboolean regexp); +void fe_set_lag (server *serv, int lag); +void fe_set_throttle (server *serv); +void fe_set_away (server *serv); +void fe_serverlist_open (session *sess); +void fe_get_str (char *prompt, char *def, void *callback, void *ud); +void fe_get_int (char *prompt, int def, void *callback, void *ud); +#define FRF_WRITE 1 /* save file */ +#define FRF_MULTIPLE 2 /* multi-select */ +#define FRF_ADDFOLDER 4 /* add ~/.xchat2 to favourites */ +#define FRF_CHOOSEFOLDER 8 /* choosing a folder only */ +#define FRF_FILTERISINITIAL 16 /* unused */ +#define FRF_NOASKOVERWRITE 32 /* don't ask to overwrite existing files */ +void fe_get_file (const char *title, char *initial, + void (*callback) (void *userdata, char *file), void *userdata, + int flags); +typedef enum { + FE_GUI_HIDE, + FE_GUI_SHOW, + FE_GUI_FOCUS, + FE_GUI_FLASH, + FE_GUI_COLOR, + FE_GUI_ICONIFY, + FE_GUI_MENU, + FE_GUI_ATTACH, + FE_GUI_APPLY, +} fe_gui_action; +void fe_ctrl_gui (session *sess, fe_gui_action action, int arg); +int fe_gui_info (session *sess, int info_type); +void *fe_gui_info_ptr (session *sess, int info_type); +void fe_confirm (const char *message, void (*yesproc)(void *), void (*noproc)(void *), void *ud); +char *fe_get_inputbox_contents (struct session *sess); +int fe_get_inputbox_cursor (struct session *sess); +void fe_set_inputbox_contents (struct session *sess, char *text); +void fe_set_inputbox_cursor (struct session *sess, int delta, int pos); +void fe_open_url (const char *url); +void fe_menu_del (menu_entry *); +char *fe_menu_add (menu_entry *); +void fe_menu_update (menu_entry *); +#define FE_SE_CONNECT 0 +#define FE_SE_LOGGEDIN 1 +#define FE_SE_DISCONNECT 2 +#define FE_SE_RECONDELAY 3 +#define FE_SE_CONNECTING 4 +void fe_server_event (server *serv, int type, int arg); +/* pass NULL filename2 for default xchat icon */ +void fe_tray_set_flash (const char *filename1, const char *filename2, int timeout); +/* pass NULL filename for default xchat icon */ +void fe_tray_set_file (const char *filename); +typedef enum +{ + FE_ICON_NORMAL = 0, + FE_ICON_MESSAGE = 2, + FE_ICON_HIGHLIGHT = 5, + FE_ICON_PRIVMSG = 8, + FE_ICON_FILEOFFER = 11 +} feicon; +void fe_tray_set_icon (feicon icon); +void fe_tray_set_tooltip (const char *text); +void fe_tray_set_balloon (const char *title, const char *text); + +#endif diff --git a/src/common/history.c b/src/common/history.c new file mode 100644 index 00000000..1cd6b508 --- /dev/null +++ b/src/common/history.c @@ -0,0 +1,121 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <string.h> +#include <stdlib.h> +#include "history.h" + +void +history_add (struct history *his, char *text) +{ + if (his->lines[his->realpos]) + free (his->lines[his->realpos]); + his->lines[his->realpos] = strdup (text); + his->realpos++; + if (his->realpos == HISTORY_SIZE) + his->realpos = 0; + his->pos = his->realpos; +} + +void +history_free (struct history *his) +{ + int i; + for (i = 0; i < HISTORY_SIZE; i++) + { + if (his->lines[i]) + { + free (his->lines[i]); + his->lines[i] = 0; + } + } +} + +char * +history_down (struct history *his) +{ + int next; + + if (his->pos == his->realpos) /* allow down only after up */ + return 0; + if (his->realpos == 0) + { + if (his->pos == HISTORY_SIZE - 1) + { + his->pos = 0; + return ""; + } + } else + { + if (his->pos == his->realpos - 1) + { + his->pos++; + return ""; + } + } + + next = 0; + if (his->pos < HISTORY_SIZE - 1) + next = his->pos + 1; + + if (his->lines[next]) + { + his->pos = next; + return his->lines[his->pos]; + } + + return 0; +} + +char * +history_up (struct history *his, char *current_text) +{ + int next; + + if (his->realpos == HISTORY_SIZE - 1) + { + if (his->pos == 0) + return 0; + } else + { + if (his->pos == his->realpos + 1) + return 0; + } + + next = HISTORY_SIZE - 1; + if (his->pos != 0) + next = his->pos - 1; + + if (his->lines[next]) + { + if + ( + current_text[0] && strcmp(current_text, his->lines[next]) && + (!his->lines[his->pos] || strcmp(current_text, his->lines[his->pos])) && + (!his->lines[his->realpos] || strcmp(current_text, his->lines[his->pos])) + ) + { + history_add (his, current_text); + } + + his->pos = next; + return his->lines[his->pos]; + } + + return 0; +} diff --git a/src/common/history.h b/src/common/history.h new file mode 100644 index 00000000..5267f1fc --- /dev/null +++ b/src/common/history.h @@ -0,0 +1,18 @@ +#ifndef XCHAT_HISTORY_H +#define XCHAT_HISTORY_H + +#define HISTORY_SIZE 100 + +struct history +{ + char *lines[HISTORY_SIZE]; + int pos; + int realpos; +}; + +void history_add (struct history *his, char *text); +void history_free (struct history *his); +char *history_up (struct history *his, char *current_text); +char *history_down (struct history *his); + +#endif diff --git a/src/common/identd.c b/src/common/identd.c new file mode 100644 index 00000000..919282ea --- /dev/null +++ b/src/common/identd.c @@ -0,0 +1,89 @@ +/* simple identd server for xchat under win32 */ + + +static int identd_is_running = FALSE; + + +static int +identd (char *username) +{ + int sok, read_sok, len; + char *p; + char buf[256]; + char outbuf[256]; + struct sockaddr_in addr; + + sok = socket (AF_INET, SOCK_STREAM, 0); + if (sok == INVALID_SOCKET) + { + free (username); + return 0; + } + + len = 1; + setsockopt (sok, SOL_SOCKET, SO_REUSEADDR, (char *) &len, sizeof (len)); + + memset (&addr, 0, sizeof (addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons (113); + + if (bind (sok, (struct sockaddr *) &addr, sizeof (addr)) == SOCKET_ERROR) + { + closesocket (sok); + free (username); + return 0; + } + + if (listen (sok, 1) == SOCKET_ERROR) + { + closesocket (sok); + free (username); + return 0; + } + + len = sizeof (addr); + read_sok = accept (sok, (struct sockaddr *) &addr, &len); + closesocket (sok); + if (read_sok == INVALID_SOCKET) + { + free (username); + return 0; + } + + identd_is_running = FALSE; + + snprintf (outbuf, sizeof (outbuf), "%%\tServicing ident request from %s\n", + inet_ntoa (addr.sin_addr)); + PrintText (current_sess, outbuf); + + recv (read_sok, buf, sizeof (buf) - 1, 0); + buf[sizeof (buf) - 1] = 0; /* ensure null termination */ + + p = strchr (buf, ','); + if (p) + { + snprintf (outbuf, sizeof (outbuf) - 1, "%d, %d : USERID : UNIX : %s\r\n", + atoi (buf), atoi (p + 1), username); + outbuf[sizeof (outbuf) - 1] = 0; /* ensure null termination */ + send (read_sok, outbuf, strlen (outbuf), 0); + } + + sleep (1); + closesocket (read_sok); + free (username); + + return 0; +} + +static void +identd_start (char *username) +{ + DWORD tid; + + if (identd_is_running == FALSE) + { + identd_is_running = TRUE; + CloseHandle (CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE) identd, + strdup (username), 0, &tid)); + } +} diff --git a/src/common/ignore.c b/src/common/ignore.c new file mode 100644 index 00000000..c3544f30 --- /dev/null +++ b/src/common/ignore.c @@ -0,0 +1,424 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "xchat.h" +#include "ignore.h" +#include "cfgfiles.h" +#include "fe.h" +#include "text.h" +#include "util.h" +#include "xchatc.h" + + +int ignored_ctcp = 0; /* keep a count of all we ignore */ +int ignored_priv = 0; +int ignored_chan = 0; +int ignored_noti = 0; +int ignored_invi = 0; +static int ignored_total = 0; + +/* ignore_exists (): + * returns: struct ig, if this mask is in the ignore list already + * NULL, otherwise + */ +struct ignore * +ignore_exists (char *mask) +{ + struct ignore *ig = 0; + GSList *list; + + list = ignore_list; + while (list) + { + ig = (struct ignore *) list->data; + if (!rfc_casecmp (ig->mask, mask)) + return ig; + list = list->next; + } + return NULL; + +} + +/* ignore_add(...) + + * returns: + * 0 fail + * 1 success + * 2 success (old ignore has been changed) + */ + +int +ignore_add (char *mask, int type) +{ + struct ignore *ig = 0; + int change_only = FALSE; + + /* first check if it's already ignored */ + ig = ignore_exists (mask); + if (ig) + change_only = TRUE; + + if (!change_only) + ig = malloc (sizeof (struct ignore)); + + if (!ig) + return 0; + + ig->mask = strdup (mask); + ig->type = type; + + if (!change_only) + ignore_list = g_slist_prepend (ignore_list, ig); + fe_ignore_update (1); + + if (change_only) + return 2; + + return 1; +} + +void +ignore_showlist (session *sess) +{ + struct ignore *ig; + GSList *list = ignore_list; + char tbuf[256]; + int i = 0; + + EMIT_SIGNAL (XP_TE_IGNOREHEADER, sess, 0, 0, 0, 0, 0); + + while (list) + { + ig = list->data; + i++; + + snprintf (tbuf, sizeof (tbuf), " %-25s ", ig->mask); + if (ig->type & IG_PRIV) + strcat (tbuf, _("YES ")); + else + strcat (tbuf, _("NO ")); + if (ig->type & IG_NOTI) + strcat (tbuf, _("YES ")); + else + strcat (tbuf, _("NO ")); + if (ig->type & IG_CHAN) + strcat (tbuf, _("YES ")); + else + strcat (tbuf, _("NO ")); + if (ig->type & IG_CTCP) + strcat (tbuf, _("YES ")); + else + strcat (tbuf, _("NO ")); + if (ig->type & IG_DCC) + strcat (tbuf, _("YES ")); + else + strcat (tbuf, _("NO ")); + if (ig->type & IG_INVI) + strcat (tbuf, _("YES ")); + else + strcat (tbuf, _("NO ")); + if (ig->type & IG_UNIG) + strcat (tbuf, _("YES ")); + else + strcat (tbuf, _("NO ")); + strcat (tbuf, "\n"); + PrintText (sess, tbuf); + /*EMIT_SIGNAL (XP_TE_IGNORELIST, sess, ig->mask, 0, 0, 0, 0); */ + /* use this later, when TE's support 7 args */ + list = list->next; + } + + if (!i) + EMIT_SIGNAL (XP_TE_IGNOREEMPTY, sess, 0, 0, 0, 0, 0); + + EMIT_SIGNAL (XP_TE_IGNOREFOOTER, sess, 0, 0, 0, 0, 0); +} + +/* ignore_del() + + * one of the args must be NULL, use mask OR *ig, not both + * + */ + +int +ignore_del (char *mask, struct ignore *ig) +{ + if (!ig) + { + GSList *list = ignore_list; + + while (list) + { + ig = (struct ignore *) list->data; + if (!rfc_casecmp (ig->mask, mask)) + break; + list = list->next; + ig = 0; + } + } + if (ig) + { + ignore_list = g_slist_remove (ignore_list, ig); + free (ig->mask); + free (ig); + fe_ignore_update (1); + return TRUE; + } + return FALSE; +} + +/* check if a msg should be ignored by browsing our ignore list */ + +int +ignore_check (char *host, int type) +{ + struct ignore *ig; + GSList *list = ignore_list; + + /* check if there's an UNIGNORE first, they take precendance. */ + while (list) + { + ig = (struct ignore *) list->data; + if (ig->type & IG_UNIG) + { + if (ig->type & type) + { + if (match (ig->mask, host)) + return FALSE; + } + } + list = list->next; + } + + list = ignore_list; + while (list) + { + ig = (struct ignore *) list->data; + + if (ig->type & type) + { + if (match (ig->mask, host)) + { + ignored_total++; + if (type & IG_PRIV) + ignored_priv++; + if (type & IG_NOTI) + ignored_noti++; + if (type & IG_CHAN) + ignored_chan++; + if (type & IG_CTCP) + ignored_ctcp++; + if (type & IG_INVI) + ignored_invi++; + fe_ignore_update (2); + return TRUE; + } + } + list = list->next; + } + + return FALSE; +} + +static char * +ignore_read_next_entry (char *my_cfg, struct ignore *ignore) +{ + char tbuf[1024]; + + /* Casting to char * done below just to satisfy compiler */ + + if (my_cfg) + { + my_cfg = cfg_get_str (my_cfg, "mask", tbuf, sizeof (tbuf)); + if (!my_cfg) + return NULL; + ignore->mask = strdup (tbuf); + } + if (my_cfg) + { + my_cfg = cfg_get_str (my_cfg, "type", tbuf, sizeof (tbuf)); + ignore->type = atoi (tbuf); + } + return my_cfg; +} + +void +ignore_load () +{ + struct ignore *ignore; + struct stat st; + char *cfg, *my_cfg; + int fh, i; + + fh = xchat_open_file ("ignore.conf", O_RDONLY, 0, 0); + if (fh != -1) + { + fstat (fh, &st); + if (st.st_size) + { + cfg = malloc (st.st_size + 1); + cfg[0] = '\0'; + i = read (fh, cfg, st.st_size); + if (i >= 0) + cfg[i] = '\0'; + my_cfg = cfg; + while (my_cfg) + { + ignore = malloc (sizeof (struct ignore)); + memset (ignore, 0, sizeof (struct ignore)); + if ((my_cfg = ignore_read_next_entry (my_cfg, ignore))) + ignore_list = g_slist_prepend (ignore_list, ignore); + else + free (ignore); + } + free (cfg); + } + close (fh); + } +} + +void +ignore_save () +{ + char buf[1024]; + int fh; + GSList *temp = ignore_list; + struct ignore *ig; + + fh = xchat_open_file ("ignore.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); + if (fh != -1) + { + while (temp) + { + ig = (struct ignore *) temp->data; + if (!(ig->type & IG_NOSAVE)) + { + snprintf (buf, sizeof (buf), "mask = %s\ntype = %d\n\n", + ig->mask, ig->type); + write (fh, buf, strlen (buf)); + } + temp = temp->next; + } + close (fh); + } + +} + +static gboolean +flood_autodialog_timeout (gpointer data) +{ + prefs.autodialog = 1; + return FALSE; +} + +int +flood_check (char *nick, char *ip, server *serv, session *sess, int what) /*0=ctcp 1=priv */ +{ + /* + serv + int ctcp_counter; + time_t ctcp_last_time; + prefs + unsigned int ctcp_number_limit; + unsigned int ctcp_time_limit; + */ + char buf[512]; + char real_ip[132]; + int i; + time_t current_time; + current_time = time (NULL); + + if (what == 0) + { + if (serv->ctcp_last_time == 0) /*first ctcp in this server */ + { + serv->ctcp_last_time = time (NULL); + serv->ctcp_counter++; + } else + { + if (difftime (current_time, serv->ctcp_last_time) < prefs.ctcp_time_limit) /*if we got the ctcp in the seconds limit */ + { + serv->ctcp_counter++; + if (serv->ctcp_counter == prefs.ctcp_number_limit) /*if we reached the maximun numbers of ctcp in the seconds limits */ + { + serv->ctcp_last_time = current_time; /*we got the flood, restore all the vars for next one */ + serv->ctcp_counter = 0; + for (i = 0; i < 128; i++) + if (ip[i] == '@') + break; + snprintf (real_ip, sizeof (real_ip), "*!*%s", &ip[i]); + /*ignore_add (char *mask, int priv, int noti, int chan, + int ctcp, int invi, int unignore, int no_save) */ + + snprintf (buf, sizeof (buf), + _("You are being CTCP flooded from %s, ignoring %s\n"), + nick, real_ip); + PrintText (sess, buf); + + /*FIXME: only ignore ctcp or all?, its ignoring ctcps for now */ + ignore_add (real_ip, IG_CTCP); + return 0; + } + } + } + } else + { + if (serv->msg_last_time == 0) + { + serv->msg_last_time = time (NULL); + serv->ctcp_counter++; + } else + { + if (difftime (current_time, serv->msg_last_time) < + prefs.msg_time_limit) + { + serv->msg_counter++; + if (serv->msg_counter == prefs.msg_number_limit) /*if we reached the maximun numbers of ctcp in the seconds limits */ + { + snprintf (buf, sizeof (buf), + _("You are being MSG flooded from %s, setting gui_auto_open_dialog OFF.\n"), + ip); + PrintText (sess, buf); + serv->msg_last_time = current_time; /*we got the flood, restore all the vars for next one */ + serv->msg_counter = 0; + /*ignore_add (char *mask, int priv, int noti, int chan, + int ctcp, int invi, int unignore, int no_save) */ + + if (prefs.autodialog) + { + /*FIXME: only ignore ctcp or all?, its ignoring ctcps for now */ + prefs.autodialog = 0; + /* turn it back on in 30 secs */ + fe_timeout_add (30000, flood_autodialog_timeout, NULL); + } + return 0; + } + } + } + } + return 1; +} + diff --git a/src/common/ignore.h b/src/common/ignore.h new file mode 100644 index 00000000..3a971a86 --- /dev/null +++ b/src/common/ignore.h @@ -0,0 +1,38 @@ +#ifndef XCHAT_IGNORE_H +#define XCHAT_IGNORE_H + +extern GSList *ignore_list; + +extern int ignored_ctcp; +extern int ignored_priv; +extern int ignored_chan; +extern int ignored_noti; +extern int ignored_invi; + +#define IG_PRIV 1 +#define IG_NOTI 2 +#define IG_CHAN 4 +#define IG_CTCP 8 +#define IG_INVI 16 +#define IG_UNIG 32 +#define IG_NOSAVE 64 +#define IG_DCC 128 + +struct ignore +{ + char *mask; + unsigned int type; /* one of more of IG_* ORed together */ +}; + +struct ignore *ignore_exists (char *mask); +int ignore_add (char *mask, int type); +void ignore_showlist (session *sess); +int ignore_del (char *mask, struct ignore *ig); +int ignore_check (char *mask, int type); +void ignore_load (void); +void ignore_save (void); +void ignore_gui_open (void); +void ignore_gui_update (int level); +int flood_check (char *nick, char *ip, server *serv, session *sess, int what); + +#endif diff --git a/src/common/inbound.c b/src/common/inbound.c new file mode 100644 index 00000000..ec7dd9d0 --- /dev/null +++ b/src/common/inbound.c @@ -0,0 +1,1336 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> +#include <unistd.h> +#include <time.h> + +#define WANTARPA +#define WANTDNS +#include "inet.h" + +#include "xchat.h" +#include "util.h" +#include "ignore.h" +#include "fe.h" +#include "modes.h" +#include "notify.h" +#include "outbound.h" +#include "inbound.h" +#include "server.h" +#include "servlist.h" +#include "text.h" +#include "ctcp.h" +#include "plugin.h" +#include "xchatc.h" + + +void +clear_channel (session *sess) +{ + if (sess->channel[0]) + strcpy (sess->waitchannel, sess->channel); + sess->channel[0] = 0; + sess->doing_who = FALSE; + sess->done_away_check = FALSE; + + log_close (sess); + + if (sess->current_modes) + { + free (sess->current_modes); + sess->current_modes = NULL; + } + + if (sess->mode_timeout_tag) + { + fe_timeout_remove (sess->mode_timeout_tag); + sess->mode_timeout_tag = 0; + } + + fe_clear_channel (sess); + userlist_clear (sess); + fe_set_nonchannel (sess, FALSE); + fe_set_title (sess); +} + +void +set_topic (session *sess, char *topic, char *stripped_topic) +{ + if (sess->topic) + free (sess->topic); + sess->topic = strdup (stripped_topic); + fe_set_topic (sess, topic, stripped_topic); +} + +static session * +find_session_from_nick (char *nick, server *serv) +{ + session *sess; + GSList *list = sess_list; + + sess = find_dialog (serv, nick); + if (sess) + return sess; + + if (serv->front_session) + { + if (userlist_find (serv->front_session, nick)) + return serv->front_session; + } + + if (current_sess && current_sess->server == serv) + { + if (userlist_find (current_sess, nick)) + return current_sess; + } + + while (list) + { + sess = list->data; + if (sess->server == serv) + { + if (userlist_find (sess, nick)) + return sess; + } + list = list->next; + } + return 0; +} + +static session * +inbound_open_dialog (server *serv, char *from) +{ + session *sess; + + sess = new_ircwindow (serv, from, SESS_DIALOG, 0); + /* for playing sounds */ + EMIT_SIGNAL (XP_TE_OPENDIALOG, sess, NULL, NULL, NULL, NULL, 0); + + return sess; +} + +static void +inbound_make_idtext (server *serv, char *idtext, int max, int id) +{ + idtext[0] = 0; + if (serv->have_idmsg) + { + if (id) + { + safe_strcpy (idtext, prefs.irc_id_ytext, max); + } else + { + safe_strcpy (idtext, prefs.irc_id_ntext, max); + } + /* convert codes like %C,%U to the proper ones */ + check_special_chars (idtext, TRUE); + } +} + +void +inbound_privmsg (server *serv, char *from, char *ip, char *text, int id) +{ + session *sess; + char idtext[64]; + + sess = find_dialog (serv, from); + + if (sess || prefs.autodialog) + { + /*0=ctcp 1=priv will set autodialog=0 here is flud detected */ + if (!sess) + { + if (flood_check (from, ip, serv, current_sess, 1)) + /* Create a dialog session */ + sess = inbound_open_dialog (serv, from); + else + sess = serv->server_session; + if (!sess) + return; /* ?? */ + } + + if (ip && ip[0]) + { + if (prefs.logging && sess->logfd != -1 && + (!sess->topic || strcmp(sess->topic, ip))) + { + char tbuf[1024]; + snprintf (tbuf, sizeof (tbuf), "[%s has address %s]\n", from, ip); + write (sess->logfd, tbuf, strlen (tbuf)); + } + set_topic (sess, ip, ip); + } + inbound_chanmsg (serv, NULL, NULL, from, text, FALSE, id); + return; + } + + inbound_make_idtext (serv, idtext, sizeof (idtext), id); + + sess = find_session_from_nick (from, serv); + if (!sess) + { + sess = serv->front_session; + EMIT_SIGNAL (XP_TE_PRIVMSG, sess, from, text, idtext, NULL, 0); + return; + } + + if (sess->type == SESS_DIALOG) + EMIT_SIGNAL (XP_TE_DPRIVMSG, sess, from, text, idtext, NULL, 0); + else + EMIT_SIGNAL (XP_TE_PRIVMSG, sess, from, text, idtext, NULL, 0); +} + +/* used for Alerts section. Masks can be separated by commas and spaces. */ + +gboolean +alert_match_word (char *word, char *masks) +{ + char *p = masks; + char endchar; + int res; + + if (masks[0] == 0) + return FALSE; + + while (1) + { + /* if it's a 0, space or comma, the word has ended. */ + if (*p == 0 || *p == ' ' || *p == ',') + { + endchar = *p; + *p = 0; + res = match (masks, word); + *p = endchar; + + if (res) + return TRUE; /* yes, matched! */ + + masks = p + 1; + if (*p == 0) + return FALSE; + } + p++; + } +} + +gboolean +alert_match_text (char *text, char *masks) +{ + unsigned char *p = text; + unsigned char endchar; + int res; + + if (masks[0] == 0) + return FALSE; + + while (1) + { + if (*p >= '0' && *p <= '9') + { + p++; + continue; + } + + /* if it's RFC1459 <special>, it can be inside a word */ + switch (*p) + { + case '-': case '[': case ']': case '\\': + case '`': case '^': case '{': case '}': + case '_': case '|': + p++; + continue; + } + + /* if it's a 0, space or comma, the word has ended. */ + if (*p == 0 || *p == ' ' || *p == ',' || + /* if it's anything BUT a letter, the word has ended. */ + (!g_unichar_isalpha (g_utf8_get_char (p)))) + { + endchar = *p; + *p = 0; + res = alert_match_word (text, masks); + *p = endchar; + + if (res) + return TRUE; /* yes, matched! */ + + text = p + g_utf8_skip [p[0]]; + if (*p == 0) + return FALSE; + } + + p += g_utf8_skip [p[0]]; + } +} + +static int +is_hilight (char *from, char *text, session *sess, server *serv) +{ + if (alert_match_word (from, prefs.irc_no_hilight)) + return 0; + + text = strip_color (text, -1, STRIP_ALL); + + if (alert_match_text (text, serv->nick) || + alert_match_text (text, prefs.irc_extra_hilight) || + alert_match_word (from, prefs.irc_nick_hilight)) + { + g_free (text); + if (sess != current_tab) + sess->nick_said = TRUE; + fe_set_hilight (sess); + return 1; + } + + g_free (text); + return 0; +} + +void +inbound_action (session *sess, char *chan, char *from, char *ip, char *text, int fromme, int id) +{ + session *def = sess; + server *serv = sess->server; + struct User *user; + char nickchar[2] = "\000"; + char idtext[64]; + int privaction = FALSE; + + if (!fromme) + { + if (is_channel (serv, chan)) + { + sess = find_channel (serv, chan); + } else + { + /* it's a private action! */ + privaction = TRUE; + /* find a dialog tab for it */ + sess = find_dialog (serv, from); + /* if non found, open a new one */ + if (!sess && prefs.autodialog) + { + /* but only if it wouldn't flood */ + if (flood_check (from, ip, serv, current_sess, 1)) + sess = inbound_open_dialog (serv, from); + else + sess = serv->server_session; + } + if (!sess) + { + sess = find_session_from_nick (from, serv); + /* still not good? */ + if (!sess) + sess = serv->front_session; + } + } + } + + if (!sess) + sess = def; + + if (sess != current_tab) + { + if (fromme) + { + sess->msg_said = FALSE; + sess->new_data = TRUE; + } else + { + sess->msg_said = TRUE; + sess->new_data = FALSE; + } + } + + user = userlist_find (sess, from); + if (user) + { + nickchar[0] = user->prefix[0]; + user->lasttalk = time (0); + } + + inbound_make_idtext (serv, idtext, sizeof (idtext), id); + + if (!fromme && !privaction) + { + if (is_hilight (from, text, sess, serv)) + { + EMIT_SIGNAL (XP_TE_HCHANACTION, sess, from, text, nickchar, idtext, 0); + return; + } + } + + if (fromme) + EMIT_SIGNAL (XP_TE_UACTION, sess, from, text, nickchar, idtext, 0); + else if (!privaction) + EMIT_SIGNAL (XP_TE_CHANACTION, sess, from, text, nickchar, idtext, 0); + else if (sess->type == SESS_DIALOG) + EMIT_SIGNAL (XP_TE_DPRIVACTION, sess, from, text, idtext, NULL, 0); + else + EMIT_SIGNAL (XP_TE_PRIVACTION, sess, from, text, idtext, NULL, 0); +} + +void +inbound_chanmsg (server *serv, session *sess, char *chan, char *from, char *text, char fromme, int id) +{ + struct User *user; + int hilight = FALSE; + char nickchar[2] = "\000"; + char idtext[64]; + + if (!sess) + { + if (chan) + { + sess = find_channel (serv, chan); + if (!sess && !is_channel (serv, chan)) + sess = find_dialog (serv, chan); + } else + { + sess = find_dialog (serv, from); + } + if (!sess) + return; + } + + if (sess != current_tab) + { + sess->msg_said = TRUE; + sess->new_data = FALSE; + } + + user = userlist_find (sess, from); + if (user) + { + nickchar[0] = user->prefix[0]; + user->lasttalk = time (0); + } + + if (fromme) + { + if (prefs.auto_unmark_away && serv->is_away) + sess->server->p_set_back (sess->server); + EMIT_SIGNAL (XP_TE_UCHANMSG, sess, from, text, nickchar, NULL, 0); + return; + } + + inbound_make_idtext (serv, idtext, sizeof (idtext), id); + + if (is_hilight (from, text, sess, serv)) + hilight = TRUE; + + if (sess->type == SESS_DIALOG) + EMIT_SIGNAL (XP_TE_DPRIVMSG, sess, from, text, idtext, NULL, 0); + else if (hilight) + EMIT_SIGNAL (XP_TE_HCHANMSG, sess, from, text, nickchar, idtext, 0); + else + EMIT_SIGNAL (XP_TE_CHANMSG, sess, from, text, nickchar, idtext, 0); +} + +void +inbound_newnick (server *serv, char *nick, char *newnick, int quiet) +{ + int me = FALSE; + session *sess; + GSList *list = sess_list; + + if (!serv->p_cmp (nick, serv->nick)) + { + me = TRUE; + safe_strcpy (serv->nick, newnick, NICKLEN); + } + + while (list) + { + sess = list->data; + if (sess->server == serv) + { + if (userlist_change (sess, nick, newnick) || (me && sess->type == SESS_SERVER)) + { + if (!quiet) + { + if (me) + EMIT_SIGNAL (XP_TE_UCHANGENICK, sess, nick, newnick, NULL, + NULL, 0); + else + EMIT_SIGNAL (XP_TE_CHANGENICK, sess, nick, newnick, NULL, + NULL, 0); + } + } + if (sess->type == SESS_DIALOG && !serv->p_cmp (sess->channel, nick)) + { + safe_strcpy (sess->channel, newnick, CHANLEN); + fe_set_channel (sess); + } + fe_set_title (sess); + } + list = list->next; + } + + dcc_change_nick (serv, nick, newnick); + + if (me) + fe_set_nick (serv, newnick); +} + +/* find a "<none>" tab */ +static session * +find_unused_session (server *serv) +{ + session *sess; + GSList *list = sess_list; + while (list) + { + sess = (session *) list->data; + if (sess->type == SESS_CHANNEL && sess->channel[0] == 0 && + sess->server == serv) + { + if (sess->waitchannel[0] == 0) + return sess; + } + list = list->next; + } + return 0; +} + +static session * +find_session_from_waitchannel (char *chan, struct server *serv) +{ + session *sess; + GSList *list = sess_list; + while (list) + { + sess = (session *) list->data; + if (sess->server == serv && sess->channel[0] == 0 && sess->type == SESS_CHANNEL) + { + if (!serv->p_cmp (chan, sess->waitchannel)) + return sess; + } + list = list->next; + } + return 0; +} + +void +inbound_ujoin (server *serv, char *chan, char *nick, char *ip) +{ + session *sess; + + /* already joined? probably a bnc */ + sess = find_channel (serv, chan); + if (!sess) + { + /* see if a window is waiting to join this channel */ + sess = find_session_from_waitchannel (chan, serv); + if (!sess) + { + /* find a "<none>" tab and use that */ + sess = find_unused_session (serv); + if (!sess) + /* last resort, open a new tab/window */ + sess = new_ircwindow (serv, chan, SESS_CHANNEL, 1); + } + } + + safe_strcpy (sess->channel, chan, CHANLEN); + + fe_set_channel (sess); + fe_set_title (sess); + fe_set_nonchannel (sess, TRUE); + userlist_clear (sess); + + log_open_or_close (sess); + + sess->waitchannel[0] = 0; + sess->ignore_date = TRUE; + sess->ignore_mode = TRUE; + sess->ignore_names = TRUE; + sess->end_of_names = FALSE; + + /* sends a MODE */ + serv->p_join_info (sess->server, chan); + + EMIT_SIGNAL (XP_TE_UJOIN, sess, nick, chan, ip, NULL, 0); + + if (prefs.userhost) + { + /* sends WHO #channel */ + serv->p_user_list (sess->server, chan); + sess->doing_who = TRUE; + } +} + +void +inbound_ukick (server *serv, char *chan, char *kicker, char *reason) +{ + session *sess = find_channel (serv, chan); + if (sess) + { + EMIT_SIGNAL (XP_TE_UKICK, sess, serv->nick, chan, kicker, reason, 0); + clear_channel (sess); + if (prefs.autorejoin) + { + serv->p_join (serv, chan, sess->channelkey); + safe_strcpy (sess->waitchannel, chan, CHANLEN); + } + } +} + +void +inbound_upart (server *serv, char *chan, char *ip, char *reason) +{ + session *sess = find_channel (serv, chan); + if (sess) + { + if (*reason) + EMIT_SIGNAL (XP_TE_UPARTREASON, sess, serv->nick, ip, chan, reason, + 0); + else + EMIT_SIGNAL (XP_TE_UPART, sess, serv->nick, ip, chan, NULL, 0); + clear_channel (sess); + } +} + +void +inbound_nameslist (server *serv, char *chan, char *names) +{ + session *sess; + char name[NICKLEN]; + int pos = 0; + + sess = find_channel (serv, chan); + if (!sess) + { + EMIT_SIGNAL (XP_TE_USERSONCHAN, serv->server_session, chan, names, NULL, + NULL, 0); + return; + } + if (!sess->ignore_names) + EMIT_SIGNAL (XP_TE_USERSONCHAN, sess, chan, names, NULL, NULL, 0); + + if (sess->end_of_names) + { + sess->end_of_names = FALSE; + userlist_clear (sess); + } + + while (1) + { + switch (*names) + { + case 0: + name[pos] = 0; + if (pos != 0) + userlist_add (sess, name, 0); + return; + case ' ': + name[pos] = 0; + pos = 0; + userlist_add (sess, name, 0); + break; + default: + name[pos] = *names; + if (pos < (NICKLEN-1)) + pos++; + } + names++; + } +} + +void +inbound_topic (server *serv, char *chan, char *topic_text) +{ + session *sess = find_channel (serv, chan); + char *stripped_topic; + + if (sess) + { + stripped_topic = strip_color (topic_text, -1, STRIP_ALL); + set_topic (sess, topic_text, stripped_topic); + g_free (stripped_topic); + } else + sess = serv->server_session; + + EMIT_SIGNAL (XP_TE_TOPIC, sess, chan, topic_text, NULL, NULL, 0); +} + +void +inbound_topicnew (server *serv, char *nick, char *chan, char *topic) +{ + session *sess; + char *stripped_topic; + + sess = find_channel (serv, chan); + if (sess) + { + stripped_topic = strip_color (topic, -1, STRIP_ALL); + set_topic (sess, topic, stripped_topic); + g_free (stripped_topic); + EMIT_SIGNAL (XP_TE_NEWTOPIC, sess, nick, topic, chan, NULL, 0); + } +} + +void +inbound_join (server *serv, char *chan, char *user, char *ip) +{ + session *sess = find_channel (serv, chan); + if (sess) + { + EMIT_SIGNAL (XP_TE_JOIN, sess, user, chan, ip, NULL, 0); + userlist_add (sess, user, ip); + } +} + +void +inbound_kick (server *serv, char *chan, char *user, char *kicker, char *reason) +{ + session *sess = find_channel (serv, chan); + if (sess) + { + EMIT_SIGNAL (XP_TE_KICK, sess, kicker, user, chan, reason, 0); + userlist_remove (sess, user); + } +} + +void +inbound_part (server *serv, char *chan, char *user, char *ip, char *reason) +{ + session *sess = find_channel (serv, chan); + if (sess) + { + if (*reason) + EMIT_SIGNAL (XP_TE_PARTREASON, sess, user, ip, chan, reason, 0); + else + EMIT_SIGNAL (XP_TE_PART, sess, user, ip, chan, NULL, 0); + userlist_remove (sess, user); + } +} + +void +inbound_topictime (server *serv, char *chan, char *nick, time_t stamp) +{ + char *tim = ctime (&stamp); + session *sess = find_channel (serv, chan); + + if (!sess) + sess = serv->server_session; + + tim[24] = 0; /* get rid of the \n */ + EMIT_SIGNAL (XP_TE_TOPICDATE, sess, chan, nick, tim, NULL, 0); +} + +void +inbound_quit (server *serv, char *nick, char *ip, char *reason) +{ + GSList *list = sess_list; + session *sess; + int was_on_front_session = FALSE; + + while (list) + { + sess = (session *) list->data; + if (sess->server == serv) + { + if (sess == current_sess) + was_on_front_session = TRUE; + if (userlist_remove (sess, nick)) + { + EMIT_SIGNAL (XP_TE_QUIT, sess, nick, reason, ip, NULL, 0); + } else if (sess->type == SESS_DIALOG && !serv->p_cmp (sess->channel, nick)) + { + EMIT_SIGNAL (XP_TE_QUIT, sess, nick, reason, ip, NULL, 0); + } + } + list = list->next; + } + + notify_set_offline (serv, nick, was_on_front_session); +} + +void +inbound_ping_reply (session *sess, char *timestring, char *from) +{ + unsigned long tim, nowtim, dif; + int lag = 0; + char outbuf[64]; + + if (strncmp (timestring, "LAG", 3) == 0) + { + timestring += 3; + lag = 1; + } + + tim = strtoul (timestring, NULL, 10); + nowtim = make_ping_time (); + dif = nowtim - tim; + + sess->server->ping_recv = time (0); + + if (lag) + { + sess->server->lag_sent = 0; + sess->server->lag = dif / 1000; + fe_set_lag (sess->server, dif / 100000); + return; + } + + if (atol (timestring) == 0) + { + if (sess->server->lag_sent) + sess->server->lag_sent = 0; + else + EMIT_SIGNAL (XP_TE_PINGREP, sess, from, "?", NULL, NULL, 0); + } else + { + snprintf (outbuf, sizeof (outbuf), "%ld.%ld%ld", dif / 1000000, (dif / 100000) % 10, dif % 10); + EMIT_SIGNAL (XP_TE_PINGREP, sess, from, outbuf, NULL, NULL, 0); + } +} + +static session * +find_session_from_type (int type, server *serv) +{ + session *sess; + GSList *list = sess_list; + while (list) + { + sess = list->data; + if (sess->type == type && serv == sess->server) + return sess; + list = list->next; + } + return 0; +} + +void +inbound_notice (server *serv, char *to, char *nick, char *msg, char *ip, int id) +{ + char *po,*ptr=to; + session *sess = 0; + int server_notice = FALSE; + + if (is_channel (serv, ptr)) + sess = find_channel (serv, ptr); + + if (!sess && ptr[0] == '@') + { + ptr++; + sess = find_channel (serv, ptr); + } + + if (!sess && ptr[0] == '%') + { + ptr++; + sess = find_channel (serv, ptr); + } + + if (!sess && ptr[0] == '+') + { + ptr++; + sess = find_channel (serv, ptr); + } + + if (strcmp (nick, ip) == 0) + server_notice = TRUE; + + if (!sess) + { + ptr = 0; + if (prefs.notices_tabs) + { + int stype = server_notice ? SESS_SNOTICES : SESS_NOTICES; + sess = find_session_from_type (stype, serv); + if (!sess) + { + if (stype == SESS_NOTICES) + sess = new_ircwindow (serv, "(notices)", SESS_NOTICES, 0); + else + sess = new_ircwindow (serv, "(snotices)", SESS_SNOTICES, 0); + fe_set_channel (sess); + fe_set_title (sess); + fe_set_nonchannel (sess, FALSE); + userlist_clear (sess); + log_open_or_close (sess); + } + /* Avoid redundancy with some Undernet notices */ + if (!strncmp (msg, "*** Notice -- ", 14)) + msg += 14; + } else + { + /* paranoia check */ + if (msg[0] == '[' && (!serv->have_idmsg || id)) + { + /* guess where chanserv meant to post this -sigh- */ + if (!strcasecmp (nick, "ChanServ") && !find_dialog (serv, nick)) + { + char *dest = strdup (msg + 1); + char *end = strchr (dest, ']'); + if (end) + { + *end = 0; + sess = find_channel (serv, dest); + } + free (dest); + } + } + if (!sess) + sess = find_session_from_nick (nick, serv); + } + if (!sess) + { + if (server_notice) + sess = serv->server_session; + else + sess = serv->front_session; + } + } + + if (msg[0] == 1) + { + msg++; + if (!strncmp (msg, "PING", 4)) + { + inbound_ping_reply (sess, msg + 5, nick); + return; + } + } + po = strchr (msg, '\001'); + if (po) + po[0] = 0; + + if (server_notice) + EMIT_SIGNAL (XP_TE_SERVNOTICE, sess, msg, nick, NULL, NULL, 0); + else if (ptr) + EMIT_SIGNAL (XP_TE_CHANNOTICE, sess, nick, to, msg, NULL, 0); + else + EMIT_SIGNAL (XP_TE_NOTICE, sess, nick, msg, NULL, NULL, 0); +} + +void +inbound_away (server *serv, char *nick, char *msg) +{ + struct away_msg *away = server_away_find_message (serv, nick); + session *sess = NULL; + GSList *list; + + if (away && !strcmp (msg, away->message)) /* Seen the msg before? */ + { + if (prefs.show_away_once && !serv->inside_whois) + return; + } else + { + server_away_save_message (serv, nick, msg); + } + + if (prefs.irc_whois_front) + sess = serv->front_session; + else + { + if (!serv->inside_whois) + sess = find_session_from_nick (nick, serv); + if (!sess) + sess = serv->server_session; + } + + /* possibly hide the output */ + if (!serv->inside_whois || !serv->skip_next_whois) + EMIT_SIGNAL (XP_TE_WHOIS5, sess, nick, msg, NULL, NULL, 0); + + list = sess_list; + while (list) + { + sess = list->data; + if (sess->server == serv) + userlist_set_away (sess, nick, TRUE); + list = list->next; + } +} + +int +inbound_nameslist_end (server *serv, char *chan) +{ + session *sess; + GSList *list; + + if (!strcmp (chan, "*")) + { + list = sess_list; + while (list) + { + sess = list->data; + if (sess->server == serv) + { + sess->end_of_names = TRUE; + sess->ignore_names = FALSE; + } + list = list->next; + } + return TRUE; + } + sess = find_channel (serv, chan); + if (sess) + { + sess->end_of_names = TRUE; + sess->ignore_names = FALSE; + return TRUE; + } + return FALSE; +} + +static gboolean +check_autojoin_channels (server *serv) +{ + char *po; + session *sess; + GSList *list = sess_list; + int i = 0; + GSList *channels, *keys; + + /* shouldnt really happen, the io tag is destroyed in server.c */ + if (!is_server (serv)) + return FALSE; + + /* send auto join list */ + if (serv->autojoin) + { + joinlist_split (serv->autojoin, &channels, &keys); + serv->p_join_list (serv, channels, keys); + joinlist_free (channels, keys); + + free (serv->autojoin); + serv->autojoin = NULL; + } + + /* this is really only for re-connects when you + * join channels not in the auto-join list. */ + while (list) + { + sess = list->data; + if (sess->server == serv) + { + if (sess->willjoinchannel[0] != 0) + { + strcpy (sess->waitchannel, sess->willjoinchannel); + sess->willjoinchannel[0] = 0; + serv->p_join (serv, sess->waitchannel, sess->channelkey); + po = strchr (sess->waitchannel, ','); + if (po) + *po = 0; + po = strchr (sess->waitchannel, ' '); + if (po) + *po = 0; + i++; + } + } + list = list->next; + } + serv->joindelay_tag = 0; + fe_server_event (serv, FE_SE_LOGGEDIN, i); + return FALSE; +} + +void +inbound_next_nick (session *sess, char *nick) +{ + char *newnick; + server *serv = sess->server; + ircnet *net; + + serv->nickcount++; + + switch (serv->nickcount) + { + case 2: + newnick = prefs.nick2; + net = serv->network; + /* use network specific "Second choice"? */ + if (net && !(net->flags & FLAG_USE_GLOBAL) && net->nick2) + newnick = net->nick2; + serv->p_change_nick (serv, newnick); + EMIT_SIGNAL (XP_TE_NICKCLASH, sess, nick, newnick, NULL, NULL, 0); + break; + + case 3: + serv->p_change_nick (serv, prefs.nick3); + EMIT_SIGNAL (XP_TE_NICKCLASH, sess, nick, prefs.nick3, NULL, NULL, 0); + break; + + default: + EMIT_SIGNAL (XP_TE_NICKFAIL, sess, NULL, NULL, NULL, NULL, 0); + } +} + +void +do_dns (session *sess, char *nick, char *host) +{ + char *po; + char tbuf[1024]; + + po = strrchr (host, '@'); + if (po) + host = po + 1; + EMIT_SIGNAL (XP_TE_RESOLVINGUSER, sess, nick, host, NULL, NULL, 0); + snprintf (tbuf, sizeof (tbuf), "exec -d %s %s", prefs.dnsprogram, host); + handle_command (sess, tbuf, FALSE); +} + +static void +set_default_modes (server *serv) +{ + char modes[8]; + + modes[0] = '+'; + modes[1] = '\0'; + + if (prefs.wallops) + strcat (modes, "w"); + if (prefs.servernotice) + strcat (modes, "s"); + if (prefs.invisible) + strcat (modes, "i"); + + if (modes[1] != '\0') + { + serv->p_mode (serv, serv->nick, modes); + } +} + +void +inbound_login_start (session *sess, char *nick, char *servname) +{ + inbound_newnick (sess->server, sess->server->nick, nick, TRUE); + server_set_name (sess->server, servname); + if (sess->type == SESS_SERVER) + log_open_or_close (sess); + /* reset our away status */ + if (sess->server->reconnect_away) + { + handle_command (sess->server->server_session, "away", FALSE); + sess->server->reconnect_away = FALSE; + } +} + +static void +inbound_set_all_away_status (server *serv, char *nick, unsigned int status) +{ + GSList *list; + session *sess; + + list = sess_list; + while (list) + { + sess = list->data; + if (sess->server == serv) + userlist_set_away (sess, nick, status); + list = list->next; + } +} + +void +inbound_uaway (server *serv) +{ + serv->is_away = TRUE; + serv->away_time = time (NULL); + fe_set_away (serv); + + inbound_set_all_away_status (serv, serv->nick, 1); +} + +void +inbound_uback (server *serv) +{ + serv->is_away = FALSE; + serv->reconnect_away = FALSE; + fe_set_away (serv); + + inbound_set_all_away_status (serv, serv->nick, 0); +} + +void +inbound_foundip (session *sess, char *ip) +{ + struct hostent *HostAddr; + + HostAddr = gethostbyname (ip); + if (HostAddr) + { + prefs.dcc_ip = ((struct in_addr *) HostAddr->h_addr)->s_addr; + EMIT_SIGNAL (XP_TE_FOUNDIP, sess, + inet_ntoa (*((struct in_addr *) HostAddr->h_addr)), + NULL, NULL, NULL, 0); + } +} + +void +inbound_user_info_start (session *sess, char *nick) +{ + /* set away to FALSE now, 301 may turn it back on */ + inbound_set_all_away_status (sess->server, nick, 0); +} + +/* reporting new information found about this user. chan may be NULL. + * away may be 0xff to indicate UNKNOWN. */ + +void +inbound_user_info (session *sess, char *chan, char *user, char *host, + char *servname, char *nick, char *realname, + unsigned int away) +{ + server *serv = sess->server; + session *who_sess; + GSList *list; + char *uhost = NULL; + + if (user && host) + { + uhost = g_malloc (strlen (user) + strlen (host) + 2); + sprintf (uhost, "%s@%s", user, host); + } + + if (chan) + { + who_sess = find_channel (serv, chan); + if (who_sess) + userlist_add_hostname (who_sess, nick, uhost, realname, servname, away); + else + { + if (serv->doing_dns && nick && host) + do_dns (sess, nick, host); + } + } + else + { + /* came from WHOIS, not channel specific */ + for (list = sess_list; list; list = list->next) + { + sess = list->data; + if (sess->type == SESS_CHANNEL && sess->server == serv) + { + userlist_add_hostname (sess, nick, uhost, realname, servname, away); + } + } + } + + g_free (uhost); +} + +int +inbound_banlist (session *sess, time_t stamp, char *chan, char *mask, char *banner, int is_exemption) +{ + char *time_str = ctime (&stamp); + server *serv = sess->server; + + time_str[19] = 0; /* get rid of the \n */ + if (stamp == 0) + time_str = ""; + + sess = find_channel (serv, chan); + if (!sess) + { + sess = serv->front_session; + goto nowindow; + } + + if (!fe_is_banwindow (sess)) + { +nowindow: + /* let proto-irc.c do the 'goto def' for exemptions */ + if (is_exemption) + return FALSE; + + EMIT_SIGNAL (XP_TE_BANLIST, sess, chan, mask, banner, time_str, 0); + return TRUE; + } + + fe_add_ban_list (sess, mask, banner, time_str, is_exemption); + return TRUE; +} + +/* execute 1 end-of-motd command */ + +static int +inbound_exec_eom_cmd (char *str, void *sess) +{ + handle_command (sess, (str[0] == '/') ? str + 1 : str, TRUE); + return 1; +} + +void +inbound_login_end (session *sess, char *text) +{ + server *serv = sess->server; + + if (!serv->end_of_motd) + { + if (prefs.ip_from_server && serv->use_who) + { + serv->skip_next_userhost = TRUE; + serv->p_get_ip_uh (serv, serv->nick); /* sends USERHOST mynick */ + } + set_default_modes (serv); + + if (serv->network) + { + /* there may be more than 1, separated by \n */ + if (((ircnet *)serv->network)->command) + token_foreach (((ircnet *)serv->network)->command, '\n', + inbound_exec_eom_cmd, sess); + + /* send nickserv password */ + if (((ircnet *)serv->network)->nickserv) + serv->p_ns_identify (serv, ((ircnet *)serv->network)->nickserv); + } + + /* send JOIN now or wait? */ + if (serv->network && ((ircnet *)serv->network)->nickserv && + prefs.irc_join_delay) + serv->joindelay_tag = fe_timeout_add (prefs.irc_join_delay * 1000, + check_autojoin_channels, serv); + else + check_autojoin_channels (serv); + if (serv->supports_watch) + notify_send_watches (serv); + serv->end_of_motd = TRUE; + } + if (prefs.skipmotd && !serv->motd_skipped) + { + serv->motd_skipped = TRUE; + EMIT_SIGNAL (XP_TE_MOTDSKIP, serv->server_session, NULL, NULL, + NULL, NULL, 0); + return; + } + EMIT_SIGNAL (XP_TE_MOTD, serv->server_session, text, NULL, + NULL, NULL, 0); +} + +void +inbound_identified (server *serv) /* 'MODE +e MYSELF' on freenode */ +{ + if (serv->joindelay_tag) + { + /* stop waiting, just auto JOIN now */ + fe_timeout_remove (serv->joindelay_tag); + serv->joindelay_tag = 0; + check_autojoin_channels (serv); + } +} diff --git a/src/common/inbound.h b/src/common/inbound.h new file mode 100644 index 00000000..b972981f --- /dev/null +++ b/src/common/inbound.h @@ -0,0 +1,39 @@ +#ifndef XCHAT_INBOUND_H +#define XCHAT_INBOUND_H + +void inbound_next_nick (session *sess, char *nick); +void inbound_uback (server *serv); +void inbound_uaway (server *serv); +void inbound_part (server *serv, char *chan, char *user, char *ip, char *reason); +void inbound_upart (server *serv, char *chan, char *ip, char *reason); +void inbound_ukick (server *serv, char *chan, char *kicker, char *reason); +void inbound_kick (server *serv, char *chan, char *user, char *kicker, char *reason); +void inbound_notice (server *serv, char *to, char *nick, char *msg, char *ip, int id); +void inbound_quit (server *serv, char *nick, char *ip, char *reason); +void inbound_topicnew (server *serv, char *nick, char *chan, char *topic); +void inbound_join (server *serv, char *chan, char *user, char *ip); +void inbound_ujoin (server *serv, char *chan, char *nick, char *ip); +void inbound_topictime (server *serv, char *chan, char *nick, time_t stamp); +void inbound_topic (server *serv, char *chan, char *topic_text); +void inbound_user_info_start (session *sess, char *nick); +void inbound_user_info (session *sess, char *chan, char *user, char *host, char *servname, char *nick, char *realname, unsigned int away); +void inbound_foundip (session *sess, char *ip); +int inbound_banlist (session *sess, time_t stamp, char *chan, char *mask, char *banner, int is_exemption); +void inbound_ping_reply (session *sess, char *timestring, char *from); +void inbound_nameslist (server *serv, char *chan, char *names); +int inbound_nameslist_end (server *serv, char *chan); +void inbound_away (server *serv, char *nick, char *msg); +void inbound_login_start (session *sess, char *nick, char *servname); +void inbound_login_end (session *sess, char *text); +void inbound_chanmsg (server *serv, session *sess, char *chan, char *from, char *text, char fromme, int id); +void clear_channel (session *sess); +void set_topic (session *sess, char *topic, char *stripped_topic); +void inbound_privmsg (server *serv, char *from, char *ip, char *text, int id); +void inbound_action (session *sess, char *chan, char *from, char *ip, char *text, int fromme, int id); +void inbound_newnick (server *serv, char *nick, char *newnick, int quiet); +void do_dns (session *sess, char *nick, char *host); +void inbound_identified (server *serv); +gboolean alert_match_word (char *word, char *masks); +gboolean alert_match_text (char *text, char *masks); + +#endif diff --git a/src/common/inet.h b/src/common/inet.h new file mode 100644 index 00000000..b420c9c6 --- /dev/null +++ b/src/common/inet.h @@ -0,0 +1,43 @@ +/* include stuff for internet */ + +#ifndef WIN32 + +#ifdef WANTSOCKET +#include <sys/types.h> +#include <sys/socket.h> +#endif +#ifdef WANTARPA +#include <netinet/in.h> +#include <arpa/inet.h> +#endif +#ifdef WANTDNS +#include <netdb.h> +#endif +#define closesocket close +#define set_blocking(sok) fcntl(sok, F_SETFL, 0) +#define set_nonblocking(sok) fcntl(sok, F_SETFL, O_NONBLOCK) +#define would_block() (errno == EAGAIN || errno == EWOULDBLOCK) +#define sock_error() (errno) + +#else + +#ifdef USE_IPV6 +#include <winsock2.h> +#include <ws2tcpip.h> +#include <tpipv6.h> +#else +#include <winsock.h> +#endif + +#define set_blocking(sok) { \ + unsigned long zero = 0; \ + ioctlsocket (sok, FIONBIO, &zero); \ + } +#define set_nonblocking(sok) { \ + unsigned long one = 1; \ + ioctlsocket (sok, FIONBIO, &one); \ + } +#define would_block() (WSAGetLastError() == WSAEWOULDBLOCK) +#define sock_error WSAGetLastError + +#endif diff --git a/src/common/make-te.c b/src/common/make-te.c new file mode 100644 index 00000000..ed87df3d --- /dev/null +++ b/src/common/make-te.c @@ -0,0 +1,58 @@ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +int main() +{ + char name[512]; + char num[512]; + char help[512]; + char def[512]; + char args[512]; + char buf[512]; + char *defines[512]; + int i = 0, max; + + printf("/* this file is auto generated, edit textevents.in instead! */\n\nconst struct text_event te[] = {\n"); + while(fgets(name, sizeof(name), stdin)) + { + name[strlen(name)-1] = 0; + fgets(num, sizeof(num), stdin); + num[strlen(num)-1] = 0; + fgets(help, sizeof(help), stdin); + help[strlen(help)-1] = 0; + fgets(def, sizeof(def), stdin); + def[strlen(def)-1] = 0; + fgets(args, sizeof(args), stdin); + args[strlen(args)-1] = 0; + fgets(buf, sizeof(buf), stdin); + + if (args[0] == 'n') + printf("\n{\"%s\", %s, %d, \n\"%s\"},\n", + name, help, atoi(args+1) | 128, def); + else + printf("\n{\"%s\", %s, %d, \nN_(\"%s\")},\n", + name, help, atoi(args), def); + defines[i] = strdup (num); + i++; + } + + printf("};\n"); + + fprintf(stderr, "/* this file is auto generated, edit textevents.in instead! */\n\nenum\n{\n"); + max = i; + i = 0; + while (i < max) + { + if (i + 1 < max) + { + fprintf(stderr, "\t%s,\t\t%s,\n", defines[i], defines[i+1]); + i++; + } else + fprintf(stderr, "\t%s,\n", defines[i]); + i++; + } + fprintf(stderr, "\tNUM_XP\n};\n"); + + return 0; +} diff --git a/src/common/modes.c b/src/common/modes.c new file mode 100644 index 00000000..1acf7f54 --- /dev/null +++ b/src/common/modes.c @@ -0,0 +1,836 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <glib.h> +#include <glib/gprintf.h> + +#include "xchat.h" +#include "xchatc.h" +#include "modes.h" +#include "server.h" +#include "text.h" +#include "fe.h" +#include "util.h" +#include "inbound.h" +#ifdef HAVE_STRINGS_H +#include <strings.h> +#endif + +typedef struct +{ + server *serv; + char *op; + char *deop; + char *voice; + char *devoice; +} mode_run; + +static int is_prefix_char (server * serv, char c); +static void record_chan_mode (session *sess, char sign, char mode, char *arg); +static char *mode_cat (char *str, char *addition); +static void handle_single_mode (mode_run *mr, char sign, char mode, char *nick, char *chan, char *arg, int quiet, int is_324); +static int mode_has_arg (server *serv, char sign, char mode); +static void mode_print_grouped (session *sess, char *nick, mode_run *mr); +static int mode_chanmode_type (server * serv, char mode); + + +/* word[] - list of nicks. + wpos - index into word[]. Where nicks really start. + end - index into word[]. Last entry plus one. + sign - a char, e.g. '+' or '-' + mode - a mode, e.g. 'o' or 'v' */ +void +send_channel_modes (session *sess, char *tbuf, char *word[], int wpos, + int end, char sign, char mode, int modes_per_line) +{ + int usable_modes, orig_len, len, wlen, i, max; + server *serv = sess->server; + + /* sanity check. IRC RFC says three per line. */ + if (serv->modes_per_line < 3) + serv->modes_per_line = 3; + if (modes_per_line < 1) + modes_per_line = serv->modes_per_line; + + /* RFC max, minus length of "MODE %s " and "\r\n" and 1 +/- sign */ + /* 512 - 6 - 2 - 1 - strlen(chan) */ + max = 503 - strlen (sess->channel); + + while (wpos < end) + { + tbuf[0] = '\0'; + orig_len = len = 0; + + /* we'll need this many modechars too */ + len += modes_per_line; + + /* how many can we fit? */ + for (i = 0; i < modes_per_line; i++) + { + /* no more nicks left? */ + if (wpos + i >= end) + break; + wlen = strlen (word[wpos + i]) + 1; + if (wlen + len > max) + break; + len += wlen; /* length of our whole string so far */ + } + if (i < 1) + return; + usable_modes = i; /* this is how many we'll send on this line */ + + /* add the +/-modemodemodemode */ + len = orig_len; + tbuf[len] = sign; + len++; + for (i = 0; i < usable_modes; i++) + { + tbuf[len] = mode; + len++; + } + tbuf[len] = 0; /* null terminate for the strcat() to work */ + + /* add all the nicknames */ + for (i = 0; i < usable_modes; i++) + { + strcat (tbuf, " "); + strcat (tbuf, word[wpos + i]); + } + serv->p_mode (serv, sess->channel, tbuf); + + wpos += usable_modes; + } +} + +/* does 'chan' have a valid prefix? e.g. # or & */ + +int +is_channel (server * serv, char *chan) +{ + if (strchr (serv->chantypes, chan[0])) + return 1; + return 0; +} + +/* is the given char a valid nick mode char? e.g. @ or + */ + +static int +is_prefix_char (server * serv, char c) +{ + int pos = 0; + char *np = serv->nick_prefixes; + + while (np[0]) + { + if (np[0] == c) + return pos; + pos++; + np++; + } + + if (serv->bad_prefix) + { + if (strchr (serv->bad_nick_prefixes, c)) + /* valid prefix char, but mode unknown */ + return -2; + } + + return -1; +} + +/* returns '@' for ops etc... */ + +char +get_nick_prefix (server * serv, unsigned int access) +{ + int pos; + char c; + + for (pos = 0; pos < USERACCESS_SIZE; pos++) + { + c = serv->nick_prefixes[pos]; + if (c == 0) + break; + if (access & (1 << pos)) + return c; + } + + return 0; +} + +/* returns the access bitfield for a nickname. E.g. + @nick would return 000010 in binary + %nick would return 000100 in binary + +nick would return 001000 in binary */ + +unsigned int +nick_access (server * serv, char *nick, int *modechars) +{ + int i; + unsigned int access = 0; + char *orig = nick; + + while (*nick) + { + i = is_prefix_char (serv, *nick); + if (i == -1) + break; + + /* -2 == valid prefix char, but mode unknown */ + if (i != -2) + access |= (1 << i); + + nick++; + } + + *modechars = nick - orig; + + return access; +} + +/* returns the access number for a particular mode. e.g. + mode 'a' returns 0 + mode 'o' returns 1 + mode 'h' returns 2 + mode 'v' returns 3 + Also puts the nick-prefix-char in 'prefix' */ + +int +mode_access (server * serv, char mode, char *prefix) +{ + int pos = 0; + + while (serv->nick_modes[pos]) + { + if (serv->nick_modes[pos] == mode) + { + *prefix = serv->nick_prefixes[pos]; + return pos; + } + pos++; + } + + *prefix = 0; + + return -1; +} + +static void +record_chan_mode (session *sess, char sign, char mode, char *arg) +{ + /* Somebody needed to acutally update sess->current_modes, needed to + play nice with bouncers, and less mode calls. Also keeps modes up + to date for scripts */ + server *serv = sess->server; + GString *current = g_string_new(sess->current_modes); + gint mode_pos = -1; + gchar *current_char = current->str; + gint modes_length; + gint argument_num = 0; + gint argument_offset = 0; + gint argument_length = 0; + int i = 0; + gchar *arguments_start; + + /* find out if the mode currently exists */ + arguments_start = g_strstr_len(current->str , -1, " "); + if (arguments_start) { + modes_length = arguments_start - current->str; + } + else { + modes_length = current->len; + /* set this to the end of the modes */ + arguments_start = current->str + current->len; + } + + while (mode_pos == -1 && i < modes_length) + { + if (*current_char == mode) + { + mode_pos = i; + } + else + { + i++; + current_char++; + } + } + + /* if the mode currently exists and has an arg, need to know where + * (including leading space) */ + if (mode_pos != -1 && mode_has_arg(serv, '+', mode)) + { + current_char = current->str; + + i = 0; + while (i <= mode_pos) + { + if (mode_has_arg(serv, '+', *current_char)) + argument_num++; + current_char++; + i++; + } + + /* check through arguments for where to start */ + current_char = arguments_start; + i = 0; + while (i < argument_num && *current_char != '\0') + { + if (*current_char == ' ') + i++; + if (i != argument_num) + current_char++; + } + argument_offset = current_char - current->str; + + /* how long the existing argument is for this key + * important for malloc and strncpy */ + if (i == argument_num) + { + argument_length++; + current_char++; + while (*current_char != '\0' && *current_char != ' ') + { + argument_length++; + current_char++; + } + } + } + + /* two cases, adding and removing a mode, handled differently */ + if (sign == '+') + { + if (mode_pos != -1) + { + /* if it already exists, only need to do something (change) + * if there should be a param */ + if (mode_has_arg(serv, sign, mode)) + { + /* leave the old space there */ + current = g_string_erase(current, argument_offset+1, argument_length-1); + current = g_string_insert(current, argument_offset+1, arg); + + free(sess->current_modes); + sess->current_modes = g_string_free(current, FALSE); + } + } + /* mode wasn't there before */ + else + { + /* insert the new mode character */ + current = g_string_insert_c(current, modes_length, mode); + + /* add the argument, with space if there is one */ + if (mode_has_arg(serv, sign, mode)) + { + current = g_string_append_c(current, ' '); + current = g_string_append(current, arg); + } + + free(sess->current_modes); + sess->current_modes = g_string_free(current, FALSE); + } + } + else if (sign == '-' && mode_pos != -1) + { + /* remove the argument first if it has one*/ + if (mode_has_arg(serv, '+', mode)) + current = g_string_erase(current, argument_offset, argument_length); + + /* remove the mode character */ + current = g_string_erase(current, mode_pos, 1); + + free(sess->current_modes); + sess->current_modes = g_string_free(current, FALSE); + } +} + +static char * +mode_cat (char *str, char *addition) +{ + int len; + + if (str) + { + len = strlen (str) + strlen (addition) + 2; + str = realloc (str, len); + strcat (str, " "); + strcat (str, addition); + } else + { + str = strdup (addition); + } + + return str; +} + +/* handle one mode, e.g. + handle_single_mode (mr,'+','b',"elite","#warez","banneduser",) */ + +static void +handle_single_mode (mode_run *mr, char sign, char mode, char *nick, + char *chan, char *arg, int quiet, int is_324) +{ + session *sess; + server *serv = mr->serv; + char outbuf[4]; + + outbuf[0] = sign; + outbuf[1] = 0; + outbuf[2] = mode; + outbuf[3] = 0; + + sess = find_channel (serv, chan); + if (!sess || !is_channel (serv, chan)) + { + /* got modes for a chan we're not in! probably nickmode +isw etc */ + sess = serv->front_session; + goto genmode; + } + + /* is this a nick mode? */ + if (strchr (serv->nick_modes, mode)) + { + /* update the user in the userlist */ + userlist_update_mode (sess, /*nickname */ arg, mode, sign); + } else + { + if (!is_324 && !sess->ignore_mode && mode_chanmode_type(serv, mode) >= 1) + record_chan_mode (sess, sign, mode, arg); + } + + switch (sign) + { + case '+': + switch (mode) + { + case 'k': + safe_strcpy (sess->channelkey, arg, sizeof (sess->channelkey)); + fe_update_channel_key (sess); + fe_update_mode_buttons (sess, mode, sign); + if (!quiet) + EMIT_SIGNAL (XP_TE_CHANSETKEY, sess, nick, arg, NULL, NULL, 0); + return; + case 'l': + sess->limit = atoi (arg); + fe_update_channel_limit (sess); + fe_update_mode_buttons (sess, mode, sign); + if (!quiet) + EMIT_SIGNAL (XP_TE_CHANSETLIMIT, sess, nick, arg, NULL, NULL, 0); + return; + case 'o': + if (!quiet) + mr->op = mode_cat (mr->op, arg); + return; + case 'h': + if (!quiet) + EMIT_SIGNAL (XP_TE_CHANHOP, sess, nick, arg, NULL, NULL, 0); + return; + case 'v': + if (!quiet) + mr->voice = mode_cat (mr->voice, arg); + return; + case 'b': + if (!quiet) + EMIT_SIGNAL (XP_TE_CHANBAN, sess, nick, arg, NULL, NULL, 0); + return; + case 'e': + if (!quiet) + EMIT_SIGNAL (XP_TE_CHANEXEMPT, sess, nick, arg, NULL, NULL, 0); + return; + case 'I': + if (!quiet) + EMIT_SIGNAL (XP_TE_CHANINVITE, sess, nick, arg, NULL, NULL, 0); + return; + } + break; + case '-': + switch (mode) + { + case 'k': + sess->channelkey[0] = 0; + fe_update_channel_key (sess); + fe_update_mode_buttons (sess, mode, sign); + if (!quiet) + EMIT_SIGNAL (XP_TE_CHANRMKEY, sess, nick, NULL, NULL, NULL, 0); + return; + case 'l': + sess->limit = 0; + fe_update_channel_limit (sess); + fe_update_mode_buttons (sess, mode, sign); + if (!quiet) + EMIT_SIGNAL (XP_TE_CHANRMLIMIT, sess, nick, NULL, NULL, NULL, 0); + return; + case 'o': + if (!quiet) + mr->deop = mode_cat (mr->deop, arg); + return; + case 'h': + if (!quiet) + EMIT_SIGNAL (XP_TE_CHANDEHOP, sess, nick, arg, NULL, NULL, 0); + return; + case 'v': + if (!quiet) + mr->devoice = mode_cat (mr->devoice, arg); + return; + case 'b': + if (!quiet) + EMIT_SIGNAL (XP_TE_CHANUNBAN, sess, nick, arg, NULL, NULL, 0); + return; + case 'e': + if (!quiet) + EMIT_SIGNAL (XP_TE_CHANRMEXEMPT, sess, nick, arg, NULL, NULL, 0); + return; + case 'I': + if (!quiet) + EMIT_SIGNAL (XP_TE_CHANRMINVITE, sess, nick, arg, NULL, NULL, 0); + return; + } + } + + fe_update_mode_buttons (sess, mode, sign); + + genmode: + /* Received umode +e. If we're waiting to send JOIN then send now! */ + if (mode == 'e' && sign == '+' && !serv->p_cmp (chan, serv->nick)) + inbound_identified (serv); + + if (!quiet) + { + if (*arg) + { + char *buf = malloc (strlen (chan) + strlen (arg) + 2); + sprintf (buf, "%s %s", chan, arg); + EMIT_SIGNAL (XP_TE_CHANMODEGEN, sess, nick, outbuf, outbuf + 2, buf, 0); + free (buf); + } else + EMIT_SIGNAL (XP_TE_CHANMODEGEN, sess, nick, outbuf, outbuf + 2, chan, 0); + } +} + +/* does this mode have an arg? like +b +l +o */ + +static int +mode_has_arg (server * serv, char sign, char mode) +{ + int type; + + /* if it's a nickmode, it must have an arg */ + if (strchr (serv->nick_modes, mode)) + return 1; + + type = mode_chanmode_type (serv, mode); + switch (type) + { + case 0: /* type A */ + case 1: /* type B */ + return 1; + case 2: /* type C */ + if (sign == '+') + return 1; + case 3: /* type D */ + return 0; + default: + return 0; + } + +} + +/* what type of chanmode is it? -1 for not in chanmode */ +static int +mode_chanmode_type (server * serv, char mode) +{ + /* see what numeric 005 CHANMODES=xxx said */ + char *cm = serv->chanmodes; + int type = 0; + int found = 0; + + while (*cm && !found) + { + if (*cm == ',') + { + type++; + } else if (*cm == mode) + { + found = 1; + } + cm++; + } + if (found) + return type; + /* not found? -1 */ + else + return -1; +} + +static void +mode_print_grouped (session *sess, char *nick, mode_run *mr) +{ + /* print all the grouped Op/Deops */ + if (mr->op) + { + EMIT_SIGNAL (XP_TE_CHANOP, sess, nick, mr->op, NULL, NULL, 0); + free (mr->op); + mr->op = NULL; + } + + if (mr->deop) + { + EMIT_SIGNAL (XP_TE_CHANDEOP, sess, nick, mr->deop, NULL, NULL, 0); + free (mr->deop); + mr->deop = NULL; + } + + if (mr->voice) + { + EMIT_SIGNAL (XP_TE_CHANVOICE, sess, nick, mr->voice, NULL, NULL, 0); + free (mr->voice); + mr->voice = NULL; + } + + if (mr->devoice) + { + EMIT_SIGNAL (XP_TE_CHANDEVOICE, sess, nick, mr->devoice, NULL, NULL, 0); + free (mr->devoice); + mr->devoice = NULL; + } +} + + +/* handle a MODE or numeric 324 from server */ + +void +handle_mode (server * serv, char *word[], char *word_eol[], + char *nick, int numeric_324) +{ + session *sess; + char *chan; + char *modes; + char *argstr; + char sign; + int len; + int arg; + int i, num_args; + int num_modes; + int offset = 3; + int all_modes_have_args = FALSE; + int using_front_tab = FALSE; + mode_run mr; + + mr.serv = serv; + mr.op = mr.deop = mr.voice = mr.devoice = NULL; + + /* numeric 324 has everything 1 word later (as opposed to MODE) */ + if (numeric_324) + offset++; + + chan = word[offset]; + modes = word[offset + 1]; + if (*modes == ':') + modes++; + + if (*modes == 0) + return; /* beyondirc's blank modes */ + + sess = find_channel (serv, chan); + if (!sess) + { + sess = serv->front_session; + using_front_tab = TRUE; + } + /* remove trailing space */ + len = strlen (word_eol[offset]) - 1; + if (word_eol[offset][len] == ' ') + word_eol[offset][len] = 0; + + if (prefs.raw_modes && !numeric_324) + EMIT_SIGNAL (XP_TE_RAWMODES, sess, nick, word_eol[offset], 0, 0, 0); + + if (numeric_324 && !using_front_tab) + { + if (sess->current_modes) + free (sess->current_modes); + sess->current_modes = strdup (word_eol[offset+1]); + } + + sign = *modes; + modes++; + arg = 1; + + /* count the number of arguments (e.g. after the -o+v) */ + num_args = 0; + i = 1; + while ((i + offset + 1) < PDIWORDS) + { + i++; + if (!(*word[i + offset])) + break; + num_args++; + } + + /* count the number of modes (without the -/+ chars */ + num_modes = 0; + i = 0; + while (i < strlen (modes)) + { + if (modes[i] != '+' && modes[i] != '-') + num_modes++; + i++; + } + + if (num_args == num_modes) + all_modes_have_args = TRUE; + + while (*modes) + { + switch (*modes) + { + case '-': + case '+': + /* print all the grouped Op/Deops */ + mode_print_grouped (sess, nick, &mr); + sign = *modes; + break; + default: + argstr = ""; + if ((all_modes_have_args || mode_has_arg (serv, sign, *modes)) && arg < (num_args+1)) + { + arg++; + argstr = word[arg + offset]; + } + handle_single_mode (&mr, sign, *modes, nick, chan, + argstr, numeric_324 || prefs.raw_modes, + numeric_324); + } + + modes++; + } + + /* update the title at the end, now that the mode update is internal now */ + if (!using_front_tab) + fe_set_title (sess); + + /* print all the grouped Op/Deops */ + mode_print_grouped (sess, nick, &mr); +} + +/* handle the 005 numeric */ + +void +inbound_005 (server * serv, char *word[]) +{ + int w; + char *pre; + + w = 4; /* start at the 4th word */ + while (w < PDIWORDS && *word[w]) + { + if (strncmp (word[w], "MODES=", 6) == 0) + { + serv->modes_per_line = atoi (word[w] + 6); + } else if (strncmp (word[w], "CHANTYPES=", 10) == 0) + { + free (serv->chantypes); + serv->chantypes = strdup (word[w] + 10); + } else if (strncmp (word[w], "CHANMODES=", 10) == 0) + { + free (serv->chanmodes); + serv->chanmodes = strdup (word[w] + 10); + } else if (strncmp (word[w], "PREFIX=", 7) == 0) + { + pre = strchr (word[w] + 7, ')'); + if (pre) + { + pre[0] = 0; /* NULL out the ')' */ + free (serv->nick_prefixes); + free (serv->nick_modes); + serv->nick_prefixes = strdup (pre + 1); + serv->nick_modes = strdup (word[w] + 8); + } else + { + /* bad! some ircds don't give us the modes. */ + /* in this case, we use it only to strip /NAMES */ + serv->bad_prefix = TRUE; + if (serv->bad_nick_prefixes) + free (serv->bad_nick_prefixes); + serv->bad_nick_prefixes = strdup (word[w] + 7); + } + } else if (strncmp (word[w], "WATCH=", 6) == 0) + { + serv->supports_watch = TRUE; + } else if (strncmp (word[w], "NETWORK=", 8) == 0) + { +/* if (serv->networkname) + free (serv->networkname); + serv->networkname = strdup (word[w] + 8);*/ + + if (serv->server_session->type == SESS_SERVER) + { + safe_strcpy (serv->server_session->channel, word[w] + 8, CHANLEN); + fe_set_channel (serv->server_session); + } + + /* use /NICKSERV */ + if (strcasecmp (word[w] + 8, "RusNet") == 0) + serv->nickservtype = 1; + else if (strcasecmp (word[w] + 8, "UniBG") == 0) + serv->nickservtype = 3; + else if (strcasecmp (word[w] + 8, "QuakeNet") == 0) + serv->nickservtype = 4; + + } else if (strncmp (word[w], "CASEMAPPING=", 12) == 0) + { + if (strcmp (word[w] + 12, "ascii") == 0) /* bahamut */ + serv->p_cmp = (void *)strcasecmp; + } else if (strncmp (word[w], "CHARSET=", 8) == 0) + { + if (strcasecmp (word[w] + 8, "UTF-8") == 0) + { + server_set_encoding (serv, "UTF-8"); + } + } else if (strcmp (word[w], "NAMESX") == 0) + { + /* 12345678901234567 */ + tcp_send_len (serv, "PROTOCTL NAMESX\r\n", 17); + } else if (strcmp (word[w], "WHOX") == 0) + { + serv->have_whox = TRUE; + } else if (strcmp (word[w], "CAPAB") == 0) + { + serv->have_capab = TRUE; + /* 12345678901234567890 */ + tcp_send_len (serv, "CAPAB IDENTIFY-MSG\r\n", 20); + /* now wait for numeric 290 */ + } else if (strcmp (word[w], "EXCEPTS") == 0) + { +#ifndef WIN32 + serv->have_except = TRUE; +#endif + } else if (strncmp (word[w], "ELIST=", 6) == 0) + { + /* supports LIST >< min/max user counts? */ + if (strchr (word[w] + 6, 'U') || strchr (word[w] + 6, 'u')) + serv->use_listargs = TRUE; + } + + w++; + } +} diff --git a/src/common/modes.h b/src/common/modes.h new file mode 100644 index 00000000..3f9c4a72 --- /dev/null +++ b/src/common/modes.h @@ -0,0 +1,12 @@ +#ifndef XCHAT_MODES_H +#define XCHAT_MODES_H + +int is_channel (server *serv, char *chan); +char get_nick_prefix (server *serv, unsigned int access); +unsigned int nick_access (server *serv, char *nick, int *modechars); +int mode_access (server *serv, char mode, char *prefix); +void inbound_005 (server *serv, char *word[]); +void handle_mode (server *serv, char *word[], char *word_eol[], char *nick, int numeric_324); +void send_channel_modes (session *sess, char *tbuf, char *word[], int start, int end, char sign, char mode, int modes_per_line); + +#endif diff --git a/src/common/msproxy.c b/src/common/msproxy.c new file mode 100644 index 00000000..9c85394d --- /dev/null +++ b/src/common/msproxy.c @@ -0,0 +1,467 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + * MS Proxy (ISA server) support is (c) 2006 Pavel Fedin <sonic_amiga@rambler.ru> + * based on Dante source code + * Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 + * Inferno Nettverk A/S, Norway. All rights reserved. + */ + +/*#define DEBUG_MSPROXY*/ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> + +#define WANTSOCKET +#define WANTARPA +#include "inet.h" + +#include "xchat.h" +#include "network.h" +#include "xchatc.h" +#include "server.h" +#include "msproxy.h" + + +#ifdef USE_MSPROXY +#include <ntlm.h> + +static int +send_msprequest(s, state, request, end) + int s; + struct msproxy_state_t *state; + struct msproxy_request_t *request; + char *end; +{ + ssize_t w; + size_t l; + + request->magic25 = htonl(MSPROXY_VERSION); + request->serverack = state->seq_recv; + /* don't start incrementing sequence until we are acking packet #2. */ + request->sequence = (unsigned char)(request->serverack >= 2 ? state->seq_sent + 1 : 0); + + memcpy(request->RWSP, "RWSP", sizeof(request->RWSP)); + + l = end - (char *)request; + /* all requests must be atleast MSPROXY_MINLENGTH it seems. */ + if (l < MSPROXY_MINLENGTH) { + bzero(end, (size_t)(MSPROXY_MINLENGTH - l)); + l = MSPROXY_MINLENGTH; + } + + if ((w = send(s, request, l, 0)) != l) { +#ifdef DEBUG_MSPROXY + printf ("send_msprequest(): send() failed (%d bytes sent instead of %d\n", w, l); + perror ("Error is"); +#endif + return -1; + } + state->seq_sent = request->sequence; + + return w; +} + +static int +recv_mspresponse(s, state, response) + int s; + struct msproxy_state_t *state; + struct msproxy_response_t *response; +{ + ssize_t r; + + do { + if ((r = recv (s, response, sizeof (*response), 0)) < MSPROXY_MINLENGTH) { +#ifdef DEBUG_MSPROXY + printf ("recv_mspresponse(): expected to read atleast %d, read %d\n", MSPROXY_MINLENGTH, r); +#endif + return -1; + } + if (state->seq_recv == 0) + break; /* not started incrementing yet. */ +#ifdef DEBUG_MSPROXY + if (response->sequence == state->seq_recv) + printf ("seq_recv: %d, dup response, seqnumber: 0x%x\n", state->seq_recv, response->sequence); +#endif + } while (response->sequence == state->seq_recv); + + state->seq_recv = response->sequence; + + return r; +} + +int +traverse_msproxy (int sok, char *serverAddr, int port, struct msproxy_state_t *state, netstore *ns_proxy, int csok4, int csok6, int *csok, char bound) +{ + struct msproxy_request_t req; + struct msproxy_response_t res; + char *data, *p; + char hostname[NT_MAXNAMELEN]; + char ntdomain[NT_MAXNAMELEN]; + char challenge[8]; + netstore *ns_client; + int clientport; + guint32 destaddr; + guint32 flags; + + if (!prefs.proxy_auth || !prefs.proxy_user[0] || !prefs.proxy_pass[0] ) + return 1; + + /* MS proxy protocol implementation currently doesn't support IPv6 */ + destaddr = net_getsockaddr_v4 (ns_proxy); + if (!destaddr) + return 1; + + state->seq_recv = 0; + state->seq_sent = 0; + +#ifdef DEBUG_MSPROXY + printf ("Connecting to %s:%d via MS proxy\n", serverAddr, port); +#endif + + gethostname (hostname, NT_MAXNAMELEN); + p = strchr (hostname, '.'); + if (p) + *p = '\0'; + + bzero (&req, sizeof(req)); + req.clientid = htonl(0x0a000000); /* Initial client ID is always 0x0a */ + req.command = htons(MSPROXY_HELLO); /* HELLO command */ + req.packet.hello.magic5 = htons(0x4b00); /* Fill in magic values */ + req.packet.hello.magic10 = htons(0x1400); + req.packet.hello.magic15 = htons(0x0400); + req.packet.hello.magic20 = htons(0x5704); + req.packet.hello.magic25 = htons(0x0004); + req.packet.hello.magic30 = htons(0x0100); + req.packet.hello.magic35 = htons(0x4a02); + req.packet.hello.magic40 = htons(0x3000); + req.packet.hello.magic45 = htons(0x4400); + req.packet.hello.magic50 = htons(0x3900); + data = req.packet.hello.data; + strcpy (data, prefs.proxy_user); /* Append a username */ + data += strlen (prefs.proxy_user)+2; /* +2 automatically creates second empty string */ + strcpy (data, MSPROXY_EXECUTABLE); /* Append an application name */ + data += strlen (MSPROXY_EXECUTABLE)+1; + strcpy (data, hostname); /* Append a hostname */ + data += strlen (hostname)+1; + + if (send_msprequest(sok, state, &req, data) == -1) + return 1; + + if (recv_mspresponse(sok, state, &res) == -1) + return 1; + + if (strcmp(res.RWSP, "RWSP") != 0) { +#ifdef DEBUG_MSPROXY + printf ("Received mailformed packet (no RWSP signature)\n"); +#endif + return 1; + } + + if (ntohs(res.command) >> 8 != 0x10) { +#ifdef DEBUG_MSPROXY + printf ("expected res.command = 10??, is %x", ntohs(res.command)); +#endif + return 1; + } + + state->clientid = htonl(rand()); + state->serverid = res.serverid; + +#ifdef DEBUG_MSPROXY + printf ("clientid: 0x%x, serverid: 0x%0x\n", state->clientid, state->serverid); + printf ("packet #2\n"); +#endif + + /* almost identical. */ + req.clientid = state->clientid; + req.serverid = state->serverid; + + if (send_msprequest(sok, state, &req, data) == -1) + return 1; + + if (recv_mspresponse(sok, state, &res) == -1) + return 1; + + if (res.serverid != state->serverid) { +#ifdef DEBUG_MSPROXY + printf ("expected serverid = 0x%x, is 0x%x\n",state->serverid, res.serverid); +#endif + return 1; + } + + if (res.sequence != 0x01) { +#ifdef DEBUG_MSPROXY + printf ("expected res.sequence = 0x01, is 0x%x\n", res.sequence); +#endif + return 1; + } + + if (ntohs(res.command) != MSPROXY_USERINFO_ACK) { +#ifdef DEBUG_MSPROXY + printf ("expected res.command = 0x%x, is 0x%x\n", MSPROXY_USERINFO_ACK, ntohs(res.command)); +#endif + return 1; + } + +#ifdef DEBUG_MSPROXY + printf ("packet #3\n"); +#endif + + bzero(&req, sizeof(req)); + req.clientid = state->clientid; + req.serverid = state->serverid; + req.command = htons(MSPROXY_AUTHENTICATE); + memcpy(req.packet.auth.NTLMSSP, "NTLMSSP", sizeof("NTLMSSP")); + req.packet.auth.bindaddr = htonl(0x02000000); + req.packet.auth.msgtype = htonl(0x01000000); + /* NTLM flags: 0x80000000 Negotiate LAN Manager key + 0x10000000 Negotiate sign + 0x04000000 Request target + 0x02000000 Negotiate OEM + 0x00800000 Always sign + 0x00020000 Negotiate NTLM + */ + req.packet.auth.flags = htonl(0x06020000); + + if (send_msprequest(sok, state, &req, &req.packet.auth.data) == -1) + return 1; + + if (recv_mspresponse(sok, state, &res) == -1) + return 1; + + if (res.serverid != state->serverid) { +#ifdef DEBUG_MSPROXY + printf ("expected serverid = 0x%x, is 0x%x\n", state->serverid, res.serverid); +#endif + return 1; + } + + if (ntohs(res.command) != MSPROXY_AUTHENTICATE_ACK) { +#ifdef DEBUG_MSPROXY + printf ("expected res.command = 0x%x, is 0x%x\n", MSPROXY_AUTHENTICATE_ACK, ntohs(res.command)); +#endif + return 1; + } + + flags = res.packet.auth.flags & htonl(0x00020000); /* Remember if the server supports NTLM */ + memcpy(challenge, &res.packet.auth.challenge, sizeof(challenge)); + memcpy(ntdomain, &res.packet.auth.NTLMSSP[res.packet.auth.target.offset], res.packet.auth.target.len); + ntdomain[res.packet.auth.target.len] = 0; + +#ifdef DEBUG_MSPROXY + printf ("ntdomain: \"%s\"\n", ntdomain); + printf ("packet #4\n"); +#endif + + bzero(&req, sizeof(req)); + req.clientid = state->clientid; + req.serverid = state->serverid; + req.command = htons(MSPROXY_AUTHENTICATE_2); /* Authentication response */ + req.packet.auth2.magic3 = htons(0x0200); /* Something */ + memcpy(req.packet.auth2.NTLMSSP, "NTLMSSP", sizeof("NTLMSSP")); /* Start of NTLM message */ + req.packet.auth2.msgtype = htonl(0x03000000); /* Message type 2 */ + req.packet.auth2.flags = flags | htonl(0x02000000); /* Choose authentication method */ + data = req.packet.auth2.data; + if (flags) { + req.packet.auth2.lm_resp.len = 0; /* We are here if NTLM is supported, */ + req.packet.auth2.lm_resp.alloc = 0; /* Do not fill in insecure LM response */ + req.packet.auth2.lm_resp.offset = data - req.packet.auth2.NTLMSSP; + req.packet.auth2.ntlm_resp.len = 24; /* Fill in NTLM response security buffer */ + req.packet.auth2.ntlm_resp.alloc = 24; + req.packet.auth2.ntlm_resp.offset = data - req.packet.auth2.NTLMSSP; + ntlm_smb_nt_encrypt(prefs.proxy_pass, challenge, data); /* Append an NTLM response */ + data += 24; + } else { + req.packet.auth2.lm_resp.len = 24; /* Fill in LM response security buffer */ + req.packet.auth2.lm_resp.alloc = 24; + req.packet.auth2.lm_resp.offset = data - req.packet.auth2.NTLMSSP; + ntlm_smb_encrypt(prefs.proxy_pass, challenge, data); /* Append an LM response */ + data += 24; + req.packet.auth2.ntlm_resp.len = 0; /* NTLM response is empty */ + req.packet.auth2.ntlm_resp.alloc = 0; + req.packet.auth2.ntlm_resp.offset = data - req.packet.auth2.NTLMSSP; + } + req.packet.auth2.ntdomain_buf.len = strlen(ntdomain); /* Domain name */ + req.packet.auth2.ntdomain_buf.alloc = req.packet.auth2.ntdomain_buf.len; + req.packet.auth2.ntdomain_buf.offset = data - req.packet.auth2.NTLMSSP; + strcpy(data, ntdomain); + data += req.packet.auth2.ntdomain_buf.len; + req.packet.auth2.username_buf.len = strlen(prefs.proxy_user); /* Username */ + req.packet.auth2.username_buf.alloc = req.packet.auth2.username_buf.len; + req.packet.auth2.username_buf.offset = data - req.packet.auth2.NTLMSSP; + strcpy(data, prefs.proxy_user); + data += req.packet.auth2.username_buf.len; + req.packet.auth2.clienthost_buf.len = strlen(hostname); /* Hostname */ + req.packet.auth2.clienthost_buf.alloc = req.packet.auth2.clienthost_buf.len; + req.packet.auth2.clienthost_buf.offset = data - req.packet.auth2.NTLMSSP; + strcpy(data, hostname); + data += req.packet.auth2.clienthost_buf.len; + req.packet.auth2.sessionkey_buf.len = 0; /* Session key (we don't use it) */ + req.packet.auth2.sessionkey_buf.alloc = 0; + req.packet.auth2.sessionkey_buf.offset = data - req.packet.auth2.NTLMSSP; + + if (send_msprequest(sok, state, &req, data) == -1) + return 1; + + if (recv_mspresponse(sok, state, &res) == -1) + return 1; + + if (res.serverid != state->serverid) { +#ifdef DEBUG_MSPROXY + printf ("expected res.serverid = 0x%x, is 0x%x\n", state->serverid, res.serverid); +#endif + return 1; + } + + if (res.clientack != 0x01) { +#ifdef DEBUG_MSPROXY + printf ("expected res.clientack = 0x01, is 0x%x\n", res.clientack); +#endif + return 1; + } + + if (ntohs(res.command) >> 8 != 0x47) { +#ifdef DEBUG_MSPROXY + printf ("expected res.command = 47??, is 0x%x\n", ntohs(res.command)); +#endif + return 1; + } + + if (ntohs(res.command) == MSPROXY_AUTHENTICATE_2_NAK) { +#ifdef DEBUG_MSPROXY + printf ("Authentication failed\n"); +#endif + return -1; + } + +#ifdef DEBUG_MSPROXY + printf ("packet #5\n"); +#endif + + bzero(&req, sizeof(req)); + req.clientid = state->clientid; + req.serverid = state->serverid; + req.command = htons(MSPROXY_CONNECT); + req.packet.connect.magic2 = htons(0x0200); + req.packet.connect.magic6 = htons(0x0200); + req.packet.connect.destport = htons(port); + req.packet.connect.destaddr = destaddr; + data = req.packet.connect.executable; + strcpy(data, MSPROXY_EXECUTABLE); + data += strlen(MSPROXY_EXECUTABLE) + 1; + + /* + * need to tell server what port we will connect from, so we bind our sockets. + */ + ns_client = net_store_new (); + if (!bound) { + net_store_fill_any (ns_client); + net_bind(ns_client, csok4, csok6); +#ifdef DEBUG_MSPROXY + perror ("bind() result"); +#endif + } + clientport = net_getsockport(csok4, csok6); + if (clientport == -1) { +#ifdef DEBUG_MSPROXY + printf ("Unable to obtain source port\n"); +#endif + return 1; + } + req.packet.connect.srcport = clientport; + + if (send_msprequest(sok, state, &req, data) == -1) + return 1; + + if (recv_mspresponse(sok, state, &res) == -1) + return 1; + + if (ntohs(res.command) != MSPROXY_CONNECT_ACK) { +#ifdef DEBUG_MSPROXY + printf ("expected res.command = 0x%x, is 0x%x\n",MSPROXY_CONNECT_ACK, ntohs(res.command)); +#endif + return 1; + } + + net_store_fill_v4 (ns_client, res.packet.connect.clientaddr, res.packet.connect.clientport); + +#ifdef DEBUG_MSPROXY + printf ("Connecting...\n"); +#endif + if (net_connect (ns_client, csok4, csok6, csok) != 0) { +#ifdef DEBUG_MSPROXY + printf ("Failed to connect to port %d\n", htons(res.packet.connect.clientport)); +#endif + net_store_destroy (ns_client); + return 1; + } + net_store_destroy (ns_client); +#ifdef DEBUG_MSPROXY + printf ("packet #6\n"); +#endif + + req.clientid = state->clientid; + req.serverid = state->serverid; + req.command = htons(MSPROXY_USERINFO_ACK); + + if (send_msprequest(sok, state, &req, req.packet.connack.data) == -1) + return 1; + + return 0; +} + +void +msproxy_keepalive (void) +{ + server *serv; + GSList *list = serv_list; + struct msproxy_request_t req; + struct msproxy_response_t res; + + while (list) + { + serv = list->data; + if (serv->connected && (serv->proxy_sok != -1)) + { +#ifdef DEBUG_MSPROXY + printf ("sending MS proxy keepalive packet\n"); +#endif + + bzero(&req, sizeof(req)); + req.clientid = serv->msp_state.clientid; + req.serverid = serv->msp_state.serverid; + req.command = htons(MSPROXY_HELLO); + + if (send_msprequest(serv->proxy_sok, &serv->msp_state, &req, req.packet.hello.data) == -1) + continue; + + recv_mspresponse(serv->proxy_sok, &serv->msp_state, &res); + +#ifdef DEBUG_MSPROXY + if (ntohs(res.command) != MSPROXY_USERINFO_ACK) + printf ("expected res.command = 0x%x, is 0x%x\n", MSPROXY_USERINFO_ACK, ntohs(res.command)); +#endif + } + list = list->next; + } +} + +#endif diff --git a/src/common/msproxy.h b/src/common/msproxy.h new file mode 100644 index 00000000..d37c81c5 --- /dev/null +++ b/src/common/msproxy.h @@ -0,0 +1,257 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + * MS Proxy (ISA server) support is (c) 2006 Pavel Fedin <sonic_amiga@rambler.ru> + * based on Dante source code + * Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 + * Inferno Nettverk A/S, Norway. All rights reserved. + */ + +#include "network.h" + +#define MSPROXY_EXECUTABLE "xchat.exe" /* This probably can be used for access control on the server side */ + +#define MSPROXY_MINLENGTH 172 /* minimum length of packet. */ +#define NT_MAXNAMELEN 17 /* maximum name length (domain etc), comes from NetBIOS */ +#define MSPROXY_VERSION 0x00010200 /* MS Proxy v2 ? */ + +/* Commands / responses */ +#define MSPROXY_HELLO 0x0500 /* packet 1 from client. */ +#define MSPROXY_HELLO_ACK 0x1000 /* packet 1 from server. */ + +#define MSPROXY_USERINFO_ACK 0x0400 /* packet 2 from server. */ + +#define MSPROXY_AUTHENTICATE 0x4700 /* authentication request */ +#define MSPROXY_AUTHENTICATE_ACK 0x4714 /* authentication challenge */ + +#define MSPROXY_AUTHENTICATE_2 0x4701 /* authentication response */ +#define MSPROXY_AUTHENTICATE_2_ACK 0x4715 /* authentication passed */ +#define MSPROXY_AUTHENTICATE_2_NAK 0x4716 /* authentication failure */ + +#define MSPROXY_CONNECT 0x071e /* connect request. */ +#define MSPROXY_CONNECT_ACK 0x0703 /* connect request accepted. */ + +#pragma pack(1) + +struct ntlm_buffer { + guint16 len; + guint16 alloc; + guint32 offset; +}; + +struct msproxy_request_t { + guint32 clientid; /* 1-4 */ + guint32 magic25; /* 5-8 */ + guint32 serverid; /* 9-12 */ + unsigned char serverack; /* 13: ack of last server packet */ + char pad10[3]; /* 14-16 */ + unsigned char sequence; /* 17: sequence # of this packet. */ + char pad11[7]; /* 18-24 */ + char RWSP[4]; /* 25-28: 0x52,0x57,0x53,0x50 */ + char pad15[8]; /* 29-36 */ + guint16 command; /* 37-38 */ + + /* packet specifics start at 39. */ + union { + struct { + char pad1[18]; /* 39-56 */ + guint16 magic3; /* 57-58 */ + char pad3[114]; /* 59-172 */ + guint16 magic5; /* 173-174: 0x4b, 0x00 */ + char pad5[2]; /* 175-176 */ + guint16 magic10; /* 177-178: 0x14, 0x00 */ + char pad6[2]; /* 179-180 */ + guint16 magic15; /* 181-182: 0x04, 0x00 */ + char pad10[2]; /* 183-184 */ + guint16 magic16; /* 185-186 */ + char pad11[2]; /* 187-188 */ + guint16 magic20; /* 189-190: 0x57, 0x04 */ + guint16 magic25; /* 191-192: 0x00, 0x04 */ + guint16 magic30; /* 193-194: 0x01, 0x00 */ + char pad20[2]; /* 195-196: 0x4a, 0x02 */ + guint16 magic35; /* 197-198: 0x4a, 0x02 */ + char pad30[10]; /* 199-208 */ + guint16 magic40; /* 209-210: 0x30, 0x00 */ + char pad40[2]; /* 211-212 */ + guint16 magic45; /* 213-214: 0x44, 0x00 */ + char pad45[2]; /* 215-216 */ + guint16 magic50; /* 217-218: 0x39, 0x00 */ + char pad50[2]; /* 219-220 */ + char data[256]; /* 221-EOP: a sequence of NULL-terminated strings: + - username; + - empty string (just a NULL); + - application name; + - hostname */ + } hello; + + struct { + char pad1[4]; /* 39-42 */ + guint16 magic2; /* 43-44 */ + char pad10[12]; /* 45-56 */ + guint32 bindaddr; /* 57-60: address to bind. */ + guint16 bindport; /* 61-62: port to bind. */ + char pad15[2]; /* 63-64 */ + guint16 magic3; /* 65-66 */ + guint16 boundport; /* 67-68 */ + char pad20[104]; /* 69-172 */ + char NTLMSSP[sizeof("NTLMSSP")]; /* 173-180: "NTLMSSP" */ + guint32 msgtype; /* 181-184: NTLM message type = 1 */ + guint32 flags; /* 185-188: NTLM message flags */ + guint16 magic20; /* 189-190: 0x28, 0x00 */ + char pad30[2]; /* 191-192 */ + guint16 magic25; /* 193-194: 0x96, 0x82 */ + guint16 magic30; /* 195-196: 0x01, 0x00 */ + char pad40[12]; /* 197-208 */ + guint16 magic50; /* 209-210: 0x30, 0x00 */ + char pad50[6]; /* 211-216 */ + guint16 magic55; /* 217-218: 0x30, 0x00 */ + char pad55[2]; /* 219-220 */ + char data[0]; /* Dummy end marker, no real data required */ + } auth; + + struct { + char pad1[4]; /* 39-42 */ + guint16 magic1; /* 43-44 */ + guint32 magic2; /* 45-48 */ + char pad2[8]; /* 49-56 */ + guint16 magic3; /* 57-58 */ + char pad3[6]; /* 59-64 */ + guint16 magic4; /* 65-66 */ + guint16 boundport; /* 67-68 */ + char pad4[104]; /* 69-172 */ + char NTLMSSP[sizeof("NTLMSSP")]; /* 173-180: "NTLMSSP" */ + guint32 msgtype; /* 181-184: NTLM message type = 3 */ + struct ntlm_buffer lm_resp; /* 185-192: LM response security buffer */ + struct ntlm_buffer ntlm_resp; /* 193-200: NTLM response security buffer */ + struct ntlm_buffer ntdomain_buf; /* 201-208: domain name security buffer */ + struct ntlm_buffer username_buf; /* 209-216: username security buffer */ + struct ntlm_buffer clienthost_buf; /* 217-224: hostname security buffer */ + struct ntlm_buffer sessionkey_buf; /* 225-232: session key security buffer */ + guint32 flags; /* 233-236: message flags */ + char data[1024]; /* 237-EOP: data area */ + } auth2; + + struct { + guint16 magic1; /* 39-40 */ + char pad1[2]; /* 41-42 */ + guint16 magic2; /* 43-44 */ + guint32 magic3; /* 45-48 */ + char pad5[8]; /* 48-56 */ + guint16 magic6; /* 57-58: 0x0200 */ + guint16 destport; /* 59-60 */ + guint32 destaddr; /* 61-64 */ + char pad10[4]; /* 65-68 */ + guint16 magic10; /* 69-70 */ + char pad15[2]; /* 71-72 */ + guint16 srcport; /* 73-74: port client connects from */ + char pad20[82]; /* 75-156 */ + char executable[256]; /* 76-EOP: application name */ + } connect; + + struct { + guint16 magic1; /* 39-40 */ + char pad5[2]; /* 41-42 */ + guint16 magic5; /* 43-44 */ + guint32 magic10; /* 45-48 */ + char pad10[2]; /* 49-50 */ + guint16 magic15; /* 51-52 */ + guint32 magic16; /* 53-56 */ + guint16 magic20; /* 57-58 */ + guint16 clientport; /* 59-60: forwarded port. */ + guint32 clientaddr; /* 61-64: forwarded address. */ + guint32 magic30; /* 65-68 */ + guint32 magic35; /* 69-72 */ + guint16 serverport; /* 73-74: port server will connect to us from. */ + guint16 srcport; /* 75-76: connect request; port used on client behalf. */ + guint16 boundport; /* 77-78: bind request; port used on client behalf. */ + guint32 boundaddr; /* 79-82: addr used on client behalf */ + char pad30[90]; /* 83-172 */ + char data[0]; /* End marker */ + } connack; + + } packet; +}; + +struct msproxy_response_t { + guint32 packetid; /* 1-4 */ + guint32 magic5; /* 5-8 */ + guint32 serverid; /* 9-12 */ + char clientack; /* 13: ack of last client packet. */ + char pad5[3]; /* 14-16 */ + unsigned char sequence; /* 17: sequence # of this packet. */ + char pad10[7]; /* 18-24 */ + char RWSP[4]; /* 25-28: 0x52,0x57,0x53,0x50 */ + char pad15[8]; /* 29-36 */ + guint16 command; /* 37-38 */ + + union { + struct { + char pad5[18]; /* 39-56 */ + guint16 magic20; /* 57-58: 0x02, 0x00 */ + char pad10[6]; /* 59-64 */ + guint16 magic30; /* 65-66: 0x74, 0x01 */ + char pad15[2]; /* 67-68 */ + guint16 magic35; /* 69-70: 0x0c, 0x00 */ + char pad20[6]; /* 71-76 */ + guint16 magic50; /* 77-78: 0x04, 0x00 */ + char pad30[6]; /* 79-84 */ + guint16 magic60; /* 85-86: 0x65, 0x05 */ + char pad35[2]; /* 87-88 */ + guint16 magic65; /* 89-90: 0x02, 0x00 */ + char pad40[8]; /* 91-98 */ + guint16 udpport; /* 99-100 */ + guint32 udpaddr; /* 101-104 */ + } hello; + + struct { + char pad1[6]; /* 39-44 */ + guint32 magic10; /* 45-48 */ + char pad3[10]; /* 49-58 */ + guint16 boundport; /* 59-60: port server bound for us. */ + guint32 boundaddr; /* 61-64: addr server bound for us. */ + char pad10[4]; /* 65-68 */ + guint16 magic15; /* 69-70 */ + char pad15[102]; /* 70-172 */ + char NTLMSSP[sizeof("NTLMSSP")]; /* 173-180: "NTLMSSP" */ + guint32 msgtype; /* 181-184: NTLM message type = 2 */ + struct ntlm_buffer target; /* 185-192: target security buffer */ + guint32 flags; /* 193-196: NTLM message flags */ + char challenge[8]; /* 197-204: NTLM challenge request */ + char context[8]; /* 205-212: NTLM context */ + char data[1024]; /* 213-EOP: target information data */ + } auth; + + struct { + guint16 magic1; /* 39-40 */ + char pad5[18]; /* 41-58 */ + guint16 clientport; /* 59-60: forwarded port. */ + guint32 clientaddr; /* 61-64: forwarded address. */ + guint32 magic10; /* 65-68 */ + guint32 magic15; /* 69-72 */ + guint16 serverport; /* 73-74: port server will connect to us from. */ + guint16 srcport; /* 75-76: connect request; port used on client behalf. */ + guint16 boundport; /* 77-78: bind request; port used on client behalf. */ + guint32 boundaddr; /* 79-82: addr used on client behalf */ + char pad10[90]; /* 83-172 */ + } connect; + } packet; +}; + +#pragma pack() + +int traverse_msproxy (int sok, char *serverAddr, int port, struct msproxy_state_t *state, netstore *ns_proxy, int csok4, int csok6, int *csok, char bound); +void msproxy_keepalive (void); diff --git a/src/common/network.c b/src/common/network.c new file mode 100644 index 00000000..0c409506 --- /dev/null +++ b/src/common/network.c @@ -0,0 +1,383 @@ +/* X-Chat + * Copyright (C) 2001 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +/* ipv4 and ipv6 networking functions with a common interface */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <glib.h> + +#include "../../config.h" /* grab USE_IPV6 and LOOKUPD defines */ + +#define WANTSOCKET +#define WANTARPA +#define WANTDNS +#include "inet.h" + +#define NETWORK_PRIVATE +#include "network.h" + +#define RAND_INT(n) ((int)(rand() / (RAND_MAX + 1.0) * (n))) + + +/* ================== COMMON ================= */ + +static void +net_set_socket_options (int sok) +{ + socklen_t sw; + + sw = 1; + setsockopt (sok, SOL_SOCKET, SO_REUSEADDR, (char *) &sw, sizeof (sw)); + sw = 1; + setsockopt (sok, SOL_SOCKET, SO_KEEPALIVE, (char *) &sw, sizeof (sw)); +} + +char * +net_ip (guint32 addr) +{ + struct in_addr ia; + + ia.s_addr = htonl (addr); + return inet_ntoa (ia); +} + +void +net_store_destroy (netstore * ns) +{ +#ifdef USE_IPV6 + if (ns->ip6_hostent) + freeaddrinfo (ns->ip6_hostent); +#endif + free (ns); +} + +netstore * +net_store_new (void) +{ + netstore *ns; + + ns = malloc (sizeof (netstore)); + memset (ns, 0, sizeof (netstore)); + + return ns; +} + +#ifndef USE_IPV6 + +/* =================== IPV4 ================== */ + +/* + A note about net_resolve and lookupd: + + Many IRC networks rely on round-robin DNS for load balancing, rotating the list + of IP address on each query. However, this method breaks when DNS queries are + cached. Mac OS X and Darwin handle DNS lookups through the lookupd daemon, which + caches queries in its default configuration: thus, if we always pick the first + address, we will be stuck with the same host (which might be down!) until the + TTL reaches 0 or lookupd is reset (typically, at reboot). Therefore, we need to + pick a random address from the result list, instead of always using the first. +*/ + +char * +net_resolve (netstore * ns, char *hostname, int port, char **real_host) +{ + ns->ip4_hostent = gethostbyname (hostname); + if (!ns->ip4_hostent) + return NULL; + + memset (&ns->addr, 0, sizeof (ns->addr)); +#ifdef LOOKUPD + int count = 0; + while (ns->ip4_hostent->h_addr_list[count]) count++; + memcpy (&ns->addr.sin_addr, + ns->ip4_hostent->h_addr_list[RAND_INT(count)], + ns->ip4_hostent->h_length); +#else + memcpy (&ns->addr.sin_addr, ns->ip4_hostent->h_addr, + ns->ip4_hostent->h_length); +#endif + ns->addr.sin_port = htons (port); + ns->addr.sin_family = AF_INET; + + *real_host = strdup (ns->ip4_hostent->h_name); + return strdup (inet_ntoa (ns->addr.sin_addr)); +} + +int +net_connect (netstore * ns, int sok4, int sok6, int *sok_return) +{ + *sok_return = sok4; + return connect (sok4, (struct sockaddr *) &ns->addr, sizeof (ns->addr)); +} + +void +net_bind (netstore * tobindto, int sok4, int sok6) +{ + bind (sok4, (struct sockaddr *) &tobindto->addr, sizeof (tobindto->addr)); +} + +void +net_sockets (int *sok4, int *sok6) +{ + *sok4 = socket (AF_INET, SOCK_STREAM, 0); + *sok6 = -1; + net_set_socket_options (*sok4); +} + +void +udp_sockets (int *sok4, int *sok6) +{ + *sok4 = socket (AF_INET, SOCK_DGRAM, 0); + *sok6 = -1; +} + +void +net_store_fill_any (netstore *ns) +{ + ns->addr.sin_family = AF_INET; + ns->addr.sin_addr.s_addr = INADDR_ANY; + ns->addr.sin_port = 0; +} + +void +net_store_fill_v4 (netstore *ns, guint32 addr, int port) +{ + ns->addr.sin_family = AF_INET; + ns->addr.sin_addr.s_addr = addr; + ns->addr.sin_port = port; +} + +guint32 +net_getsockaddr_v4 (netstore *ns) +{ + return ns->addr.sin_addr.s_addr; +} + +int +net_getsockport (int sok4, int sok6) +{ + struct sockaddr_in addr; + int len = sizeof (addr); + + if (getsockname (sok4, (struct sockaddr *)&addr, &len) == -1) + return -1; + return addr.sin_port; +} + +#else + +/* =================== IPV6 ================== */ + +char * +net_resolve (netstore * ns, char *hostname, int port, char **real_host) +{ + struct addrinfo hints; + char ipstring[MAX_HOSTNAME]; + char portstring[MAX_HOSTNAME]; + int ret; + +/* if (ns->ip6_hostent) + freeaddrinfo (ns->ip6_hostent);*/ + + sprintf (portstring, "%d", port); + + memset (&hints, 0, sizeof (struct addrinfo)); + hints.ai_family = PF_UNSPEC; /* support ipv6 and ipv4 */ + hints.ai_flags = AI_CANONNAME; + hints.ai_socktype = SOCK_STREAM; + + if (port == 0) + ret = getaddrinfo (hostname, NULL, &hints, &ns->ip6_hostent); + else + ret = getaddrinfo (hostname, portstring, &hints, &ns->ip6_hostent); + if (ret != 0) + return NULL; + +#ifdef LOOKUPD /* See note about lookupd above the IPv4 version of net_resolve. */ + struct addrinfo *tmp; + int count = 0; + + for (tmp = ns->ip6_hostent; tmp; tmp = tmp->ai_next) + count ++; + + count = RAND_INT(count); + + while (count--) ns->ip6_hostent = ns->ip6_hostent->ai_next; +#endif + + /* find the numeric IP number */ + ipstring[0] = 0; + getnameinfo (ns->ip6_hostent->ai_addr, ns->ip6_hostent->ai_addrlen, + ipstring, sizeof (ipstring), NULL, 0, NI_NUMERICHOST); + + if (ns->ip6_hostent->ai_canonname) + *real_host = strdup (ns->ip6_hostent->ai_canonname); + else + *real_host = strdup (hostname); + + return strdup (ipstring); +} + +/* the only thing making this interface unclean, this shitty sok4, sok6 business */ + +int +net_connect (netstore * ns, int sok4, int sok6, int *sok_return) +{ + struct addrinfo *res, *res0; + int error = -1; + + res0 = ns->ip6_hostent; + + for (res = res0; res; res = res->ai_next) + { +/* sok = socket (res->ai_family, res->ai_socktype, res->ai_protocol); + if (sok < 0) + continue;*/ + switch (res->ai_family) + { + case AF_INET: + error = connect (sok4, res->ai_addr, res->ai_addrlen); + *sok_return = sok4; + break; + case AF_INET6: + error = connect (sok6, res->ai_addr, res->ai_addrlen); + *sok_return = sok6; + break; + default: + error = 1; + } + + if (error == 0) + break; + } + + return error; +} + +void +net_bind (netstore * tobindto, int sok4, int sok6) +{ + bind (sok4, tobindto->ip6_hostent->ai_addr, + tobindto->ip6_hostent->ai_addrlen); + bind (sok6, tobindto->ip6_hostent->ai_addr, + tobindto->ip6_hostent->ai_addrlen); +} + +void +net_sockets (int *sok4, int *sok6) +{ + *sok4 = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); + *sok6 = socket (AF_INET6, SOCK_STREAM, IPPROTO_TCP); + net_set_socket_options (*sok4); + net_set_socket_options (*sok6); +} + +void +udp_sockets (int *sok4, int *sok6) +{ + *sok4 = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP); + *sok6 = socket (AF_INET6, SOCK_DGRAM, IPPROTO_UDP); +} + +/* the following functions are used only by MSPROXY and are not + proper ipv6 implementations - do not use in new code! */ + +void +net_store_fill_any (netstore *ns) +{ + struct addrinfo *ai; + struct sockaddr_in *sin; + + ai = ns->ip6_hostent; + if (!ai) { + ai = malloc (sizeof (struct addrinfo)); + memset (ai, 0, sizeof (struct addrinfo)); + ns->ip6_hostent = ai; + } + sin = (struct sockaddr_in *)ai->ai_addr; + if (!sin) { + sin = malloc (sizeof (struct sockaddr_in)); + memset (sin, 0, sizeof (struct sockaddr_in)); + ai->ai_addr = (struct sockaddr *)sin; + } + ai->ai_family = AF_INET; + ai->ai_addrlen = sizeof(struct sockaddr_in); + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = INADDR_ANY; + sin->sin_port = 0; + ai->ai_next = NULL; +} + +void +net_store_fill_v4 (netstore *ns, guint32 addr, int port) +{ + struct addrinfo *ai; + struct sockaddr_in *sin; + + ai = ns->ip6_hostent; + if (!ai) { + ai = malloc (sizeof (struct addrinfo)); + memset (ai, 0, sizeof (struct addrinfo)); + ns->ip6_hostent = ai; + } + sin = (struct sockaddr_in *)ai->ai_addr; + if (!sin) { + sin = malloc (sizeof (struct sockaddr_in)); + memset (sin, 0, sizeof (struct sockaddr_in)); + ai->ai_addr = (struct sockaddr *)sin; + } + ai->ai_family = AF_INET; + ai->ai_addrlen = sizeof(struct sockaddr_in); + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = addr; + sin->sin_port = port; + ai->ai_next = NULL; +} + +guint32 +net_getsockaddr_v4 (netstore *ns) +{ + struct addrinfo *ai; + struct sockaddr_in *sin; + + ai = ns->ip6_hostent; + + while (ai->ai_family != AF_INET) { + ai = ai->ai_next; + if (!ai) + return 0; + } + sin = (struct sockaddr_in *)ai->ai_addr; + return sin->sin_addr.s_addr; +} + +int +net_getsockport (int sok4, int sok6) +{ + struct sockaddr_in addr; + int len = sizeof (addr); + + if (getsockname (sok4, (struct sockaddr *)&addr, &len) == -1) + return -1; + return addr.sin_port; +} + +#endif diff --git a/src/common/network.h b/src/common/network.h new file mode 100644 index 00000000..f45f210a --- /dev/null +++ b/src/common/network.h @@ -0,0 +1,34 @@ +#ifndef XCHAT_NETWORK_H +#define XCHAT_NETWORK_H + +typedef struct netstore_ +{ +#ifdef NETWORK_PRIVATE +#ifdef USE_IPV6 + struct addrinfo *ip6_hostent; +#else + struct hostent *ip4_hostent; + struct sockaddr_in addr; +#endif +#else + int _dummy; /* some compilers don't like empty structs */ +#endif +} netstore; + +#define MAX_HOSTNAME 128 + +netstore *net_store_new (void); +void net_store_destroy (netstore *ns); +int net_connect (netstore *ns, int sok4, int sok6, int *sok_return); +char *net_resolve (netstore *ns, char *hostname, int port, char **real_host); +void net_bind (netstore *tobindto, int sok4, int sok6); +char *net_ip (guint32 addr); +void net_sockets (int *sok4, int *sok6); +/* functions for MSPROXY only! */ +void udp_sockets (int *sok4, int *sok6); +void net_store_fill_any (netstore *ns); +void net_store_fill_v4 (netstore *ns, guint32 addr, int port); +guint32 net_getsockaddr_v4 (netstore *ns); +int net_getsockport(int sok4, int sok6); + +#endif diff --git a/src/common/notify.c b/src/common/notify.c new file mode 100644 index 00000000..04795849 --- /dev/null +++ b/src/common/notify.c @@ -0,0 +1,634 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <time.h> + +#include "xchat.h" +#include "notify.h" +#include "cfgfiles.h" +#include "fe.h" +#include "server.h" +#include "text.h" +#include "util.h" +#include "xchatc.h" + + +GSList *notify_list = 0; +int notify_tag = 0; + + +static char * +despacify_dup (char *str) +{ + char *p, *res = malloc (strlen (str) + 1); + + p = res; + while (1) + { + if (*str != ' ') + { + *p = *str; + if (*p == 0) + return res; + p++; + } + str++; + } +} + +static int +notify_netcmp (char *str, void *serv) +{ + char *net = despacify_dup (server_get_network (serv, TRUE)); + + if (rfc_casecmp (str, net) == 0) + { + free (net); + return 0; /* finish & return FALSE from token_foreach() */ + } + + free (net); + return 1; /* keep going... */ +} + +/* monitor this nick on this particular network? */ + +static gboolean +notify_do_network (struct notify *notify, server *serv) +{ + if (!notify->networks) /* ALL networks for this nick */ + return TRUE; + + if (token_foreach (notify->networks, ',', notify_netcmp, serv)) + return FALSE; /* network list doesn't contain this one */ + + return TRUE; +} + +struct notify_per_server * +notify_find_server_entry (struct notify *notify, struct server *serv) +{ + GSList *list = notify->server_list; + struct notify_per_server *servnot; + + while (list) + { + servnot = (struct notify_per_server *) list->data; + if (servnot->server == serv) + return servnot; + list = list->next; + } + + /* not found, should we add it, or is this not a network where + we're monitoring this nick? */ + if (!notify_do_network (notify, serv)) + return NULL; + + servnot = malloc (sizeof (struct notify_per_server)); + if (servnot) + { + memset (servnot, 0, sizeof (struct notify_per_server)); + servnot->server = serv; + servnot->notify = notify; + notify->server_list = g_slist_prepend (notify->server_list, servnot); + } + return servnot; +} + +void +notify_save (void) +{ + int fh; + struct notify *notify; + GSList *list = notify_list; + + fh = xchat_open_file ("notify.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); + if (fh != -1) + { + while (list) + { + notify = (struct notify *) list->data; + write (fh, notify->name, strlen (notify->name)); + if (notify->networks) + { + write (fh, " ", 1); + write (fh, notify->networks, strlen (notify->networks)); + } + write (fh, "\n", 1); + list = list->next; + } + close (fh); + } +} + +void +notify_load (void) +{ + int fh; + char buf[256]; + char *sep; + + fh = xchat_open_file ("notify.conf", O_RDONLY, 0, 0); + if (fh != -1) + { + while (waitline (fh, buf, sizeof buf, FALSE) != -1) + { + if (buf[0] != '#' && buf[0] != 0) + { + sep = strchr (buf, ' '); + if (sep) + { + sep[0] = 0; + notify_adduser (buf, sep + 1); + } + else + notify_adduser (buf, NULL); + } + } + close (fh); + } +} + +static struct notify_per_server * +notify_find (server *serv, char *nick) +{ + GSList *list = notify_list; + struct notify_per_server *servnot; + struct notify *notify; + + while (list) + { + notify = (struct notify *) list->data; + + servnot = notify_find_server_entry (notify, serv); + if (!servnot) + { + list = list->next; + continue; + } + + if (!serv->p_cmp (notify->name, nick)) + return servnot; + + list = list->next; + } + + return 0; +} + +static void +notify_announce_offline (server * serv, struct notify_per_server *servnot, + char *nick, int quiet) +{ + session *sess; + + sess = serv->front_session; + + servnot->ison = FALSE; + servnot->lastoff = time (0); + if (!quiet) + EMIT_SIGNAL (XP_TE_NOTIFYOFFLINE, sess, nick, serv->servername, + server_get_network (serv, TRUE), NULL, 0); + fe_notify_update (nick); + fe_notify_update (0); +} + +static void +notify_announce_online (server * serv, struct notify_per_server *servnot, + char *nick) +{ + session *sess; + + sess = serv->front_session; + + servnot->lastseen = time (0); + if (servnot->ison) + return; + + servnot->ison = TRUE; + servnot->laston = time (0); + EMIT_SIGNAL (XP_TE_NOTIFYONLINE, sess, nick, serv->servername, + server_get_network (serv, TRUE), NULL, 0); + fe_notify_update (nick); + fe_notify_update (0); + + if (prefs.whois_on_notifyonline) + { + + /* Let's do whois with idle time (like in /quote WHOIS %s %s) */ + + char *wii_str = malloc (strlen (nick) * 2 + 2); + sprintf (wii_str, "%s %s", nick, nick); + serv->p_whois (serv, wii_str); + free (wii_str); + } +} + +/* handles numeric 601 */ + +void +notify_set_offline (server * serv, char *nick, int quiet) +{ + struct notify_per_server *servnot; + + servnot = notify_find (serv, nick); + if (!servnot) + return; + + notify_announce_offline (serv, servnot, nick, quiet); +} + +/* handles numeric 604 and 600 */ + +void +notify_set_online (server * serv, char *nick) +{ + struct notify_per_server *servnot; + + servnot = notify_find (serv, nick); + if (!servnot) + return; + + notify_announce_online (serv, servnot, nick); +} + +static void +notify_watch (server * serv, char *nick, int add) +{ + char tbuf[256]; + + snprintf (tbuf, sizeof (tbuf), "WATCH +%s", nick); + if (!add) + tbuf[6] = '-'; + serv->p_raw (serv, tbuf); +} + +static void +notify_watch_all (struct notify *notify, int add) +{ + server *serv; + GSList *list = serv_list; + while (list) + { + serv = list->data; + if (serv->connected && serv->end_of_motd && serv->supports_watch && + notify_do_network (notify, serv)) + notify_watch (serv, notify->name, add); + list = list->next; + } +} + +static void +notify_flush_watches (server * serv, GSList *from, GSList *end) +{ + char tbuf[512]; + GSList *list; + struct notify *notify; + + strcpy (tbuf, "WATCH"); + + list = from; + while (list != end) + { + notify = list->data; + strcat (tbuf, " +"); + strcat (tbuf, notify->name); + list = list->next; + } + serv->p_raw (serv, tbuf); +} + +/* called when logging in. e.g. when End of motd. */ + +void +notify_send_watches (server * serv) +{ + struct notify *notify; + GSList *list; + GSList *point; + int len; + + len = 0; + point = list = notify_list; + while (list) + { + notify = list->data; + + if (notify_do_network (notify, serv)) + { + len += strlen (notify->name) + 2 /* + and space */; + if (len > 500) + { + notify_flush_watches (serv, point, list); + len = strlen (notify->name) + 2; + point = list; + } + } + + list = list->next; + } + + if (point) + notify_flush_watches (serv, point, NULL); +} + +/* called when receiving a ISON 303 - should this func go? */ + +void +notify_markonline (server *serv, char *word[]) +{ + struct notify *notify; + struct notify_per_server *servnot; + GSList *list = notify_list; + int i, seen; + + while (list) + { + notify = (struct notify *) list->data; + servnot = notify_find_server_entry (notify, serv); + if (!servnot) + { + list = list->next; + continue; + } + i = 4; + seen = FALSE; + while (*word[i]) + { + if (!serv->p_cmp (notify->name, word[i])) + { + seen = TRUE; + notify_announce_online (serv, servnot, notify->name); + break; + } + i++; + /* FIXME: word[] is only a 32 element array, limits notify list to + about 27 people */ + if (i > PDIWORDS - 5) + { + /*fprintf (stderr, _("*** XCHAT WARNING: notify list too large.\n"));*/ + break; + } + } + if (!seen && servnot->ison) + { + notify_announce_offline (serv, servnot, notify->name, FALSE); + } + list = list->next; + } + fe_notify_update (0); +} + +/* yuck! Old routine for ISON notify */ + +static void +notify_checklist_for_server (server *serv) +{ + char outbuf[512]; + struct notify *notify; + GSList *list = notify_list; + int i = 0; + + strcpy (outbuf, "ISON "); + while (list) + { + notify = list->data; + if (notify_do_network (notify, serv)) + { + i++; + strcat (outbuf, notify->name); + strcat (outbuf, " "); + if (strlen (outbuf) > 460) + { + /* LAME: we can't send more than 512 bytes to the server, but * + * if we split it in two packets, our offline detection wouldn't * + work */ + /*fprintf (stderr, _("*** XCHAT WARNING: notify list too large.\n"));*/ + break; + } + } + list = list->next; + } + + if (i) + serv->p_raw (serv, outbuf); +} + +int +notify_checklist (void) /* check ISON list */ +{ + struct server *serv; + GSList *list = serv_list; + + while (list) + { + serv = list->data; + if (serv->connected && serv->end_of_motd && !serv->supports_watch) + { + notify_checklist_for_server (serv); + } + list = list->next; + } + return 1; +} + +void +notify_showlist (struct session *sess) +{ + char outbuf[256]; + struct notify *notify; + GSList *list = notify_list; + struct notify_per_server *servnot; + int i = 0; + + EMIT_SIGNAL (XP_TE_NOTIFYHEAD, sess, NULL, NULL, NULL, NULL, 0); + while (list) + { + i++; + notify = (struct notify *) list->data; + servnot = notify_find_server_entry (notify, sess->server); + if (servnot && servnot->ison) + snprintf (outbuf, sizeof (outbuf), _(" %-20s online\n"), notify->name); + else + snprintf (outbuf, sizeof (outbuf), _(" %-20s offline\n"), notify->name); + PrintText (sess, outbuf); + list = list->next; + } + if (i) + { + sprintf (outbuf, "%d", i); + EMIT_SIGNAL (XP_TE_NOTIFYNUMBER, sess, outbuf, NULL, NULL, NULL, 0); + } else + EMIT_SIGNAL (XP_TE_NOTIFYEMPTY, sess, NULL, NULL, NULL, NULL, 0); +} + +int +notify_deluser (char *name) +{ + struct notify *notify; + struct notify_per_server *servnot; + GSList *list = notify_list; + + while (list) + { + notify = (struct notify *) list->data; + if (!rfc_casecmp (notify->name, name)) + { + fe_notify_update (notify->name); + /* Remove the records for each server */ + while (notify->server_list) + { + servnot = (struct notify_per_server *) notify->server_list->data; + notify->server_list = + g_slist_remove (notify->server_list, servnot); + free (servnot); + } + notify_list = g_slist_remove (notify_list, notify); + notify_watch_all (notify, FALSE); + if (notify->networks) + free (notify->networks); + free (notify->name); + free (notify); + fe_notify_update (0); + return 1; + } + list = list->next; + } + return 0; +} + +void +notify_adduser (char *name, char *networks) +{ + struct notify *notify = malloc (sizeof (struct notify)); + if (notify) + { + memset (notify, 0, sizeof (struct notify)); + if (strlen (name) >= NICKLEN) + { + notify->name = malloc (NICKLEN); + safe_strcpy (notify->name, name, NICKLEN); + } else + { + notify->name = strdup (name); + } + if (networks) + notify->networks = despacify_dup (networks); + notify->server_list = 0; + notify_list = g_slist_prepend (notify_list, notify); + notify_checklist (); + fe_notify_update (notify->name); + fe_notify_update (0); + notify_watch_all (notify, TRUE); + } +} + +gboolean +notify_is_in_list (server *serv, char *name) +{ + struct notify *notify; + GSList *list = notify_list; + + while (list) + { + notify = (struct notify *) list->data; + if (!serv->p_cmp (notify->name, name)) + return TRUE; + list = list->next; + } + + return FALSE; +} + +int +notify_isnotify (struct session *sess, char *name) +{ + struct notify *notify; + struct notify_per_server *servnot; + GSList *list = notify_list; + + while (list) + { + notify = (struct notify *) list->data; + if (!sess->server->p_cmp (notify->name, name)) + { + servnot = notify_find_server_entry (notify, sess->server); + if (servnot && servnot->ison) + return TRUE; + } + list = list->next; + } + + return FALSE; +} + +void +notify_cleanup () +{ + GSList *list = notify_list; + GSList *nslist, *srvlist; + struct notify *notify; + struct notify_per_server *servnot; + struct server *serv; + int valid; + + while (list) + { + /* Traverse the list of notify structures */ + notify = (struct notify *) list->data; + nslist = notify->server_list; + while (nslist) + { + /* Look at each per-server structure */ + servnot = (struct notify_per_server *) nslist->data; + + /* Check the server is valid */ + valid = FALSE; + srvlist = serv_list; + while (srvlist) + { + serv = (struct server *) srvlist->data; + if (servnot->server == serv) + { + valid = serv->connected; /* Only valid if server is too */ + break; + } + srvlist = srvlist->next; + } + if (!valid) + { + notify->server_list = + g_slist_remove (notify->server_list, servnot); + free (servnot); + nslist = notify->server_list; + } else + { + nslist = nslist->next; + } + } + list = list->next; + } + fe_notify_update (0); +} diff --git a/src/common/notify.h b/src/common/notify.h new file mode 100644 index 00000000..37674fe5 --- /dev/null +++ b/src/common/notify.h @@ -0,0 +1,44 @@ +#ifndef XCHAT_NOTIFY_H +#define XCHAT_NOTIFY_H + +struct notify +{ + char *name; + char *networks; /* network names, comma sep */ + GSList *server_list; +}; + +struct notify_per_server +{ + struct server *server; + struct notify *notify; + time_t laston; + time_t lastseen; + time_t lastoff; + unsigned int ison:1; +}; + +extern GSList *notify_list; +extern int notify_tag; + +/* the WATCH stuff */ +void notify_set_online (server * serv, char *nick); +void notify_set_offline (server * serv, char *nick, int quiet); +void notify_send_watches (server * serv); + +/* the general stuff */ +void notify_adduser (char *name, char *networks); +int notify_deluser (char *name); +void notify_cleanup (void); +void notify_load (void); +void notify_save (void); +void notify_showlist (session *sess); +gboolean notify_is_in_list (server *serv, char *name); +int notify_isnotify (session *sess, char *name); +struct notify_per_server *notify_find_server_entry (struct notify *notify, struct server *serv); + +/* the old ISON stuff - remove me? */ +void notify_markonline (server *serv, char *word[]); +int notify_checklist (void); + +#endif diff --git a/src/common/outbound.c b/src/common/outbound.c new file mode 100644 index 00000000..7c6e5e6a --- /dev/null +++ b/src/common/outbound.c @@ -0,0 +1,4403 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#define _GNU_SOURCE /* for memrchr */ +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> +#include <limits.h> +#include <errno.h> + +#define WANTSOCKET +#define WANTARPA +#include "inet.h" + +#ifndef WIN32 +#include <sys/wait.h> +#endif + +#include <unistd.h> +#include <time.h> +#include <signal.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "xchat.h" +#include "plugin.h" +#include "ignore.h" +#include "util.h" +#include "fe.h" +#include "cfgfiles.h" /* xchat_fopen_file() */ +#include "network.h" /* net_ip() */ +#include "modes.h" +#include "notify.h" +#include "inbound.h" +#include "text.h" +#include "xchatc.h" +#include "servlist.h" +#include "server.h" +#include "tree.h" +#include "outbound.h" + + +#ifdef USE_DEBUG +extern int current_mem_usage; +#endif +#define TBUFSIZE 4096 + +static void help (session *sess, char *tbuf, char *helpcmd, int quiet); +static int cmd_server (session *sess, char *tbuf, char *word[], char *word_eol[]); +static void handle_say (session *sess, char *text, int check_spch); + + +static void +notj_msg (struct session *sess) +{ + PrintText (sess, _("No channel joined. Try /join #<channel>\n")); +} + +void +notc_msg (struct session *sess) +{ + PrintText (sess, _("Not connected. Try /server <host> [<port>]\n")); +} + +static char * +random_line (char *file_name) +{ + FILE *fh; + char buf[512]; + int lines, ran; + + if (!file_name[0]) + goto nofile; + + fh = xchat_fopen_file (file_name, "r", 0); + if (!fh) + { + nofile: + /* reason is not a file, an actual reason! */ + return strdup (file_name); + } + + /* count number of lines in file */ + lines = 0; + while (fgets (buf, sizeof (buf), fh)) + lines++; + + if (lines < 1) + goto nofile; + + /* go down a random number */ + rewind (fh); + ran = RAND_INT (lines); + do + { + fgets (buf, sizeof (buf), fh); + lines--; + } + while (lines > ran); + fclose (fh); + buf[strlen (buf) - 1] = 0; /* remove the trailing '\n' */ + return strdup (buf); +} + +void +server_sendpart (server * serv, char *channel, char *reason) +{ + if (!reason) + { + reason = random_line (prefs.partreason); + serv->p_part (serv, channel, reason); + free (reason); + } else + { + /* reason set by /quit, /close argument */ + serv->p_part (serv, channel, reason); + } +} + +void +server_sendquit (session * sess) +{ + char *rea, *colrea; + + if (!sess->quitreason) + { + colrea = strdup (prefs.quitreason); + check_special_chars (colrea, FALSE); + rea = random_line (colrea); + free (colrea); + sess->server->p_quit (sess->server, rea); + free (rea); + } else + { + /* reason set by /quit, /close argument */ + sess->server->p_quit (sess->server, sess->quitreason); + } +} + +void +process_data_init (char *buf, char *cmd, char *word[], + char *word_eol[], gboolean handle_quotes, + gboolean allow_escape_quotes) +{ + int wordcount = 2; + int space = FALSE; + int quote = FALSE; + int j = 0; + int len; + + word[0] = "\000\000"; + word_eol[0] = "\000\000"; + word[1] = (char *)buf; + word_eol[1] = (char *)cmd; + + while (1) + { + switch (*cmd) + { + case 0: + buf[j] = 0; + for (j = wordcount; j < PDIWORDS; j++) + { + word[j] = "\000\000"; + word_eol[j] = "\000\000"; + } + return; + case '\042': + if (!handle_quotes) + goto def; + /* two quotes turn into 1 */ + if (allow_escape_quotes && cmd[1] == '\042') + { + cmd++; + goto def; + } + if (quote) + { + quote = FALSE; + space = FALSE; + } else + quote = TRUE; + cmd++; + break; + case ' ': + if (!quote) + { + if (!space) + { + buf[j] = 0; + j++; + + if (wordcount < PDIWORDS) + { + word[wordcount] = &buf[j]; + word_eol[wordcount] = cmd + 1; + wordcount++; + } + + space = TRUE; + } + cmd++; + break; + } + default: +def: + space = FALSE; + len = g_utf8_skip[((unsigned char *)cmd)[0]]; + if (len == 1) + { + buf[j] = *cmd; + j++; + cmd++; + } else + { + /* skip past a multi-byte utf8 char */ + memcpy (buf + j, cmd, len); + j += len; + cmd += len; + } + } + } +} + +static int +cmd_addbutton (struct session *sess, char *tbuf, char *word[], + char *word_eol[]) +{ + if (*word[2] && *word_eol[3]) + { + if (sess->type == SESS_DIALOG) + { + list_addentry (&dlgbutton_list, word_eol[3], word[2]); + fe_dlgbuttons_update (sess); + } else + { + list_addentry (&button_list, word_eol[3], word[2]); + fe_buttons_update (sess); + } + return TRUE; + } + return FALSE; +} + +static int +cmd_allchannels (session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + GSList *list = sess_list; + + if (!*word_eol[2]) + return FALSE; + + while (list) + { + sess = list->data; + if (sess->type == SESS_CHANNEL && sess->channel[0] && sess->server->connected) + { + handle_command (sess, word_eol[2], FALSE); + } + list = list->next; + } + + return TRUE; +} + +static int +cmd_allchannelslocal (session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + GSList *list = sess_list; + server *serv = sess->server; + + if (!*word_eol[2]) + return FALSE; + + while (list) + { + sess = list->data; + if (sess->type == SESS_CHANNEL && sess->channel[0] && + sess->server->connected && sess->server == serv) + { + handle_command (sess, word_eol[2], FALSE); + } + list = list->next; + } + + return TRUE; +} + +static int +cmd_allservers (struct session *sess, char *tbuf, char *word[], + char *word_eol[]) +{ + GSList *list; + server *serv; + + if (!*word_eol[2]) + return FALSE; + + list = serv_list; + while (list) + { + serv = list->data; + if (serv->connected) + handle_command (serv->front_session, word_eol[2], FALSE); + list = list->next; + } + + return TRUE; +} + +static int +cmd_away (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + GSList *list; + char *reason = word_eol[2]; + + if (!(*reason)) + { + if (sess->server->is_away) + { + if (sess->server->last_away_reason) + PrintTextf (sess, _("Already marked away: %s\n"), sess->server->last_away_reason); + return FALSE; + } + + if (sess->server->reconnect_away) + reason = sess->server->last_away_reason; + else + /* must manage memory pointed to by random_line() */ + reason = random_line (prefs.awayreason); + } + sess->server->p_set_away (sess->server, reason); + + if (prefs.show_away_message) + { + snprintf (tbuf, TBUFSIZE, "me is away: %s", reason); + for (list = sess_list; list; list = list->next) + { + /* am I the right server and not a dialog box */ + if (((struct session *) list->data)->server == sess->server + && ((struct session *) list->data)->type == SESS_CHANNEL + && ((struct session *) list->data)->channel[0]) + { + handle_command ((session *) list->data, tbuf, TRUE); + } + } + } + + if (sess->server->last_away_reason != reason) + { + if (sess->server->last_away_reason) + free (sess->server->last_away_reason); + + if (reason == word_eol[2]) + sess->server->last_away_reason = strdup (reason); + else + sess->server->last_away_reason = reason; + } + + if (!sess->server->connected) + sess->server->reconnect_away = 1; + + return TRUE; +} + +static int +cmd_back (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + GSList *list; + unsigned int gone; + + if (sess->server->is_away) + { + sess->server->p_set_back (sess->server); + + if (prefs.show_away_message) + { + gone = time (NULL) - sess->server->away_time; + sprintf (tbuf, "me is back (gone %.2d:%.2d:%.2d)", gone / 3600, + (gone / 60) % 60, gone % 60); + for (list = sess_list; list; list = list->next) + { + /* am I the right server and not a dialog box */ + if (((struct session *) list->data)->server == sess->server + && ((struct session *) list->data)->type == SESS_CHANNEL + && ((struct session *) list->data)->channel[0]) + { + handle_command ((session *) list->data, tbuf, TRUE); + } + } + } + } + else + { + PrintText (sess, _("Already marked back.\n")); + } + + if (sess->server->last_away_reason) + free (sess->server->last_away_reason); + sess->server->last_away_reason = NULL; + + return TRUE; +} + +static void +ban (session * sess, char *tbuf, char *mask, char *bantypestr, int deop) +{ + int bantype; + struct User *user; + char *at, *dot, *lastdot; + char username[64], fullhost[128], domain[128], *mode, *p2; + server *serv = sess->server; + + user = userlist_find (sess, mask); + if (user && user->hostname) /* it's a nickname, let's find a proper ban mask */ + { + if (deop) + { + mode = "-o+b "; + p2 = user->nick; + } else + { + mode = "+b"; + p2 = ""; + } + + mask = user->hostname; + + at = strchr (mask, '@'); /* FIXME: utf8 */ + if (!at) + return; /* can't happen? */ + *at = 0; + + if (mask[0] == '~' || mask[0] == '+' || + mask[0] == '=' || mask[0] == '^' || mask[0] == '-') + { + /* the ident is prefixed with something, we replace that sign with an * */ + safe_strcpy (username+1, mask+1, sizeof (username)-1); + username[0] = '*'; + } else if (at - mask < USERNAMELEN) + { + /* we just add an * in the begining of the ident */ + safe_strcpy (username+1, mask, sizeof (username)-1); + username[0] = '*'; + } else + { + /* ident might be too long, we just ban what it gives and add an * in the end */ + safe_strcpy (username, mask, sizeof (username)); + } + *at = '@'; + safe_strcpy (fullhost, at + 1, sizeof (fullhost)); + + dot = strchr (fullhost, '.'); + if (dot) + { + safe_strcpy (domain, dot, sizeof (domain)); + } else + { + safe_strcpy (domain, fullhost, sizeof (domain)); + } + + if (*bantypestr) + bantype = atoi (bantypestr); + else + bantype = prefs.bantype; + + tbuf[0] = 0; + if (inet_addr (fullhost) != -1) /* "fullhost" is really a IP number */ + { + lastdot = strrchr (fullhost, '.'); + if (!lastdot) + return; /* can't happen? */ + + *lastdot = 0; + strcpy (domain, fullhost); + *lastdot = '.'; + + switch (bantype) + { + case 0: + snprintf (tbuf, TBUFSIZE, "%s%s *!*@%s.*", mode, p2, domain); + break; + + case 1: + snprintf (tbuf, TBUFSIZE, "%s%s *!*@%s", mode, p2, fullhost); + break; + + case 2: + snprintf (tbuf, TBUFSIZE, "%s%s *!%s@%s.*", mode, p2, username, domain); + break; + + case 3: + snprintf (tbuf, TBUFSIZE, "%s%s *!%s@%s", mode, p2, username, fullhost); + break; + } + } else + { + switch (bantype) + { + case 0: + snprintf (tbuf, TBUFSIZE, "%s%s *!*@*%s", mode, p2, domain); + break; + + case 1: + snprintf (tbuf, TBUFSIZE, "%s%s *!*@%s", mode, p2, fullhost); + break; + + case 2: + snprintf (tbuf, TBUFSIZE, "%s%s *!%s@*%s", mode, p2, username, domain); + break; + + case 3: + snprintf (tbuf, TBUFSIZE, "%s%s *!%s@%s", mode, p2, username, fullhost); + break; + } + } + + } else + { + snprintf (tbuf, TBUFSIZE, "+b %s", mask); + } + serv->p_mode (serv, sess->channel, tbuf); +} + +static int +cmd_ban (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *mask = word[2]; + + if (*mask) + { + ban (sess, tbuf, mask, word[3], 0); + } else + { + sess->server->p_mode (sess->server, sess->channel, "+b"); /* banlist */ + } + + return TRUE; +} + +static int +cmd_unban (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + /* Allow more than one mask in /unban -- tvk */ + int i = 2; + + while (1) + { + if (!*word[i]) + { + if (i == 2) + return FALSE; + send_channel_modes (sess, tbuf, word, 2, i, '-', 'b', 0); + return TRUE; + } + i++; + } +} + +static int +cmd_chanopt (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + /* chanopt.c */ + return chanopt_command (sess, tbuf, word, word_eol); +} + +static int +cmd_charset (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + server *serv = sess->server; + const char *locale = NULL; + int offset = 0; + + if (strcmp (word[2], "-quiet") == 0) + offset++; + + if (!word[2 + offset][0]) + { + g_get_charset (&locale); + PrintTextf (sess, "Current charset: %s\n", + serv->encoding ? serv->encoding : locale); + return TRUE; + } + + if (servlist_check_encoding (word[2 + offset])) + { + server_set_encoding (serv, word[2 + offset]); + if (offset < 1) + PrintTextf (sess, "Charset changed to: %s\n", word[2 + offset]); + } else + { + PrintTextf (sess, "\0034Unknown charset:\017 %s\n", word[2 + offset]); + } + + return TRUE; +} + +static int +cmd_clear (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + GSList *list = sess_list; + char *reason = word_eol[2]; + + if (strcasecmp (reason, "HISTORY") == 0) + { + history_free (&sess->history); + return TRUE; + } + + if (strncasecmp (reason, "all", 3) == 0) + { + while (list) + { + sess = list->data; + if (!sess->nick_said) + fe_text_clear (list->data, 0); + list = list->next; + } + return TRUE; + } + + if (reason[0] != '-' && !isdigit (reason[0]) && reason[0] != 0) + return FALSE; + + fe_text_clear (sess, atoi (reason)); + return TRUE; +} + +static int +cmd_close (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + GSList *list; + + if (strcmp (word[2], "-m") == 0) + { + list = sess_list; + while (list) + { + sess = list->data; + list = list->next; + if (sess->type == SESS_DIALOG) + fe_close_window (sess); + } + } else + { + if (*word_eol[2]) + sess->quitreason = word_eol[2]; + fe_close_window (sess); + } + + return TRUE; +} + +static int +cmd_ctcp (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int mbl; + char *to = word[2]; + if (*to) + { + char *msg = word_eol[3]; + if (*msg) + { + unsigned char *cmd = (unsigned char *)msg; + + /* make the first word upper case (as per RFC) */ + while (1) + { + if (*cmd == ' ' || *cmd == 0) + break; + mbl = g_utf8_skip[*cmd]; + if (mbl == 1) + *cmd = toupper (*cmd); + cmd += mbl; + } + + sess->server->p_ctcp (sess->server, to, msg); + + EMIT_SIGNAL (XP_TE_CTCPSEND, sess, to, msg, NULL, NULL, 0); + + return TRUE; + } + } + return FALSE; +} + +static int +cmd_country (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *code = word[2]; + if (*code) + { + /* search? */ + if (strcmp (code, "-s") == 0) + { + country_search (word[3], sess, (void *)PrintTextf); + return TRUE; + } + + /* search, but forgot the -s */ + if (strchr (code, '*')) + { + country_search (code, sess, (void *)PrintTextf); + return TRUE; + } + + sprintf (tbuf, "%s = %s\n", code, country (code)); + PrintText (sess, tbuf); + return TRUE; + } + return FALSE; +} + +static int +cmd_cycle (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *key = sess->channelkey; + char *chan = word[2]; + if (!*chan) + chan = sess->channel; + if (*chan && sess->type == SESS_CHANNEL) + { + sess->server->p_cycle (sess->server, chan, key); + return TRUE; + } + return FALSE; +} + +static int +cmd_dcc (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int goodtype; + struct DCC *dcc = 0; + char *type = word[2]; + if (*type) + { + if (!strcasecmp (type, "HELP")) + return FALSE; + if (!strcasecmp (type, "CLOSE")) + { + if (*word[3] && *word[4]) + { + goodtype = 0; + if (!strcasecmp (word[3], "SEND")) + { + dcc = find_dcc (word[4], word[5], TYPE_SEND); + dcc_abort (sess, dcc); + goodtype = TRUE; + } + if (!strcasecmp (word[3], "GET")) + { + dcc = find_dcc (word[4], word[5], TYPE_RECV); + dcc_abort (sess, dcc); + goodtype = TRUE; + } + if (!strcasecmp (word[3], "CHAT")) + { + dcc = find_dcc (word[4], "", TYPE_CHATRECV); + if (!dcc) + dcc = find_dcc (word[4], "", TYPE_CHATSEND); + dcc_abort (sess, dcc); + goodtype = TRUE; + } + + if (!goodtype) + return FALSE; + + if (!dcc) + EMIT_SIGNAL (XP_TE_NODCC, sess, NULL, NULL, NULL, NULL, 0); + + return TRUE; + + } + return FALSE; + } + if ((!strcasecmp (type, "CHAT")) || (!strcasecmp (type, "PCHAT"))) + { + char *nick = word[3]; + int passive = (!strcasecmp(type, "PCHAT")) ? 1 : 0; + if (*nick) + dcc_chat (sess, nick, passive); + return TRUE; + } + if (!strcasecmp (type, "LIST")) + { + dcc_show_list (sess); + return TRUE; + } + if (!strcasecmp (type, "GET")) + { + char *nick = word[3]; + char *file = word[4]; + if (!*file) + { + if (*nick) + dcc_get_nick (sess, nick); + } else + { + dcc = find_dcc (nick, file, TYPE_RECV); + if (dcc) + dcc_get (dcc); + else + EMIT_SIGNAL (XP_TE_NODCC, sess, NULL, NULL, NULL, NULL, 0); + } + return TRUE; + } + if ((!strcasecmp (type, "SEND")) || (!strcasecmp (type, "PSEND"))) + { + int i = 3, maxcps; + char *nick, *file; + int passive = (!strcasecmp(type, "PSEND")) ? 1 : 0; + + nick = word[i]; + if (!*nick) + return FALSE; + + maxcps = prefs.dcc_max_send_cps; + if (!strncasecmp(nick, "-maxcps=", 8)) + { + maxcps = atoi(nick + 8); + i++; + nick = word[i]; + if (!*nick) + return FALSE; + } + + i++; + + file = word[i]; + if (!*file) + { + fe_dcc_send_filereq (sess, nick, maxcps, passive); + return TRUE; + } + + do + { + dcc_send (sess, nick, file, maxcps, passive); + i++; + file = word[i]; + } + while (*file); + + return TRUE; + } + + return FALSE; + } + + dcc_show_list (sess); + return TRUE; +} + +static int +cmd_debug (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + struct session *s; + struct server *v; + GSList *list = sess_list; + + PrintText (sess, "Session T Channel WaitChan WillChan Server\n"); + while (list) + { + s = (struct session *) list->data; + sprintf (tbuf, "%p %1x %-10.10s %-10.10s %-10.10s %p\n", + s, s->type, s->channel, s->waitchannel, + s->willjoinchannel, s->server); + PrintText (sess, tbuf); + list = list->next; + } + + list = serv_list; + PrintText (sess, "Server Sock Name\n"); + while (list) + { + v = (struct server *) list->data; + sprintf (tbuf, "%p %-5d %s\n", + v, v->sok, v->servername); + PrintText (sess, tbuf); + list = list->next; + } + + sprintf (tbuf, + "\nfront_session: %p\n" + "current_tab: %p\n\n", + sess->server->front_session, current_tab); + PrintText (sess, tbuf); +#ifdef USE_DEBUG + sprintf (tbuf, "current mem: %d\n\n", current_mem_usage); + PrintText (sess, tbuf); +#endif /* !MEMORY_DEBUG */ + + return TRUE; +} + +static int +cmd_delbutton (struct session *sess, char *tbuf, char *word[], + char *word_eol[]) +{ + if (*word[2]) + { + if (sess->type == SESS_DIALOG) + { + if (list_delentry (&dlgbutton_list, word[2])) + fe_dlgbuttons_update (sess); + } else + { + if (list_delentry (&button_list, word[2])) + fe_buttons_update (sess); + } + return TRUE; + } + return FALSE; +} + +static int +cmd_dehop (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int i = 2; + + while (1) + { + if (!*word[i]) + { + if (i == 2) + return FALSE; + send_channel_modes (sess, tbuf, word, 2, i, '-', 'h', 0); + return TRUE; + } + i++; + } +} + +static int +cmd_deop (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int i = 2; + + while (1) + { + if (!*word[i]) + { + if (i == 2) + return FALSE; + send_channel_modes (sess, tbuf, word, 2, i, '-', 'o', 0); + return TRUE; + } + i++; + } +} + +typedef struct +{ + char **nicks; + int i; + session *sess; + char *reason; + char *tbuf; +} multidata; + +static int +mdehop_cb (struct User *user, multidata *data) +{ + if (user->hop && !user->me) + { + data->nicks[data->i] = user->nick; + data->i++; + } + return TRUE; +} + +static int +cmd_mdehop (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char **nicks = malloc (sizeof (char *) * sess->hops); + multidata data; + + data.nicks = nicks; + data.i = 0; + tree_foreach (sess->usertree, (tree_traverse_func *)mdehop_cb, &data); + send_channel_modes (sess, tbuf, nicks, 0, data.i, '-', 'h', 0); + free (nicks); + + return TRUE; +} + +static int +mdeop_cb (struct User *user, multidata *data) +{ + if (user->op && !user->me) + { + data->nicks[data->i] = user->nick; + data->i++; + } + return TRUE; +} + +static int +cmd_mdeop (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char **nicks = malloc (sizeof (char *) * sess->ops); + multidata data; + + data.nicks = nicks; + data.i = 0; + tree_foreach (sess->usertree, (tree_traverse_func *)mdeop_cb, &data); + send_channel_modes (sess, tbuf, nicks, 0, data.i, '-', 'o', 0); + free (nicks); + + return TRUE; +} + +GSList *menu_list = NULL; + +static void +menu_free (menu_entry *me) +{ + free (me->path); + if (me->label) + free (me->label); + if (me->cmd) + free (me->cmd); + if (me->ucmd) + free (me->ucmd); + if (me->group) + free (me->group); + if (me->icon) + free (me->icon); + free (me); +} + +/* strings equal? but ignore underscores */ + +int +menu_streq (const char *s1, const char *s2, int def) +{ + /* for separators */ + if (s1 == NULL && s2 == NULL) + return 0; + if (s1 == NULL || s2 == NULL) + return 1; + while (*s1) + { + if (*s1 == '_') + s1++; + if (*s2 == '_') + s2++; + if (*s1 != *s2) + return 1; + s1++; + s2++; + } + if (!*s2) + return 0; + return def; +} + +static menu_entry * +menu_entry_find (char *path, char *label) +{ + GSList *list; + menu_entry *me; + + list = menu_list; + while (list) + { + me = list->data; + if (!strcmp (path, me->path)) + { + if (me->label && label && !strcmp (label, me->label)) + return me; + } + list = list->next; + } + return NULL; +} + +static void +menu_del_children (char *path, char *label) +{ + GSList *list, *next; + menu_entry *me; + char buf[512]; + + if (!label) + label = ""; + if (path[0]) + snprintf (buf, sizeof (buf), "%s/%s", path, label); + else + snprintf (buf, sizeof (buf), "%s", label); + + list = menu_list; + while (list) + { + me = list->data; + next = list->next; + if (!menu_streq (buf, me->path, 0)) + { + menu_list = g_slist_remove (menu_list, me); + menu_free (me); + } + list = next; + } +} + +static int +menu_del (char *path, char *label) +{ + GSList *list; + menu_entry *me; + + list = menu_list; + while (list) + { + me = list->data; + if (!menu_streq (me->label, label, 1) && !menu_streq (me->path, path, 1)) + { + menu_list = g_slist_remove (menu_list, me); + fe_menu_del (me); + menu_free (me); + /* delete this item's children, if any */ + menu_del_children (path, label); + return 1; + } + list = list->next; + } + + return 0; +} + +static char +menu_is_mainmenu_root (char *path, gint16 *offset) +{ + static const char *menus[] = {"\x4$TAB","\x5$TRAY","\x4$URL","\x5$NICK","\x5$CHAN"}; + int i; + + for (i = 0; i < 5; i++) + { + if (!strncmp (path, menus[i] + 1, menus[i][0])) + { + *offset = menus[i][0] + 1; /* number of bytes to offset the root */ + return 0; /* is not main menu */ + } + } + + *offset = 0; + return 1; /* is main menu */ +} + +static void +menu_add (char *path, char *label, char *cmd, char *ucmd, int pos, int state, int markup, int enable, int mod, int key, char *group, char *icon) +{ + menu_entry *me; + + /* already exists? */ + me = menu_entry_find (path, label); + if (me) + { + /* update only */ + me->state = state; + me->enable = enable; + fe_menu_update (me); + return; + } + + me = malloc (sizeof (menu_entry)); + me->pos = pos; + me->modifier = mod; + me->is_main = menu_is_mainmenu_root (path, &me->root_offset); + me->state = state; + me->markup = markup; + me->enable = enable; + me->key = key; + me->path = strdup (path); + me->label = NULL; + me->cmd = NULL; + me->ucmd = NULL; + me->group = NULL; + me->icon = NULL; + + if (label) + me->label = strdup (label); + if (cmd) + me->cmd = strdup (cmd); + if (ucmd) + me->ucmd = strdup (ucmd); + if (group) + me->group = strdup (group); + if (icon) + me->icon = strdup (icon); + + menu_list = g_slist_append (menu_list, me); + label = fe_menu_add (me); + if (label) + { + /* FE has given us a stripped label */ + free (me->label); + me->label = strdup (label); + g_free (label); /* this is from pango */ + } +} + +static int +cmd_menu (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int idx = 2; + int len; + int pos = 0xffff; + int state; + int toggle = FALSE; + int enable = TRUE; + int markup = FALSE; + int key = 0; + int mod = 0; + char *label; + char *group = NULL; + char *icon = NULL; + + if (!word[2][0] || !word[3][0]) + return FALSE; + + /* -eX enabled or not? */ + if (word[idx][0] == '-' && word[idx][1] == 'e') + { + enable = atoi (word[idx] + 2); + idx++; + } + + /* -i<ICONFILE> */ + if (word[idx][0] == '-' && word[idx][1] == 'i') + { + icon = word[idx] + 2; + idx++; + } + + /* -k<mod>,<key> key binding */ + if (word[idx][0] == '-' && word[idx][1] == 'k') + { + char *comma = strchr (word[idx], ','); + if (!comma) + return FALSE; + mod = atoi (word[idx] + 2); + key = atoi (comma + 1); + idx++; + } + + /* -m to specify PangoMarkup language */ + if (word[idx][0] == '-' && word[idx][1] == 'm') + { + markup = TRUE; + idx++; + } + + /* -pX to specify menu position */ + if (word[idx][0] == '-' && word[idx][1] == 'p') + { + pos = atoi (word[idx] + 2); + idx++; + } + + /* -rSTATE,GROUP to specify a radio item */ + if (word[idx][0] == '-' && word[idx][1] == 'r') + { + state = atoi (word[idx] + 2); + group = word[idx] + 4; + idx++; + } + + /* -tX to specify toggle item with default state */ + if (word[idx][0] == '-' && word[idx][1] == 't') + { + state = atoi (word[idx] + 2); + idx++; + toggle = TRUE; + } + + if (word[idx+1][0] == 0) + return FALSE; + + /* the path */ + path_part (word[idx+1], tbuf, 512); + len = strlen (tbuf); + if (len) + tbuf[len - 1] = 0; + + /* the name of the item */ + label = file_part (word[idx + 1]); + if (label[0] == '-' && label[1] == 0) + label = NULL; /* separator */ + + if (markup) + { + char *p; /* to force pango closing tags through */ + for (p = label; *p; p++) + if (*p == 3) + *p = '/'; + } + + if (!strcasecmp (word[idx], "ADD")) + { + if (toggle) + { + menu_add (tbuf, label, word[idx + 2], word[idx + 3], pos, state, markup, enable, mod, key, NULL, NULL); + } else + { + if (word[idx + 2][0]) + menu_add (tbuf, label, word[idx + 2], NULL, pos, state, markup, enable, mod, key, group, icon); + else + menu_add (tbuf, label, NULL, NULL, pos, state, markup, enable, mod, key, group, icon); + } + return TRUE; + } + + if (!strcasecmp (word[idx], "DEL")) + { + menu_del (tbuf, label); + return TRUE; + } + + return FALSE; +} + +static int +mkick_cb (struct User *user, multidata *data) +{ + if (!user->op && !user->me) + data->sess->server->p_kick (data->sess->server, data->sess->channel, user->nick, data->reason); + return TRUE; +} + +static int +mkickops_cb (struct User *user, multidata *data) +{ + if (user->op && !user->me) + data->sess->server->p_kick (data->sess->server, data->sess->channel, user->nick, data->reason); + return TRUE; +} + +static int +cmd_mkick (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + multidata data; + + data.sess = sess; + data.reason = word_eol[2]; + tree_foreach (sess->usertree, (tree_traverse_func *)mkickops_cb, &data); + tree_foreach (sess->usertree, (tree_traverse_func *)mkick_cb, &data); + + return TRUE; +} + +static int +cmd_devoice (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int i = 2; + + while (1) + { + if (!*word[i]) + { + if (i == 2) + return FALSE; + send_channel_modes (sess, tbuf, word, 2, i, '-', 'v', 0); + return TRUE; + } + i++; + } +} + +static int +cmd_discon (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + sess->server->disconnect (sess, TRUE, -1); + return TRUE; +} + +static int +cmd_dns (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ +#ifdef WIN32 + PrintText (sess, "DNS is not implemented in Windows.\n"); + return TRUE; +#else + char *nick = word[2]; + struct User *user; + + if (*nick) + { + if (strchr (nick, '.') == NULL) + { + user = userlist_find (sess, nick); + if (user && user->hostname) + { + do_dns (sess, user->nick, user->hostname); + } else + { + sess->server->p_get_ip (sess->server, nick); + sess->server->doing_dns = TRUE; + } + } else + { + snprintf (tbuf, TBUFSIZE, "exec -d %s %s", prefs.dnsprogram, nick); + handle_command (sess, tbuf, FALSE); + } + return TRUE; + } + return FALSE; +#endif +} + +static int +cmd_echo (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + PrintText (sess, word_eol[2]); + return TRUE; +} + +#ifndef WIN32 + +static void +exec_check_process (struct session *sess) +{ + int val; + + if (sess->running_exec == NULL) + return; + val = waitpid (sess->running_exec->childpid, NULL, WNOHANG); + if (val == -1 || val > 0) + { + close (sess->running_exec->myfd); + fe_input_remove (sess->running_exec->iotag); + free (sess->running_exec); + sess->running_exec = NULL; + } +} + +#ifndef __EMX__ +static int +cmd_execs (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int r; + + exec_check_process (sess); + if (sess->running_exec == NULL) + { + EMIT_SIGNAL (XP_TE_NOCHILD, sess, NULL, NULL, NULL, NULL, 0); + return FALSE; + } + r = kill (sess->running_exec->childpid, SIGSTOP); + if (r == -1) + PrintText (sess, "Error in kill(2)\n"); + + return TRUE; +} + +static int +cmd_execc (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int r; + + exec_check_process (sess); + if (sess->running_exec == NULL) + { + EMIT_SIGNAL (XP_TE_NOCHILD, sess, NULL, NULL, NULL, NULL, 0); + return FALSE; + } + r = kill (sess->running_exec->childpid, SIGCONT); + if (r == -1) + PrintText (sess, "Error in kill(2)\n"); + + return TRUE; +} + +static int +cmd_execk (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int r; + + exec_check_process (sess); + if (sess->running_exec == NULL) + { + EMIT_SIGNAL (XP_TE_NOCHILD, sess, NULL, NULL, NULL, NULL, 0); + return FALSE; + } + if (strcmp (word[2], "-9") == 0) + r = kill (sess->running_exec->childpid, SIGKILL); + else + r = kill (sess->running_exec->childpid, SIGTERM); + if (r == -1) + PrintText (sess, "Error in kill(2)\n"); + + return TRUE; +} + +/* OS/2 Can't have the /EXECW command because it uses pipe(2) not socketpair + and thus it is simplex --AGL */ +static int +cmd_execw (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int len; + char *temp; + exec_check_process (sess); + if (sess->running_exec == NULL) + { + EMIT_SIGNAL (XP_TE_NOCHILD, sess, NULL, NULL, NULL, NULL, 0); + return FALSE; + } + len = strlen(word_eol[2]); + temp = malloc(len + 2); + sprintf(temp, "%s\n", word_eol[2]); + PrintText(sess, temp); + write(sess->running_exec->myfd, temp, len + 1); + free(temp); + + return TRUE; +} +#endif /* !__EMX__ */ + +/* convert ANSI escape color codes to mIRC codes */ + +static short escconv[] = +/* 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 */ +{ 1,4,3,5,2,10,6,1, 1,7,9,8,12,11,13,1 }; + +static void +exec_handle_colors (char *buf, int len) +{ + char numb[16]; + char *nbuf; + int i = 0, j = 0, k = 0, firstn = 0, col, colf = 0, colb = 0; + int esc = FALSE, backc = FALSE, bold = FALSE; + + /* any escape codes in this text? */ + if (strchr (buf, 27) == 0) + return; + + nbuf = malloc (len + 1); + + while (i < len) + { + switch (buf[i]) + { + case '\r': + break; + case 27: + esc = TRUE; + break; + case ';': + if (!esc) + goto norm; + backc = TRUE; + numb[k] = 0; + firstn = atoi (numb); + k = 0; + break; + case '[': + if (!esc) + goto norm; + break; + default: + if (esc) + { + if (buf[i] >= 'A' && buf[i] <= 'z') + { + if (buf[i] == 'm') + { + /* ^[[0m */ + if (k == 0 || (numb[0] == '0' && k == 1)) + { + nbuf[j] = '\017'; + j++; + bold = FALSE; + goto cont; + } + + numb[k] = 0; + col = atoi (numb); + backc = FALSE; + + if (firstn == 1) + bold = TRUE; + + if (firstn >= 30 && firstn <= 37) + colf = firstn - 30; + + if (col >= 40) + { + colb = col - 40; + backc = TRUE; + } + + if (col >= 30 && col <= 37) + colf = col - 30; + + if (bold) + colf += 8; + + if (backc) + { + colb = escconv[colb % 14]; + colf = escconv[colf % 14]; + j += sprintf (&nbuf[j], "\003%d,%02d", colf, colb); + } else + { + colf = escconv[colf % 14]; + j += sprintf (&nbuf[j], "\003%02d", colf); + } + } +cont: esc = FALSE; + backc = FALSE; + k = 0; + } else + { + if (isdigit ((unsigned char) buf[i]) && k < (sizeof (numb) - 1)) + { + numb[k] = buf[i]; + k++; + } + } + } else + { +norm: nbuf[j] = buf[i]; + j++; + } + } + i++; + } + + nbuf[j] = 0; + memcpy (buf, nbuf, j + 1); + free (nbuf); +} + +#ifndef HAVE_MEMRCHR +static void * +memrchr (const void *block, int c, size_t size) +{ + unsigned char *p; + + for (p = (unsigned char *)block + size; p != block; p--) + if (*p == c) + return p; + return 0; +} +#endif + +static gboolean +exec_data (GIOChannel *source, GIOCondition condition, struct nbexec *s) +{ + char *buf, *readpos, *rest; + int rd, len; + int sok = s->myfd; + + len = s->buffill; + if (len) { + /* append new data to buffered incomplete line */ + buf = malloc(len + 2050); + memcpy(buf, s->linebuf, len); + readpos = buf + len; + free(s->linebuf); + s->linebuf = NULL; + } + else + readpos = buf = malloc(2050); + + rd = read (sok, readpos, 2048); + if (rd < 1) + { + /* The process has died */ + kill(s->childpid, SIGKILL); + if (len) { + buf[len] = '\0'; + exec_handle_colors(buf, len); + if (s->tochannel) + { + /* must turn off auto-completion temporarily */ + unsigned int old = prefs.nickcompletion; + prefs.nickcompletion = 0; + handle_multiline (s->sess, buf, FALSE, TRUE); + prefs.nickcompletion = old; + } + else + PrintText (s->sess, buf); + } + free(buf); + waitpid (s->childpid, NULL, 0); + s->sess->running_exec = NULL; + fe_input_remove (s->iotag); + close (sok); + free (s); + return TRUE; + } + len += rd; + buf[len] = '\0'; + + rest = memrchr(buf, '\n', len); + if (rest) + rest++; + else + rest = buf; + if (*rest) { + s->buffill = len - (rest - buf); /* = strlen(rest) */ + s->linebuf = malloc(s->buffill); + memcpy(s->linebuf, rest, s->buffill); + *rest = '\0'; + len -= s->buffill; /* possibly 0 */ + } + else + s->buffill = 0; + + if (len) { + exec_handle_colors (buf, len); + if (s->tochannel) + handle_multiline (s->sess, buf, FALSE, TRUE); + else + PrintText (s->sess, buf); + } + + free(buf); + return TRUE; +} + +static int +cmd_exec (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int tochannel = FALSE; + char *cmd = word_eol[2]; + int fds[2], pid = 0; + struct nbexec *s; + int shell = TRUE; + int fd; + + if (*cmd) + { + exec_check_process (sess); + if (sess->running_exec != NULL) + { + EMIT_SIGNAL (XP_TE_ALREADYPROCESS, sess, NULL, NULL, NULL, NULL, 0); + return TRUE; + } + + if (!strcmp (word[2], "-d")) + { + if (!*word[3]) + return FALSE; + cmd = word_eol[3]; + shell = FALSE; + } + else if (!strcmp (word[2], "-o")) + { + if (!*word[3]) + return FALSE; + cmd = word_eol[3]; + tochannel = TRUE; + } + + if (shell) + { + if (access ("/bin/sh", X_OK) != 0) + { + fe_message (_("I need /bin/sh to run!\n"), FE_MSG_ERROR); + return TRUE; + } + } + +#ifdef __EMX__ /* if os/2 */ + if (pipe (fds) < 0) + { + PrintText (sess, "Pipe create error\n"); + return FALSE; + } + setmode (fds[0], O_BINARY); + setmode (fds[1], O_BINARY); +#else + if (socketpair (PF_UNIX, SOCK_STREAM, 0, fds) == -1) + { + PrintText (sess, "socketpair(2) failed\n"); + return FALSE; + } +#endif + s = (struct nbexec *) malloc (sizeof (struct nbexec)); + memset(s, 0, sizeof(*s)); + s->myfd = fds[0]; + s->tochannel = tochannel; + s->sess = sess; + + pid = fork (); + if (pid == 0) + { + /* This is the child's context */ + close (0); + close (1); + close (2); + /* Close parent's end of pipe */ + close(s->myfd); + /* Copy the child end of the pipe to stdout and stderr */ + dup2 (fds[1], 1); + dup2 (fds[1], 2); + /* Also copy it to stdin so we can write to it */ + dup2 (fds[1], 0); + /* Now close all open file descriptors except stdin, stdout and stderr */ + for (fd = 3; fd < 1024; fd++) close(fd); + /* Now we call /bin/sh to run our cmd ; made it more friendly -DC1 */ + if (shell) + { + execl ("/bin/sh", "sh", "-c", cmd, NULL); + } else + { + char **argv; + int argc; + + my_poptParseArgvString (cmd, &argc, &argv); + execvp (argv[0], argv); + } + /* not reached unless error */ + /*printf("exec error\n");*/ + fflush (stdout); + fflush (stdin); + _exit (0); + } + if (pid == -1) + { + /* Parent context, fork() failed */ + + PrintText (sess, "Error in fork(2)\n"); + close(fds[0]); + close(fds[1]); + } else + { + /* Parent path */ + close(fds[1]); + s->childpid = pid; + s->iotag = fe_input_add (s->myfd, FIA_READ|FIA_EX, exec_data, s); + sess->running_exec = s; + return TRUE; + } + } + return FALSE; +} + +#endif + +static int +cmd_flushq (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + sprintf (tbuf, "Flushing server send queue, %d bytes.\n", sess->server->sendq_len); + PrintText (sess, tbuf); + sess->server->flush_queue (sess->server); + return TRUE; +} + +static int +cmd_quit (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (*word_eol[2]) + sess->quitreason = word_eol[2]; + sess->server->disconnect (sess, TRUE, -1); + sess->quitreason = NULL; + return 2; +} + +static int +cmd_gate (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *server_name = word[2]; + server *serv = sess->server; + if (*server_name) + { + char *port = word[3]; +#ifdef USE_OPENSSL + serv->use_ssl = FALSE; +#endif + server_fill_her_up (serv); + if (*port) + serv->connect (serv, server_name, atoi (port), TRUE); + else + serv->connect (serv, server_name, 23, TRUE); + return TRUE; + } + return FALSE; +} + +typedef struct +{ + char *cmd; + session *sess; +} getvalinfo; + +static void +get_int_cb (int cancel, int val, getvalinfo *info) +{ + char buf[512]; + + if (!cancel) + { + snprintf (buf, sizeof (buf), "%s %d", info->cmd, val); + if (is_session (info->sess)) + handle_command (info->sess, buf, FALSE); + } + + free (info->cmd); + free (info); +} + +static int +cmd_getint (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + getvalinfo *info; + + if (!word[4][0]) + return FALSE; + + info = malloc (sizeof (*info)); + info->cmd = strdup (word[3]); + info->sess = sess; + + fe_get_int (word[4], atoi (word[2]), get_int_cb, info); + + return TRUE; +} + +static void +get_file_cb (char *cmd, char *file) +{ + char buf[1024 + 128]; + + /* execute the command once per file, then once more with + no args */ + if (file) + { + snprintf (buf, sizeof (buf), "%s %s", cmd, file); + handle_command (current_sess, buf, FALSE); + } + else + { + handle_command (current_sess, cmd, FALSE); + free (cmd); + } +} + +static int +cmd_getfile (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int idx = 2; + int flags = 0; + + if (!word[3][0]) + return FALSE; + + if (!strcmp (word[2], "-folder")) + { + flags |= FRF_CHOOSEFOLDER; + idx++; + } + + if (!strcmp (word[idx], "-multi")) + { + flags |= FRF_MULTIPLE; + idx++; + } + + if (!strcmp (word[idx], "-save")) + { + flags |= FRF_WRITE; + idx++; + } + + fe_get_file (word[idx+1], word[idx+2], (void *)get_file_cb, strdup (word[idx]), flags); + + return TRUE; +} + +static void +get_str_cb (int cancel, char *val, getvalinfo *info) +{ + char buf[512]; + + if (!cancel) + { + snprintf (buf, sizeof (buf), "%s %s", info->cmd, val); + if (is_session (info->sess)) + handle_command (info->sess, buf, FALSE); + } + + free (info->cmd); + free (info); +} + +static int +cmd_getstr (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + getvalinfo *info; + + if (!word[4][0]) + return FALSE; + + info = malloc (sizeof (*info)); + info->cmd = strdup (word[3]); + info->sess = sess; + + fe_get_str (word[4], word[2], get_str_cb, info); + + return TRUE; +} + +static int +cmd_ghost (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (!word[2][0]) + return FALSE; + + sess->server->p_ns_ghost (sess->server, word[2], word[3]); + return TRUE; +} + +static int +cmd_gui (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + switch (str_ihash (word[2])) + { + case 0x058b836e: fe_ctrl_gui (sess, 8, 0); break; /* APPLY */ + case 0xac1eee45: fe_ctrl_gui (sess, 7, 2); break; /* ATTACH */ + case 0x05a72f63: fe_ctrl_gui (sess, 4, atoi (word[3])); break; /* COLOR */ + case 0xb06a1793: fe_ctrl_gui (sess, 7, 1); break; /* DETACH */ + case 0x05cfeff0: fe_ctrl_gui (sess, 3, 0); break; /* FLASH */ + case 0x05d154d8: fe_ctrl_gui (sess, 2, 0); break; /* FOCUS */ + case 0x0030dd42: fe_ctrl_gui (sess, 0, 0); break; /* HIDE */ + case 0x61addbe3: fe_ctrl_gui (sess, 5, 0); break; /* ICONIFY */ + case 0xc0851aaa: fe_message (word[3], FE_MSG_INFO|FE_MSG_MARKUP); break; /* MSGBOX */ + case 0x0035dafd: fe_ctrl_gui (sess, 1, 0); break; /* SHOW */ + case 0x0033155f: /* MENU */ + if (!strcasecmp (word[3], "TOGGLE")) + fe_ctrl_gui (sess, 6, 0); + else + return FALSE; + break; + default: + return FALSE; + } + + return TRUE; +} + +typedef struct +{ + int longfmt; + int i, t; + char *buf; +} help_list; + +static void +show_help_line (session *sess, help_list *hl, char *name, char *usage) +{ + int j, len, max; + char *p; + + if (name[0] == '.') /* hidden command? */ + return; + + if (hl->longfmt) /* long format for /HELP -l */ + { + if (!usage || usage[0] == 0) + PrintTextf (sess, " \0034%s\003 :\n", name); + else + PrintTextf (sess, " \0034%s\003 : %s\n", name, _(usage)); + return; + } + + /* append the name into buffer, but convert to uppercase */ + len = strlen (hl->buf); + p = name; + while (*p) + { + hl->buf[len] = toupper ((unsigned char) *p); + len++; + p++; + } + hl->buf[len] = 0; + + hl->t++; + if (hl->t == 5) + { + hl->t = 0; + strcat (hl->buf, "\n"); + PrintText (sess, hl->buf); + hl->buf[0] = ' '; + hl->buf[1] = ' '; + hl->buf[2] = 0; + } else + { + /* append some spaces after the command name */ + max = strlen (name); + if (max < 10) + { + max = 10 - max; + for (j = 0; j < max; j++) + { + hl->buf[len] = ' '; + len++; + hl->buf[len] = 0; + } + } + } +} + +static int +cmd_help (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int i = 0, longfmt = 0; + char *helpcmd = ""; + GSList *list; + + if (tbuf) + helpcmd = word[2]; + if (*helpcmd && strcmp (helpcmd, "-l") == 0) + longfmt = 1; + + if (*helpcmd && !longfmt) + { + help (sess, tbuf, helpcmd, FALSE); + } else + { + struct popup *pop; + char *buf = malloc (4096); + help_list hl; + + hl.longfmt = longfmt; + hl.buf = buf; + + PrintTextf (sess, "\n%s\n\n", _("Commands Available:")); + buf[0] = ' '; + buf[1] = ' '; + buf[2] = 0; + hl.t = 0; + hl.i = 0; + while (xc_cmds[i].name) + { + show_help_line (sess, &hl, xc_cmds[i].name, xc_cmds[i].help); + i++; + } + strcat (buf, "\n"); + PrintText (sess, buf); + + PrintTextf (sess, "\n%s\n\n", _("User defined commands:")); + buf[0] = ' '; + buf[1] = ' '; + buf[2] = 0; + hl.t = 0; + hl.i = 0; + list = command_list; + while (list) + { + pop = list->data; + show_help_line (sess, &hl, pop->name, pop->cmd); + list = list->next; + } + strcat (buf, "\n"); + PrintText (sess, buf); + + PrintTextf (sess, "\n%s\n\n", _("Plugin defined commands:")); + buf[0] = ' '; + buf[1] = ' '; + buf[2] = 0; + hl.t = 0; + hl.i = 0; + plugin_command_foreach (sess, &hl, (void *)show_help_line); + strcat (buf, "\n"); + PrintText (sess, buf); + free (buf); + + PrintTextf (sess, "\n%s\n\n", _("Type /HELP <command> for more information, or /HELP -l")); + } + return TRUE; +} + +static int +cmd_id (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (word[2][0]) + { + sess->server->p_ns_identify (sess->server, word[2]); + return TRUE; + } + + return FALSE; +} + +static int +cmd_ignore (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int i; + int type = 0; + int quiet = 0; + char *mask; + + if (!*word[2]) + { + ignore_showlist (sess); + return TRUE; + } + if (!*word[3]) + return FALSE; + + i = 3; + while (1) + { + if (!*word[i]) + { + if (type == 0) + return FALSE; + + mask = word[2]; + if (strchr (mask, '?') == NULL && + strchr (mask, '*') == NULL && + userlist_find (sess, mask)) + { + mask = tbuf; + snprintf (tbuf, TBUFSIZE, "%s!*@*", word[2]); + } + + i = ignore_add (mask, type); + if (quiet) + return TRUE; + switch (i) + { + case 1: + EMIT_SIGNAL (XP_TE_IGNOREADD, sess, mask, NULL, NULL, NULL, 0); + break; + case 2: /* old ignore changed */ + EMIT_SIGNAL (XP_TE_IGNORECHANGE, sess, mask, NULL, NULL, NULL, 0); + } + return TRUE; + } + if (!strcasecmp (word[i], "UNIGNORE")) + type |= IG_UNIG; + else if (!strcasecmp (word[i], "ALL")) + type |= IG_PRIV | IG_NOTI | IG_CHAN | IG_CTCP | IG_INVI | IG_DCC; + else if (!strcasecmp (word[i], "PRIV")) + type |= IG_PRIV; + else if (!strcasecmp (word[i], "NOTI")) + type |= IG_NOTI; + else if (!strcasecmp (word[i], "CHAN")) + type |= IG_CHAN; + else if (!strcasecmp (word[i], "CTCP")) + type |= IG_CTCP; + else if (!strcasecmp (word[i], "INVI")) + type |= IG_INVI; + else if (!strcasecmp (word[i], "QUIET")) + quiet = 1; + else if (!strcasecmp (word[i], "NOSAVE")) + type |= IG_NOSAVE; + else if (!strcasecmp (word[i], "DCC")) + type |= IG_DCC; + else + { + sprintf (tbuf, _("Unknown arg '%s' ignored."), word[i]); + PrintText (sess, tbuf); + } + i++; + } +} + +static int +cmd_invite (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (!*word[2]) + return FALSE; + if (*word[3]) + sess->server->p_invite (sess->server, word[3], word[2]); + else + sess->server->p_invite (sess->server, sess->channel, word[2]); + return TRUE; +} + +static int +cmd_join (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *chan = word[2]; + if (*chan) + { + char *po, *pass = word[3]; + sess->server->p_join (sess->server, chan, pass); + if (sess->channel[0] == 0 && sess->waitchannel[0]) + { + po = strchr (chan, ','); + if (po) + *po = 0; + safe_strcpy (sess->waitchannel, chan, CHANLEN); + } + return TRUE; + } + return FALSE; +} + +static int +cmd_kick (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *nick = word[2]; + char *reason = word_eol[3]; + if (*nick) + { + sess->server->p_kick (sess->server, sess->channel, nick, reason); + return TRUE; + } + return FALSE; +} + +static int +cmd_kickban (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *nick = word[2]; + char *reason = word_eol[3]; + struct User *user; + + if (*nick) + { + /* if the reason is a 1 digit number, treat it as a bantype */ + + user = userlist_find (sess, nick); + + if (isdigit ((unsigned char) reason[0]) && reason[1] == 0) + { + ban (sess, tbuf, nick, reason, (user && user->op)); + reason[0] = 0; + } else + ban (sess, tbuf, nick, "", (user && user->op)); + + sess->server->p_kick (sess->server, sess->channel, nick, reason); + + return TRUE; + } + return FALSE; +} + +static int +cmd_killall (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + xchat_exit(); + return 2; +} + +static int +cmd_lagcheck (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + lag_check (); + return TRUE; +} + +static void +lastlog (session *sess, char *search, gboolean regexp) +{ + session *lastlog_sess; + + if (!is_session (sess)) + return; + + lastlog_sess = find_dialog (sess->server, "(lastlog)"); + if (!lastlog_sess) + lastlog_sess = new_ircwindow (sess->server, "(lastlog)", SESS_DIALOG, 0); + + lastlog_sess->lastlog_sess = sess; + lastlog_sess->lastlog_regexp = regexp; /* remember the search type */ + + fe_text_clear (lastlog_sess, 0); + fe_lastlog (sess, lastlog_sess, search, regexp); +} + +static int +cmd_lastlog (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (*word_eol[2]) + { + if (!strcmp (word[2], "-r")) + lastlog (sess, word_eol[3], TRUE); + else + lastlog (sess, word_eol[2], FALSE); + return TRUE; + } + + return FALSE; +} + +static int +cmd_list (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + sess->server->p_list_channels (sess->server, word_eol[2], 1); + + return TRUE; +} + +gboolean +load_perform_file (session *sess, char *file) +{ + char tbuf[1024 + 4]; + char *nl; + FILE *fp; + + fp = xchat_fopen_file (file, "r", XOF_FULLPATH); + if (!fp) + return FALSE; + + tbuf[1024] = 0; + while (fgets (tbuf, 1024, fp)) + { + nl = strchr (tbuf, '\n'); + if (nl == tbuf) /* skip empty commands */ + continue; + if (nl) + *nl = 0; + if (tbuf[0] == prefs.cmdchar[0]) + handle_command (sess, tbuf + 1, TRUE); + else + handle_command (sess, tbuf, TRUE); + } + fclose (fp); + return TRUE; +} + +static int +cmd_load (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *error, *arg, *file; + int len; + + if (!word[2][0]) + return FALSE; + + if (strcmp (word[2], "-e") == 0) + { + file = expand_homedir (word[3]); + if (!load_perform_file (sess, file)) + { + PrintTextf (sess, _("Cannot access %s\n"), file); + PrintText (sess, errorstring (errno)); + } + free (file); + return TRUE; + } + +#ifdef USE_PLUGIN + len = strlen (word[2]); +#ifdef WIN32 + if (len > 4 && strcasecmp (".dll", word[2] + len - 4) == 0) +#else +#if defined(__hpux) + if (len > 3 && strcasecmp (".sl", word[2] + len - 3) == 0) +#else + if (len > 3 && strcasecmp (".so", word[2] + len - 3) == 0) +#endif +#endif + { + arg = NULL; + if (word_eol[3][0]) + arg = word_eol[3]; + + file = expand_homedir (word[2]); + error = plugin_load (sess, file, arg); + free (file); + + if (error) + PrintText (sess, error); + + return TRUE; + } +#endif + + sprintf (tbuf, "Unknown file type %s. Maybe you need to install the Perl or Python plugin?\n", word[2]); + PrintText (sess, tbuf); + + return FALSE; +} + +static int +cmd_me (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *act = word_eol[2]; + + if (!(*act)) + return FALSE; + + if (sess->type == SESS_SERVER) + { + notj_msg (sess); + return TRUE; + } + + snprintf (tbuf, TBUFSIZE, "\001ACTION %s\001\r", act); + /* first try through DCC CHAT */ + if (dcc_write_chat (sess->channel, tbuf)) + { + /* print it to screen */ + inbound_action (sess, sess->channel, sess->server->nick, "", act, TRUE, FALSE); + } else + { + /* DCC CHAT failed, try through server */ + if (sess->server->connected) + { + sess->server->p_action (sess->server, sess->channel, act); + /* print it to screen */ + inbound_action (sess, sess->channel, sess->server->nick, "", act, TRUE, FALSE); + } else + { + notc_msg (sess); + } + } + + return TRUE; +} + +static int +cmd_mode (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + /* +channel channels are dying, let those servers whine about modes. + * return info about current channel if available and no info is given */ + if ((*word[2] == '+') || (*word[2] == 0) || (!is_channel(sess->server, word[2]) && + !(rfc_casecmp(sess->server->nick, word[2]) == 0))) + { + if(sess->channel[0] == 0) + return FALSE; + sess->server->p_mode (sess->server, sess->channel, word_eol[2]); + } + else + sess->server->p_mode (sess->server, word[2], word_eol[3]); + return TRUE; +} + +static int +mop_cb (struct User *user, multidata *data) +{ + if (!user->op) + { + data->nicks[data->i] = user->nick; + data->i++; + } + return TRUE; +} + +static int +cmd_mop (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char **nicks = malloc (sizeof (char *) * (sess->total - sess->ops)); + multidata data; + + data.nicks = nicks; + data.i = 0; + tree_foreach (sess->usertree, (tree_traverse_func *)mop_cb, &data); + send_channel_modes (sess, tbuf, nicks, 0, data.i, '+', 'o', 0); + + free (nicks); + + return TRUE; +} + +static int +cmd_msg (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *nick = word[2]; + char *msg = word_eol[3]; + struct session *newsess; + + if (*nick) + { + if (*msg) + { + if (strcmp (nick, ".") == 0) + { /* /msg the last nick /msg'ed */ + if (sess->lastnick[0]) + nick = sess->lastnick; + } else + { + safe_strcpy (sess->lastnick, nick, NICKLEN); /* prime the last nick memory */ + } + + if (*nick == '=') + { + nick++; + if (!dcc_write_chat (nick, msg)) + { + EMIT_SIGNAL (XP_TE_NODCC, sess, NULL, NULL, NULL, NULL, 0); + return TRUE; + } + } else + { + if (!sess->server->connected) + { + notc_msg (sess); + return TRUE; + } + sess->server->p_message (sess->server, nick, msg); + } + newsess = find_dialog (sess->server, nick); + if (!newsess) + newsess = find_channel (sess->server, nick); + if (newsess) + inbound_chanmsg (newsess->server, NULL, newsess->channel, + newsess->server->nick, msg, TRUE, FALSE); + else + { + /* mask out passwords */ + if (strcasecmp (nick, "nickserv") == 0 && + strncasecmp (msg, "identify ", 9) == 0) + msg = "identify ****"; + EMIT_SIGNAL (XP_TE_MSGSEND, sess, nick, msg, NULL, NULL, 0); + } + + return TRUE; + } + } + return FALSE; +} + +static int +cmd_names (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (*word[2]) + sess->server->p_names (sess->server, word[2]); + else + sess->server->p_names (sess->server, sess->channel); + return TRUE; +} + +static int +cmd_nctcp (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (*word_eol[3]) + { + sess->server->p_nctcp (sess->server, word[2], word_eol[3]); + return TRUE; + } + return FALSE; +} + +static int +cmd_newserver (struct session *sess, char *tbuf, char *word[], + char *word_eol[]) +{ + if (strcmp (word[2], "-noconnect") == 0) + { + new_ircwindow (NULL, word[3], SESS_SERVER, 0); + return TRUE; + } + + sess = new_ircwindow (NULL, NULL, SESS_SERVER, 0); + cmd_server (sess, tbuf, word, word_eol); + return TRUE; +} + +static int +cmd_nick (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *nick = word[2]; + if (*nick) + { + if (sess->server->connected) + sess->server->p_change_nick (sess->server, nick); + else + inbound_newnick (sess->server, sess->server->nick, nick, TRUE); + return TRUE; + } + return FALSE; +} + +static int +cmd_notice (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (*word[2] && *word_eol[3]) + { + sess->server->p_notice (sess->server, word[2], word_eol[3]); + EMIT_SIGNAL (XP_TE_NOTICESEND, sess, word[2], word_eol[3], NULL, NULL, 0); + return TRUE; + } + return FALSE; +} + +static int +cmd_notify (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int i = 1; + char *net = NULL; + + if (*word[2]) + { + if (strcmp (word[2], "-n") == 0) /* comma sep network list */ + { + net = word[3]; + i += 2; + } + + while (1) + { + i++; + if (!*word[i]) + break; + if (notify_deluser (word[i])) + { + EMIT_SIGNAL (XP_TE_DELNOTIFY, sess, word[i], NULL, NULL, NULL, 0); + return TRUE; + } + + if (net && strcmp (net, "ASK") == 0) + fe_notify_ask (word[i], NULL); + else + { + notify_adduser (word[i], net); + EMIT_SIGNAL (XP_TE_ADDNOTIFY, sess, word[i], NULL, NULL, NULL, 0); + } + } + } else + notify_showlist (sess); + return TRUE; +} + +static int +cmd_op (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int i = 2; + + while (1) + { + if (!*word[i]) + { + if (i == 2) + return FALSE; + send_channel_modes (sess, tbuf, word, 2, i, '+', 'o', 0); + return TRUE; + } + i++; + } +} + +static int +cmd_part (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *chan = word[2]; + char *reason = word_eol[3]; + if (!*chan) + chan = sess->channel; + if ((*chan) && is_channel (sess->server, chan)) + { + if (reason[0] == 0) + reason = NULL; + server_sendpart (sess->server, chan, reason); + return TRUE; + } + return FALSE; +} + +static int +cmd_ping (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char timestring[64]; + unsigned long tim; + char *to = word[2]; + + tim = make_ping_time (); + + snprintf (timestring, sizeof (timestring), "%lu", tim); + sess->server->p_ping (sess->server, to, timestring); + + return TRUE; +} + +void +open_query (server *serv, char *nick, gboolean focus_existing) +{ + session *sess; + + sess = find_dialog (serv, nick); + if (!sess) + new_ircwindow (serv, nick, SESS_DIALOG, 1); + else if (focus_existing) + fe_ctrl_gui (sess, 2, 0); /* bring-to-front */ +} + +static int +cmd_query (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *nick = word[2]; + gboolean focus = TRUE; + + if (strcmp (word[2], "-nofocus") == 0) + { + nick = word[3]; + focus = FALSE; + } + + if (*nick && !is_channel (sess->server, nick)) + { + open_query (sess->server, nick, focus); + return TRUE; + } + return FALSE; +} + +static int +cmd_quote (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *raw = word_eol[2]; + + return sess->server->p_raw (sess->server, raw); +} + +static int +cmd_reconnect (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int tmp = prefs.recon_delay; + GSList *list; + server *serv = sess->server; + + prefs.recon_delay = 0; + + if (!strcasecmp (word[2], "ALL")) + { + list = serv_list; + while (list) + { + serv = list->data; + if (serv->connected) + serv->auto_reconnect (serv, TRUE, -1); + list = list->next; + } + } + /* If it isn't "ALL" and there is something + there it *should* be a server they are trying to connect to*/ + else if (*word[2]) + { + int offset = 0; +#ifdef USE_OPENSSL + int use_ssl = FALSE; + + if (strcmp (word[2], "-ssl") == 0) + { + use_ssl = TRUE; + offset++; /* args move up by 1 word */ + } + serv->use_ssl = use_ssl; + serv->accept_invalid_cert = TRUE; +#endif + + if (*word[4+offset]) + safe_strcpy (serv->password, word[4+offset], sizeof (serv->password)); + if (*word[3+offset]) + serv->port = atoi (word[3+offset]); + safe_strcpy (serv->hostname, word[2+offset], sizeof (serv->hostname)); + serv->auto_reconnect (serv, TRUE, -1); + } + else + { + serv->auto_reconnect (serv, TRUE, -1); + } + prefs.recon_delay = tmp; + + return TRUE; +} + +static int +cmd_recv (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (*word_eol[2]) + { + sess->server->p_inline (sess->server, word_eol[2], strlen (word_eol[2])); + return TRUE; + } + + return FALSE; +} + +static int +cmd_say (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + char *speech = word_eol[2]; + if (*speech) + { + handle_say (sess, speech, FALSE); + return TRUE; + } + return FALSE; +} + +static int +cmd_send (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + guint32 addr; + socklen_t len; + struct sockaddr_in SAddr; + + if (!word[2][0]) + return FALSE; + + addr = dcc_get_my_address (); + if (addr == 0) + { + /* use the one from our connected server socket */ + memset (&SAddr, 0, sizeof (struct sockaddr_in)); + len = sizeof (SAddr); + getsockname (sess->server->sok, (struct sockaddr *) &SAddr, &len); + addr = SAddr.sin_addr.s_addr; + } + addr = ntohl (addr); + + if ((addr & 0xffff0000) == 0xc0a80000 || /* 192.168.x.x */ + (addr & 0xff000000) == 0x0a000000) /* 10.x.x.x */ + /* we got a private net address, let's PSEND or it'll fail */ + snprintf (tbuf, 512, "DCC PSEND %s", word_eol[2]); + else + snprintf (tbuf, 512, "DCC SEND %s", word_eol[2]); + + handle_command (sess, tbuf, FALSE); + + return TRUE; +} + +static int +cmd_setcursor (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int delta = FALSE; + + if (*word[2]) + { + if (word[2][0] == '-' || word[2][0] == '+') + delta = TRUE; + fe_set_inputbox_cursor (sess, delta, atoi (word[2])); + return TRUE; + } + + return FALSE; +} + +static int +cmd_settab (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (*word_eol[2]) + { + strcpy (tbuf, sess->channel); + safe_strcpy (sess->channel, word_eol[2], CHANLEN); + fe_set_channel (sess); + strcpy (sess->channel, tbuf); + } + + return TRUE; +} + +static int +cmd_settext (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + fe_set_inputbox_contents (sess, word_eol[2]); + return TRUE; +} + +static int +cmd_splay (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (*word[2]) + { + sound_play (word[2], FALSE); + return TRUE; + } + + return FALSE; +} + +static int +parse_irc_url (char *url, char *server_name[], char *port[], char *channel[], int *use_ssl) +{ + char *co; +#ifdef USE_OPENSSL + if (strncasecmp ("ircs://", url, 7) == 0) + { + *use_ssl = TRUE; + *server_name = url + 7; + goto urlserv; + } +#endif + + if (strncasecmp ("irc://", url, 6) == 0) + { + *server_name = url + 6; +#ifdef USE_OPENSSL +urlserv: +#endif + /* check for port */ + co = strchr (*server_name, ':'); + if (co) + { + *port = co + 1; + *co = 0; + } else + co = *server_name; + /* check for channel - mirc style */ + co = strchr (co + 1, '/'); + if (co) + { + *co = 0; + co++; + if (*co == '#') + *channel = co+1; + else + *channel = co; + + } + return TRUE; + } + return FALSE; +} + +static int +cmd_server (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int offset = 0; + char *server_name = NULL; + char *port = NULL; + char *pass = NULL; + char *channel = NULL; + int use_ssl = FALSE; + int is_url = TRUE; + server *serv = sess->server; + +#ifdef USE_OPENSSL + /* BitchX uses -ssl, mIRC uses -e, let's support both */ + if (strcmp (word[2], "-ssl") == 0 || strcmp (word[2], "-e") == 0) + { + use_ssl = TRUE; + offset++; /* args move up by 1 word */ + } +#endif + + if (!parse_irc_url (word[2 + offset], &server_name, &port, &channel, &use_ssl)) + { + is_url = FALSE; + server_name = word[2 + offset]; + } + if (port) + pass = word[3 + offset]; + else + { + port = word[3 + offset]; + pass = word[4 + offset]; + } + + if (!(*server_name)) + return FALSE; + + sess->server->network = NULL; + + /* dont clear it for /servchan */ + if (strncasecmp (word_eol[1], "SERVCHAN ", 9)) + sess->willjoinchannel[0] = 0; + + if (channel) + { + sess->willjoinchannel[0] = '#'; + safe_strcpy ((sess->willjoinchannel + 1), channel, (CHANLEN - 1)); + } + + /* support +7000 style ports like mIRC */ + if (port[0] == '+') + { + port++; +#ifdef USE_OPENSSL + use_ssl = TRUE; +#endif + } + + if (*pass) + { + safe_strcpy (serv->password, pass, sizeof (serv->password)); + } +#ifdef USE_OPENSSL + serv->use_ssl = use_ssl; + serv->accept_invalid_cert = TRUE; +#endif + + /* try to connect by Network name */ + if (servlist_connect_by_netname (sess, server_name, !is_url)) + return TRUE; + + if (*port) + { + serv->connect (serv, server_name, atoi (port), FALSE); + } else + { + /* -1 for default port */ + serv->connect (serv, server_name, -1, FALSE); + } + + /* try to associate this connection with a listed network */ + if (!serv->network) + /* search for this hostname in the entire server list */ + serv->network = servlist_net_find_from_server (server_name); + /* may return NULL, but that's OK */ + + return TRUE; +} + +static int +cmd_servchan (struct session *sess, char *tbuf, char *word[], + char *word_eol[]) +{ + int offset = 0; + +#ifdef USE_OPENSSL + if (strcmp (word[2], "-ssl") == 0) + offset++; +#endif + + if (*word[4 + offset]) + { + safe_strcpy (sess->willjoinchannel, word[4 + offset], CHANLEN); + return cmd_server (sess, tbuf, word, word_eol); + } + + return FALSE; +} + +static int +cmd_topic (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (word[2][0] && is_channel (sess->server, word[2])) + sess->server->p_topic (sess->server, word[2], word_eol[3]); + else + sess->server->p_topic (sess->server, sess->channel, word_eol[2]); + return TRUE; +} + +static int +cmd_tray (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (strcmp (word[2], "-b") == 0) + { + fe_tray_set_balloon (word[3], word[4][0] ? word[4] : NULL); + return TRUE; + } + + if (strcmp (word[2], "-t") == 0) + { + fe_tray_set_tooltip (word[3][0] ? word[3] : NULL); + return TRUE; + } + + if (strcmp (word[2], "-i") == 0) + { + fe_tray_set_icon (atoi (word[3])); + return TRUE; + } + + if (strcmp (word[2], "-f") != 0) + return FALSE; + + if (!word[3][0]) + { + fe_tray_set_file (NULL); /* default xchat icon */ + return TRUE; + } + + if (!word[4][0]) + { + fe_tray_set_file (word[3]); /* fixed custom icon */ + return TRUE; + } + + /* flash between 2 icons */ + fe_tray_set_flash (word[4], word[5][0] ? word[5] : NULL, atoi (word[3])); + return TRUE; +} + +static int +cmd_unignore (struct session *sess, char *tbuf, char *word[], + char *word_eol[]) +{ + char *mask = word[2]; + char *arg = word[3]; + if (*mask) + { + if (ignore_del (mask, NULL)) + { + if (strcasecmp (arg, "QUIET")) + EMIT_SIGNAL (XP_TE_IGNOREREMOVE, sess, mask, NULL, NULL, NULL, 0); + } + return TRUE; + } + return FALSE; +} + +static int +cmd_unload (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ +#ifdef USE_PLUGIN + int len, by_file = FALSE; + + len = strlen (word[2]); +#ifdef WIN32 + if (len > 4 && strcasecmp (word[2] + len - 4, ".dll") == 0) +#else +#if defined(__hpux) + if (len > 3 && strcasecmp (word[2] + len - 3, ".sl") == 0) +#else + if (len > 3 && strcasecmp (word[2] + len - 3, ".so") == 0) +#endif +#endif + by_file = TRUE; + + switch (plugin_kill (word[2], by_file)) + { + case 0: + PrintText (sess, _("No such plugin found.\n")); + break; + case 1: + return TRUE; + case 2: + PrintText (sess, _("That plugin is refusing to unload.\n")); + break; + } +#endif + + return FALSE; +} + +static server * +find_server_from_hostname (char *hostname) +{ + GSList *list = serv_list; + server *serv; + + while (list) + { + serv = list->data; + if (!strcasecmp (hostname, serv->hostname) && serv->connected) + return serv; + list = list->next; + } + + return NULL; +} + +static server * +find_server_from_net (void *net) +{ + GSList *list = serv_list; + server *serv; + + while (list) + { + serv = list->data; + if (serv->network == net && serv->connected) + return serv; + list = list->next; + } + + return NULL; +} + +static void +url_join_only (server *serv, char *tbuf, char *channel) +{ + /* already connected, JOIN only. FIXME: support keys? */ + tbuf[0] = '#'; + /* tbuf is 4kb */ + safe_strcpy ((tbuf + 1), channel, 256); + serv->p_join (serv, tbuf, ""); +} + +static int +cmd_url (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + if (word[2][0]) + { + char *server_name = NULL; + char *port = NULL; + char *channel = NULL; + char *url = g_strdup (word[2]); + int use_ssl = FALSE; + void *net; + server *serv; + + if (parse_irc_url (url, &server_name, &port, &channel, &use_ssl)) + { + /* maybe we're already connected to this net */ + + /* check for "FreeNode" */ + net = servlist_net_find (server_name, NULL, strcasecmp); + /* check for "irc.eu.freenode.net" */ + if (!net) + net = servlist_net_find_from_server (server_name); + + if (net) + { + /* found the network, but are we connected? */ + serv = find_server_from_net (net); + if (serv) + { + url_join_only (serv, tbuf, channel); + g_free (url); + return TRUE; + } + } + else + { + /* an un-listed connection */ + serv = find_server_from_hostname (server_name); + if (serv) + { + url_join_only (serv, tbuf, channel); + g_free (url); + return TRUE; + } + } + + /* not connected to this net, open new window */ + cmd_newserver (sess, tbuf, word, word_eol); + + } else + fe_open_url (word[2]); + g_free (url); + return TRUE; + } + + return FALSE; +} + +static int +userlist_cb (struct User *user, session *sess) +{ + time_t lt; + + if (!user->lasttalk) + lt = 0; + else + lt = time (0) - user->lasttalk; + PrintTextf (sess, + "\00306%s\t\00314[\00310%-38s\00314] \017ov\0033=\017%d%d away=%u lt\0033=\017%d\n", + user->nick, user->hostname, user->op, user->voice, user->away, lt); + + return TRUE; +} + +static int +cmd_uselect (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int idx = 2; + int clear = TRUE; + int scroll = FALSE; + + if (strcmp (word[2], "-a") == 0) /* ADD (don't clear selections) */ + { + clear = FALSE; + idx++; + } + if (strcmp (word[idx], "-s") == 0) /* SCROLL TO */ + { + scroll = TRUE; + idx++; + } + /* always valid, no args means clear the selection list */ + fe_uselect (sess, word + idx, clear, scroll); + return TRUE; +} + +static int +cmd_userlist (struct session *sess, char *tbuf, char *word[], + char *word_eol[]) +{ + tree_foreach (sess->usertree, (tree_traverse_func *)userlist_cb, sess); + return TRUE; +} + +static int +wallchop_cb (struct User *user, multidata *data) +{ + if (user->op) + { + if (data->i) + strcat (data->tbuf, ","); + strcat (data->tbuf, user->nick); + data->i++; + } + if (data->i == 5) + { + data->i = 0; + sprintf (data->tbuf + strlen (data->tbuf), + " :[@%s] %s", data->sess->channel, data->reason); + data->sess->server->p_raw (data->sess->server, data->tbuf); + strcpy (data->tbuf, "NOTICE "); + } + + return TRUE; +} + +static int +cmd_wallchop (struct session *sess, char *tbuf, char *word[], + char *word_eol[]) +{ + multidata data; + + if (!(*word_eol[2])) + return FALSE; + + strcpy (tbuf, "NOTICE "); + + data.reason = word_eol[2]; + data.tbuf = tbuf; + data.i = 0; + data.sess = sess; + tree_foreach (sess->usertree, (tree_traverse_func*)wallchop_cb, &data); + + if (data.i) + { + sprintf (tbuf + strlen (tbuf), + " :[@%s] %s", sess->channel, word_eol[2]); + sess->server->p_raw (sess->server, tbuf); + } + + return TRUE; +} + +static int +cmd_wallchan (struct session *sess, char *tbuf, char *word[], + char *word_eol[]) +{ + GSList *list; + + if (*word_eol[2]) + { + list = sess_list; + while (list) + { + sess = list->data; + if (sess->type == SESS_CHANNEL) + { + inbound_chanmsg (sess->server, NULL, sess->channel, + sess->server->nick, word_eol[2], TRUE, FALSE); + sess->server->p_message (sess->server, sess->channel, word_eol[2]); + } + list = list->next; + } + return TRUE; + } + return FALSE; +} + +static int +cmd_hop (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int i = 2; + + while (1) + { + if (!*word[i]) + { + if (i == 2) + return FALSE; + send_channel_modes (sess, tbuf, word, 2, i, '+', 'h', 0); + return TRUE; + } + i++; + } +} + +static int +cmd_voice (struct session *sess, char *tbuf, char *word[], char *word_eol[]) +{ + int i = 2; + + while (1) + { + if (!*word[i]) + { + if (i == 2) + return FALSE; + send_channel_modes (sess, tbuf, word, 2, i, '+', 'v', 0); + return TRUE; + } + i++; + } +} + +/* *MUST* be kept perfectly sorted for the bsearch to work */ +const struct commands xc_cmds[] = { + {"ADDBUTTON", cmd_addbutton, 0, 0, 1, + N_("ADDBUTTON <name> <action>, adds a button under the user-list")}, + {"ALLCHAN", cmd_allchannels, 0, 0, 1, + N_("ALLCHAN <cmd>, sends a command to all channels you're in")}, + {"ALLCHANL", cmd_allchannelslocal, 0, 0, 1, + N_("ALLCHANL <cmd>, sends a command to all channels you're in")}, + {"ALLSERV", cmd_allservers, 0, 0, 1, + N_("ALLSERV <cmd>, sends a command to all servers you're in")}, + {"AWAY", cmd_away, 1, 0, 1, N_("AWAY [<reason>], sets you away")}, + {"BACK", cmd_back, 1, 0, 1, N_("BACK, sets you back (not away)")}, + {"BAN", cmd_ban, 1, 1, 1, + N_("BAN <mask> [<bantype>], bans everyone matching the mask from the current channel. If they are already on the channel this doesn't kick them (needs chanop)")}, + {"CHANOPT", cmd_chanopt, 0, 0, 1, N_("CHANOPT [-quiet] <variable> [<value>]")}, + {"CHARSET", cmd_charset, 0, 0, 1, 0}, + {"CLEAR", cmd_clear, 0, 0, 1, N_("CLEAR [ALL|HISTORY], Clears the current text window or command history")}, + {"CLOSE", cmd_close, 0, 0, 1, N_("CLOSE, Closes the current window/tab")}, + + {"COUNTRY", cmd_country, 0, 0, 1, + N_("COUNTRY [-s] <code|wildcard>, finds a country code, eg: au = australia")}, + {"CTCP", cmd_ctcp, 1, 0, 1, + N_("CTCP <nick> <message>, send the CTCP message to nick, common messages are VERSION and USERINFO")}, + {"CYCLE", cmd_cycle, 1, 1, 1, + N_("CYCLE [<channel>], parts the current or given channel and immediately rejoins")}, + {"DCC", cmd_dcc, 0, 0, 1, + N_("\n" + "DCC GET <nick> - accept an offered file\n" + "DCC SEND [-maxcps=#] <nick> [file] - send a file to someone\n" + "DCC PSEND [-maxcps=#] <nick> [file] - send a file using passive mode\n" + "DCC LIST - show DCC list\n" + "DCC CHAT <nick> - offer DCC CHAT to someone\n" + "DCC PCHAT <nick> - offer DCC CHAT using passive mode\n" + "DCC CLOSE <type> <nick> <file> example:\n" + " /dcc close send johnsmith file.tar.gz")}, + {"DEBUG", cmd_debug, 0, 0, 1, 0}, + + {"DEHOP", cmd_dehop, 1, 1, 1, + N_("DEHOP <nick>, removes chanhalf-op status from the nick on the current channel (needs chanop)")}, + {"DELBUTTON", cmd_delbutton, 0, 0, 1, + N_("DELBUTTON <name>, deletes a button from under the user-list")}, + {"DEOP", cmd_deop, 1, 1, 1, + N_("DEOP <nick>, removes chanop status from the nick on the current channel (needs chanop)")}, + {"DEVOICE", cmd_devoice, 1, 1, 1, + N_("DEVOICE <nick>, removes voice status from the nick on the current channel (needs chanop)")}, + {"DISCON", cmd_discon, 0, 0, 1, N_("DISCON, Disconnects from server")}, + {"DNS", cmd_dns, 0, 0, 1, N_("DNS <nick|host|ip>, Finds a users IP number")}, + {"ECHO", cmd_echo, 0, 0, 1, N_("ECHO <text>, Prints text locally")}, +#ifndef WIN32 + {"EXEC", cmd_exec, 0, 0, 1, + N_("EXEC [-o] <command>, runs the command. If -o flag is used then output is sent to current channel, else is printed to current text box")}, +#ifndef __EMX__ + {"EXECCONT", cmd_execc, 0, 0, 1, N_("EXECCONT, sends the process SIGCONT")}, +#endif + {"EXECKILL", cmd_execk, 0, 0, 1, + N_("EXECKILL [-9], kills a running exec in the current session. If -9 is given the process is SIGKILL'ed")}, +#ifndef __EMX__ + {"EXECSTOP", cmd_execs, 0, 0, 1, N_("EXECSTOP, sends the process SIGSTOP")}, + {"EXECWRITE", cmd_execw, 0, 0, 1, N_("EXECWRITE, sends data to the processes stdin")}, +#endif +#endif + {"FLUSHQ", cmd_flushq, 0, 0, 1, + N_("FLUSHQ, flushes the current server's send queue")}, + {"GATE", cmd_gate, 0, 0, 1, + N_("GATE <host> [<port>], proxies through a host, port defaults to 23")}, + {"GETFILE", cmd_getfile, 0, 0, 1, "GETFILE [-folder] [-multi] [-save] <command> <title> [<initial>]"}, + {"GETINT", cmd_getint, 0, 0, 1, "GETINT <default> <command> <prompt>"}, + {"GETSTR", cmd_getstr, 0, 0, 1, "GETSTR <default> <command> <prompt>"}, + {"GHOST", cmd_ghost, 1, 0, 1, N_("GHOST <nick> [password], Kills a ghosted nickname")}, + {"GUI", cmd_gui, 0, 0, 1, "GUI [APPLY|ATTACH|DETACH|SHOW|HIDE|FOCUS|FLASH|ICONIFY|COLOR <n>]\n" + " GUI [MSGBOX <text>|MENU TOGGLE]"}, + {"HELP", cmd_help, 0, 0, 1, 0}, + {"HOP", cmd_hop, 1, 1, 1, + N_("HOP <nick>, gives chanhalf-op status to the nick (needs chanop)")}, + {"ID", cmd_id, 1, 0, 1, N_("ID <password>, identifies yourself to nickserv")}, + {"IGNORE", cmd_ignore, 0, 0, 1, + N_("IGNORE <mask> <types..> <options..>\n" + " mask - host mask to ignore, eg: *!*@*.aol.com\n" + " types - types of data to ignore, one or all of:\n" + " PRIV, CHAN, NOTI, CTCP, DCC, INVI, ALL\n" + " options - NOSAVE, QUIET")}, + + {"INVITE", cmd_invite, 1, 0, 1, + N_("INVITE <nick> [<channel>], invites someone to a channel, by default the current channel (needs chanop)")}, + {"JOIN", cmd_join, 1, 0, 0, N_("JOIN <channel>, joins the channel")}, + {"KICK", cmd_kick, 1, 1, 1, + N_("KICK <nick>, kicks the nick from the current channel (needs chanop)")}, + {"KICKBAN", cmd_kickban, 1, 1, 1, + N_("KICKBAN <nick>, bans then kicks the nick from the current channel (needs chanop)")}, + {"KILLALL", cmd_killall, 0, 0, 1, "KILLALL, immediately exit"}, + {"LAGCHECK", cmd_lagcheck, 0, 0, 1, + N_("LAGCHECK, forces a new lag check")}, + {"LASTLOG", cmd_lastlog, 0, 0, 1, + N_("LASTLOG <string>, searches for a string in the buffer")}, + {"LIST", cmd_list, 1, 0, 1, 0}, + {"LOAD", cmd_load, 0, 0, 1, N_("LOAD [-e] <file>, loads a plugin or script")}, + + {"MDEHOP", cmd_mdehop, 1, 1, 1, + N_("MDEHOP, Mass deop's all chanhalf-ops in the current channel (needs chanop)")}, + {"MDEOP", cmd_mdeop, 1, 1, 1, + N_("MDEOP, Mass deop's all chanops in the current channel (needs chanop)")}, + {"ME", cmd_me, 0, 0, 1, + N_("ME <action>, sends the action to the current channel (actions are written in the 3rd person, like /me jumps)")}, + {"MENU", cmd_menu, 0, 0, 1, "MENU [-eX] [-i<ICONFILE>] [-k<mod>,<key>] [-m] [-pX] [-r<X,group>] [-tX] {ADD|DEL} <path> [command] [unselect command]\n" + " See http://xchat.org/docs/menu/ for more details."}, + {"MKICK", cmd_mkick, 1, 1, 1, + N_("MKICK, Mass kicks everyone except you in the current channel (needs chanop)")}, + {"MODE", cmd_mode, 1, 0, 1, 0}, + {"MOP", cmd_mop, 1, 1, 1, + N_("MOP, Mass op's all users in the current channel (needs chanop)")}, + {"MSG", cmd_msg, 0, 0, 1, N_("MSG <nick> <message>, sends a private message")}, + + {"NAMES", cmd_names, 1, 0, 1, + N_("NAMES, Lists the nicks on the current channel")}, + {"NCTCP", cmd_nctcp, 1, 0, 1, + N_("NCTCP <nick> <message>, Sends a CTCP notice")}, + {"NEWSERVER", cmd_newserver, 0, 0, 1, N_("NEWSERVER [-noconnect] <hostname> [<port>]")}, + {"NICK", cmd_nick, 0, 0, 1, N_("NICK <nickname>, sets your nick")}, + + {"NOTICE", cmd_notice, 1, 0, 1, + N_("NOTICE <nick/channel> <message>, sends a notice. Notices are a type of message that should be auto reacted to")}, + {"NOTIFY", cmd_notify, 0, 0, 1, + N_("NOTIFY [-n network1[,network2,...]] [<nick>], displays your notify list or adds someone to it")}, + {"OP", cmd_op, 1, 1, 1, + N_("OP <nick>, gives chanop status to the nick (needs chanop)")}, + {"PART", cmd_part, 1, 1, 0, + N_("PART [<channel>] [<reason>], leaves the channel, by default the current one")}, + {"PING", cmd_ping, 1, 0, 1, + N_("PING <nick | channel>, CTCP pings nick or channel")}, + {"QUERY", cmd_query, 0, 0, 1, + N_("QUERY [-nofocus] <nick>, opens up a new privmsg window to someone")}, + {"QUIT", cmd_quit, 0, 0, 1, + N_("QUIT [<reason>], disconnects from the current server")}, + {"QUOTE", cmd_quote, 1, 0, 1, + N_("QUOTE <text>, sends the text in raw form to the server")}, +#ifdef USE_OPENSSL + {"RECONNECT", cmd_reconnect, 0, 0, 1, + N_("RECONNECT [-ssl] [<host>] [<port>] [<password>], Can be called just as /RECONNECT to reconnect to the current server or with /RECONNECT ALL to reconnect to all the open servers")}, +#else + {"RECONNECT", cmd_reconnect, 0, 0, 1, + N_("RECONNECT [<host>] [<port>] [<password>], Can be called just as /RECONNECT to reconnect to the current server or with /RECONNECT ALL to reconnect to all the open servers")}, +#endif + {"RECV", cmd_recv, 1, 0, 1, N_("RECV <text>, send raw data to xchat, as if it was received from the irc server")}, + + {"SAY", cmd_say, 0, 0, 1, + N_("SAY <text>, sends the text to the object in the current window")}, + {"SEND", cmd_send, 0, 0, 1, N_("SEND <nick> [<file>]")}, +#ifdef USE_OPENSSL + {"SERVCHAN", cmd_servchan, 0, 0, 1, + N_("SERVCHAN [-ssl] <host> <port> <channel>, connects and joins a channel")}, +#else + {"SERVCHAN", cmd_servchan, 0, 0, 1, + N_("SERVCHAN <host> <port> <channel>, connects and joins a channel")}, +#endif +#ifdef USE_OPENSSL + {"SERVER", cmd_server, 0, 0, 1, + N_("SERVER [-ssl] <host> [<port>] [<password>], connects to a server, the default port is 6667 for normal connections, and 9999 for ssl connections")}, +#else + {"SERVER", cmd_server, 0, 0, 1, + N_("SERVER <host> [<port>] [<password>], connects to a server, the default port is 6667")}, +#endif + {"SET", cmd_set, 0, 0, 1, N_("SET [-e] [-off|-on] [-quiet] <variable> [<value>]")}, + {"SETCURSOR", cmd_setcursor, 0, 0, 1, N_("SETCURSOR [-|+]<position>")}, + {"SETTAB", cmd_settab, 0, 0, 1, 0}, + {"SETTEXT", cmd_settext, 0, 0, 1, 0}, + {"SPLAY", cmd_splay, 0, 0, 1, "SPLAY <soundfile>"}, + {"TOPIC", cmd_topic, 1, 1, 1, + N_("TOPIC [<topic>], sets the topic if one is given, else shows the current topic")}, + {"TRAY", cmd_tray, 0, 0, 1, + N_("\nTRAY -f <timeout> <file1> [<file2>] Blink tray between two icons.\n" + "TRAY -f <filename> Set tray to a fixed icon.\n" + "TRAY -i <number> Blink tray with an internal icon.\n" + "TRAY -t <text> Set the tray tooltip.\n" + "TRAY -b <title> <text> Set the tray balloon." + )}, + {"UNBAN", cmd_unban, 1, 1, 1, + N_("UNBAN <mask> [<mask>...], unbans the specified masks.")}, + {"UNIGNORE", cmd_unignore, 0, 0, 1, N_("UNIGNORE <mask> [QUIET]")}, + {"UNLOAD", cmd_unload, 0, 0, 1, N_("UNLOAD <name>, unloads a plugin or script")}, + {"URL", cmd_url, 0, 0, 1, N_("URL <url>, opens a URL in your browser")}, + {"USELECT", cmd_uselect, 0, 1, 0, + N_("USELECT [-a] [-s] <nick1> <nick2> etc, highlights nick(s) in channel userlist")}, + {"USERLIST", cmd_userlist, 1, 1, 1, 0}, + {"VOICE", cmd_voice, 1, 1, 1, + N_("VOICE <nick>, gives voice status to someone (needs chanop)")}, + {"WALLCHAN", cmd_wallchan, 1, 1, 1, + N_("WALLCHAN <message>, writes the message to all channels")}, + {"WALLCHOP", cmd_wallchop, 1, 1, 1, + N_("WALLCHOP <message>, sends the message to all chanops on the current channel")}, + {0, 0, 0, 0, 0, 0} +}; + + +static int +command_compare (const void *a, const void *b) +{ + return strcasecmp (a, ((struct commands *)b)->name); +} + +static struct commands * +find_internal_command (char *name) +{ + /* the "-1" is to skip the NULL terminator */ + return bsearch (name, xc_cmds, (sizeof (xc_cmds) / + sizeof (xc_cmds[0])) - 1, sizeof (xc_cmds[0]), command_compare); +} + +static void +help (session *sess, char *tbuf, char *helpcmd, int quiet) +{ + struct commands *cmd; + + if (plugin_show_help (sess, helpcmd)) + return; + + cmd = find_internal_command (helpcmd); + + if (cmd) + { + if (cmd->help) + { + snprintf (tbuf, TBUFSIZE, _("Usage: %s\n"), _(cmd->help)); + PrintText (sess, tbuf); + } else + { + if (!quiet) + PrintText (sess, _("\nNo help available on that command.\n")); + } + return; + } + + if (!quiet) + PrintText (sess, _("No such command.\n")); +} + +/* inserts %a, %c, %d etc into buffer. Also handles &x %x for word/word_eol. * + * returns 2 on buffer overflow + * returns 1 on success * + * returns 0 on bad-args-for-user-command * + * - word/word_eol args might be NULL * + * - this beast is used for UserCommands, UserlistButtons and CTCP replies */ + +int +auto_insert (char *dest, int destlen, unsigned char *src, char *word[], + char *word_eol[], char *a, char *c, char *d, char *e, char *h, + char *n, char *s) +{ + int num; + char buf[32]; + time_t now; + struct tm *tm_ptr; + char *utf; + gsize utf_len; + char *orig = dest; + + destlen--; + + while (src[0]) + { + if (src[0] == '%' || src[0] == '&') + { + if (isdigit ((unsigned char) src[1])) + { + if (isdigit ((unsigned char) src[2]) && isdigit ((unsigned char) src[3])) + { + buf[0] = src[1]; + buf[1] = src[2]; + buf[2] = src[3]; + buf[3] = 0; + dest[0] = atoi (buf); + utf = g_locale_to_utf8 (dest, 1, 0, &utf_len, 0); + if (utf) + { + if ((dest - orig) + utf_len >= destlen) + { + g_free (utf); + return 2; + } + + memcpy (dest, utf, utf_len); + g_free (utf); + dest += utf_len; + } + src += 3; + } else + { + if (word) + { + src++; + num = src[0] - '0'; /* ascii to decimal */ + if (*word[num] == 0) + return 0; + + if (src[-1] == '%') + utf = word[num]; + else + utf = word_eol[num]; + + /* avoid recusive usercommand overflow */ + if ((dest - orig) + strlen (utf) >= destlen) + return 2; + + strcpy (dest, utf); + dest += strlen (dest); + } + } + } else + { + if (src[0] == '&') + goto lamecode; + src++; + utf = NULL; + switch (src[0]) + { + case '%': + if ((dest - orig) + 2 >= destlen) + return 2; + dest[0] = '%'; + dest[1] = 0; + dest++; + break; + case 'a': + utf = a; break; + case 'c': + utf = c; break; + case 'd': + utf = d; break; + case 'e': + utf = e; break; + case 'h': + utf = h; break; + case 'm': + utf = get_cpu_str (); break; + case 'n': + utf = n; break; + case 's': + utf = s; break; + case 't': + now = time (0); + utf = ctime (&now); + utf[19] = 0; + break; + case 'v': + utf = PACKAGE_VERSION; break; + break; + case 'y': + now = time (0); + tm_ptr = localtime (&now); + snprintf (buf, sizeof (buf), "%4d%02d%02d", 1900 + + tm_ptr->tm_year, 1 + tm_ptr->tm_mon, tm_ptr->tm_mday); + utf = buf; + break; + default: + src--; + goto lamecode; + } + + if (utf) + { + if ((dest - orig) + strlen (utf) >= destlen) + return 2; + strcpy (dest, utf); + dest += strlen (dest); + } + + } + src++; + } else + { + utf_len = g_utf8_skip[src[0]]; + + if ((dest - orig) + utf_len >= destlen) + return 2; + + if (utf_len == 1) + { + lamecode: + dest[0] = src[0]; + dest++; + src++; + } else + { + memcpy (dest, src, utf_len); + dest += utf_len; + src += utf_len; + } + } + } + + dest[0] = 0; + + return 1; +} + +void +check_special_chars (char *cmd, int do_ascii) /* check for %X */ +{ + int occur = 0; + int len = strlen (cmd); + char *buf, *utf; + char tbuf[4]; + int i = 0, j = 0; + gsize utf_len; + + if (!len) + return; + + buf = malloc (len + 1); + + if (buf) + { + while (cmd[j]) + { + switch (cmd[j]) + { + case '%': + occur++; + if ( do_ascii && + j + 3 < len && + (isdigit ((unsigned char) cmd[j + 1]) && isdigit ((unsigned char) cmd[j + 2]) && + isdigit ((unsigned char) cmd[j + 3]))) + { + tbuf[0] = cmd[j + 1]; + tbuf[1] = cmd[j + 2]; + tbuf[2] = cmd[j + 3]; + tbuf[3] = 0; + buf[i] = atoi (tbuf); + utf = g_locale_to_utf8 (buf + i, 1, 0, &utf_len, 0); + if (utf) + { + memcpy (buf + i, utf, utf_len); + g_free (utf); + i += (utf_len - 1); + } + j += 3; + } else + { + switch (cmd[j + 1]) + { + case 'R': + buf[i] = '\026'; + break; + case 'U': + buf[i] = '\037'; + break; + case 'B': + buf[i] = '\002'; + break; + case 'C': + buf[i] = '\003'; + break; + case 'O': + buf[i] = '\017'; + break; + case 'H': /* CL: invisible text code */ + buf[i] = HIDDEN_CHAR; + break; + case '%': + buf[i] = '%'; + break; + default: + buf[i] = '%'; + j--; + break; + } + j++; + break; + default: + buf[i] = cmd[j]; + } + } + j++; + i++; + } + buf[i] = 0; + if (occur) + strcpy (cmd, buf); + free (buf); + } +} + +typedef struct +{ + char *nick; + int len; + struct User *best; + int bestlen; + char *space; + char *tbuf; +} nickdata; + +static int +nick_comp_cb (struct User *user, nickdata *data) +{ + int lenu; + + if (!rfc_ncasecmp (user->nick, data->nick, data->len)) + { + lenu = strlen (user->nick); + if (lenu == data->len) + { + snprintf (data->tbuf, TBUFSIZE, "%s%s", user->nick, data->space); + data->len = -1; + return FALSE; + } else if (lenu < data->bestlen) + { + data->bestlen = lenu; + data->best = user; + } + } + + return TRUE; +} + +static void +perform_nick_completion (struct session *sess, char *cmd, char *tbuf) +{ + int len; + char *space = strchr (cmd, ' '); + if (space && space != cmd) + { + if (space[-1] == prefs.nick_suffix[0] && space - 1 != cmd) + { + len = space - cmd - 1; + if (len < NICKLEN) + { + char nick[NICKLEN]; + nickdata data; + + memcpy (nick, cmd, len); + nick[len] = 0; + + data.nick = nick; + data.len = len; + data.bestlen = INT_MAX; + data.best = NULL; + data.tbuf = tbuf; + data.space = space - 1; + tree_foreach (sess->usertree, (tree_traverse_func *)nick_comp_cb, &data); + + if (data.len == -1) + return; + + if (data.best) + { + snprintf (tbuf, TBUFSIZE, "%s%s", data.best->nick, space - 1); + return; + } + } + } + } + + strcpy (tbuf, cmd); +} + +static void +user_command (session * sess, char *tbuf, char *cmd, char *word[], + char *word_eol[]) +{ + if (!auto_insert (tbuf, 2048, cmd, word, word_eol, "", sess->channel, "", + server_get_network (sess->server, TRUE), "", + sess->server->nick, "")) + { + PrintText (sess, _("Bad arguments for user command.\n")); + return; + } + + handle_command (sess, tbuf, TRUE); +} + +/* handle text entered without a CMDchar prefix */ + +static void +handle_say (session *sess, char *text, int check_spch) +{ + struct DCC *dcc; + char *word[PDIWORDS+1]; + char *word_eol[PDIWORDS+1]; + char pdibuf_static[1024]; + char newcmd_static[1024]; + char *pdibuf = pdibuf_static; + char *newcmd = newcmd_static; + int len; + int newcmdlen = sizeof newcmd_static; + + if (strcmp (sess->channel, "(lastlog)") == 0) + { + lastlog (sess->lastlog_sess, text, sess->lastlog_regexp); + return; + } + + len = strlen (text); + if (len >= sizeof pdibuf_static) + pdibuf = malloc (len + 1); + + if (len + NICKLEN >= newcmdlen) + newcmd = malloc (newcmdlen = len + NICKLEN + 1); + + if (check_spch && prefs.perc_color) + check_special_chars (text, prefs.perc_ascii); + + /* Python relies on this */ + word[PDIWORDS] = NULL; + word_eol[PDIWORDS] = NULL; + + /* split the text into words and word_eol */ + process_data_init (pdibuf, text, word, word_eol, TRUE, FALSE); + + /* a command of "" can be hooked for non-commands */ + if (plugin_emit_command (sess, "", word, word_eol)) + goto xit; + + /* incase a plugin did /close */ + if (!is_session (sess)) + goto xit; + + if (!sess->channel[0] || sess->type == SESS_SERVER || sess->type == SESS_NOTICES || sess->type == SESS_SNOTICES) + { + notj_msg (sess); + goto xit; + } + + if (prefs.nickcompletion) + perform_nick_completion (sess, text, newcmd); + else + safe_strcpy (newcmd, text, newcmdlen); + + text = newcmd; + + if (sess->type == SESS_DIALOG) + { + /* try it via dcc, if possible */ + dcc = dcc_write_chat (sess->channel, text); + if (dcc) + { + inbound_chanmsg (sess->server, NULL, sess->channel, + sess->server->nick, text, TRUE, FALSE); + set_topic (sess, net_ip (dcc->addr), net_ip (dcc->addr)); + goto xit; + } + } + + if (sess->server->connected) + { + unsigned int max; + unsigned char t = 0; + + /* maximum allowed message text */ + /* :nickname!username@host.com PRIVMSG #channel :text\r\n */ + max = 512; + max -= 16; /* :, !, @, " PRIVMSG ", " ", :, \r, \n */ + max -= strlen (sess->server->nick); + max -= strlen (sess->channel); + if (sess->me && sess->me->hostname) + max -= strlen (sess->me->hostname); + else + { + max -= 9; /* username */ + max -= 65; /* max possible hostname and '@' */ + } + + if (strlen (text) > max) + { + int i = 0, size; + + /* traverse the utf8 string and find the nearest cut point that + doesn't split 1 char in half */ + while (1) + { + size = g_utf8_skip[((unsigned char *)text)[i]]; + if ((i + size) >= max) + break; + i += size; + } + max = i; + t = text[max]; + text[max] = 0; /* insert a NULL terminator to shorten it */ + } + + inbound_chanmsg (sess->server, sess, sess->channel, sess->server->nick, + text, TRUE, FALSE); + sess->server->p_message (sess->server, sess->channel, text); + + if (t) + { + text[max] = t; + handle_say (sess, text + max, FALSE); + } + + } else + { + notc_msg (sess); + } + +xit: + if (pdibuf != pdibuf_static) + free (pdibuf); + + if (newcmd != newcmd_static) + free (newcmd); +} + +/* handle a command, without the '/' prefix */ + +int +handle_command (session *sess, char *cmd, int check_spch) +{ + struct popup *pop; + int user_cmd = FALSE; + GSList *list; + char *word[PDIWORDS+1]; + char *word_eol[PDIWORDS+1]; + static int command_level = 0; + struct commands *int_cmd; + char pdibuf_static[1024]; + char tbuf_static[TBUFSIZE]; + char *pdibuf; + char *tbuf; + int len; + int ret = TRUE; + + if (command_level > 99) + { + fe_message (_("Too many recursive usercommands, aborting."), FE_MSG_ERROR); + return TRUE; + } + command_level++; + /* anything below MUST DEC command_level before returning */ + + len = strlen (cmd); + if (len >= sizeof (pdibuf_static)) + pdibuf = malloc (len + 1); + else + pdibuf = pdibuf_static; + + if ((len * 2) >= sizeof (tbuf_static)) + tbuf = malloc ((len * 2) + 1); + else + tbuf = tbuf_static; + + /* split the text into words and word_eol */ + process_data_init (pdibuf, cmd, word, word_eol, TRUE, TRUE); + + /* ensure an empty string at index 32 for cmd_deop etc */ + /* (internal use only, plugins can still only read 1-31). */ + word[PDIWORDS] = "\000\000"; + word_eol[PDIWORDS] = "\000\000"; + + int_cmd = find_internal_command (word[1]); + /* redo it without quotes processing, for some commands like /JOIN */ + if (int_cmd && !int_cmd->handle_quotes) + process_data_init (pdibuf, cmd, word, word_eol, FALSE, FALSE); + + if (check_spch && prefs.perc_color) + check_special_chars (cmd, prefs.perc_ascii); + + if (plugin_emit_command (sess, word[1], word, word_eol)) + goto xit; + + /* incase a plugin did /close */ + if (!is_session (sess)) + goto xit; + + /* first see if it's a userCommand */ + list = command_list; + while (list) + { + pop = (struct popup *) list->data; + if (!strcasecmp (pop->name, word[1])) + { + user_command (sess, tbuf, pop->cmd, word, word_eol); + user_cmd = TRUE; + } + list = list->next; + } + + if (user_cmd) + goto xit; + + /* now check internal commands */ + int_cmd = find_internal_command (word[1]); + + if (int_cmd) + { + if (int_cmd->needserver && !sess->server->connected) + { + notc_msg (sess); + } else if (int_cmd->needchannel && !sess->channel[0]) + { + notj_msg (sess); + } else + { + switch (int_cmd->callback (sess, tbuf, word, word_eol)) + { + case FALSE: + help (sess, tbuf, int_cmd->name, TRUE); + break; + case 2: + ret = FALSE; + goto xit; + } + } + } else + { + /* unknown command, just send it to the server and hope */ + if (!sess->server->connected) + PrintText (sess, _("Unknown Command. Try /help\n")); + else + sess->server->p_raw (sess->server, cmd); + } + +xit: + command_level--; + + if (pdibuf != pdibuf_static) + free (pdibuf); + + if (tbuf != tbuf_static) + free (tbuf); + + return ret; +} + +/* handle one line entered into the input box */ + +static int +handle_user_input (session *sess, char *text, int history, int nocommand) +{ + if (*text == '\0') + return 1; + + if (history) + history_add (&sess->history, text); + + /* is it NOT a command, just text? */ + if (nocommand || text[0] != prefs.cmdchar[0]) + { + handle_say (sess, text, TRUE); + return 1; + } + + /* check for // */ + if (text[0] == prefs.cmdchar[0] && text[1] == prefs.cmdchar[0]) + { + handle_say (sess, text + 1, TRUE); + return 1; + } + + if (prefs.cmdchar[0] == '/') + { + int i; + const char *unix_dirs [] = { + "/bin/", "/boot/", "/dev/", + "/etc/", "/home/", "/lib/", + "/lost+found/", "/mnt/", "/opt/", + "/proc/", "/root/", "/sbin/", + "/tmp/", "/usr/", "/var/", + "/gnome/", NULL}; + for (i = 0; unix_dirs[i] != NULL; i++) + if (strncmp (text, unix_dirs[i], strlen (unix_dirs[i]))==0) + { + handle_say (sess, text, TRUE); + return 1; + } + } + + return handle_command (sess, text + 1, TRUE); +} + +/* changed by Steve Green. Macs sometimes paste with imbedded \r */ +void +handle_multiline (session *sess, char *cmd, int history, int nocommand) +{ + while (*cmd) + { + char *cr = cmd + strcspn (cmd, "\n\r"); + int end_of_string = *cr == 0; + *cr = 0; + if (!handle_user_input (sess, cmd, history, nocommand)) + return; + if (end_of_string) + break; + cmd = cr + 1; + } +} + +/*void +handle_multiline (session *sess, char *cmd, int history, int nocommand) +{ + char *cr; + + cr = strchr (cmd, '\n'); + if (cr) + { + while (1) + { + if (cr) + *cr = 0; + if (!handle_user_input (sess, cmd, history, nocommand)) + return; + if (!cr) + break; + cmd = cr + 1; + if (*cmd == 0) + break; + cr = strchr (cmd, '\n'); + } + } else + { + handle_user_input (sess, cmd, history, nocommand); + } +}*/ diff --git a/src/common/outbound.h b/src/common/outbound.h new file mode 100644 index 00000000..a2047845 --- /dev/null +++ b/src/common/outbound.h @@ -0,0 +1,20 @@ +#ifndef XCHAT_OUTBOUND_H +#define XCHAT_OUTBOUND_H + +extern const struct commands xc_cmds[]; +extern GSList *menu_list; + +int auto_insert (char *dest, int destlen, unsigned char *src, char *word[], char *word_eol[], + char *a, char *c, char *d, char *e, char *h, char *n, char *s); +int handle_command (session *sess, char *cmd, int check_spch); +void process_data_init (char *buf, char *cmd, char *word[], char *word_eol[], gboolean handle_quotes, gboolean allow_escape_quotes); +void handle_multiline (session *sess, char *cmd, int history, int nocommand); +void check_special_chars (char *cmd, int do_ascii); +void notc_msg (session *sess); +void server_sendpart (server * serv, char *channel, char *reason); +void server_sendquit (session * sess); +int menu_streq (const char *s1, const char *s2, int def); +void open_query (server *serv, char *nick, gboolean focus_existing); +gboolean load_perform_file (session *sess, char *file); + +#endif diff --git a/src/common/plugin-timer.c b/src/common/plugin-timer.c new file mode 100644 index 00000000..f09074a8 --- /dev/null +++ b/src/common/plugin-timer.c @@ -0,0 +1,208 @@ +#include <stdlib.h> +#include <string.h> +#include <glib.h> +#include "xchat-plugin.h" + +#ifdef WIN32 +#define strcasecmp stricmp +#endif + +static xchat_plugin *ph; /* plugin handle */ +static GSList *timer_list = NULL; + +#define STATIC +#define HELP \ +"Usage: TIMER [-refnum <num>] [-repeat <num>] <seconds> <command>\n" \ +" TIMER [-quiet] -delete <num>" + +typedef struct +{ + xchat_hook *hook; + xchat_context *context; + char *command; + int ref; + int repeat; + float timeout; + unsigned int forever:1; +} timer; + +static void +timer_del (timer *tim) +{ + timer_list = g_slist_remove (timer_list, tim); + free (tim->command); + xchat_unhook (ph, tim->hook); + free (tim); +} + +static void +timer_del_ref (int ref, int quiet) +{ + GSList *list; + timer *tim; + + list = timer_list; + while (list) + { + tim = list->data; + if (tim->ref == ref) + { + timer_del (tim); + if (!quiet) + xchat_printf (ph, "Timer %d deleted.\n", ref); + return; + } + list = list->next; + } + if (!quiet) + xchat_print (ph, "No such ref number found.\n"); +} + +static int +timeout_cb (timer *tim) +{ + if (xchat_set_context (ph, tim->context)) + { + xchat_command (ph, tim->command); + + if (tim->forever) + return 1; + + tim->repeat--; + if (tim->repeat > 0) + return 1; + } + + timer_del (tim); + return 0; +} + +static void +timer_add (int ref, float timeout, int repeat, char *command) +{ + timer *tim; + GSList *list; + + if (ref == 0) + { + ref = 1; + list = timer_list; + while (list) + { + tim = list->data; + if (tim->ref >= ref) + ref = tim->ref + 1; + list = list->next; + } + } + + tim = malloc (sizeof (timer)); + tim->ref = ref; + tim->repeat = repeat; + tim->timeout = timeout; + tim->command = strdup (command); + tim->context = xchat_get_context (ph); + tim->forever = FALSE; + + if (repeat == 0) + tim->forever = TRUE; + + tim->hook = xchat_hook_timer (ph, timeout * 1000.0, (void *)timeout_cb, tim); + timer_list = g_slist_append (timer_list, tim); +} + +static void +timer_showlist (void) +{ + GSList *list; + timer *tim; + + if (timer_list == NULL) + { + xchat_print (ph, "No timers installed.\n"); + xchat_print (ph, HELP); + return; + } + /* 00000 00000000 0000000 abc */ + xchat_print (ph, "\026 Ref# Seconds Repeat Command \026\n"); + list = timer_list; + while (list) + { + tim = list->data; + xchat_printf (ph, "%5d %8.1f %7d %s\n", tim->ref, tim->timeout, + tim->repeat, tim->command); + list = list->next; + } +} + +static int +timer_cb (char *word[], char *word_eol[], void *userdata) +{ + int repeat = 1; + float timeout; + int offset = 0; + int ref = 0; + int quiet = FALSE; + char *command; + + if (!word[2][0]) + { + timer_showlist (); + return XCHAT_EAT_XCHAT; + } + + if (strcasecmp (word[2], "-quiet") == 0) + { + quiet = TRUE; + offset++; + } + + if (strcasecmp (word[2 + offset], "-delete") == 0) + { + timer_del_ref (atoi (word[3 + offset]), quiet); + return XCHAT_EAT_XCHAT; + } + + if (strcasecmp (word[2 + offset], "-refnum") == 0) + { + ref = atoi (word[3 + offset]); + offset += 2; + } + + if (strcasecmp (word[2 + offset], "-repeat") == 0) + { + repeat = atoi (word[3 + offset]); + offset += 2; + } + + timeout = atof (word[2 + offset]); + command = word_eol[3 + offset]; + + if (timeout < 0.1 || !command[0]) + xchat_print (ph, HELP); + else + timer_add (ref, timeout, repeat, command); + + return XCHAT_EAT_XCHAT; +} + +int +#ifdef STATIC +timer_plugin_init +#else +xchat_plugin_init +#endif + (xchat_plugin *plugin_handle, char **plugin_name, + char **plugin_desc, char **plugin_version, char *arg) +{ + /* we need to save this for use with any xchat_* functions */ + ph = plugin_handle; + + *plugin_name = "Timer"; + *plugin_desc = "IrcII style /TIMER command"; + *plugin_version = ""; + + xchat_hook_command (ph, "TIMER", XCHAT_PRI_NORM, timer_cb, HELP, 0); + + return 1; /* return 1 for success */ +} diff --git a/src/common/plugin-timer.h b/src/common/plugin-timer.h new file mode 100644 index 00000000..e3530c8d --- /dev/null +++ b/src/common/plugin-timer.h @@ -0,0 +1,7 @@ +#ifndef XCHAT_PLUGIN_TIMER_H +#define XCHAT_PLUGIN_TIMER_H + +int timer_plugin_init (xchat_plugin *plugin_handle, char **plugin_name, + char **plugin_desc, char **plugin_version, char *arg); + +#endif diff --git a/src/common/plugin.c b/src/common/plugin.c new file mode 100644 index 00000000..ada4d3be --- /dev/null +++ b/src/common/plugin.c @@ -0,0 +1,1569 @@ +/* X-Chat + * Copyright (C) 2002 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <stdio.h> + +#include "xchat.h" +#include "fe.h" +#include "util.h" +#include "outbound.h" +#include "cfgfiles.h" +#include "ignore.h" +#include "server.h" +#include "servlist.h" +#include "modes.h" +#include "notify.h" +#include "text.h" +#define PLUGIN_C +typedef struct session xchat_context; +#include "xchat-plugin.h" +#include "plugin.h" + + +#include "xchatc.h" + +/* the USE_PLUGIN define only removes libdl stuff */ + +#ifdef USE_PLUGIN +#ifdef USE_GMODULE +#include <gmodule.h> +#else +#include <dlfcn.h> +#endif +#endif + +#define DEBUG(x) {x;} + +/* crafted to be an even 32 bytes */ +struct _xchat_hook +{ + xchat_plugin *pl; /* the plugin to which it belongs */ + char *name; /* "xdcc" */ + void *callback; /* pointer to xdcc_callback */ + char *help_text; /* help_text for commands only */ + void *userdata; /* passed to the callback */ + int tag; /* for timers & FDs only */ + int type; /* HOOK_* */ + int pri; /* fd */ /* priority / fd for HOOK_FD only */ +}; + +struct _xchat_list +{ + int type; /* LIST_* */ + GSList *pos; /* current pos */ + GSList *next; /* next pos */ + GSList *head; /* for LIST_USERS only */ + struct notify_per_server *notifyps; /* notify_per_server * */ +}; + +typedef int (xchat_cmd_cb) (char *word[], char *word_eol[], void *user_data); +typedef int (xchat_serv_cb) (char *word[], char *word_eol[], void *user_data); +typedef int (xchat_print_cb) (char *word[], void *user_data); +typedef int (xchat_fd_cb) (int fd, int flags, void *user_data); +typedef int (xchat_timer_cb) (void *user_data); +typedef int (xchat_init_func) (xchat_plugin *, char **, char **, char **, char *); +typedef int (xchat_deinit_func) (xchat_plugin *); + +enum +{ + LIST_CHANNELS, + LIST_DCC, + LIST_IGNORE, + LIST_NOTIFY, + LIST_USERS +}; + +enum +{ + HOOK_COMMAND, /* /command */ + HOOK_SERVER, /* PRIVMSG, NOTICE, numerics */ + HOOK_PRINT, /* All print events */ + HOOK_TIMER, /* timeouts */ + HOOK_FD, /* sockets & fds */ + HOOK_DELETED /* marked for deletion */ +}; + +GSList *plugin_list = NULL; /* export for plugingui.c */ +static GSList *hook_list = NULL; + +extern const struct prefs vars[]; /* cfgfiles.c */ + + +/* unload a plugin and remove it from our linked list */ + +static int +plugin_free (xchat_plugin *pl, int do_deinit, int allow_refuse) +{ + GSList *list, *next; + xchat_hook *hook; + xchat_deinit_func *deinit_func; + + /* fake plugin added by xchat_plugingui_add() */ + if (pl->fake) + goto xit; + + /* run the plugin's deinit routine, if any */ + if (do_deinit && pl->deinit_callback != NULL) + { + deinit_func = pl->deinit_callback; + if (!deinit_func (pl) && allow_refuse) + return FALSE; + } + + /* remove all of this plugin's hooks */ + list = hook_list; + while (list) + { + hook = list->data; + next = list->next; + if (hook->pl == pl) + xchat_unhook (NULL, hook); + list = next; + } + +#ifdef USE_PLUGIN + if (pl->handle) +#ifdef USE_GMODULE + g_module_close (pl->handle); +#else + dlclose (pl->handle); +#endif +#endif + +xit: + if (pl->free_strings) + { + if (pl->name) + free (pl->name); + if (pl->desc) + free (pl->desc); + if (pl->version) + free (pl->version); + } + if (pl->filename) + free ((char *)pl->filename); + free (pl); + + plugin_list = g_slist_remove (plugin_list, pl); + +#ifdef USE_PLUGIN + fe_pluginlist_update (); +#endif + + return TRUE; +} + +static xchat_plugin * +plugin_list_add (xchat_context *ctx, char *filename, const char *name, + const char *desc, const char *version, void *handle, + void *deinit_func, int fake, int free_strings) +{ + xchat_plugin *pl; + + pl = malloc (sizeof (xchat_plugin)); + pl->handle = handle; + pl->filename = filename; + pl->context = ctx; + pl->name = (char *)name; + pl->desc = (char *)desc; + pl->version = (char *)version; + pl->deinit_callback = deinit_func; + pl->fake = fake; + pl->free_strings = free_strings; /* free() name,desc,version? */ + + plugin_list = g_slist_prepend (plugin_list, pl); + + return pl; +} + +static void * +xchat_dummy (xchat_plugin *ph) +{ + return NULL; +} + +#ifdef WIN32 +static int +xchat_read_fd (xchat_plugin *ph, GIOChannel *source, char *buf, int *len) +{ + return g_io_channel_read (source, buf, *len, len); +} +#endif + +/* Load a static plugin */ + +void +plugin_add (session *sess, char *filename, void *handle, void *init_func, + void *deinit_func, char *arg, int fake) +{ + xchat_plugin *pl; + char *file; + + file = NULL; + if (filename) + file = strdup (filename); + + pl = plugin_list_add (sess, file, file, NULL, NULL, handle, deinit_func, + fake, FALSE); + + if (!fake) + { + /* win32 uses these because it doesn't have --export-dynamic! */ + pl->xchat_hook_command = xchat_hook_command; + pl->xchat_hook_server = xchat_hook_server; + pl->xchat_hook_print = xchat_hook_print; + pl->xchat_hook_timer = xchat_hook_timer; + pl->xchat_hook_fd = xchat_hook_fd; + pl->xchat_unhook = xchat_unhook; + pl->xchat_print = xchat_print; + pl->xchat_printf = xchat_printf; + pl->xchat_command = xchat_command; + pl->xchat_commandf = xchat_commandf; + pl->xchat_nickcmp = xchat_nickcmp; + pl->xchat_set_context = xchat_set_context; + pl->xchat_find_context = xchat_find_context; + pl->xchat_get_context = xchat_get_context; + pl->xchat_get_info = xchat_get_info; + pl->xchat_get_prefs = xchat_get_prefs; + pl->xchat_list_get = xchat_list_get; + pl->xchat_list_free = xchat_list_free; + pl->xchat_list_fields = xchat_list_fields; + pl->xchat_list_str = xchat_list_str; + pl->xchat_list_next = xchat_list_next; + pl->xchat_list_int = xchat_list_int; + pl->xchat_plugingui_add = xchat_plugingui_add; + pl->xchat_plugingui_remove = xchat_plugingui_remove; + pl->xchat_emit_print = xchat_emit_print; +#ifdef WIN32 + pl->xchat_read_fd = (void *) xchat_read_fd; +#else + pl->xchat_read_fd = xchat_dummy; +#endif + pl->xchat_list_time = xchat_list_time; + pl->xchat_gettext = xchat_gettext; + pl->xchat_send_modes = xchat_send_modes; + pl->xchat_strip = xchat_strip; + pl->xchat_free = xchat_free; + + /* incase new plugins are loaded on older xchat */ + pl->xchat_dummy4 = xchat_dummy; + pl->xchat_dummy3 = xchat_dummy; + pl->xchat_dummy2 = xchat_dummy; + pl->xchat_dummy1 = xchat_dummy; + + /* run xchat_plugin_init, if it returns 0, close the plugin */ + if (((xchat_init_func *)init_func) (pl, &pl->name, &pl->desc, &pl->version, arg) == 0) + { + plugin_free (pl, FALSE, FALSE); + return; + } + } + +#ifdef USE_PLUGIN + fe_pluginlist_update (); +#endif +} + +/* kill any plugin by the given (file) name (used by /unload) */ + +int +plugin_kill (char *name, int by_filename) +{ + GSList *list; + xchat_plugin *pl; + + list = plugin_list; + while (list) + { + pl = list->data; + /* static-plugins (plugin-timer.c) have a NULL filename */ + if ((by_filename && pl->filename && strcasecmp (name, pl->filename) == 0) || + (by_filename && pl->filename && strcasecmp (name, file_part (pl->filename)) == 0) || + (!by_filename && strcasecmp (name, pl->name) == 0)) + { + /* statically linked plugins have a NULL filename */ + if (pl->filename != NULL && !pl->fake) + { + if (plugin_free (pl, TRUE, TRUE)) + return 1; + return 2; + } + } + list = list->next; + } + + return 0; +} + +/* kill all running plugins (at shutdown) */ + +void +plugin_kill_all (void) +{ + GSList *list, *next; + xchat_plugin *pl; + + list = plugin_list; + while (list) + { + pl = list->data; + next = list->next; + if (!pl->fake) + plugin_free (list->data, TRUE, FALSE); + list = next; + } +} + +#ifdef USE_PLUGIN + +/* load a plugin from a filename. Returns: NULL-success or an error string */ + +char * +plugin_load (session *sess, char *filename, char *arg) +{ + void *handle; + xchat_init_func *init_func; + xchat_deinit_func *deinit_func; + +#ifdef USE_GMODULE + /* load the plugin */ + handle = g_module_open (filename, 0); + if (handle == NULL) + return (char *)g_module_error (); + + /* find the init routine xchat_plugin_init */ + if (!g_module_symbol (handle, "xchat_plugin_init", (gpointer *)&init_func)) + { + g_module_close (handle); + return _("No xchat_plugin_init symbol; is this really an xchat plugin?"); + } + + /* find the plugin's deinit routine, if any */ + if (!g_module_symbol (handle, "xchat_plugin_deinit", (gpointer *)&deinit_func)) + deinit_func = NULL; + +#else + char *error; + char *filepart; + +/* OpenBSD lacks this! */ +#ifndef RTLD_GLOBAL +#define RTLD_GLOBAL 0 +#endif + +#ifndef RTLD_NOW +#define RTLD_NOW 0 +#endif + + /* get the filename without path */ + filepart = file_part (filename); + + /* load the plugin */ + if (filepart && + /* xsys draws in libgtk-1.2, causing crashes, so force RTLD_LOCAL */ + (strstr (filepart, "local") || strncmp (filepart, "libxsys-1", 9) == 0) + ) + handle = dlopen (filename, RTLD_NOW); + else + handle = dlopen (filename, RTLD_GLOBAL | RTLD_NOW); + if (handle == NULL) + return (char *)dlerror (); + dlerror (); /* Clear any existing error */ + + /* find the init routine xchat_plugin_init */ + init_func = dlsym (handle, "xchat_plugin_init"); + error = (char *)dlerror (); + if (error != NULL) + { + dlclose (handle); + return _("No xchat_plugin_init symbol; is this really an xchat plugin?"); + } + + /* find the plugin's deinit routine, if any */ + deinit_func = dlsym (handle, "xchat_plugin_deinit"); + error = (char *)dlerror (); +#endif + + /* add it to our linked list */ + plugin_add (sess, filename, handle, init_func, deinit_func, arg, FALSE); + + return NULL; +} + +static session *ps; + +static void +plugin_auto_load_cb (char *filename) +{ + char *pMsg; + +#ifndef WIN32 /* black listed */ + if (!strcmp (file_part (filename), "dbus.so")) + return; +#endif + + pMsg = plugin_load (ps, filename, NULL); + if (pMsg) + { + PrintTextf (ps, "AutoLoad failed for: %s\n", filename); + PrintText (ps, pMsg); + } +} + +void +plugin_auto_load (session *sess) +{ + ps = sess; +#ifdef WIN32 + for_files ("./plugins", "*.dll", plugin_auto_load_cb); + for_files (get_xdir_fs (), "*.dll", plugin_auto_load_cb); +#else +#if defined(__hpux) + for_files (XCHATLIBDIR"/plugins", "*.sl", plugin_auto_load_cb); + for_files (get_xdir_fs (), "*.sl", plugin_auto_load_cb); +#else + for_files (XCHATLIBDIR"/plugins", "*.so", plugin_auto_load_cb); + for_files (get_xdir_fs (), "*.so", plugin_auto_load_cb); +#endif +#endif +} + +#endif + +static GSList * +plugin_hook_find (GSList *list, int type, char *name) +{ + xchat_hook *hook; + + while (list) + { + hook = list->data; + if (hook->type == type) + { + if (strcasecmp (hook->name, name) == 0) + return list; + + if (type == HOOK_SERVER) + { + if (strcasecmp (hook->name, "RAW LINE") == 0) + return list; + } + } + list = list->next; + } + + return NULL; +} + +/* check for plugin hooks and run them */ + +static int +plugin_hook_run (session *sess, char *name, char *word[], char *word_eol[], int type) +{ + GSList *list, *next; + xchat_hook *hook; + int ret, eat = 0; + + list = hook_list; + while (1) + { + list = plugin_hook_find (list, type, name); + if (!list) + goto xit; + + hook = list->data; + next = list->next; + hook->pl->context = sess; + + /* run the plugin's callback function */ + switch (type) + { + case HOOK_COMMAND: + ret = ((xchat_cmd_cb *)hook->callback) (word, word_eol, hook->userdata); + break; + case HOOK_SERVER: + ret = ((xchat_serv_cb *)hook->callback) (word, word_eol, hook->userdata); + break; + default: /*case HOOK_PRINT:*/ + ret = ((xchat_print_cb *)hook->callback) (word, hook->userdata); + break; + } + + if ((ret & XCHAT_EAT_XCHAT) && (ret & XCHAT_EAT_PLUGIN)) + { + eat = 1; + goto xit; + } + if (ret & XCHAT_EAT_PLUGIN) + goto xit; /* stop running plugins */ + if (ret & XCHAT_EAT_XCHAT) + eat = 1; /* eventually we'll return 1, but continue running plugins */ + + list = next; + } + +xit: + /* really remove deleted hooks now */ + list = hook_list; + while (list) + { + hook = list->data; + next = list->next; + if (hook->type == HOOK_DELETED) + { + hook_list = g_slist_remove (hook_list, hook); + free (hook); + } + list = next; + } + + return eat; +} + +/* execute a plugged in command. Called from outbound.c */ + +int +plugin_emit_command (session *sess, char *name, char *word[], char *word_eol[]) +{ + return plugin_hook_run (sess, name, word, word_eol, HOOK_COMMAND); +} + +/* got a server PRIVMSG, NOTICE, numeric etc... */ + +int +plugin_emit_server (session *sess, char *name, char *word[], char *word_eol[]) +{ + return plugin_hook_run (sess, name, word, word_eol, HOOK_SERVER); +} + +/* see if any plugins are interested in this print event */ + +int +plugin_emit_print (session *sess, char *word[]) +{ + return plugin_hook_run (sess, word[0], word, NULL, HOOK_PRINT); +} + +int +plugin_emit_dummy_print (session *sess, char *name) +{ + char *word[32]; + int i; + + word[0] = name; + for (i = 1; i < 32; i++) + word[i] = "\000"; + + return plugin_hook_run (sess, name, word, NULL, HOOK_PRINT); +} + +int +plugin_emit_keypress (session *sess, unsigned int state, unsigned int keyval, + int len, char *string) +{ + char *word[PDIWORDS]; + char keyval_str[16]; + char state_str[16]; + char len_str[16]; + int i; + + if (!hook_list) + return 0; + + sprintf (keyval_str, "%u", keyval); + sprintf (state_str, "%u", state); + sprintf (len_str, "%d", len); + + word[0] = "Key Press"; + word[1] = keyval_str; + word[2] = state_str; + word[3] = string; + word[4] = len_str; + for (i = 5; i < PDIWORDS; i++) + word[i] = "\000"; + + return plugin_hook_run (sess, word[0], word, NULL, HOOK_PRINT); +} + +static int +plugin_timeout_cb (xchat_hook *hook) +{ + int ret; + + /* timer_cb's context starts as front-most-tab */ + hook->pl->context = current_sess; + + /* call the plugin's timeout function */ + ret = ((xchat_timer_cb *)hook->callback) (hook->userdata); + + /* the callback might have already unhooked it! */ + if (!g_slist_find (hook_list, hook) || hook->type == HOOK_DELETED) + return 0; + + if (ret == 0) + { + hook->tag = 0; /* avoid fe_timeout_remove, returning 0 is enough! */ + xchat_unhook (hook->pl, hook); + } + + return ret; +} + +/* insert a hook into hook_list according to its priority */ + +static void +plugin_insert_hook (xchat_hook *new_hook) +{ + GSList *list; + xchat_hook *hook; + + list = hook_list; + while (list) + { + hook = list->data; + if (hook->type == new_hook->type && hook->pri <= new_hook->pri) + { + hook_list = g_slist_insert_before (hook_list, list, new_hook); + return; + } + list = list->next; + } + + hook_list = g_slist_append (hook_list, new_hook); +} + +static gboolean +plugin_fd_cb (GIOChannel *source, GIOCondition condition, xchat_hook *hook) +{ + int flags = 0, ret; + typedef int (xchat_fd_cb2) (int fd, int flags, void *user_data, GIOChannel *); + + if (condition & G_IO_IN) + flags |= XCHAT_FD_READ; + if (condition & G_IO_OUT) + flags |= XCHAT_FD_WRITE; + if (condition & G_IO_PRI) + flags |= XCHAT_FD_EXCEPTION; + + ret = ((xchat_fd_cb2 *)hook->callback) (hook->pri, flags, hook->userdata, source); + + /* the callback might have already unhooked it! */ + if (!g_slist_find (hook_list, hook) || hook->type == HOOK_DELETED) + return 0; + + if (ret == 0) + { + hook->tag = 0; /* avoid fe_input_remove, returning 0 is enough! */ + xchat_unhook (hook->pl, hook); + } + + return ret; +} + +/* allocate and add a hook to our list. Used for all 4 types */ + +static xchat_hook * +plugin_add_hook (xchat_plugin *pl, int type, int pri, const char *name, + const char *help_text, void *callb, int timeout, void *userdata) +{ + xchat_hook *hook; + + hook = malloc (sizeof (xchat_hook)); + memset (hook, 0, sizeof (xchat_hook)); + + hook->type = type; + hook->pri = pri; + if (name) + hook->name = strdup (name); + if (help_text) + hook->help_text = strdup (help_text); + hook->callback = callb; + hook->pl = pl; + hook->userdata = userdata; + + /* insert it into the linked list */ + plugin_insert_hook (hook); + + if (type == HOOK_TIMER) + hook->tag = fe_timeout_add (timeout, plugin_timeout_cb, hook); + + return hook; +} + +GList * +plugin_command_list(GList *tmp_list) +{ + xchat_hook *hook; + GSList *list = hook_list; + + while (list) + { + hook = list->data; + if (hook->type == HOOK_COMMAND) + tmp_list = g_list_prepend(tmp_list, hook->name); + list = list->next; + } + return tmp_list; +} + +void +plugin_command_foreach (session *sess, void *userdata, + void (*cb) (session *sess, void *userdata, char *name, char *help)) +{ + GSList *list; + xchat_hook *hook; + + list = hook_list; + while (list) + { + hook = list->data; + if (hook->type == HOOK_COMMAND && hook->name[0]) + { + cb (sess, userdata, hook->name, hook->help_text); + } + list = list->next; + } +} + +int +plugin_show_help (session *sess, char *cmd) +{ + GSList *list; + xchat_hook *hook; + + list = plugin_hook_find (hook_list, HOOK_COMMAND, cmd); + if (list) + { + hook = list->data; + if (hook->help_text) + { + PrintText (sess, hook->help_text); + return 1; + } + } + + return 0; +} + +/* ========================================================= */ +/* ===== these are the functions plugins actually call ===== */ +/* ========================================================= */ + +void * +xchat_unhook (xchat_plugin *ph, xchat_hook *hook) +{ + /* perl.c trips this */ + if (!g_slist_find (hook_list, hook) || hook->type == HOOK_DELETED) + return NULL; + + if (hook->type == HOOK_TIMER && hook->tag != 0) + fe_timeout_remove (hook->tag); + + if (hook->type == HOOK_FD && hook->tag != 0) + fe_input_remove (hook->tag); + + hook->type = HOOK_DELETED; /* expunge later */ + + if (hook->name) + free (hook->name); /* NULL for timers & fds */ + if (hook->help_text) + free (hook->help_text); /* NULL for non-commands */ + + return hook->userdata; +} + +xchat_hook * +xchat_hook_command (xchat_plugin *ph, const char *name, int pri, + xchat_cmd_cb *callb, const char *help_text, void *userdata) +{ + return plugin_add_hook (ph, HOOK_COMMAND, pri, name, help_text, callb, 0, + userdata); +} + +xchat_hook * +xchat_hook_server (xchat_plugin *ph, const char *name, int pri, + xchat_serv_cb *callb, void *userdata) +{ + return plugin_add_hook (ph, HOOK_SERVER, pri, name, 0, callb, 0, userdata); +} + +xchat_hook * +xchat_hook_print (xchat_plugin *ph, const char *name, int pri, + xchat_print_cb *callb, void *userdata) +{ + return plugin_add_hook (ph, HOOK_PRINT, pri, name, 0, callb, 0, userdata); +} + +xchat_hook * +xchat_hook_timer (xchat_plugin *ph, int timeout, xchat_timer_cb *callb, + void *userdata) +{ + return plugin_add_hook (ph, HOOK_TIMER, 0, 0, 0, callb, timeout, userdata); +} + +xchat_hook * +xchat_hook_fd (xchat_plugin *ph, int fd, int flags, + xchat_fd_cb *callb, void *userdata) +{ + xchat_hook *hook; + + hook = plugin_add_hook (ph, HOOK_FD, 0, 0, 0, callb, 0, userdata); + hook->pri = fd; + /* plugin hook_fd flags correspond exactly to FIA_* flags (fe.h) */ + hook->tag = fe_input_add (fd, flags, plugin_fd_cb, hook); + + return hook; +} + +void +xchat_print (xchat_plugin *ph, const char *text) +{ + if (!is_session (ph->context)) + { + DEBUG(PrintTextf(0, "%s\txchat_print called without a valid context.\n", ph->name)); + return; + } + + PrintText (ph->context, (char *)text); +} + +void +xchat_printf (xchat_plugin *ph, const char *format, ...) +{ + va_list args; + char *buf; + + va_start (args, format); + buf = g_strdup_vprintf (format, args); + va_end (args); + + xchat_print (ph, buf); + g_free (buf); +} + +void +xchat_command (xchat_plugin *ph, const char *command) +{ + char *conv; + int len = -1; + + if (!is_session (ph->context)) + { + DEBUG(PrintTextf(0, "%s\txchat_command called without a valid context.\n", ph->name)); + return; + } + + /* scripts/plugins continue to send non-UTF8... *sigh* */ + conv = text_validate ((char **)&command, &len); + handle_command (ph->context, (char *)command, FALSE); + g_free (conv); +} + +void +xchat_commandf (xchat_plugin *ph, const char *format, ...) +{ + va_list args; + char *buf; + + va_start (args, format); + buf = g_strdup_vprintf (format, args); + va_end (args); + + xchat_command (ph, buf); + g_free (buf); +} + +int +xchat_nickcmp (xchat_plugin *ph, const char *s1, const char *s2) +{ + return ((session *)ph->context)->server->p_cmp (s1, s2); +} + +xchat_context * +xchat_get_context (xchat_plugin *ph) +{ + return ph->context; +} + +int +xchat_set_context (xchat_plugin *ph, xchat_context *context) +{ + if (is_session (context)) + { + ph->context = context; + return 1; + } + return 0; +} + +xchat_context * +xchat_find_context (xchat_plugin *ph, const char *servname, const char *channel) +{ + GSList *slist, *clist, *sessions = NULL; + server *serv; + session *sess; + char *netname; + + if (servname == NULL && channel == NULL) + return current_sess; + + slist = serv_list; + while (slist) + { + serv = slist->data; + netname = server_get_network (serv, TRUE); + + if (servname == NULL || + rfc_casecmp (servname, serv->servername) == 0 || + strcasecmp (servname, serv->hostname) == 0 || + strcasecmp (servname, netname) == 0) + { + if (channel == NULL) + return serv->front_session; + + clist = sess_list; + while (clist) + { + sess = clist->data; + if (sess->server == serv) + { + if (rfc_casecmp (channel, sess->channel) == 0) + { + if (sess->server == ph->context->server) + { + g_slist_free (sessions); + return sess; + } else + { + sessions = g_slist_prepend (sessions, sess); + } + } + } + clist = clist->next; + } + } + slist = slist->next; + } + + if (sessions) + { + sessions = g_slist_reverse (sessions); + sess = sessions->data; + g_slist_free (sessions); + return sess; + } + + return NULL; +} + +const char * +xchat_get_info (xchat_plugin *ph, const char *id) +{ + session *sess; + guint32 hash; + + /* 1234567890 */ + if (!strncmp (id, "event_text", 10)) + { + char *e = (char *)id + 10; + if (*e == ' ') e++; /* 2.8.0 only worked without a space */ + return text_find_format_string (e); + } + + hash = str_hash (id); + /* do the session independant ones first */ + switch (hash) + { + case 0x325acab5: /* libdirfs */ + return XCHATLIBDIR; + + case 0x14f51cd8: /* version */ + return PACKAGE_VERSION; + + case 0xdd9b1abd: /* xchatdir */ + return get_xdir_utf8 (); + + case 0xe33f6c4a: /* xchatdirfs */ + return get_xdir_fs (); + } + + sess = ph->context; + if (!is_session (sess)) + { + DEBUG(PrintTextf(0, "%s\txchat_get_info called without a valid context.\n", ph->name)); + return NULL; + } + + switch (hash) + { + case 0x2de2ee: /* away */ + if (sess->server->is_away) + return sess->server->last_away_reason; + return NULL; + + case 0x2c0b7d03: /* channel */ + return sess->channel; + + case 0x2c0d614c: /* charset */ + { + const char *locale; + + if (sess->server->encoding) + return sess->server->encoding; + + locale = NULL; + g_get_charset (&locale); + return locale; + } + + case 0x30f5a8: /* host */ + return sess->server->hostname; + + case 0x1c0e99c1: /* inputbox */ + return fe_get_inputbox_contents (sess); + + case 0x633fb30: /* modes */ + return sess->current_modes; + + case 0x6de15a2e: /* network */ + return server_get_network (sess->server, FALSE); + + case 0x339763: /* nick */ + return sess->server->nick; + + case 0x438fdf9: /* nickserv */ + if (sess->server->network) + return ((ircnet *)sess->server->network)->nickserv; + return NULL; + + case 0xca022f43: /* server */ + if (!sess->server->connected) + return NULL; + return sess->server->servername; + + case 0x696cd2f: /* topic */ + return sess->topic; + + case 0x3419f12d: /* gtkwin_ptr */ + return fe_gui_info_ptr (sess, 1); + + case 0x506d600b: /* native win_ptr */ + return fe_gui_info_ptr (sess, 0); + + case 0x6d3431b5: /* win_status */ + switch (fe_gui_info (sess, 0)) /* check window status */ + { + case 0: return "normal"; + case 1: return "active"; + case 2: return "hidden"; + } + return NULL; + } + + return NULL; +} + +int +xchat_get_prefs (xchat_plugin *ph, const char *name, const char **string, int *integer) +{ + int i = 0; + + /* some special run-time info (not really prefs, but may aswell throw it in here) */ + switch (str_hash (name)) + { + case 0xf82136c4: /* state_cursor */ + *integer = fe_get_inputbox_cursor (ph->context); + return 2; + + case 0xd1b: /* id */ + *integer = ph->context->server->id; + return 2; + } + + do + { + if (!strcasecmp (name, vars[i].name)) + { + switch (vars[i].type) + { + case TYPE_STR: + *string = ((char *) &prefs + vars[i].offset); + return 1; + + case TYPE_INT: + *integer = *((int *) &prefs + vars[i].offset); + return 2; + + default: + /*case TYPE_BOOL:*/ + if (*((int *) &prefs + vars[i].offset)) + *integer = 1; + else + *integer = 0; + return 3; + } + } + i++; + } + while (vars[i].name); + + return 0; +} + +xchat_list * +xchat_list_get (xchat_plugin *ph, const char *name) +{ + xchat_list *list; + + list = malloc (sizeof (xchat_list)); + list->pos = NULL; + + switch (str_hash (name)) + { + case 0x556423d0: /* channels */ + list->type = LIST_CHANNELS; + list->next = sess_list; + break; + + case 0x183c4: /* dcc */ + list->type = LIST_DCC; + list->next = dcc_list; + break; + + case 0xb90bfdd2: /* ignore */ + list->type = LIST_IGNORE; + list->next = ignore_list; + break; + + case 0xc2079749: /* notify */ + list->type = LIST_NOTIFY; + list->next = notify_list; + list->head = (void *)ph->context; /* reuse this pointer */ + break; + + case 0x6a68e08: /* users */ + if (is_session (ph->context)) + { + list->type = LIST_USERS; + list->head = list->next = userlist_flat_list (ph->context); + fe_userlist_set_selected (ph->context); + break; + } /* fall through */ + + default: + free (list); + return NULL; + } + + return list; +} + +void +xchat_list_free (xchat_plugin *ph, xchat_list *xlist) +{ + if (xlist->type == LIST_USERS) + g_slist_free (xlist->head); + free (xlist); +} + +int +xchat_list_next (xchat_plugin *ph, xchat_list *xlist) +{ + if (xlist->next == NULL) + return 0; + + xlist->pos = xlist->next; + xlist->next = xlist->pos->next; + + /* NOTIFY LIST: Find the entry which matches the context + of the plugin when list_get was originally called. */ + if (xlist->type == LIST_NOTIFY) + { + xlist->notifyps = notify_find_server_entry (xlist->pos->data, + ((session *)xlist->head)->server); + if (!xlist->notifyps) + return 0; + } + + return 1; +} + +const char * const * +xchat_list_fields (xchat_plugin *ph, const char *name) +{ + static const char * const dcc_fields[] = + { + "iaddress32","icps", "sdestfile","sfile", "snick", "iport", + "ipos", "iposhigh", "iresume", "iresumehigh", "isize", "isizehigh", "istatus", "itype", NULL + }; + static const char * const channels_fields[] = + { + "schannel", "schantypes", "pcontext", "iflags", "iid", "ilag", "imaxmodes", + "snetwork", "snickmodes", "snickprefixes", "iqueue", "sserver", "itype", "iusers", + NULL + }; + static const char * const ignore_fields[] = + { + "iflags", "smask", NULL + }; + static const char * const notify_fields[] = + { + "iflags", "snetworks", "snick", "toff", "ton", "tseen", NULL + }; + static const char * const users_fields[] = + { + "iaway", "shost", "tlasttalk", "snick", "sprefix", "srealname", "iselected", NULL + }; + static const char * const list_of_lists[] = + { + "channels", "dcc", "ignore", "notify", "users", NULL + }; + + switch (str_hash (name)) + { + case 0x556423d0: /* channels */ + return channels_fields; + case 0x183c4: /* dcc */ + return dcc_fields; + case 0xb90bfdd2: /* ignore */ + return ignore_fields; + case 0xc2079749: /* notify */ + return notify_fields; + case 0x6a68e08: /* users */ + return users_fields; + case 0x6236395: /* lists */ + return list_of_lists; + } + + return NULL; +} + +time_t +xchat_list_time (xchat_plugin *ph, xchat_list *xlist, const char *name) +{ + guint32 hash = str_hash (name); + gpointer data; + + switch (xlist->type) + { + case LIST_NOTIFY: + if (!xlist->notifyps) + return (time_t) -1; + switch (hash) + { + case 0x1ad6f: /* off */ + return xlist->notifyps->lastoff; + case 0xddf: /* on */ + return xlist->notifyps->laston; + case 0x35ce7b: /* seen */ + return xlist->notifyps->lastseen; + } + break; + + case LIST_USERS: + data = xlist->pos->data; + switch (hash) + { + case 0xa9118c42: /* lasttalk */ + return ((struct User *)data)->lasttalk; + } + } + + return (time_t) -1; +} + +const char * +xchat_list_str (xchat_plugin *ph, xchat_list *xlist, const char *name) +{ + guint32 hash = str_hash (name); + gpointer data = ph->context; + int type = LIST_CHANNELS; + + /* a NULL xlist is a shortcut to current "channels" context */ + if (xlist) + { + data = xlist->pos->data; + type = xlist->type; + } + + switch (type) + { + case LIST_CHANNELS: + switch (hash) + { + case 0x2c0b7d03: /* channel */ + return ((session *)data)->channel; + case 0x577e0867: /* chantypes */ + return ((session *)data)->server->chantypes; + case 0x38b735af: /* context */ + return data; /* this is a session * */ + case 0x6de15a2e: /* network */ + return server_get_network (((session *)data)->server, FALSE); + case 0x8455e723: /* nickprefixes */ + return ((session *)data)->server->nick_prefixes; + case 0x829689ad: /* nickmodes */ + return ((session *)data)->server->nick_modes; + case 0xca022f43: /* server */ + return ((session *)data)->server->servername; + } + break; + + case LIST_DCC: + switch (hash) + { + case 0x3d9ad31e: /* destfile */ + return ((struct DCC *)data)->destfile; + case 0x2ff57c: /* file */ + return ((struct DCC *)data)->file; + case 0x339763: /* nick */ + return ((struct DCC *)data)->nick; + } + break; + + case LIST_IGNORE: + switch (hash) + { + case 0x3306ec: /* mask */ + return ((struct ignore *)data)->mask; + } + break; + + case LIST_NOTIFY: + switch (hash) + { + case 0x4e49ec05: /* networks */ + return ((struct notify *)data)->networks; + case 0x339763: /* nick */ + return ((struct notify *)data)->name; + } + break; + + case LIST_USERS: + switch (hash) + { + case 0x339763: /* nick */ + return ((struct User *)data)->nick; + case 0x30f5a8: /* host */ + return ((struct User *)data)->hostname; + case 0xc594b292: /* prefix */ + return ((struct User *)data)->prefix; + case 0xccc6d529: /* realname */ + return ((struct User *)data)->realname; + } + break; + } + + return NULL; +} + +int +xchat_list_int (xchat_plugin *ph, xchat_list *xlist, const char *name) +{ + guint32 hash = str_hash (name); + gpointer data = ph->context; + int tmp, type = LIST_CHANNELS; + + /* a NULL xlist is a shortcut to current "channels" context */ + if (xlist) + { + data = xlist->pos->data; + type = xlist->type; + } + + switch (type) + { + case LIST_DCC: + switch (hash) + { + case 0x34207553: /* address32 */ + return ((struct DCC *)data)->addr; + case 0x181a6: /* cps */ + return ((struct DCC *)data)->cps; + case 0x349881: /* port */ + return ((struct DCC *)data)->port; + case 0x1b254: /* pos */ + return ((struct DCC *)data)->pos & 0xffffffff; + case 0xe8a945f6: /* poshigh */ + return (((struct DCC *)data)->pos >> 32) & 0xffffffff; + case 0xc84dc82d: /* resume */ + return ((struct DCC *)data)->resumable & 0xffffffff; + case 0xded4c74f: /* resumehigh */ + return (((struct DCC *)data)->resumable >> 32) & 0xffffffff; + case 0x35e001: /* size */ + return ((struct DCC *)data)->size & 0xffffffff; + case 0x3284d523: /* sizehigh */ + return (((struct DCC *)data)->size >> 32) & 0xffffffff; + case 0xcacdcff2: /* status */ + return ((struct DCC *)data)->dccstat; + case 0x368f3a: /* type */ + return ((struct DCC *)data)->type; + } + break; + + case LIST_IGNORE: + switch (hash) + { + case 0x5cfee87: /* flags */ + return ((struct ignore *)data)->type; + } + break; + + case LIST_CHANNELS: + switch (hash) + { + case 0xd1b: /* id */ + return ((struct session *)data)->server->id; + case 0x5cfee87: /* flags */ + tmp = ((struct session *)data)->alert_taskbar; /* bit 10 */ + tmp <<= 1; + tmp |= ((struct session *)data)->alert_tray; /* 9 */ + tmp <<= 1; + tmp |= ((struct session *)data)->alert_beep; /* 8 */ + tmp <<= 1; + /*tmp |= ((struct session *)data)->color_paste;*/ /* 7 */ + tmp <<= 1; + tmp |= ((struct session *)data)->text_hidejoinpart; /* 6 */ + tmp <<= 1; + tmp |= ((struct session *)data)->server->have_idmsg; /* 5 */ + tmp <<= 1; + tmp |= ((struct session *)data)->server->have_whox; /* 4 */ + tmp <<= 1; + tmp |= ((struct session *)data)->server->end_of_motd;/* 3 */ + tmp <<= 1; + tmp |= ((struct session *)data)->server->is_away; /* 2 */ + tmp <<= 1; + tmp |= ((struct session *)data)->server->connecting; /* 1 */ + tmp <<= 1; + tmp |= ((struct session *)data)->server->connected; /* 0 */ + return tmp; + case 0x1a192: /* lag */ + return ((struct session *)data)->server->lag; + case 0x1916144c: /* maxmodes */ + return ((struct session *)data)->server->modes_per_line; + case 0x66f1911: /* queue */ + return ((struct session *)data)->server->sendq_len; + case 0x368f3a: /* type */ + return ((struct session *)data)->type; + case 0x6a68e08: /* users */ + return ((struct session *)data)->total; + } + break; + + case LIST_NOTIFY: + if (!xlist->notifyps) + return -1; + switch (hash) + { + case 0x5cfee87: /* flags */ + return xlist->notifyps->ison; + } + + case LIST_USERS: + switch (hash) + { + case 0x2de2ee: /* away */ + return ((struct User *)data)->away; + case 0x4705f29b: /* selected */ + return ((struct User *)data)->selected; + } + break; + + } + + return -1; +} + +void * +xchat_plugingui_add (xchat_plugin *ph, const char *filename, + const char *name, const char *desc, + const char *version, char *reserved) +{ +#ifdef USE_PLUGIN + ph = plugin_list_add (NULL, strdup (filename), strdup (name), strdup (desc), + strdup (version), NULL, NULL, TRUE, TRUE); + fe_pluginlist_update (); +#endif + + return ph; +} + +void +xchat_plugingui_remove (xchat_plugin *ph, void *handle) +{ +#ifdef USE_PLUGIN + plugin_free (handle, FALSE, FALSE); +#endif +} + +int +xchat_emit_print (xchat_plugin *ph, const char *event_name, ...) +{ + va_list args; + /* currently only 4 because no events use more than 4. + This can be easily expanded without breaking the API. */ + char *argv[4] = {NULL, NULL, NULL, NULL}; + int i = 0; + + va_start (args, event_name); + while (1) + { + argv[i] = va_arg (args, char *); + if (!argv[i]) + break; + i++; + if (i >= 4) + break; + } + + i = text_emit_by_name ((char *)event_name, ph->context, argv[0], argv[1], + argv[2], argv[3]); + va_end (args); + + return i; +} + +char * +xchat_gettext (xchat_plugin *ph, const char *msgid) +{ + /* so that plugins can use xchat's internal gettext strings. */ + /* e.g. The EXEC plugin uses this on Windows. */ + return _(msgid); +} + +void +xchat_send_modes (xchat_plugin *ph, const char **targets, int ntargets, int modes_per_line, char sign, char mode) +{ + char tbuf[514]; /* modes.c needs 512 + null */ + + send_channel_modes (ph->context, tbuf, (char **)targets, 0, ntargets, sign, mode, modes_per_line); +} + +char * +xchat_strip (xchat_plugin *ph, const char *str, int len, int flags) +{ + return strip_color ((char *)str, len, flags); +} + +void +xchat_free (xchat_plugin *ph, void *ptr) +{ + g_free (ptr); +} diff --git a/src/common/plugin.h b/src/common/plugin.h new file mode 100644 index 00000000..b0c89d1b --- /dev/null +++ b/src/common/plugin.h @@ -0,0 +1,132 @@ +#ifndef XCHAT_COMMONPLUGIN_H +#define XCHAT_COMMONPLUGIN_H + +#ifdef PLUGIN_C +struct _xchat_plugin +{ + /* Keep these insync with xchat-plugin.h */ + /* !!don't change the order, to keep binary compat!! */ + xchat_hook *(*xchat_hook_command) (xchat_plugin *ph, + const char *name, + int pri, + int (*callback) (char *word[], char *word_eol[], void *user_data), + const char *help_text, + void *userdata); + xchat_hook *(*xchat_hook_server) (xchat_plugin *ph, + const char *name, + int pri, + int (*callback) (char *word[], char *word_eol[], void *user_data), + void *userdata); + xchat_hook *(*xchat_hook_print) (xchat_plugin *ph, + const char *name, + int pri, + int (*callback) (char *word[], void *user_data), + void *userdata); + xchat_hook *(*xchat_hook_timer) (xchat_plugin *ph, + int timeout, + int (*callback) (void *user_data), + void *userdata); + xchat_hook *(*xchat_hook_fd) (xchat_plugin *ph, + int fd, + int flags, + int (*callback) (int fd, int flags, void *user_data), + void *userdata); + void *(*xchat_unhook) (xchat_plugin *ph, + xchat_hook *hook); + void (*xchat_print) (xchat_plugin *ph, + const char *text); + void (*xchat_printf) (xchat_plugin *ph, + const char *format, ...); + void (*xchat_command) (xchat_plugin *ph, + const char *command); + void (*xchat_commandf) (xchat_plugin *ph, + const char *format, ...); + int (*xchat_nickcmp) (xchat_plugin *ph, + const char *s1, + const char *s2); + int (*xchat_set_context) (xchat_plugin *ph, + xchat_context *ctx); + xchat_context *(*xchat_find_context) (xchat_plugin *ph, + const char *servname, + const char *channel); + xchat_context *(*xchat_get_context) (xchat_plugin *ph); + const char *(*xchat_get_info) (xchat_plugin *ph, + const char *id); + int (*xchat_get_prefs) (xchat_plugin *ph, + const char *name, + const char **string, + int *integer); + xchat_list * (*xchat_list_get) (xchat_plugin *ph, + const char *name); + void (*xchat_list_free) (xchat_plugin *ph, + xchat_list *xlist); + const char * const * (*xchat_list_fields) (xchat_plugin *ph, + const char *name); + int (*xchat_list_next) (xchat_plugin *ph, + xchat_list *xlist); + const char * (*xchat_list_str) (xchat_plugin *ph, + xchat_list *xlist, + const char *name); + int (*xchat_list_int) (xchat_plugin *ph, + xchat_list *xlist, + const char *name); + void * (*xchat_plugingui_add) (xchat_plugin *ph, + const char *filename, + const char *name, + const char *desc, + const char *version, + char *reserved); + void (*xchat_plugingui_remove) (xchat_plugin *ph, + void *handle); + int (*xchat_emit_print) (xchat_plugin *ph, + const char *event_name, ...); + void *(*xchat_read_fd) (xchat_plugin *ph); + time_t (*xchat_list_time) (xchat_plugin *ph, + xchat_list *xlist, + const char *name); + char *(*xchat_gettext) (xchat_plugin *ph, + const char *msgid); + void (*xchat_send_modes) (xchat_plugin *ph, + const char **targets, + int ntargets, + int modes_per_line, + char sign, + char mode); + char *(*xchat_strip) (xchat_plugin *ph, + const char *str, + int len, + int flags); + void (*xchat_free) (xchat_plugin *ph, + void *ptr); + void *(*xchat_dummy4) (xchat_plugin *ph); + void *(*xchat_dummy3) (xchat_plugin *ph); + void *(*xchat_dummy2) (xchat_plugin *ph); + void *(*xchat_dummy1) (xchat_plugin *ph); + /* PRIVATE FIELDS! */ + void *handle; /* from dlopen */ + char *filename; /* loaded from */ + char *name; + char *desc; + char *version; + session *context; + void *deinit_callback; /* pointer to xchat_plugin_deinit */ + unsigned int fake:1; /* fake plugin. Added by xchat_plugingui_add() */ + unsigned int free_strings:1; /* free name,desc,version? */ +}; +#endif + +char *plugin_load (session *sess, char *filename, char *arg); +void plugin_add (session *sess, char *filename, void *handle, void *init_func, void *deinit_func, char *arg, int fake); +int plugin_kill (char *name, int by_filename); +void plugin_kill_all (void); +void plugin_auto_load (session *sess); +int plugin_emit_command (session *sess, char *name, char *word[], char *word_eol[]); +int plugin_emit_server (session *sess, char *name, char *word[], char *word_eol[]); +int plugin_emit_print (session *sess, char *word[]); +int plugin_emit_dummy_print (session *sess, char *name); +int plugin_emit_keypress (session *sess, unsigned int state, unsigned int keyval, int len, char *string); +GList* plugin_command_list(GList *tmp_list); +int plugin_show_help (session *sess, char *cmd); +void plugin_command_foreach (session *sess, void *userdata, void (*cb) (session *sess, void *userdata, char *name, char *usage)); + +#endif diff --git a/src/common/proto-irc.c b/src/common/proto-irc.c new file mode 100644 index 00000000..f1df6ccd --- /dev/null +++ b/src/common/proto-irc.c @@ -0,0 +1,1260 @@ +/* X-Chat + * Copyright (C) 2002 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +/* IRC RFC1459(+commonly used extensions) protocol implementation */ + +#include <unistd.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <stdarg.h> + +#include "xchat.h" +#include "ctcp.h" +#include "fe.h" +#include "ignore.h" +#include "inbound.h" +#include "modes.h" +#include "notify.h" +#include "plugin.h" +#include "server.h" +#include "text.h" +#include "outbound.h" +#include "util.h" +#include "xchatc.h" + + +static void +irc_login (server *serv, char *user, char *realname) +{ + if (serv->password[0]) + tcp_sendf (serv, "PASS %s\r\n", serv->password); + + tcp_sendf (serv, + "NICK %s\r\n" + "USER %s %s %s :%s\r\n", + serv->nick, user, user, serv->servername, realname); +} + +static void +irc_nickserv (server *serv, char *cmd, char *arg1, char *arg2, char *arg3) +{ + /* are all ircd authors idiots? */ + switch (serv->nickservtype) + { + case 0: + tcp_sendf (serv, "PRIVMSG NICKSERV :%s %s%s%s\r\n", cmd, arg1, arg2, arg3); + break; + case 1: + tcp_sendf (serv, "NICKSERV %s %s%s%s\r\n", cmd, arg1, arg2, arg3); + break; + case 2: + tcp_sendf (serv, "NS %s %s%s%s\r\n", cmd, arg1, arg2, arg3); + break; + case 3: + tcp_sendf (serv, "PRIVMSG NS :%s %s%s%s\r\n", cmd, arg1, arg2, arg3); + break; + case 4: + /* why couldn't QuakeNet implement one of the existing ones? */ + tcp_sendf (serv, "AUTH %s%s%s\r\n", cmd, arg1, arg2, arg3); + } +} + +static void +irc_ns_identify (server *serv, char *pass) +{ + irc_nickserv (serv, "IDENTIFY", pass, "", ""); +} + +static void +irc_ns_ghost (server *serv, char *usname, char *pass) +{ + if (serv->nickservtype != 4) + irc_nickserv (serv, "GHOST", usname, " ", pass); +} + +static void +irc_join (server *serv, char *channel, char *key) +{ + if (key[0]) + tcp_sendf (serv, "JOIN %s %s\r\n", channel, key); + else + tcp_sendf (serv, "JOIN %s\r\n", channel); +} + +static void +irc_join_list_flush (server *serv, GString *c, GString *k) +{ + char *chanstr, *keystr; + + chanstr = g_string_free (c, FALSE); + keystr = g_string_free (k, FALSE); + if (chanstr[0]) + { + if (keystr[0]) + tcp_sendf (serv, "JOIN %s %s\r\n", chanstr, keystr); + else + tcp_sendf (serv, "JOIN %s\r\n", chanstr); + } + g_free (chanstr); + g_free (keystr); +} + +/* join a whole list of channels & keys, split to multiple lines + * to get around 512 limit */ + +static void +irc_join_list (server *serv, GSList *channels, GSList *keys) +{ + GSList *clist; + GSList *klist; + GString *c = g_string_new (NULL); + GString *k = g_string_new (NULL); + int len; + int add; + int i, j; + + i = j = 0; + len = 9; /* "JOIN<space><space>\r\n" */ + clist = channels; + klist = keys; + + while (clist) + { + /* measure how many bytes this channel would add... */ + if (1) + { + add = strlen (clist->data); + if (i != 0) + add++; /* comma */ + } + + if (klist->data) + { + add += strlen (klist->data); + } + else + { + add++; /* 'x' filler */ + } + + if (j != 0) + add++; /* comma */ + + /* too big? dump buffer and start a fresh one */ + if (len + add > 512) + { + irc_join_list_flush (serv, c, k); + + c = g_string_new (NULL); + k = g_string_new (NULL); + i = j = 0; + len = 9; + } + + /* now actually add it to our GStrings */ + if (1) + { + add = strlen (clist->data); + if (i != 0) + { + add++; + g_string_append_c (c, ','); + } + g_string_append (c, clist->data); + i++; + } + + if (klist->data) + { + add += strlen (klist->data); + if (j != 0) + { + add++; + g_string_append_c (k, ','); + } + g_string_append (k, klist->data); + j++; + } + else + { + add++; + if (j != 0) + { + add++; + g_string_append_c (k, ','); + } + g_string_append_c (k, 'x'); + j++; + } + + len += add; + + klist = klist->next; + clist = clist->next; + } + + irc_join_list_flush (serv, c, k); +} + +static void +irc_part (server *serv, char *channel, char *reason) +{ + if (reason[0]) + tcp_sendf (serv, "PART %s :%s\r\n", channel, reason); + else + tcp_sendf (serv, "PART %s\r\n", channel); +} + +static void +irc_quit (server *serv, char *reason) +{ + if (reason[0]) + tcp_sendf (serv, "QUIT :%s\r\n", reason); + else + tcp_send_len (serv, "QUIT\r\n", 6); +} + +static void +irc_set_back (server *serv) +{ + tcp_send_len (serv, "AWAY\r\n", 6); +} + +static void +irc_set_away (server *serv, char *reason) +{ + if (reason) + { + if (!reason[0]) + reason = " "; + } + else + { + reason = " "; + } + + tcp_sendf (serv, "AWAY :%s\r\n", reason); +} + +static void +irc_ctcp (server *serv, char *to, char *msg) +{ + tcp_sendf (serv, "PRIVMSG %s :\001%s\001\r\n", to, msg); +} + +static void +irc_nctcp (server *serv, char *to, char *msg) +{ + tcp_sendf (serv, "NOTICE %s :\001%s\001\r\n", to, msg); +} + +static void +irc_cycle (server *serv, char *channel, char *key) +{ + tcp_sendf (serv, "PART %s\r\nJOIN %s %s\r\n", channel, channel, key); +} + +static void +irc_kick (server *serv, char *channel, char *nick, char *reason) +{ + if (reason[0]) + tcp_sendf (serv, "KICK %s %s :%s\r\n", channel, nick, reason); + else + tcp_sendf (serv, "KICK %s %s\r\n", channel, nick); +} + +static void +irc_invite (server *serv, char *channel, char *nick) +{ + tcp_sendf (serv, "INVITE %s %s\r\n", nick, channel); +} + +static void +irc_mode (server *serv, char *target, char *mode) +{ + tcp_sendf (serv, "MODE %s %s\r\n", target, mode); +} + +/* find channel info when joined */ + +static void +irc_join_info (server *serv, char *channel) +{ + tcp_sendf (serv, "MODE %s\r\n", channel); +} + +/* initiate userlist retreival */ + +static void +irc_user_list (server *serv, char *channel) +{ + tcp_sendf (serv, "WHO %s\r\n", channel); +} + +/* userhost */ + +static void +irc_userhost (server *serv, char *nick) +{ + tcp_sendf (serv, "USERHOST %s\r\n", nick); +} + +static void +irc_away_status (server *serv, char *channel) +{ + if (serv->have_whox) + tcp_sendf (serv, "WHO %s %%ctnf,152\r\n", channel); + else + tcp_sendf (serv, "WHO %s\r\n", channel); +} + +/*static void +irc_get_ip (server *serv, char *nick) +{ + tcp_sendf (serv, "WHO %s\r\n", nick); +}*/ + + +/* + * Command: WHOIS + * Parameters: [<server>] <nickmask>[,<nickmask>[,...]] + */ +static void +irc_user_whois (server *serv, char *nicks) +{ + tcp_sendf (serv, "WHOIS %s\r\n", nicks); +} + +static void +irc_message (server *serv, char *channel, char *text) +{ + tcp_sendf (serv, "PRIVMSG %s :%s\r\n", channel, text); +} + +static void +irc_action (server *serv, char *channel, char *act) +{ + tcp_sendf (serv, "PRIVMSG %s :\001ACTION %s\001\r\n", channel, act); +} + +static void +irc_notice (server *serv, char *channel, char *text) +{ + tcp_sendf (serv, "NOTICE %s :%s\r\n", channel, text); +} + +static void +irc_topic (server *serv, char *channel, char *topic) +{ + if (!topic) + tcp_sendf (serv, "TOPIC %s :\r\n", channel); + else if (topic[0]) + tcp_sendf (serv, "TOPIC %s :%s\r\n", channel, topic); + else + tcp_sendf (serv, "TOPIC %s\r\n", channel); +} + +static void +irc_list_channels (server *serv, char *arg, int min_users) +{ + if (arg[0]) + { + tcp_sendf (serv, "LIST %s\r\n", arg); + return; + } + + if (serv->use_listargs) + tcp_sendf (serv, "LIST >%d,<10000\r\n", min_users - 1); + else + tcp_send_len (serv, "LIST\r\n", 6); +} + +static void +irc_names (server *serv, char *channel) +{ + tcp_sendf (serv, "NAMES %s\r\n", channel); +} + +static void +irc_change_nick (server *serv, char *new_nick) +{ + tcp_sendf (serv, "NICK %s\r\n", new_nick); +} + +static void +irc_ping (server *serv, char *to, char *timestring) +{ + if (*to) + tcp_sendf (serv, "PRIVMSG %s :\001PING %s\001\r\n", to, timestring); + else + tcp_sendf (serv, "PING %s\r\n", timestring); +} + +static int +irc_raw (server *serv, char *raw) +{ + int len; + char tbuf[4096]; + if (*raw) + { + len = strlen (raw); + if (len < sizeof (tbuf) - 3) + { + len = snprintf (tbuf, sizeof (tbuf), "%s\r\n", raw); + tcp_send_len (serv, tbuf, len); + } else + { + tcp_send_len (serv, raw, len); + tcp_send_len (serv, "\r\n", 2); + } + return TRUE; + } + return FALSE; +} + +/* ============================================================== */ +/* ======================= IRC INPUT ============================ */ +/* ============================================================== */ + + +static void +channel_date (session *sess, char *chan, char *timestr) +{ + time_t timestamp = (time_t) atol (timestr); + char *tim = ctime (×tamp); + tim[24] = 0; /* get rid of the \n */ + EMIT_SIGNAL (XP_TE_CHANDATE, sess, chan, tim, NULL, NULL, 0); +} + +static void +process_numeric (session * sess, int n, + char *word[], char *word_eol[], char *text) +{ + server *serv = sess->server; + /* show whois is the server tab */ + session *whois_sess = serv->server_session; + + /* unless this setting is on */ + if (prefs.irc_whois_front) + whois_sess = serv->front_session; + + switch (n) + { + case 1: + inbound_login_start (sess, word[3], word[1]); + /* if network is PTnet then you must get your IP address + from "001" server message */ + if ((strncmp(word[7], "PTnet", 5) == 0) && + (strncmp(word[8], "IRC", 3) == 0) && + (strncmp(word[9], "Network", 7) == 0) && + (strrchr(word[10], '@') != NULL)) + { + serv->use_who = FALSE; + if (prefs.ip_from_server) + inbound_foundip (sess, strrchr(word[10], '@')+1); + } + + /* use /NICKSERV */ + if (strcasecmp (word[7], "DALnet") == 0 || + strcasecmp (word[7], "BRASnet") == 0) + serv->nickservtype = 1; + + /* use /NS */ + else if (strcasecmp (word[7], "FreeNode") == 0) + serv->nickservtype = 2; + + goto def; + + case 4: /* check the ircd type */ + serv->use_listargs = FALSE; + serv->modes_per_line = 3; /* default to IRC RFC */ + if (strncmp (word[5], "bahamut", 7) == 0) /* DALNet */ + { + serv->use_listargs = TRUE; /* use the /list args */ + } else if (strncmp (word[5], "u2.10.", 6) == 0) /* Undernet */ + { + serv->use_listargs = TRUE; /* use the /list args */ + serv->modes_per_line = 6; /* allow 6 modes per line */ + } else if (strncmp (word[5], "glx2", 4) == 0) + { + serv->use_listargs = TRUE; /* use the /list args */ + } + goto def; + + case 5: + inbound_005 (serv, word); + goto def; + + case 263: /*Server load is temporarily too heavy */ + if (fe_is_chanwindow (sess->server)) + { + fe_chan_list_end (sess->server); + fe_message (word_eol[5] + 1, FE_MSG_ERROR); + } + goto def; + + case 290: /* CAPAB reply */ + if (strstr (word_eol[1], "IDENTIFY-MSG")) + { + serv->have_idmsg = TRUE; + break; + } + goto def; + + case 301: + inbound_away (serv, word[4], + (word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5]); + break; + + case 302: + if (serv->skip_next_userhost) + { + char *eq = strchr (word[4], '='); + if (eq) + { + *eq = 0; + if (!serv->p_cmp (word[4] + 1, serv->nick)) + { + char *at = strrchr (eq + 1, '@'); + if (at) + inbound_foundip (sess, at + 1); + } + } + + serv->skip_next_userhost = FALSE; + break; + } + else goto def; + + case 303: + word[4]++; + notify_markonline (serv, word); + break; + + case 305: + inbound_uback (serv); + goto def; + + case 306: + inbound_uaway (serv); + goto def; + + case 312: + if (!serv->skip_next_whois) + EMIT_SIGNAL (XP_TE_WHOIS3, whois_sess, word[4], word_eol[5], NULL, NULL, 0); + else + inbound_user_info (sess, NULL, NULL, NULL, word[5], word[4], NULL, 0xff); + break; + + case 311: /* WHOIS 1st line */ + serv->inside_whois = 1; + inbound_user_info_start (sess, word[4]); + if (!serv->skip_next_whois) + EMIT_SIGNAL (XP_TE_WHOIS1, whois_sess, word[4], word[5], + word[6], word_eol[8] + 1, 0); + else + inbound_user_info (sess, NULL, word[5], word[6], NULL, word[4], + word_eol[8][0] == ':' ? word_eol[8] + 1 : word_eol[8], 0xff); + break; + + case 314: /* WHOWAS */ + inbound_user_info_start (sess, word[4]); + EMIT_SIGNAL (XP_TE_WHOIS1, whois_sess, word[4], word[5], + word[6], word_eol[8] + 1, 0); + break; + + case 317: + if (!serv->skip_next_whois) + { + time_t timestamp = (time_t) atol (word[6]); + long idle = atol (word[5]); + char *tim; + char outbuf[64]; + + snprintf (outbuf, sizeof (outbuf), + "%02ld:%02ld:%02ld", idle / 3600, (idle / 60) % 60, + idle % 60); + if (timestamp == 0) + EMIT_SIGNAL (XP_TE_WHOIS4, whois_sess, word[4], + outbuf, NULL, NULL, 0); + else + { + tim = ctime (×tamp); + tim[19] = 0; /* get rid of the \n */ + EMIT_SIGNAL (XP_TE_WHOIS4T, whois_sess, word[4], + outbuf, tim, NULL, 0); + } + } + break; + + case 318: /* END OF WHOIS */ + if (!serv->skip_next_whois) + EMIT_SIGNAL (XP_TE_WHOIS6, whois_sess, word[4], NULL, + NULL, NULL, 0); + serv->skip_next_whois = 0; + serv->inside_whois = 0; + break; + + case 313: + case 319: + if (!serv->skip_next_whois) + EMIT_SIGNAL (XP_TE_WHOIS2, whois_sess, word[4], + word_eol[5] + 1, NULL, NULL, 0); + break; + + case 307: /* dalnet version */ + case 320: /* :is an identified user */ + if (!serv->skip_next_whois) + EMIT_SIGNAL (XP_TE_WHOIS_ID, whois_sess, word[4], + word_eol[5] + 1, NULL, NULL, 0); + break; + + case 321: + if (!fe_is_chanwindow (sess->server)) + EMIT_SIGNAL (XP_TE_CHANLISTHEAD, serv->server_session, NULL, NULL, NULL, NULL, 0); + break; + + case 322: + if (fe_is_chanwindow (sess->server)) + { + fe_add_chan_list (sess->server, word[4], word[5], word_eol[6] + 1); + } else + { + PrintTextf (serv->server_session, "%-16s %-7d %s\017\n", + word[4], atoi (word[5]), word_eol[6] + 1); + } + break; + + case 323: + if (!fe_is_chanwindow (sess->server)) + EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1], word[2], NULL, 0); + else + fe_chan_list_end (sess->server); + break; + + case 324: + sess = find_channel (serv, word[4]); + if (!sess) + sess = serv->server_session; + if (sess->ignore_mode) + sess->ignore_mode = FALSE; + else + EMIT_SIGNAL (XP_TE_CHANMODES, sess, word[4], word_eol[5], + NULL, NULL, 0); + fe_update_mode_buttons (sess, 't', '-'); + fe_update_mode_buttons (sess, 'n', '-'); + fe_update_mode_buttons (sess, 's', '-'); + fe_update_mode_buttons (sess, 'i', '-'); + fe_update_mode_buttons (sess, 'p', '-'); + fe_update_mode_buttons (sess, 'm', '-'); + fe_update_mode_buttons (sess, 'l', '-'); + fe_update_mode_buttons (sess, 'k', '-'); + handle_mode (serv, word, word_eol, "", TRUE); + break; + + case 329: + sess = find_channel (serv, word[4]); + if (sess) + { + if (sess->ignore_date) + sess->ignore_date = FALSE; + else + channel_date (sess, word[4], word[5]); + } + break; + + case 330: + if (!serv->skip_next_whois) + EMIT_SIGNAL (XP_TE_WHOIS_AUTH, whois_sess, word[4], + word_eol[6] + 1, word[5], NULL, 0); + break; + + case 332: + inbound_topic (serv, word[4], + (word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5]); + break; + + case 333: + inbound_topictime (serv, word[4], word[5], atol (word[6])); + break; + +#if 0 + case 338: /* Undernet Real user@host, Real IP */ + EMIT_SIGNAL (XP_TE_WHOIS_REALHOST, sess, word[4], word[5], word[6], + (word_eol[7][0]==':') ? word_eol[7]+1 : word_eol[7], 0); + break; +#endif + + case 341: /* INVITE ACK */ + EMIT_SIGNAL (XP_TE_UINVITE, sess, word[4], word[5], serv->servername, + NULL, 0); + break; + + case 352: /* WHO */ + { + unsigned int away = 0; + session *who_sess = find_channel (serv, word[4]); + + if (*word[9] == 'G') + away = 1; + + inbound_user_info (sess, word[4], word[5], word[6], word[7], + word[8], word_eol[11], away); + + /* try to show only user initiated whos */ + if (!who_sess || !who_sess->doing_who) + EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1], + word[2], NULL, 0); + } + break; + + case 354: /* undernet WHOX: used as a reply for irc_away_status */ + { + unsigned int away = 0; + session *who_sess; + + /* irc_away_status sends out a "152" */ + if (!strcmp (word[4], "152")) + { + who_sess = find_channel (serv, word[5]); + + if (*word[7] == 'G') + away = 1; + + /* :SanJose.CA.us.undernet.org 354 z1 152 #zed1 z1 H@ */ + inbound_user_info (sess, word[5], 0, 0, 0, word[6], 0, away); + + /* try to show only user initiated whos */ + if (!who_sess || !who_sess->doing_who) + EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, + word[1], word[2], NULL, 0); + } else + goto def; + } + break; + + case 315: /* END OF WHO */ + { + session *who_sess; + who_sess = find_channel (serv, word[4]); + if (who_sess) + { + if (!who_sess->doing_who) + EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, + word[1], word[2], NULL, 0); + who_sess->doing_who = FALSE; + } else + { + if (!serv->doing_dns) + EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, + word[1], word[2], NULL, 0); + serv->doing_dns = FALSE; + } + } + break; + + case 348: /* +e-list entry */ + if (!inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], TRUE)) + goto def; + break; + + case 349: /* end of exemption list */ + sess = find_channel (serv, word[4]); + if (!sess) + { + sess = serv->front_session; + goto def; + } + if (!fe_is_banwindow (sess)) + goto def; + fe_ban_list_end (sess, TRUE); + break; + + case 353: /* NAMES */ + inbound_nameslist (serv, word[5], + (word_eol[6][0] == ':') ? word_eol[6] + 1 : word_eol[6]); + break; + + case 366: + if (!inbound_nameslist_end (serv, word[4])) + goto def; + break; + + case 367: /* banlist entry */ + inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], FALSE); + break; + + case 368: + sess = find_channel (serv, word[4]); + if (!sess) + { + sess = serv->front_session; + goto def; + } + if (!fe_is_banwindow (sess)) + goto def; + fe_ban_list_end (sess, FALSE); + break; + + case 369: /* WHOWAS end */ + case 406: /* WHOWAS error */ + EMIT_SIGNAL (XP_TE_SERVTEXT, whois_sess, text, word[1], word[2], NULL, 0); + serv->inside_whois = 0; + break; + + case 372: /* motd text */ + case 375: /* motd start */ + if (!prefs.skipmotd || serv->motd_skipped) + EMIT_SIGNAL (XP_TE_MOTD, serv->server_session, text, NULL, NULL, + NULL, 0); + break; + + case 376: /* end of motd */ + case 422: /* motd file is missing */ + inbound_login_end (sess, text); + break; + + case 433: /* nickname in use */ + case 432: /* erroneous nickname */ + if (serv->end_of_motd) + goto def; + inbound_next_nick (sess, word[4]); + break; + + case 437: + if (serv->end_of_motd || is_channel (serv, word[4])) + goto def; + inbound_next_nick (sess, word[4]); + break; + + case 471: + EMIT_SIGNAL (XP_TE_USERLIMIT, sess, word[4], NULL, NULL, NULL, 0); + break; + + case 473: + EMIT_SIGNAL (XP_TE_INVITE, sess, word[4], NULL, NULL, NULL, 0); + break; + + case 474: + EMIT_SIGNAL (XP_TE_BANNED, sess, word[4], NULL, NULL, NULL, 0); + break; + + case 475: + EMIT_SIGNAL (XP_TE_KEYWORD, sess, word[4], NULL, NULL, NULL, 0); + break; + + case 601: + notify_set_offline (serv, word[4], FALSE); + break; + + case 605: + notify_set_offline (serv, word[4], TRUE); + break; + + case 600: + case 604: + notify_set_online (serv, word[4]); + break; + + default: + + if (serv->inside_whois && word[4][0]) + { + /* some unknown WHOIS reply, ircd coders make them up weekly */ + if (!serv->skip_next_whois) + EMIT_SIGNAL (XP_TE_WHOIS_SPECIAL, whois_sess, word[4], + (word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5], + word[2], NULL, 0); + return; + } + + def: + if (is_channel (serv, word[4])) + { + session *realsess = find_channel (serv, word[4]); + if (!realsess) + realsess = serv->server_session; + EMIT_SIGNAL (XP_TE_SERVTEXT, realsess, text, word[1], word[2], NULL, 0); + } else + { + EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1], + word[2], NULL, 0); + } + } +} + +/* handle named messages that starts with a ':' */ + +static void +process_named_msg (session *sess, char *type, char *word[], char *word_eol[]) +{ + server *serv = sess->server; + char ip[128], nick[NICKLEN]; + char *text, *ex; + int len = strlen (type); + + /* fill in the "ip" and "nick" buffers */ + ex = strchr (word[1], '!'); + if (!ex) /* no '!', must be a server message */ + { + safe_strcpy (ip, word[1], sizeof (ip)); + safe_strcpy (nick, word[1], sizeof (nick)); + } else + { + safe_strcpy (ip, ex + 1, sizeof (ip)); + ex[0] = 0; + safe_strcpy (nick, word[1], sizeof (nick)); + ex[0] = '!'; + } + + if (len == 4) + { + guint32 t; + + t = WORDL((guint8)type[0], (guint8)type[1], (guint8)type[2], (guint8)type[3]); + /* this should compile to a bunch of: CMP.L, JE ... nice & fast */ + switch (t) + { + case WORDL('J','O','I','N'): + { + char *chan = word[3]; + + if (*chan == ':') + chan++; + if (!serv->p_cmp (nick, serv->nick)) + inbound_ujoin (serv, chan, nick, ip); + else + inbound_join (serv, chan, nick, ip); + } + return; + + case WORDL('K','I','C','K'): + { + char *kicked = word[4]; + char *reason = word_eol[5]; + if (*kicked) + { + if (*reason == ':') + reason++; + if (!strcmp (kicked, serv->nick)) + inbound_ukick (serv, word[3], nick, reason); + else + inbound_kick (serv, word[3], kicked, nick, reason); + } + } + return; + + case WORDL('K','I','L','L'): + EMIT_SIGNAL (XP_TE_KILL, sess, nick, word_eol[5], NULL, NULL, 0); + return; + + case WORDL('M','O','D','E'): + handle_mode (serv, word, word_eol, nick, FALSE); /* modes.c */ + return; + + case WORDL('N','I','C','K'): + inbound_newnick (serv, nick, (word_eol[3][0] == ':') + ? word_eol[3] + 1 : word_eol[3], FALSE); + return; + + case WORDL('P','A','R','T'): + { + char *chan = word[3]; + char *reason = word_eol[4]; + + if (*chan == ':') + chan++; + if (*reason == ':') + reason++; + if (!strcmp (nick, serv->nick)) + inbound_upart (serv, chan, ip, reason); + else + inbound_part (serv, chan, nick, ip, reason); + } + return; + + case WORDL('P','O','N','G'): + inbound_ping_reply (serv->server_session, + (word[4][0] == ':') ? word[4] + 1 : word[4], word[3]); + return; + + case WORDL('Q','U','I','T'): + inbound_quit (serv, nick, ip, + (word_eol[3][0] == ':') ? word_eol[3] + 1 : word_eol[3]); + return; + } + + goto garbage; + } + + else if (len >= 5) + { + guint32 t; + + t = WORDL((guint8)type[0], (guint8)type[1], (guint8)type[2], (guint8)type[3]); + /* this should compile to a bunch of: CMP.L, JE ... nice & fast */ + switch (t) + { + case WORDL('I','N','V','I'): + if (ignore_check (word[1], IG_INVI)) + return; + + if (word[4][0] == ':') + EMIT_SIGNAL (XP_TE_INVITED, sess, word[4] + 1, nick, + serv->servername, NULL, 0); + else + EMIT_SIGNAL (XP_TE_INVITED, sess, word[4], nick, + serv->servername, NULL, 0); + + return; + + case WORDL('N','O','T','I'): + { + int id = FALSE; /* identified */ + + text = word_eol[4]; + if (*text == ':') + text++; + + if (serv->have_idmsg) + { + if (*text == '+') + { + id = TRUE; + text++; + } else if (*text == '-') + text++; + } + + if (!ignore_check (word[1], IG_NOTI)) + inbound_notice (serv, word[3], nick, text, ip, id); + } + return; + + case WORDL('P','R','I','V'): + { + char *to = word[3]; + int len; + int id = FALSE; /* identified */ + if (*to) + { + text = word_eol[4]; + if (*text == ':') + text++; + if (serv->have_idmsg) + { + if (*text == '+') + { + id = TRUE; + text++; + } else if (*text == '-') + text++; + } + len = strlen (text); + if (text[0] == 1 && text[len - 1] == 1) /* ctcp */ + { + text[len - 1] = 0; + text++; + if (strncasecmp (text, "ACTION", 6) != 0) + flood_check (nick, ip, serv, sess, 0); + if (strncasecmp (text, "DCC ", 4) == 0) + /* redo this with handle_quotes TRUE */ + process_data_init (word[1], word_eol[1], word, word_eol, TRUE, FALSE); + ctcp_handle (sess, to, nick, ip, text, word, word_eol, id); + } else + { + if (is_channel (serv, to)) + { + if (ignore_check (word[1], IG_CHAN)) + return; + inbound_chanmsg (serv, NULL, to, nick, text, FALSE, id); + } else + { + if (ignore_check (word[1], IG_PRIV)) + return; + inbound_privmsg (serv, nick, ip, text, id); + } + } + } + } + return; + + case WORDL('T','O','P','I'): + inbound_topicnew (serv, nick, word[3], + (word_eol[4][0] == ':') ? word_eol[4] + 1 : word_eol[4]); + return; + + case WORDL('W','A','L','L'): + text = word_eol[3]; + if (*text == ':') + text++; + EMIT_SIGNAL (XP_TE_WALLOPS, sess, nick, text, NULL, NULL, 0); + return; + } + } + +garbage: + /* unknown message */ + PrintTextf (sess, "GARBAGE: %s\n", word_eol[1]); +} + +/* handle named messages that DON'T start with a ':' */ + +static void +process_named_servermsg (session *sess, char *buf, char *rawname, char *word_eol[]) +{ + sess = sess->server->server_session; + + if (!strncmp (buf, "PING ", 5)) + { + tcp_sendf (sess->server, "PONG %s\r\n", buf + 5); + return; + } + if (!strncmp (buf, "ERROR", 5)) + { + EMIT_SIGNAL (XP_TE_SERVERERROR, sess, buf + 7, NULL, NULL, NULL, 0); + return; + } + if (!strncmp (buf, "NOTICE ", 7)) + { + buf = word_eol[3]; + if (*buf == ':') + buf++; + EMIT_SIGNAL (XP_TE_SERVNOTICE, sess, buf, sess->server->servername, NULL, NULL, 0); + return; + } + + EMIT_SIGNAL (XP_TE_SERVTEXT, sess, buf, sess->server->servername, rawname, NULL, 0); +} + +/* irc_inline() - 1 single line received from serv */ + +static void +irc_inline (server *serv, char *buf, int len) +{ + session *sess, *tmp; + char *type, *text; + char *word[PDIWORDS+1]; + char *word_eol[PDIWORDS+1]; + char pdibuf_static[522]; /* 1 line can potentially be 512*6 in utf8 */ + char *pdibuf = pdibuf_static; + + /* need more than 522? fall back to malloc */ + if (len >= sizeof (pdibuf_static)) + pdibuf = malloc (len + 1); + + sess = serv->front_session; + + /* Python relies on this */ + word[PDIWORDS] = NULL; + word_eol[PDIWORDS] = NULL; + + if (buf[0] == ':') + { + /* split line into words and words_to_end_of_line */ + process_data_init (pdibuf, buf, word, word_eol, FALSE, FALSE); + + /* find a context for this message */ + if (is_channel (serv, word[3])) + { + tmp = find_channel (serv, word[3]); + if (tmp) + sess = tmp; + } + + /* for server messages, the 2nd word is the "message type" */ + type = word[2]; + + word[0] = type; + word_eol[1] = buf; /* keep the ":" for plugins */ + if (plugin_emit_server (sess, type, word, word_eol)) + goto xit; + word[1]++; + word_eol[1] = buf + 1; /* but not for xchat internally */ + + } else + { + process_data_init (pdibuf, buf, word, word_eol, FALSE, FALSE); + word[0] = type = word[1]; + if (plugin_emit_server (sess, type, word, word_eol)) + goto xit; + } + + if (buf[0] != ':') + { + process_named_servermsg (sess, buf, word[0], word_eol); + goto xit; + } + + /* see if the second word is a numeric */ + if (isdigit ((unsigned char) word[2][0])) + { + text = word_eol[4]; + if (*text == ':') + text++; + + process_numeric (sess, atoi (word[2]), word, word_eol, text); + } else + { + process_named_msg (sess, type, word, word_eol); + } + +xit: + if (pdibuf != pdibuf_static) + free (pdibuf); +} + +void +proto_fill_her_up (server *serv) +{ + serv->p_inline = irc_inline; + serv->p_invite = irc_invite; + serv->p_cycle = irc_cycle; + serv->p_ctcp = irc_ctcp; + serv->p_nctcp = irc_nctcp; + serv->p_quit = irc_quit; + serv->p_kick = irc_kick; + serv->p_part = irc_part; + serv->p_ns_identify = irc_ns_identify; + serv->p_ns_ghost = irc_ns_ghost; + serv->p_join = irc_join; + serv->p_join_list = irc_join_list; + serv->p_login = irc_login; + serv->p_join_info = irc_join_info; + serv->p_mode = irc_mode; + serv->p_user_list = irc_user_list; + serv->p_away_status = irc_away_status; + /*serv->p_get_ip = irc_get_ip;*/ + serv->p_whois = irc_user_whois; + serv->p_get_ip = irc_user_list; + serv->p_get_ip_uh = irc_userhost; + serv->p_set_back = irc_set_back; + serv->p_set_away = irc_set_away; + serv->p_message = irc_message; + serv->p_action = irc_action; + serv->p_notice = irc_notice; + serv->p_topic = irc_topic; + serv->p_list_channels = irc_list_channels; + serv->p_change_nick = irc_change_nick; + serv->p_names = irc_names; + serv->p_ping = irc_ping; + serv->p_raw = irc_raw; + serv->p_cmp = rfc_casecmp; /* can be changed by 005 in modes.c */ +} diff --git a/src/common/proto-irc.h b/src/common/proto-irc.h new file mode 100644 index 00000000..90803594 --- /dev/null +++ b/src/common/proto-irc.h @@ -0,0 +1,6 @@ +#ifndef XCHAT_PROTO_H +#define XCHAT_PROTO_H + +void proto_fill_her_up (server *serv); + +#endif diff --git a/src/common/server.c b/src/common/server.c new file mode 100644 index 00000000..dd43adff --- /dev/null +++ b/src/common/server.c @@ -0,0 +1,2047 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + * MS Proxy (ISA server) support is (c) 2006 Pavel Fedin <sonic_amiga@rambler.ru> + * based on Dante source code + * Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 + * Inferno Nettverk A/S, Norway. All rights reserved. + */ + +/*#define DEBUG_MSPROXY*/ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> + +#define WANTSOCKET +#define WANTARPA +#include "inet.h" + +#ifndef WIN32 +#include <signal.h> +#include <sys/wait.h> +#else +#include <winbase.h> +#endif + +#include "xchat.h" +#include "fe.h" +#include "cfgfiles.h" +#include "network.h" +#include "notify.h" +#include "xchatc.h" +#include "inbound.h" +#include "outbound.h" +#include "text.h" +#include "util.h" +#include "url.h" +#include "proto-irc.h" +#include "servlist.h" +#include "server.h" + +#ifdef USE_OPENSSL +#include <openssl/ssl.h> /* SSL_() */ +#include <openssl/err.h> /* ERR_() */ +#include "ssl.h" +#endif + +#ifdef USE_MSPROXY +#include "msproxy.h" +#endif + +#ifdef WIN32 +#include "identd.c" +#endif + +#ifdef USE_LIBPROXY +#include <proxy.h> +#endif + +#ifdef USE_OPENSSL +extern SSL_CTX *ctx; /* xchat.c */ +/* local variables */ +static struct session *g_sess = NULL; +#endif + +static GSList *away_list = NULL; +GSList *serv_list = NULL; + +static void auto_reconnect (server *serv, int send_quit, int err); +static void server_disconnect (session * sess, int sendquit, int err); +static int server_cleanup (server * serv); +static void server_connect (server *serv, char *hostname, int port, int no_login); + +#ifdef USE_LIBPROXY +extern pxProxyFactory *libproxy_factory; +#endif + +/* actually send to the socket. This might do a character translation or + send via SSL. server/dcc both use this function. */ + +int +tcp_send_real (void *ssl, int sok, char *encoding, int using_irc, char *buf, int len) +{ + int ret; + char *locale; + gsize loc_len; + + if (encoding == NULL) /* system */ + { + locale = NULL; + if (!prefs.utf8_locale) + { + const gchar *charset; + + g_get_charset (&charset); + locale = g_convert_with_fallback (buf, len, charset, "UTF-8", + "?", 0, &loc_len, 0); + } + } else + { + if (using_irc) /* using "IRC" encoding (CP1252/UTF-8 hybrid) */ + /* if all chars fit inside CP1252, use that. Otherwise this + returns NULL and we send UTF-8. */ + locale = g_convert (buf, len, "CP1252", "UTF-8", 0, &loc_len, 0); + else + locale = g_convert_with_fallback (buf, len, encoding, "UTF-8", + "?", 0, &loc_len, 0); + } + + if (locale) + { + len = loc_len; +#ifdef USE_OPENSSL + if (!ssl) + ret = send (sok, locale, len, 0); + else + ret = _SSL_send (ssl, locale, len); +#else + ret = send (sok, locale, len, 0); +#endif + g_free (locale); + } else + { +#ifdef USE_OPENSSL + if (!ssl) + ret = send (sok, buf, len, 0); + else + ret = _SSL_send (ssl, buf, len); +#else + ret = send (sok, buf, len, 0); +#endif + } + + return ret; +} + +static int +server_send_real (server *serv, char *buf, int len) +{ + fe_add_rawlog (serv, buf, len, TRUE); + + return tcp_send_real (serv->ssl, serv->sok, serv->encoding, serv->using_irc, + buf, len); +} + +/* new throttling system, uses the same method as the Undernet + ircu2.10 server; under test, a 200-line paste didn't flood + off the client */ + +static int +tcp_send_queue (server *serv) +{ + char *buf, *p; + int len, i, pri; + GSList *list; + time_t now = time (0); + + /* did the server close since the timeout was added? */ + if (!is_server (serv)) + return 0; + + /* try priority 2,1,0 */ + pri = 2; + while (pri >= 0) + { + list = serv->outbound_queue; + while (list) + { + buf = (char *) list->data; + if (buf[0] == pri) + { + buf++; /* skip the priority byte */ + len = strlen (buf); + + if (serv->next_send < now) + serv->next_send = now; + if (serv->next_send - now >= 10) + { + /* check for clock skew */ + if (now >= serv->prev_now) + return 1; /* don't remove the timeout handler */ + /* it is skewed, reset to something sane */ + serv->next_send = now; + } + + for (p = buf, i = len; i && *p != ' '; p++, i--); + serv->next_send += (2 + i / 120); + serv->sendq_len -= len; + serv->prev_now = now; + fe_set_throttle (serv); + + server_send_real (serv, buf, len); + + buf--; + serv->outbound_queue = g_slist_remove (serv->outbound_queue, buf); + free (buf); + list = serv->outbound_queue; + } else + { + list = list->next; + } + } + /* now try pri 0 */ + pri--; + } + return 0; /* remove the timeout handler */ +} + +int +tcp_send_len (server *serv, char *buf, int len) +{ + char *dbuf; + int noqueue = !serv->outbound_queue; + + if (!prefs.throttle) + return server_send_real (serv, buf, len); + + dbuf = malloc (len + 2); /* first byte is the priority */ + dbuf[0] = 2; /* pri 2 for most things */ + memcpy (dbuf + 1, buf, len); + dbuf[len + 1] = 0; + + /* privmsg and notice get a lower priority */ + if (strncasecmp (dbuf + 1, "PRIVMSG", 7) == 0 || + strncasecmp (dbuf + 1, "NOTICE", 6) == 0) + { + dbuf[0] = 1; + } + else + { + /* WHO/MODE get the lowest priority */ + if (strncasecmp (dbuf + 1, "WHO ", 4) == 0 || + /* but only MODE queries, not changes */ + (strncasecmp (dbuf + 1, "MODE", 4) == 0 && + strchr (dbuf, '-') == NULL && + strchr (dbuf, '+') == NULL)) + dbuf[0] = 0; + } + + serv->outbound_queue = g_slist_append (serv->outbound_queue, dbuf); + serv->sendq_len += len; /* tcp_send_queue uses strlen */ + + if (tcp_send_queue (serv) && noqueue) + fe_timeout_add (500, tcp_send_queue, serv); + + return 1; +} + +/*int +tcp_send (server *serv, char *buf) +{ + return tcp_send_len (serv, buf, strlen (buf)); +}*/ + +void +tcp_sendf (server *serv, char *fmt, ...) +{ + va_list args; + /* keep this buffer in BSS. Converting UTF-8 to ISO-8859-x might make the + string shorter, so allow alot more than 512 for now. */ + static char send_buf[1540]; /* good code hey (no it's not overflowable) */ + int len; + + va_start (args, fmt); + len = vsnprintf (send_buf, sizeof (send_buf) - 1, fmt, args); + va_end (args); + + send_buf[sizeof (send_buf) - 1] = '\0'; + if (len < 0 || len > (sizeof (send_buf) - 1)) + len = strlen (send_buf); + + tcp_send_len (serv, send_buf, len); +} + +static int +close_socket_cb (gpointer sok) +{ + closesocket (GPOINTER_TO_INT (sok)); + return 0; +} + +static void +close_socket (int sok) +{ + /* close the socket in 5 seconds so the QUIT message is not lost */ + fe_timeout_add (5000, close_socket_cb, GINT_TO_POINTER (sok)); +} + +/* handle 1 line of text received from the server */ + +static void +server_inline (server *serv, char *line, int len) +{ + char *utf_line_allocated = NULL; + + /* Checks whether we're set to use UTF-8 charset */ + if (serv->using_irc || /* 1. using CP1252/UTF-8 Hybrid */ + (serv->encoding == NULL && prefs.utf8_locale) || /* OR 2. using system default->UTF-8 */ + (serv->encoding != NULL && /* OR 3. explicitly set to UTF-8 */ + (strcasecmp (serv->encoding, "UTF8") == 0 || + strcasecmp (serv->encoding, "UTF-8") == 0))) + { + /* The user has the UTF-8 charset set, either via /charset + command or from his UTF-8 locale. Thus, we first try the + UTF-8 charset, and if we fail to convert, we assume + it to be ISO-8859-1 (see text_validate). */ + + utf_line_allocated = text_validate (&line, &len); + + } else + { + /* Since the user has an explicit charset set, either + via /charset command or from his non-UTF8 locale, + we don't fallback to ISO-8859-1 and instead try to remove + errnoeous octets till the string is convertable in the + said charset. */ + + const char *encoding = NULL; + + if (serv->encoding != NULL) + encoding = serv->encoding; + else + g_get_charset (&encoding); + + if (encoding != NULL) + { + char *conv_line; /* holds a copy of the original string */ + int conv_len; /* tells g_convert how much of line to convert */ + gsize utf_len; + gsize read_len; + GError *err; + gboolean retry; + + conv_line = g_malloc (len + 1); + memcpy (conv_line, line, len); + conv_line[len] = 0; + conv_len = len; + + /* if CP1255, convert it with the NUL terminator. + Works around SF bug #1122089 */ + if (serv->using_cp1255) + conv_len++; + + do + { + err = NULL; + retry = FALSE; + utf_line_allocated = g_convert_with_fallback (conv_line, conv_len, "UTF-8", encoding, "?", &read_len, &utf_len, &err); + if (err != NULL) + { + if (err->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE && conv_len > (read_len + 1)) + { + /* Make our best bet by removing the erroneous char. + This will work for casual 8-bit strings with non-standard chars. */ + memmove (conv_line + read_len, conv_line + read_len + 1, conv_len - read_len -1); + conv_len--; + retry = TRUE; + } + g_error_free (err); + } + } while (retry); + + g_free (conv_line); + + /* If any conversion has occured at all. Conversion might fail + due to errors other than invalid sequences, e.g. unknown charset. */ + if (utf_line_allocated != NULL) + { + line = utf_line_allocated; + len = utf_len; + if (serv->using_cp1255 && len > 0) + len--; + } + else + { + /* If all fails, treat as UTF-8 with fallback to ISO-8859-1. */ + utf_line_allocated = text_validate (&line, &len); + } + } + } + + fe_add_rawlog (serv, line, len, FALSE); + url_check_line (line, len); + + /* let proto-irc.c handle it */ + serv->p_inline (serv, line, len); + + if (utf_line_allocated != NULL) /* only if a special copy was allocated */ + g_free (utf_line_allocated); +} + +/* read data from socket */ + +static gboolean +server_read (GIOChannel *source, GIOCondition condition, server *serv) +{ + int sok = serv->sok; + int error, i, len; + char lbuf[2050]; + + while (1) + { +#ifdef USE_OPENSSL + if (!serv->ssl) +#endif + len = recv (sok, lbuf, sizeof (lbuf) - 2, 0); +#ifdef USE_OPENSSL + else + len = _SSL_recv (serv->ssl, lbuf, sizeof (lbuf) - 2); +#endif + if (len < 1) + { + error = 0; + if (len < 0) + { + if (would_block ()) + return TRUE; + error = sock_error (); + } + if (!serv->end_of_motd) + { + server_disconnect (serv->server_session, FALSE, error); + if (!servlist_cycle (serv)) + { + if (prefs.autoreconnect) + auto_reconnect (serv, FALSE, error); + } + } else + { + if (prefs.autoreconnect) + auto_reconnect (serv, FALSE, error); + else + server_disconnect (serv->server_session, FALSE, error); + } + return TRUE; + } + + i = 0; + + lbuf[len] = 0; + + while (i < len) + { + switch (lbuf[i]) + { + case '\r': + break; + + case '\n': + serv->linebuf[serv->pos] = 0; + server_inline (serv, serv->linebuf, serv->pos); + serv->pos = 0; + break; + + default: + serv->linebuf[serv->pos] = lbuf[i]; + if (serv->pos >= (sizeof (serv->linebuf) - 1)) + fprintf (stderr, + "*** XCHAT WARNING: Buffer overflow - shit server!\n"); + else + serv->pos++; + } + i++; + } + } +} + +static void +server_connected (server * serv) +{ + prefs.wait_on_exit = TRUE; + serv->ping_recv = time (0); + serv->connected = TRUE; + set_nonblocking (serv->sok); + serv->iotag = fe_input_add (serv->sok, FIA_READ|FIA_EX, server_read, serv); + if (!serv->no_login) + { + EMIT_SIGNAL (XP_TE_CONNECTED, serv->server_session, NULL, NULL, NULL, + NULL, 0); + if (serv->network) + { + serv->p_login (serv, + (!(((ircnet *)serv->network)->flags & FLAG_USE_GLOBAL) && + (((ircnet *)serv->network)->user)) ? + (((ircnet *)serv->network)->user) : + prefs.username, + (!(((ircnet *)serv->network)->flags & FLAG_USE_GLOBAL) && + (((ircnet *)serv->network)->real)) ? + (((ircnet *)serv->network)->real) : + prefs.realname); + } else + { + serv->p_login (serv, prefs.username, prefs.realname); + } + } else + { + EMIT_SIGNAL (XP_TE_SERVERCONNECTED, serv->server_session, NULL, NULL, + NULL, NULL, 0); + } + + server_set_name (serv, serv->servername); + fe_server_event (serv, FE_SE_CONNECT, 0); +} + +#ifdef WIN32 + +static gboolean +server_close_pipe (int *pipefd) /* see comments below */ +{ + close (pipefd[0]); /* close WRITE end first to cause an EOF on READ */ + close (pipefd[1]); /* in giowin32, and end that thread. */ + free (pipefd); + return FALSE; +} + +#endif + +static void +server_stopconnecting (server * serv) +{ + if (serv->iotag) + { + fe_input_remove (serv->iotag); + serv->iotag = 0; + } + + if (serv->joindelay_tag) + { + fe_timeout_remove (serv->joindelay_tag); + serv->joindelay_tag = 0; + } + +#ifndef WIN32 + /* kill the child process trying to connect */ + kill (serv->childpid, SIGKILL); + waitpid (serv->childpid, NULL, 0); + + close (serv->childwrite); + close (serv->childread); +#else + PostThreadMessage (serv->childpid, WM_QUIT, 0, 0); + + { + /* if we close the pipe now, giowin32 will crash. */ + int *pipefd = malloc (sizeof (int) * 2); + pipefd[0] = serv->childwrite; + pipefd[1] = serv->childread; + g_idle_add ((GSourceFunc)server_close_pipe, pipefd); + } +#endif + +#ifdef USE_OPENSSL + if (serv->ssl_do_connect_tag) + { + fe_timeout_remove (serv->ssl_do_connect_tag); + serv->ssl_do_connect_tag = 0; + } +#endif + + fe_progressbar_end (serv); + + serv->connecting = FALSE; + fe_server_event (serv, FE_SE_DISCONNECT, 0); +} + +#ifdef USE_OPENSSL +#define SSLTMOUT 90 /* seconds */ +static void +ssl_cb_info (SSL * s, int where, int ret) +{ +/* char buf[128];*/ + + + return; /* FIXME: make debug level adjustable in serverlist or settings */ + +/* snprintf (buf, sizeof (buf), "%s (%d)", SSL_state_string_long (s), where); + if (g_sess) + EMIT_SIGNAL (XP_TE_SSLMESSAGE, g_sess, buf, NULL, NULL, NULL, 0); + else + fprintf (stderr, "%s\n", buf);*/ +} + +static int +ssl_cb_verify (int ok, X509_STORE_CTX * ctx) +{ + char subject[256]; + char issuer[256]; + char buf[512]; + + + X509_NAME_oneline (X509_get_subject_name (ctx->current_cert), subject, + sizeof (subject)); + X509_NAME_oneline (X509_get_issuer_name (ctx->current_cert), issuer, + sizeof (issuer)); + + snprintf (buf, sizeof (buf), "* Subject: %s", subject); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, g_sess, buf, NULL, NULL, NULL, 0); + snprintf (buf, sizeof (buf), "* Issuer: %s", issuer); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, g_sess, buf, NULL, NULL, NULL, 0); + + return (TRUE); /* always ok */ +} + +static int +ssl_do_connect (server * serv) +{ + char buf[128]; + + g_sess = serv->server_session; + if (SSL_connect (serv->ssl) <= 0) + { + char err_buf[128]; + int err; + + g_sess = NULL; + if ((err = ERR_get_error ()) > 0) + { + ERR_error_string (err, err_buf); + snprintf (buf, sizeof (buf), "(%d) %s", err, err_buf); + EMIT_SIGNAL (XP_TE_CONNFAIL, serv->server_session, buf, NULL, + NULL, NULL, 0); + + if (ERR_GET_REASON (err) == SSL_R_WRONG_VERSION_NUMBER) + PrintText (serv->server_session, _("Are you sure this is a SSL capable server and port?\n")); + + server_cleanup (serv); + + if (prefs.autoreconnectonfail) + auto_reconnect (serv, FALSE, -1); + + return (0); /* remove it (0) */ + } + } + g_sess = NULL; + + if (SSL_is_init_finished (serv->ssl)) + { + struct cert_info cert_info; + struct chiper_info *chiper_info; + int verify_error; + int i; + + if (!_SSL_get_cert_info (&cert_info, serv->ssl)) + { + snprintf (buf, sizeof (buf), "* Certification info:"); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, + NULL, 0); + snprintf (buf, sizeof (buf), " Subject:"); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, + NULL, 0); + for (i = 0; cert_info.subject_word[i]; i++) + { + snprintf (buf, sizeof (buf), " %s", cert_info.subject_word[i]); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, + NULL, 0); + } + snprintf (buf, sizeof (buf), " Issuer:"); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, + NULL, 0); + for (i = 0; cert_info.issuer_word[i]; i++) + { + snprintf (buf, sizeof (buf), " %s", cert_info.issuer_word[i]); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, + NULL, 0); + } + snprintf (buf, sizeof (buf), " Public key algorithm: %s (%d bits)", + cert_info.algorithm, cert_info.algorithm_bits); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, + NULL, 0); + /*if (cert_info.rsa_tmp_bits) + { + snprintf (buf, sizeof (buf), + " Public key algorithm uses ephemeral key with %d bits", + cert_info.rsa_tmp_bits); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, + NULL, 0); + }*/ + snprintf (buf, sizeof (buf), " Sign algorithm %s", + cert_info.sign_algorithm/*, cert_info.sign_algorithm_bits*/); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, + NULL, 0); + snprintf (buf, sizeof (buf), " Valid since %s to %s", + cert_info.notbefore, cert_info.notafter); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, + NULL, 0); + } else + { + snprintf (buf, sizeof (buf), " * No Certificate"); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, + NULL, 0); + } + + chiper_info = _SSL_get_cipher_info (serv->ssl); /* static buffer */ + snprintf (buf, sizeof (buf), "* Cipher info:"); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL, + 0); + snprintf (buf, sizeof (buf), " Version: %s, cipher %s (%u bits)", + chiper_info->version, chiper_info->chiper, + chiper_info->chiper_bits); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL, + 0); + + verify_error = SSL_get_verify_result (serv->ssl); + switch (verify_error) + { + case X509_V_OK: + /* snprintf (buf, sizeof (buf), "* Verify OK (?)"); */ + /* EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL, 0); */ + break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: + case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: + case X509_V_ERR_CERT_HAS_EXPIRED: + if (serv->accept_invalid_cert) + { + snprintf (buf, sizeof (buf), "* Verify E: %s.? (%d) -- Ignored", + X509_verify_cert_error_string (verify_error), + verify_error); + EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, + NULL, 0); + break; + } + default: + snprintf (buf, sizeof (buf), "%s.? (%d)", + X509_verify_cert_error_string (verify_error), + verify_error); + EMIT_SIGNAL (XP_TE_CONNFAIL, serv->server_session, buf, NULL, NULL, + NULL, 0); + + server_cleanup (serv); + + return (0); + } + + server_stopconnecting (serv); + + /* activate gtk poll */ + server_connected (serv); + + return (0); /* remove it (0) */ + } else + { + if (serv->ssl->session && serv->ssl->session->time + SSLTMOUT < time (NULL)) + { + snprintf (buf, sizeof (buf), "SSL handshake timed out"); + EMIT_SIGNAL (XP_TE_CONNFAIL, serv->server_session, buf, NULL, + NULL, NULL, 0); + server_cleanup (serv); /* ->connecting = FALSE */ + + if (prefs.autoreconnectonfail) + auto_reconnect (serv, FALSE, -1); + + return (0); /* remove it (0) */ + } + + return (1); /* call it more (1) */ + } +} +#endif + +static int +timeout_auto_reconnect (server *serv) +{ + if (is_server (serv)) /* make sure it hasnt been closed during the delay */ + { + serv->recondelay_tag = 0; + if (!serv->connected && !serv->connecting && serv->server_session) + { + server_connect (serv, serv->hostname, serv->port, FALSE); + } + } + return 0; /* returning 0 should remove the timeout handler */ +} + +static void +auto_reconnect (server *serv, int send_quit, int err) +{ + session *s; + GSList *list; + int del; + + if (serv->server_session == NULL) + return; + + list = sess_list; + while (list) /* make sure auto rejoin can work */ + { + s = list->data; + if (s->type == SESS_CHANNEL && s->channel[0]) + { + strcpy (s->waitchannel, s->channel); + strcpy (s->willjoinchannel, s->channel); + } + list = list->next; + } + + if (serv->connected) + server_disconnect (serv->server_session, send_quit, err); + + del = prefs.recon_delay * 1000; + if (del < 1000) + del = 500; /* so it doesn't block the gui */ + +#ifndef WIN32 + if (err == -1 || err == 0 || err == ECONNRESET || err == ETIMEDOUT) +#else + if (err == -1 || err == 0 || err == WSAECONNRESET || err == WSAETIMEDOUT) +#endif + serv->reconnect_away = serv->is_away; + + /* is this server in a reconnect delay? remove it! */ + if (serv->recondelay_tag) + { + fe_timeout_remove (serv->recondelay_tag); + serv->recondelay_tag = 0; + } + + serv->recondelay_tag = fe_timeout_add (del, timeout_auto_reconnect, serv); + fe_server_event (serv, FE_SE_RECONDELAY, del); +} + +static void +server_flush_queue (server *serv) +{ + list_free (&serv->outbound_queue); + serv->sendq_len = 0; + fe_set_throttle (serv); +} + +#ifdef WIN32 + +static int +waitline2 (GIOChannel *source, char *buf, int bufsize) +{ + int i = 0; + int len; + + while (1) + { + if (g_io_channel_read (source, &buf[i], 1, &len) != G_IO_ERROR_NONE) + return -1; + if (buf[i] == '\n' || bufsize == i + 1) + { + buf[i] = 0; + return i; + } + i++; + } +} + +#else + +#define waitline2(source,buf,size) waitline(serv->childread,buf,size,0) + +#endif + +/* connect() successed */ + +static void +server_connect_success (server *serv) +{ +#ifdef USE_OPENSSL +#define SSLDOCONNTMOUT 300 + if (serv->use_ssl) + { + char *err; + + /* it'll be a memory leak, if connection isn't terminated by + server_cleanup() */ + serv->ssl = _SSL_socket (ctx, serv->sok); + if ((err = _SSL_set_verify (ctx, ssl_cb_verify, NULL))) + { + EMIT_SIGNAL (XP_TE_CONNFAIL, serv->server_session, err, NULL, + NULL, NULL, 0); + server_cleanup (serv); /* ->connecting = FALSE */ + return; + } + /* FIXME: it'll be needed by new servers */ + /* send(serv->sok, "STLS\r\n", 6, 0); sleep(1); */ + set_nonblocking (serv->sok); + serv->ssl_do_connect_tag = fe_timeout_add (SSLDOCONNTMOUT, + ssl_do_connect, serv); + return; + } + + serv->ssl = NULL; +#endif + server_stopconnecting (serv); /* ->connecting = FALSE */ + /* activate glib poll */ + server_connected (serv); +} + +/* receive info from the child-process about connection progress */ + +static gboolean +server_read_child (GIOChannel *source, GIOCondition condition, server *serv) +{ + session *sess = serv->server_session; + char tbuf[128]; + char outbuf[512]; + char host[100]; + char ip[100]; + char *p; + + waitline2 (source, tbuf, sizeof tbuf); + + switch (tbuf[0]) + { + case '0': /* print some text */ + waitline2 (source, tbuf, sizeof tbuf); + PrintText (serv->server_session, tbuf); + break; + case '1': /* unknown host */ + server_stopconnecting (serv); + closesocket (serv->sok4); + if (serv->proxy_sok4 != -1) + closesocket (serv->proxy_sok4); +#ifdef USE_IPV6 + if (serv->sok6 != -1) + closesocket (serv->sok6); + if (serv->proxy_sok6 != -1) + closesocket (serv->proxy_sok6); +#endif + EMIT_SIGNAL (XP_TE_UKNHOST, sess, NULL, NULL, NULL, NULL, 0); + if (!servlist_cycle (serv)) + if (prefs.autoreconnectonfail) + auto_reconnect (serv, FALSE, -1); + break; + case '2': /* connection failed */ + waitline2 (source, tbuf, sizeof tbuf); + server_stopconnecting (serv); + closesocket (serv->sok4); + if (serv->proxy_sok4 != -1) + closesocket (serv->proxy_sok4); +#ifdef USE_IPV6 + if (serv->sok6 != -1) + closesocket (serv->sok6); + if (serv->proxy_sok6 != -1) + closesocket (serv->proxy_sok6); +#endif + EMIT_SIGNAL (XP_TE_CONNFAIL, sess, errorstring (atoi (tbuf)), NULL, + NULL, NULL, 0); + if (!servlist_cycle (serv)) + if (prefs.autoreconnectonfail) + auto_reconnect (serv, FALSE, -1); + break; + case '3': /* gethostbyname finished */ + waitline2 (source, host, sizeof host); + waitline2 (source, ip, sizeof ip); + waitline2 (source, outbuf, sizeof outbuf); + EMIT_SIGNAL (XP_TE_CONNECT, sess, host, ip, outbuf, NULL, 0); +#ifdef WIN32 + if (prefs.identd) + { + if (serv->network) + identd_start ((((ircnet *)serv->network)->user) ? + (((ircnet *)serv->network)->user) : + prefs.username); + else + identd_start (prefs.username); + } +#else + snprintf (outbuf, sizeof (outbuf), "%s/auth/xchat_auth", + g_get_home_dir ()); + if (access (outbuf, X_OK) == 0) + { + snprintf (outbuf, sizeof (outbuf), "exec -d %s/auth/xchat_auth %s", + g_get_home_dir (), prefs.username); + handle_command (serv->server_session, outbuf, FALSE); + } +#endif + break; + case '4': /* success */ + waitline2 (source, tbuf, sizeof (tbuf)); +#ifdef USE_MSPROXY + serv->sok = strtol (tbuf, &p, 10); + if (*p++ == ' ') + { + serv->proxy_sok = strtol (p, &p, 10); + serv->msp_state.clientid = strtol (++p, &p, 10); + serv->msp_state.serverid = strtol (++p, &p, 10); + serv->msp_state.seq_sent = atoi (++p); + } else + serv->proxy_sok = -1; +#ifdef DEBUG_MSPROXY + printf ("Parent got main socket: %d, proxy socket: %d\n", serv->sok, serv->proxy_sok); + printf ("Client ID 0x%08x server ID 0x%08x seq_sent %d\n", serv->msp_state.clientid, serv->msp_state.serverid, serv->msp_state.seq_sent); +#endif +#else + serv->sok = atoi (tbuf); +#endif +#ifdef USE_IPV6 + /* close the one we didn't end up using */ + if (serv->sok == serv->sok4) + closesocket (serv->sok6); + else + closesocket (serv->sok4); + if (serv->proxy_sok != -1) + { + if (serv->proxy_sok == serv->proxy_sok4) + closesocket (serv->proxy_sok6); + else + closesocket (serv->proxy_sok4); + } +#endif + server_connect_success (serv); + break; + case '5': /* prefs ip discovered */ + waitline2 (source, tbuf, sizeof tbuf); + prefs.local_ip = inet_addr (tbuf); + break; + case '7': /* gethostbyname (prefs.hostname) failed */ + sprintf (outbuf, + _("Cannot resolve hostname %s\nCheck your IP Settings!\n"), + prefs.hostname); + PrintText (sess, outbuf); + break; + case '8': + PrintText (sess, _("Proxy traversal failed.\n")); + server_disconnect (sess, FALSE, -1); + break; + case '9': + waitline2 (source, tbuf, sizeof tbuf); + EMIT_SIGNAL (XP_TE_SERVERLOOKUP, sess, tbuf, NULL, NULL, NULL, 0); + break; + } + + return TRUE; +} + +/* kill all sockets & iotags of a server. Stop a connection attempt, or + disconnect if already connected. */ + +static int +server_cleanup (server * serv) +{ + fe_set_lag (serv, 0.0); + + if (serv->iotag) + { + fe_input_remove (serv->iotag); + serv->iotag = 0; + } + + if (serv->joindelay_tag) + { + fe_timeout_remove (serv->joindelay_tag); + serv->joindelay_tag = 0; + } + +#ifdef USE_OPENSSL + if (serv->ssl) + { + _SSL_close (serv->ssl); + serv->ssl = NULL; + } +#endif + + if (serv->connecting) + { + server_stopconnecting (serv); + closesocket (serv->sok4); + if (serv->proxy_sok4 != -1) + closesocket (serv->proxy_sok4); + if (serv->sok6 != -1) + closesocket (serv->sok6); + if (serv->proxy_sok6 != -1) + closesocket (serv->proxy_sok6); + return 1; + } + + if (serv->connected) + { + close_socket (serv->sok); + if (serv->proxy_sok) + close_socket (serv->proxy_sok); + serv->connected = FALSE; + serv->end_of_motd = FALSE; + return 2; + } + + /* is this server in a reconnect delay? remove it! */ + if (serv->recondelay_tag) + { + fe_timeout_remove (serv->recondelay_tag); + serv->recondelay_tag = 0; + return 3; + } + + return 0; +} + +static void +server_disconnect (session * sess, int sendquit, int err) +{ + server *serv = sess->server; + GSList *list; + char tbuf[64]; + gboolean shutup = FALSE; + + /* send our QUIT reason */ + if (sendquit && serv->connected) + { + server_sendquit (sess); + } + + fe_server_event (serv, FE_SE_DISCONNECT, 0); + + /* close all sockets & io tags */ + switch (server_cleanup (serv)) + { + case 0: /* it wasn't even connected! */ + notc_msg (sess); + return; + case 1: /* it was in the process of connecting */ + sprintf (tbuf, "%d", sess->server->childpid); + EMIT_SIGNAL (XP_TE_STOPCONNECT, sess, tbuf, NULL, NULL, NULL, 0); + return; + case 3: + shutup = TRUE; /* won't print "disconnected" in channels */ + } + + server_flush_queue (serv); + + list = sess_list; + while (list) + { + sess = (struct session *) list->data; + if (sess->server == serv) + { + if (!shutup || sess->type == SESS_SERVER) + /* print "Disconnected" to each window using this server */ + EMIT_SIGNAL (XP_TE_DISCON, sess, errorstring (err), NULL, NULL, NULL, 0); + + if (!sess->channel[0] || sess->type == SESS_CHANNEL) + clear_channel (sess); + } + list = list->next; + } + + serv->pos = 0; + serv->motd_skipped = FALSE; + serv->no_login = FALSE; + serv->servername[0] = 0; + serv->lag_sent = 0; + + notify_cleanup (); +} + +/* send a "print text" command to the parent process - MUST END IN \n! */ + +static void +proxy_error (int fd, char *msg) +{ + write (fd, "0\n", 2); + write (fd, msg, strlen (msg)); +} + +struct sock_connect +{ + char version; + char type; + guint16 port; + guint32 address; + char username[10]; +}; + +/* traverse_socks() returns: + * 0 success * + * 1 socks traversal failed */ + +static int +traverse_socks (int print_fd, int sok, char *serverAddr, int port) +{ + struct sock_connect sc; + unsigned char buf[256]; + + sc.version = 4; + sc.type = 1; + sc.port = htons (port); + sc.address = inet_addr (serverAddr); + strncpy (sc.username, prefs.username, 9); + + send (sok, (char *) &sc, 8 + strlen (sc.username) + 1, 0); + buf[1] = 0; + recv (sok, buf, 10, 0); + if (buf[1] == 90) + return 0; + + snprintf (buf, sizeof (buf), "SOCKS\tServer reported error %d,%d.\n", buf[0], buf[1]); + proxy_error (print_fd, buf); + return 1; +} + +struct sock5_connect1 +{ + char version; + char nmethods; + char method; +}; + +static int +traverse_socks5 (int print_fd, int sok, char *serverAddr, int port) +{ + struct sock5_connect1 sc1; + unsigned char *sc2; + unsigned int packetlen, addrlen; + unsigned char buf[260]; + int auth = prefs.proxy_auth && prefs.proxy_user[0] && prefs.proxy_pass[0]; + + sc1.version = 5; + sc1.nmethods = 1; + if (auth) + sc1.method = 2; /* Username/Password Authentication (UPA) */ + else + sc1.method = 0; /* NO Authentication */ + send (sok, (char *) &sc1, 3, 0); + if (recv (sok, buf, 2, 0) != 2) + goto read_error; + + if (buf[0] != 5) + { + proxy_error (print_fd, "SOCKS\tServer is not socks version 5.\n"); + return 1; + } + + /* did the server say no auth required? */ + if (buf[1] == 0) + auth = 0; + + if (auth) + { + int len_u=0, len_p=0; + + /* authentication sub-negotiation (RFC1929) */ + if (buf[1] != 2) /* UPA not supported by server */ + { + proxy_error (print_fd, "SOCKS\tServer doesn't support UPA authentication.\n"); + return 1; + } + + memset (buf, 0, sizeof(buf)); + + /* form the UPA request */ + len_u = strlen (prefs.proxy_user); + len_p = strlen (prefs.proxy_pass); + buf[0] = 1; + buf[1] = len_u; + memcpy (buf + 2, prefs.proxy_user, len_u); + buf[2 + len_u] = len_p; + memcpy (buf + 3 + len_u, prefs.proxy_pass, len_p); + + send (sok, buf, 3 + len_u + len_p, 0); + if ( recv (sok, buf, 2, 0) != 2 ) + goto read_error; + if ( buf[1] != 0 ) + { + proxy_error (print_fd, "SOCKS\tAuthentication failed. " + "Is username and password correct?\n"); + return 1; /* UPA failed! */ + } + } + else + { + if (buf[1] != 0) + { + proxy_error (print_fd, "SOCKS\tAuthentication required but disabled in settings.\n"); + return 1; + } + } + + addrlen = strlen (serverAddr); + packetlen = 4 + 1 + addrlen + 2; + sc2 = malloc (packetlen); + sc2[0] = 5; /* version */ + sc2[1] = 1; /* command */ + sc2[2] = 0; /* reserved */ + sc2[3] = 3; /* address type */ + sc2[4] = (unsigned char) addrlen; /* hostname length */ + memcpy (sc2 + 5, serverAddr, addrlen); + *((unsigned short *) (sc2 + 5 + addrlen)) = htons (port); + send (sok, sc2, packetlen, 0); + free (sc2); + + /* consume all of the reply */ + if (recv (sok, buf, 4, 0) != 4) + goto read_error; + if (buf[0] != 5 || buf[1] != 0) + { + if (buf[1] == 2) + snprintf (buf, sizeof (buf), "SOCKS\tProxy refused to connect to host (not allowed).\n"); + else + snprintf (buf, sizeof (buf), "SOCKS\tProxy failed to connect to host (error %d).\n", buf[1]); + proxy_error (print_fd, buf); + return 1; + } + if (buf[3] == 1) /* IPV4 32bit address */ + { + if (recv (sok, buf, 6, 0) != 6) + goto read_error; + } else if (buf[3] == 4) /* IPV6 128bit address */ + { + if (recv (sok, buf, 18, 0) != 18) + goto read_error; + } else if (buf[3] == 3) /* string, 1st byte is size */ + { + if (recv (sok, buf, 1, 0) != 1) /* read the string size */ + goto read_error; + packetlen = buf[0] + 2; /* can't exceed 260 */ + if (recv (sok, buf, packetlen, 0) != packetlen) + goto read_error; + } + + return 0; /* success */ + +read_error: + proxy_error (print_fd, "SOCKS\tRead error from server.\n"); + return 1; +} + +static int +traverse_wingate (int print_fd, int sok, char *serverAddr, int port) +{ + char buf[128]; + + snprintf (buf, sizeof (buf), "%s %d\r\n", serverAddr, port); + send (sok, buf, strlen (buf), 0); + + return 0; +} + +/* stuff for HTTP auth is here */ + +static void +three_to_four (char *from, char *to) +{ + static const char tab64[64]= + { + 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', + 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f', + 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v', + 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' + }; + + to[0] = tab64 [ (from[0] >> 2) & 63 ]; + to[1] = tab64 [ ((from[0] << 4) | (from[1] >> 4)) & 63 ]; + to[2] = tab64 [ ((from[1] << 2) | (from[2] >> 6)) & 63 ]; + to[3] = tab64 [ from[2] & 63 ]; +}; + +void +base64_encode (char *to, char *from, unsigned int len) +{ + while (len >= 3) + { + three_to_four (from, to); + len -= 3; + from += 3; + to += 4; + } + if (len) + { + char three[3]={0,0,0}; + int i=0; + for (i=0;i<len;i++) + three[i] = *from++; + three_to_four (three, to); + if (len == 1) + to[2] = to[3] = '='; + else if (len == 2) + to[3] = '='; + to += 4; + }; + to[0] = 0; +} + +static int +http_read_line (int print_fd, int sok, char *buf, int len) +{ +#ifdef WIN32 + /* make sure waitline() uses recv() or it'll fail on win32 */ + len = waitline (sok, buf, len, FALSE); +#else + len = waitline (sok, buf, len, TRUE); +#endif + if (len >= 1) + { + /* print the message out (send it to the parent process) */ + write (print_fd, "0\n", 2); + + if (buf[len-1] == '\r') + { + buf[len-1] = '\n'; + write (print_fd, buf, len); + } else + { + write (print_fd, buf, len); + write (print_fd, "\n", 1); + } + } + + return len; +} + +static int +traverse_http (int print_fd, int sok, char *serverAddr, int port) +{ + char buf[512]; + char auth_data[256]; + char auth_data2[252]; + int n, n2; + + n = snprintf (buf, sizeof (buf), "CONNECT %s:%d HTTP/1.0\r\n", + serverAddr, port); + if (prefs.proxy_auth) + { + n2 = snprintf (auth_data2, sizeof (auth_data2), "%s:%s", + prefs.proxy_user, prefs.proxy_pass); + base64_encode (auth_data, auth_data2, n2); + n += snprintf (buf+n, sizeof (buf)-n, "Proxy-Authorization: Basic %s\r\n", auth_data); + } + n += snprintf (buf+n, sizeof (buf)-n, "\r\n"); + send (sok, buf, n, 0); + + n = http_read_line (print_fd, sok, buf, sizeof (buf)); + /* "HTTP/1.0 200 OK" */ + if (n < 12) + return 1; + if (memcmp (buf, "HTTP/", 5) || memcmp (buf + 9, "200", 3)) + return 1; + while (1) + { + /* read until blank line */ + n = http_read_line (print_fd, sok, buf, sizeof (buf)); + if (n < 1 || (n == 1 && buf[0] == '\n')) + break; + } + return 0; +} + +static int +traverse_proxy (int proxy_type, int print_fd, int sok, char *ip, int port, struct msproxy_state_t *state, netstore *ns_proxy, int csok4, int csok6, int *csok, char bound) +{ + switch (proxy_type) + { + case 1: + return traverse_wingate (print_fd, sok, ip, port); + case 2: + return traverse_socks (print_fd, sok, ip, port); + case 3: + return traverse_socks5 (print_fd, sok, ip, port); + case 4: + return traverse_http (print_fd, sok, ip, port); +#ifdef USE_MSPROXY + case 5: + return traverse_msproxy (sok, ip, port, state, ns_proxy, csok4, csok6, csok, bound); +#endif + } + + return 1; +} + +/* this is the child process making the connection attempt */ + +static int +server_child (server * serv) +{ + netstore *ns_server; + netstore *ns_proxy = NULL; + netstore *ns_local; + int port = serv->port; + int error; + int sok, psok; + char *hostname = serv->hostname; + char *real_hostname = NULL; + char *ip; + char *proxy_ip = NULL; + char *local_ip; + int connect_port; + char buf[512]; + char bound = 0; + int proxy_type = 0; + char *proxy_host = NULL; + int proxy_port; + + ns_server = net_store_new (); + + /* is a hostname set? - bind to it */ + if (prefs.hostname[0]) + { + ns_local = net_store_new (); + local_ip = net_resolve (ns_local, prefs.hostname, 0, &real_hostname); + if (local_ip != NULL) + { + snprintf (buf, sizeof (buf), "5\n%s\n", local_ip); + write (serv->childwrite, buf, strlen (buf)); + net_bind (ns_local, serv->sok4, serv->sok6); + bound = 1; + } else + { + write (serv->childwrite, "7\n", 2); + } + net_store_destroy (ns_local); + } + + if (!serv->dont_use_proxy) /* blocked in serverlist? */ + { + if (FALSE) + ; +#ifdef USE_LIBPROXY + else if (prefs.proxy_type == 5) + { + char **proxy_list; + char *url, *proxy; + + url = g_strdup_printf ("irc://%s:%d", hostname, port); + proxy_list = px_proxy_factory_get_proxies (libproxy_factory, url); + + if (proxy_list) { + /* can use only one */ + proxy = proxy_list[0]; + if (!strncmp (proxy, "direct", 6)) + proxy_type = 0; + else if (!strncmp (proxy, "http", 4)) + proxy_type = 4; + else if (!strncmp (proxy, "socks5", 6)) + proxy_type = 3; + else if (!strncmp (proxy, "socks", 5)) + proxy_type = 2; + } + + if (proxy_type) { + char *c; + c = strchr (proxy, ':') + 3; + proxy_host = strdup (c); + c = strchr (proxy_host, ':'); + *c = '\0'; + proxy_port = atoi (c + 1); + } + + g_strfreev (proxy_list); + g_free (url); +#endif + } else if (prefs.proxy_host[0] && + prefs.proxy_type > 0 && + prefs.proxy_use != 2) /* proxy is NOT dcc-only */ + { + proxy_type = prefs.proxy_type; + proxy_host = strdup (prefs.proxy_host); + proxy_port = prefs.proxy_port; + } + } + + serv->proxy_type = proxy_type; + + /* first resolve where we want to connect to */ + if (proxy_type > 0) + { + snprintf (buf, sizeof (buf), "9\n%s\n", proxy_host); + write (serv->childwrite, buf, strlen (buf)); + ip = net_resolve (ns_server, proxy_host, proxy_port, &real_hostname); + free (proxy_host); + if (!ip) + { + write (serv->childwrite, "1\n", 2); + goto xit; + } + connect_port = proxy_port; + + /* if using socks4 or MS Proxy, attempt to resolve ip for irc server */ + if ((proxy_type == 2) || (proxy_type == 5)) + { + ns_proxy = net_store_new (); + proxy_ip = net_resolve (ns_proxy, hostname, port, &real_hostname); + if (!proxy_ip) + { + write (serv->childwrite, "1\n", 2); + goto xit; + } + } else /* otherwise we can just use the hostname */ + proxy_ip = strdup (hostname); + } else + { + ip = net_resolve (ns_server, hostname, port, &real_hostname); + if (!ip) + { + write (serv->childwrite, "1\n", 2); + goto xit; + } + connect_port = port; + } + + snprintf (buf, sizeof (buf), "3\n%s\n%s\n%d\n", + real_hostname, ip, connect_port); + write (serv->childwrite, buf, strlen (buf)); + + if (!serv->dont_use_proxy && (proxy_type == 5)) + error = net_connect (ns_server, serv->proxy_sok4, serv->proxy_sok6, &psok); + else + { + error = net_connect (ns_server, serv->sok4, serv->sok6, &sok); + psok = sok; + } + + if (error != 0) + { + snprintf (buf, sizeof (buf), "2\n%d\n", sock_error ()); + write (serv->childwrite, buf, strlen (buf)); + } else + { + /* connect succeeded */ + if (proxy_ip) + { + switch (traverse_proxy (proxy_type, serv->childwrite, psok, proxy_ip, port, &serv->msp_state, ns_proxy, serv->sok4, serv->sok6, &sok, bound)) + { + case 0: /* success */ +#ifdef USE_MSPROXY + if (!serv->dont_use_proxy && (proxy_type == 5)) + snprintf (buf, sizeof (buf), "4\n%d %d %d %d %d\n", sok, psok, serv->msp_state.clientid, serv->msp_state.serverid, + serv->msp_state.seq_sent); + else +#endif + snprintf (buf, sizeof (buf), "4\n%d\n", sok); /* success */ + write (serv->childwrite, buf, strlen (buf)); + break; + case 1: /* socks traversal failed */ + write (serv->childwrite, "8\n", 2); + break; + } + } else + { + snprintf (buf, sizeof (buf), "4\n%d\n", sok); /* success */ + write (serv->childwrite, buf, strlen (buf)); + } + } + +xit: + +#if defined (USE_IPV6) || defined (WIN32) + /* this is probably not needed */ + net_store_destroy (ns_server); + if (ns_proxy) + net_store_destroy (ns_proxy); +#endif + + /* no need to free ip/real_hostname, this process is exiting */ +#ifdef WIN32 + /* under win32 we use a thread -> shared memory, must free! */ + if (proxy_ip) + free (proxy_ip); + if (ip) + free (ip); + if (real_hostname) + free (real_hostname); +#endif + + return 0; +} + +static void +server_connect (server *serv, char *hostname, int port, int no_login) +{ + int pid, read_des[2]; + session *sess = serv->server_session; + +#ifdef USE_OPENSSL + if (!ctx && serv->use_ssl) + { + if (!(ctx = _SSL_context_init (ssl_cb_info, FALSE))) + { + fprintf (stderr, "_SSL_context_init failed\n"); + exit (1); + } + } +#endif + + if (!hostname[0]) + return; + + if (port < 0) + { + /* use default port for this server type */ + port = 6667; +#ifdef USE_OPENSSL + if (serv->use_ssl) + port = 9999; +#endif + } + port &= 0xffff; /* wrap around */ + + if (serv->connected || serv->connecting || serv->recondelay_tag) + server_disconnect (sess, TRUE, -1); + + fe_progressbar_start (sess); + + EMIT_SIGNAL (XP_TE_SERVERLOOKUP, sess, hostname, NULL, NULL, NULL, 0); + + safe_strcpy (serv->servername, hostname, sizeof (serv->servername)); + /* overlap illegal in strncpy */ + if (hostname != serv->hostname) + safe_strcpy (serv->hostname, hostname, sizeof (serv->hostname)); + +#ifdef USE_OPENSSL + if (serv->use_ssl) + { + char cert_file[256]; + + /* first try network specific cert/key */ + snprintf (cert_file, sizeof (cert_file), "%s/%s.pem", + get_xdir_fs (), server_get_network (serv, TRUE)); + if (SSL_CTX_use_certificate_file (ctx, cert_file, SSL_FILETYPE_PEM) == 1) + SSL_CTX_use_PrivateKey_file (ctx, cert_file, SSL_FILETYPE_PEM); + else + { + /* if that doesn't exist, try ~/.xchat2/client.pem */ + snprintf (cert_file, sizeof (cert_file), "%s/%s.pem", + get_xdir_fs (), "client"); + if (SSL_CTX_use_certificate_file (ctx, cert_file, SSL_FILETYPE_PEM) == 1) + SSL_CTX_use_PrivateKey_file (ctx, cert_file, SSL_FILETYPE_PEM); + } + } +#endif + + server_set_defaults (serv); + serv->connecting = TRUE; + serv->port = port; + serv->no_login = no_login; + + fe_server_event (serv, FE_SE_CONNECTING, 0); + fe_set_away (serv); + server_flush_queue (serv); + +#ifdef WIN32 + if (_pipe (read_des, 4096, _O_BINARY) < 0) +#else + if (pipe (read_des) < 0) +#endif + return; +#ifdef __EMX__ /* os/2 */ + setmode (read_des[0], O_BINARY); + setmode (read_des[1], O_BINARY); +#endif + serv->childread = read_des[0]; + serv->childwrite = read_des[1]; + + /* create both sockets now, drop one later */ + net_sockets (&serv->sok4, &serv->sok6); +#ifdef USE_MSPROXY + /* In case of MS Proxy we have a separate UDP control connection */ + if (!serv->dont_use_proxy && (serv->proxy_type == 5)) + udp_sockets (&serv->proxy_sok4, &serv->proxy_sok6); + else +#endif + { + serv->proxy_sok4 = -1; + serv->proxy_sok6 = -1; + } + +#ifdef WIN32 + CloseHandle (CreateThread (NULL, 0, + (LPTHREAD_START_ROUTINE)server_child, + serv, 0, (DWORD *)&pid)); +#else +#ifdef LOOKUPD + rand(); /* CL: net_resolve calls rand() when LOOKUPD is set, so prepare a different seed for each child. This method giver a bigger variation in seed values than calling srand(time(0)) in the child itself. */ +#endif + switch (pid = fork ()) + { + case -1: + return; + + case 0: + /* this is the child */ + setuid (getuid ()); + server_child (serv); + _exit (0); + } +#endif + serv->childpid = pid; + serv->iotag = fe_input_add (serv->childread, FIA_READ, server_read_child, + serv); +} + +void +server_fill_her_up (server *serv) +{ + serv->connect = server_connect; + serv->disconnect = server_disconnect; + serv->cleanup = server_cleanup; + serv->flush_queue = server_flush_queue; + serv->auto_reconnect = auto_reconnect; + + proto_fill_her_up (serv); +} + +void +server_set_encoding (server *serv, char *new_encoding) +{ + char *space; + + if (serv->encoding) + { + free (serv->encoding); + /* can be left as NULL to indicate system encoding */ + serv->encoding = NULL; + serv->using_cp1255 = FALSE; + serv->using_irc = FALSE; + } + + if (new_encoding) + { + serv->encoding = strdup (new_encoding); + /* the serverlist GUI might have added a space + and short description - remove it. */ + space = strchr (serv->encoding, ' '); + if (space) + space[0] = 0; + + /* server_inline() uses these flags */ + if (!strcasecmp (serv->encoding, "CP1255") || + !strcasecmp (serv->encoding, "WINDOWS-1255")) + serv->using_cp1255 = TRUE; + else if (!strcasecmp (serv->encoding, "IRC")) + serv->using_irc = TRUE; + } +} + +server * +server_new (void) +{ + static int id = 0; + server *serv; + + serv = malloc (sizeof (struct server)); + memset (serv, 0, sizeof (struct server)); + + /* use server.c and proto-irc.c functions */ + server_fill_her_up (serv); + + serv->id = id++; + serv->sok = -1; + strcpy (serv->nick, prefs.nick1); + server_set_defaults (serv); + + serv_list = g_slist_prepend (serv_list, serv); + + fe_new_server (serv); + + return serv; +} + +int +is_server (server *serv) +{ + return g_slist_find (serv_list, serv) ? 1 : 0; +} + +void +server_set_defaults (server *serv) +{ + if (serv->chantypes) + free (serv->chantypes); + if (serv->chanmodes) + free (serv->chanmodes); + if (serv->nick_prefixes) + free (serv->nick_prefixes); + if (serv->nick_modes) + free (serv->nick_modes); + + serv->chantypes = strdup ("#&!+"); + serv->chanmodes = strdup ("beI,k,l"); + serv->nick_prefixes = strdup ("@%+"); + serv->nick_modes = strdup ("ohv"); + + serv->nickcount = 1; + serv->nickservtype = 1; + serv->end_of_motd = FALSE; + serv->is_away = FALSE; + serv->supports_watch = FALSE; + serv->bad_prefix = FALSE; + serv->use_who = TRUE; + serv->have_namesx = FALSE; + serv->have_uhnames = FALSE; + serv->have_whox = FALSE; + serv->have_capab = FALSE; + serv->have_idmsg = FALSE; + serv->have_except = FALSE; +} + +char * +server_get_network (server *serv, gboolean fallback) +{ + if (serv->network) + return ((ircnet *)serv->network)->name; + + if (fallback) + return serv->servername; + + return NULL; +} + +void +server_set_name (server *serv, char *name) +{ + GSList *list = sess_list; + session *sess; + + if (name[0] == 0) + name = serv->hostname; + + /* strncpy parameters must NOT overlap */ + if (name != serv->servername) + { + safe_strcpy (serv->servername, name, sizeof (serv->servername)); + } + + while (list) + { + sess = (session *) list->data; + if (sess->server == serv) + fe_set_title (sess); + list = list->next; + } + + if (serv->server_session->type == SESS_SERVER) + { + if (serv->network) + { + safe_strcpy (serv->server_session->channel, ((ircnet *)serv->network)->name, CHANLEN); + } else + { + safe_strcpy (serv->server_session->channel, name, CHANLEN); + } + fe_set_channel (serv->server_session); + } +} + +struct away_msg * +server_away_find_message (server *serv, char *nick) +{ + struct away_msg *away; + GSList *list = away_list; + while (list) + { + away = (struct away_msg *) list->data; + if (away->server == serv && !serv->p_cmp (nick, away->nick)) + return away; + list = list->next; + } + return NULL; +} + +static void +server_away_free_messages (server *serv) +{ + GSList *list, *next; + struct away_msg *away; + + list = away_list; + while (list) + { + away = list->data; + next = list->next; + if (away->server == serv) + { + away_list = g_slist_remove (away_list, away); + if (away->message) + free (away->message); + free (away); + next = away_list; + } + list = next; + } +} + +void +server_away_save_message (server *serv, char *nick, char *msg) +{ + struct away_msg *away = server_away_find_message (serv, nick); + + if (away) /* Change message for known user */ + { + if (away->message) + free (away->message); + away->message = strdup (msg); + } else + /* Create brand new entry */ + { + away = malloc (sizeof (struct away_msg)); + if (away) + { + away->server = serv; + safe_strcpy (away->nick, nick, sizeof (away->nick)); + away->message = strdup (msg); + away_list = g_slist_prepend (away_list, away); + } + } +} + +void +server_free (server *serv) +{ + serv->cleanup (serv); + + serv_list = g_slist_remove (serv_list, serv); + + dcc_notify_kill (serv); + serv->flush_queue (serv); + server_away_free_messages (serv); + + free (serv->nick_modes); + free (serv->nick_prefixes); + free (serv->chanmodes); + free (serv->chantypes); + if (serv->bad_nick_prefixes) + free (serv->bad_nick_prefixes); + if (serv->last_away_reason) + free (serv->last_away_reason); + if (serv->encoding) + free (serv->encoding); + if (serv->autojoin) + free (serv->autojoin); + + fe_server_callback (serv); + + free (serv); + + notify_cleanup (); +} diff --git a/src/common/server.h b/src/common/server.h new file mode 100644 index 00000000..874d27b2 --- /dev/null +++ b/src/common/server.h @@ -0,0 +1,26 @@ +#ifndef XCHAT_SERVER_H +#define XCHAT_SERVER_H + +extern GSList *serv_list; + +/* eventually need to keep the tcp_* functions isolated to server.c */ +int tcp_send_len (server *serv, char *buf, int len); +int tcp_send (server *serv, char *buf); +void tcp_sendf (server *serv, char *fmt, ...); +int tcp_send_real (void *ssl, int sok, char *encoding, int using_irc, char *buf, int len); + +server *server_new (void); +int is_server (server *serv); +void server_fill_her_up (server *serv); +void server_set_encoding (server *serv, char *new_encoding); +void server_set_defaults (server *serv); +char *server_get_network (server *serv, gboolean fallback); +void server_set_name (server *serv, char *name); +void server_free (server *serv); + +void server_away_save_message (server *serv, char *nick, char *msg); +struct away_msg *server_away_find_message (server *serv, char *nick); + +void base64_encode (char *to, char *from, unsigned int len); + +#endif diff --git a/src/common/servlist.c b/src/common/servlist.c new file mode 100644 index 00000000..20a156a8 --- /dev/null +++ b/src/common/servlist.c @@ -0,0 +1,1311 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "xchat.h" +#include <glib/ghash.h> + +#include "cfgfiles.h" +#include "fe.h" +#include "server.h" +#include "text.h" +#include "util.h" /* token_foreach */ +#include "xchatc.h" + +#include "servlist.h" + + +struct defaultserver +{ + char *network; + char *host; + char *channel; + char *charset; +}; + +static const struct defaultserver def[] = +{ + {"2600net", 0}, + {0, "irc.2600.net"}, + + {"7-indonesia", 0}, + {0, "irc.7-indonesia.org"}, + + {"AccessIRC", 0}, + {0, "irc.accessirc.net"}, + {0, "eu.accessirc.net"}, + + {"AfterNET", 0}, + {0, "irc.afternet.org"}, + {0, "us.afternet.org"}, + {0, "eu.afternet.org"}, + + {"Aitvaras", 0}, +#ifdef USE_IPV6 +#ifdef USE_OPENSSL + {0, "irc6.ktu.lt/+7668"}, +#endif + {0, "irc6.ktu.lt/7666"}, +#endif +#ifdef USE_OPENSSL + {0, "irc.data.lt/+6668"}, + {0, "irc-ssl.omnitel.net/+6668"}, + {0, "irc-ssl.le.lt/+9999"}, +#endif + {0, "irc.data.lt"}, + {0, "irc.omnitel.net"}, + {0, "irc.ktu.lt"}, + {0, "irc.le.lt"}, + {0, "irc.takas.lt"}, + {0, "irc.5ci.net"}, + {0, "irc.kis.lt"}, + + {"AmigaNet", 0}, + {0, "irc.amiganet.org"}, + {0, "us.amiganet.org"}, + {0, "uk.amiganet.org"}, +/* {0, "no.amiganet.org"}, + {0, "au.amiganet.org"},*/ + + {"ARCNet", 0}, + {0, "se1.arcnet.vapor.com"}, + {0, "us1.arcnet.vapor.com"}, + {0, "us2.arcnet.vapor.com"}, + {0, "us3.arcnet.vapor.com"}, + {0, "ca1.arcnet.vapor.com"}, + {0, "de1.arcnet.vapor.com"}, + {0, "de3.arcnet.vapor.com"}, + {0, "ch1.arcnet.vapor.com"}, + {0, "be1.arcnet.vapor.com"}, + {0, "nl3.arcnet.vapor.com"}, + {0, "uk1.arcnet.vapor.com"}, + {0, "uk2.arcnet.vapor.com"}, +/* {0, "uk3.arcnet.vapor.com"},*/ + {0, "fr1.arcnet.vapor.com"}, + + {"AstroLink", 0}, + {0, "irc.astrolink.org"}, + + {"AustNet", 0}, + {0, "au.austnet.org"}, + {0, "us.austnet.org"}, + {0, "ca.austnet.org"}, + +/* {"AxeNet", 0}, + {0, "irc.axenet.org"}, + {0, "angel.axenet.org"}, + {0, "energy.axenet.org"}, + {0, "python.axenet.org"},*/ + + {"AzzurraNet", 0}, + {0, "irc.azzurra.org"}, + {0, "crypto.azzurra.org"}, + + {"Beirut", 0}, + {0, "irc.beirut.com"}, + + {"ChattingAway", 0}, + {0, "irc.chattingaway.com"}, + + {"ChatJunkies", 0, "#xchat"}, + {0, "irc.chatjunkies.org"}, + {0, "nl.chatjunkies.org"}, + + {"ChatNet", 0}, + {0, "US.ChatNet.Org"}, + {0, "EU.ChatNet.Org"}, + + {"ChatSociety", 0}, + {0, "us.chatsociety.net"}, + {0, "eu.chatsociety.net"}, + + {"ChatSpike", 0}, + {0, "irc.chatspike.net"}, + + {"CoolChat", 0}, + {0, "irc.coolchat.net"}, +/* {0, "unix.coolchat.net"}, + {0, "toronto.coolchat.net"},*/ + + {"Criten", 0}, + {0, "irc.criten.net"}, + {0, "irc.eu.criten.net"}, + + {"DALnet", 0}, + {0, "irc.dal.net"}, + {0, "irc.eu.dal.net"}, + + {"Dark-Tou-Net", 0}, + {0, "irc.d-t-net.de"}, + {0, "bw.d-t-net.de"}, + {0, "nc.d-t-net.de"}, + {0, "wakka.d-t-net.de"}, + + {"DarkMyst", 0}, + {0, "irc.darkmyst.org"}, + + {"DeepIRC", 0}, + {0, "irc.deepirc.net"}, + + {"DeltaAnime", 0}, + {0, "irc.deltaanime.net"}, + + {"EFnet", 0}, + {0, "irc.blackened.com"}, + {0, "irc.Prison.NET"}, + {0, "irc.Qeast.net"}, + {0, "irc.efnet.pl"}, + {0, "efnet.demon.co.uk"}, +/* {0, "irc.lagged.org"},*/ + {0, "irc.lightning.net"}, + {0, "irc.mindspring.com"}, + {0, "irc.easynews.com"}, + {0, "irc.servercentral.net"}, + + {"EnterTheGame", 0}, + {0, "IRC.EnterTheGame.Com"}, + + {"EUIrc", 0}, + {0, "irc.euirc.net"}, + {0, "irc.ham.de.euirc.net"}, + {0, "irc.ber.de.euirc.net"}, + {0, "irc.ffm.de.euirc.net"}, + {0, "irc.bre.de.euirc.net"}, + {0, "irc.hes.de.euirc.net"}, + {0, "irc.vie.at.euirc.net"}, + {0, "irc.inn.at.euirc.net"}, + {0, "irc.bas.ch.euirc.net"}, + + {"EuropNet", 0}, + {0, "irc.europnet.org"}, + + {"EU-IRC", 0}, + {0, "irc.eu-irc.net"}, + + {"FDFNet", 0}, + {0, "irc.fdfnet.net"}, + {0, "irc.eu.fdfnet.net"}, + + {"FEFNet", 0}, + {0, "irc.fef.net"}, + {0, "irc.ggn.net"}, + {0, "irc.vendetta.com"}, + + {"FreeNode", 0}, + {0, "irc.freenode.net"}, + +/* {"Freeworld", 0}, + {0, "kabel.freeworld.nu"}, + {0, "irc.freeworld.nu"},*/ + + {"GalaxyNet", 0}, + {0, "irc.galaxynet.org"}, +/* {0, "sprynet.us.galaxynet.org"}, + {0, "atlanta.ga.us.galaxynet.org"},*/ + + {"GamesNET", 0}, + {0, "irc.gamesnet.net"}, +/* {0, "irc.us.gamesnet.net"}, + {0, "east.us.gamesnet.net"}, + {0, "west.us.gamesnet.net"},*/ + {0, "irc.ca.gamesnet.net"}, + {0, "irc.eu.gamesnet.net"}, + + {"GeekShed", 0}, + {0, "irc.geekshed.net"}, + + {"German-Elite", 0}, + {0, "dominion.german-elite.net"}, + {0, "komatu.german-elite.net"}, +/* {0, "liberty.german-elite.net"},*/ + + {"GimpNet", 0}, + {0, "irc.gimp.org"}, +/* {0, "irc.au.gimp.org"},*/ + {0, "irc.us.gimp.org"}, + + {"HabberNet", 0}, + {0, "irc.habber.net"}, + + {"Hashmark", 0}, + {0, "irc.hashmark.net"}, + + {"IdleMonkeys", 0}, + {0, "irc.idlemonkeys.net"}, + +/* {"Infinity-IRC", 0}, + {0, "Atlanta.GA.US.Infinity-IRC.Org"}, + {0, "Babylon.NY.US.Infinity-IRC.Org"}, + {0, "Sunshine.Ca.US.Infinity-IRC.Org"}, + {0, "IRC.Infinity-IRC.Org"},*/ + + {"iZ-smart.net", 0}, + {0, "irc.iZ-smart.net/6666"}, + {0, "irc.iZ-smart.net/6667"}, + {0, "irc.iZ-smart.net/6668"}, + + {"IrcLink", 0}, + {0, "irc.irclink.net"}, + {0, "Alesund.no.eu.irclink.net"}, + {0, "Oslo.no.eu.irclink.net"}, + {0, "frogn.no.eu.irclink.net"}, + {0, "tonsberg.no.eu.irclink.net"}, + + {"IRCNet", 0}, + {0, "irc.ircnet.com"}, + {0, "irc.stealth.net/6668"}, + {0, "ircnet.demon.co.uk"}, +/* {0, "ircnet.hinet.hr"},*/ + {0, "irc.datacomm.ch"}, +/* {0, "ircnet.kaptech.fr"}, + {0, "ircnet.easynet.co.uk"},*/ + {0, "random.ircd.de"}, + {0, "ircnet.netvision.net.il"}, +/* {0, "irc.seed.net.tw"},*/ + {0, "irc.cs.hut.fi"}, + + {"Irctoo.net", 0}, + {0, "irc.irctoo.net"}, + + {"Krstarica", 0}, + {0, "irc.krstarica.com"}, + + {"Librenet", 0}, + {0, "irc.librenet.net"}, + {0, "ielf.fr.librenet.net"}, + + {"LinkNet", 0}, + {0, "irc.link-net.org"}, + {0, "irc.no.link-net.org"}, +/* {0, "irc.gamesden.net.au"},*/ + {0, "irc.bahnhof.se"}, +/* {0, "irc.kinexuseurope.co.uk"}, + {0, "irc.gamiix.com"},*/ + + {"MagicStar", 0}, + {0, "irc.magicstar.net"}, + + {"Majistic", 0}, + {0, "irc.majistic.net"}, + + {"MindForge", 0}, + {0, "irc.mindforge.org"}, + + {"MintIRC", 0}, + {0, "irc.mintirc.net"}, + + {"MIXXnet", 0}, + {0, "irc.mixxnet.net"}, + + {"NeverNET", 0}, + {0, "irc.nevernet.net"}, + {0, "imagine.nevernet.net"}, + {0, "dimension.nevernet.net"}, + {0, "universe.nevernet.net"}, + {0, "wayland.nevernet.net"}, + {0, "forte.nevernet.net"}, + + {"NixHelpNet", 0}, + {0, "irc.nixhelp.org"}, + {0, "us.nixhelp.org"}, + {0, "uk.nixhelp.org"}, + {0, "uk2.nixhelp.org"}, + {0, "uk3.nixhelp.org"}, + {0, "nl.nixhelp.org"}, + {0, "ca.ld.nixhelp.org"}, + {0, "us.co.nixhelp.org"}, + {0, "us.ca.nixhelp.org"}, + {0, "us.pa.nixhelp.org"}, + + {"NullusNet", 0}, + {0, "irc.nullus.net"}, + + {"Oceanius", 0}, + {0, "irc.oceanius.com"}, + + {"OFTC", 0}, + {0, "irc.oftc.net"}, + + {"OtherNet", 0}, + {0, "irc.othernet.org"}, + + {"OzNet", 0}, + {0, "irc.oz.org"}, + + {"PIRC.PL", 0}, + {0, "irc.pirc.pl"}, + + {"PTlink", 0}, + {0, "irc.PTlink.net"}, + {0, "aaia.PTlink.net"}, + + {"PTNet, ISP's", 0}, + {0, "irc.PTNet.org"}, + {0, "rccn.PTnet.org"}, + {0, "EUnet.PTnet.org"}, + {0, "madinfo.PTnet.org"}, + {0, "netc2.PTnet.org"}, + {0, "netc1.PTnet.org"}, + {0, "telepac1.ptnet.org"}, + {0, "esoterica.PTnet.org"}, + {0, "ip-hub.ptnet.org"}, + {0, "telepac1.ptnet.org"}, + {0, "nortenet.PTnet.org"}, + + {"PTNet, UNI", 0}, + {0, "irc.PTNet.org"}, + {0, "rccn.PTnet.org"}, + {0, "uevora.PTnet.org"}, + {0, "umoderna.PTnet.org"}, + {0, "ist.PTnet.org"}, + {0, "aaum.PTnet.org"}, + {0, "uc.PTnet.org"}, + {0, "ualg.ptnet.org"}, + {0, "madinfo.PTnet.org"}, +/* {0, "isep.PTnet.org"},*/ + {0, "ua.PTnet.org"}, + {0, "ipg.PTnet.org"}, + {0, "isec.PTnet.org"}, + {0, "utad.PTnet.org"}, + {0, "iscte.PTnet.org"}, + {0, "ubi.PTnet.org"}, + + {"QuakeNet", 0}, + {0, "irc.quakenet.org"}, + {0, "irc.se.quakenet.org"}, + {0, "irc.dk.quakenet.org"}, + {0, "irc.no.quakenet.org"}, + {0, "irc.fi.quakenet.org"}, + {0, "irc.be.quakenet.org"}, + {0, "irc.uk.quakenet.org"}, + {0, "irc.de.quakenet.org"}, + {0, "irc.it.quakenet.org"}, + + {"RebelChat", 0}, + {0, "irc.rebelchat.org"}, + +/* {"Recycled-IRC", 0}, + {0, "irc.recycled-irc.org"}, + {0, "vermin.recycled-irc.org"}, + {0, "waste.recycled-irc.org"}, + {0, "lumber.recycled-irc.org"}, + {0, "trash.recycled-irc.org"}, + {0, "unwashed.recycled-irc.org"}, + {0, "garbage.recycled-irc.org"}, + {0, "dustbin.recycled-irc.org"},*/ + + {"RizeNET", 0}, + {0, "irc.rizenet.org"}, + {0, "omega.rizenet.org"}, + {0, "evelance.rizenet.org"}, + {0, "lisa.rizenet.org"}, + {0, "scott.rizenet.org"}, + + {"Rizon", 0}, + {0, "irc.rizon.net"}, + + {"RusNet", 0, 0, "KOI8-R (Cyrillic)"}, + {0, "irc.tomsk.net"}, + {0, "irc.rinet.ru"}, + {0, "irc.run.net"}, + {0, "irc.ru"}, + {0, "irc.lucky.net"}, + + {"SceneNet", 0}, + {0, "irc.scene.org"}, + {0, "irc.eu.scene.org"}, + {0, "irc.us.scene.org"}, + + {"SeilEn.de", 0}, + {0, "irc.seilen.de"}, + + {"SlashNET", 0}, + {0, "irc.slashnet.org"}, + {0, "area51.slashnet.org"}, + {0, "moo.slashnet.org"}, + {0, "radon.slashnet.org"}, + + {"Sohbet.Net", 0}, + {0, "irc.sohbet.net"}, + + {"SolidIRC", 0}, + {0, "irc.solidirc.com"}, + + {"SorceryNet", 0}, + {0, "irc.sorcery.net/9000"}, + {0, "irc.us.sorcery.net/9000"}, + {0, "irc.eu.sorcery.net/9000"}, + + {"Spidernet", 0}, + {0, "us.spidernet.org"}, + {0, "eu.spidernet.org"}, + {0, "irc.spidernet.org"}, + + {"StarChat", 0}, + {0, "irc.starchat.net"}, + {0, "gainesville.starchat.net"}, + {0, "freebsd.starchat.net"}, + {0, "sunset.starchat.net"}, + {0, "revenge.starchat.net"}, + {0, "tahoma.starchat.net"}, + {0, "neo.starchat.net"}, + + {"TNI3", 0}, + {0, "irc.tni3.com"}, + + {"TURLINet", 0}, + {0, "irc.turli.net"}, + {0, "irc.servx.ru"}, + {0, "irc.gavnos.ru"}, + + {"UnderNet", 0}, + {0, "us.undernet.org"}, + {0, "eu.undernet.org"}, + + {"UniBG", 0}, + {0, "irc.lirex.com"}, + {0, "irc.naturella.com"}, + {0, "irc.spnet.net"}, + {0, "irc.techno-link.com"}, + {0, "irc.telecoms.bg"}, + {0, "irc.tu-varna.edu"}, + + {"Whiffle", 0}, + {0, "irc.whiffle.org"}, + + {"Worldnet", 0}, + {0, "irc.worldnet.net"}, + {0, "irc.fr.worldnet.net"}, + + {"Xentonix.net", 0}, + {0, "irc.xentonix.net"}, + + {"XWorld", 0}, + {0, "Buffalo.NY.US.XWorld.org"}, + {0, "Minneapolis.MN.US.Xworld.Org"}, + {0, "Rochester.NY.US.XWorld.org"}, + {0, "Bayern.DE.EU.XWorld.Org"}, + {0, "Chicago.IL.US.XWorld.Org"}, + + {0,0} +}; + +GSList *network_list = 0; + + +void +servlist_connect (session *sess, ircnet *net, gboolean join) +{ + ircserver *ircserv; + GSList *list; + char *port; + server *serv; + + if (!sess) + sess = new_ircwindow (NULL, NULL, SESS_SERVER, TRUE); + + serv = sess->server; + + /* connect to the currently selected Server-row */ + list = g_slist_nth (net->servlist, net->selected); + if (!list) + list = net->servlist; + if (!list) + return; + ircserv = list->data; + + /* incase a protocol switch is added to the servlist gui */ + server_fill_her_up (sess->server); + + if (join) + { + sess->willjoinchannel[0] = 0; + + if (net->autojoin) + { + if (serv->autojoin) + free (serv->autojoin); + serv->autojoin = strdup (net->autojoin); + } + } + + serv->password[0] = 0; + if (net->pass) + safe_strcpy (serv->password, net->pass, sizeof (serv->password)); + + if (net->flags & FLAG_USE_GLOBAL) + { + strcpy (serv->nick, prefs.nick1); + } else + { + if (net->nick) + strcpy (serv->nick, net->nick); + } + + serv->dont_use_proxy = (net->flags & FLAG_USE_PROXY) ? FALSE : TRUE; + +#ifdef USE_OPENSSL + serv->use_ssl = (net->flags & FLAG_USE_SSL) ? TRUE : FALSE; + serv->accept_invalid_cert = + (net->flags & FLAG_ALLOW_INVALID) ? TRUE : FALSE; +#endif + + serv->network = net; + + port = strrchr (ircserv->hostname, '/'); + if (port) + { + *port = 0; + + /* support "+port" to indicate SSL (like mIRC does) */ + if (port[1] == '+') + { +#ifdef USE_OPENSSL + serv->use_ssl = TRUE; +#endif + serv->connect (serv, ircserv->hostname, atoi (port + 2), FALSE); + } else + { + serv->connect (serv, ircserv->hostname, atoi (port + 1), FALSE); + } + + *port = '/'; + } else + serv->connect (serv, ircserv->hostname, -1, FALSE); + + server_set_encoding (serv, net->encoding); +} + +int +servlist_connect_by_netname (session *sess, char *network, gboolean join) +{ + ircnet *net; + GSList *list = network_list; + + while (list) + { + net = list->data; + + if (strcasecmp (net->name, network) == 0) + { + servlist_connect (sess, net, join); + return 1; + } + + list = list->next; + } + + return 0; +} + +int +servlist_have_auto (void) +{ + GSList *list = network_list; + ircnet *net; + + while (list) + { + net = list->data; + + if (net->flags & FLAG_AUTO_CONNECT) + return 1; + + list = list->next; + } + + return 0; +} + +int +servlist_auto_connect (session *sess) +{ + GSList *list = network_list; + ircnet *net; + int ret = 0; + + while (list) + { + net = list->data; + + if (net->flags & FLAG_AUTO_CONNECT) + { + servlist_connect (sess, net, TRUE); + ret = 1; + } + + list = list->next; + } + + return ret; +} + +static gint +servlist_cycle_cb (server *serv) +{ + if (serv->network) + { + PrintTextf (serv->server_session, + _("Cycling to next server in %s...\n"), ((ircnet *)serv->network)->name); + servlist_connect (serv->server_session, serv->network, TRUE); + } + + return 0; +} + +int +servlist_cycle (server *serv) +{ + ircnet *net; + int max, del; + + net = serv->network; + if (net) + { + max = g_slist_length (net->servlist); + if (max > 0) + { + /* try the next server, if that option is on */ + if (net->flags & FLAG_CYCLE) + { + net->selected++; + if (net->selected >= max) + net->selected = 0; + } + + del = prefs.recon_delay * 1000; + if (del < 1000) + del = 500; /* so it doesn't block the gui */ + + if (del) + serv->recondelay_tag = fe_timeout_add (del, servlist_cycle_cb, serv); + else + servlist_connect (serv->server_session, net, TRUE); + + return TRUE; + } + } + + return FALSE; +} + +ircserver * +servlist_server_find (ircnet *net, char *name, int *pos) +{ + GSList *list = net->servlist; + ircserver *serv; + int i = 0; + + while (list) + { + serv = list->data; + if (strcmp (serv->hostname, name) == 0) + { + if (pos) + *pos = i; + return serv; + } + i++; + list = list->next; + } + + return NULL; +} + +/* find a network (e.g. (ircnet *) to "FreeNode") from a hostname + (e.g. "irc.eu.freenode.net") */ + +ircnet * +servlist_net_find_from_server (char *server_name) +{ + GSList *list = network_list; + GSList *slist; + ircnet *net; + ircserver *serv; + + while (list) + { + net = list->data; + + slist = net->servlist; + while (slist) + { + serv = slist->data; + if (strcasecmp (serv->hostname, server_name) == 0) + return net; + slist = slist->next; + } + + list = list->next; + } + + return NULL; +} + +ircnet * +servlist_net_find (char *name, int *pos, int (*cmpfunc) (const char *, const char *)) +{ + GSList *list = network_list; + ircnet *net; + int i = 0; + + while (list) + { + net = list->data; + if (cmpfunc (net->name, name) == 0) + { + if (pos) + *pos = i; + return net; + } + i++; + list = list->next; + } + + return NULL; +} + +ircserver * +servlist_server_add (ircnet *net, char *name) +{ + ircserver *serv; + + serv = malloc (sizeof (ircserver)); + memset (serv, 0, sizeof (ircserver)); + serv->hostname = strdup (name); + + net->servlist = g_slist_append (net->servlist, serv); + + return serv; +} + +void +servlist_server_remove (ircnet *net, ircserver *serv) +{ + free (serv->hostname); + free (serv); + net->servlist = g_slist_remove (net->servlist, serv); +} + +static void +servlist_server_remove_all (ircnet *net) +{ + ircserver *serv; + + while (net->servlist) + { + serv = net->servlist->data; + servlist_server_remove (net, serv); + } +} + +static void +free_and_clear (char *str) +{ + if (str) + { + char *orig = str; + while (*str) + *str++ = 0; + free (orig); + } +} + +/* executed on exit: Clear any password strings */ + +void +servlist_cleanup (void) +{ + GSList *list; + ircnet *net; + + for (list = network_list; list; list = list->next) + { + net = list->data; + free_and_clear (net->pass); + free_and_clear (net->nickserv); + } +} + +void +servlist_net_remove (ircnet *net) +{ + GSList *list; + server *serv; + + servlist_server_remove_all (net); + network_list = g_slist_remove (network_list, net); + + if (net->nick) + free (net->nick); + if (net->nick2) + free (net->nick2); + if (net->user) + free (net->user); + if (net->real) + free (net->real); + free_and_clear (net->pass); + if (net->autojoin) + free (net->autojoin); + if (net->command) + free (net->command); + free_and_clear (net->nickserv); + if (net->comment) + free (net->comment); + if (net->encoding) + free (net->encoding); + free (net->name); + free (net); + + /* for safety */ + list = serv_list; + while (list) + { + serv = list->data; + if (serv->network == net) + serv->network = NULL; + list = list->next; + } +} + +ircnet * +servlist_net_add (char *name, char *comment, int prepend) +{ + ircnet *net; + + net = malloc (sizeof (ircnet)); + memset (net, 0, sizeof (ircnet)); + net->name = strdup (name); +/* net->comment = strdup (comment);*/ + net->flags = FLAG_CYCLE | FLAG_USE_GLOBAL | FLAG_USE_PROXY; + + if (prepend) + network_list = g_slist_prepend (network_list, net); + else + network_list = g_slist_append (network_list, net); + + return net; +} + +static void +servlist_load_defaults (void) +{ + int i = 0, j = 0; + ircnet *net = NULL; + + while (1) + { + if (def[i].network) + { + net = servlist_net_add (def[i].network, def[i].host, FALSE); + net->encoding = strdup ("IRC (Latin/Unicode Hybrid)"); + if (def[i].channel) + net->autojoin = strdup (def[i].channel); + if (def[i].charset) + { + free (net->encoding); + net->encoding = strdup (def[i].charset); + } + if (g_str_hash (def[i].network) == 0x8e1b96f7) + prefs.slist_select = j; + j++; + } else + { + servlist_server_add (net, def[i].host); + if (!def[i+1].host && !def[i+1].network) + break; + } + i++; + } +} + +static int +servlist_load (void) +{ + FILE *fp; + char buf[2048]; + int len; + char *tmp; + ircnet *net = NULL; + + fp = xchat_fopen_file ("servlist_.conf", "r", 0); + if (!fp) + return FALSE; + + while (fgets (buf, sizeof (buf) - 2, fp)) + { + len = strlen (buf); + buf[len] = 0; + buf[len-1] = 0; /* remove the trailing \n */ + if (net) + { + switch (buf[0]) + { + case 'I': + net->nick = strdup (buf + 2); + break; + case 'i': + net->nick2 = strdup (buf + 2); + break; + case 'U': + net->user = strdup (buf + 2); + break; + case 'R': + net->real = strdup (buf + 2); + break; + case 'P': + net->pass = strdup (buf + 2); + break; + case 'J': + net->autojoin = strdup (buf + 2); + break; + case 'C': + if (net->command) + { + /* concat extra commands with a \n separator */ + tmp = net->command; + net->command = malloc (strlen (tmp) + strlen (buf + 2) + 2); + strcpy (net->command, tmp); + strcat (net->command, "\n"); + strcat (net->command, buf + 2); + free (tmp); + } else + net->command = strdup (buf + 2); + break; + case 'F': + net->flags = atoi (buf + 2); + break; + case 'D': + net->selected = atoi (buf + 2); + break; + case 'E': + net->encoding = strdup (buf + 2); + break; + case 'S': /* new server/hostname for this network */ + servlist_server_add (net, buf + 2); + break; + case 'B': + net->nickserv = strdup (buf + 2); + break; + } + } + if (buf[0] == 'N') + net = servlist_net_add (buf + 2, /* comment */ NULL, FALSE); + } + fclose (fp); + + return TRUE; +} + +void +servlist_init (void) +{ + if (!network_list) + if (!servlist_load ()) + servlist_load_defaults (); +} + +/* check if a charset is known by Iconv */ +int +servlist_check_encoding (char *charset) +{ + GIConv gic; + char *c; + + c = strchr (charset, ' '); + if (c) + c[0] = 0; + + if (!strcasecmp (charset, "IRC")) /* special case */ + { + if (c) + c[0] = ' '; + return TRUE; + } + + gic = g_iconv_open (charset, "UTF-8"); + + if (c) + c[0] = ' '; + + if (gic != (GIConv)-1) + { + g_iconv_close (gic); + return TRUE; + } + + return FALSE; +} + +static int +servlist_write_ccmd (char *str, void *fp) +{ + return fprintf (fp, "C=%s\n", (str[0] == '/') ? str + 1 : str); +} + + +int +servlist_save (void) +{ + FILE *fp; + char buf[256]; + ircnet *net; + ircserver *serv; + GSList *list; + GSList *hlist; +#ifndef WIN32 + int first = FALSE; + + snprintf (buf, sizeof (buf), "%s/servlist_.conf", get_xdir_fs ()); + if (access (buf, F_OK) != 0) + first = TRUE; +#endif + + fp = xchat_fopen_file ("servlist_.conf", "w", 0); + if (!fp) + return FALSE; + +#ifndef WIN32 + if (first) + chmod (buf, 0600); +#endif + fprintf (fp, "v="PACKAGE_VERSION"\n\n"); + + list = network_list; + while (list) + { + net = list->data; + + fprintf (fp, "N=%s\n", net->name); + if (net->nick) + fprintf (fp, "I=%s\n", net->nick); + if (net->nick2) + fprintf (fp, "i=%s\n", net->nick2); + if (net->user) + fprintf (fp, "U=%s\n", net->user); + if (net->real) + fprintf (fp, "R=%s\n", net->real); + if (net->pass) + fprintf (fp, "P=%s\n", net->pass); + if (net->autojoin) + fprintf (fp, "J=%s\n", net->autojoin); + if (net->nickserv) + fprintf (fp, "B=%s\n", net->nickserv); + if (net->encoding && strcasecmp (net->encoding, "System") && + strcasecmp (net->encoding, "System default")) + { + fprintf (fp, "E=%s\n", net->encoding); + if (!servlist_check_encoding (net->encoding)) + { + snprintf (buf, sizeof (buf), _("Warning: \"%s\" character set is unknown. No conversion will be applied for network %s."), + net->encoding, net->name); + fe_message (buf, FE_MSG_WARN); + } + } + + if (net->command) + token_foreach (net->command, '\n', servlist_write_ccmd, fp); + + fprintf (fp, "F=%d\nD=%d\n", net->flags, net->selected); + + hlist = net->servlist; + while (hlist) + { + serv = hlist->data; + fprintf (fp, "S=%s\n", serv->hostname); + hlist = hlist->next; + } + + if (fprintf (fp, "\n") < 1) + { + fclose (fp); + return FALSE; + } + + list = list->next; + } + + fclose (fp); + return TRUE; +} + +static void +joinlist_free1 (GSList *list) +{ + GSList *head = list; + + for (; list; list = list->next) + g_free (list->data); + g_slist_free (head); +} + +void +joinlist_free (GSList *channels, GSList *keys) +{ + joinlist_free1 (channels); + joinlist_free1 (keys); +} + +gboolean +joinlist_is_in_list (server *serv, char *channel) +{ + GSList *channels, *keys; + GSList *list; + + if (!serv->network || !((ircnet *)serv->network)->autojoin) + return FALSE; + + joinlist_split (((ircnet *)serv->network)->autojoin, &channels, &keys); + + for (list = channels; list; list = list->next) + { + if (serv->p_cmp (list->data, channel) == 0) + return TRUE; + } + + joinlist_free (channels, keys); + + return FALSE; +} + +gchar * +joinlist_merge (GSList *channels, GSList *keys) +{ + GString *out = g_string_new (NULL); + GSList *list; + int i, j; + + for (; channels; channels = channels->next) + { + g_string_append (out, channels->data); + + if (channels->next) + g_string_append_c (out, ','); + } + + /* count number of REAL keys */ + for (i = 0, list = keys; list; list = list->next) + if (list->data) + i++; + + if (i > 0) + { + g_string_append_c (out, ' '); + + for (j = 0; keys; keys = keys->next) + { + if (keys->data) + { + g_string_append (out, keys->data); + j++; + if (j == i) + break; + } + + if (keys->next) + g_string_append_c (out, ','); + } + } + + return g_string_free (out, FALSE); +} + +void +joinlist_split (char *autojoin, GSList **channels, GSList **keys) +{ + char *parta, *partb; + char *chan, *key; + int len; + + *channels = NULL; + *keys = NULL; + + /* after the first space, the keys begin */ + parta = autojoin; + partb = strchr (autojoin, ' '); + if (partb) + partb++; + + while (1) + { + chan = parta; + key = partb; + + if (1) + { + while (parta[0] != 0 && parta[0] != ',' && parta[0] != ' ') + { + parta++; + } + } + + if (partb) + { + while (partb[0] != 0 && partb[0] != ',' && partb[0] != ' ') + { + partb++; + } + } + + len = parta - chan; + if (len < 1) + break; + *channels = g_slist_append (*channels, g_strndup (chan, len)); + + len = partb - key; + *keys = g_slist_append (*keys, len ? g_strndup (key, len) : NULL); + + if (parta[0] == ' ' || parta[0] == 0) + break; + parta++; + + if (partb) + { + if (partb[0] == 0 || partb[0] == ' ') + partb = NULL; /* no more keys, but maybe more channels? */ + else + partb++; + } + } + +#if 0 + GSList *lista, *listb; + int i; + + printf("-----\n"); + i = 0; + lista = *channels; + listb = *keys; + while (lista) + { + printf("%d. |%s| |%s|\n", i, lista->data, listb->data); + i++; + lista = lista->next; + listb = listb->next; + } + printf("-----\n\n"); +#endif +} + + diff --git a/src/common/servlist.h b/src/common/servlist.h new file mode 100644 index 00000000..9b7734f4 --- /dev/null +++ b/src/common/servlist.h @@ -0,0 +1,62 @@ +#ifndef XCHAT_SERVLIST_H +#define XCHAT_SERVLIST_H + +typedef struct ircserver +{ + char *hostname; +} ircserver; + +typedef struct ircnet +{ + char *name; + char *nick; + char *nick2; + char *user; + char *real; + char *pass; + char *autojoin; + char *command; + char *nickserv; + char *comment; + char *encoding; + GSList *servlist; + int selected; + guint32 flags; +} ircnet; + +extern GSList *network_list; + +#define FLAG_CYCLE 1 +#define FLAG_USE_GLOBAL 2 +#define FLAG_USE_SSL 4 +#define FLAG_AUTO_CONNECT 8 +#define FLAG_USE_PROXY 16 +#define FLAG_ALLOW_INVALID 32 +#define FLAG_FAVORITE 64 +#define FLAG_COUNT 7 + +void servlist_init (void); +int servlist_save (void); +int servlist_cycle (server *serv); +void servlist_connect (session *sess, ircnet *net, gboolean join); +int servlist_connect_by_netname (session *sess, char *network, gboolean join); +int servlist_auto_connect (session *sess); +int servlist_have_auto (void); +int servlist_check_encoding (char *charset); +void servlist_cleanup (void); + +ircnet *servlist_net_add (char *name, char *comment, int prepend); +void servlist_net_remove (ircnet *net); +ircnet *servlist_net_find (char *name, int *pos, int (*cmpfunc) (const char *, const char *)); +ircnet *servlist_net_find_from_server (char *server_name); + +void servlist_server_remove (ircnet *net, ircserver *serv); +ircserver *servlist_server_add (ircnet *net, char *name); +ircserver *servlist_server_find (ircnet *net, char *name, int *pos); + +void joinlist_split (char *autojoin, GSList **channels, GSList **keys); +gboolean joinlist_is_in_list (server *serv, char *channel); +void joinlist_free (GSList *channels, GSList *keys); +gchar *joinlist_merge (GSList *channels, GSList *keys); + +#endif diff --git a/src/common/ssl.c b/src/common/ssl.c new file mode 100644 index 00000000..a18ad47c --- /dev/null +++ b/src/common/ssl.c @@ -0,0 +1,323 @@ +/* + * ssl.c v0.0.3 + * Copyright (C) 2000 -- DaP <profeta@freemail.c3.hu> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <openssl/ssl.h> /* SSL_() */ +#include <openssl/err.h> /* ERR_() */ +#include <time.h> /* asctime() */ +#include <string.h> /* strncpy() */ +#include "ssl.h" /* struct cert_info */ +#include "inet.h" +#include "../../config.h" /* HAVE_SNPRINTF */ + +#ifndef HAVE_SNPRINTF +#define snprintf g_snprintf +#endif + +/* globals */ +static struct chiper_info chiper_info; /* static buffer for _SSL_get_cipher_info() */ +static char err_buf[256]; /* generic error buffer */ + + +/* +++++ Internal functions +++++ */ + +static void +__SSL_fill_err_buf (char *funcname) +{ + int err; + char buf[256]; + + + err = ERR_get_error (); + ERR_error_string (err, buf); + snprintf (err_buf, sizeof (err_buf), "%s: %s (%d)\n", funcname, buf, err); +} + + +static void +__SSL_critical_error (char *funcname) +{ + __SSL_fill_err_buf (funcname); + fprintf (stderr, "%s\n", err_buf); + + exit (1); +} + +/* +++++ SSL functions +++++ */ + +SSL_CTX * +_SSL_context_init (void (*info_cb_func), int server) +{ + SSL_CTX *ctx; +#ifdef WIN32 + int i, r; +#endif + + SSLeay_add_ssl_algorithms (); + SSL_load_error_strings (); + ctx = SSL_CTX_new (server ? SSLv3_server_method() : SSLv3_client_method ()); + + SSL_CTX_set_session_cache_mode (ctx, SSL_SESS_CACHE_BOTH); + SSL_CTX_set_timeout (ctx, 300); + + /* used in SSL_connect(), SSL_accept() */ + SSL_CTX_set_info_callback (ctx, info_cb_func); + +#ifdef WIN32 + /* under win32, OpenSSL needs to be seeded with some randomness */ + for (i = 0; i < 128; i++) + { + r = rand (); + RAND_seed ((unsigned char *)&r, sizeof (r)); + } +#endif + + return(ctx); +} + +static void +ASN1_TIME_snprintf (char *buf, int buf_len, ASN1_TIME * tm) +{ + char *expires = NULL; + BIO *inMem = BIO_new (BIO_s_mem ()); + + ASN1_TIME_print (inMem, tm); + BIO_get_mem_data (inMem, &expires); + buf[0] = 0; + if (expires != NULL) + { + memset (buf, 0, buf_len); + strncpy (buf, expires, 24); + } + BIO_free (inMem); +} + + +static void +broke_oneline (char *oneline, char *parray[]) +{ + char *pt, *ppt; + int i; + + + i = 0; + ppt = pt = oneline + 1; + while ((pt = strchr (pt, '/'))) + { + *pt = 0; + parray[i++] = ppt; + ppt = ++pt; + } + parray[i++] = ppt; + parray[i] = NULL; +} + + +/* + FIXME: Master-Key, Extensions, CA bits + (openssl x509 -text -in servcert.pem) +*/ +int +_SSL_get_cert_info (struct cert_info *cert_info, SSL * ssl) +{ + X509 *peer_cert; + EVP_PKEY *peer_pkey; + /* EVP_PKEY *ca_pkey; */ + /* EVP_PKEY *tmp_pkey; */ + char notBefore[64]; + char notAfter[64]; + int alg; + int sign_alg; + + + if (!(peer_cert = SSL_get_peer_certificate (ssl))) + return (1); /* FATAL? */ + + X509_NAME_oneline (X509_get_subject_name (peer_cert), cert_info->subject, + sizeof (cert_info->subject)); + X509_NAME_oneline (X509_get_issuer_name (peer_cert), cert_info->issuer, + sizeof (cert_info->issuer)); + broke_oneline (cert_info->subject, cert_info->subject_word); + broke_oneline (cert_info->issuer, cert_info->issuer_word); + + alg = OBJ_obj2nid (peer_cert->cert_info->key->algor->algorithm); + sign_alg = OBJ_obj2nid (peer_cert->sig_alg->algorithm); + ASN1_TIME_snprintf (notBefore, sizeof (notBefore), + X509_get_notBefore (peer_cert)); + ASN1_TIME_snprintf (notAfter, sizeof (notAfter), + X509_get_notAfter (peer_cert)); + + peer_pkey = X509_get_pubkey (peer_cert); + + strncpy (cert_info->algorithm, + (alg == NID_undef) ? "Unknown" : OBJ_nid2ln (alg), + sizeof (cert_info->algorithm)); + cert_info->algorithm_bits = EVP_PKEY_bits (peer_pkey); + strncpy (cert_info->sign_algorithm, + (sign_alg == NID_undef) ? "Unknown" : OBJ_nid2ln (sign_alg), + sizeof (cert_info->sign_algorithm)); + /* EVP_PKEY_bits(ca_pkey)); */ + cert_info->sign_algorithm_bits = 0; + strncpy (cert_info->notbefore, notBefore, sizeof (cert_info->notbefore)); + strncpy (cert_info->notafter, notAfter, sizeof (cert_info->notafter)); + + EVP_PKEY_free (peer_pkey); + + /* SSL_SESSION_print_fp(stdout, SSL_get_session(ssl)); */ +/* + if (ssl->session->sess_cert->peer_rsa_tmp) { + tmp_pkey = EVP_PKEY_new(); + EVP_PKEY_assign_RSA(tmp_pkey, ssl->session->sess_cert->peer_rsa_tmp); + cert_info->rsa_tmp_bits = EVP_PKEY_bits (tmp_pkey); + EVP_PKEY_free(tmp_pkey); + } else + fprintf(stderr, "REMOTE SIDE DOESN'T PROVIDES ->peer_rsa_tmp\n"); +*/ + cert_info->rsa_tmp_bits = 0; + + X509_free (peer_cert); + + return (0); +} + + +struct chiper_info * +_SSL_get_cipher_info (SSL * ssl) +{ + SSL_CIPHER *c; + + + c = SSL_get_current_cipher (ssl); + strncpy (chiper_info.version, SSL_CIPHER_get_version (c), + sizeof (chiper_info.version)); + strncpy (chiper_info.chiper, SSL_CIPHER_get_name (c), + sizeof (chiper_info.chiper)); + SSL_CIPHER_get_bits (c, &chiper_info.chiper_bits); + + return (&chiper_info); +} + + +int +_SSL_send (SSL * ssl, char *buf, int len) +{ + int num; + + + num = SSL_write (ssl, buf, len); + + switch (SSL_get_error (ssl, num)) + { + case SSL_ERROR_SSL: /* setup errno! */ + /* ??? */ + __SSL_fill_err_buf ("SSL_write"); + fprintf (stderr, "%s\n", err_buf); + break; + case SSL_ERROR_SYSCALL: + /* ??? */ + perror ("SSL_write/write"); + break; + case SSL_ERROR_ZERO_RETURN: + /* fprintf(stderr, "SSL closed on write\n"); */ + break; + } + + return (num); +} + + +int +_SSL_recv (SSL * ssl, char *buf, int len) +{ + int num; + + + num = SSL_read (ssl, buf, len); + + switch (SSL_get_error (ssl, num)) + { + case SSL_ERROR_SSL: + /* ??? */ + __SSL_fill_err_buf ("SSL_read"); + fprintf (stderr, "%s\n", err_buf); + break; + case SSL_ERROR_SYSCALL: + /* ??? */ + if (!would_block ()) + perror ("SSL_read/read"); + break; + case SSL_ERROR_ZERO_RETURN: + /* fprintf(stdeerr, "SSL closed on read\n"); */ + break; + } + + return (num); +} + + +SSL * +_SSL_socket (SSL_CTX *ctx, int sd) +{ + SSL *ssl; + + + if (!(ssl = SSL_new (ctx))) + /* FATAL */ + __SSL_critical_error ("SSL_new"); + + SSL_set_fd (ssl, sd); + if (ctx->method == SSLv3_client_method()) + SSL_set_connect_state (ssl); + else + SSL_set_accept_state(ssl); + + return (ssl); +} + + +char * +_SSL_set_verify (SSL_CTX *ctx, void *verify_callback, char *cacert) +{ + if (!SSL_CTX_set_default_verify_paths (ctx)) + { + __SSL_fill_err_buf ("SSL_CTX_set_default_verify_paths"); + return (err_buf); + } +/* + if (cacert) + { + if (!SSL_CTX_load_verify_locations (ctx, cacert, NULL)) + { + __SSL_fill_err_buf ("SSL_CTX_load_verify_locations"); + return (err_buf); + } + } +*/ + SSL_CTX_set_verify (ctx, SSL_VERIFY_PEER, verify_callback); + + return (NULL); +} + + +void +_SSL_close (SSL * ssl) +{ + SSL_set_shutdown (ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN); + SSL_free (ssl); + ERR_remove_state (0); /* free state buffer */ +} diff --git a/src/common/ssl.h b/src/common/ssl.h new file mode 100644 index 00000000..26eb0f88 --- /dev/null +++ b/src/common/ssl.h @@ -0,0 +1,65 @@ +/* + ... +*/ + +struct cert_info { + char subject[256]; + char *subject_word[12]; + char issuer[256]; + char *issuer_word[12]; + char algorithm[32]; + int algorithm_bits; + char sign_algorithm[32]; + int sign_algorithm_bits; + char notbefore[32]; + char notafter[32]; + + int rsa_tmp_bits; +}; + +struct chiper_info { + char version[16]; + char chiper[24]; + int chiper_bits; +}; + +SSL_CTX *_SSL_context_init (void (*info_cb_func), int server); +#define _SSL_context_free(a) SSL_CTX_free(a); + +SSL *_SSL_socket (SSL_CTX *ctx, int sd); +char *_SSL_set_verify (SSL_CTX *ctx, void *(verify_callback), char *cacert); +/* + int SSL_connect(SSL *); + int SSL_accept(SSL *); + int SSL_get_fd(SSL *); +*/ +void _SSL_close (SSL * ssl); + +int _SSL_get_cert_info (struct cert_info *cert_info, SSL * ssl); +struct chiper_info *_SSL_get_cipher_info (SSL * ssl); + +/*char *_SSL_add_keypair (SSL_CTX *ctx, char *privkey, char *cert);*/ +/*void _SSL_add_random_keypair(SSL_CTX *ctx, int bits);*/ + +int _SSL_send (SSL * ssl, char *buf, int len); +int _SSL_recv (SSL * ssl, char *buf, int len); + +/* misc */ +/*void broke_oneline (char *oneline, char *parray[]);*/ + +/*char *_SSL_do_cipher_base64(char *buf, int buf_len, char *key, int operation);*/ /* must be freed */ + +/*void *_SSL_get_sess_obj(SSL *ssl, int type);*/ /* NOT must be freed */ +#define _SSL_get_sess_pkey(a) _SSL_get_sess_obj(a, 0) +#define _SSL_get_sess_prkey(a) _SSL_get_sess_obj(a, 1) +#define _SSL_get_sess_x509(a) _SSL_get_sess_obj(a, 2) +/*char *_SSL_get_obj_base64(void *s, int type);*/ /* must be freed */ +#define _SSL_get_pkey_base64(a) _SSL_get_obj_base64(a, 0) +#define _SSL_get_prkey_base64(a) _SSL_get_obj_base64(a, 1) +#define _SSL_get_x509_base64(a) _SSL_get_obj_base64(a, 2) +/*char *_SSL_get_ctx_obj_base64(SSL_CTX *ctx, int type);*/ /* must be freed */ +#define _SSL_get_ctx_pkey_base64(a) _SSL_get_ctx_obj_base64(a, 0) +#define _SSL_get_ctx_prkey_base64(a) _SSL_get_ctx_obj_base64(a, 1) +#define _SSL_get_ctx_x509_base64(a) _SSL_get_ctx_obj_base64(a, 2) + +/*int _SSL_verify_x509(X509 *x509);*/ diff --git a/src/common/text.c b/src/common/text.c new file mode 100644 index 00000000..a2198517 --- /dev/null +++ b/src/common/text.c @@ -0,0 +1,2309 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include <time.h> +#include <sys/types.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/mman.h> + +#include "xchat.h" +#include <glib/ghash.h> +#include "cfgfiles.h" +#include "chanopt.h" +#include "plugin.h" +#include "fe.h" +#include "server.h" +#include "util.h" +#include "outbound.h" +#include "xchatc.h" +#include "text.h" +#ifdef WIN32 +#include <windows.h> +#endif + +struct pevt_stage1 +{ + int len; + char *data; + struct pevt_stage1 *next; +}; + + +static void mkdir_p (char *dir); +static char *log_create_filename (char *channame); + + +static char * +scrollback_get_filename (session *sess, char *buf, int max) +{ + char *net, *chan; + + net = server_get_network (sess->server, FALSE); + if (!net) + return NULL; + + snprintf (buf, max, "%s/scrollback/%s/%s.txt", get_xdir_fs (), net, ""); + mkdir_p (buf); + + chan = log_create_filename (sess->channel); + snprintf (buf, max, "%s/scrollback/%s/%s.txt", get_xdir_fs (), net, chan); + free (chan); + + return buf; +} + +#if 0 + +static void +scrollback_unlock (session *sess) +{ + char buf[1024]; + + if (scrollback_get_filename (sess, buf, sizeof (buf) - 6) == NULL) + return; + + strcat (buf, ".lock"); + unlink (buf); +} + +static gboolean +scrollback_lock (session *sess) +{ + char buf[1024]; + int fh; + + if (scrollback_get_filename (sess, buf, sizeof (buf) - 6) == NULL) + return FALSE; + + strcat (buf, ".lock"); + + if (access (buf, F_OK) == 0) + return FALSE; /* can't get lock */ + + fh = open (buf, O_CREAT | O_TRUNC | O_APPEND | O_WRONLY, 0644); + if (fh == -1) + return FALSE; + + return TRUE; +} + +#endif + +void +scrollback_close (session *sess) +{ + if (sess->scrollfd != -1) + { + close (sess->scrollfd); + sess->scrollfd = -1; + } +} + +static char * +file_to_buffer (char *file, int *len) +{ + int fh; + char *buf; + struct stat st; + + fh = open (file, O_RDONLY | OFLAGS); + if (fh == -1) + return NULL; + + fstat (fh, &st); + + buf = malloc (st.st_size); + if (!buf) + { + close (fh); + return NULL; + } + + if (read (fh, buf, st.st_size) != st.st_size) + { + free (buf); + close (fh); + return NULL; + } + + *len = st.st_size; + close (fh); + return buf; +} + +/* shrink the file to roughly prefs.max_lines */ + +static void +scrollback_shrink (session *sess) +{ + char file[1024]; + char *buf; + int fh; + int lines; + int line; + int len; + char *p; + + scrollback_close (sess); + sess->scrollwritten = 0; + lines = 0; + + if (scrollback_get_filename (sess, file, sizeof (file)) == NULL) + return; + + buf = file_to_buffer (file, &len); + if (!buf) + return; + + /* count all lines */ + p = buf; + while (p != buf + len) + { + if (*p == '\n') + lines++; + p++; + } + + fh = open (file, O_CREAT | O_TRUNC | O_APPEND | O_WRONLY, 0644); + if (fh == -1) + { + free (buf); + return; + } + + line = 0; + p = buf; + while (p != buf + len) + { + if (*p == '\n') + { + line++; + if (line >= lines - prefs.max_lines && + p + 1 != buf + len) + { + p++; + write (fh, p, len - (p - buf)); + break; + } + } + p++; + } + + close (fh); + free (buf); +} + +static void +scrollback_save (session *sess, char *text) +{ + char buf[512 * 4]; + time_t stamp; + int len; + + if (sess->type == SESS_SERVER) + return; + + if (sess->text_scrollback == SET_DEFAULT) + { + if (!prefs.text_replay) + return; + } + else + { + if (sess->text_scrollback != SET_ON) + return; + } + + if (sess->scrollfd == -1) + { + if (scrollback_get_filename (sess, buf, sizeof (buf)) == NULL) + return; + + sess->scrollfd = open (buf, O_CREAT | O_APPEND | O_WRONLY, 0644); + if (sess->scrollfd == -1) + return; + } + + stamp = time (0); + if (sizeof (stamp) == 4) /* gcc will optimize one of these out */ + write (sess->scrollfd, buf, snprintf (buf, sizeof (buf), "T %d ", (int)stamp)); + else + write (sess->scrollfd, buf, snprintf (buf, sizeof (buf), "T %"G_GINT64_FORMAT" ", (gint64)stamp)); + + len = strlen (text); + write (sess->scrollfd, text, len); + if (len && text[len - 1] != '\n') + write (sess->scrollfd, "\n", 1); + + sess->scrollwritten++; + + if ((sess->scrollwritten * 2 > prefs.max_lines && prefs.max_lines > 0) || + sess->scrollwritten > 32000) + scrollback_shrink (sess); +} + +void +scrollback_load (session *sess) +{ + int fh; + char buf[512 * 4]; + char *text; + time_t stamp; + int lines; + char *map, *end_map; + struct stat statbuf; + const char *begin, *eol; + + if (sess->text_scrollback == SET_DEFAULT) + { + if (!prefs.text_replay) + return; + } + else + { + if (sess->text_scrollback != SET_ON) + return; + } + + if (scrollback_get_filename (sess, buf, sizeof (buf)) == NULL) + return; + + fh = open (buf, O_RDONLY | OFLAGS); + if (fh == -1) + return; + + if (fstat (fh, &statbuf) < 0) + return; + + map = mmap (NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, fh, 0); + if (map == MAP_FAILED) + return; + + end_map = map + statbuf.st_size; + + lines = 0; + begin = map; + while (begin < end_map) + { + int n_bytes; + + eol = memchr (begin, '\n', end_map - begin); + + if (!eol) + eol = end_map; + + n_bytes = MIN (eol - begin, sizeof (buf) - 1); + + strncpy (buf, begin, n_bytes); + + buf[n_bytes] = 0; + + if (buf[0] == 'T') + { + if (sizeof (time_t) == 4) + stamp = strtoul (buf + 2, NULL, 10); + else + stamp = strtoull (buf + 2, NULL, 10); /* just incase time_t is 64 bits */ + text = strchr (buf + 3, ' '); + if (text) + { + text = strip_color (text + 1, -1, STRIP_COLOR); + fe_print_text (sess, text, stamp); + g_free (text); + } + lines++; + } + + begin = eol + 1; + } + + sess->scrollwritten = lines; + + if (lines) + { + text = ctime (&stamp); + text[24] = 0; /* get rid of the \n */ + snprintf (buf, sizeof (buf), "\n*\t%s %s\n\n", _("Loaded log from"), text); + fe_print_text (sess, buf, 0); + /*EMIT_SIGNAL (XP_TE_GENMSG, sess, "*", buf, NULL, NULL, NULL, 0);*/ + } + + munmap (map, statbuf.st_size); + close (fh); +} + +void +log_close (session *sess) +{ + char obuf[512]; + time_t currenttime; + + if (sess->logfd != -1) + { + currenttime = time (NULL); + write (sess->logfd, obuf, + snprintf (obuf, sizeof (obuf) - 1, _("**** ENDING LOGGING AT %s\n"), + ctime (¤ttime))); + close (sess->logfd); + sess->logfd = -1; + } +} + +static void +mkdir_p (char *dir) /* like "mkdir -p" from a shell, FS encoding */ +{ + char *start = dir; + + /* the whole thing already exists? */ + if (access (dir, F_OK) == 0) + return; + + while (*dir) + { +#ifdef WIN32 + if (dir != start && (*dir == '/' || *dir == '\\')) +#else + if (dir != start && *dir == '/') +#endif + { + *dir = 0; +#ifdef WIN32 + mkdir (start); +#else + mkdir (start, S_IRUSR | S_IWUSR | S_IXUSR); +#endif + *dir = '/'; + } + dir++; + } +} + +static char * +log_create_filename (char *channame) +{ + char *tmp, *ret; + int mbl; + + ret = tmp = strdup (channame); + while (*tmp) + { + mbl = g_utf8_skip[((unsigned char *)tmp)[0]]; + if (mbl == 1) + { +#ifndef WIN32 + *tmp = rfc_tolower (*tmp); + if (*tmp == '/') +#else + /* win32 can't handle filenames with \|/><:"*? characters */ + if (*tmp == '\\' || *tmp == '|' || *tmp == '/' || + *tmp == '>' || *tmp == '<' || *tmp == ':' || + *tmp == '\"' || *tmp == '*' || *tmp == '?') +#endif + *tmp = '_'; + } + tmp += mbl; + } + + return ret; +} + +/* like strcpy, but % turns into %% */ + +static char * +log_escape_strcpy (char *dest, char *src, char *end) +{ + while (*src) + { + *dest = *src; + if (dest + 1 == end) + break; + dest++; + src++; + + if (*src == '%') + { + if (dest + 1 == end) + break; + dest[0] = '%'; + dest++; + } + } + + dest[0] = 0; + return dest - 1; +} + +/* substitutes %c %n %s into buffer */ + +static void +log_insert_vars (char *buf, int bufsize, char *fmt, char *c, char *n, char *s) +{ + char *end = buf + bufsize; + + while (1) + { + switch (fmt[0]) + { + case 0: + buf[0] = 0; + return; + + case '%': + fmt++; + switch (fmt[0]) + { + case 'c': + buf = log_escape_strcpy (buf, c, end); + break; + case 'n': + buf = log_escape_strcpy (buf, n, end); + break; + case 's': + buf = log_escape_strcpy (buf, s, end); + break; + default: + buf[0] = '%'; + buf++; + buf[0] = fmt[0]; + break; + } + break; + + default: + buf[0] = fmt[0]; + } + fmt++; + buf++; + /* doesn't fit? */ + if (buf == end) + { + buf[-1] = 0; + return; + } + } +} + +static char * +log_create_pathname (char *servname, char *channame, char *netname) +{ + char fname[384]; + char fnametime[384]; + char *fs; + struct tm *tm; + time_t now; + + if (!netname) + netname = "NETWORK"; + + /* first, everything is in UTF-8 */ + if (!rfc_casecmp (channame, servname)) + channame = strdup ("server"); + else + channame = log_create_filename (channame); + log_insert_vars (fname, sizeof (fname), prefs.logmask, channame, netname, servname); + free (channame); + + /* insert time/date */ + now = time (NULL); + tm = localtime (&now); + strftime (fnametime, sizeof (fnametime), fname, tm); + + /* create final path/filename */ +#ifdef WIN32 + if (fnametime[0] == '/' || (fnametime[0] >= 'A' && fnametime[1] == ':')) +#else + if (fnametime[0] == '/') /* is it fullpath already? */ +#endif + snprintf (fname, sizeof (fname), "%s", fnametime); + else + snprintf (fname, sizeof (fname), "%s/xchatlogs/%s", get_xdir_utf8 (), fnametime); + + /* now we need it in FileSystem encoding */ + fs = xchat_filename_from_utf8 (fname, -1, 0, 0, 0); + + /* create all the subdirectories */ + if (fs) + mkdir_p (fs); + + return fs; +} + +static int +log_open_file (char *servname, char *channame, char *netname) +{ + char buf[512]; + int fd; + char *file; + time_t currenttime; + + file = log_create_pathname (servname, channame, netname); + if (!file) + return -1; + +#ifdef WIN32 + fd = open (file, O_CREAT | O_APPEND | O_WRONLY, S_IREAD|S_IWRITE); +#else + fd = open (file, O_CREAT | O_APPEND | O_WRONLY, 0644); +#endif + g_free (file); + + if (fd == -1) + return -1; + currenttime = time (NULL); + write (fd, buf, + snprintf (buf, sizeof (buf), _("**** BEGIN LOGGING AT %s\n"), + ctime (¤ttime))); + + return fd; +} + +static void +log_open (session *sess) +{ + static gboolean log_error = FALSE; + + log_close (sess); + sess->logfd = log_open_file (sess->server->servername, sess->channel, + server_get_network (sess->server, FALSE)); + + if (!log_error && sess->logfd == -1) + { + char message[512]; + snprintf (message, sizeof (message), + _("* Can't open log file(s) for writing. Check the\n" \ + " permissions on %s/xchatlogs"), get_xdir_utf8 ()); + fe_message (message, FE_MSG_WAIT | FE_MSG_ERROR); + + log_error = TRUE; + } +} + +void +log_open_or_close (session *sess) +{ + if (sess->text_logging == SET_DEFAULT) + { + if (prefs.logging) + log_open (sess); + else + log_close (sess); + } + else + { + if (sess->text_logging) + log_open (sess); + else + log_close (sess); + } +} + +int +get_stamp_str (char *fmt, time_t tim, char **ret) +{ + char *loc = NULL; + char dest[128]; + gsize len; + + /* strftime wants the format string in LOCALE! */ + if (!prefs.utf8_locale) + { + const gchar *charset; + + g_get_charset (&charset); + loc = g_convert_with_fallback (fmt, -1, charset, "UTF-8", "?", 0, 0, 0); + if (loc) + fmt = loc; + } + + len = strftime (dest, sizeof (dest), fmt, localtime (&tim)); + if (len) + { + if (prefs.utf8_locale) + *ret = g_strdup (dest); + else + *ret = g_locale_to_utf8 (dest, len, 0, &len, 0); + } + + if (loc) + g_free (loc); + + return len; +} + +static void +log_write (session *sess, char *text) +{ + char *temp; + char *stamp; + char *file; + int len; + + if (sess->text_logging == SET_DEFAULT) + { + if (!prefs.logging) + return; + } + else + { + if (sess->text_logging != SET_ON) + return; + } + + if (sess->logfd == -1) + log_open (sess); + + /* change to a different log file? */ + file = log_create_pathname (sess->server->servername, sess->channel, + server_get_network (sess->server, FALSE)); + if (file) + { + if (access (file, F_OK) != 0) + { + close (sess->logfd); + sess->logfd = log_open_file (sess->server->servername, sess->channel, + server_get_network (sess->server, FALSE)); + } + g_free (file); + } + + if (prefs.timestamp_logs) + { + len = get_stamp_str (prefs.timestamp_log_format, time (0), &stamp); + if (len) + { + write (sess->logfd, stamp, len); + g_free (stamp); + } + } + temp = strip_color (text, -1, STRIP_ALL); + len = strlen (temp); + write (sess->logfd, temp, len); + /* lots of scripts/plugins print without a \n at the end */ + if (temp[len - 1] != '\n') + write (sess->logfd, "\n", 1); /* emulate what xtext would display */ + g_free (temp); +} + +/* converts a CP1252/ISO-8859-1(5) hybrid to UTF-8 */ +/* Features: 1. It never fails, all 00-FF chars are converted to valid UTF-8 */ +/* 2. Uses CP1252 in the range 80-9f because ISO doesn't have any- */ +/* thing useful in this range and it helps us receive from mIRC */ +/* 3. The five undefined chars in CP1252 80-9f are replaced with */ +/* ISO-8859-15 control codes. */ +/* 4. Handles 0xa4 as a Euro symbol ala ISO-8859-15. */ +/* 5. Uses ISO-8859-1 (which matches CP1252) for everything else. */ +/* 6. This routine measured 3x faster than g_convert :) */ + +static unsigned char * +iso_8859_1_to_utf8 (unsigned char *text, int len, gsize *bytes_written) +{ + unsigned int idx; + unsigned char *res, *output; + static const unsigned short lowtable[] = /* 74 byte table for 80-a4 */ + { + /* compressed utf-8 table: if the first byte's 0x20 bit is set, it + indicates a 2-byte utf-8 sequence, otherwise prepend a 0xe2. */ + 0x82ac, /* 80 Euro. CP1252 from here on... */ + 0xe281, /* 81 NA */ + 0x809a, /* 82 */ + 0xe692, /* 83 */ + 0x809e, /* 84 */ + 0x80a6, /* 85 */ + 0x80a0, /* 86 */ + 0x80a1, /* 87 */ + 0xeb86, /* 88 */ + 0x80b0, /* 89 */ + 0xe5a0, /* 8a */ + 0x80b9, /* 8b */ + 0xe592, /* 8c */ + 0xe28d, /* 8d NA */ + 0xe5bd, /* 8e */ + 0xe28f, /* 8f NA */ + 0xe290, /* 90 NA */ + 0x8098, /* 91 */ + 0x8099, /* 92 */ + 0x809c, /* 93 */ + 0x809d, /* 94 */ + 0x80a2, /* 95 */ + 0x8093, /* 96 */ + 0x8094, /* 97 */ + 0xeb9c, /* 98 */ + 0x84a2, /* 99 */ + 0xe5a1, /* 9a */ + 0x80ba, /* 9b */ + 0xe593, /* 9c */ + 0xe29d, /* 9d NA */ + 0xe5be, /* 9e */ + 0xe5b8, /* 9f */ + 0xe2a0, /* a0 */ + 0xe2a1, /* a1 */ + 0xe2a2, /* a2 */ + 0xe2a3, /* a3 */ + 0x82ac /* a4 ISO-8859-15 Euro. */ + }; + + if (len == -1) + len = strlen (text); + + /* worst case scenario: every byte turns into 3 bytes */ + res = output = g_malloc ((len * 3) + 1); + if (!output) + return NULL; + + while (len) + { + if (G_LIKELY (*text < 0x80)) + { + *output = *text; /* ascii maps directly */ + } + else if (*text <= 0xa4) /* 80-a4 use a lookup table */ + { + idx = *text - 0x80; + if (lowtable[idx] & 0x2000) + { + *output++ = (lowtable[idx] >> 8) & 0xdf; /* 2 byte utf-8 */ + *output = lowtable[idx] & 0xff; + } + else + { + *output++ = 0xe2; /* 3 byte utf-8 */ + *output++ = (lowtable[idx] >> 8) & 0xff; + *output = lowtable[idx] & 0xff; + } + } + else if (*text < 0xc0) + { + *output++ = 0xc2; + *output = *text; + } + else + { + *output++ = 0xc3; + *output = *text - 0x40; + } + output++; + text++; + len--; + } + *output = 0; /* terminate */ + *bytes_written = output - res; + + return res; +} + +char * +text_validate (char **text, int *len) +{ + char *utf; + gsize utf_len; + + /* valid utf8? */ + if (g_utf8_validate (*text, *len, 0)) + return NULL; + +#ifdef WIN32 + if (GetACP () == 1252) /* our routine is better than iconv's 1252 */ +#else + if (prefs.utf8_locale) +#endif + /* fallback to iso-8859-1 */ + utf = iso_8859_1_to_utf8 (*text, *len, &utf_len); + else + { + /* fallback to locale */ + utf = g_locale_to_utf8 (*text, *len, 0, &utf_len, NULL); + if (!utf) + utf = iso_8859_1_to_utf8 (*text, *len, &utf_len); + } + + if (!utf) + { + *text = g_strdup ("%INVALID%"); + *len = 9; + } else + { + *text = utf; + *len = utf_len; + } + + return utf; +} + +void +PrintText (session *sess, char *text) +{ + char *conv; + + if (!sess) + { + if (!sess_list) + return; + sess = (session *) sess_list->data; + } + + /* make sure it's valid utf8 */ + if (text[0] == 0) + { + text = "\n"; + conv = NULL; + } else + { + int len = -1; + conv = text_validate ((char **)&text, &len); + } + + log_write (sess, text); + scrollback_save (sess, text); + fe_print_text (sess, text, 0); + + if (conv) + g_free (conv); +} + +void +PrintTextf (session *sess, char *format, ...) +{ + va_list args; + char *buf; + + va_start (args, format); + buf = g_strdup_vprintf (format, args); + va_end (args); + + PrintText (sess, buf); + g_free (buf); +} + +/* Print Events stuff here --AGL */ + +/* Consider the following a NOTES file: + + The main upshot of this is: + * Plugins and Perl scripts (when I get round to signaling perl.c) can intercept text events and do what they like + * The default text engine can be config'ed + + By default it should appear *exactly* the same (I'm working hard not to change the default style) but if you go into Settings->Edit Event Texts you can change the text's. The format is thus: + + The normal %Cx (color) and %B (bold) etc work + + $x is replaced with the data in var x (e.g. $1 is often the nick) + + $axxx is replace with a single byte of value xxx (in base 10) + + AGL (990507) + */ + +/* These lists are thus: + pntevts_text[] are the strings the user sees (WITH %x etc) + pntevts[] are the data strings with \000 etc + */ + +/* To add a new event: + + Think up a name (like "Join") + Make up a pevt_name_help struct + Add an entry to textevents.in + Type: make textevents + */ + +/* Internals: + + On startup ~/.xchat/printevents.conf is loaded if it doesn't exist the + defaults are loaded. Any missing events are filled from defaults. + Each event is parsed by pevt_build_string and a binary output is produced + which looks like: + + (byte) value: 0 = { + (int) numbers of bytes + (char []) that number of byte to be memcpy'ed into the buffer + } + 1 = + (byte) number of varable to insert + 2 = end of buffer + + Each XP_TE_* signal is hard coded to call text_emit which calls + display_event which decodes the data + + This means that this system *should be faster* than snprintf because + it always 'knows' that format of the string (basically is preparses much + of the work) + + --AGL + */ + +char *pntevts_text[NUM_XP]; +char *pntevts[NUM_XP]; + +#define pevt_generic_none_help NULL + +static char * const pevt_genmsg_help[] = { + N_("Left message"), + N_("Right message"), +}; + +static char * const pevt_join_help[] = { + N_("The nick of the joining person"), + N_("The channel being joined"), + N_("The host of the person"), +}; + +static char * const pevt_chanaction_help[] = { + N_("Nickname"), + N_("The action"), + N_("Mode char"), + N_("Identified text"), +}; + +static char * const pevt_chanmsg_help[] = { + N_("Nickname"), + N_("The text"), + N_("Mode char"), + N_("Identified text"), +}; + +static char * const pevt_privmsg_help[] = { + N_("Nickname"), + N_("The message"), + N_("Identified text") +}; + +static char * const pevt_changenick_help[] = { + N_("Old nickname"), + N_("New nickname"), +}; + +static char * const pevt_newtopic_help[] = { + N_("Nick of person who changed the topic"), + N_("Topic"), + N_("Channel"), +}; + +static char * const pevt_topic_help[] = { + N_("Channel"), + N_("Topic"), +}; + +static char * const pevt_kick_help[] = { + N_("The nickname of the kicker"), + N_("The person being kicked"), + N_("The channel"), + N_("The reason"), +}; + +static char * const pevt_part_help[] = { + N_("The nick of the person leaving"), + N_("The host of the person"), + N_("The channel"), +}; + +static char * const pevt_chandate_help[] = { + N_("The channel"), + N_("The time"), +}; + +static char * const pevt_topicdate_help[] = { + N_("The channel"), + N_("The creator"), + N_("The time"), +}; + +static char * const pevt_quit_help[] = { + N_("Nick"), + N_("Reason"), + N_("Host"), +}; + +static char * const pevt_pingrep_help[] = { + N_("Who it's from"), + N_("The time in x.x format (see below)"), +}; + +static char * const pevt_notice_help[] = { + N_("Who it's from"), + N_("The message"), +}; + +static char * const pevt_channotice_help[] = { + N_("Who it's from"), + N_("The Channel it's going to"), + N_("The message"), +}; + +static char * const pevt_uchangenick_help[] = { + N_("Old nickname"), + N_("New nickname"), +}; + +static char * const pevt_ukick_help[] = { + N_("The person being kicked"), + N_("The channel"), + N_("The nickname of the kicker"), + N_("The reason"), +}; + +static char * const pevt_partreason_help[] = { + N_("The nick of the person leaving"), + N_("The host of the person"), + N_("The channel"), + N_("The reason"), +}; + +static char * const pevt_ctcpsnd_help[] = { + N_("The sound"), + N_("The nick of the person"), + N_("The channel"), +}; + +static char * const pevt_ctcpgen_help[] = { + N_("The CTCP event"), + N_("The nick of the person"), +}; + +static char * const pevt_ctcpgenc_help[] = { + N_("The CTCP event"), + N_("The nick of the person"), + N_("The Channel it's going to"), +}; + +static char * const pevt_chansetkey_help[] = { + N_("The nick of the person who set the key"), + N_("The key"), +}; + +static char * const pevt_chansetlimit_help[] = { + N_("The nick of the person who set the limit"), + N_("The limit"), +}; + +static char * const pevt_chanop_help[] = { + N_("The nick of the person who did the op'ing"), + N_("The nick of the person who has been op'ed"), +}; + +static char * const pevt_chanhop_help[] = { + N_("The nick of the person who has been halfop'ed"), + N_("The nick of the person who did the halfop'ing"), +}; + +static char * const pevt_chanvoice_help[] = { + N_("The nick of the person who did the voice'ing"), + N_("The nick of the person who has been voice'ed"), +}; + +static char * const pevt_chanban_help[] = { + N_("The nick of the person who did the banning"), + N_("The ban mask"), +}; + +static char * const pevt_chanrmkey_help[] = { + N_("The nick who removed the key"), +}; + +static char * const pevt_chanrmlimit_help[] = { + N_("The nick who removed the limit"), +}; + +static char * const pevt_chandeop_help[] = { + N_("The nick of the person of did the deop'ing"), + N_("The nick of the person who has been deop'ed"), +}; +static char * const pevt_chandehop_help[] = { + N_("The nick of the person of did the dehalfop'ing"), + N_("The nick of the person who has been dehalfop'ed"), +}; + +static char * const pevt_chandevoice_help[] = { + N_("The nick of the person of did the devoice'ing"), + N_("The nick of the person who has been devoice'ed"), +}; + +static char * const pevt_chanunban_help[] = { + N_("The nick of the person of did the unban'ing"), + N_("The ban mask"), +}; + +static char * const pevt_chanexempt_help[] = { + N_("The nick of the person who did the exempt"), + N_("The exempt mask"), +}; + +static char * const pevt_chanrmexempt_help[] = { + N_("The nick of the person removed the exempt"), + N_("The exempt mask"), +}; + +static char * const pevt_chaninvite_help[] = { + N_("The nick of the person who did the invite"), + N_("The invite mask"), +}; + +static char * const pevt_chanrminvite_help[] = { + N_("The nick of the person removed the invite"), + N_("The invite mask"), +}; + +static char * const pevt_chanmodegen_help[] = { + N_("The nick of the person setting the mode"), + N_("The mode's sign (+/-)"), + N_("The mode letter"), + N_("The channel it's being set on"), +}; + +static char * const pevt_whois1_help[] = { + N_("Nickname"), + N_("Username"), + N_("Host"), + N_("Full name"), +}; + +static char * const pevt_whois2_help[] = { + N_("Nickname"), + N_("Channel Membership/\"is an IRC operator\""), +}; + +static char * const pevt_whois3_help[] = { + N_("Nickname"), + N_("Server Information"), +}; + +static char * const pevt_whois4_help[] = { + N_("Nickname"), + N_("Idle time"), +}; + +static char * const pevt_whois4t_help[] = { + N_("Nickname"), + N_("Idle time"), + N_("Signon time"), +}; + +static char * const pevt_whois5_help[] = { + N_("Nickname"), + N_("Away reason"), +}; + +static char * const pevt_whois6_help[] = { + N_("Nickname"), +}; + +static char * const pevt_whoisid_help[] = { + N_("Nickname"), + N_("Message"), + "Numeric" +}; + +static char * const pevt_whoisauth_help[] = { + N_("Nickname"), + N_("Message"), + N_("Account"), +}; + +static char * const pevt_whoisrealhost_help[] = { + N_("Nickname"), + N_("Real user@host"), + N_("Real IP"), + N_("Message"), +}; + +static char * const pevt_generic_channel_help[] = { + N_("Channel Name"), +}; + +static char * const pevt_servertext_help[] = { + N_("Text"), + N_("Server Name"), + N_("Raw Numeric or Identifier") +}; + +static char * const pevt_sslmessage_help[] = { + N_("Text"), + N_("Server Name") +}; + +static char * const pevt_invited_help[] = { + N_("Channel Name"), + N_("Nick of person who invited you"), + N_("Server Name"), +}; + +static char * const pevt_usersonchan_help[] = { + N_("Channel Name"), + N_("Users"), +}; + +static char * const pevt_nickclash_help[] = { + N_("Nickname in use"), + N_("Nick being tried"), +}; + +static char * const pevt_connfail_help[] = { + N_("Error"), +}; + +static char * const pevt_connect_help[] = { + N_("Host"), + N_("IP"), + N_("Port"), +}; + +static char * const pevt_sconnect_help[] = { + "PID" +}; + +static char * const pevt_generic_nick_help[] = { + N_("Nickname"), + N_("Server Name"), + N_("Network") +}; + +static char * const pevt_chanmodes_help[] = { + N_("Channel Name"), + N_("Modes string"), +}; + +static char * const pevt_rawmodes_help[] = { + N_("Nickname"), + N_("Modes string"), +}; + +static char * const pevt_kill_help[] = { + N_("Nickname"), + N_("Reason"), +}; + +static char * const pevt_dccchaterr_help[] = { + N_("Nickname"), + N_("IP address"), + N_("Port"), + N_("Error"), +}; + +static char * const pevt_dccstall_help[] = { + N_("DCC Type"), + N_("Filename"), + N_("Nickname"), +}; + +static char * const pevt_generic_file_help[] = { + N_("Filename"), + N_("Error"), +}; + +static char * const pevt_dccrecverr_help[] = { + N_("Filename"), + N_("Destination filename"), + N_("Nickname"), + N_("Error"), +}; + +static char * const pevt_dccrecvcomp_help[] = { + N_("Filename"), + N_("Destination filename"), + N_("Nickname"), + N_("CPS"), +}; + +static char * const pevt_dccconfail_help[] = { + N_("DCC Type"), + N_("Nickname"), + N_("Error"), +}; + +static char * const pevt_dccchatcon_help[] = { + N_("Nickname"), + N_("IP address"), +}; + +static char * const pevt_dcccon_help[] = { + N_("Nickname"), + N_("IP address"), + N_("Filename"), +}; + +static char * const pevt_dccsendfail_help[] = { + N_("Filename"), + N_("Nickname"), + N_("Error"), +}; + +static char * const pevt_dccsendcomp_help[] = { + N_("Filename"), + N_("Nickname"), + N_("CPS"), +}; + +static char * const pevt_dccoffer_help[] = { + N_("Filename"), + N_("Nickname"), + N_("Pathname"), +}; + +static char * const pevt_dccfileabort_help[] = { + N_("Nickname"), + N_("Filename") +}; + +static char * const pevt_dccchatabort_help[] = { + N_("Nickname"), +}; + +static char * const pevt_dccresumeoffer_help[] = { + N_("Nickname"), + N_("Filename"), + N_("Position"), +}; + +static char * const pevt_dccsendoffer_help[] = { + N_("Nickname"), + N_("Filename"), + N_("Size"), + N_("IP address"), +}; + +static char * const pevt_dccgenericoffer_help[] = { + N_("DCC String"), + N_("Nickname"), +}; + +static char * const pevt_notifynumber_help[] = { + N_("Number of notify items"), +}; + +static char * const pevt_serverlookup_help[] = { + N_("Server Name"), +}; + +static char * const pevt_servererror_help[] = { + N_("Text"), +}; + +static char * const pevt_foundip_help[] = { + N_("IP"), +}; + +static char * const pevt_dccrename_help[] = { + N_("Old Filename"), + N_("New Filename"), +}; + +static char * const pevt_ctcpsend_help[] = { + N_("Receiver"), + N_("Message"), +}; + +static char * const pevt_ignoreaddremove_help[] = { + N_("Hostmask"), +}; + +static char * const pevt_resolvinguser_help[] = { + N_("Nickname"), + N_("Hostname"), +}; + +static char * const pevt_malformed_help[] = { + N_("Nickname"), + N_("The Packet"), +}; + +static char * const pevt_pingtimeout_help[] = { + N_("Seconds"), +}; + +static char * const pevt_uinvite_help[] = { + N_("Nick of person who have been invited"), + N_("Channel Name"), + N_("Server Name"), +}; + +static char * const pevt_banlist_help[] = { + N_("Channel"), + N_("Banmask"), + N_("Who set the ban"), + N_("Ban time"), +}; + +static char * const pevt_discon_help[] = { + N_("Error"), +}; + +#include "textevents.h" + +static void +pevent_load_defaults () +{ + int i; + + for (i = 0; i < NUM_XP; i++) + { + if (pntevts_text[i]) + free (pntevts_text[i]); + + /* make-te.c sets this 128 flag (DON'T call gettext() flag) */ + if (te[i].num_args & 128) + pntevts_text[i] = strdup (te[i].def); + else + pntevts_text[i] = strdup (_(te[i].def)); + } +} + +void +pevent_make_pntevts () +{ + int i, m; + char out[1024]; + + for (i = 0; i < NUM_XP; i++) + { + if (pntevts[i] != NULL) + free (pntevts[i]); + if (pevt_build_string (pntevts_text[i], &(pntevts[i]), &m) != 0) + { + snprintf (out, sizeof (out), + _("Error parsing event %s.\nLoading default."), te[i].name); + fe_message (out, FE_MSG_WARN); + free (pntevts_text[i]); + /* make-te.c sets this 128 flag (DON'T call gettext() flag) */ + if (te[i].num_args & 128) + pntevts_text[i] = strdup (te[i].def); + else + pntevts_text[i] = strdup (_(te[i].def)); + if (pevt_build_string (pntevts_text[i], &(pntevts[i]), &m) != 0) + { + fprintf (stderr, + "XChat CRITICAL *** default event text failed to build!\n"); + abort (); + } + } + } +} + +/* Loading happens at 2 levels: + 1) File is read into blocks + 2) Pe block is parsed and loaded + + --AGL */ + +/* Better hope you pass good args.. --AGL */ + +static void +pevent_trigger_load (int *i_penum, char **i_text, char **i_snd) +{ + int penum = *i_penum, len; + char *text = *i_text, *snd = *i_snd; + + if (penum != -1 && text != NULL) + { + len = strlen (text) + 1; + if (pntevts_text[penum]) + free (pntevts_text[penum]); + pntevts_text[penum] = malloc (len); + memcpy (pntevts_text[penum], text, len); + } + + if (text) + free (text); + if (snd) + free (snd); + *i_text = NULL; + *i_snd = NULL; + *i_penum = 0; +} + +static int +pevent_find (char *name, int *i_i) +{ + int i = *i_i, j; + + j = i + 1; + while (1) + { + if (j == NUM_XP) + j = 0; + if (strcmp (te[j].name, name) == 0) + { + *i_i = j; + return j; + } + if (j == i) + return -1; + j++; + } +} + +int +pevent_load (char *filename) +{ + /* AGL, I've changed this file and pevent_save, could you please take a look at + * the changes and possibly modify them to suit you + * //David H + */ + char *buf, *ibuf; + int fd, i = 0, pnt = 0; + struct stat st; + char *text = NULL, *snd = NULL; + int penum = 0; + char *ofs; + + if (filename == NULL) + fd = xchat_open_file ("pevents.conf", O_RDONLY, 0, 0); + else + fd = xchat_open_file (filename, O_RDONLY, 0, XOF_FULLPATH); + + if (fd == -1) + return 1; + if (fstat (fd, &st) != 0) + return 1; + ibuf = malloc (st.st_size); + read (fd, ibuf, st.st_size); + close (fd); + + while (buf_get_line (ibuf, &buf, &pnt, st.st_size)) + { + if (buf[0] == '#') + continue; + if (strlen (buf) == 0) + continue; + + ofs = strchr (buf, '='); + if (!ofs) + continue; + *ofs = 0; + ofs++; + /*if (*ofs == 0) + continue;*/ + + if (strcmp (buf, "event_name") == 0) + { + if (penum >= 0) + pevent_trigger_load (&penum, &text, &snd); + penum = pevent_find (ofs, &i); + continue; + } else if (strcmp (buf, "event_text") == 0) + { + if (text) + free (text); + +#if 0 + /* This allows updating of old strings. We don't use new defaults + if the user has customized the strings (.e.g a text theme). + Hash of the old default is enough to identify and replace it. + This only works in English. */ + + switch (g_str_hash (ofs)) + { + case 0x526743a4: + /* %C08,02 Hostmask PRIV NOTI CHAN CTCP INVI UNIG %O */ + text = strdup (te[XP_TE_IGNOREHEADER].def); + break; + + case 0xe91bc9c2: + /* %C08,02 %O */ + text = strdup (te[XP_TE_IGNOREFOOTER].def); + break; + + case 0x1fbfdf22: + /* -%C10-%C11-%O$tDCC RECV: Cannot open $1 for writing - aborting. */ + text = strdup (te[XP_TE_DCCFILEERR].def); + break; + + default: + text = strdup (ofs); + } +#else + text = strdup (ofs); +#endif + + continue; + }/* else if (strcmp (buf, "event_sound") == 0) + { + if (snd) + free (snd); + snd = strdup (ofs); + continue; + }*/ + + continue; + } + + pevent_trigger_load (&penum, &text, &snd); + free (ibuf); + return 0; +} + +static void +pevent_check_all_loaded () +{ + int i; + + for (i = 0; i < NUM_XP; i++) + { + if (pntevts_text[i] == NULL) + { + /*printf ("%s\n", te[i].name); + snprintf(out, sizeof(out), "The data for event %s failed to load. Reverting to defaults.\nThis may be because a new version of XChat is loading an old config file.\n\nCheck all print event texts are correct", evtnames[i]); + gtkutil_simpledialog(out); */ + /* make-te.c sets this 128 flag (DON'T call gettext() flag) */ + if (te[i].num_args & 128) + pntevts_text[i] = strdup (te[i].def); + else + pntevts_text[i] = strdup (_(te[i].def)); + } + } +} + +void +load_text_events () +{ + memset (&pntevts_text, 0, sizeof (char *) * (NUM_XP)); + memset (&pntevts, 0, sizeof (char *) * (NUM_XP)); + + if (pevent_load (NULL)) + pevent_load_defaults (); + pevent_check_all_loaded (); + pevent_make_pntevts (); +} + +/* + CL: format_event now handles filtering of arguments: + 1) if prefs.stripcolor is set, filter all style control codes from arguments + 2) always strip \010 (ATTR_HIDDEN) from arguments: it is only for use in the format string itself +*/ +#define ARG_FLAG(argn) (1 << (argn)) + +void +format_event (session *sess, int index, char **args, char *o, int sizeofo, unsigned int stripcolor_args) +{ + int len, oi, ii, numargs; + char *i, *ar, d, a, done_all = FALSE; + + i = pntevts[index]; + numargs = te[index].num_args & 0x7f; + + oi = ii = len = d = a = 0; + o[0] = 0; + + if (i == NULL) + return; + + while (done_all == FALSE) + { + d = i[ii++]; + switch (d) + { + case 0: + memcpy (&len, &(i[ii]), sizeof (int)); + ii += sizeof (int); + if (oi + len > sizeofo) + { + printf ("Overflow in display_event (%s)\n", i); + o[0] = 0; + return; + } + memcpy (&(o[oi]), &(i[ii]), len); + oi += len; + ii += len; + break; + case 1: + a = i[ii++]; + if (a > numargs) + { + fprintf (stderr, + "XChat DEBUG: display_event: arg > numargs (%d %d %s)\n", + a, numargs, i); + break; + } + ar = args[(int) a + 1]; + if (ar == NULL) + { + printf ("arg[%d] is NULL in print event\n", a + 1); + } else + { + if (stripcolor_args & ARG_FLAG(a + 1)) len = strip_color2 (ar, -1, &o[oi], STRIP_ALL); + else len = strip_hidden_attribute (ar, &o[oi]); + oi += len; + } + break; + case 2: + o[oi++] = '\n'; + o[oi++] = 0; + done_all = TRUE; + continue; + case 3: +/* if (sess->type == SESS_DIALOG) + { + if (prefs.dialog_indent_nicks) + o[oi++] = '\t'; + else + o[oi++] = ' '; + } else + {*/ + if (prefs.indent_nicks) + o[oi++] = '\t'; + else + o[oi++] = ' '; + /*}*/ + break; + } + } + o[oi] = 0; + if (*o == '\n') + o[0] = 0; +} + +static void +display_event (session *sess, int event, char **args, unsigned int stripcolor_args) +{ + char o[4096]; + format_event (sess, event, args, o, sizeof (o), stripcolor_args); + if (o[0]) + PrintText (sess, o); +} + +int +pevt_build_string (const char *input, char **output, int *max_arg) +{ + struct pevt_stage1 *s = NULL, *base = NULL, *last = NULL, *next; + int clen; + char o[4096], d, *obuf, *i; + int oi, ii, max = -1, len, x; + + len = strlen (input); + i = malloc (len + 1); + memcpy (i, input, len + 1); + check_special_chars (i, TRUE); + + len = strlen (i); + + clen = oi = ii = 0; + + for (;;) + { + if (ii == len) + break; + d = i[ii++]; + if (d != '$') + { + o[oi++] = d; + continue; + } + if (i[ii] == '$') + { + o[oi++] = '$'; + continue; + } + if (oi > 0) + { + s = (struct pevt_stage1 *) malloc (sizeof (struct pevt_stage1)); + if (base == NULL) + base = s; + if (last != NULL) + last->next = s; + last = s; + s->next = NULL; + s->data = malloc (oi + sizeof (int) + 1); + s->len = oi + sizeof (int) + 1; + clen += oi + sizeof (int) + 1; + s->data[0] = 0; + memcpy (&(s->data[1]), &oi, sizeof (int)); + memcpy (&(s->data[1 + sizeof (int)]), o, oi); + oi = 0; + } + if (ii == len) + { + fe_message ("String ends with a $", FE_MSG_WARN); + return 1; + } + d = i[ii++]; + if (d == 'a') + { /* Hex value */ + x = 0; + if (ii == len) + goto a_len_error; + d = i[ii++]; + d -= '0'; + x = d * 100; + if (ii == len) + goto a_len_error; + d = i[ii++]; + d -= '0'; + x += d * 10; + if (ii == len) + goto a_len_error; + d = i[ii++]; + d -= '0'; + x += d; + if (x > 255) + goto a_range_error; + o[oi++] = x; + continue; + + a_len_error: + fe_message ("String ends in $a", FE_MSG_WARN); + return 1; + a_range_error: + fe_message ("$a value is greater than 255", FE_MSG_WARN); + return 1; + } + if (d == 't') + { + /* Tab - if tabnicks is set then write '\t' else ' ' */ + s = (struct pevt_stage1 *) malloc (sizeof (struct pevt_stage1)); + if (base == NULL) + base = s; + if (last != NULL) + last->next = s; + last = s; + s->next = NULL; + s->data = malloc (1); + s->len = 1; + clen += 1; + s->data[0] = 3; + + continue; + } + if (d < '1' || d > '9') + { + snprintf (o, sizeof (o), "Error, invalid argument $%c\n", d); + fe_message (o, FE_MSG_WARN); + return 1; + } + d -= '0'; + if (max < d) + max = d; + s = (struct pevt_stage1 *) malloc (sizeof (struct pevt_stage1)); + if (base == NULL) + base = s; + if (last != NULL) + last->next = s; + last = s; + s->next = NULL; + s->data = malloc (2); + s->len = 2; + clen += 2; + s->data[0] = 1; + s->data[1] = d - 1; + } + if (oi > 0) + { + s = (struct pevt_stage1 *) malloc (sizeof (struct pevt_stage1)); + if (base == NULL) + base = s; + if (last != NULL) + last->next = s; + last = s; + s->next = NULL; + s->data = malloc (oi + sizeof (int) + 1); + s->len = oi + sizeof (int) + 1; + clen += oi + sizeof (int) + 1; + s->data[0] = 0; + memcpy (&(s->data[1]), &oi, sizeof (int)); + memcpy (&(s->data[1 + sizeof (int)]), o, oi); + oi = 0; + } + s = (struct pevt_stage1 *) malloc (sizeof (struct pevt_stage1)); + if (base == NULL) + base = s; + if (last != NULL) + last->next = s; + last = s; + s->next = NULL; + s->data = malloc (1); + s->len = 1; + clen += 1; + s->data[0] = 2; + + oi = 0; + s = base; + obuf = malloc (clen); + while (s) + { + next = s->next; + memcpy (&obuf[oi], s->data, s->len); + oi += s->len; + free (s->data); + free (s); + s = next; + } + + free (i); + + if (max_arg) + *max_arg = max; + if (output) + *output = obuf; + + return 0; +} + + +/* black n white(0/1) are bad colors for nicks, and we'll use color 2 for us */ +/* also light/dark gray (14/15) */ +/* 5,7,8 are all shades of yellow which happen to look dman near the same */ + +static char rcolors[] = { 19, 20, 22, 24, 25, 26, 27, 28, 29 }; + +static int +color_of (char *name) +{ + int i = 0, sum = 0; + + while (name[i]) + sum += name[i++]; + sum %= sizeof (rcolors) / sizeof (char); + return rcolors[sum]; +} + + +/* called by EMIT_SIGNAL macro */ + +void +text_emit (int index, session *sess, char *a, char *b, char *c, char *d) +{ + char *word[PDIWORDS]; + int i; + unsigned int stripcolor_args = (prefs.stripcolor ? 0xFFFFFFFF : 0); + char tbuf[NICKLEN + 4]; + + if (prefs.colorednicks && (index == XP_TE_CHANACTION || index == XP_TE_CHANMSG)) + { + snprintf (tbuf, sizeof (tbuf), "\003%d%s", color_of (a), a); + a = tbuf; + stripcolor_args &= ~ARG_FLAG(1); /* don't strip color from this argument */ + } + + word[0] = te[index].name; + word[1] = (a ? a : "\000"); + word[2] = (b ? b : "\000"); + word[3] = (c ? c : "\000"); + word[4] = (d ? d : "\000"); + for (i = 5; i < PDIWORDS; i++) + word[i] = "\000"; + + if (plugin_emit_print (sess, word)) + return; + + /* If a plugin's callback executes "/close", 'sess' may be invalid */ + if (!is_session (sess)) + return; + + switch (index) + { + case XP_TE_JOIN: + case XP_TE_PART: + case XP_TE_PARTREASON: + case XP_TE_QUIT: + /* implement ConfMode / Hide Join and Part Messages */ + if (chanopt_is_set (prefs.confmode, sess->text_hidejoinpart)) + return; + break; + + /* ===Private message=== */ + case XP_TE_PRIVMSG: + case XP_TE_DPRIVMSG: + case XP_TE_PRIVACTION: + case XP_TE_DPRIVACTION: + if (chanopt_is_set_a (prefs.input_beep_priv, sess->alert_beep)) + sound_beep (sess); + if (chanopt_is_set_a (prefs.input_flash_priv, sess->alert_taskbar)) + fe_flash_window (sess); + /* why is this one different? because of plugin-tray.c's hooks! ugly */ + if (sess->alert_tray == SET_ON) + fe_tray_set_icon (FE_ICON_MESSAGE); + break; + + /* ===Highlighted message=== */ + case XP_TE_HCHANACTION: + case XP_TE_HCHANMSG: + if (chanopt_is_set_a (prefs.input_beep_hilight, sess->alert_beep)) + sound_beep (sess); + if (chanopt_is_set_a (prefs.input_flash_hilight, sess->alert_taskbar)) + fe_flash_window (sess); + if (sess->alert_tray == SET_ON) + fe_tray_set_icon (FE_ICON_MESSAGE); + break; + + /* ===Channel message=== */ + case XP_TE_CHANACTION: + case XP_TE_CHANMSG: + if (chanopt_is_set_a (prefs.input_beep_chans, sess->alert_beep)) + sound_beep (sess); + if (chanopt_is_set_a (prefs.input_flash_chans, sess->alert_taskbar)) + fe_flash_window (sess); + if (sess->alert_tray == SET_ON) + fe_tray_set_icon (FE_ICON_MESSAGE); + break; + } + + sound_play_event (index); + display_event (sess, index, word, stripcolor_args); +} + +char * +text_find_format_string (char *name) +{ + int i = 0; + + i = pevent_find (name, &i); + if (i >= 0) + return pntevts_text[i]; + + return NULL; +} + +int +text_emit_by_name (char *name, session *sess, char *a, char *b, char *c, char *d) +{ + int i = 0; + + i = pevent_find (name, &i); + if (i >= 0) + { + text_emit (i, sess, a, b, c, d); + return 1; + } + + return 0; +} + +void +pevent_save (char *fn) +{ + int fd, i; + char buf[1024]; + + if (!fn) + fd = xchat_open_file ("pevents.conf", O_CREAT | O_TRUNC | O_WRONLY, + 0x180, XOF_DOMODE); + else + fd = xchat_open_file (fn, O_CREAT | O_TRUNC | O_WRONLY, 0x180, + XOF_FULLPATH | XOF_DOMODE); + if (fd == -1) + { + /* + fe_message ("Error opening config file\n", FALSE); + If we get here when X-Chat is closing the fe-message causes a nice & hard crash + so we have to use perror which doesn't rely on GTK + */ + + perror ("Error opening config file\n"); + return; + } + + for (i = 0; i < NUM_XP; i++) + { + write (fd, buf, snprintf (buf, sizeof (buf), + "event_name=%s\n", te[i].name)); + write (fd, buf, snprintf (buf, sizeof (buf), + "event_text=%s\n\n", pntevts_text[i])); + } + + close (fd); +} + +/* =========================== */ +/* ========== SOUND ========== */ +/* =========================== */ + +char *sound_files[NUM_XP]; + +void +sound_beep (session *sess) +{ + if (sound_files[XP_TE_BEEP] && sound_files[XP_TE_BEEP][0]) + /* user defined beep _file_ */ + sound_play_event (XP_TE_BEEP); + else + /* system beep */ + fe_beep (); +} + +static char * +sound_find_command (void) +{ + /* some sensible unix players. You're bound to have one of them */ + static const char * const progs[] = {"aplay", "esdplay", "soxplay", "artsplay", NULL}; + char *cmd; + int i = 0; + + if (prefs.soundcmd[0]) + return g_strdup (prefs.soundcmd); + + while (progs[i]) + { + cmd = g_find_program_in_path (progs[i]); + if (cmd) + return cmd; + i++; + } + + return NULL; +} + +void +sound_play (const char *file, gboolean quiet) +{ + char buf[512]; + char wavfile[512]; + char *file_fs; + char *cmd; + + /* the pevents GUI editor triggers this after removing a soundfile */ + if (!file[0]) + return; + +#ifdef WIN32 + /* check for fullpath, windows style */ + if (strlen (file) > 3 && + file[1] == ':' && (file[2] == '\\' || file[2] == '/') ) + { + strncpy (wavfile, file, sizeof (wavfile)); + } else +#endif + if (file[0] != '/') + { + snprintf (wavfile, sizeof (wavfile), "%s/%s", prefs.sounddir, file); + } else + { + strncpy (wavfile, file, sizeof (wavfile)); + } + wavfile[sizeof (wavfile) - 1] = 0; /* ensure termination */ + + file_fs = xchat_filename_from_utf8 (wavfile, -1, 0, 0, 0); + if (!file_fs) + return; + + if (access (file_fs, R_OK) == 0) + { + cmd = sound_find_command (); + +#ifdef WIN32 + if (cmd == NULL || strcmp (cmd, "esdplay") == 0) + { + PlaySound (file_fs, NULL, SND_NODEFAULT|SND_FILENAME|SND_ASYNC); + } else +#endif + { + if (cmd) + { + if (strchr (file_fs, ' ')) + snprintf (buf, sizeof (buf), "%s \"%s\"", cmd, file_fs); + else + snprintf (buf, sizeof (buf), "%s %s", cmd, file_fs); + buf[sizeof (buf) - 1] = '\0'; + xchat_exec (buf); + } + } + + if (cmd) + g_free (cmd); + + } else + { + if (!quiet) + { + snprintf (buf, sizeof (buf), _("Cannot read sound file:\n%s"), wavfile); + fe_message (buf, FE_MSG_ERROR); + } + } + + g_free (file_fs); +} + +void +sound_play_event (int i) +{ + if (sound_files[i]) + sound_play (sound_files[i], FALSE); +} + +static void +sound_load_event (char *evt, char *file) +{ + int i = 0; + + if (file[0] && pevent_find (evt, &i) != -1) + { + if (sound_files[i]) + free (sound_files[i]); + sound_files[i] = strdup (file); + } +} + +void +sound_load () +{ + int fd; + char buf[512]; + char evt[128]; + + memset (&sound_files, 0, sizeof (char *) * (NUM_XP)); + + fd = xchat_open_file ("sound.conf", O_RDONLY, 0, 0); + if (fd == -1) + return; + + evt[0] = 0; + while (waitline (fd, buf, sizeof buf, FALSE) != -1) + { + if (strncmp (buf, "event=", 6) == 0) + { + safe_strcpy (evt, buf + 6, sizeof (evt)); + } + else if (strncmp (buf, "sound=", 6) == 0) + { + if (evt[0] != 0) + { + sound_load_event (evt, buf + 6); + evt[0] = 0; + } + } + } + + close (fd); +} + +void +sound_save () +{ + int fd, i; + char buf[512]; + + fd = xchat_open_file ("sound.conf", O_CREAT | O_TRUNC | O_WRONLY, 0x180, + XOF_DOMODE); + if (fd == -1) + return; + + for (i = 0; i < NUM_XP; i++) + { + if (sound_files[i] && sound_files[i][0]) + { + write (fd, buf, snprintf (buf, sizeof (buf), + "event=%s\n", te[i].name)); + write (fd, buf, snprintf (buf, sizeof (buf), + "sound=%s\n\n", sound_files[i])); + } + } + + close (fd); +} diff --git a/src/common/text.h b/src/common/text.h new file mode 100644 index 00000000..150821ae --- /dev/null +++ b/src/common/text.h @@ -0,0 +1,42 @@ +#include "textenums.h" + +#ifndef XCHAT_TEXT_H +#define XCHAT_TEXT_H + +#define EMIT_SIGNAL(i, sess, a, b, c, d, e) text_emit(i, sess, a, b, c, d) + +struct text_event +{ + char *name; + char * const *help; + int num_args; + char *def; +}; + +void scrollback_close (session *sess); +void scrollback_load (session *sess); + +int text_word_check (char *word, int len); +void PrintText (session *sess, char *text); +void PrintTextf (session *sess, char *format, ...); +void log_close (session *sess); +void log_open_or_close (session *sess); +void load_text_events (void); +void pevent_save (char *fn); +int pevt_build_string (const char *input, char **output, int *max_arg); +int pevent_load (char *filename); +void pevent_make_pntevts (void); +void text_emit (int index, session *sess, char *a, char *b, char *c, char *d); +int text_emit_by_name (char *name, session *sess, char *a, char *b, char *c, char *d); +char *text_validate (char **text, int *len); +int get_stamp_str (char *fmt, time_t tim, char **ret); +void format_event (session *sess, int index, char **args, char *o, int sizeofo, unsigned int stripcolor_args); +char *text_find_format_string (char *name); + +void sound_play (const char *file, gboolean quiet); +void sound_play_event (int i); +void sound_beep (session *); +void sound_load (); +void sound_save (); + +#endif diff --git a/src/common/textenums.h b/src/common/textenums.h new file mode 100644 index 00000000..682a35ef --- /dev/null +++ b/src/common/textenums.h @@ -0,0 +1,76 @@ +/* this file is auto generated, edit textevents.in instead! */ + +enum +{ + XP_TE_ADDNOTIFY, XP_TE_BANLIST, + XP_TE_BANNED, XP_TE_BEEP, + XP_TE_CHANGENICK, XP_TE_CHANACTION, + XP_TE_HCHANACTION, XP_TE_CHANBAN, + XP_TE_CHANDATE, XP_TE_CHANDEHOP, + XP_TE_CHANDEOP, XP_TE_CHANDEVOICE, + XP_TE_CHANEXEMPT, XP_TE_CHANHOP, + XP_TE_CHANINVITE, XP_TE_CHANLISTHEAD, + XP_TE_CHANMSG, XP_TE_CHANMODEGEN, + XP_TE_CHANMODES, XP_TE_HCHANMSG, + XP_TE_CHANNOTICE, XP_TE_CHANOP, + XP_TE_CHANRMEXEMPT, XP_TE_CHANRMINVITE, + XP_TE_CHANRMKEY, XP_TE_CHANRMLIMIT, + XP_TE_CHANSETKEY, XP_TE_CHANSETLIMIT, + XP_TE_CHANUNBAN, XP_TE_CHANVOICE, + XP_TE_CONNECTED, XP_TE_CONNECT, + XP_TE_CONNFAIL, XP_TE_CTCPGEN, + XP_TE_CTCPGENC, XP_TE_CTCPSEND, + XP_TE_CTCPSND, XP_TE_CTCPSNDC, + XP_TE_DCCCHATABORT, XP_TE_DCCCONCHAT, + XP_TE_DCCCHATF, XP_TE_DCCCHATOFFER, + XP_TE_DCCCHATOFFERING, XP_TE_DCCCHATREOFFER, + XP_TE_DCCCONFAIL, XP_TE_DCCGENERICOFFER, + XP_TE_DCCHEAD, XP_TE_MALFORMED, + XP_TE_DCCOFFER, XP_TE_DCCIVAL, + XP_TE_DCCRECVABORT, XP_TE_DCCRECVCOMP, + XP_TE_DCCCONRECV, XP_TE_DCCRECVERR, + XP_TE_DCCFILEERR, XP_TE_DCCRENAME, + XP_TE_DCCRESUMEREQUEST, XP_TE_DCCSENDABORT, + XP_TE_DCCSENDCOMP, XP_TE_DCCCONSEND, + XP_TE_DCCSENDFAIL, XP_TE_DCCSENDOFFER, + XP_TE_DCCSTALL, XP_TE_DCCTOUT, + XP_TE_DELNOTIFY, XP_TE_DISCON, + XP_TE_FOUNDIP, XP_TE_GENMSG, + XP_TE_IGNOREADD, XP_TE_IGNORECHANGE, + XP_TE_IGNOREFOOTER, XP_TE_IGNOREHEADER, + XP_TE_IGNOREREMOVE, XP_TE_IGNOREEMPTY, + XP_TE_INVITE, XP_TE_INVITED, + XP_TE_JOIN, XP_TE_KEYWORD, + XP_TE_KICK, XP_TE_KILL, + XP_TE_MSGSEND, XP_TE_MOTD, + XP_TE_MOTDSKIP, XP_TE_NICKCLASH, + XP_TE_NICKFAIL, XP_TE_NODCC, + XP_TE_NOCHILD, XP_TE_NOTICE, + XP_TE_NOTICESEND, XP_TE_NOTIFYEMPTY, + XP_TE_NOTIFYHEAD, XP_TE_NOTIFYNUMBER, + XP_TE_NOTIFYOFFLINE, XP_TE_NOTIFYONLINE, + XP_TE_OPENDIALOG, XP_TE_PART, + XP_TE_PARTREASON, XP_TE_PINGREP, + XP_TE_PINGTIMEOUT, XP_TE_PRIVACTION, + XP_TE_DPRIVACTION, XP_TE_PRIVMSG, + XP_TE_DPRIVMSG, XP_TE_ALREADYPROCESS, + XP_TE_QUIT, XP_TE_RAWMODES, + XP_TE_WALLOPS, XP_TE_RESOLVINGUSER, + XP_TE_SERVERCONNECTED, XP_TE_SERVERERROR, + XP_TE_SERVERLOOKUP, XP_TE_SERVNOTICE, + XP_TE_SERVTEXT, XP_TE_SSLMESSAGE, + XP_TE_STOPCONNECT, XP_TE_TOPIC, + XP_TE_NEWTOPIC, XP_TE_TOPICDATE, + XP_TE_UKNHOST, XP_TE_USERLIMIT, + XP_TE_USERSONCHAN, XP_TE_WHOIS_AUTH, + XP_TE_WHOIS5, XP_TE_WHOIS2, + XP_TE_WHOIS6, XP_TE_WHOIS_ID, + XP_TE_WHOIS4, XP_TE_WHOIS4T, + XP_TE_WHOIS1, XP_TE_WHOIS_REALHOST, + XP_TE_WHOIS3, XP_TE_WHOIS_SPECIAL, + XP_TE_UJOIN, XP_TE_UKICK, + XP_TE_UPART, XP_TE_UPARTREASON, + XP_TE_UACTION, XP_TE_UINVITE, + XP_TE_UCHANMSG, XP_TE_UCHANGENICK, + NUM_XP +}; diff --git a/src/common/textevents.h b/src/common/textevents.h new file mode 100644 index 00000000..08a54da5 --- /dev/null +++ b/src/common/textevents.h @@ -0,0 +1,424 @@ +/* this file is auto generated, edit textevents.in instead! */ + +const struct text_event te[] = { + +{"Add Notify", pevt_generic_nick_help, 1, +N_("%C22*%O$t$1 added to notify list.")}, + +{"Ban List", pevt_banlist_help, 4, +N_("%C22*%O$t$1 Banlist:%C19 $4%C20 $2%C21 $3")}, + +{"Banned", pevt_generic_channel_help, 1, +N_("%C22*%O$tCannot join%C26 %B$1 %O(You are banned).")}, + +{"Beep", pevt_generic_none_help, 128, +""}, + +{"Change Nick", pevt_changenick_help, 2, +N_("%C22*%O$t$1 is now known as $2")}, + +{"Channel Action", pevt_chanaction_help, 132, +"%C18*$t$1%O $2"}, + +{"Channel Action Hilight", pevt_chanaction_help, 132, +"%C21*%O$t%C21%B$1%O%C21 $2"}, + +{"Channel Ban", pevt_chanban_help, 2, +N_("%C22*%O$t$1 sets ban on $2")}, + +{"Channel Creation", pevt_chandate_help, 2, +N_("%C22*%O$tChannel $1 created on $2")}, + +{"Channel DeHalfOp", pevt_chandehop_help, 2, +N_("%C22*%O$t%C26$1%O removes channel half-operator status from%C26 $2")}, + +{"Channel DeOp", pevt_chandeop_help, 2, +N_("%C22*%O$t%C26$1%O removes channel operator status from%C26 $2")}, + +{"Channel DeVoice", pevt_chandevoice_help, 2, +N_("%C22*%O$t%C26$1%O removes voice from%C26 $2")}, + +{"Channel Exempt", pevt_chanexempt_help, 2, +N_("%C22*%O$t$1 sets exempt on $2")}, + +{"Channel Half-Operator", pevt_chanhop_help, 2, +N_("%C22*%O$t%C26$1%O gives channel half-operator status to%C26 $2")}, + +{"Channel INVITE", pevt_chaninvite_help, 2, +N_("%C22*%O$t$1 sets invite on $2")}, + +{"Channel List", pevt_generic_none_help, 0, +N_("%UChannel Users Topic")}, + +{"Channel Message", pevt_chanmsg_help, 132, +"%C18%H<%H$4$1%H>%H%O$t$2"}, + +{"Channel Mode Generic", pevt_chanmodegen_help, 4, +N_("%C22*%O$t$1 sets mode $2$3 $4")}, + +{"Channel Modes", pevt_chanmodes_help, 2, +N_("%C22*%O$t%C22Channel $1 modes: $2")}, + +{"Channel Msg Hilight", pevt_chanmsg_help, 132, +"$4%C21%B%H<%H$1%H>%H%O%C21$t$2"}, + +{"Channel Notice", pevt_channotice_help, 131, +"%C28-%C29$1/$2%C28-%O$t$3"}, + +{"Channel Operator", pevt_chanop_help, 2, +N_("%C22*%O$t%C26$1%O gives channel operator status to%C26 $2")}, + +{"Channel Remove Exempt", pevt_chanrmexempt_help, 2, +N_("%C22*%O$t$1 removes exempt on $2")}, + +{"Channel Remove Invite", pevt_chanrminvite_help, 2, +N_("%C22*%O$t$1 removes invite on $2")}, + +{"Channel Remove Keyword", pevt_chanrmkey_help, 1, +N_("%C22*%O$t$1 removes channel keyword")}, + +{"Channel Remove Limit", pevt_chanrmlimit_help, 1, +N_("%C22*%O$t$1 removes user limit")}, + +{"Channel Set Key", pevt_chansetkey_help, 2, +N_("%C22*%O$t$1 sets channel keyword to $2")}, + +{"Channel Set Limit", pevt_chansetlimit_help, 2, +N_("%C22*%O$t$1 sets channel limit to $2")}, + +{"Channel UnBan", pevt_chanunban_help, 2, +N_("%C22*%O$t$1 removes ban on $2")}, + +{"Channel Voice", pevt_chanvoice_help, 2, +N_("%C22*%O$t%C26$1%O gives voice to%C26 $2")}, + +{"Connected", pevt_generic_none_help, 0, +N_("%C22*%O$t%C22Connected. Now logging in...")}, + +{"Connecting", pevt_connect_help, 3, +N_("%C22*%O$t%C22Connecting to $1 ($2) port $3%O...")}, + +{"Connection Failed", pevt_connfail_help, 1, +N_("%C21*%O$t%C21Connection failed. Error: $1")}, + +{"CTCP Generic", pevt_ctcpgen_help, 2, +N_("%C22*%O$tReceived a CTCP $1 from $2")}, + +{"CTCP Generic to Channel", pevt_ctcpgenc_help, 3, +N_("%C22*%O$tReceived a CTCP $1 from $2 (to $3)")}, + +{"CTCP Send", pevt_ctcpsend_help, 2, +N_("%C19>%O$1%C19<%O$tCTCP $2")}, + +{"CTCP Sound", pevt_ctcpsnd_help, 2, +N_("%C22*%O$tReceived a CTCP Sound $1 from $2")}, + +{"CTCP Sound to Channel", pevt_ctcpsnd_help, 3, +N_("%C22*%O$tReceived a CTCP Sound $1 from $2 (to $3)")}, + +{"DCC CHAT Abort", pevt_dccchatabort_help, 1, +N_("%C22*%O$tDCC CHAT to %C26$1%O aborted.")}, + +{"DCC CHAT Connect", pevt_dccchatcon_help, 2, +N_("%C22*%O$tDCC CHAT connection established to %C26$1 %C30[%O$2%C30]")}, + +{"DCC CHAT Failed", pevt_dccchaterr_help, 4, +N_("%C22*%O$tDCC CHAT to %C26$1%O lost ($4).")}, + +{"DCC CHAT Offer", pevt_generic_nick_help, 1, +N_("%C22*%O$tReceived a DCC CHAT offer from $1")}, + +{"DCC CHAT Offering", pevt_generic_nick_help, 1, +N_("%C22*%O$tOffering DCC CHAT to $1")}, + +{"DCC CHAT Reoffer", pevt_generic_nick_help, 1, +N_("%C22*%O$tAlready offering CHAT to $1")}, + +{"DCC Conection Failed", pevt_dccconfail_help, 3, +N_("%C22*%O$tDCC $1 connect attempt to%C26 $2%O failed (err=$3).")}, + +{"DCC Generic Offer", pevt_dccgenericoffer_help, 2, +N_("%C22*%O$tReceived '$1%O' from $2")}, + +{"DCC Header", pevt_generic_none_help, 0, +N_("%C24,18 Type To/From Status Size Pos File ")}, + +{"DCC Malformed", pevt_malformed_help, 2, +N_("%C22*%O$tReceived a malformed DCC request from %C26$1%O.%010%C22*%O$tContents of packet: $2")}, + +{"DCC Offer", pevt_dccoffer_help, 3, +N_("%C22*%O$tOffering%C26 $1%O to%C26 $2")}, + +{"DCC Offer Not Valid", pevt_generic_none_help, 0, +N_("%C22*%O$tNo such DCC offer.")}, + +{"DCC RECV Abort", pevt_dccfileabort_help, 2, +N_("%C22*%O$tDCC RECV%C26 $2%O to%C26 $1%O aborted.")}, + +{"DCC RECV Complete", pevt_dccrecvcomp_help, 4, +N_("%C22*%O$tDCC RECV%C26 $1%O from%C26 $3%O complete %C30[%C26$4%O cps%C30]%O.")}, + +{"DCC RECV Connect", pevt_dcccon_help, 3, +N_("%C22*%O$tDCC RECV connection established to%C26 $1 %C30[%O$2%C30]")}, + +{"DCC RECV Failed", pevt_dccrecverr_help, 4, +N_("%C22*%O$tDCC RECV%C26 $1%O from%C26 $3%O failed ($4).")}, + +{"DCC RECV File Open Error", pevt_generic_file_help, 2, +N_("%C22*%O$tDCC RECV: Cannot open $1 for writing ($2).")}, + +{"DCC Rename", pevt_dccrename_help, 2, +N_("%C22*%O$tThe file%C26 $1%C already exists, saving it as%C26 $2%O instead.")}, + +{"DCC RESUME Request", pevt_dccresumeoffer_help, 3, +N_("%C22*%O$t%C26$1 %Ohas requested to resume%C26 $2 %Cfrom%C26 $3%C.")}, + +{"DCC SEND Abort", pevt_dccfileabort_help, 2, +N_("%C22*%O$tDCC SEND%C26 $2%O to%C26 $1%O aborted.")}, + +{"DCC SEND Complete", pevt_dccsendcomp_help, 3, +N_("%C22*%O$tDCC SEND%C26 $1%O to%C26 $2%O complete %C30[%C26$3%O cps%C30]%O.")}, + +{"DCC SEND Connect", pevt_dcccon_help, 3, +N_("%C22*%O$tDCC SEND connection established to%C26 $1 %C30[%O$2%C30]")}, + +{"DCC SEND Failed", pevt_dccsendfail_help, 3, +N_("%C22*%O$tDCC SEND%C26 $1%O to%C26 $2%O failed. $3")}, + +{"DCC SEND Offer", pevt_dccsendoffer_help, 4, +N_("%C22*%O$t%C26$1 %Ohas offered%C26 $2 %O(%C26$3 %Obytes)")}, + +{"DCC Stall", pevt_dccstall_help, 3, +N_("%C22*%O$tDCC $1%C26 $2 %Oto%C26 $3 %Cstalled - aborting.")}, + +{"DCC Timeout", pevt_dccstall_help, 3, +N_("%C22*%O$tDCC $1%C26 $2 %Oto%C26 $3 %Otimed out - aborting.")}, + +{"Delete Notify", pevt_generic_nick_help, 1, +N_("%C22*%O$t$1 deleted from notify list.")}, + +{"Disconnected", pevt_discon_help, 1, +N_("%C22*%O$tDisconnected ($1).")}, + +{"Found IP", pevt_foundip_help, 1, +N_("%C22*%O$tFound your IP: [$1]")}, + +{"Generic Message", pevt_genmsg_help, 130, +"$1$t$2"}, + +{"Ignore Add", pevt_ignoreaddremove_help, 1, +N_("%O%C26$1%O added to ignore list.")}, + +{"Ignore Changed", pevt_ignoreaddremove_help, 1, +N_("Ignore on %C26$1%O changed.")}, + +{"Ignore Footer", pevt_generic_none_help, 0, +N_("%C24,18 ")}, + +{"Ignore Header", pevt_generic_none_help, 0, +N_("%C24,18 Hostmask PRIV NOTI CHAN CTCP DCC INVI UNIG ")}, + +{"Ignore Remove", pevt_ignoreaddremove_help, 1, +N_("%O%C26$1%O removed from ignore list.")}, + +{"Ignorelist Empty", pevt_generic_none_help, 0, +N_(" Ignore list is empty.")}, + +{"Invite", pevt_generic_channel_help, 1, +N_("%C22*%O$tCannot join%C26 %B$1 %O(Channel is invite only).")}, + +{"Invited", pevt_invited_help, 3, +N_("%C22*%O$tYou have been invited to%C26 $1%O by%C26 $2%C (%C26$3%C)")}, + +{"Join", pevt_join_help, 3, +N_("%C19*%O$t%C19%B$1 %B($3) has joined $2")}, + +{"Keyword", pevt_generic_channel_help, 1, +N_("%C22*%O$tCannot join%C26 %B$1 %O(Requires keyword).")}, + +{"Kick", pevt_kick_help, 4, +N_("%C21*%O$t%C21$1 has kicked $2 from $3 ($4%O%C21)")}, + +{"Killed", pevt_kill_help, 2, +N_("%C22*%O$tYou have been killed by $1 ($2%O%C22)")}, + +{"Message Send", pevt_ctcpsend_help, 130, +"%C19>%O$1%C19<%O$t$2"}, + +{"Motd", pevt_servertext_help, 129, +"%C16*%O$t$1%O"}, + +{"MOTD Skipped", pevt_generic_none_help, 0, +N_("%C22*%O$t%C22MOTD Skipped.")}, + +{"Nick Clash", pevt_nickclash_help, 2, +N_("%C22*%O$t$1 already in use. Retrying with $2...")}, + +{"Nick Failed", pevt_generic_none_help, 0, +N_("%C22*%O$tNickname already in use. Use /NICK to try another.")}, + +{"No DCC", pevt_generic_none_help, 0, +N_("%C22*%O$tNo such DCC.")}, + +{"No Running Process", pevt_generic_none_help, 0, +N_("%C22*%O$tNo process is currently running")}, + +{"Notice", pevt_notice_help, 130, +"%C28-%C29$1%C28-%O$t$2"}, + +{"Notice Send", pevt_ctcpsend_help, 130, +"%C19>%O$1%C19<%O$t$2"}, + +{"Notify Empty", pevt_generic_none_help, 0, +N_("$tNotify list is empty.")}, + +{"Notify Header", pevt_generic_none_help, 0, +N_("%C24,18 %B Notify List ")}, + +{"Notify Number", pevt_notifynumber_help, 1, +N_("%C22*%O$t$1 users in notify list.")}, + +{"Notify Offline", pevt_generic_nick_help, 3, +N_("%C22*%O$tNotify: $1 is offline ($3).")}, + +{"Notify Online", pevt_generic_nick_help, 3, +N_("%C22*%O$tNotify: $1 is online ($3).")}, + +{"Open Dialog", pevt_generic_none_help, 128, +""}, + +{"Part", pevt_part_help, 3, +N_("%C23*%O$t%C23$1 (%O%C23$2) has left $3")}, + +{"Part with Reason", pevt_partreason_help, 4, +N_("%C23*%O$t%C23$1 (%O%C23$2) has left $3 (%O%C23%B%B$4%O%C23)")}, + +{"Ping Reply", pevt_pingrep_help, 2, +N_("%C22*%O$tPing reply from $1: $2 second(s)")}, + +{"Ping Timeout", pevt_pingtimeout_help, 1, +N_("%C22*%O$tNo ping reply for $1 seconds, disconnecting.")}, + +{"Private Action", pevt_privmsg_help, 131, +"%C18**$t$3$1%O $2 %C18**"}, + +{"Private Action to Dialog", pevt_privmsg_help, 131, +"%C18*$t$3$1%O $2"}, + +{"Private Message", pevt_privmsg_help, 131, +"%C28*%C29$3$1%C28*$t%O$2"}, + +{"Private Message to Dialog", pevt_privmsg_help, 131, +"%C18%H<%H$3$1%H>%H%O$t$2"}, + +{"Process Already Running", pevt_generic_none_help, 0, +N_("%C22*%O$tA process is already running")}, + +{"Quit", pevt_quit_help, 3, +N_("%C23*%O$t%C23$1 has quit (%O%C23%B%B$2%O%C23)")}, + +{"Raw Modes", pevt_rawmodes_help, 2, +N_("%C22*%O$t$1 sets modes%B %C30[%O$2%B%C30]")}, + +{"Receive Wallops", pevt_privmsg_help, 2, +N_("%C28-%C29$1/Wallops%C28-%O$t$2")}, + +{"Resolving User", pevt_resolvinguser_help, 2, +N_("%C22*%O$tLooking up IP number for%C26 $1%O...")}, + +{"Server Connected", pevt_generic_none_help, 0, +N_("%C22*%O$t%C22Connected.")}, + +{"Server Error", pevt_servererror_help, 129, +"%C22*%O$t$1"}, + +{"Server Lookup", pevt_serverlookup_help, 1, +N_("%C22*%O$t%C22Looking up $1")}, + +{"Server Notice", pevt_servertext_help, 130, +"%C22*%O$t$1"}, + +{"Server Text", pevt_servertext_help, 131, +"%C22*%O$t$1"}, + +{"SSL Message", pevt_sslmessage_help, 130, +"%C22*%O$t$1"}, + +{"Stop Connection", pevt_sconnect_help, 1, +N_("%C22*%O$tStopped previous connection attempt (pid=$1)")}, + +{"Topic", pevt_topic_help, 2, +N_("%C29*%O$t%C29Topic for $1%C %C29is: $2")}, + +{"Topic Change", pevt_newtopic_help, 3, +N_("%C22*%O$t$1 has changed the topic to: $2")}, + +{"Topic Creation", pevt_topicdate_help, 3, +N_("%C29*%O$t%C29Topic for $1%C %C29set by $2%C %C29at $3")}, + +{"Unknown Host", pevt_generic_none_help, 0, +N_("%C22*%O$tUnknown host. Maybe you misspelled it?")}, + +{"User Limit", pevt_generic_channel_help, 1, +N_("%C22*%O$tCannot join%C26 %B$1 %O(User limit reached).")}, + +{"Users On Channel", pevt_usersonchan_help, 2, +N_("%C22*%O$t%C26Users on $1:%C $2")}, + +{"WhoIs Authenticated", pevt_whoisauth_help, 3, +N_("%C22*%O$t%C28[%O$1%C28] %O$2%C27 $3")}, + +{"WhoIs Away Line", pevt_whois5_help, 2, +N_("%C22*%O$t%C28[%O$1%C28] %Cis away %C30(%O$2%O%C30)")}, + +{"WhoIs Channel/Oper Line", pevt_whois2_help, 2, +N_("%C22*%O$t%C28[%O$1%C28]%O $2")}, + +{"WhoIs End", pevt_whois6_help, 1, +N_("%C22*%O$t%C28[%O$1%C28] %OEnd of WHOIS list.")}, + +{"WhoIs Identified", pevt_whoisid_help, 2, +N_("%C22*%O$t%C28[%O$1%C28]%O $2")}, + +{"WhoIs Idle Line", pevt_whois4_help, 2, +N_("%C22*%O$t%C28[%O$1%C28]%O idle%C26 $2")}, + +{"WhoIs Idle Line with Signon", pevt_whois4t_help, 3, +N_("%C22*%O$t%C28[%O$1%C28]%O idle%C26 $2%O, signon:%C26 $3")}, + +{"WhoIs Name Line", pevt_whois1_help, 4, +N_("%C22*%O$t%C28[%O$1%C28] %C30(%O$2@$3%C30)%O: $4")}, + +{"WhoIs Real Host", pevt_whoisrealhost_help, 4, +N_("%C22*%O$t%C28[%O$1%C28] %Oreal user@host%C27 $2%O, real IP%C27 $3")}, + +{"WhoIs Server Line", pevt_whois3_help, 2, +N_("%C22*%O$t%C28[%O$1%C28]%O $2")}, + +{"WhoIs Special", pevt_whoisid_help, 3, +N_("%C22*%O$t%C28[%O$1%C28]%O $2")}, + +{"You Join", pevt_join_help, 3, +N_("%C19*%O$t%C19Now talking on $2")}, + +{"You Kicked", pevt_ukick_help, 4, +N_("%C23*$tYou have been kicked from $2 by $3 ($4%O%C23)")}, + +{"You Part", pevt_part_help, 3, +N_("%C23*$tYou have left channel $3")}, + +{"You Part with Reason", pevt_partreason_help, 4, +N_("%C23*$tYou have left channel $3 (%O%C23%B%B$4%O%C23)")}, + +{"Your Action", pevt_chanaction_help, 131, +"%C18*$t$1%O $2"}, + +{"Your Invitation", pevt_uinvite_help, 3, +N_("%C22*%O$tYou've invited%C26 $1%O to%C26 $2%O (%C26$3%O)")}, + +{"Your Message", pevt_chanmsg_help, 132, +"%C31%H<%H$4$1%H>%H%O%C30$t$2"}, + +{"Your Nick Changing", pevt_uchangenick_help, 2, +N_("%C22*%O$tYou are now known as $2")}, +}; diff --git a/src/common/textevents.in b/src/common/textevents.in new file mode 100644 index 00000000..5b4ce18c --- /dev/null +++ b/src/common/textevents.in @@ -0,0 +1,840 @@ +Add Notify +XP_TE_ADDNOTIFY +pevt_generic_nick_help +%C22*%O$t$1 added to notify list. +1 + +Ban List +XP_TE_BANLIST +pevt_banlist_help +%C22*%O$t$1 Banlist:%C19 $4%C20 $2%C21 $3 +4 + +Banned +XP_TE_BANNED +pevt_generic_channel_help +%C22*%O$tCannot join%C26 %B$1 %O(You are banned). +1 + +Beep +XP_TE_BEEP +pevt_generic_none_help + +n0 + +Change Nick +XP_TE_CHANGENICK +pevt_changenick_help +%C22*%O$t$1 is now known as $2 +2 + +Channel Action +XP_TE_CHANACTION +pevt_chanaction_help +%C18*$t$1%O $2 +n4 + +Channel Action Hilight +XP_TE_HCHANACTION +pevt_chanaction_help +%C21*%O$t%C21%B$1%O%C21 $2 +n4 + +Channel Ban +XP_TE_CHANBAN +pevt_chanban_help +%C22*%O$t$1 sets ban on $2 +2 + +Channel Creation +XP_TE_CHANDATE +pevt_chandate_help +%C22*%O$tChannel $1 created on $2 +2 + +Channel DeHalfOp +XP_TE_CHANDEHOP +pevt_chandehop_help +%C22*%O$t%C26$1%O removes channel half-operator status from%C26 $2 +2 + +Channel DeOp +XP_TE_CHANDEOP +pevt_chandeop_help +%C22*%O$t%C26$1%O removes channel operator status from%C26 $2 +2 + +Channel DeVoice +XP_TE_CHANDEVOICE +pevt_chandevoice_help +%C22*%O$t%C26$1%O removes voice from%C26 $2 +2 + +Channel Exempt +XP_TE_CHANEXEMPT +pevt_chanexempt_help +%C22*%O$t$1 sets exempt on $2 +2 + +Channel Half-Operator +XP_TE_CHANHOP +pevt_chanhop_help +%C22*%O$t%C26$1%O gives channel half-operator status to%C26 $2 +2 + +Channel INVITE +XP_TE_CHANINVITE +pevt_chaninvite_help +%C22*%O$t$1 sets invite on $2 +2 + +Channel List +XP_TE_CHANLISTHEAD +pevt_generic_none_help +%UChannel Users Topic +0 + +Channel Message +XP_TE_CHANMSG +pevt_chanmsg_help +%C18%H<%H$4$1%H>%H%O$t$2 +n4 + +Channel Mode Generic +XP_TE_CHANMODEGEN +pevt_chanmodegen_help +%C22*%O$t$1 sets mode $2$3 $4 +4 + +Channel Modes +XP_TE_CHANMODES +pevt_chanmodes_help +%C22*%O$t%C22Channel $1 modes: $2 +2 + +Channel Msg Hilight +XP_TE_HCHANMSG +pevt_chanmsg_help +$4%C21%B%H<%H$1%H>%H%O%C21$t$2 +n4 + +Channel Notice +XP_TE_CHANNOTICE +pevt_channotice_help +%C28-%C29$1/$2%C28-%O$t$3 +n3 + +Channel Operator +XP_TE_CHANOP +pevt_chanop_help +%C22*%O$t%C26$1%O gives channel operator status to%C26 $2 +2 + +Channel Remove Exempt +XP_TE_CHANRMEXEMPT +pevt_chanrmexempt_help +%C22*%O$t$1 removes exempt on $2 +2 + +Channel Remove Invite +XP_TE_CHANRMINVITE +pevt_chanrminvite_help +%C22*%O$t$1 removes invite on $2 +2 + +Channel Remove Keyword +XP_TE_CHANRMKEY +pevt_chanrmkey_help +%C22*%O$t$1 removes channel keyword +1 + +Channel Remove Limit +XP_TE_CHANRMLIMIT +pevt_chanrmlimit_help +%C22*%O$t$1 removes user limit +1 + +Channel Set Key +XP_TE_CHANSETKEY +pevt_chansetkey_help +%C22*%O$t$1 sets channel keyword to $2 +2 + +Channel Set Limit +XP_TE_CHANSETLIMIT +pevt_chansetlimit_help +%C22*%O$t$1 sets channel limit to $2 +2 + +Channel UnBan +XP_TE_CHANUNBAN +pevt_chanunban_help +%C22*%O$t$1 removes ban on $2 +2 + +Channel Voice +XP_TE_CHANVOICE +pevt_chanvoice_help +%C22*%O$t%C26$1%O gives voice to%C26 $2 +2 + +Connected +XP_TE_CONNECTED +pevt_generic_none_help +%C22*%O$t%C22Connected. Now logging in... +0 + +Connecting +XP_TE_CONNECT +pevt_connect_help +%C22*%O$t%C22Connecting to $1 ($2) port $3%O... +3 + +Connection Failed +XP_TE_CONNFAIL +pevt_connfail_help +%C21*%O$t%C21Connection failed. Error: $1 +1 + +CTCP Generic +XP_TE_CTCPGEN +pevt_ctcpgen_help +%C22*%O$tReceived a CTCP $1 from $2 +2 + +CTCP Generic to Channel +XP_TE_CTCPGENC +pevt_ctcpgenc_help +%C22*%O$tReceived a CTCP $1 from $2 (to $3) +3 + +CTCP Send +XP_TE_CTCPSEND +pevt_ctcpsend_help +%C19>%O$1%C19<%O$tCTCP $2 +2 + +CTCP Sound +XP_TE_CTCPSND +pevt_ctcpsnd_help +%C22*%O$tReceived a CTCP Sound $1 from $2 +2 + +CTCP Sound to Channel +XP_TE_CTCPSNDC +pevt_ctcpsnd_help +%C22*%O$tReceived a CTCP Sound $1 from $2 (to $3) +3 + +DCC CHAT Abort +XP_TE_DCCCHATABORT +pevt_dccchatabort_help +%C22*%O$tDCC CHAT to %C26$1%O aborted. +1 + +DCC CHAT Connect +XP_TE_DCCCONCHAT +pevt_dccchatcon_help +%C22*%O$tDCC CHAT connection established to %C26$1 %C30[%O$2%C30] +2 + +DCC CHAT Failed +XP_TE_DCCCHATF +pevt_dccchaterr_help +%C22*%O$tDCC CHAT to %C26$1%O lost ($4). +4 + +DCC CHAT Offer +XP_TE_DCCCHATOFFER +pevt_generic_nick_help +%C22*%O$tReceived a DCC CHAT offer from $1 +1 + +DCC CHAT Offering +XP_TE_DCCCHATOFFERING +pevt_generic_nick_help +%C22*%O$tOffering DCC CHAT to $1 +1 + +DCC CHAT Reoffer +XP_TE_DCCCHATREOFFER +pevt_generic_nick_help +%C22*%O$tAlready offering CHAT to $1 +1 + +DCC Conection Failed +XP_TE_DCCCONFAIL +pevt_dccconfail_help +%C22*%O$tDCC $1 connect attempt to%C26 $2%O failed (err=$3). +3 + +DCC Generic Offer +XP_TE_DCCGENERICOFFER +pevt_dccgenericoffer_help +%C22*%O$tReceived '$1%O' from $2 +2 + +DCC Header +XP_TE_DCCHEAD +pevt_generic_none_help +%C24,18 Type To/From Status Size Pos File +0 + +DCC Malformed +XP_TE_MALFORMED +pevt_malformed_help +%C22*%O$tReceived a malformed DCC request from %C26$1%O.%010%C22*%O$tContents of packet: $2 +2 + +DCC Offer +XP_TE_DCCOFFER +pevt_dccoffer_help +%C22*%O$tOffering%C26 $1%O to%C26 $2 +3 + +DCC Offer Not Valid +XP_TE_DCCIVAL +pevt_generic_none_help +%C22*%O$tNo such DCC offer. +0 + +DCC RECV Abort +XP_TE_DCCRECVABORT +pevt_dccfileabort_help +%C22*%O$tDCC RECV%C26 $2%O to%C26 $1%O aborted. +2 + +DCC RECV Complete +XP_TE_DCCRECVCOMP +pevt_dccrecvcomp_help +%C22*%O$tDCC RECV%C26 $1%O from%C26 $3%O complete %C30[%C26$4%O cps%C30]%O. +4 + +DCC RECV Connect +XP_TE_DCCCONRECV +pevt_dcccon_help +%C22*%O$tDCC RECV connection established to%C26 $1 %C30[%O$2%C30] +3 + +DCC RECV Failed +XP_TE_DCCRECVERR +pevt_dccrecverr_help +%C22*%O$tDCC RECV%C26 $1%O from%C26 $3%O failed ($4). +4 + +DCC RECV File Open Error +XP_TE_DCCFILEERR +pevt_generic_file_help +%C22*%O$tDCC RECV: Cannot open $1 for writing ($2). +2 + +DCC Rename +XP_TE_DCCRENAME +pevt_dccrename_help +%C22*%O$tThe file%C26 $1%C already exists, saving it as%C26 $2%O instead. +2 + +DCC RESUME Request +XP_TE_DCCRESUMEREQUEST +pevt_dccresumeoffer_help +%C22*%O$t%C26$1 %Ohas requested to resume%C26 $2 %Cfrom%C26 $3%C. +3 + +DCC SEND Abort +XP_TE_DCCSENDABORT +pevt_dccfileabort_help +%C22*%O$tDCC SEND%C26 $2%O to%C26 $1%O aborted. +2 + +DCC SEND Complete +XP_TE_DCCSENDCOMP +pevt_dccsendcomp_help +%C22*%O$tDCC SEND%C26 $1%O to%C26 $2%O complete %C30[%C26$3%O cps%C30]%O. +3 + +DCC SEND Connect +XP_TE_DCCCONSEND +pevt_dcccon_help +%C22*%O$tDCC SEND connection established to%C26 $1 %C30[%O$2%C30] +3 + +DCC SEND Failed +XP_TE_DCCSENDFAIL +pevt_dccsendfail_help +%C22*%O$tDCC SEND%C26 $1%O to%C26 $2%O failed. $3 +3 + +DCC SEND Offer +XP_TE_DCCSENDOFFER +pevt_dccsendoffer_help +%C22*%O$t%C26$1 %Ohas offered%C26 $2 %O(%C26$3 %Obytes) +4 + +DCC Stall +XP_TE_DCCSTALL +pevt_dccstall_help +%C22*%O$tDCC $1%C26 $2 %Oto%C26 $3 %Cstalled - aborting. +3 + +DCC Timeout +XP_TE_DCCTOUT +pevt_dccstall_help +%C22*%O$tDCC $1%C26 $2 %Oto%C26 $3 %Otimed out - aborting. +3 + +Delete Notify +XP_TE_DELNOTIFY +pevt_generic_nick_help +%C22*%O$t$1 deleted from notify list. +1 + +Disconnected +XP_TE_DISCON +pevt_discon_help +%C22*%O$tDisconnected ($1). +1 + +Found IP +XP_TE_FOUNDIP +pevt_foundip_help +%C22*%O$tFound your IP: [$1] +1 + +Generic Message +XP_TE_GENMSG +pevt_genmsg_help +$1$t$2 +n2 + +Ignore Add +XP_TE_IGNOREADD +pevt_ignoreaddremove_help +%O%C26$1%O added to ignore list. +1 + +Ignore Changed +XP_TE_IGNORECHANGE +pevt_ignoreaddremove_help +Ignore on %C26$1%O changed. +1 + +Ignore Footer +XP_TE_IGNOREFOOTER +pevt_generic_none_help +%C24,18 +0 + +Ignore Header +XP_TE_IGNOREHEADER +pevt_generic_none_help +%C24,18 Hostmask PRIV NOTI CHAN CTCP DCC INVI UNIG +0 + +Ignore Remove +XP_TE_IGNOREREMOVE +pevt_ignoreaddremove_help +%O%C26$1%O removed from ignore list. +1 + +Ignorelist Empty +XP_TE_IGNOREEMPTY +pevt_generic_none_help + Ignore list is empty. +0 + +Invite +XP_TE_INVITE +pevt_generic_channel_help +%C22*%O$tCannot join%C26 %B$1 %O(Channel is invite only). +1 + +Invited +XP_TE_INVITED +pevt_invited_help +%C22*%O$tYou have been invited to%C26 $1%O by%C26 $2%C (%C26$3%C) +3 + +Join +XP_TE_JOIN +pevt_join_help +%C19*%O$t%C19%B$1 %B($3) has joined $2 +3 + +Keyword +XP_TE_KEYWORD +pevt_generic_channel_help +%C22*%O$tCannot join%C26 %B$1 %O(Requires keyword). +1 + +Kick +XP_TE_KICK +pevt_kick_help +%C21*%O$t%C21$1 has kicked $2 from $3 ($4%O%C21) +4 + +Killed +XP_TE_KILL +pevt_kill_help +%C22*%O$tYou have been killed by $1 ($2%O%C22) +2 + +Message Send +XP_TE_MSGSEND +pevt_ctcpsend_help +%C19>%O$1%C19<%O$t$2 +n2 + +Motd +XP_TE_MOTD +pevt_servertext_help +%C16*%O$t$1%O +n1 + +MOTD Skipped +XP_TE_MOTDSKIP +pevt_generic_none_help +%C22*%O$t%C22MOTD Skipped. +0 + +Nick Clash +XP_TE_NICKCLASH +pevt_nickclash_help +%C22*%O$t$1 already in use. Retrying with $2... +2 + +Nick Failed +XP_TE_NICKFAIL +pevt_generic_none_help +%C22*%O$tNickname already in use. Use /NICK to try another. +0 + +No DCC +XP_TE_NODCC +pevt_generic_none_help +%C22*%O$tNo such DCC. +0 + +No Running Process +XP_TE_NOCHILD +pevt_generic_none_help +%C22*%O$tNo process is currently running +0 + +Notice +XP_TE_NOTICE +pevt_notice_help +%C28-%C29$1%C28-%O$t$2 +n2 + +Notice Send +XP_TE_NOTICESEND +pevt_ctcpsend_help +%C19>%O$1%C19<%O$t$2 +n2 + +Notify Empty +XP_TE_NOTIFYEMPTY +pevt_generic_none_help +$tNotify list is empty. +0 + +Notify Header +XP_TE_NOTIFYHEAD +pevt_generic_none_help +%C24,18 %B Notify List +0 + +Notify Number +XP_TE_NOTIFYNUMBER +pevt_notifynumber_help +%C22*%O$t$1 users in notify list. +1 + +Notify Offline +XP_TE_NOTIFYOFFLINE +pevt_generic_nick_help +%C22*%O$tNotify: $1 is offline ($3). +3 + +Notify Online +XP_TE_NOTIFYONLINE +pevt_generic_nick_help +%C22*%O$tNotify: $1 is online ($3). +3 + +Open Dialog +XP_TE_OPENDIALOG +pevt_generic_none_help + +n0 + +Part +XP_TE_PART +pevt_part_help +%C23*%O$t%C23$1 (%O%C23$2) has left $3 +3 + +Part with Reason +XP_TE_PARTREASON +pevt_partreason_help +%C23*%O$t%C23$1 (%O%C23$2) has left $3 (%O%C23%B%B$4%O%C23) +4 + +Ping Reply +XP_TE_PINGREP +pevt_pingrep_help +%C22*%O$tPing reply from $1: $2 second(s) +2 + +Ping Timeout +XP_TE_PINGTIMEOUT +pevt_pingtimeout_help +%C22*%O$tNo ping reply for $1 seconds, disconnecting. +1 + +Private Action +XP_TE_PRIVACTION +pevt_privmsg_help +%C18**$t$3$1%O $2 %C18** +n3 + +Private Action to Dialog +XP_TE_DPRIVACTION +pevt_privmsg_help +%C18*$t$3$1%O $2 +n3 + +Private Message +XP_TE_PRIVMSG +pevt_privmsg_help +%C28*%C29$3$1%C28*$t%O$2 +n3 + +Private Message to Dialog +XP_TE_DPRIVMSG +pevt_privmsg_help +%C18%H<%H$3$1%H>%H%O$t$2 +n3 + +Process Already Running +XP_TE_ALREADYPROCESS +pevt_generic_none_help +%C22*%O$tA process is already running +0 + +Quit +XP_TE_QUIT +pevt_quit_help +%C23*%O$t%C23$1 has quit (%O%C23%B%B$2%O%C23) +3 + +Raw Modes +XP_TE_RAWMODES +pevt_rawmodes_help +%C22*%O$t$1 sets modes%B %C30[%O$2%B%C30] +2 + +Receive Wallops +XP_TE_WALLOPS +pevt_privmsg_help +%C28-%C29$1/Wallops%C28-%O$t$2 +2 + +Resolving User +XP_TE_RESOLVINGUSER +pevt_resolvinguser_help +%C22*%O$tLooking up IP number for%C26 $1%O... +2 + +Server Connected +XP_TE_SERVERCONNECTED +pevt_generic_none_help +%C22*%O$t%C22Connected. +0 + +Server Error +XP_TE_SERVERERROR +pevt_servererror_help +%C22*%O$t$1 +n1 + +Server Lookup +XP_TE_SERVERLOOKUP +pevt_serverlookup_help +%C22*%O$t%C22Looking up $1 +1 + +Server Notice +XP_TE_SERVNOTICE +pevt_servertext_help +%C22*%O$t$1 +n2 + +Server Text +XP_TE_SERVTEXT +pevt_servertext_help +%C22*%O$t$1 +n3 + +SSL Message +XP_TE_SSLMESSAGE +pevt_sslmessage_help +%C22*%O$t$1 +n2 + +Stop Connection +XP_TE_STOPCONNECT +pevt_sconnect_help +%C22*%O$tStopped previous connection attempt (pid=$1) +1 + +Topic +XP_TE_TOPIC +pevt_topic_help +%C29*%O$t%C29Topic for $1%C %C29is: $2 +2 + +Topic Change +XP_TE_NEWTOPIC +pevt_newtopic_help +%C22*%O$t$1 has changed the topic to: $2 +3 + +Topic Creation +XP_TE_TOPICDATE +pevt_topicdate_help +%C29*%O$t%C29Topic for $1%C %C29set by $2%C %C29at $3 +3 + +Unknown Host +XP_TE_UKNHOST +pevt_generic_none_help +%C22*%O$tUnknown host. Maybe you misspelled it? +0 + +User Limit +XP_TE_USERLIMIT +pevt_generic_channel_help +%C22*%O$tCannot join%C26 %B$1 %O(User limit reached). +1 + +Users On Channel +XP_TE_USERSONCHAN +pevt_usersonchan_help +%C22*%O$t%C26Users on $1:%C $2 +2 + +WhoIs Authenticated +XP_TE_WHOIS_AUTH +pevt_whoisauth_help +%C22*%O$t%C28[%O$1%C28] %O$2%C27 $3 +3 + +WhoIs Away Line +XP_TE_WHOIS5 +pevt_whois5_help +%C22*%O$t%C28[%O$1%C28] %Cis away %C30(%O$2%O%C30) +2 + +WhoIs Channel/Oper Line +XP_TE_WHOIS2 +pevt_whois2_help +%C22*%O$t%C28[%O$1%C28]%O $2 +2 + +WhoIs End +XP_TE_WHOIS6 +pevt_whois6_help +%C22*%O$t%C28[%O$1%C28] %OEnd of WHOIS list. +1 + +WhoIs Identified +XP_TE_WHOIS_ID +pevt_whoisid_help +%C22*%O$t%C28[%O$1%C28]%O $2 +2 + +WhoIs Idle Line +XP_TE_WHOIS4 +pevt_whois4_help +%C22*%O$t%C28[%O$1%C28]%O idle%C26 $2 +2 + +WhoIs Idle Line with Signon +XP_TE_WHOIS4T +pevt_whois4t_help +%C22*%O$t%C28[%O$1%C28]%O idle%C26 $2%O, signon:%C26 $3 +3 + +WhoIs Name Line +XP_TE_WHOIS1 +pevt_whois1_help +%C22*%O$t%C28[%O$1%C28] %C30(%O$2@$3%C30)%O: $4 +4 + +WhoIs Real Host +XP_TE_WHOIS_REALHOST +pevt_whoisrealhost_help +%C22*%O$t%C28[%O$1%C28] %Oreal user@host%C27 $2%O, real IP%C27 $3 +4 + +WhoIs Server Line +XP_TE_WHOIS3 +pevt_whois3_help +%C22*%O$t%C28[%O$1%C28]%O $2 +2 + +WhoIs Special +XP_TE_WHOIS_SPECIAL +pevt_whoisid_help +%C22*%O$t%C28[%O$1%C28]%O $2 +3 + +You Join +XP_TE_UJOIN +pevt_join_help +%C19*%O$t%C19Now talking on $2 +3 + +You Kicked +XP_TE_UKICK +pevt_ukick_help +%C23*$tYou have been kicked from $2 by $3 ($4%O%C23) +4 + +You Part +XP_TE_UPART +pevt_part_help +%C23*$tYou have left channel $3 +3 + +You Part with Reason +XP_TE_UPARTREASON +pevt_partreason_help +%C23*$tYou have left channel $3 (%O%C23%B%B$4%O%C23) +4 + +Your Action +XP_TE_UACTION +pevt_chanaction_help +%C18*$t$1%O $2 +n3 + +Your Invitation +XP_TE_UINVITE +pevt_uinvite_help +%C22*%O$tYou've invited%C26 $1%O to%C26 $2%O (%C26$3%O) +3 + +Your Message +XP_TE_UCHANMSG +pevt_chanmsg_help +%C31%H<%H$4$1%H>%H%O%C30$t$2 +n4 + +Your Nick Changing +XP_TE_UCHANGENICK +pevt_uchangenick_help +%C22*%O$tYou are now known as $2 +2 + diff --git a/src/common/tree.c b/src/common/tree.c new file mode 100644 index 00000000..1627bd98 --- /dev/null +++ b/src/common/tree.c @@ -0,0 +1,215 @@ +/* +This is used for quick userlist insertion and lookup. It's not really +a tree, but it could be :) +*/ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include "tree.h" + +#define ARRAY_GROW 32 + +struct _tree +{ + int elements; + int array_size; + void **array; + tree_cmp_func *cmp; + void *data; +}; + +tree * +tree_new (tree_cmp_func *cmp, void *data) +{ + tree *t = calloc (1, sizeof (tree)); + t->cmp = cmp; + t->data = data; + return t; +} + +void +tree_destroy (tree *t) +{ + if (t) + { + if (t->array) + free (t->array); + free (t); + } +} + +static int +tree_find_insertion_pos (tree *t, void *key, int *done) +{ + int c, u, l, idx; + + if (t->elements < 1) + { + *done = 1; + t->array[0] = key; + t->elements++; + return 0; + } + + if (t->elements < 2) + { + *done = 1; + c = t->cmp (key, t->array[0], t->data); + if (c == 0) + return -1; + t->elements++; + if (c > 0) + { + t->array[1] = key; + return 1; + } + t->array[1] = t->array[0]; + t->array[0] = key; + return 0; + } + + *done = 0; + + c = t->cmp (key, t->array[0], t->data); + if (c < 0) + return 0; /* prepend */ + + c = t->cmp (key, t->array[t->elements - 1], t->data); + if (c > 0) + return t->elements; /* append */ + + l = 0; + u = t->elements - 1; + while (1) + { + idx = (l + u) / 2; + c = t->cmp (key, t->array[idx], t->data); + + if (0 > c) + u = idx; + else if (0 < c && 0 > t->cmp (key, t->array[idx+1], t->data)) + return idx + 1; + else if (c == 0) + return -1; + else + l = idx + 1; + } +} + +static void +tree_insert_at_pos (tree *t, void *key, int pos) +{ + int post_bytes; + + /* append is easy */ + if (pos != t->elements) + { + post_bytes = (t->elements - pos) * sizeof (void *); + memmove (&t->array[pos + 1], &t->array[pos], post_bytes); + } + + t->array[pos] = key; + t->elements++; +} + +static void * +mybsearch (const void *key, void **array, size_t nmemb, + int (*compar) (const void *, const void *, void *data), void *data, int *pos) +{ + int l, u, idx; + int comparison; + + l = 0; + u = nmemb; + while (l < u) + { + idx = (l + u) / 2; + comparison = (*compar) (key, array[idx], data); + if (comparison < 0) + u = idx; + else if (comparison > 0) + l = idx + 1; + else + { + *pos = idx; + return array[idx]; + } + } + + return NULL; +} + +void * +tree_find (tree *t, void *key, tree_cmp_func *cmp, void *data, int *pos) +{ + if (!t || !t->array) + return NULL; + + return mybsearch (key, &t->array[0], t->elements, cmp, data, pos); +} + +static void +tree_remove_at_pos (tree *t, int pos) +{ + int post_bytes; + + t->elements--; + if (pos != t->elements) + { + post_bytes = (t->elements - pos) * sizeof (void *); + memmove (&t->array[pos], &t->array[pos + 1], post_bytes); + } +} + +int +tree_remove (tree *t, void *key, int *pos) +{ + void *data; + + data = tree_find (t, key, t->cmp, t->data, pos); + if (!data) + return 0; + + tree_remove_at_pos (t, *pos); + return 1; +} + +void +tree_foreach (tree *t, tree_traverse_func *func, void *data) +{ + int j; + + if (!t || !t->array) + return; + + for (j = 0; j < t->elements; j++) + { + if (!func (t->array[j], data)) + break; + } +} + +int +tree_insert (tree *t, void *key) +{ + int pos, done; + + if (!t) + return -1; + + if (t->array_size < t->elements + 1) + { + int new_size = t->array_size + ARRAY_GROW; + + t->array = realloc (t->array, sizeof (void *) * new_size); + t->array_size = new_size; + } + + pos = tree_find_insertion_pos (t, key, &done); + if (!done && pos != -1) + tree_insert_at_pos (t, key, pos); + + return pos; +} diff --git a/src/common/tree.h b/src/common/tree.h new file mode 100644 index 00000000..b1b66aa9 --- /dev/null +++ b/src/common/tree.h @@ -0,0 +1,16 @@ +#ifndef XCHAT_TREE_H +#define XCHAT_TREE_H + +typedef struct _tree tree; + +typedef int (tree_cmp_func) (const void *keya, const void *keyb, void *data); +typedef int (tree_traverse_func) (const void *key, void *data); + +tree *tree_new (tree_cmp_func *cmp, void *data); +void tree_destroy (tree *t); +void *tree_find (tree *t, void *key, tree_cmp_func *cmp, void *data, int *pos); +int tree_remove (tree *t, void *key, int *pos); +void tree_foreach (tree *t, tree_traverse_func *func, void *data); +int tree_insert (tree *t, void *key); + +#endif diff --git a/src/common/url.c b/src/common/url.c new file mode 100644 index 00000000..92aeab0a --- /dev/null +++ b/src/common/url.c @@ -0,0 +1,280 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include "xchat.h" +#include "cfgfiles.h" +#include "fe.h" +#include "tree.h" +#include "url.h" +#ifdef HAVE_STRINGS_H +#include <strings.h> +#endif + +void *url_tree = NULL; + + +static int +url_free (char *url, void *data) +{ + free (url); + return TRUE; +} + +void +url_clear (void) +{ + tree_foreach (url_tree, (tree_traverse_func *)url_free, NULL); + tree_destroy (url_tree); + url_tree = NULL; +} + +static int +url_save_cb (char *url, FILE *fd) +{ + fprintf (fd, "%s\n", url); + return TRUE; +} + +void +url_save (const char *fname, const char *mode, gboolean fullpath) +{ + FILE *fd; + + if (fullpath) + fd = xchat_fopen_file (fname, mode, XOF_FULLPATH); + else + fd = xchat_fopen_file (fname, mode, 0); + if (fd == NULL) + return; + + tree_foreach (url_tree, (tree_traverse_func *)url_save_cb, fd); + fclose (fd); +} + +void +url_autosave (void) +{ + url_save ("url.save", "a", FALSE); +} + +static int +url_find (char *urltext) +{ + int pos; + + if (tree_find (url_tree, urltext, (tree_cmp_func *)strcasecmp, NULL, &pos)) + return 1; + return 0; +} + +static void +url_add (char *urltext, int len) +{ + char *data = malloc (len + 1); + if (!data) + return; + memcpy (data, urltext, len); + data[len] = 0; + + if (data[len - 1] == '.') /* chop trailing dot */ + { + len--; + data[len] = 0; + } + if (data[len - 1] == ')') /* chop trailing ) */ + data[len - 1] = 0; + + if (url_find (data)) + { + free (data); + return; + } + + if (!url_tree) + url_tree = tree_new ((tree_cmp_func *)strcasecmp, NULL); + + tree_insert (url_tree, data); + fe_url_add (data); +} + +/* check if a word is clickable. This is called on mouse motion events, so + keep it FAST! This new version was found to be almost 3x faster than + 2.4.4 release. */ + +int +url_check_word (char *word, int len) +{ +#define D(x) (x), ((sizeof (x)) - 1) + static const struct { + const char *s; + int len; + } + prefix[] = { + { D("irc.") }, + { D("ftp.") }, + { D("www.") }, + { D("irc://") }, + { D("ftp://") }, + { D("http://") }, + { D("https://") }, + { D("file://") }, + { D("rtsp://") }, + { D("ut2004://") }, + }, + suffix[] = { + { D(".org") }, + { D(".net") }, + { D(".com") }, + { D(".edu") }, + { D(".html") }, + { D(".info") }, + { D(".name") }, + }; +#undef D + const char *at, *dot; + int i, dots; + + if (len > 1 && word[1] == '#' && strchr("@+^%*#", word[0])) + return WORD_CHANNEL; + + if ((word[0] == '#' || word[0] == '&') && word[1] != '#' && word[1] != 0) + return WORD_CHANNEL; + + for (i = 0; i < G_N_ELEMENTS(prefix); i++) + { + int l; + + l = prefix[i].len; + if (len > l) + { + int j; + + /* This is pretty much strncasecmp(). */ + for (j = 0; j < l; j++) + { + unsigned char c = word[j]; + if (tolower(c) != prefix[i].s[j]) + break; + } + if (j == l) + return WORD_URL; + } + } + + at = strchr (word, '@'); /* check for email addy */ + dot = strrchr (word, '.'); + if (at && dot) + { + if (at < dot) + { + if (strchr (word, '*')) + return WORD_HOST; + else + return WORD_EMAIL; + } + } + + /* check if it's an IP number */ + dots = 0; + for (i = 0; i < len; i++) + { + if (word[i] == '.' && i > 0) + dots++; /* allow 127.0.0.1:80 */ + else if (!isdigit ((unsigned char) word[i]) && word[i] != ':') + { + dots = 0; + break; + } + } + if (dots == 3) + return WORD_HOST; + + if (len > 5) + { + for (i = 0; i < G_N_ELEMENTS(suffix); i++) + { + int l; + + l = suffix[i].len; + if (len > l) + { + const unsigned char *p = &word[len - l]; + int j; + + /* This is pretty much strncasecmp(). */ + for (j = 0; j < l; j++) + { + if (tolower(p[j]) != suffix[i].s[j]) + break; + } + if (j == l) + return WORD_HOST; + } + } + + if (word[len - 3] == '.' && + isalpha ((unsigned char) word[len - 2]) && + isalpha ((unsigned char) word[len - 1])) + return WORD_HOST; + } + + return 0; +} + +void +url_check_line (char *buf, int len) +{ + char *po = buf; + char *start; + int wlen; + + if (buf[0] == ':' && buf[1] != 0) + po++; + + start = po; + + /* check each "word" (space separated) */ + while (1) + { + switch (po[0]) + { + case 0: + case ' ': + wlen = po - start; + if (wlen > 2) + { + if (url_check_word (start, wlen) == WORD_URL) + { + url_add (start, wlen); + } + } + if (po[0] == 0) + return; + po++; + start = po; + break; + + default: + po++; + } + } +} diff --git a/src/common/url.h b/src/common/url.h new file mode 100644 index 00000000..487a2007 --- /dev/null +++ b/src/common/url.h @@ -0,0 +1,19 @@ +#ifndef XCHAT_URL_H +#define XCHAT_URL_H + +extern void *url_tree; + +#define WORD_URL 1 +#define WORD_NICK 2 +#define WORD_CHANNEL 3 +#define WORD_HOST 4 +#define WORD_EMAIL 5 +#define WORD_DIALOG -1 + +void url_clear (void); +void url_save (const char *fname, const char *mode, gboolean fullpath); +void url_autosave (void); +int url_check_word (char *word, int len); +void url_check_line (char *buf, int len); + +#endif diff --git a/src/common/userlist.c b/src/common/userlist.c new file mode 100644 index 00000000..18ac4bed --- /dev/null +++ b/src/common/userlist.c @@ -0,0 +1,454 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "xchat.h" +#include "modes.h" +#include "fe.h" +#include "notify.h" +#include "tree.h" +#include "xchatc.h" +#include "util.h" + + +static int +nick_cmp_az_ops (server *serv, struct User *user1, struct User *user2) +{ + unsigned int access1 = user1->access; + unsigned int access2 = user2->access; + int pos; + + if (access1 != access2) + { + for (pos = 0; pos < USERACCESS_SIZE; pos++) + { + if ((access1&(1<<pos)) && (access2&(1<<pos))) + break; + if ((access1&(1<<pos)) && !(access2&(1<<pos))) + return -1; + if (!(access1&(1<<pos)) && (access2&(1<<pos))) + return 1; + } + } + + return serv->p_cmp (user1->nick, user2->nick); +} + +static int +nick_cmp_alpha (struct User *user1, struct User *user2, server *serv) +{ + return serv->p_cmp (user1->nick, user2->nick); +} + +static int +nick_cmp (struct User *user1, struct User *user2, server *serv) +{ + switch (prefs.userlist_sort) + { + case 0: + return nick_cmp_az_ops (serv, user1, user2); + case 1: + return serv->p_cmp (user1->nick, user2->nick); + case 2: + return -1 * nick_cmp_az_ops (serv, user1, user2); + case 3: + return -1 * serv->p_cmp (user1->nick, user2->nick); + default: + return -1; + } +} + +/* + insert name in appropriate place in linked list. Returns row number or: + -1: duplicate +*/ + +static int +userlist_insertname (session *sess, struct User *newuser) +{ + if (!sess->usertree) + { + sess->usertree = tree_new ((tree_cmp_func *)nick_cmp, sess->server); + sess->usertree_alpha = tree_new ((tree_cmp_func *)nick_cmp_alpha, sess->server); + } + + tree_insert (sess->usertree_alpha, newuser); + return tree_insert (sess->usertree, newuser); +} + +void +userlist_set_away (struct session *sess, char *nick, unsigned int away) +{ + struct User *user; + + user = userlist_find (sess, nick); + if (user) + { + if (user->away != away) + { + user->away = away; + /* rehash GUI */ + fe_userlist_rehash (sess, user); + if (away) + fe_userlist_update (sess, user); + } + } +} + +int +userlist_add_hostname (struct session *sess, char *nick, char *hostname, + char *realname, char *servername, unsigned int away) +{ + struct User *user; + + user = userlist_find (sess, nick); + if (user) + { + if (!user->hostname && hostname) + user->hostname = strdup (hostname); + if (!user->realname && realname) + user->realname = strdup (realname); + if (!user->servername && servername) + user->servername = strdup (servername); + + if (away != 0xff) + { + if (prefs.showhostname_in_userlist || user->away != away) + { + user->away = away; + fe_userlist_rehash (sess, user); + } + user->away = away; + } + + fe_userlist_update (sess, user); + + return 1; + } + return 0; +} + +static int +free_user (struct User *user, gpointer data) +{ + if (user->realname) + free (user->realname); + if (user->hostname) + free (user->hostname); + if (user->servername) + free (user->servername); + free (user); + + return TRUE; +} + +void +userlist_free (session *sess) +{ + tree_foreach (sess->usertree, (tree_traverse_func *)free_user, NULL); + tree_destroy (sess->usertree); + tree_destroy (sess->usertree_alpha); + + sess->usertree = NULL; + sess->usertree_alpha = NULL; + sess->me = NULL; + + sess->ops = 0; + sess->hops = 0; + sess->voices = 0; + sess->total = 0; +} + +void +userlist_clear (session *sess) +{ + fe_userlist_clear (sess); + userlist_free (sess); + fe_userlist_numbers (sess); +} + +static int +find_cmp (const char *name, struct User *user, server *serv) +{ + return serv->p_cmp ((char *)name, user->nick); +} + +struct User * +userlist_find (struct session *sess, char *name) +{ + int pos; + + if (sess->usertree_alpha) + return tree_find (sess->usertree_alpha, name, + (tree_cmp_func *)find_cmp, sess->server, &pos); + + return NULL; +} + +struct User * +userlist_find_global (struct server *serv, char *name) +{ + struct User *user; + session *sess; + GSList *list = sess_list; + while (list) + { + sess = (session *) list->data; + if (sess->server == serv) + { + user = userlist_find (sess, name); + if (user) + return user; + } + list = list->next; + } + return 0; +} + +static void +update_counts (session *sess, struct User *user, char prefix, + int level, int offset) +{ + switch (prefix) + { + case '@': + user->op = level; + sess->ops += offset; + break; + case '%': + user->hop = level; + sess->hops += offset; + break; + case '+': + user->voice = level; + sess->voices += offset; + break; + } +} + +void +userlist_update_mode (session *sess, char *name, char mode, char sign) +{ + int access; + int offset = 0; + int level; + int pos; + char prefix; + struct User *user; + + user = userlist_find (sess, name); + if (!user) + return; + + /* remove from binary trees, before we loose track of it */ + tree_remove (sess->usertree, user, &pos); + tree_remove (sess->usertree_alpha, user, &pos); + + /* which bit number is affected? */ + access = mode_access (sess->server, mode, &prefix); + + if (sign == '+') + { + level = TRUE; + if (!(user->access & (1 << access))) + { + offset = 1; + user->access |= (1 << access); + } + } else + { + level = FALSE; + if (user->access & (1 << access)) + { + offset = -1; + user->access &= ~(1 << access); + } + } + + /* now what is this users highest prefix? e.g. @ for ops */ + user->prefix[0] = get_nick_prefix (sess->server, user->access); + + /* update the various counts using the CHANGED prefix only */ + update_counts (sess, user, prefix, level, offset); + + /* insert it back into its new place */ + tree_insert (sess->usertree_alpha, user); + pos = tree_insert (sess->usertree, user); + + /* let GTK move it too */ + fe_userlist_move (sess, user, pos); + fe_userlist_numbers (sess); +} + +int +userlist_change (struct session *sess, char *oldname, char *newname) +{ + struct User *user = userlist_find (sess, oldname); + int pos; + + if (user) + { + tree_remove (sess->usertree, user, &pos); + tree_remove (sess->usertree_alpha, user, &pos); + + safe_strcpy (user->nick, newname, NICKLEN); + + tree_insert (sess->usertree_alpha, user); + + fe_userlist_move (sess, user, tree_insert (sess->usertree, user)); + fe_userlist_numbers (sess); + + return 1; + } + + return 0; +} + +int +userlist_remove (struct session *sess, char *name) +{ + struct User *user; + int pos; + + user = userlist_find (sess, name); + if (!user) + return FALSE; + + if (user->voice) + sess->voices--; + if (user->op) + sess->ops--; + if (user->hop) + sess->hops--; + sess->total--; + fe_userlist_numbers (sess); + fe_userlist_remove (sess, user); + + if (user == sess->me) + sess->me = NULL; + + tree_remove (sess->usertree, user, &pos); + tree_remove (sess->usertree_alpha, user, &pos); + free_user (user, NULL); + + return TRUE; +} + +void +userlist_add (struct session *sess, char *name, char *hostname) +{ + struct User *user; + int row, prefix_chars; + unsigned int acc; + + acc = nick_access (sess->server, name, &prefix_chars); + + notify_set_online (sess->server, name + prefix_chars); + + user = malloc (sizeof (struct User)); + memset (user, 0, sizeof (struct User)); + + user->access = acc; + + /* assume first char is the highest level nick prefix */ + if (prefix_chars) + user->prefix[0] = name[0]; + + /* add it to our linked list */ + if (hostname) + user->hostname = strdup (hostname); + safe_strcpy (user->nick, name + prefix_chars, NICKLEN); + /* is it me? */ + if (!sess->server->p_cmp (user->nick, sess->server->nick)) + user->me = TRUE; + row = userlist_insertname (sess, user); + + /* duplicate? some broken servers trigger this */ + if (row == -1) + { + if (user->hostname) + free (user->hostname); + free (user); + return; + } + + sess->total++; + + /* most ircds don't support multiple modechars infront of the nickname + for /NAMES - though they should. */ + while (prefix_chars) + { + update_counts (sess, user, name[0], TRUE, 1); + name++; + prefix_chars--; + } + + if (user->me) + sess->me = user; + + fe_userlist_insert (sess, user, row, FALSE); + fe_userlist_numbers (sess); +} + +static int +rehash_cb (struct User *user, session *sess) +{ + fe_userlist_rehash (sess, user); + return TRUE; +} + +void +userlist_rehash (session *sess) +{ + tree_foreach (sess->usertree_alpha, (tree_traverse_func *)rehash_cb, sess); +} + +static int +flat_cb (struct User *user, GSList **list) +{ + *list = g_slist_prepend (*list, user); + return TRUE; +} + +GSList * +userlist_flat_list (session *sess) +{ + GSList *list = NULL; + + tree_foreach (sess->usertree_alpha, (tree_traverse_func *)flat_cb, &list); + return g_slist_reverse (list); +} + +static int +double_cb (struct User *user, GList **list) +{ + *list = g_list_prepend(*list, user); + return TRUE; +} + +GList * +userlist_double_list(session *sess) +{ + GList *list = NULL; + + tree_foreach (sess->usertree_alpha, (tree_traverse_func *)double_cb, &list); + return list; +} diff --git a/src/common/userlist.h b/src/common/userlist.h new file mode 100644 index 00000000..28831acd --- /dev/null +++ b/src/common/userlist.h @@ -0,0 +1,41 @@ +#include <time.h> + +#ifndef XCHAT_USERLIST_H +#define XCHAT_USERLIST_H + +struct User +{ + char nick[NICKLEN]; + char *hostname; + char *realname; + char *servername; + time_t lasttalk; + unsigned int access; /* axs bit field */ + char prefix[2]; /* @ + % */ + unsigned int op:1; + unsigned int hop:1; + unsigned int voice:1; + unsigned int me:1; + unsigned int away:1; + unsigned int selected:1; +}; + +#define USERACCESS_SIZE 12 + +int userlist_add_hostname (session *sess, char *nick, + char *hostname, char *realname, + char *servername, unsigned int away); +void userlist_set_away (session *sess, char *nick, unsigned int away); +struct User *userlist_find (session *sess, char *name); +struct User *userlist_find_global (server *serv, char *name); +void userlist_clear (session *sess); +void userlist_free (session *sess); +void userlist_add (session *sess, char *name, char *hostname); +int userlist_remove (session *sess, char *name); +int userlist_change (session *sess, char *oldname, char *newname); +void userlist_update_mode (session *sess, char *name, char mode, char sign); +GSList *userlist_flat_list (session *sess); +GList *userlist_double_list (session *sess); +void userlist_rehash (session *sess); + +#endif diff --git a/src/common/util.c b/src/common/util.c new file mode 100644 index 00000000..49517ec4 --- /dev/null +++ b/src/common/util.c @@ -0,0 +1,1729 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#define __APPLE_API_STRICT_CONFORMANCE + +#define _FILE_OFFSET_BITS 64 +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#ifdef WIN32 +#include <sys/timeb.h> +#include <process.h> +#else +#include <sys/types.h> +#include <pwd.h> +#include <sys/time.h> +#include <sys/utsname.h> +#endif +#include <fcntl.h> +#include <dirent.h> +#include <errno.h> +#include "xchat.h" +#include "xchatc.h" +#include <glib/gmarkup.h> +#include <ctype.h> +#include "util.h" +#include "../../config.h" + +#define WANTSOCKET +#include "inet.h" + +#if defined (USING_FREEBSD) || defined (__APPLE__) +#include <sys/sysctl.h> +#endif +#ifdef SOCKS +#include <socks.h> +#endif + +#ifndef HAVE_SNPRINTF +#define snprintf g_snprintf +#endif + +#ifdef USE_DEBUG + +#undef free +#undef malloc +#undef realloc +#undef strdup + +int current_mem_usage; + +struct mem_block +{ + char *file; + void *buf; + int size; + int line; + int total; + struct mem_block *next; +}; + +struct mem_block *mroot = NULL; + +void * +xchat_malloc (int size, char *file, int line) +{ + void *ret; + struct mem_block *new; + + current_mem_usage += size; + ret = malloc (size); + if (!ret) + { + printf ("Out of memory! (%d)\n", current_mem_usage); + exit (255); + } + + new = malloc (sizeof (struct mem_block)); + new->buf = ret; + new->size = size; + new->next = mroot; + new->line = line; + new->file = strdup (file); + mroot = new; + + printf ("%s:%d Malloc'ed %d bytes, now \033[35m%d\033[m\n", file, line, + size, current_mem_usage); + + return ret; +} + +void * +xchat_realloc (char *old, int len, char *file, int line) +{ + char *ret; + + ret = xchat_malloc (len, file, line); + if (ret) + { + strcpy (ret, old); + xchat_dfree (old, file, line); + } + return ret; +} + +void * +xchat_strdup (char *str, char *file, int line) +{ + void *ret; + struct mem_block *new; + int size; + + size = strlen (str) + 1; + current_mem_usage += size; + ret = malloc (size); + if (!ret) + { + printf ("Out of memory! (%d)\n", current_mem_usage); + exit (255); + } + strcpy (ret, str); + + new = malloc (sizeof (struct mem_block)); + new->buf = ret; + new->size = size; + new->next = mroot; + new->line = line; + new->file = strdup (file); + mroot = new; + + printf ("%s:%d strdup (\"%-.40s\") size: %d, total: \033[35m%d\033[m\n", + file, line, str, size, current_mem_usage); + + return ret; +} + +void +xchat_mem_list (void) +{ + struct mem_block *cur, *p; + GSList *totals = 0; + GSList *list; + + cur = mroot; + while (cur) + { + list = totals; + while (list) + { + p = list->data; + if (p->line == cur->line && + strcmp (p->file, cur->file) == 0) + { + p->total += p->size; + break; + } + list = list->next; + } + if (!list) + { + cur->total = cur->size; + totals = g_slist_prepend (totals, cur); + } + cur = cur->next; + } + + fprintf (stderr, "file line size num total\n"); + list = totals; + while (list) + { + cur = list->data; + fprintf (stderr, "%-15.15s %6d %6d %6d %6d\n", cur->file, cur->line, + cur->size, cur->total/cur->size, cur->total); + list = list->next; + } +} + +void +xchat_dfree (void *buf, char *file, int line) +{ + struct mem_block *cur, *last; + + if (buf == NULL) + { + printf ("%s:%d \033[33mTried to free NULL\033[m\n", file, line); + return; + } + + last = NULL; + cur = mroot; + while (cur) + { + if (buf == cur->buf) + break; + last = cur; + cur = cur->next; + } + if (cur == NULL) + { + printf ("%s:%d \033[31mTried to free unknown block %lx!\033[m\n", + file, line, (unsigned long) buf); + /* abort(); */ + free (buf); + return; + } + current_mem_usage -= cur->size; + printf ("%s:%d Free'ed %d bytes, usage now \033[35m%d\033[m\n", + file, line, cur->size, current_mem_usage); + if (last) + last->next = cur->next; + else + mroot = cur->next; + free (cur->file); + free (cur); +} + +#define malloc(n) xchat_malloc(n, __FILE__, __LINE__) +#define realloc(n, m) xchat_realloc(n, m, __FILE__, __LINE__) +#define free(n) xchat_dfree(n, __FILE__, __LINE__) +#define strdup(n) xchat_strdup(n, __FILE__, __LINE__) + +#endif /* MEMORY_DEBUG */ + +char * +file_part (char *file) +{ + char *filepart = file; + if (!file) + return ""; + while (1) + { + switch (*file) + { + case 0: + return (filepart); + case '/': +#ifdef WIN32 + case '\\': +#endif + filepart = file + 1; + break; + } + file++; + } +} + +void +path_part (char *file, char *path, int pathlen) +{ + unsigned char t; + char *filepart = file_part (file); + t = *filepart; + *filepart = 0; + safe_strcpy (path, file, pathlen); + *filepart = t; +} + +char * /* like strstr(), but nocase */ +nocasestrstr (const char *s, const char *wanted) +{ + register const int len = strlen (wanted); + + if (len == 0) + return (char *)s; + while (rfc_tolower(*s) != rfc_tolower(*wanted) || strncasecmp (s, wanted, len)) + if (*s++ == '\0') + return (char *)NULL; + return (char *)s; +} + +char * +errorstring (int err) +{ + switch (err) + { + case -1: + return ""; + case 0: + return _("Remote host closed socket"); +#ifndef WIN32 + } +#else + case WSAECONNREFUSED: + return _("Connection refused"); + case WSAENETUNREACH: + case WSAEHOSTUNREACH: + return _("No route to host"); + case WSAETIMEDOUT: + return _("Connection timed out"); + case WSAEADDRNOTAVAIL: + return _("Cannot assign that address"); + case WSAECONNRESET: + return _("Connection reset by peer"); + } + + /* can't use strerror() on Winsock errors! */ + if (err >= WSABASEERR) + { + static char tbuf[384]; + OSVERSIONINFO osvi; + + osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); + GetVersionEx (&osvi); + + /* FormatMessage works on WSA*** errors starting from Win2000 */ + if (osvi.dwMajorVersion >= 5) + { + if (FormatMessageA (FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_MAX_WIDTH_MASK, + NULL, err, + MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), + tbuf, sizeof (tbuf), NULL)) + { + int len; + char *utf; + + tbuf[sizeof (tbuf) - 1] = 0; + len = strlen (tbuf); + if (len >= 2) + tbuf[len - 2] = 0; /* remove the cr-lf */ + + /* now convert to utf8 */ + utf = g_locale_to_utf8 (tbuf, -1, 0, 0, 0); + if (utf) + { + safe_strcpy (tbuf, utf, sizeof (tbuf)); + g_free (utf); + return tbuf; + } + } + } /* ! if (osvi.dwMajorVersion >= 5) */ + + /* fallback to error number */ + sprintf (tbuf, "%s %d", _("Error"), err); + return tbuf; + } /* ! if (err >= WSABASEERR) */ +#endif /* ! WIN32 */ + + return strerror (err); +} + +int +waitline (int sok, char *buf, int bufsize, int use_recv) +{ + int i = 0; + + while (1) + { + if (use_recv) + { + if (recv (sok, &buf[i], 1, 0) < 1) + return -1; + } else + { + if (read (sok, &buf[i], 1) < 1) + return -1; + } + if (buf[i] == '\n' || bufsize == i + 1) + { + buf[i] = 0; + return i; + } + i++; + } +} + +/* checks for "~" in a file and expands */ + +char * +expand_homedir (char *file) +{ +#ifndef WIN32 + char *ret, *user; + struct passwd *pw; + + if (*file == '~') + { + if (file[1] != '\0' && file[1] != '/') + { + user = strdup(file); + if (strchr(user,'/') != NULL) + *(strchr(user,'/')) = '\0'; + if ((pw = getpwnam(user + 1)) == NULL) + { + free(user); + return strdup(file); + } + free(user); + user = strchr(file, '/') != NULL ? strchr(file,'/') : file; + ret = malloc(strlen(user) + strlen(pw->pw_dir) + 1); + strcpy(ret, pw->pw_dir); + strcat(ret, user); + } + else + { + ret = malloc (strlen (file) + strlen (g_get_home_dir ()) + 1); + sprintf (ret, "%s%s", g_get_home_dir (), file + 1); + } + return ret; + } +#endif + return strdup (file); +} + +gchar * +strip_color (const char *text, int len, int flags) +{ + char *new_str; + + if (len == -1) + len = strlen (text); + + new_str = g_malloc (len + 2); + strip_color2 (text, len, new_str, flags); + + if (flags & STRIP_ESCMARKUP) + { + char *esc = g_markup_escape_text (new_str, -1); + g_free (new_str); + return esc; + } + + return new_str; +} + +/* CL: strip_color2 strips src and writes the output at dst; pass the same pointer + in both arguments to strip in place. */ +int +strip_color2 (const char *src, int len, char *dst, int flags) +{ + int rcol = 0, bgcol = 0; + char *start = dst; + + if (len == -1) len = strlen (src); + while (len-- > 0) + { + if (rcol > 0 && (isdigit ((unsigned char)*src) || + (*src == ',' && isdigit ((unsigned char)src[1]) && !bgcol))) + { + if (src[1] != ',') rcol--; + if (*src == ',') + { + rcol = 2; + bgcol = 1; + } + } else + { + rcol = bgcol = 0; + switch (*src) + { + case '\003': /*ATTR_COLOR: */ + if (!(flags & STRIP_COLOR)) goto pass_char; + rcol = 2; + break; + case HIDDEN_CHAR: /* CL: invisible text (for event formats only) */ /* this takes care of the topic */ + if (!(flags & STRIP_HIDDEN)) goto pass_char; + break; + case '\007': /*ATTR_BEEP: */ + case '\017': /*ATTR_RESET: */ + case '\026': /*ATTR_REVERSE: */ + case '\002': /*ATTR_BOLD: */ + case '\037': /*ATTR_UNDERLINE: */ + case '\035': /*ATTR_ITALICS: */ + if (!(flags & STRIP_ATTRIB)) goto pass_char; + break; + default: + pass_char: + *dst++ = *src; + } + } + src++; + } + *dst = 0; + + return (int) (dst - start); +} + +int +strip_hidden_attribute (char *src, char *dst) +{ + int len = 0; + while (*src != '\000') + { + if (*src != HIDDEN_CHAR) + { + *dst++ = *src; + len++; + } + src++; + } + return len; +} + +#if defined (USING_LINUX) || defined (USING_FREEBSD) || defined (__APPLE__) + +static void +get_cpu_info (double *mhz, int *cpus) +{ + +#ifdef USING_LINUX + + char buf[256]; + int fh; + + *mhz = 0; + *cpus = 0; + + fh = open ("/proc/cpuinfo", O_RDONLY); /* linux 2.2+ only */ + if (fh == -1) + { + *cpus = 1; + return; + } + + while (1) + { + if (waitline (fh, buf, sizeof buf, FALSE) < 0) + break; + if (!strncmp (buf, "cycle frequency [Hz]\t:", 22)) /* alpha */ + { + *mhz = atoi (buf + 23) / 1000000; + } else if (!strncmp (buf, "cpu MHz\t\t:", 10)) /* i386 */ + { + *mhz = atof (buf + 11) + 0.5; + } else if (!strncmp (buf, "clock\t\t:", 8)) /* PPC */ + { + *mhz = atoi (buf + 9); + } else if (!strncmp (buf, "processor\t", 10)) + { + (*cpus)++; + } + } + close (fh); + if (!*cpus) + *cpus = 1; + +#endif +#ifdef USING_FREEBSD + + int mib[2], ncpu; + u_long freq; + size_t len; + + freq = 0; + *mhz = 0; + *cpus = 0; + + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + + len = sizeof(ncpu); + sysctl(mib, 2, &ncpu, &len, NULL, 0); + + len = sizeof(freq); + sysctlbyname("machdep.tsc_freq", &freq, &len, NULL, 0); + + *cpus = ncpu; + *mhz = (freq / 1000000); + +#endif +#ifdef __APPLE__ + + int mib[2], ncpu; + unsigned long long freq; + size_t len; + + freq = 0; + *mhz = 0; + *cpus = 0; + + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + + len = sizeof(ncpu); + sysctl(mib, 2, &ncpu, &len, NULL, 0); + + len = sizeof(freq); + sysctlbyname("hw.cpufrequency", &freq, &len, NULL, 0); + + *cpus = ncpu; + *mhz = (freq / 1000000); + +#endif + +} +#endif + +#ifdef WIN32 + +static int +get_mhz (void) +{ + HKEY hKey; + int result, data, dataSize; + + if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, "Hardware\\Description\\System\\" + "CentralProcessor\\0", 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) + { + dataSize = sizeof (data); + result = RegQueryValueEx (hKey, "~MHz", 0, 0, (LPBYTE)&data, &dataSize); + RegCloseKey (hKey); + if (result == ERROR_SUCCESS) + return data; + } + return 0; /* fails on Win9x */ +} + +char * +get_cpu_str (void) +{ + static char verbuf[64]; + OSVERSIONINFO osvi; + SYSTEM_INFO si; + double mhz; + + osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); + GetVersionEx (&osvi); + GetSystemInfo (&si); + + mhz = get_mhz (); + if (mhz) + { + double cpuspeed = ( mhz > 1000 ) ? mhz / 1000 : mhz; + const char *cpuspeedstr = ( mhz > 1000 ) ? "GHz" : "MHz"; + sprintf (verbuf, "Windows %ld.%ld [i%d86/%.2f%s]", + osvi.dwMajorVersion, osvi.dwMinorVersion, si.wProcessorLevel, + cpuspeed, cpuspeedstr); + } else + sprintf (verbuf, "Windows %ld.%ld [i%d86]", + osvi.dwMajorVersion, osvi.dwMinorVersion, si.wProcessorLevel); + + return verbuf; +} + +#else + +char * +get_cpu_str (void) +{ +#if defined (USING_LINUX) || defined (USING_FREEBSD) || defined (__APPLE__) + double mhz; +#endif + int cpus = 1; + struct utsname un; + static char *buf = NULL; + + if (buf) + return buf; + + buf = malloc (128); + + uname (&un); + +#if defined (USING_LINUX) || defined (USING_FREEBSD) || defined (__APPLE__) + get_cpu_info (&mhz, &cpus); + if (mhz) + { + double cpuspeed = ( mhz > 1000 ) ? mhz / 1000 : mhz; + const char *cpuspeedstr = ( mhz > 1000 ) ? "GHz" : "MHz"; + snprintf (buf, 128, + (cpus == 1) ? "%s %s [%s/%.2f%s]" : "%s %s [%s/%.2f%s/SMP]", + un.sysname, un.release, un.machine, + cpuspeed, cpuspeedstr); + } else +#endif + snprintf (buf, 128, + (cpus == 1) ? "%s %s [%s]" : "%s %s [%s/SMP]", + un.sysname, un.release, un.machine); + + return buf; +} + +#endif + +int +buf_get_line (char *ibuf, char **buf, int *position, int len) +{ + int pos = *position, spos = pos; + + if (pos == len) + return 0; + + while (ibuf[pos++] != '\n') + { + if (pos == len) + return 0; + } + pos--; + ibuf[pos] = 0; + *buf = &ibuf[spos]; + pos++; + *position = pos; + return 1; +} + +int match(const char *mask, const char *string) +{ + register const char *m = mask, *s = string; + register char ch; + const char *bm, *bs; /* Will be reg anyway on a decent CPU/compiler */ + + /* Process the "head" of the mask, if any */ + while ((ch = *m++) && (ch != '*')) + switch (ch) + { + case '\\': + if (*m == '?' || *m == '*') + ch = *m++; + default: + if (rfc_tolower(*s) != rfc_tolower(ch)) + return 0; + case '?': + if (!*s++) + return 0; + }; + if (!ch) + return !(*s); + + /* We got a star: quickly find if/where we match the next char */ +got_star: + bm = m; /* Next try rollback here */ + while ((ch = *m++)) + switch (ch) + { + case '?': + if (!*s++) + return 0; + case '*': + bm = m; + continue; /* while */ + case '\\': + if (*m == '?' || *m == '*') + ch = *m++; + default: + goto break_while; /* C is structured ? */ + }; +break_while: + if (!ch) + return 1; /* mask ends with '*', we got it */ + ch = rfc_tolower(ch); + while (rfc_tolower(*s++) != ch) + if (!*s) + return 0; + bs = s; /* Next try start from here */ + + /* Check the rest of the "chunk" */ + while ((ch = *m++)) + { + switch (ch) + { + case '*': + goto got_star; + case '\\': + if (*m == '?' || *m == '*') + ch = *m++; + default: + if (rfc_tolower(*s) != rfc_tolower(ch)) + { + if (!*s) + return 0; + m = bm; + s = bs; + goto got_star; + }; + case '?': + if (!*s++) + return 0; + }; + }; + if (*s) + { + m = bm; + s = bs; + goto got_star; + }; + return 1; +} + +void +for_files (char *dirname, char *mask, void callback (char *file)) +{ + DIR *dir; + struct dirent *ent; + char *buf; + + dir = opendir (dirname); + if (dir) + { + while ((ent = readdir (dir))) + { + if (strcmp (ent->d_name, ".") && strcmp (ent->d_name, "..")) + { + if (match (mask, ent->d_name)) + { + buf = malloc (strlen (dirname) + strlen (ent->d_name) + 2); + sprintf (buf, "%s/%s", dirname, ent->d_name); + callback (buf); + free (buf); + } + } + } + closedir (dir); + } +} + +/*void +tolowerStr (char *str) +{ + while (*str) + { + *str = rfc_tolower (*str); + str++; + } +}*/ + +typedef struct +{ + char *code, *country; +} domain_t; + +static int +country_compare (const void *a, const void *b) +{ + return strcasecmp (a, ((domain_t *)b)->code); +} + +static const domain_t domain[] = +{ + {"AC", N_("Ascension Island") }, + {"AD", N_("Andorra") }, + {"AE", N_("United Arab Emirates") }, + {"AF", N_("Afghanistan") }, + {"AG", N_("Antigua and Barbuda") }, + {"AI", N_("Anguilla") }, + {"AL", N_("Albania") }, + {"AM", N_("Armenia") }, + {"AN", N_("Netherlands Antilles") }, + {"AO", N_("Angola") }, + {"AQ", N_("Antarctica") }, + {"AR", N_("Argentina") }, + {"ARPA", N_("Reverse DNS") }, + {"AS", N_("American Samoa") }, + {"AT", N_("Austria") }, + {"ATO", N_("Nato Fiel") }, + {"AU", N_("Australia") }, + {"AW", N_("Aruba") }, + {"AX", N_("Aland Islands") }, + {"AZ", N_("Azerbaijan") }, + {"BA", N_("Bosnia and Herzegovina") }, + {"BB", N_("Barbados") }, + {"BD", N_("Bangladesh") }, + {"BE", N_("Belgium") }, + {"BF", N_("Burkina Faso") }, + {"BG", N_("Bulgaria") }, + {"BH", N_("Bahrain") }, + {"BI", N_("Burundi") }, + {"BIZ", N_("Businesses"), }, + {"BJ", N_("Benin") }, + {"BM", N_("Bermuda") }, + {"BN", N_("Brunei Darussalam") }, + {"BO", N_("Bolivia") }, + {"BR", N_("Brazil") }, + {"BS", N_("Bahamas") }, + {"BT", N_("Bhutan") }, + {"BV", N_("Bouvet Island") }, + {"BW", N_("Botswana") }, + {"BY", N_("Belarus") }, + {"BZ", N_("Belize") }, + {"CA", N_("Canada") }, + {"CC", N_("Cocos Islands") }, + {"CD", N_("Democratic Republic of Congo") }, + {"CF", N_("Central African Republic") }, + {"CG", N_("Congo") }, + {"CH", N_("Switzerland") }, + {"CI", N_("Cote d'Ivoire") }, + {"CK", N_("Cook Islands") }, + {"CL", N_("Chile") }, + {"CM", N_("Cameroon") }, + {"CN", N_("China") }, + {"CO", N_("Colombia") }, + {"COM", N_("Internic Commercial") }, + {"CR", N_("Costa Rica") }, + {"CS", N_("Serbia and Montenegro") }, + {"CU", N_("Cuba") }, + {"CV", N_("Cape Verde") }, + {"CX", N_("Christmas Island") }, + {"CY", N_("Cyprus") }, + {"CZ", N_("Czech Republic") }, + {"DE", N_("Germany") }, + {"DJ", N_("Djibouti") }, + {"DK", N_("Denmark") }, + {"DM", N_("Dominica") }, + {"DO", N_("Dominican Republic") }, + {"DZ", N_("Algeria") }, + {"EC", N_("Ecuador") }, + {"EDU", N_("Educational Institution") }, + {"EE", N_("Estonia") }, + {"EG", N_("Egypt") }, + {"EH", N_("Western Sahara") }, + {"ER", N_("Eritrea") }, + {"ES", N_("Spain") }, + {"ET", N_("Ethiopia") }, + {"EU", N_("European Union") }, + {"FI", N_("Finland") }, + {"FJ", N_("Fiji") }, + {"FK", N_("Falkland Islands") }, + {"FM", N_("Micronesia") }, + {"FO", N_("Faroe Islands") }, + {"FR", N_("France") }, + {"GA", N_("Gabon") }, + {"GB", N_("Great Britain") }, + {"GD", N_("Grenada") }, + {"GE", N_("Georgia") }, + {"GF", N_("French Guiana") }, + {"GG", N_("British Channel Isles") }, + {"GH", N_("Ghana") }, + {"GI", N_("Gibraltar") }, + {"GL", N_("Greenland") }, + {"GM", N_("Gambia") }, + {"GN", N_("Guinea") }, + {"GOV", N_("Government") }, + {"GP", N_("Guadeloupe") }, + {"GQ", N_("Equatorial Guinea") }, + {"GR", N_("Greece") }, + {"GS", N_("S. Georgia and S. Sandwich Isles") }, + {"GT", N_("Guatemala") }, + {"GU", N_("Guam") }, + {"GW", N_("Guinea-Bissau") }, + {"GY", N_("Guyana") }, + {"HK", N_("Hong Kong") }, + {"HM", N_("Heard and McDonald Islands") }, + {"HN", N_("Honduras") }, + {"HR", N_("Croatia") }, + {"HT", N_("Haiti") }, + {"HU", N_("Hungary") }, + {"ID", N_("Indonesia") }, + {"IE", N_("Ireland") }, + {"IL", N_("Israel") }, + {"IM", N_("Isle of Man") }, + {"IN", N_("India") }, + {"INFO", N_("Informational") }, + {"INT", N_("International") }, + {"IO", N_("British Indian Ocean Territory") }, + {"IQ", N_("Iraq") }, + {"IR", N_("Iran") }, + {"IS", N_("Iceland") }, + {"IT", N_("Italy") }, + {"JE", N_("Jersey") }, + {"JM", N_("Jamaica") }, + {"JO", N_("Jordan") }, + {"JP", N_("Japan") }, + {"KE", N_("Kenya") }, + {"KG", N_("Kyrgyzstan") }, + {"KH", N_("Cambodia") }, + {"KI", N_("Kiribati") }, + {"KM", N_("Comoros") }, + {"KN", N_("St. Kitts and Nevis") }, + {"KP", N_("North Korea") }, + {"KR", N_("South Korea") }, + {"KW", N_("Kuwait") }, + {"KY", N_("Cayman Islands") }, + {"KZ", N_("Kazakhstan") }, + {"LA", N_("Laos") }, + {"LB", N_("Lebanon") }, + {"LC", N_("Saint Lucia") }, + {"LI", N_("Liechtenstein") }, + {"LK", N_("Sri Lanka") }, + {"LR", N_("Liberia") }, + {"LS", N_("Lesotho") }, + {"LT", N_("Lithuania") }, + {"LU", N_("Luxembourg") }, + {"LV", N_("Latvia") }, + {"LY", N_("Libya") }, + {"MA", N_("Morocco") }, + {"MC", N_("Monaco") }, + {"MD", N_("Moldova") }, + {"MED", N_("United States Medical") }, + {"MG", N_("Madagascar") }, + {"MH", N_("Marshall Islands") }, + {"MIL", N_("Military") }, + {"MK", N_("Macedonia") }, + {"ML", N_("Mali") }, + {"MM", N_("Myanmar") }, + {"MN", N_("Mongolia") }, + {"MO", N_("Macau") }, + {"MP", N_("Northern Mariana Islands") }, + {"MQ", N_("Martinique") }, + {"MR", N_("Mauritania") }, + {"MS", N_("Montserrat") }, + {"MT", N_("Malta") }, + {"MU", N_("Mauritius") }, + {"MV", N_("Maldives") }, + {"MW", N_("Malawi") }, + {"MX", N_("Mexico") }, + {"MY", N_("Malaysia") }, + {"MZ", N_("Mozambique") }, + {"NA", N_("Namibia") }, + {"NC", N_("New Caledonia") }, + {"NE", N_("Niger") }, + {"NET", N_("Internic Network") }, + {"NF", N_("Norfolk Island") }, + {"NG", N_("Nigeria") }, + {"NI", N_("Nicaragua") }, + {"NL", N_("Netherlands") }, + {"NO", N_("Norway") }, + {"NP", N_("Nepal") }, + {"NR", N_("Nauru") }, + {"NU", N_("Niue") }, + {"NZ", N_("New Zealand") }, + {"OM", N_("Oman") }, + {"ORG", N_("Internic Non-Profit Organization") }, + {"PA", N_("Panama") }, + {"PE", N_("Peru") }, + {"PF", N_("French Polynesia") }, + {"PG", N_("Papua New Guinea") }, + {"PH", N_("Philippines") }, + {"PK", N_("Pakistan") }, + {"PL", N_("Poland") }, + {"PM", N_("St. Pierre and Miquelon") }, + {"PN", N_("Pitcairn") }, + {"PR", N_("Puerto Rico") }, + {"PS", N_("Palestinian Territory") }, + {"PT", N_("Portugal") }, + {"PW", N_("Palau") }, + {"PY", N_("Paraguay") }, + {"QA", N_("Qatar") }, + {"RE", N_("Reunion") }, + {"RO", N_("Romania") }, + {"RPA", N_("Old School ARPAnet") }, + {"RU", N_("Russian Federation") }, + {"RW", N_("Rwanda") }, + {"SA", N_("Saudi Arabia") }, + {"SB", N_("Solomon Islands") }, + {"SC", N_("Seychelles") }, + {"SD", N_("Sudan") }, + {"SE", N_("Sweden") }, + {"SG", N_("Singapore") }, + {"SH", N_("St. Helena") }, + {"SI", N_("Slovenia") }, + {"SJ", N_("Svalbard and Jan Mayen Islands") }, + {"SK", N_("Slovak Republic") }, + {"SL", N_("Sierra Leone") }, + {"SM", N_("San Marino") }, + {"SN", N_("Senegal") }, + {"SO", N_("Somalia") }, + {"SR", N_("Suriname") }, + {"ST", N_("Sao Tome and Principe") }, + {"SU", N_("Former USSR") }, + {"SV", N_("El Salvador") }, + {"SY", N_("Syria") }, + {"SZ", N_("Swaziland") }, + {"TC", N_("Turks and Caicos Islands") }, + {"TD", N_("Chad") }, + {"TF", N_("French Southern Territories") }, + {"TG", N_("Togo") }, + {"TH", N_("Thailand") }, + {"TJ", N_("Tajikistan") }, + {"TK", N_("Tokelau") }, + {"TL", N_("East Timor") }, + {"TM", N_("Turkmenistan") }, + {"TN", N_("Tunisia") }, + {"TO", N_("Tonga") }, + {"TP", N_("East Timor") }, + {"TR", N_("Turkey") }, + {"TT", N_("Trinidad and Tobago") }, + {"TV", N_("Tuvalu") }, + {"TW", N_("Taiwan") }, + {"TZ", N_("Tanzania") }, + {"UA", N_("Ukraine") }, + {"UG", N_("Uganda") }, + {"UK", N_("United Kingdom") }, + {"US", N_("United States of America") }, + {"UY", N_("Uruguay") }, + {"UZ", N_("Uzbekistan") }, + {"VA", N_("Vatican City State") }, + {"VC", N_("St. Vincent and the Grenadines") }, + {"VE", N_("Venezuela") }, + {"VG", N_("British Virgin Islands") }, + {"VI", N_("US Virgin Islands") }, + {"VN", N_("Vietnam") }, + {"VU", N_("Vanuatu") }, + {"WF", N_("Wallis and Futuna Islands") }, + {"WS", N_("Samoa") }, + {"YE", N_("Yemen") }, + {"YT", N_("Mayotte") }, + {"YU", N_("Yugoslavia") }, + {"ZA", N_("South Africa") }, + {"ZM", N_("Zambia") }, + {"ZW", N_("Zimbabwe") }, +}; + +char * +country (char *hostname) +{ + char *p; + domain_t *dom; + + if (!hostname || !*hostname || isdigit ((unsigned char) hostname[strlen (hostname) - 1])) + return _("Unknown"); + if ((p = strrchr (hostname, '.'))) + p++; + else + p = hostname; + + dom = bsearch (p, domain, sizeof (domain) / sizeof (domain_t), + sizeof (domain_t), country_compare); + + if (!dom) + return _("Unknown"); + + return _(dom->country); +} + +void +country_search (char *pattern, void *ud, void (*print)(void *, char *, ...)) +{ + const domain_t *dom; + int i; + + for (i = 0; i < sizeof (domain) / sizeof (domain_t); i++) + { + dom = &domain[i]; + if (match (pattern, dom->country) || match (pattern, _(dom->country))) + { + print (ud, "%s = %s\n", dom->code, _(dom->country)); + } + } +} + +/* I think gnome1.0.x isn't necessarily linked against popt, ah well! */ +/* !!! For now use this inlined function, or it would break fe-text building */ +/* .... will find a better solution later. */ +/*#ifndef USE_GNOME*/ + +/* this is taken from gnome-libs 1.2.4 */ +#define POPT_ARGV_ARRAY_GROW_DELTA 5 + +int my_poptParseArgvString(const char * s, int * argcPtr, char *** argvPtr) { + char * buf, * bufStart, * dst; + const char * src; + char quote = '\0'; + int argvAlloced = POPT_ARGV_ARRAY_GROW_DELTA; + char ** argv = malloc(sizeof(*argv) * argvAlloced); + const char ** argv2; + int argc = 0; + int i, buflen; + + buflen = strlen(s) + 1; +/* bufStart = buf = alloca(buflen);*/ + bufStart = buf = malloc (buflen); + memset(buf, '\0', buflen); + + src = s; + argv[argc] = buf; + + while (*src) { + if (quote == *src) { + quote = '\0'; + } else if (quote) { + if (*src == '\\') { + src++; + if (!*src) { + free(argv); + free(bufStart); + return 1; + } + if (*src != quote) *buf++ = '\\'; + } + *buf++ = *src; + /*} else if (isspace((unsigned char) *src)) {*/ + } else if (*src == ' ') { + if (*argv[argc]) { + buf++, argc++; + if (argc == argvAlloced) { + argvAlloced += POPT_ARGV_ARRAY_GROW_DELTA; + argv = realloc(argv, sizeof(*argv) * argvAlloced); + } + argv[argc] = buf; + } + } else switch (*src) { + case '"': + case '\'': + quote = *src; + break; + case '\\': + src++; + if (!*src) { + free(argv); + free(bufStart); + return 1; + } + /* fallthrough */ + default: + *buf++ = *src; + } + + src++; + } + + if (strlen(argv[argc])) { + argc++, buf++; + } + + dst = malloc((argc + 1) * sizeof(*argv) + (buf - bufStart)); + argv2 = (void *) dst; + dst += (argc + 1) * sizeof(*argv); + memcpy((void *)argv2, argv, argc * sizeof(*argv)); + argv2[argc] = NULL; + memcpy(dst, bufStart, buf - bufStart); + + for (i = 0; i < argc; i++) { + argv2[i] = dst + (argv[i] - bufStart); + } + + free(argv); + + *argvPtr = (char **)argv2; /* XXX don't change the API */ + *argcPtr = argc; + + free (bufStart); + + return 0; +} + +int +util_exec (const char *cmd) +{ + int pid; + char **argv; + int argc; + int fd; + + if (my_poptParseArgvString (cmd, &argc, &argv) != 0) + return -1; + +#ifndef WIN32 + pid = fork (); + if (pid == -1) + return -1; + if (pid == 0) + { + /* Now close all open file descriptors except stdin, stdout and stderr */ + for (fd = 3; fd < 1024; fd++) close(fd); + execvp (argv[0], argv); + _exit (0); + } else + { + free (argv); + return pid; + } +#else + spawnvp (_P_DETACH, argv[0], argv); + free (argv); + return 0; +#endif +} + +int +util_execv (char * const argv[]) +{ + int pid, fd; + +#ifndef WIN32 + pid = fork (); + if (pid == -1) + return -1; + if (pid == 0) + { + /* Now close all open file descriptors except stdin, stdout and stderr */ + for (fd = 3; fd < 1024; fd++) close(fd); + execv (argv[0], argv); + _exit (0); + } else + { + return pid; + } +#else + spawnv (_P_DETACH, argv[0], argv); + return 0; +#endif +} + +unsigned long +make_ping_time (void) +{ +#ifndef WIN32 + struct timeval timev; + gettimeofday (&timev, 0); +#else + GTimeVal timev; + g_get_current_time (&timev); +#endif + return (timev.tv_sec - 50000) * 1000000 + timev.tv_usec; +} + + +/************************************************************************ + * This technique was borrowed in part from the source code to + * ircd-hybrid-5.3 to implement case-insensitive string matches which + * are fully compliant with Section 2.2 of RFC 1459, the copyright + * of that code being (C) 1990 Jarkko Oikarinen and under the GPL. + * + * A special thanks goes to Mr. Okarinen for being the one person who + * seems to have ever noticed this section in the original RFC and + * written code for it. Shame on all the rest of you (myself included). + * + * --+ Dagmar d'Surreal + */ + +int +rfc_casecmp (const char *s1, const char *s2) +{ + register unsigned char *str1 = (unsigned char *) s1; + register unsigned char *str2 = (unsigned char *) s2; + register int res; + + while ((res = rfc_tolower (*str1) - rfc_tolower (*str2)) == 0) + { + if (*str1 == '\0') + return 0; + str1++; + str2++; + } + return (res); +} + +int +rfc_ncasecmp (char *str1, char *str2, int n) +{ + register unsigned char *s1 = (unsigned char *) str1; + register unsigned char *s2 = (unsigned char *) str2; + register int res; + + while ((res = rfc_tolower (*s1) - rfc_tolower (*s2)) == 0) + { + s1++; + s2++; + n--; + if (n == 0 || (*s1 == '\0' && *s2 == '\0')) + return 0; + } + return (res); +} + +const unsigned char rfc_tolowertab[] = + { 0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, + 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x1f, + ' ', '!', '"', '#', '$', '%', '&', 0x27, '(', ')', + '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + ':', ';', '<', '=', '>', '?', + '@', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', + 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', + 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', + '_', + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', + 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', + 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', + 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, + 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, + 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, + 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, + 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, + 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, + 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff +}; + +/*static unsigned char touppertab[] = + { 0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, + 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x1f, + ' ', '!', '"', '#', '$', '%', '&', 0x27, '(', ')', + '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', + 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', + 0x5f, + '`', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', + 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', + 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, + 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, + 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, + 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, + 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, + 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, + 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff +};*/ + +/*static int +rename_utf8 (char *oldname, char *newname) +{ + int sav, res; + char *fso, *fsn; + + fso = xchat_filename_from_utf8 (oldname, -1, 0, 0, 0); + if (!fso) + return FALSE; + fsn = xchat_filename_from_utf8 (newname, -1, 0, 0, 0); + if (!fsn) + { + g_free (fso); + return FALSE; + } + + res = rename (fso, fsn); + sav = errno; + g_free (fso); + g_free (fsn); + errno = sav; + return res; +} + +static int +unlink_utf8 (char *fname) +{ + int res; + char *fs; + + fs = xchat_filename_from_utf8 (fname, -1, 0, 0, 0); + if (!fs) + return FALSE; + + res = unlink (fs); + g_free (fs); + return res; +}*/ + +static gboolean +file_exists_utf8 (char *fname) +{ + int res; + char *fs; + + fs = xchat_filename_from_utf8 (fname, -1, 0, 0, 0); + if (!fs) + return FALSE; + + res = access (fs, F_OK); + g_free (fs); + if (res == 0) + return TRUE; + return FALSE; +} + +static gboolean +copy_file (char *dl_src, char *dl_dest, int permissions) /* FS encoding */ +{ + int tmp_src, tmp_dest; + gboolean ok = FALSE; + char dl_tmp[4096]; + int return_tmp, return_tmp2; + + if ((tmp_src = open (dl_src, O_RDONLY | OFLAGS)) == -1) + { + fprintf (stderr, "Unable to open() file '%s' (%s) !", dl_src, + strerror (errno)); + return FALSE; + } + + if ((tmp_dest = + open (dl_dest, O_WRONLY | O_CREAT | O_TRUNC | OFLAGS, permissions)) < 0) + { + close (tmp_src); + fprintf (stderr, "Unable to create file '%s' (%s) !", dl_src, + strerror (errno)); + return FALSE; + } + + for (;;) + { + return_tmp = read (tmp_src, dl_tmp, sizeof (dl_tmp)); + + if (!return_tmp) + { + ok = TRUE; + break; + } + + if (return_tmp < 0) + { + fprintf (stderr, "download_move_to_completed_dir(): " + "error reading while moving file to save directory (%s)", + strerror (errno)); + break; + } + + return_tmp2 = write (tmp_dest, dl_tmp, return_tmp); + + if (return_tmp2 < 0) + { + fprintf (stderr, "download_move_to_completed_dir(): " + "error writing while moving file to save directory (%s)", + strerror (errno)); + break; + } + + if (return_tmp < sizeof (dl_tmp)) + { + ok = TRUE; + break; + } + } + + close (tmp_dest); + close (tmp_src); + return ok; +} + +/* Takes care of moving a file from a temporary download location to a completed location. Now in UTF-8. */ +void +move_file_utf8 (char *src_dir, char *dst_dir, char *fname, int dccpermissions) +{ + char src[4096]; + char dst[4096]; + int res, i; + char *src_fs; /* FileSystem encoding */ + char *dst_fs; + + /* if dcc_dir and dcc_completed_dir are the same then we are done */ + if (0 == strcmp (src_dir, dst_dir) || + 0 == dst_dir[0]) + return; /* Already in "completed dir" */ + + snprintf (src, sizeof (src), "%s/%s", src_dir, fname); + snprintf (dst, sizeof (dst), "%s/%s", dst_dir, fname); + + /* already exists in completed dir? Append a number */ + if (file_exists_utf8 (dst)) + { + for (i = 0; ; i++) + { + snprintf (dst, sizeof (dst), "%s/%s.%d", dst_dir, fname, i); + if (!file_exists_utf8 (dst)) + break; + } + } + + /* convert UTF-8 to filesystem encoding */ + src_fs = xchat_filename_from_utf8 (src, -1, 0, 0, 0); + if (!src_fs) + return; + dst_fs = xchat_filename_from_utf8 (dst, -1, 0, 0, 0); + if (!dst_fs) + { + g_free (src_fs); + return; + } + + /* first try a simple rename move */ + res = rename (src_fs, dst_fs); + + if (res == -1 && (errno == EXDEV || errno == EPERM)) + { + /* link failed because either the two paths aren't on the */ + /* same filesystem or the filesystem doesn't support hard */ + /* links, so we have to do a copy. */ + if (copy_file (src_fs, dst_fs, dccpermissions)) + unlink (src_fs); + } + + g_free (dst_fs); + g_free (src_fs); +} + +int +mkdir_utf8 (char *dir) +{ + int ret; + + dir = xchat_filename_from_utf8 (dir, -1, 0, 0, 0); + if (!dir) + return -1; + +#ifdef WIN32 + ret = mkdir (dir); +#else + ret = mkdir (dir, S_IRUSR | S_IWUSR | S_IXUSR); +#endif + g_free (dir); + + return ret; +} + +/* separates a string according to a 'sep' char, then calls the callback + function for each token found */ + +int +token_foreach (char *str, char sep, + int (*callback) (char *str, void *ud), void *ud) +{ + char t, *start = str; + + while (1) + { + if (*str == sep || *str == 0) + { + t = *str; + *str = 0; + if (callback (start, ud) < 1) + { + *str = t; + return FALSE; + } + *str = t; + + if (*str == 0) + break; + str++; + start = str; + + } else + { + /* chars $00-$7f can never be embedded in utf-8 */ + str++; + } + } + + return TRUE; +} + +/* 31 bit string hash functions */ + +guint32 +str_hash (const char *key) +{ + const char *p = key; + guint32 h = *p; + + if (h) + for (p += 1; *p != '\0'; p++) + h = (h << 5) - h + *p; + + return h; +} + +guint32 +str_ihash (const unsigned char *key) +{ + const char *p = key; + guint32 h = rfc_tolowertab [(guint)*p]; + + if (h) + for (p += 1; *p != '\0'; p++) + h = (h << 5) - h + rfc_tolowertab [(guint)*p]; + + return h; +} + +/* features: 1. "src" must be valid, NULL terminated UTF-8 */ +/* 2. "dest" will be left with valid UTF-8 - no partial chars! */ + +void +safe_strcpy (char *dest, const char *src, int bytes_left) +{ + int mbl; + + while (1) + { + mbl = g_utf8_skip[*((unsigned char *)src)]; + + if (bytes_left < (mbl + 1)) /* can't fit with NULL? */ + { + *dest = 0; + break; + } + + if (mbl == 1) /* one byte char */ + { + *dest = *src; + if (*src == 0) + break; /* it all fit */ + dest++; + src++; + bytes_left--; + } + else /* multibyte char */ + { + memcpy (dest, src, mbl); + dest += mbl; + src += mbl; + bytes_left -= mbl; + } + } +} diff --git a/src/common/util.h b/src/common/util.h new file mode 100644 index 00000000..fce45def --- /dev/null +++ b/src/common/util.h @@ -0,0 +1,54 @@ +/************************************************************************ + * This technique was borrowed in part from the source code to + * ircd-hybrid-5.3 to implement case-insensitive string matches which + * are fully compliant with Section 2.2 of RFC 1459, the copyright + * of that code being (C) 1990 Jarkko Oikarinen and under the GPL. + * + * A special thanks goes to Mr. Okarinen for being the one person who + * seems to have ever noticed this section in the original RFC and + * written code for it. Shame on all the rest of you (myself included). + * + * --+ Dagmar d'Surreal + */ + +#ifndef XCHAT_UTIL_H +#define XCHAT_UTIL_H + +#define rfc_tolower(c) (rfc_tolowertab[(unsigned char)(c)]) + +extern const unsigned char rfc_tolowertab[]; + +int my_poptParseArgvString(const char * s, int * argcPtr, char *** argvPtr); +char *expand_homedir (char *file); +void path_part (char *file, char *path, int pathlen); +int match (const char *mask, const char *string); +char *file_part (char *file); +void for_files (char *dirname, char *mask, void callback (char *file)); +int rfc_casecmp (const char *, const char *); +int rfc_ncasecmp (char *, char *, int); +int buf_get_line (char *, char **, int *, int len); +char *nocasestrstr (const char *text, const char *tofind); +char *country (char *); +void country_search (char *pattern, void *ud, void (*print)(void *, char *, ...)); +char *get_cpu_str (void); +int util_exec (const char *cmd); +int util_execv (char * const argv[]); +#define STRIP_COLOR 1 +#define STRIP_ATTRIB 2 +#define STRIP_HIDDEN 4 +#define STRIP_ESCMARKUP 8 +#define STRIP_ALL 7 +gchar *strip_color (const char *text, int len, int flags); +int strip_color2 (const char *src, int len, char *dst, int flags); +int strip_hidden_attribute (char *src, char *dst); +char *errorstring (int err); +int waitline (int sok, char *buf, int bufsize, int); +unsigned long make_ping_time (void); +void move_file_utf8 (char *src_dir, char *dst_dir, char *fname, int dccpermissions); +int mkdir_utf8 (char *dir); +int token_foreach (char *str, char sep, int (*callback) (char *str, void *ud), void *ud); +guint32 str_hash (const char *key); +guint32 str_ihash (const unsigned char *key); +void safe_strcpy (char *dest, const char *src, int bytes_left); + +#endif diff --git a/src/common/xchat-plugin.h b/src/common/xchat-plugin.h new file mode 100644 index 00000000..30b19295 --- /dev/null +++ b/src/common/xchat-plugin.h @@ -0,0 +1,334 @@ +/* You can distribute this header with your plugins for easy compilation */ +#ifndef XCHAT_PLUGIN_H +#define XCHAT_PLUGIN_H + +#include <time.h> + +#define XCHAT_IFACE_MAJOR 1 +#define XCHAT_IFACE_MINOR 9 +#define XCHAT_IFACE_MICRO 11 +#define XCHAT_IFACE_VERSION ((XCHAT_IFACE_MAJOR * 10000) + \ + (XCHAT_IFACE_MINOR * 100) + \ + (XCHAT_IFACE_MICRO)) + +#define XCHAT_PRI_HIGHEST 127 +#define XCHAT_PRI_HIGH 64 +#define XCHAT_PRI_NORM 0 +#define XCHAT_PRI_LOW (-64) +#define XCHAT_PRI_LOWEST (-128) + +#define XCHAT_FD_READ 1 +#define XCHAT_FD_WRITE 2 +#define XCHAT_FD_EXCEPTION 4 +#define XCHAT_FD_NOTSOCKET 8 + +#define XCHAT_EAT_NONE 0 /* pass it on through! */ +#define XCHAT_EAT_XCHAT 1 /* don't let xchat see this event */ +#define XCHAT_EAT_PLUGIN 2 /* don't let other plugins see this event */ +#define XCHAT_EAT_ALL (XCHAT_EAT_XCHAT|XCHAT_EAT_PLUGIN) /* don't let anything see this event */ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _xchat_plugin xchat_plugin; +typedef struct _xchat_list xchat_list; +typedef struct _xchat_hook xchat_hook; +#ifndef PLUGIN_C +typedef struct _xchat_context xchat_context; +#endif + +#ifndef PLUGIN_C +struct _xchat_plugin +{ + /* these are only used on win32 */ + xchat_hook *(*xchat_hook_command) (xchat_plugin *ph, + const char *name, + int pri, + int (*callback) (char *word[], char *word_eol[], void *user_data), + const char *help_text, + void *userdata); + xchat_hook *(*xchat_hook_server) (xchat_plugin *ph, + const char *name, + int pri, + int (*callback) (char *word[], char *word_eol[], void *user_data), + void *userdata); + xchat_hook *(*xchat_hook_print) (xchat_plugin *ph, + const char *name, + int pri, + int (*callback) (char *word[], void *user_data), + void *userdata); + xchat_hook *(*xchat_hook_timer) (xchat_plugin *ph, + int timeout, + int (*callback) (void *user_data), + void *userdata); + xchat_hook *(*xchat_hook_fd) (xchat_plugin *ph, + int fd, + int flags, + int (*callback) (int fd, int flags, void *user_data), + void *userdata); + void *(*xchat_unhook) (xchat_plugin *ph, + xchat_hook *hook); + void (*xchat_print) (xchat_plugin *ph, + const char *text); + void (*xchat_printf) (xchat_plugin *ph, + const char *format, ...); + void (*xchat_command) (xchat_plugin *ph, + const char *command); + void (*xchat_commandf) (xchat_plugin *ph, + const char *format, ...); + int (*xchat_nickcmp) (xchat_plugin *ph, + const char *s1, + const char *s2); + int (*xchat_set_context) (xchat_plugin *ph, + xchat_context *ctx); + xchat_context *(*xchat_find_context) (xchat_plugin *ph, + const char *servname, + const char *channel); + xchat_context *(*xchat_get_context) (xchat_plugin *ph); + const char *(*xchat_get_info) (xchat_plugin *ph, + const char *id); + int (*xchat_get_prefs) (xchat_plugin *ph, + const char *name, + const char **string, + int *integer); + xchat_list * (*xchat_list_get) (xchat_plugin *ph, + const char *name); + void (*xchat_list_free) (xchat_plugin *ph, + xchat_list *xlist); + const char * const * (*xchat_list_fields) (xchat_plugin *ph, + const char *name); + int (*xchat_list_next) (xchat_plugin *ph, + xchat_list *xlist); + const char * (*xchat_list_str) (xchat_plugin *ph, + xchat_list *xlist, + const char *name); + int (*xchat_list_int) (xchat_plugin *ph, + xchat_list *xlist, + const char *name); + void * (*xchat_plugingui_add) (xchat_plugin *ph, + const char *filename, + const char *name, + const char *desc, + const char *version, + char *reserved); + void (*xchat_plugingui_remove) (xchat_plugin *ph, + void *handle); + int (*xchat_emit_print) (xchat_plugin *ph, + const char *event_name, ...); + int (*xchat_read_fd) (xchat_plugin *ph, + void *src, + char *buf, + int *len); + time_t (*xchat_list_time) (xchat_plugin *ph, + xchat_list *xlist, + const char *name); + char *(*xchat_gettext) (xchat_plugin *ph, + const char *msgid); + void (*xchat_send_modes) (xchat_plugin *ph, + const char **targets, + int ntargets, + int modes_per_line, + char sign, + char mode); + char *(*xchat_strip) (xchat_plugin *ph, + const char *str, + int len, + int flags); + void (*xchat_free) (xchat_plugin *ph, + void *ptr); +}; +#endif + + +xchat_hook * +xchat_hook_command (xchat_plugin *ph, + const char *name, + int pri, + int (*callback) (char *word[], char *word_eol[], void *user_data), + const char *help_text, + void *userdata); + +xchat_hook * +xchat_hook_server (xchat_plugin *ph, + const char *name, + int pri, + int (*callback) (char *word[], char *word_eol[], void *user_data), + void *userdata); + +xchat_hook * +xchat_hook_print (xchat_plugin *ph, + const char *name, + int pri, + int (*callback) (char *word[], void *user_data), + void *userdata); + +xchat_hook * +xchat_hook_timer (xchat_plugin *ph, + int timeout, + int (*callback) (void *user_data), + void *userdata); + +xchat_hook * +xchat_hook_fd (xchat_plugin *ph, + int fd, + int flags, + int (*callback) (int fd, int flags, void *user_data), + void *userdata); + +void * +xchat_unhook (xchat_plugin *ph, + xchat_hook *hook); + +void +xchat_print (xchat_plugin *ph, + const char *text); + +void +xchat_printf (xchat_plugin *ph, + const char *format, ...); + +void +xchat_command (xchat_plugin *ph, + const char *command); + +void +xchat_commandf (xchat_plugin *ph, + const char *format, ...); + +int +xchat_nickcmp (xchat_plugin *ph, + const char *s1, + const char *s2); + +int +xchat_set_context (xchat_plugin *ph, + xchat_context *ctx); + +xchat_context * +xchat_find_context (xchat_plugin *ph, + const char *servname, + const char *channel); + +xchat_context * +xchat_get_context (xchat_plugin *ph); + +const char * +xchat_get_info (xchat_plugin *ph, + const char *id); + +int +xchat_get_prefs (xchat_plugin *ph, + const char *name, + const char **string, + int *integer); + +xchat_list * +xchat_list_get (xchat_plugin *ph, + const char *name); + +void +xchat_list_free (xchat_plugin *ph, + xchat_list *xlist); + +const char * const * +xchat_list_fields (xchat_plugin *ph, + const char *name); + +int +xchat_list_next (xchat_plugin *ph, + xchat_list *xlist); + +const char * +xchat_list_str (xchat_plugin *ph, + xchat_list *xlist, + const char *name); + +int +xchat_list_int (xchat_plugin *ph, + xchat_list *xlist, + const char *name); + +time_t +xchat_list_time (xchat_plugin *ph, + xchat_list *xlist, + const char *name); + +void * +xchat_plugingui_add (xchat_plugin *ph, + const char *filename, + const char *name, + const char *desc, + const char *version, + char *reserved); + +void +xchat_plugingui_remove (xchat_plugin *ph, + void *handle); + +int +xchat_emit_print (xchat_plugin *ph, + const char *event_name, ...); + +char * +xchat_gettext (xchat_plugin *ph, + const char *msgid); + +void +xchat_send_modes (xchat_plugin *ph, + const char **targets, + int ntargets, + int modes_per_line, + char sign, + char mode); + +char * +xchat_strip (xchat_plugin *ph, + const char *str, + int len, + int flags); + +void +xchat_free (xchat_plugin *ph, + void *ptr); + +#if !defined(PLUGIN_C) && defined(WIN32) +#ifndef XCHAT_PLUGIN_HANDLE +#define XCHAT_PLUGIN_HANDLE (ph) +#endif +#define xchat_hook_command ((XCHAT_PLUGIN_HANDLE)->xchat_hook_command) +#define xchat_hook_server ((XCHAT_PLUGIN_HANDLE)->xchat_hook_server) +#define xchat_hook_print ((XCHAT_PLUGIN_HANDLE)->xchat_hook_print) +#define xchat_hook_timer ((XCHAT_PLUGIN_HANDLE)->xchat_hook_timer) +#define xchat_hook_fd ((XCHAT_PLUGIN_HANDLE)->xchat_hook_fd) +#define xchat_unhook ((XCHAT_PLUGIN_HANDLE)->xchat_unhook) +#define xchat_print ((XCHAT_PLUGIN_HANDLE)->xchat_print) +#define xchat_printf ((XCHAT_PLUGIN_HANDLE)->xchat_printf) +#define xchat_command ((XCHAT_PLUGIN_HANDLE)->xchat_command) +#define xchat_commandf ((XCHAT_PLUGIN_HANDLE)->xchat_commandf) +#define xchat_nickcmp ((XCHAT_PLUGIN_HANDLE)->xchat_nickcmp) +#define xchat_set_context ((XCHAT_PLUGIN_HANDLE)->xchat_set_context) +#define xchat_find_context ((XCHAT_PLUGIN_HANDLE)->xchat_find_context) +#define xchat_get_context ((XCHAT_PLUGIN_HANDLE)->xchat_get_context) +#define xchat_get_info ((XCHAT_PLUGIN_HANDLE)->xchat_get_info) +#define xchat_get_prefs ((XCHAT_PLUGIN_HANDLE)->xchat_get_prefs) +#define xchat_list_get ((XCHAT_PLUGIN_HANDLE)->xchat_list_get) +#define xchat_list_free ((XCHAT_PLUGIN_HANDLE)->xchat_list_free) +#define xchat_list_fields ((XCHAT_PLUGIN_HANDLE)->xchat_list_fields) +#define xchat_list_str ((XCHAT_PLUGIN_HANDLE)->xchat_list_str) +#define xchat_list_int ((XCHAT_PLUGIN_HANDLE)->xchat_list_int) +#define xchat_list_time ((XCHAT_PLUGIN_HANDLE)->xchat_list_time) +#define xchat_list_next ((XCHAT_PLUGIN_HANDLE)->xchat_list_next) +#define xchat_plugingui_add ((XCHAT_PLUGIN_HANDLE)->xchat_plugingui_add) +#define xchat_plugingui_remove ((XCHAT_PLUGIN_HANDLE)->xchat_plugingui_remove) +#define xchat_emit_print ((XCHAT_PLUGIN_HANDLE)->xchat_emit_print) +#define xchat_gettext ((XCHAT_PLUGIN_HANDLE)->xchat_gettext) +#define xchat_send_modes ((XCHAT_PLUGIN_HANDLE)->xchat_send_modes) +#define xchat_strip ((XCHAT_PLUGIN_HANDLE)->xchat_strip) +#define xchat_free ((XCHAT_PLUGIN_HANDLE)->xchat_free) +#endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/common/xchat.c b/src/common/xchat.c new file mode 100644 index 00000000..afac9a0e --- /dev/null +++ b/src/common/xchat.c @@ -0,0 +1,951 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#define WANTSOCKET +#include "inet.h" + +#ifndef WIN32 +#include <sys/wait.h> +#include <signal.h> +#endif + +#include "xchat.h" +#include "fe.h" +#include "util.h" +#include "cfgfiles.h" +#include "chanopt.h" +#include "ignore.h" +#include "xchat-plugin.h" +#include "plugin.h" +#include "plugin-timer.h" +#include "notify.h" +#include "server.h" +#include "servlist.h" +#include "outbound.h" +#include "text.h" +#include "url.h" +#include "xchatc.h" + +#ifdef USE_OPENSSL +#include <openssl/ssl.h> /* SSL_() */ +#include "ssl.h" +#endif + +#ifdef USE_MSPROXY +#include "msproxy.h" +#endif + +#ifdef USE_LIBPROXY +#include <proxy.h> +#endif + +GSList *popup_list = 0; +GSList *button_list = 0; +GSList *dlgbutton_list = 0; +GSList *command_list = 0; +GSList *ctcp_list = 0; +GSList *replace_list = 0; +GSList *sess_list = 0; +GSList *dcc_list = 0; +GSList *ignore_list = 0; +GSList *usermenu_list = 0; +GSList *urlhandler_list = 0; +GSList *tabmenu_list = 0; + +static int in_xchat_exit = FALSE; +int xchat_is_quitting = FALSE; +/* command-line args */ +int arg_dont_autoconnect = FALSE; +int arg_skip_plugins = FALSE; +char *arg_url = NULL; +char *arg_command = NULL; +gint arg_existing = FALSE; + +#ifdef USE_DBUS +#include "dbus/dbus-client.h" +#include "dbus/dbus-plugin.h" +#endif /* USE_DBUS */ + +struct session *current_tab; +struct session *current_sess = 0; +struct xchatprefs prefs; + +#ifdef USE_OPENSSL +SSL_CTX *ctx = NULL; +#endif + +#ifdef USE_LIBPROXY +pxProxyFactory *libproxy_factory; +#endif + +int +is_session (session * sess) +{ + return g_slist_find (sess_list, sess) ? 1 : 0; +} + +session * +find_dialog (server *serv, char *nick) +{ + GSList *list = sess_list; + session *sess; + + while (list) + { + sess = list->data; + if (sess->server == serv && sess->type == SESS_DIALOG) + { + if (!serv->p_cmp (nick, sess->channel)) + return (sess); + } + list = list->next; + } + return 0; +} + +session * +find_channel (server *serv, char *chan) +{ + session *sess; + GSList *list = sess_list; + while (list) + { + sess = list->data; + if ((!serv || serv == sess->server) && sess->type != SESS_DIALOG) + { + if (!serv->p_cmp (chan, sess->channel)) + return sess; + } + list = list->next; + } + return 0; +} + +static void +lagcheck_update (void) +{ + server *serv; + GSList *list = serv_list; + + if (!prefs.lagometer) + return; + + while (list) + { + serv = list->data; + if (serv->lag_sent) + fe_set_lag (serv, -1); + + list = list->next; + } +} + +void +lag_check (void) +{ + server *serv; + GSList *list = serv_list; + unsigned long tim; + char tbuf[128]; + time_t now = time (0); + int lag; + + tim = make_ping_time (); + + while (list) + { + serv = list->data; + if (serv->connected && serv->end_of_motd) + { + lag = now - serv->ping_recv; + if (prefs.pingtimeout && lag > prefs.pingtimeout && lag > 0) + { + sprintf (tbuf, "%d", lag); + EMIT_SIGNAL (XP_TE_PINGTIMEOUT, serv->server_session, tbuf, NULL, + NULL, NULL, 0); + if (prefs.autoreconnect) + serv->auto_reconnect (serv, FALSE, -1); + } else + { + snprintf (tbuf, sizeof (tbuf), "LAG%lu", tim); + serv->p_ping (serv, "", tbuf); + serv->lag_sent = tim; + fe_set_lag (serv, -1); + } + } + list = list->next; + } +} + +static int +away_check (void) +{ + session *sess; + GSList *list; + int full, sent, loop = 0; + + if (!prefs.away_track || prefs.away_size_max < 1) + return 1; + +doover: + /* request an update of AWAY status of 1 channel every 30 seconds */ + full = TRUE; + sent = 0; /* number of WHOs (users) requested */ + list = sess_list; + while (list) + { + sess = list->data; + + if (sess->server->connected && + sess->type == SESS_CHANNEL && + sess->channel[0] && + sess->total <= prefs.away_size_max) + { + if (!sess->done_away_check) + { + full = FALSE; + + /* if we're under 31 WHOs, send another channels worth */ + if (sent < 31 && !sess->doing_who) + { + sess->done_away_check = TRUE; + sess->doing_who = TRUE; + /* this'll send a WHO #channel */ + sess->server->p_away_status (sess->server, sess->channel); + sent += sess->total; + } + } + } + + list = list->next; + } + + /* done them all, reset done_away_check to FALSE and start over */ + if (full) + { + list = sess_list; + while (list) + { + sess = list->data; + sess->done_away_check = FALSE; + list = list->next; + } + loop++; + if (loop < 2) + goto doover; + } + + return 1; +} + +static int +xchat_misc_checks (void) /* this gets called every 1/2 second */ +{ + static int count = 0; +#ifdef USE_MSPROXY + static int count2 = 0; +#endif + + count++; + + lagcheck_update (); /* every 500ms */ + + if (count % 2) + dcc_check_timeouts (); /* every 1 second */ + + if (count >= 60) /* every 30 seconds */ + { + if (prefs.lagometer) + lag_check (); + count = 0; + } + +#ifdef USE_MSPROXY + count2++; + if (count2 >= 720) /* 720 every 6 minutes */ + { + msproxy_keepalive (); + count2 = 0; + } +#endif + + return 1; +} + +/* executed when the first irc window opens */ + +static void +irc_init (session *sess) +{ + static int done_init = FALSE; + char buf[512]; + + if (done_init) + return; + + done_init = TRUE; + + plugin_add (sess, NULL, NULL, timer_plugin_init, NULL, NULL, FALSE); + +#ifdef USE_PLUGIN + if (!arg_skip_plugins) + plugin_auto_load (sess); /* autoload ~/.xchat *.so */ +#endif + +#ifdef USE_DBUS + plugin_add (sess, NULL, NULL, dbus_plugin_init, NULL, NULL, FALSE); +#endif + + if (prefs.notify_timeout) + notify_tag = fe_timeout_add (prefs.notify_timeout * 1000, + notify_checklist, 0); + + fe_timeout_add (prefs.away_timeout * 1000, away_check, 0); + fe_timeout_add (500, xchat_misc_checks, 0); + + if (arg_url != NULL) + { + snprintf (buf, sizeof (buf), "server %s", arg_url); + handle_command (sess, buf, FALSE); + g_free (arg_url); /* from GOption */ + } + + if (arg_command != NULL) + { + g_free (arg_command); + } + + /* load -e ~/.xchat2/startup.txt */ + snprintf (buf, sizeof (buf), "%s/%s", get_xdir_fs (), "startup.txt"); + load_perform_file (sess, buf); +} + +static session * +session_new (server *serv, char *from, int type, int focus) +{ + session *sess; + + sess = malloc (sizeof (struct session)); + memset (sess, 0, sizeof (struct session)); + + sess->server = serv; + sess->logfd = -1; + sess->scrollfd = -1; + sess->type = type; + + sess->alert_beep = SET_DEFAULT; + sess->alert_taskbar = SET_DEFAULT; + sess->alert_tray = SET_DEFAULT; + + sess->text_hidejoinpart = SET_DEFAULT; + sess->text_logging = SET_DEFAULT; + sess->text_scrollback = SET_DEFAULT; + + if (from != NULL) + safe_strcpy (sess->channel, from, CHANLEN); + + sess_list = g_slist_prepend (sess_list, sess); + + fe_new_window (sess, focus); + + return sess; +} + +session * +new_ircwindow (server *serv, char *name, int type, int focus) +{ + session *sess; + + switch (type) + { + case SESS_SERVER: + serv = server_new (); + if (prefs.use_server_tab) + sess = session_new (serv, name, SESS_SERVER, focus); + else + sess = session_new (serv, name, SESS_CHANNEL, focus); + serv->server_session = sess; + serv->front_session = sess; + break; + case SESS_DIALOG: + sess = session_new (serv, name, type, focus); + log_open_or_close (sess); + break; + default: +/* case SESS_CHANNEL: + case SESS_NOTICES: + case SESS_SNOTICES:*/ + sess = session_new (serv, name, type, focus); + break; + } + + irc_init (sess); + scrollback_load (sess); + chanopt_load (sess); + plugin_emit_dummy_print (sess, "Open Context"); + + return sess; +} + +static void +exec_notify_kill (session * sess) +{ +#ifndef WIN32 + struct nbexec *re; + if (sess->running_exec != NULL) + { + re = sess->running_exec; + sess->running_exec = NULL; + kill (re->childpid, SIGKILL); + waitpid (re->childpid, NULL, WNOHANG); + fe_input_remove (re->iotag); + close (re->myfd); + if (re->linebuf) + free(re->linebuf); + free (re); + } +#endif +} + +static void +send_quit_or_part (session * killsess) +{ + int willquit = TRUE; + GSList *list; + session *sess; + server *killserv = killsess->server; + + /* check if this is the last session using this server */ + list = sess_list; + while (list) + { + sess = (session *) list->data; + if (sess->server == killserv && sess != killsess) + { + willquit = FALSE; + list = 0; + } else + list = list->next; + } + + if (xchat_is_quitting) + willquit = TRUE; + + if (killserv->connected) + { + if (willquit) + { + if (!killserv->sent_quit) + { + killserv->flush_queue (killserv); + server_sendquit (killsess); + killserv->sent_quit = TRUE; + } + } else + { + if (killsess->type == SESS_CHANNEL && killsess->channel[0] && + !killserv->sent_quit) + { + server_sendpart (killserv, killsess->channel, 0); + } + } + } +} + +void +session_free (session *killsess) +{ + server *killserv = killsess->server; + session *sess; + GSList *list; + + plugin_emit_dummy_print (killsess, "Close Context"); + + if (current_tab == killsess) + current_tab = NULL; + + if (killserv->server_session == killsess) + killserv->server_session = NULL; + + if (killserv->front_session == killsess) + { + /* front_session is closed, find a valid replacement */ + killserv->front_session = NULL; + list = sess_list; + while (list) + { + sess = (session *) list->data; + if (sess != killsess && sess->server == killserv) + { + killserv->front_session = sess; + if (!killserv->server_session) + killserv->server_session = sess; + break; + } + list = list->next; + } + } + + if (!killserv->server_session) + killserv->server_session = killserv->front_session; + + sess_list = g_slist_remove (sess_list, killsess); + + if (killsess->type == SESS_CHANNEL) + userlist_free (killsess); + + exec_notify_kill (killsess); + + log_close (killsess); + scrollback_close (killsess); + chanopt_save (killsess); + + send_quit_or_part (killsess); + + history_free (&killsess->history); + if (killsess->topic) + free (killsess->topic); + if (killsess->current_modes) + free (killsess->current_modes); + + fe_session_callback (killsess); + + if (current_sess == killsess) + { + current_sess = NULL; + if (sess_list) + current_sess = sess_list->data; + } + + free (killsess); + + if (!sess_list && !in_xchat_exit) + xchat_exit (); /* sess_list is empty, quit! */ + + list = sess_list; + while (list) + { + sess = (session *) list->data; + if (sess->server == killserv) + return; /* this server is still being used! */ + list = list->next; + } + + server_free (killserv); +} + +static void +free_sessions (void) +{ + GSList *list = sess_list; + + while (list) + { + fe_close_window (list->data); + list = sess_list; + } +} + + +static char defaultconf_ctcp[] = + "NAME TIME\n" "CMD nctcp %s TIME %t\n\n"\ + "NAME PING\n" "CMD nctcp %s PING %d\n\n"; + +static char defaultconf_replace[] = + "NAME teh\n" "CMD the\n\n"; +/* "NAME r\n" "CMD are\n\n"\ + "NAME u\n" "CMD you\n\n"*/ + +static char defaultconf_commands[] = + "NAME ACTION\n" "CMD me &2\n\n"\ + "NAME AME\n" "CMD allchan me &2\n\n"\ + "NAME ANICK\n" "CMD allserv nick &2\n\n"\ + "NAME AMSG\n" "CMD allchan say &2\n\n"\ + "NAME BANLIST\n" "CMD quote MODE %c +b\n\n"\ + "NAME CHAT\n" "CMD dcc chat %2\n\n"\ + "NAME DIALOG\n" "CMD query %2\n\n"\ + "NAME DMSG\n" "CMD msg =%2 &3\n\n"\ + "NAME EXIT\n" "CMD quit\n\n"\ + "NAME GREP\n" "CMD lastlog -r &2\n\n"\ + "NAME J\n" "CMD join &2\n\n"\ + "NAME KILL\n" "CMD quote KILL %2 :&3\n\n"\ + "NAME LEAVE\n" "CMD part &2\n\n"\ + "NAME M\n" "CMD msg &2\n\n"\ + "NAME ONOTICE\n" "CMD notice @%c &2\n\n"\ + "NAME RAW\n" "CMD quote &2\n\n"\ + "NAME SERVHELP\n" "CMD quote HELP\n\n"\ + "NAME SPING\n" "CMD ping\n\n"\ + "NAME SQUERY\n" "CMD quote SQUERY %2 :&3\n\n"\ + "NAME SSLSERVER\n" "CMD server -ssl &2\n\n"\ + "NAME SV\n" "CMD echo xchat %v %m\n\n"\ + "NAME UMODE\n" "CMD mode %n &2\n\n"\ + "NAME UPTIME\n" "CMD quote STATS u\n\n"\ + "NAME VER\n" "CMD ctcp %2 VERSION\n\n"\ + "NAME VERSION\n" "CMD ctcp %2 VERSION\n\n"\ + "NAME WALLOPS\n" "CMD quote WALLOPS :&2\n\n"\ + "NAME WII\n" "CMD quote WHOIS %2 %2\n\n"; + +static char defaultconf_urlhandlers[] = + "NAME Open Link in Opera\n" "CMD !opera -remote 'openURL(%s)'\n\n"; + +#ifdef USE_SIGACTION +/* Close and open log files on SIGUSR1. Usefull for log rotating */ + +static void +sigusr1_handler (int signal, siginfo_t *si, void *un) +{ + GSList *list = sess_list; + session *sess; + + while (list) + { + sess = list->data; + log_open_or_close (sess); + list = list->next; + } +} + +/* Execute /SIGUSR2 when SIGUSR2 received */ + +static void +sigusr2_handler (int signal, siginfo_t *si, void *un) +{ + session *sess = current_sess; + + if (sess) + handle_command (sess, "SIGUSR2", FALSE); +} +#endif + +static gint +xchat_auto_connect (gpointer userdata) +{ + servlist_auto_connect (NULL); + return 0; +} + +static void +xchat_init (void) +{ + char buf[3068]; + const char *cs = NULL; + +#ifdef WIN32 + WSADATA wsadata; + +#ifdef USE_IPV6 + if (WSAStartup(0x0202, &wsadata) != 0) + { + MessageBox (NULL, "Cannot find winsock 2.2+", "Error", MB_OK); + exit (0); + } +#else + WSAStartup(0x0101, &wsadata); +#endif /* !USE_IPV6 */ +#endif /* !WIN32 */ + +#ifdef USE_SIGACTION + struct sigaction act; + + /* ignore SIGPIPE's */ + act.sa_handler = SIG_IGN; + act.sa_flags = 0; + sigemptyset (&act.sa_mask); + sigaction (SIGPIPE, &act, NULL); + + /* Deal with SIGUSR1's & SIGUSR2's */ + act.sa_sigaction = sigusr1_handler; + act.sa_flags = 0; + sigemptyset (&act.sa_mask); + sigaction (SIGUSR1, &act, NULL); + + act.sa_sigaction = sigusr2_handler; + act.sa_flags = 0; + sigemptyset (&act.sa_mask); + sigaction (SIGUSR2, &act, NULL); +#else +#ifndef WIN32 + /* good enough for these old systems */ + signal (SIGPIPE, SIG_IGN); +#endif +#endif + + if (g_get_charset (&cs)) + prefs.utf8_locale = TRUE; + + load_text_events (); + sound_load (); + notify_load (); + ignore_load (); + + snprintf (buf, sizeof (buf), + "NAME %s~%s~\n" "CMD query %%s\n\n"\ + "NAME %s~%s~\n" "CMD send %%s\n\n"\ + "NAME %s~%s~\n" "CMD whois %%s %%s\n\n"\ + "NAME %s~%s~\n" "CMD notify -n ASK %%s\n\n"\ + + "NAME SUB\n" "CMD %s\n\n"\ + "NAME %s\n" "CMD op %%a\n\n"\ + "NAME %s\n" "CMD deop %%a\n\n"\ + "NAME SEP\n" "CMD \n\n"\ + "NAME %s\n" "CMD voice %%a\n\n"\ + "NAME %s\n" "CMD devoice %%a\n"\ + "NAME SEP\n" "CMD \n\n"\ + "NAME SUB\n" "CMD %s\n\n"\ + "NAME %s\n" "CMD kick %%s\n\n"\ + "NAME %s\n" "CMD ban %%s\n\n"\ + "NAME SEP\n" "CMD \n\n"\ + "NAME %s *!*@*.host\n""CMD ban %%s 0\n\n"\ + "NAME %s *!*@domain\n""CMD ban %%s 1\n\n"\ + "NAME %s *!*user@*.host\n""CMD ban %%s 2\n\n"\ + "NAME %s *!*user@domain\n""CMD ban %%s 3\n\n"\ + "NAME SEP\n" "CMD \n\n"\ + "NAME %s *!*@*.host\n""CMD kickban %%s 0\n\n"\ + "NAME %s *!*@domain\n""CMD kickban %%s 1\n\n"\ + "NAME %s *!*user@*.host\n""CMD kickban %%s 2\n\n"\ + "NAME %s *!*user@domain\n""CMD kickban %%s 3\n\n"\ + "NAME ENDSUB\n" "CMD \n\n"\ + "NAME ENDSUB\n" "CMD \n\n", + + _("_Open Dialog Window"), "xchat-dialog", + _("_Send a File"), "gtk-floppy", + _("_User Info (WhoIs)"), "gtk-info", + _("_Add to Friends List"), "gtk-add", + _("O_perator Actions"), + + _("Give Ops"), + _("Take Ops"), + _("Give Voice"), + _("Take Voice"), + + _("Kick/Ban"), + _("Kick"), + _("Ban"), + _("Ban"), + _("Ban"), + _("Ban"), + _("Ban"), + _("KickBan"), + _("KickBan"), + _("KickBan"), + _("KickBan")); + + list_loadconf ("popup.conf", &popup_list, buf); + + snprintf (buf, sizeof (buf), + "NAME %s\n" "CMD part\n\n" + "NAME %s\n" "CMD getstr # join \"%s\"\n\n" + "NAME %s\n" "CMD quote LINKS\n\n" + "NAME %s\n" "CMD ping\n\n" + "NAME TOGGLE %s\n" "CMD irc_hide_version\n\n", + _("Leave Channel"), + _("Join Channel..."), + _("Enter Channel to Join:"), + _("Server Links"), + _("Ping Server"), + _("Hide Version")); + list_loadconf ("usermenu.conf", &usermenu_list, buf); + + snprintf (buf, sizeof (buf), + "NAME %s\n" "CMD op %%a\n\n" + "NAME %s\n" "CMD deop %%a\n\n" + "NAME %s\n" "CMD ban %%s\n\n" + "NAME %s\n" "CMD getstr \"%s\" \"kick %%s\" \"%s\"\n\n" + "NAME %s\n" "CMD send %%s\n\n" + "NAME %s\n" "CMD query %%s\n\n", + _("Op"), + _("DeOp"), + _("Ban"), + _("Kick"), + _("bye"), + _("Enter reason to kick %s:"), + _("Sendfile"), + _("Dialog")); + list_loadconf ("buttons.conf", &button_list, buf); + + snprintf (buf, sizeof (buf), + "NAME %s\n" "CMD whois %%s %%s\n\n" + "NAME %s\n" "CMD send %%s\n\n" + "NAME %s\n" "CMD dcc chat %%s\n\n" + "NAME %s\n" "CMD clear\n\n" + "NAME %s\n" "CMD ping %%s\n\n", + _("WhoIs"), + _("Send"), + _("Chat"), + _("Clear"), + _("Ping")); + list_loadconf ("dlgbuttons.conf", &dlgbutton_list, buf); + + list_loadconf ("tabmenu.conf", &tabmenu_list, NULL); + list_loadconf ("ctcpreply.conf", &ctcp_list, defaultconf_ctcp); + list_loadconf ("commands.conf", &command_list, defaultconf_commands); + list_loadconf ("replace.conf", &replace_list, defaultconf_replace); + list_loadconf ("urlhandlers.conf", &urlhandler_list, + defaultconf_urlhandlers); + + servlist_init (); /* load server list */ + + /* if we got a URL, don't open the server list GUI */ + if (!prefs.slist_skip && !arg_url) + fe_serverlist_open (NULL); + + /* turned OFF via -a arg */ + if (!arg_dont_autoconnect) + { + /* do any auto connects */ + if (!servlist_have_auto ()) /* if no new windows open .. */ + { + /* and no serverlist gui ... */ + if (prefs.slist_skip || arg_url) + /* we'll have to open one. */ + new_ircwindow (NULL, NULL, SESS_SERVER, 0); + } else + { + fe_idle_add (xchat_auto_connect, NULL); + } + } else + { + if (prefs.slist_skip || arg_url) + new_ircwindow (NULL, NULL, SESS_SERVER, 0); + } +} + +void +xchat_exit (void) +{ + xchat_is_quitting = TRUE; + in_xchat_exit = TRUE; + plugin_kill_all (); + fe_cleanup (); + if (prefs.autosave) + { + save_config (); + if (prefs.save_pevents) + pevent_save (NULL); + } + if (prefs.autosave_url) + url_autosave (); + sound_save (); + notify_save (); + ignore_save (); + free_sessions (); + chanopt_save_all (); + servlist_cleanup (); + fe_exit (); +} + +#ifndef WIN32 + +static int +child_handler (gpointer userdata) +{ + int pid = GPOINTER_TO_INT (userdata); + + if (waitpid (pid, 0, WNOHANG) == pid) + return 0; /* remove timeout handler */ + return 1; /* keep the timeout handler */ +} + +#endif + +void +xchat_exec (const char *cmd) +{ +#ifdef WIN32 + util_exec (cmd); +#else + int pid = util_exec (cmd); + if (pid != -1) + /* zombie avoiding system. Don't ask! it has to be like this to work + with zvt (which overrides the default handler) */ + fe_timeout_add (1000, child_handler, GINT_TO_POINTER (pid)); +#endif +} + +void +xchat_execv (char * const argv[]) +{ +#ifdef WIN32 + util_execv (argv); +#else + int pid = util_execv (argv); + if (pid != -1) + /* zombie avoiding system. Don't ask! it has to be like this to work + with zvt (which overrides the default handler) */ + fe_timeout_add (1000, child_handler, GINT_TO_POINTER (pid)); +#endif +} + +int +main (int argc, char *argv[]) +{ + int ret; + + srand (time (0)); /* CL: do this only once! */ + +#ifdef SOCKS + SOCKSinit (argv[0]); +#endif + + ret = fe_args (argc, argv); + if (ret != -1) + return ret; + +#ifdef USE_DBUS + xchat_remote (); +#endif + + load_config (); + +#ifdef USE_LIBPROXY + libproxy_factory = px_proxy_factory_new(); +#endif + + fe_init (); + + xchat_init (); + + fe_main (); + +#ifdef USE_LIBPROXY + px_proxy_factory_free(libproxy_factory); +#endif + +#ifdef USE_OPENSSL + if (ctx) + _SSL_context_free (ctx); +#endif + +#ifdef USE_DEBUG + xchat_mem_list (); +#endif + +#ifdef WIN32 + WSACleanup (); +#endif + + return 0; +} diff --git a/src/common/xchat.h b/src/common/xchat.h new file mode 100644 index 00000000..db7a6c4b --- /dev/null +++ b/src/common/xchat.h @@ -0,0 +1,575 @@ +#include "../../config.h" + +#include <glib/gslist.h> +#include <glib/glist.h> +#include <glib/gutils.h> +#include <glib/giochannel.h> +#include <glib/gstrfuncs.h> +#include <time.h> /* need time_t */ + +#ifndef XCHAT_H +#define XCHAT_H + +#include "history.h" + +#ifndef HAVE_SNPRINTF +#define snprintf g_snprintf +#endif + +#ifndef HAVE_VSNPRINTF +#define vsnprintf g_vsnprintf +#endif + +#ifdef USE_DEBUG +#define malloc(n) xchat_malloc(n, __FILE__, __LINE__) +#define realloc(n, m) xchat_realloc(n, m, __FILE__, __LINE__) +#define free(n) xchat_dfree(n, __FILE__, __LINE__) +#define strdup(n) xchat_strdup(n, __FILE__, __LINE__) +void *xchat_malloc (int size, char *file, int line); +void *xchat_strdup (char *str, char *file, int line); +void xchat_dfree (void *buf, char *file, int line); +void *xchat_realloc (char *old, int len, char *file, int line); +#endif + +#ifdef SOCKS +#ifdef __sgi +#include <sys/time.h> +#define INCLUDE_PROTOTYPES 1 +#endif +#include <socks.h> +#endif + +#ifdef USE_OPENSSL +#include <openssl/ssl.h> /* SSL_() */ +#endif + +#ifdef __EMX__ /* for o/s 2 */ +#define OFLAGS O_BINARY +#define strcasecmp stricmp +#define strncasecmp strnicmp +#define PATH_MAX MAXPATHLEN +#define FILEPATH_LEN_MAX MAXPATHLEN +#endif + +/* force a 32bit CMP.L */ +#define CMPL(a, c0, c1, c2, c3) (a == (guint32)(c0 | (c1 << 8) | (c2 << 16) | (c3 << 24))) +#define WORDL(c0, c1, c2, c3) (guint32)(c0 | (c1 << 8) | (c2 << 16) | (c3 << 24)) +#define WORDW(c0, c1) (guint16)(c0 | (c1 << 8)) + +#ifdef WIN32 /* for win32 */ +#define OFLAGS O_BINARY +#define sleep(t) _sleep(t*1000) +#include <direct.h> +#define F_OK 0 +#define X_OK 1 +#define W_OK 2 +#define R_OK 4 +#ifndef S_ISDIR +#define S_ISDIR(m) ((m) & _S_IFDIR) +#endif +#define NETWORK_PRIVATE +#else /* for unix */ +#define OFLAGS 0 +#endif + +#define FONTNAMELEN 127 +#define PATHLEN 255 +#define DOMAINLEN 100 +#define NICKLEN 64 /* including the NULL, so 63 really */ +#define CHANLEN 300 +#define PDIWORDS 32 +#define USERNAMELEN 10 +#define HIDDEN_CHAR 8 /* invisible character for xtext */ + +#if defined(ENABLE_NLS) && !defined(_) +# include <libintl.h> +# define _(x) gettext(x) +# ifdef gettext_noop +# define N_(String) gettext_noop (String) +# else +# define N_(String) (String) +# endif +#endif +#if !defined(_) +# define N_(String) (String) +# define _(x) (x) +#endif + +struct nbexec +{ + int myfd; + int childpid; + int tochannel; /* making this int keeps the struct 4-byte aligned */ + int iotag; + char *linebuf; + int buffill; + struct session *sess; +}; + +struct xchatprefs +{ + char nick1[NICKLEN]; + char nick2[NICKLEN]; + char nick3[NICKLEN]; + char realname[127]; + char username[127]; + char nick_suffix[4]; /* Only ever holds a one-character string. */ + char awayreason[256]; + char quitreason[256]; + char partreason[256]; + char font_normal[FONTNAMELEN + 1]; + char doubleclickuser[256]; + char sounddir[PATHLEN + 1]; + char soundcmd[PATHLEN + 1]; + char background[PATHLEN + 1]; + char dccdir[PATHLEN + 1]; + char dcc_completed_dir[PATHLEN + 1]; + char irc_extra_hilight[300]; + char irc_no_hilight[300]; + char irc_nick_hilight[300]; + char dnsprogram[72]; + char hostname[127]; + char cmdchar[4]; + char logmask[256]; + char stamp_format[64]; + char timestamp_log_format[64]; + char irc_id_ytext[64]; + char irc_id_ntext[64]; + + char proxy_host[64]; + int proxy_port; + int proxy_type; /* 0=disabled, 1=wingate 2=socks4, 3=socks5, 4=http */ + int proxy_use; /* 0=all 1=IRC_ONLY 2=DCC_ONLY */ + unsigned int proxy_auth; + char proxy_user[32]; + char proxy_pass[32]; + + int first_dcc_send_port; + int last_dcc_send_port; + + int tint_red; + int tint_green; + int tint_blue; + + int away_timeout; + int away_size_max; + + int gui_pane_left_size; + int gui_pane_right_size; + + int gui_ulist_pos; + int tab_pos; + + int _tabs_position; + int tab_layout; + int max_auto_indent; + int dcc_blocksize; + int max_lines; + int notify_timeout; + int dcctimeout; + int dccstalltimeout; + int dcc_global_max_get_cps; + int dcc_global_max_send_cps; + int dcc_max_get_cps; + int dcc_max_send_cps; + int mainwindow_left; + int mainwindow_top; + int mainwindow_width; + int mainwindow_height; + int completion_sort; + int gui_win_state; + int gui_url_mod; + int gui_usermenu; + int gui_join_dialog; + int gui_quit_dialog; + int dialog_left; + int dialog_top; + int dialog_width; + int dialog_height; + int dccpermissions; + int recon_delay; + int bantype; + int userlist_sort; + guint32 local_ip; + guint32 dcc_ip; + char dcc_ip_str[DOMAINLEN + 1]; + + unsigned int tab_small; + unsigned int tab_sort; + unsigned int mainwindow_save; + unsigned int perc_color; + unsigned int perc_ascii; + unsigned int autosave; + unsigned int autodialog; + unsigned int autosave_url; + unsigned int autoreconnect; + unsigned int autoreconnectonfail; + unsigned int invisible; + unsigned int servernotice; + unsigned int wallops; + unsigned int skipmotd; + unsigned int autorejoin; + unsigned int colorednicks; + unsigned int chanmodebuttons; + unsigned int userlistbuttons; + unsigned int showhostname_in_userlist; + unsigned int nickcompletion; + unsigned int completion_amount; + unsigned int tabchannels; + unsigned int paned_userlist; + unsigned int autodccchat; + unsigned int autodccsend; + unsigned int autoresume; + unsigned int autoopendccsendwindow; + unsigned int autoopendccrecvwindow; + unsigned int autoopendccchatwindow; + unsigned int transparent; + unsigned int stripcolor; + unsigned int timestamp; + unsigned int fastdccsend; + unsigned int dcc_send_fillspaces; + unsigned int dcc_remove; + unsigned int slist_fav; + unsigned int slist_skip; + unsigned int slist_select; + unsigned int filterbeep; + + unsigned int input_balloon_chans; + unsigned int input_balloon_hilight; + unsigned int input_balloon_priv; + unsigned int input_balloon_time; + + unsigned int input_beep_chans; + unsigned int input_beep_hilight; + unsigned int input_beep_priv; + + unsigned int input_flash_chans; + unsigned int input_flash_hilight; + unsigned int input_flash_priv; + + unsigned int input_tray_chans; + unsigned int input_tray_hilight; + unsigned int input_tray_priv; + + unsigned int truncchans; + unsigned int privmsgtab; + unsigned int irc_join_delay; + unsigned int logging; + unsigned int timestamp_logs; + unsigned int newtabstofront; + unsigned int dccwithnick; + unsigned int hidever; + unsigned int ip_from_server; + unsigned int raw_modes; + unsigned int show_away_once; + unsigned int show_away_message; + unsigned int auto_unmark_away; + unsigned int away_track; + unsigned int userhost; + unsigned int irc_whois_front; + unsigned int use_server_tab; + unsigned int notices_tabs; + unsigned int style_namelistgad; + unsigned int style_inputbox; + unsigned int windows_as_tabs; + unsigned int indent_nicks; + unsigned int text_replay; + unsigned int show_marker; + unsigned int show_separator; + unsigned int thin_separator; + unsigned int auto_indent; + unsigned int wordwrap; + unsigned int gui_input_spell; + unsigned int gui_tray; + unsigned int gui_tray_flags; + unsigned int gui_tweaks; + unsigned int _gui_ulist_left; + unsigned int throttle; + unsigned int topicbar; + unsigned int hideuserlist; + unsigned int hidemenu; + unsigned int perlwarnings; + unsigned int lagometer; + unsigned int throttlemeter; + unsigned int pingtimeout; + unsigned int whois_on_notifyonline; + unsigned int wait_on_exit; + unsigned int confmode; + unsigned int utf8_locale; + unsigned int identd; + + unsigned int ctcp_number_limit; /*flood */ + unsigned int ctcp_time_limit; /*seconds of floods */ + + unsigned int msg_number_limit; /*same deal */ + unsigned int msg_time_limit; + + /* Tells us if we need to save, only when they've been edited. + This is so that we continue using internal defaults (which can + change in the next release) until the user edits them. */ + unsigned int save_pevents:1; +}; + +/* Session types */ +#define SESS_SERVER 1 +#define SESS_CHANNEL 2 +#define SESS_DIALOG 3 +#define SESS_NOTICES 4 +#define SESS_SNOTICES 5 + +/* Per-Channel Settings */ +#define SET_OFF 0 +#define SET_ON 1 +#define SET_DEFAULT 2 /* use global setting */ + +typedef struct session +{ + /* Per-Channel Alerts */ + /* use a byte, because we need a pointer to each element */ + guint8 alert_beep; + guint8 alert_taskbar; + guint8 alert_tray; + + /* Per-Channel Settings */ + guint8 text_hidejoinpart; + guint8 text_logging; + guint8 text_scrollback; + + struct server *server; + void *usertree_alpha; /* pure alphabetical tree */ + void *usertree; /* ordered with Ops first */ + struct User *me; /* points to myself in the usertree */ + char channel[CHANLEN]; + char waitchannel[CHANLEN]; /* waiting to join channel (/join sent) */ + char willjoinchannel[CHANLEN]; /* will issue /join for this channel */ + char channelkey[64]; /* XXX correct max length? */ + int limit; /* channel user limit */ + int logfd; + int scrollfd; /* scrollback filedes */ + int scrollwritten; /* number of lines written */ + + char lastnick[NICKLEN]; /* last nick you /msg'ed */ + + struct history history; + + int ops; /* num. of ops in channel */ + int hops; /* num. of half-oped users */ + int voices; /* num. of voiced people */ + int total; /* num. of users in channel */ + + char *quitreason; + char *topic; + char *current_modes; /* free() me */ + + int mode_timeout_tag; + + struct session *lastlog_sess; + struct nbexec *running_exec; + + struct session_gui *gui; /* initialized by fe_new_window */ + struct restore_gui *res; + + int type; /* SESS_* */ + + int new_data:1; /* new data avail? (purple tab) */ + int nick_said:1; /* your nick mentioned? (blue tab) */ + int msg_said:1; /* new msg available? (red tab) */ + + int ignore_date:1; + int ignore_mode:1; + int ignore_names:1; + int end_of_names:1; + int doing_who:1; /* /who sent on this channel */ + int done_away_check:1; /* done checking for away status changes */ + unsigned int lastlog_regexp:1; /* this is a lastlog and using regexp */ +} session; + +struct msproxy_state_t +{ + gint32 clientid; + gint32 serverid; + unsigned char seq_recv; /* seq number of last packet recv. */ + unsigned char seq_sent; /* seq number of last packet sent. */ +}; + +typedef struct server +{ + /* server control operations (in server*.c) */ + void (*connect)(struct server *, char *hostname, int port, int no_login); + void (*disconnect)(struct session *, int sendquit, int err); + int (*cleanup)(struct server *); + void (*flush_queue)(struct server *); + void (*auto_reconnect)(struct server *, int send_quit, int err); + /* irc protocol functions (in proto*.c) */ + void (*p_inline)(struct server *, char *buf, int len); + void (*p_invite)(struct server *, char *channel, char *nick); + void (*p_cycle)(struct server *, char *channel, char *key); + void (*p_ctcp)(struct server *, char *to, char *msg); + void (*p_nctcp)(struct server *, char *to, char *msg); + void (*p_quit)(struct server *, char *reason); + void (*p_kick)(struct server *, char *channel, char *nick, char *reason); + void (*p_part)(struct server *, char *channel, char *reason); + void (*p_ns_identify)(struct server *, char *pass); + void (*p_ns_ghost)(struct server *, char *usname, char *pass); + void (*p_join)(struct server *, char *channel, char *key); + void (*p_join_list)(struct server *, GSList *channels, GSList *keys); + void (*p_login)(struct server *, char *user, char *realname); + void (*p_join_info)(struct server *, char *channel); + void (*p_mode)(struct server *, char *target, char *mode); + void (*p_user_list)(struct server *, char *channel); + void (*p_away_status)(struct server *, char *channel); + void (*p_whois)(struct server *, char *nicks); + void (*p_get_ip)(struct server *, char *nick); + void (*p_get_ip_uh)(struct server *, char *nick); + void (*p_set_back)(struct server *); + void (*p_set_away)(struct server *, char *reason); + void (*p_message)(struct server *, char *channel, char *text); + void (*p_action)(struct server *, char *channel, char *act); + void (*p_notice)(struct server *, char *channel, char *text); + void (*p_topic)(struct server *, char *channel, char *topic); + void (*p_list_channels)(struct server *, char *arg, int min_users); + void (*p_change_nick)(struct server *, char *new_nick); + void (*p_names)(struct server *, char *channel); + void (*p_ping)(struct server *, char *to, char *timestring); +/* void (*p_set_away)(struct server *);*/ + int (*p_raw)(struct server *, char *raw); + int (*p_cmp)(const char *s1, const char *s2); + + int port; + int sok; /* is equal to sok4 or sok6 (the one we are using) */ + int sok4; /* tcp4 socket */ + int sok6; /* tcp6 socket */ + int proxy_type; + int proxy_sok; /* Additional information for MS Proxy beast */ + int proxy_sok4; + int proxy_sok6; + struct msproxy_state_t msp_state; + int id; /* unique ID number (for plugin API) */ +#ifdef USE_OPENSSL + SSL *ssl; + int ssl_do_connect_tag; +#else + void *ssl; +#endif + int childread; + int childwrite; + int childpid; + int iotag; + int recondelay_tag; /* reconnect delay timeout */ + int joindelay_tag; /* waiting before we send JOIN */ + char hostname[128]; /* real ip number */ + char servername[128]; /* what the server says is its name */ + char password[86]; + char nick[NICKLEN]; + char linebuf[2048]; /* RFC says 512 chars including \r\n */ + char *last_away_reason; + int pos; /* current position in linebuf */ + int nickcount; + int nickservtype; /* 0=/MSG nickserv 1=/NICKSERV 2=/NS */ + + char *chantypes; /* for 005 numeric - free me */ + char *chanmodes; /* for 005 numeric - free me */ + char *nick_prefixes; /* e.g. "*@%+" */ + char *nick_modes; /* e.g. "aohv" */ + char *bad_nick_prefixes; /* for ircd that doesn't give the modes */ + int modes_per_line; /* 6 on undernet, 4 on efnet etc... */ + + void *network; /* points to entry in servlist.c or NULL! */ + + GSList *outbound_queue; + time_t next_send; /* cptr->since in ircu */ + time_t prev_now; /* previous now-time */ + int sendq_len; /* queue size */ + int lag; /* milliseconds */ + + struct session *front_session; /* front-most window/tab */ + struct session *server_session; /* server window/tab */ + + struct server_gui *gui; /* initialized by fe_new_server */ + + unsigned int ctcp_counter; /*flood */ + time_t ctcp_last_time; + + unsigned int msg_counter; /*counts the msg tab opened in a certain time */ + time_t msg_last_time; + + /*time_t connect_time;*/ /* when did it connect? */ + time_t lag_sent; + time_t ping_recv; /* when we last got a ping reply */ + time_t away_time; /* when we were marked away */ + + char *encoding; /* NULL for system */ + char *autojoin; /* list of channels & keys to join */ + + unsigned int motd_skipped:1; + unsigned int connected:1; + unsigned int connecting:1; + unsigned int no_login:1; + unsigned int skip_next_userhost:1;/* used for "get my ip from server" */ + unsigned int skip_next_whois:1; /* hide whois output */ + unsigned int inside_whois:1; + unsigned int doing_dns:1; /* /dns has been done */ + unsigned int end_of_motd:1; /* end of motd reached (logged in) */ + unsigned int sent_quit:1; /* sent a QUIT already? */ + unsigned int use_listargs:1; /* undernet and dalnet need /list >0,<10000 */ + unsigned int is_away:1; + unsigned int reconnect_away:1; /* whether to reconnect in is_away state */ + unsigned int dont_use_proxy:1; /* to proxy or not to proxy */ + unsigned int supports_watch:1; /* supports the WATCH command */ + unsigned int bad_prefix:1; /* gave us a bad PREFIX= 005 number */ + unsigned int have_namesx:1; /* 005 tokens NAMESX and UHNAMES */ + unsigned int have_uhnames:1; + unsigned int have_whox:1; /* have undernet's WHOX features */ + unsigned int have_capab:1; /* supports CAPAB (005 tells us) */ + unsigned int have_idmsg:1; /* freenode's IDENTIFY-MSG */ + unsigned int have_except:1; /* ban exemptions +e */ + unsigned int using_cp1255:1; /* encoding is CP1255/WINDOWS-1255? */ + unsigned int using_irc:1; /* encoding is "IRC" (CP1252/UTF-8 hybrid)? */ + unsigned int use_who:1; /* whether to use WHO command to get dcc_ip */ +#ifdef USE_OPENSSL + unsigned int use_ssl:1; /* is server SSL capable? */ + unsigned int accept_invalid_cert:1;/* ignore result of server's cert. verify */ +#endif +} server; + +typedef int (*cmd_callback) (struct session * sess, char *tbuf, char *word[], + char *word_eol[]); + +struct commands +{ + char *name; + cmd_callback callback; + char needserver; + char needchannel; + gint16 handle_quotes; + char *help; +}; + +struct away_msg +{ + struct server *server; + char nick[NICKLEN]; + char *message; +}; + +/* not just for popups, but used for usercommands, ctcp replies, + userlist buttons etc */ + +struct popup +{ + char *cmd; + char *name; +}; + +/* CL: get a random int in the range [0..n-1]. DON'T use rand() % n, it gives terrible results. */ +#define RAND_INT(n) ((int)(rand() / (RAND_MAX + 1.0) * (n))) + +#if defined(WIN32) && GLIB_CHECK_VERSION(2,4,0) +#define xchat_filename_from_utf8 g_locale_from_utf8 +#define xchat_filename_to_utf8 g_locale_to_utf8 +#else +#define xchat_filename_from_utf8 g_filename_from_utf8 +#define xchat_filename_to_utf8 g_filename_to_utf8 +#endif + +#endif diff --git a/src/common/xchatc.h b/src/common/xchatc.h new file mode 100644 index 00000000..c22e17dd --- /dev/null +++ b/src/common/xchatc.h @@ -0,0 +1,39 @@ +#ifndef XCHAT_C_H +#define XCHAT_C_H + +extern struct xchatprefs prefs; + +extern int xchat_is_quitting; +extern gint arg_skip_plugins; /* command-line args */ +extern gint arg_dont_autoconnect; +extern char *arg_url; +extern char *arg_command; +extern gint arg_existing; + +extern session *current_sess; +extern session *current_tab; + +extern GSList *popup_list; +extern GSList *button_list; +extern GSList *dlgbutton_list; +extern GSList *command_list; +extern GSList *ctcp_list; +extern GSList *replace_list; +extern GSList *sess_list; +extern GSList *dcc_list; +extern GSList *ignore_list; +extern GSList *usermenu_list; +extern GSList *urlhandler_list; +extern GSList *tabmenu_list; + +session * find_channel (server *serv, char *chan); +session * find_dialog (server *serv, char *nick); +session * new_ircwindow (server *serv, char *name, int type, int focus); +int is_session (session * sess); +void session_free (session *killsess); +void lag_check (void); +void xchat_exit (void); +void xchat_exec (const char *cmd); +void xchat_execv (char * const argv[]); + +#endif diff --git a/src/fe-gtk/Makefile.am b/src/fe-gtk/Makefile.am new file mode 100644 index 00000000..7db38096 --- /dev/null +++ b/src/fe-gtk/Makefile.am @@ -0,0 +1,32 @@ +localedir = $(datadir)/locale + +bin_PROGRAMS = xchat + +INCLUDES = $(GUI_CFLAGS) -DG_DISABLE_CAST_CHECKS -DLOCALEDIR=\"$(localedir)\" + +xchat_LDADD = ../common/libxchatcommon.a $(GUI_LIBS) + +EXTRA_DIST = \ + about.h ascii.h banlist.h chanlist.h chanview.h chanview-tabs.c \ + chanview-tree.c custom-list.h editlist.h fe-gtk.h fkeys.h gtkutil.h joind.h \ + maingui.h menu.h mmx_cmod.S mmx_cmod.h notifygui.h palette.h pixmaps.h \ + plugin-tray.h plugingui.c plugingui.h rawlog.h search.h sexy-spell-entry.h \ + textgui.h urlgrab.h userlistgui.h xtext.h + +if USE_MMX +mmx_cmod_S = mmx_cmod.S +endif + +if DO_PLUGIN +plugingui_c = plugingui.c +endif + +if USE_LIBSEXY +sexy_spell_entry_c = sexy-spell-entry.c +endif + +xchat_SOURCES = about.c ascii.c banlist.c chanlist.c chanview.c custom-list.c \ + dccgui.c editlist.c fe-gtk.c fkeys.c gtkutil.c ignoregui.c joind.c menu.c \ + maingui.c $(mmx_cmod_S) notifygui.c palette.c pixmaps.c plugin-tray.c $(plugingui_c) \ + rawlog.c search.c servlistgui.c setup.c $(sexy_spell_entry_c) textgui.c \ + urlgrab.c userlistgui.c xtext.c diff --git a/src/fe-gtk/about.c b/src/fe-gtk/about.c new file mode 100644 index 00000000..60700aec --- /dev/null +++ b/src/fe-gtk/about.c @@ -0,0 +1,161 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "fe-gtk.h" + +#include <gtk/gtkmain.h> +#include <gtk/gtkcontainer.h> +#include <gtk/gtkdialog.h> +#include <gtk/gtkvbox.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkimage.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkbutton.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkwindow.h> + +#ifdef USE_XLIB +#include <gdk/gdkx.h> +#endif + +#include "../common/xchat.h" +#include "../common/util.h" +#include "palette.h" +#include "pixmaps.h" +#include "gtkutil.h" +#include "about.h" + + +#if 0 /*def USE_GNOME*/ + +void +menu_about (GtkWidget * wid, gpointer sess) +{ + char buf[512]; + const gchar *author[] = { "Peter Zelezny <zed@xchat.org>", 0 }; + + (snprintf) (buf, sizeof (buf), + "An IRC Client for UNIX.\n\n" + "This binary was compiled on "__DATE__"\n" + "Using GTK %d.%d.%d X %d\n" + "Running on %s", + gtk_major_version, gtk_minor_version, gtk_micro_version, +#ifdef USE_XLIB + VendorRelease (GDK_DISPLAY ()), get_cpu_str()); +#else + 666, get_cpu_str()); +#endif + + gtk_widget_show (gnome_about_new ("X-Chat", PACKAGE_VERSION, + "(C) 1998-2005 Peter Zelezny", author, buf, 0)); +} + +#else + +static GtkWidget *about = 0; + +static int +about_close (void) +{ + about = 0; + return 0; +} + +void +menu_about (GtkWidget * wid, gpointer sess) +{ + GtkWidget *vbox, *label, *hbox; + char buf[512]; + const char *locale = NULL; + extern GtkWindow *parent_window; /* maingui.c */ + + if (about) + { + gtk_window_present (GTK_WINDOW (about)); + return; + } + + about = gtk_dialog_new (); + gtk_window_set_position (GTK_WINDOW (about), GTK_WIN_POS_CENTER); + gtk_window_set_resizable (GTK_WINDOW (about), FALSE); + gtk_window_set_title (GTK_WINDOW (about), _("About "DISPLAY_NAME)); + if (parent_window) + gtk_window_set_transient_for (GTK_WINDOW (about), parent_window); + g_signal_connect (G_OBJECT (about), "destroy", + G_CALLBACK (about_close), 0); + + vbox = GTK_DIALOG (about)->vbox; + + wid = gtk_image_new_from_pixbuf (pix_xchat); + gtk_container_add (GTK_CONTAINER (vbox), wid); + + label = gtk_label_new (NULL); + gtk_label_set_selectable (GTK_LABEL (label), TRUE); + gtk_container_add (GTK_CONTAINER (vbox), label); + g_get_charset (&locale); + (snprintf) (buf, sizeof (buf), + "<span size=\"x-large\"><b>"DISPLAY_NAME" "PACKAGE_VERSION"</b></span>\n\n" + "%s\n\n" +#ifdef WIN32 + /* leave this message to avoid time wasting bug reports! */ + "This version is unofficial and comes with no support.\n\n" +#endif + "%s\n" + "<b>Charset</b>: %s " +#ifdef WIN32 + "<b>GTK+</b>: %i.%i.%i\n" +#else + "<b>Renderer</b>: %s\n" +#endif + "<b>Compiled</b>: "__DATE__"\n\n" + "<small>\302\251 1998-2010 Peter \305\275elezn\303\275 <zed@xchat.org></small>", + _("A multiplatform IRC Client"), + get_cpu_str(), + locale, +#ifdef WIN32 + gtk_major_version, + gtk_minor_version, + gtk_micro_version +#else +#ifdef USE_XFT + "Xft" +#else + "Pango" +#endif +#endif + ); + gtk_label_set_markup (GTK_LABEL (label), buf); + gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER); + + hbox = gtk_hbox_new (0, 2); + gtk_container_add (GTK_CONTAINER (vbox), hbox); + + wid = gtk_button_new_from_stock (GTK_STOCK_CLOSE); + GTK_WIDGET_SET_FLAGS (GTK_WIDGET (wid), GTK_CAN_DEFAULT); + gtk_box_pack_end (GTK_BOX (GTK_DIALOG (about)->action_area), wid, 0, 0, 0); + gtk_widget_grab_default (wid); + g_signal_connect (G_OBJECT (wid), "clicked", + G_CALLBACK (gtkutil_destroy), about); + + gtk_widget_show_all (about); +} +#endif diff --git a/src/fe-gtk/about.h b/src/fe-gtk/about.h new file mode 100644 index 00000000..2bad159c --- /dev/null +++ b/src/fe-gtk/about.h @@ -0,0 +1 @@ +void menu_about (GtkWidget * wid, gpointer sess); diff --git a/src/fe-gtk/ascii.c b/src/fe-gtk/ascii.c new file mode 100644 index 00000000..f1adbdfc --- /dev/null +++ b/src/fe-gtk/ascii.c @@ -0,0 +1,177 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "fe-gtk.h" + +#include <gtk/gtkeditable.h> +#include <gtk/gtkframe.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkbutton.h> + +#include "../common/xchat.h" +#include "../common/xchatc.h" +#include "gtkutil.h" +#include "ascii.h" +#include "maingui.h" + +static const unsigned char table[]= +{ +/* Line 1 */ '\n', +0xc2,0xa1,0xc2,0xbf,0xc2,0xa2,0xc2,0xa3,0xe2,0x82,0xac,0xc2,0xa5,0xc2,0xa6,0xc2, +0xa7,0xc2,0xa8,0xc2,0xa9,0xc2,0xae,0xc2,0xaa,0xc2,0xab,0xc2,0xbb,0xc2,0xac,0xc2, +0xad,0xc2,0xaf,0xe2,0x99,0xaa, +/* Line 2 */ '\n', +0xc2,0xba,0xc2,0xb9,0xc2,0xb2,0xc2,0xb3,0xc2,0xb4,0xc2,0xb5,0xc3,0x9e,0xc3,0xbe, +0xc2,0xb6,0xc2,0xb7,0xc2,0xb8,0xc2,0xb0,0xc2,0xbc,0xc2,0xbd,0xc2,0xbe,0xc3,0x97, +0xc2,0xb1,0xc3,0xb7, +/* Line 3 */ '\n', +0xc3,0x80,0xc3,0x81,0xc3,0x82,0xc3,0x83,0xc3,0x84,0xc3,0x85,0xc3,0x86,0xc4,0x82, +0xc4,0x84,0x20,0xc3,0x87,0xc4,0x86,0xc4,0x8c,0xc5,0x92,0x20,0xc4,0x8e,0xc4,0x90, +0x20, +/* Line 4 */ '\n', +0xc3,0xa0,0xc3,0xa1,0xc3,0xa2,0xc3,0xa3,0xc3,0xa4,0xc3,0xa5,0xc3,0xa6,0xc4,0x83, +0xc4,0x85,0x20,0xc3,0xa7,0xc4,0x87,0xc4,0x8d,0xc5,0x93,0x20,0xc4,0x8f,0xc4,0x91, +0x20, +/* Line 5 */ '\n', +0xc3,0x88,0xc3,0x89,0xc3,0x8a,0xc3,0x8b,0xc4,0x98,0xc4,0x9a,0x20,0xc4,0x9e,0x20, +0xc3,0x8c,0xc3,0x8d,0xc3,0x8e,0xc3,0x8f,0xc4,0xb0,0x20,0xc4,0xb9,0xc4,0xbd,0xc5, +0x81, +/* Line 6 */ '\n', +0xc3,0xa8,0xc3,0xa9,0xc3,0xaa,0xc3,0xab,0xc4,0x99,0xc4,0x9b,0x20,0xc4,0x9f,0x20, +0xc3,0xac,0xc3,0xad,0xc3,0xae,0xc3,0xaf,0xc4,0xb1,0x20,0xc4,0xba,0xc4,0xbe,0xc5, +0x82, +/* Line 7 */ '\n', +0xc3,0x91,0xc5,0x83,0xc5,0x87,0x20,0xc3,0x92,0xc3,0x93,0xc3,0x94,0xc3,0x95,0xc3, +0x96,0xc3,0x98,0xc5,0x90,0x20,0xc5,0x94,0xc5,0x98,0x20,0xc5,0x9a,0xc5,0x9e,0xc5, +0xa0, +/* Line 8 */ '\n', +0xc3,0xb1,0xc5,0x84,0xc5,0x88,0x20,0xc3,0xb2,0xc3,0xb3,0xc3,0xb4,0xc3,0xb5,0xc3, +0xb6,0xc3,0xb8,0xc5,0x91,0x20,0xc5,0x95,0xc5,0x99,0x20,0xc5,0x9b,0xc5,0x9f,0xc5, +0xa1, +/* Line 9 */ '\n', +0x20,0xc5,0xa2,0xc5,0xa4,0x20,0xc3,0x99,0xc3,0x9a,0xc3,0x9b,0xc5,0xb2,0xc3,0x9c, +0xc5,0xae,0xc5,0xb0,0x20,0xc3,0x9d,0xc3,0x9f,0x20,0xc5,0xb9,0xc5,0xbb,0xc5,0xbd, +/* Line 10 */ '\n', +0x20,0xc5,0xa3,0xc5,0xa5,0x20,0xc3,0xb9,0xc3,0xba,0xc3,0xbb,0xc5,0xb3,0xc3,0xbc, +0xc5,0xaf,0xc5,0xb1,0x20,0xc3,0xbd,0xc3,0xbf,0x20,0xc5,0xba,0xc5,0xbc,0xc5,0xbe, +/* Line 11 */ '\n', +0xd0,0x90,0xd0,0x91,0xd0,0x92,0xd0,0x93,0xd0,0x94,0xd0,0x95,0xd0,0x81,0xd0,0x96, +0xd0,0x97,0xd0,0x98,0xd0,0x99,0xd0,0x9a,0xd0,0x9b,0xd0,0x9c,0xd0,0x9d,0xd0,0x9e, +0xd0,0x9f,0xd0,0xa0, +/* Line 12 */ '\n', +0xd0,0xb0,0xd0,0xb1,0xd0,0xb2,0xd0,0xb3,0xd0,0xb4,0xd0,0xb5,0xd1,0x91,0xd0,0xb6, +0xd0,0xb7,0xd0,0xb8,0xd0,0xb9,0xd0,0xba,0xd0,0xbb,0xd0,0xbc,0xd0,0xbd,0xd0,0xbe, +0xd0,0xbf,0xd1,0x80, +/* Line 13 */ '\n', +0xd0,0xa1,0xd0,0xa2,0xd0,0xa3,0xd0,0xa4,0xd0,0xa5,0xd0,0xa6,0xd0,0xa7,0xd0,0xa8, +0xd0,0xa9,0xd0,0xaa,0xd0,0xab,0xd0,0xac,0xd0,0xad,0xd0,0xae,0xd0,0xaf, +/* Line 14 */ '\n', +0xd1,0x81,0xd1,0x82,0xd1,0x83,0xd1,0x84,0xd1,0x85,0xd1,0x86,0xd1,0x87,0xd1,0x88, +0xd1,0x89,0xd1,0x8a,0xd1,0x8b,0xd1,0x8c,0xd1,0x8d,0xd1,0x8e,0xd1,0x8f,0 +}; + + +static gboolean +ascii_enter (GtkWidget * wid, GdkEventCrossing *event, GtkWidget *label) +{ + char buf[64]; + const char *text; + gunichar u; + + text = gtk_button_get_label (GTK_BUTTON (wid)); + u = g_utf8_get_char (text); + sprintf (buf, "%s U+%04X", text, u); + gtk_label_set_text (GTK_LABEL (label), buf); + + return FALSE; +} + +static void +ascii_click (GtkWidget * wid, gpointer userdata) +{ + int tmp_pos; + const char *text; + + if (current_sess) + { + text = gtk_button_get_label (GTK_BUTTON (wid)); + wid = current_sess->gui->input_box; + tmp_pos = SPELL_ENTRY_GET_POS (wid); + SPELL_ENTRY_INSERT (wid, text, -1, &tmp_pos); + SPELL_ENTRY_SET_POS (wid, tmp_pos); + } +} + +void +ascii_open (void) +{ + int i, len; + const unsigned char *table_pos; + char name[8]; + GtkWidget *frame, *label, *but, *hbox = NULL, *vbox, *win; + + win = mg_create_generic_tab ("charmap", _("Character Chart"), TRUE, TRUE, + NULL, NULL, 0, 0, &vbox, NULL); + gtk_container_set_border_width (GTK_CONTAINER (win), 5); + + label = gtk_label_new (NULL); + + table_pos = table; + i = 0; + while (table_pos[0] != 0) + { + if (table_pos[0] == '\n' || i == 0) + { + table_pos++; + hbox = gtk_hbox_new (0, 0); + gtk_container_add (GTK_CONTAINER (vbox), hbox); + gtk_widget_show (hbox); + i++; + continue; + } + + i++; + len = g_utf8_skip[table_pos[0]]; + memcpy (name, table_pos, len); + name[len] = 0; + + but = gtk_button_new_with_label (name); + gtk_widget_set_size_request (but, 28, -1); + g_signal_connect (G_OBJECT (but), "clicked", + G_CALLBACK (ascii_click), NULL); + g_signal_connect (G_OBJECT (but), "enter_notify_event", + G_CALLBACK (ascii_enter), label); + gtk_box_pack_start (GTK_BOX (hbox), but, 0, 0, 0); + gtk_widget_show (but); + + table_pos += len; + } + + frame = gtk_frame_new (""); + gtk_container_add (GTK_CONTAINER (hbox), frame); + gtk_container_add (GTK_CONTAINER (frame), label); + gtk_widget_show (label); + gtk_widget_show (frame); + + gtk_widget_show (win); +} diff --git a/src/fe-gtk/ascii.h b/src/fe-gtk/ascii.h new file mode 100644 index 00000000..afd3bd4f --- /dev/null +++ b/src/fe-gtk/ascii.h @@ -0,0 +1 @@ +void ascii_open (void); diff --git a/src/fe-gtk/banlist.c b/src/fe-gtk/banlist.c new file mode 100644 index 00000000..afaa7eb4 --- /dev/null +++ b/src/fe-gtk/banlist.c @@ -0,0 +1,418 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <time.h> + +#include "fe-gtk.h" + +#include <gtk/gtkhbox.h> +#include <gtk/gtkvbox.h> +#include <gtk/gtkhbbox.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkliststore.h> +#include <gtk/gtkmessagedialog.h> +#include <gtk/gtktreeview.h> +#include <gtk/gtktreeselection.h> + +#include "../common/xchat.h" +#include "../common/fe.h" +#include "../common/modes.h" +#include "../common/outbound.h" +#include "../common/xchatc.h" +#include "gtkutil.h" +#include "maingui.h" +#include "banlist.h" + +/* model for the banlist tree */ +enum +{ + MASK_COLUMN, + FROM_COLUMN, + DATE_COLUMN, + N_COLUMNS +}; + +static GtkTreeView * +get_view (struct session *sess) +{ + return GTK_TREE_VIEW (sess->res->banlist_treeview); +} + +static GtkListStore * +get_store (struct session *sess) +{ + return GTK_LIST_STORE (gtk_tree_view_get_model (get_view (sess))); +} + +static gboolean +supports_exempt (server *serv) +{ + char *cm = serv->chanmodes; + + if (serv->have_except) + return TRUE; + + if (!cm) + return FALSE; + + while (*cm) + { + if (*cm == ',') + break; + if (*cm == 'e') + return TRUE; + cm++; + } + + return FALSE; +} + +void +fe_add_ban_list (struct session *sess, char *mask, char *who, char *when, int is_exempt) +{ + GtkListStore *store; + GtkTreeIter iter; + char buf[512]; + + store = get_store (sess); + gtk_list_store_append (store, &iter); + + if (is_exempt) + { + snprintf (buf, sizeof (buf), "(EX) %s", mask); + gtk_list_store_set (store, &iter, 0, buf, 1, who, 2, when, -1); + } else + { + gtk_list_store_set (store, &iter, 0, mask, 1, who, 2, when, -1); + } +} + +void +fe_ban_list_end (struct session *sess, int is_exemption) +{ + gtk_widget_set_sensitive (sess->res->banlist_butRefresh, TRUE); +} + +/** + * * Performs the actual refresh operations. + * */ +static void +banlist_do_refresh (struct session *sess) +{ + char tbuf[256]; + if (sess->server->connected) + { + GtkListStore *store; + + gtk_widget_set_sensitive (sess->res->banlist_butRefresh, FALSE); + + snprintf (tbuf, sizeof tbuf, "XChat: Ban List (%s, %s)", + sess->channel, sess->server->servername); + mg_set_title (sess->res->banlist_window, tbuf); + + store = get_store (sess); + gtk_list_store_clear (store); + + handle_command (sess, "ban", FALSE); +#ifdef WIN32 + if (0) +#else + if (supports_exempt (sess->server)) +#endif + { + snprintf (tbuf, sizeof (tbuf), "quote mode %s +e", sess->channel); + handle_command (sess, tbuf, FALSE); + } + + } else + { + fe_message (_("Not connected."), FE_MSG_ERROR); + } +} + +static void +banlist_refresh (GtkWidget * wid, struct session *sess) +{ + /* JG NOTE: Didn't see actual use of wid here, so just forwarding + * * this to chanlist_do_refresh because I use it without any widget + * * param in chanlist_build_gui_list when the user presses enter + * * or apply for the first time if the list has not yet been + * * received. + * */ + banlist_do_refresh (sess); +} + +static int +banlist_unban_inner (gpointer none, struct session *sess, int do_exempts) +{ + GtkTreeModel *model; + GtkTreeSelection *sel; + GtkTreeIter iter; + char tbuf[2048]; + char **masks, *tmp, *space; + int num_sel, i; + + /* grab the list of selected items */ + model = GTK_TREE_MODEL (get_store (sess)); + sel = gtk_tree_view_get_selection (get_view (sess)); + num_sel = 0; + if (gtk_tree_model_get_iter_first (model, &iter)) + { + do + { + if (gtk_tree_selection_iter_is_selected (sel, &iter)) + num_sel++; + } + while (gtk_tree_model_iter_next (model, &iter)); + } + + if (num_sel < 1) + return 0; + + /* create an array of all the masks */ + masks = calloc (1, num_sel * sizeof (char *)); + + i = 0; + gtk_tree_model_get_iter_first (model, &iter); + do + { + if (gtk_tree_selection_iter_is_selected (sel, &iter)) + { + gtk_tree_model_get (model, &iter, MASK_COLUMN, &masks[i], -1); + space = strchr (masks[i], ' '); + + if (do_exempts) + { + if (space) + { + /* remove the "(EX) " */ + tmp = masks[i]; + masks[i] = g_strdup (space + 1); + g_free (tmp); + i++; + } + } else + { + if (!space) + i++; + } + } + } + while (gtk_tree_model_iter_next (model, &iter)); + + /* and send to server */ + if (do_exempts) + send_channel_modes (sess, tbuf, masks, 0, i, '-', 'e', 0); + else + send_channel_modes (sess, tbuf, masks, 0, i, '-', 'b', 0); + + /* now free everything, and refresh banlist */ + for (i=0; i < num_sel; i++) + g_free (masks[i]); + free (masks); + + return num_sel; +} + +static void +banlist_unban (GtkWidget * wid, struct session *sess) +{ + int num = 0; + + num += banlist_unban_inner (wid, sess, FALSE); + num += banlist_unban_inner (wid, sess, TRUE); + + if (num < 1) + { + fe_message (_("You must select some bans."), FE_MSG_ERROR); + return; + } + + banlist_do_refresh (sess); +} + +static void +banlist_clear_cb (GtkDialog *dialog, gint response, gpointer sess) +{ + GtkTreeSelection *sel; + + gtk_widget_destroy (GTK_WIDGET (dialog)); + + if (response == GTK_RESPONSE_OK) + { + sel = gtk_tree_view_get_selection (get_view (sess)); + gtk_tree_selection_select_all (sel); + banlist_unban (NULL, sess); + } +} + +static void +banlist_clear (GtkWidget * wid, struct session *sess) +{ + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (NULL, 0, + GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL, + _("Are you sure you want to remove all bans in %s?"), sess->channel); + g_signal_connect (G_OBJECT (dialog), "response", + G_CALLBACK (banlist_clear_cb), sess); + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + gtk_widget_show (dialog); +} + +static void +banlist_add_selected_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) +{ + GSList **lp = data; + GSList *list = NULL; + GtkTreeIter *copy; + + if (!lp) return; + list = *lp; + copy = g_malloc (sizeof (GtkTreeIter)); + g_return_if_fail (copy != NULL); + *copy = *iter; + + list = g_slist_append (list, copy); + *(GSList **)data = list; +} + +static void +banlist_crop (GtkWidget * wid, struct session *sess) +{ + GtkTreeSelection *select; + GSList *list = NULL, *node; + int num_sel; + + /* remember which bans are selected */ + select = gtk_tree_view_get_selection (get_view (sess)); + /* gtk_tree_selected_get_selected_rows() isn't present in gtk 2.0.x */ + gtk_tree_selection_selected_foreach (select, banlist_add_selected_cb, + &list); + + num_sel = g_slist_length (list); + /* select all, then unselect those that we remembered */ + if (num_sel) + { + gtk_tree_selection_select_all (select); + + for (node = list; node; node = node->next) + gtk_tree_selection_unselect_iter (select, node->data); + + g_slist_foreach (list, (GFunc)g_free, NULL); + g_slist_free (list); + + banlist_unban (NULL, sess); + } else + fe_message (_("You must select some bans."), FE_MSG_ERROR); +} + +static GtkWidget * +banlist_treeview_new (GtkWidget *box) +{ + GtkListStore *store; + GtkWidget *view; + GtkTreeSelection *select; + GtkTreeViewColumn *col; + + store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_STRING); + g_return_val_if_fail (store != NULL, NULL); + view = gtkutil_treeview_new (box, GTK_TREE_MODEL (store), NULL, + MASK_COLUMN, _("Mask"), + FROM_COLUMN, _("From"), + DATE_COLUMN, _("Date"), -1); + + col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), MASK_COLUMN); + gtk_tree_view_column_set_alignment (col, 0.5); + gtk_tree_view_column_set_min_width (col, 300); + gtk_tree_view_column_set_sort_column_id (col, MASK_COLUMN); + + col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), FROM_COLUMN); + gtk_tree_view_column_set_alignment (col, 0.5); + gtk_tree_view_column_set_sort_column_id (col, FROM_COLUMN); + + col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), DATE_COLUMN); + gtk_tree_view_column_set_alignment (col, 0.5); + + select = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); + gtk_tree_selection_set_mode (select, GTK_SELECTION_MULTIPLE); + + gtk_widget_show (view); + return view; +} + +static void +banlist_closegui (GtkWidget *wid, session *sess) +{ + if (is_session (sess)) + sess->res->banlist_window = 0; +} + +void +banlist_opengui (struct session *sess) +{ + GtkWidget *vbox1; + GtkWidget *bbox; + char tbuf[256]; + + if (sess->res->banlist_window) + { + mg_bring_tofront (sess->res->banlist_window); + return; + } + + if (sess->type != SESS_CHANNEL) + { + fe_message (_("You can only open the Ban List window while in a channel tab."), FE_MSG_ERROR); + return; + } + + snprintf (tbuf, sizeof tbuf, _("XChat: Ban List (%s)"), + sess->server->servername); + + sess->res->banlist_window = mg_create_generic_tab ("BanList", tbuf, FALSE, + TRUE, banlist_closegui, sess, 550, 200, &vbox1, sess->server); + + /* create banlist view */ + sess->res->banlist_treeview = banlist_treeview_new (vbox1); + + bbox = gtk_hbutton_box_new (); + gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD); + gtk_container_set_border_width (GTK_CONTAINER (bbox), 5); + gtk_box_pack_end (GTK_BOX (vbox1), bbox, 0, 0, 0); + gtk_widget_show (bbox); + + gtkutil_button (bbox, GTK_STOCK_REMOVE, 0, banlist_unban, sess, + _("Remove")); + gtkutil_button (bbox, GTK_STOCK_REMOVE, 0, banlist_crop, sess, + _("Crop")); + gtkutil_button (bbox, GTK_STOCK_CLEAR, 0, banlist_clear, sess, + _("Clear")); + + sess->res->banlist_butRefresh = gtkutil_button (bbox, GTK_STOCK_REFRESH, 0, banlist_refresh, sess, _("Refresh")); + + banlist_do_refresh (sess); + + gtk_widget_show (sess->res->banlist_window); +} diff --git a/src/fe-gtk/banlist.h b/src/fe-gtk/banlist.h new file mode 100644 index 00000000..7ceccd00 --- /dev/null +++ b/src/fe-gtk/banlist.h @@ -0,0 +1 @@ +void banlist_opengui (session *sess); diff --git a/src/fe-gtk/chanlist.c b/src/fe-gtk/chanlist.c new file mode 100644 index 00000000..2f65b518 --- /dev/null +++ b/src/fe-gtk/chanlist.c @@ -0,0 +1,943 @@ +/* X-Chat + * Copyright (C) 1998-2006 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <time.h> + +#include "fe-gtk.h" + +#include <gtk/gtkalignment.h> +#include <gtk/gtkcellrenderertext.h> +#include <gtk/gtkcheckbutton.h> +#include <gtk/gtkcombobox.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkliststore.h> +#include <gtk/gtkscrolledwindow.h> +#include <gtk/gtkspinbutton.h> +#include <gtk/gtkstock.h> +#include <gtk/gtktable.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtkvbox.h> +#include <gtk/gtkvseparator.h> +#include <gdk/gdkkeysyms.h> + +#include "../common/xchat.h" +#include "../common/xchatc.h" +#include "../common/cfgfiles.h" +#include "../common/outbound.h" +#include "../common/util.h" +#include "../common/fe.h" +#include "../common/server.h" +#include "gtkutil.h" +#include "maingui.h" + + +#include "custom-list.h" + + +enum +{ + COL_CHANNEL, + COL_USERS, + COL_TOPIC, + N_COLUMNS +}; + +#ifndef CUSTOM_LIST +typedef struct /* this is now in custom-list.h */ +{ + char *topic; + char *collation_key; + guint32 pos; + guint32 users; + /* channel string lives beyond "users" */ +#define GET_CHAN(row) (((char *)row)+sizeof(chanlistrow)) +} +chanlistrow; +#endif + +#define GET_MODEL(xserv) (gtk_tree_view_get_model(GTK_TREE_VIEW(xserv->gui->chanlist_list))) + + +static gboolean +chanlist_match (server *serv, const char *str) +{ + switch (serv->gui->chanlist_search_type) + { + case 1: + return match (GTK_ENTRY (serv->gui->chanlist_wild)->text, str); +#ifndef WIN32 + case 2: + if (!serv->gui->have_regex) + return 0; + /* regex returns 0 if it's a match: */ + return !regexec (&serv->gui->chanlist_match_regex, str, 1, NULL, REG_NOTBOL); +#endif + default: /* case 0: */ + return nocasestrstr (str, GTK_ENTRY (serv->gui->chanlist_wild)->text) ? 1 : 0; + } +} + +/** + * Updates the caption to reflect the number of users and channels + */ +static void +chanlist_update_caption (server *serv) +{ + gchar tbuf[256]; + + snprintf (tbuf, sizeof tbuf, + _("Displaying %d/%d users on %d/%d channels."), + serv->gui->chanlist_users_shown_count, + serv->gui->chanlist_users_found_count, + serv->gui->chanlist_channels_shown_count, + serv->gui->chanlist_channels_found_count); + + gtk_label_set_text (GTK_LABEL (serv->gui->chanlist_label), tbuf); + serv->gui->chanlist_caption_is_stale = FALSE; +} + +static void +chanlist_update_buttons (server *serv) +{ + if (serv->gui->chanlist_channels_shown_count) + { + gtk_widget_set_sensitive (serv->gui->chanlist_join, TRUE); + gtk_widget_set_sensitive (serv->gui->chanlist_savelist, TRUE); + } + else + { + gtk_widget_set_sensitive (serv->gui->chanlist_join, FALSE); + gtk_widget_set_sensitive (serv->gui->chanlist_savelist, FALSE); + } +} + +static void +chanlist_reset_counters (server *serv) +{ + serv->gui->chanlist_users_found_count = 0; + serv->gui->chanlist_users_shown_count = 0; + serv->gui->chanlist_channels_found_count = 0; + serv->gui->chanlist_channels_shown_count = 0; + + chanlist_update_caption (serv); + chanlist_update_buttons (serv); +} + +/* free up our entire linked list and all the nodes */ + +static void +chanlist_data_free (server *serv) +{ + GSList *rows; + chanlistrow *data; + + if (serv->gui->chanlist_data_stored_rows) + { + for (rows = serv->gui->chanlist_data_stored_rows; rows != NULL; + rows = rows->next) + { + data = rows->data; + g_free (data->topic); + g_free (data->collation_key); + free (data); + } + + g_slist_free (serv->gui->chanlist_data_stored_rows); + serv->gui->chanlist_data_stored_rows = NULL; + } + + g_slist_free (serv->gui->chanlist_pending_rows); + serv->gui->chanlist_pending_rows = NULL; +} + +/* add any rows we received from the server in the last 0.25s to the GUI */ + +static void +chanlist_flush_pending (server *serv) +{ + GSList *list = serv->gui->chanlist_pending_rows; + GtkTreeModel *model; + chanlistrow *row; + + if (!list) + { + if (serv->gui->chanlist_caption_is_stale) + chanlist_update_caption (serv); + return; + } + model = GET_MODEL (serv); + + while (list) + { + row = list->data; + custom_list_append (CUSTOM_LIST (model), row); + list = list->next; + } + + g_slist_free (serv->gui->chanlist_pending_rows); + serv->gui->chanlist_pending_rows = NULL; + chanlist_update_caption (serv); +} + +static gboolean +chanlist_timeout (server *serv) +{ + chanlist_flush_pending (serv); + return TRUE; +} + +/** + * Places a data row into the gui GtkTreeView, if and only if the row matches + * the user and regex/search requirements. + */ +static void +chanlist_place_row_in_gui (server *serv, chanlistrow *next_row, gboolean force) +{ + GtkTreeModel *model; + + /* First, update the 'found' counter values */ + serv->gui->chanlist_users_found_count += next_row->users; + serv->gui->chanlist_channels_found_count++; + + if (serv->gui->chanlist_channels_shown_count == 1) + /* join & save buttons become live */ + chanlist_update_buttons (serv); + + if (next_row->users < serv->gui->chanlist_minusers) + { + serv->gui->chanlist_caption_is_stale = TRUE; + return; + } + + if (next_row->users > serv->gui->chanlist_maxusers + && serv->gui->chanlist_maxusers > 0) + { + serv->gui->chanlist_caption_is_stale = TRUE; + return; + } + + if (GTK_ENTRY (serv->gui->chanlist_wild)->text[0]) + { + /* Check what the user wants to match. If both buttons or _neither_ + * button is checked, look for match in both by default. + */ + if (serv->gui->chanlist_match_wants_channel == + serv->gui->chanlist_match_wants_topic) + { + if (!chanlist_match (serv, GET_CHAN (next_row)) + && !chanlist_match (serv, next_row->topic)) + { + serv->gui->chanlist_caption_is_stale = TRUE; + return; + } + } + + else if (serv->gui->chanlist_match_wants_channel) + { + if (!chanlist_match (serv, GET_CHAN (next_row))) + { + serv->gui->chanlist_caption_is_stale = TRUE; + return; + } + } + + else if (serv->gui->chanlist_match_wants_topic) + { + if (!chanlist_match (serv, next_row->topic)) + { + serv->gui->chanlist_caption_is_stale = TRUE; + return; + } + } + } + + if (force || serv->gui->chanlist_channels_shown_count < 20) + { + model = GET_MODEL (serv); + /* makes it appear fast :) */ + custom_list_append (CUSTOM_LIST (model), next_row); + chanlist_update_caption (serv); + } + else + /* add it to GUI at the next update interval */ + serv->gui->chanlist_pending_rows = g_slist_prepend (serv->gui->chanlist_pending_rows, next_row); + + /* Update the 'shown' counter values */ + serv->gui->chanlist_users_shown_count += next_row->users; + serv->gui->chanlist_channels_shown_count++; +} + +/* Performs the LIST download from the IRC server. */ + +static void +chanlist_do_refresh (server *serv) +{ + if (serv->gui->chanlist_flash_tag) + { + g_source_remove (serv->gui->chanlist_flash_tag); + serv->gui->chanlist_flash_tag = 0; + } + + if (!serv->connected) + { + fe_message (_("Not connected."), FE_MSG_ERROR); + return; + } + + custom_list_clear ((CustomList *)GET_MODEL (serv)); + gtk_widget_set_sensitive (serv->gui->chanlist_refresh, FALSE); + + chanlist_data_free (serv); + chanlist_reset_counters (serv); + + /* can we request a list with minusers arg? */ + if (serv->use_listargs) + { + /* yes - it will download faster */ + serv->p_list_channels (serv, "", serv->gui->chanlist_minusers); + /* don't allow the spin button below this value from now on */ + serv->gui->chanlist_minusers_downloaded = serv->gui->chanlist_minusers; + } + else + { + /* download all, filter minusers locally only */ + serv->p_list_channels (serv, "", 1); + serv->gui->chanlist_minusers_downloaded = 1; + } + +/* gtk_spin_button_set_range ((GtkSpinButton *)serv->gui->chanlist_min_spin, + serv->gui->chanlist_minusers_downloaded, 999999);*/ +} + +static void +chanlist_refresh (GtkWidget * wid, server *serv) +{ + chanlist_do_refresh (serv); +} + +/** + * Fills the gui GtkTreeView with stored items from the GSList. + */ +static void +chanlist_build_gui_list (server *serv) +{ + GSList *rows; + + /* first check if the list is present */ + if (serv->gui->chanlist_data_stored_rows == NULL) + { + /* start a download */ + chanlist_do_refresh (serv); + return; + } + + custom_list_clear ((CustomList *)GET_MODEL (serv)); + + /* discard pending rows FIXME: free the structs? */ + g_slist_free (serv->gui->chanlist_pending_rows); + serv->gui->chanlist_pending_rows = NULL; + + /* Reset the counters */ + chanlist_reset_counters (serv); + + /* Refill the list */ + for (rows = serv->gui->chanlist_data_stored_rows; rows != NULL; + rows = rows->next) + { + chanlist_place_row_in_gui (serv, rows->data, TRUE); + } + + custom_list_resort ((CustomList *)GET_MODEL (serv)); +} + +/** + * Accepts incoming channel data from inbound.c, allocates new space for a + * chanlistrow, adds it to our linked list and calls chanlist_place_row_in_gui. + */ +void +fe_add_chan_list (server *serv, char *chan, char *users, char *topic) +{ + chanlistrow *next_row; + int len = strlen (chan) + 1; + + /* we allocate the struct and channel string in one go */ + next_row = malloc (sizeof (chanlistrow) + len); + memcpy (((char *)next_row) + sizeof (chanlistrow), chan, len); + next_row->topic = strip_color (topic, -1, STRIP_ALL); + next_row->collation_key = g_utf8_collate_key (chan, len-1); + if (!(next_row->collation_key)) + next_row->collation_key = g_strdup (chan); + next_row->users = atoi (users); + + /* add this row to the data */ + serv->gui->chanlist_data_stored_rows = + g_slist_prepend (serv->gui->chanlist_data_stored_rows, next_row); + + /* _possibly_ add the row to the gui */ + chanlist_place_row_in_gui (serv, next_row, FALSE); +} + +void +fe_chan_list_end (server *serv) +{ + /* download complete */ + chanlist_flush_pending (serv); + gtk_widget_set_sensitive (serv->gui->chanlist_refresh, TRUE); + custom_list_resort ((CustomList *)GET_MODEL (serv)); +} + +static void +chanlist_search_pressed (GtkButton * button, server *serv) +{ + chanlist_build_gui_list (serv); +} + +static void +chanlist_find_cb (GtkWidget * wid, server *serv) +{ +#ifndef WIN32 + /* recompile the regular expression. */ + if (serv->gui->have_regex) + { + serv->gui->have_regex = 0; + regfree (&serv->gui->chanlist_match_regex); + } + + if (regcomp (&serv->gui->chanlist_match_regex, GTK_ENTRY (wid)->text, + REG_ICASE | REG_EXTENDED | REG_NOSUB) == 0) + serv->gui->have_regex = 1; +#endif +} + +static void +chanlist_match_channel_button_toggled (GtkWidget * wid, server *serv) +{ + serv->gui->chanlist_match_wants_channel = GTK_TOGGLE_BUTTON (wid)->active; +} + +static void +chanlist_match_topic_button_toggled (GtkWidget * wid, server *serv) +{ + serv->gui->chanlist_match_wants_topic = GTK_TOGGLE_BUTTON (wid)->active; +} + +static char * +chanlist_get_selected (server *serv, gboolean get_topic) +{ + char *chan; + GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (serv->gui->chanlist_list)); + GtkTreeModel *model; + GtkTreeIter iter; + + if (!gtk_tree_selection_get_selected (sel, &model, &iter)) + return NULL; + + gtk_tree_model_get (model, &iter, get_topic ? COL_TOPIC : COL_CHANNEL, &chan, -1); + return chan; +} + +static void +chanlist_join (GtkWidget * wid, server *serv) +{ + char tbuf[CHANLEN + 6]; + char *chan = chanlist_get_selected (serv, FALSE); + if (chan) + { + if (serv->connected && (strcmp (chan, "*") != 0)) + { + snprintf (tbuf, sizeof (tbuf), "join %s", chan); + handle_command (serv->server_session, tbuf, FALSE); + } else + gdk_beep (); + g_free (chan); + } +} + +static void +chanlist_filereq_done (server *serv, char *file) +{ + time_t t = time (0); + int fh, users; + char *chan, *topic; + char buf[1024]; + GtkTreeModel *model = GET_MODEL (serv); + GtkTreeIter iter; + + if (!file) + return; + + fh = xchat_open_file (file, O_TRUNC | O_WRONLY | O_CREAT, 0600, + XOF_DOMODE | XOF_FULLPATH); + if (fh == -1) + return; + + snprintf (buf, sizeof buf, "XChat Channel List: %s - %s\n", + serv->servername, ctime (&t)); + write (fh, buf, strlen (buf)); + + if (gtk_tree_model_get_iter_first (model, &iter)) + { + do + { + gtk_tree_model_get (model, &iter, + COL_CHANNEL, &chan, + COL_USERS, &users, + COL_TOPIC, &topic, -1); + snprintf (buf, sizeof buf, "%-16s %-5d%s\n", chan, users, topic); + g_free (chan); + g_free (topic); + write (fh, buf, strlen (buf)); + } + while (gtk_tree_model_iter_next (model, &iter)); + } + + close (fh); +} + +static void +chanlist_save (GtkWidget * wid, server *serv) +{ + GtkTreeIter iter; + GtkTreeModel *model = GET_MODEL (serv); + + if (gtk_tree_model_get_iter_first (model, &iter)) + gtkutil_file_req (_("Select an output filename"), chanlist_filereq_done, + serv, NULL, FRF_WRITE); +} + +static gboolean +chanlist_flash (server *serv) +{ + if (serv->gui->chanlist_refresh->state != GTK_STATE_ACTIVE) + gtk_widget_set_state (serv->gui->chanlist_refresh, GTK_STATE_ACTIVE); + else + gtk_widget_set_state (serv->gui->chanlist_refresh, GTK_STATE_PRELIGHT); + + return TRUE; +} + +static void +chanlist_minusers (GtkSpinButton *wid, server *serv) +{ + serv->gui->chanlist_minusers = gtk_spin_button_get_value_as_int (wid); + + if (serv->gui->chanlist_minusers < serv->gui->chanlist_minusers_downloaded) + { + if (serv->gui->chanlist_flash_tag == 0) + serv->gui->chanlist_flash_tag = g_timeout_add (500, (GSourceFunc)chanlist_flash, serv); + } + else + { + if (serv->gui->chanlist_flash_tag) + { + g_source_remove (serv->gui->chanlist_flash_tag); + serv->gui->chanlist_flash_tag = 0; + } + } +} + +static void +chanlist_maxusers (GtkSpinButton *wid, server *serv) +{ + serv->gui->chanlist_maxusers = gtk_spin_button_get_value_as_int (wid); +} + +static void +chanlist_dclick_cb (GtkTreeView *view, GtkTreePath *path, + GtkTreeViewColumn *column, gpointer data) +{ + chanlist_join (0, (server *) data); /* double clicked a row */ +} + +static void +chanlist_menu_destroy (GtkWidget *menu, gpointer userdata) +{ + gtk_widget_destroy (menu); + g_object_unref (menu); +} + +static void +chanlist_copychannel (GtkWidget *item, server *serv) +{ + char *chan = chanlist_get_selected (serv, FALSE); + if (chan) + { + gtkutil_copy_to_clipboard (item, NULL, chan); + g_free (chan); + } +} + +static void +chanlist_copytopic (GtkWidget *item, server *serv) +{ + char *topic = chanlist_get_selected (serv, TRUE); + if (topic) + { + gtkutil_copy_to_clipboard (item, NULL, topic); + g_free (topic); + } +} + +static gboolean +chanlist_button_cb (GtkTreeView *tree, GdkEventButton *event, server *serv) +{ + GtkWidget *menu; + GtkTreeSelection *sel; + GtkTreePath *path; + char *chan; + + if (event->button != 3) + return FALSE; + + if (!gtk_tree_view_get_path_at_pos (tree, event->x, event->y, &path, 0, 0, 0)) + return FALSE; + + /* select what they right-clicked on */ + sel = gtk_tree_view_get_selection (tree); + gtk_tree_selection_unselect_all (sel); + gtk_tree_selection_select_path (sel, path); + gtk_tree_path_free (path); + + menu = gtk_menu_new (); + if (event->window) + gtk_menu_set_screen (GTK_MENU (menu), gdk_drawable_get_screen (event->window)); + g_object_ref (menu); + g_object_ref_sink (menu); + g_object_unref (menu); + g_signal_connect (G_OBJECT (menu), "selection-done", + G_CALLBACK (chanlist_menu_destroy), NULL); + mg_create_icon_item (_("_Join Channel"), GTK_STOCK_JUMP_TO, menu, + chanlist_join, serv); + mg_create_icon_item (_("_Copy Channel Name"), GTK_STOCK_COPY, menu, + chanlist_copychannel, serv); + mg_create_icon_item (_("Copy _Topic Text"), GTK_STOCK_COPY, menu, + chanlist_copytopic, serv); + + chan = chanlist_get_selected (serv, FALSE); + menu_addfavoritemenu (serv, menu, chan); + g_free (chan); + + gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 0, event->time); + + return TRUE; +} + +static void +chanlist_destroy_widget (GtkWidget *wid, server *serv) +{ + custom_list_clear ((CustomList *)GET_MODEL (serv)); + chanlist_data_free (serv); + + if (serv->gui->chanlist_flash_tag) + { + g_source_remove (serv->gui->chanlist_flash_tag); + serv->gui->chanlist_flash_tag = 0; + } + + if (serv->gui->chanlist_tag) + { + g_source_remove (serv->gui->chanlist_tag); + serv->gui->chanlist_tag = 0; + } + +#ifndef WIN32 + if (serv->gui->have_regex) + { + regfree (&serv->gui->chanlist_match_regex); + serv->gui->have_regex = 0; + } +#endif +} + +static void +chanlist_closegui (GtkWidget *wid, server *serv) +{ + if (is_server (serv)) + serv->gui->chanlist_window = NULL; +} + +static void +chanlist_add_column (GtkWidget *tree, int textcol, int size, char *title, gboolean right_justified) +{ + GtkCellRenderer *renderer; + GtkTreeViewColumn *col; + + renderer = gtk_cell_renderer_text_new (); + if (right_justified) + g_object_set (G_OBJECT (renderer), "xalign", (gfloat) 1.0, NULL); + g_object_set (G_OBJECT (renderer), "ypad", (gint) 0, NULL); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree), -1, title, + renderer, "text", textcol, NULL); + gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer), 1); + + col = gtk_tree_view_get_column (GTK_TREE_VIEW (tree), textcol); + gtk_tree_view_column_set_sort_column_id (col, textcol); + gtk_tree_view_column_set_resizable (col, TRUE); + if (textcol != COL_TOPIC) + { + gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_FIXED); + gtk_tree_view_column_set_fixed_width (col, size); + } +} + +static void +chanlist_combo_cb (GtkWidget *combo, server *serv) +{ + serv->gui->chanlist_search_type = gtk_combo_box_get_active (GTK_COMBO_BOX (combo)); +} + +void +chanlist_opengui (server *serv, int do_refresh) +{ + GtkWidget *vbox, *hbox, *table, *wid, *view; + char tbuf[256]; + GtkListStore *store; + + if (serv->gui->chanlist_window) + { + mg_bring_tofront (serv->gui->chanlist_window); + return; + } + + snprintf (tbuf, sizeof tbuf, _("XChat: Channel List (%s)"), + server_get_network (serv, TRUE)); + + serv->gui->chanlist_pending_rows = NULL; + serv->gui->chanlist_tag = 0; + serv->gui->chanlist_flash_tag = 0; + serv->gui->chanlist_data_stored_rows = NULL; + + if (!serv->gui->chanlist_minusers) + serv->gui->chanlist_minusers = 5; + + if (!serv->gui->chanlist_maxusers) + serv->gui->chanlist_maxusers = 9999; + + serv->gui->chanlist_window = + mg_create_generic_tab ("ChanList", tbuf, FALSE, TRUE, chanlist_closegui, + serv, 640, 480, &vbox, serv); + + gtk_container_set_border_width (GTK_CONTAINER (vbox), 6); + gtk_box_set_spacing (GTK_BOX (vbox), 12); + + /* make a label to store the user/channel info */ + wid = gtk_label_new (NULL); + gtk_box_pack_start (GTK_BOX (vbox), wid, 0, 0, 0); + gtk_widget_show (wid); + serv->gui->chanlist_label = wid; + + /* ============================================================= */ + + store = (GtkListStore *) custom_list_new(); + view = gtkutil_treeview_new (vbox, GTK_TREE_MODEL (store), NULL, -1); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (view->parent), + GTK_SHADOW_IN); + serv->gui->chanlist_list = view; + + g_signal_connect (G_OBJECT (view), "row_activated", + G_CALLBACK (chanlist_dclick_cb), serv); + g_signal_connect (G_OBJECT (view), "button-press-event", + G_CALLBACK (chanlist_button_cb), serv); + + chanlist_add_column (view, COL_CHANNEL, 96, _("Channel"), FALSE); + chanlist_add_column (view, COL_USERS, 50, _("Users"), TRUE); + chanlist_add_column (view, COL_TOPIC, 50, _("Topic"), FALSE); + gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE); + /* this is a speed up, but no horizontal scrollbar :( */ + /*gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (view), TRUE);*/ + gtk_widget_show (view); + + /* ============================================================= */ + + table = gtk_table_new (4, 4, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 12); + gtk_table_set_row_spacings (GTK_TABLE (table), 3); + gtk_box_pack_start (GTK_BOX (vbox), table, 0, 1, 0); + gtk_widget_show (table); + + wid = gtkutil_button (NULL, GTK_STOCK_FIND, 0, chanlist_search_pressed, serv, + _("_Search")); + serv->gui->chanlist_search = wid; + gtk_table_attach (GTK_TABLE (table), wid, 3, 4, 3, 4, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + + wid = gtkutil_button (NULL, GTK_STOCK_REFRESH, 0, chanlist_refresh, serv, + _("_Download List")); + serv->gui->chanlist_refresh = wid; + gtk_table_attach (GTK_TABLE (table), wid, 3, 4, 2, 3, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + + wid = gtkutil_button (NULL, GTK_STOCK_SAVE_AS, 0, chanlist_save, serv, + _("Save _List...")); + serv->gui->chanlist_savelist = wid; + gtk_table_attach (GTK_TABLE (table), wid, 3, 4, 1, 2, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + + wid = gtkutil_button (NULL, GTK_STOCK_JUMP_TO, 0, chanlist_join, serv, + _("_Join Channel")); + serv->gui->chanlist_join = wid; + gtk_table_attach (GTK_TABLE (table), wid, 3, 4, 0, 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + + /* ============================================================= */ + + wid = gtk_label_new (_("Show only:")); + gtk_misc_set_alignment (GTK_MISC (wid), 0, 0.5); + gtk_table_attach (GTK_TABLE (table), wid, 0, 1, 3, 4, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (wid); + + hbox = gtk_hbox_new (0, 0); + gtk_box_set_spacing (GTK_BOX (hbox), 9); + gtk_table_attach (GTK_TABLE (table), hbox, 1, 2, 3, 4, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (hbox); + + wid = gtk_label_new (_("channels with")); + gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0); + gtk_widget_show (wid); + + wid = gtk_spin_button_new_with_range (1, 999999, 1); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (wid), + serv->gui->chanlist_minusers); + g_signal_connect (G_OBJECT (wid), "value_changed", + G_CALLBACK (chanlist_minusers), serv); + gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0); + gtk_widget_show (wid); + serv->gui->chanlist_min_spin = wid; + + wid = gtk_label_new (_("to")); + gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0); + gtk_widget_show (wid); + + wid = gtk_spin_button_new_with_range (1, 999999, 1); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (wid), + serv->gui->chanlist_maxusers); + g_signal_connect (G_OBJECT (wid), "value_changed", + G_CALLBACK (chanlist_maxusers), serv); + gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0); + gtk_widget_show (wid); + + wid = gtk_label_new (_("users.")); + gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0); + gtk_widget_show (wid); + + /* ============================================================= */ + + wid = gtk_label_new (_("Look in:")); + gtk_misc_set_alignment (GTK_MISC (wid), 0, 0.5); + gtk_table_attach (GTK_TABLE (table), wid, 0, 1, 2, 3, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (wid); + + hbox = gtk_hbox_new (0, 0); + gtk_box_set_spacing (GTK_BOX (hbox), 12); + gtk_table_attach (GTK_TABLE (table), hbox, 1, 2, 2, 3, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (hbox); + + wid = gtk_check_button_new_with_label (_("Channel name")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), TRUE); + gtk_signal_connect (GTK_OBJECT (wid), "toggled", + GTK_SIGNAL_FUNC + (chanlist_match_channel_button_toggled), serv); + gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0); + gtk_widget_show (wid); + + wid = gtk_check_button_new_with_label (_("Topic")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), TRUE); + gtk_signal_connect (GTK_OBJECT (wid), "toggled", + GTK_SIGNAL_FUNC (chanlist_match_topic_button_toggled), + serv); + gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0); + gtk_widget_show (wid); + + serv->gui->chanlist_match_wants_channel = 1; + serv->gui->chanlist_match_wants_topic = 1; + + /* ============================================================= */ + + wid = gtk_label_new (_("Search type:")); + gtk_misc_set_alignment (GTK_MISC (wid), 0, 0.5); + gtk_table_attach (GTK_TABLE (table), wid, 0, 1, 1, 2, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (wid); + + wid = gtk_combo_box_new_text (); + gtk_combo_box_append_text (GTK_COMBO_BOX (wid), _("Simple Search")); + gtk_combo_box_append_text (GTK_COMBO_BOX (wid), _("Pattern Match (Wildcards)")); +#ifndef WIN32 + gtk_combo_box_append_text (GTK_COMBO_BOX (wid), _("Regular Expression")); +#endif + gtk_combo_box_set_active (GTK_COMBO_BOX (wid), serv->gui->chanlist_search_type); + gtk_table_attach (GTK_TABLE (table), wid, 1, 2, 1, 2, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + g_signal_connect (G_OBJECT (wid), "changed", + G_CALLBACK (chanlist_combo_cb), serv); + gtk_widget_show (wid); + + /* ============================================================= */ + + wid = gtk_label_new (_("Find:")); + gtk_misc_set_alignment (GTK_MISC (wid), 0, 0.5); + gtk_table_attach (GTK_TABLE (table), wid, 0, 1, 0, 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (wid); + + wid = gtk_entry_new_with_max_length (255); + gtk_signal_connect (GTK_OBJECT (wid), "changed", + GTK_SIGNAL_FUNC (chanlist_find_cb), serv); + gtk_signal_connect (GTK_OBJECT (wid), "activate", + GTK_SIGNAL_FUNC (chanlist_search_pressed), + (gpointer) serv); + gtk_table_attach (GTK_TABLE (table), wid, 1, 2, 0, 1, + GTK_EXPAND | GTK_FILL, 0, 0, 0); + gtk_widget_show (wid); + serv->gui->chanlist_wild = wid; + + chanlist_find_cb (wid, serv); + + /* ============================================================= */ + + wid = gtk_vseparator_new (); + gtk_table_attach (GTK_TABLE (table), wid, 2, 3, 0, 5, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (wid); + + g_signal_connect (G_OBJECT (serv->gui->chanlist_window), "destroy", + G_CALLBACK (chanlist_destroy_widget), serv); + + /* reset the counters. */ + chanlist_reset_counters (serv); + + serv->gui->chanlist_tag = g_timeout_add (250, (GSourceFunc)chanlist_timeout, serv); + + if (do_refresh) + chanlist_do_refresh (serv); + + chanlist_update_buttons (serv); + gtk_widget_show (serv->gui->chanlist_window); + gtk_widget_grab_focus (serv->gui->chanlist_refresh); +} diff --git a/src/fe-gtk/chanlist.h b/src/fe-gtk/chanlist.h new file mode 100644 index 00000000..19a8b25e --- /dev/null +++ b/src/fe-gtk/chanlist.h @@ -0,0 +1 @@ +void chanlist_opengui (server *serv, int do_refresh); diff --git a/src/fe-gtk/chanview-tabs.c b/src/fe-gtk/chanview-tabs.c new file mode 100644 index 00000000..8e3da8e6 --- /dev/null +++ b/src/fe-gtk/chanview-tabs.c @@ -0,0 +1,779 @@ +/* file included in chanview.c */ + +typedef struct +{ + GtkWidget *outer; /* outer box */ + GtkWidget *inner; /* inner box */ + GtkWidget *b1; /* button1 */ + GtkWidget *b2; /* button2 */ +} tabview; + +static void chanview_populate (chanview *cv); + +/* ignore "toggled" signal? */ +static int ignore_toggle = FALSE; +static int tab_left_is_moving = 0; +static int tab_right_is_moving = 0; + +/* userdata for gobjects used here: + * + * tab (togglebuttons inside boxes): + * "u" userdata passed to tab-focus callback function (sess) + * "c" the tab's (chan *) + * + * box (family box) + * "f" family + * + */ + +/* + * GtkViewports request at least as much space as their children do. + * If we don't intervene here, the GtkViewport will be granted its + * request, even at the expense of resizing the top-level window. + */ +static void +cv_tabs_sizerequest (GtkWidget *viewport, GtkRequisition *requisition, chanview *cv) +{ + if (!cv->vertical) + requisition->width = 1; + else + requisition->height = 1; +} + +static void +cv_tabs_sizealloc (GtkWidget *widget, GtkAllocation *allocation, chanview *cv) +{ + GtkAdjustment *adj; + GtkWidget *inner; + gint viewport_size; + + inner = ((tabview *)cv)->inner; + + if (cv->vertical) + { + adj = gtk_viewport_get_vadjustment (GTK_VIEWPORT (inner->parent)); + gdk_window_get_geometry (inner->parent->window, 0, 0, 0, &viewport_size, 0); + } else + { + adj = gtk_viewport_get_hadjustment (GTK_VIEWPORT (inner->parent)); + gdk_window_get_geometry (inner->parent->window, 0, 0, &viewport_size, 0, 0); + } + + if (adj->upper <= viewport_size) + { + gtk_widget_hide (((tabview *)cv)->b1); + gtk_widget_hide (((tabview *)cv)->b2); + } else + { + gtk_widget_show (((tabview *)cv)->b1); + gtk_widget_show (((tabview *)cv)->b2); + } +} + +static gint +tab_search_offset (GtkWidget *inner, gint start_offset, + gboolean forward, gboolean vertical) +{ + GList *boxes; + GList *tabs; + GtkWidget *box; + GtkWidget *button; + gint found; + + boxes = GTK_BOX (inner)->children; + if (!forward && boxes) + boxes = g_list_last (boxes); + + while (boxes) + { + box = ((GtkBoxChild *)boxes->data)->widget; + boxes = (forward ? boxes->next : boxes->prev); + + tabs = GTK_BOX (box)->children; + if (!forward && tabs) + tabs = g_list_last (tabs); + + while (tabs) + { + button = ((GtkBoxChild *)tabs->data)->widget; + tabs = (forward ? tabs->next : tabs->prev); + + if (!GTK_IS_TOGGLE_BUTTON (button)) + continue; + + found = (vertical ? button->allocation.y : button->allocation.x); + if ((forward && found > start_offset) || + (!forward && found < start_offset)) + return found; + } + } + + return 0; +} + +static void +tab_scroll_left_up_clicked (GtkWidget *widget, chanview *cv) +{ + GtkAdjustment *adj; + gint viewport_size; + gfloat new_value; + GtkWidget *inner; + gfloat i; + + inner = ((tabview *)cv)->inner; + + if (cv->vertical) + { + adj = gtk_viewport_get_vadjustment (GTK_VIEWPORT (inner->parent)); + gdk_window_get_geometry (inner->parent->window, 0, 0, 0, &viewport_size, 0); + } else + { + adj = gtk_viewport_get_hadjustment (GTK_VIEWPORT (inner->parent)); + gdk_window_get_geometry (inner->parent->window, 0, 0, &viewport_size, 0, 0); + } + + new_value = tab_search_offset (inner, adj->value, 0, cv->vertical); + + if (new_value + viewport_size > adj->upper) + new_value = adj->upper - viewport_size; + + if (!tab_left_is_moving) + { + tab_left_is_moving = 1; + + for (i = adj->value; ((i > new_value) && (tab_left_is_moving)); i -= 0.1) + { + gtk_adjustment_set_value (adj, i); + while (g_main_pending ()) + g_main_iteration (TRUE); + } + + gtk_adjustment_set_value (adj, new_value); + + tab_left_is_moving = 0; /* hSP: set to false in case we didnt get stopped (the normal case) */ + } + else + { + tab_left_is_moving = 0; /* hSP: jump directly to next element if user is clicking faster than we can scroll.. */ + } +} + +static void +tab_scroll_right_down_clicked (GtkWidget *widget, chanview *cv) +{ + GtkAdjustment *adj; + gint viewport_size; + gfloat new_value; + GtkWidget *inner; + gfloat i; + + inner = ((tabview *)cv)->inner; + + if (cv->vertical) + { + adj = gtk_viewport_get_vadjustment (GTK_VIEWPORT (inner->parent)); + gdk_window_get_geometry (inner->parent->window, 0, 0, 0, &viewport_size, 0); + } else + { + adj = gtk_viewport_get_hadjustment (GTK_VIEWPORT (inner->parent)); + gdk_window_get_geometry (inner->parent->window, 0, 0, &viewport_size, 0, 0); + } + + new_value = tab_search_offset (inner, adj->value, 1, cv->vertical); + + if (new_value == 0 || new_value + viewport_size > adj->upper) + new_value = adj->upper - viewport_size; + + if (!tab_right_is_moving) + { + tab_right_is_moving = 1; + + for (i = adj->value; ((i < new_value) && (tab_right_is_moving)); i += 0.1) + { + gtk_adjustment_set_value (adj, i); + while (g_main_pending ()) + g_main_iteration (TRUE); + } + + gtk_adjustment_set_value (adj, new_value); + + tab_right_is_moving = 0; /* hSP: set to false in case we didnt get stopped (the normal case) */ + } + else + { + tab_right_is_moving = 0; /* hSP: jump directly to next element if user is clicking faster than we can scroll.. */ + } +} + +static gboolean +tab_scroll_cb (GtkWidget *widget, GdkEventScroll *event, gpointer cv) +{ + /* mouse wheel scrolling */ + if (event->direction == GDK_SCROLL_UP) + tab_scroll_left_up_clicked (widget, cv); + else if (event->direction == GDK_SCROLL_DOWN) + tab_scroll_right_down_clicked (widget, cv); + + return FALSE; +} + +static void +cv_tabs_xclick_cb (GtkWidget *button, chanview *cv) +{ + cv->cb_xbutton (cv, cv->focused, cv->focused->tag, cv->focused->userdata); +} + +/* make a Scroll (arrow) button */ + +static GtkWidget * +make_sbutton (GtkArrowType type, void *click_cb, void *userdata) +{ + GtkWidget *button, *arrow; + + button = gtk_button_new (); + arrow = gtk_arrow_new (type, GTK_SHADOW_NONE); + gtk_container_add (GTK_CONTAINER (button), arrow); + gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE); + g_signal_connect (G_OBJECT (button), "clicked", + G_CALLBACK (click_cb), userdata); + g_signal_connect (G_OBJECT (button), "scroll_event", + G_CALLBACK (tab_scroll_cb), userdata); + gtk_widget_show (arrow); + + return button; +} + +static void +cv_tabs_init (chanview *cv) +{ + GtkWidget *box, *hbox = NULL; + GtkWidget *viewport; + GtkWidget *outer; + GtkWidget *button; + + if (cv->vertical) + outer = gtk_vbox_new (0, 0); + else + outer = gtk_hbox_new (0, 0); + ((tabview *)cv)->outer = outer; + g_signal_connect (G_OBJECT (outer), "size_allocate", + G_CALLBACK (cv_tabs_sizealloc), cv); +/* gtk_container_set_border_width (GTK_CONTAINER (outer), 2);*/ + gtk_widget_show (outer); + + viewport = gtk_viewport_new (0, 0); + gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE); + g_signal_connect (G_OBJECT (viewport), "size_request", + G_CALLBACK (cv_tabs_sizerequest), cv); + g_signal_connect (G_OBJECT (viewport), "scroll_event", + G_CALLBACK (tab_scroll_cb), cv); + gtk_box_pack_start (GTK_BOX (outer), viewport, 1, 1, 0); + gtk_widget_show (viewport); + + if (cv->vertical) + box = gtk_vbox_new (FALSE, 0); + else + box = gtk_hbox_new (FALSE, 0); + ((tabview *)cv)->inner = box; + gtk_container_add (GTK_CONTAINER (viewport), box); + gtk_widget_show (box); + + /* if vertical, the buttons can be side by side */ + if (cv->vertical) + { + hbox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (outer), hbox, 0, 0, 0); + gtk_widget_show (hbox); + } + + /* make the Scroll buttons */ + ((tabview *)cv)->b2 = make_sbutton (cv->vertical ? + GTK_ARROW_UP : GTK_ARROW_LEFT, + tab_scroll_left_up_clicked, + cv); + + ((tabview *)cv)->b1 = make_sbutton (cv->vertical ? + GTK_ARROW_DOWN : GTK_ARROW_RIGHT, + tab_scroll_right_down_clicked, + cv); + + if (hbox) + { + gtk_container_add (GTK_CONTAINER (hbox), ((tabview *)cv)->b2); + gtk_container_add (GTK_CONTAINER (hbox), ((tabview *)cv)->b1); + } else + { + gtk_box_pack_start (GTK_BOX (outer), ((tabview *)cv)->b2, 0, 0, 0); + gtk_box_pack_start (GTK_BOX (outer), ((tabview *)cv)->b1, 0, 0, 0); + } + + button = gtkutil_button (outer, GTK_STOCK_CLOSE, NULL, cv_tabs_xclick_cb, + cv, 0); + gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE); + GTK_WIDGET_UNSET_FLAGS (button, GTK_CAN_FOCUS); + + gtk_container_add (GTK_CONTAINER (cv->box), outer); +} + +static void +cv_tabs_postinit (chanview *cv) +{ +} + +static void +tab_add_sorted (chanview *cv, GtkWidget *box, GtkWidget *tab, chan *ch) +{ + GList *list; + GtkBoxChild *child; + int i = 0; + void *b; + + if (!cv->sorted) + { + gtk_box_pack_start (GTK_BOX (box), tab, 0, 0, 0); + gtk_widget_show (tab); + return; + } + + /* sorting TODO: + * - move tab if renamed (dialogs) */ + + /* userdata, passed to mg_tabs_compare() */ + b = ch->userdata; + + list = GTK_BOX (box)->children; + while (list) + { + child = list->data; + if (!GTK_IS_SEPARATOR (child->widget)) + { + void *a = g_object_get_data (G_OBJECT (child->widget), "u"); + + if (ch->tag == 0 && cv->cb_compare (a, b) > 0) + { + gtk_box_pack_start (GTK_BOX (box), tab, 0, 0, 0); + gtk_box_reorder_child (GTK_BOX (box), tab, i); + gtk_widget_show (tab); + return; + } + } + i++; + list = list->next; + } + + /* append */ + gtk_box_pack_start (GTK_BOX (box), tab, 0, 0, 0); + gtk_box_reorder_child (GTK_BOX (box), tab, i); + gtk_widget_show (tab); +} + +/* remove empty boxes and separators */ + +static void +cv_tabs_prune (chanview *cv) +{ + GList *boxes, *children; + GtkWidget *box, *inner; + GtkBoxChild *child; + int empty; + + inner = ((tabview *)cv)->inner; + boxes = GTK_BOX (inner)->children; + while (boxes) + { + child = boxes->data; + box = child->widget; + boxes = boxes->next; + + /* check if the box is empty (except a vseperator) */ + empty = TRUE; + children = GTK_BOX (box)->children; + while (children) + { + if (!GTK_IS_SEPARATOR (((GtkBoxChild *)children->data)->widget)) + { + empty = FALSE; + break; + } + children = children->next; + } + + if (empty) + gtk_widget_destroy (box); + } +} + +static void +tab_add_real (chanview *cv, GtkWidget *tab, chan *ch) +{ + GList *boxes, *children; + GtkWidget *sep, *box, *inner; + GtkBoxChild *child; + int empty; + + inner = ((tabview *)cv)->inner; + /* see if a family for this tab already exists */ + boxes = GTK_BOX (inner)->children; + while (boxes) + { + child = boxes->data; + box = child->widget; + + if (g_object_get_data (G_OBJECT (box), "f") == ch->family) + { + tab_add_sorted (cv, box, tab, ch); + gtk_widget_queue_resize (inner->parent); + return; + } + + boxes = boxes->next; + + /* check if the box is empty (except a vseperator) */ + empty = TRUE; + children = GTK_BOX (box)->children; + while (children) + { + if (!GTK_IS_SEPARATOR (((GtkBoxChild *)children->data)->widget)) + { + empty = FALSE; + break; + } + children = children->next; + } + + if (empty) + gtk_widget_destroy (box); + } + + /* create a new family box */ + if (cv->vertical) + { + /* vertical */ + box = gtk_vbox_new (FALSE, 0); + sep = gtk_hseparator_new (); + } else + { + /* horiz */ + box = gtk_hbox_new (FALSE, 0); + sep = gtk_vseparator_new (); + } + + gtk_box_pack_end (GTK_BOX (box), sep, 0, 0, 4); + gtk_widget_show (sep); + gtk_box_pack_start (GTK_BOX (inner), box, 0, 0, 0); + g_object_set_data (G_OBJECT (box), "f", ch->family); + gtk_box_pack_start (GTK_BOX (box), tab, 0, 0, 0); + gtk_widget_show (tab); + gtk_widget_show (box); + gtk_widget_queue_resize (inner->parent); +} + +static gboolean +tab_ignore_cb (GtkWidget *widget, GdkEventCrossing *event, gpointer user_data) +{ + return TRUE; +} + +/* called when a tab is clicked (button down) */ + +static void +tab_pressed_cb (GtkToggleButton *tab, chan *ch) +{ + chan *old_tab; + int is_switching = TRUE; + chanview *cv = ch->cv; + + ignore_toggle = TRUE; + /* de-activate the old tab */ + old_tab = cv->focused; + if (old_tab && old_tab->impl) + { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (old_tab->impl), FALSE); + if (old_tab == ch) + is_switching = FALSE; + } + gtk_toggle_button_set_active (tab, TRUE); + ignore_toggle = FALSE; + cv->focused = ch; + + if (/*tab->active*/is_switching) + /* call the focus callback */ + cv->cb_focus (cv, ch, ch->tag, ch->userdata); +} + +/* called for keyboard tab toggles only */ +static void +tab_toggled_cb (GtkToggleButton *tab, chan *ch) +{ + if (ignore_toggle) + return; + + /* activated a tab via keyboard */ + tab_pressed_cb (tab, ch); +} + +static gboolean +tab_click_cb (GtkWidget *wid, GdkEventButton *event, chan *ch) +{ + return ch->cv->cb_contextmenu (ch->cv, ch, ch->tag, ch->userdata, event); +} + +static void * +cv_tabs_add (chanview *cv, chan *ch, char *name, GtkTreeIter *parent) +{ + GtkWidget *but; + + but = gtk_toggle_button_new_with_label (name); + gtk_widget_set_name (but, "xchat-tab"); + g_object_set_data (G_OBJECT (but), "c", ch); + /* used to trap right-clicks */ + g_signal_connect (G_OBJECT (but), "button_press_event", + G_CALLBACK (tab_click_cb), ch); + /* avoid prelights */ + g_signal_connect (G_OBJECT (but), "enter_notify_event", + G_CALLBACK (tab_ignore_cb), NULL); + g_signal_connect (G_OBJECT (but), "leave_notify_event", + G_CALLBACK (tab_ignore_cb), NULL); + g_signal_connect (G_OBJECT (but), "pressed", + G_CALLBACK (tab_pressed_cb), ch); + /* for keyboard */ + g_signal_connect (G_OBJECT (but), "toggled", + G_CALLBACK (tab_toggled_cb), ch); + g_object_set_data (G_OBJECT (but), "u", ch->userdata); + + tab_add_real (cv, but, ch); + + return but; +} + +/* traverse all the family boxes of tabs + * + * A "group" is basically: + * GtkV/HBox + * `-GtkViewPort + * `-GtkV/HBox (inner box) + * `- GtkBox (family box) + * `- GtkToggleButton + * `- GtkToggleButton + * `- ... + * `- GtkBox + * `- GtkToggleButton + * `- GtkToggleButton + * `- ... + * `- ... + * + * */ + +static int +tab_group_for_each_tab (chanview *cv, + int (*callback) (GtkWidget *tab, int num, int usernum), + int usernum) +{ + GList *tabs; + GList *boxes; + GtkBoxChild *child; + GtkBox *innerbox; + int i; + + innerbox = (GtkBox *) ((tabview *)cv)->inner; + boxes = innerbox->children; + i = 0; + while (boxes) + { + child = boxes->data; + tabs = GTK_BOX (child->widget)->children; + + while (tabs) + { + child = tabs->data; + + if (!GTK_IS_SEPARATOR (child->widget)) + { + if (callback (child->widget, i, usernum) != -1) + return i; + i++; + } + tabs = tabs->next; + } + + boxes = boxes->next; + } + + return i; +} + +static int +tab_check_focus_cb (GtkWidget *tab, int num, int unused) +{ + if (GTK_TOGGLE_BUTTON (tab)->active) + return num; + + return -1; +} + +/* returns the currently focused tab number */ + +static int +tab_group_get_cur_page (chanview *cv) +{ + return tab_group_for_each_tab (cv, tab_check_focus_cb, 0); +} + +static void +cv_tabs_focus (chan *ch) +{ + if (ch->impl) + /* focus the new one (tab_pressed_cb defocuses the old one) */ + tab_pressed_cb (GTK_TOGGLE_BUTTON (ch->impl), ch); +} + +static int +tab_focus_num_cb (GtkWidget *tab, int num, int want) +{ + if (num == want) + { + cv_tabs_focus (g_object_get_data (G_OBJECT (tab), "c")); + return 1; + } + + return -1; +} + +static void +cv_tabs_change_orientation (chanview *cv) +{ + /* cleanup the old one */ + if (cv->func_cleanup) + cv->func_cleanup (cv); + + /* now rebuild a new tabbar or tree */ + cv->func_init (cv); + chanview_populate (cv); +} + +/* switch to the tab number specified */ + +static void +cv_tabs_move_focus (chanview *cv, gboolean relative, int num) +{ + int i, max; + + if (relative) + { + max = cv->size; + i = tab_group_get_cur_page (cv) + num; + /* make it wrap around at both ends */ + if (i < 0) + i = max - 1; + if (i >= max) + i = 0; + tab_group_for_each_tab (cv, tab_focus_num_cb, i); + return; + } + + tab_group_for_each_tab (cv, tab_focus_num_cb, num); +} + +static void +cv_tabs_remove (chan *ch) +{ + gtk_widget_destroy (ch->impl); + ch->impl = NULL; + + cv_tabs_prune (ch->cv); +} + +static void +cv_tabs_move (chan *ch, int delta) +{ + int i, pos = 0; + GList *list; + GtkWidget *parent = ((GtkWidget *)ch->impl)->parent; + + i = 0; + for (list = GTK_BOX (parent)->children; list; list = list->next) + { + GtkBoxChild *child_entry; + + child_entry = list->data; + if (child_entry->widget == ch->impl) + pos = i; + i++; + } + + pos = (pos - delta) % i; + gtk_box_reorder_child (GTK_BOX (parent), ch->impl, pos); +} + +static void +cv_tabs_move_family (chan *ch, int delta) +{ + int i, pos = 0; + GList *list; + GtkWidget *box = NULL; + + /* find position of tab's family */ + i = 0; + for (list = GTK_BOX (((tabview *)ch->cv)->inner)->children; list; list = list->next) + { + GtkBoxChild *child_entry; + void *fam; + + child_entry = list->data; + fam = g_object_get_data (G_OBJECT (child_entry->widget), "f"); + if (fam == ch->family) + { + box = child_entry->widget; + pos = i; + } + i++; + } + + pos = (pos - delta) % i; + gtk_box_reorder_child (GTK_BOX (box->parent), box, pos); +} + +static void +cv_tabs_cleanup (chanview *cv) +{ + if (cv->box) + gtk_widget_destroy (((tabview *)cv)->outer); +} + +static void +cv_tabs_set_color (chan *ch, PangoAttrList *list) +{ + gtk_label_set_attributes (GTK_LABEL (GTK_BIN (ch->impl)->child), list); +} + +static void +cv_tabs_rename (chan *ch, char *name) +{ + PangoAttrList *attr; + GtkWidget *tab = ch->impl; + + attr = gtk_label_get_attributes (GTK_LABEL (GTK_BIN (tab)->child)); + if (attr) + pango_attr_list_ref (attr); + + gtk_button_set_label (GTK_BUTTON (tab), name); + gtk_widget_queue_resize (tab->parent->parent->parent); + + if (attr) + { + gtk_label_set_attributes (GTK_LABEL (GTK_BIN (tab)->child), attr); + pango_attr_list_unref (attr); + } +} + +static gboolean +cv_tabs_is_collapsed (chan *ch) +{ + return FALSE; +} + +static chan * +cv_tabs_get_parent (chan *ch) +{ + return NULL; +} diff --git a/src/fe-gtk/chanview-tree.c b/src/fe-gtk/chanview-tree.c new file mode 100644 index 00000000..5373f21b --- /dev/null +++ b/src/fe-gtk/chanview-tree.c @@ -0,0 +1,364 @@ +/* file included in chanview.c */ + +typedef struct +{ + GtkTreeView *tree; + GtkWidget *scrollw; /* scrolledWindow */ +} treeview; + +#include "../common/xchat.h" +#include "../common/xchatc.h" +#include "fe-gtk.h" +#include "maingui.h" + +#include <gdk/gdk.h> +#include <gtk/gtktreeview.h> + +static void /* row-activated, when a row is double clicked */ +cv_tree_activated_cb (GtkTreeView *view, GtkTreePath *path, + GtkTreeViewColumn *column, gpointer data) +{ + if (gtk_tree_view_row_expanded (view, path)) + gtk_tree_view_collapse_row (view, path); + else + gtk_tree_view_expand_row (view, path, FALSE); +} + +static void /* row selected callback */ +cv_tree_sel_cb (GtkTreeSelection *sel, chanview *cv) +{ + GtkTreeModel *model; + GtkTreeIter iter; + chan *ch; + + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + { + gtk_tree_model_get (model, &iter, COL_CHAN, &ch, -1); + + cv->focused = ch; + cv->cb_focus (cv, ch, ch->tag, ch->userdata); + } +} + +static gboolean +cv_tree_click_cb (GtkTreeView *tree, GdkEventButton *event, chanview *cv) +{ + chan *ch; + GtkTreeSelection *sel; + GtkTreePath *path; + GtkTreeIter iter; + int ret = FALSE; + + if (event->button != 3 && event->state == 0) + return FALSE; + + sel = gtk_tree_view_get_selection (tree); + if (gtk_tree_view_get_path_at_pos (tree, event->x, event->y, &path, 0, 0, 0)) + { + if (event->button == 2) + { + gtk_tree_selection_unselect_all (sel); + gtk_tree_selection_select_path (sel, path); + } + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (cv->store), &iter, path)) + { + gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &iter, COL_CHAN, &ch, -1); + ret = cv->cb_contextmenu (cv, ch, ch->tag, ch->userdata, event); + } + gtk_tree_path_free (path); + } + return ret; +} + +static void +cv_tree_init (chanview *cv) +{ + GtkWidget *view, *win; + GtkCellRenderer *renderer; + static const GtkTargetEntry dnd_src_target[] = + { + {"XCHAT_CHANVIEW", GTK_TARGET_SAME_APP, 75 } + }; + static const GtkTargetEntry dnd_dest_target[] = + { + {"XCHAT_USERLIST", GTK_TARGET_SAME_APP, 75 } + }; + + win = gtk_scrolled_window_new (0, 0); + /*gtk_container_set_border_width (GTK_CONTAINER (win), 1);*/ + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (win), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (win), + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_container_add (GTK_CONTAINER (cv->box), win); + gtk_widget_show (win); + + view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (cv->store)); + gtk_widget_set_name (view, "xchat-tree"); + if (cv->style) + gtk_widget_set_style (view, cv->style); + /*gtk_widget_modify_base (view, GTK_STATE_NORMAL, &colors[COL_BG]);*/ + GTK_WIDGET_UNSET_FLAGS (view, GTK_CAN_FOCUS); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view), FALSE); +#if GTK_CHECK_VERSION(2,10,0) + if (!(prefs.gui_tweaks & 8)) + gtk_tree_view_set_enable_tree_lines (GTK_TREE_VIEW (view), TRUE); +#endif + gtk_container_add (GTK_CONTAINER (win), view); + + /* icon column */ + if (cv->use_icons) + { + renderer = gtk_cell_renderer_pixbuf_new (); + if (prefs.gui_tweaks & 32) + g_object_set (G_OBJECT (renderer), "ypad", 0, NULL); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), + -1, NULL, renderer, + "pixbuf", COL_PIXBUF, NULL); + } + + /* main column */ + renderer = gtk_cell_renderer_text_new (); + if (prefs.gui_tweaks & 32) + g_object_set (G_OBJECT (renderer), "ypad", 0, NULL); + gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer), 1); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), + -1, NULL, renderer, + "text", COL_NAME, "attributes", COL_ATTR, NULL); + + g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (view))), + "changed", G_CALLBACK (cv_tree_sel_cb), cv); + g_signal_connect (G_OBJECT (view), "button-press-event", + G_CALLBACK (cv_tree_click_cb), cv); + g_signal_connect (G_OBJECT (view), "row-activated", + G_CALLBACK (cv_tree_activated_cb), NULL); + + gtk_drag_dest_set (view, GTK_DEST_DEFAULT_ALL, dnd_dest_target, 1, + GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK); + gtk_drag_source_set (view, GDK_BUTTON1_MASK, dnd_src_target, 1, GDK_ACTION_COPY); + +#ifndef WIN32 + g_signal_connect (G_OBJECT (view), "drag_begin", + G_CALLBACK (mg_drag_begin_cb), NULL); + g_signal_connect (G_OBJECT (view), "drag_drop", + G_CALLBACK (mg_drag_drop_cb), NULL); + g_signal_connect (G_OBJECT (view), "drag_motion", + G_CALLBACK (mg_drag_motion_cb), NULL); + g_signal_connect (G_OBJECT (view), "drag_end", + G_CALLBACK (mg_drag_end_cb), NULL); +#endif + + ((treeview *)cv)->tree = GTK_TREE_VIEW (view); + ((treeview *)cv)->scrollw = win; + gtk_widget_show (view); +} + +static void +cv_tree_postinit (chanview *cv) +{ + gtk_tree_view_expand_all (((treeview *)cv)->tree); +} + +static void * +cv_tree_add (chanview *cv, chan *ch, char *name, GtkTreeIter *parent) +{ + GtkTreePath *path; + + if (parent) + { + /* expand the parent node */ + path = gtk_tree_model_get_path (GTK_TREE_MODEL (cv->store), parent); + if (path) + { + gtk_tree_view_expand_row (((treeview *)cv)->tree, path, FALSE); + gtk_tree_path_free (path); + } + } + + return NULL; +} + +static void +cv_tree_change_orientation (chanview *cv) +{ +} + +static void +cv_tree_focus (chan *ch) +{ + GtkTreeView *tree = ((treeview *)ch->cv)->tree; + GtkTreeModel *model = gtk_tree_view_get_model (tree); + GtkTreePath *path; + GtkTreeIter parent; + GdkRectangle cell_rect; + GdkRectangle vis_rect; + gint dest_y; + + /* expand the parent node */ + if (gtk_tree_model_iter_parent (model, &parent, &ch->iter)) + { + path = gtk_tree_model_get_path (model, &parent); + if (path) + { + /*if (!gtk_tree_view_row_expanded (tree, path)) + { + gtk_tree_path_free (path); + return; + }*/ + gtk_tree_view_expand_row (tree, path, FALSE); + gtk_tree_path_free (path); + } + } + + path = gtk_tree_model_get_path (model, &ch->iter); + if (path) + { + /* This full section does what + * gtk_tree_view_scroll_to_cell (tree, path, NULL, TRUE, 0.5, 0.5); + * does, except it only scrolls the window if the provided cell is + * not visible. Basic algorithm taken from gtktreeview.c */ + + /* obtain information to see if the cell is visible */ + gtk_tree_view_get_background_area (tree, path, NULL, &cell_rect); + gtk_tree_view_get_visible_rect (tree, &vis_rect); + + /* The cordinates aren't offset correctly */ + gtk_tree_view_widget_to_tree_coords( tree, cell_rect.x, cell_rect.y, NULL, &cell_rect.y ); + + /* only need to scroll if out of bounds */ + if (cell_rect.y < vis_rect.y || + cell_rect.y + cell_rect.height > vis_rect.y + vis_rect.height) + { + dest_y = cell_rect.y - ((vis_rect.height - cell_rect.height) * 0.5); + if (dest_y < 0) + dest_y = 0; + gtk_tree_view_scroll_to_point (tree, -1, dest_y); + } + /* theft done, now make it focused like */ + gtk_tree_view_set_cursor (tree, path, NULL, FALSE); + gtk_tree_path_free (path); + } +} + +static void +cv_tree_move_focus (chanview *cv, gboolean relative, int num) +{ + chan *ch; + + if (relative) + { + num += cv_find_number_of_chan (cv, cv->focused); + num %= cv->size; + /* make it wrap around at both ends */ + if (num < 0) + num = cv->size - 1; + } + + ch = cv_find_chan_by_number (cv, num); + if (ch) + cv_tree_focus (ch); +} + +static void +cv_tree_remove (chan *ch) +{ +} + +static void +move_row (chan *ch, int delta, GtkTreeIter *parent) +{ + GtkTreeStore *store = ch->cv->store; + GtkTreeIter *src = &ch->iter; + GtkTreeIter dest = ch->iter; + GtkTreePath *dest_path; + + if (delta < 0) /* down */ + { + if (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &dest)) + gtk_tree_store_swap (store, src, &dest); + else /* move to top */ + gtk_tree_store_move_after (store, src, NULL); + + } else + { + dest_path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &dest); + if (gtk_tree_path_prev (dest_path)) + { + gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &dest, dest_path); + gtk_tree_store_swap (store, src, &dest); + } else + { /* move to bottom */ + gtk_tree_store_move_before (store, src, NULL); + } + + gtk_tree_path_free (dest_path); + } +} + +static void +cv_tree_move (chan *ch, int delta) +{ + GtkTreeIter parent; + + /* do nothing if this is a server row */ + if (gtk_tree_model_iter_parent (GTK_TREE_MODEL (ch->cv->store), &parent, &ch->iter)) + move_row (ch, delta, &parent); +} + +static void +cv_tree_move_family (chan *ch, int delta) +{ + move_row (ch, delta, NULL); +} + +static void +cv_tree_cleanup (chanview *cv) +{ + if (cv->box) + /* kill the scrolled window */ + gtk_widget_destroy (((treeview *)cv)->scrollw); +} + +static void +cv_tree_set_color (chan *ch, PangoAttrList *list) +{ + /* nothing to do, it's already set in the store */ +} + +static void +cv_tree_rename (chan *ch, char *name) +{ + /* nothing to do, it's already renamed in the store */ +} + +static chan * +cv_tree_get_parent (chan *ch) +{ + chan *parent_ch = NULL; + GtkTreeIter parent; + + if (gtk_tree_model_iter_parent (GTK_TREE_MODEL (ch->cv->store), &parent, &ch->iter)) + { + gtk_tree_model_get (GTK_TREE_MODEL (ch->cv->store), &parent, COL_CHAN, &parent_ch, -1); + } + + return parent_ch; +} + +static gboolean +cv_tree_is_collapsed (chan *ch) +{ + chan *parent = cv_tree_get_parent (ch); + GtkTreePath *path = NULL; + gboolean ret; + + if (parent == NULL) + return FALSE; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (parent->cv->store), + &parent->iter); + ret = !gtk_tree_view_row_expanded (((treeview *)parent->cv)->tree, path); + gtk_tree_path_free (path); + + return ret; +} diff --git a/src/fe-gtk/chanview.c b/src/fe-gtk/chanview.c new file mode 100644 index 00000000..e90c4df8 --- /dev/null +++ b/src/fe-gtk/chanview.c @@ -0,0 +1,643 @@ +/* abstract channel view: tabs or tree or anything you like */ + +#include <stdlib.h> +#include <string.h> + +#include <gtk/gtk.h> + +#include "chanview.h" +#include "gtkutil.h" + + +/* treeStore columns */ + +#define COL_NAME 0 /* (char *) */ +#define COL_CHAN 1 /* (chan *) */ +#define COL_ATTR 2 /* (PangoAttrList *) */ +#define COL_PIXBUF 3 /* (GdkPixbuf *) */ + +struct _chanview +{ + /* impl scratch area */ + char implscratch[sizeof (void *) * 8]; + + GtkTreeStore *store; + int size; /* number of channels in view */ + + GtkWidget *box; /* the box we destroy when changing implementations */ + GtkStyle *style; /* style used for tree */ + chan *focused; /* currently focused channel */ + int trunc_len; + + /* callbacks */ + void (*cb_focus) (chanview *, chan *, int tag, void *userdata); + void (*cb_xbutton) (chanview *, chan *, int tag, void *userdata); + gboolean (*cb_contextmenu) (chanview *, chan *, int tag, void *userdata, GdkEventButton *); + int (*cb_compare) (void *a, void *b); + + /* impl */ + void (*func_init) (chanview *); + void (*func_postinit) (chanview *); + void *(*func_add) (chanview *, chan *, char *, GtkTreeIter *); + void (*func_move_focus) (chanview *, gboolean, int); + void (*func_change_orientation) (chanview *); + void (*func_remove) (chan *); + void (*func_move) (chan *, int delta); + void (*func_move_family) (chan *, int delta); + void (*func_focus) (chan *); + void (*func_set_color) (chan *, PangoAttrList *); + void (*func_rename) (chan *, char *); + gboolean (*func_is_collapsed) (chan *); + chan *(*func_get_parent) (chan *); + void (*func_cleanup) (chanview *); + + unsigned int sorted:1; + unsigned int vertical:1; + unsigned int use_icons:1; +}; + +struct _chan +{ + chanview *cv; /* our owner */ + GtkTreeIter iter; + void *userdata; /* session * */ + void *family; /* server * or null */ + void *impl; /* togglebutton or null */ + GdkPixbuf *icon; + short allow_closure; /* allow it to be closed when it still has children? */ + short tag; +}; + +static chan *cv_find_chan_by_number (chanview *cv, int num); +static int cv_find_number_of_chan (chanview *cv, chan *find_ch); + + +/* ======= TABS ======= */ + +#include "chanview-tabs.c" + + +/* ======= TREE ======= */ + +#include "chanview-tree.c" + + +/* ==== ABSTRACT CHANVIEW ==== */ + +static char * +truncate_tab_name (char *name, int max) +{ + char *buf; + + if (max > 2 && g_utf8_strlen (name, -1) > max) + { + /* truncate long channel names */ + buf = malloc (strlen (name) + 4); + strcpy (buf, name); + g_utf8_offset_to_pointer (buf, max)[0] = 0; + strcat (buf, ".."); + return buf; + } + + return name; +} + +/* iterate through a model, into 1 depth of children */ + +static void +model_foreach_1 (GtkTreeModel *model, void (*func)(void *, GtkTreeIter *), + void *userdata) +{ + GtkTreeIter iter, inner; + + if (gtk_tree_model_get_iter_first (model, &iter)) + { + do + { + func (userdata, &iter); + if (gtk_tree_model_iter_children (model, &inner, &iter)) + { + do + func (userdata, &inner); + while (gtk_tree_model_iter_next (model, &inner)); + } + } + while (gtk_tree_model_iter_next (model, &iter)); + } +} + +static void +chanview_pop_cb (chanview *cv, GtkTreeIter *iter) +{ + chan *ch; + char *name; + PangoAttrList *attr; + + gtk_tree_model_get (GTK_TREE_MODEL (cv->store), iter, + COL_NAME, &name, COL_CHAN, &ch, COL_ATTR, &attr, -1); + ch->impl = cv->func_add (cv, ch, name, NULL); + if (attr) + { + cv->func_set_color (ch, attr); + pango_attr_list_unref (attr); + } + g_free (name); +} + +static void +chanview_populate (chanview *cv) +{ + model_foreach_1 (GTK_TREE_MODEL (cv->store), (void *)chanview_pop_cb, cv); +} + +void +chanview_set_impl (chanview *cv, int type) +{ + /* cleanup the old one */ + if (cv->func_cleanup) + cv->func_cleanup (cv); + + switch (type) + { + case 0: + cv->func_init = cv_tabs_init; + cv->func_postinit = cv_tabs_postinit; + cv->func_add = cv_tabs_add; + cv->func_move_focus = cv_tabs_move_focus; + cv->func_change_orientation = cv_tabs_change_orientation; + cv->func_remove = cv_tabs_remove; + cv->func_move = cv_tabs_move; + cv->func_move_family = cv_tabs_move_family; + cv->func_focus = cv_tabs_focus; + cv->func_set_color = cv_tabs_set_color; + cv->func_rename = cv_tabs_rename; + cv->func_is_collapsed = cv_tabs_is_collapsed; + cv->func_get_parent = cv_tabs_get_parent; + cv->func_cleanup = cv_tabs_cleanup; + break; + + default: + cv->func_init = cv_tree_init; + cv->func_postinit = cv_tree_postinit; + cv->func_add = cv_tree_add; + cv->func_move_focus = cv_tree_move_focus; + cv->func_change_orientation = cv_tree_change_orientation; + cv->func_remove = cv_tree_remove; + cv->func_move = cv_tree_move; + cv->func_move_family = cv_tree_move_family; + cv->func_focus = cv_tree_focus; + cv->func_set_color = cv_tree_set_color; + cv->func_rename = cv_tree_rename; + cv->func_is_collapsed = cv_tree_is_collapsed; + cv->func_get_parent = cv_tree_get_parent; + cv->func_cleanup = cv_tree_cleanup; + break; + } + + /* now rebuild a new tabbar or tree */ + cv->func_init (cv); + + chanview_populate (cv); + + cv->func_postinit (cv); + + /* force re-focus */ + if (cv->focused) + cv->func_focus (cv->focused); +} + +static void +chanview_free_ch (chanview *cv, GtkTreeIter *iter) +{ + chan *ch; + + gtk_tree_model_get (GTK_TREE_MODEL (cv->store), iter, COL_CHAN, &ch, -1); + free (ch); +} + +static void +chanview_destroy_store (chanview *cv) /* free every (chan *) in the store */ +{ + model_foreach_1 (GTK_TREE_MODEL (cv->store), (void *)chanview_free_ch, cv); + g_object_unref (cv->store); +} + +static void +chanview_destroy (chanview *cv) +{ + if (cv->func_cleanup) + cv->func_cleanup (cv); + + if (cv->box) + gtk_widget_destroy (cv->box); + + chanview_destroy_store (cv); + free (cv); +} + +static void +chanview_box_destroy_cb (GtkWidget *box, chanview *cv) +{ + cv->box = NULL; + chanview_destroy (cv); +} + +chanview * +chanview_new (int type, int trunc_len, gboolean sort, gboolean use_icons, + GtkStyle *style) +{ + chanview *cv; + + cv = calloc (1, sizeof (chanview)); + cv->store = gtk_tree_store_new (4, G_TYPE_STRING, G_TYPE_POINTER, + PANGO_TYPE_ATTR_LIST, GDK_TYPE_PIXBUF); + cv->style = style; + cv->box = gtk_hbox_new (0, 0); + cv->trunc_len = trunc_len; + cv->sorted = sort; + cv->use_icons = use_icons; + gtk_widget_show (cv->box); + chanview_set_impl (cv, type); + + g_signal_connect (G_OBJECT (cv->box), "destroy", + G_CALLBACK (chanview_box_destroy_cb), cv); + + return cv; +} + +/* too lazy for signals */ + +void +chanview_set_callbacks (chanview *cv, + void (*cb_focus) (chanview *, chan *, int tag, void *userdata), + void (*cb_xbutton) (chanview *, chan *, int tag, void *userdata), + gboolean (*cb_contextmenu) (chanview *, chan *, int tag, void *userdata, GdkEventButton *), + int (*cb_compare) (void *a, void *b)) +{ + cv->cb_focus = cb_focus; + cv->cb_xbutton = cb_xbutton; + cv->cb_contextmenu = cb_contextmenu; + cv->cb_compare = cb_compare; +} + +/* find a place to insert this new entry, based on the compare function */ + +static void +chanview_insert_sorted (chanview *cv, GtkTreeIter *add_iter, GtkTreeIter *parent, void *ud) +{ + GtkTreeIter iter; + chan *ch; + + if (cv->sorted && gtk_tree_model_iter_children (GTK_TREE_MODEL (cv->store), &iter, parent)) + { + do + { + gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &iter, COL_CHAN, &ch, -1); + if (ch->tag == 0 && cv->cb_compare (ch->userdata, ud) > 0) + { + gtk_tree_store_insert_before (cv->store, add_iter, parent, &iter); + return; + } + } + while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &iter)); + } + + gtk_tree_store_append (cv->store, add_iter, parent); +} + +/* find a parent node with the same "family" pointer (i.e. the Server tab) */ + +static int +chanview_find_parent (chanview *cv, void *family, GtkTreeIter *search_iter, chan *avoid) +{ + chan *search_ch; + + /* find this new row's parent, if any */ + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (cv->store), search_iter)) + { + do + { + gtk_tree_model_get (GTK_TREE_MODEL (cv->store), search_iter, + COL_CHAN, &search_ch, -1); + if (family == search_ch->family && search_ch != avoid /*&& + gtk_tree_store_iter_depth (cv->store, search_iter) == 0*/) + return TRUE; + } + while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), search_iter)); + } + + return FALSE; +} + +static chan * +chanview_add_real (chanview *cv, char *name, void *family, void *userdata, + gboolean allow_closure, int tag, GdkPixbuf *icon, + chan *ch, chan *avoid) +{ + GtkTreeIter parent_iter; + GtkTreeIter iter; + gboolean has_parent = FALSE; + + if (chanview_find_parent (cv, family, &parent_iter, avoid)) + { + chanview_insert_sorted (cv, &iter, &parent_iter, userdata); + has_parent = TRUE; + } else + { + gtk_tree_store_append (cv->store, &iter, NULL); + } + + if (!ch) + { + ch = calloc (1, sizeof (chan)); + ch->userdata = userdata; + ch->family = family; + ch->cv = cv; + ch->allow_closure = allow_closure; + ch->tag = tag; + ch->icon = icon; + } + memcpy (&(ch->iter), &iter, sizeof (iter)); + + gtk_tree_store_set (cv->store, &iter, COL_NAME, name, COL_CHAN, ch, + COL_PIXBUF, icon, -1); + + cv->size++; + if (!has_parent) + ch->impl = cv->func_add (cv, ch, name, NULL); + else + ch->impl = cv->func_add (cv, ch, name, &parent_iter); + + return ch; +} + +chan * +chanview_add (chanview *cv, char *name, void *family, void *userdata, gboolean allow_closure, int tag, GdkPixbuf *icon) +{ + char *new_name; + chan *ret; + + new_name = truncate_tab_name (name, cv->trunc_len); + + ret = chanview_add_real (cv, new_name, family, userdata, allow_closure, tag, icon, NULL, NULL); + + if (new_name != name) + free (new_name); + + return ret; +} + +int +chanview_get_size (chanview *cv) +{ + return cv->size; +} + +GtkWidget * +chanview_get_box (chanview *cv) +{ + return cv->box; +} + +void +chanview_move_focus (chanview *cv, gboolean relative, int num) +{ + cv->func_move_focus (cv, relative, num); +} + +GtkOrientation +chanview_get_orientation (chanview *cv) +{ + return (cv->vertical ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL); +} + +void +chanview_set_orientation (chanview *cv, gboolean vertical) +{ + if (vertical != cv->vertical) + { + cv->vertical = vertical; + cv->func_change_orientation (cv); + } +} + +int +chan_get_tag (chan *ch) +{ + return ch->tag; +} + +void * +chan_get_userdata (chan *ch) +{ + return ch->userdata; +} + +void +chan_focus (chan *ch) +{ + if (ch->cv->focused == ch) + return; + + ch->cv->func_focus (ch); +} + +void +chan_move (chan *ch, int delta) +{ + ch->cv->func_move (ch, delta); +} + +void +chan_move_family (chan *ch, int delta) +{ + ch->cv->func_move_family (ch, delta); +} + +void +chan_set_color (chan *ch, PangoAttrList *list) +{ + gtk_tree_store_set (ch->cv->store, &ch->iter, COL_ATTR, list, -1); + ch->cv->func_set_color (ch, list); +} + +void +chan_rename (chan *ch, char *name, int trunc_len) +{ + char *new_name; + + new_name = truncate_tab_name (name, trunc_len); + + gtk_tree_store_set (ch->cv->store, &ch->iter, COL_NAME, new_name, -1); + ch->cv->func_rename (ch, new_name); + ch->cv->trunc_len = trunc_len; + + if (new_name != name) + free (new_name); +} + +/* this thing is overly complicated */ + +static int +cv_find_number_of_chan (chanview *cv, chan *find_ch) +{ + GtkTreeIter iter, inner; + chan *ch; + int i = 0; + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (cv->store), &iter)) + { + do + { + gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &iter, COL_CHAN, &ch, -1); + if (ch == find_ch) + return i; + i++; + + if (gtk_tree_model_iter_children (GTK_TREE_MODEL (cv->store), &inner, &iter)) + { + do + { + gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &inner, COL_CHAN, &ch, -1); + if (ch == find_ch) + return i; + i++; + } + while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &inner)); + } + } + while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &iter)); + } + + return 0; /* WARNING */ +} + +/* this thing is overly complicated too */ + +static chan * +cv_find_chan_by_number (chanview *cv, int num) +{ + GtkTreeIter iter, inner; + chan *ch; + int i = 0; + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (cv->store), &iter)) + { + do + { + if (i == num) + { + gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &iter, COL_CHAN, &ch, -1); + return ch; + } + i++; + + if (gtk_tree_model_iter_children (GTK_TREE_MODEL (cv->store), &inner, &iter)) + { + do + { + if (i == num) + { + gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &inner, COL_CHAN, &ch, -1); + return ch; + } + i++; + } + while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &inner)); + } + } + while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &iter)); + } + + return NULL; +} + +static void +chan_emancipate_children (chan *ch) +{ + char *name; + chan *childch; + GtkTreeIter childiter; + PangoAttrList *attr; + + while (gtk_tree_model_iter_children (GTK_TREE_MODEL (ch->cv->store), &childiter, &ch->iter)) + { + /* remove and re-add all the children, but avoid using "ch" as parent */ + gtk_tree_model_get (GTK_TREE_MODEL (ch->cv->store), &childiter, + COL_NAME, &name, COL_CHAN, &childch, COL_ATTR, &attr, -1); + ch->cv->func_remove (childch); + gtk_tree_store_remove (ch->cv->store, &childiter); + ch->cv->size--; + chanview_add_real (childch->cv, name, childch->family, childch->userdata, childch->allow_closure, childch->tag, childch->icon, childch, ch); + if (attr) + { + childch->cv->func_set_color (childch, attr); + pango_attr_list_unref (attr); + } + g_free (name); + } +} + +gboolean +chan_remove (chan *ch, gboolean force) +{ + chan *new_ch; + int i, num; + extern int xchat_is_quitting; + + if (xchat_is_quitting) /* avoid lots of looping on exit */ + return TRUE; + + /* is this ch allowed to be closed while still having children? */ + if (!force && + gtk_tree_model_iter_has_child (GTK_TREE_MODEL (ch->cv->store), &ch->iter) && + !ch->allow_closure) + return FALSE; + + chan_emancipate_children (ch); + ch->cv->func_remove (ch); + + /* is it the focused one? */ + if (ch->cv->focused == ch) + { + ch->cv->focused = NULL; + + /* try to move the focus to some other valid channel */ + num = cv_find_number_of_chan (ch->cv, ch); + /* move to the one left of the closing tab */ + new_ch = cv_find_chan_by_number (ch->cv, num - 1); + if (new_ch && new_ch != ch) + { + chan_focus (new_ch); /* this'll will set ch->cv->focused for us too */ + } else + { + /* if it fails, try focus from tab 0 and up */ + for (i = 0; i < ch->cv->size; i++) + { + new_ch = cv_find_chan_by_number (ch->cv, i); + if (new_ch && new_ch != ch) + { + chan_focus (new_ch); /* this'll will set ch->cv->focused for us too */ + break; + } + } + } + } + + ch->cv->size--; + gtk_tree_store_remove (ch->cv->store, &ch->iter); + free (ch); + return TRUE; +} + +gboolean +chan_is_collapsed (chan *ch) +{ + return ch->cv->func_is_collapsed (ch); +} + +chan * +chan_get_parent (chan *ch) +{ + return ch->cv->func_get_parent (ch); +} diff --git a/src/fe-gtk/chanview.h b/src/fe-gtk/chanview.h new file mode 100644 index 00000000..75b5ef1f --- /dev/null +++ b/src/fe-gtk/chanview.h @@ -0,0 +1,31 @@ +typedef struct _chanview chanview; +typedef struct _chan chan; + +chanview *chanview_new (int type, int trunc_len, gboolean sort, gboolean use_icons, GtkStyle *style); +void chanview_set_callbacks (chanview *cv, + void (*cb_focus) (chanview *, chan *, int tag, void *userdata), + void (*cb_xbutton) (chanview *, chan *, int tag, void *userdata), + gboolean (*cb_contextmenu) (chanview *, chan *, int tag, void *userdata, GdkEventButton *), + int (*cb_compare) (void *a, void *b)); +void chanview_set_impl (chanview *cv, int type); +chan *chanview_add (chanview *cv, char *name, void *family, void *userdata, gboolean allow_closure, int tag, GdkPixbuf *icon); +int chanview_get_size (chanview *cv); +GtkWidget *chanview_get_box (chanview *cv); +void chanview_move_focus (chanview *cv, gboolean relative, int num); +GtkOrientation chanview_get_orientation (chanview *cv); +void chanview_set_orientation (chanview *cv, gboolean vertical); + +int chan_get_tag (chan *ch); +void *chan_get_userdata (chan *ch); +void chan_focus (chan *ch); +void chan_move (chan *ch, int delta); +void chan_move_family (chan *ch, int delta); +void chan_set_color (chan *ch, PangoAttrList *list); +void chan_rename (chan *ch, char *new_name, int trunc_len); +gboolean chan_remove (chan *ch, gboolean force); +gboolean chan_is_collapsed (chan *ch); +chan * chan_get_parent (chan *ch); + +#define FOCUS_NEW_ALL 1 +#define FOCUS_NEW_ONLY_ASKED 2 +#define FOCUS_NEW_NONE 0 diff --git a/src/fe-gtk/custom-list.c b/src/fe-gtk/custom-list.c new file mode 100644 index 00000000..ac20e0ff --- /dev/null +++ b/src/fe-gtk/custom-list.c @@ -0,0 +1,753 @@ +#include <string.h> +#include <stdlib.h> +#include "custom-list.h" + +/* indent -i3 -ci3 -ut -ts3 -bli0 -c0 custom-list.c */ + +/* boring declarations of local functions */ + +static void custom_list_init (CustomList * pkg_tree); + +static void custom_list_class_init (CustomListClass * klass); + +static void custom_list_tree_model_init (GtkTreeModelIface * iface); + +static void custom_list_finalize (GObject * object); + +static GtkTreeModelFlags custom_list_get_flags (GtkTreeModel * tree_model); + +static gint custom_list_get_n_columns (GtkTreeModel * tree_model); + +static GType custom_list_get_column_type (GtkTreeModel * tree_model, + gint index); + +static gboolean custom_list_get_iter (GtkTreeModel * tree_model, + GtkTreeIter * iter, GtkTreePath * path); + +static GtkTreePath *custom_list_get_path (GtkTreeModel * tree_model, + GtkTreeIter * iter); + +static void custom_list_get_value (GtkTreeModel * tree_model, + GtkTreeIter * iter, + gint column, GValue * value); + +static gboolean custom_list_iter_next (GtkTreeModel * tree_model, + GtkTreeIter * iter); + +static gboolean custom_list_iter_children (GtkTreeModel * tree_model, + GtkTreeIter * iter, + GtkTreeIter * parent); + +static gboolean custom_list_iter_has_child (GtkTreeModel * tree_model, + GtkTreeIter * iter); + +static gint custom_list_iter_n_children (GtkTreeModel * tree_model, + GtkTreeIter * iter); + +static gboolean custom_list_iter_nth_child (GtkTreeModel * tree_model, + GtkTreeIter * iter, + GtkTreeIter * parent, gint n); + +static gboolean custom_list_iter_parent (GtkTreeModel * tree_model, + GtkTreeIter * iter, + GtkTreeIter * child); + + /* -- GtkTreeSortable interface functions -- */ + +static gboolean custom_list_sortable_get_sort_column_id (GtkTreeSortable * + sortable, + gint * sort_col_id, + GtkSortType * order); + +static void custom_list_sortable_set_sort_column_id (GtkTreeSortable * + sortable, + gint sort_col_id, + GtkSortType order); + +static void custom_list_sortable_set_sort_func (GtkTreeSortable * sortable, + gint sort_col_id, + GtkTreeIterCompareFunc + sort_func, gpointer user_data, + GtkDestroyNotify + destroy_func); + +static void custom_list_sortable_set_default_sort_func (GtkTreeSortable * + sortable, + GtkTreeIterCompareFunc + sort_func, + gpointer user_data, + GtkDestroyNotify + destroy_func); + +static gboolean custom_list_sortable_has_default_sort_func (GtkTreeSortable * + sortable); + + + +static GObjectClass *parent_class = NULL; /* GObject stuff - nothing to worry about */ + + +static void +custom_list_sortable_init (GtkTreeSortableIface * iface) +{ + iface->get_sort_column_id = custom_list_sortable_get_sort_column_id; + iface->set_sort_column_id = custom_list_sortable_set_sort_column_id; + iface->set_sort_func = custom_list_sortable_set_sort_func; /* NOT SUPPORTED */ + iface->set_default_sort_func = custom_list_sortable_set_default_sort_func; /* NOT SUPPORTED */ + iface->has_default_sort_func = custom_list_sortable_has_default_sort_func; /* NOT SUPPORTED */ +} + +/***************************************************************************** + * + * custom_list_get_type: here we register our new type and its interfaces + * with the type system. If you want to implement + * additional interfaces like GtkTreeSortable, you + * will need to do it here. + * + *****************************************************************************/ + +static GType +custom_list_get_type (void) +{ + static GType custom_list_type = 0; + + if (custom_list_type) + return custom_list_type; + + /* Some boilerplate type registration stuff */ + if (1) + { + static const GTypeInfo custom_list_info = { + sizeof (CustomListClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) custom_list_class_init, + NULL, /* class finalize */ + NULL, /* class_data */ + sizeof (CustomList), + 0, /* n_preallocs */ + (GInstanceInitFunc) custom_list_init + }; + + custom_list_type = + g_type_register_static (G_TYPE_OBJECT, "CustomList", + &custom_list_info, (GTypeFlags) 0); + } + + /* Here we register our GtkTreeModel interface with the type system */ + if (1) + { + static const GInterfaceInfo tree_model_info = { + (GInterfaceInitFunc) custom_list_tree_model_init, + NULL, + NULL + }; + + g_type_add_interface_static (custom_list_type, GTK_TYPE_TREE_MODEL, + &tree_model_info); + } + + /* Add GtkTreeSortable interface */ + if (1) + { + static const GInterfaceInfo tree_sortable_info = { + (GInterfaceInitFunc) custom_list_sortable_init, + NULL, + NULL + }; + + g_type_add_interface_static (custom_list_type, + GTK_TYPE_TREE_SORTABLE, + &tree_sortable_info); + } + + return custom_list_type; +} + +/***************************************************************************** + * + * custom_list_class_init: more boilerplate GObject/GType stuff. + * Init callback for the type system, + * called once when our new class is created. + * + *****************************************************************************/ + +static void +custom_list_class_init (CustomListClass * klass) +{ + GObjectClass *object_class; + + parent_class = (GObjectClass *) g_type_class_peek_parent (klass); + object_class = (GObjectClass *) klass; + + object_class->finalize = custom_list_finalize; +} + +/***************************************************************************** + * + * custom_list_tree_model_init: init callback for the interface registration + * in custom_list_get_type. Here we override + * the GtkTreeModel interface functions that + * we implement. + * + *****************************************************************************/ + +static void +custom_list_tree_model_init (GtkTreeModelIface * iface) +{ + iface->get_flags = custom_list_get_flags; + iface->get_n_columns = custom_list_get_n_columns; + iface->get_column_type = custom_list_get_column_type; + iface->get_iter = custom_list_get_iter; + iface->get_path = custom_list_get_path; + iface->get_value = custom_list_get_value; + iface->iter_next = custom_list_iter_next; + iface->iter_children = custom_list_iter_children; + iface->iter_has_child = custom_list_iter_has_child; + iface->iter_n_children = custom_list_iter_n_children; + iface->iter_nth_child = custom_list_iter_nth_child; + iface->iter_parent = custom_list_iter_parent; +} + + +/***************************************************************************** + * + * custom_list_init: this is called everytime a new custom list object + * instance is created (we do that in custom_list_new). + * Initialise the list structure's fields here. + * + *****************************************************************************/ + +static void +custom_list_init (CustomList * custom_list) +{ + custom_list->n_columns = CUSTOM_LIST_N_COLUMNS; + + custom_list->column_types[0] = G_TYPE_STRING; /* CUSTOM_LIST_COL_NAME */ + custom_list->column_types[1] = G_TYPE_UINT; /* CUSTOM_LIST_COL_USERS */ + custom_list->column_types[2] = G_TYPE_STRING; /* CUSTOM_LIST_COL_TOPIC */ + + custom_list->num_rows = 0; + custom_list->num_alloc = 0; + custom_list->rows = NULL; + + custom_list->sort_id = SORT_ID_CHANNEL; + custom_list->sort_order = GTK_SORT_ASCENDING; +} + + +/***************************************************************************** + * + * custom_list_finalize: this is called just before a custom list is + * destroyed. Free dynamically allocated memory here. + * + *****************************************************************************/ + +static void +custom_list_finalize (GObject * object) +{ + custom_list_clear (CUSTOM_LIST (object)); + + /* must chain up - finalize parent */ + (*parent_class->finalize) (object); +} + + +/***************************************************************************** + * + * custom_list_get_flags: tells the rest of the world whether our tree model + * has any special characteristics. In our case, + * we have a list model (instead of a tree), and each + * tree iter is valid as long as the row in question + * exists, as it only contains a pointer to our struct. + * + *****************************************************************************/ + +static GtkTreeModelFlags +custom_list_get_flags (GtkTreeModel * tree_model) +{ + return (GTK_TREE_MODEL_LIST_ONLY /*| GTK_TREE_MODEL_ITERS_PERSIST */ ); +} + + +/***************************************************************************** + * + * custom_list_get_n_columns: tells the rest of the world how many data + * columns we export via the tree model interface + * + *****************************************************************************/ + +static gint +custom_list_get_n_columns (GtkTreeModel * tree_model) +{ + return 3;/*CUSTOM_LIST (tree_model)->n_columns;*/ +} + + +/***************************************************************************** + * + * custom_list_get_column_type: tells the rest of the world which type of + * data an exported model column contains + * + *****************************************************************************/ + +static GType +custom_list_get_column_type (GtkTreeModel * tree_model, gint index) +{ + return CUSTOM_LIST (tree_model)->column_types[index]; +} + + +/***************************************************************************** + * + * custom_list_get_iter: converts a tree path (physical position) into a + * tree iter structure (the content of the iter + * fields will only be used internally by our model). + * We simply store a pointer to our chanlistrow + * structure that represents that row in the tree iter. + * + *****************************************************************************/ + +static gboolean +custom_list_get_iter (GtkTreeModel * tree_model, + GtkTreeIter * iter, GtkTreePath * path) +{ + CustomList *custom_list = CUSTOM_LIST (tree_model); + chanlistrow *record; + gint n; + + n = gtk_tree_path_get_indices (path)[0]; + if (n >= custom_list->num_rows || n < 0) + return FALSE; + + record = custom_list->rows[n]; + + /* We simply store a pointer to our custom record in the iter */ + iter->user_data = record; + + return TRUE; +} + + +/***************************************************************************** + * + * custom_list_get_path: converts a tree iter into a tree path (ie. the + * physical position of that row in the list). + * + *****************************************************************************/ + +static GtkTreePath * +custom_list_get_path (GtkTreeModel * tree_model, GtkTreeIter * iter) +{ + GtkTreePath *path; + chanlistrow *record; + + record = (chanlistrow *) iter->user_data; + + path = gtk_tree_path_new (); + gtk_tree_path_append_index (path, record->pos); + + return path; +} + + +/***************************************************************************** + * + * custom_list_get_value: Returns a row's exported data columns + * (_get_value is what gtk_tree_model_get uses) + * + *****************************************************************************/ + +static void +custom_list_get_value (GtkTreeModel * tree_model, + GtkTreeIter * iter, gint column, GValue * value) +{ + chanlistrow *record; + CustomList *custom_list = CUSTOM_LIST (tree_model); + + if (custom_list->num_rows == 0) + return; + + g_value_init (value, custom_list->column_types[column]); + + record = (chanlistrow *) iter->user_data; + + switch (column) + { + case CUSTOM_LIST_COL_NAME: + g_value_set_static_string (value, GET_CHAN (record)); + break; + + case CUSTOM_LIST_COL_USERS: + g_value_set_uint (value, record->users); + break; + + case CUSTOM_LIST_COL_TOPIC: + g_value_set_static_string (value, record->topic); + break; + } +} + + +/***************************************************************************** + * + * custom_list_iter_next: Takes an iter structure and sets it to point + * to the next row. + * + *****************************************************************************/ + +static gboolean +custom_list_iter_next (GtkTreeModel * tree_model, GtkTreeIter * iter) +{ + chanlistrow *record, *nextrecord; + CustomList *custom_list = CUSTOM_LIST (tree_model); + + record = (chanlistrow *) iter->user_data; + + /* Is this the last record in the list? */ + if ((record->pos + 1) >= custom_list->num_rows) + return FALSE; + + nextrecord = custom_list->rows[(record->pos + 1)]; + + g_assert (nextrecord != NULL); + g_assert (nextrecord->pos == (record->pos + 1)); + + iter->user_data = nextrecord; + + return TRUE; +} + + +/***************************************************************************** + * + * custom_list_iter_children: Returns TRUE or FALSE depending on whether + * the row specified by 'parent' has any children. + * If it has children, then 'iter' is set to + * point to the first child. Special case: if + * 'parent' is NULL, then the first top-level + * row should be returned if it exists. + * + *****************************************************************************/ + +static gboolean +custom_list_iter_children (GtkTreeModel * tree_model, + GtkTreeIter * iter, GtkTreeIter * parent) +{ + CustomList *custom_list = CUSTOM_LIST (tree_model); + + /* this is a list, nodes have no children */ + if (parent) + return FALSE; + + /* parent == NULL is a special case; we need to return the first top-level row */ + /* No rows => no first row */ + if (custom_list->num_rows == 0) + return FALSE; + + /* Set iter to first item in list */ + iter->user_data = custom_list->rows[0]; + + return TRUE; +} + + +/***************************************************************************** + * + * custom_list_iter_has_child: Returns TRUE or FALSE depending on whether + * the row specified by 'iter' has any children. + * We only have a list and thus no children. + * + *****************************************************************************/ + +static gboolean +custom_list_iter_has_child (GtkTreeModel * tree_model, GtkTreeIter * iter) +{ + return FALSE; +} + + +/***************************************************************************** + * + * custom_list_iter_n_children: Returns the number of children the row + * specified by 'iter' has. This is usually 0, + * as we only have a list and thus do not have + * any children to any rows. A special case is + * when 'iter' is NULL, in which case we need + * to return the number of top-level nodes, + * ie. the number of rows in our list. + * + *****************************************************************************/ + +static gint +custom_list_iter_n_children (GtkTreeModel * tree_model, GtkTreeIter * iter) +{ + CustomList *custom_list = CUSTOM_LIST (tree_model); + + /* special case: if iter == NULL, return number of top-level rows */ + if (!iter) + return custom_list->num_rows; + + return 0; /* otherwise, this is easy again for a list */ +} + + +/***************************************************************************** + * + * custom_list_iter_nth_child: If the row specified by 'parent' has any + * children, set 'iter' to the n-th child and + * return TRUE if it exists, otherwise FALSE. + * A special case is when 'parent' is NULL, in + * which case we need to set 'iter' to the n-th + * row if it exists. + * + *****************************************************************************/ + +static gboolean +custom_list_iter_nth_child (GtkTreeModel * tree_model, + GtkTreeIter * iter, GtkTreeIter * parent, gint n) +{ + CustomList *custom_list = CUSTOM_LIST (tree_model); + + /* a list has only top-level rows */ + if (parent) + return FALSE; + + /* special case: if parent == NULL, set iter to n-th top-level row */ + if (n >= custom_list->num_rows) + return FALSE; + + iter->user_data = custom_list->rows[n]; + return TRUE; +} + + +/***************************************************************************** + * + * custom_list_iter_parent: Point 'iter' to the parent node of 'child'. As + * we have a list and thus no children and no + * parents of children, we can just return FALSE. + * + *****************************************************************************/ + +static gboolean +custom_list_iter_parent (GtkTreeModel * tree_model, + GtkTreeIter * iter, GtkTreeIter * child) +{ + return FALSE; +} + +static gboolean +custom_list_sortable_get_sort_column_id (GtkTreeSortable * sortable, + gint * sort_col_id, + GtkSortType * order) +{ + CustomList *custom_list = CUSTOM_LIST (sortable); + + if (sort_col_id) + *sort_col_id = custom_list->sort_id; + + if (order) + *order = custom_list->sort_order; + + return TRUE; +} + + +static void +custom_list_sortable_set_sort_column_id (GtkTreeSortable * sortable, + gint sort_col_id, GtkSortType order) +{ + CustomList *custom_list = CUSTOM_LIST (sortable); + + if (custom_list->sort_id == sort_col_id + && custom_list->sort_order == order) + return; + + custom_list->sort_id = sort_col_id; + custom_list->sort_order = order; + + custom_list_resort (custom_list); + + /* emit "sort-column-changed" signal to tell any tree views + * that the sort column has changed (so the little arrow + * in the column header of the sort column is drawn + * in the right column) */ + + gtk_tree_sortable_sort_column_changed (sortable); +} + +static void +custom_list_sortable_set_sort_func (GtkTreeSortable * sortable, + gint sort_col_id, + GtkTreeIterCompareFunc sort_func, + gpointer user_data, + GtkDestroyNotify destroy_func) +{ +} + +static void +custom_list_sortable_set_default_sort_func (GtkTreeSortable * sortable, + GtkTreeIterCompareFunc sort_func, + gpointer user_data, + GtkDestroyNotify destroy_func) +{ +} + +static gboolean +custom_list_sortable_has_default_sort_func (GtkTreeSortable * sortable) +{ + return FALSE; +} + +/* fast as possible compare func for sorting. + TODO: If fast enough, use a unicode collation key and strcmp. */ + +#define TOSML(c) (((c) >= 'A' && (c) <= 'Z') ? (c) - 'A' + 'a' : (c)) + +static inline int +fast_ascii_stricmp (const char *s1, const char *s2) +{ + int c1, c2; + + while (*s1 && *s2) + { + c1 = (int) (unsigned char) TOSML (*s1); + c2 = (int) (unsigned char) TOSML (*s2); + if (c1 != c2) + return (c1 - c2); + s1++; + s2++; + } + + return (((int) (unsigned char) *s1) - ((int) (unsigned char) *s2)); +} + +static gint +custom_list_qsort_compare_func (chanlistrow ** a, chanlistrow ** b, + CustomList * custom_list) +{ + if (custom_list->sort_order == GTK_SORT_DESCENDING) + { + chanlistrow **tmp = a; + a = b; + b = tmp; + } + + if (custom_list->sort_id == SORT_ID_USERS) + { + return (*a)->users - (*b)->users; + } + + if (custom_list->sort_id == SORT_ID_TOPIC) + { + return fast_ascii_stricmp ((*a)->topic, (*b)->topic); + } + + return strcmp ((*a)->collation_key, (*b)->collation_key); +} + +/***************************************************************************** + * + * custom_list_new: This is what you use in your own code to create a + * new custom list tree model for you to use. + * + *****************************************************************************/ + +CustomList * +custom_list_new (void) +{ + return (CustomList *) g_object_new (CUSTOM_TYPE_LIST, NULL); +} + +void +custom_list_append (CustomList * custom_list, chanlistrow * newrecord) +{ + GtkTreeIter iter; + GtkTreePath *path; + gulong newsize; + guint pos; + + if (custom_list->num_rows >= custom_list->num_alloc) + { + custom_list->num_alloc += 64; + newsize = custom_list->num_alloc * sizeof (chanlistrow *); + custom_list->rows = g_realloc (custom_list->rows, newsize); + } + + /* TODO: Binary search insert? */ + + pos = custom_list->num_rows; + custom_list->rows[pos] = newrecord; + custom_list->num_rows++; + newrecord->pos = pos; + + /* inform the tree view and other interested objects + * (e.g. tree row references) that we have inserted + * a new row, and where it was inserted */ + + path = gtk_tree_path_new (); + gtk_tree_path_append_index (path, newrecord->pos); +/* custom_list_get_iter(GTK_TREE_MODEL(custom_list), &iter, path);*/ + iter.user_data = newrecord; + gtk_tree_model_row_inserted (GTK_TREE_MODEL (custom_list), path, &iter); + gtk_tree_path_free (path); +} + +void +custom_list_resort (CustomList * custom_list) +{ + GtkTreePath *path; + gint *neworder, i; + + if (custom_list->num_rows < 2) + return; + + /* resort */ + g_qsort_with_data (custom_list->rows, + custom_list->num_rows, + sizeof (chanlistrow *), + (GCompareDataFunc) custom_list_qsort_compare_func, + custom_list); + + /* let other objects know about the new order */ + neworder = malloc (sizeof (gint) * custom_list->num_rows); + + for (i = custom_list->num_rows - 1; i >= 0; i--) + { + /* Note that the API reference might be wrong about + * this, see bug number 124790 on bugs.gnome.org. + * Both will work, but one will give you 'jumpy' + * selections after row reordering. */ + /* neworder[(custom_list->rows[i])->pos] = i; */ + neworder[i] = (custom_list->rows[i])->pos; + (custom_list->rows[i])->pos = i; + } + + path = gtk_tree_path_new (); + gtk_tree_model_rows_reordered (GTK_TREE_MODEL (custom_list), path, NULL, + neworder); + gtk_tree_path_free (path); + free (neworder); +} + +void +custom_list_clear (CustomList * custom_list) +{ + int i, max = custom_list->num_rows - 1; + GtkTreePath *path; + + for (i = max; i >= 0; i--) + { + path = gtk_tree_path_new (); + gtk_tree_path_append_index (path, custom_list->rows[i]->pos); + gtk_tree_model_row_deleted (GTK_TREE_MODEL (custom_list), path); + gtk_tree_path_free (path); + } + + custom_list->num_rows = 0; + custom_list->num_alloc = 0; + + g_free (custom_list->rows); + custom_list->rows = NULL; +} diff --git a/src/fe-gtk/custom-list.h b/src/fe-gtk/custom-list.h new file mode 100644 index 00000000..d9e4f09e --- /dev/null +++ b/src/fe-gtk/custom-list.h @@ -0,0 +1,85 @@ +#ifndef _custom_list_h_included_ +#define _custom_list_h_included_ + +#include <gtk/gtk.h> + +/* Some boilerplate GObject defines. 'klass' is used + * instead of 'class', because 'class' is a C++ keyword */ + +#define CUSTOM_TYPE_LIST (custom_list_get_type ()) +#define CUSTOM_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CUSTOM_TYPE_LIST, CustomList)) +#define CUSTOM_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CUSTOM_TYPE_LIST, CustomListClass)) +#define CUSTOM_IS_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CUSTOM_TYPE_LIST)) +#define CUSTOM_IS_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CUSTOM_TYPE_LIST)) +#define CUSTOM_LIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CUSTOM_TYPE_LIST, CustomListClass)) + +/* The data columns that we export via the tree model interface */ + +enum +{ + CUSTOM_LIST_COL_NAME, + CUSTOM_LIST_COL_USERS, + CUSTOM_LIST_COL_TOPIC, + CUSTOM_LIST_N_COLUMNS +}; + +enum +{ + SORT_ID_CHANNEL, + SORT_ID_USERS, + SORT_ID_TOPIC +}; + +typedef struct +{ + char *topic; + char *collation_key; + guint32 pos; /* pos within the array */ + guint32 users; + /* channel string lives beyond "users" */ +#define GET_CHAN(row) (((char *)row)+sizeof(chanlistrow)) +} +chanlistrow; + +typedef struct _CustomList CustomList; +typedef struct _CustomListClass CustomListClass; + + + +/* CustomList: this structure contains everything we need for our + * model implementation. You can add extra fields to + * this structure, e.g. hashtables to quickly lookup + * rows or whatever else you might need, but it is + * crucial that 'parent' is the first member of the + * structure. */ +struct _CustomList +{ + GObject parent; + + guint num_rows; /* number of rows that we have used */ + guint num_alloc; /* number of rows allocated */ + chanlistrow **rows; /* a dynamically allocated array of pointers to the + * CustomRecord structure for each row */ + + gint n_columns; + GType column_types[CUSTOM_LIST_N_COLUMNS]; + + gint sort_id; + GtkSortType sort_order; +}; + + +/* CustomListClass: more boilerplate GObject stuff */ + +struct _CustomListClass +{ + GObjectClass parent_class; +}; + + +CustomList *custom_list_new (void); +void custom_list_append (CustomList *, chanlistrow *); +void custom_list_resort (CustomList *); +void custom_list_clear (CustomList *); + +#endif /* _custom_list_h_included_ */ diff --git a/src/fe-gtk/dccgui.c b/src/fe-gtk/dccgui.c new file mode 100644 index 00000000..61f6d502 --- /dev/null +++ b/src/fe-gtk/dccgui.c @@ -0,0 +1,1098 @@ +/* X-Chat + * Copyright (C) 1998-2006 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <time.h> + +#define WANTSOCKET +#define WANTARPA +#include "../common/inet.h" +#include "fe-gtk.h" + +#include <gtk/gtkhbbox.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkmessagedialog.h> +#include <gtk/gtktable.h> +#include <gtk/gtktreeview.h> +#include <gtk/gtkexpander.h> +#include <gtk/gtkliststore.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtkcellrendererpixbuf.h> +#include <gtk/gtkcellrenderertext.h> +#include <gtk/gtkcheckmenuitem.h> +#include <gtk/gtkradiobutton.h> +#include <gtk/gtkversion.h> + +#include "../common/xchat.h" +#include "../common/xchatc.h" +#include "../common/fe.h" +#include "../common/util.h" +#include "../common/network.h" +#include "gtkutil.h" +#include "palette.h" +#include "maingui.h" + + +enum /* DCC SEND/RECV */ +{ + COL_TYPE, + COL_STATUS, + COL_FILE, + COL_SIZE, + COL_POS, + COL_PERC, + COL_SPEED, + COL_ETA, + COL_NICK, + COL_DCC, /* struct DCC * */ + COL_COLOR, /* GdkColor */ + N_COLUMNS +}; + +enum /* DCC CHAT */ +{ + CCOL_STATUS, + CCOL_NICK, + CCOL_RECV, + CCOL_SENT, + CCOL_START, + CCOL_DCC, /* struct DCC * */ + CCOL_COLOR, /* GdkColor * */ + CN_COLUMNS +}; + +struct dccwindow +{ + GtkWidget *window; + + GtkWidget *list; + GtkListStore *store; + GtkTreeSelection *sel; + + GtkWidget *abort_button; + GtkWidget *accept_button; + GtkWidget *resume_button; + GtkWidget *open_button; + + GtkWidget *file_label; + GtkWidget *address_label; +}; + +struct my_dcc_send +{ + struct session *sess; + char *nick; + int maxcps; + int passive; +}; + +static struct dccwindow dccfwin = {NULL, }; /* file */ +static struct dccwindow dcccwin = {NULL, }; /* chat */ +static GdkPixbuf *pix_up = NULL; /* down arrow */ +static GdkPixbuf *pix_dn = NULL; /* up arrow */ +static int win_width = 600; +static int win_height = 256; +static short view_mode; /* 1=download 2=upload 3=both */ +#define VIEW_DOWNLOAD 1 +#define VIEW_UPLOAD 2 +#define VIEW_BOTH 3 + +#define KILOBYTE 1024 +#define MEGABYTE (KILOBYTE * 1024) +#define GIGABYTE (MEGABYTE * 1024) + + +static void +proper_unit (DCC_SIZE size, char *buf, int buf_len) +{ + if (size <= KILOBYTE) + { + snprintf (buf, buf_len, "%"DCC_SFMT"B", size); + } + else if (size > KILOBYTE && size <= MEGABYTE) + { + snprintf (buf, buf_len, "%"DCC_SFMT"kB", size / KILOBYTE); + } + else + { + snprintf (buf, buf_len, "%.2fMB", (float)size / MEGABYTE); + } +} + +static void +dcc_send_filereq_file (struct my_dcc_send *mdc, char *file) +{ + if (file) + dcc_send (mdc->sess, mdc->nick, file, mdc->maxcps, mdc->passive); + else + { + free (mdc->nick); + free (mdc); + } +} + +void +fe_dcc_send_filereq (struct session *sess, char *nick, int maxcps, int passive) +{ + char tbuf[128]; + struct my_dcc_send *mdc; + + mdc = malloc (sizeof (*mdc)); + mdc->sess = sess; + mdc->nick = strdup (nick); + mdc->maxcps = maxcps; + mdc->passive = passive; + + snprintf (tbuf, sizeof tbuf, _("Send file to %s"), nick); + gtkutil_file_req (tbuf, dcc_send_filereq_file, mdc, NULL, FRF_MULTIPLE); +} + +static void +dcc_prepare_row_chat (struct DCC *dcc, GtkListStore *store, GtkTreeIter *iter, + gboolean update_only) +{ + static char pos[16], siz[16]; + char *date; + + date = ctime (&dcc->starttime); + date[strlen (date) - 1] = 0; /* remove the \n */ + + proper_unit (dcc->pos, pos, sizeof (pos)); + proper_unit (dcc->size, siz, sizeof (siz)); + + gtk_list_store_set (store, iter, + CCOL_STATUS, _(dccstat[dcc->dccstat].name), + CCOL_NICK, dcc->nick, + CCOL_RECV, pos, + CCOL_SENT, siz, + CCOL_START, date, + CCOL_DCC, dcc, + CCOL_COLOR, + dccstat[dcc->dccstat].color == 1 ? + NULL : + colors + dccstat[dcc->dccstat].color, + -1); +} + +static void +dcc_prepare_row_send (struct DCC *dcc, GtkListStore *store, GtkTreeIter *iter, + gboolean update_only) +{ + static char pos[16], size[16], kbs[14], perc[14], eta[14]; + int to_go; + float per; + + if (!pix_up) + pix_up = gtk_widget_render_icon (dccfwin.window, "gtk-go-up", + GTK_ICON_SIZE_MENU, NULL); + + /* percentage ack'ed */ + per = (float) ((dcc->ack * 100.00) / dcc->size); + proper_unit (dcc->size, size, sizeof (size)); + proper_unit (dcc->pos, pos, sizeof (pos)); + snprintf (kbs, sizeof (kbs), "%.1f", ((float)dcc->cps) / 1024); +/* proper_unit (dcc->ack, ack, sizeof (ack));*/ + snprintf (perc, sizeof (perc), "%.0f%%", per); + if (dcc->cps != 0) + { + to_go = (dcc->size - dcc->ack) / dcc->cps; + snprintf (eta, sizeof (eta), "%.2d:%.2d:%.2d", + to_go / 3600, (to_go / 60) % 60, to_go % 60); + } else + strcpy (eta, "--:--:--"); + + if (update_only) + gtk_list_store_set (store, iter, + COL_STATUS, _(dccstat[dcc->dccstat].name), + COL_POS, pos, + COL_PERC, perc, + COL_SPEED, kbs, + COL_ETA, eta, + COL_COLOR, + dccstat[dcc->dccstat].color == 1 ? + NULL : + colors + dccstat[dcc->dccstat].color, + -1); + else + gtk_list_store_set (store, iter, + COL_TYPE, pix_up, + COL_STATUS, _(dccstat[dcc->dccstat].name), + COL_FILE, file_part (dcc->file), + COL_SIZE, size, + COL_POS, pos, + COL_PERC, perc, + COL_SPEED, kbs, + COL_ETA, eta, + COL_NICK, dcc->nick, + COL_DCC, dcc, + COL_COLOR, + dccstat[dcc->dccstat].color == 1 ? + NULL : + colors + dccstat[dcc->dccstat].color, + -1); +} + +static void +dcc_prepare_row_recv (struct DCC *dcc, GtkListStore *store, GtkTreeIter *iter, + gboolean update_only) +{ + static char size[16], pos[16], kbs[16], perc[14], eta[16]; + float per; + int to_go; + + if (!pix_dn) + pix_dn = gtk_widget_render_icon (dccfwin.window, "gtk-go-down", + GTK_ICON_SIZE_MENU, NULL); + + proper_unit (dcc->size, size, sizeof (size)); + if (dcc->dccstat == STAT_QUEUED) + proper_unit (dcc->resumable, pos, sizeof (pos)); + else + proper_unit (dcc->pos, pos, sizeof (pos)); + snprintf (kbs, sizeof (kbs), "%.1f", ((float)dcc->cps) / 1024); + /* percentage recv'ed */ + per = (float) ((dcc->pos * 100.00) / dcc->size); + snprintf (perc, sizeof (perc), "%.0f%%", per); + if (dcc->cps != 0) + { + to_go = (dcc->size - dcc->pos) / dcc->cps; + snprintf (eta, sizeof (eta), "%.2d:%.2d:%.2d", + to_go / 3600, (to_go / 60) % 60, to_go % 60); + } else + strcpy (eta, "--:--:--"); + + if (update_only) + gtk_list_store_set (store, iter, + COL_STATUS, _(dccstat[dcc->dccstat].name), + COL_POS, pos, + COL_PERC, perc, + COL_SPEED, kbs, + COL_ETA, eta, + COL_COLOR, + dccstat[dcc->dccstat].color == 1 ? + NULL : + colors + dccstat[dcc->dccstat].color, + -1); + else + gtk_list_store_set (store, iter, + COL_TYPE, pix_dn, + COL_STATUS, _(dccstat[dcc->dccstat].name), + COL_FILE, file_part (dcc->file), + COL_SIZE, size, + COL_POS, pos, + COL_PERC, perc, + COL_SPEED, kbs, + COL_ETA, eta, + COL_NICK, dcc->nick, + COL_DCC, dcc, + COL_COLOR, + dccstat[dcc->dccstat].color == 1 ? + NULL : + colors + dccstat[dcc->dccstat].color, + -1); +} + +static gboolean +dcc_find_row (struct DCC *find_dcc, GtkTreeModel *model, GtkTreeIter *iter, int col) +{ + struct DCC *dcc; + + if (gtk_tree_model_get_iter_first (model, iter)) + { + do + { + gtk_tree_model_get (model, iter, col, &dcc, -1); + if (dcc == find_dcc) + return TRUE; + } + while (gtk_tree_model_iter_next (model, iter)); + } + + return FALSE; +} + +static void +dcc_update_recv (struct DCC *dcc) +{ + GtkTreeIter iter; + + if (!dccfwin.window) + return; + + if (!dcc_find_row (dcc, GTK_TREE_MODEL (dccfwin.store), &iter, COL_DCC)) + return; + + dcc_prepare_row_recv (dcc, dccfwin.store, &iter, TRUE); +} + +static void +dcc_update_chat (struct DCC *dcc) +{ + GtkTreeIter iter; + + if (!dcccwin.window) + return; + + if (!dcc_find_row (dcc, GTK_TREE_MODEL (dcccwin.store), &iter, CCOL_DCC)) + return; + + dcc_prepare_row_chat (dcc, dcccwin.store, &iter, TRUE); +} + +static void +dcc_update_send (struct DCC *dcc) +{ + GtkTreeIter iter; + + if (!dccfwin.window) + return; + + if (!dcc_find_row (dcc, GTK_TREE_MODEL (dccfwin.store), &iter, COL_DCC)) + return; + + dcc_prepare_row_send (dcc, dccfwin.store, &iter, TRUE); +} + +static void +close_dcc_file_window (GtkWindow *win, gpointer data) +{ + dccfwin.window = NULL; +} + +static void +dcc_append (struct DCC *dcc, GtkListStore *store, gboolean prepend) +{ + GtkTreeIter iter; + + if (prepend) + gtk_list_store_prepend (store, &iter); + else + gtk_list_store_append (store, &iter); + + if (dcc->type == TYPE_RECV) + dcc_prepare_row_recv (dcc, store, &iter, FALSE); + else + dcc_prepare_row_send (dcc, store, &iter, FALSE); +} + +static void +dcc_fill_window (int flags) +{ + struct DCC *dcc; + GSList *list; + GtkTreeIter iter; + int i = 0; + + gtk_list_store_clear (GTK_LIST_STORE (dccfwin.store)); + + if (flags & VIEW_UPLOAD) + { + list = dcc_list; + while (list) + { + dcc = list->data; + if (dcc->type == TYPE_SEND) + { + dcc_append (dcc, dccfwin.store, FALSE); + i++; + } + list = list->next; + } + } + + if (flags & VIEW_DOWNLOAD) + { + list = dcc_list; + while (list) + { + dcc = list->data; + if (dcc->type == TYPE_RECV) + { + dcc_append (dcc, dccfwin.store, FALSE); + i++; + } + list = list->next; + } + } + + /* if only one entry, select it (so Accept button can work) */ + if (i == 1) + { + gtk_tree_model_get_iter_first (GTK_TREE_MODEL (dccfwin.store), &iter); + gtk_tree_selection_select_iter (dccfwin.sel, &iter); + } +} + +/* return list of selected DCCs */ + +static GSList * +treeview_get_selected (GtkTreeModel *model, GtkTreeSelection *sel, int column) +{ + GtkTreeIter iter; + GSList *list = NULL; + void *ptr; + + if (gtk_tree_model_get_iter_first (model, &iter)) + { + do + { + if (gtk_tree_selection_iter_is_selected (sel, &iter)) + { + gtk_tree_model_get (model, &iter, column, &ptr, -1); + list = g_slist_prepend (list, ptr); + } + } + while (gtk_tree_model_iter_next (model, &iter)); + } + + return g_slist_reverse (list); +} + +static GSList * +dcc_get_selected (void) +{ + return treeview_get_selected (GTK_TREE_MODEL (dccfwin.store), + dccfwin.sel, COL_DCC); +} + +static void +resume_clicked (GtkWidget * wid, gpointer none) +{ + struct DCC *dcc; + char buf[512]; + GSList *list; + + list = dcc_get_selected (); + if (!list) + return; + dcc = list->data; + g_slist_free (list); + + if (dcc->type == TYPE_RECV && !dcc_resume (dcc)) + { + switch (dcc->resume_error) + { + case 0: /* unknown error */ + fe_message (_("That file is not resumable."), FE_MSG_ERROR); + break; + case 1: + snprintf (buf, sizeof (buf), + _( "Cannot access file: %s\n" + "%s.\n" + "Resuming not possible."), dcc->destfile, + errorstring (dcc->resume_errno)); + fe_message (buf, FE_MSG_ERROR); + break; + case 2: + fe_message (_("File in download directory is larger " + "than file offered. Resuming not possible."), FE_MSG_ERROR); + break; + case 3: + fe_message (_("Cannot resume the same file from two people."), FE_MSG_ERROR); + } + } +} + +static void +abort_clicked (GtkWidget * wid, gpointer none) +{ + struct DCC *dcc; + GSList *start, *list; + + start = list = dcc_get_selected (); + for (; list; list = list->next) + { + dcc = list->data; + dcc_abort (dcc->serv->front_session, dcc); + } + g_slist_free (start); +} + +static void +accept_clicked (GtkWidget * wid, gpointer none) +{ + struct DCC *dcc; + GSList *start, *list; + + start = list = dcc_get_selected (); + for (; list; list = list->next) + { + dcc = list->data; + if (dcc->type != TYPE_SEND) + dcc_get (dcc); + } + g_slist_free (start); +} + +static void +browse_folder (char *dir) +{ +#ifdef WIN32 + /* no need for file:// in ShellExecute() */ + fe_open_url (dir); +#else + char buf[512]; + + snprintf (buf, sizeof (buf), "file://%s", dir); + fe_open_url (buf); +#endif +} + +static void +browse_dcc_folder (void) +{ + if (prefs.dcc_completed_dir[0]) + browse_folder (prefs.dcc_completed_dir); + else + browse_folder (prefs.dccdir); +} + +static void +dcc_details_populate (struct DCC *dcc) +{ + char buf[128]; + + if (!dcc) + { + gtk_label_set_text (GTK_LABEL (dccfwin.file_label), NULL); + gtk_label_set_text (GTK_LABEL (dccfwin.address_label), NULL); + return; + } + + /* full path */ + if (dcc->type == TYPE_RECV) + gtk_label_set_text (GTK_LABEL (dccfwin.file_label), dcc->destfile); + else + gtk_label_set_text (GTK_LABEL (dccfwin.file_label), dcc->file); + + /* address and port */ + snprintf (buf, sizeof (buf), "%s : %d", net_ip (dcc->addr), dcc->port); + gtk_label_set_text (GTK_LABEL (dccfwin.address_label), buf); +} + +static void +dcc_row_cb (GtkTreeSelection *sel, gpointer user_data) +{ + struct DCC *dcc; + GSList *list; + + list = dcc_get_selected (); + if (!list) + { + gtk_widget_set_sensitive (dccfwin.accept_button, FALSE); + gtk_widget_set_sensitive (dccfwin.resume_button, FALSE); + gtk_widget_set_sensitive (dccfwin.abort_button, FALSE); + dcc_details_populate (NULL); + return; + } + + gtk_widget_set_sensitive (dccfwin.abort_button, TRUE); + + if (list->next) /* multi selection */ + { + gtk_widget_set_sensitive (dccfwin.accept_button, TRUE); + gtk_widget_set_sensitive (dccfwin.resume_button, TRUE); + dcc_details_populate (list->data); + } + else + { + /* turn OFF/ON appropriate buttons */ + dcc = list->data; + if (dcc->dccstat == STAT_QUEUED && dcc->type == TYPE_RECV) + { + gtk_widget_set_sensitive (dccfwin.accept_button, TRUE); + gtk_widget_set_sensitive (dccfwin.resume_button, TRUE); + } + else + { + gtk_widget_set_sensitive (dccfwin.accept_button, FALSE); + gtk_widget_set_sensitive (dccfwin.resume_button, FALSE); + } + + dcc_details_populate (dcc); + } + + g_slist_free (list); +} + +static void +dcc_dclick_cb (GtkTreeView *view, GtkTreePath *path, + GtkTreeViewColumn *column, gpointer data) +{ + struct DCC *dcc; + GSList *list; + + list = dcc_get_selected (); + if (!list) + return; + dcc = list->data; + g_slist_free (list); + + if (dcc->type == TYPE_RECV) + { + accept_clicked (0, 0); + return; + } + + switch (dcc->dccstat) + { + case STAT_FAILED: + case STAT_ABORTED: + case STAT_DONE: + dcc_abort (dcc->serv->front_session, dcc); + } +} + +static void +dcc_add_column (GtkWidget *tree, int textcol, int colorcol, char *title, gboolean right_justified) +{ + GtkCellRenderer *renderer; + + renderer = gtk_cell_renderer_text_new (); + if (right_justified) + g_object_set (G_OBJECT (renderer), "xalign", (float) 1.0, NULL); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree), -1, title, renderer, + "text", textcol, "foreground-gdk", colorcol, + NULL); + gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer), 1); +} + +static GtkWidget * +dcc_detail_label (char *text, GtkWidget *box, int num) +{ + GtkWidget *label; + char buf[64]; + + label = gtk_label_new (NULL); + snprintf (buf, sizeof (buf), "<b>%s</b>", text); + gtk_label_set_markup (GTK_LABEL (label), buf); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_table_attach (GTK_TABLE (box), label, 0, 1, 0 + num, 1 + num, GTK_FILL, GTK_FILL, 0, 0); + + label = gtk_label_new (NULL); + gtk_label_set_selectable (GTK_LABEL (label), TRUE); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_table_attach (GTK_TABLE (box), label, 1, 2, 0 + num, 1 + num, GTK_FILL, GTK_FILL, 0, 0); + + return label; +} + +static void +dcc_exp_cb (GtkWidget *exp, GtkWidget *box) +{ +#if GTK_CHECK_VERSION(2,20,0) + if (gtk_widget_get_visible (box)) +#else + if (GTK_WIDGET_VISIBLE (box)) +#endif + gtk_widget_hide (box); + else + gtk_widget_show (box); +} + +static void +dcc_toggle (GtkWidget *item, gpointer data) +{ + if (GTK_TOGGLE_BUTTON (item)->active) + { + view_mode = GPOINTER_TO_INT (data); + dcc_fill_window (GPOINTER_TO_INT (data)); + } +} + +static gboolean +dcc_configure_cb (GtkWindow *win, GdkEventConfigure *event, gpointer data) +{ + /* remember the window size */ + gtk_window_get_size (win, &win_width, &win_height); + return FALSE; +} + +int +fe_dcc_open_recv_win (int passive) +{ + GtkWidget *radio, *table, *vbox, *bbox, *view, *exp, *detailbox; + GtkListStore *store; + GSList *group; + + if (dccfwin.window) + { + if (!passive) + mg_bring_tofront (dccfwin.window); + return TRUE; + } + dccfwin.window = mg_create_generic_tab ("Transfers", _("XChat: Uploads and Downloads"), + FALSE, TRUE, close_dcc_file_window, NULL, + win_width, win_height, &vbox, 0); + gtk_container_set_border_width (GTK_CONTAINER (dccfwin.window), 3); + gtk_box_set_spacing (GTK_BOX (vbox), 3); + + store = gtk_list_store_new (N_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_POINTER, GDK_TYPE_COLOR); + view = gtkutil_treeview_new (vbox, GTK_TREE_MODEL (store), NULL, -1); + gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE); + /* Up/Down Icon column */ + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, NULL, + gtk_cell_renderer_pixbuf_new (), + "pixbuf", COL_TYPE, NULL); + dcc_add_column (view, COL_STATUS, COL_COLOR, _("Status"), FALSE); + dcc_add_column (view, COL_FILE, COL_COLOR, _("File"), FALSE); + dcc_add_column (view, COL_SIZE, COL_COLOR, _("Size"), TRUE); + dcc_add_column (view, COL_POS, COL_COLOR, _("Position"), TRUE); + dcc_add_column (view, COL_PERC, COL_COLOR, "%", TRUE); + dcc_add_column (view, COL_SPEED, COL_COLOR, "KB/s", TRUE); + dcc_add_column (view, COL_ETA, COL_COLOR, _("ETA"), FALSE); + dcc_add_column (view, COL_NICK, COL_COLOR, _("Nick"), FALSE); + + gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), COL_FILE), TRUE); + gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), COL_NICK), TRUE); + + dccfwin.list = view; + dccfwin.store = store; + dccfwin.sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); + view_mode = VIEW_BOTH; + gtk_tree_selection_set_mode (dccfwin.sel, GTK_SELECTION_MULTIPLE); + + if (!prefs.windows_as_tabs) + g_signal_connect (G_OBJECT (dccfwin.window), "configure_event", + G_CALLBACK (dcc_configure_cb), 0); + g_signal_connect (G_OBJECT (dccfwin.sel), "changed", + G_CALLBACK (dcc_row_cb), NULL); + /* double click */ + g_signal_connect (G_OBJECT (view), "row-activated", + G_CALLBACK (dcc_dclick_cb), NULL); + + table = gtk_table_new (1, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 16); + gtk_box_pack_start (GTK_BOX (vbox), table, 0, 0, 0); + + radio = gtk_radio_button_new_with_mnemonic (NULL, _("Both")); + g_signal_connect (G_OBJECT (radio), "toggled", + G_CALLBACK (dcc_toggle), GINT_TO_POINTER (VIEW_BOTH)); + gtk_table_attach (GTK_TABLE (table), radio, 3, 4, 0, 1, GTK_FILL, GTK_FILL, 0, 0); + group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio)); + + radio = gtk_radio_button_new_with_mnemonic (group, _("Uploads")); + g_signal_connect (G_OBJECT (radio), "toggled", + G_CALLBACK (dcc_toggle), GINT_TO_POINTER (VIEW_UPLOAD)); + gtk_table_attach (GTK_TABLE (table), radio, 1, 2, 0, 1, GTK_FILL, GTK_FILL, 0, 0); + group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio)); + + radio = gtk_radio_button_new_with_mnemonic (group, _("Downloads")); + g_signal_connect (G_OBJECT (radio), "toggled", + G_CALLBACK (dcc_toggle), GINT_TO_POINTER (VIEW_DOWNLOAD)); + gtk_table_attach (GTK_TABLE (table), radio, 2, 3, 0, 1, GTK_FILL, GTK_FILL, 0, 0); + + exp = gtk_expander_new (_("Details")); + gtk_table_attach (GTK_TABLE (table), exp, 0, 1, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); + + detailbox = gtk_table_new (3, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (detailbox), 6); + gtk_table_set_row_spacings (GTK_TABLE (detailbox), 2); + gtk_container_set_border_width (GTK_CONTAINER (detailbox), 6); + g_signal_connect (G_OBJECT (exp), "activate", + G_CALLBACK (dcc_exp_cb), detailbox); + gtk_table_attach (GTK_TABLE (table), detailbox, 0, 4, 1, 2, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); + + dccfwin.file_label = dcc_detail_label (_("File:"), detailbox, 0); + dccfwin.address_label = dcc_detail_label (_("Address:"), detailbox, 1); + + bbox = gtk_hbutton_box_new (); + gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD); + gtk_box_pack_end (GTK_BOX (vbox), bbox, FALSE, FALSE, 2); + + dccfwin.abort_button = gtkutil_button (bbox, GTK_STOCK_CANCEL, 0, abort_clicked, 0, _("Abort")); + dccfwin.accept_button = gtkutil_button (bbox, GTK_STOCK_APPLY, 0, accept_clicked, 0, _("Accept")); + dccfwin.resume_button = gtkutil_button (bbox, GTK_STOCK_REFRESH, 0, resume_clicked, 0, _("Resume")); + dccfwin.open_button = gtkutil_button (bbox, 0, 0, browse_dcc_folder, 0, _("Open Folder...")); + gtk_widget_set_sensitive (dccfwin.accept_button, FALSE); + gtk_widget_set_sensitive (dccfwin.resume_button, FALSE); + gtk_widget_set_sensitive (dccfwin.abort_button, FALSE); + + dcc_fill_window (3); + gtk_widget_show_all (dccfwin.window); + gtk_widget_hide (detailbox); + + return FALSE; +} + +int +fe_dcc_open_send_win (int passive) +{ + /* combined send/recv GUI */ + return fe_dcc_open_recv_win (passive); +} + + +/* DCC CHAT GUIs BELOW */ + +static GSList * +dcc_chat_get_selected (void) +{ + return treeview_get_selected (GTK_TREE_MODEL (dcccwin.store), + dcccwin.sel, CCOL_DCC); +} + +static void +accept_chat_clicked (GtkWidget * wid, gpointer none) +{ + struct DCC *dcc; + GSList *start, *list; + + start = list = dcc_chat_get_selected (); + for (; list; list = list->next) + { + dcc = list->data; + dcc_get (dcc); + } + g_slist_free (start); +} + +static void +abort_chat_clicked (GtkWidget * wid, gpointer none) +{ + struct DCC *dcc; + GSList *start, *list; + + start = list = dcc_chat_get_selected (); + for (; list; list = list->next) + { + dcc = list->data; + dcc_abort (dcc->serv->front_session, dcc); + } + g_slist_free (start); +} + +static void +dcc_chat_close_cb (void) +{ + dcccwin.window = NULL; +} + +static void +dcc_chat_append (struct DCC *dcc, GtkListStore *store, gboolean prepend) +{ + GtkTreeIter iter; + + if (prepend) + gtk_list_store_prepend (store, &iter); + else + gtk_list_store_append (store, &iter); + + dcc_prepare_row_chat (dcc, store, &iter, FALSE); +} + +static void +dcc_chat_fill_win (void) +{ + struct DCC *dcc; + GSList *list; + GtkTreeIter iter; + int i = 0; + + gtk_list_store_clear (GTK_LIST_STORE (dcccwin.store)); + + list = dcc_list; + while (list) + { + dcc = list->data; + if (dcc->type == TYPE_CHATSEND || dcc->type == TYPE_CHATRECV) + { + dcc_chat_append (dcc, dcccwin.store, FALSE); + i++; + } + list = list->next; + } + + /* if only one entry, select it (so Accept button can work) */ + if (i == 1) + { + gtk_tree_model_get_iter_first (GTK_TREE_MODEL (dcccwin.store), &iter); + gtk_tree_selection_select_iter (dcccwin.sel, &iter); + } +} + +static void +dcc_chat_row_cb (GtkTreeSelection *sel, gpointer user_data) +{ + struct DCC *dcc; + GSList *list; + + list = dcc_chat_get_selected (); + if (!list) + { + gtk_widget_set_sensitive (dcccwin.accept_button, FALSE); + gtk_widget_set_sensitive (dcccwin.abort_button, FALSE); + return; + } + + gtk_widget_set_sensitive (dcccwin.abort_button, TRUE); + + if (list->next) /* multi selection */ + gtk_widget_set_sensitive (dcccwin.accept_button, TRUE); + else + { + /* turn OFF/ON appropriate buttons */ + dcc = list->data; + if (dcc->dccstat == STAT_QUEUED && dcc->type == TYPE_CHATRECV) + gtk_widget_set_sensitive (dcccwin.accept_button, TRUE); + else + gtk_widget_set_sensitive (dcccwin.accept_button, FALSE); + } + + g_slist_free (list); +} + +static void +dcc_chat_dclick_cb (GtkTreeView *view, GtkTreePath *path, + GtkTreeViewColumn *column, gpointer data) +{ + accept_chat_clicked (0, 0); +} + +int +fe_dcc_open_chat_win (int passive) +{ + GtkWidget *view, *vbox, *bbox; + GtkListStore *store; + + if (dcccwin.window) + { + if (!passive) + mg_bring_tofront (dcccwin.window); + return TRUE; + } + + dcccwin.window = + mg_create_generic_tab ("DCCChat", _("XChat: DCC Chat List"), + FALSE, TRUE, dcc_chat_close_cb, NULL, 550, 180, &vbox, 0); + gtk_container_set_border_width (GTK_CONTAINER (dcccwin.window), 3); + gtk_box_set_spacing (GTK_BOX (vbox), 3); + + store = gtk_list_store_new (CN_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_POINTER, GDK_TYPE_COLOR); + view = gtkutil_treeview_new (vbox, GTK_TREE_MODEL (store), NULL, -1); + + dcc_add_column (view, CCOL_STATUS, CCOL_COLOR, _("Status"), FALSE); + dcc_add_column (view, CCOL_NICK, CCOL_COLOR, _("Nick"), FALSE); + dcc_add_column (view, CCOL_RECV, CCOL_COLOR, _("Recv"), TRUE); + dcc_add_column (view, CCOL_SENT, CCOL_COLOR, _("Sent"), TRUE); + dcc_add_column (view, CCOL_START, CCOL_COLOR, _("Start Time"), FALSE); + + gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), 1), TRUE); + gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE); + + dcccwin.list = view; + dcccwin.store = store; + dcccwin.sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); + gtk_tree_selection_set_mode (dcccwin.sel, GTK_SELECTION_MULTIPLE); + + g_signal_connect (G_OBJECT (dcccwin.sel), "changed", + G_CALLBACK (dcc_chat_row_cb), NULL); + /* double click */ + g_signal_connect (G_OBJECT (view), "row-activated", + G_CALLBACK (dcc_chat_dclick_cb), NULL); + + bbox = gtk_hbutton_box_new (); + gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD); + gtk_box_pack_end (GTK_BOX (vbox), bbox, FALSE, FALSE, 2); + + dcccwin.abort_button = gtkutil_button (bbox, GTK_STOCK_CANCEL, 0, abort_chat_clicked, 0, _("Abort")); + dcccwin.accept_button = gtkutil_button (bbox, GTK_STOCK_APPLY, 0, accept_chat_clicked, 0, _("Accept")); + gtk_widget_set_sensitive (dcccwin.accept_button, FALSE); + gtk_widget_set_sensitive (dcccwin.abort_button, FALSE); + + dcc_chat_fill_win (); + gtk_widget_show_all (dcccwin.window); + + return FALSE; +} + +void +fe_dcc_add (struct DCC *dcc) +{ + switch (dcc->type) + { + case TYPE_RECV: + if (dccfwin.window && (view_mode & VIEW_DOWNLOAD)) + dcc_append (dcc, dccfwin.store, TRUE); + break; + + case TYPE_SEND: + if (dccfwin.window && (view_mode & VIEW_UPLOAD)) + dcc_append (dcc, dccfwin.store, TRUE); + break; + + default: /* chat */ + if (dcccwin.window) + dcc_chat_append (dcc, dcccwin.store, TRUE); + } +} + +void +fe_dcc_update (struct DCC *dcc) +{ + switch (dcc->type) + { + case TYPE_SEND: + dcc_update_send (dcc); + break; + + case TYPE_RECV: + dcc_update_recv (dcc); + break; + + default: + dcc_update_chat (dcc); + } +} + +void +fe_dcc_remove (struct DCC *dcc) +{ + GtkTreeIter iter; + + switch (dcc->type) + { + case TYPE_SEND: + case TYPE_RECV: + if (dccfwin.window) + { + if (dcc_find_row (dcc, GTK_TREE_MODEL (dccfwin.store), &iter, COL_DCC)) + gtk_list_store_remove (dccfwin.store, &iter); + } + break; + + default: /* chat */ + if (dcccwin.window) + { + if (dcc_find_row (dcc, GTK_TREE_MODEL (dcccwin.store), &iter, CCOL_DCC)) + gtk_list_store_remove (dcccwin.store, &iter); + } + break; + } +} diff --git a/src/fe-gtk/editlist.c b/src/fe-gtk/editlist.c new file mode 100644 index 00000000..5af67e32 --- /dev/null +++ b/src/fe-gtk/editlist.c @@ -0,0 +1,409 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include "fe-gtk.h" + +#include <gtk/gtkstock.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkclist.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkvseparator.h> + +#include "../common/xchat.h" +#include "../common/cfgfiles.h" +#include "../common/xchatc.h" +#include "../common/fe.h" +#include "menu.h" +#include "gtkutil.h" +#include "maingui.h" +#include "editlist.h" + + +static GtkWidget *editlist_gui_entry_name; +static GtkWidget *editlist_gui_entry_cmd; +static GtkWidget *editlist_gui_window; +static GtkWidget *editlist_gui_list; +static GSList *editlist_list; +static char *editlist_file; +static char *editlist_help; + + + +static void +editlist_gui_load (GtkWidget * listgad) +{ + gchar *nnew[2]; + GSList *list = editlist_list; + struct popup *pop; + + while (list) + { + pop = (struct popup *) list->data; + nnew[0] = pop->name; + nnew[1] = pop->cmd; + gtk_clist_append (GTK_CLIST (listgad), nnew); + list = list->next; + } +} + +static void +editlist_gui_row_unselected (GtkWidget * clist, gint row, gint column, + GdkEventButton * even, gpointer none) +{ + gtk_entry_set_text (GTK_ENTRY (editlist_gui_entry_name), ""); + gtk_entry_set_text (GTK_ENTRY (editlist_gui_entry_cmd), ""); +} + +static void +editlist_gui_row_selected (GtkWidget * clist, gint row, gint column, + GdkEventButton * even, gpointer none) +{ + char *name, *cmd; + + row = gtkutil_clist_selection (editlist_gui_list); + if (row != -1) + { + gtk_clist_get_text (GTK_CLIST (clist), row, 0, &name); + gtk_clist_get_text (GTK_CLIST (clist), row, 1, &cmd); + + name = strdup (name); + cmd = strdup (cmd); + + gtk_entry_set_text (GTK_ENTRY (editlist_gui_entry_name), name); + gtk_entry_set_text (GTK_ENTRY (editlist_gui_entry_cmd), cmd); + + free (name); + free (cmd); + } else + { + editlist_gui_row_unselected (0, 0, 0, 0, 0); + } +} + +static void +editlist_gui_handle_cmd (GtkWidget * igad) +{ + int row; + const char *reply; + + row = gtkutil_clist_selection (editlist_gui_list); + if (row != -1) + { + reply = gtk_entry_get_text (GTK_ENTRY (igad)); + gtk_clist_set_text (GTK_CLIST (editlist_gui_list), row, 1, reply); + } +} + +static void +editlist_gui_handle_name (GtkWidget * igad) +{ + int row; + const char *ctcp; + + row = gtkutil_clist_selection (editlist_gui_list); + if (row != -1) + { + ctcp = gtk_entry_get_text (GTK_ENTRY (igad)); + gtk_clist_set_text (GTK_CLIST (editlist_gui_list), row, 0, ctcp); + } +} + +static void +editlist_gui_addnew (GtkWidget * igad) +{ + int i; + gchar *nnew[2]; + + nnew[0] = _("*NEW*"); + nnew[1] = _("EDIT ME"); + + i = gtk_clist_append (GTK_CLIST (editlist_gui_list), nnew); + gtk_clist_select_row (GTK_CLIST (editlist_gui_list), i, 0); + gtk_clist_moveto (GTK_CLIST (editlist_gui_list), i, 0, 0.5, 0); +} + +static void +editlist_gui_delete (GtkWidget * igad) +{ + int row; + + row = gtkutil_clist_selection (editlist_gui_list); + if (row != -1) + { + gtk_clist_unselect_all (GTK_CLIST (editlist_gui_list)); + gtk_clist_remove (GTK_CLIST (editlist_gui_list), row); + } +} + +static void +editlist_gui_save (GtkWidget * igad) +{ + int fh, i = 0; + char buf[512]; + char *a, *b; + + fh = xchat_open_file (editlist_file, O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); + if (fh != -1) + { + while (1) + { + if (!gtk_clist_get_text (GTK_CLIST (editlist_gui_list), i, 0, &a)) + break; + gtk_clist_get_text (GTK_CLIST (editlist_gui_list), i, 1, &b); + snprintf (buf, sizeof (buf), "NAME %s\nCMD %s\n\n", a, b); + write (fh, buf, strlen (buf)); + i++; + } + close (fh); + gtk_widget_destroy (editlist_gui_window); + if (editlist_list == replace_list) + { + list_free (&replace_list); + list_loadconf (editlist_file, &replace_list, 0); + } else if (editlist_list == popup_list) + { + list_free (&popup_list); + list_loadconf (editlist_file, &popup_list, 0); + } else if (editlist_list == button_list) + { + GSList *list = sess_list; + struct session *sess; + list_free (&button_list); + list_loadconf (editlist_file, &button_list, 0); + while (list) + { + sess = (struct session *) list->data; + fe_buttons_update (sess); + list = list->next; + } + } else if (editlist_list == dlgbutton_list) + { + GSList *list = sess_list; + struct session *sess; + list_free (&dlgbutton_list); + list_loadconf (editlist_file, &dlgbutton_list, 0); + while (list) + { + sess = (struct session *) list->data; + fe_dlgbuttons_update (sess); + list = list->next; + } + } else if (editlist_list == ctcp_list) + { + list_free (&ctcp_list); + list_loadconf (editlist_file, &ctcp_list, 0); + } else if (editlist_list == command_list) + { + list_free (&command_list); + list_loadconf (editlist_file, &command_list, 0); + } else if (editlist_list == usermenu_list) + { + list_free (&usermenu_list); + list_loadconf (editlist_file, &usermenu_list, 0); + usermenu_update (); + } else + { + list_free (&urlhandler_list); + list_loadconf (editlist_file, &urlhandler_list, 0); + } + } +} + +static void +editlist_gui_help (GtkWidget * igad) +{ +/* if (editlist_help)*/ + fe_message (editlist_help, FE_MSG_INFO); +} + +static void +editlist_gui_sort (GtkWidget * igad) +{ + int row; + + row = gtkutil_clist_selection (editlist_gui_list); + if (row != -1) + gtk_clist_unselect_row (GTK_CLIST (editlist_gui_list), row, 0); + gtk_clist_sort (GTK_CLIST (editlist_gui_list)); +} + +static void +editlist_gui_movedown (GtkWidget * igad) +{ + int row; + char *temp; + + row = gtkutil_clist_selection (editlist_gui_list); + if (row != -1) + { + if (!gtk_clist_get_text (GTK_CLIST (editlist_gui_list), row + 1, 0, &temp)) + return; + gtk_clist_freeze (GTK_CLIST (editlist_gui_list)); + gtk_clist_swap_rows (GTK_CLIST (editlist_gui_list), row, row + 1); + gtk_clist_thaw (GTK_CLIST (editlist_gui_list)); + row++; + if (!gtk_clist_row_is_visible (GTK_CLIST (editlist_gui_list), row) != + GTK_VISIBILITY_FULL) + gtk_clist_moveto (GTK_CLIST (editlist_gui_list), row, 0, 0.9, 0); + } +} + +static void +editlist_gui_moveup (GtkWidget * igad) +{ + int row; + + row = gtkutil_clist_selection (editlist_gui_list); + if (row != -1 && row > 0) + { + gtk_clist_freeze (GTK_CLIST (editlist_gui_list)); + gtk_clist_swap_rows (GTK_CLIST (editlist_gui_list), row - 1, row); + gtk_clist_thaw (GTK_CLIST (editlist_gui_list)); + row--; + if (gtk_clist_row_is_visible (GTK_CLIST (editlist_gui_list), row) != + GTK_VISIBILITY_FULL) + gtk_clist_moveto (GTK_CLIST (editlist_gui_list), row, 0, 0.1, 0); + } +} + +static void +editlist_gui_close (void) +{ + editlist_gui_window = 0; +} + +void +editlist_gui_open (char *title1, char *title2, GSList * list, char *title, char *wmclass, + char *file, char *help) +{ + gchar *titles[2]; + GtkWidget *vbox, *hbox, *button; + + if (title1) + { + titles[0] = title1; + titles[1] = title2; + } else + { + titles[0] = _("Name"); + titles[1] = _("Command"); + } + + if (editlist_gui_window) + { + mg_bring_tofront (editlist_gui_window); + return; + } + + editlist_list = list; + editlist_file = file; + editlist_help = help; + + editlist_gui_window = + mg_create_generic_tab (wmclass, title, TRUE, FALSE, + editlist_gui_close, NULL, 450, 250, &vbox, 0); + + editlist_gui_list = gtkutil_clist_new (2, titles, vbox, GTK_POLICY_ALWAYS, + editlist_gui_row_selected, 0, + editlist_gui_row_unselected, 0, + GTK_SELECTION_BROWSE); + gtk_clist_set_column_width (GTK_CLIST (editlist_gui_list), 0, 90); + + hbox = gtk_hbox_new (0, 2); + gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 0); + gtk_widget_show (hbox); + + button = gtkutil_button (hbox, GTK_STOCK_GO_UP, 0, editlist_gui_moveup, + 0, _("Move Up")); + gtk_widget_set_usize (button, 100, 0); + + button = gtkutil_button (hbox, GTK_STOCK_GO_DOWN, 0, editlist_gui_movedown, + 0, _("Move Dn")); + gtk_widget_set_usize (button, 100, 0); + + button = gtk_vseparator_new (); + gtk_container_add (GTK_CONTAINER (hbox), button); + gtk_widget_show (button); + + button = gtkutil_button (hbox, GTK_STOCK_CANCEL, 0, gtkutil_destroy, + editlist_gui_window, _("Cancel")); + gtk_widget_set_usize (button, 100, 0); + + button = gtkutil_button (hbox, GTK_STOCK_SAVE, 0, editlist_gui_save, + 0, _("Save")); + gtk_widget_set_usize (button, 100, 0); + + hbox = gtk_hbox_new (0, 2); + gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 0); + gtk_widget_show (hbox); + + button = gtkutil_button (hbox, GTK_STOCK_ADD, 0, editlist_gui_addnew, + 0, _("Add New")); + gtk_widget_set_usize (button, 100, 0); + + button = gtkutil_button (hbox, GTK_STOCK_REMOVE, 0, editlist_gui_delete, + 0, _("Delete")); + gtk_widget_set_usize (button, 100, 0); + + button = gtk_vseparator_new (); + gtk_container_add (GTK_CONTAINER (hbox), button); + gtk_widget_show (button); + + button = gtkutil_button (hbox, GTK_STOCK_SORT_ASCENDING, 0, editlist_gui_sort, + 0, _("Sort")); + gtk_widget_set_usize (button, 100, 0); + + button = gtkutil_button (hbox, GTK_STOCK_HELP, 0, editlist_gui_help, + 0, _("Help")); + gtk_widget_set_usize (button, 100, 0); + + if (!help) + gtk_widget_set_sensitive (GTK_WIDGET (button), FALSE); + + hbox = gtk_hbox_new (0, 2); + gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 0); + gtk_widget_show (hbox); + + editlist_gui_entry_name = gtk_entry_new_with_max_length (82); + gtk_widget_set_usize (editlist_gui_entry_name, 96, 0); + gtk_signal_connect (GTK_OBJECT (editlist_gui_entry_name), "changed", + GTK_SIGNAL_FUNC (editlist_gui_handle_name), 0); + gtk_box_pack_start (GTK_BOX (hbox), editlist_gui_entry_name, 0, 0, 0); + gtk_widget_show (editlist_gui_entry_name); + + editlist_gui_entry_cmd = gtk_entry_new_with_max_length (255); + gtk_signal_connect (GTK_OBJECT (editlist_gui_entry_cmd), "changed", + GTK_SIGNAL_FUNC (editlist_gui_handle_cmd), 0); + gtk_container_add (GTK_CONTAINER (hbox), editlist_gui_entry_cmd); + gtk_widget_show (editlist_gui_entry_cmd); + + hbox = gtk_hbox_new (0, 2); + gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 0); + gtk_widget_show (hbox); + + editlist_gui_load (editlist_gui_list); + + gtk_widget_show (editlist_gui_window); +} diff --git a/src/fe-gtk/editlist.h b/src/fe-gtk/editlist.h new file mode 100644 index 00000000..f17cc2e0 --- /dev/null +++ b/src/fe-gtk/editlist.h @@ -0,0 +1 @@ +void editlist_gui_open (char *title1, char *title2, GSList * list, char *title, char *wmclass, char *file, char *help); diff --git a/src/fe-gtk/fe-gtk.c b/src/fe-gtk/fe-gtk.c new file mode 100644 index 00000000..5efcaeec --- /dev/null +++ b/src/fe-gtk/fe-gtk.c @@ -0,0 +1,1063 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> + +#include "fe-gtk.h" + +#include <gtk/gtkmain.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkprogressbar.h> +#include <gtk/gtkbox.h> +#include <gtk/gtklabel.h> +#include <gtk/gtktogglebutton.h> +#include <gtk/gtkmessagedialog.h> +#include <gtk/gtkversion.h> + +#include "../common/xchat.h" +#include "../common/fe.h" +#include "../common/util.h" +#include "../common/text.h" +#include "../common/cfgfiles.h" +#include "../common/xchatc.h" +#include "../common/plugin.h" +#include "gtkutil.h" +#include "maingui.h" +#include "pixmaps.h" +#include "joind.h" +#include "xtext.h" +#include "palette.h" +#include "menu.h" +#include "notifygui.h" +#include "textgui.h" +#include "fkeys.h" +#include "plugin-tray.h" +#include "urlgrab.h" + +#ifdef USE_XLIB +#include <gdk/gdkx.h> +#include <gtk/gtkinvisible.h> +#endif + +#ifdef USE_GTKSPELL +#include <gtk/gtktextview.h> +#endif + +#ifdef WIN32 +#include <windows.h> +#endif + +GdkPixmap *channelwin_pix; + + +#ifdef USE_XLIB + +static void +redraw_trans_xtexts (void) +{ + GSList *list = sess_list; + session *sess; + int done_main = FALSE; + + while (list) + { + sess = list->data; + if (GTK_XTEXT (sess->gui->xtext)->transparent) + { + if (!sess->gui->is_tab || !done_main) + gtk_xtext_refresh (GTK_XTEXT (sess->gui->xtext), 1); + if (sess->gui->is_tab) + done_main = TRUE; + } + list = list->next; + } +} + +static GdkFilterReturn +root_event_cb (GdkXEvent *xev, GdkEventProperty *event, gpointer data) +{ + static Atom at = None; + XEvent *xevent = (XEvent *)xev; + + if (xevent->type == PropertyNotify) + { + if (at == None) + at = XInternAtom (xevent->xproperty.display, "_XROOTPMAP_ID", True); + + if (at == xevent->xproperty.atom) + redraw_trans_xtexts (); + } + + return GDK_FILTER_CONTINUE; +} + +#endif + +/* === command-line parameter parsing : requires glib 2.6 === */ + +static char *arg_cfgdir = NULL; +static gint arg_show_autoload = 0; +static gint arg_show_config = 0; +static gint arg_show_version = 0; +static gint arg_minimize = 0; + +static const GOptionEntry gopt_entries[] = +{ + {"no-auto", 'a', 0, G_OPTION_ARG_NONE, &arg_dont_autoconnect, N_("Don't auto connect to servers"), NULL}, + {"cfgdir", 'd', 0, G_OPTION_ARG_STRING, &arg_cfgdir, N_("Use a different config directory"), "PATH"}, + {"no-plugins", 'n', 0, G_OPTION_ARG_NONE, &arg_skip_plugins, N_("Don't auto load any plugins"), NULL}, + {"plugindir", 'p', 0, G_OPTION_ARG_NONE, &arg_show_autoload, N_("Show plugin auto-load directory"), NULL}, + {"configdir", 'u', 0, G_OPTION_ARG_NONE, &arg_show_config, N_("Show user config directory"), NULL}, + {"url", 0, 0, G_OPTION_ARG_STRING, &arg_url, N_("Open an irc://server:port/channel URL"), "URL"}, +#ifndef WIN32 /* uses DBUS */ + {"command", 'c', 0, G_OPTION_ARG_STRING, &arg_command, N_("Execute command:"), "COMMAND"}, + {"existing", 'e', 0, G_OPTION_ARG_NONE, &arg_existing, N_("Open URL or execute command in an existing XChat"), NULL}, +#endif + {"minimize", 0, 0, G_OPTION_ARG_INT, &arg_minimize, N_("Begin minimized. Level 0=Normal 1=Iconified 2=Tray"), N_("level")}, + {"version", 'v', 0, G_OPTION_ARG_NONE, &arg_show_version, N_("Show version information"), NULL}, + {NULL} +}; + +int +fe_args (int argc, char *argv[]) +{ + GError *error = NULL; + GOptionContext *context; + +#ifdef ENABLE_NLS + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); +#endif + + context = g_option_context_new (NULL); + g_option_context_add_main_entries (context, gopt_entries, GETTEXT_PACKAGE); + g_option_context_add_group (context, gtk_get_option_group (FALSE)); + g_option_context_parse (context, &argc, &argv, &error); + + if (error) + { + if (error->message) + printf ("%s\n", error->message); + return 1; + } + + g_option_context_free (context); + + if (arg_show_version) + { + printf (PACKAGE_TARNAME" "PACKAGE_VERSION"\n"); + return 0; + } + + if (arg_show_autoload) + { +#ifdef WIN32 + /* see the chdir() below */ + char *sl, *exe = strdup (argv[0]); + sl = strrchr (exe, '\\'); + if (sl) + { + *sl = 0; + printf ("%s\\plugins\n", exe); + } +#else + printf ("%s\n", XCHATLIBDIR"/plugins"); +#endif + return 0; + } + + if (arg_show_config) + { + printf ("%s\n", get_xdir_fs ()); + return 0; + } + +#ifdef WIN32 + /* this is mainly for irc:// URL handling. When windows calls us from */ + /* I.E, it doesn't give an option of "Start in" directory, like short */ + /* cuts can. So we have to set the current dir manually, to the path */ + /* of the exe. */ + { + char *tmp = strdup (argv[0]); + char *sl; + + sl = strrchr (tmp, '\\'); + if (sl) + { + *sl = 0; + chdir (tmp); + } + free (tmp); + } +#endif + + if (arg_cfgdir) /* we want filesystem encoding */ + { + xdir_fs = strdup (arg_cfgdir); + if (xdir_fs[strlen (xdir_fs) - 1] == '/') + xdir_fs[strlen (xdir_fs) - 1] = 0; + g_free (arg_cfgdir); + } + + gtk_init (&argc, &argv); + +#ifdef USE_XLIB + gdk_window_set_events (gdk_get_default_root_window (), GDK_PROPERTY_CHANGE_MASK); + gdk_window_add_filter (gdk_get_default_root_window (), + (GdkFilterFunc)root_event_cb, NULL); +#endif + + return -1; +} + +const char cursor_color_rc[] = + "style \"xc-ib-st\"" + "{" +#ifdef USE_GTKSPELL + "GtkTextView::cursor-color=\"#%02x%02x%02x\"" +#else + "GtkEntry::cursor-color=\"#%02x%02x%02x\"" +#endif + "}" + "widget \"*.xchat-inputbox\" style : application \"xc-ib-st\""; + +GtkStyle * +create_input_style (GtkStyle *style) +{ + char buf[256]; + static int done_rc = FALSE; + + pango_font_description_free (style->font_desc); + style->font_desc = pango_font_description_from_string (prefs.font_normal); + + /* fall back */ + if (pango_font_description_get_size (style->font_desc) == 0) + { + snprintf (buf, sizeof (buf), _("Failed to open font:\n\n%s"), prefs.font_normal); + fe_message (buf, FE_MSG_ERROR); + pango_font_description_free (style->font_desc); + style->font_desc = pango_font_description_from_string ("sans 11"); + } + + if (prefs.style_inputbox && !done_rc) + { + done_rc = TRUE; + sprintf (buf, cursor_color_rc, (colors[COL_FG].red >> 8), + (colors[COL_FG].green >> 8), (colors[COL_FG].blue >> 8)); + gtk_rc_parse_string (buf); + } + + style->bg[GTK_STATE_NORMAL] = colors[COL_FG]; + style->base[GTK_STATE_NORMAL] = colors[COL_BG]; + style->text[GTK_STATE_NORMAL] = colors[COL_FG]; + + return style; +} + +void +fe_init (void) +{ + palette_load (); + key_init (); + pixmaps_init (); + + channelwin_pix = pixmap_load_from_file (prefs.background); + input_style = create_input_style (gtk_style_new ()); +} + +void +fe_main (void) +{ + gtk_main (); + + /* sleep for 3 seconds so any QUIT messages are not lost. The */ + /* GUI is closed at this point, so the user doesn't even know! */ + if (prefs.wait_on_exit) + sleep (3); +} + +void +fe_cleanup (void) +{ + /* it's saved when pressing OK in setup.c */ + /*palette_save ();*/ +} + +void +fe_exit (void) +{ + gtk_main_quit (); +} + +int +fe_timeout_add (int interval, void *callback, void *userdata) +{ + return g_timeout_add (interval, (GSourceFunc) callback, userdata); +} + +void +fe_timeout_remove (int tag) +{ + g_source_remove (tag); +} + +#ifdef WIN32 + +static void +log_handler (const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer unused_data) +{ + session *sess; + + if (getenv ("XCHAT_WARNING_IGNORE")) + return; + + sess = find_dialog (serv_list->data, "(warnings)"); + if (!sess) + sess = new_ircwindow (serv_list->data, "(warnings)", SESS_DIALOG, 0); + + PrintTextf (sess, "%s\t%s\n", log_domain, message); + if (getenv ("XCHAT_WARNING_ABORT")) + abort (); +} + +#endif + +/* install tray stuff */ + +static int +fe_idle (gpointer data) +{ + session *sess = sess_list->data; + + plugin_add (sess, NULL, NULL, tray_plugin_init, tray_plugin_deinit, NULL, FALSE); + + if (arg_minimize == 1) + gtk_window_iconify (GTK_WINDOW (sess->gui->window)); + else if (arg_minimize == 2) + tray_toggle_visibility (FALSE); + + return 0; +} + +void +fe_new_window (session *sess, int focus) +{ + int tab = FALSE; + + if (sess->type == SESS_DIALOG) + { + if (prefs.privmsgtab) + tab = TRUE; + } else + { + if (prefs.tabchannels) + tab = TRUE; + } + + mg_changui_new (sess, NULL, tab, focus); + +#ifdef WIN32 + g_log_set_handler ("GLib", G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING, (GLogFunc)log_handler, 0); + g_log_set_handler ("GLib-GObject", G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING, (GLogFunc)log_handler, 0); + g_log_set_handler ("Gdk", G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING, (GLogFunc)log_handler, 0); + g_log_set_handler ("Gtk", G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING, (GLogFunc)log_handler, 0); +#endif + + if (!sess_list->next) + g_idle_add (fe_idle, NULL); +} + +void +fe_new_server (struct server *serv) +{ + serv->gui = malloc (sizeof (struct server_gui)); + memset (serv->gui, 0, sizeof (struct server_gui)); +} + +void +fe_message (char *msg, int flags) +{ + GtkWidget *dialog; + int type = GTK_MESSAGE_WARNING; + + if (flags & FE_MSG_ERROR) + type = GTK_MESSAGE_ERROR; + if (flags & FE_MSG_INFO) + type = GTK_MESSAGE_INFO; + + dialog = gtk_message_dialog_new (GTK_WINDOW (parent_window), 0, type, + GTK_BUTTONS_OK, "%s", msg); + if (flags & FE_MSG_MARKUP) + gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (dialog), msg); + g_signal_connect (G_OBJECT (dialog), "response", + G_CALLBACK (gtk_widget_destroy), 0); + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + gtk_widget_show (dialog); + + if (flags & FE_MSG_WAIT) + gtk_dialog_run (GTK_DIALOG (dialog)); +} + +void +fe_idle_add (void *func, void *data) +{ + g_idle_add (func, data); +} + +void +fe_input_remove (int tag) +{ + g_source_remove (tag); +} + +int +fe_input_add (int sok, int flags, void *func, void *data) +{ + int tag, type = 0; + GIOChannel *channel; + +#ifdef WIN32 + if (flags & FIA_FD) + channel = g_io_channel_win32_new_fd (sok); + else + channel = g_io_channel_win32_new_socket (sok); +#else + channel = g_io_channel_unix_new (sok); +#endif + + if (flags & FIA_READ) + type |= G_IO_IN | G_IO_HUP | G_IO_ERR; + if (flags & FIA_WRITE) + type |= G_IO_OUT | G_IO_ERR; + if (flags & FIA_EX) + type |= G_IO_PRI; + + tag = g_io_add_watch (channel, type, (GIOFunc) func, data); + g_io_channel_unref (channel); + + return tag; +} + +void +fe_set_topic (session *sess, char *topic, char *stripped_topic) +{ + if (!sess->gui->is_tab || sess == current_tab) + { + gtk_entry_set_text (GTK_ENTRY (sess->gui->topic_entry), stripped_topic); + mg_set_topic_tip (sess); + } else + { + if (sess->res->topic_text) + free (sess->res->topic_text); + sess->res->topic_text = strdup (stripped_topic); + } +} + +void +fe_set_hilight (struct session *sess) +{ + if (sess->gui->is_tab) + fe_set_tab_color (sess, 3); /* set tab to blue */ + + if (prefs.input_flash_hilight) + fe_flash_window (sess); /* taskbar flash */ +} + +static void +fe_update_mode_entry (session *sess, GtkWidget *entry, char **text, char *new_text) +{ + if (!sess->gui->is_tab || sess == current_tab) + { + if (sess->gui->flag_wid[0]) /* channel mode buttons enabled? */ + gtk_entry_set_text (GTK_ENTRY (entry), new_text); + } else + { + if (sess->gui->is_tab) + { + if (*text) + free (*text); + *text = strdup (new_text); + } + } +} + +void +fe_update_channel_key (struct session *sess) +{ + fe_update_mode_entry (sess, sess->gui->key_entry, + &sess->res->key_text, sess->channelkey); + fe_set_title (sess); +} + +void +fe_update_channel_limit (struct session *sess) +{ + char tmp[16]; + + sprintf (tmp, "%d", sess->limit); + fe_update_mode_entry (sess, sess->gui->limit_entry, + &sess->res->limit_text, tmp); + fe_set_title (sess); +} + +int +fe_is_chanwindow (struct server *serv) +{ + if (!serv->gui->chanlist_window) + return 0; + return 1; +} + +int +fe_is_banwindow (struct session *sess) +{ + if (!sess->res->banlist_window) + return 0; + return 1; +} + +void +fe_notify_update (char *name) +{ + if (!name) + notify_gui_update (); +} + +void +fe_text_clear (struct session *sess, int lines) +{ + gtk_xtext_clear (sess->res->buffer, lines); +} + +void +fe_close_window (struct session *sess) +{ + if (sess->gui->is_tab) + mg_tab_close (sess); + else + gtk_widget_destroy (sess->gui->window); +} + +void +fe_progressbar_start (session *sess) +{ + if (!sess->gui->is_tab || current_tab == sess) + /* if it's the focused tab, create it for real! */ + mg_progressbar_create (sess->gui); + else + /* otherwise just remember to create on when it gets focused */ + sess->res->c_graph = TRUE; +} + +void +fe_progressbar_end (server *serv) +{ + GSList *list = sess_list; + session *sess; + + while (list) /* check all windows that use this server and * + * remove the connecting graph, if it has one. */ + { + sess = list->data; + if (sess->server == serv) + { + if (sess->gui->bar) + mg_progressbar_destroy (sess->gui); + sess->res->c_graph = FALSE; + } + list = list->next; + } +} + +void +fe_print_text (struct session *sess, char *text, time_t stamp) +{ + PrintTextRaw (sess->res->buffer, (unsigned char *)text, prefs.indent_nicks, stamp); + + if (!sess->new_data && sess != current_tab && + sess->gui->is_tab && !sess->nick_said && stamp == 0) + { + sess->new_data = TRUE; + if (sess->msg_said) + fe_set_tab_color (sess, 2); + else + fe_set_tab_color (sess, 1); + } +} + +void +fe_beep (void) +{ + gdk_beep (); +} + +#ifndef WIN32 +static int +lastlog_regex_cmp (char *a, regex_t *reg) +{ + return !regexec (reg, a, 1, NULL, REG_NOTBOL); +} +#endif + +void +fe_lastlog (session *sess, session *lastlog_sess, char *sstr, gboolean regexp) +{ +#ifndef WIN32 + regex_t reg; +#endif + + if (gtk_xtext_is_empty (sess->res->buffer)) + { + PrintText (lastlog_sess, _("Search buffer is empty.\n")); + return; + } + + if (!regexp) + { + gtk_xtext_lastlog (lastlog_sess->res->buffer, sess->res->buffer, + (void *) nocasestrstr, sstr); + return; + } + +#ifndef WIN32 + if (regcomp (®, sstr, REG_ICASE | REG_EXTENDED | REG_NOSUB) == 0) + { + gtk_xtext_lastlog (lastlog_sess->res->buffer, sess->res->buffer, + (void *) lastlog_regex_cmp, ®); + regfree (®); + } +#endif +} + +void +fe_set_lag (server *serv, int lag) +{ + GSList *list = sess_list; + session *sess; + gdouble per; + char lagtext[64]; + char lagtip[128]; + unsigned long nowtim; + + if (lag == -1) + { + if (!serv->lag_sent) + return; + nowtim = make_ping_time (); + lag = (nowtim - serv->lag_sent) / 100000; + } + + per = (double)((double)lag / (double)10); + if (per > 1.0) + per = 1.0; + + snprintf (lagtext, sizeof (lagtext) - 1, "%s%d.%ds", + serv->lag_sent ? "+" : "", lag / 10, lag % 10); + snprintf (lagtip, sizeof (lagtip) - 1, "Lag: %s%d.%d seconds", + serv->lag_sent ? "+" : "", lag / 10, lag % 10); + + while (list) + { + sess = list->data; + if (sess->server == serv) + { + if (sess->res->lag_tip) + free (sess->res->lag_tip); + sess->res->lag_tip = strdup (lagtip); + + if (!sess->gui->is_tab || current_tab == sess) + { + if (sess->gui->lagometer) + { + gtk_progress_bar_set_fraction ((GtkProgressBar *) sess->gui->lagometer, per); + add_tip (sess->gui->lagometer->parent, lagtip); + } + if (sess->gui->laginfo) + gtk_label_set_text ((GtkLabel *) sess->gui->laginfo, lagtext); + } else + { + sess->res->lag_value = per; + if (sess->res->lag_text) + free (sess->res->lag_text); + sess->res->lag_text = strdup (lagtext); + } + } + list = list->next; + } +} + +void +fe_set_throttle (server *serv) +{ + GSList *list = sess_list; + struct session *sess; + float per; + char tbuf[96]; + char tip[160]; + + per = (float) serv->sendq_len / 1024.0; + if (per > 1.0) + per = 1.0; + + while (list) + { + sess = list->data; + if (sess->server == serv) + { + snprintf (tbuf, sizeof (tbuf) - 1, _("%d bytes"), serv->sendq_len); + snprintf (tip, sizeof (tip) - 1, _("Network send queue: %d bytes"), serv->sendq_len); + + if (sess->res->queue_tip) + free (sess->res->queue_tip); + sess->res->queue_tip = strdup (tip); + + if (!sess->gui->is_tab || current_tab == sess) + { + if (sess->gui->throttlemeter) + { + gtk_progress_bar_set_fraction ((GtkProgressBar *) sess->gui->throttlemeter, per); + add_tip (sess->gui->throttlemeter->parent, tip); + } + if (sess->gui->throttleinfo) + gtk_label_set_text ((GtkLabel *) sess->gui->throttleinfo, tbuf); + } else + { + sess->res->queue_value = per; + if (sess->res->queue_text) + free (sess->res->queue_text); + sess->res->queue_text = strdup (tbuf); + } + } + list = list->next; + } +} + +void +fe_ctrl_gui (session *sess, fe_gui_action action, int arg) +{ + switch (action) + { + case FE_GUI_HIDE: + gtk_widget_hide (sess->gui->window); break; + case FE_GUI_SHOW: + gtk_widget_show (sess->gui->window); + gtk_window_present (GTK_WINDOW (sess->gui->window)); + break; + case FE_GUI_FOCUS: + mg_bring_tofront_sess (sess); break; + case FE_GUI_FLASH: + fe_flash_window (sess); break; + case FE_GUI_COLOR: + fe_set_tab_color (sess, arg); break; + case FE_GUI_ICONIFY: + gtk_window_iconify (GTK_WINDOW (sess->gui->window)); break; + case FE_GUI_MENU: + menu_bar_toggle (); /* toggle menubar on/off */ + break; + case FE_GUI_ATTACH: + mg_detach (sess, arg); /* arg: 0=toggle 1=detach 2=attach */ + break; + case FE_GUI_APPLY: + setup_apply_real (TRUE, TRUE); + } +} + +static void +dcc_saveas_cb (struct DCC *dcc, char *file) +{ + if (is_dcc (dcc)) + { + if (dcc->dccstat == STAT_QUEUED) + { + if (file) + dcc_get_with_destfile (dcc, file); + else if (dcc->resume_sent == 0) + dcc_abort (dcc->serv->front_session, dcc); + } + } +} + +void +fe_confirm (const char *message, void (*yesproc)(void *), void (*noproc)(void *), void *ud) +{ + /* warning, assuming fe_confirm is used by DCC only! */ + struct DCC *dcc = ud; + + if (dcc->file) + gtkutil_file_req (message, dcc_saveas_cb, ud, dcc->file, + FRF_WRITE|FRF_FILTERISINITIAL|FRF_NOASKOVERWRITE); +} + +int +fe_gui_info (session *sess, int info_type) +{ + switch (info_type) + { + case 0: /* window status */ +#if GTK_CHECK_VERSION(2,20,0) + if (!gtk_widget_get_visible (GTK_WIDGET (sess->gui->window))) +#else + if (!GTK_WIDGET_VISIBLE (GTK_WIDGET (sess->gui->window))) +#endif + return 2; /* hidden (iconified or systray) */ +#if GTK_CHECK_VERSION(2,4,0) + if (gtk_window_is_active (GTK_WINDOW (sess->gui->window))) +#else +#if GTK_CHECK_VERSION(2,2,0) + if (GTK_WINDOW (sess->gui->window)->is_active) +#endif +#endif + return 1; /* active/focused */ + + return 0; /* normal (no keyboard focus or behind a window) */ + } + + return -1; +} + +void * +fe_gui_info_ptr (session *sess, int info_type) +{ + switch (info_type) + { + case 0: /* native window pointer (for plugins) */ +#ifdef WIN32 + return GDK_WINDOW_HWND (sess->gui->window->window); +#else + return sess->gui->window; +#endif + break; + + case 1: /* GtkWindow * (for plugins) */ + return sess->gui->window; + } + return NULL; +} + +char * +fe_get_inputbox_contents (session *sess) +{ + /* not the current tab */ + if (sess->res->input_text) + return sess->res->input_text; + + /* current focused tab */ + return SPELL_ENTRY_GET_TEXT (sess->gui->input_box); +} + +int +fe_get_inputbox_cursor (session *sess) +{ + /* not the current tab (we don't remember the cursor pos) */ + if (sess->res->input_text) + return 0; + + /* current focused tab */ + return SPELL_ENTRY_GET_POS (sess->gui->input_box); +} + +void +fe_set_inputbox_cursor (session *sess, int delta, int pos) +{ + if (!sess->gui->is_tab || sess == current_tab) + { + if (delta) + pos += SPELL_ENTRY_GET_POS (sess->gui->input_box); + SPELL_ENTRY_SET_POS (sess->gui->input_box, pos); + } else + { + /* we don't support changing non-front tabs yet */ + } +} + +void +fe_set_inputbox_contents (session *sess, char *text) +{ + if (!sess->gui->is_tab || sess == current_tab) + { + SPELL_ENTRY_SET_TEXT (sess->gui->input_box, text); + } else + { + if (sess->res->input_text) + free (sess->res->input_text); + sess->res->input_text = strdup (text); + } +} + +#ifndef WIN32 + +static gboolean +try_browser (const char *browser, const char *arg, const char *url) +{ + const char *argv[4]; + char *path; + + path = g_find_program_in_path (browser); + if (!path) + return 0; + + argv[0] = path; + argv[1] = url; + argv[2] = NULL; + if (arg) + { + argv[1] = arg; + argv[2] = url; + argv[3] = NULL; + } + xchat_execv (argv); + g_free (path); + return 1; +} + +#endif + +static void +fe_open_url_inner (const char *url) +{ +#ifdef WIN32 + ShellExecute (0, "open", url, NULL, NULL, SW_SHOWNORMAL); +#else + /* universal desktop URL opener (from xdg-utils). Supports gnome,kde,xfce4. */ + if (try_browser ("xdg-open", NULL, url)) + return; + + /* try to detect GNOME */ + if (g_getenv ("GNOME_DESKTOP_SESSION_ID")) + { + if (try_browser ("gnome-open", NULL, url)) /* Gnome 2.4+ has this */ + return; + } + + /* try to detect KDE */ + if (g_getenv ("KDE_FULL_SESSION")) + { + if (try_browser ("kfmclient", "exec", url)) + return; + } + + /* everything failed, what now? just try firefox */ + if (try_browser ("firefox", NULL, url)) + return; + + /* fresh out of ideas... */ + try_browser ("mozilla", NULL, url); +#endif +} + +static void +fe_open_url_locale (const char *url) +{ +#ifndef WIN32 + if (url[0] != '/' && strchr (url, ':') == NULL) + { + url = g_strdup_printf ("http://%s", url); + fe_open_url_inner (url); + g_free ((char *)url); + return; + } +#endif + fe_open_url_inner (url); +} + +void +fe_open_url (const char *url) +{ + char *loc; + + if (prefs.utf8_locale) + { + fe_open_url_locale (url); + return; + } + + /* the OS expects it in "locale" encoding. This makes it work on + unix systems that use ISO-8859-x and Win32. */ + loc = g_locale_from_utf8 (url, -1, 0, 0, 0); + if (loc) + { + fe_open_url_locale (loc); + g_free (loc); + } +} + +void +fe_server_event (server *serv, int type, int arg) +{ + GSList *list = sess_list; + session *sess; + + while (list) + { + sess = list->data; + if (sess->server == serv && (current_tab == sess || !sess->gui->is_tab)) + { + session_gui *gui = sess->gui; + + switch (type) + { + case FE_SE_CONNECTING: /* connecting in progress */ + case FE_SE_RECONDELAY: /* reconnect delay begun */ + /* enable Disconnect item */ + gtk_widget_set_sensitive (gui->menu_item[MENU_ID_DISCONNECT], 1); + break; + + case FE_SE_CONNECT: + /* enable Disconnect and Away menu items */ + gtk_widget_set_sensitive (gui->menu_item[MENU_ID_AWAY], 1); + gtk_widget_set_sensitive (gui->menu_item[MENU_ID_DISCONNECT], 1); + break; + + case FE_SE_LOGGEDIN: /* end of MOTD */ + gtk_widget_set_sensitive (gui->menu_item[MENU_ID_JOIN], 1); + /* if number of auto-join channels is zero, open joind */ + if (arg == 0) + joind_open (serv); + break; + + case FE_SE_DISCONNECT: + /* disable Disconnect and Away menu items */ + gtk_widget_set_sensitive (gui->menu_item[MENU_ID_AWAY], 0); + gtk_widget_set_sensitive (gui->menu_item[MENU_ID_DISCONNECT], 0); + gtk_widget_set_sensitive (gui->menu_item[MENU_ID_JOIN], 0); + /* close the join-dialog, if one exists */ + joind_close (serv); + } + } + list = list->next; + } +} + +void +fe_get_file (const char *title, char *initial, + void (*callback) (void *userdata, char *file), void *userdata, + int flags) + +{ + /* OK: Call callback once per file, then once more with file=NULL. */ + /* CANCEL: Call callback once with file=NULL. */ + gtkutil_file_req (title, callback, userdata, initial, flags | FRF_FILTERISINITIAL); +} diff --git a/src/fe-gtk/fe-gtk.h b/src/fe-gtk/fe-gtk.h new file mode 100644 index 00000000..12516259 --- /dev/null +++ b/src/fe-gtk/fe-gtk.h @@ -0,0 +1,197 @@ +#include "../../config.h" + +#ifdef WIN32 +/* If you're compiling this for Windows, your release is un-official + * and not condoned. Please don't use the XChat name. Make up your + * own name! */ +#define DISPLAY_NAME "XChat-Unofficial" +#else +#define DISPLAY_NAME "XChat" +#endif + +#ifndef WIN32 +#include <sys/types.h> +#include <regex.h> +#endif + +#if defined(ENABLE_NLS) && !defined(_) +# include <libintl.h> +# define _(x) gettext(x) +# ifdef gettext_noop +# define N_(String) gettext_noop (String) +# else +# define N_(String) (String) +# endif +#endif +#if !defined(ENABLE_NLS) && defined(_) +# undef _ +# define N_(String) (String) +# define _(x) (x) +#endif + +#include <gtk/gtkwidget.h> +#include <gtk/gtkcontainer.h> +#include <gtk/gtksignal.h> + +#undef gtk_signal_connect +#define gtk_signal_connect g_signal_connect + +#define flag_t flag_wid[0] +#define flag_n flag_wid[1] +#define flag_s flag_wid[2] +#define flag_i flag_wid[3] +#define flag_p flag_wid[4] +#define flag_m flag_wid[5] +#define flag_l flag_wid[6] +#define flag_k flag_wid[7] +#define flag_b flag_wid[8] +#define NUM_FLAG_WIDS 9 + +struct server_gui +{ + GtkWidget *rawlog_window; + GtkWidget *rawlog_textlist; + + /* join dialog */ + GtkWidget *joind_win; + GtkWidget *joind_entry; + GtkWidget *joind_radio1; + GtkWidget *joind_radio2; + GtkWidget *joind_check; + + /* chanlist variables */ + GtkWidget *chanlist_wild; /* GtkEntry */ + GtkWidget *chanlist_window; + GtkWidget *chanlist_list; + GtkWidget *chanlist_label; + GtkWidget *chanlist_min_spin; /* minusers GtkSpinButton */ + GtkWidget *chanlist_refresh; /* buttons */ + GtkWidget *chanlist_join; + GtkWidget *chanlist_savelist; + GtkWidget *chanlist_search; + + GSList *chanlist_data_stored_rows; /* stored list so it can be resorted */ + GSList *chanlist_pending_rows; + gint chanlist_tag; + gint chanlist_flash_tag; + + gboolean chanlist_match_wants_channel; /* match in channel name */ + gboolean chanlist_match_wants_topic; /* match in topic */ + +#ifndef WIN32 + regex_t chanlist_match_regex; /* compiled regular expression here */ + unsigned int have_regex; +#endif + + guint chanlist_users_found_count; /* users total for all channels */ + guint chanlist_users_shown_count; /* users total for displayed channels */ + guint chanlist_channels_found_count; /* channel total for /LIST operation */ + guint chanlist_channels_shown_count; /* total number of displayed + channels */ + + int chanlist_maxusers; + int chanlist_minusers; + int chanlist_minusers_downloaded; /* used by LIST IRC command */ + int chanlist_search_type; /* 0=simple 1=pattern/wildcard 2=regexp */ + gboolean chanlist_caption_is_stale; +}; + +/* this struct is persistant even when delinking/relinking */ + +typedef struct restore_gui +{ + /* banlist stuff */ + GtkWidget *banlist_window; + GtkWidget *banlist_treeview; + GtkWidget *banlist_butRefresh; + + void *tab; /* (chan *) */ + + /* information stored when this tab isn't front-most */ + void *user_model; /* for filling the GtkTreeView */ + void *buffer; /* xtext_Buffer */ + char *input_text; /* input text buffer (while not-front tab) */ + char *topic_text; /* topic GtkEntry buffer */ + char *key_text; + char *limit_text; + gfloat old_ul_value; /* old userlist value (for adj) */ + gfloat lag_value; /* lag-o-meter */ + char *lag_text; /* lag-o-meter text */ + char *lag_tip; /* lag-o-meter tooltip */ + gfloat queue_value; /* outbound queue meter */ + char *queue_text; /* outbound queue text */ + char *queue_tip; /* outbound queue tooltip */ + short flag_wid_state[NUM_FLAG_WIDS]; + unsigned int c_graph:1; /* connecting graph, is there one? */ +} restore_gui; + +typedef struct session_gui +{ + GtkWidget + *xtext, + *vscrollbar, + *window, /* toplevel */ + *topic_entry, + *note_book, + *main_table, + *user_tree, /* GtkTreeView */ + *user_box, /* userlist box */ + *button_box_parent, + *button_box, /* userlist buttons' box */ + *dialogbutton_box, + *topicbutton_box, + *meter_box, /* all the meters inside this */ + *lagometer, + *laginfo, + *throttlemeter, + *throttleinfo, + *topic_bar, + *hpane_left, + *hpane_right, + *vpane_left, + *vpane_right, + *menu, + *bar, /* connecting progress bar */ + *nick_box, /* contains label to the left of input_box */ + *nick_label, + *op_xpm, /* icon to the left of nickname */ + *namelistinfo, /* label above userlist */ + *input_box, + *flag_wid[NUM_FLAG_WIDS], /* channelmode buttons */ + *limit_entry, /* +l */ + *key_entry; /* +k */ + +#define MENU_ID_NUM 12 + GtkWidget *menu_item[MENU_ID_NUM+1]; /* some items we may change state of */ + + void *chanview; /* chanview.h */ + + int bartag; /*connecting progressbar timeout */ + + int pane_left_size; /*last position of the pane*/ + int pane_right_size; + + guint16 is_tab; /* is tab or toplevel? */ + guint16 ul_hidden; /* userlist hidden? */ + +} session_gui; + +extern GdkPixmap *channelwin_pix; +extern GdkPixmap *dialogwin_pix; + + +#ifdef USE_GTKSPELL +char *SPELL_ENTRY_GET_TEXT (GtkWidget *entry); +#define SPELL_ENTRY_SET_TEXT(e,txt) gtk_text_buffer_set_text (gtk_text_view_get_buffer(GTK_TEXT_VIEW(e)),txt,-1); +#define SPELL_ENTRY_SET_EDITABLE(e,v) gtk_text_view_set_editable(GTK_TEXT_VIEW(e), v) +int SPELL_ENTRY_GET_POS (GtkWidget *entry); +void SPELL_ENTRY_SET_POS (GtkWidget *entry, int pos); +void SPELL_ENTRY_INSERT (GtkWidget *entry, const char *text, int len, int *pos); +#else +#define SPELL_ENTRY_GET_TEXT(e) (GTK_ENTRY(e)->text) +#define SPELL_ENTRY_SET_TEXT(e,txt) gtk_entry_set_text(GTK_ENTRY(e),txt) +#define SPELL_ENTRY_SET_EDITABLE(e,v) gtk_editable_set_editable(GTK_EDITABLE(e),v) +#define SPELL_ENTRY_GET_POS(e) gtk_editable_get_position(GTK_EDITABLE(e)) +#define SPELL_ENTRY_SET_POS(e,p) gtk_editable_set_position(GTK_EDITABLE(e),p); +#define SPELL_ENTRY_INSERT(e,t,l,p) gtk_editable_insert_text(GTK_EDITABLE(e),t,l,p) +#endif diff --git a/src/fe-gtk/fkeys.c b/src/fe-gtk/fkeys.c new file mode 100644 index 00000000..014b5cc3 --- /dev/null +++ b/src/fe-gtk/fkeys.c @@ -0,0 +1,1814 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <ctype.h> + +#include "fe-gtk.h" + +#include <gtk/gtklabel.h> +#include <gtk/gtkeditable.h> +#include <gtk/gtkmenu.h> +#include <gtk/gtkmenuitem.h> +#include <gtk/gtkoptionmenu.h> +#include <gtk/gtkvbox.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkclist.h> +#include <gtk/gtknotebook.h> +#include <gtk/gtkcheckbutton.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkvscrollbar.h> + +#include "../common/xchat.h" +#include "../common/xchatc.h" +#include "../common/cfgfiles.h" +#include "../common/fe.h" +#include "../common/userlist.h" +#include "../common/outbound.h" +#include "../common/util.h" +#include "../common/text.h" +#include "../common/plugin.h" +#include <gdk/gdkkeysyms.h> +#include "gtkutil.h" +#include "menu.h" +#include "xtext.h" +#include "palette.h" +#include "maingui.h" +#include "textgui.h" +#include "fkeys.h" + +#ifdef USE_GTKSPELL +#include <gtk/gtktextview.h> +#endif + +static void replace_handle (GtkWidget * wid); +void key_action_tab_clean (void); + +/***************** Key Binding Code ******************/ + +/* NOTES: + + To add a new action: + 1) inc KEY_MAX_ACTIONS + 2) write the function at the bottom of this file (with all the others) + FIXME: Write about calling and returning + 3) Add it to key_actions + + --AGL + + */ + +/* Remember that the *number* of actions is this *plus* 1 --AGL */ +#define KEY_MAX_ACTIONS 14 +/* These are cp'ed from history.c --AGL */ +#define STATE_SHIFT GDK_SHIFT_MASK +#define STATE_ALT GDK_MOD1_MASK +#define STATE_CTRL GDK_CONTROL_MASK + +struct key_binding +{ + int keyval; /* GDK keynumber */ + char *keyname; /* String with the name of the function */ + int action; /* Index into key_actions */ + int mod; /* Flags of STATE_* above */ + char *data1, *data2; /* Pointers to strings, these must be freed */ + struct key_binding *next; +}; + +struct key_action +{ + int (*handler) (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2, + struct session * sess); + char *name; + char *help; +}; + +struct gcomp_data +{ + char data[CHANLEN]; + int elen; +}; + +static int key_load_kbs (char *); +static void key_load_defaults (); +static void key_save_kbs (char *); +static int key_action_handle_command (GtkWidget * wid, GdkEventKey * evt, + char *d1, char *d2, + struct session *sess); +static int key_action_page_switch (GtkWidget * wid, GdkEventKey * evt, + char *d1, char *d2, struct session *sess); +int key_action_insert (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2, + struct session *sess); +static int key_action_scroll_page (GtkWidget * wid, GdkEventKey * evt, + char *d1, char *d2, struct session *sess); +static int key_action_set_buffer (GtkWidget * wid, GdkEventKey * evt, + char *d1, char *d2, struct session *sess); +static int key_action_history_up (GtkWidget * wid, GdkEventKey * evt, + char *d1, char *d2, struct session *sess); +static int key_action_history_down (GtkWidget * wid, GdkEventKey * evt, + char *d1, char *d2, struct session *sess); +static int key_action_tab_comp (GtkWidget * wid, GdkEventKey * evt, char *d1, + char *d2, struct session *sess); +static int key_action_comp_chng (GtkWidget * wid, GdkEventKey * evt, char *d1, + char *d2, struct session *sess); +static int key_action_replace (GtkWidget * wid, GdkEventKey * evt, char *d1, + char *d2, struct session *sess); +static int key_action_move_tab_left (GtkWidget * wid, GdkEventKey * evt, + char *d1, char *d2, + struct session *sess); +static int key_action_move_tab_right (GtkWidget * wid, GdkEventKey * evt, + char *d1, char *d2, + struct session *sess); +static int key_action_move_tab_family_left (GtkWidget * wid, GdkEventKey * evt, + char *d1, char *d2, + struct session *sess); +static int key_action_move_tab_family_right (GtkWidget * wid, GdkEventKey * evt, + char *d1, char *d2, + struct session *sess); +static int key_action_put_history (GtkWidget * wid, GdkEventKey * evt, + char *d1, char *d2, + struct session *sess); + +static GtkWidget *key_dialog; +static struct key_binding *keys_root = NULL; + +static const struct key_action key_actions[KEY_MAX_ACTIONS + 1] = { + + {key_action_handle_command, "Run Command", + N_("The \002Run Command\002 action runs the data in Data 1 as if it has been typed into the entry box where you pressed the key sequence. Thus it can contain text (which will be sent to the channel/person), commands or user commands. When run all \002\\n\002 characters in Data 1 are used to deliminate seperate commands so it is possible to run more than one command. If you want a \002\\\002 in the actual text run then enter \002\\\\\002")}, + {key_action_page_switch, "Change Page", + N_("The \002Change Page\002 command switches between pages in the notebook. Set Data 1 to the page you want to switch to. If Data 2 is set to anything then the switch will be relative to the current position")}, + {key_action_insert, "Insert in Buffer", + N_("The \002Insert in Buffer\002 command will insert the contents of Data 1 into the entry where the key sequence was pressed at the current cursor position")}, + {key_action_scroll_page, "Scroll Page", + N_("The \002Scroll Page\002 command scrolls the text widget up or down one page or one line. Set Data 1 to either Up, Down, +1 or -1.")}, + {key_action_set_buffer, "Set Buffer", + N_("The \002Set Buffer\002 command sets the entry where the key sequence was entered to the contents of Data 1")}, + {key_action_history_up, "Last Command", + N_("The \002Last Command\002 command sets the entry to contain the last command entered - the same as pressing up in a shell")}, + {key_action_history_down, "Next Command", + N_("The \002Next Command\002 command sets the entry to contain the next command entered - the same as pressing down in a shell")}, + {key_action_tab_comp, "Complete nick/command", + N_("This command changes the text in the entry to finish an incomplete nickname or command. If Data 1 is set then double-tabbing in a string will select the last nick, not the next")}, + {key_action_comp_chng, "Change Selected Nick", + N_("This command scrolls up and down through the list of nicks. If Data 1 is set to anything it will scroll up, else it scrolls down")}, + {key_action_replace, "Check For Replace", + N_("This command checks the last word entered in the entry against the replace list and replaces it if it finds a match")}, + {key_action_move_tab_left, "Move front tab left", + N_("This command moves the front tab left by one")}, + {key_action_move_tab_right, "Move front tab right", + N_("This command moves the front tab right by one")}, + {key_action_move_tab_family_left, "Move tab family left", + N_("This command moves the current tab family to the left")}, + {key_action_move_tab_family_right, "Move tab family right", + N_("This command moves the current tab family to the right")}, + {key_action_put_history, "Push input line into history", + N_("Push input line into history but doesn't send to server")}, +}; + +void +key_init () +{ + keys_root = NULL; + if (key_load_kbs (NULL) == 1) + { + key_load_defaults (); + if (key_load_kbs (NULL) == 1) + fe_message (_("There was an error loading key" + " bindings configuration"), FE_MSG_ERROR); + } +} + +static char * +key_get_key_name (int keyval) +{ + return gdk_keyval_name (gdk_keyval_to_lower (keyval)); +} + +/* Ok, here are the NOTES + + key_handle_key_press now handles all the key presses and history_keypress is + now defunct. It goes thru the linked list keys_root and finds a matching + key. It runs the action func and switches on these values: + 0) Return + 1) Find next + 2) stop signal and return + + * history_keypress is now dead (and gone) + * key_handle_key_press now takes its role + * All the possible actions are in a struct called key_actions (in fkeys.c) + * it is made of {function, name, desc} + * key bindings can pass 2 *text* strings to the handler. If more options are nee + ded a format can be put on one of these options + * key actions are passed { + the entry widget + the Gdk event + data 1 + data 2 + session struct + } + * key bindings are stored in a linked list of key_binding structs + * which looks like { + int keyval; GDK keynumber + char *keyname; String with the name of the function + int action; Index into key_actions + int mod; Flags of STATE_* above + char *data1, *data2; Pointers to strings, these must be freed + struct key_binding *next; + } + * remember that is (data1 || data2) != NULL then they need to be free()'ed + + --AGL + + */ + +gboolean +key_handle_key_press (GtkWidget *wid, GdkEventKey *evt, session *sess) +{ + struct key_binding *kb, *last = NULL; + int keyval = evt->keyval; + int mod, n; + GSList *list; + + /* where did this event come from? */ + list = sess_list; + while (list) + { + sess = list->data; + if (sess->gui->input_box == wid) + { + if (sess->gui->is_tab) + sess = current_tab; + break; + } + list = list->next; + } + if (!list) + return FALSE; + current_sess = sess; + + if (plugin_emit_keypress (sess, evt->state, evt->keyval, evt->length, evt->string)) + return 1; + + /* maybe the plugin closed this tab? */ + if (!is_session (sess)) + return 1; + + mod = evt->state & (STATE_CTRL | STATE_ALT | STATE_SHIFT); + + kb = keys_root; + while (kb) + { + if (kb->keyval == keyval && kb->mod == mod) + { + if (kb->action < 0 || kb->action > KEY_MAX_ACTIONS) + return 0; + + /* Bump this binding to the top of the list */ + if (last != NULL) + { + last->next = kb->next; + kb->next = keys_root; + keys_root = kb; + } + /* Run the function */ + n = key_actions[kb->action].handler (wid, evt, kb->data1, + kb->data2, sess); + switch (n) + { + case 0: + return 1; + case 2: + g_signal_stop_emission_by_name (G_OBJECT (wid), + "key_press_event"); + return 1; + } + } + last = kb; + kb = kb->next; + } + + switch (keyval) + { + case GDK_space: + key_action_tab_clean (); + break; + +#if defined(USE_GTKSPELL) && !defined(WIN32) + /* gtktextview has no 'activate' event, so we trap ENTER here */ + case GDK_Return: + case GDK_KP_Enter: + if (!(evt->state & GDK_CONTROL_MASK)) + { + g_signal_stop_emission_by_name (G_OBJECT (wid), "key_press_event"); + mg_inputbox_cb (wid, sess->gui); + } +#endif + } + + return 0; +} + +/* Walks keys_root and free()'s everything */ +/*static void +key_free_all () +{ + struct key_binding *cur, *next; + + cur = keys_root; + while (cur) + { + next = cur->next; + if (cur->data1) + free (cur->data1); + if (cur->data2) + free (cur->data2); + free (cur); + cur = next; + } + keys_root = NULL; +}*/ + +/* Turns mod flags into a C-A-S string */ +static char * +key_make_mod_str (int mod, char *buf) +{ + int i = 0; + + if (mod & STATE_CTRL) + { + if (i) + buf[i++] = '-'; + buf[i++] = 'C'; + } + if (mod & STATE_ALT) + { + if (i) + buf[i++] = '-'; + buf[i++] = 'A'; + } + if (mod & STATE_SHIFT) + { + if (i) + buf[i++] = '-'; + buf[i++] = 'S'; + } + buf[i] = 0; + return buf; +} + +/* ***** GUI code here ******************* */ + +/* NOTE: The key_dialog defin is above --AGL */ +static GtkWidget *key_dialog_act_menu, *key_dialog_kb_clist; +static GtkWidget *key_dialog_tog_c, *key_dialog_tog_s, *key_dialog_tog_a; +static GtkWidget *key_dialog_ent_key, *key_dialog_ent_d1, *key_dialog_ent_d2; +static GtkWidget *key_dialog_text; + +static void +key_load_defaults () +{ + /* This is the default config */ +#define defcfg \ + "C\nPrior\nChange Page\nD1:-1\nD2:Relative\n\n"\ + "C\nNext\nChange Page\nD1:1\nD2:Relative\n\n"\ + "A\n9\nChange Page\nD1:9\nD2!\n\n"\ + "A\n8\nChange Page\nD1:8\nD2!\n\n"\ + "A\n7\nChange Page\nD1:7\nD2!\n\n"\ + "A\n6\nChange Page\nD1:6\nD2!\n\n"\ + "A\n5\nChange Page\nD1:5\nD2!\n\n"\ + "A\n4\nChange Page\nD1:4\nD2!\n\n"\ + "A\n3\nChange Page\nD1:3\nD2!\n\n"\ + "A\n2\nChange Page\nD1:2\nD2!\n\n"\ + "A\n1\nChange Page\nD1:1\nD2!\n\n"\ + "C\no\nInsert in Buffer\nD1:\nD2!\n\n"\ + "C\nb\nInsert in Buffer\nD1:\nD2!\n\n"\ + "C\nk\nInsert in Buffer\nD1:\nD2!\n\n"\ + "S\nNext\nChange Selected Nick\nD1!\nD2!\n\n"\ + "S\nPrior\nChange Selected Nick\nD1:Up\nD2!\n\n"\ + "None\nNext\nScroll Page\nD1:Down\nD2!\n\n"\ + "None\nPrior\nScroll Page\nD1:Up\nD2!\n\n"\ + "S\nDown\nScroll Page\nD1:+1\nD2!\n\n"\ + "S\nUp\nScroll Page\nD1:-1\nD2!\n\n"\ + "None\nDown\nNext Command\nD1!\nD2!\n\n"\ + "None\nUp\nLast Command\nD1!\nD2!\n\n"\ + "None\nTab\nComplete nick/command\nD1!\nD2!\n\n"\ + "None\nspace\nCheck For Replace\nD1!\nD2!\n\n"\ + "None\nReturn\nCheck For Replace\nD1!\nD2!\n\n"\ + "None\nKP_Enter\nCheck For Replace\nD1!\nD2!\n\n"\ + "C\nTab\nComplete nick/command\nD1:Up\nD2!\n\n"\ + "A\nLeft\nMove front tab left\nD1!\nD2!\n\n"\ + "A\nRight\nMove front tab right\nD1!\nD2!\n\n"\ + "CS\nPrior\nMove tab family left\nD1!\nD2!\n\n"\ + "CS\nNext\nMove tab family right\nD1!\nD2!\n\n"\ + "None\nF9\nRun Command\nD1:/GUI MENU TOGGLE\nD2!\n\n" + int fd; + + fd = xchat_open_file ("keybindings.conf", O_CREAT | O_TRUNC | O_WRONLY, 0x180, XOF_DOMODE); + if (fd < 0) + /* ???!!! */ + return; + + write (fd, defcfg, strlen (defcfg)); + close (fd); +} + +static void +key_dialog_close () +{ + key_dialog = NULL; + key_save_kbs (NULL); +} + +static void +key_dialog_add_new (GtkWidget * button, GtkCList * list) +{ + gchar *strs[] = { "", NULL, NULL, NULL, NULL }; + struct key_binding *kb; + + strs[1] = _("<none>"); + strs[2] = _("<none>"); + strs[3] = _("<none>"); + strs[4] = _("<none>"); + + kb = malloc (sizeof (struct key_binding)); + + kb->keyval = 0; + kb->keyname = NULL; + kb->action = -1; + kb->mod = 0; + kb->data1 = kb->data2 = NULL; + kb->next = keys_root; + + keys_root = kb; + + gtk_clist_set_row_data (GTK_CLIST (list), + gtk_clist_append (GTK_CLIST (list), strs), kb); + +} + +static void +key_dialog_delete (GtkWidget * button, GtkCList * list) +{ + struct key_binding *kb, *cur, *last; + int row = gtkutil_clist_selection ((GtkWidget *) list); + + if (row != -1) + { + kb = gtk_clist_get_row_data (list, row); + cur = keys_root; + last = NULL; + while (cur) + { + if (cur == kb) + { + if (last) + last->next = kb->next; + else + keys_root = kb->next; + + if (kb->data1) + free (kb->data1); + if (kb->data2) + free (kb->data2); + free (kb); + gtk_clist_remove (list, row); + return; + } + last = cur; + cur = cur->next; + } + printf ("*** key_dialog_delete: couldn't find kb in list!\n"); + /*if (getenv ("XCHAT_DEBUG")) + abort ();*/ + } +} + +static void +key_print_text (GtkXText *xtext, char *text) +{ + unsigned int old = prefs.timestamp; + prefs.timestamp = 0; /* temporarily disable stamps */ + gtk_xtext_clear (GTK_XTEXT (xtext)->buffer, 0); + PrintTextRaw (GTK_XTEXT (xtext)->buffer, text, 0, 0); + prefs.timestamp = old; +} + +static void +key_dialog_sel_act (GtkWidget * un, int num) +{ + int row = gtkutil_clist_selection (key_dialog_kb_clist); + struct key_binding *kb; + + if (row != -1) + { + kb = gtk_clist_get_row_data (GTK_CLIST (key_dialog_kb_clist), row); + kb->action = num; + gtk_clist_set_text (GTK_CLIST (key_dialog_kb_clist), row, 2, + _(key_actions[num].name)); + if (key_actions[num].help) + { + key_print_text (GTK_XTEXT (key_dialog_text), _(key_actions[num].help)); + } + } +} + +static void +key_dialog_sel_row (GtkWidget * clist, gint row, gint column, + GdkEventButton * evt, gpointer data) +{ + struct key_binding *kb = gtk_clist_get_row_data (GTK_CLIST (clist), row); + + if (kb == NULL) + { + printf ("*** key_dialog_sel_row: kb == NULL\n"); + abort (); + } + if (kb->action > -1 && kb->action <= KEY_MAX_ACTIONS) + { + gtk_option_menu_set_history (GTK_OPTION_MENU (key_dialog_act_menu), + kb->action); + if (key_actions[kb->action].help) + { + key_print_text (GTK_XTEXT (key_dialog_text), _(key_actions[kb->action].help)); + } + } + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (key_dialog_tog_c), + (kb->mod & STATE_CTRL) == STATE_CTRL); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (key_dialog_tog_s), + (kb->mod & STATE_SHIFT) == STATE_SHIFT); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (key_dialog_tog_a), + (kb->mod & STATE_ALT) == STATE_ALT); + + if (kb->data1) + gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_d1), kb->data1); + else + gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_d1), ""); + + if (kb->data2) + gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_d2), kb->data2); + else + gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_d2), ""); + + if (kb->keyname) + gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_key), kb->keyname); + else + gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_key), ""); +} + +static void +key_dialog_tog_key (GtkWidget * tog, int kstate) +{ + int state = GTK_TOGGLE_BUTTON (tog)->active; + int row = gtkutil_clist_selection (key_dialog_kb_clist); + struct key_binding *kb; + char buf[32]; + + if (row == -1) + return; + + kb = gtk_clist_get_row_data (GTK_CLIST (key_dialog_kb_clist), row); + if (state) + kb->mod |= kstate; + else + kb->mod &= ~kstate; + + gtk_clist_set_text (GTK_CLIST (key_dialog_kb_clist), row, 0, + key_make_mod_str (kb->mod, buf)); +} + +static GtkWidget * +key_dialog_make_toggle (char *label, void *callback, void *option, + GtkWidget * box) +{ + GtkWidget *wid; + + wid = gtk_check_button_new_with_label (label); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), 0); + gtk_signal_connect (GTK_OBJECT (wid), "toggled", + GTK_SIGNAL_FUNC (callback), option); + gtk_box_pack_end (GTK_BOX (box), wid, 0, 0, 0); + gtk_widget_show (wid); + + return wid; +} + +static GtkWidget * +key_dialog_make_entry (char *label, char *act, void *callback, void *option, + GtkWidget * box) +{ + GtkWidget *wid, *hbox;; + + hbox = gtk_hbox_new (0, 2); + if (label) + { + wid = gtk_label_new (label); + gtk_widget_show (wid); + gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0); + } + wid = gtk_entry_new (); + if (act) + { + gtk_signal_connect (GTK_OBJECT (wid), act, GTK_SIGNAL_FUNC (callback), + option); + } + gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0); + gtk_widget_show (wid); + gtk_widget_show (hbox); + + gtk_box_pack_start (GTK_BOX (box), hbox, 0, 0, 0); + + return wid; +} + +static void +key_dialog_set_key (GtkWidget * entry, GdkEventKey * evt, void *none) +{ + int row = gtkutil_clist_selection (key_dialog_kb_clist); + struct key_binding *kb; + + gtk_entry_set_text (GTK_ENTRY (entry), ""); + + if (row == -1) + return; + + kb = gtk_clist_get_row_data (GTK_CLIST (key_dialog_kb_clist), row); + kb->keyval = evt->keyval; + kb->keyname = key_get_key_name (kb->keyval); + gtk_clist_set_text (GTK_CLIST (key_dialog_kb_clist), row, 1, kb->keyname); + gtk_entry_set_text (GTK_ENTRY (entry), kb->keyname); + g_signal_stop_emission_by_name (G_OBJECT (entry), "key_press_event"); +} + +static void +key_dialog_set_data (GtkWidget * entry, int d) +{ + const char *text = gtk_entry_get_text (GTK_ENTRY (entry)); + int row = gtkutil_clist_selection (key_dialog_kb_clist); + struct key_binding *kb; + char *buf; + int len = strlen (text); + + len++; + + if (row == -1) + return; + + kb = gtk_clist_get_row_data (GTK_CLIST (key_dialog_kb_clist), row); + if (d == 0) + { /* using data1 */ + if (kb->data1) + free (kb->data1); + buf = (char *) malloc (len); + memcpy (buf, text, len); + kb->data1 = buf; + gtk_clist_set_text (GTK_CLIST (key_dialog_kb_clist), row, 3, text); + } else + { + if (kb->data2) + free (kb->data2); + buf = (char *) malloc (len); + memcpy (buf, text, len); + kb->data2 = buf; + gtk_clist_set_text (GTK_CLIST (key_dialog_kb_clist), row, 4, text); + } +} + +void +key_dialog_show () +{ + GtkWidget *vbox, *hbox, *list, *vbox2, *wid, *wid2, *wid3, *hbox2; + struct key_binding *kb; + gchar *titles[] = { NULL, NULL, NULL, "1", "2" }; + char temp[32]; + int i; + + titles[0] = _("Mod"); + titles[1] = _("Key"); + titles[2] = _("Action"); + + if (key_dialog) + { + mg_bring_tofront (key_dialog); + return; + } + + key_dialog = + mg_create_generic_tab ("editkeys", _("XChat: Keyboard Shortcuts"), + TRUE, FALSE, key_dialog_close, NULL, 560, 330, &vbox, 0); + + hbox = gtk_hbox_new (0, 2); + gtk_box_pack_start (GTK_BOX (vbox), hbox, 1, 1, 0); + + list = gtkutil_clist_new (5, titles, hbox, 0, key_dialog_sel_row, 0, NULL, + 0, GTK_SELECTION_SINGLE); + gtk_widget_set_usize (list, 400, 0); + key_dialog_kb_clist = list; + + gtk_widget_show (hbox); + + kb = keys_root; + + gtk_clist_set_column_width (GTK_CLIST (list), 1, 50); + gtk_clist_set_column_width (GTK_CLIST (list), 2, 120); + gtk_clist_set_column_width (GTK_CLIST (list), 3, 50); + gtk_clist_set_column_width (GTK_CLIST (list), 4, 50); + + while (kb) + { + titles[0] = key_make_mod_str (kb->mod, temp); + titles[1] = kb->keyname; + if (kb->action < 0 || kb->action > KEY_MAX_ACTIONS) + titles[2] = _("<none>"); + else + titles[2] = key_actions[kb->action].name; + if (kb->data1) + titles[3] = kb->data1; + else + titles[3] = _("<none>"); + + if (kb->data2) + titles[4] = kb->data2; + else + titles[4] = _("<none>"); + + gtk_clist_set_row_data (GTK_CLIST (list), + gtk_clist_append (GTK_CLIST (list), titles), + kb); + + kb = kb->next; + } + + vbox2 = gtk_vbox_new (0, 2); + gtk_box_pack_end (GTK_BOX (hbox), vbox2, 1, 1, 0); + wid = gtk_button_new_with_label (_("Add New")); + gtk_box_pack_start (GTK_BOX (vbox2), wid, 0, 0, 0); + gtk_signal_connect (GTK_OBJECT (wid), "clicked", + GTK_SIGNAL_FUNC (key_dialog_add_new), list); + gtk_widget_show (wid); + wid = gtk_button_new_with_label (_("Delete")); + gtk_box_pack_start (GTK_BOX (vbox2), wid, 0, 0, 0); + gtk_signal_connect (GTK_OBJECT (wid), "clicked", + GTK_SIGNAL_FUNC (key_dialog_delete), list); + gtk_widget_show (wid); + gtk_widget_show (vbox2); + + wid = gtk_option_menu_new (); + wid2 = gtk_menu_new (); + + for (i = 0; i <= KEY_MAX_ACTIONS; i++) + { + wid3 = gtk_menu_item_new_with_label (_(key_actions[i].name)); + gtk_widget_show (wid3); + gtk_menu_shell_append (GTK_MENU_SHELL (wid2), wid3); + gtk_signal_connect (GTK_OBJECT (wid3), "activate", + GTK_SIGNAL_FUNC (key_dialog_sel_act), + GINT_TO_POINTER (i)); + } + + gtk_option_menu_set_menu (GTK_OPTION_MENU (wid), wid2); + gtk_option_menu_set_history (GTK_OPTION_MENU (wid), 0); + gtk_box_pack_end (GTK_BOX (vbox2), wid, 0, 0, 0); + gtk_widget_show (wid); + key_dialog_act_menu = wid; + + key_dialog_tog_s = key_dialog_make_toggle (_("Shift"), key_dialog_tog_key, + (void *) STATE_SHIFT, vbox2); + key_dialog_tog_a = key_dialog_make_toggle (_("Alt"), key_dialog_tog_key, + (void *) STATE_ALT, vbox2); + key_dialog_tog_c = key_dialog_make_toggle (_("Ctrl"), key_dialog_tog_key, + (void *) STATE_CTRL, vbox2); + + key_dialog_ent_key = key_dialog_make_entry (_("Key"), "key_press_event", + key_dialog_set_key, NULL, + vbox2); + + key_dialog_ent_d1 = key_dialog_make_entry (_("Data 1"), "activate", + key_dialog_set_data, NULL, + vbox2); + key_dialog_ent_d2 = key_dialog_make_entry (_("Data 2"), "activate", + key_dialog_set_data, + (void *) 1, vbox2); + + hbox2 = gtk_hbox_new (0, 2); + gtk_box_pack_end (GTK_BOX (vbox), hbox2, 0, 0, 1); + + wid = gtk_xtext_new (colors, 0); + gtk_xtext_set_tint (GTK_XTEXT (wid), prefs.tint_red, prefs.tint_green, prefs.tint_blue); + gtk_xtext_set_background (GTK_XTEXT (wid), + channelwin_pix, + prefs.transparent); + gtk_widget_set_usize (wid, 0, 75); + gtk_box_pack_start (GTK_BOX (hbox2), wid, 1, 1, 1); + gtk_xtext_set_font (GTK_XTEXT (wid), prefs.font_normal); + gtk_widget_show (wid); + + wid2 = gtk_vscrollbar_new (GTK_XTEXT (wid)->adj); + gtk_box_pack_start (GTK_BOX (hbox2), wid2, 0, 0, 0); + gtk_widget_show (wid2); + + gtk_widget_show (hbox2); + key_dialog_text = wid; + + gtk_widget_show_all (key_dialog); +} + +static void +key_save_kbs (char *fn) +{ + int fd, i; + char buf[512]; + struct key_binding *kb; + + if (!fn) + fd = xchat_open_file ("keybindings.conf", O_CREAT | O_TRUNC | O_WRONLY, + 0x180, XOF_DOMODE); + else + fd = xchat_open_file (fn, O_CREAT | O_TRUNC | O_WRONLY, + 0x180, XOF_DOMODE | XOF_FULLPATH); + if (fd < 0) + { + fe_message (_("Error opening keys config file\n"), FE_MSG_ERROR); + return; + } + write (fd, buf, + snprintf (buf, 510, "# XChat key bindings config file\n\n")); + + kb = keys_root; + i = 0; + + while (kb) + { + if (kb->keyval == -1 || kb->keyname == NULL || kb->action < 0) + { + kb = kb->next; + continue; + } + i = 0; + if (kb->mod & STATE_CTRL) + { + i++; + write (fd, "C", 1); + } + if (kb->mod & STATE_ALT) + { + i++; + write (fd, "A", 1); + } + if (kb->mod & STATE_SHIFT) + { + i++; + write (fd, "S", 1); + } + if (i == 0) + write (fd, "None\n", 5); + else + write (fd, "\n", 1); + + write (fd, buf, snprintf (buf, 510, "%s\n%s\n", kb->keyname, + key_actions[kb->action].name)); + if (kb->data1 && kb->data1[0]) + write (fd, buf, snprintf (buf, 510, "D1:%s\n", kb->data1)); + else + write (fd, "D1!\n", 4); + + if (kb->data2 && kb->data2[0]) + write (fd, buf, snprintf (buf, 510, "D2:%s\n", kb->data2)); + else + write (fd, "D2!\n", 4); + + write (fd, "\n", 1); + + kb = kb->next; + } + + close (fd); +} + +/* I just know this is going to be a nasty parse, if you think it's bugged + it almost certainly is so contact the XChat dev team --AGL */ + +static inline int +key_load_kbs_helper_mod (char *in, int *out) +{ + int n, len, mod = 0; + + /* First strip off the fluff */ + while (in[0] == ' ' || in[0] == '\t') + in++; + len = strlen (in); + while (in[len] == ' ' || in[len] == '\t') + { + in[len] = 0; + len--; + } + + if (strcmp (in, "None") == 0) + { + *out = 0; + return 0; + } + for (n = 0; n < len; n++) + { + switch (in[n]) + { + case 'C': + mod |= STATE_CTRL; + break; + case 'A': + mod |= STATE_ALT; + break; + case 'S': + mod |= STATE_SHIFT; + break; + default: + return 1; + } + } + + *out = mod; + return 0; +} + +/* These are just local defines to keep me sane --AGL */ + +#define KBSTATE_MOD 0 +#define KBSTATE_KEY 1 +#define KBSTATE_ACT 2 +#define KBSTATE_DT1 3 +#define KBSTATE_DT2 4 + +/* *** Warning, Warning! - massive function ahead! --AGL */ + +static int +key_load_kbs (char *filename) +{ + char *buf, *ibuf; + struct stat st; + struct key_binding *kb = NULL, *last = NULL; + int fd, len, pnt = 0, state = 0, n; + + if (filename == NULL) + fd = xchat_open_file ("keybindings.conf", O_RDONLY, 0, 0); + else + fd = xchat_open_file (filename, O_RDONLY, 0, XOF_FULLPATH); + if (fd < 0) + return 1; + if (fstat (fd, &st) != 0) + return 1; + ibuf = malloc (st.st_size); + read (fd, ibuf, st.st_size); + close (fd); + + while (buf_get_line (ibuf, &buf, &pnt, st.st_size)) + { + if (buf[0] == '#') + continue; + if (strlen (buf) == 0) + continue; + + switch (state) + { + case KBSTATE_MOD: + kb = (struct key_binding *) malloc (sizeof (struct key_binding)); + if (key_load_kbs_helper_mod (buf, &kb->mod)) + goto corrupt_file; + state = KBSTATE_KEY; + continue; + case KBSTATE_KEY: + /* First strip off the fluff */ + while (buf[0] == ' ' || buf[0] == '\t') + buf++; + len = strlen (buf); + while (buf[len] == ' ' || buf[len] == '\t') + { + buf[len] = 0; + len--; + } + + n = gdk_keyval_from_name (buf); + if (n == 0) + { + /* Unknown keyname, abort */ + if (last) + last->next = NULL; + free (ibuf); + ibuf = malloc (1024); + snprintf (ibuf, 1024, + _("Unknown keyname %s in key bindings config file\nLoad aborted, please fix %s/keybindings.conf\n"), + buf, get_xdir_utf8 ()); + fe_message (ibuf, FE_MSG_ERROR); + free (ibuf); + return 2; + } + kb->keyname = gdk_keyval_name (n); + kb->keyval = n; + + state = KBSTATE_ACT; + continue; + case KBSTATE_ACT: + /* First strip off the fluff */ + while (buf[0] == ' ' || buf[0] == '\t') + buf++; + len = strlen (buf); + while (buf[len] == ' ' || buf[len] == '\t') + { + buf[len] = 0; + len--; + } + + for (n = 0; n < KEY_MAX_ACTIONS + 1; n++) + { + if (strcmp (key_actions[n].name, buf) == 0) + { + kb->action = n; + break; + } + } + + if (n == KEY_MAX_ACTIONS + 1) + { + if (last) + last->next = NULL; + free (ibuf); + ibuf = malloc (1024); + snprintf (ibuf, 1024, + _("Unknown action %s in key bindings config file\nLoad aborted, Please fix %s/keybindings\n"), + buf, get_xdir_utf8 ()); + fe_message (ibuf, FE_MSG_ERROR); + free (ibuf); + return 3; + } + state = KBSTATE_DT1; + continue; + case KBSTATE_DT1: + case KBSTATE_DT2: + if (state == KBSTATE_DT1) + kb->data1 = kb->data2 = NULL; + + while (buf[0] == ' ' || buf[0] == '\t') + buf++; + + if (buf[0] != 'D') + { + free (ibuf); + ibuf = malloc (1024); + snprintf (ibuf, 1024, + _("Expecting Data line (beginning Dx{:|!}) but got:\n%s\n\nLoad aborted, Please fix %s/keybindings\n"), + buf, get_xdir_utf8 ()); + fe_message (ibuf, FE_MSG_ERROR); + free (ibuf); + return 4; + } + switch (buf[1]) + { + case '1': + if (state != KBSTATE_DT1) + goto corrupt_file; + break; + case '2': + if (state != KBSTATE_DT2) + goto corrupt_file; + break; + default: + goto corrupt_file; + } + + if (buf[2] == ':') + { + len = strlen (buf); + /* Add one for the NULL, subtract 3 for the "Dx:" */ + len++; + len -= 3; + if (state == KBSTATE_DT1) + { + kb->data1 = malloc (len); + memcpy (kb->data1, &buf[3], len); + } else + { + kb->data2 = malloc (len); + memcpy (kb->data2, &buf[3], len); + } + } else if (buf[2] == '!') + { + if (state == KBSTATE_DT1) + kb->data1 = NULL; + else + kb->data2 = NULL; + } + if (state == KBSTATE_DT1) + { + state = KBSTATE_DT2; + continue; + } else + { + if (last) + last->next = kb; + else + keys_root = kb; + last = kb; + + state = KBSTATE_MOD; + } + + continue; + } + } + if (last) + last->next = NULL; + free (ibuf); + return 0; + + corrupt_file: + /*if (getenv ("XCHAT_DEBUG")) + abort ();*/ + snprintf (ibuf, 1024, + _("Key bindings config file is corrupt, load aborted\n" + "Please fix %s/keybindings.conf\n"), + get_xdir_utf8 ()); + fe_message (ibuf, FE_MSG_ERROR); + free (ibuf); + return 5; +} + +/* ***** Key actions start here *********** */ + +/* See the NOTES above --AGL */ + +/* "Run command" */ +static int +key_action_handle_command (GtkWidget * wid, GdkEventKey * evt, char *d1, + char *d2, struct session *sess) +{ + int ii, oi, len; + char out[2048], d = 0; + + if (!d1) + return 0; + + len = strlen (d1); + + /* Replace each "\n" substring with '\n' */ + for (ii = oi = 0; ii < len; ii++) + { + d = d1[ii]; + if (d == '\\') + { + ii++; + d = d1[ii]; + if (d == 'n') + out[oi++] = '\n'; + else if (d == '\\') + out[oi++] = '\\'; + else + { + out[oi++] = '\\'; + out[oi++] = d; + } + continue; + } + out[oi++] = d; + } + out[oi] = 0; + + handle_multiline (sess, out, 0, 0); + return 0; +} + +static int +key_action_page_switch (GtkWidget * wid, GdkEventKey * evt, char *d1, + char *d2, struct session *sess) +{ + int len, i, num; + + if (!d1) + return 1; + + len = strlen (d1); + if (!len) + return 1; + + for (i = 0; i < len; i++) + { + if (d1[i] < '0' || d1[i] > '9') + { + if (i == 0 && (d1[i] == '+' || d1[i] == '-')) + continue; + else + return 1; + } + } + + num = atoi (d1); + if (!d2) + num--; + if (!d2 || d2[0] == 0) + mg_switch_page (FALSE, num); + else + mg_switch_page (TRUE, num); + return 0; +} + +int +key_action_insert (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2, + struct session *sess) +{ + int tmp_pos; + + if (!d1) + return 1; + + tmp_pos = SPELL_ENTRY_GET_POS (wid); + SPELL_ENTRY_INSERT (wid, d1, strlen (d1), &tmp_pos); + SPELL_ENTRY_SET_POS (wid, tmp_pos); + return 2; +} + +/* handles PageUp/Down keys */ +static int +key_action_scroll_page (GtkWidget * wid, GdkEventKey * evt, char *d1, + char *d2, struct session *sess) +{ + int value, end; + GtkAdjustment *adj; + enum scroll_type { PAGE_UP, PAGE_DOWN, LINE_UP, LINE_DOWN }; + int type = PAGE_DOWN; + + if (d1) + { + if (!strcasecmp (d1, "up")) + type = PAGE_UP; + else if (!strcasecmp (d1, "+1")) + type = LINE_DOWN; + else if (!strcasecmp (d1, "-1")) + type = LINE_UP; + } + + if (!sess) + return 0; + + adj = GTK_RANGE (sess->gui->vscrollbar)->adjustment; + end = adj->upper - adj->lower - adj->page_size; + + switch (type) + { + case LINE_UP: + value = adj->value - 1.0; + break; + + case LINE_DOWN: + value = adj->value + 1.0; + break; + + case PAGE_UP: + value = adj->value - (adj->page_size - 1); + break; + + default: /* PAGE_DOWN */ + value = adj->value + (adj->page_size - 1); + break; + } + + if (value < 0) + value = 0; + if (value > end) + value = end; + + gtk_adjustment_set_value (adj, value); + + return 0; +} + +static int +key_action_set_buffer (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2, + struct session *sess) +{ + if (!d1) + return 1; + if (d1[0] == 0) + return 1; + + SPELL_ENTRY_SET_TEXT (wid, d1); + SPELL_ENTRY_SET_POS (wid, -1); + + return 2; +} + +static int +key_action_history_up (GtkWidget * wid, GdkEventKey * ent, char *d1, char *d2, + struct session *sess) +{ + char *new_line; + + new_line = history_up (&sess->history, SPELL_ENTRY_GET_TEXT (wid)); + if (new_line) + { + SPELL_ENTRY_SET_TEXT (wid, new_line); + SPELL_ENTRY_SET_POS (wid, -1); + } + + return 2; +} + +static int +key_action_history_down (GtkWidget * wid, GdkEventKey * ent, char *d1, + char *d2, struct session *sess) +{ + char *new_line; + + new_line = history_down (&sess->history); + if (new_line) + { + SPELL_ENTRY_SET_TEXT (wid, new_line); + SPELL_ENTRY_SET_POS (wid, -1); + } + + return 2; +} + +/* old data that we reuse */ +static struct gcomp_data old_gcomp; + +/* work on the data, ie return only channels */ +static int +double_chan_cb (session *lsess, GList **list) +{ + if (lsess->type == SESS_CHANNEL) + *list = g_list_prepend(*list, lsess->channel); + return TRUE; +} + +/* convert a slist -> list. */ +static GList * +chanlist_double_list (GSList *inlist) +{ + GList *list = NULL; + g_slist_foreach(inlist, (GFunc)double_chan_cb, &list); + return list; +} + +/* handle commands */ +static int +double_cmd_cb (struct popup *pop, GList **list) +{ + *list = g_list_prepend(*list, pop->name); + return TRUE; +} + +/* convert a slist -> list. */ +static GList * +cmdlist_double_list (GSList *inlist) +{ + GList *list = NULL; + g_slist_foreach (inlist, (GFunc)double_cmd_cb, &list); + return list; +} + +static char * +gcomp_nick_func (char *data) +{ + if (data) + return ((struct User *)data)->nick; + return ""; +} + +void +key_action_tab_clean(void) +{ + if (old_gcomp.elen) + { + old_gcomp.data[0] = 0; + old_gcomp.elen = 0; + } +} + +/* Used in the followig completers */ +#define COMP_BUF 2048 + +/* For use in sorting the user list for completion */ +static int +talked_recent_cmp (struct User *a, struct User *b) +{ + if (a->lasttalk < b->lasttalk) + return -1; + + if (a->lasttalk > b->lasttalk) + return 1; + + return 0; +} + +static int +key_action_tab_comp (GtkWidget *t, GdkEventKey *entry, char *d1, char *d2, + struct session *sess) +{ + int len = 0, elen = 0, i = 0, cursor_pos, ent_start = 0, comp = 0, found = 0, + prefix_len, skip_len = 0, is_nick, is_cmd = 0; + char buf[COMP_BUF], ent[CHANLEN], *postfix = NULL, *result, *ch; + GList *list = NULL, *tmp_list = NULL; + const char *text; + GCompletion *gcomp = NULL; + + /* force the IM Context to reset */ + SPELL_ENTRY_SET_EDITABLE (t, FALSE); + SPELL_ENTRY_SET_EDITABLE (t, TRUE); + + text = SPELL_ENTRY_GET_TEXT (t); + if (text[0] == 0) + return 1; + + len = g_utf8_strlen (text, -1); /* must be null terminated */ + + cursor_pos = SPELL_ENTRY_GET_POS (t); + + buf[0] = 0; /* make sure we don't get garbage in the buffer */ + + /* handle "nick: " or "nick " or "#channel "*/ + ch = g_utf8_find_prev_char(text, g_utf8_offset_to_pointer(text,cursor_pos)); + if (ch && ch[0] == ' ') + { + skip_len++; + ch = g_utf8_find_prev_char(text, ch); + if (!ch) + return 2; + + cursor_pos = g_utf8_pointer_to_offset(text, ch); + if (cursor_pos && (g_utf8_get_char_validated(ch, -1) == ':' || + g_utf8_get_char_validated(ch, -1) == ',' || + g_utf8_get_char_validated(ch, -1) == prefs.nick_suffix[0])) + { + skip_len++; + } + else + cursor_pos = g_utf8_pointer_to_offset(text, g_utf8_offset_to_pointer(ch, 1)); + } + + comp = skip_len; + + /* store the text following the cursor for reinsertion later */ + if ((cursor_pos + skip_len) < len) + postfix = g_utf8_offset_to_pointer(text, cursor_pos + skip_len); + + for (ent_start = cursor_pos; ; --ent_start) + { + if (ent_start == 0) + break; + ch = g_utf8_offset_to_pointer(text, ent_start - 1); + if (ch && ch[0] == ' ') + break; + } + + if (ent_start == 0 && text[0] == prefs.cmdchar[0]) + { + ent_start++; + is_cmd = 1; + } + + prefix_len = ent_start; + elen = cursor_pos - ent_start; + + g_utf8_strncpy (ent, g_utf8_offset_to_pointer (text, prefix_len), elen); + + is_nick = (ent[0] == '#' || ent[0] == '&' || is_cmd) ? 0 : 1; + + if (sess->type == SESS_DIALOG && is_nick) + { + /* tab in a dialog completes the other person's name */ + if (rfc_ncasecmp (sess->channel, ent, elen) == 0) + { + result = sess->channel; + is_nick = 0; + } + else + return 2; + } + else + { + if (is_nick) + { + gcomp = g_completion_new((GCompletionFunc)gcomp_nick_func); + tmp_list = userlist_double_list(sess); /* create a temp list so we can free the memory */ + if (prefs.completion_sort == 1) /* sort in last-talk order? */ + tmp_list = g_list_sort (tmp_list, (void *)talked_recent_cmp); + } + else + { + gcomp = g_completion_new (NULL); + if (is_cmd) + { + tmp_list = cmdlist_double_list (command_list); + for(i = 0; xc_cmds[i].name != NULL ; i++) + { + tmp_list = g_list_prepend (tmp_list, xc_cmds[i].name); + } + tmp_list = plugin_command_list(tmp_list); + } + else + tmp_list = chanlist_double_list (sess_list); + } + tmp_list = g_list_reverse(tmp_list); /* make the comp entries turn up in the right order */ + g_completion_set_compare (gcomp, (GCompletionStrncmpFunc)rfc_ncasecmp); + if (tmp_list) + { + g_completion_add_items (gcomp, tmp_list); + g_list_free (tmp_list); + } + + if (comp && !(rfc_ncasecmp(old_gcomp.data, ent, old_gcomp.elen) == 0)) + { + key_action_tab_clean (); + comp = 0; + } + +#if GLIB_CHECK_VERSION(2,4,0) + list = g_completion_complete_utf8 (gcomp, comp ? old_gcomp.data : ent, &result); +#else + list = g_completion_complete (gcomp, comp ? old_gcomp.data : ent, &result); +#endif + + if (result == NULL) /* No matches found */ + { + g_completion_free(gcomp); + return 2; + } + + if (comp) /* existing completion */ + { + while(list) /* find the current entry */ + { + if(rfc_ncasecmp(list->data, ent, elen) == 0) + { + found = 1; + break; + } + list = list->next; + } + + if (found) + { + if (!(d1 && d1[0])) /* not holding down shift */ + { + if (g_list_next(list) == NULL) + list = g_list_first(list); + else + list = g_list_next(list); + } + else + { + if (g_list_previous(list) == NULL) + list = g_list_last(list); + else + list = g_list_previous(list); + } + g_free(result); + result = (char*)list->data; + } + else + { + g_free(result); + g_completion_free(gcomp); + return 2; + } + } + else + { + strcpy(old_gcomp.data, ent); + old_gcomp.elen = elen; + + /* Get the first nick and put out the data for future nickcompletes */ + if (prefs.completion_amount && g_list_length (list) <= prefs.completion_amount) + { + g_free(result); + result = (char*)list->data; + } + else + { + /* bash style completion */ + if (g_list_next(list) != NULL) + { + if (strlen (result) > elen) /* the largest common prefix is larger than nick, change the data */ + { + if (prefix_len) + g_utf8_strncpy (buf, text, prefix_len); + strncat (buf, result, COMP_BUF - prefix_len); + cursor_pos = strlen (buf); + g_free(result); +#if !GLIB_CHECK_VERSION(2,4,0) + g_utf8_validate (buf, -1, (const gchar **)&result); + (*result) = 0; +#endif + if (postfix) + { + strcat (buf, " "); + strncat (buf, postfix, COMP_BUF - cursor_pos -1); + } + SPELL_ENTRY_SET_TEXT (t, buf); + SPELL_ENTRY_SET_POS (t, g_utf8_pointer_to_offset(buf, buf + cursor_pos)); + buf[0] = 0; + } + else + g_free(result); + while (list) + { + len = strlen (buf); /* current buffer */ + elen = strlen (list->data); /* next item to add */ + if (len + elen + 2 >= COMP_BUF) /* +2 is space + null */ + { + PrintText (sess, buf); + buf[0] = 0; + len = 0; + } + strcpy (buf + len, (char *) list->data); + strcpy (buf + len + elen, " "); + list = list->next; + } + PrintText (sess, buf); + g_completion_free(gcomp); + return 2; + } + /* Only one matching entry */ + g_free(result); + result = list->data; + } + } + } + + if(result) + { + if (prefix_len) + g_utf8_strncpy(buf, text, prefix_len); + strncat (buf, result, COMP_BUF - (prefix_len + 3)); /* make sure nicksuffix and space fits */ + if(!prefix_len && is_nick) + strcat (buf, &prefs.nick_suffix[0]); + strcat (buf, " "); + cursor_pos = strlen (buf); + if (postfix) + strncat (buf, postfix, COMP_BUF - cursor_pos - 2); + SPELL_ENTRY_SET_TEXT (t, buf); + SPELL_ENTRY_SET_POS (t, g_utf8_pointer_to_offset(buf, buf + cursor_pos)); + } + if (gcomp) + g_completion_free(gcomp); + return 2; +} +#undef COMP_BUF + +static int +key_action_comp_chng (GtkWidget * wid, GdkEventKey * ent, char *d1, char *d2, + struct session *sess) +{ + key_action_tab_comp(wid, ent, d1, d2, sess); + return 2; +} + + +static int +key_action_replace (GtkWidget * wid, GdkEventKey * ent, char *d1, char *d2, + struct session *sess) +{ + replace_handle (wid); + return 1; +} + + +static int +key_action_move_tab_left (GtkWidget * wid, GdkEventKey * ent, char *d1, + char *d2, struct session *sess) +{ + mg_move_tab (sess, +1); + return 2; /* don't allow default action */ +} + +static int +key_action_move_tab_right (GtkWidget * wid, GdkEventKey * ent, char *d1, + char *d2, struct session *sess) +{ + mg_move_tab (sess, -1); + return 2; /* -''- */ +} + +static int +key_action_move_tab_family_left (GtkWidget * wid, GdkEventKey * ent, char *d1, + char *d2, struct session *sess) +{ + mg_move_tab_family (sess, +1); + return 2; /* don't allow default action */ +} + +static int +key_action_move_tab_family_right (GtkWidget * wid, GdkEventKey * ent, char *d1, + char *d2, struct session *sess) +{ + mg_move_tab_family (sess, -1); + return 2; /* -''- */ +} + +static int +key_action_put_history (GtkWidget * wid, GdkEventKey * ent, char *d1, + char *d2, struct session *sess) +{ + history_add (&sess->history, SPELL_ENTRY_GET_TEXT (wid)); + SPELL_ENTRY_SET_TEXT (wid, ""); + return 2; /* -''- */ +} + + +/* -------- */ + + +#define STATE_SHIFT GDK_SHIFT_MASK +#define STATE_ALT GDK_MOD1_MASK +#define STATE_CTRL GDK_CONTROL_MASK + +static void +replace_handle (GtkWidget *t) +{ + const char *text, *postfix_pnt; + struct popup *pop; + GSList *list = replace_list; + char word[256]; + char postfix[256]; + char outbuf[4096]; + int c, len, xlen; + + text = SPELL_ENTRY_GET_TEXT (t); + + len = strlen (text); + if (len < 1) + return; + + for (c = len - 1; c > 0; c--) + { + if (text[c] == ' ') + break; + } + if (text[c] == ' ') + c++; + xlen = c; + if (len - c >= (sizeof (word) - 12)) + return; + if (len - c < 1) + return; + memcpy (word, &text[c], len - c); + word[len - c] = 0; + len = strlen (word); + if (word[0] == '\'' && word[len] == '\'') + return; + postfix_pnt = NULL; + for (c = 0; c < len; c++) + { + if (word[c] == '\'') + { + postfix_pnt = &word[c + 1]; + word[c] = 0; + break; + } + } + + if (postfix_pnt != NULL) + { + if (strlen (postfix_pnt) > sizeof (postfix) - 12) + return; + strcpy (postfix, postfix_pnt); + } + while (list) + { + pop = (struct popup *) list->data; + if (strcmp (pop->name, word) == 0) + { + memcpy (outbuf, text, xlen); + outbuf[xlen] = 0; + if (postfix_pnt == NULL) + snprintf (word, sizeof (word), "%s", pop->cmd); + else + snprintf (word, sizeof (word), "%s%s", pop->cmd, postfix); + strcat (outbuf, word); + SPELL_ENTRY_SET_TEXT (t, outbuf); + SPELL_ENTRY_SET_POS (t, -1); + return; + } + list = list->next; + } +} + diff --git a/src/fe-gtk/fkeys.h b/src/fe-gtk/fkeys.h new file mode 100644 index 00000000..20cd4c73 --- /dev/null +++ b/src/fe-gtk/fkeys.h @@ -0,0 +1,5 @@ +void key_init (void); +void key_dialog_show (void); +int key_handle_key_press (GtkWidget * wid, GdkEventKey * evt, session *sess); +int key_action_insert (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2, + session *sess); diff --git a/src/fe-gtk/gtkutil.c b/src/fe-gtk/gtkutil.c new file mode 100644 index 00000000..63ab491b --- /dev/null +++ b/src/fe-gtk/gtkutil.c @@ -0,0 +1,675 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ +#define _FILE_OFFSET_BITS 64 /* allow selection of large files */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include "fe-gtk.h" + +#include <gtk/gtkbutton.h> +#include <gtk/gtkclist.h> +#include <gtk/gtkscrolledwindow.h> +#include <gtk/gtkmessagedialog.h> +#include <gtk/gtkwindow.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkimage.h> +#include <gtk/gtktooltips.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkspinbutton.h> +#include <gtk/gtkclipboard.h> +#include <gtk/gtktreeview.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtkcellrenderertext.h> +#include <gtk/gtkcellrenderertoggle.h> +#include <gtk/gtkversion.h> +#include <gtk/gtkfilechooserdialog.h> + +#include "../common/xchat.h" +#include "../common/fe.h" +#include "../common/util.h" +#include "gtkutil.h" +#include "pixmaps.h" + +/* gtkutil.c, just some gtk wrappers */ + +extern void path_part (char *file, char *path, int pathlen); + + +struct file_req +{ + GtkWidget *dialog; + void *userdata; + filereqcallback callback; + int flags; /* FRF_* flags */ +}; + +static char last_dir[256] = ""; + + +static void +gtkutil_file_req_destroy (GtkWidget * wid, struct file_req *freq) +{ + freq->callback (freq->userdata, NULL); + free (freq); +} + +static void +gtkutil_check_file (char *file, struct file_req *freq) +{ + struct stat st; + int axs = FALSE; + + path_part (file, last_dir, sizeof (last_dir)); + + /* check if the file is readable or writable */ + if (freq->flags & FRF_WRITE) + { + if (access (last_dir, W_OK) == 0) + axs = TRUE; + } else + { + if (stat (file, &st) != -1) + { + if (!S_ISDIR (st.st_mode) || (freq->flags & FRF_CHOOSEFOLDER)) + axs = TRUE; + } + } + + if (axs) + { + char *utf8_file; + /* convert to UTF8. It might be converted back to locale by + server.c's g_convert */ + utf8_file = xchat_filename_to_utf8 (file, -1, NULL, NULL, NULL); + if (utf8_file) + { + freq->callback (freq->userdata, utf8_file); + g_free (utf8_file); + } else + { + fe_message ("Filename encoding is corrupt.", FE_MSG_ERROR); + } + } else + { + if (freq->flags & FRF_WRITE) + fe_message (_("Cannot write to that file."), FE_MSG_ERROR); + else + fe_message (_("Cannot read that file."), FE_MSG_ERROR); + } +} + +static void +gtkutil_file_req_done (GtkWidget * wid, struct file_req *freq) +{ + GSList *files, *cur; + GtkFileChooser *fs = GTK_FILE_CHOOSER (freq->dialog); + + if (freq->flags & FRF_MULTIPLE) + { + files = cur = gtk_file_chooser_get_filenames (fs); + while (cur) + { + gtkutil_check_file (cur->data, freq); + g_free (cur->data); + cur = cur->next; + } + if (files) + g_slist_free (files); + } else + { + if (freq->flags & FRF_CHOOSEFOLDER) + gtkutil_check_file (gtk_file_chooser_get_current_folder (fs), freq); + else + gtkutil_check_file (gtk_file_chooser_get_filename (fs), freq); + } + + /* this should call the "destroy" cb, where we free(freq) */ + gtk_widget_destroy (freq->dialog); +} + +static void +gtkutil_file_req_response (GtkWidget *dialog, gint res, struct file_req *freq) +{ + switch (res) + { + case GTK_RESPONSE_ACCEPT: + gtkutil_file_req_done (dialog, freq); + break; + + case GTK_RESPONSE_CANCEL: + /* this should call the "destroy" cb, where we free(freq) */ + gtk_widget_destroy (freq->dialog); + } +} + +void +gtkutil_file_req (const char *title, void *callback, void *userdata, char *filter, + int flags) +{ + struct file_req *freq; + GtkWidget *dialog; + extern char *get_xdir_fs (void); + + if (flags & FRF_WRITE) + { + dialog = gtk_file_chooser_dialog_new (title, NULL, + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + if (filter && filter[0]) /* filter becomes initial name when saving */ + { + char temp[1024]; + path_part (filter, temp, sizeof (temp)); + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), temp); + gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), file_part (filter)); + } +#if GTK_CHECK_VERSION(2,8,0) + if (!(flags & FRF_NOASKOVERWRITE)) + gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE); +#endif + } + else + dialog = gtk_file_chooser_dialog_new (title, NULL, + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + NULL); + if (flags & FRF_MULTIPLE) + gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dialog), TRUE); + if (last_dir[0]) + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), last_dir); + if (flags & FRF_ADDFOLDER) + gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog), + get_xdir_fs (), NULL); + if (flags & FRF_CHOOSEFOLDER) + { + gtk_file_chooser_set_action (GTK_FILE_CHOOSER (dialog), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), filter); + } + else + { + if (filter && (flags & FRF_FILTERISINITIAL)) + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), filter); + } + + freq = malloc (sizeof (struct file_req)); + freq->dialog = dialog; + freq->flags = flags; + freq->callback = callback; + freq->userdata = userdata; + + g_signal_connect (G_OBJECT (dialog), "response", + G_CALLBACK (gtkutil_file_req_response), freq); + g_signal_connect (G_OBJECT (dialog), "destroy", + G_CALLBACK (gtkutil_file_req_destroy), (gpointer) freq); + gtk_widget_show (dialog); +} + +void +gtkutil_destroy (GtkWidget * igad, GtkWidget * dgad) +{ + gtk_widget_destroy (dgad); +} + +static void +gtkutil_get_str_response (GtkDialog *dialog, gint arg1, gpointer entry) +{ + void (*callback) (int cancel, char *text, void *user_data); + char *text; + void *user_data; + + text = (char *) gtk_entry_get_text (GTK_ENTRY (entry)); + callback = g_object_get_data (G_OBJECT (dialog), "cb"); + user_data = g_object_get_data (G_OBJECT (dialog), "ud"); + + switch (arg1) + { + case GTK_RESPONSE_REJECT: + callback (TRUE, text, user_data); + gtk_widget_destroy (GTK_WIDGET (dialog)); + break; + case GTK_RESPONSE_ACCEPT: + callback (FALSE, text, user_data); + gtk_widget_destroy (GTK_WIDGET (dialog)); + break; + } +} + +static void +gtkutil_str_enter (GtkWidget *entry, GtkWidget *dialog) +{ + gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT); +} + +void +fe_get_str (char *msg, char *def, void *callback, void *userdata) +{ + GtkWidget *dialog; + GtkWidget *entry; + GtkWidget *hbox; + GtkWidget *label; + + dialog = gtk_dialog_new_with_buttons (msg, NULL, 0, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + NULL); + gtk_box_set_homogeneous (GTK_BOX (GTK_DIALOG (dialog)->vbox), TRUE); + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + hbox = gtk_hbox_new (TRUE, 0); + + g_object_set_data (G_OBJECT (dialog), "cb", callback); + g_object_set_data (G_OBJECT (dialog), "ud", userdata); + + entry = gtk_entry_new (); + g_signal_connect (G_OBJECT (entry), "activate", + G_CALLBACK (gtkutil_str_enter), dialog); + gtk_entry_set_text (GTK_ENTRY (entry), def); + gtk_box_pack_end (GTK_BOX (hbox), entry, 0, 0, 0); + + label = gtk_label_new (msg); + gtk_box_pack_end (GTK_BOX (hbox), label, 0, 0, 0); + + g_signal_connect (G_OBJECT (dialog), "response", + G_CALLBACK (gtkutil_get_str_response), entry); + + gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), hbox); + + gtk_widget_show_all (dialog); +} + +static void +gtkutil_get_number_response (GtkDialog *dialog, gint arg1, gpointer spin) +{ + void (*callback) (int cancel, int value, void *user_data); + int num; + void *user_data; + + num = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (spin)); + callback = g_object_get_data (G_OBJECT (dialog), "cb"); + user_data = g_object_get_data (G_OBJECT (dialog), "ud"); + + switch (arg1) + { + case GTK_RESPONSE_REJECT: + callback (TRUE, num, user_data); + gtk_widget_destroy (GTK_WIDGET (dialog)); + break; + case GTK_RESPONSE_ACCEPT: + callback (FALSE, num, user_data); + gtk_widget_destroy (GTK_WIDGET (dialog)); + break; + } +} + +void +fe_get_int (char *msg, int def, void *callback, void *userdata) +{ + GtkWidget *dialog; + GtkWidget *spin; + GtkWidget *hbox; + GtkWidget *label; + GtkAdjustment *adj; + + dialog = gtk_dialog_new_with_buttons (msg, NULL, 0, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + NULL); + gtk_box_set_homogeneous (GTK_BOX (GTK_DIALOG (dialog)->vbox), TRUE); + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + hbox = gtk_hbox_new (TRUE, 0); + + g_object_set_data (G_OBJECT (dialog), "cb", callback); + g_object_set_data (G_OBJECT (dialog), "ud", userdata); + + spin = gtk_spin_button_new (NULL, 1, 0); + adj = gtk_spin_button_get_adjustment ((GtkSpinButton*)spin); + adj->lower = 0; + adj->upper = 1024; + adj->step_increment = 1; + gtk_adjustment_changed (adj); + gtk_spin_button_set_value ((GtkSpinButton*)spin, def); + gtk_box_pack_end (GTK_BOX (hbox), spin, 0, 0, 0); + + label = gtk_label_new (msg); + gtk_box_pack_end (GTK_BOX (hbox), label, 0, 0, 0); + + g_signal_connect (G_OBJECT (dialog), "response", + G_CALLBACK (gtkutil_get_number_response), spin); + + gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), hbox); + + gtk_widget_show_all (dialog); +} + +GtkWidget * +gtkutil_button (GtkWidget *box, char *stock, char *tip, void *callback, + void *userdata, char *labeltext) +{ + GtkWidget *wid, *img, *bbox; + + wid = gtk_button_new (); + + if (labeltext) + { + gtk_button_set_label (GTK_BUTTON (wid), labeltext); + gtk_button_set_image (GTK_BUTTON (wid), gtk_image_new_from_stock (stock, GTK_ICON_SIZE_MENU)); + gtk_button_set_use_underline (GTK_BUTTON (wid), TRUE); + if (box) + gtk_container_add (GTK_CONTAINER (box), wid); + } + else + { + bbox = gtk_hbox_new (0, 0); + gtk_container_add (GTK_CONTAINER (wid), bbox); + gtk_widget_show (bbox); + + img = gtk_image_new_from_stock (stock, GTK_ICON_SIZE_MENU); + if (stock == GTK_STOCK_GOTO_LAST) + gtk_widget_set_usize (img, 10, 6); + gtk_container_add (GTK_CONTAINER (bbox), img); + gtk_widget_show (img); + gtk_box_pack_start (GTK_BOX (box), wid, 0, 0, 0); + } + + g_signal_connect (G_OBJECT (wid), "clicked", + G_CALLBACK (callback), userdata); + gtk_widget_show (wid); + if (tip) + add_tip (wid, tip); + + return wid; +} + +void +gtkutil_label_new (char *text, GtkWidget * box) +{ + GtkWidget *label = gtk_label_new (text); + gtk_container_add (GTK_CONTAINER (box), label); + gtk_widget_show (label); +} + +GtkWidget * +gtkutil_entry_new (int max, GtkWidget * box, void *callback, + gpointer userdata) +{ + GtkWidget *entry = gtk_entry_new_with_max_length (max); + gtk_container_add (GTK_CONTAINER (box), entry); + if (callback) + g_signal_connect (G_OBJECT (entry), "changed", + G_CALLBACK (callback), userdata); + gtk_widget_show (entry); + return entry; +} + +GtkWidget * +gtkutil_clist_new (int columns, char *titles[], + GtkWidget * box, int policy, + void *select_callback, gpointer select_userdata, + void *unselect_callback, + gpointer unselect_userdata, int selection_mode) +{ + GtkWidget *clist, *win; + + win = gtk_scrolled_window_new (0, 0); + gtk_container_add (GTK_CONTAINER (box), win); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (win), + GTK_POLICY_AUTOMATIC, policy); + gtk_widget_show (win); + + if (titles) + clist = gtk_clist_new_with_titles (columns, titles); + else + clist = gtk_clist_new (columns); + + gtk_clist_set_selection_mode (GTK_CLIST (clist), selection_mode); + gtk_clist_column_titles_passive (GTK_CLIST (clist)); + gtk_container_add (GTK_CONTAINER (win), clist); + if (select_callback) + { + g_signal_connect (G_OBJECT (clist), "select_row", + G_CALLBACK (select_callback), select_userdata); + } + if (unselect_callback) + { + g_signal_connect (G_OBJECT (clist), "unselect_row", + G_CALLBACK (unselect_callback), unselect_userdata); + } + gtk_widget_show (clist); + + return clist; +} + +int +gtkutil_clist_selection (GtkWidget * clist) +{ + if (GTK_CLIST (clist)->selection) + return GPOINTER_TO_INT(GTK_CLIST (clist)->selection->data); + return -1; +} + +static int +int_compare (const int * elem1, const int * elem2) +{ + return (*elem1) - (*elem2); +} + +int +gtkutil_clist_multiple_selection (GtkWidget * clist, int ** rows, const int max_rows) +{ + int i = 0; + GList *tmp_clist; + *rows = malloc (sizeof (int) * max_rows ); + memset( *rows, -1, max_rows * sizeof(int) ); + + for( tmp_clist = GTK_CLIST(clist)->selection; + tmp_clist && i < max_rows; tmp_clist = tmp_clist->next, i++) + { + (*rows)[i] = GPOINTER_TO_INT( tmp_clist->data ); + } + qsort(*rows, i, sizeof(int), (void *)int_compare); + return i; + +} + +void +add_tip (GtkWidget * wid, char *text) +{ + static GtkTooltips *tip = NULL; + if (!tip) + tip = gtk_tooltips_new (); + gtk_tooltips_set_tip (tip, wid, text, 0); +} + +void +show_and_unfocus (GtkWidget * wid) +{ + GTK_WIDGET_UNSET_FLAGS (wid, GTK_CAN_FOCUS); + gtk_widget_show (wid); +} + +void +gtkutil_set_icon (GtkWidget *win) +{ + gtk_window_set_icon (GTK_WINDOW (win), pix_xchat); +} + +extern GtkWidget *parent_window; /* maingui.c */ + +GtkWidget * +gtkutil_window_new (char *title, char *role, int width, int height, int flags) +{ + GtkWidget *win; + + win = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtkutil_set_icon (win); +#ifdef WIN32 + gtk_window_set_wmclass (GTK_WINDOW (win), "XChat", "xchat"); +#endif + gtk_window_set_title (GTK_WINDOW (win), title); + gtk_window_set_default_size (GTK_WINDOW (win), width, height); + gtk_window_set_role (GTK_WINDOW (win), role); + if (flags & 1) + gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_MOUSE); + if ((flags & 2) && parent_window) + { + gtk_window_set_type_hint (GTK_WINDOW (win), GDK_WINDOW_TYPE_HINT_DIALOG); + gtk_window_set_transient_for (GTK_WINDOW (win), GTK_WINDOW (parent_window)); + } + + return win; +} + +/* pass NULL as selection to paste to both clipboard & X11 text */ +void +gtkutil_copy_to_clipboard (GtkWidget *widget, GdkAtom selection, + const gchar *str) +{ + GtkWidget *win; + GtkClipboard *clip, *clip2; + + win = gtk_widget_get_toplevel (GTK_WIDGET (widget)); + if (GTK_WIDGET_TOPLEVEL (win)) + { + int len = strlen (str); + + if (selection) + { + clip = gtk_widget_get_clipboard (win, selection); + gtk_clipboard_set_text (clip, str, len); + } else + { + /* copy to both primary X selection and clipboard */ + clip = gtk_widget_get_clipboard (win, GDK_SELECTION_PRIMARY); + clip2 = gtk_widget_get_clipboard (win, GDK_SELECTION_CLIPBOARD); + gtk_clipboard_set_text (clip, str, len); + gtk_clipboard_set_text (clip2, str, len); + } + } +} + +/* Treeview util functions */ + +GtkWidget * +gtkutil_treeview_new (GtkWidget *box, GtkTreeModel *model, + GtkTreeCellDataFunc mapper, ...) +{ + GtkWidget *win, *view; + GtkCellRenderer *renderer = NULL; + GtkTreeViewColumn *col; + va_list args; + int col_id = 0; + GType type; + char *title, *attr; + + win = gtk_scrolled_window_new (0, 0); + gtk_container_add (GTK_CONTAINER (box), win); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (win), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_widget_show (win); + + view = gtk_tree_view_new_with_model (model); + /* the view now has a ref on the model, we can unref it */ + g_object_unref (G_OBJECT (model)); + gtk_container_add (GTK_CONTAINER (win), view); + + va_start (args, mapper); + for (col_id = va_arg (args, int); col_id != -1; col_id = va_arg (args, int)) + { + type = gtk_tree_model_get_column_type (model, col_id); + switch (type) + { + case G_TYPE_BOOLEAN: + renderer = gtk_cell_renderer_toggle_new (); + attr = "active"; + break; + case G_TYPE_STRING: /* fall through */ + default: + renderer = gtk_cell_renderer_text_new (); + attr = "text"; + break; + } + + title = va_arg (args, char *); + if (mapper) /* user-specified function to set renderer attributes */ + { + col = gtk_tree_view_column_new_with_attributes (title, renderer, NULL); + gtk_tree_view_column_set_cell_data_func (col, renderer, mapper, + GINT_TO_POINTER (col_id), NULL); + } else + { + /* just set the typical attribute for this type of renderer */ + col = gtk_tree_view_column_new_with_attributes (title, renderer, + attr, col_id, NULL); + } + gtk_tree_view_append_column (GTK_TREE_VIEW (view), col); + } + + va_end (args); + + return view; +} + +gboolean +gtkutil_treemodel_string_to_iter (GtkTreeModel *model, gchar *pathstr, GtkTreeIter *iter_ret) +{ + GtkTreePath *path = gtk_tree_path_new_from_string (pathstr); + gboolean success; + + success = gtk_tree_model_get_iter (model, iter_ret, path); + gtk_tree_path_free (path); + return success; +} + +/*gboolean +gtkutil_treeview_get_selected_iter (GtkTreeView *view, GtkTreeIter *iter_ret) +{ + GtkTreeModel *store; + GtkTreeSelection *select; + + select = gtk_tree_view_get_selection (view); + return gtk_tree_selection_get_selected (select, &store, iter_ret); +}*/ + +gboolean +gtkutil_treeview_get_selected (GtkTreeView *view, GtkTreeIter *iter_ret, ...) +{ + GtkTreeModel *store; + GtkTreeSelection *select; + gboolean has_selected; + va_list args; + + select = gtk_tree_view_get_selection (view); + has_selected = gtk_tree_selection_get_selected (select, &store, iter_ret); + + if (has_selected) { + va_start (args, iter_ret); + gtk_tree_model_get_valist (store, iter_ret, args); + va_end (args); + } + + return has_selected; +} + diff --git a/src/fe-gtk/gtkutil.h b/src/fe-gtk/gtkutil.h new file mode 100644 index 00000000..9bf9e058 --- /dev/null +++ b/src/fe-gtk/gtkutil.h @@ -0,0 +1,39 @@ +#include <gtk/gtktreeview.h> +#include <gtk/gtktreemodel.h> + +typedef void (*filereqcallback) (void *, char *file); + +#define FRF_WRITE 1 +#define FRF_MULTIPLE 2 +#define FRF_ADDFOLDER 4 +#define FRF_CHOOSEFOLDER 8 +#define FRF_FILTERISINITIAL 16 +#define FRF_NOASKOVERWRITE 32 + +void gtkutil_file_req (const char *title, void *callback, void *userdata, char *filter, int flags); +void gtkutil_destroy (GtkWidget * igad, GtkWidget * dgad); +GtkWidget *gtkutil_button (GtkWidget *box, char *stock, char *tip, void *callback, + void *userdata, char *labeltext); +void gtkutil_label_new (char *text, GtkWidget * box); +GtkWidget *gtkutil_entry_new (int max, GtkWidget * box, void *callback, + gpointer userdata); +GtkWidget *gtkutil_clist_new (int columns, char *titles[], GtkWidget * box, + int policy, void *select_callback, + gpointer select_userdata, + void *unselect_callback, + gpointer unselect_userdata, int selection_mode); +int gtkutil_clist_selection (GtkWidget * clist); +int gtkutil_clist_multiple_selection (GtkWidget * clist, + int ** rows, const int max_rows); +void add_tip (GtkWidget * wid, char *text); +void show_and_unfocus (GtkWidget * wid); +void gtkutil_set_icon (GtkWidget *win); +GtkWidget *gtkutil_window_new (char *title, char *role, int width, int height, int flags); +void gtkutil_copy_to_clipboard (GtkWidget *widget, GdkAtom selection, + const gchar *str); +GtkWidget *gtkutil_treeview_new (GtkWidget *box, GtkTreeModel *model, + GtkTreeCellDataFunc mapper, ...); +gboolean gtkutil_treemodel_string_to_iter (GtkTreeModel *model, gchar *pathstr, GtkTreeIter *iter_ret); +gboolean gtkutil_treeview_get_selected_iter (GtkTreeView *view, GtkTreeIter *iter_ret); +gboolean gtkutil_treeview_get_selected (GtkTreeView *view, GtkTreeIter *iter_ret, ...); + diff --git a/src/fe-gtk/ignoregui.c b/src/fe-gtk/ignoregui.c new file mode 100644 index 00000000..dc5fce9c --- /dev/null +++ b/src/fe-gtk/ignoregui.c @@ -0,0 +1,449 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include "fe-gtk.h" + +#include <gtk/gtkcheckbutton.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkhbbox.h> +#include <gtk/gtkframe.h> +#include <gtk/gtkhseparator.h> +#include <gtk/gtkversion.h> + +#include <gtk/gtkliststore.h> +#include <gtk/gtktreeview.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtkcellrenderertext.h> +#include <gtk/gtkcellrenderertoggle.h> + +#include "../common/xchat.h" +#include "../common/ignore.h" +#include "../common/cfgfiles.h" +#include "../common/fe.h" +#include "gtkutil.h" +#include "maingui.h" + +enum +{ + MASK_COLUMN, + CHAN_COLUMN, + PRIV_COLUMN, + NOTICE_COLUMN, + CTCP_COLUMN, + DCC_COLUMN, + INVITE_COLUMN, + UNIGNORE_COLUMN, + N_COLUMNS +}; + +static GtkWidget *ignorewin = 0; + +static GtkWidget *num_ctcp; +static GtkWidget *num_priv; +static GtkWidget *num_chan; +static GtkWidget *num_noti; +static GtkWidget *num_invi; + +static GtkTreeModel * +get_store (void) +{ + return gtk_tree_view_get_model (g_object_get_data (G_OBJECT (ignorewin), "view")); +} + +static int +ignore_get_flags (GtkTreeModel *model, GtkTreeIter *iter) +{ + gboolean chan, priv, noti, ctcp, dcc, invi, unig; + int flags = 0; + + gtk_tree_model_get (model, iter, 1, &chan, 2, &priv, 3, ¬i, + 4, &ctcp, 5, &dcc, 6, &invi, 7, &unig, -1); + if (chan) + flags |= IG_CHAN; + if (priv) + flags |= IG_PRIV; + if (noti) + flags |= IG_NOTI; + if (ctcp) + flags |= IG_CTCP; + if (dcc) + flags |= IG_DCC; + if (invi) + flags |= IG_INVI; + if (unig) + flags |= IG_UNIG; + return flags; +} + +static void +mask_edited (GtkCellRendererText *render, gchar *path, gchar *new, gpointer dat) +{ + GtkListStore *store = GTK_LIST_STORE (get_store ()); + GtkTreeIter iter; + char *old; + int flags; + + gtkutil_treemodel_string_to_iter (GTK_TREE_MODEL (store), path, &iter); + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, 0, &old, -1); + + if (!strcmp (old, new)) /* no change */ + ; + else if (ignore_exists (new)) /* duplicate, ignore */ + fe_message (_("That mask already exists."), FE_MSG_ERROR); + else + { + /* delete old mask, and add new one with original flags */ + ignore_del (old, NULL); + flags = ignore_get_flags (GTK_TREE_MODEL (store), &iter); + ignore_add (new, flags); + + /* update tree */ + gtk_list_store_set (store, &iter, MASK_COLUMN, new, -1); + } + g_free (old); + +} + +static void +option_toggled (GtkCellRendererToggle *render, gchar *path, gpointer data) +{ + GtkListStore *store = GTK_LIST_STORE (get_store ()); + GtkTreeIter iter; + int col_id = GPOINTER_TO_INT (data); + gboolean active; + char *mask; + int flags; + + gtkutil_treemodel_string_to_iter (GTK_TREE_MODEL (store), path, &iter); + + /* update model */ + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, col_id, &active, -1); + gtk_list_store_set (store, &iter, col_id, !active, -1); + + /* update ignore list */ + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, 0, &mask, -1); + flags = ignore_get_flags (GTK_TREE_MODEL (store), &iter); + if (ignore_add (mask, flags) != 2) + g_warning ("ignore treeview is out of sync!\n"); + + g_free (mask); +} + +static GtkWidget * +ignore_treeview_new (GtkWidget *box) +{ + GtkListStore *store; + GtkWidget *view; + GtkTreeViewColumn *col; + GtkCellRenderer *render; + int col_id; + + store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, + G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN, G_TYPE_BOOLEAN); + g_return_val_if_fail (store != NULL, NULL); + + view = gtkutil_treeview_new (box, GTK_TREE_MODEL (store), + NULL, + MASK_COLUMN, _("Mask"), + CHAN_COLUMN, _("Channel"), + PRIV_COLUMN, _("Private"), + NOTICE_COLUMN, _("Notice"), + CTCP_COLUMN, _("CTCP"), + DCC_COLUMN, _("DCC"), + INVITE_COLUMN, _("Invite"), + UNIGNORE_COLUMN, _("Unignore"), + -1); + + gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE); + gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), 0), TRUE); + + /* attach to signals and customise columns */ + for (col_id=0; (col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), col_id)); + col_id++) + { + GList *list = gtk_tree_view_column_get_cell_renderers (col); + GList *tmp; + + for (tmp = list; tmp; tmp = tmp->next) + { + render = tmp->data; + if (col_id > 0) /* it's a toggle button column */ + { + g_signal_connect (render, "toggled", G_CALLBACK (option_toggled), + GINT_TO_POINTER (col_id)); + } else /* mask column */ + { + g_object_set (G_OBJECT (render), "editable", TRUE, NULL); + g_signal_connect (render, "edited", G_CALLBACK (mask_edited), NULL); + /* make this column sortable */ + gtk_tree_view_column_set_sort_column_id (col, col_id); + gtk_tree_view_column_set_min_width (col, 272); + } + /* centre titles */ + gtk_tree_view_column_set_alignment (col, 0.5); + } + + g_list_free (list); + } + + gtk_widget_show (view); + return view; +} + +static void +ignore_delete_entry_clicked (GtkWidget * wid, struct session *sess) +{ + GtkTreeView *view = g_object_get_data (G_OBJECT (ignorewin), "view"); + GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (view)); + GtkTreeIter iter; + GtkTreePath *path; + char *mask = NULL; + + if (gtkutil_treeview_get_selected (view, &iter, 0, &mask, -1)) + { + /* delete this row, select next one */ +#if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION == 0) + gtk_list_store_remove (store, &iter); +#else + if (gtk_list_store_remove (store, &iter)) + { + path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter); + gtk_tree_view_scroll_to_cell (view, path, NULL, TRUE, 1.0, 0.0); + gtk_tree_view_set_cursor (view, path, NULL, FALSE); + gtk_tree_path_free (path); + } +#endif + + ignore_del (mask, NULL); + g_free (mask); + } +} + +static void +ignore_store_new (int cancel, char *mask, gpointer data) +{ + GtkTreeView *view = g_object_get_data (G_OBJECT (ignorewin), "view"); + GtkListStore *store = GTK_LIST_STORE (get_store ()); + GtkTreeIter iter; + GtkTreePath *path; + int flags = IG_CHAN | IG_PRIV | IG_NOTI | IG_CTCP | IG_DCC | IG_INVI; + + if (cancel) + return; + /* check if it already exists */ + if (ignore_exists (mask)) + { + fe_message (_("That mask already exists."), FE_MSG_ERROR); + return; + } + + ignore_add (mask, flags); + + gtk_list_store_append (store, &iter); + /* ignore everything by default */ + gtk_list_store_set (store, &iter, 0, mask, 1, TRUE, 2, TRUE, 3, TRUE, + 4, TRUE, 5, TRUE, 6, TRUE, 7, FALSE, -1); + /* make sure the new row is visible and selected */ + path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter); + gtk_tree_view_scroll_to_cell (view, path, NULL, TRUE, 1.0, 0.0); + gtk_tree_view_set_cursor (view, path, NULL, FALSE); + gtk_tree_path_free (path); +} + +static void +ignore_clear_entry_clicked (GtkWidget * wid, gpointer unused) +{ + GtkListStore *store = GTK_LIST_STORE (get_store ()); + GtkTreeIter iter; + char *mask; + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) + { + /* remove from ignore_list */ + do + { + mask = NULL; + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, MASK_COLUMN, &mask, -1); + ignore_del (mask, NULL); + g_free (mask); + } + while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter)); + + /* remove from GUI */ + gtk_list_store_clear (store); + } +} + +static void +ignore_new_entry_clicked (GtkWidget * wid, struct session *sess) +{ + fe_get_str (_("Enter mask to ignore:"), "nick!userid@host.com", + ignore_store_new, NULL); + +} + +static void +close_ignore_gui_callback () +{ + ignore_save (); + ignorewin = 0; +} + +static GtkWidget * +ignore_stats_entry (GtkWidget * box, char *label, int value) +{ + GtkWidget *wid; + char buf[16]; + + sprintf (buf, "%d", value); + gtkutil_label_new (label, box); + wid = gtkutil_entry_new (16, box, 0, 0); + gtk_widget_set_size_request (wid, 30, -1); + gtk_editable_set_editable (GTK_EDITABLE (wid), FALSE); + gtk_widget_set_sensitive (GTK_WIDGET (wid), FALSE); + gtk_entry_set_text (GTK_ENTRY (wid), buf); + + return wid; +} + +void +ignore_gui_open () +{ + GtkWidget *vbox, *box, *stat_box, *frame; + GtkWidget *view; + GtkListStore *store; + GtkTreeIter iter; + GSList *temp = ignore_list; + char *mask; + gboolean private, chan, notice, ctcp, dcc, invite, unignore; + + if (ignorewin) + { + mg_bring_tofront (ignorewin); + return; + } + + ignorewin = + mg_create_generic_tab ("IgnoreList", _("XChat: Ignore list"), + FALSE, TRUE, close_ignore_gui_callback, + NULL, 600, 256, &vbox, 0); + + view = ignore_treeview_new (vbox); + g_object_set_data (G_OBJECT (ignorewin), "view", view); + + frame = gtk_frame_new (_("Ignore Stats:")); + gtk_widget_show (frame); + + stat_box = gtk_hbox_new (0, 2); + gtk_container_set_border_width (GTK_CONTAINER (stat_box), 6); + gtk_container_add (GTK_CONTAINER (frame), stat_box); + gtk_widget_show (stat_box); + + num_chan = ignore_stats_entry (stat_box, _("Channel:"), ignored_chan); + num_priv = ignore_stats_entry (stat_box, _("Private:"), ignored_priv); + num_noti = ignore_stats_entry (stat_box, _("Notice:"), ignored_noti); + num_ctcp = ignore_stats_entry (stat_box, _("CTCP:"), ignored_ctcp); + num_invi = ignore_stats_entry (stat_box, _("Invite:"), ignored_invi); + + gtk_box_pack_start (GTK_BOX (vbox), frame, 0, 0, 5); + + box = gtk_hbutton_box_new (); + gtk_button_box_set_layout (GTK_BUTTON_BOX (box), GTK_BUTTONBOX_SPREAD); + gtk_box_pack_start (GTK_BOX (vbox), box, FALSE, FALSE, 2); + gtk_container_set_border_width (GTK_CONTAINER (box), 5); + gtk_widget_show (box); + + gtkutil_button (box, GTK_STOCK_NEW, 0, ignore_new_entry_clicked, 0, + _("Add...")); + gtkutil_button (box, GTK_STOCK_DELETE, 0, ignore_delete_entry_clicked, + 0, _("Delete")); + gtkutil_button (box, GTK_STOCK_CLEAR, 0, ignore_clear_entry_clicked, + 0, _("Clear")); + + store = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (view))); + + while (temp) + { + struct ignore *ignore = temp->data; + + mask = ignore->mask; + chan = (ignore->type & IG_CHAN); + private = (ignore->type & IG_PRIV); + notice = (ignore->type & IG_NOTI); + ctcp = (ignore->type & IG_CTCP); + dcc = (ignore->type & IG_DCC); + invite = (ignore->type & IG_INVI); + unignore = (ignore->type & IG_UNIG); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + MASK_COLUMN, mask, + CHAN_COLUMN, chan, + PRIV_COLUMN, private, + NOTICE_COLUMN, notice, + CTCP_COLUMN, ctcp, + DCC_COLUMN, dcc, + INVITE_COLUMN, invite, + UNIGNORE_COLUMN, unignore, + -1); + + temp = temp->next; + } + gtk_widget_show (ignorewin); +} + +void +fe_ignore_update (int level) +{ + /* some ignores have changed via /ignore, we should update + the gui now */ + /* level 1 = the list only. */ + /* level 2 = the numbers only. */ + /* for now, ignore level 1, since the ignore GUI isn't realtime, + only saved when you click OK */ + char buf[16]; + + if (level == 2 && ignorewin) + { + sprintf (buf, "%d", ignored_ctcp); + gtk_entry_set_text (GTK_ENTRY (num_ctcp), buf); + + sprintf (buf, "%d", ignored_noti); + gtk_entry_set_text (GTK_ENTRY (num_noti), buf); + + sprintf (buf, "%d", ignored_chan); + gtk_entry_set_text (GTK_ENTRY (num_chan), buf); + + sprintf (buf, "%d", ignored_invi); + gtk_entry_set_text (GTK_ENTRY (num_invi), buf); + + sprintf (buf, "%d", ignored_priv); + gtk_entry_set_text (GTK_ENTRY (num_priv), buf); + } +} diff --git a/src/fe-gtk/joind.c b/src/fe-gtk/joind.c new file mode 100644 index 00000000..ee5c56d1 --- /dev/null +++ b/src/fe-gtk/joind.c @@ -0,0 +1,257 @@ +/* Copyright (c) 2005 Peter Zelezny + All Rights Reserved. + + joind.c - The Join Dialog. + + Popups up when you connect without any autojoin channels and helps you + to find or join a channel. +*/ + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> + +#include <gtk/gtkbbox.h> +#include <gtk/gtkbutton.h> +#include <gtk/gtkdialog.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkimage.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkradiobutton.h> +#include <gtk/gtkvbox.h> +#include <gtk/gtkwindow.h> + +#include "../common/xchat.h" +#include "../common/xchatc.h" +#include "../common/server.h" +#include "../common/fe.h" +#include "fe-gtk.h" +#include "chanlist.h" + + +static void +joind_radio2_cb (GtkWidget *radio, server *serv) +{ + if (GTK_TOGGLE_BUTTON (radio)->active) + { + gtk_widget_grab_focus (serv->gui->joind_entry); + gtk_editable_set_position (GTK_EDITABLE (serv->gui->joind_entry), 999); + } +} + +static void +joind_entryenter_cb (GtkWidget *entry, GtkWidget *ok) +{ + gtk_widget_grab_focus (ok); +} + +static void +joind_entryfocus_cb (GtkWidget *entry, GdkEventFocus *event, server *serv) +{ + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (serv->gui->joind_radio2), TRUE); +} + +static void +joind_destroy_cb (GtkWidget *win, server *serv) +{ + if (is_server (serv)) + serv->gui->joind_win = NULL; +} + +static void +joind_ok_cb (GtkWidget *ok, server *serv) +{ + if (!is_server (serv)) + { + gtk_widget_destroy (gtk_widget_get_toplevel (ok)); + return; + } + + /* do nothing */ + if (GTK_TOGGLE_BUTTON (serv->gui->joind_radio1)->active) + goto xit; + + /* join specific channel */ + if (GTK_TOGGLE_BUTTON (serv->gui->joind_radio2)->active) + { + char *text = GTK_ENTRY (serv->gui->joind_entry)->text; + if (strlen (text) < 2) + { + fe_message (_("Channel name too short, try again."), FE_MSG_ERROR); + return; + } + serv->p_join (serv, text, ""); + goto xit; + } + + /* channel list */ + chanlist_opengui (serv, TRUE); + +xit: + prefs.gui_join_dialog = 0; + if (GTK_TOGGLE_BUTTON (serv->gui->joind_check)->active) + prefs.gui_join_dialog = 1; + + gtk_widget_destroy (serv->gui->joind_win); + serv->gui->joind_win = NULL; +} + +static void +joind_show_dialog (server *serv) +{ + GtkWidget *dialog1; + GtkWidget *dialog_vbox1; + GtkWidget *vbox1; + GtkWidget *hbox1; + GtkWidget *image1; + GtkWidget *vbox2; + GtkWidget *label; + GtkWidget *radiobutton1; + GtkWidget *radiobutton2; + GtkWidget *radiobutton3; + GSList *radiobutton1_group; + GtkWidget *hbox2; + GtkWidget *entry1; + GtkWidget *checkbutton1; + GtkWidget *dialog_action_area1; + GtkWidget *okbutton1; + char buf[256]; + char buf2[256]; + + serv->gui->joind_win = dialog1 = gtk_dialog_new (); + gtk_window_set_title (GTK_WINDOW (dialog1), _("XChat: Connection Complete")); + gtk_window_set_type_hint (GTK_WINDOW (dialog1), GDK_WINDOW_TYPE_HINT_DIALOG); + gtk_window_set_position (GTK_WINDOW (dialog1), GTK_WIN_POS_MOUSE); + + dialog_vbox1 = GTK_DIALOG (dialog1)->vbox; + gtk_widget_show (dialog_vbox1); + + vbox1 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox1); + gtk_box_pack_start (GTK_BOX (dialog_vbox1), vbox1, TRUE, TRUE, 0); + + hbox1 = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox1), hbox1, TRUE, TRUE, 0); + + image1 = gtk_image_new_from_stock ("gtk-yes", GTK_ICON_SIZE_DIALOG); + gtk_widget_show (image1); + gtk_box_pack_start (GTK_BOX (hbox1), image1, FALSE, TRUE, 24); + gtk_misc_set_alignment (GTK_MISC (image1), 0.5, 0.06); + + vbox2 = gtk_vbox_new (FALSE, 10); + gtk_container_set_border_width (GTK_CONTAINER (vbox2), 6); + gtk_widget_show (vbox2); + gtk_box_pack_start (GTK_BOX (hbox1), vbox2, TRUE, TRUE, 0); + + snprintf (buf2, sizeof (buf2), _("Connection to %s complete."), + server_get_network (serv, TRUE)); + snprintf (buf, sizeof (buf), "\n<b>%s</b>", buf2); + label = gtk_label_new (buf); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, FALSE, 0); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + + label = gtk_label_new (_("In the Server-List window, no channel (chat room) has been entered to be automatically joined for this network.")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, FALSE, 0); + GTK_LABEL (label)->wrap = TRUE; + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + + label = gtk_label_new (_("What would you like to do next?")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, FALSE, 0); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + + serv->gui->joind_radio1 = radiobutton1 = gtk_radio_button_new_with_mnemonic (NULL, _("_Nothing, I'll join a channel later.")); + gtk_widget_show (radiobutton1); + gtk_box_pack_start (GTK_BOX (vbox2), radiobutton1, FALSE, FALSE, 0); + radiobutton1_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radiobutton1)); + + hbox2 = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox2); + gtk_box_pack_start (GTK_BOX (vbox2), hbox2, FALSE, FALSE, 0); + + serv->gui->joind_radio2 = radiobutton2 = gtk_radio_button_new_with_mnemonic (NULL, _("_Join this channel:")); + gtk_widget_show (radiobutton2); + gtk_box_pack_start (GTK_BOX (hbox2), radiobutton2, FALSE, FALSE, 0); + gtk_radio_button_set_group (GTK_RADIO_BUTTON (radiobutton2), radiobutton1_group); + radiobutton1_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radiobutton2)); + + serv->gui->joind_entry = entry1 = gtk_entry_new (); + gtk_entry_set_text (GTK_ENTRY (entry1), "#"); + gtk_widget_show (entry1); + gtk_box_pack_start (GTK_BOX (hbox2), entry1, TRUE, TRUE, 8); + + snprintf (buf, sizeof (buf), "<small> %s</small>", + _("If you know the name of the channel you want to join, enter it here.")); + label = gtk_label_new (buf); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, FALSE, 0); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + + radiobutton3 = gtk_radio_button_new_with_mnemonic (NULL, _("O_pen the Channel-List window.")); + gtk_widget_show (radiobutton3); + gtk_box_pack_start (GTK_BOX (vbox2), radiobutton3, FALSE, FALSE, 0); + gtk_radio_button_set_group (GTK_RADIO_BUTTON (radiobutton3), radiobutton1_group); + radiobutton1_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radiobutton3)); + + snprintf (buf, sizeof (buf), "<small> %s</small>", + _("Retrieving the Channel-List may take a minute or two.")); + label = gtk_label_new (buf); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, FALSE, 0); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + + serv->gui->joind_check = checkbutton1 = gtk_check_button_new_with_mnemonic (_("_Always show this dialog after connecting.")); + if (prefs.gui_join_dialog) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbutton1), TRUE); + gtk_widget_show (checkbutton1); + gtk_box_pack_start (GTK_BOX (vbox1), checkbutton1, FALSE, FALSE, 0); + + dialog_action_area1 = GTK_DIALOG (dialog1)->action_area; + gtk_widget_show (dialog_action_area1); + gtk_button_box_set_layout (GTK_BUTTON_BOX (dialog_action_area1), GTK_BUTTONBOX_END); + + okbutton1 = gtk_button_new_from_stock ("gtk-ok"); + gtk_widget_show (okbutton1); + gtk_box_pack_end (GTK_BOX (GTK_DIALOG (dialog1)->action_area), okbutton1, FALSE, TRUE, 0); + GTK_WIDGET_SET_FLAGS (okbutton1, GTK_CAN_DEFAULT); + + g_signal_connect (G_OBJECT (dialog1), "destroy", + G_CALLBACK (joind_destroy_cb), serv); + g_signal_connect (G_OBJECT (entry1), "focus_in_event", + G_CALLBACK (joind_entryfocus_cb), serv); + g_signal_connect (G_OBJECT (entry1), "activate", + G_CALLBACK (joind_entryenter_cb), okbutton1); + g_signal_connect (G_OBJECT (radiobutton2), "toggled", + G_CALLBACK (joind_radio2_cb), serv); + g_signal_connect (G_OBJECT (okbutton1), "clicked", + G_CALLBACK (joind_ok_cb), serv); + + gtk_widget_grab_focus (okbutton1); + gtk_widget_show_all (dialog1); +} + +void +joind_open (server *serv) +{ + if (prefs.gui_join_dialog) + joind_show_dialog (serv); +} + +void +joind_close (server *serv) +{ + if (serv->gui->joind_win) + { + gtk_widget_destroy (serv->gui->joind_win); + serv->gui->joind_win = NULL; + } +} diff --git a/src/fe-gtk/joind.h b/src/fe-gtk/joind.h new file mode 100644 index 00000000..aa0fd0ad --- /dev/null +++ b/src/fe-gtk/joind.h @@ -0,0 +1,2 @@ +void joind_open (server *serv); +void joind_close (server *serv); diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c new file mode 100644 index 00000000..ef269f95 --- /dev/null +++ b/src/fe-gtk/maingui.c @@ -0,0 +1,3811 @@ +/* X-Chat + * Copyright (C) 1998-2005 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> + +#include <gtk/gtkarrow.h> +#include <gtk/gtktogglebutton.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkvbox.h> +#include <gtk/gtkeventbox.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkhpaned.h> +#include <gtk/gtkvpaned.h> +#include <gtk/gtkframe.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkmenuitem.h> +#include <gtk/gtkprogressbar.h> +#include <gtk/gtkscrolledwindow.h> +#include <gtk/gtkstock.h> +#include <gtk/gtktable.h> +#include <gtk/gtknotebook.h> +#include <gtk/gtkimage.h> +#include <gtk/gtkmessagedialog.h> +#include <gtk/gtkcheckmenuitem.h> +#include <gtk/gtkcheckbutton.h> +#include <gtk/gtkbbox.h> +#include <gtk/gtkvscrollbar.h> + +#include "../common/xchat.h" +#include "../common/fe.h" +#include "../common/server.h" +#include "../common/xchatc.h" +#include "../common/outbound.h" +#include "../common/inbound.h" +#include "../common/plugin.h" +#include "../common/modes.h" +#include "../common/url.h" +#include "fe-gtk.h" +#include "banlist.h" +#include "gtkutil.h" +#include "joind.h" +#include "palette.h" +#include "maingui.h" +#include "menu.h" +#include "fkeys.h" +#include "userlistgui.h" +#include "chanview.h" +#include "pixmaps.h" +#include "plugin-tray.h" +#include "xtext.h" + +#ifdef USE_GTKSPELL +#include <gtk/gtktextview.h> +#include <gtkspell/gtkspell.h> +#endif + +#ifdef USE_LIBSEXY +#include "sexy-spell-entry.h" +#endif + +#define GUI_SPACING (3) +#define GUI_BORDER (0) +#define SCROLLBAR_SPACING (2) + +enum +{ + POS_INVALID = 0, + POS_TOPLEFT = 1, + POS_BOTTOMLEFT = 2, + POS_TOPRIGHT = 3, + POS_BOTTOMRIGHT = 4, + POS_TOP = 5, /* for tabs only */ + POS_BOTTOM = 6, + POS_HIDDEN = 7 +}; + +/* two different types of tabs */ +#define TAG_IRC 0 /* server, channel, dialog */ +#define TAG_UTIL 1 /* dcc, notify, chanlist */ + +static void mg_create_entry (session *sess, GtkWidget *box); +static void mg_link_irctab (session *sess, int focus); + +static session_gui static_mg_gui; +static session_gui *mg_gui = NULL; /* the shared irc tab */ +static int ignore_chanmode = FALSE; +static const char chan_flags[] = { 't', 'n', 's', 'i', 'p', 'm', 'l', 'k' }; + +static chan *active_tab = NULL; /* active tab */ +GtkWidget *parent_window = NULL; /* the master window */ + +GtkStyle *input_style; + +static PangoAttrList *away_list; +static PangoAttrList *newdata_list; +static PangoAttrList *nickseen_list; +static PangoAttrList *newmsg_list; +static PangoAttrList *plain_list = NULL; + + +#ifdef USE_GTKSPELL + +/* use these when it's a GtkTextView instead of GtkEntry */ + +char * +SPELL_ENTRY_GET_TEXT (GtkWidget *entry) +{ + static char *last = NULL; /* warning: don't overlap 2 GET_TEXT calls! */ + GtkTextBuffer *buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (entry)); + GtkTextIter start_iter, end_iter; + + gtk_text_buffer_get_iter_at_offset (buf, &start_iter, 0); + gtk_text_buffer_get_end_iter (buf, &end_iter); + g_free (last); + last = gtk_text_buffer_get_text (buf, &start_iter, &end_iter, FALSE); + return last; +} + +void +SPELL_ENTRY_SET_POS (GtkWidget *entry, int pos) +{ + GtkTextIter iter; + GtkTextBuffer *buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (entry)); + + gtk_text_buffer_get_iter_at_offset (buf, &iter, pos); + gtk_text_buffer_place_cursor (buf, &iter); +} + +int +SPELL_ENTRY_GET_POS (GtkWidget *entry) +{ + GtkTextIter cursor; + GtkTextBuffer *buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (entry)); + + gtk_text_buffer_get_iter_at_mark (buf, &cursor, gtk_text_buffer_get_insert (buf)); + return gtk_text_iter_get_offset (&cursor); +} + +void +SPELL_ENTRY_INSERT (GtkWidget *entry, const char *text, int len, int *pos) +{ + GtkTextIter iter; + GtkTextBuffer *buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (entry)); + + /* len is bytes. pos is chars. */ + gtk_text_buffer_get_iter_at_offset (buf, &iter, *pos); + gtk_text_buffer_insert (buf, &iter, text, len); + *pos += g_utf8_strlen (text, len); +} + +#endif + +static PangoAttrList * +mg_attr_list_create (GdkColor *col, int size) +{ + PangoAttribute *attr; + PangoAttrList *list; + + list = pango_attr_list_new (); + + if (col) + { + attr = pango_attr_foreground_new (col->red, col->green, col->blue); + attr->start_index = 0; + attr->end_index = 0xffff; + pango_attr_list_insert (list, attr); + } + + if (size > 0) + { + attr = pango_attr_scale_new (size == 1 ? PANGO_SCALE_SMALL : PANGO_SCALE_X_SMALL); + attr->start_index = 0; + attr->end_index = 0xffff; + pango_attr_list_insert (list, attr); + } + + return list; +} + +static void +mg_create_tab_colors (void) +{ + if (plain_list) + { + pango_attr_list_unref (plain_list); + pango_attr_list_unref (newmsg_list); + pango_attr_list_unref (newdata_list); + pango_attr_list_unref (nickseen_list); + pango_attr_list_unref (away_list); + } + + plain_list = mg_attr_list_create (NULL, prefs.tab_small); + newdata_list = mg_attr_list_create (&colors[COL_NEW_DATA], prefs.tab_small); + nickseen_list = mg_attr_list_create (&colors[COL_HILIGHT], prefs.tab_small); + newmsg_list = mg_attr_list_create (&colors[COL_NEW_MSG], prefs.tab_small); + away_list = mg_attr_list_create (&colors[COL_AWAY], FALSE); +} + +#ifdef WIN32 +#define WINVER 0x0501 /* needed for vc6? */ +#include <windows.h> +#include <gdk/gdkwin32.h> + +/* Flash the taskbar button on Windows when there's a highlight event. */ + +static void +flash_window (GtkWidget *win) +{ + FLASHWINFO fi; + static HMODULE user = NULL; + static BOOL (*flash) (PFLASHWINFO) = NULL; + + if (!user) + { + user = GetModuleHandleA ("USER32"); + if (!user) + return; /* this should never fail */ + } + + if (!flash) + { + flash = (void *)GetProcAddress (user, "FlashWindowEx"); + if (!flash) + return; /* this fails on NT4.0 and Win95 */ + } + + fi.cbSize = sizeof (fi); + fi.hwnd = GDK_WINDOW_HWND (win->window); + fi.dwFlags = FLASHW_ALL | FLASHW_TIMERNOFG; + fi.uCount = 0; + fi.dwTimeout = 500; + flash (&fi); + /*FlashWindowEx (&fi);*/ +} +#else + +#ifdef USE_XLIB +#include <gdk/gdkx.h> + +static void +set_window_urgency (GtkWidget *win, gboolean set) +{ + XWMHints *hints; + + hints = XGetWMHints(GDK_WINDOW_XDISPLAY(win->window), GDK_WINDOW_XWINDOW(win->window)); + if (set) + hints->flags |= XUrgencyHint; + else + hints->flags &= ~XUrgencyHint; + XSetWMHints(GDK_WINDOW_XDISPLAY(win->window), + GDK_WINDOW_XWINDOW(win->window), hints); + XFree(hints); +} + +static void +flash_window (GtkWidget *win) +{ + set_window_urgency (win, TRUE); +} + +static void +unflash_window (GtkWidget *win) +{ + set_window_urgency (win, FALSE); +} +#endif +#endif + +/* flash the taskbar button */ + +void +fe_flash_window (session *sess) +{ +#if defined(WIN32) || defined(USE_XLIB) + if (fe_gui_info (sess, 0) != 1) /* only do it if not focused */ + flash_window (sess->gui->window); +#endif +} + +/* set a tab plain, red, light-red, or blue */ + +void +fe_set_tab_color (struct session *sess, int col) +{ + struct session *server_sess = sess->server->server_session; + if (sess->gui->is_tab && (col == 0 || sess != current_tab)) + { + switch (col) + { + case 0: /* no particular color (theme default) */ + sess->new_data = FALSE; + sess->msg_said = FALSE; + sess->nick_said = FALSE; + chan_set_color (sess->res->tab, plain_list); + break; + case 1: /* new data has been displayed (dark red) */ + sess->new_data = TRUE; + sess->msg_said = FALSE; + sess->nick_said = FALSE; + chan_set_color (sess->res->tab, newdata_list); + + if (chan_is_collapsed (sess->res->tab)) + { + server_sess->new_data = TRUE; + server_sess->msg_said = FALSE; + server_sess->nick_said = FALSE; + chan_set_color (chan_get_parent (sess->res->tab), newdata_list); + } + + break; + case 2: /* new message arrived in channel (light red) */ + sess->new_data = FALSE; + sess->msg_said = TRUE; + sess->nick_said = FALSE; + chan_set_color (sess->res->tab, newmsg_list); + + if (chan_is_collapsed (sess->res->tab)) + { + server_sess->new_data = FALSE; + server_sess->msg_said = TRUE; + server_sess->nick_said = FALSE; + chan_set_color (chan_get_parent (sess->res->tab), newmsg_list); + } + + break; + case 3: /* your nick has been seen (blue) */ + sess->new_data = FALSE; + sess->msg_said = FALSE; + sess->nick_said = TRUE; + chan_set_color (sess->res->tab, nickseen_list); + + if (chan_is_collapsed (sess->res->tab)) + { + server_sess->new_data = FALSE; + server_sess->msg_said = FALSE; + server_sess->nick_said = TRUE; + chan_set_color (chan_get_parent (sess->res->tab), nickseen_list); + } + + break; + } + } +} + +static void +mg_set_myself_away (session_gui *gui, gboolean away) +{ + gtk_label_set_attributes (GTK_LABEL (GTK_BIN (gui->nick_label)->child), + away ? away_list : NULL); +} + +/* change the little icon to the left of your nickname */ + +void +mg_set_access_icon (session_gui *gui, GdkPixbuf *pix, gboolean away) +{ + if (gui->op_xpm) + { + if (pix == gtk_image_get_pixbuf (GTK_IMAGE (gui->op_xpm))) /* no change? */ + { + mg_set_myself_away (gui, away); + return; + } + + gtk_widget_destroy (gui->op_xpm); + gui->op_xpm = NULL; + } + + if (pix) + { + gui->op_xpm = gtk_image_new_from_pixbuf (pix); + gtk_box_pack_start (GTK_BOX (gui->nick_box), gui->op_xpm, 0, 0, 0); + gtk_widget_show (gui->op_xpm); + } + + mg_set_myself_away (gui, away); +} + +static gboolean +mg_inputbox_focus (GtkWidget *widget, GdkEventFocus *event, session_gui *gui) +{ + GSList *list; + session *sess; + + if (gui->is_tab) + return FALSE; + + list = sess_list; + while (list) + { + sess = list->data; + if (sess->gui == gui) + { + current_sess = sess; + if (!sess->server->server_session) + sess->server->server_session = sess; + break; + } + list = list->next; + } + + return FALSE; +} + +void +mg_inputbox_cb (GtkWidget *igad, session_gui *gui) +{ + char *cmd; + static int ignore = FALSE; + GSList *list; + session *sess = NULL; + + if (ignore) + return; + + cmd = SPELL_ENTRY_GET_TEXT (igad); + if (cmd[0] == 0) + return; + + cmd = strdup (cmd); + + /* avoid recursive loop */ + ignore = TRUE; + SPELL_ENTRY_SET_TEXT (igad, ""); + ignore = FALSE; + + /* where did this event come from? */ + if (gui->is_tab) + { + sess = current_tab; + } else + { + list = sess_list; + while (list) + { + sess = list->data; + if (sess->gui == gui) + break; + list = list->next; + } + if (!list) + sess = NULL; + } + + if (sess) + handle_multiline (sess, cmd, TRUE, FALSE); + + free (cmd); +} + +static gboolean +has_key (char *modes) +{ + if (!modes) + return FALSE; + /* this is a crude check, but "-k" can't exist, so it works. */ + while (*modes) + { + if (*modes == 'k') + return TRUE; + if (*modes == ' ') + return FALSE; + modes++; + } + return FALSE; +} + +void +fe_set_title (session *sess) +{ + char tbuf[512]; + int type; + + if (sess->gui->is_tab && sess != current_tab) + return; + + type = sess->type; + + if (sess->server->connected == FALSE && sess->type != SESS_DIALOG) + goto def; + + switch (type) + { + case SESS_DIALOG: + snprintf (tbuf, sizeof (tbuf), DISPLAY_NAME": %s %s @ %s", + _("Dialog with"), sess->channel, server_get_network (sess->server, TRUE)); + break; + case SESS_SERVER: + snprintf (tbuf, sizeof (tbuf), DISPLAY_NAME": %s @ %s", + sess->server->nick, server_get_network (sess->server, TRUE)); + break; + case SESS_CHANNEL: + /* don't display keys in the titlebar */ + if ((!(prefs.gui_tweaks & 16)) && has_key (sess->current_modes)) + snprintf (tbuf, sizeof (tbuf), + DISPLAY_NAME": %s @ %s / %s", + sess->server->nick, server_get_network (sess->server, TRUE), + sess->channel); + else + snprintf (tbuf, sizeof (tbuf), + DISPLAY_NAME": %s @ %s / %s (%s)", + sess->server->nick, server_get_network (sess->server, TRUE), + sess->channel, sess->current_modes ? sess->current_modes : ""); + if (prefs.gui_tweaks & 1) + snprintf (tbuf + strlen (tbuf), 9, " (%d)", sess->total); + break; + case SESS_NOTICES: + case SESS_SNOTICES: + snprintf (tbuf, sizeof (tbuf), DISPLAY_NAME": %s @ %s (notices)", + sess->server->nick, server_get_network (sess->server, TRUE)); + break; + default: + def: + gtk_window_set_title (GTK_WINDOW (sess->gui->window), DISPLAY_NAME); + return; + } + + gtk_window_set_title (GTK_WINDOW (sess->gui->window), tbuf); +} + +static gboolean +mg_windowstate_cb (GtkWindow *wid, GdkEventWindowState *event, gpointer userdata) +{ + prefs.gui_win_state = 0; + if (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) + prefs.gui_win_state = 1; + + if ((event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) && + (event->new_window_state & GDK_WINDOW_STATE_ICONIFIED) && + (prefs.gui_tray_flags & 4)) + { + tray_toggle_visibility (TRUE); + gtk_window_deiconify (wid); + } + + return FALSE; +} + +static gboolean +mg_configure_cb (GtkWidget *wid, GdkEventConfigure *event, session *sess) +{ + if (sess == NULL) /* for the main_window */ + { + if (mg_gui) + { + if (prefs.mainwindow_save) + { + sess = current_sess; + gtk_window_get_position (GTK_WINDOW (wid), &prefs.mainwindow_left, + &prefs.mainwindow_top); + gtk_window_get_size (GTK_WINDOW (wid), &prefs.mainwindow_width, + &prefs.mainwindow_height); + } + } + } + + if (sess) + { + if (sess->type == SESS_DIALOG && prefs.mainwindow_save) + { + gtk_window_get_position (GTK_WINDOW (wid), &prefs.dialog_left, + &prefs.dialog_top); + gtk_window_get_size (GTK_WINDOW (wid), &prefs.dialog_width, + &prefs.dialog_height); + } + + if (((GtkXText *) sess->gui->xtext)->transparent) + gtk_widget_queue_draw (sess->gui->xtext); + } + + return FALSE; +} + +/* move to a non-irc tab */ + +static void +mg_show_generic_tab (GtkWidget *box) +{ + int num; + GtkWidget *f = NULL; + +#if defined(GTK_WIDGET_HAS_FOCUS) + if (current_sess && GTK_WIDGET_HAS_FOCUS (current_sess->gui->input_box)) +#else + if (current_sess && gtk_widget_has_focus (current_sess->gui->input_box)) +#endif + f = current_sess->gui->input_box; + + num = gtk_notebook_page_num (GTK_NOTEBOOK (mg_gui->note_book), box); + gtk_notebook_set_current_page (GTK_NOTEBOOK (mg_gui->note_book), num); + gtk_tree_view_set_model (GTK_TREE_VIEW (mg_gui->user_tree), NULL); + gtk_window_set_title (GTK_WINDOW (mg_gui->window), + g_object_get_data (G_OBJECT (box), "title")); + gtk_widget_set_sensitive (mg_gui->menu, FALSE); + + if (f) + gtk_widget_grab_focus (f); +} + +/* a channel has been focused */ + +static void +mg_focus (session *sess) +{ + if (sess->gui->is_tab) + current_tab = sess; + current_sess = sess; + + /* dirty trick to avoid auto-selection */ + SPELL_ENTRY_SET_EDITABLE (sess->gui->input_box, FALSE); + gtk_widget_grab_focus (sess->gui->input_box); + SPELL_ENTRY_SET_EDITABLE (sess->gui->input_box, TRUE); + + sess->server->front_session = sess; + + if (sess->server->server_session != NULL) + { + if (sess->server->server_session->type != SESS_SERVER) + sess->server->server_session = sess; + } else + { + sess->server->server_session = sess; + } + + if (sess->new_data || sess->nick_said || sess->msg_said) + { + sess->nick_said = FALSE; + sess->msg_said = FALSE; + sess->new_data = FALSE; + /* when called via mg_changui_new, is_tab might be true, but + sess->res->tab is still NULL. */ + if (sess->res->tab) + fe_set_tab_color (sess, 0); + } +} + +static int +mg_progressbar_update (GtkWidget *bar) +{ + static int type = 0; + static float pos = 0; + + pos += 0.05; + if (pos >= 0.99) + { + if (type == 0) + { + type = 1; + gtk_progress_bar_set_orientation ((GtkProgressBar *) bar, + GTK_PROGRESS_RIGHT_TO_LEFT); + } else + { + type = 0; + gtk_progress_bar_set_orientation ((GtkProgressBar *) bar, + GTK_PROGRESS_LEFT_TO_RIGHT); + } + pos = 0.05; + } + gtk_progress_bar_set_fraction ((GtkProgressBar *) bar, pos); + return 1; +} + +void +mg_progressbar_create (session_gui *gui) +{ + gui->bar = gtk_progress_bar_new (); + gtk_box_pack_start (GTK_BOX (gui->nick_box), gui->bar, 0, 0, 0); + gtk_widget_show (gui->bar); + gui->bartag = fe_timeout_add (50, mg_progressbar_update, gui->bar); +} + +void +mg_progressbar_destroy (session_gui *gui) +{ + fe_timeout_remove (gui->bartag); + gtk_widget_destroy (gui->bar); + gui->bar = 0; + gui->bartag = 0; +} + +/* switching tabs away from this one, so remember some info about it! */ + +static void +mg_unpopulate (session *sess) +{ + restore_gui *res; + session_gui *gui; + int i; + + gui = sess->gui; + res = sess->res; + + res->input_text = strdup (SPELL_ENTRY_GET_TEXT (gui->input_box)); + res->topic_text = strdup (GTK_ENTRY (gui->topic_entry)->text); + res->limit_text = strdup (GTK_ENTRY (gui->limit_entry)->text); + res->key_text = strdup (GTK_ENTRY (gui->key_entry)->text); + if (gui->laginfo) + res->lag_text = strdup (gtk_label_get_text (GTK_LABEL (gui->laginfo))); + if (gui->throttleinfo) + res->queue_text = strdup (gtk_label_get_text (GTK_LABEL (gui->throttleinfo))); + + for (i = 0; i < NUM_FLAG_WIDS - 1; i++) + res->flag_wid_state[i] = GTK_TOGGLE_BUTTON (gui->flag_wid[i])->active; + + res->old_ul_value = userlist_get_value (gui->user_tree); + if (gui->lagometer) + res->lag_value = gtk_progress_bar_get_fraction ( + GTK_PROGRESS_BAR (gui->lagometer)); + if (gui->throttlemeter) + res->queue_value = gtk_progress_bar_get_fraction ( + GTK_PROGRESS_BAR (gui->throttlemeter)); + + if (gui->bar) + { + res->c_graph = TRUE; /* still have a graph, just not visible now */ + mg_progressbar_destroy (gui); + } +} + +static void +mg_restore_label (GtkWidget *label, char **text) +{ + if (!label) + return; + + if (*text) + { + gtk_label_set_text (GTK_LABEL (label), *text); + free (*text); + *text = NULL; + } else + { + gtk_label_set_text (GTK_LABEL (label), ""); + } +} + +static void +mg_restore_entry (GtkWidget *entry, char **text) +{ + if (*text) + { + gtk_entry_set_text (GTK_ENTRY (entry), *text); + free (*text); + *text = NULL; + } else + { + gtk_entry_set_text (GTK_ENTRY (entry), ""); + } + gtk_editable_set_position (GTK_EDITABLE (entry), -1); +} + +static void +mg_restore_speller (GtkWidget *entry, char **text) +{ + if (*text) + { + SPELL_ENTRY_SET_TEXT (entry, *text); + free (*text); + *text = NULL; + } else + { + SPELL_ENTRY_SET_TEXT (entry, ""); + } + SPELL_ENTRY_SET_POS (entry, -1); +} + +void +mg_set_topic_tip (session *sess) +{ + char *text; + + switch (sess->type) + { + case SESS_CHANNEL: + if (sess->topic) + { + text = g_strdup_printf (_("Topic for %s is: %s"), sess->channel, + sess->topic); + add_tip (sess->gui->topic_entry, text); + g_free (text); + } else + add_tip (sess->gui->topic_entry, _("No topic is set")); + break; + default: + if (GTK_ENTRY (sess->gui->topic_entry)->text && + GTK_ENTRY (sess->gui->topic_entry)->text[0]) + add_tip (sess->gui->topic_entry, GTK_ENTRY (sess->gui->topic_entry)->text); + else + add_tip (sess->gui->topic_entry, NULL); + } +} + +static void +mg_hide_empty_pane (GtkPaned *pane) +{ +#if defined(GTK_WIDGET_VISIBLE) + if ((pane->child1 == NULL || !GTK_WIDGET_VISIBLE (pane->child1)) && + (pane->child2 == NULL || !GTK_WIDGET_VISIBLE (pane->child2))) +#else + if ((pane->child1 == NULL || !gtk_widget_get_visible (pane->child1)) && + (pane->child2 == NULL || !gtk_widget_get_visible (pane->child2))) +#endif + { + gtk_widget_hide (GTK_WIDGET (pane)); + return; + } + + gtk_widget_show (GTK_WIDGET (pane)); +} + +static void +mg_hide_empty_boxes (session_gui *gui) +{ + /* hide empty vpanes - so the handle is not shown */ + mg_hide_empty_pane ((GtkPaned*)gui->vpane_right); + mg_hide_empty_pane ((GtkPaned*)gui->vpane_left); +} + +static void +mg_userlist_showhide (session *sess, int show) +{ + session_gui *gui = sess->gui; + int handle_size; + + if (show) + { + gtk_widget_show (gui->user_box); + gui->ul_hidden = 0; + + gtk_widget_style_get (GTK_WIDGET (gui->hpane_right), "handle-size", &handle_size, NULL); + gtk_paned_set_position (GTK_PANED (gui->hpane_right), GTK_WIDGET (gui->hpane_right)->allocation.width - (prefs.gui_pane_right_size + handle_size)); + } + else + { + gtk_widget_hide (gui->user_box); + gui->ul_hidden = 1; + } + + mg_hide_empty_boxes (gui); +} + +static gboolean +mg_is_userlist_and_tree_combined (void) +{ + if (prefs.tab_pos == POS_TOPLEFT && prefs.gui_ulist_pos == POS_BOTTOMLEFT) + return TRUE; + if (prefs.tab_pos == POS_BOTTOMLEFT && prefs.gui_ulist_pos == POS_TOPLEFT) + return TRUE; + + if (prefs.tab_pos == POS_TOPRIGHT && prefs.gui_ulist_pos == POS_BOTTOMRIGHT) + return TRUE; + if (prefs.tab_pos == POS_BOTTOMRIGHT && prefs.gui_ulist_pos == POS_TOPRIGHT) + return TRUE; + + return FALSE; +} + +/* decide if the userlist should be shown or hidden for this tab */ + +void +mg_decide_userlist (session *sess, gboolean switch_to_current) +{ + /* when called from menu.c we need this */ + if (sess->gui == mg_gui && switch_to_current) + sess = current_tab; + + if (prefs.hideuserlist) + { + mg_userlist_showhide (sess, FALSE); + return; + } + + switch (sess->type) + { + case SESS_SERVER: + case SESS_DIALOG: + case SESS_NOTICES: + case SESS_SNOTICES: + if (mg_is_userlist_and_tree_combined ()) + mg_userlist_showhide (sess, TRUE); /* show */ + else + mg_userlist_showhide (sess, FALSE); /* hide */ + break; + default: + mg_userlist_showhide (sess, TRUE); /* show */ + } +} + +static void +mg_userlist_toggle_cb (GtkWidget *button, gpointer userdata) +{ + prefs.hideuserlist = !prefs.hideuserlist; + mg_decide_userlist (current_sess, FALSE); + gtk_widget_grab_focus (current_sess->gui->input_box); +} + +static int ul_tag = 0; + +static gboolean +mg_populate_userlist (session *sess) +{ + session_gui *gui; + + if (!sess) + sess = current_tab; + + if (is_session (sess)) + { + gui = sess->gui; + if (sess->type == SESS_DIALOG) + mg_set_access_icon (sess->gui, NULL, sess->server->is_away); + else + mg_set_access_icon (sess->gui, get_user_icon (sess->server, sess->me), sess->server->is_away); + userlist_show (sess); + userlist_set_value (sess->gui->user_tree, sess->res->old_ul_value); + } + + ul_tag = 0; + return 0; +} + +/* fill the irc tab with a new channel */ + +static void +mg_populate (session *sess) +{ + session_gui *gui = sess->gui; + restore_gui *res = sess->res; + int i, render = TRUE; + guint16 vis = gui->ul_hidden; + + switch (sess->type) + { + case SESS_DIALOG: + /* show the dialog buttons */ + gtk_widget_show (gui->dialogbutton_box); + /* hide the chan-mode buttons */ + gtk_widget_hide (gui->topicbutton_box); + /* hide the userlist */ + mg_decide_userlist (sess, FALSE); + /* shouldn't edit the topic */ + gtk_editable_set_editable (GTK_EDITABLE (gui->topic_entry), FALSE); + break; + case SESS_SERVER: + if (prefs.chanmodebuttons) + gtk_widget_show (gui->topicbutton_box); + /* hide the dialog buttons */ + gtk_widget_hide (gui->dialogbutton_box); + /* hide the userlist */ + mg_decide_userlist (sess, FALSE); + /* shouldn't edit the topic */ + gtk_editable_set_editable (GTK_EDITABLE (gui->topic_entry), FALSE); + break; + default: + /* hide the dialog buttons */ + gtk_widget_hide (gui->dialogbutton_box); + if (prefs.chanmodebuttons) + gtk_widget_show (gui->topicbutton_box); + /* show the userlist */ + mg_decide_userlist (sess, FALSE); + /* let the topic be editted */ + gtk_editable_set_editable (GTK_EDITABLE (gui->topic_entry), TRUE); + } + + /* move to THE irc tab */ + if (gui->is_tab) + gtk_notebook_set_current_page (GTK_NOTEBOOK (gui->note_book), 0); + + /* xtext size change? Then don't render, wait for the expose caused + by showing/hidding the userlist */ + if (vis != gui->ul_hidden && gui->user_box->allocation.width > 1) + render = FALSE; + + gtk_xtext_buffer_show (GTK_XTEXT (gui->xtext), res->buffer, render); + + if (gui->is_tab) + gtk_widget_set_sensitive (gui->menu, TRUE); + + /* restore all the GtkEntry's */ + mg_restore_entry (gui->topic_entry, &res->topic_text); + mg_restore_speller (gui->input_box, &res->input_text); + mg_restore_entry (gui->key_entry, &res->key_text); + mg_restore_entry (gui->limit_entry, &res->limit_text); + mg_restore_label (gui->laginfo, &res->lag_text); + mg_restore_label (gui->throttleinfo, &res->queue_text); + + mg_focus (sess); + fe_set_title (sess); + + /* this one flickers, so only change if necessary */ + if (strcmp (sess->server->nick, gtk_button_get_label (GTK_BUTTON (gui->nick_label))) != 0) + gtk_button_set_label (GTK_BUTTON (gui->nick_label), sess->server->nick); + + /* this is slow, so make it a timeout event */ + if (!gui->is_tab) + { + mg_populate_userlist (sess); + } else + { + if (ul_tag == 0) + ul_tag = g_idle_add ((GSourceFunc)mg_populate_userlist, NULL); + } + + fe_userlist_numbers (sess); + + /* restore all the channel mode buttons */ + ignore_chanmode = TRUE; + for (i = 0; i < NUM_FLAG_WIDS - 1; i++) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gui->flag_wid[i]), + res->flag_wid_state[i]); + ignore_chanmode = FALSE; + + if (gui->lagometer) + { + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (gui->lagometer), + res->lag_value); + if (res->lag_tip) + add_tip (sess->gui->lagometer->parent, res->lag_tip); + } + if (gui->throttlemeter) + { + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (gui->throttlemeter), + res->queue_value); + if (res->queue_tip) + add_tip (sess->gui->throttlemeter->parent, res->queue_tip); + } + + /* did this tab have a connecting graph? restore it.. */ + if (res->c_graph) + { + res->c_graph = FALSE; + mg_progressbar_create (gui); + } + + /* menu items */ + GTK_CHECK_MENU_ITEM (gui->menu_item[MENU_ID_AWAY])->active = sess->server->is_away; + gtk_widget_set_sensitive (gui->menu_item[MENU_ID_AWAY], sess->server->connected); + gtk_widget_set_sensitive (gui->menu_item[MENU_ID_JOIN], sess->server->end_of_motd); + gtk_widget_set_sensitive (gui->menu_item[MENU_ID_DISCONNECT], + sess->server->connected || sess->server->recondelay_tag); + + mg_set_topic_tip (sess); + + plugin_emit_dummy_print (sess, "Focus Tab"); +} + +void +mg_bring_tofront_sess (session *sess) /* IRC tab or window */ +{ + if (sess->gui->is_tab) + chan_focus (sess->res->tab); + else + gtk_window_present (GTK_WINDOW (sess->gui->window)); +} + +void +mg_bring_tofront (GtkWidget *vbox) /* non-IRC tab or window */ +{ + chan *ch; + + ch = g_object_get_data (G_OBJECT (vbox), "ch"); + if (ch) + chan_focus (ch); + else + gtk_window_present (GTK_WINDOW (gtk_widget_get_toplevel (vbox))); +} + +void +mg_switch_page (int relative, int num) +{ + if (mg_gui) + chanview_move_focus (mg_gui->chanview, relative, num); +} + +/* a toplevel IRC window was destroyed */ + +static void +mg_topdestroy_cb (GtkWidget *win, session *sess) +{ +/* printf("enter mg_topdestroy. sess %p was destroyed\n", sess);*/ + + /* kill the text buffer */ + gtk_xtext_buffer_free (sess->res->buffer); + /* kill the user list */ + g_object_unref (G_OBJECT (sess->res->user_model)); + + session_free (sess); /* tell xchat.c about it */ +} + +/* cleanup an IRC tab */ + +static void +mg_ircdestroy (session *sess) +{ + GSList *list; + + /* kill the text buffer */ + gtk_xtext_buffer_free (sess->res->buffer); + /* kill the user list */ + g_object_unref (G_OBJECT (sess->res->user_model)); + + session_free (sess); /* tell xchat.c about it */ + + if (mg_gui == NULL) + { +/* puts("-> mg_gui is already NULL");*/ + return; + } + + list = sess_list; + while (list) + { + sess = list->data; + if (sess->gui->is_tab) + { +/* puts("-> some tabs still remain");*/ + return; + } + list = list->next; + } + +/* puts("-> no tabs left, killing main tabwindow");*/ + gtk_widget_destroy (mg_gui->window); + active_tab = NULL; + mg_gui = NULL; + parent_window = NULL; +} + +static void +mg_tab_close_cb (GtkWidget *dialog, gint arg1, session *sess) +{ + GSList *list, *next; + + gtk_widget_destroy (dialog); + if (arg1 == GTK_RESPONSE_OK && is_session (sess)) + { + /* force it NOT to send individual PARTs */ + sess->server->sent_quit = TRUE; + + for (list = sess_list; list;) + { + next = list->next; + if (((session *)list->data)->server == sess->server && + ((session *)list->data) != sess) + fe_close_window ((session *)list->data); + list = next; + } + + /* just send one QUIT - better for BNCs */ + sess->server->sent_quit = FALSE; + fe_close_window (sess); + } +} + +void +mg_tab_close (session *sess) +{ + GtkWidget *dialog; + GSList *list; + int i; + + if (chan_remove (sess->res->tab, FALSE)) + mg_ircdestroy (sess); + else + { + for (i = 0, list = sess_list; list; list = list->next) + if (((session *)list->data)->server == sess->server) + i++; + dialog = gtk_message_dialog_new (GTK_WINDOW (parent_window), 0, + GTK_MESSAGE_WARNING, GTK_BUTTONS_OK_CANCEL, + _("This server still has %d channels or dialogs associated with it. " + "Close them all?"), i); + g_signal_connect (G_OBJECT (dialog), "response", + G_CALLBACK (mg_tab_close_cb), sess); + if (prefs.tab_layout) + { + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + } + else + { + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER_ON_PARENT); + } + gtk_widget_show (dialog); + } +} + +static void +mg_menu_destroy (GtkWidget *menu, gpointer userdata) +{ + gtk_widget_destroy (menu); + g_object_unref (menu); +} + +void +mg_create_icon_item (char *label, char *stock, GtkWidget *menu, + void *callback, void *userdata) +{ + GtkWidget *item; + + item = create_icon_menu (label, stock, TRUE); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (callback), + userdata); + gtk_widget_show (item); +} + +static int +mg_count_networks (void) +{ + int cons = 0; + GSList *list; + + for (list = serv_list; list; list = list->next) + { + if (((server *)list->data)->connected) + cons++; + } + return cons; +} + +static int +mg_count_dccs (void) +{ + GSList *list; + struct DCC *dcc; + int dccs = 0; + + list = dcc_list; + while (list) + { + dcc = list->data; + if ((dcc->type == TYPE_SEND || dcc->type == TYPE_RECV) && + dcc->dccstat == STAT_ACTIVE) + dccs++; + list = list->next; + } + + return dccs; +} + +void +mg_open_quit_dialog (gboolean minimize_button) +{ + static GtkWidget *dialog = NULL; + GtkWidget *dialog_vbox1; + GtkWidget *table1; + GtkWidget *image; + GtkWidget *checkbutton1; + GtkWidget *label; + GtkWidget *dialog_action_area1; + GtkWidget *button; + char *text, *connecttext; + int cons; + int dccs; + + if (dialog) + { + gtk_window_present (GTK_WINDOW (dialog)); + return; + } + + dccs = mg_count_dccs (); + cons = mg_count_networks (); + if (dccs + cons == 0 || !prefs.gui_quit_dialog) + { + xchat_exit (); + return; + } + + dialog = gtk_dialog_new (); + gtk_container_set_border_width (GTK_CONTAINER (dialog), 6); + gtk_window_set_title (GTK_WINDOW (dialog), _("Quit XChat?")); + gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window)); + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE); + + dialog_vbox1 = GTK_DIALOG (dialog)->vbox; + gtk_widget_show (dialog_vbox1); + + table1 = gtk_table_new (2, 2, FALSE); + gtk_widget_show (table1); + gtk_box_pack_start (GTK_BOX (dialog_vbox1), table1, TRUE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (table1), 6); + gtk_table_set_row_spacings (GTK_TABLE (table1), 12); + gtk_table_set_col_spacings (GTK_TABLE (table1), 12); + + image = gtk_image_new_from_stock ("gtk-dialog-warning", GTK_ICON_SIZE_DIALOG); + gtk_widget_show (image); + gtk_table_attach (GTK_TABLE (table1), image, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + + checkbutton1 = gtk_check_button_new_with_mnemonic (_("Don't ask next time.")); + gtk_widget_show (checkbutton1); + gtk_table_attach (GTK_TABLE (table1), checkbutton1, 0, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 4); + + connecttext = g_strdup_printf (_("You are connected to %i IRC networks."), cons); + text = g_strdup_printf ("<span weight=\"bold\" size=\"larger\">%s</span>\n\n%s\n%s", + _("Are you sure you want to quit?"), + cons ? connecttext : "", + dccs ? _("Some file transfers are still active.") : ""); + g_free (connecttext); + label = gtk_label_new (text); + g_free (text); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table1), label, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_EXPAND | GTK_SHRINK | GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND | GTK_SHRINK), 0, 0); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + + dialog_action_area1 = GTK_DIALOG (dialog)->action_area; + gtk_widget_show (dialog_action_area1); + gtk_button_box_set_layout (GTK_BUTTON_BOX (dialog_action_area1), + GTK_BUTTONBOX_END); + + if (minimize_button) + { + button = gtk_button_new_with_mnemonic (_("_Minimize to Tray")); + gtk_widget_show (button); + gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, 1); + } + + button = gtk_button_new_from_stock ("gtk-cancel"); + gtk_widget_show (button); + gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, + GTK_RESPONSE_CANCEL); + gtk_widget_grab_focus (button); + + button = gtk_button_new_from_stock ("gtk-quit"); + gtk_widget_show (button); + gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, 0); + + gtk_widget_show (dialog); + + switch (gtk_dialog_run (GTK_DIALOG (dialog))) + { + case 0: + if (GTK_TOGGLE_BUTTON (checkbutton1)->active) + prefs.gui_quit_dialog = 0; + xchat_exit (); + break; + case 1: /* minimize to tray */ + if (GTK_TOGGLE_BUTTON (checkbutton1)->active) + { + prefs.gui_tray_flags |= 1; + /*prefs.gui_quit_dialog = 0;*/ + } + /* force tray icon ON, if not already */ + if (!prefs.gui_tray) + { + prefs.gui_tray = 1; + tray_apply_setup (); + } + tray_toggle_visibility (TRUE); + break; + } + + gtk_widget_destroy (dialog); + dialog = NULL; +} + +void +mg_close_sess (session *sess) +{ + if (sess_list->next == NULL) + { + mg_open_quit_dialog (FALSE); + return; + } + + fe_close_window (sess); +} + +static int +mg_chan_remove (chan *ch) +{ + /* remove the tab from chanview */ + chan_remove (ch, TRUE); + /* any tabs left? */ + if (chanview_get_size (mg_gui->chanview) < 1) + { + /* if not, destroy the main tab window */ + gtk_widget_destroy (mg_gui->window); + current_tab = NULL; + active_tab = NULL; + mg_gui = NULL; + parent_window = NULL; + return TRUE; + } + return FALSE; +} + +/* destroy non-irc tab/window */ + +static void +mg_close_gen (chan *ch, GtkWidget *box) +{ + char *title = g_object_get_data (G_OBJECT (box), "title"); + + if (title) + free (title); + if (!ch) + ch = g_object_get_data (G_OBJECT (box), "ch"); + if (ch) + { + /* remove from notebook */ + gtk_widget_destroy (box); + /* remove the tab from chanview */ + mg_chan_remove (ch); + } else + { + gtk_widget_destroy (gtk_widget_get_toplevel (box)); + } +} + +/* the "X" close button has been pressed (tab-view) */ + +static void +mg_xbutton_cb (chanview *cv, chan *ch, int tag, gpointer userdata) +{ + if (tag == TAG_IRC) /* irc tab */ + mg_close_sess (userdata); + else /* non-irc utility tab */ + mg_close_gen (ch, userdata); +} + +static void +mg_link_gentab (chan *ch, GtkWidget *box) +{ + int num; + GtkWidget *win; + + g_object_ref (box); + + num = gtk_notebook_page_num (GTK_NOTEBOOK (mg_gui->note_book), box); + gtk_notebook_remove_page (GTK_NOTEBOOK (mg_gui->note_book), num); + mg_chan_remove (ch); + + win = gtkutil_window_new (g_object_get_data (G_OBJECT (box), "title"), "", + GPOINTER_TO_INT (g_object_get_data (G_OBJECT (box), "w")), + GPOINTER_TO_INT (g_object_get_data (G_OBJECT (box), "h")), + 3); + /* so it doesn't try to chan_remove (there's no tab anymore) */ + g_object_steal_data (G_OBJECT (box), "ch"); + gtk_container_set_border_width (GTK_CONTAINER (box), 0); + gtk_container_add (GTK_CONTAINER (win), box); + gtk_widget_show (win); + + g_object_unref (box); +} + +static void +mg_detach_tab_cb (GtkWidget *item, chan *ch) +{ + if (chan_get_tag (ch) == TAG_IRC) /* IRC tab */ + { + /* userdata is session * */ + mg_link_irctab (chan_get_userdata (ch), 1); + return; + } + + /* userdata is GtkWidget * */ + mg_link_gentab (ch, chan_get_userdata (ch)); /* non-IRC tab */ +} + +static void +mg_destroy_tab_cb (GtkWidget *item, chan *ch) +{ + /* treat it just like the X button press */ + mg_xbutton_cb (mg_gui->chanview, ch, chan_get_tag (ch), chan_get_userdata (ch)); +} + +static void +mg_color_insert (GtkWidget *item, gpointer userdata) +{ + char buf[32]; + char *text; + int num = GPOINTER_TO_INT (userdata); + + if (num > 99) + { + switch (num) + { + case 100: + text = "\002"; break; + case 101: + text = "\037"; break; + case 102: + text = "\035"; break; + default: + text = "\017"; break; + } + key_action_insert (current_sess->gui->input_box, 0, text, 0, 0); + } else + { + sprintf (buf, "\003%02d", num); + key_action_insert (current_sess->gui->input_box, 0, buf, 0, 0); + } +} + +static void +mg_markup_item (GtkWidget *menu, char *text, int arg) +{ + GtkWidget *item; + + item = gtk_menu_item_new_with_label (""); + gtk_label_set_markup (GTK_LABEL (GTK_BIN (item)->child), text); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (mg_color_insert), GINT_TO_POINTER (arg)); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + gtk_widget_show (item); +} + +GtkWidget * +mg_submenu (GtkWidget *menu, char *text) +{ + GtkWidget *submenu, *item; + + item = gtk_menu_item_new_with_mnemonic (text); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + gtk_widget_show (item); + + submenu = gtk_menu_new (); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu); + gtk_widget_show (submenu); + + return submenu; +} + +static void +mg_create_color_menu (GtkWidget *menu, session *sess) +{ + GtkWidget *submenu; + GtkWidget *subsubmenu; + char buf[256]; + int i; + + submenu = mg_submenu (menu, _("Insert Attribute or Color Code")); + + mg_markup_item (submenu, _("<b>Bold</b>"), 100); + mg_markup_item (submenu, _("<u>Underline</u>"), 101); + /*mg_markup_item (submenu, _("<i>Italic</i>"), 102);*/ + mg_markup_item (submenu, _("Normal"), 103); + + subsubmenu = mg_submenu (submenu, _("Colors 0-7")); + + for (i = 0; i < 8; i++) + { + sprintf (buf, "<tt><sup>%02d</sup> <span background=\"#%02x%02x%02x\">" + " </span></tt>", + i, colors[i].red >> 8, colors[i].green >> 8, colors[i].blue >> 8); + mg_markup_item (subsubmenu, buf, i); + } + + subsubmenu = mg_submenu (submenu, _("Colors 8-15")); + + for (i = 8; i < 16; i++) + { + sprintf (buf, "<tt><sup>%02d</sup> <span background=\"#%02x%02x%02x\">" + " </span></tt>", + i, colors[i].red >> 8, colors[i].green >> 8, colors[i].blue >> 8); + mg_markup_item (subsubmenu, buf, i); + } +} + +static void +mg_set_guint8 (GtkCheckMenuItem *item, guint8 *setting) +{ + session *sess = current_sess; + guint8 logging = sess->text_logging; + + *setting = SET_OFF; + if (item->active) + *setting = SET_ON; + + /* has the logging setting changed? */ + if (logging != sess->text_logging) + log_open_or_close (sess); +} + +static void +mg_perchan_menu_item (char *label, GtkWidget *menu, guint8 *setting, guint global) +{ + guint8 initial_value = *setting; + + /* if it's using global value, use that as initial state */ + if (initial_value == SET_DEFAULT) + initial_value = global; + + menu_toggle_item (label, menu, mg_set_guint8, setting, initial_value); +} + +static void +mg_create_perchannelmenu (session *sess, GtkWidget *menu) +{ + GtkWidget *submenu; + + submenu = menu_quick_sub (_("_Settings"), menu, NULL, XCMENU_MNEMONIC, -1); + + mg_perchan_menu_item (_("_Log to Disk"), submenu, &sess->text_logging, prefs.logging); + mg_perchan_menu_item (_("_Reload Scrollback"), submenu, &sess->text_scrollback, prefs.text_replay); + if (sess->type == SESS_CHANNEL) + mg_perchan_menu_item (_("_Hide Join/Part Messages"), submenu, &sess->text_hidejoinpart, prefs.confmode); +} + +static void +mg_create_alertmenu (session *sess, GtkWidget *menu) +{ + GtkWidget *submenu; + + submenu = menu_quick_sub (_("_Extra Alerts"), menu, NULL, XCMENU_MNEMONIC, -1); + + mg_perchan_menu_item (_("Beep on _Message"), submenu, &sess->alert_beep, prefs.input_beep_chans); + mg_perchan_menu_item (_("Blink Tray _Icon"), submenu, &sess->alert_tray, prefs.input_tray_chans); + mg_perchan_menu_item (_("Blink Task _Bar"), submenu, &sess->alert_taskbar, prefs.input_flash_chans); +} + +static void +mg_create_tabmenu (session *sess, GdkEventButton *event, chan *ch) +{ + GtkWidget *menu, *item; + char buf[256]; + + menu = gtk_menu_new (); + + if (sess) + { + char *name = g_markup_escape_text (sess->channel[0] ? sess->channel : _("<none>"), -1); + snprintf (buf, sizeof (buf), "<span foreground=\"#3344cc\"><b>%s</b></span>", name); + g_free (name); + + item = gtk_menu_item_new_with_label (""); + gtk_label_set_markup (GTK_LABEL (GTK_BIN (item)->child), buf); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + gtk_widget_show (item); + + /* separator */ + menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0); + + /* per-channel alerts */ + mg_create_alertmenu (sess, menu); + + /* per-channel settings */ + mg_create_perchannelmenu (sess, menu); + + /* separator */ + menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0); + + if (sess->type == SESS_CHANNEL) + menu_addfavoritemenu (sess->server, menu, sess->channel); + } + + mg_create_icon_item (_("_Detach"), GTK_STOCK_REDO, menu, + mg_detach_tab_cb, ch); + mg_create_icon_item (_("_Close"), GTK_STOCK_CLOSE, menu, + mg_destroy_tab_cb, ch); + if (sess && tabmenu_list) + menu_create (menu, tabmenu_list, sess->channel, FALSE); + menu_add_plugin_items (menu, "\x4$TAB", sess->channel); + + if (event->window) + gtk_menu_set_screen (GTK_MENU (menu), gdk_drawable_get_screen (event->window)); + g_object_ref (menu); + g_object_ref_sink (menu); + g_object_unref (menu); + g_signal_connect (G_OBJECT (menu), "selection-done", + G_CALLBACK (mg_menu_destroy), NULL); + gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 0, event->time); +} + +static gboolean +mg_tab_contextmenu_cb (chanview *cv, chan *ch, int tag, gpointer ud, GdkEventButton *event) +{ + /* shift-click to close a tab */ + if ((event->state & GDK_SHIFT_MASK) && event->type == GDK_BUTTON_PRESS) + { + mg_xbutton_cb (cv, ch, tag, ud); + return FALSE; + } + + if (event->button != 3) + return FALSE; + + if (tag == TAG_IRC) + mg_create_tabmenu (ud, event, ch); + else + mg_create_tabmenu (NULL, event, ch); + + return TRUE; +} + +void +mg_dnd_drop_file (session *sess, char *target, char *uri) +{ + char *p, *data, *next, *fname; + + p = data = strdup (uri); + while (*p) + { + next = strchr (p, '\r'); + if (strncasecmp ("file:", p, 5) == 0) + { + if (next) + *next = 0; + fname = g_filename_from_uri (p, NULL, NULL); + if (fname) + { + /* dcc_send() expects utf-8 */ + p = xchat_filename_to_utf8 (fname, -1, 0, 0, 0); + if (p) + { + dcc_send (sess, target, p, prefs.dcc_max_send_cps, 0); + g_free (p); + } + g_free (fname); + } + } + if (!next) + break; + p = next + 1; + if (*p == '\n') + p++; + } + free (data); + +} + +static void +mg_dialog_dnd_drop (GtkWidget * widget, GdkDragContext * context, gint x, + gint y, GtkSelectionData * selection_data, guint info, + guint32 time, gpointer ud) +{ + if (current_sess->type == SESS_DIALOG) + /* sess->channel is really the nickname of dialogs */ + mg_dnd_drop_file (current_sess, current_sess->channel, selection_data->data); +} + +/* add a tabbed channel */ + +static void +mg_add_chan (session *sess) +{ + GdkPixbuf *icon; + char *name = _("<none>"); + + if (sess->channel[0]) + name = sess->channel; + + switch (sess->type) + { + case SESS_CHANNEL: + icon = pix_channel; + break; + case SESS_SERVER: + icon = pix_server; + break; + default: + icon = pix_dialog; + } + + sess->res->tab = chanview_add (sess->gui->chanview, name, sess->server, sess, + sess->type == SESS_SERVER ? FALSE : TRUE, + TAG_IRC, icon); + if (plain_list == NULL) + mg_create_tab_colors (); + + chan_set_color (sess->res->tab, plain_list); + + if (sess->res->buffer == NULL) + { + sess->res->buffer = gtk_xtext_buffer_new (GTK_XTEXT (sess->gui->xtext)); + gtk_xtext_set_time_stamp (sess->res->buffer, prefs.timestamp); + sess->res->user_model = userlist_create_model (); + } +} + +static void +mg_userlist_button (GtkWidget * box, char *label, char *cmd, + int a, int b, int c, int d) +{ + GtkWidget *wid = gtk_button_new_with_label (label); + g_signal_connect (G_OBJECT (wid), "clicked", + G_CALLBACK (userlist_button_cb), cmd); + gtk_table_attach_defaults (GTK_TABLE (box), wid, a, b, c, d); + show_and_unfocus (wid); +} + +static GtkWidget * +mg_create_userlistbuttons (GtkWidget *box) +{ + struct popup *pop; + GSList *list = button_list; + int a = 0, b = 0; + GtkWidget *tab; + + tab = gtk_table_new (5, 2, FALSE); + gtk_box_pack_end (GTK_BOX (box), tab, FALSE, FALSE, 0); + + while (list) + { + pop = list->data; + if (pop->cmd[0]) + { + mg_userlist_button (tab, pop->name, pop->cmd, a, a + 1, b, b + 1); + a++; + if (a == 2) + { + a = 0; + b++; + } + } + list = list->next; + } + + return tab; +} + +static void +mg_topic_cb (GtkWidget *entry, gpointer userdata) +{ + session *sess = current_sess; + char *text; + + if (sess->channel[0] && sess->server->connected && sess->type == SESS_CHANNEL) + { + text = GTK_ENTRY (entry)->text; + if (text[0] == 0) + text = NULL; + sess->server->p_topic (sess->server, sess->channel, text); + } else + gtk_entry_set_text (GTK_ENTRY (entry), ""); + /* restore focus to the input widget, where the next input will most +likely be */ + gtk_widget_grab_focus (sess->gui->input_box); +} + +static void +mg_tabwindow_kill_cb (GtkWidget *win, gpointer userdata) +{ + GSList *list, *next; + session *sess; + +/* puts("enter mg_tabwindow_kill_cb");*/ + xchat_is_quitting = TRUE; + + /* see if there's any non-tab windows left */ + list = sess_list; + while (list) + { + sess = list->data; + next = list->next; + if (!sess->gui->is_tab) + { + xchat_is_quitting = FALSE; +/* puts("-> will not exit, some toplevel windows left");*/ + } else + { + mg_ircdestroy (sess); + } + list = next; + } + + current_tab = NULL; + active_tab = NULL; + mg_gui = NULL; + parent_window = NULL; +} + +static GtkWidget * +mg_changui_destroy (session *sess) +{ + GtkWidget *ret = NULL; + + if (sess->gui->is_tab) + { + /* avoid calling the "destroy" callback */ + g_signal_handlers_disconnect_by_func (G_OBJECT (sess->gui->window), + mg_tabwindow_kill_cb, 0); + /* remove the tab from the chanview */ + if (!mg_chan_remove (sess->res->tab)) + /* if the window still exists, restore the signal handler */ + g_signal_connect (G_OBJECT (sess->gui->window), "destroy", + G_CALLBACK (mg_tabwindow_kill_cb), 0); + } else + { + /* avoid calling the "destroy" callback */ + g_signal_handlers_disconnect_by_func (G_OBJECT (sess->gui->window), + mg_topdestroy_cb, sess); + /*gtk_widget_destroy (sess->gui->window);*/ + /* don't destroy until the new one is created. Not sure why, but */ + /* it fixes: Gdk-CRITICAL **: gdk_colormap_get_screen: */ + /* assertion `GDK_IS_COLORMAP (cmap)' failed */ + ret = sess->gui->window; + free (sess->gui); + sess->gui = NULL; + } + return ret; +} + +static void +mg_link_irctab (session *sess, int focus) +{ + GtkWidget *win; + + if (sess->gui->is_tab) + { + win = mg_changui_destroy (sess); + mg_changui_new (sess, sess->res, 0, focus); + mg_populate (sess); + xchat_is_quitting = FALSE; + if (win) + gtk_widget_destroy (win); + return; + } + + mg_unpopulate (sess); + win = mg_changui_destroy (sess); + mg_changui_new (sess, sess->res, 1, focus); + /* the buffer is now attached to a different widget */ + ((xtext_buffer *)sess->res->buffer)->xtext = (GtkXText *)sess->gui->xtext; + if (win) + gtk_widget_destroy (win); +} + +void +mg_detach (session *sess, int mode) +{ + switch (mode) + { + /* detach only */ + case 1: + if (sess->gui->is_tab) + mg_link_irctab (sess, 1); + break; + /* attach only */ + case 2: + if (!sess->gui->is_tab) + mg_link_irctab (sess, 1); + break; + /* toggle */ + default: + mg_link_irctab (sess, 1); + } +} + +static int +check_is_number (char *t) +{ + while (*t) + { + if (*t < '0' || *t > '9') + return FALSE; + t++; + } + return TRUE; +} + +static void +mg_change_flag (GtkWidget * wid, session *sess, char flag) +{ + server *serv = sess->server; + char mode[3]; + + mode[1] = flag; + mode[2] = '\0'; + if (serv->connected && sess->channel[0]) + { + if (GTK_TOGGLE_BUTTON (wid)->active) + mode[0] = '+'; + else + mode[0] = '-'; + serv->p_mode (serv, sess->channel, mode); + serv->p_join_info (serv, sess->channel); + sess->ignore_mode = TRUE; + sess->ignore_date = TRUE; + } +} + +static void +flagl_hit (GtkWidget * wid, struct session *sess) +{ + char modes[512]; + const char *limit_str; + server *serv = sess->server; + + if (GTK_TOGGLE_BUTTON (wid)->active) + { + if (serv->connected && sess->channel[0]) + { + limit_str = gtk_entry_get_text (GTK_ENTRY (sess->gui->limit_entry)); + if (check_is_number ((char *)limit_str) == FALSE) + { + fe_message (_("User limit must be a number!\n"), FE_MSG_ERROR); + gtk_entry_set_text (GTK_ENTRY (sess->gui->limit_entry), ""); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), FALSE); + return; + } + snprintf (modes, sizeof (modes), "+l %d", atoi (limit_str)); + serv->p_mode (serv, sess->channel, modes); + serv->p_join_info (serv, sess->channel); + } + } else + mg_change_flag (wid, sess, 'l'); +} + +static void +flagk_hit (GtkWidget * wid, struct session *sess) +{ + char modes[512]; + server *serv = sess->server; + + if (serv->connected && sess->channel[0]) + { + snprintf (modes, sizeof (modes), "-k %s", + gtk_entry_get_text (GTK_ENTRY (sess->gui->key_entry))); + + if (GTK_TOGGLE_BUTTON (wid)->active) + modes[0] = '+'; + + serv->p_mode (serv, sess->channel, modes); + } +} + +static void +mg_flagbutton_cb (GtkWidget *but, char *flag) +{ + session *sess; + char mode; + + if (ignore_chanmode) + return; + + sess = current_sess; + mode = tolower ((unsigned char) flag[0]); + + switch (mode) + { + case 'l': + flagl_hit (but, sess); + break; + case 'k': + flagk_hit (but, sess); + break; + case 'b': + ignore_chanmode = TRUE; + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sess->gui->flag_b), FALSE); + ignore_chanmode = FALSE; + banlist_opengui (sess); + break; + default: + mg_change_flag (but, sess, mode); + } +} + +static GtkWidget * +mg_create_flagbutton (char *tip, GtkWidget *box, char *face) +{ + GtkWidget *wid; + + wid = gtk_toggle_button_new_with_label (face); + gtk_widget_set_size_request (wid, 18, 0); + add_tip (wid, tip); + gtk_box_pack_start (GTK_BOX (box), wid, 0, 0, 0); + g_signal_connect (G_OBJECT (wid), "toggled", + G_CALLBACK (mg_flagbutton_cb), face); + show_and_unfocus (wid); + + return wid; +} + +static void +mg_key_entry_cb (GtkWidget * igad, gpointer userdata) +{ + char modes[512]; + session *sess = current_sess; + server *serv = sess->server; + + if (serv->connected && sess->channel[0]) + { + snprintf (modes, sizeof (modes), "+k %s", + gtk_entry_get_text (GTK_ENTRY (igad))); + serv->p_mode (serv, sess->channel, modes); + serv->p_join_info (serv, sess->channel); + } +} + +static void +mg_limit_entry_cb (GtkWidget * igad, gpointer userdata) +{ + char modes[512]; + session *sess = current_sess; + server *serv = sess->server; + + if (serv->connected && sess->channel[0]) + { + if (check_is_number ((char *)gtk_entry_get_text (GTK_ENTRY (igad))) == FALSE) + { + gtk_entry_set_text (GTK_ENTRY (igad), ""); + fe_message (_("User limit must be a number!\n"), FE_MSG_ERROR); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sess->gui->flag_l), FALSE); + return; + } + snprintf (modes, sizeof(modes), "+l %d", + atoi (gtk_entry_get_text (GTK_ENTRY (igad)))); + serv->p_mode (serv, sess->channel, modes); + serv->p_join_info (serv, sess->channel); + } +} + +static void +mg_apply_entry_style (GtkWidget *entry) +{ + gtk_widget_modify_base (entry, GTK_STATE_NORMAL, &colors[COL_BG]); + gtk_widget_modify_text (entry, GTK_STATE_NORMAL, &colors[COL_FG]); + gtk_widget_modify_font (entry, input_style->font_desc); +} + +static void +mg_create_chanmodebuttons (session_gui *gui, GtkWidget *box) +{ + gui->flag_t = mg_create_flagbutton (_("Topic Protection"), box, "T"); + gui->flag_n = mg_create_flagbutton (_("No outside messages"), box, "N"); + gui->flag_s = mg_create_flagbutton (_("Secret"), box, "S"); + gui->flag_i = mg_create_flagbutton (_("Invite Only"), box, "I"); + gui->flag_p = mg_create_flagbutton (_("Private"), box, "P"); + gui->flag_m = mg_create_flagbutton (_("Moderated"), box, "M"); + gui->flag_b = mg_create_flagbutton (_("Ban List"), box, "B"); + + gui->flag_k = mg_create_flagbutton (_("Keyword"), box, "K"); + gui->key_entry = gtk_entry_new (); + gtk_widget_set_name (gui->key_entry, "xchat-inputbox"); + gtk_entry_set_max_length (GTK_ENTRY (gui->key_entry), 16); + gtk_widget_set_size_request (gui->key_entry, 30, -1); + gtk_box_pack_start (GTK_BOX (box), gui->key_entry, 0, 0, 0); + g_signal_connect (G_OBJECT (gui->key_entry), "activate", + G_CALLBACK (mg_key_entry_cb), NULL); + + if (prefs.style_inputbox) + mg_apply_entry_style (gui->key_entry); + + gui->flag_l = mg_create_flagbutton (_("User Limit"), box, "L"); + gui->limit_entry = gtk_entry_new (); + gtk_widget_set_name (gui->limit_entry, "xchat-inputbox"); + gtk_entry_set_max_length (GTK_ENTRY (gui->limit_entry), 10); + gtk_widget_set_size_request (gui->limit_entry, 30, -1); + gtk_box_pack_start (GTK_BOX (box), gui->limit_entry, 0, 0, 0); + g_signal_connect (G_OBJECT (gui->limit_entry), "activate", + G_CALLBACK (mg_limit_entry_cb), NULL); + + if (prefs.style_inputbox) + mg_apply_entry_style (gui->limit_entry); +} + +/*static void +mg_create_link_buttons (GtkWidget *box, gpointer userdata) +{ + gtkutil_button (box, GTK_STOCK_CLOSE, _("Close this tab/window"), + mg_x_click_cb, userdata, 0); + + if (!userdata) + gtkutil_button (box, GTK_STOCK_REDO, _("Attach/Detach this tab"), + mg_link_cb, userdata, 0); +}*/ + +static void +mg_dialog_button_cb (GtkWidget *wid, char *cmd) +{ + /* the longest cmd is 12, and the longest nickname is 64 */ + char buf[128]; + char *host = ""; + char *topic; + + if (!current_sess) + return; + + topic = (char *)(GTK_ENTRY (current_sess->gui->topic_entry)->text); + topic = strrchr (topic, '@'); + if (topic) + host = topic + 1; + + auto_insert (buf, sizeof (buf), cmd, 0, 0, "", "", "", + server_get_network (current_sess->server, TRUE), host, "", + current_sess->channel); + + handle_command (current_sess, buf, TRUE); + + /* dirty trick to avoid auto-selection */ + SPELL_ENTRY_SET_EDITABLE (current_sess->gui->input_box, FALSE); + gtk_widget_grab_focus (current_sess->gui->input_box); + SPELL_ENTRY_SET_EDITABLE (current_sess->gui->input_box, TRUE); +} + +static void +mg_dialog_button (GtkWidget *box, char *name, char *cmd) +{ + GtkWidget *wid; + + wid = gtk_button_new_with_label (name); + gtk_box_pack_start (GTK_BOX (box), wid, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (wid), "clicked", + G_CALLBACK (mg_dialog_button_cb), cmd); + gtk_widget_set_size_request (wid, -1, 0); +} + +static void +mg_create_dialogbuttons (GtkWidget *box) +{ + struct popup *pop; + GSList *list = dlgbutton_list; + + while (list) + { + pop = list->data; + if (pop->cmd[0]) + mg_dialog_button (box, pop->name, pop->cmd); + list = list->next; + } +} + +static void +mg_create_topicbar (session *sess, GtkWidget *box) +{ + GtkWidget *hbox, *topic, *bbox; + session_gui *gui = sess->gui; + + gui->topic_bar = hbox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (box), hbox, 0, 0, 0); + + if (!gui->is_tab) + sess->res->tab = NULL; + + gui->topic_entry = topic = gtk_entry_new (); + gtk_widget_set_name (topic, "xchat-inputbox"); + gtk_container_add (GTK_CONTAINER (hbox), topic); + g_signal_connect (G_OBJECT (topic), "activate", + G_CALLBACK (mg_topic_cb), 0); + + if (prefs.style_inputbox) + mg_apply_entry_style (topic); + + gui->topicbutton_box = bbox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (hbox), bbox, 0, 0, 0); + mg_create_chanmodebuttons (gui, bbox); + + gui->dialogbutton_box = bbox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (hbox), bbox, 0, 0, 0); + mg_create_dialogbuttons (bbox); + + if (!prefs.paned_userlist) + gtkutil_button (hbox, GTK_STOCK_GOTO_LAST, _("Show/Hide userlist"), + mg_userlist_toggle_cb, 0, 0); +} + +/* check if a word is clickable */ + +static int +mg_word_check (GtkWidget * xtext, char *word, int len) +{ + session *sess = current_sess; + int ret; + + ret = url_check_word (word, len); /* common/url.c */ + if (ret == 0) + { + if (( (word[0]=='@' || word[0]=='+' || word[0]=='%') && userlist_find (sess, word+1)) || userlist_find (sess, word)) + return WORD_NICK; + + if (sess->type == SESS_DIALOG) + return WORD_DIALOG; + } + + return ret; +} + +/* mouse click inside text area */ + +static void +mg_word_clicked (GtkWidget *xtext, char *word, GdkEventButton *even) +{ + session *sess = current_sess; + + if (even->button == 1) /* left button */ + { + if (word == NULL) + { + mg_focus (sess); + return; + } + + if ((even->state & 13) == prefs.gui_url_mod) + { + switch (mg_word_check (xtext, word, strlen (word))) + { + case WORD_URL: + case WORD_HOST: + fe_open_url (word); + } + } + return; + } + + if (even->button == 2) + { + if (sess->type == SESS_DIALOG) + menu_middlemenu (sess, even); + else if (even->type == GDK_2BUTTON_PRESS) + userlist_select (sess, word); + return; + } + + switch (mg_word_check (xtext, word, strlen (word))) + { + case 0: + menu_middlemenu (sess, even); + break; + case WORD_URL: + case WORD_HOST: + menu_urlmenu (even, word); + break; + case WORD_NICK: + menu_nickmenu (sess, even, (word[0]=='@' || word[0]=='+' || word[0]=='%') ? + word+1 : word, FALSE); + break; + case WORD_CHANNEL: + if (*word == '@' || *word == '+' || *word=='^' || *word=='%' || *word=='*') + word++; + menu_chanmenu (sess, even, word); + break; + case WORD_EMAIL: + { + char *newword = malloc (strlen (word) + 10); + if (*word == '~') + word++; + sprintf (newword, "mailto:%s", word); + menu_urlmenu (even, newword); + free (newword); + } + break; + case WORD_DIALOG: + menu_nickmenu (sess, even, sess->channel, FALSE); + break; + } +} + +void +mg_update_xtext (GtkWidget *wid) +{ + GtkXText *xtext = GTK_XTEXT (wid); + + gtk_xtext_set_palette (xtext, colors); + gtk_xtext_set_max_lines (xtext, prefs.max_lines); + gtk_xtext_set_tint (xtext, prefs.tint_red, prefs.tint_green, prefs.tint_blue); + gtk_xtext_set_background (xtext, channelwin_pix, prefs.transparent); + gtk_xtext_set_wordwrap (xtext, prefs.wordwrap); + gtk_xtext_set_show_marker (xtext, prefs.show_marker); + gtk_xtext_set_show_separator (xtext, prefs.indent_nicks ? prefs.show_separator : 0); + gtk_xtext_set_indent (xtext, prefs.indent_nicks); + if (!gtk_xtext_set_font (xtext, prefs.font_normal)) + { + fe_message ("Failed to open any font. I'm out of here!", FE_MSG_WAIT | FE_MSG_ERROR); + exit (1); + } + + gtk_xtext_refresh (xtext, FALSE); +} + +/* handle errors reported by xtext */ + +static void +mg_xtext_error (int type) +{ + switch (type) + { + case 0: + fe_message (_("Unable to set transparent background!\n\n" + "You may be using a non-compliant window\n" + "manager that is not currently supported.\n"), FE_MSG_WARN); + prefs.transparent = 0; + /* no others exist yet */ + } +} + +static void +mg_create_textarea (session *sess, GtkWidget *box) +{ + GtkWidget *inbox, *vbox, *frame; + GtkXText *xtext; + session_gui *gui = sess->gui; + static const GtkTargetEntry dnd_targets[] = + { + {"text/uri-list", 0, 1} + }; + static const GtkTargetEntry dnd_dest_targets[] = + { + {"XCHAT_CHANVIEW", GTK_TARGET_SAME_APP, 75 }, + {"XCHAT_USERLIST", GTK_TARGET_SAME_APP, 75 } + }; + + vbox = gtk_vbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (box), vbox); + + inbox = gtk_hbox_new (FALSE, SCROLLBAR_SPACING); + gtk_container_add (GTK_CONTAINER (vbox), inbox); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (inbox), frame); + + gui->xtext = gtk_xtext_new (colors, TRUE); + xtext = GTK_XTEXT (gui->xtext); + gtk_xtext_set_max_indent (xtext, prefs.max_auto_indent); + gtk_xtext_set_thin_separator (xtext, prefs.thin_separator); + gtk_xtext_set_error_function (xtext, mg_xtext_error); + gtk_xtext_set_urlcheck_function (xtext, mg_word_check); + gtk_xtext_set_max_lines (xtext, prefs.max_lines); + gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (xtext)); + mg_update_xtext (GTK_WIDGET (xtext)); + + g_signal_connect (G_OBJECT (xtext), "word_click", + G_CALLBACK (mg_word_clicked), NULL); + + gui->vscrollbar = gtk_vscrollbar_new (GTK_XTEXT (xtext)->adj); + gtk_box_pack_start (GTK_BOX (inbox), gui->vscrollbar, FALSE, TRUE, 0); +#ifndef WIN32 /* needs more work */ + gtk_drag_dest_set (gui->vscrollbar, 5, dnd_dest_targets, 2, + GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK); + g_signal_connect (G_OBJECT (gui->vscrollbar), "drag_begin", + G_CALLBACK (mg_drag_begin_cb), NULL); + g_signal_connect (G_OBJECT (gui->vscrollbar), "drag_drop", + G_CALLBACK (mg_drag_drop_cb), NULL); + g_signal_connect (G_OBJECT (gui->vscrollbar), "drag_motion", + G_CALLBACK (mg_drag_motion_cb), gui->vscrollbar); + g_signal_connect (G_OBJECT (gui->vscrollbar), "drag_end", + G_CALLBACK (mg_drag_end_cb), NULL); + + gtk_drag_dest_set (gui->xtext, GTK_DEST_DEFAULT_ALL, dnd_targets, 1, + GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK); + g_signal_connect (G_OBJECT (gui->xtext), "drag_data_received", + G_CALLBACK (mg_dialog_dnd_drop), NULL); +#endif +} + +static GtkWidget * +mg_create_infoframe (GtkWidget *box) +{ + GtkWidget *frame, *label, *hbox; + + frame = gtk_frame_new (0); + gtk_frame_set_shadow_type ((GtkFrame*)frame, GTK_SHADOW_OUT); + gtk_container_add (GTK_CONTAINER (box), frame); + + hbox = gtk_hbox_new (0, 0); + gtk_container_add (GTK_CONTAINER (frame), hbox); + + label = gtk_label_new (NULL); + gtk_container_add (GTK_CONTAINER (hbox), label); + + return label; +} + +static void +mg_create_meters (session_gui *gui, GtkWidget *parent_box) +{ + GtkWidget *infbox, *wid, *box; + + gui->meter_box = infbox = box = gtk_vbox_new (0, 1); + gtk_box_pack_start (GTK_BOX (parent_box), box, 0, 0, 0); + + if ((prefs.lagometer & 2) || (prefs.throttlemeter & 2)) + { + infbox = gtk_hbox_new (0, 0); + gtk_box_pack_start (GTK_BOX (box), infbox, 0, 0, 0); + } + + if (prefs.lagometer & 1) + { + gui->lagometer = wid = gtk_progress_bar_new (); +#ifdef WIN32 + gtk_widget_set_size_request (wid, 1, 10); +#else + gtk_widget_set_size_request (wid, 1, 8); +#endif + + wid = gtk_event_box_new (); + gtk_container_add (GTK_CONTAINER (wid), gui->lagometer); + gtk_box_pack_start (GTK_BOX (box), wid, 0, 0, 0); + } + if (prefs.lagometer & 2) + { + gui->laginfo = wid = mg_create_infoframe (infbox); + gtk_label_set_text ((GtkLabel *) wid, "Lag"); + } + + if (prefs.throttlemeter & 1) + { + gui->throttlemeter = wid = gtk_progress_bar_new (); +#ifdef WIN32 + gtk_widget_set_size_request (wid, 1, 10); +#else + gtk_widget_set_size_request (wid, 1, 8); +#endif + + wid = gtk_event_box_new (); + gtk_container_add (GTK_CONTAINER (wid), gui->throttlemeter); + gtk_box_pack_start (GTK_BOX (box), wid, 0, 0, 0); + } + if (prefs.throttlemeter & 2) + { + gui->throttleinfo = wid = mg_create_infoframe (infbox); + gtk_label_set_text ((GtkLabel *) wid, "Throttle"); + } +} + +void +mg_update_meters (session_gui *gui) +{ + gtk_widget_destroy (gui->meter_box); + gui->lagometer = NULL; + gui->laginfo = NULL; + gui->throttlemeter = NULL; + gui->throttleinfo = NULL; + + mg_create_meters (gui, gui->button_box_parent); + gtk_widget_show_all (gui->meter_box); +} + +static void +mg_create_userlist (session_gui *gui, GtkWidget *box) +{ + GtkWidget *frame, *ulist, *vbox; + + vbox = gtk_vbox_new (0, 1); + gtk_container_add (GTK_CONTAINER (box), vbox); + + frame = gtk_frame_new (NULL); + if (!(prefs.gui_tweaks & 1)) + gtk_box_pack_start (GTK_BOX (vbox), frame, 0, 0, GUI_SPACING); + + gui->namelistinfo = gtk_label_new (NULL); + gtk_container_add (GTK_CONTAINER (frame), gui->namelistinfo); + + gui->user_tree = ulist = userlist_create (vbox); + + if (prefs.style_namelistgad) + { + gtk_widget_set_style (ulist, input_style); + gtk_widget_modify_base (ulist, GTK_STATE_NORMAL, &colors[COL_BG]); + } + + mg_create_meters (gui, vbox); + + gui->button_box_parent = vbox; + gui->button_box = mg_create_userlistbuttons (vbox); +} + +static void +mg_leftpane_cb (GtkPaned *pane, GParamSpec *param, session_gui *gui) +{ + prefs.gui_pane_left_size = gtk_paned_get_position (pane); +} + +static void +mg_rightpane_cb (GtkPaned *pane, GParamSpec *param, session_gui *gui) +{ + int handle_size; + +/* if (pane->child1 == NULL || (!GTK_WIDGET_VISIBLE (pane->child1))) + return; + if (pane->child2 == NULL || (!GTK_WIDGET_VISIBLE (pane->child2))) + return;*/ + + gtk_widget_style_get (GTK_WIDGET (pane), "handle-size", &handle_size, NULL); + /* record the position from the RIGHT side */ + prefs.gui_pane_right_size = GTK_WIDGET (pane)->allocation.width - gtk_paned_get_position (pane) - handle_size; +} + +static gboolean +mg_add_pane_signals (session_gui *gui) +{ + g_signal_connect (G_OBJECT (gui->hpane_right), "notify::position", + G_CALLBACK (mg_rightpane_cb), gui); + g_signal_connect (G_OBJECT (gui->hpane_left), "notify::position", + G_CALLBACK (mg_leftpane_cb), gui); + return FALSE; +} + +static void +mg_create_center (session *sess, session_gui *gui, GtkWidget *box) +{ + GtkWidget *vbox, *hbox, *book; + + /* sep between top and bottom of left side */ + gui->vpane_left = gtk_vpaned_new (); + + /* sep between top and bottom of right side */ + gui->vpane_right = gtk_vpaned_new (); + + /* sep between left and xtext */ + gui->hpane_left = gtk_hpaned_new (); + gtk_paned_set_position (GTK_PANED (gui->hpane_left), prefs.gui_pane_left_size); + + /* sep between xtext and right side */ + gui->hpane_right = gtk_hpaned_new (); + + if (prefs.gui_tweaks & 4) + { + gtk_paned_pack2 (GTK_PANED (gui->hpane_left), gui->vpane_left, FALSE, TRUE); + gtk_paned_pack1 (GTK_PANED (gui->hpane_left), gui->hpane_right, TRUE, TRUE); + } + else + { + gtk_paned_pack1 (GTK_PANED (gui->hpane_left), gui->vpane_left, FALSE, TRUE); + gtk_paned_pack2 (GTK_PANED (gui->hpane_left), gui->hpane_right, TRUE, TRUE); + } + gtk_paned_pack2 (GTK_PANED (gui->hpane_right), gui->vpane_right, FALSE, TRUE); + + gtk_container_add (GTK_CONTAINER (box), gui->hpane_left); + + gui->note_book = book = gtk_notebook_new (); + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (book), FALSE); + gtk_notebook_set_show_border (GTK_NOTEBOOK (book), FALSE); + gtk_paned_pack1 (GTK_PANED (gui->hpane_right), book, TRUE, TRUE); + + hbox = gtk_hbox_new (FALSE, 0); + gtk_paned_pack1 (GTK_PANED (gui->vpane_right), hbox, FALSE, TRUE); + mg_create_userlist (gui, hbox); + + gui->user_box = hbox; + + vbox = gtk_vbox_new (FALSE, 3); + gtk_notebook_append_page (GTK_NOTEBOOK (book), vbox, NULL); + mg_create_topicbar (sess, vbox); + mg_create_textarea (sess, vbox); + mg_create_entry (sess, vbox); + + g_idle_add ((GSourceFunc)mg_add_pane_signals, gui); +} + +static void +mg_change_nick (int cancel, char *text, gpointer userdata) +{ + char buf[256]; + + if (!cancel) + { + snprintf (buf, sizeof (buf), "nick %s", text); + handle_command (current_sess, buf, FALSE); + } +} + +static void +mg_nickclick_cb (GtkWidget *button, gpointer userdata) +{ + fe_get_str (_("Enter new nickname:"), current_sess->server->nick, + mg_change_nick, NULL); +} + +/* make sure chanview and userlist positions are sane */ + +static void +mg_sanitize_positions (int *cv, int *ul) +{ + if (prefs.tab_layout == 2) + { + /* treeview can't be on TOP or BOTTOM */ + if (*cv == POS_TOP || *cv == POS_BOTTOM) + *cv = POS_TOPLEFT; + } + + /* userlist can't be on TOP or BOTTOM */ + if (*ul == POS_TOP || *ul == POS_BOTTOM) + *ul = POS_TOPRIGHT; + + /* can't have both in the same place */ + if (*cv == *ul) + { + *cv = POS_TOPRIGHT; + if (*ul == POS_TOPRIGHT) + *cv = POS_BOTTOMRIGHT; + } +} + +static void +mg_place_userlist_and_chanview_real (session_gui *gui, GtkWidget *userlist, GtkWidget *chanview) +{ + int unref_userlist = FALSE; + int unref_chanview = FALSE; + + /* first, remove userlist/treeview from their containers */ + if (userlist && userlist->parent) + { + g_object_ref (userlist); + gtk_container_remove (GTK_CONTAINER (userlist->parent), userlist); + unref_userlist = TRUE; + } + + if (chanview && chanview->parent) + { + g_object_ref (chanview); + gtk_container_remove (GTK_CONTAINER (chanview->parent), chanview); + unref_chanview = TRUE; + } + + if (chanview) + { + /* incase the previous pos was POS_HIDDEN */ + gtk_widget_show (chanview); + + gtk_table_set_row_spacing (GTK_TABLE (gui->main_table), 1, 0); + gtk_table_set_row_spacing (GTK_TABLE (gui->main_table), 2, 2); + + /* then place them back in their new positions */ + switch (prefs.tab_pos) + { + case POS_TOPLEFT: + gtk_paned_pack1 (GTK_PANED (gui->vpane_left), chanview, FALSE, TRUE); + break; + case POS_BOTTOMLEFT: + gtk_paned_pack2 (GTK_PANED (gui->vpane_left), chanview, FALSE, TRUE); + break; + case POS_TOPRIGHT: + gtk_paned_pack1 (GTK_PANED (gui->vpane_right), chanview, FALSE, TRUE); + break; + case POS_BOTTOMRIGHT: + gtk_paned_pack2 (GTK_PANED (gui->vpane_right), chanview, FALSE, TRUE); + break; + case POS_TOP: + gtk_table_set_row_spacing (GTK_TABLE (gui->main_table), 1, GUI_SPACING-1); + gtk_table_attach (GTK_TABLE (gui->main_table), chanview, + 1, 2, 1, 2, GTK_FILL, GTK_FILL, 0, 0); + break; + case POS_HIDDEN: + gtk_widget_hide (chanview); + /* always attach it to something to avoid ref_count=0 */ + if (prefs.gui_ulist_pos == POS_TOP) + gtk_table_attach (GTK_TABLE (gui->main_table), chanview, + 1, 2, 3, 4, GTK_FILL, GTK_FILL, 0, 0); + + else + gtk_table_attach (GTK_TABLE (gui->main_table), chanview, + 1, 2, 1, 2, GTK_FILL, GTK_FILL, 0, 0); + break; + default:/* POS_BOTTOM */ + gtk_table_set_row_spacing (GTK_TABLE (gui->main_table), 2, 3); + gtk_table_attach (GTK_TABLE (gui->main_table), chanview, + 1, 2, 3, 4, GTK_FILL, GTK_FILL, 0, 0); + } + } + + if (userlist) + { + switch (prefs.gui_ulist_pos) + { + case POS_TOPLEFT: + gtk_paned_pack1 (GTK_PANED (gui->vpane_left), userlist, FALSE, TRUE); + break; + case POS_BOTTOMLEFT: + gtk_paned_pack2 (GTK_PANED (gui->vpane_left), userlist, FALSE, TRUE); + break; + case POS_BOTTOMRIGHT: + gtk_paned_pack2 (GTK_PANED (gui->vpane_right), userlist, FALSE, TRUE); + break; + /*case POS_HIDDEN: + break;*/ /* Hide using the VIEW menu instead */ + default:/* POS_TOPRIGHT */ + gtk_paned_pack1 (GTK_PANED (gui->vpane_right), userlist, FALSE, TRUE); + } + } + + if (unref_chanview) + g_object_unref (chanview); + if (unref_userlist) + g_object_unref (userlist); + + mg_hide_empty_boxes (gui); +} + +static void +mg_place_userlist_and_chanview (session_gui *gui) +{ + GtkOrientation orientation; + GtkWidget *chanviewbox = NULL; + int pos; + + mg_sanitize_positions (&prefs.tab_pos, &prefs.gui_ulist_pos); + + if (gui->chanview) + { + pos = prefs.tab_pos; + + orientation = chanview_get_orientation (gui->chanview); + if ((pos == POS_BOTTOM || pos == POS_TOP) && orientation == GTK_ORIENTATION_VERTICAL) + chanview_set_orientation (gui->chanview, FALSE); + else if ((pos == POS_TOPLEFT || pos == POS_BOTTOMLEFT || pos == POS_TOPRIGHT || pos == POS_BOTTOMRIGHT) && orientation == GTK_ORIENTATION_HORIZONTAL) + chanview_set_orientation (gui->chanview, TRUE); + chanviewbox = chanview_get_box (gui->chanview); + } + + mg_place_userlist_and_chanview_real (gui, gui->user_box, chanviewbox); +} + +void +mg_change_layout (int type) +{ + if (mg_gui) + { + /* put tabs at the bottom */ + if (type == 0 && prefs.tab_pos != POS_BOTTOM && prefs.tab_pos != POS_TOP) + prefs.tab_pos = POS_BOTTOM; + + mg_place_userlist_and_chanview (mg_gui); + chanview_set_impl (mg_gui->chanview, type); + } +} + +static void +mg_inputbox_rightclick (GtkEntry *entry, GtkWidget *menu) +{ + mg_create_color_menu (menu, NULL); +} + +static void +mg_create_entry (session *sess, GtkWidget *box) +{ + GtkWidget *sw, *hbox, *but, *entry; + session_gui *gui = sess->gui; + + hbox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (box), hbox, 0, 0, 0); + + gui->nick_box = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (hbox), gui->nick_box, 0, 0, 0); + + gui->nick_label = but = gtk_button_new_with_label (sess->server->nick); + gtk_button_set_relief (GTK_BUTTON (but), GTK_RELIEF_NONE); + GTK_WIDGET_UNSET_FLAGS (but, GTK_CAN_FOCUS); + gtk_box_pack_end (GTK_BOX (gui->nick_box), but, 0, 0, 0); + g_signal_connect (G_OBJECT (but), "clicked", + G_CALLBACK (mg_nickclick_cb), NULL); + +#ifdef USE_GTKSPELL + gui->input_box = entry = gtk_text_view_new (); + gtk_widget_set_size_request (entry, 0, 1); + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (entry), GTK_WRAP_NONE); + gtk_text_view_set_accepts_tab (GTK_TEXT_VIEW (entry), FALSE); + if (prefs.gui_input_spell) + gtkspell_new_attach (GTK_TEXT_VIEW (entry), NULL, NULL); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_NEVER, + GTK_POLICY_NEVER); + gtk_container_add (GTK_CONTAINER (sw), entry); + gtk_container_add (GTK_CONTAINER (hbox), sw); +#else +#ifdef USE_LIBSEXY + gui->input_box = entry = sexy_spell_entry_new (); + sexy_spell_entry_set_checked ((SexySpellEntry *)entry, prefs.gui_input_spell); +#else + gui->input_box = entry = gtk_entry_new (); +#endif + gtk_entry_set_max_length (GTK_ENTRY (gui->input_box), 2048); + g_signal_connect (G_OBJECT (entry), "activate", + G_CALLBACK (mg_inputbox_cb), gui); + gtk_container_add (GTK_CONTAINER (hbox), entry); +#endif + + gtk_widget_set_name (entry, "xchat-inputbox"); + g_signal_connect (G_OBJECT (entry), "key_press_event", + G_CALLBACK (key_handle_key_press), NULL); + g_signal_connect (G_OBJECT (entry), "focus_in_event", + G_CALLBACK (mg_inputbox_focus), gui); + g_signal_connect (G_OBJECT (entry), "populate_popup", + G_CALLBACK (mg_inputbox_rightclick), NULL); + gtk_widget_grab_focus (entry); + + if (prefs.style_inputbox) + mg_apply_entry_style (entry); +} + +static void +mg_switch_tab_cb (chanview *cv, chan *ch, int tag, gpointer ud) +{ + chan *old; + session *sess = ud; + + old = active_tab; + active_tab = ch; + + if (tag == TAG_IRC) + { + if (active_tab != old) + { + if (old && current_tab) + mg_unpopulate (current_tab); + mg_populate (sess); + } + } else if (old != active_tab) + { + /* userdata for non-irc tabs is actually the GtkBox */ + mg_show_generic_tab (ud); + if (!mg_is_userlist_and_tree_combined ()) + mg_userlist_showhide (current_sess, FALSE); /* hide */ + } +} + +/* compare two tabs (for tab sorting function) */ + +static int +mg_tabs_compare (session *a, session *b) +{ + /* server tabs always go first */ + if (a->type == SESS_SERVER) + return -1; + + /* then channels */ + if (a->type == SESS_CHANNEL && b->type != SESS_CHANNEL) + return -1; + if (a->type != SESS_CHANNEL && b->type == SESS_CHANNEL) + return 1; + + return strcasecmp (a->channel, b->channel); +} + +static void +mg_create_tabs (session_gui *gui) +{ + gboolean use_icons = FALSE; + + /* if any one of these PNGs exist, the chanview will create + * the extra column for icons. */ + if (pix_channel || pix_dialog || pix_server || pix_util) + use_icons = TRUE; + + gui->chanview = chanview_new (prefs.tab_layout, prefs.truncchans, + prefs.tab_sort, use_icons, + prefs.style_namelistgad ? input_style : NULL); + chanview_set_callbacks (gui->chanview, mg_switch_tab_cb, mg_xbutton_cb, + mg_tab_contextmenu_cb, (void *)mg_tabs_compare); + mg_place_userlist_and_chanview (gui); +} + +static gboolean +mg_tabwin_focus_cb (GtkWindow * win, GdkEventFocus *event, gpointer userdata) +{ + current_sess = current_tab; + if (current_sess) + { + gtk_xtext_check_marker_visibility (GTK_XTEXT (current_sess->gui->xtext)); + plugin_emit_dummy_print (current_sess, "Focus Window"); + } +#ifndef WIN32 +#ifdef USE_XLIB + unflash_window (GTK_WIDGET (win)); +#endif +#endif + return FALSE; +} + +static gboolean +mg_topwin_focus_cb (GtkWindow * win, GdkEventFocus *event, session *sess) +{ + current_sess = sess; + if (!sess->server->server_session) + sess->server->server_session = sess; + gtk_xtext_check_marker_visibility(GTK_XTEXT (current_sess->gui->xtext)); +#ifndef WIN32 +#ifdef USE_XLIB + unflash_window (GTK_WIDGET (win)); +#endif +#endif + plugin_emit_dummy_print (sess, "Focus Window"); + return FALSE; +} + +static void +mg_create_menu (session_gui *gui, GtkWidget *table, int away_state) +{ + GtkAccelGroup *accel_group; + + accel_group = gtk_accel_group_new (); + gtk_window_add_accel_group (GTK_WINDOW (gtk_widget_get_toplevel (table)), + accel_group); + g_object_unref (accel_group); + + gui->menu = menu_create_main (accel_group, TRUE, away_state, !gui->is_tab, + gui->menu_item); + gtk_table_attach (GTK_TABLE (table), gui->menu, 0, 3, 0, 1, + GTK_EXPAND | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); +} + +static void +mg_create_irctab (session *sess, GtkWidget *table) +{ + GtkWidget *vbox; + session_gui *gui = sess->gui; + + vbox = gtk_vbox_new (FALSE, 0); + gtk_table_attach (GTK_TABLE (table), vbox, 1, 2, 2, 3, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + mg_create_center (sess, gui, vbox); +} + +static void +mg_create_topwindow (session *sess) +{ + GtkWidget *win; + GtkWidget *table; + + if (sess->type == SESS_DIALOG) + win = gtkutil_window_new ("XChat", NULL, + prefs.dialog_width, prefs.dialog_height, 0); + else + win = gtkutil_window_new ("XChat", NULL, + prefs.mainwindow_width, + prefs.mainwindow_height, 0); + sess->gui->window = win; + gtk_container_set_border_width (GTK_CONTAINER (win), GUI_BORDER); + + g_signal_connect (G_OBJECT (win), "focus_in_event", + G_CALLBACK (mg_topwin_focus_cb), sess); + g_signal_connect (G_OBJECT (win), "destroy", + G_CALLBACK (mg_topdestroy_cb), sess); + g_signal_connect (G_OBJECT (win), "configure_event", + G_CALLBACK (mg_configure_cb), sess); + + palette_alloc (win); + + table = gtk_table_new (4, 3, FALSE); + /* spacing under the menubar */ + gtk_table_set_row_spacing (GTK_TABLE (table), 0, GUI_SPACING); + /* left and right borders */ + gtk_table_set_col_spacing (GTK_TABLE (table), 0, 1); + gtk_table_set_col_spacing (GTK_TABLE (table), 1, 1); + gtk_container_add (GTK_CONTAINER (win), table); + + mg_create_irctab (sess, table); + mg_create_menu (sess->gui, table, sess->server->is_away); + + if (sess->res->buffer == NULL) + { + sess->res->buffer = gtk_xtext_buffer_new (GTK_XTEXT (sess->gui->xtext)); + gtk_xtext_buffer_show (GTK_XTEXT (sess->gui->xtext), sess->res->buffer, TRUE); + gtk_xtext_set_time_stamp (sess->res->buffer, prefs.timestamp); + sess->res->user_model = userlist_create_model (); + } + + userlist_show (sess); + + gtk_widget_show_all (table); + + if (prefs.hidemenu) + gtk_widget_hide (sess->gui->menu); + + if (!prefs.topicbar) + gtk_widget_hide (sess->gui->topic_bar); + + if (!prefs.userlistbuttons) + gtk_widget_hide (sess->gui->button_box); + + if (prefs.gui_tweaks & 2) + gtk_widget_hide (sess->gui->nick_box); + + mg_decide_userlist (sess, FALSE); + + if (sess->type == SESS_DIALOG) + { + /* hide the chan-mode buttons */ + gtk_widget_hide (sess->gui->topicbutton_box); + } else + { + gtk_widget_hide (sess->gui->dialogbutton_box); + + if (!prefs.chanmodebuttons) + gtk_widget_hide (sess->gui->topicbutton_box); + } + + mg_place_userlist_and_chanview (sess->gui); + + gtk_widget_show (win); +} + +static gboolean +mg_tabwindow_de_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) +{ + GSList *list; + session *sess; + + if ((prefs.gui_tray_flags & 1) && tray_toggle_visibility (FALSE)) + return TRUE; + + /* check for remaining toplevel windows */ + list = sess_list; + while (list) + { + sess = list->data; + if (!sess->gui->is_tab) + return FALSE; + list = list->next; + } + + mg_open_quit_dialog (TRUE); + return TRUE; +} + +static void +mg_create_tabwindow (session *sess) +{ + GtkWidget *win; + GtkWidget *table; + + win = gtkutil_window_new ("XChat", NULL, prefs.mainwindow_width, + prefs.mainwindow_height, 0); + sess->gui->window = win; + gtk_window_move (GTK_WINDOW (win), prefs.mainwindow_left, + prefs.mainwindow_top); + if (prefs.gui_win_state) + gtk_window_maximize (GTK_WINDOW (win)); + gtk_container_set_border_width (GTK_CONTAINER (win), GUI_BORDER); + + g_signal_connect (G_OBJECT (win), "delete_event", + G_CALLBACK (mg_tabwindow_de_cb), 0); + g_signal_connect (G_OBJECT (win), "destroy", + G_CALLBACK (mg_tabwindow_kill_cb), 0); + g_signal_connect (G_OBJECT (win), "focus_in_event", + G_CALLBACK (mg_tabwin_focus_cb), NULL); + g_signal_connect (G_OBJECT (win), "configure_event", + G_CALLBACK (mg_configure_cb), NULL); + g_signal_connect (G_OBJECT (win), "window_state_event", + G_CALLBACK (mg_windowstate_cb), NULL); + + palette_alloc (win); + + sess->gui->main_table = table = gtk_table_new (4, 3, FALSE); + /* spacing under the menubar */ + gtk_table_set_row_spacing (GTK_TABLE (table), 0, GUI_SPACING); + /* left and right borders */ + gtk_table_set_col_spacing (GTK_TABLE (table), 0, 1); + gtk_table_set_col_spacing (GTK_TABLE (table), 1, 1); + gtk_container_add (GTK_CONTAINER (win), table); + + mg_create_irctab (sess, table); + mg_create_tabs (sess->gui); + mg_create_menu (sess->gui, table, sess->server->is_away); + + mg_focus (sess); + + gtk_widget_show_all (table); + + if (prefs.hidemenu) + gtk_widget_hide (sess->gui->menu); + + mg_decide_userlist (sess, FALSE); + + if (!prefs.topicbar) + gtk_widget_hide (sess->gui->topic_bar); + + if (!prefs.chanmodebuttons) + gtk_widget_hide (sess->gui->topicbutton_box); + + if (!prefs.userlistbuttons) + gtk_widget_hide (sess->gui->button_box); + + if (prefs.gui_tweaks & 2) + gtk_widget_hide (sess->gui->nick_box); + + mg_place_userlist_and_chanview (sess->gui); + + gtk_widget_show (win); +} + +void +mg_apply_setup (void) +{ + GSList *list = sess_list; + session *sess; + int done_main = FALSE; + + mg_create_tab_colors (); + + while (list) + { + sess = list->data; + gtk_xtext_set_time_stamp (sess->res->buffer, prefs.timestamp); + ((xtext_buffer *)sess->res->buffer)->needs_recalc = TRUE; + if (!sess->gui->is_tab || !done_main) + mg_place_userlist_and_chanview (sess->gui); + if (sess->gui->is_tab) + done_main = TRUE; + list = list->next; + } +} + +static chan * +mg_add_generic_tab (char *name, char *title, void *family, GtkWidget *box) +{ + chan *ch; + + gtk_notebook_append_page (GTK_NOTEBOOK (mg_gui->note_book), box, NULL); + gtk_widget_show (box); + + ch = chanview_add (mg_gui->chanview, name, NULL, box, TRUE, TAG_UTIL, pix_util); + chan_set_color (ch, plain_list); + /* FIXME: memory leak */ + g_object_set_data (G_OBJECT (box), "title", strdup (title)); + g_object_set_data (G_OBJECT (box), "ch", ch); + + if (prefs.newtabstofront) + chan_focus (ch); + + return ch; +} + +void +fe_buttons_update (session *sess) +{ + session_gui *gui = sess->gui; + + gtk_widget_destroy (gui->button_box); + gui->button_box = mg_create_userlistbuttons (gui->button_box_parent); + + if (prefs.userlistbuttons) + gtk_widget_show (sess->gui->button_box); + else + gtk_widget_hide (sess->gui->button_box); +} + +void +fe_clear_channel (session *sess) +{ + char tbuf[CHANLEN+6]; + session_gui *gui = sess->gui; + + if (sess->gui->is_tab) + { + if (sess->waitchannel[0]) + { + if (prefs.truncchans > 2 && g_utf8_strlen (sess->waitchannel, -1) > prefs.truncchans) + { + /* truncate long channel names */ + tbuf[0] = '('; + strcpy (tbuf + 1, sess->waitchannel); + g_utf8_offset_to_pointer(tbuf, prefs.truncchans)[0] = 0; + strcat (tbuf, "..)"); + } else + { + sprintf (tbuf, "(%s)", sess->waitchannel); + } + } + else + strcpy (tbuf, _("<none>")); + chan_rename (sess->res->tab, tbuf, prefs.truncchans); + } + + if (!sess->gui->is_tab || sess == current_tab) + { + gtk_entry_set_text (GTK_ENTRY (gui->topic_entry), ""); + + if (gui->op_xpm) + { + gtk_widget_destroy (gui->op_xpm); + gui->op_xpm = 0; + } + } else + { + if (sess->res->topic_text) + { + free (sess->res->topic_text); + sess->res->topic_text = NULL; + } + } +} + +void +fe_set_nonchannel (session *sess, int state) +{ +} + +void +fe_dlgbuttons_update (session *sess) +{ + GtkWidget *box; + session_gui *gui = sess->gui; + + gtk_widget_destroy (gui->dialogbutton_box); + + gui->dialogbutton_box = box = gtk_hbox_new (0, 0); + gtk_box_pack_start (GTK_BOX (gui->topic_bar), box, 0, 0, 0); + gtk_box_reorder_child (GTK_BOX (gui->topic_bar), box, 3); + mg_create_dialogbuttons (box); + + gtk_widget_show_all (box); + + if (current_tab && current_tab->type != SESS_DIALOG) + gtk_widget_hide (current_tab->gui->dialogbutton_box); +} + +void +fe_update_mode_buttons (session *sess, char mode, char sign) +{ + int state, i; + + if (sign == '+') + state = TRUE; + else + state = FALSE; + + for (i = 0; i < NUM_FLAG_WIDS - 1; i++) + { + if (chan_flags[i] == mode) + { + if (!sess->gui->is_tab || sess == current_tab) + { + ignore_chanmode = TRUE; + if (GTK_TOGGLE_BUTTON (sess->gui->flag_wid[i])->active != state) + gtk_toggle_button_set_active ( + GTK_TOGGLE_BUTTON (sess->gui->flag_wid[i]), state); + ignore_chanmode = FALSE; + } else + { + sess->res->flag_wid_state[i] = state; + } + return; + } + } +} + +void +fe_set_nick (server *serv, char *newnick) +{ + GSList *list = sess_list; + session *sess; + + while (list) + { + sess = list->data; + if (sess->server == serv) + { + if (current_tab == sess || !sess->gui->is_tab) + gtk_button_set_label (GTK_BUTTON (sess->gui->nick_label), newnick); + } + list = list->next; + } +} + +void +fe_set_away (server *serv) +{ + GSList *list = sess_list; + session *sess; + + while (list) + { + sess = list->data; + if (sess->server == serv) + { + if (!sess->gui->is_tab || sess == current_tab) + { + GTK_CHECK_MENU_ITEM (sess->gui->menu_item[MENU_ID_AWAY])->active = serv->is_away; + /* gray out my nickname */ + mg_set_myself_away (sess->gui, serv->is_away); + } + } + list = list->next; + } +} + +void +fe_set_channel (session *sess) +{ + if (sess->res->tab != NULL) + chan_rename (sess->res->tab, sess->channel, prefs.truncchans); +} + +void +mg_changui_new (session *sess, restore_gui *res, int tab, int focus) +{ + int first_run = FALSE; + session_gui *gui; + struct User *user = NULL; + + if (!res) + { + res = malloc (sizeof (restore_gui)); + memset (res, 0, sizeof (restore_gui)); + } + + sess->res = res; + + if (!sess->server->front_session) + sess->server->front_session = sess; + + if (!is_channel (sess->server, sess->channel)) + user = userlist_find_global (sess->server, sess->channel); + + if (!tab) + { + gui = malloc (sizeof (session_gui)); + memset (gui, 0, sizeof (session_gui)); + gui->is_tab = FALSE; + sess->gui = gui; + mg_create_topwindow (sess); + fe_set_title (sess); + if (user && user->hostname) + set_topic (sess, user->hostname, user->hostname); + return; + } + + if (mg_gui == NULL) + { + first_run = TRUE; + gui = &static_mg_gui; + memset (gui, 0, sizeof (session_gui)); + gui->is_tab = TRUE; + sess->gui = gui; + mg_create_tabwindow (sess); + mg_gui = gui; + parent_window = gui->window; + } else + { + sess->gui = gui = mg_gui; + gui->is_tab = TRUE; + } + + if (user && user->hostname) + set_topic (sess, user->hostname, user->hostname); + + mg_add_chan (sess); + + if (first_run || (prefs.newtabstofront == FOCUS_NEW_ONLY_ASKED && focus) + || prefs.newtabstofront == FOCUS_NEW_ALL ) + chan_focus (res->tab); +} + +GtkWidget * +mg_create_generic_tab (char *name, char *title, int force_toplevel, + int link_buttons, + void *close_callback, void *userdata, + int width, int height, GtkWidget **vbox_ret, + void *family) +{ + GtkWidget *vbox, *win; + + if (prefs.tab_pos == POS_HIDDEN && prefs.windows_as_tabs) + prefs.windows_as_tabs = 0; + + if (force_toplevel || !prefs.windows_as_tabs) + { + win = gtkutil_window_new (title, name, width, height, 3); + vbox = gtk_vbox_new (0, 0); + *vbox_ret = vbox; + gtk_container_add (GTK_CONTAINER (win), vbox); + gtk_widget_show (vbox); + if (close_callback) + g_signal_connect (G_OBJECT (win), "destroy", + G_CALLBACK (close_callback), userdata); + return win; + } + + vbox = gtk_vbox_new (0, 2); + g_object_set_data (G_OBJECT (vbox), "w", GINT_TO_POINTER (width)); + g_object_set_data (G_OBJECT (vbox), "h", GINT_TO_POINTER (height)); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 3); + *vbox_ret = vbox; + + if (close_callback) + g_signal_connect (G_OBJECT (vbox), "destroy", + G_CALLBACK (close_callback), userdata); + + mg_add_generic_tab (name, title, family, vbox); + +/* if (link_buttons) + { + hbox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), hbox, 0, 0, 0); + mg_create_link_buttons (hbox, ch); + gtk_widget_show (hbox); + }*/ + + return vbox; +} + +void +mg_move_tab (session *sess, int delta) +{ + if (sess->gui->is_tab) + chan_move (sess->res->tab, delta); +} + +void +mg_move_tab_family (session *sess, int delta) +{ + if (sess->gui->is_tab) + chan_move_family (sess->res->tab, delta); +} + +void +mg_set_title (GtkWidget *vbox, char *title) /* for non-irc tab/window only */ +{ + char *old; + + old = g_object_get_data (G_OBJECT (vbox), "title"); + if (old) + { + g_object_set_data (G_OBJECT (vbox), "title", strdup (title)); + free (old); + } else + { + gtk_window_set_title (GTK_WINDOW (vbox), title); + } +} + +void +fe_server_callback (server *serv) +{ + joind_close (serv); + + if (serv->gui->chanlist_window) + mg_close_gen (NULL, serv->gui->chanlist_window); + + if (serv->gui->rawlog_window) + mg_close_gen (NULL, serv->gui->rawlog_window); + + free (serv->gui); +} + +/* called when a session is being killed */ + +void +fe_session_callback (session *sess) +{ + if (sess->res->banlist_window) + mg_close_gen (NULL, sess->res->banlist_window); + + if (sess->res->input_text) + free (sess->res->input_text); + + if (sess->res->topic_text) + free (sess->res->topic_text); + + if (sess->res->limit_text) + free (sess->res->limit_text); + + if (sess->res->key_text) + free (sess->res->key_text); + + if (sess->res->queue_text) + free (sess->res->queue_text); + if (sess->res->queue_tip) + free (sess->res->queue_tip); + + if (sess->res->lag_text) + free (sess->res->lag_text); + if (sess->res->lag_tip) + free (sess->res->lag_tip); + + if (sess->gui->bartag) + fe_timeout_remove (sess->gui->bartag); + + if (sess->gui != &static_mg_gui) + free (sess->gui); + free (sess->res); +} + +/* ===== DRAG AND DROP STUFF ===== */ + +static gboolean +is_child_of (GtkWidget *widget, GtkWidget *parent) +{ + while (widget) + { + if (widget->parent == parent) + return TRUE; + widget = widget->parent; + } + return FALSE; +} + +static void +mg_handle_drop (GtkWidget *widget, int y, int *pos, int *other_pos) +{ + int height; + session_gui *gui = current_sess->gui; + + gdk_drawable_get_size (widget->window, NULL, &height); + + if (y < height / 2) + { + if (is_child_of (widget, gui->vpane_left)) + *pos = 1; /* top left */ + else + *pos = 3; /* top right */ + } + else + { + if (is_child_of (widget, gui->vpane_left)) + *pos = 2; /* bottom left */ + else + *pos = 4; /* bottom right */ + } + + /* both in the same pos? must move one */ + if (*pos == *other_pos) + { + switch (*other_pos) + { + case 1: + *other_pos = 2; + break; + case 2: + *other_pos = 1; + break; + case 3: + *other_pos = 4; + break; + case 4: + *other_pos = 3; + break; + } + } + + mg_place_userlist_and_chanview (gui); +} + +static gboolean +mg_is_gui_target (GdkDragContext *context) +{ + char *target_name; + + if (!context || !context->targets || !context->targets->data) + return FALSE; + + target_name = gdk_atom_name (context->targets->data); + if (target_name) + { + /* if it's not XCHAT_CHANVIEW or XCHAT_USERLIST */ + /* we should ignore it. */ + if (target_name[0] != 'X') + { + g_free (target_name); + return FALSE; + } + g_free (target_name); + } + + return TRUE; +} + +/* this begin callback just creates an nice of the source */ + +gboolean +mg_drag_begin_cb (GtkWidget *widget, GdkDragContext *context, gpointer userdata) +{ +#ifndef WIN32 /* leaks GDI pool memory - don't use on win32 */ + int width, height; + GdkColormap *cmap; + GdkPixbuf *pix, *pix2; + + /* ignore file drops */ + if (!mg_is_gui_target (context)) + return FALSE; + + cmap = gtk_widget_get_colormap (widget); + gdk_drawable_get_size (widget->window, &width, &height); + + pix = gdk_pixbuf_get_from_drawable (NULL, widget->window, cmap, 0, 0, 0, 0, width, height); + pix2 = gdk_pixbuf_scale_simple (pix, width * 4 / 5, height / 2, GDK_INTERP_HYPER); + g_object_unref (pix); + + gtk_drag_set_icon_pixbuf (context, pix2, 0, 0); + g_object_set_data (G_OBJECT (widget), "ico", pix2); +#endif + + return TRUE; +} + +void +mg_drag_end_cb (GtkWidget *widget, GdkDragContext *context, gpointer userdata) +{ + /* ignore file drops */ + if (!mg_is_gui_target (context)) + return; + +#ifndef WIN32 + g_object_unref (g_object_get_data (G_OBJECT (widget), "ico")); +#endif +} + +/* drop complete */ + +gboolean +mg_drag_drop_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, guint time, gpointer user_data) +{ + /* ignore file drops */ + if (!mg_is_gui_target (context)) + return FALSE; + + switch (context->action) + { + case GDK_ACTION_MOVE: + /* from userlist */ + mg_handle_drop (widget, y, &prefs.gui_ulist_pos, &prefs.tab_pos); + break; + case GDK_ACTION_COPY: + /* from tree - we use GDK_ACTION_COPY for the tree */ + mg_handle_drop (widget, y, &prefs.tab_pos, &prefs.gui_ulist_pos); + break; + default: + return FALSE; + } + + return TRUE; +} + +/* draw highlight rectangle in the destination */ + +gboolean +mg_drag_motion_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, guint time, gpointer scbar) +{ + GdkGC *gc; + GdkColor col; + GdkGCValues val; + int half, width, height; + int ox, oy; + GtkPaned *paned; + GdkDrawable *draw; + + /* ignore file drops */ + if (!mg_is_gui_target (context)) + return FALSE; + + if (scbar) /* scrollbar */ + { + ox = widget->allocation.x; + oy = widget->allocation.y; + width = widget->allocation.width; + height = widget->allocation.height; + draw = widget->window; + } + else + { + ox = oy = 0; + gdk_drawable_get_size (widget->window, &width, &height); + draw = widget->window; + } + + val.subwindow_mode = GDK_INCLUDE_INFERIORS; + val.graphics_exposures = 0; + val.function = GDK_XOR; + + gc = gdk_gc_new_with_values (widget->window, &val, GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW | GDK_GC_FUNCTION); + col.red = rand() % 0xffff; + col.green = rand() % 0xffff; + col.blue = rand() % 0xffff; + gdk_colormap_alloc_color (gtk_widget_get_colormap (widget), &col, FALSE, TRUE); + gdk_gc_set_foreground (gc, &col); + + half = height / 2; + +#if 0 + /* are both tree/userlist on the same side? */ + paned = (GtkPaned *)widget->parent->parent; + if (paned->child1 != NULL && paned->child2 != NULL) + { + gdk_draw_rectangle (draw, gc, 0, 1, 2, width - 3, height - 4); + gdk_draw_rectangle (draw, gc, 0, 0, 1, width - 1, height - 2); + g_object_unref (gc); + return TRUE; + } +#endif + + if (y < half) + { + gdk_draw_rectangle (draw, gc, FALSE, 1 + ox, 2 + oy, width - 3, half - 4); + gdk_draw_rectangle (draw, gc, FALSE, 0 + ox, 1 + oy, width - 1, half - 2); + gtk_widget_queue_draw_area (widget, ox, half + oy, width, height - half); + } + else + { + gdk_draw_rectangle (draw, gc, FALSE, 0 + ox, half + 1 + oy, width - 1, half - 2); + gdk_draw_rectangle (draw, gc, FALSE, 1 + ox, half + 2 + oy, width - 3, half - 4); + gtk_widget_queue_draw_area (widget, ox, oy, width, half); + } + + g_object_unref (gc); + + return TRUE; +} diff --git a/src/fe-gtk/maingui.h b/src/fe-gtk/maingui.h new file mode 100644 index 00000000..bc9aaefd --- /dev/null +++ b/src/fe-gtk/maingui.h @@ -0,0 +1,33 @@ +extern GtkStyle *input_style; +extern GtkWidget *parent_window; + +void mg_changui_new (session *sess, restore_gui *res, int tab, int focus); +void mg_update_xtext (GtkWidget *wid); +void mg_open_quit_dialog (gboolean minimize_button); +void mg_switch_page (int relative, int num); +void mg_move_tab (session *, int delta); +void mg_move_tab_family (session *, int delta); +void mg_bring_tofront (GtkWidget *vbox); +void mg_bring_tofront_sess (session *sess); +void mg_decide_userlist (session *sess, gboolean switch_to_current); +void mg_set_topic_tip (session *sess); +GtkWidget *mg_create_generic_tab (char *name, char *title, int force_toplevel, int link_buttons, void *close_callback, void *userdata, int width, int height, GtkWidget **vbox_ret, void *family); +void mg_set_title (GtkWidget *button, char *title); +void mg_set_access_icon (session_gui *gui, GdkPixbuf *pix, gboolean away); +void mg_apply_setup (void); +void mg_close_sess (session *); +void mg_tab_close (session *sess); +void mg_detach (session *sess, int mode); +void mg_progressbar_create (session_gui *gui); +void mg_progressbar_destroy (session_gui *gui); +void mg_dnd_drop_file (session *sess, char *target, char *uri); +void mg_change_layout (int type); +void mg_update_meters (session_gui *); +void mg_inputbox_cb (GtkWidget *igad, session_gui *gui); +void mg_create_icon_item (char *label, char *stock, GtkWidget *menu, void *callback, void *userdata); +GtkWidget *mg_submenu (GtkWidget *menu, char *text); +/* DND */ +gboolean mg_drag_begin_cb (GtkWidget *widget, GdkDragContext *context, gpointer userdata); +void mg_drag_end_cb (GtkWidget *widget, GdkDragContext *context, gpointer userdata); +gboolean mg_drag_drop_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, guint time, gpointer user_data); +gboolean mg_drag_motion_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, guint time, gpointer user_data); diff --git a/src/fe-gtk/menu.c b/src/fe-gtk/menu.c new file mode 100644 index 00000000..d04be222 --- /dev/null +++ b/src/fe-gtk/menu.c @@ -0,0 +1,2270 @@ +/* X-Chat + * Copyright (C) 1998-2007 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <string.h> +#include <unistd.h> + +#ifdef WIN32 +#include <windows.h> +#endif + +#include "fe-gtk.h" + +#include <gtk/gtkhbox.h> +#include <gtk/gtkcheckmenuitem.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkimage.h> +#include <gtk/gtkimagemenuitem.h> +#include <gtk/gtkradiomenuitem.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkmessagedialog.h> +#include <gtk/gtkmenu.h> +#include <gtk/gtkmenubar.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkversion.h> +#include <gdk/gdkkeysyms.h> + +#include "../common/xchat.h" +#include "../common/xchatc.h" +#include "../common/cfgfiles.h" +#include "../common/outbound.h" +#include "../common/ignore.h" +#include "../common/fe.h" +#include "../common/server.h" +#include "../common/servlist.h" +#include "../common/notify.h" +#include "../common/util.h" +#include "xtext.h" +#include "about.h" +#include "ascii.h" +#include "banlist.h" +#include "chanlist.h" +#include "editlist.h" +#include "fkeys.h" +#include "gtkutil.h" +#include "maingui.h" +#include "notifygui.h" +#include "pixmaps.h" +#include "rawlog.h" +#include "palette.h" +#include "plugingui.h" +#include "search.h" +#include "textgui.h" +#include "urlgrab.h" +#include "userlistgui.h" +#include "menu.h" + +static GSList *submenu_list; + +enum +{ + M_MENUITEM, + M_NEWMENU, + M_END, + M_SEP, + M_MENUTOG, + M_MENURADIO, + M_MENUSTOCK, + M_MENUPIX, + M_MENUSUB +}; + +struct mymenu +{ + char *text; + void *callback; + char *image; + unsigned char type; /* M_XXX */ + unsigned char id; /* MENU_ID_XXX (menu.h) */ + unsigned char state; /* ticked or not? */ + unsigned char sensitive; /* shaded out? */ + guint key; /* GDK_x */ +}; + +#define XCMENU_DOLIST 1 +#define XCMENU_SHADED 1 +#define XCMENU_MARKUP 2 +#define XCMENU_MNEMONIC 4 + +/* execute a userlistbutton/popupmenu command */ + +static void +nick_command (session * sess, char *cmd) +{ + if (*cmd == '!') + xchat_exec (cmd + 1); + else + handle_command (sess, cmd, TRUE); +} + +/* fill in the %a %s %n etc and execute the command */ + +void +nick_command_parse (session *sess, char *cmd, char *nick, char *allnick) +{ + char *buf; + char *host = _("Host unknown"); + struct User *user; + int len; + +/* if (sess->type == SESS_DIALOG) + { + buf = (char *)(GTK_ENTRY (sess->gui->topic_entry)->text); + buf = strrchr (buf, '@'); + if (buf) + host = buf + 1; + } else*/ + { + user = userlist_find (sess, nick); + if (user && user->hostname) + host = strchr (user->hostname, '@') + 1; + } + + /* this can't overflow, since popup->cmd is only 256 */ + len = strlen (cmd) + strlen (nick) + strlen (allnick) + 512; + buf = malloc (len); + + auto_insert (buf, len, cmd, 0, 0, allnick, sess->channel, "", + server_get_network (sess->server, TRUE), host, + sess->server->nick, nick); + + nick_command (sess, buf); + + free (buf); +} + +/* userlist button has been clicked */ + +void +userlist_button_cb (GtkWidget * button, char *cmd) +{ + int i, num_sel, using_allnicks = FALSE; + char **nicks, *allnicks; + char *nick = NULL; + session *sess; + + sess = current_sess; + + if (strstr (cmd, "%a")) + using_allnicks = TRUE; + + if (sess->type == SESS_DIALOG) + { + /* fake a selection */ + nicks = malloc (sizeof (char *) * 2); + nicks[0] = g_strdup (sess->channel); + nicks[1] = NULL; + num_sel = 1; + } else + { + /* find number of selected rows */ + nicks = userlist_selection_list (sess->gui->user_tree, &num_sel); + if (num_sel < 1) + { + nick_command_parse (sess, cmd, "", ""); + return; + } + } + + /* create "allnicks" string */ + allnicks = malloc (((NICKLEN + 1) * num_sel) + 1); + *allnicks = 0; + + i = 0; + while (nicks[i]) + { + if (i > 0) + strcat (allnicks, " "); + strcat (allnicks, nicks[i]); + + if (!nick) + nick = nicks[0]; + + /* if not using "%a", execute the command once for each nickname */ + if (!using_allnicks) + nick_command_parse (sess, cmd, nicks[i], ""); + + i++; + } + + if (using_allnicks) + { + if (!nick) + nick = ""; + nick_command_parse (sess, cmd, nick, allnicks); + } + + while (num_sel) + { + num_sel--; + g_free (nicks[num_sel]); + } + + free (nicks); + free (allnicks); +} + +/* a popup-menu-item has been selected */ + +static void +popup_menu_cb (GtkWidget * item, char *cmd) +{ + char *nick; + + /* the userdata is set in menu_quick_item() */ + nick = g_object_get_data (G_OBJECT (item), "u"); + + if (!nick) /* userlist popup menu */ + { + /* treat it just like a userlist button */ + userlist_button_cb (NULL, cmd); + return; + } + + if (!current_sess) /* for url grabber window */ + nick_command_parse (sess_list->data, cmd, nick, nick); + else + nick_command_parse (current_sess, cmd, nick, nick); +} + +GtkWidget * +menu_toggle_item (char *label, GtkWidget *menu, void *callback, void *userdata, + int state) +{ + GtkWidget *item; + + item = gtk_check_menu_item_new_with_mnemonic (label); + gtk_check_menu_item_set_active ((GtkCheckMenuItem*)item, state); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (callback), userdata); + gtk_widget_show (item); + + return item; +} + +GtkWidget * +menu_quick_item (char *cmd, char *label, GtkWidget * menu, int flags, + gpointer userdata, char *icon) +{ + GtkWidget *img, *item; + char *path; + + if (!label) + item = gtk_menu_item_new (); + else + { + if (icon) + { + /*if (flags & XCMENU_MARKUP) + item = gtk_image_menu_item_new_with_markup (label); + else*/ + item = gtk_image_menu_item_new_with_mnemonic (label); + img = NULL; + if (access (icon, R_OK) == 0) /* try fullpath */ + img = gtk_image_new_from_file (icon); + else + { + /* try relative to ~/.xchat2 */ + path = g_strdup_printf ("%s/%s", get_xdir_fs (), icon); + if (access (path, R_OK) == 0) + img = gtk_image_new_from_file (path); + else + img = gtk_image_new_from_stock (icon, GTK_ICON_SIZE_MENU); + g_free (path); + } + + if (img) + gtk_image_menu_item_set_image ((GtkImageMenuItem *)item, img); + } + else + { + if (flags & XCMENU_MARKUP) + { + item = gtk_menu_item_new_with_label (""); + if (flags & XCMENU_MNEMONIC) + gtk_label_set_markup_with_mnemonic (GTK_LABEL (GTK_BIN (item)->child), label); + else + gtk_label_set_markup (GTK_LABEL (GTK_BIN (item)->child), label); + } else + { + if (flags & XCMENU_MNEMONIC) + item = gtk_menu_item_new_with_mnemonic (label); + else + item = gtk_menu_item_new_with_label (label); + } + } + } + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + g_object_set_data (G_OBJECT (item), "u", userdata); + if (cmd) + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (popup_menu_cb), cmd); + if (flags & XCMENU_SHADED) + gtk_widget_set_sensitive (GTK_WIDGET (item), FALSE); + gtk_widget_show_all (item); + + return item; +} + +static void +menu_quick_item_with_callback (void *callback, char *label, GtkWidget * menu, + void *arg) +{ + GtkWidget *item; + + item = gtk_menu_item_new_with_label (label); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (callback), arg); + gtk_widget_show (item); +} + +GtkWidget * +menu_quick_sub (char *name, GtkWidget *menu, GtkWidget **sub_item_ret, int flags, int pos) +{ + GtkWidget *sub_menu; + GtkWidget *sub_item; + + if (!name) + return menu; + + /* Code to add a submenu */ + sub_menu = gtk_menu_new (); + if (flags & XCMENU_MARKUP) + { + sub_item = gtk_menu_item_new_with_label (""); + gtk_label_set_markup (GTK_LABEL (GTK_BIN (sub_item)->child), name); + } + else + { + if (flags & XCMENU_MNEMONIC) + sub_item = gtk_menu_item_new_with_mnemonic (name); + else + sub_item = gtk_menu_item_new_with_label (name); + } + gtk_menu_shell_insert (GTK_MENU_SHELL (menu), sub_item, pos); + gtk_widget_show (sub_item); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (sub_item), sub_menu); + + if (sub_item_ret) + *sub_item_ret = sub_item; + + if (flags & XCMENU_DOLIST) + /* We create a new element in the list */ + submenu_list = g_slist_prepend (submenu_list, sub_menu); + return sub_menu; +} + +static GtkWidget * +menu_quick_endsub () +{ + /* Just delete the first element in the linked list pointed to by first */ + if (submenu_list) + submenu_list = g_slist_remove (submenu_list, submenu_list->data); + + if (submenu_list) + return (submenu_list->data); + else + return NULL; +} + +static void +toggle_cb (GtkWidget *item, char *pref_name) +{ + char buf[256]; + + if (GTK_CHECK_MENU_ITEM (item)->active) + snprintf (buf, sizeof (buf), "set %s 1", pref_name); + else + snprintf (buf, sizeof (buf), "set %s 0", pref_name); + + handle_command (current_sess, buf, FALSE); +} + +static int +is_in_path (char *cmd) +{ + char *prog = strdup (cmd + 1); /* 1st char is "!" */ + char *space, *path, *orig; + + orig = prog; /* save for free()ing */ + /* special-case these default entries. */ + /* 123456789012345678 */ + if (strncmp (prog, "gnome-terminal -x ", 18) == 0) + /* don't check for gnome-terminal, but the thing it's executing! */ + prog += 18; + + space = strchr (prog, ' '); /* this isn't 100% but good enuf */ + if (space) + *space = 0; + + path = g_find_program_in_path (prog); + if (path) + { + g_free (path); + g_free (orig); + return 1; + } + + g_free (orig); + return 0; +} + +/* syntax: "LABEL~ICON~STUFF~ADDED~LATER~" */ + +static void +menu_extract_icon (char *name, char **label, char **icon) +{ + char *p = name; + char *start = NULL; + char *end = NULL; + + while (*p) + { + if (*p == '~') + { + /* escape \~ */ + if (p == name || p[-1] != '\\') + { + if (!start) + start = p + 1; + else if (!end) + end = p + 1; + } + } + p++; + } + + if (!end) + end = p; + + if (start && start != end) + { + *label = g_strndup (name, (start - name) - 1); + *icon = g_strndup (start, (end - start) - 1); + } + else + { + *label = g_strdup (name); + *icon = NULL; + } +} + +/* append items to "menu" using the (struct popup*) list provided */ + +void +menu_create (GtkWidget *menu, GSList *list, char *target, int check_path) +{ + struct popup *pop; + GtkWidget *tempmenu = menu, *subitem = NULL; + int childcount = 0; + + submenu_list = g_slist_prepend (0, menu); + while (list) + { + pop = (struct popup *) list->data; + + if (!strncasecmp (pop->name, "SUB", 3)) + { + childcount = 0; + tempmenu = menu_quick_sub (pop->cmd, tempmenu, &subitem, XCMENU_DOLIST|XCMENU_MNEMONIC, -1); + + } else if (!strncasecmp (pop->name, "TOGGLE", 6)) + { + childcount++; + menu_toggle_item (pop->name + 7, tempmenu, toggle_cb, pop->cmd, + cfg_get_bool (pop->cmd)); + + } else if (!strncasecmp (pop->name, "ENDSUB", 6)) + { + /* empty sub menu due to no programs in PATH? */ + if (check_path && childcount < 1) + gtk_widget_destroy (subitem); + subitem = NULL; + + if (tempmenu != menu) + tempmenu = menu_quick_endsub (); + /* If we get here and tempmenu equals menu that means we havent got any submenus to exit from */ + + } else if (!strncasecmp (pop->name, "SEP", 3)) + { + menu_quick_item (0, 0, tempmenu, XCMENU_SHADED, 0, 0); + + } else + { + char *icon, *label; + + /* default command in xchat.c */ + if (pop->cmd[0] == 'n' && !strcmp (pop->cmd, "notify -n ASK %s")) + { + /* don't create this item if already in notify list */ + if (!target || notify_is_in_list (current_sess->server, target)) + { + list = list->next; + continue; + } + } + + menu_extract_icon (pop->name, &label, &icon); + + if (!check_path || pop->cmd[0] != '!') + { + menu_quick_item (pop->cmd, label, tempmenu, 0, target, icon); + /* check if the program is in path, if not, leave it out! */ + } else if (is_in_path (pop->cmd)) + { + childcount++; + menu_quick_item (pop->cmd, label, tempmenu, 0, target, icon); + } + + g_free (label); + g_free (icon); + } + + list = list->next; + } + + /* Let's clean up the linked list from mem */ + while (submenu_list) + submenu_list = g_slist_remove (submenu_list, submenu_list->data); +} + +static char *str_copy = NULL; /* for all pop-up menus */ +static GtkWidget *nick_submenu = NULL; /* user info submenu */ + +static void +menu_destroy (GtkWidget *menu, gpointer objtounref) +{ + gtk_widget_destroy (menu); + g_object_unref (menu); + if (objtounref) + g_object_unref (G_OBJECT (objtounref)); + nick_submenu = NULL; +} + +static void +menu_popup (GtkWidget *menu, GdkEventButton *event, gpointer objtounref) +{ +#if (GTK_MAJOR_VERSION != 2) || (GTK_MINOR_VERSION != 0) + if (event && event->window) + gtk_menu_set_screen (GTK_MENU (menu), gdk_drawable_get_screen (event->window)); +#endif + + g_object_ref (menu); + g_object_ref_sink (menu); + g_object_unref (menu); + g_signal_connect (G_OBJECT (menu), "selection-done", + G_CALLBACK (menu_destroy), objtounref); + gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, + 0, event ? event->time : 0); +} + +static void +menu_nickinfo_cb (GtkWidget *menu, session *sess) +{ + char buf[512]; + + if (!is_session (sess)) + return; + + /* issue a /WHOIS */ + snprintf (buf, sizeof (buf), "WHOIS %s %s", str_copy, str_copy); + handle_command (sess, buf, FALSE); + /* and hide the output */ + sess->server->skip_next_whois = 1; +} + +static void +copy_to_clipboard_cb (GtkWidget *item, char *url) +{ + gtkutil_copy_to_clipboard (item, NULL, url); +} + +/* returns boolean: Some data is missing */ + +static gboolean +menu_create_nickinfo_menu (struct User *user, GtkWidget *submenu) +{ + char buf[512]; + char unknown[96]; + char *real, *fmt; + struct away_msg *away; + gboolean missing = FALSE; + GtkWidget *item; + + /* let the translators tweak this if need be */ + fmt = _("<tt><b>%-11s</b></tt> %s"); + snprintf (unknown, sizeof (unknown), "<i>%s</i>", _("Unknown")); + + if (user->realname) + { + real = strip_color (user->realname, -1, STRIP_ALL|STRIP_ESCMARKUP); + snprintf (buf, sizeof (buf), fmt, _("Real Name:"), real); + g_free (real); + } else + { + snprintf (buf, sizeof (buf), fmt, _("Real Name:"), unknown); + } + item = menu_quick_item (0, buf, submenu, XCMENU_MARKUP, 0, 0); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (copy_to_clipboard_cb), + user->realname ? user->realname : unknown); + + snprintf (buf, sizeof (buf), fmt, _("User:"), + user->hostname ? user->hostname : unknown); + item = menu_quick_item (0, buf, submenu, XCMENU_MARKUP, 0, 0); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (copy_to_clipboard_cb), + user->hostname ? user->hostname : unknown); + + snprintf (buf, sizeof (buf), fmt, _("Country:"), + user->hostname ? country(user->hostname) : unknown); + item = menu_quick_item (0, buf, submenu, XCMENU_MARKUP, 0, 0); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (copy_to_clipboard_cb), + user->hostname ? country(user->hostname) : unknown); + + snprintf (buf, sizeof (buf), fmt, _("Server:"), + user->servername ? user->servername : unknown); + item = menu_quick_item (0, buf, submenu, XCMENU_MARKUP, 0, 0); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (copy_to_clipboard_cb), + user->servername ? user->servername : unknown); + + if (user->lasttalk) + { + char min[96]; + + snprintf (min, sizeof (min), _("%u minutes ago"), + (unsigned int) ((time (0) - user->lasttalk) / 60)); + snprintf (buf, sizeof (buf), fmt, _("Last Msg:"), min); + } else + { + snprintf (buf, sizeof (buf), fmt, _("Last Msg:"), unknown); + } + menu_quick_item (0, buf, submenu, XCMENU_MARKUP, 0, 0); + + if (user->away) + { + away = server_away_find_message (current_sess->server, user->nick); + if (away) + { + char *msg = strip_color (away->message ? away->message : unknown, -1, STRIP_ALL|STRIP_ESCMARKUP); + snprintf (buf, sizeof (buf), fmt, _("Away Msg:"), msg); + g_free (msg); + item = menu_quick_item (0, buf, submenu, XCMENU_MARKUP, 0, 0); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (copy_to_clipboard_cb), + away->message ? away->message : unknown); + } + else + missing = TRUE; + } + + return missing; +} + +void +fe_userlist_update (session *sess, struct User *user) +{ + GList *items, *next; + + if (!nick_submenu || !str_copy) + return; + + /* not the same nick as the menu? */ + if (sess->server->p_cmp (user->nick, str_copy)) + return; + + /* get rid of the "show" signal */ + g_signal_handlers_disconnect_by_func (nick_submenu, menu_nickinfo_cb, sess); + + /* destroy all the old items */ + items = ((GtkMenuShell *) nick_submenu)->children; + while (items) + { + next = items->next; + gtk_widget_destroy (items->data); + items = next; + } + + /* and re-create them with new info */ + menu_create_nickinfo_menu (user, nick_submenu); +} + +void +menu_nickmenu (session *sess, GdkEventButton *event, char *nick, int num_sel) +{ + char buf[512]; + struct User *user; + GtkWidget *submenu, *menu = gtk_menu_new (); + + if (str_copy) + free (str_copy); + str_copy = strdup (nick); + + submenu_list = 0; /* first time through, might not be 0 */ + + /* more than 1 nick selected? */ + if (num_sel > 1) + { + snprintf (buf, sizeof (buf), _("%d nicks selected."), num_sel); + menu_quick_item (0, buf, menu, 0, 0, 0); + menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0); + } else + { + user = userlist_find (sess, nick); /* lasttalk is channel specific */ + if (!user) + user = userlist_find_global (current_sess->server, nick); + if (user) + { + nick_submenu = submenu = menu_quick_sub (nick, menu, NULL, XCMENU_DOLIST, -1); + + if (menu_create_nickinfo_menu (user, submenu) || + !user->hostname || !user->realname || !user->servername) + { + g_signal_connect (G_OBJECT (submenu), "show", G_CALLBACK (menu_nickinfo_cb), sess); + } + + menu_quick_endsub (); + menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0); + } + } + + if (num_sel > 1) + menu_create (menu, popup_list, NULL, FALSE); + else + menu_create (menu, popup_list, str_copy, FALSE); + + if (num_sel == 0) /* xtext click */ + menu_add_plugin_items (menu, "\x5$NICK", str_copy); + else /* userlist treeview click */ + menu_add_plugin_items (menu, "\x5$NICK", NULL); + + menu_popup (menu, event, NULL); +} + +/* stuff for the View menu */ + +static void +menu_showhide_cb (session *sess) +{ + if (prefs.hidemenu) + gtk_widget_hide (sess->gui->menu); + else + gtk_widget_show (sess->gui->menu); +} + +static void +menu_topic_showhide_cb (session *sess) +{ + if (prefs.topicbar) + gtk_widget_show (sess->gui->topic_bar); + else + gtk_widget_hide (sess->gui->topic_bar); +} + +static void +menu_userlist_showhide_cb (session *sess) +{ + mg_decide_userlist (sess, TRUE); +} + +static void +menu_ulbuttons_showhide_cb (session *sess) +{ + if (prefs.userlistbuttons) + gtk_widget_show (sess->gui->button_box); + else + gtk_widget_hide (sess->gui->button_box); +} + +static void +menu_cmbuttons_showhide_cb (session *sess) +{ + switch (sess->type) + { + case SESS_CHANNEL: + if (prefs.chanmodebuttons) + gtk_widget_show (sess->gui->topicbutton_box); + else + gtk_widget_hide (sess->gui->topicbutton_box); + break; + default: + gtk_widget_hide (sess->gui->topicbutton_box); + } +} + +static void +menu_setting_foreach (void (*callback) (session *), int id, guint state) +{ + session *sess; + GSList *list; + int maindone = FALSE; /* do it only once for EVERY tab */ + + list = sess_list; + while (list) + { + sess = list->data; + + if (!sess->gui->is_tab || !maindone) + { + if (sess->gui->is_tab) + maindone = TRUE; + if (id != -1) + GTK_CHECK_MENU_ITEM (sess->gui->menu_item[id])->active = state; + if (callback) + callback (sess); + } + + list = list->next; + } +} + +void +menu_bar_toggle (void) +{ + prefs.hidemenu = !prefs.hidemenu; + menu_setting_foreach (menu_showhide_cb, MENU_ID_MENUBAR, !prefs.hidemenu); +} + +static void +menu_bar_toggle_cb (void) +{ + menu_bar_toggle (); + if (prefs.hidemenu) + fe_message (_("The Menubar is now hidden. You can show it again" + " by pressing F9 or right-clicking in a blank part of" + " the main text area."), FE_MSG_INFO); +} + +static void +menu_topicbar_toggle (GtkWidget *wid, gpointer ud) +{ + prefs.topicbar = !prefs.topicbar; + menu_setting_foreach (menu_topic_showhide_cb, MENU_ID_TOPICBAR, + prefs.topicbar); +} + +static void +menu_userlist_toggle (GtkWidget *wid, gpointer ud) +{ + prefs.hideuserlist = !prefs.hideuserlist; + menu_setting_foreach (menu_userlist_showhide_cb, MENU_ID_USERLIST, + !prefs.hideuserlist); +} + +static void +menu_ulbuttons_toggle (GtkWidget *wid, gpointer ud) +{ + prefs.userlistbuttons = !prefs.userlistbuttons; + menu_setting_foreach (menu_ulbuttons_showhide_cb, MENU_ID_ULBUTTONS, + prefs.userlistbuttons); +} + +static void +menu_cmbuttons_toggle (GtkWidget *wid, gpointer ud) +{ + prefs.chanmodebuttons = !prefs.chanmodebuttons; + menu_setting_foreach (menu_cmbuttons_showhide_cb, MENU_ID_MODEBUTTONS, + prefs.chanmodebuttons); +} + +void +menu_middlemenu (session *sess, GdkEventButton *event) +{ + GtkWidget *menu; + GtkAccelGroup *accel_group; + + accel_group = gtk_accel_group_new (); + menu = menu_create_main (accel_group, FALSE, sess->server->is_away, !sess->gui->is_tab, NULL); + menu_popup (menu, event, accel_group); +} + +static void +open_url_cb (GtkWidget *item, char *url) +{ + char buf[512]; + + /* pass this to /URL so it can handle irc:// */ + snprintf (buf, sizeof (buf), "URL %s", url); + handle_command (current_sess, buf, FALSE); +} + +void +menu_urlmenu (GdkEventButton *event, char *url) +{ + GtkWidget *menu; + char *tmp, *chop; + + if (str_copy) + free (str_copy); + str_copy = strdup (url); + + menu = gtk_menu_new (); + /* more than 51 chars? Chop it */ + if (g_utf8_strlen (str_copy, -1) >= 52) + { + tmp = strdup (str_copy); + chop = g_utf8_offset_to_pointer (tmp, 48); + chop[0] = chop[1] = chop[2] = '.'; + chop[3] = 0; + menu_quick_item (0, tmp, menu, XCMENU_SHADED, 0, 0); + free (tmp); + } else + { + menu_quick_item (0, str_copy, menu, XCMENU_SHADED, 0, 0); + } + menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0); + + /* Two hardcoded entries */ + if (strncmp (str_copy, "irc://", 6) == 0 || + strncmp (str_copy, "ircs://",7) == 0) + menu_quick_item_with_callback (open_url_cb, _("Connect"), menu, str_copy); + else + menu_quick_item_with_callback (open_url_cb, _("Open Link in Browser"), menu, str_copy); + menu_quick_item_with_callback (copy_to_clipboard_cb, _("Copy Selected Link"), menu, str_copy); + /* custom ones from urlhandlers.conf */ + menu_create (menu, urlhandler_list, str_copy, TRUE); + menu_add_plugin_items (menu, "\x4$URL", str_copy); + menu_popup (menu, event, NULL); +} + +static void +menu_chan_cycle (GtkWidget * menu, char *chan) +{ + char tbuf[256]; + + if (current_sess) + { + snprintf (tbuf, sizeof tbuf, "CYCLE %s", chan); + handle_command (current_sess, tbuf, FALSE); + } +} + +static void +menu_chan_part (GtkWidget * menu, char *chan) +{ + char tbuf[256]; + + if (current_sess) + { + snprintf (tbuf, sizeof tbuf, "part %s", chan); + handle_command (current_sess, tbuf, FALSE); + } +} + +static void +menu_chan_join (GtkWidget * menu, char *chan) +{ + char tbuf[256]; + + if (current_sess) + { + snprintf (tbuf, sizeof tbuf, "join %s", chan); + handle_command (current_sess, tbuf, FALSE); + } +} + +void +menu_chanmenu (struct session *sess, GdkEventButton * event, char *chan) +{ + GtkWidget *menu; + int is_joined = FALSE; + + if (find_channel (sess->server, chan)) + is_joined = TRUE; + + if (str_copy) + free (str_copy); + str_copy = strdup (chan); + + menu = gtk_menu_new (); + + menu_quick_item (0, chan, menu, XCMENU_SHADED, str_copy, 0); + menu_quick_item (0, 0, menu, XCMENU_SHADED, str_copy, 0); + + if (!is_joined) + menu_quick_item_with_callback (menu_chan_join, _("Join Channel"), menu, + str_copy); + else + { + menu_quick_item_with_callback (menu_chan_part, _("Part Channel"), menu, + str_copy); + menu_quick_item_with_callback (menu_chan_cycle, _("Cycle Channel"), menu, + str_copy); + } + + menu_addfavoritemenu (sess->server, menu, str_copy); + + menu_add_plugin_items (menu, "\x5$CHAN", str_copy); + menu_popup (menu, event, NULL); +} + +static void +menu_delfav_cb (GtkWidget *item, server *serv) +{ + servlist_autojoinedit (serv->network, str_copy, FALSE); +} + +static void +menu_addfav_cb (GtkWidget *item, server *serv) +{ + servlist_autojoinedit (serv->network, str_copy, TRUE); +} + +void +menu_addfavoritemenu (server *serv, GtkWidget *menu, char *channel) +{ + if (!serv->network) + return; + + if (channel != str_copy) + { + if (str_copy) + free (str_copy); + str_copy = strdup (channel); + } + + if (joinlist_is_in_list (serv, channel)) + mg_create_icon_item (_("_Remove from Favorites"), GTK_STOCK_REMOVE, menu, menu_delfav_cb, serv); + else + mg_create_icon_item (_("_Add to Favorites"), GTK_STOCK_ADD, menu, menu_addfav_cb, serv); +} + +static void +menu_open_server_list (GtkWidget *wid, gpointer none) +{ + fe_serverlist_open (current_sess); +} + +static void +menu_settings (GtkWidget * wid, gpointer none) +{ + extern void setup_open (void); + setup_open (); +} + +static void +menu_usermenu (void) +{ + editlist_gui_open (NULL, NULL, usermenu_list, _("XChat: User menu"), + "usermenu", "usermenu.conf", 0); +} + +static void +usermenu_create (GtkWidget *menu) +{ + menu_create (menu, usermenu_list, "", FALSE); + menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0); /* sep */ + menu_quick_item_with_callback (menu_usermenu, _("Edit This Menu..."), menu, 0); +} + +static void +usermenu_destroy (GtkWidget * menu) +{ + GList *items = ((GtkMenuShell *) menu)->children; + GList *next; + + while (items) + { + next = items->next; + gtk_widget_destroy (items->data); + items = next; + } +} + +void +usermenu_update (void) +{ + int done_main = FALSE; + GSList *list = sess_list; + session *sess; + GtkWidget *menu; + + while (list) + { + sess = list->data; + menu = sess->gui->menu_item[MENU_ID_USERMENU]; + if (sess->gui->is_tab) + { + if (!done_main && menu) + { + usermenu_destroy (menu); + usermenu_create (menu); + done_main = TRUE; + } + } else if (menu) + { + usermenu_destroy (menu); + usermenu_create (menu); + } + list = list->next; + } +} + +static void +menu_newserver_window (GtkWidget * wid, gpointer none) +{ + int old = prefs.tabchannels; + + prefs.tabchannels = 0; + new_ircwindow (NULL, NULL, SESS_SERVER, 0); + prefs.tabchannels = old; +} + +static void +menu_newchannel_window (GtkWidget * wid, gpointer none) +{ + int old = prefs.tabchannels; + + prefs.tabchannels = 0; + new_ircwindow (current_sess->server, NULL, SESS_CHANNEL, 0); + prefs.tabchannels = old; +} + +static void +menu_newserver_tab (GtkWidget * wid, gpointer none) +{ + int old = prefs.tabchannels; + int oldf = prefs.newtabstofront; + + prefs.tabchannels = 1; + /* force focus if setting is "only requested tabs" */ + if (prefs.newtabstofront == 2) + prefs.newtabstofront = 1; + new_ircwindow (NULL, NULL, SESS_SERVER, 0); + prefs.tabchannels = old; + prefs.newtabstofront = oldf; +} + +static void +menu_newchannel_tab (GtkWidget * wid, gpointer none) +{ + int old = prefs.tabchannels; + + prefs.tabchannels = 1; + new_ircwindow (current_sess->server, NULL, SESS_CHANNEL, 0); + prefs.tabchannels = old; +} + +static void +menu_rawlog (GtkWidget * wid, gpointer none) +{ + open_rawlog (current_sess->server); +} + +static void +menu_detach (GtkWidget * wid, gpointer none) +{ + mg_detach (current_sess, 0); +} + +static void +menu_close (GtkWidget * wid, gpointer none) +{ + mg_close_sess (current_sess); +} + +static void +menu_quit (GtkWidget * wid, gpointer none) +{ + mg_open_quit_dialog (FALSE); +} + +static void +menu_search () +{ + search_open (current_sess); +} + +static void +menu_resetmarker (GtkWidget * wid, gpointer none) +{ + gtk_xtext_reset_marker_pos (GTK_XTEXT (current_sess->gui->xtext)); +} + +static void +menu_flushbuffer (GtkWidget * wid, gpointer none) +{ + fe_text_clear (current_sess, 0); +} + +static void +savebuffer_req_done (session *sess, char *file) +{ + int fh; + + if (!file) + return; + + fh = open (file, O_TRUNC | O_WRONLY | O_CREAT, 0600); + if (fh != -1) + { + gtk_xtext_save (GTK_XTEXT (sess->gui->xtext), fh); + close (fh); + } +} + +static void +menu_savebuffer (GtkWidget * wid, gpointer none) +{ + gtkutil_file_req (_("Select an output filename"), savebuffer_req_done, + current_sess, NULL, FRF_WRITE); +} + +static void +menu_disconnect (GtkWidget * wid, gpointer none) +{ + handle_command (current_sess, "DISCON", FALSE); +} + +static void +menu_reconnect (GtkWidget * wid, gpointer none) +{ + if (current_sess->server->hostname[0]) + handle_command (current_sess, "RECONNECT", FALSE); + else + fe_serverlist_open (current_sess); +} + +static void +menu_join_cb (GtkWidget *dialog, gint response, GtkEntry *entry) +{ + switch (response) + { + case GTK_RESPONSE_ACCEPT: + menu_chan_join (NULL, entry->text); + break; + + case GTK_RESPONSE_HELP: + chanlist_opengui (current_sess->server, TRUE); + break; + } + + gtk_widget_destroy (dialog); +} + +static void +menu_join_entry_cb (GtkWidget *entry, GtkDialog *dialog) +{ + gtk_dialog_response (dialog, GTK_RESPONSE_ACCEPT); +} + +static void +menu_join (GtkWidget * wid, gpointer none) +{ + GtkWidget *hbox, *dialog, *entry, *label; + + dialog = gtk_dialog_new_with_buttons (_("Join Channel"), + GTK_WINDOW (parent_window), 0, + _("Retrieve channel list..."), GTK_RESPONSE_HELP, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + NULL); + gtk_box_set_homogeneous (GTK_BOX (GTK_DIALOG (dialog)->vbox), TRUE); + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + hbox = gtk_hbox_new (TRUE, 0); + + entry = gtk_entry_new (); + GTK_ENTRY (entry)->editable = 0; /* avoid auto-selection */ + gtk_entry_set_text (GTK_ENTRY (entry), "#"); + g_signal_connect (G_OBJECT (entry), "activate", + G_CALLBACK (menu_join_entry_cb), dialog); + gtk_box_pack_end (GTK_BOX (hbox), entry, 0, 0, 0); + + label = gtk_label_new (_("Enter Channel to Join:")); + gtk_box_pack_end (GTK_BOX (hbox), label, 0, 0, 0); + + g_signal_connect (G_OBJECT (dialog), "response", + G_CALLBACK (menu_join_cb), entry); + + gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), hbox); + + gtk_widget_show_all (dialog); + + gtk_editable_set_editable (GTK_EDITABLE (entry), TRUE); + gtk_editable_set_position (GTK_EDITABLE (entry), 1); +} + +static void +menu_away (GtkCheckMenuItem *item, gpointer none) +{ + handle_command (current_sess, item->active ? "away" : "back", FALSE); +} + +static void +menu_chanlist (GtkWidget * wid, gpointer none) +{ + chanlist_opengui (current_sess->server, FALSE); +} + +static void +menu_banlist (GtkWidget * wid, gpointer none) +{ + banlist_opengui (current_sess); +} + +#ifdef USE_PLUGIN + +static void +menu_loadplugin (void) +{ + plugingui_load (); +} + +static void +menu_pluginlist (void) +{ + plugingui_open (); +} + +#else + +#define menu_pluginlist 0 +#define menu_loadplugin 0 + +#endif + +#define usercommands_help _("User Commands - Special codes:\n\n"\ + "%c = current channel\n"\ + "%e = current network name\n"\ + "%m = machine info\n"\ + "%n = your nick\n"\ + "%t = time/date\n"\ + "%v = xchat version\n"\ + "%2 = word 2\n"\ + "%3 = word 3\n"\ + "&2 = word 2 to the end of line\n"\ + "&3 = word 3 to the end of line\n\n"\ + "eg:\n"\ + "/cmd john hello\n\n"\ + "%2 would be \042john\042\n"\ + "&2 would be \042john hello\042.") + +#define ulbutton_help _("Userlist Buttons - Special codes:\n\n"\ + "%a = all selected nicks\n"\ + "%c = current channel\n"\ + "%e = current network name\n"\ + "%h = selected nick's hostname\n"\ + "%m = machine info\n"\ + "%n = your nick\n"\ + "%s = selected nick\n"\ + "%t = time/date\n") + +#define dlgbutton_help _("Dialog Buttons - Special codes:\n\n"\ + "%a = all selected nicks\n"\ + "%c = current channel\n"\ + "%e = current network name\n"\ + "%h = selected nick's hostname\n"\ + "%m = machine info\n"\ + "%n = your nick\n"\ + "%s = selected nick\n"\ + "%t = time/date\n") + +#define ctcp_help _("CTCP Replies - Special codes:\n\n"\ + "%d = data (the whole ctcp)\n"\ + "%e = current network name\n"\ + "%m = machine info\n"\ + "%s = nick who sent the ctcp\n"\ + "%t = time/date\n"\ + "%2 = word 2\n"\ + "%3 = word 3\n"\ + "&2 = word 2 to the end of line\n"\ + "&3 = word 3 to the end of line\n\n") + +#define url_help _("URL Handlers - Special codes:\n\n"\ + "%s = the URL string\n\n"\ + "Putting a ! infront of the command\n"\ + "indicates it should be sent to a\n"\ + "shell instead of XChat") + +static void +menu_usercommands (void) +{ + editlist_gui_open (NULL, NULL, command_list, _("XChat: User Defined Commands"), + "commands", "commands.conf", usercommands_help); +} + +static void +menu_ulpopup (void) +{ + editlist_gui_open (NULL, NULL, popup_list, _("XChat: Userlist Popup menu"), "popup", + "popup.conf", ulbutton_help); +} + +static void +menu_rpopup (void) +{ + editlist_gui_open (_("Text"), _("Replace with"), replace_list, _("XChat: Replace"), "replace", + "replace.conf", 0); +} + +static void +menu_urlhandlers (void) +{ + editlist_gui_open (NULL, NULL, urlhandler_list, _("XChat: URL Handlers"), "urlhandlers", + "urlhandlers.conf", url_help); +} + +static void +menu_evtpopup (void) +{ + pevent_dialog_show (); +} + +static void +menu_keypopup (void) +{ + key_dialog_show (); +} + +static void +menu_ulbuttons (void) +{ + editlist_gui_open (NULL, NULL, button_list, _("XChat: Userlist buttons"), "buttons", + "buttons.conf", ulbutton_help); +} + +static void +menu_dlgbuttons (void) +{ + editlist_gui_open (NULL, NULL, dlgbutton_list, _("XChat: Dialog buttons"), "dlgbuttons", + "dlgbuttons.conf", dlgbutton_help); +} + +static void +menu_ctcpguiopen (void) +{ + editlist_gui_open (NULL, NULL, ctcp_list, _("XChat: CTCP Replies"), "ctcpreply", + "ctcpreply.conf", ctcp_help); +} + +static void +menu_docs (GtkWidget *wid, gpointer none) +{ + fe_open_url ("http://xchat.org/docs/"); +} + +/*static void +menu_webpage (GtkWidget *wid, gpointer none) +{ + fe_open_url ("http://xchat.org"); +}*/ + +static void +menu_dcc_win (GtkWidget *wid, gpointer none) +{ + fe_dcc_open_recv_win (FALSE); + fe_dcc_open_send_win (FALSE); +} + +static void +menu_dcc_chat_win (GtkWidget *wid, gpointer none) +{ + fe_dcc_open_chat_win (FALSE); +} + +void +menu_change_layout (void) +{ + if (prefs.tab_layout == 0) + { + menu_setting_foreach (NULL, MENU_ID_LAYOUT_TABS, 1); + menu_setting_foreach (NULL, MENU_ID_LAYOUT_TREE, 0); + mg_change_layout (0); + } else + { + menu_setting_foreach (NULL, MENU_ID_LAYOUT_TABS, 0); + menu_setting_foreach (NULL, MENU_ID_LAYOUT_TREE, 1); + mg_change_layout (2); + } +} + +static void +menu_layout_cb (GtkWidget *item, gpointer none) +{ + prefs.tab_layout = 2; + if (GTK_CHECK_MENU_ITEM (item)->active) + prefs.tab_layout = 0; + + menu_change_layout (); +} + +static void +menu_apply_metres_cb (session *sess) +{ + mg_update_meters (sess->gui); +} + +static void +menu_metres_off (GtkWidget *item, gpointer none) +{ + if (GTK_CHECK_MENU_ITEM (item)->active) + { + prefs.lagometer = 0; + prefs.throttlemeter = 0; + menu_setting_foreach (menu_apply_metres_cb, -1, 0); + } +} + +static void +menu_metres_text (GtkWidget *item, gpointer none) +{ + if (GTK_CHECK_MENU_ITEM (item)->active) + { + prefs.lagometer = 2; + prefs.throttlemeter = 2; + menu_setting_foreach (menu_apply_metres_cb, -1, 0); + } +} + +static void +menu_metres_graph (GtkWidget *item, gpointer none) +{ + if (GTK_CHECK_MENU_ITEM (item)->active) + { + prefs.lagometer = 1; + prefs.throttlemeter = 1; + menu_setting_foreach (menu_apply_metres_cb, -1, 0); + } +} + +static void +menu_metres_both (GtkWidget *item, gpointer none) +{ + if (GTK_CHECK_MENU_ITEM (item)->active) + { + prefs.lagometer = 3; + prefs.throttlemeter = 3; + menu_setting_foreach (menu_apply_metres_cb, -1, 0); + } +} + +static struct mymenu mymenu[] = { + {N_("_XChat"), 0, 0, M_NEWMENU, 0, 0, 1}, + {N_("Network Li_st..."), menu_open_server_list, (char *)&pix_book, M_MENUPIX, 0, 0, 1, GDK_s}, + {0, 0, 0, M_SEP, 0, 0, 0}, + + {N_("_New"), 0, GTK_STOCK_NEW, M_MENUSUB, 0, 0, 1}, + {N_("Server Tab..."), menu_newserver_tab, 0, M_MENUITEM, 0, 0, 1, GDK_t}, + {N_("Channel Tab..."), menu_newchannel_tab, 0, M_MENUITEM, 0, 0, 1}, + {N_("Server Window..."), menu_newserver_window, 0, M_MENUITEM, 0, 0, 1}, + {N_("Channel Window..."), menu_newchannel_window, 0, M_MENUITEM, 0, 0, 1}, + {0, 0, 0, M_END, 0, 0, 0}, + {0, 0, 0, M_SEP, 0, 0, 0}, + +#ifdef USE_PLUGIN + {N_("_Load Plugin or Script..."), menu_loadplugin, GTK_STOCK_REVERT_TO_SAVED, M_MENUSTOCK, 0, 0, 1}, +#else + {N_("_Load Plugin or Script..."), 0, GTK_STOCK_REVERT_TO_SAVED, M_MENUSTOCK, 0, 0, 0}, +#endif + {0, 0, 0, M_SEP, 0, 0, 0}, /* 11 */ +#define DETACH_OFFSET (12) + {0, menu_detach, GTK_STOCK_REDO, M_MENUSTOCK, 0, 0, 1, GDK_I}, /* 12 */ +#define CLOSE_OFFSET (13) + {0, menu_close, GTK_STOCK_CLOSE, M_MENUSTOCK, 0, 0, 1, GDK_w}, + {0, 0, 0, M_SEP, 0, 0, 0}, + {N_("_Quit"), menu_quit, GTK_STOCK_QUIT, M_MENUSTOCK, 0, 0, 1, GDK_q}, /* 15 */ + + {N_("_View"), 0, 0, M_NEWMENU, 0, 0, 1}, +#define MENUBAR_OFFSET (17) + {N_("_Menu Bar"), menu_bar_toggle_cb, 0, M_MENUTOG, MENU_ID_MENUBAR, 0, 1, GDK_F9}, + {N_("_Topic Bar"), menu_topicbar_toggle, 0, M_MENUTOG, MENU_ID_TOPICBAR, 0, 1}, + {N_("_User List"), menu_userlist_toggle, 0, M_MENUTOG, MENU_ID_USERLIST, 0, 1, GDK_F7}, + {N_("U_serlist Buttons"), menu_ulbuttons_toggle, 0, M_MENUTOG, MENU_ID_ULBUTTONS, 0, 1}, + {N_("M_ode Buttons"), menu_cmbuttons_toggle, 0, M_MENUTOG, MENU_ID_MODEBUTTONS, 0, 1}, + {0, 0, 0, M_SEP, 0, 0, 0}, + {N_("_Channel Switcher"), 0, 0, M_MENUSUB, 0, 0, 1}, /* 23 */ +#define TABS_OFFSET (24) + {N_("_Tabs"), menu_layout_cb, 0, M_MENURADIO, MENU_ID_LAYOUT_TABS, 0, 1}, + {N_("T_ree"), 0, 0, M_MENURADIO, MENU_ID_LAYOUT_TREE, 0, 1}, + {0, 0, 0, M_END, 0, 0, 0}, + {N_("_Network Meters"), 0, 0, M_MENUSUB, 0, 0, 1}, /* 27 */ +#define METRE_OFFSET (28) + {N_("Off"), menu_metres_off, 0, M_MENURADIO, 0, 0, 1}, + {N_("Graph"), menu_metres_graph, 0, M_MENURADIO, 0, 0, 1}, + {N_("Text"), menu_metres_text, 0, M_MENURADIO, 0, 0, 1}, + {N_("Both"), menu_metres_both, 0, M_MENURADIO, 0, 0, 1}, + {0, 0, 0, M_END, 0, 0, 0}, /* 32 */ + + {N_("_Server"), 0, 0, M_NEWMENU, 0, 0, 1}, + {N_("_Disconnect"), menu_disconnect, GTK_STOCK_DISCONNECT, M_MENUSTOCK, MENU_ID_DISCONNECT, 0, 1}, + {N_("_Reconnect"), menu_reconnect, GTK_STOCK_CONNECT, M_MENUSTOCK, MENU_ID_RECONNECT, 0, 1}, + {N_("Join a Channel..."), menu_join, GTK_STOCK_JUMP_TO, M_MENUSTOCK, MENU_ID_JOIN, 0, 1}, + {N_("List of Channels..."), menu_chanlist, GTK_STOCK_INDEX, M_MENUITEM, 0, 0, 1}, + {0, 0, 0, M_SEP, 0, 0, 0}, +#define AWAY_OFFSET (39) + {N_("Marked Away"), menu_away, 0, M_MENUTOG, MENU_ID_AWAY, 0, 1, GDK_a}, + + {N_("_Usermenu"), 0, 0, M_NEWMENU, MENU_ID_USERMENU, 0, 1}, /* 40 */ + + {N_("S_ettings"), 0, 0, M_NEWMENU, 0, 0, 1}, + {N_("_Preferences"), menu_settings, GTK_STOCK_PREFERENCES, M_MENUSTOCK, 0, 0, 1}, + + {N_("Advanced"), 0, GTK_STOCK_JUSTIFY_LEFT, M_MENUSUB, 0, 0, 1}, + {N_("Auto Replace..."), menu_rpopup, 0, M_MENUITEM, 0, 0, 1}, + {N_("CTCP Replies..."), menu_ctcpguiopen, 0, M_MENUITEM, 0, 0, 1}, + {N_("Dialog Buttons..."), menu_dlgbuttons, 0, M_MENUITEM, 0, 0, 1}, + {N_("Keyboard Shortcuts..."), menu_keypopup, 0, M_MENUITEM, 0, 0, 1}, + {N_("Text Events..."), menu_evtpopup, 0, M_MENUITEM, 0, 0, 1}, + {N_("URL Handlers..."), menu_urlhandlers, 0, M_MENUITEM, 0, 0, 1}, + {N_("User Commands..."), menu_usercommands, 0, M_MENUITEM, 0, 0, 1}, + {N_("Userlist Buttons..."), menu_ulbuttons, 0, M_MENUITEM, 0, 0, 1}, + {N_("Userlist Popup..."), menu_ulpopup, 0, M_MENUITEM, 0, 0, 1}, + {0, 0, 0, M_END, 0, 0, 0}, /* 53 */ + + {N_("_Window"), 0, 0, M_NEWMENU, 0, 0, 1}, + {N_("Ban List..."), menu_banlist, 0, M_MENUITEM, 0, 0, 1}, + {N_("Character Chart..."), ascii_open, 0, M_MENUITEM, 0, 0, 1}, + {N_("Direct Chat..."), menu_dcc_chat_win, 0, M_MENUITEM, 0, 0, 1}, + {N_("File Transfers..."), menu_dcc_win, 0, M_MENUITEM, 0, 0, 1}, + {N_("Friends List..."), notify_opengui, 0, M_MENUITEM, 0, 0, 1}, + {N_("Ignore List..."), ignore_gui_open, 0, M_MENUITEM, 0, 0, 1}, + {N_("Plugins and Scripts..."), menu_pluginlist, 0, M_MENUITEM, 0, 0, 1}, + {N_("Raw Log..."), menu_rawlog, 0, M_MENUITEM, 0, 0, 1}, /* 62 */ + {N_("URL Grabber..."), url_opengui, 0, M_MENUITEM, 0, 0, 1}, + {0, 0, 0, M_SEP, 0, 0, 0}, + {N_("Reset Marker Line"), menu_resetmarker, 0, M_MENUITEM, 0, 0, 1, GDK_m}, + {N_("C_lear Text"), menu_flushbuffer, GTK_STOCK_CLEAR, M_MENUSTOCK, 0, 0, 1, GDK_l}, +#define SEARCH_OFFSET 67 + {N_("Search Text..."), menu_search, GTK_STOCK_FIND, M_MENUSTOCK, 0, 0, 1, GDK_f}, + {N_("Save Text..."), menu_savebuffer, GTK_STOCK_SAVE, M_MENUSTOCK, 0, 0, 1}, + + {N_("_Help"), 0, 0, M_NEWMENU, 0, 0, 1}, /* 69 */ + {N_("_Contents"), menu_docs, GTK_STOCK_HELP, M_MENUSTOCK, 0, 0, 1, GDK_F1}, +#if 0 + {N_("Check for updates"), menu_update, 0, M_MENUITEM, 0, 1}, +#endif + {N_("_About"), menu_about, GTK_STOCK_ABOUT, M_MENUSTOCK, 0, 0, 1}, + + {0, 0, 0, M_END, 0, 0, 0}, +}; + +GtkWidget * +create_icon_menu (char *labeltext, void *stock_name, int is_stock) +{ + GtkWidget *item, *img; + + if (is_stock) + img = gtk_image_new_from_stock (stock_name, GTK_ICON_SIZE_MENU); + else + img = gtk_image_new_from_pixbuf (*((GdkPixbuf **)stock_name)); + item = gtk_image_menu_item_new_with_mnemonic (labeltext); + gtk_image_menu_item_set_image ((GtkImageMenuItem *)item, img); + gtk_widget_show (img); + + return item; +} + +#if GTK_CHECK_VERSION(2,4,0) + +/* Override the default GTK2.4 handler, which would make menu + bindings not work when the menu-bar is hidden. */ +static gboolean +menu_canacaccel (GtkWidget *widget, guint signal_id, gpointer user_data) +{ + /* GTK2.2 behaviour */ +#if GTK_CHECK_VERSION(2,20,0) + return gtk_widget_is_sensitive (widget); +#else + return GTK_WIDGET_IS_SENSITIVE (widget); +#endif +} + +#endif + + +/* === STUFF FOR /MENU === */ + +static GtkMenuItem * +menu_find_item (GtkWidget *menu, char *name) +{ + GList *items = ((GtkMenuShell *) menu)->children; + GtkMenuItem *item; + GtkWidget *child; + const char *labeltext; + + while (items) + { + item = items->data; + child = GTK_BIN (item)->child; + if (child) /* separators arn't labels, skip them */ + { + labeltext = g_object_get_data (G_OBJECT (item), "name"); + if (!labeltext) + labeltext = gtk_label_get_text (GTK_LABEL (child)); + if (!menu_streq (labeltext, name, 1)) + return item; + } else if (name == NULL) + { + return item; + } + items = items->next; + } + + return NULL; +} + +static GtkWidget * +menu_find_path (GtkWidget *menu, char *path) +{ + GtkMenuItem *item; + char *s; + char name[128]; + int len; + + /* grab the next part of the path */ + s = strchr (path, '/'); + len = s - path; + if (!s) + len = strlen (path); + len = MIN (len, sizeof (name) - 1); + memcpy (name, path, len); + name[len] = 0; + + item = menu_find_item (menu, name); + if (!item) + return NULL; + + menu = gtk_menu_item_get_submenu (item); + if (!menu) + return NULL; + + path += len; + if (*path == 0) + return menu; + + return menu_find_path (menu, path + 1); +} + +static GtkWidget * +menu_find (GtkWidget *menu, char *path, char *label) +{ + GtkWidget *item = NULL; + + if (path[0] != 0) + menu = menu_find_path (menu, path); + if (menu) + item = (GtkWidget *)menu_find_item (menu, label); + return item; +} + +static void +menu_foreach_gui (menu_entry *me, void (*callback) (GtkWidget *, menu_entry *, char *)) +{ + GSList *list = sess_list; + int tabdone = FALSE; + session *sess; + + if (!me->is_main) + return; /* not main menu */ + + while (list) + { + sess = list->data; + /* do it only once for tab sessions, since they share a GUI */ + if (!sess->gui->is_tab || !tabdone) + { + callback (sess->gui->menu, me, NULL); + if (sess->gui->is_tab) + tabdone = TRUE; + } + list = list->next; + } +} + +static void +menu_update_cb (GtkWidget *menu, menu_entry *me, char *target) +{ + GtkWidget *item; + + item = menu_find (menu, me->path, me->label); + if (item) + { + gtk_widget_set_sensitive (item, me->enable); + /* must do it without triggering the callback */ + if (GTK_IS_CHECK_MENU_ITEM (item)) + GTK_CHECK_MENU_ITEM (item)->active = me->state; + } +} + +/* radio state changed via mouse click */ +static void +menu_radio_cb (GtkCheckMenuItem *item, menu_entry *me) +{ + me->state = 0; + if (item->active) + me->state = 1; + + /* update the state, incase this was changed via right-click. */ + /* This will update all other windows and menu bars */ + menu_foreach_gui (me, menu_update_cb); + + if (me->state && me->cmd) + handle_command (current_sess, me->cmd, FALSE); +} + +/* toggle state changed via mouse click */ +static void +menu_toggle_cb (GtkCheckMenuItem *item, menu_entry *me) +{ + me->state = 0; + if (item->active) + me->state = 1; + + /* update the state, incase this was changed via right-click. */ + /* This will update all other windows and menu bars */ + menu_foreach_gui (me, menu_update_cb); + + if (me->state) + handle_command (current_sess, me->cmd, FALSE); + else + handle_command (current_sess, me->ucmd, FALSE); +} + +static GtkWidget * +menu_radio_item (char *label, GtkWidget *menu, void *callback, void *userdata, + int state, char *groupname) +{ + GtkWidget *item; + GtkMenuItem *parent; + GSList *grouplist = NULL; + + parent = menu_find_item (menu, groupname); + if (parent) + grouplist = gtk_radio_menu_item_get_group ((GtkRadioMenuItem *)parent); + + item = gtk_radio_menu_item_new_with_label (grouplist, label); + gtk_check_menu_item_set_active ((GtkCheckMenuItem*)item, state); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (callback), userdata); + gtk_widget_show (item); + + return item; +} + +static void +menu_reorder (GtkMenu *menu, GtkWidget *item, int pos) +{ + if (pos == 0xffff) /* outbound.c uses this default */ + return; + + if (pos < 0) /* position offset from end/bottom */ + gtk_menu_reorder_child (menu, item, (g_list_length (GTK_MENU_SHELL (menu)->children) + pos) - 1); + else + gtk_menu_reorder_child (menu, item, pos); +} + +static GtkWidget * +menu_add_radio (GtkWidget *menu, menu_entry *me) +{ + GtkWidget *item = NULL; + char *path = me->path + me->root_offset; + + if (path[0] != 0) + menu = menu_find_path (menu, path); + if (menu) + { + item = menu_radio_item (me->label, menu, menu_radio_cb, me, me->state, me->group); + menu_reorder (GTK_MENU (menu), item, me->pos); + } + return item; +} + +static GtkWidget * +menu_add_toggle (GtkWidget *menu, menu_entry *me) +{ + GtkWidget *item = NULL; + char *path = me->path + me->root_offset; + + if (path[0] != 0) + menu = menu_find_path (menu, path); + if (menu) + { + item = menu_toggle_item (me->label, menu, menu_toggle_cb, me, me->state); + menu_reorder (GTK_MENU (menu), item, me->pos); + } + return item; +} + +static GtkWidget * +menu_add_item (GtkWidget *menu, menu_entry *me, char *target) +{ + GtkWidget *item = NULL; + char *path = me->path + me->root_offset; + + if (path[0] != 0) + menu = menu_find_path (menu, path); + if (menu) + { + item = menu_quick_item (me->cmd, me->label, menu, me->markup ? XCMENU_MARKUP|XCMENU_MNEMONIC : XCMENU_MNEMONIC, target, me->icon); + menu_reorder (GTK_MENU (menu), item, me->pos); + } + return item; +} + +static GtkWidget * +menu_add_sub (GtkWidget *menu, menu_entry *me) +{ + GtkWidget *item = NULL; + char *path = me->path + me->root_offset; + int pos; + + if (path[0] != 0) + menu = menu_find_path (menu, path); + if (menu) + { + pos = me->pos; + if (pos < 0) /* position offset from end/bottom */ + pos = g_list_length (GTK_MENU_SHELL (menu)->children) + pos; + menu_quick_sub (me->label, menu, &item, me->markup ? XCMENU_MARKUP|XCMENU_MNEMONIC : XCMENU_MNEMONIC, pos); + } + return item; +} + +static void +menu_del_cb (GtkWidget *menu, menu_entry *me, char *target) +{ + GtkWidget *item = menu_find (menu, me->path + me->root_offset, me->label); + if (item) + gtk_widget_destroy (item); +} + +static void +menu_add_cb (GtkWidget *menu, menu_entry *me, char *target) +{ + GtkWidget *item; + GtkAccelGroup *accel_group; + + if (me->group) /* have a group name? Must be a radio item */ + item = menu_add_radio (menu, me); + else if (me->ucmd) /* have unselect-cmd? Must be a toggle item */ + item = menu_add_toggle (menu, me); + else if (me->cmd || !me->label) /* label=NULL for separators */ + item = menu_add_item (menu, me, target); + else + item = menu_add_sub (menu, me); + + if (item) + { + gtk_widget_set_sensitive (item, me->enable); + if (me->key) + { + accel_group = g_object_get_data (G_OBJECT (menu), "accel"); + if (accel_group) /* popup menus don't have them */ + gtk_widget_add_accelerator (item, "activate", accel_group, me->key, + me->modifier, GTK_ACCEL_VISIBLE); + } + } +} + +char * +fe_menu_add (menu_entry *me) +{ + char *text; + + menu_foreach_gui (me, menu_add_cb); + + if (!me->markup) + return NULL; + + if (!pango_parse_markup (me->label, -1, 0, NULL, &text, NULL, NULL)) + return NULL; + + /* return the label with markup stripped */ + return text; +} + +void +fe_menu_del (menu_entry *me) +{ + menu_foreach_gui (me, menu_del_cb); +} + +void +fe_menu_update (menu_entry *me) +{ + menu_foreach_gui (me, menu_update_cb); +} + +/* used to add custom menus to the right-click menu */ + +static void +menu_add_plugin_mainmenu_items (GtkWidget *menu) +{ + GSList *list; + menu_entry *me; + + list = menu_list; /* outbound.c */ + while (list) + { + me = list->data; + if (me->is_main) + menu_add_cb (menu, me, NULL); + list = list->next; + } +} + +void +menu_add_plugin_items (GtkWidget *menu, char *root, char *target) +{ + GSList *list; + menu_entry *me; + + list = menu_list; /* outbound.c */ + while (list) + { + me = list->data; + if (!me->is_main && !strncmp (me->path, root + 1, root[0])) + menu_add_cb (menu, me, target); + list = list->next; + } +} + +/* === END STUFF FOR /MENU === */ + +GtkWidget * +menu_create_main (void *accel_group, int bar, int away, int toplevel, + GtkWidget **menu_widgets) +{ + int i = 0; + GtkWidget *item; + GtkWidget *menu = 0; + GtkWidget *menu_item = 0; + GtkWidget *menu_bar; + GtkWidget *usermenu = 0; + GtkWidget *submenu = 0; + int close_mask = GDK_CONTROL_MASK; + int away_mask = GDK_MOD1_MASK; + char *key_theme = NULL; + GtkSettings *settings; + GSList *group = NULL; + + if (bar) + menu_bar = gtk_menu_bar_new (); + else + menu_bar = gtk_menu_new (); + + /* /MENU needs to know this later */ + g_object_set_data (G_OBJECT (menu_bar), "accel", accel_group); + +#if GTK_CHECK_VERSION(2,4,0) + g_signal_connect (G_OBJECT (menu_bar), "can-activate-accel", + G_CALLBACK (menu_canacaccel), 0); +#endif + + /* set the initial state of toggles */ + mymenu[MENUBAR_OFFSET].state = !prefs.hidemenu; + mymenu[MENUBAR_OFFSET+1].state = prefs.topicbar; + mymenu[MENUBAR_OFFSET+2].state = !prefs.hideuserlist; + mymenu[MENUBAR_OFFSET+3].state = prefs.userlistbuttons; + mymenu[MENUBAR_OFFSET+4].state = prefs.chanmodebuttons; + + mymenu[AWAY_OFFSET].state = away; + + switch (prefs.tab_layout) + { + case 0: + mymenu[TABS_OFFSET].state = 1; + mymenu[TABS_OFFSET+1].state = 0; + break; + default: + mymenu[TABS_OFFSET].state = 0; + mymenu[TABS_OFFSET+1].state = 1; + } + + mymenu[METRE_OFFSET].state = 0; + mymenu[METRE_OFFSET+1].state = 0; + mymenu[METRE_OFFSET+2].state = 0; + mymenu[METRE_OFFSET+3].state = 0; + switch (prefs.lagometer) + { + case 0: + mymenu[METRE_OFFSET].state = 1; + break; + case 1: + mymenu[METRE_OFFSET+1].state = 1; + break; + case 2: + mymenu[METRE_OFFSET+2].state = 1; + break; + default: + mymenu[METRE_OFFSET+3].state = 1; + } + + /* change Close binding to ctrl-shift-w when using emacs keys */ + settings = gtk_widget_get_settings (menu_bar); + if (settings) + { + g_object_get (settings, "gtk-key-theme-name", &key_theme, NULL); + if (key_theme) + { + if (!strcasecmp (key_theme, "Emacs")) + { + close_mask = GDK_SHIFT_MASK | GDK_CONTROL_MASK; + mymenu[SEARCH_OFFSET].key = 0; + } + g_free (key_theme); + } + } + + /* Away binding to ctrl-alt-a if the _Help menu conflicts (FR/PT/IT) */ + { + char *help = _("_Help"); + char *under = strchr (help, '_'); + if (under && (under[1] == 'a' || under[1] == 'A')) + away_mask = GDK_MOD1_MASK | GDK_CONTROL_MASK; + } + + if (!toplevel) + { + mymenu[DETACH_OFFSET].text = N_("_Detach"); + mymenu[CLOSE_OFFSET].text = N_("_Close"); + } + else + { + mymenu[DETACH_OFFSET].text = N_("_Attach"); + mymenu[CLOSE_OFFSET].text = N_("_Close"); + } + + while (1) + { + item = NULL; + if (mymenu[i].id == MENU_ID_USERMENU && !prefs.gui_usermenu) + { + i++; + continue; + } + + switch (mymenu[i].type) + { + case M_NEWMENU: + if (menu) + gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), menu); + item = menu = gtk_menu_new (); + if (mymenu[i].id == MENU_ID_USERMENU) + usermenu = menu; + menu_item = gtk_menu_item_new_with_mnemonic (_(mymenu[i].text)); + /* record the English name for /menu */ + g_object_set_data (G_OBJECT (menu_item), "name", mymenu[i].text); + gtk_menu_shell_append (GTK_MENU_SHELL (menu_bar), menu_item); + gtk_widget_show (menu_item); + break; + + case M_MENUPIX: + item = create_icon_menu (_(mymenu[i].text), mymenu[i].image, FALSE); + goto normalitem; + + case M_MENUSTOCK: + item = create_icon_menu (_(mymenu[i].text), mymenu[i].image, TRUE); + goto normalitem; + + case M_MENUITEM: + item = gtk_menu_item_new_with_mnemonic (_(mymenu[i].text)); +normalitem: + if (mymenu[i].key != 0) + gtk_widget_add_accelerator (item, "activate", accel_group, + mymenu[i].key, + mymenu[i].key == GDK_F1 ? 0 : + mymenu[i].key == GDK_w ? close_mask : + GDK_CONTROL_MASK, + GTK_ACCEL_VISIBLE); + if (mymenu[i].callback) + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (mymenu[i].callback), 0); + if (submenu) + gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item); + else + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + gtk_widget_show (item); + break; + + case M_MENUTOG: + item = gtk_check_menu_item_new_with_mnemonic (_(mymenu[i].text)); +togitem: + /* must avoid callback for Radio buttons */ + GTK_CHECK_MENU_ITEM (item)->active = mymenu[i].state; + /*gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), + mymenu[i].state);*/ + if (mymenu[i].key != 0) + gtk_widget_add_accelerator (item, "activate", accel_group, + mymenu[i].key, mymenu[i].id == MENU_ID_AWAY ? + away_mask : GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE); + if (mymenu[i].callback) + g_signal_connect (G_OBJECT (item), "toggled", + G_CALLBACK (mymenu[i].callback), 0); + if (submenu) + gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item); + else + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + gtk_widget_show (item); + gtk_widget_set_sensitive (item, mymenu[i].sensitive); + break; + + case M_MENURADIO: + item = gtk_radio_menu_item_new_with_mnemonic (group, _(mymenu[i].text)); + group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item)); + goto togitem; + + case M_SEP: + item = gtk_menu_item_new (); + gtk_widget_set_sensitive (item, FALSE); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + gtk_widget_show (item); + break; + + case M_MENUSUB: + group = NULL; + submenu = gtk_menu_new (); + item = create_icon_menu (_(mymenu[i].text), mymenu[i].image, TRUE); + /* record the English name for /menu */ + g_object_set_data (G_OBJECT (item), "name", mymenu[i].text); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + gtk_widget_show (item); + break; + + /*case M_END:*/ default: + if (!submenu) + { + if (menu) + { + gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), menu); + menu_add_plugin_mainmenu_items (menu_bar); + } + if (usermenu) + usermenu_create (usermenu); + return (menu_bar); + } + submenu = NULL; + } + + /* record this GtkWidget * so it's state might be changed later */ + if (mymenu[i].id != 0 && menu_widgets) + /* this ends up in sess->gui->menu_item[MENU_ID_XXX] */ + menu_widgets[mymenu[i].id] = item; + + i++; + } +} diff --git a/src/fe-gtk/menu.h b/src/fe-gtk/menu.h new file mode 100644 index 00000000..7fef79cd --- /dev/null +++ b/src/fe-gtk/menu.h @@ -0,0 +1,41 @@ +GtkWidget *menu_create_main (void *accel_group, int bar, int away, int toplevel, GtkWidget **menu_widgets); +void menu_urlmenu (GdkEventButton * event, char *url); +void menu_chanmenu (session *sess, GdkEventButton * event, char *chan); +void menu_addfavoritemenu (server *serv, GtkWidget *menu, char *channel); +void menu_nickmenu (session *sess, GdkEventButton * event, char *nick, int num_sel); +void menu_middlemenu (session *sess, GdkEventButton *event); +void userlist_button_cb (GtkWidget * button, char *cmd); +void nick_command_parse (session *sess, char *cmd, char *nick, char *allnick); +void usermenu_update (void); +GtkWidget *menu_toggle_item (char *label, GtkWidget *menu, void *callback, void *userdata, int state); +GtkWidget *menu_quick_item (char *cmd, char *label, GtkWidget * menu, int flags, gpointer userdata, char *icon); +GtkWidget *menu_quick_sub (char *name, GtkWidget *menu, GtkWidget **sub_item_ret, int flags, int pos); +GtkWidget *create_icon_menu (char *labeltext, void *stock_name, int is_stock); +void menu_create (GtkWidget *menu, GSList *list, char *target, int check_path); +void menu_bar_toggle (void); +void menu_add_plugin_items (GtkWidget *menu, char *root, char *target); +void menu_change_layout (void); + +/* for menu_quick functions */ +#define XCMENU_DOLIST 1 +#define XCMENU_SHADED 1 +#define XCMENU_MARKUP 2 +#define XCMENU_MNEMONIC 4 + +/* menu items we keep a GtkWidget* for (to change their state) */ +#define MENU_ID_AWAY 1 +#define MENU_ID_MENUBAR 2 +#define MENU_ID_TOPICBAR 3 +#define MENU_ID_USERLIST 4 +#define MENU_ID_ULBUTTONS 5 +#define MENU_ID_MODEBUTTONS 6 +#define MENU_ID_LAYOUT_TABS 7 +#define MENU_ID_LAYOUT_TREE 8 +#define MENU_ID_DISCONNECT 9 +#define MENU_ID_RECONNECT 10 +#define MENU_ID_JOIN 11 +#define MENU_ID_USERMENU 12 + +#if (MENU_ID_NUM < MENU_ID_USERMENU) +#error MENU_ID_NUM is set wrong +#endif diff --git a/src/fe-gtk/mmx_cmod.S b/src/fe-gtk/mmx_cmod.S new file mode 100644 index 00000000..12e866de --- /dev/null +++ b/src/fe-gtk/mmx_cmod.S @@ -0,0 +1,530 @@ +/* + * Copyright (C) 1997-2001, Michael Jennings + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies of the Software, its documentation and marketing & publicity + * materials, and acknowledgment shall be given in the documentation, materials + * and software packages that this Software was used. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* MMX routines for tinting XImages written by Willem Monsuwe <willem@stack.nl> */ + +/* Function calling conventions: + * shade_ximage_xx(void *data, int bpl, int w, int h, int rm, int gm, int bm); + */ + +#define data 8(%ebp) +#define bpl 12(%ebp) +#define w 16(%ebp) +#define h 20(%ebp) +#define rm 24(%ebp) +#define gm 28(%ebp) +#define bm 32(%ebp) + +#ifdef UNDERSCORE_SYMBOLS /* need this to link with msvc */ +#define SHADE_XIMAGE_15 _shade_ximage_15_mmx +#define SHADE_XIMAGE_16 _shade_ximage_16_mmx +#define SHADE_XIMAGE_32 _shade_ximage_32_mmx +#define HAVE_MMX _have_mmx +#else +#define SHADE_XIMAGE_15 shade_ximage_15_mmx +#define SHADE_XIMAGE_16 shade_ximage_16_mmx +#define SHADE_XIMAGE_32 shade_ximage_32_mmx +#define HAVE_MMX have_mmx +#endif + +.globl SHADE_XIMAGE_15 +.globl SHADE_XIMAGE_16 +.globl SHADE_XIMAGE_32 +.globl HAVE_MMX + +.bss +.text +.align 8 + +#define ENTER \ + pushl %ebp ;\ + movl %esp, %ebp ;\ + pushl %ebx ;\ + pushl %ecx ;\ + pushl %edx ;\ + pushl %edi ;\ + pushl %esi ;\ + movl data, %esi ;\ + movl w, %ebx ;\ + movl h, %edx + +#define LEAVE \ +4: ;\ + emms ;\ + popl %esi ;\ + popl %edi ;\ + popl %edx ;\ + popl %ecx ;\ + popl %ebx ;\ + movl %ebp, %esp ;\ + popl %ebp ;\ + ret + + +SHADE_XIMAGE_15: + ENTER + + leal -6(%esi, %ebx, 2), %esi + negl %ebx + jz 5f + + /* Setup multipliers */ + movd rm, %mm5 + movd gm, %mm6 + movd bm, %mm7 + punpcklwd %mm5, %mm5 /* 00 00 00 00 rm rm rm rm */ + punpcklwd %mm6, %mm6 /* 00 00 00 00 gm gm gm gm */ + punpcklwd %mm7, %mm7 /* 00 00 00 00 bm bm bm bm */ + punpckldq %mm5, %mm5 /* rm rm rm rm rm rm rm rm */ + punpckldq %mm6, %mm6 /* gm gm gm gm gm gm gm gm */ + punpckldq %mm7, %mm7 /* bm bm bm bm bm bm bm bm */ + + cmpl $256, rm + jg shade_ximage_15_mmx_saturate + cmpl $256, gm + jg shade_ximage_15_mmx_saturate + cmpl $256, bm + jg shade_ximage_15_mmx_saturate + +1: movl %ebx, %ecx + addl $3, %ecx + jns 3f +2: + movq (%esi, %ecx, 2), %mm0 + + movq %mm0, %mm1 /* rg gb */ + movq %mm0, %mm2 /* rg gb */ + psrlw $5, %mm1 /* 0r rg */ + psrlw $10, %mm0 /* 00 0r */ + psllw $11, %mm2 /* b0 00 */ + psllw $11, %mm1 /* g0 00 */ + psllw $8, %mm0 /* 0r 00 */ + psrlw $3, %mm1 /* 0g 00 */ + psrlw $3, %mm2 /* 0b 00 */ + + pmulhw %mm5, %mm0 /* 00 0r */ + pmulhw %mm6, %mm1 /* 00 0g */ + pmulhw %mm7, %mm2 /* 00 0b */ + + psllw $10, %mm0 /* r0 00 */ + psllw $5, %mm1 /* 0g g0 */ + por %mm2, %mm0 /* r0 0b */ + por %mm1, %mm0 /* rg gb */ + + movq %mm0, (%esi, %ecx, 2) + + addl $4, %ecx + js 2b + jmp 4f +3: + movw (%esi, %ecx, 2), %ax + movd %eax, %mm0 + + movq %mm0, %mm1 /* rg gb */ + movq %mm0, %mm2 /* rg gb */ + psrlw $5, %mm1 /* 0r rg */ + psrlw $10, %mm0 /* 00 0r */ + psllw $11, %mm2 /* b0 00 */ + psllw $11, %mm1 /* g0 00 */ + psllw $8, %mm0 /* 0r 00 */ + psrlw $3, %mm1 /* 0g 00 */ + psrlw $3, %mm2 /* 0b 00 */ + + pmulhw %mm5, %mm0 /* 00 0r */ + pmulhw %mm6, %mm1 /* 00 0g */ + pmulhw %mm7, %mm2 /* 00 0b */ + + psllw $10, %mm0 /* r0 00 */ + psllw $5, %mm1 /* 0g g0 */ + por %mm2, %mm0 /* r0 0b */ + por %mm1, %mm0 /* rg gb */ + + movd %mm0, %eax + movw %ax, (%esi, %ecx, 2) + + incl %ecx +4: + cmpl $2, %ecx + jng 3b + + addl bpl, %esi + decl %edx + jnz 1b +5: + LEAVE + + +shade_ximage_15_mmx_saturate: + + pcmpeqw %mm3, %mm3 + psllw $5, %mm3 /* ff e0 ff e0 ff e0 ff e0 */ + +1: movl %ebx, %ecx + addl $3, %ecx + jns 3f +2: + movq (%esi, %ecx, 2), %mm0 + + movq %mm0, %mm1 /* rg gb */ + movq %mm0, %mm2 /* rg gb */ + psrlw $5, %mm1 /* 0r rg */ + psrlw $10, %mm0 /* 00 0r */ + psllw $11, %mm2 /* b0 00 */ + psllw $11, %mm1 /* g0 00 */ + psllw $8, %mm0 /* 0r 00 */ + psrlw $3, %mm1 /* 0g 00 */ + psrlw $3, %mm2 /* 0b 00 */ + + pmulhw %mm5, %mm0 /* xx xr */ + pmulhw %mm6, %mm1 /* xx xg */ + pmulhw %mm7, %mm2 /* xx xb */ + + /* Saturate upper */ + paddusw %mm3, %mm0 /* ff er */ + paddusw %mm3, %mm1 /* ff eg */ + paddusw %mm3, %mm2 /* ff eb */ + + psubw %mm3, %mm0 /* 00 0r */ + psubw %mm3, %mm1 /* 00 0g */ + psubw %mm3, %mm2 /* 00 0b */ + + psllw $10, %mm0 /* r0 00 */ + psllw $5, %mm1 /* 0g g0 */ + por %mm2, %mm0 /* r0 0b */ + por %mm1, %mm0 /* rg gb */ + + movq %mm0, (%esi, %ecx, 2) + + addl $4, %ecx + js 2b + jmp 4f +3: + movw (%esi, %ecx, 2), %ax + movd %eax, %mm0 + + movq %mm0, %mm1 /* rg gb */ + movq %mm0, %mm2 /* rg gb */ + psrlw $5, %mm1 /* 0r rg */ + psrlw $10, %mm0 /* 00 0r */ + psllw $11, %mm2 /* b0 00 */ + psllw $11, %mm1 /* g0 00 */ + psllw $8, %mm0 /* 0r 00 */ + psrlw $3, %mm1 /* 0g 00 */ + psrlw $3, %mm2 /* 0b 00 */ + + pmulhw %mm5, %mm0 /* xx xr */ + pmulhw %mm6, %mm1 /* xx xg */ + pmulhw %mm7, %mm2 /* xx xb */ + + /* Saturate upper */ + paddusw %mm3, %mm0 /* ff er */ + paddusw %mm3, %mm1 /* ff eg */ + paddusw %mm3, %mm2 /* ff eb */ + + psubw %mm3, %mm0 /* 00 0r */ + psubw %mm3, %mm1 /* 00 0g */ + psubw %mm3, %mm2 /* 00 0b */ + + psllw $10, %mm0 /* r0 00 */ + psllw $5, %mm1 /* 0g g0 */ + por %mm2, %mm0 /* r0 0b */ + por %mm1, %mm0 /* rg gb */ + + movd %mm0, %eax + movw %ax, (%esi, %ecx, 2) + + incl %ecx +4: + cmpl $2, %ecx + jng 3b + + addl bpl, %esi + decl %edx + jnz 1b +5: + LEAVE + + +SHADE_XIMAGE_16: + ENTER + + leal -6(%esi, %ebx, 2), %esi + negl %ebx + jz 5f + + /* Setup multipliers */ + movd rm, %mm5 + movd gm, %mm6 + movd bm, %mm7 + punpcklwd %mm5, %mm5 /* 00 00 00 00 rm rm rm rm */ + punpcklwd %mm6, %mm6 /* 00 00 00 00 gm gm gm gm */ + punpcklwd %mm7, %mm7 /* 00 00 00 00 bm bm bm bm */ + punpckldq %mm5, %mm5 /* rm rm rm rm rm rm rm rm */ + punpckldq %mm6, %mm6 /* gm gm gm gm gm gm gm gm */ + punpckldq %mm7, %mm7 /* bm bm bm bm bm bm bm bm */ + + cmpl $256, rm + jg shade_ximage_16_mmx_saturate + cmpl $256, gm + jg shade_ximage_16_mmx_saturate + cmpl $256, bm + jg shade_ximage_16_mmx_saturate + +1: movl %ebx, %ecx + addl $3, %ecx + jns 3f +2: + movq (%esi, %ecx, 2), %mm0 + + movq %mm0, %mm1 /* rg gb */ + movq %mm0, %mm2 /* rg gb */ + psrlw $5, %mm1 /* 0r rg */ + psrlw $11, %mm0 /* 00 0r */ + psllw $11, %mm2 /* b0 00 */ + psllw $10, %mm1 /* g0 00 */ + psllw $8, %mm0 /* 0r 00 */ + psrlw $2, %mm1 /* 0g 00 */ + psrlw $3, %mm2 /* 0b 00 */ + + pmulhw %mm5, %mm0 /* 00 0r */ + pmulhw %mm6, %mm1 /* 00 0g */ + pmulhw %mm7, %mm2 /* 00 0b */ + + psllw $11, %mm0 /* r0 00 */ + psllw $5, %mm1 /* 0g g0 */ + por %mm2, %mm0 /* r0 0b */ + por %mm1, %mm0 /* rg gb */ + + movq %mm0, (%esi, %ecx, 2) + + addl $4, %ecx + js 2b + jmp 4f +3: + movw (%esi, %ecx, 2), %ax + movd %eax, %mm0 + + movq %mm0, %mm1 /* rg gb */ + movq %mm0, %mm2 /* rg gb */ + psrlw $5, %mm1 /* 0r rg */ + psrlw $11, %mm0 /* 00 0r */ + psllw $11, %mm2 /* b0 00 */ + psllw $10, %mm1 /* g0 00 */ + psllw $8, %mm0 /* 0r 00 */ + psrlw $2, %mm1 /* 0g 00 */ + psrlw $3, %mm2 /* 0b 00 */ + + pmulhw %mm5, %mm0 /* 00 0r */ + pmulhw %mm6, %mm1 /* 00 0g */ + pmulhw %mm7, %mm2 /* 00 0b */ + + psllw $11, %mm0 /* r0 00 */ + psllw $5, %mm1 /* 0g g0 */ + por %mm2, %mm0 /* r0 0b */ + por %mm1, %mm0 /* rg gb */ + + movd %mm0, %eax + movw %ax, (%esi, %ecx, 2) + + incl %ecx +4: + cmpl $2, %ecx + jng 3b + + addl bpl, %esi + decl %edx + jnz 1b +5: + LEAVE + + +shade_ximage_16_mmx_saturate: + + pcmpeqw %mm3, %mm3 + movq %mm3, %mm4 + psllw $5, %mm3 /* ff e0 ff e0 ff e0 ff e0 */ + psllw $6, %mm4 /* ff c0 ff c0 ff c0 ff c0 */ + +1: movl %ebx, %ecx + addl $3, %ecx + jns 3f +2: + movq (%esi, %ecx, 2), %mm0 + + movq %mm0, %mm1 /* rg gb */ + movq %mm0, %mm2 /* rg gb */ + psrlw $5, %mm1 /* 0r rg */ + psrlw $11, %mm0 /* 00 0r */ + psllw $11, %mm2 /* b0 00 */ + psllw $10, %mm1 /* g0 00 */ + psllw $8, %mm0 /* 0r 00 */ + psrlw $2, %mm1 /* 0g 00 */ + psrlw $3, %mm2 /* 0b 00 */ + + pmulhw %mm5, %mm0 /* xx xr */ + pmulhw %mm6, %mm1 /* xx xg */ + pmulhw %mm7, %mm2 /* xx xb */ + + /* Saturate upper */ + paddusw %mm3, %mm0 /* ff er */ + paddusw %mm4, %mm1 /* ff cg */ + paddusw %mm3, %mm2 /* ff eb */ + + psubw %mm4, %mm1 /* 00 0g */ + psubw %mm3, %mm2 /* 00 0b */ + + psllw $11, %mm0 /* r0 00 */ + psllw $5, %mm1 /* 0g g0 */ + por %mm2, %mm0 /* r0 0b */ + por %mm1, %mm0 /* rg gb */ + + movq %mm0, (%esi, %ecx, 2) + + addl $4, %ecx + js 2b + jmp 4f +3: + movw (%esi, %ecx, 2), %ax + movd %eax, %mm0 + + movq %mm0, %mm1 /* rg gb */ + movq %mm0, %mm2 /* rg gb */ + psrlw $5, %mm1 /* 0r rg */ + psrlw $11, %mm0 /* 00 0r */ + psllw $11, %mm2 /* b0 00 */ + psllw $10, %mm1 /* g0 00 */ + psllw $8, %mm0 /* 0r 00 */ + psrlw $2, %mm1 /* 0g 00 */ + psrlw $3, %mm2 /* 0b 00 */ + + pmulhw %mm5, %mm0 /* xx xr */ + pmulhw %mm6, %mm1 /* xx xg */ + pmulhw %mm7, %mm2 /* xx xb */ + + /* Saturate upper */ + paddusw %mm3, %mm0 /* ff er */ + paddusw %mm4, %mm1 /* ff cg */ + paddusw %mm3, %mm2 /* ff eb */ + + psubw %mm4, %mm1 /* 00 0g */ + psubw %mm3, %mm2 /* 00 0b */ + + psllw $11, %mm0 /* r0 00 */ + psllw $5, %mm1 /* 0g g0 */ + por %mm2, %mm0 /* r0 0b */ + por %mm1, %mm0 /* rg gb */ + + movd %mm0, %eax + movw %ax, (%esi, %ecx, 2) + + incl %ecx +4: + cmpl $2, %ecx + jng 3b + + addl bpl, %esi + decl %edx + jnz 1b +5: + LEAVE + + +SHADE_XIMAGE_32: + ENTER + + leal (%esi, %ebx, 4), %esi + negl %ebx + jz 3f + + movd rm, %mm4 + movd gm, %mm5 + movd bm, %mm6 + psllq $32, %mm4 + psllq $16, %mm5 + por %mm6, %mm4 + por %mm5, %mm4 + + pcmpeqw %mm6, %mm6 + psllw $15, %mm6 /* 80 00 80 00 80 00 80 00 */ + movq %mm6, %mm5 + pmulhw %mm4, %mm5 /* Get correction factor */ +1: + movl %ebx, %ecx +2: + movd (%esi, %ecx, 4), %mm1 /* 00 rr gg bb */ + pxor %mm0, %mm0 + punpcklbw %mm1, %mm0 /* 00 00 rr 00 gg 00 bb 00 */ + pxor %mm6, %mm0 /* Flip sign */ + + pmulhw %mm4, %mm0 /* 00 00 xx rr xx gg xx bb */ + psubw %mm5, %mm0 /* Correct range */ + packuswb %mm0, %mm0 /* 00 rr gg bb 00 rr gg bb */ + + movd %mm0, (%esi, %ecx, 4) + + incl %ecx + jnz 2b + + addl bpl, %esi + decl %edx + jnz 1b +3: + LEAVE + + +HAVE_MMX: + push %ebx +/* Check if bit 21 in flags word is writeable */ + pushfl + popl %eax + movl %eax,%ebx + xorl $0x00200000, %eax + pushl %eax + popfl + pushfl + popl %eax + + cmpl %eax, %ebx + je 8f + +/* OK, we have CPUID */ + + movl $1, %eax + cpuid + + test $0x00800000, %edx + jz 8f + + movl $1, %eax /* success, have mmx */ + popl %ebx + ret + +8: + xorl %eax,%eax /* failed, no mmx */ + popl %ebx + ret + +#if defined(__GNUC__) && !defined(_WIN32) +.section .note.GNU-stack, "", @progbits +.previous +#endif diff --git a/src/fe-gtk/mmx_cmod.h b/src/fe-gtk/mmx_cmod.h new file mode 100644 index 00000000..52d07102 --- /dev/null +++ b/src/fe-gtk/mmx_cmod.h @@ -0,0 +1,4 @@ +void shade_ximage_15_mmx(void *data, int bpl, int w, int h, int rm, int gm, int bm); +void shade_ximage_16_mmx(void *data, int bpl, int w, int h, int rm, int gm, int bm); +void shade_ximage_32_mmx(void *data, int bpl, int w, int h, int rm, int gm, int bm); +int have_mmx (void); diff --git a/src/fe-gtk/notifygui.c b/src/fe-gtk/notifygui.c new file mode 100644 index 00000000..5acb683a --- /dev/null +++ b/src/fe-gtk/notifygui.c @@ -0,0 +1,442 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <time.h> + +#include "fe-gtk.h" + +#include <gtk/gtkhbox.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkhbbox.h> +#include <gtk/gtkscrolledwindow.h> + +#include <gtk/gtklabel.h> +#include <gtk/gtkliststore.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkmessagedialog.h> +#include <gtk/gtktable.h> +#include <gtk/gtktreeview.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtkcellrenderertext.h> + +#include "../common/xchat.h" +#include "../common/notify.h" +#include "../common/cfgfiles.h" +#include "../common/fe.h" +#include "../common/server.h" +#include "../common/util.h" +#include "../common/userlist.h" +#include "gtkutil.h" +#include "maingui.h" +#include "palette.h" +#include "notifygui.h" + + +/* model for the notify treeview */ +enum +{ + USER_COLUMN, + STATUS_COLUMN, + SERVER_COLUMN, + SEEN_COLUMN, + COLOUR_COLUMN, + NPS_COLUMN, /* struct notify_per_server * */ + N_COLUMNS +}; + + +static GtkWidget *notify_window = 0; +static GtkWidget *notify_button_opendialog; +static GtkWidget *notify_button_remove; + + +static void +notify_closegui (void) +{ + notify_window = 0; +} + +/* Need this to be able to set the foreground colour property of a row + * from a GdkColor * in the model -Vince + */ +static void +notify_treecell_property_mapper (GtkTreeViewColumn *col, GtkCellRenderer *cell, + GtkTreeModel *model, GtkTreeIter *iter, + gpointer data) +{ + gchar *text; + GdkColor *colour; + int model_column = GPOINTER_TO_INT (data); + + gtk_tree_model_get (GTK_TREE_MODEL (model), iter, + COLOUR_COLUMN, &colour, + model_column, &text, -1); + g_object_set (G_OBJECT (cell), "text", text, NULL); + g_object_set (G_OBJECT (cell), "foreground-gdk", colour, NULL); + g_free (text); +} + +static void +notify_row_cb (GtkTreeSelection *sel, GtkTreeView *view) +{ + GtkTreeIter iter; + struct notify_per_server *servnot; + + if (gtkutil_treeview_get_selected (view, &iter, NPS_COLUMN, &servnot, -1)) + { + gtk_widget_set_sensitive (notify_button_opendialog, servnot ? servnot->ison : 0); + gtk_widget_set_sensitive (notify_button_remove, TRUE); + return; + } + + gtk_widget_set_sensitive (notify_button_opendialog, FALSE); + gtk_widget_set_sensitive (notify_button_remove, FALSE); +} + +static GtkWidget * +notify_treeview_new (GtkWidget *box) +{ + GtkListStore *store; + GtkWidget *view; + GtkTreeViewColumn *col; + int col_id; + + store = gtk_list_store_new (N_COLUMNS, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_POINTER, /* can't specify colour! */ + G_TYPE_POINTER + ); + g_return_val_if_fail (store != NULL, NULL); + + view = gtkutil_treeview_new (box, GTK_TREE_MODEL (store), + notify_treecell_property_mapper, + USER_COLUMN, _("Name"), + STATUS_COLUMN, _("Status"), + SERVER_COLUMN, _("Network"), + SEEN_COLUMN, _("Last Seen"), -1); + gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), 0), TRUE); + + for (col_id=0; (col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), col_id)); + col_id++) + gtk_tree_view_column_set_alignment (col, 0.5); + + g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (view))), + "changed", G_CALLBACK (notify_row_cb), view); + + gtk_widget_show (view); + return view; +} + +void +notify_gui_update (void) +{ + struct notify *notify; + struct notify_per_server *servnot; + GSList *list = notify_list; + GSList *slist; + gchar *name, *status, *server, *seen; + int online, servcount; + time_t lastseen; + char agobuf[128]; + + GtkListStore *store; + GtkTreeView *view; + GtkTreeIter iter; + gboolean valid; /* true if we don't need to append a new tree row */ + + if (!notify_window) + return; + + view = g_object_get_data (G_OBJECT (notify_window), "view"); + store = GTK_LIST_STORE (gtk_tree_view_get_model (view)); + valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter); + + while (list) + { + notify = (struct notify *) list->data; + name = notify->name; + status = _("Offline"); + server = ""; + + online = FALSE; + lastseen = 0; + /* First see if they're online on any servers */ + slist = notify->server_list; + while (slist) + { + servnot = (struct notify_per_server *) slist->data; + if (servnot->ison) + online = TRUE; + if (servnot->lastseen > lastseen) + lastseen = servnot->lastseen; + slist = slist->next; + } + + if (!online) /* Offline on all servers */ + { + if (!lastseen) + seen = _("Never"); + else + { + snprintf (agobuf, sizeof (agobuf), _("%d minutes ago"), (int)(time (0) - lastseen) / 60); + seen = agobuf; + } + if (!valid) /* create new tree row if required */ + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, name, 1, status, + 2, server, 3, seen, 4, &colors[4], 5, NULL, -1); + if (valid) + valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter); + + } else + { + /* Online - add one line per server */ + servcount = 0; + slist = notify->server_list; + status = _("Online"); + while (slist) + { + servnot = (struct notify_per_server *) slist->data; + if (servnot->ison) + { + if (servcount > 0) + name = ""; + server = server_get_network (servnot->server, TRUE); + + snprintf (agobuf, sizeof (agobuf), _("%d minutes ago"), (int)(time (0) - lastseen) / 60); + seen = agobuf; + + if (!valid) /* create new tree row if required */ + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, name, 1, status, + 2, server, 3, seen, 4, &colors[3], 5, servnot, -1); + if (valid) + valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter); + + servcount++; + } + slist = slist->next; + } + } + + list = list->next; + } + + while (valid) + { + GtkTreeIter old = iter; + /* get next iter now because removing invalidates old one */ + valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), + &iter); + gtk_list_store_remove (store, &old); + } +} + +static void +notify_opendialog_clicked (GtkWidget * igad) +{ + GtkTreeView *view; + GtkTreeIter iter; + struct notify_per_server *servnot; + + view = g_object_get_data (G_OBJECT (notify_window), "view"); + if (gtkutil_treeview_get_selected (view, &iter, NPS_COLUMN, &servnot, -1)) + { + if (servnot) + open_query (servnot->server, servnot->notify->name, TRUE); + } +} + +static void +notify_remove_clicked (GtkWidget * igad) +{ + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreePath *path = NULL; + gboolean found = FALSE; + char *name; + + view = g_object_get_data (G_OBJECT (notify_window), "view"); + if (gtkutil_treeview_get_selected (view, &iter, USER_COLUMN, &name, -1)) + { + model = gtk_tree_view_get_model (view); + found = (*name != 0); + while (!found) /* the real nick is some previous node */ + { + g_free (name); /* it's useless to us */ + if (!path) + path = gtk_tree_model_get_path (model, &iter); + if (!gtk_tree_path_prev (path)) /* arrgh! no previous node! */ + { + g_warning ("notify list state is invalid\n"); + break; + } + if (!gtk_tree_model_get_iter (model, &iter, path)) + break; + gtk_tree_model_get (model, &iter, USER_COLUMN, &name, -1); + found = (*name != 0); + } + if (path) + gtk_tree_path_free (path); + if (!found) + return; + + /* ok, now we can remove it */ + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); + notify_deluser (name); + g_free (name); + } +} + +static void +notifygui_add_cb (GtkDialog *dialog, gint response, gpointer entry) +{ + char *networks; + char *text; + + text = GTK_ENTRY (entry)->text; + if (text[0] && response == GTK_RESPONSE_ACCEPT) + { + networks = GTK_ENTRY (g_object_get_data (G_OBJECT (entry), "net"))->text; + if (strcasecmp (networks, "ALL") == 0 || networks[0] == 0) + notify_adduser (text, NULL); + else + notify_adduser (text, networks); + } + + gtk_widget_destroy (GTK_WIDGET (dialog)); +} + +static void +notifygui_add_enter (GtkWidget *entry, GtkWidget *dialog) +{ + gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT); +} + +void +fe_notify_ask (char *nick, char *networks) +{ + GtkWidget *dialog; + GtkWidget *entry; + GtkWidget *label; + GtkWidget *wid; + GtkWidget *table; + char *msg = _("Enter nickname to add:"); + char buf[256]; + + dialog = gtk_dialog_new_with_buttons (msg, NULL, 0, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + NULL); + if (parent_window) + gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window)); + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + + table = gtk_table_new (2, 3, FALSE); + gtk_container_set_border_width (GTK_CONTAINER (table), 12); + gtk_table_set_row_spacings (GTK_TABLE (table), 3); + gtk_table_set_col_spacings (GTK_TABLE (table), 8); + gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), table); + + label = gtk_label_new (msg); + gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, 0, 1); + + entry = gtk_entry_new (); + gtk_entry_set_text (GTK_ENTRY (entry), nick); + g_signal_connect (G_OBJECT (entry), "activate", + G_CALLBACK (notifygui_add_enter), dialog); + gtk_table_attach_defaults (GTK_TABLE (table), entry, 1, 2, 0, 1); + + g_signal_connect (G_OBJECT (dialog), "response", + G_CALLBACK (notifygui_add_cb), entry); + + label = gtk_label_new (_("Notify on these networks:")); + gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, 2, 3); + + wid = gtk_entry_new (); + g_object_set_data (G_OBJECT (entry), "net", wid); + g_signal_connect (G_OBJECT (wid), "activate", + G_CALLBACK (notifygui_add_enter), dialog); + gtk_entry_set_text (GTK_ENTRY (wid), networks ? networks : "ALL"); + gtk_table_attach_defaults (GTK_TABLE (table), wid, 1, 2, 2, 3); + + label = gtk_label_new (NULL); + snprintf (buf, sizeof (buf), "<i><span size=\"smaller\">%s</span></i>", _("Comma separated list of networks is accepted.")); + gtk_label_set_markup (GTK_LABEL (label), buf); + gtk_table_attach_defaults (GTK_TABLE (table), label, 1, 2, 3, 4); + + gtk_widget_show_all (dialog); +} + +static void +notify_add_clicked (GtkWidget * igad) +{ + fe_notify_ask ("", NULL); +} + +void +notify_opengui (void) +{ + GtkWidget *vbox, *bbox; + GtkWidget *view; + + if (notify_window) + { + mg_bring_tofront (notify_window); + return; + } + + notify_window = + mg_create_generic_tab ("Notify", _("XChat: Friends List"), FALSE, TRUE, + notify_closegui, NULL, 400, 250, &vbox, 0); + + view = notify_treeview_new (vbox); + g_object_set_data (G_OBJECT (notify_window), "view", view); + + bbox = gtk_hbutton_box_new (); + gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD); + gtk_container_set_border_width (GTK_CONTAINER (bbox), 5); + gtk_box_pack_end (GTK_BOX (vbox), bbox, 0, 0, 0); + gtk_widget_show (bbox); + + gtkutil_button (bbox, GTK_STOCK_NEW, 0, notify_add_clicked, 0, + _("Add...")); + + notify_button_remove = + gtkutil_button (bbox, GTK_STOCK_DELETE, 0, notify_remove_clicked, 0, + _("Remove")); + + notify_button_opendialog = + gtkutil_button (bbox, NULL, 0, notify_opendialog_clicked, 0, + _("Open Dialog")); + + gtk_widget_set_sensitive (notify_button_opendialog, FALSE); + gtk_widget_set_sensitive (notify_button_remove, FALSE); + + notify_gui_update (); + + gtk_widget_show (notify_window); +} diff --git a/src/fe-gtk/notifygui.h b/src/fe-gtk/notifygui.h new file mode 100644 index 00000000..360834dc --- /dev/null +++ b/src/fe-gtk/notifygui.h @@ -0,0 +1,2 @@ +void notify_gui_update (void); +void notify_opengui (void); diff --git a/src/fe-gtk/palette.c b/src/fe-gtk/palette.c new file mode 100644 index 00000000..ebae92ff --- /dev/null +++ b/src/fe-gtk/palette.c @@ -0,0 +1,226 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "fe-gtk.h" +#include "palette.h" + +#include "../common/xchat.h" +#include "../common/util.h" +#include "../common/cfgfiles.h" + + +GdkColor colors[] = { + /* colors for xtext */ + {0, 0xcccc, 0xcccc, 0xcccc}, /* 16 white */ + {0, 0x0000, 0x0000, 0x0000}, /* 17 black */ + {0, 0x35c2, 0x35c2, 0xb332}, /* 18 blue */ + {0, 0x2a3d, 0x8ccc, 0x2a3d}, /* 19 green */ + {0, 0xc3c3, 0x3b3b, 0x3b3b}, /* 20 red */ + {0, 0xc7c7, 0x3232, 0x3232}, /* 21 light red */ + {0, 0x8000, 0x2666, 0x7fff}, /* 22 purple */ + {0, 0x6666, 0x3636, 0x1f1f}, /* 23 orange */ + {0, 0xd999, 0xa6d3, 0x4147}, /* 24 yellow */ + {0, 0x3d70, 0xcccc, 0x3d70}, /* 25 green */ + {0, 0x199a, 0x5555, 0x5555}, /* 26 aqua */ + {0, 0x2eef, 0x8ccc, 0x74df}, /* 27 light aqua */ + {0, 0x451e, 0x451e, 0xe666}, /* 28 blue */ + {0, 0xb0b0, 0x3737, 0xb0b0}, /* 29 light purple */ + {0, 0x4c4c, 0x4c4c, 0x4c4c}, /* 30 grey */ + {0, 0x9595, 0x9595, 0x9595}, /* 31 light grey */ + + {0, 0xcccc, 0xcccc, 0xcccc}, /* 16 white */ + {0, 0x0000, 0x0000, 0x0000}, /* 17 black */ + {0, 0x35c2, 0x35c2, 0xb332}, /* 18 blue */ + {0, 0x2a3d, 0x8ccc, 0x2a3d}, /* 19 green */ + {0, 0xc3c3, 0x3b3b, 0x3b3b}, /* 20 red */ + {0, 0xc7c7, 0x3232, 0x3232}, /* 21 light red */ + {0, 0x8000, 0x2666, 0x7fff}, /* 22 purple */ + {0, 0x6666, 0x3636, 0x1f1f}, /* 23 orange */ + {0, 0xd999, 0xa6d3, 0x4147}, /* 24 yellow */ + {0, 0x3d70, 0xcccc, 0x3d70}, /* 25 green */ + {0, 0x199a, 0x5555, 0x5555}, /* 26 aqua */ + {0, 0x2eef, 0x8ccc, 0x74df}, /* 27 light aqua */ + {0, 0x451e, 0x451e, 0xe666}, /* 28 blue */ + {0, 0xb0b0, 0x3737, 0xb0b0}, /* 29 light purple */ + {0, 0x4c4c, 0x4c4c, 0x4c4c}, /* 30 grey */ + {0, 0x9595, 0x9595, 0x9595}, /* 31 light grey */ + + {0, 0xffff, 0xffff, 0xffff}, /* 32 marktext Fore (white) */ + {0, 0x3535, 0x6e6e, 0xc1c1}, /* 33 marktext Back (blue) */ + {0, 0x0000, 0x0000, 0x0000}, /* 34 foreground (black) */ + {0, 0xf0f0, 0xf0f0, 0xf0f0}, /* 35 background (white) */ + {0, 0xcccc, 0x1010, 0x1010}, /* 36 marker line (red) */ + + /* colors for GUI */ + {0, 0x9999, 0x0000, 0x0000}, /* 37 tab New Data (dark red) */ + {0, 0x0000, 0x0000, 0xffff}, /* 38 tab Nick Mentioned (blue) */ + {0, 0xffff, 0x0000, 0x0000}, /* 39 tab New Message (red) */ + {0, 0x9595, 0x9595, 0x9595}, /* 40 away user (grey) */ +}; +#define MAX_COL 40 + + +void +palette_alloc (GtkWidget * widget) +{ + int i; + static int done_alloc = FALSE; + GdkColormap *cmap; + + if (!done_alloc) /* don't do it again */ + { + done_alloc = TRUE; + cmap = gtk_widget_get_colormap (widget); + for (i = MAX_COL; i >= 0; i--) + gdk_colormap_alloc_color (cmap, &colors[i], FALSE, TRUE); + } +} + +/* maps XChat 2.0.x colors to current */ +static const int remap[] = +{ + 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, + 33, /* 16:marktextback */ + 32, /* 17:marktextfore */ + 34, /* 18: fg */ + 35, /* 19: bg */ + 37, /* 20: newdata */ + 38, /* 21: blue */ + 39, /* 22: newmsg */ + 40 /* 23: away */ +}; + +void +palette_load (void) +{ + int i, j, l, fh, res; + char prefname[256]; + struct stat st; + char *cfg; + int red, green, blue; + int upgrade = FALSE; + + fh = xchat_open_file ("colors.conf", O_RDONLY, 0, 0); + if (fh == -1) + { + fh = xchat_open_file ("palette.conf", O_RDONLY, 0, 0); + upgrade = TRUE; + } + + if (fh != -1) + { + fstat (fh, &st); + cfg = malloc (st.st_size + 1); + if (cfg) + { + cfg[0] = '\0'; + l = read (fh, cfg, st.st_size); + if (l >= 0) + cfg[l] = '\0'; + + if (!upgrade) + { + /* mIRC colors 0-31 are here */ + for (i = 0; i < 32; i++) + { + snprintf (prefname, sizeof prefname, "color_%d", i); + cfg_get_color (cfg, prefname, &red, &green, &blue); + colors[i].red = red; + colors[i].green = green; + colors[i].blue = blue; + } + + /* our special colors are mapped at 256+ */ + for (i = 256, j = 32; j < MAX_COL+1; i++, j++) + { + snprintf (prefname, sizeof prefname, "color_%d", i); + cfg_get_color (cfg, prefname, &red, &green, &blue); + colors[j].red = red; + colors[j].green = green; + colors[j].blue = blue; + } + + } else + { + /* loading 2.0.x palette.conf */ + for (i = 0; i < MAX_COL+1; i++) + { + snprintf (prefname, sizeof prefname, "color_%d_red", i); + red = cfg_get_int (cfg, prefname); + + snprintf (prefname, sizeof prefname, "color_%d_grn", i); + green = cfg_get_int (cfg, prefname); + + snprintf (prefname, sizeof prefname, "color_%d_blu", i); + blue = cfg_get_int_with_result (cfg, prefname, &res); + + if (res) + { + colors[remap[i]].red = red; + colors[remap[i]].green = green; + colors[remap[i]].blue = blue; + } + } + + /* copy 0-15 to 16-31 */ + for (i = 0; i < 16; i++) + { + colors[i+16].red = colors[i].red; + colors[i+16].green = colors[i].green; + colors[i+16].blue = colors[i].blue; + } + } + free (cfg); + } + close (fh); + } +} + +void +palette_save (void) +{ + int i, j, fh; + char prefname[256]; + + fh = xchat_open_file ("colors.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); + if (fh != -1) + { + /* mIRC colors 0-31 are here */ + for (i = 0; i < 32; i++) + { + snprintf (prefname, sizeof prefname, "color_%d", i); + cfg_put_color (fh, colors[i].red, colors[i].green, colors[i].blue, prefname); + } + + /* our special colors are mapped at 256+ */ + for (i = 256, j = 32; j < MAX_COL+1; i++, j++) + { + snprintf (prefname, sizeof prefname, "color_%d", i); + cfg_put_color (fh, colors[j].red, colors[j].green, colors[j].blue, prefname); + } + + close (fh); + } +} diff --git a/src/fe-gtk/palette.h b/src/fe-gtk/palette.h new file mode 100644 index 00000000..c97693bb --- /dev/null +++ b/src/fe-gtk/palette.h @@ -0,0 +1,15 @@ +extern GdkColor colors[]; + +#define COL_MARK_FG 32 +#define COL_MARK_BG 33 +#define COL_FG 34 +#define COL_BG 35 +#define COL_MARKER 36 +#define COL_NEW_DATA 37 +#define COL_HILIGHT 38 +#define COL_NEW_MSG 39 +#define COL_AWAY 40 + +void palette_alloc (GtkWidget * widget); +void palette_load (void); +void palette_save (void); diff --git a/src/fe-gtk/pixmaps.c b/src/fe-gtk/pixmaps.c new file mode 100644 index 00000000..3d85c3b0 --- /dev/null +++ b/src/fe-gtk/pixmaps.c @@ -0,0 +1,123 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include "fe-gtk.h" +#include "../common/xchat.h" +#include "../common/fe.h" + +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <gdk-pixbuf/gdk-pixdata.h> +#include <gtk/gtkstock.h> + +#include "../pixmaps/inline_pngs.h" + +GdkPixbuf *pix_xchat; +GdkPixbuf *pix_book; + +GdkPixbuf *pix_purple; +GdkPixbuf *pix_red; +GdkPixbuf *pix_op; +GdkPixbuf *pix_hop; +GdkPixbuf *pix_voice; + +GdkPixbuf *pix_tray_msg; +GdkPixbuf *pix_tray_hilight; +GdkPixbuf *pix_tray_file; + +GdkPixbuf *pix_channel; +GdkPixbuf *pix_dialog; +GdkPixbuf *pix_server; +GdkPixbuf *pix_util; + + +static GdkPixmap * +pixmap_load_from_file_real (char *file) +{ + GdkPixbuf *img; + GdkPixmap *pixmap; + + img = gdk_pixbuf_new_from_file (file, 0); + if (!img) + return NULL; + gdk_pixbuf_render_pixmap_and_mask (img, &pixmap, NULL, 128); + gdk_pixbuf_unref (img); + + return pixmap; +} + +GdkPixmap * +pixmap_load_from_file (char *filename) +{ + char buf[256]; + GdkPixmap *pix; + + if (filename[0] == '\0') + return NULL; + + pix = pixmap_load_from_file_real (filename); + if (pix == NULL) + { + strcpy (buf, "Cannot open:\n\n"); + strncpy (buf + 14, filename, sizeof (buf) - 14); + buf[sizeof (buf) - 1] = 0; + fe_message (buf, FE_MSG_ERROR); + } + + return pix; +} + +#define LOADPIX(vv,pp,ff) \ + vv = gdk_pixbuf_new_from_file (XCHATSHAREDIR"/xchat/"ff, 0); \ + if (!vv) \ + vv = gdk_pixbuf_new_from_inline (-1, pp, FALSE, 0); + +#define LOADPIX_DISKONLY(vv,ff) \ + vv = gdk_pixbuf_new_from_file (XCHATSHAREDIR"/xchat/"ff, 0); + +#define EXT ".png" + +void +pixmaps_init (void) +{ + pix_book = gdk_pixbuf_new_from_inline (-1, bookpng, FALSE, 0); + + /* used in About window, tray icon and WindowManager icon. */ + LOADPIX (pix_xchat, xchatpng, "xchat"EXT); + + /* userlist icons, with inlined defaults */ + LOADPIX (pix_hop, hoppng, "hop"EXT); + LOADPIX (pix_purple, purplepng, "purple"EXT); + LOADPIX (pix_red, redpng, "red"EXT); + LOADPIX (pix_op, oppng, "op"EXT); + LOADPIX (pix_voice, voicepng, "voice"EXT); + + /* tray icons, with inlined defaults */ + LOADPIX (pix_tray_msg, traymsgpng, "message"EXT); + LOADPIX (pix_tray_hilight, trayhilightpng, "highlight"EXT); + LOADPIX (pix_tray_file, trayfilepng, "fileoffer"EXT); + + /* treeview icons, no defaults, load from disk only */ + LOADPIX_DISKONLY (pix_channel, "channel"EXT); + LOADPIX_DISKONLY (pix_dialog, "dialog"EXT); + LOADPIX_DISKONLY (pix_server, "server"EXT); + LOADPIX_DISKONLY (pix_util, "util"EXT); +} diff --git a/src/fe-gtk/pixmaps.h b/src/fe-gtk/pixmaps.h new file mode 100644 index 00000000..91b9696e --- /dev/null +++ b/src/fe-gtk/pixmaps.h @@ -0,0 +1,19 @@ +extern GdkPixbuf *pix_book; +extern GdkPixbuf *pix_hop; +extern GdkPixbuf *pix_purple; +extern GdkPixbuf *pix_red; +extern GdkPixbuf *pix_op; +extern GdkPixbuf *pix_voice; +extern GdkPixbuf *pix_xchat; + +extern GdkPixbuf *pix_tray_msg; +extern GdkPixbuf *pix_tray_hilight; +extern GdkPixbuf *pix_tray_file; + +extern GdkPixbuf *pix_channel; +extern GdkPixbuf *pix_dialog; +extern GdkPixbuf *pix_server; +extern GdkPixbuf *pix_util; + +extern GdkPixmap *pixmap_load_from_file (char *file); +extern void pixmaps_init (void); diff --git a/src/fe-gtk/plugin-tray.c b/src/fe-gtk/plugin-tray.c new file mode 100644 index 00000000..8603abf4 --- /dev/null +++ b/src/fe-gtk/plugin-tray.c @@ -0,0 +1,852 @@ +/* Copyright (C) 2006-2007 Peter Zelezny. */ + +#include <string.h> +#include <unistd.h> +#include "../common/xchat-plugin.h" +#include "../common/xchat.h" +#include "../common/xchatc.h" +#include "../common/inbound.h" +#include "../common/server.h" +#include "../common/fe.h" +#include "../common/util.h" +#include "fe-gtk.h" +#include "pixmaps.h" +#include "maingui.h" +#include "menu.h" +#include <gtk/gtk.h> + +#define LIBNOTIFY + +typedef enum /* current icon status */ +{ + TS_NONE, + TS_MESSAGE, + TS_HIGHLIGHT, + TS_FILEOFFER, + TS_CUSTOM /* plugin */ +} TrayStatus; + +typedef enum +{ + WS_FOCUSED, + WS_NORMAL, + WS_HIDDEN +} WinStatus; + +typedef GdkPixbuf* TrayIcon; +#define tray_icon_from_file(f) gdk_pixbuf_new_from_file(f,NULL) +#define tray_icon_free(i) g_object_unref(i) + +#define ICON_NORMAL pix_xchat +#define ICON_MSG pix_tray_msg +#define ICON_HILIGHT pix_tray_hilight +#define ICON_FILE pix_tray_file +#define TIMEOUT 500 + +static GtkStatusIcon *sticon; +static gint flash_tag; +static TrayStatus tray_status; +static xchat_plugin *ph; + +static TrayIcon custom_icon1; +static TrayIcon custom_icon2; + +static int tray_priv_count = 0; +static int tray_pub_count = 0; +static int tray_hilight_count = 0; +static int tray_file_count = 0; + + +void tray_apply_setup (void); + + +static WinStatus +tray_get_window_status (void) +{ + const char *st; + + st = xchat_get_info (ph, "win_status"); + + if (!st) + return WS_HIDDEN; + + if (!strcmp (st, "active")) + return WS_FOCUSED; + + if (!strcmp (st, "hidden")) + return WS_HIDDEN; + + return WS_NORMAL; +} + +static int +tray_count_channels (void) +{ + int cons = 0; + GSList *list; + session *sess; + + for (list = sess_list; list; list = list->next) + { + sess = list->data; + if (sess->server->connected && sess->channel[0] && + sess->type == SESS_CHANNEL) + cons++; + } + return cons; +} + +static int +tray_count_networks (void) +{ + int cons = 0; + GSList *list; + + for (list = serv_list; list; list = list->next) + { + if (((server *)list->data)->connected) + cons++; + } + return cons; +} + +void +fe_tray_set_tooltip (const char *text) +{ + if (sticon) + gtk_status_icon_set_tooltip (sticon, text); +} + +#ifdef LIBNOTIFY + +/* dynamic access to libnotify.so */ + +static void *nn_mod = NULL; +/* prototypes */ +static gboolean (*nn_init) (char *); +static void (*nn_uninit) (void); +/* recent versions of libnotify don't take the fourth GtkWidget argument, but passing an + * extra NULL argument will be fine */ +static void *(*nn_new) (const gchar *summary, const gchar *message, const gchar *icon, gpointer dummy); +static gboolean (*nn_show) (void *noti, GError **error); +static void (*nn_set_timeout) (void *noti, gint timeout); + +static void +libnotify_cleanup (void) +{ + if (nn_mod) + { + nn_uninit (); + g_module_close (nn_mod); + nn_mod = NULL; + } +} + +static gboolean +libnotify_notify_new (const char *title, const char *text, GtkStatusIcon *icon) +{ + void *noti; + + if (!nn_mod) + { + nn_mod = g_module_open ("libnotify", G_MODULE_BIND_LAZY); + if (!nn_mod) + { + nn_mod = g_module_open ("libnotify.so.1", G_MODULE_BIND_LAZY); + if (!nn_mod) + return FALSE; + } + + if (!g_module_symbol (nn_mod, "notify_init", (gpointer)&nn_init)) + goto bad; + if (!g_module_symbol (nn_mod, "notify_uninit", (gpointer)&nn_uninit)) + goto bad; + if (!g_module_symbol (nn_mod, "notify_notification_new", (gpointer)&nn_new)) + goto bad; + if (!g_module_symbol (nn_mod, "notify_notification_show", (gpointer)&nn_show)) + goto bad; + if (!g_module_symbol (nn_mod, "notify_notification_set_timeout", (gpointer)&nn_set_timeout)) + goto bad; + if (!nn_init (PACKAGE_NAME)) + goto bad; + } + + text = strip_color (text, -1, STRIP_ALL|STRIP_ESCMARKUP); + title = strip_color (title, -1, STRIP_ALL); + noti = nn_new (title, text, XCHATSHAREDIR"/pixmaps/xchat.png", NULL); + g_free ((char *)title); + g_free ((char *)text); + + nn_set_timeout (noti, prefs.input_balloon_time*1000); + nn_show (noti, NULL); + g_object_unref (G_OBJECT (noti)); + + return TRUE; + +bad: + g_module_close (nn_mod); + nn_mod = NULL; + return FALSE; +} + +#endif + +void +fe_tray_set_balloon (const char *title, const char *text) +{ +#ifndef WIN32 + const char *argv[8]; + const char *path; + char time[16]; + WinStatus ws; + + /* no balloons if the window is focused */ + ws = tray_get_window_status (); + if (ws == WS_FOCUSED) + return; + + /* bit 1 of flags means "no balloons unless hidden/iconified" */ + if (ws != WS_HIDDEN && (prefs.gui_tray_flags & 2)) + return; + + /* FIXME: this should close the current balloon */ + if (!text) + return; + +#ifdef LIBNOTIFY + /* try it via libnotify.so */ + if (libnotify_notify_new (title, text, sticon)) + return; /* success */ +#endif + + /* try it the crude way */ + path = g_find_program_in_path ("notify-send"); + if (path) + { + sprintf(time, "%d000",prefs.input_balloon_time); + argv[0] = path; + argv[1] = "-i"; + argv[2] = "gtk-dialog-info"; + if (access (XCHATSHAREDIR"/pixmaps/xchat.png", R_OK) == 0) + argv[2] = XCHATSHAREDIR"/pixmaps/xchat.png"; + argv[3] = "-t"; + argv[4] = time; + argv[5] = title; + text = strip_color (text, -1, STRIP_ALL|STRIP_ESCMARKUP); + argv[6] = text; + argv[7] = NULL; + xchat_execv (argv); + g_free ((char *)path); + g_free ((char *)text); + } + else + { + /* show this error only once */ + static unsigned char said_it = FALSE; + if (!said_it) + { + said_it = TRUE; + fe_message (_("Cannot find 'notify-send' to open balloon alerts.\nPlease install libnotify."), FE_MSG_ERROR); + } + } +#endif +} + +static void +tray_set_balloonf (const char *text, const char *format, ...) +{ + va_list args; + char *buf; + + va_start (args, format); + buf = g_strdup_vprintf (format, args); + va_end (args); + + fe_tray_set_balloon (buf, text); + g_free (buf); +} + +static void +tray_set_tipf (const char *format, ...) +{ + va_list args; + char *buf; + + va_start (args, format); + buf = g_strdup_vprintf (format, args); + va_end (args); + + fe_tray_set_tooltip (buf); + g_free (buf); +} + +static void +tray_stop_flash (void) +{ + int nets, chans; + + if (flash_tag) + { + g_source_remove (flash_tag); + flash_tag = 0; + } + + if (sticon) + { + gtk_status_icon_set_from_pixbuf (sticon, ICON_NORMAL); + nets = tray_count_networks (); + chans = tray_count_channels (); + if (nets) + tray_set_tipf (_("XChat: Connected to %u networks and %u channels"), + nets, chans); + else + tray_set_tipf ("XChat: %s", _("Not connected.")); + } + + if (custom_icon1) + { + tray_icon_free (custom_icon1); + custom_icon1 = NULL; + } + + if (custom_icon2) + { + tray_icon_free (custom_icon2); + custom_icon2 = NULL; + } + + tray_status = TS_NONE; +} + +static void +tray_reset_counts (void) +{ + tray_priv_count = 0; + tray_pub_count = 0; + tray_hilight_count = 0; + tray_file_count = 0; +} + +static int +tray_timeout_cb (TrayIcon icon) +{ + if (custom_icon1) + { + if (gtk_status_icon_get_pixbuf (sticon) == custom_icon1) + { + if (custom_icon2) + gtk_status_icon_set_from_pixbuf (sticon, custom_icon2); + else + gtk_status_icon_set_from_pixbuf (sticon, ICON_NORMAL); + } + else + { + gtk_status_icon_set_from_pixbuf (sticon, custom_icon1); + } + } + else + { + if (gtk_status_icon_get_pixbuf (sticon) == ICON_NORMAL) + gtk_status_icon_set_from_pixbuf (sticon, icon); + else + gtk_status_icon_set_from_pixbuf (sticon, ICON_NORMAL); + } + return 1; +} + +static void +tray_set_flash (TrayIcon icon) +{ + if (!sticon) + return; + + /* already flashing the same icon */ + if (flash_tag && gtk_status_icon_get_pixbuf (sticon) == icon) + return; + + /* no flashing if window is focused */ + if (tray_get_window_status () == WS_FOCUSED) + return; + + tray_stop_flash (); + + gtk_status_icon_set_from_pixbuf (sticon, icon); + flash_tag = g_timeout_add (TIMEOUT, (GSourceFunc) tray_timeout_cb, icon); +} + +void +fe_tray_set_flash (const char *filename1, const char *filename2, int tout) +{ + tray_apply_setup (); + if (!sticon) + return; + + tray_stop_flash (); + + if (tout == -1) + tout = TIMEOUT; + + custom_icon1 = tray_icon_from_file (filename1); + if (filename2) + custom_icon2 = tray_icon_from_file (filename2); + + gtk_status_icon_set_from_pixbuf (sticon, custom_icon1); + flash_tag = g_timeout_add (tout, (GSourceFunc) tray_timeout_cb, NULL); + tray_status = TS_CUSTOM; +} + +void +fe_tray_set_icon (feicon icon) +{ + tray_apply_setup (); + if (!sticon) + return; + + tray_stop_flash (); + + switch (icon) + { + case FE_ICON_NORMAL: + break; + case FE_ICON_MESSAGE: + tray_set_flash (ICON_MSG); + break; + case FE_ICON_HIGHLIGHT: + case FE_ICON_PRIVMSG: + tray_set_flash (ICON_HILIGHT); + break; + case FE_ICON_FILEOFFER: + tray_set_flash (ICON_FILE); + } +} + +void +fe_tray_set_file (const char *filename) +{ + tray_apply_setup (); + if (!sticon) + return; + + tray_stop_flash (); + + if (filename) + { + custom_icon1 = tray_icon_from_file (filename); + gtk_status_icon_set_from_pixbuf (sticon, custom_icon1); + tray_status = TS_CUSTOM; + } +} + +gboolean +tray_toggle_visibility (gboolean force_hide) +{ + static int x, y; + static GdkScreen *screen; + GtkWindow *win; + + if (!sticon) + return FALSE; + + /* ph may have an invalid context now */ + xchat_set_context (ph, xchat_find_context (ph, NULL, NULL)); + + win = (GtkWindow *)xchat_get_info (ph, "win_ptr"); + + tray_stop_flash (); + tray_reset_counts (); + + if (!win) + return FALSE; + +#if GTK_CHECK_VERSION(2,20,0) + if (force_hide || gtk_widget_get_visible (GTK_WIDGET (win))) +#else + if (force_hide || GTK_WIDGET_VISIBLE (win)) +#endif + { + gtk_window_get_position (win, &x, &y); + screen = gtk_window_get_screen (win); + gtk_widget_hide (GTK_WIDGET (win)); + } + else + { + gtk_window_set_screen (win, screen); + gtk_window_move (win, x, y); + gtk_widget_show (GTK_WIDGET (win)); + gtk_window_present (win); + } + + return TRUE; +} + +static void +tray_menu_restore_cb (GtkWidget *item, gpointer userdata) +{ + tray_toggle_visibility (FALSE); +} + +static void +tray_menu_quit_cb (GtkWidget *item, gpointer userdata) +{ + mg_open_quit_dialog (FALSE); +} + +/* returns 0-mixed 1-away 2-back */ + +static int +tray_find_away_status (void) +{ + GSList *list; + server *serv; + int away = 0; + int back = 0; + + for (list = serv_list; list; list = list->next) + { + serv = list->data; + + if (serv->is_away || serv->reconnect_away) + away++; + else + back++; + } + + if (away && back) + return 0; + + if (away) + return 1; + + return 2; +} + +static void +tray_foreach_server (GtkWidget *item, char *cmd) +{ + GSList *list; + server *serv; + + for (list = serv_list; list; list = list->next) + { + serv = list->data; + if (serv->connected) + handle_command (serv->server_session, cmd, FALSE); + } +} + +static GtkWidget * +tray_make_item (GtkWidget *menu, char *label, void *callback, void *userdata) +{ + GtkWidget *item; + + if (label) + item = gtk_menu_item_new_with_mnemonic (label); + else + item = gtk_menu_item_new (); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (callback), userdata); + gtk_widget_show (item); + + return item; +} + +static void +tray_toggle_cb (GtkCheckMenuItem *item, unsigned int *setting) +{ + *setting = item->active; +} + +static void +blink_item (unsigned int *setting, GtkWidget *menu, char *label) +{ + menu_toggle_item (label, menu, tray_toggle_cb, setting, *setting); +} + +static void +tray_menu_destroy (GtkWidget *menu, gpointer userdata) +{ + gtk_widget_destroy (menu); + g_object_unref (menu); +} + +static void +tray_menu_cb (GtkWidget *widget, guint button, guint time, gpointer userdata) +{ + GtkWidget *menu; + GtkWidget *submenu; + GtkWidget *item; + int away_status; + + /* ph may have an invalid context now */ + xchat_set_context (ph, xchat_find_context (ph, NULL, NULL)); + + menu = gtk_menu_new (); + /*gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget));*/ + + if (tray_get_window_status () == WS_HIDDEN) + tray_make_item (menu, _("_Restore"), tray_menu_restore_cb, NULL); + else + tray_make_item (menu, _("_Hide"), tray_menu_restore_cb, NULL); + tray_make_item (menu, NULL, tray_menu_quit_cb, NULL); + + submenu = mg_submenu (menu, _("_Blink on")); + blink_item (&prefs.input_tray_chans, submenu, _("Channel Message")); + blink_item (&prefs.input_tray_priv, submenu, _("Private Message")); + blink_item (&prefs.input_tray_hilight, submenu, _("Highlighted Message")); + /*blink_item (BIT_FILEOFFER, submenu, _("File Offer"));*/ + + submenu = mg_submenu (menu, _("_Change status")); + away_status = tray_find_away_status (); + item = tray_make_item (submenu, _("_Away"), tray_foreach_server, "away"); + if (away_status == 1) + gtk_widget_set_sensitive (item, FALSE); + item = tray_make_item (submenu, _("_Back"), tray_foreach_server, "back"); + if (away_status == 2) + gtk_widget_set_sensitive (item, FALSE); + + tray_make_item (menu, NULL, tray_menu_quit_cb, NULL); + mg_create_icon_item (_("_Quit"), GTK_STOCK_QUIT, menu, tray_menu_quit_cb, NULL); + + menu_add_plugin_items (menu, "\x5$TRAY", NULL); + + g_object_ref (menu); + g_object_ref_sink (menu); + g_object_unref (menu); + g_signal_connect (G_OBJECT (menu), "selection-done", + G_CALLBACK (tray_menu_destroy), NULL); + + gtk_menu_popup (GTK_MENU (menu), NULL, NULL, gtk_status_icon_position_menu, + userdata, button, time); +} + +static void +tray_init (void) +{ + flash_tag = 0; + tray_status = TS_NONE; + custom_icon1 = NULL; + custom_icon2 = NULL; + + sticon = gtk_status_icon_new_from_pixbuf (ICON_NORMAL); + if (!sticon) + return; + g_signal_connect (G_OBJECT (sticon), "popup-menu", + G_CALLBACK (tray_menu_cb), sticon); + g_signal_connect (G_OBJECT (sticon), "activate", + G_CALLBACK (tray_menu_restore_cb), NULL); +} + +static int +tray_hilight_cb (char *word[], void *userdata) +{ + /*if (tray_status == TS_HIGHLIGHT) + return XCHAT_EAT_NONE;*/ + + if (prefs.input_tray_hilight) + { + tray_set_flash (ICON_HILIGHT); + + /* FIXME: hides any previous private messages */ + tray_hilight_count++; + if (tray_hilight_count == 1) + tray_set_tipf (_("XChat: Highlighted message from: %s (%s)"), + word[1], xchat_get_info (ph, "channel")); + else + tray_set_tipf (_("XChat: %u highlighted messages, latest from: %s (%s)"), + tray_hilight_count, word[1], xchat_get_info (ph, "channel")); + } + + if (prefs.input_balloon_hilight) + tray_set_balloonf (word[2], _("XChat: Highlighted message from: %s (%s)"), + word[1], xchat_get_info (ph, "channel")); + + return XCHAT_EAT_NONE; +} + +static int +tray_message_cb (char *word[], void *userdata) +{ + if (/*tray_status == TS_MESSAGE ||*/ tray_status == TS_HIGHLIGHT) + return XCHAT_EAT_NONE; + + if (prefs.input_tray_chans) + { + tray_set_flash (ICON_MSG); + + tray_pub_count++; + if (tray_pub_count == 1) + tray_set_tipf (_("XChat: New public message from: %s (%s)"), + word[1], xchat_get_info (ph, "channel")); + else + tray_set_tipf (_("XChat: %u new public messages."), tray_pub_count); + } + + if (prefs.input_balloon_chans) + tray_set_balloonf (word[2], _("XChat: New public message from: %s (%s)"), + word[1], xchat_get_info (ph, "channel")); + + return XCHAT_EAT_NONE; +} + +static void +tray_priv (char *from, char *text) +{ + const char *network; + + if (alert_match_word (from, prefs.irc_no_hilight)) + return; + + tray_set_flash (ICON_HILIGHT); + + network = xchat_get_info (ph, "network"); + if (!network) + network = xchat_get_info (ph, "server"); + + tray_priv_count++; + if (tray_priv_count == 1) + tray_set_tipf (_("XChat: Private message from: %s (%s)"), + from, network); + else + tray_set_tipf (_("XChat: %u private messages, latest from: %s (%s)"), + tray_priv_count, from, network); + + if (prefs.input_balloon_priv) + tray_set_balloonf (text, _("XChat: Private message from: %s (%s)"), + from, network); +} + +static int +tray_priv_cb (char *word[], void *userdata) +{ + /*if (tray_status == TS_HIGHLIGHT) + return XCHAT_EAT_NONE;*/ + + if (prefs.input_tray_priv) + tray_priv (word[1], word[2]); + + return XCHAT_EAT_NONE; +} + +static int +tray_invited_cb (char *word[], void *userdata) +{ + /*if (tray_status == TS_HIGHLIGHT) + return XCHAT_EAT_NONE;*/ + + if (prefs.input_tray_priv) + tray_priv (word[2], "Invited"); + + return XCHAT_EAT_NONE; +} + +static int +tray_dcc_cb (char *word[], void *userdata) +{ + const char *network; + +/* if (tray_status == TS_FILEOFFER) + return XCHAT_EAT_NONE;*/ + + network = xchat_get_info (ph, "network"); + if (!network) + network = xchat_get_info (ph, "server"); + + if (prefs.input_tray_priv) + { + tray_set_flash (ICON_FILE); + + tray_file_count++; + if (tray_file_count == 1) + tray_set_tipf (_("XChat: File offer from: %s (%s)"), + word[1], network); + else + tray_set_tipf (_("XChat: %u file offers, latest from: %s (%s)"), + tray_file_count, word[1], network); + } + + if (prefs.input_balloon_priv) + tray_set_balloonf ("", _("XChat: File offer from: %s (%s)"), + word[1], network); + + return XCHAT_EAT_NONE; +} + +static int +tray_focus_cb (char *word[], void *userdata) +{ + tray_stop_flash (); + tray_reset_counts (); + return XCHAT_EAT_NONE; +} + +static void +tray_cleanup (void) +{ + tray_stop_flash (); + + if (sticon) + { + g_object_unref ((GObject *)sticon); + sticon = NULL; + } +} + +void +tray_apply_setup (void) +{ + if (sticon) + { + if (!prefs.gui_tray) + tray_cleanup (); + } + else + { + if (prefs.gui_tray) + tray_init (); + } +} + +int +tray_plugin_init (xchat_plugin *plugin_handle, char **plugin_name, + char **plugin_desc, char **plugin_version, char *arg) +{ + /* we need to save this for use with any xchat_* functions */ + ph = plugin_handle; + + *plugin_name = ""; + *plugin_desc = ""; + *plugin_version = ""; + + xchat_hook_print (ph, "Channel Msg Hilight", -1, tray_hilight_cb, NULL); + xchat_hook_print (ph, "Channel Action Hilight", -1, tray_hilight_cb, NULL); + + xchat_hook_print (ph, "Channel Message", -1, tray_message_cb, NULL); + xchat_hook_print (ph, "Channel Action", -1, tray_message_cb, NULL); + xchat_hook_print (ph, "Channel Notice", -1, tray_message_cb, NULL); + + xchat_hook_print (ph, "Private Message", -1, tray_priv_cb, NULL); + xchat_hook_print (ph, "Private Message to Dialog", -1, tray_priv_cb, NULL); + xchat_hook_print (ph, "Notice", -1, tray_priv_cb, NULL); + xchat_hook_print (ph, "Invited", -1, tray_invited_cb, NULL); + + xchat_hook_print (ph, "DCC Offer", -1, tray_dcc_cb, NULL); + + xchat_hook_print (ph, "Focus Window", -1, tray_focus_cb, NULL); + + if (prefs.gui_tray) + tray_init (); + + return 1; /* return 1 for success */ +} + +int +tray_plugin_deinit (xchat_plugin *plugin_handle) +{ +#ifdef WIN32 + tray_cleanup (); +#elif defined(LIBNOTIFY) + libnotify_cleanup (); +#endif + return 1; +} diff --git a/src/fe-gtk/plugin-tray.h b/src/fe-gtk/plugin-tray.h new file mode 100644 index 00000000..d54be5a4 --- /dev/null +++ b/src/fe-gtk/plugin-tray.h @@ -0,0 +1,4 @@ +int tray_plugin_init (void *, char **, char **, char **, char *); +int tray_plugin_deinit (void *); +gboolean tray_toggle_visibility (gboolean force_hide); +void tray_apply_setup (void); diff --git a/src/fe-gtk/plugingui.c b/src/fe-gtk/plugingui.c new file mode 100644 index 00000000..de59e649 --- /dev/null +++ b/src/fe-gtk/plugingui.c @@ -0,0 +1,239 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include "fe-gtk.h" + +#include <gtk/gtkdialog.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkbox.h> +#include <gtk/gtkscrolledwindow.h> + +#include <gtk/gtkliststore.h> +#include <gtk/gtktreeview.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtkcellrenderertext.h> + +#include "../common/xchat.h" +#define PLUGIN_C +typedef struct session xchat_context; +#include "../common/xchat-plugin.h" +#include "../common/plugin.h" +#include "../common/util.h" +#include "../common/outbound.h" +#include "../common/fe.h" +#include "../common/xchatc.h" +#include "gtkutil.h" + +/* model for the plugin treeview */ +enum +{ + NAME_COLUMN, + VERSION_COLUMN, + FILE_COLUMN, + DESC_COLUMN, + N_COLUMNS +}; + +static GtkWidget *plugin_window = NULL; + + +static GtkWidget * +plugingui_treeview_new (GtkWidget *box) +{ + GtkListStore *store; + GtkWidget *view; + GtkTreeViewColumn *col; + int col_id; + + store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_STRING); + g_return_val_if_fail (store != NULL, NULL); + view = gtkutil_treeview_new (box, GTK_TREE_MODEL (store), NULL, + NAME_COLUMN, _("Name"), + VERSION_COLUMN, _("Version"), + FILE_COLUMN, _("File"), + DESC_COLUMN, _("Description"), -1); + gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE); + for (col_id=0; (col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), col_id)); + col_id++) + gtk_tree_view_column_set_alignment (col, 0.5); + + gtk_widget_show (view); + return view; +} + +static void +plugingui_close_button (GtkWidget * wid, gpointer a) +{ + gtk_widget_destroy (plugin_window); +} + +static void +plugingui_close (GtkWidget * wid, gpointer a) +{ + plugin_window = NULL; +} + +extern GSList *plugin_list; + +void +fe_pluginlist_update (void) +{ + xchat_plugin *pl; + GSList *list; + GtkTreeView *view; + GtkListStore *store; + GtkTreeIter iter; + + if (!plugin_window) + return; + + view = g_object_get_data (G_OBJECT (plugin_window), "view"); + store = GTK_LIST_STORE (gtk_tree_view_get_model (view)); + gtk_list_store_clear (store); + + list = plugin_list; + while (list) + { + pl = list->data; + if (pl->version[0] != 0) + { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, NAME_COLUMN, pl->name, + VERSION_COLUMN, pl->version, + FILE_COLUMN, file_part (pl->filename), + DESC_COLUMN, pl->desc, -1); + } + list = list->next; + } +} + +static void +plugingui_load_cb (session *sess, char *file) +{ + if (file) + { + char *buf = malloc (strlen (file) + 9); + + if (strchr (file, ' ')) + sprintf (buf, "LOAD \"%s\"", file); + else + sprintf (buf, "LOAD %s", file); + handle_command (sess, buf, FALSE); + free (buf); + } +} + +void +plugingui_load (void) +{ + gtkutil_file_req (_("Select a Plugin or Script to load"), plugingui_load_cb, + current_sess, NULL, FRF_ADDFOLDER); +} + +static void +plugingui_loadbutton_cb (GtkWidget * wid, gpointer unused) +{ + plugingui_load (); +} + +static void +plugingui_unload (GtkWidget * wid, gpointer unused) +{ + int len; + char *modname, *file, *buf; + GtkTreeView *view; + GtkTreeIter iter; + + view = g_object_get_data (G_OBJECT (plugin_window), "view"); + if (!gtkutil_treeview_get_selected (view, &iter, NAME_COLUMN, &modname, + FILE_COLUMN, &file, -1)) + return; + + len = strlen (file); +#ifdef WIN32 + if (len > 4 && strcasecmp (file + len - 4, ".dll") == 0) +#else +#if defined(__hpux) + if (len > 3 && strcasecmp (file + len - 3, ".sl") == 0) +#else + if (len > 3 && strcasecmp (file + len - 3, ".so") == 0) +#endif +#endif + { + if (plugin_kill (modname, FALSE) == 2) + fe_message (_("That plugin is refusing to unload.\n"), FE_MSG_ERROR); + } else + { + /* let python.so or perl.so handle it */ + buf = malloc (strlen (file) + 10); + if (strchr (file, ' ')) + sprintf (buf, "UNLOAD \"%s\"", file); + else + sprintf (buf, "UNLOAD %s", file); + handle_command (current_sess, buf, FALSE); + free (buf); + } + + g_free (modname); + g_free (file); +} + +void +plugingui_open (void) +{ + GtkWidget *view; + GtkWidget *vbox, *action_area; + + if (plugin_window) + { + gtk_window_present (GTK_WINDOW (plugin_window)); + return; + } + + plugin_window = gtk_dialog_new (); + g_signal_connect (G_OBJECT (plugin_window), "destroy", + G_CALLBACK (plugingui_close), 0); + gtk_window_set_default_size (GTK_WINDOW (plugin_window), 500, 250); + vbox = GTK_DIALOG (plugin_window)->vbox; + action_area = GTK_DIALOG (plugin_window)->action_area; + gtk_container_set_border_width (GTK_CONTAINER (vbox), 4); + gtk_window_set_position (GTK_WINDOW (plugin_window), GTK_WIN_POS_CENTER); + gtk_window_set_title (GTK_WINDOW (plugin_window), _("XChat: Plugins and Scripts")); + + view = plugingui_treeview_new (vbox); + g_object_set_data (G_OBJECT (plugin_window), "view", view); + + gtkutil_button (action_area, GTK_STOCK_REVERT_TO_SAVED, NULL, + plugingui_loadbutton_cb, NULL, _("_Load...")); + + gtkutil_button (action_area, GTK_STOCK_DELETE, NULL, + plugingui_unload, NULL, _("_UnLoad")); + + gtkutil_button (action_area, + GTK_STOCK_CLOSE, NULL, plugingui_close_button, + NULL, _("_Close")); + + fe_pluginlist_update (); + + gtk_widget_show (plugin_window); +} diff --git a/src/fe-gtk/plugingui.h b/src/fe-gtk/plugingui.h new file mode 100644 index 00000000..945d9a01 --- /dev/null +++ b/src/fe-gtk/plugingui.h @@ -0,0 +1,2 @@ +void plugingui_open (void); +void plugingui_load (void); diff --git a/src/fe-gtk/rawlog.c b/src/fe-gtk/rawlog.c new file mode 100644 index 00000000..56ca0510 --- /dev/null +++ b/src/fe-gtk/rawlog.c @@ -0,0 +1,152 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> + +#include "fe-gtk.h" + +#include <gtk/gtkbutton.h> +#include <gtk/gtkhbbox.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkvscrollbar.h> +#include <gtk/gtkstock.h> + +#include "../common/xchat.h" +#include "../common/xchatc.h" +#include "../common/cfgfiles.h" +#include "../common/server.h" +#include "gtkutil.h" +#include "palette.h" +#include "maingui.h" +#include "rawlog.h" +#include "xtext.h" + + +static void +close_rawlog (GtkWidget *wid, server *serv) +{ + if (is_server (serv)) + serv->gui->rawlog_window = 0; +} + +static void +rawlog_save (server *serv, char *file) +{ + int fh = -1; + + if (file) + { + if (serv->gui->rawlog_window) + fh = xchat_open_file (file, O_TRUNC | O_WRONLY | O_CREAT, + 0600, XOF_DOMODE | XOF_FULLPATH); + if (fh != -1) + { + gtk_xtext_save (GTK_XTEXT (serv->gui->rawlog_textlist), fh); + close (fh); + } + } +} + +static int +rawlog_clearbutton (GtkWidget * wid, server *serv) +{ + gtk_xtext_clear (GTK_XTEXT (serv->gui->rawlog_textlist)->buffer, 0); + return FALSE; +} + +static int +rawlog_savebutton (GtkWidget * wid, server *serv) +{ + gtkutil_file_req (_("Save As..."), rawlog_save, serv, NULL, FRF_WRITE); + return FALSE; +} + +void +open_rawlog (struct server *serv) +{ + GtkWidget *hbox, *vscrollbar, *vbox; + char tbuf[256]; + + if (serv->gui->rawlog_window) + { + mg_bring_tofront (serv->gui->rawlog_window); + return; + } + + snprintf (tbuf, sizeof tbuf, _("XChat: Rawlog (%s)"), serv->servername); + serv->gui->rawlog_window = + mg_create_generic_tab ("RawLog", tbuf, FALSE, TRUE, close_rawlog, serv, + 640, 320, &vbox, serv); + + hbox = gtk_hbox_new (FALSE, 2); + gtk_container_add (GTK_CONTAINER (vbox), hbox); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 4); + gtk_widget_show (hbox); + + serv->gui->rawlog_textlist = gtk_xtext_new (colors, 0); + gtk_xtext_set_tint (GTK_XTEXT (serv->gui->rawlog_textlist), prefs.tint_red, prefs.tint_green, prefs.tint_blue); + gtk_xtext_set_background (GTK_XTEXT (serv->gui->rawlog_textlist), + channelwin_pix, prefs.transparent); + + gtk_container_add (GTK_CONTAINER (hbox), serv->gui->rawlog_textlist); + gtk_xtext_set_font (GTK_XTEXT (serv->gui->rawlog_textlist), prefs.font_normal); + GTK_XTEXT (serv->gui->rawlog_textlist)->ignore_hidden = 1; + gtk_widget_show (serv->gui->rawlog_textlist); + + vscrollbar = gtk_vscrollbar_new (GTK_XTEXT (serv->gui->rawlog_textlist)->adj); + gtk_box_pack_start (GTK_BOX (hbox), vscrollbar, FALSE, FALSE, 0); + show_and_unfocus (vscrollbar); + + hbox = gtk_hbutton_box_new (); + gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_SPREAD); + gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 0); + gtk_widget_show (hbox); + + gtkutil_button (hbox, GTK_STOCK_CLEAR, NULL, rawlog_clearbutton, + serv, _("Clear rawlog")); + + gtkutil_button (hbox, GTK_STOCK_SAVE_AS, NULL, rawlog_savebutton, + serv, _("Save As...")); + + gtk_widget_show (serv->gui->rawlog_window); +} + +void +fe_add_rawlog (server *serv, char *text, int len, int outbound) +{ + char *new_text; + + if (!serv->gui->rawlog_window) + return; + + new_text = malloc (len + 7); + + len = sprintf (new_text, "\0033>>\017 %s", text); + if (outbound) + { + new_text[1] = '4'; + new_text[2] = '<'; + new_text[3] = '<'; + } + gtk_xtext_append (GTK_XTEXT (serv->gui->rawlog_textlist)->buffer, new_text, len); + free (new_text); +} diff --git a/src/fe-gtk/rawlog.h b/src/fe-gtk/rawlog.h new file mode 100644 index 00000000..db41e2a7 --- /dev/null +++ b/src/fe-gtk/rawlog.h @@ -0,0 +1 @@ +void open_rawlog (server *serv); diff --git a/src/fe-gtk/search.c b/src/fe-gtk/search.c new file mode 100644 index 00000000..d62e79c7 --- /dev/null +++ b/src/fe-gtk/search.c @@ -0,0 +1,159 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include "fe-gtk.h" + +#include <gtk/gtkentry.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkvbox.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkhbbox.h> +#include <gtk/gtkhseparator.h> +#include <gtk/gtkvseparator.h> +#include <gtk/gtkradiobutton.h> +#include <gtk/gtktogglebutton.h> +#include <gdk/gdkkeysyms.h> + +#include "../common/xchat.h" +#include "../common/fe.h" +#include "../common/util.h" +#include "../common/xchatc.h" +#include "gtkutil.h" +#include "xtext.h" +#include "maingui.h" + + +static textentry *last; /* our last search pos */ +static int case_match = 0; +static int search_backward = 0; + + +static void +search_search (session * sess, const gchar *text) +{ + if (!is_session (sess)) + { + fe_message (_("The window you opened this Search " + "for doesn't exist anymore."), FE_MSG_ERROR); + return; + } + + last = gtk_xtext_search (GTK_XTEXT (sess->gui->xtext), text, + last, case_match, search_backward); + if (!last) + fe_message (_("Search hit end, not found."), FE_MSG_ERROR); +} + +static void +search_find_cb (GtkWidget * button, session * sess) +{ + GtkEntry *entry; + const gchar *text; + + entry = g_object_get_data (G_OBJECT (button), "e"); + text = gtk_entry_get_text (entry); + search_search (sess, text); +} + +static void +search_close_cb (GtkWidget * button, GtkWidget * win) +{ + gtk_widget_destroy (win); +} + +static void +search_entry_cb (GtkWidget * entry, session * sess) +{ + search_search (sess, gtk_entry_get_text (GTK_ENTRY (entry))); +} + +static gboolean +search_key_cb (GtkWidget * window, GdkEventKey * key, gpointer userdata) +{ + if (key->keyval == GDK_Escape) + gtk_widget_destroy (window); + return FALSE; +} + +static void +search_caseign_cb (GtkToggleButton * but, session * sess) +{ + case_match = (but->active)? 1: 0; +} + +static void +search_dirbwd_cb (GtkToggleButton * but, session * sess) +{ + search_backward = (but->active)? 1: 0; +} + +void +search_open (session * sess) +{ + GtkWidget *win, *hbox, *vbox, *entry, *wid; + + last = NULL; + win = mg_create_generic_tab ("search", _("XChat: Search"), TRUE, FALSE, + NULL, NULL, 0, 0, &vbox, 0); + gtk_container_set_border_width (GTK_CONTAINER (win), 12); + gtk_box_set_spacing (GTK_BOX (vbox), 4); + + hbox = gtk_hbox_new (0, 10); + gtk_container_add (GTK_CONTAINER (vbox), hbox); + gtk_widget_show (hbox); + + gtkutil_label_new (_("Find:"), hbox); + + entry = gtk_entry_new (); + g_signal_connect (G_OBJECT (entry), "activate", + G_CALLBACK (search_entry_cb), sess); + gtk_container_add (GTK_CONTAINER (hbox), entry); + gtk_widget_show (entry); + gtk_widget_grab_focus (entry); + + wid = gtk_check_button_new_with_mnemonic (_("_Match case")); + GTK_TOGGLE_BUTTON (wid)->active = case_match; + g_signal_connect (G_OBJECT (wid), "toggled", G_CALLBACK (search_caseign_cb), sess); + gtk_container_add (GTK_CONTAINER (vbox), wid); + gtk_widget_show (wid); + + wid = gtk_check_button_new_with_mnemonic (_("Search _backwards")); + GTK_TOGGLE_BUTTON (wid)->active = search_backward; + g_signal_connect (G_OBJECT (wid), "toggled", G_CALLBACK (search_dirbwd_cb), sess); + gtk_container_add (GTK_CONTAINER (vbox), wid); + gtk_widget_show (wid); + + hbox = gtk_hbutton_box_new (); + gtk_box_pack_start (GTK_BOX (vbox), hbox, 0, 0, 4); + gtk_widget_show (hbox); + + gtkutil_button (hbox, GTK_STOCK_CLOSE, 0, search_close_cb, win, + _("_Close")); + wid = gtkutil_button (hbox, GTK_STOCK_FIND, 0, search_find_cb, sess, + _("_Find")); + g_object_set_data (G_OBJECT (wid), "e", entry); + + g_signal_connect (G_OBJECT (win), "key-press-event", G_CALLBACK (search_key_cb), win); + + gtk_widget_show (win); +} diff --git a/src/fe-gtk/search.h b/src/fe-gtk/search.h new file mode 100644 index 00000000..8fa1b628 --- /dev/null +++ b/src/fe-gtk/search.h @@ -0,0 +1 @@ +void search_open (session * sess); diff --git a/src/fe-gtk/servlistgui.c b/src/fe-gtk/servlistgui.c new file mode 100644 index 00000000..2ac0e6c9 --- /dev/null +++ b/src/fe-gtk/servlistgui.c @@ -0,0 +1,1918 @@ +/* X-Chat + * Copyright (C) 2004-2008 Peter Zelezny. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> + +#include <gtk/gtkversion.h> +#include <gtk/gtkcheckbutton.h> +#include <gtk/gtkcellrenderertext.h> +#include <gtk/gtkcomboboxentry.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkhbbox.h> +#include <gtk/gtkhseparator.h> +#include <gtk/gtkimage.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkliststore.h> +#include <gtk/gtkmenuitem.h> +#include <gtk/gtkmessagedialog.h> +#include <gtk/gtkscrolledwindow.h> +#include <gtk/gtkstock.h> +#include <gtk/gtktable.h> +#include <gtk/gtktogglebutton.h> +#include <gtk/gtktree.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtktreeview.h> +#include <gtk/gtkvbbox.h> +#include <gtk/gtkvbox.h> +#include <gtk/gtkwindow.h> +#include <gdk/gdkkeysyms.h> + +#include "../common/xchat.h" +#include "../common/xchatc.h" +#include "../common/servlist.h" +#include "../common/cfgfiles.h" +#include "../common/fe.h" + +#include "fe-gtk.h" +#include "gtkutil.h" +#include "menu.h" +#include "pixmaps.h" + + +/* servlistgui.c globals */ +static GtkWidget *serverlist_win = NULL; +static GtkWidget *networks_tree; /* network TreeView */ +static int ignore_changed = FALSE; +#ifdef WIN32 +static int win_width = 324; +static int win_height = 426; +#else +static int win_width = 364; +static int win_height = 478; +#endif + +/* global user info */ +static GtkWidget *entry_nick1; +static GtkWidget *entry_nick2; +static GtkWidget *entry_nick3; +static GtkWidget *entry_guser; +static GtkWidget *entry_greal; + +/* edit area */ +static GtkWidget *edit_win; +static GtkWidget *edit_entry_nick; +static GtkWidget *edit_entry_nick2; +static GtkWidget *edit_entry_user; +static GtkWidget *edit_entry_real; +static GtkWidget *edit_entry_join; +static GtkWidget *edit_entry_pass; +static GtkWidget *edit_entry_cmd; +static GtkWidget *edit_entry_nickserv; +static GtkWidget *edit_label_nick; +static GtkWidget *edit_label_nick2; +static GtkWidget *edit_label_real; +static GtkWidget *edit_label_user; +static GtkWidget *edit_tree; + +static ircnet *selected_net = NULL; +static ircserver *selected_serv = NULL; +static ircnet *fav_add_net = NULL; /* used in Add/Remove fav context menus */ +static session *servlist_sess; + +static void servlist_network_row_cb (GtkTreeSelection *sel, gpointer user_data); +static GtkWidget *servlist_open_edit (GtkWidget *parent, ircnet *net); + + +static const char *pages[]= +{ + "UTF-8 (Unicode)", + "IRC (Latin/Unicode Hybrid)", + "ISO-8859-15 (Western Europe)", + "ISO-8859-2 (Central Europe)", + "ISO-8859-7 (Greek)", + "ISO-8859-8 (Hebrew)", + "ISO-8859-9 (Turkish)", + "ISO-2022-JP (Japanese)", + "SJIS (Japanese)", + "CP949 (Korean)", + "KOI8-R (Cyrillic)", + "CP1251 (Cyrillic)", + "CP1256 (Arabic)", + "CP1257 (Baltic)", + "GB18030 (Chinese)", + "TIS-620 (Thai)", + NULL +}; + +static void +servlist_select_and_show (GtkTreeView *treeview, GtkTreeIter *iter, + GtkListStore *store) +{ + GtkTreePath *path; + GtkTreeSelection *sel; + + sel = gtk_tree_view_get_selection (treeview); + + /* select this network */ + gtk_tree_selection_select_iter (sel, iter); + /* and make sure it's visible */ + path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), iter); + if (path) + { + gtk_tree_view_scroll_to_cell (treeview, path, NULL, TRUE, 0.5, 0.5); + gtk_tree_view_set_cursor (treeview, path, NULL, FALSE); + gtk_tree_path_free (path); + } +} + +static void +servlist_servers_populate (ircnet *net, GtkWidget *treeview) +{ + GtkListStore *store; + GtkTreeIter iter; + int i; + ircserver *serv; + GSList *list = net->servlist; + + store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (treeview)); + gtk_list_store_clear (store); + + i = 0; + while (list) + { + serv = list->data; + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, serv->hostname, 1, 1, -1); + + if (net->selected == i) + /* select this server */ + servlist_select_and_show (GTK_TREE_VIEW (treeview), &iter, store); + + i++; + list = list->next; + } +} + +static void +servlist_networks_populate_ (GtkWidget *treeview, GSList *netlist, gboolean favorites) +{ + GtkListStore *store; + GtkTreeIter iter; + int i; + ircnet *net; + + if (!netlist) + { + net = servlist_net_add (_("New Network"), "", FALSE); + servlist_server_add (net, "newserver/6667"); + netlist = network_list; + } + store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (treeview)); + gtk_list_store_clear (store); + + i = 0; + while (netlist) + { + net = netlist->data; + if (!favorites || (net->flags & FLAG_FAVORITE)) + { + if (favorites) + gtk_list_store_insert_with_values (store, &iter, 0x7fffffff, 0, net->name, 1, 1, 2, 400, -1); + else + gtk_list_store_insert_with_values (store, &iter, 0x7fffffff, 0, net->name, 1, 1, 2, (net->flags & FLAG_FAVORITE) ? 800 : 400, -1); + if (i == prefs.slist_select) + { + /* select this network */ + servlist_select_and_show (GTK_TREE_VIEW (treeview), &iter, store); + selected_net = net; + } + } + i++; + netlist = netlist->next; + } +} + +static void +servlist_networks_populate (GtkWidget *treeview, GSList *netlist) +{ + servlist_networks_populate_ (treeview, netlist, prefs.slist_fav); +} + +static void +servlist_server_row_cb (GtkTreeSelection *sel, gpointer user_data) +{ + GtkTreeModel *model; + GtkTreeIter iter; + ircserver *serv; + char *servname; + int pos; + + if (!selected_net) + return; + + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + { + gtk_tree_model_get (model, &iter, 0, &servname, -1); + serv = servlist_server_find (selected_net, servname, &pos); + g_free (servname); + if (serv) + selected_net->selected = pos; + selected_serv = serv; + } +} + +static void +servlist_start_editing (GtkTreeView *tree) +{ + GtkTreeSelection *sel; + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreePath *path; + + sel = gtk_tree_view_get_selection (tree); + + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + { + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); + if (path) + { + gtk_tree_view_set_cursor (tree, path, + gtk_tree_view_get_column (tree, 0), TRUE); + gtk_tree_path_free (path); + } + } +} + +static void +servlist_addserver_cb (GtkWidget *item, GtkWidget *treeview) +{ + GtkTreeIter iter; + GtkListStore *store; + + if (!selected_net) + return; + + store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (treeview)); + servlist_server_add (selected_net, "newserver/6667"); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, "newserver/6667", 1, 1, -1); + + /* select this server */ + servlist_select_and_show (GTK_TREE_VIEW (treeview), &iter, store); + /*servlist_start_editing (GTK_TREE_VIEW (treeview));*/ + + servlist_server_row_cb (gtk_tree_view_get_selection (GTK_TREE_VIEW (networks_tree)), NULL); +} + +static void +servlist_addnet_cb (GtkWidget *item, GtkTreeView *treeview) +{ + GtkTreeIter iter; + GtkListStore *store; + ircnet *net; + + net = servlist_net_add (_("New Network"), "", TRUE); + net->encoding = strdup ("IRC (Latin/Unicode Hybrid)"); + servlist_server_add (net, "newserver/6667"); + + store = (GtkListStore *)gtk_tree_view_get_model (treeview); + gtk_list_store_prepend (store, &iter); + gtk_list_store_set (store, &iter, 0, net->name, 1, 1, -1); + + /* select this network */ + servlist_select_and_show (GTK_TREE_VIEW (networks_tree), &iter, store); + servlist_start_editing (GTK_TREE_VIEW (networks_tree)); + + servlist_network_row_cb (gtk_tree_view_get_selection (GTK_TREE_VIEW (networks_tree)), NULL); +} + +static void +servlist_deletenetwork (ircnet *net) +{ + GtkTreeSelection *sel; + GtkTreeModel *model; + GtkTreeIter iter; + + /* remove from GUI */ + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (networks_tree)); + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); + + /* remove from list */ + servlist_net_remove (net); + + /* force something to be selected */ + gtk_tree_model_get_iter_first (model, &iter); + servlist_select_and_show (GTK_TREE_VIEW (networks_tree), &iter, + GTK_LIST_STORE (model)); + servlist_network_row_cb (sel, NULL); +} + +static void +servlist_deletenetdialog_cb (GtkDialog *dialog, gint arg1, ircnet *net) +{ + gtk_widget_destroy (GTK_WIDGET (dialog)); + if (arg1 == GTK_RESPONSE_OK) + servlist_deletenetwork (net); +} + +static void +servlist_move_server (ircserver *serv, int delta) +{ + int pos; + + pos = g_slist_index (selected_net->servlist, serv); + if (pos >= 0) + { + pos += delta; + if (pos >= 0) + { + selected_net->servlist = g_slist_remove (selected_net->servlist, serv); + selected_net->servlist = g_slist_insert (selected_net->servlist, serv, pos); + servlist_servers_populate (selected_net, edit_tree); + } + } +} + +static void +servlist_move_network (ircnet *net, int delta) +{ + int pos; + + pos = g_slist_index (network_list, net); + if (pos >= 0) + { + pos += delta; + if (pos >= 0) + { + /*prefs.slist_select += delta;*/ + network_list = g_slist_remove (network_list, net); + network_list = g_slist_insert (network_list, net, pos); + servlist_networks_populate (networks_tree, network_list); + } + } +} + +static gboolean +servlist_net_keypress_cb (GtkWidget *wid, GdkEventKey *evt, gpointer tree) +{ + if (!selected_net) + return FALSE; + + if (evt->state & GDK_SHIFT_MASK) + { + if (evt->keyval == GDK_Up) + { + servlist_move_network (selected_net, -1); + } + else if (evt->keyval == GDK_Down) + { + servlist_move_network (selected_net, +1); + } + } + + return FALSE; +} + +static gboolean +servlist_serv_keypress_cb (GtkWidget *wid, GdkEventKey *evt, gpointer userdata) +{ + if (!selected_net || !selected_serv) + return FALSE; + + if (evt->state & GDK_SHIFT_MASK) + { + if (evt->keyval == GDK_Up) + { + servlist_move_server (selected_serv, -1); + } + else if (evt->keyval == GDK_Down) + { + servlist_move_server (selected_serv, +1); + } + } + + return FALSE; +} + +static gint +servlist_compare (ircnet *net1, ircnet *net2) +{ + gchar *net1_casefolded, *net2_casefolded; + int result=0; + + net1_casefolded=g_utf8_casefold(net1->name,-1), + net2_casefolded=g_utf8_casefold(net2->name,-1), + + result=g_utf8_collate(net1_casefolded,net2_casefolded); + + g_free(net1_casefolded); + g_free(net2_casefolded); + + return result; + +} + +static void +servlist_sort (GtkWidget *button, gpointer none) +{ + network_list=g_slist_sort(network_list,(GCompareFunc)servlist_compare); + servlist_networks_populate (networks_tree, network_list); +} + +static gboolean +servlist_has_selection (GtkTreeView *tree) +{ + GtkTreeSelection *sel; + GtkTreeModel *model; + GtkTreeIter iter; + + /* make sure something is selected */ + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree)); + return gtk_tree_selection_get_selected (sel, &model, &iter); +} + +static void +servlist_favor (GtkWidget *button, gpointer none) +{ + GtkTreeSelection *sel; + GtkTreeModel *model; + GtkTreeIter iter; + + if (!selected_net) + return; + + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (networks_tree)); + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + { + if (selected_net->flags & FLAG_FAVORITE) + { + gtk_list_store_set (GTK_LIST_STORE (model), &iter, 2, 400, -1); + selected_net->flags &= ~FLAG_FAVORITE; + } + else + { + gtk_list_store_set (GTK_LIST_STORE (model), &iter, 2, 800, -1); + selected_net->flags |= FLAG_FAVORITE; + } + } +} + +static void +servlist_update_from_entry (char **str, GtkWidget *entry) +{ + if (*str) + free (*str); + + if (GTK_ENTRY (entry)->text[0] == 0) + *str = NULL; + else + *str = strdup (GTK_ENTRY (entry)->text); +} + +static void +servlist_edit_update (ircnet *net) +{ + servlist_update_from_entry (&net->nick, edit_entry_nick); + servlist_update_from_entry (&net->nick2, edit_entry_nick2); + servlist_update_from_entry (&net->user, edit_entry_user); + servlist_update_from_entry (&net->real, edit_entry_real); + + servlist_update_from_entry (&net->autojoin, edit_entry_join); + servlist_update_from_entry (&net->command, edit_entry_cmd); + servlist_update_from_entry (&net->nickserv, edit_entry_nickserv); + servlist_update_from_entry (&net->pass, edit_entry_pass); +} + +static void +servlist_edit_close_cb (GtkWidget *button, gpointer userdata) +{ + if (selected_net) + servlist_edit_update (selected_net); + + gtk_widget_destroy (edit_win); + edit_win = NULL; +} + +static gint +servlist_editwin_delete_cb (GtkWidget *win, GdkEventAny *event, gpointer none) +{ + servlist_edit_close_cb (NULL, NULL); + return FALSE; +} + +static gboolean +servlist_configure_cb (GtkWindow *win, GdkEventConfigure *event, gpointer none) +{ + /* remember the window size */ + gtk_window_get_size (win, &win_width, &win_height); + return FALSE; +} + +static void +servlist_edit_cb (GtkWidget *but, gpointer none) +{ + if (!servlist_has_selection (GTK_TREE_VIEW (networks_tree))) + return; + + edit_win = servlist_open_edit (serverlist_win, selected_net); + gtkutil_set_icon (edit_win); + servlist_servers_populate (selected_net, edit_tree); + g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (edit_tree))), + "changed", G_CALLBACK (servlist_server_row_cb), NULL); + g_signal_connect (G_OBJECT (edit_win), "delete_event", + G_CALLBACK (servlist_editwin_delete_cb), 0); + g_signal_connect (G_OBJECT (edit_tree), "key_press_event", + G_CALLBACK (servlist_serv_keypress_cb), 0); + gtk_widget_show (edit_win); +} + +static void +servlist_deletenet_cb (GtkWidget *item, ircnet *net) +{ + GtkWidget *dialog; + + if (!servlist_has_selection (GTK_TREE_VIEW (networks_tree))) + return; + + net = selected_net; + if (!net) + return; + dialog = gtk_message_dialog_new (GTK_WINDOW (serverlist_win), + GTK_DIALOG_DESTROY_WITH_PARENT | + GTK_DIALOG_MODAL, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_OK_CANCEL, + _("Really remove network \"%s\" and all its servers?"), + net->name); + g_signal_connect (dialog, "response", + G_CALLBACK (servlist_deletenetdialog_cb), net); + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + gtk_widget_show (dialog); +} + +static void +servlist_deleteserver (ircserver *serv, GtkTreeModel *model) +{ + GtkTreeSelection *sel; + GtkTreeIter iter; + + /* don't remove the last server */ + if (selected_net && g_slist_length (selected_net->servlist) < 2) + return; + + /* remove from GUI */ + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (edit_tree)); + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); + + /* remove from list */ + if (selected_net) + servlist_server_remove (selected_net, serv); +} + +static void +servlist_editserverbutton_cb (GtkWidget *item, gpointer none) +{ + servlist_start_editing (GTK_TREE_VIEW (edit_tree)); +} + +static void +servlist_deleteserver_cb (GtkWidget *item, gpointer none) +{ + GtkTreeSelection *sel; + GtkTreeModel *model; + GtkTreeIter iter; + char *servname; + ircserver *serv; + int pos; + + /* find the selected item in the GUI */ + model = gtk_tree_view_get_model (GTK_TREE_VIEW (edit_tree)); + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (edit_tree)); + + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + { + gtk_tree_model_get (model, &iter, 0, &servname, -1); + serv = servlist_server_find (selected_net, servname, &pos); + g_free (servname); + if (serv) + servlist_deleteserver (serv, model); + } +} + +static ircnet * +servlist_find_selected_net (GtkTreeSelection *sel) +{ + GtkTreeModel *model; + GtkTreeIter iter; + char *netname; + int pos; + ircnet *net = NULL; + + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + { + gtk_tree_model_get (model, &iter, 0, &netname, -1); + net = servlist_net_find (netname, &pos, strcmp); + g_free (netname); + if (net) + prefs.slist_select = pos; + } + + return net; +} + +static void +servlist_network_row_cb (GtkTreeSelection *sel, gpointer user_data) +{ + ircnet *net; + + selected_net = NULL; + + net = servlist_find_selected_net (sel); + if (net) + selected_net = net; +} + +static int +servlist_savegui (void) +{ + char *sp; + + /* check for blank username, ircd will not allow this */ + if (GTK_ENTRY (entry_guser)->text[0] == 0) + return 1; + + if (GTK_ENTRY (entry_greal)->text[0] == 0) + return 1; + + strcpy (prefs.nick1, GTK_ENTRY (entry_nick1)->text); + strcpy (prefs.nick2, GTK_ENTRY (entry_nick2)->text); + strcpy (prefs.nick3, GTK_ENTRY (entry_nick3)->text); + strcpy (prefs.username, GTK_ENTRY (entry_guser)->text); + sp = strchr (prefs.username, ' '); + if (sp) + sp[0] = 0; /* spaces will break the login */ + strcpy (prefs.realname, GTK_ENTRY (entry_greal)->text); + servlist_save (); + + return 0; +} + +static gboolean +servlist_get_iter_from_name (GtkTreeModel *model, gchar *name, GtkTreeIter *iter) +{ + GtkTreePath *path = gtk_tree_path_new_from_string (name); + + if (!gtk_tree_model_get_iter (model, iter, path)) + { + gtk_tree_path_free (path); + return FALSE; + } + + gtk_tree_path_free (path); + return TRUE; +} + +static void +servlist_editchannel_cb (GtkCellRendererText *cell, gchar *name, gchar *newval, GtkTreeModel *model) +{ + GtkTreeIter iter; + static int loop_guard = FALSE; + + if (loop_guard) + return; + + if (!servlist_get_iter_from_name (model, name, &iter)) + return; + + loop_guard = TRUE; + /* delete empty item */ + if (newval[0] == 0) + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); + else + gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, newval, -1); + loop_guard = FALSE; +} + +static void +servlist_editkey_cb (GtkCellRendererText *cell, gchar *name, gchar *newval, GtkTreeModel *model) +{ + GtkTreeIter iter; + + if (!servlist_get_iter_from_name (model, name, &iter)) + return; + + gtk_list_store_set (GTK_LIST_STORE (model), &iter, 1, newval, -1); +} + +static void +servlist_addchannel (GtkWidget *tree, char *channel) +{ + GtkTreeIter iter; + GtkListStore *store; + + store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (tree)); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, channel, 1, "", 2, TRUE, -1); + + /* select this server */ + servlist_select_and_show (GTK_TREE_VIEW (tree), &iter, store); + servlist_start_editing (GTK_TREE_VIEW (tree)); +} + +static void +servlist_addchannel_cb (GtkWidget *item, GtkWidget *tree) +{ + servlist_addchannel (tree, _("#channel")); +} + +static void +servlist_deletechannel_cb (GtkWidget *item, GtkWidget *tree) +{ + GtkTreeSelection *sel; + GtkTreeModel *model; + GtkTreeIter iter; + + /* find the selected item in the GUI */ + model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree)); + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree)); + + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); +} + +static void +servlist_editchannelbutton_cb (GtkWidget *item, GtkWidget *tree) +{ + servlist_start_editing (GTK_TREE_VIEW (tree)); +} + +/* save everything from the GUI to the GtkEntry */ + +static void +servlist_autojoineditok_cb (GtkWidget *button, GtkWidget *tree) +{ + GtkTreeModel *model; + GtkTreeIter iter; + char *channel, *key; + char *autojoin; + GSList *channels = NULL, *keys = NULL; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree)); + + if (gtk_tree_model_get_iter_first (model, &iter)) + { + do + { + gtk_tree_model_get (model, &iter, 0, &channel, 1, &key, -1); + channels = g_slist_append (channels, channel); + if (key && key[0] == 0) + { + /* NULL out empty keys */ + g_free (key); + keys = g_slist_append (keys, NULL); + } + else + keys = g_slist_append (keys, key); + } + while (gtk_tree_model_iter_next (model, &iter)); + } + + gtk_widget_destroy (gtk_widget_get_toplevel (button)); + + autojoin = joinlist_merge (channels, keys); + if (autojoin) + { + if (edit_win && selected_net) + gtk_entry_set_text (GTK_ENTRY (edit_entry_join), autojoin); + else + { + if (fav_add_net->autojoin) + free (fav_add_net->autojoin); + fav_add_net->autojoin = strdup (autojoin); + } + g_free (autojoin); + } + + /* this does g_free too */ + joinlist_free (channels, keys); + + if (fav_add_net) + servlist_save (); +} + +void +servlist_autojoinedit (ircnet *net, char *channel, gboolean add) +{ + GtkWidget *win; + GtkWidget *scrolledwindow; + GtkTreeModel *model; + GtkListStore *store; + GtkCellRenderer *renderer; + GtkWidget *tree; + GtkWidget *table; + GtkWidget *label; + GtkWidget *label2; + GtkWidget *bbox; + GtkWidget *wid; + + GtkWidget *vbuttonbox1; + GtkWidget *buttonadd; + GtkWidget *buttonremove; + GtkWidget *buttonedit; + + char buf[128]; + char lab[128]; + GSList *channels, *keys; + GSList *clist, *klist; + GtkTreeIter iter; + + if (edit_win && selected_net) + /* update net->autojoin */ + servlist_edit_update (selected_net); + + win = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_container_set_border_width (GTK_CONTAINER (win), 4); + gtk_window_set_title (GTK_WINDOW (win), _("XChat: Favorite Channels (Auto-Join List)")); + gtk_window_set_default_size (GTK_WINDOW (win), 354, 256); + gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_MOUSE); + if (edit_win) + gtk_window_set_transient_for (GTK_WINDOW (win), GTK_WINDOW (edit_win)); + gtk_window_set_modal (GTK_WINDOW (win), TRUE); + gtk_window_set_type_hint (GTK_WINDOW (win), GDK_WINDOW_TYPE_HINT_DIALOG); + gtk_window_set_role (GTK_WINDOW (win), "editserv"); + + table = gtk_table_new (1, 1, FALSE); + gtk_container_add (GTK_CONTAINER (win), table); + gtk_widget_show (table); + + snprintf (buf, sizeof (buf), _("These channels will be joined whenever you connect to %s."), net->name); + label = gtk_label_new (buf); + gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER); + gtk_table_attach (GTK_TABLE (table), label, 0, 2, 0, 1, GTK_FILL|GTK_EXPAND, GTK_FILL, 3, 3); + gtk_widget_show (label); + + label2 = gtk_label_new (""); + gtk_label_set_justify (GTK_LABEL (label2), GTK_JUSTIFY_CENTER); + gtk_table_attach (GTK_TABLE (table), label2, 0, 2, 1, 2, GTK_FILL, 0, 3, 3); + gtk_widget_show (label2); + + scrolledwindow = gtk_scrolled_window_new (NULL, NULL); + gtk_table_attach (GTK_TABLE (table), scrolledwindow, 0, 1, 2, 3, GTK_FILL|GTK_EXPAND, GTK_FILL|GTK_EXPAND, 0, 0); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow), + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow), + GTK_SHADOW_IN); + gtk_widget_show (scrolledwindow); + + store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN); + model = GTK_TREE_MODEL (store); + + tree = gtk_tree_view_new_with_model (model); + g_object_unref (model); + gtk_container_add (GTK_CONTAINER (scrolledwindow), tree); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tree), TRUE); + gtk_widget_show (tree); + + renderer = gtk_cell_renderer_text_new (); + g_signal_connect (G_OBJECT (renderer), "edited", + G_CALLBACK (servlist_editchannel_cb), model); + gtk_tree_view_insert_column_with_attributes ( + GTK_TREE_VIEW (tree), -1, + _("Channel"), renderer, + "text", 0, + "editable", 2, + NULL); + + renderer = gtk_cell_renderer_text_new (); + g_signal_connect (G_OBJECT (renderer), "edited", + G_CALLBACK (servlist_editkey_cb), model); + gtk_tree_view_insert_column_with_attributes ( + GTK_TREE_VIEW (tree), -1, + _("Key (Password)"), renderer, + "text", 1, + "editable", 2, + NULL); + + gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (tree), 0), TRUE); + gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (tree), 1), TRUE); + + gtk_tree_sortable_set_sort_column_id ((GtkTreeSortable *)model, 0, GTK_SORT_ASCENDING); + + /* ===BUTTONS=== */ + vbuttonbox1 = gtk_vbutton_box_new (); + gtk_box_set_spacing (GTK_BOX (vbuttonbox1), 3); + gtk_button_box_set_layout (GTK_BUTTON_BOX (vbuttonbox1), GTK_BUTTONBOX_START); + gtk_widget_show (vbuttonbox1); + gtk_table_attach (GTK_TABLE (table), vbuttonbox1, 1, 2, 2, 3, GTK_FILL, GTK_FILL, 3, 0); + + buttonadd = gtk_button_new_from_stock ("gtk-add"); + g_signal_connect (G_OBJECT (buttonadd), "clicked", + G_CALLBACK (servlist_addchannel_cb), tree); + gtk_widget_show (buttonadd); + gtk_container_add (GTK_CONTAINER (vbuttonbox1), buttonadd); + GTK_WIDGET_SET_FLAGS (buttonadd, GTK_CAN_DEFAULT); + + buttonremove = gtk_button_new_from_stock ("gtk-remove"); + g_signal_connect (G_OBJECT (buttonremove), "clicked", + G_CALLBACK (servlist_deletechannel_cb), tree); + gtk_widget_show (buttonremove); + gtk_container_add (GTK_CONTAINER (vbuttonbox1), buttonremove); + GTK_WIDGET_SET_FLAGS (buttonremove, GTK_CAN_DEFAULT); + + buttonedit = gtk_button_new_with_mnemonic (_("_Edit")); + g_signal_connect (G_OBJECT (buttonedit), "clicked", + G_CALLBACK (servlist_editchannelbutton_cb), tree); + gtk_widget_show (buttonedit); + gtk_container_add (GTK_CONTAINER (vbuttonbox1), buttonedit); + GTK_WIDGET_SET_FLAGS (buttonedit, GTK_CAN_DEFAULT); + + bbox = gtk_hbutton_box_new (); + gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_END); + gtk_box_set_spacing (GTK_BOX (bbox), 4); + gtk_table_attach (GTK_TABLE (table), bbox, 0, 1, 3, 4, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 4); + gtk_widget_show (bbox); + + wid = gtk_button_new_from_stock (GTK_STOCK_CANCEL); + g_signal_connect (G_OBJECT (wid), "clicked", G_CALLBACK (gtkutil_destroy), win); + gtk_container_add (GTK_CONTAINER (bbox), wid); + gtk_widget_show (wid); + + wid = gtk_button_new_from_stock (GTK_STOCK_OK); + g_signal_connect (G_OBJECT (wid), "clicked", G_CALLBACK (servlist_autojoineditok_cb), tree); + gtk_container_add (GTK_CONTAINER (bbox), wid); + gtk_widget_show (wid); + gtk_widget_grab_focus (wid); + /* =========== */ + + if (net->autojoin) + { + joinlist_split (net->autojoin, &channels, &keys); + + clist = channels; + klist = keys; + + while (clist) + { + if (channel && !add && !rfc_casecmp (channel, clist->data)) + { + snprintf (buf, sizeof (buf), _("%s has been removed."), channel); + snprintf (lab, sizeof (lab), "<span foreground=\"#2222DD\">%s</span>", buf); + gtk_label_set_markup (GTK_LABEL (label2), lab); + } + else + { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, clist->data, 1, klist->data, 2, TRUE, -1); + } + + klist = klist->next; + clist = clist->next; + } + + joinlist_free (channels, keys); + } + + if (channel && add) + { + servlist_addchannel (tree, channel); + snprintf (buf, sizeof (buf), _("%s has been added."), channel); + snprintf (lab, sizeof (lab), "<span foreground=\"#2222DD\">%s</span>", buf); + gtk_label_set_markup (GTK_LABEL (label2), lab); + } + + fav_add_net = net; + + gtk_widget_show (win); +} + +static void +servlist_autojoinedit_cb (GtkWidget *button, ircnet *net) +{ + servlist_autojoinedit (net, NULL, FALSE); +} + +static void +servlist_connect_cb (GtkWidget *button, gpointer userdata) +{ + if (!selected_net) + return; + + if (servlist_savegui () != 0) + { + fe_message (_("User name and Real name cannot be left blank."), FE_MSG_ERROR); + return; + } + + if (!is_session (servlist_sess)) + servlist_sess = NULL; /* open a new one */ + + { + GSList *list; + session *sess; + session *chosen = servlist_sess; + + servlist_sess = NULL; /* open a new one */ + + for (list = sess_list; list; list = list->next) + { + sess = list->data; + if (sess->server->network == selected_net) + { + servlist_sess = sess; + if (sess->server->connected) + servlist_sess = NULL; /* open a new one */ + break; + } + } + + /* use the chosen one, if it's empty */ + if (!servlist_sess && + chosen && + !chosen->server->connected && + chosen->server->server_session->channel[0] == 0) + { + servlist_sess = chosen; + } + } + + servlist_connect (servlist_sess, selected_net, TRUE); + + gtk_widget_destroy (serverlist_win); + serverlist_win = NULL; + selected_net = NULL; +} + +static void +servlist_celledit_cb (GtkCellRendererText *cell, gchar *arg1, gchar *arg2, + gpointer user_data) +{ + GtkTreeModel *model = (GtkTreeModel *)user_data; + GtkTreeIter iter; + GtkTreePath *path; + char *netname; + ircnet *net; + + if (!arg1 || !arg2) + return; + + path = gtk_tree_path_new_from_string (arg1); + if (!path) + return; + + if (!gtk_tree_model_get_iter (model, &iter, path)) + { + gtk_tree_path_free (path); + return; + } + gtk_tree_model_get (model, &iter, 0, &netname, -1); + + net = servlist_net_find (netname, NULL, strcmp); + g_free (netname); + if (net) + { + /* delete empty item */ + if (arg2[0] == 0) + { + servlist_deletenetwork (net); + gtk_tree_path_free (path); + return; + } + + netname = net->name; + net->name = strdup (arg2); + gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, net->name, -1); + free (netname); + } + + gtk_tree_path_free (path); +} + +static void +servlist_check_cb (GtkWidget *but, gpointer num_p) +{ + int num = GPOINTER_TO_INT (num_p); + + if (!selected_net) + return; + + if ((1 << num) == FLAG_CYCLE || (1 << num) == FLAG_USE_PROXY) + { + /* these ones are reversed, so it's compat with 2.0.x */ + if (GTK_TOGGLE_BUTTON (but)->active) + selected_net->flags &= ~(1 << num); + else + selected_net->flags |= (1 << num); + } else + { + if (GTK_TOGGLE_BUTTON (but)->active) + selected_net->flags |= (1 << num); + else + selected_net->flags &= ~(1 << num); + } + + if ((1 << num) == FLAG_USE_GLOBAL) + { + if (GTK_TOGGLE_BUTTON (but)->active) + { + gtk_widget_hide (edit_label_nick); + gtk_widget_hide (edit_entry_nick); + + gtk_widget_hide (edit_label_nick2); + gtk_widget_hide (edit_entry_nick2); + + gtk_widget_hide (edit_label_user); + gtk_widget_hide (edit_entry_user); + + gtk_widget_hide (edit_label_real); + gtk_widget_hide (edit_entry_real); + } else + { + gtk_widget_show (edit_label_nick); + gtk_widget_show (edit_entry_nick); + + gtk_widget_show (edit_label_nick2); + gtk_widget_show (edit_entry_nick2); + + gtk_widget_show (edit_label_user); + gtk_widget_show (edit_entry_user); + + gtk_widget_show (edit_label_real); + gtk_widget_show (edit_entry_real); + } + } +} + +static GtkWidget * +servlist_create_check (int num, int state, GtkWidget *table, int row, int col, char *labeltext) +{ + GtkWidget *but; + + but = gtk_check_button_new_with_label (labeltext); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (but), state); + g_signal_connect (G_OBJECT (but), "toggled", + G_CALLBACK (servlist_check_cb), GINT_TO_POINTER (num)); + gtk_table_attach (GTK_TABLE (table), but, col, col+2, row, row+1, + GTK_FILL|GTK_EXPAND, 0, 0, 0); + gtk_widget_show (but); + + return but; +} + +static GtkWidget * +servlist_create_entry (GtkWidget *table, char *labeltext, int row, + char *def, GtkWidget **label_ret, char *tip) +{ + GtkWidget *label, *entry; + + label = gtk_label_new_with_mnemonic (labeltext); + if (label_ret) + *label_ret = label; + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 1, 2, row, row+1, + GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + + entry = gtk_entry_new (); + add_tip (entry, tip); + gtk_widget_show (entry); + gtk_entry_set_text (GTK_ENTRY (entry), def ? def : ""); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry); + + if (row == 15) /* for "Channels to Join:" */ + { + GtkWidget *button, *box; + + box = gtk_hbox_new (0, 0); + button = gtk_button_new_with_label ("..."); + g_signal_connect (G_OBJECT (button), "clicked", + G_CALLBACK (servlist_autojoinedit_cb), selected_net); + + gtk_box_pack_start (GTK_BOX (box), entry, TRUE, TRUE, 0); + gtk_box_pack_end (GTK_BOX (box), button, 0, 0, 0); + gtk_widget_show_all (box); + + gtk_table_attach (GTK_TABLE (table), box, 2, 3, row, row+1, + GTK_FILL|GTK_EXPAND, 0, 0, 0); + } + else + { + gtk_table_attach (GTK_TABLE (table), entry, 2, 3, row, row+1, + GTK_FILL|GTK_EXPAND, 0, 0, 0); + } + + return entry; +} + +static gint +servlist_delete_cb (GtkWidget *win, GdkEventAny *event, gpointer userdata) +{ + servlist_savegui (); + serverlist_win = NULL; + selected_net = NULL; + + if (sess_list == NULL) + xchat_exit (); + + return FALSE; +} + +static void +servlist_close_cb (GtkWidget *button, gpointer userdata) +{ + servlist_savegui (); + gtk_widget_destroy (serverlist_win); + serverlist_win = NULL; + selected_net = NULL; + + if (sess_list == NULL) + xchat_exit (); +} + +/* convert "host:port" format to "host/port" */ + +static char * +servlist_sanitize_hostname (char *host) +{ + char *ret, *c, *e; + + ret = strdup (host); + + c = strchr (ret, ':'); + e = strrchr (ret, ':'); + + /* if only one colon exists it's probably not IPv6 */ + if (c && c == e) + *c = '/'; + + return ret; +} + +static void +servlist_editserver_cb (GtkCellRendererText *cell, gchar *arg1, gchar *arg2, + gpointer user_data) +{ + GtkTreeModel *model = (GtkTreeModel *)user_data; + GtkTreeIter iter; + GtkTreePath *path; + char *servname; + ircserver *serv; + + if (!selected_net) + return; + + path = gtk_tree_path_new_from_string (arg1); + + if (!gtk_tree_model_get_iter (model, &iter, path)) + { + gtk_tree_path_free (path); + return; + } + + gtk_tree_model_get (model, &iter, 0, &servname, -1); + serv = servlist_server_find (selected_net, servname, NULL); + g_free (servname); + + if (serv) + { + /* delete empty item */ + if (arg2[0] == 0) + { + servlist_deleteserver (serv, model); + gtk_tree_path_free (path); + return; + } + + servname = serv->hostname; + serv->hostname = servlist_sanitize_hostname (arg2); + gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, serv->hostname, -1); + free (servname); + } + + gtk_tree_path_free (path); +} + +static void +servlist_combo_cb (GtkEntry *entry, gpointer userdata) +{ + if (!selected_net) + return; + + if (!ignore_changed) + { + if (selected_net->encoding) + free (selected_net->encoding); + selected_net->encoding = strdup (entry->text); + } +} + +static GtkWidget * +servlist_create_charsetcombo (void) +{ + GtkWidget *cb; + int i; + + cb = gtk_combo_box_entry_new_text (); + gtk_combo_box_append_text (GTK_COMBO_BOX (cb), "System default"); + i = 0; + while (pages[i]) + { + gtk_combo_box_append_text (GTK_COMBO_BOX (cb), (char *)pages[i]); + i++; + } + g_signal_connect (G_OBJECT (GTK_BIN (cb)->child), "changed", + G_CALLBACK (servlist_combo_cb), NULL); + + return cb; +} + +static void +no_servlist (GtkWidget * igad, gpointer serv) +{ + if (GTK_TOGGLE_BUTTON (igad)->active) + prefs.slist_skip = TRUE; + else + prefs.slist_skip = FALSE; +} + +static void +fav_servlist (GtkWidget * igad, gpointer serv) +{ + if (GTK_TOGGLE_BUTTON (igad)->active) + prefs.slist_fav = TRUE; + else + prefs.slist_fav = FALSE; + + servlist_networks_populate (networks_tree, network_list); +} + +static GtkWidget * +bold_label (char *text) +{ + char buf[128]; + GtkWidget *label; + + snprintf (buf, sizeof (buf), "<b>%s</b>", text); + label = gtk_label_new (buf); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + gtk_widget_show (label); + + return label; +} + +static GtkWidget * +servlist_open_edit (GtkWidget *parent, ircnet *net) +{ + GtkWidget *editwindow; + GtkWidget *vbox5; + GtkWidget *table3; + GtkWidget *label17; + GtkWidget *label16; + GtkWidget *label21; + GtkWidget *label34; + GtkWidget *comboboxentry_charset; + GtkWidget *hbox1; + GtkWidget *scrolledwindow2; + GtkWidget *treeview_servers; + GtkWidget *vbuttonbox1; + GtkWidget *buttonadd; + GtkWidget *buttonremove; + GtkWidget *buttonedit; + GtkWidget *hseparator2; + GtkWidget *hbuttonbox4; + GtkWidget *button10; + GtkWidget *check; + GtkTreeModel *model; + GtkListStore *store; + GtkCellRenderer *renderer; + char buf[128]; + char buf2[128 + 8]; + + editwindow = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_container_set_border_width (GTK_CONTAINER (editwindow), 4); + snprintf (buf, sizeof (buf), _("XChat: Edit %s"), net->name); + gtk_window_set_title (GTK_WINDOW (editwindow), buf); + gtk_window_set_default_size (GTK_WINDOW (editwindow), 354, 0); + gtk_window_set_position (GTK_WINDOW (editwindow), GTK_WIN_POS_MOUSE); + gtk_window_set_transient_for (GTK_WINDOW (editwindow), GTK_WINDOW (parent)); + gtk_window_set_modal (GTK_WINDOW (editwindow), TRUE); + gtk_window_set_type_hint (GTK_WINDOW (editwindow), GDK_WINDOW_TYPE_HINT_DIALOG); + gtk_window_set_role (GTK_WINDOW (editwindow), "editserv"); + + vbox5 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox5); + gtk_container_add (GTK_CONTAINER (editwindow), vbox5); + + table3 = gtk_table_new (17, 3, FALSE); + gtk_widget_show (table3); + gtk_box_pack_start (GTK_BOX (vbox5), table3, TRUE, TRUE, 0); + gtk_table_set_row_spacings (GTK_TABLE (table3), 2); + gtk_table_set_col_spacings (GTK_TABLE (table3), 8); + + snprintf (buf, sizeof (buf), _("Servers for %s"), net->name); + snprintf (buf2, sizeof (buf2), "<b>%s</b>", buf); + label16 = gtk_label_new (buf2); + gtk_widget_show (label16); + gtk_table_attach (GTK_TABLE (table3), label16, 0, 3, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 3); + gtk_label_set_use_markup (GTK_LABEL (label16), TRUE); + gtk_misc_set_alignment (GTK_MISC (label16), 0, 0.5); + + check = servlist_create_check (0, !(net->flags & FLAG_CYCLE), table3, + 2, 1, _("Connect to selected server only")); + add_tip (check, _("Don't cycle through all the servers when the connection fails.")); + + label17 = bold_label (_("Your Details")); + gtk_table_attach (GTK_TABLE (table3), label17, 0, 3, 3, 4, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 3); + + servlist_create_check (1, net->flags & FLAG_USE_GLOBAL, table3, + 4, 1, _("Use global user information")); + + edit_entry_nick = + servlist_create_entry (table3, _("_Nick name:"), 5, net->nick, + &edit_label_nick, 0); + + edit_entry_nick2 = + servlist_create_entry (table3, _("Second choice:"), 6, net->nick2, + &edit_label_nick2, 0); + + edit_entry_user = + servlist_create_entry (table3, _("_User name:"), 7, net->user, + &edit_label_user, 0); + + edit_entry_real = + servlist_create_entry (table3, _("Rea_l name:"), 8, net->real, + &edit_label_real, 0); + + label21 = bold_label (_("Connecting")); + gtk_table_attach (GTK_TABLE (table3), label21, 0, 3, 9, 10, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 3); + + servlist_create_check (3, net->flags & FLAG_AUTO_CONNECT, table3, + 11, 1, _("Auto connect to this network at startup")); + servlist_create_check (4, !(net->flags & FLAG_USE_PROXY), table3, + 12, 1, _("Bypass proxy server")); + check = servlist_create_check (2, net->flags & FLAG_USE_SSL, table3, + 13, 1, _("Use SSL for all the servers on this network")); +#ifndef USE_OPENSSL + gtk_widget_set_sensitive (check, FALSE); +#endif + check = servlist_create_check (5, net->flags & FLAG_ALLOW_INVALID, table3, + 14, 1, _("Accept invalid SSL certificate")); +#ifndef USE_OPENSSL + gtk_widget_set_sensitive (check, FALSE); +#endif + + edit_entry_join = + servlist_create_entry (table3, _("_Favorite channels:"), 15, + net->autojoin, 0, + _("Channels to join, separated by commas, but not spaces!")); + + edit_entry_cmd = + servlist_create_entry (table3, _("Connect command:"), 16, + net->command, 0, + _("Extra command to execute after connecting. If you need more than one, set this to LOAD -e <filename>, where <filename> is a text-file full of commands to execute.")); + + edit_entry_nickserv = + servlist_create_entry (table3, _("Nickserv password:"), 17, + net->nickserv, 0, + _("If your nickname requires a password, enter it here. Not all IRC networks support this.")); + gtk_entry_set_visibility (GTK_ENTRY (edit_entry_nickserv), FALSE); + + edit_entry_pass = + servlist_create_entry (table3, _("Server password:"), 18, + net->pass, 0, + _("Password for the server, if in doubt, leave blank.")); + gtk_entry_set_visibility (GTK_ENTRY (edit_entry_pass), FALSE); + + label34 = gtk_label_new (_("Character set:")); + gtk_widget_show (label34); + gtk_table_attach (GTK_TABLE (table3), label34, 1, 2, 19, 20, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label34), 0, 0.5); + + comboboxentry_charset = servlist_create_charsetcombo (); + ignore_changed = TRUE; + gtk_entry_set_text (GTK_ENTRY (GTK_BIN (comboboxentry_charset)->child), net->encoding ? net->encoding : "System default"); + ignore_changed = FALSE; + gtk_widget_show (comboboxentry_charset); + gtk_table_attach (GTK_TABLE (table3), comboboxentry_charset, 2, 3, 19, 20, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + + hbox1 = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox1); + gtk_table_attach (GTK_TABLE (table3), hbox1, 1, 3, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 0, 0); + + scrolledwindow2 = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (scrolledwindow2); + gtk_box_pack_start (GTK_BOX (hbox1), scrolledwindow2, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow2), + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow2), + GTK_SHADOW_IN); + + store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN); + model = GTK_TREE_MODEL (store); + + edit_tree = treeview_servers = gtk_tree_view_new_with_model (model); + g_object_unref (model); + gtk_widget_show (treeview_servers); + gtk_container_add (GTK_CONTAINER (scrolledwindow2), treeview_servers); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview_servers), + FALSE); + + renderer = gtk_cell_renderer_text_new (); + g_signal_connect (G_OBJECT (renderer), "edited", + G_CALLBACK (servlist_editserver_cb), model); + gtk_tree_view_insert_column_with_attributes ( + GTK_TREE_VIEW (treeview_servers), -1, + 0, renderer, + "text", 0, + "editable", 1, + NULL); + + vbuttonbox1 = gtk_vbutton_box_new (); + gtk_box_set_spacing (GTK_BOX (vbuttonbox1), 3); + gtk_button_box_set_layout (GTK_BUTTON_BOX (vbuttonbox1), GTK_BUTTONBOX_START); + gtk_widget_show (vbuttonbox1); + gtk_box_pack_start (GTK_BOX (hbox1), vbuttonbox1, FALSE, FALSE, 3); + + buttonadd = gtk_button_new_from_stock ("gtk-add"); + g_signal_connect (G_OBJECT (buttonadd), "clicked", + G_CALLBACK (servlist_addserver_cb), edit_tree); + gtk_widget_show (buttonadd); + gtk_container_add (GTK_CONTAINER (vbuttonbox1), buttonadd); + GTK_WIDGET_SET_FLAGS (buttonadd, GTK_CAN_DEFAULT); + + buttonremove = gtk_button_new_from_stock ("gtk-remove"); + g_signal_connect (G_OBJECT (buttonremove), "clicked", + G_CALLBACK (servlist_deleteserver_cb), NULL); + gtk_widget_show (buttonremove); + gtk_container_add (GTK_CONTAINER (vbuttonbox1), buttonremove); + GTK_WIDGET_SET_FLAGS (buttonremove, GTK_CAN_DEFAULT); + + buttonedit = gtk_button_new_with_mnemonic (_("_Edit")); + g_signal_connect (G_OBJECT (buttonedit), "clicked", + G_CALLBACK (servlist_editserverbutton_cb), NULL); + gtk_widget_show (buttonedit); + gtk_container_add (GTK_CONTAINER (vbuttonbox1), buttonedit); + GTK_WIDGET_SET_FLAGS (buttonedit, GTK_CAN_DEFAULT); + + hseparator2 = gtk_hseparator_new (); + gtk_widget_show (hseparator2); + gtk_box_pack_start (GTK_BOX (vbox5), hseparator2, FALSE, FALSE, 8); + + hbuttonbox4 = gtk_hbutton_box_new (); + gtk_widget_show (hbuttonbox4); + gtk_box_pack_start (GTK_BOX (vbox5), hbuttonbox4, FALSE, FALSE, 0); + gtk_button_box_set_layout (GTK_BUTTON_BOX (hbuttonbox4), + GTK_BUTTONBOX_END); + + button10 = gtk_button_new_from_stock ("gtk-close"); + g_signal_connect (G_OBJECT (button10), "clicked", + G_CALLBACK (servlist_edit_close_cb), 0); + gtk_widget_show (button10); + gtk_container_add (GTK_CONTAINER (hbuttonbox4), button10); + GTK_WIDGET_SET_FLAGS (button10, GTK_CAN_DEFAULT); + + if (net->flags & FLAG_USE_GLOBAL) + { + gtk_widget_hide (edit_label_nick); + gtk_widget_hide (edit_entry_nick); + + gtk_widget_hide (edit_label_nick2); + gtk_widget_hide (edit_entry_nick2); + + gtk_widget_hide (edit_label_user); + gtk_widget_hide (edit_entry_user); + + gtk_widget_hide (edit_label_real); + gtk_widget_hide (edit_entry_real); + } + + gtk_widget_grab_focus (button10); + gtk_widget_grab_default (button10); + + return editwindow; +} + +static GtkWidget * +servlist_open_networks (void) +{ + GtkWidget *servlist; + GtkWidget *vbox1; + GtkWidget *label2; + GtkWidget *table1; + GtkWidget *label3; + GtkWidget *label4; + GtkWidget *label5; + GtkWidget *label6; + GtkWidget *label7; + GtkWidget *entry1; + GtkWidget *entry2; + GtkWidget *entry3; + GtkWidget *entry4; + GtkWidget *entry5; + GtkWidget *vbox2; + GtkWidget *label1; + GtkWidget *table4; + GtkWidget *scrolledwindow3; + GtkWidget *treeview_networks; + GtkWidget *checkbutton_skip; + GtkWidget *checkbutton_fav; + GtkWidget *hbox; + GtkWidget *vbuttonbox2; + GtkWidget *button_add; + GtkWidget *button_remove; + GtkWidget *button_edit; + GtkWidget *button_sort; + GtkWidget *hseparator1; + GtkWidget *hbuttonbox1; + GtkWidget *button_connect; + GtkWidget *button_close; + GtkTreeModel *model; + GtkListStore *store; + GtkCellRenderer *renderer; + + servlist = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_container_set_border_width (GTK_CONTAINER (servlist), 4); + gtk_window_set_title (GTK_WINDOW (servlist), _("XChat: Network List")); + gtk_window_set_default_size (GTK_WINDOW (servlist), win_width, win_height); + gtk_window_set_position (GTK_WINDOW (servlist), GTK_WIN_POS_MOUSE); + gtk_window_set_role (GTK_WINDOW (servlist), "servlist"); + gtk_window_set_type_hint (GTK_WINDOW (servlist), GDK_WINDOW_TYPE_HINT_DIALOG); + if (current_sess) + gtk_window_set_transient_for (GTK_WINDOW (servlist), GTK_WINDOW (current_sess->gui->window)); + + vbox1 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox1); + gtk_container_add (GTK_CONTAINER (servlist), vbox1); + + label2 = bold_label (_("User Information")); + gtk_box_pack_start (GTK_BOX (vbox1), label2, FALSE, FALSE, 0); + + table1 = gtk_table_new (5, 2, FALSE); + gtk_widget_show (table1); + gtk_box_pack_start (GTK_BOX (vbox1), table1, FALSE, FALSE, 0); + gtk_container_set_border_width (GTK_CONTAINER (table1), 8); + gtk_table_set_row_spacings (GTK_TABLE (table1), 2); + gtk_table_set_col_spacings (GTK_TABLE (table1), 4); + + label3 = gtk_label_new_with_mnemonic (_("_Nick name:")); + gtk_widget_show (label3); + gtk_table_attach (GTK_TABLE (table1), label3, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label3), 0, 0.5); + + label4 = gtk_label_new (_("Second choice:")); + gtk_widget_show (label4); + gtk_table_attach (GTK_TABLE (table1), label4, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label4), 0, 0.5); + + label5 = gtk_label_new (_("Third choice:")); + gtk_widget_show (label5); + gtk_table_attach (GTK_TABLE (table1), label5, 0, 1, 2, 3, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label5), 0, 0.5); + + label6 = gtk_label_new_with_mnemonic (_("_User name:")); + gtk_widget_show (label6); + gtk_table_attach (GTK_TABLE (table1), label6, 0, 1, 3, 4, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label6), 0, 0.5); + + label7 = gtk_label_new_with_mnemonic (_("Rea_l name:")); + gtk_widget_show (label7); + gtk_table_attach (GTK_TABLE (table1), label7, 0, 1, 4, 5, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label7), 0, 0.5); + + entry_nick1 = entry1 = gtk_entry_new (); + gtk_entry_set_text (GTK_ENTRY (entry1), prefs.nick1); + gtk_widget_show (entry1); + gtk_table_attach (GTK_TABLE (table1), entry1, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + entry_nick2 = entry2 = gtk_entry_new (); + gtk_entry_set_text (GTK_ENTRY (entry2), prefs.nick2); + gtk_widget_show (entry2); + gtk_table_attach (GTK_TABLE (table1), entry2, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + entry_nick3 = entry3 = gtk_entry_new (); + gtk_entry_set_text (GTK_ENTRY (entry3), prefs.nick3); + gtk_widget_show (entry3); + gtk_table_attach (GTK_TABLE (table1), entry3, 1, 2, 2, 3, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + entry_guser = entry4 = gtk_entry_new (); + gtk_entry_set_text (GTK_ENTRY (entry4), prefs.username); + gtk_widget_show (entry4); + gtk_table_attach (GTK_TABLE (table1), entry4, 1, 2, 3, 4, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + entry_greal = entry5 = gtk_entry_new (); + gtk_entry_set_text (GTK_ENTRY (entry5), prefs.realname); + gtk_widget_show (entry5); + gtk_table_attach (GTK_TABLE (table1), entry5, 1, 2, 4, 5, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + vbox2 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox2); + gtk_box_pack_start (GTK_BOX (vbox1), vbox2, TRUE, TRUE, 0); + + label1 = bold_label (_("Networks")); + gtk_box_pack_start (GTK_BOX (vbox2), label1, FALSE, FALSE, 0); + + table4 = gtk_table_new (2, 2, FALSE); + gtk_widget_show (table4); + gtk_box_pack_start (GTK_BOX (vbox2), table4, TRUE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (table4), 8); + gtk_table_set_row_spacings (GTK_TABLE (table4), 2); + gtk_table_set_col_spacings (GTK_TABLE (table4), 3); + + scrolledwindow3 = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (scrolledwindow3); + gtk_table_attach (GTK_TABLE (table4), scrolledwindow3, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 0, 0); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow3), + GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow3), + GTK_SHADOW_IN); + + store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_INT); + model = GTK_TREE_MODEL (store); + + networks_tree = treeview_networks = gtk_tree_view_new_with_model (model); + g_object_unref (model); + gtk_widget_show (treeview_networks); + gtk_container_add (GTK_CONTAINER (scrolledwindow3), treeview_networks); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview_networks), + FALSE); + + renderer = gtk_cell_renderer_text_new (); + g_signal_connect (G_OBJECT (renderer), "edited", + G_CALLBACK (servlist_celledit_cb), model); + gtk_tree_view_insert_column_with_attributes ( + GTK_TREE_VIEW (treeview_networks), -1, + 0, renderer, + "text", 0, + "editable", 1, + "weight", 2, + NULL); + + hbox = gtk_hbox_new (0, FALSE); + gtk_table_attach (GTK_TABLE (table4), hbox, 0, 2, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (hbox); + + checkbutton_skip = + gtk_check_button_new_with_mnemonic (_("Skip network list on startup")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbutton_skip), + prefs.slist_skip); + gtk_container_add (GTK_CONTAINER (hbox), checkbutton_skip); + g_signal_connect (G_OBJECT (checkbutton_skip), "toggled", + G_CALLBACK (no_servlist), 0); + gtk_widget_show (checkbutton_skip); + + checkbutton_fav = + gtk_check_button_new_with_mnemonic (_("Show favorites only")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbutton_fav), + prefs.slist_fav); + gtk_container_add (GTK_CONTAINER (hbox), checkbutton_fav); + g_signal_connect (G_OBJECT (checkbutton_fav), "toggled", + G_CALLBACK (fav_servlist), 0); + gtk_widget_show (checkbutton_fav); + + vbuttonbox2 = gtk_vbutton_box_new (); + gtk_box_set_spacing (GTK_BOX (vbuttonbox2), 3); + gtk_button_box_set_layout (GTK_BUTTON_BOX (vbuttonbox2), GTK_BUTTONBOX_START); + gtk_widget_show (vbuttonbox2); + gtk_table_attach (GTK_TABLE (table4), vbuttonbox2, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + + button_add = gtk_button_new_from_stock ("gtk-add"); + g_signal_connect (G_OBJECT (button_add), "clicked", + G_CALLBACK (servlist_addnet_cb), networks_tree); + gtk_widget_show (button_add); + gtk_container_add (GTK_CONTAINER (vbuttonbox2), button_add); + GTK_WIDGET_SET_FLAGS (button_add, GTK_CAN_DEFAULT); + + button_remove = gtk_button_new_from_stock ("gtk-remove"); + g_signal_connect (G_OBJECT (button_remove), "clicked", + G_CALLBACK (servlist_deletenet_cb), 0); + gtk_widget_show (button_remove); + gtk_container_add (GTK_CONTAINER (vbuttonbox2), button_remove); + GTK_WIDGET_SET_FLAGS (button_remove, GTK_CAN_DEFAULT); + + button_edit = gtk_button_new_with_mnemonic (_("_Edit...")); + g_signal_connect (G_OBJECT (button_edit), "clicked", + G_CALLBACK (servlist_edit_cb), 0); + gtk_widget_show (button_edit); + gtk_container_add (GTK_CONTAINER (vbuttonbox2), button_edit); + GTK_WIDGET_SET_FLAGS (button_edit, GTK_CAN_DEFAULT); + + button_sort = gtk_button_new_with_mnemonic (_("_Sort")); + add_tip (button_sort, _("Sorts the network list in alphabetical order. " + "Use SHIFT-UP and SHIFT-DOWN keys to move a row.")); + g_signal_connect (G_OBJECT (button_sort), "clicked", + G_CALLBACK (servlist_sort), 0); + gtk_widget_show (button_sort); + gtk_container_add (GTK_CONTAINER (vbuttonbox2), button_sort); + GTK_WIDGET_SET_FLAGS (button_sort, GTK_CAN_DEFAULT); + + button_sort = gtk_button_new_with_mnemonic (_("_Favor")); + add_tip (button_sort, _("Mark or unmark this network as a favorite.")); + g_signal_connect (G_OBJECT (button_sort), "clicked", + G_CALLBACK (servlist_favor), 0); + gtk_widget_show (button_sort); + gtk_container_add (GTK_CONTAINER (vbuttonbox2), button_sort); + GTK_WIDGET_SET_FLAGS (button_sort, GTK_CAN_DEFAULT); + + hseparator1 = gtk_hseparator_new (); + gtk_widget_show (hseparator1); + gtk_box_pack_start (GTK_BOX (vbox1), hseparator1, FALSE, TRUE, 4); + + hbuttonbox1 = gtk_hbutton_box_new (); + gtk_widget_show (hbuttonbox1); + gtk_box_pack_start (GTK_BOX (vbox1), hbuttonbox1, FALSE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (hbuttonbox1), 8); + + button_close = gtk_button_new_from_stock ("gtk-close"); + gtk_widget_show (button_close); + g_signal_connect (G_OBJECT (button_close), "clicked", + G_CALLBACK (servlist_close_cb), 0); + gtk_container_add (GTK_CONTAINER (hbuttonbox1), button_close); + GTK_WIDGET_SET_FLAGS (button_close, GTK_CAN_DEFAULT); + + button_connect = gtkutil_button (hbuttonbox1, GTK_STOCK_CONNECT, NULL, + servlist_connect_cb, NULL, _("C_onnect")); + GTK_WIDGET_SET_FLAGS (button_connect, GTK_CAN_DEFAULT); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label3), entry1); + gtk_label_set_mnemonic_widget (GTK_LABEL (label6), entry4); + gtk_label_set_mnemonic_widget (GTK_LABEL (label7), entry5); + + gtk_widget_grab_focus (networks_tree); + gtk_widget_grab_default (button_close); + return servlist; +} + +void +fe_serverlist_open (session *sess) +{ + if (serverlist_win) + { + gtk_window_present (GTK_WINDOW (serverlist_win)); + return; + } + + servlist_sess = sess; + + serverlist_win = servlist_open_networks (); + gtkutil_set_icon (serverlist_win); + + servlist_networks_populate (networks_tree, network_list); + + g_signal_connect (G_OBJECT (serverlist_win), "delete_event", + G_CALLBACK (servlist_delete_cb), 0); + g_signal_connect (G_OBJECT (serverlist_win), "configure_event", + G_CALLBACK (servlist_configure_cb), 0); + g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (networks_tree))), + "changed", G_CALLBACK (servlist_network_row_cb), NULL); + g_signal_connect (G_OBJECT (networks_tree), "key_press_event", + G_CALLBACK (servlist_net_keypress_cb), networks_tree); + + gtk_widget_show (serverlist_win); +} diff --git a/src/fe-gtk/setup.c b/src/fe-gtk/setup.c new file mode 100644 index 00000000..517e0944 --- /dev/null +++ b/src/fe-gtk/setup.c @@ -0,0 +1,2137 @@ +/* X-Chat + * Copyright (C) 2004-2007 Peter Zelezny. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "../common/xchat.h" +#include "../common/cfgfiles.h" +#include "../common/fe.h" +#include "../common/text.h" +#include "../common/userlist.h" +#include "../common/util.h" +#include "../common/xchatc.h" +#include "fe-gtk.h" +#include "gtkutil.h" +#include "maingui.h" +#include "palette.h" +#include "pixmaps.h" +#include "menu.h" + +#include <gtk/gtkcolorseldialog.h> +#include <gtk/gtktable.h> +#include <gtk/gtkentry.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkmisc.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkvbox.h> +#include <gtk/gtkalignment.h> +#include <gtk/gtknotebook.h> +#include <gtk/gtkframe.h> +#include <gtk/gtkfontsel.h> +#include <gtk/gtkcheckbutton.h> +#include <gtk/gtkscrolledwindow.h> +#include <gtk/gtkspinbutton.h> +#include <gtk/gtkstock.h> +#include <gtk/gtktreeview.h> +#include <gtk/gtkhbbox.h> +#include <gtk/gtkhseparator.h> +#include <gtk/gtkradiobutton.h> +#include <gtk/gtkcombobox.h> +#include <gtk/gtkliststore.h> +#include <gtk/gtktreestore.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtkcellrenderertext.h> +#include <gtk/gtkhscale.h> +#ifdef WIN32 +#include "../common/fe.h" +#endif +#ifdef USE_GTKSPELL +#include <gtk/gtktextview.h> +#include <gtkspell/gtkspell.h> +#endif +#ifdef USE_LIBSEXY +#include "sexy-spell-entry.h" +#endif + +GtkStyle *create_input_style (GtkStyle *); + +#define LABEL_INDENT 12 + +static int last_selected_page = 0; +static int last_selected_row = 0; /* sound row */ +static gboolean color_change; +static struct xchatprefs setup_prefs; +static GtkWidget *cancel_button; +static GtkWidget *font_dialog = NULL; + +enum +{ + ST_END, + ST_TOGGLE, + ST_TOGGLR, + ST_3OGGLE, + ST_ENTRY, + ST_EFONT, + ST_EFILE, + ST_EFOLDER, + ST_MENU, + ST_RADIO, + ST_NUMBER, + ST_HSCALE, + ST_HEADER, + ST_LABEL, + ST_ALERTHEAD +}; + +typedef struct +{ + int type; + char *label; + int offset; + char *tooltip; + char const *const *list; + int extra; +} setting; + + +static const setting textbox_settings[] = +{ + {ST_HEADER, N_("Text Box Appearance"),0,0,0}, + {ST_EFONT, N_("Font:"), P_OFFSETNL(font_normal), 0, 0, sizeof prefs.font_normal}, + {ST_EFILE, N_("Background image:"), P_OFFSETNL(background), 0, 0, sizeof prefs.background}, + {ST_NUMBER, N_("Scrollback lines:"), P_OFFINTNL(max_lines),0,0,100000}, + {ST_TOGGLE, N_("Colored nick names"), P_OFFINTNL(colorednicks), + N_("Give each person on IRC a different color"),0,0}, + {ST_TOGGLR, N_("Indent nick names"), P_OFFINTNL(indent_nicks), + N_("Make nick names right-justified"),0,0}, + {ST_TOGGLE, N_("Transparent background"), P_OFFINTNL(transparent),0,0,0}, + {ST_TOGGLR, N_("Show marker line"), P_OFFINTNL(show_marker), + N_("Insert a red line after the last read text."),0,0}, + {ST_HEADER, N_("Transparency Settings"), 0,0,0}, + {ST_HSCALE, N_("Red:"), P_OFFINTNL(tint_red),0,0,0}, + {ST_HSCALE, N_("Green:"), P_OFFINTNL(tint_green),0,0,0}, + {ST_HSCALE, N_("Blue:"), P_OFFINTNL(tint_blue),0,0,0}, + + {ST_HEADER, N_("Time Stamps"),0,0,0}, + {ST_TOGGLE, N_("Enable time stamps"), P_OFFINTNL(timestamp),0,0,2}, + {ST_ENTRY, N_("Time stamp format:"), P_OFFSETNL(stamp_format), + N_("See strftime manpage for details."),0,sizeof prefs.stamp_format}, + + {ST_END, 0, 0, 0, 0, 0} +}; + +static const char *const tabcompmenu[] = +{ + N_("A-Z"), + N_("Last-spoke order"), + NULL +}; + +static const setting inputbox_settings[] = +{ + {ST_HEADER, N_("Input box"),0,0,0}, + {ST_TOGGLE, N_("Use the Text box font and colors"), P_OFFINTNL(style_inputbox),0,0,0}, +#if defined(USE_GTKSPELL) || defined(USE_LIBSEXY) + {ST_TOGGLE, N_("Spell checking"), P_OFFINTNL(gui_input_spell),0,0,0}, +#endif + + {ST_HEADER, N_("Nick Completion"),0,0,0}, + {ST_TOGGLE, N_("Automatic nick completion (without TAB key)"), P_OFFINTNL(nickcompletion), + 0,0,0}, + {ST_ENTRY, N_("Nick completion suffix:"), P_OFFSETNL(nick_suffix),0,0,sizeof prefs.nick_suffix}, + {ST_MENU, N_("Nick completion sorted:"), P_OFFINTNL(completion_sort), 0, tabcompmenu, 0}, + +#if 0 /* obsolete */ + {ST_HEADER, N_("Input Box Codes"),0,0,0}, + {ST_TOGGLE, N_("Interpret %nnn as an ASCII value"), P_OFFINTNL(perc_ascii),0,0,0}, + {ST_TOGGLE, N_("Interpret %C, %B as Color, Bold etc"), P_OFFINTNL(perc_color),0,0,0}, +#endif + + {ST_END, 0, 0, 0, 0, 0} +}; + +/*static const char *const lagmenutext[] = +{ + N_("Off"), + N_("Graph"), + N_("Info text"), + N_("Both"), + NULL +};*/ + +static const char *const ulmenutext[] = +{ + N_("A-Z, Ops first"), + N_("A-Z"), + N_("Z-A, Ops last"), + N_("Z-A"), + N_("Unsorted"), + NULL +}; + +static const char *const cspos[] = +{ + N_("Left (Upper)"), + N_("Left (Lower)"), + N_("Right (Upper)"), + N_("Right (Lower)"), + N_("Top"), + N_("Bottom"), + N_("Hidden"), + NULL +}; + +static const char *const ulpos[] = +{ + N_("Left (Upper)"), + N_("Left (Lower)"), + N_("Right (Upper)"), + N_("Right (Lower)"), + NULL +}; + +static const setting userlist_settings[] = +{ + {ST_HEADER, N_("User List"),0,0,0}, + {ST_TOGGLE, N_("Show hostnames in user list"), P_OFFINTNL(showhostname_in_userlist), 0, 0, 0}, + {ST_TOGGLE, N_("Use the Text box font and colors"), P_OFFINTNL(style_namelistgad),0,0,0}, +/* {ST_TOGGLE, N_("Resizable user list"), P_OFFINTNL(paned_userlist),0,0,0},*/ + {ST_MENU, N_("User list sorted by:"), P_OFFINTNL(userlist_sort), 0, ulmenutext, 0}, + {ST_MENU, N_("Show user list at:"), P_OFFINTNL(gui_ulist_pos), 0, ulpos, 1}, + + {ST_HEADER, N_("Away tracking"),0,0,0}, + {ST_TOGGLE, N_("Track the Away status of users and mark them in a different color"), P_OFFINTNL(away_track),0,0,2}, + {ST_NUMBER, N_("On channels smaller than:"), P_OFFINTNL(away_size_max),0,0,10000}, + + {ST_HEADER, N_("Action Upon Double Click"),0,0,0}, + {ST_ENTRY, N_("Execute command:"), P_OFFSETNL(doubleclickuser), 0, 0, sizeof prefs.doubleclickuser}, + +/* {ST_HEADER, N_("Extra Gadgets"),0,0,0}, + {ST_MENU, N_("Lag meter:"), P_OFFINTNL(lagometer), 0, lagmenutext, 0}, + {ST_MENU, N_("Throttle meter:"), P_OFFINTNL(throttlemeter), 0, lagmenutext, 0},*/ + + {ST_END, 0, 0, 0, 0, 0} +}; + +static const char *const tabwin[] = +{ + N_("Windows"), + N_("Tabs"), + NULL +}; + +#if 0 +static const char *const focusnewtabsmenu[] = +{ + N_("Never"), + N_("Always"), + N_("Only requested tabs"), + NULL +}; +#endif + +static const char *const swtype[] = +{ + N_("Tabs"), /* 0 tabs */ + "", /* 1 reserved */ + N_("Tree"), /* 2 tree */ + NULL +}; + +static const setting tabs_settings[] = +{ + /*{ST_HEADER, N_("Channel Switcher"),0,0,0},*/ + {ST_RADIO, N_("Switcher type:"),P_OFFINTNL(tab_layout), 0, swtype, 0}, + {ST_TOGGLE, N_("Open an extra tab for server messages"), P_OFFINTNL(use_server_tab), 0, 0, 0}, + {ST_TOGGLE, N_("Open an extra tab for server notices"), P_OFFINTNL(notices_tabs), 0, 0, 0}, + {ST_TOGGLE, N_("Open a new tab when you receive a private message"), P_OFFINTNL(autodialog), 0, 0, 0}, + {ST_TOGGLE, N_("Sort tabs in alphabetical order"), P_OFFINTNL(tab_sort), 0, 0, 0}, + {ST_TOGGLE, N_("Smaller text"), P_OFFINTNL(tab_small), 0, 0, 0}, +#if 0 + {ST_MENU, N_("Focus new tabs:"), P_OFFINTNL(newtabstofront), 0, focusnewtabsmenu, 0}, +#endif + {ST_MENU, N_("Show channel switcher at:"), P_OFFINTNL(tab_pos), 0, cspos, 1}, + {ST_NUMBER, N_("Shorten tab labels to:"), P_OFFINTNL(truncchans), 0, (const char **)N_("letters."), 99}, + + {ST_HEADER, N_("Tabs or Windows"),0,0,0}, + {ST_MENU, N_("Open channels in:"), P_OFFINTNL(tabchannels), 0, tabwin, 0}, + {ST_MENU, N_("Open dialogs in:"), P_OFFINTNL(privmsgtab), 0, tabwin, 0}, + {ST_MENU, N_("Open utilities in:"), P_OFFINTNL(windows_as_tabs), N_("Open DCC, Ignore, Notify etc, in tabs or windows?"), tabwin, 0}, + + {ST_END, 0, 0, 0, 0, 0} +}; + +static const char *const dccaccept[] = +{ + N_("No"), + N_("Yes"), + N_("Browse for save folder every time"), + NULL +}; + +static const setting filexfer_settings[] = +{ + {ST_HEADER, N_("Files and Directories"), 0, 0, 0}, + {ST_MENU, N_("Auto accept file offers:"), P_OFFINTNL(autodccsend), 0, dccaccept, 0}, + {ST_EFOLDER,N_("Download files to:"), P_OFFSETNL(dccdir), 0, 0, sizeof prefs.dccdir}, + {ST_EFOLDER,N_("Move completed files to:"), P_OFFSETNL(dcc_completed_dir), 0, 0, sizeof prefs.dcc_completed_dir}, + {ST_TOGGLE, N_("Save nick name in filenames"), P_OFFINTNL(dccwithnick), 0, 0, 0}, + + {ST_HEADER, N_("Network Settings"), 0, 0, 0}, + {ST_TOGGLE, N_("Get my address from the IRC server"), P_OFFINTNL(ip_from_server), + N_("Asks the IRC server for your real address. Use this if you have a 192.168.*.* address!"), 0, 0}, + {ST_ENTRY, N_("DCC IP address:"), P_OFFSETNL(dcc_ip_str), + N_("Claim you are at this address when offering files."), 0, sizeof prefs.dcc_ip_str}, + {ST_NUMBER, N_("First DCC send port:"), P_OFFINTNL(first_dcc_send_port), 0, 0, 65535}, + {ST_NUMBER, N_("Last DCC send port:"), P_OFFINTNL(last_dcc_send_port), 0, + (const char **)N_("!Leave ports at zero for full range."), 65535}, + + {ST_HEADER, N_("Maximum File Transfer Speeds (bytes per second)"), 0, 0, 0}, + {ST_NUMBER, N_("One upload:"), P_OFFINTNL(dcc_max_send_cps), + N_("Maximum speed for one transfer"), 0, 1000000}, + {ST_NUMBER, N_("One download:"), P_OFFINTNL(dcc_max_get_cps), + N_("Maximum speed for one transfer"), 0, 1000000}, + {ST_NUMBER, N_("All uploads combined:"), P_OFFINTNL(dcc_global_max_send_cps), + N_("Maximum speed for all files"), 0, 1000000}, + {ST_NUMBER, N_("All downloads combined:"), P_OFFINTNL(dcc_global_max_get_cps), + N_("Maximum speed for all files"), 0, 1000000}, + + {ST_END, 0, 0, 0, 0, 0} +}; + +static const int balloonlist[3] = +{ + P_OFFINTNL(input_balloon_chans), P_OFFINTNL(input_balloon_priv), P_OFFINTNL(input_balloon_hilight) +}; + +static const int trayblinklist[3] = +{ + P_OFFINTNL(input_tray_chans), P_OFFINTNL(input_tray_priv), P_OFFINTNL(input_tray_hilight) +}; + +static const int taskbarlist[3] = +{ + P_OFFINTNL(input_flash_chans), P_OFFINTNL(input_flash_priv), P_OFFINTNL(input_flash_hilight) +}; + +static const int beeplist[3] = +{ + P_OFFINTNL(input_beep_chans), P_OFFINTNL(input_beep_priv), P_OFFINTNL(input_beep_hilight) +}; + +static const setting alert_settings[] = +{ + {ST_HEADER, N_("Alerts"),0,0,0}, + + {ST_ALERTHEAD}, +#ifndef WIN32 + {ST_3OGGLE, N_("Show tray balloons on:"), 0, 0, (void *)balloonlist, 0}, +#endif + {ST_3OGGLE, N_("Blink tray icon on:"), 0, 0, (void *)trayblinklist, 0}, + {ST_3OGGLE, N_("Blink task bar on:"), 0, 0, (void *)taskbarlist, 0}, + {ST_3OGGLE, N_("Make a beep sound on:"), 0, 0, (void *)beeplist, 0}, + + {ST_TOGGLE, N_("Enable system tray icon"), P_OFFINTNL(gui_tray), 0, 0, 0}, + + {ST_HEADER, N_("Highlighted Messages"),0,0,0}, + {ST_LABEL, N_("Highlighted messages are ones where your nickname is mentioned, but also:"), 0, 0, 0, 1}, + + {ST_ENTRY, N_("Extra words to highlight:"), P_OFFSETNL(irc_extra_hilight), 0, 0, sizeof prefs.irc_extra_hilight}, + {ST_ENTRY, N_("Nick names not to highlight:"), P_OFFSETNL(irc_no_hilight), 0, 0, sizeof prefs.irc_no_hilight}, + {ST_ENTRY, N_("Nick names to always highlight:"), P_OFFSETNL(irc_nick_hilight), 0, 0, sizeof prefs.irc_nick_hilight}, + {ST_LABEL, N_("Separate multiple words with commas.\nWildcards are accepted.")}, + {ST_END, 0, 0, 0, 0, 0} +}; + +static const setting general_settings[] = +{ + {ST_HEADER, N_("Default Messages"),0,0,0}, + {ST_ENTRY, N_("Quit:"), P_OFFSETNL(quitreason), 0, 0, sizeof prefs.quitreason}, + {ST_ENTRY, N_("Leave channel:"), P_OFFSETNL(partreason), 0, 0, sizeof prefs.partreason}, + {ST_ENTRY, N_("Away:"), P_OFFSETNL(awayreason), 0, 0, sizeof prefs.awayreason}, + + {ST_HEADER, N_("Away"),0,0,0}, + {ST_TOGGLE, N_("Announce away messages"), P_OFFINTNL(show_away_message), + N_("Announce your away messages to all channels"), 0, 0}, + {ST_TOGGLE, N_("Show away once"), P_OFFINTNL(show_away_once), N_("Show identical away messages only once"), 0, 0}, + {ST_TOGGLE, N_("Automatically unmark away"), P_OFFINTNL(auto_unmark_away), N_("Unmark yourself as away before sending messages"), 0, 0}, + {ST_END, 0, 0, 0, 0, 0} +}; + +#if 0 +static const setting advanced_settings[] = +{ + {ST_HEADER, N_("Advanced Settings"),0,0,0}, + {ST_NUMBER, N_("Auto reconnect delay:"), P_OFFINTNL(recon_delay), 0, 0, 9999}, + {ST_TOGGLE, N_("Display MODEs in raw form"), P_OFFINTNL(raw_modes), 0, 0, 0}, + {ST_TOGGLE, N_("Whois on notify"), P_OFFINTNL(whois_on_notifyonline), N_("Sends a /WHOIS when a user comes online in your notify list"), 0, 0}, + {ST_TOGGLE, N_("Hide join and part messages"), P_OFFINTNL(confmode), N_("Hide channel join/part messages by default"), 0, 0}, + {ST_HEADER, N_("Auto Open DCC Windows"),0,0,0}, + {ST_TOGGLE, N_("Send window"), P_OFFINTNL(autoopendccsendwindow), 0, 0, 0}, + {ST_TOGGLE, N_("Receive window"), P_OFFINTNL(autoopendccrecvwindow), 0, 0, 0}, + {ST_TOGGLE, N_("Chat window"), P_OFFINTNL(autoopendccchatwindow), 0, 0, 0}, + + {ST_END, 0, 0, 0, 0, 0} +}; +#endif + +static const setting logging_settings[] = +{ + {ST_HEADER, N_("Logging"),0,0,0}, + {ST_TOGGLE, N_("Display scrollback from previous session"), P_OFFINTNL(text_replay), 0, 0, 0}, + {ST_TOGGLE, N_("Enable logging of conversations to disk"), P_OFFINTNL(logging), 0, 0, 2}, + {ST_ENTRY, N_("Log filename:"), P_OFFSETNL(logmask), 0, 0, sizeof prefs.logmask}, + {ST_LABEL, N_("%s=Server %c=Channel %n=Network.")}, + + {ST_HEADER, N_("Time Stamps"),0,0,0}, + {ST_TOGGLE, N_("Insert timestamps in logs"), P_OFFINTNL(timestamp_logs), 0, 0, 2}, + {ST_ENTRY, N_("Log timestamp format:"), P_OFFSETNL(timestamp_log_format), 0, 0, sizeof prefs.timestamp_log_format}, + {ST_LABEL, N_("See strftime manpage for details.")}, + + {ST_END, 0, 0, 0, 0, 0} +}; + +static const char *const proxytypes[] = +{ + N_("(Disabled)"), + N_("Wingate"), + N_("Socks4"), + N_("Socks5"), + N_("HTTP"), +#ifdef USE_MSPROXY + N_("MS Proxy (ISA)"), +#endif +#ifdef USE_LIBPROXY + N_("Auto"), +#endif + NULL +}; + +static const char *const proxyuse[] = +{ + N_("All Connections"), + N_("IRC Server Only"), + N_("DCC Get Only"), + NULL +}; + +static const setting network_settings[] = +{ + {ST_HEADER, N_("Your Address"), 0, 0, 0, 0}, + {ST_ENTRY, N_("Bind to:"), P_OFFSETNL(hostname), 0, 0, sizeof prefs.hostname}, + {ST_LABEL, N_("Only useful for computers with multiple addresses.")}, + + {ST_HEADER, N_("Proxy Server"), 0, 0, 0, 0}, + {ST_ENTRY, N_("Hostname:"), P_OFFSETNL(proxy_host), 0, 0, sizeof prefs.proxy_host}, + {ST_NUMBER, N_("Port:"), P_OFFINTNL(proxy_port), 0, 0, 65535}, + {ST_MENU, N_("Type:"), P_OFFINTNL(proxy_type), 0, proxytypes, 0}, + {ST_MENU, N_("Use proxy for:"), P_OFFINTNL(proxy_use), 0, proxyuse, 0}, + + {ST_HEADER, N_("Proxy Authentication"), 0, 0, 0, 0}, +#ifdef USE_MSPROXY + {ST_TOGGLE, N_("Use Authentication (MS Proxy, HTTP or Socks5 only)"), P_OFFINTNL(proxy_auth), 0, 0, 0}, +#else + {ST_TOGGLE, N_("Use Authentication (HTTP or Socks5 only)"), P_OFFINTNL(proxy_auth), 0, 0, 0}, +#endif + {ST_ENTRY, N_("Username:"), P_OFFSETNL(proxy_user), 0, 0, sizeof prefs.proxy_user}, + {ST_ENTRY, N_("Password:"), P_OFFSETNL(proxy_pass), 0, GINT_TO_POINTER(1), sizeof prefs.proxy_pass}, + + {ST_END, 0, 0, 0, 0, 0} +}; + +#define setup_get_str(pr,set) (((char *)pr)+set->offset) +#define setup_get_int(pr,set) *(((int *)pr)+set->offset) +#define setup_get_int3(pr,off) *(((int *)pr)+off) + +#define setup_set_int(pr,set,num) *((int *)pr+set->offset)=num +#define setup_set_str(pr,set,str) strcpy(((char *)pr)+set->offset,str) + + +static void +setup_3oggle_cb (GtkToggleButton *but, unsigned int *setting) +{ + *setting = but->active; +} + +static void +setup_headlabel (GtkWidget *tab, int row, int col, char *text) +{ + GtkWidget *label; + char buf[128]; + char *sp; + + snprintf (buf, sizeof (buf), "<b><span size=\"smaller\">%s</span></b>", text); + sp = strchr (buf + 17, ' '); + if (sp) + *sp = '\n'; + + label = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL (label), buf); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (tab), label, col, col + 1, row, row + 1, 0, 0, 4, 0); +} + +static void +setup_create_alert_header (GtkWidget *tab, int row, const setting *set) +{ + setup_headlabel (tab, row, 3, _("Channel Message")); + setup_headlabel (tab, row, 4, _("Private Message")); + setup_headlabel (tab, row, 5, _("Highlighted Message")); +} + +/* makes 3 toggles side-by-side */ + +static void +setup_create_3oggle (GtkWidget *tab, int row, const setting *set) +{ + GtkWidget *label, *wid; + int *offsets = (int *)set->list; + + label = gtk_label_new (_(set->label)); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (tab), label, 2, 3, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + + wid = gtk_check_button_new (); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), + setup_get_int3 (&setup_prefs, offsets[0])); + g_signal_connect (G_OBJECT (wid), "toggled", + G_CALLBACK (setup_3oggle_cb), ((int *)&setup_prefs) + offsets[0]); + gtk_table_attach (GTK_TABLE (tab), wid, 3, 4, row, row + 1, 0, 0, 0, 0); + + wid = gtk_check_button_new (); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), + setup_get_int3 (&setup_prefs, offsets[1])); + g_signal_connect (G_OBJECT (wid), "toggled", + G_CALLBACK (setup_3oggle_cb), ((int *)&setup_prefs) + offsets[1]); + gtk_table_attach (GTK_TABLE (tab), wid, 4, 5, row, row + 1, 0, 0, 0, 0); + + wid = gtk_check_button_new (); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), + setup_get_int3 (&setup_prefs, offsets[2])); + g_signal_connect (G_OBJECT (wid), "toggled", + G_CALLBACK (setup_3oggle_cb), ((int *)&setup_prefs) + offsets[2]); + gtk_table_attach (GTK_TABLE (tab), wid, 5, 6, row, row + 1, 0, 0, 0, 0); +} + +static void +setup_toggle_cb (GtkToggleButton *but, const setting *set) +{ + GtkWidget *label, *disable_wid; + + setup_set_int (&setup_prefs, set, but->active ? 1 : 0); + + /* does this toggle also enable/disable another widget? */ + disable_wid = g_object_get_data (G_OBJECT (but), "nxt"); + if (disable_wid) + { + gtk_widget_set_sensitive (disable_wid, but->active); + label = g_object_get_data (G_OBJECT (disable_wid), "lbl"); + gtk_widget_set_sensitive (label, but->active); + } +} + +static void +setup_create_toggleR (GtkWidget *tab, int row, const setting *set) +{ + GtkWidget *wid; + + wid = gtk_check_button_new_with_label (_(set->label)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), + setup_get_int (&setup_prefs, set)); + g_signal_connect (G_OBJECT (wid), "toggled", + G_CALLBACK (setup_toggle_cb), (gpointer)set); + if (set->tooltip) + add_tip (wid, _(set->tooltip)); + gtk_table_attach (GTK_TABLE (tab), wid, 4, 5, row, row + 1, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); +} + +static GtkWidget * +setup_create_toggleL (GtkWidget *tab, int row, const setting *set) +{ + GtkWidget *wid; + + wid = gtk_check_button_new_with_label (_(set->label)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), + setup_get_int (&setup_prefs, set)); + g_signal_connect (G_OBJECT (wid), "toggled", + G_CALLBACK (setup_toggle_cb), (gpointer)set); + if (set->tooltip) + add_tip (wid, _(set->tooltip)); + gtk_table_attach (GTK_TABLE (tab), wid, 2, row==6 ? 6 : 4, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + + return wid; +} + +#if 0 +static void +setup_create_toggle (GtkWidget *box, int row, const setting *set) +{ + GtkWidget *wid; + + wid = gtk_check_button_new_with_label (_(set->label)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), + setup_get_int (&setup_prefs, set)); + g_signal_connect (G_OBJECT (wid), "toggled", + G_CALLBACK (setup_toggle_cb), (gpointer)set); + if (set->tooltip) + add_tip (wid, _(set->tooltip)); + gtk_box_pack_start (GTK_BOX (box), wid, 0, 0, 0); +} +#endif + +static GtkWidget * +setup_create_italic_label (char *text) +{ + GtkWidget *label; + char buf[256]; + + label = gtk_label_new (NULL); + snprintf (buf, sizeof (buf), "<i><span size=\"smaller\">%s</span></i>", text); + gtk_label_set_markup (GTK_LABEL (label), buf); + gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER); + + return label; +} + +static void +setup_spin_cb (GtkSpinButton *spin, const setting *set) +{ + setup_set_int (&setup_prefs, set, gtk_spin_button_get_value_as_int (spin)); +} + +static GtkWidget * +setup_create_spin (GtkWidget *table, int row, const setting *set) +{ + GtkWidget *label, *wid, *rbox, *align; + char *text; + + label = gtk_label_new (_(set->label)); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), label, 2, 3, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + + align = gtk_alignment_new (0.0, 0.5, 0.0, 0.0); + gtk_table_attach (GTK_TABLE (table), align, 3, 4, row, row + 1, + GTK_EXPAND | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + + rbox = gtk_hbox_new (0, 0); + gtk_container_add (GTK_CONTAINER (align), rbox); + + wid = gtk_spin_button_new_with_range (0, set->extra, 1); + g_object_set_data (G_OBJECT (wid), "lbl", label); + if (set->tooltip) + add_tip (wid, _(set->tooltip)); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (wid), + setup_get_int (&setup_prefs, set)); + g_signal_connect (G_OBJECT (wid), "value_changed", + G_CALLBACK (setup_spin_cb), (gpointer)set); + gtk_box_pack_start (GTK_BOX (rbox), wid, 0, 0, 0); + + if (set->list) + { + text = _((char *)set->list); + if (text[0] == '!') + label = setup_create_italic_label (text + 1); + else + label = gtk_label_new (text); + gtk_box_pack_start (GTK_BOX (rbox), label, 0, 0, 6); + } + + return wid; +} + +static gint +setup_apply_tint (int *tag) +{ + prefs.tint_red = setup_prefs.tint_red; + prefs.tint_green = setup_prefs.tint_green; + prefs.tint_blue = setup_prefs.tint_blue; + mg_update_xtext (current_sess->gui->xtext); + *tag = 0; + return 0; +} + +static void +setup_hscale_cb (GtkHScale *wid, const setting *set) +{ + static int tag = 0; + + setup_set_int (&setup_prefs, set, gtk_range_get_value(GTK_RANGE(wid))); + if(tag == 0) + tag = g_idle_add ((GSourceFunc)setup_apply_tint, &tag); +} + +static void +setup_create_hscale (GtkWidget *table, int row, const setting *set) +{ + GtkWidget *wid; + + wid = gtk_label_new (_(set->label)); + gtk_misc_set_alignment (GTK_MISC (wid), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), wid, 2, 3, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + + wid = gtk_hscale_new_with_range (0., 255., 1.); + gtk_scale_set_value_pos (GTK_SCALE (wid), GTK_POS_RIGHT); + gtk_range_set_value (GTK_RANGE (wid), setup_get_int (&setup_prefs, set)); + g_signal_connect (G_OBJECT(wid), "value_changed", + G_CALLBACK (setup_hscale_cb), (gpointer)set); + gtk_table_attach (GTK_TABLE (table), wid, 3, 6, row, row + 1, + GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); +} + + +static GtkWidget *proxy_user; /* username GtkEntry */ +static GtkWidget *proxy_pass; /* password GtkEntry */ + +static void +setup_menu_cb (GtkWidget *cbox, const setting *set) +{ + int n = gtk_combo_box_get_active (GTK_COMBO_BOX (cbox)); + + /* set the prefs.<field> */ + setup_set_int (&setup_prefs, set, n + set->extra); + + if (set->list == proxytypes) + { + /* only HTTP and Socks5 can use a username/pass */ + gtk_widget_set_sensitive (proxy_user, (n == 3 || n == 4 || n == 5)); + gtk_widget_set_sensitive (proxy_pass, (n == 3 || n == 4 || n == 5)); + } +} + +static void +setup_radio_cb (GtkWidget *item, const setting *set) +{ + if (GTK_TOGGLE_BUTTON (item)->active) + { + int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "n")); + /* set the prefs.<field> */ + setup_set_int (&setup_prefs, set, n); + } +} + +static int +setup_create_radio (GtkWidget *table, int row, const setting *set) +{ + GtkWidget *wid, *hbox; + int i; + const char **text = (const char **)set->list; + GSList *group; + + wid = gtk_label_new (_(set->label)); + gtk_misc_set_alignment (GTK_MISC (wid), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), wid, 2, 3, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + + hbox = gtk_hbox_new (0, 0); + gtk_table_attach (GTK_TABLE (table), hbox, 3, 4, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + + i = 0; + group = NULL; + while (text[i]) + { + if (text[i][0] != 0) + { + wid = gtk_radio_button_new_with_mnemonic (group, _(text[i])); + /*if (set->tooltip) + add_tip (wid, _(set->tooltip));*/ + group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (wid)); + gtk_container_add (GTK_CONTAINER (hbox), wid); + if (i == setup_get_int (&setup_prefs, set)) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), TRUE); + g_object_set_data (G_OBJECT (wid), "n", GINT_TO_POINTER (i)); + g_signal_connect (G_OBJECT (wid), "toggled", + G_CALLBACK (setup_radio_cb), (gpointer)set); + } + i++; + row++; + } + + return i; +} + +/* +static const char *id_strings[] = +{ + "", + "*", + "%C4*%C18%B%B", + "%U" +}; + +static void +setup_id_menu_cb (GtkWidget *item, char *dest) +{ + int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "n")); + + strcpy (dest, id_strings[n]); +} + +static void +setup_create_id_menu (GtkWidget *table, char *label, int row, char *dest) +{ + GtkWidget *wid, *menu, *item; + int i, def = 0; + static const char *text[] = + { + ("(disabled)"), + ("A star (*)"), + ("A red star (*)"), + ("Underlined") + }; + + wid = gtk_label_new (label); + gtk_misc_set_alignment (GTK_MISC (wid), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), wid, 2, 3, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + + wid = gtk_option_menu_new (); + menu = gtk_menu_new (); + + for (i = 0; i < 4; i++) + { + if (strcmp (id_strings[i], dest) == 0) + { + def = i; + break; + } + } + + i = 0; + while (text[i]) + { + item = gtk_menu_item_new_with_label (_(text[i])); + g_object_set_data (G_OBJECT (item), "n", GINT_TO_POINTER (i)); + + gtk_widget_show (item); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (setup_id_menu_cb), dest); + i++; + } + + gtk_option_menu_set_menu (GTK_OPTION_MENU (wid), menu); + gtk_option_menu_set_history (GTK_OPTION_MENU (wid), def); + + gtk_table_attach (GTK_TABLE (table), wid, 3, 4, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); +} + +*/ + +static void +setup_create_menu (GtkWidget *table, int row, const setting *set) +{ + GtkWidget *wid, *cbox, *box; + const char **text = (const char **)set->list; + int i; + + wid = gtk_label_new (_(set->label)); + gtk_misc_set_alignment (GTK_MISC (wid), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), wid, 2, 3, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + + cbox = gtk_combo_box_new_text (); + + for (i = 0; text[i]; i++) + gtk_combo_box_append_text (GTK_COMBO_BOX (cbox), _(text[i])); + + gtk_combo_box_set_active (GTK_COMBO_BOX (cbox), + setup_get_int (&setup_prefs, set) - set->extra); + g_signal_connect (G_OBJECT (cbox), "changed", + G_CALLBACK (setup_menu_cb), (gpointer)set); + + box = gtk_hbox_new (0, 0); + gtk_box_pack_start (GTK_BOX (box), cbox, 0, 0, 0); + gtk_table_attach (GTK_TABLE (table), box, 3, 4, row, row + 1, + GTK_EXPAND | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); +} + +static void +setup_filereq_cb (GtkWidget *entry, char *file) +{ + if (file) + { + if (file[0]) + gtk_entry_set_text (GTK_ENTRY (entry), file); + } +} + +static void +setup_browsefile_cb (GtkWidget *button, GtkWidget *entry) +{ + gtkutil_file_req (_("Select an Image File"), setup_filereq_cb, entry, NULL, 0); +} + +static void +setup_fontsel_destroy (GtkWidget *button, GtkFontSelectionDialog *dialog) +{ + font_dialog = NULL; +} + +static void +setup_fontsel_cb (GtkWidget *button, GtkFontSelectionDialog *dialog) +{ + GtkWidget *entry; + char *font_name; + + entry = g_object_get_data (G_OBJECT (button), "e"); + font_name = gtk_font_selection_dialog_get_font_name (dialog); + + gtk_entry_set_text (GTK_ENTRY (entry), font_name); + + g_free (font_name); + gtk_widget_destroy (GTK_WIDGET (dialog)); + font_dialog = NULL; +} + +static void +setup_fontsel_cancel (GtkWidget *button, GtkFontSelectionDialog *dialog) +{ + gtk_widget_destroy (GTK_WIDGET (dialog)); + font_dialog = NULL; +} + +static void +setup_browsefolder_cb (GtkWidget *button, GtkEntry *entry) +{ + gtkutil_file_req (_("Select Download Folder"), setup_filereq_cb, entry, entry->text, FRF_CHOOSEFOLDER); +} + +static void +setup_browsefont_cb (GtkWidget *button, GtkWidget *entry) +{ + GtkFontSelection *sel; + GtkFontSelectionDialog *dialog; + + dialog = (GtkFontSelectionDialog *) gtk_font_selection_dialog_new (_("Select font")); + font_dialog = (GtkWidget *)dialog; /* global var */ + + sel = (GtkFontSelection *) dialog->fontsel; + + if (GTK_ENTRY (entry)->text[0]) + gtk_font_selection_set_font_name (sel, GTK_ENTRY (entry)->text); + + g_object_set_data (G_OBJECT (dialog->ok_button), "e", entry); + + g_signal_connect (G_OBJECT (dialog), "destroy", + G_CALLBACK (setup_fontsel_destroy), dialog); + g_signal_connect (G_OBJECT (dialog->ok_button), "clicked", + G_CALLBACK (setup_fontsel_cb), dialog); + g_signal_connect (G_OBJECT (dialog->cancel_button), "clicked", + G_CALLBACK (setup_fontsel_cancel), dialog); + + gtk_widget_show (GTK_WIDGET (dialog)); +} + +static void +setup_entry_cb (GtkEntry *entry, setting *set) +{ + int size; + int pos; + int len = strlen (entry->text); + unsigned char *p = entry->text; + + /* need to truncate? */ + if (len >= set->extra) + { + len = pos = 0; + while (1) + { + size = g_utf8_skip [*p]; + len += size; + p += size; + /* truncate to "set->extra" BYTES */ + if (len >= set->extra) + { + gtk_editable_delete_text (GTK_EDITABLE (entry), pos, -1); + break; + } + pos++; + } + } + else + { + setup_set_str (&setup_prefs, set, entry->text); + } +} + +static void +setup_create_label (GtkWidget *table, int row, const setting *set) +{ + gtk_table_attach (GTK_TABLE (table), setup_create_italic_label (_(set->label)), + set->extra ? 1 : 3, 5, row, row + 1, GTK_FILL, + GTK_SHRINK | GTK_FILL, 0, 0); +} + +static GtkWidget * +setup_create_entry (GtkWidget *table, int row, const setting *set) +{ + GtkWidget *label; + GtkWidget *wid, *bwid; + + label = gtk_label_new (_(set->label)); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), label, 2, 3, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + + wid = gtk_entry_new (); + g_object_set_data (G_OBJECT (wid), "lbl", label); + if (set->list) + gtk_entry_set_visibility (GTK_ENTRY (wid), FALSE); + if (set->tooltip) + add_tip (wid, _(set->tooltip)); + gtk_entry_set_max_length (GTK_ENTRY (wid), set->extra - 1); + gtk_entry_set_text (GTK_ENTRY (wid), setup_get_str (&setup_prefs, set)); + g_signal_connect (G_OBJECT (wid), "changed", + G_CALLBACK (setup_entry_cb), (gpointer)set); + + if (set->offset == P_OFFSETNL(proxy_user)) + proxy_user = wid; + if (set->offset == P_OFFSETNL(proxy_pass)) + proxy_pass = wid; + + /* only http and Socks5 can auth */ + if ( (set->offset == P_OFFSETNL(proxy_pass) || + set->offset == P_OFFSETNL(proxy_user)) && + (setup_prefs.proxy_type != 4 && setup_prefs.proxy_type != 3 && setup_prefs.proxy_type != 5) ) + gtk_widget_set_sensitive (wid, FALSE); + + if (set->type == ST_ENTRY) + gtk_table_attach (GTK_TABLE (table), wid, 3, 6, row, row + 1, + GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); + else + { + gtk_table_attach (GTK_TABLE (table), wid, 3, 5, row, row + 1, + GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0); + bwid = gtk_button_new_with_label (_("Browse...")); + gtk_table_attach (GTK_TABLE (table), bwid, 5, 6, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_FILL, 0, 0); + if (set->type == ST_EFILE) + g_signal_connect (G_OBJECT (bwid), "clicked", + G_CALLBACK (setup_browsefile_cb), wid); + if (set->type == ST_EFONT) + g_signal_connect (G_OBJECT (bwid), "clicked", + G_CALLBACK (setup_browsefont_cb), wid); + if (set->type == ST_EFOLDER) + g_signal_connect (G_OBJECT (bwid), "clicked", + G_CALLBACK (setup_browsefolder_cb), wid); + } + + return wid; +} + +static void +setup_create_header (GtkWidget *table, int row, char *labeltext) +{ + GtkWidget *label; + char buf[128]; + + if (row == 0) + snprintf (buf, sizeof (buf), "<b>%s</b>", _(labeltext)); + else + snprintf (buf, sizeof (buf), "\n<b>%s</b>", _(labeltext)); + + label = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL (label), buf); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), label, 0, 4, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 5); +} + +static GtkWidget * +setup_create_frame (GtkWidget **left, GtkWidget *box) +{ + GtkWidget *tab, *hbox, *inbox = box; + + tab = gtk_table_new (3, 2, FALSE); + gtk_container_set_border_width (GTK_CONTAINER (tab), 6); + gtk_table_set_row_spacings (GTK_TABLE (tab), 2); + gtk_table_set_col_spacings (GTK_TABLE (tab), 3); + gtk_container_add (GTK_CONTAINER (inbox), tab); + + hbox = gtk_hbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (inbox), hbox); + + *left = gtk_vbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (hbox), *left, 0, 0, 0); + + return tab; +} + +static void +open_data_cb (GtkWidget *button, gpointer data) +{ + fe_open_url (get_xdir_utf8 ()); +} + +static GtkWidget * +setup_create_page (const setting *set) +{ + int i, row, do_disable; + GtkWidget *tab, *box, *left; + GtkWidget *wid = NULL, *prev; + + box = gtk_vbox_new (FALSE, 1); + gtk_container_set_border_width (GTK_CONTAINER (box), 6); + + tab = setup_create_frame (&left, box); + + i = row = do_disable = 0; + while (set[i].type != ST_END) + { + prev = wid; + + switch (set[i].type) + { + case ST_HEADER: + setup_create_header (tab, row, set[i].label); + break; + case ST_EFONT: + case ST_ENTRY: + case ST_EFILE: + case ST_EFOLDER: + wid = setup_create_entry (tab, row, &set[i]); + break; + case ST_TOGGLR: + row--; + setup_create_toggleR (tab, row, &set[i]); + break; + case ST_TOGGLE: + wid = setup_create_toggleL (tab, row, &set[i]); + do_disable = set[i].extra; + break; + case ST_3OGGLE: + setup_create_3oggle (tab, row, &set[i]); + break; + case ST_MENU: + setup_create_menu (tab, row, &set[i]); + break; + case ST_RADIO: + row += setup_create_radio (tab, row, &set[i]); + break; + case ST_NUMBER: + wid = setup_create_spin (tab, row, &set[i]); + break; + case ST_HSCALE: + setup_create_hscale (tab, row, &set[i]); + break; + case ST_LABEL: + setup_create_label (tab, row, &set[i]); + break; + case ST_ALERTHEAD: + setup_create_alert_header (tab, row, &set[i]); + } + + /* will this toggle disable the "next" widget? */ + do_disable--; + if (do_disable == 0) + { + /* setup_toggle_cb uses this data */ + g_object_set_data (G_OBJECT (prev), "nxt", wid); + /* force initial sensitive state */ + gtk_widget_set_sensitive (wid, GTK_TOGGLE_BUTTON (prev)->active); + gtk_widget_set_sensitive (g_object_get_data (G_OBJECT (wid), "lbl"), + GTK_TOGGLE_BUTTON (prev)->active); + } + + i++; + row++; + } + +#if 0 + if (set == general_settings) + { + setup_create_id_menu (tab, _("Mark identified users with:"), + row, setup_prefs.irc_id_ytext); + setup_create_id_menu (tab, _("Mark not-identified users with:"), + row + 1, setup_prefs.irc_id_ntext); + } +#endif + + if (set == logging_settings) + { + GtkWidget *but = gtk_button_new_with_label (_("Open Data Folder")); + gtk_box_pack_start (GTK_BOX (left), but, 0, 0, 0); + g_signal_connect (G_OBJECT (but), "clicked", + G_CALLBACK (open_data_cb), 0); + } + + return box; +} + +static void +setup_color_ok_cb (GtkWidget *button, GtkWidget *dialog) +{ + GtkColorSelectionDialog *cdialog = GTK_COLOR_SELECTION_DIALOG (dialog); + GdkColor *col; + GdkColor old_color; + GtkStyle *style; + + col = g_object_get_data (G_OBJECT (button), "c"); + old_color = *col; + + button = g_object_get_data (G_OBJECT (button), "b"); + + if (!GTK_IS_WIDGET (button)) + { + gtk_widget_destroy (dialog); + return; + } + + color_change = TRUE; + + gtk_color_selection_get_current_color (GTK_COLOR_SELECTION (cdialog->colorsel), col); + + gdk_colormap_alloc_color (gtk_widget_get_colormap (button), col, TRUE, TRUE); + + style = gtk_style_new (); + style->bg[0] = *col; + gtk_widget_set_style (button, style); + g_object_unref (style); + + /* is this line correct?? */ + gdk_colormap_free_colors (gtk_widget_get_colormap (button), &old_color, 1); + + gtk_widget_destroy (dialog); +} + +static void +setup_color_cb (GtkWidget *button, gpointer userdata) +{ + GtkWidget *dialog; + GtkColorSelectionDialog *cdialog; + GdkColor *color; + + color = &colors[GPOINTER_TO_INT (userdata)]; + + dialog = gtk_color_selection_dialog_new (_("Select color")); + cdialog = GTK_COLOR_SELECTION_DIALOG (dialog); + + gtk_widget_hide (cdialog->help_button); + g_signal_connect (G_OBJECT (cdialog->ok_button), "clicked", + G_CALLBACK (setup_color_ok_cb), dialog); + g_signal_connect (G_OBJECT (cdialog->cancel_button), "clicked", + G_CALLBACK (gtkutil_destroy), dialog); + g_object_set_data (G_OBJECT (cdialog->ok_button), "c", color); + g_object_set_data (G_OBJECT (cdialog->ok_button), "b", button); + gtk_widget_set_sensitive (cdialog->help_button, FALSE); + gtk_color_selection_set_current_color (GTK_COLOR_SELECTION (cdialog->colorsel), color); + gtk_widget_show (dialog); +} + +static void +setup_create_color_button (GtkWidget *table, int num, int row, int col) +{ + GtkWidget *but; + GtkStyle *style; + char buf[64]; + + if (num > 31) + strcpy (buf, "<span size=\"x-small\"> </span>"); + else + /* 12345678901 23456789 01 23456789 */ + sprintf (buf, "<span size=\"x-small\">%d</span>", num); + but = gtk_button_new_with_label (" "); + gtk_label_set_markup (GTK_LABEL (GTK_BIN (but)->child), buf); + /* win32 build uses this to turn off themeing */ + g_object_set_data (G_OBJECT (but), "xchat-color", (gpointer)1); + gtk_table_attach (GTK_TABLE (table), but, col, col+1, row, row+1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + g_signal_connect (G_OBJECT (but), "clicked", + G_CALLBACK (setup_color_cb), GINT_TO_POINTER (num)); + style = gtk_style_new (); + style->bg[GTK_STATE_NORMAL] = colors[num]; + gtk_widget_set_style (but, style); + g_object_unref (style); +} + +static void +setup_create_other_colorR (char *text, int num, int row, GtkWidget *tab) +{ + GtkWidget *label; + + label = gtk_label_new (text); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (tab), label, 5, 9, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + setup_create_color_button (tab, num, row, 9); +} + +static void +setup_create_other_color (char *text, int num, int row, GtkWidget *tab) +{ + GtkWidget *label; + + label = gtk_label_new (text); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (tab), label, 2, 3, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + setup_create_color_button (tab, num, row, 3); +} + +static GtkWidget * +setup_create_color_page (void) +{ + GtkWidget *tab, *box, *label; + int i; + + box = gtk_vbox_new (FALSE, 0); + gtk_container_set_border_width (GTK_CONTAINER (box), 6); + + tab = gtk_table_new (9, 2, FALSE); + gtk_container_set_border_width (GTK_CONTAINER (tab), 6); + gtk_table_set_row_spacings (GTK_TABLE (tab), 2); + gtk_table_set_col_spacings (GTK_TABLE (tab), 3); + gtk_container_add (GTK_CONTAINER (box), tab); + + setup_create_header (tab, 0, N_("Text Colors")); + + label = gtk_label_new (_("mIRC colors:")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (tab), label, 2, 3, 1, 2, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + + for (i = 0; i < 16; i++) + setup_create_color_button (tab, i, 1, i+3); + + label = gtk_label_new (_("Local colors:")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (tab), label, 2, 3, 2, 3, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + + for (i = 16; i < 32; i++) + setup_create_color_button (tab, i, 2, (i+3) - 16); + + setup_create_other_color (_("Foreground:"), COL_FG, 3, tab); + setup_create_other_colorR (_("Background:"), COL_BG, 3, tab); + + setup_create_header (tab, 5, N_("Marking Text")); + + setup_create_other_color (_("Foreground:"), COL_MARK_FG, 6, tab); + setup_create_other_colorR (_("Background:"), COL_MARK_BG, 6, tab); + + setup_create_header (tab, 8, N_("Interface Colors")); + + setup_create_other_color (_("New data:"), COL_NEW_DATA, 9, tab); + setup_create_other_colorR (_("Marker line:"), COL_MARKER, 9, tab); + setup_create_other_color (_("New message:"), COL_NEW_MSG, 10, tab); + setup_create_other_colorR (_("Away user:"), COL_AWAY, 10, tab); + setup_create_other_color (_("Highlight:"), COL_HILIGHT, 11, tab); + + return box; +} + +/* === GLOBALS for sound GUI === */ + +static GtkWidget *sndprog_entry; +static GtkWidget *sndfile_entry; +static GtkWidget *snddir_entry; +static int ignore_changed = FALSE; + +extern struct text_event te[]; /* text.c */ +extern char *sound_files[]; + +static void +setup_snd_apply (void) +{ + strcpy (setup_prefs.sounddir, GTK_ENTRY (snddir_entry)->text); + strcpy (setup_prefs.soundcmd, GTK_ENTRY (sndprog_entry)->text); +} + +static void +setup_snd_populate (GtkTreeView * treeview) +{ + GtkListStore *store; + GtkTreeIter iter; + GtkTreeSelection *sel; + GtkTreePath *path; + int i; + + sel = gtk_tree_view_get_selection (treeview); + store = (GtkListStore *)gtk_tree_view_get_model (treeview); + + for (i = NUM_XP-1; i >= 0; i--) + { + gtk_list_store_prepend (store, &iter); + if (sound_files[i]) + gtk_list_store_set (store, &iter, 0, te[i].name, 1, sound_files[i], 2, i, -1); + else + gtk_list_store_set (store, &iter, 0, te[i].name, 1, "", 2, i, -1); + if (i == last_selected_row) + { + gtk_tree_selection_select_iter (sel, &iter); + path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter); + if (path) + { + gtk_tree_view_scroll_to_cell (treeview, path, NULL, TRUE, 0.5, 0.5); + gtk_tree_view_set_cursor (treeview, path, NULL, FALSE); + gtk_tree_path_free (path); + } + } + } +} + +static int +setup_snd_get_selected (GtkTreeSelection *sel, GtkTreeIter *iter) +{ + int n; + GtkTreeModel *model; + + if (!gtk_tree_selection_get_selected (sel, &model, iter)) + return -1; + + gtk_tree_model_get (model, iter, 2, &n, -1); + return n; +} + +static void +setup_snd_row_cb (GtkTreeSelection *sel, gpointer user_data) +{ + int n; + GtkTreeIter iter; + + n = setup_snd_get_selected (sel, &iter); + if (n == -1) + return; + last_selected_row = n; + + ignore_changed = TRUE; + if (sound_files[n]) + gtk_entry_set_text (GTK_ENTRY (sndfile_entry), sound_files[n]); + else + gtk_entry_set_text (GTK_ENTRY (sndfile_entry), ""); + ignore_changed = FALSE; +} + +static void +setup_snd_add_columns (GtkTreeView * treeview) +{ + GtkCellRenderer *renderer; + GtkTreeModel *model; + + /* event column */ + renderer = gtk_cell_renderer_text_new (); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview), + -1, _("Event"), renderer, + "text", 0, NULL); + + /* file column */ + renderer = gtk_cell_renderer_text_new (); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview), + -1, _("Sound file"), renderer, + "text", 1, NULL); + + model = GTK_TREE_MODEL (gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT)); + gtk_tree_view_set_model (GTK_TREE_VIEW (treeview), model); + g_object_unref (model); +} + +static void +setup_autotoggle_cb (GtkToggleButton *but, GtkToggleButton *ext) +{ + if (but->active) + { + setup_prefs.soundcmd[0] = 0; + gtk_entry_set_text (GTK_ENTRY (sndprog_entry), ""); + gtk_widget_set_sensitive (sndprog_entry, FALSE); + } else + { + gtk_widget_set_sensitive (sndprog_entry, TRUE); + } +} + +static void +setup_snd_filereq_cb (GtkWidget *entry, char *file) +{ + if (file) + { + if (file[0]) + gtk_entry_set_text (GTK_ENTRY (entry), file); + } +} + +static void +setup_snd_browse_cb (GtkWidget *button, GtkEntry *entry) +{ + gtkutil_file_req (_("Select a sound file"), setup_snd_filereq_cb, entry, NULL, 0); +} + +static void +setup_snd_play_cb (GtkWidget *button, GtkEntry *entry) +{ + sound_play (entry->text, FALSE); +} + +static void +setup_snd_changed_cb (GtkEntry *ent, GtkTreeView *tree) +{ + int n; + GtkTreeIter iter; + GtkListStore *store; + GtkTreeSelection *sel; + + if (ignore_changed) + return; + + sel = gtk_tree_view_get_selection (tree); + n = setup_snd_get_selected (sel, &iter); + if (n == -1) + return; + + /* get the new sound file */ + if (sound_files[n]) + free (sound_files[n]); + sound_files[n] = strdup (GTK_ENTRY (ent)->text); + + /* update the TreeView list */ + store = (GtkListStore *)gtk_tree_view_get_model (tree); + gtk_list_store_set (store, &iter, 1, sound_files[n], -1); + + gtk_widget_set_sensitive (cancel_button, FALSE); +} + +static GtkWidget * +setup_create_sound_page (void) +{ + GtkWidget *vbox1; + GtkWidget *vbox2; + GtkWidget *table2; + GtkWidget *label2; + GtkWidget *label3; + GtkWidget *radio_external; + GSList *radio_group = NULL; + GtkWidget *radio_auto; + GtkWidget *label4; + GtkWidget *entry3; + GtkWidget *scrolledwindow1; + GtkWidget *sound_tree; + GtkWidget *table1; + GtkWidget *sound_label; + GtkWidget *sound_browse; + GtkWidget *sound_play; + GtkTreeSelection *sel; + + vbox1 = gtk_vbox_new (FALSE, 0); + gtk_container_set_border_width (GTK_CONTAINER (vbox1), 6); + gtk_widget_show (vbox1); + + vbox2 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox2); + gtk_container_add (GTK_CONTAINER (vbox1), vbox2); + + table2 = gtk_table_new (4, 3, FALSE); + gtk_widget_show (table2); + gtk_box_pack_start (GTK_BOX (vbox2), table2, FALSE, TRUE, 8); + gtk_table_set_row_spacings (GTK_TABLE (table2), 2); + gtk_table_set_col_spacings (GTK_TABLE (table2), 4); + + label2 = gtk_label_new (_("Sound playing method:")); + gtk_widget_show (label2); + gtk_table_attach (GTK_TABLE (table2), label2, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label2), 0, 0.5); + + label3 = + gtk_label_new_with_mnemonic (_("External sound playing _program:")); + gtk_widget_show (label3); + gtk_table_attach (GTK_TABLE (table2), label3, 0, 1, 2, 3, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label3), 0, 0.5); + + sndprog_entry = gtk_entry_new (); + if (setup_prefs.soundcmd[0] == 0) + gtk_widget_set_sensitive (sndprog_entry, FALSE); + else + gtk_entry_set_text (GTK_ENTRY (sndprog_entry), setup_prefs.soundcmd); + gtk_widget_show (sndprog_entry); + gtk_table_attach (GTK_TABLE (table2), sndprog_entry, 1, 3, 2, 3, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + radio_external = + gtk_radio_button_new_with_mnemonic (NULL, _("_External program")); + gtk_widget_show (radio_external); + gtk_table_attach (GTK_TABLE (table2), radio_external, 1, 3, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_radio_button_set_group (GTK_RADIO_BUTTON (radio_external), + radio_group); + radio_group = + gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio_external)); + + radio_auto = gtk_radio_button_new_with_mnemonic (NULL, _("_Automatic")); + g_signal_connect (G_OBJECT (radio_auto), "toggled", + G_CALLBACK (setup_autotoggle_cb), radio_external); + gtk_widget_show (radio_auto); + gtk_table_attach (GTK_TABLE (table2), radio_auto, 1, 3, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_radio_button_set_group (GTK_RADIO_BUTTON (radio_auto), + radio_group); + radio_group = + gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio_auto)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio_auto), setup_prefs.soundcmd[0] == 0); + + label4 = gtk_label_new_with_mnemonic (_("Sound files _directory:")); + gtk_widget_show (label4); + gtk_table_attach (GTK_TABLE (table2), label4, 0, 1, 3, 4, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label4), 0, 0.5); + + snddir_entry = entry3 = gtk_entry_new (); + gtk_entry_set_text (GTK_ENTRY (entry3), setup_prefs.sounddir); + gtk_widget_show (entry3); + gtk_table_attach (GTK_TABLE (table2), entry3, 1, 3, 3, 4, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + scrolledwindow1 = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (scrolledwindow1); + gtk_container_add (GTK_CONTAINER (vbox2), scrolledwindow1); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow1), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow1), + GTK_SHADOW_IN); + + sound_tree = gtk_tree_view_new (); + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (sound_tree)); + gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE); + setup_snd_add_columns (GTK_TREE_VIEW (sound_tree)); + setup_snd_populate (GTK_TREE_VIEW (sound_tree)); + g_signal_connect (G_OBJECT (sel), "changed", + G_CALLBACK (setup_snd_row_cb), NULL); + gtk_widget_show (sound_tree); + gtk_container_add (GTK_CONTAINER (scrolledwindow1), sound_tree); + gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (sound_tree), TRUE); + + table1 = gtk_table_new (2, 3, FALSE); + gtk_widget_show (table1); + gtk_box_pack_start (GTK_BOX (vbox2), table1, FALSE, TRUE, 8); + gtk_table_set_row_spacings (GTK_TABLE (table1), 2); + gtk_table_set_col_spacings (GTK_TABLE (table1), 4); + + sound_label = gtk_label_new_with_mnemonic (_("Sound file:")); + gtk_widget_show (sound_label); + gtk_table_attach (GTK_TABLE (table1), sound_label, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (sound_label), 0, 0.5); + + sndfile_entry = gtk_entry_new (); + g_signal_connect (G_OBJECT (sndfile_entry), "changed", + G_CALLBACK (setup_snd_changed_cb), sound_tree); + gtk_widget_show (sndfile_entry); + gtk_table_attach (GTK_TABLE (table1), sndfile_entry, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + sound_browse = gtk_button_new_with_mnemonic (_("_Browse...")); + g_signal_connect (G_OBJECT (sound_browse), "clicked", + G_CALLBACK (setup_snd_browse_cb), sndfile_entry); + gtk_widget_show (sound_browse); + gtk_table_attach (GTK_TABLE (table1), sound_browse, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + +#ifdef GTK_STOCK_MEDIA_PLAY + sound_play = gtk_button_new_from_stock (GTK_STOCK_MEDIA_PLAY); +#else + sound_play = gtk_button_new_with_mnemonic (_("_Play")); +#endif + g_signal_connect (G_OBJECT (sound_play), "clicked", + G_CALLBACK (setup_snd_play_cb), sndfile_entry); + gtk_widget_show (sound_play); + gtk_table_attach (GTK_TABLE (table1), sound_play, 2, 3, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label3), sndprog_entry); + gtk_label_set_mnemonic_widget (GTK_LABEL (label4), entry3); + setup_snd_row_cb (sel, NULL); + + return vbox1; +} + +static void +setup_add_page (const char *title, GtkWidget *book, GtkWidget *tab) +{ + GtkWidget *oframe, *frame, *label, *vvbox; + char buf[128]; + + /* frame for whole page */ + oframe = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (oframe), GTK_SHADOW_IN); + + vvbox = gtk_vbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (oframe), vvbox); + + /* border for the label */ + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); + gtk_box_pack_start (GTK_BOX (vvbox), frame, FALSE, TRUE, 0); + + /* label */ + label = gtk_label_new (NULL); + snprintf (buf, sizeof (buf), "<b><big>%s</big></b>", _(title)); + gtk_label_set_markup (GTK_LABEL (label), buf); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_misc_set_padding (GTK_MISC (label), 2, 1); + gtk_container_add (GTK_CONTAINER (frame), label); + + gtk_container_add (GTK_CONTAINER (vvbox), tab); + + gtk_notebook_append_page (GTK_NOTEBOOK (book), oframe, NULL); +} + +static const char *const cata[] = +{ + N_("Interface"), + N_("Text box"), + N_("Input box"), + N_("User list"), + N_("Channel switcher"), + N_("Colors"), + NULL, + N_("Chatting"), + N_("Alerts"), + N_("General"), + N_("Logging"), + N_("Sound"), +/* N_("Advanced"),*/ + NULL, + N_("Network"), + N_("Network setup"), + N_("File transfers"), + NULL, + NULL +}; + +static GtkWidget * +setup_create_pages (GtkWidget *box) +{ + GtkWidget *book; + + book = gtk_notebook_new (); + + setup_add_page (cata[1], book, setup_create_page (textbox_settings)); + setup_add_page (cata[2], book, setup_create_page (inputbox_settings)); + setup_add_page (cata[3], book, setup_create_page (userlist_settings)); + setup_add_page (cata[4], book, setup_create_page (tabs_settings)); + setup_add_page (cata[5], book, setup_create_color_page ()); + setup_add_page (cata[8], book, setup_create_page (alert_settings)); + setup_add_page (cata[9], book, setup_create_page (general_settings)); + setup_add_page (cata[10], book, setup_create_page (logging_settings)); + setup_add_page (cata[11], book, setup_create_sound_page ()); + setup_add_page (cata[14], book, setup_create_page (network_settings)); + setup_add_page (cata[15], book, setup_create_page (filexfer_settings)); + + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (book), FALSE); + gtk_notebook_set_show_border (GTK_NOTEBOOK (book), FALSE); + gtk_container_add (GTK_CONTAINER (box), book); + + return book; +} + +static void +setup_tree_cb (GtkTreeView *treeview, GtkWidget *book) +{ + GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview); + GtkTreeIter iter; + GtkTreeModel *model; + int page; + + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + { + gtk_tree_model_get (model, &iter, 1, &page, -1); + if (page != -1) + { + gtk_notebook_set_current_page (GTK_NOTEBOOK (book), page); + last_selected_page = page; + } + } +} + +static gboolean +setup_tree_select_filter (GtkTreeSelection *selection, GtkTreeModel *model, + GtkTreePath *path, gboolean path_selected, + gpointer data) +{ + if (gtk_tree_path_get_depth (path) > 1) + return TRUE; + return FALSE; +} + +static void +setup_create_tree (GtkWidget *box, GtkWidget *book) +{ + GtkWidget *tree; + GtkWidget *frame; + GtkTreeStore *model; + GtkTreeIter iter; + GtkTreeIter child_iter; + GtkTreeIter *sel_iter = NULL; + GtkCellRenderer *renderer; + GtkTreeSelection *sel; + int i, page; + + model = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_INT); + + i = 0; + page = 0; + do + { + gtk_tree_store_append (model, &iter, NULL); + gtk_tree_store_set (model, &iter, 0, _(cata[i]), 1, -1, -1); + i++; + + do + { + gtk_tree_store_append (model, &child_iter, &iter); + gtk_tree_store_set (model, &child_iter, 0, _(cata[i]), 1, page, -1); + if (page == last_selected_page) + sel_iter = gtk_tree_iter_copy (&child_iter); + page++; + i++; + } while (cata[i]); + + i++; + + } while (cata[i]); + + tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model)); + g_object_unref (G_OBJECT (model)); + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree)); + gtk_tree_selection_set_mode (sel, GTK_SELECTION_BROWSE); + gtk_tree_selection_set_select_function (sel, setup_tree_select_filter, + NULL, NULL); + g_signal_connect (G_OBJECT (tree), "cursor_changed", + G_CALLBACK (setup_tree_cb), book); + + renderer = gtk_cell_renderer_text_new (); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree), + -1, _("Categories"), renderer, "text", 0, NULL); + gtk_tree_view_expand_all (GTK_TREE_VIEW (tree)); + + frame = gtk_frame_new (NULL); + gtk_container_add (GTK_CONTAINER (frame), tree); + gtk_box_pack_start (GTK_BOX (box), frame, 0, 0, 0); + gtk_box_reorder_child (GTK_BOX (box), frame, 0); + + if (sel_iter) + { + gtk_tree_selection_select_iter (sel, sel_iter); + gtk_tree_iter_free (sel_iter); + } +} + +static void +setup_apply_entry_style (GtkWidget *entry) +{ + gtk_widget_modify_base (entry, GTK_STATE_NORMAL, &colors[COL_BG]); + gtk_widget_modify_text (entry, GTK_STATE_NORMAL, &colors[COL_FG]); + gtk_widget_modify_font (entry, input_style->font_desc); +} + +static void +setup_apply_to_sess (session_gui *gui) +{ +#ifdef USE_GTKSPELL + GtkSpell *spell; +#endif + + mg_update_xtext (gui->xtext); + + if (prefs.style_namelistgad) + gtk_widget_set_style (gui->user_tree, input_style); + + if (prefs.style_inputbox) + { + extern char cursor_color_rc[]; + char buf[256]; + sprintf (buf, cursor_color_rc, + (colors[COL_FG].red >> 8), + (colors[COL_FG].green >> 8), + (colors[COL_FG].blue >> 8)); + gtk_rc_parse_string (buf); + + setup_apply_entry_style (gui->input_box); + setup_apply_entry_style (gui->limit_entry); + setup_apply_entry_style (gui->key_entry); + setup_apply_entry_style (gui->topic_entry); + } + + if (prefs.userlistbuttons) + gtk_widget_show (gui->button_box); + else + gtk_widget_hide (gui->button_box); + +#ifdef USE_GTKSPELL + spell = gtkspell_get_from_text_view (GTK_TEXT_VIEW (gui->input_box)); + if (prefs.gui_input_spell) + { + if (!spell) + gtkspell_new_attach (GTK_TEXT_VIEW (gui->input_box), NULL, NULL); + } + else + { + if (spell) + gtkspell_detach (spell); + } +#endif + +#ifdef USE_LIBSEXY + sexy_spell_entry_set_checked ((SexySpellEntry *)gui->input_box, prefs.gui_input_spell); +#endif +} + +static void +unslash (char *dir) +{ + if (dir[0]) + { + int len = strlen (dir) - 1; +#ifdef WIN32 + if (dir[len] == '/' || dir[len] == '\\') +#else + if (dir[len] == '/') +#endif + dir[len] = 0; + } +} + +void +setup_apply_real (int new_pix, int do_ulist, int do_layout) +{ + GSList *list; + session *sess; + int done_main = FALSE; + + /* remove trailing slashes */ + unslash (prefs.dccdir); + unslash (prefs.dcc_completed_dir); + + mkdir_utf8 (prefs.dccdir); + mkdir_utf8 (prefs.dcc_completed_dir); + + if (new_pix) + { + if (channelwin_pix) + g_object_unref (channelwin_pix); + channelwin_pix = pixmap_load_from_file (prefs.background); + } + + input_style = create_input_style (input_style); + + list = sess_list; + while (list) + { + sess = list->data; + if (sess->gui->is_tab) + { + /* only apply to main tabwindow once */ + if (!done_main) + { + done_main = TRUE; + setup_apply_to_sess (sess->gui); + } + } else + { + setup_apply_to_sess (sess->gui); + } + + log_open_or_close (sess); + + if (do_ulist) + userlist_rehash (sess); + + list = list->next; + } + + mg_apply_setup (); + tray_apply_setup (); + + if (do_layout) + menu_change_layout (); +} + +static void +setup_apply (struct xchatprefs *pr) +{ + int new_pix = FALSE; + int noapply = FALSE; + int do_ulist = FALSE; + int do_layout = FALSE; + + if (strcmp (pr->background, prefs.background) != 0) + new_pix = TRUE; + +#define DIFF(a) (pr->a != prefs.a) + + if (DIFF (paned_userlist)) + noapply = TRUE; + if (DIFF (lagometer)) + noapply = TRUE; + if (DIFF (throttlemeter)) + noapply = TRUE; + if (DIFF (showhostname_in_userlist)) + noapply = TRUE; + if (DIFF (tab_small)) + noapply = TRUE; + if (DIFF (tab_sort)) + noapply = TRUE; + if (DIFF (use_server_tab)) + noapply = TRUE; + if (DIFF (style_namelistgad)) + noapply = TRUE; + if (DIFF (truncchans)) + noapply = TRUE; + if (DIFF (tab_layout)) + do_layout = TRUE; + + if (color_change || (DIFF (away_size_max)) || (DIFF (away_track))) + do_ulist = TRUE; + + if ((pr->tab_pos == 5 || pr->tab_pos == 6) && + pr->tab_layout == 2 && pr->tab_pos != prefs.tab_pos) + fe_message (_("You cannot place the tree on the top or bottom!\n" + "Please change to the <b>Tabs</b> layout in the <b>View</b>" + " menu first."), + FE_MSG_WARN | FE_MSG_MARKUP); + + memcpy (&prefs, pr, sizeof (prefs)); + + setup_apply_real (new_pix, do_ulist, do_layout); + + if (noapply) + fe_message (_("Some settings were changed that require a" + " restart to take full effect."), FE_MSG_WARN); + +#ifndef WIN32 + if (prefs.autodccsend == 1) + { + if (!strcmp ((char *)g_get_home_dir (), prefs.dccdir)) + { + fe_message (_("*WARNING*\n" + "Auto accepting DCC to your home directory\n" + "can be dangerous and is exploitable. Eg:\n" + "Someone could send you a .bash_profile"), FE_MSG_WARN); + } + } +#endif +} + +#if 0 +static void +setup_apply_cb (GtkWidget *but, GtkWidget *win) +{ + /* setup_prefs -> xchat */ + setup_apply (&setup_prefs); +} +#endif + +static void +setup_ok_cb (GtkWidget *but, GtkWidget *win) +{ + setup_snd_apply (); + gtk_widget_destroy (win); + setup_apply (&setup_prefs); + save_config (); + palette_save (); +} + +static GtkWidget * +setup_window_open (void) +{ + GtkWidget *wid, *win, *vbox, *hbox, *hbbox; + + win = gtkutil_window_new (_("XChat: Preferences"), "prefs", 0, 0, 3); + + vbox = gtk_vbox_new (FALSE, 5); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 6); + gtk_container_add (GTK_CONTAINER (win), vbox); + + hbox = gtk_hbox_new (FALSE, 4); + gtk_container_add (GTK_CONTAINER (vbox), hbox); + + setup_create_tree (hbox, setup_create_pages (hbox)); + + hbox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + + /* prepare the button box */ + hbbox = gtk_hbutton_box_new (); + gtk_box_set_spacing (GTK_BOX (hbbox), 4); + gtk_box_pack_end (GTK_BOX (hbox), hbbox, FALSE, FALSE, 0); + + /* standard buttons */ + /* GNOME doesn't like apply */ +#if 0 + wid = gtk_button_new_from_stock (GTK_STOCK_APPLY); + g_signal_connect (G_OBJECT (wid), "clicked", + G_CALLBACK (setup_apply_cb), win); + gtk_box_pack_start (GTK_BOX (hbbox), wid, FALSE, FALSE, 0); +#endif + + cancel_button = wid = gtk_button_new_from_stock (GTK_STOCK_CANCEL); + g_signal_connect (G_OBJECT (wid), "clicked", + G_CALLBACK (gtkutil_destroy), win); + gtk_box_pack_start (GTK_BOX (hbbox), wid, FALSE, FALSE, 0); + + wid = gtk_button_new_from_stock (GTK_STOCK_OK); + g_signal_connect (G_OBJECT (wid), "clicked", + G_CALLBACK (setup_ok_cb), win); + gtk_box_pack_start (GTK_BOX (hbbox), wid, FALSE, FALSE, 0); + + wid = gtk_hseparator_new (); + gtk_box_pack_end (GTK_BOX (vbox), wid, FALSE, FALSE, 0); + + gtk_widget_show_all (win); + + return win; +} + +static void +setup_close_cb (GtkWidget *win, GtkWidget **swin) +{ + *swin = NULL; + + if (font_dialog) + { + gtk_widget_destroy (font_dialog); + font_dialog = NULL; + } +} + +void +setup_open (void) +{ + static GtkWidget *setup_window = NULL; + + if (setup_window) + { + gtk_window_present (GTK_WINDOW (setup_window)); + return; + } + + memcpy (&setup_prefs, &prefs, sizeof (prefs)); + + color_change = FALSE; + setup_window = setup_window_open (); + + g_signal_connect (G_OBJECT (setup_window), "destroy", + G_CALLBACK (setup_close_cb), &setup_window); +} diff --git a/src/fe-gtk/sexy-spell-entry.c b/src/fe-gtk/sexy-spell-entry.c new file mode 100644 index 00000000..d67ffe2d --- /dev/null +++ b/src/fe-gtk/sexy-spell-entry.c @@ -0,0 +1,1329 @@ +/* + * @file libsexy/sexy-icon-entry.c Entry widget + * + * @Copyright (C) 2004-2006 Christian Hammond. + * Some of this code is from gtkspell, Copyright (C) 2002 Evan Martin. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <gtk/gtk.h> +#include "sexy-spell-entry.h" +#include <string.h> +#include <glib/gi18n.h> +#include <sys/types.h> +/*#include "gtkspell-iso-codes.h" +#include "sexy-marshal.h"*/ + +/* + * Bunch of poop to make enchant into a runtime dependency rather than a + * compile-time dependency. This makes it so I don't have to hear the + * complaints from people with binary distributions who don't get spell + * checking because they didn't check their configure output. + */ +struct EnchantDict; +struct EnchantBroker; + +typedef void (*EnchantDictDescribeFn) (const char * const lang_tag, + const char * const provider_name, + const char * const provider_desc, + const char * const provider_file, + void * user_data); + +static struct EnchantBroker * (*enchant_broker_init) (void); +static void (*enchant_broker_free) (struct EnchantBroker * broker); +static void (*enchant_broker_free_dict) (struct EnchantBroker * broker, struct EnchantDict * dict); +static void (*enchant_broker_list_dicts) (struct EnchantBroker * broker, EnchantDictDescribeFn fn, void * user_data); +static struct EnchantDict * (*enchant_broker_request_dict) (struct EnchantBroker * broker, const char *const tag); + +static void (*enchant_dict_add_to_personal) (struct EnchantDict * dict, const char *const word, ssize_t len); +static void (*enchant_dict_add_to_session) (struct EnchantDict * dict, const char *const word, ssize_t len); +static int (*enchant_dict_check) (struct EnchantDict * dict, const char *const word, ssize_t len); +static void (*enchant_dict_describe) (struct EnchantDict * dict, EnchantDictDescribeFn fn, void * user_data); +static void (*enchant_dict_free_suggestions) (struct EnchantDict * dict, char **suggestions); +static void (*enchant_dict_store_replacement) (struct EnchantDict * dict, const char *const mis, ssize_t mis_len, const char *const cor, ssize_t cor_len); +static char ** (*enchant_dict_suggest) (struct EnchantDict * dict, const char *const word, ssize_t len, size_t * out_n_suggs); +static gboolean have_enchant = FALSE; + +struct _SexySpellEntryPriv +{ + struct EnchantBroker *broker; + PangoAttrList *attr_list; + gint mark_character; + GHashTable *dict_hash; + GSList *dict_list; + gchar **words; + gint *word_starts; + gint *word_ends; + gboolean checked; +}; + +static void sexy_spell_entry_class_init(SexySpellEntryClass *klass); +static void sexy_spell_entry_editable_init (GtkEditableClass *iface); +static void sexy_spell_entry_init(SexySpellEntry *entry); +static void sexy_spell_entry_finalize(GObject *obj); +static void sexy_spell_entry_destroy(GtkObject *obj); +static gint sexy_spell_entry_expose(GtkWidget *widget, GdkEventExpose *event); +static gint sexy_spell_entry_button_press(GtkWidget *widget, GdkEventButton *event); + +/* GtkEditable handlers */ +static void sexy_spell_entry_changed(GtkEditable *editable, gpointer data); + +/* Other handlers */ +static gboolean sexy_spell_entry_popup_menu(GtkWidget *widget, SexySpellEntry *entry); + +/* Internal utility functions */ +static gint gtk_entry_find_position (GtkEntry *entry, + gint x); +static gboolean word_misspelled (SexySpellEntry *entry, + int start, + int end); +static gboolean default_word_check (SexySpellEntry *entry, + const gchar *word); +static gboolean sexy_spell_entry_activate_language_internal (SexySpellEntry *entry, + const gchar *lang, + GError **error); +static gchar *get_lang_from_dict (struct EnchantDict *dict); +static void sexy_spell_entry_recheck_all (SexySpellEntry *entry); +static void entry_strsplit_utf8 (GtkEntry *entry, + gchar ***set, + gint **starts, + gint **ends); + +static GtkEntryClass *parent_class = NULL; + +G_DEFINE_TYPE_EXTENDED(SexySpellEntry, sexy_spell_entry, GTK_TYPE_ENTRY, 0, G_IMPLEMENT_INTERFACE(GTK_TYPE_EDITABLE, sexy_spell_entry_editable_init)); + +enum +{ + WORD_CHECK, + LAST_SIGNAL +}; +static guint signals[LAST_SIGNAL] = {0}; + +static gboolean +spell_accumulator(GSignalInvocationHint *hint, GValue *return_accu, const GValue *handler_return, gpointer data) +{ + gboolean ret = g_value_get_boolean(handler_return); + /* Handlers return TRUE if the word is misspelled. In this + * case, it means that we want to stop if the word is checked + * as correct */ + g_value_set_boolean (return_accu, ret); + return ret; +} + +static void +initialize_enchant () +{ + GModule *enchant; + gpointer funcptr; + + enchant = g_module_open("libenchant", 0); + if (enchant == NULL) + { + enchant = g_module_open("libenchant.so.1", 0); + if (enchant == NULL) + return; + } + + have_enchant = TRUE; + +#define MODULE_SYMBOL(name, func) \ + g_module_symbol(enchant, (name), &funcptr); \ + (func) = funcptr; + + MODULE_SYMBOL("enchant_broker_init", enchant_broker_init) + MODULE_SYMBOL("enchant_broker_free", enchant_broker_free) + MODULE_SYMBOL("enchant_broker_free_dict", enchant_broker_free_dict) + MODULE_SYMBOL("enchant_broker_list_dicts", enchant_broker_list_dicts) + MODULE_SYMBOL("enchant_broker_request_dict", enchant_broker_request_dict) + + MODULE_SYMBOL("enchant_dict_add_to_personal", enchant_dict_add_to_personal) + MODULE_SYMBOL("enchant_dict_add_to_session", enchant_dict_add_to_session) + MODULE_SYMBOL("enchant_dict_check", enchant_dict_check) + MODULE_SYMBOL("enchant_dict_describe", enchant_dict_describe) + MODULE_SYMBOL("enchant_dict_free_suggestions", + enchant_dict_free_suggestions) + MODULE_SYMBOL("enchant_dict_store_replacement", + enchant_dict_store_replacement) + MODULE_SYMBOL("enchant_dict_suggest", enchant_dict_suggest) + +#undef MODULE_SYMBOL +} + +static void +sexy_spell_entry_class_init(SexySpellEntryClass *klass) +{ + GObjectClass *gobject_class; + GtkObjectClass *object_class; + GtkWidgetClass *widget_class; + GtkEntryClass *entry_class; + + initialize_enchant(); + + parent_class = g_type_class_peek_parent(klass); + + gobject_class = G_OBJECT_CLASS(klass); + object_class = GTK_OBJECT_CLASS(klass); + widget_class = GTK_WIDGET_CLASS(klass); + entry_class = GTK_ENTRY_CLASS(klass); + + if (have_enchant) + klass->word_check = default_word_check; + + gobject_class->finalize = sexy_spell_entry_finalize; + + object_class->destroy = sexy_spell_entry_destroy; + + widget_class->expose_event = sexy_spell_entry_expose; + widget_class->button_press_event = sexy_spell_entry_button_press; + + /** + * SexySpellEntry::word-check: + * @entry: The entry on which the signal is emitted. + * @word: The word to check. + * + * The ::word-check signal is emitted whenever the entry has to check + * a word. This allows the application to mark words as correct even + * if none of the active dictionaries contain it, such as nicknames in + * a chat client. + * + * Returns: %FALSE to indicate that the word should be marked as + * correct. + */ +/* signals[WORD_CHECK] = g_signal_new("word_check", + G_TYPE_FROM_CLASS(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(SexySpellEntryClass, word_check), + (GSignalAccumulator) spell_accumulator, NULL, + sexy_marshal_BOOLEAN__STRING, + G_TYPE_BOOLEAN, + 1, G_TYPE_STRING);*/ +} + +static void +sexy_spell_entry_editable_init (GtkEditableClass *iface) +{ +} + +static gint +gtk_entry_find_position (GtkEntry *entry, gint x) +{ + PangoLayout *layout; + PangoLayoutLine *line; + const gchar *text; + gint cursor_index; + gint index; + gint pos; + gboolean trailing; + + x = x + entry->scroll_offset; + + layout = gtk_entry_get_layout(entry); + text = pango_layout_get_text(layout); + cursor_index = g_utf8_offset_to_pointer(text, entry->current_pos) - text; + + line = pango_layout_get_lines(layout)->data; + pango_layout_line_x_to_index(line, x * PANGO_SCALE, &index, &trailing); + + if (index >= cursor_index && entry->preedit_length) { + if (index >= cursor_index + entry->preedit_length) { + index -= entry->preedit_length; + } else { + index = cursor_index; + trailing = FALSE; + } + } + + pos = g_utf8_pointer_to_offset (text, text + index); + pos += trailing; + + return pos; +} + +static void +insert_underline(SexySpellEntry *entry, guint start, guint end) +{ + PangoAttribute *ucolor = pango_attr_underline_color_new (65535, 0, 0); + PangoAttribute *unline = pango_attr_underline_new (PANGO_UNDERLINE_ERROR); + + ucolor->start_index = start; + unline->start_index = start; + + ucolor->end_index = end; + unline->end_index = end; + + pango_attr_list_insert (entry->priv->attr_list, ucolor); + pango_attr_list_insert (entry->priv->attr_list, unline); +} + +static void +get_word_extents_from_position(SexySpellEntry *entry, gint *start, gint *end, guint position) +{ + const gchar *text; + gint i, bytes_pos; + + *start = -1; + *end = -1; + + if (entry->priv->words == NULL) + return; + + text = gtk_entry_get_text(GTK_ENTRY(entry)); + bytes_pos = (gint) (g_utf8_offset_to_pointer(text, position) - text); + + for (i = 0; entry->priv->words[i]; i++) { + if (bytes_pos >= entry->priv->word_starts[i] && + bytes_pos <= entry->priv->word_ends[i]) { + *start = entry->priv->word_starts[i]; + *end = entry->priv->word_ends[i]; + return; + } + } +} + +static void +add_to_dictionary(GtkWidget *menuitem, SexySpellEntry *entry) +{ + char *word; + gint start, end; + struct EnchantDict *dict; + + if (!have_enchant) + return; + + get_word_extents_from_position(entry, &start, &end, entry->priv->mark_character); + word = gtk_editable_get_chars(GTK_EDITABLE(entry), start, end); + + dict = (struct EnchantDict *) g_object_get_data(G_OBJECT(menuitem), "enchant-dict"); + if (dict) + enchant_dict_add_to_personal(dict, word, -1); + + g_free(word); + + if (entry->priv->words) { + g_strfreev(entry->priv->words); + g_free(entry->priv->word_starts); + g_free(entry->priv->word_ends); + } + entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends); + sexy_spell_entry_recheck_all(entry); +} + +static void +ignore_all(GtkWidget *menuitem, SexySpellEntry *entry) +{ + char *word; + gint start, end; + GSList *li; + + if (!have_enchant) + return; + + get_word_extents_from_position(entry, &start, &end, entry->priv->mark_character); + word = gtk_editable_get_chars(GTK_EDITABLE(entry), start, end); + + for (li = entry->priv->dict_list; li; li = g_slist_next (li)) { + struct EnchantDict *dict = (struct EnchantDict *) li->data; + enchant_dict_add_to_session(dict, word, -1); + } + + g_free(word); + + if (entry->priv->words) { + g_strfreev(entry->priv->words); + g_free(entry->priv->word_starts); + g_free(entry->priv->word_ends); + } + entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends); + sexy_spell_entry_recheck_all(entry); +} + +static void +replace_word(GtkWidget *menuitem, SexySpellEntry *entry) +{ + char *oldword; + const char *newword; + gint start, end; + gint cursor; + struct EnchantDict *dict; + + if (!have_enchant) + return; + + get_word_extents_from_position(entry, &start, &end, entry->priv->mark_character); + oldword = gtk_editable_get_chars(GTK_EDITABLE(entry), start, end); + newword = gtk_label_get_text(GTK_LABEL(GTK_BIN(menuitem)->child)); + + cursor = gtk_editable_get_position(GTK_EDITABLE(entry)); + /* is the cursor at the end? If so, restore it there */ + if (g_utf8_strlen(gtk_entry_get_text(GTK_ENTRY(entry)), -1) == cursor) + cursor = -1; + else if(cursor > start && cursor <= end) + cursor = start; + + gtk_editable_delete_text(GTK_EDITABLE(entry), start, end); + gtk_editable_set_position(GTK_EDITABLE(entry), start); + gtk_editable_insert_text(GTK_EDITABLE(entry), newword, strlen(newword), + &start); + gtk_editable_set_position(GTK_EDITABLE(entry), cursor); + + dict = (struct EnchantDict *) g_object_get_data(G_OBJECT(menuitem), "enchant-dict"); + + if (dict) + enchant_dict_store_replacement(dict, + oldword, -1, + newword, -1); + + g_free(oldword); +} + +static void +build_suggestion_menu(SexySpellEntry *entry, GtkWidget *menu, struct EnchantDict *dict, const gchar *word) +{ + GtkWidget *mi; + gchar **suggestions; + size_t n_suggestions, i; + + if (!have_enchant) + return; + + suggestions = enchant_dict_suggest(dict, word, -1, &n_suggestions); + + if (suggestions == NULL || n_suggestions == 0) { + /* no suggestions. put something in the menu anyway... */ + GtkWidget *label = gtk_label_new(""); + gtk_label_set_markup(GTK_LABEL(label), _("<i>(no suggestions)</i>")); + + mi = gtk_separator_menu_item_new(); + gtk_container_add(GTK_CONTAINER(mi), label); + gtk_widget_show_all(mi); + gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi); + } else { + /* build a set of menus with suggestions */ + for (i = 0; i < n_suggestions; i++) { + if ((i != 0) && (i % 10 == 0)) { + mi = gtk_separator_menu_item_new(); + gtk_widget_show(mi); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); + + mi = gtk_menu_item_new_with_label(_("More...")); + gtk_widget_show(mi); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); + + menu = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), menu); + } + + mi = gtk_menu_item_new_with_label(suggestions[i]); + g_object_set_data(G_OBJECT(mi), "enchant-dict", dict); + g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(replace_word), entry); + gtk_widget_show(mi); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); + } + } + + enchant_dict_free_suggestions(dict, suggestions); +} + +static GtkWidget * +build_spelling_menu(SexySpellEntry *entry, const gchar *word) +{ + struct EnchantDict *dict; + GtkWidget *topmenu, *mi; + gchar *label; + + if (!have_enchant) + return NULL; + + topmenu = gtk_menu_new(); + + if (entry->priv->dict_list == NULL) + return topmenu; + +#if 1 + dict = (struct EnchantDict *) entry->priv->dict_list->data; + build_suggestion_menu(entry, topmenu, dict, word); +#else + /* Suggestions */ + if (g_slist_length(entry->priv->dict_list) == 1) { + dict = (struct EnchantDict *) entry->priv->dict_list->data; + build_suggestion_menu(entry, topmenu, dict, word); + } else { + GSList *li; + GtkWidget *menu; + gchar *lang, *lang_name; + + for (li = entry->priv->dict_list; li; li = g_slist_next (li)) { + dict = (struct EnchantDict *) li->data; + lang = get_lang_from_dict(dict); + lang_name = gtkspell_iso_codes_lookup_name_for_code(lang); + if (lang_name) { + mi = gtk_menu_item_new_with_label(lang_name); + g_free(lang_name); + } else { + mi = gtk_menu_item_new_with_label(lang); + } + g_free(lang); + + gtk_widget_show(mi); + gtk_menu_shell_append(GTK_MENU_SHELL(topmenu), mi); + menu = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), menu); + build_suggestion_menu(entry, menu, dict, word); + } + } +#endif + + /* Separator */ + mi = gtk_separator_menu_item_new (); + gtk_widget_show(mi); + gtk_menu_shell_append(GTK_MENU_SHELL(topmenu), mi); + + /* + Add to Dictionary */ + label = g_strdup_printf(_("Add \"%s\" to Dictionary"), word); + mi = gtk_image_menu_item_new_with_label(label); + g_free(label); + + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU)); + +#if 1 + dict = (struct EnchantDict *) entry->priv->dict_list->data; + g_object_set_data(G_OBJECT(mi), "enchant-dict", dict); + g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(add_to_dictionary), entry); +#else + if (g_slist_length(entry->priv->dict_list) == 1) { + dict = (struct EnchantDict *) entry->priv->dict_list->data; + g_object_set_data(G_OBJECT(mi), "enchant-dict", dict); + g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(add_to_dictionary), entry); + } else { + GSList *li; + GtkWidget *menu, *submi; + gchar *lang, *lang_name; + + menu = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), menu); + + for (li = entry->priv->dict_list; li; li = g_slist_next(li)) { + dict = (struct EnchantDict *)li->data; + lang = get_lang_from_dict(dict); + lang_name = gtkspell_iso_codes_lookup_name_for_code(lang); + if (lang_name) { + submi = gtk_menu_item_new_with_label(lang_name); + g_free(lang_name); + } else { + submi = gtk_menu_item_new_with_label(lang); + } + g_free(lang); + g_object_set_data(G_OBJECT(submi), "enchant-dict", dict); + + g_signal_connect(G_OBJECT(submi), "activate", G_CALLBACK(add_to_dictionary), entry); + + gtk_widget_show(submi); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), submi); + } + } +#endif + + gtk_widget_show_all(mi); + gtk_menu_shell_append(GTK_MENU_SHELL(topmenu), mi); + + /* - Ignore All */ + mi = gtk_image_menu_item_new_with_label(_("Ignore All")); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), gtk_image_new_from_stock(GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU)); + g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(ignore_all), entry); + gtk_widget_show_all(mi); + gtk_menu_shell_append(GTK_MENU_SHELL(topmenu), mi); + + return topmenu; +} + +static void +sexy_spell_entry_populate_popup(SexySpellEntry *entry, GtkMenu *menu, gpointer data) +{ + GtkWidget *icon, *mi; + gint start, end; + gchar *word; + + if ((have_enchant == FALSE) || (entry->priv->checked == FALSE)) + return; + + if (g_slist_length(entry->priv->dict_list) == 0) + return; + + get_word_extents_from_position(entry, &start, &end, entry->priv->mark_character); + if (start == end) + return; + if (!word_misspelled(entry, start, end)) + return; + + /* separator */ + mi = gtk_separator_menu_item_new(); + gtk_widget_show(mi); + gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi); + + /* Above the separator, show the suggestions menu */ + icon = gtk_image_new_from_stock(GTK_STOCK_SPELL_CHECK, GTK_ICON_SIZE_MENU); + mi = gtk_image_menu_item_new_with_label(_("Spelling Suggestions")); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), icon); + + word = gtk_editable_get_chars(GTK_EDITABLE(entry), start, end); + g_assert(word != NULL); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), build_spelling_menu(entry, word)); + g_free(word); + + gtk_widget_show_all(mi); + gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi); +} + +static void +sexy_spell_entry_init(SexySpellEntry *entry) +{ + entry->priv = g_new0(SexySpellEntryPriv, 1); + + entry->priv->dict_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + + if (have_enchant) + sexy_spell_entry_activate_default_languages(entry); + + entry->priv->attr_list = pango_attr_list_new(); + + entry->priv->checked = TRUE; + + g_signal_connect(G_OBJECT(entry), "popup-menu", G_CALLBACK(sexy_spell_entry_popup_menu), entry); + g_signal_connect(G_OBJECT(entry), "populate-popup", G_CALLBACK(sexy_spell_entry_populate_popup), NULL); + g_signal_connect(G_OBJECT(entry), "changed", G_CALLBACK(sexy_spell_entry_changed), NULL); +} + +static void +sexy_spell_entry_finalize(GObject *obj) +{ + SexySpellEntry *entry; + + g_return_if_fail(obj != NULL); + g_return_if_fail(SEXY_IS_SPELL_ENTRY(obj)); + + entry = SEXY_SPELL_ENTRY(obj); + + if (entry->priv->attr_list) + pango_attr_list_unref(entry->priv->attr_list); + if (entry->priv->dict_hash) + g_hash_table_destroy(entry->priv->dict_hash); + if (entry->priv->words) + g_strfreev(entry->priv->words); + if (entry->priv->word_starts) + g_free(entry->priv->word_starts); + if (entry->priv->word_ends) + g_free(entry->priv->word_ends); + + if (have_enchant) { + if (entry->priv->broker) { + GSList *li; + for (li = entry->priv->dict_list; li; li = g_slist_next(li)) { + struct EnchantDict *dict = (struct EnchantDict*) li->data; + enchant_broker_free_dict (entry->priv->broker, dict); + } + g_slist_free (entry->priv->dict_list); + + enchant_broker_free(entry->priv->broker); + } + } + + g_free(entry->priv); + + if (G_OBJECT_CLASS(parent_class)->finalize) + G_OBJECT_CLASS(parent_class)->finalize(obj); +} + +static void +sexy_spell_entry_destroy(GtkObject *obj) +{ + SexySpellEntry *entry; + + entry = SEXY_SPELL_ENTRY(obj); + + if (GTK_OBJECT_CLASS(parent_class)->destroy) + GTK_OBJECT_CLASS(parent_class)->destroy(obj); +} + +/** + * sexy_spell_entry_new + * + * Creates a new SexySpellEntry widget. + * + * Returns: a new #SexySpellEntry. + */ +GtkWidget * +sexy_spell_entry_new(void) +{ + return GTK_WIDGET(g_object_new(SEXY_TYPE_SPELL_ENTRY, NULL)); +} + +GQuark +sexy_spell_error_quark(void) +{ + static GQuark q = 0; + if (q == 0) + q = g_quark_from_static_string("sexy-spell-error-quark"); + return q; +} + +static gboolean +default_word_check(SexySpellEntry *entry, const gchar *word) +{ + gboolean result = TRUE; + GSList *li; + + if (!have_enchant) + return result; + + if (g_unichar_isalpha(*word) == FALSE) { + /* We only want to check words */ + return FALSE; + } + for (li = entry->priv->dict_list; li; li = g_slist_next (li)) { + struct EnchantDict *dict = (struct EnchantDict *) li->data; + if (enchant_dict_check(dict, word, strlen(word)) == 0) { + result = FALSE; + break; + } + } + return result; +} + +static gboolean +word_misspelled(SexySpellEntry *entry, int start, int end) +{ + const gchar *text; + gchar *word; + gboolean ret; + + if (start == end) + return FALSE; + text = gtk_entry_get_text(GTK_ENTRY(entry)); + word = g_new0(gchar, end - start + 2); + + g_strlcpy(word, text + start, end - start + 1); + +#if 0 + g_signal_emit(entry, signals[WORD_CHECK], 0, word, &ret); +#else + ret = default_word_check (entry, word); +#endif + + g_free(word); + return ret; +} + +static void +check_word(SexySpellEntry *entry, int start, int end) +{ + PangoAttrIterator *it; + + /* Check to see if we've got any attributes at this position. + * If so, free them, since we'll readd it if the word is misspelled */ + it = pango_attr_list_get_iterator(entry->priv->attr_list); + if (it == NULL) + return; + do { + gint s, e; + pango_attr_iterator_range(it, &s, &e); + if (s == start) { + GSList *attrs = pango_attr_iterator_get_attrs(it); + g_slist_foreach(attrs, (GFunc) pango_attribute_destroy, NULL); + g_slist_free(attrs); + } + } while (pango_attr_iterator_next(it)); + pango_attr_iterator_destroy(it); + + if (word_misspelled(entry, start, end)) + insert_underline(entry, start, end); +} + +static void +sexy_spell_entry_recheck_all(SexySpellEntry *entry) +{ + GdkRectangle rect; + GtkWidget *widget = GTK_WIDGET(entry); + PangoLayout *layout; + int length, i; + + if ((have_enchant == FALSE) || (entry->priv->checked == FALSE)) + return; + + if (g_slist_length(entry->priv->dict_list) == 0) + return; + + /* Remove all existing pango attributes. These will get readded as we check */ + pango_attr_list_unref(entry->priv->attr_list); + entry->priv->attr_list = pango_attr_list_new(); + + /* Loop through words */ + for (i = 0; entry->priv->words[i]; i++) { + length = strlen(entry->priv->words[i]); + if (length == 0) + continue; + check_word(entry, entry->priv->word_starts[i], entry->priv->word_ends[i]); + } + + layout = gtk_entry_get_layout(GTK_ENTRY(entry)); + pango_layout_set_attributes(layout, entry->priv->attr_list); + + if (GTK_WIDGET_REALIZED(GTK_WIDGET(entry))) { + rect.x = 0; rect.y = 0; + rect.width = widget->allocation.width; + rect.height = widget->allocation.height; + gdk_window_invalidate_rect(widget->window, &rect, TRUE); + } +} + +static gint +sexy_spell_entry_expose(GtkWidget *widget, GdkEventExpose *event) +{ + SexySpellEntry *entry = SEXY_SPELL_ENTRY(widget); + GtkEntry *gtk_entry = GTK_ENTRY(widget); + PangoLayout *layout; + + if (entry->priv->checked) { + layout = gtk_entry_get_layout(gtk_entry); + pango_layout_set_attributes(layout, entry->priv->attr_list); + } + + return GTK_WIDGET_CLASS(parent_class)->expose_event (widget, event); +} + +static gint +sexy_spell_entry_button_press(GtkWidget *widget, GdkEventButton *event) +{ + SexySpellEntry *entry = SEXY_SPELL_ENTRY(widget); + GtkEntry *gtk_entry = GTK_ENTRY(widget); + gint pos; + + pos = gtk_entry_find_position(gtk_entry, event->x); + entry->priv->mark_character = pos; + + return GTK_WIDGET_CLASS(parent_class)->button_press_event (widget, event); +} + +static gboolean +sexy_spell_entry_popup_menu(GtkWidget *widget, SexySpellEntry *entry) +{ + /* Menu popped up from a keybinding (menu key or <shift>+F10). Use + * the cursor position as the mark position */ + entry->priv->mark_character = gtk_editable_get_position (GTK_EDITABLE (entry)); + return FALSE; +} + +static void +entry_strsplit_utf8(GtkEntry *entry, gchar ***set, gint **starts, gint **ends) +{ + PangoLayout *layout; + PangoLogAttr *log_attrs; + const gchar *text; + gint n_attrs, n_strings, i, j; + + layout = gtk_entry_get_layout(GTK_ENTRY(entry)); + text = gtk_entry_get_text(GTK_ENTRY(entry)); + pango_layout_get_log_attrs(layout, &log_attrs, &n_attrs); + + /* Find how many words we have */ + n_strings = 0; + for (i = 0; i < n_attrs; i++) + if (log_attrs[i].is_word_start) + n_strings++; + + *set = g_new0(gchar *, n_strings + 1); + *starts = g_new0(gint, n_strings); + *ends = g_new0(gint, n_strings); + + /* Copy out strings */ + for (i = 0, j = 0; i < n_attrs; i++) { + if (log_attrs[i].is_word_start) { + gint cend, bytes; + gchar *start; + + /* Find the end of this string */ + cend = i; + while (!(log_attrs[cend].is_word_end)) + cend++; + + /* Copy sub-string */ + start = g_utf8_offset_to_pointer(text, i); + bytes = (gint) (g_utf8_offset_to_pointer(text, cend) - start); + (*set)[j] = g_new0(gchar, bytes + 1); + (*starts)[j] = (gint) (start - text); + (*ends)[j] = (gint) (start - text + bytes); + g_utf8_strncpy((*set)[j], start, cend - i); + + /* Move on to the next word */ + j++; + } + } + + g_free (log_attrs); +} + +static void +sexy_spell_entry_changed(GtkEditable *editable, gpointer data) +{ + SexySpellEntry *entry = SEXY_SPELL_ENTRY(editable); + if (entry->priv->checked == FALSE) + return; + if (g_slist_length(entry->priv->dict_list) == 0) + return; + + if (entry->priv->words) { + g_strfreev(entry->priv->words); + g_free(entry->priv->word_starts); + g_free(entry->priv->word_ends); + } + entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends); + sexy_spell_entry_recheck_all(entry); +} + +static gboolean +enchant_has_lang(const gchar *lang, GSList *langs) { + GSList *i; + for (i = langs; i; i = g_slist_next(i)) { + if (strcmp(lang, i->data) == 0) { + return TRUE; + } + } + return FALSE; +} + +/** + * sexy_spell_entry_activate_default_languages: + * @entry: A #SexySpellEntry. + * + * Activate spell checking for languages specified in the $LANG + * or $LANGUAGE environment variables. These languages are + * activated by default, so this function need only be called + * if they were previously deactivated. + */ +void +sexy_spell_entry_activate_default_languages(SexySpellEntry *entry) +{ +#if GLIB_CHECK_VERSION (2, 6, 0) + const gchar* const *langs; + int i; + gchar *lastprefix = NULL; + GSList *enchant_langs; + + if (!have_enchant) + return; + + if (!entry->priv->broker) + entry->priv->broker = enchant_broker_init(); + + + langs = g_get_language_names (); + + if (langs == NULL) + return; + + enchant_langs = sexy_spell_entry_get_languages(entry); + + for (i = 0; langs[i]; i++) { + if ((g_strncasecmp(langs[i], "C", 1) != 0) && + (strlen(langs[i]) >= 2) && + enchant_has_lang(langs[i], enchant_langs)) { + if ((lastprefix == NULL) || (g_str_has_prefix(langs[i], lastprefix) == FALSE)) + sexy_spell_entry_activate_language_internal(entry, langs[i], NULL); + if (lastprefix != NULL) + g_free(lastprefix); + lastprefix = g_strndup(langs[i], 2); + } + } + if (lastprefix != NULL) + g_free(lastprefix); + + g_slist_foreach(enchant_langs, (GFunc) g_free, NULL); + g_slist_free(enchant_langs); + + /* If we don't have any languages activated, use "en" */ + if (entry->priv->dict_list == NULL) + sexy_spell_entry_activate_language_internal(entry, "en", NULL); +#else + gchar *lang; + + if (!have_enchant) + return; + + lang = (gchar *) g_getenv("LANG"); + + if (lang != NULL) { + if (g_strncasecmp(lang, "C", 1) == 0) + lang = NULL; + else if (lang[0] == '\0') + lang = NULL; + } + + if (lang == NULL) + lang = "en"; + + sexy_spell_entry_activate_language_internal(entry, lang, NULL); +#endif +} + +static void +get_lang_from_dict_cb(const char * const lang_tag, + const char * const provider_name, + const char * const provider_desc, + const char * const provider_file, + void * user_data) { + gchar **lang = (gchar **)user_data; + *lang = g_strdup(lang_tag); +} + +static gchar * +get_lang_from_dict(struct EnchantDict *dict) +{ + gchar *lang; + + if (!have_enchant) + return NULL; + + enchant_dict_describe(dict, get_lang_from_dict_cb, &lang); + return lang; +} + +static gboolean +sexy_spell_entry_activate_language_internal(SexySpellEntry *entry, const gchar *lang, GError **error) +{ + struct EnchantDict *dict; + + if (!have_enchant) + return FALSE; + + if (!entry->priv->broker) + entry->priv->broker = enchant_broker_init(); + + if (g_hash_table_lookup(entry->priv->dict_hash, lang)) + return TRUE; + + dict = enchant_broker_request_dict(entry->priv->broker, lang); + + if (!dict) { + g_set_error(error, SEXY_SPELL_ERROR, SEXY_SPELL_ERROR_BACKEND, _("enchant error for language: %s"), lang); + return FALSE; + } + + entry->priv->dict_list = g_slist_append(entry->priv->dict_list, (gpointer) dict); + g_hash_table_insert(entry->priv->dict_hash, get_lang_from_dict(dict), (gpointer) dict); + + return TRUE; +} + +static void +dict_describe_cb(const char * const lang_tag, + const char * const provider_name, + const char * const provider_desc, + const char * const provider_file, + void * user_data) +{ + GSList **langs = (GSList **)user_data; + + *langs = g_slist_append(*langs, (gpointer)g_strdup(lang_tag)); +} + +/** + * sexy_spell_entry_get_languages: + * @entry: A #SexySpellEntry. + * + * Retrieve a list of language codes for which dictionaries are available. + * + * Returns: a new #GList object, or %NULL on error. + */ +GSList * +sexy_spell_entry_get_languages(const SexySpellEntry *entry) +{ + GSList *langs = NULL; + + g_return_val_if_fail(entry != NULL, NULL); + g_return_val_if_fail(SEXY_IS_SPELL_ENTRY(entry), NULL); + + if (enchant_broker_list_dicts == NULL) + return NULL; + + if (!entry->priv->broker) + return NULL; + + enchant_broker_list_dicts(entry->priv->broker, dict_describe_cb, &langs); + + return langs; +} + +/** + * sexy_spell_entry_get_language_name: + * @entry: A #SexySpellEntry. + * @lang: The language code to lookup a friendly name for. + * + * Get a friendly name for a given locale. + * + * Returns: The name of the locale. Should be freed with g_free() + */ +gchar * +sexy_spell_entry_get_language_name(const SexySpellEntry *entry, + const gchar *lang) +{ + /*if (have_enchant) + return gtkspell_iso_codes_lookup_name_for_code(lang);*/ + return NULL; +} + +/** + * sexy_spell_entry_language_is_active: + * @entry: A #SexySpellEntry. + * @lang: The language to use, in a form enchant understands. + * + * Determine if a given language is currently active. + * + * Returns: TRUE if the language is active. + */ +gboolean +sexy_spell_entry_language_is_active(const SexySpellEntry *entry, + const gchar *lang) +{ + return (g_hash_table_lookup(entry->priv->dict_hash, lang) != NULL); +} + +/** + * sexy_spell_entry_activate_language: + * @entry: A #SexySpellEntry + * @lang: The language to use in a form Enchant understands. Typically either + * a two letter language code or a locale code in the form xx_XX. + * @error: Return location for error. + * + * Activate spell checking for the language specifed. + * + * Returns: FALSE if there was an error. + */ +gboolean +sexy_spell_entry_activate_language(SexySpellEntry *entry, const gchar *lang, GError **error) +{ + gboolean ret; + + g_return_val_if_fail(entry != NULL, FALSE); + g_return_val_if_fail(SEXY_IS_SPELL_ENTRY(entry), FALSE); + g_return_val_if_fail(lang != NULL && lang != '\0', FALSE); + + if (!have_enchant) + return FALSE; + + if (error) + g_return_val_if_fail(*error == NULL, FALSE); + + ret = sexy_spell_entry_activate_language_internal(entry, lang, error); + + if (ret) { + if (entry->priv->words) { + g_strfreev(entry->priv->words); + g_free(entry->priv->word_starts); + g_free(entry->priv->word_ends); + } + entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends); + sexy_spell_entry_recheck_all(entry); + } + + return ret; +} + +/** + * sexy_spell_entry_deactivate_language: + * @entry: A #SexySpellEntry. + * @lang: The language in a form Enchant understands. Typically either + * a two letter language code or a locale code in the form xx_XX. + * + * Deactivate spell checking for the language specifed. + */ +void +sexy_spell_entry_deactivate_language(SexySpellEntry *entry, const gchar *lang) +{ + g_return_if_fail(entry != NULL); + g_return_if_fail(SEXY_IS_SPELL_ENTRY(entry)); + + if (!have_enchant) + return; + + if (!entry->priv->dict_list) + return; + + if (lang) { + struct EnchantDict *dict; + + dict = g_hash_table_lookup(entry->priv->dict_hash, lang); + if (!dict) + return; + enchant_broker_free_dict(entry->priv->broker, dict); + entry->priv->dict_list = g_slist_remove(entry->priv->dict_list, dict); + g_hash_table_remove (entry->priv->dict_hash, lang); + } else { + /* deactivate all */ + GSList *li; + struct EnchantDict *dict; + + for (li = entry->priv->dict_list; li; li = g_slist_next(li)) { + dict = (struct EnchantDict *)li->data; + enchant_broker_free_dict(entry->priv->broker, dict); + } + + g_slist_free (entry->priv->dict_list); + g_hash_table_destroy (entry->priv->dict_hash); + entry->priv->dict_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + entry->priv->dict_list = NULL; + } + + if (entry->priv->words) { + g_strfreev(entry->priv->words); + g_free(entry->priv->word_starts); + g_free(entry->priv->word_ends); + } + entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends); + sexy_spell_entry_recheck_all(entry); +} + +/** + * sexy_spell_entry_set_active_languages: + * @entry: A #SexySpellEntry + * @langs: A list of language codes to activate, in a form Enchant understands. + * Typically either a two letter language code or a locale code in the + * form xx_XX. + * @error: Return location for error. + * + * Activate spell checking for only the languages specified. + * + * Returns: FALSE if there was an error. + */ +gboolean +sexy_spell_entry_set_active_languages(SexySpellEntry *entry, GSList *langs, GError **error) +{ + GSList *li; + + g_return_val_if_fail(entry != NULL, FALSE); + g_return_val_if_fail(SEXY_IS_SPELL_ENTRY(entry), FALSE); + g_return_val_if_fail(langs != NULL, FALSE); + + if (!have_enchant) + return FALSE; + + /* deactivate all languages first */ + sexy_spell_entry_deactivate_language(entry, NULL); + + for (li = langs; li; li = g_slist_next(li)) { + if (sexy_spell_entry_activate_language_internal(entry, + (const gchar *) li->data, error) == FALSE) + return FALSE; + } + if (entry->priv->words) { + g_strfreev(entry->priv->words); + g_free(entry->priv->word_starts); + g_free(entry->priv->word_ends); + } + entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends); + sexy_spell_entry_recheck_all(entry); + return TRUE; +} + +/** + * sexy_spell_entry_get_active_languages: + * @entry: A #SexySpellEntry + * + * Retrieve a list of the currently active languages. + * + * Returns: A GSList of char* values with language codes (en, fr, etc). Both + * the data and the list must be freed by the user. + */ +GSList * +sexy_spell_entry_get_active_languages(SexySpellEntry *entry) +{ + GSList *ret = NULL, *li; + struct EnchantDict *dict; + gchar *lang; + + g_return_val_if_fail(entry != NULL, NULL); + g_return_val_if_fail(SEXY_IS_SPELL_ENTRY(entry), NULL); + + if (!have_enchant) + return NULL; + + for (li = entry->priv->dict_list; li; li = g_slist_next(li)) { + dict = (struct EnchantDict *) li->data; + lang = get_lang_from_dict(dict); + ret = g_slist_append(ret, lang); + } + return ret; +} + +/** + * sexy_spell_entry_is_checked: + * @entry: A #SexySpellEntry. + * + * Queries a #SexySpellEntry and returns whether the entry has spell-checking enabled. + * + * Returns: TRUE if the entry has spell-checking enabled. + */ +gboolean +sexy_spell_entry_is_checked(SexySpellEntry *entry) +{ + return entry->priv->checked; +} + +/** + * sexy_spell_entry_set_checked: + * @entry: A #SexySpellEntry. + * @checked: Whether to enable spell-checking + * + * Sets whether the entry has spell-checking enabled. + */ +void +sexy_spell_entry_set_checked(SexySpellEntry *entry, gboolean checked) +{ + GtkWidget *widget; + + if (entry->priv->checked == checked) + return; + + entry->priv->checked = checked; + widget = GTK_WIDGET(entry); + + if (checked == FALSE && GTK_WIDGET_REALIZED(widget)) { + PangoLayout *layout; + GdkRectangle rect; + + pango_attr_list_unref(entry->priv->attr_list); + entry->priv->attr_list = pango_attr_list_new(); + + layout = gtk_entry_get_layout(GTK_ENTRY(entry)); + pango_layout_set_attributes(layout, entry->priv->attr_list); + + rect.x = 0; rect.y = 0; + rect.width = widget->allocation.width; + rect.height = widget->allocation.height; + gdk_window_invalidate_rect(widget->window, &rect, TRUE); + } else { + if (entry->priv->words) { + g_strfreev(entry->priv->words); + g_free(entry->priv->word_starts); + g_free(entry->priv->word_ends); + } + entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends); + sexy_spell_entry_recheck_all(entry); + } +} diff --git a/src/fe-gtk/sexy-spell-entry.h b/src/fe-gtk/sexy-spell-entry.h new file mode 100644 index 00000000..61d1b795 --- /dev/null +++ b/src/fe-gtk/sexy-spell-entry.h @@ -0,0 +1,87 @@ +/* + * @file libsexy/sexy-spell-entry.h Entry widget + * + * @Copyright (C) 2004-2006 Christian Hammond. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifndef _SEXY_SPELL_ENTRY_H_ +#define _SEXY_SPELL_ENTRY_H_ + +typedef struct _SexySpellEntry SexySpellEntry; +typedef struct _SexySpellEntryClass SexySpellEntryClass; +typedef struct _SexySpellEntryPriv SexySpellEntryPriv; + +#include <gtk/gtkentry.h> + +#define SEXY_TYPE_SPELL_ENTRY (sexy_spell_entry_get_type()) +#define SEXY_SPELL_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SEXY_TYPE_SPELL_ENTRY, SexySpellEntry)) +#define SEXY_SPELL_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SEXY_TYPE_SPELL_ENTRY, SexySpellEntryClass)) +#define SEXY_IS_SPELL_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SEXY_TYPE_SPELL_ENTRY)) +#define SEXY_IS_SPELL_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SEXY_TYPE_SPELL_ENTRY)) +#define SEXY_SPELL_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SEXY_TYPE_SPELL_ENTRY, SexySpellEntryClass)) + +#define SEXY_SPELL_ERROR (sexy_spell_error_quark()) + +typedef enum { + SEXY_SPELL_ERROR_BACKEND, +} SexySpellError; + +struct _SexySpellEntry +{ + GtkEntry parent_object; + + SexySpellEntryPriv *priv; + + void (*gtk_reserved1)(void); + void (*gtk_reserved2)(void); + void (*gtk_reserved3)(void); + void (*gtk_reserved4)(void); +}; + +struct _SexySpellEntryClass +{ + GtkEntryClass parent_class; + + /* Signals */ + gboolean (*word_check)(SexySpellEntry *entry, const gchar *word); + + void (*gtk_reserved1)(void); + void (*gtk_reserved2)(void); + void (*gtk_reserved3)(void); + void (*gtk_reserved4)(void); +}; + +G_BEGIN_DECLS + +GType sexy_spell_entry_get_type(void); +GtkWidget *sexy_spell_entry_new(void); +GQuark sexy_spell_error_quark(void); + +GSList *sexy_spell_entry_get_languages(const SexySpellEntry *entry); +gchar *sexy_spell_entry_get_language_name(const SexySpellEntry *entry, const gchar *lang); +gboolean sexy_spell_entry_language_is_active(const SexySpellEntry *entry, const gchar *lang); +gboolean sexy_spell_entry_activate_language(SexySpellEntry *entry, const gchar *lang, GError **error); +void sexy_spell_entry_deactivate_language(SexySpellEntry *entry, const gchar *lang); +gboolean sexy_spell_entry_set_active_languages(SexySpellEntry *entry, GSList *langs, GError **error); +GSList *sexy_spell_entry_get_active_languages(SexySpellEntry *entry); +gboolean sexy_spell_entry_is_checked(SexySpellEntry *entry); +void sexy_spell_entry_set_checked(SexySpellEntry *entry, gboolean checked); +void sexy_spell_entry_activate_default_languages(SexySpellEntry *entry); + +G_END_DECLS + +#endif diff --git a/src/fe-gtk/textgui.c b/src/fe-gtk/textgui.c new file mode 100644 index 00000000..604da44b --- /dev/null +++ b/src/fe-gtk/textgui.c @@ -0,0 +1,456 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "fe-gtk.h" + +#include <gtk/gtkbutton.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkhbbox.h> +#include <gtk/gtkliststore.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkvbox.h> +#include <gtk/gtkvpaned.h> +#include <gtk/gtkvscrollbar.h> + +#include "../common/xchat.h" +#include "../common/xchatc.h" +#include "../common/cfgfiles.h" +#include "../common/outbound.h" +#include "../common/fe.h" +#include "../common/text.h" +#include "gtkutil.h" +#include "xtext.h" +#include "maingui.h" +#include "palette.h" +#include "textgui.h" + +extern struct text_event te[]; +extern char *pntevts_text[]; +extern char *pntevts[]; + +static GtkWidget *pevent_dialog = NULL, *pevent_dialog_twid, + *pevent_dialog_entry, + *pevent_dialog_list, *pevent_dialog_hlist; + +enum +{ + COL_EVENT_NAME, + COL_EVENT_TEXT, + COL_ROW, + N_COLUMNS +}; + + +/* this is only used in xtext.c for indented timestamping */ +int +xtext_get_stamp_str (time_t tim, char **ret) +{ + return get_stamp_str (prefs.stamp_format, tim, ret); +} + +static void +PrintTextLine (xtext_buffer *xtbuf, unsigned char *text, int len, int indent, time_t timet) +{ + unsigned char *tab, *new_text; + int leftlen; + + if (len == 0) + len = 1; + + if (!indent) + { + if (prefs.timestamp) + { + int stamp_size; + char *stamp; + + if (timet == 0) + timet = time (0); + + stamp_size = get_stamp_str (prefs.stamp_format, timet, &stamp); + new_text = malloc (len + stamp_size + 1); + memcpy (new_text, stamp, stamp_size); + g_free (stamp); + memcpy (new_text + stamp_size, text, len); + gtk_xtext_append (xtbuf, new_text, len + stamp_size); + free (new_text); + } else + gtk_xtext_append (xtbuf, text, len); + return; + } + + tab = strchr (text, '\t'); + if (tab && tab < (text + len)) + { + leftlen = tab - text; + gtk_xtext_append_indent (xtbuf, + text, leftlen, tab + 1, len - (leftlen + 1), timet); + } else + gtk_xtext_append_indent (xtbuf, 0, 0, text, len, timet); +} + +void +PrintTextRaw (void *xtbuf, unsigned char *text, int indent, time_t stamp) +{ + char *last_text = text; + int len = 0; + int beep_done = FALSE; + + /* split the text into separate lines */ + while (1) + { + switch (*text) + { + case 0: + PrintTextLine (xtbuf, last_text, len, indent, stamp); + return; + case '\n': + PrintTextLine (xtbuf, last_text, len, indent, stamp); + text++; + if (*text == 0) + return; + last_text = text; + len = 0; + break; + case ATTR_BEEP: + *text = ' '; + if (!beep_done) /* beeps may be slow, so only do 1 per line */ + { + beep_done = TRUE; + if (!prefs.filterbeep) + gdk_beep (); + } + default: + text++; + len++; + } + } +} + +static void +pevent_dialog_close (GtkWidget *wid, gpointer arg) +{ + pevent_dialog = NULL; + pevent_save (NULL); +} + +static void +pevent_dialog_update (GtkWidget * wid, GtkWidget * twid) +{ + int len, m; + const char *text; + char *out; + int sig; + GtkTreeIter iter; + GtkListStore *store; + + if (!gtkutil_treeview_get_selected (GTK_TREE_VIEW (pevent_dialog_list), + &iter, COL_ROW, &sig, -1)) + return; + + text = gtk_entry_get_text (GTK_ENTRY (wid)); + len = strlen (text); + + if (pevt_build_string (text, &out, &m) != 0) + { + fe_message (_("There was an error parsing the string"), FE_MSG_ERROR); + return; + } + if (m > (te[sig].num_args & 0x7f)) + { + free (out); + out = malloc (4096); + snprintf (out, 4096, + _("This signal is only passed %d args, $%d is invalid"), + te[sig].num_args & 0x7f, m); + fe_message (out, FE_MSG_WARN); + free (out); + return; + } + + store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (pevent_dialog_list)); + gtk_list_store_set (store, &iter, COL_EVENT_TEXT, text, -1); + + if (pntevts_text[sig]) + free (pntevts_text[sig]); + if (pntevts[sig]) + free (pntevts[sig]); + + pntevts_text[sig] = malloc (len + 1); + memcpy (pntevts_text[sig], text, len + 1); + pntevts[sig] = out; + + out = malloc (len + 2); + memcpy (out, text, len + 1); + out[len] = '\n'; + out[len + 1] = 0; + check_special_chars (out, TRUE); + + PrintTextRaw (GTK_XTEXT (twid)->buffer, out, 0, 0); + free (out); + + /* save this when we exit */ + prefs.save_pevents = 1; +} + +static void +pevent_dialog_hfill (GtkWidget * list, int e) +{ + int i = 0; + char *text; + GtkTreeIter iter; + GtkListStore *store; + + store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (pevent_dialog_hlist)); + gtk_list_store_clear (store); + while (i < (te[e].num_args & 0x7f)) + { + text = _(te[e].help[i]); + i++; + if (text[0] == '\001') + text++; + gtk_list_store_insert_with_values (store, &iter, -1, + 0, i, + 1, text, -1); + } +} + +static void +pevent_dialog_unselect (void) +{ + gtk_entry_set_text (GTK_ENTRY (pevent_dialog_entry), ""); + gtk_list_store_clear ((GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (pevent_dialog_hlist))); +} + +static void +pevent_dialog_select (GtkTreeSelection *sel, gpointer store) +{ + char *text; + int sig; + GtkTreeIter iter; + + if (!gtkutil_treeview_get_selected (GTK_TREE_VIEW (pevent_dialog_list), + &iter, COL_ROW, &sig, -1)) + { + pevent_dialog_unselect (); + } + else + { + gtk_tree_model_get (store, &iter, COL_EVENT_TEXT, &text, -1); + gtk_entry_set_text (GTK_ENTRY (pevent_dialog_entry), text); + g_free (text); + pevent_dialog_hfill (pevent_dialog_hlist, sig); + } +} + +static void +pevent_dialog_fill (GtkWidget * list) +{ + int i; + GtkListStore *store; + GtkTreeIter iter; + + store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (list)); + gtk_list_store_clear (store); + + i = NUM_XP; + do + { + i--; + gtk_list_store_insert_with_values (store, &iter, 0, + COL_EVENT_NAME, te[i].name, + COL_EVENT_TEXT, pntevts_text[i], + COL_ROW, i, -1); + } + while (i != 0); +} + +static void +pevent_save_req_cb (void *arg1, char *file) +{ + if (file) + pevent_save (file); +} + +static void +pevent_save_cb (GtkWidget * wid, void *data) +{ + if (data) + { + gtkutil_file_req (_("Print Texts File"), pevent_save_req_cb, NULL, + NULL, FRF_WRITE); + return; + } + pevent_save (NULL); +} + +static void +pevent_load_req_cb (void *arg1, char *file) +{ + if (file) + { + pevent_load (file); + pevent_make_pntevts (); + pevent_dialog_fill (pevent_dialog_list); + pevent_dialog_unselect (); + prefs.save_pevents = 1; + } +} + +static void +pevent_load_cb (GtkWidget * wid, void *data) +{ + gtkutil_file_req (_("Print Texts File"), pevent_load_req_cb, NULL, NULL, 0); +} + +static void +pevent_ok_cb (GtkWidget * wid, void *data) +{ + gtk_widget_destroy (pevent_dialog); +} + +static void +pevent_test_cb (GtkWidget * wid, GtkWidget * twid) +{ + int len, n; + char *out, *text; + + for (n = 0; n < NUM_XP; n++) + { + text = _(pntevts_text[n]); + len = strlen (text); + + out = malloc (len + 2); + memcpy (out, text, len + 1); + out[len] = '\n'; + out[len + 1] = 0; + check_special_chars (out, TRUE); + + PrintTextRaw (GTK_XTEXT (twid)->buffer, out, 0, 0); + free (out); + } +} + +void +pevent_dialog_show () +{ + GtkWidget *vbox, *hbox, *tbox, *wid, *bh, *th; + GtkListStore *store, *hstore; + GtkTreeSelection *sel; + + if (pevent_dialog) + { + mg_bring_tofront (pevent_dialog); + return; + } + + pevent_dialog = + mg_create_generic_tab ("edit events", _("Edit Events"), + TRUE, FALSE, pevent_dialog_close, NULL, + 600, 455, &vbox, 0); + + wid = gtk_vpaned_new (); + th = gtk_vbox_new (0, 2); + bh = gtk_vbox_new (0, 2); + gtk_widget_show (th); + gtk_widget_show (bh); + gtk_paned_pack1 (GTK_PANED (wid), th, 1, 1); + gtk_paned_pack2 (GTK_PANED (wid), bh, 0, 1); + gtk_box_pack_start (GTK_BOX (vbox), wid, 1, 1, 0); + gtk_widget_show (wid); + + store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_INT); + pevent_dialog_list = gtkutil_treeview_new (th, GTK_TREE_MODEL (store), NULL, + COL_EVENT_NAME, _("Event"), + COL_EVENT_TEXT, _("Text"), -1); + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (pevent_dialog_list)); + g_signal_connect (G_OBJECT (sel), "changed", + G_CALLBACK (pevent_dialog_select), store); + + pevent_dialog_twid = gtk_xtext_new (colors, 0); + gtk_xtext_set_tint (GTK_XTEXT (pevent_dialog_twid), prefs.tint_red, prefs.tint_green, prefs.tint_blue); + gtk_xtext_set_background (GTK_XTEXT (pevent_dialog_twid), + channelwin_pix, prefs.transparent); + + pevent_dialog_entry = gtk_entry_new_with_max_length (255); + g_signal_connect (G_OBJECT (pevent_dialog_entry), "activate", + G_CALLBACK (pevent_dialog_update), pevent_dialog_twid); + gtk_box_pack_start (GTK_BOX (bh), pevent_dialog_entry, 0, 0, 0); + gtk_widget_show (pevent_dialog_entry); + + tbox = gtk_hbox_new (0, 0); + gtk_container_add (GTK_CONTAINER (bh), tbox); + gtk_widget_show (tbox); + + gtk_widget_set_usize (pevent_dialog_twid, 150, 20); + gtk_container_add (GTK_CONTAINER (tbox), pevent_dialog_twid); + gtk_xtext_set_font (GTK_XTEXT (pevent_dialog_twid), prefs.font_normal); + + wid = gtk_vscrollbar_new (GTK_XTEXT (pevent_dialog_twid)->adj); + gtk_box_pack_start (GTK_BOX (tbox), wid, FALSE, FALSE, 0); + show_and_unfocus (wid); + + gtk_widget_show (pevent_dialog_twid); + + hstore = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING); + pevent_dialog_hlist = gtkutil_treeview_new (bh, GTK_TREE_MODEL (hstore), + NULL, + 0, _("$ Number"), + 1, _("Description"), -1); + gtk_widget_show (pevent_dialog_hlist); + + pevent_dialog_fill (pevent_dialog_list); + gtk_widget_show (pevent_dialog_list); + + hbox = gtk_hbutton_box_new (); + gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 2); + /*wid = gtk_button_new_with_label (_("Save")); + gtk_box_pack_end (GTK_BOX (hbox), wid, 0, 0, 0); + gtk_signal_connect (GTK_OBJECT (wid), "clicked", + GTK_SIGNAL_FUNC (pevent_save_cb), NULL); + gtk_widget_show (wid);*/ + gtkutil_button (hbox, GTK_STOCK_SAVE_AS, NULL, pevent_save_cb, + (void *) 1, _("Save As...")); + gtkutil_button (hbox, GTK_STOCK_OPEN, NULL, pevent_load_cb, + (void *) 0, _("Load From...")); + wid = gtk_button_new_with_label (_("Test All")); + gtk_box_pack_end (GTK_BOX (hbox), wid, 0, 0, 0); + g_signal_connect (G_OBJECT (wid), "clicked", + G_CALLBACK (pevent_test_cb), pevent_dialog_twid); + gtk_widget_show (wid); + + wid = gtk_button_new_from_stock (GTK_STOCK_OK); + gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0); + g_signal_connect (G_OBJECT (wid), "clicked", + G_CALLBACK (pevent_ok_cb), NULL); + gtk_widget_show (wid); + + gtk_widget_show (hbox); + + gtk_widget_show (pevent_dialog); +} diff --git a/src/fe-gtk/textgui.h b/src/fe-gtk/textgui.h new file mode 100644 index 00000000..23db5848 --- /dev/null +++ b/src/fe-gtk/textgui.h @@ -0,0 +1,2 @@ +void PrintTextRaw (void *xtbuf, unsigned char *text, int indent, time_t stamp); +void pevent_dialog_show (void); diff --git a/src/fe-gtk/urlgrab.c b/src/fe-gtk/urlgrab.c new file mode 100644 index 00000000..6e5f1e0d --- /dev/null +++ b/src/fe-gtk/urlgrab.c @@ -0,0 +1,208 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include "fe-gtk.h" + +#include <gtk/gtkhbox.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkhbbox.h> +#include <gtk/gtkscrolledwindow.h> + +#include <gtk/gtkliststore.h> +#include <gtk/gtktreeview.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtkcellrenderertext.h> + +#include "../common/xchat.h" +#include "../common/cfgfiles.h" +#include "../common/fe.h" +#include "../common/url.h" +#include "../common/tree.h" +#include "gtkutil.h" +#include "menu.h" +#include "maingui.h" +#include "urlgrab.h" + +/* model for the URL treeview */ +enum +{ + URL_COLUMN, + N_COLUMNS +}; + +static GtkWidget *urlgrabberwindow = 0; + + +static gboolean +url_treeview_url_clicked_cb (GtkWidget *view, GdkEventButton *event, + gpointer data) +{ + GtkTreeIter iter; + gchar *url; + + if (!event || + !gtkutil_treeview_get_selected (GTK_TREE_VIEW (view), &iter, + URL_COLUMN, &url, -1)) + { + return FALSE; + } + + switch (event->button) + { + case 1: + if (event->type == GDK_2BUTTON_PRESS) + fe_open_url (url); + break; + case 3: + menu_urlmenu (event, url); + break; + default: + break; + } + g_free (url); + + return FALSE; +} + +static GtkWidget * +url_treeview_new (GtkWidget *box) +{ + GtkListStore *store; + GtkWidget *view; + + store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING); + g_return_val_if_fail (store != NULL, NULL); + + view = gtkutil_treeview_new (box, GTK_TREE_MODEL (store), NULL, + URL_COLUMN, _("URL"), -1); + g_signal_connect (G_OBJECT (view), "button_press_event", + G_CALLBACK (url_treeview_url_clicked_cb), NULL); + /* don't want column headers */ + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view), FALSE); + gtk_widget_show (view); + return view; +} + +static void +url_closegui (GtkWidget *wid, gpointer userdata) +{ + urlgrabberwindow = 0; +} + +static void +url_button_clear (void) +{ + GtkListStore *store; + + url_clear (); + store = GTK_LIST_STORE (g_object_get_data (G_OBJECT (urlgrabberwindow), + "model")); + gtk_list_store_clear (store); +} + +static void +url_button_copy (GtkWidget *widget, gpointer data) +{ + GtkTreeView *view = GTK_TREE_VIEW (data); + GtkTreeIter iter; + gchar *url = NULL; + + if (gtkutil_treeview_get_selected (view, &iter, URL_COLUMN, &url, -1)) + { + gtkutil_copy_to_clipboard (GTK_WIDGET (view), NULL, url); + g_free (url); + } +} + +static void +url_save_callback (void *arg1, char *file) +{ + if (file) + url_save (file, "w", TRUE); +} + +static void +url_button_save (void) +{ + gtkutil_file_req (_("Select an output filename"), + url_save_callback, NULL, NULL, FRF_WRITE); +} + +void +fe_url_add (const char *urltext) +{ + GtkListStore *store; + GtkTreeIter iter; + + if (urlgrabberwindow) + { + store = GTK_LIST_STORE (g_object_get_data (G_OBJECT (urlgrabberwindow), + "model")); + gtk_list_store_prepend (store, &iter); + gtk_list_store_set (store, &iter, + URL_COLUMN, urltext, + -1); + } +} + +static int +populate_cb (char *urltext, gpointer userdata) +{ + fe_url_add (urltext); + return TRUE; +} + +void +url_opengui () +{ + GtkWidget *vbox, *hbox, *view; + + if (urlgrabberwindow) + { + mg_bring_tofront (urlgrabberwindow); + return; + } + + urlgrabberwindow = + mg_create_generic_tab ("UrlGrabber", _("XChat: URL Grabber"), FALSE, + TRUE, url_closegui, NULL, 400, 256, &vbox, 0); + view = url_treeview_new (vbox); + g_object_set_data (G_OBJECT (urlgrabberwindow), "model", + gtk_tree_view_get_model (GTK_TREE_VIEW (view))); + + hbox = gtk_hbutton_box_new (); + gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_SPREAD); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 5); + gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 0); + gtk_widget_show (hbox); + + gtkutil_button (hbox, GTK_STOCK_CLEAR, + _("Clear list"), url_button_clear, 0, _("Clear")); + gtkutil_button (hbox, GTK_STOCK_COPY, + _("Copy selected URL"), url_button_copy, view, _("Copy")); + gtkutil_button (hbox, GTK_STOCK_SAVE_AS, + _("Save list to a file"), url_button_save, 0, _("Save As...")); + + gtk_widget_show (urlgrabberwindow); + + tree_foreach (url_tree, (tree_traverse_func *)populate_cb, NULL); +} diff --git a/src/fe-gtk/urlgrab.h b/src/fe-gtk/urlgrab.h new file mode 100644 index 00000000..cc534241 --- /dev/null +++ b/src/fe-gtk/urlgrab.h @@ -0,0 +1,2 @@ +void url_autosave (void); +void url_opengui (void); diff --git a/src/fe-gtk/userlistgui.c b/src/fe-gtk/userlistgui.c new file mode 100644 index 00000000..f040a6a1 --- /dev/null +++ b/src/fe-gtk/userlistgui.c @@ -0,0 +1,718 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include "fe-gtk.h" + +#include <gtk/gtkbox.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkdnd.h> +#include <gtk/gtkentry.h> +#include <gtk/gtktreeview.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtkscrolledwindow.h> +#include <gtk/gtkcellrendererpixbuf.h> +#include <gtk/gtkcellrenderertext.h> +#include <gtk/gtkliststore.h> +#include <gdk/gdkkeysyms.h> + +#include "../common/xchat.h" +#include "../common/util.h" +#include "../common/userlist.h" +#include "../common/modes.h" +#include "../common/notify.h" +#include "../common/xchatc.h" +#include "gtkutil.h" +#include "palette.h" +#include "maingui.h" +#include "menu.h" +#include "pixmaps.h" +#include "userlistgui.h" + +#ifdef USE_GTKSPELL +#include <gtk/gtktextview.h> +#endif + + +enum +{ + COL_PIX=0, // GdkPixbuf * + COL_NICK=1, // char * + COL_HOST=2, // char * + COL_USER=3, // struct User * + COL_GDKCOLOR=4 // GdkColor * +}; + + +GdkPixbuf * +get_user_icon (server *serv, struct User *user) +{ + char *pre; + int level; + + if (!user) + return NULL; + + /* these ones are hardcoded */ + switch (user->prefix[0]) + { + case 0: return NULL; + case '@': return pix_op; + case '%': return pix_hop; + case '+': return pix_voice; + } + + /* find out how many levels above Op this user is */ + pre = strchr (serv->nick_prefixes, '@'); + if (pre && pre != serv->nick_prefixes) + { + pre--; + level = 0; + while (1) + { + if (pre[0] == user->prefix[0]) + { + switch (level) + { + case 0: return pix_red; /* 1 level above op */ + case 1: return pix_purple; /* 2 levels above op */ + } + break; /* 3+, no icons */ + } + level++; + if (pre == serv->nick_prefixes) + break; + pre--; + } + } + + return NULL; +} + +void +fe_userlist_numbers (session *sess) +{ + char tbuf[256]; + + if (sess == current_tab || !sess->gui->is_tab) + { + if (sess->total) + { + snprintf (tbuf, sizeof (tbuf), _("%d ops, %d total"), sess->ops, sess->total); + tbuf[sizeof (tbuf) - 1] = 0; + gtk_label_set_text (GTK_LABEL (sess->gui->namelistinfo), tbuf); + } else + { + gtk_label_set_text (GTK_LABEL (sess->gui->namelistinfo), NULL); + } + + if (sess->type == SESS_CHANNEL && prefs.gui_tweaks & 1) + fe_set_title (sess); + } +} + +static void +scroll_to_iter (GtkTreeIter *iter, GtkTreeView *treeview, GtkTreeModel *model) +{ + GtkTreePath *path = gtk_tree_model_get_path (model, iter); + if (path) + { + gtk_tree_view_scroll_to_cell (treeview, path, NULL, TRUE, 0.5, 0.5); + gtk_tree_path_free (path); + } +} + +/* select a row in the userlist by nick-name */ + +void +userlist_select (session *sess, char *name) +{ + GtkTreeIter iter; + GtkTreeView *treeview = GTK_TREE_VIEW (sess->gui->user_tree); + GtkTreeModel *model = gtk_tree_view_get_model (treeview); + GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview); + struct User *row_user; + + if (gtk_tree_model_get_iter_first (model, &iter)) + { + do + { + gtk_tree_model_get (model, &iter, COL_USER, &row_user, -1); + if (sess->server->p_cmp (row_user->nick, name) == 0) + { + if (gtk_tree_selection_iter_is_selected (selection, &iter)) + gtk_tree_selection_unselect_iter (selection, &iter); + else + gtk_tree_selection_select_iter (selection, &iter); + + /* and make sure it's visible */ + scroll_to_iter (&iter, treeview, model); + return; + } + } + while (gtk_tree_model_iter_next (model, &iter)); + } +} + +char ** +userlist_selection_list (GtkWidget *widget, int *num_ret) +{ + GtkTreeIter iter; + GtkTreeView *treeview = (GtkTreeView *) widget; + GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview); + GtkTreeModel *model = gtk_tree_view_get_model (treeview); + struct User *user; + int i, num_sel; + char **nicks; + + *num_ret = 0; + /* first, count the number of selections */ + num_sel = 0; + if (gtk_tree_model_get_iter_first (model, &iter)) + { + do + { + if (gtk_tree_selection_iter_is_selected (selection, &iter)) + num_sel++; + } + while (gtk_tree_model_iter_next (model, &iter)); + } + + if (num_sel < 1) + return NULL; + + nicks = malloc (sizeof (char *) * (num_sel + 1)); + + i = 0; + gtk_tree_model_get_iter_first (model, &iter); + do + { + if (gtk_tree_selection_iter_is_selected (selection, &iter)) + { + gtk_tree_model_get (model, &iter, COL_USER, &user, -1); + nicks[i] = g_strdup (user->nick); + i++; + nicks[i] = NULL; + } + } + while (gtk_tree_model_iter_next (model, &iter)); + + *num_ret = i; + return nicks; +} + +void +fe_userlist_set_selected (struct session *sess) +{ + GtkListStore *store = sess->res->user_model; + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (sess->gui->user_tree)); + GtkTreeIter iter; + struct User *user; + + /* if it's not front-most tab it doesn't own the GtkTreeView! */ + if (store != (GtkListStore*) gtk_tree_view_get_model (GTK_TREE_VIEW (sess->gui->user_tree))) + return; + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) + { + do + { + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, COL_USER, &user, -1); + + if (gtk_tree_selection_iter_is_selected (selection, &iter)) + user->selected = 1; + else + user->selected = 0; + + } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter)); + } +} + +static GtkTreeIter * +find_row (GtkTreeView *treeview, GtkTreeModel *model, struct User *user, + int *selected) +{ + static GtkTreeIter iter; + struct User *row_user; + + *selected = FALSE; + if (gtk_tree_model_get_iter_first (model, &iter)) + { + do + { + gtk_tree_model_get (model, &iter, COL_USER, &row_user, -1); + if (row_user == user) + { + if (gtk_tree_view_get_model (treeview) == model) + { + if (gtk_tree_selection_iter_is_selected (gtk_tree_view_get_selection (treeview), &iter)) + *selected = TRUE; + } + return &iter; + } + } + while (gtk_tree_model_iter_next (model, &iter)); + } + + return NULL; +} + +void +userlist_set_value (GtkWidget *treeview, gfloat val) +{ + gtk_adjustment_set_value ( + gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (treeview)), val); +} + +gfloat +userlist_get_value (GtkWidget *treeview) +{ + return gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (treeview))->value; +} + +int +fe_userlist_remove (session *sess, struct User *user) +{ + GtkTreeIter *iter; +/* GtkAdjustment *adj; + gfloat val, end;*/ + int sel; + + iter = find_row (GTK_TREE_VIEW (sess->gui->user_tree), + sess->res->user_model, user, &sel); + if (!iter) + return 0; + +/* adj = gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (sess->gui->user_tree)); + val = adj->value;*/ + + gtk_list_store_remove (sess->res->user_model, iter); + + /* is it the front-most tab? */ +/* if (gtk_tree_view_get_model (GTK_TREE_VIEW (sess->gui->user_tree)) + == sess->res->user_model) + { + end = adj->upper - adj->lower - adj->page_size; + if (val > end) + val = end; + gtk_adjustment_set_value (adj, val); + }*/ + + return sel; +} + +void +fe_userlist_rehash (session *sess, struct User *user) +{ + GtkTreeIter *iter; + int sel; + int do_away = TRUE; + + iter = find_row (GTK_TREE_VIEW (sess->gui->user_tree), + sess->res->user_model, user, &sel); + if (!iter) + return; + + if (prefs.away_size_max < 1 || !prefs.away_track) + do_away = FALSE; + + gtk_list_store_set (GTK_LIST_STORE (sess->res->user_model), iter, + COL_HOST, user->hostname, + COL_GDKCOLOR, (do_away) + ? (user->away ? &colors[COL_AWAY] : NULL) + : (NULL), + -1); +} + +void +fe_userlist_insert (session *sess, struct User *newuser, int row, int sel) +{ + GtkTreeModel *model = sess->res->user_model; + GdkPixbuf *pix = get_user_icon (sess->server, newuser); + GtkTreeIter iter; + int do_away = TRUE; + char *nick; + + if (prefs.away_size_max < 1 || !prefs.away_track) + do_away = FALSE; + + nick = newuser->nick; + if (prefs.gui_tweaks & 64) + { + nick = malloc (strlen (newuser->nick) + 2); + nick[0] = newuser->prefix[0]; + if (!nick[0] || nick[0] == ' ') + strcpy (nick, newuser->nick); + else + strcpy (nick + 1, newuser->nick); + pix = NULL; + } + + gtk_list_store_insert_with_values (GTK_LIST_STORE (model), &iter, row, + COL_PIX, pix, + COL_NICK, nick, + COL_HOST, newuser->hostname, + COL_USER, newuser, + COL_GDKCOLOR, (do_away) + ? (newuser->away ? &colors[COL_AWAY] : NULL) + : (NULL), + -1); + + if (prefs.gui_tweaks & 64) + free (nick); + + /* is it me? */ + if (newuser->me && sess->gui->nick_box) + { + if (!sess->gui->is_tab || sess == current_tab) + mg_set_access_icon (sess->gui, pix, sess->server->is_away); + } + +#if 0 + if (prefs.hilitenotify && notify_isnotify (sess, newuser->nick)) + { + gtk_clist_set_foreground ((GtkCList *) sess->gui->user_clist, row, + &colors[prefs.nu_color]); + } +#endif + + /* is it the front-most tab? */ + if (gtk_tree_view_get_model (GTK_TREE_VIEW (sess->gui->user_tree)) + == model) + { + if (sel) + gtk_tree_selection_select_iter (gtk_tree_view_get_selection + (GTK_TREE_VIEW (sess->gui->user_tree)), &iter); + } +} + +void +fe_userlist_move (session *sess, struct User *user, int new_row) +{ + fe_userlist_insert (sess, user, new_row, fe_userlist_remove (sess, user)); +} + +void +fe_userlist_clear (session *sess) +{ + gtk_list_store_clear (sess->res->user_model); +} + +static void +userlist_dnd_drop (GtkTreeView *widget, GdkDragContext *context, + gint x, gint y, GtkSelectionData *selection_data, + guint info, guint ttime, gpointer userdata) +{ + struct User *user; + GtkTreePath *path; + GtkTreeModel *model; + GtkTreeIter iter; + + if (!gtk_tree_view_get_path_at_pos (widget, x, y, &path, NULL, NULL, NULL)) + return; + + model = gtk_tree_view_get_model (widget); + if (!gtk_tree_model_get_iter (model, &iter, path)) + return; + gtk_tree_model_get (model, &iter, COL_USER, &user, -1); + + mg_dnd_drop_file (current_sess, user->nick, selection_data->data); +} + +static gboolean +userlist_dnd_motion (GtkTreeView *widget, GdkDragContext *context, gint x, + gint y, guint ttime, gpointer tree) +{ + GtkTreePath *path; + GtkTreeSelection *sel; + + if (!tree) + return FALSE; + + if (gtk_tree_view_get_path_at_pos (widget, x, y, &path, NULL, NULL, NULL)) + { + sel = gtk_tree_view_get_selection (widget); + gtk_tree_selection_unselect_all (sel); + gtk_tree_selection_select_path (sel, path); + } + + return FALSE; +} + +static gboolean +userlist_dnd_leave (GtkTreeView *widget, GdkDragContext *context, guint ttime) +{ + gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (widget)); + return TRUE; +} + +void * +userlist_create_model (void) +{ + return gtk_list_store_new (5, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_POINTER, GDK_TYPE_COLOR); +} + +static void +userlist_add_columns (GtkTreeView * treeview) +{ + GtkCellRenderer *renderer; + + /* icon column */ + renderer = gtk_cell_renderer_pixbuf_new (); + if (prefs.gui_tweaks & 32) + g_object_set (G_OBJECT (renderer), "ypad", 0, NULL); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview), + -1, NULL, renderer, + "pixbuf", 0, NULL); + + /* nick column */ + renderer = gtk_cell_renderer_text_new (); + if (prefs.gui_tweaks & 32) + g_object_set (G_OBJECT (renderer), "ypad", 0, NULL); + gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer), 1); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview), + -1, NULL, renderer, + "text", 1, "foreground-gdk", 4, NULL); + + if (prefs.showhostname_in_userlist) + { + /* hostname column */ + renderer = gtk_cell_renderer_text_new (); + if (prefs.gui_tweaks & 32) + g_object_set (G_OBJECT (renderer), "ypad", 0, NULL); + gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer), 1); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview), + -1, NULL, renderer, + "text", 2, NULL); + } +} + +static gint +userlist_click_cb (GtkWidget *widget, GdkEventButton *event, gpointer userdata) +{ + char **nicks; + int i; + GtkTreeSelection *sel; + GtkTreePath *path; + + if (!event) + return FALSE; + + if (!(event->state & GDK_CONTROL_MASK) && + event->type == GDK_2BUTTON_PRESS && prefs.doubleclickuser[0]) + { + nicks = userlist_selection_list (widget, &i); + if (nicks) + { + nick_command_parse (current_sess, prefs.doubleclickuser, nicks[0], + nicks[0]); + while (i) + { + i--; + g_free (nicks[i]); + } + free (nicks); + } + return TRUE; + } + + if (event->button == 3) + { + /* do we have a multi-selection? */ + nicks = userlist_selection_list (widget, &i); + if (nicks && i > 1) + { + menu_nickmenu (current_sess, event, nicks[0], i); + while (i) + { + i--; + g_free (nicks[i]); + } + free (nicks); + return TRUE; + } + if (nicks) + { + g_free (nicks[0]); + free (nicks); + } + + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget)); + if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), + event->x, event->y, &path, 0, 0, 0)) + { + gtk_tree_selection_unselect_all (sel); + gtk_tree_selection_select_path (sel, path); + gtk_tree_path_free (path); + nicks = userlist_selection_list (widget, &i); + if (nicks) + { + menu_nickmenu (current_sess, event, nicks[0], i); + while (i) + { + i--; + g_free (nicks[i]); + } + free (nicks); + } + } else + { + gtk_tree_selection_unselect_all (sel); + } + + return TRUE; + } + + return FALSE; +} + +static gboolean +userlist_key_cb (GtkWidget *wid, GdkEventKey *evt, gpointer userdata) +{ + if (evt->keyval >= GDK_asterisk && evt->keyval <= GDK_z) + { + /* dirty trick to avoid auto-selection */ + SPELL_ENTRY_SET_EDITABLE (current_sess->gui->input_box, FALSE); + gtk_widget_grab_focus (current_sess->gui->input_box); + SPELL_ENTRY_SET_EDITABLE (current_sess->gui->input_box, TRUE); + gtk_widget_event (current_sess->gui->input_box, (GdkEvent *)evt); + return TRUE; + } + + return FALSE; +} + +GtkWidget * +userlist_create (GtkWidget *box) +{ + GtkWidget *sw, *treeview; + static const GtkTargetEntry dnd_dest_targets[] = + { + {"text/uri-list", 0, 1}, + {"XCHAT_CHANVIEW", GTK_TARGET_SAME_APP, 75 } + }; + static const GtkTargetEntry dnd_src_target[] = + { + {"XCHAT_USERLIST", GTK_TARGET_SAME_APP, 75 } + }; + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), + GTK_SHADOW_ETCHED_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + prefs.showhostname_in_userlist ? + GTK_POLICY_AUTOMATIC : + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_box_pack_start (GTK_BOX (box), sw, TRUE, TRUE, 0); + gtk_widget_show (sw); + + treeview = gtk_tree_view_new (); + gtk_widget_set_name (treeview, "xchat-userlist"); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE); + gtk_tree_selection_set_mode (gtk_tree_view_get_selection + (GTK_TREE_VIEW (treeview)), + GTK_SELECTION_MULTIPLE); + + /* set up drops */ + gtk_drag_dest_set (treeview, GTK_DEST_DEFAULT_ALL, dnd_dest_targets, 2, + GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK); + gtk_drag_source_set (treeview, GDK_BUTTON1_MASK, dnd_src_target, 1, GDK_ACTION_MOVE); + + /* file DND (for DCC) */ + g_signal_connect (G_OBJECT (treeview), "drag_motion", + G_CALLBACK (userlist_dnd_motion), treeview); + g_signal_connect (G_OBJECT (treeview), "drag_leave", + G_CALLBACK (userlist_dnd_leave), 0); + g_signal_connect (G_OBJECT (treeview), "drag_data_received", + G_CALLBACK (userlist_dnd_drop), treeview); + + g_signal_connect (G_OBJECT (treeview), "button_press_event", + G_CALLBACK (userlist_click_cb), 0); + g_signal_connect (G_OBJECT (treeview), "key_press_event", + G_CALLBACK (userlist_key_cb), 0); + + /* tree/chanview DND */ +#ifndef WIN32 /* leaks GDI pool memory, don't enable */ + g_signal_connect (G_OBJECT (treeview), "drag_begin", + G_CALLBACK (mg_drag_begin_cb), NULL); + g_signal_connect (G_OBJECT (treeview), "drag_drop", + G_CALLBACK (mg_drag_drop_cb), NULL); + g_signal_connect (G_OBJECT (treeview), "drag_motion", + G_CALLBACK (mg_drag_motion_cb), NULL); + g_signal_connect (G_OBJECT (treeview), "drag_end", + G_CALLBACK (mg_drag_end_cb), NULL); +#endif + + userlist_add_columns (GTK_TREE_VIEW (treeview)); + + gtk_container_add (GTK_CONTAINER (sw), treeview); + gtk_widget_show (treeview); + + return treeview; +} + +void +userlist_show (session *sess) +{ + gtk_tree_view_set_model (GTK_TREE_VIEW (sess->gui->user_tree), + sess->res->user_model); +} + +void +fe_uselect (session *sess, char *word[], int do_clear, int scroll_to) +{ + int thisname; + char *name; + GtkTreeIter iter; + GtkTreeView *treeview = GTK_TREE_VIEW (sess->gui->user_tree); + GtkTreeModel *model = gtk_tree_view_get_model (treeview); + GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview); + struct User *row_user; + + if (gtk_tree_model_get_iter_first (model, &iter)) + { + if (do_clear) + gtk_tree_selection_unselect_all (selection); + + do + { + if (*word[0]) + { + gtk_tree_model_get (model, &iter, COL_USER, &row_user, -1); + thisname = 0; + while ( *(name = word[thisname++]) ) + { + if (sess->server->p_cmp (row_user->nick, name) == 0) + { + gtk_tree_selection_select_iter (selection, &iter); + if (scroll_to) + scroll_to_iter (&iter, treeview, model); + break; + } + } + } + + } + while (gtk_tree_model_iter_next (model, &iter)); + } +} diff --git a/src/fe-gtk/userlistgui.h b/src/fe-gtk/userlistgui.h new file mode 100644 index 00000000..b49e2b9b --- /dev/null +++ b/src/fe-gtk/userlistgui.h @@ -0,0 +1,8 @@ +void userlist_set_value (GtkWidget *treeview, gfloat val); +gfloat userlist_get_value (GtkWidget *treeview); +GtkWidget *userlist_create (GtkWidget *box); +void *userlist_create_model (void); +void userlist_show (session *sess); +void userlist_select (session *sess, char *name); +char **userlist_selection_list (GtkWidget *widget, int *num_ret); +GdkPixbuf *get_user_icon (server *serv, struct User *user); diff --git a/src/fe-gtk/xtext.c b/src/fe-gtk/xtext.c new file mode 100644 index 00000000..fa9803c7 --- /dev/null +++ b/src/fe-gtk/xtext.c @@ -0,0 +1,5478 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * ========================================================================= + * + * xtext, the text widget used by X-Chat. + * By Peter Zelezny <zed@xchat.org>. + * + */ + +#define XCHAT /* using xchat */ +#define TINT_VALUE 195 /* 195/255 of the brightness. */ +#define MOTION_MONITOR /* URL hilights. */ +#define SMOOTH_SCROLL /* line-by-line or pixel scroll? */ +#define SCROLL_HACK /* use XCopyArea scroll, or full redraw? */ +#undef COLOR_HILIGHT /* Color instead of underline? */ +/* Italic is buggy because it assumes drawing an italic string will have + identical extents to the normal font. This is only true some of the + time, so we can't use this hack yet. */ +#undef ITALIC /* support Italic? */ +#define GDK_MULTIHEAD_SAFE +#define USE_DB /* double buffer */ + +#define MARGIN 2 /* dont touch. */ +#define REFRESH_TIMEOUT 20 +#define WORDWRAP_LIMIT 24 + +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> +#include <gtk/gtkmain.h> +#include <gtk/gtksignal.h> +#include <gtk/gtkselection.h> +#include <gtk/gtkclipboard.h> +#include <gtk/gtkversion.h> +#include <gtk/gtkwindow.h> + +#ifdef XCHAT +#include "../../config.h" /* can define USE_XLIB here */ +#else +#define USE_XLIB +#endif + +#ifdef USE_XLIB +#include <gdk/gdkx.h> +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#endif + +#ifdef USE_MMX +#include "mmx_cmod.h" +#endif + +#include "xtext.h" + +#define charlen(str) g_utf8_skip[*(guchar *)(str)] + +#ifdef WIN32 +#include <windows.h> +#include <gdk/gdkwin32.h> +#endif + +/* is delimiter */ +#define is_del(c) \ + (c == ' ' || c == '\n' || c == ')' || c == '(' || \ + c == '>' || c == '<' || c == ATTR_RESET || c == ATTR_BOLD || c == 0) + +#ifdef SCROLL_HACK +/* force scrolling off */ +#define dontscroll(buf) (buf)->last_pixel_pos = 0x7fffffff +#else +#define dontscroll(buf) +#endif + +static GtkWidgetClass *parent_class = NULL; + +struct textentry +{ + struct textentry *next; + struct textentry *prev; + unsigned char *str; + time_t stamp; + gint16 str_width; + gint16 str_len; + gint16 mark_start; + gint16 mark_end; + gint16 indent; + gint16 left_len; + gint16 lines_taken; +#define RECORD_WRAPS 4 + guint16 wrap_offset[RECORD_WRAPS]; + guchar mb; /* boolean: is multibyte? */ + guchar tag; + guchar pad1; + guchar pad2; /* 32-bit align : 44 bytes total */ +}; + +enum +{ + WORD_CLICK, + LAST_SIGNAL +}; + +/* values for selection info */ +enum +{ + TARGET_UTF8_STRING, + TARGET_STRING, + TARGET_TEXT, + TARGET_COMPOUND_TEXT +}; + +static guint xtext_signals[LAST_SIGNAL]; + +#ifdef XCHAT +char *nocasestrstr (const char *text, const char *tofind); /* util.c */ +int xtext_get_stamp_str (time_t, char **); +#endif +static void gtk_xtext_render_page (GtkXText * xtext); +static void gtk_xtext_calc_lines (xtext_buffer *buf, int); +#if defined(USE_XLIB) || defined(WIN32) +static void gtk_xtext_load_trans (GtkXText * xtext); +static void gtk_xtext_free_trans (GtkXText * xtext); +#endif +static char *gtk_xtext_selection_get_text (GtkXText *xtext, int *len_ret); +static textentry *gtk_xtext_nth (GtkXText *xtext, int line, int *subline); +static void gtk_xtext_adjustment_changed (GtkAdjustment * adj, + GtkXText * xtext); +static int gtk_xtext_render_ents (GtkXText * xtext, textentry *, textentry *); +static void gtk_xtext_recalc_widths (xtext_buffer *buf, int); +static void gtk_xtext_fix_indent (xtext_buffer *buf); +static int gtk_xtext_find_subline (GtkXText *xtext, textentry *ent, int line); +static char *gtk_xtext_conv_color (unsigned char *text, int len, int *newlen); +static unsigned char * +gtk_xtext_strip_color (unsigned char *text, int len, unsigned char *outbuf, + int *newlen, int *mb_ret, int strip_hidden); +static gboolean gtk_xtext_check_ent_visibility (GtkXText * xtext, textentry *find_ent, int add); +static int gtk_xtext_render_page_timeout (GtkXText * xtext); + +/* some utility functions first */ + +#ifndef XCHAT /* xchat has this in util.c */ + +static char * +nocasestrstr (const char *s, const char *tofind) +{ + register const size_t len = strlen (tofind); + + if (len == 0) + return (char *)s; + while (toupper(*s) != toupper(*tofind) || strncasecmp (s, tofind, len)) + if (*s++ == '\0') + return (char *)NULL; + return (char *)s; +} + +#endif + +/* gives width of a 8bit string - with no mIRC codes in it */ + +static int +gtk_xtext_text_width_8bit (GtkXText *xtext, unsigned char *str, int len) +{ + int width = 0; + + while (len) + { + width += xtext->fontwidth[*str]; + str++; + len--; + } + + return width; +} + +#ifdef WIN32 + +static void +win32_draw_bg (GtkXText *xtext, int x, int y, int width, int height) +{ + HDC hdc; + HWND hwnd; + HRGN rgn; + + if (xtext->shaded) + { + /* xtext->pixmap is really a GdkImage, created in win32_tint() */ + gdk_draw_image (xtext->draw_buf, xtext->bgc, (GdkImage*)xtext->pixmap, + x, y, x, y, width, height); + } else + { + hwnd = GDK_WINDOW_HWND (xtext->draw_buf); + hdc = GetDC (hwnd); + + rgn = CreateRectRgn (x, y, x + width, y + height); + SelectClipRgn (hdc, rgn); + + PaintDesktop (hdc); + + ReleaseDC (hwnd, hdc); + DeleteObject (rgn); + } +} + +static void +xtext_draw_bg (GtkXText *xtext, int x, int y, int width, int height) +{ + if (xtext->transparent) + win32_draw_bg (xtext, x, y, width, height); + else + gdk_draw_rectangle (xtext->draw_buf, xtext->bgc, 1, x, y, width, height); +} + +#else + +#define xtext_draw_bg(xt,x,y,w,h) gdk_draw_rectangle(xt->draw_buf, xt->bgc, \ + 1,x,y,w,h); + +#endif + +/* ========================================= */ +/* ========== XFT 1 and 2 BACKEND ========== */ +/* ========================================= */ + +#ifdef USE_XFT + +static void +backend_font_close (GtkXText *xtext) +{ + XftFontClose (GDK_WINDOW_XDISPLAY (xtext->draw_buf), xtext->font); +#ifdef ITALIC + XftFontClose (GDK_WINDOW_XDISPLAY (xtext->draw_buf), xtext->ifont); +#endif +} + +static void +backend_init (GtkXText *xtext) +{ + if (xtext->xftdraw == NULL) + { + xtext->xftdraw = XftDrawCreate ( + GDK_WINDOW_XDISPLAY (xtext->draw_buf), + GDK_WINDOW_XWINDOW (xtext->draw_buf), + GDK_VISUAL_XVISUAL (gdk_drawable_get_visual (xtext->draw_buf)), + GDK_COLORMAP_XCOLORMAP (gdk_drawable_get_colormap (xtext->draw_buf))); + XftDrawSetSubwindowMode (xtext->xftdraw, IncludeInferiors); + } +} + +static void +backend_deinit (GtkXText *xtext) +{ + if (xtext->xftdraw) + { + XftDrawDestroy (xtext->xftdraw); + xtext->xftdraw = NULL; + } +} + +static XftFont * +backend_font_open_real (Display *xdisplay, char *name, gboolean italics) +{ + XftFont *font = NULL; + PangoFontDescription *fontd; + int weight, slant, screen = DefaultScreen (xdisplay); + + fontd = pango_font_description_from_string (name); + + if (pango_font_description_get_size (fontd) != 0) + { + weight = pango_font_description_get_weight (fontd); + /* from pangoft2-fontmap.c */ + if (weight < (PANGO_WEIGHT_NORMAL + PANGO_WEIGHT_LIGHT) / 2) + weight = XFT_WEIGHT_LIGHT; + else if (weight < (PANGO_WEIGHT_NORMAL + 600) / 2) + weight = XFT_WEIGHT_MEDIUM; + else if (weight < (600 + PANGO_WEIGHT_BOLD) / 2) + weight = XFT_WEIGHT_DEMIBOLD; + else if (weight < (PANGO_WEIGHT_BOLD + PANGO_WEIGHT_ULTRABOLD) / 2) + weight = XFT_WEIGHT_BOLD; + else + weight = XFT_WEIGHT_BLACK; + + slant = pango_font_description_get_style (fontd); + if (slant == PANGO_STYLE_ITALIC) + slant = XFT_SLANT_ITALIC; + else if (slant == PANGO_STYLE_OBLIQUE) + slant = XFT_SLANT_OBLIQUE; + else + slant = XFT_SLANT_ROMAN; + + font = XftFontOpen (xdisplay, screen, + XFT_FAMILY, XftTypeString, pango_font_description_get_family (fontd), + XFT_CORE, XftTypeBool, False, + XFT_SIZE, XftTypeDouble, (double)pango_font_description_get_size (fontd)/PANGO_SCALE, + XFT_WEIGHT, XftTypeInteger, weight, + XFT_SLANT, XftTypeInteger, italics ? XFT_SLANT_ITALIC : slant, + NULL); + } + pango_font_description_free (fontd); + + if (font == NULL) + { + font = XftFontOpenName (xdisplay, screen, name); + if (font == NULL) + font = XftFontOpenName (xdisplay, screen, "sans-11"); + } + + return font; +} + +static void +backend_font_open (GtkXText *xtext, char *name) +{ + Display *dis = GDK_WINDOW_XDISPLAY (xtext->draw_buf); + + xtext->font = backend_font_open_real (dis, name, FALSE); +#ifdef ITALIC + xtext->ifont = backend_font_open_real (dis, name, TRUE); +#endif +} + +inline static int +backend_get_char_width (GtkXText *xtext, unsigned char *str, int *mbl_ret) +{ + XGlyphInfo ext; + + if (*str < 128) + { + *mbl_ret = 1; + return xtext->fontwidth[*str]; + } + + *mbl_ret = charlen (str); + XftTextExtentsUtf8 (GDK_WINDOW_XDISPLAY (xtext->draw_buf), xtext->font, str, *mbl_ret, &ext); + + return ext.xOff; +} + +static int +backend_get_text_width (GtkXText *xtext, guchar *str, int len, int is_mb) +{ + XGlyphInfo ext; + + if (!is_mb) + return gtk_xtext_text_width_8bit (xtext, str, len); + + XftTextExtentsUtf8 (GDK_WINDOW_XDISPLAY (xtext->draw_buf), xtext->font, str, len, &ext); + return ext.xOff; +} + +static void +backend_draw_text (GtkXText *xtext, int dofill, GdkGC *gc, int x, int y, + char *str, int len, int str_width, int is_mb) +{ + /*Display *xdisplay = GDK_WINDOW_XDISPLAY (xtext->draw_buf);*/ + void (*draw_func) (XftDraw *, XftColor *, XftFont *, int, int, XftChar8 *, int) = (void *)XftDrawString8; + XftFont *font; + + /* if all ascii, use String8 to avoid the conversion penalty */ + if (is_mb) + draw_func = (void *)XftDrawStringUtf8; + + if (dofill) + { +/* register GC xgc = GDK_GC_XGC (gc); + XSetForeground (xdisplay, xgc, xtext->xft_bg->pixel); + XFillRectangle (xdisplay, GDK_WINDOW_XWINDOW (xtext->draw_buf), xgc, x, + y - xtext->font->ascent, str_width, xtext->fontsize);*/ + XftDrawRect (xtext->xftdraw, xtext->xft_bg, x, + y - xtext->font->ascent, str_width, xtext->fontsize); + } + + font = xtext->font; +#ifdef ITALIC + if (xtext->italics) + font = xtext->ifont; +#endif + + draw_func (xtext->xftdraw, xtext->xft_fg, font, x, y, str, len); + + if (xtext->overdraw) + draw_func (xtext->xftdraw, xtext->xft_fg, font, x, y, str, len); + + if (xtext->bold) + draw_func (xtext->xftdraw, xtext->xft_fg, font, x + 1, y, str, len); +} + +/*static void +backend_set_clip (GtkXText *xtext, GdkRectangle *area) +{ + gdk_gc_set_clip_rectangle (xtext->fgc, area); + gdk_gc_set_clip_rectangle (xtext->bgc, area); +} + +static void +backend_clear_clip (GtkXText *xtext) +{ + gdk_gc_set_clip_rectangle (xtext->fgc, NULL); + gdk_gc_set_clip_rectangle (xtext->bgc, NULL); +}*/ + +/*static void +backend_set_clip (GtkXText *xtext, GdkRectangle *area) +{ + Region reg; + XRectangle rect; + + rect.x = area->x; + rect.y = area->y; + rect.width = area->width; + rect.height = area->height; + + reg = XCreateRegion (); + XUnionRectWithRegion (&rect, reg, reg); + XftDrawSetClip (xtext->xftdraw, reg); + XDestroyRegion (reg); + + gdk_gc_set_clip_rectangle (xtext->fgc, area); +} + +static void +backend_clear_clip (GtkXText *xtext) +{ + XftDrawSetClip (xtext->xftdraw, NULL); + gdk_gc_set_clip_rectangle (xtext->fgc, NULL); +} +*/ +#else /* !USE_XFT */ + +/* ======================================= */ +/* ============ PANGO BACKEND ============ */ +/* ======================================= */ + +static void +backend_font_close (GtkXText *xtext) +{ + pango_font_description_free (xtext->font->font); +#ifdef ITALIC + pango_font_description_free (xtext->font->ifont); +#endif +} + +static void +backend_init (GtkXText *xtext) +{ + if (xtext->layout == NULL) + { + xtext->layout = gtk_widget_create_pango_layout (GTK_WIDGET (xtext), 0); + if (xtext->font) + pango_layout_set_font_description (xtext->layout, xtext->font->font); + } +} + +static void +backend_deinit (GtkXText *xtext) +{ + if (xtext->layout) + { + g_object_unref (xtext->layout); + xtext->layout = NULL; + } +} + +static PangoFontDescription * +backend_font_open_real (char *name) +{ + PangoFontDescription *font; + + font = pango_font_description_from_string (name); + if (font && pango_font_description_get_size (font) == 0) + { + pango_font_description_free (font); + font = pango_font_description_from_string ("sans 11"); + } + if (!font) + font = pango_font_description_from_string ("sans 11"); + + return font; +} + +static void +backend_font_open (GtkXText *xtext, char *name) +{ + PangoLanguage *lang; + PangoContext *context; + PangoFontMetrics *metrics; + + xtext->font = &xtext->pango_font; + xtext->font->font = backend_font_open_real (name); + if (!xtext->font->font) + { + xtext->font = NULL; + return; + } +#ifdef ITALIC + xtext->font->ifont = backend_font_open_real (name); + pango_font_description_set_style (xtext->font->ifont, PANGO_STYLE_ITALIC); +#endif + + backend_init (xtext); + pango_layout_set_font_description (xtext->layout, xtext->font->font); + + /* vte does it this way */ + context = gtk_widget_get_pango_context (GTK_WIDGET (xtext)); + lang = pango_context_get_language (context); + metrics = pango_context_get_metrics (context, xtext->font->font, lang); + xtext->font->ascent = pango_font_metrics_get_ascent (metrics) / PANGO_SCALE; + xtext->font->descent = pango_font_metrics_get_descent (metrics) / PANGO_SCALE; + pango_font_metrics_unref (metrics); +} + +static int +backend_get_text_width (GtkXText *xtext, guchar *str, int len, int is_mb) +{ + int width; + + if (!is_mb) + return gtk_xtext_text_width_8bit (xtext, str, len); + + if (*str == 0) + return 0; + + pango_layout_set_text (xtext->layout, str, len); + pango_layout_get_pixel_size (xtext->layout, &width, NULL); + + return width; +} + +inline static int +backend_get_char_width (GtkXText *xtext, unsigned char *str, int *mbl_ret) +{ + int width; + + if (*str < 128) + { + *mbl_ret = 1; + return xtext->fontwidth[*str]; + } + + *mbl_ret = charlen (str); + pango_layout_set_text (xtext->layout, str, *mbl_ret); + pango_layout_get_pixel_size (xtext->layout, &width, NULL); + + return width; +} + +/* simplified version of gdk_draw_layout_line_with_colors() */ + +static void +xtext_draw_layout_line (GdkDrawable *drawable, + GdkGC *gc, + gint x, + gint y, + PangoLayoutLine *line) +{ + GSList *tmp_list = line->runs; + PangoRectangle logical_rect; + gint x_off = 0; + + while (tmp_list) + { + PangoLayoutRun *run = tmp_list->data; + + pango_glyph_string_extents (run->glyphs, run->item->analysis.font, + NULL, &logical_rect); + + gdk_draw_glyphs (drawable, gc, run->item->analysis.font, + x + x_off / PANGO_SCALE, y, run->glyphs); + + x_off += logical_rect.width; + tmp_list = tmp_list->next; + } +} + +static void +backend_draw_text (GtkXText *xtext, int dofill, GdkGC *gc, int x, int y, + char *str, int len, int str_width, int is_mb) +{ + GdkGCValues val; + GdkColor col; + PangoLayoutLine *line; + +#ifdef ITALIC + if (xtext->italics) + pango_layout_set_font_description (xtext->layout, xtext->font->ifont); +#endif + + pango_layout_set_text (xtext->layout, str, len); + + if (dofill) + { +#ifdef WIN32 + if (xtext->transparent && !xtext->backcolor) + win32_draw_bg (xtext, x, y - xtext->font->ascent, str_width, + xtext->fontsize); + else +#endif + { + gdk_gc_get_values (gc, &val); + col.pixel = val.background.pixel; + gdk_gc_set_foreground (gc, &col); + gdk_draw_rectangle (xtext->draw_buf, gc, 1, x, y - + xtext->font->ascent, str_width, xtext->fontsize); + col.pixel = val.foreground.pixel; + gdk_gc_set_foreground (gc, &col); + } + } + + line = pango_layout_get_lines (xtext->layout)->data; + + xtext_draw_layout_line (xtext->draw_buf, gc, x, y, line); + + if (xtext->overdraw) + xtext_draw_layout_line (xtext->draw_buf, gc, x, y, line); + + if (xtext->bold) + xtext_draw_layout_line (xtext->draw_buf, gc, x + 1, y, line); + +#ifdef ITALIC + if (xtext->italics) + pango_layout_set_font_description (xtext->layout, xtext->font->font); +#endif +} + +/*static void +backend_set_clip (GtkXText *xtext, GdkRectangle *area) +{ + gdk_gc_set_clip_rectangle (xtext->fgc, area); + gdk_gc_set_clip_rectangle (xtext->bgc, area); +} + +static void +backend_clear_clip (GtkXText *xtext) +{ + gdk_gc_set_clip_rectangle (xtext->fgc, NULL); + gdk_gc_set_clip_rectangle (xtext->bgc, NULL); +}*/ + +#endif /* !USE_PANGO */ + +static void +xtext_set_fg (GtkXText *xtext, GdkGC *gc, int index) +{ + GdkColor col; + + col.pixel = xtext->palette[index]; + gdk_gc_set_foreground (gc, &col); + +#ifdef USE_XFT + if (gc == xtext->fgc) + xtext->xft_fg = &xtext->color[index]; + else + xtext->xft_bg = &xtext->color[index]; +#endif +} + +#ifdef USE_XFT + +#define xtext_set_bg(xt,gc,index) xt->xft_bg = &xt->color[index] + +#else + +static void +xtext_set_bg (GtkXText *xtext, GdkGC *gc, int index) +{ + GdkColor col; + + col.pixel = xtext->palette[index]; + gdk_gc_set_background (gc, &col); +} + +#endif + +static void +gtk_xtext_init (GtkXText * xtext) +{ + xtext->pixmap = NULL; + xtext->io_tag = 0; + xtext->add_io_tag = 0; + xtext->scroll_tag = 0; + xtext->max_lines = 0; + xtext->col_back = XTEXT_BG; + xtext->col_fore = XTEXT_FG; + xtext->nc = 0; + xtext->pixel_offset = 0; + xtext->bold = FALSE; + xtext->underline = FALSE; + xtext->italics = FALSE; + xtext->hidden = FALSE; + xtext->font = NULL; +#ifdef USE_XFT + xtext->xftdraw = NULL; +#else + xtext->layout = NULL; +#endif + xtext->jump_out_offset = 0; + xtext->jump_in_offset = 0; + xtext->ts_x = 0; + xtext->ts_y = 0; + xtext->clip_x = 0; + xtext->clip_x2 = 1000000; + xtext->clip_y = 0; + xtext->clip_y2 = 1000000; + xtext->error_function = NULL; + xtext->urlcheck_function = NULL; + xtext->color_paste = FALSE; + xtext->skip_border_fills = FALSE; + xtext->skip_stamp = FALSE; + xtext->render_hilights_only = FALSE; + xtext->un_hilight = FALSE; + xtext->recycle = FALSE; + xtext->dont_render = FALSE; + xtext->dont_render2 = FALSE; + xtext->overdraw = FALSE; + xtext->tint_red = xtext->tint_green = xtext->tint_blue = TINT_VALUE; + + xtext->adj = (GtkAdjustment *) gtk_adjustment_new (0, 0, 1, 1, 1, 1); + g_object_ref (G_OBJECT (xtext->adj)); + g_object_ref_sink (G_OBJECT (xtext->adj)); + g_object_unref (G_OBJECT (xtext->adj)); + + xtext->vc_signal_tag = g_signal_connect (G_OBJECT (xtext->adj), + "value_changed", G_CALLBACK (gtk_xtext_adjustment_changed), xtext); + { + static const GtkTargetEntry targets[] = { + { "UTF8_STRING", 0, TARGET_UTF8_STRING }, + { "STRING", 0, TARGET_STRING }, + { "TEXT", 0, TARGET_TEXT }, + { "COMPOUND_TEXT", 0, TARGET_COMPOUND_TEXT } + }; + static const gint n_targets = sizeof (targets) / sizeof (targets[0]); + + gtk_selection_add_targets (GTK_WIDGET (xtext), GDK_SELECTION_PRIMARY, + targets, n_targets); + } + + if (getenv ("XCHAT_OVERDRAW")) + xtext->overdraw = TRUE; +} + +static void +gtk_xtext_adjustment_set (xtext_buffer *buf, int fire_signal) +{ + GtkAdjustment *adj = buf->xtext->adj; + + if (buf->xtext->buffer == buf) + { + adj->lower = 0; + adj->upper = buf->num_lines; + + if (adj->upper == 0) + adj->upper = 1; + + adj->page_size = + (GTK_WIDGET (buf->xtext)->allocation.height - + buf->xtext->font->descent) / buf->xtext->fontsize; + adj->page_increment = adj->page_size; + + if (adj->value > adj->upper - adj->page_size) + adj->value = adj->upper - adj->page_size; + + if (adj->value < 0) + adj->value = 0; + + if (fire_signal) + gtk_adjustment_changed (adj); + } +} + +static gint +gtk_xtext_adjustment_timeout (GtkXText * xtext) +{ + gtk_xtext_render_page (xtext); + xtext->io_tag = 0; + return 0; +} + +static void +gtk_xtext_adjustment_changed (GtkAdjustment * adj, GtkXText * xtext) +{ +#ifdef SMOOTH_SCROLL + if (xtext->buffer->old_value != xtext->adj->value) +#else + if ((int) xtext->buffer->old_value != (int) xtext->adj->value) +#endif + { + if (xtext->adj->value >= xtext->adj->upper - xtext->adj->page_size) + xtext->buffer->scrollbar_down = TRUE; + else + xtext->buffer->scrollbar_down = FALSE; + + if (xtext->adj->value + 1 == xtext->buffer->old_value || + xtext->adj->value - 1 == xtext->buffer->old_value) /* clicked an arrow? */ + { + if (xtext->io_tag) + { + g_source_remove (xtext->io_tag); + xtext->io_tag = 0; + } + gtk_xtext_render_page (xtext); + } else + { + if (!xtext->io_tag) + xtext->io_tag = g_timeout_add (REFRESH_TIMEOUT, + (GSourceFunc) + gtk_xtext_adjustment_timeout, + xtext); + } + } + xtext->buffer->old_value = adj->value; +} + +GtkWidget * +gtk_xtext_new (GdkColor palette[], int separator) +{ + GtkXText *xtext; + + xtext = g_object_new (gtk_xtext_get_type (), NULL); + xtext->separator = separator; + xtext->wordwrap = TRUE; + xtext->buffer = gtk_xtext_buffer_new (xtext); + xtext->orig_buffer = xtext->buffer; + + gtk_widget_set_double_buffered (GTK_WIDGET (xtext), FALSE); + gtk_xtext_set_palette (xtext, palette); + + return GTK_WIDGET (xtext); +} + +static void +gtk_xtext_destroy (GtkObject * object) +{ + GtkXText *xtext = GTK_XTEXT (object); + + if (xtext->add_io_tag) + { + g_source_remove (xtext->add_io_tag); + xtext->add_io_tag = 0; + } + + if (xtext->scroll_tag) + { + g_source_remove (xtext->scroll_tag); + xtext->scroll_tag = 0; + } + + if (xtext->io_tag) + { + g_source_remove (xtext->io_tag); + xtext->io_tag = 0; + } + + if (xtext->pixmap) + { +#if defined(USE_XLIB) || defined(WIN32) + if (xtext->transparent) + gtk_xtext_free_trans (xtext); + else +#endif + g_object_unref (xtext->pixmap); + xtext->pixmap = NULL; + } + + if (xtext->font) + { + backend_font_close (xtext); + xtext->font = NULL; + } + + if (xtext->adj) + { + g_signal_handlers_disconnect_matched (G_OBJECT (xtext->adj), + G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, xtext); + /* gtk_signal_disconnect_by_data (GTK_OBJECT (xtext->adj), xtext);*/ + g_object_unref (G_OBJECT (xtext->adj)); + xtext->adj = NULL; + } + + if (xtext->bgc) + { + g_object_unref (xtext->bgc); + xtext->bgc = NULL; + } + + if (xtext->fgc) + { + g_object_unref (xtext->fgc); + xtext->fgc = NULL; + } + + if (xtext->light_gc) + { + g_object_unref (xtext->light_gc); + xtext->light_gc = NULL; + } + + if (xtext->dark_gc) + { + g_object_unref (xtext->dark_gc); + xtext->dark_gc = NULL; + } + + if (xtext->thin_gc) + { + g_object_unref (xtext->thin_gc); + xtext->thin_gc = NULL; + } + + if (xtext->marker_gc) + { + g_object_unref (xtext->marker_gc); + xtext->marker_gc = NULL; + } + + if (xtext->hand_cursor) + { + gdk_cursor_unref (xtext->hand_cursor); + xtext->hand_cursor = NULL; + } + + if (xtext->resize_cursor) + { + gdk_cursor_unref (xtext->resize_cursor); + xtext->resize_cursor = NULL; + } + + if (xtext->orig_buffer) + { + gtk_xtext_buffer_free (xtext->orig_buffer); + xtext->orig_buffer = NULL; + } + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (*GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + +static void +gtk_xtext_unrealize (GtkWidget * widget) +{ + backend_deinit (GTK_XTEXT (widget)); + + /* if there are still events in the queue, this'll avoid segfault */ + gdk_window_set_user_data (widget->window, NULL); + + if (parent_class->unrealize) + (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget); +} + +static void +gtk_xtext_realize (GtkWidget * widget) +{ + GtkXText *xtext; + GdkWindowAttr attributes; + GdkGCValues val; + GdkColor col; + GdkColormap *cmap; + + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); + xtext = GTK_XTEXT (widget); + + attributes.x = widget->allocation.x; + attributes.y = widget->allocation.y; + attributes.width = widget->allocation.width; + attributes.height = widget->allocation.height; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.window_type = GDK_WINDOW_CHILD; + attributes.event_mask = gtk_widget_get_events (widget) | + GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK +#ifdef MOTION_MONITOR + | GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK; +#else + | GDK_POINTER_MOTION_MASK; +#endif + + cmap = gtk_widget_get_colormap (widget); + attributes.colormap = cmap; + attributes.visual = gtk_widget_get_visual (widget); + + widget->window = gdk_window_new (widget->parent->window, &attributes, + GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | + GDK_WA_COLORMAP); + + gdk_window_set_user_data (widget->window, widget); + + xtext->depth = gdk_drawable_get_visual (widget->window)->depth; + + val.subwindow_mode = GDK_INCLUDE_INFERIORS; + val.graphics_exposures = 0; + + xtext->bgc = gdk_gc_new_with_values (widget->window, &val, + GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); + xtext->fgc = gdk_gc_new_with_values (widget->window, &val, + GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); + xtext->light_gc = gdk_gc_new_with_values (widget->window, &val, + GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); + xtext->dark_gc = gdk_gc_new_with_values (widget->window, &val, + GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); + xtext->thin_gc = gdk_gc_new_with_values (widget->window, &val, + GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); + xtext->marker_gc = gdk_gc_new_with_values (widget->window, &val, + GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); + + /* for the separator bar (light) */ + col.red = 0xffff; col.green = 0xffff; col.blue = 0xffff; + gdk_colormap_alloc_color (cmap, &col, FALSE, TRUE); + gdk_gc_set_foreground (xtext->light_gc, &col); + + /* for the separator bar (dark) */ + col.red = 0x1111; col.green = 0x1111; col.blue = 0x1111; + gdk_colormap_alloc_color (cmap, &col, FALSE, TRUE); + gdk_gc_set_foreground (xtext->dark_gc, &col); + + /* for the separator bar (thinline) */ + col.red = 0x8e38; col.green = 0x8e38; col.blue = 0x9f38; + gdk_colormap_alloc_color (cmap, &col, FALSE, TRUE); + gdk_gc_set_foreground (xtext->thin_gc, &col); + + /* for the marker bar (marker) */ + col.pixel = xtext->palette[XTEXT_MARKER]; + gdk_gc_set_foreground (xtext->marker_gc, &col); + + xtext_set_fg (xtext, xtext->fgc, XTEXT_FG); + xtext_set_bg (xtext, xtext->fgc, XTEXT_BG); + xtext_set_fg (xtext, xtext->bgc, XTEXT_BG); + + /* draw directly to window */ + xtext->draw_buf = widget->window; + +#if defined(USE_XLIB) || defined(WIN32) + if (xtext->transparent) + { + gtk_xtext_load_trans (xtext); + } else +#endif + if (xtext->pixmap) + { + gdk_gc_set_tile (xtext->bgc, xtext->pixmap); + gdk_gc_set_ts_origin (xtext->bgc, 0, 0); + xtext->ts_x = xtext->ts_y = 0; + gdk_gc_set_fill (xtext->bgc, GDK_TILED); + } + +#if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION == 0) + xtext->hand_cursor = gdk_cursor_new (GDK_HAND1); + xtext->resize_cursor = gdk_cursor_new (GDK_LEFT_SIDE); +#else + xtext->hand_cursor = gdk_cursor_new_for_display (gdk_drawable_get_display (widget->window), GDK_HAND1); + xtext->resize_cursor = gdk_cursor_new_for_display (gdk_drawable_get_display (widget->window), GDK_LEFT_SIDE); +#endif + + gdk_window_set_back_pixmap (widget->window, NULL, FALSE); + widget->style = gtk_style_attach (widget->style, widget->window); + + backend_init (xtext); +} + +static void +gtk_xtext_size_request (GtkWidget * widget, GtkRequisition * requisition) +{ + requisition->width = 200; + requisition->height = 90; +} + +static void +gtk_xtext_size_allocate (GtkWidget * widget, GtkAllocation * allocation) +{ + GtkXText *xtext = GTK_XTEXT (widget); + int height_only = FALSE; + int do_trans = TRUE; + + if (allocation->width == xtext->buffer->window_width) + height_only = TRUE; + + if (allocation->x == widget->allocation.x && + allocation->y == widget->allocation.y && xtext->avoid_trans) + do_trans = FALSE; + + xtext->avoid_trans = FALSE; + + widget->allocation = *allocation; + if (GTK_WIDGET_REALIZED (widget)) + { + xtext->buffer->window_width = allocation->width; + xtext->buffer->window_height = allocation->height; + + gdk_window_move_resize (widget->window, allocation->x, allocation->y, + allocation->width, allocation->height); + dontscroll (xtext->buffer); /* force scrolling off */ + if (!height_only) + gtk_xtext_calc_lines (xtext->buffer, FALSE); + else + { + xtext->buffer->pagetop_ent = NULL; + gtk_xtext_adjustment_set (xtext->buffer, FALSE); + } +#if defined(USE_XLIB) || defined(WIN32) + if (do_trans && xtext->transparent && xtext->shaded) + { + gtk_xtext_free_trans (xtext); + gtk_xtext_load_trans (xtext); + } +#endif + if (xtext->buffer->scrollbar_down) + gtk_adjustment_set_value (xtext->adj, xtext->adj->upper - + xtext->adj->page_size); + } +} + +static void +gtk_xtext_selection_clear_full (xtext_buffer *buf) +{ + textentry *ent = buf->text_first; + while (ent) + { + ent->mark_start = -1; + ent->mark_end = -1; + ent = ent->next; + } +} + +static int +gtk_xtext_selection_clear (xtext_buffer *buf) +{ + textentry *ent; + int ret = 0; + + ent = buf->last_ent_start; + while (ent) + { + if (ent->mark_start != -1) + ret = 1; + ent->mark_start = -1; + ent->mark_end = -1; + if (ent == buf->last_ent_end) + break; + ent = ent->next; + } + + return ret; +} + +static int +find_x (GtkXText *xtext, textentry *ent, unsigned char *text, int x, int indent) +{ + int xx = indent; + int i = 0; + int rcol = 0, bgcol = 0; + int hidden = FALSE; + unsigned char *orig = text; + int mbl; + int char_width; + + while (*text) + { + mbl = 1; + if (rcol > 0 && (isdigit (*text) || (*text == ',' && isdigit (text[1]) && !bgcol))) + { + if (text[1] != ',') rcol--; + if (*text == ',') + { + rcol = 2; + bgcol = 1; + } + text++; + } else + { + rcol = bgcol = 0; + switch (*text) + { + case ATTR_COLOR: + rcol = 2; + case ATTR_BEEP: + case ATTR_RESET: + case ATTR_REVERSE: + case ATTR_BOLD: + case ATTR_UNDERLINE: + case ATTR_ITALICS: + text++; + break; + case ATTR_HIDDEN: + if (xtext->ignore_hidden) + goto def; + hidden = !hidden; + text++; + break; + default: + def: + char_width = backend_get_char_width (xtext, text, &mbl); + if (!hidden) xx += char_width; + text += mbl; + if (xx >= x) + return i + (orig - ent->str); + } + } + + i += mbl; + if (text - orig >= ent->str_len) + return ent->str_len; + } + + return ent->str_len; +} + +static int +gtk_xtext_find_x (GtkXText * xtext, int x, textentry * ent, int subline, + int line, int *out_of_bounds) +{ + int indent; + unsigned char *str; + + if (subline < 1) + indent = ent->indent; + else + indent = xtext->buffer->indent; + + if (line > xtext->adj->page_size || line < 0) + return 0; + + if (xtext->buffer->grid_dirty || line > 255) + { + str = ent->str + gtk_xtext_find_subline (xtext, ent, subline); + if (str >= ent->str + ent->str_len) + return 0; + } else + { + if (xtext->buffer->grid_offset[line] > ent->str_len) + return 0; + str = ent->str + xtext->buffer->grid_offset[line]; + } + + if (x < indent) + { + *out_of_bounds = 1; + return (str - ent->str); + } + + *out_of_bounds = 0; + + return find_x (xtext, ent, str, x, indent); +} + +static textentry * +gtk_xtext_find_char (GtkXText * xtext, int x, int y, int *off, + int *out_of_bounds) +{ + textentry *ent; + int line; + int subline; + + line = (y + xtext->pixel_offset) / xtext->fontsize; + ent = gtk_xtext_nth (xtext, line + (int)xtext->adj->value, &subline); + if (!ent) + return 0; + + if (off) + *off = gtk_xtext_find_x (xtext, x, ent, subline, line, out_of_bounds); + + return ent; +} + +static void +gtk_xtext_draw_sep (GtkXText * xtext, int y) +{ + int x, height; + GdkGC *light, *dark; + + if (y == -1) + { + y = 0; + height = GTK_WIDGET (xtext)->allocation.height; + } else + { + height = xtext->fontsize; + } + + /* draw the separator line */ + if (xtext->separator && xtext->buffer->indent) + { + light = xtext->light_gc; + dark = xtext->dark_gc; + + x = xtext->buffer->indent - ((xtext->space_width + 1) / 2); + if (x < 1) + return; + + if (xtext->thinline) + { + if (xtext->moving_separator) + gdk_draw_line (xtext->draw_buf, light, x, y, x, y + height); + else + gdk_draw_line (xtext->draw_buf, xtext->thin_gc, x, y, x, y + height); + } else + { + if (xtext->moving_separator) + { + gdk_draw_line (xtext->draw_buf, light, x - 1, y, x - 1, y + height); + gdk_draw_line (xtext->draw_buf, dark, x, y, x, y + height); + } else + { + gdk_draw_line (xtext->draw_buf, dark, x - 1, y, x - 1, y + height); + gdk_draw_line (xtext->draw_buf, light, x, y, x, y + height); + } + } + } +} + +static void +gtk_xtext_draw_marker (GtkXText * xtext, textentry * ent, int y) +{ + int x, width, render_y; + + if (!xtext->marker) return; + + if (xtext->buffer->marker_pos == ent) + { + render_y = y + xtext->font->descent; + } + else if (xtext->buffer->marker_pos == ent->next && ent->next != NULL) + { + render_y = y + xtext->font->descent + xtext->fontsize * ent->lines_taken; + } + else return; + + x = 0; + width = GTK_WIDGET (xtext)->allocation.width; + + gdk_draw_line (xtext->draw_buf, xtext->marker_gc, x, render_y, x + width, render_y); + +#if GTK_CHECK_VERSION(2,4,0) + if (gtk_window_has_toplevel_focus (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (xtext))))) +#else + if (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (xtext)))->has_focus) +#endif + { + xtext->buffer->marker_seen = TRUE; + } +} + +#ifdef USE_SHM +static int +have_shm_pixmaps(Display *dpy) +{ + int major, minor; + static int checked = 0; + static int have = FALSE; + + if (!checked) + { + XShmQueryVersion (dpy, &major, &minor, &have); + checked = 1; + } + + return have; +} +#endif + +static void +gtk_xtext_paint (GtkWidget *widget, GdkRectangle *area) +{ + GtkXText *xtext = GTK_XTEXT (widget); + textentry *ent_start, *ent_end; + int x, y; + +#if defined(USE_XLIB) || defined(WIN32) + if (xtext->transparent) + { + gdk_window_get_origin (widget->window, &x, &y); + /* update transparency only if it moved */ + if (xtext->last_win_x != x || xtext->last_win_y != y) + { + xtext->last_win_x = x; + xtext->last_win_y = y; +#ifndef WIN32 +#ifdef USE_SHM + if (xtext->shaded && !have_shm_pixmaps(GDK_WINDOW_XDISPLAY (xtext->draw_buf))) +#else + if (xtext->shaded) +#endif + { + xtext->recycle = TRUE; + gtk_xtext_load_trans (xtext); + xtext->recycle = FALSE; + } else +#endif + { + gtk_xtext_free_trans (xtext); + gtk_xtext_load_trans (xtext); + } + } + } +#endif + + if (area->x == 0 && area->y == 0 && + area->height == widget->allocation.height && + area->width == widget->allocation.width) + { + dontscroll (xtext->buffer); /* force scrolling off */ + gtk_xtext_render_page (xtext); + return; + } + + ent_start = gtk_xtext_find_char (xtext, area->x, area->y, NULL, NULL); + if (!ent_start) + { + xtext_draw_bg (xtext, area->x, area->y, area->width, area->height); + goto xit; + } + ent_end = gtk_xtext_find_char (xtext, area->x + area->width, + area->y + area->height, NULL, NULL); + if (!ent_end) + ent_end = xtext->buffer->text_last; + + /* can't set a clip here, because fgc/bgc are used to draw the DB too */ +/* backend_set_clip (xtext, area);*/ + xtext->clip_x = area->x; + xtext->clip_x2 = area->x + area->width; + xtext->clip_y = area->y; + xtext->clip_y2 = area->y + area->height; + + /* y is the last pixel y location it rendered text at */ + y = gtk_xtext_render_ents (xtext, ent_start, ent_end); + + if (y && y < widget->allocation.height && !ent_end->next) + { + GdkRectangle rect; + + rect.x = 0; + rect.y = y; + rect.width = widget->allocation.width; + rect.height = widget->allocation.height - y; + + /* fill any space below the last line that also intersects with + the exposure rectangle */ + if (gdk_rectangle_intersect (area, &rect, &rect)) + { + xtext_draw_bg (xtext, rect.x, rect.y, rect.width, rect.height); + } + } + + /*backend_clear_clip (xtext);*/ + xtext->clip_x = 0; + xtext->clip_x2 = 1000000; + xtext->clip_y = 0; + xtext->clip_y2 = 1000000; + +xit: + x = xtext->buffer->indent - ((xtext->space_width + 1) / 2); + if (area->x <= x) + gtk_xtext_draw_sep (xtext, -1); +} + +static gboolean +gtk_xtext_expose (GtkWidget * widget, GdkEventExpose * event) +{ + gtk_xtext_paint (widget, &event->area); + return FALSE; +} + +/* render a selection that has extended or contracted upward */ + +static void +gtk_xtext_selection_up (GtkXText *xtext, textentry *start, textentry *end, + int start_offset) +{ + /* render all the complete lines */ + if (start->next == end) + gtk_xtext_render_ents (xtext, end, NULL); + else + gtk_xtext_render_ents (xtext, start->next, end); + + /* now the incomplete upper line */ + if (start == xtext->buffer->last_ent_start) + xtext->jump_in_offset = xtext->buffer->last_offset_start; + else + xtext->jump_in_offset = start_offset; + gtk_xtext_render_ents (xtext, start, NULL); + xtext->jump_in_offset = 0; +} + +/* render a selection that has extended or contracted downward */ + +static void +gtk_xtext_selection_down (GtkXText *xtext, textentry *start, textentry *end, + int end_offset) +{ + /* render all the complete lines */ + if (end->prev == start) + gtk_xtext_render_ents (xtext, start, NULL); + else + gtk_xtext_render_ents (xtext, start, end->prev); + + /* now the incomplete bottom line */ + if (end == xtext->buffer->last_ent_end) + xtext->jump_out_offset = xtext->buffer->last_offset_end; + else + xtext->jump_out_offset = end_offset; + gtk_xtext_render_ents (xtext, end, NULL); + xtext->jump_out_offset = 0; +} + +static void +gtk_xtext_selection_render (GtkXText *xtext, + textentry *start_ent, int start_offset, + textentry *end_ent, int end_offset) +{ + textentry *ent; + int start, end; + + xtext->skip_border_fills = TRUE; + xtext->skip_stamp = TRUE; + + /* force an optimized render if there was no previous selection */ + if (xtext->buffer->last_ent_start == NULL && start_ent == end_ent) + { + xtext->buffer->last_offset_start = start_offset; + xtext->buffer->last_offset_end = end_offset; + goto lamejump; + } + + /* mark changed within 1 ent only? */ + if (xtext->buffer->last_ent_start == start_ent && + xtext->buffer->last_ent_end == end_ent) + { + /* when only 1 end of the selection is changed, we can really + save on rendering */ + if (xtext->buffer->last_offset_start == start_offset || + xtext->buffer->last_offset_end == end_offset) + { +lamejump: + ent = end_ent; + /* figure out where to start and end the rendering */ + if (end_offset > xtext->buffer->last_offset_end) + { + end = end_offset; + start = xtext->buffer->last_offset_end; + } else if (end_offset < xtext->buffer->last_offset_end) + { + end = xtext->buffer->last_offset_end; + start = end_offset; + } else if (start_offset < xtext->buffer->last_offset_start) + { + end = xtext->buffer->last_offset_start; + start = start_offset; + ent = start_ent; + } else if (start_offset > xtext->buffer->last_offset_start) + { + end = start_offset; + start = xtext->buffer->last_offset_start; + ent = start_ent; + } else + { /* WORD selects end up here */ + end = end_offset; + start = start_offset; + } + } else + { + /* LINE selects end up here */ + /* so which ent actually changed? */ + ent = start_ent; + if (xtext->buffer->last_offset_start == start_offset) + ent = end_ent; + + end = MAX (xtext->buffer->last_offset_end, end_offset); + start = MIN (xtext->buffer->last_offset_start, start_offset); + } + + xtext->jump_out_offset = end; + xtext->jump_in_offset = start; + gtk_xtext_render_ents (xtext, ent, NULL); + xtext->jump_out_offset = 0; + xtext->jump_in_offset = 0; + } + /* marking downward? */ + else if (xtext->buffer->last_ent_start == start_ent && + xtext->buffer->last_offset_start == start_offset) + { + /* find the range that covers both old and new selection */ + ent = start_ent; + while (ent) + { + if (ent == xtext->buffer->last_ent_end) + { + gtk_xtext_selection_down (xtext, ent, end_ent, end_offset); + /*gtk_xtext_render_ents (xtext, ent, end_ent);*/ + break; + } + if (ent == end_ent) + { + gtk_xtext_selection_down (xtext, ent, xtext->buffer->last_ent_end, end_offset); + /*gtk_xtext_render_ents (xtext, ent, xtext->buffer->last_ent_end);*/ + break; + } + ent = ent->next; + } + } + /* marking upward? */ + else if (xtext->buffer->last_ent_end == end_ent && + xtext->buffer->last_offset_end == end_offset) + { + ent = end_ent; + while (ent) + { + if (ent == start_ent) + { + gtk_xtext_selection_up (xtext, xtext->buffer->last_ent_start, ent, start_offset); + /*gtk_xtext_render_ents (xtext, xtext->buffer->last_ent_start, ent);*/ + break; + } + if (ent == xtext->buffer->last_ent_start) + { + gtk_xtext_selection_up (xtext, start_ent, ent, start_offset); + /*gtk_xtext_render_ents (xtext, start_ent, ent);*/ + break; + } + ent = ent->prev; + } + } + else /* cross-over mark (stretched or shrunk at both ends) */ + { + /* unrender the old mark */ + gtk_xtext_render_ents (xtext, xtext->buffer->last_ent_start, xtext->buffer->last_ent_end); + /* now render the new mark, but skip overlaps */ + if (start_ent == xtext->buffer->last_ent_start) + { + /* if the new mark is a sub-set of the old, do nothing */ + if (start_ent != end_ent) + gtk_xtext_render_ents (xtext, start_ent->next, end_ent); + } else if (end_ent == xtext->buffer->last_ent_end) + { + /* if the new mark is a sub-set of the old, do nothing */ + if (start_ent != end_ent) + gtk_xtext_render_ents (xtext, start_ent, end_ent->prev); + } else + gtk_xtext_render_ents (xtext, start_ent, end_ent); + } + + xtext->buffer->last_ent_start = start_ent; + xtext->buffer->last_ent_end = end_ent; + xtext->buffer->last_offset_start = start_offset; + xtext->buffer->last_offset_end = end_offset; + + xtext->skip_border_fills = FALSE; + xtext->skip_stamp = FALSE; +} + +static void +gtk_xtext_selection_draw (GtkXText * xtext, GdkEventMotion * event, gboolean render) +{ + textentry *ent; + textentry *ent_end; + textentry *ent_start; + int offset_start; + int offset_end; + int low_x; + int low_y; + int high_x; + int high_y; + int tmp; + + if (xtext->select_start_y > xtext->select_end_y) + { + low_x = xtext->select_end_x; + low_y = xtext->select_end_y; + high_x = xtext->select_start_x; + high_y = xtext->select_start_y; + } else + { + low_x = xtext->select_start_x; + low_y = xtext->select_start_y; + high_x = xtext->select_end_x; + high_y = xtext->select_end_y; + } + + ent_start = gtk_xtext_find_char (xtext, low_x, low_y, &offset_start, &tmp); + if (!ent_start) + { + if (xtext->adj->value != xtext->buffer->old_value) + gtk_xtext_render_page (xtext); + return; + } + + ent_end = gtk_xtext_find_char (xtext, high_x, high_y, &offset_end, &tmp); + if (!ent_end) + { + ent_end = xtext->buffer->text_last; + if (!ent_end) + { + if (xtext->adj->value != xtext->buffer->old_value) + gtk_xtext_render_page (xtext); + return; + } + offset_end = ent_end->str_len; + } + + /* marking less than a complete line? */ + /* make sure "start" is smaller than "end" (swap them if need be) */ + if (ent_start == ent_end && offset_start > offset_end) + { + tmp = offset_start; + offset_start = offset_end; + offset_end = tmp; + } + + /* has the selection changed? Dont render unless necessary */ + if (xtext->buffer->last_ent_start == ent_start && + xtext->buffer->last_ent_end == ent_end && + xtext->buffer->last_offset_start == offset_start && + xtext->buffer->last_offset_end == offset_end) + return; + + /* set all the old mark_ fields to -1 */ + gtk_xtext_selection_clear (xtext->buffer); + + ent_start->mark_start = offset_start; + ent_start->mark_end = offset_end; + + if (ent_start != ent_end) + { + ent_start->mark_end = ent_start->str_len; + if (offset_end != 0) + { + ent_end->mark_start = 0; + ent_end->mark_end = offset_end; + } + + /* set all the mark_ fields of the ents within the selection */ + ent = ent_start->next; + while (ent && ent != ent_end) + { + ent->mark_start = 0; + ent->mark_end = ent->str_len; + ent = ent->next; + } + } + + if (render) + gtk_xtext_selection_render (xtext, ent_start, offset_start, ent_end, offset_end); +} + +static gint +gtk_xtext_scrolldown_timeout (GtkXText * xtext) +{ + int p_y, win_height; + + gdk_window_get_pointer (GTK_WIDGET (xtext)->window, 0, &p_y, 0); + gdk_drawable_get_size (GTK_WIDGET (xtext)->window, 0, &win_height); + + if (p_y > win_height && + xtext->adj->value < (xtext->adj->upper - xtext->adj->page_size)) + { + xtext->adj->value++; + gtk_adjustment_changed (xtext->adj); + gtk_xtext_render_page (xtext); + return 1; + } + + xtext->scroll_tag = 0; + return 0; +} + +static gint +gtk_xtext_scrollup_timeout (GtkXText * xtext) +{ + int p_y; + + gdk_window_get_pointer (GTK_WIDGET (xtext)->window, 0, &p_y, 0); + + if (p_y < 0 && xtext->adj->value > 0.0) + { + xtext->adj->value--; + gtk_adjustment_changed (xtext->adj); + gtk_xtext_render_page (xtext); + return 1; + } + + xtext->scroll_tag = 0; + return 0; +} + +static void +gtk_xtext_selection_update (GtkXText * xtext, GdkEventMotion * event, int p_y, gboolean render) +{ + int win_height; + int moved; + + gdk_drawable_get_size (GTK_WIDGET (xtext)->window, 0, &win_height); + + /* selecting past top of window, scroll up! */ + if (p_y < 0 && xtext->adj->value >= 0) + { + if (!xtext->scroll_tag) + xtext->scroll_tag = g_timeout_add (100, + (GSourceFunc) + gtk_xtext_scrollup_timeout, + xtext); + return; + } + + /* selecting past bottom of window, scroll down! */ + if (p_y > win_height && + xtext->adj->value < (xtext->adj->upper - xtext->adj->page_size)) + { + if (!xtext->scroll_tag) + xtext->scroll_tag = g_timeout_add (100, + (GSourceFunc) + gtk_xtext_scrolldown_timeout, + xtext); + return; + } + + moved = (int)xtext->adj->value - xtext->select_start_adj; + xtext->select_start_y -= (moved * xtext->fontsize); + xtext->select_start_adj = xtext->adj->value; + gtk_xtext_selection_draw (xtext, event, render); +} + +static char * +gtk_xtext_get_word (GtkXText * xtext, int x, int y, textentry ** ret_ent, + int *ret_off, int *ret_len) +{ + textentry *ent; + int offset; + unsigned char *str; + unsigned char *word; + int len; + int out_of_bounds = 0; + + ent = gtk_xtext_find_char (xtext, x, y, &offset, &out_of_bounds); + if (!ent) + return 0; + + if (out_of_bounds) + return 0; + + if (offset == ent->str_len) + return 0; + + if (offset < 1) + return 0; + + /*offset--;*/ /* FIXME: not all chars are 1 byte */ + + str = ent->str + offset; + + while (!is_del (*str) && str != ent->str) + str--; + word = str + 1; + + len = 0; + str = word; + while (!is_del (*str) && len != ent->str_len) + { + str++; + len++; + } + + if (len > 0 && word[len-1]=='.') + { + len--; + str--; + } + + if (ret_ent) + *ret_ent = ent; + if (ret_off) + *ret_off = word - ent->str; + if (ret_len) + *ret_len = str - word; + + return gtk_xtext_strip_color (word, len, xtext->scratch_buffer, NULL, NULL, FALSE); +} + +#ifdef MOTION_MONITOR + +static void +gtk_xtext_unrender_hilight (GtkXText *xtext) +{ + xtext->render_hilights_only = TRUE; + xtext->skip_border_fills = TRUE; + xtext->skip_stamp = TRUE; + xtext->un_hilight = TRUE; + + gtk_xtext_render_ents (xtext, xtext->hilight_ent, NULL); + + xtext->render_hilights_only = FALSE; + xtext->skip_border_fills = FALSE; + xtext->skip_stamp = FALSE; + xtext->un_hilight = FALSE; +} + +static gboolean +gtk_xtext_leave_notify (GtkWidget * widget, GdkEventCrossing * event) +{ + GtkXText *xtext = GTK_XTEXT (widget); + + if (xtext->cursor_hand) + { + gtk_xtext_unrender_hilight (xtext); + xtext->hilight_start = -1; + xtext->hilight_end = -1; + xtext->cursor_hand = FALSE; + gdk_window_set_cursor (widget->window, 0); + xtext->hilight_ent = NULL; + } + + if (xtext->cursor_resize) + { + gtk_xtext_unrender_hilight (xtext); + xtext->hilight_start = -1; + xtext->hilight_end = -1; + xtext->cursor_resize = FALSE; + gdk_window_set_cursor (widget->window, 0); + xtext->hilight_ent = NULL; + } + + return FALSE; +} + +#endif + +/* check if we should mark time stamps, and if a redraw is needed */ + +static gboolean +gtk_xtext_check_mark_stamp (GtkXText *xtext, GdkModifierType mask) +{ + gboolean redraw = FALSE; + + if ((mask & GDK_SHIFT_MASK)) + { + if (!xtext->mark_stamp) + { + redraw = TRUE; /* must redraw all */ + xtext->mark_stamp = TRUE; + } + } else + { + if (xtext->mark_stamp) + { + redraw = TRUE; /* must redraw all */ + xtext->mark_stamp = FALSE; + } + } + return redraw; +} + +static gboolean +gtk_xtext_motion_notify (GtkWidget * widget, GdkEventMotion * event) +{ + GtkXText *xtext = GTK_XTEXT (widget); + GdkModifierType mask; + int redraw, tmp, x, y, offset, len, line_x; + unsigned char *word; + textentry *word_ent; + + gdk_window_get_pointer (widget->window, &x, &y, &mask); + + if (xtext->moving_separator) + { + if (x < (3 * widget->allocation.width) / 5 && x > 15) + { + tmp = xtext->buffer->indent; + xtext->buffer->indent = x; + gtk_xtext_fix_indent (xtext->buffer); + if (tmp != xtext->buffer->indent) + { + gtk_xtext_recalc_widths (xtext->buffer, FALSE); + if (xtext->buffer->scrollbar_down) + gtk_adjustment_set_value (xtext->adj, xtext->adj->upper - + xtext->adj->page_size); + if (!xtext->io_tag) + xtext->io_tag = g_timeout_add (REFRESH_TIMEOUT, + (GSourceFunc) + gtk_xtext_adjustment_timeout, + xtext); + } + } + return FALSE; + } + + if (xtext->button_down) + { + redraw = gtk_xtext_check_mark_stamp (xtext, mask); + gtk_grab_add (widget); + /*gdk_pointer_grab (widget->window, TRUE, + GDK_BUTTON_RELEASE_MASK | + GDK_BUTTON_MOTION_MASK, NULL, NULL, 0);*/ + xtext->select_end_x = x; + xtext->select_end_y = y; + gtk_xtext_selection_update (xtext, event, y, !redraw); + xtext->hilighting = TRUE; + + /* user has pressed or released SHIFT, must redraw entire selection */ + if (redraw) + { + xtext->force_stamp = TRUE; + gtk_xtext_render_ents (xtext, xtext->buffer->last_ent_start, + xtext->buffer->last_ent_end); + xtext->force_stamp = FALSE; + } + return FALSE; + } +#ifdef MOTION_MONITOR + + if (xtext->separator && xtext->buffer->indent) + { + line_x = xtext->buffer->indent - ((xtext->space_width + 1) / 2); + if (line_x == x || line_x == x + 1 || line_x == x - 1) + { + if (!xtext->cursor_resize) + { + gdk_window_set_cursor (GTK_WIDGET (xtext)->window, + xtext->resize_cursor); + xtext->cursor_resize = TRUE; + } + return FALSE; + } + } + + if (xtext->urlcheck_function == NULL) + return FALSE; + + word = gtk_xtext_get_word (xtext, x, y, &word_ent, &offset, &len); + if (word) + { + if (xtext->urlcheck_function (GTK_WIDGET (xtext), word, len) > 0) + { + if (!xtext->cursor_hand || + xtext->hilight_ent != word_ent || + xtext->hilight_start != offset || + xtext->hilight_end != offset + len) + { + if (!xtext->cursor_hand) + { + gdk_window_set_cursor (GTK_WIDGET (xtext)->window, + xtext->hand_cursor); + xtext->cursor_hand = TRUE; + } + + /* un-render the old hilight */ + if (xtext->hilight_ent) + gtk_xtext_unrender_hilight (xtext); + + xtext->hilight_ent = word_ent; + xtext->hilight_start = offset; + xtext->hilight_end = offset + len; + + xtext->skip_border_fills = TRUE; + xtext->render_hilights_only = TRUE; + xtext->skip_stamp = TRUE; + + gtk_xtext_render_ents (xtext, word_ent, NULL); + + xtext->skip_border_fills = FALSE; + xtext->render_hilights_only = FALSE; + xtext->skip_stamp = FALSE; + } + return FALSE; + } + } + + gtk_xtext_leave_notify (widget, NULL); + +#endif + + return FALSE; +} + +static void +gtk_xtext_set_clip_owner (GtkWidget * xtext, GdkEventButton * event) +{ + char *str; + int len; + + if (GTK_XTEXT (xtext)->selection_buffer && + GTK_XTEXT (xtext)->selection_buffer != GTK_XTEXT (xtext)->buffer) + gtk_xtext_selection_clear (GTK_XTEXT (xtext)->selection_buffer); + + GTK_XTEXT (xtext)->selection_buffer = GTK_XTEXT (xtext)->buffer; + + str = gtk_xtext_selection_get_text (GTK_XTEXT (xtext), &len); + if (str) + { +#if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION == 0) + gtk_clipboard_set_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD), + str, len); +#else + gtk_clipboard_set_text (gtk_widget_get_clipboard (xtext, GDK_SELECTION_CLIPBOARD), + str, len); +#endif + free (str); + } + + gtk_selection_owner_set (xtext, GDK_SELECTION_PRIMARY, event->time); +} + +static void +gtk_xtext_unselect (GtkXText *xtext) +{ + xtext_buffer *buf = xtext->buffer; + + xtext->skip_border_fills = TRUE; + xtext->skip_stamp = TRUE; + + xtext->jump_in_offset = buf->last_ent_start->mark_start; + /* just a single ent was marked? */ + if (buf->last_ent_start == buf->last_ent_end) + { + xtext->jump_out_offset = buf->last_ent_start->mark_end; + buf->last_ent_end = NULL; + } + + gtk_xtext_selection_clear (xtext->buffer); + + /* FIXME: use jump_out on multi-line selects too! */ + gtk_xtext_render_ents (xtext, buf->last_ent_start, buf->last_ent_end); + + xtext->jump_in_offset = 0; + xtext->jump_out_offset = 0; + + xtext->skip_border_fills = FALSE; + xtext->skip_stamp = FALSE; + + xtext->buffer->last_ent_start = NULL; + xtext->buffer->last_ent_end = NULL; +} + +static gboolean +gtk_xtext_button_release (GtkWidget * widget, GdkEventButton * event) +{ + GtkXText *xtext = GTK_XTEXT (widget); + unsigned char *word; + int old; + + if (xtext->moving_separator) + { + xtext->moving_separator = FALSE; + old = xtext->buffer->indent; + if (event->x < (4 * widget->allocation.width) / 5 && event->x > 15) + xtext->buffer->indent = event->x; + gtk_xtext_fix_indent (xtext->buffer); + if (xtext->buffer->indent != old) + { + gtk_xtext_recalc_widths (xtext->buffer, FALSE); + gtk_xtext_adjustment_set (xtext->buffer, TRUE); + gtk_xtext_render_page (xtext); + } else + gtk_xtext_draw_sep (xtext, -1); + return FALSE; + } + + if (xtext->word_or_line_select) + { + xtext->word_or_line_select = FALSE; + xtext->button_down = FALSE; + return FALSE; + } + + if (event->button == 1) + { + xtext->button_down = FALSE; + + gtk_grab_remove (widget); + /*gdk_pointer_ungrab (0);*/ + + /* got a new selection? */ + if (xtext->buffer->last_ent_start) + { + xtext->color_paste = FALSE; + if (event->state & GDK_CONTROL_MASK) + xtext->color_paste = TRUE; + gtk_xtext_set_clip_owner (GTK_WIDGET (xtext), event); + } + + if (xtext->select_start_x == event->x && + xtext->select_start_y == event->y && + xtext->buffer->last_ent_start) + { + gtk_xtext_unselect (xtext); + xtext->mark_stamp = FALSE; + return FALSE; + } + + if (!xtext->hilighting) + { + word = gtk_xtext_get_word (xtext, event->x, event->y, 0, 0, 0); + g_signal_emit (G_OBJECT (xtext), xtext_signals[WORD_CLICK], 0, word ? word : NULL, event); + } else + { + xtext->hilighting = FALSE; + } + } + + + return FALSE; +} + +static gboolean +gtk_xtext_button_press (GtkWidget * widget, GdkEventButton * event) +{ + GtkXText *xtext = GTK_XTEXT (widget); + GdkModifierType mask; + textentry *ent; + unsigned char *word; + int line_x, x, y, offset, len; + + gdk_window_get_pointer (widget->window, &x, &y, &mask); + + if (event->button == 3 || event->button == 2) /* right/middle click */ + { + word = gtk_xtext_get_word (xtext, x, y, 0, 0, 0); + if (word) + { + g_signal_emit (G_OBJECT (xtext), xtext_signals[WORD_CLICK], 0, + word, event); + } else + g_signal_emit (G_OBJECT (xtext), xtext_signals[WORD_CLICK], 0, + "", event); + return FALSE; + } + + if (event->button != 1) /* we only want left button */ + return FALSE; + + if (event->type == GDK_2BUTTON_PRESS) /* WORD select */ + { + gtk_xtext_check_mark_stamp (xtext, mask); + if (gtk_xtext_get_word (xtext, x, y, &ent, &offset, &len)) + { + if (len == 0) + return FALSE; + gtk_xtext_selection_clear (xtext->buffer); + ent->mark_start = offset; + ent->mark_end = offset + len; + gtk_xtext_selection_render (xtext, ent, offset, ent, offset + len); + xtext->word_or_line_select = TRUE; + gtk_xtext_set_clip_owner (GTK_WIDGET (xtext), event); + } + + return FALSE; + } + + if (event->type == GDK_3BUTTON_PRESS) /* LINE select */ + { + gtk_xtext_check_mark_stamp (xtext, mask); + if (gtk_xtext_get_word (xtext, x, y, &ent, 0, 0)) + { + gtk_xtext_selection_clear (xtext->buffer); + ent->mark_start = 0; + ent->mark_end = ent->str_len; + gtk_xtext_selection_render (xtext, ent, 0, ent, ent->str_len); + xtext->word_or_line_select = TRUE; + gtk_xtext_set_clip_owner (GTK_WIDGET (xtext), event); + } + + return FALSE; + } + + /* check if it was a separator-bar click */ + if (xtext->separator && xtext->buffer->indent) + { + line_x = xtext->buffer->indent - ((xtext->space_width + 1) / 2); + if (line_x == x || line_x == x + 1 || line_x == x - 1) + { + xtext->moving_separator = TRUE; + /* draw the separator line */ + gtk_xtext_draw_sep (xtext, -1); + return FALSE; + } + } + + xtext->button_down = TRUE; + xtext->select_start_x = x; + xtext->select_start_y = y; + xtext->select_start_adj = xtext->adj->value; + + return FALSE; +} + +/* another program has claimed the selection */ + +static gboolean +gtk_xtext_selection_kill (GtkXText *xtext, GdkEventSelection *event) +{ +#ifndef WIN32 + if (xtext->buffer->last_ent_start) + gtk_xtext_unselect (xtext); +#endif + return TRUE; +} + +static char * +gtk_xtext_selection_get_text (GtkXText *xtext, int *len_ret) +{ + textentry *ent; + char *txt; + char *pos; + char *stripped; + int len; + int first = TRUE; + xtext_buffer *buf; + + buf = xtext->selection_buffer; + if (!buf) + return NULL; + + /* first find out how much we need to malloc ... */ + len = 0; + ent = buf->last_ent_start; + while (ent) + { + if (ent->mark_start != -1) + { + /* include timestamp? */ + if (ent->mark_start == 0 && xtext->mark_stamp) + { + char *time_str; + int stamp_size = xtext_get_stamp_str (ent->stamp, &time_str); + g_free (time_str); + len += stamp_size; + } + + if (ent->mark_end - ent->mark_start > 0) + len += (ent->mark_end - ent->mark_start) + 1; + else + len++; + } + if (ent == buf->last_ent_end) + break; + ent = ent->next; + } + + if (len < 1) + return NULL; + + /* now allocate mem and copy buffer */ + pos = txt = malloc (len); + ent = buf->last_ent_start; + while (ent) + { + if (ent->mark_start != -1) + { + if (!first) + { + *pos = '\n'; + pos++; + } + first = FALSE; + if (ent->mark_end - ent->mark_start > 0) + { + /* include timestamp? */ + if (ent->mark_start == 0 && xtext->mark_stamp) + { + char *time_str; + int stamp_size = xtext_get_stamp_str (ent->stamp, &time_str); + memcpy (pos, time_str, stamp_size); + g_free (time_str); + pos += stamp_size; + } + + memcpy (pos, ent->str + ent->mark_start, + ent->mark_end - ent->mark_start); + pos += ent->mark_end - ent->mark_start; + } + } + if (ent == buf->last_ent_end) + break; + ent = ent->next; + } + *pos = 0; + + if (xtext->color_paste) + { + /*stripped = gtk_xtext_conv_color (txt, strlen (txt), &len);*/ + stripped = txt; + len = strlen (txt); + } else + { + stripped = gtk_xtext_strip_color (txt, strlen (txt), NULL, &len, 0, FALSE); + free (txt); + } + + *len_ret = len; + return stripped; +} + +/* another program is asking for our selection */ + +static void +gtk_xtext_selection_get (GtkWidget * widget, + GtkSelectionData * selection_data_ptr, + guint info, guint time) +{ + GtkXText *xtext = GTK_XTEXT (widget); + char *stripped; + guchar *new_text; + int len; + gsize glen; + + stripped = gtk_xtext_selection_get_text (xtext, &len); + if (!stripped) + return; + + switch (info) + { + case TARGET_UTF8_STRING: + /* it's already in utf8 */ + gtk_selection_data_set_text (selection_data_ptr, stripped, len); + break; + case TARGET_TEXT: + case TARGET_COMPOUND_TEXT: + { + GdkAtom encoding; + gint format; + gint new_length; + +#if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION == 0) + gdk_string_to_compound_text ( +#else + gdk_string_to_compound_text_for_display ( + gdk_drawable_get_display (widget->window), +#endif + stripped, &encoding, &format, &new_text, + &new_length); + gtk_selection_data_set (selection_data_ptr, encoding, format, + new_text, new_length); + gdk_free_compound_text (new_text); + } + break; + default: + new_text = g_locale_from_utf8 (stripped, len, NULL, &glen, NULL); + gtk_selection_data_set (selection_data_ptr, GDK_SELECTION_TYPE_STRING, + 8, new_text, glen); + g_free (new_text); + } + + free (stripped); +} + +static gboolean +gtk_xtext_scroll (GtkWidget *widget, GdkEventScroll *event) +{ + GtkXText *xtext = GTK_XTEXT (widget); + gfloat new_value; + + if (event->direction == GDK_SCROLL_UP) /* mouse wheel pageUp */ + { + new_value = xtext->adj->value - (xtext->adj->page_increment / 10); + if (new_value < xtext->adj->lower) + new_value = xtext->adj->lower; + gtk_adjustment_set_value (xtext->adj, new_value); + } + else if (event->direction == GDK_SCROLL_DOWN) /* mouse wheel pageDn */ + { + new_value = xtext->adj->value + (xtext->adj->page_increment / 10); + if (new_value > (xtext->adj->upper - xtext->adj->page_size)) + new_value = xtext->adj->upper - xtext->adj->page_size; + gtk_adjustment_set_value (xtext->adj, new_value); + } + + return FALSE; +} + +static void +gtk_xtext_class_init (GtkXTextClass * class) +{ + GtkObjectClass *object_class; + GtkWidgetClass *widget_class; + GtkXTextClass *xtext_class; + + object_class = (GtkObjectClass *) class; + widget_class = (GtkWidgetClass *) class; + xtext_class = (GtkXTextClass *) class; + + parent_class = gtk_type_class (gtk_widget_get_type ()); + + xtext_signals[WORD_CLICK] = + g_signal_new ("word_click", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkXTextClass, word_click), + NULL, NULL, + gtk_marshal_VOID__POINTER_POINTER, + G_TYPE_NONE, + 2, G_TYPE_POINTER, G_TYPE_POINTER); + object_class->destroy = gtk_xtext_destroy; + + widget_class->realize = gtk_xtext_realize; + widget_class->unrealize = gtk_xtext_unrealize; + widget_class->size_request = gtk_xtext_size_request; + widget_class->size_allocate = gtk_xtext_size_allocate; + widget_class->button_press_event = gtk_xtext_button_press; + widget_class->button_release_event = gtk_xtext_button_release; + widget_class->motion_notify_event = gtk_xtext_motion_notify; + widget_class->selection_clear_event = (void *)gtk_xtext_selection_kill; + widget_class->selection_get = gtk_xtext_selection_get; + widget_class->expose_event = gtk_xtext_expose; + widget_class->scroll_event = gtk_xtext_scroll; +#ifdef MOTION_MONITOR + widget_class->leave_notify_event = gtk_xtext_leave_notify; +#endif + + xtext_class->word_click = NULL; +} + +GType +gtk_xtext_get_type (void) +{ + static GType xtext_type = 0; + + if (!xtext_type) + { + static const GTypeInfo xtext_info = + { + sizeof (GtkXTextClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) gtk_xtext_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (GtkXText), + 0, /* n_preallocs */ + (GInstanceInitFunc) gtk_xtext_init, + }; + + xtext_type = g_type_register_static (GTK_TYPE_WIDGET, "GtkXText", + &xtext_info, 0); + } + + return xtext_type; +} + +/* strip MIRC colors and other attribs. */ + +/* CL: needs to strip hidden when called by gtk_xtext_text_width, but not when copying text */ + +static unsigned char * +gtk_xtext_strip_color (unsigned char *text, int len, unsigned char *outbuf, + int *newlen, int *mb_ret, int strip_hidden) +{ + int i = 0; + int rcol = 0, bgcol = 0; + int hidden = FALSE; + unsigned char *new_str; + int mb = FALSE; + + if (outbuf == NULL) + new_str = malloc (len + 2); + else + new_str = outbuf; + + while (len > 0) + { + if (*text >= 128) + mb = TRUE; + + if (rcol > 0 && (isdigit (*text) || (*text == ',' && isdigit (text[1]) && !bgcol))) + { + if (text[1] != ',') rcol--; + if (*text == ',') + { + rcol = 2; + bgcol = 1; + } + } else + { + rcol = bgcol = 0; + switch (*text) + { + case ATTR_COLOR: + rcol = 2; + break; + case ATTR_BEEP: + case ATTR_RESET: + case ATTR_REVERSE: + case ATTR_BOLD: + case ATTR_UNDERLINE: + case ATTR_ITALICS: + break; + case ATTR_HIDDEN: + hidden = !hidden; + break; + default: + if (!(hidden && strip_hidden)) + new_str[i++] = *text; + } + } + text++; + len--; + } + + new_str[i] = 0; + + if (newlen != NULL) + *newlen = i; + + if (mb_ret != NULL) + *mb_ret = mb; + + return new_str; +} + +/* GeEkMaN: converts mIRC control codes to literal control codes */ + +static char * +gtk_xtext_conv_color (unsigned char *text, int len, int *newlen) +{ + int i, j = 2; + char cchar = 0; + char *new_str; + int mbl; + + for (i = 0; i < len;) + { + switch (text[i]) + { + case ATTR_COLOR: + case ATTR_RESET: + case ATTR_REVERSE: + case ATTR_BOLD: + case ATTR_UNDERLINE: + case ATTR_ITALICS: + case ATTR_HIDDEN: + j += 3; + i++; + break; + default: + mbl = charlen (text + i); + j += mbl; + i += mbl; + } + } + + new_str = malloc (j); + j = 0; + + for (i = 0; i < len;) + { + switch (text[i]) + { + case ATTR_COLOR: + cchar = 'C'; + break; + case ATTR_RESET: + cchar = 'O'; + break; + case ATTR_REVERSE: + cchar = 'R'; + break; + case ATTR_BOLD: + cchar = 'B'; + break; + case ATTR_UNDERLINE: + cchar = 'U'; + break; + case ATTR_ITALICS: + cchar = 'I'; + break; + case ATTR_HIDDEN: + cchar = 'H'; + break; + case ATTR_BEEP: + break; + default: + mbl = charlen (text + i); + if (mbl == 1) + { + new_str[j] = text[i]; + j++; + i++; + } else + { + /* invalid utf8 safe guard */ + if (i + mbl > len) + mbl = len - i; + memcpy (new_str + j, text + i, mbl); + j += mbl; + i += mbl; + } + } + if (cchar != 0) + { + new_str[j++] = '%'; + new_str[j++] = cchar; + cchar = 0; + i++; + } + } + + new_str[j] = 0; + *newlen = j; + + return new_str; +} + +/* gives width of a string, excluding the mIRC codes */ + +static int +gtk_xtext_text_width (GtkXText *xtext, unsigned char *text, int len, + int *mb_ret) +{ + unsigned char *new_buf; + int new_len, mb; + + new_buf = gtk_xtext_strip_color (text, len, xtext->scratch_buffer, + &new_len, &mb, !xtext->ignore_hidden); + + if (mb_ret) + *mb_ret = mb; + + return backend_get_text_width (xtext, new_buf, new_len, mb); +} + +/* actually draw text to screen (one run with the same color/attribs) */ + +static int +gtk_xtext_render_flush (GtkXText * xtext, int x, int y, unsigned char *str, + int len, GdkGC *gc, int is_mb) +{ + int str_width, dofill; + GdkDrawable *pix = NULL; + int dest_x, dest_y; + + if (xtext->dont_render || len < 1 || xtext->hidden) + return 0; + + str_width = backend_get_text_width (xtext, str, len, is_mb); + + if (xtext->dont_render2) + return str_width; + + /* roll-your-own clipping (avoiding XftDrawString is always good!) */ + if (x > xtext->clip_x2 || x + str_width < xtext->clip_x) + return str_width; + if (y - xtext->font->ascent > xtext->clip_y2 || (y - xtext->font->ascent) + xtext->fontsize < xtext->clip_y) + return str_width; + + if (xtext->render_hilights_only) + { + if (!xtext->in_hilight) /* is it a hilight prefix? */ + return str_width; +#ifndef COLOR_HILIGHT + if (!xtext->un_hilight) /* doing a hilight? no need to draw the text */ + goto dounder; +#endif + } + +#ifdef USE_DB +#ifdef WIN32 + if (!xtext->transparent) +#endif + { + pix = gdk_pixmap_new (xtext->draw_buf, str_width, xtext->fontsize, xtext->depth); + if (pix) + { +#ifdef USE_XFT + XftDrawChange (xtext->xftdraw, GDK_WINDOW_XWINDOW (pix)); +#endif + dest_x = x; + dest_y = y - xtext->font->ascent; + + gdk_gc_set_ts_origin (xtext->bgc, xtext->ts_x - x, xtext->ts_y - dest_y); + + x = 0; + y = xtext->font->ascent; + xtext->draw_buf = pix; + } + } +#endif + + dofill = TRUE; + + /* backcolor is always handled by XDrawImageString */ + if (!xtext->backcolor && xtext->pixmap) + { + /* draw the background pixmap behind the text - CAUSES FLICKER HERE!! */ + xtext_draw_bg (xtext, x, y - xtext->font->ascent, str_width, + xtext->fontsize); + dofill = FALSE; /* already drawn the background */ + } + + backend_draw_text (xtext, dofill, gc, x, y, str, len, str_width, is_mb); + +#ifdef USE_DB + if (pix) + { + GdkRectangle clip; + GdkRectangle dest; + + gdk_gc_set_ts_origin (xtext->bgc, xtext->ts_x, xtext->ts_y); + xtext->draw_buf = GTK_WIDGET (xtext)->window; +#ifdef USE_XFT + XftDrawChange (xtext->xftdraw, GDK_WINDOW_XWINDOW (xtext->draw_buf)); +#endif +#if 0 + gdk_draw_drawable (xtext->draw_buf, xtext->bgc, pix, 0, 0, dest_x, + dest_y, str_width, xtext->fontsize); +#else + clip.x = xtext->clip_x; + clip.y = xtext->clip_y; + clip.width = xtext->clip_x2 - xtext->clip_x; + clip.height = xtext->clip_y2 - xtext->clip_y; + + dest.x = dest_x; + dest.y = dest_y; + dest.width = str_width; + dest.height = xtext->fontsize; + + if (gdk_rectangle_intersect (&clip, &dest, &dest)) + /* dump the DB to window, but only within the clip_x/x2/y/y2 */ + gdk_draw_drawable (xtext->draw_buf, xtext->bgc, pix, + dest.x - dest_x, dest.y - dest_y, + dest.x, dest.y, dest.width, dest.height); +#endif + g_object_unref (pix); + } +#endif + + if (xtext->underline) + { +#ifdef USE_XFT + GdkColor col; +#endif + +#ifndef COLOR_HILIGHT +dounder: +#endif + +#ifdef USE_XFT + col.pixel = xtext->xft_fg->pixel; + gdk_gc_set_foreground (gc, &col); +#endif + if (pix) + y = dest_y + xtext->font->ascent + 1; + else + { + y++; + dest_x = x; + } + /* draw directly to window, it's out of the range of our DB */ + gdk_draw_line (xtext->draw_buf, gc, dest_x, y, dest_x + str_width - 1, y); + } + + return str_width; +} + +static void +gtk_xtext_reset (GtkXText * xtext, int mark, int attribs) +{ + if (attribs) + { + xtext->underline = FALSE; + xtext->bold = FALSE; + xtext->italics = FALSE; + xtext->hidden = FALSE; + } + if (!mark) + { + xtext->backcolor = FALSE; + if (xtext->col_fore != XTEXT_FG) + xtext_set_fg (xtext, xtext->fgc, XTEXT_FG); + if (xtext->col_back != XTEXT_BG) + xtext_set_bg (xtext, xtext->fgc, XTEXT_BG); + } + xtext->col_fore = XTEXT_FG; + xtext->col_back = XTEXT_BG; + xtext->parsing_color = FALSE; + xtext->parsing_backcolor = FALSE; + xtext->nc = 0; +} + +/* render a single line, which WONT wrap, and parse mIRC colors */ + +static int +gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, + unsigned char *str, int len, int win_width, int indent, + int line, int left_only, int *x_size_ret) +{ + GdkGC *gc; + int i = 0, x = indent, j = 0; + unsigned char *pstr = str; + int col_num, tmp; + int offset; + int mark = FALSE; + int ret = 1; + + xtext->in_hilight = FALSE; + + offset = str - ent->str; + + if (line < 255 && line >= 0) + xtext->buffer->grid_offset[line] = offset; + + gc = xtext->fgc; /* our foreground GC */ + + if (ent->mark_start != -1 && + ent->mark_start <= i + offset && ent->mark_end > i + offset) + { + xtext_set_bg (xtext, gc, XTEXT_MARK_BG); + xtext_set_fg (xtext, gc, XTEXT_MARK_FG); + xtext->backcolor = TRUE; + mark = TRUE; + } +#ifdef MOTION_MONITOR + if (xtext->hilight_ent == ent && + xtext->hilight_start <= i + offset && xtext->hilight_end > i + offset) + { + if (!xtext->un_hilight) + { +#ifdef COLOR_HILIGHT + xtext_set_bg (xtext, gc, 2); +#else + xtext->underline = TRUE; +#endif + } + xtext->in_hilight = TRUE; + } +#endif + + if (!xtext->skip_border_fills && !xtext->dont_render) + { + /* draw background to the left of the text */ + if (str == ent->str && indent > MARGIN && xtext->buffer->time_stamp) + { + /* don't overwrite the timestamp */ + if (indent > xtext->stamp_width) + { + xtext_draw_bg (xtext, xtext->stamp_width, y - xtext->font->ascent, + indent - xtext->stamp_width, xtext->fontsize); + } + } else + { + /* fill the indent area with background gc */ + if (indent >= xtext->clip_x) + { + xtext_draw_bg (xtext, 0, y - xtext->font->ascent, + MIN (indent, xtext->clip_x2), xtext->fontsize); + } + } + } + + if (xtext->jump_in_offset > 0 && offset < xtext->jump_in_offset) + xtext->dont_render2 = TRUE; + + while (i < len) + { + +#ifdef MOTION_MONITOR + if (xtext->hilight_ent == ent && xtext->hilight_start == (i + offset)) + { + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + pstr += j; + j = 0; + if (!xtext->un_hilight) + { +#ifdef COLOR_HILIGHT + xtext_set_bg (xtext, gc, 2); +#else + xtext->underline = TRUE; +#endif + } + + xtext->in_hilight = TRUE; + } +#endif + + if ((xtext->parsing_color && isdigit (str[i]) && xtext->nc < 2) || + (xtext->parsing_color && str[i] == ',' && isdigit (str[i+1]) && xtext->nc < 3 && !xtext->parsing_backcolor)) + { + pstr++; + if (str[i] == ',') + { + xtext->parsing_backcolor = TRUE; + if (xtext->nc) + { + xtext->num[xtext->nc] = 0; + xtext->nc = 0; + col_num = atoi (xtext->num); + if (col_num == 99) /* mIRC lameness */ + col_num = XTEXT_FG; + else + col_num = col_num % XTEXT_MIRC_COLS; + xtext->col_fore = col_num; + if (!mark) + xtext_set_fg (xtext, gc, col_num); + } + } else + { + xtext->num[xtext->nc] = str[i]; + if (xtext->nc < 7) + xtext->nc++; + } + } else + { + if (xtext->parsing_color) + { + xtext->parsing_color = FALSE; + if (xtext->nc) + { + xtext->num[xtext->nc] = 0; + xtext->nc = 0; + col_num = atoi (xtext->num); + if (xtext->parsing_backcolor) + { + if (col_num == 99) /* mIRC lameness */ + col_num = XTEXT_BG; + else + col_num = col_num % XTEXT_MIRC_COLS; + if (col_num == XTEXT_BG) + xtext->backcolor = FALSE; + else + xtext->backcolor = TRUE; + if (!mark) + xtext_set_bg (xtext, gc, col_num); + xtext->col_back = col_num; + } else + { + if (col_num == 99) /* mIRC lameness */ + col_num = XTEXT_FG; + else + col_num = col_num % XTEXT_MIRC_COLS; + if (!mark) + xtext_set_fg (xtext, gc, col_num); + xtext->col_fore = col_num; + } + xtext->parsing_backcolor = FALSE; + } else + { + /* got a \003<non-digit>... i.e. reset colors */ + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + pstr += j; + j = 0; + gtk_xtext_reset (xtext, mark, FALSE); + } + } + + switch (str[i]) + { + case '\n': + /*case ATTR_BEEP:*/ + break; + case ATTR_REVERSE: + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + pstr += j + 1; + j = 0; + tmp = xtext->col_fore; + xtext->col_fore = xtext->col_back; + xtext->col_back = tmp; + if (!mark) + { + xtext_set_fg (xtext, gc, xtext->col_fore); + xtext_set_bg (xtext, gc, xtext->col_back); + } + if (xtext->col_back != XTEXT_BG) + xtext->backcolor = TRUE; + else + xtext->backcolor = FALSE; + break; + case ATTR_BOLD: + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + xtext->bold = !xtext->bold; + pstr += j + 1; + j = 0; + break; + case ATTR_UNDERLINE: + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + xtext->underline = !xtext->underline; + pstr += j + 1; + j = 0; + break; + case ATTR_ITALICS: + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + xtext->italics = !xtext->italics; + pstr += j + 1; + j = 0; + break; + case ATTR_HIDDEN: + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + xtext->hidden = (!xtext->hidden) & (!xtext->ignore_hidden); + pstr += j + 1; + j = 0; + break; + case ATTR_RESET: + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + pstr += j + 1; + j = 0; + gtk_xtext_reset (xtext, mark, !xtext->in_hilight); + break; + case ATTR_COLOR: + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + xtext->parsing_color = TRUE; + pstr += j + 1; + j = 0; + break; + default: + tmp = charlen (str + i); + /* invalid utf8 safe guard */ + if (tmp + i > len) + tmp = len - i; + j += tmp; /* move to the next utf8 char */ + } + } + i += charlen (str + i); /* move to the next utf8 char */ + /* invalid utf8 safe guard */ + if (i > len) + i = len; + + /* Separate the left part, the space and the right part + into separate runs, and reset bidi state inbetween. + Perform this only on the first line of the message. + */ + if (offset == 0) + { + /* we've reached the end of the left part? */ + if ((pstr-str)+j == ent->left_len) + { + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + pstr += j; + j = 0; + } + else if ((pstr-str)+j == ent->left_len+1) + { + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + pstr += j; + j = 0; + } + } + + /* have we been told to stop rendering at this point? */ + if (xtext->jump_out_offset > 0 && xtext->jump_out_offset <= (i + offset)) + { + gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + ret = 0; /* skip the rest of the lines, we're done. */ + j = 0; + break; + } + + if (xtext->jump_in_offset > 0 && xtext->jump_in_offset == (i + offset)) + { + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + pstr += j; + j = 0; + xtext->dont_render2 = FALSE; + } + +#ifdef MOTION_MONITOR + if (xtext->hilight_ent == ent && xtext->hilight_end == (i + offset)) + { + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + pstr += j; + j = 0; +#ifdef COLOR_HILIGHT + if (mark) + { + xtext_set_bg (xtext, gc, XTEXT_MARK_BG); + xtext->backcolor = TRUE; + } else + { + xtext_set_bg (xtext, gc, xtext->col_back); + if (xtext->col_back != XTEXT_BG) + xtext->backcolor = TRUE; + else + xtext->backcolor = FALSE; + } +#else + xtext->underline = FALSE; +#endif + xtext->in_hilight = FALSE; + if (xtext->render_hilights_only) + { + /* stop drawing this ent */ + ret = 0; + break; + } + } +#endif + + if (!mark && ent->mark_start == (i + offset)) + { + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + pstr += j; + j = 0; + xtext_set_bg (xtext, gc, XTEXT_MARK_BG); + xtext_set_fg (xtext, gc, XTEXT_MARK_FG); + xtext->backcolor = TRUE; + mark = TRUE; + } + + if (mark && ent->mark_end == (i + offset)) + { + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + pstr += j; + j = 0; + xtext_set_bg (xtext, gc, xtext->col_back); + xtext_set_fg (xtext, gc, xtext->col_fore); + if (xtext->col_back != XTEXT_BG) + xtext->backcolor = TRUE; + else + xtext->backcolor = FALSE; + mark = FALSE; + } + + } + + if (j) + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + + if (mark) + { + xtext_set_bg (xtext, gc, xtext->col_back); + xtext_set_fg (xtext, gc, xtext->col_fore); + if (xtext->col_back != XTEXT_BG) + xtext->backcolor = TRUE; + else + xtext->backcolor = FALSE; + } + + /* draw background to the right of the text */ + if (!left_only && !xtext->dont_render) + { + /* draw separator now so it doesn't appear to flicker */ + gtk_xtext_draw_sep (xtext, y - xtext->font->ascent); + if (!xtext->skip_border_fills && xtext->clip_x2 >= x) + { + int xx = MAX (x, xtext->clip_x); + + xtext_draw_bg (xtext, + xx, /* x */ + y - xtext->font->ascent, /* y */ + MIN (xtext->clip_x2 - xx, (win_width + MARGIN) - xx), /* width */ + xtext->fontsize); /* height */ + } + } + + xtext->dont_render2 = FALSE; + + /* return how much we drew in the x direction */ + if (x_size_ret) + *x_size_ret = x - indent; + + return ret; +} + +#ifdef USE_XLIB + +/* get the desktop/root window */ + +static Window desktop_window = None; + +static Window +get_desktop_window (Display *xdisplay, Window the_window) +{ + Atom prop, type; + int format; + unsigned long length, after; + unsigned char *data; + unsigned int nchildren; + Window w, root, *children, parent; + + prop = XInternAtom (xdisplay, "_XROOTPMAP_ID", True); + if (prop == None) + { + prop = XInternAtom (xdisplay, "_XROOTCOLOR_PIXEL", True); + if (prop == None) + return None; + } + + for (w = the_window; w; w = parent) + { + if ((XQueryTree (xdisplay, w, &root, &parent, &children, + &nchildren)) == False) + return None; + + if (nchildren) + XFree (children); + + XGetWindowProperty (xdisplay, w, prop, 0L, 1L, False, + AnyPropertyType, &type, &format, &length, &after, + &data); + if (data) + XFree (data); + + if (type != None) + return (desktop_window = w); + } + + return (desktop_window = None); +} + +/* find the root window (backdrop) Pixmap */ + +static Pixmap +get_pixmap_prop (Display *xdisplay, Window the_window) +{ + Atom type; + int format; + unsigned long length, after; + unsigned char *data; + Pixmap pix = None; + static Atom prop = None; + + if (desktop_window == None) + desktop_window = get_desktop_window (xdisplay, the_window); + if (desktop_window == None) + desktop_window = DefaultRootWindow (xdisplay); + + if (prop == None) + prop = XInternAtom (xdisplay, "_XROOTPMAP_ID", True); + if (prop == None) + return None; + + XGetWindowProperty (xdisplay, desktop_window, prop, 0L, 1L, False, + AnyPropertyType, &type, &format, &length, &after, + &data); + if (data) + { + if (type == XA_PIXMAP) + pix = *((Pixmap *) data); + + XFree (data); + } + + return pix; +} + +/* slow generic routine, for the depths/bpp we don't know about */ + +static void +shade_ximage_generic (GdkVisual *visual, XImage *ximg, int bpl, int w, int h, int rm, int gm, int bm, int bg) +{ + int x, y; + int bgr = (256 - rm) * (bg & visual->red_mask); + int bgg = (256 - gm) * (bg & visual->green_mask); + int bgb = (256 - bm) * (bg & visual->blue_mask); + + for (x = 0; x < w; x++) + { + for (y = 0; y < h; y++) + { + unsigned long pixel = XGetPixel (ximg, x, y); + int r, g, b; + + r = rm * (pixel & visual->red_mask) + bgr; + g = gm * (pixel & visual->green_mask) + bgg; + b = bm * (pixel & visual->blue_mask) + bgb; + + XPutPixel (ximg, x, y, + ((r >> 8) & visual->red_mask) | + ((g >> 8) & visual->green_mask) | + ((b >> 8) & visual->blue_mask)); + } + } +} + +#endif + +/* Fast shading routine. Based on code by Willem Monsuwe <willem@stack.nl> */ + +#define SHADE_IMAGE(bytes, type, rmask, gmask, bmask) \ + unsigned char *ptr; \ + int x, y; \ + int bgr = (256 - rm) * (bg & rmask); \ + int bgg = (256 - gm) * (bg & gmask); \ + int bgb = (256 - bm) * (bg & bmask); \ + ptr = (unsigned char *) data + (w * bytes); \ + for (y = h; --y >= 0;) \ + { \ + for (x = -w; x < 0; x++) \ + { \ + int r, g, b; \ + b = ((type *) ptr)[x]; \ + r = rm * (b & rmask) + bgr; \ + g = gm * (b & gmask) + bgg; \ + b = bm * (b & bmask) + bgb; \ + ((type *) ptr)[x] = ((r >> 8) & rmask) \ + | ((g >> 8) & gmask) \ + | ((b >> 8) & bmask); \ + } \ + ptr += bpl; \ + } + +/* RGB 15 */ +static void +shade_ximage_15 (void *data, int bpl, int w, int h, int rm, int gm, int bm, int bg) +{ + SHADE_IMAGE (2, guint16, 0x7c00, 0x3e0, 0x1f); +} + +/* RGB 16 */ +static void +shade_ximage_16 (void *data, int bpl, int w, int h, int rm, int gm, int bm, int bg) +{ + SHADE_IMAGE (2, guint16, 0xf800, 0x7e0, 0x1f); +} + +/* RGB 24 */ +static void +shade_ximage_24 (void *data, int bpl, int w, int h, int rm, int gm, int bm, int bg) +{ + /* 24 has to be a special case, there's no guint24, or 24bit MOV :) */ + unsigned char *ptr; + int x, y; + int bgr = (256 - rm) * ((bg & 0xff0000) >> 16); + int bgg = (256 - gm) * ((bg & 0xff00) >> 8); + int bgb = (256 - bm) * (bg & 0xff); + + ptr = (unsigned char *) data + (w * 3); + for (y = h; --y >= 0;) + { + for (x = -(w * 3); x < 0; x += 3) + { + int r, g, b; + +#if (G_BYTE_ORDER == G_BIG_ENDIAN) + r = (ptr[x + 0] * rm + bgr) >> 8; + g = (ptr[x + 1] * gm + bgg) >> 8; + b = (ptr[x + 2] * bm + bgb) >> 8; + ptr[x + 0] = r; + ptr[x + 1] = g; + ptr[x + 2] = b; +#else + r = (ptr[x + 2] * rm + bgr) >> 8; + g = (ptr[x + 1] * gm + bgg) >> 8; + b = (ptr[x + 0] * bm + bgb) >> 8; + ptr[x + 2] = r; + ptr[x + 1] = g; + ptr[x + 0] = b; +#endif + } + ptr += bpl; + } +} + +/* RGB 32 */ +static void +shade_ximage_32 (void *data, int bpl, int w, int h, int rm, int gm, int bm, int bg) +{ + SHADE_IMAGE (4, guint32, 0xff0000, 0xff00, 0xff); +} + +static void +shade_image (GdkVisual *visual, void *data, int bpl, int bpp, int w, int h, + int rm, int gm, int bm, int bg, int depth) +{ + int bg_r, bg_g, bg_b; + + bg_r = bg & visual->red_mask; + bg_g = bg & visual->green_mask; + bg_b = bg & visual->blue_mask; + +#ifdef USE_MMX + /* the MMX routines are about 50% faster at 16-bit. */ + /* only use MMX routines with a pure black background */ + if (bg_r == 0 && bg_g == 0 && bg_b == 0 && have_mmx ()) /* do a runtime check too! */ + { + switch (depth) + { + case 15: + shade_ximage_15_mmx (data, bpl, w, h, rm, gm, bm); + break; + case 16: + shade_ximage_16_mmx (data, bpl, w, h, rm, gm, bm); + break; + case 24: + if (bpp != 32) + goto generic; + case 32: + shade_ximage_32_mmx (data, bpl, w, h, rm, gm, bm); + break; + default: + goto generic; + } + } else + { +generic: +#endif + switch (depth) + { + case 15: + shade_ximage_15 (data, bpl, w, h, rm, gm, bm, bg); + break; + case 16: + shade_ximage_16 (data, bpl, w, h, rm, gm, bm, bg); + break; + case 24: + if (bpp != 32) + { + shade_ximage_24 (data, bpl, w, h, rm, gm, bm, bg); + break; + } + case 32: + shade_ximage_32 (data, bpl, w, h, rm, gm, bm, bg); + } +#ifdef USE_MMX + } +#endif +} + +#ifdef USE_XLIB + +#ifdef USE_SHM + +static XImage * +get_shm_image (Display *xdisplay, XShmSegmentInfo *shminfo, int x, int y, + int w, int h, int depth, Pixmap pix) +{ + XImage *ximg; + + shminfo->shmid = -1; + shminfo->shmaddr = (char*) -1; + ximg = XShmCreateImage (xdisplay, 0, depth, ZPixmap, 0, shminfo, w, h); + if (!ximg) + return NULL; + + shminfo->shmid = shmget (IPC_PRIVATE, ximg->bytes_per_line * ximg->height, + IPC_CREAT|0600); + if (shminfo->shmid == -1) + { + XDestroyImage (ximg); + return NULL; + } + + shminfo->readOnly = False; + ximg->data = shminfo->shmaddr = (char *)shmat (shminfo->shmid, 0, 0); + if (shminfo->shmaddr == ((char *)-1)) + { + shmctl (shminfo->shmid, IPC_RMID, 0); + XDestroyImage (ximg); + return NULL; + } + + XShmAttach (xdisplay, shminfo); + XSync (xdisplay, False); + shmctl (shminfo->shmid, IPC_RMID, 0); + XShmGetImage (xdisplay, pix, ximg, x, y, AllPlanes); + + return ximg; +} + +static XImage * +get_image (GtkXText *xtext, Display *xdisplay, XShmSegmentInfo *shminfo, + int x, int y, int w, int h, int depth, Pixmap pix) +{ + XImage *ximg; + + xtext->shm = 1; + ximg = get_shm_image (xdisplay, shminfo, x, y, w, h, depth, pix); + if (!ximg) + { + xtext->shm = 0; + ximg = XGetImage (xdisplay, pix, x, y, w, h, -1, ZPixmap); + } + + return ximg; +} + +#endif + +static GdkPixmap * +shade_pixmap (GtkXText * xtext, Pixmap p, int x, int y, int w, int h) +{ + unsigned int dummy, width, height, depth; + GdkPixmap *shaded_pix; + Window root; + Pixmap tmp; + XImage *ximg; + XGCValues gcv; + GC tgc; + Display *xdisplay = GDK_WINDOW_XDISPLAY (xtext->draw_buf); + +#ifdef USE_SHM + int shm_pixmaps; + shm_pixmaps = have_shm_pixmaps(xdisplay); +#endif + + XGetGeometry (xdisplay, p, &root, &dummy, &dummy, &width, &height, + &dummy, &depth); + + if (width < x + w || height < y + h || x < 0 || y < 0) + { + gcv.subwindow_mode = IncludeInferiors; + gcv.graphics_exposures = False; + tgc = XCreateGC (xdisplay, p, GCGraphicsExposures|GCSubwindowMode, + &gcv); + tmp = XCreatePixmap (xdisplay, p, w, h, depth); + XSetTile (xdisplay, tgc, p); + XSetFillStyle (xdisplay, tgc, FillTiled); + XSetTSOrigin (xdisplay, tgc, -x, -y); + XFillRectangle (xdisplay, tmp, tgc, 0, 0, w, h); + XFreeGC (xdisplay, tgc); + +#ifdef USE_SHM + if (shm_pixmaps) + ximg = get_image (xtext, xdisplay, &xtext->shminfo, 0, 0, w, h, depth, tmp); + else +#endif + ximg = XGetImage (xdisplay, tmp, 0, 0, w, h, -1, ZPixmap); + XFreePixmap (xdisplay, tmp); + } else + { +#ifdef USE_SHM + if (shm_pixmaps) + ximg = get_image (xtext, xdisplay, &xtext->shminfo, x, y, w, h, depth, p); + else +#endif + ximg = XGetImage (xdisplay, p, x, y, w, h, -1, ZPixmap); + } + + if (!ximg) + return NULL; + + if (depth <= 14) + { + shade_ximage_generic (gdk_drawable_get_visual (GTK_WIDGET (xtext)->window), + ximg, ximg->bytes_per_line, w, h, xtext->tint_red, + xtext->tint_green, xtext->tint_blue, + xtext->palette[XTEXT_BG]); + } else + { + shade_image (gdk_drawable_get_visual (GTK_WIDGET (xtext)->window), + ximg->data, ximg->bytes_per_line, ximg->bits_per_pixel, + w, h, xtext->tint_red, xtext->tint_green, xtext->tint_blue, + xtext->palette[XTEXT_BG], depth); + } + + if (xtext->recycle) + shaded_pix = xtext->pixmap; + else + { +#ifdef USE_SHM + if (xtext->shm && shm_pixmaps) + { +#if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION == 0) + shaded_pix = gdk_pixmap_foreign_new ( + XShmCreatePixmap (xdisplay, p, ximg->data, &xtext->shminfo, w, h, depth)); +#else + shaded_pix = gdk_pixmap_foreign_new_for_display ( + gdk_drawable_get_display (xtext->draw_buf), + XShmCreatePixmap (xdisplay, p, ximg->data, &xtext->shminfo, w, h, depth)); +#endif + } else +#endif + { + shaded_pix = gdk_pixmap_new (GTK_WIDGET (xtext)->window, w, h, depth); + } + } + +#ifdef USE_SHM + if (!xtext->shm || !shm_pixmaps) +#endif + XPutImage (xdisplay, GDK_WINDOW_XWINDOW (shaded_pix), + GDK_GC_XGC (xtext->fgc), ximg, 0, 0, 0, 0, w, h); + XDestroyImage (ximg); + + return shaded_pix; +} + +#endif /* !USE_XLIB */ + +/* free transparency xtext->pixmap */ +#if defined(USE_XLIB) || defined(WIN32) + +static void +gtk_xtext_free_trans (GtkXText * xtext) +{ + if (xtext->pixmap) + { +#ifdef USE_SHM + if (xtext->shm && have_shm_pixmaps(GDK_WINDOW_XDISPLAY (xtext->draw_buf))) + { + XFreePixmap (GDK_WINDOW_XDISPLAY (xtext->pixmap), + GDK_WINDOW_XWINDOW (xtext->pixmap)); + XShmDetach (GDK_WINDOW_XDISPLAY (xtext->draw_buf), &xtext->shminfo); + shmdt (xtext->shminfo.shmaddr); + } +#endif + g_object_unref (xtext->pixmap); + xtext->pixmap = NULL; + xtext->shm = 0; + } +} + +#endif + +#ifdef WIN32 + +static GdkPixmap * +win32_tint (GtkXText *xtext, GdkImage *img, int width, int height) +{ + guchar *pixelp; + int x, y; + GdkPixmap *pix; + GdkVisual *visual = gdk_drawable_get_visual (GTK_WIDGET (xtext)->window); + guint32 pixel; + int r, g, b; + + if (img->depth <= 14) + { + /* slow generic routine */ + for (y = 0; y < height; y++) + { + for (x = 0; x < width; x++) + { + if (img->depth == 1) + { + pixel = (((guchar *) img->mem)[y * img->bpl + (x >> 3)] & (1 << (7 - (x & 0x7)))) != 0; + goto here; + } + + if (img->depth == 4) + { + pixelp = (guchar *) img->mem + y * img->bpl + (x >> 1); + if (x&1) + { + pixel = (*pixelp) & 0x0F; + goto here; + } + + pixel = (*pixelp) >> 4; + goto here; + } + + pixelp = (guchar *) img->mem + y * img->bpl + x * img->bpp; + + switch (img->bpp) + { + case 1: + pixel = *pixelp; break; + + /* Windows is always LSB, no need to check img->byte_order. */ + case 2: + pixel = pixelp[0] | (pixelp[1] << 8); break; + + case 3: + pixel = pixelp[0] | (pixelp[1] << 8) | (pixelp[2] << 16); break; + + case 4: + pixel = pixelp[0] | (pixelp[1] << 8) | (pixelp[2] << 16); break; + } + +here: + r = (pixel & visual->red_mask) >> visual->red_shift; + g = (pixel & visual->green_mask) >> visual->green_shift; + b = (pixel & visual->blue_mask) >> visual->blue_shift; + + /* actual tinting is only these 3 lines */ + pixel = ((r * xtext->tint_red) >> 8) << visual->red_shift | + ((g * xtext->tint_green) >> 8) << visual->green_shift | + ((b * xtext->tint_blue) >> 8) << visual->blue_shift; + + if (img->depth == 1) + if (pixel & 1) + ((guchar *) img->mem)[y * img->bpl + (x >> 3)] |= (1 << (7 - (x & 0x7))); + else + ((guchar *) img->mem)[y * img->bpl + (x >> 3)] &= ~(1 << (7 - (x & 0x7))); + else if (img->depth == 4) + { + pixelp = (guchar *) img->mem + y * img->bpl + (x >> 1); + + if (x&1) + { + *pixelp &= 0xF0; + *pixelp |= (pixel & 0x0F); + } else + { + *pixelp &= 0x0F; + *pixelp |= (pixel << 4); + } + } else + { + pixelp = (guchar *) img->mem + y * img->bpl + x * img->bpp; + + /* Windows is always LSB, no need to check img->byte_order. */ + switch (img->bpp) + { + case 4: + pixelp[3] = 0; + case 3: + pixelp[2] = ((pixel >> 16) & 0xFF); + case 2: + pixelp[1] = ((pixel >> 8) & 0xFF); + case 1: + pixelp[0] = (pixel & 0xFF); + } + } + } + } + } else + { + shade_image (visual, img->mem, img->bpl, img->bpp, width, height, + xtext->tint_red, xtext->tint_green, xtext->tint_blue, + xtext->palette[XTEXT_BG], visual->depth); + } + + /* no need to dump it to a Pixmap, it's one and the same on win32 */ + pix = (GdkPixmap *)img; + + return pix; +} + +#endif /* !WIN32 */ + +/* grab pixmap from root window and set xtext->pixmap */ +#if defined(USE_XLIB) || defined(WIN32) + +static void +gtk_xtext_load_trans (GtkXText * xtext) +{ +#ifdef WIN32 + GdkImage *img; + int width, height; + HDC hdc; + HWND hwnd; + + /* if not shaded, we paint directly with PaintDesktop() */ + if (!xtext->shaded) + return; + + hwnd = GDK_WINDOW_HWND (GTK_WIDGET (xtext)->window); + hdc = GetDC (hwnd); + PaintDesktop (hdc); + ReleaseDC (hwnd, hdc); + + gdk_window_get_size (GTK_WIDGET (xtext)->window, &width, &height); + img = gdk_image_get (GTK_WIDGET (xtext)->window, 0, 0, width+128, height); + xtext->pixmap = win32_tint (xtext, img, img->width, img->height); + +#else + + Pixmap rootpix; + GtkWidget *widget = GTK_WIDGET (xtext); + int x, y; + + rootpix = get_pixmap_prop (GDK_WINDOW_XDISPLAY (widget->window), GDK_WINDOW_XWINDOW (widget->window)); + if (rootpix == None) + { + if (xtext->error_function) + xtext->error_function (0); + xtext->transparent = FALSE; + return; + } + + gdk_window_get_origin (widget->window, &x, &y); + + if (xtext->shaded) + { + int width, height; + gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &width, &height); + xtext->pixmap = shade_pixmap (xtext, rootpix, x, y, width+105, height); + if (xtext->pixmap == NULL) + { + xtext->shaded = 0; + goto noshade; + } + gdk_gc_set_tile (xtext->bgc, xtext->pixmap); + gdk_gc_set_ts_origin (xtext->bgc, 0, 0); + xtext->ts_x = xtext->ts_y = 0; + } else + { +noshade: +#if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION == 0) + xtext->pixmap = gdk_pixmap_foreign_new (rootpix); +#else + xtext->pixmap = gdk_pixmap_foreign_new_for_display (gdk_drawable_get_display (GTK_WIDGET (xtext)->window), rootpix); +#endif + gdk_gc_set_tile (xtext->bgc, xtext->pixmap); + gdk_gc_set_ts_origin (xtext->bgc, -x, -y); + xtext->ts_x = -x; + xtext->ts_y = -y; + } + gdk_gc_set_fill (xtext->bgc, GDK_TILED); +#endif /* !WIN32 */ +} + +#endif /* ! XLIB || WIN32 */ + +/* walk through str until this line doesn't fit anymore */ + +static int +find_next_wrap (GtkXText * xtext, textentry * ent, unsigned char *str, + int win_width, int indent) +{ + unsigned char *last_space = str; + unsigned char *orig_str = str; + int str_width = indent; + int rcol = 0, bgcol = 0; + int hidden = FALSE; + int mbl; + int char_width; + int ret; + int limit_offset = 0; + + /* single liners */ + if (win_width >= ent->str_width + ent->indent) + return ent->str_len; + + /* it does happen! */ + if (win_width < 1) + { + ret = ent->str_len - (str - ent->str); + goto done; + } + + while (1) + { + if (rcol > 0 && (isdigit (*str) || (*str == ',' && isdigit (str[1]) && !bgcol))) + { + if (str[1] != ',') rcol--; + if (*str == ',') + { + rcol = 2; + bgcol = 1; + } + limit_offset++; + str++; + } else + { + rcol = bgcol = 0; + switch (*str) + { + case ATTR_COLOR: + rcol = 2; + case ATTR_BEEP: + case ATTR_RESET: + case ATTR_REVERSE: + case ATTR_BOLD: + case ATTR_UNDERLINE: + case ATTR_ITALICS: + limit_offset++; + str++; + break; + case ATTR_HIDDEN: + if (xtext->ignore_hidden) + goto def; + hidden = !hidden; + limit_offset++; + str++; + break; + default: + def: + char_width = backend_get_char_width (xtext, str, &mbl); + if (!hidden) str_width += char_width; + if (str_width > win_width) + { + if (xtext->wordwrap) + { + if (str - last_space > WORDWRAP_LIMIT + limit_offset) + ret = str - orig_str; /* fall back to character wrap */ + else + { + if (*last_space == ' ') + last_space++; + ret = last_space - orig_str; + if (ret == 0) /* fall back to character wrap */ + ret = str - orig_str; + } + goto done; + } + ret = str - orig_str; + goto done; + } + + /* keep a record of the last space, for wordwrapping */ + if (is_del (*str)) + { + last_space = str; + limit_offset = 0; + } + + /* progress to the next char */ + str += mbl; + + } + } + + if (str >= ent->str + ent->str_len) + { + ret = str - orig_str; + goto done; + } + } + +done: + + /* must make progress */ + if (ret < 1) + ret = 1; + + return ret; +} + +/* find the offset, in bytes, that wrap number 'line' starts at */ + +static int +gtk_xtext_find_subline (GtkXText *xtext, textentry *ent, int line) +{ + int win_width; + unsigned char *str; + int indent, str_pos, line_pos, len; + + if (ent->lines_taken < 2 || line < 1) + return 0; + + /* we record the first 4 lines' wraps, so take a shortcut */ + if (line <= RECORD_WRAPS) + return ent->wrap_offset[line - 1]; + + gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &win_width, 0); + win_width -= MARGIN; + +/* indent = ent->indent; + str = ent->str; + line_pos = str_pos = 0;*/ + + /* start from the last recorded wrap, and move forward */ + indent = xtext->buffer->indent; + str_pos = ent->wrap_offset[RECORD_WRAPS-1]; + str = str_pos + ent->str; + line_pos = RECORD_WRAPS; + + do + { + len = find_next_wrap (xtext, ent, str, win_width, indent); + indent = xtext->buffer->indent; + str += len; + str_pos += len; + line_pos++; + if (line_pos >= line) + return str_pos; + } + while (str < ent->str + ent->str_len); + + return 0; +} + +/* horrible hack for drawing time stamps */ + +static void +gtk_xtext_render_stamp (GtkXText * xtext, textentry * ent, + char *text, int len, int line, int win_width) +{ + textentry tmp_ent; + int jo, ji, hs; + int xsize, y; + + /* trashing ent here, so make a backup first */ + memcpy (&tmp_ent, ent, sizeof (tmp_ent)); + ent->mb = TRUE; /* make non-english days of the week work */ + jo = xtext->jump_out_offset; /* back these up */ + ji = xtext->jump_in_offset; + hs = xtext->hilight_start; + xtext->jump_out_offset = 0; + xtext->jump_in_offset = 0; + xtext->hilight_start = 0xffff; /* temp disable */ + + if (xtext->mark_stamp) + { + /* if this line is marked, mark this stamp too */ + if (ent->mark_start == 0) + { + ent->mark_start = 0; + ent->mark_end = len; + } + else + { + ent->mark_start = -1; + ent->mark_end = -1; + } + ent->str = text; + } + + y = (xtext->fontsize * line) + xtext->font->ascent - xtext->pixel_offset; + gtk_xtext_render_str (xtext, y, ent, text, len, + win_width, 2, line, TRUE, &xsize); + + /* restore everything back to how it was */ + memcpy (ent, &tmp_ent, sizeof (tmp_ent)); + xtext->jump_out_offset = jo; + xtext->jump_in_offset = ji; + xtext->hilight_start = hs; + + /* with a non-fixed-width font, sometimes we don't draw enough + background i.e. when this stamp is shorter than xtext->stamp_width */ + xsize += MARGIN; + if (xsize < xtext->stamp_width) + { + y -= xtext->font->ascent; + xtext_draw_bg (xtext, + xsize, /* x */ + y, /* y */ + xtext->stamp_width - xsize, /* width */ + xtext->fontsize /* height */); + } +} + +/* render a single line, which may wrap to more lines */ + +static int +gtk_xtext_render_line (GtkXText * xtext, textentry * ent, int line, + int lines_max, int subline, int win_width) +{ + unsigned char *str; + int indent, taken, entline, len, y, start_subline; + + entline = taken = 0; + str = ent->str; + indent = ent->indent; + start_subline = subline; + +#ifdef XCHAT + /* draw the timestamp */ + if (xtext->auto_indent && xtext->buffer->time_stamp && + (!xtext->skip_stamp || xtext->mark_stamp || xtext->force_stamp)) + { + char *time_str; + int len; + + len = xtext_get_stamp_str (ent->stamp, &time_str); + gtk_xtext_render_stamp (xtext, ent, time_str, len, line, win_width); + g_free (time_str); + } +#endif + + /* draw each line one by one */ + do + { + /* if it's one of the first 4 wraps, we don't need to calculate it, it's + recorded in ->wrap_offset. This saves us a loop. */ + if (entline < RECORD_WRAPS) + { + if (ent->lines_taken < 2) + len = ent->str_len; + else + { + if (entline > 0) + len = ent->wrap_offset[entline] - ent->wrap_offset[entline-1]; + else + len = ent->wrap_offset[0]; + } + } else + len = find_next_wrap (xtext, ent, str, win_width, indent); + + entline++; + + y = (xtext->fontsize * line) + xtext->font->ascent - xtext->pixel_offset; + if (!subline) + { + if (!gtk_xtext_render_str (xtext, y, ent, str, len, win_width, + indent, line, FALSE, NULL)) + { + /* small optimization */ + gtk_xtext_draw_marker (xtext, ent, y - xtext->fontsize * (taken + start_subline + 1)); + return ent->lines_taken - subline; + } + } else + { + xtext->dont_render = TRUE; + gtk_xtext_render_str (xtext, y, ent, str, len, win_width, + indent, line, FALSE, NULL); + xtext->dont_render = FALSE; + subline--; + line--; + taken--; + } + + indent = xtext->buffer->indent; + line++; + taken++; + str += len; + + if (line >= lines_max) + break; + + } + while (str < ent->str + ent->str_len); + + gtk_xtext_draw_marker (xtext, ent, y - xtext->fontsize * (taken + start_subline)); + + return taken; +} + +void +gtk_xtext_set_palette (GtkXText * xtext, GdkColor palette[]) +{ + int i; + GdkColor col; + + for (i = (XTEXT_COLS-1); i >= 0; i--) + { +#ifdef USE_XFT + xtext->color[i].color.red = palette[i].red; + xtext->color[i].color.green = palette[i].green; + xtext->color[i].color.blue = palette[i].blue; + xtext->color[i].color.alpha = 0xffff; + xtext->color[i].pixel = palette[i].pixel; +#endif + xtext->palette[i] = palette[i].pixel; + } + + if (GTK_WIDGET_REALIZED (xtext)) + { + xtext_set_fg (xtext, xtext->fgc, XTEXT_FG); + xtext_set_bg (xtext, xtext->fgc, XTEXT_BG); + xtext_set_fg (xtext, xtext->bgc, XTEXT_BG); + + col.pixel = xtext->palette[XTEXT_MARKER]; + gdk_gc_set_foreground (xtext->marker_gc, &col); + } + xtext->col_fore = XTEXT_FG; + xtext->col_back = XTEXT_BG; +} + +static void +gtk_xtext_fix_indent (xtext_buffer *buf) +{ + int j; + + /* make indent a multiple of the space width */ + if (buf->indent && buf->xtext->space_width) + { + j = 0; + while (j < buf->indent) + { + j += buf->xtext->space_width; + } + buf->indent = j; + } + + dontscroll (buf); /* force scrolling off */ +} + +static void +gtk_xtext_recalc_widths (xtext_buffer *buf, int do_str_width) +{ + textentry *ent; + + /* since we have a new font, we have to recalc the text widths */ + ent = buf->text_first; + while (ent) + { + if (do_str_width) + { + ent->str_width = gtk_xtext_text_width (buf->xtext, ent->str, + ent->str_len, NULL); + } + if (ent->left_len != -1) + { + ent->indent = + (buf->indent - + gtk_xtext_text_width (buf->xtext, ent->str, + ent->left_len, NULL)) - buf->xtext->space_width; + if (ent->indent < MARGIN) + ent->indent = MARGIN; + } + ent = ent->next; + } + + gtk_xtext_calc_lines (buf, FALSE); +} + +int +gtk_xtext_set_font (GtkXText *xtext, char *name) +{ + int i; + unsigned char c; + + if (xtext->font) + backend_font_close (xtext); + + /* realize now, so that font_open has a XDisplay */ + gtk_widget_realize (GTK_WIDGET (xtext)); + + backend_font_open (xtext, name); + if (xtext->font == NULL) + return FALSE; + + /* measure the width of every char; only the ASCII ones for XFT */ + for (i = 0; i < sizeof(xtext->fontwidth)/sizeof(xtext->fontwidth[0]); i++) + { + c = i; + xtext->fontwidth[i] = backend_get_text_width (xtext, &c, 1, TRUE); + } + xtext->space_width = xtext->fontwidth[' ']; + xtext->fontsize = xtext->font->ascent + xtext->font->descent; + +#ifdef XCHAT + { + char *time_str; + int stamp_size = xtext_get_stamp_str (time(0), &time_str); + xtext->stamp_width = + gtk_xtext_text_width (xtext, time_str, stamp_size, NULL) + MARGIN; + g_free (time_str); + } +#endif + + gtk_xtext_fix_indent (xtext->buffer); + + if (GTK_WIDGET_REALIZED (xtext)) + gtk_xtext_recalc_widths (xtext->buffer, TRUE); + + return TRUE; +} + +void +gtk_xtext_set_background (GtkXText * xtext, GdkPixmap * pixmap, gboolean trans) +{ + GdkGCValues val; + gboolean shaded = FALSE; + + if (trans && (xtext->tint_red != 255 || xtext->tint_green != 255 || xtext->tint_blue != 255)) + shaded = TRUE; + +#if !defined(USE_XLIB) && !defined(WIN32) + shaded = FALSE; + trans = FALSE; +#endif + + if (xtext->pixmap) + { +#if defined(USE_XLIB) || defined(WIN32) + if (xtext->transparent) + gtk_xtext_free_trans (xtext); + else +#endif + g_object_unref (xtext->pixmap); + xtext->pixmap = NULL; + } + + xtext->transparent = trans; + +#if defined(USE_XLIB) || defined(WIN32) + if (trans) + { + xtext->shaded = shaded; + if (GTK_WIDGET_REALIZED (xtext)) + gtk_xtext_load_trans (xtext); + return; + } +#endif + + dontscroll (xtext->buffer); + xtext->pixmap = pixmap; + + if (pixmap != 0) + { + g_object_ref (pixmap); + if (GTK_WIDGET_REALIZED (xtext)) + { + gdk_gc_set_tile (xtext->bgc, pixmap); + gdk_gc_set_ts_origin (xtext->bgc, 0, 0); + xtext->ts_x = xtext->ts_y = 0; + gdk_gc_set_fill (xtext->bgc, GDK_TILED); + } + } else if (GTK_WIDGET_REALIZED (xtext)) + { + g_object_unref (xtext->bgc); + val.subwindow_mode = GDK_INCLUDE_INFERIORS; + val.graphics_exposures = 0; + xtext->bgc = gdk_gc_new_with_values (GTK_WIDGET (xtext)->window, + &val, GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); + xtext_set_fg (xtext, xtext->bgc, XTEXT_BG); + } +} + +void +gtk_xtext_save (GtkXText * xtext, int fh) +{ + textentry *ent; + int newlen; + char *buf; + + ent = xtext->buffer->text_first; + while (ent) + { + buf = gtk_xtext_strip_color (ent->str, ent->str_len, NULL, + &newlen, NULL, FALSE); + write (fh, buf, newlen); + write (fh, "\n", 1); + free (buf); + ent = ent->next; + } +} + +/* count how many lines 'ent' will take (with wraps) */ + +static int +gtk_xtext_lines_taken (xtext_buffer *buf, textentry * ent) +{ + unsigned char *str; + int indent, taken, len; + int win_width; + + win_width = buf->window_width - MARGIN; + + if (ent->str_width + ent->indent < win_width) + return 1; + + indent = ent->indent; + str = ent->str; + taken = 0; + + do + { + len = find_next_wrap (buf->xtext, ent, str, win_width, indent); + if (taken < RECORD_WRAPS) + ent->wrap_offset[taken] = (str + len) - ent->str; + indent = buf->indent; + taken++; + str += len; + } + while (str < ent->str + ent->str_len); + + return taken; +} + +/* Calculate number of actual lines (with wraps), to set adj->lower. * + * This should only be called when the window resizes. */ + +static void +gtk_xtext_calc_lines (xtext_buffer *buf, int fire_signal) +{ + textentry *ent; + int width; + int height; + int lines; + + gdk_drawable_get_size (GTK_WIDGET (buf->xtext)->window, &width, &height); + width -= MARGIN; + + if (width < 30 || height < buf->xtext->fontsize || width < buf->indent + 30) + return; + + lines = 0; + ent = buf->text_first; + while (ent) + { + ent->lines_taken = gtk_xtext_lines_taken (buf, ent); + lines += ent->lines_taken; + ent = ent->next; + } + + buf->pagetop_ent = NULL; + buf->num_lines = lines; + gtk_xtext_adjustment_set (buf, fire_signal); +} + +/* find the n-th line in the linked list, this includes wrap calculations */ + +static textentry * +gtk_xtext_nth (GtkXText *xtext, int line, int *subline) +{ + int lines = 0; + textentry *ent; + + ent = xtext->buffer->text_first; + + /* -- optimization -- try to make a short-cut using the pagetop ent */ + if (xtext->buffer->pagetop_ent) + { + if (line == xtext->buffer->pagetop_line) + { + *subline = xtext->buffer->pagetop_subline; + return xtext->buffer->pagetop_ent; + } + if (line > xtext->buffer->pagetop_line) + { + /* lets start from the pagetop instead of the absolute beginning */ + ent = xtext->buffer->pagetop_ent; + lines = xtext->buffer->pagetop_line - xtext->buffer->pagetop_subline; + } + else if (line > xtext->buffer->pagetop_line - line) + { + /* move backwards from pagetop */ + ent = xtext->buffer->pagetop_ent; + lines = xtext->buffer->pagetop_line - xtext->buffer->pagetop_subline; + while (1) + { + if (lines <= line) + { + *subline = line - lines; + return ent; + } + ent = ent->prev; + if (!ent) + break; + lines -= ent->lines_taken; + } + return 0; + } + } + /* -- end of optimization -- */ + + while (ent) + { + lines += ent->lines_taken; + if (lines > line) + { + *subline = ent->lines_taken - (lines - line); + return ent; + } + ent = ent->next; + } + return 0; +} + +/* render enta (or an inclusive range enta->entb) */ + +static int +gtk_xtext_render_ents (GtkXText * xtext, textentry * enta, textentry * entb) +{ + textentry *ent, *orig_ent, *tmp_ent; + int line; + int lines_max; + int width; + int height; + int subline; + int drawing = FALSE; + + if (xtext->buffer->indent < MARGIN) + xtext->buffer->indent = MARGIN; /* 2 pixels is our left margin */ + + gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &width, &height); + width -= MARGIN; + + if (width < 32 || height < xtext->fontsize || width < xtext->buffer->indent + 30) + return 0; + + lines_max = ((height + xtext->pixel_offset) / xtext->fontsize) + 1; + line = 0; + orig_ent = xtext->buffer->pagetop_ent; + subline = xtext->buffer->pagetop_subline; + + /* used before a complete page is in buffer */ + if (orig_ent == NULL) + orig_ent = xtext->buffer->text_first; + + /* check if enta is before the start of this page */ + if (entb) + { + tmp_ent = orig_ent; + while (tmp_ent) + { + if (tmp_ent == enta) + break; + if (tmp_ent == entb) + { + drawing = TRUE; + break; + } + tmp_ent = tmp_ent->next; + } + } + + ent = orig_ent; + while (ent) + { + if (entb && ent == enta) + drawing = TRUE; + + if (drawing || ent == entb || ent == enta) + { + gtk_xtext_reset (xtext, FALSE, TRUE); + line += gtk_xtext_render_line (xtext, ent, line, lines_max, + subline, width); + subline = 0; + xtext->jump_in_offset = 0; /* jump_in_offset only for the 1st */ + } else + { + if (ent == orig_ent) + { + line -= subline; + subline = 0; + } + line += ent->lines_taken; + } + + if (ent == entb) + break; + + if (line >= lines_max) + break; + + ent = ent->next; + } + + /* space below last line */ + return (xtext->fontsize * line) - xtext->pixel_offset; +} + +/* render a whole page/window, starting from 'startline' */ + +static void +gtk_xtext_render_page (GtkXText * xtext) +{ + textentry *ent; + int line; + int lines_max; + int width; + int height; + int subline; + int startline = xtext->adj->value; + + if(!GTK_WIDGET_REALIZED(xtext)) + return; + + if (xtext->buffer->indent < MARGIN) + xtext->buffer->indent = MARGIN; /* 2 pixels is our left margin */ + + gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &width, &height); + + if (width < 34 || height < xtext->fontsize || width < xtext->buffer->indent + 32) + return; + +#ifdef SMOOTH_SCROLL + xtext->pixel_offset = (xtext->adj->value - startline) * xtext->fontsize; +#else + xtext->pixel_offset = 0; +#endif + + subline = line = 0; + ent = xtext->buffer->text_first; + + if (startline > 0) + ent = gtk_xtext_nth (xtext, startline, &subline); + + xtext->buffer->pagetop_ent = ent; + xtext->buffer->pagetop_subline = subline; + xtext->buffer->pagetop_line = startline; + +#ifdef SCROLL_HACK +{ + int pos, overlap; + GdkRectangle area; + + if (xtext->buffer->num_lines <= xtext->adj->page_size) + dontscroll (xtext->buffer); + +#ifdef SMOOTH_SCROLL + pos = xtext->adj->value * xtext->fontsize; +#else + pos = startline * xtext->fontsize; +#endif + overlap = xtext->buffer->last_pixel_pos - pos; + xtext->buffer->last_pixel_pos = pos; + +#ifdef USE_DB +#ifdef WIN32 + if (!xtext->transparent && !xtext->pixmap && abs (overlap) < height) +#else + if (!xtext->pixmap && abs (overlap) < height) +#endif +#else + /* dont scroll PageUp/Down without a DB, it looks ugly */ +#ifdef WIN32 + if (!xtext->transparent && !xtext->pixmap && abs (overlap) < height - (3*xtext->fontsize)) +#else + if (!xtext->pixmap && abs (overlap) < height - (3*xtext->fontsize)) +#endif +#endif + { + /* so the obscured regions are exposed */ + gdk_gc_set_exposures (xtext->fgc, TRUE); + if (overlap < 1) /* DOWN */ + { + int remainder; + + gdk_draw_drawable (xtext->draw_buf, xtext->fgc, xtext->draw_buf, + 0, -overlap, 0, 0, width, height + overlap); + remainder = ((height - xtext->font->descent) % xtext->fontsize) + + xtext->font->descent; + area.y = (height + overlap) - remainder; + area.height = remainder - overlap; + } else + { + gdk_draw_drawable (xtext->draw_buf, xtext->fgc, xtext->draw_buf, + 0, 0, 0, overlap, width, height - overlap); + area.y = 0; + area.height = overlap; + } + gdk_gc_set_exposures (xtext->fgc, FALSE); + + if (area.height > 0) + { + area.x = 0; + area.width = width; + gtk_xtext_paint (GTK_WIDGET (xtext), &area); + } + xtext->buffer->grid_dirty = TRUE; + + return; + } +} +#endif + + xtext->buffer->grid_dirty = FALSE; + width -= MARGIN; + lines_max = ((height + xtext->pixel_offset) / xtext->fontsize) + 1; + + while (ent) + { + gtk_xtext_reset (xtext, FALSE, TRUE); + line += gtk_xtext_render_line (xtext, ent, line, lines_max, + subline, width); + subline = 0; + + if (line >= lines_max) + break; + + ent = ent->next; + } + + line = (xtext->fontsize * line) - xtext->pixel_offset; + /* fill any space below the last line with our background GC */ + xtext_draw_bg (xtext, 0, line, width + MARGIN, height - line); + + /* draw the separator line */ + gtk_xtext_draw_sep (xtext, -1); +} + +void +gtk_xtext_refresh (GtkXText * xtext, int do_trans) +{ + if (GTK_WIDGET_REALIZED (GTK_WIDGET (xtext))) + { +#if defined(USE_XLIB) || defined(WIN32) + if (xtext->transparent && do_trans) + { + gtk_xtext_free_trans (xtext); + gtk_xtext_load_trans (xtext); + } +#endif + gtk_xtext_render_page (xtext); + } +} + +static int +gtk_xtext_kill_ent (xtext_buffer *buffer, textentry *ent) +{ + int visible; + + /* Set visible to TRUE if this is the current buffer */ + /* and this ent shows up on the screen now */ + visible = buffer->xtext->buffer == buffer && + gtk_xtext_check_ent_visibility (buffer->xtext, ent, 0); + + if (ent == buffer->pagetop_ent) + buffer->pagetop_ent = NULL; + + if (ent == buffer->last_ent_start) + { + buffer->last_ent_start = ent->next; + buffer->last_offset_start = 0; + } + + if (ent == buffer->last_ent_end) + { + buffer->last_ent_start = NULL; + buffer->last_ent_end = NULL; + } + + if (buffer->marker_pos == ent) buffer->marker_pos = NULL; + + free (ent); + return visible; +} + +/* remove the topline from the list */ + +static void +gtk_xtext_remove_top (xtext_buffer *buffer) +{ + textentry *ent; + + ent = buffer->text_first; + if (!ent) + return; + buffer->num_lines -= ent->lines_taken; + buffer->pagetop_line -= ent->lines_taken; + buffer->last_pixel_pos -= (ent->lines_taken * buffer->xtext->fontsize); + buffer->text_first = ent->next; + if (buffer->text_first) + buffer->text_first->prev = NULL; + else + buffer->text_last = NULL; + + buffer->old_value -= ent->lines_taken; + if (buffer->xtext->buffer == buffer) /* is it the current buffer? */ + { + buffer->xtext->adj->value -= ent->lines_taken; + buffer->xtext->select_start_adj -= ent->lines_taken; + } + + if (gtk_xtext_kill_ent (buffer, ent)) + { + if (!buffer->xtext->add_io_tag) + { + /* remove scrolling events */ + if (buffer->xtext->io_tag) + { + g_source_remove (buffer->xtext->io_tag); + buffer->xtext->io_tag = 0; + } + buffer->xtext->force_render = TRUE; + buffer->xtext->add_io_tag = g_timeout_add (REFRESH_TIMEOUT * 2, + (GSourceFunc) + gtk_xtext_render_page_timeout, + buffer->xtext); + } + } +} + +static void +gtk_xtext_remove_bottom (xtext_buffer *buffer) +{ + textentry *ent; + + ent = buffer->text_last; + if (!ent) + return; + buffer->num_lines -= ent->lines_taken; + buffer->text_last = ent->prev; + if (buffer->text_last) + buffer->text_last->next = NULL; + else + buffer->text_first = NULL; + + gtk_xtext_kill_ent (buffer, ent); +} + +/* If lines=0 => clear all */ + +void +gtk_xtext_clear (xtext_buffer *buf, int lines) +{ + textentry *next; + + if (lines != 0) + { + if (lines < 0) + { + /* delete lines from bottom */ + lines *= -1; + while (lines) + { + gtk_xtext_remove_bottom (buf); + lines--; + } + } + else + { + /* delete lines from top */ + while (lines) + { + gtk_xtext_remove_top (buf); + lines--; + } + } + } + else + { + /* delete all */ + if (buf->xtext->auto_indent) + buf->indent = MARGIN; + buf->scrollbar_down = TRUE; + buf->last_ent_start = NULL; + buf->last_ent_end = NULL; + buf->marker_pos = NULL; + dontscroll (buf); + + while (buf->text_first) + { + next = buf->text_first->next; + free (buf->text_first); + buf->text_first = next; + } + buf->text_last = NULL; + } + + if (buf->xtext->buffer == buf) + { + gtk_xtext_calc_lines (buf, TRUE); + gtk_xtext_refresh (buf->xtext, 0); + } else + { + gtk_xtext_calc_lines (buf, FALSE); + } +} + +static gboolean +gtk_xtext_check_ent_visibility (GtkXText * xtext, textentry *find_ent, int add) +{ + textentry *ent; + int lines_max; + int line = 0; + int width; + int height; + + gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &width, &height); + + lines_max = ((height + xtext->pixel_offset) / xtext->fontsize) + add; + ent = xtext->buffer->pagetop_ent; + + while (ent && line < lines_max) + { + if (find_ent == ent) + return TRUE; + line += ent->lines_taken; + ent = ent->next; + } + + return FALSE; +} + +void +gtk_xtext_check_marker_visibility (GtkXText * xtext) +{ + if (gtk_xtext_check_ent_visibility (xtext, xtext->buffer->marker_pos, 1)) + xtext->buffer->marker_seen = TRUE; +} + +textentry * +gtk_xtext_search (GtkXText * xtext, const gchar *text, textentry *start, gboolean case_match, gboolean backward) +{ + textentry *ent, *fent; + int line; + gchar *str, *nee, *hay; /* needle in haystack */ + + gtk_xtext_selection_clear_full (xtext->buffer); + xtext->buffer->last_ent_start = NULL; + xtext->buffer->last_ent_end = NULL; + + /* set up text comparand for Case Match or Ignore */ + if (case_match) + nee = g_strdup (text); + else + nee = g_utf8_casefold (text, strlen (text)); + + /* Validate that start gives a currently valid ent pointer */ + ent = xtext->buffer->text_first; + while (ent) + { + if (ent == start) + break; + ent = ent->next; + } + if (!ent) + start = NULL; + + /* Choose first ent to look at */ + if (start) + ent = backward? start->prev: start->next; + else + ent = backward? xtext->buffer->text_last: xtext->buffer->text_first; + + /* Search from there to one end or the other until found */ + while (ent) + { + /* If Case Ignore, fold before & free after calling strstr */ + if (case_match) + hay = g_strdup (ent->str); + else + hay = g_utf8_casefold (ent->str, strlen (ent->str)); + /* Try to find the needle in this haystack */ + str = g_strstr_len (hay, strlen (hay), nee); + g_free (hay); + if (str) + break; + ent = backward? ent->prev: ent->next; + } + fent = ent; + + /* Save distance to start, end of found string */ + if (ent) + { + ent->mark_start = str - hay; + ent->mark_end = ent->mark_start + strlen (nee); + + /* is the match visible? Might need to scroll */ + if (!gtk_xtext_check_ent_visibility (xtext, ent, 0)) + { + ent = xtext->buffer->text_first; + line = 0; + while (ent) + { + line += ent->lines_taken; + ent = ent->next; + if (ent == fent) + break; + } + while (line > xtext->adj->upper - xtext->adj->page_size) + line--; + if (backward) + line -= xtext->adj->page_size - ent->lines_taken; + xtext->adj->value = line; + xtext->buffer->scrollbar_down = FALSE; + gtk_adjustment_changed (xtext->adj); + } + } + + g_free (nee); + gtk_widget_queue_draw (GTK_WIDGET (xtext)); + + return fent; +} + +static int +gtk_xtext_render_page_timeout (GtkXText * xtext) +{ + GtkAdjustment *adj = xtext->adj; + + xtext->add_io_tag = 0; + + /* less than a complete page? */ + if (xtext->buffer->num_lines <= adj->page_size) + { + xtext->buffer->old_value = 0; + adj->value = 0; + gtk_xtext_render_page (xtext); + } else if (xtext->buffer->scrollbar_down) + { + g_signal_handler_block (xtext->adj, xtext->vc_signal_tag); + gtk_xtext_adjustment_set (xtext->buffer, FALSE); + gtk_adjustment_set_value (adj, adj->upper - adj->page_size); + g_signal_handler_unblock (xtext->adj, xtext->vc_signal_tag); + xtext->buffer->old_value = adj->value; + gtk_xtext_render_page (xtext); + } else + { + gtk_xtext_adjustment_set (xtext->buffer, TRUE); + if (xtext->force_render) + { + xtext->force_render = FALSE; + gtk_xtext_render_page (xtext); + } + } + + return 0; +} + +/* append a textentry to our linked list */ + +static void +gtk_xtext_append_entry (xtext_buffer *buf, textentry * ent, time_t stamp) +{ + unsigned int mb; + int i; + + /* we don't like tabs */ + i = 0; + while (i < ent->str_len) + { + if (ent->str[i] == '\t') + ent->str[i] = ' '; + i++; + } + + ent->stamp = stamp; + if (stamp == 0) + ent->stamp = time (0); + ent->str_width = gtk_xtext_text_width (buf->xtext, ent->str, ent->str_len, &mb); + ent->mb = FALSE; + if (mb) + ent->mb = TRUE; + ent->mark_start = -1; + ent->mark_end = -1; + ent->next = NULL; + + if (ent->indent < MARGIN) + ent->indent = MARGIN; /* 2 pixels is the left margin */ + + /* append to our linked list */ + if (buf->text_last) + buf->text_last->next = ent; + else + buf->text_first = ent; + ent->prev = buf->text_last; + buf->text_last = ent; + + ent->lines_taken = gtk_xtext_lines_taken (buf, ent); + buf->num_lines += ent->lines_taken; + + if (buf->reset_marker_pos || + ((buf->marker_pos == NULL || buf->marker_seen) && (buf->xtext->buffer != buf || +#if GTK_CHECK_VERSION(2,4,0) + !gtk_window_has_toplevel_focus (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (buf->xtext))))))) +#else + !(GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (buf->xtext))))->has_focus))) +#endif + { + buf->marker_pos = ent; + dontscroll (buf); /* force scrolling off */ + buf->marker_seen = FALSE; + buf->reset_marker_pos = FALSE; + } + + if (buf->xtext->max_lines > 2 && buf->xtext->max_lines < buf->num_lines) + { + gtk_xtext_remove_top (buf); + } + + if (buf->xtext->buffer == buf) + { +#ifdef SCROLL_HACK + /* this could be improved */ + if ((buf->num_lines - 1) <= buf->xtext->adj->page_size) + dontscroll (buf); +#endif + + if (!buf->xtext->add_io_tag) + { + /* remove scrolling events */ + if (buf->xtext->io_tag) + { + g_source_remove (buf->xtext->io_tag); + buf->xtext->io_tag = 0; + } + buf->xtext->add_io_tag = g_timeout_add (REFRESH_TIMEOUT * 2, + (GSourceFunc) + gtk_xtext_render_page_timeout, + buf->xtext); + } + } else if (buf->scrollbar_down) + { + buf->old_value = buf->num_lines - buf->xtext->adj->page_size; + if (buf->old_value < 0) + buf->old_value = 0; + } +} + +/* the main two public functions */ + +void +gtk_xtext_append_indent (xtext_buffer *buf, + unsigned char *left_text, int left_len, + unsigned char *right_text, int right_len, + time_t stamp) +{ + textentry *ent; + unsigned char *str; + int space; + int tempindent; + int left_width; + + if (left_len == -1) + left_len = strlen (left_text); + + if (right_len == -1) + right_len = strlen (right_text); + + if (right_len >= sizeof (buf->xtext->scratch_buffer)) + right_len = sizeof (buf->xtext->scratch_buffer) - 1; + + if (right_text[right_len-1] == '\n') + right_len--; + + ent = malloc (left_len + right_len + 2 + sizeof (textentry)); + str = (unsigned char *) ent + sizeof (textentry); + + memcpy (str, left_text, left_len); + str[left_len] = ' '; + memcpy (str + left_len + 1, right_text, right_len); + str[left_len + 1 + right_len] = 0; + + left_width = gtk_xtext_text_width (buf->xtext, left_text, left_len, NULL); + + ent->left_len = left_len; + ent->str = str; + ent->str_len = left_len + 1 + right_len; + ent->indent = (buf->indent - left_width) - buf->xtext->space_width; + + if (buf->time_stamp) + space = buf->xtext->stamp_width; + else + space = 0; + + /* do we need to auto adjust the separator position? */ + if (buf->xtext->auto_indent && ent->indent < MARGIN + space) + { + tempindent = MARGIN + space + buf->xtext->space_width + left_width; + + if (tempindent > buf->indent) + buf->indent = tempindent; + + if (buf->indent > buf->xtext->max_auto_indent) + buf->indent = buf->xtext->max_auto_indent; + + gtk_xtext_fix_indent (buf); + gtk_xtext_recalc_widths (buf, FALSE); + + ent->indent = (buf->indent - left_width) - buf->xtext->space_width; + buf->xtext->force_render = TRUE; + } + + gtk_xtext_append_entry (buf, ent, stamp); +} + +void +gtk_xtext_append (xtext_buffer *buf, unsigned char *text, int len) +{ + textentry *ent; + + if (len == -1) + len = strlen (text); + + if (text[len-1] == '\n') + len--; + + if (len >= sizeof (buf->xtext->scratch_buffer)) + len = sizeof (buf->xtext->scratch_buffer) - 1; + + ent = malloc (len + 1 + sizeof (textentry)); + ent->str = (unsigned char *) ent + sizeof (textentry); + ent->str_len = len; + if (len) + memcpy (ent->str, text, len); + ent->str[len] = 0; + ent->indent = 0; + ent->left_len = -1; + + gtk_xtext_append_entry (buf, ent, 0); +} + +gboolean +gtk_xtext_is_empty (xtext_buffer *buf) +{ + return buf->text_first == NULL; +} + +int +gtk_xtext_lastlog (xtext_buffer *out, xtext_buffer *search_area, + int (*cmp_func) (char *, void *), void *userdata) +{ + textentry *ent = search_area->text_first; + int matches = 0; + + while (ent) + { + if (cmp_func (ent->str, userdata)) + { + matches++; + /* copy the text over */ + if (search_area->xtext->auto_indent) + gtk_xtext_append_indent (out, ent->str, ent->left_len, + ent->str + ent->left_len + 1, + ent->str_len - ent->left_len - 1, 0); + else + gtk_xtext_append (out, ent->str, ent->str_len); + /* copy the timestamp over */ + out->text_last->stamp = ent->stamp; + } + ent = ent->next; + } + + return matches; +} + +void +gtk_xtext_foreach (xtext_buffer *buf, GtkXTextForeach func, void *data) +{ + textentry *ent = buf->text_first; + + while (ent) + { + (*func) (buf->xtext, ent->str, data); + ent = ent->next; + } +} + +void +gtk_xtext_set_error_function (GtkXText *xtext, void (*error_function) (int)) +{ + xtext->error_function = error_function; +} + +void +gtk_xtext_set_indent (GtkXText *xtext, gboolean indent) +{ + xtext->auto_indent = indent; +} + +void +gtk_xtext_set_max_indent (GtkXText *xtext, int max_auto_indent) +{ + xtext->max_auto_indent = max_auto_indent; +} + +void +gtk_xtext_set_max_lines (GtkXText *xtext, int max_lines) +{ + xtext->max_lines = max_lines; +} + +void +gtk_xtext_set_show_marker (GtkXText *xtext, gboolean show_marker) +{ + xtext->marker = show_marker; +} + +void +gtk_xtext_set_show_separator (GtkXText *xtext, gboolean show_separator) +{ + xtext->separator = show_separator; +} + +void +gtk_xtext_set_thin_separator (GtkXText *xtext, gboolean thin_separator) +{ + xtext->thinline = thin_separator; +} + +void +gtk_xtext_set_time_stamp (xtext_buffer *buf, gboolean time_stamp) +{ + buf->time_stamp = time_stamp; +} + +void +gtk_xtext_set_tint (GtkXText *xtext, int tint_red, int tint_green, int tint_blue) +{ + xtext->tint_red = tint_red; + xtext->tint_green = tint_green; + xtext->tint_blue = tint_blue; + + /*if (xtext->tint_red != 255 || xtext->tint_green != 255 || xtext->tint_blue != 255) + shaded = TRUE;*/ +} + +void +gtk_xtext_set_urlcheck_function (GtkXText *xtext, int (*urlcheck_function) (GtkWidget *, char *, int)) +{ + xtext->urlcheck_function = urlcheck_function; +} + +void +gtk_xtext_set_wordwrap (GtkXText *xtext, gboolean wordwrap) +{ + xtext->wordwrap = wordwrap; +} + +void +gtk_xtext_reset_marker_pos (GtkXText *xtext) +{ + xtext->buffer->marker_pos = NULL; + dontscroll (xtext->buffer); /* force scrolling off */ + gtk_xtext_render_page (xtext); + xtext->buffer->reset_marker_pos = TRUE; +} + +void +gtk_xtext_buffer_show (GtkXText *xtext, xtext_buffer *buf, int render) +{ + int w, h; + + buf->xtext = xtext; + + if (xtext->buffer == buf) + return; + +/*printf("text_buffer_show: xtext=%p buffer=%p\n", xtext, buf);*/ + + if (xtext->add_io_tag) + { + g_source_remove (xtext->add_io_tag); + xtext->add_io_tag = 0; + } + + if (xtext->io_tag) + { + g_source_remove (xtext->io_tag); + xtext->io_tag = 0; + } + + if (!GTK_WIDGET_REALIZED (GTK_WIDGET (xtext))) + gtk_widget_realize (GTK_WIDGET (xtext)); + + gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &w, &h); + + /* after a font change */ + if (buf->needs_recalc) + { + buf->needs_recalc = FALSE; + gtk_xtext_recalc_widths (buf, TRUE); + } + + /* now change to the new buffer */ + xtext->buffer = buf; + dontscroll (buf); /* force scrolling off */ + xtext->adj->value = buf->old_value; + xtext->adj->upper = buf->num_lines; + if (xtext->adj->upper == 0) + xtext->adj->upper = 1; + /* sanity check */ + else if (xtext->adj->value > xtext->adj->upper - xtext->adj->page_size) + { + /*buf->pagetop_ent = NULL;*/ + xtext->adj->value = xtext->adj->upper - xtext->adj->page_size; + if (xtext->adj->value < 0) + xtext->adj->value = 0; + } + + if (render) + { + /* did the window change size since this buffer was last shown? */ + if (buf->window_width != w) + { + buf->window_width = w; + gtk_xtext_calc_lines (buf, FALSE); + if (buf->scrollbar_down) + gtk_adjustment_set_value (xtext->adj, xtext->adj->upper - + xtext->adj->page_size); + } else if (buf->window_height != h) + { + buf->window_height = h; + buf->pagetop_ent = NULL; + gtk_xtext_adjustment_set (buf, FALSE); + } + + gtk_xtext_render_page (xtext); + gtk_adjustment_changed (xtext->adj); + } else + { + /* avoid redoing the transparency */ + xtext->avoid_trans = TRUE; + } +} + +xtext_buffer * +gtk_xtext_buffer_new (GtkXText *xtext) +{ + xtext_buffer *buf; + + buf = malloc (sizeof (xtext_buffer)); + memset (buf, 0, sizeof (xtext_buffer)); + buf->old_value = -1; + buf->xtext = xtext; + buf->scrollbar_down = TRUE; + buf->indent = xtext->space_width * 2; + dontscroll (buf); + + return buf; +} + +void +gtk_xtext_buffer_free (xtext_buffer *buf) +{ + textentry *ent, *next; + + if (buf->xtext->buffer == buf) + buf->xtext->buffer = buf->xtext->orig_buffer; + + if (buf->xtext->selection_buffer == buf) + buf->xtext->selection_buffer = NULL; + + ent = buf->text_first; + while (ent) + { + next = ent->next; + free (ent); + ent = next; + } + + free (buf); +} diff --git a/src/fe-gtk/xtext.h b/src/fe-gtk/xtext.h new file mode 100644 index 00000000..a37ddc32 --- /dev/null +++ b/src/fe-gtk/xtext.h @@ -0,0 +1,275 @@ +#ifndef __XTEXT_H__ +#define __XTEXT_H__ + +#include <gtk/gtkadjustment.h> +#ifdef USE_XFT +#include <X11/Xft/Xft.h> +#endif + +#ifdef USE_SHM +#include <X11/Xlib.h> +#include <sys/ipc.h> +#include <sys/shm.h> +#include <X11/extensions/XShm.h> +#endif + +#define GTK_TYPE_XTEXT (gtk_xtext_get_type ()) +#define GTK_XTEXT(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GTK_TYPE_XTEXT, GtkXText)) +#define GTK_XTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_XTEXT, GtkXTextClass)) +#define GTK_IS_XTEXT(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GTK_TYPE_XTEXT)) +#define GTK_IS_XTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_XTEXT)) +#define GTK_XTEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_XTEXT, GtkXTextClass)) + +#define ATTR_BOLD '\002' +#define ATTR_COLOR '\003' +#define ATTR_BLINK '\006' +#define ATTR_BEEP '\007' +#define ATTR_HIDDEN '\010' +#define ATTR_ITALICS2 '\011' +#define ATTR_RESET '\017' +#define ATTR_REVERSE '\026' +#define ATTR_ITALICS '\035' +#define ATTR_UNDERLINE '\037' + +/* these match palette.h */ +#define XTEXT_MIRC_COLS 32 +#define XTEXT_COLS 37 /* 32 plus 5 for extra stuff below */ +#define XTEXT_MARK_FG 32 /* for marking text */ +#define XTEXT_MARK_BG 33 +#define XTEXT_FG 34 +#define XTEXT_BG 35 +#define XTEXT_MARKER 36 /* for marker line */ + +typedef struct _GtkXText GtkXText; +typedef struct _GtkXTextClass GtkXTextClass; +typedef struct textentry textentry; + +typedef struct { + GtkXText *xtext; /* attached to this widget */ + + gfloat old_value; /* last known adj->value */ + textentry *text_first; + textentry *text_last; + guint16 grid_offset[256]; + + textentry *last_ent_start; /* this basically describes the last rendered */ + textentry *last_ent_end; /* selection. */ + int last_offset_start; + int last_offset_end; + + int last_pixel_pos; + + int pagetop_line; + int pagetop_subline; + textentry *pagetop_ent; /* what's at xtext->adj->value */ + + int num_lines; + int indent; /* position of separator (pixels) from left */ + + textentry *marker_pos; + + int window_width; /* window size when last rendered. */ + int window_height; + + unsigned int time_stamp:1; + unsigned int scrollbar_down:1; + unsigned int needs_recalc:1; + unsigned int grid_dirty:1; + unsigned int marker_seen:1; + unsigned int reset_marker_pos:1; +} xtext_buffer; + +struct _GtkXText +{ + GtkWidget widget; + + xtext_buffer *buffer; + xtext_buffer *orig_buffer; + xtext_buffer *selection_buffer; + +#ifdef USE_SHM + XShmSegmentInfo shminfo; +#endif + + GtkAdjustment *adj; + GdkPixmap *pixmap; /* 0 = use palette[19] */ + GdkDrawable *draw_buf; /* points to ->window */ + GdkCursor *hand_cursor; + GdkCursor *resize_cursor; + + int pixel_offset; /* amount of pixels the top line is chopped by */ + + int last_win_x; + int last_win_y; + int last_win_h; + int last_win_w; + + int tint_red; + int tint_green; + int tint_blue; + + GdkGC *bgc; /* backing pixmap */ + GdkGC *fgc; /* text foreground color */ + GdkGC *light_gc; /* sep bar */ + GdkGC *dark_gc; + GdkGC *thin_gc; + GdkGC *marker_gc; + gulong palette[XTEXT_COLS]; + + gint io_tag; /* for delayed refresh events */ + gint add_io_tag; /* "" when adding new text */ + gint scroll_tag; /* marking-scroll timeout */ + gulong vc_signal_tag; /* signal handler for "value_changed" adj */ + + int select_start_adj; /* the adj->value when the selection started */ + int select_start_x; + int select_start_y; + int select_end_x; + int select_end_y; + + int max_lines; + + int col_fore; + int col_back; + + int depth; /* gdk window depth */ + + char num[8]; /* for parsing mirc color */ + int nc; /* offset into xtext->num */ + + textentry *hilight_ent; + int hilight_start; + int hilight_end; + + guint16 fontwidth[128]; /* each char's width, only the ASCII ones */ + +#ifdef USE_XFT + XftColor color[XTEXT_COLS]; + XftColor *xft_fg; + XftColor *xft_bg; /* both point into color[20] */ + XftDraw *xftdraw; + XftFont *font; + XftFont *ifont; /* italics */ +#else + struct pangofont + { + PangoFontDescription *font; + PangoFontDescription *ifont; /* italics */ + int ascent; + int descent; + } *font, pango_font; + PangoLayout *layout; +#endif + + int fontsize; + int space_width; /* width (pixels) of the space " " character */ + int stamp_width; /* width of "[88:88:88]" */ + int max_auto_indent; + + unsigned char scratch_buffer[4096]; + + void (*error_function) (int type); + int (*urlcheck_function) (GtkWidget * xtext, char *word, int len); + + int jump_out_offset; /* point at which to stop rendering */ + int jump_in_offset; /* "" start rendering */ + + int ts_x; /* ts origin for ->bgc GC */ + int ts_y; + + int clip_x; /* clipping (x directions) */ + int clip_x2; /* from x to x2 */ + + int clip_y; /* clipping (y directions) */ + int clip_y2; /* from y to y2 */ + + /* current text states */ + unsigned int bold:1; + unsigned int underline:1; + unsigned int italics:1; + unsigned int hidden:1; + + /* text parsing states */ + unsigned int parsing_backcolor:1; + unsigned int parsing_color:1; + unsigned int backcolor:1; + + /* various state information */ + unsigned int moving_separator:1; + unsigned int word_or_line_select:1; + unsigned int button_down:1; + unsigned int hilighting:1; + unsigned int dont_render:1; + unsigned int dont_render2:1; + unsigned int cursor_hand:1; + unsigned int cursor_resize:1; + unsigned int skip_border_fills:1; + unsigned int skip_stamp:1; + unsigned int mark_stamp:1; /* Cut&Paste with stamps? */ + unsigned int force_stamp:1; /* force redrawing it */ + unsigned int render_hilights_only:1; + unsigned int in_hilight:1; + unsigned int un_hilight:1; + unsigned int recycle:1; + unsigned int avoid_trans:1; + unsigned int force_render:1; + unsigned int shm:1; + unsigned int color_paste:1; /* CTRL was pressed when selection finished */ + + /* settings/prefs */ + unsigned int auto_indent:1; + unsigned int thinline:1; + unsigned int transparent:1; + unsigned int shaded:1; + unsigned int marker:1; + unsigned int separator:1; + unsigned int wordwrap:1; + unsigned int overdraw:1; + unsigned int ignore_hidden:1; /* rawlog uses this */ +}; + +struct _GtkXTextClass +{ + GtkWidgetClass parent_class; + void (*word_click) (GtkXText * xtext, char *word, GdkEventButton * event); +}; + +GtkWidget *gtk_xtext_new (GdkColor palette[], int separator); +void gtk_xtext_append (xtext_buffer *buf, unsigned char *text, int len); +void gtk_xtext_append_indent (xtext_buffer *buf, + unsigned char *left_text, int left_len, + unsigned char *right_text, int right_len, + time_t stamp); +int gtk_xtext_set_font (GtkXText *xtext, char *name); +void gtk_xtext_set_background (GtkXText * xtext, GdkPixmap * pixmap, gboolean trans); +void gtk_xtext_set_palette (GtkXText * xtext, GdkColor palette[]); +void gtk_xtext_clear (xtext_buffer *buf, int lines); +void gtk_xtext_save (GtkXText * xtext, int fh); +void gtk_xtext_refresh (GtkXText * xtext, int do_trans); +int gtk_xtext_lastlog (xtext_buffer *out, xtext_buffer *search_area, int (*cmp_func) (char *, void *userdata), void *userdata); +textentry *gtk_xtext_search (GtkXText * xtext, const gchar *text, textentry *start, gboolean case_match, gboolean backward); +void gtk_xtext_reset_marker_pos (GtkXText *xtext); +void gtk_xtext_check_marker_visibility(GtkXText *xtext); + +gboolean gtk_xtext_is_empty (xtext_buffer *buf); +typedef void (*GtkXTextForeach) (GtkXText *xtext, unsigned char *text, void *data); +void gtk_xtext_foreach (xtext_buffer *buf, GtkXTextForeach func, void *data); + +void gtk_xtext_set_error_function (GtkXText *xtext, void (*error_function) (int)); +void gtk_xtext_set_indent (GtkXText *xtext, gboolean indent); +void gtk_xtext_set_max_indent (GtkXText *xtext, int max_auto_indent); +void gtk_xtext_set_max_lines (GtkXText *xtext, int max_lines); +void gtk_xtext_set_show_marker (GtkXText *xtext, gboolean show_marker); +void gtk_xtext_set_show_separator (GtkXText *xtext, gboolean show_separator); +void gtk_xtext_set_thin_separator (GtkXText *xtext, gboolean thin_separator); +void gtk_xtext_set_time_stamp (xtext_buffer *buf, gboolean timestamp); +void gtk_xtext_set_tint (GtkXText *xtext, int tint_red, int tint_green, int tint_blue); +void gtk_xtext_set_urlcheck_function (GtkXText *xtext, int (*urlcheck_function) (GtkWidget *, char *, int)); +void gtk_xtext_set_wordwrap (GtkXText *xtext, gboolean word_wrap); + +xtext_buffer *gtk_xtext_buffer_new (GtkXText *xtext); +void gtk_xtext_buffer_free (xtext_buffer *buf); +void gtk_xtext_buffer_show (GtkXText *xtext, xtext_buffer *buf, int render); +GType gtk_xtext_get_type (void); + +#endif diff --git a/src/fe-text/Makefile.am b/src/fe-text/Makefile.am new file mode 100644 index 00000000..ef48203e --- /dev/null +++ b/src/fe-text/Makefile.am @@ -0,0 +1,9 @@ +bin_PROGRAMS = xchat-text + +EXTRA_DIST = README + +INCLUDES = $(COMMON_CFLAGS) + +xchat_text_LDADD = ../common/libxchatcommon.a $(COMMON_LIBS) +xchat_text_SOURCES = fe-text.c fe-text.h + diff --git a/src/fe-text/README b/src/fe-text/README new file mode 100644 index 00000000..bb760aae --- /dev/null +++ b/src/fe-text/README @@ -0,0 +1,5 @@ +fe-text README +~~~~~~~~~~~~~~ + +This is an experimental text frontend for X-Chat. +If anyone wants to add ncurses support, they are welcome to. diff --git a/src/fe-text/fe-text.c b/src/fe-text/fe-text.c new file mode 100644 index 00000000..2bc2e649 --- /dev/null +++ b/src/fe-text/fe-text.c @@ -0,0 +1,862 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_STRINGS_H +#include <strings.h> +#endif +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> +#include <ctype.h> +#include "../common/xchat.h" +#include "../common/xchatc.h" +#include "../common/outbound.h" +#include "../common/util.h" +#include "../common/fe.h" +#include "fe-text.h" + + +static GSList *tmr_list; /* timer list */ +static int tmr_list_count; +static GSList *se_list; /* socket event list */ +static int se_list_count; +static int done = FALSE; /* finished ? */ + + +static void +send_command (char *cmd) +{ + handle_multiline (sess_list->data, cmd, TRUE, FALSE); +} + +static void +read_stdin (void) +{ + int len, i = 0; + static int pos = 0; + static char inbuf[1024]; + char tmpbuf[512]; + + len = read (STDIN_FILENO, tmpbuf, sizeof tmpbuf - 1); + + while (i < len) + { + switch (tmpbuf[i]) + { + case '\r': + break; + + case '\n': + inbuf[pos] = 0; + pos = 0; + send_command (inbuf); + break; + + default: + inbuf[pos] = tmpbuf[i]; + if (pos < (sizeof inbuf - 2)) + pos++; + } + i++; + } +} + +static int done_intro = 0; + +void +fe_new_window (struct session *sess, int focus) +{ + char buf[512]; + + sess->gui = malloc (4); + + if (!sess->server->front_session) + sess->server->front_session = sess; + if (!sess->server->server_session) + sess->server->server_session = sess; + if (!current_tab) + current_tab = sess; + + if (done_intro) + return; + done_intro = 1; + + snprintf (buf, sizeof (buf), + "\n" + " \017xchat \00310"PACKAGE_VERSION"\n" + " \017Running on \00310%s \017glib \00310%d.%d.%d\n" + " \017This binary compiled \00310"__DATE__"\017\n", + get_cpu_str(), + glib_major_version, glib_minor_version, glib_micro_version); + fe_print_text (sess, buf, 0); + + fe_print_text (sess, "\n\nCompiled in Features\0032:\017 " +#ifdef USE_PLUGIN + "Plugin " +#endif +#ifdef ENABLE_NLS + "NLS " +#endif +#ifdef USE_OPENSSL + "OpenSSL " +#endif +#ifdef USE_IPV6 + "IPv6" +#endif + "\n\n", 0); + fflush (stdout); + fflush (stdin); +} + +static int +get_stamp_str (time_t tim, char *dest, int size) +{ + return strftime (dest, size, prefs.stamp_format, localtime (&tim)); +} + +static int +timecat (char *buf) +{ + char stampbuf[64]; + + get_stamp_str (time (0), stampbuf, sizeof (stampbuf)); + strcat (buf, stampbuf); + return strlen (stampbuf); +} + +/* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 */ +static const short colconv[] = { 0, 7, 4, 2, 1, 3, 5, 11, 13, 12, 6, 16, 14, 15, 10, 7 }; + +void +fe_print_text (struct session *sess, char *text, time_t stamp) +{ + int dotime = FALSE; + char num[8]; + int reverse = 0, under = 0, bold = 0, + comma, k, i = 0, j = 0, len = strlen (text); + unsigned char *newtext = malloc (len + 1024); + + if (prefs.timestamp) + { + newtext[0] = 0; + j += timecat (newtext); + } + while (i < len) + { + if (dotime && text[i] != 0) + { + dotime = FALSE; + newtext[j] = 0; + j += timecat (newtext); + } + switch (text[i]) + { + case 3: + i++; + if (!isdigit (text[i])) + { + newtext[j] = 27; + j++; + newtext[j] = '['; + j++; + newtext[j] = 'm'; + j++; + i--; + goto jump2; + } + k = 0; + comma = FALSE; + while (i < len) + { + if (text[i] >= '0' && text[i] <= '9' && k < 2) + { + num[k] = text[i]; + k++; + } else + { + int col, mirc; + num[k] = 0; + newtext[j] = 27; + j++; + newtext[j] = '['; + j++; + if (k == 0) + { + newtext[j] = 'm'; + j++; + } else + { + if (comma) + col = 40; + else + col = 30; + mirc = atoi (num); + mirc = colconv[mirc]; + if (mirc > 9) + { + mirc += 50; + sprintf ((char *) &newtext[j], "%dm", mirc + col); + } else + { + sprintf ((char *) &newtext[j], "%dm", mirc + col); + } + j = strlen (newtext); + } + switch (text[i]) + { + case ',': + comma = TRUE; + break; + default: + goto jump; + } + k = 0; + } + i++; + } + break; + case '\026': /* REVERSE */ + if (reverse) + { + reverse = FALSE; + strcpy (&newtext[j], "\033[27m"); + } else + { + reverse = TRUE; + strcpy (&newtext[j], "\033[7m"); + } + j = strlen (newtext); + break; + case '\037': /* underline */ + if (under) + { + under = FALSE; + strcpy (&newtext[j], "\033[24m"); + } else + { + under = TRUE; + strcpy (&newtext[j], "\033[4m"); + } + j = strlen (newtext); + break; + case '\002': /* bold */ + if (bold) + { + bold = FALSE; + strcpy (&newtext[j], "\033[22m"); + } else + { + bold = TRUE; + strcpy (&newtext[j], "\033[1m"); + } + j = strlen (newtext); + break; + case '\007': + if (!prefs.filterbeep) + { + newtext[j] = text[i]; + j++; + } + break; + case '\017': /* reset all */ + strcpy (&newtext[j], "\033[m"); + j += 3; + reverse = FALSE; + bold = FALSE; + under = FALSE; + break; + case '\t': + newtext[j] = ' '; + j++; + break; + case '\n': + newtext[j] = '\r'; + j++; + if (prefs.timestamp) + dotime = TRUE; + default: + newtext[j] = text[i]; + j++; + } + jump2: + i++; + jump: + i += 0; + } + newtext[j] = 0; + write (STDOUT_FILENO, newtext, j); + free (newtext); +} + +void +fe_timeout_remove (int tag) +{ + timerevent *te; + GSList *list; + + list = tmr_list; + while (list) + { + te = (timerevent *) list->data; + if (te->tag == tag) + { + tmr_list = g_slist_remove (tmr_list, te); + free (te); + return; + } + list = list->next; + } +} + +int +fe_timeout_add (int interval, void *callback, void *userdata) +{ + struct timeval now; + timerevent *te = malloc (sizeof (timerevent)); + + tmr_list_count++; /* this overflows at 2.2Billion, who cares!! */ + + te->tag = tmr_list_count; + te->interval = interval; + te->callback = callback; + te->userdata = userdata; + + gettimeofday (&now, NULL); + te->next_call = now.tv_sec * 1000 + (now.tv_usec / 1000) + te->interval; + + tmr_list = g_slist_prepend (tmr_list, te); + + return te->tag; +} + +void +fe_input_remove (int tag) +{ + socketevent *se; + GSList *list; + + list = se_list; + while (list) + { + se = (socketevent *) list->data; + if (se->tag == tag) + { + se_list = g_slist_remove (se_list, se); + free (se); + return; + } + list = list->next; + } +} + +int +fe_input_add (int sok, int flags, void *func, void *data) +{ + socketevent *se = malloc (sizeof (socketevent)); + + se_list_count++; /* this overflows at 2.2Billion, who cares!! */ + + se->tag = se_list_count; + se->sok = sok; + se->rread = flags & FIA_READ; + se->wwrite = flags & FIA_WRITE; + se->eexcept = flags & FIA_EX; + se->callback = func; + se->userdata = data; + se_list = g_slist_prepend (se_list, se); + + return se->tag; +} + +int +fe_args (int argc, char *argv[]) +{ + if (argc > 1) + { + if (!strcasecmp (argv[1], "--version") || !strcasecmp (argv[1], "-v")) + { + puts (PACKAGE_VERSION); + return 0; + } + } + return -1; +} + +void +fe_init (void) +{ + se_list = 0; + se_list_count = 0; + tmr_list = 0; + tmr_list_count = 0; + prefs.autosave = 0; + prefs.use_server_tab = 0; + prefs.autodialog = 0; + prefs.lagometer = 0; + prefs.slist_skip = 1; +} + +void +fe_main (void) +{ + struct timeval timeout, now; + socketevent *se; + timerevent *te; + fd_set rd, wd, ex; + GSList *list; + guint64 shortest, delay; + + if (!sess_list) + new_ircwindow (NULL, NULL, SESS_SERVER, 0); + +#ifdef ENABLE_NLS + bindtextdomain (GETTEXT_PACKAGE, PREFIX"/share/locale"); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); +#endif + + while (!done) + { + FD_ZERO (&rd); + FD_ZERO (&wd); + FD_ZERO (&ex); + + list = se_list; + while (list) + { + se = (socketevent *) list->data; + if (se->rread) + FD_SET (se->sok, &rd); + if (se->wwrite) + FD_SET (se->sok, &wd); + if (se->eexcept) + FD_SET (se->sok, &ex); + list = list->next; + } + + FD_SET (STDIN_FILENO, &rd); /* for reading keyboard */ + + /* find the shortest timeout event */ + shortest = 0; + list = tmr_list; + while (list) + { + te = (timerevent *) list->data; + if (te->next_call < shortest || shortest == 0) + shortest = te->next_call; + list = list->next; + } + gettimeofday (&now, NULL); + delay = shortest - ((now.tv_sec * 1000) + (now.tv_usec / 1000)); + timeout.tv_sec = delay / 1000; + timeout.tv_usec = (delay % 1000) * 1000; + + select (FD_SETSIZE, &rd, &wd, &ex, &timeout); + + if (FD_ISSET (STDIN_FILENO, &rd)) + read_stdin (); + + /* set all checked flags to false */ + list = se_list; + while (list) + { + se = (socketevent *) list->data; + se->checked = 0; + list = list->next; + } + + /* check all the socket callbacks */ + list = se_list; + while (list) + { + se = (socketevent *) list->data; + se->checked = 1; + if (se->rread && FD_ISSET (se->sok, &rd)) + { + se->callback (NULL, 1, se->userdata); + } else if (se->wwrite && FD_ISSET (se->sok, &wd)) + { + se->callback (NULL, 2, se->userdata); + } else if (se->eexcept && FD_ISSET (se->sok, &ex)) + { + se->callback (NULL, 4, se->userdata); + } + list = se_list; + if (list) + { + se = (socketevent *) list->data; + while (se->checked) + { + list = list->next; + if (!list) + break; + se = (socketevent *) list->data; + } + } + } + + /* now check our list of timeout events, some might need to be called! */ + gettimeofday (&now, NULL); + list = tmr_list; + while (list) + { + te = (timerevent *) list->data; + list = list->next; + if (now.tv_sec * 1000 + (now.tv_usec / 1000) >= te->next_call) + { + /* if the callback returns 0, it must be removed */ + if (te->callback (te->userdata) == 0) + { + fe_timeout_remove (te->tag); + } else + { + te->next_call = now.tv_sec * 1000 + (now.tv_usec / 1000) + te->interval; + } + } + } + + } +} + +void +fe_exit (void) +{ + done = TRUE; +} + +void +fe_new_server (struct server *serv) +{ + serv->gui = malloc (4); +} + +void +fe_message (char *msg, int flags) +{ + puts (msg); +} + +void +fe_close_window (struct session *sess) +{ + session_free (sess); + done = TRUE; +} + +void +fe_beep (void) +{ + putchar (7); +} + +void +fe_add_rawlog (struct server *serv, char *text, int len, int outbound) +{ +} +void +fe_set_topic (struct session *sess, char *topic, char *stripped_topic) +{ +} +void +fe_cleanup (void) +{ +} +void +fe_set_hilight (struct session *sess) +{ +} +void +fe_set_tab_color (struct session *sess, int col) +{ +} +void +fe_update_mode_buttons (struct session *sess, char mode, char sign) +{ +} +void +fe_update_channel_key (struct session *sess) +{ +} +void +fe_update_channel_limit (struct session *sess) +{ +} +int +fe_is_chanwindow (struct server *serv) +{ + return 0; +} + +void +fe_add_chan_list (struct server *serv, char *chan, char *users, char *topic) +{ +} +void +fe_chan_list_end (struct server *serv) +{ +} +int +fe_is_banwindow (struct session *sess) +{ + return 0; +} +void +fe_add_ban_list (struct session *sess, char *mask, char *who, char *when, int is_exemption) +{ +} +void +fe_ban_list_end (struct session *sess, int is_exemption) +{ +} +void +fe_notify_update (char *name) +{ +} +void +fe_notify_ask (char *name, char *networks) +{ +} +void +fe_text_clear (struct session *sess, int lines) +{ +} +void +fe_progressbar_start (struct session *sess) +{ +} +void +fe_progressbar_end (struct server *serv) +{ +} +void +fe_userlist_insert (struct session *sess, struct User *newuser, int row, int sel) +{ +} +int +fe_userlist_remove (struct session *sess, struct User *user) +{ + return 0; +} +void +fe_userlist_rehash (struct session *sess, struct User *user) +{ +} +void +fe_userlist_move (struct session *sess, struct User *user, int new_row) +{ +} +void +fe_userlist_numbers (struct session *sess) +{ +} +void +fe_userlist_clear (struct session *sess) +{ +} +void +fe_userlist_set_selected (struct session *sess) +{ +} +void +fe_dcc_add (struct DCC *dcc) +{ +} +void +fe_dcc_update (struct DCC *dcc) +{ +} +void +fe_dcc_remove (struct DCC *dcc) +{ +} +void +fe_clear_channel (struct session *sess) +{ +} +void +fe_session_callback (struct session *sess) +{ +} +void +fe_server_callback (struct server *serv) +{ +} +void +fe_url_add (const char *text) +{ +} +void +fe_pluginlist_update (void) +{ +} +void +fe_buttons_update (struct session *sess) +{ +} +void +fe_dlgbuttons_update (struct session *sess) +{ +} +void +fe_dcc_send_filereq (struct session *sess, char *nick, int maxcps, int passive) +{ +} +void +fe_set_channel (struct session *sess) +{ +} +void +fe_set_title (struct session *sess) +{ +} +void +fe_set_nonchannel (struct session *sess, int state) +{ +} +void +fe_set_nick (struct server *serv, char *newnick) +{ +} +void +fe_change_nick (struct server *serv, char *nick, char *newnick) +{ +} +void +fe_ignore_update (int level) +{ +} +int +fe_dcc_open_recv_win (int passive) +{ + return FALSE; +} +int +fe_dcc_open_send_win (int passive) +{ + return FALSE; +} +int +fe_dcc_open_chat_win (int passive) +{ + return FALSE; +} +void +fe_userlist_hide (session * sess) +{ +} +void +fe_lastlog (session * sess, session * lastlog_sess, char *sstr, gboolean regexp) +{ +} +void +fe_set_lag (server * serv, int lag) +{ +} +void +fe_set_throttle (server * serv) +{ +} +void +fe_set_away (server *serv) +{ +} +void +fe_serverlist_open (session *sess) +{ +} +void +fe_get_str (char *prompt, char *def, void *callback, void *ud) +{ +} +void +fe_get_int (char *prompt, int def, void *callback, void *ud) +{ +} +void +fe_idle_add (void *func, void *data) +{ +} +void +fe_ctrl_gui (session *sess, fe_gui_action action, int arg) +{ +} +int +fe_gui_info (session *sess, int info_type) +{ + return -1; +} +void * +fe_gui_info_ptr (session *sess, int info_type) +{ + return NULL; +} +void fe_confirm (const char *message, void (*yesproc)(void *), void (*noproc)(void *), void *ud) +{ +} +char *fe_get_inputbox_contents (struct session *sess) +{ + return NULL; +} +void fe_set_inputbox_contents (struct session *sess, char *text) +{ +} +int fe_get_inputbox_cursor (struct session *sess) +{ + return 0; +} +void fe_set_inputbox_cursor (struct session *sess, int delta, int pos) +{ +} +void fe_open_url (const char *url) +{ +} +void fe_menu_del (menu_entry *me) +{ +} +char *fe_menu_add (menu_entry *me) +{ + return NULL; +} +void fe_menu_update (menu_entry *me) +{ +} +void fe_uselect (struct session *sess, char *word[], int do_clear, int scroll_to) +{ +} +void +fe_server_event (server *serv, int type, int arg) +{ +} +void +fe_flash_window (struct session *sess) +{ +} +void fe_get_file (const char *title, char *initial, + void (*callback) (void *userdata, char *file), void *userdata, + int flags) +{ +} +void fe_tray_set_flash (const char *filename1, const char *filename2, int timeout){} +void fe_tray_set_file (const char *filename){} +void fe_tray_set_icon (feicon icon){} +void fe_tray_set_tooltip (const char *text){} +void fe_tray_set_balloon (const char *title, const char *text){} +void fe_userlist_update (session *sess, struct User *user){} diff --git a/src/fe-text/fe-text.h b/src/fe-text/fe-text.h new file mode 100644 index 00000000..b8afa284 --- /dev/null +++ b/src/fe-text/fe-text.h @@ -0,0 +1,29 @@ + +typedef int (*socket_callback) (void *source, int condition, void *user_data); +typedef int (*timer_callback) (void *user_data); + +struct socketeventRec +{ + socket_callback callback; + void *userdata; + int sok; + int tag; + int rread:1; + int wwrite:1; + int eexcept:1; + int checked:1; +}; + +typedef struct socketeventRec socketevent; + + +struct timerRec +{ + timer_callback callback; + void *userdata; + int interval; + int tag; + guint64 next_call; /* miliseconds */ +}; + +typedef struct timerRec timerevent; diff --git a/src/pixmaps/Makefile.am b/src/pixmaps/Makefile.am new file mode 100644 index 00000000..5476f9d4 --- /dev/null +++ b/src/pixmaps/Makefile.am @@ -0,0 +1,20 @@ +## Process this file with automake to produce Makefile.in + +LIST = traymsgpng $(srcdir)/message.png \ + trayhilightpng $(srcdir)/highlight.png \ + trayfilepng $(srcdir)/fileoffer.png \ + bookpng $(srcdir)/book.png \ + hoppng $(srcdir)/hop.png \ + oppng $(srcdir)/op.png \ + purplepng $(srcdir)/purple.png \ + redpng $(srcdir)/red.png \ + voicepng $(srcdir)/voice.png \ + xchatpng $(srcdir)/../../xchat.png + +PNGS = message.png highlight.png fileoffer.png book.png hop.png op.png purple.png red.png voice.png +noinst_HEADERS = inline_pngs.h +CLEANFILES = $(noinst_HEADERS) +EXTRA_DIST = $(PNGS) + +inline_pngs.h: $(PNGS) + @gdkpixbufcsourcepath@ --raw --build-list $(LIST) > $(srcdir)/inline_pngs.h diff --git a/src/pixmaps/book.png b/src/pixmaps/book.png new file mode 100644 index 00000000..1f1e4301 --- /dev/null +++ b/src/pixmaps/book.png Binary files differdiff --git a/src/pixmaps/fileoffer.png b/src/pixmaps/fileoffer.png new file mode 100644 index 00000000..fb16cb22 --- /dev/null +++ b/src/pixmaps/fileoffer.png Binary files differdiff --git a/src/pixmaps/highlight.png b/src/pixmaps/highlight.png new file mode 100644 index 00000000..818f60f1 --- /dev/null +++ b/src/pixmaps/highlight.png Binary files differdiff --git a/src/pixmaps/hop.png b/src/pixmaps/hop.png new file mode 100644 index 00000000..30a84601 --- /dev/null +++ b/src/pixmaps/hop.png Binary files differdiff --git a/src/pixmaps/message.png b/src/pixmaps/message.png new file mode 100644 index 00000000..de09c2ae --- /dev/null +++ b/src/pixmaps/message.png Binary files differdiff --git a/src/pixmaps/op.png b/src/pixmaps/op.png new file mode 100644 index 00000000..8b2f4f9f --- /dev/null +++ b/src/pixmaps/op.png Binary files differdiff --git a/src/pixmaps/purple.png b/src/pixmaps/purple.png new file mode 100644 index 00000000..5910d3f0 --- /dev/null +++ b/src/pixmaps/purple.png Binary files differdiff --git a/src/pixmaps/red.png b/src/pixmaps/red.png new file mode 100644 index 00000000..cb940a33 --- /dev/null +++ b/src/pixmaps/red.png Binary files differdiff --git a/src/pixmaps/voice.png b/src/pixmaps/voice.png new file mode 100644 index 00000000..8bbf7a57 --- /dev/null +++ b/src/pixmaps/voice.png Binary files differdiff --git a/src/version-script b/src/version-script new file mode 100644 index 00000000..048c1f55 --- /dev/null +++ b/src/version-script @@ -0,0 +1,34 @@ +EXPORTED { + global: + xchat_hook_command; + xchat_hook_server; + xchat_hook_print; + xchat_hook_timer; + xchat_hook_fd; + xchat_unhook; + xchat_print; + xchat_printf; + xchat_command; + xchat_commandf; + xchat_nickcmp; + xchat_set_context; + xchat_find_context; + xchat_get_context; + xchat_get_info; + xchat_get_prefs; + xchat_list_get; + xchat_list_free; + xchat_list_fields; + xchat_list_next; + xchat_list_str; + xchat_list_int; + xchat_plugingui_add; + xchat_plugingui_remove; + xchat_emit_print; + xchat_list_time; + xchat_gettext; + xchat_send_modes; + xchat_strip; + xchat_free; + local: *; +}; |