1use std::ffi::{CStr, CString};
2use std::os::raw::{c_char, c_int, c_void};
3use std::ptr;
4
5#[link(name = "CoreGraphics", kind = "framework")]
7#[link(name = "CoreFoundation", kind = "framework")]
8#[link(name = "ApplicationServices", kind = "framework")]
9extern "C" {
10 fn CGWindowListCopyWindowInfo(option: u32, relative_window_id: u32) -> *const c_void;
11 fn CFArrayGetCount(array: *const c_void) -> isize;
12 fn CFArrayGetValueAtIndex(array: *const c_void, index: isize) -> *const c_void;
13 fn CFDictionaryGetValue(dict: *const c_void, key: *const c_void) -> *const c_void;
14 fn CFStringCreateWithCString(
15 allocator: *const c_void,
16 c_str: *const c_char,
17 encoding: u32,
18 ) -> *const c_void;
19 fn CFStringGetCStringPtr(string: *const c_void, encoding: u32) -> *const c_char;
20 fn CFStringGetCString(
21 string: *const c_void,
22 buffer: *mut c_char,
23 buffer_size: isize,
24 encoding: u32,
25 ) -> bool;
26 fn CFNumberGetValue(number: *const c_void, number_type: c_int, value_ptr: *mut c_void) -> bool;
27 fn CFRelease(cf_type: *const c_void);
28 fn CFGetTypeID(cf_type: *const c_void) -> usize;
29 fn CFStringGetTypeID() -> usize;
30 fn CFNumberGetTypeID() -> usize;
31}
32
33const K_CG_WINDOW_LIST_OPTION_ALL: u32 = 0;
35const K_CF_STRING_ENCODING_UTF8: u32 = 0x08000100;
36const K_CF_NUMBER_INT_TYPE: c_int = 9;
37const WINDOW_OWNER_NAME: &str = "kCGWindowOwnerName";
38const WINDOW_SHARING_STATE: &str = "kCGWindowSharingState";
39const WINDOW_LAYER: &str = "kCGWindowLayer";
40const WINDOW_NUMBER: &str = "kCGWindowNumber";
41
42#[repr(C)]
44#[derive(Debug, Clone, Copy)]
45pub struct ClueLyDetectionResult {
46 pub is_detected: bool,
47 pub window_count: u32,
48 pub screen_capture_evasion_count: u32, pub elevated_layer_count: u32, pub max_layer_detected: i32, }
52
53#[derive(Debug)]
55struct WindowInfo {
56 owner: String,
57 window_id: i32,
58 sharing_state: i32,
59 layer: i32,
60}
61
62fn create_cfstring(s: &str) -> *const c_void {
63 let c_str = CString::new(s).unwrap();
64 unsafe { CFStringCreateWithCString(ptr::null(), c_str.as_ptr(), K_CF_STRING_ENCODING_UTF8) }
65}
66
67fn cfstring_to_string(cf_string: *const c_void) -> String {
68 if cf_string.is_null() {
69 return String::new();
70 }
71
72 unsafe {
73 let c_str_ptr = CFStringGetCStringPtr(cf_string, K_CF_STRING_ENCODING_UTF8);
74 if !c_str_ptr.is_null() {
75 return CStr::from_ptr(c_str_ptr).to_string_lossy().into_owned();
76 }
77
78 let mut buffer = vec![0u8; 1024];
79 let success = CFStringGetCString(
80 cf_string,
81 buffer.as_mut_ptr() as *mut c_char,
82 buffer.len() as isize,
83 K_CF_STRING_ENCODING_UTF8,
84 );
85
86 if success {
87 let c_str = CStr::from_ptr(buffer.as_ptr() as *const c_char);
88 c_str.to_string_lossy().into_owned()
89 } else {
90 String::new()
91 }
92 }
93}
94
95fn get_dict_string(dict: *const c_void, key: &str) -> String {
96 unsafe {
97 let cf_key = create_cfstring(key);
98 let value = CFDictionaryGetValue(dict, cf_key);
99 CFRelease(cf_key);
100
101 if value.is_null() {
102 return String::new();
103 }
104
105 if CFGetTypeID(value) == CFStringGetTypeID() {
106 cfstring_to_string(value)
107 } else {
108 String::new()
109 }
110 }
111}
112
113fn get_dict_int(dict: *const c_void, key: &str) -> i32 {
114 unsafe {
115 let cf_key = create_cfstring(key);
116 let value = CFDictionaryGetValue(dict, cf_key);
117 CFRelease(cf_key);
118
119 if value.is_null() {
120 return 0;
121 }
122
123 if CFGetTypeID(value) == CFNumberGetTypeID() {
124 let mut result: i32 = 0;
125 CFNumberGetValue(
126 value,
127 K_CF_NUMBER_INT_TYPE,
128 &mut result as *mut i32 as *mut c_void,
129 );
130 result
131 } else {
132 0
133 }
134 }
135}
136
137fn is_cluely_process(owner: &str) -> bool {
138 let owner_lower = owner.to_lowercase();
139
140 if owner_lower.contains("no-cluely") {
142 return false;
143 }
144
145 owner_lower.contains("cluely")
147 || owner_lower.contains("clue.ly")
148 || owner_lower.contains("com.cluely")
149 || owner_lower.contains("io.cluely")
150 || owner_lower.contains("co.cluely")
151}
152
153fn analyze_cluely_windows() -> (Vec<WindowInfo>, ClueLyDetectionResult) {
154 let mut cluely_windows = Vec::new();
155 let mut result = ClueLyDetectionResult {
156 is_detected: false,
157 window_count: 0,
158 screen_capture_evasion_count: 0,
159 elevated_layer_count: 0,
160 max_layer_detected: 0,
161 };
162
163 unsafe {
164 let window_list = CGWindowListCopyWindowInfo(K_CG_WINDOW_LIST_OPTION_ALL, 0);
165
166 if window_list.is_null() {
167 return (cluely_windows, result);
168 }
169
170 let count = CFArrayGetCount(window_list);
171
172 for i in 0..count {
173 let window_dict = CFArrayGetValueAtIndex(window_list, i);
174 if window_dict.is_null() {
175 continue;
176 }
177
178 let owner = get_dict_string(window_dict, WINDOW_OWNER_NAME);
179 if is_cluely_process(&owner) {
180 let window_id = get_dict_int(window_dict, WINDOW_NUMBER);
181 let sharing_state = get_dict_int(window_dict, WINDOW_SHARING_STATE);
182 let layer = get_dict_int(window_dict, WINDOW_LAYER);
183
184 let window_info = WindowInfo {
185 owner,
186 window_id,
187 sharing_state,
188 layer,
189 };
190
191 result.is_detected = true;
192 result.window_count += 1;
193
194 if sharing_state == 0 {
196 result.screen_capture_evasion_count += 1;
197 }
198
199 if layer > 0 {
200 result.elevated_layer_count += 1;
201 if layer > result.max_layer_detected {
202 result.max_layer_detected = layer;
203 }
204 }
205
206 cluely_windows.push(window_info);
207 }
208 }
209
210 CFRelease(window_list);
211 }
212
213 (cluely_windows, result)
214}
215
216pub fn detect_cluely_rust() -> ClueLyDetectionResult {
219 let (_, result) = analyze_cluely_windows();
220 result
221}
222
223pub fn is_cluely_running_rust() -> bool {
225 let result = detect_cluely_rust();
226 result.is_detected
227}
228
229pub fn get_cluely_window_count_rust() -> u32 {
231 let result = detect_cluely_rust();
232 result.window_count
233}
234
235#[no_mangle]
240pub extern "C" fn detect_cluely() -> ClueLyDetectionResult {
241 let (_, result) = analyze_cluely_windows();
242 result
243}
244
245#[no_mangle]
250pub extern "C" fn is_cluely_running() -> c_int {
251 if detect_cluely().is_detected {
252 1
253 } else {
254 0
255 }
256}
257
258#[no_mangle]
263pub extern "C" fn get_cluely_window_count() -> u32 {
264 detect_cluely().window_count
265}
266
267#[no_mangle]
274pub extern "C" fn get_cluely_report() -> *mut c_char {
275 let (windows, result) = analyze_cluely_windows();
276
277 let mut report = String::new();
278
279 if result.is_detected {
280 report.push_str("🚨 CLUELY EMPLOYEE MONITORING DETECTED\n");
281 report.push_str("=====================================\n\n");
282
283 report.push_str("📊 Summary:\n");
284 report.push_str(&format!(
285 " • Total Cluely windows: {}\n",
286 result.window_count
287 ));
288 report.push_str(&format!(
289 " • Screen capture evasion: {}\n",
290 result.screen_capture_evasion_count
291 ));
292 report.push_str(&format!(
293 " • Elevated layer usage: {}\n",
294 result.elevated_layer_count
295 ));
296 if result.max_layer_detected > 0 {
297 report.push_str(&format!(
298 " • Highest layer detected: {}\n",
299 result.max_layer_detected
300 ));
301 }
302 report.push('\n');
303
304 report.push_str("🔍 Evasion Techniques Detected:\n");
305 if result.screen_capture_evasion_count > 0 {
306 report.push_str(&format!(
307 " ⚠️ {} window(s) configured to avoid screen capture\n",
308 result.screen_capture_evasion_count
309 ));
310 }
311 if result.elevated_layer_count > 0 {
312 report.push_str(&format!(
313 " ⚠️ {} window(s) using elevated display layers\n",
314 result.elevated_layer_count
315 ));
316 }
317 report.push('\n');
318
319 report.push_str("📋 Window Details:\n");
320 for (i, window) in windows.iter().enumerate() {
321 report.push_str(&format!(
322 " {}. Window ID: {} [{}]\n",
323 i + 1,
324 window.window_id,
325 window.owner
326 ));
327 report.push_str(&format!(
328 " - Sharing State: {} {}\n",
329 window.sharing_state,
330 if window.sharing_state == 0 {
331 "(avoiding screen capture)"
332 } else {
333 "(normal)"
334 }
335 ));
336 report.push_str(&format!(
337 " - Layer: {} {}\n",
338 window.layer,
339 if window.layer > 0 {
340 "(elevated - potential overlay)"
341 } else {
342 "(normal)"
343 }
344 ));
345
346 let mut techniques = Vec::new();
347 if window.sharing_state == 0 {
348 techniques.push("Screen capture evasion");
349 }
350 if window.layer > 0 {
351 techniques.push("Elevated layer positioning");
352 }
353
354 if !techniques.is_empty() {
355 report.push_str(&format!(" - Techniques: {}\n", techniques.join(", ")));
356 }
357 report.push('\n');
358 }
359
360 report.push_str("⚠️ WARNING:\n");
361 report.push_str(" This software is designed to monitor employee activity\n");
362 report.push_str(" while remaining hidden during screen sharing sessions.\n");
363 report.push_str(" Your activities may be recorded even when sharing your screen.\n");
364 } else {
365 report.push_str("✅ NO CLUELY MONITORING DETECTED\n");
366 report.push_str("================================\n\n");
367 report.push_str("No Cluely employee monitoring software found.\n");
368 report.push_str("Your system appears to be free from this monitoring tool.\n");
369 }
370
371 let c_string = CString::new(report).unwrap();
373 c_string.into_raw()
374}
375
376#[no_mangle]
382#[allow(clippy::missing_safety_doc)]
383pub unsafe extern "C" fn free_cluely_report(ptr: *mut c_char) {
384 if !ptr.is_null() {
385 unsafe {
386 let _ = CString::from_raw(ptr);
387 }
388 }
389}