如何获取应用程序是否聚焦于 macOS?

问题描述 投票:0回答:2

我需要收集哪个应用程序具有焦点。为此,我的方法是:列出窗口,获取焦点窗口,最后检查哪个进程和应用程序显示它。如果有一些:getWindowWithFocus(),那就太棒了。

要求:

  • 该程序是用 C++ 实现的,但如果需要的话可以与 Objective-C 接口。
  • 程序将以 root 权限运行。
  • 列出的窗口列表必须包括所有用户应用程序。
  • 返回的窗口允许获取属性,例如它正在处理以及是否具有 UI 焦点。
  • 理想情况下,不使用第三方工具,仅使用标准库(STL、Unix API 和 macOS API,最终使用 Qt/Boost)。
  • 必须支持 HSierra 到 Big-Sur。

我设法列出了所有窗口,但现在我正在努力检测窗口是否具有焦点。

问题:

  • 哪个API函数可以用来检查窗口是否有焦点?有样品吗?
  • 有更好的方法解决这个问题吗?

之前的研究:

我创建了一个 POC/示例,其中列出了所有窗口,包括其中一些属性。

CGWindowListCopyWindowInfo

https://developer.apple.com/documentation/coregraphics/1455137-cgwindowlistcopywindowinfo?language=objc

免责声明:这是一个 POC,仅用于演示,并错过了正确项目所需的代码质量。例如,CFObjects 没有被释放,从而导致内存泄漏。

#include <CoreFoundation/CoreFoundation.h>
#include <CoreGraphics/CGWindow.h> // CoreGraphics 
#include <iostream>

int main()
{
    CFArrayRef ref = CGWindowListCopyWindowInfo(kCGNullWindowID, 0);
    
    CFIndex nameCount = CFArrayGetCount( ref );
    
    std::cout << "NumCounts: " << nameCount << " windows" << std::endl;
    
    for( int i = 0; i < nameCount ; ++i  )
    {
        std::cerr << " -------- " << std::endl;
        CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex( ref, i );
        
        auto printKeys = [](const void* key, const void* value, void* context) 
        {
            CFShow(key);
            std::cerr << "    ";
            CFShow(value);
        };
        
        CFDictionaryApplyFunction(dict, printKeys, nullptr);

        // Process PID can be extracted with key:kCGWindowOwnerPID
        // DOES THIS WINDOW HAS FOCUS?
    }
}
c++ macos focus
2个回答
3
投票

这是一个示例,基于 这个解决方案,用 C++ 封装(嗯,实际上主要是 C)。

唯一发现的问题是,它必须在主线程中运行,这很不方便,但这是另一个话题了。

main.cpp:

#include "focus_oc_wrapper.hpp"
#include <thread>
        
int main(int argc, const char * argv[])
{
    FocusDetector::AppFocus focus;
    focus.run();

    //std::thread threadListener(&FocusDetector::AppFocus::run, &focus); //Does not works
    //if (threadListener.joinable())
    //{
    //  threadListener.join();
    //}
}

focus_oc_wrapper.hpp

namespace FocusDetector
{
    struct AppFocusImpl;
    struct AppFocus
    {
        AppFocusImpl* impl=nullptr;
        AppFocus() noexcept;
        ~AppFocus();
        void run();
    };
}

focus_oc_wrapper.mm

#include "focus_oc_wrapper.hpp"

#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import "focus_oc.h"

namespace FocusDetector
{

struct AppFocusImpl
{
    OCAppFocus* wrapped=nullptr;
};

AppFocus::AppFocus() noexcept: impl(new AppFocusImpl)
{
    impl->wrapped = [[OCAppFocus alloc] init];
}

AppFocus::~AppFocus()
{
    if (impl)
    {
        [impl->wrapped release];
    }
    delete impl;
}

void AppFocus::run()
{
    [NSApplication sharedApplication];
    [NSApp setDelegate:impl->wrapped];
    [NSApp run];
}

}

focus_oc.h

#import <Foundation/Foundation.h>

@interface OCAppFocus : NSObject <NSApplicationDelegate> 
{
    NSRunningApplication    *currentApp;
}
@property (retain) NSRunningApplication *currentApp;
@end

@implementation OCAppFocus 
@synthesize currentApp;

- (id)init 
{
    if ((self = [super init])) 
    {
        [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
                      selector:@selector(activeAppDidChange:)
               name:NSWorkspaceDidActivateApplicationNotification object:nil];
    }
    return self;
}
- (void)dealloc 
{
    [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self];
    [super dealloc];
}
- (void)activeAppDidChange:(NSNotification *)notification 
{
    self.currentApp = [[notification userInfo] objectForKey:NSWorkspaceApplicationKey];
    
    NSLog(@"App:      %@", [currentApp localizedName]);
    NSLog(@"Bundle:   %@", [currentApp bundleIdentifier]);
    NSLog(@"Exec Url: %@", [currentApp executableURL]);
    NSLog(@"PID:      %d", [currentApp processIdentifier]);
}
@end

CMakeLists.txt

cmake_minimum_required(VERSION 3.0)

set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13" CACHE STRING "Minimum OS X deployment version")

project("focus_detection")
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework CoreFoundation -framework AppKit")
set ( TESTCPP main.cpp focus_oc_wrapper.mm )

add_executable( ${PROJECT_NAME} ${TESTCPP} ) 

0
投票

我最近需要自己执行此操作,但没有接管主线程的问题。解决方案最终非常简单:

FocusedApplicationFinder.hpp

#pragma once

#include <string>

struct ApplicationInfo {
    std::string name;
    std::string exec;
    int pid = 0; 
};

// These namespaces or return types aren't necessary,
// just do whatever works for your application, this
// is just an example.
namespace osx {
::ApplicationInfo get_current_application();
}

FocusWrapper.mm

#include "FocusedApplicationFinder.hpp"

#import <AppKit/AppKit.h>
#import <AppKit/NSWorkspace.h>
#import <Foundation/Foundation.h>

ApplicationInfo osx::get_current_application()
{
    ApplicationInfo app{};

    NSRunningApplication* focusedApp;
    focusedApp = [[NSWorkspace sharedWorkspace] frontmostApplication];

    // Example information that you can extract.
    app.pid  = [focusedApp processIdentifier];
    app.name = std::string([[focusedApp localizedName] UTF8String]);
    app.exec = std::string([[[focusedApp executableURL] lastPathComponent] UTF8String]);

    return app;
}

如果您不熟悉 Objective-C 集成,正如 Adrian 之前的答案所建议的,您所需要做的就是

ADD_EXECUTABLE
.mm
文件,这是 Objective-C 和 C++ 集成的文件类型。或多或少,这一切都有效。

我添加了一些额外的代码来给出示例用例。这里唯一真正重要的是,您可以简单地调用

[[NSWorkspace sharedWorkspace] frontmostApplication]
来获取当前最前面/焦点应用程序的
NSRunningApplication*

© www.soinside.com 2019 - 2024. All rights reserved.