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