feat: Set window roles for session restoration (#7058)

This sets the window roles for our windows similar to what I described
in #6655. All roles are prefixed with `chatterino.` to avoid collision
in the case where we're embedded.

- Main Window: `chatterino.main`
- Popups: `chatterino.popup.{id}`
- Emote popup: `chatterino.emote-popup`
- Settings: `chatterino.settings`
- Overlay: `chatterino.overlay`

Right now, you need to pass `-session {session-id}_0` when starting, but
I hope future Qt versions add a way to set the session ID after the
`QGuiApplication` is created. Note that because of this, restoration
won't work out of the box. That's expected. You can see the session
status in the logs of `qt.qpa.wayland`.

Tested with KDE 6.7 and Qt 6.12-beta1.

Closes #6655.

Reviewed-by: pajlada <rasmus.karlsson@pajlada.com>
Reviewed-by: Mm2PL <mm2pl+gh@kotmisia.pl>
This commit is contained in:
Nerixyz
2026-07-26 13:24:37 +00:00
committed by GitHub
parent b97457297b
commit 1e5dd7363b
12 changed files with 132 additions and 28 deletions
+8 -1
View File
@@ -202,7 +202,7 @@ WindowLayout WindowLayout::loadFromFile(const QString &path)
// "deserialize"
for (const auto windowVal : loadWindowArray(path))
{
QJsonObject windowObj = windowVal.toObject();
const QJsonObject windowObj = windowVal.toObject();
WindowDescriptor window;
@@ -244,6 +244,13 @@ WindowLayout WindowLayout::loadFromFile(const QString &path)
window.geometry_ = QRect(x, y, width, height);
}
// Load popup ID
auto idVal = windowObj["popupID"];
if (idVal.isDouble())
{
window.popupID = idVal.toInt(1);
}
bool hasSetASelectedTab = false;
// Load window tabs
+1
View File
@@ -106,6 +106,7 @@ struct WindowDescriptor {
State state_ = State::None;
QRect geometry_;
std::optional<size_t> popupID;
std::vector<TabDescriptor> tabs_;
};
+80 -22
View File
@@ -322,18 +322,16 @@ Window *WindowManager::getLastSelectedWindow() const
return this->selectedWindow_;
}
Window &WindowManager::createWindow(WindowType type, bool show, QWidget *parent)
Window &WindowManager::createWindow(WindowType type,
const CreateWindowArgs &args)
{
assertInGuiThread();
auto *const realParent = [this, type, parent]() -> QWidget * {
(void)this;
(void)type;
if (parent)
auto *const realParent = [&]() -> QWidget * {
if (args.parent)
{
// If a parent is explicitly specified, we use that immediately.
return parent;
return args.parent;
}
// FIXME: On Windows, parenting popup windows causes unwanted behavior (see
@@ -355,9 +353,28 @@ Window &WindowManager::createWindow(WindowType type, bool show, QWidget *parent)
}();
auto *window = new Window(type, realParent);
assert(!window->testAttribute(Qt::WA_WState_Created));
switch (type)
{
case WindowType::Main: {
window->setWindowRole(u"chatterino.main"_s);
}
break;
case WindowType::Popup: {
size_t popupID = this->takePopupID(args.popupID);
window->setWindowRole(u"chatterino.popup." %
QString::number(popupID));
window->setPopupID(popupID);
qCDebug(chatterinoWindowmanager)
<< "Creating popup with ID" << popupID;
}
break;
case WindowType::Attached:
break; // No window role for you.
}
this->windows_.push_back(window);
if (show)
if (args.parent)
{
window->show();
}
@@ -366,17 +383,15 @@ Window &WindowManager::createWindow(WindowType type, bool show, QWidget *parent)
{
window->setAttribute(Qt::WA_DeleteOnClose);
QObject::connect(window, &QWidget::destroyed, this, [this, window] {
for (auto it = this->windows_.begin(); it != this->windows_.end();
it++)
{
if (*it == window)
{
this->windows_.erase(it);
break;
}
}
});
auto popupID = window->popupID();
QObject::connect(window, &QWidget::destroyed, this,
[this, window, popupID] {
std::erase(this->windows_, window);
if (popupID)
{
this->closePopup(*popupID);
}
});
}
return *window;
@@ -384,7 +399,9 @@ Window &WindowManager::createWindow(WindowType type, bool show, QWidget *parent)
Window &WindowManager::openInPopup(ChannelPtr channel)
{
auto &popup = this->createWindow(WindowType::Popup, true);
auto &popup = this->createWindow(WindowType::Popup, {
.show = true,
});
auto *split =
popup.getNotebook().getOrAddSelectedPage()->appendNewSplit(false);
split->setChannel(channel);
@@ -459,7 +476,7 @@ void WindowManager::initialize()
// No main window has been created from loading, create an empty one
if (this->mainWindow_ == nullptr)
{
this->mainWindow_ = &this->createWindow(WindowType::Main);
this->mainWindow_ = &this->createWindow(WindowType::Main, {});
this->mainWindow_->getNotebook().addPage(true);
// TODO: don't create main window if it's a frameless embed
@@ -524,6 +541,12 @@ void WindowManager::save()
windowObj.insert("width", rect.width());
windowObj.insert("height", rect.height());
auto popupID = window->popupID();
if (popupID)
{
windowObj.insert("popupID", static_cast<qsizetype>(*popupID));
}
windowObj["emotePopup"] = QJsonObject{
{"x", this->emotePopupBounds_.x()},
{"y", this->emotePopupBounds_.y()},
@@ -783,7 +806,9 @@ void WindowManager::applyWindowLayout(const WindowLayout &layout)
{
auto type = windowData.type_;
Window &window = this->createWindow(type, false);
Window &window = this->createWindow(type, {
.show = false,
});
if (type == WindowType::Main)
{
@@ -881,6 +906,39 @@ void WindowManager::applyWindowLayout(const WindowLayout &layout)
break;
}
}
// We might've opened a few popups, so make sure the next ID is unused.
this->refreshNextPopupID();
}
size_t WindowManager::takePopupID(std::optional<size_t> preferred)
{
size_t id = this->nextPopupID;
if (preferred && !this->usedPopupIDs.contains(*preferred))
{
id = *preferred;
}
assert(!this->usedPopupIDs.contains(id));
this->usedPopupIDs.insert(id);
this->refreshNextPopupID();
return id;
}
void WindowManager::closePopup(size_t id)
{
// The user closed a popup. Remember this ID, so the popup will get this ID.
this->nextPopupID = id;
this->usedPopupIDs.remove(id);
}
void WindowManager::refreshNextPopupID()
{
size_t selected = 1;
while (this->usedPopupIDs.contains(selected))
{
selected += 1;
}
this->nextPopupID = selected;
}
} // namespace chatterino
+15 -2
View File
@@ -102,8 +102,13 @@ public:
// - If the window was unfocused since being selected, this function will still return it.
Window *getLastSelectedWindow() const;
Window &createWindow(WindowType type, bool show = true,
QWidget *parent = nullptr);
struct CreateWindowArgs {
bool show = true;
QWidget *parent = nullptr;
std::optional<size_t> popupID;
};
Window &createWindow(WindowType type, const CreateWindowArgs &args);
// Use this method if you want to open a "new" channel in a popup. If you want to popup an
// existing Split or SplitContainer, consider using Split::popup() or SplitContainer::popup().
@@ -176,6 +181,10 @@ private:
// Apply a window layout for this window manager.
void applyWindowLayout(const WindowLayout &layout);
size_t takePopupID(std::optional<size_t> preferred);
void closePopup(size_t id);
void refreshNextPopupID();
// Contains the full path to the window layout file, e.g. /home/pajlada/.local/share/Chatterino/Settings/window-layout.json
const QString windowLayoutFilePath;
@@ -187,6 +196,10 @@ private:
std::vector<Window *> windows_;
/// ID to be used for the next popup.
size_t nextPopupID = 1;
QSet<size_t> usedPopupIDs;
std::unique_ptr<FramelessEmbedWindow> framelessEmbedWindow_;
Window *mainWindow_{};
Window *selectedWindow_{};
+1
View File
@@ -111,6 +111,7 @@ OverlayWindow::OverlayWindow(IndirectChannel channel,
{
this->setAttribute(Qt::WA_DeleteOnClose);
this->setWindowTitle(u"Chatterino - Overlay"_s);
this->setWindowRole(u"chatterino.overlay"_s);
// QGridLayout is (ab)used to stack widgets and position them
auto *grid = new QGridLayout(this);
+10
View File
@@ -122,6 +122,16 @@ SplitNotebook &Window::getNotebook()
return *this->notebook_;
}
void Window::setPopupID(size_t id)
{
this->popupID_ = id;
}
std::optional<size_t> Window::popupID() const
{
return this->popupID_;
}
bool Window::event(QEvent *event)
{
switch (event->type())
+5
View File
@@ -34,6 +34,9 @@ public:
WindowType getType();
SplitNotebook &getNotebook();
void setPopupID(size_t id);
std::optional<size_t> popupID() const;
pajlada::Signals::NoArgSignal closed;
protected:
@@ -63,6 +66,8 @@ private:
PixmapButton *streamerModeTitlebarIcon_ = nullptr;
void updateStreamerModeIcon();
std::optional<size_t> popupID_;
friend class Notebook;
};
+3
View File
@@ -41,6 +41,8 @@
#include <algorithm>
#include <utility>
using namespace Qt::Literals;
namespace {
using namespace chatterino;
@@ -395,6 +397,7 @@ EmotePopup::EmotePopup(QWidget *parent)
, notebook_(new Notebook(this))
{
// this->setStayInScreenRect(true);
this->setWindowRole(u"chatterino.emote-popup"_s);
auto bounds = getApp()->getWindows()->emotePopupBounds();
if (bounds.size().isEmpty())
{
+3
View File
@@ -30,6 +30,8 @@
#include <QDialogButtonBox>
#include <QLineEdit>
using namespace Qt::Literals;
namespace chatterino {
SettingsDialog::SettingsDialog(QWidget *parent)
@@ -45,6 +47,7 @@ SettingsDialog::SettingsDialog(QWidget *parent)
{
this->setObjectName("SettingsDialog");
this->setWindowTitle("Chatterino Settings");
this->setWindowRole(u"chatterino.settings"_s);
// Disable the ? button in the titlebar until we decide to use it
this->setWindowFlags(this->windowFlags() &
~Qt::WindowContextHelpButtonHint);
+1 -1
View File
@@ -320,7 +320,7 @@ UserInfoPopup::UserInfoPopup(bool closeAutomatically, Split *split)
[loginName] {
auto *app = getApp();
auto &window = app->getWindows()->createWindow(
WindowType::Popup, true);
WindowType::Popup, {});
auto *split = window.getNotebook()
.getOrAddSelectedPage()
->appendNewSplit(false);
+3 -1
View File
@@ -56,6 +56,8 @@
#include <functional>
using namespace Qt::Literals;
namespace chatterino {
namespace {
void showTutorialVideo(QWidget *parent, const QString &source,
@@ -1111,7 +1113,7 @@ void Split::explainSplitting()
void Split::popup()
{
auto *app = getApp();
Window &window = app->getWindows()->createWindow(WindowType::Popup);
Window &window = app->getWindows()->createWindow(WindowType::Popup, {});
auto *split = new Split(window.getNotebook().getOrAddSelectedPage());
+2 -1
View File
@@ -816,7 +816,8 @@ void SplitContainer::applyFromDescriptor(const NodeDescriptor &rootNode)
void SplitContainer::popup()
{
Window &window = getApp()->getWindows()->createWindow(WindowType::Popup);
Window &window =
getApp()->getWindows()->createWindow(WindowType::Popup, {});
auto *popupContainer = window.getNotebook().getOrAddSelectedPage();
QJsonObject encodedTab;