[plasma/kwin/fork-stable] /: workspace: add optional mouse follows focus behavior

Natalie Clarius null at kde.org
Tue Aug 29 03:56:34 BST 2023


Git commit 66a267743961127b283283417c6f674d097ca09f by Natalie Clarius.
Committed on 29/08/2023 at 04:52.
Pushed by nclarius into branch 'fork-stable'.

workspace: add optional mouse follows focus behavior

M  +9    -0    doc/windowbehaviour/index.docbook
M  +19   -2    src/kcms/options/focus.ui
M  +4    -0    src/kcms/options/kwinoptions_settings.kcfg
M  +3    -0    src/kwin.kcfg
M  +11   -0    src/options.cpp
M  +15   -0    src/options.h
M  +7    -0    src/window.cpp
M  +32   -0    src/workspace.cpp
M  +9    -0    src/workspace.h

https://invent.kde.org/plasma/kwin/-/commit/66a267743961127b283283417c6f674d097ca09f

diff --git a/doc/windowbehaviour/index.docbook b/doc/windowbehaviour/index.docbook
index 18b5e8ce779..46b8e3119e3 100644
--- a/doc/windowbehaviour/index.docbook
+++ b/doc/windowbehaviour/index.docbook
@@ -275,6 +275,15 @@ that window to the front.
 
 </sect3>
 
+<sect3 id="focus-mouse-warp">
+<title>Mouse warp</title>
+
+<para>
+When <guilabel>Mouse follows focus</guilabel> is enabled, the mouse cursor will automatically jump into the active window whenever a window is added, activated, moved or resized.
+</para>
+
+</sect3>
+
 <sect3 id="focus-multiscreen">
 <title>Multiscreen behavior</title>
 
diff --git a/src/kcms/options/focus.ui b/src/kcms/options/focus.ui
index eaef6270af3..456dc4f5daa 100644
--- a/src/kcms/options/focus.ui
+++ b/src/kcms/options/focus.ui
@@ -191,13 +191,30 @@
       </layout>
      </item>
      <item row="6" column="0">
+        <widget class="QLabel" name="mouseWarpLabel">
+         <property name="text">
+          <string>Mouse warp:</string>
+         </property>
+        </widget>
+       </item>
+       <item row="6" column="1">
+        <widget class="QCheckBox" name="kcfg_MouseFollowsFocus">
+         <property name="whatsThis">
+          <string>When this option is enabled, the mouse cursor will always be kept in the focused window. This means that when opening a new window, switching tasks, or moving a window, the cursor will automatically be moved into the focused window.</string>
+         </property>
+         <property name="text">
+          <string>Mouse follows focus</string>
+         </property>
+        </widget>
+       </item>
+     <item row="7" column="0">
       <widget class="QLabel" name="multiscreenBehaviorLabel">
        <property name="text">
         <string>Multiscreen behavior:</string>
        </property>
       </widget>
      </item>
-     <item row="6" column="1">
+     <item row="7" column="1">
       <widget class="QCheckBox" name="kcfg_ActiveMouseScreen">
        <property name="whatsThis">
         <string>When this option is enabled, the active screen (where new windows appear, for example) is the screen containing the mouse pointer. When disabled, the active screen is the screen containing the focused window.</string>
@@ -207,7 +224,7 @@
        </property>
       </widget>
      </item>
-     <item row="7" column="1">
+     <item row="8" column="1">
       <widget class="QCheckBox" name="kcfg_SeparateScreenFocus">
        <property name="whatsThis">
         <string>When this option is enabled, focus operations are limited only to the active screen</string>
diff --git a/src/kcms/options/kwinoptions_settings.kcfg b/src/kcms/options/kwinoptions_settings.kcfg
index bc54b520928..35c4a78ca59 100644
--- a/src/kcms/options/kwinoptions_settings.kcfg
+++ b/src/kcms/options/kwinoptions_settings.kcfg
@@ -135,6 +135,10 @@
         <default>true</default>
     </entry>
 
+    <entry key="MouseFollowsFocus" type="Bool">
+        <default>false</default>
+    </entry>
+
     <entry key="SeparateScreenFocus" type="Bool">
         <default>false</default>
     </entry>
diff --git a/src/kwin.kcfg b/src/kwin.kcfg
index 00f9ee23660..48a9f2da626 100644
--- a/src/kwin.kcfg
+++ b/src/kwin.kcfg
@@ -97,6 +97,9 @@
         <entry name="NextFocusPrefersMouse" type="Bool">
             <default>false</default>
         </entry>
