brewery

chroma

public

Lightweight wallpaper daemon for Wayland

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/chroma.git
src/ipc.cC545 lines14.5 KB
1
#include <errno.h>
2
#include <fcntl.h>
3
#include <stdio.h>
4
#include <stdlib.h>
5
#include <string.h>
6
#include <sys/socket.h>
7
#include <sys/stat.h>
8
#include <sys/un.h>
9
#include <unistd.h>
10
11
#include "../include/chroma.h"
12
13
#define IPC_MAX_MSG 4096
14
#define IPC_RESP_OK "ok\n"
15
#define IPC_RESP_ERR "err %s\n"
16
17
static int parse_set_command(const char *cmd, char *output_name, size_t name_sz,
18
                             char *image_path, size_t path_sz) {
19
  char action[64];
20
  int n = sscanf(cmd, "%63s", action);
21
  if (n != 1)
22
    return -1;
23
24
  if (strcmp(action, "set") == 0) {
25
    const char *rest = cmd + 4;
26
    while (*rest == ' ')
27
      rest++;
28
29
    const char *name_start = rest;
30
    while (*rest && *rest != ' ')
31
      rest++;
32
    size_t name_len = (size_t)(rest - name_start);
33
    if (name_len == 0 || name_len >= name_sz)
34
      return -1;
35
    memcpy(output_name, name_start, name_len);
36
    output_name[name_len] = '\0';
37
38
    while (*rest == ' ')
39
      rest++;
40
    const char *path_start = rest;
41
    while (*rest && *rest != '\n' && *rest != '\r')
42
      rest++;
43
    size_t path_len = (size_t)(rest - path_start);
44
    if (path_len == 0 || path_len >= path_sz)
45
      return -1;
46
    memcpy(image_path, path_start, path_len);
47
    image_path[path_len] = '\0';
48
    return 0;
49
  }
50
51
  return -1;
52
}
53
54
static int parse_set_all_command(const char *cmd, char *image_path,
55
                                 size_t path_sz) {
56
  char action[64];
57
  int n = sscanf(cmd, "%63s", action);
58
  if (n != 1)
59
    return -1;
60
61
  if (strcmp(action, "set_all") == 0) {
62
    const char *rest = cmd + 8;
63
    while (*rest == ' ')
64
      rest++;
65
    const char *path_start = rest;
66
    while (*rest && *rest != '\n' && *rest != '\r')
67
      rest++;
68
    size_t path_len = (size_t)(rest - path_start);
69
    if (path_len == 0 || path_len >= path_sz)
70
      return -1;
71
    memcpy(image_path, path_start, path_len);
72
    image_path[path_len] = '\0';
73
    return 0;
74
  }
75
76
  return -1;
77
}
78
79
static int parse_clear_command(const char *cmd, char *output_name,
80
                               size_t name_sz, char *hex_color,
81
                               size_t color_sz) {
82
  (void)name_sz;
83
  char action[64];
84
  int n = sscanf(cmd, "%63s", action);
85
  if (n != 1)
86
    return -1;
87
88
  if (strcmp(action, "clear") == 0) {
89
    const char *rest = cmd + 6;
90
    while (*rest == ' ')
91
      rest++;
92
93
    if (*rest == '\0' || *rest == '\n' || *rest == '\r') {
94
      output_name[0] = '\0';
95
      hex_color[0] = '\0';
96
      return 0;
97
    }
98
99
    const char *arg1_start = rest;
100
    while (*rest && *rest != ' ')
101
      rest++;
102
    size_t arg1_len = (size_t)(rest - arg1_start);
103
104
    while (*rest == ' ')
105
      rest++;
106
107
    if (*rest == '\0' || *rest == '\n' || *rest == '\r') {
108
      memcpy(hex_color, arg1_start, arg1_len);
109
      hex_color[arg1_len] = '\0';
110
      output_name[0] = '\0';
111
      return 0;
112
    }
113
114
    memcpy(output_name, arg1_start, arg1_len);
115
    output_name[arg1_len] = '\0';
116
117
    const char *color_start = rest;
118
    while (*rest && *rest != '\n' && *rest != '\r')
119
      rest++;
120
    size_t color_len = (size_t)(rest - color_start);
121
    if (color_len >= color_sz)
122
      return -1;
123
    memcpy(hex_color, color_start, color_len);
124
    hex_color[color_len] = '\0';
125
    return 0;
126
  }
127
128
  return -1;
129
}
130
131
static uint32_t parse_hex_color(const char *hex, bool *valid) {
132
  *valid = false;
133
  if (!hex || hex[0] == '\0')
134
    return 0xFF000000;
135
136
  size_t len = strlen(hex);
137
  if (len != 6 && len != 8)
138
    return 0xFF000000;
139
140
  for (size_t i = 0; i < len; i++) {
141
    if (!((hex[i] >= '0' && hex[i] <= '9') ||
142
          (hex[i] >= 'a' && hex[i] <= 'f') || (hex[i] >= 'A' && hex[i] <= 'F')))
143
      return 0xFF000000;
144
  }
145
146
  unsigned int r, g, b, a = 0xFF;
147
  if (len == 6) {
148
    sscanf(hex, "%2x%2x%2x", &r, &g, &b);
149
  } else {
150
    sscanf(hex, "%2x%2x%2x%2x", &r, &g, &b, &a);
151
  }
152
153
  *valid = true;
154
  return ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) |
