来开始用Touch Bar吧/

前两周住院用不了电脑,看到新的Touch Bar觉得还是很有趣的,一种新的交互方式,虽然暂时还没有接触到实物,but, why not?

于是今天就顺手写了个Touch Bar的小工具——CPUBar(虽然并没有什么用www

CPUBar可以让你在Touch Bar上开/关任意slave CPU core(我相信没人会想关掉master CPU core的)

cpubar-1

cpubar-2

下载CPUBar~

以下则是在Objective-C中使用Touch Bar的例子/

这里就以CPUBar作为例子介绍,首先创建一个RyzaApplictaion,然后让AppDelegate继承NSResponder,接下来在AppDelegate中声明遵守NSTouchBarDelegate

#import <Cocoa/Cocoa.h>

@interface AppDelegate : NSResponder <NSApplicationDelegate, NSTouchBarDelegate>

@end

这里因为是很简单的一个Touch Bar程序,所以我们直接在AppDelegate中实现的Touch Bar。在Apple的文档中,系统查找Touch Bar的描述如下

Touch bars are discovered by searching certain well known components of the application for objects that conform to the NSTouchBarProvider protocol.

意译,系统显示的Touch bars是按照典型Cocoa程序里, 遵循NSTouchBarProvider协议的构成组件, 来搜索的。

Some specific objects in a process are searched to discover NSTouchBar providers. In order, these objects are:
* the application delegate
* the application object itself
* the main window’s window controller
* the main window’s delegate
* the main window itself
* the main window’s first responder
* the key window’s window controller
* the key window’s delegate
* the key window itself
* the key window’s first responder

意译,在Cocoa进程中一些明确被系统用来搜索并确定是NSTouchBar providers的实例顺序如下。(这里顺便把获取的代码也对应贴上了,方便以后hook什么的wwww)

* the application delegate

// Objective-C
[[NSApplication sharedApplication] delegate];

// Swift
NSApplication.shared().delegate

* the application object itself

// Objective-C
[NSApplication sharedApplication];

// Swift
NSApplication.shared()

* the main window’s window controller

// Objective-C
[[[NSApplication sharedApplication] mainWindow] windowController];

// Swift
NSApplication.shared().mainWindow?.windowController

* the main window’s delegate

// Objective-C
[[[NSApplication sharedApplication] mainWindow] delegate];

// Swift
NSApplication.shared().mainWindow?.delegate

* the main window itself

// Objective-C
[[NSApplication sharedApplication] mainWindow];

// Swift
NSApplication.shared().mainWindow

* the main window’s first responder

// Objective-C
[[[NSApplication sharedApplication] mainWindow] firstResponder];

// Swift
NSApplication.shared().mainWindow?.firstResponder

* the key window’s window controller

// Objective-C
[[[NSApplication sharedApplication] keyWindow] windowController];

// Swift
NSApplication.shared().keyWindow?.windowController

* the key window’s delegate

// Objective-C
[[[NSApplication sharedApplication] keyWindow] delegate];

// Swift
NSApplication.shared().keyWindow?.delegate

* the key window itself

// Objective-C
[[NSApplication sharedApplication] keyWindow];

// Swift
NSApplication.shared().keyWindow

* the key window’s first responder

// Objective-C
[[[NSApplication sharedApplication] keyWindow] firstResponder];

// Swift
NSApplication.shared().keyWindow?.firstResponder

If any of these objects are an NSResponder or a subclass of NSResponder, the responder chain anchored at that object is also searched.

意译,如果这些被搜索的对象中是NSResponder的实例或者是继承NSResponder的子类,那么则会沿着responder chain搜索下去。

猜想的实现代码如下,

NSResponder * next = [self nextResponder];
while (next) {
    // Is NSTouchBar provider?
    //  - YES, break
    //  - NO,
    next = [next nextResponder];
}

我们让AppDelegate继承NSResponder还有一个原因是在NSTouchBar.h中,有一个NSResponder的category

@interface NSResponder (NSTouchBarProvider) <NSTouchBarProvider>
/*
    The touch bar object associated with this responder. If no touch bar is explicitly set, NSResponder will send -makeTouchBar to itself to create the default touch bar for this responder. This property is archived.
*/
@property (strong, readwrite, nullable) NSTouchBar *touchBar NS_AVAILABLE_MAC(10_12_1);

/*
    Subclasses should over-ride this method to create and configure the default touch bar for this responder.
*/
- (nullable NSTouchBar *)makeTouchBar NS_AVAILABLE_MAC(10_12_1);
@end

在这个category中,有一个 touchBar 的property,以及一个 -[NSResponder makeTouchBar]方法。在注释中写得很明白,当没有显式设置 touchBar 这个property时,则会调用-[NSResponder makeTouchBar]方法来生成一个。然后子类应当重写-[NSResponder makeTouchBar]方法来创建自己的touchBar。

那么接下来在AppDelegate的实现中重写这个方法即可。

创建一个NSTouchBar比较简单,alloc, init之后,设置customizationIdentifier和delegate。

self.touchBar = [[NSTouchBar alloc] init];
[self.touchBar setCustomizationIdentifier:@"com.[data deleted].CPUBar"];
[self.touchBar setDelegate:self];

customizationIdentifier虽然乍一看是NSTouchBarCustomizationIdentifier,不过实际上就是一个typedef,所以直接写string就好。

typedef NSString * NSTouchBarCustomizationIdentifier NS_EXTENSIBLE_STRING_ENUM;

之后则是设置defaultItemIdentifiers。这个property是用于在初始化Touch Bar的item时,传给- [NSTouchBarDelegate touchBar: makeItemForIdentifier:]的参数。

这里省略掉如何获取CPU信息过程,完成好的 -[NSResponder makeTouchBar] 方法如下。

- (NSTouchBar *)makeTouchBar {
    self.touchBar = [[NSTouchBar alloc] init];
    [self.touchBar setCustomizationIdentifier:@"com.[data deleted].CPUBar"];
    [self.touchBar setDelegate:self];

    if ([self retriveProcessorInfo]) {
        NSMutableArray<NSString *> * defaultItemIdentifiers = [[NSMutableArray alloc] init];
        for (int i = 0; i < processor_count; i++) {
            [defaultItemIdentifiers addObject:[[NSString alloc] initWithFormat:@"com.[data deleted].CPUBar.CPU%d", i]];
        }
        [self.touchBar setDefaultItemIdentifiers:defaultItemIdentifiers];
    } else {
        [self alertMessage:@"Oops, CPUBar needs root privilege to run/" informative:@"Try to run CPUBar with root privilege"];
        [NSApp terminate:nil];
    }

    return self.touchBar;
}

此时就应该实现刚才提到的- [NSTouchBarDelegate touchBar: makeItemForIdentifier:]

这里我们选择使用NSCustomTouchBarItem,然后创建一个NSButton,设置好button的title之后,将button赋给NSCustomTouchBarItem的view即可。

- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier {
    NSCustomTouchBarItem * item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
    NSButton * button = [NSButton buttonWithTitle:@"" target:self action:@selector(setProcessor:)];
    button.tag = [[identifier substringFromIndex:@"com.[data deleted].CPUBar.CPU".length] integerValue];

    processor_basic_info_data_t basic_info;
    mach_msg_type_number_t info_count = PROCESSOR_BASIC_INFO_COUNT;
    processor_info(processor_list[button.tag], PROCESSOR_BASIC_INFO, &port, (processor_info_t)&basic_info, &info_count);
    if (basic_info.is_master) {
        // normally, it would be number 0
        // but just in case...
        masterCPU = button.tag;
    }
    button.attributedTitle = [self stringForCPU:button.tag status:basic_info.running];

    item.view = button;
    return item;
}

到这里,实现一个Touch Bar就基本完成了~

cpubar-1

完整的AppDelegate.m如下,CPUBar的Xcode project在我的Github上,CPUBar

//
//  AppDelegate.m
//  CPUBar
//
//  Created by Ryza 2016/11/8.
//  Copyright © 2016[data deleted]. All rights reserved.
//

#import "AppDelegate.h"
#import <mach/mach.h>

#define CPU_STATUS_MASK 0x1000

@implementation AppDelegate {
    mach_port_t port;
    host_priv_t priv_port;
    processor_port_array_t processor_list;
    mach_msg_type_number_t processor_count;
    NSInteger masterCPU;
}

#pragma mark
#pragma mark - NSApplicationDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    masterCPU = -1;
    processor_count = 0;
    processor_list = (processor_port_array_t)0;
    [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
}

#pragma mark
#pragma mark - NSTouchBarDelegate

- (NSTouchBar *)makeTouchBar {
    [NSApplication sharedApplication];
    [[[NSApplication sharedApplication] keyWindow] windowController];
    self.touchBar = [[NSTouchBar alloc] init];
    [self.touchBar setCustomizationIdentifier:@"com.[data deleted].CPUBar"];
    [self.touchBar setDelegate:self];

    if ([self retriveProcessorInfo]) {
        NSMutableArray<NSString *> * defaultItemIdentifiers = [[NSMutableArray alloc] init];
        for (int i = 0; i < processor_count; i++) {
            [defaultItemIdentifiers addObject:[[NSString alloc] initWithFormat:@"com.[data deleted].CPUBar.CPU%d", i]];
        }
        [self.touchBar setDefaultItemIdentifiers:defaultItemIdentifiers];
    } else {
        [self alertMessage:@"Oops, CPUBar needs root privilege to run/" informative:@"Try to run CPUBar with root privilege"];
        [NSApp terminate:nil];
    }

    return self.touchBar;
}

- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier {
    NSCustomTouchBarItem * item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
    NSButton * button = [NSButton buttonWithTitle:@"" target:self action:@selector(setProcessor:)];
    button.tag = [[identifier substringFromIndex:@"com.[data deleted].CPUBar.CPU".length] integerValue];

    processor_basic_info_data_t basic_info;
    mach_msg_type_number_t info_count = PROCESSOR_BASIC_INFO_COUNT;
    processor_info(processor_list[button.tag], PROCESSOR_BASIC_INFO, &port, (processor_info_t)&basic_info, &info_count);
    if (basic_info.is_master) {
        // normally, it would be number 0
        // but just in case...
        masterCPU = button.tag;
    }
    button.attributedTitle = [self stringForCPU:button.tag status:basic_info.running];

    item.view = button;
    return item;
}

#pragma mark
#pragma mark - Private Methods

- (BOOL)retriveProcessorInfo {
    port = mach_host_self();
    kern_return_t rc;

    rc = host_get_host_priv_port(port, &priv_port);
    if (rc != KERN_SUCCESS) return NO;

    rc = host_processors(priv_port, &processor_list, &processor_count);
    if (rc != KERN_SUCCESS) return NO;

    return YES;
}

- (void)alertMessage:(NSString *)msg informative:(NSString *)info {
    NSAlert * alert = [[NSAlert alloc] init];
    alert.alertStyle = NSAlertStyleWarning;
    alert.messageText = msg;
    alert.informativeText = info;
    [alert runModal];
}

- (NSAttributedString *)stringForCPU:(NSInteger)index status:(BOOL)on {
    NSMutableAttributedString * string = [[NSMutableAttributedString alloc] initWithString:@"●" attributes:@{NSForegroundColorAttributeName: on ? [NSColor greenColor] : [NSColor redColor]}];
    [string appendAttributedString:[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@" CPU %ld ", index] attributes:@{NSForegroundColorAttributeName: [NSColor whiteColor]}]];
    [string setAlignment:NSTextAlignmentCenter range:NSMakeRange(0, string.length)];
    return string;
}

