[Xcb] [PATCH:xwininfo 1/2] Handle non-latin-1 window names
Alan Coopersmith
alan.coopersmith at oracle.com
Tue Jun 29 23:04:33 PDT 2010
Uses _NET_WM_NAME to get UTF-8 encoding, iconv to convert to current locale
Warns that COMPOUND_TEXT WM_NAMEs aren't supported if _NET_WM_NAME isn't set
Adds local atom caching code to dsimple.c and uses it in all three *.c
Signed-off-by: Alan Coopersmith <alan.coopersmith at oracle.com>
---
clientwin.c | 33 +++--------
dsimple.c | 196 +++++++++++++++++++++++++++++++++++++++++++++++++++--------
dsimple.h | 10 ++-
xwininfo.c | 165 ++++++++++++++++++++++++++++++++++++++++++-------
4 files changed, 326 insertions(+), 78 deletions(-)
diff --git a/clientwin.c b/clientwin.c
index fe6bd18..2b1de04 100644
--- a/clientwin.c
+++ b/clientwin.c
@@ -26,9 +26,9 @@
#include <string.h>
#include "clientwin.h"
+#include "dsimple.h"
static xcb_atom_t atom_wm_state = XCB_ATOM_NONE;
-typedef enum { False = 0, True } Bool;
/*
* Check if window has given property
@@ -139,9 +139,7 @@ Find_Client_In_Children(xcb_connection_t * dpy, xcb_window_t win)
static xcb_window_t *
Find_Roots(xcb_connection_t * dpy, xcb_window_t root, unsigned int *num)
{
- xcb_atom_t atom = XCB_ATOM_NONE;
- xcb_intern_atom_cookie_t atom_cookie;
- xcb_intern_atom_reply_t *atom_reply;
+ xcb_atom_t atom_virtual_root;
xcb_get_property_cookie_t prop_cookie;
xcb_get_property_reply_t *prop_reply;
@@ -150,18 +148,12 @@ Find_Roots(xcb_connection_t * dpy, xcb_window_t root, unsigned int *num)
*num = 0;
- atom_cookie = xcb_intern_atom (dpy, False, strlen("_NET_VIRTUAL_ROOTS"),
- "_NET_VIRTUAL_ROOTS");
- atom_reply = xcb_intern_atom_reply (dpy, atom_cookie, NULL);
- if (atom_reply) {
- atom = atom_reply->atom;
- free (atom_reply);
- }
- if (!atom)
+ atom_virtual_root = Get_Atom (dpy, "_NET_VIRTUAL_ROOTS");
+ if (atom_virtual_root == XCB_ATOM_NONE)
return NULL;
- prop_cookie = xcb_get_property (dpy, False, root, atom, XCB_ATOM_WINDOW,
- 0, 0x7fffffff);
+ prop_cookie = xcb_get_property (dpy, False, root, atom_virtual_root,
+ XCB_ATOM_WINDOW, 0, 0x7fffffff);
prop_reply = xcb_get_property_reply (dpy, prop_cookie, NULL);
if (!prop_reply)
return NULL;
@@ -235,17 +227,8 @@ Find_Client(xcb_connection_t * dpy, xcb_window_t root, xcb_window_t subwin)
free (roots);
if (atom_wm_state == XCB_ATOM_NONE) {
- xcb_intern_atom_cookie_t atom_cookie;
- xcb_intern_atom_reply_t *atom_reply;
-
- atom_cookie = xcb_intern_atom (dpy, False,
- strlen("WM_STATE"), "WM_STATE");
- atom_reply = xcb_intern_atom_reply (dpy, atom_cookie, NULL);
- if (atom_reply) {
- atom_wm_state = atom_reply->atom;
- free (atom_reply);
- }
- if (!atom_wm_state)
+ atom_wm_state = Get_Atom(dpy, "WM_STATE");
+ if (atom_wm_state == XCB_ATOM_NONE)
return subwin;
}
diff --git a/dsimple.c b/dsimple.c
index f28d270..a9b8678 100644
--- a/dsimple.c
+++ b/dsimple.c
@@ -230,6 +230,7 @@ xcb_window_t Select_Window(xcb_connection_t *dpy,
*/
struct wininfo_cookies {
+ xcb_get_property_cookie_t get_net_wm_name;
xcb_get_property_cookie_t get_wm_name;
xcb_query_tree_cookie_t query_tree;
};
@@ -240,6 +241,13 @@ struct wininfo_cookies {
XCB_GET_PROPERTY_TYPE_ANY, 0, BUFSIZ)
#endif
+static xcb_atom_t atom_net_wm_name, atom_utf8_string;
+
+# define xcb_get_net_wm_name(Dpy, Win) \
+ xcb_get_property (Dpy, False, Win, atom_net_wm_name, \
+ atom_utf8_string, 0, BUFSIZ)
+
+
static xcb_window_t
recursive_Window_With_Name (
xcb_connection_t *dpy,
@@ -254,43 +262,71 @@ recursive_Window_With_Name (
xcb_generic_error_t *err;
xcb_query_tree_reply_t *tree;
struct wininfo_cookies *child_cookies;
+ xcb_get_property_reply_t *prop;
-#ifdef USE_XCB_ICCCM
- xcb_get_text_property_reply_t prop;
+ if (cookies->get_net_wm_name.sequence) {
+ prop = xcb_get_property_reply (dpy, cookies->get_net_wm_name, &err);
- if (xcb_get_wm_name_reply (dpy, cookies->get_wm_name, &prop, &err)) {
- /* can't use strcmp, since prop.name is not null terminated */
- if (strncmp (prop.name, name, prop.name_len) == 0) {
- w = window;
+ if (prop) {
+ if (prop->type == atom_utf8_string) {
+ const char *prop_name = xcb_get_property_value (prop);
+ int prop_name_len = xcb_get_property_value_length (prop);
+
+ /* can't use strcmp, since prop.name is not null terminated */
+ if (strncmp (prop_name, name, prop_name_len) == 0) {
+ w = window;
+ }
+ }
+ free (prop);
+ } else if (err) {
+ if (err->response_type == 0)
+ Print_X_Error (dpy, err);
+ return 0;
}
+ }
- xcb_get_text_property_reply_wipe (&prop);
+ if (w) {
+ xcb_discard_reply (dpy, cookies->get_wm_name.sequence);
+ } else {
+#ifdef USE_XCB_ICCCM
+ xcb_get_text_property_reply_t nameprop;
+
+ if (xcb_get_wm_name_reply (dpy, cookies->get_wm_name,
+ &nameprop, &err)) {
+ /* can't use strcmp, since nameprop.name is not null terminated */
+ if (strncmp (nameprop.name, name, nameprop.name_len) == 0) {
+ w = window;
+ }
+
+ xcb_get_text_property_reply_wipe (&nameprop);
+ }
#else
- xcb_get_property_reply_t *prop
- = xcb_get_property_reply (dpy, cookies->get_wm_name, &err);
+ prop = xcb_get_property_reply (dpy, cookies->get_wm_name, &err);
- if (prop) {
- if (prop->type == XCB_ATOM_STRING) {
- const char *prop_name = xcb_get_property_value (prop);
- int prop_name_len = xcb_get_property_value_length (prop);
+ if (prop) {
+ if (prop->type == XCB_ATOM_STRING) {
+ const char *prop_name = xcb_get_property_value (prop);
+ int prop_name_len = xcb_get_property_value_length (prop);
- /* can't use strcmp, since prop.name is not null terminated */
- if (strncmp (prop_name, name, prop_name_len) == 0) {
- w = window;
+ /* can't use strcmp, since prop.name is not null terminated */
+ if (strncmp (prop_name, name, prop_name_len) == 0) {
+ w = window;
+ }
}
+ free (prop);
}
- free (prop);
#endif
-
- if (w)
- {
- xcb_discard_reply (dpy, cookies->query_tree.sequence);
- return w;
+ else if (err) {
+ if (err->response_type == 0)
+ Print_X_Error (dpy, err);
+ return 0;
}
- } else if (err) {
- if (err->response_type == 0)
- Print_X_Error (dpy, err);
- return 0;
+ }
+
+ if (w)
+ {
+ xcb_discard_reply (dpy, cookies->query_tree.sequence);
+ return w;
}
tree = xcb_query_tree_reply (dpy, cookies->query_tree, &err);
@@ -308,6 +344,9 @@ recursive_Window_With_Name (
Fatal_Error("Failed to allocate memory in recursive_Window_With_Name");
for (i = 0; i < nchildren; i++) {
+ if (atom_net_wm_name && atom_utf8_string)
+ child_cookies[i].get_net_wm_name =
+ xcb_get_net_wm_name (dpy, children[i]);
child_cookies[i].get_wm_name = xcb_get_wm_name (dpy, children[i]);
child_cookies[i].query_tree = xcb_query_tree (dpy, children[i]);
}
@@ -324,6 +363,9 @@ recursive_Window_With_Name (
{
/* clean up remaining replies */
for (/* keep previous i */; i < nchildren; i++) {
+ if (child_cookies[i].get_net_wm_name.sequence)
+ xcb_discard_reply (dpy,
+ child_cookies[i].get_net_wm_name.sequence);
xcb_discard_reply (dpy, child_cookies[i].get_wm_name.sequence);
xcb_discard_reply (dpy, child_cookies[i].query_tree.sequence);
}
@@ -342,8 +384,14 @@ Window_With_Name (
{
struct wininfo_cookies cookies;
+ atom_net_wm_name = Get_Atom (dpy, "_NET_WM_NAME");
+ atom_utf8_string = Get_Atom (dpy, "UTF8_STRING");
+
+ if (atom_net_wm_name && atom_utf8_string)
+ cookies.get_net_wm_name = xcb_get_net_wm_name (dpy, top);
cookies.get_wm_name = xcb_get_wm_name (dpy, top);
cookies.query_tree = xcb_query_tree (dpy, top);
+ xcb_flush (dpy);
return recursive_Window_With_Name(dpy, top, &cookies, name);
}
@@ -485,3 +533,99 @@ Print_X_Error (
fprintf (stderr, " Request serial number: %d\n", err->full_sequence);
}
+
+/*
+ * Cache for atom lookups in either direction
+ */
+struct atom_cache_entry {
+ xcb_atom_t atom;
+ const char *name;
+ xcb_intern_atom_cookie_t intern_atom;
+ struct atom_cache_entry *next;
+};
+
+static struct atom_cache_entry *atom_cache;
+
+/*
+ * Send a request to the server for an atom by name
+ * Does not create the atom if it is not already present
+ */
+struct atom_cache_entry *Intern_Atom (xcb_connection_t * dpy, const char *name)
+{
+ struct atom_cache_entry *a;
+
+ for (a = atom_cache ; a != NULL ; a = a->next) {
+ if (strcmp (a->name, name) == 0)
+ return a; /* already requested or found */
+ }
+
+ a = calloc(1, sizeof(struct atom_cache_entry));
+ if (a != NULL) {
+ a->name = name;
+ a->intern_atom = xcb_intern_atom (dpy, False, strlen (name), (name));
+ a->next = atom_cache;
+ atom_cache = a;
+ }
+ return a;
+}
+
+/* Get an atom by name when it is needed. */
+xcb_atom_t Get_Atom (xcb_connection_t * dpy, const char *name)
+{
+ struct atom_cache_entry *a = Intern_Atom (dpy, name);
+
+ if (a == NULL)
+ return XCB_ATOM_NONE;
+
+ if (a->atom == XCB_ATOM_NONE) {
+ xcb_intern_atom_reply_t *reply;
+
+ reply = xcb_intern_atom_reply(dpy, a->intern_atom, NULL);
+ if (reply) {
+ a->atom = reply->atom;
+ free (reply);
+ } else {
+ a->atom = -1;
+ }
+ }
+ if (a->atom == -1) /* internal error */
+ return XCB_ATOM_NONE;
+
+ return a->atom;
+}
+
+/* Get the name for an atom when it is needed. */
+const char *Get_Atom_Name (xcb_connection_t * dpy, xcb_atom_t atom)
+{
+ struct atom_cache_entry *a;
+
+ for (a = atom_cache ; a != NULL ; a = a->next) {
+ if (a->atom == atom)
+ return a->name; /* already requested or found */
+ }
+
+ a = calloc(1, sizeof(struct atom_cache_entry));
+ if (a != NULL) {
+ xcb_get_atom_name_cookie_t cookie = xcb_get_atom_name (dpy, atom);
+ xcb_get_atom_name_reply_t *reply
+ = xcb_get_atom_name_reply (dpy, cookie, NULL);
+
+ a->atom = atom;
+ if (reply) {
+ int len = xcb_get_atom_name_name_length (reply);
+ char *name = malloc(len + 1);
+ if (name) {
+ memcpy (name, xcb_get_atom_name_name (reply), len);
+ name[len] = '\0';
+ a->name = name;
+ }
+ free (reply);
+ }
+
+ a->next = atom_cache;
+ atom_cache = a;
+
+ return a->name;
+ }
+ return NULL;
+}
diff --git a/dsimple.h b/dsimple.h
index 1a689e0..b6adc43 100644
--- a/dsimple.h
+++ b/dsimple.h
@@ -51,9 +51,13 @@ const char *Get_Display_Name (const char *displayname);
void Setup_Display_And_Screen (const char *displayname,
xcb_connection_t **dpy, xcb_screen_t **screen);
-xcb_window_t Select_Window(xcb_connection_t *, const xcb_screen_t *, int);
-xcb_window_t Window_With_Name(xcb_connection_t *, xcb_window_t, const char *);
+xcb_window_t Select_Window (xcb_connection_t *, const xcb_screen_t *, int);
+xcb_window_t Window_With_Name (xcb_connection_t *, xcb_window_t, const char *);
-void Fatal_Error(char *, ...) _X_NORETURN _X_ATTRIBUTE_PRINTF(1, 2);
+void Fatal_Error (char *, ...) _X_NORETURN _X_ATTRIBUTE_PRINTF(1, 2);
void Print_X_Error (xcb_connection_t *, xcb_generic_error_t *);
+
+struct atom_cache_entry *Intern_Atom (xcb_connection_t *, const char *);
+xcb_atom_t Get_Atom (xcb_connection_t *, const char *);
+const char *Get_Atom_Name (xcb_connection_t *, xcb_atom_t);
diff --git a/xwininfo.c b/xwininfo.c
index 6a3cda2..2c6221c 100644
--- a/xwininfo.c
+++ b/xwininfo.c
@@ -76,6 +76,9 @@ of the copyright holder.
#include <stdlib.h>
#include <string.h>
#include <locale.h>
+#include <langinfo.h>
+#include <iconv.h>
+#include <errno.h>
/* Include routines to handle parsing defaults */
#include "dsimple.h"
@@ -178,12 +181,18 @@ enum {
xcb_get_wm_size_hints(Dpy, Win, XCB_ATOM_WM_NORMAL_HINTS)
#endif
+/* Possibly in xcb-emwh in the future? */
+static xcb_atom_t atom_net_wm_name, atom_utf8_string;
+static xcb_get_property_cookie_t get_net_wm_name (xcb_connection_t *,
+ xcb_window_t);
+
/* Information we keep track of for each window to allow prefetching/reusing */
struct wininfo {
xcb_window_t window;
/* cookies for requests we've sent */
xcb_get_geometry_cookie_t geometry_cookie;
+ xcb_get_property_cookie_t net_wm_name_cookie;
xcb_get_property_cookie_t wm_name_cookie;
xcb_get_property_cookie_t wm_class_cookie;
xcb_translate_coordinates_cookie_t trans_coords_cookie;
@@ -222,6 +231,10 @@ static void wininfo_wipe (struct wininfo *);
static const char *window_id_format = "0x%lx";
+static const char *user_encoding;
+static iconv_t iconv_from_utf8;
+static void print_utf8 (const char *, char *, size_t, const char *);
+
static xcb_connection_t *dpy;
static xcb_screen_t *screen;
static xcb_generic_error_t *err;
@@ -407,6 +420,7 @@ main (int argc, char **argv)
if (!setlocale (LC_ALL, ""))
fprintf (stderr, "%s: can not set locale properly\n", program_name);
+ user_encoding = nl_langinfo (CODESET);
memset (w, 0, sizeof(struct wininfo));
@@ -493,6 +507,10 @@ main (int argc, char **argv)
Setup_Display_And_Screen (display_name, &dpy, &screen);
+ /* preload atoms we may need later */
+ Intern_Atom (dpy, "_NET_WM_NAME");
+ Intern_Atom (dpy, "UTF8_STRING");
+
/* initialize scaling data */
scale_init(screen);
@@ -510,6 +528,8 @@ main (int argc, char **argv)
"xwininfo: Please select the window about which you\n"
" would like information by clicking the\n"
" mouse in that window.\n");
+ Intern_Atom (dpy, "_NET_VIRTUAL_ROOTS");
+ Intern_Atom (dpy, "WM_STATE");
window = Select_Window (dpy, screen, !frame);
}
@@ -541,6 +561,7 @@ main (int argc, char **argv)
/* Send requests to prefetch data we'll need */
w->window = window;
+ w->net_wm_name_cookie = get_net_wm_name (dpy, window);
w->wm_name_cookie = xcb_get_wm_name (dpy, window);
if (children || tree)
w->tree_cookie = xcb_query_tree (dpy, window);
@@ -581,6 +602,9 @@ main (int argc, char **argv)
wininfo_wipe (w);
xcb_disconnect (dpy);
+ if (iconv_from_utf8 && (iconv_from_utf8 != (iconv_t) -1)) {
+ iconv_close (iconv_from_utf8);
+ }
exit (0);
}
@@ -694,13 +718,13 @@ static void
Display_Window_Id (struct wininfo *w, Bool newline_wanted)
{
#ifdef USE_XCB_ICCCM
- xcb_get_text_property_reply_t prop;
-#else
- xcb_get_property_reply_t *prop;
+ xcb_get_text_property_reply_t wmn_reply;
#endif
- uint8_t got_reply;
- const char *wm_name;
- int wm_name_len;
+ xcb_get_property_reply_t *prop;
+ uint8_t got_reply = False;
+ const char *wm_name = NULL;
+ unsigned int wm_name_len = 0;
+ xcb_atom_t wm_name_encoding = XCB_NONE;
printf (window_id_format, w->window); /* print id # in hex/dec */
@@ -711,34 +735,54 @@ Display_Window_Id (struct wininfo *w, Bool newline_wanted)
printf (" (the root window)");
}
/* Get window name if any */
-#ifdef USE_XCB_ICCCM
- got_reply = xcb_get_wm_name_reply (dpy, w->wm_name_cookie,
- &prop, NULL);
- if (got_reply) {
- wm_name = prop.name;
- wm_name_len = prop.name_len;
- }
-#else
- prop = xcb_get_property_reply (dpy, w->wm_name_cookie, NULL);
- if (prop && (prop->type == XCB_ATOM_STRING)) {
+ prop = xcb_get_property_reply (dpy, w->net_wm_name_cookie, NULL);
+ if (prop && (prop->type != XCB_NONE)) {
wm_name = xcb_get_property_value (prop);
- wm_name_len = xcb_get_property_value_length (prop);
+ wm_name_len = xcb_get_property_value_length (prop);
+ wm_name_encoding = prop->type;
got_reply = True;
- } else {
- got_reply = False;
}
+
+ if (!got_reply) { /* No _NET_WM_NAME, check WM_NAME */
+#ifdef USE_XCB_ICCCM
+ got_reply = xcb_get_wm_name_reply (dpy, w->wm_name_cookie,
+ &wmn_reply, NULL);
+ if (got_reply) {
+ wm_name = wmn_reply.name;
+ wm_name_len = wmn_reply.name_len;
+ wm_name_encoding = wmn_reply.encoding;
+ }
+#else
+ prop = xcb_get_property_reply (dpy, w->wm_name_cookie, NULL);
+ if (prop && (prop->type != XCB_NONE)) {
+ wm_name = xcb_get_property_value (prop);
+ wm_name_len = xcb_get_property_value_length (prop);
+ wm_name_encoding = prop->type;
+ got_reply = True;
+ }
#endif
+ }
if (!got_reply || wm_name_len == 0) {
printf (" (has no name)");
} else {
- printf (" \"");
- /* XXX: need to handle encoding */
- printf ("%.*s", wm_name_len, wm_name);
- printf ("\"");
+ if (wm_name_encoding == XCB_ATOM_STRING) {
+ printf (" \"%.*s\"", wm_name_len, wm_name);
+ } else if (wm_name_encoding == atom_utf8_string) {
+ print_utf8 (" \"", (char *) wm_name, wm_name_len, "\"");
+ } else {
+ /* Encodings we don't support, including COMPOUND_TEXT */
+ const char *enc_name = Get_Atom_Name (dpy, wm_name_encoding);
+ if (enc_name) {
+ printf (" (name in unsupported encoding %s)", enc_name);
+ } else {
+ printf (" (name in unsupported encoding ATOM 0x%x)",
+ wm_name_encoding);
+ }
+ }
}
#ifdef USE_XCB_ICCCM
if (got_reply)
- xcb_get_text_property_reply_wipe (&prop);
+ xcb_get_text_property_reply_wipe (&wmn_reply);
#else
free (prop);
#endif
@@ -1166,8 +1210,10 @@ display_tree_info_1 (struct wininfo *w, int recurse, int level)
if (level == 0) {
struct wininfo rw, pw;
rw.window = tree->root;
+ rw.net_wm_name_cookie = get_net_wm_name (dpy, rw.window);
rw.wm_name_cookie = xcb_get_wm_name (dpy, rw.window);
pw.window = tree->parent;
+ pw.net_wm_name_cookie = get_net_wm_name (dpy, pw.window);
pw.wm_name_cookie = xcb_get_wm_name (dpy, pw.window);
xcb_flush (dpy);
@@ -1199,6 +1245,7 @@ display_tree_info_1 (struct wininfo *w, int recurse, int level)
struct wininfo *cw = &children[i];
cw->window = child_list[i];
+ cw->net_wm_name_cookie = get_net_wm_name (dpy, child_list[i]);
cw->wm_name_cookie = xcb_get_wm_name (dpy, child_list[i]);
cw->wm_class_cookie = xcb_get_wm_class (dpy, child_list[i]);
cw->geometry_cookie = xcb_get_geometry (dpy, child_list[i]);
@@ -1536,6 +1583,7 @@ Display_WM_Info (struct wininfo *w)
if (flags & XCB_WM_HINT_ICON_WINDOW) {
struct wininfo iw;
iw.window = wmhints.icon_window;
+ iw.net_wm_name_cookie = get_net_wm_name (dpy, iw.window);
iw.wm_name_cookie = xcb_get_wm_name (dpy, iw.window);
printf (" Icon window id: ");
@@ -1559,3 +1607,72 @@ wininfo_wipe (struct wininfo *w)
free (w->win_attributes);
free (w->normal_hints);
}
+
+/* Gets UTF-8 encoded EMWH property _NET_WM_NAME for a window */
+static xcb_get_property_cookie_t
+get_net_wm_name (xcb_connection_t *dpy, xcb_window_t win)
+{
+ if (!atom_net_wm_name)
+ atom_net_wm_name = Get_Atom (dpy, "_NET_WM_NAME");
+
+ if (!atom_utf8_string)
+ atom_utf8_string = Get_Atom (dpy, "UTF8_STRING");
+
+ if (atom_net_wm_name && atom_utf8_string)
+ return xcb_get_property (dpy, False, win, atom_net_wm_name,
+ atom_utf8_string, 0, BUFSIZ);
+ else {
+ xcb_get_property_cookie_t dummy = { 0 };
+ return dummy;
+ }
+}
+
+/*
+ * Converts a UTF-8 encoded string to the current locale encoding,
+ * if possible, and prints it, with prefix before and suffix after.
+ * Length of the string is specified in bytes, or -1 for going until '\0'
+ */
+static void
+print_utf8 (const char *prefix, char *u8str, size_t length, const char *suffix)
+{
+ char convbuf[BUFSIZ];
+ char *inp = u8str;
+ size_t inlen = length;
+ int convres;
+
+ if (inlen < 0) {
+ inlen = strlen (inp);
+ }
+
+ if (!iconv_from_utf8) {
+ iconv_from_utf8 = iconv_open (user_encoding, "UTF-8");
+ }
+
+ if (iconv_from_utf8 != (iconv_t) -1) {
+ Bool done = True;
+
+ printf ("%s", prefix);
+ do {
+ char *outp = convbuf;
+ size_t outlen = sizeof(convbuf);
+
+ convres = iconv (iconv_from_utf8, &inp, &inlen, &outp, &outlen);
+
+ if ((convres == -1) && (errno == E2BIG)) {
+ done = False;
+ convres = 0;
+ }
+
+ if (convres == 0) {
+ fwrite (convbuf, 1, sizeof(convbuf) - outlen, stdout);
+ } else {
+ printf (" (failure in conversion from UTF8_STRING to %s)",
+ user_encoding);
+ }
+ } while (!done);
+ printf ("%s", suffix);
+ } else {
+ printf (" (can't load iconv conversion for UTF8_STRING to %s)",
+ user_encoding);
+ }
+}
--
1.5.6.5
More information about the Xcb
mailing list