设置非自有窗口始终位于顶部 - 就像应用程序"Afloat"

Noi*_*art 12 macos cocoa macos-carbon objective-c core-foundation

我已经建立了一个全球热键RegisterEventHotkey.当用户按下它时,它会获得当前聚焦的窗口CGWindowListCopyWindowInfo,然后我需要将它始终置于顶部.

如果当前窗口在我的进程中(我从中执行代码)我可以简单地将windowNumberfrom 转换CGWindowListCopyWindowInfo为a NSWindow并执行setLevel:

nswin = [NSApp windowWithWindowNumber:windowNumber]
[nswin setLevel: Int(CGWindowLevelForKey(kCGFloatingWindowLevelKey))]
Run Code Online (Sandbox Code Playgroud)

我的问题如果当前关注的窗口不在我的进程中,我无法做到这一点.你能告诉我怎么样吗?

我试过的东西:

我的用法:我正在尝试复制我的开源,免费,浏览器插件的功能 - https://addons.mozilla.org/en-US/firefox/addon/topick/ - 所以我的调用过程如果Firefox.它现在适用于Windows和Linux,只需要弄清楚如何在Mac中为非Firefox窗口做这件事.

小智 1

看来您想让外部进程的窗口保持在所有其他应用程序之上,而我在这里提供的代码并不能完全满足您的需求,它至少有些相似,并且可能足以满足您的需求,取决于您的用例。在此示例中,我演示了如何将 a 保持CGWindowID在特定的NSWindow *. 注意 -NSWindow *是父窗口,它需要由您的应用程序拥有,但CGWindowID用于子窗口的 可以属于任何应用程序)。如果您希望NSWindow *成为子窗口,请将NSWindowBelow选项更改为NSWindowAbove

这个解决方案有一个小问题,那就是当父窗口试图获得焦点但随后立即失去焦点时,这里和那里会出现一些轻微的闪烁 - 闪烁发生得非常快且间歇性,如果你超级强大,也许可以忽略它绝望的。

无论如何,代码是...

可可.mm

#import "subclass.h"
#import <Cocoa/Cocoa.h>
#import <sys/types.h>

NSWindow *cocoa_window_from_wid(CGWindowID wid) {
  return [NSApp windowWithWindowNumber:wid];
}

CGWindowID cocoa_wid_from_window(NSWindow *window) {
  return [window windowNumber];
}

bool cocoa_wid_exists(CGWindowID wid) {
  bool result = false;
  const CGWindowLevel kScreensaverWindowLevel = CGWindowLevelForKey(kCGScreenSaverWindowLevelKey);
  CFArrayRef windowArray = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, 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) {
          result = true;
          break;
        }
      }
    }
  }
  CFRelease(windowArray);
  return result;
}

pid_t cocoa_pid_from_wid(CGWindowID wid) {
  pid_t pid;
  const CGWindowLevel kScreensaverWindowLevel = CGWindowLevelForKey(kCGScreenSaverWindowLevelKey);
  CFArrayRef windowArray = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, 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) {
          pid = ownerPID.integerValue;
          break;
        }
      }
    }
  }
  CFRelease(windowArray);
  return pid;
}

unsigned long cocoa_get_wid_or_pid(bool wid) {
  unsigned long result;
  const CGWindowLevel kScreensaverWindowLevel = CGWindowLevelForKey(kCGScreenSaverWindowLevelKey);
  CFArrayRef windowArray = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, 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 == 0) {
        NSNumber *windowID = windowInfoDictionary[(id)kCGWindowNumber];
        result = wid ? windowID.integerValue : ownerPID.integerValue;
        break;
      }
    }
  }
  CFRelease(windowArray);
  return result;
}

void cocoa_wid_to_top(CGWindowID wid) {
  CFIndex appCount = [[[NSWorkspace sharedWorkspace] runningApplications] count];
  for (CFIndex i = 0; i < appCount; i++) {
    NSWorkspace *sharedWS = [NSWorkspace sharedWorkspace];
    NSArray *runningApps = [sharedWS runningApplications];
    NSRunningApplication *currentApp = [runningApps objectAtIndex:i];
    if (cocoa_pid_from_wid(wid) == [currentApp processIdentifier]) {
      NSRunningApplication *appWithPID = currentApp;
      NSUInteger options = NSApplicationActivateAllWindows;
      options |= NSApplicationActivateIgnoringOtherApps;
      [appWithPID activateWithOptions:options];
      break;
    }
  }
}

void cocoa_wid_set_pwid(CGWindowID wid, CGWindowID pwid) {
  [cocoa_window_from_wid(pwid) setChildWindowWithNumber:wid];
}
Run Code Online (Sandbox Code Playgroud)

子类.mm

#import "subclass.h"
#import <Cocoa/Cocoa.h>

CGWindowID cocoa_wid = kCGNullWindowID;
CGWindowID cocoa_pwid = kCGNullWindowID;

