【问题标题】:Getting window owner names via CGWindowListCopyWindowInfo in Rust在 Rust 中通过 CGWindowListCopyWindowInfo 获取窗口所有者名称
【发布时间】:2020-02-07 16:19:24
【问题描述】:

我正在尝试通过 Rust 中的 CGWindowListCopyWindowInfo 获取窗口所有者名称。到目前为止,我已经设法获得了 CFDictionaryRef,但我无法为 CFDictionaryGetValueIfPresent 使用正确的指针。

这是方法签名:https://docs.rs/CoreFoundation-sys/0.1.4/CoreFoundation_sys/dictionary/fn.CFDictionaryGetValueIfPresent.html

const options: CGWindowListOption = kCGWindowListOptionOnScreenOnly + kCGWindowListExcludeDesktopElements;
const ptr_window_list_info: CFArrayRef = unsafe { CGWindowListCopyWindowInfo(options, kCGNullWindowID) as CFArrayRef };

const count = unsafe { CFArrayGetCount(ptr_window_list_info) };

for i in 0..count-1 {
    let dic_ref = CFArrayGetValueAtIndex(ptr_window_list_info, i) as CFDictionaryRef;
    //let key = CFString::new("kCGWindowOwnerName");
    let b = CFDictionaryGetValueIfPresent(dic_ref, ?, ?);
    println!("window owner name: {}", value);
}

我是 Rust 新手,希望能得到任何帮助。

