use std::path::Path;
use objc2::AnyThread;
use objc2_app_kit::{
NSApplicationActivationPolicy, NSBitmapFormat, NSBitmapImageRep, NSCalibratedRGBColorSpace,
NSCompositingOperation, NSGraphicsContext, NSImage, NSWorkspace,
};
use objc2_foundation::{NSPoint, NSRect, NSSize, NSString};
pub struct RunningApp {
pub bundle_id: String,
pub name: String,
pub bundle_path: Option<String>,
}
pub fn list_running_apps() -> Vec<RunningApp> {
super::app::run_on_main_sync(|| {
let workspace = NSWorkspace::sharedWorkspace();
let apps = workspace.runningApplications();
let mut seen = std::collections::HashSet::new();
let mut out = Vec::new();
for app in apps.iter() {
if app.activationPolicy() != NSApplicationActivationPolicy::Regular {
continue;
}
let Some(bundle_id) = app.bundleIdentifier().map(|s| s.to_string()) else {
continue;
};
if !seen.insert(bundle_id.clone()) {
continue; }
let name = app
.localizedName()
.map(|s| s.to_string())
.unwrap_or_else(|| bundle_id.clone());
let bundle_path = app
.bundleURL()
.and_then(|u| u.path())
.map(|s| s.to_string());
out.push(RunningApp {
bundle_id,
name,
bundle_path,
});
}
out.sort_by_key(|a| a.name.to_lowercase());
out
})
}
pub fn app_icon_rgba(bundle_path: &str, size: u32) -> Option<Vec<u8>> {
let bundle_path = bundle_path.to_string();
super::app::run_on_main_sync(move || icon_rgba_main(Path::new(&bundle_path), size))
}
#[allow(deprecated)] pub fn icon_rgba_for_bundle_id(bundle_id: &str, size: u32) -> Option<Vec<u8>> {
let bundle_id = bundle_id.to_string();
super::app::run_on_main_sync(move || {
let workspace = NSWorkspace::sharedWorkspace();
let bid = NSString::from_str(&bundle_id);
let url = workspace.URLForApplicationWithBundleIdentifier(&bid)?;
let path = url.path()?;
icon_rgba_main(Path::new(&path.to_string()), size)
})
}
fn icon_rgba_main(bundle_path: &Path, size: u32) -> Option<Vec<u8>> {
let workspace = NSWorkspace::sharedWorkspace();
let ns_path = NSString::from_str(&bundle_path.to_string_lossy());
let icon: objc2::rc::Retained<NSImage> = workspace.iconForFile(&ns_path);
let target = NSSize {
width: size as f64,
height: size as f64,
};
icon.setSize(target);
let bitmap = unsafe {
NSBitmapImageRep::initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bitmapFormat_bytesPerRow_bitsPerPixel(
NSBitmapImageRep::alloc(),
std::ptr::null_mut(),
size as isize,
size as isize,
8,
4,
true,
false,
NSCalibratedRGBColorSpace,
NSBitmapFormat(0),
(size * 4) as isize,
32,
)?
};
let ctx = NSGraphicsContext::graphicsContextWithBitmapImageRep(&bitmap)?;
NSGraphicsContext::saveGraphicsState_class();
NSGraphicsContext::setCurrentContext(Some(&ctx));
let rect = NSRect {
origin: NSPoint { x: 0.0, y: 0.0 },
size: target,
};
let zero = NSRect {
origin: NSPoint { x: 0.0, y: 0.0 },
size: NSSize {
width: 0.0,
height: 0.0,
},
};
icon.drawInRect_fromRect_operation_fraction(rect, zero, NSCompositingOperation::Copy, 1.0);
ctx.flushGraphics();
NSGraphicsContext::restoreGraphicsState_class();
let row_bytes = bitmap.bytesPerRow() as usize;
let expected_row = (size as usize) * 4;
if row_bytes != expected_row {
return None;
}
let total = expected_row * (size as usize);
let data_ptr = bitmap.bitmapData();
if data_ptr.is_null() {
return None;
}
let mut bytes = unsafe { std::slice::from_raw_parts(data_ptr, total) }.to_vec();
for px in bytes.chunks_exact_mut(4) {
let a = px[3];
if a == 0 {
continue;
}
let inv = 255.0 / a as f32;
px[0] = ((px[0] as f32 * inv).round() as u32).min(255) as u8;
px[1] = ((px[1] as f32 * inv).round() as u32).min(255) as u8;
px[2] = ((px[2] as f32 * inv).round() as u32).min(255) as u8;
}
Some(bytes)
}