Skip to content
Open
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
2 changes: 1 addition & 1 deletion crates/tauri/scripts/bundle.global.js

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions crates/tauri/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,9 @@ pub enum Error {
/// tokio oneshot channel failed to receive message
#[error(transparent)]
TokioOneshotRecv(#[from] tokio::sync::oneshot::error::RecvError),
/// Invalid Path Error.
#[error("invalid path: {0}")]
InvalidPath(String),
}

impl From<getrandom::Error> for Error {
Expand Down
2 changes: 2 additions & 0 deletions crates/tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ pub mod process;
/// The allowlist scopes.
pub mod scope;
mod state;
/// Recent Doc APIs.
pub mod recent_doc;

#[cfg(all(desktop, feature = "tray-icon"))]
#[cfg_attr(docsrs, doc(cfg(all(desktop, feature = "tray-icon"))))]
Expand Down
1 change: 1 addition & 0 deletions crates/tauri/src/recent_doc/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub(crate) mod plugin;
186 changes: 186 additions & 0 deletions crates/tauri/src/recent_doc/plugin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
use crate::command;
/// recent document plugin
use crate::{
plugin::{Builder, TauriPlugin},
Runtime,
};


#[command(root = "crate")]
/// add recent
fn add_recent_document(path: &str) -> crate::Result<()> {
#[cfg(target_os = "windows")]
{
use windows::{core::*, Win32::Foundation::*, Win32::System::Com::*, Win32::UI::Shell::*};
use std::os::windows::ffi::{OsStrExt, OsStringExt};
unsafe {
let path_wide: Vec<u16> = OsStr::new(path)
.encode_wide()
.chain(iter::once(0))
.collect();
let item = SHCreateItemFromParsingName(path_wide.as_str(), None).ok()?;

let info: SHARDAPPIDINFO = SHARDAPPIDINFO {
psi: Some(item),
pszAppID: GetAppUserModelId()?,
};

SHAddToRecentDocs(SHARD_APPIDINFO, &info as *const _);
}
}

#[cfg(target_os = "macos")]
{
use objc2_appkit::NSDocumentController;
use objc2::rc::Id;
use objc2::foundation::{NSString, NSURL};

unsafe {
let ns_path = NSURL::file_url_with_path(&NSString::from_str(path));
let controller: Id<NSDocumentController> = NSDocumentController::shared_document_controller();
controller.note_new_recent_document_url(&ns_path);
}
}

#[cfg(unix)]
{
// Recent documents are not supported on Unix-like systems.
println!("Recent documents are not supported on Unix-like systems.");
}

Ok(())
}

#[command(root = "crate")]
pub fn clear_recent_documents() -> crate::Result<()> {
#[cfg(target_os = "windows")]
{
use windows::Win32::UI::Shell::*;
unsafe {
SHAddToRecentDocs(SHARD_APPIDINFO, std::ptr::null());
}
}

#[cfg(target_os = "macos")]
{
use objc2_appkit::NSDocumentController;
use objc2::rc::Id;

unsafe {
let controller: Id<NSDocumentController> = NSDocumentController::shared_document_controller();
controller.clear_recent_documents();
}
}

#[cfg(unix)]
{
// Recent documents are not supported on Unix-like systems.
println!("Recent documents are not supported on Unix-like systems.");
}

Ok(())
}

#[command(root = "crate")]
fn get_recent_documents() -> crate::Result<Vec<String>> {
let mut recent_docs = Vec::new();

#[cfg(target_os = "windows")]
{
use std::fs;
use std::path::{Path, PathBuf};
use windows::{core::*, Win32::Foundation::*, Win32::System::Com::*, Win32::UI::Shell::*};
unsafe {
let recent_path_ptr: PWSTR = SHGetKnownFolderPath(&FOLDERID_Recent, 0, None)?;

if !recent_path_ptr.is_null() {
let recent_path = PWSTR::from_raw(recent_path_ptr.0);
let recent_os_string = recent_path.to_string();
let recent_folder = PathBuf::from(recent_os_string);

if let Ok(entries) = fs::read_dir(recent_folder) {
for entry in entries.flatten() {
if let Ok(entry) = entry {
let path = entry.path();

if path.extension().and_then(|s| s.to_str()) == Some("lnk") {
if let Ok(resolved_path) = Self::resolve_shortcut(&path) {
recent_docs.push(resolved_path);
}
}
}
}
}

CoTaskMemFree(recent_path_ptr.0 as *mut _);
}
}
}

#[cfg(target_os = "macos")]
{
use objc2_appkit::NSDocumentController;
use objc2::rc::Id;
use objc2::foundation::{NSArray, NSString};

unsafe {
let controller: Id<NSDocumentController> = NSDocumentController::shared_document_controller();
let urls: Id<NSArray<NSString>> = controller.recent_document_urls();

for i in 0..urls.count() {
if let Some(ns_string) = urls.object_at(i) {
recent_docs.push(ns_string.to_string());
}
}
}
}

#[cfg(unix)]
{
// Recent documents are not supported on Unix-like systems.
println!("Recent documents are not supported on Unix-like systems.");
}

Ok(recent_docs)
}

#[cfg(target_os = "windows")]
fn resolve_shortcut(lnk_path: &Path) -> crate::Result<String> {
let path_string = String::new();

use windows::{core::*, Win32::Foundation::*, Win32::System::Com::*, Win32::UI::Shell::*};
unsafe {
// Create IShellLink instance
let shell_link: IShellLinkW =
CoCreateInstance(&ShellLink as *const _, None, CLSCTX_INPROC_SERVER)?;

// Get IPersistFile interface
let persist_file: IPersistFile = shell_link.cast()?;

// Convert path to wide string
let path_wide = HSTRING::from(lnk_path);

// Load the shortcut file
persist_file.Load(&path_wide, STGM_READ)?;

// Resolve the target path
let mut target_path = [0u16; MAX_PATH as usize];
shell_link.GetPath(&mut target_path, None, None, 0)?;

// Convert wide string to regular string
let path_string = PWSTR::from_raw(target_path.as_mut_ptr()).to_string()?;
}

Ok(path_string)
}

pub(crate) fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("recent_doc")
.invoke_handler(crate::generate_handler![
#![plugin(recent_doc)]
add_recent_document,
clear_recent_documents,
get_recent_documents
])
.build()
}
2 changes: 1 addition & 1 deletion packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"eslint-plugin-security": "3.0.1",
"fast-glob": "3.3.3",
"globals": "^16.2.0",
"rollup": "4.53.2",
"rollup": "4.53.3",
"tslib": "^2.8.1",
"typescript": "^5.8.3",
"typescript-eslint": "^8.34.1"
Expand Down
2 changes: 2 additions & 0 deletions packages/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import * as image from './image'
import * as menu from './menu'
import * as mocks from './mocks'
import * as path from './path'
import * as recentDoc from './recentDoc'
import * as tray from './tray'
import * as webview from './webview'
import * as webviewWindow from './webviewWindow'
Expand All @@ -45,6 +46,7 @@ export {
menu,
mocks,
path,
recentDoc,
tray,
webview,
webviewWindow,
Expand Down
19 changes: 19 additions & 0 deletions packages/api/src/recentDoc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { invoke } from './core';

async function addRecentDocument(path: string): Promise<void> {
return invoke('plugin:recent_doc|add_recent_document', { path });
}

async function getRecentDocuments(): Promise<string[]> {
return invoke<string[]>('plugin:recent_doc|get_recent_documents');
}

async function clearRecentDocuments(): Promise<void> {
return invoke('plugin:recent_doc|clear_recent_documents');
}

export {
addRecentDocument,
getRecentDocuments,
clearRecentDocuments
}
Loading