mirror of https://github.com/ospab/ostp.git
Compare commits
2 Commits
b2ee9eb010
...
6361a87072
| Author | SHA1 | Date |
|---|---|---|
|
|
6361a87072 | |
|
|
fbc37e9d39 |
|
|
@ -762,14 +762,39 @@ fn launch_as_admin(exe: &std::path::PathBuf, token: &str, port: u16) -> anyhow::
|
||||||
let params_str = format!("--port {} --token-file \"{}\"", port, token_file.display());
|
let params_str = format!("--port {} --token-file \"{}\"", port, token_file.display());
|
||||||
let params_wstr: Vec<u16> = OsStr::new(¶ms_str).encode_wide().chain(Some(0)).collect();
|
let params_wstr: Vec<u16> = OsStr::new(¶ms_str).encode_wide().chain(Some(0)).collect();
|
||||||
#[link(name = "shell32")] extern "system" { fn ShellExecuteW(h: *mut std::ffi::c_void, op: *const u16, f: *const u16, p: *const u16, d: *const u16, s: i32) -> isize; }
|
#[link(name = "shell32")] extern "system" { fn ShellExecuteW(h: *mut std::ffi::c_void, op: *const u16, f: *const u16, p: *const u16, d: *const u16, s: i32) -> isize; }
|
||||||
|
#[link(name = "kernel32")] extern "system" { fn GetLastError() -> u32; }
|
||||||
|
|
||||||
// Use the GUI executable's directory as the working directory so dependencies are found
|
// Use the GUI executable's directory as the working directory so dependencies are found
|
||||||
let cwd_path = std::env::current_exe().unwrap_or_else(|_| std::path::PathBuf::from("."));
|
let cwd_path = std::env::current_exe().unwrap_or_else(|_| std::path::PathBuf::from("."));
|
||||||
let dir_wstr: Vec<u16> = cwd_path.parent().unwrap_or(std::path::Path::new(".")).as_os_str().encode_wide().chain(Some(0)).collect();
|
let dir_wstr: Vec<u16> = cwd_path.parent().unwrap_or(std::path::Path::new(".")).as_os_str().encode_wide().chain(Some(0)).collect();
|
||||||
|
// Remove Mark of the Web (Zone.Identifier) so SmartScreen doesn't block UAC
|
||||||
|
let zone_id = format!("{}:Zone.Identifier", exe.display());
|
||||||
|
let _ = std::fs::remove_file(zone_id);
|
||||||
|
|
||||||
let ret = unsafe { ShellExecuteW(null_mut(), verb_wstr.as_ptr(), exe_wstr.as_ptr(), params_wstr.as_ptr(), dir_wstr.as_ptr(), 0) };
|
// Use SW_SHOWNORMAL (1) instead of SW_HIDE (0) because runas with SW_HIDE is automatically blocked by UAC
|
||||||
|
let ret = unsafe { ShellExecuteW(null_mut(), verb_wstr.as_ptr(), exe_wstr.as_ptr(), params_wstr.as_ptr(), dir_wstr.as_ptr(), 1) };
|
||||||
|
|
||||||
if ret <= 32 { anyhow::bail!("UAC denied or helper missing."); }
|
// ShellExecuteW's return is a pseudo-HINSTANCE: > 32 means the call itself
|
||||||
|
// "succeeded" — but that range INCLUDES ERROR_CANCELLED (1223), which is
|
||||||
|
// exactly what Windows returns when the user clicks "No" on the UAC prompt.
|
||||||
|
// The old `ret <= 32` check alone treated a user-denied prompt as success,
|
||||||
|
// silently starting nothing and reporting a single opaque "denied or
|
||||||
|
// missing" message that could not distinguish "no prompt ever shown"
|
||||||
|
// (missing exe, ret<=32) from "prompt shown and declined" (ret==1223) from
|
||||||
|
// any other Win32 failure — exactly the ambiguity blocking diagnosis here.
|
||||||
|
if ret == 1223 {
|
||||||
|
anyhow::bail!("UAC elevation was denied. TUN mode requires administrator privileges.");
|
||||||
|
}
|
||||||
|
if ret <= 32 {
|
||||||
|
let win_err = unsafe { GetLastError() };
|
||||||
|
anyhow::bail!(
|
||||||
|
"Failed to request UAC elevation for the TUN helper (ShellExecuteW ret={}, \
|
||||||
|
GetLastError={}, path={}). If this keeps happening with no prompt ever appearing, \
|
||||||
|
an unsigned binary can be silently blocked by SmartScreen/antivirus during \
|
||||||
|
elevation — try running ostp-gui.exe as Administrator manually.",
|
||||||
|
ret, win_err, exe.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -90,18 +90,18 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Live RTT + speeds -->
|
<!-- Live RTT + speeds -->
|
||||||
<div class="live-stats">
|
<div class="live-stats" id="live-stats-container">
|
||||||
<div class="live-stat">
|
<div class="live-stat" id="stat-rtt-box">
|
||||||
<span class="live-stat-label">RTT</span>
|
<span class="live-stat-label">RTT</span>
|
||||||
<span id="live-rtt" class="live-stat-value">--</span>
|
<span id="live-rtt" class="live-stat-value">--</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="live-stat-sep"></div>
|
<div class="live-stat-sep" id="stat-sep-1"></div>
|
||||||
<div class="live-stat">
|
<div class="live-stat" id="stat-down-box">
|
||||||
<span class="live-stat-label">↓</span>
|
<span class="live-stat-label">↓</span>
|
||||||
<span id="live-down-speed" class="live-stat-value">0 B/s</span>
|
<span id="live-down-speed" class="live-stat-value">0 B/s</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="live-stat-sep"></div>
|
<div class="live-stat-sep" id="stat-sep-2"></div>
|
||||||
<div class="live-stat">
|
<div class="live-stat" id="stat-up-box">
|
||||||
<span class="live-stat-label">↑</span>
|
<span class="live-stat-label">↑</span>
|
||||||
<span id="live-up-speed" class="live-stat-value">0 B/s</span>
|
<span id="live-up-speed" class="live-stat-value">0 B/s</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -280,10 +280,33 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="toggle-row" style="border-top:none;">
|
||||||
|
<div class="toggle-text">
|
||||||
|
<span class="toggle-name">Show RTT</span>
|
||||||
|
<span class="toggle-hint">Display live ping on dashboard</span>
|
||||||
</div>
|
</div>
|
||||||
|
<label class="toggle">
|
||||||
|
<input type="checkbox" id="in-show-rtt" checked />
|
||||||
|
<span class="toggle-track"><span class="toggle-thumb"></span></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toggle-row" style="border-top:none;">
|
||||||
|
<div class="toggle-text">
|
||||||
|
<span class="toggle-name">Show Speed</span>
|
||||||
|
<span class="toggle-hint">Display traffic speed on dashboard</span>
|
||||||
|
</div>
|
||||||
|
<label class="toggle">
|
||||||
|
<input type="checkbox" id="in-show-speed" checked />
|
||||||
|
<span class="toggle-track"><span class="toggle-thumb"></span></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div> <!-- client-settings-card -->
|
||||||
|
|
||||||
<div class="app-version" id="app-version">OSTP GUI</div>
|
<div class="app-version" id="app-version">OSTP GUI</div>
|
||||||
</div>
|
</div> <!-- settings-body -->
|
||||||
</div>
|
</div> <!-- settings-screen -->
|
||||||
|
|
||||||
<!-- ── ADD PROFILE DROPDOWN ─────────────────────────────── -->
|
<!-- ── ADD PROFILE DROPDOWN ─────────────────────────────── -->
|
||||||
<div id="add-menu" class="add-menu hidden">
|
<div id="add-menu" class="add-menu hidden">
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,8 @@ const inExProcs = $('in-ex-procs');
|
||||||
const inAutoconnect = $('in-autoconnect');
|
const inAutoconnect = $('in-autoconnect');
|
||||||
const inLaunchStartup = $('in-launch-startup');
|
const inLaunchStartup = $('in-launch-startup');
|
||||||
const inDebug = $('in-debug');
|
const inDebug = $('in-debug');
|
||||||
|
const inShowRtt = $('in-show-rtt');
|
||||||
|
const inShowSpeed = $('in-show-speed');
|
||||||
const groupKillSwitch = $('group-kill-switch');
|
const groupKillSwitch = $('group-kill-switch');
|
||||||
const groupMuxSessions = $('group-mux-sessions');
|
const groupMuxSessions = $('group-mux-sessions');
|
||||||
|
|
||||||
|
|
@ -664,6 +666,8 @@ function loadSettingsIntoForm() {
|
||||||
inAutoconnect.checked = !!s.autoconnect;
|
inAutoconnect.checked = !!s.autoconnect;
|
||||||
inLaunchStartup.checked = !!s.launchStartup;
|
inLaunchStartup.checked = !!s.launchStartup;
|
||||||
inDebug.checked = !!s.debug;
|
inDebug.checked = !!s.debug;
|
||||||
|
inShowRtt.checked = s.showRtt !== false;
|
||||||
|
inShowSpeed.checked = s.showSpeed !== false;
|
||||||
updateClientVisibility();
|
updateClientVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -682,6 +686,8 @@ function collectAndSaveSettings() {
|
||||||
autoconnect: inAutoconnect.checked,
|
autoconnect: inAutoconnect.checked,
|
||||||
launchStartup: inLaunchStartup.checked,
|
launchStartup: inLaunchStartup.checked,
|
||||||
debug: inDebug.checked,
|
debug: inDebug.checked,
|
||||||
|
showRtt: inShowRtt.checked,
|
||||||
|
showSpeed: inShowSpeed.checked,
|
||||||
};
|
};
|
||||||
saveClientSettings(s);
|
saveClientSettings(s);
|
||||||
updateClientVisibility();
|
updateClientVisibility();
|
||||||
|
|
@ -703,6 +709,23 @@ function collectAndSaveSettings() {
|
||||||
function updateClientVisibility() {
|
function updateClientVisibility() {
|
||||||
groupKillSwitch.style.display = inTun.checked ? 'flex' : 'none';
|
groupKillSwitch.style.display = inTun.checked ? 'flex' : 'none';
|
||||||
groupMuxSessions.style.display = inMux.checked ? 'flex' : 'none';
|
groupMuxSessions.style.display = inMux.checked ? 'flex' : 'none';
|
||||||
|
|
||||||
|
const showRtt = inShowRtt.checked;
|
||||||
|
const showSpeed = inShowSpeed.checked;
|
||||||
|
const rttBox = $('stat-rtt-box');
|
||||||
|
const downBox = $('stat-down-box');
|
||||||
|
const upBox = $('stat-up-box');
|
||||||
|
const sep1 = $('stat-sep-1');
|
||||||
|
const sep2 = $('stat-sep-2');
|
||||||
|
const container = $('live-stats-container');
|
||||||
|
|
||||||
|
if (rttBox) rttBox.style.display = showRtt ? 'flex' : 'none';
|
||||||
|
if (downBox) downBox.style.display = showSpeed ? 'flex' : 'none';
|
||||||
|
if (upBox) upBox.style.display = showSpeed ? 'flex' : 'none';
|
||||||
|
|
||||||
|
if (sep1) sep1.style.display = (showRtt && showSpeed) ? 'block' : 'none';
|
||||||
|
if (sep2) sep2.style.display = showSpeed ? 'block' : 'none';
|
||||||
|
if (container) container.style.display = (showRtt || showSpeed) ? 'flex' : 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── INIT ──────────────────────────────────────────────────────────────
|
// ── INIT ──────────────────────────────────────────────────────────────
|
||||||
|
|
@ -858,7 +881,7 @@ window.addEventListener('DOMContentLoaded', async () => {
|
||||||
wintunModal.addEventListener('click', e => { if (e.target === wintunModal) wintunModal.classList.add('hidden'); });
|
wintunModal.addEventListener('click', e => { if (e.target === wintunModal) wintunModal.classList.add('hidden'); });
|
||||||
|
|
||||||
// Client settings — wire all inputs
|
// Client settings — wire all inputs
|
||||||
[inTun, inKillSwitch, inMux, inAutoconnect, inLaunchStartup, inDebug]
|
[inTun, inKillSwitch, inMux, inAutoconnect, inLaunchStartup, inDebug, inShowRtt, inShowSpeed]
|
||||||
.forEach(el => el.addEventListener('change', collectAndSaveSettings));
|
.forEach(el => el.addEventListener('change', collectAndSaveSettings));
|
||||||
[inMuxSessions, inMtu, inDns, inSocks, inExDomains, inExIps, inExProcs]
|
[inMuxSessions, inMtu, inDns, inSocks, inExDomains, inExIps, inExProcs]
|
||||||
.forEach(el => {
|
.forEach(el => {
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@
|
||||||
|
|
||||||
/* ── Reset ───────────────────────────────────────────────────────────── */
|
/* ── Reset ───────────────────────────────────────────────────────────── */
|
||||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
html, body { width: 100%; height: 100%; background: var(--c-bg); overflow: hidden; user-select: none; }
|
html, body { width: 100%; height: 100%; background: var(--c-bg); user-select: none; }
|
||||||
button { cursor: pointer; font-family: inherit; border: none; background: none; }
|
button { cursor: pointer; font-family: inherit; border: none; background: none; }
|
||||||
input, textarea, select { font-family: inherit; }
|
input, textarea, select { font-family: inherit; }
|
||||||
a { text-decoration: none; }
|
a { text-decoration: none; }
|
||||||
|
|
@ -356,8 +356,12 @@ a { text-decoration: none; }
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
padding: 10px 18px;
|
padding: 10px 4px;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 80px;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.live-stat-label {
|
.live-stat-label {
|
||||||
font-size: 0.6rem;
|
font-size: 0.6rem;
|
||||||
|
|
@ -408,19 +412,13 @@ a { text-decoration: none; }
|
||||||
.settings-body {
|
.settings-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
display: flex;
|
overflow-y: scroll;
|
||||||
flex-direction: column;
|
scrollbar-width: none;
|
||||||
gap: 0;
|
|
||||||
overflow-y: auto;
|
|
||||||
overflow-x: hidden;
|
|
||||||
scrollbar-width: thin;
|
|
||||||
scrollbar-color: rgba(255,255,255,0.07) transparent;
|
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
.settings-body::-webkit-scrollbar { width: 3px; }
|
.settings-body::-webkit-scrollbar { display: none; }
|
||||||
.settings-body::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.07); border-radius: 10px; }
|
|
||||||
|
|
||||||
/* ── Profile list ────────────────────────────────────────────────────── */
|
/* ── Profile list ────────────────────────────────────────────────────── */
|
||||||
.profile-list {
|
.profile-list {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue