577 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			577 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
	
| /* SPDX-License-Identifier: MIT OR X11
 | |
|  *
 | |
|  * Copyright © 2024 Enrico Weigelt, metux IT consult <info@metux.net>
 | |
|  */
 | |
| #include <dix-config.h>
 | |
| 
 | |
| #include <xcb/xcb.h>
 | |
| #include <xcb/xcb_aux.h>
 | |
| #include <xcb/xcb_icccm.h>
 | |
| 
 | |
| #include <X11/X.h>
 | |
| #include <X11/Xdefs.h>
 | |
| #include <X11/Xproto.h>
 | |
| #include <xcb/xkb.h>
 | |
| 
 | |
| #include "include/gc.h"
 | |
| #include "include/servermd.h"
 | |
| 
 | |
| #include "xnest-xcb.h"
 | |
| #include "xnest-xkb.h"
 | |
| #include "XNGC.h"
 | |
| #include "Display.h"
 | |
| 
 | |
| struct xnest_upstream_info xnestUpstreamInfo = { 0 };
 | |
| xnest_visual_t *xnestVisualMap;
 | |
| int xnestNumVisualMap;
 | |
| 
 | |
| Bool xnest_upstream_setup(const char* displayName)
 | |
| {
 | |
|     xnestUpstreamInfo.conn = xcb_connect(displayName, &xnestUpstreamInfo.screenId);
 | |
|     if (!xnestUpstreamInfo.conn)
 | |
|         return FALSE;
 | |
| 
 | |
|     /* retrieve setup data for our screen */
 | |
|     xnestUpstreamInfo.setup = xcb_get_setup(xnestUpstreamInfo.conn);
 | |
|     xcb_screen_iterator_t iter = xcb_setup_roots_iterator (xnestUpstreamInfo.setup);
 | |
| 
 | |
|     for (int i = 0; i < xnestUpstreamInfo.screenId; ++i)
 | |
|         xcb_screen_next (&iter);
 | |
|     xnestUpstreamInfo.screenInfo = iter.data;
 | |
| 
 | |
|     xorg_list_init(&xnestUpstreamInfo.eventQueue.entry);
 | |
| 
 | |
|     return TRUE;
 | |
| }
 | |
| 
 | |
| /* retrieve upstream GC XID for our xserver GC */
 | |
| uint32_t xnest_upstream_gc(GCPtr pGC) {
 | |
|     if (pGC == NULL) return 0;
 | |
| 
 | |
|     xnestPrivGC *priv = dixLookupPrivate(&(pGC)->devPrivates, xnestGCPrivateKey);
 | |
|     if (priv == NULL) return 0;
 | |
| 
 | |
|     return priv->gc;
 | |
| }
 | |
| 
 | |
| const char WM_COLORMAP_WINDOWS[] = "WM_COLORMAP_WINDOWS";
 | |
| 
 | |
| void xnest_wm_colormap_windows(
 | |
|     xcb_connection_t *conn,
 | |
|     xcb_window_t w,
 | |
|     xcb_window_t *windows,
 | |
|     int count)
 | |
| {
 | |
|     xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(
 | |
|         conn,
 | |
|         xcb_intern_atom(
 | |
|             conn, 0,
 | |
|             sizeof(WM_COLORMAP_WINDOWS)-1,
 | |
|             WM_COLORMAP_WINDOWS),
 | |
|         NULL);
 | |
| 
 | |
|     if (!reply)
 | |
|         return;
 | |
| 
 | |
|     xcb_icccm_set_wm_colormap_windows_checked(
 | |
|         conn,
 | |
|         w,
 | |
|         reply->atom,
 | |
|         count,
 | |
|         (xcb_window_t*)windows);
 | |
| 
 | |
|     free(reply);
 | |
| }
 | |
| 
 | |
| uint32_t xnest_create_bitmap_from_data(
 | |
|      xcb_connection_t *conn,
 | |
|      uint32_t drawable,
 | |
|      const char *data,
 | |
|      uint32_t width,
 | |
|      uint32_t height)
 | |
