brewery
notashelf /
786389000243517c774749f96ce9985958f891ad

chroma

public

Lightweight wallpaper daemon for Wayland

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