483 lines
14 KiB
C
483 lines
14 KiB
C
/*
|
|
SST_WMEvent_Xlib.c
|
|
Author: Patrick Baggett <ptbaggett@762studios.com>
|
|
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 <SST/SST_WMEvent.h>
|
|
#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
|
|
};
|
|
|