Активируйте окно, используя его идентификатор окна

Как бы я программным образом активировал i.e перемещение вперед-назад и фокусировку окна на macOS (не принадлежащего моему приложению), учитывая его Window ID. Мое приложение будет запускаться с предоставленными пользователем разрешениями на доступ и т.д.

Удивительно, но ни одна из функций, описанных на странице Quartz Window Services, не делает этого.

В настоящее время используется Swift, но я открыт для использования Objective-C, AppleScript или любого другого.

EDIT:

Я не хочу выводить на передний план все окна родительского приложения - только те, которые соответствуют идентификатору окна.

Edit:

Я знаю, что тип NSWindow предназначен только для ссылок на окна текущего процесса, но нет ли класса, представляющего окна, принадлежащие внешним приложениям? Подобно тому, как NSRunningApplication ссылается на любое запущенное приложение, включая внешние, я ожидал, что API обработает все открытые окна (при условии правильных разрешений). Есть ли какой-нибудь класс, подобный NSOpenWindow или CGWindow где-то похож?

Ответы

Ответ 1

Я еще не нашел способ перейти к определенному окну, но вы можете переключиться на приложение, содержащее определенное окно, используя эту функцию:

func switchToApp(withWindow windowNumber: Int32) {
    let options = CGWindowListOption(arrayLiteral: CGWindowListOption.excludeDesktopElements, CGWindowListOption.optionOnScreenOnly)
    let windowListInfo = CGWindowListCopyWindowInfo(options, CGWindowID(0))
    guard let infoList = windowListInfo as NSArray? as? [[String: AnyObject]] else { return }
    if let window = infoList.first(where: { ($0["kCGWindowNumber"] as? Int32) == windowNumber}), let pid = window["kCGWindowOwnerPID"] as? Int32 {
        let app = NSRunningApplication(processIdentifier: pid)
        app?.activate(options: .activateIgnoringOtherApps)
    }
}

Вероятно, полезно также переключать по имени:

func switchToApp(named windowOwnerName: String) {
    let options = CGWindowListOption(arrayLiteral: CGWindowListOption.excludeDesktopElements, CGWindowListOption.optionOnScreenOnly)
    let windowListInfo = CGWindowListCopyWindowInfo(options, CGWindowID(0))
    guard let infoList = windowListInfo as NSArray? as? [[String: AnyObject]] else { return }

    if let window = infoList.first(where: { ($0["kCGWindowOwnerName"] as? String) == windowOwnerName}), let pid = window["kCGWindowOwnerPID"] as? Int32 {
        let app = NSRunningApplication(processIdentifier: pid)
        app?.activate(options: .activateIgnoringOtherApps)
    }
}

Пример: switchToApp(named: "OpenOffice")

В моем mac OpenOffice было запущено окно с kCGWindowNumber = 599, поэтому это имеет тот же эффект: switchToApp(withWindow: 599)

Насколько я понял до сих пор, ваши параметры, похоже, показывают текущее активное окно приложения или показать все окна (используя .activateAllWindows в качестве опции активации)

Ответ 2

Для тех, кто ищет решение Objective C:

#import <Cocoa/Cocoa.h>
#import <libproc.h>
#import <string.h>
#import <stdlib.h>
#import <stdio.h>

bool activate_window_of_id(long wid) {
  bool success = false;
  const CGWindowLevel kScreensaverWindowLevel = CGWindowLevelForKey(kCGScreenSaverWindowLevelKey);
  CFArrayRef windowArray = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID);
  CFIndex windowCount = 0;
  if ((windowCount = CFArrayGetCount(windowArray))) {
    for (CFIndex i = 0; i < windowCount; i++) {
      NSDictionary *windowInfoDictionary = (__bridge NSDictionary *)((CFDictionaryRef)CFArrayGetValueAtIndex(windowArray, i));
      NSNumber *ownerPID = (NSNumber *)(windowInfoDictionary[(id)kCGWindowOwnerPID]);
      NSNumber *level = (NSNumber *)(windowInfoDictionary[(id)kCGWindowLayer]);
      if (level.integerValue < kScreensaverWindowLevel) {
        NSNumber *windowID = windowInfoDictionary[(id)kCGWindowNumber];
        if (wid == windowID.integerValue) {
          CFIndex appCount = [[[NSWorkspace sharedWorkspace] runningApplications] count];
          for (CFIndex j = 0; j < appCount; j++) {
            if (ownerPID.integerValue == [[[[NSWorkspace sharedWorkspace] runningApplications] objectAtIndex:j] processIdentifier]) {
              NSRunningApplication *appWithPID = [[[NSWorkspace sharedWorkspace] runningApplications] objectAtIndex:j];
              [appWithPID activateWithOptions:NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps];
              char buf[PROC_PIDPATHINFO_MAXSIZE];
              proc_pidpath(ownerPID.integerValue, buf, sizeof(buf));
              NSString *buffer = [NSString stringWithUTF8String:buf];
              long location = [buffer rangeOfString:@".app/Contents/MacOS/" options:NSBackwardsSearch].location;
              NSString *path = (location != NSNotFound) ? [buffer substringWithRange:NSMakeRange(0, location)] : buffer;
              NSString *app = [@" of application \\\"" stringByAppendingString:[path lastPathComponent]];
              NSString *index = [@"set index of window id " stringByAppendingString:[windowID stringValue]];
              NSString *execScript = [[index stringByAppendingString:app] stringByAppendingString:@"\\\" to 1"];
              char *pointer = NULL;
              size_t buffer_size = 0;
              NSMutableArray *array = [[NSMutableArray alloc] init];
              FILE *file = popen([[[@"osascript -e \"" stringByAppendingString:execScript] stringByAppendingString:@"\""] UTF8String], "r");
              while (getline(&pointer, &buffer_size, file) != -1)
                [array addObject:[NSString stringWithUTF8String:pointer]];
              char *error = (char *)[[array componentsJoinedByString:@""] UTF8String];
              if (strlen(error) > 0 && error[strlen(error) - 1] == '\n')
                error[strlen(error) - 1] = '\0';
              if ([[NSString stringWithUTF8String:error] isEqualToString:@""])
                success = true;
              [array release];
              free(pointer);
              pclose(file);
              break;
            }
          }
        }
      }
    }
  }
  CFRelease(windowArray);
  return success;
}

Обратите внимание, что в отличие от ответа Даниэля, это не только выведет указанные окна приложений на передний план, но также будет гарантировать, что конкретное окно, идентификатор которого совпадает с указанным, будет самым верхним из этой коллекции окон приложения. Он вернет истину в случае успеха и ложь в случае неудачи. Я заметил, что это приводит к выводу на первый план для некоторых приложений, но не для других. Я не уверен почему. Код, на котором он основан, не работает так, как рекламируется для его первоначальной цели. Хотя это очень помогло мне разобраться со всем, что мне нужно, чтобы ответить на этот вопрос. Код, на котором основан мой ответ, можно найти здесь. Проигнорируйте оригинальное использование.