diff options
-rw-r--r-- | include/ipc.h | 18 | ||||
-rw-r--r-- | include/log.h | 1 | ||||
-rw-r--r-- | include/sway.h | 6 | ||||
-rw-r--r-- | sway/commands.c | 3 | ||||
-rw-r--r-- | sway/ipc.c | 229 | ||||
-rw-r--r-- | sway/log.c | 33 | ||||
-rw-r--r-- | sway/main.c | 18 |
7 files changed, 305 insertions, 3 deletions
diff --git a/include/ipc.h b/include/ipc.h new file mode 100644 index 00000000..0b6441f6 --- /dev/null +++ b/include/ipc.h | |||
@@ -0,0 +1,18 @@ | |||
1 | #ifndef _SWAY_IPC_H | ||
2 | #define _SWAY_IPC_H | ||
3 | |||
4 | enum ipc_command_type { | ||
5 | IPC_COMMAND = 0, | ||
6 | IPC_GET_WORKSPACES = 1, | ||
7 | IPC_SUBSCRIBE = 2, | ||
8 | IPC_GET_OUTPUTS = 3, | ||
9 | IPC_GET_TREE = 4, | ||
10 | IPC_GET_MARKS = 5, | ||
11 | IPC_GET_BAR_CONFIG = 6, | ||
12 | IPC_GET_VERSION = 7, | ||
13 | }; | ||
14 | |||
15 | void ipc_init(void); | ||
16 | void ipc_terminate(void); | ||
17 | |||
18 | #endif | ||
diff --git a/include/log.h b/include/log.h index 7aea2ded..47a83321 100644 --- a/include/log.h +++ b/include/log.h | |||
@@ -13,6 +13,7 @@ typedef enum { | |||
13 | void init_log(int verbosity); | 13 | void init_log(int verbosity); |
14 | void sway_log_colors(int mode); | 14 | void sway_log_colors(int mode); |
15 | void sway_log(int verbosity, const char* format, ...) __attribute__((format(printf,2,3))); | 15 | void sway_log(int verbosity, const char* format, ...) __attribute__((format(printf,2,3))); |
16 | void sway_log_errno(int verbosity, char* format, ...) __attribute__((format(printf,2,3))); | ||
16 | void sway_abort(const char* format, ...) __attribute__((format(printf,1,2))); | 17 | void sway_abort(const char* format, ...) __attribute__((format(printf,1,2))); |
17 | bool sway_assert(bool condition, const char* format, ...) __attribute__((format(printf,2,3))); | 18 | bool sway_assert(bool condition, const char* format, ...) __attribute__((format(printf,2,3))); |
18 | 19 | ||
diff --git a/include/sway.h b/include/sway.h new file mode 100644 index 00000000..6499c81d --- /dev/null +++ b/include/sway.h | |||
@@ -0,0 +1,6 @@ | |||
1 | #ifndef _SWAY_SWAY_H | ||
2 | #define _SWAY_SWAY_H | ||
3 | |||
4 | void sway_terminate(void); | ||
5 | |||
6 | #endif | ||
diff --git a/sway/commands.c b/sway/commands.c index 803d9a21..644b8005 100644 --- a/sway/commands.c +++ b/sway/commands.c | |||
@@ -15,6 +15,7 @@ | |||
15 | #include "commands.h" | 15 | #include "commands.h" |
16 | #include "container.h" | 16 | #include "container.h" |
17 | #include "handlers.h" | 17 | #include "handlers.h" |
18 | #include "sway.h" | ||
18 | 19 | ||
19 | struct modifier_key { | 20 | struct modifier_key { |
20 | char *name; | 21 | char *name; |
@@ -186,7 +187,7 @@ static bool cmd_exit(struct sway_config *config, int argc, char **argv) { | |||
186 | } | 187 | } |
187 | // Close all views | 188 | // Close all views |
188 | container_map(&root_container, kill_views, NULL); | 189 | container_map(&root_container, kill_views, NULL); |
189 | exit(0); | 190 | sway_terminate(); |
190 | return true; | 191 | return true; |
191 | } | 192 | } |
192 | 193 | ||
diff --git a/sway/ipc.c b/sway/ipc.c new file mode 100644 index 00000000..505c17f8 --- /dev/null +++ b/sway/ipc.c | |||
@@ -0,0 +1,229 @@ | |||
1 | // See https://i3wm.org/docs/ipc.html for protocol information | ||
2 | |||
3 | #include <errno.h> | ||
4 | #include <string.h> | ||
5 | #include <sys/socket.h> | ||
6 | #include <sys/un.h> | ||
7 | #include <stdbool.h> | ||
8 | #include <wlc/wlc.h> | ||
9 | #include <unistd.h> | ||
10 | #include <stdlib.h> | ||
11 | #include <stropts.h> | ||
12 | #include <sys/ioctl.h> | ||
13 | #include <fcntl.h> | ||
14 | #include "ipc.h" | ||
15 | #include "log.h" | ||
16 | #include "config.h" | ||
17 | #include "commands.h" | ||
18 | |||
19 | static int ipc_socket = -1; | ||
20 | static struct wlc_event_source *ipc_event_source = NULL; | ||
21 | static struct sockaddr_un ipc_sockaddr = { | ||
22 | .sun_family = AF_UNIX, | ||
23 | .sun_path = "/tmp/sway-ipc.sock" | ||
24 | }; | ||
25 | |||
26 | static const char ipc_magic[] = {'i', '3', '-', 'i', 'p', 'c'}; | ||
27 | |||
28 | struct ipc_client { | ||
29 | struct wlc_event_source *event_source; | ||
30 | int fd; | ||
31 | uint32_t payload_length; | ||
32 | enum ipc_command_type current_command; | ||
33 | }; | ||
34 | |||
35 | int ipc_handle_connection(int fd, uint32_t mask, void *data); | ||
36 | int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data); | ||
37 | void ipc_client_disconnect(struct ipc_client *client); | ||
38 | void ipc_client_handle_command(struct ipc_client *client); | ||
39 | bool ipc_send_reply(struct ipc_client *client, const char *payload, uint32_t payload_length); | ||
40 | |||
41 | void ipc_init(void) { | ||
42 | ipc_socket = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); | ||
43 | if (ipc_socket == -1) { | ||
44 | sway_abort("Unable to create IPC socket"); | ||
45 | } | ||
46 | |||
47 | if (getenv("SWAYSOCK") != NULL) { | ||
48 | strncpy(ipc_sockaddr.sun_path, getenv("SWAYSOCK"), sizeof(ipc_sockaddr.sun_path)); | ||
49 | } | ||
50 | |||
51 | unlink(ipc_sockaddr.sun_path); | ||
52 | if (bind(ipc_socket, (struct sockaddr *)&ipc_sockaddr, sizeof(ipc_sockaddr)) == -1) { | ||
53 | sway_abort("Unable to bind IPC socket"); | ||
54 | } | ||
55 | |||
56 | if (listen(ipc_socket, 3) == -1) { | ||
57 | sway_abort("Unable to listen on IPC socket"); | ||
58 | } | ||
59 | |||
60 | // Set i3 IPC socket path so that i3-msg works out of the box | ||
61 | setenv("I3SOCK", ipc_sockaddr.sun_path, 1); | ||
62 | |||
63 | ipc_event_source = wlc_event_loop_add_fd(ipc_socket, WLC_EVENT_READABLE, ipc_handle_connection, NULL); | ||
64 | } | ||
65 | |||
66 | void ipc_terminate(void) { | ||
67 | if (ipc_event_source) { | ||
68 | wlc_event_source_remove(ipc_event_source); | ||
69 | } | ||
70 | close(ipc_socket); | ||
71 | unlink(ipc_sockaddr.sun_path); | ||
72 | } | ||
73 | |||
74 | int ipc_handle_connection(int fd, uint32_t mask, void *data) { | ||
75 | (void) fd; (void) data; | ||
76 | sway_log(L_DEBUG, "Event on IPC listening socket"); | ||
77 | assert(mask == WLC_EVENT_READABLE); | ||
78 | |||
79 | int client_fd = accept(ipc_socket, NULL, NULL); | ||
80 | if (client_fd == -1) { | ||
81 | sway_log_errno(L_INFO, "Unable to accept IPC client connection"); | ||
82 | return 0; | ||
83 | } | ||
84 | |||
85 | int flags; | ||
86 | if ((flags=fcntl(client_fd, F_GETFD)) == -1 || fcntl(client_fd, F_SETFD, flags|FD_CLOEXEC) == -1) { | ||
87 | sway_log_errno(L_INFO, "Unable to set CLOEXEC on IPC client socket"); | ||
88 | return 0; | ||
89 | } | ||
90 | |||
91 | struct ipc_client* client = malloc(sizeof(struct ipc_client)); | ||
92 | client->payload_length = 0; | ||
93 | client->fd = client_fd; | ||
94 | client->event_source = wlc_event_loop_add_fd(client_fd, WLC_EVENT_READABLE, ipc_client_handle_readable, client); | ||
95 | |||
96 | return 0; | ||
97 | } | ||
98 | |||
99 | static const int ipc_header_size = sizeof(ipc_magic)+8; | ||
100 | |||
101 | int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data) { | ||
102 | struct ipc_client *client = data; | ||
103 | sway_log(L_DEBUG, "Event on IPC client socket %d", client_fd); | ||
104 | |||
105 | if (mask & WLC_EVENT_ERROR) { | ||
106 | sway_log(L_INFO, "IPC Client socket error, removing client"); | ||
107 | ipc_client_disconnect(client); | ||
108 | return 0; | ||
109 | } | ||
110 | |||
111 | if (mask & WLC_EVENT_HANGUP) { | ||
112 | ipc_client_disconnect(client); | ||
113 | return 0; | ||
114 | } | ||
115 | |||
116 | int read_available; | ||
117 | ioctl(client_fd, FIONREAD, &read_available); | ||
118 | |||
119 | // Wait for the rest of the command payload in case the header has already been read | ||
120 | if (client->payload_length > 0) { | ||
121 | if (read_available >= client->payload_length) { | ||
122 | ipc_client_handle_command(client); | ||
123 | } | ||
124 | else { | ||
125 | sway_log(L_DEBUG, "Too little data to read payload on IPC Client socket, waiting for more (%d < %d)", read_available, client->payload_length); | ||
126 | } | ||
127 | return 0; | ||
128 | } | ||
129 | |||
130 | if (read_available < ipc_header_size) { | ||
131 | sway_log(L_DEBUG, "Too little data to read header on IPC Client socket, waiting for more (%d < %d)", read_available, ipc_header_size); | ||
132 | return 0; | ||
133 | } | ||
134 | |||
135 | char buf[ipc_header_size]; | ||
136 | ssize_t received = recv(client_fd, buf, ipc_header_size, 0); | ||
137 | if (received == -1) { | ||
138 | sway_log_errno(L_INFO, "Unable to receive header from IPC client"); | ||
139 | ipc_client_disconnect(client); | ||
140 | return 0; | ||
141 | } | ||
142 | |||
143 | if (memcmp(buf, ipc_magic, sizeof(ipc_magic)) != 0) { | ||
144 | sway_log(L_DEBUG, "IPC header check failed"); | ||
145 | ipc_client_disconnect(client); | ||
146 | return 0; | ||
147 | } | ||
148 | |||
149 | client->payload_length = *(uint32_t *)&buf[sizeof(ipc_magic)]; | ||
150 | client->current_command = (enum ipc_command_type) *(uint32_t *)&buf[sizeof(ipc_magic)+4]; | ||
151 | |||
152 | if (read_available - received >= client->payload_length) { | ||
153 | ipc_client_handle_command(client); | ||
154 | } | ||
155 | |||
156 | return 0; | ||
157 | } | ||
158 | |||
159 | void ipc_client_disconnect(struct ipc_client *client) | ||
160 | { | ||
161 | if (!sway_assert(client != NULL, "client != NULL")) { | ||
162 | return; | ||
163 | } | ||
164 | |||
165 | sway_log(L_INFO, "IPC Client %d disconnected", client->fd); | ||
166 | wlc_event_source_remove(client->event_source); | ||
167 | close(client->fd); | ||
168 | free(client); | ||
169 | } | ||
170 | |||
171 | void ipc_client_handle_command(struct ipc_client *client) { | ||
172 | if (!sway_assert(client != NULL, "client != NULL")) { | ||
173 | return; | ||
174 | } | ||
175 | |||
176 | char buf[client->payload_length + 1]; | ||
177 | if (client->payload_length > 0) | ||
178 | { | ||
179 | ssize_t received = recv(client->fd, buf, client->payload_length, 0); | ||
180 | if (received == -1) | ||
181 | { | ||
182 | sway_log_errno(L_INFO, "Unable to receive payload from IPC client"); | ||
183 | ipc_client_disconnect(client); | ||
184 | return; | ||
185 | } | ||
186 | } | ||
187 | |||
188 | switch (client->current_command) { | ||
189 | case IPC_COMMAND: | ||
190 | { | ||
191 | buf[client->payload_length] = '\0'; | ||
192 | bool success = handle_command(config, buf); | ||
193 | char reply[64]; | ||
194 | int length = snprintf(reply, sizeof(reply), "{\"success\":%s}", success ? "true" : "false"); | ||
195 | ipc_send_reply(client, reply, (uint32_t) length); | ||
196 | break; | ||
197 | } | ||
198 | default: | ||
199 | sway_log(L_INFO, "Unknown IPC command type %i", client->current_command); | ||
200 | ipc_client_disconnect(client); | ||
201 | break; | ||
202 | } | ||
203 | |||
204 | client->payload_length = 0; | ||
205 | } | ||
206 | |||
207 | bool ipc_send_reply(struct ipc_client *client, const char *payload, uint32_t payload_length) { | ||
208 | assert(payload); | ||
209 | |||
210 | char data[ipc_header_size]; | ||
211 | |||
212 | memcpy(data, ipc_magic, sizeof(ipc_magic)); | ||
213 | *(uint32_t *)&(data[sizeof(ipc_magic)]) = payload_length; | ||
214 | *(uint32_t *)&(data[sizeof(ipc_magic)+4]) = client->current_command; | ||
215 | |||
216 | if (write(client->fd, data, ipc_header_size) == -1) { | ||
217 | sway_log_errno(L_INFO, "Unable to send header to IPC client"); | ||
218 | ipc_client_disconnect(client); | ||
219 | return false; | ||
220 | } | ||
221 | |||
222 | if (write(client->fd, payload, payload_length) == -1) { | ||
223 | sway_log_errno(L_INFO, "Unable to send payload to IPC client"); | ||
224 | ipc_client_disconnect(client); | ||
225 | return false; | ||
226 | } | ||
227 | |||
228 | return true; | ||
229 | } | ||
@@ -1,10 +1,13 @@ | |||
1 | #include "log.h" | 1 | #include "log.h" |
2 | #include "sway.h" | ||
2 | #include <stdarg.h> | 3 | #include <stdarg.h> |
3 | #include <stdio.h> | 4 | #include <stdio.h> |
4 | #include <stdlib.h> | 5 | #include <stdlib.h> |
5 | #include <fcntl.h> | 6 | #include <fcntl.h> |
6 | #include <unistd.h> | 7 | #include <unistd.h> |
7 | #include <signal.h> | 8 | #include <signal.h> |
9 | #include <errno.h> | ||
10 | #include <string.h> | ||
8 | 11 | ||
9 | int colored = 1; | 12 | int colored = 1; |
10 | int v = 0; | 13 | int v = 0; |
@@ -40,7 +43,7 @@ void sway_abort(const char *format, ...) { | |||
40 | vfprintf(stderr, format, args); | 43 | vfprintf(stderr, format, args); |
41 | va_end(args); | 44 | va_end(args); |
42 | fprintf(stderr, "\n"); | 45 | fprintf(stderr, "\n"); |
43 | exit(1); | 46 | sway_terminate(); |
44 | } | 47 | } |
45 | 48 | ||
46 | void sway_log(int verbosity, const char* format, ...) { | 49 | void sway_log(int verbosity, const char* format, ...) { |
@@ -66,6 +69,34 @@ void sway_log(int verbosity, const char* format, ...) { | |||
66 | } | 69 | } |
67 | } | 70 | } |
68 | 71 | ||
72 | void sway_log_errno(int verbosity, char* format, ...) { | ||
73 | if (verbosity <= v) { | ||
74 | int c = verbosity; | ||
75 | if (c > sizeof(verbosity_colors) / sizeof(char *)) { | ||
76 | c = sizeof(verbosity_colors) / sizeof(char *) - 1; | ||
77 | } | ||
78 | |||
79 | if (colored) { | ||
80 | fprintf(stderr, verbosity_colors[c]); | ||
81 | } | ||
82 | |||
83 | va_list args; | ||
84 | va_start(args, format); | ||
85 | vfprintf(stderr, format, args); | ||
86 | va_end(args); | ||
87 | |||
88 | fprintf(stderr, ": "); | ||
89 | char error[256]; | ||
90 | strerror_r(errno, error, sizeof(error)); | ||
91 | fprintf(stderr, error); | ||
92 | |||
93 | if (colored) { | ||
94 | fprintf(stderr, "\x1B[0m"); | ||
95 | } | ||
96 | fprintf(stderr, "\n"); | ||
97 | } | ||
98 | } | ||
99 | |||
69 | bool sway_assert(bool condition, const char* format, ...) { | 100 | bool sway_assert(bool condition, const char* format, ...) { |
70 | if (condition) { | 101 | if (condition) { |
71 | return true; | 102 | return true; |
diff --git a/sway/main.c b/sway/main.c index 4a7e13c1..f37f086d 100644 --- a/sway/main.c +++ b/sway/main.c | |||
@@ -9,6 +9,15 @@ | |||
9 | #include "config.h" | 9 | #include "config.h" |
10 | #include "log.h" | 10 | #include "log.h" |
11 | #include "handlers.h" | 11 | #include "handlers.h" |
12 | #include "ipc.h" | ||
13 | #include "sway.h" | ||
14 | |||
15 | static bool terminate_request = false; | ||
16 | |||
17 | void sway_terminate(void) { | ||
18 | terminate_request = true; | ||
19 | wlc_terminate(); | ||
20 | } | ||
12 | 21 | ||
13 | static void sigchld_handle(int signal); | 22 | static void sigchld_handle(int signal); |
14 | 23 | ||
@@ -99,11 +108,18 @@ int main(int argc, char **argv) { | |||
99 | free(config_path); | 108 | free(config_path); |
100 | } | 109 | } |
101 | 110 | ||
102 | wlc_run(); | 111 | ipc_init(); |
112 | |||
113 | if (!terminate_request) { | ||
114 | wlc_run(); | ||
115 | } | ||
116 | |||
103 | if (devnull) { | 117 | if (devnull) { |
104 | fclose(devnull); | 118 | fclose(devnull); |
105 | } | 119 | } |
106 | 120 | ||
121 | ipc_terminate(); | ||
122 | |||
107 | return 0; | 123 | return 0; |
108 | } | 124 | } |
109 | 125 | ||