brewery
notashelf /
50c41fa883aee07beef62e361bd7b93c792a1f99

chroma

public

Lightweight wallpaper daemon for Wayland

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