brewery
notashelf /
308c82f479ec29a9681e1702478bf2123fcaf0d5

chroma

public

Lightweight wallpaper daemon for Wayland

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