aboutsummaryrefslogtreecommitdiffstats
path: root/sway/sway_text_node.c
diff options
context:
space:
mode:
Diffstat (limited to 'sway/sway_text_node.c')
-rw-r--r--sway/sway_text_node.c308
1 files changed, 308 insertions, 0 deletions
diff --git a/sway/sway_text_node.c b/sway/sway_text_node.c
new file mode 100644
index 00000000..4b7ee999
--- /dev/null
+++ b/sway/sway_text_node.c
@@ -0,0 +1,308 @@
1#include <drm_fourcc.h>
2#include <stdio.h>
3#include <stdlib.h>
4#include <string.h>
5#include <wlr/types/wlr_buffer.h>
6#include <wlr/interfaces/wlr_buffer.h>
7#include "cairo_util.h"
8#include "log.h"
9#include "pango.h"
10#include "sway/config.h"
11#include "sway/sway_text_node.h"
12
13struct cairo_buffer {
14 struct wlr_buffer base;
15 cairo_surface_t *surface;
16 cairo_t *cairo;
17};
18
19static void cairo_buffer_handle_destroy(struct wlr_buffer *wlr_buffer) {
20 struct cairo_buffer *buffer = wl_container_of(wlr_buffer, buffer, base);
21
22 cairo_surface_destroy(buffer->surface);
23 cairo_destroy(buffer->cairo);
24 free(buffer);
25}
26
27static bool cairo_buffer_handle_begin_data_ptr_access(struct wlr_buffer *wlr_buffer,
28 uint32_t flags, void **data, uint32_t *format, size_t *stride) {
29 struct cairo_buffer *buffer = wl_container_of(wlr_buffer, buffer, base);
30 *data = cairo_image_surface_get_data(buffer->surface);
31 *stride = cairo_image_surface_get_stride(buffer->surface);
32 *format = DRM_FORMAT_ARGB8888;
33 return true;
34}
35
36static void cairo_buffer_handle_end_data_ptr_access(struct wlr_buffer *wlr_buffer) {
37 // This space is intentionally left blank
38}
39
40static const struct wlr_buffer_impl cairo_buffer_impl = {
41 .destroy = cairo_buffer_handle_destroy,
42 .begin_data_ptr_access = cairo_buffer_handle_begin_data_ptr_access,
43 .end_data_ptr_access = cairo_buffer_handle_end_data_ptr_access,
44};
45
46struct text_buffer {
47 struct wlr_scene_buffer *buffer_node;
48 char *text;
49 struct sway_text_node props;
50
51 bool visible;
52 float scale;
53 enum wl_output_subpixel subpixel;
54
55 struct wl_listener outputs_update;
56 struct wl_listener destroy;
57};
58
59static int get_text_width(struct sway_text_node *props) {
60 int width = props->width;
61 if (props->max_width >= 0) {
62 width = MIN(width, props->max_width);
63 }
64 return MAX(width, 0);
65}
66
67static void update_source_box(struct text_buffer *buffer) {
68 struct sway_text_node *props = &buffer->props;
69 struct wlr_fbox source_box = {
70 .x = 0,
71 .y = 0,
72 .width = ceil(get_text_width(props) * buffer->scale),
73 .height = ceil(props->height * buffer->scale),
74 };
75
76 wlr_scene_buffer_set_source_box(buffer->buffer_node, &source_box);
77}
78
79static void render_backing_buffer(struct text_buffer *buffer) {
80 if (!buffer->visible) {
81 return;
82 }
83
84 if (buffer->props.max_width == 0) {
85 wlr_scene_buffer_set_buffer(buffer->buffer_node, NULL);
86 return;
87 }
88
89 float scale = buffer->scale;
90 int width = ceil(buffer->props.width * scale);
91 int height = ceil(buffer->props.height * scale);
92 float *color = (float *)&buffer->props.color;
93 float *background = (float *)&buffer->props.background;
94 PangoContext *pango = NULL;
95
96 cairo_font_options_t *fo = cairo_font_options_create();
97 cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL);
98 enum wl_output_subpixel subpixel = buffer->subpixel;
99 if (subpixel == WL_OUTPUT_SUBPIXEL_NONE || subpixel == WL_OUTPUT_SUBPIXEL_UNKNOWN) {
100 cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_GRAY);
101 } else {
102 cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL);
103 cairo_font_options_set_subpixel_order(fo, to_cairo_subpixel_order(subpixel));
104 }
105
106 cairo_surface_t *surface = cairo_image_surface_create(
107 CAIRO_FORMAT_ARGB32, width, height);
108 cairo_status_t status = cairo_surface_status(surface);
109 if (status != CAIRO_STATUS_SUCCESS) {
110 sway_log(SWAY_ERROR, "cairo_image_surface_create failed: %s",
111 cairo_status_to_string(status));
112 goto err;
113 }
114
115 struct cairo_buffer *cairo_buffer = calloc(1, sizeof(*cairo_buffer));
116 if (!cairo_buffer) {
117 sway_log(SWAY_ERROR, "cairo_buffer allocation failed");
118 goto err;
119 }
120
121 cairo_t *cairo = cairo_create(surface);
122 if (!cairo) {
123 sway_log(SWAY_ERROR, "cairo_create failed");
124 free(cairo_buffer);
125 goto err;
126 }
127
128 cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST);
129 cairo_set_font_options(cairo, fo);
130 pango = pango_cairo_create_context(cairo);
131
132 cairo_set_source_rgba(cairo, background[0], background[1], background[2], background[3]);
133 cairo_rectangle(cairo, 0, 0, width, height);
134 cairo_fill(cairo);
135
136 cairo_set_source_rgba(cairo, color[0], color[1], color[2], color[3]);
137 cairo_move_to(cairo, 0, (config->font_baseline - buffer->props.baseline) * scale);
138
139 render_text(cairo, config->font_description, scale, buffer->props.pango_markup,
140 "%s", buffer->text);
141
142 cairo_surface_flush(surface);
143
144 wlr_buffer_init(&cairo_buffer->base, &cairo_buffer_impl, width, height);
145 cairo_buffer->surface = surface;
146 cairo_buffer->cairo = cairo;
147
148 wlr_scene_buffer_set_buffer(buffer->buffer_node, &cairo_buffer->base);
149 wlr_buffer_drop(&cairo_buffer->base);
150 update_source_box(buffer);
151
152 pixman_region32_t opaque;
153 pixman_region32_init(&opaque);
154 if (background[3] == 1) {
155 pixman_region32_union_rect(&opaque, &opaque, 0, 0,
156 buffer->props.width, buffer->props.height);
157 }
158 wlr_scene_buffer_set_opaque_region(buffer->buffer_node, &opaque);
159 pixman_region32_fini(&opaque);
160
161err:
162 if (pango) g_object_unref(pango);
163 cairo_font_options_destroy(fo);
164}
165
166static void handle_outputs_update(struct wl_listener *listener, void *data) {
167 struct text_buffer *buffer = wl_container_of(listener, buffer, outputs_update);
168 struct wlr_scene_outputs_update_event *event = data;
169
170 float scale = 0;
171 enum wl_output_subpixel subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN;
172
173 for (size_t i = 0; i < event->size; i++) {
174 struct wlr_scene_output *output = event->active[i];
175 if (subpixel == WL_OUTPUT_SUBPIXEL_UNKNOWN) {
176 subpixel = output->output->subpixel;
177 } else if (subpixel != output->output->subpixel) {
178 subpixel = WL_OUTPUT_SUBPIXEL_NONE;
179 }
180
181 if (scale != 0 && scale != output->output->scale) {
182 // drop down to gray scale if we encounter outputs with different
183 // scales or else we will have chromatic aberations
184 subpixel = WL_OUTPUT_SUBPIXEL_NONE;
185 }
186
187 if (scale < output->output->scale) {
188 scale = output->output->scale;
189 }
190 }
191
192 buffer->visible = event->size > 0;
193
194 if (scale != buffer->scale || subpixel != buffer->subpixel) {
195 buffer->scale = scale;
196 buffer->subpixel = subpixel;
197 render_backing_buffer(buffer);
198 }
199}
200
201static void handle_destroy(struct wl_listener *listener, void *data) {
202 struct text_buffer *buffer = wl_container_of(listener, buffer, destroy);
203
204 wl_list_remove(&buffer->outputs_update.link);
205 wl_list_remove(&buffer->destroy.link);
206
207 free(buffer->text);
208 free(buffer);
209}
210
211static void text_calc_size(struct text_buffer *buffer) {
212 struct sway_text_node *props = &buffer->props;
213
214 cairo_t *c = cairo_create(NULL);
215 if (!c) {
216 sway_log(SWAY_ERROR, "cairo_t allocation failed");
217 return;
218 }
219
220 cairo_set_antialias(c, CAIRO_ANTIALIAS_BEST);
221 get_text_size(c, config->font_description, &props->width, NULL,
222 &props->baseline, 1, props->pango_markup, "%s", buffer->text);
223 cairo_destroy(c);
224
225 wlr_scene_buffer_set_dest_size(buffer->buffer_node,
226 get_text_width(props), props->height);
227}
228
229struct sway_text_node *sway_text_node_create(struct wlr_scene_tree *parent,
230 char *text, float color[4], bool pango_markup) {
231 struct text_buffer *buffer = calloc(1, sizeof(*buffer));
232 if (!buffer) {
233 return NULL;
234 }
235
236 struct wlr_scene_buffer *node = wlr_scene_buffer_create(parent, NULL);
237 if (!node) {
238 free(buffer);
239 return NULL;
240 }
241
242 buffer->buffer_node = node;
243 buffer->props.node = &node->node;
244 buffer->props.max_width = -1;
245 buffer->text = strdup(text);
246 if (!buffer->text) {
247 free(buffer);
248 wlr_scene_node_destroy(&node->node);
249 return NULL;
250 }
251
252 buffer->props.height = config->font_height;
253 buffer->props.pango_markup = pango_markup;
254 memcpy(&buffer->props.color, color, sizeof(*color) * 4);
255
256 buffer->destroy.notify = handle_destroy;
257 wl_signal_add(&node->node.events.destroy, &buffer->destroy);
258 buffer->outputs_update.notify = handle_outputs_update;
259 wl_signal_add(&node->events.outputs_update, &buffer->outputs_update);
260
261 text_calc_size(buffer);
262
263 return &buffer->props;
264}
265
266void sway_text_node_set_color(struct sway_text_node *node, float color[4]) {
267 if (memcmp(&node->color, color, sizeof(*color) * 4) == 0) {
268 return;
269 }
270
271 memcpy(&node->color, color, sizeof(*color) * 4);
272 struct text_buffer *buffer = wl_container_of(node, buffer, props);
273
274 render_backing_buffer(buffer);
275}
276
277void sway_text_node_set_text(struct sway_text_node *node, char *text) {
278 struct text_buffer *buffer = wl_container_of(node, buffer, props);
279 if (strcmp(buffer->text, text) == 0) {
280 return;
281 }
282
283 char *new_text = strdup(text);
284 if (!new_text) {
285 return;
286 }
287
288 free(buffer->text);
289 buffer->text = new_text;
290
291 text_calc_size(buffer);
292 render_backing_buffer(buffer);
293}
294
295void sway_text_node_set_max_width(struct sway_text_node *node, int max_width) {
296 struct text_buffer *buffer = wl_container_of(node, buffer, props);
297 buffer->props.max_width = max_width;
298 wlr_scene_buffer_set_dest_size(buffer->buffer_node,
299 get_text_width(&buffer->props), buffer->props.height);
300 update_source_box(buffer);
301 render_backing_buffer(buffer);
302}
303
304void sway_text_node_set_background(struct sway_text_node *node, float background[4]) {
305 struct text_buffer *buffer = wl_container_of(node, buffer, props);
306 memcpy(&node->background, background, sizeof(*background) * 4);
307 render_backing_buffer(buffer);
308}