objective-c - 将非拥有的窗口设置为始终在顶部 - 就像应用程序 "Afloat"

标签 objective-c macos cocoa macos-carbon core-foundation

我已经设置了一个全局热键 RegisterEventHotkey .当用户按下它时,它会使用 CGWindowListCopyWindowInfo 获取当前聚焦的窗口,然后我需要将它始终设置在顶部。

如果当前窗口在我的进程中(我正在执行代码),我可以简单地转换 windowNumber来自 CGWindowListCopyWindowInfoNSWindow并做 setLevel :

nswin = [NSApp windowWithWindowNumber:windowNumber]
[nswin setLevel: Int(CGWindowLevelForKey(kCGFloatingWindowLevelKey))]

我的问题 如果当前聚焦的窗口不在我的进程中,我将无法执行此操作。你能告诉我怎么做吗?

我试过的东西 :
  • 这个名为“Afloat”的应用程序使用“SIMBL”来实现这一点。在任何窗口中,您都可以按 Cmd + A,它将始终设置在顶部。但是,我正在尝试从我的普通桌面应用程序中使用 C/ObjC,而不使用像 SIMBL 这样的帮助程序。
  • 来源:Force keeping app window on top - Mac OS X
  • SIMBL:http://www.culater.net/software/SIMBL/SIMBL.php
  • 漂浮:https://www.macupdate.com/app/mac/22237/afloat
  • 我遇到了CGSSetWindowLevelCGPrivate.h - 未记录的东西 - https://gist.github.com/Noitidart/3664c5c2059c9aa6779f#file-cgsprivate-h-L63 - 但是我记得我过去尝试过类似的事情,但是当我尝试连接到不在调用过程中的窗口时会出错。
  • 它在这里说 - https://github.com/lipidity/CLIMac/blob/114dfee39d24809f62ccb000ea22dfda15c11ce8/src/CGS/CGSInternal/.svn/text-base/CGSConnection.h.svn-base#L82

    Only the owner of a window can manipulate it. So, Apple has the concept of a universal owner that owns all windows and can manipulate them all. There can only be one universal owner at a time (the Dock).

  • 也许,无论如何要假装我的调用过程暂时成为码头?也许 CGSGetConnectionIDForPSN用于扩展坞然后使用该连接?

  • 我的用途:我试图复制我的开源、免费、浏览器插件的功能 - https://addons.mozilla.org/en-US/firefox/addon/topick/ - 所以我的调用过程如果是 Firefox。它现在适用于 Windows 和 Linux,只需要弄清楚如何在 mac 中为非 Firefox 窗口执行此操作。

    最佳答案

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

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

    无论如何,代码是...

    cocoa .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];
    }
    

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

    子类.h
    #import <Cocoa/Cocoa.h>
    
    bool cocoa_wid_exists(CGWindowID wid);
    
    @interface NSWindow(subclass)
    
    - (void)setChildWindowWithNumber:(CGWindowID)wid;
    - (void)windowDidBecomeKey:(NSNotification *)notification;
    
    @end
    

    我加倍努力并添加了一些功能来帮助您检索适当的 CGWindowID基于最前面 CGWindowID ,如果您知道正确的 CGWindowID事先,通过 AppleScript,或者你喜欢,你可以使用 cocoa_wid_to_top(wid) 把它放在前面。 ,(如果用户允许),但是这对于同时拥有多个可见窗口的进程来说效果不佳,因为它带来了与给定 CGWindowID 关联的进程 ID 所拥有的所有窗口。到顶部,所以您可能没有 CGWindowID你一定想在窗口堆栈的绝对顶部。您可能希望将窗口置于堆栈顶部的原因是,在某些情况下,您可能希望创建一个子窗口,但它出现在父窗口下方的屏幕上,从而强制您可以在窗口的父/子关系有效发生之前单击它。

    下面的文档...
    NSWindow *cocoa_window_from_wid(CGWindowID wid);返回 NSWindow *来自给定的 CGWindowID ,提供 CGWindowID属于当前应用,否则无效CGWindowID返回,可以用常量 kCGNullWindowID 表示.
    CGWindowID cocoa_wid_from_window(NSWindow *window);返回 CGWindowID来自给定的 NSWindow * ,提供 NSWindow *属于当前应用程序,否则我相信你会得到段错误。当您知道 NSWindow * 的值时,这就是我的测试中发生的情况。并尝试在不属于它的应用程序中使用它,所以不要尝试。
    bool cocoa_wid_exists(CGWindowID wid);返回 true如果基于指定的窗口CGWindowID存在,不包括您的屏幕保护程序和桌面元素,false如果没有。
    pid_t cocoa_pid_from_wid(CGWindowID wid); cocoa_wid_to_top(wid) 的辅助函数返回与给定 pid_t 关联的进程 ID(或 CGWindowID) .
    unsigned long cocoa_get_wid_or_pid(bool wid);返回最前面 CGWindowID如果 widtrue ,否则最前面的进程 ID(或 pid_t)是结果。注意返回类型 unsigned long可以安全地转换到 CGWindowIDpid_t如所须。
    void cocoa_wid_to_top(CGWindowID wid);尝试将属于进程 ID(或 pid_t)的所有窗口与给定的 CGWindowID 关联。成为最顶级的应用程序。

    现在是最重要的功能......
    void cocoa_wid_set_pwid(CGWindowID wid, CGWindowID pwid);基于指定的 CGWindowID 分配父窗口到与正确关联的给定子窗口 CGWindowID .父窗口 ID(或 pwid)必须由当前应用程序拥有,而子窗口 ID(或 wid)可以属于任何应用程序,不包括屏幕保护程序和桌面元素。如果父窗口或子窗口不存在,则失去父子关系以避免回收CGWindowID来自继承关系。如果 parent 或 child 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);
    

    关于objective-c - 将非拥有的窗口设置为始终在顶部 - 就像应用程序 "Afloat",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36075213/

    相关文章:

    macos - Mac OSX 10.6 编译器 : a puzzling experience with 32 vs 64 bit

    c++ - 如何使用 Clang 在 macOS 上链接核心框架?

    c++ - mac os X 上的 qmake 子目录模板

    ios - 如何在 IBAction 中使用 Core Data 插入数据

    iphone - 两个应用程序之间如何通信

    ios - 如何禁用或隐藏数字键盘类型的本地化?

    ios - iOS 按文本宽度对 UITableView 进行排序

    cocoa 的 NSDictionary : why are keys copied?

    objective-c - 多 View 应用程序的问题

    objective-c - ( 符合协议(protocol) : && RespondsToSelector: ) vs just ( respondsToSelector: )