use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int, c_void};
use std::ptr;
#[link(name = "CoreGraphics", kind = "framework")]
#[link(name = "CoreFoundation", kind = "framework")]
#[link(name = "ApplicationServices", kind = "framework")]
extern "C" {
fn CGWindowListCopyWindowInfo(option: u32, relative_window_id: u32) -> *const c_void;
fn CFArrayGetCount(array: *const c_void) -> isize;
fn CFArrayGetValueAtIndex(array: *const c_void, index: isize) -> *const c_void;
fn CFDictionaryGetValue(dict: *const c_void, key: *const c_void) -> *const c_void;
fn CFStringCreateWithCString(
allocator: *const c_void,
c_str: *const c_char,
encoding: u32,
) -> *const c_void;
fn CFStringGetCStringPtr(string: *const c_void, encoding: u32) -> *const c_char;
fn CFStringGetCString(
string: *const c_void,
buffer: *mut c_char,
buffer_size: isize,
encoding: u32,
) -> bool;
fn CFNumberGetValue(number: *const c_void, number_type: c_int, value_ptr: *mut c_void) -> bool;
fn CFRelease(cf_type: *const c_void);
fn CFGetTypeID(cf_type: *const c_void) -> usize;
fn CFStringGetTypeID() -> usize;
fn CFNumberGetTypeID() -> usize;
}
const K_CG_WINDOW_LIST_OPTION_ALL: u32 = 0;
const K_CF_STRING_ENCODING_UTF8: u32 = 0x08000100;
const K_CF_NUMBER_INT_TYPE: c_int = 9;
const WINDOW_OWNER_NAME: &str = "kCGWindowOwnerName";
const WINDOW_SHARING_STATE: &str = "kCGWindowSharingState";
const WINDOW_LAYER: &str = "kCGWindowLayer";
const WINDOW_NUMBER: &str = "kCGWindowNumber";
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct ClueLyDetectionResult {
pub is_detected: bool,
pub window_count: u32,
pub screen_capture_evasion_count: u32, pub elevated_layer_count: u32, pub max_layer_detected: i32, }
#[derive(Debug)]
struct WindowInfo {
owner: String,
window_id: i32,
sharing_state: i32,
layer: i32,
}
fn create_cfstring(s: &str) -> *const c_void {
let c_str = CString::new(s).unwrap();
unsafe { CFStringCreateWithCString(ptr::null(), c_str.as_ptr(), K_CF_STRING_ENCODING_UTF8) }
}
fn cfstring_to_string(cf_string: *const c_void) -> String {
if cf_string.is_null() {
return String::new();
}
unsafe {
let c_str_ptr = CFStringGetCStringPtr(cf_string, K_CF_STRING_ENCODING_UTF8);
if !c_str_ptr.is_null() {
return CStr::from_ptr(c_str_ptr).to_string_lossy().into_owned();
}
let mut buffer = vec![0u8; 1024];
let success = CFStringGetCString(
cf_string,
buffer.as_mut_ptr() as *mut c_char,
buffer.len() as isize,
K_CF_STRING_ENCODING_UTF8,
);
if success {
let c_str = CStr::from_ptr(buffer.as_ptr() as *const c_char);
c_str.to_string_lossy().into_owned()
} else {
String::new()
}
}
}
fn get_dict_string(dict: *const c_void, key: &str) -> String {
unsafe {
let cf_key = create_cfstring(key);
let value = CFDictionaryGetValue(dict, cf_key);
CFRelease(cf_key);
if value.is_null() {
return String::new();
}
if CFGetTypeID(value) == CFStringGetTypeID() {
cfstring_to_string(value)
} else {
String::new()
}
}
}
fn get_dict_int(dict: *const c_void, key: &str) -> i32 {
unsafe {
let cf_key = create_cfstring(key);
let value = CFDictionaryGetValue(dict, cf_key);
CFRelease(cf_key);
if value.is_null() {
return 0;
}
if CFGetTypeID(value) == CFNumberGetTypeID() {
let mut result: i32 = 0;
CFNumberGetValue(
value,
K_CF_NUMBER_INT_TYPE,
&mut result as *mut i32 as *mut c_void,
);
result
} else {
0
}
}
}
fn is_cluely_process(owner: &str) -> bool {
let owner_lower = owner.to_lowercase();
if owner_lower.contains("no-cluely") {
return false;
}
owner_lower.contains("cluely")
|| owner_lower.contains("clue.ly")
|| owner_lower.contains("com.cluely")
|| owner_lower.contains("io.cluely")
|| owner_lower.contains("co.cluely")
}
fn analyze_cluely_windows() -> (Vec<WindowInfo>, ClueLyDetectionResult) {
let mut cluely_windows = Vec::new();
let mut result = ClueLyDetectionResult {
is_detected: false,
window_count: 0,
screen_capture_evasion_count: 0,
elevated_layer_count: 0,
max_layer_detected: 0,
};
unsafe {
let window_list = CGWindowListCopyWindowInfo(K_CG_WINDOW_LIST_OPTION_ALL, 0);
if window_list.is_null() {
return (cluely_windows, result);
}
let count = CFArrayGetCount(window_list);
for i in 0..count {
let window_dict = CFArrayGetValueAtIndex(window_list, i);
if window_dict.is_null() {
continue;
}
let owner = get_dict_string(window_dict, WINDOW_OWNER_NAME);
if is_cluely_process(&owner) {
let window_id = get_dict_int(window_dict, WINDOW_NUMBER);
let sharing_state = get_dict_int(window_dict, WINDOW_SHARING_STATE);
let layer = get_dict_int(window_dict, WINDOW_LAYER);
let window_info = WindowInfo {
owner,
window_id,
sharing_state,
layer,
};
result.is_detected = true;
result.window_count += 1;
if sharing_state == 0 {
result.screen_capture_evasion_count += 1;
}
if layer > 0 {
result.elevated_layer_count += 1;
if layer > result.max_layer_detected {
result.max_layer_detected = layer;
}
}
cluely_windows.push(window_info);
}
}
CFRelease(window_list);
}
(cluely_windows, result)
}
pub fn detect_cluely_rust() -> ClueLyDetectionResult {
let (_, result) = analyze_cluely_windows();
result
}
pub fn is_cluely_running_rust() -> bool {
let result = detect_cluely_rust();
result.is_detected
}
pub fn get_cluely_window_count_rust() -> u32 {
let result = detect_cluely_rust();
result.window_count
}
#[no_mangle]
pub extern "C" fn detect_cluely() -> ClueLyDetectionResult {
let (_, result) = analyze_cluely_windows();
result
}
#[no_mangle]
pub extern "C" fn is_cluely_running() -> c_int {
if detect_cluely().is_detected {
1
} else {
0
}
}
#[no_mangle]
pub extern "C" fn get_cluely_window_count() -> u32 {
detect_cluely().window_count
}
#[no_mangle]
pub extern "C" fn get_cluely_report() -> *mut c_char {
let (windows, result) = analyze_cluely_windows();
let mut report = String::new();
if result.is_detected {
report.push_str("🚨 CLUELY EMPLOYEE MONITORING DETECTED\n");
report.push_str("=====================================\n\n");
report.push_str("📊 Summary:\n");
report.push_str(&format!(
" • Total Cluely windows: {}\n",
result.window_count
));
report.push_str(&format!(
" • Screen capture evasion: {}\n",
result.screen_capture_evasion_count
));
report.push_str(&format!(
" • Elevated layer usage: {}\n",
result.elevated_layer_count
));
if result.max_layer_detected > 0 {
report.push_str(&format!(
" • Highest layer detected: {}\n",
result.max_layer_detected
));
}
report.push('\n');
report.push_str("🔍 Evasion Techniques Detected:\n");
if result.screen_capture_evasion_count > 0 {
report.push_str(&format!(
" ⚠️ {} window(s) configured to avoid screen capture\n",
result.screen_capture_evasion_count
));
}
if result.elevated_layer_count > 0 {
report.push_str(&format!(
" ⚠️ {} window(s) using elevated display layers\n",
result.elevated_layer_count
));
}
report.push('\n');
report.push_str("📋 Window Details:\n");
for (i, window) in windows.iter().enumerate() {
report.push_str(&format!(
" {}. Window ID: {} [{}]\n",
i + 1,
window.window_id,
window.owner
));
report.push_str(&format!(
" - Sharing State: {} {}\n",
window.sharing_state,
if window.sharing_state == 0 {
"(avoiding screen capture)"
} else {
"(normal)"
}
));
report.push_str(&format!(
" - Layer: {} {}\n",
window.layer,
if window.layer > 0 {
"(elevated - potential overlay)"
} else {
"(normal)"
}
));
let mut techniques = Vec::new();
if window.sharing_state == 0 {
techniques.push("Screen capture evasion");
}
if window.layer > 0 {
techniques.push("Elevated layer positioning");
}
if !techniques.is_empty() {
report.push_str(&format!(" - Techniques: {}\n", techniques.join(", ")));
}
report.push('\n');
}
report.push_str("⚠️ WARNING:\n");
report.push_str(" This software is designed to monitor employee activity\n");
report.push_str(" while remaining hidden during screen sharing sessions.\n");
report.push_str(" Your activities may be recorded even when sharing your screen.\n");
} else {
report.push_str("✅ NO CLUELY MONITORING DETECTED\n");
report.push_str("================================\n\n");
report.push_str("No Cluely employee monitoring software found.\n");
report.push_str("Your system appears to be free from this monitoring tool.\n");
}
let c_string = CString::new(report).unwrap();
c_string.into_raw()
}
#[no_mangle]
#[allow(clippy::missing_safety_doc)]
pub unsafe extern "C" fn free_cluely_report(ptr: *mut c_char) {
if !ptr.is_null() {
unsafe {
let _ = CString::from_raw(ptr);
}
}
}