brewery
notashelf /
292491b7388d7799a4e5ed0ddeb0265872b2f9d2

chroma

public

Lightweight wallpaper daemon for Wayland

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/chroma.git
src/wayland.cC467 lines14.3 KB
1
#include <errno.h>
2
#include <stdio.h>
3
#include <stdlib.h>
4
#include <string.h>
5
#include <unistd.h>
6
7
#include "../include/chroma.h"
8
9
// Registry listener
10
static void registry_global(void *data, struct wl_registry *registry,
11
                            uint32_t id, const char *interface,
12
                            uint32_t version) {
13
  chroma_state_t *state = (chroma_state_t *)data;
14
15
  chroma_log("DEBUG", "Registry global: %s (id=%u, version=%u)", interface, id,
16
             version);
17
  chroma_log("TRACE", "Checking interface binding for: %s", interface);
18
19
  if (strcmp(interface, wl_compositor_interface.name) == 0) {
20
    state->compositor = wl_registry_bind(registry, id, &wl_compositor_interface,
21
                                         version < 4 ? version : 4);
22
    if (!state->compositor) {
23
      chroma_log("ERROR", "Failed to bind compositor");
24
    } else {
25
      chroma_log("INFO", "Bound compositor (version %u)", version);
26
      chroma_log("TRACE", "Compositor binding successful, interface ready");
27
    }
28
  } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
29
    state->layer_shell =
30
        wl_registry_bind(registry, id, &zwlr_layer_shell_v1_interface,
31
                         version < 4 ? version : 4);
32
    if (!state->layer_shell) {
33
      chroma_log("ERROR", "Failed to bind layer shell");
34
    } else {
35
      chroma_log("INFO", "Bound layer shell (version %u)", version);
36
      chroma_log(
37
          "TRACE",
38
          "wlr-layer-shell protocol available, wallpaper support enabled");
39
    }
40
  } else if (strcmp(interface, wl_shm_interface.name) == 0) {
41
    state->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1);
42
    if (!state->shm) {
43
      chroma_log("ERROR", "Failed to bind shm");
44
    } else {
45
      chroma_log("INFO", "Bound shm (version %u)", version);
46
    }
47
  } else if (strcmp(interface, wl_output_interface.name) == 0) {
48
    struct wl_output *output = wl_registry_bind(
49
        registry, id, &wl_output_interface, version < 4 ? version : 4);
50
    if (!output) {
51
      chroma_log("ERROR", "Failed to bind output %u", id);
52
      return;
53
    }
54
55
    if (chroma_output_add(state, id, output) != CHROMA_OK) {
56
      chroma_log("ERROR", "Failed to add output %u", id);
57
      wl_output_destroy(output);
58
    } else {
59
      chroma_log("INFO", "Added output %u", id);
60
    }
61
  }
