mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-27 13:44:26 +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
@@ -51,6 +51,7 @@
|
||||
- Improved the search results from external data sources (ERI servers), which now tell the AI what kind of content a result comes from, for example, a dictionary or a book chapter, when the server names it.
|
||||
- Improved images in chats and assistants. AI Studio now scales large photos down before it sends them to the AI, so they no longer fail at providers with size limits, such as Anthropic. Photos also no longer carry their location data along.
|
||||
- Improved attaching images to chats and assistants. AI Studio now converts BMP and TIFF images into a format the AI understands, and it tells you right away when it cannot give an image to the AI, for example, in HEIC or SVG format.
|
||||
- Improved the jump to the right page for the sources of your own documents (RAG): when you open PDFs with PdfCraft, the document now opens directly at the page where the passage was found, on Windows, macOS, and Linux. Each source opens in a PdfCraft window of its own.
|
||||
- Changed the banner on the start page to a fresh design that shows what AI Studio can do today.
|
||||
- Changed when a chat you add to a workspace in the workspace list appears there. Like any other new chat, it now appears once there is something to keep, for example, your first message, instead of right away.
|
||||
- Fixed a warning about the confidence level that stayed in the dialog for adding a local data source, even after you chose an embedding provider that meets it. The dialog now checks again whenever you change either of them, so a warning also appears when you switch to a provider that falls short.
|
||||
|
||||
+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