mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-03 10:07:21 +00:00
Added the page jump for PdfCraft on Windows, macOS, and Linux (#1102)
This commit is contained in:
1 parent
ba99bf6fae
commit
ab9df99cf0
3 files changed
+265
-31
No files matched your search
+2
-2
@@ -99,8 +99,8 @@ windows-collections = "=0.2.0"
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
apple-native-keyring-store = { version = "1.0.0", features = ["keychain"] }
|
||||
objc2 = "0.6.3"
|
||||
objc2-app-kit = { version = "0.3.2", features = ["NSResponder", "NSSharingService", "NSView"] }
|
||||
objc2-foundation = { version = "0.3.2", features = ["NSArray", "NSGeometry", "NSString", "NSURL"] }
|
||||
objc2-app-kit = { version = "0.3.2", features = ["NSResponder", "NSSharingService", "NSView", "NSWorkspace"] }
|
||||
objc2-foundation = { version = "0.3.2", features = ["NSArray", "NSBundle", "NSGeometry", "NSString", "NSURL"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
ashpd = { version = "0.13.12", default-features = false, features = ["tokio", "open_uri", "global_shortcuts"] }
|
||||
|
||||
+262
-29
@@ -515,13 +515,17 @@ fn refuse_document(requested_path: &Path) -> Option<String> {
|
||||
}
|
||||
|
||||
/// Tries to show the document on the given page, and says whether it did.
|
||||
#[cfg(any(windows, target_os = "linux"))]
|
||||
///
|
||||
/// On macOS, `open` drops the fragment of a URL before the program it starts ever sees it, with and
|
||||
/// without `-a`, so neither a browser nor Preview can be told a page from the command line. Only a
|
||||
/// program which takes the page as an argument of its own gets there; with any other, the document
|
||||
/// opens on its first page, and the source names the page for the reader.
|
||||
async fn try_open_at_page(path: &Path, page: u32) -> bool {
|
||||
let DocumentOpenPlan::WithPage { program, arguments } = resolve_document_open_plan(path, page).await else {
|
||||
return false;
|
||||
};
|
||||
|
||||
match start_page_aware_viewer(&program, &arguments) {
|
||||
match start_page_aware_viewer(&program, &arguments).await {
|
||||
Ok(()) => true,
|
||||
|
||||
//
|
||||
@@ -535,16 +539,6 @@ async fn try_open_at_page(path: &Path, page: u32) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Never shows a page on macOS.
|
||||
///
|
||||
/// `open` drops the fragment of a URL before the program it starts ever sees it, with and without
|
||||
/// `-a`, so a page cannot be named from the command line at all. The document opens on its first
|
||||
/// page, and the source names the page for the reader.
|
||||
#[cfg(target_os = "macos")]
|
||||
async fn try_open_at_page(_path: &Path, _page: u32) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// How a document viewer wants to be told which page to show.
|
||||
///
|
||||
/// They all mean the same thing and every one of them spells it differently. A viewer which is not
|
||||
@@ -552,13 +546,12 @@ async fn try_open_at_page(_path: &Path, _page: u32) -> bool {
|
||||
///
|
||||
/// Which spellings exist follows from where a viewer is found: Acrobat and Foxit are named by the
|
||||
/// Windows registration and by nothing else, and the three Linux viewers are named by a desktop
|
||||
/// entry and by nothing else. Only a browser is reached on both, so only its spelling is needed
|
||||
/// everywhere.
|
||||
#[cfg(any(windows, target_os = "linux", test))]
|
||||
/// entry and by nothing else. A browser is reached on both, and PdfCraft on every system we run on.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum PageArgument {
|
||||
/// The page travels in the URL fragment, the way the PDF Open Parameters define it. Browsers
|
||||
/// read it, and on Windows a browser is what most people open a PDF with.
|
||||
#[cfg(any(windows, target_os = "linux", test))]
|
||||
UrlFragment,
|
||||
|
||||
/// Acrobat and Acrobat Reader take an open action: `/A page=12`.
|
||||
@@ -581,10 +574,12 @@ enum PageArgument {
|
||||
/// Zathura takes `-P 12`.
|
||||
#[cfg(any(target_os = "linux", test))]
|
||||
ZathuraPage,
|
||||
|
||||
/// PdfCraft takes `--page 12`, counted from one, and spells it the same on every system.
|
||||
PdfCraftPage,
|
||||
}
|
||||
|
||||
/// What it takes to show a document on a page.
|
||||
#[cfg(any(windows, target_os = "linux"))]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum DocumentOpenPlan {
|
||||
/// Hand the file to the system and let it decide. The document opens on its first page.
|
||||
@@ -599,16 +594,15 @@ enum DocumentOpenPlan {
|
||||
/// Only PDFs are sent to a page: the handler is looked up for PDFs, and the arguments below are
|
||||
/// the ones PDF viewers understand. A Word file has a page too, but the programs which show one
|
||||
/// cannot be told to go there.
|
||||
#[cfg(any(windows, target_os = "linux", test))]
|
||||
fn is_pdf_document(path: &Path) -> bool {
|
||||
path.extension().is_some_and(|extension| extension.eq_ignore_ascii_case("pdf"))
|
||||
}
|
||||
|
||||
/// Builds the arguments which name the page, in the spelling this viewer expects.
|
||||
#[cfg(any(windows, target_os = "linux", test))]
|
||||
fn page_arguments(argument: PageArgument, path: &Path, page: u32) -> Option<Vec<String>> {
|
||||
let path_argument = path.to_string_lossy().to_string();
|
||||
Some(match argument {
|
||||
#[cfg(any(windows, target_os = "linux", test))]
|
||||
PageArgument::UrlFragment => vec![document_url_with_page(path, page)?],
|
||||
|
||||
#[cfg(any(windows, test))]
|
||||
@@ -625,6 +619,8 @@ fn page_arguments(argument: PageArgument, path: &Path, page: u32) -> Option<Vec<
|
||||
|
||||
#[cfg(any(target_os = "linux", test))]
|
||||
PageArgument::ZathuraPage => vec![String::from("-P"), page.to_string(), path_argument],
|
||||
|
||||
PageArgument::PdfCraftPage => vec![String::from("--page"), page.to_string(), path_argument],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -643,20 +639,40 @@ fn document_url_with_page(path: &Path, page: u32) -> Option<String> {
|
||||
///
|
||||
/// Waiting for it to say so is not possible: a viewer runs until the user closes it, so waiting
|
||||
/// would hold the request open for as long as the document stays on screen.
|
||||
#[cfg(any(windows, target_os = "linux"))]
|
||||
fn start_page_aware_viewer(program: &str, arguments: &[String]) -> Result<(), String> {
|
||||
let mut command = std::process::Command::new(program);
|
||||
command.args(arguments);
|
||||
///
|
||||
/// On macOS the program started is `open`, which ends as soon as LaunchServices has started the
|
||||
/// viewer. Its answer is waited for there, so a viewer which cannot be started still leaves the
|
||||
/// caller the chance to open the document plainly.
|
||||
async fn start_page_aware_viewer(program: &str, arguments: &[String]) -> Result<(), String> {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let output = tokio::process::Command::new(program)
|
||||
.args(arguments)
|
||||
.output()
|
||||
.await
|
||||
.map_err(|error| format!("Failed to start '{program}': {error}"))?;
|
||||
|
||||
#[cfg(windows)]
|
||||
command.creation_flags(CREATE_NO_WINDOW);
|
||||
if output.status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("'{program}' could not start the viewer ({}): {}", output.status, String::from_utf8_lossy(&output.stderr).trim()))
|
||||
}
|
||||
}
|
||||
|
||||
command.spawn()
|
||||
.map(|_| ())
|
||||
.map_err(|error| format!("Failed to start '{program}': {error}"))
|
||||
#[cfg(any(windows, target_os = "linux"))]
|
||||
{
|
||||
let mut command = std::process::Command::new(program);
|
||||
command.args(arguments);
|
||||
|
||||
#[cfg(windows)]
|
||||
command.creation_flags(CREATE_NO_WINDOW);
|
||||
|
||||
command.spawn()
|
||||
.map(|_| ())
|
||||
.map_err(|error| format!("Failed to start '{program}': {error}"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(windows, target_os = "linux"))]
|
||||
async fn resolve_document_open_plan(path: &Path, page: u32) -> DocumentOpenPlan {
|
||||
if !is_pdf_document(path) {
|
||||
return DocumentOpenPlan::Plain;
|
||||
@@ -699,8 +715,47 @@ async fn resolve_document_open_plan(path: &Path, page: u32) -> DocumentOpenPlan
|
||||
return DocumentOpenPlan::Plain;
|
||||
};
|
||||
|
||||
//
|
||||
// A program installed as a Flatpak keeps the desktop entry of its native package but is
|
||||
// not on the path. The native program comes first, because it is the one the user chose
|
||||
// to install along with the system; the Flatpak is what is left when there is none.
|
||||
//
|
||||
if !linux_program_on_path(&program)
|
||||
&& let Some(app_id) = linux_flatpak_app_id(&desktop_id)
|
||||
&& linux_flatpak_installed(app_id).await
|
||||
{
|
||||
return DocumentOpenPlan::WithPage {
|
||||
program: String::from("flatpak"),
|
||||
arguments: flatpak_run_arguments(app_id, &arguments, path),
|
||||
};
|
||||
}
|
||||
|
||||
DocumentOpenPlan::WithPage { program, arguments }
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let Some(MacApplication { bundle_id, bundle_path }) = macos_document_application(path) else {
|
||||
return DocumentOpenPlan::Plain;
|
||||
};
|
||||
|
||||
let Some(argument) = macos_page_argument(&bundle_id) else {
|
||||
info!("The program registered for PDFs as '{bundle_id}' cannot be sent to a page; the document opens on its first page.");
|
||||
return DocumentOpenPlan::Plain;
|
||||
};
|
||||
|
||||
let Some(arguments) = page_arguments(argument, path, page) else {
|
||||
return DocumentOpenPlan::Plain;
|
||||
};
|
||||
|
||||
//
|
||||
// The absolute path keeps a program of the same name earlier on the path from running.
|
||||
//
|
||||
DocumentOpenPlan::WithPage {
|
||||
program: String::from("/usr/bin/open"),
|
||||
arguments: macos_open_arguments(&bundle_path, arguments),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads which program the user opens PDFs with.
|
||||
@@ -787,6 +842,14 @@ fn windows_page_argument(prog_id: &str) -> Option<PageArgument> {
|
||||
return Some(PageArgument::FoxitOpenAction);
|
||||
}
|
||||
|
||||
//
|
||||
// The installer registers PdfCraft as `PdfCraft.Document`, and the portable copy is picked
|
||||
// through 'Open with', which registers it by its file name, `pdfcraft.exe`.
|
||||
//
|
||||
if prog_id.contains("pdfcraft") {
|
||||
return Some(PageArgument::PdfCraftPage);
|
||||
}
|
||||
|
||||
const BROWSERS: [&str; 5] = ["msedge", "chrome", "firefox", "opera", "brave"];
|
||||
if BROWSERS.iter().any(|browser| prog_id.contains(browser)) {
|
||||
return Some(PageArgument::UrlFragment);
|
||||
@@ -836,7 +899,7 @@ async fn linux_default_pdf_handler() -> Option<String> {
|
||||
/// it always did, through the desktop's own handler.
|
||||
#[cfg(any(target_os = "linux", test))]
|
||||
fn linux_page_aware_program(desktop_id: &str) -> Option<(String, PageArgument)> {
|
||||
const KNOWN_VIEWERS: [(&str, &str, PageArgument); 9] = [
|
||||
const KNOWN_VIEWERS: [(&str, &str, PageArgument); 10] = [
|
||||
//
|
||||
// Atril and Xreader are forks of Evince and count their pages from zero just as it does.
|
||||
//
|
||||
@@ -847,6 +910,13 @@ fn linux_page_aware_program(desktop_id: &str) -> Option<(String, PageArgument)>
|
||||
("okular", "okular", PageArgument::OkularPage),
|
||||
("zathura", "zathura", PageArgument::ZathuraPage),
|
||||
|
||||
//
|
||||
// PdfCraft's desktop entry is `ai.storyteller.pdfcraft.desktop`. Its packages for Debian,
|
||||
// Fedora and Arch put the program on the path, its Flatpak is started through `flatpak`,
|
||||
// and its AppImage is found by neither, so it opens on the first page.
|
||||
//
|
||||
("pdfcraft", "pdfcraft", PageArgument::PdfCraftPage),
|
||||
|
||||
//
|
||||
// Chrome is asked about before Chromium, so that a desktop entry naming both lands on the
|
||||
// program the user actually installed.
|
||||
@@ -863,6 +933,110 @@ fn linux_page_aware_program(desktop_id: &str) -> Option<(String, PageArgument)>
|
||||
.map(|(_, program, argument)| (String::from(*program), *argument))
|
||||
}
|
||||
|
||||
/// Names the Flatpak behind a desktop entry, for the viewers we start through `flatpak` when they
|
||||
/// are not on the path.
|
||||
#[cfg(any(target_os = "linux", test))]
|
||||
fn linux_flatpak_app_id(desktop_id: &str) -> Option<&'static str> {
|
||||
const KNOWN_FLATPAKS: [(&str, &str); 1] = [
|
||||
("pdfcraft", "ai.storyteller.pdfcraft"),
|
||||
];
|
||||
|
||||
let desktop_id = desktop_id.to_ascii_lowercase();
|
||||
KNOWN_FLATPAKS.iter()
|
||||
.find(|(needle, _)| desktop_id.contains(needle))
|
||||
.map(|(_, app_id)| *app_id)
|
||||
}
|
||||
|
||||
/// Whether a program of this name lies in one of the folders of the path.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn linux_program_on_path(program: &str) -> bool {
|
||||
std::env::var_os("PATH")
|
||||
.is_some_and(|path| std::env::split_paths(&path).any(|directory| directory.join(program).is_file()))
|
||||
}
|
||||
|
||||
/// Whether this Flatpak is installed, for the user or for the whole system.
|
||||
#[cfg(target_os = "linux")]
|
||||
async fn linux_flatpak_installed(app_id: &str) -> bool {
|
||||
tokio::process::Command::new("flatpak")
|
||||
.args(["info", app_id])
|
||||
.stdout(std::process::Stdio::null())
|
||||
.stderr(std::process::Stdio::null())
|
||||
.status()
|
||||
.await
|
||||
.is_ok_and(|status| status.success())
|
||||
}
|
||||
|
||||
/// Builds the arguments which start a Flatpak with the page arguments of its program.
|
||||
///
|
||||
/// A Flatpak sees only the folders it was granted, and PdfCraft's are Documents and Downloads, while
|
||||
/// a data source may lie anywhere. The file is therefore handed over through the document portal,
|
||||
/// which `--file-forwarding` does for every argument between two `@@`.
|
||||
#[cfg(any(target_os = "linux", test))]
|
||||
fn flatpak_run_arguments(app_id: &str, arguments: &[String], path: &Path) -> Vec<String> {
|
||||
let path_argument = path.to_string_lossy().to_string();
|
||||
let mut run_arguments = vec![String::from("run"), String::from("--file-forwarding"), String::from(app_id)];
|
||||
for argument in arguments {
|
||||
if *argument == path_argument {
|
||||
run_arguments.extend([String::from("@@"), argument.clone(), String::from("@@")]);
|
||||
} else {
|
||||
run_arguments.push(argument.clone());
|
||||
}
|
||||
}
|
||||
|
||||
run_arguments
|
||||
}
|
||||
|
||||
/// The application macOS opens a document with.
|
||||
#[cfg(target_os = "macos")]
|
||||
struct MacApplication {
|
||||
/// The bundle identifier, such as `com.apple.Preview`.
|
||||
bundle_id: String,
|
||||
|
||||
/// Where the application bundle lies, such as `/Applications/PdfCraft.app`.
|
||||
bundle_path: String,
|
||||
}
|
||||
|
||||
/// Asks LaunchServices which application opens this document.
|
||||
///
|
||||
/// The document itself is asked about rather than its type, so a program the user picked for this
|
||||
/// one file through Get Info is found as well. Only strings leave this function: the Objective-C
|
||||
/// objects behind them must not be held across an await, or the request would no longer be `Send`.
|
||||
/// The lookup runs in a pool of its own because a Tokio worker has none to collect what the
|
||||
/// framework autoreleases.
|
||||
#[cfg(target_os = "macos")]
|
||||
fn macos_document_application(path: &Path) -> Option<MacApplication> {
|
||||
use objc2::rc::autoreleasepool;
|
||||
use objc2_app_kit::NSWorkspace;
|
||||
use objc2_foundation::{NSBundle, NSString, NSURL};
|
||||
|
||||
autoreleasepool(|_| {
|
||||
let document = NSURL::fileURLWithPath(&NSString::from_str(&path.to_string_lossy()));
|
||||
let application = NSWorkspace::sharedWorkspace().URLForApplicationToOpenURL(&document)?;
|
||||
let bundle_id = NSBundle::bundleWithURL(&application)?.bundleIdentifier()?.to_string();
|
||||
let bundle_path = application.path()?.to_string();
|
||||
|
||||
Some(MacApplication { bundle_id, bundle_path })
|
||||
})
|
||||
}
|
||||
|
||||
/// Maps a bundle identifier onto the way its program wants to hear about a page.
|
||||
#[cfg(any(target_os = "macos", test))]
|
||||
fn macos_page_argument(bundle_id: &str) -> Option<PageArgument> {
|
||||
bundle_id.eq_ignore_ascii_case("ai.storyteller.pdfcraft").then_some(PageArgument::PdfCraftPage)
|
||||
}
|
||||
|
||||
/// Builds the arguments with which `open` starts the application and hands it the page arguments.
|
||||
///
|
||||
/// `-n` starts a new instance even when the application already runs. Without it, a running
|
||||
/// application only comes to the front, and the arguments after `--args` are lost, document and
|
||||
/// page alike.
|
||||
#[cfg(any(target_os = "macos", test))]
|
||||
fn macos_open_arguments(bundle_path: &str, page_arguments: Vec<String>) -> Vec<String> {
|
||||
let mut arguments = vec![String::from("-n"), String::from("-a"), String::from(bundle_path), String::from("--args")];
|
||||
arguments.extend(page_arguments);
|
||||
arguments
|
||||
}
|
||||
|
||||
/// Applies an optional file type filter to a FileDialogBuilder.
|
||||
fn apply_filter<R: tauri::Runtime>(file_dialog: FileDialogBuilder<R>, filter: &Option<FileTypeFilter>) -> FileDialogBuilder<R> {
|
||||
match filter {
|
||||
@@ -1274,6 +1448,12 @@ mod tests {
|
||||
page_arguments(PageArgument::ZathuraPage, document, 12).unwrap(),
|
||||
vec![String::from("-P"), String::from("12"), String::from("/docs/handbook.pdf")],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
page_arguments(PageArgument::PdfCraftPage, document, 12).unwrap(),
|
||||
vec![String::from("--page"), String::from("12"), String::from("/docs/handbook.pdf")],
|
||||
"PdfCraft counts from one, so page twelve stays twelve.",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1283,6 +1463,8 @@ mod tests {
|
||||
assert_eq!(windows_page_argument("FoxitPDFEditor.Document"), Some(PageArgument::FoxitOpenAction));
|
||||
assert_eq!(windows_page_argument("FoxitReader.Document"), Some(PageArgument::FoxitOpenAction));
|
||||
assert_eq!(windows_page_argument("Applications\\FoxitPDFReader.exe"), Some(PageArgument::FoxitOpenAction), "A program picked through 'Open with' is registered by its file name.");
|
||||
assert_eq!(windows_page_argument("PdfCraft.Document"), Some(PageArgument::PdfCraftPage));
|
||||
assert_eq!(windows_page_argument("Applications\\pdfcraft.exe"), Some(PageArgument::PdfCraftPage), "The portable PdfCraft is picked through 'Open with'.");
|
||||
assert_eq!(windows_page_argument("MSEdgePDF"), Some(PageArgument::UrlFragment));
|
||||
assert_eq!(windows_page_argument("ChromePDF"), Some(PageArgument::UrlFragment));
|
||||
assert_eq!(windows_page_argument("FirefoxPDF"), Some(PageArgument::UrlFragment));
|
||||
@@ -1303,6 +1485,11 @@ mod tests {
|
||||
"The file name the command names is dropped, because the page arguments name the file themselves.",
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
executable_from_command(r#""C:\Program Files\PdfCraft\pdfcraft.exe" "%1""#).as_deref(),
|
||||
Some(r"C:\Program Files\PdfCraft\pdfcraft.exe"),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
executable_from_command(r#"C:\Windows\System32\viewer.exe "%1""#).as_deref(),
|
||||
Some(r"C:\Windows\System32\viewer.exe"),
|
||||
@@ -1321,9 +1508,55 @@ mod tests {
|
||||
|
||||
assert_eq!(linux_page_aware_program("okularApplication_pdf.desktop"), Some((String::from("okular"), PageArgument::OkularPage)));
|
||||
assert_eq!(linux_page_aware_program("org.pwmt.zathura.desktop"), Some((String::from("zathura"), PageArgument::ZathuraPage)));
|
||||
assert_eq!(linux_page_aware_program("ai.storyteller.pdfcraft.desktop"), Some((String::from("pdfcraft"), PageArgument::PdfCraftPage)));
|
||||
assert_eq!(linux_page_aware_program("firefox.desktop"), Some((String::from("firefox"), PageArgument::UrlFragment)));
|
||||
assert_eq!(linux_page_aware_program("google-chrome.desktop"), Some((String::from("google-chrome"), PageArgument::UrlFragment)));
|
||||
assert_eq!(linux_page_aware_program("chromium_chromium.desktop"), Some((String::from("chromium"), PageArgument::UrlFragment)));
|
||||
assert_eq!(linux_page_aware_program("com.example.SomeViewer.desktop"), None, "A viewer we know nothing about opens its first page.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn linux_knows_the_flatpaks_it_can_start_with_a_page() {
|
||||
assert_eq!(linux_flatpak_app_id("ai.storyteller.pdfcraft.desktop"), Some("ai.storyteller.pdfcraft"), "The Flatpak keeps the desktop entry of the native package.");
|
||||
assert_eq!(linux_flatpak_app_id("org.gnome.Evince.desktop"), None, "Only the Flatpaks we start ourselves are named.");
|
||||
}
|
||||
|
||||
/// The Flatpak can read only Documents and Downloads, so the file has to travel through the
|
||||
/// document portal, and the page arguments must reach the program unchanged.
|
||||
#[test]
|
||||
fn a_flatpak_receives_the_file_through_the_document_portal() {
|
||||
let document = Path::new("/srv/Größere Übersicht.pdf");
|
||||
let arguments = page_arguments(PageArgument::PdfCraftPage, document, 12).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
flatpak_run_arguments("ai.storyteller.pdfcraft", &arguments, document),
|
||||
vec![
|
||||
String::from("run"), String::from("--file-forwarding"), String::from("ai.storyteller.pdfcraft"),
|
||||
String::from("--page"), String::from("12"),
|
||||
String::from("@@"), String::from("/srv/Größere Übersicht.pdf"), String::from("@@"),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macos_recognizes_the_programs_it_can_send_to_a_page() {
|
||||
assert_eq!(macos_page_argument("ai.storyteller.pdfcraft"), Some(PageArgument::PdfCraftPage));
|
||||
assert_eq!(macos_page_argument("com.apple.Preview"), None, "Preview takes no page, and 'open' drops the one a URL names.");
|
||||
assert_eq!(macos_page_argument("com.adobe.Reader"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macos_starts_a_new_instance_which_keeps_the_page() {
|
||||
let document = Path::new("/Users/someone/Größere Übersicht.pdf");
|
||||
let arguments = page_arguments(PageArgument::PdfCraftPage, document, 12).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
macos_open_arguments("/Applications/PdfCraft.app", arguments),
|
||||
vec![
|
||||
String::from("-n"), String::from("-a"), String::from("/Applications/PdfCraft.app"), String::from("--args"),
|
||||
String::from("--page"), String::from("12"), String::from("/Users/someone/Größere Übersicht.pdf"),
|
||||
],
|
||||
"Without -n, a running PdfCraft only comes to the front and loses document and page.",
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user