brewery
notashelf /
172c36ba42b26c85d310f1e55773d63642061ada

chroma

public

Lightweight wallpaper daemon for Wayland

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/chroma.git
src/core.cC629 lines17.7 KB
1
#include <errno.h>
2
#include <stdarg.h>
3
#include <stdio.h>
4
#include <stdlib.h>
5
#include <string.h>
6
#include <sys/time.h>
7
#include <time.h>
8
#include <unistd.h>
9
10
#include "../include/chroma.h"
11
12
// Global logging level (using CHROMA_LOG_* constants)
13
static int log_level = CHROMA_LOG_WARN; // Default to WARN and ERROR only
14
15
// Initialize chroma state
16
int chroma_init(chroma_state_t *state) {
17
  if (!state) {
18
    return CHROMA_ERROR_INIT;
19
  }
20
21
  // Initialize all fields to zero
22
  memset(state, 0, sizeof(chroma_state_t));
23
  state->ipc_fd = -1;
24
25
  // Set initial state
26
  state->running = false;
27
  state->initialized = false;
28
29
  // Initialize stb_image
30
  chroma_image_init_stb();
31
32
  state->initialized = true;
33
  chroma_log("INFO", "Chroma state initialized");
34
  chroma_log_resource_allocation("chroma_state", sizeof(chroma_state_t),
35
                                 "main application state");
36
37
  return CHROMA_OK;
38
}
39
40
// Cleanup chroma state
41
void chroma_cleanup(chroma_state_t *state) {
42
  if (!state || !state->initialized) {
43
    return;
44
  }
45
46
  chroma_log("INFO", "Cleaning up chroma state");
47
  chroma_log_memory_stats("pre-cleanup");
48
49
  // Stop the main loop
50
  state->running = false;
51
52
  // Clean up all images
53
  chroma_images_cleanup(state);
54
55
  // Clean up Wayland
56
  chroma_wayland_disconnect(state);
57
58
  // Clean up configuration
59
  chroma_config_free(&state->config);
60
61
  state->initialized = false;
62
  chroma_log_resource_deallocation("chroma_state", sizeof(chroma_state_t),
63
                                   "main application state");
64
  chroma_log("INFO", "Chroma cleanup complete");
65
}
66
67
// Assign wallpaper to an output
68
static int assign_wallpaper_to_output(chroma_state_t *state,
69
                                      chroma_output_t *output) {
70
  if (!state || !output || !output->active) {
71
    return CHROMA_ERROR_INIT;
72
  }
73
74
  // Get image path for this output
75
  const char *image_path = chroma_config_get_image_for_output(
76
      &state->config, output->name ? output->name : "unknown",
77
      output->description);
78
  if (!image_path) {
79
    chroma_log("WARN", "No wallpaper configured for output %u (%s)", output->id,
80
               output->name ? output->name : "unknown");
81
    return CHROMA_ERROR_CONFIG;
82
  }
83
84
  // Check if image path is empty (no default configured)
85
  if (strlen(image_path) == 0) {
86
    chroma_log("WARN",
87
               "No wallpaper image configured for output %u (%s). "
88
               "Set default_image in config or provide -c config.toml",
89
               output->id, output->name ? output->name : "unknown");
90
    return CHROMA_ERROR_CONFIG;
91
  }
92
93
  // Load or get cached image with output dimensions for intelligent
94
  // downsampling
95
  chroma_image_t *image = chroma_image_get_or_load(
96
      state, image_path, output->width, output->height);
97
  if (!image) {
98
    chroma_log("ERROR", "Failed to load image for output %u: %s", output->id,
99
               image_path);
100
    return CHROMA_ERROR_IMAGE;
101
  }
102
103
  // Check if image changed
104
  bool image_changed = (output->image != image);
105
  if (image_changed && output->image) {
106
    chroma_image_release(output->image);
107
    chroma_log("DEBUG", "Image changed for output %u", output->id);
108
  }
109
110
  // Assign image to output
111
  output->image = image;
112
113
  // Store old configuration values for comparison
114
  chroma_scale_mode_t old_scale_mode = output->scale_mode;
115
  chroma_filter_quality_t old_filter_quality = output->filter_quality;
116
  chroma_anchor_t old_anchor = output->anchor;
117
  float old_anchor_x = output->anchor_x;
118
  float old_anchor_y = output->anchor_y;
119
  bool had_config = output->config_loaded;
120
121
  // Load configuration for this output (scale mode, filter quality, anchor,
122
  // anchor coords)
123
  if (chroma_config_get_mapping_for_output(
124
          &state->config, output->name ? output->name : "unknown",
125
          output->description, &output->scale_mode, &output->filter_quality,
126
          &output->anchor, &output->anchor_x, &output->anchor_y) == CHROMA_OK) {
127
    output->config_loaded = true;
128
    chroma_log("DEBUG",
129
               "Loaded config for output %u: scale=%d, filter=%d, anchor=%d @ "
130
               "%.1f,%.1f",
131
               output->id, output->scale_mode, output->filter_quality,
132
               output->anchor, (double)output->anchor_x,
133
               (double)output->anchor_y);
134
135
    // Check if configuration changed
136
    if (had_config &&
137
        (old_scale_mode != output->scale_mode ||
138
         old_filter_quality != output->filter_quality ||
139
         old_anchor != output->anchor || old_anchor_x != output->anchor_x ||
140
         old_anchor_y != output->anchor_y)) {
141
      chroma_log("DEBUG", "Configuration changed for output %u", output->id);
142
    }
143
  } else {
144
    output->config_loaded = false;
145
    chroma_log("WARN", "Failed to load config for output %u", output->id);
146
  }
147
148
  // Create surface if it doesn't exist
149
  if (!output->surface) {
150
    int ret = chroma_surface_create(state, output);
151
    if (ret != CHROMA_OK) {
152
      chroma_log("ERROR", "Failed to create surface for output %u", output->id);
153
      return ret;
154
    }
155
  }
156
157
  // Render wallpaper
158
  int ret = chroma_render_wallpaper(state, output);
159
  if (ret != CHROMA_OK) {
160
    chroma_log("ERROR", "Failed to render wallpaper for output %u: %s",
161
               output->id, chroma_error_string(ret));
162
    return ret;
163
  }
164
165
  chroma_log("INFO", "Assigned wallpaper to output %u (%s): %s", output->id,
166
             output->name ? output->name : "unknown", image_path);
167
  chroma_log_memory_stats("post-wallpaper-assignment");
168
169
  return CHROMA_OK;
170
}
171
172
// Assign wallpapers to all active outputs
173
static int assign_wallpapers_to_all_outputs(chroma_state_t *state) {
174
  if (!state) {
175
    return CHROMA_ERROR_INIT;
176
  }
177
178
  int success_count = 0;
179
  int error_count = 0;
180
181
  for (int i = 0; i < state->output_count; i++) {
182
    chroma_output_t *output = &state->outputs[i];
183
184
    if (!output->active) {
185
      chroma_log("DEBUG", "Skipping inactive output %u", output->id);
186
      continue;
187
    }
188
189
    if (assign_wallpaper_to_output(state, output) == CHROMA_OK) {
190
      success_count++;
191
    } else {
192
      error_count++;
193
    }
194
  }
195
196
  chroma_log("INFO", "Wallpaper assignment complete: %d success, %d errors",
197
             success_count, error_count);
198
199
  return (success_count > 0) ? CHROMA_OK : CHROMA_ERROR_IMAGE;
200
}
201
202
// Handle output configuration complete event
203
void handle_output_done(chroma_state_t *state, chroma_output_t *output) {
204
  if (!state || !output) {
205
    return;
206
  }
207
208
  chroma_log("INFO",
209
             "Output %u (%s) configuration complete: %dx%d@%d, scale=%d",
210
             output->id, output->name ? output->name : "unknown", output->width,
211
             output->height, 0, output->scale);
212
213
  /* Assign wallpaper to this output */
214
  if (assign_wallpaper_to_output(state, output) != CHROMA_OK) {
215
    chroma_log("ERROR", "Failed to assign wallpaper to output %u", output->id);
216
  }
217
}
218
219
// Process Wayland events
220
static int process_wayland_events(chroma_state_t *state) {
221
  if (!state || !state->display) {
222
    return CHROMA_ERROR_WAYLAND;
223
  }
224
225
  // Dispatch pending events
226
  if (wl_display_dispatch_pending(state->display) == -1) {
227
    chroma_log("ERROR", "Failed to dispatch pending Wayland events: %s",
228
               strerror(errno));
229
    return CHROMA_ERROR_WAYLAND;
230
  }
231
232
  // Read events from the server
233
  if (wl_display_read_events(state->display) == -1) {
234
    chroma_log("ERROR", "Failed to read Wayland events: %s", strerror(errno));
235
    return CHROMA_ERROR_WAYLAND;
236
  }
237
238
  // Dispatch the read events
239
  if (wl_display_dispatch_pending(state->display) == -1) {
240
    chroma_log("ERROR", "Failed to dispatch read Wayland events: %s",
241
               strerror(errno));
242
    return CHROMA_ERROR_WAYLAND;
243
  }
244
245
  return CHROMA_OK;
246
}
247
248
// Main event loop
249
int chroma_run(chroma_state_t *state) {
250
  if (!state || !state->initialized) {
251
    return CHROMA_ERROR_INIT;
252
  }
253
254
  chroma_log("INFO", "Starting main event loop");
255
  chroma_log_memory_stats("main-loop-start");
256
  state->running = true;
257
258
  // Initial wallpaper assignment
259
  chroma_log("INFO", "Performing initial wallpaper assignment");
260
  assign_wallpapers_to_all_outputs(state);
261
262
  int ipc_fd = chroma_ipc_get_fd(state);
263
  bool has_anim = chroma_anim_needs_redraw(state);
264
  bool has_transition = chroma_transition_needs_redraw(state);
265
266
  // Main event loop
267
  while (state->running && !chroma_should_quit) {
268
    // Dispatch any pending events first
269
    if (wl_display_dispatch_pending(state->display) == -1) {
270
      chroma_log("ERROR", "Failed to dispatch pending events: %s",
271
                 strerror(errno));
272
      break;
273
    }
274
275
    // Handle animation frame updates
276
    if (has_anim) {
277
      chroma_anim_update_outputs(state);
278
      has_anim = chroma_anim_needs_redraw(state);
279
    }
280
281
    // Handle transition frame updates
282
    if (has_transition) {
283
      chroma_transition_update_outputs(state);
284
      has_transition = chroma_transition_needs_redraw(state);
285
    }
286
287
    // Prepare to read events
288
    if (wl_display_prepare_read(state->display) == -1) {
289
      chroma_log("ERROR", "Failed to prepare Wayland display for reading");
290
      break;
291
    }
292
293
    // Flush outgoing requests
294
    if (wl_display_flush(state->display) == -1) {
295
      chroma_log("ERROR", "Failed to flush Wayland display: %s",
296
                 strerror(errno));
297
      wl_display_cancel_read(state->display);
298
      break;
299
    }
300
301
    // Get the display file descriptor
302
    int fd = wl_display_get_fd(state->display);
303
    if (fd == -1) {
304
      chroma_log("ERROR", "Failed to get Wayland display file descriptor");
305
      wl_display_cancel_read(state->display);
306
      break;
307
    }
308
309
    // Build fd_set for select
310
    fd_set readfds;
311
    FD_ZERO(&readfds);
312
    FD_SET(fd, &readfds);
313
314
    int max_fd = fd;
315
    if (ipc_fd >= 0) {
316
      FD_SET(ipc_fd, &readfds);
317
      if (ipc_fd > max_fd)
318
        max_fd = ipc_fd;
319
    }
320
321
    // Use a short timeout when animations or transitions are active
322
    struct timeval timeout;
323
    if (has_anim || has_transition) {
324
      timeout.tv_sec = 0;
325
      timeout.tv_usec = 16000; // ~60fps
326
    } else {
327
      timeout.tv_sec = 10;
328
      timeout.tv_usec = 0;
329
    }
330
331
    int select_result = select(max_fd + 1, &readfds, NULL, NULL, &timeout);
332
333
    if (select_result == -1) {
334
      if (errno == EINTR) {
335
        wl_display_cancel_read(state->display);
336
        continue;
337
      }
338
      chroma_log("ERROR", "select() failed: %s", strerror(errno));
339
      wl_display_cancel_read(state->display);
340
      break;
341
    }
342
343
    if (select_result == 0) {
344
      // Timeout - check for animation/transition needs
345
      wl_display_cancel_read(state->display);
346
      has_anim = chroma_anim_needs_redraw(state);
347
      has_transition = chroma_transition_needs_redraw(state);
348
      continue;
349
    }
350
351
    // Handle IPC events
352
    if (ipc_fd >= 0 && FD_ISSET(ipc_fd, &readfds)) {
353
      chroma_ipc_process(state);
354
      select_result--;
355
    }
356
357
    // Handle Wayland events
358
    if (select_result > 0 && FD_ISSET(fd, &readfds)) {
359
      if (process_wayland_events(state) != CHROMA_OK) {
360
        break;
361
      }
362
    } else if (select_result > 0) {
363
      wl_display_cancel_read(state->display);
364
    }
365
  }
366
367
  state->running = false;
368
  chroma_log("INFO", "Main event loop ended");
369
370
  return CHROMA_OK;
371
}
372
373
// Error code to string conversion
374
const char *chroma_error_string(chroma_error_t error) {
375
  switch (error) {
376
  case CHROMA_OK:
377
    return "Success";
378
  case CHROMA_ERROR_INIT:
379
    return "Initialization error";
380
  case CHROMA_ERROR_WAYLAND:
381
    return "Wayland error";
382
  case CHROMA_ERROR_EGL:
383
    return "EGL error";
384
  case CHROMA_ERROR_IMAGE:
385
    return "Image loading error";
386
  case CHROMA_ERROR_CONFIG:
387
    return "Configuration error";
388
  case CHROMA_ERROR_MEMORY:
389
    return "Memory allocation error";
390
  default:
391
    return "Unknown error";
392
  }
393
}
394
395
// Convert log level string to numeric level
396
static int log_level_from_string(const char *level) {
397
  if (strcmp(level, "ERROR") == 0)
398
    return CHROMA_LOG_ERROR;
399
  if (strcmp(level, "WARN") == 0)
400
    return CHROMA_LOG_WARN;
401
  if (strcmp(level, "INFO") == 0)
402
    return CHROMA_LOG_INFO;
403
  if (strcmp(level, "DEBUG") == 0)
404
    return CHROMA_LOG_DEBUG;
405
  if (strcmp(level, "TRACE") == 0)
406
    return CHROMA_LOG_TRACE;
407
  return CHROMA_LOG_ERROR; // default to ERROR for unknown levels
408
}
409
410
// Logging function with level filtering
411
void chroma_log(const char *level, const char *format, ...) {
412
  int msg_level = log_level_from_string(level);
413
  if (msg_level > log_level) {
414
    return;
415
  }
416
417
  va_list args;
418
  char timestamp[32];
419
  int truncation_check = 0;
420
  struct timeval tv;
421
  struct tm *tm_info;
422
423
  gettimeofday(&tv, NULL);
424
  tm_info = localtime(&tv.tv_sec);
425
426
  truncation_check =
427
      snprintf(timestamp, sizeof(timestamp),
428
               "%04d-%02d-%02d %02d:%02d:%02d.%03d", tm_info->tm_year + 1900,
429
               tm_info->tm_mon + 1, tm_info->tm_mday, tm_info->tm_hour,
430
               tm_info->tm_min, tm_info->tm_sec, (int)(tv.tv_usec / 1000));
431
432
  if (truncation_check > 32 || truncation_check < 0) {
433
    // Something went seriously wrong with the snprintf, this is a fairly
434
    // serious error as the timestamp may be incomplete or corrupted, so print a
435
    // warning
436
    printf("Following timestamp may be incomplete, truncated or corrupted!\n");
437
  }
438
  printf("[%s] %s: ", timestamp, level);
439
440
  va_start(args, format);
441
  vprintf(format, args);
442
  va_end(args);
443
444
  printf("\n");
445
  fflush(stdout);
446
}
447
448
// Set log level
449
void chroma_set_log_level(int level) { log_level = level; }
450
451
// Get log level
452
int chroma_get_log_level(void) { return log_level; }
453
454
// Handle configuration reload (SIGHUP)
455
int chroma_reload_config(chroma_state_t *state, const char *config_file) {
456
  if (!state) {
457
    return CHROMA_ERROR_INIT;
458
  }
459
460
  chroma_log("INFO", "Reloading configuration");
461
462
  // Free current configuration
463
  chroma_config_free(&state->config);
464
465
  // Load new configuration
466
  int ret = chroma_config_load(&state->config, config_file);
467
  if (ret != CHROMA_OK) {
468
    chroma_log("ERROR", "Failed to reload configuration: %s",
469
               chroma_error_string(ret));
470
    return ret;
471
  }
472
473
  // Reassign wallpapers with new configuration
474
  ret = assign_wallpapers_to_all_outputs(state);
475
  if (ret != CHROMA_OK) {
476
    chroma_log("ERROR", "Failed to reassign wallpapers after config reload");
477
    return ret;
478
  }
479
480
  chroma_log("INFO", "Configuration reloaded successfully");
481
  return CHROMA_OK;
482
}
483
484
// Check if an output needs wallpaper update
485
static bool output_needs_update(chroma_output_t *output) {
486
  if (!output || !output->active) {
487
    return false;
488
  }
489
490
  // Check if output has no surface or image assigned
491
  if (!output->surface || !output->image) {
492
    return true;
493
  }
494
495
  // Check if image is no longer loaded
496
  if (!output->image->loaded) {
497
    return true;
498
  }
499
500
  return false;
501
}
502
503
// Update outputs that need wallpaper refresh
504
int chroma_update_outputs(chroma_state_t *state) {
505
  if (!state) {
506
    return CHROMA_ERROR_INIT;
507
  }
508
509
  int updated_count = 0;
510
511
  for (int i = 0; i < state->output_count; i++) {
512
    chroma_output_t *output = &state->outputs[i];
513
514
    if (output_needs_update(output)) {
515
      if (assign_wallpaper_to_output(state, output) == CHROMA_OK) {
516
        updated_count++;
517
      }
518
    }
519
  }
520
521
  if (updated_count > 0) {
522
    chroma_log("INFO", "Updated wallpapers for %d outputs", updated_count);
523
  }
524
525
  return CHROMA_OK;
526
}
527
528
// Get statistics
529
void chroma_get_stats(chroma_state_t *state, int *active_outputs,
530
                      int *loaded_images) {
531
  if (!state) {
532
    if (active_outputs)
533
      *active_outputs = 0;
534
    if (loaded_images)
535
      *loaded_images = 0;
536
    return;
537
  }
538
539
  int active = 0;
540
  for (int i = 0; i < state->output_count; i++) {
541
    if (state->outputs[i].active) {
542
      active++;
543
    }
544
  }
545
546
  int loaded = 0;
547
  for (int i = 0; i < state->image_count; i++) {
548
    if (state->images[i].loaded) {
549
      loaded++;
550
    }
551
  }
552
553
  if (active_outputs)
554
    *active_outputs = active;
555
  if (loaded_images)
556
    *loaded_images = loaded;
557
}
558
559
// Get current memory usage from /proc/self/status
560
// FIXME: some users may choose to confine /proc, we need a cleaner
561
// way of getting this data in the future
562
size_t chroma_get_memory_usage(void) {
563
  FILE *file = fopen("/proc/self/status", "r");
564
  if (!file) {
565
    return 0;
566
  }
567
568
  size_t vm_rss = 0;
569
  char line[256];
570
571
  while (fgets(line, sizeof(line), file)) {
572
    if (strncmp(line, "VmRSS:", 6) == 0) {
573
      // Parse VmRSS line: "VmRSS:    1234 kB"
574
      char *ptr = line + 6;
575
      while (*ptr == ' ' || *ptr == '\t')
576
        ptr++;                                        // Skip whitespace
577
      vm_rss = (size_t)strtoul(ptr, NULL, 10) * 1024; // Convert kB to bytes
578
      break;
579
    }
580
  }
581
582
  fclose(file);
583
  return vm_rss;
584
}
585
586
// Log memory statistics with context
587
void chroma_log_memory_stats(const char *context) {
588
  size_t memory_usage = chroma_get_memory_usage();
589
  char mem_str[64];
590
591
  chroma_format_memory_size(memory_usage, mem_str, sizeof(mem_str));
592
  chroma_log("INFO", "Memory usage [%s]: %s", context, mem_str);
593
}
594
595
// Log resource allocation
596
void chroma_log_resource_allocation(const char *resource_type, size_t size,
597
                                    const char *description) {
598
  if (size > 0) {
599
    char size_str[64];
600
    chroma_format_memory_size(size, size_str, sizeof(size_str));
601
    chroma_log("DEBUG", "Allocated %s: %s (%s)", resource_type, size_str,
602
               description);
603
  } else {
604
    chroma_log("DEBUG", "Allocated %s: %s", resource_type, description);
605
  }
606
607
  // Log memory stats after significant allocations (>1MB)
608
  if (size > 1024 * 1024) {
609
    chroma_log_memory_stats("post-allocation");
610
  }
611
}
612
613
// Log resource deallocation
614
void chroma_log_resource_deallocation(const char *resource_type, size_t size,
615
                                      const char *description) {
616
  if (size > 0) {
617
    char size_str[64];
618
    chroma_format_memory_size(size, size_str, sizeof(size_str));
619
    chroma_log("DEBUG", "Deallocated %s: %s (%s)", resource_type, size_str,
620
               description);
621
  } else {
622
    chroma_log("DEBUG", "Deallocated %s: %s", resource_type, description);
623
  }
624
625
  // Log memory stats after significant deallocations (>1MB)
626
  if (size > 1024 * 1024) {
627
    chroma_log_memory_stats("post-deallocation");
628
  }
629
}