【问题讨论】:

    标签: macos rust core-foundation


    【解决方案1】:

    除了TheNextman的回答,我还修改了代码,避免使用transmute。

    use core_graphics::display::*;
    use core_foundation::string::*;
    use core_foundation::number::*;
    use core_foundation::base::*;
    use std::ffi::{ CStr, c_void };
    
    fn main() {
        const OPTIONS: CGWindowListOption = kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements;
        let window_list_info = unsafe { CGWindowListCopyWindowInfo(OPTIONS, kCGNullWindowID) };
        let count = unsafe { CFArrayGetCount(window_list_info) };
    
        for i in 0..count {
    
            let dic_ref = unsafe { CFArrayGetValueAtIndex(window_list_info, i as isize) as CFDictionaryRef };
    
            let key = CFString::new("kCGWindowOwnerName");
            let mut value: *const c_void = std::ptr::null();
    
            if unsafe { CFDictionaryGetValueIfPresent(dic_ref, key.to_void(), &mut value) != 0 } {
    
                let cf_ref = value as CFStringRef;
                let c_ptr = unsafe { CFStringGetCStringPtr(cf_ref, kCFStringEncodingUTF8) };
                if !c_ptr.is_null() {
                    let c_result = unsafe { CStr::from_ptr(c_ptr) };
                    let result = String::from(c_result.to_str().unwrap());
                    println!("window owner name: {}", result)
                }
    
            }
    
        }
    
        unsafe {
            CFRelease(window_list_info as CFTypeRef)
        }
    }
    

    【讨论】:

    • key.to_void()
    • 您还确保发布CGWindowListCopyWindowInfo 的结果,我的答案错过了
    【解决方案2】:

    CFDictionaryGetValueIfPresent 看起来像这样:

    Boolean CFDictionaryGetValueIfPresent(CFDictionaryRef theDict, const void *key, const void **value);

    在 core_foundation 中是这样的:

    pub unsafe extern "C" fn CFDictionaryGetValueIfPresent(
        theDict: CFDictionaryRef, 
        key: *const c_void, 
        value: *mut *const c_void
    ) -> Boolean
    

    我相信关键是CFStringRef;在 C 中,有一个宏可以让这更容易(CFSTR("cstring")),这里我们必须使用稍长的形式:

    let c_key = CString::new("kCGWindowOwnerName").unwrap();
    let cf_key = unsafe { 
        CFStringCreateWithCString(std::ptr::null(), c_key.as_ptr(), kCFStringEncodingUTF8) 
    };
    

    这给了我们CFStringRef,在 core_foundation 中它是这样定义的:

    pub type CFStringRef = *const __CFString;

    它已经是一个*const 指针,只是不是我们需要的类型。你可以改为c_void

    let key_ptr: *const c_void = unsafe { std::mem::transmute(cf_key) };

    value 参数只需要一个原始双指针来存储结果。创建空指针:

    let mut value: *const c_void = std::ptr::null();

    并传递对其的可变引用 (&mut value) 以获取 *mut *const c_void


    您的 rust 代码中有一些错误和奇怪之处。这是一个带注释的工作示例:

    use core_graphics::display::*;
    use core_foundation::string::*;
    use std::ffi::{ CStr, CString, c_void };
    
    fn main() {
        // CGWindowListOption is a bitmask, combine the flags with bitwise OR
        const OPTIONS: CGWindowListOption = kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements;
        // No need to specify the type or use 'as'; CFArrayRef is the return type from CGWindowListCopyWindowInfo
        let window_list_info = unsafe { CGWindowListCopyWindowInfo(OPTIONS, kCGNullWindowID) };
        // Don't use const here, CFArrayGetCount returns CFIndex (long)
        let count: i64 = unsafe { CFArrayGetCount(window_list_info) };
    
        for i in 0..count-1 {
            // Here we need the 'as', CFArrayGetValueAtIndex just returns void*
            let dic_ref = unsafe { CFArrayGetValueAtIndex(window_list_info, i) as CFDictionaryRef };
    
            // Create a CString from the key name we are interested in
            let c_key = CString::new("kCGWindowOwnerName").unwrap();
            // Create a CFString, needs to be released with `CFRelease`. I leave that as an exercise for the reader.
            let cf_key = unsafe { CFStringCreateWithCString(std::ptr::null(), c_key.as_ptr(), kCFStringEncodingUTF8) };
    
            // cf_key is a CFStringRef, which is a type alias to *const __CFString
            // We transmute it into *const c_void since that is what CFDictionaryGetValueIfPresent wants
            let key_ptr: *const c_void = unsafe { std::mem::transmute(cf_key) };
    
            // A raw void* to hold the result
            let mut value: *const c_void = std::ptr::null();
    
            if unsafe { CFDictionaryGetValueIfPresent(dic_ref, key_ptr, &mut value) != 0 } {
                // CFDictionaryGetValueIfPresent returned true; that means value must point to a CFStringRef
                let cf_ref = value as core_foundation::string::CFStringRef;
                // Get a pointer to a C-string buffer with the characters from the CFString
                let c_ptr = unsafe { CFStringGetCStringPtr(cf_ref, kCFStringEncodingUTF8) };
    
                // The value may be null; don't pass it to CStr::from_ptr
                if c_ptr.is_null() { continue; }
    
                // Wrap the pointer in a rust CStr so we can convert a str
                let c_value = unsafe { CStr::from_ptr(c_ptr) };
                println!("{}", c_value.to_str().unwrap());
            }
        }
    }
    

    作为参考,这里是 C 中的相同示例:

    #import <Foundation/Foundation.h>
    #import <CoreGraphics/CoreGraphics.h>
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            CGWindowListOption options = kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements;
            CFArrayRef windows = CGWindowListCopyWindowInfo(options, kCGNullWindowID);
    
            CFIndex count = CFArrayGetCount(windows);
    
            for (int i = 0; i < count; i++)
            {
                CFDictionaryRef windowDict = CFArrayGetValueAtIndex(windows, i);
                CFStringRef key = CFStringCreateWithCString(NULL, "kCGWindowOwnerName", kCFStringEncodingUTF8);
                const void* value = nil;
    
                if (CFDictionaryGetValueIfPresent(windowDict, key, &value) == YES)
                {
                    const char* c_value = CFStringGetCStringPtr(value, kCFStringEncodingUTF8);
    
                    NSLog(@"window: %s", c_value);
                }
    
                CFRelease(key);
            }
        }
        return 0;
    }
    
    

    免责声明我对 rust 也比较陌生,这可能不是惯用的解决方案

    【讨论】:

    • 感谢您提供如此详细的答案。一个问题,在循环中使用 isize 和 i64 有什么不同?
    • 没有区别 AFAIK;我只是在移植 C 版本,CFArrayGetCount 的返回类型是 CFIndex
    【解决方案3】:

    以防万一您也偶然发现了这个问题。 kCGWindowOwnerName 的某些条目不是有效的简单 CFString,这意味着转换后您最终会得到一个空指针。

    要验证这一点,您可以运行这个小 c 程序:

    #include <Carbon/Carbon.h>
    
    // compile as such:
    //  clang -framework carbon get-win.c -o get-win
    
    int main(int argc, const char *argv[]) {
        CGWindowListOption options = kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements;
        CFArrayRef windows = CGWindowListCopyWindowInfo(options, kCGNullWindowID);
        CFIndex count = CFArrayGetCount(windows);
    
        for (int i = 0; i < count; i++) {
            CFDictionaryRef windowDict = CFArrayGetValueAtIndex(windows, i);
            CFStringRef key = CFStringCreateWithCString(NULL, "kCGWindowOwnerName", kCFStringEncodingUTF8);
            const void *value = nil;
    
            if (CFDictionaryGetValueIfPresent(windowDict, key, &value) == 1) {
                const char *c_value = CFStringGetCStringPtr(value, kCFStringEncodingUTF8);
                if( c_value == NULL ) {
                    // this is the very strange case, where a window owner name c_value is null, but `CFShow(value)`
                    // yields a string, just without the regular quotation marks
                    // like Finder vs "Finder"
                    CFShow(value);
                    CFShowStr(value);   // tells: This is an NSString, not CFString
                } else {
                    printf("kCGWindowOwnerName = %s\n", c_value);
                    printf("-- Details about the string:");
                    CFShowStr(value);
                    printf("-- End of Details\n\n");
                }
            }
    
            CFRelease(key);
        }
        CFRelease(windows);
    
        return 0;
    }
    
    

    我还不知道如何将NSString* 转换为CFString*char*。 如果您知道如何在 c(不是 Objective-c)中执行此操作,请发表评论。

    【讨论】:

    猜你喜欢
    • 2013-05-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-14
    • 1970-01-01
    • 2021-11-27
    相关资源
    最近更新 更多