/* SST_WMEvent_Xlib.c Author: Patrick Baggett Created: 1/8/2013 Purpose: Window event functions (Xlib) License: This program is free software. It comes without any warranty, to the extent permitted by applicable law. You can redistribute it and/or modify it under the terms of the Do What The Fuck You Want To Public License, Version 2, as published by Sam Hocevar. See http://sam.zoy.org/wtfpl/COPYING for more details. */ #include #include "XlibPrivate.h" #include "APIPrivate.h" static SST_WMKey XlibKeyToSSTKey(XEvent* e, uint32_t* utf32Return); #ifdef HAVE_XINPUT2 static void handleXI2Event(SST_DisplayTarget_Xlib*, XGenericEventCookie* cookie); #endif /******************************************************************************/ static int Xlib_GetEvent(SST_DisplayTarget target, SST_WMEvent* eventReturn) { XEvent e; SST_DisplayTarget_Xlib* displayTarget = (SST_DisplayTarget_Xlib*)target; Display* display = displayTarget->display; EventQueue* q = &displayTarget->eventQueue; /* Read all X events */ while(X.Pending(display)) { SST_Window_Xlib* win = NULL; SST_Window_Xlib* next; SST_WMEvent* event; #ifdef HAVE_XINPUT2 XGenericEventCookie* cookie = &e.xcookie; #endif int up = 1; X.NextEvent(display, &e); /* Find the window for which this matches and save into 'win' */ next = displayTarget->firstWindow; while(next) { /* Window ID matches this event? */ if(next->xwin == e.xany.window) { /* Found it, stop here */ win = next; break; } next = next->next; } #ifdef HAVE_XINPUT2 /* Handle X extension events */ if(cookie->type == GenericEvent && cookie->extension == displayTarget->xi2opcode) { X.GetEventData(display, cookie); handleXI2Event(displayTarget, cookie); X.FreeEventData(display, cookie); continue; } #endif /* Event doesn't apply to any window we own, ignore it */ if(win == NULL) continue; switch(e.type) { /* Moved mouse */ case MotionNotify: { //Ignore MotionNotify when using relative mouse movements if(!displayTarget->relativeMouse) { event = AllocSlotInEQ(q); if(event) { event->window = win; event->type = SSTWMEVENT_MOUSEMOVED; event->details.mouseEvent.x = (uint32_t)e.xmotion.x; event->details.mouseEvent.y = (uint32_t)e.xmotion.y; event->details.mouseEvent.button = 0; } } break; } case ButtonPress: up = 0; /* Intentional fall through */ case ButtonRelease: { const uint32_t button = e.xbutton.button; event = AllocSlotInEQ(q); if(event) { event->window = win; /* Button press (1 - 3) */ if(button >= Button1 && button <= Button3) { event->type = (up ? SSTWMEVENT_MOUSEUP : SSTWMEVENT_MOUSEDOWN); event->details.mouseEvent.x = (uint32_t)e.xbutton.x; event->details.mouseEvent.y = (uint32_t)e.xbutton.y; event->details.mouseEvent.button = SST_MB1 + (button - Button1); } else if(e.type == ButtonPress) /* Scroll wheel. Button4 = scroll up, Button5 = scroll down. */ { /* X will send two events: press/release for a single scroll. Only send 1 event */ event->type = SSTWMEVENT_MOUSEWHEEL; event->details.scrollEvent.vscroll = (button == Button4 ? 1.0f : -1.0f); event->details.scrollEvent.hscroll = 0.0f; /* TODO: how to get horizonal scroll wheel? */ } } break; } case KeyPress: up = 0; /* Intentional fall through */ case KeyRelease: { uint32_t modState = 0; uint8_t whichBit = e.xkey.keycode % 8; uint32_t whichByte = e.xkey.keycode / 8; /* X will autorepeat with a bunch of keyd release messages. We only want the key up, so it looks like: PPPPPPPPPPPPPR instead of: PRPRPRPRPRPRPR To find this out, when receive a KeyRelease message, we check if there is another KeyPress message with the same exact time. If so, we ignore this KeyRelease message. */ if(e.xany.type == KeyRelease) { if(X.Pending(display)) { XEvent nextEvent; X.PeekEvent(display, &nextEvent); if( nextEvent.type == KeyPress && /* Opposite of KeyRelease */ nextEvent.xkey.keycode == e.xkey.keycode && /* Same key code */ nextEvent.xkey.time == e.xkey.time) /* Same time */ { /* Ignore it */ continue; } } /* OK, really is a key release then -- unset this bit */ displayTarget->keymapBitvector[whichByte] &= ~(1 << whichBit); } else { if(displayTarget->keymapBitvector[whichByte] >> whichBit) modState |= SSTKEYMOD_REPEAT; /* TODO: how to set SSTKEYMOD_REPEAT flag? Maybe use XQueryKeymap()? */ displayTarget->keymapBitvector[whichByte] |= (1 << whichBit); } event = AllocSlotInEQ(q); if(event) { SST_WMKey key; uint32_t utf32; key = XlibKeyToSSTKey(&e, &utf32); event->window = win; event->type = (up ? SSTWMEVENT_KEYUP : SSTWMEVENT_KEYDOWN); event->details.keyEvent.key = key; event->details.keyEvent.utf32 = utf32; event->details.keyEvent.modifierState = modState; } break; } /* Window shown */ case Expose: { /* TODO: software rendering should blit image */ break; } case ConfigureNotify: { /* Clients are told to ignore this message if override_redirect is true. */ if(e.xconfigure.override_redirect == False) { const XConfigureEvent* xc = (const XConfigureEvent*)&e.xconfigure; /* Check if window moved */ if(win->lastX != xc->x || win->lastY != xc->y) { event = AllocSlotInEQ(q); if(event) { event->window = win; event->type = SSTWMEVENT_MOVED; event->details.winEvent.x = (uint32_t)(xc->x < 0 ? 0 : xc->x); event->details.winEvent.y = (uint32_t)(xc->y < 0 ? 0 : xc->y); } } if(win->lastWidth != xc->width || win->lastHeight != xc->height) { event = AllocSlotInEQ(q); if(event) { event->window = win; event->type = SSTWMEVENT_RESIZED; event->details.winEvent.x = (uint32_t)xc->width; event->details.winEvent.y = (uint32_t)xc->height; } win->lastWidth = xc->width; win->lastHeight = xc->height; } } break; } case ClientMessage: { /* Close window attempt? */ if(e.xclient.message_type == displayTarget->atomWmProtocols && (Atom)e.xclient.data.l[0] == displayTarget->atomWmDeleteWindow) { event = AllocSlotInEQ(q); if(event) { event->window = win; event->type = SSTWMEVENT_CLOSE; memset(&event->details, 0, sizeof(event->details)); } } break; } } } /* Dequeue for user events first */ if(RemoveFromEQ(&displayTarget->userEventQueue, eventReturn)) return 1; /* Remove system events next */ if(RemoveFromEQ(&displayTarget->eventQueue, eventReturn)) return 1; return 0; } /******************************************************************************/ static EventQueue* Xlib_getUserEventQueue(SST_DisplayTarget target) { SST_DisplayTarget_Xlib* displayTarget = (SST_DisplayTarget_Xlib*)target; return &displayTarget->userEventQueue; } /******************************************************************************/ static void Xlib_lockUserEventQueue(SST_DisplayTarget target) { SST_DisplayTarget_Xlib* displayTarget = (SST_DisplayTarget_Xlib*)target; pthread_mutex_lock(&displayTarget->eventLock); } /******************************************************************************/ static void Xlib_unlockUserEventQueue(SST_DisplayTarget target) { SST_DisplayTarget_Xlib* displayTarget = (SST_DisplayTarget_Xlib*)target; pthread_mutex_unlock(&displayTarget->eventLock); } /******************************************************************************/ static SST_WMKey XlibKeyToSSTKey(XEvent* e, uint32_t* utf32Return) { uint8_t chars[8]; uint32_t utf32; KeySym keycode; X.LookupString(&e->xkey, (char*)chars, sizeof(chars), &keycode, NULL); /* TODO: figure out how to extract multicharacter values */ utf32 = (uint32_t)chars[0]; /* X allows Ctrl+[a-z] to generate some weird characters */ if(utf32 < 0x20u) utf32 = 0; *utf32Return = utf32; /* 0 - 9 */ if(keycode >= XK_0 && keycode <= XK_9) return (SST_WMKey)(SSTWMKEY_0 + (keycode - XK_0)); /* Keypad 0 - 9 */ if(keycode >= XK_KP_0 && keycode <= XK_KP_9) return (SST_WMKey)(SSTWMKEY_KEYPAD_0 + (keycode - XK_KP_0)); /* Capital A-Z */ if(keycode >= XK_A && keycode <= XK_Z) return (SST_WMKey)(SSTWMKEY_A + (keycode - XK_A)); /* Lower case A-Z */ if(keycode >= XK_a && keycode <= XK_z) return (SST_WMKey)(SSTWMKEY_A + (keycode - XK_a)); /* Function keys (F1-F15) */ if(keycode >= XK_F1 && keycode <= XK_F15) return (SST_WMKey)(SSTWMKEY_F1 + (keycode - XK_F1)); switch(keycode) { case XK_BackSpace: return SSTWMKEY_BACKSPACE; case XK_Tab: return SSTWMKEY_TAB; case XK_Return: return SSTWMKEY_RETURN; case XK_Escape: return SSTWMKEY_ESCAPE; case XK_space: return SSTWMKEY_SPACE; case XK_Caps_Lock: return SSTWMKEY_CAPSLOCK; #if 0 case VK_OEM_COMMA : return SSTWMKEY_COMMA; case VK_OEM_PERIOD: return SSTWMKEY_PERIOD; case VK_OEM_2: return SSTWMKEY_FORWARDSLASH; /* '/?' key */ case VK_OEM_1: return SSTWMKEY_SEMICOLON; /* ';:' key */ case VK_OEM_7: return SSTWMKEY_QUOTES; /* single-quote/double quotes key */ case VK_OEM_4: return SSTWMKEY_OPENBRACKET; /* '{{' key */ case VK_OEM_6: return SSTWMKEY_CLOSEBRACKET; /* ']}' key */ case VK_OEM_5: return SSTWMKEY_BACKSLASH; /* '\|' key */ case VK_OEM_MINUS : return SSTWMKEY_UNDERBAR; case VK_OEM_PLUS: return SSTWMKEY_EQUALS; #endif //Keypad case XK_KP_Enter: return SSTWMKEY_KEYPAD_ENTER; case XK_Num_Lock: return SSTWMKEY_NUMLOCK; case XK_KP_Decimal: return SSTWMKEY_KEYPAD_PERIOD; case XK_KP_Divide: return SSTWMKEY_KEYPAD_DIVIDE; case XK_KP_Multiply: return SSTWMKEY_KEYPAD_MULTIPLY; case XK_KP_Subtract: return SSTWMKEY_KEYPAD_MINUS; case XK_KP_Add: return SSTWMKEY_KEYPAD_PLUS; /* Arrow keys */ case XK_Up: return SSTWMKEY_ARROW_UP; case XK_Down: return SSTWMKEY_ARROW_DOWN; case XK_Left: return SSTWMKEY_ARROW_LEFT; case XK_Right: return SSTWMKEY_ARROW_RIGHT; /* The page up/down block */ case XK_Insert: return SSTWMKEY_INSERT; case XK_Home: return SSTWMKEY_HOME; case XK_End: return SSTWMKEY_END; case XK_Page_Up: return SSTWMKEY_PAGEUP; case XK_Page_Down: return SSTWMKEY_PAGEDOWN; case XK_Delete: return SSTWMKEY_DELETE; case XK_Shift_L: return SSTWMKEY_LEFTSHIFT; case XK_Shift_R: return SSTWMKEY_RIGHTSHIFT; case XK_Control_L: return SSTWMKEY_LEFTCONTROL; case XK_Control_R: return SSTWMKEY_RIGHTCONTROL; case XK_Alt_L: return SSTWMKEY_LEFTALT; case XK_Alt_R: return SSTWMKEY_RIGHTALT; case XK_Super_L: return SSTWMKEY_LEFTSUPER; case XK_Super_R: return SSTWMKEY_RIGHTSUPER; #if 0 /* Misc */ case VK_SNAPSHOT: return SSTWMKEY_PRINTSCREEN; case VK_SCROLL: return SSTWMKEY_SCROLLLOCK; case VK_PAUSE: return SSTWMKEY_PAUSE; #endif case XK_asciitilde: return SSTWMKEY_TILDE; /* `~ key */ default: break; } return SSTWMKEY_NONE; } /******************************************************************************/ #ifdef HAVE_XINPUT2 static size_t min2(size_t x, size_t y) { return (x < y ? x : y); } static void extractXI2values(const XIRawEvent* rawev, double* valuesReturn, size_t valueArraySize) { size_t i; const double* raw_values = rawev->raw_values; const unsigned char* mask = rawev->valuators.mask; const size_t elemsToCheck = min2(valueArraySize, rawev->valuators.mask_len * 8); /* From what I can understand, raw_values[] is a compressed array. XIMaskIsSet(mask,i) is used to check whether element i of the output should be read from raw_values[]. So for example, raw_values[] could be just { 3.0, 5.0}, but the mask could be 1001b, which means that: XIMaskIsSet(1001b,0) == true; -> output[0] = 3.0 (raw_values[0)] XIMaskIsSet(1001b,1) == false; -> output[1] = 0.0 (default value) XIMaskIsSet(1001b,2) == false; -> output[2] = 0.0 (default value) XIMaskIsSet(1001b,3) == true; -> output[3] = 5.0 (raw_values[1]) */ for(i=0; i < elemsToCheck; i++) { if(XIMaskIsSet(mask, i)) { valuesReturn[i] = *raw_values; raw_values++; } else { valuesReturn[i] = 0.0; } } /* Any uninitialized elements at the end set to zero */ if(elemsToCheck < valueArraySize) { memset(&valuesReturn[elemsToCheck], 0, (valueArraySize-elemsToCheck)*sizeof(*valuesReturn)); } } static void handleXI2Event(SST_DisplayTarget_Xlib* displayTarget, XGenericEventCookie* cookie) { XIDeviceEvent* ev = (XIDeviceEvent*)cookie->data; EventQueue* q = &displayTarget->eventQueue; SST_WMEvent* event; switch(ev->evtype) { case XI_RawButtonPress: case XI_RawButtonRelease: break; case XI_RawMotion: { XIRawEvent* re = (XIRawEvent*)cookie->data; event = AllocSlotInEQ(q); if(event) { double rel[2]; extractXI2values(re, rel, 2); event->window = NULL; /* TODO: ... ?*/ event->type = SSTWMEVENT_MOUSERELMOVED; event->details.relMouseEvent.relx = (int32_t)rel[0]; event->details.relMouseEvent.rely = (int32_t)rel[1]; } break; } } } #endif const struct SST_WM_EventFuncs Xlib_EventFuncs = { Xlib_GetEvent, Xlib_getUserEventQueue, Xlib_lockUserEventQueue, Xlib_unlockUserEventQueue };