修复 QuickLook(2023)
摘要
一篇技术指南,介绍如何使用 LLDB 对 macOS Finder 进行逆向工程,以解决在升级到 macOS Ventura 后 QuickLook 预览中出现 unwanted 圆角的问题。
<p><a href="https://lobste.rs/s/symlbk/fixing_quicklook_2023">评论</a></p>
查看缓存全文
缓存时间: 2026/05/09 18:43
# 修复 QuickLook:https://foon.uk/fixing-quicklook/ ## 在从 Mojave 升级到 Ventura 后,我注意到苹果对 QuickLook 进行了改动。QuickLook 是我最喜欢的软件功能之一。你只需在 Finder 中选中一个文件,按下空格键,该文件就会弹出现在屏幕上。再按一次空格键,它又会消失。焦点始终保留在 Finder 中,因此你可以使用方向键选择其他文件,而 QuickLook 窗口会随之更新以显示它们。它简单、设计精良、快速、优雅,整体表现非常出色。直到最近为止。
不知出于何种原因,现在的 QuickLook 在显示图像前会将其四角裁切掉。无论这些图像是照片、游戏资源还是你正在设计的 UI 元素,所有图片在显示之前都会被圆角处理。
自然,我四处寻找能关闭这一功能的秘密开关。苹果通常很擅长让我们选择是否使用他们最新的改进功能,所以肯定有办法吧?是辅助功能设置吗?还是某个 `defaults.write` 命令?据我所知,并没有这样的选项。
那么,我们是不是只能认命了?不。别这么想。虽然它是 Mac,但它仍然是你的电脑。让我们来修复这个问题。
## QuickLook 头文件
如果我们想干预 QuickLook 显示内容的方式,就需要获取到它的窗口。然后我们 hopefully 可以探索其中的视图,并更改它们的绘制方式。QuickLook API 是公开且有文档记录的(https://developer.apple.com/documentation/quicklook?language=objc),查阅文档你会发现,这个窗口被称为 `QLPreviewPanel`,每个应用只有一个其实例,可以通过 `[QLPreviewPanel sharedPreviewPanel]` 获取。
## 调试 Finder
我之前一直只在 Finder 中使用 QuickLook,所以我认为应该从这里开始。让我们启动调试器看看 Finder 在做什么:
```bash
$ lldb -n Finder
error: process exited with status -1 (attach failed (Not allowed to attach to process.))
```
实际上,首先我们需要处理 SIP(系统完整性保护)。SIP 是苹果的一项新技术,通过阻止你读写自己电脑的内存来“保护”你。为了修复此类系统进程中的问题,我们必须关闭它并重启。
好吧,让我们再试一次:
```bash
$ lldb -n Finder
Process 4040 stopped
thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
frame #0: 0x000000018df1bf54 libsystem_kernel.dylib`mach_msg2_trap + 8
libsystem_kernel.dylib`mach_msg2_trap:
-> 0x18df1bf54 <+8>: ret
libsystem_kernel.dylib`macx_swapon:
0x18df1bf58 <+0>: mov x16, #-0x30
0x18df1bf5c <+4>: svc #0x80
0x18df1bf60 <+8>: ret
Target 0: (Finder) stopped.
Executable module set to "/System/Library/CoreServices/Finder.app/Contents/MacOS/Finder".
Architecture set to: arm64e-apple-macosx-.
```
我们已经进入了系统。让我们尝试在 `sharedPreviewPanel` 上设置一个断点。
```bash
(lldb) break set -n sharedPreviewPanel
Breakpoint 5: where = QuickLookUI`+[QLPreviewPanel sharedPreviewPanel], address = 0x00000001f81c025c
(lldb) c
```
导航到一个图像文件,按下空格键,然后:
```bash
Process 4040 stopped
thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 5.1
frame #0: 0x00000001f81c025c QuickLookUI`+[QLPreviewPanel sharedPreviewPanel]
QuickLookUI`+[QLPreviewPanel sharedPreviewPanel]:
-> 0x1f81c025c <+0>: pacibsp
0x1f81c0260 <+4>: sub sp, sp, #0x40
0x1f81c0264 <+8>: stp x22, x21, [sp, #0x10]
0x1f81c0268 <+12>: stp x20, x19, [sp, #0x20]
```
LLDB 在 `sharedPreviewPanel` 函数中暂停了 Finder 进程。让我们获取返回值,这应该是预览面板的实例:
```bash
(lldb) finish
(lldb) po $x0
```
太好了!我们有了进展。让我们看看这个面板里有什么:
```bash
(lldb) po [$x0 recursiveDescription]
[
A w ] h=--- v=--- NSNextStepFrame 0x12cea29b0 f=(0,0,370,223) b=(-) => <_NSViewBackingLayer: 0x600003f7fcf0>
[ A w ] h=-&- v=-&- QLPreviewBackgroundView 0x11e00df90 f=(0,0,370,223) b=(-) => <_NSViewBackingLayer: 0x600003f7fc60>
[ A w ] h=--- v=--- NSView 0x11e040ec0 f=(0,0,370,223) b=(-) => <_NSViewBackingLayer: 0x600003f7fc00>
[ A w ] h=--- v=--- NSView 0x11e047f50 f=(5,5,360,180) b=(-) => <_NSViewBackingLayer: 0x600003f7f9f0>
[ A w ] h=-&- v=-&- NSView 0x11cf35780 f=(0,0,360,180) b=(-) => <_NSViewBackingLayer: 0x600003f7f960>
[ A w ] h=-&- v=-&- NSView 0x11cf31c50 f=(0,0,360,180) b=(-) => <_NSViewBackingLayer: 0x600003f7f8d0>
[ A w ] h=-&- v=-&- NSView 0x11cf31fb0 f=(0,0,360,180) b=(-) => <_NSViewBackingLayer: 0x600003f7f840>
[ A w ] h=-&- v=-&- QLPanelPreviewView 0x11cf35d10 f=(0,0,360,180) b=(-) => <_NSViewBackingLayer: 0x600003f7f4e0>
[ A W ] h=-&- v=-&- QLPreviewContainerView 0x11cf360d0 f=(0,0,360,180) b=(-) =>
[ A W ] h=--- v=--- NSRemoteView 0x119c3a040 f=(0,0,360,180) b=(-) =>
[ A W ] h=--- v=--- QLPreviewTitleBarView 0x11e0482b0 f=(8,189,354,30) b=(-) => <_NSViewBackingLayer: 0x600003fbaf70>
[ AF w ] h=--- v=--- QLPreviewWindowButton 0x11cf35300 "Button" f=(4,9,12,12) b=(-) => <_NSViewBackingLayer: 0x600003f7fa80>
[ A V W ] h=--- v=--- NSButtonImageView 0x11cfaeca0 f=(0,0,12,12) b=(-) => <_NSViewBackingLayer: 0x600003f7f2a0>
[ AF w ] h=--- v=--- QLPreviewWindowButton 0x11cf31310 "Button" f=(24,9,12,12) b=(-) => <_NSViewBackingLayer: 0x600003f7fb40>
[ A V W ] h=--- v=--- NSButtonImageView 0x11cfaef00 f=(0,0,12,12) b=(-) => <_NSViewBackingLayer: 0x600003f7f390>
[ A w ] h=--- v=--- NSView 0x11e048d80 f=(114,4,240,22) b=(-) => <_NSViewBackingLayer: 0x600003f7a310>
[ A w ] h=--- v=--- QLControlsCenteringView 0x11cfa9960 f=(0,0,240,22) b=(-) => <_NSViewBackingLayer: 0x600003f7a280>
[ A w ] h=--- v=--- QLControlsContainerView 0x11cfa9ed0 f=(0,0,240,22) b=(-) => <_NSViewBackingLayer: 0x600003f7a220>
[ AF w ] h=--- v=--- QLControlSegmentedControl 0x11cf630a0 f=(38,-2,33,25) b=(-) => <_NSViewBackingLayer: 0x600003ced470>
[ AF V W ] h=--- v=--- NSSegmentItemView 0x11cf63560 f=(0,0,33,25) b=(-) => <_NSViewBackingLayer: 0x600003ced500>
[ A V W ] h=--- v=--- NSSegmentItemBezelView 0x11cf63840 f=(0,0,33,25) b=(-) => <_NSViewBackingLayer: 0x600003f80d80>
[ A w ] h=--- v=--- NSSegmentItemImageView 0x11cf63bb0 f=(10,1.5,15,17) b=(-) => <_NSViewBackingLayer: 0x600003ced410>
[ AF w ] h=--- v=--- QLControlSegmentedControl 0x11cf5b880 f=(-1,-2,33,25) b=(-) => <_NSViewBackingLayer: 0x600003cec690>
[ AF V W ] h=--- v=--- NSSegmentItemView 0x11cf5bd40 f=(0,0,33,25) b=(-) => <_NSViewBackingLayer: 0x600003ced530>
[ A V W ] h=--- v=--- NSSegmentItemBezelView 0x11cf5c020 f=(0,0,33,25) b=(-) => <_NSViewBackingLayer: 0x600003f80060>
[ A w ] h=--- v=--- NSSegmentItemImageView 0x11cf5c390 f=(9,4,15,15) b=(-) => <_NSViewBackingLayer: 0x600003ceccc0>
[ AF w ] h=--- v=--- QLControlSegmentedControl 0x11cf53ee0 f=(77,-2,33,25) b=(-) => <_NSViewBackingLayer: 0x600003cee370>
[ AF V W ] h=--- v=--- NSSegmentItemView 0x11cf543a0 f=(0,0,33,25) b=(-) => <_NSViewBackingLayer: 0x600003ced110>
[ A V W ] h=--- v=--- NSSegmentItemBezelView 0x11cf54680 f=(0,0,33,25) b=(-) => <_NSViewBackingLayer: 0x600003f80270>
[ A w ] h=--- v=--- NSSegmentItemImageView 0x11cf549f0 f=(9.5,2,15,17) b=(-) => <_NSViewBackingLayer: 0x600003cec9c0>
[ AF w ] h=--- v=--- QLControlSegmentedControl 0x11cf4c8c0 f=(116,-2,125,25) b=(-) => <_NSViewBackingLayer: 0x600003f784e0>
[ AF V W ] h=--- v=--- NSSegmentItemView 0x11cf4cc70 f=(0,0,125,25) b=(-) => <_NSViewBackingLayer: 0x600003f78720>
[ A V W ] h=--- v=--- NSSegmentItemBezelView 0x11cf4cf50 f=(0,0,125,25) b=(-) => <_NSViewBackingLayer: 0x600003f83000>
[ AF w ] h=--- v=--- NSSegmentItemLabelView 0x11cf4d650 "Open with Preview" f=(2,3,121,16) b=(-) =>
[ AF w ] h=--- v=--- NSTextField 0x11e04cda0 "Screenshot 2023-08-28 at 16.21.26" f=(42,8,66,16) b=(-) =>
A=autoresizesSubviews, C=canDrawConcurrently, D=needsDisplay, F=flipped, G=gstate, H=hidden (h=by ancestor), L=needsLayout (l=child needsLayout), U=needsUpdateConstraints (u=child needsUpdateConstraints), O=opaque, P=preservesContentDuringLiveResize, S=scaled/rotated, W=wantsLayer (w=ancestor wantsLayer), V=needsVibrancy (v=allowsVibrancy), #=has surface
```
这信息量很大。我们可以忽略 `QLPreviewTitleBarView` 及其子视图,所以让我们关注树状结构的另一分支。这里有一个 `QLPreviewBackgroundView`,接着是一堆 `NSView`,然后是 `QLPanelPreviewView`、`QLPreviewContainerView`,最顶部是一个 `NSRemoteView`。
## NSRemoteView
我之前从未见过 `NSRemoteView`,也不太确定它是什么。似乎没有公开的头文件供它使用。我想知道它是如何工作的?
好吧,我们可能有点跑题了。我们仍然不知道 `NSRemoteView` 是否与我们的图像有关。让我们把它隐藏起来,看看图像是否消失。
```bash
(lldb) po [(NSRemoteView *)0x119c3a040 setHidden:YES]
(lldb) c
```
确实消失了。所以,正是这个 `NSRemoteView` 在显示我们的图像。“Remote”(远程)这个词让我觉得它与另一个执行实际渲染的进程进行通信,所以我们可能需要在远程进程中查看它做了什么。但是是哪个进程呢?也许 `NSRemoteView` 可以告诉我们?让我们看看它能做什么:
```bash
(lldb) po [[NSRemoteView class] fp_shortMethodDescription]
:in NSRemoteView:
Class Methods:
+ (void) initialize; (0xd465800195a3a894)
+ (void) observeValueForKeyPath:(id)arg1 ofObject:(id)arg2 change:(id)arg3 context:(void *)arg4; (0xea63800195a944bc)
...
+ (id) _remoteViewForIdentifier:(id)arg1; (0xda2a800195a94dc4)
+ (BOOL) anyRemoteViewAttachedToWindow:(id)arg1; (0x285f800195a94c14)
...
Properties:
@property (retain, nonatomic) NSAccessibilityRemoteUIElement* accessoryViewAccessibilityParent;
@property (readonly, nonatomic) NSRemoteView* spawnedBy;
...
Instance Methods:
- (oneway void) release; (0xc97d800195a40a60)
...
```
好多!在该方法列表中搜索“process”得到了 `_serviceProcessIdentifier`,所以让我们调用它。
```bash
(lldb) po [$v _serviceProcessIdentifier]
5959
```
那个进程是什么?
```bash
$ ps -c 5959
PID TTY TIME CMD
5959 ?? 0:14.34 QuickLookUIService
```
肯定是它了。调试 Finder 并不是我们想要的。我们需要调试 `QuickLookUIService`。
## 调试 QuickLookUIService
```bash
(lldb) detach
Process 4040 detached
(lldb) attach 5959
Process 5959 stopped
thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
frame #0: 0x000000018df1bf54 libsystem_kernel.dylib`mach_msg2_trap + 8
libsystem_kernel.dylib`mach_msg2_trap:
-> 0x18df1bf54 <+8>: ret
libsystem_kernel.dylib`macx_swapon:
0x18df1bf58 <+0>: mov x16, #-0x30
0x18df1bf5c <+4>: svc #0x80
0x18df1bf60 <+8>: ret
Target 0: (QuickLookUIService) stopped.
```
所以,`NSRemoteView` 的另一端就在……这个进程中的某个地方。如果我们能遍历该进程中的所有视图,就能看看是否有任何视图看起来像 `NSRemoteViewDelegate` 的另一端。我在互联网上搜索获取所有视图的函数,但除了偶尔有人建议直接使用 Xcode 之外,什么也没找到 [^1]。
好吧,那我们直接用 Xcode 怎么样?
在打开 Xcode、创建项目、选择保存位置、输入团队名称、选择模板、输入 bundle identifier、创建 scheme 并设置 scheme 目标以允许我使用调试器之后,我将其附加到 `QuickLookUIService`,按下“Show View Hierarchy”,然后:
*Xcode 的视图调试器已附加到 QuickLookUIService。*
哇,这太简单了!我们可以点击这些视图并获取关于它们的信息,包括它们的地址,以便我们在调试器中操作它们。而且我们可以立刻看到,最前面的视图(Xcode 告诉我们是一个 `QLBorderView`)是一个带圆角的边框!等等,有个边框?
## 边框
确实有。起初我没注意到,但如果放大看,你会发现除了圆角处理外,QuickLook 还叠加了一个略小于图像内容的边框:
`QLBorderView` 原来非常简单;它只是一个图层具有 `borderWidth` 和 `borderColor` 的视图。让我们从 Xcode 获取它的地址,并让它停止这样做:
```bash
(lldb) po [0x600000850180 setBorderWidth:0];
```
边框成功移除!
## 取消裁剪
所以,边框不见了,但角落仍然是圆形的。我们在视图调试器中可以看到图像视图的角落没有被圆角处理,所以一定有东西在裁剪它。让我们检查所有父视图的图层,看看是否有任何图层的 `cornerRadius` 非零。
你可以写一些代码来做到这一点,但我只是逐个点击并复制其图层地址到调试器中进行检查:
```bash
(lldb) p [(CALayer *)0x60000283f5a0 cornerRadius]
(double) $78 = 0
(lldb) p [(CALayer *)0x60000283f660 cornerRadius]
(double) $79 = 0.43438914027149322
```
好吧,这没用多久。让我们试试……
```bash
(lldb) po [0x60000283f660 setMasksToBounds
相似文章
QuickRight
QuickRight 为 macOS Finder 添加了缺失的右键功能,提升了工作效率。
Glance
Glance 是一款工具,让用户能在 macOS 上通过 Quick Look 即时预览 .md 文件。
@dealignai: DeepSeek-V4-Flash 破解版(已消融/未审查) - 仅限Mac (Osaurus/vMLX) https://huggingface.co/dealignai/DeepSeek-V4…
DeepSeek-V4-Flash 的消融版(未审查),针对 Apple Mac 使用 MLX 进行了优化,移除了拒绝行为,同时保留了知识和推理能力。
@lawrencecchen: 介绍 cmux Finder cmux 现在有了文件浏览器,可以预览视频、图片、PDF 和 Markdown 文件。我喜欢用它……
介绍 cmux Finder,它是 cmux 内置的文件浏览器,支持预览视频、图片、PDF 和 Markdown 文件,可通过 Cmd+Shift+P 切换。
Bug Archeology:借助LLM解开一个十年的Swift/C++谜题
一位开发者讲述了如何利用LLM解决一个Swift/C++跨平台音乐应用中存在十年的Bug,展示了AI如何协助调试复杂问题。