c++ 如何获取应用程序是否专注于macOS?

wqsoz72f  于 4个月前  发布在  Mac
关注(0)|答案(2)|浏览(70)

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

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

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

问题:

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

前期研究:

我创建了一个POC/sample,它列出了所有窗口,包括它的一些属性。

CGWindowListCopyWindowInfo

字符串
https://developer.apple.com/documentation/coregraphics/1455137-cgwindowlistcopywindowinfo?language=objc
免责声明:这是一个POC,只是为了演示,并错过了适当项目所需的代码质量。2例如,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?
    }
}

zzoitvuj

zzoitvuj1#

下面是一个例子,基于this solution,用C++(实际上主要是C) Package 。
唯一发现的问题是,它必须运行在主线程,这是不方便的,但这是另一个主题。
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} )

beq87vna

beq87vna2#

我最近需要自己做这件事,但没有接管主线程的问题。解决方案最终非常简单:
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*

相关问题