Browse Source

WIP: Linux port
- Fixed dragging without a title bar
- Properly handle closing of a non-main window
- Fixed an issue where window background image didn't show
- Fixed an issue where showOnTaskBar property didn't work properly
- Added VSync toggle

Marko Pintera 8 years ago
parent
commit
987f146707

+ 0 - 2
Source/BansheeCore/CoreThread/BsCoreThread.cpp

@@ -269,8 +269,6 @@ namespace bs
 
 		while(true)
 		{
-			// TODO - This might be causing a deadlock in Release mode. I'm thinking because mCommandsCompleted isn't marked as volatile.
-
 			// Check if our command id is in the completed list
 			auto iter = mCommandsCompleted.begin();
 			for(; iter != mCommandsCompleted.end(); ++iter)

+ 15 - 10
Source/BansheeCore/Linux/BsLinuxPlatform.cpp

@@ -411,8 +411,7 @@ namespace bs
 		LinuxWindow* linuxWindow;
 		window.getCustomAttribute("LINUX_WINDOW", &linuxWindow);
 
-		// Note: Only supporting a single area
-		linuxWindow->_setDragZone(nonClientAreas[0]);
+		linuxWindow->_setDragZones(nonClientAreas);
 	}
 
 	void Platform::setResizeNonClientAreas(const ct::RenderWindow& window, const Vector<NonClientResizeArea>& nonClientAreas)
@@ -422,7 +421,12 @@ namespace bs
 
 	void Platform::resetNonClientAreas(const ct::RenderWindow& window)
 	{
-		// Do nothing, resize areas not supported on Linux (but they are provided even on undecorated windows by the WM)
+		Lock lock(mData->lock);
+
+		LinuxWindow* linuxWindow;
+		window.getCustomAttribute("LINUX_WINDOW", &linuxWindow);
+
+		linuxWindow->_setDragZones({});
 	}
 
 	void Platform::sleep(UINT32 duration)
@@ -769,11 +773,13 @@ namespace bs
 				LinuxWindow* window = getLinuxWindow(mData, event.xdestroywindow.window);
 				if(window != nullptr)
 				{
-					CoreApplication::instance().quitRequested();
 					window->_cleanUp();
 
 					if (mData->mainXWindow == 0)
+					{
+						CoreApplication::instance().quitRequested();
 						return;
+					}
 				}
 			}
 				break;
@@ -879,7 +885,7 @@ namespace bs
 				{
 					LinuxWindow* window = getLinuxWindow(mData, event.xbutton.window);
 					if(window != nullptr)
-						window->_dragStart(event.xbutton.x, event.xbutton.y);
+						window->_dragStart(event.xbutton);
 				}
 
 				break;
@@ -950,11 +956,6 @@ namespace bs
 				btnStates.mouseButtons[2] = (event.xmotion.state & Button3Mask) != 0;
 
 				onCursorMoved(pos, btnStates);
-
-				// Handle window dragging for windows without a title bar
-				LinuxWindow* window = getLinuxWindow(mData, event.xmotion.window);
-				if(window != nullptr)
-					window->_dragUpdate(event.xmotion.x, event.xmotion.y);
 			}
 				break;
 			case EnterNotify:
@@ -1076,6 +1077,10 @@ namespace bs
 				// Report minimize, maximize and restore events
 				if(event.xproperty.atom == mData->atomWmState)
 				{
+					// Check that the window hasn't been destroyed
+					if(getLinuxWindow(mData, event.xproperty.window) == nullptr)
+						break;
+
 					Atom type;
 					INT32 format;
 					unsigned long count, bytesRemaining;

+ 93 - 36
Source/BansheeCore/Linux/BsLinuxWindow.cpp

@@ -6,11 +6,16 @@
 
 #include <X11/Xatom.h>
 #include <X11/extensions/Xrandr.h>
+#include <X11/Xutil.h>
+#include <X11/Xlib.h>
 
 #define _NET_WM_STATE_REMOVE 0
 #define _NET_WM_STATE_ADD 1
 #define _NET_WM_STATE_TOGGLE 2
 
+#define _NET_WM_MOVERESIZE_MOVE 8
+#define _NET_WM_MOVERESIZE_CANCEL 11
+
 #define WM_NormalState 1
 #define WM_IconicState 3
 
@@ -34,8 +39,7 @@ namespace bs
 		bool resizeDisabled = false;
 		WindowState state = WindowState::Normal;
 
-		Rect2I dragZone;
-		INT32 dragStartX, dragStartY;
+		Vector<Rect2I> dragZones;
 
 		void* userData = nullptr;
 	};
