/* 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 . */ #include #include #include #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(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(getSession()); /* Find matching visuals */ m_visinfo = createVisual(); /* Fullscreen mode selection */ std::vector 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; ihdisplay == 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(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(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(getSession()); XMapRaised(session->m_display, m_window); XSync(session->m_display, 0); m_visible = true; } else if (!visible && m_visible) { X11Session *session = static_cast(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(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(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(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(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(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(&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(getSession()); GLXRenderer *glxRenderer = static_cast(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