From 48cc1aa496de23a02fa3a236a0078f4df53d46f1 Mon Sep 17 00:00:00 2001 From: Ed Swartz Date: Fri, 17 Apr 2015 13:20:14 -0500 Subject: [PATCH 1/4] Implement an M_confined mouse mode to ensure mouse stays in window. --- panda/src/display/graphicsWindow.cxx | 22 +--- panda/src/display/windowProperties.I | 35 ++++-- panda/src/display/windowProperties.cxx | 4 + panda/src/display/windowProperties.h | 1 + panda/src/windisplay/winGraphicsWindow.cxx | 46 +++++++ panda/src/windisplay/winGraphicsWindow.h | 3 + panda/src/x11display/x11GraphicsWindow.cxx | 132 ++++++++++++++------- 7 files changed, 169 insertions(+), 74 deletions(-) diff --git a/panda/src/display/graphicsWindow.cxx b/panda/src/display/graphicsWindow.cxx index caee3ea4ed..6166f9cbac 100644 --- a/panda/src/display/graphicsWindow.cxx +++ b/panda/src/display/graphicsWindow.cxx @@ -736,24 +736,10 @@ set_properties_now(WindowProperties &properties) { // Fullscreen property specified, but unchanged. properties.clear_fullscreen(); } - if (properties.has_mouse_mode() ) { - - if (properties.get_mouse_mode() == _properties.get_mouse_mode()) { - properties.clear_mouse_mode(); - } - else { - if(properties.get_mouse_mode() == WindowProperties::M_absolute) { - _properties.set_mouse_mode(WindowProperties::M_absolute); - mouse_mode_absolute(); - properties.clear_mouse_mode(); - } - else - { - _properties.set_mouse_mode(WindowProperties::M_relative); - mouse_mode_relative(); - properties.clear_mouse_mode(); - } - } + if (properties.has_mouse_mode() && + properties.get_mouse_mode() == _properties.get_mouse_mode()) { + // Mouse mode specified, but unchanged. + properties.clear_mouse_mode(); } } diff --git a/panda/src/display/windowProperties.I b/panda/src/display/windowProperties.I index ca307062a4..3597adbc55 100644 --- a/panda/src/display/windowProperties.I +++ b/panda/src/display/windowProperties.I @@ -798,19 +798,30 @@ clear_z_order() { // Function: WindowProperties::set_mouse_mode // Access: Published // Description: Specifies the mode in which the window is to operate -// its mouse pointer. The default is M_absolute, which -// is the normal mode in which a mouse pointer operates; -// but you can also set M_relative, which is -// particularly useful for FPS-style mouse movements -// where you have hidden the mouse pointer and are are -// more interested in how fast the mouse is moving, -// rather than precisely where the pointer is hovering. +// its mouse pointer. +// +// M_absolute: the normal mode in which a mouse pointer +// operates, where the mouse can move outside the window +// and the mouse coordinates are relative to its +// position in the window. +// +// M_relative (OSX or Unix/X11 only): a mode where only +// relative movements are reported; particularly useful +// for FPS-style mouse movements where you have hidden +// the mouse pointer and are are more interested in how +// fast the mouse is moving, rather than precisely where +// the pointer is hovering. +// +// This has no effect on Windows. On Unix/X11, this +// requires the Xxf86dga extension to be available. +// +// M_confined: this mode reports absolute mouse +// positions, but confines the mouse pointer to +// the window boundary. It can portably replace +// M_relative for an FPS, but you need to periodically +// move the pointer to the center of the window +// and track movement deltas. // -// This has no effect on Windows, which does not -// have this concept; but is important to do on OSX -// and Unix/X11 to properly enable a smooth FPS-style -// mouselook mode. On Unix/X11, this requires the -// Xxf86dga extension to be available. //////////////////////////////////////////////////////////////////// INLINE void WindowProperties:: set_mouse_mode(MouseMode mode) { diff --git a/panda/src/display/windowProperties.cxx b/panda/src/display/windowProperties.cxx index d38241f2ce..8e4178526f 100644 --- a/panda/src/display/windowProperties.cxx +++ b/panda/src/display/windowProperties.cxx @@ -400,6 +400,8 @@ operator << (ostream &out, WindowProperties::MouseMode mode) { return out << "absolute"; case WindowProperties::M_relative: return out << "relative"; + case WindowProperties::M_confined: + return out << "confined"; } return out << "**invalid WindowProperties::MouseMode(" << (int)mode << ")**"; } @@ -413,6 +415,8 @@ operator >> (istream &in, WindowProperties::MouseMode &mode) { mode = WindowProperties::M_absolute; } else if (word == "relative") { mode = WindowProperties::M_relative; + } else if (word == "confined") { + mode = WindowProperties::M_confined; } else { display_cat.warning() << "Unknown mouse mode: " << word << "\n"; diff --git a/panda/src/display/windowProperties.h b/panda/src/display/windowProperties.h index 5f8a6bb32d..dc0fcac84f 100644 --- a/panda/src/display/windowProperties.h +++ b/panda/src/display/windowProperties.h @@ -40,6 +40,7 @@ PUBLISHED: enum MouseMode { M_absolute, M_relative, + M_confined, }; WindowProperties(); diff --git a/panda/src/windisplay/winGraphicsWindow.cxx b/panda/src/windisplay/winGraphicsWindow.cxx index 99d4c9629e..d4fcf6a9e2 100644 --- a/panda/src/windisplay/winGraphicsWindow.cxx +++ b/panda/src/windisplay/winGraphicsWindow.cxx @@ -35,6 +35,8 @@ WinGraphicsWindow *WinGraphicsWindow::_creating_window = NULL; WinGraphicsWindow *WinGraphicsWindow::_cursor_window = NULL; bool WinGraphicsWindow::_cursor_hidden = false; +RECT WinGraphicsWindow::_mouse_unconfined_cliprect; + // These are used to save the previous state of the fancy Win2000 // effects that interfere with rendering when the mouse wanders into a // window's client area. @@ -359,6 +361,50 @@ set_properties_now(WindowProperties &properties) { } } } + + if (properties.has_mouse_mode()) { + if (properties.get_mouse_mode() != _properties.get_mouse_mode()) { + switch (properties.get_mouse_mode()) { + case WindowProperties::M_absolute: + case WindowProperties::M_relative: // not implemented, treat as absolute + + if (_properties.get_mouse_mode() == WindowProperties::M_confined) { + ClipCursor(NULL); + windisplay_cat.info() << "Unconfining cursor from window\n"; + } + _properties.set_mouse_mode(WindowProperties::M_absolute); + break; + + case WindowProperties::M_confined: + { + RECT clip; + + if (!GetWindowRect(_hWnd, &clip)) { + if (windisplay_cat.is_debug()) { + windisplay_cat.debug() + << "GetWindowRect() failed in set_properties_now. Cannot confine cursor.\n"; + } + } else { + windisplay_cat.debug() + << "ClipCursor() to " << clip.left << "," << clip.top << " to " + << clip.right << "," << clip.bottom << endl; + + GetClipCursor(&_mouse_unconfined_cliprect); + if (!ClipCursor(&clip)) { + windisplay_cat.debug() + << "ClipCursor() failed in set_properties_now. Ignoring.\n"; + } else { + _properties.set_mouse_mode(WindowProperties::M_confined); + windisplay_cat.info() << "Confining cursor to window\n"; + } + } + } + break; + } + } + properties.clear_mouse_mode(); + } + } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/windisplay/winGraphicsWindow.h b/panda/src/windisplay/winGraphicsWindow.h index cbd7c46bb6..1b3ee93f95 100644 --- a/panda/src/windisplay/winGraphicsWindow.h +++ b/panda/src/windisplay/winGraphicsWindow.h @@ -214,6 +214,9 @@ private: static BOOL _saved_cursor_shadow; static BOOL _saved_mouse_vanish; + // The mouse constraints before applying mouse mode M_confined. + static RECT _mouse_unconfined_cliprect; + // Since the Panda API requests icons and cursors by filename, we // need a table mapping filenames to handles, so we can avoid // re-reading the file each time we change icons. diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index 7f090ecd5c..f1484522a1 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/panda/src/x11display/x11GraphicsWindow.cxx @@ -711,6 +711,92 @@ set_properties_now(WindowProperties &properties) { properties.clear_foreground(); } + if (properties.has_mouse_mode()) { + switch (properties.get_mouse_mode()) { + case WindowProperties::M_absolute: + XUngrabPointer(_display, CurrentTime); +#ifdef HAVE_XF86DGA + if (_dga_mouse_enabled) { + x11display_cat.info() << "Disabling relative mouse using XF86DGA extension\n"; + XF86DGADirectVideo(_display, _screen, 0); + _dga_mouse_enabled = false; + } +#endif + _properties.set_mouse_mode(WindowProperties::M_absolute); + properties.clear_mouse_mode(); + break; + + case WindowProperties::M_relative: +#ifdef HAVE_XF86DGA + if (!_dga_mouse_enabled) { + int major_ver, minor_ver; + if (XF86DGAQueryVersion(_display, &major_ver, &minor_ver)) { + + X11_Cursor cursor = None; + if (_properties.get_cursor_hidden()) { + x11GraphicsPipe *x11_pipe; + DCAST_INTO_V(x11_pipe, _pipe); + cursor = x11_pipe->get_hidden_cursor(); + } + + if (XGrabPointer(_display, _xwindow, True, 0, GrabModeAsync, + GrabModeAsync, _xwindow, cursor, CurrentTime) != GrabSuccess) { + x11display_cat.error() << "Failed to grab pointer!\n"; + } else { + x11display_cat.info() << "Enabling relative mouse using XF86DGA extension\n"; + XF86DGADirectVideo(_display, _screen, XF86DGADirectMouse); + + _properties.set_mouse_mode(WindowProperties::M_relative); + _dga_mouse_enabled = true; + + // Get the real mouse position, so we can add/subtract + // our relative coordinates later. + XEvent event; + XQueryPointer(_display, _xwindow, &event.xbutton.root, + &event.xbutton.window, &event.xbutton.x_root, &event.xbutton.y_root, + &event.xbutton.x, &event.xbutton.y, &event.xbutton.state); + _input_devices[0].set_pointer_in_window(event.xbutton.x, event.xbutton.y); + } + } else { + x11display_cat.info() << "XF86DGA extension not available\n"; + _dga_mouse_enabled = false; + } + } + else +#endif + { + // can't change + properties.clear_mouse_mode(); + } + break; + + case WindowProperties::M_confined: + { +#ifdef HAVE_XF86DGA + if (_dga_mouse_enabled) { + XF86DGADirectVideo(_display, _screen, 0); + _dga_mouse_enabled = false; + } +#endif + X11_Cursor cursor = None; + if (_properties.get_cursor_hidden()) { + x11GraphicsPipe *x11_pipe; + DCAST_INTO_V(x11_pipe, _pipe); + cursor = x11_pipe->get_hidden_cursor(); + } + + if (XGrabPointer(_display, _xwindow, True, 0, GrabModeAsync, + GrabModeAsync, _xwindow, cursor, CurrentTime) != GrabSuccess) { + x11display_cat.error() << "Failed to grab pointer!\n"; + } else { + _properties.set_mouse_mode(WindowProperties::M_confined); + properties.clear_mouse_mode(); + } + } + break; + } + } + set_wm_properties(wm_properties, true); } @@ -721,14 +807,7 @@ set_properties_now(WindowProperties &properties) { //////////////////////////////////////////////////////////////////// void x11GraphicsWindow:: mouse_mode_absolute() { -#ifdef HAVE_XF86DGA - if (!_dga_mouse_enabled) return; - - XUngrabPointer(_display, CurrentTime); - x11display_cat.info() << "Disabling relative mouse using XF86DGA extension\n"; - XF86DGADirectVideo(_display, _screen, 0); - _dga_mouse_enabled = false; -#endif + // unused: remove in 1.10! } //////////////////////////////////////////////////////////////////// @@ -738,42 +817,7 @@ mouse_mode_absolute() { //////////////////////////////////////////////////////////////////// void x11GraphicsWindow:: mouse_mode_relative() { -#ifdef HAVE_XF86DGA - if (_dga_mouse_enabled) return; - - int major_ver, minor_ver; - if (XF86DGAQueryVersion(_display, &major_ver, &minor_ver)) { - - X11_Cursor cursor = None; - if (_properties.get_cursor_hidden()) { - x11GraphicsPipe *x11_pipe; - DCAST_INTO_V(x11_pipe, _pipe); - cursor = x11_pipe->get_hidden_cursor(); - } - - if (XGrabPointer(_display, _xwindow, True, 0, GrabModeAsync, - GrabModeAsync, _xwindow, cursor, CurrentTime) != GrabSuccess) { - x11display_cat.error() << "Failed to grab pointer!\n"; - return; - } - - x11display_cat.info() << "Enabling relative mouse using XF86DGA extension\n"; - XF86DGADirectVideo(_display, _screen, XF86DGADirectMouse); - - _dga_mouse_enabled = true; - } else { - x11display_cat.info() << "XF86DGA extension not available\n"; - _dga_mouse_enabled = false; - } - - // Get the real mouse position, so we can add/subtract - // our relative coordinates later. - XEvent event; - XQueryPointer(_display, _xwindow, &event.xbutton.root, - &event.xbutton.window, &event.xbutton.x_root, &event.xbutton.y_root, - &event.xbutton.x, &event.xbutton.y, &event.xbutton.state); - _input_devices[0].set_pointer_in_window(event.xbutton.x, event.xbutton.y); -#endif + // unused: remove in 1.10! } //////////////////////////////////////////////////////////////////// From f6841d7fc5f043a9f7affeb999c81eebe54c7d7b Mon Sep 17 00:00:00 2001 From: Ed Swartz Date: Fri, 17 Apr 2015 14:58:53 -0500 Subject: [PATCH 2/4] Add sample program demonstrating the mouse modes --- samples/mouse-modes/main.py | 172 ++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 samples/mouse-modes/main.py diff --git a/samples/mouse-modes/main.py b/samples/mouse-modes/main.py new file mode 100644 index 0000000000..50e81f550e --- /dev/null +++ b/samples/mouse-modes/main.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python +''' +Demonstrate different mouse modes +''' + +# from panda3d.core import loadPrcFileData +# +# loadPrcFileData("", "notify-level-x11display debug") +# loadPrcFileData("", "notify-level-windisplay debug") +# +# loadPrcFileData("", "load-display p3tinydisplay") + +from panda3d.core import WindowProperties, TextNode +from direct.task.TaskManagerGlobal import taskMgr +from direct.gui.OnscreenText import OnscreenText +from direct.task import Task +from direct.showbase.ShowBase import ShowBase + +import sys + +class App(ShowBase): + def __init__(self): + ShowBase.__init__(self) + self.base = self + self.setup() + + def genLabelText(self, text, i): + text = OnscreenText(text = text, pos = (-1.3, .5-.05*i), fg=(0,1,0,1), + align = TextNode.ALeft, scale = .05) + return text + + + def setup(self): + # Disable the camera trackball controls. + self.disableMouse() + + self.mouseMagnitude = 144 + + self.rotateX, self.rotateY = 0, 0 + + self.genLabelText("[0] Absolute mode, [1] Relative mode, [2] Confined mode", 0) + + self.base.accept('0', lambda: self.setMouseMode(WindowProperties.M_absolute)) + self.base.accept('1', lambda: self.setMouseMode(WindowProperties.M_relative)) + self.base.accept('2', lambda: self.setMouseMode(WindowProperties.M_confined)) + + self.genLabelText("[C] Manually re-center mouse on each tick", 1) + self.base.accept('C', lambda: self.toggleRecenter()) + self.base.accept('c', lambda: self.toggleRecenter()) + + self.genLabelText("[S] Show mouse", 2) + self.base.accept('S', lambda: self.toggleMouse()) + self.base.accept('s', lambda: self.toggleMouse()) + + self.base.accept('escape', sys.exit, [0]) + + self.mouseText = self.genLabelText("", 5) + self.deltaText = self.genLabelText("", 6) + self.positionText = self.genLabelText("", 8) + + self.lastMouseX, self.lastMouseY = None, None + + self.hideMouse = False + + self.setMouseMode(WindowProperties.M_absolute) + self.manualRecenterMouse = True + + # make a box to move with the mouse + self.model = self.loader.loadModel("box.egg") + self.model.reparentTo(self.render) + + self.cam.setPos(0, -5, 0) + self.cam.lookAt(0, 0, 0) + + self.mouseTask = taskMgr.add(self.mouseTask, "Mouse Task") + + def setMouseMode(self, mode): + print "Changing mode to",mode + + self.mouseMode = mode + + wp = WindowProperties() + wp.setMouseMode(mode) + self.base.win.requestProperties(wp) + + # these changes may require a tick to apply + self.base.taskMgr.doMethodLater(0, self.resolveMouse, "Resolve mouse setting") + + def resolveMouse(self, t): + wp = self.base.win.getProperties() + + actualMode = wp.getMouseMode() + if self.mouseMode != actualMode: + print "ACTUAL MOUSE MODE:", actualMode + + self.mouseMode = actualMode + + self.rotateX, self.rotateY = -.5, -.5 + self.lastMouseX, self.lastMouseY = None, None + self.recenterMouse() + + def recenterMouse(self): + self.base.win.movePointer(0, + int(self.base.win.getProperties().getXSize() / 2), + int(self.base.win.getProperties().getYSize() / 2)) + + + def toggleRecenter(self): + print "Toggling re-center behavior" + self.manualRecenterMouse = not self.manualRecenterMouse + + def toggleMouse(self): + print "Toggling mouse visibility" + + self.hideMouse = not self.hideMouse + + wp = WindowProperties() + wp.setCursorHidden(self.hideMouse) + self.base.win.requestProperties(wp) + + def mouseTask(self, task): + mw = self.base.mouseWatcherNode + + hasMouse = mw.hasMouse() + if hasMouse: + # get the window manager's idea of the mouse position + x, y = mw.getMouseX(), mw.getMouseY() + + if self.lastMouseX is not None: + # get the delta + if self.manualRecenterMouse: + # when recentering, the position IS the delta + # since the center is reported as 0, 0 + dx, dy = x, y + else: + dx, dy = x - self.lastMouseX, y - self.lastMouseY + else: + # no data to compare with yet + dx, dy = 0, 0 + + self.lastMouseX, self.lastMouseY = x, y + + else: + x, y, dx, dy = 0, 0, 0, 0 + + if self.manualRecenterMouse: + # move mouse back to center + self.recenterMouse() + + # scale position and delta to pixels for user + w, h = self.win.getSize() + + self.mouseText.setText("Mode: {0}, Recenter: {1} | Mouse: {2}, {3} | hasMouse: {4}".format( + self.mouseMode, self.manualRecenterMouse, + int(x*w), int(y*h), + hasMouse)) + self.deltaText.setText("Delta: {0}, {1}".format( + int(dx*w), int(dy*h))) + + # rotate box by delta + self.rotateX += dx * 10 + self.rotateY += dy * 10 + + self.positionText.setText("Model rotation: {0}, {1}".format( + int(self.rotateX*1000)/1000., int(self.rotateY*1000)/1000.)) + + self.model.setH(self.rotateX) + self.model.setP(self.rotateY) + return Task.cont + +app = App() +app.run() From 0f5b1a819205f78d990374cfe360a04a1bb8e1ed Mon Sep 17 00:00:00 2001 From: Ed Swartz Date: Fri, 17 Apr 2015 15:50:11 -0500 Subject: [PATCH 3/4] Fix X11 M_relative case when missing XF86DGA --- panda/src/x11display/x11GraphicsWindow.cxx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index f1484522a1..c503bda1cc 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/panda/src/x11display/x11GraphicsWindow.cxx @@ -747,6 +747,7 @@ set_properties_now(WindowProperties &properties) { XF86DGADirectVideo(_display, _screen, XF86DGADirectMouse); _properties.set_mouse_mode(WindowProperties::M_relative); + properties.clear_mouse_mode(); _dga_mouse_enabled = true; // Get the real mouse position, so we can add/subtract @@ -762,12 +763,7 @@ set_properties_now(WindowProperties &properties) { _dga_mouse_enabled = false; } } - else #endif - { - // can't change - properties.clear_mouse_mode(); - } break; case WindowProperties::M_confined: From 53171c93c853637af9a6d23a9ffa30e7701e62b5 Mon Sep 17 00:00:00 2001 From: Ed Swartz Date: Fri, 17 Apr 2015 15:55:46 -0500 Subject: [PATCH 4/4] Replace some debug() calls with info()/warning() calls. --- panda/src/windisplay/winGraphicsWindow.cxx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/panda/src/windisplay/winGraphicsWindow.cxx b/panda/src/windisplay/winGraphicsWindow.cxx index d4fcf6a9e2..a226dcde1b 100644 --- a/panda/src/windisplay/winGraphicsWindow.cxx +++ b/panda/src/windisplay/winGraphicsWindow.cxx @@ -380,18 +380,16 @@ set_properties_now(WindowProperties &properties) { RECT clip; if (!GetWindowRect(_hWnd, &clip)) { - if (windisplay_cat.is_debug()) { - windisplay_cat.debug() + windisplay_cat.warning() << "GetWindowRect() failed in set_properties_now. Cannot confine cursor.\n"; - } } else { - windisplay_cat.debug() + windisplay_cat.info() << "ClipCursor() to " << clip.left << "," << clip.top << " to " << clip.right << "," << clip.bottom << endl; GetClipCursor(&_mouse_unconfined_cliprect); if (!ClipCursor(&clip)) { - windisplay_cat.debug() + windisplay_cat.warning() << "ClipCursor() failed in set_properties_now. Ignoring.\n"; } else { _properties.set_mouse_mode(WindowProperties::M_confined);