/* * Matchbox Keyboard - A lightweight software keyboard. * * Authored By Matthew Allum * * Copyright (c) 2005 OpenedHand Ltd - http://o-hand.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program 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 General Public License for more details. * */ #include "matchbox-keyboard.h" #define PROP_MOTIF_WM_HINTS_ELEMENTS 5 #define MWM_HINTS_DECORATIONS (1L << 1) #define MWM_DECOR_BORDER (1L << 1) typedef struct { unsigned long flags; unsigned long functions; unsigned long decorations; long inputMode; unsigned long status; } PropMotifWmHints; struct MBKeyboardUI { Display *xdpy; int xscreen; Window xwin_root, xwin; Pixmap backbuffer; int dpy_width, dpy_height; int xwin_width, xwin_height; int key_uwidth, key_uheight; int base_alloc_width, base_alloc_height; int base_font_pt_size; Bool want_embedding; Bool is_daemon; Bool visible; FakeKey *fakekey; MBKeyboardUIBackend *backend; MBKeyboard *kbd; MBKeyboardDisplayOrientation dpy_orientation; MBKeyboardDisplayOrientation valid_orientation; }; static void mb_kbd_ui_resize(MBKeyboardUI *ui, int width, int height); static int mb_kbd_ui_load_font(MBKeyboardUI *ui); static char* get_current_window_manager_name (MBKeyboardUI *ui) { Atom atom_utf8_string, atom_wm_name, atom_check, type; int result, format; char *val, *retval; unsigned long nitems, bytes_after; Window *support_xwin = NULL; atom_check = XInternAtom (ui->xdpy, "_NET_SUPPORTING_WM_CHECK", False); XGetWindowProperty (ui->xdpy, RootWindow(ui->xdpy, ui->xscreen), atom_check, 0, 16L, False, XA_WINDOW, &type, &format, &nitems, &bytes_after, (unsigned char **)&support_xwin); if (support_xwin == NULL) return NULL; atom_utf8_string = XInternAtom (ui->xdpy, "UTF8_STRING", False); atom_wm_name = XInternAtom (ui->xdpy, "_NET_WM_NAME", False); result = XGetWindowProperty (ui->xdpy, *support_xwin, atom_wm_name, 0, 1000L,False, atom_utf8_string, &type, &format, &nitems, &bytes_after, (unsigned char **)&val); if (result != Success) return NULL; if (type != atom_utf8_string || format !=8 || nitems == 0) { if (val) XFree (val); return NULL; } retval = strdup (val); XFree (val); return retval; } static boolean get_desktop_area(MBKeyboardUI *ui, int *x, int *y, int *width, int *height) { Atom atom_area, type; int result, format; unsigned long nitems, bytes_after; int *geometry = NULL; atom_area = XInternAtom (ui->xdpy, "_NET_WORKAREA", False); result = XGetWindowProperty (ui->xdpy, RootWindow(ui->xdpy, ui->xscreen), atom_area, 0, 16L, False, XA_CARDINAL, &type, &format, &nitems, &bytes_after, (unsigned char **)&geometry); if (result != Success || nitems < 4 || geometry == NULL) { if (geometry) XFree(geometry); return False; } if (x) *x = geometry[0]; if (y) *y = geometry[1]; if (width) *width = geometry[2]; if (height) *height = geometry[3]; XFree(geometry); return True; } static void update_display_size(MBKeyboardUI *ui) { XWindowAttributes winattr; MARK(); XGetWindowAttributes(ui->xdpy, ui->xwin_root, &winattr); /* XXX should actually trap an X error here */ ui->dpy_width = winattr.width; ui->dpy_height = winattr.height; if (ui->dpy_width > ui->dpy_height) ui->dpy_orientation = MBKeyboardDisplayLandscape; else ui->dpy_orientation = MBKeyboardDisplayPortrait; if (ui->valid_orientation && ui->dpy_orientation != ui->valid_orientation) mb_kbd_ui_hide (ui); DBG("#### Orientation know '%i'", ui->dpy_orientation); return; } static boolean want_extended(MBKeyboardUI *ui) { /* Are we in portrait ? */ if (ui->dpy_width > ui->dpy_height) return True; return False; } static boolean get_xevent_timed(Display *dpy, XEvent *event_return, struct timeval *tv) { if (tv->tv_usec == 0 && tv->tv_sec == 0) { XNextEvent(dpy, event_return); return True; } XFlush(dpy); if (XPending(dpy) == 0) { int fd = ConnectionNumber(dpy); fd_set readset; FD_ZERO(&readset); FD_SET(fd, &readset); if (select(fd+1, &readset, NULL, NULL, tv) == 0) return False; else { XNextEvent(dpy, event_return); return True; } } else { XNextEvent(dpy, event_return); return True; } } void mb_kbd_ui_send_press(MBKeyboardUI *ui, const char *utf8_char_in, int modifiers) { DBG("Sending '%s'", utf8_char_in); fakekey_press(ui->fakekey, (unsigned char*)utf8_char_in, -1, modifiers); } void mb_kbd_ui_send_keysym_press(MBKeyboardUI *ui, KeySym ks, int modifiers) { fakekey_press_keysym(ui->fakekey, ks, modifiers); } void mb_kbd_ui_send_release(MBKeyboardUI *ui) { fakekey_release(ui->fakekey); } static void mb_kdb_ui_unit_key_size(MBKeyboardUI *ui, int *width, int *height) { MBKeyboardLayout *layout; List *row_item, *key_item; MBKeyboardKeyStateType state; const char *face_str; *width = 0; *height = 0; layout = mb_kbd_get_selected_layout(ui->kbd); row_item = mb_kbd_layout_rows(layout); /* * Figure out the base size of a 'regular' single glyph key. */ while (row_item != NULL) { mb_kbd_row_for_each_key(row_item->data, key_item) { MBKeyboardKey *key = key_item->data; if (!mb_kbd_is_extended(ui->kbd) && mb_kbd_key_get_extended(key)) continue; /* Ignore keys whose width is forced */ if (mb_kbd_key_get_req_uwidth(key)) continue; mb_kdb_key_foreach_state(key, state) { if (mb_kbd_key_get_face_type(key, state) == MBKeyboardKeyFaceGlyph) { face_str = mb_kbd_key_get_glyph_face(key, state); if (util_utf8_char_cnt(face_str) == 1) { int str_w =0, str_h = 0; ui->backend->text_extents(ui, face_str, &str_w, &str_h); if (str_w > *width) *width = str_w; if (str_h > *height) *height = str_h; } } else if (mb_kbd_key_get_face_type(key, state) == MBKeyboardKeyFaceImage) { MBKeyboardImage *img; img = mb_kbd_key_get_image_face(key, state); if (mb_kbd_image_width (img) > *width) *width = mb_kbd_image_width (img); if (mb_kbd_image_height (img) > *height) *height = mb_kbd_image_height (img); } } } row_item = util_list_next(row_item); } /* FIXME: hack for small displays */ if (mb_kbd_ui_display_height(ui) <= 320) { *height += 4; } } static void mb_kbd_ui_min_key_size(MBKeyboardUI *ui, MBKeyboardKey *key, int *width, int *height) { const char *face_str = NULL; int max_w = 0, max_h = 0, state, kw, kh; /* * Figure out how small a key can really be UI wise. */ if (mb_kbd_key_get_req_uwidth(key) || mb_kbd_key_is_blank(key)) { *width = (ui->key_uwidth * mb_kbd_key_get_req_uwidth(key)) / 1000 ; *height = ui->key_uheight; return; } mb_kdb_key_foreach_state(key, state) { if (mb_kbd_key_get_face_type(key, state) == MBKeyboardKeyFaceGlyph) { face_str = mb_kbd_key_get_glyph_face(key, state); ui->backend->text_extents(ui, face_str, &kw, &kh); if (kw > max_w) max_w = kw; if (kh > max_h) max_h = kh; } else if (mb_kbd_key_get_face_type(key, state) == MBKeyboardKeyFaceImage) { MBKeyboardImage *img; img = mb_kbd_key_get_image_face(key, state); if (mb_kbd_image_width (img) > max_w) max_w = mb_kbd_image_width (img); if (mb_kbd_image_height (img) > max_h) max_h = mb_kbd_image_height (img); } } } void mb_kbd_ui_allocate_ui_layout(MBKeyboardUI *ui, int *width, int *height) { MBKeyboardLayout *layout; List *row_item, *key_item; int key_y = 0, key_x = 0; int row_y, max_row_key_height, max_row_width; layout = mb_kbd_get_selected_layout(ui->kbd); /* Do an initial run to figure out a 'base' size for single glyph keys */ mb_kdb_ui_unit_key_size(ui, &ui->key_uwidth, &ui->key_uheight); row_item = mb_kbd_layout_rows(layout); row_y = mb_kbd_row_spacing(ui->kbd); max_row_width = 0; /* * First of entire keyboard, basically get the minimum space needed */ while (row_item != NULL) { MBKeyboardRow *row = row_item->data; key_x = mb_kbd_col_spacing(ui->kbd); max_row_key_height = 0; mb_kbd_row_for_each_key(row, key_item) { int key_w = 0, key_h = 0; MBKeyboardKey *key = key_item->data; mb_kbd_key_set_extra_height_pad(key, 0); mb_kbd_key_set_extra_width_pad(key, 0); mb_kbd_key_set_geometry(key, 0, 0, 0, 0); if (!mb_kbd_is_extended(ui->kbd) && mb_kbd_key_get_extended(key)) continue; mb_kbd_ui_min_key_size(ui, key, &key_w, &key_h); if (!mb_kbd_key_get_req_uwidth(key) && key_w < ui->key_uwidth) key_w = ui->key_uwidth; if (key_h < ui->key_uheight) key_h = ui->key_uheight; key_y = 0; key_w += 2 * ( mb_kbd_keys_border(ui->kbd) + mb_kbd_keys_margin(ui->kbd) + mb_kbd_keys_pad(ui->kbd) ); key_h += 2 * ( mb_kbd_keys_border(ui->kbd) + mb_kbd_keys_margin(ui->kbd) + mb_kbd_keys_pad(ui->kbd) ); if (key_h > max_row_key_height) max_row_key_height = key_h; mb_kbd_key_set_geometry(key, key_x, key_y, key_w, key_h); key_x += (mb_kbd_col_spacing(ui->kbd) + key_w); } if (key_x > max_row_width) /* key_x now represents row width */ max_row_width = key_x; mb_kbd_row_set_y(row, row_y); row_y += max_row_key_height + mb_kbd_row_spacing(ui->kbd); row_item = util_list_next(row_item); } *height = row_y; row_item = mb_kbd_layout_rows(layout); /* Now pass again allocating any extra space with have left over */ while (row_item != NULL) { MBKeyboardRow *row = row_item->data; int n_fillers = 0, free_space = 0, new_w = 0; mb_kbd_row_for_each_key(row,key_item) { if (!mb_kbd_is_extended(ui->kbd) && mb_kbd_key_get_extended(key_item->data)) continue; if (mb_kbd_key_get_fill(key_item->data) || mb_kbd_ui_display_height(ui) <= 320 || mb_kbd_ui_display_width(ui) <= 320 ) n_fillers++; } if (!n_fillers) goto next_row; free_space = max_row_width - mb_kbd_row_width(row); mb_kbd_row_for_each_key(row, key_item) { if (!mb_kbd_is_extended(ui->kbd) && mb_kbd_key_get_extended(key_item->data)) continue; if (mb_kbd_key_get_fill(key_item->data) || mb_kbd_ui_display_height(ui) <= 320 || mb_kbd_ui_display_width(ui) <= 320 ) { int old_w; List *nudge_key_item = util_list_next(key_item); old_w = mb_kbd_key_width(key_item->data); new_w = old_w + (free_space/n_fillers); mb_kbd_key_set_geometry(key_item->data, -1, -1, new_w, -1); /* nudge next keys forward */ for (; nudge_key_item != NULL; nudge_key_item = util_list_next(nudge_key_item)) { if (!mb_kbd_is_extended(ui->kbd) && mb_kbd_key_get_extended(nudge_key_item->data)) continue; mb_kbd_key_set_geometry(nudge_key_item->data, mb_kbd_key_x(nudge_key_item->data) + (new_w - old_w ), -1, -1, -1); } } } next_row: row_item = util_list_next(row_item); } /* Now center the rows */ row_item = mb_kbd_layout_rows(layout); while (row_item != NULL) { MBKeyboardRow *row = row_item->data; mb_kbd_row_set_x(row, (max_row_width - mb_kbd_row_width(row))/2); row_item = util_list_next(row_item); } *width = max_row_width; } void mb_kbd_ui_redraw_key(MBKeyboardUI *ui, MBKeyboardKey *key) { ui->backend->redraw_key(ui, key); } static void mb_kbd_ui_redraw_row(MBKeyboardUI *ui, MBKeyboardRow *row) { List *key_item; mb_kbd_row_for_each_key(row, key_item) { if (!mb_kbd_is_extended(ui->kbd) && mb_kbd_key_get_extended(key_item->data)) continue; mb_kbd_ui_redraw_key(ui, key_item->data); } } void mb_kbd_ui_swap_buffers(MBKeyboardUI *ui) { XClearWindow(ui->xdpy, ui->xwin); XSync(ui->xdpy, False); } void mb_kbd_ui_redraw(MBKeyboardUI *ui) { List *row_item; MBKeyboardLayout *layout; MARK(); /* gives backend a chance to clear everything */ ui->backend->pre_redraw(ui); layout = mb_kbd_get_selected_layout(ui->kbd); row_item = mb_kbd_layout_rows(layout); while (row_item != NULL) { MBKeyboardRow *row = row_item->data; mb_kbd_ui_redraw_row(ui, row); row_item = util_list_next(row_item); } mb_kbd_ui_swap_buffers(ui); } void mb_kbd_ui_show(MBKeyboardUI *ui) { if (ui->visible) return; if (ui->valid_orientation && ui->dpy_orientation != ui->valid_orientation) return; XMapWindow(ui->xdpy, ui->xwin); mb_kbd_ui_redraw (ui); ui->visible = True; } void mb_kbd_ui_hide(MBKeyboardUI *ui) { if (!ui->visible) return; XUnmapWindow(ui->xdpy, ui->xwin); ui->visible = False; } static int mb_kbd_ui_resources_create(MBKeyboardUI *ui) { #define PROP_MOTIF_WM_HINTS_ELEMENTS 5 #define MWM_HINTS_DECORATIONS (1L << 1) #define MWM_DECOR_BORDER (1L << 1) typedef struct { unsigned long flags; unsigned long functions; unsigned long decorations; long inputMode; unsigned long status; } PropMotifWmHints ; Atom /* atom_wm_protocols[3], */ atom_NET_WM_WINDOW_TYPE, atom_NET_WM_WINDOW_TYPE_TOOLBAR, atom_NET_WM_WINDOW_TYPE_DOCK, atom_NET_WM_STRUT_PARTIAL, atom_NET_WM_STATE_SKIP_PAGER, atom_NET_WM_STATE_SKIP_TASKBAR, atom_NET_WM_STATE, atom_MOTIF_WM_HINTS; PropMotifWmHints *mwm_hints; XSizeHints size_hints; XWMHints *wm_hints; XSetWindowAttributes win_attr; char *wm_name; boolean have_matchbox_wm = False; boolean have_ewmh_wm = False; /* atom_wm_protocols = { XInternAtom(ui->xdpy, "WM_DELETE_WINDOW",False), XInternAtom(ui->xdpy, "WM_PROTOCOLS",False), XInternAtom(ui->xdpy, "WM_NORMAL_HINTS", False), }; */ atom_NET_WM_WINDOW_TYPE = XInternAtom(ui->xdpy, "_NET_WM_WINDOW_TYPE" , False); atom_NET_WM_WINDOW_TYPE_TOOLBAR = XInternAtom(ui->xdpy, "_NET_WM_WINDOW_TYPE_TOOLBAR", False); atom_NET_WM_WINDOW_TYPE_DOCK = XInternAtom(ui->xdpy, "_NET_WM_WINDOW_TYPE_DOCK", False); atom_MOTIF_WM_HINTS = XInternAtom(ui->xdpy, "_MOTIF_WM_HINTS", False); atom_NET_WM_STRUT_PARTIAL = XInternAtom(ui->xdpy, "_NET_WM_STRUT_PARTIAL", False); atom_NET_WM_STATE_SKIP_TASKBAR = XInternAtom(ui->xdpy, "_NET_WM_STATE_SKIP_TASKBAR", False); atom_NET_WM_STATE_SKIP_PAGER = XInternAtom(ui->xdpy, "_NET_WM_STATE_SKIP_PAGER", False); atom_NET_WM_STATE = XInternAtom(ui->xdpy, "_NET_WM_STATE", False); if ((wm_name = get_current_window_manager_name(ui)) != NULL) { have_ewmh_wm = True; /* basically assumed to be Metacity or at least only tested with mcity */ } else { if (ui->is_daemon) { /* Hack to avoid starting before the WM. Needed only in daemon mode */ while (wm_name == NULL) { sleep(1); wm_name = get_current_window_manager_name(ui); } have_ewmh_wm = True; } } if (wm_name && streq(wm_name, "matchbox")) have_matchbox_wm = True; win_attr.override_redirect = False; /* Set to true for extreme case */ win_attr.event_mask = ButtonPressMask|ButtonReleaseMask|Button1MotionMask|StructureNotifyMask; ui->xwin = XCreateWindow(ui->xdpy, ui->xwin_root, 0, 0, ui->xwin_width, ui->xwin_height, 0, CopyFromParent, CopyFromParent, CopyFromParent, CWOverrideRedirect|CWEventMask, &win_attr); XSelectInput (ui->xdpy, ui->xwin_root, SubstructureNotifyMask|StructureNotifyMask); wm_hints = XAllocWMHints(); if (wm_hints) { DBG("setting no focus hint"); wm_hints->input = False; wm_hints->flags = InputHint; XSetWMHints(ui->xdpy, ui->xwin, wm_hints ); XFree(wm_hints); } size_hints.flags = PPosition | PSize | PMinSize; size_hints.x = 0; size_hints.y = 0; size_hints.width = ui->xwin_width; size_hints.height = ui->xwin_height; size_hints.min_width = ui->xwin_width; size_hints.min_height = ui->xwin_height; XSetStandardProperties(ui->xdpy, ui->xwin, "Keyboard", NULL, 0, NULL, 0, &size_hints); if (!ui->want_embedding) { mwm_hints = util_malloc0(sizeof(PropMotifWmHints)); if (mwm_hints) { mwm_hints->flags = MWM_HINTS_DECORATIONS; mwm_hints->decorations = 0; XChangeProperty(ui->xdpy, ui->xwin, atom_MOTIF_WM_HINTS, XA_ATOM, 32, PropModeReplace, (unsigned char *)mwm_hints, PROP_MOTIF_WM_HINTS_ELEMENTS); free(mwm_hints); } if (have_ewmh_wm) { /* XXX Fix this for display size */ int wm_struct_vals[] = { 0, /* left */ 0, /* right */ 0, /* top */ 0, /* bottom */ 0, /* left_start_y */ 0, /* left_end_y */ 0, /* right_start_y */ 0, /* right_end_y */ 0, /* top_start_x */ 0, /* top_end_x */ 0, /* bottom_start_x */ 1399 }; /* bottom_end_x */ Atom states[] = { atom_NET_WM_STATE_SKIP_TASKBAR, atom_NET_WM_STATE_SKIP_PAGER }; int desk_width = 0, desk_height = 0, desk_y = 0; XChangeProperty(ui->xdpy, ui->xwin, atom_NET_WM_STATE, XA_ATOM, 32, PropModeReplace, (unsigned char *)states, 2); if (get_desktop_area(ui, NULL, &desk_y, &desk_width, &desk_height)) { /* Assuming we take up all available display width * ( at least true with matchbox wm ). we resize * the base ui width to this ( and height as a factor ) * to avoid the case of mapping and then the wm resizing * us, causing an ugly repaint. */ if (desk_width > ui->xwin_width) { mb_kbd_ui_resize(ui, desk_width, ( desk_width * ui->xwin_height ) / ui->xwin_width); } wm_struct_vals[2] = desk_y + desk_height - ui->xwin_height; wm_struct_vals[11] = desk_width; XChangeProperty(ui->xdpy, ui->xwin, atom_NET_WM_STRUT_PARTIAL, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)wm_struct_vals , 12); DBG("desk width: %i, desk height: %i xwin_height :%i", desk_width, desk_height, ui->xwin_height); } if (have_matchbox_wm) { XChangeProperty(ui->xdpy, ui->xwin, atom_NET_WM_WINDOW_TYPE, XA_ATOM, 32, PropModeReplace, (unsigned char *) &atom_NET_WM_WINDOW_TYPE_TOOLBAR, 1); } else { /* XChangeProperty(ui->xdpy, ui->xwin, atom_NET_WM_WINDOW_TYPE, XA_ATOM, 32, PropModeReplace, (unsigned char *) &atom_NET_WM_WINDOW_TYPE_DOCK, 1); */ } } } ui->backbuffer = XCreatePixmap(ui->xdpy, ui->xwin, ui->xwin_width, ui->xwin_height, DefaultDepth(ui->xdpy, ui->xscreen)); XSetWindowBackgroundPixmap(ui->xdpy, ui->xwin, ui->backbuffer); ui->backend->resources_create(ui); /* Get root size change events for rotation */ /* XSelectInput(ui->xdpy, ui->xwin_root, StructureNotifyMask); */ return 1; } static void mb_kbd_ui_resize(MBKeyboardUI *ui, int width, int height) { MBKeyboard *kbd = ui->kbd; MBKeyboardLayout *layout; List *row_item, *key_item; int width_diff, height_diff; int height_font_pt_size, width_font_pt_size; int next_row_y, n_rows, extra_key_height; MARK(); width_diff = width - ui->base_alloc_width; height_diff = height - ui->base_alloc_height; if (width_diff < 0 || height_diff < 0) return; /* dont go smaller than our int request - get clipped */ layout = mb_kbd_get_selected_layout(ui->kbd); row_item = mb_kbd_layout_rows(layout); /* load a bigger font ? * Only load if height and width have changed */ width_font_pt_size = ( (ui->base_font_pt_size * width) / ui->base_alloc_width ); if (util_abs(width_font_pt_size - kbd->font_pt_size) > 2) { height_font_pt_size = ( (ui->base_font_pt_size * height) / ui->base_alloc_height ); if (util_abs(height_font_pt_size - kbd->font_pt_size) > 2) { ui->kbd->font_pt_size = (height_font_pt_size > width_font_pt_size) ? width_font_pt_size : height_font_pt_size; mb_kbd_ui_load_font(ui); } } n_rows = util_list_length(row_item); extra_key_height = (height_diff / n_rows); DBG("****** extra height is %i ******", extra_key_height); next_row_y = mb_kbd_row_spacing(ui->kbd); /* allocate the extra width we have as padding to keys */ while (row_item != NULL) { int row_base_width, new_row_base_width, row_width_diff; int next_key_x = 0, n_fillers = 0, free_space = 0, new_w = 0; row_base_width = mb_kbd_row_base_width(row_item->data); new_row_base_width = ( row_base_width * width ) / ui->base_alloc_width; row_width_diff = new_row_base_width - row_base_width; DBG("row_width_diff = %i", row_width_diff); next_key_x = mb_kbd_col_spacing(ui->kbd); /* * row_base_width * -------------- X new_width = new_base_width * base_width * * key_extra_pad = key_base_width X base_width_diff * -------------------------------- * row_base_width */ mb_kbd_row_for_each_key(row_item->data, key_item) { MBKeyboardKey *key = key_item->data; int key_base_width, key_new_pad; if (!mb_kbd_is_extended(kbd) && mb_kbd_key_get_extended(key)) continue; key_base_width =( mb_kbd_key_width(key) - mb_kbd_key_get_extra_width_pad(key)); key_new_pad= ( (key_base_width + mb_kbd_col_spacing(kbd)) * row_width_diff) / row_base_width; mb_kbd_key_set_extra_width_pad (key, key_new_pad ); /* Height */ mb_kbd_key_set_extra_height_pad (key, extra_key_height); mb_kbd_key_set_geometry(key, next_key_x, -1, -1, -1); next_key_x += (mb_kbd_key_width(key) + mb_kbd_col_spacing(ui->kbd)); if (mb_kbd_key_get_fill(key)) n_fillers++; } /* The above ( likely due to rounding ) leaves a few pixels free. * This can be critical on a small handheld display. Therefore * we do a second parse deviding up any left over space between * keys marked as fill. */ if (n_fillers) { free_space = width - mb_kbd_row_width(row_item->data); mb_kbd_row_for_each_key(row_item->data, key_item) { if (!mb_kbd_is_extended(kbd) && mb_kbd_key_get_extended(key_item->data)) continue; if (mb_kbd_key_get_fill(key_item->data)) { int old_w; List *nudge_key_item = util_list_next(key_item); old_w = mb_kbd_key_width(key_item->data); new_w = old_w + (free_space/n_fillers); mb_kbd_key_set_geometry(key_item->data, -1, -1, new_w, -1); /* nudge next keys forward */ for (; nudge_key_item != NULL; nudge_key_item = util_list_next(nudge_key_item)) { if (!mb_kbd_is_extended(ui->kbd) && mb_kbd_key_get_extended(nudge_key_item->data)) continue; mb_kbd_key_set_geometry(nudge_key_item->data, mb_kbd_key_x(nudge_key_item->data) + (new_w - old_w ), -1, -1, -1); } } } } /* re-center row */ mb_kbd_row_set_x(row_item->data, (width - mb_kbd_row_width(row_item->data))/2); /* and position down */ mb_kbd_row_set_y(row_item->data, next_row_y); next_row_y += (mb_kbd_row_height(row_item->data) + mb_kbd_row_spacing(ui->kbd)); row_item = util_list_next(row_item); } /* center entire layout vertically if space left */ if (next_row_y < height) { int vspace = ( height - next_row_y ) / 2; row_item = mb_kbd_layout_rows(layout); while (row_item != NULL) { mb_kbd_row_set_y(row_item->data, mb_kbd_row_y(row_item->data) + vspace + 1); row_item = util_list_next(row_item); } } XResizeWindow(ui->xdpy, ui->xwin, width, height); ui->xwin_width = width; ui->xwin_height = height; if (ui->backbuffer) /* may get called before initialised */ { XFreePixmap(ui->xdpy, ui->backbuffer); ui->backbuffer = XCreatePixmap(ui->xdpy, ui->xwin, ui->xwin_width, ui->xwin_height, DefaultDepth(ui->xdpy, ui->xscreen)); ui->backend->resize(ui, width, height); XSetWindowBackgroundPixmap(ui->xdpy, ui->xwin, ui->backbuffer); mb_kbd_ui_redraw(ui); } } void mb_kbd_ui_handle_configure(MBKeyboardUI *ui, int width, int height) { boolean old_state, new_state; MARK(); /* Figure out if screen size has changed - does a round trip - bad */ update_display_size(ui); old_state = mb_kbd_is_extended(ui->kbd); new_state = want_extended(ui); if (new_state == old_state) /* Not a rotation */ { mb_kbd_ui_resize(ui, width, height); return; } mb_kbd_set_extended(ui->kbd, new_state); /* realocate the layout */ mb_kbd_ui_allocate_ui_layout(ui, &ui->base_alloc_width, &ui->base_alloc_height); mb_kbd_ui_resize(ui, width, height); } void mb_kbd_ui_event_loop(MBKeyboardUI *ui) { MBKeyboardKey *key = NULL; struct timeval tvt; /* Key repeat - values for standard xorg install ( xset q) */ int repeat_delay = 100 * 10000; int repeat_rate = 30 * 1000; int hide_delay = 100 * 1000; int to_hide = 0; int press_x = 0, press_y = 0; tvt.tv_sec = 0; tvt.tv_usec = repeat_delay; while (True) { XEvent xev; if (get_xevent_timed(ui->xdpy, &xev, &tvt)) { switch (xev.type) { case ButtonPress: press_x = xev.xbutton.x; press_y = xev.xbutton.y; DBG("got button bress at %i,%i", xev.xbutton.x, xev.xbutton.y); key = mb_kbd_locate_key(ui->kbd, xev.xbutton.x, xev.xbutton.y); if (key) { /* Hack if we never get a release event */ if (key != mb_kbd_get_held_key(ui->kbd)) { mb_kbd_key_release(ui->kbd); tvt.tv_usec = repeat_delay; } else tvt.tv_usec = repeat_rate; DBG("found key for press"); mb_kbd_key_press(key); } break; case ButtonRelease: if (mb_kbd_get_held_key(ui->kbd) != NULL) { mb_kbd_key_release(ui->kbd); tvt.tv_usec = repeat_delay; /* Gestures */ #if 0 /* FIXME: check time first */ if ( (press_x - xev.xbutton.x) > ui->key_uwidth ) { /* <-- slide back ...backspace */ fakekey_press_keysym(ui->fakekey, XK_BackSpace, 0); fakekey_repeat(ui->fakekey); fakekey_release(ui->fakekey); /* FIXME: add <-- --> <-- --> support */ } else if ( (xev.xbutton.y - press_y) > ui->key_uheight ) { /* V slide down ...return */ fakekey_press_keysym(ui->fakekey, XK_BackSpace, 0); fakekey_release(ui->fakekey); fakekey_press_keysym(ui->fakekey, XK_Return, 0); fakekey_release(ui->fakekey); } #endif /* TODO ^ caps support */ } break; case ConfigureNotify: if (xev.xconfigure.window == ui->xwin && (xev.xconfigure.width != ui->xwin_width || xev.xconfigure.height != ui->xwin_height)) { mb_kbd_ui_handle_configure(ui, xev.xconfigure.width, xev.xconfigure.height); } if (xev.xconfigure.window == ui->xwin_root) update_display_size(ui); break; case MappingNotify: fakekey_reload_keysyms(ui->fakekey); XRefreshKeyboardMapping(&xev.xmapping); break; default: break; } if (ui->want_embedding) mb_kbd_xembed_process_xevents (ui, &xev); if (ui->is_daemon) { switch (mb_kbd_remote_process_xevents (ui, &xev)) { case MBKeyboardRemoteHide: if (to_hide == 1) { mb_kbd_ui_hide(ui); } tvt.tv_usec = hide_delay; to_hide = 1; break; case MBKeyboardRemoteShow: mb_kbd_ui_show(ui); tvt.tv_usec = repeat_delay; to_hide = 0; break; case MBKeyboardRemoteToggle: to_hide = 0; tvt.tv_usec = repeat_delay; if (ui->visible) mb_kbd_ui_hide(ui); else mb_kbd_ui_show(ui); break; case MBKeyboardRemoteNone: if (to_hide == 1) { mb_kbd_ui_hide(ui); tvt.tv_usec = repeat_delay; to_hide = 0; } break; } } } else { /* Hide timed out */ if (to_hide) { DBG("Hide timed out, calling mb_kbd_ui_hide"); mb_kbd_ui_hide(ui); tvt.tv_usec = repeat_delay; to_hide = 0; } /* Keyrepeat */ if (mb_kbd_get_held_key(ui->kbd) != NULL) { fakekey_repeat(ui->fakekey); tvt.tv_usec = repeat_rate; } } } } static int mb_kbd_ui_load_font(MBKeyboardUI *ui) { return ui->backend->font_load(ui); } int mb_kbd_ui_display_width(MBKeyboardUI *ui) { return ui->dpy_width; } int mb_kbd_ui_display_height(MBKeyboardUI *ui) { return ui->dpy_height; } MBKeyboardUIBackend* mb_kbd_ui_backend(MBKeyboardUI *ui) { return ui->backend; } Display* mb_kbd_ui_x_display(MBKeyboardUI *ui) { return ui->xdpy; } int mb_kbd_ui_x_screen(MBKeyboardUI *ui) { return ui->xscreen; } Window mb_kbd_ui_x_win(MBKeyboardUI *ui) { return ui->xwin; } Window mb_kbd_ui_x_win_root(MBKeyboardUI *ui) { return ui->xwin_root; } int mb_kbd_ui_x_win_height(MBKeyboardUI *ui) { return ui->xwin_height; } int mb_kbd_ui_x_win_width(MBKeyboardUI *ui) { return ui->xwin_width; } Pixmap mb_kbd_ui_backbuffer(MBKeyboardUI *ui) { return ui->backbuffer; } MBKeyboard* mb_kbd_ui_kbd(MBKeyboardUI *ui) { return ui->kbd; } int mb_kbd_ui_realize(MBKeyboardUI *ui) { ui->base_font_pt_size = ui->kbd->font_pt_size; if (!mb_kbd_ui_load_font(ui)) return 0; /* potrait or landscape */ if (want_extended(ui)) mb_kbd_set_extended(ui->kbd, True); /* * figure out how small this keyboard can be.. */ mb_kbd_ui_allocate_ui_layout(ui, &ui->base_alloc_width, &ui->base_alloc_height); ui->xwin_width = ui->base_alloc_width; ui->xwin_height = ui->base_alloc_height; mb_kbd_ui_resources_create(ui); unless (mb_kbd_ui_embeded(ui)) { if (ui->is_daemon) { /* Dont map daemon to begin with */ mb_kbd_remote_init (ui); } else { mb_kbd_ui_show(ui); mb_kbd_ui_redraw(ui); } } else mb_kbd_xembed_init (ui); return 1; } int mb_kbd_ui_init(MBKeyboard *kbd) { MBKeyboardUI *ui = NULL; ui = kbd->ui = util_malloc0(sizeof(MBKeyboardUI)); ui->kbd = kbd; if ((ui->xdpy = XOpenDisplay(getenv("DISPLAY"))) == NULL) return 0; if ((ui->fakekey = fakekey_init(ui->xdpy)) == NULL) return 0; ui->xscreen = DefaultScreen(ui->xdpy); ui->xwin_root = RootWindow(ui->xdpy, ui->xscreen); ui->backend = MB_KBD_UI_BACKEND_INIT_FUNC(ui); update_display_size(ui); return 1; } /* Embedding */ void mb_kbd_ui_set_embeded (MBKeyboardUI *ui, int embed) { ui->want_embedding = embed; } int mb_kbd_ui_embeded (MBKeyboardUI *ui) { return ui->want_embedding; } void mb_kbd_ui_print_window (MBKeyboardUI *ui) { fprintf(stdout, "%li\n", mb_kbd_ui_x_win(ui)); fflush(stdout); } /* Remote */ void mb_kbd_ui_set_daemon (MBKeyboardUI *ui, int value) { ui->is_daemon = value; } void mb_kbd_ui_limit_orientation (MBKeyboardUI *ui, MBKeyboardDisplayOrientation orientation) { ui->valid_orientation = orientation; }