]> 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 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 <sys/socket.h>
16 #include <sys/un.h>
17 #include <i3/ipc.h>
18 #include <ev.h>
19
20 #include "common.h"
21
22 ev_io *i3_connection;
23
24 typedef void(*handler_t)(char*);
25
26 /*
27  * Get a connect to the IPC-interface of i3 and return a filedescriptor
28  *
29  */
30 int get_ipc_fd(const char *socket_path) {
31     int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
32     if (sockfd == -1) {
33         ELOG("Could not create Socket!\n");
34         exit(EXIT_FAILURE);
35     }
36
37     struct sockaddr_un addr;
38     memset(&addr, 0, sizeof(struct sockaddr_un));
39     addr.sun_family = AF_LOCAL;
40     strcpy(addr.sun_path, socket_path);
41     if (connect(sockfd, (const struct sockaddr*) &addr, sizeof(struct sockaddr_un)) < 0) {
42         ELOG("Could not connct to i3!\n");
43         exit(EXIT_FAILURE);
44     }
45     return sockfd;
46 }
47
48 /*
49  * Called, when we get a reply to a command from i3.
50  * Since i3 does not give us much feedback on commands, we do not much
51  *
52  */
53 void got_command_reply(char *reply) {
54     /* TODO: Error handling for command-replies */
55 }
56
57 /*
58  * Called, when we get a reply with workspaces-data
59  *
60  */
61 void got_workspace_reply(char *reply) {
62     DLOG("Got Workspace-Data!\n");
63     parse_workspaces_json(reply);
64     draw_bars();
65 }
66
67 /*
68  * Called, when we get a reply for a subscription.
69  * Since i3 does not give us much feedback on commands, we do not much
70  *
71  */
72 void got_subscribe_reply(char *reply) {
73     DLOG("Got Subscribe Reply: %s\n", reply);
74     /* TODO: Error handling for subscribe-commands */
75 }
76
77 /*
78  * Called, when we get a reply with outputs-data
79  *
80  */
81 void got_output_reply(char *reply) {
82     DLOG("Parsing Outputs-JSON...\n");
83     parse_outputs_json(reply);
84     DLOG("Reconfiguring Windows...\n");
85     reconfig_windows();
86 }
87
88 /* Data-structure to easily call the reply-handlers later */
89 handler_t reply_handlers[] = {
90     &got_command_reply,
91     &got_workspace_reply,
92     &got_subscribe_reply,
93     &got_output_reply,
94 };
95
96 /*
97  * Called, when a workspace-event arrives (i.e. the user changed the workspace)
98  *
99  */
100 void got_workspace_event(char *event) {
101     DLOG("Got Workspace Event!\n");
102     i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
103 }
104
105 /*
106  * Called, when an output-event arrives (i.e. the screen-configuration changed)
107  *
108  */
109 void got_output_event(char *event) {
110     DLOG("Got Output Event!\n");
111     i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
112     i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
113 }
114
115 /* Data-structure to easily call the reply-handlers later */
116 handler_t event_handlers[] = {
117     &got_workspace_event,
118     &got_output_event
119 };
120
121 /*
122  * Called, when we get a message from i3
123  *
124  */
125 void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
126     DLOG("Got data!\n");
127     int fd = watcher->fd;
128
129     /* First we only read the header, because we know it's length */
130     uint32_t header_len = strlen(I3_IPC_MAGIC) + sizeof(uint32_t)*2;
131     char *header = malloc(header_len);
132     if (header == NULL) {
133         ELOG("Could not allocate memory!\n");
134         exit(EXIT_FAILURE);
135     }
136
137     /* We first parse the fixed-length IPC-header, to know, how much data
138      * we have to expect */
139     uint32_t rec = 0;
140     while (rec < header_len) {
141         int n = read(fd, header + rec, header_len - rec);
142         if (n == -1) {
143             ELOG("read() failed!\n");
144             exit(EXIT_FAILURE);
145         }
146         if (n == 0) {
147             ELOG("Nothing to read!\n");
148             exit(EXIT_FAILURE);
149         }
150         rec += n;
151     }
152
153     if (strncmp(header, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC))) {
154         ELOG("Wrong magic code: %.*s\n Expected: %s\n",
155              (int) strlen(I3_IPC_MAGIC),
156              header,
157              I3_IPC_MAGIC);
158         exit(EXIT_FAILURE);
159     }
160
161     char *walk = header + strlen(I3_IPC_MAGIC);
162     uint32_t size = *((uint32_t*) walk);
163     walk += sizeof(uint32_t);
164     uint32_t type = *((uint32_t*) walk);
165
166     /* Now that we know, what to expect, we can start read()ing the rest
167      * of the message */
168     char *buffer = malloc(size + 1);
169     if (buffer == NULL) {
170         ELOG("Could not allocate memory!\n");
171         exit(EXIT_FAILURE);
172     }
173     rec = 0;
174
175     while (rec < size) {
176         int n = read(fd, buffer + rec, size - rec);
177         if (n == -1) {
178             ELOG("read() failed!\n");
179             exit(EXIT_FAILURE);
180         }
181         if (n == 0) {
182             ELOG("Nothing to read!\n");
183             exit(EXIT_FAILURE);
184         }
185         rec += n;
186     }
187     buffer[size] = '\0';
188
189     /* And call the callback (indexed by the type) */
190     if (type & (1 << 31)) {
191         type ^= 1 << 31;
192         event_handlers[type](buffer);
193     } else {
194         reply_handlers[type](buffer);
195     }
196
197     FREE(header);
198     FREE(buffer);
199 }
200
201 /*
202  * Sends a Message to i3.
203  * type must be a valid I3_IPC_MESSAGE_TYPE (see i3/ipc.h for further information)
204  *
205  */
206 int i3_send_msg(uint32_t type, const char *payload) {
207     uint32_t len = 0;
208     if (payload != NULL) {
209         len = strlen(payload);
210     }
211
212     /* We are a wellbehaved client and send a proper header first */
213     uint32_t to_write = strlen (I3_IPC_MAGIC) + sizeof(uint32_t)*2 + len;
214     /* TODO: I'm not entirely sure if this buffer really has to contain more
215      * than the pure header (why not just write() the payload from *payload?),
216      * but we leave it for now */
217     char *buffer = malloc(to_write);
218     if (buffer == NULL) {
219         ELOG("Could not allocate memory\n");
220         exit(EXIT_FAILURE);
221     }
222
223     char *walk = buffer;
224
225     strncpy(buffer, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC));
226     walk += strlen(I3_IPC_MAGIC);
227     memcpy(walk, &len, sizeof(uint32_t));
228     walk += sizeof(uint32_t);
229     memcpy(walk, &type, sizeof(uint32_t));
230     walk += sizeof(uint32_t);
231
232     strncpy(walk, payload, len);
233
234     uint32_t written = 0;
235
236     while (to_write > 0) {
237         int n = write(i3_connection->fd, buffer + written, to_write);
238         if (n == -1) {
239             ELOG("write() failed!\n");
240             exit(EXIT_FAILURE);
241         }
242
243         to_write -= n;
244         written += n;
245     }
246
247     FREE(buffer);
248
249     return 1;
250 }
251
252 /*
253  * Initiate a connection to i3.
254  * socket-path must be a valid path to the ipc_socket of i3
255  *
256  */
257 int init_connection(const char *socket_path) {
258     int sockfd = get_ipc_fd(socket_path);
259
260     i3_connection = malloc(sizeof(ev_io));
261     ev_io_init(i3_connection, &got_data, sockfd, EV_READ);
262     ev_io_start(main_loop, i3_connection);
263
264     return 1;
265 }
266
267 /*
268  * Subscribe to all the i3-events, we need
269  *
270  */
271 void subscribe_events() {
272     i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"workspace\", \"output\" ]");
273 }