brewery
notashelf /
de0dcba8c81e1e4700215113a34641a6ee2e7c95

chroma

public

Lightweight wallpaper daemon for Wayland

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