| {
 | |
|     uint32_t pix = xcb_generate_id(xnestUpstreamInfo.conn);
 | |
|     xcb_create_pixmap(conn, 1, pix, drawable, width, height);
 | |
| 
 | |
|     uint32_t gc = xcb_generate_id(xnestUpstreamInfo.conn);
 | |
|     xcb_create_gc(conn, gc, pix, 0, NULL);
 | |
| 
 | |
|     const int leftPad = 0;
 | |
| 
 | |
|     xcb_put_image(conn,
 | |
|                   XYPixmap,
 | |
|                   pix,
 | |
|                   gc,
 | |
|                   width,
 | |
|                   height,
 | |
|                   0 /* dst_x */,
 | |
|                   0 /* dst_y */,
 | |
|                   leftPad,
 | |
|                   1 /* depth */,
 | |
|                   BitmapBytePad(width + leftPad) * height,
 | |
|                   (uint8_t*)data);
 | |
| 
 | |
|     xcb_free_gc(conn, gc);
 | |
|     return pix;
 | |
| }
 | |
| 
 | |
| uint32_t xnest_create_pixmap_from_bitmap_data(
 | |
|     xcb_connection_t *conn,
 | |
|     uint32_t drawable,
 | |
|     const char *data,
 | |
|     uint32_t width,
 | |
|     uint32_t height,
 | |
|     uint32_t fg,
 | |
|     uint32_t bg,
 | |
|     uint16_t depth)
 | |
| {
 | |
|     uint32_t pix = xcb_generate_id(xnestUpstreamInfo.conn);
 | |
|     xcb_create_pixmap(conn, depth, pix, drawable, width, height);
 | |
| 
 | |
|     uint32_t gc = xcb_generate_id(xnestUpstreamInfo.conn);
 | |
|     xcb_create_gc(conn, gc, pix, 0, NULL);
 | |
| 
 | |
|     xcb_params_gc_t gcv = {
 | |
|         .foreground = fg,
 | |
|         .background = bg
 | |
|     };
 | |
| 
 | |
|     xcb_aux_change_gc(conn, gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, &gcv);
 | |
| 
 | |
|     const int leftPad = 0;
 | |
|     xcb_put_image(conn,
 | |
|                   XYBitmap,
 | |
|                   pix,
 | |
|                   gc,
 | |
|                   width,
 | |
|                   height,
 | |
|                   0 /* dst_x */,
 | |
|                   0 /* dst_y */,
 | |
|                   leftPad,
 | |
|                   1 /* depth */,
 | |
|                   BitmapBytePad(width + leftPad) * height,
 | |
|                   (uint8_t*)data);
 | |
| 
 | |
|     xcb_free_gc(conn, gc);
 | |
|     return pix;
 | |
| }
 | |
| 
 | |
| void xnest_set_command(
 | |
|     xcb_connection_t *conn,
 | |
|     xcb_window_t window,
 | |
|     char **argv,
 | |
|     int argc)
 | |
| {
 | |
|     int i = 0, nbytes = 0;
 | |
| 
 | |
|     for (i = 0, nbytes = 0; i < argc; i++)
 | |
|         nbytes += strlen(argv[i]) + 1;
 | |
| 
 | |
|     if (nbytes >= (2^16) - 1)
 | |
|         return;
 | |
| 
 | |
|     char *buf = calloc(1, nbytes+1);
 | |
|     if (!buf)
 | |
|         return; // BadAlloc
 | |
| 
 | |
|     char *bp = buf;
 | |
| 
 | |
|     /* copy arguments into single buffer */
 | |
|     for (i = 0; i < argc; i++) {
 | |
|         strcpy(bp, argv[i]);
 | |
|         bp += strlen(argv[i]) + 1;
 | |
|     }
 | |
| 
 | |
|     xcb_change_property(conn,
 | |
|                         XCB_PROP_MODE_REPLACE,
 | |
|                         window,
 | |
|                         XCB_ATOM_WM_COMMAND,
 | |
|                         XCB_ATOM_STRING,
 | |
|                         8,
 | |
|                         nbytes,
 | |
|                         buf);
 | |
|     free(buf);
 | |
| }
 | |
| 
 | |
| void xnest_xkb_init(xcb_connection_t *conn)
 | |
