Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 82 additions & 7 deletions pcsx2-qt/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2837,6 +2837,18 @@ void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main)
}

m_display_surface = new DisplaySurface();

const int monitor_index = Host::GetBaseIntSettingValue("UI", "DisplayMonitor", 0);
const QList<QScreen*> screens = QGuiApplication::screens();
QScreen* target_screen;
if (monitor_index == 1)
target_screen = QGuiApplication::primaryScreen();
//Separator from UI takes up index 2
else if (monitor_index >= 3 && (monitor_index - 3) < screens.size())
target_screen = screens[monitor_index - 3];
else
target_screen = screen();

if (fullscreen || !render_to_main)
{
#ifdef DISPLAY_SURFACE_WINDOW
Expand All @@ -2860,19 +2872,20 @@ void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main)

#ifdef DISPLAY_SURFACE_WINDOW
if (isVisible() && g_emu_thread->shouldRenderToMain())
m_display_surface->setGeometry(screen()->geometry());
m_display_surface->setGeometry(target_screen->geometry());
else
restoreDisplayWindowGeometryFromConfig();
restoreDisplayWindowGeometryFromConfig(target_screen);

if (fullscreen)
m_display_surface->showFullScreen();
else
m_display_surface->showNormal();
#else
m_display_container->setScreen(target_screen);
if (isVisible() && g_emu_thread->shouldRenderToMain())
m_display_container->move(screen()->availableGeometry().topLeft());
m_display_container->move(target_screen->availableGeometry().topLeft());
else
restoreDisplayWindowGeometryFromConfig();
restoreDisplayWindowGeometryFromConfig(target_screen);

