-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.cpp
More file actions
153 lines (136 loc) · 6.69 KB
/
Copy pathmain.cpp
File metadata and controls
153 lines (136 loc) · 6.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#include <QApplication>
#include <QIcon>
#include <QLocalServer>
#include <QLocalSocket>
#include <QMessageBox>
#include <QSettings>
#include <QSystemTrayIcon>
#include <QTimer>
#include "logging.h"
#include "mainwindow.h"
#include "translations/tsparser.h"
// The version reaches C++ exactly once, here, from CMake's project(VERSION …).
// Everything else in the app reads it back with applicationVersion(), so this is
// the only place it can go stale — and a build that fails to define it stops
// rather than silently reporting a wrong version.
#ifndef TRACKCLICK_VERSION
# error "TRACKCLICK_VERSION must be defined by the build — see CMakeLists.txt."
#endif
// Name of the local socket used as the single-instance lock. Scoped to the
// current user so separate logged-in users each get their own instance and
// can't collide on (or connect to) each other's socket.
static QString singleInstanceKey()
{
QString user = qEnvironmentVariable("USER");
if (user.isEmpty()) user = qEnvironmentVariable("USERNAME");
return "TrackClick-singleinstance-" + user;
}
int main(int argc, char* argv[])
{
#ifdef Q_OS_LINUX
// TrackClick is an X11 application: it injects clicks with XTest, tracks the
// pointer with XQueryPointer, and places the click-indicator overlay at
// global screen coordinates. The native Wayland platform plugin supports
// none of that — it forbids a client from positioning its own top-level
// windows (so the overlay ends up glued to the main window) and does not
// expose XTest. All of it works under XWayland, so prefer the xcb plugin on
// Linux, falling back to Wayland only if X is genuinely unavailable. A user
// who sets QT_QPA_PLATFORM explicitly is always respected.
if (qEnvironmentVariableIsEmpty("QT_QPA_PLATFORM"))
qputenv("QT_QPA_PLATFORM", "xcb;wayland");
#endif
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
// High-DPI is always enabled in Qt6 — no extra setup needed
#endif
QApplication app(argc, argv);
app.setApplicationName("TrackClick");
app.setApplicationVersion(QStringLiteral(TRACKCLICK_VERSION));
app.setOrganizationName("TrackClick");
app.setOrganizationDomain("trackclick.app");
// Start capturing log output before anything else runs. The messages worth
// having are emitted during start-up — above all which pointer-tracking
// backend ClickInjector settled on — so installing this after MainWindow is
// constructed would miss exactly the lines support needs. Must follow the
// application/organisation names: they determine both the settings key it
// reads and the directory it writes to.
logging::install();
// ── Single-instance guard ────────────────────────────────────────────────
// A named local socket acts as the lock. If we can connect to it, another
// TrackClick is already running: ask it to surface its window, then exit.
// Otherwise we become the primary instance and listen for future launches.
const QString instanceKey = singleInstanceKey();
{
QLocalSocket probe;
probe.connectToServer(instanceKey);
if (probe.waitForConnected(300)) {
probe.write("show");
probe.waitForBytesWritten(300);
probe.disconnectFromServer();
return 0; // a primary instance is already running
}
}
// We are the primary instance. Remove any stale socket left by a previous
// crash (otherwise listen() fails on Unix), then start listening.
QLocalServer::removeServer(instanceKey);
QLocalServer instanceServer;
instanceServer.setSocketOptions(QLocalServer::UserAccessOption);
instanceServer.listen(instanceKey);
#ifdef Q_OS_LINUX
// Windows and macOS get their app icon from the embedded .ico / bundle .icns;
// Linux has no embedded equivalent, so set the window icon at runtime (used
// in the task switcher / window list) and tell the desktop environment which
// .desktop entry this window belongs to so GNOME/KDE show its icon in the
// dock and overview (the basename must match the installed TrackClick.desktop
// and the window's WM_CLASS / app_id).
app.setWindowIcon(QIcon(":/icons/app.svg"));
app.setDesktopFileName("TrackClick");
#endif
// Install translator for the saved language before any UI is created.
// The pointer is passed to MainWindow so it can remove it when the user
// switches languages (e.g. back to English); MainWindow takes ownership.
QTranslator* startupTranslator = nullptr;
{
QSettings s("TrackClick", "TrackClick");
const QString lang = s.value("language", "en").toString();
startupTranslator = loadBestTranslator(lang, &app);
if (startupTranslator)
app.installTranslator(startupTranslator);
// Also translate Qt's own built-in widget strings (e.g. QKeySequenceEdit's
// "Press shortcut" placeholder) for the saved language.
installQtBaseTranslator(lang);
}
// Don't quit when last window is hidden (keep tray alive)
app.setQuitOnLastWindowClosed(false);
if (!QSystemTrayIcon::isSystemTrayAvailable()) {
QMessageBox::warning(nullptr,
QCoreApplication::translate("main", "TrackClick"),
QCoreApplication::translate("main",
"No system tray detected. The application will still run,\n"
"but you won't be able to hide it to the tray."));
}
MainWindow w(startupTranslator);
// Honour "start minimized to tray": read the persisted setting before
// showing the window so we never flash it on screen then hide it.
{
QSettings s("TrackClick", "TrackClick");
if (!s.value("window/startMin", false).toBool())
w.show();
}
// When another launch connects to our single-instance socket, surface this
// window (it may be hidden in the tray or minimised) instead of starting a
// second copy. The payload is irrelevant — any connection means "show me".
QObject::connect(&instanceServer, &QLocalServer::newConnection, &w, [&]{
while (QLocalSocket* conn = instanceServer.nextPendingConnection()) {
conn->disconnectFromServer();
conn->deleteLater();
}
w.showNormal(); // de-minimise / un-hide from tray
w.raise();
w.activateWindow();
});
// Once the event loop is running, offer to fix input-device permissions on
// Linux/Wayland if needed so dwell-clicking works over every window. No-op
// on other platforms and when access is already available.
QTimer::singleShot(0, &w, [&w]{ w.promptForInputAccessIfNeeded(); });
return app.exec();
}