]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/gtkcomm.c
first pass
[PuTTY.git] / unix / gtkcomm.c
1 /*
2  * gtkcomm.c: machinery in the GTK front end which is common to all
3  * programs that run a session in a terminal window, and also common
4  * across all _sessions_ rather than specific to one session. (Timers,
5  * uxsel etc.)
6  */
7
8 #define _GNU_SOURCE
9
10 #include <string.h>
11 #include <assert.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <signal.h>
15 #include <stdio.h>
16 #include <time.h>
17 #include <errno.h>
18 #include <locale.h>
19 #include <fcntl.h>
20 #include <unistd.h>
21 #include <sys/types.h>
22 #include <sys/wait.h>
23 #include <gtk/gtk.h>
24 #if !GTK_CHECK_VERSION(3,0,0)
25 #include <gdk/gdkkeysyms.h>
26 #endif
27
28 #if GTK_CHECK_VERSION(2,0,0)
29 #include <gtk/gtkimmodule.h>
30 #endif
31
32 #define MAY_REFER_TO_GTK_IN_HEADERS
33
34 #include "putty.h"
35 #include "terminal.h"
36 #include "gtkcompat.h"
37 #include "gtkfont.h"
38 #include "gtkmisc.h"
39
40 #ifndef NOT_X_WINDOWS
41 #include <gdk/gdkx.h>
42 #include <X11/Xlib.h>
43 #include <X11/Xutil.h>
44 #include <X11/Xatom.h>
45 #endif
46
47 #define CAT2(x,y) x ## y
48 #define CAT(x,y) CAT2(x,y)
49 #define ASSERT(x) enum {CAT(assertion_,__LINE__) = 1 / (x)}
50
51 #if GTK_CHECK_VERSION(2,0,0)
52 ASSERT(sizeof(long) <= sizeof(gsize));
53 #define LONG_TO_GPOINTER(l) GSIZE_TO_POINTER(l)
54 #define GPOINTER_TO_LONG(p) GPOINTER_TO_SIZE(p)
55 #else /* Gtk 1.2 */
56 ASSERT(sizeof(long) <= sizeof(gpointer));
57 #define LONG_TO_GPOINTER(l) ((gpointer)(long)(l))
58 #define GPOINTER_TO_LONG(p) ((long)(p))
59 #endif
60
61 /* ----------------------------------------------------------------------
62  * File descriptors and uxsel.
63  */
64
65 struct uxsel_id {
66 #if GTK_CHECK_VERSION(2,0,0)
67     GIOChannel *chan;
68     guint watch_id;
69 #else
70     int id;
71 #endif
72 };
73
74 #if GTK_CHECK_VERSION(2,0,0)
75 gboolean fd_input_func(GIOChannel *source, GIOCondition condition,
76                        gpointer data)
77 {
78     int sourcefd = g_io_channel_unix_get_fd(source);
79     /*
80      * We must process exceptional notifications before ordinary
81      * readability ones, or we may go straight past the urgent
82      * marker.
83      */
84     if (condition & G_IO_PRI)
85         select_result(sourcefd, 4);
86     if (condition & G_IO_IN)
87         select_result(sourcefd, 1);
88     if (condition & G_IO_OUT)
89         select_result(sourcefd, 2);
90
91     return TRUE;
92 }
93 #else
94 void fd_input_func(gpointer data, gint sourcefd, GdkInputCondition condition)
95 {
96     if (condition & GDK_INPUT_EXCEPTION)
97         select_result(sourcefd, 4);
98     if (condition & GDK_INPUT_READ)
99         select_result(sourcefd, 1);
100     if (condition & GDK_INPUT_WRITE)
101         select_result(sourcefd, 2);
102 }
103 #endif
104
105 uxsel_id *uxsel_input_add(int fd, int rwx) {
106     uxsel_id *id = snew(uxsel_id);
107
108 #if GTK_CHECK_VERSION(2,0,0)
109     int flags = 0;
110     if (rwx & 1) flags |= G_IO_IN;
111     if (rwx & 2) flags |= G_IO_OUT;
112     if (rwx & 4) flags |= G_IO_PRI;
113     id->chan = g_io_channel_unix_new(fd);
114     g_io_channel_set_encoding(id->chan, NULL, NULL);
115     id->watch_id = g_io_add_watch_full(id->chan, GDK_PRIORITY_REDRAW+1, flags,
116                                        fd_input_func, NULL, NULL);
117 #else
118     int flags = 0;
119     if (rwx & 1) flags |= GDK_INPUT_READ;
120     if (rwx & 2) flags |= GDK_INPUT_WRITE;
121     if (rwx & 4) flags |= GDK_INPUT_EXCEPTION;
122     assert(flags);
123     id->id = gdk_input_add(fd, flags, fd_input_func, NULL);
124 #endif
125
126     return id;
127 }
128
129 void uxsel_input_remove(uxsel_id *id) {
130 #if GTK_CHECK_VERSION(2,0,0)
131     g_source_remove(id->watch_id);
132     g_io_channel_unref(id->chan);
133 #else
134     gdk_input_remove(id->id);
135 #endif
136     sfree(id);
137 }
138
139 /* ----------------------------------------------------------------------
140  * Timers.
141  */
142
143 static guint timer_id = 0;
144
145 static gint timer_trigger(gpointer data)
146 {
147     unsigned long now = GPOINTER_TO_LONG(data);
148     unsigned long next, then;
149     long ticks;
150
151     /*
152      * Destroy the timer we got here on.
153      */
154     if (timer_id) {
155         g_source_remove(timer_id);
156         timer_id = 0;
157     }
158
159     /*
160      * run_timers() may cause a call to timer_change_notify, in which
161      * case a new timer will already have been set up and left in
162      * timer_id. If it hasn't, and run_timers reports that some timing
163      * still needs to be done, we do it ourselves.
164      */
165     if (run_timers(now, &next) && !timer_id) {
166         then = now;
167         now = GETTICKCOUNT();
168         if (now - then > next - then)
169             ticks = 0;
170         else
171             ticks = next - now;
172         timer_id = g_timeout_add(ticks, timer_trigger, LONG_TO_GPOINTER(next));
173     }
174
175     /*
176      * Returning FALSE means 'don't call this timer again', which
177      * _should_ be redundant given that we removed it above, but just
178      * in case, return FALSE anyway.
179      */
180     return FALSE;
181 }
182
183 void timer_change_notify(unsigned long next)
184 {
185     long ticks;
186
187     if (timer_id)
188         g_source_remove(timer_id);
189
190     ticks = next - GETTICKCOUNT();
191     if (ticks <= 0)
192         ticks = 1;                     /* just in case */
193
194     timer_id = g_timeout_add(ticks, timer_trigger, LONG_TO_GPOINTER(next));
195 }
196
197 /* ----------------------------------------------------------------------
198  * Toplevel callbacks.
199  */
200
201 static guint toplevel_callback_idle_id;
202 static int idle_fn_scheduled;
203
204 static void notify_toplevel_callback(void *);
205
206 /*
207  * Replacement code for the gtk_quit_add() function, which GTK2 - in
208  * their unbounded wisdom - deprecated without providing any usable
209  * replacement, and which we were using to ensure that our idle
210  * function for toplevel callbacks was only run from the outermost
211  * gtk_main().
212  *
213  * We must make sure that all our subsidiary calls to gtk_main() are
214  * followed by a call to post_main(), so that the idle function can be
215  * re-established when we end up back at the top level.
216  */
217 void post_main(void)
218 {
219     if (gtk_main_level() == 1)
220         notify_toplevel_callback(NULL);
221 }
222
223 static gint idle_toplevel_callback_func(gpointer data)
224 {
225     if (gtk_main_level() > 1) {
226         /*
227          * We don't run callbacks if we're in the middle of a
228          * subsidiary gtk_main. So unschedule this idle function; it
229          * will be rescheduled by post_main() when we come back up a
230          * level, which is the earliest we might actually do
231          * something.
232          */
233         if (idle_fn_scheduled) {      /* double-check, just in case */
234             g_source_remove(toplevel_callback_idle_id);
235             idle_fn_scheduled = FALSE;
236         }
237     } else {
238         run_toplevel_callbacks();
239     }
240
241     /*
242      * If we've emptied our toplevel callback queue, unschedule
243      * ourself. Otherwise, leave ourselves pending so we'll be called
244      * again to deal with more callbacks after another round of the
245      * event loop.
246      */
247     if (!toplevel_callback_pending() && idle_fn_scheduled) {
248         g_source_remove(toplevel_callback_idle_id);
249         idle_fn_scheduled = FALSE;
250     }
251
252     return TRUE;
253 }
254
255 static void notify_toplevel_callback(void *frontend)
256 {
257     if (!idle_fn_scheduled) {
258         toplevel_callback_idle_id =
259             g_idle_add(idle_toplevel_callback_func, NULL);
260         idle_fn_scheduled = TRUE;
261     }
262 }
263
264 /* ----------------------------------------------------------------------
265  * Setup function. The real main program must call this.
266  */
267
268 void gtkcomm_setup(void)
269 {
270     uxsel_init();
271     request_callback_notifications(notify_toplevel_callback, NULL);
272 }