diff --git a/xqf/ChangeLog b/xqf/ChangeLog index e33dd01..11d6675 100644 --- a/xqf/ChangeLog +++ b/xqf/ChangeLog @@ -3,6 +3,7 @@ Jun 19, 2004: Ludwig Nussel all servers. Today's qstat cvs version required. - add gametypes for World of Padman Q3A mod - add support for Jedi Academy (wine game, patch by Steffen Pankratz) +- add an animated tray icon for the GTK2 version (patch by Jochen Baier) Jun 06, 2004: Ludwig Nussel - update Danish translation (Morten Brix Pedersen) diff --git a/xqf/configure.in b/xqf/configure.in index db6e8ab..bd48854 100644 --- a/xqf/configure.in +++ b/xqf/configure.in @@ -48,7 +48,7 @@ if test "x$USE_GTK2" != "xno"; then OLD_GTK_SUPPORT="-DGTK_ENABLE_BROKEN=1" AC_SUBST(OLD_GTK_SUPPORT) - pkg_modules="gtk+-2.0 >= 2.0.0" + pkg_modules="gtk+-2.0 >= 2.0.0 gdk-pixbuf-xlib-2.0" PKG_CHECK_MODULES(PACKAGE, [$pkg_modules]) AC_SUBST(PACKAGE_CFLAGS) AC_SUBST(PACKAGE_LIBS) @@ -195,6 +195,7 @@ AC_CONFIG_FILES([ docs/Makefile pixmaps/Makefile pixmaps/flags/Makefile + pixmaps/trayicon/Makefile xqf.spec po/Makefile.in ]) diff --git a/xqf/pixmaps/Makefile.am b/xqf/pixmaps/Makefile.am index b45ac73..e93bfa4 100644 --- a/xqf/pixmaps/Makefile.am +++ b/xqf/pixmaps/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = flags +SUBDIRS = flags trayicon EXTRA_DIST = $(splash_DATA) $(icon_DATA) diff --git a/xqf/pixmaps/trayicon/Makefile.am b/xqf/pixmaps/trayicon/Makefile.am new file mode 100644 index 0000000..6c03027 --- /dev/null +++ b/xqf/pixmaps/trayicon/Makefile.am @@ -0,0 +1,4 @@ +EXTRA_DIST = $(tray_DATA) + +traydir = $(pkgdatadir)/default/trayicon +tray_DATA = $(wildcard *.png) $(wildcard *.ani) diff --git a/xqf/pixmaps/trayicon/around_frame_1.png b/xqf/pixmaps/trayicon/around_frame_1.png new file mode 100644 index 0000000..926ab0c Binary files /dev/null and b/xqf/pixmaps/trayicon/around_frame_1.png differ diff --git a/xqf/pixmaps/trayicon/around_frame_2.png b/xqf/pixmaps/trayicon/around_frame_2.png new file mode 100644 index 0000000..d54bcc3 Binary files /dev/null and b/xqf/pixmaps/trayicon/around_frame_2.png differ diff --git a/xqf/pixmaps/trayicon/around_frame_3.png b/xqf/pixmaps/trayicon/around_frame_3.png new file mode 100644 index 0000000..ffd4588 Binary files /dev/null and b/xqf/pixmaps/trayicon/around_frame_3.png differ diff --git a/xqf/pixmaps/trayicon/around_frame_4.png b/xqf/pixmaps/trayicon/around_frame_4.png new file mode 100644 index 0000000..8c1700e Binary files /dev/null and b/xqf/pixmaps/trayicon/around_frame_4.png differ diff --git a/xqf/pixmaps/trayicon/busy.ani b/xqf/pixmaps/trayicon/busy.ani new file mode 100644 index 0000000..2467f51 --- /dev/null +++ b/xqf/pixmaps/trayicon/busy.ani @@ -0,0 +1,5 @@ +around_frame_1.png 8 +around_frame_2.png 8 +around_frame_3.png 8 +around_frame_4.png 8 + diff --git a/xqf/pixmaps/trayicon/frame_basic.png b/xqf/pixmaps/trayicon/frame_basic.png new file mode 100644 index 0000000..74b6c6e Binary files /dev/null and b/xqf/pixmaps/trayicon/frame_basic.png differ diff --git a/xqf/pixmaps/trayicon/ready.ani b/xqf/pixmaps/trayicon/ready.ani new file mode 100644 index 0000000..3372d3a --- /dev/null +++ b/xqf/pixmaps/trayicon/ready.ani @@ -0,0 +1,16 @@ +red_frame_4_4.png 50 +frame_basic.png 50 +red_frame_4_4.png 50 +frame_basic.png 50 +red_frame_4_4.png 50 +frame_basic.png 50 +red_frame_4_4.png 50 +frame_basic.png 50 +red_frame_4_4.png 50 +frame_basic.png 50 +red_frame_4_4.png 6000 +red_frame_3_4.png 6000 +red_frame_2_4.png 6000 +red_frame_1_4.png 6000 +frame_basic.png 1 + diff --git a/xqf/pixmaps/trayicon/red_frame_4_4.png b/xqf/pixmaps/trayicon/red_frame_4_4.png new file mode 100644 index 0000000..d41adc5 Binary files /dev/null and b/xqf/pixmaps/trayicon/red_frame_4_4.png differ diff --git a/xqf/src/Makefile.am b/xqf/src/Makefile.am index 1c30512..8e89fe4 100644 --- a/xqf/src/Makefile.am +++ b/xqf/src/Makefile.am @@ -57,6 +57,7 @@ srv-list.c \ srv-prop.c \ stat.c \ statistics.c \ +trayicon.c \ utils.c \ xqf.c \ xqf-ui.c \ @@ -98,6 +99,7 @@ srv-list.h \ srv-prop.h \ stat.h \ statistics.h \ +trayicon.h \ utils.h \ xqf-ui.h \ xqf.h \ diff --git a/xqf/src/pref.c b/xqf/src/pref.c index 5ed3e20..1f22e21 100644 --- a/xqf/src/pref.c +++ b/xqf/src/pref.c @@ -101,6 +101,7 @@ int default_save_plrinfo; int default_auto_favorites; int default_show_splash; int default_auto_maps; +int default_show_tray_icon; int default_toolbar_style; int default_toolbar_tips; int default_refresh_sorts; @@ -170,6 +171,7 @@ static GtkWidget *save_plrinfo_check_button; static GtkWidget *auto_favorites_check_button; static GtkWidget *show_splash_button; static GtkWidget *auto_maps_check_button; +static GtkWidget *tray_icon_check_button; static GtkWidget *show_hostnames_check_button; static GtkWidget *show_defport_check_button; static GtkWidget *toolbar_style_radio_buttons[3]; @@ -941,6 +943,12 @@ static void get_new_defaults (void) { if (i != default_auto_maps) config_set_bool ("search maps", default_auto_maps = i); +#ifdef USE_GTK2 + i = GTK_TOGGLE_BUTTON (tray_icon_check_button)->active; + if (i != default_show_tray_icon) + config_set_bool ("showtray", default_show_tray_icon = i); +#endif + config_pop_prefix (); /* QStat */ @@ -3922,6 +3930,23 @@ static GtkWidget *general_options_page (void) { gtk_widget_show (hbox); +#ifdef USE_GTK2 + /*Tray icon*/ + hbox = gtk_hbox_new (FALSE, 4); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + + + tray_icon_check_button = gtk_check_button_new_with_label (_("Minimize to system tray")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (tray_icon_check_button), default_show_tray_icon); + + gtk_tooltips_set_tip (tooltips, tray_icon_check_button, + _("Enable xqf tray icon. You need to restart xqf to take effect !"), NULL); + + gtk_box_pack_start (GTK_BOX (hbox), tray_icon_check_button, FALSE, FALSE, 0); + gtk_widget_show (tray_icon_check_button); + gtk_widget_show (hbox); +#endif + gtk_widget_show (vbox); gtk_widget_show (frame); @@ -5030,6 +5055,7 @@ int prefs_load (void) { default_auto_favorites = config_get_bool ("refresh favorites=false"); default_show_splash = config_get_bool ("splash screen=true"); default_auto_maps = config_get_bool ("search maps=false"); + default_show_tray_icon = config_get_bool ("showtray=false"); config_pop_prefix (); @@ -5319,4 +5345,3 @@ static void file_dialog(const char *title, GtkSignalFunc ok_callback, enum serve gtk_widget_show(file_selector); } - diff --git a/xqf/src/pref.h b/xqf/src/pref.h index 59f6bfa..fdf5163 100644 --- a/xqf/src/pref.h +++ b/xqf/src/pref.h @@ -88,6 +88,7 @@ extern int default_save_srvinfo; extern int default_save_plrinfo; extern int default_auto_favorites; extern int default_show_splash; +extern int default_show_tray_icon; extern int default_always_resolve; extern int default_toolbar_style; extern int default_toolbar_tips; @@ -126,5 +127,3 @@ extern void free_user_info (void); extern int prefs_load (void); #endif /* __PREF_H__ */ - - diff --git a/xqf/src/srv-list.c b/xqf/src/srv-list.c index fbeeb9f..58a61e1 100644 --- a/xqf/src/srv-list.c +++ b/xqf/src/srv-list.c @@ -495,6 +495,19 @@ GSList *server_clist_selected_servers (void) { return list; } +GSList *server_clist_get_n_servers (int amount) { + GSList *list = NULL; + struct server *server; + int row; + + for (row = 0; (row < server_clist->rows && row < amount) ; row++) { + server = (struct server *) gtk_clist_get_row_data (server_clist, row); + list = server_list_prepend (list, server); + } + + return g_slist_reverse (list); +} + /* server_clist_all_servers -- Return all servers that are in the server clist widget. It returns a new list. Note that the prepend function diff --git a/xqf/src/srv-list.h b/xqf/src/srv-list.h index ae5ac67..59dcbca 100644 --- a/xqf/src/srv-list.h +++ b/xqf/src/srv-list.h @@ -38,6 +38,7 @@ extern int server_clist_refresh_server (struct server *s); extern void server_clist_select_one (int row); extern GSList *server_clist_selected_servers (void); extern GSList *server_clist_all_servers (void); +extern GSList *server_clist_get_n_servers (int amount); extern void server_clist_selection_visible (void); diff --git a/xqf/src/trayicon.c b/xqf/src/trayicon.c new file mode 100644 index 0000000..f39fcd8 --- /dev/null +++ b/xqf/src/trayicon.c @@ -0,0 +1,926 @@ +/* XQF - Quake server browser and launcher + * + * 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 +.* +.* +.* trayicon.c: +.* + * Copyright (C) Jochen Baier +.* +.* based on: +.* +.* eggtrayicon.c api +.* + * Copyright (C) Anders Carlsson +.* +.* magic transparent KDE icon: +.* +.* Copyright (C) Jochen Baier +.* + */ + + +#include +#include "gnuconfig.h" + +#ifndef USE_GTK2 +/*dummy functions for gtk 1.x, this avoid a lot ifdef´s*/ +void tray_init (GtkWidget * window) {/**/}; +void tray_delete_event_hook (void) {/**/}; +void tray_icon_set_tooltip (gchar *tip) {/**/}; +void tray_icon_stop_animation (void) {/**/}; +void tray_icon_start_animation (void) {/**/}; +extern void tray_done (void) {/**/}; +extern gboolean tray_icon_work(void) {return FALSE;}; +#else + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "i18n.h" +#include "pref.h" +#include "xqf.h" +#include "debug.h" +#include "loadpixmap.h" +#include "srv-list.h" +#include "source.h" + +#define SYSTEM_TRAY_REQUEST_DOCK 0 +#define SYSTEM_TRAY_BEGIN_MESSAGE 1 +#define SYSTEM_TRAY_CANCEL_MESSAGE 2 + +#define EGG_TYPE_TRAY_ICON (egg_tray_icon_get_type ()) +#define EGG_TRAY_ICON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_TRAY_ICON, EggTrayIcon)) +#define EGG_TRAY_ICON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_TRAY_ICON, EggTrayIconClass)) +#define EGG_IS_TRAY_ICON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_TRAY_ICON)) +#define EGG_IS_TRAY_ICON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_TRAY_ICON)) +#define EGG_TRAY_ICON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_TRAY_ICON, EggTrayIconClass)) + +#define EVENT_REFRESH 2 +#define EVENT_UPDATE 3 +#define N_SERVER 100 +#define ANI_TIME 10 + +static const char kde_window_manger[]="KWin"; + +typedef struct _EggTrayIcon EggTrayIcon; +typedef struct _EggTrayIconClass EggTrayIconClass; + +struct _EggTrayIcon +{ + GtkPlug parent_instance; + guint stamp; + Atom selection_atom; + Atom manager_atom; + Atom system_tray_opcode_atom; + Window manager_window; + GtkWidget *box; + GtkWidget *background; + GtkWidget *image; + GdkPixbuf *default_pix; + gboolean ready; +}; + +struct _EggTrayIconClass +{ + GtkPlugClass parent_class; +}; + +GType egg_tray_icon_get_type (void); +static GtkPlugClass *parent_class = NULL; + +EggTrayIcon *egg_tray_icon_new (const gchar *name, GdkPixbuf *pix); +guint egg_tray_icon_send_message (EggTrayIcon *icon, gint timeout, const char *message, gint len); +void egg_tray_icon_cancel_message (EggTrayIcon *icon, guint id); +static void egg_tray_icon_init (EggTrayIcon *icon); +static void egg_tray_icon_class_init (EggTrayIconClass *klass); +static void egg_tray_icon_unrealize (GtkWidget *widget); +static void egg_tray_icon_update_manager_window (EggTrayIcon *icon); +GdkPixbuf *kde_dock_background(EggTrayIcon *icon); + +GtkTooltips *tray_icon_tips = NULL; +EggTrayIcon *tray_icon = NULL; + +GdkPixbuf *frame_basic; + +GtkWidget *menu = NULL; +GtkWidget *refresh_item; +GtkWidget *update_item; +GtkWidget *stop_item; +GtkWidget *show_item; +GtkWidget *hide_item; +GtkWidget *exit_item; + +typedef struct _frame frame; + +struct _frame +{ + GdkPixbuf *pix; + gint delay; +}; + +typedef struct _animation animation; + +struct _animation +{ + GArray *array; + frame current_frame; + gint frame_counter; + gint time_counter; + gboolean loop; +}; + +static animation *busy_ani; +static animation *ready_ani; + +static GtkWidget *window; /* copy of the main window */ +static gint x_pos = 50; +static gint y_pos = 50; + +static gboolean animation_running = FALSE; +static gboolean refresh_update=FALSE; + +static gint animation_timer=0; +static gint animation_callback (gpointer nothing); + +gboolean tray_icon_work(void) +{ + if (!tray_icon) + return FALSE; + + return tray_icon->ready; +} + +/*user close main window-> hide it*/ +void tray_delete_event_hook(void) +{ + gtk_window_get_position(GTK_WINDOW(window), &x_pos, &y_pos); + gtk_widget_hide(window); +} + +void show_main_window_call(GtkWidget * button, void *data) +{ + static gboolean first_call = TRUE; + + if (first_call) { + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_widget_show(window); + first_call = FALSE; + } else { + gtk_window_move(GTK_WINDOW(window), x_pos, y_pos); + gtk_widget_show(window); + } +} + +void hide_main_window_call(GtkWidget * button, void *data) +{ + gtk_window_get_position(GTK_WINDOW(window), &x_pos, &y_pos); + gtk_widget_hide(window); +} + +void exit_call(GtkWidget * button, void *data) +{ + gtk_widget_destroy(GTK_WIDGET(tray_icon)); + gtk_main_quit(); +} + +void set_menu_sens(void) +{ + gtk_widget_set_sensitive (show_item, !GTK_WIDGET_VISIBLE(window)); + gtk_widget_set_sensitive (hide_item, GTK_WIDGET_VISIBLE(window)); + gtk_widget_set_sensitive (stop_item, refresh_update); +} + +static void +tray_icon_pressed(GtkWidget * button, GdkEventButton * event, EggTrayIcon * icon) +{ + + /*right click */ + if (event->button == 1) { + if (GTK_WIDGET_VISIBLE(window)) { + gtk_window_get_position(GTK_WINDOW(window), &x_pos, &y_pos); + gtk_widget_hide(window); + } else { + show_main_window_call(NULL, NULL); + } + } + + /*left click */ + if (event->button == 3) { + + set_menu_sens (); + + gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 0, + gtk_get_current_event_time()); + } +} + +void tray_icon_set_tooltip(gchar * tip) +{ + if (!tray_icon || !tray_icon->ready ) + return; + + if(!tray_icon_tips) + tray_icon_tips = gtk_tooltips_new(); + + gtk_tooltips_set_tip(GTK_TOOLTIPS(tray_icon_tips), + GTK_WIDGET(tray_icon), tip, tip); +} + +static gint animation_callback(gpointer _ani) +{ + animation *ani= _ani; + + if (ani->frame_counter==0 && ani->time_counter==0) { + ani->current_frame=g_array_index (ani->array, frame, ani->frame_counter); + gtk_image_set_from_pixbuf(GTK_IMAGE(tray_icon->image), ani->current_frame.pix); + } + + if (ani->time_counter < ani->current_frame.delay) { + ani->time_counter++; + return TRUE; + } + + if (ani->frame_counter == ani->array->len-1) { + ani->frame_counter= 0; + ani->time_counter =0; + + if (ani->loop) { + return TRUE; + } + else { + animation_running=FALSE; + return FALSE; + } + } + + ani->frame_counter++; + ani->current_frame=g_array_index (ani->array, frame, ani->frame_counter); + gtk_image_set_from_pixbuf(GTK_IMAGE(tray_icon->image), ani->current_frame.pix); + ani->time_counter=0; + + return TRUE; +} + +void tray_icon_start_animation(void) +{ + + if (!tray_icon_work()) + return; + + refresh_update=TRUE; + + if (animation_running) { + if (animation_timer) g_source_remove(animation_timer); + } + + busy_ani->frame_counter = 0; + busy_ani->time_counter = 0; + + animation_timer = g_timeout_add(ANI_TIME, (GtkFunction) animation_callback, busy_ani); + animation_running = TRUE; +} + +void tray_icon_stop_animation(void) +{ + if (!tray_icon_work()) + return; + + refresh_update=FALSE; + set_menu_sens (); + + if (animation_running) { + if (animation_timer) + g_source_remove(animation_timer); + + ready_ani->frame_counter = 0; + ready_ani->time_counter = 0; + + animation_timer = g_timeout_add(ANI_TIME, (GtkFunction) animation_callback, ready_ani); + } +} + +static animation *tray_icon_load_animation (gchar *name, gboolean loop) +{ + + gint i=0; + gint line_number=0; + gboolean eof=FALSE; + gchar *ani_file; + gchar *content; + gchar *begin; + gchar *line; + gchar *png_start; + gchar *delay_string=NULL; + gchar *png_filename; + + animation *ani; + frame tmp_frame; + + if(!name) return NULL; + + { + char* tmp = g_strconcat("trayicon", G_DIR_SEPARATOR_S, name, NULL); + ani_file = find_pixmap_directory(tmp); + g_free(tmp); + } + + if(!ani_file || !g_file_get_contents (ani_file, &content, NULL, NULL)) + { + xqf_warning("Could not load animation file '%s'", name); + g_free(ani_file); + return NULL; + } + + ani=g_new(animation, 1); + ani->array = g_array_new (FALSE, FALSE, sizeof (frame)); + ani->frame_counter = 0; + ani->time_counter = 0; + ani->loop = loop; + + begin=content; + + while (!eof) { + + if ((content[i] == '\n') || (content[i] == '\0') ) { + + if ( content[i-1] != '\n') { + + line_number++; + + do { + + line= g_strndup (begin, &content[i]-begin); + if (strlen (line) <= 1) { + xqf_warning (_("Error in file: %s line: %d\n"), ani_file, line_number); + break; + } + + png_start =g_strrstr (line, ".png"); + if (!png_start) { + xqf_warning (_("Error in file: %s line: %d\n"), ani_file, line_number); + break; + } + + delay_string=g_strdup (png_start+4); + g_strstrip(delay_string); + + if (strlen (delay_string) < 1) { + xqf_warning (_("Error in file: %s line: %d\n"), ani_file, line_number); + break; + } + + tmp_frame.delay= strtol(delay_string, NULL, 10); + if (tmp_frame.delay== 0) { + xqf_warning (_("Error in file: %s line: %d\n"), ani_file, line_number); + break; + } + + if (tmp_frame.delay != 1) + tmp_frame.delay=tmp_frame.delay / 2; + + *(png_start+4)=0; /*cut anything behind ".png" */ + + png_filename= g_strconcat(PACKAGE_DATA_DIR, G_DIR_SEPARATOR_S,"default", + G_DIR_SEPARATOR_S, "trayicon", G_DIR_SEPARATOR_S, line, NULL); + + tmp_frame.pix = load_pixmap_as_pixbuf (png_filename); + if (tmp_frame.pix == NULL) { + xqf_warning (_("Error in file: %s line: %d\n"), ani_file, line_number); + break; + } + + if (png_filename) + g_free (png_filename); + + g_array_append_val (ani->array, tmp_frame); + + } while (0); + + if (line) + g_free(line); + if (delay_string) + g_free(delay_string); + + } + + if (content[i] == '\0' || (content[i] == '\n' && content[i+1] == '\0' )) + eof=TRUE; + else + begin=&content[i]+1; + + } + i++; + } + + if (content) + g_free(content); + if (ani_file) + g_free(ani_file); + + return ani; +} + +void tray_create_menu (void) +{ + + GtkWidget *separator1 = NULL; + + menu = gtk_menu_new(); + + refresh_item = gtk_menu_item_new_with_label(_("Refresh")); + g_signal_connect(G_OBJECT(refresh_item), "activate", + G_CALLBACK(refresh_n_server), GINT_TO_POINTER(N_SERVER)); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), refresh_item); + + update_item= gtk_menu_item_new_with_label(_("Update")); + g_signal_connect(G_OBJECT(update_item), "activate", + G_CALLBACK(update_source_callback), NULL); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), update_item); + + stop_item = gtk_menu_item_new_with_label(_("Stop")); + g_signal_connect(G_OBJECT(stop_item), "activate", + G_CALLBACK(stop_callback), NULL); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), stop_item); + + separator1 = gtk_menu_item_new(); + gtk_widget_show(separator1); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), separator1); + gtk_widget_set_sensitive(separator1, FALSE); + + show_item = gtk_menu_item_new_with_label(_("Show")); + g_signal_connect(G_OBJECT(show_item), "activate", + G_CALLBACK(show_main_window_call), NULL); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), show_item); + + hide_item= gtk_menu_item_new_with_label(_("Hide")); + g_signal_connect(G_OBJECT(hide_item), "activate", + G_CALLBACK(hide_main_window_call), NULL); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), hide_item); + + exit_item = gtk_menu_item_new_with_label(_("Exit")); + g_signal_connect(G_OBJECT(exit_item), "activate", + G_CALLBACK(exit_call), NULL); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), exit_item); + + gtk_widget_show_all(menu); +} + +void tray_init(GtkWidget * main_window) +{ + gdk_pixbuf_xlib_init (GDK_DISPLAY(), DefaultScreen (GDK_DISPLAY())); + + /* local copy */ + window = main_window; + + gtk_window_get_position(GTK_WINDOW(window), &x_pos, &y_pos); + + tray_create_menu(); + + busy_ani = tray_icon_load_animation ("busy.ani", TRUE); + ready_ani = tray_icon_load_animation("ready.ani", FALSE); + + frame_basic = load_pixmap_as_pixbuf("trayicon/frame_basic.png"); + + if(frame_basic) + tray_icon = egg_tray_icon_new ("xqf", frame_basic); + + if (tray_icon && tray_icon->ready) + { + g_signal_connect(tray_icon, "button_press_event", + G_CALLBACK(tray_icon_pressed),tray_icon); + + gtk_widget_hide(window); + } + else + gtk_widget_show(window); +} + +void tray_done (void) +{ + if (busy_ani) + { + if (busy_ani->array) + g_array_free (busy_ani->array, TRUE); + + if (ready_ani->array) + g_array_free (ready_ani->array, TRUE); + + g_free(busy_ani); + } + + g_free(ready_ani); + + //FIXME + //if (tray_icon) + //gtk_widget_destroy(GTK_WIDGET(tray_icon)); +} + +/*eggtrayicon stuff*/ +GType +egg_tray_icon_get_type (void) +{ + static GType our_type = 0; + + our_type = g_type_from_name ("EggTrayIcon"); + + if (our_type == 0) { + static const GTypeInfo our_info = + { + sizeof (EggTrayIconClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) egg_tray_icon_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (EggTrayIcon), + 0, /* n_preallocs */ + (GInstanceInitFunc) egg_tray_icon_init + }; + + our_type = g_type_register_static (GTK_TYPE_PLUG, "EggTrayIcon", &our_info, 0); + } + else if (parent_class == NULL) { + /* we're reheating the old class from a previous instance - engage ugly hack =( */ + egg_tray_icon_class_init ((EggTrayIconClass *)g_type_class_peek (our_type)); + } + + return our_type; +} + +static void +egg_tray_icon_init (EggTrayIcon *icon) +{ + icon->stamp = 1; + gtk_widget_add_events (GTK_WIDGET (icon), GDK_PROPERTY_CHANGE_MASK); +} + +static void +egg_tray_icon_class_init (EggTrayIconClass *klass) +{ + GtkWidgetClass *widget_class = (GtkWidgetClass *)klass; + + parent_class = g_type_class_peek_parent (klass); + widget_class->unrealize = egg_tray_icon_unrealize; +} + +static GdkFilterReturn +egg_tray_icon_manager_filter (GdkXEvent *xevent, GdkEvent *event, gpointer user_data) +{ + EggTrayIcon *icon = user_data; + XEvent *xev = (XEvent *)xevent; + + if (xev->xany.type == ClientMessage && + xev->xclient.message_type == icon->manager_atom && + xev->xclient.data.l[1] == icon->selection_atom) + { + egg_tray_icon_update_manager_window (icon); + } + else if (xev->xany.window == icon->manager_window) + { + if (xev->xany.type == DestroyNotify) + { + egg_tray_icon_update_manager_window (icon); + } + } + + return GDK_FILTER_CONTINUE; +} + +static void +egg_tray_icon_unrealize (GtkWidget *widget) +{ + EggTrayIcon *icon = EGG_TRAY_ICON (widget); + GdkWindow *root_window=NULL; + + if (icon->manager_window != None) + { + gdk_window_remove_filter (root_window, egg_tray_icon_manager_filter, icon); + + if (GTK_WIDGET_CLASS (parent_class)->unrealize) + (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget); + } +} + +static void +egg_tray_icon_send_manager_message (EggTrayIcon *icon, + long message, + Window window, + long data1, + long data2, + long data3) +{ + XClientMessageEvent ev; + Display *display; + + ev.type = ClientMessage; + ev.window = window; + ev.message_type = icon->system_tray_opcode_atom; + ev.format = 32; + ev.data.l[0] = gdk_x11_get_server_time (GTK_WIDGET (icon)->window); + ev.data.l[1] = message; + ev.data.l[2] = data1; + ev.data.l[3] = data2; + ev.data.l[4] = data3; + + display = gdk_display; + + gdk_error_trap_push (); + XSendEvent (display, + icon->manager_window, False, NoEventMask, (XEvent *)&ev); + XSync (display, False); + gdk_error_trap_pop (); + + icon->ready=TRUE; +} + +static void +egg_tray_icon_send_dock_request (EggTrayIcon *icon) +{ + + egg_tray_icon_send_manager_message (icon, + SYSTEM_TRAY_REQUEST_DOCK, + icon->manager_window, + gtk_plug_get_id (GTK_PLUG (icon)), + 0, 0); +} + +/*from gdk-pixbuf-xlib-drawabel.c*/ +static gboolean +xlib_window_is_viewable (Window w) +{ + XWindowAttributes wa; + + while (w != 0) { + Window parent, root, *children; + int nchildren; + + XGetWindowAttributes (GDK_DISPLAY(), w, &wa); + + if (wa.map_state != IsViewable) { + return FALSE; + } + + if (!XQueryTree (GDK_DISPLAY(), w, &root, + &parent, &children, &nchildren)) + return FALSE; + + if (nchildren > 0) + XFree (children); + + if ((parent == root) || (w == root)) + return TRUE; + + w = parent; + } + return FALSE; +} + +gboolean kde_dock (EggTrayIcon *icon) +{ + + Window win; + Window data; + Atom kde_tray_atom = XInternAtom (GDK_DISPLAY(), "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR", False); + Atom kde_dock_atom = XInternAtom (GDK_DISPLAY(), "KWM_DOCKWINDOW", False); //KDE 2 support not tested + + win = GDK_WINDOW_XID (GTK_WIDGET (icon)->window); + + XSetWindowBackgroundPixmap(GDK_DISPLAY(), win, ParentRelative); + + data = 1; + XChangeProperty (GDK_DISPLAY(), win, kde_dock_atom, XA_WINDOW, 32, + PropModeReplace, (unsigned char *)&data, 1); + data = GDK_WINDOW_XID (window->window); + XChangeProperty (GDK_DISPLAY(), win, kde_tray_atom, XA_WINDOW, 32, + PropModeReplace, (unsigned char *)&data, 1); + + XMapWindow (GDK_DISPLAY(), win); + XFlush (GDK_DISPLAY()); + + return (xlib_window_is_viewable(win)); +} + +GdkPixbuf *kde_dock_background(EggTrayIcon *icon) +{ + + Window win; + XWindowAttributes wa; + GdkPixbuf *background; + + win = GDK_WINDOW_XID (GTK_WIDGET (icon)->window); + XGetWindowAttributes (GDK_DISPLAY(),win, &wa); + + background=gdk_pixbuf_xlib_get_from_drawable (NULL, win, 0, NULL, + 0, 0, 0, 0, wa.width, wa.height); + + return background; +} + +static void +egg_tray_icon_update_manager_window (EggTrayIcon *icon) +{ + + static GdkPixbuf *background_pixbuf; + const gchar *window_manager=NULL; + + if (icon->manager_window != None) { + GdkWindow *gdkwin; + gdkwin = gdk_window_lookup (icon->manager_window); + gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon); + } + + XGrabServer (GDK_DISPLAY()); + + icon->manager_window = XGetSelectionOwner (GDK_DISPLAY(), + icon->selection_atom); + + if (icon->manager_window != None) + XSelectInput (GDK_DISPLAY(), icon->manager_window, StructureNotifyMask); + + XUngrabServer (GDK_DISPLAY()); + XFlush (GDK_DISPLAY()); + + if (icon->manager_window == None) + return; + + window_manager=gdk_x11_screen_get_window_manager_name (gdk_screen_get_default()); + + if ( !g_ascii_strcasecmp(window_manager, kde_window_manger) && kde_dock (icon)) { + + if ((background_pixbuf=kde_dock_background(icon)) !=NULL) { + + icon->box= gtk_fixed_new (); + gtk_fixed_set_has_window(GTK_FIXED (icon->box),TRUE); + gtk_container_add(GTK_CONTAINER(icon), icon->box); + + icon->image=gtk_image_new (); + gtk_image_set_from_pixbuf(GTK_IMAGE(icon->image), icon->default_pix); + + icon->background =gtk_image_new (); + gtk_image_set_from_pixbuf(GTK_IMAGE(icon->background), background_pixbuf); + + gtk_fixed_put (GTK_FIXED (icon->box), GTK_WIDGET(icon->background), 0, 0); + gtk_fixed_put (GTK_FIXED (icon->box), GTK_WIDGET(icon->image), 0, 0); + + gtk_widget_show (icon->background); + gtk_widget_show (icon->image); + gtk_widget_show(icon->box); + + icon->ready=TRUE; + } + } else { + + icon->box=gtk_event_box_new (); + gtk_container_add(GTK_CONTAINER(icon), icon->box); + + icon->image=gtk_image_new (); + gtk_image_set_from_pixbuf(GTK_IMAGE(icon->image),icon->default_pix); + gtk_container_add(GTK_CONTAINER(icon->box), icon->image); + + gtk_widget_show (icon->image); + gtk_widget_show(icon->box); + + GdkWindow *gdkwin; + + gdkwin = gdk_window_lookup (icon->manager_window); + gdk_window_add_filter (gdkwin, egg_tray_icon_manager_filter, icon); + + /* Send a request that we'd like to dock */ + egg_tray_icon_send_dock_request (icon); + } +} + +EggTrayIcon * +egg_tray_icon_new (const char *name, GdkPixbuf *pix) +{ + EggTrayIcon *icon; + char buffer[256]; + GdkWindow *root_window; + Screen *xscreen=DefaultScreenOfDisplay (GDK_DISPLAY()); + + g_return_val_if_fail (pix!= NULL, NULL); + + icon = g_object_new (EGG_TYPE_TRAY_ICON, NULL); + gtk_window_set_title (GTK_WINDOW (icon), name); + + gtk_plug_construct (GTK_PLUG (icon), 0); + gtk_widget_realize (GTK_WIDGET (icon)); + + icon->ready=FALSE; + icon->default_pix=pix; + + /* Now see if there's a manager window around */ + g_snprintf (buffer, sizeof (buffer), "_NET_SYSTEM_TRAY_S%d", + XScreenNumberOfScreen (xscreen)); + + icon->selection_atom = XInternAtom (DisplayOfScreen (xscreen), + buffer, False); + + icon->manager_atom = XInternAtom (DisplayOfScreen (xscreen), + "MANAGER", False); + + icon->system_tray_opcode_atom = XInternAtom (DisplayOfScreen (xscreen), + "_NET_SYSTEM_TRAY_OPCODE", False); + + gtk_window_present (GTK_WINDOW (icon)); + egg_tray_icon_update_manager_window (icon); + root_window = gdk_window_lookup (gdk_x11_get_default_root_xwindow ()); + + /* Add a root window filter so that we get changes on MANAGER */ + gdk_window_add_filter (root_window, egg_tray_icon_manager_filter, icon); + + return icon; +} + +guint +egg_tray_icon_send_message (EggTrayIcon *icon, + gint timeout, + const gchar *message, + gint len) +{ + guint stamp; + + g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), 0); + g_return_val_if_fail (timeout >= 0, 0); + g_return_val_if_fail (message != NULL, 0); + + if (icon->manager_window == None) + return 0; + + if (len < 0) + len = strlen (message); + + stamp = icon->stamp++; + + /* Get ready to send the message */ + egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_BEGIN_MESSAGE, + (Window)gtk_plug_get_id (GTK_PLUG (icon)), + timeout, len, stamp); + + /* Now to send the actual message */ + gdk_error_trap_push (); + while (len > 0) + { + XClientMessageEvent ev; + Display *xdisplay; + + xdisplay = GDK_DISPLAY(); + + ev.type = ClientMessage; + ev.window = (Window)gtk_plug_get_id (GTK_PLUG (icon)); + ev.format = 8; + ev.message_type = XInternAtom (xdisplay, + "_NET_SYSTEM_TRAY_MESSAGE_DATA", False); + if (len > 20) + { + memcpy (&ev.data, message, 20); + len -= 20; + message += 20; + } + else + { + memcpy (&ev.data, message, len); + len = 0; + } + + XSendEvent (GDK_DISPLAY(), + icon->manager_window, False, StructureNotifyMask, (XEvent *)&ev); + XSync (GDK_DISPLAY(), False); + } + gdk_error_trap_pop (); + + return stamp; +} + +void +egg_tray_icon_cancel_message (EggTrayIcon *icon, + guint id) +{ + g_return_if_fail (EGG_IS_TRAY_ICON (icon)); + g_return_if_fail (id > 0); + + egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_CANCEL_MESSAGE, + (Window)gtk_plug_get_id (GTK_PLUG (icon)), + id, 0, 0); +} + +#endif /*GTK 2*/ diff --git a/xqf/src/trayicon.h b/xqf/src/trayicon.h new file mode 100644 index 0000000..98bed6a --- /dev/null +++ b/xqf/src/trayicon.h @@ -0,0 +1,48 @@ +/* XQF - Quake server browser and launcher + * + * 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 +.* +.* +.* trayicon.c: +.* + * Copyright (C) Jochen Baier +.* +.* based on: +.* +.* eggtrayicon.c api +.* + * Copyright (C) Anders Carlsson +.* +.* magic transparent KDE icon: +.* +.* Copyright (C) Jochen Baier +.* + */ + + +#ifndef _system_tray_h_ +#define _system_tray_h_ + +#include + +extern void tray_init (GtkWidget * window) ; +extern void tray_done (void); +extern void tray_delete_event_hook (void); +extern void tray_icon_set_tooltip (gchar *tip); +extern gboolean tray_icon_work(void); +extern void tray_icon_stop_animation (void); +extern void tray_icon_start_animation (void); + +#endif /*_system_tray_h_*/ diff --git a/xqf/src/xqf-ui.c b/xqf/src/xqf-ui.c index 2950da5..cf203d4 100644 --- a/xqf/src/xqf-ui.c +++ b/xqf/src/xqf-ui.c @@ -34,6 +34,7 @@ #include "sort.h" #include "pref.h" #include "debug.h" +#include "trayicon.h" static GSList *xqf_windows = NULL; static GtkWidget *target_window = NULL; @@ -128,13 +129,22 @@ void print_status (GtkWidget *sbar, char *fmt, ...) { gtk_statusbar_pop (GTK_STATUSBAR (sbar), context_id); gtk_statusbar_push (GTK_STATUSBAR (sbar), context_id, buf); + + if (default_show_tray_icon) + tray_icon_set_tooltip(buf); } } int window_delete_event_callback (GtkWidget *widget, gpointer data) { - target_window = widget; - gtk_widget_destroy ((GtkWidget *) (xqf_windows->data)); + + if (default_show_tray_icon && tray_icon_work()) { + tray_delete_event_hook(); + } + else { + target_window = widget; + gtk_widget_destroy ((GtkWidget *) (xqf_windows->data)); + } return TRUE; } @@ -844,5 +854,3 @@ GtkWidget *create_server_type_menu (enum server_type active_type, return option_menu; } - - diff --git a/xqf/src/xqf.c b/xqf/src/xqf.c index 767bb2d..b900bbd 100644 --- a/xqf/src/xqf.c +++ b/xqf/src/xqf.c @@ -81,6 +81,7 @@ #include "redial.h" #include "splash.h" #include "loadpixmap.h" +#include "trayicon.h" #ifdef USE_GEOIP #include "country-filter.h" @@ -961,6 +962,8 @@ static void stat_lists_state_handler (struct stat_job *job, case STAT_UPDATE_SOURCE: progress_bar_str = _("Updating lists..."); + if (default_show_tray_icon) + tray_icon_start_animation (); break; case STAT_RESOLVE_NAMES: @@ -969,6 +972,8 @@ static void stat_lists_state_handler (struct stat_job *job, case STAT_REFRESH_SERVERS: progress_bar_str = _("Refreshing: %d/%d"); + if (default_show_tray_icon) + tray_icon_start_animation (); break; case STAT_RESOLVE_HOSTS: @@ -1001,6 +1006,8 @@ static void stat_lists_close_handler (struct stat_job *job, int killed) { print_status (main_status_bar, _("Waiting to redial server(s)...")); else */ + + tray_icon_stop_animation (); print_status (main_status_bar, _("Done.")); progress_bar_reset (main_progress_bar); @@ -1494,7 +1501,7 @@ static int srvinf_clist_compare_func (GtkCList *clist, } -static void update_source_callback (GtkWidget *widget, gpointer data) { +void update_source_callback (GtkWidget *widget, gpointer data) { GSList *masters = NULL; GSList *servers = NULL; GSList *uservers = NULL; @@ -1554,8 +1561,24 @@ static void refresh_callback (GtkWidget *widget, gpointer data) { } } +void refresh_n_server (GtkWidget * button, gpointer *data) +{ + GSList *list; + gint number; -static void stop_callback (GtkWidget *widget, gpointer data) { + if (stat_process) + return; + + event_type = EVENT_REFRESH; + number=GPOINTER_TO_INT(data); + + list = server_clist_get_n_servers(number); + + if (list) + stat_lists(NULL, NULL, list, NULL); +} + +void stop_callback (GtkWidget *widget, gpointer data) { event_type = 0; // To prevent sound from stopped action from playing @@ -3584,8 +3607,6 @@ void create_main_window (void) { window_set_icon(main_window); - gtk_widget_show (main_window); - gtk_window_add_accel_group (GTK_WINDOW (main_window), accel_group); gtk_accel_group_unref (accel_group); @@ -3595,7 +3616,6 @@ void create_main_window (void) { gtk_tooltips_enable(tooltips); else gtk_tooltips_disable(tooltips); - } void play_sound (const char *sound, const int override) @@ -3898,6 +3918,11 @@ int main (int argc, char *argv[]) { create_main_window (); + if (default_show_tray_icon) + tray_init(main_window); + else + gtk_widget_show (main_window); + source_ctree_select_source (favorites); filter_menu_activate_current(); @@ -3912,10 +3937,15 @@ int main (int argc, char *argv[]) { debug(1,"startup time %ds", time(NULL)-xqf_start_time); + tray_icon_set_tooltip(_("nothing yet...")); + gtk_main (); play_sound(sound_xqf_quit, 0); + if (default_show_tray_icon) + tray_done(); + unregister_window (main_window); main_window = NULL; @@ -3998,7 +4028,7 @@ int main (int argc, char *argv[]) { #endif games_done(); - + debug( 6, "EXIT: Done."); diff --git a/xqf/src/xqf.h b/xqf/src/xqf.h index 4cb20ff..3f156ef 100644 --- a/xqf/src/xqf.h +++ b/xqf/src/xqf.h @@ -26,6 +26,7 @@ #include /* struct in_addr */ #include /* time_t */ +#include #include #define RC_DIR ".qf" @@ -277,5 +278,8 @@ extern int event_type; extern int dontlaunch; extern void refresh_source_list (void); +extern void update_source_callback (GtkWidget *widget, gpointer data); +extern void refresh_n_server(GtkWidget * button, gpointer *data); +extern void stop_callback (GtkWidget *widget, gpointer data); #endif /* __XQF_H__ */