/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* Copyright (C) 2010 Red Hat, Inc. Copyright © 2006-2010 Collabora Ltd. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see . */ #include "config.h" #include #include #include #include #include #include "spice-util-priv.h" #include "spice-util.h" #include "spice-util-priv.h" /** * SECTION:spice-util * @short_description: version and debugging functions * @title: Utilities * @section_id: * @stability: Stable * @include: spice-client.h * * Various functions for debugging and informational purposes. */ static GOnce debug_once = G_ONCE_INIT; static void spice_util_enable_debug_messages(void) { #if GLIB_CHECK_VERSION(2, 31, 0) const gchar *doms = g_getenv("G_MESSAGES_DEBUG"); if (!doms) { g_setenv("G_MESSAGES_DEBUG", G_LOG_DOMAIN, 1); } else if (g_str_equal(doms, "all")) { return; } else if (!strstr(doms, G_LOG_DOMAIN)) { gchar *newdoms = g_strdup_printf("%s %s", doms, G_LOG_DOMAIN); g_setenv("G_MESSAGES_DEBUG", newdoms, 1); g_free(newdoms); } #endif } /** * spice_util_set_debug: * @enabled: %TRUE or %FALSE * * Enable or disable Spice-GTK debugging messages. **/ void spice_util_set_debug(gboolean enabled) { /* Make sure debug_once has been initialised * with the value of SPICE_DEBUG already, otherwise * spice_util_get_debug() may overwrite the value * that was just set using spice_util_set_debug() */ spice_util_get_debug(); if (enabled) { spice_util_enable_debug_messages(); } debug_once.retval = GINT_TO_POINTER(enabled); } static gpointer getenv_debug(gpointer data) { gboolean debug; debug = (g_getenv("SPICE_DEBUG") != NULL); if (debug) spice_util_enable_debug_messages(); return GINT_TO_POINTER(debug); } gboolean spice_util_get_debug(void) { g_once(&debug_once, getenv_debug, NULL); return GPOINTER_TO_INT(debug_once.retval); } /** * spice_util_get_version_string: * * Gets the version string * * Returns: Spice-GTK version as a const string. **/ const gchar *spice_util_get_version_string(void) { return VERSION; } G_GNUC_INTERNAL gboolean spice_strv_contains(const GStrv strv, const gchar *str) { int i; if (strv == NULL) return FALSE; for (i = 0; strv[i] != NULL; i++) if (g_str_equal(strv[i], str)) return TRUE; return FALSE; } /** * spice_uuid_to_string: * @uuid: UUID byte array * * Creates a string representation of @uuid, of the form * "06e023d5-86d8-420e-8103-383e4566087a" * * Returns: A string that should be freed with g_free(). * Since: 0.22 **/ gchar* spice_uuid_to_string(const guint8 uuid[16]) { return g_strdup_printf(UUID_FMT, uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7], uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15]); } typedef struct { GObject *instance; GObject *observer; GClosure *closure; gulong handler_id; } WeakHandlerCtx; static WeakHandlerCtx * whc_new (GObject *instance, GObject *observer) { WeakHandlerCtx *ctx = g_slice_new0 (WeakHandlerCtx); ctx->instance = instance; ctx->observer = observer; return ctx; } static void whc_free (WeakHandlerCtx *ctx) { g_slice_free (WeakHandlerCtx, ctx); } static void observer_destroyed_cb (gpointer, GObject *); static void closure_invalidated_cb (gpointer, GClosure *); /* * If signal handlers are removed before the object is destroyed, this * callback will never get triggered. */ static void instance_destroyed_cb (gpointer ctx_, GObject *where_the_instance_was) { WeakHandlerCtx *ctx = ctx_; /* No need to disconnect the signal here, the instance has gone away. */ g_object_weak_unref (ctx->observer, observer_destroyed_cb, ctx); g_closure_remove_invalidate_notifier (ctx->closure, ctx, closure_invalidated_cb); whc_free (ctx); } /* Triggered when the observer is destroyed. */ static void observer_destroyed_cb (gpointer ctx_, GObject *where_the_observer_was) { WeakHandlerCtx *ctx = ctx_; g_closure_remove_invalidate_notifier (ctx->closure, ctx, closure_invalidated_cb); g_signal_handler_disconnect (ctx->instance, ctx->handler_id); g_object_weak_unref (ctx->instance, instance_destroyed_cb, ctx); whc_free (ctx); } /* Triggered when either object is destroyed or the handler is disconnected. */ static void closure_invalidated_cb (gpointer ctx_, GClosure *where_the_closure_was) { WeakHandlerCtx *ctx = ctx_; g_object_weak_unref (ctx->instance, instance_destroyed_cb, ctx); g_object_weak_unref (ctx->observer, observer_destroyed_cb, ctx); whc_free (ctx); } /* Copied from tp_g_signal_connect_object. See documentation. */ /** * spice_g_signal_connect_object: (skip) * @instance: the instance to connect to. * @detailed_signal: a string of the form "signal-name::detail". * @c_handler: the #GCallback to connect. * @gobject: the object to pass as data to @c_handler. * @connect_flags: a combination of #GConnectFlags. * * Similar to g_signal_connect_object() but will delete connection * when any of the objects is destroyed. * * Returns: the handler id. */ gulong spice_g_signal_connect_object (gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer gobject, GConnectFlags connect_flags) { GObject *instance_obj = G_OBJECT (instance); WeakHandlerCtx *ctx = whc_new (instance_obj, gobject); g_return_val_if_fail (G_TYPE_CHECK_INSTANCE (instance), 0); g_return_val_if_fail (detailed_signal != NULL, 0); g_return_val_if_fail (c_handler != NULL, 0); g_return_val_if_fail (G_IS_OBJECT (gobject), 0); g_return_val_if_fail ( (connect_flags & ~(G_CONNECT_AFTER|G_CONNECT_SWAPPED)) == 0, 0); if (connect_flags & G_CONNECT_SWAPPED) ctx->closure = g_cclosure_new_object_swap (c_handler, gobject); else ctx->closure = g_cclosure_new_object (c_handler, gobject); ctx->handler_id = g_signal_connect_closure (instance, detailed_signal, ctx->closure, (connect_flags & G_CONNECT_AFTER) ? TRUE : FALSE); g_object_weak_ref (instance_obj, instance_destroyed_cb, ctx); g_object_weak_ref (gobject, observer_destroyed_cb, ctx); g_closure_add_invalidate_notifier (ctx->closure, ctx, closure_invalidated_cb); return ctx->handler_id; } G_GNUC_INTERNAL const gchar* spice_yes_no(gboolean value) { return value ? "yes" : "no"; } G_GNUC_INTERNAL guint16 spice_make_scancode(guint scancode, gboolean release) { SPICE_DEBUG("%s: %s scancode %d", __FUNCTION__, release ? "release" : "", scancode); if (release) { if (scancode < 0x100) return scancode | 0x80; else return 0x80e0 | ((scancode - 0x100) << 8); } else { if (scancode < 0x100) return scancode; else return 0xe0 | ((scancode - 0x100) << 8); } g_return_val_if_reached(0); } typedef enum { NEWLINE_TYPE_LF, NEWLINE_TYPE_CR_LF } NewlineType; static gssize get_line(const gchar *str, gsize len, NewlineType type, gsize *nl_len, GError **error) { const gchar *p, *endl; gsize nl = 0; endl = (type == NEWLINE_TYPE_CR_LF) ? "\r\n" : "\n"; p = g_strstr_len(str, len, endl); if (p) { len = p - str; nl = strlen(endl); } *nl_len = nl; return len; } static gchar* spice_convert_newlines(const gchar *str, gssize len, NewlineType from, NewlineType to, GError **error) { GError *err = NULL; gssize length; gsize nl; GString *output; gboolean free_segment = FALSE; gint i; g_return_val_if_fail(str != NULL, NULL); g_return_val_if_fail(len >= -1, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* only 2 supported combinations */ g_return_val_if_fail((from == NEWLINE_TYPE_LF && to == NEWLINE_TYPE_CR_LF) || (from == NEWLINE_TYPE_CR_LF && to == NEWLINE_TYPE_LF), NULL); if (len == -1) len = strlen(str); /* sometime we get \0 terminated strings, skip that, or it fails to utf8 validate line with \0 end */ else if (len > 0 && str[len-1] == 0) len -= 1; /* allocate worst case, if it's small enough, we don't care much, * if it's big, malloc will put us in mmap'd region, and we can * over allocate. */ output = g_string_sized_new(len * 2 + 1); for (i = 0; i < len; i += length + nl) { length = get_line(str + i, len - i, from, &nl, &err); if (length < 0) break; g_string_append_len(output, str + i, length); if (nl) { /* let's not double \r if it's already in the line */ if (to == NEWLINE_TYPE_CR_LF && output->str[output->len - 1] != '\r') g_string_append_c(output, '\r'); g_string_append_c(output, '\n'); } } if (err) { g_propagate_error(error, err); free_segment = TRUE; } return g_string_free(output, free_segment); } G_GNUC_INTERNAL gchar* spice_dos2unix(const gchar *str, gssize len, GError **error) { return spice_convert_newlines(str, len, NEWLINE_TYPE_CR_LF, NEWLINE_TYPE_LF, error); } G_GNUC_INTERNAL gchar* spice_unix2dos(const gchar *str, gssize len, GError **error) { return spice_convert_newlines(str, len, NEWLINE_TYPE_LF, NEWLINE_TYPE_CR_LF, error); } static bool buf_is_ones(unsigned size, const guint8 *data) { int i; for (i = 0 ; i < size; ++i) { if (data[i] != 0xff) { return false; } } return true; } static bool is_edge_helper(const guint8 *xor, int bpl, int x, int y) { return (xor[bpl * y + (x / 8)] & (0x80 >> (x % 8))) > 0; } static bool is_edge(unsigned width, unsigned height, const guint8 *xor, int bpl, int x, int y) { if (x == 0 || x == width -1 || y == 0 || y == height - 1) { return 0; } #define P(x, y) is_edge_helper(xor, bpl, x, y) return !P(x, y) && (P(x - 1, y + 1) || P(x, y + 1) || P(x + 1, y + 1) || P(x - 1, y) || P(x + 1, y) || P(x - 1, y - 1) || P(x, y - 1) || P(x + 1, y - 1)); #undef P } /* Mono cursors have two places, "and" and "xor". If a bit is 1 in both, it * means invertion of the corresponding pixel in the display. Since X11 (and * gdk) doesn't do invertion, instead we do edge detection and turn the * sorrounding edge pixels black, and the invert-me pixels white. To * illustrate: * * and xor dest RGB (1=0xffffff, 0=0x000000) * * dest alpha (1=0xff, 0=0x00) * * 11111 00000 00000 00000 * 11111 00000 00000 01110 * 11111 00100 => 00100 01110 * 11111 00100 00100 01110 * 11111 00000 00000 01110 * 11111 00000 00000 00000 * * See tests/util.c for more tests * * Notes: * Assumes width >= 8 (i.e. bytes per line is at least 1) * Assumes edges are not on the boundary (first/last line/column) for simplicity * */ G_GNUC_INTERNAL void spice_mono_edge_highlight(unsigned width, unsigned height, const guint8 *and, const guint8 *xor, guint8 *dest) { int bpl = (width + 7) / 8; bool and_ones = buf_is_ones(height * bpl, and); int x, y, bit; const guint8 *xor_base = xor; for (y = 0; y < height; y++) { bit = 0x80; for (x = 0; x < width; x++, dest += 4) { if (is_edge(width, height, xor_base, bpl, x, y) && and_ones) { dest[0] = 0x00; dest[1] = 0x00; dest[2] = 0x00; dest[3] = 0xff; goto next_bit; } if (and[x/8] & bit) { if (xor[x/8] & bit) { dest[0] = 0xff; dest[1] = 0xff; dest[2] = 0xff; dest[3] = 0xff; } else { /* unchanged -> transparent */ dest[0] = 0x00; dest[1] = 0x00; dest[2] = 0x00; dest[3] = 0x00; } } else { if (xor[x/8] & bit) { /* set -> white */ dest[0] = 0xff; dest[1] = 0xff; dest[2] = 0xff; dest[3] = 0xff; } else { /* clear -> black */ dest[0] = 0x00; dest[1] = 0x00; dest[2] = 0x00; dest[3] = 0xff; } } next_bit: bit >>= 1; if (bit == 0) { bit = 0x80; } } and += bpl; xor += bpl; } }