mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-03 16:41:26 +01:00
feat(gui): add pdfs-prompt --preload for session autostart
--preload registers the resident GTK launcher and realizes its window without showing it, so the first shortcut opens it as fast as later ones. When a launcher is already running it exits without activating it. The application hold now happens in startup, which only the primary instance runs. Taken before registration, it also kept every remote invocation alive after it had forwarded its activation.
This commit is contained in:
1 parent
3d61fc637b
commit
f13b6fc0ef
3 files changed
+69
-2
No files matched your search
@@ -535,6 +535,12 @@ async fn mounted_target_within(mountpoint: PathBuf, hit: SearchHit) -> Option<Pa
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Ui {
|
impl Ui {
|
||||||
|
/// `--preload`: create the surface, renderer and style state now, without
|
||||||
|
/// mapping the window, so the first real summon only has to present it.
|
||||||
|
pub(crate) fn preload(&self) {
|
||||||
|
WidgetExt::realize(&self.window);
|
||||||
|
}
|
||||||
|
|
||||||
/// Reset and present the resident prompt in response to GApplication
|
/// Reset and present the resident prompt in response to GApplication
|
||||||
/// activation. A second invocation while a file is materialising merely
|
/// activation. A second invocation while a file is materialising merely
|
||||||
/// raises the progress window; it must not make the in-flight open mutable.
|
/// raises the progress window; it must not make the in-flight open mutable.
|
||||||
|
|||||||
@@ -268,6 +268,9 @@ Options:
|
|||||||
every keystroke — results appear as you type, unlike
|
every keystroke — results appear as you type, unlike
|
||||||
--dmenu. Also settable as \"prompt\": { \"mode\": \"fzf\" }.
|
--dmenu. Also settable as \"prompt\": { \"mode\": \"fzf\" }.
|
||||||
--gtk Force the built-in window, overriding that setting.
|
--gtk Force the built-in window, overriding that setting.
|
||||||
|
--preload Start the built-in window hidden and stay resident, so
|
||||||
|
the first summon opens at once. For session autostart;
|
||||||
|
does nothing if the prompt is already running.
|
||||||
--menu <COMMAND> Launcher command line for --dmenu, e.g.
|
--menu <COMMAND> Launcher command line for --dmenu, e.g.
|
||||||
--menu 'fuzzel --dmenu --width 60'. Overrides
|
--menu 'fuzzel --dmenu --width 60'. Overrides
|
||||||
\"prompt\": { \"menu\": [...] }.
|
\"prompt\": { \"menu\": [...] }.
|
||||||
@@ -293,6 +296,8 @@ struct Args {
|
|||||||
feed: Option<Option<String>>,
|
feed: Option<Option<String>>,
|
||||||
/// `--inner`: we are the process a spawned terminal is running.
|
/// `--inner`: we are the process a spawned terminal is running.
|
||||||
inner: bool,
|
inner: bool,
|
||||||
|
/// `--preload`: become the resident GTK instance without showing it.
|
||||||
|
preload: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_args(argv: impl Iterator<Item = String>) -> Result<Args, String> {
|
fn parse_args(argv: impl Iterator<Item = String>) -> Result<Args, String> {
|
||||||
@@ -302,6 +307,7 @@ fn parse_args(argv: impl Iterator<Item = String>) -> Result<Args, String> {
|
|||||||
query: None,
|
query: None,
|
||||||
feed: None,
|
feed: None,
|
||||||
inner: false,
|
inner: false,
|
||||||
|
preload: false,
|
||||||
};
|
};
|
||||||
let mut argv = argv.peekable();
|
let mut argv = argv.peekable();
|
||||||
while let Some(arg) = argv.next() {
|
while let Some(arg) = argv.next() {
|
||||||
@@ -310,6 +316,7 @@ fn parse_args(argv: impl Iterator<Item = String>) -> Result<Args, String> {
|
|||||||
"--fzf" => args.mode = Some(PromptMode::Fzf),
|
"--fzf" => args.mode = Some(PromptMode::Fzf),
|
||||||
"--gtk" => args.mode = Some(PromptMode::Gtk),
|
"--gtk" => args.mode = Some(PromptMode::Gtk),
|
||||||
"--inner" => args.inner = true,
|
"--inner" => args.inner = true,
|
||||||
|
"--preload" => args.preload = true,
|
||||||
// fzf passes an empty argument for an empty input, so a missing
|
// fzf passes an empty argument for an empty input, so a missing
|
||||||
// value here is that case rather than a user error.
|
// value here is that case rather than a user error.
|
||||||
"--feed" => args.feed = Some(argv.next().filter(|text| !text.trim().is_empty())),
|
"--feed" => args.feed = Some(argv.next().filter(|text| !text.trim().is_empty())),
|
||||||
@@ -337,6 +344,13 @@ fn parse_args(argv: impl Iterator<Item = String>) -> Result<Args, String> {
|
|||||||
other => return Err(format!("unrecognised argument: {other}")),
|
other => return Err(format!("unrecognised argument: {other}")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Only the GTK window has a resident process worth warming up.
|
||||||
|
if args.preload {
|
||||||
|
if matches!(args.mode, Some(PromptMode::Dmenu | PromptMode::Fzf)) || args.query.is_some() {
|
||||||
|
return Err("--preload only applies to the built-in window".to_string());
|
||||||
|
}
|
||||||
|
args.mode = Some(PromptMode::Gtk);
|
||||||
|
}
|
||||||
Ok(args)
|
Ok(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,9 +412,31 @@ fn main() -> glib::ExitCode {
|
|||||||
// GtkApplication normally remains active while it owns a window, including
|
// GtkApplication normally remains active while it owns a window, including
|
||||||
// when that window is hidden. Hold it explicitly as well: residency is a
|
// when that window is hidden. Hold it explicitly as well: residency is a
|
||||||
// product requirement here, not an incidental window-lifetime side effect.
|
// product requirement here, not an incidental window-lifetime side effect.
|
||||||
let _resident = app.hold();
|
// Only the primary instance runs startup; a hold taken before registration
|
||||||
app.connect_startup(|_| theme::load_resources("/de/nils/protondrivelinux/prompt.css"));
|
// would also keep every remote invocation alive after it has forwarded
|
||||||
|
// its activation.
|
||||||
|
let resident = Rc::new(RefCell::new(None));
|
||||||
|
app.connect_startup(move |app| {
|
||||||
|
*resident.borrow_mut() = Some(app.hold());
|
||||||
|
theme::load_resources("/de/nils/protondrivelinux/prompt.css");
|
||||||
|
});
|
||||||
|
if args.preload {
|
||||||
|
if let Err(e) = app.register(gtk4::gio::Cancellable::NONE) {
|
||||||
|
eprintln!("pdfs-prompt: {e}");
|
||||||
|
return glib::ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
// Another instance is already resident; activating it would show it.
|
||||||
|
// Dropping a registered application that never ran makes GIO warn
|
||||||
|
// about the D-Bus registration, so leave it to process exit.
|
||||||
|
if app.is_remote() {
|
||||||
|
std::mem::forget(app);
|
||||||
|
return glib::ExitCode::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
let prompt: Rc<RefCell<Option<Rc<hud::Ui>>>> = Rc::new(RefCell::new(None));
|
let prompt: Rc<RefCell<Option<Rc<hud::Ui>>>> = Rc::new(RefCell::new(None));
|
||||||
|
// The first activation of a preloading instance is its own startup, not a
|
||||||
|
// summon: build the window but leave it hidden.
|
||||||
|
let quiet = std::cell::Cell::new(args.preload);
|
||||||
app.connect_activate(move |app| {
|
app.connect_activate(move |app| {
|
||||||
let ui = if let Some(ui) = prompt.borrow().clone() {
|
let ui = if let Some(ui) = prompt.borrow().clone() {
|
||||||
ui
|
ui
|
||||||
@@ -411,6 +447,10 @@ fn main() -> glib::ExitCode {
|
|||||||
*prompt.borrow_mut() = Some(ui.clone());
|
*prompt.borrow_mut() = Some(ui.clone());
|
||||||
ui
|
ui
|
||||||
};
|
};
|
||||||
|
if quiet.replace(false) {
|
||||||
|
ui.preload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
ui.activate();
|
ui.activate();
|
||||||
});
|
});
|
||||||
// GTK must not try to parse our own flags; they were consumed above.
|
// GTK must not try to parse our own flags; they were consumed above.
|
||||||
@@ -582,4 +622,16 @@ mod tests {
|
|||||||
let args = parse(&["--dmenu", "--query", "tax return 2024"]).unwrap();
|
let args = parse(&["--dmenu", "--query", "tax return 2024"]).unwrap();
|
||||||
assert_eq!(args.query.as_deref(), Some("tax return 2024"));
|
assert_eq!(args.query.as_deref(), Some("tax return 2024"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preload_forces_the_gtk_window_and_rejects_the_launchers() {
|
||||||
|
let args = parse(&["--preload"]).unwrap();
|
||||||
|
assert!(args.preload);
|
||||||
|
assert_eq!(args.mode, Some(PromptMode::Gtk));
|
||||||
|
|
||||||
|
assert!(parse(&["--preload", "--dmenu"]).is_err());
|
||||||
|
assert!(parse(&["--fzf", "--preload"]).is_err());
|
||||||
|
assert!(parse(&["--preload", "--menu", "fuzzel --dmenu"]).is_err());
|
||||||
|
assert!(parse(&["--preload", "--query", "tax"]).is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -235,6 +235,15 @@ The window stays loaded after the first use, so later shortcuts open it instantl
|
|||||||
and video open straight from the Proton Drive folder, so players can start before the whole file
|
and video open straight from the Proton Drive folder, so players can start before the whole file
|
||||||
downloads. Other Drive files download first, then open.
|
downloads. Other Drive files download first, then open.
|
||||||
|
|
||||||
|
To make the first shortcut instant as well, start the launcher hidden with your session.
|
||||||
|
`pdfs-prompt --preload` loads the window without showing it and does nothing if the launcher is
|
||||||
|
already running. For Hyprland:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
exec-once = pdfs-prompt --preload
|
||||||
|
bind = SUPER, space, exec, pdfs-prompt
|
||||||
|
```
|
||||||
|
|
||||||
### Using your own launcher
|
### Using your own launcher
|
||||||
|
|
||||||
`pdfs-prompt --dmenu` shows the results in fuzzel, rofi, wofi, tofi, bemenu or dmenu, whichever is
|
`pdfs-prompt --dmenu` shows the results in fuzzel, rofi, wofi, tofi, bemenu or dmenu, whichever is
|
||||||
|
|||||||
Reference in new issue
Block a user