| {
 | |
|     xcb_generic_error_t *err = NULL;
 | |
|     xcb_xkb_use_extension_reply_t *reply = xcb_xkb_use_extension_reply(
 | |
|         xnestUpstreamInfo.conn,
 | |
|         xcb_xkb_use_extension(
 | |
|             xnestUpstreamInfo.conn,
 | |
|             XCB_XKB_MAJOR_VERSION,
 | |
|             XCB_XKB_MINOR_VERSION),
 | |
|         &err);
 | |
| 
 | |
|     if (err) {
 | |
|         ErrorF("failed query xkb extension: %d\n", err->error_code);
 | |
|         free(err);
 | |
|     } else {
 | |
|         free(reply);
 | |
|     }
 | |
| }
 | |
| 
 | |
| #define XkbGBN_AllComponentsMask_2 ( \
 | |
|     XCB_XKB_GBN_DETAIL_TYPES | \
 | |
|     XCB_XKB_GBN_DETAIL_COMPAT_MAP | \
 | |
|     XCB_XKB_GBN_DETAIL_CLIENT_SYMBOLS | \
 | |
|     XCB_XKB_GBN_DETAIL_SERVER_SYMBOLS | \
 | |
|     XCB_XKB_GBN_DETAIL_INDICATOR_MAPS | \
 | |
|     XCB_XKB_GBN_DETAIL_KEY_NAMES | \
 | |
|     XCB_XKB_GBN_DETAIL_GEOMETRY | \
 | |
|     XCB_XKB_GBN_DETAIL_OTHER_NAMES)
 | |
| 
 | |
| int xnest_xkb_device_id(xcb_connection_t *conn)
 | |
| {
 | |
|     int device_id = -1;
 | |
|     uint8_t xlen[6] = { 0 };
 | |
|     xcb_generic_error_t *err = NULL;
 | |
| 
 | |
|     xcb_xkb_get_kbd_by_name_reply_t *reply = xcb_xkb_get_kbd_by_name_reply(
 | |
|         xnestUpstreamInfo.conn,
 | |
|         xcb_xkb_get_kbd_by_name_2(
 | |
|             xnestUpstreamInfo.conn,
 | |
|             XCB_XKB_ID_USE_CORE_KBD,
 | |
|             XkbGBN_AllComponentsMask_2,
 | |
|             XkbGBN_AllComponentsMask_2,
 | |
|             0,
 | |
|             sizeof(xlen),
 | |
|             xlen),
 | |
|         &err);
 | |
| 
 | |
|     if (err) {
 | |
|         ErrorF("failed retrieving core keyboard: %d\n", err->error_code);
 | |
|         free(err);
 | |
|         return -1;
 | |
|     }
 | |
| 
 | |
|     if (!reply) {
 | |
|         ErrorF("failed retrieving core keyboard: no reply");
 | |
|         return -1;
 | |
|     }
 | |
| 
 | |
|     device_id = reply->deviceID;
 | |
|     free(reply);
 | |
|     return device_id;
 | |
| }
 | |
| 
 | |
| xcb_get_keyboard_mapping_reply_t *xnest_get_keyboard_mapping(
 | |
|     xcb_connection_t *conn,
 | |
|     int min_keycode,
 | |
|     int count
 | |
| ) {
 | |
|     xcb_generic_error_t *err= NULL;
 | |
|     xcb_get_keyboard_mapping_reply_t * reply = xcb_get_keyboard_mapping_reply(
 | |
|         xnestUpstreamInfo.conn,
 | |
|         xcb_get_keyboard_mapping(conn, min_keycode, count),
 | |
|         &err);
 | |
| 
 | |
|     if (err) {
 | |
|         ErrorF("Couldn't get keyboard mapping: %d\n", err->error_code);
 | |
|         free(err);
 | |
|     }
 | |
| 
 | |
|     return reply;
 | |
| }
 | |
| 
 | |
| void xnest_get_pointer_control(
 | |
|     xcb_connection_t *conn,
 | |
|     int *acc_num,
 | |
|     int *acc_den,
 | |
|     int *threshold)
 | |
