refactor!: modernize toolchain to Rust 2024 edition

- Bump workspace edition 2021 → 2024 and MSRV 1.78 → 1.85.
- Add [workspace.lints] block with project-wide rust/clippy policy:
  unsafe_op_in_unsafe_fn warn, unused_must_use deny, dbg_macro deny.
- Migrate all extern "C" blocks to unsafe extern "C" (Edition 2024 req).
- Migrate all #[no_mangle] to #[unsafe(no_mangle)] (Edition 2024 req).
- Wrap unsafe operations inside unsafe fn bodies in explicit unsafe blocks
  via cargo fix.
- Switch RefEntry::path from Vec<usize> to SmallVec<[usize; 8]> with
  serde+union features. Eliminates per-ref heap allocation for typical
  accessibility tree depths (<8) while serializing identically to the
  prior Vec form.
- Strip non-doc-comments from workspace Cargo.toml.

BREAKING CHANGE: minimum supported Rust version is now 1.85. Downstream
consumers building from source must upgrade their toolchain.
This commit is contained in:
Lahfir 2026-05-14 15:14:56 -07:00
parent 1f1d8d1b53
commit 11427b271a
147 changed files with 661 additions and 548 deletions

View file

@ -3,8 +3,8 @@ members = ["crates/core", "crates/macos", "crates/windows", "crates/linux", "cr
resolver = "2"
[workspace.package]
edition = "2021"
rust-version = "1.78"
edition = "2024"
rust-version = "1.85"
license = "Apache-2.0"
version = "0.1.14" # x-release-please-version
@ -18,8 +18,29 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
base64 = "0.22"
rustc-hash = "2.1"
libc = "0.2"
smallvec = { version = "1.13", features = ["serde", "union"] }
agent-desktop-core = { path = "crates/core" }
[workspace.lints.rust]
unsafe_op_in_unsafe_fn = "warn"
unreachable_pub = "allow"
unused_must_use = "deny"
let_underscore_drop = "allow"
[workspace.lints.clippy]
dbg_macro = "deny"
todo = "warn"
needless_collect = "warn"
redundant_clone = "allow"
unwrap_used = "allow"
expect_used = "allow"
needless_pass_by_value = "allow"
manual_let_else = "allow"
items_after_statements = "allow"
collapsible_if = "allow"
print_stdout = "allow"
print_stderr = "allow"
[profile.release]
opt-level = "z"
lto = true
@ -27,8 +48,6 @@ codegen-units = 1
strip = true
panic = "abort"
# Fast build for CI: skip LTO and size optimisations, keep debug info for
# better test failure messages. Inherits release settings not listed here.
[profile.ci]
inherits = "release"
opt-level = 1
@ -37,9 +56,6 @@ codegen-units = 16
strip = false
debug = 1
# Dedicated profile for the FFI cdylib. Overrides panic = "abort" so
# catch_unwind actually catches at the extern "C" boundary — the CLI
# release profile stays abort for size reasons.
[profile.release-ffi]
inherits = "release"
panic = "unwind"

View file

@ -12,3 +12,7 @@ tracing.workspace = true
rustc-hash.workspace = true
base64.workspace = true
libc.workspace = true
smallvec.workspace = true
[lints]
workspace = true

View file

@ -1,4 +1,5 @@
use crate::{
PermissionReport, PermissionState,
action::{
ActionRequest, ActionResult, DragParams, ElementState, KeyCombo, MouseEvent, WindowOp,
},
@ -6,7 +7,6 @@ use crate::{
node::{AccessibilityNode, AppInfo, Rect, SurfaceInfo, WindowInfo},
notification::{NotificationFilter, NotificationIdentity, NotificationInfo},
refs::RefEntry,
PermissionReport, PermissionState,
};
use std::marker::PhantomData;

View file

@ -1,7 +1,7 @@
use crate::{
action::{Action, ActionRequest},
adapter::PlatformAdapter,
commands::helpers::{execute_ref_action, RefArgs},
commands::helpers::{RefArgs, execute_ref_action},
error::AppError,
};
use serde_json::Value;

View file

@ -1,7 +1,7 @@
use crate::{
action::{Action, ActionRequest},
adapter::PlatformAdapter,
commands::helpers::{execute_ref_action, RefArgs},
commands::helpers::{RefArgs, execute_ref_action},
error::AppError,
};
use serde_json::Value;

View file

@ -1,7 +1,7 @@
use crate::{
action::{Action, ActionRequest},
adapter::PlatformAdapter,
commands::helpers::{execute_ref_action, RefArgs},
commands::helpers::{RefArgs, execute_ref_action},
error::AppError,
};
use serde_json::Value;

View file

@ -1,5 +1,5 @@
use crate::{adapter::PlatformAdapter, error::AppError};
use serde_json::{json, Value};
use serde_json::{Value, json};
pub fn execute(adapter: &dyn PlatformAdapter) -> Result<Value, AppError> {
adapter.clear_clipboard()?;

View file

@ -1,5 +1,5 @@
use crate::{adapter::PlatformAdapter, error::AppError};
use serde_json::{json, Value};
use serde_json::{Value, json};
pub fn execute(adapter: &dyn PlatformAdapter) -> Result<Value, AppError> {
let text = adapter.get_clipboard()?;

View file

@ -1,5 +1,5 @@
use crate::{adapter::PlatformAdapter, error::AppError};
use serde_json::{json, Value};
use serde_json::{Value, json};
pub fn execute(text: String, adapter: &dyn PlatformAdapter) -> Result<Value, AppError> {
adapter.set_clipboard(&text)?;

View file

@ -1,5 +1,5 @@
use crate::{adapter::PlatformAdapter, error::AppError};
use serde_json::{json, Value};
use serde_json::{Value, json};
const PROTECTED_PROCESSES: &[&str] = &["loginwindow", "windowserver", "dock", "launchd", "finder"];

View file

@ -1,7 +1,7 @@
use crate::{
action::{Action, ActionRequest},
adapter::PlatformAdapter,
commands::helpers::{execute_ref_action, RefArgs},
commands::helpers::{RefArgs, execute_ref_action},
error::AppError,
};
use serde_json::Value;

View file

@ -1,5 +1,5 @@
use crate::{adapter::PlatformAdapter, error::AppError};
use serde_json::{json, Value};
use serde_json::{Value, json};
pub struct DismissAllNotificationsArgs {
pub app: Option<String>,

View file

@ -1,5 +1,5 @@
use crate::{adapter::PlatformAdapter, error::AppError};
use serde_json::{json, Value};
use serde_json::{Value, json};
pub struct DismissNotificationArgs {
pub index: usize,

View file

@ -1,7 +1,7 @@
use crate::{
action::{Action, ActionRequest},
adapter::PlatformAdapter,
commands::helpers::{execute_ref_action, RefArgs},
commands::helpers::{RefArgs, execute_ref_action},
error::AppError,
};
use serde_json::Value;

View file

@ -4,7 +4,7 @@ use crate::{
commands::helpers::resolve_ref,
error::AppError,
};
use serde_json::{json, Value};
use serde_json::{Value, json};
pub struct DragArgs {
pub from_ref: Option<String>,

View file

@ -1,7 +1,7 @@
use crate::{
action::{Action, ActionRequest},
adapter::PlatformAdapter,
commands::helpers::{execute_ref_action, RefArgs},
commands::helpers::{RefArgs, execute_ref_action},
error::AppError,
};
use serde_json::Value;

View file

@ -2,7 +2,7 @@ use crate::{
adapter::PlatformAdapter, commands::search_text, error::AppError, node::AccessibilityNode,
snapshot,
};
use serde_json::{json, Value};
use serde_json::{Value, json};
const DEFAULT_LIMIT: usize = 50;

View file

@ -1,7 +1,7 @@
use crate::{
action::{Action, ActionRequest},
adapter::PlatformAdapter,
commands::helpers::{execute_ref_action, RefArgs},
commands::helpers::{RefArgs, execute_ref_action},
error::AppError,
};
use serde_json::Value;

View file

@ -3,7 +3,7 @@ use crate::{
error::{AdapterError, AppError, ErrorCode},
node::WindowInfo,
};
use serde_json::{json, Value};
use serde_json::{Value, json};
use std::time::{Duration, Instant};
#[cfg(not(test))]

View file

@ -1,5 +1,5 @@
use crate::{adapter::PlatformAdapter, commands::helpers::resolve_ref, error::AppError};
use serde_json::{json, Value};
use serde_json::{Value, json};
pub struct GetArgs {
pub ref_id: String,

View file

@ -8,7 +8,7 @@ use crate::{
refs_store::RefStore,
window_lookup,
};
use serde_json::{json, Value};
use serde_json::{Value, json};
pub struct AppArgs {
pub app: Option<String>,
@ -137,8 +137,8 @@ mod tests {
use crate::node::AppInfo;
use crate::refs::RefMap;
use crate::refs_test_support::HomeGuard;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Mutex;
use std::sync::atomic::{AtomicU32, Ordering};
struct ReleaseCountingAdapter {
releases: AtomicU32,
@ -215,7 +215,7 @@ mod tests {
source_app: None,
source_window_title: None,
root_ref: None,
path: Vec::new(),
path: smallvec::SmallVec::new(),
}
}

View file

@ -4,7 +4,7 @@ use crate::{
commands::helpers::resolve_ref,
error::AppError,
};
use serde_json::{json, Value};
use serde_json::{Value, json};
pub struct HoverArgs {
pub ref_id: Option<String>,

View file

@ -2,7 +2,7 @@ use crate::{
action::ElementState, adapter::PlatformAdapter, commands::helpers::resolve_ref,
error::AppError, refs::RefEntry,
};
use serde_json::{json, Value};
use serde_json::{Value, json};
pub struct IsArgs {
pub ref_id: String,
@ -130,7 +130,7 @@ mod tests {
source_app: None,
source_window_title: None,
root_ref: None,
path: Vec::new(),
path: smallvec::SmallVec::new(),
}
}
@ -252,7 +252,7 @@ mod tests {
source_app: None,
source_window_title: None,
root_ref: None,
path: Vec::new(),
path: smallvec::SmallVec::new(),
});
let adapter = LiveStateAdapter {
state: Mutex::new(None),

View file

@ -1,5 +1,5 @@
use crate::{adapter::PlatformAdapter, commands::press::parse_combo, error::AppError};
use serde_json::{json, Value};
use serde_json::{Value, json};
pub struct KeyDownArgs {
pub combo: String,

View file

@ -1,5 +1,5 @@
use crate::{adapter::PlatformAdapter, commands::press::parse_combo, error::AppError};
use serde_json::{json, Value};
use serde_json::{Value, json};
pub struct KeyUpArgs {
pub combo: String,

View file

@ -1,5 +1,5 @@
use crate::{adapter::PlatformAdapter, commands::search_text, error::AppError};
use serde_json::{json, Value};
use serde_json::{Value, json};
pub struct ListAppsArgs {
pub app: Option<String>,

View file

@ -1,5 +1,5 @@
use crate::{adapter::PlatformAdapter, error::AppError, notification::NotificationFilter};
use serde_json::{json, Value};
use serde_json::{Value, json};
pub struct ListNotificationsArgs {
pub app: Option<String>,

View file

@ -1,5 +1,5 @@
use crate::{adapter::PlatformAdapter, commands::helpers::resolve_app_pid, error::AppError};
use serde_json::{json, Value};
use serde_json::{Value, json};
pub struct ListSurfacesArgs {
pub app: Option<String>,

View file

@ -1,7 +1,7 @@
use crate::{
action::WindowOp,
adapter::PlatformAdapter,
commands::helpers::{window_op_command, AppArgs},
commands::helpers::{AppArgs, window_op_command},
error::AppError,
};
use serde_json::Value;

View file

@ -1,7 +1,7 @@
use crate::{
action::WindowOp,
adapter::PlatformAdapter,
commands::helpers::{window_op_command, AppArgs},
commands::helpers::{AppArgs, window_op_command},
error::AppError,
};
use serde_json::Value;

View file

@ -3,7 +3,7 @@ use crate::{
adapter::PlatformAdapter,
error::AppError,
};
use serde_json::{json, Value};
use serde_json::{Value, json};
pub struct MouseClickArgs {
pub x: f64,

View file

@ -3,7 +3,7 @@ use crate::{
adapter::PlatformAdapter,
error::AppError,
};
use serde_json::{json, Value};
use serde_json::{Value, json};
pub struct MouseDownArgs {
pub x: f64,

View file

@ -3,7 +3,7 @@ use crate::{
adapter::PlatformAdapter,
error::AppError,
};
use serde_json::{json, Value};
use serde_json::{Value, json};
pub struct MouseMoveArgs {
pub x: f64,

View file

@ -3,7 +3,7 @@ use crate::{
adapter::PlatformAdapter,
error::AppError,
};
use serde_json::{json, Value};
use serde_json::{Value, json};
pub struct MouseUpArgs {
pub x: f64,

View file

@ -2,7 +2,7 @@ use crate::{
action::WindowOp, adapter::PlatformAdapter, commands::helpers::resolve_window_for_app,
error::AppError,
};
use serde_json::{json, Value};
use serde_json::{Value, json};
pub struct MoveWindowArgs {
pub app: Option<String>,

View file

@ -1,5 +1,5 @@
use crate::{adapter::PlatformAdapter, error::AppError, notification::NotificationIdentity};
use serde_json::{json, Value};
use serde_json::{Value, json};
pub struct NotificationActionArgs {
pub index: usize,

View file

@ -1,5 +1,5 @@
use crate::{adapter::PlatformAdapter, error::AppError, PermissionReport};
use serde_json::{json, Value};
use crate::{PermissionReport, adapter::PlatformAdapter, error::AppError};
use serde_json::{Value, json};
pub struct PermissionsArgs {
pub request: bool,

View file

@ -59,7 +59,7 @@ pub fn parse_combo(s: &str) -> Result<KeyCombo, AppError> {
other => {
return Err(AppError::invalid_input(format!(
"Unknown modifier: '{other}'"
)))
)));
}
};
modifiers.push(modifier);

View file

@ -57,7 +57,7 @@ fn snapshot_id() -> String {
source_app: None,
source_window_title: None,
root_ref: None,
path: Vec::new(),
path: smallvec::SmallVec::new(),
});
RefStore::new().unwrap().save_new_snapshot(&refmap).unwrap()
}

View file

@ -2,7 +2,7 @@ use crate::{
action::WindowOp, adapter::PlatformAdapter, commands::helpers::resolve_window_for_app,
error::AppError,
};
use serde_json::{json, Value};
use serde_json::{Value, json};
pub struct ResizeWindowArgs {
pub app: Option<String>,

View file

@ -1,7 +1,7 @@
use crate::{
action::WindowOp,
adapter::PlatformAdapter,
commands::helpers::{window_op_command, AppArgs},
commands::helpers::{AppArgs, window_op_command},
error::AppError,
};
use serde_json::Value;

View file

@ -1,12 +1,12 @@
use crate::{
action::{Action, ActionRequest},
adapter::{PlatformAdapter, SnapshotSurface, TreeOptions, WindowFilter},
commands::helpers::{resolve_ref, RefArgs},
commands::helpers::{RefArgs, resolve_ref},
error::AppError,
refs::RefEntry,
snapshot,
};
use serde_json::{json, Value};
use serde_json::{Value, json};
pub fn execute(args: RefArgs, adapter: &dyn PlatformAdapter) -> Result<Value, AppError> {
let (entry, handle) = resolve_ref(&args.ref_id, args.snapshot_id.as_deref(), adapter)?;
@ -180,7 +180,7 @@ mod tests {
source_app,
source_window_title: None,
root_ref: None,
path: Vec::new(),
path: smallvec::SmallVec::new(),
});
RefStore::new().unwrap().save_new_snapshot(&refmap).unwrap()
}
@ -223,9 +223,11 @@ mod tests {
assert_eq!(value["action"], "right_click");
assert_eq!(value["menu_probe"]["ok"], false);
assert_eq!(value["menu_probe"]["error"]["code"], "ELEMENT_NOT_FOUND");
assert!(value["menu_probe"]["error"]["suggestion"]
.as_str()
.unwrap()
.contains("snapshot --surface menu"));
assert!(
value["menu_probe"]["error"]["suggestion"]
.as_str()
.unwrap()
.contains("snapshot --surface menu")
);
}
}

View file

@ -3,7 +3,7 @@ use crate::{
error::AppError,
};
use base64::Engine;
use serde_json::{json, Value};
use serde_json::{Value, json};
use std::path::PathBuf;
pub struct ScreenshotArgs {

View file

@ -1,7 +1,7 @@
use crate::{
action::{Action, ActionRequest},
adapter::PlatformAdapter,
commands::helpers::{execute_ref_action, RefArgs},
commands::helpers::{RefArgs, execute_ref_action},
error::AppError,
};
use serde_json::Value;

View file

@ -1,5 +1,5 @@
use crate::error::AppError;
use serde_json::{json, Value};
use serde_json::{Value, json};
const SKILL_DESKTOP_MAIN: &str = include_str!("../../../../skills/agent-desktop/SKILL.md");
const SKILL_DESKTOP_REF_OBSERVATION: &str =
@ -43,11 +43,26 @@ const SKILLS: &[Skill] = &[
summary: "Primary guide. Snapshot/ref loop, JSON envelope, 54 commands across observation, interaction, keyboard/mouse, app lifecycle, notifications, clipboard, wait.",
main: SKILL_DESKTOP_MAIN,
refs: &[
SkillRef { rel_path: "references/commands-observation.md", body: SKILL_DESKTOP_REF_OBSERVATION },
SkillRef { rel_path: "references/commands-interaction.md", body: SKILL_DESKTOP_REF_INTERACTION },
SkillRef { rel_path: "references/commands-system.md", body: SKILL_DESKTOP_REF_SYSTEM },
SkillRef { rel_path: "references/workflows.md", body: SKILL_DESKTOP_REF_WORKFLOWS },
SkillRef { rel_path: "references/macos.md", body: SKILL_DESKTOP_REF_MACOS },
SkillRef {
rel_path: "references/commands-observation.md",
body: SKILL_DESKTOP_REF_OBSERVATION,
},
SkillRef {
rel_path: "references/commands-interaction.md",
body: SKILL_DESKTOP_REF_INTERACTION,
},
SkillRef {
rel_path: "references/commands-system.md",
body: SKILL_DESKTOP_REF_SYSTEM,
},
SkillRef {
rel_path: "references/workflows.md",
body: SKILL_DESKTOP_REF_WORKFLOWS,
},
SkillRef {
rel_path: "references/macos.md",
body: SKILL_DESKTOP_REF_MACOS,
},
],
},
Skill {
@ -56,10 +71,22 @@ const SKILLS: &[Skill] = &[
summary: "Embedding agent-desktop in another process via the C ABI. Build/link, error propagation, handle ownership, threading rules.",
main: SKILL_FFI_MAIN,
refs: &[
SkillRef { rel_path: "references/build-and-link.md", body: SKILL_FFI_REF_BUILD },
SkillRef { rel_path: "references/error-handling.md", body: SKILL_FFI_REF_ERRORS },
SkillRef { rel_path: "references/ownership.md", body: SKILL_FFI_REF_OWNERSHIP },
SkillRef { rel_path: "references/threading.md", body: SKILL_FFI_REF_THREADING },
SkillRef {
rel_path: "references/build-and-link.md",
body: SKILL_FFI_REF_BUILD,
},
SkillRef {
rel_path: "references/error-handling.md",
body: SKILL_FFI_REF_ERRORS,
},
SkillRef {
rel_path: "references/ownership.md",
body: SKILL_FFI_REF_OWNERSHIP,
},
SkillRef {
rel_path: "references/threading.md",
body: SKILL_FFI_REF_THREADING,
},
],
},
];

View file

@ -4,7 +4,7 @@ use crate::{
error::AppError,
snapshot, snapshot_ref,
};
use serde_json::{json, Value};
use serde_json::{Value, json};
pub struct SnapshotArgs {
pub app: Option<String>,

View file

@ -1,11 +1,11 @@
use crate::{
PermissionReport,
adapter::PlatformAdapter,
commands::permissions::{self, PermissionsArgs},
error::AppError,
refs_store::RefStore,
PermissionReport,
};
use serde_json::{json, Value};
use serde_json::{Value, json};
pub fn execute(adapter: &dyn PlatformAdapter) -> Result<Value, AppError> {
let report = adapter.permission_report();

View file

@ -1,7 +1,7 @@
use crate::{
action::{Action, ActionRequest},
adapter::PlatformAdapter,
commands::helpers::{execute_ref_action, RefArgs},
commands::helpers::{RefArgs, execute_ref_action},
error::AppError,
};
use serde_json::Value;

View file

@ -1,7 +1,7 @@
use crate::{
action::{Action, ActionRequest},
adapter::PlatformAdapter,
commands::helpers::{execute_ref_action, RefArgs},
commands::helpers::{RefArgs, execute_ref_action},
error::AppError,
};
use serde_json::Value;

View file

@ -1,7 +1,7 @@
use crate::{
action::{Action, ActionRequest},
adapter::PlatformAdapter,
commands::helpers::{execute_ref_action, RefArgs},
commands::helpers::{RefArgs, execute_ref_action},
error::AppError,
};
use serde_json::Value;

View file

@ -1,5 +1,5 @@
use crate::error::AppError;
use serde_json::{json, Value};
use serde_json::{Value, json};
pub struct VersionArgs {
pub json: bool,

View file

@ -8,7 +8,7 @@ use crate::{
refs_store::RefStore,
snapshot,
};
use serde_json::{json, Value};
use serde_json::{Value, json};
use std::time::{Duration, Instant};
pub struct WaitArgs {

View file

@ -40,7 +40,7 @@ fn snapshot_with_one_ref() -> String {
source_app: None,
source_window_title: None,
root_ref: None,
path: Vec::new(),
path: smallvec::SmallVec::new(),
});
RefStore::new().unwrap().save_new_snapshot(&refmap).unwrap()
}
@ -142,7 +142,7 @@ fn latest_ref_cache_picks_up_newer_snapshot_after_refresh() {
source_app: None,
source_window_title: None,
root_ref: None,
path: Vec::new(),
path: smallvec::SmallVec::new(),
});
let second_id = store.save_new_snapshot(&second).unwrap();
assert_ne!(first_id, second_id);
@ -178,7 +178,7 @@ fn latest_ref_cache_debounces_consecutive_refreshes() {
source_app: None,
source_window_title: None,
root_ref: None,
path: Vec::new(),
path: smallvec::SmallVec::new(),
});
let _ = store.save_new_snapshot(&other).unwrap();

View file

@ -40,7 +40,7 @@ pub(crate) fn ref_entry_from_node(
source_app: source_app.map(str::to_string),
source_window_title: source_window_title.map(str::to_string),
root_ref,
path: path.to_vec(),
path: smallvec::SmallVec::from_slice(path),
}
}
@ -122,7 +122,6 @@ fn allocate_refs_at_path(
config: &RefAllocConfig,
path: &mut Vec<usize>,
) -> AccessibilityNode {
let root_ref_owned = config.root_ref_id.map(str::to_string);
let is_interactive = INTERACTIVE_ROLES.contains(&node.role.as_str());
if is_interactive {
@ -131,7 +130,7 @@ fn allocate_refs_at_path(
config.pid,
config.source_app,
config.source_window_title,
root_ref_owned.clone(),
config.root_ref_id.map(str::to_string),
path,
);
node.ref_id = Some(refmap.allocate(entry));
@ -304,7 +303,7 @@ mod tests {
let save_ref = out.children[0].ref_id.as_deref().unwrap();
let open_ref = out.children[1].children[0].ref_id.as_deref().unwrap();
assert_eq!(refmap.get(save_ref).unwrap().path, vec![0]);
assert_eq!(refmap.get(open_ref).unwrap().path, vec![1, 0]);
assert_eq!(refmap.get(save_ref).unwrap().path.as_slice(), [0]);
assert_eq!(refmap.get(open_ref).unwrap().path.as_slice(), [1, 0]);
}
}

View file

@ -1,13 +1,16 @@
use crate::error::AppError;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use std::cell::RefCell;
use std::collections::hash_map::RandomState;
use std::collections::HashMap;
use std::collections::hash_map::RandomState;
use std::hash::BuildHasher;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
pub type RefPath = SmallVec<[usize; 8]>;
pub(crate) const MAX_REFMAP_BYTES: u64 = 1_048_576;
static SNAPSHOT_COUNTER: AtomicU64 = AtomicU64::new(0);
@ -33,8 +36,8 @@ pub struct RefEntry {
pub source_window_title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub root_ref: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub path: Vec<usize>,
#[serde(default, skip_serializing_if = "SmallVec::is_empty")]
pub path: RefPath,
}
#[derive(Debug, Clone, Serialize, Deserialize)]

View file

@ -1,6 +1,6 @@
use crate::{
error::{AdapterError, AppError},
refs::{home_dir, new_snapshot_id, validate_snapshot_id, write_private_file, RefMap},
refs::{RefMap, home_dir, new_snapshot_id, validate_snapshot_id, write_private_file},
refs_lock::RefStoreLock,
};
use std::io::Read;

View file

@ -14,7 +14,7 @@ fn entry(role: &str, name: Option<&str>) -> RefEntry {
source_app: None,
source_window_title: None,
root_ref: None,
path: Vec::new(),
path: smallvec::SmallVec::new(),
}
}
@ -43,7 +43,7 @@ fn test_get_existing() {
source_app: Some("Finder".into()),
source_window_title: Some("Documents".into()),
root_ref: None,
path: Vec::new(),
path: smallvec::SmallVec::new(),
});
let retrieved = map.get(&ref_id).unwrap();
assert_eq!(retrieved.pid, 42);
@ -167,7 +167,7 @@ fn test_save_load_roundtrip_with_home_override() {
source_app: Some("TestApp".into()),
source_window_title: Some("Test Window".into()),
root_ref: None,
path: Vec::new(),
path: smallvec::SmallVec::new(),
});
map.save().expect("save should succeed under HomeGuard");
@ -195,7 +195,7 @@ fn test_refstore_snapshot_roundtrip_and_latest_pointer() {
source_app: Some("TestApp".into()),
source_window_title: Some("Test Window".into()),
root_ref: None,
path: Vec::new(),
path: smallvec::SmallVec::new(),
});
let snapshot_id = store.save_new_snapshot(&map).unwrap();

View file

@ -9,6 +9,7 @@ publish = false
crate-type = ["cdylib", "rlib"]
[dependencies]
smallvec.workspace = true
agent-desktop-core.workspace = true
[target.'cfg(target_os = "macos")'.dependencies]
@ -23,3 +24,6 @@ agent-desktop-linux = { path = "../linux" }
[build-dependencies]
cbindgen = "= 0.27.0"
[lints]
workspace = true

View file

@ -12,7 +12,7 @@
use std::panic::AssertUnwindSafe;
#[no_mangle]
#[unsafe(no_mangle)]
pub extern "C" fn spike_panicking_entry() -> i32 {
std::panic::catch_unwind(AssertUnwindSafe(|| -> i32 {
panic!("synthetic panic inside extern C fn");

View file

@ -20,95 +20,99 @@ fn direction_from_c(d: AdDirection) -> Direction {
const MAX_MODIFIERS_PER_COMBO: u32 = 4;
pub(crate) unsafe fn key_combo_from_c(k: &AdKeyCombo) -> Result<CoreKeyCombo, &'static str> {
let key = c_to_string(k.key).ok_or("key is null or invalid UTF-8")?;
unsafe {
let key = c_to_string(k.key).ok_or("key is null or invalid UTF-8")?;
if k.modifier_count > MAX_MODIFIERS_PER_COMBO {
return Err("modifier_count exceeds MAX_MODIFIERS_PER_COMBO (4)");
}
if k.modifier_count > 0 && k.modifiers.is_null() {
return Err("modifier_count > 0 but modifiers pointer is null");
}
let mut modifiers = Vec::with_capacity(k.modifier_count as usize);
if k.modifier_count > 0 {
let slice = std::slice::from_raw_parts(k.modifiers, k.modifier_count as usize);
for raw_modifier in slice {
let m = AdModifier::from_c(*raw_modifier).ok_or("invalid modifier discriminant")?;
let modifier = match m {
AdModifier::Cmd => Modifier::Cmd,
AdModifier::Ctrl => Modifier::Ctrl,
AdModifier::Alt => Modifier::Alt,
AdModifier::Shift => Modifier::Shift,
};
modifiers.push(modifier);
if k.modifier_count > MAX_MODIFIERS_PER_COMBO {
return Err("modifier_count exceeds MAX_MODIFIERS_PER_COMBO (4)");
}
if k.modifier_count > 0 && k.modifiers.is_null() {
return Err("modifier_count > 0 but modifiers pointer is null");
}
let mut modifiers = Vec::with_capacity(k.modifier_count as usize);
if k.modifier_count > 0 {
let slice = std::slice::from_raw_parts(k.modifiers, k.modifier_count as usize);
for raw_modifier in slice {
let m = AdModifier::from_c(*raw_modifier).ok_or("invalid modifier discriminant")?;
let modifier = match m {
AdModifier::Cmd => Modifier::Cmd,
AdModifier::Ctrl => Modifier::Ctrl,
AdModifier::Alt => Modifier::Alt,
AdModifier::Shift => Modifier::Shift,
};
modifiers.push(modifier);
}
}
Ok(CoreKeyCombo { key, modifiers })
}
Ok(CoreKeyCombo { key, modifiers })
}
pub(crate) unsafe fn action_from_c(action: &AdAction) -> Result<Action, &'static str> {
let kind = AdActionKind::from_c(action.kind).ok_or("invalid action kind discriminant")?;
match kind {
AdActionKind::Click => Ok(Action::Click),
AdActionKind::DoubleClick => Ok(Action::DoubleClick),
AdActionKind::RightClick => Ok(Action::RightClick),
AdActionKind::TripleClick => Ok(Action::TripleClick),
AdActionKind::SetFocus => Ok(Action::SetFocus),
AdActionKind::Expand => Ok(Action::Expand),
AdActionKind::Collapse => Ok(Action::Collapse),
AdActionKind::Toggle => Ok(Action::Toggle),
AdActionKind::Check => Ok(Action::Check),
AdActionKind::Uncheck => Ok(Action::Uncheck),
AdActionKind::ScrollTo => Ok(Action::ScrollTo),
AdActionKind::Clear => Ok(Action::Clear),
AdActionKind::Hover => Ok(Action::Hover),
AdActionKind::SetValue => {
let text = c_to_string(action.text).ok_or("text is null or invalid UTF-8")?;
Ok(Action::SetValue(text))
}
AdActionKind::Select => {
let text = c_to_string(action.text).ok_or("text is null or invalid UTF-8")?;
Ok(Action::Select(text))
}
AdActionKind::TypeText => {
let text = c_to_string(action.text).ok_or("text is null or invalid UTF-8")?;
Ok(Action::TypeText(text))
}
AdActionKind::Scroll => {
let raw_dir = AdDirection::from_c(action.scroll.direction)
.ok_or("invalid scroll direction discriminant")?;
let dir = direction_from_c(raw_dir);
Ok(Action::Scroll(dir, action.scroll.amount))
}
AdActionKind::PressKey => {
let combo = key_combo_from_c(&action.key)?;
Ok(Action::PressKey(combo))
}
AdActionKind::KeyDown => {
let combo = key_combo_from_c(&action.key)?;
Ok(Action::KeyDown(combo))
}
AdActionKind::KeyUp => {
let combo = key_combo_from_c(&action.key)?;
Ok(Action::KeyUp(combo))
}
AdActionKind::Drag => {
let params = CoreDragParams {
from: CorePoint {
x: action.drag.from.x,
y: action.drag.from.y,
},
to: CorePoint {
x: action.drag.to.x,
y: action.drag.to.y,
},
duration_ms: if action.drag.duration_ms == 0 {
None
} else {
Some(action.drag.duration_ms)
},
};
Ok(Action::Drag(params))
unsafe {
let kind = AdActionKind::from_c(action.kind).ok_or("invalid action kind discriminant")?;
match kind {
AdActionKind::Click => Ok(Action::Click),
AdActionKind::DoubleClick => Ok(Action::DoubleClick),
AdActionKind::RightClick => Ok(Action::RightClick),
AdActionKind::TripleClick => Ok(Action::TripleClick),
AdActionKind::SetFocus => Ok(Action::SetFocus),
AdActionKind::Expand => Ok(Action::Expand),
AdActionKind::Collapse => Ok(Action::Collapse),
AdActionKind::Toggle => Ok(Action::Toggle),
AdActionKind::Check => Ok(Action::Check),
AdActionKind::Uncheck => Ok(Action::Uncheck),
AdActionKind::ScrollTo => Ok(Action::ScrollTo),
AdActionKind::Clear => Ok(Action::Clear),
AdActionKind::Hover => Ok(Action::Hover),
AdActionKind::SetValue => {
let text = c_to_string(action.text).ok_or("text is null or invalid UTF-8")?;
Ok(Action::SetValue(text))
}
AdActionKind::Select => {
let text = c_to_string(action.text).ok_or("text is null or invalid UTF-8")?;
Ok(Action::Select(text))
}
AdActionKind::TypeText => {
let text = c_to_string(action.text).ok_or("text is null or invalid UTF-8")?;
Ok(Action::TypeText(text))
}
AdActionKind::Scroll => {
let raw_dir = AdDirection::from_c(action.scroll.direction)
.ok_or("invalid scroll direction discriminant")?;
let dir = direction_from_c(raw_dir);
Ok(Action::Scroll(dir, action.scroll.amount))
}
AdActionKind::PressKey => {
let combo = key_combo_from_c(&action.key)?;
Ok(Action::PressKey(combo))
}
AdActionKind::KeyDown => {
let combo = key_combo_from_c(&action.key)?;
Ok(Action::KeyDown(combo))
}
AdActionKind::KeyUp => {
let combo = key_combo_from_c(&action.key)?;
Ok(Action::KeyUp(combo))
}
AdActionKind::Drag => {
let params = CoreDragParams {
from: CorePoint {
x: action.drag.from.x,
y: action.drag.from.y,
},
to: CorePoint {
x: action.drag.to.x,
y: action.drag.to.y,
},
duration_ms: if action.drag.duration_ms == 0 {
None
} else {
Some(action.drag.duration_ms)
},
};
Ok(Action::Drag(params))
}
}
}
}

View file

@ -1,9 +1,9 @@
use crate::AdAdapter;
use crate::actions::conversion::action_from_c;
use crate::actions::result::action_result_to_c;
use crate::error::{self, AdResult};
use crate::ffi_try::trap_panic;
use crate::types::{AdAction, AdActionResult, AdNativeHandle, AdPolicyKind};
use crate::AdAdapter;
use agent_desktop_core::{action::ActionRequest, adapter::NativeHandle};
/// # Safety
@ -12,7 +12,7 @@ use agent_desktop_core::{action::ActionRequest, adapter::NativeHandle};
/// `handle` must be a non-null pointer to a valid `AdNativeHandle`.
/// `action` must be a non-null pointer to a valid `AdAction`.
/// `out` must be a non-null pointer to an `AdActionResult` to write the result into.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_execute_action(
adapter: *const AdAdapter,
handle: *const AdNativeHandle,
@ -30,7 +30,7 @@ pub unsafe extern "C" fn ad_execute_action(
/// `handle` must be a non-null pointer to a valid `AdNativeHandle`.
/// `action` must be a non-null pointer to a valid `AdAction`.
/// `out` must be a non-null pointer to an `AdActionResult` to write the result into.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_execute_action_with_policy(
adapter: *const AdAdapter,
handle: *const AdNativeHandle,

View file

@ -1,7 +1,7 @@
use crate::error::{set_last_error, AdResult};
use crate::AdAdapter;
use crate::error::{AdResult, set_last_error};
use crate::ffi_try::trap_panic;
use crate::types::AdNativeHandle;
use crate::AdAdapter;
use agent_desktop_core::adapter::NativeHandle;
/// Releases a handle previously returned by `ad_resolve_element` and
@ -28,7 +28,7 @@ use agent_desktop_core::adapter::NativeHandle;
/// `handle` must be null or a `*mut AdNativeHandle` previously
/// populated by `ad_resolve_element`. On return `(*handle).ptr` is
/// `NULL` so a double-call is a no-op instead of a double-free.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_free_handle(
adapter: *const AdAdapter,
handle: *mut AdNativeHandle,

View file

@ -1,8 +1,8 @@
use crate::AdAdapter;
use crate::convert::string::{c_to_string, try_c_to_string};
use crate::error::{self, AdResult};
use crate::ffi_try::trap_panic;
use crate::types::{AdNativeHandle, AdRefEntry};
use crate::AdAdapter;
use agent_desktop_core::refs::RefEntry as CoreRefEntry;
/// # Safety
@ -10,7 +10,7 @@ use agent_desktop_core::refs::RefEntry as CoreRefEntry;
/// `adapter` must be a non-null pointer returned by `ad_adapter_create`.
/// `entry` must be a non-null pointer to a valid `AdRefEntry`.
/// `out` must be a non-null pointer to an `AdNativeHandle` to write the result into.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_resolve_element(
adapter: *const AdAdapter,
entry: *const AdRefEntry,
@ -68,7 +68,7 @@ pub unsafe extern "C" fn ad_resolve_element(
source_app: None,
source_window_title: None,
root_ref: None,
path: Vec::new(),
path: smallvec::SmallVec::new(),
};
match adapter.inner.resolve_element(&core_entry) {
Ok(handle) => {

View file

@ -42,7 +42,7 @@ pub(crate) fn action_result_to_c(r: &CoreActionResult) -> AdActionResult {
///
/// `result` must be a pointer to an `AdActionResult` previously written by `ad_execute_action`,
/// or null. After this call all pointers inside the struct are invalid.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_free_action_result(result: *mut AdActionResult) {
crate::ffi_try::trap_panic_void(|| unsafe {
if result.is_null() {

View file

@ -1,6 +1,6 @@
use crate::error::{self, AdResult};
use crate::ffi_try::{trap_panic, trap_panic_ptr, trap_panic_void};
use agent_desktop_core::{adapter::PlatformAdapter, PermissionState};
use agent_desktop_core::{PermissionState, adapter::PlatformAdapter};
pub struct AdAdapter {
pub(crate) inner: Box<dyn PlatformAdapter>,
@ -33,7 +33,7 @@ fn build_adapter() -> Box<dyn PlatformAdapter> {
/// The returned pointer is owned by the caller and must be released with
/// `ad_adapter_destroy`. Creating and destroying adapters is cheap; the
/// common pattern is one adapter per process lifetime.
#[no_mangle]
#[unsafe(no_mangle)]
pub extern "C" fn ad_adapter_create() -> *mut AdAdapter {
trap_panic_ptr(|| {
let adapter = AdAdapter {
@ -47,7 +47,7 @@ pub extern "C" fn ad_adapter_create() -> *mut AdAdapter {
///
/// `adapter` must be a pointer returned by `ad_adapter_create`, or null.
/// After this call the pointer is invalid and must not be used.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_adapter_destroy(adapter: *mut AdAdapter) {
trap_panic_void(|| {
if !adapter.is_null() {
@ -60,7 +60,7 @@ pub unsafe extern "C" fn ad_adapter_destroy(adapter: *mut AdAdapter) {
///
/// `adapter` must be a non-null pointer returned by `ad_adapter_create` that
/// has not yet been destroyed.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_check_permissions(adapter: *const AdAdapter) -> AdResult {
trap_panic(|| {
crate::pointer_guard::guard_non_null!(adapter, c"adapter is null");

View file

@ -1,7 +1,7 @@
use crate::convert::string::c_to_string;
use crate::error::{set_last_error, AdResult};
use crate::ffi_try::trap_panic;
use crate::AdAdapter;
use crate::convert::string::c_to_string;
use crate::error::{AdResult, set_last_error};
use crate::ffi_try::trap_panic;
use std::os::raw::c_char;
/// Closes the application identified by `id` (bundle id on macOS,
@ -10,7 +10,7 @@ use std::os::raw::c_char;
///
/// # Safety
/// `adapter` must be non-null. `id` must be a non-null UTF-8 C string.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_close_app(
adapter: *const AdAdapter,
id: *const c_char,

View file

@ -1,9 +1,9 @@
use crate::AdAdapter;
use crate::convert::string::c_to_string;
use crate::convert::window::window_info_to_c;
use crate::error::{set_last_error, AdResult};
use crate::error::{AdResult, set_last_error};
use crate::ffi_try::trap_panic;
use crate::types::AdWindowInfo;
use crate::AdAdapter;
use std::os::raw::c_char;
/// Launches the application identified by `id` (bundle id on macOS,
@ -19,7 +19,7 @@ use std::os::raw::c_char;
/// # Safety
/// `adapter` must be non-null. `id` must be a non-null UTF-8 C string.
/// `out` must be a non-null writable `*mut AdWindowInfo`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_launch_app(
adapter: *const AdAdapter,
id: *const c_char,

View file

@ -1,8 +1,8 @@
use crate::AdAdapter;
use crate::convert::app::{app_info_to_c, free_app_info_fields};
use crate::error::{set_last_error, AdResult};
use crate::error::{AdResult, set_last_error};
use crate::ffi_try::{trap_panic, trap_panic_void};
use crate::types::{AdAppInfo, AdAppList};
use crate::AdAdapter;
use std::ptr;
/// # Safety
@ -10,7 +10,7 @@ use std::ptr;
/// `out` must be a valid writable `*mut *mut AdAppList`.
/// On success, `*out` is a newly-allocated opaque list freed with
/// `ad_app_list_free`. On error, `*out` is null and last-error is set.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_list_apps(
adapter: *const AdAdapter,
out: *mut *mut AdAppList,
@ -42,7 +42,7 @@ pub unsafe extern "C" fn ad_list_apps(
/// # Safety
/// `list` must be null or a pointer returned by `ad_list_apps`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_app_list_count(list: *const AdAppList) -> u32 {
if list.is_null() {
return 0;
@ -56,7 +56,7 @@ pub unsafe extern "C" fn ad_app_list_count(list: *const AdAppList) -> u32 {
///
/// # Safety
/// `list` must be null or a pointer returned by `ad_list_apps`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_app_list_get(list: *const AdAppList, index: u32) -> *const AdAppInfo {
if list.is_null() {
return ptr::null();
@ -73,7 +73,7 @@ pub unsafe extern "C" fn ad_app_list_get(list: *const AdAppList, index: u32) ->
///
/// # Safety
/// `list` must be null or a pointer returned by `ad_list_apps`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_app_list_free(list: *mut AdAppList) {
trap_panic_void(|| unsafe {
if list.is_null() {

View file

@ -13,10 +13,12 @@ pub(crate) fn app_info_to_c(a: &AppInfo) -> AdAppInfo {
}
pub(crate) unsafe fn free_app_info_fields(a: &mut AdAppInfo) {
free_c_string(a.name as *mut c_char);
free_c_string(a.bundle_id as *mut c_char);
a.name = ptr::null();
a.bundle_id = ptr::null();
unsafe {
free_c_string(a.name as *mut c_char);
free_c_string(a.bundle_id as *mut c_char);
a.name = ptr::null();
a.bundle_id = ptr::null();
}
}
#[cfg(test)]

View file

@ -17,15 +17,17 @@ pub(crate) fn notification_info_to_c(info: &NotificationInfo) -> AdNotificationI
}
pub(crate) unsafe fn free_notification_info_fields(info: &mut AdNotificationInfo) {
free_c_string(info.app_name as *mut c_char);
free_c_string(info.title as *mut c_char);
free_c_string(info.body as *mut c_char);
free_c_string_array(info.actions, info.action_count);
info.app_name = ptr::null();
info.title = ptr::null();
info.body = ptr::null();
info.actions = ptr::null_mut();
info.action_count = 0;
unsafe {
free_c_string(info.app_name as *mut c_char);
free_c_string(info.title as *mut c_char);
free_c_string(info.body as *mut c_char);
free_c_string_array(info.actions, info.action_count);
info.app_name = ptr::null();
info.title = ptr::null();
info.body = ptr::null();
info.actions = ptr::null_mut();
info.action_count = 0;
}
}
fn strings_to_c_array(strings: &[String]) -> (*mut *mut c_char, u32) {
@ -41,15 +43,17 @@ fn strings_to_c_array(strings: &[String]) -> (*mut *mut c_char, u32) {
}
unsafe fn free_c_string_array(arr: *mut *mut c_char, count: u32) {
if arr.is_null() {
return;
unsafe {
if arr.is_null() {
return;
}
let slice = std::slice::from_raw_parts_mut(arr, count as usize);
for p in slice.iter_mut() {
free_c_string(*p);
}
drop(Box::from_raw(std::ptr::slice_from_raw_parts_mut(
arr,
count as usize,
)));
}
let slice = std::slice::from_raw_parts_mut(arr, count as usize);
for p in slice.iter_mut() {
free_c_string(*p);
}
drop(Box::from_raw(std::ptr::slice_from_raw_parts_mut(
arr,
count as usize,
)));
}

View file

@ -36,16 +36,20 @@ pub(crate) fn opt_string_to_c(s: Option<&str>) -> *mut c_char {
}
pub(crate) unsafe fn free_c_string(ptr: *mut c_char) {
if !ptr.is_null() {
drop(CString::from_raw(ptr));
unsafe {
if !ptr.is_null() {
drop(CString::from_raw(ptr));
}
}
}
pub(crate) unsafe fn c_to_string(ptr: *const c_char) -> Option<String> {
if ptr.is_null() {
return None;
unsafe {
if ptr.is_null() {
return None;
}
CStr::from_ptr(ptr).to_str().ok().map(str::to_owned)
}
CStr::from_ptr(ptr).to_str().ok().map(str::to_owned)
}
/// Tri-state decode of a foreign C string used for optional filter
@ -62,12 +66,14 @@ pub(crate) unsafe fn c_to_string(ptr: *const c_char) -> Option<String> {
/// # Safety
/// `ptr` must be null or a NUL-terminated C string.
pub(crate) unsafe fn try_c_to_string(ptr: *const c_char) -> Result<Option<String>, ()> {
if ptr.is_null() {
return Ok(None);
}
match CStr::from_ptr(ptr).to_str() {
Ok(s) => Ok(Some(s.to_owned())),
Err(_) => Err(()),
unsafe {
if ptr.is_null() {
return Ok(None);
}
match CStr::from_ptr(ptr).to_str() {
Ok(s) => Ok(Some(s.to_owned())),
Err(_) => Err(()),
}
}
}

View file

@ -13,10 +13,12 @@ pub(crate) fn surface_info_to_c(s: &SurfaceInfo) -> AdSurfaceInfo {
}
pub(crate) unsafe fn free_surface_info_fields(s: &mut AdSurfaceInfo) {
free_c_string(s.kind as *mut c_char);
free_c_string(s.title as *mut c_char);
s.kind = ptr::null();
s.title = ptr::null();
unsafe {
free_c_string(s.kind as *mut c_char);
free_c_string(s.title as *mut c_char);
s.kind = ptr::null();
s.title = ptr::null();
}
}
#[cfg(test)]

View file

@ -30,12 +30,14 @@ pub(crate) fn window_info_to_c(w: &WindowInfo) -> AdWindowInfo {
}
pub(crate) unsafe fn free_window_info_fields(w: &mut AdWindowInfo) {
free_c_string(w.id as *mut c_char);
free_c_string(w.title as *mut c_char);
free_c_string(w.app_name as *mut c_char);
w.id = ptr::null();
w.title = ptr::null();
w.app_name = ptr::null();
unsafe {
free_c_string(w.id as *mut c_char);
free_c_string(w.title as *mut c_char);
free_c_string(w.app_name as *mut c_char);
w.id = ptr::null();
w.title = ptr::null();
w.app_name = ptr::null();
}
}
#[cfg(test)]

View file

@ -1,6 +1,6 @@
use agent_desktop_core::error::{AdapterError, ErrorCode};
use std::cell::RefCell;
use std::ffi::{c_char, CStr, CString};
use std::ffi::{CStr, CString, c_char};
const fn error_code_variant_count() -> usize {
let mut count = 0;
@ -215,7 +215,7 @@ pub(crate) fn last_error_code() -> AdResult {
/// leaks to Thread B.
/// Returns the `AdResult` code of the last error on the calling thread,
/// or `AD_RESULT_OK` if no error has been recorded.
#[no_mangle]
#[unsafe(no_mangle)]
pub extern "C" fn ad_last_error_code() -> AdResult {
crate::ffi_try::trap_panic(last_error_code)
}
@ -224,7 +224,7 @@ pub extern "C" fn ad_last_error_code() -> AdResult {
/// error has been recorded on the calling thread. The pointer remains
/// valid across any number of subsequent *successful* FFI calls; only
/// the next failing call overwrites it.
#[no_mangle]
#[unsafe(no_mangle)]
pub extern "C" fn ad_last_error_message() -> *const c_char {
crate::ffi_try::trap_panic_const_ptr(|| {
LAST_ERROR.with(|cell| {
@ -239,7 +239,7 @@ pub extern "C" fn ad_last_error_message() -> *const c_char {
/// Returns a borrowed C string with a human-readable suggestion for how
/// to recover from the last error, or null if the adapter didn't emit
/// one. Same lifetime rules as `ad_last_error_message`.
#[no_mangle]
#[unsafe(no_mangle)]
pub extern "C" fn ad_last_error_suggestion() -> *const c_char {
crate::ffi_try::trap_panic_const_ptr(|| {
LAST_ERROR.with(|cell| {
@ -255,7 +255,7 @@ pub extern "C" fn ad_last_error_suggestion() -> *const c_char {
/// for the last error (AX error codes, COM HRESULTs, AT-SPI messages,
/// etc.), or null if the adapter didn't supply one. Same lifetime rules
/// as `ad_last_error_message`.
#[no_mangle]
#[unsafe(no_mangle)]
pub extern "C" fn ad_last_error_platform_detail() -> *const c_char {
crate::ffi_try::trap_panic_const_ptr(|| {
LAST_ERROR.with(|cell| {

View file

@ -1,6 +1,6 @@
use crate::error::{set_last_error_static, AdResult};
use crate::error::{AdResult, set_last_error_static};
use std::ffi::CStr;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::panic::{AssertUnwindSafe, catch_unwind};
static PANIC_MESSAGE: &CStr = c"rust panic in FFI boundary";

View file

@ -1,7 +1,7 @@
use crate::AdAdapter;
use crate::convert::string::{c_to_string, free_c_string, string_to_c};
use crate::error::{self, AdResult};
use crate::ffi_try::{trap_panic, trap_panic_void};
use crate::AdAdapter;
use std::os::raw::c_char;
/// Reads the current clipboard text and writes an owned C string into
@ -11,7 +11,7 @@ use std::os::raw::c_char;
/// # Safety
/// `adapter` must be a non-null pointer returned by `ad_adapter_create`.
/// `out` must be a non-null writable `*mut *mut c_char`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_get_clipboard(
adapter: *const AdAdapter,
out: *mut *mut c_char,
@ -28,12 +28,10 @@ pub unsafe extern "C" fn ad_get_clipboard(
Ok(text) => {
let c = string_to_c(&text);
if c.is_null() {
error::set_last_error(
&agent_desktop_core::error::AdapterError::new(
agent_desktop_core::error::ErrorCode::Internal,
"clipboard text contains an interior NUL and cannot be represented as a C string",
),
);
error::set_last_error(&agent_desktop_core::error::AdapterError::new(
agent_desktop_core::error::ErrorCode::Internal,
"clipboard text contains an interior NUL and cannot be represented as a C string",
));
return AdResult::ErrInternal;
}
*out = c;
@ -53,7 +51,7 @@ pub unsafe extern "C" fn ad_get_clipboard(
/// # Safety
/// `adapter` must be a non-null pointer returned by `ad_adapter_create`.
/// `text` must be a non-null, NUL-terminated UTF-8 C string.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_set_clipboard(
adapter: *const AdAdapter,
text: *const c_char,
@ -88,7 +86,7 @@ pub unsafe extern "C" fn ad_set_clipboard(
///
/// # Safety
/// `adapter` must be a non-null pointer returned by `ad_adapter_create`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_clear_clipboard(adapter: *const AdAdapter) -> AdResult {
trap_panic(|| unsafe {
if let Err(rc) = crate::main_thread::require_main_thread() {
@ -113,7 +111,7 @@ pub unsafe extern "C" fn ad_clear_clipboard(adapter: *const AdAdapter) -> AdResu
/// # Safety
/// `s` must be null or a pointer previously handed out by this crate.
/// After this call the pointer is invalid and must not be used.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_free_string(s: *mut c_char) {
trap_panic_void(|| unsafe { free_c_string(s) })
}

View file

@ -1,7 +1,7 @@
use crate::AdAdapter;
use crate::error::{self, AdResult};
use crate::ffi_try::trap_panic;
use crate::types::AdDragParams;
use crate::AdAdapter;
use agent_desktop_core::action::{DragParams as CoreDragParams, Point as CorePoint};
/// Synthesizes a mouse drag from `params.from` to `params.to`. When
@ -11,7 +11,7 @@ use agent_desktop_core::action::{DragParams as CoreDragParams, Point as CorePoin
/// # Safety
/// `adapter` must be a non-null pointer returned by `ad_adapter_create`.
/// `params` must be a non-null pointer to a valid `AdDragParams`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_drag(
adapter: *const AdAdapter,
params: *const AdDragParams,

View file

@ -1,7 +1,7 @@
use crate::AdAdapter;
use crate::error::{self, AdResult};
use crate::ffi_try::trap_panic;
use crate::types::{AdMouseButton, AdMouseEvent, AdMouseEventKind};
use crate::AdAdapter;
use agent_desktop_core::action::{
MouseButton as CoreMouseButton, MouseEvent as CoreMouseEvent,
MouseEventKind as CoreMouseEventKind, Point as CorePoint,
@ -22,7 +22,7 @@ pub(crate) fn mouse_button_from_c(b: AdMouseButton) -> CoreMouseButton {
/// # Safety
/// `adapter` must be a non-null pointer returned by `ad_adapter_create`.
/// `event` must be a non-null pointer to a valid `AdMouseEvent`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_mouse_event(
adapter: *const AdAdapter,
event: *const AdMouseEvent,

View file

@ -17,7 +17,7 @@
//! / `ad_free_string` / `ad_free_action_result` / `ad_free_tree` family.
//! Those paths touch no AX/Cocoa state and are safe from any thread.
use crate::error::{set_last_error_static, AdResult};
use crate::error::{AdResult, set_last_error_static};
use std::ffi::CStr;
static OFF_MAIN_THREAD_MESSAGE: &CStr =

View file

@ -1,9 +1,9 @@
use crate::AdAdapter;
use crate::actions::result::action_result_to_c;
use crate::convert::string::{c_to_string, decode_optional_filter};
use crate::error::{set_last_error, AdResult};
use crate::error::{AdResult, set_last_error};
use crate::ffi_try::trap_panic;
use crate::types::AdActionResult;
use crate::AdAdapter;
use agent_desktop_core::notification::NotificationIdentity;
use std::os::raw::c_char;
@ -34,7 +34,7 @@ use std::os::raw::c_char;
/// is rejected with `AD_RESULT_ERR_INVALID_ARGS` rather than silently
/// treated as "no fingerprint". `out` must be a valid writable
/// `*mut AdActionResult`; on error it is zero-initialized.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_notification_action(
adapter: *const AdAdapter,
index: u32,

View file

@ -1,7 +1,7 @@
use crate::convert::string::decode_optional_filter;
use crate::error::{set_last_error, AdResult};
use crate::ffi_try::trap_panic;
use crate::AdAdapter;
use crate::convert::string::decode_optional_filter;
use crate::error::{AdResult, set_last_error};
use crate::ffi_try::trap_panic;
use std::os::raw::c_char;
/// Dismisses the notification at `index`. Indexes are only valid within
@ -11,7 +11,7 @@ use std::os::raw::c_char;
///
/// # Safety
/// `adapter` must be valid. `app_filter` may be null.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_dismiss_notification(
adapter: *const AdAdapter,
index: u32,

View file

@ -1,10 +1,10 @@
use crate::AdAdapter;
use crate::convert::notification::notification_info_to_c;
use crate::convert::string::decode_optional_filter;
use crate::error::{set_last_error, AdResult};
use crate::error::{AdResult, set_last_error};
use crate::ffi_try::trap_panic;
use crate::notifications::list::ad_notification_list_free;
use crate::types::{AdNotificationInfo, AdNotificationList};
use crate::AdAdapter;
use std::os::raw::c_char;
use std::ptr;
@ -23,7 +23,7 @@ use std::ptr;
/// # Safety
/// `adapter` must be valid. `app_filter` may be null. `dismissed_out`
/// and `failed_out` must both be valid writable `*mut *mut AdNotificationList`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_dismiss_all_notifications(
adapter: *const AdAdapter,
app_filter: *const c_char,
@ -86,7 +86,7 @@ pub unsafe extern "C" fn ad_dismiss_all_notifications(
/// # Safety
/// Both arguments must be null or pointers from
/// `ad_dismiss_all_notifications`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_dismiss_all_notifications_free(
dismissed: *mut AdNotificationList,
failed: *mut AdNotificationList,

View file

@ -1,9 +1,9 @@
use crate::AdAdapter;
use crate::convert::notification::{free_notification_info_fields, notification_info_to_c};
use crate::error::{set_last_error, AdResult};
use crate::error::{AdResult, set_last_error};
use crate::ffi_try::{trap_panic, trap_panic_void};
use crate::notifications::filter::filter_from_c;
use crate::types::{AdNotificationFilter, AdNotificationInfo, AdNotificationList};
use crate::AdAdapter;
use std::ptr;
/// Lists the notifications currently on-screen.
@ -17,7 +17,7 @@ use std::ptr;
/// `adapter` must be valid. `filter` may be null. `out` must be a valid
/// writable `*mut *mut AdNotificationList`. On success `*out` is a
/// non-null handle freed with `ad_notification_list_free`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_list_notifications(
adapter: *const AdAdapter,
filter: *const AdNotificationFilter,
@ -58,7 +58,7 @@ pub unsafe extern "C" fn ad_list_notifications(
/// # Safety
/// `list` must be null or a pointer returned by `ad_list_notifications`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_notification_list_count(list: *const AdNotificationList) -> u32 {
if list.is_null() {
return 0;
@ -71,7 +71,7 @@ pub unsafe extern "C" fn ad_notification_list_count(list: *const AdNotificationL
///
/// # Safety
/// `list` must be null or a pointer returned by `ad_list_notifications`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_notification_list_get(
list: *const AdNotificationList,
index: u32,
@ -90,7 +90,7 @@ pub unsafe extern "C" fn ad_notification_list_get(
///
/// # Safety
/// `list` must be null or a pointer returned by `ad_list_notifications`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_notification_list_free(list: *mut AdNotificationList) {
trap_panic_void(|| unsafe {
if list.is_null() {

View file

@ -1,9 +1,9 @@
use crate::AdAdapter;
use crate::convert::string::decode_optional_filter;
use crate::error::{set_last_error, AdResult};
use crate::error::{AdResult, set_last_error};
use crate::ffi_try::trap_panic;
use crate::observation::walk::find_first_match;
use crate::types::{AdFindQuery, AdNativeHandle, AdWindowInfo};
use crate::AdAdapter;
use agent_desktop_core::adapter::{SnapshotSurface, TreeOptions};
use agent_desktop_core::refs::RefEntry;
@ -22,7 +22,7 @@ use agent_desktop_core::refs::RefEntry;
/// `adapter`, `win`, and `query` must be valid pointers. `out_handle`
/// must be a valid writable `*mut AdNativeHandle`. On
/// `AD_RESULT_ERR_ELEMENT_NOT_FOUND` the out-handle is zero-initialized.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_find(
adapter: *const AdAdapter,
win: *const AdWindowInfo,
@ -102,7 +102,7 @@ pub unsafe extern "C" fn ad_find(
source_app: None,
source_window_title: Some(core_win.title.clone()),
root_ref: None,
path: Vec::new(),
path: smallvec::SmallVec::new(),
};
match adapter.inner.resolve_element(&ref_entry) {
Ok(handle) => {

View file

@ -1,8 +1,8 @@
use crate::AdAdapter;
use crate::convert::string::{c_to_string, string_to_c_lossy};
use crate::error::{set_last_error, AdResult};
use crate::error::{AdResult, set_last_error};
use crate::ffi_try::trap_panic;
use crate::types::AdNativeHandle;
use crate::AdAdapter;
use agent_desktop_core::adapter::NativeHandle;
use std::os::raw::c_char;
@ -20,7 +20,7 @@ use std::os::raw::c_char;
/// `adapter` must be valid. `handle` must be a non-null `AdNativeHandle`.
/// `property` must be a non-null UTF-8 C string. `out` must be a valid
/// writable `*mut *mut c_char`; it is null-initialized on entry.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_get(
adapter: *const AdAdapter,
handle: *const AdNativeHandle,

View file

@ -1,8 +1,8 @@
use crate::AdAdapter;
use crate::convert::string::{c_to_string, decode_optional_filter};
use crate::error::{set_last_error, AdResult};
use crate::error::{AdResult, set_last_error};
use crate::ffi_try::trap_panic;
use crate::types::{AdFindQuery, AdWindowInfo};
use crate::AdAdapter;
use agent_desktop_core::adapter::{SnapshotSurface, TreeOptions};
use agent_desktop_core::node::AccessibilityNode;
use std::os::raw::c_char;
@ -36,7 +36,7 @@ use std::os::raw::c_char;
/// # Safety
/// All pointers must be valid. `property` must be a non-null UTF-8
/// C string. `out` must be a valid writable `*mut bool`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_is(
adapter: *const AdAdapter,
win: *const AdWindowInfo,

View file

@ -6,7 +6,7 @@ use std::ptr;
///
/// # Safety
/// `buf` must be null or returned by `ad_screenshot`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_image_buffer_data(buf: *const AdImageBuffer) -> *const u8 {
if buf.is_null() {
return ptr::null();
@ -20,7 +20,7 @@ pub unsafe extern "C" fn ad_image_buffer_data(buf: *const AdImageBuffer) -> *con
///
/// # Safety
/// `buf` must be null or returned by `ad_screenshot`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_image_buffer_size(buf: *const AdImageBuffer) -> u64 {
if buf.is_null() {
return 0;
@ -33,7 +33,7 @@ pub unsafe extern "C" fn ad_image_buffer_size(buf: *const AdImageBuffer) -> u64
///
/// # Safety
/// `buf` must be null or returned by `ad_screenshot`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_image_buffer_width(buf: *const AdImageBuffer) -> u32 {
if buf.is_null() {
return 0;
@ -46,7 +46,7 @@ pub unsafe extern "C" fn ad_image_buffer_width(buf: *const AdImageBuffer) -> u32
///
/// # Safety
/// `buf` must be null or returned by `ad_screenshot`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_image_buffer_height(buf: *const AdImageBuffer) -> u32 {
if buf.is_null() {
return 0;
@ -60,7 +60,7 @@ pub unsafe extern "C" fn ad_image_buffer_height(buf: *const AdImageBuffer) -> u3
///
/// # Safety
/// `buf` must be null or returned by `ad_screenshot`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_image_buffer_format(buf: *const AdImageBuffer) -> AdImageFormat {
if buf.is_null() {
return AdImageFormat::Png;

View file

@ -1,7 +1,7 @@
use crate::error::{set_last_error, AdResult};
use crate::AdAdapter;
use crate::error::{AdResult, set_last_error};
use crate::ffi_try::trap_panic;
use crate::types::{AdImageBuffer, AdImageFormat, AdScreenshotKind, AdScreenshotTarget};
use crate::AdAdapter;
use agent_desktop_core::adapter::{ImageFormat, ScreenshotTarget as CoreScreenshotTarget};
use std::ptr;
@ -14,7 +14,7 @@ use std::ptr;
/// `adapter` and `target` must be valid pointers. `out` must be a valid
/// writable `*mut *mut AdImageBuffer`. On error `*out` is null and
/// last-error is set.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_screenshot(
adapter: *const AdAdapter,
target: *const AdScreenshotTarget,

View file

@ -6,7 +6,7 @@ use crate::types::AdImageBuffer;
/// # Safety
/// `buf` must be null or a pointer previously returned by `ad_screenshot`.
/// Double-free is undefined behavior.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_image_buffer_free(buf: *mut AdImageBuffer) {
trap_panic_void(|| unsafe {
if buf.is_null() {

View file

@ -1,15 +1,15 @@
use crate::AdAdapter;
use crate::convert::surface::{free_surface_info_fields, surface_info_to_c};
use crate::error::{set_last_error, AdResult};
use crate::error::{AdResult, set_last_error};
use crate::ffi_try::{trap_panic, trap_panic_void};
use crate::types::{AdSurfaceInfo, AdSurfaceList};
use crate::AdAdapter;
use std::ptr;
/// # Safety
/// `adapter` must be valid. `out` must be a valid writable
/// `*mut *mut AdSurfaceList`. Success produces a list handle freed via
/// `ad_surface_list_free`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_list_surfaces(
adapter: *const AdAdapter,
pid: i32,
@ -42,7 +42,7 @@ pub unsafe extern "C" fn ad_list_surfaces(
/// # Safety
/// `list` must be null or a pointer returned by `ad_list_surfaces`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_surface_list_count(list: *const AdSurfaceList) -> u32 {
if list.is_null() {
return 0;
@ -55,7 +55,7 @@ pub unsafe extern "C" fn ad_surface_list_count(list: *const AdSurfaceList) -> u3
///
/// # Safety
/// `list` must be null or a pointer returned by `ad_list_surfaces`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_surface_list_get(
list: *const AdSurfaceList,
index: u32,
@ -74,7 +74,7 @@ pub unsafe extern "C" fn ad_surface_list_get(
///
/// # Safety
/// `list` must be null or a pointer returned by `ad_list_surfaces`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_surface_list_free(list: *mut AdSurfaceList) {
trap_panic_void(|| unsafe {
if list.is_null() {

View file

@ -4,41 +4,45 @@ use std::os::raw::c_char;
use std::ptr;
unsafe fn free_c_string_array(arr: *mut *mut c_char, count: u32) {
if arr.is_null() {
return;
unsafe {
if arr.is_null() {
return;
}
let slice = std::slice::from_raw_parts_mut(arr, count as usize);
for p in slice.iter_mut() {
free_c_string(*p);
}
drop(Box::from_raw(std::ptr::slice_from_raw_parts_mut(
arr,
count as usize,
)));
}
let slice = std::slice::from_raw_parts_mut(arr, count as usize);
for p in slice.iter_mut() {
free_c_string(*p);
}
drop(Box::from_raw(std::ptr::slice_from_raw_parts_mut(
arr,
count as usize,
)));
}
unsafe fn free_node_fields(node: &mut AdNode) {
free_c_string(node.ref_id as *mut c_char);
free_c_string(node.role as *mut c_char);
free_c_string(node.name as *mut c_char);
free_c_string(node.value as *mut c_char);
free_c_string(node.description as *mut c_char);
free_c_string(node.hint as *mut c_char);
free_c_string_array(node.states, node.state_count);
node.ref_id = ptr::null();
node.role = ptr::null();
node.name = ptr::null();
node.value = ptr::null();
node.description = ptr::null();
node.hint = ptr::null();
node.states = ptr::null_mut();
node.state_count = 0;
unsafe {
free_c_string(node.ref_id as *mut c_char);
free_c_string(node.role as *mut c_char);
free_c_string(node.name as *mut c_char);
free_c_string(node.value as *mut c_char);
free_c_string(node.description as *mut c_char);
free_c_string(node.hint as *mut c_char);
free_c_string_array(node.states, node.state_count);
node.ref_id = ptr::null();
node.role = ptr::null();
node.name = ptr::null();
node.value = ptr::null();
node.description = ptr::null();
node.hint = ptr::null();
node.states = ptr::null_mut();
node.state_count = 0;
}
}
/// # Safety
/// `tree` must be null or point to a valid `AdNodeTree` previously returned
/// by `flatten_tree` or `ad_get_tree`. After this call the tree is zeroed.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_free_tree(tree: *mut AdNodeTree) {
crate::ffi_try::trap_panic_void(|| unsafe {
if tree.is_null() {

View file

@ -1,8 +1,8 @@
use crate::error::{set_last_error, AdResult};
use crate::AdAdapter;
use crate::error::{AdResult, set_last_error};
use crate::ffi_try::trap_panic;
use crate::tree::flatten::flatten_tree;
use crate::types::{AdNodeTree, AdSnapshotSurface, AdTreeOptions, AdWindowInfo};
use crate::AdAdapter;
use agent_desktop_core::adapter::SnapshotSurface;
use std::ptr;
@ -58,7 +58,7 @@ fn core_surface(s: AdSnapshotSurface) -> SnapshotSurface {
/// # Safety
/// All pointers must be non-null. `win.id` and `win.title` must be
/// valid UTF-8 C strings. `out` must be writable.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_get_tree(
adapter: *const AdAdapter,
win: *const AdWindowInfo,

View file

@ -1,8 +1,8 @@
use crate::error::{set_last_error, AdResult};
use crate::AdAdapter;
use crate::error::{AdResult, set_last_error};
use crate::ffi_try::trap_panic;
use crate::types::AdWindowInfo;
use crate::windows::to_core::ad_window_to_core;
use crate::AdAdapter;
/// Brings `win` to the foreground on the current space. Returns
/// `AD_RESULT_ERR_WINDOW_NOT_FOUND` when the referenced window no longer
@ -12,7 +12,7 @@ use crate::AdAdapter;
/// `adapter` must be a non-null pointer from `ad_adapter_create`. `win`
/// must be a non-null pointer to an `AdWindowInfo` whose `id` and
/// `title` fields are non-null, valid UTF-8 C strings.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_focus_window(
adapter: *const AdAdapter,
win: *const AdWindowInfo,

View file

@ -16,7 +16,7 @@ use crate::types::AdWindowInfo;
/// `win` must be null or point to a valid `AdWindowInfo` whose string
/// fields were allocated by this crate. Do not call on pointers inside
/// an `AdWindowList` — free the list instead.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_release_window_fields(win: *mut AdWindowInfo) {
trap_panic_void(|| unsafe {
if win.is_null() {

View file

@ -1,9 +1,9 @@
use crate::AdAdapter;
use crate::convert::string::decode_optional_filter;
use crate::convert::window::{free_window_info_fields, window_info_to_c};
use crate::error::{set_last_error, AdResult};
use crate::error::{AdResult, set_last_error};
use crate::ffi_try::{trap_panic, trap_panic_void};
use crate::types::{AdWindowInfo, AdWindowList};
use crate::AdAdapter;
use agent_desktop_core::adapter::WindowFilter;
use std::os::raw::c_char;
use std::ptr;
@ -12,7 +12,7 @@ use std::ptr;
/// `adapter` must be valid. `out` must be a valid writable
/// `*mut *mut AdWindowList`. `app_filter` may be null or a C string.
/// Success produces a list handle freed via `ad_window_list_free`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_list_windows(
adapter: *const AdAdapter,
app_filter: *const c_char,
@ -50,7 +50,7 @@ pub unsafe extern "C" fn ad_list_windows(
/// # Safety
/// `list` must be null or a pointer returned by `ad_list_windows`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_window_list_count(list: *const AdWindowList) -> u32 {
if list.is_null() {
return 0;
@ -63,7 +63,7 @@ pub unsafe extern "C" fn ad_window_list_count(list: *const AdWindowList) -> u32
///
/// # Safety
/// `list` must be null or a pointer returned by `ad_list_windows`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_window_list_get(
list: *const AdWindowList,
index: u32,
@ -82,7 +82,7 @@ pub unsafe extern "C" fn ad_window_list_get(
///
/// # Safety
/// `list` must be null or a pointer returned by `ad_list_windows`.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_window_list_free(list: *mut AdWindowList) {
trap_panic_void(|| unsafe {
if list.is_null() {

View file

@ -1,8 +1,8 @@
use crate::error::{set_last_error, AdResult};
use crate::AdAdapter;
use crate::error::{AdResult, set_last_error};
use crate::ffi_try::trap_panic;
use crate::types::{AdWindowInfo, AdWindowOp, AdWindowOpKind};
use crate::windows::to_core::ad_window_to_core;
use crate::AdAdapter;
use agent_desktop_core::action::WindowOp;
/// Performs a window-manager operation (`Resize`, `Move`, `Minimize`,
@ -15,7 +15,7 @@ use agent_desktop_core::action::WindowOp;
/// # Safety
/// `adapter` and `win` must be non-null pointers. `win.id` and
/// `win.title` must be non-null valid UTF-8 C strings.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ad_window_op(
adapter: *const AdAdapter,
win: *const AdWindowInfo,

View file

@ -1,8 +1,8 @@
mod common;
use common::{
ad_execute_action, ad_execute_action_with_policy, default_action, with_adapter, AdActionResult,
AdNativeHandle, AdPolicyKind, AdResult,
AdActionResult, AdNativeHandle, AdPolicyKind, AdResult, ad_execute_action,
ad_execute_action_with_policy, default_action, with_adapter,
};
#[test]

View file

@ -1,8 +1,8 @@
mod common;
use common::{
ad_launch_app, ad_list_windows, ad_resolve_element, c_char, with_adapter, AdNativeHandle,
AdRefEntry, AdResult, AdWindowInfo, AdWindowList,
AdNativeHandle, AdRefEntry, AdResult, AdWindowInfo, AdWindowList, ad_launch_app,
ad_list_windows, ad_resolve_element, c_char, with_adapter,
};
#[test]

View file

@ -1,10 +1,10 @@
mod common;
use common::{
AdAppList, AdFindQuery, AdNativeHandle, AdResult, AdWindowInfo, AdWindowList, CStr,
ad_adapter_create, ad_adapter_destroy, ad_app_list_count, ad_app_list_free, ad_app_list_get,
ad_check_permissions, ad_find, ad_free_handle, ad_last_error_code, ad_last_error_message,
ad_list_apps, ad_list_windows, ad_window_list_count, ad_window_list_free, with_adapter,
AdAppList, AdFindQuery, AdNativeHandle, AdResult, AdWindowInfo, AdWindowList, CStr,
};
#[test]

View file

@ -10,7 +10,7 @@ pub use agent_desktop_ffi::{
pub use std::ffi::CStr;
pub use std::os::raw::c_char;
extern "C" {
unsafe extern "C" {
pub fn ad_adapter_create() -> *mut AdAdapter;
pub fn ad_adapter_destroy(adapter: *mut AdAdapter);
pub fn ad_check_permissions(adapter: *const AdAdapter) -> AdResult;

Some files were not shown because too many files have changed in this diff Show more