brewery
notashelf /
b6780cc1804075f6863dcf0a370a5bf645082014

chroma

public

Lightweight wallpaper daemon for Wayland

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