@@ -169,6 +173,14 @@ namespace bs
 		setShowDecorations(desc.showDecorations);
 		setIsModal(desc.modal);
 
+		XClassHint* classHint = XAllocClassHint();
+
+		classHint->res_class = (char*)"banshee3d";
+		classHint->res_name = (char*)desc.title.c_str();
+
+		XSetClassHint(display, m->xWindow, classHint);
+		XFree(classHint);
+
 		// Ensures the child window is always on top of the parent window
 		if(desc.parent)
 			XSetTransientForHint(display, m->xWindow, desc.parent);
@@ -181,7 +193,6 @@ namespace bs
 				PointerMotionMask | ButtonMotionMask |
 				StructureNotifyMask | PropertyChangeMask
 		);
-		XMapWindow(display, m->xWindow);
 
 		// Make sure we get the window delete message from WM, so we can clean up ourselves
 		Atom atomDeleteWindow = XInternAtom(display, "WM_DELETE_WINDOW", False);
@@ -200,6 +211,10 @@ namespace bs
 			XSync(display, 0);
 		}
 
+		// Show the window (needs to happen after setting the background pixmap)
+		if(!desc.hidden)
+			XMapWindow(display, m->xWindow);
+
 		if(!desc.showOnTaskBar)
 			showOnTaskbar(false);
 
@@ -212,17 +227,17 @@ namespace bs
 	LinuxWindow::~LinuxWindow()
 	{
 		if(m->xWindow != 0)
-			_cleanUp();
+		{
+			XUnmapWindow(LinuxPlatform::getXDisplay(), m->xWindow);
+			XSync(LinuxPlatform::getXDisplay(), 0);
 
-		bs_delete(m);
-	}
+			XDestroyWindow(LinuxPlatform::getXDisplay(), m->xWindow);
+			XSync(LinuxPlatform::getXDisplay(), 0);
 
-	void LinuxWindow::close()
-	{
-		XDestroyWindow(LinuxPlatform::getXDisplay(), m->xWindow);
-		XFlush(LinuxPlatform::getXDisplay());
+			_cleanUp();
+		}
 
-		_cleanUp();
+		bs_delete(m);
 	}
 
 	void LinuxWindow::move(INT32 x, INT32 y)
@@ -365,46 +380,81 @@ namespace bs
 		m->xWindow = 0;
 	}
 
-	void LinuxWindow::_setDragZone(const Rect2I& rect)
+	void LinuxWindow::_setDragZones(const Vector<Rect2I>& rects)
 	{
-		m->dragZone = rect;
+		m->dragZones = rects;
 	}
 
-	bool LinuxWindow::_dragStart(INT32 x, INT32 y)
+	void LinuxWindow::_dragStart(const XButtonEvent& event)
 	{
-		if(m->hasTitleBar)
-			return false;
+		// Make sure to reset the flag since WM could have (and probably has) handled the drag end event, so we never
+		// received _dragEnd() call.
+		m->dragInProgress = false;
 
-		if(m->dragZone.width == 0 || m->dragZone.height == 0)
-			return false;
+		// If window has a titlebar, custom drag zones aren't used
+		if(m->hasTitleBar)
+			return;
 
-		if(x >= m->dragZone.x && x < (INT32)(m->dragZone.x + m->dragZone.width) &&
-		   y >= m->dragZone.y && y < (INT32)(m->dragZone.y + m->dragZone.height))
+		for(auto& entry : m->dragZones)
 		{
-			m->dragStartX = x;
-			m->dragStartY = y;
+			if (entry.width == 0 || entry.height == 0)
+				continue;
 
-			m->dragInProgress = true;
-			return true;
+			if(entry.contains(Vector2I(event.x, event.y)))
+			{
+				XUngrabPointer(LinuxPlatform::getXDisplay(), 0L);
+				XFlush(LinuxPlatform::getXDisplay());
+
+				Atom wmMoveResize = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_MOVERESIZE", False);
+
+				XEvent xev;
+				memset(&xev, 0, sizeof(xev));
+				xev.type = ClientMessage;
+				xev.xclient.window = m->xWindow;
+				xev.xclient.message_type = wmMoveResize;
+				xev.xclient.format = 32;
+				xev.xclient.data.l[0] = event.x_root;
+				xev.xclient.data.l[1] = event.y_root;
+				xev.xclient.data.l[2] = _NET_WM_MOVERESIZE_MOVE;
+				xev.xclient.data.l[3] = Button1;
+				xev.xclient.data.l[4] = 0;
+
+				XSendEvent(LinuxPlatform::getXDisplay(), DefaultRootWindow(LinuxPlatform::getXDisplay()), False,
+						SubstructureRedirectMask | SubstructureNotifyMask, &xev);
+				XSync(LinuxPlatform::getXDisplay(), 0);
+
+				m->dragInProgress = true;
+				return;
+			}
 		}
 
-		return false;
+		return;
 	}
 