if (fullscreen)
m_display_container->showFullScreen();
Expand All @@ -2886,18 +2899,34 @@ void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main)
if (m_is_temporarily_windowed && g_emu_thread->shouldRenderToMain())
m_display_surface->setGeometry(geometry());
else
restoreDisplayWindowGeometryFromConfig();
restoreDisplayWindowGeometryFromConfig(target_screen);
m_display_surface->showNormal();
#else
m_display_container->setScreen(target_screen);
if (m_is_temporarily_windowed && g_emu_thread->shouldRenderToMain())
m_display_container->setGeometry(geometry());
else
restoreDisplayWindowGeometryFromConfig();
restoreDisplayWindowGeometryFromConfig(target_screen);
m_display_container->showNormal();
#endif
}
else
{
if (screen() != target_screen)
{
m_pre_game_main_window_geometry = saveGeometry();

if (!m_target_screen_main_window_geometry.isEmpty())
restoreGeometry(m_target_screen_main_window_geometry);

if (m_target_screen_main_window_geometry.isEmpty() ||
!target_screen->availableGeometry().contains(geometry().center()))
{
const QRect screenGeo = target_screen->availableGeometry();
move(screenGeo.x() + (screenGeo.width() - width()) / 2,
screenGeo.y() + (screenGeo.height() - height()) / 2);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible that the target screen geometry is smaller than the current window geometry? e.g. moving to a smaller screen. In that case, should we clamp width and height to the available screen geometry?
I suggest writing a helper function for this, since the same logic is used two more times in restoreDisplayWindowGeometryFromConfig().

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. We have to clamp slightly a little bit more than the available screen geometry though. availableGeometry() doesn't take to account the title bar, at least on windows, so it's possible it gets cut off. I need to use frameGeometry() to subtract the difference.

}
}
pxAssertRel(m_ui.mainContainer->count() == 1, "Has no display widget");
m_ui.mainContainer->addWidget(m_display_container);
m_ui.mainContainer->setCurrentIndex(1);
Expand Down Expand Up @@ -3005,6 +3034,24 @@ void MainWindow::destroyDisplayWidget(bool show_game_list)
{
pxAssertRel(m_ui.mainContainer->indexOf(m_display_container) == 1, "Display widget in stack");
m_ui.mainContainer->removeWidget(m_display_container);
if (!m_pre_game_main_window_geometry.isEmpty())
{
const int monitor_index = Host::GetBaseIntSettingValue("UI", "DisplayMonitor", 0);
const QList<QScreen*> screens = QGuiApplication::screens();
QScreen* target_screen;
if (monitor_index == 1)
target_screen = QGuiApplication::primaryScreen();
else if (monitor_index >= 3 && (monitor_index - 3) < screens.size())
target_screen = screens[monitor_index - 3];
else
target_screen = nullptr;
if (target_screen && screen() == target_screen)
m_target_screen_main_window_geometry = saveGeometry();
else
m_target_screen_main_window_geometry.clear();
restoreGeometry(m_pre_game_main_window_geometry);
m_pre_game_main_window_geometry.clear();
}
if (show_game_list)
{
m_ui.mainContainer->setCurrentIndex(0);
Expand Down Expand Up @@ -3116,10 +3163,11 @@ void MainWindow::saveDisplayWindowGeometryToConfig()
}
}

void MainWindow::restoreDisplayWindowGeometryFromConfig()
void MainWindow::restoreDisplayWindowGeometryFromConfig(QScreen* target_screen)
{
const std::string geometry_b64 = Host::GetBaseStringSettingValue("UI", "DisplayWindowGeometry");
const QByteArray geometry = QByteArray::fromBase64(QByteArray::fromStdString(geometry_b64));

if (!geometry.isEmpty())
{
m_display_surface->restoreGeometry(geometry);
Expand All @@ -3138,6 +3186,28 @@ void MainWindow::restoreDisplayWindowGeometryFromConfig()
m_display_surface->resize(640, 480);
#else
m_display_container->resize(640, 480);
#endif
}

// if a target screen is specified and the window is not on it, center on it
if (target_screen)
{
#ifdef DISPLAY_SURFACE_WINDOW
if (!target_screen->availableGeometry().contains(m_display_surface->geometry()))
{
const QRect screenGeo = target_screen->availableGeometry();
const QPoint center(screenGeo.x() + (screenGeo.width() - m_display_surface->width()) / 2,
screenGeo.y() + (screenGeo.height() - m_display_surface->height()) / 2);
m_display_surface->setGeometry(QRect(center, m_display_surface->size()));
}
#else
if (!target_screen->availableGeometry().contains(m_display_container->geometry()))
{
const QRect screenGeo = target_screen->availableGeometry();
const QPoint center(screenGeo.x() + (screenGeo.width() - m_display_container->width()) / 2,
screenGeo.y() + (screenGeo.height() - m_display_container->height()) / 2);
m_display_container->setGeometry(QRect(center, m_display_container->size()));
}
#endif
}
}
Expand Down Expand Up @@ -3165,6 +3235,11 @@ void MainWindow::doSettings(const char* category /* = nullptr */)
if (!dlg->isVisible())
{
dlg->show();
if (dlg->screen() != screen())
{
const QRect screenGeo = screen()->availableGeometry();
dlg->move(screenGeo.center() - QPoint(dlg->frameGeometry().width() / 2, dlg->frameGeometry().height() / 2));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe slightly easier to use: dlg->frameGeometry().moveCenter(screenGeo.center())

Also it's probably(?) more efficient to do this before calling show(), unless the dialog is required to be visible to get the correct frame geometry.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try it and see it if it works.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dlg->frameGeometry().moveCenter(screenGeo.center())

dlg->frameGeometry() returns a copy of Qrect of the settings window so calling moveCenter() on it won't move the settings window directly.

unless the dialog is required to be visible to get the correct frame geometry

This part is correct, but it's more about the correct frame geometry than visibility. I looked into this a bit more. Before show() is called, frameGeometry() pretty much equals geometry() because the windows manager hasn't decorated the final window so the final size hasn't been determined yet. So if show() is called after frameGeometry(), frameGeometry() will be missing the title bar and borders and our calculations will be wrong. That's why I had to move some showNormals() to get clamping to work correctly.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dlg->frameGeometry().moveCenter(screenGeo.center())

dlg->frameGeometry() returns a copy of Qrect of the settings window so calling moveCenter() on it won't move the settings window directly.

The line I wrote was just meant as a hint to convey my idea, it's not the full code. Of course you still need to call move() or setGeometry().

}
}
else
{
Expand Down
4 changes: 3 additions & 1 deletion pcsx2-qt/MainWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ private Q_SLOTS:

QWidget* getContentParent();
void saveDisplayWindowGeometryToConfig();
void restoreDisplayWindowGeometryFromConfig();
void restoreDisplayWindowGeometryFromConfig(QScreen* target_screen = nullptr);
void createDisplayWidget(bool fullscreen, bool render_to_main);
void destroyDisplayWidget(bool show_game_list);
void updateDisplayWidgetCursor();
Expand Down Expand Up @@ -348,6 +348,8 @@ private Q_SLOTS:
bool m_was_disc_change_request = false;
bool m_is_closing = false;
bool m_is_temporarily_windowed = false;
QByteArray m_pre_game_main_window_geometry;
QByteArray m_target_screen_main_window_geometry;

QString m_last_fps_status;

Expand Down
16 changes: 16 additions & 0 deletions pcsx2-qt/Settings/InterfaceSettingsWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "QtHost.h"

#include <QtCore/QLocale>
#include <QtGui/QScreen>

const char* InterfaceSettingsWidget::THEME_NAMES[] = {
QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Native"),
Expand Down Expand Up @@ -123,6 +124,17 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog
{
m_ui.mouseLock->setEnabled(false);
}
m_ui.displayMonitor->addItem(tr("Current Monitor"));
m_ui.displayMonitor->addItem(tr("Primary Monitor"));
m_ui.displayMonitor->insertSeparator(m_ui.displayMonitor->count());
const QList<QScreen*> screens = QGuiApplication::screens();
for (int i = 0; i < screens.size(); i++)
m_ui.displayMonitor->addItem(tr("Monitor %1: %2").arg(i + 1).arg(screens[i]->name()));
m_ui.displayMonitor->setCurrentIndex(Host::GetBaseIntSettingValue("UI", "DisplayMonitor", 0));
connect(m_ui.displayMonitor, &QComboBox::currentIndexChanged, this, [](int index) {
Host::SetBaseIntSettingValue("UI", "DisplayMonitor", index);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the combobox index as persistent screen identifier seems unreliable to me. The documentation of QGuiApplication::screens() doesn't guarantee a consistent ordering of the returned list. Even if the order was stable, what happens if I have three screens connected, select the second screen, then unplug it? Won't the wrong screen be used then?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently if an invalid index is used, the current monitor will be used so we don't have to worry about crashes. There are QT signals for display add and remove so we can have the interface settings update in real time whenever a monitor is connected or disconnected. I haven't pushed that yet, but it seems to be working fine so far.

But it is true, there is no guarantee the ordering will stable. We can use the names of the displays... but that won't work in case someone has multiple monitors of the same name. The monitors have to be sorted in a consistent, deterministic order. My idea right now is geometry position, but I haven't tested yet.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently if an invalid index is used, the current monitor will be used so we don't have to worry about crashes.

I wasn't worried about crashing (the code is already robust against that) but about the wrong monitor being used, e.g., when the index is still valid but refers to a different monitor because the screens() list changed.

We can use the names of the displays... but that won't work in case someone has multiple monitors of the same name. The monitors have to be sorted in a consistent, deterministic order. My idea right now is geometry position, but I haven't tested yet.

There is QScreen::serialNumber but I haven't checked what it returns and whether it's reliable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QScreen::serialNumber doesn't work consistently so I went with sorting displays by geometry positions, so the order is always consistent. If a user rearranges their monitors in their OS, a game may launch on an unexpected display, but the interface settings will still reflect the correct index, and they can correct it from there.

Host::CommitBaseSettingChanges();
});

SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.startFullscreen, "UI", "StartFullscreen", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.doubleClickTogglesFullscreen, "UI", "DoubleClickTogglesFullscreen", true);
Expand Down Expand Up @@ -203,6 +215,10 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog
tr("Checked"), tr("Displays a modal dialog when a save state load/save operation fails."));
dialog()->registerWidgetHelp(m_ui.preferEnglishGameList, tr("Prefer English Game Titles"), tr("Unchecked"),
tr("For games with both a title in the game's native language and one in English, prefer the English title. Affects how game titles are displayed on the game list, window title and Discord Presence"));
dialog()->registerWidgetHelp(
m_ui.displayMonitor, tr("Display Monitor"), tr("Current Monitor"),
tr("Selects which monitor the PCSX2 game window will be displayed on when launching."
"<br><b>On Linux Wayland, this setting only takes effect when Start Fullscreen is enabled.</b>"));
dialog()->registerWidgetHelp(m_ui.startFullscreen, tr("Start Fullscreen"), tr("Unchecked"),
tr("Automatically switches to fullscreen mode when a game is started."));
dialog()->registerWidgetHelp(m_ui.hideMouseCursor, tr("Hide Cursor In Fullscreen"), tr("Unchecked"),
Expand Down
14 changes: 14 additions & 0 deletions pcsx2-qt/Settings/InterfaceSettingsWidget.ui
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,19 @@
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="displayMonitorLabel">
<property name="text">
<string>Display Monitor:</string>
</property>
<property name="buddy">
<cstring>displayMonitor</cstring>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="displayMonitor"/>
</item>
</layout>
</widget>
</item>
Expand Down Expand Up @@ -411,6 +424,7 @@
<tabstop>disableWindowResizing</tabstop>
<tabstop>hideMouseCursor</tabstop>
<tabstop>startFullscreenUI</tabstop>
<tabstop>displayMonitor</tabstop>
<tabstop>language</tabstop>
<tabstop>theme</tabstop>
<tabstop>backgroundBrowse</tabstop>
Expand Down
Loading