Skip to content

Commit

Permalink
Simplify window geometry restoring
Browse files Browse the repository at this point in the history
This changes tries to simplify and improve behavior of window geometry
restore/save functionality.

The main change is that geometry is changed only initially and when
current screen changes. The current screen is indicated by mouse
pointer/cursor position and is applied to the window when the window
"Show" event is triggered. In case the current mouse pointer position
cannot be inspected (for example on some Wayland compositors), it is
left up to the window manager whether it decides to move the window to
another screen (app listens to `QWindow::screenChanged` signals).

This also adds a workaround for resizing window on Sway window
compositor without breaking the window's central position (window
position cannot be set in Qt on most or all Wayland compositors).
  • Loading branch information
hluk committed Feb 26, 2022
1 parent ce2b815 commit fc30712
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 73 deletions.
84 changes: 20 additions & 64 deletions src/common/config.cpp
Expand Up @@ -42,19 +42,6 @@ const char propertyGeometryLockedUntilHide[] = "CopyQ_geometry_locked_until_hide
constexpr int windowMinWidth = 50;
constexpr int windowMinHeight = 50;

enum class GeometryAction {
Save,
Restore
};

bool isMousePositionSupported()
{
// On Wayland, getting mouse position can return
// the last known mouse position in an own Qt application window.
static const bool supported = !QCursor::pos().isNull();
return supported;
}

QString toString(const QRect &geometry)
{
return QString::fromLatin1("%1x%2,%3,%4")
Expand All @@ -64,16 +51,8 @@ QString toString(const QRect &geometry)
.arg(geometry.y());
}