-	void LinuxWindow::_dragUpdate(INT32 x, INT32 y)
+	void LinuxWindow::_dragEnd()
 	{
-		if(!m->dragInProgress)
-			return;
+		// WM failed to end the drag, send the cancel drag event
+		if(m->dragInProgress)
+		{
+			Atom wmMoveResize = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_MOVERESIZE", False);
 
-		INT32 offsetX = x - m->dragStartX;
-		INT32 offsetY = y - m->dragStartY;
+			XEvent xev;
+			memset(&xev, 0, sizeof(xev));
+			xev.type = ClientMessage;
+			xev.xclient.window = m->xWindow;
+			xev.xclient.message_type = wmMoveResize;
+			xev.xclient.format = 32;
+			xev.xclient.data.l[0] = 0;
+			xev.xclient.data.l[1] = 0;
+			xev.xclient.data.l[2] = _NET_WM_MOVERESIZE_CANCEL;
+			xev.xclient.data.l[3] = Button1;
+			xev.xclient.data.l[4] = 0;
 
-		move(getLeft() + offsetX, getTop() + offsetY);
-	}
+			XSendEvent(LinuxPlatform::getXDisplay(), DefaultRootWindow(LinuxPlatform::getXDisplay()), False,
+					SubstructureRedirectMask | SubstructureNotifyMask, &xev);
 
-	void LinuxWindow::_dragEnd()
-	{
-		m->dragInProgress = false;
+			m->dragInProgress = false;
+		}
 	}
 
 	::Window LinuxWindow::_getXWindow() const
@@ -523,6 +573,7 @@ namespace bs
 	{
 		Atom wmState = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_STATE", False);
 		Atom wmSkipTaskbar = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_STATE_SKIP_TASKBAR", False);
+		Atom wmSkipPager = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_STATE_SKIP_PAGER", False);
 
 		XEvent xev;
 		memset(&xev, 0, sizeof(xev));
@@ -530,11 +581,17 @@ namespace bs
 		xev.xclient.window = m->xWindow;
 		xev.xclient.message_type = wmState;
 		xev.xclient.format = 32;
-		xev.xclient.data.l[0] = enable ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
+		xev.xclient.data.l[0] = enable ? _NET_WM_STATE_REMOVE : _NET_WM_STATE_ADD;
 		xev.xclient.data.l[1] = wmSkipTaskbar;
 
 		XSendEvent(LinuxPlatform::getXDisplay(), DefaultRootWindow(LinuxPlatform::getXDisplay()), False,
 				SubstructureRedirectMask | SubstructureNotifyMask, &xev);
+
+		xev.xclient.data.l[1] = wmSkipPager;
+		XSendEvent(LinuxPlatform::getXDisplay(), DefaultRootWindow(LinuxPlatform::getXDisplay()), False,
+				SubstructureRedirectMask | SubstructureNotifyMask, &xev);
+
+		XSync(LinuxPlatform::getXDisplay(), 0);
 	}
 
 	void LinuxWindow::_setFullscreen(bool fullscreen)

+ 6 - 14
Source/BansheeCore/Linux/BsLinuxWindow.h

@@ -28,6 +28,7 @@ namespace bs
 		bool allowResize;
 		bool modal;
 		bool showOnTaskBar;
+		bool hidden;
 		::Window parent;
 		XVisualInfo visualInfo;
 		SPtr<PixelData> background;
@@ -70,9 +71,6 @@ namespace bs
 		/**	Restores the window to original position and size if it is minimized or maximized. */
 		void restore();
 
-		/** Closes the window. Window becomes unusable past this call. */
-		void close();
-
 		/**	Change the size of the window. */
 		void resize(UINT32 width, UINT32 height);
 
@@ -101,22 +99,16 @@ namespace bs
 		 * when window has no title bar, yet you still want to allow the user to drag it by clicking on some specific area
 		 * (e.g. a title bar you manually render).
 		 *
-		 * @param[in]	rect	Area of the window (relative to the window origin in top-left corner) in which the drag
+		 * @param[in]	rects	Areas of the window (relative to the window origin in top-left corner) in which the drag
 		 * 						operation in allowed.
 		 */
