├── .gitignore ├── Configuration.xcconfig ├── DTIPreferences ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon_128x128.png │ │ ├── icon_128x128@2x.png │ │ ├── icon_16x16.png │ │ ├── icon_16x16@2x.png │ │ ├── icon_256x256.png │ │ ├── icon_256x256@2x.png │ │ ├── icon_32x32.png │ │ ├── icon_32x32@2x.png │ │ ├── icon_512x512.png │ │ └── icon_512x512@2x.png │ └── Contents.json ├── Base.lproj │ └── Main.storyboard ├── Credits.rtf ├── DTIAppDelegate.h ├── DTIAppDelegate.m ├── DTIBatteryPreferencesViewController │ ├── DTIBatteryPreferencesViewController.h │ ├── DTIBatteryPreferencesViewController.m │ ├── DTIBatteryPreferencesViewController.xib │ ├── DTIFontLocalizedDisplayNameValueTransformer.h │ └── DTIFontLocalizedDisplayNameValueTransformer.m ├── DTIDateTimePreferencesViewController │ ├── DTIDateTimePreferencesViewController.h │ ├── DTIDateTimePreferencesViewController.m │ └── DTIDateTimePreferencesViewController.xib ├── DTILayoutPreferencesViewController │ ├── DTILayoutPreferencesViewController.h │ ├── DTILayoutPreferencesViewController.m │ └── DTILayoutPreferencesViewController.xib ├── DTIPreferences.entitlements ├── DTIPreferencesContentViewController │ ├── DTIPreferencesContentViewController.h │ ├── DTIPreferencesContentViewController.m │ └── DTIPreferencesContentViewControllers.h ├── DTIPreferencesViewController │ ├── DTIPreferenceItem.h │ ├── DTIPreferenceItem.m │ ├── DTIPreferencesViewController.h │ ├── DTIPreferencesViewController.m │ ├── DTIPreferencesWindowController.h │ └── DTIPreferencesWindowController.m ├── Extensions │ ├── NSFont+DTILocalizedDisplayName.h │ └── NSFont+DTILocalizedDisplayName.m ├── Info.plist └── main.m ├── DesktopTime.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ └── DesktopTimeKit.xcscheme ├── DesktopTime.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── DesktopTime ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon_128x128.png │ │ ├── icon_128x128@2x.png │ │ ├── icon_16x16.png │ │ ├── icon_16x16@2x.png │ │ ├── icon_256x256.png │ │ ├── icon_256x256@2x.png │ │ ├── icon_32x32.png │ │ ├── icon_32x32@2x.png │ │ ├── icon_512x512.png │ │ └── icon_512x512@2x.png │ └── Contents.json ├── Base.lproj │ └── MainMenu.xib ├── DTIAppController.h ├── DTIAppController.m ├── DTIDefines.h ├── DTILayoutController │ ├── DTILayoutController.h │ └── DTILayoutController.m ├── DTILayoutWindowController │ ├── DTILayoutElementDataSource.h │ ├── DTILayoutElementDataSource.m │ ├── DTILayoutWindowController.h │ ├── DTILayoutWindowController.m │ └── DTILayoutWindowController.xib ├── DesktopTime.entitlements ├── Info.plist ├── de.lproj │ └── MainMenu.strings ├── fr.lproj │ └── MainMenu.strings └── main.m ├── DesktopTimeKit ├── DTIAppGroup │ ├── DTIAppGroup.h │ └── DTIAppGroup.m ├── DTIBatteryStatus │ ├── DTIBatteryStatus.h │ └── DTIBatteryStatus.m ├── DTILayout │ ├── DTILayout.h │ ├── DTILayout.m │ ├── DTILayoutElement.h │ ├── DTILayoutElement.m │ └── Extensions │ │ ├── NSDateFormatter+DTILayoutElement.h │ │ └── NSDateFormatter+DTILayoutElement.m ├── DTINotificationCenter │ ├── DTINotificationCenter.h │ └── DTINotificationCenter.m ├── DTIPreferences │ ├── DTIDefaultsProvider.h │ ├── DTIPreferences.h │ ├── DTIPreferences.m │ └── Extensions │ │ ├── NSAlert+DTIPreferences.h │ │ ├── NSAlert+DTIPreferences.m │ │ ├── NSURL+DTIPreferences.h │ │ ├── NSURL+DTIPreferences.m │ │ ├── NSWorkspace+DTIPreferences.h │ │ └── NSWorkspace+DTIPreferences.m ├── DTIWorkspaceMetrics │ ├── DTIWorkspaceMetrics.h │ ├── DTIWorkspaceMetrics.m │ └── Extensions │ │ ├── NSScreen+DTIWorkspaceMetrics.h │ │ └── NSScreen+DTIWorkspaceMetrics.m ├── DesktopTimeKit.h └── Info.plist ├── DesktopTimeKitTests ├── DesktopTimeKitTests.m └── Info.plist ├── Extras ├── DesktopTime Icon.sketch └── Screenshot@2x.jpg ├── Icon.png ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | xcuserdata 3 | build 4 | -------------------------------------------------------------------------------- /Configuration.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Configuration.xcconfig 3 | // DesktopTime 4 | // 5 | // Created by Marcel Dierkes on 31.04.18. 6 | // Copyright (c) 2018 Marcel Dierkes. All rights reserved. 7 | // 8 | 9 | MACOSX_DEPLOYMENT_TARGET = 12.0 10 | PRODUCT_BUNDLE_IDENTIFIER = info.marcel-dierkes.DesktopTime 11 | 12 | MARKETING_VERSION = 2.1.0 13 | CURRENT_PROJECT_VERSION = 2010001 14 | 15 | DTI_COPYRIGHT_TEXT = Copyright © 2015 – 2023 Marcel Dierkes. All rights reserved. 16 | -------------------------------------------------------------------------------- /DTIPreferences/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.525", 9 | "green" : "0.569", 10 | "red" : "0.298" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /DTIPreferences/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon_16x16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "icon_16x16@2x.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "icon_32x32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "icon_32x32@2x.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "icon_128x128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "icon_128x128@2x.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "icon_256x256.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "icon_256x256@2x.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "icon_512x512.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "icon_512x512@2x.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /DTIPreferences/Assets.xcassets/AppIcon.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newmarcel/DesktopTime/5146b11b21624bcd482abccce0925952abe2f16f/DTIPreferences/Assets.xcassets/AppIcon.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /DTIPreferences/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newmarcel/DesktopTime/5146b11b21624bcd482abccce0925952abe2f16f/DTIPreferences/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /DTIPreferences/Assets.xcassets/AppIcon.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newmarcel/DesktopTime/5146b11b21624bcd482abccce0925952abe2f16f/DTIPreferences/Assets.xcassets/AppIcon.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /DTIPreferences/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newmarcel/DesktopTime/5146b11b21624bcd482abccce0925952abe2f16f/DTIPreferences/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /DTIPreferences/Assets.xcassets/AppIcon.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newmarcel/DesktopTime/5146b11b21624bcd482abccce0925952abe2f16f/DTIPreferences/Assets.xcassets/AppIcon.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /DTIPreferences/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newmarcel/DesktopTime/5146b11b21624bcd482abccce0925952abe2f16f/DTIPreferences/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /DTIPreferences/Assets.xcassets/AppIcon.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newmarcel/DesktopTime/5146b11b21624bcd482abccce0925952abe2f16f/DTIPreferences/Assets.xcassets/AppIcon.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /DTIPreferences/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newmarcel/DesktopTime/5146b11b21624bcd482abccce0925952abe2f16f/DTIPreferences/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /DTIPreferences/Assets.xcassets/AppIcon.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newmarcel/DesktopTime/5146b11b21624bcd482abccce0925952abe2f16f/DTIPreferences/Assets.xcassets/AppIcon.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /DTIPreferences/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newmarcel/DesktopTime/5146b11b21624bcd482abccce0925952abe2f16f/DTIPreferences/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /DTIPreferences/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DTIPreferences/Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf2580 2 | \cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica-Bold;\f1\fswiss\fcharset0 Helvetica;\f2\fswiss\fcharset0 Helvetica-Oblique; 3 | } 4 | {\colortbl;\red255\green255\blue255;} 5 | {\*\expandedcolortbl;;} 6 | \margl1440\margr1440\vieww9000\viewh8400\viewkind0 7 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 8 | 9 | \f0\b\fs24 \cf0 \ 10 | \ 11 | \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0 12 | \cf0 DesktopTime\ 13 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 14 | 15 | \f1\b0 \cf0 Displays the current time and date on your desktop\ 16 | \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0 17 | \cf0 \ 18 | \ 19 | \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0 20 | 21 | \f0\b \cf0 Creator 22 | \f1\b0 \ 23 | Marcel Dierkes\ 24 | \ 25 | \ 26 | \ 27 | \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0 28 | 29 | \f2\i \cf0 DesktopTime was created in a very difficult time of my life when I prioritized work over my own health. This little project will always remind me that I was lucky and got a second chance. Nothing is more valuable than the health of the people you love and your own. 30 | \f1\i0 \ 31 | } -------------------------------------------------------------------------------- /DTIPreferences/DTIAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTIAppDelegate.h 3 | // DesktopTime Preferences 4 | // 5 | // Created by Marcel Dierkes on 29.01.21. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface DTIAppDelegate : NSObject 13 | @end 14 | 15 | NS_ASSUME_NONNULL_END 16 | -------------------------------------------------------------------------------- /DTIPreferences/DTIAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTIAppDelegate.m 3 | // DesktopTime Preferences 4 | // 5 | // Created by Marcel Dierkes on 29.01.21. 6 | // 7 | 8 | #import "DTIAppDelegate.h" 9 | #import "DTIDefines.h" 10 | 11 | @interface DTIAppDelegate () 12 | @end 13 | 14 | @implementation DTIAppDelegate 15 | 16 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification 17 | { 18 | } 19 | 20 | - (void)applicationWillTerminate:(NSNotification *)aNotification 21 | { 22 | } 23 | 24 | - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender 25 | { 26 | return YES; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /DTIPreferences/DTIBatteryPreferencesViewController/DTIBatteryPreferencesViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTIBatteryPreferencesViewController.h 3 | // DesktopTime Preferences 4 | // 5 | // Created by Marcel Dierkes on 30.01.21. 6 | // 7 | 8 | #import 9 | #import "DTIPreferencesContentViewController.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface DTIBatteryPreferencesViewController : DTIPreferencesContentViewController 14 | @property (weak, nonatomic, nullable) IBOutlet NSTextField *fontTextField; 15 | 16 | - (IBAction)resetDefaultValues:(id)sender; 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /DTIPreferences/DTIBatteryPreferencesViewController/DTIBatteryPreferencesViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTIBatteryPreferencesViewController.m 3 | // DesktopTime Preferences 4 | // 5 | // Created by Marcel Dierkes on 30.01.21. 6 | // 7 | 8 | #import "DTIBatteryPreferencesViewController.h" 9 | #import "DTIDefines.h" 10 | #import "DTIPreferenceItem.h" 11 | 12 | #define DTI_L10N_BATTERY_LEVEL NSLocalizedString(@"Battery Level", @"Battery Level") 13 | 14 | @interface DTIBatteryPreferencesViewController () 15 | @property (nonatomic) NSFont *textFont; 16 | @property (nonatomic) NSColor *textColor; 17 | @property (nonatomic) NSColor *shadowColor; 18 | @end 19 | 20 | @implementation DTIBatteryPreferencesViewController 21 | 22 | + (DTIPreferenceItem *)preferenceItem 23 | { 24 | Auto item = [[DTIPreferenceItem alloc] initWithUUID:[NSUUID UUID] 25 | name:DTI_L10N_BATTERY_LEVEL]; 26 | item.image = [NSImage imageWithSystemSymbolName:@"battery.100.bolt" 27 | accessibilityDescription:nil]; 28 | item.viewControllerClass = [self class]; 29 | return item; 30 | } 31 | 32 | - (NSString *)preferredTitle 33 | { 34 | return DTI_L10N_BATTERY_LEVEL; 35 | } 36 | 37 | - (void)viewWillAppear 38 | { 39 | [super viewWillAppear]; 40 | 41 | NSColorPanel.sharedColorPanel.showsAlpha = YES; 42 | 43 | Auto fontManager = NSFontManager.sharedFontManager; 44 | fontManager.target = self; 45 | 46 | Auto font = self.textFont; 47 | [fontManager setSelectedFont:font isMultiple:NO]; 48 | } 49 | 50 | - (void)viewWillDisappear 51 | { 52 | [super viewWillDisappear]; 53 | 54 | Auto fontManager = NSFontManager.sharedFontManager; 55 | if(fontManager.target == self) 56 | { 57 | fontManager.target = nil; 58 | } 59 | } 60 | 61 | #pragma mark - Actions 62 | 63 | - (void)resetDefaultValues:(id)sender 64 | { 65 | self.textFont = nil; 66 | self.textColor = nil; 67 | self.shadowColor = nil; 68 | } 69 | 70 | #pragma mark - Fonts & Colors 71 | 72 | - (NSFont *)textFont 73 | { 74 | return DTIPreferences.sharedPreferences.batteryLevelFont; 75 | } 76 | 77 | - (void)setTextFont:(NSFont *)textFont 78 | { 79 | [self willChangeValueForKey:@"textFont"]; 80 | DTIPreferences.sharedPreferences.batteryLevelFont = textFont; 81 | [self didChangeValueForKey:@"textFont"]; 82 | } 83 | 84 | - (NSColor *)textColor 85 | { 86 | return DTIPreferences.sharedPreferences.batteryLevelTextColor; 87 | } 88 | 89 | - (void)setTextColor:(NSColor *)textColor 90 | { 91 | DTIPreferences.sharedPreferences.batteryLevelTextColor = textColor; 92 | } 93 | 94 | - (NSColor *)shadowColor 95 | { 96 | return DTIPreferences.sharedPreferences.batteryLevelShadowColor; 97 | } 98 | 99 | - (void)setShadowColor:(NSColor *)shadowColor 100 | { 101 | DTIPreferences.sharedPreferences.batteryLevelShadowColor = shadowColor; 102 | } 103 | 104 | #pragma mark - NSFontChanging 105 | 106 | - (void)changeFont:(NSFontManager *)fontManager 107 | { 108 | self.textFont = [fontManager convertFont:self.textFont]; 109 | } 110 | 111 | - (NSFontPanelModeMask)validModesForFontPanel:(NSFontPanel *)fontPanel 112 | { 113 | return NSFontPanelModeMaskCollection 114 | | NSFontPanelModeMaskFace 115 | | NSFontPanelModeMaskSize; 116 | } 117 | 118 | @end 119 | -------------------------------------------------------------------------------- /DTIPreferences/DTIBatteryPreferencesViewController/DTIBatteryPreferencesViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | DTIFontLocalizedDisplayNameValueTransformer 53 | 54 | 55 | 56 | 57 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /DTIPreferences/DTIBatteryPreferencesViewController/DTIFontLocalizedDisplayNameValueTransformer.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTIFontLocalizedDisplayNameValueTransformer.h 3 | // DesktopTime Preferences 4 | // 5 | // Created by Marcel Dierkes on 02.02.21. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface DTIFontLocalizedDisplayNameValueTransformer : NSValueTransformer 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /DTIPreferences/DTIBatteryPreferencesViewController/DTIFontLocalizedDisplayNameValueTransformer.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTIFontLocalizedDisplayNameValueTransformer.m 3 | // DesktopTime Preferences 4 | // 5 | // Created by Marcel Dierkes on 02.02.21. 6 | // 7 | 8 | #import "DTIFontLocalizedDisplayNameValueTransformer.h" 9 | #import "NSFont+DTILocalizedDisplayName.h" 10 | 11 | @implementation DTIFontLocalizedDisplayNameValueTransformer 12 | 13 | + (Class)transformedValueClass 14 | { 15 | return [NSString class]; 16 | } 17 | 18 | + (BOOL)allowsReverseTransformation 19 | { 20 | return NO; 21 | } 22 | 23 | - (id)transformedValue:(id)value 24 | { 25 | if(![value isKindOfClass:[NSFont class]]) { return [value description]; } 26 | return ((NSFont *)value).dti_localizedDisplayName; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /DTIPreferences/DTIDateTimePreferencesViewController/DTIDateTimePreferencesViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTIDateTimePreferencesViewController.h 3 | // DesktopTime Preferences 4 | // 5 | // Created by Marcel Dierkes on 30.01.21. 6 | // 7 | 8 | #import 9 | #import "DTIPreferencesContentViewController.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface DTIDateTimePreferencesViewController : DTIPreferencesContentViewController 14 | @property (weak, nonatomic, nullable) IBOutlet NSTextField *fontTextField; 15 | 16 | - (IBAction)resetDefaultValues:(id)sender; 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /DTIPreferences/DTIDateTimePreferencesViewController/DTIDateTimePreferencesViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTIDateTimePreferencesViewController.m 3 | // DesktopTime Preferences 4 | // 5 | // Created by Marcel Dierkes on 30.01.21. 6 | // 7 | 8 | #import "DTIDateTimePreferencesViewController.h" 9 | #import "DTIDefines.h" 10 | #import "DTIPreferenceItem.h" 11 | 12 | #define DTI_L10N_DATE_TIME NSLocalizedString(@"Date & Time", @"Date & Time") 13 | 14 | @interface DTIDateTimePreferencesViewController () 15 | @property (nonatomic) NSFont *textFont; 16 | @property (nonatomic) NSColor *textColor; 17 | @property (nonatomic) NSColor *shadowColor; 18 | @end 19 | 20 | @implementation DTIDateTimePreferencesViewController 21 | 22 | + (DTIPreferenceItem *)preferenceItem 23 | { 24 | Auto item = [[DTIPreferenceItem alloc] initWithUUID:[NSUUID UUID] 25 | name:DTI_L10N_DATE_TIME]; 26 | item.image = [NSImage imageWithSystemSymbolName:@"calendar.badge.clock" 27 | accessibilityDescription:nil]; 28 | item.viewControllerClass = [self class]; 29 | return item; 30 | } 31 | 32 | - (NSString *)preferredTitle 33 | { 34 | return DTI_L10N_DATE_TIME; 35 | } 36 | 37 | - (void)viewWillAppear 38 | { 39 | [super viewWillAppear]; 40 | 41 | NSColorPanel.sharedColorPanel.showsAlpha = YES; 42 | 43 | Auto fontManager = NSFontManager.sharedFontManager; 44 | fontManager.target = self; 45 | 46 | Auto font = self.textFont; 47 | [fontManager setSelectedFont:font isMultiple:NO]; 48 | } 49 | 50 | - (void)viewWillDisappear 51 | { 52 | [super viewWillDisappear]; 53 | 54 | Auto fontManager = NSFontManager.sharedFontManager; 55 | if(fontManager.target == self) 56 | { 57 | fontManager.target = nil; 58 | } 59 | } 60 | 61 | #pragma mark - Actions 62 | 63 | - (void)resetDefaultValues:(id)sender 64 | { 65 | self.textFont = nil; 66 | self.textColor = nil; 67 | self.shadowColor = nil; 68 | } 69 | 70 | #pragma mark - Fonts & Colors 71 | 72 | - (NSFont *)textFont 73 | { 74 | return DTIPreferences.sharedPreferences.dateTimeFont; 75 | } 76 | 77 | - (void)setTextFont:(NSFont *)textFont 78 | { 79 | [self willChangeValueForKey:@"textFont"]; 80 | DTIPreferences.sharedPreferences.dateTimeFont = textFont; 81 | [self didChangeValueForKey:@"textFont"]; 82 | } 83 | 84 | - (NSColor *)textColor 85 | { 86 | return DTIPreferences.sharedPreferences.dateTimeTextColor; 87 | } 88 | 89 | - (void)setTextColor:(NSColor *)textColor 90 | { 91 | DTIPreferences.sharedPreferences.dateTimeTextColor = textColor; 92 | } 93 | 94 | - (NSColor *)shadowColor 95 | { 96 | return DTIPreferences.sharedPreferences.dateTimeShadowColor; 97 | } 98 | 99 | - (void)setShadowColor:(NSColor *)shadowColor 100 | { 101 | DTIPreferences.sharedPreferences.dateTimeShadowColor = shadowColor; 102 | } 103 | 104 | #pragma mark - NSFontChanging 105 | 106 | - (void)changeFont:(NSFontManager *)fontManager 107 | { 108 | self.textFont = [fontManager convertFont:self.textFont]; 109 | } 110 | 111 | - (NSFontPanelModeMask)validModesForFontPanel:(NSFontPanel *)fontPanel 112 | { 113 | return NSFontPanelModeMaskCollection 114 | | NSFontPanelModeMaskFace 115 | | NSFontPanelModeMaskSize; 116 | } 117 | 118 | @end 119 | -------------------------------------------------------------------------------- /DTIPreferences/DTIDateTimePreferencesViewController/DTIDateTimePreferencesViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | DTIFontLocalizedDisplayNameValueTransformer 54 | 55 | 56 | 57 | 58 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /DTIPreferences/DTILayoutPreferencesViewController/DTILayoutPreferencesViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTILayoutPreferencesViewController.h 3 | // DesktopTime Preferences 4 | // 5 | // Created by Marcel Dierkes on 30.01.21. 6 | // 7 | 8 | #import 9 | #import "DTIPreferencesContentViewController.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface DTILayoutPreferencesViewController : DTIPreferencesContentViewController 14 | @property (nonatomic, readonly) DTILayout *layout; 15 | 16 | @end 17 | 18 | NS_ASSUME_NONNULL_END 19 | -------------------------------------------------------------------------------- /DTIPreferences/DTILayoutPreferencesViewController/DTILayoutPreferencesViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTILayoutPreferencesViewController.m 3 | // DesktopTime Preferences 4 | // 5 | // Created by Marcel Dierkes on 30.01.21. 6 | // 7 | 8 | #import "DTILayoutPreferencesViewController.h" 9 | #import "DTIDefines.h" 10 | #import "DTIPreferenceItem.h" 11 | 12 | #define DTI_L10N_LAYOUT NSLocalizedString(@"Layout", @"Layout") 13 | 14 | @interface DTILayoutPreferencesViewController () 15 | @property (nonatomic, readwrite) DTILayout *layout; 16 | @property (nonatomic, readwrite) NSArray *availableLayoutElements; 17 | @property (nonatomic) BOOL avoidsDockOverlapping; 18 | @property (nonatomic) BOOL avoidsMenuBarOverlapping; 19 | @end 20 | 21 | @implementation DTILayoutPreferencesViewController 22 | 23 | + (DTIPreferenceItem *)preferenceItem 24 | { 25 | Auto item = [[DTIPreferenceItem alloc] initWithUUID:[NSUUID UUID] 26 | name:DTI_L10N_LAYOUT]; 27 | item.image = [NSImage imageWithSystemSymbolName:@"dock.rectangle" 28 | accessibilityDescription:nil]; 29 | item.viewControllerClass = [self class]; 30 | return item; 31 | } 32 | 33 | - (NSString *)preferredTitle 34 | { 35 | return DTI_L10N_LAYOUT; 36 | } 37 | 38 | - (void)viewWillAppear 39 | { 40 | [super viewWillAppear]; 41 | 42 | self.layout = DTIPreferences.sharedPreferences.layout; 43 | self.layout.delegate = self; 44 | self.availableLayoutElements = DTILayoutElementGetAllElements(); 45 | } 46 | 47 | #pragma mark - Avoids Overlapping 48 | 49 | - (BOOL)avoidsDockOverlapping 50 | { 51 | return DTIPreferences.sharedPreferences.avoidsDockOverlapping; 52 | } 53 | 54 | - (void)setAvoidsDockOverlapping:(BOOL)avoidsDockOverlapping 55 | { 56 | [self willChangeValueForKey:@"avoidsDockOverlapping"]; 57 | DTIPreferences.sharedPreferences.avoidsDockOverlapping = avoidsDockOverlapping; 58 | [self willChangeValueForKey:@"avoidsDockOverlapping"]; 59 | } 60 | 61 | - (BOOL)avoidsMenuBarOverlapping 62 | { 63 | return DTIPreferences.sharedPreferences.avoidsMenuBarOverlapping; 64 | } 65 | 66 | - (void)setAvoidsMenuBarOverlapping:(BOOL)avoidsMenuBarOverlapping 67 | { 68 | [self willChangeValueForKey:@"avoidsMenuBarOverlapping"]; 69 | DTIPreferences.sharedPreferences.avoidsMenuBarOverlapping = avoidsMenuBarOverlapping; 70 | [self willChangeValueForKey:@"avoidsMenuBarOverlapping"]; 71 | } 72 | 73 | #pragma mark - DTILayoutDelegate 74 | 75 | - (void)layoutDidChange:(DTILayout *)layout keyPath:(NSString *)keyPath 76 | { 77 | DTIPreferences.sharedPreferences.layout = layout; 78 | } 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /DTIPreferences/DTILayoutPreferencesViewController/DTILayoutPreferencesViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 111 | 119 | 127 | 135 | 143 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 321 | 331 | 332 | 333 | 334 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | -------------------------------------------------------------------------------- /DTIPreferences/DTIPreferences.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.application-groups 8 | 9 | group.info.marcel-dierkes.DesktopTime 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /DTIPreferences/DTIPreferencesContentViewController/DTIPreferencesContentViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTIPreferencesContentViewController.h 3 | // DesktopTime Preferences 4 | // 5 | // Created by Marcel Dierkes on 30.01.21. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @class DTIPreferenceItem; 14 | 15 | @interface DTIPreferencesContentViewController : NSViewController 16 | @property (class, nonatomic, readonly) NSUserInterfaceItemIdentifier identifier; 17 | @property (class, nonatomic, readonly) DTIPreferenceItem *preferenceItem; 18 | @property (nonatomic, readonly) NSString *preferredTitle; 19 | @end 20 | 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /DTIPreferences/DTIPreferencesContentViewController/DTIPreferencesContentViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTIPreferencesContentViewController.m 3 | // DesktopTime Preferences 4 | // 5 | // Created by Marcel Dierkes on 30.01.21. 6 | // 7 | 8 | #import "DTIPreferencesContentViewController.h" 9 | #import "DTIDefines.h" 10 | #import "DTIPreferenceItem.h" 11 | 12 | @interface DTIPreferencesContentViewController () 13 | @end 14 | 15 | @implementation DTIPreferencesContentViewController 16 | 17 | + (NSUserInterfaceItemIdentifier)identifier 18 | { 19 | return NSStringFromClass(self); 20 | } 21 | 22 | + (DTIPreferenceItem *)preferenceItem 23 | { 24 | @throw [NSException exceptionWithName:NSInternalInconsistencyException 25 | reason:@"+preferenceItem needs to be implemented by the subclass." 26 | userInfo:nil]; 27 | } 28 | 29 | - (instancetype)init 30 | { 31 | Auto nibName = NSStringFromClass([self class]); 32 | self = [super initWithNibName:nibName bundle:nil]; 33 | if(self) 34 | { 35 | } 36 | return self; 37 | } 38 | 39 | - (void)viewDidAppear 40 | { 41 | [super viewDidAppear]; 42 | 43 | Auto preferredTitle = self.preferredTitle; 44 | if(preferredTitle != nil) 45 | { 46 | Auto window = self.view.window; 47 | window.subtitle = preferredTitle; 48 | } 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /DTIPreferences/DTIPreferencesContentViewController/DTIPreferencesContentViewControllers.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTIPreferencesContentViewControllers.h 3 | // DesktopTime Preferences 4 | // 5 | // Created by Marcel Dierkes on 30.01.21. 6 | // 7 | 8 | #import 9 | 10 | #import "DTIPreferencesContentViewController.h" 11 | 12 | #import "DTILayoutPreferencesViewController.h" 13 | #import "DTIDateTimePreferencesViewController.h" 14 | #import "DTIBatteryPreferencesViewController.h" 15 | -------------------------------------------------------------------------------- /DTIPreferences/DTIPreferencesViewController/DTIPreferenceItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTIPreferenceItem.h 3 | // DesktopTime Preferences 4 | // 5 | // Created by Marcel Dierkes on 30.01.21. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface DTIPreferenceItem : NSObject 14 | @property (nonatomic) NSUUID *UUID; 15 | @property (nonatomic) NSString *name; 16 | 17 | @property (nonatomic, nullable) Class viewControllerClass; 18 | 19 | @property (nonatomic, getter=isHeader) BOOL header; 20 | @property (nonatomic, getter=isExpanded) BOOL expanded; 21 | 22 | @property (nonatomic, nullable) NSArray *children; 23 | @property (weak, nonatomic, nullable) DTIPreferenceItem *parent; 24 | 25 | @property (nonatomic, nullable) NSImage *image; 26 | @property (nonatomic, nullable) NSColor *tintColor; 27 | @property (nonatomic, nullable, readonly) NSTintConfiguration *tintConfiguration; 28 | 29 | + (instancetype)new NS_UNAVAILABLE; 30 | - (instancetype)init NS_UNAVAILABLE; 31 | 32 | - (instancetype)initWithUUID:(NSUUID *)UUID 33 | name:(NSString *)name NS_DESIGNATED_INITIALIZER; 34 | 35 | - (instancetype)initWithUUID:(NSUUID *)UUID 36 | headerName:(NSString *)name 37 | image:(nullable NSImage *)image 38 | children:(nullable NSArray *)children; 39 | 40 | @end 41 | 42 | NS_ASSUME_NONNULL_END 43 | -------------------------------------------------------------------------------- /DTIPreferences/DTIPreferencesViewController/DTIPreferenceItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTIPreferenceItem.m 3 | // DesktopTime Preferences 4 | // 5 | // Created by Marcel Dierkes on 30.01.21. 6 | // 7 | 8 | #import "DTIPreferenceItem.h" 9 | #import "DTIDefines.h" 10 | 11 | @interface DTIPreferenceItem () 12 | @end 13 | 14 | @implementation DTIPreferenceItem 15 | 16 | - (instancetype)initWithUUID:(NSUUID *)UUID name:(NSString *)name 17 | { 18 | NSParameterAssert(UUID); 19 | NSParameterAssert(name); 20 | 21 | self = [super init]; 22 | if(self) 23 | { 24 | self.UUID = UUID; 25 | self.name = name; 26 | } 27 | return self; 28 | } 29 | 30 | - (instancetype)initWithUUID:(NSUUID *)UUID headerName:(NSString *)name image:(NSImage *)image children:(NSArray *)children 31 | { 32 | NSParameterAssert(UUID); 33 | NSParameterAssert(name); 34 | 35 | Auto item = [self initWithUUID:UUID name:name]; 36 | item.header = YES; 37 | item.expanded = YES; 38 | item.children = children; 39 | item.image = image; 40 | return item; 41 | } 42 | 43 | - (NSImage *)image 44 | { 45 | Auto parent = self.parent; 46 | if(parent != nil && parent.image != nil) { return parent.image; } 47 | return _image; 48 | } 49 | 50 | - (NSColor *)tintColor 51 | { 52 | Auto parent = self.parent; 53 | if(parent != nil && parent.tintColor != nil) { return parent.tintColor; } 54 | return _tintColor; 55 | } 56 | 57 | - (NSTintConfiguration *)tintConfiguration 58 | { 59 | Auto tintColor = self.tintColor; 60 | if(tintColor != nil) 61 | { 62 | return [NSTintConfiguration tintConfigurationWithPreferredColor:tintColor]; 63 | } 64 | return nil; 65 | } 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /DTIPreferences/DTIPreferencesViewController/DTIPreferencesViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTIPreferencesViewController.h 3 | // DesktopTime Preferences 4 | // 5 | // Created by Marcel Dierkes on 30.01.21. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface DTIPreferencesViewController : NSViewController 13 | @property (weak, nonatomic, nullable) IBOutlet NSOutlineView *outlineView; 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /DTIPreferences/DTIPreferencesViewController/DTIPreferencesViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTIPreferencesViewController.m 3 | // DesktopTime Preferences 4 | // 5 | // Created by Marcel Dierkes on 30.01.21. 6 | // 7 | 8 | #import "DTIPreferencesViewController.h" 9 | #import "DTIDefines.h" 10 | #import "DTIPreferenceItem.h" 11 | #import "DTIPreferencesContentViewControllers.h" 12 | 13 | #define DTI_L10N_PREFERENCES NSLocalizedString(@"Preferences", @"Preferences") 14 | 15 | static const NSUserInterfaceItemIdentifier DTIHeaderCellIdentifier = @"HeaderCell"; 16 | static const NSUserInterfaceItemIdentifier DTIDataCellIdentifier = @"DataCell"; 17 | 18 | @interface DTIPreferencesViewController () 19 | @property (nonatomic) NSArray *preferenceItems; 20 | @property (nonatomic, readonly) NSSplitViewController *splitViewController; 21 | @end 22 | 23 | @implementation DTIPreferencesViewController 24 | 25 | - (void)viewDidLoad 26 | { 27 | [super viewDidLoad]; 28 | 29 | [self configurePreferenceItems]; 30 | 31 | [self.outlineView reloadData]; 32 | 33 | [self expandAllGroups]; 34 | } 35 | 36 | - (void)viewWillAppear 37 | { 38 | [super viewWillAppear]; 39 | 40 | [self selectFirstItem]; 41 | } 42 | 43 | - (NSSplitViewController *)splitViewController 44 | { 45 | return (NSSplitViewController *)self.parentViewController; 46 | } 47 | 48 | - (void)configurePreferenceItems 49 | { 50 | Auto items = @[ 51 | DTILayoutPreferencesViewController.preferenceItem, 52 | DTIDateTimePreferencesViewController.preferenceItem, 53 | DTIBatteryPreferencesViewController.preferenceItem 54 | ]; 55 | 56 | self.preferenceItems = @[ 57 | [[DTIPreferenceItem alloc] initWithUUID:[NSUUID UUID] 58 | headerName:DTI_L10N_PREFERENCES 59 | image:nil 60 | children:items] 61 | ]; 62 | } 63 | 64 | - (void)expandAllGroups 65 | { 66 | for(DTIPreferenceItem *header in self.preferenceItems) 67 | { 68 | [self.outlineView expandItem:header]; 69 | } 70 | } 71 | 72 | - (void)selectFirstItem 73 | { 74 | Auto outlineView = self.outlineView; 75 | Auto firstItem = self.preferenceItems.firstObject.children.firstObject; 76 | Auto row = [outlineView rowForItem:firstItem]; 77 | [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] 78 | byExtendingSelection:NO]; 79 | } 80 | 81 | - (void)showDetailsForPreferenceItem:(DTIPreferenceItem *)preferenceItem 82 | { 83 | NSParameterAssert(preferenceItem); 84 | 85 | Auto split = self.splitViewController; 86 | Auto detailItem = split.splitViewItems.lastObject; 87 | 88 | Class viewControllerClass = preferenceItem.viewControllerClass; 89 | if(viewControllerClass != nil && ![detailItem isKindOfClass:viewControllerClass]) 90 | { 91 | Auto controller = (__kindof DTIPreferencesContentViewController *)[viewControllerClass new]; 92 | [self setSplitDetailViewController:controller]; 93 | } 94 | } 95 | 96 | - (void)setSplitDetailViewController:(__kindof NSViewController *)controller 97 | { 98 | NSAssert(self.view.window != nil, @"The view must be attached to a window."); 99 | 100 | Auto split = self.splitViewController; 101 | Auto sidebarItem = split.splitViewItems.firstObject; 102 | Auto detailItem = [NSSplitViewItem splitViewItemWithViewController:controller]; 103 | detailItem.titlebarSeparatorStyle = NSTitlebarSeparatorStyleLine; 104 | 105 | split.splitViewItems = @[sidebarItem, detailItem]; 106 | } 107 | 108 | #pragma mark - NSOutlineViewDataSource 109 | 110 | - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item 111 | { 112 | // Root 113 | if(item == nil) { return self.preferenceItems[index]; } 114 | 115 | Auto preferenceItem = (DTIPreferenceItem *)item; 116 | return preferenceItem.children[index]; 117 | } 118 | 119 | - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item 120 | { 121 | // Root 122 | if(item == nil) { return YES; } 123 | 124 | Auto preferenceItem = (DTIPreferenceItem *)item; 125 | return [preferenceItem isHeader]; 126 | } 127 | 128 | - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item 129 | { 130 | // Root 131 | if(item == nil) { return self.preferenceItems.count; } 132 | 133 | Auto preferenceItem = (DTIPreferenceItem *)item; 134 | return preferenceItem.children.count; 135 | } 136 | 137 | #pragma mark - NSOutlineViewDelegate 138 | 139 | - (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item 140 | { 141 | Auto preferenceItem = (DTIPreferenceItem *)item; 142 | if([preferenceItem isHeader]) 143 | { 144 | Auto header = [outlineView makeViewWithIdentifier:DTIHeaderCellIdentifier owner:self]; 145 | Auto textField = (NSTextField *)header.subviews.firstObject; 146 | textField.stringValue = preferenceItem.name; 147 | return header; 148 | } 149 | else 150 | { 151 | Auto cell = [outlineView makeViewWithIdentifier:DTIDataCellIdentifier owner:self]; 152 | Auto imageView = (NSImageView *)cell.subviews.firstObject; 153 | imageView.image = preferenceItem.image; 154 | Auto textField = (NSTextField *)cell.subviews.lastObject; 155 | textField.stringValue = preferenceItem.name; 156 | return cell; 157 | } 158 | } 159 | 160 | - (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item 161 | { 162 | Auto preferenceItem = (DTIPreferenceItem *)item; 163 | return ![preferenceItem isHeader]; 164 | } 165 | 166 | - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item 167 | { 168 | Auto preferenceItem = (DTIPreferenceItem *)item; 169 | return [preferenceItem isHeader]; 170 | } 171 | 172 | - (NSTintConfiguration *)outlineView:(NSOutlineView *)outlineView tintConfigurationForItem:(id)item 173 | { 174 | Auto preferenceItem = (DTIPreferenceItem *)item; 175 | return preferenceItem.tintConfiguration; 176 | } 177 | 178 | - (void)outlineViewSelectionDidChange:(NSNotification *)notification 179 | { 180 | Auto outlineView = (NSOutlineView *)notification.object; 181 | 182 | Auto row = outlineView.selectedRow; 183 | Auto preferenceItem = (DTIPreferenceItem *)[outlineView itemAtRow:row]; 184 | if(preferenceItem != nil) 185 | { 186 | [self showDetailsForPreferenceItem:preferenceItem]; 187 | } 188 | } 189 | 190 | @end 191 | -------------------------------------------------------------------------------- /DTIPreferences/DTIPreferencesViewController/DTIPreferencesWindowController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTIPreferencesWindowController.h 3 | // DesktopTime Preferences 4 | // 5 | // Created by Marcel Dierkes on 29.01.21. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface DTIPreferencesWindowController : NSWindowController 14 | 15 | - (IBAction)completelyTerminate:(id)sender; 16 | - (IBAction)showAboutPanel:(nullable id)sender; 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /DTIPreferences/DTIPreferencesViewController/DTIPreferencesWindowController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTIPreferencesWindowController.m 3 | // DesktopTime Preferences 4 | // 5 | // Created by Marcel Dierkes on 29.01.21. 6 | // 7 | 8 | #import "DTIPreferencesWindowController.h" 9 | #import "DTIDefines.h" 10 | 11 | @interface DTIPreferencesWindowController () 12 | @end 13 | 14 | @implementation DTIPreferencesWindowController 15 | 16 | - (void)windowDidLoad 17 | { 18 | [super windowDidLoad]; 19 | 20 | // Window auto save identifier 21 | self.windowFrameAutosaveName = @"DTIPreferencesWindow"; 22 | 23 | // Split auto save identifier 24 | Auto splitViewController = (NSSplitViewController *)self.contentViewController; 25 | splitViewController.splitView.autosaveName = @"DTIPreferencesSplit"; 26 | } 27 | 28 | - (IBAction)completelyTerminate:(id)sender 29 | { 30 | Auto center = DTINotificationCenter.defaultCenter; 31 | [center postNotification:DTIAppShouldTerminateNotification]; 32 | [NSApplication.sharedApplication terminate:sender]; 33 | } 34 | 35 | - (IBAction)showAboutPanel:(nullable id)sender 36 | { 37 | Auto appName = NSLocalizedString(@"DesktopTime", @"DesktopTime"); 38 | [NSApplication.sharedApplication orderFrontStandardAboutPanelWithOptions:@{ 39 | NSAboutPanelOptionApplicationName: appName 40 | }]; 41 | } 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /DTIPreferences/Extensions/NSFont+DTILocalizedDisplayName.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSFont+DTILocalizedDisplayName.h 3 | // DesktopTime Preferences 4 | // 5 | // Created by Marcel Dierkes on 31.01.21. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface NSFont (DTILocalizedDisplayName) 13 | /// A localized version of `displayName`, incl. the font size. 14 | @property (nonatomic, readonly) NSString *dti_localizedDisplayName; 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /DTIPreferences/Extensions/NSFont+DTILocalizedDisplayName.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSFont+DTILocalizedDisplayName.m 3 | // DesktopTime Preferences 4 | // 5 | // Created by Marcel Dierkes on 31.01.21. 6 | // 7 | 8 | #import "NSFont+DTILocalizedDisplayName.h" 9 | #import "DTIDefines.h" 10 | 11 | @implementation NSFont (DTILocalizedDisplayName) 12 | 13 | - (NSString *)dti_localizedDisplayName 14 | { 15 | Auto fontName = self.displayName; 16 | Auto fontSize = @(self.pointSize); 17 | Auto fontSizeName = [[self numberFormatter] stringFromNumber:fontSize]; 18 | return [NSString stringWithFormat:@"%@ %@", fontName, fontSizeName]; 19 | } 20 | 21 | - (NSNumberFormatter *)numberFormatter 22 | { 23 | Auto nf = [NSNumberFormatter new]; 24 | nf.numberStyle = NSNumberFormatterDecimalStyle; 25 | nf.minimumFractionDigits = 1; 26 | nf.formattingContext = NSFormattingContextStandalone; 27 | return nf; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /DTIPreferences/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | LSApplicationCategoryType 24 | public.app-category.utilities 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSMainStoryboardFile 28 | Main 29 | NSHumanReadableCopyright 30 | ${DTI_COPYRIGHT_TEXT} 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /DTIPreferences/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // DTIPreferences 4 | // 5 | // Created by Marcel Dierkes on 29.01.21. 6 | // 7 | 8 | #import 9 | 10 | int main(int argc, const char *argv[]) 11 | { 12 | @autoreleasepool {} 13 | return NSApplicationMain(argc, argv); 14 | } 15 | -------------------------------------------------------------------------------- /DesktopTime.xcodeproj/xcshareddata/xcschemes/DesktopTimeKit.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /DesktopTime.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /DesktopTime.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DesktopTime/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.525", 9 | "green" : "0.569", 10 | "red" : "0.298" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /DesktopTime/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon_16x16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "icon_16x16@2x.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "icon_32x32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "icon_32x32@2x.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "icon_128x128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "icon_128x128@2x.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "icon_256x256.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "icon_256x256@2x.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "icon_512x512.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "icon_512x512@2x.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /DesktopTime/Assets.xcassets/AppIcon.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newmarcel/DesktopTime/5146b11b21624bcd482abccce0925952abe2f16f/DesktopTime/Assets.xcassets/AppIcon.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /DesktopTime/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newmarcel/DesktopTime/5146b11b21624bcd482abccce0925952abe2f16f/DesktopTime/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /DesktopTime/Assets.xcassets/AppIcon.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newmarcel/DesktopTime/5146b11b21624bcd482abccce0925952abe2f16f/DesktopTime/Assets.xcassets/AppIcon.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /DesktopTime/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newmarcel/DesktopTime/5146b11b21624bcd482abccce0925952abe2f16f/DesktopTime/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /DesktopTime/Assets.xcassets/AppIcon.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newmarcel/DesktopTime/5146b11b21624bcd482abccce0925952abe2f16f/DesktopTime/Assets.xcassets/AppIcon.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /DesktopTime/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newmarcel/DesktopTime/5146b11b21624bcd482abccce0925952abe2f16f/DesktopTime/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /DesktopTime/Assets.xcassets/AppIcon.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newmarcel/DesktopTime/5146b11b21624bcd482abccce0925952abe2f16f/DesktopTime/Assets.xcassets/AppIcon.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /DesktopTime/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newmarcel/DesktopTime/5146b11b21624bcd482abccce0925952abe2f16f/DesktopTime/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /DesktopTime/Assets.xcassets/AppIcon.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newmarcel/DesktopTime/5146b11b21624bcd482abccce0925952abe2f16f/DesktopTime/Assets.xcassets/AppIcon.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /DesktopTime/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newmarcel/DesktopTime/5146b11b21624bcd482abccce0925952abe2f16f/DesktopTime/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /DesktopTime/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DesktopTime/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /DesktopTime/DTIAppController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTIAppController.h 3 | // DesktopTime 4 | // 5 | // Created by Marcel Dierkes on 17.01.21. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface DTIAppController : NSObject 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /DesktopTime/DTIAppController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTIAppController.m 3 | // DesktopTime 4 | // 5 | // Created by Marcel Dierkes on 17.01.21. 6 | // 7 | 8 | #import "DTIAppController.h" 9 | #import "DTIDefines.h" 10 | #import "DTILayoutController.h" 11 | 12 | @interface DTIAppController () 13 | @property (nonatomic) DTILayoutController *layoutController; 14 | @end 15 | 16 | @implementation DTIAppController 17 | 18 | - (instancetype)init 19 | { 20 | self = [super init]; 21 | if(self) 22 | { 23 | [self configureNotifications]; 24 | } 25 | return self; 26 | } 27 | 28 | #pragma mark - Notifications 29 | 30 | - (void)configureNotifications 31 | { 32 | Auto center = DTINotificationCenter.defaultCenter; 33 | [center addObserver:self 34 | selector:@selector(handleShouldTerminate:) 35 | name:DTIAppShouldTerminateNotification]; 36 | [center addObserver:self 37 | selector:@selector(handleLayoutDidChange:) 38 | name:DTILayoutDidChangeNotification]; 39 | } 40 | 41 | - (void)handleShouldTerminate:(NSNotification *)notification 42 | { 43 | [NSApplication.sharedApplication terminate:notification]; 44 | } 45 | 46 | - (void)handleLayoutDidChange:(NSNotification *)notification 47 | { 48 | [self.layoutController reloadLayout]; 49 | } 50 | 51 | #pragma mark - NSApplicationDelegate 52 | 53 | - (void)applicationDidFinishLaunching:(NSNotification *)notification 54 | { 55 | Auto layoutController = [DTILayoutController new]; 56 | [layoutController reloadLayout]; 57 | [layoutController startTimer]; 58 | self.layoutController = layoutController; 59 | 60 | [DTIPreferences.sharedPreferences performBlockOnFirstLaunch:^{ 61 | Auto workspace = NSWorkspace.sharedWorkspace; 62 | Auto alert = NSAlert.dti_welcomeAlert; 63 | [alert dti_runWelcomeAlertWithOpenPreferencesHandler:^{ 64 | [workspace dti_openPreferencesWithCompletionHandler:nil]; 65 | }]; 66 | }]; 67 | } 68 | 69 | - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender 70 | { 71 | return YES; 72 | } 73 | 74 | - (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag 75 | { 76 | [NSWorkspace.sharedWorkspace dti_openPreferencesWithCompletionHandler:nil]; 77 | return NO; 78 | } 79 | 80 | - (void)applicationDidChangeScreenParameters:(NSNotification *)notification 81 | { 82 | [self.layoutController reloadLayout]; 83 | } 84 | 85 | @end 86 | -------------------------------------------------------------------------------- /DesktopTime/DTIDefines.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTIDefines.h 3 | // DesktopTime 4 | // 5 | // Created by Marcel Dierkes on 17.01.21. 6 | // 7 | 8 | #ifndef DTI_DEFINES_H 9 | #define DTI_DEFINES_H 10 | 11 | #define Auto const __auto_type 12 | #define AutoVar __auto_type 13 | #define AutoWeak __weak __auto_type 14 | 15 | #if DEBUG 16 | #define DTILog(_args...) NSLog(_args) 17 | #else 18 | #define DTILog(_args...) 19 | #endif 20 | 21 | #endif /* DTI_DEFINES_H */ 22 | -------------------------------------------------------------------------------- /DesktopTime/DTILayoutController/DTILayoutController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTILayoutController.h 3 | // DesktopTime 4 | // 5 | // Created by Marcel Dierkes on 05.02.21. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface DTILayoutController : NSObject 14 | @property (nonatomic, readonly, nullable) DTILayout *currentLayout; 15 | 16 | - (void)reloadLayout; 17 | 18 | - (void)startTimer; 19 | - (void)stopTimer; 20 | 21 | @end 22 | 23 | NS_ASSUME_NONNULL_END 24 | -------------------------------------------------------------------------------- /DesktopTime/DTILayoutController/DTILayoutController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTILayoutController.m 3 | // DesktopTime 4 | // 5 | // Created by Marcel Dierkes on 05.02.21. 6 | // 7 | 8 | #import "DTILayoutController.h" 9 | #import "DTIDefines.h" 10 | #import "DTILayoutWindowController.h" 11 | #import "DTILayoutElementDataSource.h" 12 | 13 | static const NSTimeInterval DTITimerTickInterval = 1.0f; 14 | 15 | @interface DTILayoutController () 16 | @property (nonatomic) NSMutableArray *windowControllers; 17 | @property (nonatomic, readwrite, nullable) DTILayout *currentLayout; 18 | @property (nonatomic) NSTimer *timer; 19 | @property (nonatomic) NSMapTable *elementDataSources; 20 | @end 21 | 22 | @implementation DTILayoutController 23 | 24 | - (instancetype)init 25 | { 26 | self = [super init]; 27 | if(self) 28 | { 29 | self.elementDataSources = [NSMapTable new]; 30 | self.windowControllers = [NSMutableArray new]; 31 | } 32 | return self; 33 | } 34 | 35 | - (void)dealloc 36 | { 37 | [self stopTimer]; 38 | 39 | for(NSWindowController *windowController in self.windowControllers) 40 | { 41 | [windowController close]; 42 | } 43 | self.windowControllers = nil; 44 | } 45 | 46 | - (void)reloadLayout 47 | { 48 | Auto layout = DTIPreferences.sharedPreferences.layout; 49 | self.currentLayout = layout; 50 | 51 | [self clearWindows]; 52 | [self createWindowsForLayout:layout]; 53 | } 54 | 55 | #pragma mark - Window Management 56 | 57 | - (void)clearWindows 58 | { 59 | for(NSWindowController *windowController in self.windowControllers) 60 | { 61 | [windowController close]; 62 | } 63 | [self.windowControllers removeAllObjects]; 64 | } 65 | 66 | - (void)createWindowsForLayout:(DTILayout *)layout 67 | { 68 | NSParameterAssert(layout); 69 | 70 | Auto screens = NSScreen.screens; 71 | Auto mainScreen = NSScreen.mainScreen; 72 | 73 | switch(layout.displayMode) 74 | { 75 | case DTILayoutDisplayModeAllDisplays: 76 | { 77 | for(NSScreen *screen in screens) 78 | { 79 | [self createWindowsForLayout:layout onScreen:screen]; 80 | } 81 | break; 82 | } 83 | case DTILayoutDisplayModeOnlyMainDisplay: 84 | { 85 | [self createWindowsForLayout:layout onScreen:mainScreen]; 86 | break; 87 | } 88 | case DTILayoutDisplayModeOnlySecondaryDisplays: 89 | { 90 | if(screens.count == 1) 91 | { 92 | // If only one screen is connected, just use this 93 | // instead of not displaying anything. 94 | [self createWindowsForLayout:layout onScreen:screens.firstObject]; 95 | break; 96 | } 97 | 98 | for(NSScreen *screen in screens) 99 | { 100 | if([screen isEqual:mainScreen]) { continue; } 101 | [self createWindowsForLayout:layout onScreen:screen]; 102 | } 103 | break; 104 | } 105 | } 106 | } 107 | 108 | - (void)createWindowsForLayout:(DTILayout *)layout onScreen:(NSScreen *)screen 109 | { 110 | NSParameterAssert(layout); 111 | NSParameterAssert(screen); 112 | 113 | Auto windowController = [DTILayoutWindowController new]; 114 | windowController.layout = self.currentLayout; 115 | windowController.targetScreen = screen; 116 | windowController.elementDataSource = self; 117 | 118 | [self.windowControllers addObject:windowController]; 119 | [windowController showWindow:self]; 120 | [windowController reloadWindow]; 121 | } 122 | 123 | #pragma mark - Timer 124 | 125 | - (void)startTimer 126 | { 127 | self.timer = [NSTimer scheduledTimerWithTimeInterval:DTITimerTickInterval 128 | target:self 129 | selector:@selector(tick:) 130 | userInfo:nil 131 | repeats:YES]; 132 | // The initial tick 133 | [self tick:self.timer]; 134 | } 135 | 136 | - (void)stopTimer 137 | { 138 | [self.timer invalidate]; 139 | self.timer = nil; 140 | } 141 | 142 | - (void)tick:(NSTimer *)timer 143 | { 144 | for(DTILayoutWindowController *windowController in self.windowControllers) 145 | { 146 | [windowController reloadWindow]; 147 | } 148 | } 149 | 150 | #pragma mark - DTILayoutWindowElementDataSource 151 | 152 | - (DTILayoutElementDataSource *)dataSourceForLayoutElement:(DTILayoutElement)element 153 | { 154 | Auto key = @(element); 155 | AutoVar dataSource = [self.elementDataSources objectForKey:key]; 156 | if(dataSource == nil) 157 | { 158 | dataSource = [DTILayoutElementDataSource dataSourceForElement:element]; 159 | if(dataSource == nil) { return nil; } 160 | [self.elementDataSources setObject:dataSource forKey:key]; 161 | } 162 | return dataSource; 163 | } 164 | 165 | - (void)applyStyleOfLayoutElement:(DTILayoutElement)element toLabel:(NSTextField *)label 166 | { 167 | Auto preferences = DTIPreferences.sharedPreferences; 168 | switch(element) 169 | { 170 | case DTILayoutElementDateTime: 171 | case DTILayoutElementDate: 172 | case DTILayoutElementTime: 173 | label.font = preferences.dateTimeFont; 174 | label.textColor = preferences.dateTimeTextColor; 175 | label.layer.shadowColor = preferences.dateTimeShadowColor.CGColor; 176 | break; 177 | case DTILayoutElementBatteryLevel: 178 | label.font = preferences.batteryLevelFont; 179 | label.textColor = preferences.batteryLevelTextColor; 180 | label.layer.shadowColor = preferences.batteryLevelShadowColor.CGColor; 181 | break; 182 | default: 183 | break; 184 | } 185 | } 186 | 187 | @end 188 | -------------------------------------------------------------------------------- /DesktopTime/DTILayoutWindowController/DTILayoutElementDataSource.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTILayoutElementDataSource.h 3 | // DesktopTime 4 | // 5 | // Created by Marcel Dierkes on 08.02.21. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface DTILayoutElementDataSource : NSObject 14 | @property (nonatomic, readonly) DTILayoutElement element; 15 | @property (nonatomic, readonly, nullable) NSString *stringValue; 16 | 17 | + (nullable instancetype)dataSourceForElement:(DTILayoutElement)element; 18 | 19 | + (instancetype)new NS_UNAVAILABLE; 20 | - (instancetype)init NS_UNAVAILABLE; 21 | 22 | - (nullable instancetype)initWithElement:(DTILayoutElement)element; 23 | 24 | @end 25 | 26 | NS_ASSUME_NONNULL_END 27 | -------------------------------------------------------------------------------- /DesktopTime/DTILayoutWindowController/DTILayoutElementDataSource.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTILayoutElementDataSource.m 3 | // DesktopTime 4 | // 5 | // Created by Marcel Dierkes on 08.02.21. 6 | // 7 | 8 | #import "DTILayoutElementDataSource.h" 9 | #import "DTIDefines.h" 10 | 11 | typedef NS_ENUM(NSUInteger, DTILayoutElementDateTimeMode) 12 | { 13 | DTILayoutElementDateTimeModeDateTime = 0, 14 | DTILayoutElementDateTimeModeDate, 15 | DTILayoutElementDateTimeModeTime, 16 | }; 17 | 18 | @interface DTILayoutElementDataSource () 19 | @property (nonatomic, readwrite) DTILayoutElement element; 20 | @end 21 | 22 | @interface DTILayoutElementDateTimeDataSource : DTILayoutElementDataSource 23 | @property (nonatomic) DTILayoutElementDateTimeMode mode; 24 | @property (nonatomic) NSDateFormatter *dateFormatter; 25 | @end 26 | 27 | @interface DTILayoutElementBatteryLevelDataSource : DTILayoutElementDataSource 28 | @property (nonatomic) DTIBatteryStatus *batteryStatus; 29 | @end 30 | 31 | @implementation DTILayoutElementDataSource 32 | 33 | + (instancetype)dataSourceForElement:(DTILayoutElement)element 34 | { 35 | switch(element) 36 | { 37 | case DTILayoutElementDateTime: 38 | case DTILayoutElementDate: 39 | case DTILayoutElementTime: 40 | return [[DTILayoutElementDateTimeDataSource alloc] initWithElement:element]; 41 | case DTILayoutElementBatteryLevel: 42 | return [[DTILayoutElementBatteryLevelDataSource alloc] initWithElement:element]; 43 | default: 44 | return nil; 45 | } 46 | } 47 | 48 | - (instancetype)initWithElement:(DTILayoutElement)element 49 | { 50 | self = [super init]; 51 | if(self) 52 | { 53 | } 54 | return self; 55 | } 56 | 57 | - (NSString *)stringValue 58 | { 59 | return nil; 60 | } 61 | 62 | @end 63 | 64 | @implementation DTILayoutElementDateTimeDataSource 65 | 66 | - (instancetype)initWithElement:(DTILayoutElement)element 67 | { 68 | self = [super initWithElement:element]; 69 | if(self) 70 | { 71 | self.dateFormatter = [NSDateFormatter dti_dateFormatterForLayoutElement:element]; 72 | } 73 | return self; 74 | } 75 | 76 | - (NSString *)stringValue 77 | { 78 | Auto now = [NSDate date]; 79 | return [self.dateFormatter stringFromDate:now]; 80 | } 81 | 82 | @end 83 | 84 | @implementation DTILayoutElementBatteryLevelDataSource 85 | 86 | - (instancetype)initWithElement:(DTILayoutElement)element 87 | { 88 | self = [super initWithElement:element]; 89 | if(self) 90 | { 91 | self.batteryStatus = [DTIBatteryStatus new]; 92 | } 93 | return self; 94 | } 95 | 96 | - (NSString *)stringValue 97 | { 98 | return self.batteryStatus.localizedCurrentCapacity; 99 | } 100 | 101 | @end 102 | -------------------------------------------------------------------------------- /DesktopTime/DTILayoutWindowController/DTILayoutWindowController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTILayoutWindowController.h 3 | // DesktopTime 4 | // 5 | // Created by Marcel Dierkes on 07.02.21. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @protocol DTILayoutWindowElementDataSource; 14 | 15 | @interface DTILayoutWindowController : NSWindowController 16 | @property (nonatomic, nullable) DTILayout *layout; 17 | @property (weak, nonatomic, nullable) NSScreen *targetScreen; 18 | @property (weak, nonatomic, nullable) id elementDataSource; 19 | 20 | - (void)reloadWindow; 21 | 22 | @end 23 | 24 | @class DTILayoutElementDataSource; 25 | 26 | @protocol DTILayoutWindowElementDataSource 27 | - (nullable DTILayoutElementDataSource *)dataSourceForLayoutElement:(DTILayoutElement)element; 28 | - (void)applyStyleOfLayoutElement:(DTILayoutElement)element toLabel:(NSTextField *)label; 29 | @end 30 | 31 | NS_ASSUME_NONNULL_END 32 | -------------------------------------------------------------------------------- /DesktopTime/DTILayoutWindowController/DTILayoutWindowController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTILayoutWindowController.m 3 | // DesktopTime 4 | // 5 | // Created by Marcel Dierkes on 07.02.21. 6 | // 7 | 8 | #import "DTILayoutWindowController.h" 9 | #import "DTIDefines.h" 10 | #import "DTILayoutElementDataSource.h" 11 | 12 | @interface DTILayoutWindowController () 13 | @property (nonatomic) NSTimer *timer; 14 | 15 | @property (weak, nonatomic, nullable) IBOutlet NSTextField *topLeftLabel; 16 | @property (weak, nonatomic, nullable) IBOutlet NSTextField *topMiddleLabel; 17 | @property (weak, nonatomic, nullable) IBOutlet NSTextField *topRightLabel; 18 | 19 | //@property (weak, nonatomic, nullable) IBOutlet NSTextField *centerMiddleLabel; 20 | 21 | @property (weak, nonatomic, nullable) IBOutlet NSTextField *bottomLeftLabel; 22 | @property (weak, nonatomic, nullable) IBOutlet NSTextField *bottomMiddleLabel; 23 | @property (weak, nonatomic, nullable) IBOutlet NSTextField *bottomRightLabel; 24 | @end 25 | 26 | @implementation DTILayoutWindowController 27 | 28 | - (instancetype)init 29 | { 30 | self = [super initWithWindowNibName:NSStringFromClass([self class])]; 31 | if(self) 32 | { 33 | } 34 | return self; 35 | } 36 | 37 | - (void)windowDidLoad 38 | { 39 | [super windowDidLoad]; 40 | 41 | [self configureWindow]; 42 | [self reloadWindow]; 43 | } 44 | 45 | #pragma mark - Window Management 46 | 47 | - (void)configureWindow 48 | { 49 | Auto window = self.window; 50 | window.level = CGWindowLevelForKey(kCGDesktopWindowLevel); 51 | window.ignoresMouseEvents = YES; 52 | window.backgroundColor = NSColor.clearColor; 53 | } 54 | 55 | - (void)updateOffsets 56 | { 57 | Auto window = self.window; 58 | Auto screen = self.targetScreen ?: window.screen; 59 | 60 | Auto metrics = screen.dti_workspaceMetrics; 61 | Auto insets = metrics.usableEdgeInsets; 62 | window.contentView.additionalSafeAreaInsets = insets; 63 | 64 | [window setFrame:screen.frame display:NO]; 65 | } 66 | 67 | - (void)reloadWindow 68 | { 69 | Auto layout = self.layout; 70 | 71 | Auto elementDataSource = self.elementDataSource; 72 | if(elementDataSource == nil) { return; } 73 | 74 | Auto configureElement = ^(DTILayoutElement element, NSTextField *label) { 75 | Auto dataSource = [elementDataSource dataSourceForLayoutElement:element]; 76 | if(dataSource != nil) 77 | { 78 | label.stringValue = dataSource.stringValue; 79 | label.hidden = NO; 80 | [elementDataSource applyStyleOfLayoutElement:element toLabel:label]; 81 | } 82 | else 83 | { 84 | label.stringValue = @""; 85 | label.hidden = YES; 86 | } 87 | }; 88 | 89 | configureElement(layout.topLeftElement, self.topLeftLabel); 90 | configureElement(layout.topMiddleElement, self.topMiddleLabel); 91 | configureElement(layout.topRightElement, self.topRightLabel); 92 | 93 | configureElement(layout.bottomLeftElement, self.bottomLeftLabel); 94 | configureElement(layout.bottomMiddleElement, self.bottomMiddleLabel); 95 | configureElement(layout.bottomRightElement, self.bottomRightLabel); 96 | 97 | [self updateOffsets]; 98 | } 99 | 100 | @end 101 | -------------------------------------------------------------------------------- /DesktopTime/DTILayoutWindowController/DTILayoutWindowController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /DesktopTime/DesktopTime.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.application-groups 8 | 9 | group.info.marcel-dierkes.DesktopTime 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /DesktopTime/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | LSApplicationCategoryType 24 | public.app-category.utilities 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSMainNibFile 28 | MainMenu 29 | LSUIElement 30 | 31 | NSHumanReadableCopyright 32 | ${DTI_COPYRIGHT_TEXT} 33 | NSPrincipalClass 34 | NSApplication 35 | 36 | 37 | -------------------------------------------------------------------------------- /DesktopTime/de.lproj/MainMenu.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "NSMenuItem"; title = "Customize Toolbar…"; ObjectID = "1UK-8n-QPP"; */ 3 | "1UK-8n-QPP.title" = "Customize Toolbar…"; 4 | 5 | /* Class = "NSMenuItem"; title = "DesktopTime"; ObjectID = "1Xt-HY-uBw"; */ 6 | "1Xt-HY-uBw.title" = "DesktopTime"; 7 | 8 | /* Class = "NSMenuItem"; title = "Enter Full Screen"; ObjectID = "4J7-dP-txa"; */ 9 | "4J7-dP-txa.title" = "Enter Full Screen"; 10 | 11 | /* Class = "NSMenuItem"; title = "Quit DesktopTime"; ObjectID = "4sb-4s-VLi"; */ 12 | "4sb-4s-VLi.title" = "Quit DesktopTime"; 13 | 14 | /* Class = "NSMenuItem"; title = "About DesktopTime"; ObjectID = "5kV-Vb-QxS"; */ 15 | "5kV-Vb-QxS.title" = "About DesktopTime"; 16 | 17 | /* Class = "NSMenu"; title = "Main Menu"; ObjectID = "AYu-sK-qS6"; */ 18 | "AYu-sK-qS6.title" = "Main Menu"; 19 | 20 | /* Class = "NSMenuItem"; title = "Preferences…"; ObjectID = "BOF-NM-1cW"; */ 21 | "BOF-NM-1cW.title" = "Preferences…"; 22 | 23 | /* Class = "NSMenu"; title = "Help"; ObjectID = "F2S-fz-NVQ"; */ 24 | "F2S-fz-NVQ.title" = "Help"; 25 | 26 | /* Class = "NSMenuItem"; title = "DesktopTime Help"; ObjectID = "FKE-Sm-Kum"; */ 27 | "FKE-Sm-Kum.title" = "DesktopTime Help"; 28 | 29 | /* Class = "NSMenuItem"; title = "View"; ObjectID = "H8h-7b-M4v"; */ 30 | "H8h-7b-M4v.title" = "View"; 31 | 32 | /* Class = "NSMenu"; title = "View"; ObjectID = "HyV-fh-RgO"; */ 33 | "HyV-fh-RgO.title" = "View"; 34 | 35 | /* Class = "NSMenuItem"; title = "Show All"; ObjectID = "Kd2-mp-pUS"; */ 36 | "Kd2-mp-pUS.title" = "Show All"; 37 | 38 | /* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "LE2-aR-0XJ"; */ 39 | "LE2-aR-0XJ.title" = "Bring All to Front"; 40 | 41 | /* Class = "NSMenuItem"; title = "Services"; ObjectID = "NMo-om-nkz"; */ 42 | "NMo-om-nkz.title" = "Services"; 43 | 44 | /* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "OY7-WF-poV"; */ 45 | "OY7-WF-poV.title" = "Minimize"; 46 | 47 | /* Class = "NSMenuItem"; title = "Hide DesktopTime"; ObjectID = "Olw-nP-bQN"; */ 48 | "Olw-nP-bQN.title" = "Hide DesktopTime"; 49 | 50 | /* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "R4o-n2-Eq4"; */ 51 | "R4o-n2-Eq4.title" = "Zoom"; 52 | 53 | /* Class = "NSMenu"; title = "Window"; ObjectID = "Td7-aD-5lo"; */ 54 | "Td7-aD-5lo.title" = "Window"; 55 | 56 | /* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "Vdr-fp-XzO"; */ 57 | "Vdr-fp-XzO.title" = "Hide Others"; 58 | 59 | /* Class = "NSMenuItem"; title = "Window"; ObjectID = "aUF-d1-5bR"; */ 60 | "aUF-d1-5bR.title" = "Window"; 61 | 62 | /* Class = "NSMenu"; title = "Services"; ObjectID = "hz9-B4-Xy5"; */ 63 | "hz9-B4-Xy5.title" = "Services"; 64 | 65 | /* Class = "NSMenuItem"; title = "Show Sidebar"; ObjectID = "kIP-vf-haE"; */ 66 | "kIP-vf-haE.title" = "Show Sidebar"; 67 | 68 | /* Class = "NSMenuItem"; title = "Show Toolbar"; ObjectID = "snW-S8-Cw5"; */ 69 | "snW-S8-Cw5.title" = "Show Toolbar"; 70 | 71 | /* Class = "NSMenu"; title = "DesktopTime"; ObjectID = "uQy-DD-JDr"; */ 72 | "uQy-DD-JDr.title" = "DesktopTime"; 73 | 74 | /* Class = "NSMenuItem"; title = "Help"; ObjectID = "wpr-3q-Mcd"; */ 75 | "wpr-3q-Mcd.title" = "Help"; 76 | -------------------------------------------------------------------------------- /DesktopTime/fr.lproj/MainMenu.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "NSMenuItem"; title = "Customize Toolbar…"; ObjectID = "1UK-8n-QPP"; */ 3 | "1UK-8n-QPP.title" = "Customize Toolbar…"; 4 | 5 | /* Class = "NSMenuItem"; title = "DesktopTime"; ObjectID = "1Xt-HY-uBw"; */ 6 | "1Xt-HY-uBw.title" = "DesktopTime"; 7 | 8 | /* Class = "NSMenuItem"; title = "Enter Full Screen"; ObjectID = "4J7-dP-txa"; */ 9 | "4J7-dP-txa.title" = "Enter Full Screen"; 10 | 11 | /* Class = "NSMenuItem"; title = "Quit DesktopTime"; ObjectID = "4sb-4s-VLi"; */ 12 | "4sb-4s-VLi.title" = "Quit DesktopTime"; 13 | 14 | /* Class = "NSMenuItem"; title = "About DesktopTime"; ObjectID = "5kV-Vb-QxS"; */ 15 | "5kV-Vb-QxS.title" = "About DesktopTime"; 16 | 17 | /* Class = "NSMenu"; title = "Main Menu"; ObjectID = "AYu-sK-qS6"; */ 18 | "AYu-sK-qS6.title" = "Main Menu"; 19 | 20 | /* Class = "NSMenuItem"; title = "Preferences…"; ObjectID = "BOF-NM-1cW"; */ 21 | "BOF-NM-1cW.title" = "Preferences…"; 22 | 23 | /* Class = "NSMenu"; title = "Help"; ObjectID = "F2S-fz-NVQ"; */ 24 | "F2S-fz-NVQ.title" = "Help"; 25 | 26 | /* Class = "NSMenuItem"; title = "DesktopTime Help"; ObjectID = "FKE-Sm-Kum"; */ 27 | "FKE-Sm-Kum.title" = "DesktopTime Help"; 28 | 29 | /* Class = "NSMenuItem"; title = "View"; ObjectID = "H8h-7b-M4v"; */ 30 | "H8h-7b-M4v.title" = "View"; 31 | 32 | /* Class = "NSMenu"; title = "View"; ObjectID = "HyV-fh-RgO"; */ 33 | "HyV-fh-RgO.title" = "View"; 34 | 35 | /* Class = "NSMenuItem"; title = "Show All"; ObjectID = "Kd2-mp-pUS"; */ 36 | "Kd2-mp-pUS.title" = "Show All"; 37 | 38 | /* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "LE2-aR-0XJ"; */ 39 | "LE2-aR-0XJ.title" = "Bring All to Front"; 40 | 41 | /* Class = "NSMenuItem"; title = "Services"; ObjectID = "NMo-om-nkz"; */ 42 | "NMo-om-nkz.title" = "Services"; 43 | 44 | /* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "OY7-WF-poV"; */ 45 | "OY7-WF-poV.title" = "Minimize"; 46 | 47 | /* Class = "NSMenuItem"; title = "Hide DesktopTime"; ObjectID = "Olw-nP-bQN"; */ 48 | "Olw-nP-bQN.title" = "Hide DesktopTime"; 49 | 50 | /* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "R4o-n2-Eq4"; */ 51 | "R4o-n2-Eq4.title" = "Zoom"; 52 | 53 | /* Class = "NSMenu"; title = "Window"; ObjectID = "Td7-aD-5lo"; */ 54 | "Td7-aD-5lo.title" = "Window"; 55 | 56 | /* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "Vdr-fp-XzO"; */ 57 | "Vdr-fp-XzO.title" = "Hide Others"; 58 | 59 | /* Class = "NSMenuItem"; title = "Window"; ObjectID = "aUF-d1-5bR"; */ 60 | "aUF-d1-5bR.title" = "Window"; 61 | 62 | /* Class = "NSMenu"; title = "Services"; ObjectID = "hz9-B4-Xy5"; */ 63 | "hz9-B4-Xy5.title" = "Services"; 64 | 65 | /* Class = "NSMenuItem"; title = "Show Sidebar"; ObjectID = "kIP-vf-haE"; */ 66 | "kIP-vf-haE.title" = "Show Sidebar"; 67 | 68 | /* Class = "NSMenuItem"; title = "Show Toolbar"; ObjectID = "snW-S8-Cw5"; */ 69 | "snW-S8-Cw5.title" = "Show Toolbar"; 70 | 71 | /* Class = "NSMenu"; title = "DesktopTime"; ObjectID = "uQy-DD-JDr"; */ 72 | "uQy-DD-JDr.title" = "DesktopTime"; 73 | 74 | /* Class = "NSMenuItem"; title = "Help"; ObjectID = "wpr-3q-Mcd"; */ 75 | "wpr-3q-Mcd.title" = "Help"; 76 | -------------------------------------------------------------------------------- /DesktopTime/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // DesktopTime 4 | // 5 | // Created by Marcel Dierkes on 17.01.21. 6 | // 7 | 8 | #import 9 | 10 | int main(int argc, const char *argv[]) 11 | { 12 | @autoreleasepool {} 13 | return NSApplicationMain(argc, argv); 14 | } 15 | -------------------------------------------------------------------------------- /DesktopTimeKit/DTIAppGroup/DTIAppGroup.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTIAppGroup.h 3 | // DesktopTimeKit 4 | // 5 | // Created by Marcel Dierkes on 23.12.20. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | FOUNDATION_EXPORT NSString * const DTIAppGroupIdentifier; 13 | 14 | NSURL *DTIAppGroupGetURLWithPathComponent(NSString *); 15 | 16 | NSURL *DTIAppGroupGetDefaultDirectoryURL(void); 17 | 18 | NSUserDefaults *DTIAppGroupGetUserDefaults(void); 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /DesktopTimeKit/DTIAppGroup/DTIAppGroup.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTIAppGroup.m 3 | // DesktopTimeKit 4 | // 5 | // Created by Marcel Dierkes on 23.12.20. 6 | // 7 | 8 | #import "DTIAppGroup.h" 9 | #import "DTIDefines.h" 10 | 11 | NSString * const DTIAppGroupIdentifier = @"group.info.marcel-dierkes.DesktopTime"; 12 | static NSString * const DTIAppGroupDefaultDirectoryName = @"DesktopTime"; 13 | 14 | NSURL *DTIAppGroupGetURLWithPathComponent(NSString *pathComponent) 15 | { 16 | NSCParameterAssert(pathComponent); 17 | 18 | Auto fileManager = NSFileManager.defaultManager; 19 | Auto identifier = DTIAppGroupIdentifier; 20 | Auto container = [fileManager containerURLForSecurityApplicationGroupIdentifier:identifier]; 21 | 22 | Auto URL = [container URLByAppendingPathComponent:pathComponent]; 23 | if(![fileManager fileExistsAtPath:URL.path]) 24 | { 25 | NSError *error; 26 | [fileManager createDirectoryAtURL:URL withIntermediateDirectories:YES attributes:nil error:&error]; 27 | if(error != nil) 28 | { 29 | @throw [NSException exceptionWithName:NSInternalInconsistencyException 30 | reason:@"Could not create app group directory." 31 | userInfo:@{NSUnderlyingErrorKey: error}]; 32 | } 33 | } 34 | return URL; 35 | } 36 | 37 | NSURL *DTIAppGroupGetDefaultDirectoryURL() 38 | { 39 | return DTIAppGroupGetURLWithPathComponent(DTIAppGroupDefaultDirectoryName); 40 | } 41 | 42 | NSUserDefaults *DTIAppGroupGetUserDefaults() 43 | { 44 | return [[NSUserDefaults alloc] initWithSuiteName:DTIAppGroupIdentifier]; 45 | } 46 | -------------------------------------------------------------------------------- /DesktopTimeKit/DTIBatteryStatus/DTIBatteryStatus.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTIBatteryStatus.h 3 | // KeepingYouAwake 4 | // 5 | // Created by Marcel Dierkes on 21.12.15. 6 | // Copyright © 2015 Marcel Dierkes. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | FOUNDATION_EXPORT const CGFloat DTIBatteryStatusUnavailable; 14 | 15 | typedef void(^DTIBatteryStatusChangeBlock)(CGFloat capacity); 16 | 17 | /** 18 | An object that represents the current battery capacity. 19 | */ 20 | @interface DTIBatteryStatus : NSObject 21 | 22 | /** 23 | Returns YES if the current device actually has a built-in battery. 24 | */ 25 | @property (nonatomic, readonly, getter=isBatteryStatusAvailable) BOOL batteryStatusAvailable; 26 | 27 | /// Returns the current battery charging level of the internal battery, 28 | /// or `DTIBatteryStatusUnavailable`. 29 | @property (nonatomic, readonly) CGFloat currentCapacity; 30 | 31 | /// Returns a localized string representation of `currentCapacity`. 32 | @property (nonatomic, readonly) NSString *localizedCurrentCapacity; 33 | 34 | /** 35 | An optional block that will be called when the power source changes and 36 | registerForCapacityChanges was called. 37 | */ 38 | @property (copy, nonatomic, nullable) DTIBatteryStatusChangeBlock capacityChangeHandler; 39 | 40 | /** 41 | Registers the current instance with the runloop to receive power source change notifications. 42 | 43 | The capacityChangeHandler block will be called when a power source change occurs. 44 | */ 45 | - (void)registerForCapacityChangesIfNeeded; 46 | 47 | /** 48 | Unregisters the current instance from all capacity change notifications. 49 | This method will automatically be called on dealloc. 50 | */ 51 | - (void)unregisterFromCapacityChanges; 52 | 53 | @end 54 | 55 | NS_ASSUME_NONNULL_END 56 | -------------------------------------------------------------------------------- /DesktopTimeKit/DTIBatteryStatus/DTIBatteryStatus.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTIBatteryStatus.m 3 | // KeepingYouAwake 4 | // 5 | // Created by Marcel Dierkes on 21.12.15. 6 | // Copyright © 2015 Marcel Dierkes. All rights reserved. 7 | // 8 | 9 | #import "DTIBatteryStatus.h" 10 | #import "DTIDefines.h" 11 | #import 12 | 13 | const CGFloat DTIBatteryStatusUnavailable = -1.0; 14 | 15 | static void DTIBatteryStatusChangeHandler(void *context); 16 | 17 | @interface DTIBatteryStatus () 18 | @property (nonatomic, nullable) CFRunLoopSourceRef runLoopSource; 19 | @property (nonatomic) NSNumberFormatter *percentFormatter; 20 | @end 21 | 22 | @implementation DTIBatteryStatus 23 | 24 | - (instancetype)init 25 | { 26 | self = [super init]; 27 | if(self) 28 | { 29 | } 30 | return self; 31 | } 32 | 33 | - (void)dealloc 34 | { 35 | [self unregisterFromCapacityChanges]; 36 | } 37 | 38 | - (BOOL)isBatteryStatusAvailable 39 | { 40 | Auto powerSourceInfos = [self powerSourceInfos]; 41 | if(powerSourceInfos == nil) 42 | { 43 | return NO; 44 | } 45 | 46 | Auto key = [NSString stringWithUTF8String:kIOPSTypeKey]; 47 | Auto internalBatteryTypeKey = [NSString stringWithUTF8String:kIOPSInternalBatteryType]; 48 | NSString *batteryType = powerSourceInfos[key]; 49 | return [batteryType isEqualToString:internalBatteryTypeKey]; 50 | } 51 | 52 | - (void)configurePercentFormatter 53 | { 54 | Auto locale = NSLocale.currentLocale; 55 | Auto nf = [NSNumberFormatter new]; 56 | nf.numberStyle = NSNumberFormatterPercentStyle; 57 | nf.locale = locale; 58 | nf.allowsFloats = NO; 59 | nf.formattingContext = NSFormattingContextStandalone; 60 | nf.lenient = YES; 61 | 62 | nf.minimum = @0.0f; 63 | nf.maximum = @100.0f; 64 | 65 | self.percentFormatter = nf; 66 | } 67 | 68 | - (NSString *)localizedCurrentCapacity 69 | { 70 | CGFloat capacity = self.currentCapacity; 71 | if(capacity == DTIBatteryStatusUnavailable) { return @""; } 72 | 73 | if(self.percentFormatter == nil) 74 | { 75 | [self configurePercentFormatter]; 76 | } 77 | 78 | return [self.percentFormatter stringFromNumber:@(capacity / 100.0f)]; 79 | } 80 | 81 | - (CGFloat)currentCapacity 82 | { 83 | Auto powerSourceInfos = [self powerSourceInfos]; 84 | if(powerSourceInfos == nil) 85 | { 86 | return DTIBatteryStatusUnavailable; 87 | } 88 | 89 | Auto key = [NSString stringWithUTF8String:kIOPSCurrentCapacityKey]; 90 | NSNumber *capacity = powerSourceInfos[key]; 91 | if(capacity == nil) 92 | { 93 | return DTIBatteryStatusUnavailable; 94 | } 95 | 96 | return capacity.floatValue; 97 | } 98 | 99 | - (nullable NSDictionary *)powerSourceInfos 100 | { 101 | Auto blob = IOPSCopyPowerSourcesInfo(); 102 | Auto sourcesList = IOPSCopyPowerSourcesList(blob); 103 | CFRelease(blob); 104 | 105 | if(CFArrayGetCount(sourcesList) == 0) { 106 | CFRelease(sourcesList); 107 | return nil; 108 | } 109 | 110 | Auto powerSource = IOPSGetPowerSourceDescription(blob, CFArrayGetValueAtIndex(sourcesList, 0)); 111 | CFRetain(powerSource); 112 | CFRelease(sourcesList); 113 | 114 | CFAutorelease(powerSource); 115 | return (__bridge NSDictionary *)powerSource; 116 | } 117 | 118 | #pragma mark - Capacity Change Handler 119 | 120 | - (void)registerForCapacityChangesIfNeeded 121 | { 122 | if(self.runLoopSource) 123 | { 124 | return; 125 | } 126 | 127 | Auto runLoopSource = IOPSNotificationCreateRunLoopSource(DTIBatteryStatusChangeHandler, (__bridge void *)self); 128 | CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopDefaultMode); 129 | 130 | self.runLoopSource = runLoopSource; 131 | CFRelease(runLoopSource); 132 | } 133 | 134 | - (void)unregisterFromCapacityChanges 135 | { 136 | if(!self.runLoopSource) 137 | { 138 | return; 139 | } 140 | 141 | CFRunLoopRemoveSource(CFRunLoopGetCurrent(), self.runLoopSource, kCFRunLoopDefaultMode); 142 | self.runLoopSource = nil; 143 | } 144 | 145 | @end 146 | 147 | #pragma mark - DTIBatteryStatusChangeHandler 148 | 149 | static void DTIBatteryStatusChangeHandler(void *context) 150 | { 151 | Auto batteryStatus = (__bridge DTIBatteryStatus *)context; 152 | if(batteryStatus.capacityChangeHandler) 153 | { 154 | batteryStatus.capacityChangeHandler(batteryStatus.currentCapacity); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /DesktopTimeKit/DTILayout/DTILayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTILayout.h 3 | // DesktopTimeKit 4 | // 5 | // Created by Marcel Dierkes on 04.02.21. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | typedef NS_ENUM(NSUInteger, DTILayoutDisplayMode) 14 | { 15 | DTILayoutDisplayModeAllDisplays = 0, 16 | DTILayoutDisplayModeOnlyMainDisplay, 17 | DTILayoutDisplayModeOnlySecondaryDisplays, 18 | }; 19 | 20 | typedef NS_CLOSED_ENUM(NSUInteger, DTILayoutPosition) { 21 | DTILayoutPositionUndefined = 0, 22 | DTILayoutPositionTopLeft, 23 | DTILayoutPositionTopMiddle, 24 | DTILayoutPositionTopRight, 25 | DTILayoutPositionBottomLeft, 26 | DTILayoutPositionBottomMiddle, 27 | DTILayoutPositionBottomRight 28 | }; 29 | 30 | @protocol DTILayoutDelegate; 31 | 32 | @interface DTILayout : NSObject 33 | @property (weak, nonatomic, nullable) id delegate; 34 | 35 | @property (nonatomic) DTILayoutDisplayMode displayMode; 36 | 37 | @property (nonatomic) DTILayoutElement topLeftElement; 38 | @property (nonatomic) DTILayoutElement topMiddleElement; 39 | @property (nonatomic) DTILayoutElement topRightElement; 40 | 41 | @property (nonatomic) DTILayoutElement bottomLeftElement; 42 | @property (nonatomic) DTILayoutElement bottomMiddleElement; 43 | @property (nonatomic) DTILayoutElement bottomRightElement; 44 | 45 | - (DTILayoutElement)elementAtPosition:(DTILayoutPosition)position; 46 | 47 | @end 48 | 49 | @protocol DTILayoutDelegate 50 | @optional 51 | - (void)layoutDidChange:(DTILayout *)layout keyPath:(NSString *)keyPath; 52 | @end 53 | 54 | NS_ASSUME_NONNULL_END 55 | -------------------------------------------------------------------------------- /DesktopTimeKit/DTILayout/DTILayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTILayout.m 3 | // DesktopTimeKit 4 | // 5 | // Created by Marcel Dierkes on 04.02.21. 6 | // 7 | 8 | #import "DTILayout.h" 9 | #import "DTIDefines.h" 10 | 11 | @interface DTILayout () 12 | @end 13 | 14 | @implementation DTILayout 15 | 16 | - (instancetype)init 17 | { 18 | self = [super init]; 19 | if(self) 20 | { 21 | self.bottomRightElement = DTILayoutElementDateTime; 22 | } 23 | return self; 24 | } 25 | 26 | - (DTILayoutElement)elementAtPosition:(DTILayoutPosition)position 27 | { 28 | switch(position) 29 | { 30 | case DTILayoutPositionUndefined: return DTILayoutElementNone; 31 | case DTILayoutPositionTopLeft: return self.topLeftElement; 32 | case DTILayoutPositionTopMiddle: return self.topMiddleElement; 33 | case DTILayoutPositionTopRight: return self.topRightElement; 34 | case DTILayoutPositionBottomLeft: return self.bottomLeftElement; 35 | case DTILayoutPositionBottomMiddle: return self.bottomMiddleElement; 36 | case DTILayoutPositionBottomRight: return self.bottomRightElement; 37 | } 38 | } 39 | 40 | #pragma mark - Description 41 | 42 | - (NSString *)description 43 | { 44 | return [NSString stringWithFormat: 45 | @"%@:\n" 46 | @"[%@] - [%@] - [%@]\n" 47 | @"[%@] - [%@] - [%@]", 48 | super.description, 49 | DTILayoutElementGetLocalizedName(self.topLeftElement), 50 | DTILayoutElementGetLocalizedName(self.topMiddleElement), 51 | DTILayoutElementGetLocalizedName(self.topRightElement), 52 | DTILayoutElementGetLocalizedName(self.bottomLeftElement), 53 | DTILayoutElementGetLocalizedName(self.bottomMiddleElement), 54 | DTILayoutElementGetLocalizedName(self.bottomRightElement) 55 | ]; 56 | } 57 | 58 | #pragma mark - KVO 59 | 60 | - (void)didChangeValueForKey:(NSString *)key 61 | { 62 | [super didChangeValueForKey:key]; 63 | 64 | Auto delegate = self.delegate; 65 | if([delegate respondsToSelector:@selector(layoutDidChange:keyPath:)]) 66 | { 67 | [delegate layoutDidChange:self keyPath:key]; 68 | } 69 | } 70 | 71 | #pragma mark - NSSecureCoding 72 | 73 | #define kCodingKeyDisplayMode @"DTIDisplayMode" 74 | 75 | #define kCodingKeyTopLeftElement @"DTITopLeftElement" 76 | #define kCodingKeyTopMiddleElement @"DTITopMiddleElement" 77 | #define kCodingKeyTopRightElement @"DTITopRightElement" 78 | 79 | #define kCodingKeyBottomLeftElement @"DTIBottomLeftElement" 80 | #define kCodingKeyBottomMiddleElement @"DTIBottomMiddleElement" 81 | #define kCodingKeyBottomRightElement @"DTIBottomRightElement" 82 | 83 | + (BOOL)supportsSecureCoding 84 | { 85 | return YES; 86 | } 87 | 88 | - (instancetype)initWithCoder:(NSCoder *)coder 89 | { 90 | self = [self init]; 91 | if(self) 92 | { 93 | self.displayMode = (DTILayoutDisplayMode)[coder decodeIntegerForKey:kCodingKeyDisplayMode]; 94 | 95 | self.topLeftElement = (DTILayoutElement)[coder decodeIntegerForKey:kCodingKeyTopLeftElement]; 96 | self.topMiddleElement = (DTILayoutElement)[coder decodeIntegerForKey:kCodingKeyTopMiddleElement]; 97 | self.topRightElement = (DTILayoutElement)[coder decodeIntegerForKey:kCodingKeyTopRightElement]; 98 | 99 | self.bottomLeftElement = (DTILayoutElement)[coder decodeIntegerForKey:kCodingKeyBottomLeftElement]; 100 | self.bottomMiddleElement = (DTILayoutElement)[coder decodeIntegerForKey:kCodingKeyBottomMiddleElement]; 101 | self.bottomRightElement = (DTILayoutElement)[coder decodeIntegerForKey:kCodingKeyBottomRightElement]; 102 | } 103 | return self; 104 | } 105 | 106 | - (void)encodeWithCoder:(NSCoder *)coder 107 | { 108 | [coder encodeInteger:(NSInteger)self.displayMode forKey:kCodingKeyDisplayMode]; 109 | 110 | [coder encodeInteger:(NSInteger)self.topLeftElement forKey:kCodingKeyTopLeftElement]; 111 | [coder encodeInteger:(NSInteger)self.topMiddleElement forKey:kCodingKeyTopMiddleElement]; 112 | [coder encodeInteger:(NSInteger)self.topRightElement forKey:kCodingKeyTopRightElement]; 113 | 114 | [coder encodeInteger:(NSInteger)self.bottomLeftElement forKey:kCodingKeyBottomLeftElement]; 115 | [coder encodeInteger:(NSInteger)self.bottomMiddleElement forKey:kCodingKeyBottomMiddleElement]; 116 | [coder encodeInteger:(NSInteger)self.bottomRightElement forKey:kCodingKeyBottomRightElement]; 117 | } 118 | 119 | @end 120 | -------------------------------------------------------------------------------- /DesktopTimeKit/DTILayout/DTILayoutElement.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTILayoutElement.h 3 | // DesktopTimeKit 4 | // 5 | // Created by Marcel Dierkes on 04.02.21. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | typedef NS_ENUM(NSUInteger, DTILayoutElement) 13 | { 14 | DTILayoutElementNone = 0, 15 | DTILayoutElementDateTime, 16 | DTILayoutElementDate, 17 | DTILayoutElementTime, 18 | DTILayoutElementBatteryLevel, 19 | }; 20 | 21 | NSString *DTILayoutElementGetLocalizedName(DTILayoutElement); 22 | NSArray *DTILayoutElementGetAllElements(void); 23 | 24 | NS_ASSUME_NONNULL_END 25 | -------------------------------------------------------------------------------- /DesktopTimeKit/DTILayout/DTILayoutElement.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTILayoutElement.m 3 | // DesktopTimeKit 4 | // 5 | // Created by Marcel Dierkes on 04.02.21. 6 | // 7 | 8 | #import "DTILayoutElement.h" 9 | #import "DTIDefines.h" 10 | 11 | NSString *DTILayoutElementGetLocalizedName(DTILayoutElement layoutElement) 12 | { 13 | switch(layoutElement) 14 | { 15 | case DTILayoutElementNone: return NSLocalizedString(@"–", @"–"); 16 | case DTILayoutElementDateTime: return NSLocalizedString(@"Date & Time", @"Date & Time"); 17 | case DTILayoutElementDate: return NSLocalizedString(@"Date", @"Date"); 18 | case DTILayoutElementTime: return NSLocalizedString(@"Time", @"Time"); 19 | case DTILayoutElementBatteryLevel: return NSLocalizedString(@"Battery Level", @"Battery Level"); 20 | default: return @""; 21 | } 22 | } 23 | 24 | NSArray *DTILayoutElementGetAllElements() 25 | { 26 | return @[ 27 | DTILayoutElementGetLocalizedName(DTILayoutElementNone), 28 | DTILayoutElementGetLocalizedName(DTILayoutElementDateTime), 29 | DTILayoutElementGetLocalizedName(DTILayoutElementDate), 30 | DTILayoutElementGetLocalizedName(DTILayoutElementTime), 31 | DTILayoutElementGetLocalizedName(DTILayoutElementBatteryLevel) 32 | ]; 33 | } 34 | -------------------------------------------------------------------------------- /DesktopTimeKit/DTILayout/Extensions/NSDateFormatter+DTILayoutElement.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSDateFormatter+DTILayoutElement.h 3 | // DesktopTimeKit 4 | // 5 | // Created by Marcel Dierkes on 06.02.21. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NSDateFormatter (DTILayoutElement) 14 | 15 | + (instancetype)dti_dateFormatterForLayoutElement:(DTILayoutElement)layoutElement; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /DesktopTimeKit/DTILayout/Extensions/NSDateFormatter+DTILayoutElement.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSDateFormatter+DTILayoutElement.m 3 | // DesktopTimeKit 4 | // 5 | // Created by Marcel Dierkes on 06.02.21. 6 | // 7 | 8 | #import "NSDateFormatter+DTILayoutElement.h" 9 | #import "DTIDefines.h" 10 | 11 | @implementation NSDateFormatter (DTILayoutElement) 12 | 13 | + (instancetype)dti_dateFormatterForLayoutElement:(DTILayoutElement)layoutElement 14 | { 15 | Auto locale = NSLocale.currentLocale; 16 | Auto df = [NSDateFormatter new]; 17 | df.formattingContext = NSFormattingContextStandalone; 18 | 19 | switch(layoutElement) 20 | { 21 | case DTILayoutElementDate: 22 | df.dateStyle = NSDateFormatterLongStyle; 23 | df.timeStyle = NSDateFormatterNoStyle; 24 | break; 25 | case DTILayoutElementTime: 26 | df.dateStyle = NSDateFormatterNoStyle; 27 | df.timeStyle = NSDateFormatterShortStyle; 28 | break; 29 | default: 30 | df.dateFormat = [NSDateFormatter dateFormatFromTemplate:@"eddMMMHHmm" 31 | options:0 32 | locale:locale]; 33 | break; 34 | } 35 | 36 | return df; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /DesktopTimeKit/DTINotificationCenter/DTINotificationCenter.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTINotificationCenter.h 3 | // DesktopTimeKit 4 | // 5 | // Created by Marcel Dierkes on 09.01.21. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | typedef NSNotificationName DTINotificationName NS_EXTENSIBLE_STRING_ENUM; 13 | 14 | FOUNDATION_EXPORT const DTINotificationName DTIAppShouldTerminateNotification; 15 | FOUNDATION_EXPORT const DTINotificationName DTILayoutDidChangeNotification; 16 | 17 | @interface DTINotificationCenter : NSObject 18 | @property (class, nonatomic, readonly) DTINotificationCenter *defaultCenter; 19 | 20 | - (void)postNotification:(DTINotificationName)notification; 21 | 22 | - (void)addObserver:(id)observer selector:(SEL)selector name:(DTINotificationName)notificationName; 23 | - (void)removeObserver:(id)observer name:(DTINotificationName)notificationName; 24 | 25 | @end 26 | 27 | NS_ASSUME_NONNULL_END 28 | -------------------------------------------------------------------------------- /DesktopTimeKit/DTINotificationCenter/DTINotificationCenter.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTINotificationCenter.m 3 | // DesktopTimeKit 4 | // 5 | // Created by Marcel Dierkes on 09.01.21. 6 | // 7 | 8 | #import "DTINotificationCenter.h" 9 | #import "DTIDefines.h" 10 | 11 | const DTINotificationName DTIAppShouldTerminateNotification = @"DTIAppShouldTerminateNotification"; 12 | const DTINotificationName DTILayoutDidChangeNotification = @"DTILayoutDidChangeNotification"; 13 | 14 | @interface DTINotificationCenter () 15 | @property (nonatomic) NSDistributedNotificationCenter *distributedCenter; 16 | @end 17 | 18 | @implementation DTINotificationCenter 19 | 20 | + (instancetype)defaultCenter 21 | { 22 | static dispatch_once_t once; 23 | static DTINotificationCenter *defaultCenter; 24 | dispatch_once(&once, ^{ 25 | defaultCenter = [self new]; 26 | }); 27 | return defaultCenter; 28 | } 29 | 30 | - (instancetype)init 31 | { 32 | self = [super init]; 33 | if(self) 34 | { 35 | self.distributedCenter = NSDistributedNotificationCenter.defaultCenter; 36 | } 37 | return self; 38 | } 39 | 40 | - (void)postNotification:(DTINotificationName)notification 41 | { 42 | NSParameterAssert(notification); 43 | 44 | [self.distributedCenter postNotificationName:notification 45 | object:nil userInfo:nil 46 | deliverImmediately:YES]; 47 | } 48 | 49 | - (void)addObserver:(id)observer selector:(SEL)selector name:(DTINotificationName)notificationName 50 | { 51 | NSParameterAssert(observer); 52 | NSParameterAssert(selector); 53 | NSParameterAssert(notificationName); 54 | 55 | [self.distributedCenter addObserver:observer 56 | selector:selector 57 | name:notificationName 58 | object:nil]; 59 | } 60 | 61 | - (void)removeObserver:(id)observer name:(DTINotificationName)notificationName 62 | { 63 | NSParameterAssert(observer); 64 | NSParameterAssert(notificationName); 65 | 66 | [self.distributedCenter removeObserver:observer name:notificationName object:nil]; 67 | } 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /DesktopTimeKit/DTIPreferences/DTIDefaultsProvider.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTIDefaultsProvider.h 3 | // DesktopTimeKit 4 | // 5 | // Created by Marcel Dierkes on 23.12.20. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @protocol DTIDefaultsProvider 13 | - (void)removeObjectForKey:(NSString *)defaultName; 14 | 15 | - (nullable id)objectForKey:(NSString *)defaultName; 16 | - (void)setObject:(nullable id)value forKey:(NSString *)defaultName; 17 | 18 | - (nullable NSString *)stringForKey:(NSString *)defaultName; 19 | - (nullable NSData *)dataForKey:(NSString *)defaultName; 20 | 21 | - (NSInteger)integerForKey:(NSString *)defaultName; 22 | - (float)floatForKey:(NSString *)defaultName; 23 | - (double)doubleForKey:(NSString *)defaultName; 24 | - (BOOL)boolForKey:(NSString *)defaultName; 25 | - (nullable NSURL *)URLForKey:(NSString *)defaultName; 26 | 27 | - (void)setInteger:(NSInteger)value forKey:(NSString *)defaultName; 28 | - (void)setFloat:(float)value forKey:(NSString *)defaultName; 29 | - (void)setDouble:(double)value forKey:(NSString *)defaultName; 30 | - (void)setBool:(BOOL)value forKey:(NSString *)defaultName; 31 | - (void)setURL:(nullable NSURL *)url forKey:(NSString *)defaultName; 32 | @end 33 | 34 | @interface NSUserDefaults (DTIDefaultsProvider) 35 | @end 36 | 37 | NS_ASSUME_NONNULL_END 38 | -------------------------------------------------------------------------------- /DesktopTimeKit/DTIPreferences/DTIPreferences.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTIPreferences.h 3 | // DesktopTimeKit 4 | // 5 | // Created by Marcel Dierkes on 23.12.20. 6 | // 7 | 8 | #import 9 | #import 10 | #import 11 | #import 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | FOUNDATION_EXPORT NSString * const DTIFirstLaunchFinishedKey; 16 | FOUNDATION_EXPORT NSString * const DTIAvoidsDockOverlappingKey; 17 | FOUNDATION_EXPORT NSString * const DTIAvoidsMenuBarOverlappingKey; 18 | FOUNDATION_EXPORT NSString * const DTILayoutKey; 19 | FOUNDATION_EXPORT NSString * const DTIDateTimeFontKey; 20 | FOUNDATION_EXPORT NSString * const DTIDateTimeTextColorKey; 21 | FOUNDATION_EXPORT NSString * const DTIDateTimeShadowColorKey; 22 | FOUNDATION_EXPORT NSString * const DTIBatteryLevelFontKey; 23 | FOUNDATION_EXPORT NSString * const DTIBatteryLevelTextColorKey; 24 | FOUNDATION_EXPORT NSString * const DTIBatteryLevelShadowColorKey; 25 | 26 | @class DTILayout; 27 | 28 | @interface DTIPreferences : NSObject 29 | @property (class, nonatomic, readonly) DTIPreferences *sharedPreferences; 30 | @property (nonatomic, readonly) id defaults; 31 | 32 | @property (nonatomic, getter=isFirstLaunchFinished) BOOL firstLaunchFinished; 33 | @property (nonatomic) BOOL avoidsDockOverlapping; 34 | @property (nonatomic) BOOL avoidsMenuBarOverlapping; 35 | 36 | @property (nonatomic, null_resettable) DTILayout *layout; 37 | 38 | @property (nonatomic, null_resettable) NSFont *dateTimeFont; 39 | @property (nonatomic, null_resettable) NSColor *dateTimeTextColor; 40 | @property (nonatomic, null_resettable) NSColor *dateTimeShadowColor; 41 | 42 | @property (nonatomic, null_resettable) NSFont *batteryLevelFont; 43 | @property (nonatomic, null_resettable) NSColor *batteryLevelTextColor; 44 | @property (nonatomic, null_resettable) NSColor *batteryLevelShadowColor; 45 | 46 | - (instancetype)init; 47 | - (instancetype)initWithDefaultsProvider:(id)defaults NS_DESIGNATED_INITIALIZER; 48 | 49 | /// Performs the given block only when the app is launched for the first time 50 | /// and marks the first launch as completed. 51 | /// @param block A custom block 52 | - (void)performBlockOnFirstLaunch:(void(NS_NOESCAPE ^)(void))block; 53 | 54 | @end 55 | 56 | NS_ASSUME_NONNULL_END 57 | -------------------------------------------------------------------------------- /DesktopTimeKit/DTIPreferences/DTIPreferences.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTIPreferences.m 3 | // DesktopTimeKit 4 | // 5 | // Created by Marcel Dierkes on 23.12.20. 6 | // 7 | 8 | #import "DTIPreferences.h" 9 | #import "DTIDefines.h" 10 | #import "DTIAppGroup.h" 11 | #import "DTILayout.h" 12 | #import "DTINotificationCenter.h" 13 | 14 | NSString * const DTIFirstLaunchFinishedKey = @"info.marcel-dierkes.DesktopTime.FirstLaunchFinshed"; 15 | NSString * const DTIAvoidsDockOverlappingKey = @"info.marcel-dierkes.DesktopTime.AvoidsDockOverlapping"; 16 | NSString * const DTIAvoidsMenuBarOverlappingKey = @"info.marcel-dierkes.DesktopTime.AvoidsMenuBarOverlapping"; 17 | 18 | NSString * const DTILayoutKey = @"info.marcel-dierkes.DesktopTime.Layout"; 19 | NSString * const DTIDateTimeFontKey = @"info.marcel-dierkes.DesktopTime.DateTime.Font"; 20 | NSString * const DTIDateTimeTextColorKey = @"info.marcel-dierkes.DesktopTime.DateTime.TextColor"; 21 | NSString * const DTIDateTimeShadowColorKey = @"info.marcel-dierkes.DesktopTime.DateTime.ShadowColor"; 22 | 23 | NSString * const DTIBatteryLevelFontKey = @"info.marcel-dierkes.DesktopTime.BatteryLevel.Font"; 24 | NSString * const DTIBatteryLevelTextColorKey = @"info.marcel-dierkes.DesktopTime.BatteryLevel.TextColor"; 25 | NSString * const DTIBatteryLevelShadowColorKey = @"info.marcel-dierkes.DesktopTime.BatteryLevel.ShadowColor"; 26 | 27 | NS_INLINE NSFont *_Nullable DTIFontForKey(id defaults, NSString *key) 28 | { 29 | NSCParameterAssert(defaults); 30 | 31 | Auto data = [defaults dataForKey:key]; 32 | if(data == nil) { return nil; } 33 | 34 | NSError *error; 35 | Auto font = (NSFont *)[NSKeyedUnarchiver unarchivedObjectOfClass:[NSFont class] 36 | fromData:data error:&error]; 37 | if(error != nil) 38 | { 39 | DTILog(@"Failed to unarchive font. %@", error.userInfo); 40 | return nil; 41 | } 42 | 43 | return font; 44 | } 45 | 46 | NS_INLINE void DTISetFontForKey(id defaults, NSFont *_Nullable font, NSString *key) 47 | { 48 | NSCParameterAssert(defaults); 49 | 50 | if(font == nil) 51 | { 52 | [defaults removeObjectForKey:key]; 53 | return; 54 | } 55 | 56 | NSError *error; 57 | Auto data = [NSKeyedArchiver archivedDataWithRootObject:font 58 | requiringSecureCoding:YES 59 | error:&error]; 60 | if(error != nil) 61 | { 62 | DTILog(@"Failed to archive font. %@", error.userInfo); 63 | return; 64 | } 65 | 66 | [defaults setObject:data forKey:key]; 67 | } 68 | 69 | NS_INLINE NSColor *_Nullable DTIColorForKey(id defaults, NSString *key) 70 | { 71 | NSCParameterAssert(defaults); 72 | 73 | Auto data = [defaults dataForKey:key]; 74 | if(data == nil) { return nil; } 75 | 76 | NSError *error; 77 | Auto color = (NSColor *)[NSKeyedUnarchiver unarchivedObjectOfClass:[NSColor class] 78 | fromData:data error:&error]; 79 | if(error != nil) 80 | { 81 | DTILog(@"Failed to unarchive color. %@", error.userInfo); 82 | return nil; 83 | } 84 | 85 | return color; 86 | } 87 | 88 | NS_INLINE void DTISetColorForKey(id defaults, NSColor *_Nullable color, NSString *key) 89 | { 90 | NSCParameterAssert(defaults); 91 | 92 | if(color == nil) 93 | { 94 | [defaults removeObjectForKey:key]; 95 | return; 96 | } 97 | 98 | NSError *error; 99 | Auto data = [NSKeyedArchiver archivedDataWithRootObject:color 100 | requiringSecureCoding:YES 101 | error:&error]; 102 | if(error != nil) 103 | { 104 | DTILog(@"Failed to archive color. %@", error.userInfo); 105 | return; 106 | } 107 | 108 | [defaults setObject:data forKey:key]; 109 | } 110 | 111 | @interface DTIPreferences () 112 | @property (nonatomic, readwrite) id defaults; 113 | @end 114 | 115 | @implementation DTIPreferences 116 | 117 | + (instancetype)sharedPreferences 118 | { 119 | static dispatch_once_t once; 120 | static id sharedInstance; 121 | dispatch_once(&once, ^{ 122 | sharedInstance = [[self alloc] init]; 123 | }); 124 | return sharedInstance; 125 | } 126 | 127 | - (instancetype)init 128 | { 129 | return [self initWithDefaultsProvider:DTIAppGroupGetUserDefaults()]; 130 | } 131 | 132 | - (instancetype)initWithDefaultsProvider:(id)defaults 133 | { 134 | NSParameterAssert(defaults); 135 | 136 | self = [super init]; 137 | if(self) 138 | { 139 | self.defaults = defaults; 140 | } 141 | return self; 142 | } 143 | 144 | #pragma mark - First Launch 145 | 146 | - (BOOL)isFirstLaunchFinished 147 | { 148 | return [self.defaults boolForKey:DTIFirstLaunchFinishedKey]; 149 | } 150 | 151 | - (void)setFirstLaunchFinished:(BOOL)firstLaunchFinished 152 | { 153 | if(firstLaunchFinished == YES) 154 | { 155 | [self.defaults setBool:firstLaunchFinished forKey:DTIFirstLaunchFinishedKey]; 156 | } 157 | else 158 | { 159 | [self.defaults removeObjectForKey:DTIFirstLaunchFinishedKey]; 160 | } 161 | } 162 | 163 | - (void)performBlockOnFirstLaunch:(void(NS_NOESCAPE ^)(void))block 164 | { 165 | if([self isFirstLaunchFinished] == NO) 166 | { 167 | self.firstLaunchFinished = YES; 168 | block(); 169 | } 170 | } 171 | 172 | #pragma mark - Avoids Overlapping 173 | 174 | - (BOOL)avoidsDockOverlapping 175 | { 176 | return [self.defaults boolForKey:DTIAvoidsDockOverlappingKey]; 177 | } 178 | 179 | - (void)setAvoidsDockOverlapping:(BOOL)avoidsDockOverlapping 180 | { 181 | [self.defaults setBool:avoidsDockOverlapping forKey:DTIAvoidsDockOverlappingKey]; 182 | } 183 | 184 | - (BOOL)avoidsMenuBarOverlapping 185 | { 186 | return [self.defaults boolForKey:DTIAvoidsMenuBarOverlappingKey]; 187 | } 188 | 189 | - (void)setAvoidsMenuBarOverlapping:(BOOL)avoidsDockOverlapping 190 | { 191 | [self.defaults setBool:avoidsDockOverlapping forKey:DTIAvoidsMenuBarOverlappingKey]; 192 | } 193 | 194 | #pragma mark - Layout 195 | 196 | - (DTILayout *)layout 197 | { 198 | Auto loadLayout = ^DTILayout *{ 199 | Auto data = [self.defaults dataForKey:DTILayoutKey]; 200 | if(data == nil) { return nil; } 201 | 202 | NSError *error; 203 | Auto layout = (DTILayout *)[NSKeyedUnarchiver unarchivedObjectOfClass:[DTILayout class] 204 | fromData:data error:&error]; 205 | if(error != nil) 206 | { 207 | DTILog(@"Failed to unarchive layout. %@", error.userInfo); 208 | return nil; 209 | } 210 | return layout; 211 | }; 212 | 213 | return loadLayout() ?: [DTILayout new]; 214 | } 215 | 216 | - (void)setLayout:(DTILayout *)layout 217 | { 218 | Auto notificationCenter = DTINotificationCenter.defaultCenter; 219 | if(layout == nil) 220 | { 221 | [self.defaults removeObjectForKey:DTILayoutKey]; 222 | 223 | [notificationCenter postNotification:DTILayoutDidChangeNotification]; 224 | return; 225 | } 226 | 227 | // Discard empty layouts 228 | if(layout.topLeftElement == DTILayoutElementNone 229 | && layout.topMiddleElement == DTILayoutElementNone 230 | && layout.topRightElement == DTILayoutElementNone 231 | && layout.bottomLeftElement == DTILayoutElementNone 232 | && layout.bottomMiddleElement == DTILayoutElementNone 233 | && layout.bottomRightElement == DTILayoutElementNone) { 234 | [self.defaults removeObjectForKey:DTILayoutKey]; 235 | 236 | [notificationCenter postNotification:DTILayoutDidChangeNotification]; 237 | return; 238 | } 239 | 240 | NSError *error; 241 | Auto data = [NSKeyedArchiver archivedDataWithRootObject:layout requiringSecureCoding:YES error:&error]; 242 | if(error != nil) 243 | { 244 | DTILog(@"Failed to archive layout. %@", error.userInfo); 245 | return; 246 | } 247 | 248 | [self.defaults setObject:data forKey:DTILayoutKey]; 249 | 250 | [notificationCenter postNotification:DTILayoutDidChangeNotification]; 251 | } 252 | 253 | #pragma mark - Date/Time Appearance 254 | 255 | - (NSFont *)dateTimeFont 256 | { 257 | return DTIFontForKey(self.defaults, DTIDateTimeFontKey) 258 | ?: [NSFont systemFontOfSize:16.0f weight:NSFontWeightSemibold]; 259 | } 260 | 261 | - (void)setDateTimeFont:(NSFont *)font 262 | { 263 | DTISetFontForKey(self.defaults, font, DTIDateTimeFontKey); 264 | } 265 | 266 | - (NSColor *)dateTimeTextColor 267 | { 268 | return DTIColorForKey(self.defaults, DTIDateTimeTextColorKey) 269 | ?: [NSColor colorWithWhite:0.94 alpha:1.0f]; 270 | } 271 | 272 | - (void)setDateTimeTextColor:(NSColor *)textColor 273 | { 274 | DTISetColorForKey(self.defaults, textColor, DTIDateTimeTextColorKey); 275 | } 276 | 277 | - (NSColor *)dateTimeShadowColor 278 | { 279 | return DTIColorForKey(self.defaults, DTIDateTimeShadowColorKey) 280 | ?: [NSColor.blackColor colorWithAlphaComponent:0.7f]; 281 | } 282 | 283 | - (void)setDateTimeShadowColor:(NSColor *)shadowColor 284 | { 285 | DTISetColorForKey(self.defaults, shadowColor, DTIDateTimeShadowColorKey); 286 | } 287 | 288 | #pragma mark - Battery Level Appearance 289 | 290 | - (NSFont *)batteryLevelFont 291 | { 292 | return DTIFontForKey(self.defaults, DTIBatteryLevelFontKey) 293 | ?: [NSFont systemFontOfSize:16.0f weight:NSFontWeightSemibold]; 294 | } 295 | 296 | - (void)setBatteryLevelFont:(NSFont *)font 297 | { 298 | DTISetFontForKey(self.defaults, font, DTIBatteryLevelFontKey); 299 | } 300 | 301 | - (NSColor *)batteryLevelTextColor 302 | { 303 | return DTIColorForKey(self.defaults, DTIBatteryLevelTextColorKey) 304 | ?: [NSColor colorWithWhite:0.94 alpha:1.0f]; 305 | } 306 | 307 | - (void)setBatteryLevelTextColor:(NSColor *)textColor 308 | { 309 | DTISetColorForKey(self.defaults, textColor, DTIBatteryLevelTextColorKey); 310 | } 311 | 312 | - (NSColor *)batteryLevelShadowColor 313 | { 314 | return DTIColorForKey(self.defaults, DTIBatteryLevelShadowColorKey) 315 | ?: [NSColor.blackColor colorWithAlphaComponent:0.7f]; 316 | } 317 | 318 | - (void)setBatteryLevelShadowColor:(NSColor *)shadowColor 319 | { 320 | DTISetColorForKey(self.defaults, shadowColor, DTIBatteryLevelShadowColorKey); 321 | } 322 | 323 | @end 324 | -------------------------------------------------------------------------------- /DesktopTimeKit/DTIPreferences/Extensions/NSAlert+DTIPreferences.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSAlert+DTIPreferences.h 3 | // DesktopTimeKit 4 | // 5 | // Created by Marcel Dierkes on 26.02.21. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface NSAlert (DTIPreferences) 13 | @property (class, nonatomic, readonly) NSAlert *dti_welcomeAlert; 14 | 15 | - (void)dti_runWelcomeAlertWithOpenPreferencesHandler:(void(^)(void))handler; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /DesktopTimeKit/DTIPreferences/Extensions/NSAlert+DTIPreferences.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSAlert+DTIPreferences.m 3 | // DesktopTimeKit 4 | // 5 | // Created by Marcel Dierkes on 26.02.21. 6 | // 7 | 8 | #import "NSAlert+DTIPreferences.h" 9 | #import "DTIDefines.h" 10 | 11 | #define DTI_L10N_WELCOME_TITLE NSLocalizedString(@"Welcome to DesktopTime", @"Welcome to DesktopTime") 12 | #define DTI_L10N_WELCOME_MESSAGE NSLocalizedString( \ 13 | @"DesktopTime allows you to show the time, date and other information on your desktop. Open the app again to show preferences and to completely quit the app.", \ 14 | @"DesktopTime allows you to show the time, date and other information on your desktop. Open the app again to show preferences and to completely quit the app.") 15 | #define DTI_L10N_WELCOME_OK NSLocalizedString(@"OK", @"OK") 16 | #define DTI_L10N_WELCOME_OPEN_PREFERENCES NSLocalizedString(@"Open Preferences", @"Open Preferences") 17 | 18 | @implementation NSAlert (DTIPreferences) 19 | 20 | + (NSAlert *)dti_welcomeAlert 21 | { 22 | Auto alert = [NSAlert new]; 23 | alert.alertStyle = NSAlertStyleInformational; 24 | alert.informativeText = DTI_L10N_WELCOME_MESSAGE; 25 | alert.messageText = DTI_L10N_WELCOME_TITLE; 26 | [alert addButtonWithTitle:DTI_L10N_WELCOME_OK]; 27 | [alert addButtonWithTitle:DTI_L10N_WELCOME_OPEN_PREFERENCES]; 28 | return alert; 29 | } 30 | 31 | - (void)dti_runWelcomeAlertWithOpenPreferencesHandler:(void (^)(void))handler 32 | { 33 | NSParameterAssert(handler); 34 | 35 | NSModalResponse response = [self runModal]; 36 | if(response == NSAlertSecondButtonReturn) 37 | { 38 | handler(); 39 | } 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /DesktopTimeKit/DTIPreferences/Extensions/NSURL+DTIPreferences.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSURL+DTIPreferences.h 3 | // DesktopTimeKit 4 | // 5 | // Created by Marcel Dierkes on 26.12.20. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface NSURL (DTIPreferences) 13 | @property (class, nonatomic, readonly) NSURL *dti_preferencesAppURL; 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /DesktopTimeKit/DTIPreferences/Extensions/NSURL+DTIPreferences.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSURL+DTIPreferences.m 3 | // DesktopTimeKit 4 | // 5 | // Created by Marcel Dierkes on 26.12.20. 6 | // 7 | 8 | #import "NSURL+DTIPreferences.h" 9 | #import "DTIDefines.h" 10 | 11 | static NSString * const DTIPreferencesAppBundleName = @"DesktopTime Preferences.app"; 12 | 13 | @implementation NSURL (DTIPreferences) 14 | 15 | + (NSURL *)dti_preferencesAppURL 16 | { 17 | Auto bundle = NSBundle.mainBundle; 18 | return [bundle URLForAuxiliaryExecutable:DTIPreferencesAppBundleName]; 19 | } 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /DesktopTimeKit/DTIPreferences/Extensions/NSWorkspace+DTIPreferences.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSWorkspace+DTIPreferences.h 3 | // DesktopTimeKit 4 | // 5 | // Created by Marcel Dierkes on 27.12.20. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | typedef void(^DTIOpenPreferencesCompletionHandler)(NSRunningApplication *_Nullable app, 13 | NSError *_Nullable error); 14 | 15 | @interface NSWorkspace (DTIPreferences) 16 | 17 | /// Opens the preferences app and calls the completion handler 18 | /// either with the running app instance or an error. 19 | /// @param completionHandler An optional completion handler, called on a private queue 20 | - (void)dti_openPreferencesWithCompletionHandler:(nullable DTIOpenPreferencesCompletionHandler)completionHandler; 21 | 22 | @end 23 | 24 | NS_ASSUME_NONNULL_END 25 | -------------------------------------------------------------------------------- /DesktopTimeKit/DTIPreferences/Extensions/NSWorkspace+DTIPreferences.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSWorkspace+DTIPreferences.m 3 | // DesktopTimeKit 4 | // 5 | // Created by Marcel Dierkes on 27.12.20. 6 | // 7 | 8 | #import "NSWorkspace+DTIPreferences.h" 9 | #import "DTIDefines.h" 10 | #import "NSURL+DTIPreferences.h" 11 | 12 | @implementation NSWorkspace (DTIPreferences) 13 | 14 | - (void)dti_openPreferencesWithCompletionHandler:(void(^)(NSRunningApplication *app, NSError *error))completionHandler 15 | { 16 | Auto appURL = NSURL.dti_preferencesAppURL; 17 | Auto config = [NSWorkspaceOpenConfiguration configuration]; 18 | config.addsToRecentItems = NO; 19 | 20 | [self openApplicationAtURL:appURL 21 | configuration:config 22 | completionHandler:^(NSRunningApplication *app, NSError *error) { 23 | if(completionHandler != nil) 24 | { 25 | completionHandler(app, error); 26 | } 27 | }]; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /DesktopTimeKit/DTIWorkspaceMetrics/DTIWorkspaceMetrics.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTIWorkspaceMetrics.h 3 | // DesktopTimeKit 4 | // 5 | // Created by Marcel Dierkes on 23.02.21. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | typedef NS_ENUM(NSUInteger, DTIWorkspaceDockPosition) 13 | { 14 | DTIWorkspaceDockPositionNone = 0, 15 | DTIWorkspaceDockPositionLeft, 16 | DTIWorkspaceDockPositionBottom, 17 | DTIWorkspaceDockPositionRight 18 | }; 19 | 20 | @class DTIPreferences; 21 | 22 | @interface DTIWorkspaceDockMetrics : NSObject 23 | @property (nonatomic, readonly) CGFloat thickness; 24 | @property (nonatomic, readonly) DTIWorkspaceDockPosition position; 25 | 26 | + (instancetype)new NS_UNAVAILABLE; 27 | - (instancetype)init NS_UNAVAILABLE; 28 | 29 | @end 30 | 31 | @interface DTIWorkspaceMenuBarMetrics : NSObject 32 | @property (nonatomic, readonly) CGFloat thickness; 33 | @property (nonatomic, readonly, getter=isHidden) BOOL hidden; 34 | 35 | + (instancetype)new NS_UNAVAILABLE; 36 | - (instancetype)init NS_UNAVAILABLE; 37 | 38 | @end 39 | 40 | @interface DTIWorkspaceMetrics : NSObject 41 | @property (nonatomic, readonly) DTIWorkspaceDockMetrics *dockMetrics; 42 | @property (nonatomic, readonly) DTIWorkspaceMenuBarMetrics *menuBarMetrics; 43 | @property (nonatomic, readonly) CGRect usableFrame; 44 | @property (nonatomic, readonly) NSEdgeInsets usableEdgeInsets; 45 | @property (nonatomic, readonly) DTIPreferences *preferences; 46 | 47 | + (instancetype)new NS_UNAVAILABLE; 48 | - (instancetype)init NS_UNAVAILABLE; 49 | 50 | - (instancetype)initWithScreenFrame:(CGRect)screenFrame 51 | visibleFrame:(CGRect)visibleFrame 52 | preferences:(DTIPreferences *)preferences NS_DESIGNATED_INITIALIZER; 53 | 54 | @end 55 | 56 | NS_ASSUME_NONNULL_END 57 | -------------------------------------------------------------------------------- /DesktopTimeKit/DTIWorkspaceMetrics/DTIWorkspaceMetrics.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTIWorkspaceMetrics.m 3 | // DesktopTimeKit 4 | // 5 | // Created by Marcel Dierkes on 23.02.21. 6 | // 7 | 8 | #import "DTIWorkspaceMetrics.h" 9 | #import "DTIDefines.h" 10 | #import "DTIPreferences.h" 11 | 12 | @interface DTIWorkspaceMetrics () 13 | @property (nonatomic, readwrite) DTIWorkspaceDockMetrics *dockMetrics; 14 | @property (nonatomic, readwrite) DTIWorkspaceMenuBarMetrics *menuBarMetrics; 15 | @property (nonatomic, readwrite) CGRect usableFrame; 16 | @property (nonatomic, readwrite) NSEdgeInsets usableEdgeInsets; 17 | @property (nonatomic, readwrite) DTIPreferences *preferences; 18 | @end 19 | 20 | @interface DTIWorkspaceDockMetrics () 21 | @property (nonatomic, readwrite) CGFloat thickness; 22 | @property (nonatomic, readwrite) DTIWorkspaceDockPosition position; 23 | - (instancetype)initWithThickness:(CGFloat)thickness 24 | position:(DTIWorkspaceDockPosition)position NS_DESIGNATED_INITIALIZER; 25 | @end 26 | 27 | @interface DTIWorkspaceMenuBarMetrics () 28 | @property (nonatomic, readwrite) CGFloat thickness; 29 | @property (nonatomic, readwrite, getter=isHidden) BOOL hidden; 30 | - (instancetype)initWithThickness:(CGFloat)thickness 31 | hidden:(BOOL)hidden NS_DESIGNATED_INITIALIZER; 32 | @end 33 | 34 | @implementation DTIWorkspaceMetrics 35 | 36 | - (instancetype)initWithScreenFrame:(CGRect)screenFrame visibleFrame:(CGRect)visibleFrame preferences:(DTIPreferences *)preferences 37 | { 38 | NSParameterAssert(preferences); 39 | 40 | self = [super init]; 41 | if(self) 42 | { 43 | self.preferences = preferences; 44 | [self calculateMetricsWithFrame:screenFrame visibleFrame:visibleFrame]; 45 | } 46 | return self; 47 | } 48 | 49 | - (void)calculateMetricsWithFrame:(CGRect)frame visibleFrame:(CGRect)visibleFrame 50 | { 51 | // Remember: The AppKit origin is bottom left!! 52 | 53 | // Find the bottom Dock: 54 | DTIWorkspaceDockPosition dockPosition = DTIWorkspaceDockPositionNone; 55 | CGFloat dockThickness = 0.0f; 56 | 57 | CGFloat diffBottom = visibleFrame.origin.y - frame.origin.y; 58 | if(diffBottom > 0.0f) 59 | { 60 | dockPosition = DTIWorkspaceDockPositionBottom; 61 | dockThickness = diffBottom; 62 | } 63 | 64 | // Find the left Dock: 65 | 66 | CGFloat diffLeft = visibleFrame.origin.x - frame.origin.x; 67 | if(diffLeft > 0.0f) 68 | { 69 | dockPosition = DTIWorkspaceDockPositionLeft; 70 | dockThickness = diffLeft; 71 | } 72 | 73 | // Find the right Dock: 74 | 75 | CGFloat diffRight = frame.size.width - visibleFrame.size.width; 76 | if(diffLeft == 0.0f && diffRight > 0.0f) 77 | { 78 | dockPosition = DTIWorkspaceDockPositionRight; 79 | dockThickness = diffRight; 80 | } 81 | 82 | Auto dock = [[DTIWorkspaceDockMetrics alloc] initWithThickness:dockThickness 83 | position:dockPosition]; 84 | 85 | // Find the Menu Bar: 86 | BOOL hasMenuBar = NO; 87 | CGFloat menuBarThickness = 0.0f; 88 | 89 | CGFloat diffTop = frame.size.height - visibleFrame.size.height - diffBottom; 90 | if(diffTop > 0.0f) 91 | { 92 | hasMenuBar = YES; 93 | menuBarThickness = diffTop; 94 | } 95 | 96 | Auto menuBar = [[DTIWorkspaceMenuBarMetrics alloc] initWithThickness:menuBarThickness 97 | hidden:!hasMenuBar]; 98 | 99 | // Usable screen frame 100 | AutoVar usableFrame = frame; 101 | AutoVar usableEdgeInsets = NSEdgeInsetsZero; 102 | 103 | Auto preferences = self.preferences; 104 | BOOL avoidsDock = [preferences avoidsDockOverlapping]; 105 | BOOL avoidsMenuBar = [preferences avoidsMenuBarOverlapping]; 106 | 107 | if(avoidsDock == YES) 108 | { 109 | switch(dockPosition) 110 | { 111 | case DTIWorkspaceDockPositionBottom: 112 | usableFrame.origin.y += dockThickness; 113 | usableFrame.size.height -= dockThickness; 114 | usableEdgeInsets.bottom = dockThickness; 115 | break; 116 | case DTIWorkspaceDockPositionLeft: 117 | usableFrame.origin.x += dockThickness; 118 | usableFrame.size.width -= dockThickness; 119 | usableEdgeInsets.left = dockThickness; 120 | break; 121 | case DTIWorkspaceDockPositionRight: 122 | usableFrame.size.width -= dockThickness; 123 | usableEdgeInsets.right = dockThickness; 124 | break; 125 | case DTIWorkspaceDockPositionNone: 126 | break; 127 | } 128 | } 129 | 130 | if(avoidsMenuBar == YES) 131 | { 132 | usableFrame.size.height -= menuBarThickness; 133 | usableEdgeInsets.top = menuBarThickness; 134 | } 135 | 136 | self.dockMetrics = dock; 137 | self.menuBarMetrics = menuBar; 138 | self.usableFrame = usableFrame; 139 | self.usableEdgeInsets = usableEdgeInsets; 140 | } 141 | 142 | @end 143 | 144 | @implementation DTIWorkspaceDockMetrics 145 | 146 | - (instancetype)initWithThickness:(CGFloat)thickness position:(DTIWorkspaceDockPosition)position 147 | { 148 | self = [super init]; 149 | if(self) 150 | { 151 | self.thickness = thickness; 152 | self.position = position; 153 | } 154 | return self; 155 | } 156 | 157 | @end 158 | 159 | @implementation DTIWorkspaceMenuBarMetrics 160 | 161 | - (instancetype)initWithThickness:(CGFloat)thickness hidden:(BOOL)hidden 162 | { 163 | self = [super init]; 164 | if(self) 165 | { 166 | self.thickness = thickness; 167 | self.hidden = hidden; 168 | } 169 | return self; 170 | } 171 | 172 | @end 173 | -------------------------------------------------------------------------------- /DesktopTimeKit/DTIWorkspaceMetrics/Extensions/NSScreen+DTIWorkspaceMetrics.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSScreen+DTIWorkspaceMetrics.h 3 | // DesktopTimeKit 4 | // 5 | // Created by Marcel Dierkes on 23.02.21. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NSScreen (DTIWorkspaceMetrics) 14 | @property (nonatomic, readonly) DTIWorkspaceMetrics *dti_workspaceMetrics; 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /DesktopTimeKit/DTIWorkspaceMetrics/Extensions/NSScreen+DTIWorkspaceMetrics.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSScreen+DTIWorkspaceMetrics.m 3 | // DesktopTimeKit 4 | // 5 | // Created by Marcel Dierkes on 23.02.21. 6 | // 7 | 8 | #import "NSScreen+DTIWorkspaceMetrics.h" 9 | #import "DTIDefines.h" 10 | #import "DTIWorkspaceMetrics.h" 11 | #import "DTIPreferences.h" 12 | 13 | @implementation NSScreen (DTIWorkspaceMetrics) 14 | 15 | - (DTIWorkspaceMetrics *)dti_workspaceMetrics 16 | { 17 | Auto preferences = DTIPreferences.sharedPreferences; 18 | return [[DTIWorkspaceMetrics alloc] initWithScreenFrame:self.frame 19 | visibleFrame:self.visibleFrame 20 | preferences:preferences]; 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /DesktopTimeKit/DesktopTimeKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // DesktopTimeKit.h 3 | // DesktopTimeKit 4 | // 5 | // Created by Marcel Dierkes on 17.01.21. 6 | // 7 | 8 | #import 9 | 10 | /// Project version number for DesktopTimeKit. 11 | FOUNDATION_EXPORT double DesktopTimeKitVersionNumber; 12 | 13 | /// Project version string for DesktopTimeKit. 14 | FOUNDATION_EXPORT const unsigned char DesktopTimeKitVersionString[]; 15 | 16 | #import 17 | #import 18 | #import 19 | #import 20 | #import 21 | #import 22 | #import 23 | #import 24 | #import 25 | #import 26 | #import 27 | #import 28 | #import 29 | -------------------------------------------------------------------------------- /DesktopTimeKit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /DesktopTimeKitTests/DesktopTimeKitTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // DesktopTimeKitTests.m 3 | // DesktopTimeKitTests 4 | // 5 | // Created by Marcel Dierkes on 17.01.21. 6 | // 7 | 8 | #import 9 | 10 | @interface DesktopTimeKitTests : XCTestCase 11 | @end 12 | 13 | @implementation DesktopTimeKitTests 14 | @end 15 | -------------------------------------------------------------------------------- /DesktopTimeKitTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Extras/DesktopTime Icon.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newmarcel/DesktopTime/5146b11b21624bcd482abccce0925952abe2f16f/Extras/DesktopTime Icon.sketch -------------------------------------------------------------------------------- /Extras/Screenshot@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newmarcel/DesktopTime/5146b11b21624bcd482abccce0925952abe2f16f/Extras/Screenshot@2x.jpg -------------------------------------------------------------------------------- /Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newmarcel/DesktopTime/5146b11b21624bcd482abccce0925952abe2f16f/Icon.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 - present Marcel Dierkes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DesktopTime 2 | Displays the current time and date on your desktop 3 | 4 | 5 | 6 | DesktopTime allows you to add the fully-customizable time, date and battery level on top of your desktop wallpaper. 7 | 8 | ## Usage 9 | Launch the app to display the time. Launch the app again to show Preferences and quit. 10 | 11 | ## Features 12 | - displays the time, date and battery level in selected screen corners 13 | - customizable fonts and colors 14 | - displays on the main screen, secondary screens or all screens at once 15 | 16 | ## License 17 | Everything in this project is licensed under the [MIT license](http://opensource.org/licenses/MIT). 18 | --------------------------------------------------------------------------------