62
}
63
64
static void registry_global_remove(void *data, struct wl_registry *registry,
65
                                   uint32_t id) {
66
  chroma_state_t *state = (chroma_state_t *)data;
67
  (void)registry;
68
69
  chroma_log("DEBUG", "Registry global remove: id=%u", id);
70
  chroma_log("TRACE", "Global object removal initiated for id=%u", id);
71
  chroma_output_remove(state, id);
72
}
73
74
const struct wl_registry_listener chroma_registry_listener_impl = {
75
    .global = registry_global,
76
    .global_remove = registry_global_remove,
77
};
78
79
// Layer surface event handlers
80
static void layer_surface_configure(void *data,
81
                                    struct zwlr_layer_surface_v1 *layer_surface,
82
                                    uint32_t serial, uint32_t width,
83
                                    uint32_t height) {
84
  chroma_output_t *output = (chroma_output_t *)data;
85
  (void)layer_surface;
86
87
  chroma_log("DEBUG", "Layer surface configure: %ux%u, serial=%u", width,
88
             height, serial);
89
  chroma_log(
90
      "TRACE",
91
      "Configuring layer surface for output %u: dimensions=%ux%u, serial=%u",
92
      output->id, width, height, serial);
93
94
  output->configure_serial = serial;
95
96
  // Acknowledge the configure event
97
  zwlr_layer_surface_v1_ack_configure(output->layer_surface, serial);
98
  chroma_log("TRACE", "Sent configure acknowledgment for output %u serial %u",
99
             output->id, serial);
100
101
  // Mark as configured - actual commit happens in render via wl_surface_commit
102
  output->configured = true;
103
104
  chroma_log("DEBUG", "Acknowledged layer surface configure for output %u",
105
             output->id);
106
}
107
108
static void layer_surface_closed(void *data,
109
                                 struct zwlr_layer_surface_v1 *layer_surface) {
110
  chroma_output_t *output = (chroma_output_t *)data;
111
  (void)layer_surface;
112
113
  chroma_log("INFO", "Layer surface closed for output %u", output->id);
114
115
  // Clean up the surface
116
  if (output->surface) {
117
    chroma_surface_destroy(output);
118
  }
119
}
120
121
const struct zwlr_layer_surface_v1_listener chroma_layer_surface_listener_impl =
122
    {
123
        .configure = layer_surface_configure,
124
        .closed = layer_surface_closed,
125
};
126
127
// Output event handlers
128
static void output_geometry(void *data, struct wl_output *output, int32_t x,
129
                            int32_t y, int32_t physical_width,
130
                            int32_t physical_height, int32_t subpixel,
131
                            const char *make, const char *model,
132
                            int32_t transform) {
133
  chroma_output_t *chroma_output = (chroma_output_t *)data;
134
  (void)output;
135
  (void)subpixel;
136
  (void)make;
137
  (void)model;
138
139
  chroma_output->x = x;
140
  chroma_output->y = y;
141
  chroma_output->transform = transform;
142
143
  chroma_log("DEBUG", "Output %u geometry: %dx%d at (%d,%d), transform=%d",
144
             chroma_output->id, physical_width, physical_height, x, y,
145
             transform);
146
  chroma_log("TRACE",
147
             "Output %u geometry details: physical_size=%dx%dmm, "
148
             "position=(%d,%d), transform=%d",
149
             chroma_output->id, physical_width, physical_height, x, y,
150
             transform);
151
}
152
153
static void output_mode(void *data, struct wl_output *output, uint32_t flags,
154
                        int32_t width, int32_t height, int32_t refresh) {
155
  chroma_output_t *chroma_output = (chroma_output_t *)data;
156
  (void)output;
157
158
  if (flags & WL_OUTPUT_MODE_CURRENT) {
159
    chroma_output->width = width;
160
    chroma_output->height = height;
161
    chroma_log("DEBUG", "Output %u mode: %dx%d@%d (current)", chroma_output->id,
162
               width, height, refresh);
163
    chroma_log("TRACE",
164
               "Current mode set for output %u: %dx%d@%dHz, flags=0x%x",
165
               chroma_output->id, width, height, refresh, flags);
166
  } else {
167
    chroma_log("TRACE",
168
               "Non-current mode for output %u: %dx%d@%dHz, flags=0x%x",
169
               chroma_output->id, width, height, refresh, flags);
170
  }
171
}
172
173
static void output_scale(void *data, struct wl_output *output, int32_t scale) {
174
  chroma_output_t *chroma_output = (chroma_output_t *)data;
175
  (void)output;
176
177
  chroma_output->scale = scale;
178
  chroma_log("DEBUG", "Output %u scale: %d", chroma_output->id, scale);
179
}
180
181
static void output_name(void *data, struct wl_output *output,
182
                        const char *name) {
183
  chroma_output_t *chroma_output = (chroma_output_t *)data;
184
  (void)output;
185
186
  free(chroma_output->name);
187
  chroma_output->name = strdup(name);
188
  if (!chroma_output->name) {
189
    chroma_log("ERROR", "Failed to allocate memory for output name");
190
    return;
191
  }
192
193
  chroma_log("DEBUG", "Output %u name: %s", chroma_output->id, name);
194
}
195
196
static void output_description(void *data, struct wl_output *output,
197
                               const char *description) {
198
  chroma_output_t *chroma_output = (chroma_output_t *)data;
199
  (void)output;
200
201
  free(chroma_output->description);
202
  chroma_output->description = strdup(description);
203
  if (!chroma_output->description) {
204
    chroma_log("ERROR", "Failed to allocate memory for output description");
205
    return;
206
  }
207
208
  chroma_log("DEBUG", "Output %u description: %s", chroma_output->id,
209
             description);
210
}
211
212
static void output_done(void *data, struct wl_output *output) {
213
  chroma_output_t *chroma_output = (chroma_output_t *)data;
214
  (void)output;
215
216
  chroma_log("DEBUG", "Output %u done - configuration complete",
217
             chroma_output->id);
218
  chroma_log("TRACE",
219
             "Output %u configuration finalized: %dx%d, scale=%d, name='%s'",
220
             chroma_output->id, chroma_output->width, chroma_output->height,
221
             chroma_output->scale,
222
             chroma_output->name ? chroma_output->name : "unknown");
223
224
  // Mark output as active and ready for wallpaper assignment
225
  chroma_output->active = true;
226
227
  // Trigger wallpaper assignment for this output
228
  if (chroma_output->state) {
229
    chroma_log("TRACE", "Triggering wallpaper assignment for output %u",
230
               chroma_output->id);
231
    handle_output_done(chroma_output->state, chroma_output);
232
  }
233
}
234
235
const struct wl_output_listener chroma_output_listener_impl = {
236
    .geometry = output_geometry,
237
    .mode = output_mode,
238
    .scale = output_scale,
239
    .name = output_name,
240
    .description = output_description,
241
    .done = output_done,
242
};
243
244
// Wayland connection functions
245
int chroma_wayland_connect(chroma_state_t *state) {
246
  if (!state) {
247
    return CHROMA_ERROR_INIT;
248
  }
249
250
  // Connect to Wayland display
251
  state->display = wl_display_connect(NULL);
252
  if (!state->display) {
253
    chroma_log("ERROR", "Failed to connect to Wayland display: %s",
254
               strerror(errno));
255
    return CHROMA_ERROR_WAYLAND;
256
  }
257
258
  // Get registry
259
  state->registry = wl_display_get_registry(state->display);
260
  if (!state->registry) {
261
    chroma_log("ERROR", "Failed to get Wayland registry");
262
    chroma_wayland_disconnect(state);
263
    return CHROMA_ERROR_WAYLAND;
264
  }
265
266
  // Add registry listener
267
  wl_registry_add_listener(state->registry, &chroma_registry_listener_impl,
268
                           state);
269
270
  // Roundtrip to get all globals
271
  chroma_log("TRACE", "Starting Wayland display roundtrip to discover globals");
272
  if (wl_display_roundtrip(state->display) == -1) {
273
    chroma_log("ERROR", "Failed to roundtrip Wayland display");
274
    chroma_wayland_disconnect(state);
275
    return CHROMA_ERROR_WAYLAND;
276
  }
277
  chroma_log("TRACE", "Wayland display roundtrip completed");
278
279
  // Check if we got a compositor
280
  if (!state->compositor) {
281
    chroma_log("ERROR", "No compositor available");
282
    chroma_wayland_disconnect(state);
283
    return CHROMA_ERROR_WAYLAND;
284
  }
285
286
  // Check if we got layer shell
287
  if (!state->layer_shell) {
288
    chroma_log("ERROR", "No layer shell available - compositor may not support "
289
                        "wlr-layer-shell");
290
    chroma_wayland_disconnect(state);
291
    return CHROMA_ERROR_WAYLAND;
292
  }
293
294
  // Check if we got shm
295
  if (!state->shm) {
296
    chroma_log("ERROR", "No shm available");
297
    chroma_wayland_disconnect(state);
298
    return CHROMA_ERROR_WAYLAND;
299
  }
300
301
  chroma_log("INFO", "Connected to Wayland display, found %d outputs",
302
             state->output_count);
303
  return CHROMA_OK;
304
}
305
306
void chroma_wayland_disconnect(chroma_state_t *state) {
307
  if (!state) {
308
    return;
309
  }
310
311
  // Clean up all outputs
312
  for (int i = 0; i < state->output_count; i++) {
313
    chroma_output_t *output = &state->outputs[i];
314
    if (output->surface) {
315
      chroma_surface_destroy(output);
316
    }
317
318
    if (output->wl_output) {
319
      wl_output_destroy(output->wl_output);
320
    }
321
322
    free(output->name);
323
    free(output->description);
324
  }
325
  state->output_count = 0;
326
327
  // Clean up Wayland objects
328
  if (state->layer_shell) {
329
    zwlr_layer_shell_v1_destroy(state->layer_shell);
330
    state->layer_shell = NULL;
331
  }
332
333
  if (state->shm) {
334
    wl_shm_destroy(state->shm);
335
    state->shm = NULL;
336
  }
337
338
  if (state->compositor) {
339
    wl_compositor_destroy(state->compositor);
340
    state->compositor = NULL;
341
  }
342
343
  if (state->registry) {
344
    wl_registry_destroy(state->registry);
345
    state->registry = NULL;
346
  }
347
348
  if (state->display) {
349
    wl_display_disconnect(state->display);
350
    state->display = NULL;
351
  }
352
353
  chroma_log("INFO", "Disconnected from Wayland display");
354
}
355
356
// Output management functions
357
int chroma_output_add(chroma_state_t *state, uint32_t id,
358
                      struct wl_output *output) {
359
  if (!state || !output) {
360
    return CHROMA_ERROR_INIT;
361
  }
362
363
  if (state->output_count >= MAX_OUTPUTS) {
364
    chroma_log("ERROR", "Maximum number of outputs (%d) exceeded", MAX_OUTPUTS);
365
    return CHROMA_ERROR_MEMORY;
366
  }
367
368
  chroma_output_t *chroma_output = &state->outputs[state->output_count];
369
  memset(chroma_output, 0, sizeof(chroma_output_t));
370
371
  chroma_output->wl_output = output;
372
  chroma_output->id = id;
373
  chroma_output->scale = 1; // default scale
374
  chroma_output->active = false;
375
  chroma_output->state = state;
376
377
  // Add output listener
378
  wl_output_add_listener(output, &chroma_output_listener_impl, chroma_output);
379
  chroma_log("TRACE", "Output listener attached for output %u", id);
380
381
  state->output_count++;
382
383
  chroma_log("INFO", "Added output %u (total: %d)", id, state->output_count);
384
  chroma_log("TRACE",
385
             "Output %u initialized with default scale=1, active=false", id);
386
  return CHROMA_OK;
387
}
388
389
void chroma_output_remove(chroma_state_t *state, uint32_t id) {
390
  if (!state) {
391
    return;
392
  }
393
394
  chroma_output_t *output = chroma_output_find_by_id(state, id);
395
  if (!output) {
396
    chroma_log("WARN", "Attempted to remove non-existent output %u", id);
397
    return;
398
  }
399
400
  chroma_log("INFO", "Removing output %u (%s)", id,
401
             output->name ? output->name : "unknown");
402
403
  // Release image reference if the output holds one
404
  if (output->image) {
405
    chroma_image_release(output->image);
406
    output->image = NULL;
407
  }
408
409
  // Clean up surface if it exists
410
  if (output->surface) {
411
    chroma_surface_destroy(output);
412
  }
413
414
  // Clean up Wayland output
415
  if (output->wl_output) {
416
    wl_output_destroy(output->wl_output);
417
  }
418
419
  // Free allocated strings
420
  free(output->name);
421
  free(output->description);
422
423
  // Remove from array by shifting remaining elements
424
  ptrdiff_t index = output - state->outputs;
425
  size_t remaining = (size_t)(state->output_count - index - 1);
426
  chroma_log("TRACE", "Removing output %u from array: index=%td, remaining=%zu",
427
             id, index, remaining);
428
  if (remaining > 0) {
429
    memmove(output, output + 1, remaining * sizeof(chroma_output_t));
430
    chroma_log("TRACE", "Shifted %zu outputs in array after removal",
431
               remaining);
432
  }
433
434
  state->output_count--;
435
  chroma_log("INFO", "Removed output %u (remaining: %d)", id,
436
             state->output_count);
437
}
438
439
chroma_output_t *chroma_output_find_by_id(chroma_state_t *state, uint32_t id) {
440
  if (!state) {
441
    return NULL;
442
  }
443
444
  for (int i = 0; i < state->output_count; i++) {
445
    if (state->outputs[i].id == id) {
446
      return &state->outputs[i];
447
    }
448
  }
449
450
  return NULL;
451
}
452
453
chroma_output_t *chroma_output_find_by_name(chroma_state_t *state,
454
                                            const char *name) {
455
  if (!state || !name) {
456
    return NULL;
457
  }
458
459
  for (int i = 0; i < state->output_count; i++) {
460
    chroma_output_t *output = &state->outputs[i];
461
    if (output->name && strcmp(output->name, name) == 0) {
462
      return output;
463
    }
464
  }
465
466
  return NULL;
467
}