*)fp_dictionaryFromTXTRecordData;
21 | - (NSString *)fp_typeName;
22 | - (NSURL *)fp_URL;
23 | - (NSString *)fp_discoveredType;
24 | - (NSString *)fp_model;
25 | @end
26 |
27 | @interface NSNetService (FPMenuAddtions)
28 | - (NSMenuItem *)fp_menuItem:(SEL)action;
29 | - (NSMenu *)fp_submenuItems:(SEL)action;
30 | @end
31 |
32 | @interface NSMenuItem (FPMenuAddtions)
33 | + (NSMenuItem *)fp_itemWithTitle:(NSString *)title URL:(NSURL *)URL type:(NSString *)type action:(SEL)action;
34 | - (void)fp_imageString:(NSString *)image;
35 | @end
36 |
37 | @interface NSData (FPMenuAddtions)
38 | - (NSDictionary *)fp_parseTXTData;
39 | @end
40 |
--------------------------------------------------------------------------------
/BonjourMenu/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSHumanReadableCopyright
26 | Copyright © 2017 Ford Parsons. All rights reserved.
27 | NSMainStoryboardFile
28 | Main
29 | NSPrincipalClass
30 | NSApplication
31 | LSUIElement
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BonjourMenu
2 | **BonjourMenu** adds a system status menu that lists local network Bonjour services.
3 |
4 | ## Background
5 | Safari previously included Bonjour webpages and printers in the bookmarks menu, however this feature was removed in Safari 11. **BonjourMenu** restores this functionality and moves the menu to the system menu bar so it is always available.
6 |
7 | ## Screenshot
8 |
9 |
10 | ## Features
11 | In addition to restoring access to Bonjour webpages and printers, **BonjourMenu** also displays other common services, facilitating access to Mac/Windows file shares, Plex media servers, Raspberry Pis (advertising with Avahi), and IoT devices.
12 |
13 | | Type | Service | Launch URL | Application |
14 | | --- | --- | --- | --- |
15 | | _http._tcp. | HTTP | `http://:@server:port/` | Safari |
16 | | _afpovertcp._tcp. | Apple File Protocol | `afp://:@server:port/` | Finder |
17 | | _smb._tcp. | Samba | `smb://:@server:port/` | Finder |
18 | | _ssh._tcp. | SSH | `ssh://:@server:port/` | Terminal |
19 | | _rfb._tcp. | VNC | `vnc://:@server:port/` | Screen Sharing |
20 | | _plexmediasvr._tcp. | Plex | `http://:@server:port/` | Safari |
21 | | _ipp._tcp. | Internet Printing Protocol | `ipp://:@server:port/` | Preferences |
22 |
23 | Note: the ``, ``, and `` variables are set via the Bonjour TXT record [as specified in the RFC](http://www.dns-sd.org/ServiceTypes.html)
24 |
25 | ## Similar
26 | [Bonjour Browser](http://tildesoft.com) by Lily Ballard
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ignore
2 |
3 | # Xcode
4 | #
5 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
6 |
7 | ## User settings
8 | xcuserdata/
9 |
10 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
11 | *.xcscmblueprint
12 | *.xccheckout
13 |
14 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
15 | build/
16 | DerivedData/
17 | *.moved-aside
18 | *.pbxuser
19 | !default.pbxuser
20 | *.mode1v3
21 | !default.mode1v3
22 | *.mode2v3
23 | !default.mode2v3
24 | *.perspectivev3
25 | !default.perspectivev3
26 |
27 | ## Obj-C/Swift specific
28 | *.hmap
29 |
30 | ## App packaging
31 | *.ipa
32 | *.dSYM.zip
33 | *.dSYM
34 |
35 | # CocoaPods
36 | #
37 | # We recommend against adding the Pods directory to your .gitignore. However
38 | # you should judge for yourself, the pros and cons are mentioned at:
39 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
40 | #
41 | # Pods/
42 | #
43 | # Add this line if you want to avoid checking in source code from the Xcode workspace
44 | # *.xcworkspace
45 |
46 | # Carthage
47 | #
48 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
49 | # Carthage/Checkouts
50 |
51 | Carthage/Build/
52 |
53 | # fastlane
54 | #
55 | # It is recommended to not store the screenshots in the git repo.
56 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
57 | # For more information about the recommended setup visit:
58 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
59 |
60 | fastlane/report.xml
61 | fastlane/Preview.html
62 | fastlane/screenshots/**/*.png
63 | fastlane/test_output
64 |
65 | # Code Injection
66 | #
67 | # After new code Injection tools there's a generated folder /iOSInjectionProject
68 | # https://github.com/johnno1962/injectionforxcode
69 |
70 | iOSInjectionProject/
71 |
--------------------------------------------------------------------------------
/BonjourMenu/types.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | types
6 |
7 | _http._tcp.
8 |
9 | scheme
10 | http
11 | image
12 | com.apple.Safari
13 | typeName
14 | HTTP
15 |
16 | _afpovertcp._tcp.
17 |
18 | scheme
19 | afp
20 | image
21 | /System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericAirDiskIcon.icns
22 | typeName
23 | AFP
24 |
25 | _adisk._tcp.
26 |
27 | scheme
28 | afp
29 | image
30 | /System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericAirDiskIcon.icns
31 | typeName
32 | Disk
33 |
34 | _smb._tcp.
35 |
36 | scheme
37 | smb
38 | image
39 | /System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericFileServerIcon.icns
40 | typeName
41 | Samba
42 |
43 | _ssh._tcp.
44 |
45 | scheme
46 | ssh
47 | image
48 | com.apple.Terminal
49 | typeName
50 | SSH
51 |
52 | _sftp-ssh._tcp.
53 |
54 | scheme
55 | sftp
56 | image
57 | com.apple.Terminal
58 | typeName
59 | SFTP
60 |
61 | _rfb._tcp.
62 |
63 | scheme
64 | vnc
65 | image
66 | com.apple.ScreenSharing
67 | typeName
68 | VNC
69 |
70 | _plexmediasvr._tcp.
71 |
72 | scheme
73 | http
74 | image
75 | Plex
76 | path
77 | /web
78 | typeName
79 | Plex
80 |
81 | _ipp._tcp.
82 |
83 | scheme
84 | ipp
85 | image
86 | /System/Library/PreferencePanes/PrintAndScan.prefPane/Contents/Resources/PrintScanPref.icns
87 | path
88 | /ipp/
89 | typeName
90 | IPP
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/BonjourMenu/FPNetServiceBrowser.m:
--------------------------------------------------------------------------------
1 | //
2 | // FPNetServiceBrowser.m
3 | // BonjourMenu
4 | //
5 | // Created by Ford Parsons on 12/12/17.
6 | // Copyright © 2017 Ford Parsons. All rights reserved.
7 | //
8 |
9 | #import "FPNetServiceBrowser.h"
10 |
11 | @interface FPNetServiceBrowser () {
12 | NSMutableArray *browsers;
13 | NSMutableArray *services;
14 | NSMutableArray *devices;
15 | NSTimer *timer;
16 | }
17 | @end
18 |
19 | @implementation FPNetServiceBrowser
20 |
21 | - (void)searchForServicesOfTypes:(NSArray *)types {
22 | [self stop];
23 | browsers = NSMutableArray.array;
24 | services = NSMutableArray.array;
25 | devices = NSMutableArray.array;
26 | self.deviceMap = NSMutableDictionary.dictionary;
27 | [types enumerateObjectsUsingBlock:^(NSString * _Nonnull type, NSUInteger idx, BOOL * _Nonnull stop) {
28 | NSNetServiceBrowser *browser = NSNetServiceBrowser.new;
29 | browser.delegate = self;
30 | [browser searchForServicesOfType:type inDomain:@""];
31 | [browsers addObject:browser];
32 | }];
33 | }
34 |
35 | - (void)stop {
36 | [browsers makeObjectsPerformSelector:@selector(stop)];
37 | [services makeObjectsPerformSelector:@selector(stop)];
38 | browsers = nil;
39 | services = nil;
40 | }
41 |
42 | #pragma mark NSNetServiceBrowserDelegate
43 |
44 | - (void)netServiceBrowser:(NSNetServiceBrowser *)browser didFindService:(NSNetService *)service moreComing:(BOOL)moreComing {
45 | [services addObject:service];
46 | service.delegate = self;
47 | [service resolveWithTimeout:5];
48 | // https://stackoverflow.com/questions/4309740/how-do-i-obtain-model-name-for-a-networked-device-potentially-using-bonjour/5294662#5294662
49 | if(![[devices valueForKeyPath:@"@distinctUnionOfObjects.name"] containsObject:service.name]) {
50 | NSNetService *device = [[NSNetService alloc] initWithDomain:@"local" type:@"_device-info._tcp" name:service.name];
51 | device.delegate = self;
52 | [device startMonitoring];
53 | [devices addObject:device];
54 | }
55 | }
56 |
57 | - (void)netServiceBrowser:(NSNetServiceBrowser *)browser didRemoveService:(NSNetService *)service moreComing:(BOOL)moreComing {
58 | [services removeObject:service];
59 | if(!moreComing) { [self.delegate receivedServices:services]; }
60 | }
61 |
62 | #pragma mark NSNetServiceDelegate
63 |
64 | - (void)netServiceDidResolveAddress:(NSNetService *)sender {
65 | if(timer != nil) { return; }
66 | timer = [NSTimer timerWithTimeInterval:1 repeats:NO block:^(NSTimer * _Nonnull _timer) {
67 | [self.delegate receivedServices:self->services];
68 | [self->timer invalidate];
69 | self->timer = nil;
70 | }];
71 | [NSRunLoop.mainRunLoop addTimer:timer forMode:NSRunLoopCommonModes];
72 | }
73 |
74 | - (void)netService:(NSNetService *)sender didUpdateTXTRecordData:(NSData *)data {
75 | [self.deviceMap setObject:sender.fp_model forKey:sender.name];
76 | [sender stopMonitoring];
77 | }
78 |
79 | @end
80 |
81 | @interface FPNetServiceTypeBrowser () {
82 | NSNetServiceBrowser *browser;
83 | NSMutableSet *types;
84 | }
85 | @end
86 |
87 | @implementation FPNetServiceTypeBrowser
88 | - (void)searchForTypes {
89 | if(!!browser) return;
90 | [self stop];
91 | types = NSMutableSet.set;
92 | browser = NSNetServiceBrowser.new;
93 | browser.includesPeerToPeer = YES;
94 | browser.delegate = self;
95 | [browser searchForServicesOfType:@"_services._dns-sd._udp." inDomain:@""];
96 | }
97 |
98 | - (void)stop {
99 | [browser stop];
100 | types = nil;
101 | browser = nil;
102 | }
103 |
104 | #pragma mark NSNetServiceBrowserDelegate
105 |
106 | - (void)netServiceBrowser:(NSNetServiceBrowser *)browser didFindService:(NSNetService *)service moreComing:(BOOL)moreComing {
107 | [types addObject:service.fp_discoveredType];
108 | if(!moreComing) { [self.delegate receivedTypes:types.allObjects]; }
109 | }
110 |
111 | - (void)netServiceBrowser:(NSNetServiceBrowser *)browser didRemoveService:(NSNetService *)service moreComing:(BOOL)moreComing {
112 | [types removeObject:service.fp_discoveredType];
113 | if(!moreComing) { [self.delegate receivedTypes:types.allObjects]; }
114 | }
115 | @end
116 |
--------------------------------------------------------------------------------
/BonjourMenu/AppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.m
3 | // BonjourMenu
4 | //
5 | // Created by Ford Parsons on 11/21/17.
6 | // Copyright © 2017 Ford Parsons. All rights reserved.
7 | //
8 |
9 | #import "AppDelegate.h"
10 |
11 | @interface AppDelegate () {
12 | FPNetServiceTypeBrowser *typeBrowser;
13 | FPNetServiceBrowser *browser;
14 | NSStatusItem *mainStatusItem;
15 | }
16 | @end
17 |
18 | @implementation AppDelegate
19 |
20 | - (NSDictionary *)types {
21 | return [NSUserDefaults.standardUserDefaults objectForKey:NSStringFromSelector(_cmd)];
22 | }
23 |
24 | #pragma mark NSApplicationDelegate
25 |
26 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
27 | [NSUserDefaults.standardUserDefaults registerDefaults:[NSDictionary dictionaryWithContentsOfFile:[NSBundle.mainBundle pathForResource:@"types" ofType:@"plist"]]];
28 |
29 | mainStatusItem = [NSStatusBar.systemStatusBar statusItemWithLength:NSSquareStatusItemLength];
30 | mainStatusItem.button.image = [NSImage imageNamed:NSImageNameBonjour];
31 | mainStatusItem.button.image.template = YES;
32 | mainStatusItem.button.image.size = NSMakeSize(16, 16);
33 | mainStatusItem.menu = NSMenu.new;
34 |
35 | typeBrowser = FPNetServiceTypeBrowser.new;
36 | typeBrowser.delegate = self;
37 | [typeBrowser searchForTypes];
38 |
39 | browser = FPNetServiceBrowser.new;
40 | browser.delegate = self;
41 | }
42 |
43 | #pragma mark FPNetServiceTypeBrowserDelegate
44 |
45 | - (void)receivedTypes:(NSArray *)types {
46 | [browser searchForServicesOfTypes:types];
47 | }
48 |
49 | #pragma mark FPNetServiceBrowserDelegate
50 |
51 | - (void)receivedServices:(NSArray *)services {
52 | [mainStatusItem.menu removeAllItems];
53 |
54 | NSArray *types = [[services valueForKeyPath:@"@distinctUnionOfObjects.type"] sortedArrayUsingSelector:@selector(compare:)];
55 | NSArray *hostNames = [[services valueForKeyPath:@"@distinctUnionOfObjects.hostName"] sortedArrayUsingSelector:@selector(compare:)];
56 |
57 | [types enumerateObjectsUsingBlock:^(NSString * _Nonnull type, NSUInteger idx_type, BOOL * _Nonnull stop_type) {
58 | if(![self.types.allKeys containsObject:type]) return; // continue;
59 | [[[services filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSNetService *service, NSDictionary * _Nullable bindings) { return [service.type isEqualToString:type]; }]] sortedArrayUsingComparator:^NSComparisonResult(NSNetService *obj1, NSNetService *obj2) { return [obj1.name compare:obj2.name]; }] enumerateObjectsUsingBlock:^(NSNetService * _Nonnull service, NSUInteger idx_service, BOOL * _Nonnull stop_service) {
60 | [mainStatusItem.menu addItem:[service fp_menuItem:@selector(statusItemAction:)]];
61 | }];
62 | [mainStatusItem.menu addItem:NSMenuItem.separatorItem];
63 | }];
64 | [hostNames enumerateObjectsUsingBlock:^(NSString * _Nonnull hostName, NSUInteger idx_hostName, BOOL * _Nonnull stop_hostName) {
65 | NSMenuItem *menuItem = [mainStatusItem.menu addItemWithTitle:hostName action:nil keyEquivalent:@""];
66 | NSString *image = browser.deviceMap[[services filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSNetService *device, NSDictionary * _Nullable bindings) {
67 | return [device.hostName isEqualToString:hostName] && self->browser.deviceMap[device.name] != nil;
68 | }]].firstObject.name];
69 | [menuItem fp_imageString:image];
70 | menuItem.submenu = NSMenu.new;
71 | [[[services filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSNetService *service, NSDictionary * _Nullable bindings) { return [service.hostName isEqualToString:hostName]; }]] sortedArrayUsingComparator:^NSComparisonResult(NSNetService *obj1, NSNetService *obj2) { return [obj1.type compare:obj2.type] ?: [obj1.name compare:obj2.name]; }] enumerateObjectsUsingBlock:^(NSNetService * _Nonnull service, NSUInteger idx_service, BOOL * _Nonnull stop_service) {
72 | [mainStatusItem.menu.itemArray.lastObject.submenu addItem:[service fp_menuItem:@selector(statusItemAction:)]];
73 | }];
74 | }];
75 | [mainStatusItem.menu addItem:NSMenuItem.separatorItem];
76 | [types enumerateObjectsUsingBlock:^(NSString * _Nonnull type, NSUInteger idx_type, BOOL * _Nonnull stop_type) {
77 | NSMenuItem *menuItem = [mainStatusItem.menu addItemWithTitle:type action:nil keyEquivalent:@""];
78 | menuItem.submenu = NSMenu.new;
79 | [[[services filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSNetService *service, NSDictionary * _Nullable bindings) { return [service.type isEqualToString:type]; }]] sortedArrayUsingComparator:^NSComparisonResult(NSNetService *obj1, NSNetService *obj2) { return [obj1.name compare:obj2.name]; }] enumerateObjectsUsingBlock:^(NSNetService * _Nonnull service, NSUInteger idx_service, BOOL * _Nonnull stop_service) {
80 | [mainStatusItem.menu.itemArray.lastObject.submenu addItem:[service fp_menuItem:@selector(statusItemAction:)]];
81 | }];
82 | }];
83 | [mainStatusItem.menu addItem:NSMenuItem.separatorItem];
84 | [mainStatusItem.menu addItemWithTitle:@"Quit" action:@selector(quit:) keyEquivalent:@"q"];
85 | }
86 |
87 | #pragma mark IBAction
88 |
89 | - (IBAction)quit:(id)sender {
90 | [NSApplication.sharedApplication terminate:sender];
91 | }
92 |
93 | - (IBAction)statusItemAction:(NSMenuItem *)sender {
94 | [NSWorkspace.sharedWorkspace openURL:sender.representedObject];
95 | }
96 |
97 | @end
98 |
--------------------------------------------------------------------------------
/BonjourMenu/NSNetService+Additions.m:
--------------------------------------------------------------------------------
1 | //
2 | // NSNetService+Additions.m
3 | // BonjourMenu
4 | //
5 | // Created by Ford Parsons on 12/22/17.
6 | // Copyright © 2017 Ford Parsons. All rights reserved.
7 | //
8 |
9 | #import "NSNetService+Additions.h"
10 | #include
11 | #import
12 |
13 | @implementation FPNetServiceTXTRecord
14 | - (NSString *)objectForKeyedSubscript:(NSString *)key {
15 | NSData *data = [NSNetService dictionaryFromTXTRecordData:self.netService.TXTRecordData][key];
16 | return (data != nil) ? [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] : nil;
17 | }
18 | @end
19 |
20 | @implementation NSNetService (FPNetServiceAddtions)
21 | - (NSString *)fp_ipv4 {
22 | for(NSData *data in self.addresses) {
23 | struct sockaddr *addr = (struct sockaddr *)data.bytes;
24 | if(addr->sa_family == AF_INET) {
25 | struct sockaddr_in *ip4 = (struct sockaddr_in *)data.bytes;
26 | char dest[INET_ADDRSTRLEN];
27 | return [NSString stringWithFormat:@"%s", inet_ntop(AF_INET, &ip4->sin_addr, dest, sizeof dest)];
28 | }
29 | }
30 | return nil;
31 | }
32 | - (NSString *)fp_ipv6 {
33 | for(NSData *data in self.addresses) {
34 | struct sockaddr *addr = (struct sockaddr *)data.bytes;
35 | if(addr->sa_family == AF_INET6) {
36 | struct sockaddr_in6 *ip6 = (struct sockaddr_in6 *)data.bytes;
37 | char dest[INET6_ADDRSTRLEN];
38 | return [NSString stringWithFormat:@"%s", inet_ntop(AF_INET6, &ip6->sin6_addr, dest, sizeof dest)];
39 | }
40 | }
41 | return nil;
42 | }
43 | - (FPNetServiceTXTRecord *)fp_TXTRecord {
44 | FPNetServiceTXTRecord *txtRecord = objc_getAssociatedObject(self, @selector(fp_TXTRecord));
45 | if(!txtRecord) {
46 | txtRecord = FPNetServiceTXTRecord.new;
47 | txtRecord.netService = self;
48 | objc_setAssociatedObject(self, @selector(fp_TXTRecord), txtRecord, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
49 | }
50 | return txtRecord;
51 | }
52 | - (NSDictionary *)fp_dictionaryFromTXTRecordData {
53 | return [NSNetService dictionaryFromTXTRecordData:self.TXTRecordData];
54 | }
55 | - (NSString *)fp_typeName {
56 | NSDictionary *dict = [NSUserDefaults.standardUserDefaults objectForKey:@"types"][self.type];
57 | return dict[@"typeName"] ?: [[self.type componentsSeparatedByString:@"."].firstObject stringByReplacingOccurrencesOfString:@"_" withString:@""];
58 | }
59 | - (NSURL *)fp_URL {
60 | NSDictionary *dict = [NSUserDefaults.standardUserDefaults objectForKey:@"types"][self.type];
61 | NSURLComponents *url = NSURLComponents.new;
62 | url.scheme = dict[@"scheme"] ?: @"http";
63 | url.user = self.fp_TXTRecord[@"u"];
64 | url.password = self.fp_TXTRecord[@"p"];
65 | url.host = self.hostName;
66 | if(self.port >= 0) { url.port = @(self.port); }
67 | url.path = self.fp_TXTRecord[@"path"] ?: dict[@"path"];
68 | return url.URL;
69 | }
70 | - (NSString *)fp_discoveredType {
71 | return [NSString stringWithFormat:@"%@.%@.", self.name, [self.type componentsSeparatedByString:@"."].firstObject];
72 | }
73 | // http://cocoadev.github.io/GettingTheIconOfNetworkMachinesTypes/
74 | // TODO: http://www.openradar.me/30927725
75 | - (NSString *)fp_model {
76 | // Uses undocumented _LSIconPath. kUTTypeIconFileKey doesn't seem to exist any more and when it does, it lacks `/Contents/Resources/` and isn't found in CoreTypes
77 | NSString *model = self.fp_TXTRecord[@"model"];
78 | if(!model) return nil;
79 | CFStringRef uti = CFAutorelease(UTTypeCreatePreferredIdentifierForTag((__bridge CFStringRef)@"com.apple.device-model-code", (__bridge CFStringRef)model, nil));
80 | if(!uti) return nil;
81 | CFDictionaryRef decl = CFAutorelease(UTTypeCopyDeclaration(uti));
82 | if(!decl) return nil;
83 | CFStringRef icon = CFDictionaryGetValue(decl, @"_LSIconPath");
84 | while(icon == nil && uti != nil) {
85 | CFArrayRef utis = CFDictionaryGetValue(decl, kUTTypeConformsToKey);
86 | for(CFIndex i = 0; i
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
673 |
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
684 |
685 |
--------------------------------------------------------------------------------