This repository has been archived on 2026-08-16. You can view files and clone it, but cannot push or open issues or pull requests.
ollie-kde/gui/main.cpp

201 lines
6.4 KiB
C++

#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQuickStyle>
#include <QIcon>
#include <QPixmap>
#include <QPainter>
#include <QFont>
#include <QFontDatabase>
#include <QSystemTrayIcon>
#include <QMenu>
#include <QAction>
#include <QWindow>
#include <QDBusConnection>
#include <QDBusInterface>
#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)
{
}
public slots:
void Activate()
{
const auto roots = m_engine->rootObjects();
if (roots.isEmpty())
return;
auto *window = qobject_cast<QWindow *>(roots.first());
if (!window)
return;
window->show();
window->raise();
window->requestActivate();
}
private:
QQmlApplicationEngine *m_engine;
};
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("Activate"));
return 0;
}
// Register types for QML instantiation
qmlRegisterType<ChatBlockModel>("org.ollie.private", 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);
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](QSystemTrayIcon::ActivationReason reason) {
if (reason != QSystemTrayIcon::Trigger)
return;
auto roots = engine.rootObjects();
if (roots.isEmpty()) return;
auto *window = qobject_cast<QWindow *>(roots.first());
if (!window) return;
if (window->isVisible()) {
window->hide();
} else {
window->show();
window->raise();
window->requestActivate();
}
});
QObject::connect(showAction, &QAction::triggered, [&engine]() {
auto roots = engine.rootObjects();
if (roots.isEmpty()) return;
auto *window = qobject_cast<QWindow *>(roots.first());
if (!window) return;
window->show();
window->raise();
window->requestActivate();
});
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"