+        <entry name="MouseFollowsFocus" type="Bool">
+            <default>false</default>
+        </entry>
         <entry name="SeparateScreenFocus" type="Bool">
             <default>false</default>
         </entry>
diff --git a/src/options.cpp b/src/options.cpp
index 3ee7d3f93f4..106a6c4aafc 100644
--- a/src/options.cpp
+++ b/src/options.cpp
@@ -42,6 +42,7 @@ Options::Options(QObject *parent)
     , m_delayFocusInterval(0)
     , m_shadeHover(false)
     , m_shadeHoverInterval(0)
+    , m_mouseFollowsFocus(false)
     , m_separateScreenFocus(false)
     , m_activeMouseScreen(false)
     , m_placement(PlacementNone)
@@ -230,6 +231,15 @@ void Options::setShadeHoverInterval(int shadeHoverInterval)
     Q_EMIT shadeHoverIntervalChanged();
 }
 
+void Options::setMouseFollowsFocus(bool mouseFollowsFocus)
+{
+    if (m_mouseFollowsFocus == mouseFollowsFocus) {
+        return;
+    }
+    m_mouseFollowsFocus = mouseFollowsFocus;
+    Q_EMIT mouseFollowsFocusChanged();
+}
+
 void Options::setSeparateScreenFocus(bool separateScreenFocus)
 {
     if (m_separateScreenFocus == separateScreenFocus) {
@@ -791,6 +801,7 @@ void Options::syncFromKcfgc()
     setCondensedTitle(m_settings->condensedTitle());
     setFocusPolicy(m_settings->focusPolicy());
     setNextFocusPrefersMouse(m_settings->nextFocusPrefersMouse());
+    setMouseFollowsFocus(m_settings->mouseFollowsFocus());
     setSeparateScreenFocus(m_settings->separateScreenFocus());
     setActiveMouseScreen(m_settings->activeMouseScreen());
     setRollOverDesktops(m_settings->rollOverDesktops());
diff --git a/src/options.h b/src/options.h
index 667af178a3d..f8629e5002d 100644
--- a/src/options.h
+++ b/src/options.h
@@ -124,6 +124,10 @@ class KWIN_EXPORT Options : public QObject
      * Shade hover interval.
      */
     Q_PROPERTY(int shadeHoverInterval READ shadeHoverInterval WRITE setShadeHoverInterval NOTIFY shadeHoverIntervalChanged)
+    /**
+    Whether to automatically move the mosue cursor when focus changes
+    */
+    Q_PROPERTY(bool mouseFollowsFocus READ mouseFollowsFocus WRITE setMouseFollowsFocus NOTIFY mouseFollowsFocusChanged)
     /**
      * Whether to see Xinerama screens separately for focus (in Alt+Tab, when activating next client)
      */
@@ -335,6 +339,14 @@ public:
         return m_shadeHoverInterval;
     }
 
+    /**
+    * Whether to automatically move the mouse cursor when focus changes
+    */
+    bool mouseFollowsFocus() const
+    {
+        return m_mouseFollowsFocus;
+    }
+
     /**
      * Whether to see Xinerama screens separately for focus (in Alt+Tab, when activating next client)
      */
@@ -729,6 +741,7 @@ public:
     void setDelayFocusInterval(int delayFocusInterval);
     void setShadeHover(bool shadeHover);
     void setShadeHoverInterval(int shadeHoverInterval);
+    void setMouseFollowsFocus(bool mouseFollowsFocus);
     void setSeparateScreenFocus(bool separateScreenFocus);
     void setActiveMouseScreen(bool activeMouseScreen);
     void setPlacement(PlacementPolicy placement);
@@ -940,6 +953,7 @@ Q_SIGNALS:
     void delayFocusIntervalChanged();
     void shadeHoverChanged();
     void shadeHoverIntervalChanged();
+    void mouseFollowsFocusChanged();
     void separateScreenFocusChanged(bool);
     void activeMouseScreenChanged();
     void placementChanged();
@@ -1004,6 +1018,7 @@ private:
     int m_delayFocusInterval;
     bool m_shadeHover;
     int m_shadeHoverInterval;
+    bool m_mouseFollowsFocus;
     bool m_separateScreenFocus;
     bool m_activeMouseScreen;
     PlacementPolicy m_placement;
diff --git a/src/window.cpp b/src/window.cpp
index 04e2ae584f9..f3271fcea41 100644
--- a/src/window.cpp
+++ b/src/window.cpp
@@ -845,6 +845,7 @@ void Window::setActive(bool act)
     doSetActive();
     Q_EMIT activeChanged();
     updateMouseGrab();
+    workspace()->updateMousePos(this, inputGeometry());
 }
 
 void Window::doSetActive()
