brewery
notashelf /
c9d32e14ab93f00829803348cdbb8f52b717531a

chroma

public

Lightweight wallpaper daemon for Wayland

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