Skip to content

Commit 6c0429d

Browse files
committed
feat(RpiConnect/Windows): Receive rpi-imager callback without UAC prompt
Signed-off-by: paulober <[email protected]>
1 parent b71988d commit 6c0429d

File tree

5 files changed

+84
-47
lines changed

5 files changed

+84
-47
lines changed

src/main.cpp

Lines changed: 15 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@
3737
#ifdef Q_OS_WIN
3838
#include <windows.h>
3939
#include <winnls.h>
40-
#include <QLocalServer>
41-
#include <QLocalSocket>
40+
#include <QTcpServer>
41+
#include <QTcpSocket>
4242
#endif
4343
#include "imageadvancedoptions.h"
4444

@@ -55,6 +55,7 @@ static QTextStream cerr(stderr);
5555
static void consoleMsgHandler(QtMsgType, const QMessageLogContext &, const QString &str) {
5656
cerr << str << endl;
5757
}
58+
const quint16 kPort = 49629;
5859
#endif
5960

6061

@@ -311,55 +312,25 @@ int main(int argc, char *argv[])
311312
}
312313

313314
#ifdef Q_OS_WIN
314-
// ---- single-instance + URL forwarding ----
315-
// Build a stable per-user name by hashing the per-user AppData path.
316-
// This is consistent across elevated/non-elevated tokens for the same user.
317-
auto base = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
318-
if (base.isEmpty()) base = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
319-
QByteArray hash = QCryptographicHash::hash(base.toUtf8(), QCryptographicHash::Sha1);
320-
const QString serverName = QStringLiteral("rpi-imager-urlpipe-%1").arg(QString::fromLatin1(hash.toHex()));
321-
qDebug() << "Using IPC Server name:" << serverName;
322-
323-
// First, try to connect to an existing instance (client-first).
324-
{
325-
QLocalSocket probe;
326-
probe.connectToServer(serverName);
327-
if (probe.waitForConnected(150)) {
328-
// A primary instance exists; forward the URL (if any) and exit.
329-
if (!callbackUrl.isEmpty()) {
330-
const QByteArray msg = callbackUrl.toString(QUrl::FullyEncoded).toUtf8();
331-
probe.write(msg);
332-
probe.flush();
333-
probe.waitForBytesWritten(200);
334-
}
335-
return 0; // secondary: do not open another window
336-
}
337-
}
338-
339-
// No instance reachable. Become the server.
340-
// Remove stale endpoint left by a crash (safe even if it doesn’t exist).
341-
QLocalServer::removeServer(serverName);
342-
343-
static QLocalServer server; // must outlive the lambda
344-
if (!server.listen(serverName)) {
345-
// As a last resort: if listen still fails, we’re better off continuing as a single instance.
346-
qWarning() << "Failed to listen on" << serverName << ":" << server.errorString();
347-
} else {
348-
QObject::connect(&server, &QLocalServer::newConnection, &app, [&imageWriter]() {
349-
while (QLocalSocket *s = server.nextPendingConnection()) {
350-
s->waitForReadyRead(1000);
315+
// callback server
316+
QTcpServer server;
317+
QObject::connect(&server, &QTcpServer::newConnection, &app, [&]() {
318+
while (auto *s = server.nextPendingConnection()) {
319+
QObject::connect(s, &QTcpSocket::readyRead, s, [s, &imageWriter]() {
351320
const QByteArray payload = s->readAll();
352-
s->close();
353-
s->deleteLater();
321+
s->disconnectFromHost();
354322
QMetaObject::invokeMethod(
355323
&imageWriter,
356324
[payload, &imageWriter] {
357325
imageWriter.handleIncomingUrl(QUrl(QString::fromUtf8(payload)));
358326
},
359327
Qt::QueuedConnection
360-
);
361-
}
362-
});
328+
);
329+
});
330+
}
331+
});
332+
if (!server.listen(QHostAddress::LocalHost, kPort)) {
333+
qWarning() << "TCP listen failed:" << server.errorString();
363334
}
364335
#endif
365336

src/windows/PlatformPackaging.cmake

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ add_custom_command(TARGET ${PROJECT_NAME}
6464
POST_BUILD
6565
COMMAND ${CMAKE_COMMAND} -E copy
6666
"${CMAKE_BINARY_DIR}/${PROJECT_NAME}.exe"
67-
"${CMAKE_SOURCE_DIR}/../license.txt" "${CMAKE_SOURCE_DIR}/windows/rpi-imager-cli.cmd"
67+
"${CMAKE_SOURCE_DIR}/../license.txt"
68+
"${CMAKE_SOURCE_DIR}/windows/rpi-imager-cli.cmd"
69+
"${CMAKE_SOURCE_DIR}/windows/RpiImagerCallbackRelay.ps1"
6870
"${CMAKE_BINARY_DIR}/deploy")
6971

