This commit is contained in:
relikd
2019-08-18 22:57:05 +02:00
commit c9811dd3fb
11 changed files with 946 additions and 0 deletions

173
source/AppDelegate.m Normal file
View File

@@ -0,0 +1,173 @@
@import Cocoa;
@interface AppDelegate : NSObject <NSApplicationDelegate, NSTableViewDataSource, NSComboBoxCellDataSource>
@end
static NSMutableDictionary<NSString*, NSString*> *nameCache;
// ################################################################
// #
// # MARK: - AppId -
// #
// ################################################################
@interface AppId : NSObject
@property (copy) NSString *bundleId;
@property (copy) NSString *name;
@end
@implementation AppId
+ (instancetype)bundleId:(NSString*)bundleId {
AppId *a = [AppId new];
a.bundleId = bundleId;
[a updateAppName];
return a;
}
/// First query name cache for available names. If not set, add new name to cache
- (void)updateAppName {
self.name = nameCache[self.bundleId];
if (!self.name) {
self.name = [self applicationNameForBundleId:self.bundleId];
if (!self.name) self.name = self.bundleId;
[nameCache setValue:self.name forKey:self.bundleId];
}
}
/// Returns application name for given identifier
- (NSString*)applicationNameForBundleId:(NSString*)bundleID {
NSArray<NSURL*> *urls = CFBridgingRelease(LSCopyApplicationURLsForBundleIdentifier((__bridge CFStringRef)bundleID, NULL));
if (urls.count > 0) {
NSDictionary *info = CFBridgingRelease(CFBundleCopyInfoDictionaryForURL((CFURLRef)urls.firstObject));
return info[(NSString*)kCFBundleExecutableKey];
}
return nil;
}
@end
// ################################################################
// #
// # MARK: - Scheme -
// #
// ################################################################
@interface Scheme : NSObject
@property (copy) NSString *name;
@property (weak) AppId *registered;
@property (strong) NSArray<AppId*> *available;
@end
@implementation Scheme
+ (instancetype)name:(NSString*)name {
Scheme *s = [Scheme new];
s.name = name;
[s prepareAvailable];
return s;
}
/// Select app at index and set it default. Checks whether set successful. Ignores setting same id.
- (void)setDefault:(NSUInteger)index {
AppId *app = self.available[index];
if (app == self.registered) return;
OSStatus s = LSSetDefaultHandlerForURLScheme((__bridge CFStringRef)self.name, (__bridge CFStringRef)app.bundleId);
if (s == 0) self.registered = app;
}
/// Gathers all registered application for scheme and inserts to available
- (void)prepareAvailable {
NSMutableArray *list = [NSMutableArray array];
NSString *defaultId = CFBridgingRelease(LSCopyDefaultHandlerForURLScheme((__bridge CFStringRef)self.name));
NSArray<NSString*> *ids = CFBridgingRelease(LSCopyAllHandlersForURLScheme((__bridge CFStringRef)self.name));
// LSCopyDefaultRoleHandlerForContentType, LSCopyAllRoleHandlersForContentType, kLSRolesAll
for (NSString *bundleId in ids) {
[list addObject:[AppId bundleId:bundleId]];
if ([bundleId isEqualToString:defaultId])
self.registered = list.lastObject;
}
self.available = [list sortedArrayUsingComparator:^NSComparisonResult(AppId *a, AppId *b) {
return [[a.name lowercaseString] compare:[b.name lowercaseString]];
}];
}
@end
// ################################################################
// #
// # MARK: - Main -
// #
// ################################################################
@interface AppDelegate ()
@property (weak) IBOutlet NSWindow *window;
@property (weak) IBOutlet NSTableView *table;
@property (strong) NSMutableArray<Scheme*> *data;
@end
@implementation AppDelegate
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
return YES;
}
- (void)applicationWillFinishLaunching:(NSNotification *)notification {
nameCache = [NSMutableDictionary dictionary];
self.data = [NSMutableArray array];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
for (NSString *urlScheme in [self readLaunchServicesSchemes]) {
Scheme *s = [Scheme name:urlScheme];
if (s.available.count > 1)
[self.data addObject:s];
}
[self.data sortUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]]];
[self.table reloadData];
}
- (NSSet*)readLaunchServicesSchemes {
NSUserDefaults *ud = [[NSUserDefaults alloc] initWithSuiteName:@"com.apple.LaunchServices/com.apple.launchservices.secure"];
NSMutableSet<NSString*> *allSchemes = [NSMutableSet set];
for (NSDictionary *handler in [ud arrayForKey:@"LSHandlers"]) {
NSString *scheme = handler[@"LSHandlerURLScheme"]; // LSHandlerContentType
if (scheme) [allSchemes addObject:scheme];
}
return allSchemes;
}
#pragma mark - TableView & ComboBox data source
// table view data source
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return self.data.count;
}
// table view data source
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
if ([tableColumn.identifier isEqualToString:@"colScheme"])
return self.data[row].name;
return self.data[row].registered.name;
}
// table view data source
- (void)tableView:(NSTableView *)tableView sortDescriptorsDidChange:(NSArray<NSSortDescriptor *> *)oldDescriptors {
[self.data sortUsingDescriptors:tableView.sortDescriptors];
[tableView setNeedsDisplay];
}
// table view data source
- (void)tableView:(NSTableView *)tableView setObjectValue:(nullable id)object forTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row {
NSInteger idx = [[tableView selectedCell] indexOfSelectedItem];
[self.data[row] setDefault:idx];
}
// combo box data source
- (NSInteger)numberOfItemsInComboBoxCell:(NSComboBoxCell *)comboBoxCell {
Scheme *s = self.data[self.table.selectedRow];
comboBoxCell.representedObject = s.available;
return s.available.count;
}
// combo box data source
- (id)comboBoxCell:(NSComboBoxCell *)comboBoxCell objectValueForItemAtIndex:(NSInteger)index {
NSArray<AppId*> *apps = comboBoxCell.representedObject;
return apps[index].name;
}
@end
// Rebuild Launch Services cache
// https://eclecticlight.co/2017/08/11/launch-services-database-problems-correcting-and-rebuilding/
// /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -kill -r -v -apps u