-		void _setDragZone(const Rect2I& rect);
-
-		/**
-		 * Notifies the window that user has started dragging the window using the custom drag zone. Provided coordinates
-		 * specify the location of the drag start. They are relative to the window top left origin.
-		 */
-		bool _dragStart(int32_t x, int32_t y);
+		void _setDragZones(const Vector<Rect2I>& rects);
 
 		/**
-		 * Notifies the window that the user has moved the cursor while dragging the window. The provided coordinates are
-		 * relative to the window top left origin.
+		 * Notifies the window that user has started dragging the window using a custom drag zone. Provided parameter is the
+		 * event that started the drag.
 		 */
-		void _dragUpdate(int32_t x, int32_t y);
+		void _dragStart(const XButtonEvent& event);
 
 		/** Notifies the window the user has stopped the window drag operation. */
 		void _dragEnd();

+ 0 - 2
Source/BansheeCore/RenderAPI/BsRenderWindow.cpp

@@ -242,8 +242,6 @@ namespace bs
 
 	RenderWindow::~RenderWindow()
 	{
-		Platform::resetNonClientAreas(*this);
-
 		RenderWindowManager::instance().windowDestroyed(this);
 	}
 

+ 11 - 0
Source/BansheeCore/RenderAPI/BsRenderWindow.h

@@ -265,6 +265,17 @@ namespace bs
 		/**	Reposition the window. */
 		virtual void move(INT32 left, INT32 top) = 0;
 
+		/**
+		 * Enables or disables vertical synchronization. When enabled the system will wait for monitor refresh before
+		 * presenting the back buffer. This eliminates tearing but can result in increased input lag.
+		 *
+		 * @param enabled 		True to enable vsync, false to disable.
+		 * @param interval 		Interval at which to perform the sync. Value of one means the sync will be performed for
+		 * 						each monitor refresh, value of two means it will be performs for every second (half the
+		 * 						rate), and so on.
+		 */
+		virtual void setVSync(bool enabled, UINT32 interval = 1) = 0;
+
 		/**	Returns properties that describe the render window. */
 		const RenderWindowProperties& getProperties() const;
 

+ 1 - 0
Source/BansheeD3D11RenderAPI/BsD3D11RenderWindow.cpp

@@ -99,6 +99,7 @@ namespace bs
 		}
 
 		destroySizeDependedD3DResources();
+		Platform::resetNonClientAreas(*this);
 	}
 
 	void D3D11RenderWindow::initialize()

+ 1 - 0
Source/BansheeEngine/Platform/BsSplashScreen.cpp

@@ -117,6 +117,7 @@ namespace bs
 		windowDesc.title = "Banshee Splash";
 		windowDesc.showDecorations = false;
 		windowDesc.allowResize = false;
+		windowDesc.hidden = false;
 
 		SPtr<PixelData> splashPixelData = BuiltinResources::getSplashScreen();
 		if (splashPixelData == nullptr)

+ 26 - 5
Source/BansheeGLRenderAPI/Linux/BsLinuxRenderWindow.cpp

