]> git.sur5r.net Git - i3/i3/blob - i3bar/src/ipc.c
Stop the reconn-timer before starting it again, else it's running twice
[i3/i3] / i3bar / src / ipc.c
1 /*
2  * i3bar - an xcb-based status- and ws-bar for i3
3  *
4  * © 2010 Axel Wagner and contributors
5  *
6  * See file LICNSE for license information
7  *
8  * src/ipc.c: Communicating with i3
9  *
10  */
11 #include <stdlib.h>
12 #include <stdio.h>
13 #include <unistd.h>
14 #include <stdint.h>
15 #include <errno.h>
16 #include <sys/socket.h>
17 #include <sys/un.h>
18 #include <i3/ipc.h>
19 #include <ev.h>
20
21 #include "common.h"
22
23 ev_io      *i3_connection;
24 ev_timer   *reconn = NULL;
25
26 const char *sock_path;
27
28 typedef void(*handler_t)(char*);
29
30 /*
31  * Retry to connect.
32  *
33  */
34 void retry_connection(struct ev_loop *loop, ev_timer *w, int events) {
35     static int retries = 8;
36     if (init_connection(sock_path) == 0) {
37         if (retries == 0) {
38             ELOG("Retried 8 times - connection failed!\n");
39             exit(EXIT_FAILURE);
40         }
41         retries--;
42         return;
43     }
44     retries = 8;
45     ev_timer_stop(loop, w);
46     subscribe_events();
47     reconfig_windows();
48 }
49
50 /*
51  * Schedule a reconnect
52  *
53  */
54 void reconnect() {
55     if (reconn == NULL) {
56         if ((reconn = malloc(sizeof(ev_timer))) == NULL) {
57             ELOG("malloc() failed: %s\n", strerror(errno));
58             exit(EXIT_FAILURE);
59         }
60     } else {
61         ev_timer_stop(main_loop, reconn);
62     }
63     ev_timer_init(reconn, retry_connection, 0.25, 0.25);
64     ev_timer_start(main_loop, reconn);
65 }
66
67 /*
68  * Called, when we get a reply to a command from i3.
69  * Since i3 does not give us much feedback on commands, we do not much
70  *
71  */
72 void got_command_reply(char *reply) {
73     /* TODO: Error handling for command-replies */
74 }
75
76 /*
77  * Called, when we get a reply with workspaces-data
78  *
79  */
80 void got_workspace_reply(char *reply) {
81     DLOG("Got Workspace-Data!\n");
82     parse_workspaces_json(reply);
83     draw_bars();
84 }
85
86 /*
87  * Called, when we get a reply for a subscription.
88  * Since i3 does not give us much feedback on commands, we do not much
89  *
90  */
91 void got_subscribe_reply(char *reply) {
92     DLOG("Got Subscribe Reply: %s\n", reply);
93     /* TODO: Error handling for subscribe-commands */
94 }
95
96 /*
97  * Called, when we get a reply with outputs-data
98  *
99  */
100 void got_output_reply(char *reply) {
101     DLOG("Parsing Outputs-JSON...\n");
102     parse_outputs_json(reply);
103     DLOG("Reconfiguring Windows...\n");
104     realloc_sl_buffer();
105     reconfig_windows();
106 }
107
108 /* Data-structure to easily call the reply-handlers later */
109 handler_t reply_handlers[] = {
110     &got_command_reply,
111     &got_workspace_reply,
112     &got_subscribe_reply,
113     &got_output_reply,
114 };
115
116 /*
117  * Called, when a workspace-event arrives (i.e. the user changed the workspace)
118  *
119  */
120 void got_workspace_event(char *event) {
121     DLOG("Got Workspace Event!\n");
122     i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
123 }
124
125 /*
126  * Called, when an output-event arrives (i.e. the screen-configuration changed)
127  *
128  */
129 void got_output_event(char *event) {
130     DLOG("Got Output Event!\n");
131     i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
132     if (!config.disable_ws) {
133         i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
134     }
135 }
136
137 /* Data-structure to easily call the reply-handlers later */
138 handler_t event_handlers[] = {
139     &got_workspace_event,
140     &got_output_event
141 };
142
143 /*
144  * Called, when we get a message from i3
145  *
146  */
147 void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
148     DLOG("Got data!\n");
149     int fd = watcher->fd;
150
151     /* First we only read the header, because we know its length */
152     uint32_t header_len = strlen(I3_IPC_MAGIC) + sizeof(uint32_t)*2;
153     char *header = malloc(header_len);
154     if (header == NULL) {
155         ELOG("Could not allocate memory: %s\n", strerror(errno));
156         exit(EXIT_FAILURE);
157     }
158
159     /* We first parse the fixed-length IPC-header, to know, how much data
160      * we have to expect */
161     uint32_t rec = 0;
162     while (rec < header_len) {
163         int n = read(fd, header + rec, header_len - rec);
164         if (n == -1) {
165             ELOG("read() failed: %s\n", strerror(errno));
166             exit(EXIT_FAILURE);
167         }
168         if (n == 0) {
169             /* EOF received. We try to recover a few times, because most likely
170              * i3 just restarted */
171             ELOG("EOF received, try to recover...\n");
172             destroy_connection();
173             reconnect();
174             return;
175         }
176         rec += n;
177     }
178
179     if (strncmp(header, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC))) {
180         ELOG("Wrong magic code: %.*s\n Expected: %s\n",
181              (int) strlen(I3_IPC_MAGIC),
182              header,
183              I3_IPC_MAGIC);
184         exit(EXIT_FAILURE);
185     }
186
187     char *walk = header + strlen(I3_IPC_MAGIC);
188     uint32_t size;
189     memcpy(&size, (uint32_t*)walk, sizeof(uint32_t));
190     walk += sizeof(uint32_t);
191     uint32_t type;
192     memcpy(&type, (uint32_t*)walk, sizeof(uint32_t));
193
194     /* Now that we know, what to expect, we can start read()ing the rest
195      * of the message */
196     char *buffer = malloc(size + 1);
197     if (buffer == NULL) {
198         /* EOF received. We try to recover a few times, because most likely
199          * i3 just restarted */
200         ELOG("EOF received, try to recover...\n");
201         destroy_connection();
202         reconnect();
203         return;
204     }
205     rec = 0;
206
207     while (rec < size) {
208         int n = read(fd, buffer + rec, size - rec);
209         if (n == -1) {
210             ELOG("read() failed: %s\n", strerror(errno));
211             exit(EXIT_FAILURE);
212         }
213         if (n == 0) {
214             ELOG("Nothing to read!\n");
215             exit(EXIT_FAILURE);
216         }
217         rec += n;
218     }
219     buffer[size] = '\0';
220
221     /* And call the callback (indexed by the type) */
222     if (type & (1 << 31)) {
223         type ^= 1 << 31;
224         event_handlers[type](buffer);
225     } else {
226         reply_handlers[type](buffer);
227     }
228
229     FREE(header);
230     FREE(buffer);
231 }
232
233 /*
234  * Sends a Message to i3.
235  * type must be a valid I3_IPC_MESSAGE_TYPE (see i3/ipc.h for further information)
236  *
237  */
238 int i3_send_msg(uint32_t type, const char *payload) {
239     uint32_t len = 0;
240     if (payload != NULL) {
241         len = strlen(payload);
242     }
243
244     /* We are a wellbehaved client and send a proper header first */
245     uint32_t to_write = strlen (I3_IPC_MAGIC) + sizeof(uint32_t)*2 + len;
246     /* TODO: I'm not entirely sure if this buffer really has to contain more
247      * than the pure header (why not just write() the payload from *payload?),
248      * but we leave it for now */
249     char *buffer = malloc(to_write);
250     if (buffer == NULL) {
251         ELOG("Could not allocate memory: %s\n", strerror(errno));
252         exit(EXIT_FAILURE);
253     }
254
255     char *walk = buffer;
256
257     strncpy(buffer, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC));
258     walk += strlen(I3_IPC_MAGIC);
259     memcpy(walk, &len, sizeof(uint32_t));
260     walk += sizeof(uint32_t);
261     memcpy(walk, &type, sizeof(uint32_t));
262     walk += sizeof(uint32_t);
263
264     if (payload != NULL)
265         strncpy(walk, payload, len);
266
267     uint32_t written = 0;
268
269     while (to_write > 0) {
270         int n = write(i3_connection->fd, buffer + written, to_write);
271         if (n == -1) {
272             ELOG("write() failed: %s\n", strerror(errno));
273             exit(EXIT_FAILURE);
274         }
275
276         to_write -= n;
277         written += n;
278     }
279
280     FREE(buffer);
281
282     return 1;
283 }
284
285 /*
286  * Initiate a connection to i3.
287  * socket-path must be a valid path to the ipc_socket of i3
288  *
289  */
290 int init_connection(const char *socket_path) {
291     sock_path = socket_path;
292     int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
293     if (sockfd == -1) {
294         ELOG("Could not create Socket: %s\n", strerror(errno));
295         exit(EXIT_FAILURE);
296     }
297
298     struct sockaddr_un addr;
299     memset(&addr, 0, sizeof(struct sockaddr_un));
300     addr.sun_family = AF_LOCAL;
301     strcpy(addr.sun_path, sock_path);
302     if (connect(sockfd, (const struct sockaddr*) &addr, sizeof(struct sockaddr_un)) < 0) {
303         ELOG("Could not connect to i3! %s: %s\n", sock_path, strerror(errno));
304         reconnect();
305         return 0;
306     }
307
308     i3_connection = malloc(sizeof(ev_io));
309     if (i3_connection == NULL) {
310         ELOG("malloc() failed: %s\n", strerror(errno));
311         exit(EXIT_FAILURE);
312     }
313     ev_io_init(i3_connection, &got_data, sockfd, EV_READ);
314     ev_io_start(main_loop, i3_connection);
315     return 1;
316 }
317
318 /*
319  * Destroy the connection to i3.
320  */
321 void destroy_connection() {
322     close(i3_connection->fd);
323     ev_io_stop(main_loop, i3_connection);
324 }
325
326 /*
327  * Subscribe to all the i3-events, we need
328  *
329  */
330 void subscribe_events() {
331     if (config.disable_ws) {
332         i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"output\" ]");
333     } else {
334         i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"workspace\", \"output\" ]");
335     }
336 }