BIN
source/AppIcon.icns Normal file

Binary file not shown.

34
source/Info.plist Normal file
View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleVersion</key>
<string>189</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019 relikd. Public Domain.</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

184
source/MainMenu.xib Normal file
View File

@@ -0,0 +1,184 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate">
<connections>
<outlet property="table" destination="VIj-4u-Ayb" id="zxl-IS-Ykc"/>
<outlet property="window" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
</connections>
</customObject>
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="URL Scheme Defaults" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="URL Scheme Defaults" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="About URL Scheme Defaults" id="5kV-Vb-QxS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
<menuItem title="Services" id="NMo-om-nkz">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
<menuItem title="Hide URL Scheme Defaults" keyEquivalent="h" id="Olw-nP-bQN">
<connections>
<action selector="hide:" target="-1" id="PnN-Uc-m68"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
</connections>
</menuItem>
<menuItem title="Show All" id="Kd2-mp-pUS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
<menuItem title="Quit URL Scheme Defaults" keyEquivalent="q" id="4sb-4s-VLi">
<connections>
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Window" id="aUF-d1-5bR">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
<items>
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
<connections>
<action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
</connections>
</menuItem>
<menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG">
<connections>
<action selector="performClose:" target="-1" id="HmO-Ls-i7Q"/>
</connections>
</menuItem>
<menuItem title="Zoom" id="R4o-n2-Eq4">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
<point key="canvasLocation" x="-67" y="-341"/>
</menu>
<window title="URL Scheme Defaults" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" animationBehavior="default" tabbingMode="disallowed" id="QvC-M9-y7g">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowCollectionBehavior key="collectionBehavior" fullScreenNone="YES" fullScreenDisallowsTiling="YES"/>
<rect key="contentRect" x="0.0" y="0.0" width="400" height="200"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="878"/>
<value key="minSize" type="size" width="320" height="200"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="400" height="200"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<scrollView fixedFrame="YES" autohidesScrollers="YES" horizontalLineScroll="20" horizontalPageScroll="10" verticalLineScroll="20" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="AWT-SO-eMg">
<rect key="frame" x="-1" y="21" width="402" height="180"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" copiesOnScroll="NO" id="Jyi-ee-N8g">
<rect key="frame" x="1" y="0.0" width="400" height="179"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" selectionHighlightStyle="sourceList" columnReordering="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="19" headerView="d5x-hJ-iGB" id="VIj-4u-Ayb">
<rect key="frame" x="0.0" y="0.0" width="400" height="156"/>
<autoresizingMask key="autoresizingMask"/>
<size key="intercellSpacing" width="10" height="1"/>
<color key="backgroundColor" name="_sourceListBackgroundColor" catalog="System" colorSpace="catalog"/>
<tableViewGridLines key="gridStyleMask" vertical="YES"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn identifier="colScheme" editable="NO" width="135" minWidth="60" maxWidth="10000" id="xAk-fJ-vg3">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="center" title="URL Scheme">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingMiddle" selectable="YES" allowsUndo="NO" alignment="right" title="Text Cell" usesSingleLineMode="YES" id="Chb-uW-rMg">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<sortDescriptor key="sortDescriptorPrototype" selector="compare:" sortKey="name"/>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
<tableColumn identifier="colApp" width="245" minWidth="100" maxWidth="10000" id="e5d-gJ-zc9">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="center" title="Application">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<comboBoxCell key="dataCell" lineBreakMode="truncatingTail" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="left" usesSingleLineMode="YES" buttonBordered="NO" completes="NO" usesDataSource="YES" numberOfVisibleItems="400" id="PSB-WY-9EH">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<connections>
<outlet property="dataSource" destination="Voe-Tx-rLC" id="yYb-j9-gSe"/>
</connections>
</comboBoxCell>
<sortDescriptor key="sortDescriptorPrototype" selector="compare:" sortKey="registered.name"/>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
</tableColumns>
<connections>
<outlet property="dataSource" destination="Voe-Tx-rLC" id="1RG-C6-RKI"/>
</connections>
</tableView>
</subviews>
<nil key="backgroundColor"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="QVc-od-33H">
<rect key="frame" x="1" y="163" width="400" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="JXh-pC-WcA">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<tableHeaderView key="headerView" id="d5x-hJ-iGB">
<rect key="frame" x="0.0" y="0.0" width="400" height="23"/>
<autoresizingMask key="autoresizingMask"/>
</tableHeaderView>
</scrollView>
</subviews>
</view>
<contentBorderThickness minY="22"/>
<point key="canvasLocation" x="-2" y="-136"/>
</window>
</objects>
</document>

5
source/main.m Normal file
View File

@@ -0,0 +1,5 @@
@import AppKit.NSApplication;
int main(int argc, const char * argv[]) {
return NSApplicationMain(argc, argv);
}