brewery
notashelf /
e72da82b32d46daf27c4ff663e5f918058b65fd7

chroma

public

Lightweight wallpaper daemon for Wayland

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/chroma.git
src/core.cC447 lines11.3 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
13
static int log_level = 0; // 0=ERROR, 1=WARN, 2=INFO, 3=DEBUG
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
36
  return CHROMA_OK;
37
}
38
39
// Cleanup chroma state
40
void chroma_cleanup(chroma_state_t *state) {
41
  if (!state || !state->initialized) {
42
    return;
43
  }
44
45
  chroma_log("INFO", "Cleaning up chroma state");
46
47
  // Stop the main loop
48
  state->running = false;
49
50
  // Clean up all images
51
  chroma_images_cleanup(state);
52
53
  // Clean up EGL
54
  chroma_egl_cleanup(state);
55
56
  // Clean up Wayland
57
  chroma_wayland_disconnect(state);
58
59
  // Clean up configuration
60
  chroma_config_free(&state->config);
61
62
  state->initialized = false;
63
  chroma_log("INFO", "Chroma cleanup complete");
64
}
65
66
// Assign wallpaper to an output
67
static int assign_wallpaper_to_output(chroma_state_t *state,
68
                                      chroma_output_t *output) {
69
  if (!state || !output || !output->active) {
70
    return CHROMA_ERROR_INIT;
71
  }
72
73
  // Get image path for this output
74
  const char *image_path = chroma_config_get_image_for_output(
75
      &state->config, output->name ? output->name : "unknown");
76
  if (!image_path) {
77
    chroma_log("WARN", "No wallpaper configured for output %u (%s)", output->id,
78
               output->name ? output->name : "unknown");
79
    return CHROMA_ERROR_CONFIG;
80
  }
81
82
  // Load or get cached image
83
  chroma_image_t *image = chroma_image_get_or_load(state, image_path);
84
  if (!image) {
85
    chroma_log("ERROR", "Failed to load image for output %u: %s", output->id,
86
               image_path);
87
    return CHROMA_ERROR_IMAGE;
88
  }
89
90
  // Check if image changed and invalidate texture cache if neceessary
91
  bool image_changed = (output->image != image);
92
  if (image_changed && output->image) {
93
    chroma_output_invalidate_texture(output);
94
    chroma_log("DEBUG", "Image changed for output %u, invalidated texture",
95
               output->id);
96
  }
97
98
  // Assign image to output
99
  output->image = image;
100
101
  // Create surface if it doesn't exist
102
  if (!output->surface) {
103
    int ret = chroma_surface_create(state, output);
104
    if (ret != CHROMA_OK) {
105
      chroma_log("ERROR", "Failed to create surface for output %u", output->id);
106
      return ret;
107
    }
108
  }
109
110
  // Render wallpaper
111
  int ret = chroma_render_wallpaper(state, output);
112
  if (ret != CHROMA_OK) {
113
    chroma_log("ERROR", "Failed to render wallpaper for output %u", output->id);
114
    return ret;
115
  }
116
117
  chroma_log("INFO", "Assigned wallpaper to output %u (%s): %s", output->id,
118
             output->name ? output->name : "unknown", image_path);
119
120
  return CHROMA_OK;
121
}
122
123
// Assign wallpapers to all active outputs
124
static int assign_wallpapers_to_all_outputs(chroma_state_t *state) {
125
  if (!state) {
126
    return CHROMA_ERROR_INIT;
127
  }
128
129
  int success_count = 0;
130
  int error_count = 0;
131
132
  for (int i = 0; i < state->output_count; i++) {
133
    chroma_output_t *output = &state->outputs[i];
134
135
    if (!output->active) {
136
      chroma_log("DEBUG", "Skipping inactive output %u", output->id);
137
      continue;
138
    }
139
140
    if (assign_wallpaper_to_output(state, output) == CHROMA_OK) {
141
      success_count++;
142
    } else {
143
      error_count++;
144
    }
145
  }
146
147
  chroma_log("INFO", "Wallpaper assignment complete: %d success, %d errors",
148
             success_count, error_count);
149
150
  return (success_count > 0) ? CHROMA_OK : CHROMA_ERROR_IMAGE;
151
}
152
153
// Handle output configuration complete event
154
void handle_output_done(chroma_state_t *state, chroma_output_t *output) {
155
  if (!state || !output) {
156
    return;
157
  }
158
159
  chroma_log("INFO",
160
             "Output %u (%s) configuration complete: %dx%d@%d, scale=%d",
161
             output->id, output->name ? output->name : "unknown", output->width,
162
             output->height, 0, output->scale);
163
164
  /* Assign wallpaper to this output */
165
  if (assign_wallpaper_to_output(state, output) != CHROMA_OK) {
166
    chroma_log("ERROR", "Failed to assign wallpaper to output %u", output->id);
167
  }
168
}
169
170
// Process Wayland events
171
static int process_wayland_events(chroma_state_t *state) {
172
  if (!state || !state->display) {
173
    return CHROMA_ERROR_WAYLAND;
174
  }
175
176
  /* Dispatch pending events */
177
  if (wl_display_dispatch_pending(state->display) == -1) {
178
    chroma_log("ERROR", "Failed to dispatch pending Wayland events: %s",
179
               strerror(errno));
180
    return CHROMA_ERROR_WAYLAND;
181
  }
182
183
  /* Read events from the server */
184
  if (wl_display_read_events(state->display) == -1) {
185
    chroma_log("ERROR", "Failed to read Wayland events: %s", strerror(errno));
186
    return CHROMA_ERROR_WAYLAND;
187
  }
188
189
  /* Dispatch the read events */
190
  if (wl_display_dispatch_pending(state->display) == -1) {
191
    chroma_log("ERROR", "Failed to dispatch read Wayland events: %s",
192
               strerror(errno));
193
    return CHROMA_ERROR_WAYLAND;
194
  }
195
196
  return CHROMA_OK;
197
}
198
199
// Main event loop
200
int chroma_run(chroma_state_t *state) {
201
  if (!state || !state->initialized) {
202
    return CHROMA_ERROR_INIT;
203
  }
204
205
  chroma_log("INFO", "Starting main event loop");
206
  state->running = true;
207
208
  // Initial wallpaper assignment
209
  chroma_log("INFO", "Performing initial wallpaper assignment");
210
  assign_wallpapers_to_all_outputs(state);
211
212
  // Main event loop
213
  while (state->running && !chroma_should_quit) {
214
    // Dispatch any pending events first
215
    if (wl_display_dispatch_pending(state->display) == -1) {
216
      chroma_log("ERROR", "Failed to dispatch pending events: %s",
217
                 strerror(errno));
218
      break;
219
    }
220
221
    // Prepare to read events
222
    if (wl_display_prepare_read(state->display) == -1) {
223
      chroma_log("ERROR", "Failed to prepare Wayland display for reading");
224
      break;
225
    }
226
227
    // Flush outgoing requests
228
    if (wl_display_flush(state->display) == -1) {
229
      chroma_log("ERROR", "Failed to flush Wayland display: %s",
230
                 strerror(errno));
231
      wl_display_cancel_read(state->display);
232
      break;
233
    }
234
235
    // Get the display file descriptor
236
    int fd = wl_display_get_fd(state->display);
237
    if (fd == -1) {
238
      chroma_log("ERROR", "Failed to get Wayland display file descriptor");
239
      wl_display_cancel_read(state->display);
240
      break;
241
    }
242
243
    // Use select() to wait for events with timeout
244
    fd_set readfds;
245
    struct timeval timeout;
246
247
    FD_ZERO(&readfds);
248
    FD_SET(fd, &readfds);
249
250
    timeout.tv_sec = 1; // 1 second timeout
251
    timeout.tv_usec = 0;
252
253
    int select_result = select(fd + 1, &readfds, NULL, NULL, &timeout);
254
255
    if (select_result == -1) {
256
      if (errno == EINTR) {
257
        // Interrupted by signal, check if we should quit
258
        wl_display_cancel_read(state->display);
259
        continue;
260
      }
261
      chroma_log("ERROR", "select() failed: %s", strerror(errno));
262
      wl_display_cancel_read(state->display);
263
      break;
264
    }
265
266
    if (select_result == 0) {
267
      // Timeout - no events available
268
      wl_display_cancel_read(state->display);
269
      continue;
270
    }
271
272
    // Events are available
273
    if (FD_ISSET(fd, &readfds)) {
274
      if (process_wayland_events(state) != CHROMA_OK) {
275
        break;
276
      }
277
    } else {
278
      wl_display_cancel_read(state->display);
279
    }
280
  }
281
282
  state->running = false;
283
  chroma_log("INFO", "Main event loop ended");
284
285
  return CHROMA_OK;
286
}
287
288
// Error code to string conversion
289
const char *chroma_error_string(chroma_error_t error) {
290
  switch (error) {
291
  case CHROMA_OK:
292
    return "Success";
293
  case CHROMA_ERROR_INIT:
294
    return "Initialization error";
295
  case CHROMA_ERROR_WAYLAND:
296
    return "Wayland error";
297
  case CHROMA_ERROR_EGL:
298
    return "EGL error";
299
  case CHROMA_ERROR_IMAGE:
300
    return "Image loading error";
301
  case CHROMA_ERROR_CONFIG:
302
    return "Configuration error";
303
  case CHROMA_ERROR_MEMORY:
304
    return "Memory allocation error";
305
  default:
306
    return "Unknown error";
307
  }
308
}
309
310
// Logging function
311
void chroma_log(const char *level, const char *format, ...) {
312
  va_list args;
313
  char timestamp[32];
314
  struct timeval tv;
315
  struct tm *tm_info;
316
317
  // Get current time
318
  gettimeofday(&tv, NULL);
319
  tm_info = localtime(&tv.tv_sec);
320
321
  // Format timestamp
322
  snprintf(timestamp, sizeof(timestamp), "%04d-%02d-%02d %02d:%02d:%02d.%03d",
323
           tm_info->tm_year + 1900, tm_info->tm_mon + 1, tm_info->tm_mday,
324
           tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec,
325
           (int)(tv.tv_usec / 1000));
326
327
  // Print log message
328
  printf("[%s] %s: ", timestamp, level);
329
330
  va_start(args, format);
331
  vprintf(format, args);
332
  va_end(args);
333
334
  printf("\n");
335
  fflush(stdout);
336
}
337
338
// Set log level
339
void chroma_set_log_level(int level) { log_level = level; }
340
341
// Get log level
342
int chroma_get_log_level(void) { return log_level; }
343
344
// Handle configuration reload (SIGHUP)
345
int chroma_reload_config(chroma_state_t *state, const char *config_file) {
346
  if (!state) {
347
    return CHROMA_ERROR_INIT;
348
  }
349
350
  chroma_log("INFO", "Reloading configuration");
351
352
  // Free current configuration
353
  chroma_config_free(&state->config);
354
355
  // Load new configuration
356
  int ret = chroma_config_load(&state->config, config_file);
357
  if (ret != CHROMA_OK) {
358
    chroma_log("ERROR", "Failed to reload configuration: %s",
359
               chroma_error_string(ret));
360
    return ret;
361
  }
362
363
  // Reassign wallpapers with new configuration
364
  ret = assign_wallpapers_to_all_outputs(state);
365
  if (ret != CHROMA_OK) {
366
    chroma_log("ERROR", "Failed to reassign wallpapers after config reload");
367
    return ret;
368
  }
369
370
  chroma_log("INFO", "Configuration reloaded successfully");
371
  return CHROMA_OK;
372
}
373
374
// Check if an output needs wallpaper update
375
static bool output_needs_update(chroma_output_t *output) {
376
  if (!output || !output->active) {
377
    return false;
378
  }
379
380
  // Check if output has no surface or image assigned
381
  if (!output->surface || !output->image) {
382
    return true;
383
  }
384
385
  // Check if image is no longer loaded
386
  if (!output->image->loaded) {
387
    return true;
388
  }
389
390
  return false;
391
}
392
393
// Update outputs that need wallpaper refresh
394
int chroma_update_outputs(chroma_state_t *state) {
395
  if (!state) {
396
    return CHROMA_ERROR_INIT;
397
  }
398
399
  int updated_count = 0;
400
401
  for (int i = 0; i < state->output_count; i++) {
402
    chroma_output_t *output = &state->outputs[i];
403
404
    if (output_needs_update(output)) {
405
      if (assign_wallpaper_to_output(state, output) == CHROMA_OK) {
406
        updated_count++;
407
      }
408
    }
409
  }
410
411
  if (updated_count > 0) {
412
    chroma_log("INFO", "Updated wallpapers for %d outputs", updated_count);
413
  }
414
415
  return CHROMA_OK;
416
}
417
418
// Get statistics
419
void chroma_get_stats(chroma_state_t *state, int *active_outputs,
420
                      int *loaded_images) {
421
  if (!state) {
422
    if (active_outputs)
423
      *active_outputs = 0;
424
    if (loaded_images)
425
      *loaded_images = 0;
426
    return;
427
  }
428
429
  int active = 0;
430
  for (int i = 0; i < state->output_count; i++) {
431
    if (state->outputs[i].active) {
432
      active++;
433
    }
434
  }
435
436
  int loaded = 0;
437
  for (int i = 0; i < state->image_count; i++) {
438
    if (state->images[i].loaded) {
439
      loaded++;
440
    }
441
  }
442
443
  if (active_outputs)
444
    *active_outputs = active;
445
  if (loaded_images)
446
    *loaded_images = loaded;
447
}