diff --git a/include/SFML/Window/Window.hpp b/include/SFML/Window/Window.hpp index 2076f8e09..6d4ecf79b 100644 --- a/include/SFML/Window/Window.hpp +++ b/include/SFML/Window/Window.hpp @@ -403,6 +403,7 @@ public: /// on the previous thread first if it was active. /// Only one window can be active on a thread at a time, thus /// the window previously active (if any) automatically gets deactivated. + /// This is not to be confused with requestFocus(). /// /// \param active True to activate, false to deactivate /// @@ -411,6 +412,35 @@ public: //////////////////////////////////////////////////////////// bool setActive(bool active = true) const; + //////////////////////////////////////////////////////////// + /// \brief Request the current window to be made the active + /// foreground window + /// + /// At any given time, only one window may have the input focus + /// to receive input events such as keystrokes or mouse events. + /// If a window requests focus, it only hints to the operating + /// system, that it would like to be focused. The operating system + /// is free to deny the request. + /// This is not to be confused with setActive(). + /// + /// \see hasFocus + /// + //////////////////////////////////////////////////////////// + void requestFocus(); + + //////////////////////////////////////////////////////////// + /// \brief Check whether the window has the input focus + /// + /// At any given time, only one window may have the input focus + /// to receive input events such as keystrokes or most mouse + /// events. + /// + /// \return True if window has focus, false otherwise + /// \see requestFocus + /// + //////////////////////////////////////////////////////////// + bool hasFocus() const; + //////////////////////////////////////////////////////////// /// \brief Display on screen what has been rendered to the window so far /// diff --git a/src/SFML/Window/Android/WindowImplAndroid.cpp b/src/SFML/Window/Android/WindowImplAndroid.cpp index 1b1a1a4ba..22cf60cab 100644 --- a/src/SFML/Window/Android/WindowImplAndroid.cpp +++ b/src/SFML/Window/Android/WindowImplAndroid.cpp @@ -181,6 +181,21 @@ void WindowImplAndroid::setKeyRepeatEnabled(bool enabled) } +//////////////////////////////////////////////////////////// +void WindowImplAndroid::requestFocus() +{ + // Not applicable +} + + +//////////////////////////////////////////////////////////// +bool WindowImplAndroid::hasFocus() const +{ + // Not applicable + return false; +} + + //////////////////////////////////////////////////////////// void WindowImplAndroid::forwardEvent(const Event& event) { diff --git a/src/SFML/Window/Android/WindowImplAndroid.hpp b/src/SFML/Window/Android/WindowImplAndroid.hpp index 7786248be..f5106e3ae 100644 --- a/src/SFML/Window/Android/WindowImplAndroid.hpp +++ b/src/SFML/Window/Android/WindowImplAndroid.hpp @@ -154,6 +154,21 @@ public: //////////////////////////////////////////////////////////// virtual void setKeyRepeatEnabled(bool enabled); + //////////////////////////////////////////////////////////// + /// \brief Request the current window to be made the active + /// foreground window + /// + //////////////////////////////////////////////////////////// + virtual void requestFocus(); + + //////////////////////////////////////////////////////////// + /// \brief Check whether the window has the input focus + /// + /// \return True if window has focus, false otherwise + /// + //////////////////////////////////////////////////////////// + virtual bool hasFocus() const; + static void forwardEvent(const Event& event); static WindowImplAndroid* singleInstance; diff --git a/src/SFML/Window/OSX/SFViewController.mm b/src/SFML/Window/OSX/SFViewController.mm index 2b7676914..ba2e8a0f1 100644 --- a/src/SFML/Window/OSX/SFViewController.mm +++ b/src/SFML/Window/OSX/SFViewController.mm @@ -187,6 +187,26 @@ } +//////////////////////////////////////////////////////// +-(void)requestFocus +{ + // Note: this doesn't imply that the view will get any event. + // The user has to make sure events are forwarded to the view + // with the usual responder chain. + [[m_view window] makeKeyAndOrderFront:nil]; + + // In case the app is not active, make its dock icon bounce for one sec + [NSApp requestUserAttention:NSInformationalRequest]; +} + + +//////////////////////////////////////////////////////////// +-(BOOL)hasFocus +{ + return [NSApp keyWindow] == [m_view window]; +} + + //////////////////////////////////////////////////////// -(void)enableKeyRepeat { diff --git a/src/SFML/Window/OSX/SFWindowController.mm b/src/SFML/Window/OSX/SFWindowController.mm index 1b0a0de7c..afdd31872 100644 --- a/src/SFML/Window/OSX/SFWindowController.mm +++ b/src/SFML/Window/OSX/SFWindowController.mm @@ -451,6 +451,23 @@ } +//////////////////////////////////////////////////////// +-(void)requestFocus +{ + [m_window makeKeyAndOrderFront:nil]; + + // In case the app is not active, make its dock icon bounce for one sec + [NSApp requestUserAttention:NSInformationalRequest]; +} + + +//////////////////////////////////////////////////////////// +-(BOOL)hasFocus +{ + return [NSApp keyWindow] == m_window; +} + + //////////////////////////////////////////////////////// -(void)enableKeyRepeat { diff --git a/src/SFML/Window/OSX/WindowImplCocoa.hpp b/src/SFML/Window/OSX/WindowImplCocoa.hpp index 723fee71d..4a66d743c 100644 --- a/src/SFML/Window/OSX/WindowImplCocoa.hpp +++ b/src/SFML/Window/OSX/WindowImplCocoa.hpp @@ -320,6 +320,21 @@ public: //////////////////////////////////////////////////////////// virtual void setKeyRepeatEnabled(bool enabled); + //////////////////////////////////////////////////////////// + /// \brief Request the current window to be made the active + /// foreground window + /// + //////////////////////////////////////////////////////////// + virtual void requestFocus(); + + //////////////////////////////////////////////////////////// + /// \brief Check whether the window has the input focus + /// + /// \return True if window has focus, false otherwise + /// + //////////////////////////////////////////////////////////// + virtual bool hasFocus() const; + protected: //////////////////////////////////////////////////////////// diff --git a/src/SFML/Window/OSX/WindowImplCocoa.mm b/src/SFML/Window/OSX/WindowImplCocoa.mm index 6b1c8be16..0ddfa78fc 100644 --- a/src/SFML/Window/OSX/WindowImplCocoa.mm +++ b/src/SFML/Window/OSX/WindowImplCocoa.mm @@ -552,6 +552,20 @@ void WindowImplCocoa::setKeyRepeatEnabled(bool enabled) } +//////////////////////////////////////////////////////////// +void WindowImplCocoa::requestFocus() +{ + [m_delegate requestFocus]; +} + + +//////////////////////////////////////////////////////////// +bool WindowImplCocoa::hasFocus() const +{ + return [m_delegate hasFocus]; +} + + } // namespace priv } // namespace sf diff --git a/src/SFML/Window/OSX/WindowImplDelegateProtocol.h b/src/SFML/Window/OSX/WindowImplDelegateProtocol.h index b43805a64..0132f4018 100644 --- a/src/SFML/Window/OSX/WindowImplDelegateProtocol.h +++ b/src/SFML/Window/OSX/WindowImplDelegateProtocol.h @@ -166,6 +166,21 @@ namespace sf { //////////////////////////////////////////////////////////// -(void)closeWindow; +//////////////////////////////////////////////////////////// +/// \brief Request the current window to be made the active +/// foreground window +/// +//////////////////////////////////////////////////////////// +-(void)requestFocus; + +//////////////////////////////////////////////////////////// +/// \brief Check whether the window has the input focus +/// +/// \return True if window has focus, false otherwise +/// +//////////////////////////////////////////////////////////// +-(BOOL)hasFocus; + //////////////////////////////////////////////////////////// /// \brief Enable key repeat /// diff --git a/src/SFML/Window/Unix/WindowImplX11.cpp b/src/SFML/Window/Unix/WindowImplX11.cpp index a9f2f0c3a..e4dfe775a 100644 --- a/src/SFML/Window/Unix/WindowImplX11.cpp +++ b/src/SFML/Window/Unix/WindowImplX11.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #ifdef SFML_OPENGL_ES #include @@ -54,10 +55,11 @@ //////////////////////////////////////////////////////////// namespace { - sf::priv::WindowImplX11* fullscreenWindow = NULL; - unsigned long eventMask = FocusChangeMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask | - PointerMotionMask | KeyPressMask | KeyReleaseMask | StructureNotifyMask | - EnterWindowMask | LeaveWindowMask; + sf::priv::WindowImplX11* fullscreenWindow = NULL; + std::vector allWindows; + unsigned long eventMask = FocusChangeMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask | + PointerMotionMask | KeyPressMask | KeyReleaseMask | StructureNotifyMask | + EnterWindowMask | LeaveWindowMask; // Filter the events received by windows (only allow those matching a specific window) Bool checkEvent(::Display*, XEvent* event, XPointer userData) @@ -192,7 +194,7 @@ m_useSizeHints(false) { static const unsigned long MWM_HINTS_FUNCTIONS = 1 << 0; static const unsigned long MWM_HINTS_DECORATIONS = 1 << 1; - + //static const unsigned long MWM_DECOR_ALL = 1 << 0; static const unsigned long MWM_DECOR_BORDER = 1 << 1; static const unsigned long MWM_DECOR_RESIZEH = 1 << 2; @@ -207,7 +209,7 @@ m_useSizeHints(false) static const unsigned long MWM_FUNC_MINIMIZE = 1 << 3; static const unsigned long MWM_FUNC_MAXIMIZE = 1 << 4; static const unsigned long MWM_FUNC_CLOSE = 1 << 5; - + struct WMHints { unsigned long flags; @@ -216,7 +218,7 @@ m_useSizeHints(false) long inputMode; unsigned long state; }; - + WMHints hints; hints.flags = MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS; hints.decorations = 0; @@ -250,11 +252,11 @@ m_useSizeHints(false) sizeHints->flags = PMinSize | PMaxSize; sizeHints->min_width = sizeHints->max_width = width; sizeHints->min_height = sizeHints->max_height = height; - XSetWMNormalHints(m_display, m_window, sizeHints); + XSetWMNormalHints(m_display, m_window, sizeHints); XFree(sizeHints); } } - + // Set the window's WM class (this can be used by window managers) char windowClass[512]; findExecutableName(windowClass, sizeof(windowClass)); @@ -303,6 +305,9 @@ WindowImplX11::~WindowImplX11() // Close the connection with the X server CloseDisplay(m_display); + + // Remove this window from the global list of windows (required for focus request) + allWindows.erase(std::find(allWindows.begin(), allWindows.end(), this)); } @@ -365,7 +370,7 @@ void WindowImplX11::setSize(const Vector2u& size) sizeHints->flags = PMinSize | PMaxSize; sizeHints->min_width = sizeHints->max_width = size.x; sizeHints->min_height = sizeHints->max_height = size.y; - XSetWMNormalHints(m_display, m_window, sizeHints); + XSetWMNormalHints(m_display, m_window, sizeHints); XFree(sizeHints); } @@ -379,17 +384,17 @@ void WindowImplX11::setTitle(const String& title) { // Bare X11 has no Unicode window title support. // There is however an option to tell the window manager your unicode title via hints. - + // Convert to UTF-8 encoding. std::basic_string utf8Title; Utf32::toUtf8(title.begin(), title.end(), std::back_inserter(utf8Title)); - + // Set the _NET_WM_NAME atom, which specifies a UTF-8 encoded window title. Atom wmName = XInternAtom(m_display, "_NET_WM_NAME", False); Atom useUtf8 = XInternAtom(m_display, "UTF8_STRING", False); XChangeProperty(m_display, m_window, wmName, useUtf8, 8, PropModeReplace, utf8Title.c_str(), utf8Title.size()); - + // Set the non-Unicode title as a fallback for window managers who don't support _NET_WM_NAME. XStoreName(m_display, m_window, title.toAnsiString().c_str()); } @@ -437,7 +442,7 @@ void WindowImplX11::setIcon(unsigned int width, unsigned int height, const Uint8 if (i * 8 + k < width) { Uint8 opacity = (pixels[(i * 8 + k + j * width) * 4 + 3] > 0) ? 1 : 0; - maskPixels[i + j * pitch] |= (opacity << k); + maskPixels[i + j * pitch] |= (opacity << k); } } } @@ -483,6 +488,67 @@ void WindowImplX11::setKeyRepeatEnabled(bool enabled) } +//////////////////////////////////////////////////////////// +void WindowImplX11::requestFocus() +{ + // Focus is only stolen among SFML windows, not between applications + // Check the global list of windows to find out whether an SFML window has the focus + // Note: can't handle console and other non-SFML windows belonging to the application. + bool sfmlWindowFocused = false; + for (std::vector::iterator itr = allWindows.begin(); itr != allWindows.end(); ++itr) + { + if ((*itr)->hasFocus()) + { + sfmlWindowFocused = true; + break; + } + } + + // Check if window is viewable (not on other desktop, ...) + // TODO: Check also if minimized + XWindowAttributes attributes; + if (XGetWindowAttributes(m_display, m_window, &attributes) == 0) + { + sf::err() << "Failed to check if window is viewable while requesting focus" << std::endl; + return; // error getting attribute + } + + bool windowViewable = (attributes.map_state == IsViewable); + + if (sfmlWindowFocused && windowViewable) + { + // Another SFML window of this application has the focus and the current window is viewable: + // steal focus (i.e. bring window to the front and give it input focus) + XRaiseWindow(m_display, m_window); + XSetInputFocus(m_display, m_window, RevertToPointerRoot, CurrentTime); + } + else + { + // Otherwise: display urgency hint (flashing application logo) + // Ensure WM hints exist, allocate if necessary + XWMHints* hints = XGetWMHints(m_display, m_window); + if (hints == NULL) + hints = XAllocWMHints(); + + // Add urgency (notification) flag to hints + hints->flags |= XUrgencyHint; + XSetWMHints(m_display, m_window, hints); + XFree(hints); + } +} + + +//////////////////////////////////////////////////////////// +bool WindowImplX11::hasFocus() const +{ + ::Window focusedWindow = 0; + int revertToReturn = 0; + XGetInputFocus(m_display, &focusedWindow, &revertToReturn); + + return m_window == focusedWindow; +} + + //////////////////////////////////////////////////////////// void WindowImplX11::switchToFullscreen(const VideoMode& mode) { @@ -568,6 +634,9 @@ void WindowImplX11::initialize() // Flush the commands queue XFlush(m_display); + + // Add this window to the global list of windows (required for focus request) + allWindows.push_back(this); } @@ -599,7 +668,7 @@ void WindowImplX11::cleanup() { // Get current screen info XRRScreenConfiguration* config = XRRGetScreenInfo(m_display, RootWindow(m_display, m_screen)); - if (config) + if (config) { // Get the current rotation Rotation currentRotation; @@ -610,7 +679,7 @@ void WindowImplX11::cleanup() // Free the configuration instance XRRFreeScreenConfigInfo(config); - } + } // Reset the fullscreen window fullscreenWindow = NULL; @@ -680,6 +749,16 @@ bool WindowImplX11::processEvent(XEvent windowEvent) Event event; event.type = Event::GainedFocus; pushEvent(event); + + // If the window has been previously marked urgent (notification) as a result of a focus request, undo that + XWMHints* hints = XGetWMHints(m_display, m_window); + if (hints != NULL) + { + // Remove urgency (notification) flag from hints + hints->flags &= ~XUrgencyHint; + XSetWMHints(m_display, m_window, hints); + XFree(hints); + } break; } @@ -717,7 +796,7 @@ bool WindowImplX11::processEvent(XEvent windowEvent) // Close event case ClientMessage: { - if ((windowEvent.xclient.format == 32) && (windowEvent.xclient.data.l[0]) == static_cast(m_atomClose)) + if ((windowEvent.xclient.format == 32) && (windowEvent.xclient.data.l[0]) == static_cast(m_atomClose)) { Event event; event.type = Event::Closed; @@ -823,7 +902,7 @@ bool WindowImplX11::processEvent(XEvent windowEvent) case Button2: event.mouseButton.button = Mouse::Middle; break; case Button3: event.mouseButton.button = Mouse::Right; break; case 8: event.mouseButton.button = Mouse::XButton1; break; - case 9: event.mouseButton.button = Mouse::XButton2; break; + case 9: event.mouseButton.button = Mouse::XButton2; break; } pushEvent(event); } @@ -846,7 +925,7 @@ bool WindowImplX11::processEvent(XEvent windowEvent) case Button2: event.mouseButton.button = Mouse::Middle; break; case Button3: event.mouseButton.button = Mouse::Right; break; case 8: event.mouseButton.button = Mouse::XButton1; break; - case 9: event.mouseButton.button = Mouse::XButton2; break; + case 9: event.mouseButton.button = Mouse::XButton2; break; } pushEvent(event); } diff --git a/src/SFML/Window/Unix/WindowImplX11.hpp b/src/SFML/Window/Unix/WindowImplX11.hpp index 7e2b48526..19de32f0c 100644 --- a/src/SFML/Window/Unix/WindowImplX11.hpp +++ b/src/SFML/Window/Unix/WindowImplX11.hpp @@ -154,6 +154,21 @@ public: //////////////////////////////////////////////////////////// virtual void setKeyRepeatEnabled(bool enabled); + //////////////////////////////////////////////////////////// + /// \brief Request the current window to be made the active + /// foreground window + /// + //////////////////////////////////////////////////////////// + virtual void requestFocus(); + + //////////////////////////////////////////////////////////// + /// \brief Check whether the window has the input focus + /// + /// \return True if window has focus, false otherwise + /// + //////////////////////////////////////////////////////////// + virtual bool hasFocus() const; + protected: //////////////////////////////////////////////////////////// diff --git a/src/SFML/Window/Win32/WindowImplWin32.cpp b/src/SFML/Window/Win32/WindowImplWin32.cpp index 6d9ac28c0..5ebc8a80c 100644 --- a/src/SFML/Window/Win32/WindowImplWin32.cpp +++ b/src/SFML/Window/Win32/WindowImplWin32.cpp @@ -366,6 +366,40 @@ void WindowImplWin32::setKeyRepeatEnabled(bool enabled) } +//////////////////////////////////////////////////////////// +void WindowImplWin32::requestFocus() +{ + // Allow focus stealing only within the same process; compare PIDs of current and foreground window + DWORD thisPid = GetWindowThreadProcessId(m_handle, NULL); + DWORD foregroundPid = GetWindowThreadProcessId(GetForegroundWindow(), NULL); + + if (thisPid == foregroundPid) + { + // The window requesting focus belongs to the same process as the current window: steal focus + SetForegroundWindow(m_handle); + } + else + { + // Different process: don't steal focus, but create a taskbar notification ("flash") + FLASHWINFO info; + info.cbSize = sizeof(info); + info.hwnd = m_handle; + info.dwFlags = FLASHW_TRAY; + info.dwTimeout = 0; + info.uCount = 3; + + FlashWindowEx(&info); + } +} + + +//////////////////////////////////////////////////////////// +bool WindowImplWin32::hasFocus() const +{ + return m_handle == GetForegroundWindow(); +} + + //////////////////////////////////////////////////////////// void WindowImplWin32::registerWindowClass() { diff --git a/src/SFML/Window/Win32/WindowImplWin32.hpp b/src/SFML/Window/Win32/WindowImplWin32.hpp index 3678d79e2..0bb3ce840 100644 --- a/src/SFML/Window/Win32/WindowImplWin32.hpp +++ b/src/SFML/Window/Win32/WindowImplWin32.hpp @@ -153,6 +153,21 @@ public: //////////////////////////////////////////////////////////// virtual void setKeyRepeatEnabled(bool enabled); + //////////////////////////////////////////////////////////// + /// \brief Request the current window to be made the active + /// foreground window + /// + //////////////////////////////////////////////////////////// + virtual void requestFocus(); + + //////////////////////////////////////////////////////////// + /// \brief Check whether the window has the input focus + /// + /// \return True if window has focus, false otherwise + /// + //////////////////////////////////////////////////////////// + virtual bool hasFocus() const; + protected: //////////////////////////////////////////////////////////// diff --git a/src/SFML/Window/Window.cpp b/src/SFML/Window/Window.cpp index 397cbbf2d..7487ea6ef 100644 --- a/src/SFML/Window/Window.cpp +++ b/src/SFML/Window/Window.cpp @@ -337,6 +337,22 @@ bool Window::setActive(bool active) const //////////////////////////////////////////////////////////// +void Window::requestFocus() +{ + if (m_impl) + m_impl->requestFocus(); +} + + +//////////////////////////////////////////////////////////// +bool Window::hasFocus() const +{ + return m_impl && m_impl->hasFocus(); +} + + +//////////////////////////////////////////////////////////// + void Window::display() { // Display the backbuffer on screen diff --git a/src/SFML/Window/WindowImpl.hpp b/src/SFML/Window/WindowImpl.hpp index 604702d4f..cdca80e0d 100644 --- a/src/SFML/Window/WindowImpl.hpp +++ b/src/SFML/Window/WindowImpl.hpp @@ -194,6 +194,21 @@ public: //////////////////////////////////////////////////////////// virtual void setKeyRepeatEnabled(bool enabled) = 0; + //////////////////////////////////////////////////////////// + /// \brief Request the current window to be made the active + /// foreground window + /// + //////////////////////////////////////////////////////////// + virtual void requestFocus() = 0; + + //////////////////////////////////////////////////////////// + /// \brief Check whether the window has the input focus + /// + /// \return True if window has focus, false otherwise + /// + //////////////////////////////////////////////////////////// + virtual bool hasFocus() const = 0; + protected: //////////////////////////////////////////////////////////// diff --git a/src/SFML/Window/iOS/WindowImplUIKit.hpp b/src/SFML/Window/iOS/WindowImplUIKit.hpp index 9f56fe906..61d082886 100644 --- a/src/SFML/Window/iOS/WindowImplUIKit.hpp +++ b/src/SFML/Window/iOS/WindowImplUIKit.hpp @@ -157,6 +157,21 @@ public: //////////////////////////////////////////////////////////// virtual void setKeyRepeatEnabled(bool enabled); + //////////////////////////////////////////////////////////// + /// \brief Request the current window to be made the active + /// foreground window + /// + //////////////////////////////////////////////////////////// + virtual void requestFocus(); + + //////////////////////////////////////////////////////////// + /// \brief Check whether the window has the input focus + /// + /// \return True if window has focus, false otherwise + /// + //////////////////////////////////////////////////////////// + virtual bool hasFocus() const; + public: using WindowImpl::pushEvent; diff --git a/src/SFML/Window/iOS/WindowImplUIKit.mm b/src/SFML/Window/iOS/WindowImplUIKit.mm index 12b91aa41..7ccf7b318 100644 --- a/src/SFML/Window/iOS/WindowImplUIKit.mm +++ b/src/SFML/Window/iOS/WindowImplUIKit.mm @@ -169,6 +169,21 @@ void WindowImplUIKit::setKeyRepeatEnabled(bool enabled) // Not applicable } + +//////////////////////////////////////////////////////////// +void WindowImplUIKit::requestFocus() +{ + // Not applicable +} + + +//////////////////////////////////////////////////////////// +bool WindowImplUIKit::hasFocus() const +{ + // Not applicable + return false; +} + //////////////////////////////////////////////////////////// SFView* WindowImplUIKit::getGlView() const