@@ -3714,24 +3715,30 @@ void Window::setMoveResizeOutput(Output *output)
 
 void Window::move(const QPointF &point)
 {
+    QRectF prevRect = inputGeometry();
     const QRectF rect = QRectF(point, m_moveResizeGeometry.size());
 
     setMoveResizeGeometry(rect);
     moveResizeInternal(rect, MoveResizeMode::Move);
+    workspace()->updateMousePos(this, m_moveResizeGeometry, prevRect);
 }
 
 void Window::resize(const QSizeF &size)
 {
+    QRectF prevRect = inputGeometry();
     const QRectF rect = QRectF(m_moveResizeGeometry.topLeft(), size);
 
     setMoveResizeGeometry(rect);
     moveResizeInternal(rect, MoveResizeMode::Resize);
+    workspace()->updateMousePos(this, m_moveResizeGeometry, prevRect);
 }
 
 void Window::moveResize(const QRectF &rect)
 {
+    QRectF prevPos = inputGeometry();
     setMoveResizeGeometry(rect);
     moveResizeInternal(rect, MoveResizeMode::MoveResize);
+    workspace()->updateMousePos(this, m_moveResizeGeometry, prevPos);
 }
 
 void Window::setElectricBorderMode(QuickTileMode mode)
diff --git a/src/workspace.cpp b/src/workspace.cpp
index 2f94dbb88bf..05c0838702f 100644
--- a/src/workspace.cpp
+++ b/src/workspace.cpp
@@ -67,6 +67,7 @@
 #include <KStartupInfo>
 // Qt
 #include <QtConcurrentRun>
+#include <QPoint>
 // xcb
 #include <xcb/xinerama.h>
 
@@ -3080,6 +3081,37 @@ void Workspace::setMoveResizeWindow(Window *window)
     }
 }
 
+void Workspace::updateMousePos(Window *window, const QRectF &geo, const QRectF &prevGeo)
+{
+    if (!options->mouseFollowsFocus()) {
+        return;
+    }
+    if (!window->isActive())
+        return;
+    if (!(window->isNormalWindow() || window->isDialog()))
+        return;
+    if (m_moveResizeWindow)
+        return;
+    KWin::Cursor *cursor = Cursors::self()->mouse();
+    QPointF prevPos = cursor->pos();
+    QPointF pos = prevPos;
+    // only move cursor if cursor is not already in window
+    if (!geo.contains(pos)) {
+        if (prevGeo.isEmpty()) {
+            // new or unchanged window: position cursor centered
+            pos = geo.center();
+        } else {
+            // regeometrized window: position cursor in same position relative to previous window geometry
+            float relX = static_cast<float>(prevPos.x() - prevGeo.x()) / prevGeo.width();
+            float relY = static_cast<float>(prevPos.y() - prevGeo.y()) / prevGeo.height();
+            int xp = geo.x() + relX * geo.width();
+            int yp = geo.y() + relY * geo.height();
+            pos = QPointF(xp, yp);
+        }
+        cursor->setPos(pos);
+    }
+}
+
 // When kwin crashes, windows will not be gravitated back to their original position
 // and will remain offset by the size of the decoration. So when restarting, fix this
 // (the property with the size of the frame remains on the window after the crash).
diff --git a/src/workspace.h b/src/workspace.h
index 595c5bb08b8..8c5e067832d 100644
--- a/src/workspace.h
+++ b/src/workspace.h
@@ -21,6 +21,7 @@
 #include <QStringList>
 #include <QTimer>
 #include <QVector>
+#include <QPoint>
 // std
 #include <functional>
 #include <memory>
@@ -422,6 +423,14 @@ public:
     void updateFocusMousePosition(const QPointF &pos);
     QPointF focusMousePosition() const;
 
+    /**
+    Follows the mouse cursor along with the focused window as the window gets activated or regeometrized.
+    @param window The window that thriggered the update
+    @param geo The rectangle to place the mouse into
+    @param prevGeo The previous rectangle the mouse was in
+    ***/
+    void updateMousePos(Window *window, const QRectF &geo, const QRectF &prevGeo = QRect());
+
     /**
      * Returns a window that is currently being moved or resized by the user.
      *



More information about the kde-doc-english mailing list