brewery
notashelf /
ecff681d373bd0973a027d43b1fe36fb2ab15059

chroma

public

Lightweight wallpaper daemon for Wayland

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/chroma.git
src/core.cC569 lines15.6 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
  struct timeval tv;
368
  struct tm *tm_info;
369
370
  gettimeofday(&tv, NULL);
371
  tm_info = localtime(&tv.tv_sec);
372
373
  snprintf(timestamp, sizeof(timestamp), "%04d-%02d-%02d %02d:%02d:%02d.%03d",
374
           tm_info->tm_year + 1900, tm_info->tm_mon + 1, tm_info->tm_mday,
375
           tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec,
376
           (int)(tv.tv_usec / 1000));
377
378
  printf("[%s] %s: ", timestamp, level);
379
380
  va_start(args, format);
381
  vprintf(format, args);
382
  va_end(args);
383
384
  printf("\n");
385
  fflush(stdout);
386
}
387
388
// Set log level
389
void chroma_set_log_level(int level) { log_level = level; }
390
391
// Get log level
392
int chroma_get_log_level(void) { return log_level; }
393
394
// Handle configuration reload (SIGHUP)
395
int chroma_reload_config(chroma_state_t *state, const char *config_file) {
396
  if (!state) {
397
    return CHROMA_ERROR_INIT;
398
  }
399
400
  chroma_log("INFO", "Reloading configuration");
401
402
  // Free current configuration
403
  chroma_config_free(&state->config);
404
405
  // Load new configuration
406
  int ret = chroma_config_load(&state->config, config_file);
407
  if (ret != CHROMA_OK) {
408
    chroma_log("ERROR", "Failed to reload configuration: %s",
409
               chroma_error_string(ret));
410
    return ret;
411
  }
412
413
  // Reassign wallpapers with new configuration
414
  ret = assign_wallpapers_to_all_outputs(state);
415
  if (ret != CHROMA_OK) {
416
    chroma_log("ERROR", "Failed to reassign wallpapers after config reload");
417
    return ret;
418
  }
419
420
  chroma_log("INFO", "Configuration reloaded successfully");
421
  return CHROMA_OK;
422
}
423
424
// Check if an output needs wallpaper update
425
static bool output_needs_update(chroma_output_t *output) {
426
  if (!output || !output->active) {
427
    return false;
428
  }
429
430
  // Check if output has no surface or image assigned
431
  if (!output->surface || !output->image) {
432
    return true;
433
  }
434
435
  // Check if image is no longer loaded
436
  if (!output->image->loaded) {
437
    return true;
438
  }
439
440
  return false;
441
}
442
443
// Update outputs that need wallpaper refresh
444
int chroma_update_outputs(chroma_state_t *state) {
445
  if (!state) {
446
    return CHROMA_ERROR_INIT;
447
  }
448
449
  int updated_count = 0;
450
451
  for (int i = 0; i < state->output_count; i++) {
452
    chroma_output_t *output = &state->outputs[i];
453
454
    if (output_needs_update(output)) {
455
      if (assign_wallpaper_to_output(state, output) == CHROMA_OK) {
456
        updated_count++;
457
      }
458
    }
459
  }
460
461
  if (updated_count > 0) {
462
    chroma_log("INFO", "Updated wallpapers for %d outputs", updated_count);
463
  }
464
465
  return CHROMA_OK;
466
}
467
468
// Get statistics
469
void chroma_get_stats(chroma_state_t *state, int *active_outputs,
470
                      int *loaded_images) {
471
  if (!state) {
472
    if (active_outputs)
473
      *active_outputs = 0;
474
    if (loaded_images)
475
      *loaded_images = 0;
476
    return;
477
  }
478
479
  int active = 0;
480
  for (int i = 0; i < state->output_count; i++) {
481
    if (state->outputs[i].active) {
482
      active++;
483
    }
484
  }
485
486
  int loaded = 0;
487
  for (int i = 0; i < state->image_count; i++) {
488
    if (state->images[i].loaded) {
489
      loaded++;
490
    }
491
  }
492
493
  if (active_outputs)
494
    *active_outputs = active;
495
  if (loaded_images)
496
    *loaded_images = loaded;
497
}
498
499
// Get current memory usage from /proc/self/status
500
// FIXME: some users may choose to confine /proc, we need a cleaner
501
// way of getting this data in the future
502
size_t chroma_get_memory_usage(void) {
503
  FILE *file = fopen("/proc/self/status", "r");
504
  if (!file) {
505
    return 0;
506
  }
507
508
  size_t vm_rss = 0;
509
  char line[256];
510
511
  while (fgets(line, sizeof(line), file)) {
512
    if (strncmp(line, "VmRSS:", 6) == 0) {
513
      // Parse VmRSS line: "VmRSS:    1234 kB"
514
      char *ptr = line + 6;
515
      while (*ptr == ' ' || *ptr == '\t')
516
        ptr++;                                        // Skip whitespace
517
      vm_rss = (size_t)strtoul(ptr, NULL, 10) * 1024; // Convert kB to bytes
518
      break;
519
    }
520
  }
521
522
  fclose(file);
523
  return vm_rss;
524
}
525
526
// Log memory statistics with context
527
void chroma_log_memory_stats(const char *context) {
528
  size_t memory_usage = chroma_get_memory_usage();
529
  char mem_str[64];
530
531
  chroma_format_memory_size(memory_usage, mem_str, sizeof(mem_str));
532
  chroma_log("INFO", "Memory usage [%s]: %s", context, mem_str);
533
}
534
535
// Log resource allocation
536
void chroma_log_resource_allocation(const char *resource_type, size_t size,
537
                                    const char *description) {
538
  if (size > 0) {
539
    char size_str[64];
540
    chroma_format_memory_size(size, size_str, sizeof(size_str));
541
    chroma_log("DEBUG", "Allocated %s: %s (%s)", resource_type, size_str,
542
               description);
543
  } else {
544
    chroma_log("DEBUG", "Allocated %s: %s", resource_type, description);
545
  }
546
547
  // Log memory stats after significant allocations (>1MB)
548
  if (size > 1024 * 1024) {
549
    chroma_log_memory_stats("post-allocation");
550
  }
551
}
552
553
// Log resource deallocation
554
void chroma_log_resource_deallocation(const char *resource_type, size_t size,
555
                                      const char *description) {
556
  if (size > 0) {
557
    char size_str[64];
558
    chroma_format_memory_size(size, size_str, sizeof(size_str));
559
    chroma_log("DEBUG", "Deallocated %s: %s (%s)", resource_type, size_str,
560
               description);
561
  } else {
562
    chroma_log("DEBUG", "Deallocated %s: %s", resource_type, description);
563
  }
564
565
  // Log memory stats after significant deallocations (>1MB)
566
  if (size > 1024 * 1024) {
567
    chroma_log_memory_stats("post-deallocation");
568
  }
569
}