mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-18 02:21:51 +08:00
fix(native): potential sharablecontent icon/name crash (#10464)
This commit is contained in:
Generated
+1
@@ -69,6 +69,7 @@ dependencies = [
|
|||||||
"core-foundation",
|
"core-foundation",
|
||||||
"coreaudio-rs",
|
"coreaudio-rs",
|
||||||
"dispatch2",
|
"dispatch2",
|
||||||
|
"libc",
|
||||||
"napi",
|
"napi",
|
||||||
"napi-build",
|
"napi-build",
|
||||||
"napi-derive",
|
"napi-derive",
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ dispatch2 = "0.2"
|
|||||||
dotenvy = "0.15"
|
dotenvy = "0.15"
|
||||||
file-format = { version = "0.26", features = ["reader"] }
|
file-format = { version = "0.26", features = ["reader"] }
|
||||||
homedir = "0.3"
|
homedir = "0.3"
|
||||||
|
libc = "0.2"
|
||||||
mimalloc = "0.1"
|
mimalloc = "0.1"
|
||||||
napi = { version = "3.0.0-alpha.12", features = ["async", "chrono_date", "error_anyhow", "napi9", "serde"] }
|
napi = { version = "3.0.0-alpha.12", features = ["async", "chrono_date", "error_anyhow", "napi9", "serde"] }
|
||||||
napi-build = { version = "2" }
|
napi-build = { version = "2" }
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||||
import { exec } from 'node:child_process';
|
|
||||||
import { createServer } from 'node:http';
|
import { createServer } from 'node:http';
|
||||||
import { promisify } from 'node:util';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type Application,
|
type Application,
|
||||||
@@ -58,7 +56,7 @@ interface RecordingMetadata {
|
|||||||
interface AppInfo {
|
interface AppInfo {
|
||||||
app: Application;
|
app: Application;
|
||||||
processId: number;
|
processId: number;
|
||||||
processGroupId: number | null;
|
processGroupId: number;
|
||||||
bundleIdentifier: string;
|
bundleIdentifier: string;
|
||||||
name: string;
|
name: string;
|
||||||
running: boolean;
|
running: boolean;
|
||||||
@@ -226,8 +224,7 @@ async function startRecording(app: Application) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the root app of the process group
|
const processGroupId = app.processGroupId;
|
||||||
const processGroupId = await getProcessGroupId(app.processId);
|
|
||||||
const rootApp = processGroupId
|
const rootApp = processGroupId
|
||||||
? (shareableContent
|
? (shareableContent
|
||||||
.applications()
|
.applications()
|
||||||
@@ -431,42 +428,25 @@ async function setupRecordingsWatcher() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process management
|
|
||||||
async function getProcessGroupId(pid: number): Promise<number | null> {
|
|
||||||
try {
|
|
||||||
const execAsync = promisify(exec);
|
|
||||||
const { stdout } = await execAsync(`ps -o pgid -p ${pid}`);
|
|
||||||
const lines = stdout.trim().split('\n');
|
|
||||||
if (lines.length < 2) return null;
|
|
||||||
|
|
||||||
const pgid = parseInt(lines[1].trim(), 10);
|
|
||||||
return isNaN(pgid) ? null : pgid;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Application management
|
// Application management
|
||||||
const shareableContent = new ShareableContent();
|
const shareableContent = new ShareableContent();
|
||||||
|
|
||||||
async function getAllApps(): Promise<AppInfo[]> {
|
async function getAllApps(): Promise<AppInfo[]> {
|
||||||
const apps = await Promise.all(
|
const apps = shareableContent.applications().map(app => {
|
||||||
shareableContent.applications().map(async app => {
|
try {
|
||||||
try {
|
return {
|
||||||
return {
|
app,
|
||||||
app,
|
processId: app.processId,
|
||||||
processId: app.processId,
|
processGroupId: app.processGroupId,
|
||||||
processGroupId: await getProcessGroupId(app.processId),
|
bundleIdentifier: app.bundleIdentifier,
|
||||||
bundleIdentifier: app.bundleIdentifier,
|
name: app.name,
|
||||||
name: app.name,
|
running: app.isRunning,
|
||||||
running: app.isRunning,
|
};
|
||||||
};
|
} catch (error) {
|
||||||
} catch (error) {
|
console.error(error);
|
||||||
console.error(error);
|
return null;
|
||||||
return null;
|
}
|
||||||
}
|
});
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const filteredApps = apps.filter(
|
const filteredApps = apps.filter(
|
||||||
(v): v is AppInfo =>
|
(v): v is AppInfo =>
|
||||||
|
|||||||
Vendored
+1
@@ -3,6 +3,7 @@
|
|||||||
export declare class Application {
|
export declare class Application {
|
||||||
static tapGlobalAudio(excludedProcesses: Array<Application> | undefined | null, audioStreamCallback: ((err: Error | null, arg: Float32Array) => void)): AudioTapStream
|
static tapGlobalAudio(excludedProcesses: Array<Application> | undefined | null, audioStreamCallback: ((err: Error | null, arg: Float32Array) => void)): AudioTapStream
|
||||||
get processId(): number
|
get processId(): number
|
||||||
|
get processGroupId(): number
|
||||||
get bundleIdentifier(): string
|
get bundleIdentifier(): string
|
||||||
get name(): string
|
get name(): string
|
||||||
get icon(): Buffer
|
get icon(): Buffer
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ block2 = { workspace = true }
|
|||||||
core-foundation = { workspace = true, features = ["with-uuid"] }
|
core-foundation = { workspace = true, features = ["with-uuid"] }
|
||||||
coreaudio-rs = { workspace = true }
|
coreaudio-rs = { workspace = true }
|
||||||
dispatch2 = { workspace = true }
|
dispatch2 = { workspace = true }
|
||||||
|
libc = { workspace = true }
|
||||||
objc2 = { workspace = true }
|
objc2 = { workspace = true }
|
||||||
objc2-foundation = { workspace = true }
|
objc2-foundation = { workspace = true }
|
||||||
screencapturekit = { workspace = true }
|
screencapturekit = { workspace = true }
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ use coreaudio::sys::{
|
|||||||
AudioObjectAddPropertyListenerBlock, AudioObjectID, AudioObjectPropertyAddress,
|
AudioObjectAddPropertyListenerBlock, AudioObjectID, AudioObjectPropertyAddress,
|
||||||
AudioObjectRemovePropertyListenerBlock,
|
AudioObjectRemovePropertyListenerBlock,
|
||||||
};
|
};
|
||||||
|
use libc;
|
||||||
use napi::{
|
use napi::{
|
||||||
bindgen_prelude::{Buffer, Error, Float32Array, Result, Status},
|
bindgen_prelude::{Buffer, Error, Float32Array, Result, Status},
|
||||||
threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode},
|
threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode},
|
||||||
@@ -27,7 +28,6 @@ use napi::{
|
|||||||
use napi_derive::napi;
|
use napi_derive::napi;
|
||||||
use objc2::{
|
use objc2::{
|
||||||
msg_send,
|
msg_send,
|
||||||
rc::Retained,
|
|
||||||
runtime::{AnyClass, AnyObject},
|
runtime::{AnyClass, AnyObject},
|
||||||
Encode, Encoding,
|
Encode, Encoding,
|
||||||
};
|
};
|
||||||
@@ -114,136 +114,227 @@ impl TappableApplication {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self) -> Result<String> {
|
fn name(&self) -> Result<String> {
|
||||||
let pid = self.process_id()?;
|
// Use catch_unwind to prevent any panics
|
||||||
|
let name_result = std::panic::catch_unwind(|| {
|
||||||
|
// Get process ID with error handling
|
||||||
|
let pid = match self.process_id() {
|
||||||
|
Ok(pid) => pid,
|
||||||
|
Err(_) => {
|
||||||
|
return Ok(String::new());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Get NSRunningApplication class
|
// Get NSRunningApplication class with error handling
|
||||||
let running_app_class = NSRUNNING_APPLICATION_CLASS.as_ref().ok_or_else(|| {
|
let running_app_class = match NSRUNNING_APPLICATION_CLASS.as_ref() {
|
||||||
Error::new(
|
Some(class) => class,
|
||||||
Status::GenericFailure,
|
None => {
|
||||||
"NSRunningApplication class not found",
|
return Ok(String::new());
|
||||||
)
|
}
|
||||||
})?;
|
};
|
||||||
|
|
||||||
// Get running application with PID
|
// Get running application with PID
|
||||||
let running_app: *mut AnyObject =
|
let running_app: *mut AnyObject =
|
||||||
unsafe { msg_send![*running_app_class, runningApplicationWithProcessIdentifier: pid] };
|
unsafe { msg_send![*running_app_class, runningApplicationWithProcessIdentifier: pid] };
|
||||||
if running_app.is_null() {
|
|
||||||
return Ok(String::new());
|
if running_app.is_null() {
|
||||||
|
return Ok(String::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instead of using Retained::from_raw which takes ownership,
|
||||||
|
// we'll just copy the string value and let the Objective-C runtime
|
||||||
|
// handle the memory management of the original object
|
||||||
|
unsafe {
|
||||||
|
// Get localized name
|
||||||
|
let name_ptr: *mut NSString = msg_send![running_app, localizedName];
|
||||||
|
if name_ptr.is_null() {
|
||||||
|
return Ok(String::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a copy of the string without taking ownership of the NSString
|
||||||
|
let length: usize = msg_send![name_ptr, length];
|
||||||
|
let utf8_ptr: *const u8 = msg_send![name_ptr, UTF8String];
|
||||||
|
|
||||||
|
if utf8_ptr.is_null() {
|
||||||
|
return Ok(String::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes = std::slice::from_raw_parts(utf8_ptr, length);
|
||||||
|
match std::str::from_utf8(bytes) {
|
||||||
|
Ok(s) => Ok(s.to_string()),
|
||||||
|
Err(_) => Ok(String::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle any panics that might have occurred
|
||||||
|
match name_result {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(_) => Ok(String::new()),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get localized name
|
|
||||||
let name: *mut NSString = unsafe { msg_send![running_app, localizedName] };
|
|
||||||
if name.is_null() {
|
|
||||||
return Ok(String::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a safe wrapper and convert to string
|
|
||||||
let name = unsafe {
|
|
||||||
Retained::from_raw(name).ok_or_else(|| {
|
|
||||||
Error::new(
|
|
||||||
Status::GenericFailure,
|
|
||||||
"Failed to create safe wrapper for localizedName",
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
};
|
|
||||||
Ok(name.to_string())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn icon(&self) -> Result<Vec<u8>> {
|
fn icon(&self) -> Result<Vec<u8>> {
|
||||||
let pid = self.process_id()?;
|
// Use catch_unwind to prevent any panics
|
||||||
|
let icon_result = std::panic::catch_unwind(|| {
|
||||||
// Get NSRunningApplication class
|
// Get process ID with error handling
|
||||||
let running_app_class = NSRUNNING_APPLICATION_CLASS.as_ref().ok_or_else(|| {
|
let pid = match self.process_id() {
|
||||||
Error::new(
|
Ok(pid) => pid,
|
||||||
Status::GenericFailure,
|
Err(_) => {
|
||||||
"NSRunningApplication class not found",
|
return Ok(Vec::new());
|
||||||
)
|
}
|
||||||
})?;
|
|
||||||
|
|
||||||
// Get running application with PID
|
|
||||||
let running_app: *mut AnyObject =
|
|
||||||
unsafe { msg_send![*running_app_class, runningApplicationWithProcessIdentifier: pid] };
|
|
||||||
if running_app.is_null() {
|
|
||||||
return Ok(Vec::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
// Get original icon
|
|
||||||
let icon: *mut AnyObject = msg_send![running_app, icon];
|
|
||||||
if icon.is_null() {
|
|
||||||
return Ok(Vec::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new NSImage with 64x64 size
|
|
||||||
let nsimage_class = AnyClass::get(c"NSImage")
|
|
||||||
.ok_or_else(|| Error::new(Status::GenericFailure, "NSImage class not found"))?;
|
|
||||||
let resized_image: *mut AnyObject = msg_send![nsimage_class, alloc];
|
|
||||||
let resized_image: *mut AnyObject =
|
|
||||||
msg_send![resized_image, initWithSize: NSSize { width: 64.0, height: 64.0 }];
|
|
||||||
let _: () = msg_send![resized_image, lockFocus];
|
|
||||||
|
|
||||||
// Define drawing rectangle for 64x64 image
|
|
||||||
let draw_rect = NSRect {
|
|
||||||
origin: NSPoint { x: 0.0, y: 0.0 },
|
|
||||||
size: NSSize {
|
|
||||||
width: 64.0,
|
|
||||||
height: 64.0,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Draw the original icon into draw_rect (using NSCompositingOperationCopy = 2)
|
// Get NSRunningApplication class with error handling
|
||||||
let _: () = msg_send![icon, drawInRect: draw_rect, fromRect: NSRect { origin: NSPoint { x: 0.0, y: 0.0 }, size: NSSize { width: 0.0, height: 0.0 } }, operation: 2, fraction: 1.0];
|
let running_app_class = match NSRUNNING_APPLICATION_CLASS.as_ref() {
|
||||||
let _: () = msg_send![resized_image, unlockFocus];
|
Some(class) => class,
|
||||||
|
None => {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Get TIFF representation from the downsized image
|
// Get running application with PID
|
||||||
let tiff_data: *mut AnyObject = msg_send![resized_image, TIFFRepresentation];
|
let running_app: *mut AnyObject =
|
||||||
if tiff_data.is_null() {
|
unsafe { msg_send![*running_app_class, runningApplicationWithProcessIdentifier: pid] };
|
||||||
|
if running_app.is_null() {
|
||||||
return Ok(Vec::new());
|
return Ok(Vec::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create bitmap image rep from TIFF
|
unsafe {
|
||||||
let bitmap_class = AnyClass::get(c"NSBitmapImageRep")
|
// Get original icon
|
||||||
.ok_or_else(|| Error::new(Status::GenericFailure, "NSBitmapImageRep class not found"))?;
|
let icon: *mut AnyObject = msg_send![running_app, icon];
|
||||||
let bitmap: *mut AnyObject = msg_send![bitmap_class, imageRepWithData: tiff_data];
|
if icon.is_null() {
|
||||||
if bitmap.is_null() {
|
return Ok(Vec::new());
|
||||||
return Ok(Vec::new());
|
}
|
||||||
|
|
||||||
|
// Create a new NSImage with 64x64 size
|
||||||
|
let nsimage_class = match AnyClass::get(c"NSImage") {
|
||||||
|
Some(class) => class,
|
||||||
|
None => return Ok(Vec::new()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let resized_image: *mut AnyObject = msg_send![nsimage_class, alloc];
|
||||||
|
if resized_image.is_null() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let resized_image: *mut AnyObject =
|
||||||
|
msg_send![resized_image, initWithSize: NSSize { width: 64.0, height: 64.0 }];
|
||||||
|
if resized_image.is_null() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let _: () = msg_send![resized_image, lockFocus];
|
||||||
|
|
||||||
|
// Define drawing rectangle for 64x64 image
|
||||||
|
let draw_rect = NSRect {
|
||||||
|
origin: NSPoint { x: 0.0, y: 0.0 },
|
||||||
|
size: NSSize {
|
||||||
|
width: 64.0,
|
||||||
|
height: 64.0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Draw the original icon into draw_rect (using NSCompositingOperationCopy = 2)
|
||||||
|
let _: () = msg_send![icon, drawInRect: draw_rect, fromRect: NSRect { origin: NSPoint { x: 0.0, y: 0.0 }, size: NSSize { width: 0.0, height: 0.0 } }, operation: 2, fraction: 1.0];
|
||||||
|
let _: () = msg_send![resized_image, unlockFocus];
|
||||||
|
|
||||||
|
// Get TIFF representation from the downsized image
|
||||||
|
let tiff_data: *mut AnyObject = msg_send![resized_image, TIFFRepresentation];
|
||||||
|
if tiff_data.is_null() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create bitmap image rep from TIFF
|
||||||
|
let bitmap_class = match AnyClass::get(c"NSBitmapImageRep") {
|
||||||
|
Some(class) => class,
|
||||||
|
None => return Ok(Vec::new()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let bitmap: *mut AnyObject = msg_send![bitmap_class, imageRepWithData: tiff_data];
|
||||||
|
if bitmap.is_null() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create properties dictionary with compression factor
|
||||||
|
let dict_class = match AnyClass::get(c"NSMutableDictionary") {
|
||||||
|
Some(class) => class,
|
||||||
|
None => return Ok(Vec::new()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let properties: *mut AnyObject = msg_send![dict_class, dictionary];
|
||||||
|
if properties.is_null() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add compression properties
|
||||||
|
let compression_key = NSString::from_str("NSImageCompressionFactor");
|
||||||
|
let number_class = match AnyClass::get(c"NSNumber") {
|
||||||
|
Some(class) => class,
|
||||||
|
None => return Ok(Vec::new()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let compression_value: *mut AnyObject = msg_send![number_class, numberWithDouble: 0.8];
|
||||||
|
if compression_value.is_null() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let _: () = msg_send![properties, setObject: compression_value, forKey: &*compression_key];
|
||||||
|
|
||||||
|
// Get PNG data with properties
|
||||||
|
let png_data: *mut AnyObject =
|
||||||
|
msg_send![bitmap, representationUsingType: 4, properties: properties]; // 4 = PNG
|
||||||
|
|
||||||
|
if png_data.is_null() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get bytes from NSData
|
||||||
|
let bytes: *const u8 = msg_send![png_data, bytes];
|
||||||
|
let length: usize = msg_send![png_data, length];
|
||||||
|
|
||||||
|
if bytes.is_null() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy bytes into a Vec<u8> instead of using the original memory
|
||||||
|
let data = std::slice::from_raw_parts(bytes, length).to_vec();
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle any panics that might have occurred
|
||||||
|
match icon_result {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(_) => Ok(Vec::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_group_id(&self) -> Result<i32> {
|
||||||
|
// Use catch_unwind to prevent any panics
|
||||||
|
let pgid_result = std::panic::catch_unwind(|| {
|
||||||
|
// First get the process ID
|
||||||
|
let pid = match self.process_id() {
|
||||||
|
Ok(pid) => pid,
|
||||||
|
Err(_) => {
|
||||||
|
return Ok(-1); // Return -1 for error cases
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Call libc's getpgid function to get the process group ID
|
||||||
|
let pgid = unsafe { libc::getpgid(pid) };
|
||||||
|
|
||||||
|
// getpgid returns -1 on error
|
||||||
|
if pgid == -1 {
|
||||||
|
return Ok(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create properties dictionary with compression factor
|
Ok(pgid)
|
||||||
let dict_class = AnyClass::get(c"NSMutableDictionary").ok_or_else(|| {
|
});
|
||||||
Error::new(
|
|
||||||
Status::GenericFailure,
|
|
||||||
"NSMutableDictionary class not found",
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
let properties: *mut AnyObject = msg_send![dict_class, dictionary];
|
|
||||||
|
|
||||||
// Add compression properties
|
// Handle any panics
|
||||||
let compression_key = NSString::from_str("NSImageCompressionFactor");
|
match pgid_result {
|
||||||
let number_class = AnyClass::get(c"NSNumber")
|
Ok(result) => result,
|
||||||
.ok_or_else(|| Error::new(Status::GenericFailure, "NSNumber class not found"))?;
|
Err(_) => Ok(-1),
|
||||||
let compression_value: *mut AnyObject = msg_send![number_class, numberWithDouble: 0.8];
|
|
||||||
let _: () = msg_send![properties, setObject: compression_value, forKey: &*compression_key];
|
|
||||||
|
|
||||||
// Get PNG data with properties
|
|
||||||
let png_data: *mut AnyObject =
|
|
||||||
msg_send![bitmap, representationUsingType: 4, properties: properties]; // 4 = PNG
|
|
||||||
|
|
||||||
if png_data.is_null() {
|
|
||||||
return Ok(Vec::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get bytes from NSData
|
|
||||||
let bytes: *const u8 = msg_send![png_data, bytes];
|
|
||||||
let length: usize = msg_send![png_data, length];
|
|
||||||
|
|
||||||
if bytes.is_null() {
|
|
||||||
return Ok(Vec::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy bytes into a Vec<u8>
|
|
||||||
let data = std::slice::from_raw_parts(bytes, length).to_vec();
|
|
||||||
Ok(data)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -253,6 +344,7 @@ pub struct Application {
|
|||||||
inner: TappableApplication,
|
inner: TappableApplication,
|
||||||
pub(crate) object_id: AudioObjectID,
|
pub(crate) object_id: AudioObjectID,
|
||||||
pub(crate) process_id: i32,
|
pub(crate) process_id: i32,
|
||||||
|
pub(crate) process_group_id: i32,
|
||||||
pub(crate) bundle_identifier: String,
|
pub(crate) bundle_identifier: String,
|
||||||
pub(crate) name: String,
|
pub(crate) name: String,
|
||||||
}
|
}
|
||||||
@@ -264,11 +356,13 @@ impl Application {
|
|||||||
let bundle_identifier = app.bundle_identifier()?;
|
let bundle_identifier = app.bundle_identifier()?;
|
||||||
let name = app.name()?;
|
let name = app.name()?;
|
||||||
let process_id = app.process_id()?;
|
let process_id = app.process_id()?;
|
||||||
|
let process_group_id = app.process_group_id()?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
inner: app,
|
inner: app,
|
||||||
object_id,
|
object_id,
|
||||||
process_id,
|
process_id,
|
||||||
|
process_group_id,
|
||||||
bundle_identifier,
|
bundle_identifier,
|
||||||
name,
|
name,
|
||||||
})
|
})
|
||||||
@@ -294,6 +388,11 @@ impl Application {
|
|||||||
self.process_id
|
self.process_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[napi(getter)]
|
||||||
|
pub fn process_group_id(&self) -> i32 {
|
||||||
|
self.process_group_id
|
||||||
|
}
|
||||||
|
|
||||||
#[napi(getter)]
|
#[napi(getter)]
|
||||||
pub fn bundle_identifier(&self) -> String {
|
pub fn bundle_identifier(&self) -> String {
|
||||||
self.bundle_identifier.clone()
|
self.bundle_identifier.clone()
|
||||||
@@ -306,16 +405,40 @@ impl Application {
|
|||||||
|
|
||||||
#[napi(getter)]
|
#[napi(getter)]
|
||||||
pub fn icon(&self) -> Result<Buffer> {
|
pub fn icon(&self) -> Result<Buffer> {
|
||||||
let icon = self.inner.icon()?;
|
// Use catch_unwind to prevent any panics
|
||||||
Ok(Buffer::from(icon))
|
let result = std::panic::catch_unwind(|| match self.inner.icon() {
|
||||||
|
Ok(icon) => Ok(Buffer::from(icon)),
|
||||||
|
Err(_) => Ok(Buffer::from(Vec::<u8>::new())),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle any panics
|
||||||
|
match result {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(_) => Ok(Buffer::from(Vec::<u8>::new())),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi(getter)]
|
#[napi(getter)]
|
||||||
pub fn get_is_running(&self) -> Result<bool> {
|
pub fn get_is_running(&self) -> Result<bool> {
|
||||||
Ok(get_process_property(
|
// Use catch_unwind to prevent any panics
|
||||||
&self.object_id,
|
let result = std::panic::catch_unwind(|| {
|
||||||
kAudioProcessPropertyIsRunningInput,
|
match get_process_property(&self.object_id, kAudioProcessPropertyIsRunningInput) {
|
||||||
)?)
|
Ok(is_running) => Ok(is_running),
|
||||||
|
Err(_) => {
|
||||||
|
// Default to true to avoid potential issues
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle any panics
|
||||||
|
match result {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(_) => {
|
||||||
|
// Default to true to avoid potential issues
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
|
|||||||
Reference in New Issue
Block a user