brewery
notashelf /
889bd0a44f903c6418e76cd5ca860a1c1b928819

chroma

public

Lightweight wallpaper daemon for Wayland

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