如何在 OSX 上存储 HTML-based QuickLook 生成器的首选项?

How to store preferences for a HTML-based QuickLook generator on OSX?

我曾经使用必须单独安装的自定义 PrefPane 来完成,但这并不令人满意。

我有一个 HTML-based QuickLook 生成器,可以创建一些不均匀内容的缩略图和预览(具有长 ASCII header 的文件,以及各种二进制扩展名,每个扩展名都有一些header).

在 QL 方法 OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options) 中,我尝试使用 [NSUserDefaults standardUserDefaults],但它没有效果,并且没有写入首选项文件,可能是因为我们在一个包中,而不是一个应用程序中。

知道如何实现吗?我知道一些优秀的 QL 生成器可以做到这一点,例如 BetterZip QL。我试图对 BetterZip QL 进行逆向工程,但没有成功。

我直接联系了 BetterZip QL 的作者,我们一起想出了一个解决方案。在这里。

简而言之:

  1. 首先,创建一个小助手应用程序,它将被捆绑在 发电机。此应用程序将负责编写首选项 文件。
  2. 让这个应用程序注册一个自定义 URL 方案并实施与之关联的处理。
  3. 使基于 HTML 的 QL 打开一个特殊格式的 URL,使用该自定义方案,使用 Javascript。

好的,现在详细介绍。

首先在您的 Xcode workspace/project 中创建一个小的辅助应用程序目标。我的 QL 生成器名为 QLFits,我选择了 QLFitsConfig

默认情况下,有一个 MainMenu.xib 与该应用相关联。留着它。它由 Info.plist 使用,对调试很有用。事实上,要调试自定义 URL 方案,您可以向该 xib 添加一个 NSWindow,并放置可用于显示调试消息的标签。调试此问题时,我发现没有真正的其他方法来记录或显示调试消息。

但最后,你有一个小 windowless 应用程序。这个应用程序必须有两个配置。

  1. 表示此应用程序是代理的标志(见图)。当 运行ning.
  2. 时,它会阻止应用程序出现在文档中
  3. 自定义 URL 方案的声明,具有 Editor 角色。另请参见图片示例(此处 qlfitsconfig

接下来,应用程序的实现需要注册URL方案来告诉系统有一个应用程序可以打开它。下面是应用程序 AppDelegate 中我的 "appDidFinishLaunching" 方法的实现。

分为三个部分:自定义URL方案的处理程序的注册。与 QL 生成器 共享的具有套件名称 NSUserDefaults 对象的实例化。最后,注册首选项的默认值(使用与应用程序捆绑在一起的 .plist 文件)。

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager];
    [appleEventManager setEventHandler:self andSelector:@selector(handleAppleEvent:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL];

    [[NSUserDefaults standardUserDefaults] addSuiteNamed:suiteName];
    NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:suiteName];

    NSString *optionsPath = [[NSBundle mainBundle] pathForResource:@"defaultOptions" ofType:@"plist"];
    NSDictionary *defaultOptions = [NSDictionary dictionaryWithContentsOfFile:optionsPath];
    [defaults registerDefaults:defaultOptions];
}

suiteName 变量是具有反向 DNS 格式的静态 NSString:static NSString *suiteName = @"com.onekiloparsec.qlfitsconfig.user-defaults-suite";

然后,应用程序需要根据事件的触发进行操作。因此,必须对事件做一些事情,并使用该事件来存储偏好。这是实现。请注意,方法的签名必须恰好是那个,不是因为我们在上面声明了它,而是因为它是系统唯一识别的。

- (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent
{
    NSString *URLString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
    if (URLString) {
        NSURL *URL = [NSURL URLWithString:URLString];
        if (URL) {
            NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:suiteName];

            for (NSString *component in [URL pathComponents]) {
                if ([component containsString:@"="]) {
                    NSArray *keyValue = [component componentsSeparatedByString:@"="];
                    [defaults setObject:keyValue.lastObject forKey:keyValue.firstObject];
                }
            }

            [defaults synchronize];
        }
    }
}

基本思想是我们将通过 URL 参数作为键值对提供偏好。因此,我们在这里将 URL 字符串转换为一对首选项,并将其存储为字符串。

这就是应用程序的全部内容。要测试和调试它,您需要构建并 运行 它(例如,检查 /Utilities/Activity Monitor.app 它是 运行ning)。您可以在终端中键入以下命令以查看会发生什么:

$ open qlfitsconfig://save/option1=value1/option2=value2

如果您保留了 window 和上面提到的标签,您可以使用它们来 display/debug 您的应用接收什么事件。

现在,回到 QL 生成器。将配置应用作为 "Target Dependency" 包含在生成器的 "Build Phases" 中。此外,添加一个新的 "Copy Files" 构建阶段(在 Copy Bundle Resources 构建阶段之后)以在 QL 包中复制该助手应用程序(见图)。

现在,在代码中,更准确地说,在方法预览方法内部:OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options)

在开始时,我确保配置帮助应用程序确实已在系统的启动服务中注册。要找到它,必须使用 QL 生成器的包标识符。请特别注意应用程序的 URL 是如何构造的,基于它在构建阶段(Helpers 目录)中的复制位置。

NSBundle *bundle = [NSBundle bundleWithIdentifier:@"com.onekiloparsec.QLFits3"];
NSURL *urlConfig = [NSURL fileURLWithPath:[[bundle bundlePath] stringByAppendingPathComponent:@"Contents/Helpers/QLFitsConfig.app"]];
LSRegisterURL((__bridge CFURLRef) urlConfig, true);

最后一行使用的是遗留 API,但我无法使新 API 正常工作。这是一个弱点,在某些时候可能应该找到更好的方法。

现在,如果已经保存了一些首选项,则可以使用 NSUserDefaults 的实例访问它们,假设我们使用 中定义的相同套件名称 对其进行初始化帮助应用程序。示例:

    static NSString *suiteName = @"com.onekiloparsec.qlfitsconfig.user-defaults-suite";
    NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:suiteName];

    BOOL alwaysShowHeaders = YES;
    if ([defaults stringForKey:@"alwaysShowHeaders"]) {
        alwaysShowHeaders = [[defaults stringForKey:@"alwaysShowHeaders"] isEqualToString:@"1"];
    }

Obj-C 代码就是这些。

最后一部分是Javascript代码。在我的 QL 生成器 (whose code can be checked on GitHub) 中,我使用了一个包含所有 html 和 JS 代码的 template.html 文件。您可以在这里以不同的方式组织自己。

我最初打算在切换复选框时更改 QL 首选项。但它似乎不起作用(没有触发任何事件)。我让它工作的唯一方法是,一旦设置了我的复选框,就要求用户使用按钮 "save" 首选项。单击该按钮后,我会保存首选项。这是我的 template.html

中的 JS 代码
<script>
function saveConfig (a) {
    a.href  = "qlfitsconfig://save";
    a.href += "/alwaysShowHeaders=" + (document.getElementById("alwaysShowHeadersInput").checked ? "1" : "0");
    a.href += "/showSummaryInThumbnails=" + (document.getElementById("showSummaryInThumbnailsInput").checked ? "1" : "0");
    return true;
}
</script>

alwaysShowHeadersInputshowSummaryInThumbnailsInput 是 HTML 代码中我的复选框的 'id'。并且保存按钮触发了 saveConfig 函数。

并且按钮必须在 a 标记内声明:

<a href="#" onClick="saveConfig(this);return true;" style="float:right;"><input id="save" type="button" value="Save"></a>

这是我的 QL 中的偏好设置 window:

瞧瞧!