brewery
notashelf /
87d445340a666b2f700aedcc26fabe8f771b7272

chroma

public

Lightweight wallpaper daemon for Wayland

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