Files
zoitechat/src/fe-gtk/plugin-tray.c
deepend 0a0dbd384c Added GTK3 icon theme change handling to refresh themed icons and reapply the current tray icon state after theme updates.
Disconnects the GTK3 icon theme signal during backend cleanup to avoid stale handlers.
2026-01-31 13:24:04 -07:00

1345 lines
29 KiB
C

/* X-Chat
* Copyright (C) 2006-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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <string.h>
#include "../common/zoitechat-plugin.h"
#include "../common/zoitechat.h"
#include "../common/zoitechatc.h"
#include "../common/inbound.h"
#include "../common/server.h"
#include "../common/fe.h"
#include "../common/util.h"
#include "../common/outbound.h"
#include "fe-gtk.h"
#include "pixmaps.h"
#include "maingui.h"
#include "menu.h"
#include "gtkutil.h"
#if HAVE_GTK3
#include <gio/gio.h>
#if defined(HAVE_AYATANA_APPINDICATOR)
#include <libayatana-appindicator/app-indicator.h>
#else
#include <libappindicator/app-indicator.h>
#endif
#define ICON_TRAY_PREFERENCES "preferences-system"
#define ICON_TRAY_QUIT "application-exit"
#endif
#if !HAVE_GTK3
#define ICON_TRAY_PREFERENCES GTK_STOCK_PREFERENCES
#define ICON_TRAY_QUIT GTK_STOCK_QUIT
#endif
#ifndef WIN32
#include <unistd.h>
#endif
typedef enum /* current icon status */
{
TRAY_ICON_NONE,
TRAY_ICON_NORMAL,
TRAY_ICON_MESSAGE,
TRAY_ICON_HIGHLIGHT,
TRAY_ICON_FILEOFFER,
TRAY_ICON_CUSTOM1,
TRAY_ICON_CUSTOM2
} TrayIconState;
typedef enum
{
WS_FOCUSED,
WS_NORMAL,
WS_HIDDEN
} WinStatus;
#if HAVE_GTK3
/* GTK3: use AppIndicator/StatusNotifier item for tray integration. */
typedef GIcon *TrayIcon;
typedef GIcon *TrayCustomIcon;
#define tray_icon_free(i) g_object_unref(i)
#define ICON_NORMAL_NAME "net.zoite.Zoitechat"
#define ICON_MSG_NAME "mail-unread"
#define ICON_HILIGHT_NAME "dialog-warning"
#define ICON_FILE_NAME "folder-download"
static TrayIcon tray_icon_normal;
static TrayIcon tray_icon_msg;
static TrayIcon tray_icon_hilight;
static TrayIcon tray_icon_file;
#define ICON_NORMAL tray_icon_normal
#define ICON_MSG tray_icon_msg
#define ICON_HILIGHT tray_icon_hilight
#define ICON_FILE tray_icon_file
#endif
#if !HAVE_GTK3
typedef GdkPixbuf* TrayIcon;
typedef GdkPixbuf* TrayCustomIcon;
#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_tray_normal
#define ICON_MSG pix_tray_message
#define ICON_HILIGHT pix_tray_highlight
#define ICON_FILE pix_tray_fileoffer
#endif
#define TIMEOUT 500
void tray_apply_setup (void);
static gboolean tray_menu_try_restore (void);
static void tray_cleanup (void);
static void tray_init (void);
static void tray_set_icon_state (TrayIcon icon, TrayIconState state);
static void tray_menu_restore_cb (GtkWidget *item, gpointer userdata);
static void tray_menu_notify_cb (GObject *tray, GParamSpec *pspec, gpointer user_data);
#if HAVE_GTK3
static void tray_menu_show_cb (GtkWidget *menu, gpointer userdata);
#endif
#if !HAVE_GTK3
static void tray_menu_cb (GtkWidget *widget, guint button, guint time, gpointer userdata);
#endif
typedef struct
{
gboolean (*init)(void);
void (*set_icon)(TrayIcon icon);
void (*set_tooltip)(const char *text);
gboolean (*is_embedded)(void);
void (*cleanup)(void);
} TrayBackendOps;
#if HAVE_GTK3
static AppIndicator *tray_indicator;
static GtkWidget *tray_menu;
#endif
#if !HAVE_GTK3
static GtkStatusIcon *tray_status_icon;
#endif
static gboolean tray_backend_active = FALSE;
static gint flash_tag;
static TrayIconState tray_icon_state;
static TrayIcon tray_flash_icon;
static TrayIconState tray_flash_state;
#if !HAVE_GTK3 && defined(WIN32)
static guint tray_menu_timer;
static gint64 tray_menu_inactivetime;
#endif
static zoitechat_plugin *ph;
static TrayCustomIcon custom_icon1;
static TrayCustomIcon 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;
static int tray_restore_timer = 0;
#if HAVE_GTK3
static TrayCustomIcon
tray_icon_from_file (const char *filename)
{
GFile *file;
TrayCustomIcon icon;
if (!filename)
return NULL;
file = g_file_new_for_path (filename);
icon = g_file_icon_new (file);
g_object_unref (file);
return icon;
}
static char *
tray_gtk3_cache_pixbuf_icon (const char *basename, GdkPixbuf *pixbuf)
{
char *cache_dir;
char *filename;
char *path;
if (!pixbuf || !basename)
return NULL;
cache_dir = g_build_filename (g_get_user_cache_dir (), "zoitechat", "tray-icons", NULL);
if (g_mkdir_with_parents (cache_dir, 0700) != 0)
{
g_free (cache_dir);
return NULL;
}
filename = g_strdup_printf ("%s.png", basename);
path = g_build_filename (cache_dir, filename, NULL);
g_free (filename);
g_free (cache_dir);
if (!g_file_test (path, G_FILE_TEST_EXISTS))
gdk_pixbuf_save (pixbuf, path, "png", NULL, NULL);
return path;
}
static char *
tray_gtk3_fallback_icon_path_for_name (const char *name)
{
if (g_strcmp0 (name, ICON_NORMAL_NAME) == 0)
return tray_gtk3_cache_pixbuf_icon ("tray_normal", pix_tray_normal);
if (g_strcmp0 (name, ICON_MSG_NAME) == 0)
return tray_gtk3_cache_pixbuf_icon ("tray_message", pix_tray_message);
if (g_strcmp0 (name, ICON_HILIGHT_NAME) == 0)
return tray_gtk3_cache_pixbuf_icon ("tray_highlight", pix_tray_highlight);
if (g_strcmp0 (name, ICON_FILE_NAME) == 0)
return tray_gtk3_cache_pixbuf_icon ("tray_fileoffer", pix_tray_fileoffer);
return NULL;
}
static void
tray_gtk3_icons_init (void)
{
if (!tray_icon_normal)
tray_icon_normal = g_themed_icon_new (ICON_NORMAL_NAME);
if (!tray_icon_msg)
tray_icon_msg = g_themed_icon_new (ICON_MSG_NAME);
if (!tray_icon_hilight)
tray_icon_hilight = g_themed_icon_new (ICON_HILIGHT_NAME);
if (!tray_icon_file)
tray_icon_file = g_themed_icon_new (ICON_FILE_NAME);
}
static void
tray_gtk3_icons_cleanup (void)
{
g_clear_object (&tray_icon_normal);
g_clear_object (&tray_icon_msg);
g_clear_object (&tray_icon_hilight);
g_clear_object (&tray_icon_file);
}
static GtkIconTheme *tray_gtk3_icon_theme = NULL;
static gulong tray_gtk3_icon_theme_changed_handler = 0;
static void
tray_gtk3_reapply_icon_state (void)
{
switch (tray_icon_state)
{
case TRAY_ICON_NORMAL:
tray_set_icon_state (ICON_NORMAL, TRAY_ICON_NORMAL);
break;
case TRAY_ICON_MESSAGE:
tray_set_icon_state (ICON_MSG, TRAY_ICON_MESSAGE);
break;
case TRAY_ICON_HIGHLIGHT:
tray_set_icon_state (ICON_HILIGHT, TRAY_ICON_HIGHLIGHT);
break;
case TRAY_ICON_FILEOFFER:
tray_set_icon_state (ICON_FILE, TRAY_ICON_FILEOFFER);
break;
case TRAY_ICON_CUSTOM1:
tray_set_icon_state (custom_icon1, TRAY_ICON_CUSTOM1);
break;
case TRAY_ICON_CUSTOM2:
tray_set_icon_state (custom_icon2, TRAY_ICON_CUSTOM2);
break;
case TRAY_ICON_NONE:
default:
break;
}
}
static void
tray_gtk3_theme_changed_cb (GtkIconTheme *theme, gpointer user_data)
{
(void)theme;
(void)user_data;
if (!tray_backend_active)
return;
tray_gtk3_icons_cleanup ();
tray_gtk3_icons_init ();
tray_gtk3_reapply_icon_state ();
}
static const char *
tray_gtk3_icon_to_name (TrayIcon icon, char **allocated)
{
const char * const *names;
GtkIconTheme *theme;
GFile *file;
if (!icon)
return NULL;
if (G_IS_THEMED_ICON (icon))
{
names = g_themed_icon_get_names (G_THEMED_ICON (icon));
if (names && names[0])
{
theme = gtk_icon_theme_get_default ();
if (theme && gtk_icon_theme_has_icon (theme, names[0]))
return names[0];
*allocated = tray_gtk3_fallback_icon_path_for_name (names[0]);
if (*allocated)
return *allocated;
}
}
if (G_IS_FILE_ICON (icon))
{
file = g_file_icon_get_file (G_FILE_ICON (icon));
if (file)
{
*allocated = g_file_get_path (file);
if (*allocated)
return *allocated;
}
}
*allocated = g_icon_to_string (icon);
return *allocated;
}
static void
tray_app_indicator_set_icon (TrayIcon icon)
{
char *icon_name_alloc = NULL;
const char *icon_name;
if (!tray_indicator)
return;
icon_name = tray_gtk3_icon_to_name (icon, &icon_name_alloc);
if (!icon_name)
return;
app_indicator_set_icon_full (tray_indicator, icon_name, _(DISPLAY_NAME));
app_indicator_set_status (tray_indicator, APP_INDICATOR_STATUS_ACTIVE);
g_free (icon_name_alloc);
}
static void
tray_app_indicator_set_tooltip (const char *text)
{
if (!tray_indicator)
return;
app_indicator_set_title (tray_indicator, text ? text : "");
}
static gboolean
tray_app_indicator_is_embedded (void)
{
gboolean connected = TRUE;
GObjectClass *klass;
if (!tray_indicator)
return FALSE;
klass = G_OBJECT_GET_CLASS (tray_indicator);
if (klass && g_object_class_find_property (klass, "connected"))
{
g_object_get (tray_indicator, "connected", &connected, NULL);
}
return connected;
}
static void
tray_app_indicator_cleanup (void)
{
if (tray_indicator)
{
g_object_unref (tray_indicator);
tray_indicator = NULL;
}
if (tray_menu)
{
gtk_widget_destroy (tray_menu);
tray_menu = NULL;
}
}
static gboolean
tray_app_indicator_init (void)
{
GObjectClass *klass;
tray_indicator = app_indicator_new ("zoitechat", ICON_NORMAL_NAME,
APP_INDICATOR_CATEGORY_COMMUNICATIONS);
if (!tray_indicator)
return FALSE;
tray_menu = gtk_menu_new ();
g_signal_connect (G_OBJECT (tray_menu), "show",
G_CALLBACK (tray_menu_show_cb), NULL);
app_indicator_set_menu (tray_indicator, GTK_MENU (tray_menu));
app_indicator_set_status (tray_indicator, APP_INDICATOR_STATUS_ACTIVE);
klass = G_OBJECT_GET_CLASS (tray_indicator);
if (klass && g_object_class_find_property (klass, "connected"))
{
g_signal_connect (G_OBJECT (tray_indicator), "notify::connected",
G_CALLBACK (tray_menu_notify_cb), NULL);
}
return TRUE;
}
static const TrayBackendOps tray_backend_ops = {
tray_app_indicator_init,
tray_app_indicator_set_icon,
tray_app_indicator_set_tooltip,
tray_app_indicator_is_embedded,
tray_app_indicator_cleanup
};
#endif
#if !HAVE_GTK3
static void
tray_status_icon_set_icon (TrayIcon icon)
{
if (!tray_status_icon)
return;
gtk_status_icon_set_from_pixbuf (tray_status_icon, icon);
}
static void
tray_status_icon_set_tooltip (const char *text)
{
if (!tray_status_icon)
return;
gtk_status_icon_set_tooltip_text (tray_status_icon, text);
}
static gboolean
tray_status_icon_is_embedded (void)
{
if (!tray_status_icon)
return FALSE;
return gtk_status_icon_is_embedded (tray_status_icon);
}
static void
tray_status_icon_cleanup (void)
{
if (tray_status_icon)
{
g_object_unref (tray_status_icon);
tray_status_icon = NULL;
}
}
static gboolean
tray_status_icon_init (void)
{
tray_status_icon = gtk_status_icon_new_from_pixbuf (ICON_NORMAL);
if (!tray_status_icon)
return FALSE;
g_signal_connect (G_OBJECT (tray_status_icon), "popup-menu",
G_CALLBACK (tray_menu_cb), tray_status_icon);
g_signal_connect (G_OBJECT (tray_status_icon), "activate",
G_CALLBACK (tray_menu_restore_cb), NULL);
g_signal_connect (G_OBJECT (tray_status_icon), "notify::embedded",
G_CALLBACK (tray_menu_notify_cb), NULL);
return TRUE;
}
static const TrayBackendOps tray_backend_ops = {
tray_status_icon_init,
tray_status_icon_set_icon,
tray_status_icon_set_tooltip,
tray_status_icon_is_embedded,
tray_status_icon_cleanup
};
#endif
static gboolean
tray_backend_init (void)
{
if (!tray_backend_ops.init)
return FALSE;
#if HAVE_GTK3
tray_gtk3_icons_init ();
if (!tray_gtk3_icon_theme)
tray_gtk3_icon_theme = gtk_icon_theme_get_default ();
if (tray_gtk3_icon_theme && tray_gtk3_icon_theme_changed_handler == 0)
{
tray_gtk3_icon_theme_changed_handler = g_signal_connect (
tray_gtk3_icon_theme,
"changed",
G_CALLBACK (tray_gtk3_theme_changed_cb),
NULL);
}
#endif
tray_backend_active = tray_backend_ops.init ();
return tray_backend_active;
}
static void
tray_backend_set_icon (TrayIcon icon)
{
if (tray_backend_active && tray_backend_ops.set_icon)
tray_backend_ops.set_icon (icon);
}
static void
tray_backend_set_tooltip (const char *text)
{
if (tray_backend_active && tray_backend_ops.set_tooltip)
tray_backend_ops.set_tooltip (text);
}
static gboolean
tray_backend_is_embedded (void)
{
if (!tray_backend_active || !tray_backend_ops.is_embedded)
return FALSE;
return tray_backend_ops.is_embedded ();
}
static void
tray_backend_cleanup (void)
{
if (tray_backend_ops.cleanup)
tray_backend_ops.cleanup ();
#if HAVE_GTK3
if (tray_gtk3_icon_theme && tray_gtk3_icon_theme_changed_handler)
{
g_signal_handler_disconnect (tray_gtk3_icon_theme,
tray_gtk3_icon_theme_changed_handler);
tray_gtk3_icon_theme_changed_handler = 0;
}
tray_gtk3_icon_theme = NULL;
tray_gtk3_icons_cleanup ();
#endif
tray_backend_active = FALSE;
}
static WinStatus
tray_get_window_status (void)
{
const char *st;
st = zoitechat_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;
}
static void
tray_set_icon_state (TrayIcon icon, TrayIconState state)
{
tray_backend_set_icon (icon);
tray_icon_state = state;
}
static void
tray_set_custom_icon_state (TrayCustomIcon icon, TrayIconState state)
{
tray_backend_set_icon (icon);
tray_icon_state = state;
}
void
fe_tray_set_tooltip (const char *text)
{
if (!tray_backend_active)
return;
tray_backend_set_tooltip (text);
}
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 (tray_backend_active)
{
tray_set_icon_state (ICON_NORMAL, TRAY_ICON_NORMAL);
nets = tray_count_networks ();
chans = tray_count_channels ();
if (nets)
tray_set_tipf (_("Connected to %u networks and %u channels - %s"),
nets, chans, _(DISPLAY_NAME));
else
tray_set_tipf ("%s - %s", _("Not connected."), _(DISPLAY_NAME));
}
if (custom_icon1)
{
tray_icon_free (custom_icon1);
custom_icon1 = NULL;
}
if (custom_icon2)
{
tray_icon_free (custom_icon2);
custom_icon2 = NULL;
}
tray_flash_icon = NULL;
tray_flash_state = TRAY_ICON_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 (gpointer userdata)
{
(void)userdata;
if (custom_icon1)
{
if (tray_icon_state == TRAY_ICON_CUSTOM1)
{
if (custom_icon2)
tray_set_custom_icon_state (custom_icon2, TRAY_ICON_CUSTOM2);
else
tray_set_icon_state (ICON_NORMAL, TRAY_ICON_NORMAL);
}
else
{
tray_set_custom_icon_state (custom_icon1, TRAY_ICON_CUSTOM1);
}
}
else
{
if (tray_icon_state == TRAY_ICON_NORMAL)
tray_set_icon_state (tray_flash_icon, tray_flash_state);
else
tray_set_icon_state (ICON_NORMAL, TRAY_ICON_NORMAL);
}
return 1;
}
static void
tray_set_flash (TrayIcon icon, TrayIconState state)
{
if (!tray_backend_active)
return;
/* already flashing the same icon */
if (flash_tag && tray_icon_state == state)
return;
/* no flashing if window is focused */
if (tray_get_window_status () == WS_FOCUSED)
return;
tray_stop_flash ();
tray_flash_icon = icon;
tray_flash_state = state;
tray_set_icon_state (icon, state);
if (prefs.hex_gui_tray_blink)
flash_tag = g_timeout_add (TIMEOUT, (GSourceFunc) tray_timeout_cb, NULL);
}
void
fe_tray_set_flash (const char *filename1, const char *filename2, int tout)
{
tray_apply_setup ();
if (!tray_backend_active)
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);
tray_set_custom_icon_state (custom_icon1, TRAY_ICON_CUSTOM1);
flash_tag = g_timeout_add (tout, (GSourceFunc) tray_timeout_cb, NULL);
}
void
fe_tray_set_icon (feicon icon)
{
tray_apply_setup ();
if (!tray_backend_active)
return;
tray_stop_flash ();
switch (icon)
{
case FE_ICON_NORMAL:
break;
case FE_ICON_MESSAGE:
case FE_ICON_PRIVMSG:
tray_set_flash (ICON_MSG, TRAY_ICON_MESSAGE);
break;
case FE_ICON_HIGHLIGHT:
tray_set_flash (ICON_HILIGHT, TRAY_ICON_HIGHLIGHT);
break;
case FE_ICON_FILEOFFER:
tray_set_flash (ICON_FILE, TRAY_ICON_FILEOFFER);
}
}
void
fe_tray_set_file (const char *filename)
{
tray_apply_setup ();
if (!tray_backend_active)
return;
tray_stop_flash ();
if (filename)
{
custom_icon1 = tray_icon_from_file (filename);
tray_set_custom_icon_state (custom_icon1, TRAY_ICON_CUSTOM1);
}
}
gboolean
tray_toggle_visibility (gboolean force_hide)
{
static int x, y;
static GdkScreen *screen;
static int maximized;
static int fullscreen;
GtkWindow *win;
if (!tray_backend_active)
return FALSE;
/* ph may have an invalid context now */
zoitechat_set_context (ph, zoitechat_find_context (ph, NULL, NULL));
win = GTK_WINDOW (zoitechat_get_info (ph, "gtkwin_ptr"));
tray_stop_flash ();
tray_reset_counts ();
if (!win)
return FALSE;
if (force_hide || gtk_widget_get_visible (GTK_WIDGET (win)))
{
if (prefs.hex_gui_tray_away)
zoitechat_command (ph, "ALLSERV AWAY");
gtk_window_get_position (win, &x, &y);
screen = gtk_window_get_screen (win);
maximized = prefs.hex_gui_win_state;
fullscreen = prefs.hex_gui_win_fullscreen;
gtk_widget_hide (GTK_WIDGET (win));
}
else
{
if (prefs.hex_gui_tray_away)
zoitechat_command (ph, "ALLSERV BACK");
gtk_window_set_screen (win, screen);
gtk_window_move (win, x, y);
if (maximized)
gtk_window_maximize (win);
if (fullscreen)
gtk_window_fullscreen (win);
gtk_widget_show (GTK_WIDGET (win));
gtk_window_deiconify (win);
gtk_window_present (win);
}
return TRUE;
}
static void
tray_menu_restore_cb (GtkWidget *item, gpointer userdata)
{
(void)item;
(void)userdata;
tray_toggle_visibility (FALSE);
}
static void
tray_menu_notify_cb (GObject *tray, GParamSpec *pspec, gpointer user_data)
{
(void)tray;
(void)pspec;
(void)user_data;
if (tray_backend_active)
{
if (!tray_backend_is_embedded ())
{
tray_restore_timer = g_timeout_add(500, (GSourceFunc)tray_menu_try_restore, NULL);
}
else
{
if (tray_restore_timer)
{
g_source_remove (tray_restore_timer);
tray_restore_timer = 0;
}
}
}
}
static gboolean
tray_menu_try_restore (void)
{
tray_cleanup();
tray_init();
return TRUE;
}
static void
tray_menu_quit_cb (GtkWidget *item, gpointer userdata)
{
(void)item;
(void)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;
}
#ifndef WIN32
static void
tray_toggle_cb (GtkCheckMenuItem *item, unsigned int *setting)
{
*setting = gtk_check_menu_item_get_active (item);
}
static void
blink_item (unsigned int *setting, GtkWidget *menu, char *label)
{
menu_toggle_item (label, menu, tray_toggle_cb, setting, *setting);
}
#endif
#if !HAVE_GTK3
static void
tray_menu_destroy (GtkWidget *menu, gpointer userdata)
{
(void)userdata;
gtk_widget_destroy (menu);
g_object_unref (menu);
#ifdef WIN32
g_source_remove (tray_menu_timer);
#endif
}
#ifdef WIN32
static gboolean
tray_menu_enter_cb (GtkWidget *menu)
{
(void)menu;
tray_menu_inactivetime = 0;
return FALSE;
}
static gboolean
tray_menu_left_cb (GtkWidget *menu)
{
(void)menu;
tray_menu_inactivetime = g_get_real_time ();
return FALSE;
}
static gboolean
tray_check_hide (GtkWidget *menu)
{
if (tray_menu_inactivetime && g_get_real_time () - tray_menu_inactivetime >= 2000000)
{
tray_menu_destroy (menu, NULL);
return G_SOURCE_REMOVE;
}
return G_SOURCE_CONTINUE;
}
#endif
#endif
static void
tray_menu_settings (GtkWidget * wid, gpointer none)
{
(void)wid;
(void)none;
extern void setup_open (void);
setup_open ();
}
static void
tray_menu_populate (GtkWidget *menu)
{
GtkWidget *submenu;
GtkWidget *item;
int away_status;
/* ph may have an invalid context now */
zoitechat_set_context (ph, zoitechat_find_context (ph, NULL, NULL));
if (tray_get_window_status () == WS_HIDDEN)
tray_make_item (menu, _("_Restore Window"), tray_menu_restore_cb, NULL);
else
tray_make_item (menu, _("_Hide Window"), tray_menu_restore_cb, NULL);
tray_make_item (menu, NULL, tray_menu_quit_cb, NULL);
#ifndef WIN32 /* submenus are buggy on win32 */
submenu = mg_submenu (menu, _("_Blink on"));
blink_item (&prefs.hex_input_tray_chans, submenu, _("Channel Message"));
blink_item (&prefs.hex_input_tray_priv, submenu, _("Private Message"));
blink_item (&prefs.hex_input_tray_hilight, submenu, _("Highlighted Message"));
/*blink_item (BIT_FILEOFFER, submenu, _("File Offer"));*/
submenu = mg_submenu (menu, _("_Change status"));
#else /* so show away/back in main tray menu */
submenu = menu;
#endif
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);
menu_add_plugin_items (menu, "\x5$TRAY", NULL);
tray_make_item (menu, NULL, tray_menu_quit_cb, NULL);
mg_create_icon_item (_("_Preferences"), ICON_TRAY_PREFERENCES, menu, tray_menu_settings, NULL);
tray_make_item (menu, NULL, tray_menu_quit_cb, NULL);
mg_create_icon_item (_("_Quit"), ICON_TRAY_QUIT, menu, tray_menu_quit_cb, NULL);
}
#if HAVE_GTK3
static void
tray_menu_clear (GtkWidget *menu)
{
GList *children;
GList *iter;
children = gtk_container_get_children (GTK_CONTAINER (menu));
for (iter = children; iter; iter = iter->next)
gtk_widget_destroy (GTK_WIDGET (iter->data));
g_list_free (children);
}
static void
tray_menu_show_cb (GtkWidget *menu, gpointer userdata)
{
(void)userdata;
tray_menu_clear (menu);
tray_menu_populate (menu);
}
#endif
#if !HAVE_GTK3
static void
tray_menu_cb (GtkWidget *widget, guint button, guint time, gpointer userdata)
{
static GtkWidget *menu;
(void)widget;
/* close any old menu */
if (G_IS_OBJECT (menu))
{
tray_menu_destroy (menu, NULL);
}
menu = gtk_menu_new ();
/*gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget));*/
tray_menu_populate (menu);
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);
#ifdef WIN32
g_signal_connect (G_OBJECT (menu), "leave-notify-event",
G_CALLBACK (tray_menu_left_cb), NULL);
g_signal_connect (G_OBJECT (menu), "enter-notify-event",
G_CALLBACK (tray_menu_enter_cb), NULL);
tray_menu_timer = g_timeout_add (500, (GSourceFunc)tray_check_hide, menu);
#endif
gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL,
userdata, button, time);
}
#endif
static void
tray_init (void)
{
flash_tag = 0;
tray_icon_state = TRAY_ICON_NONE;
tray_flash_icon = NULL;
tray_flash_state = TRAY_ICON_NONE;
custom_icon1 = NULL;
custom_icon2 = NULL;
if (!tray_backend_init ())
return;
tray_icon_state = TRAY_ICON_NORMAL;
tray_set_icon_state (ICON_NORMAL, TRAY_ICON_NORMAL);
}
static int
tray_hilight_cb (char *word[], void *userdata)
{
/*if (tray_icon_state == TRAY_ICON_HIGHLIGHT)
return ZOITECHAT_EAT_NONE;*/
if (prefs.hex_input_tray_hilight)
{
tray_set_flash (ICON_HILIGHT, TRAY_ICON_HIGHLIGHT);
/* FIXME: hides any previous private messages */
tray_hilight_count++;
if (tray_hilight_count == 1)
tray_set_tipf (_("Highlighted message from: %s (%s) - %s"),
word[1], zoitechat_get_info (ph, "channel"), _(DISPLAY_NAME));
else
tray_set_tipf (_("%u highlighted messages, latest from: %s (%s) - %s"),
tray_hilight_count, word[1], zoitechat_get_info (ph, "channel"),
_(DISPLAY_NAME));
}
return ZOITECHAT_EAT_NONE;
}
static int
tray_message_cb (char *word[], void *userdata)
{
if (/*tray_icon_state == TRAY_ICON_MESSAGE ||*/ tray_icon_state == TRAY_ICON_HIGHLIGHT)
return ZOITECHAT_EAT_NONE;
if (prefs.hex_input_tray_chans)
{
tray_set_flash (ICON_MSG, TRAY_ICON_MESSAGE);
tray_pub_count++;
if (tray_pub_count == 1)
tray_set_tipf (_("Channel message from: %s (%s) - %s"),
word[1], zoitechat_get_info (ph, "channel"), _(DISPLAY_NAME));
else
tray_set_tipf (_("%u channel messages. - %s"), tray_pub_count, _(DISPLAY_NAME));
}
return ZOITECHAT_EAT_NONE;
}
static void
tray_priv (char *from, char *text)
{
const char *network;
if (alert_match_word (from, prefs.hex_irc_no_hilight))
return;
network = zoitechat_get_info (ph, "network");
if (!network)
network = zoitechat_get_info (ph, "server");
if (prefs.hex_input_tray_priv)
{
tray_set_flash (ICON_MSG, TRAY_ICON_MESSAGE);
tray_priv_count++;
if (tray_priv_count == 1)
tray_set_tipf (_("Private message from: %s (%s) - %s"), from,
network, _(DISPLAY_NAME));
else
tray_set_tipf (_("%u private messages, latest from: %s (%s) - %s"),
tray_priv_count, from, network, _(DISPLAY_NAME));
}
}
static int
tray_priv_cb (char *word[], void *userdata)
{
tray_priv (word[1], word[2]);
return ZOITECHAT_EAT_NONE;
}
static int
tray_invited_cb (char *word[], void *userdata)
{
if (!prefs.hex_away_omit_alerts || tray_find_away_status () != 1)
tray_priv (word[2], "Invited");
return ZOITECHAT_EAT_NONE;
}
static int
tray_dcc_cb (char *word[], void *userdata)
{
const char *network;
/* if (tray_icon_state == TRAY_ICON_FILEOFFER)
return ZOITECHAT_EAT_NONE;*/
network = zoitechat_get_info (ph, "network");
if (!network)
network = zoitechat_get_info (ph, "server");
if (prefs.hex_input_tray_priv && (!prefs.hex_away_omit_alerts || tray_find_away_status () != 1))
{
tray_set_flash (ICON_FILE, TRAY_ICON_FILEOFFER);
tray_file_count++;
if (tray_file_count == 1)
tray_set_tipf (_("File offer from: %s (%s) - %s"), word[1], network,
_(DISPLAY_NAME));
else
tray_set_tipf (_("%u file offers, latest from: %s (%s) - %s"),
tray_file_count, word[1], network, _(DISPLAY_NAME));
}
return ZOITECHAT_EAT_NONE;
}
static int
tray_focus_cb (char *word[], void *userdata)
{
tray_stop_flash ();
tray_reset_counts ();
return ZOITECHAT_EAT_NONE;
}
static void
tray_cleanup (void)
{
tray_stop_flash ();
if (tray_backend_active)
tray_backend_cleanup ();
}
void
tray_apply_setup (void)
{
if (tray_backend_active)
{
if (!prefs.hex_gui_tray)
tray_cleanup ();
}
else
{
GtkWindow *window = GTK_WINDOW(zoitechat_get_info (ph, "gtkwin_ptr"));
if (prefs.hex_gui_tray && gtkutil_tray_icon_supported (window))
tray_init ();
}
}
int
tray_plugin_init (zoitechat_plugin *plugin_handle, char **plugin_name,
char **plugin_desc, char **plugin_version, char *arg)
{
/* we need to save this for use with any zoitechat_* functions */
ph = plugin_handle;
*plugin_name = "";
*plugin_desc = "";
*plugin_version = "";
zoitechat_hook_print (ph, "Channel Msg Hilight", -1, tray_hilight_cb, NULL);
zoitechat_hook_print (ph, "Channel Action Hilight", -1, tray_hilight_cb, NULL);
zoitechat_hook_print (ph, "Channel Message", -1, tray_message_cb, NULL);
zoitechat_hook_print (ph, "Channel Action", -1, tray_message_cb, NULL);
zoitechat_hook_print (ph, "Channel Notice", -1, tray_message_cb, NULL);
zoitechat_hook_print (ph, "Private Message", -1, tray_priv_cb, NULL);
zoitechat_hook_print (ph, "Private Message to Dialog", -1, tray_priv_cb, NULL);
zoitechat_hook_print (ph, "Private Action", -1, tray_priv_cb, NULL);
zoitechat_hook_print (ph, "Private Action to Dialog", -1, tray_priv_cb, NULL);
zoitechat_hook_print (ph, "Notice", -1, tray_priv_cb, NULL);
zoitechat_hook_print (ph, "Invited", -1, tray_invited_cb, NULL);
zoitechat_hook_print (ph, "DCC Offer", -1, tray_dcc_cb, NULL);
zoitechat_hook_print (ph, "Focus Window", -1, tray_focus_cb, NULL);
GtkWindow *window = GTK_WINDOW(zoitechat_get_info (ph, "gtkwin_ptr"));
if (prefs.hex_gui_tray && gtkutil_tray_icon_supported (window))
tray_init ();
return 1; /* return 1 for success */
}
int
tray_plugin_deinit (zoitechat_plugin *plugin_handle)
{
#ifdef WIN32
tray_cleanup ();
#endif
return 1;
}