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"
49 #include "notifygui.h"
52 #include "plugin-tray.h"
57 #include <gtk/gtkinvisible.h>
61 #include <gtk/gtktextview.h>
64 GdkPixmap
*channelwin_pix
;
70 redraw_trans_xtexts (void)
72 GSList
*list
= sess_list
;
74 int done_main
= FALSE
;
79 if (GTK_XTEXT (sess
->gui
->xtext
)->transparent
)
81 if (!sess
->gui
->is_tab
|| !done_main
)
82 gtk_xtext_refresh (GTK_XTEXT (sess
->gui
->xtext
), 1);
83 if (sess
->gui
->is_tab
)
90 static GdkFilterReturn
91 root_event_cb (GdkXEvent
*xev
, GdkEventProperty
*event
, gpointer data
)
93 static Atom at
= None
;
94 XEvent
*xevent
= (XEvent
*)xev
;
96 if (xevent
->type
== PropertyNotify
)
99 at
= XInternAtom (xevent
->xproperty
.display
, "_XROOTPMAP_ID", True
);
101 if (at
== xevent
->xproperty
.atom
)
102 redraw_trans_xtexts ();
105 return GDK_FILTER_CONTINUE
;
110 /* === command-line parameter parsing : requires glib 2.6 === */
112 static char *arg_cfgdir
= NULL
;
113 static gint arg_show_autoload
= 0;
114 static gint arg_show_config
= 0;
115 static gint arg_show_version
= 0;
116 static gint arg_minimize
= 0;
118 static const GOptionEntry gopt_entries
[] =
120 {"no-auto", 'a', 0, G_OPTION_ARG_NONE
, &arg_dont_autoconnect
, N_("Don't auto connect to servers"), NULL
},
121 {"cfgdir", 'd', 0, G_OPTION_ARG_STRING
, &arg_cfgdir
, N_("Use a different config directory"), "PATH"},
122 {"no-plugins", 'n', 0, G_OPTION_ARG_NONE
, &arg_skip_plugins
, N_("Don't auto load any plugins"), NULL
},
123 {"plugindir", 'p', 0, G_OPTION_ARG_NONE
, &arg_show_autoload
, N_("Show plugin auto-load directory"), NULL
},
124 {"configdir", 'u', 0, G_OPTION_ARG_NONE
, &arg_show_config
, N_("Show user config directory"), NULL
},
125 {"url", 0, 0, G_OPTION_ARG_STRING
, &arg_url
, N_("Open an irc://server:port/channel URL"), "URL"},
126 {"command", 'c', 0, G_OPTION_ARG_STRING
, &arg_command
, N_("Execute command:"), "COMMAND"},
127 {"existing", 'e', 0, G_OPTION_ARG_NONE
, &arg_existing
, N_("Open URL or execute command in an existing XChat"), NULL
},
128 {"minimize", 0, 0, G_OPTION_ARG_INT
, &arg_minimize
, N_("Begin minimized. Level 0=Normal 1=Iconified 2=Tray"), N_("level")},
129 {"version", 'v', 0, G_OPTION_ARG_NONE
, &arg_show_version
, N_("Show version information"), NULL
},
134 fe_args (int argc
, char *argv
[])
136 GError
*error
= NULL
;
137 GOptionContext
*context
;
139 context
= g_option_context_new (NULL
);
140 //g_option_context_add_main_entries (context, gopt_entries, GETTEXT_PACKAGE);
141 g_option_context_add_group (context
, gtk_get_option_group (FALSE
));
142 g_option_context_parse (context
, &argc
, &argv
, &error
);
147 printf ("%s\n", error
->message
);
151 g_option_context_free (context
);
153 if (arg_show_version
)
155 printf (PACKAGE_TARNAME
" "PACKAGE_VERSION
"\n");
159 if (arg_show_autoload
)
161 printf ("%s\n", XCHATLIBDIR
"/plugins");
167 printf ("%s\n", get_xdir_fs ());
171 if (arg_cfgdir
) /* we want filesystem encoding */
173 xdir_fs
= strdup (arg_cfgdir
);
174 if (xdir_fs
[strlen (xdir_fs
) - 1] == '/')
175 xdir_fs
[strlen (xdir_fs
) - 1] = 0;
179 gtk_init (&argc
, &argv
);
182 gdk_window_set_events (gdk_get_default_root_window (), GDK_PROPERTY_CHANGE_MASK
);
183 gdk_window_add_filter (gdk_get_default_root_window (),
184 (GdkFilterFunc
)root_event_cb
, NULL
);
190 const char cursor_color_rc
[] =
194 "GtkTextView::cursor-color=\"#%02x%02x%02x\""
196 "GtkEntry::cursor-color=\"#%02x%02x%02x\""
199 "widget \"*.xchat-inputbox\" style : application \"xc-ib-st\"";
202 create_input_style (GtkStyle
*style
)
205 static int done_rc
= FALSE
;
207 pango_font_description_free (style
->font_desc
);
208 style
->font_desc
= pango_font_description_from_string (prefs
.font_normal
);
211 if (pango_font_description_get_size (style
->font_desc
) == 0)
213 snprintf (buf
, sizeof (buf
), _("Failed to open font:\n\n%s"), prefs
.font_normal
);
214 fe_message (buf
, FE_MSG_ERROR
);
215 pango_font_description_free (style
->font_desc
);
216 style
->font_desc
= pango_font_description_from_string ("sans 11");
219 if (prefs
.style_inputbox
&& !done_rc
)
222 sprintf (buf
, cursor_color_rc
, (colors
[COL_FG
].red
>> 8),
223 (colors
[COL_FG
].green
>> 8), (colors
[COL_FG
].blue
>> 8));
224 gtk_rc_parse_string (buf
);
227 style
->bg
[GTK_STATE_NORMAL
] = colors
[COL_FG
];
228 style
->base
[GTK_STATE_NORMAL
] = colors
[COL_BG
];
229 style
->text
[GTK_STATE_NORMAL
] = colors
[COL_FG
];
241 channelwin_pix
= pixmap_load_from_file (prefs
.background
);
242 input_style
= create_input_style (gtk_style_new ());
250 /* sleep for 3 seconds so any QUIT messages are not lost. The */
251 /* GUI is closed at this point, so the user doesn't even know! */
252 if (prefs
.wait_on_exit
)
259 /* it's saved when pressing OK in setup.c */
270 fe_timeout_add (int interval
, void *callback
, void *userdata
)
272 return g_timeout_add (interval
, (GSourceFunc
) callback
, userdata
);
276 fe_timeout_remove (int tag
)
278 g_source_remove (tag
);
281 /* install tray stuff */
284 fe_idle (gpointer data
)
286 session
*sess
= sess_list
->data
;
288 plugin_add (sess
, NULL
, NULL
, tray_plugin_init
, tray_plugin_deinit
, NULL
, FALSE
);
290 if (arg_minimize
== 1)
291 gtk_window_iconify (GTK_WINDOW (sess
->gui
->window
));
292 else if (arg_minimize
== 2)
293 tray_toggle_visibility (FALSE
);
299 fe_new_window (session
*sess
, int focus
)
303 if (sess
->type
== SESS_DIALOG
)
305 if (prefs
.privmsgtab
)
309 if (prefs
.tabchannels
)
313 mg_changui_new (sess
, NULL
, tab
, focus
);
315 if (!sess_list
->next
)
316 g_idle_add (fe_idle
, NULL
);
320 fe_new_server (struct server
*serv
)
322 serv
->gui
= malloc (sizeof (struct server_gui
));
323 memset (serv
->gui
, 0, sizeof (struct server_gui
));
327 fe_message (char *msg
, int flags
)
330 int type
= GTK_MESSAGE_WARNING
;
332 if (flags
& FE_MSG_ERROR
)
333 type
= GTK_MESSAGE_ERROR
;
334 if (flags
& FE_MSG_INFO
)
335 type
= GTK_MESSAGE_INFO
;
337 dialog
= gtk_message_dialog_new (GTK_WINDOW (parent_window
), 0, type
,
338 GTK_BUTTONS_OK
, "%s", msg
);
339 if (flags
& FE_MSG_MARKUP
)
340 gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (dialog
), msg
);
341 g_signal_connect (G_OBJECT (dialog
), "response",
342 G_CALLBACK (gtk_widget_destroy
), 0);
343 gtk_window_set_resizable (GTK_WINDOW (dialog
), FALSE
);
344 gtk_window_set_position (GTK_WINDOW (dialog
), GTK_WIN_POS_MOUSE
);
345 gtk_widget_show (dialog
);
347 if (flags
& FE_MSG_WAIT
)
348 gtk_dialog_run (GTK_DIALOG (dialog
));
352 fe_idle_add (void *func
, void *data
)
354 g_idle_add (func
, data
);
358 fe_input_remove (int tag
)
360 g_source_remove (tag
);
364 fe_input_add (int sok
, int flags
, void *func
, void *data
)
369 channel
= g_io_channel_unix_new (sok
);
371 if (flags
& FIA_READ
)
372 type
|= G_IO_IN
| G_IO_HUP
| G_IO_ERR
;
373 if (flags
& FIA_WRITE
)
374 type
|= G_IO_OUT
| G_IO_ERR
;
378 tag
= g_io_add_watch (channel
, type
, (GIOFunc
) func
, data
);
379 g_io_channel_unref (channel
);
385 fe_set_topic (session
*sess
, char *topic
, char *stripped_topic
)
387 if (!sess
->gui
->is_tab
|| sess
== current_tab
)
389 gtk_entry_set_text (GTK_ENTRY (sess
->gui
->topic_entry
), stripped_topic
);
390 mg_set_topic_tip (sess
);
393 if (sess
->res
->topic_text
)
394 free (sess
->res
->topic_text
);
395 sess
->res
->topic_text
= strdup (stripped_topic
);
400 fe_set_hilight (struct session
*sess
)
402 if (sess
->gui
->is_tab
)
403 fe_set_tab_color (sess
, 3); /* set tab to blue */
405 if (prefs
.input_flash_hilight
)
406 fe_flash_window (sess
); /* taskbar flash */
410 fe_update_mode_entry (session
*sess
, GtkWidget
*entry
, char **text
, char *new_text
)
412 if (!sess
->gui
->is_tab
|| sess
== current_tab
)
414 if (sess
->gui
->flag_wid
[0]) /* channel mode buttons enabled? */
415 gtk_entry_set_text (GTK_ENTRY (entry
), new_text
);
418 if (sess
->gui
->is_tab
)
422 *text
= strdup (new_text
);
428 fe_update_channel_key (struct session
*sess
)
430 fe_update_mode_entry (sess
, sess
->gui
->key_entry
,
431 &sess
->res
->key_text
, sess
->channelkey
);
436 fe_update_channel_limit (struct session
*sess
)
440 sprintf (tmp
, "%d", sess
->limit
);
441 fe_update_mode_entry (sess
, sess
->gui
->limit_entry
,
442 &sess
->res
->limit_text
, tmp
);
447 fe_is_chanwindow (struct server
*serv
)
449 if (!serv
->gui
->chanlist_window
)
455 fe_is_banwindow (struct session
*sess
)
457 if (!sess
->res
->banlist_window
)
463 fe_notify_update (char *name
)
466 notify_gui_update ();
470 fe_text_clear (struct session
*sess
, int lines
)
472 gtk_xtext_clear (sess
->res
->buffer
, lines
);
476 fe_close_window (struct session
*sess
)
478 if (sess
->gui
->is_tab
)
481 gtk_widget_destroy (sess
->gui
->window
);
485 fe_progressbar_start (session
*sess
)
487 if (!sess
->gui
->is_tab
|| current_tab
== sess
)
488 /* if it's the focused tab, create it for real! */
489 mg_progressbar_create (sess
->gui
);
491 /* otherwise just remember to create on when it gets focused */
492 sess
->res
->c_graph
= TRUE
;
496 fe_progressbar_end (server
*serv
)
498 GSList
*list
= sess_list
;
501 while (list
) /* check all windows that use this server and *
502 * remove the connecting graph, if it has one. */
505 if (sess
->server
== serv
)
508 mg_progressbar_destroy (sess
->gui
);
509 sess
->res
->c_graph
= FALSE
;
516 fe_print_text (struct session
*sess
, char *text
, time_t stamp
)
518 PrintTextRaw (sess
->res
->buffer
, (unsigned char *)text
, prefs
.indent_nicks
, stamp
);
520 if (!sess
->new_data
&& sess
!= current_tab
&&
521 sess
->gui
->is_tab
&& !sess
->nick_said
&& stamp
== 0)
523 sess
->new_data
= TRUE
;
525 fe_set_tab_color (sess
, 2);
527 fe_set_tab_color (sess
, 1);
538 lastlog_regex_cmp (char *a
, regex_t
*reg
)
540 return !regexec (reg
, a
, 1, NULL
, REG_NOTBOL
);
544 fe_lastlog (session
*sess
, session
*lastlog_sess
, char *sstr
, gboolean regexp
)
548 if (gtk_xtext_is_empty (sess
->res
->buffer
))
550 PrintText (lastlog_sess
, _("Search buffer is empty.\n"));
556 gtk_xtext_lastlog (lastlog_sess
->res
->buffer
, sess
->res
->buffer
,
557 (void *) nocasestrstr
, sstr
);
561 if (regcomp (®
, sstr
, REG_ICASE
| REG_EXTENDED
| REG_NOSUB
) == 0)
563 gtk_xtext_lastlog (lastlog_sess
->res
->buffer
, sess
->res
->buffer
,
564 (void *) lastlog_regex_cmp
, ®
);
570 fe_set_lag (server
*serv
, int lag
)
572 GSList
*list
= sess_list
;
577 unsigned long nowtim
;
583 nowtim
= make_ping_time ();
584 lag
= (nowtim
- serv
->lag_sent
) / 100000;
587 per
= (double)((double)lag
/ (double)10);
591 snprintf (lagtext
, sizeof (lagtext
) - 1, "%s%d.%ds",
592 serv
->lag_sent
? "+" : "", lag
/ 10, lag
% 10);
593 snprintf (lagtip
, sizeof (lagtip
) - 1, "Lag: %s%d.%d seconds",
594 serv
->lag_sent
? "+" : "", lag
/ 10, lag
% 10);
599 if (sess
->server
== serv
)
601 if (sess
->res
->lag_tip
)
602 free (sess
->res
->lag_tip
);
603 sess
->res
->lag_tip
= strdup (lagtip
);
605 if (!sess
->gui
->is_tab
|| current_tab
== sess
)
607 if (sess
->gui
->lagometer
)
609 gtk_progress_bar_set_fraction ((GtkProgressBar
*) sess
->gui
->lagometer
, per
);
610 add_tip (sess
->gui
->lagometer
->parent
, lagtip
);
612 if (sess
->gui
->laginfo
)
613 gtk_label_set_text ((GtkLabel
*) sess
->gui
->laginfo
, lagtext
);
616 sess
->res
->lag_value
= per
;
617 if (sess
->res
->lag_text
)
618 free (sess
->res
->lag_text
);
619 sess
->res
->lag_text
= strdup (lagtext
);
627 fe_set_throttle (server
*serv
)
629 GSList
*list
= sess_list
;
630 struct session
*sess
;
635 per
= (float) serv
->sendq_len
/ 1024.0;
642 if (sess
->server
== serv
)
644 snprintf (tbuf
, sizeof (tbuf
) - 1, _("%d bytes"), serv
->sendq_len
);
645 snprintf (tip
, sizeof (tip
) - 1, _("Network send queue: %d bytes"), serv
->sendq_len
);
647 if (sess
->res
->queue_tip
)
648 free (sess
->res
->queue_tip
);
649 sess
->res
->queue_tip
= strdup (tip
);
651 if (!sess
->gui
->is_tab
|| current_tab
== sess
)
653 if (sess
->gui
->throttlemeter
)
655 gtk_progress_bar_set_fraction ((GtkProgressBar
*) sess
->gui
->throttlemeter
, per
);
656 add_tip (sess
->gui
->throttlemeter
->parent
, tip
);
658 if (sess
->gui
->throttleinfo
)
659 gtk_label_set_text ((GtkLabel
*) sess
->gui
->throttleinfo
, tbuf
);
662 sess
->res
->queue_value
= per
;
663 if (sess
->res
->queue_text
)
664 free (sess
->res
->queue_text
);
665 sess
->res
->queue_text
= strdup (tbuf
);
673 fe_ctrl_gui (session
*sess
, fe_gui_action action
, int arg
)
678 gtk_widget_hide (sess
->gui
->window
); break;
680 gtk_widget_show (sess
->gui
->window
);
681 gtk_window_present (GTK_WINDOW (sess
->gui
->window
));
684 mg_bring_tofront_sess (sess
); break;
686 fe_flash_window (sess
); break;
688 fe_set_tab_color (sess
, arg
); break;
690 gtk_window_iconify (GTK_WINDOW (sess
->gui
->window
)); break;
692 menu_bar_toggle (); /* toggle menubar on/off */
695 mg_detach (sess
, arg
); /* arg: 0=toggle 1=detach 2=attach */
698 setup_apply_real (TRUE
, TRUE
);
703 dcc_saveas_cb (struct DCC
*dcc
, char *file
)
707 if (dcc
->dccstat
== STAT_QUEUED
)
710 dcc_get_with_destfile (dcc
, file
);
711 else if (dcc
->resume_sent
== 0)
712 dcc_abort (dcc
->serv
->front_session
, dcc
);
718 fe_confirm (const char *message
, void (*yesproc
)(void *), void (*noproc
)(void *), void *ud
)
720 /* warning, assuming fe_confirm is used by DCC only! */
721 struct DCC
*dcc
= ud
;
724 gtkutil_file_req (message
, dcc_saveas_cb
, ud
, dcc
->file
,
725 FRF_WRITE
|FRF_FILTERISINITIAL
|FRF_NOASKOVERWRITE
);
729 fe_gui_info (session
*sess
, int info_type
)
733 case 0: /* window status */
734 #if GTK_CHECK_VERSION(2,20,0)
735 if (!gtk_widget_get_visible (GTK_WIDGET (sess
->gui
->window
)))
737 if (!GTK_WIDGET_VISIBLE (GTK_WIDGET (sess
->gui
->window
)))
739 return 2; /* hidden (iconified or systray) */
741 if (gtk_window_is_active (GTK_WINDOW (sess
->gui
->window
)))
742 return 1; /* active/focused */
744 return 0; /* normal (no keyboard focus or behind a window) */
751 fe_gui_info_ptr (session
*sess
, int info_type
)
755 case 0: /* native window pointer (for plugins) */
756 return sess
->gui
->window
;
758 case 1: /* GtkWindow * (for plugins) */
759 return sess
->gui
->window
;
765 fe_get_inputbox_contents (session
*sess
)
767 /* not the current tab */
768 if (sess
->res
->input_text
)
769 return sess
->res
->input_text
;
771 /* current focused tab */
772 return SPELL_ENTRY_GET_TEXT (sess
->gui
->input_box
);
776 fe_get_inputbox_cursor (session
*sess
)
778 /* not the current tab (we don't remember the cursor pos) */
779 if (sess
->res
->input_text
)
782 /* current focused tab */
783 return SPELL_ENTRY_GET_POS (sess
->gui
->input_box
);
787 fe_set_inputbox_cursor (session
*sess
, int delta
, int pos
)
789 if (!sess
->gui
->is_tab
|| sess
== current_tab
)
792 pos
+= SPELL_ENTRY_GET_POS (sess
->gui
->input_box
);
793 SPELL_ENTRY_SET_POS (sess
->gui
->input_box
, pos
);
796 /* we don't support changing non-front tabs yet */
801 fe_set_inputbox_contents (session
*sess
, char *text
)
803 if (!sess
->gui
->is_tab
|| sess
== current_tab
)
805 SPELL_ENTRY_SET_TEXT (sess
->gui
->input_box
, text
);
808 if (sess
->res
->input_text
)
809 free (sess
->res
->input_text
);
810 sess
->res
->input_text
= strdup (text
);
815 try_browser (const char *browser
, const char *arg
, const char *url
)
820 path
= g_find_program_in_path (browser
);
839 fe_open_url_inner (const char *url
)
841 /* universal desktop URL opener (from xdg-utils). Supports gnome,kde,xfce4. */
842 if (try_browser ("xdg-open", NULL
, url
))
845 /* try to detect GNOME */
846 if (g_getenv ("GNOME_DESKTOP_SESSION_ID"))
848 if (try_browser ("gnome-open", NULL
, url
)) /* Gnome 2.4+ has this */
852 /* try to detect KDE */
853 if (g_getenv ("KDE_FULL_SESSION"))
855 if (try_browser ("kfmclient", "exec", url
))
859 /* everything failed, what now? just try firefox */
860 if (try_browser ("firefox", NULL
, url
))
863 /* fresh out of ideas... */
864 try_browser ("mozilla", NULL
, url
);
868 fe_open_url_locale (const char *url
)
870 if (url
[0] != '/' && strchr (url
, ':') == NULL
)
872 url
= g_strdup_printf ("http://%s", url
);
873 fe_open_url_inner (url
);
874 g_free ((char *)url
);
877 fe_open_url_inner (url
);
881 fe_open_url (const char *url
)
885 if (prefs
.utf8_locale
)
887 fe_open_url_locale (url
);
891 /* the OS expects it in "locale" encoding. This makes it work on
892 unix systems that use ISO-8859-x and Win32. */
893 loc
= g_locale_from_utf8 (url
, -1, 0, 0, 0);
896 fe_open_url_locale (loc
);
902 fe_server_event (server
*serv
, int type
, int arg
)
904 GSList
*list
= sess_list
;
910 if (sess
->server
== serv
&& (current_tab
== sess
|| !sess
->gui
->is_tab
))
912 session_gui
*gui
= sess
->gui
;
916 case FE_SE_CONNECTING
: /* connecting in progress */
917 case FE_SE_RECONDELAY
: /* reconnect delay begun */
918 /* enable Disconnect item */
919 gtk_widget_set_sensitive (gui
->menu_item
[MENU_ID_DISCONNECT
], 1);
923 /* enable Disconnect and Away menu items */
924 gtk_widget_set_sensitive (gui
->menu_item
[MENU_ID_AWAY
], 1);
925 gtk_widget_set_sensitive (gui
->menu_item
[MENU_ID_DISCONNECT
], 1);
928 case FE_SE_LOGGEDIN
: /* end of MOTD */
929 gtk_widget_set_sensitive (gui
->menu_item
[MENU_ID_JOIN
], 1);
930 /* if number of auto-join channels is zero, open joind */
935 case FE_SE_DISCONNECT
:
936 /* disable Disconnect and Away menu items */
937 gtk_widget_set_sensitive (gui
->menu_item
[MENU_ID_AWAY
], 0);
938 gtk_widget_set_sensitive (gui
->menu_item
[MENU_ID_DISCONNECT
], 0);
939 gtk_widget_set_sensitive (gui
->menu_item
[MENU_ID_JOIN
], 0);
940 /* close the join-dialog, if one exists */
949 fe_get_file (const char *title
, char *initial
,
950 void (*callback
) (void *userdata
, char *file
), void *userdata
,
954 /* OK: Call callback once per file, then once more with file=NULL. */
955 /* CANCEL: Call callback once with file=NULL. */
956 gtkutil_file_req (title
, callback
, userdata
, initial
, flags
| FRF_FILTERISINITIAL
);