aboutsummaryrefslogtreecommitdiffstats
path: root/common
diff options
context:
space:
mode:
Diffstat (limited to 'common')
-rw-r--r--common/background-image.c220
-rw-r--r--common/cairo.c4
-rw-r--r--common/gesture.c332
-rw-r--r--common/ipc-client.c1
-rw-r--r--common/log.c1
-rw-r--r--common/loop.c1
-rw-r--r--common/meson.build3
-rw-r--r--common/pango.c50
-rw-r--r--common/stringop.c34
-rw-r--r--common/util.c13
10 files changed, 399 insertions, 260 deletions
diff --git a/common/background-image.c b/common/background-image.c
deleted file mode 100644
index de42e8e9..00000000
--- a/common/background-image.c
+++ /dev/null
@@ -1,220 +0,0 @@
1#include <assert.h>
2#include "background-image.h"
3#include "cairo.h"
4#include "log.h"
5#if HAVE_GDK_PIXBUF
6#include <gdk-pixbuf/gdk-pixbuf.h>
7#endif
8
9enum background_mode parse_background_mode(const char *mode) {
10 if (strcmp(mode, "stretch") == 0) {
11 return BACKGROUND_MODE_STRETCH;
12 } else if (strcmp(mode, "fill") == 0) {
13 return BACKGROUND_MODE_FILL;
14 } else if (strcmp(mode, "fit") == 0) {
15 return BACKGROUND_MODE_FIT;
16 } else if (strcmp(mode, "center") == 0) {
17 return BACKGROUND_MODE_CENTER;
18 } else if (strcmp(mode, "tile") == 0) {
19 return BACKGROUND_MODE_TILE;
20 } else if (strcmp(mode, "solid_color") == 0) {
21 return BACKGROUND_MODE_SOLID_COLOR;
22 }
23 sway_log(SWAY_ERROR, "Unsupported background mode: %s", mode);
24 return BACKGROUND_MODE_INVALID;
25}
26
27#if HAVE_GDK_PIXBUF
28static cairo_surface_t* gdk_cairo_image_surface_create_from_pixbuf(
29 const GdkPixbuf *gdkbuf) {
30 int chan = gdk_pixbuf_get_n_channels(gdkbuf);
31 if (chan < 3) {
32 return NULL;
33 }
34
35 const guint8* gdkpix = gdk_pixbuf_read_pixels(gdkbuf);
36 if (!gdkpix) {
37 return NULL;
38 }
39 gint w = gdk_pixbuf_get_width(gdkbuf);
40 gint h = gdk_pixbuf_get_height(gdkbuf);
41 int stride = gdk_pixbuf_get_rowstride(gdkbuf);
42
43 cairo_format_t fmt = (chan == 3) ? CAIRO_FORMAT_RGB24 : CAIRO_FORMAT_ARGB32;
44 cairo_surface_t * cs = cairo_image_surface_create (fmt, w, h);
45 cairo_surface_flush (cs);
46 if ( !cs || cairo_surface_status(cs) != CAIRO_STATUS_SUCCESS) {
47 return NULL;
48 }
49
50 int cstride = cairo_image_surface_get_stride(cs);
51 unsigned char * cpix = cairo_image_surface_get_data(cs);
52
53 if (chan == 3) {
54 int i;
55 for (i = h; i; --i) {
56 const guint8 *gp = gdkpix;
57 unsigned char *cp = cpix;
58 const guint8* end = gp + 3*w;
59 while (gp < end) {
60#if G_BYTE_ORDER == G_LITTLE_ENDIAN
61 cp[0] = gp[2];
62 cp[1] = gp[1];
63 cp[2] = gp[0];
64#else
65 cp[1] = gp[0];
66 cp[2] = gp[1];
67 cp[3] = gp[2];
68#endif
69 gp += 3;
70 cp += 4;
71 }
72 gdkpix += stride;
73 cpix += cstride;
74 }
75 } else {
76 /* premul-color = alpha/255 * color/255 * 255 = (alpha*color)/255
77 * (z/255) = z/256 * 256/255 = z/256 (1 + 1/255)
78 * = z/256 + (z/256)/255 = (z + z/255)/256
79 * # recurse once
80 * = (z + (z + z/255)/256)/256
81 * = (z + z/256 + z/256/255) / 256
82 * # only use 16bit uint operations, loose some precision,
83 * # result is floored.
84 * -> (z + z>>8)>>8
85 * # add 0x80/255 = 0.5 to convert floor to round
86 * => (z+0x80 + (z+0x80)>>8 ) >> 8
87 * ------
88 * tested as equal to lround(z/255.0) for uint z in [0..0xfe02]
89 */
90#define PREMUL_ALPHA(x,a,b,z) \
91 G_STMT_START { z = a * b + 0x80; x = (z + (z >> 8)) >> 8; } \
92 G_STMT_END
93 int i;
94 for (i = h; i; --i) {
95 const guint8 *gp = gdkpix;
96 unsigned char *cp = cpix;
97 const guint8* end = gp + 4*w;
98 guint z1, z2, z3;
99 while (gp < end) {
100#if G_BYTE_ORDER == G_LITTLE_ENDIAN
101 PREMUL_ALPHA(cp[0], gp[2], gp[3], z1);
102 PREMUL_ALPHA(cp[1], gp[1], gp[3], z2);
103 PREMUL_ALPHA(cp[2], gp[0], gp[3], z3);
104 cp[3] = gp[3];
105#else
106 PREMUL_ALPHA(cp[1], gp[0], gp[3], z1);
107 PREMUL_ALPHA(cp[2], gp[1], gp[3], z2);
108 PREMUL_ALPHA(cp[3], gp[2], gp[3], z3);
109 cp[0] = gp[3];
110#endif
111 gp += 4;
112 cp += 4;
113 }
114 gdkpix += stride;
115 cpix += cstride;
116 }
117#undef PREMUL_ALPHA
118 }
119 cairo_surface_mark_dirty(cs);
120 return cs;
121}
122#endif // HAVE_GDK_PIXBUF
123
124cairo_surface_t *load_background_image(const char *path) {
125 cairo_surface_t *image;
126#if HAVE_GDK_PIXBUF
127 GError *err = NULL;
128 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &err);
129 if (!pixbuf) {
130 sway_log(SWAY_ERROR, "Failed to load background image (%s).",
131 err->message);
132 return NULL;
133 }
134 image = gdk_cairo_image_surface_create_from_pixbuf(pixbuf);
135 g_object_unref(pixbuf);
136#else
137 image = cairo_image_surface_create_from_png(path);
138#endif // HAVE_GDK_PIXBUF
139 if (!image) {
140 sway_log(SWAY_ERROR, "Failed to read background image.");
141 return NULL;
142 }
143 if (cairo_surface_status(image) != CAIRO_STATUS_SUCCESS) {
144 sway_log(SWAY_ERROR, "Failed to read background image: %s."
145#if !HAVE_GDK_PIXBUF
146 "\nSway was compiled without gdk_pixbuf support, so only"
147 "\nPNG images can be loaded. This is the likely cause."
148#endif // !HAVE_GDK_PIXBUF
149 , cairo_status_to_string(cairo_surface_status(image)));
150 return NULL;
151 }
152 return image;
153}
154
155void render_background_image(cairo_t *cairo, cairo_surface_t *image,
156 enum background_mode mode, int buffer_width, int buffer_height) {
157 double width = cairo_image_surface_get_width(image);
158 double height = cairo_image_surface_get_height(image);
159
160 cairo_save(cairo);
161 switch (mode) {
162 case BACKGROUND_MODE_STRETCH:
163 cairo_scale(cairo,
164 (double)buffer_width / width,
165 (double)buffer_height / height);
166 cairo_set_source_surface(cairo, image, 0, 0);
167 break;
168 case BACKGROUND_MODE_FILL: {
169 double window_ratio = (double)buffer_width / buffer_height;
170 double bg_ratio = width / height;
171
172 if (window_ratio > bg_ratio) {
173 double scale = (double)buffer_width / width;
174 cairo_scale(cairo, scale, scale);
175 cairo_set_source_surface(cairo, image,
176 0, (double)buffer_height / 2 / scale - height / 2);
177 } else {
178 double scale = (double)buffer_height / height;
179 cairo_scale(cairo, scale, scale);
180 cairo_set_source_surface(cairo, image,
181 (double)buffer_width / 2 / scale - width / 2, 0);
182 }
183 break;
184 }
185 case BACKGROUND_MODE_FIT: {
186 double window_ratio = (double)buffer_width / buffer_height;
187 double bg_ratio = width / height;
188
189 if (window_ratio > bg_ratio) {
190 double scale = (double)buffer_height / height;
191 cairo_scale(cairo, scale, scale);
192 cairo_set_source_surface(cairo, image,
193 (double)buffer_width / 2 / scale - width / 2, 0);
194 } else {
195 double scale = (double)buffer_width / width;
196 cairo_scale(cairo, scale, scale);
197 cairo_set_source_surface(cairo, image,
198 0, (double)buffer_height / 2 / scale - height / 2);
199 }
200 break;
201 }
202 case BACKGROUND_MODE_CENTER:
203 cairo_set_source_surface(cairo, image,
204 (double)buffer_width / 2 - width / 2,
205 (double)buffer_height / 2 - height / 2);
206 break;
207 case BACKGROUND_MODE_TILE: {
208 cairo_pattern_t *pattern = cairo_pattern_create_for_surface(image);
209 cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
210 cairo_set_source(cairo, pattern);
211 break;
212 }
213 case BACKGROUND_MODE_SOLID_COLOR:
214 case BACKGROUND_MODE_INVALID:
215 assert(0);
216 break;
217 }
218 cairo_paint(cairo);
219 cairo_restore(cairo);
220}
diff --git a/common/cairo.c b/common/cairo.c
index 403dcf49..7c59d48c 100644
--- a/common/cairo.c
+++ b/common/cairo.c
@@ -1,6 +1,6 @@
1#include <stdint.h> 1#include <stdint.h>
2#include <cairo/cairo.h> 2#include <cairo.h>
3#include "cairo.h" 3#include "cairo_util.h"
4 4
5void cairo_set_source_u32(cairo_t *cairo, uint32_t color) { 5void cairo_set_source_u32(cairo_t *cairo, uint32_t color) {
6 cairo_set_source_rgba(cairo, 6 cairo_set_source_rgba(cairo,
diff --git a/common/gesture.c b/common/gesture.c
new file mode 100644
index 00000000..272aa837
--- /dev/null
+++ b/common/gesture.c
@@ -0,0 +1,332 @@
1#include "gesture.h"
2
3#include <math.h>
4#include <stdarg.h>
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8#include "list.h"
9#include "log.h"
10#include "stringop.h"
11
12const uint8_t GESTURE_FINGERS_ANY = 0;
13
14char *gesture_parse(const char *input, struct gesture *output) {
15 // Clear output in case of failure
16 output->type = GESTURE_TYPE_NONE;
17 output->fingers = GESTURE_FINGERS_ANY;
18 output->directions = GESTURE_DIRECTION_NONE;
19
20 // Split input type, fingers and directions
21 list_t *split = split_string(input, ":");
22 if (split->length < 1 || split->length > 3) {
23 return format_str(
24 "expected <gesture>[:<fingers>][:direction], got %s",
25 input);
26 }
27
28 // Parse gesture type
29 if (strcmp(split->items[0], "hold") == 0) {
30 output->type = GESTURE_TYPE_HOLD;
31 } else if (strcmp(split->items[0], "pinch") == 0) {
32 output->type = GESTURE_TYPE_PINCH;
33 } else if (strcmp(split->items[0], "swipe") == 0) {
34 output->type = GESTURE_TYPE_SWIPE;
35 } else {
36 return format_str("expected hold|pinch|swipe, got %s",
37 (const char *)split->items[0]);
38 }
39
40 // Parse optional arguments
41 if (split->length > 1) {
42 char *next = split->items[1];
43
44 // Try to parse as finger count (1-9)
45 if (strlen(next) == 1 && '1' <= next[0] && next[0] <= '9') {
46 output->fingers = atoi(next);
47
48 // Move to next if available
49 next = split->length == 3 ? split->items[2] : NULL;
50 } else if (split->length == 3) {
51 // Fail here if argument can only be finger count
52 return format_str("expected 1-9, got %s", next);
53 }
54
55 // If there is an argument left, try to parse as direction
56 if (next) {
57 list_t *directions = split_string(next, "+");
58
59 for (int i = 0; i < directions->length; ++i) {
60 const char *item = directions->items[i];
61 if (strcmp(item, "any") == 0) {
62 continue;
63 } else if (strcmp(item, "up") == 0) {
64 output->directions |= GESTURE_DIRECTION_UP;
65 } else if (strcmp(item, "down") == 0) {
66 output->directions |= GESTURE_DIRECTION_DOWN;
67 } else if (strcmp(item, "left") == 0) {
68 output->directions |= GESTURE_DIRECTION_LEFT;
69 } else if (strcmp(item, "right") == 0) {
70 output->directions |= GESTURE_DIRECTION_RIGHT;
71 } else if (strcmp(item, "inward") == 0) {
72 output->directions |= GESTURE_DIRECTION_INWARD;
73 } else if (strcmp(item, "outward") == 0) {
74 output->directions |= GESTURE_DIRECTION_OUTWARD;
75 } else if (strcmp(item, "clockwise") == 0) {
76 output->directions |= GESTURE_DIRECTION_CLOCKWISE;
77 } else if (strcmp(item, "counterclockwise") == 0) {
78 output->directions |= GESTURE_DIRECTION_COUNTERCLOCKWISE;
79 } else {
80 return format_str("expected direction, got %s", item);
81 }
82 }
83 list_free_items_and_destroy(directions);
84 }
85 } // if optional args
86
87 list_free_items_and_destroy(split);
88
89 return NULL;
90}
91
92const char *gesture_type_string(enum gesture_type type) {
93 switch (type) {
94 case GESTURE_TYPE_NONE:
95 return "none";
96 case GESTURE_TYPE_HOLD:
97 return "hold";
98 case GESTURE_TYPE_PINCH:
99 return "pinch";
100 case GESTURE_TYPE_SWIPE:
101 return "swipe";
102 }
103
104 return NULL;
105}
106
107const char *gesture_direction_string(enum gesture_direction direction) {
108 switch (direction) {
109 case GESTURE_DIRECTION_NONE:
110 return "none";
111 case GESTURE_DIRECTION_UP:
112 return "up";
113 case GESTURE_DIRECTION_DOWN:
114 return "down";
115 case GESTURE_DIRECTION_LEFT:
116 return "left";
117 case GESTURE_DIRECTION_RIGHT:
118 return "right";
119 case GESTURE_DIRECTION_INWARD:
120 return "inward";
121 case GESTURE_DIRECTION_OUTWARD:
122 return "outward";
123 case GESTURE_DIRECTION_CLOCKWISE:
124 return "clockwise";
125 case GESTURE_DIRECTION_COUNTERCLOCKWISE:
126 return "counterclockwise";
127 }
128
129 return NULL;
130}
131
132// Helper to turn combination of directions flags into string representation.
133static char *gesture_directions_to_string(uint32_t directions) {
134 char *result = NULL;
135
136 for (uint8_t bit = 0; bit < 32; bit++) {
137 uint32_t masked = directions & (1 << bit);
138 if (masked) {
139 const char *name = gesture_direction_string(masked);
140
141 if (!name) {
142 name = "unknown";
143 }
144
145 if (!result) {
146 result = strdup(name);
147 } else {
148 char *new = format_str("%s+%s", result, name);
149 free(result);
150 result = new;
151 }
152 }
153 }
154
155 if(!result) {
156 return strdup("any");
157 }
158
159 return result;
160}
161
162char *gesture_to_string(struct gesture *gesture) {
163 char *directions = gesture_directions_to_string(gesture->directions);
164 char *result = format_str("%s:%u:%s",
165 gesture_type_string(gesture->type),
166 gesture->fingers, directions);
167 free(directions);
168 return result;
169}
170
171bool gesture_check(struct gesture *target, enum gesture_type type, uint8_t fingers) {
172 // Check that gesture type matches
173 if (target->type != type) {
174 return false;
175 }
176
177 // Check that finger count matches
178 if (target->fingers != GESTURE_FINGERS_ANY && target->fingers != fingers) {
179 return false;
180 }
181
182 return true;
183}
184
185bool gesture_match(struct gesture *target, struct gesture *to_match, bool exact) {
186 // Check type and fingers
187 if (!gesture_check(target, to_match->type, to_match->fingers)) {
188 return false;
189 }
190
191 // Enforce exact matches ...
192 if (exact && target->directions != to_match->directions) {
193 return false;
194 }
195
196 // ... or ensure all target directions are matched
197 return (target->directions & to_match->directions) == target->directions;
198}
199
200bool gesture_equal(struct gesture *a, struct gesture *b) {
201 return a->type == b->type
202 && a->fingers == b->fingers
203 && a->directions == b->directions;
204}
205
206// Return count of set bits in directions bit field.
207static uint8_t gesture_directions_count(uint32_t directions) {
208 uint8_t count = 0;
209 for (; directions; directions >>= 1) {
210 count += directions & 1;
211 }
212 return count;
213}
214
215// Compare direction bit count of two direction.
216static int8_t gesture_directions_compare(uint32_t a, uint32_t b) {
217 return gesture_directions_count(a) - gesture_directions_count(b);
218}
219
220// Compare two direction based on their direction bit count
221int8_t gesture_compare(struct gesture *a, struct gesture *b) {
222 return gesture_directions_compare(a->directions, b->directions);
223}
224
225void gesture_tracker_begin(struct gesture_tracker *tracker,
226 enum gesture_type type, uint8_t fingers) {
227 tracker->type = type;
228 tracker->fingers = fingers;
229
230 tracker->dx = 0.0;
231 tracker->dy = 0.0;
232 tracker->scale = 1.0;
233 tracker->rotation = 0.0;
234
235 sway_log(SWAY_DEBUG, "begin tracking %s:%u:? gesture",
236 gesture_type_string(type), fingers);
237}
238
239bool gesture_tracker_check(struct gesture_tracker *tracker, enum gesture_type type) {
240 return tracker->type == type;
241}
242
243void gesture_tracker_update(struct gesture_tracker *tracker,
244 double dx, double dy, double scale, double rotation) {
245 if (tracker->type == GESTURE_TYPE_HOLD) {
246 sway_assert(false, "hold does not update.");
247 return;
248 }
249
250 tracker->dx += dx;
251 tracker->dy += dy;
252
253 if (tracker->type == GESTURE_TYPE_PINCH) {
254 tracker->scale = scale;
255 tracker->rotation += rotation;
256 }
257
258 sway_log(SWAY_DEBUG, "update tracking %s:%u:? gesture: %f %f %f %f",
259 gesture_type_string(tracker->type),
260 tracker->fingers,
261 tracker->dx, tracker->dy,
262 tracker->scale, tracker->rotation);
263}
264
265void gesture_tracker_cancel(struct gesture_tracker *tracker) {
266 sway_log(SWAY_DEBUG, "cancel tracking %s:%u:? gesture",
267 gesture_type_string(tracker->type), tracker->fingers);
268
269 tracker->type = GESTURE_TYPE_NONE;
270}
271
272struct gesture *gesture_tracker_end(struct gesture_tracker *tracker) {
273 struct gesture *result = calloc(1, sizeof(struct gesture));
274
275 // Ignore gesture under some threshold
276 // TODO: Make configurable
277 const double min_rotation = 5;
278 const double min_scale_delta = 0.1;
279
280 // Determine direction
281 switch(tracker->type) {
282 // Gestures with scale and rotation
283 case GESTURE_TYPE_PINCH:
284 if (tracker->rotation > min_rotation) {
285 result->directions |= GESTURE_DIRECTION_CLOCKWISE;
286 }
287 if (tracker->rotation < -min_rotation) {
288 result->directions |= GESTURE_DIRECTION_COUNTERCLOCKWISE;
289 }
290
291 if (tracker->scale > (1.0 + min_scale_delta)) {
292 result->directions |= GESTURE_DIRECTION_OUTWARD;
293 }
294 if (tracker->scale < (1.0 - min_scale_delta)) {
295 result->directions |= GESTURE_DIRECTION_INWARD;
296 }
297 __attribute__ ((fallthrough));
298 // Gestures with dx and dy
299 case GESTURE_TYPE_SWIPE:
300 if (fabs(tracker->dx) > fabs(tracker->dy)) {
301 if (tracker->dx > 0) {
302 result->directions |= GESTURE_DIRECTION_RIGHT;
303 } else {
304 result->directions |= GESTURE_DIRECTION_LEFT;
305 }
306 } else {
307 if (tracker->dy > 0) {
308 result->directions |= GESTURE_DIRECTION_DOWN;
309 } else {
310 result->directions |= GESTURE_DIRECTION_UP;
311 }
312 }
313 // Gesture without any direction
314 case GESTURE_TYPE_HOLD:
315 break;
316 // Not tracking any gesture
317 case GESTURE_TYPE_NONE:
318 sway_assert(false, "Not tracking any gesture.");
319 return result;
320 }
321
322 result->type = tracker->type;
323 result->fingers = tracker->fingers;
324
325 char *description = gesture_to_string(result);
326 sway_log(SWAY_DEBUG, "end tracking gesture: %s", description);
327 free(description);
328
329 tracker->type = GESTURE_TYPE_NONE;
330
331 return result;
332}
diff --git a/common/ipc-client.c b/common/ipc-client.c
index d30212d2..a0be2b2d 100644
--- a/common/ipc-client.c
+++ b/common/ipc-client.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdio.h> 1#include <stdio.h>
3#include <stdint.h> 2#include <stdint.h>
4#include <stdlib.h> 3#include <stdlib.h>
diff --git a/common/log.c b/common/log.c
index 483420e7..3eacdb34 100644
--- a/common/log.c
+++ b/common/log.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200112L
2#include <signal.h> 1#include <signal.h>
3#include <stdarg.h> 2#include <stdarg.h>
4#include <stdio.h> 3#include <stdio.h>
diff --git a/common/loop.c b/common/loop.c
index 80fe18ea..b99c6d55 100644
--- a/common/loop.c
+++ b/common/loop.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200112L
2#include <limits.h> 1#include <limits.h>
3#include <string.h> 2#include <string.h>
4#include <stdbool.h> 3#include <stdbool.h>
diff --git a/common/meson.build b/common/meson.build
index c653dd72..c0ce1f68 100644
--- a/common/meson.build
+++ b/common/meson.build
@@ -1,8 +1,8 @@
1lib_sway_common = static_library( 1lib_sway_common = static_library(
2 'sway-common', 2 'sway-common',
3 files( 3 files(
4 'background-image.c',
5 'cairo.c', 4 'cairo.c',
5 'gesture.c',
6 'ipc-client.c', 6 'ipc-client.c',
7 'log.c', 7 'log.c',
8 'loop.c', 8 'loop.c',
@@ -13,7 +13,6 @@ lib_sway_common = static_library(
13 ), 13 ),
14 dependencies: [ 14 dependencies: [
15 cairo, 15 cairo,
16 gdk_pixbuf,
17 pango, 16 pango,
18 pangocairo, 17 pangocairo,
19 wayland_client.partial_dependency(compile_args: true) 18 wayland_client.partial_dependency(compile_args: true)
diff --git a/common/pango.c b/common/pango.c
index fc3d0688..288569b3 100644
--- a/common/pango.c
+++ b/common/pango.c
@@ -1,4 +1,4 @@
1#include <cairo/cairo.h> 1#include <cairo.h>
2#include <pango/pangocairo.h> 2#include <pango/pangocairo.h>
3#include <stdarg.h> 3#include <stdarg.h>
4#include <stdbool.h> 4#include <stdbool.h>
@@ -6,7 +6,7 @@
6#include <stdio.h> 6#include <stdio.h>
7#include <stdlib.h> 7#include <stdlib.h>
8#include <string.h> 8#include <string.h>
9#include "cairo.h" 9#include "cairo_util.h"
10#include "log.h" 10#include "log.h"
11#include "stringop.h" 11#include "stringop.h"
12 12
@@ -50,7 +50,7 @@ size_t escape_markup_text(const char *src, char *dest) {
50 return length; 50 return length;
51} 51}
52 52
53PangoLayout *get_pango_layout(cairo_t *cairo, const char *font, 53PangoLayout *get_pango_layout(cairo_t *cairo, const PangoFontDescription *desc,
54 const char *text, double scale, bool markup) { 54 const char *text, double scale, bool markup) {
55 PangoLayout *layout = pango_cairo_create_layout(cairo); 55 PangoLayout *layout = pango_cairo_create_layout(cairo);
56 PangoAttrList *attrs; 56 PangoAttrList *attrs;
@@ -73,60 +73,59 @@ PangoLayout *get_pango_layout(cairo_t *cairo, const char *font,
73 } 73 }
74 74
75 pango_attr_list_insert(attrs, pango_attr_scale_new(scale)); 75 pango_attr_list_insert(attrs, pango_attr_scale_new(scale));
76 PangoFontDescription *desc = pango_font_description_from_string(font);
77 pango_layout_set_font_description(layout, desc); 76 pango_layout_set_font_description(layout, desc);
78 pango_layout_set_single_paragraph_mode(layout, 1); 77 pango_layout_set_single_paragraph_mode(layout, 1);
79 pango_layout_set_attributes(layout, attrs); 78 pango_layout_set_attributes(layout, attrs);
80 pango_attr_list_unref(attrs); 79 pango_attr_list_unref(attrs);
81 pango_font_description_free(desc);
82 return layout; 80 return layout;
83} 81}
84 82
85void get_text_size(cairo_t *cairo, const char *font, int *width, int *height, 83void get_text_size(cairo_t *cairo, const PangoFontDescription *desc, int *width, int *height,
86 int *baseline, double scale, bool markup, const char *fmt, ...) { 84 int *baseline, double scale, bool markup, const char *fmt, ...) {
87 va_list args; 85 va_list args;
88 va_start(args, fmt); 86 va_start(args, fmt);
89 // Add one since vsnprintf excludes null terminator. 87 char *buf = vformat_str(fmt, args);
90 int length = vsnprintf(NULL, 0, fmt, args) + 1;
91 va_end(args); 88 va_end(args);
92
93 char *buf = malloc(length);
94 if (buf == NULL) { 89 if (buf == NULL) {
95 sway_log(SWAY_ERROR, "Failed to allocate memory");
96 return; 90 return;
97 } 91 }
98 va_start(args, fmt);
99 vsnprintf(buf, length, fmt, args);
100 va_end(args);
101 92
102 PangoLayout *layout = get_pango_layout(cairo, font, buf, scale, markup); 93 PangoLayout *layout = get_pango_layout(cairo, desc, buf, scale, markup);
103 pango_cairo_update_layout(cairo, layout); 94 pango_cairo_update_layout(cairo, layout);
104 pango_layout_get_pixel_size(layout, width, height); 95 pango_layout_get_pixel_size(layout, width, height);
105 if (baseline) { 96 if (baseline) {
106 *baseline = pango_layout_get_baseline(layout) / PANGO_SCALE; 97 *baseline = pango_layout_get_baseline(layout) / PANGO_SCALE;
107 } 98 }
108 g_object_unref(layout); 99 g_object_unref(layout);
100
109 free(buf); 101 free(buf);
110} 102}
111 103
112void pango_printf(cairo_t *cairo, const char *font, 104void get_text_metrics(const PangoFontDescription *description, int *height, int *baseline) {
105 cairo_t *cairo = cairo_create(NULL);
106 PangoContext *pango = pango_cairo_create_context(cairo);
107 // When passing NULL as a language, pango uses the current locale.
108 PangoFontMetrics *metrics = pango_context_get_metrics(pango, description, NULL);
109
110 *baseline = pango_font_metrics_get_ascent(metrics) / PANGO_SCALE;
111 *height = *baseline + pango_font_metrics_get_descent(metrics) / PANGO_SCALE;
112
113 pango_font_metrics_unref(metrics);
114 g_object_unref(pango);
115 cairo_destroy(cairo);
116}
117
118void render_text(cairo_t *cairo, const PangoFontDescription *desc,
113 double scale, bool markup, const char *fmt, ...) { 119 double scale, bool markup, const char *fmt, ...) {
114 va_list args; 120 va_list args;
115 va_start(args, fmt); 121 va_start(args, fmt);
116 // Add one since vsnprintf excludes null terminator. 122 char *buf = vformat_str(fmt, args);
117 int length = vsnprintf(NULL, 0, fmt, args) + 1;
118 va_end(args); 123 va_end(args);
119
120 char *buf = malloc(length);
121 if (buf == NULL) { 124 if (buf == NULL) {
122 sway_log(SWAY_ERROR, "Failed to allocate memory");
123 return; 125 return;
124 } 126 }
125 va_start(args, fmt);
126 vsnprintf(buf, length, fmt, args);
127 va_end(args);
128 127
129 PangoLayout *layout = get_pango_layout(cairo, font, buf, scale, markup); 128 PangoLayout *layout = get_pango_layout(cairo, desc, buf, scale, markup);
130 cairo_font_options_t *fo = cairo_font_options_create(); 129 cairo_font_options_t *fo = cairo_font_options_create();
131 cairo_get_font_options(cairo, fo); 130 cairo_get_font_options(cairo, fo);
132 pango_cairo_context_set_font_options(pango_layout_get_context(layout), fo); 131 pango_cairo_context_set_font_options(pango_layout_get_context(layout), fo);
@@ -134,5 +133,6 @@ void pango_printf(cairo_t *cairo, const char *font,
134 pango_cairo_update_layout(cairo, layout); 133 pango_cairo_update_layout(cairo, layout);
135 pango_cairo_show_layout(cairo, layout); 134 pango_cairo_show_layout(cairo, layout);
136 g_object_unref(layout); 135 g_object_unref(layout);
136
137 free(buf); 137 free(buf);
138} 138}
diff --git a/common/stringop.c b/common/stringop.c
index 7fb3fe12..16d04917 100644
--- a/common/stringop.c
+++ b/common/stringop.c
@@ -1,5 +1,5 @@
1#define _POSIX_C_SOURCE 200809L
2#include <ctype.h> 1#include <ctype.h>
2#include <stdarg.h>
3#include <stdbool.h> 3#include <stdbool.h>
4#include <stdio.h> 4#include <stdio.h>
5#include <stdlib.h> 5#include <stdlib.h>
@@ -328,3 +328,35 @@ bool expand_path(char **path) {
328 wordfree(&p); 328 wordfree(&p);
329 return true; 329 return true;
330} 330}
331
332char *vformat_str(const char *fmt, va_list args) {
333 char *str = NULL;
334 va_list args_copy;
335 va_copy(args_copy, args);
336
337 int len = vsnprintf(NULL, 0, fmt, args);
338 if (len < 0) {
339 sway_log_errno(SWAY_ERROR, "vsnprintf(\"%s\") failed", fmt);
340 goto out;
341 }
342
343 str = malloc(len + 1);
344 if (str == NULL) {
345 sway_log_errno(SWAY_ERROR, "malloc() failed");
346 goto out;
347 }
348
349 vsnprintf(str, len + 1, fmt, args_copy);
350
351out:
352 va_end(args_copy);
353 return str;
354}
355
356char *format_str(const char *fmt, ...) {
357 va_list args;
358 va_start(args, fmt);
359 char *str = vformat_str(fmt, args);
360 va_end(args);
361 return str;
362}
diff --git a/common/util.c b/common/util.c
index 5ea94f48..7c492bcb 100644
--- a/common/util.c
+++ b/common/util.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <ctype.h> 1#include <ctype.h>
3#include <fcntl.h> 2#include <fcntl.h>
4#include <math.h> 3#include <math.h>
@@ -10,12 +9,6 @@
10#include "log.h" 9#include "log.h"
11#include "util.h" 10#include "util.h"
12 11
13uint32_t get_current_time_msec(void) {
14 struct timespec now;
15 clock_gettime(CLOCK_MONOTONIC, &now);
16 return now.tv_sec * 1000 + now.tv_nsec / 1000000;
17}
18
19int wrap(int i, int max) { 12int wrap(int i, int max) {
20 return ((i % max) + max) % max; 13 return ((i % max) + max) % max;
21} 14}
@@ -86,6 +79,12 @@ enum movement_unit parse_movement_unit(const char *unit) {
86 79
87int parse_movement_amount(int argc, char **argv, 80int parse_movement_amount(int argc, char **argv,
88 struct movement_amount *amount) { 81 struct movement_amount *amount) {
82 if (!sway_assert(argc > 0, "Expected args in parse_movement_amount")) {
83 amount->amount = 0;
84 amount->unit = MOVEMENT_UNIT_INVALID;
85 return 0;
86 }
87
89 char *err; 88 char *err;
90 amount->amount = (int)strtol(argv[0], &err, 10); 89 amount->amount = (int)strtol(argv[0], &err, 10);
91 if (*err) { 90 if (*err) {