1 /* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3 Copyright (C) 2010 Red Hat, Inc.
4 Copyright © 2006-2010 Collabora Ltd. <http://www.collabora.co.uk/>
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public
17 License along with this library; if not, see <http://www.gnu.org/licenses/>.
18 */
19 #include "config.h"
20
21 #include <stdbool.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <glib.h>
25 #include <glib-object.h>
26 #include "spice-util-priv.h"
27 #include "spice-util.h"
28 #include "spice-util-priv.h"
29
30 /**
31 * SECTION:spice-util
32 * @short_description: version and debugging functions
33 * @title: Utilities
34 * @section_id:
35 * @stability: Stable
36 * @include: spice-client.h
37 *
38 * Various functions for debugging and informational purposes.
39 */
40
41 static GOnce debug_once = G_ONCE_INIT;
42
spice_util_enable_debug_messages(void)43 static void spice_util_enable_debug_messages(void)
44 {
45 const gchar *doms = g_getenv("G_MESSAGES_DEBUG");
46 if (!doms) {
47 g_setenv("G_MESSAGES_DEBUG", G_LOG_DOMAIN, 1);
48 } else if (g_str_equal(doms, "all")) {
49 return;
50 } else if (!strstr(doms, G_LOG_DOMAIN)) {
51 gchar *newdoms = g_strdup_printf("%s %s", doms, G_LOG_DOMAIN);
52 g_setenv("G_MESSAGES_DEBUG", newdoms, 1);
53 g_free(newdoms);
54 }
55 }
56
57 /**
58 * spice_util_set_debug:
59 * @enabled: %TRUE or %FALSE
60 *
61 * Enable or disable Spice-GTK debugging messages.
62 **/
spice_util_set_debug(gboolean enabled)63 void spice_util_set_debug(gboolean enabled)
64 {
65 /* Make sure debug_once has been initialised
66 * with the value of SPICE_DEBUG already, otherwise
67 * spice_util_get_debug() may overwrite the value
68 * that was just set using spice_util_set_debug()
69 */
70 spice_util_get_debug();
71
72 if (enabled) {
73 spice_util_enable_debug_messages();
74 }
75
76 debug_once.retval = GINT_TO_POINTER(enabled);
77 }
78
getenv_debug(gpointer data)79 static gpointer getenv_debug(gpointer data)
80 {
81 gboolean debug;
82
83 debug = (g_getenv("SPICE_DEBUG") != NULL);
84 if (debug)
85 spice_util_enable_debug_messages();
86
87 return GINT_TO_POINTER(debug);
88 }
89
spice_util_get_debug(void)90 gboolean spice_util_get_debug(void)
91 {
92 g_once(&debug_once, getenv_debug, NULL);
93
94 return GPOINTER_TO_INT(debug_once.retval);
95 }
96
97 /**
98 * spice_util_get_version_string:
99 *
100 * Gets the version string
101 *
102 * Returns: Spice-GTK version as a const string.
103 **/
spice_util_get_version_string(void)104 const gchar *spice_util_get_version_string(void)
105 {
106 return VERSION;
107 }
108
109 G_GNUC_INTERNAL
spice_strv_contains(const GStrv strv,const gchar * str)110 gboolean spice_strv_contains(const GStrv strv, const gchar *str)
111 {
112 int i;
113
114 if (strv == NULL)
115 return FALSE;
116
117 for (i = 0; strv[i] != NULL; i++)
118 if (g_str_equal(strv[i], str))
119 return TRUE;
120
121 return FALSE;
122 }
123
124 /**
125 * spice_uuid_to_string:
126 * @uuid: UUID byte array
127 *
128 * Creates a string representation of @uuid, of the form
129 * "06e023d5-86d8-420e-8103-383e4566087a"
130 *
131 * Returns: A string that should be freed with g_free().
132 * Since: 0.22
133 **/
spice_uuid_to_string(const guint8 uuid[16])134 gchar* spice_uuid_to_string(const guint8 uuid[16])
135 {
136 return g_strdup_printf(UUID_FMT, uuid[0], uuid[1],
137 uuid[2], uuid[3], uuid[4], uuid[5],
138 uuid[6], uuid[7], uuid[8], uuid[9],
139 uuid[10], uuid[11], uuid[12], uuid[13],
140 uuid[14], uuid[15]);
141 }
142
143 typedef struct {
144 GObject *instance;
145 GObject *observer;
146 GClosure *closure;
147 gulong handler_id;
148 } WeakHandlerCtx;
149
150 static WeakHandlerCtx *
whc_new(GObject * instance,GObject * observer)151 whc_new (GObject *instance,
152 GObject *observer)
153 {
154 WeakHandlerCtx *ctx = g_new0 (WeakHandlerCtx, 1);
155
156 ctx->instance = instance;
157 ctx->observer = observer;
158
159 return ctx;
160 }
161
162 static void
whc_free(WeakHandlerCtx * ctx)163 whc_free (WeakHandlerCtx *ctx)
164 {
165 g_free (ctx);
166 }
167
168 static void observer_destroyed_cb (gpointer, GObject *);
169 static void closure_invalidated_cb (gpointer, GClosure *);
170
171 /*
172 * If signal handlers are removed before the object is destroyed, this
173 * callback will never get triggered.
174 */
175 static void
instance_destroyed_cb(gpointer ctx_,GObject * where_the_instance_was)176 instance_destroyed_cb (gpointer ctx_,
177 GObject *where_the_instance_was)
178 {
179 WeakHandlerCtx *ctx = ctx_;
180
181 /* No need to disconnect the signal here, the instance has gone away. */
182 g_object_weak_unref (ctx->observer, observer_destroyed_cb, ctx);
183 g_closure_remove_invalidate_notifier (ctx->closure, ctx,
184 closure_invalidated_cb);
185 whc_free (ctx);
186 }
187
188 /* Triggered when the observer is destroyed. */
189 static void
observer_destroyed_cb(gpointer ctx_,GObject * where_the_observer_was)190 observer_destroyed_cb (gpointer ctx_,
191 GObject *where_the_observer_was)
192 {
193 WeakHandlerCtx *ctx = ctx_;
194
195 g_closure_remove_invalidate_notifier (ctx->closure, ctx,
196 closure_invalidated_cb);
197 g_signal_handler_disconnect (ctx->instance, ctx->handler_id);
198 g_object_weak_unref (ctx->instance, instance_destroyed_cb, ctx);
199 whc_free (ctx);
200 }
201
202 /* Triggered when either object is destroyed or the handler is disconnected. */
203 static void
closure_invalidated_cb(gpointer ctx_,GClosure * where_the_closure_was)204 closure_invalidated_cb (gpointer ctx_,
205 GClosure *where_the_closure_was)
206 {
207 WeakHandlerCtx *ctx = ctx_;
208
209 g_object_weak_unref (ctx->instance, instance_destroyed_cb, ctx);
210 g_object_weak_unref (ctx->observer, observer_destroyed_cb, ctx);
211 whc_free (ctx);
212 }
213
214 /* Copied from tp_g_signal_connect_object. See documentation. */
215 /**
216 * spice_g_signal_connect_object: (skip)
217 * @instance: the instance to connect to.
218 * @detailed_signal: a string of the form "signal-name::detail".
219 * @c_handler: the #GCallback to connect.
220 * @gobject: the object to pass as data to @c_handler.
221 * @connect_flags: a combination of #GConnectFlags.
222 *
223 * Similar to g_signal_connect_object() but will delete connection
224 * when any of the objects is destroyed.
225 *
226 * Returns: the handler id.
227 */
spice_g_signal_connect_object(gpointer instance,const gchar * detailed_signal,GCallback c_handler,gpointer gobject,GConnectFlags connect_flags)228 gulong spice_g_signal_connect_object (gpointer instance,
229 const gchar *detailed_signal,
230 GCallback c_handler,
231 gpointer gobject,
232 GConnectFlags connect_flags)
233 {
234 GObject *instance_obj = G_OBJECT (instance);
235 WeakHandlerCtx *ctx = whc_new (instance_obj, gobject);
236
237 g_return_val_if_fail (G_TYPE_CHECK_INSTANCE (instance), 0);
238 g_return_val_if_fail (detailed_signal != NULL, 0);
239 g_return_val_if_fail (c_handler != NULL, 0);
240 g_return_val_if_fail (G_IS_OBJECT (gobject), 0);
241 g_return_val_if_fail (
242 (connect_flags & ~(G_CONNECT_AFTER|G_CONNECT_SWAPPED)) == 0, 0);
243
244 if (connect_flags & G_CONNECT_SWAPPED)
245 ctx->closure = g_cclosure_new_object_swap (c_handler, gobject);
246 else
247 ctx->closure = g_cclosure_new_object (c_handler, gobject);
248
249 ctx->handler_id = g_signal_connect_closure (instance, detailed_signal,
250 ctx->closure, (connect_flags & G_CONNECT_AFTER) ? TRUE : FALSE);
251
252 g_object_weak_ref (instance_obj, instance_destroyed_cb, ctx);
253 g_object_weak_ref (gobject, observer_destroyed_cb, ctx);
254 g_closure_add_invalidate_notifier (ctx->closure, ctx,
255 closure_invalidated_cb);
256
257 return ctx->handler_id;
258 }
259
260 G_GNUC_INTERNAL
spice_yes_no(gboolean value)261 const gchar* spice_yes_no(gboolean value)
262 {
263 return value ? "yes" : "no";
264 }
265
266 G_GNUC_INTERNAL
spice_make_scancode(guint scancode,gboolean release)267 guint16 spice_make_scancode(guint scancode, gboolean release)
268 {
269 SPICE_DEBUG("%s: %s scancode %u",
270 __FUNCTION__, release ? "release" : "", scancode);
271
272 scancode &= 0x37f;
273 if (release)
274 scancode |= 0x80;
275 if (scancode < 0x100)
276 return scancode;
277 return GUINT16_SWAP_LE_BE(0xe000 | (scancode - 0x100));
278 }
279
280 typedef enum {
281 NEWLINE_TYPE_LF,
282 NEWLINE_TYPE_CR_LF
283 } NewlineType;
284
get_line(const gchar * str,gsize len,NewlineType type,gsize * nl_len)285 static gssize get_line(const gchar *str, gsize len,
286 NewlineType type, gsize *nl_len)
287 {
288 const gchar *p, *endl;
289 gsize nl = 0;
290
291 endl = (type == NEWLINE_TYPE_CR_LF) ? "\r\n" : "\n";
292 p = g_strstr_len(str, len, endl);
293 if (p) {
294 len = p - str;
295 nl = strlen(endl);
296 }
297
298 *nl_len = nl;
299 return len;
300 }
301
302
spice_convert_newlines(const gchar * str,gssize len,NewlineType from,NewlineType to)303 static gchar* spice_convert_newlines(const gchar *str, gssize len,
304 NewlineType from,
305 NewlineType to)
306 {
307 gssize length;
308 gsize nl;
309 GString *output;
310 gint i;
311
312 g_return_val_if_fail(str != NULL, NULL);
313 g_return_val_if_fail(len >= -1, NULL);
314 /* only 2 supported combinations */
315 g_return_val_if_fail((from == NEWLINE_TYPE_LF &&
316 to == NEWLINE_TYPE_CR_LF) ||
317 (from == NEWLINE_TYPE_CR_LF &&
318 to == NEWLINE_TYPE_LF), NULL);
319
320 if (len == -1)
321 len = strlen(str);
322 /* sometime we get \0 terminated strings, skip that, or it fails
323 to utf8 validate line with \0 end */
324 else if (len > 0 && str[len-1] == 0)
325 len -= 1;
326
327 /* allocate worst case, if it's small enough, we don't care much,
328 * if it's big, malloc will put us in mmap'd region, and we can
329 * over allocate.
330 */
331 output = g_string_sized_new(len * 2 + 1);
332
333 for (i = 0; i < len; i += length + nl) {
334 length = get_line(str + i, len - i, from, &nl);
335 if (length < 0)
336 break;
337
338 g_string_append_len(output, str + i, length);
339
340 if (nl) {
341 /* let's not double \r if it's already in the line */
342 if (to == NEWLINE_TYPE_CR_LF &&
343 (output->len == 0 || output->str[output->len - 1] != '\r'))
344 g_string_append_c(output, '\r');
345
346 g_string_append_c(output, '\n');
347 }
348 }
349
350 return g_string_free(output, FALSE);
351 }
352
353 G_GNUC_INTERNAL
spice_dos2unix(const gchar * str,gssize len)354 gchar* spice_dos2unix(const gchar *str, gssize len)
355 {
356 return spice_convert_newlines(str, len,
357 NEWLINE_TYPE_CR_LF,
358 NEWLINE_TYPE_LF);
359 }
360
361 G_GNUC_INTERNAL
spice_unix2dos(const gchar * str,gssize len)362 gchar* spice_unix2dos(const gchar *str, gssize len)
363 {
364 return spice_convert_newlines(str, len,
365 NEWLINE_TYPE_LF,
366 NEWLINE_TYPE_CR_LF);
367 }
368
buf_is_ones(unsigned size,const guint8 * data)369 static bool buf_is_ones(unsigned size, const guint8 *data)
370 {
371 int i;
372
373 for (i = 0 ; i < size; ++i) {
374 if (data[i] != 0xff) {
375 return false;
376 }
377 }
378 return true;
379 }
380
is_edge_helper(const guint8 * xor,int bpl,int x,int y)381 static bool is_edge_helper(const guint8 *xor, int bpl, int x, int y)
382 {
383 return (xor[bpl * y + (x / 8)] & (0x80 >> (x % 8))) > 0;
384 }
385
is_edge(unsigned width,unsigned height,const guint8 * xor,int bpl,int x,int y)386 static bool is_edge(unsigned width, unsigned height, const guint8 *xor, int bpl, int x, int y)
387 {
388 if (x == 0 || x == width -1 || y == 0 || y == height - 1) {
389 return 0;
390 }
391 #define P(x, y) is_edge_helper(xor, bpl, x, y)
392 return !P(x, y) && (P(x - 1, y + 1) || P(x, y + 1) || P(x + 1, y + 1) ||
393 P(x - 1, y) || P(x + 1, y) ||
394 P(x - 1, y - 1) || P(x, y - 1) || P(x + 1, y - 1));
395 #undef P
396 }
397
398 /* Mono cursors have two places, "and" and "xor". If a bit is 1 in both, it
399 * means invertion of the corresponding pixel in the display. Since X11 (and
400 * gdk) doesn't do invertion, instead we do edge detection and turn the
401 * sorrounding edge pixels black, and the invert-me pixels white. To
402 * illustrate:
403 *
404 * and xor dest RGB (1=0xffffff, 0=0x000000)
405 *
406 * dest alpha (1=0xff, 0=0x00)
407 *
408 * 11111 00000 00000 00000
409 * 11111 00000 00000 01110
410 * 11111 00100 => 00100 01110
411 * 11111 00100 00100 01110
412 * 11111 00000 00000 01110
413 * 11111 00000 00000 00000
414 *
415 * See tests/util.c for more tests
416 *
417 * Notes:
418 * Assumes width >= 8 (i.e. bytes per line is at least 1)
419 * Assumes edges are not on the boundary (first/last line/column) for simplicity
420 *
421 */
422 G_GNUC_INTERNAL
spice_mono_edge_highlight(unsigned width,unsigned height,const guint8 * and,const guint8 * xor,guint8 * dest)423 void spice_mono_edge_highlight(unsigned width, unsigned height,
424 const guint8 *and, const guint8 *xor, guint8 *dest)
425 {
426 int bpl = (width + 7) / 8;
427 bool and_ones = buf_is_ones(height * bpl, and);
428 int x, y, bit;
429 const guint8 *xor_base = xor;
430
431 for (y = 0; y < height; y++) {
432 bit = 0x80;
433 for (x = 0; x < width; x++, dest += 4) {
434 if (is_edge(width, height, xor_base, bpl, x, y) && and_ones) {
435 dest[0] = 0x00;
436 dest[1] = 0x00;
437 dest[2] = 0x00;
438 dest[3] = 0xff;
439 goto next_bit;
440 }
441 if (and[x/8] & bit) {
442 if (xor[x/8] & bit) {
443 dest[0] = 0xff;
444 dest[1] = 0xff;
445 dest[2] = 0xff;
446 dest[3] = 0xff;
447 } else {
448 /* unchanged -> transparent */
449 dest[0] = 0x00;
450 dest[1] = 0x00;
451 dest[2] = 0x00;
452 dest[3] = 0x00;
453 }
454 } else {
455 if (xor[x/8] & bit) {
456 /* set -> white */
457 dest[0] = 0xff;
458 dest[1] = 0xff;
459 dest[2] = 0xff;
460 dest[3] = 0xff;
461 } else {
462 /* clear -> black */
463 dest[0] = 0x00;
464 dest[1] = 0x00;
465 dest[2] = 0x00;
466 dest[3] = 0xff;
467 }
468 }
469 next_bit:
470 bit >>= 1;
471 if (bit == 0) {
472 bit = 0x80;
473 }
474 }
475 and += bpl;
476 xor += bpl;
477 }
478 }
479