diff options
Diffstat (limited to 'src/fe-gtk/plugin-tray.c')
-rw-r--r-- | src/fe-gtk/plugin-tray.c | 852 |
1 files changed, 852 insertions, 0 deletions
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; +} |