diff --git a/CHANGELOG.md b/CHANGELOG.md
index 49dd993..8e0042d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
- *Settings, Feeds:* Drag & Drop feeds from / to OPML file
- *Settings, Feeds:* Drag & Drop feed titles and urls as text
- *Settings, Feeds:* OPML export with selected items only
+- *Settings, General*: [Auxiliary application](https://github.com/relikd/URL-Scheme-Defaults) for changing default feed reader
- *UI:* Accessibility hints for most UI elements
- *UI*: Show welcome message upon first usage (empty db)
- Welcome message also adds Github releases feed
@@ -46,6 +47,7 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
- *Settings, Feeds:* Single add button for feeds, groups, and separators
- *Settings, Feeds:* Always append new items at the end
- *Settings, General*: Moved `Fix cache` button to `About` text section
+- *Settings, General*: Changing default feed reader is prohibited within sandbox
- *Status Bar Menu*: Show `(no title)` instead of `(error)`
- *Status Bar Menu*: `Update all feeds` will show error alerts for broken URLs
- *UI:* Interface builder files replaced with code equivalent
diff --git a/baRSS/Constants.h b/baRSS/Constants.h
index e0665bf..24bd08d 100644
--- a/baRSS/Constants.h
+++ b/baRSS/Constants.h
@@ -34,6 +34,8 @@
static NSPasteboardType const UTI_OPML = @"org.opml";
/// URL with newest baRSS releases. Automatically added when user starts baRSS for the first time.
static NSString* const versionUpdateURL = @"https://github.com/relikd/baRSS/releases.atom";
+/// URL to help page of auxiliary application "URL Scheme Defaults"
+static NSString* const auxiliaryAppURL = @"https://github.com/relikd/URL-Scheme-Defaults#url-scheme-defaults";
#pragma mark - NSImageName constants
diff --git a/baRSS/Helper/NSView+Ext.h b/baRSS/Helper/NSView+Ext.h
index 3909fa5..29696c1 100644
--- a/baRSS/Helper/NSView+Ext.h
+++ b/baRSS/Helper/NSView+Ext.h
@@ -61,6 +61,7 @@ NS_INLINE CGFloat NSMaxWidth(NSView *a, NSView *b) { return Max(NSWidth(a.frame)
+ (NSButton*)button:(NSString*)text;
+ (NSButton*)buttonImageSquare:(nonnull NSImageName)name;
+ (NSButton*)buttonIcon:(nonnull NSImageName)name size:(CGFloat)size;
++ (NSButton*)helpButton;
+ (NSButton*)inlineButton:(NSString*)text;
+ (NSPopUpButton*)popupButton:(CGFloat)w;
// UI: Others
diff --git a/baRSS/Helper/NSView+Ext.m b/baRSS/Helper/NSView+Ext.m
index 41acbca..3c76769 100644
--- a/baRSS/Helper/NSView+Ext.m
+++ b/baRSS/Helper/NSView+Ext.m
@@ -94,6 +94,14 @@
return btn;
}
+/// Create round button with question mark. @c 21x21px
++ (NSButton*)helpButton {
+ NSButton *btn = [[NSButton alloc] initWithFrame: NSMakeRect(0, 0, 21, 21)];
+ btn.bezelStyle = NSBezelStyleHelpButton;
+ btn.title = @"";
+ return btn;
+}
+
/// Create gray inline button with rounded corners. @c 16px height.
+ (NSButton*)inlineButton:(NSString*)text {
NSButton *btn = [[NSButton alloc] initWithFrame: NSMakeRect(0, 0, 0, HEIGHT_INLINEBUTTON)];
diff --git a/baRSS/Info.plist b/baRSS/Info.plist
index 4a513b7..8815b40 100644
--- a/baRSS/Info.plist
+++ b/baRSS/Info.plist
@@ -70,7 +70,7 @@
CFBundleVersion
- 11159
+ 11387
LSApplicationCategoryType
public.app-category.news
LSMinimumSystemVersion
diff --git a/baRSS/Preferences/General Tab/SettingsGeneral.h b/baRSS/Preferences/General Tab/SettingsGeneral.h
index d264578..b17f7db 100644
--- a/baRSS/Preferences/General Tab/SettingsGeneral.h
+++ b/baRSS/Preferences/General Tab/SettingsGeneral.h
@@ -24,5 +24,5 @@
@interface SettingsGeneral : NSViewController
- (void)changeHttpApplication:(NSPopUpButton *)sender;
-- (void)changeDefaultRSSReader:(NSPopUpButton *)sender;
+- (void)clickHowToDefaults:(NSButton *)sender;
@end
diff --git a/baRSS/Preferences/General Tab/SettingsGeneral.m b/baRSS/Preferences/General Tab/SettingsGeneral.m
index 97f5a70..dbfee87 100644
--- a/baRSS/Preferences/General Tab/SettingsGeneral.m
+++ b/baRSS/Preferences/General Tab/SettingsGeneral.m
@@ -36,102 +36,51 @@
- (void)loadView {
self.view = [[SettingsGeneralView alloc] initWithController:self];
// Default http application for opening the feed urls
- [self generateMenuForPopup:self.view.popupHttpApplication withScheme:@"https"];
- [self.view.popupHttpApplication insertItemWithTitle:NSLocalizedString(@"System Default", @"Default web browser application") atIndex:0];
- [self selectBundleID:[UserPrefs getHttpApplication] inPopup:self.view.popupHttpApplication];
+ NSPopUpButton *pop = self.view.popupHttpApplication;
+ [pop removeAllItems];
+ [pop addItemWithTitle:NSLocalizedString(@"System Default", @"Default web browser application")];
+ NSArray *browsers = CFBridgingRelease(LSCopyAllHandlersForURLScheme(CFSTR("https")));
+ for (NSString *bundleID in browsers) {
+ [pop addItemWithTitle: [self applicationNameForBundleId:bundleID]];
+ pop.lastItem.representedObject = bundleID;
+ }
+ [pop selectItemAtIndex:[pop indexOfItemWithRepresentedObject:[UserPrefs getHttpApplication]]];
// Default RSS Reader application
- [self generateMenuForPopup:self.view.popupDefaultRSSReader withScheme:@"feed"];
- [self selectBundleID:[self defaultBundleIdForScheme:@"feed"] inPopup:self.view.popupDefaultRSSReader];
+ NSString *feedBundleId = CFBridgingRelease(LSCopyDefaultHandlerForURLScheme(CFSTR("feed")));
+ self.view.defaultReader.objectValue = [self applicationNameForBundleId:feedBundleId];
}
-#pragma mark - UI interaction with IBAction
+/// Get human readable application name such as 'Safari' or 'baRSS'
+- (nonnull NSString*)applicationNameForBundleId:(nonnull NSString*)bundleID {
+ NSString *name;
+ NSArray *urls = CFBridgingRelease(LSCopyApplicationURLsForBundleIdentifier((__bridge CFStringRef)bundleID, NULL));
+ if (urls.count > 0) {
+ NSDictionary *info = CFBridgingRelease(CFBundleCopyInfoDictionaryForURL((CFURLRef)urls.firstObject));
+ name = info[(NSString*)kCFBundleExecutableKey];
+ }
+ return name ? name : bundleID;
+}
+#pragma mark - User interaction
+
+// Callback method fired when user selects a different item from popup list
- (void)changeHttpApplication:(NSPopUpButton *)sender {
[UserPrefs setHttpApplication:sender.selectedItem.representedObject];
}
-- (void)changeDefaultRSSReader:(NSPopUpButton *)sender {
- if ([self setDefaultRSSApplication:sender.selectedItem.representedObject] == NO) {
- // in case anything went wrong, restore previous selection
- [self selectBundleID:[self defaultBundleIdForScheme:@"feed"] inPopup:sender];
- }
+// Callback method from round help button right of default feed reader text
+- (void)clickHowToDefaults:(NSButton *)sender {
+ NSAlert *alert = [[NSAlert alloc] init];
+ alert.alertStyle = NSAlertStyleInformational;
+ alert.messageText = NSLocalizedString(@"How to change default feed reader", nil);
+ alert.informativeText = NSLocalizedString(@"Unfortunately sandboxed applications are not allowed to change the default application. However, there is an auxiliary application.\n\nFollow the instructions to change the 'feed:' scheme.", nil);
+ [alert addButtonWithTitle:NSLocalizedString(@"Close", nil)];
+ [alert addButtonWithTitle:NSLocalizedString(@"Go to download page", nil)].toolTip = auxiliaryAppURL;
+ [alert beginSheetModalForWindow:self.view.window completionHandler:^(NSModalResponse returnCode) {
+ if (returnCode == NSAlertSecondButtonReturn) {
+ [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:auxiliaryAppURL]];
+ }
+ }];
}
-#pragma mark - Helper methods
-
-/**
- Populate @c NSPopUpButton menu with all available application for that scheme.
-
- @param scheme URL scheme like @c 'feed' or @c 'https'
- */
-- (void)generateMenuForPopup:(NSPopUpButton*)popup withScheme:(NSString*)scheme {
- [popup removeAllItems];
- NSArray *apps = [self listOfBundleIdsForScheme:scheme];
- for (NSString *bundleID in apps) {
- NSString *appName = [self applicationNameForBundleId:bundleID];
- if (!appName)
- appName = bundleID;
- [popup addItemWithTitle:appName];
- popup.lastItem.representedObject = bundleID;
- }
-}
-
-/**
- For a given @c NSPopUpButton select the item which represents the @c bundleID.
- */
-- (void)selectBundleID:(NSString*)bundleID inPopup:(NSPopUpButton*)popup {
- [popup selectItemAtIndex:[popup indexOfItemWithRepresentedObject:bundleID]];
-}
-
-/**
- Get human readable, application name from @c bundleID.
-
- @param bundleID as defined in @c Info.plist
- @return Application name such as 'Safari' or 'baRSS'
- */
-- (NSString*)applicationNameForBundleId:(NSString*)bundleID {
- NSArray *urls = CFBridgingRelease(LSCopyApplicationURLsForBundleIdentifier((__bridge CFStringRef)bundleID, NULL));
- if (urls.count > 0) {
- NSDictionary *info = CFBridgingRelease(CFBundleCopyInfoDictionaryForURL((CFURLRef)urls.firstObject));
- return info[(NSString*)kCFBundleExecutableKey];
- }
- return nil;
-}
-
-/**
- Get a list of all installed applications supporting that URL scheme.
-
- @param scheme URL scheme like @c 'feed' or @c 'https'
- @return Array of @c bundleIDs of installed applications supporting that url scheme.
- */
-- (NSArray*)listOfBundleIdsForScheme:(NSString*)scheme {
- return CFBridgingRelease(LSCopyAllHandlersForURLScheme((__bridge CFStringRef _Nonnull)(scheme)));
-}
-
-/**
- Get current default application for provided URL scheme. (e.g., )
-
- @param scheme URL scheme like @c 'feed' or @c 'https'
- @return @c bundleID of default application
- */
-- (NSString*)defaultBundleIdForScheme:(NSString*)scheme {
- return CFBridgingRelease(LSCopyDefaultHandlerForURLScheme((__bridge CFStringRef _Nonnull)(scheme)));
-}
-
-/**
- Sets the default application for @c feed:// urls. (system wide)
-
- @param bundleID as defined in @c Info.plist
- @return Return @c YES if operation was successfull. @c NO otherwise.
- */
-- (BOOL)setDefaultRSSApplication:(NSString*)bundleID {
- // TODO: Does not work with sandboxing.
- OSStatus s = LSSetDefaultHandlerForURLScheme(CFSTR("feed"), (__bridge CFStringRef _Nonnull)(bundleID));
- return s == 0;
-}
-
-// 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
-
@end
diff --git a/baRSS/Preferences/General Tab/SettingsGeneralView.h b/baRSS/Preferences/General Tab/SettingsGeneralView.h
index 13c975b..9c8258c 100644
--- a/baRSS/Preferences/General Tab/SettingsGeneralView.h
+++ b/baRSS/Preferences/General Tab/SettingsGeneralView.h
@@ -25,7 +25,7 @@
@interface SettingsGeneralView : NSView
@property (weak) IBOutlet NSPopUpButton* popupHttpApplication;
-@property (weak) IBOutlet NSPopUpButton* popupDefaultRSSReader;
+@property (weak) IBOutlet NSTextField *defaultReader;
- (instancetype)initWithController:(SettingsGeneral*)controller NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithFrame:(NSRect)frameRect NS_UNAVAILABLE;
diff --git a/baRSS/Preferences/General Tab/SettingsGeneralView.m b/baRSS/Preferences/General Tab/SettingsGeneralView.m
index de98898..d8ce93c 100644
--- a/baRSS/Preferences/General Tab/SettingsGeneralView.m
+++ b/baRSS/Preferences/General Tab/SettingsGeneralView.m
@@ -28,20 +28,16 @@
- (instancetype)initWithController:(SettingsGeneral*)controller {
self = [super initWithFrame:NSZeroRect];
-
- NSArray *lbls = @[NSLocalizedString(@"Open URLs with:", nil),
- NSLocalizedString(@"Default RSS Reader:", nil)];
- NSView *labels = [[NSView labelColumn:lbls rowHeight:HEIGHT_POPUP padding:PAD_M] placeIn:self x:PAD_WIN yTop:PAD_WIN];
- CGFloat x = NSMaxX(labels.frame) + PAD_S;
-
- self.popupHttpApplication = [[self createPopup:x top: PAD_WIN + 1] action:@selector(changeHttpApplication:) target:controller];
- self.popupDefaultRSSReader = [[self createPopup:x top: YFromTop(self.popupHttpApplication) + PAD_M] action:@selector(changeDefaultRSSReader:) target:controller];
+ // Change default feed reader application
+ NSTextField *l1 = [[NSView label:NSLocalizedString(@"Default feed reader:", nil)] placeIn:self x:PAD_WIN yTop:PAD_WIN + 3];
+ NSButton *help = [[[NSView helpButton] action:@selector(clickHowToDefaults:) target:controller] placeIn:self xRight:PAD_WIN yTop:PAD_WIN];
+ self.defaultReader = [[[[NSView label:@""] bold] placeIn:self x:NSMaxX(l1.frame) + PAD_S yTop:PAD_WIN + 3] sizeToRight:NSWidth(help.frame) + PAD_WIN];
+ // Popup button 'Open URLs with:'
+ CGFloat y = YFromTop(help) + PAD_M;
+ NSTextField *l2 = [[NSView label:NSLocalizedString(@"Open URLs with:", nil)] placeIn:self x:PAD_WIN yTop:y + 1];
+ self.popupHttpApplication = [[[[NSView popupButton:0] placeIn:self x:NSMaxX(l2.frame) + PAD_S yTop:y] sizeToRight:PAD_WIN]
+ action:@selector(changeHttpApplication:) target:controller];
return self;
}
-/// Helper method to create sizable popup button
-- (NSPopUpButton*)createPopup:(CGFloat)x top:(CGFloat)y {
- return [[[NSView popupButton:0] placeIn:self x:x yTop:y] sizeToRight:PAD_WIN];
-}
-
@end