응용 프로그램이 macOS에 초점을 맞추고 있는지 확인하는 방법은 무엇입니까?

Nov 18 2020

어떤 애플리케이션에 초점이 맞춰져 있는지 수집해야합니다. 이를 위해 내 접근 방식은 창을 나열하고, 포커스가있는 창을 가져오고, 마지막으로 어떤 프로세스와 응용 프로그램이 표시하는지 확인하는 것입니다. getWindowWithFocus ()가 있다면 환상적 일 것입니다.

요구 사항 :

  • 이 프로그램은 C ++로 구현되지만 필요한 경우 Objective-C와 인터페이스 할 수 있습니다.
  • 프로그램은 루트 권한으로 실행됩니다.
  • 나열된 창 목록에는 모든 사용자 응용 프로그램이 포함되어야합니다 .
  • 반환 된 창은 처리 및 UI 포커스가 있는지 여부와 같은 속성을 가져올 수 있습니다.
  • 이상적으로는 타사 도구가 사용되지 않고 표준 라이브러리 (STL, Unix API 및 macOS API, 결국 Qt / Boost) 만 사용됩니다.
  • HSierra to Big-Sur를 지원해야합니다.

모든 창을 나열 할 수 있었지만 이제는 창에 포커스가 있는지 여부를 감지하는 데 어려움을 겪고 있습니다.

질문:

  • 창에 포커스가 있는지 여부를 확인하는 데 사용할 수있는 API 함수는 무엇입니까? 샘플이 있습니까?
  • 이 문제에 대한 더 나은 접근 방법이 있습니까?

이전 연구 :

일부 속성을 포함하여 모든 창을 나열하는 POC / 샘플을 만들었습니다.

CGWindowListCopyWindowInfo

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

DISCLAIM : 이것은 단지 데모를위한 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?
    }
}

답변

2 AdrianMaire Nov 23 2020 at 10:47

다음은 이 솔루션을 기반으로 하는 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} )