155
         (uint32_t)b;
156
}
157
158
static int send_response(int fd, const char *response) {
159
  size_t len = strlen(response);
160
  ssize_t written = write(fd, response, len);
161
  return (written == (ssize_t)len) ? 0 : -1;
162
}
163
164
static int handle_command(chroma_state_t *state, int client_fd,
165
                          const char *command) {
166
  chroma_log("DEBUG", "IPC command: %s", command);
167
168
  if (strcmp(command, "ping") == 0) {
169
    return send_response(client_fd, "pong\n");
170
  }
171
172
  if (strcmp(command, "reload") == 0) {
173
    const char *cfg = state->config_file[0] != '\0' ? state->config_file : NULL;
174
    int ret = chroma_reload_config(state, cfg);
175
    if (ret != CHROMA_OK) {
176
      return send_response(client_fd, "err config_reload_failed\n");
177
    }
178
    return send_response(client_fd, IPC_RESP_OK);
179
  }
180
181
  if (strncmp(command, "set_all ", 8) == 0 || strcmp(command, "set_all") == 0) {
182
    char image_path[MAX_PATH_LEN];
183
    if (parse_set_all_command(command, image_path, sizeof(image_path)) != 0) {
184
      return send_response(client_fd, "err invalid_syntax\n");
185
    }
186
    int ret = chroma_set_wallpaper_for_all(state, image_path);
187
    if (ret != CHROMA_OK) {
188
      return send_response(client_fd, "err failed_to_set\n");
189
    }
190
    return send_response(client_fd, IPC_RESP_OK);
191
  }
192
193
  if (strncmp(command, "set ", 4) == 0) {
194
    char output_name[256];
195
    char image_path[MAX_PATH_LEN];
196
    if (parse_set_command(command, output_name, sizeof(output_name), image_path,
197
                          sizeof(image_path)) != 0) {
198
      return send_response(client_fd, "err invalid_syntax\n");
199
    }
200
    int ret = chroma_set_wallpaper_for_output(state, output_name, image_path);
201
    if (ret != CHROMA_OK) {
202
      return send_response(client_fd, "err failed_to_set\n");
203
    }
204
    return send_response(client_fd, IPC_RESP_OK);
205
  }
206
207
  if (strncmp(command, "clear", 5) == 0) {
208
    char output_name[256];
209
    char hex_color[10];
210
    if (parse_clear_command(command, output_name, sizeof(output_name),
211
                            hex_color, sizeof(hex_color)) != 0) {
212
      return send_response(client_fd, "err invalid_syntax\n");
213
    }
214
    if (output_name[0] != '\0') {
215
      int ret =
216
          chroma_set_solid_color_for_output(state, output_name, hex_color);
217
      if (ret != CHROMA_OK) {
218
        return send_response(client_fd, "err failed_to_set\n");
219
      }
220
    } else {
221
      int ret = chroma_set_solid_color_for_all(state, hex_color);
222
      if (ret != CHROMA_OK) {
223
        return send_response(client_fd, "err failed_to_set\n");
224
      }
225
    }
226
    return send_response(client_fd, IPC_RESP_OK);
227
  }
228
229
  return send_response(client_fd, "err unknown_command\n");
230
}
231
232
int chroma_ipc_init(chroma_state_t *state) {
233
  if (!state || !state->config.ipc_enabled) {
234
    return CHROMA_OK;
235
  }
236
237
  const char *socket_path = state->config.ipc_socket_path;
238
  if (socket_path[0] == '\0') {
239
    chroma_log("WARN", "IPC enabled but no socket path configured");
240
    return CHROMA_OK;
241
  }
242
243
  struct sockaddr_un addr;
244
  memset(&addr, 0, sizeof(addr));
245
  addr.sun_family = AF_UNIX;
246
247
  size_t path_len = strlen(socket_path);
248
  if (path_len >= sizeof(addr.sun_path)) {
249
    chroma_log("ERROR", "IPC socket path too long (%zu >= %zu)", path_len,
250
               sizeof(addr.sun_path));
251
    return CHROMA_ERROR_CONFIG;
252
  }
253
  memcpy(addr.sun_path, socket_path, path_len);
254
255
  struct stat st;
256
  if (lstat(socket_path, &st) == 0) {
257
    if (!S_ISSOCK(st.st_mode)) {
258
      chroma_log("ERROR", "IPC socket path exists but is not a socket: %s",
259
                 socket_path);
260
      return CHROMA_ERROR_CONFIG;
261
    }
262
    unlink(socket_path);
263
  }
264
265
  int fd = socket(AF_UNIX, SOCK_STREAM, 0);
266
  if (fd < 0) {
267
    chroma_log("ERROR", "Failed to create IPC socket: %s", strerror(errno));
268
    return CHROMA_ERROR_INIT;
269
  }
270
271
  if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
272
    chroma_log("ERROR", "Failed to bind IPC socket to %s: %s", socket_path,
273
               strerror(errno));
274
    close(fd);
275
    return CHROMA_ERROR_INIT;
276
  }
