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