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