brewery
notashelf /
7ccb21af79777e31a67a668f2efc18d7be603241

chroma

public

Lightweight wallpaper daemon for Wayland

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