2 * Copyright (C) 1998 Peter Zelezny.
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
26 #include <gtk/gtkmain.h>
27 #include <gtk/gtkentry.h>
28 #include <gtk/gtkprogressbar.h>
29 #include <gtk/gtkbox.h>
30 #include <gtk/gtklabel.h>
31 #include <gtk/gtktogglebutton.h>
32 #include <gtk/gtkmessagedialog.h>
33 #include <gtk/gtkversion.h>
35 #include "../common/xchat.h"
36 #include "../common/fe.h"
37 #include "../common/util.h"
38 #include "../common/text.h"
39 #include "../common/cfgfiles.h"
40 #include "../common/xchatc.h"
41 #include "../common/plugin.h"
50 #include "notifygui.h"
53 #include "plugin-tray.h"
58 #include <gtk/gtkinvisible.h>
62 #include <gtk/gtktextview.h>
65 GdkPixmap
*channelwin_pix
;
71 redraw_trans_xtexts (void)
73 GSList
*list
= sess_list
;
75 int done_main
= FALSE
;
80 if (GTK_XTEXT (sess
->gui
->xtext
)->transparent
)
82 if (!sess
->gui
->is_tab
|| !done_main
)
83 gtk_xtext_refresh (GTK_XTEXT (sess
->gui
->xtext
), 1);
84 if (sess
->gui
->is_tab
)
91 static GdkFilterReturn
92 root_event_cb (GdkXEvent
*xev
, GdkEventProperty
*event
, gpointer data
)
94 static Atom at
= None
;
95 XEvent
*xevent
= (XEvent
*)xev
;
97 if (xevent
->type
== PropertyNotify
)
100 at
= XInternAtom (xevent
->xproperty
.display
, "_XROOTPMAP_ID", True
);
102 if (at
== xevent
->xproperty
.atom
)
103 redraw_trans_xtexts ();
106 return GDK_FILTER_CONTINUE
;
111 /* === command-line parameter parsing : requires glib 2.6 === */
113 static char *arg_cfgdir
= NULL
;
114 static gint arg_show_autoload
= 0;
115 static gint arg_show_config
= 0;
116 static gint arg_show_version
= 0;
117 static gint arg_minimize
= 0;
119 static const GOptionEntry gopt_entries
[] =
121 {"no-auto", 'a', 0, G_OPTION_ARG_NONE
, &arg_dont_autoconnect
, N_("Don't auto connect to servers"), NULL
},
122 {"cfgdir", 'd', 0, G_OPTION_ARG_STRING
, &arg_cfgdir
, N_("Use a different config directory"), "PATH"},
123 {"no-plugins", 'n', 0, G_OPTION_ARG_NONE
, &arg_skip_plugins
, N_("Don't auto load any plugins"), NULL
},
124 {"plugindir", 'p', 0, G_OPTION_ARG_NONE
, &arg_show_autoload
, N_("Show plugin auto-load directory"), NULL
},
125 {"configdir", 'u', 0, G_OPTION_ARG_NONE
, &arg_show_config
, N_("Show user config directory"), NULL
},
126 {"url", 0, 0, G_OPTION_ARG_STRING
, &arg_url
, N_("Open an irc://server:port/channel URL"), "URL"},
127 {"command", 'c', 0, G_OPTION_ARG_STRING
, &arg_command
, N_("Execute command:"), "COMMAND"},
128 {"existing", 'e', 0, G_OPTION_ARG_NONE
, &arg_existing
, N_("Open URL or execute command in an existing XChat"), NULL
},
129 {"minimize", 0, 0, G_OPTION_ARG_INT
, &arg_minimize
, N_("Begin minimized. Level 0=Normal 1=Iconified 2=Tray"), N_("level")},
130 {"version", 'v', 0, G_OPTION_ARG_NONE
, &arg_show_version
, N_("Show version information"), NULL
},
135 fe_args (int argc
, char *argv
[])
137 GError
*error
= NULL
;
138 GOptionContext
*context
;
140 context
= g_option_context_new (NULL
);
141 //g_option_context_add_main_entries (context, gopt_entries, GETTEXT_PACKAGE);
142 g_option_context_add_group (context
, gtk_get_option_group (FALSE
));
143 g_option_context_parse (context
, &argc
, &argv
, &error
);
148 printf ("%s\n", error
->message
);
152 g_option_context_free (context
);
154 if (arg_show_version
)
156 printf (PACKAGE_TARNAME
" "PACKAGE_VERSION
"\n");
160 if (arg_show_autoload
)
162 printf ("%s\n", XCHATLIBDIR
"/plugins");
168 printf ("%s\n", get_xdir_fs ());
172 if (arg_cfgdir
) /* we want filesystem encoding */
174 xdir_fs
= strdup (arg_cfgdir
);
175 if (xdir_fs
[strlen (xdir_fs
) - 1] == '/')
176 xdir_fs
[strlen (xdir_fs
) - 1] = 0;
180 gtk_init (&argc
, &argv
);
183 gdk_window_set_events (gdk_get_default_root_window (), GDK_PROPERTY_CHANGE_MASK
);
184 gdk_window_add_filter (gdk_get_default_root_window (),
185 (GdkFilterFunc
)root_event_cb
, NULL
);
191 const char cursor_color_rc
[] =
195 "GtkTextView::cursor-color=\"#%02x%02x%02x\""
197 "GtkEntry::cursor-color=\"#%02x%02x%02x\""
200 "widget \"*.xchat-inputbox\" style : application \"xc-ib-st\"";
203 create_input_style (GtkStyle
*style
)
206 static int done_rc
= FALSE
;
208 pango_font_description_free (style
->font_desc
);
209 style
->font_desc
= pango_font_description_from_string (prefs
.font_normal
);
212 if (pango_font_description_get_size (style
->font_desc
) == 0)
214 snprintf (buf
, sizeof (buf
), _("Failed to open font:\n\n%s"), prefs
.font_normal
);
215 fe_message (buf
, FE_MSG_ERROR
);
216 pango_font_description_free (style
->font_desc
);
217 style
->font_desc
= pango_font_description_from_string ("sans 11");
220 if (prefs
.style_inputbox
&& !done_rc
)
223 sprintf (buf
, cursor_color_rc
, (colors
[COL_FG
].red
>> 8),
224 (colors
[COL_FG
].green
>> 8), (colors
[COL_FG
].blue
>> 8));
225 gtk_rc_parse_string (buf
);
228 style
->bg
[GTK_STATE_NORMAL
] = colors
[COL_FG
];
229 style
->base
[GTK_STATE_NORMAL
] = colors
[COL_BG
];
230 style
->text
[GTK_STATE_NORMAL
] = colors
[COL_FG
];
242 channelwin_pix
= pixmap_load_from_file (prefs
.background
);
243 input_style
= create_input_style (gtk_style_new ());
251 /* sleep for 3 seconds so any QUIT messages are not lost. The */
252 /* GUI is closed at this point, so the user doesn't even know! */
253 if (prefs
.wait_on_exit
)
260 /* it's saved when pressing OK in setup.c */
271 fe_timeout_add (int interval
, void *callback
, void *userdata
)
273 return g_timeout_add (interval
, (GSourceFunc
) callback
, userdata
);
277 fe_timeout_remove (int tag
)
279 g_source_remove (tag
);
282 /* install tray stuff */
285 fe_idle (gpointer data
)
287 session
*sess
= sess_list
->data
;
289 plugin_add (sess
, NULL
, NULL
, tray_plugin_init
, tray_plugin_deinit
, NULL
, FALSE
);
291 if (arg_minimize
== 1)
292 gtk_window_iconify (GTK_WINDOW (sess
->gui
->window
));
293 else if (arg_minimize
== 2)
294 tray_toggle_visibility (FALSE
);
300 fe_new_window (session
*sess
, int focus
)
304 if (sess
->type
== SESS_DIALOG
)
306 if (prefs
.privmsgtab
)
310 if (prefs
.tabchannels
)
314 mg_changui_new (sess
, NULL
, tab
, focus
);
316 if (!sess_list
->next
)
317 g_idle_add (fe_idle
, NULL
);
321 fe_new_server (struct server
*serv
)
323 serv
->gui
= malloc (sizeof (struct server_gui
));
324 memset (serv
->gui
, 0, sizeof (struct server_gui
));
328 fe_message (char *msg
, int flags
)
331 int type
= GTK_MESSAGE_WARNING
;
333 if (flags
& FE_MSG_ERROR
)
334 type
= GTK_MESSAGE_ERROR
;
335 if (flags
& FE_MSG_INFO
)
336 type
= GTK_MESSAGE_INFO
;
338 dialog
= gtk_message_dialog_new (GTK_WINDOW (parent_window
), 0, type
,
339 GTK_BUTTONS_OK
, "%s", msg
);
340 if (flags
& FE_MSG_MARKUP
)
341 gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (dialog
), msg
);
342 g_signal_connect (G_OBJECT (dialog
), "response",
343 G_CALLBACK (gtk_widget_destroy
), 0);
344 gtk_window_set_resizable (GTK_WINDOW (dialog
), FALSE
);
345 gtk_window_set_position (GTK_WINDOW (dialog
), GTK_WIN_POS_MOUSE
);
346 gtk_widget_show (dialog
);
348 if (flags
& FE_MSG_WAIT
)
349 gtk_dialog_run (GTK_DIALOG (dialog
));
353 fe_idle_add (void *func
, void *data
)
355 g_idle_add (func
, data
);
359 fe_input_remove (int tag
)
361 g_source_remove (tag
);
365 fe_input_add (int sok
, int flags
, void *func
, void *data
)
370 channel
= g_io_channel_unix_new (sok
);
372 if (flags
& FIA_READ
)
373 type
|= G_IO_IN
| G_IO_HUP
| G_IO_ERR
;
374 if (flags
& FIA_WRITE
)
375 type
|= G_IO_OUT
| G_IO_ERR
;
379 tag
= g_io_add_watch (channel
, type
, (GIOFunc
) func
, data
);
380 g_io_channel_unref (channel
);
386 fe_set_topic (session
*sess
, char *topic
, char *stripped_topic
)
388 if (!sess
->gui
->is_tab
|| sess
== current_tab
)
390 gtk_entry_set_text (GTK_ENTRY (sess
->gui
->topic_entry
), stripped_topic
);
391 mg_set_topic_tip (sess
);
394 if (sess
->res
->topic_text
)
395 free (sess
->res
->topic_text
);
396 sess
->res
->topic_text
= strdup (stripped_topic
);
401 fe_set_hilight (struct session
*sess
)
403 if (sess
->gui
->is_tab
)
404 fe_set_tab_color (sess
, 3); /* set tab to blue */
406 if (prefs
.input_flash_hilight
)
407 fe_flash_window (sess
); /* taskbar flash */
411 fe_update_mode_entry (session
*sess
, GtkWidget
*entry
, char **text
, char *new_text
)
413 if (!sess
->gui
->is_tab
|| sess
== current_tab
)
415 if (sess
->gui
->flag_wid
[0]) /* channel mode buttons enabled? */
416 gtk_entry_set_text (GTK_ENTRY (entry
), new_text
);
419 if (sess
->gui
->is_tab
)
423 *text
= strdup (new_text
);
429 fe_update_channel_key (struct session
*sess
)
431 fe_update_mode_entry (sess
, sess
->gui
->key_entry
,
432 &sess
->res
->key_text
, sess
->channelkey
);
437 fe_update_channel_limit (struct session
*sess
)
441 sprintf (tmp
, "%d", sess
->limit
);
442 fe_update_mode_entry (sess
, sess
->gui
->limit_entry
,
443 &sess
->res
->limit_text
, tmp
);
448 fe_is_chanwindow (struct server
*serv
)
450 if (!serv
->gui
->chanlist_window
)
456 fe_is_banwindow (struct session
*sess
)
458 if (!sess
->res
->banlist_window
)
464 fe_notify_update (char *name
)
467 notify_gui_update ();
471 fe_text_clear (struct session
*sess
, int lines
)
473 gtk_xtext_clear (sess
->res
->buffer
, lines
);
477 fe_close_window (struct session
*sess
)
479 if (sess
->gui
->is_tab
)
482 gtk_widget_destroy (sess
->gui
->window
);
486 fe_progressbar_start (session
*sess
)
488 if (!sess
->gui
->is_tab
|| current_tab
== sess
)
489 /* if it's the focused tab, create it for real! */
490 mg_progressbar_create (sess
->gui
);
492 /* otherwise just remember to create on when it gets focused */
493 sess
->res
->c_graph
= TRUE
;
497 fe_progressbar_end (server
*serv
)
499 GSList
*list
= sess_list
;
502 while (list
) /* check all windows that use this server and *
503 * remove the connecting graph, if it has one. */
506 if (sess
->server
== serv
)
509 mg_progressbar_destroy (sess
->gui
);
510 sess
->res
->c_graph
= FALSE
;
517 fe_print_text (struct session
*sess
, char *text
, time_t stamp
)
519 PrintTextRaw (sess
->res
->buffer
, (unsigned char *)text
, prefs
.indent_nicks
, stamp
);
521 if (!sess
->new_data
&& sess
!= current_tab
&&
522 sess
->gui
->is_tab
&& !sess
->nick_said
&& stamp
== 0)
524 sess
->new_data
= TRUE
;
526 fe_set_tab_color (sess
, 2);
528 fe_set_tab_color (sess
, 1);
539 lastlog_regex_cmp (char *a
, regex_t
*reg
)
541 return !regexec (reg
, a
, 1, NULL
, REG_NOTBOL
);
545 fe_lastlog (session
*sess
, session
*lastlog_sess
, char *sstr
, gboolean regexp
)
549 if (gtk_xtext_is_empty (sess
->res
->buffer
))
551 PrintText (lastlog_sess
, _("Search buffer is empty.\n"));
557 gtk_xtext_lastlog (lastlog_sess
->res
->buffer
, sess
->res
->buffer
,
558 (void *) nocasestrstr
, sstr
);
562 if (regcomp (®
, sstr
, REG_ICASE
| REG_EXTENDED
| REG_NOSUB
) == 0)
564 gtk_xtext_lastlog (lastlog_sess
->res
->buffer
, sess
->res
->buffer
,
565 (void *) lastlog_regex_cmp
, ®
);
571 fe_set_lag (server
*serv
, int lag
)
573 GSList
*list
= sess_list
;
578 unsigned long nowtim
;
584 nowtim
= make_ping_time ();
585 lag
= (nowtim
- serv
->lag_sent
) / 100000;
588 per
= (double)((double)lag
/ (double)10);
592 snprintf (lagtext
, sizeof (lagtext
) - 1, "%s%d.%ds",
593 serv
->lag_sent
? "+" : "", lag
/ 10, lag
% 10);
594 snprintf (lagtip
, sizeof (lagtip
) - 1, "Lag: %s%d.%d seconds",
595 serv
->lag_sent
? "+" : "", lag
/ 10, lag
% 10);
600 if (sess
->server
== serv
)
602 if (sess
->res
->lag_tip
)
603 free (sess
->res
->lag_tip
);
604 sess
->res
->lag_tip
= strdup (lagtip
);
606 if (!sess
->gui
->is_tab
|| current_tab
== sess
)
608 if (sess
->gui
->lagometer
)
610 gtk_progress_bar_set_fraction ((GtkProgressBar
*) sess
->gui
->lagometer
, per
);
611 add_tip (sess
->gui
->lagometer
->parent
, lagtip
);
613 if (sess
->gui
->laginfo
)
614 gtk_label_set_text ((GtkLabel
*) sess
->gui
->laginfo
, lagtext
);
617 sess
->res
->lag_value
= per
;
618 if (sess
->res
->lag_text
)
619 free (sess
->res
->lag_text
);
620 sess
->res
->lag_text
= strdup (lagtext
);
628 fe_set_throttle (server
*serv
)
630 GSList
*list
= sess_list
;
631 struct session
*sess
;
636 per
= (float) serv
->sendq_len
/ 1024.0;
643 if (sess
->server
== serv
)
645 snprintf (tbuf
, sizeof (tbuf
) - 1, _("%d bytes"), serv
->sendq_len
);
646 snprintf (tip
, sizeof (tip
) - 1, _("Network send queue: %d bytes"), serv
->sendq_len
);
648 if (sess
->res
->queue_tip
)
649 free (sess
->res
->queue_tip
);
650 sess
->res
->queue_tip
= strdup (tip
);
652 if (!sess
->gui
->is_tab
|| current_tab
== sess
)
654 if (sess
->gui
->throttlemeter
)
656 gtk_progress_bar_set_fraction ((GtkProgressBar
*) sess
->gui
->throttlemeter
, per
);
657 add_tip (sess
->gui
->throttlemeter
->parent
, tip
);
659 if (sess
->gui
->throttleinfo
)
660 gtk_label_set_text ((GtkLabel
*) sess
->gui
->throttleinfo
, tbuf
);
663 sess
->res
->queue_value
= per
;
664 if (sess
->res
->queue_text
)
665 free (sess
->res
->queue_text
);
666 sess
->res
->queue_text
= strdup (tbuf
);
674 fe_ctrl_gui (session
*sess
, fe_gui_action action
, int arg
)
679 gtk_widget_hide (sess
->gui
->window
); break;
681 gtk_widget_show (sess
->gui
->window
);
682 gtk_window_present (GTK_WINDOW (sess
->gui
->window
));
685 mg_bring_tofront_sess (sess
); break;
687 fe_flash_window (sess
); break;
689 fe_set_tab_color (sess
, arg
); break;
691 gtk_window_iconify (GTK_WINDOW (sess
->gui
->window
)); break;
693 menu_bar_toggle (); /* toggle menubar on/off */
696 mg_detach (sess
, arg
); /* arg: 0=toggle 1=detach 2=attach */
699 setup_apply_real (TRUE
, TRUE
, FALSE
);
704 dcc_saveas_cb (struct DCC
*dcc
, char *file
)
708 if (dcc
->dccstat
== STAT_QUEUED
)
711 dcc_get_with_destfile (dcc
, file
);
712 else if (dcc
->resume_sent
== 0)
713 dcc_abort (dcc
->serv
->front_session
, dcc
);
719 fe_confirm (const char *message
, void (*yesproc
)(void *), void (*noproc
)(void *), void *ud
)
721 /* warning, assuming fe_confirm is used by DCC only! */
722 struct DCC
*dcc
= ud
;
725 gtkutil_file_req (message
, dcc_saveas_cb
, ud
, dcc
->file
,
726 FRF_WRITE
|FRF_FILTERISINITIAL
|FRF_NOASKOVERWRITE
);
730 fe_gui_info (session
*sess
, int info_type
)
734 case 0: /* window status */
735 #if GTK_CHECK_VERSION(2,20,0)
736 if (!gtk_widget_get_visible (GTK_WIDGET (sess
->gui
->window
)))
738 if (!GTK_WIDGET_VISIBLE (GTK_WIDGET (sess
->gui
->window
)))
740 return 2; /* hidden (iconified or systray) */
742 if (gtk_window_is_active (GTK_WINDOW (sess
->gui
->window
)))
743 return 1; /* active/focused */
745 return 0; /* normal (no keyboard focus or behind a window) */
752 fe_gui_info_ptr (session
*sess
, int info_type
)
756 case 0: /* native window pointer (for plugins) */
757 return sess
->gui
->window
;
759 case 1: /* GtkWindow * (for plugins) */
760 return sess
->gui
->window
;
766 fe_get_inputbox_contents (session
*sess
)
768 /* not the current tab */
769 if (sess
->res
->input_text
)
770 return sess
->res
->input_text
;
772 /* current focused tab */
773 return SPELL_ENTRY_GET_TEXT (sess
->gui
->input_box
);
777 fe_get_inputbox_cursor (session
*sess
)
779 /* not the current tab (we don't remember the cursor pos) */
780 if (sess
->res
->input_text
)
783 /* current focused tab */
784 return SPELL_ENTRY_GET_POS (sess
->gui
->input_box
);
788 fe_set_inputbox_cursor (session
*sess
, int delta
, int pos
)
790 if (!sess
->gui
->is_tab
|| sess
== current_tab
)
793 pos
+= SPELL_ENTRY_GET_POS (sess
->gui
->input_box
);
794 SPELL_ENTRY_SET_POS (sess
->gui
->input_box
, pos
);
797 /* we don't support changing non-front tabs yet */
802 fe_set_inputbox_contents (session
*sess
, char *text
)
804 if (!sess
->gui
->is_tab
|| sess
== current_tab
)
806 SPELL_ENTRY_SET_TEXT (sess
->gui
->input_box
, text
);
809 if (sess
->res
->input_text
)
810 free (sess
->res
->input_text
);
811 sess
->res
->input_text
= strdup (text
);
816 try_browser (const char *browser
, const char *arg
, const char *url
)
821 path
= g_find_program_in_path (browser
);
840 fe_open_url_inner (const char *url
)
842 /* universal desktop URL opener (from xdg-utils). Supports gnome,kde,xfce4. */
843 if (try_browser ("xdg-open", NULL
, url
))
846 /* try to detect GNOME */
847 if (g_getenv ("GNOME_DESKTOP_SESSION_ID"))
849 if (try_browser ("gnome-open", NULL
, url
)) /* Gnome 2.4+ has this */
853 /* try to detect KDE */
854 if (g_getenv ("KDE_FULL_SESSION"))
856 if (try_browser ("kfmclient", "exec", url
))
860 /* everything failed, what now? just try firefox */
861 if (try_browser ("firefox", NULL
, url
))
864 /* fresh out of ideas... */
865 try_browser ("mozilla", NULL
, url
);
869 fe_open_url_locale (const char *url
)
871 if (url
[0] != '/' && strchr (url
, ':') == NULL
)
873 url
= g_strdup_printf ("http://%s", url
);
874 fe_open_url_inner (url
);
875 g_free ((char *)url
);
878 fe_open_url_inner (url
);
882 fe_open_url (const char *url
)
886 if (prefs
.utf8_locale
)
888 fe_open_url_locale (url
);
892 /* the OS expects it in "locale" encoding. This makes it work on
893 unix systems that use ISO-8859-x and Win32. */
894 loc
= g_locale_from_utf8 (url
, -1, 0, 0, 0);
897 fe_open_url_locale (loc
);
903 fe_server_event (server
*serv
, int type
, int arg
)
905 GSList
*list
= sess_list
;
911 if (sess
->server
== serv
&& (current_tab
== sess
|| !sess
->gui
->is_tab
))
913 session_gui
*gui
= sess
->gui
;
917 case FE_SE_CONNECTING
: /* connecting in progress */
918 case FE_SE_RECONDELAY
: /* reconnect delay begun */
919 /* enable Disconnect item */
920 gtk_widget_set_sensitive (gui
->menu_item
[MENU_ID_DISCONNECT
], 1);
924 /* enable Disconnect and Away menu items */
925 gtk_widget_set_sensitive (gui
->menu_item
[MENU_ID_AWAY
], 1);
926 gtk_widget_set_sensitive (gui
->menu_item
[MENU_ID_DISCONNECT
], 1);
929 case FE_SE_LOGGEDIN
: /* end of MOTD */
930 gtk_widget_set_sensitive (gui
->menu_item
[MENU_ID_JOIN
], 1);
931 /* if number of auto-join channels is zero, open joind */
936 case FE_SE_DISCONNECT
:
937 /* disable Disconnect and Away menu items */
938 gtk_widget_set_sensitive (gui
->menu_item
[MENU_ID_AWAY
], 0);
939 gtk_widget_set_sensitive (gui
->menu_item
[MENU_ID_DISCONNECT
], 0);
940 gtk_widget_set_sensitive (gui
->menu_item
[MENU_ID_JOIN
], 0);
941 /* close the join-dialog, if one exists */
950 fe_get_file (const char *title
, char *initial
,
951 void (*callback
) (void *userdata
, char *file
), void *userdata
,
955 /* OK: Call callback once per file, then once more with file=NULL. */
956 /* CANCEL: Call callback once with file=NULL. */
957 gtkutil_file_req (title
, callback
, userdata
, initial
, flags
| FRF_FILTERISINITIAL
);