brewery
notashelf /
237a013e0331cc6f497559079d17f4659ceecd8e

chroma

public

Lightweight wallpaper daemon for Wayland

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