1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 obt/mainloop.c for the Openbox window manager
4 Copyright (c) 2006 Mikael Magnusson
5 Copyright (c) 2003-2007 Dana Jansens
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 See the COPYING file for a copy of the GNU General Public License.
20 #include "obt/mainloop.h"
21 #include "obt/display.h"
30 #ifdef HAVE_SYS_SELECT_H
31 #include <sys/select.h>
33 #ifdef HAVE_SYS_SOCKET_H
34 #include <sys/socket.h>
40 typedef struct _ObtMainLoopTimer ObtMainLoopTimer
;
41 typedef struct _ObtMainLoopSignal ObtMainLoopSignal
;
42 typedef struct _ObtMainLoopSignalHandlerType ObtMainLoopSignalHandlerType
;
43 typedef struct _ObtMainLoopXHandlerType ObtMainLoopXHandlerType
;
44 typedef struct _ObtMainLoopFdHandlerType ObtMainLoopFdHandlerType
;
46 /* this should be more than the number of possible signals on any
48 #define NUM_SIGNALS 99
50 /* all created ObtMainLoops. Used by the signal handler to pass along
52 static GSList
*all_loops
;
54 /* signals are global to all loops */
56 guint installed
; /* a ref count */
57 struct sigaction oldact
;
58 } all_signals
[NUM_SIGNALS
];
60 /* a set of all possible signals */
61 static sigset_t all_signals_set
;
63 /* signals which cause a core dump, these can't be used for callbacks */
64 static gint core_signals
[] =
77 #define NUM_CORE_SIGNALS (sizeof(core_signals) / sizeof(core_signals[0]))
79 static void sighandler(gint sig
);
80 static void timer_dispatch(ObtMainLoop
*loop
, GTimeVal
**wait
);
81 static void fd_handler_destroy(gpointer data
);
82 static void calc_max_fd(ObtMainLoop
*loop
);
89 gboolean run
; /* do keep running */
90 gboolean running
; /* is still running */
94 gint fd_x
; /* The X fd is a special case! */
96 GHashTable
*fd_handlers
;
103 gboolean signal_fired
;
104 guint signals_fired
[NUM_SIGNALS
];
105 GSList
*signal_handlers
[NUM_SIGNALS
];
108 struct _ObtMainLoopTimer
114 GDestroyNotify destroy
;
116 /* The timer needs to be freed */
118 /* The time the last fire should've been at */
120 /* When this timer will next trigger */
123 /* Only allow a timer's function to fire once per run through the list,
124 so that it doesn't get locked in there forever */
128 struct _ObtMainLoopSignalHandlerType
133 ObtMainLoopSignalHandler func
;
134 GDestroyNotify destroy
;
137 struct _ObtMainLoopXHandlerType
141 ObtMainLoopXHandler func
;
142 GDestroyNotify destroy
;
145 struct _ObtMainLoopFdHandlerType
150 ObtMainLoopFdHandler func
;
151 GDestroyNotify destroy
;
154 ObtMainLoop
*obt_main_loop_new(void)
158 loop
= g_new0(ObtMainLoop
, 1);
160 FD_ZERO(&loop
->fd_set
);
164 loop
->fd_handlers
= g_hash_table_new_full(g_int_hash
, g_int_equal
,
165 NULL
, fd_handler_destroy
);
167 g_get_current_time(&loop
->now
);
169 /* only do this if we're the first loop created */
172 struct sigaction action
;
175 /* initialize the all_signals_set */
176 sigfillset(&all_signals_set
);
178 sigemptyset(&sigset
);
179 action
.sa_handler
= sighandler
;
180 action
.sa_mask
= sigset
;
181 action
.sa_flags
= SA_NOCLDSTOP
;
183 /* grab all the signals that cause core dumps */
184 for (i
= 0; i
< NUM_CORE_SIGNALS
; ++i
) {
185 /* SIGABRT is curiously not grabbed here!! that's because when we
186 get one of the core_signals, we use abort() to dump the core.
187 And having the abort() only go back to our signal handler again
188 is less than optimal */
189 if (core_signals
[i
] != SIGABRT
) {
190 sigaction(core_signals
[i
], &action
,
191 &all_signals
[core_signals
[i
]].oldact
);
192 all_signals
[core_signals
[i
]].installed
++;
197 all_loops
= g_slist_prepend(all_loops
, loop
);
202 void obt_main_loop_ref(ObtMainLoop
*loop
)
207 void obt_main_loop_unref(ObtMainLoop
*loop
)
212 if (loop
&& --loop
->ref
== 0) {
213 g_assert(loop
->running
== FALSE
);
215 for (it
= loop
->x_handlers
; it
; it
= next
) {
216 ObtMainLoopXHandlerType
*h
= it
->data
;
217 next
= g_slist_next(it
);
218 obt_main_loop_x_remove(loop
, h
->func
);
221 g_hash_table_destroy(loop
->fd_handlers
);
223 for (it
= loop
->timers
; it
; it
= g_slist_next(it
)) {
224 ObtMainLoopTimer
*t
= it
->data
;
225 if (t
->destroy
) t
->destroy(t
->data
);
226 g_slice_free(ObtMainLoopTimer
, t
);
228 g_slist_free(loop
->timers
);
231 for (i
= 0; i
< NUM_SIGNALS
; ++i
)
232 for (it
= loop
->signal_handlers
[i
]; it
; it
= next
) {
233 ObtMainLoopSignalHandlerType
*h
= it
->data
;
234 next
= g_slist_next(it
);
235 obt_main_loop_signal_remove(loop
, h
->func
);
238 all_loops
= g_slist_remove(all_loops
, loop
);
240 /* only do this if we're the last loop destroyed */
242 /* grab all the signals that cause core dumps */
243 for (i
= 0; i
< NUM_CORE_SIGNALS
; ++i
) {
244 if (all_signals
[core_signals
[i
]].installed
) {
245 sigaction(core_signals
[i
],
246 &all_signals
[core_signals
[i
]].oldact
, NULL
);
247 all_signals
[core_signals
[i
]].installed
--;
252 obt_free0(loop
, ObtMainLoop
, 1);
256 static void fd_handle_foreach(gpointer key
,
260 ObtMainLoopFdHandlerType
*h
= value
;
263 if (FD_ISSET(h
->fd
, set
))
264 h
->func(h
->fd
, h
->data
);
267 void obt_main_loop_run(ObtMainLoop
*loop
)
270 struct timeval
*wait
;
275 loop
->running
= TRUE
;
278 if (loop
->signal_fired
) {
282 /* block signals so that we can do this without the data changing
284 sigprocmask(SIG_SETMASK
, &all_signals_set
, &oldset
);
286 for (i
= 0; i
< NUM_SIGNALS
; ++i
) {
287 while (loop
->signals_fired
[i
]) {
288 for (it
= loop
->signal_handlers
[i
];
289 it
; it
= g_slist_next(it
)) {
290 ObtMainLoopSignalHandlerType
*h
= it
->data
;
293 loop
->signals_fired
[i
]--;
296 loop
->signal_fired
= FALSE
;
298 sigprocmask(SIG_SETMASK
, &oldset
, NULL
);
299 } else if (loop
->display
&& XPending(loop
->display
)) {
301 XNextEvent(loop
->display
, &e
);
303 if (e
.type
== MappingNotify
)
304 XRefreshKeyboardMapping(&e
.xmapping
);
306 for (it
= loop
->x_handlers
; it
; it
= g_slist_next(it
)) {
307 ObtMainLoopXHandlerType
*h
= it
->data
;
308 h
->func(&e
, h
->data
);
310 } while (XPending(loop
->display
) && loop
->run
);
312 /* this only runs if there were no x events received */
314 timer_dispatch(loop
, (GTimeVal
**)&wait
);
316 selset
= loop
->fd_set
;
317 /* there is a small race condition here. if a signal occurs
318 between this if() and the select() then we will not process
319 the signal until 'wait' expires. possible solutions include
320 using GStaticMutex, and having the signal handler set 'wait'
322 if (!loop
->signal_fired
)
323 select(loop
->fd_max
+ 1, &selset
, NULL
, NULL
, wait
);
325 /* handle the X events with highest prioirity */
326 if (FD_ISSET(loop
->fd_x
, &selset
))
329 g_hash_table_foreach(loop
->fd_handlers
,
330 fd_handle_foreach
, &selset
);
334 loop
->running
= FALSE
;
337 void obt_main_loop_exit(ObtMainLoop
*loop
)
342 /*** XEVENT WATCHERS ***/
344 void obt_main_loop_x_add(ObtMainLoop
*loop
,
345 ObtMainLoopXHandler handler
,
347 GDestroyNotify notify
)
349 ObtMainLoopXHandlerType
*h
;
351 h
= g_slice_new(ObtMainLoopXHandlerType
);
357 if (!loop
->x_handlers
) {
358 g_assert(obt_display
); /* is the display open? */
360 loop
->display
= obt_display
;
361 loop
->fd_x
= ConnectionNumber(loop
->display
);
362 FD_SET(loop
->fd_x
, &loop
->fd_set
);
366 loop
->x_handlers
= g_slist_prepend(loop
->x_handlers
, h
);
369 void obt_main_loop_x_remove(ObtMainLoop
*loop
,
370 ObtMainLoopXHandler handler
)
374 for (it
= loop
->x_handlers
; it
; it
= next
) {
375 ObtMainLoopXHandlerType
*h
= it
->data
;
376 next
= g_slist_next(it
);
377 if (h
->func
== handler
) {
378 loop
->x_handlers
= g_slist_delete_link(loop
->x_handlers
, it
);
379 if (h
->destroy
) h
->destroy(h
->data
);
380 g_slice_free(ObtMainLoopXHandlerType
, h
);
384 if (!loop
->x_handlers
) {
385 FD_CLR(loop
->fd_x
, &loop
->fd_set
);
390 /*** SIGNAL WATCHERS ***/
392 static void sighandler(gint sig
)
397 g_return_if_fail(sig
< NUM_SIGNALS
);
399 for (i
= 0; i
< NUM_CORE_SIGNALS
; ++i
)
400 if (sig
== core_signals
[i
]) {
401 /* XXX special case for signals that default to core dump.
402 but throw some helpful output here... */
404 fprintf(stderr
, "How are you gentlemen? All your base are"
405 " belong to us. (Openbox received signal %d)\n", sig
);
407 /* die with a core dump */
411 for (it
= all_loops
; it
; it
= g_slist_next(it
)) {
412 ObtMainLoop
*loop
= it
->data
;
413 loop
->signal_fired
= TRUE
;
414 loop
->signals_fired
[sig
]++;
418 void obt_main_loop_signal_add(ObtMainLoop
*loop
,
420 ObtMainLoopSignalHandler handler
,
422 GDestroyNotify notify
)
424 ObtMainLoopSignalHandlerType
*h
;
426 g_return_if_fail(signal
< NUM_SIGNALS
);
428 h
= g_slice_new(ObtMainLoopSignalHandlerType
);
434 loop
->signal_handlers
[h
->signal
] =
435 g_slist_prepend(loop
->signal_handlers
[h
->signal
], h
);
437 if (!all_signals
[signal
].installed
) {
438 struct sigaction action
;
441 sigemptyset(&sigset
);
442 action
.sa_handler
= sighandler
;
443 action
.sa_mask
= sigset
;
444 action
.sa_flags
= SA_NOCLDSTOP
;
446 sigaction(signal
, &action
, &all_signals
[signal
].oldact
);
449 all_signals
[signal
].installed
++;
452 void obt_main_loop_signal_remove(ObtMainLoop
*loop
,
453 ObtMainLoopSignalHandler handler
)
458 for (i
= 0; i
< NUM_SIGNALS
; ++i
) {
459 for (it
= loop
->signal_handlers
[i
]; it
; it
= next
) {
460 ObtMainLoopSignalHandlerType
*h
= it
->data
;
462 next
= g_slist_next(it
);
464 if (h
->func
== handler
) {
465 g_assert(all_signals
[h
->signal
].installed
> 0);
467 all_signals
[h
->signal
].installed
--;
468 if (!all_signals
[h
->signal
].installed
) {
469 sigaction(h
->signal
, &all_signals
[h
->signal
].oldact
, NULL
);
472 loop
->signal_handlers
[i
] =
473 g_slist_delete_link(loop
->signal_handlers
[i
], it
);
474 if (h
->destroy
) h
->destroy(h
->data
);
476 g_slice_free(ObtMainLoopSignalHandlerType
, h
);
483 /*** FILE DESCRIPTOR WATCHERS ***/
485 static void max_fd_func(gpointer key
, gpointer value
, gpointer data
)
487 ObtMainLoop
*loop
= data
;
490 loop
->fd_max
= MAX(loop
->fd_max
, *(gint
*)key
);
493 static void calc_max_fd(ObtMainLoop
*loop
)
495 loop
->fd_max
= loop
->fd_x
;
497 g_hash_table_foreach(loop
->fd_handlers
, max_fd_func
, loop
);
500 void obt_main_loop_fd_add(ObtMainLoop
*loop
,
502 ObtMainLoopFdHandler handler
,
504 GDestroyNotify notify
)
506 ObtMainLoopFdHandlerType
*h
;
508 h
= g_slice_new(ObtMainLoopFdHandlerType
);
515 g_hash_table_replace(loop
->fd_handlers
, &h
->fd
, h
);
516 FD_SET(h
->fd
, &loop
->fd_set
);
520 static void fd_handler_destroy(gpointer data
)
522 ObtMainLoopFdHandlerType
*h
= data
;
524 FD_CLR(h
->fd
, &h
->loop
->fd_set
);
528 g_slice_free(ObtMainLoopFdHandlerType
, h
);
531 void obt_main_loop_fd_remove(ObtMainLoop
*loop
,
534 g_hash_table_remove(loop
->fd_handlers
, &fd
);
540 #define NEAREST_TIMEOUT(loop) \
541 (((ObtMainLoopTimer*)(loop)->timers->data)->timeout)
543 static glong
timecompare(GTimeVal
*a
, GTimeVal
*b
)
546 if ((r
= a
->tv_sec
- b
->tv_sec
)) return r
;
547 return a
->tv_usec
- b
->tv_usec
;
550 static void insert_timer(ObtMainLoop
*loop
, ObtMainLoopTimer
*ins
)
553 for (it
= loop
->timers
; it
; it
= g_slist_next(it
)) {
554 ObtMainLoopTimer
*t
= it
->data
;
555 if (timecompare(&ins
->timeout
, &t
->timeout
) <= 0) {
556 loop
->timers
= g_slist_insert_before(loop
->timers
, it
, ins
);
560 if (it
== NULL
) /* didnt fit anywhere in the list */
561 loop
->timers
= g_slist_append(loop
->timers
, ins
);
564 void obt_main_loop_timeout_add(ObtMainLoop
*loop
,
569 GDestroyNotify notify
)
571 ObtMainLoopTimer
*t
= g_slice_new(ObtMainLoopTimer
);
573 g_assert(microseconds
> 0); /* if it's 0 it'll cause an infinite loop */
575 t
->delay
= microseconds
;
581 g_get_current_time(&loop
->now
);
582 t
->last
= t
->timeout
= loop
->now
;
583 g_time_val_add(&t
->timeout
, t
->delay
);
585 insert_timer(loop
, t
);
588 void obt_main_loop_timeout_remove(ObtMainLoop
*loop
,
593 for (it
= loop
->timers
; it
; it
= g_slist_next(it
)) {
594 ObtMainLoopTimer
*t
= it
->data
;
595 if (t
->func
== handler
)
600 void obt_main_loop_timeout_remove_data(ObtMainLoop
*loop
, GSourceFunc handler
,
601 gpointer data
, gboolean cancel_dest
)
605 for (it
= loop
->timers
; it
; it
= g_slist_next(it
)) {
606 ObtMainLoopTimer
*t
= it
->data
;
607 if (t
->func
== handler
&& t
->equal(t
->data
, data
)) {
615 /* find the time to wait for the nearest timeout */
616 static gboolean
nearest_timeout_wait(ObtMainLoop
*loop
, GTimeVal
*tm
)
618 if (loop
->timers
== NULL
)
621 tm
->tv_sec
= NEAREST_TIMEOUT(loop
).tv_sec
- loop
->now
.tv_sec
;
622 tm
->tv_usec
= NEAREST_TIMEOUT(loop
).tv_usec
- loop
->now
.tv_usec
;
624 while (tm
->tv_usec
< 0) {
625 tm
->tv_usec
+= G_USEC_PER_SEC
;
628 tm
->tv_sec
+= tm
->tv_usec
/ G_USEC_PER_SEC
;
629 tm
->tv_usec
%= G_USEC_PER_SEC
;
636 static void timer_dispatch(ObtMainLoop
*loop
, GTimeVal
**wait
)
640 gboolean fired
= FALSE
;
642 g_get_current_time(&loop
->now
);
644 for (it
= loop
->timers
; it
; it
= next
) {
645 ObtMainLoopTimer
*curr
;
647 next
= g_slist_next(it
);
651 /* since timer_stop doesn't actually free the timer, we have to do our
652 real freeing in here.
656 loop
->timers
= g_slist_delete_link(loop
->timers
, it
);
658 curr
->destroy(curr
->data
);
659 g_slice_free(ObtMainLoopTimer
, curr
);
663 /* the queue is sorted, so if this timer shouldn't fire, none are
665 if (timecompare(&NEAREST_TIMEOUT(loop
), &loop
->now
) > 0)
668 /* we set the last fired time to delay msec after the previous firing,
669 then re-insert. timers maintain their order and may trigger more
670 than once if they've waited more than one delay's worth of time.
672 loop
->timers
= g_slist_delete_link(loop
->timers
, it
);
673 g_time_val_add(&curr
->last
, curr
->delay
);
674 if (curr
->func(curr
->data
)) {
675 g_time_val_add(&curr
->timeout
, curr
->delay
);
676 insert_timer(loop
, curr
);
679 curr
->destroy(curr
->data
);
680 g_slice_free(ObtMainLoopTimer
, curr
);
683 /* the timer queue has been shuffled, start from the beginning
684 (which is the next one to fire) */
691 /* if at least one timer fires, then don't wait on X events, as there
692 may already be some in the queue from the timer callbacks.
694 loop
->ret_wait
.tv_sec
= loop
->ret_wait
.tv_usec
= 0;
695 *wait
= &loop
->ret_wait
;
696 } else if (nearest_timeout_wait(loop
, &loop
->ret_wait
))
697 *wait
= &loop
->ret_wait
;