int screenNumber(const QWidget &widget, GeometryAction geometryAction)
int screenNumber(const QWidget &widget)
{
if (geometryAction == GeometryAction::Restore) {
if ( !isMousePositionSupported() )
return -1;
const int n = screenNumberAt(QCursor::pos());
if (n != -1)
return n;
}

QWindow *windowHandle = widget.windowHandle();
if (windowHandle) {
QScreen *screen = windowHandle->screen();
Expand All @@ -89,14 +68,14 @@ QString geometryOptionName(const QWidget &widget)
return QString::fromLatin1("Options/%1_geometry").arg(widget.objectName());
}

QString geometryOptionName(const QWidget &widget, GeometryAction geometryAction, bool openOnCurrentScreen)
QString geometryOptionName(const QWidget &widget, bool openOnCurrentScreen)
{
const QString baseGeometryName = geometryOptionName(widget);

if (!openOnCurrentScreen)
return QString::fromLatin1("%1_global").arg(baseGeometryName);

const int n = screenNumber(widget, geometryAction);
const int n = screenNumber(widget);
if (n > 0)
return QString::fromLatin1("%1_screen_%2").arg(baseGeometryName).arg(n);

Expand All @@ -116,10 +95,10 @@ QString resolutionTagForScreen(int i)
.arg(screenGeometry.height());
}

QString resolutionTag(const QWidget &widget, GeometryAction geometryAction, bool openOnCurrentScreen)
QString resolutionTag(const QWidget &widget, bool openOnCurrentScreen)
{
if (openOnCurrentScreen) {
const int i = screenNumber(widget, geometryAction);
const int i = screenNumber(widget);
if (i == -1)
return QString();
return resolutionTagForScreen(i);
Expand All @@ -132,15 +111,15 @@ QString resolutionTag(const QWidget &widget, GeometryAction geometryAction, bool
return tag;
}

void ensureWindowOnScreen(QWidget *widget, QPoint pos)
void ensureWindowOnScreen(QWidget *widget)
{
const QSize size = widget->frameSize();
int x = pos.x();
int y = pos.y();
int w = qMax(windowMinWidth, size.width());
int h = qMax(windowMinHeight, size.height());
const QRect frame = widget->frameGeometry();
int x = widget->x();
int y = widget->y();
int w = qMax(windowMinWidth, frame.width());
int h = qMax(windowMinHeight, frame.height());

const QRect availableGeometry = screenAvailableGeometry(pos);
const QRect availableGeometry = screenAvailableGeometry(*widget);
if ( availableGeometry.isValid() ) {
// Ensure that the window fits the screen, otherwise it would be moved
// to a neighboring screen automatically.
Expand All @@ -160,7 +139,7 @@ void ensureWindowOnScreen(QWidget *widget, QPoint pos)
y = availableGeometry.top();
}

if ( size != QSize(w, h) ) {
if ( frame.size() != QSize(w, h) ) {
GEOMETRY_LOG( widget, QString::fromLatin1("Resize window: %1x%2").arg(w).arg(h) );
widget->resize(w, h);
}
Expand All @@ -171,12 +150,6 @@ void ensureWindowOnScreen(QWidget *widget, QPoint pos)
}
}

void ensureWindowOnScreen(QWidget *w)
{
const QPoint pos = w->pos();
ensureWindowOnScreen(w, pos);
}

QString getConfigurationFilePathHelper()
{
const QSettings settings(
Expand Down Expand Up @@ -225,8 +198,8 @@ void setGeometryOptionValue(const QString &optionName, const QVariant &value)

void restoreWindowGeometry(QWidget *w, bool openOnCurrentScreen)
{
const QString optionName = geometryOptionName(*w, GeometryAction::Restore, openOnCurrentScreen);
const QString tag = resolutionTag(*w, GeometryAction::Restore, openOnCurrentScreen);
const QString optionName = geometryOptionName(*w, openOnCurrentScreen);
const QString tag = resolutionTag(*w, openOnCurrentScreen);
QByteArray geometry = geometryOptionValue(optionName + tag).toByteArray();

// If geometry for screen resolution doesn't exist, use last saved one.
Expand All @@ -236,24 +209,7 @@ void restoreWindowGeometry(QWidget *w, bool openOnCurrentScreen)

// If geometry for the screen doesn't exist, move window to the middle of the screen.
if (geometry.isEmpty()) {
const QRect availableGeometry = screenAvailableGeometry(w->pos());
if ( availableGeometry.isValid() ) {
const QPoint position = availableGeometry.center() - w->rect().center();
w->move(position);
}
}
}

if (openOnCurrentScreen) {
const int screenNumber = ::screenNumber(*w, GeometryAction::Restore);
QScreen *screen = QGuiApplication::screens().value(screenNumber);
if (screen) {
// WORKAROUND: Fixes QWidget::restoreGeometry() for different monitor scaling.
QWindow *windowHandle = w->windowHandle();
if ( windowHandle && windowHandle->screen() != screen )
windowHandle->setScreen(screen);

const QRect availableGeometry = screen->availableGeometry();
const QRect availableGeometry = screenAvailableGeometry(*w);
if ( availableGeometry.isValid() ) {
const QPoint position = availableGeometry.center() - w->rect().center();
w->move(position);
Expand All @@ -279,8 +235,8 @@ void restoreWindowGeometry(QWidget *w, bool openOnCurrentScreen)

void saveWindowGeometry(QWidget *w, bool openOnCurrentScreen)
{
const QString optionName = geometryOptionName(*w, GeometryAction::Save, openOnCurrentScreen);
const QString tag = resolutionTag(*w, GeometryAction::Save, openOnCurrentScreen);
const QString optionName = geometryOptionName(*w, openOnCurrentScreen);
const QString tag = resolutionTag(*w, openOnCurrentScreen);
QSettings geometrySettings( getGeometryConfigurationFilePath(), QSettings::IniFormat );
const auto geometry = w->saveGeometry();
geometrySettings.setValue(optionName + tag, geometry);
Expand Down Expand Up @@ -312,7 +268,6 @@ void moveToCurrentWorkspace(QWidget *w)
w->hide();
if (blockUntilHide)
setGeometryGuardBlockedUntilHidden(w, true);
w->show();
}
#else
Q_UNUSED(w)
Expand All @@ -321,7 +276,8 @@ void moveToCurrentWorkspace(QWidget *w)

void moveWindowOnScreen(QWidget *w, QPoint pos)
{
ensureWindowOnScreen(w, pos);
w->move(pos);
ensureWindowOnScreen(w);
moveToCurrentWorkspace(w);
}

Expand Down
4 changes: 3 additions & 1 deletion src/common/display.cpp
Expand Up @@ -59,8 +59,10 @@ QPoint toScreen(QPoint pos, QWidget *w)
QWindow *window = w->windowHandle();
if (window)
window->setPosition(pos);
else
w->move(pos);

const QRect availableGeometry = screenAvailableGeometry(pos);
const QRect availableGeometry = screenAvailableGeometry(*w);
if ( !availableGeometry.isValid() )
return pos;

Expand Down
2 changes: 1 addition & 1 deletion src/gui/execmenu.cpp
Expand Up @@ -49,7 +49,7 @@ namespace Utils {
QAction *execMenuAtWidget(QMenu *menu, QWidget *widget)
{
QPoint p;
QRect screen = screenAvailableGeometry(widget->pos());
QRect screen = screenAvailableGeometry(*widget);
QSize sh = menu->sizeHint();
QRect rect = widget->rect();
if (widget->isRightToLeft()) {
Expand Down
9 changes: 7 additions & 2 deletions src/gui/screen.cpp
Expand Up @@ -2,6 +2,8 @@

#include <QApplication>
#include <QScreen>
#include <QWidget>
#include <QWindow>

#if QT_VERSION < QT_VERSION_CHECK(5,11,0)
# include <QDesktopWidget>
Expand Down Expand Up @@ -46,10 +48,13 @@ QRect screenGeometry(int i)
#endif
}

QRect screenAvailableGeometry(const QPoint &pos)
QRect screenAvailableGeometry(const QWidget &w)
{
if ( w.windowHandle() && w.windowHandle()->screen() )
return w.windowHandle()->screen()->availableGeometry();

#if QT_VERSION >= QT_VERSION_CHECK(5,11,0)
auto screen = QGuiApplication::screenAt(pos);
auto screen = QGuiApplication::screenAt(w.pos());
return screen ? screen->availableGeometry() : screenGeometry(0);
#else
return QApplication::desktop()->availableGeometry(pos);
Expand Down
3 changes: 2 additions & 1 deletion src/gui/screen.h
Expand Up @@ -3,13 +3,14 @@

class QPoint;
class QRect;
class QWidget;

int screenCount();

int screenNumberAt(const QPoint &pos);

QRect screenGeometry(int i);

QRect screenAvailableGeometry(const QPoint &pos);
QRect screenAvailableGeometry(const QWidget &w);

#endif // SCREEN_H
80 changes: 76 additions & 4 deletions src/gui/windowgeometryguard.cpp
Expand Up @@ -22,7 +22,9 @@
#include "common/appconfig.h"
#include "common/common.h"
#include "common/config.h"
#include "common/log.h"
#include "common/timer.h"
#include "gui/screen.h"

#include <QApplication>
#include <QEvent>
Expand All @@ -43,6 +45,27 @@ bool isRestoreGeometryEnabled()
return AppConfig().option<Config::restore_geometry>();
}

bool isMousePositionSupported()
{
// On Wayland, getting mouse position can return
// the last known mouse position in an own Qt application window.
static const bool supported = !QCursor::pos().isNull();
return supported;
}

QScreen *currentScreen()
{
if (!isMousePositionSupported())
return nullptr;

const int i = screenNumberAt(QCursor::pos());
const auto screens = QGuiApplication::screens();
if (0 <= i && i < screens.size())
return screens[i];

return nullptr;
}

} // namespace

void WindowGeometryGuard::create(QWidget *window)
Expand All @@ -57,12 +80,24 @@ bool WindowGeometryGuard::eventFilter(QObject *, QEvent *event)
const QEvent::Type type = event->type();

switch (type) {
case QEvent::Show:
if ( !isWindowGeometryLocked() ) {
m_timerSaveGeometry.stop();
m_timerRestoreGeometry.start();
case QEvent::Show: {
m_timerSaveGeometry.stop();

QWindow *window = m_window->windowHandle();
if (window) {
connect( window, &QWindow::screenChanged,
this, &WindowGeometryGuard::onScreenChanged, Qt::UniqueConnection );

if ( !isWindowGeometryLocked() && openOnCurrentScreen() && isMousePositionSupported() ) {
QScreen *screen = currentScreen();
if (screen && window->screen() != screen) {
COPYQ_LOG(QStringLiteral("Geometry: Moving to screen: %1").arg(screen->name()));
m_window->move(screen->availableGeometry().topLeft());
}
}
}
break;
}

case QEvent::Move:
case QEvent::Resize:
Expand Down Expand Up @@ -130,3 +165,40 @@ void WindowGeometryGuard::unlockWindowGeometry()
{
m_timerUnlockGeometry.stop();
}

void WindowGeometryGuard::onScreenChanged()
{
if ( isGeometryGuardBlockedUntilHidden(m_window) )
return;

if (!openOnCurrentScreen())
return;

QWindow *window = m_window->windowHandle();
if (!window)
return;

QScreen *screen = window->screen();
if (!screen)
return;

COPYQ_LOG(QStringLiteral("Geometry: Screen changed: %1").arg(screen->name()));

const bool isMousePositionSupported = ::isMousePositionSupported();
if ( window && isMousePositionSupported && screen != currentScreen() ) {
COPYQ_LOG(QStringLiteral("Geometry: Avoiding geometry-restore on incorrect screen"));
return;
}

setGeometryGuardBlockedUntilHidden(m_window, true);
if (isMousePositionSupported) {
::restoreWindowGeometry(m_window, true);
} else {
// WORKAROUND: Center window position on Sway window compositor which
// does not support changing window position.
m_window->hide();
::restoreWindowGeometry(m_window, true);
m_window->show();
}
setGeometryGuardBlockedUntilHidden(m_window, false);
}
2 changes: 2 additions & 0 deletions src/gui/windowgeometryguard.h
Expand Up @@ -41,6 +41,8 @@ class WindowGeometryGuard final : public QObject
void restoreWindowGeometry();
void unlockWindowGeometry();

void onScreenChanged();

QWidget *m_window;

QTimer m_timerSaveGeometry;
Expand Down

0 comments on commit fc30712

Please sign in to comment.