├── EyeSaver.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ ├── 5km.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ │ └── marno.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── xcshareddata │ └── xcschemes │ │ └── EyeSaver.xcscheme └── xcuserdata │ ├── 5km.xcuserdatad │ └── xcschemes │ │ └── xcschememanagement.plist │ └── marno.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── EyeSaver ├── AboutWindowController.swift ├── AboutWindowController.xib ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon-128.png │ │ ├── icon-128@2x.png │ │ ├── icon-16.png │ │ ├── icon-16@2x.png │ │ ├── icon-256.png │ │ ├── icon-256@2x.png │ │ ├── icon-32.png │ │ ├── icon-32@2x.png │ │ ├── icon-512.png │ │ └── icon-512@2x.png │ ├── Contents.json │ ├── aboutIcon.imageset │ │ ├── About (1).png │ │ ├── About (2).png │ │ ├── About (3).png │ │ ├── About (4).png │ │ ├── About (5).png │ │ ├── About.png │ │ └── Contents.json │ ├── appLogo.imageset │ │ ├── Contents.json │ │ ├── icon-256.png │ │ └── icon-256@2x.png │ ├── helpIcon.imageset │ │ ├── Contents.json │ │ ├── help (1).png │ │ ├── help (2).png │ │ ├── help (3).png │ │ ├── help (4).png │ │ ├── help (5).png │ │ └── help.png │ ├── pauseIcon.imageset │ │ ├── Contents.json │ │ ├── pause-white.png │ │ └── pause.png │ ├── startIcon.imageset │ │ ├── Contents.json │ │ ├── start.png │ │ └── start_white.png │ └── statusIcon.imageset │ │ ├── Contents.json │ │ └── 眼睛.png ├── Base.lproj │ └── MainMenu.xib ├── EventMonitor.swift ├── FullscreenRemindScreen.xib ├── Info.plist ├── PopoverDemo.entitlements ├── PopoverDemoViewController.swift ├── PopoverDemoViewController.xib ├── RectangleView.swift ├── RelaxRemindWindowController.swift ├── RelaxRemindWindowController.xib ├── RelaxTimeManager.swift ├── Utils │ ├── ConfigUtils.swift │ └── TimeUtils.swift └── WorkTimerManager.swift ├── EyeSaverLauncher ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ └── MainMenu.xib ├── EyeSaverLauncher.entitlements └── Info.plist ├── LICENSE.txt ├── OGCircularBar ├── Info.plist ├── NSBezierPath+CGPath.swift ├── OGCircularBar.h └── OGCircularBarView.swift └── README.md /EyeSaver.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2E3231AD23560F0200AB1880 /* RelaxTimeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E3231AC23560F0200AB1880 /* RelaxTimeManager.swift */; }; 11 | 2E55237A233381DB00962FF3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E552379233381DB00962FF3 /* AppDelegate.swift */; }; 12 | 2E55237C233381DC00962FF3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2E55237B233381DC00962FF3 /* Assets.xcassets */; }; 13 | 2E55237F233381DC00962FF3 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2E55237D233381DC00962FF3 /* MainMenu.xib */; }; 14 | 2E5523852333829400962FF3 /* EyeSaverLauncher.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 2E552377233381DB00962FF3 /* EyeSaverLauncher.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 15 | 2E59781F232A31B8009B7945 /* NSBezierPath+CGPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E59781B232A31B8009B7945 /* NSBezierPath+CGPath.swift */; }; 16 | 2E597820232A31B8009B7945 /* OGCircularBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E59781D232A31B8009B7945 /* OGCircularBarView.swift */; }; 17 | 2E597821232A31B8009B7945 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2E59781E232A31B8009B7945 /* Info.plist */; }; 18 | 2E83C6E6233487590047AC89 /* AboutWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E83C6E4233487590047AC89 /* AboutWindowController.swift */; }; 19 | 2E83C6E7233487590047AC89 /* AboutWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2E83C6E5233487590047AC89 /* AboutWindowController.xib */; }; 20 | 2EF1F15C232D1DC20033A630 /* RelaxRemindWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EF1F15A232D1DC20033A630 /* RelaxRemindWindowController.swift */; }; 21 | 2EF1F161232D33160033A630 /* RectangleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EF1F160232D33160033A630 /* RectangleView.swift */; }; 22 | 2EF1F163232D35C80033A630 /* RelaxRemindWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2EF1F162232D35C80033A630 /* RelaxRemindWindowController.xib */; }; 23 | 2EF1F173232E69B20033A630 /* WorkTimerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EF1F172232E69B20033A630 /* WorkTimerManager.swift */; }; 24 | 2EF1F175232E76EC0033A630 /* ConfigUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EF1F174232E76EC0033A630 /* ConfigUtils.swift */; }; 25 | 2EF1F178232E7BEF0033A630 /* TimeUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EF1F177232E7BEF0033A630 /* TimeUtils.swift */; }; 26 | 6BCD44BE20E5D5480042DC44 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BCD44BD20E5D5480042DC44 /* AppDelegate.swift */; }; 27 | 6BCD44C020E5D5490042DC44 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6BCD44BF20E5D5490042DC44 /* Assets.xcassets */; }; 28 | 6BCD44C320E5D5490042DC44 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6BCD44C120E5D5490042DC44 /* MainMenu.xib */; }; 29 | 6BCD44CD20E5F6350042DC44 /* PopoverDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BCD44CB20E5F6350042DC44 /* PopoverDemoViewController.swift */; }; 30 | 6BCD44CE20E5F6350042DC44 /* PopoverDemoViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6BCD44CC20E5F6350042DC44 /* PopoverDemoViewController.xib */; }; 31 | 6BCD44D020E60ADB0042DC44 /* EventMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BCD44CF20E60ADB0042DC44 /* EventMonitor.swift */; }; 32 | /* End PBXBuildFile section */ 33 | 34 | /* Begin PBXCopyFilesBuildPhase section */ 35 | 2E55237123336CC000962FF3 /* CopyFiles */ = { 36 | isa = PBXCopyFilesBuildPhase; 37 | buildActionMask = 2147483647; 38 | dstPath = Contents/Library/LoginItems; 39 | dstSubfolderSpec = 1; 40 | files = ( 41 | 2E5523852333829400962FF3 /* EyeSaverLauncher.app in CopyFiles */, 42 | ); 43 | runOnlyForDeploymentPostprocessing = 0; 44 | }; 45 | /* End PBXCopyFilesBuildPhase section */ 46 | 47 | /* Begin PBXFileReference section */ 48 | 2E3231AC23560F0200AB1880 /* RelaxTimeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaxTimeManager.swift; sourceTree = ""; }; 49 | 2E552377233381DB00962FF3 /* EyeSaverLauncher.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = EyeSaverLauncher.app; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | 2E552379233381DB00962FF3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 51 | 2E55237B233381DC00962FF3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 52 | 2E55237E233381DC00962FF3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 53 | 2E552380233381DC00962FF3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 54 | 2E552381233381DC00962FF3 /* EyeSaverLauncher.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = EyeSaverLauncher.entitlements; sourceTree = ""; }; 55 | 2E59781B232A31B8009B7945 /* NSBezierPath+CGPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSBezierPath+CGPath.swift"; sourceTree = ""; }; 56 | 2E59781C232A31B8009B7945 /* OGCircularBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OGCircularBar.h; sourceTree = ""; }; 57 | 2E59781D232A31B8009B7945 /* OGCircularBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OGCircularBarView.swift; sourceTree = ""; }; 58 | 2E59781E232A31B8009B7945 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 59 | 2E83C6E4233487590047AC89 /* AboutWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutWindowController.swift; sourceTree = ""; }; 60 | 2E83C6E5233487590047AC89 /* AboutWindowController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AboutWindowController.xib; sourceTree = ""; }; 61 | 2EF1F15A232D1DC20033A630 /* RelaxRemindWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaxRemindWindowController.swift; sourceTree = ""; }; 62 | 2EF1F160232D33160033A630 /* RectangleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RectangleView.swift; sourceTree = ""; }; 63 | 2EF1F162232D35C80033A630 /* RelaxRemindWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RelaxRemindWindowController.xib; sourceTree = ""; }; 64 | 2EF1F172232E69B20033A630 /* WorkTimerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkTimerManager.swift; sourceTree = ""; }; 65 | 2EF1F174232E76EC0033A630 /* ConfigUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigUtils.swift; sourceTree = ""; }; 66 | 2EF1F177232E7BEF0033A630 /* TimeUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeUtils.swift; sourceTree = ""; }; 67 | 6BCD44BA20E5D5480042DC44 /* EyeSaver.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = EyeSaver.app; sourceTree = BUILT_PRODUCTS_DIR; }; 68 | 6BCD44BD20E5D5480042DC44 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 69 | 6BCD44BF20E5D5490042DC44 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 70 | 6BCD44C220E5D5490042DC44 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 71 | 6BCD44C420E5D5490042DC44 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 72 | 6BCD44C520E5D5490042DC44 /* PopoverDemo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PopoverDemo.entitlements; sourceTree = ""; }; 73 | 6BCD44CB20E5F6350042DC44 /* PopoverDemoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopoverDemoViewController.swift; sourceTree = ""; }; 74 | 6BCD44CC20E5F6350042DC44 /* PopoverDemoViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PopoverDemoViewController.xib; sourceTree = ""; }; 75 | 6BCD44CF20E60ADB0042DC44 /* EventMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventMonitor.swift; sourceTree = ""; }; 76 | /* End PBXFileReference section */ 77 | 78 | /* Begin PBXFrameworksBuildPhase section */ 79 | 2E552374233381DB00962FF3 /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | 6BCD44B720E5D5480042DC44 /* Frameworks */ = { 87 | isa = PBXFrameworksBuildPhase; 88 | buildActionMask = 2147483647; 89 | files = ( 90 | ); 91 | runOnlyForDeploymentPostprocessing = 0; 92 | }; 93 | /* End PBXFrameworksBuildPhase section */ 94 | 95 | /* Begin PBXGroup section */ 96 | 2E552378233381DB00962FF3 /* EyeSaverLauncher */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | 2E552379233381DB00962FF3 /* AppDelegate.swift */, 100 | 2E55237B233381DC00962FF3 /* Assets.xcassets */, 101 | 2E55237D233381DC00962FF3 /* MainMenu.xib */, 102 | 2E552380233381DC00962FF3 /* Info.plist */, 103 | 2E552381233381DC00962FF3 /* EyeSaverLauncher.entitlements */, 104 | ); 105 | path = EyeSaverLauncher; 106 | sourceTree = ""; 107 | }; 108 | 2E59781A232A31B8009B7945 /* OGCircularBar */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 2E59781B232A31B8009B7945 /* NSBezierPath+CGPath.swift */, 112 | 2E59781C232A31B8009B7945 /* OGCircularBar.h */, 113 | 2E59781D232A31B8009B7945 /* OGCircularBarView.swift */, 114 | 2E59781E232A31B8009B7945 /* Info.plist */, 115 | ); 116 | path = OGCircularBar; 117 | sourceTree = ""; 118 | }; 119 | 2EF1F176232E7BB10033A630 /* Utils */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 2EF1F174232E76EC0033A630 /* ConfigUtils.swift */, 123 | 2EF1F177232E7BEF0033A630 /* TimeUtils.swift */, 124 | ); 125 | path = Utils; 126 | sourceTree = ""; 127 | }; 128 | 6BCD44B120E5D5480042DC44 = { 129 | isa = PBXGroup; 130 | children = ( 131 | 2E59781A232A31B8009B7945 /* OGCircularBar */, 132 | 6BCD44BC20E5D5480042DC44 /* EyeSaver */, 133 | 2E552378233381DB00962FF3 /* EyeSaverLauncher */, 134 | 6BCD44BB20E5D5480042DC44 /* Products */, 135 | ); 136 | sourceTree = ""; 137 | }; 138 | 6BCD44BB20E5D5480042DC44 /* Products */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | 6BCD44BA20E5D5480042DC44 /* EyeSaver.app */, 142 | 2E552377233381DB00962FF3 /* EyeSaverLauncher.app */, 143 | ); 144 | name = Products; 145 | sourceTree = ""; 146 | }; 147 | 6BCD44BC20E5D5480042DC44 /* EyeSaver */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 2EF1F176232E7BB10033A630 /* Utils */, 151 | 6BCD44BF20E5D5490042DC44 /* Assets.xcassets */, 152 | 6BCD44BD20E5D5480042DC44 /* AppDelegate.swift */, 153 | 6BCD44C120E5D5490042DC44 /* MainMenu.xib */, 154 | 6BCD44CF20E60ADB0042DC44 /* EventMonitor.swift */, 155 | 6BCD44CB20E5F6350042DC44 /* PopoverDemoViewController.swift */, 156 | 6BCD44CC20E5F6350042DC44 /* PopoverDemoViewController.xib */, 157 | 2EF1F15A232D1DC20033A630 /* RelaxRemindWindowController.swift */, 158 | 2EF1F162232D35C80033A630 /* RelaxRemindWindowController.xib */, 159 | 2EF1F160232D33160033A630 /* RectangleView.swift */, 160 | 2EF1F172232E69B20033A630 /* WorkTimerManager.swift */, 161 | 2E3231AC23560F0200AB1880 /* RelaxTimeManager.swift */, 162 | 2E83C6E4233487590047AC89 /* AboutWindowController.swift */, 163 | 2E83C6E5233487590047AC89 /* AboutWindowController.xib */, 164 | 6BCD44C520E5D5490042DC44 /* PopoverDemo.entitlements */, 165 | 6BCD44C420E5D5490042DC44 /* Info.plist */, 166 | ); 167 | path = EyeSaver; 168 | sourceTree = ""; 169 | }; 170 | /* End PBXGroup section */ 171 | 172 | /* Begin PBXNativeTarget section */ 173 | 2E552376233381DB00962FF3 /* EyeSaverLauncher */ = { 174 | isa = PBXNativeTarget; 175 | buildConfigurationList = 2E552382233381DC00962FF3 /* Build configuration list for PBXNativeTarget "EyeSaverLauncher" */; 176 | buildPhases = ( 177 | 2E552373233381DB00962FF3 /* Sources */, 178 | 2E552374233381DB00962FF3 /* Frameworks */, 179 | 2E552375233381DB00962FF3 /* Resources */, 180 | ); 181 | buildRules = ( 182 | ); 183 | dependencies = ( 184 | ); 185 | name = EyeSaverLauncher; 186 | productName = EyeSaverLauncher; 187 | productReference = 2E552377233381DB00962FF3 /* EyeSaverLauncher.app */; 188 | productType = "com.apple.product-type.application"; 189 | }; 190 | 6BCD44B920E5D5480042DC44 /* EyeSaver */ = { 191 | isa = PBXNativeTarget; 192 | buildConfigurationList = 6BCD44C820E5D5490042DC44 /* Build configuration list for PBXNativeTarget "EyeSaver" */; 193 | buildPhases = ( 194 | 6BCD44B620E5D5480042DC44 /* Sources */, 195 | 6BCD44B720E5D5480042DC44 /* Frameworks */, 196 | 6BCD44B820E5D5480042DC44 /* Resources */, 197 | 2E55237123336CC000962FF3 /* CopyFiles */, 198 | ); 199 | buildRules = ( 200 | ); 201 | dependencies = ( 202 | ); 203 | name = EyeSaver; 204 | productName = PopoverDemo; 205 | productReference = 6BCD44BA20E5D5480042DC44 /* EyeSaver.app */; 206 | productType = "com.apple.product-type.application"; 207 | }; 208 | /* End PBXNativeTarget section */ 209 | 210 | /* Begin PBXProject section */ 211 | 6BCD44B220E5D5480042DC44 /* Project object */ = { 212 | isa = PBXProject; 213 | attributes = { 214 | LastSwiftUpdateCheck = 1100; 215 | LastUpgradeCheck = 1100; 216 | ORGANIZATIONNAME = 5km; 217 | TargetAttributes = { 218 | 2E552376233381DB00962FF3 = { 219 | CreatedOnToolsVersion = 11.0; 220 | }; 221 | 6BCD44B920E5D5480042DC44 = { 222 | CreatedOnToolsVersion = 9.4.1; 223 | LastSwiftMigration = 1100; 224 | }; 225 | }; 226 | }; 227 | buildConfigurationList = 6BCD44B520E5D5480042DC44 /* Build configuration list for PBXProject "EyeSaver" */; 228 | compatibilityVersion = "Xcode 9.3"; 229 | developmentRegion = en; 230 | hasScannedForEncodings = 0; 231 | knownRegions = ( 232 | en, 233 | Base, 234 | ); 235 | mainGroup = 6BCD44B120E5D5480042DC44; 236 | productRefGroup = 6BCD44BB20E5D5480042DC44 /* Products */; 237 | projectDirPath = ""; 238 | projectRoot = ""; 239 | targets = ( 240 | 6BCD44B920E5D5480042DC44 /* EyeSaver */, 241 | 2E552376233381DB00962FF3 /* EyeSaverLauncher */, 242 | ); 243 | }; 244 | /* End PBXProject section */ 245 | 246 | /* Begin PBXResourcesBuildPhase section */ 247 | 2E552375233381DB00962FF3 /* Resources */ = { 248 | isa = PBXResourcesBuildPhase; 249 | buildActionMask = 2147483647; 250 | files = ( 251 | 2E55237C233381DC00962FF3 /* Assets.xcassets in Resources */, 252 | 2E55237F233381DC00962FF3 /* MainMenu.xib in Resources */, 253 | ); 254 | runOnlyForDeploymentPostprocessing = 0; 255 | }; 256 | 6BCD44B820E5D5480042DC44 /* Resources */ = { 257 | isa = PBXResourcesBuildPhase; 258 | buildActionMask = 2147483647; 259 | files = ( 260 | 2E597821232A31B8009B7945 /* Info.plist in Resources */, 261 | 6BCD44C020E5D5490042DC44 /* Assets.xcassets in Resources */, 262 | 2EF1F163232D35C80033A630 /* RelaxRemindWindowController.xib in Resources */, 263 | 2E83C6E7233487590047AC89 /* AboutWindowController.xib in Resources */, 264 | 6BCD44C320E5D5490042DC44 /* MainMenu.xib in Resources */, 265 | 6BCD44CE20E5F6350042DC44 /* PopoverDemoViewController.xib in Resources */, 266 | ); 267 | runOnlyForDeploymentPostprocessing = 0; 268 | }; 269 | /* End PBXResourcesBuildPhase section */ 270 | 271 | /* Begin PBXSourcesBuildPhase section */ 272 | 2E552373233381DB00962FF3 /* Sources */ = { 273 | isa = PBXSourcesBuildPhase; 274 | buildActionMask = 2147483647; 275 | files = ( 276 | 2E55237A233381DB00962FF3 /* AppDelegate.swift in Sources */, 277 | ); 278 | runOnlyForDeploymentPostprocessing = 0; 279 | }; 280 | 6BCD44B620E5D5480042DC44 /* Sources */ = { 281 | isa = PBXSourcesBuildPhase; 282 | buildActionMask = 2147483647; 283 | files = ( 284 | 2EF1F178232E7BEF0033A630 /* TimeUtils.swift in Sources */, 285 | 6BCD44D020E60ADB0042DC44 /* EventMonitor.swift in Sources */, 286 | 2EF1F175232E76EC0033A630 /* ConfigUtils.swift in Sources */, 287 | 2E3231AD23560F0200AB1880 /* RelaxTimeManager.swift in Sources */, 288 | 2EF1F173232E69B20033A630 /* WorkTimerManager.swift in Sources */, 289 | 2EF1F161232D33160033A630 /* RectangleView.swift in Sources */, 290 | 2E59781F232A31B8009B7945 /* NSBezierPath+CGPath.swift in Sources */, 291 | 2E83C6E6233487590047AC89 /* AboutWindowController.swift in Sources */, 292 | 2E597820232A31B8009B7945 /* OGCircularBarView.swift in Sources */, 293 | 6BCD44BE20E5D5480042DC44 /* AppDelegate.swift in Sources */, 294 | 6BCD44CD20E5F6350042DC44 /* PopoverDemoViewController.swift in Sources */, 295 | 2EF1F15C232D1DC20033A630 /* RelaxRemindWindowController.swift in Sources */, 296 | ); 297 | runOnlyForDeploymentPostprocessing = 0; 298 | }; 299 | /* End PBXSourcesBuildPhase section */ 300 | 301 | /* Begin PBXVariantGroup section */ 302 | 2E55237D233381DC00962FF3 /* MainMenu.xib */ = { 303 | isa = PBXVariantGroup; 304 | children = ( 305 | 2E55237E233381DC00962FF3 /* Base */, 306 | ); 307 | name = MainMenu.xib; 308 | sourceTree = ""; 309 | }; 310 | 6BCD44C120E5D5490042DC44 /* MainMenu.xib */ = { 311 | isa = PBXVariantGroup; 312 | children = ( 313 | 6BCD44C220E5D5490042DC44 /* Base */, 314 | ); 315 | name = MainMenu.xib; 316 | sourceTree = ""; 317 | }; 318 | /* End PBXVariantGroup section */ 319 | 320 | /* Begin XCBuildConfiguration section */ 321 | 2E552383233381DC00962FF3 /* Debug */ = { 322 | isa = XCBuildConfiguration; 323 | buildSettings = { 324 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 325 | CODE_SIGN_ENTITLEMENTS = EyeSaverLauncher/EyeSaverLauncher.entitlements; 326 | CODE_SIGN_IDENTITY = "Mac Developer"; 327 | CODE_SIGN_STYLE = Automatic; 328 | COMBINE_HIDPI_IMAGES = YES; 329 | DEPLOYMENT_POSTPROCESSING = YES; 330 | DEVELOPMENT_TEAM = 2GD9KGFG9R; 331 | ENABLE_HARDENED_RUNTIME = YES; 332 | INFOPLIST_FILE = EyeSaverLauncher/Info.plist; 333 | LD_RUNPATH_SEARCH_PATHS = ( 334 | "$(inherited)", 335 | "@executable_path/../Frameworks", 336 | ); 337 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 338 | MTL_FAST_MATH = YES; 339 | PRODUCT_BUNDLE_IDENTIFIER = cn.marno.EyeSaverLauncher; 340 | PRODUCT_NAME = "$(TARGET_NAME)"; 341 | PROVISIONING_PROFILE_SPECIFIER = ""; 342 | SKIP_INSTALL = YES; 343 | SWIFT_VERSION = 5.0; 344 | }; 345 | name = Debug; 346 | }; 347 | 2E552384233381DC00962FF3 /* Release */ = { 348 | isa = XCBuildConfiguration; 349 | buildSettings = { 350 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 351 | CODE_SIGN_ENTITLEMENTS = EyeSaverLauncher/EyeSaverLauncher.entitlements; 352 | CODE_SIGN_IDENTITY = "Mac Developer"; 353 | CODE_SIGN_STYLE = Automatic; 354 | COMBINE_HIDPI_IMAGES = YES; 355 | DEPLOYMENT_POSTPROCESSING = YES; 356 | DEVELOPMENT_TEAM = 2GD9KGFG9R; 357 | ENABLE_HARDENED_RUNTIME = YES; 358 | INFOPLIST_FILE = EyeSaverLauncher/Info.plist; 359 | LD_RUNPATH_SEARCH_PATHS = ( 360 | "$(inherited)", 361 | "@executable_path/../Frameworks", 362 | ); 363 | MTL_FAST_MATH = YES; 364 | PRODUCT_BUNDLE_IDENTIFIER = cn.marno.EyeSaverLauncher; 365 | PRODUCT_NAME = "$(TARGET_NAME)"; 366 | PROVISIONING_PROFILE_SPECIFIER = ""; 367 | SKIP_INSTALL = YES; 368 | SWIFT_VERSION = 5.0; 369 | }; 370 | name = Release; 371 | }; 372 | 6BCD44C620E5D5490042DC44 /* Debug */ = { 373 | isa = XCBuildConfiguration; 374 | buildSettings = { 375 | ALWAYS_SEARCH_USER_PATHS = NO; 376 | CLANG_ANALYZER_NONNULL = YES; 377 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 378 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 379 | CLANG_CXX_LIBRARY = "libc++"; 380 | CLANG_ENABLE_MODULES = YES; 381 | CLANG_ENABLE_OBJC_ARC = YES; 382 | CLANG_ENABLE_OBJC_WEAK = YES; 383 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 384 | CLANG_WARN_BOOL_CONVERSION = YES; 385 | CLANG_WARN_COMMA = YES; 386 | CLANG_WARN_CONSTANT_CONVERSION = YES; 387 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 388 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 389 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 390 | CLANG_WARN_EMPTY_BODY = YES; 391 | CLANG_WARN_ENUM_CONVERSION = YES; 392 | CLANG_WARN_INFINITE_RECURSION = YES; 393 | CLANG_WARN_INT_CONVERSION = YES; 394 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 395 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 396 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 397 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 398 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 399 | CLANG_WARN_STRICT_PROTOTYPES = YES; 400 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 401 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 402 | CLANG_WARN_UNREACHABLE_CODE = YES; 403 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 404 | CODE_SIGN_IDENTITY = "Mac Developer"; 405 | COPY_PHASE_STRIP = YES; 406 | DEBUG_INFORMATION_FORMAT = dwarf; 407 | DEPLOYMENT_POSTPROCESSING = YES; 408 | ENABLE_STRICT_OBJC_MSGSEND = YES; 409 | ENABLE_TESTABILITY = YES; 410 | GCC_C_LANGUAGE_STANDARD = gnu11; 411 | GCC_DYNAMIC_NO_PIC = NO; 412 | GCC_NO_COMMON_BLOCKS = YES; 413 | GCC_OPTIMIZATION_LEVEL = 0; 414 | GCC_PREPROCESSOR_DEFINITIONS = ( 415 | "DEBUG=1", 416 | "$(inherited)", 417 | ); 418 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 419 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 420 | GCC_WARN_UNDECLARED_SELECTOR = YES; 421 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 422 | GCC_WARN_UNUSED_FUNCTION = YES; 423 | GCC_WARN_UNUSED_VARIABLE = YES; 424 | MACOSX_DEPLOYMENT_TARGET = 10.12; 425 | MTL_ENABLE_DEBUG_INFO = YES; 426 | ONLY_ACTIVE_ARCH = YES; 427 | SDKROOT = macosx; 428 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 429 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 430 | }; 431 | name = Debug; 432 | }; 433 | 6BCD44C720E5D5490042DC44 /* Release */ = { 434 | isa = XCBuildConfiguration; 435 | buildSettings = { 436 | ALWAYS_SEARCH_USER_PATHS = NO; 437 | CLANG_ANALYZER_NONNULL = YES; 438 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 439 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 440 | CLANG_CXX_LIBRARY = "libc++"; 441 | CLANG_ENABLE_MODULES = YES; 442 | CLANG_ENABLE_OBJC_ARC = YES; 443 | CLANG_ENABLE_OBJC_WEAK = YES; 444 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 445 | CLANG_WARN_BOOL_CONVERSION = YES; 446 | CLANG_WARN_COMMA = YES; 447 | CLANG_WARN_CONSTANT_CONVERSION = YES; 448 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 449 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 450 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 451 | CLANG_WARN_EMPTY_BODY = YES; 452 | CLANG_WARN_ENUM_CONVERSION = YES; 453 | CLANG_WARN_INFINITE_RECURSION = YES; 454 | CLANG_WARN_INT_CONVERSION = YES; 455 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 456 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 457 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 458 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 459 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 460 | CLANG_WARN_STRICT_PROTOTYPES = YES; 461 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 462 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 463 | CLANG_WARN_UNREACHABLE_CODE = YES; 464 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 465 | CODE_SIGN_IDENTITY = "Mac Developer"; 466 | COPY_PHASE_STRIP = YES; 467 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 468 | DEPLOYMENT_POSTPROCESSING = YES; 469 | ENABLE_NS_ASSERTIONS = NO; 470 | ENABLE_STRICT_OBJC_MSGSEND = YES; 471 | GCC_C_LANGUAGE_STANDARD = gnu11; 472 | GCC_NO_COMMON_BLOCKS = YES; 473 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 474 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 475 | GCC_WARN_UNDECLARED_SELECTOR = YES; 476 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 477 | GCC_WARN_UNUSED_FUNCTION = YES; 478 | GCC_WARN_UNUSED_VARIABLE = YES; 479 | MACOSX_DEPLOYMENT_TARGET = 10.12; 480 | MTL_ENABLE_DEBUG_INFO = NO; 481 | SDKROOT = macosx; 482 | SWIFT_COMPILATION_MODE = wholemodule; 483 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 484 | }; 485 | name = Release; 486 | }; 487 | 6BCD44C920E5D5490042DC44 /* Debug */ = { 488 | isa = XCBuildConfiguration; 489 | buildSettings = { 490 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 491 | CODE_SIGN_ENTITLEMENTS = EyeSaver/PopoverDemo.entitlements; 492 | CODE_SIGN_IDENTITY = "Mac Developer"; 493 | CODE_SIGN_STYLE = Automatic; 494 | COMBINE_HIDPI_IMAGES = YES; 495 | COPY_PHASE_STRIP = YES; 496 | DEPLOYMENT_POSTPROCESSING = YES; 497 | DEVELOPMENT_TEAM = 2GD9KGFG9R; 498 | INFOPLIST_FILE = EyeSaver/Info.plist; 499 | LD_RUNPATH_SEARCH_PATHS = ( 500 | "$(inherited)", 501 | "@executable_path/../Frameworks", 502 | ); 503 | PRODUCT_BUNDLE_IDENTIFIER = cn.marno.eyesaver; 504 | PRODUCT_NAME = "$(TARGET_NAME)"; 505 | PROVISIONING_PROFILE_SPECIFIER = ""; 506 | SWIFT_VERSION = 5.0; 507 | }; 508 | name = Debug; 509 | }; 510 | 6BCD44CA20E5D5490042DC44 /* Release */ = { 511 | isa = XCBuildConfiguration; 512 | buildSettings = { 513 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 514 | CODE_SIGN_ENTITLEMENTS = EyeSaver/PopoverDemo.entitlements; 515 | CODE_SIGN_IDENTITY = "Mac Developer"; 516 | CODE_SIGN_STYLE = Automatic; 517 | COMBINE_HIDPI_IMAGES = YES; 518 | COPY_PHASE_STRIP = YES; 519 | DEPLOYMENT_POSTPROCESSING = YES; 520 | DEVELOPMENT_TEAM = 2GD9KGFG9R; 521 | INFOPLIST_FILE = EyeSaver/Info.plist; 522 | LD_RUNPATH_SEARCH_PATHS = ( 523 | "$(inherited)", 524 | "@executable_path/../Frameworks", 525 | ); 526 | PRODUCT_BUNDLE_IDENTIFIER = cn.marno.eyesaver; 527 | PRODUCT_NAME = "$(TARGET_NAME)"; 528 | PROVISIONING_PROFILE_SPECIFIER = ""; 529 | SWIFT_VERSION = 5.0; 530 | }; 531 | name = Release; 532 | }; 533 | /* End XCBuildConfiguration section */ 534 | 535 | /* Begin XCConfigurationList section */ 536 | 2E552382233381DC00962FF3 /* Build configuration list for PBXNativeTarget "EyeSaverLauncher" */ = { 537 | isa = XCConfigurationList; 538 | buildConfigurations = ( 539 | 2E552383233381DC00962FF3 /* Debug */, 540 | 2E552384233381DC00962FF3 /* Release */, 541 | ); 542 | defaultConfigurationIsVisible = 0; 543 | defaultConfigurationName = Release; 544 | }; 545 | 6BCD44B520E5D5480042DC44 /* Build configuration list for PBXProject "EyeSaver" */ = { 546 | isa = XCConfigurationList; 547 | buildConfigurations = ( 548 | 6BCD44C620E5D5490042DC44 /* Debug */, 549 | 6BCD44C720E5D5490042DC44 /* Release */, 550 | ); 551 | defaultConfigurationIsVisible = 0; 552 | defaultConfigurationName = Release; 553 | }; 554 | 6BCD44C820E5D5490042DC44 /* Build configuration list for PBXNativeTarget "EyeSaver" */ = { 555 | isa = XCConfigurationList; 556 | buildConfigurations = ( 557 | 6BCD44C920E5D5490042DC44 /* Debug */, 558 | 6BCD44CA20E5D5490042DC44 /* Release */, 559 | ); 560 | defaultConfigurationIsVisible = 0; 561 | defaultConfigurationName = Release; 562 | }; 563 | /* End XCConfigurationList section */ 564 | }; 565 | rootObject = 6BCD44B220E5D5480042DC44 /* Project object */; 566 | } 567 | -------------------------------------------------------------------------------- /EyeSaver.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /EyeSaver.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /EyeSaver.xcodeproj/project.xcworkspace/xcuserdata/5km.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver.xcodeproj/project.xcworkspace/xcuserdata/5km.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /EyeSaver.xcodeproj/project.xcworkspace/xcuserdata/marno.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver.xcodeproj/project.xcworkspace/xcuserdata/marno.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /EyeSaver.xcodeproj/xcshareddata/xcschemes/EyeSaver.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /EyeSaver.xcodeproj/xcuserdata/5km.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PopoverDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /EyeSaver.xcodeproj/xcuserdata/marno.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 22 | 23 | 24 | 26 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /EyeSaver.xcodeproj/xcuserdata/marno.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | EyeSaver.xcscheme_^#shared#^_ 8 | 9 | isShown 10 | 11 | orderHint 12 | 0 13 | 14 | EyeSaverLancher.xcscheme_^#shared#^_ 15 | 16 | orderHint 17 | 1 18 | 19 | EyeSaverLauncher.xcscheme_^#shared#^_ 20 | 21 | orderHint 22 | 1 23 | 24 | PopoverDemo.xcscheme_^#shared#^_ 25 | 26 | orderHint 27 | 0 28 | 29 | 30 | SuppressBuildableAutocreation 31 | 32 | 6BCD44B920E5D5480042DC44 33 | 34 | primary 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /EyeSaver/AboutWindowController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AboutWindowController.swift 3 | // EyeSaver 4 | // 5 | // Created by Marno on 9/20/19. 6 | // Copyright © 2019 Marno All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class AboutWindow:NSWindow{} 12 | 13 | class AboutWindowController: NSWindowController { 14 | 15 | 16 | @IBOutlet weak var bgEffectView: NSVisualEffectView! 17 | @IBOutlet weak var tvVersion: NSTextField! 18 | @IBOutlet weak var tvCopyright: NSTextField! 19 | 20 | override func windowDidLoad() { 21 | super.windowDidLoad() 22 | 23 | bgEffectView.state = .active 24 | bgEffectView.blendingMode = .behindWindow 25 | bgEffectView.material = .sidebar 26 | 27 | self.window!.level = .popUpMenu 28 | self.window!.collectionBehavior = [.fullScreenAuxiliary, .stationary, .canJoinAllSpaces] 29 | self.window!.canHide = false 30 | self.window!.isMovableByWindowBackground = true 31 | self.window!.isMovable = true 32 | 33 | let bundle = Bundle.main 34 | tvVersion.stringValue = "v\(bundle.object(forInfoDictionaryKey: "CFBundleShortVersionString") ?? "")(build\(bundle.object(forInfoDictionaryKey: "CFBundleVersion") ?? ""))" 35 | 36 | tvCopyright.stringValue = bundle.object(forInfoDictionaryKey: "NSHumanReadableCopyright") as? String ?? "" 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /EyeSaver/AboutWindowController.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 | -------------------------------------------------------------------------------- /EyeSaver/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // EyeSaver 4 | // 5 | // Created by Marno on 9/14/19. 6 | // Copyright © 2019 Marno. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import UserNotifications 11 | import ServiceManagement 12 | 13 | @NSApplicationMain 14 | class AppDelegate: NSObject, NSApplicationDelegate,NSWindowDelegate,TimerTickDelegate, UNUserNotificationCenterDelegate { 15 | 16 | @IBOutlet weak var popover: NSPopover! 17 | 18 | let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) 19 | 20 | var eventMonitor: EventMonitor? 21 | 22 | func applicationDidFinishLaunching(_ aNotification: Notification) { 23 | // clearUserDefaults() 24 | ConfigUtils.shared.start() 25 | 26 | if let button = statusItem.button { 27 | button.image = NSImage(named: NSImage.Name("statusIcon")) 28 | button.imagePosition = NSControl.ImagePosition.imageLeading 29 | button.action = #selector(togglePopover) 30 | } 31 | 32 | eventMonitor = EventMonitor(mask: [.leftMouseDown, .rightMouseDown]) { [weak self] event in 33 | if let strongSelf = self, strongSelf.popover.isShown { 34 | strongSelf.closePopover(event!) 35 | } 36 | } 37 | 38 | autoLaunch() 39 | 40 | // 监听是否显示状态栏时间 41 | DistributedNotificationCenter.default().addObserver(self, selector: #selector(hideMenuBarTimer), name: NSNotification.Name(rawValue: "cn.marno.enableMenuBarTimer"), object: nil) 42 | 43 | WorkTimerManager.shared.addDelegate(delegate: self) 44 | WorkTimerManager.shared.startTimer() 45 | 46 | addScreenLockerListener() 47 | } 48 | 49 | func applicationWillTerminate(_ aNotification: Notification) { 50 | 51 | } 52 | 53 | // 添加对锁屏和解锁的监听 54 | func addScreenLockerListener(){ 55 | // 屏幕锁定 56 | DistributedNotificationCenter.default().addObserver(self, selector: #selector(screenLocked), name: NSNotification.Name(rawValue: "com.apple.screenIsLocked"), object: nil) 57 | 58 | // 屏幕解锁 59 | DistributedNotificationCenter.default().addObserver(self, selector: #selector(screenUnlocked), name: NSNotification.Name(rawValue: "com.apple.screenIsUnlocked"), object: nil) 60 | 61 | } 62 | 63 | // 开机自启动 64 | func autoLaunch() { 65 | let launcherAppId = "cn.marno.EyeSaverLauncher" 66 | let runningApps = NSWorkspace.shared.runningApplications 67 | let isRunning = !runningApps.filter { $0.bundleIdentifier == launcherAppId }.isEmpty 68 | 69 | SMLoginItemSetEnabled(launcherAppId as CFString, ConfigUtils.shared.valueStartWhenMacLogin) 70 | 71 | if isRunning { 72 | DistributedNotificationCenter.default().post(name: Notification.Name("killLauncher"), object: Bundle.main.bundleIdentifier!) 73 | } 74 | } 75 | 76 | @objc func screenLocked(){ 77 | if ConfigUtils.shared.valueDisableWhenLockScreen { 78 | if(!WorkTimerManager.shared.isManualStop){ WorkTimerManager.shared.stopTimer(isManualStop: false) 79 | } 80 | } 81 | } 82 | 83 | @objc func screenUnlocked(){ 84 | if ConfigUtils.shared.valueDisableWhenLockScreen { 85 | if(!WorkTimerManager.shared.isManualStop) { 86 | WorkTimerManager.shared.startTimer() 87 | } 88 | } 89 | } 90 | 91 | @objc func hideMenuBarTimer(){ 92 | onTick(currentTime:WorkTimerManager.shared.countTimeLength) 93 | } 94 | 95 | @objc func showPopover(_ sender: AnyObject) { 96 | if let button = statusItem.button { 97 | popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY) 98 | eventMonitor?.start() 99 | } 100 | } 101 | 102 | @objc func closePopover(_ sender: AnyObject) { 103 | popover.performClose(sender) 104 | eventMonitor?.stop() 105 | } 106 | 107 | @objc func togglePopover(_ sender: AnyObject) { 108 | if popover.isShown { 109 | closePopover(sender) 110 | } else { 111 | showPopover(sender) 112 | } 113 | } 114 | 115 | func onTick(currentTime: Double) { 116 | if let button = statusItem.button { 117 | button.title = ConfigUtils.shared.valueEnableMenuBarTimer ? TimeUtils.convertSec2Min(time: currentTime):"" 118 | } 119 | } 120 | 121 | func onFinish() { 122 | var windowControllers = [RelaxRemindWindowController]() 123 | for screen in NSScreen.screens { 124 | let relaxWindowController = RelaxRemindWindowController(windowNibName: "RelaxRemindWindowController") 125 | relaxWindowController.window?.setFrame(screen.frame, display: false) 126 | windowControllers.append(relaxWindowController) 127 | } 128 | for contronller in windowControllers{ 129 | contronller.show(controllers: windowControllers) 130 | } 131 | if popover.isShown { closePopover(popover) } 132 | } 133 | 134 | func onReset(){ 135 | if let button = statusItem.button { 136 | button.title = TimeUtils.convertSec2Min(time: ConfigUtils.shared.valueWorkTime) 137 | } 138 | } 139 | 140 | // 测试用 141 | func clearUserDefaults(){ 142 | let userDefaults = UserDefaults.standard 143 | let keys = userDefaults.dictionaryRepresentation() 144 | for key in keys { 145 | userDefaults.removeObject(forKey: key.key) 146 | } 147 | userDefaults.synchronize() 148 | } 149 | } 150 | 151 | -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "size": "16x16", 5 | "idiom": "mac", 6 | "filename": "icon-16.png", 7 | "scale": "1x" 8 | }, 9 | { 10 | "size": "16x16", 11 | "idiom": "mac", 12 | "filename": "icon-16@2x.png", 13 | "scale": "2x" 14 | }, 15 | { 16 | "size": "32x32", 17 | "idiom": "mac", 18 | "filename": "icon-32.png", 19 | "scale": "1x" 20 | }, 21 | { 22 | "size": "32x32", 23 | "idiom": "mac", 24 | "filename": "icon-32@2x.png", 25 | "scale": "2x" 26 | }, 27 | { 28 | "size": "128x128", 29 | "idiom": "mac", 30 | "filename": "icon-128.png", 31 | "scale": "1x" 32 | }, 33 | { 34 | "size": "128x128", 35 | "idiom": "mac", 36 | "filename": "icon-128@2x.png", 37 | "scale": "2x" 38 | }, 39 | { 40 | "size": "256x256", 41 | "idiom": "mac", 42 | "filename": "icon-256.png", 43 | "scale": "1x" 44 | }, 45 | { 46 | "size": "256x256", 47 | "idiom": "mac", 48 | "filename": "icon-256@2x.png", 49 | "scale": "2x" 50 | }, 51 | { 52 | "size": "512x512", 53 | "idiom": "mac", 54 | "filename": "icon-512.png", 55 | "scale": "1x" 56 | }, 57 | { 58 | "size": "512x512", 59 | "idiom": "mac", 60 | "filename": "icon-512@2x.png", 61 | "scale": "2x" 62 | } 63 | ], 64 | "info": { 65 | "version": 1, 66 | "author": "icon.wuruihong.com" 67 | } 68 | } -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/AppIcon.appiconset/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/AppIcon.appiconset/icon-128.png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/AppIcon.appiconset/icon-128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/AppIcon.appiconset/icon-128@2x.png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/AppIcon.appiconset/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/AppIcon.appiconset/icon-16.png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/AppIcon.appiconset/icon-16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/AppIcon.appiconset/icon-16@2x.png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/AppIcon.appiconset/icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/AppIcon.appiconset/icon-256.png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/AppIcon.appiconset/icon-256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/AppIcon.appiconset/icon-256@2x.png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/AppIcon.appiconset/icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/AppIcon.appiconset/icon-32.png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/AppIcon.appiconset/icon-32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/AppIcon.appiconset/icon-32@2x.png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/AppIcon.appiconset/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/AppIcon.appiconset/icon-512.png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/AppIcon.appiconset/icon-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/AppIcon.appiconset/icon-512@2x.png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/aboutIcon.imageset/About (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/aboutIcon.imageset/About (1).png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/aboutIcon.imageset/About (2).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/aboutIcon.imageset/About (2).png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/aboutIcon.imageset/About (3).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/aboutIcon.imageset/About (3).png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/aboutIcon.imageset/About (4).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/aboutIcon.imageset/About (4).png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/aboutIcon.imageset/About (5).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/aboutIcon.imageset/About (5).png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/aboutIcon.imageset/About.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/aboutIcon.imageset/About.png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/aboutIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "About (2).png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "About (3).png", 11 | "appearances" : [ 12 | { 13 | "appearance" : "luminosity", 14 | "value" : "dark" 15 | } 16 | ], 17 | "scale" : "1x" 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "filename" : "About (1).png", 22 | "scale" : "2x" 23 | }, 24 | { 25 | "idiom" : "universal", 26 | "filename" : "About (4).png", 27 | "appearances" : [ 28 | { 29 | "appearance" : "luminosity", 30 | "value" : "dark" 31 | } 32 | ], 33 | "scale" : "2x" 34 | }, 35 | { 36 | "idiom" : "universal", 37 | "filename" : "About.png", 38 | "scale" : "3x" 39 | }, 40 | { 41 | "idiom" : "universal", 42 | "filename" : "About (5).png", 43 | "appearances" : [ 44 | { 45 | "appearance" : "luminosity", 46 | "value" : "dark" 47 | } 48 | ], 49 | "scale" : "3x" 50 | } 51 | ], 52 | "info" : { 53 | "version" : 1, 54 | "author" : "xcode" 55 | } 56 | } -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/appLogo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "icon-256.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "icon-256@2x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/appLogo.imageset/icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/appLogo.imageset/icon-256.png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/appLogo.imageset/icon-256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/appLogo.imageset/icon-256@2x.png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/helpIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "help.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "help (5).png", 11 | "appearances" : [ 12 | { 13 | "appearance" : "luminosity", 14 | "value" : "dark" 15 | } 16 | ], 17 | "scale" : "1x" 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "filename" : "help (1).png", 22 | "scale" : "2x" 23 | }, 24 | { 25 | "idiom" : "universal", 26 | "filename" : "help (4).png", 27 | "appearances" : [ 28 | { 29 | "appearance" : "luminosity", 30 | "value" : "dark" 31 | } 32 | ], 33 | "scale" : "2x" 34 | }, 35 | { 36 | "idiom" : "universal", 37 | "filename" : "help (2).png", 38 | "scale" : "3x" 39 | }, 40 | { 41 | "idiom" : "universal", 42 | "filename" : "help (3).png", 43 | "appearances" : [ 44 | { 45 | "appearance" : "luminosity", 46 | "value" : "dark" 47 | } 48 | ], 49 | "scale" : "3x" 50 | } 51 | ], 52 | "info" : { 53 | "version" : 1, 54 | "author" : "xcode" 55 | } 56 | } -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/helpIcon.imageset/help (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/helpIcon.imageset/help (1).png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/helpIcon.imageset/help (2).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/helpIcon.imageset/help (2).png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/helpIcon.imageset/help (3).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/helpIcon.imageset/help (3).png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/helpIcon.imageset/help (4).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/helpIcon.imageset/help (4).png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/helpIcon.imageset/help (5).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/helpIcon.imageset/help (5).png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/helpIcon.imageset/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/helpIcon.imageset/help.png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/pauseIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "1x", 10 | "appearances" : [ 11 | { 12 | "appearance" : "luminosity", 13 | "value" : "dark" 14 | } 15 | ] 16 | }, 17 | { 18 | "idiom" : "universal", 19 | "filename" : "pause.png", 20 | "scale" : "2x" 21 | }, 22 | { 23 | "idiom" : "universal", 24 | "filename" : "pause-white.png", 25 | "appearances" : [ 26 | { 27 | "appearance" : "luminosity", 28 | "value" : "dark" 29 | } 30 | ], 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "universal", 35 | "scale" : "3x" 36 | }, 37 | { 38 | "idiom" : "universal", 39 | "scale" : "3x", 40 | "appearances" : [ 41 | { 42 | "appearance" : "luminosity", 43 | "value" : "dark" 44 | } 45 | ] 46 | } 47 | ], 48 | "info" : { 49 | "version" : 1, 50 | "author" : "xcode" 51 | } 52 | } -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/pauseIcon.imageset/pause-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/pauseIcon.imageset/pause-white.png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/pauseIcon.imageset/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/pauseIcon.imageset/pause.png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/startIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "1x", 10 | "appearances" : [ 11 | { 12 | "appearance" : "luminosity", 13 | "value" : "dark" 14 | } 15 | ] 16 | }, 17 | { 18 | "idiom" : "universal", 19 | "filename" : "start.png", 20 | "scale" : "2x" 21 | }, 22 | { 23 | "idiom" : "universal", 24 | "filename" : "start_white.png", 25 | "appearances" : [ 26 | { 27 | "appearance" : "luminosity", 28 | "value" : "dark" 29 | } 30 | ], 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "universal", 35 | "scale" : "3x" 36 | }, 37 | { 38 | "idiom" : "universal", 39 | "scale" : "3x", 40 | "appearances" : [ 41 | { 42 | "appearance" : "luminosity", 43 | "value" : "dark" 44 | } 45 | ] 46 | } 47 | ], 48 | "info" : { 49 | "version" : 1, 50 | "author" : "xcode" 51 | } 52 | } -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/startIcon.imageset/start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/startIcon.imageset/start.png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/startIcon.imageset/start_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/startIcon.imageset/start_white.png -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/statusIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "眼睛.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | }, 21 | "properties" : { 22 | "template-rendering-intent" : "template" 23 | } 24 | } -------------------------------------------------------------------------------- /EyeSaver/Assets.xcassets/statusIcon.imageset/眼睛.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarnoDev/EyeSaver/b2d60c3631b885c99290fd0ba9aafca14c033e37/EyeSaver/Assets.xcassets/statusIcon.imageset/眼睛.png -------------------------------------------------------------------------------- /EyeSaver/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 | -------------------------------------------------------------------------------- /EyeSaver/EventMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventMonitor.swift 3 | // EyeSaver 4 | // 5 | // Created by Marno on 9/14/19. 6 | // Copyright © 2019 Marno. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class EventMonitor { 12 | var mask: NSEvent.EventTypeMask 13 | var handler : (NSEvent?) -> () 14 | var monitor: Any? 15 | 16 | init(mask: NSEvent.EventTypeMask, handler: @escaping (NSEvent?) -> ()){ 17 | self.mask = mask 18 | self.handler = handler 19 | } 20 | 21 | deinit { 22 | stop() 23 | } 24 | 25 | func start(){ 26 | monitor = NSEvent.addGlobalMonitorForEvents(matching: mask, handler: handler) 27 | } 28 | 29 | func stop() { 30 | if monitor != nil { 31 | NSEvent.removeMonitor(monitor!) 32 | monitor = nil 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /EyeSaver/FullscreenRemindScreen.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 | -------------------------------------------------------------------------------- /EyeSaver/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0.3 21 | CFBundleVersion 22 | 103 23 | LSApplicationCategoryType 24 | public.app-category.utilities 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | LSUIElement 28 | 29 | NSHumanReadableCopyright 30 | Copyright © 2019 Marno. All rights reserved. 31 | NSMainNibFile 32 | MainMenu 33 | NSPrincipalClass 34 | NSApplication 35 | 36 | 37 | -------------------------------------------------------------------------------- /EyeSaver/PopoverDemo.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /EyeSaver/PopoverDemoViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PopoverDemoViewController.swift 3 | // EyeSaver 4 | // 5 | // Created by Marno on 9/14/19. 6 | // Copyright © 2019 Marno. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class PopoverDemoViewController: NSViewController, NSWindowDelegate, TimerTickDelegate, NSTextFieldDelegate { 12 | 13 | // 组件绑定 14 | @IBOutlet weak var tvTimeDisplay: NSTextField! // 显示计时 15 | @IBOutlet weak var btnStart: NSButton! // 开始 16 | @IBOutlet weak var btnSkip: NSButton! // 跳过 17 | @IBOutlet weak var btnReset: NSTextField! // 重置 18 | @IBOutlet weak var barView: OGCircularBarView! // 进度 19 | @IBOutlet weak var segment: NSSegmentedControl! // tab 控制 20 | @IBOutlet weak var tabView: NSTabView! // tab 页面 21 | @IBOutlet weak var tvWorkTimeLength: NSTextField! // 设置:工作时长显示 22 | @IBOutlet weak var tvRelaxTimeLength: NSTextField! // 设置:休息时长显示 23 | @IBOutlet weak var tvStopIntervalTime: NSTextField! // 计时:暂停时长显示 24 | @IBOutlet weak var sliderStopTimeInterval: NSSlider! // 计时:选择恢复计时时长 25 | @IBOutlet weak var sliderWorkTime: NSSlider! // 设置:选择工作时长 26 | @IBOutlet weak var sliderRelaxTime: NSSlider! // 设置:选择休息时长 27 | @IBOutlet weak var btnEnableRightClick: NSButton! // 设置:启动双击跳过休息 28 | @IBOutlet weak var btnEnableSpace: NSButton! // 设置:启用空格跳过休息 29 | @IBOutlet weak var btnEnableMenubarTimer: NSButton! // 设置:启用状态栏显示倒计时 30 | @IBOutlet weak var btnStartWhenMacLogin: NSButton! // 设置:开机启动 31 | @IBOutlet weak var inputHintText: NSTextField! 32 | @IBOutlet weak var btnDisableWhenLockScreen: NSButton! // 设置:锁屏暂停 33 | 34 | var initialStopTime:Double = 0 35 | var isPlaying:Bool = false 36 | var isManualPause:Bool = false 37 | var countStopTimer:Timer? = nil 38 | let aboutWindowController = AboutWindowController(windowNibName: "AboutWindowController") 39 | 40 | override func viewDidLoad() { 41 | super.viewDidLoad() 42 | WorkTimerManager.shared.addDelegate(delegate: self) 43 | initProgressView() 44 | initViewState() 45 | } 46 | 47 | override var representedObject: Any? { 48 | didSet {} 49 | } 50 | 51 | // 初始化组件的值 52 | func initViewState(){ 53 | sliderWorkTime.intValue = Int32(ConfigUtils.shared.valueWorkTime/60) 54 | sliderRelaxTime.intValue = Int32(ConfigUtils.shared.valueRelaxTime) 55 | tvWorkTimeLength.stringValue = "\(sliderWorkTime.intValue)m" 56 | tvRelaxTimeLength.stringValue = "\(sliderRelaxTime.intValue)s" 57 | tvTimeDisplay.stringValue = TimeUtils.convertSec2Min(time: ConfigUtils.shared.valueWorkTime) 58 | 59 | btnEnableRightClick.state = ConfigUtils.shared.valueEnableRightClickSkip ? .on : .off 60 | btnEnableSpace.state = ConfigUtils.shared.valueEnableSpaceSkip ? .on : .off 61 | btnEnableMenubarTimer.state = ConfigUtils.shared.valueEnableMenuBarTimer ? .on : .off 62 | btnStartWhenMacLogin.state = ConfigUtils.shared.valueStartWhenMacLogin ? .on : .off 63 | 64 | inputHintText.isBordered = true 65 | inputHintText.placeholderString = ConfigUtils.shared.valueHintText 66 | inputHintText.delegate = self 67 | 68 | btnDisableWhenLockScreen.state = ConfigUtils.shared.valueDisableWhenLockScreen ? .on : .off 69 | } 70 | 71 | // 初始化进度条 72 | func initProgressView(){ 73 | let green = NSColor(calibratedRed: 8/255, green: 193/255, blue: 97/255, alpha: 1) 74 | barView.addBarBackground(startAngle: 90, endAngle: -270, radius: 80, width: 26, color: green.withAlphaComponent(0.1)) 75 | barView.addBar(startAngle: 90, endAngle: -270, progress: 0, radius: 80, width: 26, color: green, animationDuration: 1.5, glowOpacity: 0.4, glowRadius: 6) 76 | barView.bars.last?.animateProgress(0.001, duration: 0) 77 | } 78 | 79 | func onTick(currentTime: Double) { 80 | isPlaying = true 81 | updateBtnStartImage() 82 | tvTimeDisplay.stringValue = TimeUtils.convertSec2Min(time: currentTime) 83 | let totalTime = ConfigUtils.shared.valueWorkTime 84 | barView.bars.last?.animateProgress(CGFloat(1.0 - currentTime/totalTime), duration: 1) 85 | } 86 | 87 | func onStart(initialTime: Double) { 88 | isPlaying = true 89 | updateBtnStartImage() 90 | } 91 | 92 | func onPause() { 93 | isPlaying = false 94 | updateBtnStartImage() 95 | } 96 | 97 | func onFinish() { 98 | isPlaying = false 99 | updateBtnStartImage() 100 | } 101 | 102 | func onReset() { 103 | updateBtnStartImage() 104 | tvTimeDisplay.stringValue = TimeUtils.convertSec2Min(time: ConfigUtils.shared.valueWorkTime) 105 | barView.bars.last?.progress = 0.001 106 | tvStopIntervalTime.stringValue = "0:00:00" 107 | sliderStopTimeInterval.doubleValue = 0.0 108 | } 109 | 110 | func updateBtnStartImage(){ 111 | btnStart.image = isPlaying ? #imageLiteral(resourceName: "pauseIcon") : #imageLiteral(resourceName: "startIcon") 112 | sliderStopTimeInterval.isEnabled = !isPlaying 113 | tvStopIntervalTime.isEnabled = !isPlaying 114 | } 115 | 116 | @IBAction func openHelpLink(_ sender: Any) { 117 | NSWorkspace.shared.open(URL(string: "https://mp.weixin.qq.com/mp/homepage?__biz=MzA3NjgyNzk2Mw==&hid=9&sn=a8659578289ebda32f75ac0949ea1258")!) 118 | 119 | } 120 | 121 | @IBAction func openAbout(_ sender: Any) { 122 | aboutWindowController.showWindow(aboutWindowController) 123 | } 124 | 125 | // 自定义提示语 126 | func controlTextDidChange(_ obj: Notification) { 127 | let afterTrimString = inputHintText.stringValue.trimmingCharacters(in: .whitespaces) 128 | if afterTrimString.count == 0 { return } 129 | 130 | // if afterTrimString.count > 8 { 131 | // inputHintText.stringValue = String(afterTrimString.prefix(8)) 132 | // } 133 | ConfigUtils.shared.valueHintText = inputHintText.stringValue 134 | } 135 | 136 | func controlTextDidEndEditing(_ obj: Notification) { 137 | inputHintText.placeholderString = ConfigUtils.shared.valueHintText 138 | } 139 | 140 | // 锁屏时是否暂停 141 | @IBAction func disableWhenLockScreen(_ sender: Any) { 142 | let isEnable = btnDisableWhenLockScreen.state == .on 143 | ConfigUtils.shared.valueDisableWhenLockScreen = isEnable 144 | } 145 | 146 | // 状态栏是否显示计时 147 | @IBAction func enableMenubarTimer(_ sender: Any) { 148 | let isEnable = btnEnableMenubarTimer.state == .on 149 | ConfigUtils.shared.valueEnableMenuBarTimer = isEnable 150 | DistributedNotificationCenter.default().post(name: NSNotification.Name("cn.marno.enableMenuBarTimer"), object: nil) 151 | } 152 | 153 | // 启用双击跳过 154 | @IBAction func enableRightClick(_ sender: Any) { 155 | let isEnable = btnEnableRightClick.state == .on 156 | ConfigUtils.shared.valueEnableRightClickSkip = isEnable 157 | } 158 | 159 | // 启用长按空格跳过 160 | @IBAction func enableSpace(_ sender: Any) { 161 | let isEnable = btnEnableSpace.state == .on 162 | ConfigUtils.shared.valueEnableSpaceSkip = isEnable 163 | } 164 | 165 | // 选择停止的时长 166 | @IBAction func selectStopInterval(_ sender: Any) { 167 | initialStopTime = sliderStopTimeInterval.doubleValue * 3600 168 | tvStopIntervalTime.stringValue = TimeUtils.convertSec2Min(time:initialStopTime, needHour: true) 169 | if initialStopTime != 0.0 { 170 | countDownStopTime() 171 | }else{ 172 | countStopTimer?.invalidate() 173 | } 174 | } 175 | 176 | // 选择休息时长 177 | @IBAction func selectRelaxTimeLength(_ sender: Any) { 178 | tvRelaxTimeLength.stringValue = "\(sliderRelaxTime.intValue)s" 179 | ConfigUtils.shared.valueRelaxTime = sliderRelaxTime.doubleValue 180 | } 181 | 182 | // 选择工作时长 183 | @IBAction func selectWorkTimeLength(_ sender: Any) { 184 | tvWorkTimeLength.stringValue = "\(sliderWorkTime.intValue)m" 185 | ConfigUtils.shared.valueWorkTime = sliderWorkTime.doubleValue * 60 186 | } 187 | 188 | // 开启启动 189 | @IBAction func launchAtLogin(_ sender: Any) { 190 | let isEnable = btnStartWhenMacLogin.state == .on 191 | ConfigUtils.shared.valueStartWhenMacLogin = isEnable 192 | } 193 | 194 | // 切换 tab 195 | @IBAction func segmentControlClick(_ sender: Any) { 196 | tabView.selectTabViewItem(at: segment.selectedSegment) 197 | } 198 | 199 | // 退出应用 200 | @IBAction func quitApp(_ sender: Any) { 201 | NSApplication.shared.terminate(self) 202 | } 203 | 204 | // 切换计时开关状态 205 | @IBAction func toggleTimer(_ sender: Any) { 206 | if !isPlaying { 207 | WorkTimerManager.shared.startTimer() 208 | countStopTimer?.invalidate() 209 | initialStopTime = sliderStopTimeInterval.doubleValue * 3600 210 | tvStopIntervalTime.stringValue = TimeUtils.convertSec2Min(time:initialStopTime, needHour: true) 211 | }else{ 212 | WorkTimerManager.shared.stopTimer(isManualStop: true) 213 | if sliderStopTimeInterval.doubleValue != 0.0 { 214 | countDownStopTime() 215 | } 216 | } 217 | } 218 | 219 | // 跳过计时 220 | @IBAction func skipTimer(_ sender: Any) { 221 | WorkTimerManager.shared.skipTimer() 222 | } 223 | 224 | // 重置计时 225 | @IBAction func resetTimer(_ sender: Any) { 226 | WorkTimerManager.shared.resetTimer(directPlay:isPlaying) 227 | } 228 | 229 | // 开始暂停倒计时 230 | func countDownStopTime() { 231 | countStopTimer?.invalidate() 232 | countStopTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {(timer) in 233 | if self.initialStopTime == 0{ 234 | timer.invalidate() 235 | self.initialStopTime = self.sliderStopTimeInterval.doubleValue * 3600 236 | self.tvStopIntervalTime.stringValue = TimeUtils.convertSec2Min(time:self.initialStopTime, needHour: true) 237 | self.toggleTimer(-1) 238 | }else{ 239 | self.initialStopTime -= 1 240 | } 241 | self.tvStopIntervalTime.stringValue = TimeUtils.convertSec2Min(time: self.initialStopTime,needHour: true) 242 | }) 243 | } 244 | 245 | } 246 | -------------------------------------------------------------------------------- /EyeSaver/PopoverDemoViewController.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 | 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 | %{value1}@ 77 | 78 | 79 | 80 | 81 | 92 | 93 | 94 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 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 | 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 | 287 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 317 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | -------------------------------------------------------------------------------- /EyeSaver/RectangleView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RectangleView.swift 3 | // EyeSaver 4 | // 5 | // Created by Marno on 9/14/19. 6 | // Copyright © 2019 Marno. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class RectangleView: NSVisualEffectView { 12 | 13 | override func awakeFromNib() { 14 | self.autoresizingMask = [.width, .height] 15 | self.blendingMode = .behindWindow 16 | self.state = .active 17 | // self.material = .toolTip 18 | self.material = .sidebar 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /EyeSaver/RelaxRemindWindowController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RelaxRemindWindowController.swift 3 | // EyeSaver 4 | // 5 | // Created by Marno on 9/14/19. 6 | // Copyright © 2019 Marno. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class RelaxRemindWindow:NSWindow{} 12 | 13 | class RelaxRemindWindowController: NSWindowController, NSWindowDelegate,TimerTickDelegate { 14 | 15 | @IBOutlet weak var rectView: RectangleView! 16 | @IBOutlet weak var tvRelaxTime: NSTextField! 17 | @IBOutlet weak var tvHint: NSTextField! 18 | 19 | var controllers = [RelaxRemindWindowController]() 20 | var countRelaxTimer: Timer? = nil 21 | var isWantStopTimer: Bool = false // 用户手动暂停计时 22 | var isScreenLock:Bool = false // 屏幕是否锁定状态 23 | 24 | override func windowDidLoad() { 25 | super.windowDidLoad() 26 | 27 | addEventListener() 28 | addScreenLockerListener() 29 | initWindow() 30 | 31 | RelaxTimerManager.shared.addDelegate(delegate: self) 32 | RelaxTimerManager.shared.startTimer() 33 | } 34 | 35 | func addEventListener(){ 36 | // 添加对键盘的监听 37 | NSEvent.addLocalMonitorForEvents(matching: .keyDown) { (aEvent) -> NSEvent? in 38 | self.keyDown(with: aEvent) 39 | return aEvent 40 | } 41 | // 添加对右键点击的监听 42 | NSEvent.addLocalMonitorForEvents(matching: .rightMouseDown){ (aEvent) -> NSEvent? in 43 | self.rightMouseDown(with: aEvent) 44 | return aEvent 45 | } 46 | } 47 | 48 | // 添加对锁屏和解锁的监听 49 | func addScreenLockerListener(){ 50 | // 屏幕锁定 51 | DistributedNotificationCenter.default().addObserver(self, selector: #selector(screenLocked), name: NSNotification.Name(rawValue: "com.apple.screenIsLocked"), object: nil) 52 | 53 | 54 | // 屏幕解锁 55 | DistributedNotificationCenter.default().addObserver(self, selector: #selector(screenUnlocked), name: NSNotification.Name(rawValue: "com.apple.screenIsUnlocked"), object: nil) 56 | } 57 | 58 | @objc func screenLocked(){ 59 | if ConfigUtils.shared.valueDisableWhenLockScreen { 60 | isScreenLock = true 61 | } 62 | 63 | } 64 | 65 | @objc func screenUnlocked(){ 66 | if ConfigUtils.shared.valueDisableWhenLockScreen { 67 | isScreenLock = false 68 | } 69 | } 70 | 71 | func initWindow(){ 72 | self.window!.styleMask = .borderless 73 | self.window!.hasShadow = false 74 | self.window!.level = .popUpMenu 75 | self.window!.isOpaque = false 76 | self.window!.collectionBehavior = [.fullScreenAuxiliary, .stationary, .canJoinAllSpaces] 77 | self.window!.canHide = false 78 | 79 | self.window!.backgroundColor = .clear 80 | self.window!.isMovableByWindowBackground = false 81 | self.window!.isMovable = false 82 | 83 | // tvHint.autoresizingMask = .height 84 | // tvHint.sizeToFit() 85 | } 86 | 87 | 88 | 89 | // 右键双击关闭 90 | override func rightMouseDown(with event: NSEvent) { 91 | if ConfigUtils.shared.valueEnableRightClickSkip && event.clickCount == 2 { 92 | closeAllWindow() 93 | } 94 | } 95 | 96 | // 长按空格关闭(连按3次) 97 | override func keyDown(with event: NSEvent) { 98 | if ConfigUtils.shared.valueEnableSpaceSkip && event.characters == " " && event.isARepeat { 99 | closeAllWindow() 100 | } 101 | } 102 | 103 | func windowWillClose(_ notification: Notification) { 104 | if !isWantStopTimer { 105 | WorkTimerManager.shared.resetTimer(directPlay: !isScreenLock) 106 | } 107 | } 108 | 109 | func show(controllers:[RelaxRemindWindowController]){ 110 | isWantStopTimer = false 111 | showWindow(self.window) 112 | self.window!.orderFrontRegardless() 113 | self.controllers = controllers 114 | showBgAlphaAnimation(controllers: controllers) 115 | } 116 | 117 | // 显示渐变动画 118 | public func showBgAlphaAnimation(controllers:[RelaxRemindWindowController]) { 119 | tvHint.stringValue = ConfigUtils.shared.valueHintText 120 | rectView.alphaValue = 0.0 121 | NSAnimationContext.endGrouping() 122 | NSAnimationContext.runAnimationGroup({(context) in 123 | 124 | context.duration = 1 125 | let timingFunc = CAMediaTimingFunction(name: .easeInEaseOut) 126 | context.timingFunction = timingFunc 127 | self.rectView.animator().setFrameOrigin(NSMakePoint(0, 0)) 128 | self.rectView.animator().alphaValue = 0.95 129 | 130 | }) 131 | } 132 | 133 | @IBAction func closeWindow(_ sender: Any) { 134 | closeAllWindow() 135 | } 136 | 137 | @IBAction func pauseTimer(_ sender: Any) { 138 | isWantStopTimer = true 139 | closeAllWindow() 140 | WorkTimerManager.shared.resetTimer(directPlay: false) 141 | } 142 | 143 | func onStart(initialTime: Double) { 144 | self.tvRelaxTime.stringValue = TimeUtils.convertSec2Min(time: initialTime) 145 | } 146 | 147 | func onTick(currentTime: Double) { 148 | self.tvRelaxTime.stringValue = TimeUtils.convertSec2Min(time: currentTime) 149 | } 150 | 151 | func onFinish() { 152 | closeAllWindow() 153 | } 154 | 155 | func closeAllWindow(){ 156 | for wc in controllers {wc.window?.close()} 157 | RelaxTimerManager.shared.resetTimer(directPlay: false) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /EyeSaver/RelaxRemindWindowController.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 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /EyeSaver/RelaxTimeManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimerManager.swift 3 | // EyeSaver 4 | // 5 | // Created by Marno on 9/15/19. 6 | // Copyright © 2019 Marno. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class RelaxTimerManager { 12 | 13 | var countTimeLength: Double 14 | var initialTimeLength: Double 15 | var countTimer: Timer? = nil 16 | var delegates = [TimerTickDelegate]() 17 | 18 | static let shared = RelaxTimerManager() 19 | 20 | private init(){ 21 | self.countTimeLength = ConfigUtils.shared.valueRelaxTime 22 | self.initialTimeLength = self.countTimeLength 23 | } 24 | 25 | func addDelegate(delegate:TimerTickDelegate){ 26 | delegates.append(delegate) 27 | } 28 | 29 | func startTimer(){ 30 | for d in self.delegates { d.onStart(initialTime: countTimeLength) } 31 | countTimer?.invalidate() 32 | countTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {(timer) in 33 | if self.countTimeLength == 0{ 34 | timer.invalidate() 35 | for d in self.delegates { d.onFinish() } 36 | }else{ 37 | self.countTimeLength -= 1 38 | for d in self.delegates { d.onTick(currentTime: self.countTimeLength)} 39 | } 40 | }) 41 | } 42 | 43 | func stopTimer(){ 44 | countTimer?.invalidate() 45 | for d in self.delegates { d.onPause() } 46 | } 47 | 48 | func resetTimer(directPlay:Bool = true){ 49 | stopTimer() 50 | countTimeLength = ConfigUtils.shared.valueRelaxTime 51 | for d in self.delegates { d.onReset() } 52 | if directPlay{ startTimer() } 53 | } 54 | 55 | func skipTimer(){ 56 | for d in self.delegates { d.onFinish() } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /EyeSaver/Utils/ConfigUtils.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | class ConfigUtils { 4 | 5 | private var INITIAL_WORK_TIME: Double = 20 * 60 6 | private var INITIAL_RELAX_TIME: Double = 20 7 | 8 | let userDefaults = UserDefaults.standard 9 | 10 | private let keyIsSecondOpenApp = "keyIsFirstOpenApp" 11 | private let keyStartWhenMacLogin = "keyStartWhenMacLogin" 12 | private let keyEnableMenuBarTimer = "keyEnableMenuBarTimer" 13 | private let keyRightClickSkip = "keyRightClickSkip" 14 | private let keySpaceSkip = "keySpaceSkip" 15 | private let keyDisableWhenFullscreen = "keyDisableWhenFullscreen" 16 | private let keyWorkTime = "keyWorkTime" 17 | private let keyRelaxTime = "keyRelaxTime" 18 | private let keyHintText = "keyHintText" 19 | private let keyDisableWhenLockScreen = "keyDisableWhenLockScreen" 20 | 21 | var valueIsSecondOpenApp: Bool{ 22 | willSet{ 23 | if valueIsSecondOpenApp != newValue { 24 | userDefaults.set(newValue, forKey: keyIsSecondOpenApp) 25 | } 26 | } 27 | } 28 | 29 | // 是否开机启动 30 | var valueStartWhenMacLogin:Bool{ 31 | willSet{ 32 | if valueStartWhenMacLogin != newValue { userDefaults.set(newValue, forKey: keyStartWhenMacLogin) } 33 | } 34 | } 35 | 36 | // 是否显示状态栏计时 37 | var valueEnableMenuBarTimer: Bool{ 38 | willSet{ 39 | if valueEnableMenuBarTimer != newValue { userDefaults.set(newValue, forKey: keyEnableMenuBarTimer) } 40 | } 41 | } 42 | 43 | // 鼠标右键双击跳过 44 | var valueEnableRightClickSkip: Bool{ 45 | willSet{ 46 | if valueEnableRightClickSkip != newValue { userDefaults.set(newValue, forKey: keyRightClickSkip)} 47 | } 48 | } 49 | 50 | // 长按空格跳过 51 | var valueEnableSpaceSkip: Bool{ 52 | willSet{ 53 | if valueEnableSpaceSkip != newValue { userDefaults.set(newValue, forKey: keySpaceSkip)} 54 | } 55 | } 56 | 57 | // 全屏时禁用 58 | var valueDisableWhenFullscreen: Bool{ 59 | willSet{ 60 | if valueDisableWhenFullscreen != newValue { userDefaults.set(newValue, forKey: keyDisableWhenFullscreen)} 61 | } 62 | } 63 | 64 | // 工作时长 65 | var valueWorkTime:Double{ 66 | willSet{ 67 | if valueWorkTime != newValue { 68 | userDefaults.set(newValue, forKey: keyWorkTime)} 69 | } 70 | } 71 | 72 | // 休息时长 73 | var valueRelaxTime: Double{ 74 | willSet{ 75 | if valueRelaxTime != newValue { userDefaults.set(newValue, forKey: keyRelaxTime)} 76 | } 77 | } 78 | 79 | // 提示语 80 | var valueHintText:String { 81 | willSet{ 82 | if valueHintText != newValue { userDefaults.set(newValue, forKey: keyHintText)} 83 | } 84 | } 85 | 86 | var valueDisableWhenLockScreen:Bool{ 87 | willSet{ 88 | if valueDisableWhenLockScreen != newValue { userDefaults.set(newValue, forKey: keyDisableWhenLockScreen)} 89 | } 90 | } 91 | 92 | static let shared = ConfigUtils() 93 | 94 | private init(){ 95 | valueIsSecondOpenApp = userDefaults.bool(forKey: keyIsSecondOpenApp) 96 | 97 | // 判断是否是初次打开应用 98 | if !valueIsSecondOpenApp { // 是 99 | valueStartWhenMacLogin = false 100 | valueEnableMenuBarTimer = true 101 | valueEnableRightClickSkip = true 102 | valueEnableSpaceSkip = true 103 | valueDisableWhenFullscreen = false 104 | valueWorkTime = INITIAL_WORK_TIME 105 | valueRelaxTime = INITIAL_RELAX_TIME 106 | valueHintText = "站起来,看远方" 107 | valueDisableWhenLockScreen = true 108 | userDefaults.set(true, forKey: keyIsSecondOpenApp) 109 | userDefaults.set(valueStartWhenMacLogin, forKey: keyStartWhenMacLogin) 110 | userDefaults.set(valueEnableMenuBarTimer, forKey: keyEnableMenuBarTimer) 111 | userDefaults.set(valueEnableRightClickSkip, forKey: keyRightClickSkip) 112 | userDefaults.set(valueEnableSpaceSkip, forKey: keySpaceSkip) 113 | userDefaults.set(valueDisableWhenFullscreen, forKey: keyDisableWhenFullscreen) 114 | userDefaults.set(valueWorkTime, forKey: keyWorkTime) 115 | userDefaults.set(valueRelaxTime, forKey: keyRelaxTime) 116 | userDefaults.set(valueHintText, forKey: keyHintText) 117 | userDefaults.set(valueDisableWhenLockScreen, forKey: keyDisableWhenLockScreen) 118 | } else { // 不是 119 | valueStartWhenMacLogin = userDefaults.bool(forKey: keyStartWhenMacLogin) 120 | valueEnableMenuBarTimer = userDefaults.bool(forKey: keyEnableMenuBarTimer) 121 | valueEnableRightClickSkip = userDefaults.bool(forKey: keyRightClickSkip) 122 | valueEnableSpaceSkip = userDefaults.bool(forKey: keySpaceSkip) 123 | valueDisableWhenFullscreen = userDefaults.bool(forKey: keyDisableWhenFullscreen) 124 | valueWorkTime = userDefaults.double(forKey: keyWorkTime) 125 | valueRelaxTime = userDefaults.double(forKey: keyRelaxTime) 126 | // valueWorkTime = 10 127 | // valueRelaxTime = 5 128 | valueHintText = userDefaults.string(forKey: keyHintText)! 129 | valueDisableWhenLockScreen = userDefaults.bool(forKey: keyDisableWhenLockScreen) 130 | } 131 | } 132 | 133 | func start(){} 134 | 135 | } 136 | -------------------------------------------------------------------------------- /EyeSaver/Utils/TimeUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateTimeUtils.swift 3 | // EyeSaver 4 | // 5 | // Created by Marno on 9/15/19. 6 | // Copyright © 2019 Marno All rights reserved. 7 | // 8 | 9 | class TimeUtils { 10 | 11 | private init(){} 12 | 13 | // 把秒数格式化为 00:00:00 或 00:00 14 | static func convertSec2Min(time:Double, needHour:Bool = false) ->String { 15 | let allTime: Int = Int(time) 16 | var hours = 0 17 | var minutes = 0 18 | var seconds = 0 19 | var hoursText = "" 20 | var minutesText = "" 21 | var secondsText = "" 22 | 23 | hours = allTime / 3600 24 | hoursText = hours > 9 ? "\(hours)" : "\(hours)" 25 | 26 | minutes = allTime % 3600 / 60 27 | minutesText = minutes > 9 ? "\(minutes)" : "0\(minutes)" 28 | 29 | seconds = allTime % 3600 % 60 30 | secondsText = seconds > 9 ? "\(seconds)" : "0\(seconds)" 31 | 32 | return needHour ? "\(hoursText):\(minutesText):\(secondsText)" : "\(minutesText):\(secondsText)" 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /EyeSaver/WorkTimerManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimerManager.swift 3 | // EyeSaver 4 | // 5 | // Created by Marno on 9/15/19. 6 | // Copyright © 2019 Marno. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class WorkTimerManager { 12 | 13 | var countTimeLength: Double 14 | var initialTimeLength: Double 15 | var countTimer: Timer? = nil 16 | var delegates = [TimerTickDelegate]() 17 | var isManualStop: Bool = false 18 | 19 | static let shared = WorkTimerManager() 20 | 21 | private init(){ 22 | self.countTimeLength = ConfigUtils.shared.valueWorkTime 23 | self.initialTimeLength = self.countTimeLength 24 | } 25 | 26 | func addDelegate(delegate:TimerTickDelegate){ 27 | delegates.append(delegate) 28 | } 29 | 30 | func startTimer(){ 31 | for d in self.delegates { d.onStart(initialTime: countTimeLength) } 32 | countTimer?.invalidate() 33 | countTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {(timer) in 34 | if self.countTimeLength == 0{ 35 | timer.invalidate() 36 | for d in self.delegates { d.onFinish() } 37 | }else{ 38 | self.countTimeLength -= 1 39 | for d in self.delegates { d.onTick(currentTime: self.countTimeLength)} 40 | } 41 | }) 42 | } 43 | 44 | func stopTimer(isManualStop:Bool = false){ 45 | self.isManualStop = isManualStop 46 | countTimer?.invalidate() 47 | for d in self.delegates { d.onPause() } 48 | } 49 | 50 | func resetTimer(directPlay:Bool = true){ 51 | stopTimer() 52 | countTimeLength = ConfigUtils.shared.valueWorkTime 53 | for d in self.delegates { d.onReset() } 54 | if directPlay{ startTimer() } 55 | } 56 | 57 | func skipTimer(){ 58 | for d in self.delegates { d.onFinish() } 59 | } 60 | 61 | } 62 | 63 | protocol TimerTickDelegate { 64 | func onTick(currentTime: Double) 65 | func onStart(initialTime: Double) 66 | func onPause() 67 | func onFinish() 68 | func onReset() 69 | } 70 | 71 | extension TimerTickDelegate{ 72 | func onTick(currentTime: Double) {} 73 | func onStart(initialTime: Double) {} 74 | func onPause() {} 75 | func onFinish() {} 76 | func onReset(){} 77 | } 78 | -------------------------------------------------------------------------------- /EyeSaverLauncher/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // EyeSaverLancher 4 | // 5 | // Created by Marno on 9/19/19. 6 | // Copyright © 2019 Marno. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | 15 | func applicationDidFinishLaunching(_ aNotification: Notification) { 16 | 17 | let mainAppIdentifier = "cn.marno.eyesaver" 18 | let runningApps = NSWorkspace.shared.runningApplications 19 | let isRunning = !runningApps.filter { $0.bundleIdentifier == mainAppIdentifier }.isEmpty 20 | 21 | if isRunning { 22 | self.terminate() 23 | }else{ 24 | DistributedNotificationCenter.default().addObserver(self, 25 | selector: #selector(self.terminate), 26 | name: Notification.Name("killLauncher"), 27 | object: mainAppIdentifier) 28 | 29 | let url = [Int](repeating: 0, count: 4).reduce(Bundle.main.bundleURL) { (url, _) -> URL in 30 | url.deletingLastPathComponent() 31 | } 32 | 33 | try? NSWorkspace.shared.launchApplication(at: url, options: [.withoutActivation], configuration: [:]) 34 | } 35 | 36 | } 37 | 38 | @objc func terminate() { 39 | NSApp.terminate(nil) 40 | } 41 | 42 | } 43 | 44 | -------------------------------------------------------------------------------- /EyeSaverLauncher/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /EyeSaverLauncher/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /EyeSaverLauncher/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /EyeSaverLauncher/EyeSaverLauncher.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /EyeSaverLauncher/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | LSBackgroundOnly 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIconFile 12 | 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2019 Marno All rights reserved. 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | NSSupportsAutomaticTermination 34 | 35 | NSSupportsSuddenTermination 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /OGCircularBar/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSHumanReadableCopyright 22 | Copyright © 2017 Oskar Groth. All rights reserved. 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /OGCircularBar/NSBezierPath+CGPath.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSBezierPath+CGPath.swift 3 | // OGCircularBar 4 | // 5 | // Created by Oskar Groth on 2017-04-15. 6 | // Copyright © 2017 Oskar Groth. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AppKit 11 | 12 | extension NSBezierPath { 13 | 14 | internal var cgPath: CGPath { 15 | let path = CGMutablePath() 16 | var points = [CGPoint](repeating: .zero, count: 3) 17 | for i in 0 ..< self.elementCount { 18 | let type = self.element(at: i, associatedPoints: &points) 19 | switch type { 20 | case .moveTo: path.move(to: points[0]) 21 | case .lineTo: path.addLine(to: points[0]) 22 | case .curveTo: path.addCurve(to: points[2], control1: points[0], control2: points[1]) 23 | case .closePath: path.closeSubpath() 24 | } 25 | } 26 | return path 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /OGCircularBar/OGCircularBar.h: -------------------------------------------------------------------------------- 1 | // 2 | // OGCircularBar.h 3 | // OGCircularBar 4 | // 5 | // Created by Oskar Groth on 2017-04-17. 6 | // Copyright © 2017 Oskar Groth. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for OGCircularBar. 12 | FOUNDATION_EXPORT double OGCircularBarVersionNumber; 13 | 14 | //! Project version string for OGCircularBar. 15 | FOUNDATION_EXPORT const unsigned char OGCircularBarVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /OGCircularBar/OGCircularBarView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OGCircularBar.swift 3 | // OGCircularBar 4 | // 5 | // Created by Oskar Groth on 2017-02-19. 6 | // Copyright © 2017 Oskar Groth. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | public class OGCircularBarView: NSView, Sequence { 12 | 13 | public var bars: [CircularBarLayer] = [] 14 | public var circleBars: [CircularBarLayer] = [] 15 | var center: CGPoint { 16 | get { 17 | return NSMakePoint(floor(bounds.width/2), floor(bounds.height/2)) 18 | } 19 | } 20 | 21 | public override func awakeFromNib() { 22 | super.awakeFromNib() 23 | setup() 24 | } 25 | 26 | override public init(frame frameRect: NSRect) { 27 | super.init(frame: frameRect) 28 | setup() 29 | } 30 | 31 | required public init?(coder aDecoder: NSCoder) { 32 | super.init(coder: aDecoder) 33 | setup() 34 | } 35 | 36 | func setup() { 37 | wantsLayer = true 38 | } 39 | 40 | public func addBar(startAngle: CGFloat, endAngle: CGFloat, progress: CGFloat, radius: CGFloat, width: CGFloat, color: NSColor, animationDuration: CGFloat, glowOpacity: Float, glowRadius: CGFloat) { 41 | let barLayer = CircularBarLayer(center: center, radius: radius, width: width, startAngle: startAngle, endAngle: endAngle, color: color) 42 | barLayer.shadowColor = color.cgColor 43 | barLayer.shadowRadius = glowRadius 44 | barLayer.shadowOpacity = glowOpacity 45 | barLayer.shadowOffset = NSSize.zero 46 | 47 | layer?.addSublayer(barLayer) 48 | bars.append(barLayer) 49 | if animationDuration > 0 { 50 | barLayer.animateProgress(progress, duration: animationDuration) 51 | } else { 52 | barLayer.progress = progress 53 | } 54 | } 55 | 56 | public func addBarBackground(startAngle: CGFloat, endAngle: CGFloat, radius: CGFloat, width: CGFloat, color: NSColor) { 57 | let barLayer = CircularBarLayer(center: center, radius: radius, width: width, startAngle: startAngle, endAngle: endAngle, color: color) 58 | barLayer.progress = 1 59 | layer?.addSublayer(barLayer) 60 | } 61 | 62 | 63 | public subscript(index: Int) -> CircularBarLayer { 64 | return bars[index] 65 | } 66 | 67 | public func makeIterator() -> IndexingIterator<[CircularBarLayer]> { 68 | return bars.makeIterator() 69 | } 70 | 71 | } 72 | 73 | open class CircularBarLayer: CAShapeLayer, CALayerDelegate, CAAnimationDelegate { 74 | 75 | var completion: (() -> Void)? 76 | 77 | open var progress: CGFloat? { 78 | get { 79 | return strokeEnd 80 | } 81 | set { 82 | strokeEnd = (newValue ?? 0) 83 | } 84 | } 85 | 86 | public init(center: CGPoint, radius: CGFloat, width: CGFloat, startAngle: CGFloat, endAngle: CGFloat, color: NSColor) { 87 | super.init() 88 | let bezier = NSBezierPath() 89 | bezier.appendArc(withCenter: NSZeroPoint, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: startAngle > endAngle) 90 | bezier.transform(using: AffineTransform(translationByX: center.x, byY: center.y)) 91 | delegate = self as CALayerDelegate 92 | path = bezier.cgPath 93 | fillColor = NSColor.clear.cgColor 94 | strokeColor = color.cgColor 95 | lineWidth = width 96 | lineCap = CAShapeLayerLineCap.round 97 | strokeStart = 0 98 | strokeEnd = 0 99 | } 100 | 101 | public required init?(coder aDecoder: NSCoder) { 102 | super.init(coder: aDecoder) 103 | } 104 | 105 | override init(layer: Any) { 106 | super.init(layer: layer) 107 | } 108 | 109 | open func animateProgress(_ progress: CGFloat, duration: CGFloat, completion: (() -> Void)? = nil) { 110 | removeAllAnimations() 111 | let progress = progress 112 | let animation = CABasicAnimation(keyPath: "strokeEnd") 113 | animation.fromValue = strokeEnd 114 | animation.toValue = progress 115 | animation.duration = CFTimeInterval(duration) 116 | animation.delegate = self as CAAnimationDelegate 117 | animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut) 118 | strokeEnd = progress 119 | add(animation, forKey: "strokeEnd") 120 | } 121 | 122 | public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { 123 | if flag { 124 | completion?() 125 | } 126 | } 127 | } 128 | 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 一、功能介绍 2 | 3 | 回到正题,先大概介绍下,软件名字叫《**护眼提醒(EyeSaver)**》,顾名思义,是一款定时提醒我们保护视力的应用,目前具备以下功能: 4 | 5 | - 全屏护眼提醒 6 | - 自定义提醒语 7 | - 自定义工作时长 8 | - 自定义休息时长 9 | - 锁屏自动暂停计时 10 | - 快捷跳过休息提醒 11 | - 暂停后自动恢复计时 12 | - 多屏幕时同时显示提醒 13 | - 提醒界面直接暂停程序 14 | 15 | 软件的核心功能很简单,可以近似的认为就是一个倒计时功能,倒计时结束后给一个强制的全屏提醒。 16 | 17 | ## 二、开发心得 18 | 19 | 不过说起来很简单,但由于我也是第一次用 Swift 开发 macOS 的原生应用,所以这个过程中还是查阅了很多资料的。不得不说,**macOS 相关的开发资料真是少**的可怜,很多问题我至今也没找到解决办法,可能是因为苹果电脑的用户不多吧,所以相关的生态和社区都不是很完善。 20 | 21 | 22 | 经过这两个月小范围的测试使用,软件相对来说,还是比较稳定了。**运行时对于内存的占用也非常低,大概只有20多M(多个屏幕时,占用会高点),而且几乎不会占用 CPU**。因为第一次开发,没有什么优化经验,也希望有经验的朋友可以提提建议。 23 | 24 | 由于软件的功能比较简单,所以使用方法也非常简单,基本启动以后就不需要再管了。不过为了防止有些功能大家不熟悉,我还是会写一篇使用教程放到公众号里面,以供参考。 25 | 26 | ## 三、下载使用 27 | 28 | **下载地址:**
29 | 公众号对话框回复(不是文章留言):**EyeSaver**(大小写都可) 30 | 31 | **截图展示:**
32 | 33 | ![](https://user-gold-cdn.xitu.io/2019/11/18/16e7c73389976c72?w=660&h=431&f=png&s=242095) 34 | 35 | **使用方式:**
36 | - 下载到电脑后进行解压 37 | - 将 `EyeSaver.app` 复制到“应用程序”文件夹 38 | - 点击打开即可 39 | 40 | 41 | **支持系统:**
42 | 软件理论上支持从 `10.12 - 10.14 的 macOS`,但是测试中发现有些 Mac 电脑使用时会有异常,比如软件已经运行,但是状态栏不显示图标等,目前还不知道什么问题,后续版本会解决。 43 | 44 | **问题解决:**
45 | 如果软件提示已损坏,或者点击了没有反应,可以参考下面链接中的方式解决:(长按复制,在浏览器打开) 46 | - http://www.sdifen.com/Sierraany.html 47 | - http://www.sdifen.com/crashes.html 48 | 49 | 50 | **软件后续的更新也会发布到公众号中,如有需要可以持续关注公众号的动态。另外,使用过程有什么 bug 也可以在公众号留言给我,不过一定要描述清楚问题。** 51 | 52 | ***** 53 | 54 |

保护视力,也许有用,来试试吧!

55 | 56 | ![](https://user-gold-cdn.xitu.io/2019/11/18/16e7c67fbd8a2952?w=636&h=400&f=png&s=58369) 57 | --------------------------------------------------------------------------------