Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ Planned:

Transmission v2.40 or later is required.

## Startup visibility

In **Application Settings > Integrations**, enable **Show tray icon** and
**Hide on startup** to launch with only a tray icon. The option is off by default
and takes effect after restarting the application. If a tray icon cannot be
created, the window opens normally. Use the tray icon or launch the application
again to show the existing window, including when no server tab is open.

## Downloads

You can get the latest release from the
Expand Down
24 changes: 24 additions & 0 deletions src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,30 @@ pub async fn list_system_fonts() -> Vec<String> {
system_fonts::query_all()
}

#[tauri::command]
pub fn finish_startup(
app: tauri::AppHandle,
window: tauri::WebviewWindow,
startup: State<'_, crate::StartupState>,
hide_on_startup: bool,
) -> Result<(), String> {
if window.label() != "main" {
return Err("Startup visibility is only available for the main window".into());
}
let first_window = startup.0.swap(false, std::sync::atomic::Ordering::SeqCst);
let hidden = first_window && hide_on_startup && app.tray_by_id(tray::TRAY_ID).is_some();
if hidden {
window.hide().map_err(|e| e.to_string())?;
tray::set_tray_showhide_text(&app, "Show");
} else {
window.show().map_err(|e| e.to_string())?;
window.set_focus().ok();
tray::set_tray_showhide_text(&app, "Hide");
}
println!("Main window startup: {}", if hidden { "hidden" } else { "visible" });
Ok(())
}

#[tauri::command]
pub fn create_tray(app_handle: tauri::AppHandle) {
if app_handle.tray_by_id(tray::TRAY_ID).is_none() {
Expand Down
18 changes: 18 additions & 0 deletions src-tauri/src/ipc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,19 @@ async fn http_response(
toggle_main_window(&app, None);
}

// Opening another instance without files must also work when no
// server tab is open (the frontend argument listener is not mounted).
if serde_json::from_slice::<Vec<String>>(&payload).is_ok_and(|args| args.is_empty()) {
if let Some(window) = app.get_webview_window("main") {
window.show().ok();
window.unminimize().ok();
window.set_focus().ok();
window.emit("window-shown", "").ok();
crate::tray::set_tray_showhide_text(&app, "Hide");
}
return Ok(Response::builder().body(make_body("TrguiNG OK")).unwrap());
}

let lock = args_lock.acquire().await.unwrap();
send_payload(&app, payload).await;
drop(lock);
Expand Down Expand Up @@ -453,6 +466,11 @@ impl Ipc {
}

pub async fn send(&self, args: &Vec<String>, app: AppHandle) -> Result<(), String> {
// Only a second instance or real launch arguments should reveal the UI.
// The primary instance already creates its window during setup.
if self.listening && args.is_empty() {
return Ok(());
}
let client = reqwest::Client::builder()
.connect_timeout(Duration::from_secs(3))
.read_timeout(Duration::from_secs(10))
Expand Down
5 changes: 4 additions & 1 deletion src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
windows_subsystem = "windows"
)]

use std::{sync::Arc, time::Duration};
use std::{sync::{Arc, atomic::AtomicBool}, time::Duration};

use createtorrent::CreationRequestsHandle;
use geoip::MmdbReaderHandle;
Expand All @@ -42,6 +42,7 @@ mod torrentcache;
mod tray;

struct ListenerHandle(Arc<RwLock<ipc::Ipc>>);
struct StartupState(AtomicBool);

#[cfg(target_os = "macos")]
fn handle_uris(app: &AppHandle, uris: Vec<String>) {
Expand Down Expand Up @@ -208,10 +209,12 @@ fn main() {
commands::save_create_torrent,
commands::pass_to_window,
commands::list_system_fonts,
commands::finish_startup,
commands::create_tray,
commands::save_text_file,
commands::load_text_file,
])
.manage(StartupState(AtomicBool::new(true)))
.manage(ListenerHandle(Arc::new(RwLock::new(ipc))))
.manage(TorrentCacheHandle::default())
.manage(PollerHandle::default())
Expand Down
5 changes: 2 additions & 3 deletions src-tauri/src/tray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,13 @@ pub fn toggle_main_window(app: &AppHandle, window: Option<WebviewWindow>) {
});
}
None => {
let window =
WebviewWindowBuilder::new(app, "main", tauri::WebviewUrl::App("index.html".into()))
WebviewWindowBuilder::new(app, "main", tauri::WebviewUrl::App("index.html".into()))
.title("Transmission GUI")
.inner_size(1024., 800.)
.visible(false)
.build()
.unwrap();
set_tray_showhide_text(app, "Hide");
window.set_focus().ok();
}
}
}
Expand Down
14 changes: 14 additions & 0 deletions src/components/modals/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ interface FormValues extends InterfaceFormValues {
toastNotifications: boolean,
toastNotificationSound: boolean,
showTrayIcon: boolean,
hideOnStartup: boolean,
onMinimize: WindowMinimizeOption,
onClose: WindowCloseOption,
},
Expand Down Expand Up @@ -303,13 +304,26 @@ function IntegrationsPanel({ form }: { form: UseFormReturnType<FormValues> }) {
onChange={(e) => {
form.setFieldValue("app.showTrayIcon", e.currentTarget.checked);
if (!e.currentTarget.checked) {
form.setFieldValue("app.hideOnStartup", false);
form.setFieldValue("app.onMinimize", "minimize");
form.setFieldValue("app.onClose", "quit");
}
}}
/>
</Grid.Col>
<Grid.Col span={4}>(takes effect after restart)</Grid.Col>
<Grid.Col span={6}>Hide on startup</Grid.Col>
<Grid.Col span={2}>
<Switch
onLabel="ON"
offLabel="OFF"
size="xl"
styles={bigSwitchStyles}
disabled={!form.values.app.showTrayIcon}
{...form.getInputProps("app.hideOnStartup", { type: "checkbox" })}
/>
</Grid.Col>
<Grid.Col span={4}>Start in the system tray (after restart)</Grid.Col>
<Grid.Col span={6}>When minimized</Grid.Col>
<Grid.Col span={6}>
<SegmentedControl
Expand Down
2 changes: 2 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ interface Settings {
toastNotifications: boolean,
toastNotificationSound: boolean,
showTrayIcon: boolean,
hideOnStartup: boolean,
onMinimize: WindowMinimizeOption,
onClose: WindowCloseOption,
fontSizeBase: number,
Expand Down Expand Up @@ -260,6 +261,7 @@ const DefaultSettings: Settings = {
toastNotifications: true,
toastNotificationSound: true,
showTrayIcon: true,
hideOnStartup: false,
onMinimize: "minimize",
onClose: "quit",
fontSizeBase: 0.9,
Expand Down
3 changes: 2 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,11 @@ async function run(config: Config) {
setupTauriEvents(config, app);

if (config.values.app.showTrayIcon) {
void invoke("create_tray");
await invoke("create_tray");
}

await restoreWindow(config);
await invoke("finish_startup", { hideOnStartup: config.values.app.hideOnStartup });
} else {
setupWebEvents(config);
}
Expand Down