277
278
  if (listen(fd, 5) < 0) {
279
    chroma_log("ERROR", "Failed to listen on IPC socket: %s", strerror(errno));
280
    close(fd);
281
    return CHROMA_ERROR_INIT;
282
  }
283
284
  state->ipc_fd = fd;
285
  chroma_log("INFO", "IPC socket listening on %s", socket_path);
286
  return CHROMA_OK;
287
}
288
289
void chroma_ipc_cleanup(chroma_state_t *state) {
290
  if (!state || state->ipc_fd < 0) {
291
    return;
292
  }
293
294
  close(state->ipc_fd);
295
  state->ipc_fd = -1;
296
297
  if (state->config.ipc_socket_path[0] != '\0') {
298
    unlink(state->config.ipc_socket_path);
299
  }
300
301
  chroma_log("DEBUG", "IPC socket cleaned up");
302
}
303
304
int chroma_ipc_get_fd(chroma_state_t *state) {
305
  if (!state || state->ipc_fd < 0) {
306
    return -1;
307
  }
308
  return state->ipc_fd;
309
}
310
311
int chroma_ipc_process(chroma_state_t *state) {
312
  if (!state || state->ipc_fd < 0) {
313
    return CHROMA_OK;
314
  }
315
316
  int client_fd = accept(state->ipc_fd, NULL, NULL);
317
  if (client_fd < 0) {
318
    if (errno == EAGAIN || errno == EWOULDBLOCK) {
319
      return CHROMA_OK;
320
    }
321
    chroma_log("ERROR", "IPC accept failed: %s", strerror(errno));
322
    return CHROMA_OK;
323
  }
324
325
  char buffer[IPC_MAX_MSG];
326
  memset(buffer, 0, sizeof(buffer));
327
328
  ssize_t n = read(client_fd, buffer, sizeof(buffer) - 1);
329
  if (n <= 0) {
330
    close(client_fd);
331
    return CHROMA_OK;
332
  }
333
334
  buffer[n] = '\0';
335
336
  size_t len = strlen(buffer);
337
  while (len > 0 && (buffer[len - 1] == '\n' || buffer[len - 1] == '\r')) {
338
    buffer[--len] = '\0';
339
  }
340
341
  handle_command(state, client_fd, buffer);
342
343
  close(client_fd);
344
  return CHROMA_OK;
345
}
346
347
int chroma_set_wallpaper_for_output(chroma_state_t *state,
348
                                    const char *output_name,
349
                                    const char *image_path) {
350
  if (!state || !output_name || !image_path) {
351
    return CHROMA_ERROR_INIT;
352
  }
353
354
  char *expanded = chroma_expand_path(image_path);
355
  const char *path = expanded ? expanded : image_path;
356
357
  if (!chroma_path_exists(path)) {
358
    chroma_log("ERROR", "Image not found: %s", path);
359
    if (expanded)
360
      free(expanded);
361
    return CHROMA_ERROR_IMAGE;
362
  }
363
364
  chroma_output_t *output = chroma_output_find_by_name(state, output_name);
365
  if (!output) {
366
    chroma_log("ERROR", "Output not found: %s", output_name);
367
    if (expanded)
368
      free(expanded);
369
    return CHROMA_ERROR_INIT;
370
  }
371
372
  chroma_image_t *image =
373
      chroma_image_get_or_load(state, path, output->width, output->height);
374
  if (!image) {
375
    chroma_log("ERROR", "Failed to load image for output %s: %s", output_name,
376
               path);
377
    if (expanded)
378
      free(expanded);
379
    return CHROMA_ERROR_IMAGE;
380
  }
381
382
  if (output->image) {
383
    chroma_image_release(output->image);
384
  }
385
  output->image = image;
386
387
  if (state->config.transition_enabled) {
388
    chroma_transition_init_output(output, state->config.transition_duration_ms);
389
  }
390
391
  if (!output->surface) {
392
    int ret = chroma_surface_create(state, output);
393
    if (ret != CHROMA_OK) {
394
      chroma_log("ERROR", "Failed to create surface for output %s",
395
                 output_name);
396
      if (expanded)
397
        free(expanded);
398
      return ret;
399
    }
400
  }
401
402
  int ret = chroma_render_wallpaper(state, output);
403
  if (ret != CHROMA_OK) {
404
    chroma_log("ERROR", "Failed to render wallpaper for output %s",
405
               output_name);
406
    if (expanded)
407
      free(expanded);
408
    return ret;
409
  }
410
411
  if (expanded)
412
    free(expanded);
413
414
  chroma_log("INFO", "Set wallpaper for output %s: %s", output_name, path);
415
  return CHROMA_OK;
416
}
417
418
int chroma_set_wallpaper_for_all(chroma_state_t *state,
419
                                 const char *image_path) {
420
  if (!state || !image_path) {
421
    return CHROMA_ERROR_INIT;
422
  }
423
424
  char *expanded = chroma_expand_path(image_path);
425
  const char *path = expanded ? expanded : image_path;
426
427
  if (!chroma_path_exists(path)) {
428
    chroma_log("ERROR", "Image not found: %s", path);
429
    if (expanded)
430
      free(expanded);
431
    return CHROMA_ERROR_IMAGE;
432
  }
433
434
  int success = 0;
435
  for (int i = 0; i < state->output_count; i++) {
436
    chroma_output_t *output = &state->outputs[i];
437
    if (!output->active)
438
      continue;
439
440
    if (chroma_set_wallpaper_for_output(state,
441
                                        output->name ? output->name : "unknown",
442
                                        path) == CHROMA_OK) {
443
      success++;
444
    }
445
  }
446
447
  if (expanded)
448
    free(expanded);
449
450
  return (success > 0) ? CHROMA_OK : CHROMA_ERROR_IMAGE;
451
}
452
453
int chroma_set_solid_color_for_output(chroma_state_t *state,
454
                                      const char *output_name,
455
                                      const char *hex_color) {
456
  if (!state || !output_name) {
457
    return CHROMA_ERROR_INIT;
458
  }
459
460
  bool valid = false;
461
  uint32_t color = parse_hex_color(hex_color, &valid);
462
463
  chroma_output_t *output = chroma_output_find_by_name(state, output_name);
464
  if (!output) {
465
    chroma_log("ERROR", "Output not found: %s", output_name);
466
    return CHROMA_ERROR_INIT;
467
  }
468
469
  chroma_image_t *img = NULL;
470
  char color_path[MAX_PATH_LEN];
471
  snprintf(color_path, sizeof(color_path), "color:#%08X", color);
472
  img = chroma_image_find_by_path(state, color_path);
473
  if (img) {
474
    img->ref_count++;
475
    chroma_log("DEBUG", "Reusing solid color image: %s", color_path);
476
  } else {
477
    for (int i = 0; i < state->image_count; i++) {
478
      if (!state->images[i].loaded) {
479
        img = &state->images[i];
480
        break;
481
      }
482
    }
483
    if (!img) {
484
      if (state->image_count >= MAX_OUTPUTS) {
485
        chroma_log("ERROR", "Maximum number of images reached (%d)",
486
                   MAX_OUTPUTS);
487
        return CHROMA_ERROR_MEMORY;
488
      }
489
      img = &state->images[state->image_count];
490
      state->image_count++;
491
    }
492
    int load_ret =
493
        chroma_image_load_color(img, color, output->width, output->height);
494
    if (load_ret != CHROMA_OK) {
495
      return load_ret;
496
    }
497
  }
498
499
  int ret;
500
501
  if (output->image) {
502
    chroma_image_release(output->image);
503
  }
504
  output->image = img;
505
506
  if (state->config.transition_enabled) {
507
    chroma_transition_init_output(output, state->config.transition_duration_ms);
508
  }
509
510
  if (!output->surface) {
511
    ret = chroma_surface_create(state, output);
512
    if (ret != CHROMA_OK)
513
      return ret;
514
  }
515
516
  ret = chroma_render_wallpaper(state, output);
517
  if (ret != CHROMA_OK)
518
    return ret;
519
520
  chroma_log("INFO", "Set solid color for output %s: %s", output_name,
521
             hex_color ? hex_color : "none");
522
  return CHROMA_OK;
523
}
524
525
int chroma_set_solid_color_for_all(chroma_state_t *state,
526
                                   const char *hex_color) {
527
  if (!state) {
528
    return CHROMA_ERROR_INIT;
529
  }
530
531
  int success = 0;
532
  for (int i = 0; i < state->output_count; i++) {
533
    chroma_output_t *output = &state->outputs[i];
534
    if (!output->active)
535
      continue;
536
537
    if (chroma_set_solid_color_for_output(
538
            state, output->name ? output->name : "unknown", hex_color) ==
539
        CHROMA_OK) {
540
      success++;
541
    }
542
  }
543
544
  return (success > 0) ? CHROMA_OK : CHROMA_ERROR_IMAGE;
545
}