mirror of
https://github.com/Chatterino/chatterino2.git
synced 2026-08-24 10:04:53 -05:00
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:
@@ -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
|
||||
|
||||
@@ -106,6 +106,7 @@ struct WindowDescriptor {
|
||||
State state_ = State::None;
|
||||
|
||||
QRect geometry_;
|
||||
std::optional<size_t> popupID;
|
||||
|
||||
std::vector<TabDescriptor> tabs_;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_{};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user