使用核心图形技术强制窗口重新绘制自身?

6
我开发了注入系统,并连接了一些石英API,以在Mac OS X上创建一些漂亮的窗口效果。例如,当用户在窗口中设置颜色为红色时,它是红色的光泽。
但是,当我注入到已经运行的应用程序中时,由于窗口已经被绘制,我无法给它想要的效果。因此,我正在寻找石英/核心图形中的一些技术,可以让我重新绘制整个窗口或者一些可以让我发送一些事件/调用某些函数来使系统重新绘制整个窗口的技巧。
我的意思是,窗口上的所有东西都需要重新绘制,这样我的钩子API才能按顺序执行,以创建正确的效果、阴影和颜色。这里窗口创建和绘制的顺序很重要。
我使用类似于inject&interpose的技术,注入代码是C/C++代码。
有人知道我该如何实现吗?

2
在Mac上是否有类似invalidateRect的函数可以强制窗口重绘? - RLT
3个回答

5
如果您想使用比Cocoa更低级别的API来强制窗口重新绘制自己,那么据我所知,这是不可能的。当调用窗口的内容视图的drawRect:方法时,窗口会重新绘制自己。它向窗口传递一个CGContextRef,然后该方法使用它来重新绘制窗口。CoreGraphics不负责重新绘制窗口。Cocoa使用CoreGraphics重新绘制窗口。
在drawRect:之外获取窗口的图形上下文并随时进行绘制是可能的(例如,请参见here),但听起来你真正想做的是拦截窗口普通绘图例程的结果,并在其上添加一些自己的东西。您可以通过切换窗口内容视图的类并覆盖drawRect来实现此操作。处理注入的辅助函数将如下所示:
typedef void (^InjectedBlock)(CGContextRef, CGRect);

void InjectIntoView(NSView* view, InjectedBlock aBlock)
{
    Class viewClass = [view class];
    InjectedBlock injectedBlock = [aBlock copy];

    void(^drawRect)(id, SEL, NSRect) = ^(id self, SEL _cmd, NSRect rect)
    {
        struct objc_super superId = { self, viewClass };
        objc_msgSendSuper(superId, @selector(drawRect:), rect);

        injectedBlock([[NSGraphicsContext currentContext] graphicsPort], CGRectFromNSRect(rect));
    };

    NSString* subclassName = [NSString stringWithFormat:"%s_injected", class_getName(viewClass)]
    Class subclass objc_allocateClassPair(viewClass, [subclassName UTF8String], 0);
    objc_registerClassPair(subclass);

    Method overriddenMethod = class_getInstanceMethod([NSView class], @selector(drawRect:));
    IMP imp = imp_implementationWithBlock(drawRect);

    class_addMethod(subclass, @selector(drawRect:), imp, method_getTypeEncoding(overriddenMethod))
}

编辑:

啊,你对整个窗口感兴趣。框架等也是NSView实例,但它们是您无法直接访问的私有子类。您可以通过在窗口上调用display来强制它们重新绘制,但这可能会覆盖您对窗口所做的任何操作,因为它将使用这些类的现有绘图例程。

因此,您可能还想考虑交换这些视图的drawRect:方法(在drawRect:中调用[[NSGraphicsContext currentContext] graphicsPort]将为您提供一个CGContextRef,您可以使用Quartz API与之一起使用)。您可以通过在窗口的内容视图上调用superview来获取框架视图。

请注意,窗口框架视图的排列未经记录,可能会随着系统更新而更改。

无论如何,听起来是一个有趣的项目!


我理解了你的回答的第一部分,但是第二部分我不太明白。所以,CoreGraphics不能绘制到窗口,而cocoa可以。好吧!但是在第二部分中,你展示了交换方法(我之前不知道这个技巧,谢谢)。然而,更合适的解决方案可能是将drawRect:]指令发送给窗口?我正在使用类似于“https://github.com/comex/inject_and_interpose/” 的技术。所以这是C/C++代码。你能否提供一些相关的建议? - RLT
我觉得我可能误解了你在做什么...也许你可以在问题中多说一点关于你注入的应用程序窗口所做的事情? - Chris Devereux
我将尝试使用swilling drawrect来管理Cocoa应用程序,但是我无法使用这些技术来管理Dock、Finder。因此,我在Core-Graphics中寻找解决方案。 - RLT
挖掘一下。你可能会发现它们也是某种NSView子类。至少从10.6开始,Finder绝对是基于Cocoa的。如果谷歌无法帮助你找到这种东西,FScript Anywhere是一个很好的工具。 - Chris Devereux

5

-[NSView setNeedsDisplayInRect:]-[NSView setNeedsDisplay:]invalidateRect的直接等价函数。

我不知道你所说的需要在Quartz/CoreGraphics中使用是什么意思。Cocoa已经在绘图时使用它们了。

如果你想调用一些神奇的CGxxx()函数来使窗口重新绘制,这是做不到的。窗口的标题和框架由系统绘制,但是对于内容,低级别的API无法知道应该绘制什么。唯一知道如何绘制视图的人是视图本身。(也许有一些缓存在窗口后备存储器中的东西,但我不知道任何公共或未记录的API可以访问它)。

无论你找到什么,都必须要求NSWindow对象重新绘制其视图。如果你已经注入到一个进程中,可能涉及以下步骤:

  • 定位obj-c运行时(你将需要至少objc_msgSend函数)
  • 定位NSApplication类
  • 使用+[NSApplication sharedApplication]-[NSApplication windows]查找NSWindow*对象指针
  • 使用contentViewdisplay等重新绘制

抱歉,我应该添加更多细节。我想在Quartz中找到解决方案。 - RLT
@MachinTosh,我不确定我理解你的意思,但请看我的更新答案。 - hamstergene
你的回答提供了一个很好的解决方案。我一直在寻找一些神奇的CGxxx(:D),因为我正在努力达到Quartz的水平,而且我一直试图避免使用swizzling。 - RLT

3
我没有遇到过使矩形无效的情况,但由于你的问题是如何重新绘制完整的窗口,看起来似乎并不是你需要的。
当进行失效处理时,您告诉系统您的视图的一部分无效。下一次系统绘制时间可用时(通常是在声明失效后立即),它将重新绘制您失效的矩形。
setNeedsDisplay执行完全相同的操作,只是针对整个视图而不是该视图内的特定矩形。在您的问题中,这并不重要,因为您想要刷新整个窗口。它从UIView链转换到Quartz再到内部系统,因此只要通过drawRect处理,就会使用您的Quartz绘图。
所以只需调用 [yourWindow setNeedsDisplay]; 系统将知道尽快重新绘制您的窗口。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接