Activate a window using its Window ID

被刻印的时光 ゝ 提交于 2019-12-12 08:20:02

问题


How would I programmatically activate i.e move-to-front-and-focus a window on macOS (not belonging to my app) given its 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.

Am using Swift currently, but am open to using Objective-C, AppleScript or whatever.

EDIT:

I don't want to bring to front all windows of the parent app - only the specific that matches the window 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? 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). Is there some class like NSOpenWindow or CGWindow buried somewhere?


回答1:


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

On my mac OpenOffice was started with a window with kCGWindowNumber = 599, so this has the same effect: 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)




回答2:


For anyone looking for an Objective C solution:

#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;
}

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. 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.



来源:https://stackoverflow.com/questions/47152551/activate-a-window-using-its-window-id

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!