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"
26 #include <sys/select.h>
29 typedef struct _ObtMainLoopTimer ObtMainLoopTimer
;
30 typedef struct _ObtMainLoopSignal ObtMainLoopSignal
;
31 typedef struct _ObtMainLoopSignalHandlerType ObtMainLoopSignalHandlerType
;
32 typedef struct _ObtMainLoopXHandlerType ObtMainLoopXHandlerType
;
33 typedef struct _ObtMainLoopFdHandlerType ObtMainLoopFdHandlerType
;
35 /* this should be more than the number of possible signals on any
37 #define NUM_SIGNALS 99
39 /* all created ObtMainLoops. Used by the signal handler to pass along
41 static GSList
*all_loops
;
43 /* signals are global to all loops */
45 guint installed
; /* a ref count */
46 struct sigaction oldact
;
47 } all_signals
[NUM_SIGNALS
];
49 /* a set of all possible signals */
50 static sigset_t all_signals_set
;
52 /* signals which cause a core dump, these can't be used for callbacks */
53 static gint core_signals
[] =
66 #define NUM_CORE_SIGNALS (sizeof(core_signals) / sizeof(core_signals[0]))
68 static void sighandler(gint sig
);
69 static void timer_dispatch(ObtMainLoop
*loop
, GTimeVal
**wait
);
70 static void fd_handler_destroy(gpointer data
);
71 static void calc_max_fd(ObtMainLoop
*loop
);
78 gboolean run
; /* do keep running */
79 gboolean running
; /* is still running */
83 gint fd_x
; /* The X fd is a special case! */
85 GHashTable
*fd_handlers
;
92 gboolean signal_fired
;
93 guint signals_fired
[NUM_SIGNALS
];
94 GSList
*signal_handlers
[NUM_SIGNALS
];
97 struct _ObtMainLoopTimer
103 GDestroyNotify destroy
;
105 /* The timer needs to be freed */
107 /* The time the last fire should've been at */
109 /* When this timer will next trigger */
112 /* Only allow a timer's function to fire once per run through the list,
113 so that it doesn't get locked in there forever */
117 struct _ObtMainLoopSignalHandlerType
122 ObtMainLoopSignalHandler func
;
123 GDestroyNotify destroy
;
126 struct _ObtMainLoopXHandlerType
130 ObtMainLoopXHandler func
;
131 GDestroyNotify destroy
;
134 struct _ObtMainLoopFdHandlerType
139 ObtMainLoopFdHandler func
;
140 GDestroyNotify destroy
;
143 ObtMainLoop
*obt_main_loop_new()
147 loop
= g_new0(ObtMainLoop
, 1);
149 FD_ZERO(&loop
->fd_set
);
153 loop
->fd_handlers
= g_hash_table_new_full(g_int_hash
, g_int_equal
,
154 NULL
, fd_handler_destroy
);
156 g_get_current_time(&loop
->now
);
158 /* only do this if we're the first loop created */
161 struct sigaction action
;
164 /* initialize the all_signals_set */
165 sigfillset(&all_signals_set
);
167 sigemptyset(&sigset
);
168 action
.sa_handler
= sighandler
;
169 action
.sa_mask
= sigset
;
170 action
.sa_flags
= SA_NOCLDSTOP
;
172 /* grab all the signals that cause core dumps */
173 for (i
= 0; i
< NUM_CORE_SIGNALS
; ++i
) {
174 /* SIGABRT is curiously not grabbed here!! that's because when we
175 get one of the core_signals, we use abort() to dump the core.
176 And having the abort() only go back to our signal handler again
177 is less than optimal */
178 if (core_signals
[i
] != SIGABRT
) {
179 sigaction(core_signals
[i
], &action
,
180 &all_signals
[core_signals
[i
]].oldact
);
181 all_signals
[core_signals
[i
]].installed
++;
186 all_loops
= g_slist_prepend(all_loops
, loop
);
191 void obt_main_loop_ref(ObtMainLoop
*loop
)
196 void obt_main_loop_unref(ObtMainLoop
*loop
)
201 if (loop
&& --loop
->ref
== 0) {
202 g_assert(loop
->running
== FALSE
);
204 for (it
= loop
->x_handlers
; it
; it
= next
) {
205 ObtMainLoopXHandlerType
*h
= it
->data
;
206 next
= g_slist_next(it
);
207 obt_main_loop_x_remove(loop
, h
->func
);
210 g_hash_table_destroy(loop
->fd_handlers
);
212 for (it
= loop
->timers
; it
; it
= g_slist_next(it
)) {
213 ObtMainLoopTimer
*t
= it
->data
;
214 if (t
->destroy
) t
->destroy(t
->data
);
217 g_slist_free(loop
->timers
);
220 for (i
= 0; i
< NUM_SIGNALS
; ++i
)
221 for (it
= loop
->signal_handlers
[i
]; it
; it
= next
) {
222 ObtMainLoopSignalHandlerType
*h
= it
->data
;
223 next
= g_slist_next(it
);
224 obt_main_loop_signal_remove(loop
, h
->func
);
227 all_loops
= g_slist_remove(all_loops
, loop
);
229 /* only do this if we're the last loop destroyed */
231 /* grab all the signals that cause core dumps */
232 for (i
= 0; i
< NUM_CORE_SIGNALS
; ++i
) {
233 if (all_signals
[core_signals
[i
]].installed
) {
234 sigaction(core_signals
[i
],
235 &all_signals
[core_signals
[i
]].oldact
, NULL
);
236 all_signals
[core_signals
[i
]].installed
--;
241 obt_free0(loop
, ObtMainLoop
, 1);
245 static void fd_handle_foreach(gpointer key
,
249 ObtMainLoopFdHandlerType
*h
= value
;
252 if (FD_ISSET(h
->fd
, set
))
253 h
->func(h
->fd
, h
->data
);
256 void obt_main_loop_run(ObtMainLoop
*loop
)
259 struct timeval
*wait
;
264 loop
->running
= TRUE
;
267 if (loop
->signal_fired
) {
271 /* block signals so that we can do this without the data changing
273 sigprocmask(SIG_SETMASK
, &all_signals_set
, &oldset
);
275 for (i
= 0; i
< NUM_SIGNALS
; ++i
) {
276 while (loop
->signals_fired
[i
]) {
277 for (it
= loop
->signal_handlers
[i
];
278 it
; it
= g_slist_next(it
)) {
279 ObtMainLoopSignalHandlerType
*h
= it
->data
;
282 loop
->signals_fired
[i
]--;
285 loop
->signal_fired
= FALSE
;
287 sigprocmask(SIG_SETMASK
, &oldset
, NULL
);
288 } else if (loop
->display
&& XPending(loop
->display
)) {
290 XNextEvent(loop
->display
, &e
);
292 for (it
= loop
->x_handlers
; it
; it
= g_slist_next(it
)) {
293 ObtMainLoopXHandlerType
*h
= it
->data
;
294 h
->func(&e
, h
->data
);
296 } while (XPending(loop
->display
) && loop
->run
);
298 /* this only runs if there were no x events received */
300 timer_dispatch(loop
, (GTimeVal
**)&wait
);
302 selset
= loop
->fd_set
;
303 /* there is a small race condition here. if a signal occurs
304 between this if() and the select() then we will not process
305 the signal until 'wait' expires. possible solutions include
306 using GStaticMutex, and having the signal handler set 'wait'
308 if (!loop
->signal_fired
)
309 select(loop
->fd_max
+ 1, &selset
, NULL
, NULL
, wait
);
311 /* handle the X events with highest prioirity */
312 if (FD_ISSET(loop
->fd_x
, &selset
))
315 g_hash_table_foreach(loop
->fd_handlers
,
316 fd_handle_foreach
, &selset
);
320 loop
->running
= FALSE
;
323 void obt_main_loop_exit(ObtMainLoop
*loop
)
328 /*** XEVENT WATCHERS ***/
330 void obt_main_loop_x_add(ObtMainLoop
*loop
,
331 ObtMainLoopXHandler handler
,
333 GDestroyNotify notify
)
335 ObtMainLoopXHandlerType
*h
;
337 h
= g_new(ObtMainLoopXHandlerType
, 1);
343 if (!loop
->x_handlers
) {
344 g_assert(obt_display
); /* is the display open? */
346 loop
->display
= obt_display
;
347 loop
->fd_x
= ConnectionNumber(loop
->display
);
348 FD_SET(loop
->fd_x
, &loop
->fd_set
);
352 loop
->x_handlers
= g_slist_prepend(loop
->x_handlers
, h
);
355 void obt_main_loop_x_remove(ObtMainLoop
*loop
,
356 ObtMainLoopXHandler handler
)
360 for (it
= loop
->x_handlers
; it
; it
= next
) {
361 ObtMainLoopXHandlerType
*h
= it
->data
;
362 next
= g_slist_next(it
);
363 if (h
->func
== handler
) {
364 loop
->x_handlers
= g_slist_delete_link(loop
->x_handlers
, it
);
365 if (h
->destroy
) h
->destroy(h
->data
);
370 if (!loop
->x_handlers
) {
371 FD_CLR(loop
->fd_x
, &loop
->fd_set
);
376 /*** SIGNAL WATCHERS ***/
378 static void sighandler(gint sig
)
383 g_return_if_fail(sig
< NUM_SIGNALS
);
385 for (i
= 0; i
< NUM_CORE_SIGNALS
; ++i
)
386 if (sig
== core_signals
[i
]) {
387 /* XXX special case for signals that default to core dump.
388 but throw some helpful output here... */
390 fprintf(stderr
, "How are you gentlemen? All your base are"
391 " belong to us. (Openbox received signal %d)\n", sig
);
393 /* die with a core dump */
397 for (it
= all_loops
; it
; it
= g_slist_next(it
)) {
398 ObtMainLoop
*loop
= it
->data
;
399 loop
->signal_fired
= TRUE
;
400 loop
->signals_fired
[sig
]++;
404 void obt_main_loop_signal_add(ObtMainLoop
*loop
,
406 ObtMainLoopSignalHandler handler
,
408 GDestroyNotify notify
)
410 ObtMainLoopSignalHandlerType
*h
;
412 g_return_if_fail(signal
< NUM_SIGNALS
);
414 h
= g_new(ObtMainLoopSignalHandlerType
, 1);
420 loop
->signal_handlers
[h
->signal
] =
421 g_slist_prepend(loop
->signal_handlers
[h
->signal
], h
);
423 if (!all_signals
[signal
].installed
) {
424 struct sigaction action
;
427 sigemptyset(&sigset
);
428 action
.sa_handler
= sighandler
;
429 action
.sa_mask
= sigset
;
430 action
.sa_flags
= SA_NOCLDSTOP
;
432 sigaction(signal
, &action
, &all_signals
[signal
].oldact
);
435 all_signals
[signal
].installed
++;
438 void obt_main_loop_signal_remove(ObtMainLoop
*loop
,
439 ObtMainLoopSignalHandler handler
)
444 for (i
= 0; i
< NUM_SIGNALS
; ++i
) {
445 for (it
= loop
->signal_handlers
[i
]; it
; it
= next
) {
446 ObtMainLoopSignalHandlerType
*h
= it
->data
;
448 next
= g_slist_next(it
);
450 if (h
->func
== handler
) {
451 g_assert(all_signals
[h
->signal
].installed
> 0);
453 all_signals
[h
->signal
].installed
--;
454 if (!all_signals
[h
->signal
].installed
) {
455 sigaction(h
->signal
, &all_signals
[h
->signal
].oldact
, NULL
);
458 loop
->signal_handlers
[i
] =
459 g_slist_delete_link(loop
->signal_handlers
[i
], it
);
460 if (h
->destroy
) h
->destroy(h
->data
);
469 /*** FILE DESCRIPTOR WATCHERS ***/
471 static void max_fd_func(gpointer key
, gpointer value
, gpointer data
)
473 ObtMainLoop
*loop
= data
;
476 loop
->fd_max
= MAX(loop
->fd_max
, *(gint
*)key
);
479 static void calc_max_fd(ObtMainLoop
*loop
)
481 loop
->fd_max
= loop
->fd_x
;
483 g_hash_table_foreach(loop
->fd_handlers
, max_fd_func
, loop
);
486 void obt_main_loop_fd_add(ObtMainLoop
*loop
,
488 ObtMainLoopFdHandler handler
,
490 GDestroyNotify notify
)
492 ObtMainLoopFdHandlerType
*h
;
494 h
= g_new(ObtMainLoopFdHandlerType
, 1);
501 g_hash_table_replace(loop
->fd_handlers
, &h
->fd
, h
);
502 FD_SET(h
->fd
, &loop
->fd_set
);
506 static void fd_handler_destroy(gpointer data
)
508 ObtMainLoopFdHandlerType
*h
= data
;
510 FD_CLR(h
->fd
, &h
->loop
->fd_set
);
516 void obt_main_loop_fd_remove(ObtMainLoop
*loop
,
519 g_hash_table_remove(loop
->fd_handlers
, &fd
);
525 #define NEAREST_TIMEOUT(loop) \
526 (((ObtMainLoopTimer*)(loop)->timers->data)->timeout)
528 static glong
timecompare(GTimeVal
*a
, GTimeVal
*b
)
531 if ((r
= a
->tv_sec
- b
->tv_sec
)) return r
;
532 return a
->tv_usec
- b
->tv_usec
;
535 static void insert_timer(ObtMainLoop
*loop
, ObtMainLoopTimer
*ins
)
538 for (it
= loop
->timers
; it
; it
= g_slist_next(it
)) {
539 ObtMainLoopTimer
*t
= it
->data
;
540 if (timecompare(&ins
->timeout
, &t
->timeout
) <= 0) {
541 loop
->timers
= g_slist_insert_before(loop
->timers
, it
, ins
);
545 if (it
== NULL
) /* didnt fit anywhere in the list */
546 loop
->timers
= g_slist_append(loop
->timers
, ins
);
549 void obt_main_loop_timeout_add(ObtMainLoop
*loop
,
554 GDestroyNotify notify
)
556 ObtMainLoopTimer
*t
= g_new(ObtMainLoopTimer
, 1);
558 g_assert(microseconds
> 0); /* if it's 0 it'll cause an infinite loop */
560 t
->delay
= microseconds
;
566 g_get_current_time(&loop
->now
);
567 t
->last
= t
->timeout
= loop
->now
;
568 g_time_val_add(&t
->timeout
, t
->delay
);
570 insert_timer(loop
, t
);
573 void obt_main_loop_timeout_remove(ObtMainLoop
*loop
,
578 for (it
= loop
->timers
; it
; it
= g_slist_next(it
)) {
579 ObtMainLoopTimer
*t
= it
->data
;
580 if (t
->func
== handler
)
585 void obt_main_loop_timeout_remove_data(ObtMainLoop
*loop
, GSourceFunc handler
,
586 gpointer data
, gboolean cancel_dest
)
590 for (it
= loop
->timers
; it
; it
= g_slist_next(it
)) {
591 ObtMainLoopTimer
*t
= it
->data
;
592 if (t
->func
== handler
&& t
->equal(t
->data
, data
)) {
600 /* find the time to wait for the nearest timeout */
601 static gboolean
nearest_timeout_wait(ObtMainLoop
*loop
, GTimeVal
*tm
)
603 if (loop
->timers
== NULL
)
606 tm
->tv_sec
= NEAREST_TIMEOUT(loop
).tv_sec
- loop
->now
.tv_sec
;
607 tm
->tv_usec
= NEAREST_TIMEOUT(loop
).tv_usec
- loop
->now
.tv_usec
;
609 while (tm
->tv_usec
< 0) {
610 tm
->tv_usec
+= G_USEC_PER_SEC
;
613 tm
->tv_sec
+= tm
->tv_usec
/ G_USEC_PER_SEC
;
614 tm
->tv_usec
%= G_USEC_PER_SEC
;
621 static void timer_dispatch(ObtMainLoop
*loop
, GTimeVal
**wait
)
625 gboolean fired
= FALSE
;
627 g_get_current_time(&loop
->now
);
629 for (it
= loop
->timers
; it
; it
= next
) {
630 ObtMainLoopTimer
*curr
;
632 next
= g_slist_next(it
);
636 /* since timer_stop doesn't actually free the timer, we have to do our
637 real freeing in here.
641 loop
->timers
= g_slist_delete_link(loop
->timers
, it
);
643 curr
->destroy(curr
->data
);
648 /* the queue is sorted, so if this timer shouldn't fire, none are
650 if (timecompare(&NEAREST_TIMEOUT(loop
), &loop
->now
) > 0)
653 /* we set the last fired time to delay msec after the previous firing,
654 then re-insert. timers maintain their order and may trigger more
655 than once if they've waited more than one delay's worth of time.
657 loop
->timers
= g_slist_delete_link(loop
->timers
, it
);
658 g_time_val_add(&curr
->last
, curr
->delay
);
659 if (curr
->func(curr
->data
)) {
660 g_time_val_add(&curr
->timeout
, curr
->delay
);
661 insert_timer(loop
, curr
);
664 curr
->destroy(curr
->data
);
668 /* the timer queue has been shuffled, start from the beginning
669 (which is the next one to fire) */
676 /* if at least one timer fires, then don't wait on X events, as there
677 may already be some in the queue from the timer callbacks.
679 loop
->ret_wait
.tv_sec
= loop
->ret_wait
.tv_usec
= 0;
680 *wait
= &loop
->ret_wait
;
681 } else if (nearest_timeout_wait(loop
, &loop
->ret_wait
))
682 *wait
= &loop
->ret_wait
;