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
);
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 for (it
= loop
->x_handlers
; it
; it
= g_slist_next(it
)) {
304 ObtMainLoopXHandlerType
*h
= it
->data
;
305 h
->func(&e
, h
->data
);
307 } while (XPending(loop
->display
) && loop
->run
);
309 /* this only runs if there were no x events received */
311 timer_dispatch(loop
, (GTimeVal
**)&wait
);
313 selset
= loop
->fd_set
;
314 /* there is a small race condition here. if a signal occurs
315 between this if() and the select() then we will not process
316 the signal until 'wait' expires. possible solutions include
317 using GStaticMutex, and having the signal handler set 'wait'
319 if (!loop
->signal_fired
)
320 select(loop
->fd_max
+ 1, &selset
, NULL
, NULL
, wait
);
322 /* handle the X events with highest prioirity */
323 if (FD_ISSET(loop
->fd_x
, &selset
))
326 g_hash_table_foreach(loop
->fd_handlers
,
327 fd_handle_foreach
, &selset
);
331 loop
->running
= FALSE
;
334 void obt_main_loop_exit(ObtMainLoop
*loop
)
339 /*** XEVENT WATCHERS ***/
341 void obt_main_loop_x_add(ObtMainLoop
*loop
,
342 ObtMainLoopXHandler handler
,
344 GDestroyNotify notify
)
346 ObtMainLoopXHandlerType
*h
;
348 h
= g_new(ObtMainLoopXHandlerType
, 1);
354 if (!loop
->x_handlers
) {
355 g_assert(obt_display
); /* is the display open? */
357 loop
->display
= obt_display
;
358 loop
->fd_x
= ConnectionNumber(loop
->display
);
359 FD_SET(loop
->fd_x
, &loop
->fd_set
);
363 loop
->x_handlers
= g_slist_prepend(loop
->x_handlers
, h
);
366 void obt_main_loop_x_remove(ObtMainLoop
*loop
,
367 ObtMainLoopXHandler handler
)
371 for (it
= loop
->x_handlers
; it
; it
= next
) {
372 ObtMainLoopXHandlerType
*h
= it
->data
;
373 next
= g_slist_next(it
);
374 if (h
->func
== handler
) {
375 loop
->x_handlers
= g_slist_delete_link(loop
->x_handlers
, it
);
376 if (h
->destroy
) h
->destroy(h
->data
);
381 if (!loop
->x_handlers
) {
382 FD_CLR(loop
->fd_x
, &loop
->fd_set
);
387 /*** SIGNAL WATCHERS ***/
389 static void sighandler(gint sig
)
394 g_return_if_fail(sig
< NUM_SIGNALS
);
396 for (i
= 0; i
< NUM_CORE_SIGNALS
; ++i
)
397 if (sig
== core_signals
[i
]) {
398 /* XXX special case for signals that default to core dump.
399 but throw some helpful output here... */
401 fprintf(stderr
, "How are you gentlemen? All your base are"
402 " belong to us. (Openbox received signal %d)\n", sig
);
404 /* die with a core dump */
408 for (it
= all_loops
; it
; it
= g_slist_next(it
)) {
409 ObtMainLoop
*loop
= it
->data
;
410 loop
->signal_fired
= TRUE
;
411 loop
->signals_fired
[sig
]++;
415 void obt_main_loop_signal_add(ObtMainLoop
*loop
,
417 ObtMainLoopSignalHandler handler
,
419 GDestroyNotify notify
)
421 ObtMainLoopSignalHandlerType
*h
;
423 g_return_if_fail(signal
< NUM_SIGNALS
);
425 h
= g_new(ObtMainLoopSignalHandlerType
, 1);
431 loop
->signal_handlers
[h
->signal
] =
432 g_slist_prepend(loop
->signal_handlers
[h
->signal
], h
);
434 if (!all_signals
[signal
].installed
) {
435 struct sigaction action
;
438 sigemptyset(&sigset
);
439 action
.sa_handler
= sighandler
;
440 action
.sa_mask
= sigset
;
441 action
.sa_flags
= SA_NOCLDSTOP
;
443 sigaction(signal
, &action
, &all_signals
[signal
].oldact
);
446 all_signals
[signal
].installed
++;
449 void obt_main_loop_signal_remove(ObtMainLoop
*loop
,
450 ObtMainLoopSignalHandler handler
)
455 for (i
= 0; i
< NUM_SIGNALS
; ++i
) {
456 for (it
= loop
->signal_handlers
[i
]; it
; it
= next
) {
457 ObtMainLoopSignalHandlerType
*h
= it
->data
;
459 next
= g_slist_next(it
);
461 if (h
->func
== handler
) {
462 g_assert(all_signals
[h
->signal
].installed
> 0);
464 all_signals
[h
->signal
].installed
--;
465 if (!all_signals
[h
->signal
].installed
) {
466 sigaction(h
->signal
, &all_signals
[h
->signal
].oldact
, NULL
);
469 loop
->signal_handlers
[i
] =
470 g_slist_delete_link(loop
->signal_handlers
[i
], it
);
471 if (h
->destroy
) h
->destroy(h
->data
);
480 /*** FILE DESCRIPTOR WATCHERS ***/
482 static void max_fd_func(gpointer key
, gpointer value
, gpointer data
)
484 ObtMainLoop
*loop
= data
;
487 loop
->fd_max
= MAX(loop
->fd_max
, *(gint
*)key
);
490 static void calc_max_fd(ObtMainLoop
*loop
)
492 loop
->fd_max
= loop
->fd_x
;
494 g_hash_table_foreach(loop
->fd_handlers
, max_fd_func
, loop
);
497 void obt_main_loop_fd_add(ObtMainLoop
*loop
,
499 ObtMainLoopFdHandler handler
,
501 GDestroyNotify notify
)
503 ObtMainLoopFdHandlerType
*h
;
505 h
= g_new(ObtMainLoopFdHandlerType
, 1);
512 g_hash_table_replace(loop
->fd_handlers
, &h
->fd
, h
);
513 FD_SET(h
->fd
, &loop
->fd_set
);
517 static void fd_handler_destroy(gpointer data
)
519 ObtMainLoopFdHandlerType
*h
= data
;
521 FD_CLR(h
->fd
, &h
->loop
->fd_set
);
527 void obt_main_loop_fd_remove(ObtMainLoop
*loop
,
530 g_hash_table_remove(loop
->fd_handlers
, &fd
);
536 #define NEAREST_TIMEOUT(loop) \
537 (((ObtMainLoopTimer*)(loop)->timers->data)->timeout)
539 static glong
timecompare(GTimeVal
*a
, GTimeVal
*b
)
542 if ((r
= a
->tv_sec
- b
->tv_sec
)) return r
;
543 return a
->tv_usec
- b
->tv_usec
;
546 static void insert_timer(ObtMainLoop
*loop
, ObtMainLoopTimer
*ins
)
549 for (it
= loop
->timers
; it
; it
= g_slist_next(it
)) {
550 ObtMainLoopTimer
*t
= it
->data
;
551 if (timecompare(&ins
->timeout
, &t
->timeout
) <= 0) {
552 loop
->timers
= g_slist_insert_before(loop
->timers
, it
, ins
);
556 if (it
== NULL
) /* didnt fit anywhere in the list */
557 loop
->timers
= g_slist_append(loop
->timers
, ins
);
560 void obt_main_loop_timeout_add(ObtMainLoop
*loop
,
565 GDestroyNotify notify
)
567 ObtMainLoopTimer
*t
= g_new(ObtMainLoopTimer
, 1);
569 g_assert(microseconds
> 0); /* if it's 0 it'll cause an infinite loop */
571 t
->delay
= microseconds
;
577 g_get_current_time(&loop
->now
);
578 t
->last
= t
->timeout
= loop
->now
;
579 g_time_val_add(&t
->timeout
, t
->delay
);
581 insert_timer(loop
, t
);
584 void obt_main_loop_timeout_remove(ObtMainLoop
*loop
,
589 for (it
= loop
->timers
; it
; it
= g_slist_next(it
)) {
590 ObtMainLoopTimer
*t
= it
->data
;
591 if (t
->func
== handler
)
596 void obt_main_loop_timeout_remove_data(ObtMainLoop
*loop
, GSourceFunc handler
,
597 gpointer data
, gboolean cancel_dest
)
601 for (it
= loop
->timers
; it
; it
= g_slist_next(it
)) {
602 ObtMainLoopTimer
*t
= it
->data
;
603 if (t
->func
== handler
&& t
->equal(t
->data
, data
)) {
611 /* find the time to wait for the nearest timeout */
612 static gboolean
nearest_timeout_wait(ObtMainLoop
*loop
, GTimeVal
*tm
)
614 if (loop
->timers
== NULL
)
617 tm
->tv_sec
= NEAREST_TIMEOUT(loop
).tv_sec
- loop
->now
.tv_sec
;
618 tm
->tv_usec
= NEAREST_TIMEOUT(loop
).tv_usec
- loop
->now
.tv_usec
;
620 while (tm
->tv_usec
< 0) {
621 tm
->tv_usec
+= G_USEC_PER_SEC
;
624 tm
->tv_sec
+= tm
->tv_usec
/ G_USEC_PER_SEC
;
625 tm
->tv_usec
%= G_USEC_PER_SEC
;
632 static void timer_dispatch(ObtMainLoop
*loop
, GTimeVal
**wait
)
636 gboolean fired
= FALSE
;
638 g_get_current_time(&loop
->now
);
640 for (it
= loop
->timers
; it
; it
= next
) {
641 ObtMainLoopTimer
*curr
;
643 next
= g_slist_next(it
);
647 /* since timer_stop doesn't actually free the timer, we have to do our
648 real freeing in here.
652 loop
->timers
= g_slist_delete_link(loop
->timers
, it
);
654 curr
->destroy(curr
->data
);
659 /* the queue is sorted, so if this timer shouldn't fire, none are
661 if (timecompare(&NEAREST_TIMEOUT(loop
), &loop
->now
) > 0)
664 /* we set the last fired time to delay msec after the previous firing,
665 then re-insert. timers maintain their order and may trigger more
666 than once if they've waited more than one delay's worth of time.
668 loop
->timers
= g_slist_delete_link(loop
->timers
, it
);
669 g_time_val_add(&curr
->last
, curr
->delay
);
670 if (curr
->func(curr
->data
)) {
671 g_time_val_add(&curr
->timeout
, curr
->delay
);
672 insert_timer(loop
, curr
);
675 curr
->destroy(curr
->data
);
679 /* the timer queue has been shuffled, start from the beginning
680 (which is the next one to fire) */
687 /* if at least one timer fires, then don't wait on X events, as there
688 may already be some in the queue from the timer callbacks.
690 loop
->ret_wait
.tv_sec
= loop
->ret_wait
.tv_usec
= 0;
691 *wait
= &loop
->ret_wait
;
692 } else if (nearest_timeout_wait(loop
, &loop
->ret_wait
))
693 *wait
= &loop
->ret_wait
;