brewery
notashelf /
3cbf6d56457be861472bea470fc4920e5d067989

chroma

public

Lightweight wallpaper daemon for Wayland

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