| {
 | |
|     xcb_generic_error_t *err = NULL;
 | |
|     xcb_get_pointer_control_reply_t *reply = xcb_get_pointer_control_reply(
 | |
|         xnestUpstreamInfo.conn,
 | |
|         xcb_get_pointer_control(xnestUpstreamInfo.conn),
 | |
|         &err);
 | |
| 
 | |
|     if (err) {
 | |
|         ErrorF("error retrieving pointer control data: %d\n", err->error_code);
 | |
|         free(err);
 | |
|     }
 | |
| 
 | |
|     if (!reply) {
 | |
|         ErrorF("error retrieving pointer control data: no reply\n");
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     *acc_num = reply->acceleration_numerator;
 | |
|     *acc_den = reply->acceleration_denominator;
 | |
|     *threshold = reply->threshold;
 | |
|     free(reply);
 | |
| }
 | |
| 
 | |
| xRectangle xnest_get_geometry(xcb_connection_t *conn, uint32_t window)
 | |
| {
 | |
|     xcb_generic_error_t *err = NULL;
 | |
|     xcb_get_geometry_reply_t *reply = xcb_get_geometry_reply(
 | |
|         xnestUpstreamInfo.conn,
 | |
|         xcb_get_geometry(xnestUpstreamInfo.conn, window),
 | |
|         &err);
 | |
| 
 | |
|     if (err) {
 | |
|         ErrorF("failed getting window attributes for %d: %d\n", window, err->error_code);
 | |
|         free(err);
 | |
|         return (xRectangle) { 0 };
 | |
|     }
 | |
| 
 | |
|     if (!reply) {
 | |
|         ErrorF("failed getting window attributes for %d: no reply\n", window);
 | |
|         return (xRectangle) { 0 };
 | |
|     }
 | |
| 
 | |
|     return (xRectangle) {
 | |
|         .x = reply->x,
 | |
|         .y = reply->y,
 | |
|         .width = reply->width,
 | |
|         .height = reply->height };
 | |
| }
 | |
| 
 | |
| static int __readint(const char *str, const char **next)
 | |
| {
 | |
|     int res = 0, sign = 1;
 | |
| 
 | |
|     if (*str=='+')
 | |
|         str++;
 | |
|     else if (*str=='-') {
 | |
|         str++;
 | |
|         sign = -1;
 | |
|     }
 | |
| 
 | |
|     for (; (*str>='0') && (*str<='9'); str++)
 | |
|         res = (res * 10) + (*str-'0');
 | |
| 
 | |
|     *next = str;
 | |
|     return sign * res;
 | |
| }
 | |
| 
 | |
| int xnest_parse_geometry(const char *string, xRectangle *geometry)
 | |
| {
 | |
|     int mask = 0;
 | |
|     const char *next;
 | |
|     xRectangle temp = { 0 };
 | |
| 
 | |
|     if ((string == NULL) || (*string == '\0')) return 0;
 | |
| 
 | |
|     if (*string == '=')
 | |
|         string++;  /* ignore possible '=' at beg of geometry spec */
 | |
| 
 | |
|     if (*string != '+' && *string != '-' && *string != 'x') {
 | |
|         temp.width = __readint(string, &next);
 | |
|         if (string == next)
 | |
|             return 0;
 | |
|         string = next;
 | |
|         mask |= XCB_CONFIG_WINDOW_WIDTH;
 | |
|     }
 | |
| 
 | |
|     if (*string == 'x' || *string == 'X') {
 | |
|         string++;
 | |
|         temp.height = __readint(string, &next);
 | |
|         if (string == next)
 | |
|             return 0;
 | |
|         string = next;
 | |
|         mask |= XCB_CONFIG_WINDOW_HEIGHT;
 | |
|     }
 | |
| 
 | |
|     if ((*string == '+') || (*string== '-')) {
 | |
|         if (*string== '-') {
 | |
|             string++;
 | |
|             temp.x = -__readint(string, &next);
 | |
|             if (string == next)
 | |
|                 return 0;
 | |
|             string = next;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             string++;
 | |
|             temp.x = __readint(string, &next);
 | |
|             if (string == next)
 | |
|                 return 0;
 | |
|             string = next;
 | |
|         }
 | |
|         mask |= XCB_CONFIG_WINDOW_X;
 | |
|         if ((*string == '+') || (*string== '-')) {
 | |
|             if (*string== '-') {
 | |
|                 string++;
 | |
|                 temp.y = -__readint(string, &next);
 | |
|                 if (string == next)
 | |
|                     return 0;
 | |
|                 string = next;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 string++;
 | |
|                 temp.y = __readint(string, &next);
 | |
|                 if (string == next)
 | |
|                     return 0;
 | |
|                 string = next;
 | |
|             }
 | |
|             mask |= XCB_CONFIG_WINDOW_Y;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (*string != '\0') return 0;
 | |
| 
 | |
|     if (mask & XCB_CONFIG_WINDOW_X)
 | |
|         geometry->x = temp.x;
 | |
|     if (mask & XCB_CONFIG_WINDOW_Y)
 | |
|         geometry->y = temp.y;
 | |
|     if (mask & XCB_CONFIG_WINDOW_WIDTH)
 | |
|         geometry->width = temp.width;
 | |
|     if (mask & XCB_CONFIG_WINDOW_HEIGHT)
 | |
|         geometry->height = temp.height;
 | |
| 
 | |
|     return mask;
 | |
| }
 | |
| 
 | |
| uint32_t xnest_visual_map_to_upstream(VisualID visual)
 | |
| {
 | |
|     for (int i = 0; i < xnestNumVisualMap; i++) {
 | |
|         if (xnestVisualMap[i].ourXID == visual) {
 | |
|             return xnestVisualMap[i].upstreamVisual->visual_id;
 | |
|         }
 | |
|     }
 | |
|     return XCB_NONE;
 | |
| }
 | |
| 
 | |
| uint32_t xnest_upstream_visual_to_cmap(uint32_t upstreamVisual)
 | |
| {
 | |
|     for (int i = 0; i < xnestNumVisualMap; i++) {
 | |
|         if (xnestVisualMap[i].upstreamVisual->visual_id == upstreamVisual) {
 | |
|             return xnestVisualMap[i].upstreamCMap;
 | |
|         }
 | |
|     }
 | |
|     return XCB_COLORMAP_NONE;
 | |
| }
 | |
| 
 | |
| uint32_t xnest_visual_to_upstream_cmap(uint32_t visual)
 | |
| {
 | |
|     for (int i = 0; i < xnestNumVisualMap; i++) {
 | |
|         if (xnestVisualMap[i].ourXID == visual) {
 | |
|             return xnestVisualMap[i].upstreamCMap;
 | |
|         }
 | |
|     }
 | |
|     return XCB_COLORMAP_NONE;
 | |
| }
 | |
| 
 | |
| static inline char XN_CI_NONEXISTCHAR(xcb_charinfo_t *cs)
 | |
| {
 | |
|     return ((cs->character_width == 0) && \
 | |
|              ((cs->right_side_bearing | cs->left_side_bearing | cs->ascent | cs->descent) == 0));
 | |
| }
 | |
| 
 | |
| #define XN_CI_GET_CHAR_INFO_1D(font,col,def,cs) \
 | |
| do { \
 | |
|     cs = def; \
 | |
|     if (col >= font->font_reply->min_char_or_byte2 && col <= font->font_reply->max_char_or_byte2) { \
 | |
|         if (font->chars == NULL) { \
 | |
|             cs = &font->font_reply->min_bounds; \
 | |
|         } else { \
 | |
|             cs = (xcb_charinfo_t *)&font->chars[(col - font->font_reply->min_char_or_byte2)]; \
 | |
|             if (XN_CI_NONEXISTCHAR(cs)) cs = def; \
 | |
|         } \
 | |
|     } \
 | |
| } while (0)
 | |
| 
 | |
| #define XN_CI_GET_CHAR_INFO_2D(font,row,col,def,cs) \
 | |
| do { \
 | |
|     cs = def; \
 | |
|     if (row >= font->font_reply->min_byte1 && row <= font->font_reply->max_byte1 && \
 | |
|         col >= font->font_reply->min_char_or_byte2 && col <= font->font_reply->max_char_or_byte2) { \
 | |
|         if (font->chars == NULL) { \
 | |
|             cs = &font->font_reply->min_bounds; \
 | |
|         } else { \
 | |
|             cs = (xcb_charinfo_t*)&font->chars[((row - font->font_reply->min_byte1) * \
 | |
|                                 (font->font_reply->max_char_or_byte2 - \
 | |
|                                  font->font_reply->min_char_or_byte2 + 1)) + \
 | |
|                                (col - font->font_reply->min_char_or_byte2)]; \
 | |
|             if (XN_CI_NONEXISTCHAR(cs)) cs = def; \
 | |
|         } \
 | |
|     } \
 | |
| } while (0)
 | |
| 
 | |
| #define XN_CI_GET_DEFAULT_INFO_2D(font,cs) \
 | |
| do { \
 | |
|     unsigned int r = (font->font_reply->default_char >> 8); \
 | |
|     unsigned int c = (font->font_reply->default_char & 0xff); \
 | |
|     XN_CI_GET_CHAR_INFO_2D (font, r, c, NULL, cs); \
 | |
| } while (0)
 | |
| 
 | |
| #define XN_CI_GET_ROWZERO_CHAR_INFO_2D(font,col,def,cs) \
 | |
| do { \
 | |
|     cs = def; \
 | |
|     if (font->font_reply->min_byte1 == 0 && \
 | |
|         col >= font->font_reply->min_char_or_byte2 && col <= font->font_reply->max_char_or_byte2) { \
 | |
|         if (font->chars == NULL) { \
 | |
|             cs = &font->font_reply->min_bounds; \
 | |
|         } else { \
 | |
|             cs = (xcb_charinfo_t*)&font->chars[(col - font->font_reply->min_char_or_byte2)]; \
 | |
|             if (XN_CI_NONEXISTCHAR(cs)) cs = def; \
 | |
|         } \
 | |
|     } \
 | |
| } while (0)
 | |
| 
 | |
| int xnest_text_width(xnestPrivFont *font, const char *string, int count)
 | |
| {
 | |
|     xcb_charinfo_t *def;
 | |
| 
 | |
|     if (font->font_reply->max_byte1 == 0)
 | |
|         XN_CI_GET_CHAR_INFO_1D (font, font->font_reply->default_char, NULL, def);
 | |
|     else
 | |
|         XN_CI_GET_DEFAULT_INFO_2D (font, def);
 | |
| 
 | |
|     if (def && font->font_reply->min_bounds.character_width == font->font_reply->max_bounds.character_width)
 | |
|         return (font->font_reply->min_bounds.character_width * count);
 | |
| 
 | |
|     int width = 0, i = 0;
 | |
|     unsigned char *us;
 | |
|     for (i = 0, us = (unsigned char *) string; i < count; i++, us++) {
 | |
|         unsigned uc = (unsigned) *us;
 | |
|         xcb_charinfo_t *cs;
 | |
| 
 | |
|         if (font->font_reply->max_byte1 == 0) {
 | |
|             XN_CI_GET_CHAR_INFO_1D (font, uc, def, cs);
 | |
|         } else {
 | |
|             XN_CI_GET_ROWZERO_CHAR_INFO_2D (font, uc, def, cs);
 | |
|         }
 | |
| 
 | |
|         if (cs) width += cs->character_width;
 | |
|     }
 | |
| 
 | |
|     return width;
 | |
| }
 | |
| 
 | |
| int xnest_text_width_16 (xnestPrivFont *font, const uint16_t* str, int count)
 | |
| {
 | |
|     xcb_charinfo_t *def;
 | |
|     xcb_char2b_t *string = (xcb_char2b_t*)str;
 | |
| 
 | |
|     if (font->font_reply->max_byte1 == 0)
 | |
|         XN_CI_GET_CHAR_INFO_1D (font, font->font_reply->default_char, NULL, def);
 | |
|     else
 | |
|         XN_CI_GET_DEFAULT_INFO_2D (font, def);
 | |
| 
 | |
|     if (def && font->font_reply->min_bounds.character_width == font->font_reply->max_bounds.character_width)
 | |
|         return (font->font_reply->min_bounds.character_width * count);
 | |
| 
 | |
|     int width = 0;
 | |
|     for (int i = 0; i < count; i++, string++) {
 | |
|         xcb_charinfo_t *cs;
 | |
|         unsigned int r = (unsigned int) string->byte1;
 | |
|         unsigned int c = (unsigned int) string->byte2;
 | |
| 
 | |
|         if (font->font_reply->max_byte1 == 0) {
 | |
|             unsigned int ind = ((r << 8) | c);
 | |
|             XN_CI_GET_CHAR_INFO_1D (font, ind, def, cs);
 | |
|         } else {
 | |
|             XN_CI_GET_CHAR_INFO_2D (font, r, c, def, cs);
 | |
|         }
 | |
| 
 | |
|         if (cs) width += cs->character_width;
 | |
|     }
 | |
| 
 | |
|     return width;
 | |
| }
 |