diff options
author | Drew DeVault <sir@cmpwn.com> | 2017-11-18 11:22:02 -0500 |
---|---|---|
committer | Drew DeVault <sir@cmpwn.com> | 2017-11-18 11:22:02 -0500 |
commit | 733993a651c71f7e2198d505960d6bbd31e0e107 (patch) | |
tree | e51732c5872b624e73355f9e5b3f762101f3cd0d /sway/ipc-server.c | |
parent | Initial (awful) pass on xdg shell support (diff) | |
download | sway-733993a651c71f7e2198d505960d6bbd31e0e107.tar.gz sway-733993a651c71f7e2198d505960d6bbd31e0e107.tar.zst sway-733993a651c71f7e2198d505960d6bbd31e0e107.zip |
Move everything to sway/old/
Diffstat (limited to 'sway/ipc-server.c')
-rw-r--r-- | sway/ipc-server.c | 845 |
1 files changed, 0 insertions, 845 deletions
diff --git a/sway/ipc-server.c b/sway/ipc-server.c deleted file mode 100644 index d4db4e7a..00000000 --- a/sway/ipc-server.c +++ /dev/null | |||
@@ -1,845 +0,0 @@ | |||
1 | // See https://i3wm.org/docs/ipc.html for protocol information | ||
2 | |||
3 | #ifndef __FreeBSD__ | ||
4 | // Any value will hide SOCK_CLOEXEC on FreeBSD (__BSD_VISIBLE=0) | ||
5 | #define _XOPEN_SOURCE 700 | ||
6 | #endif | ||
7 | |||
8 | #include <errno.h> | ||
9 | #include <string.h> | ||
10 | #include <sys/socket.h> | ||
11 | #include <sys/un.h> | ||
12 | #include <stdbool.h> | ||
13 | #include <unistd.h> | ||
14 | #include <stdlib.h> | ||
15 | #include <sys/ioctl.h> | ||
16 | #include <fcntl.h> | ||
17 | #include <json-c/json.h> | ||
18 | #include <list.h> | ||
19 | #include <libinput.h> | ||
20 | #ifdef __linux__ | ||
21 | struct ucred { | ||
22 | pid_t pid; | ||
23 | uid_t uid; | ||
24 | gid_t gid; | ||
25 | }; | ||
26 | #endif | ||
27 | #include "sway/ipc-json.h" | ||
28 | #include "sway/ipc-server.h" | ||
29 | #include "sway/security.h" | ||
30 | #include "sway/config.h" | ||
31 | #include "sway/commands.h" | ||
32 | #include "sway/input.h" | ||
33 | #include "sway/server.h" | ||
34 | #include "stringop.h" | ||
35 | #include "log.h" | ||
36 | #include "list.h" | ||
37 | #include "util.h" | ||
38 | |||
39 | static int ipc_socket = -1; | ||
40 | static struct wl_event_source *ipc_event_source = NULL; | ||
41 | static struct sockaddr_un *ipc_sockaddr = NULL; | ||
42 | static list_t *ipc_client_list = NULL; | ||
43 | |||
44 | static const char ipc_magic[] = {'i', '3', '-', 'i', 'p', 'c'}; | ||
45 | |||
46 | struct ipc_client { | ||
47 | struct wl_event_source *event_source; | ||
48 | struct wl_event_source *writable_event_source; | ||
49 | int fd; | ||
50 | uint32_t payload_length; | ||
51 | uint32_t security_policy; | ||
52 | enum ipc_command_type current_command; | ||
53 | enum ipc_command_type subscribed_events; | ||
54 | size_t write_buffer_len; | ||
55 | size_t write_buffer_size; | ||
56 | char *write_buffer; | ||
57 | }; | ||
58 | |||
59 | static list_t *ipc_get_pixel_requests = NULL; | ||
60 | |||
61 | struct sockaddr_un *ipc_user_sockaddr(void); | ||
62 | int ipc_handle_connection(int fd, uint32_t mask, void *data); | ||
63 | int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data); | ||
64 | int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data); | ||
65 | void ipc_client_disconnect(struct ipc_client *client); | ||
66 | void ipc_client_handle_command(struct ipc_client *client); | ||
67 | bool ipc_send_reply(struct ipc_client *client, const char *payload, uint32_t payload_length); | ||
68 | void ipc_get_workspaces_callback(swayc_t *workspace, void *data); | ||
69 | void ipc_get_outputs_callback(swayc_t *container, void *data); | ||
70 | static void ipc_get_marks_callback(swayc_t *container, void *data); | ||
71 | |||
72 | void ipc_init(void) { | ||
73 | ipc_socket = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); | ||
74 | if (ipc_socket == -1) { | ||
75 | sway_abort("Unable to create IPC socket"); | ||
76 | } | ||
77 | |||
78 | ipc_sockaddr = ipc_user_sockaddr(); | ||
79 | |||
80 | // We want to use socket name set by user, not existing socket from another sway instance. | ||
81 | if (getenv("SWAYSOCK") != NULL && access(getenv("SWAYSOCK"), F_OK) == -1) { | ||
82 | strncpy(ipc_sockaddr->sun_path, getenv("SWAYSOCK"), sizeof(ipc_sockaddr->sun_path)); | ||
83 | ipc_sockaddr->sun_path[sizeof(ipc_sockaddr->sun_path) - 1] = 0; | ||
84 | } | ||
85 | |||
86 | unlink(ipc_sockaddr->sun_path); | ||
87 | if (bind(ipc_socket, (struct sockaddr *)ipc_sockaddr, sizeof(*ipc_sockaddr)) == -1) { | ||
88 | sway_abort("Unable to bind IPC socket"); | ||
89 | } | ||
90 | |||
91 | if (listen(ipc_socket, 3) == -1) { | ||
92 | sway_abort("Unable to listen on IPC socket"); | ||
93 | } | ||
94 | |||
95 | // Set i3 IPC socket path so that i3-msg works out of the box | ||
96 | setenv("I3SOCK", ipc_sockaddr->sun_path, 1); | ||
97 | setenv("SWAYSOCK", ipc_sockaddr->sun_path, 1); | ||
98 | |||
99 | ipc_client_list = create_list(); | ||
100 | ipc_get_pixel_requests = create_list(); | ||
101 | |||
102 | ipc_event_source = wl_event_loop_add_fd(server.wl_event_loop, ipc_socket, | ||
103 | WL_EVENT_READABLE, ipc_handle_connection, NULL); | ||
104 | } | ||
105 | |||
106 | void ipc_terminate(void) { | ||
107 | if (ipc_event_source) { | ||
108 | wl_event_source_remove(ipc_event_source); | ||
109 | } | ||
110 | close(ipc_socket); | ||
111 | unlink(ipc_sockaddr->sun_path); | ||
112 | |||
113 | list_free(ipc_client_list); | ||
114 | |||
115 | if (ipc_sockaddr) { | ||
116 | free(ipc_sockaddr); | ||
117 | } | ||
118 | } | ||
119 | |||
120 | struct sockaddr_un *ipc_user_sockaddr(void) { | ||
121 | struct sockaddr_un *ipc_sockaddr = malloc(sizeof(struct sockaddr_un)); | ||
122 | if (ipc_sockaddr == NULL) { | ||
123 | sway_abort("Can't allocate ipc_sockaddr"); | ||
124 | } | ||
125 | |||
126 | ipc_sockaddr->sun_family = AF_UNIX; | ||
127 | int path_size = sizeof(ipc_sockaddr->sun_path); | ||
128 | |||
129 | // Env var typically set by logind, e.g. "/run/user/<user-id>" | ||
130 | const char *dir = getenv("XDG_RUNTIME_DIR"); | ||
131 | if (!dir) { | ||
132 | dir = "/tmp"; | ||
133 | } | ||
134 | if (path_size <= snprintf(ipc_sockaddr->sun_path, path_size, | ||
135 | "%s/sway-ipc.%i.%i.sock", dir, getuid(), getpid())) { | ||
136 | sway_abort("Socket path won't fit into ipc_sockaddr->sun_path"); | ||
137 | } | ||
138 | |||
139 | return ipc_sockaddr; | ||
140 | } | ||
141 | |||
142 | static pid_t get_client_pid(int client_fd) { | ||
143 | // FreeBSD supports getting uid/gid, but not pid | ||
144 | #ifdef __linux__ | ||
145 | struct ucred ucred; | ||
146 | socklen_t len = sizeof(struct ucred); | ||
147 | |||
148 | if (getsockopt(client_fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1) { | ||
149 | return -1; | ||
150 | } | ||
151 | |||
152 | return ucred.pid; | ||
153 | #else | ||
154 | return -1; | ||
155 | #endif | ||
156 | } | ||
157 | |||
158 | int ipc_handle_connection(int fd, uint32_t mask, void *data) { | ||
159 | (void) fd; (void) data; | ||
160 | sway_log(L_DEBUG, "Event on IPC listening socket"); | ||
161 | assert(mask == WL_EVENT_READABLE); | ||
162 | |||
163 | int client_fd = accept(ipc_socket, NULL, NULL); | ||
164 | if (client_fd == -1) { | ||
165 | sway_log_errno(L_ERROR, "Unable to accept IPC client connection"); | ||
166 | return 0; | ||
167 | } | ||
168 | |||
169 | int flags; | ||
170 | if ((flags = fcntl(client_fd, F_GETFD)) == -1 | ||
171 | || fcntl(client_fd, F_SETFD, flags|FD_CLOEXEC) == -1) { | ||
172 | sway_log_errno(L_ERROR, "Unable to set CLOEXEC on IPC client socket"); | ||
173 | close(client_fd); | ||
174 | return 0; | ||
175 | } | ||
176 | if ((flags = fcntl(client_fd, F_GETFL)) == -1 | ||
177 | || fcntl(client_fd, F_SETFL, flags|O_NONBLOCK) == -1) { | ||
178 | sway_log_errno(L_ERROR, "Unable to set NONBLOCK on IPC client socket"); | ||
179 | close(client_fd); | ||
180 | return 0; | ||
181 | } | ||
182 | |||
183 | struct ipc_client* client = malloc(sizeof(struct ipc_client)); | ||
184 | if (!client) { | ||
185 | sway_log(L_ERROR, "Unable to allocate ipc client"); | ||
186 | close(client_fd); | ||
187 | return 0; | ||
188 | } | ||
189 | client->payload_length = 0; | ||
190 | client->fd = client_fd; | ||
191 | client->subscribed_events = 0; | ||
192 | client->event_source = wl_event_loop_add_fd(server.wl_event_loop, client_fd, | ||
193 | WL_EVENT_READABLE, ipc_client_handle_readable, client); | ||
194 | client->writable_event_source = NULL; | ||
195 | |||
196 | client->write_buffer_size = 128; | ||
197 | client->write_buffer_len = 0; | ||
198 | client->write_buffer = malloc(client->write_buffer_size); | ||
199 | if (!client->write_buffer) { | ||
200 | sway_log(L_ERROR, "Unable to allocate ipc client write buffer"); | ||
201 | close(client_fd); | ||
202 | return 0; | ||
203 | } | ||
204 | |||
205 | pid_t pid = get_client_pid(client->fd); | ||
206 | client->security_policy = get_ipc_policy_mask(pid); | ||
207 | |||
208 | sway_log(L_DEBUG, "New client: fd %d, pid %d", client_fd, pid); | ||
209 | |||
210 | list_add(ipc_client_list, client); | ||
211 | |||
212 | return 0; | ||
213 | } | ||
214 | |||
215 | static const int ipc_header_size = sizeof(ipc_magic)+8; | ||
216 | |||
217 | int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data) { | ||
218 | struct ipc_client *client = data; | ||
219 | |||
220 | if (mask & WL_EVENT_ERROR) { | ||
221 | sway_log(L_ERROR, "IPC Client socket error, removing client"); | ||
222 | ipc_client_disconnect(client); | ||
223 | return 0; | ||
224 | } | ||
225 | |||
226 | if (mask & WL_EVENT_HANGUP) { | ||
227 | sway_log(L_DEBUG, "Client %d hung up", client->fd); | ||
228 | ipc_client_disconnect(client); | ||
229 | return 0; | ||
230 | } | ||
231 | |||
232 | sway_log(L_DEBUG, "Client %d readable", client->fd); | ||
233 | |||
234 | int read_available; | ||
235 | if (ioctl(client_fd, FIONREAD, &read_available) == -1) { | ||
236 | sway_log_errno(L_INFO, "Unable to read IPC socket buffer size"); | ||
237 | ipc_client_disconnect(client); | ||
238 | return 0; | ||
239 | } | ||
240 | |||
241 | // Wait for the rest of the command payload in case the header has already been read | ||
242 | if (client->payload_length > 0) { | ||
243 | if ((uint32_t)read_available >= client->payload_length) { | ||
244 | ipc_client_handle_command(client); | ||
245 | } | ||
246 | return 0; | ||
247 | } | ||
248 | |||
249 | if (read_available < ipc_header_size) { | ||
250 | return 0; | ||
251 | } | ||
252 | |||
253 | uint8_t buf[ipc_header_size]; | ||
254 | uint32_t *buf32 = (uint32_t*)(buf + sizeof(ipc_magic)); | ||
255 | // Should be fully available, because read_available >= ipc_header_size | ||
256 | ssize_t received = recv(client_fd, buf, ipc_header_size, 0); | ||
257 | if (received == -1) { | ||
258 | sway_log_errno(L_INFO, "Unable to receive header from IPC client"); | ||
259 | ipc_client_disconnect(client); | ||
260 | return 0; | ||
261 | } | ||
262 | |||
263 | if (memcmp(buf, ipc_magic, sizeof(ipc_magic)) != 0) { | ||
264 | sway_log(L_DEBUG, "IPC header check failed"); | ||
265 | ipc_client_disconnect(client); | ||
266 | return 0; | ||
267 | } | ||
268 | |||
269 | client->payload_length = buf32[0]; | ||
270 | client->current_command = (enum ipc_command_type)buf32[1]; | ||
271 | |||
272 | if (read_available - received >= (long)client->payload_length) { | ||
273 | ipc_client_handle_command(client); | ||
274 | } | ||
275 | |||
276 | return 0; | ||
277 | } | ||
278 | |||
279 | int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data) { | ||
280 | struct ipc_client *client = data; | ||
281 | |||
282 | if (mask & WL_EVENT_ERROR) { | ||
283 | sway_log(L_ERROR, "IPC Client socket error, removing client"); | ||
284 | ipc_client_disconnect(client); | ||
285 | return 0; | ||
286 | } | ||
287 | |||
288 | if (mask & WL_EVENT_HANGUP) { | ||
289 | sway_log(L_DEBUG, "Client %d hung up", client->fd); | ||
290 | ipc_client_disconnect(client); | ||
291 | return 0; | ||
292 | } | ||
293 | |||
294 | if (client->write_buffer_len <= 0) { | ||
295 | return 0; | ||
296 | } | ||
297 | |||
298 | sway_log(L_DEBUG, "Client %d writable", client->fd); | ||
299 | |||
300 | ssize_t written = write(client->fd, client->write_buffer, client->write_buffer_len); | ||
301 | |||
302 | if (written == -1 && errno == EAGAIN) { | ||
303 | return 0; | ||
304 | } else if (written == -1) { | ||
305 | sway_log_errno(L_INFO, "Unable to send data from queue to IPC client"); | ||
306 | ipc_client_disconnect(client); | ||
307 | return 0; | ||
308 | } | ||
309 | |||
310 | memmove(client->write_buffer, client->write_buffer + written, client->write_buffer_len - written); | ||
311 | client->write_buffer_len -= written; | ||
312 | |||
313 | if (client->write_buffer_len == 0 && client->writable_event_source) { | ||
314 | wl_event_source_remove(client->writable_event_source); | ||
315 | client->writable_event_source = NULL; | ||
316 | } | ||
317 | |||
318 | return 0; | ||
319 | } | ||
320 | |||
321 | void ipc_client_disconnect(struct ipc_client *client) { | ||
322 | if (!sway_assert(client != NULL, "client != NULL")) { | ||
323 | return; | ||
324 | } | ||
325 | |||
326 | if (client->fd != -1) { | ||
327 | shutdown(client->fd, SHUT_RDWR); | ||
328 | } | ||
329 | |||
330 | sway_log(L_INFO, "IPC Client %d disconnected", client->fd); | ||
331 | wl_event_source_remove(client->event_source); | ||
332 | if (client->writable_event_source) { | ||
333 | wl_event_source_remove(client->writable_event_source); | ||
334 | } | ||
335 | int i = 0; | ||
336 | while (i < ipc_client_list->length && ipc_client_list->items[i] != client) i++; | ||
337 | list_del(ipc_client_list, i); | ||
338 | free(client->write_buffer); | ||
339 | close(client->fd); | ||
340 | free(client); | ||
341 | } | ||
342 | |||
343 | bool output_by_name_test(swayc_t *view, void *data) { | ||
344 | char *name = (char *)data; | ||
345 | if (view->type != C_OUTPUT) { | ||
346 | return false; | ||
347 | } | ||
348 | return !strcmp(name, view->name); | ||
349 | } | ||
350 | |||
351 | // greedy wildcard (only "*") matching | ||
352 | bool mime_type_matches(const char *mime_type, const char *pattern) { | ||
353 | const char *wildcard = NULL; | ||
354 | while (*mime_type && *pattern) { | ||
355 | if (*pattern == '*' && !wildcard) { | ||
356 | wildcard = pattern; | ||
357 | ++pattern; | ||
358 | } | ||
359 | |||
360 | if (*mime_type != *pattern) { | ||
361 | if (!wildcard) | ||
362 | return false; | ||
363 | |||
364 | pattern = wildcard; | ||
365 | ++mime_type; | ||
366 | continue; | ||
367 | } | ||
368 | |||
369 | ++mime_type; | ||
370 | ++pattern; | ||
371 | } | ||
372 | |||
373 | while (*pattern == '*') { | ||
374 | ++pattern; | ||
375 | } | ||
376 | |||
377 | return (*mime_type == *pattern); | ||
378 | } | ||
379 | |||
380 | void ipc_client_handle_command(struct ipc_client *client) { | ||
381 | if (!sway_assert(client != NULL, "client != NULL")) { | ||
382 | return; | ||
383 | } | ||
384 | |||
385 | char *buf = malloc(client->payload_length + 1); | ||
386 | if (!buf) { | ||
387 | sway_log_errno(L_INFO, "Unable to allocate IPC payload"); | ||
388 | ipc_client_disconnect(client); | ||
389 | return; | ||
390 | } | ||
391 | if (client->payload_length > 0) { | ||
392 | // Payload should be fully available | ||
393 | ssize_t received = recv(client->fd, buf, client->payload_length, 0); | ||
394 | if (received == -1) | ||
395 | { | ||
396 | sway_log_errno(L_INFO, "Unable to receive payload from IPC client"); | ||
397 | ipc_client_disconnect(client); | ||
398 | free(buf); | ||
399 | return; | ||
400 | } | ||
401 | } | ||
402 | buf[client->payload_length] = '\0'; | ||
403 | |||
404 | const char *error_denied = "{ \"success\": false, \"error\": \"Permission denied\" }"; | ||
405 | |||
406 | switch (client->current_command) { | ||
407 | case IPC_COMMAND: | ||
408 | { | ||
409 | if (!(client->security_policy & IPC_FEATURE_COMMAND)) { | ||
410 | goto exit_denied; | ||
411 | } | ||
412 | struct cmd_results *results = handle_command(buf, CONTEXT_IPC); | ||
413 | const char *json = cmd_results_to_json(results); | ||
414 | char reply[256]; | ||
415 | int length = snprintf(reply, sizeof(reply), "%s", json); | ||
416 | ipc_send_reply(client, reply, (uint32_t) length); | ||
417 | free_cmd_results(results); | ||
418 | goto exit_cleanup; | ||
419 | } | ||
420 | |||
421 | case IPC_SUBSCRIBE: | ||
422 | { | ||
423 | // TODO: Check if they're permitted to use these events | ||
424 | struct json_object *request = json_tokener_parse(buf); | ||
425 | if (request == NULL) { | ||
426 | ipc_send_reply(client, "{\"success\": false}", 18); | ||
427 | sway_log_errno(L_INFO, "Failed to read request"); | ||
428 | goto exit_cleanup; | ||
429 | } | ||
430 | |||
431 | // parse requested event types | ||
432 | for (int i = 0; i < json_object_array_length(request); i++) { | ||
433 | const char *event_type = json_object_get_string(json_object_array_get_idx(request, i)); | ||
434 | if (strcmp(event_type, "workspace") == 0) { | ||
435 | client->subscribed_events |= event_mask(IPC_EVENT_WORKSPACE); | ||
436 | } else if (strcmp(event_type, "barconfig_update") == 0) { | ||
437 | client->subscribed_events |= event_mask(IPC_EVENT_BARCONFIG_UPDATE); | ||
438 | } else if (strcmp(event_type, "mode") == 0) { | ||
439 | client->subscribed_events |= event_mask(IPC_EVENT_MODE); | ||
440 | } else if (strcmp(event_type, "window") == 0) { | ||
441 | client->subscribed_events |= event_mask(IPC_EVENT_WINDOW); | ||
442 | } else if (strcmp(event_type, "modifier") == 0) { | ||
443 | client->subscribed_events |= event_mask(IPC_EVENT_MODIFIER); | ||
444 | } else if (strcmp(event_type, "binding") == 0) { | ||
445 | client->subscribed_events |= event_mask(IPC_EVENT_BINDING); | ||
446 | } else { | ||
447 | ipc_send_reply(client, "{\"success\": false}", 18); | ||
448 | json_object_put(request); | ||
449 | sway_log_errno(L_INFO, "Failed to parse request"); | ||
450 | goto exit_cleanup; | ||
451 | } | ||
452 | } | ||
453 | |||
454 | json_object_put(request); | ||
455 | |||
456 | ipc_send_reply(client, "{\"success\": true}", 17); | ||
457 | goto exit_cleanup; | ||
458 | } | ||
459 | |||
460 | case IPC_GET_WORKSPACES: | ||
461 | { | ||
462 | if (!(client->security_policy & IPC_FEATURE_GET_WORKSPACES)) { | ||
463 | goto exit_denied; | ||
464 | } | ||
465 | json_object *workspaces = json_object_new_array(); | ||
466 | container_map(&root_container, ipc_get_workspaces_callback, workspaces); | ||
467 | const char *json_string = json_object_to_json_string(workspaces); | ||
468 | ipc_send_reply(client, json_string, (uint32_t) strlen(json_string)); | ||
469 | json_object_put(workspaces); // free | ||
470 | goto exit_cleanup; | ||
471 | } | ||
472 | |||
473 | case IPC_GET_INPUTS: | ||
474 | { | ||
475 | if (!(client->security_policy & IPC_FEATURE_GET_INPUTS)) { | ||
476 | goto exit_denied; | ||
477 | } | ||
478 | json_object *inputs = json_object_new_array(); | ||
479 | /* TODO WLR | ||
480 | if (input_devices) { | ||
481 | for(int i = 0; i<input_devices->length; i++) { | ||
482 | struct libinput_device *device = input_devices->items[i]; | ||
483 | json_object_array_add(inputs, ipc_json_describe_input(device)); | ||
484 | } | ||
485 | } | ||
486 | */ | ||
487 | const char *json_string = json_object_to_json_string(inputs); | ||
488 | ipc_send_reply(client, json_string, (uint32_t) strlen(json_string)); | ||
489 | json_object_put(inputs); | ||
490 | goto exit_cleanup; | ||
491 | } | ||
492 | |||
493 | case IPC_GET_OUTPUTS: | ||
494 | { | ||
495 | if (!(client->security_policy & IPC_FEATURE_GET_OUTPUTS)) { | ||
496 | goto exit_denied; | ||
497 | } | ||
498 | json_object *outputs = json_object_new_array(); | ||
499 | container_map(&root_container, ipc_get_outputs_callback, outputs); | ||
500 | const char *json_string = json_object_to_json_string(outputs); | ||
501 | ipc_send_reply(client, json_string, (uint32_t) strlen(json_string)); | ||
502 | json_object_put(outputs); // free | ||
503 | goto exit_cleanup; | ||
504 | } | ||
505 | |||
506 | case IPC_GET_TREE: | ||
507 | { | ||
508 | if (!(client->security_policy & IPC_FEATURE_GET_TREE)) { | ||
509 | goto exit_denied; | ||
510 | } | ||
511 | json_object *tree = ipc_json_describe_container_recursive(&root_container); | ||
512 | const char *json_string = json_object_to_json_string(tree); | ||
513 | ipc_send_reply(client, json_string, (uint32_t) strlen(json_string)); | ||
514 | json_object_put(tree); | ||
515 | goto exit_cleanup; | ||
516 | } | ||
517 | |||
518 | case IPC_GET_MARKS: | ||
519 | { | ||
520 | if (!(client->security_policy & IPC_FEATURE_GET_MARKS)) { | ||
521 | goto exit_denied; | ||
522 | } | ||
523 | json_object *marks = json_object_new_array(); | ||
524 | container_map(&root_container, ipc_get_marks_callback, marks); | ||
525 | const char *json_string = json_object_to_json_string(marks); | ||
526 | ipc_send_reply(client, json_string, (uint32_t) strlen(json_string)); | ||
527 | json_object_put(marks); | ||
528 | goto exit_cleanup; | ||
529 | } | ||
530 | |||
531 | case IPC_GET_VERSION: | ||
532 | { | ||
533 | json_object *version = ipc_json_get_version(); | ||
534 | const char *json_string = json_object_to_json_string(version); | ||
535 | ipc_send_reply(client, json_string, (uint32_t)strlen(json_string)); | ||
536 | json_object_put(version); // free | ||
537 | goto exit_cleanup; | ||
538 | } | ||
539 | |||
540 | case IPC_GET_BAR_CONFIG: | ||
541 | { | ||
542 | if (!(client->security_policy & IPC_FEATURE_GET_BAR_CONFIG)) { | ||
543 | goto exit_denied; | ||
544 | } | ||
545 | if (!buf[0]) { | ||
546 | // Send list of configured bar IDs | ||
547 | json_object *bars = json_object_new_array(); | ||
548 | int i; | ||
549 | for (i = 0; i < config->bars->length; ++i) { | ||
550 | struct bar_config *bar = config->bars->items[i]; | ||
551 | json_object_array_add(bars, json_object_new_string(bar->id)); | ||
552 | } | ||
553 | const char *json_string = json_object_to_json_string(bars); | ||
554 | ipc_send_reply(client, json_string, (uint32_t)strlen(json_string)); | ||
555 | json_object_put(bars); // free | ||
556 | } else { | ||
557 | // Send particular bar's details | ||
558 | struct bar_config *bar = NULL; | ||
559 | int i; | ||
560 | for (i = 0; i < config->bars->length; ++i) { | ||
561 | bar = config->bars->items[i]; | ||
562 | if (strcmp(buf, bar->id) == 0) { | ||
563 | break; | ||
564 | } | ||
565 | bar = NULL; | ||
566 | } | ||
567 | if (!bar) { | ||
568 | const char *error = "{ \"success\": false, \"error\": \"No bar with that ID\" }"; | ||
569 | ipc_send_reply(client, error, (uint32_t)strlen(error)); | ||
570 | goto exit_cleanup; | ||
571 | } | ||
572 | json_object *json = ipc_json_describe_bar_config(bar); | ||
573 | const char *json_string = json_object_to_json_string(json); | ||
574 | ipc_send_reply(client, json_string, (uint32_t)strlen(json_string)); | ||
575 | json_object_put(json); // free | ||
576 | } | ||
577 | goto exit_cleanup; | ||
578 | } | ||
579 | |||
580 | case IPC_GET_CLIPBOARD: | ||
581 | // TODO WLR | ||
582 | break; | ||
583 | |||
584 | default: | ||
585 | sway_log(L_INFO, "Unknown IPC command type %i", client->current_command); | ||
586 | goto exit_cleanup; | ||
587 | } | ||
588 | |||
589 | exit_denied: | ||
590 | ipc_send_reply(client, error_denied, (uint32_t)strlen(error_denied)); | ||
591 | sway_log(L_DEBUG, "Denied IPC client access to %i", client->current_command); | ||
592 | |||
593 | exit_cleanup: | ||
594 | client->payload_length = 0; | ||
595 | free(buf); | ||
596 | return; | ||
597 | } | ||
598 | |||
599 | bool ipc_send_reply(struct ipc_client *client, const char *payload, uint32_t payload_length) { | ||
600 | assert(payload); | ||
601 | |||
602 | char data[ipc_header_size]; | ||
603 | uint32_t *data32 = (uint32_t*)(data + sizeof(ipc_magic)); | ||
604 | |||
605 | memcpy(data, ipc_magic, sizeof(ipc_magic)); | ||
606 | data32[0] = payload_length; | ||
607 | data32[1] = client->current_command; | ||
608 | |||
609 | while (client->write_buffer_len + ipc_header_size + payload_length >= | ||
610 | client->write_buffer_size) { | ||
611 | client->write_buffer_size *= 2; | ||
612 | } | ||
613 | |||
614 | // TODO: reduce the limit back to 4 MB when screenshooter is implemented | ||
615 | if (client->write_buffer_size > (1 << 28)) { // 256 MB | ||
616 | sway_log(L_ERROR, "Client write buffer too big, disconnecting client"); | ||
617 | ipc_client_disconnect(client); | ||
618 | return false; | ||
619 | } | ||
620 | |||
621 | char *new_buffer = realloc(client->write_buffer, client->write_buffer_size); | ||
622 | if (!new_buffer) { | ||
623 | sway_log(L_ERROR, "Unable to reallocate ipc client write buffer"); | ||
624 | ipc_client_disconnect(client); | ||
625 | return false; | ||
626 | } | ||
627 | client->write_buffer = new_buffer; | ||
628 | |||
629 | memcpy(client->write_buffer + client->write_buffer_len, data, ipc_header_size); | ||
630 | client->write_buffer_len += ipc_header_size; | ||
631 | memcpy(client->write_buffer + client->write_buffer_len, payload, payload_length); | ||
632 | client->write_buffer_len += payload_length; | ||
633 | |||
634 | if (!client->writable_event_source) { | ||
635 | client->writable_event_source = wl_event_loop_add_fd( | ||
636 | server.wl_event_loop, client->fd, WL_EVENT_WRITABLE, | ||
637 | ipc_client_handle_writable, client); | ||
638 | } | ||
639 | |||
640 | sway_log(L_DEBUG, "Added IPC reply to client %d queue: %s", client->fd, payload); | ||
641 | |||
642 | return true; | ||
643 | } | ||
644 | |||
645 | void ipc_get_workspaces_callback(swayc_t *workspace, void *data) { | ||
646 | if (workspace->type == C_WORKSPACE) { | ||
647 | json_object *workspace_json = ipc_json_describe_container(workspace); | ||
648 | // override the default focused indicator because | ||
649 | // it's set differently for the get_workspaces reply | ||
650 | bool focused = root_container.focused == workspace->parent && workspace->parent->focused == workspace; | ||
651 | json_object_object_del(workspace_json, "focused"); | ||
652 | json_object_object_add(workspace_json, "focused", json_object_new_boolean(focused)); | ||
653 | json_object_array_add((json_object *)data, workspace_json); | ||
654 | } | ||
655 | } | ||
656 | |||
657 | void ipc_get_outputs_callback(swayc_t *container, void *data) { | ||
658 | if (container->type == C_OUTPUT) { | ||
659 | json_object_array_add((json_object *)data, ipc_json_describe_container(container)); | ||
660 | } | ||
661 | } | ||
662 | |||
663 | static void ipc_get_marks_callback(swayc_t *container, void *data) { | ||
664 | json_object *object = (json_object *)data; | ||
665 | if (container->marks) { | ||
666 | for (int i = 0; i < container->marks->length; ++i) { | ||
667 | char *mark = (char *)container->marks->items[i]; | ||
668 | json_object_array_add(object, json_object_new_string(mark)); | ||
669 | } | ||
670 | } | ||
671 | } | ||
672 | |||
673 | void ipc_send_event(const char *json_string, enum ipc_command_type event) { | ||
674 | static struct { | ||
675 | enum ipc_command_type event; | ||
676 | enum ipc_feature feature; | ||
677 | } security_mappings[] = { | ||
678 | { IPC_EVENT_WORKSPACE, IPC_FEATURE_EVENT_WORKSPACE }, | ||
679 | { IPC_EVENT_OUTPUT, IPC_FEATURE_EVENT_OUTPUT }, | ||
680 | { IPC_EVENT_MODE, IPC_FEATURE_EVENT_MODE }, | ||
681 | { IPC_EVENT_WINDOW, IPC_FEATURE_EVENT_WINDOW }, | ||
682 | { IPC_EVENT_BINDING, IPC_FEATURE_EVENT_BINDING }, | ||
683 | { IPC_EVENT_INPUT, IPC_FEATURE_EVENT_INPUT } | ||
684 | }; | ||
685 | |||
686 | uint32_t security_mask = 0; | ||
687 | for (size_t i = 0; i < sizeof(security_mappings) / sizeof(security_mappings[0]); ++i) { | ||
688 | if (security_mappings[i].event == event) { | ||
689 | security_mask = security_mappings[i].feature; | ||
690 | break; | ||
691 | } | ||
692 | } | ||
693 | |||
694 | int i; | ||
695 | struct ipc_client *client; | ||
696 | for (i = 0; i < ipc_client_list->length; i++) { | ||
697 | client = ipc_client_list->items[i]; | ||
698 | if (!(client->security_policy & security_mask)) { | ||
699 | continue; | ||
700 | } | ||
701 | if ((client->subscribed_events & event_mask(event)) == 0) { | ||
702 | continue; | ||
703 | } | ||
704 | client->current_command = event; | ||
705 | if (!ipc_send_reply(client, json_string, (uint32_t) strlen(json_string))) { | ||
706 | sway_log_errno(L_INFO, "Unable to send reply to IPC client"); | ||
707 | ipc_client_disconnect(client); | ||
708 | } | ||
709 | } | ||
710 | } | ||
711 | |||
712 | void ipc_event_workspace(swayc_t *old, swayc_t *new, const char *change) { | ||
713 | sway_log(L_DEBUG, "Sending workspace::%s event", change); | ||
714 | json_object *obj = json_object_new_object(); | ||
715 | json_object_object_add(obj, "change", json_object_new_string(change)); | ||
716 | if (strcmp("focus", change) == 0) { | ||
717 | if (old) { | ||
718 | json_object_object_add(obj, "old", ipc_json_describe_container_recursive(old)); | ||
719 | } else { | ||
720 | json_object_object_add(obj, "old", NULL); | ||
721 | } | ||
722 | } | ||
723 | |||
724 | if (new) { | ||
725 | json_object_object_add(obj, "current", ipc_json_describe_container_recursive(new)); | ||
726 | } else { | ||
727 | json_object_object_add(obj, "current", NULL); | ||
728 | } | ||
729 | |||
730 | const char *json_string = json_object_to_json_string(obj); | ||
731 | ipc_send_event(json_string, IPC_EVENT_WORKSPACE); | ||
732 | |||
733 | json_object_put(obj); // free | ||
734 | } | ||
735 | |||
736 | void ipc_event_window(swayc_t *window, const char *change) { | ||
737 | sway_log(L_DEBUG, "Sending window::%s event", change); | ||
738 | json_object *obj = json_object_new_object(); | ||
739 | json_object_object_add(obj, "change", json_object_new_string(change)); | ||
740 | json_object_object_add(obj, "container", ipc_json_describe_container_recursive(window)); | ||
741 | |||
742 | const char *json_string = json_object_to_json_string(obj); | ||
743 | ipc_send_event(json_string, IPC_EVENT_WINDOW); | ||
744 | |||
745 | json_object_put(obj); // free | ||
746 | } | ||
747 | |||
748 | void ipc_event_barconfig_update(struct bar_config *bar) { | ||
749 | sway_log(L_DEBUG, "Sending barconfig_update event"); | ||
750 | json_object *json = ipc_json_describe_bar_config(bar); | ||
751 | const char *json_string = json_object_to_json_string(json); | ||
752 | ipc_send_event(json_string, IPC_EVENT_BARCONFIG_UPDATE); | ||
753 | |||
754 | json_object_put(json); // free | ||
755 | } | ||
756 | |||
757 | void ipc_event_mode(const char *mode) { | ||
758 | sway_log(L_DEBUG, "Sending mode::%s event", mode); | ||
759 | json_object *obj = json_object_new_object(); | ||
760 | json_object_object_add(obj, "change", json_object_new_string(mode)); | ||
761 | |||
762 | const char *json_string = json_object_to_json_string(obj); | ||
763 | ipc_send_event(json_string, IPC_EVENT_MODE); | ||
764 | |||
765 | json_object_put(obj); // free | ||
766 | } | ||
767 | |||
768 | void ipc_event_modifier(uint32_t modifier, const char *state) { | ||
769 | sway_log(L_DEBUG, "Sending modifier::%s event", state); | ||
770 | json_object *obj = json_object_new_object(); | ||
771 | json_object_object_add(obj, "change", json_object_new_string(state)); | ||
772 | |||
773 | const char *modifier_name = get_modifier_name_by_mask(modifier); | ||
774 | json_object_object_add(obj, "modifier", json_object_new_string(modifier_name)); | ||
775 | |||
776 | const char *json_string = json_object_to_json_string(obj); | ||
777 | ipc_send_event(json_string, IPC_EVENT_MODIFIER); | ||
778 | |||
779 | json_object_put(obj); // free | ||
780 | } | ||
781 | |||
782 | static void ipc_event_binding(json_object *sb_obj) { | ||
783 | sway_log(L_DEBUG, "Sending binding::run event"); | ||
784 | json_object *obj = json_object_new_object(); | ||
785 | json_object_object_add(obj, "change", json_object_new_string("run")); | ||
786 | json_object_object_add(obj, "binding", sb_obj); | ||
787 | |||
788 | const char *json_string = json_object_to_json_string(obj); | ||
789 | ipc_send_event(json_string, IPC_EVENT_BINDING); | ||
790 | |||
791 | json_object_put(obj); // free | ||
792 | } | ||
793 | |||
794 | void ipc_event_binding_keyboard(struct sway_binding *sb) { | ||
795 | json_object *sb_obj = json_object_new_object(); | ||
796 | json_object_object_add(sb_obj, "command", json_object_new_string(sb->command)); | ||
797 | |||
798 | const char *names[10]; | ||
799 | |||
800 | int len = get_modifier_names(names, sb->modifiers); | ||
801 | int i; | ||
802 | json_object *modifiers = json_object_new_array(); | ||
803 | for (i = 0; i < len; ++i) { | ||
804 | json_object_array_add(modifiers, json_object_new_string(names[i])); | ||
805 | } | ||
806 | |||
807 | json_object_object_add(sb_obj, "event_state_mask", modifiers); | ||
808 | |||
809 | json_object *input_codes = json_object_new_array(); | ||
810 | int input_code = 0; | ||
811 | json_object *symbols = json_object_new_array(); | ||
812 | json_object *symbol = NULL; | ||
813 | |||
814 | if (sb->bindcode) { // bindcode: populate input_codes | ||
815 | uint32_t keycode; | ||
816 | for (i = 0; i < sb->keys->length; ++i) { | ||
817 | keycode = *(uint32_t *)sb->keys->items[i]; | ||
818 | json_object_array_add(input_codes, json_object_new_int(keycode)); | ||
819 | if (i == 0) { | ||
820 | input_code = keycode; | ||
821 | } | ||
822 | } | ||
823 | } else { // bindsym: populate symbols | ||
824 | uint32_t keysym; | ||
825 | char buffer[64]; | ||
826 | for (i = 0; i < sb->keys->length; ++i) { | ||
827 | keysym = *(uint32_t *)sb->keys->items[i]; | ||
828 | if (xkb_keysym_get_name(keysym, buffer, 64) > 0) { | ||
829 | json_object *str = json_object_new_string(buffer); | ||
830 | json_object_array_add(symbols, str); | ||
831 | if (i == 0) { | ||
832 | symbol = str; | ||
833 | } | ||
834 | } | ||
835 | } | ||
836 | } | ||
837 | |||
838 | json_object_object_add(sb_obj, "input_codes", input_codes); | ||
839 | json_object_object_add(sb_obj, "input_code", json_object_new_int(input_code)); | ||
840 | json_object_object_add(sb_obj, "symbols", symbols); | ||
841 | json_object_object_add(sb_obj, "symbol", symbol); | ||
842 | json_object_object_add(sb_obj, "input_type", json_object_new_string("keyboard")); | ||
843 | |||
844 | ipc_event_binding(sb_obj); | ||
845 | } | ||