make the obt library branch compile again with all the changes merged in from backport
[dana/openbox.git] / openbox / ping.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    client.h for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2008   Dana Jansens
6
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.
11
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.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "ping.h"
21 #include "client.h"
22 #include "event.h"
23 #include "debug.h"
24 #include "openbox.h"
25 #include "obt/mainloop.h"
26 #include "obt/prop.h"
27
28 typedef struct _ObPingTarget
29 {
30     ObClient *client;
31     ObPingEventHandler h;
32     guint32 id;
33     gint waiting;
34 } ObPingTarget;
35
36 static GHashTable *ping_ids     = NULL;
37 static guint32     ping_next_id = 1;
38
39 #define PING_TIMEOUT (G_USEC_PER_SEC * 3)
40 /*! Warn the user after this many PING_TIMEOUT intervals */
41 #define PING_TIMEOUT_WARN 3
42
43 static void     ping_send(ObPingTarget *t);
44 static void     ping_end(ObClient *client, gpointer data);
45 static gboolean ping_timeout(gpointer data);
46 static gboolean find_client(gpointer key, gpointer value, gpointer client);
47
48 void ping_startup(gboolean reconfigure)
49 {
50     if (reconfigure) return;
51
52     ping_ids = g_hash_table_new(g_int_hash, g_int_equal);
53
54     /* listen for clients to disappear */
55     client_add_destroy_notify(ping_end, NULL);
56 }
57
58 void ping_shutdown(gboolean reconfigure)
59 {
60     if (reconfigure) return;
61
62     g_hash_table_unref(ping_ids);
63     ping_ids = NULL;
64
65     client_remove_destroy_notify(ping_end);
66 }
67
68 void ping_start(struct _ObClient *client, ObPingEventHandler h)
69 {
70     ObPingTarget *t;
71
72     /* make sure we're not already pinging the client */
73     g_assert(g_hash_table_find(ping_ids, find_client, client) == NULL);
74
75     g_assert(client->ping == TRUE);
76
77     t = g_new0(ObPingTarget, 1);
78     t->client = client;
79     t->h = h;
80
81     obt_main_loop_timeout_add(ob_main_loop, PING_TIMEOUT, ping_timeout,
82                               t, g_direct_equal, NULL);
83     /* act like we just timed out immediately, to start the pinging process
84        now instead of after the first delay.  this makes sure the client
85        ends up in the ping_ids hash table now. */
86     ping_timeout(t);
87
88     /* make sure we can remove the client later */
89     g_assert(g_hash_table_find(ping_ids, find_client, client) != NULL);
90 }
91
92 void ping_stop(struct _ObClient *c)
93 {
94     ping_end(c, NULL);
95 }
96
97 void ping_got_pong(guint32 id)
98 {
99     ObPingTarget *t;
100
101     if ((t = g_hash_table_lookup(ping_ids, &id))) {
102         /*ob_debug("-PONG: '%s' (id %u)\n", t->client->title, t->id);*/
103         if (t->waiting > PING_TIMEOUT_WARN) {
104             /* we had notified that they weren't responding, so now we
105                need to notify that they are again */
106             t->h(t->client, FALSE);
107         }
108         t->waiting = 0; /* not waiting for a reply anymore */
109     }
110     else
111         ob_debug("Got PONG with id %u but not waiting for one\n", id);
112 }
113
114 static gboolean find_client(gpointer key, gpointer value, gpointer client)
115 {
116     ObPingTarget *t = value;
117     return t->client == client;
118 }
119
120 static void ping_send(ObPingTarget *t)
121 {
122     /* t->id is 0 when it hasn't been assigned an id ever yet.
123        we can reuse ids when t->waiting == 0, because we won't be getting a
124        pong for that id in the future again.  that way for apps that aren't
125        timing out we don't need to remove/add them from/to the hash table */
126     if (t->id == 0 || t->waiting > 0) {
127         /* pick an id, and reinsert in the hash table with the new id */
128         if (t->id) g_hash_table_remove(ping_ids, &t->id);
129         t->id = ping_next_id;
130         if (++ping_next_id == 0) ++ping_next_id; /* skip 0 on wraparound */
131         g_hash_table_insert(ping_ids, &t->id, t);
132     }
133
134     /*ob_debug("+PING: '%s' (id %u)\n", t->client->title, t->id);*/
135     OBT_PROP_MSG_TO(t->client->window, t->client->window, WM_PROTOCOLS,
136                     OBT_PROP_ATOM(NET_WM_PING), t->id, t->client->window, 0, 0,
137                     NoEventMask);
138 }
139
140 static gboolean ping_timeout(gpointer data)
141 {
142     ObPingTarget *t = data;
143
144     ping_send(t);
145
146     /* if the client hasn't been responding then do something about it */
147     if (t->waiting == PING_TIMEOUT_WARN)
148         t->h(t->client, TRUE); /* notify that the client isn't responding */
149
150     ++t->waiting;
151
152     return TRUE; /* repeat */
153 }
154
155 static void ping_end(ObClient *client, gpointer data)
156 {
157     ObPingTarget *t;
158
159     if ((t = g_hash_table_find(ping_ids, find_client, client))) {
160         g_hash_table_remove(ping_ids, &t->id);
161
162         obt_main_loop_timeout_remove_data(ob_main_loop, ping_timeout,
163                                           t, FALSE);
164
165         g_free(t);
166     }
167 }