mitsuba/src/libhw/x11device.cpp

597 lines
18 KiB
C++

/*
This file is part of Mitsuba, a physically based rendering system.
Copyright (c) 2007-2011 by Wenzel Jakob and others.
Mitsuba is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Mitsuba 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.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <mitsuba/hw/glxrenderer.h>
#include <mitsuba/hw/x11device.h>
#include <X11/keysym.h>
#define DEFINE_KEY(xsym, sym) m_keymap[xsym & 0xFF] = sym
MTS_NAMESPACE_BEGIN
X11Device::X11Device(X11Session *session)
: Device(session), m_visinfo(NULL), m_visible(false) {
m_title = "Mitsuba [x11]";
for (int i=0; i<256; i++)
m_keymap[i] = ENoSpecial;
/* X11 <-> Mitsuba Keyboard mappings */
DEFINE_KEY(XK_BackSpace, EKeyBackspace);
DEFINE_KEY(XK_Tab, EKeyTab);
DEFINE_KEY(XK_Clear, EKeyClear);
DEFINE_KEY(XK_Return, EKeyReturn);
DEFINE_KEY(XK_Linefeed, EKeyReturn);
DEFINE_KEY(XK_Pause, EKeyPause);
DEFINE_KEY(XK_Escape, EKeyEscape);
DEFINE_KEY(XK_Delete, EKeyDelete);
DEFINE_KEY(XK_KP_0, EKeyKeyPad0);
DEFINE_KEY(XK_KP_1, EKeyKeyPad1);
DEFINE_KEY(XK_KP_2, EKeyKeyPad2);
DEFINE_KEY(XK_KP_3, EKeyKeyPad3);
DEFINE_KEY(XK_KP_4, EKeyKeyPad4);
DEFINE_KEY(XK_KP_5, EKeyKeyPad5);
DEFINE_KEY(XK_KP_6, EKeyKeyPad6);
DEFINE_KEY(XK_KP_7, EKeyKeyPad7);
DEFINE_KEY(XK_KP_8, EKeyKeyPad8);
DEFINE_KEY(XK_KP_9, EKeyKeyPad9);
DEFINE_KEY(XK_KP_Insert, EKeyKeyPad0);
DEFINE_KEY(XK_KP_End, EKeyKeyPad1);
DEFINE_KEY(XK_KP_Down, EKeyKeyPad2);
DEFINE_KEY(XK_KP_Page_Down, EKeyKeyPad3);
DEFINE_KEY(XK_KP_Left, EKeyKeyPad4);
DEFINE_KEY(XK_KP_Begin, EKeyKeyPad5);
DEFINE_KEY(XK_KP_Right, EKeyKeyPad6);
DEFINE_KEY(XK_KP_Home, EKeyKeyPad7);
DEFINE_KEY(XK_KP_Up, EKeyKeyPad8);
DEFINE_KEY(XK_KP_Page_Up, EKeyKeyPad9);
DEFINE_KEY(XK_KP_Delete, EKeyKeyPadPeriod);
DEFINE_KEY(XK_KP_Decimal, EKeyKeyPadPeriod);
DEFINE_KEY(XK_KP_Divide, EKeyKeyPadDivide);
DEFINE_KEY(XK_KP_Multiply, EKeyKeyPadMultiply);
DEFINE_KEY(XK_KP_Subtract, EKeyKeyPadMinus);
DEFINE_KEY(XK_KP_Add, EKeyKeyPadPlus);
DEFINE_KEY(XK_KP_Enter, EKeyKeyPadEnter);
DEFINE_KEY(XK_KP_Equal, EKeyKeyPadEquals);
DEFINE_KEY(XK_Up, EKeyUp);
DEFINE_KEY(XK_Down, EKeyDown);
DEFINE_KEY(XK_Right, EKeyRight);
DEFINE_KEY(XK_Left, EKeyLeft);
DEFINE_KEY(XK_Insert, EKeyInsert);
DEFINE_KEY(XK_Home, EKeyHome);
DEFINE_KEY(XK_End, EKeyEnd);
DEFINE_KEY(XK_Page_Up, EKeyPageUp);
DEFINE_KEY(XK_Page_Down, EKeyPageDown);
DEFINE_KEY(XK_F1, EKeyF1);
DEFINE_KEY(XK_F2, EKeyF2);
DEFINE_KEY(XK_F3, EKeyF3);
DEFINE_KEY(XK_F4, EKeyF4);
DEFINE_KEY(XK_F5, EKeyF5);
DEFINE_KEY(XK_F6, EKeyF6);
DEFINE_KEY(XK_F7, EKeyF7);
DEFINE_KEY(XK_F8, EKeyF8);
DEFINE_KEY(XK_F9, EKeyF9);
DEFINE_KEY(XK_F10, EKeyF10);
DEFINE_KEY(XK_F11, EKeyF11);
DEFINE_KEY(XK_F12, EKeyF12);
DEFINE_KEY(XK_F13, EKeyF13);
DEFINE_KEY(XK_F14, EKeyF14);
DEFINE_KEY(XK_F15, EKeyF15);
DEFINE_KEY(XK_Num_Lock, EKeyNumLock);
DEFINE_KEY(XK_Caps_Lock, EKeyCapsLock);
DEFINE_KEY(XK_Scroll_Lock, EKeyScrollLock);
DEFINE_KEY(XK_Shift_L, EKeyLShift);
DEFINE_KEY(XK_Shift_R, EKeyRShift);
DEFINE_KEY(XK_Meta_L, EKeyLMeta);
DEFINE_KEY(XK_Meta_R, EKeyRMeta);
DEFINE_KEY(XK_Alt_L, EKeyLAlt);
DEFINE_KEY(XK_Alt_R, EKeyRAlt);
DEFINE_KEY(XK_Control_L, EKeyLControl);
DEFINE_KEY(XK_Control_R, EKeyRControl);
}
X11Device::~X11Device() {
Log(EDebug, "Destroying X11 device");
if (m_initialized)
shutdown();
}
namespace algo {
struct vsync_sort {
/**
* Approximate the vertical retrace speed of the mode info structure
* (taken from bzflag)
*/
inline int getVSync(XF86VidModeModeInfo *mode) {
return ((int) (0.5f + (1000.0f * mode->dotclock) / (mode->htotal * mode->vtotal)));
}
bool operator() (XF86VidModeModeInfo *mode1, XF86VidModeModeInfo *mode2) {
return getVSync(mode1) > getVSync(mode2);
}
};
}
XVisualInfo *X11Device::createVisual() {
XVisualInfo visTempl;
int visCount;
X11Session *session = static_cast<X11Session *>(getSession());
/* Search for visuals on the current screen matching the requrested color depth */
visTempl.screen = session->m_screen;
visTempl.depth = m_redBits + m_blueBits + m_greenBits + m_alphaBits;
XVisualInfo *visinfo = XGetVisualInfo(session->m_display, VisualScreenMask | VisualDepthMask, &visTempl, &visCount);
if (visinfo == NULL)
Log(EError, "Could not find a matching visual!");
return visinfo;
}
void X11Device::init(Device *other) {
Device::init(other);
Log(EDebug, "Initializing X11 device");
X11Session *session = static_cast<X11Session *>(getSession());
/* Find matching visuals */
m_visinfo = createVisual();
/* Fullscreen mode selection */
std::vector<XF86VidModeModeInfo *> modeList;
if (m_fullscreen) {
if (!session->m_hasVidMode)
Log(EError, "VidMode extension is required for fullscreen display");
/* Retrieve a list of all video modes */
int modeCount;
XF86VidModeModeInfo** modes;
XF86VidModeGetAllModeLines(session->m_display, session->m_screen, &modeCount, &modes);
/* Save the current mode */
m_previousMode = *modes[0];
/* Find the best matching screen resolution */
for (int i=0; i<modeCount; ++i) {
if (modes[i]->hdisplay == m_size.x &&
modes[i]->vdisplay == m_size.y)
modeList.push_back(modes[i]);
}
/* Release the memory held by the resolution list */
XFree(modes);
std::sort(modeList.begin(), modeList.end(), algo::vsync_sort());
if (modeList.size() == 0) {
Log(EWarn, "No matching fullscreen resolution found, using windowed mode!");
m_fullscreen = false;
}
}
/* Create the window's attributes */
XSetWindowAttributes x11attr;
x11attr.background_pixel = x11attr.border_pixel =
BlackPixel(session->m_display, session->m_screen);
/* Create a colormap for the window */
Colormap colormap = XCreateColormap(session->m_display, session->m_root, m_visinfo->visual, AllocNone);
x11attr.colormap = colormap;
if (m_fullscreen) {
/* Switch to the best matching resolution */
XF86VidModeSwitchToMode(session->m_display, session->m_screen, modeList[0]);
XF86VidModeSetViewPort(session->m_display, session->m_screen, 0, 0);
x11attr.override_redirect = True;
m_window = XCreateWindow(session->m_display, session->m_root,
0, 0, /* x,y position*/
m_size.x, m_size.y,
0, m_visinfo->depth, /* border width, color depth */
InputOutput, m_visinfo->visual,
CWBackPixel | CWBorderPixel | CWColormap |
CWOverrideRedirect, &x11attr);
XWarpPointer(session->m_display, None, m_window, 0, 0, 0, 0, 0, 0);
XMapRaised(session->m_display, m_window);
/* Grab the pointer & keyboard */
XGrabKeyboard(session->m_display, m_window, True,
GrabModeAsync, GrabModeAsync, CurrentTime);
XGrabPointer(session->m_display, m_window, True,
ButtonPressMask, GrabModeAsync, GrabModeAsync,
m_window, None, CurrentTime);
m_visible = true;
} else {
/* Center the window if needed */
if (m_center) {
m_position.x = (DisplayWidth(session->m_display, session->m_screen) - m_size.x) / 2;
m_position.y = (DisplayHeight(session->m_display, session->m_screen) - m_size.y) / 2;
}
/* Create the X window */
unsigned long mask = CWBackPixel | CWBorderPixel | CWColormap;
m_window = XCreateWindow(session->m_display, session->m_root,
m_position.x, m_position.y, m_size.x, m_size.y, 0, m_visinfo->depth,
InputOutput, m_visinfo->visual, mask, &x11attr);
if (!m_window)
Log(EError, "Could not create the window");
/* Make the window non-resizable */
XSizeHints *hints = XAllocSizeHints();
hints->width = m_size.x;
hints->height = m_size.y;
if (m_resizeAllowed) {
hints->min_width = hints->min_height = 10;
hints->max_width = hints->max_height = INT_MAX;
} else {
hints->min_width = hints->max_width = m_size.x;
hints->min_height = hints->max_height = m_size.y;
}
hints->x = m_position.x; hints->y = m_position.y;
hints->flags = PMaxSize | PMinSize | USSize | USPosition;
XSetNormalHints(session->m_display, m_window, hints);
XFree(hints);
/* Set input hints */
XWMHints *wmHints = XAllocWMHints();
wmHints->input = True;
wmHints->flags = InputHint;
XSetWMHints(session->m_display, m_window, wmHints);
XFree(wmHints);
/* Make the window closeable */
m_deleteWindow = XInternAtom(session->m_display, "WM_DELETE_WINDOW", False);
XSetWMProtocols(session->m_display, m_window, &m_deleteWindow, 1);
}
/* Mark events types in which we are interested */
XSelectInput(session->m_display, m_window, FocusChangeMask | KeyPressMask | KeyReleaseMask
| ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask);
/* Initialize member variables */
m_cursor = None;
m_mouse = Point2i(-1, -1);
m_modifierState = 0;
m_buttonState = 0;
m_grab = false;
m_initialized = true;
setTitle(m_title);
}
void X11Device::setTitle(const std::string &title) {
X11Session *session = static_cast<X11Session *>(getSession());
Device::setTitle(title);
if (m_initialized) {
std::string finalTitle;
if (m_showFPS && m_fps != 0) {
finalTitle = formatString("%s - %i FPS", title.c_str(), m_fps);
} else {
finalTitle = title;
}
XStoreName(session->m_display, m_window, finalTitle.c_str());
XFlush(session->m_display);
}
}
void X11Device::setPosition(const Point2i &position) {
Assert(m_initialized);
X11Session *session = static_cast<X11Session *>(getSession());
Device::setPosition(position);
if (m_initialized && !m_fullscreen) {
XMoveWindow(session->m_display, m_window, m_position.x, m_position.y);
XFlush(session->m_display);
}
}
void X11Device::setVisible(bool visible) {
Assert(m_initialized);
if (visible && !m_visible) {
X11Session *session = static_cast<X11Session *>(getSession());
XMapRaised(session->m_display, m_window);
XSync(session->m_display, 0);
m_visible = true;
} else if (!visible && m_visible) {
X11Session *session = static_cast<X11Session *>(getSession());
XUnmapWindow(session->m_display, m_window);
XSync(session->m_display, 0);
m_visible = false;
}
}
void X11Device::warpMouse(const Point2i &position) {
Assert(m_initialized);
X11Session *session = static_cast<X11Session *>(getSession());
XEvent event;
XWarpPointer(session->m_display, None, m_window, 0, 0, 0, 0, position.x, position.y);
XSync(session->m_display, False);
/* Remove the caused event from the queue */
XCheckTypedWindowEvent(session->m_display, m_window, MotionNotify, &event);
m_mouse = Point2i(position.x, position.y);
}
void X11Device::showCursor(bool enabled) {
X11Session *session = static_cast<X11Session *>(getSession());
if (enabled) {
if (m_cursor != None) {
XFreeCursor(session->m_display, m_cursor);
m_cursor = None;
}
XUndefineCursor(session->m_display, m_window);
XSync(session->m_display, False);
} else {
if (m_cursor == None) {
/* Create a transparent cursor */
char bm[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
Pixmap pix = XCreateBitmapFromData(session->m_display, m_window, bm, 8, 8);
XColor black;
memset(&black, 0, sizeof(XColor));
black.flags = DoRed | DoGreen | DoBlue;
m_cursor = XCreatePixmapCursor(session->m_display, pix, pix, &black, &black, 0, 0);
XFreePixmap(session->m_display, pix);
}
XDefineCursor(session->m_display, m_window, m_cursor);
XSync(session->m_display, False);
}
}
void X11Device::shutdown() {
X11Session *session = static_cast<X11Session *>(getSession());
Log(EDebug, "Shutting down X11 device");
Device::shutdown();
setVisible(false);
XDestroyWindow(session->m_display, m_window);
XFree(m_visinfo);
if (m_fullscreen) {
/* Switch back to the previous screen resolution */
XF86VidModeSwitchToMode(session->m_display, session->m_screen, &m_previousMode);
XF86VidModeSetViewPort(session->m_display, session->m_screen, 0, 0);
}
/* In case auto_repeat was left on */
XKeyboardState xkbs;
XAutoRepeatOn(session->m_display);
XGetKeyboardControl(session->m_display, &xkbs);
if (!xkbs.global_auto_repeat)
Log(EWarn, "Unable to restore the keyboard auto-repeat flag");
m_initialized = false;
}
void X11Device::flip() {
Assert(m_initialized);
}
void X11Device::processEvent(const XEvent &event) {
DeviceEvent deviceEvent(ENoEvent);
X11Session *session = static_cast<X11Session *>(getSession());
if (m_callbacks.size() == 0)
return;
switch (event.type) {
case ClientMessage:
/* The window close button pressed */
if ((event.xclient.format == 32) && ((unsigned) event.xclient.data.l[0] == m_deleteWindow)) {
deviceEvent.setType(EQuitEvent);
}
break;
case FocusIn:
/* Deactivate auto-repeat */
XAutoRepeatOff(session->m_display);
deviceEvent.setType(EGainFocusEvent);
m_modifierState = 0;
m_buttonState = 0;
break;
case FocusOut:
/* Reactivate auto-repeat */
XAutoRepeatOn(session->m_display);
deviceEvent.setType(ELoseFocusEvent);
m_modifierState = 0;
m_buttonState = 0;
break;
case ButtonPress:
deviceEvent.setType(EMouseButtonDownEvent);
translateMouse(event, deviceEvent);
m_buttonState |= deviceEvent.getMouseButton();
break;
case ButtonRelease:
deviceEvent.setType(EMouseButtonUpEvent);
translateMouse(event, deviceEvent);
m_buttonState &= ~deviceEvent.getMouseButton();
break;
case MotionNotify: {
deviceEvent.setType(m_buttonState == 0 ? EMouseMotionEvent : EMouseDragEvent);
translateMouse(event, deviceEvent);
deviceEvent.setMouseButton(m_buttonState);
if (m_grab)
warpMouse(Point2i(getSize().x / 2, getSize().y/2));
int xpos = deviceEvent.getMousePosition().x;
int ypos = deviceEvent.getMousePosition().y;
if (xpos > m_size.x || xpos < 0 || ypos > m_size.y || ypos < 0)
return;
}
break;
case KeyPress:
if (translateKeyboard(event, deviceEvent)) {
deviceEvent.setType(EKeyDownEvent);
int special = deviceEvent.getKeyboardSpecial();
/* Update the current modifier state */
if (special == EKeyLShift || special == EKeyRShift) {
m_modifierState |= EShiftModifier;
} else if (special == EKeyLAlt || special == EKeyRAlt) {
m_modifierState |= EAltModifier;
} else if (special == EKeyLControl || special == EKeyRControl) {
m_modifierState |= EControlModifier;
}
deviceEvent.setKeyboardModifiers(m_modifierState);
}
break;
case KeyRelease:
if (translateKeyboard(event, deviceEvent)) {
deviceEvent.setType(EKeyUpEvent);
int special = deviceEvent.getKeyboardSpecial();
/* Update the current modifier state */
if (special == EKeyLShift || special == EKeyRShift) {
m_modifierState = m_modifierState & (~EShiftModifier);
} else if (special == EKeyLAlt || special == EKeyRAlt) {
m_modifierState = m_modifierState & (~EAltModifier);
} else if (special == EKeyLControl || special == EKeyRControl) {
m_modifierState = m_modifierState & (~EControlModifier);
}
deviceEvent.setKeyboardModifiers(m_modifierState);
}
break;
case MapNotify:
case UnmapNotify:
m_modifierState = 0;
break;
case ConfigureNotify: {
Vector2i size(event.xconfigure.width, event.xconfigure.height);
if (m_size != size) {
m_size = size;
deviceEvent.setType(EResizeEvent);
}
}
break;
case ReparentNotify:
case Expose:
break;
default:
Log(EWarn, "Unknown event %i received", event.type);
}
if (deviceEvent.getType() != ENoEvent)
fireDeviceEvent(deviceEvent);
}
void X11Device::translateMouse(const XEvent &xEvent, DeviceEvent &event) {
event.setMousePosition(Point2i(xEvent.xbutton.x, xEvent.xbutton.y));
/* Calculate relative coordinates */
if (m_mouse.x != -1 && m_mouse.y != -1) {
event.setMouseRelative(Vector2i(event.getMousePosition() - m_mouse));
} else {
event.setMouseRelative(Vector2i(0,0));
}
m_mouse = event.getMousePosition();
if (xEvent.xbutton.button == Button1)
event.setMouseButton(ELeftButton);
else if (xEvent.xbutton.button == Button2)
event.setMouseButton(EMiddleButton);
else if (xEvent.xbutton.button == Button3)
event.setMouseButton(ERightButton);
else if (xEvent.xbutton.button == Button4)
event.setMouseButton(EWheelUpButton);
else if (xEvent.xbutton.button == Button5)
event.setMouseButton(EWheelDownButton);
else
event.setMouseButton(ENoButton);
}
bool X11Device::translateKeyboard(const XEvent &xEvent, DeviceEvent &event) {
X11Session *session = static_cast<X11Session *>(getSession());
KeySym sym = XKeycodeToKeysym(session->m_display, xEvent.xkey.keycode, 0);
/* Default: 0 */
event.setKeyboardKey(0);
event.setKeyboardSpecial(0);
/* Retrieve an 'interpreted' version of the key event */
int noc = XLookupString(const_cast<XKeyEvent *>(&xEvent.xkey), event.getKeyboardInterpreted(),
15, NULL, NULL);
event.getKeyboardInterpreted()[noc] = 0;
if (sym != 0) {
int type = sym >> 8;
if (type >= 0x00 && type <= 0x0D) {
/* Alphanumeric key */
char key = sym & 0xFF;
if (key >= 'A' && key <= 'Z') {
/* Convert to lower-case */
key += ('a' - 'A');
}
event.setKeyboardKey(key);
} else if (type == 0xFE) {
/* Ignore */
return false;
} else if (type == 0xFF) {
/* Lookup special key */
event.setKeyboardSpecial(m_keymap[sym & 0xFF]);
if (event.getKeyboardSpecial() == ENoSpecial)
return false;
} else {
Log(EWarn, "Unknown X11 keysym: 0x%x", (int) sym);
return false;
}
return true;
}
return false;
}
void X11Device::setGrab(bool grab) {
Assert(m_initialized);
m_grab = grab;
showCursor(!grab);
}
void X11Device::makeCurrent(Renderer *renderer) {
Assert(m_initialized);
X11Session *session = static_cast<X11Session *>(getSession());
GLXRenderer *glxRenderer = static_cast<GLXRenderer *>(renderer);
int retval;
if (glxRenderer == NULL)
retval = glXMakeCurrent(session->m_display, None, NULL);
else
retval = glXMakeCurrent(session->m_display, m_window, glxRenderer->getGLXContext());
if (retval != True)
Log(EError, "Error in glXMakeCurrent - unable to activate the rendering context");
}
MTS_IMPLEMENT_CLASS(X11Device, false, Device)
MTS_NAMESPACE_END