简体   繁体   English

使用窗口 ID 激活窗口

[英]Activate a window using its Window ID

How would I programmatically activate ie move-to-front-and-focus a window on macOS (not belonging to my app) given its Window ID .鉴于其Window ID ,我将如何以编程方式激活 macOS (不属于我的应用程序)上的Window ID My app would run with user granted Accessibility permissions etc.我的应用程序将在用户授予辅助功能等权限的情况下运行。

Surprisingly, none of the functions described on the Quartz Window Services page seem to do that.令人惊讶的是, Quartz Window Services 页面上描述的功能似乎都没有做到这一点。

Am using Swift currently, but am open to using Objective-C, AppleScript or whatever.我目前使用 Swift,但我愿意使用 Objective-C、AppleScript 或其他任何东西。

EDIT:编辑:

I don't want to bring to front all windows of the parent app - only the specific that matches the window ID.我不想将父应用程序的所有窗口都放在前面 - 只有与窗口 ID 匹配的特定窗口。

Edit:编辑:

I know that the NSWindow type is only meant to refer to windows of the current process, but is there no class that represents windows owned by external apps?我知道NSWindow类型仅用于引用当前进程的窗口,但是是否没有表示外部应用程序拥有的窗口的类? Like we have NSRunningApplication to refer to any running app including external ones, I was expecting an API to deal all open windows (assuming the right permissions).就像我们有NSRunningApplication来引用任何正在运行的应用程序,包括外部应用程序,我期待一个 API 来处理所有打开的窗口(假设有正确的权限)。 Is there some class like NSOpenWindow or CGWindow buried somewhere?有没有像NSOpenWindowCGWindow这样的类埋在某个地方?

I didn't find a way to switch to a specific window yet, but you can switch to the app that contains a specific window using this function:我还没有找到切换到特定窗口的方法,但您可以使用此功能切换到包含特定窗口的应用程序:

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)
    }
}

It is probably usefull to switch by name as well:按名称切换也可能很有用:

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)
    }
}

Example: switchToApp(named: "OpenOffice")示例: switchToApp(named: "OpenOffice")

On my mac OpenOffice was started with a window with kCGWindowNumber = 599 , so this has the same effect: switchToApp(withWindow: 599)在我的 Mac 上,OpenOffice 以kCGWindowNumber = 599的窗口启动,因此具有相同的效果: switchToApp(withWindow: 599)

As far as I found out so far, your options seem to be to show the currently active window of the app, or to show all windows (using .activateAllWindows as activation option)据我目前发现,您的选择似乎是显示应用程序的当前活动窗口,或显示所有窗口(使用.activateAllWindows作为激活选项)

For anyone looking for an Objective C solution:对于任何寻找 Objective C 解决方案的人:

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

bool activate_window_of_id(unsigned 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];
              unsigned 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:@"\" 2>&1"] 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;
}

Note, unlike Daniel's answer, this will not just bring the specified application's windows to the front, it will also make sure the specific window whose id matches the one specified will be the topmost out of that app's collection of windows.请注意,与 Daniel 的回答不同,这不仅会将指定应用程序的窗口置于最前面,还会确保 id 与指定窗口匹配的特定窗口将成为该应用程序窗口集合中的最顶层。 It will return true on success, and false on failure.成功时返回真,失败时返回假。 I noticed it brings to front for some apps but not for others.我注意到它为某些应用程序带来了前沿,但对于其他应用程序却没有。 I'm not sure why.我不知道为什么。 The code it is based on does not work as advertised for its original purpose.它所基于的代码并不像其原始目的所宣传的那样工作。 Although, it did help me a lot to get working all the stuff I needed to answer this question.尽管如此,它确实帮助我解决了回答这个问题所需的所有内容。 The code my answer is based on can be found here . 可以在此处找到我的答案所基于的代码 Ignore the original usage.忽略原来的用法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM