brewery
notashelf /
23527908c28dc5cc0890411240419c1f5183db35

chroma

public

Lightweight wallpaper daemon for Wayland

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