brewery
notashelf /
dadba853e886c4fe1473481ff48f88da9b03c7da

chroma

public

Lightweight wallpaper daemon for Wayland

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