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