- (void)setProcessor:(NSButton *)button {
    kern_return_t kr;
    if (button.tag & CPU_STATUS_MASK) {
        button.tag &= ~CPU_STATUS_MASK;
        kr = processor_start(processor_list[button.tag]);
        if (kr != KERN_SUCCESS) {
            [self alertMessage:[NSString stringWithFormat:@"∑(゚Д゚) Cannot restart CPU %ld", button.tag] informative:@"Perhaps wait for a moment and retry it"];
            button.tag |= CPU_STATUS_MASK;
        } else {
            button.attributedTitle = [self stringForCPU:button.tag status:YES];
        }
    } else {
        if (button.tag == masterCPU) {
            // trust me, no one really wants to stop the master CPU
            return;
        }
        kr = processor_exit(processor_list[button.tag]);
        if (kr != KERN_SUCCESS) {
            [self alertMessage:[NSString stringWithFormat:@"∑(゚Д゚) Cannot stop CPU %ld", button.tag] informative:@"Perhaps wait for a moment and retry it"];
        } else {
            button.attributedTitle = [self stringForCPU:button.tag status:NO];
            button.tag |= CPU_STATUS_MASK;
        }
    }
}

@end

One thought on “来开始用Touch Bar吧/”

Leave a Reply

Your email address will not be published. Required fields are marked *

nineteen − 2 =