@@ -78,9 +78,9 @@ namespace bs
 
 		if (mWindow != nullptr)
 		{
-			LinuxPlatform::lockX();
+			Platform::resetNonClientAreas(*this);
 
-			mWindow->close();
+			LinuxPlatform::lockX();
 
 			bs_delete(mWindow);
 			mWindow = nullptr;
@@ -113,6 +113,7 @@ namespace bs
 		windowDesc.modal = mDesc.modal;
 		windowDesc.visualInfo = visualConfig.visualInfo;
 		windowDesc.screen = mDesc.videoMode.getOutputIdx();
+		windowDesc.hidden = mDesc.hideUntilSwap || mDesc.hidden;
 
 		NameValuePairList::const_iterator opt;
 		opt = mDesc.platformSpecific.find("parentWindowHandle");
@@ -152,8 +153,8 @@ namespace bs
 		if(mDesc.fullscreen && !mIsChild)
 			setFullscreen(mDesc.videoMode);
 
-		if(mDesc.hideUntilSwap || mDesc.hidden)
-			setHidden(true);
+		if(mDesc.vsync && mDesc.vsyncInterval > 0)
+			setVSync(true, mDesc.vsyncInterval);
 
 		{
 			ScopedSpinLock lock(mLock);
@@ -463,6 +464,25 @@ namespace bs
 		LinuxPlatform::unlockX();
 	}
 
+	void LinuxRenderWindow::setVSync(bool enabled, UINT32 interval)
+	{
+		THROW_IF_NOT_CORE_THREAD;
+
+		if(!enabled)
+			interval = 0;
+
+		LinuxPlatform::lockX();
+
+		if(glXSwapIntervalEXT != nullptr)
+			glXSwapIntervalEXT(LinuxPlatform::getXDisplay(), mWindow->_getXWindow(), interval);
+		else if(glXSwapIntervalMESA != nullptr)
+			glXSwapIntervalMESA(interval);
+		else if(glXSwapIntervalSGI != nullptr)
+			glXSwapIntervalSGI(interval);
+
+		LinuxPlatform::unlockX();
+	}
+
 	void LinuxRenderWindow::swapBuffers(UINT32 syncMask)
 	{
 		THROW_IF_NOT_CORE_THREAD;
@@ -571,7 +591,8 @@ namespace bs
 	{
 		THROW_IF_NOT_CORE_THREAD;
 
-		mShowOnSwap = false;
+		if(!hidden)
+			mShowOnSwap = false;
 
 		LinuxPlatform::lockX();
 

+ 3 - 0
Source/BansheeGLRenderAPI/Linux/BsLinuxRenderWindow.h

@@ -101,6 +101,9 @@ namespace bs
 			/** @copydoc RenderWindow::resize */
 			void resize(UINT32 width, UINT32 height) override;
 
+			/** @copydoc RenderWindow::setVSync */
+			void setVSync(bool enabled, UINT32 interval = 1) override;
+
 			/**
 			 * Copies the contents of a frame buffer into the pre-allocated buffer.
 			 *

+ 13 - 13
Source/BansheeGLRenderAPI/Win32/BsWin32Context.cpp

@@ -9,21 +9,21 @@
 
 namespace bs { namespace ct
 {
-    Win32Context::Win32Context(HDC hdc, HGLRC glrc, bool ownsContext):
-		mHDC(hdc), mGlrc(glrc), mOwnsContext(ownsContext)
-    {
-    }
-    
-    Win32Context::~Win32Context()
-    {
+	Win32Context::Win32Context(HDC hdc, HGLRC glrc, bool ownsContext):
+			mHDC(hdc), mGlrc(glrc), mOwnsContext(ownsContext)
+	{
+	}
+
+	Win32Context::~Win32Context()
+	{
 		if (mOwnsContext)
 			releaseContext();
-    }
-        
-    void Win32Context::setCurrent()
-    {
-         wglMakeCurrent(mHDC, mGlrc);      
-    }
+	}
+
+	void Win32Context::setCurrent()
+	{
+		wglMakeCurrent(mHDC, mGlrc);
+	}
 
 	void Win32Context::endCurrent()
 	{

+ 5 - 0
Source/BansheeGLRenderAPI/Win32/BsWin32RenderWindow.cpp

@@ -30,6 +30,11 @@ namespace bs
 
 	}
 
+	Win32RenderWindow::~Win32RenderWindow()
+	{
+		Platform::resetNonClientAreas(*this);
+	}
+
 	void Win32RenderWindow::getCustomAttribute(const String& name, void* pData) const
 	{
 		if (name == "WINDOW")

+ 1 - 1
Source/BansheeGLRenderAPI/Win32/BsWin32RenderWindow.h

@@ -19,7 +19,7 @@ namespace bs
 	class Win32RenderWindow : public RenderWindow
 	{
 	public:
-		~Win32RenderWindow() { }
+		~Win32RenderWindow();
 
 		/** @copydoc RenderWindow::screenToWindowPos */
 		void getCustomAttribute(const String& name, void* pData) const override;

+ 5 - 0
Source/BansheeVulkanRenderAPI/Win32/BsWin32RenderWindow.cpp

@@ -22,6 +22,11 @@ namespace bs
 		: RenderWindow(desc, windowId), mProperties(desc)
 	{ }
 
+	Win32RenderWindow::~Win32RenderWindow()
+	{
+		Platform::resetNonClientAreas(*this);
+	}
+
 	void Win32RenderWindow::getCustomAttribute(const String& name, void* pData) const
 	{
 		if (name == "WINDOW")

+ 1 - 1
Source/BansheeVulkanRenderAPI/Win32/BsWin32RenderWindow.h

@@ -19,7 +19,7 @@ namespace bs
 	class Win32RenderWindow : public RenderWindow
 	{
 	public:
-		~Win32RenderWindow() { }
+		~Win32RenderWindow();
 
 		/** @copydoc RenderWindow::screenToWindowPos */
 		void getCustomAttribute(const String& name, void* pData) const override;