7072
add_custom_command(TARGET ${PROJECT_NAME}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
param([string]$url)
2+
3+
# --- Configuration ---
4+
$port = 49629 # must match the port used by imager
5+
# ---------------------
6+
7+
function Send-Url {
8+
param([string]$u)
9+
try {
10+
$client = New-Object System.Net.Sockets.TcpClient
11+
$iar = $client.BeginConnect('127.0.0.1', $port, $null, $null)
12+
$wait = $iar.AsyncWaitHandle.WaitOne(300) # 300 ms connect timeout
13+
if (-not $wait) {
14+
$client.Close()
15+
return $false
16+
}
17+
$client.EndConnect($iar)
18+
19+
$stream = $client.GetStream()
20+
$bytes = [System.Text.Encoding]::UTF8.GetBytes($u)
21+
$stream.Write($bytes, 0, $bytes.Length)
22+
$stream.Flush()
23+
$stream.Close()
24+
$client.Close()
25+
return $true
26+
} catch {
27+
return $false
28+
}
29+
}
30+
31+
if (Send-Url $url) {
32+
exit 0
33+
}
34+
35+
try {
36+
$exe = Join-Path $PSScriptRoot "rpi-imager.exe"
37+
if (Test-Path -LiteralPath $exe) {
38+
Start-Process -FilePath $exe -ArgumentList @("$url") -WorkingDirectory $PSScriptRoot
39+
exit 0
40+
} else {
41+
Add-Type -AssemblyName System.Windows.Forms
42+
$title = "Raspberry Pi Imager"
43+
$message = "Could not find Raspberry Pi Imager. Please start the application manually and try again."
44+
[System.Windows.Forms.MessageBox]::Show($message, $title, [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error) | Out-Null
45+
exit 2
46+
}
47+
} catch {
48+
# show an error dialog
49+
Add-Type -AssemblyName System.Windows.Forms
50+
$title = "Raspberry Pi Imager"
51+
$message = "Raspberry Pi Imager is not running. Please start the application manually and try again."
52+
[System.Windows.Forms.MessageBox]::Show($message, $title, [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error) | Out-Null
53+
exit 2
54+
}

src/windows/platformquirks_windows.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,11 +165,20 @@ void applyQuirks() {
165165
if (hasNvidiaGraphicsCard()) {
166166
SetEnvironmentVariableA("QSG_RHI_PREFER_SOFTWARE_RENDERER", "1");
167167
}
168+
169+
// make imager single instance because of rpi-connect callback server
170+
// will be automatically released once the process exits cleanly or crashes
171+
HANDLE hMutex = CreateMutexW(nullptr, TRUE, L"Global\\RaspberryPiImagerMutex");
172+
if (GetLastError() == ERROR_ALREADY_EXISTS) {
173+
// Another instance running
174+
MessageBoxW(nullptr, L"Raspberry Pi Imager is already running.", L"Raspberry Pi Imager", MB_OK | MB_ICONINFORMATION);
175+
exit(0);
176+
}
168177
}
169178

170179
void beep() {
171180
// Use Windows MessageBeep for system beep sound
172181
MessageBeep(MB_OK);
173182
}
174183

175-
} // namespace PlatformQuirks
184+
} // namespace PlatformQuirks

src/windows/rpi-imager.iss.in

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,14 @@ Root: HKLM; Subkey: "Software\Classes\RPI_IMAGINGUTILITY\shell\open\command"; Va
6666
Root: HKCR; Subkey: "rpi-imager"; ValueType: string; ValueName: ""; ValueData: "URL:Raspberry Pi Imager"; Flags: uninsdeletekey
6767
Root: HKCR; Subkey: "rpi-imager"; ValueType: string; ValueName: "URL Protocol"; ValueData: ""; Flags: uninsdeletekey
6868
Root: HKCR; Subkey: "rpi-imager\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#MyAppExeName},1"; Flags: uninsdeletekey
69-
Root: HKCR; Subkey: "rpi-imager\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; Flags: uninsdeletekey
69+
Root: HKCR; Subkey: "rpi-imager\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """powershell.exe"" -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File ""{app}\RpiImagerCallbackRelay.ps1"" ""%1"""; Flags: uninsdeletekey
7070

7171
[Files]
7272
; Main program files
7373
Source: "@CMAKE_BINARY_DIR@\deploy\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion signonce
7474

7575
Source: "@CMAKE_BINARY_DIR@\deploy\rpi-imager-cli.cmd"; DestDir: "{app}"; Flags: ignoreversion
76+
Source: "@CMAKE_BINARY_DIR@\deploy\RpiImagerCallbackRelay.ps1"; DestDir: "{app}"; Flags: ignoreversion
7677
Source: "@CMAKE_BINARY_DIR@\deploy\license.txt"; DestDir: "{app}"; Flags: ignoreversion
7778

7879
; Core DLLs

0 commit comments

Comments
 (0)