#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "olliedbusclient.h" #include "thememanager.h" #include "chatblockmodel.h" class GuiActivator : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.ollie.Gui") public: explicit GuiActivator(QQmlApplicationEngine *engine, QObject *parent = nullptr) : QObject(parent) , m_engine(engine) { } void setWindowUrl(const QUrl &url) { m_url = url; } public slots: void Activate() { const auto roots = m_engine->rootObjects(); if (roots.isEmpty()) return; auto *window = qobject_cast(roots.last()); if (!window) return; window->show(); window->raise(); window->requestActivate(); } void NewWindow() { if (m_url.isEmpty()) return; m_engine->load(m_url); } void SelectSession(const QString &id) { auto *dbus = qobject_cast( m_engine->rootContext()->contextProperty("dbus").value()); if (dbus) dbus->setActiveSessionId(id); Activate(); } private: QQmlApplicationEngine *m_engine; QUrl m_url; }; static QIcon makeBrainIcon() { // Use the theme icon if available (from hicolor scalable/apps/ollie.svg) if (QIcon::hasThemeIcon("ollie")) return QIcon::fromTheme("ollie"); // Fallback: render FA6 brain glyph from installed font QFont fa("Font Awesome 6 Free Solid"); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) if (!QFontDatabase::hasFamily("Font Awesome 6 Free Solid")) { #else if (!QFontDatabase().families().contains("Font Awesome 6 Free Solid")) { #endif fa = QFont("Font Awesome 6 Free"); fa.setWeight(QFont::Black); } QIcon icon; for (int size : {16, 22, 32, 48, 64, 128}) { QPixmap pix(size, size); pix.fill(Qt::transparent); QPainter p(&pix); fa.setPixelSize(size - 2); p.setFont(fa); p.setPen(QColor(0xe9, 0x1e, 0x63)); p.drawText(pix.rect(), Qt::AlignCenter, QString(QChar(0xf5dc))); p.end(); icon.addPixmap(pix); } return icon; } int main(int argc, char *argv[]) { // HiDPI support #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); #endif QApplication::setHighDpiScaleFactorRoundingPolicy( Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); QApplication app(argc, argv); app.setApplicationName("ollie-gui"); app.setDesktopFileName("org.ollie.gui"); app.setOrganizationName("ollie"); app.setWindowIcon(makeBrainIcon()); // Don't force org.kde.desktop style — it ignores explicit palette overrides. // Let the platform theme provide the style, or fall back to Basic which // properly respects the QML palette {} block. if (qEnvironmentVariableIsEmpty("QT_QUICK_CONTROLS_STYLE")) QQuickStyle::setStyle("Basic"); // Theme manager — reads KDE color schemes, applies palette auto *theme = new ThemeManager(&app); QQmlApplicationEngine engine; // Only one GUI may run per desktop session. A second invocation asks the // existing process to show and activate its window, then exits. constexpr auto guiService = "org.ollie.Gui"; constexpr auto guiPath = "/org/ollie/Gui"; GuiActivator activator(&engine, &app); QDBusConnection sessionBus = QDBusConnection::sessionBus(); sessionBus.registerObject(guiPath, &activator, QDBusConnection::ExportAllSlots); if (!sessionBus.registerService(guiService)) { QDBusInterface runningGui(guiService, guiPath, guiService, sessionBus); runningGui.call(QDBus::NoBlock, QStringLiteral("NewWindow")); return 0; } // Register types for QML instantiation qmlRegisterType("org.ollie.gui", 1, 0, "ChatBlockModel"); // Register OllieDBusClient as a singleton auto *dbus = new OllieDBusClient(&app); auto *chatModel = new ChatBlockModel(&app); engine.rootContext()->setContextProperty("dbus", dbus); engine.rootContext()->setContextProperty("theme", theme); engine.rootContext()->setContextProperty("chatModel", chatModel); engine.rootContext()->setContextProperty("systemTrayAvailable", QSystemTrayIcon::isSystemTrayAvailable()); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) const QUrl url(QStringLiteral("qrc:/gui/main.qml")); #else const QUrl url(QStringLiteral("qrc:/main.qml")); #endif QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); activator.setWindowUrl(url); engine.load(url); // --- System tray icon (shown when close-to-tray is enabled) --- auto *trayIcon = new QSystemTrayIcon(&app); trayIcon->setIcon(app.windowIcon()); trayIcon->setToolTip("Ollie"); auto *trayMenu = new QMenu(); auto *showAction = trayMenu->addAction("Show"); trayMenu->addSeparator(); auto *quitAction = trayMenu->addAction("Quit"); trayIcon->setContextMenu(trayMenu); // Toggle window visibility on tray icon activation QObject::connect(trayIcon, &QSystemTrayIcon::activated, [&engine, &activator](QSystemTrayIcon::ActivationReason reason) { if (reason != QSystemTrayIcon::Trigger) return; auto roots = engine.rootObjects(); if (roots.isEmpty()) return; auto *window = qobject_cast(roots.last()); if (!window) return; if (window->isVisible()) { window->hide(); } else { activator.Activate(); } }); QObject::connect(showAction, &QAction::triggered, [&activator]() { activator.Activate(); }); QObject::connect(quitAction, &QAction::triggered, &app, &QApplication::quit); // Show/hide tray icon based on setting auto updateTrayVisibility = [trayIcon, theme]() { trayIcon->setVisible(theme->closeToTray()); }; updateTrayVisibility(); QObject::connect(theme, &ThemeManager::closeToTrayChanged, updateTrayVisibility); return app.exec(); } #include "main.moc"