@implementation NSWindow(subclass)

- (void)setChildWindowWithNumber:(CGWindowID)wid {
  [[NSNotificationCenter defaultCenter] addObserver:self
    selector:@selector(windowDidBecomeKey:)
    name:NSWindowDidUpdateNotification object:self];
  cocoa_pwid = [self windowNumber]; cocoa_wid = wid;
  [self orderWindow:NSWindowBelow relativeTo:wid];
}

- (void)windowDidBecomeKey:(NSNotification *)notification {
  if (cocoa_wid_exists(cocoa_wid)) {
    [self setCanHide:NO];
    [self orderWindow:NSWindowBelow relativeTo:cocoa_wid];
  } else {
    cocoa_wid = kCGNullWindowID;
    [self setCanHide:YES];
  }
}

@end
Run Code Online (Sandbox Code Playgroud)

子类.h

#import <Cocoa/Cocoa.h>

bool cocoa_wid_exists(CGWindowID wid);

@interface NSWindow(subclass)

- (void)setChildWindowWithNumber:(CGWindowID)wid;
- (void)windowDidBecomeKey:(NSNotification *)notification;

@end
Run Code Online (Sandbox Code Playgroud)

我付出了额外的努力,添加了一些函数来帮助您CGWindowID根据最前面的检索适当的内容CGWindowID,如果您事先通过 AppleScript 知道正确的内容CGWindowID,或者您​​愿意,您可以使用 将其带到前面cocoa_wid_to_top(wid),(如果用户允许),但是这对于同时拥有多个可见窗口的进程来说效果不佳,因为它将与给定关联的进程 id 拥有的所有窗口带到顶部CGWindowID,因此您可能没有您CGWindowID想要的绝对顶部窗口堆栈是必然的。您可能希望将窗口置于堆栈顶部的原因是,在某些情况下,可能会打开一个窗口,您希望创建一个子窗口,但它出现在父窗口下方的屏幕上,从而强制您必须单击它才能有效地建立窗口的父/子关系。

文档如下...

NSWindow *cocoa_window_from_wid(CGWindowID wid);NSWindow *从给定的 中返回一个CGWindowID,前提是该CGWindowID属于当前应用程序,否则CGWindowID返回一个无效值,可以用常量 来表示kCGNullWindowID

CGWindowID cocoa_wid_from_window(NSWindow *window);CGWindowID从给定的返回 a NSWindow *,前提是该NSWindow *属于当前应用程序,否则我相信您会遇到段错误。这就是我的测试中发生的情况,当您知道 an 的值NSWindow *并尝试在它不属于的应用程序中使用它时,所以不要尝试。

bool cocoa_wid_exists(CGWindowID wid);返回true基于指定的窗口是否存在,如果不存在CGWindowID,则不包括屏幕保护程序和桌面元素。false

pid_t cocoa_pid_from_wid(CGWindowID wid);返回与给定 关联的cocoa_wid_to_top(wid)进程 ID(或)的辅助函数。pid_tCGWindowID

unsigned long cocoa_get_wid_or_pid(bool wid);CGWindowID如果wid是则返回最前面true,否则最前面的进程ID(或pid_t)是结果。请注意,返回类型可以根据需要unsigned long安全地与CGWindowIDor进行转换。pid_t

void cocoa_wid_to_top(CGWindowID wid);pid_t尝试将属于与给定关联的进程 ID(或 )的所有窗口设置CGWindowID为最顶层应用程序。

现在最重要的功能...

void cocoa_wid_set_pwid(CGWindowID wid, CGWindowID pwid);根据指定的父窗口分配CGWindowID给与正确的关联的给定子窗口CGWindowID。父窗口 id(或pwid)必须属于当前应用程序,而子窗口 id(或wid)可以属于任何应用程序,不包括屏幕保护程序和桌面元素。如果父窗口或子窗口不再存在,它们将失去父子关系,以避免CGWindowID被回收的继承关系。如果父项或子项CGWindowID不存在,它们将被设置为kCGNullWindowID,这会可靠地结束关系。

请注意,此代码已在 Catalina 中进行了测试,并且确实按照撰写本文时所宣传的那样工作。

要使用我在 C 或 C++ 代码中提供的 cocoa 函数,您可以在标头中执行此操作:

typedef void NSWindow;
typedef unsigned long CGWindowID;

extern "C" NSWindow *cocoa_window_from_wid(CGWindowID wid);
extern "C" CGWindowID cocoa_wid_from_window(NSWindow *window);
extern "C" bool cocoa_wid_exists(CGWindowID wid);
extern "C" pid_t cocoa_pid_from_wid(CGWindowID wid);
extern "C" unsigned long cocoa_get_wid_or_pid(bool wid);
extern "C" void cocoa_wid_to_top(CGWindowID wid);
extern "C" void cocoa_wid_set_pwid(CGWindowID wid, CGWindowID pwid);
Run Code Online (Sandbox Code Playgroud)