brewery
notashelf /
ca468ce677b3f6c584b3483120f79f8ed58c9e96

chroma

public

Lightweight wallpaper daemon for Wayland

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