├── .gitattributes ├── .gitignore ├── .swiftlint.yml ├── Apple Juice.xcodeproj └── project.pbxproj ├── AppleJuice ├── AppDelegate.swift ├── Apple Juice.entitlements ├── ApplicationController.swift ├── ApplicationMenuDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon-1024.png │ │ ├── icon-128.png │ │ ├── icon-16.png │ │ ├── icon-256.png │ │ ├── icon-32.png │ │ ├── icon-512.png │ │ └── icon-64.png │ ├── BatteryFill.imageset │ │ ├── Contents.json │ │ └── Fill.pdf │ ├── BatteryFillCapLeft.imageset │ │ ├── CapLeft.pdf │ │ └── Contents.json │ ├── BatteryFillCapRight.imageset │ │ ├── CapRight.pdf │ │ └── Contents.json │ ├── BatteryOutline.imageset │ │ ├── Contents.json │ │ └── Outline.pdf │ ├── ChargedAndPlugged.imageset │ │ ├── Charged and Plugged.pdf │ │ └── Contents.json │ ├── Charging.imageset │ │ ├── Charging.pdf │ │ └── Contents.json │ ├── Contents.json │ ├── DeadCropped.imageset │ │ ├── Contents.json │ │ └── Dead Cropped.pdf │ ├── LowBattery.imageset │ │ ├── Contents.json │ │ └── Low Battery.pdf │ └── None.imageset │ │ ├── Contents.json │ │ └── None.pdf ├── Base.lproj │ ├── Localizable.strings │ └── MainMenu.xib ├── BatteryError.swift ├── BatteryImageCache.swift ├── BatteryRegistryPropertyKey.swift ├── BatteryService.swift ├── BatteryState.swift ├── Info.plist ├── MenuInfoView.swift ├── MenuInfoViewModel.swift ├── NSImageExtension.swift ├── NotificationKey.swift ├── Percentage.swift ├── PowerSource.swift ├── PreferenceKey.swift ├── StatusBarIcon.swift ├── StatusBarItem.swift ├── StatusNotification.swift ├── TimeRemaining.swift ├── UserPreferences.swift ├── de.lproj │ ├── Localizable.strings │ └── MainMenu.xib ├── dsa_pub.pem ├── en.lproj │ ├── Localizable.strings │ └── MainMenu.xib ├── es.lproj │ ├── Localizable.strings │ └── MainMenu.xib ├── fr.lproj │ ├── Localizable.strings │ └── MainMenu.xib ├── it.lproj │ ├── Localizable.strings │ └── MainMenu.xib └── pt-BR.lproj │ ├── Localizable.strings │ └── MainMenu.xib ├── AppleJuiceToday ├── Apple Juice Today.entitlements ├── BatteryHealthType.swift ├── BatteryInfoTypeProtocol.swift ├── ChargeInfoType.swift ├── CycleCountInfoType.swift ├── Info.plist ├── ListRowViewController.swift ├── ListRowViewController.xib ├── PercentageInfoType.swift ├── PowerUsageInfoType.swift ├── SourceInfoType.swift ├── TemperatureInfoType.swift ├── TimeRemainingInfoType.swift ├── TodayViewController.swift ├── TodayViewController.xib ├── de.lproj │ └── Info.plist ├── en.lproj │ └── Info.plist ├── es.lproj │ └── Info.plist ├── fr.lproj │ └── Info.plist └── it.lproj │ └── Info.plist ├── Cartfile ├── Cartfile.resolved ├── LICENSE ├── README.md ├── applejuice.png ├── screenshot_appmenu.png ├── screenshot_notifications.png └── screenshot_today.png /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj binary merge=union 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X Finder 2 | .DS_Store 3 | 4 | # Xcode per-user config 5 | *.mode1 6 | *.mode1v3 7 | *.mode2v3 8 | *.perspective 9 | *.perspectivev3 10 | *.pbxuser 11 | *.xcworkspace 12 | xcuserdata 13 | 14 | # Build products 15 | DerivedData/ 16 | build/ 17 | *.o 18 | *.LinkFileList 19 | *.hmap 20 | 21 | # Automatic backup files 22 | *~.nib/ 23 | *.swp 24 | *~ 25 | *.dat 26 | *.dep 27 | 28 | # Dependency management 29 | Carthage/ 30 | Pods/ 31 | 32 | # Debug Symbols 33 | *~.dSYM/ 34 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | # Exclude Dependencies 2 | excluded: 3 | - Carthage 4 | 5 | # Rules 6 | line_length: 125 7 | 8 | disabled_rules: 9 | - block_based_kvo 10 | - trailing_comma 11 | - opening_brace 12 | 13 | cyclomatic_complexity: 14 | warning: 3 15 | error: 5 16 | ignores_case_statements: true 17 | 18 | vertical_whitespace: 19 | max_empty_lines: 1 20 | 21 | -------------------------------------------------------------------------------- /Apple Juice.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8E0D33B01D3118CD002D3463 /* NotificationCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8EE1297C1C46F891000D404F /* NotificationCenter.framework */; }; 11 | 8E0D33B51D3118CD002D3463 /* TodayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E0D33B41D3118CD002D3463 /* TodayViewController.swift */; }; 12 | 8E0D33B71D3118CD002D3463 /* ListRowViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E0D33B61D3118CD002D3463 /* ListRowViewController.swift */; }; 13 | 8E0D33C41D3118CD002D3463 /* Apple Juice Today.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 8E0D33AF1D3118CD002D3463 /* Apple Juice Today.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 14 | 8E31BF9F1D42651F00055A5B /* BatteryImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E31BF9E1D42651F00055A5B /* BatteryImageCache.swift */; }; 15 | 8E58CBA21D339BC200920567 /* TodayViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8EF30EE81D32D0ED0010BC49 /* TodayViewController.xib */; }; 16 | 8E58CBA31D339BC900920567 /* ListRowViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8EF30EEA1D32D0FA0010BC49 /* ListRowViewController.xib */; }; 17 | 8E7373D91EACB58E00F10AD7 /* BatteryInfoTypeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E7373D81EACB58E00F10AD7 /* BatteryInfoTypeProtocol.swift */; }; 18 | 8E7373DB1EACB78500F10AD7 /* TimeRemainingInfoType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E7373DA1EACB78500F10AD7 /* TimeRemainingInfoType.swift */; }; 19 | 8E7373DD1EACB8D400F10AD7 /* PercentageInfoType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E7373DC1EACB8D400F10AD7 /* PercentageInfoType.swift */; }; 20 | 8E7373DF1EACBB3300F10AD7 /* PowerUsageInfoType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E7373DE1EACBB3300F10AD7 /* PowerUsageInfoType.swift */; }; 21 | 8E7373E11EACBBDB00F10AD7 /* ChargeInfoType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E7373E01EACBBDB00F10AD7 /* ChargeInfoType.swift */; }; 22 | 8E7373E31EACBC5D00F10AD7 /* CycleCountInfoType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E7373E21EACBC5D00F10AD7 /* CycleCountInfoType.swift */; }; 23 | 8E995BAF1EABD8C800CE8364 /* BatteryError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E995BA91EABD8C800CE8364 /* BatteryError.swift */; }; 24 | 8E995BB01EABD8C800CE8364 /* BatteryState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E995BAA1EABD8C800CE8364 /* BatteryState.swift */; }; 25 | 8E995BB11EABD8C800CE8364 /* NotificationKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E995BAB1EABD8C800CE8364 /* NotificationKey.swift */; }; 26 | 8E995BB31EABD8C800CE8364 /* BatteryRegistryPropertyKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E995BAD1EABD8C800CE8364 /* BatteryRegistryPropertyKey.swift */; }; 27 | 8E995BB41EABD8C800CE8364 /* StatusBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E995BAE1EABD8C800CE8364 /* StatusBarItem.swift */; }; 28 | 8E995BB51EABDA6800CE8364 /* BatteryState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E995BAA1EABD8C800CE8364 /* BatteryState.swift */; }; 29 | 8E995BB61EABDA6C00CE8364 /* BatteryError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E995BA91EABD8C800CE8364 /* BatteryError.swift */; }; 30 | 8E995BB71EABDA7100CE8364 /* BatteryRegistryPropertyKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E995BAD1EABD8C800CE8364 /* BatteryRegistryPropertyKey.swift */; }; 31 | 8E995BBB1EABDB2F00CE8364 /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = 8E995BBA1EABDB2F00CE8364 /* dsa_pub.pem */; }; 32 | 8EA14D281D3D4728004D36C6 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8EBF29EC1C485493005BBAE0 /* Localizable.strings */; }; 33 | 8EAE0D211EB4EFBF00E3D594 /* SourceInfoType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EAE0D201EB4EFBF00E3D594 /* SourceInfoType.swift */; }; 34 | 8EAE0D231EB4EFD000E3D594 /* TemperatureInfoType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EAE0D221EB4EFD000E3D594 /* TemperatureInfoType.swift */; }; 35 | 8EBEB4461C466091001DBE91 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EBEB4451C466091001DBE91 /* AppDelegate.swift */; }; 36 | 8EBEB4481C466091001DBE91 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8EBEB4471C466091001DBE91 /* Assets.xcassets */; }; 37 | 8EBEB44B1C466091001DBE91 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8EBEB4491C466091001DBE91 /* MainMenu.xib */; }; 38 | 8EBF29E91C485493005BBAE0 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8EBF29EC1C485493005BBAE0 /* Localizable.strings */; }; 39 | 8EE1296A1C4663F7000D404F /* ApplicationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EE129691C4663F7000D404F /* ApplicationController.swift */; }; 40 | 8EE1296E1C469326000D404F /* StatusBarIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EE1296D1C469326000D404F /* StatusBarIcon.swift */; }; 41 | 8EE129701C4695B5000D404F /* BatteryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EE1296F1C4695B5000D404F /* BatteryService.swift */; }; 42 | 8EE129721C46CA1B000D404F /* UserPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EE129711C46CA1B000D404F /* UserPreferences.swift */; }; 43 | 8EE129741C46D3C7000D404F /* StatusNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EE129731C46D3C7000D404F /* StatusNotification.swift */; }; 44 | 8EF30ED31D32B3170010BC49 /* BatteryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EE1296F1C4695B5000D404F /* BatteryService.swift */; }; 45 | C52233AF25575A0A00526725 /* NSImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C52233AE25575A0A00526725 /* NSImageExtension.swift */; }; 46 | C5696AB42573C7A1001316FC /* Percentage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5696AB32573C7A1001316FC /* Percentage.swift */; }; 47 | C5696AB62573C861001316FC /* Percentage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5696AB32573C7A1001316FC /* Percentage.swift */; }; 48 | C5696ABA2573DAA1001316FC /* TimeRemaining.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5696AB92573DAA1001316FC /* TimeRemaining.swift */; }; 49 | C5696ABD2573DD27001316FC /* TimeRemaining.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5696AB92573DAA1001316FC /* TimeRemaining.swift */; }; 50 | C5696AC12573E0F9001316FC /* PowerSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5696AC02573E0F9001316FC /* PowerSource.swift */; }; 51 | C5696AC42573E180001316FC /* PowerSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5696AC02573E0F9001316FC /* PowerSource.swift */; }; 52 | C57FE28B255CA29000E4E2B4 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C57FE287255CA25F00E4E2B4 /* Sparkle.framework */; }; 53 | C57FE28C255CA29000E4E2B4 /* Sparkle.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C57FE287255CA25F00E4E2B4 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 54 | C57FE291255CA2EB00E4E2B4 /* Sparkle.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = C57FE290255CA2EB00E4E2B4 /* Sparkle.framework.dSYM */; }; 55 | C57FE293255CA2F600E4E2B4 /* Sparkle.framework.dSYM in CopyFiles */ = {isa = PBXBuildFile; fileRef = C57FE290255CA2EB00E4E2B4 /* Sparkle.framework.dSYM */; }; 56 | C5819EF4257BE32700F964E3 /* ApplicationMenuDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5819EF2257BE32700F964E3 /* ApplicationMenuDelegate.swift */; }; 57 | C5819EF8257BE38E00F964E3 /* MenuInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5819EF7257BE38E00F964E3 /* MenuInfoView.swift */; }; 58 | C5819EFD2580256400F964E3 /* MenuInfoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5819EFC2580256300F964E3 /* MenuInfoViewModel.swift */; }; 59 | C59FD5E225614C3000D50FC1 /* LaunchAtLogin.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C59FD5E125614C3000D50FC1 /* LaunchAtLogin.framework */; }; 60 | C59FD5E325614C3000D50FC1 /* LaunchAtLogin.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C59FD5E125614C3000D50FC1 /* LaunchAtLogin.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 61 | C59FD5E525614C7000D50FC1 /* LaunchAtLogin.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = C59FD5E425614C7000D50FC1 /* LaunchAtLogin.framework.dSYM */; }; 62 | C59FD5E725614C8000D50FC1 /* LaunchAtLogin.framework.dSYM in CopyFiles */ = {isa = PBXBuildFile; fileRef = C59FD5E425614C7000D50FC1 /* LaunchAtLogin.framework.dSYM */; }; 63 | C5D7EB48209891D500012A81 /* BatteryHealthType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5D7EB47209891D500012A81 /* BatteryHealthType.swift */; }; 64 | C5EEDA6920276240004DE64C /* PreferenceKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5EEDA6820276240004DE64C /* PreferenceKey.swift */; }; 65 | /* End PBXBuildFile section */ 66 | 67 | /* Begin PBXContainerItemProxy section */ 68 | 8E0D33C21D3118CD002D3463 /* PBXContainerItemProxy */ = { 69 | isa = PBXContainerItemProxy; 70 | containerPortal = 8EBEB43A1C466091001DBE91 /* Project object */; 71 | proxyType = 1; 72 | remoteGlobalIDString = 8E0D33AE1D3118CD002D3463; 73 | remoteInfo = "Apple Juice Widget"; 74 | }; 75 | /* End PBXContainerItemProxy section */ 76 | 77 | /* Begin PBXCopyFilesBuildPhase section */ 78 | 8EE129951C46F891000D404F /* Embed App Extensions */ = { 79 | isa = PBXCopyFilesBuildPhase; 80 | buildActionMask = 2147483647; 81 | dstPath = ""; 82 | dstSubfolderSpec = 13; 83 | files = ( 84 | 8E0D33C41D3118CD002D3463 /* Apple Juice Today.appex in Embed App Extensions */, 85 | ); 86 | name = "Embed App Extensions"; 87 | runOnlyForDeploymentPostprocessing = 0; 88 | }; 89 | C57FE28D255CA29100E4E2B4 /* Embed Frameworks */ = { 90 | isa = PBXCopyFilesBuildPhase; 91 | buildActionMask = 2147483647; 92 | dstPath = ""; 93 | dstSubfolderSpec = 10; 94 | files = ( 95 | C59FD5E325614C3000D50FC1 /* LaunchAtLogin.framework in Embed Frameworks */, 96 | C57FE28C255CA29000E4E2B4 /* Sparkle.framework in Embed Frameworks */, 97 | ); 98 | name = "Embed Frameworks"; 99 | runOnlyForDeploymentPostprocessing = 0; 100 | }; 101 | C57FE28F255CA2CB00E4E2B4 /* CopyFiles */ = { 102 | isa = PBXCopyFilesBuildPhase; 103 | buildActionMask = 2147483647; 104 | dstPath = ""; 105 | dstSubfolderSpec = 16; 106 | files = ( 107 | C59FD5E725614C8000D50FC1 /* LaunchAtLogin.framework.dSYM in CopyFiles */, 108 | C57FE293255CA2F600E4E2B4 /* Sparkle.framework.dSYM in CopyFiles */, 109 | ); 110 | runOnlyForDeploymentPostprocessing = 0; 111 | }; 112 | /* End PBXCopyFilesBuildPhase section */ 113 | 114 | /* Begin PBXFileReference section */ 115 | 5EF9F80621FB7A3300748374 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; 116 | 8E0D33AF1D3118CD002D3463 /* Apple Juice Today.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Apple Juice Today.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 117 | 8E0D33B41D3118CD002D3463 /* TodayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayViewController.swift; sourceTree = ""; }; 118 | 8E0D33B61D3118CD002D3463 /* ListRowViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRowViewController.swift; sourceTree = ""; }; 119 | 8E31BF9E1D42651F00055A5B /* BatteryImageCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatteryImageCache.swift; sourceTree = ""; }; 120 | 8E5BC0241C50D13D0077228F /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; 121 | 8E7373D81EACB58E00F10AD7 /* BatteryInfoTypeProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatteryInfoTypeProtocol.swift; sourceTree = ""; }; 122 | 8E7373DA1EACB78500F10AD7 /* TimeRemainingInfoType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeRemainingInfoType.swift; sourceTree = ""; }; 123 | 8E7373DC1EACB8D400F10AD7 /* PercentageInfoType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PercentageInfoType.swift; sourceTree = ""; }; 124 | 8E7373DE1EACBB3300F10AD7 /* PowerUsageInfoType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PowerUsageInfoType.swift; sourceTree = ""; }; 125 | 8E7373E01EACBBDB00F10AD7 /* ChargeInfoType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChargeInfoType.swift; sourceTree = ""; }; 126 | 8E7373E21EACBC5D00F10AD7 /* CycleCountInfoType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CycleCountInfoType.swift; sourceTree = ""; }; 127 | 8E954FA81C4EE528006D0F34 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; 128 | 8E995BA91EABD8C800CE8364 /* BatteryError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatteryError.swift; sourceTree = ""; }; 129 | 8E995BAA1EABD8C800CE8364 /* BatteryState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatteryState.swift; sourceTree = ""; }; 130 | 8E995BAB1EABD8C800CE8364 /* NotificationKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationKey.swift; sourceTree = ""; }; 131 | 8E995BAD1EABD8C800CE8364 /* BatteryRegistryPropertyKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatteryRegistryPropertyKey.swift; sourceTree = ""; }; 132 | 8E995BAE1EABD8C800CE8364 /* StatusBarItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarItem.swift; sourceTree = ""; }; 133 | 8E995BBA1EABDB2F00CE8364 /* dsa_pub.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = dsa_pub.pem; sourceTree = ""; }; 134 | 8EAE0D201EB4EFBF00E3D594 /* SourceInfoType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SourceInfoType.swift; sourceTree = ""; }; 135 | 8EAE0D221EB4EFD000E3D594 /* TemperatureInfoType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TemperatureInfoType.swift; sourceTree = ""; }; 136 | 8EBEB4421C466091001DBE91 /* Apple Juice.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Apple Juice.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 137 | 8EBEB4451C466091001DBE91 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 138 | 8EBEB4471C466091001DBE91 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 139 | 8EBEB44A1C466091001DBE91 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 140 | 8EBEB44C1C466091001DBE91 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 141 | 8EBF29EB1C485493005BBAE0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 142 | 8EBF29ED1C48552D005BBAE0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; 143 | 8EBF29F31C485741005BBAE0 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; 144 | 8EBF29FD1C486640005BBAE0 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; 145 | 8EE129691C4663F7000D404F /* ApplicationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplicationController.swift; sourceTree = ""; }; 146 | 8EE1296D1C469326000D404F /* StatusBarIcon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarIcon.swift; sourceTree = ""; }; 147 | 8EE1296F1C4695B5000D404F /* BatteryService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatteryService.swift; sourceTree = ""; }; 148 | 8EE129711C46CA1B000D404F /* UserPreferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserPreferences.swift; sourceTree = ""; }; 149 | 8EE129731C46D3C7000D404F /* StatusNotification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusNotification.swift; sourceTree = ""; }; 150 | 8EE1297C1C46F891000D404F /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; 151 | 8EF30EE81D32D0ED0010BC49 /* TodayViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TodayViewController.xib; sourceTree = ""; }; 152 | 8EF30EEA1D32D0FA0010BC49 /* ListRowViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ListRowViewController.xib; sourceTree = ""; }; 153 | 8EF30EF01D32D1C70010BC49 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 154 | C52233AE25575A0A00526725 /* NSImageExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSImageExtension.swift; sourceTree = ""; }; 155 | C526408E22A29A5E00D46AE0 /* Apple Juice.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Apple Juice.entitlements"; sourceTree = ""; }; 156 | C566887025630F9C005D745C /* Apple Juice Today.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Apple Juice Today.entitlements"; sourceTree = ""; }; 157 | C5696AB32573C7A1001316FC /* Percentage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Percentage.swift; sourceTree = ""; }; 158 | C5696AB92573DAA1001316FC /* TimeRemaining.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeRemaining.swift; sourceTree = ""; }; 159 | C5696AC02573E0F9001316FC /* PowerSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowerSource.swift; sourceTree = ""; }; 160 | C57FE287255CA25F00E4E2B4 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = Carthage/Build/Mac/Sparkle.framework; sourceTree = ""; }; 161 | C57FE290255CA2EB00E4E2B4 /* Sparkle.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = Sparkle.framework.dSYM; path = Carthage/Build/Mac/Sparkle.framework.dSYM; sourceTree = ""; }; 162 | C5819EF2257BE32700F964E3 /* ApplicationMenuDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplicationMenuDelegate.swift; sourceTree = ""; }; 163 | C5819EF7257BE38E00F964E3 /* MenuInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuInfoView.swift; sourceTree = ""; }; 164 | C5819EFC2580256300F964E3 /* MenuInfoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuInfoViewModel.swift; sourceTree = ""; }; 165 | C58CD0B6222179A300D286C0 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainMenu.xib; sourceTree = ""; }; 166 | C58CD0B7222179D200D286C0 /* de */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = de; path = de.lproj/MainMenu.xib; sourceTree = ""; }; 167 | C58CD0B822217BAB00D286C0 /* fr */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = fr; path = fr.lproj/MainMenu.xib; sourceTree = ""; }; 168 | C58CD0B92221D9A800D286C0 /* it */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = it; path = it.lproj/MainMenu.xib; sourceTree = ""; }; 169 | C58CD0BA2221DBB500D286C0 /* es */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = es; path = es.lproj/MainMenu.xib; sourceTree = ""; }; 170 | C58CD0BB2221E6B900D286C0 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = "pt-BR"; path = "pt-BR.lproj/MainMenu.xib"; sourceTree = ""; }; 171 | C59FD5E125614C3000D50FC1 /* LaunchAtLogin.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LaunchAtLogin.framework; path = Carthage/Build/Mac/LaunchAtLogin.framework; sourceTree = ""; }; 172 | C59FD5E425614C7000D50FC1 /* LaunchAtLogin.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = LaunchAtLogin.framework.dSYM; path = Carthage/Build/Mac/LaunchAtLogin.framework.dSYM; sourceTree = ""; }; 173 | C5D7EB47209891D500012A81 /* BatteryHealthType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryHealthType.swift; sourceTree = ""; }; 174 | C5EEDA6820276240004DE64C /* PreferenceKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferenceKey.swift; sourceTree = ""; }; 175 | /* End PBXFileReference section */ 176 | 177 | /* Begin PBXFrameworksBuildPhase section */ 178 | 8E0D33AC1D3118CD002D3463 /* Frameworks */ = { 179 | isa = PBXFrameworksBuildPhase; 180 | buildActionMask = 2147483647; 181 | files = ( 182 | 8E0D33B01D3118CD002D3463 /* NotificationCenter.framework in Frameworks */, 183 | ); 184 | runOnlyForDeploymentPostprocessing = 0; 185 | }; 186 | C57FE26D255C9DDE00E4E2B4 /* Frameworks */ = { 187 | isa = PBXFrameworksBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | C57FE28B255CA29000E4E2B4 /* Sparkle.framework in Frameworks */, 191 | C59FD5E225614C3000D50FC1 /* LaunchAtLogin.framework in Frameworks */, 192 | ); 193 | runOnlyForDeploymentPostprocessing = 0; 194 | }; 195 | /* End PBXFrameworksBuildPhase section */ 196 | 197 | /* Begin PBXGroup section */ 198 | 8E03E9DC1C4BCFBA00D12D11 /* Supporting Files */ = { 199 | isa = PBXGroup; 200 | children = ( 201 | 8EBEB44C1C466091001DBE91 /* Info.plist */, 202 | 8E995BBA1EABDB2F00CE8364 /* dsa_pub.pem */, 203 | ); 204 | name = "Supporting Files"; 205 | sourceTree = ""; 206 | }; 207 | 8E0D33B11D3118CD002D3463 /* AppleJuiceToday */ = { 208 | isa = PBXGroup; 209 | children = ( 210 | 8E0D33B41D3118CD002D3463 /* TodayViewController.swift */, 211 | 8E0D33B61D3118CD002D3463 /* ListRowViewController.swift */, 212 | 8E7373D81EACB58E00F10AD7 /* BatteryInfoTypeProtocol.swift */, 213 | 8E7373DA1EACB78500F10AD7 /* TimeRemainingInfoType.swift */, 214 | 8E7373DC1EACB8D400F10AD7 /* PercentageInfoType.swift */, 215 | 8E7373DE1EACBB3300F10AD7 /* PowerUsageInfoType.swift */, 216 | 8E7373E01EACBBDB00F10AD7 /* ChargeInfoType.swift */, 217 | 8E7373E21EACBC5D00F10AD7 /* CycleCountInfoType.swift */, 218 | 8EAE0D201EB4EFBF00E3D594 /* SourceInfoType.swift */, 219 | 8EAE0D221EB4EFD000E3D594 /* TemperatureInfoType.swift */, 220 | C5D7EB47209891D500012A81 /* BatteryHealthType.swift */, 221 | 8E0D33C81D311906002D3463 /* UI */, 222 | 8E0D33B21D3118CD002D3463 /* Supporting Files */, 223 | ); 224 | path = AppleJuiceToday; 225 | sourceTree = ""; 226 | }; 227 | 8E0D33B21D3118CD002D3463 /* Supporting Files */ = { 228 | isa = PBXGroup; 229 | children = ( 230 | C566887025630F9C005D745C /* Apple Juice Today.entitlements */, 231 | 8EF30EF01D32D1C70010BC49 /* Info.plist */, 232 | ); 233 | name = "Supporting Files"; 234 | sourceTree = ""; 235 | }; 236 | 8E0D33C81D311906002D3463 /* UI */ = { 237 | isa = PBXGroup; 238 | children = ( 239 | 8EF30EE81D32D0ED0010BC49 /* TodayViewController.xib */, 240 | 8EF30EEA1D32D0FA0010BC49 /* ListRowViewController.xib */, 241 | ); 242 | name = UI; 243 | sourceTree = ""; 244 | }; 245 | 8EBEB4391C466091001DBE91 = { 246 | isa = PBXGroup; 247 | children = ( 248 | 8EBEB4441C466091001DBE91 /* AppleJuice */, 249 | 8E0D33B11D3118CD002D3463 /* AppleJuiceToday */, 250 | 8EE1297B1C46F891000D404F /* Frameworks */, 251 | 8EBEB4431C466091001DBE91 /* Products */, 252 | ); 253 | sourceTree = ""; 254 | }; 255 | 8EBEB4431C466091001DBE91 /* Products */ = { 256 | isa = PBXGroup; 257 | children = ( 258 | 8EBEB4421C466091001DBE91 /* Apple Juice.app */, 259 | 8E0D33AF1D3118CD002D3463 /* Apple Juice Today.appex */, 260 | ); 261 | name = Products; 262 | sourceTree = ""; 263 | }; 264 | 8EBEB4441C466091001DBE91 /* AppleJuice */ = { 265 | isa = PBXGroup; 266 | children = ( 267 | 8EBEB4451C466091001DBE91 /* AppDelegate.swift */, 268 | 8EE129691C4663F7000D404F /* ApplicationController.swift */, 269 | C5819EF2257BE32700F964E3 /* ApplicationMenuDelegate.swift */, 270 | C5819EF7257BE38E00F964E3 /* MenuInfoView.swift */, 271 | C5819EFC2580256300F964E3 /* MenuInfoViewModel.swift */, 272 | 8E995BAE1EABD8C800CE8364 /* StatusBarItem.swift */, 273 | 8EE1296D1C469326000D404F /* StatusBarIcon.swift */, 274 | 8E31BF9E1D42651F00055A5B /* BatteryImageCache.swift */, 275 | 8EE129731C46D3C7000D404F /* StatusNotification.swift */, 276 | 8E995BAB1EABD8C800CE8364 /* NotificationKey.swift */, 277 | 8EE1296F1C4695B5000D404F /* BatteryService.swift */, 278 | 8E995BAA1EABD8C800CE8364 /* BatteryState.swift */, 279 | C5696AB32573C7A1001316FC /* Percentage.swift */, 280 | C5696AB92573DAA1001316FC /* TimeRemaining.swift */, 281 | C5696AC02573E0F9001316FC /* PowerSource.swift */, 282 | 8E995BAD1EABD8C800CE8364 /* BatteryRegistryPropertyKey.swift */, 283 | 8E995BA91EABD8C800CE8364 /* BatteryError.swift */, 284 | 8EE129711C46CA1B000D404F /* UserPreferences.swift */, 285 | C5EEDA6820276240004DE64C /* PreferenceKey.swift */, 286 | C52233AE25575A0A00526725 /* NSImageExtension.swift */, 287 | 8EE129681C4661BA000D404F /* UI */, 288 | 8EE129671C4661AC000D404F /* Resources */, 289 | 8E03E9DC1C4BCFBA00D12D11 /* Supporting Files */, 290 | ); 291 | path = AppleJuice; 292 | sourceTree = ""; 293 | }; 294 | 8EE129671C4661AC000D404F /* Resources */ = { 295 | isa = PBXGroup; 296 | children = ( 297 | 8EBF29EC1C485493005BBAE0 /* Localizable.strings */, 298 | C526408E22A29A5E00D46AE0 /* Apple Juice.entitlements */, 299 | 8EBEB4471C466091001DBE91 /* Assets.xcassets */, 300 | ); 301 | name = Resources; 302 | sourceTree = ""; 303 | }; 304 | 8EE129681C4661BA000D404F /* UI */ = { 305 | isa = PBXGroup; 306 | children = ( 307 | 8EBEB4491C466091001DBE91 /* MainMenu.xib */, 308 | ); 309 | name = UI; 310 | sourceTree = ""; 311 | }; 312 | 8EE1297B1C46F891000D404F /* Frameworks */ = { 313 | isa = PBXGroup; 314 | children = ( 315 | 8EE1297C1C46F891000D404F /* NotificationCenter.framework */, 316 | C59FD5E425614C7000D50FC1 /* LaunchAtLogin.framework.dSYM */, 317 | C59FD5E125614C3000D50FC1 /* LaunchAtLogin.framework */, 318 | C57FE290255CA2EB00E4E2B4 /* Sparkle.framework.dSYM */, 319 | C57FE287255CA25F00E4E2B4 /* Sparkle.framework */, 320 | ); 321 | name = Frameworks; 322 | sourceTree = ""; 323 | }; 324 | /* End PBXGroup section */ 325 | 326 | /* Begin PBXNativeTarget section */ 327 | 8E0D33AE1D3118CD002D3463 /* Apple Juice Today */ = { 328 | isa = PBXNativeTarget; 329 | buildConfigurationList = 8E0D33C51D3118CD002D3463 /* Build configuration list for PBXNativeTarget "Apple Juice Today" */; 330 | buildPhases = ( 331 | 8E0D33AB1D3118CD002D3463 /* Sources */, 332 | 8E0D33AC1D3118CD002D3463 /* Frameworks */, 333 | 8E0D33AD1D3118CD002D3463 /* Resources */, 334 | ); 335 | buildRules = ( 336 | ); 337 | dependencies = ( 338 | ); 339 | name = "Apple Juice Today"; 340 | productName = "Apple Juice Widget"; 341 | productReference = 8E0D33AF1D3118CD002D3463 /* Apple Juice Today.appex */; 342 | productType = "com.apple.product-type.app-extension"; 343 | }; 344 | 8EBEB4411C466091001DBE91 /* Apple Juice */ = { 345 | isa = PBXNativeTarget; 346 | buildConfigurationList = 8EBEB44F1C466091001DBE91 /* Build configuration list for PBXNativeTarget "Apple Juice" */; 347 | buildPhases = ( 348 | 8EBEB43E1C466091001DBE91 /* Sources */, 349 | 8EBEB4401C466091001DBE91 /* Resources */, 350 | 8EE129951C46F891000D404F /* Embed App Extensions */, 351 | C57FE26D255C9DDE00E4E2B4 /* Frameworks */, 352 | C57FE28D255CA29100E4E2B4 /* Embed Frameworks */, 353 | C59FD5DF25614B0200D50FC1 /* ShellScript */, 354 | C57FE28F255CA2CB00E4E2B4 /* CopyFiles */, 355 | C57FE24E255C8B4100E4E2B4 /* ShellScript */, 356 | 8EE129751C46E993000D404F /* ShellScript */, 357 | ); 358 | buildRules = ( 359 | ); 360 | dependencies = ( 361 | 8E0D33C31D3118CD002D3463 /* PBXTargetDependency */, 362 | ); 363 | name = "Apple Juice"; 364 | packageProductDependencies = ( 365 | ); 366 | productName = "Apple Juice"; 367 | productReference = 8EBEB4421C466091001DBE91 /* Apple Juice.app */; 368 | productType = "com.apple.product-type.application"; 369 | }; 370 | /* End PBXNativeTarget section */ 371 | 372 | /* Begin PBXProject section */ 373 | 8EBEB43A1C466091001DBE91 /* Project object */ = { 374 | isa = PBXProject; 375 | attributes = { 376 | LastSwiftUpdateCheck = 0800; 377 | LastUpgradeCheck = 1200; 378 | ORGANIZATIONNAME = "Raphael Hanneken"; 379 | TargetAttributes = { 380 | 8E0D33AE1D3118CD002D3463 = { 381 | CreatedOnToolsVersion = 8.0; 382 | LastSwiftMigration = 1020; 383 | ProvisioningStyle = Automatic; 384 | SystemCapabilities = { 385 | com.apple.Sandbox = { 386 | enabled = 1; 387 | }; 388 | }; 389 | }; 390 | 8EBEB4411C466091001DBE91 = { 391 | CreatedOnToolsVersion = 7.2; 392 | LastSwiftMigration = 1020; 393 | SystemCapabilities = { 394 | com.apple.HardenedRuntime = { 395 | enabled = 0; 396 | }; 397 | com.apple.Sandbox = { 398 | enabled = 0; 399 | }; 400 | }; 401 | }; 402 | }; 403 | }; 404 | buildConfigurationList = 8EBEB43D1C466091001DBE91 /* Build configuration list for PBXProject "Apple Juice" */; 405 | compatibilityVersion = "Xcode 11.0"; 406 | developmentRegion = en; 407 | hasScannedForEncodings = 0; 408 | knownRegions = ( 409 | en, 410 | Base, 411 | de, 412 | fr, 413 | it, 414 | es, 415 | "pt-BR", 416 | ); 417 | mainGroup = 8EBEB4391C466091001DBE91; 418 | packageReferences = ( 419 | ); 420 | productRefGroup = 8EBEB4431C466091001DBE91 /* Products */; 421 | projectDirPath = ""; 422 | projectRoot = ""; 423 | targets = ( 424 | 8EBEB4411C466091001DBE91 /* Apple Juice */, 425 | 8E0D33AE1D3118CD002D3463 /* Apple Juice Today */, 426 | ); 427 | }; 428 | /* End PBXProject section */ 429 | 430 | /* Begin PBXResourcesBuildPhase section */ 431 | 8E0D33AD1D3118CD002D3463 /* Resources */ = { 432 | isa = PBXResourcesBuildPhase; 433 | buildActionMask = 2147483647; 434 | files = ( 435 | 8EA14D281D3D4728004D36C6 /* Localizable.strings in Resources */, 436 | 8E58CBA31D339BC900920567 /* ListRowViewController.xib in Resources */, 437 | 8E58CBA21D339BC200920567 /* TodayViewController.xib in Resources */, 438 | ); 439 | runOnlyForDeploymentPostprocessing = 0; 440 | }; 441 | 8EBEB4401C466091001DBE91 /* Resources */ = { 442 | isa = PBXResourcesBuildPhase; 443 | buildActionMask = 2147483647; 444 | files = ( 445 | 8EBEB4481C466091001DBE91 /* Assets.xcassets in Resources */, 446 | 8E995BBB1EABDB2F00CE8364 /* dsa_pub.pem in Resources */, 447 | C59FD5E525614C7000D50FC1 /* LaunchAtLogin.framework.dSYM in Resources */, 448 | 8EBF29E91C485493005BBAE0 /* Localizable.strings in Resources */, 449 | C57FE291255CA2EB00E4E2B4 /* Sparkle.framework.dSYM in Resources */, 450 | 8EBEB44B1C466091001DBE91 /* MainMenu.xib in Resources */, 451 | ); 452 | runOnlyForDeploymentPostprocessing = 0; 453 | }; 454 | /* End PBXResourcesBuildPhase section */ 455 | 456 | /* Begin PBXShellScriptBuildPhase section */ 457 | 8EE129751C46E993000D404F /* ShellScript */ = { 458 | isa = PBXShellScriptBuildPhase; 459 | buildActionMask = 2147483647; 460 | files = ( 461 | ); 462 | inputPaths = ( 463 | ); 464 | outputPaths = ( 465 | ); 466 | runOnlyForDeploymentPostprocessing = 0; 467 | shellPath = /bin/sh; 468 | shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"SwiftLint not found! Please install SwiftLint from https://github.com/realm/SwiftLint\"\nfi\n"; 469 | }; 470 | C57FE24E255C8B4100E4E2B4 /* ShellScript */ = { 471 | isa = PBXShellScriptBuildPhase; 472 | buildActionMask = 2147483647; 473 | files = ( 474 | ); 475 | inputFileListPaths = ( 476 | ); 477 | inputPaths = ( 478 | ); 479 | outputFileListPaths = ( 480 | ); 481 | outputPaths = ( 482 | ); 483 | runOnlyForDeploymentPostprocessing = 0; 484 | shellPath = /bin/sh; 485 | shellScript = "# Check for outdated dependencies\n/usr/local/bin/carthage outdated --xcode-warnings 2>/dev/null\n"; 486 | }; 487 | C59FD5DF25614B0200D50FC1 /* ShellScript */ = { 488 | isa = PBXShellScriptBuildPhase; 489 | buildActionMask = 2147483647; 490 | files = ( 491 | ); 492 | inputFileListPaths = ( 493 | ); 494 | inputPaths = ( 495 | ); 496 | outputFileListPaths = ( 497 | ); 498 | outputPaths = ( 499 | ); 500 | runOnlyForDeploymentPostprocessing = 0; 501 | shellPath = /bin/sh; 502 | shellScript = "# Copy Launch at Login Helper script\n\"${PROJECT_DIR}/Carthage/Build/Mac/LaunchAtLogin.framework/Resources/copy-helper.sh\"\n"; 503 | }; 504 | /* End PBXShellScriptBuildPhase section */ 505 | 506 | /* Begin PBXSourcesBuildPhase section */ 507 | 8E0D33AB1D3118CD002D3463 /* Sources */ = { 508 | isa = PBXSourcesBuildPhase; 509 | buildActionMask = 2147483647; 510 | files = ( 511 | C5696ABD2573DD27001316FC /* TimeRemaining.swift in Sources */, 512 | 8E995BB61EABDA6C00CE8364 /* BatteryError.swift in Sources */, 513 | 8E7373D91EACB58E00F10AD7 /* BatteryInfoTypeProtocol.swift in Sources */, 514 | 8EF30ED31D32B3170010BC49 /* BatteryService.swift in Sources */, 515 | 8E0D33B51D3118CD002D3463 /* TodayViewController.swift in Sources */, 516 | 8E0D33B71D3118CD002D3463 /* ListRowViewController.swift in Sources */, 517 | 8E7373E31EACBC5D00F10AD7 /* CycleCountInfoType.swift in Sources */, 518 | 8E995BB71EABDA7100CE8364 /* BatteryRegistryPropertyKey.swift in Sources */, 519 | 8E995BB51EABDA6800CE8364 /* BatteryState.swift in Sources */, 520 | 8E7373DB1EACB78500F10AD7 /* TimeRemainingInfoType.swift in Sources */, 521 | C5696AB62573C861001316FC /* Percentage.swift in Sources */, 522 | 8EAE0D211EB4EFBF00E3D594 /* SourceInfoType.swift in Sources */, 523 | C5696AC42573E180001316FC /* PowerSource.swift in Sources */, 524 | C5D7EB48209891D500012A81 /* BatteryHealthType.swift in Sources */, 525 | 8EAE0D231EB4EFD000E3D594 /* TemperatureInfoType.swift in Sources */, 526 | 8E7373DF1EACBB3300F10AD7 /* PowerUsageInfoType.swift in Sources */, 527 | 8E7373DD1EACB8D400F10AD7 /* PercentageInfoType.swift in Sources */, 528 | 8E7373E11EACBBDB00F10AD7 /* ChargeInfoType.swift in Sources */, 529 | ); 530 | runOnlyForDeploymentPostprocessing = 0; 531 | }; 532 | 8EBEB43E1C466091001DBE91 /* Sources */ = { 533 | isa = PBXSourcesBuildPhase; 534 | buildActionMask = 2147483647; 535 | files = ( 536 | 8E995BB01EABD8C800CE8364 /* BatteryState.swift in Sources */, 537 | 8EE129741C46D3C7000D404F /* StatusNotification.swift in Sources */, 538 | 8E995BB11EABD8C800CE8364 /* NotificationKey.swift in Sources */, 539 | 8EE129721C46CA1B000D404F /* UserPreferences.swift in Sources */, 540 | C52233AF25575A0A00526725 /* NSImageExtension.swift in Sources */, 541 | 8E995BB41EABD8C800CE8364 /* StatusBarItem.swift in Sources */, 542 | 8E995BAF1EABD8C800CE8364 /* BatteryError.swift in Sources */, 543 | C5819EFD2580256400F964E3 /* MenuInfoViewModel.swift in Sources */, 544 | C5696ABA2573DAA1001316FC /* TimeRemaining.swift in Sources */, 545 | 8EE1296A1C4663F7000D404F /* ApplicationController.swift in Sources */, 546 | 8E995BB31EABD8C800CE8364 /* BatteryRegistryPropertyKey.swift in Sources */, 547 | 8E31BF9F1D42651F00055A5B /* BatteryImageCache.swift in Sources */, 548 | C5EEDA6920276240004DE64C /* PreferenceKey.swift in Sources */, 549 | C5819EF4257BE32700F964E3 /* ApplicationMenuDelegate.swift in Sources */, 550 | C5819EF8257BE38E00F964E3 /* MenuInfoView.swift in Sources */, 551 | 8EE1296E1C469326000D404F /* StatusBarIcon.swift in Sources */, 552 | C5696AC12573E0F9001316FC /* PowerSource.swift in Sources */, 553 | 8EBEB4461C466091001DBE91 /* AppDelegate.swift in Sources */, 554 | 8EE129701C4695B5000D404F /* BatteryService.swift in Sources */, 555 | C5696AB42573C7A1001316FC /* Percentage.swift in Sources */, 556 | ); 557 | runOnlyForDeploymentPostprocessing = 0; 558 | }; 559 | /* End PBXSourcesBuildPhase section */ 560 | 561 | /* Begin PBXTargetDependency section */ 562 | 8E0D33C31D3118CD002D3463 /* PBXTargetDependency */ = { 563 | isa = PBXTargetDependency; 564 | target = 8E0D33AE1D3118CD002D3463 /* Apple Juice Today */; 565 | targetProxy = 8E0D33C21D3118CD002D3463 /* PBXContainerItemProxy */; 566 | }; 567 | /* End PBXTargetDependency section */ 568 | 569 | /* Begin PBXVariantGroup section */ 570 | 8EBEB4491C466091001DBE91 /* MainMenu.xib */ = { 571 | isa = PBXVariantGroup; 572 | children = ( 573 | 8EBEB44A1C466091001DBE91 /* Base */, 574 | C58CD0B6222179A300D286C0 /* en */, 575 | C58CD0B7222179D200D286C0 /* de */, 576 | C58CD0B822217BAB00D286C0 /* fr */, 577 | C58CD0B92221D9A800D286C0 /* it */, 578 | C58CD0BA2221DBB500D286C0 /* es */, 579 | C58CD0BB2221E6B900D286C0 /* pt-BR */, 580 | ); 581 | name = MainMenu.xib; 582 | sourceTree = ""; 583 | }; 584 | 8EBF29EC1C485493005BBAE0 /* Localizable.strings */ = { 585 | isa = PBXVariantGroup; 586 | children = ( 587 | 8EBF29EB1C485493005BBAE0 /* en */, 588 | 8EBF29ED1C48552D005BBAE0 /* Base */, 589 | 8EBF29F31C485741005BBAE0 /* de */, 590 | 8EBF29FD1C486640005BBAE0 /* fr */, 591 | 8E954FA81C4EE528006D0F34 /* it */, 592 | 8E5BC0241C50D13D0077228F /* es */, 593 | 5EF9F80621FB7A3300748374 /* pt-BR */, 594 | ); 595 | name = Localizable.strings; 596 | sourceTree = ""; 597 | }; 598 | /* End PBXVariantGroup section */ 599 | 600 | /* Begin XCBuildConfiguration section */ 601 | 8E0D33C61D3118CD002D3463 /* Debug */ = { 602 | isa = XCBuildConfiguration; 603 | buildSettings = { 604 | CLANG_ANALYZER_NONNULL = YES; 605 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 606 | CODE_SIGN_ENTITLEMENTS = "AppleJuiceToday/Apple Juice Today.entitlements"; 607 | CODE_SIGN_IDENTITY = "-"; 608 | COMBINE_HIDPI_IMAGES = YES; 609 | CURRENT_PROJECT_VERSION = 420; 610 | INFOPLIST_FILE = AppleJuiceToday/Info.plist; 611 | LD_RUNPATH_SEARCH_PATHS = ( 612 | "$(inherited)", 613 | "@executable_path/../Frameworks", 614 | "@executable_path/../../../../Frameworks", 615 | ); 616 | MACOSX_DEPLOYMENT_TARGET = 10.12; 617 | MARKETING_VERSION = 2020.12.0; 618 | PRODUCT_BUNDLE_IDENTIFIER = "com.raphaelhanneken.applejuice.Apple-Juice-Today"; 619 | PRODUCT_NAME = "$(TARGET_NAME)"; 620 | SKIP_INSTALL = YES; 621 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 622 | SWIFT_VERSION = 5.0; 623 | }; 624 | name = Debug; 625 | }; 626 | 8E0D33C71D3118CD002D3463 /* Release */ = { 627 | isa = XCBuildConfiguration; 628 | buildSettings = { 629 | CLANG_ANALYZER_NONNULL = YES; 630 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 631 | CODE_SIGN_ENTITLEMENTS = "AppleJuiceToday/Apple Juice Today.entitlements"; 632 | CODE_SIGN_IDENTITY = "-"; 633 | COMBINE_HIDPI_IMAGES = YES; 634 | CURRENT_PROJECT_VERSION = 420; 635 | INFOPLIST_FILE = AppleJuiceToday/Info.plist; 636 | LD_RUNPATH_SEARCH_PATHS = ( 637 | "$(inherited)", 638 | "@executable_path/../Frameworks", 639 | "@executable_path/../../../../Frameworks", 640 | ); 641 | MACOSX_DEPLOYMENT_TARGET = 10.12; 642 | MARKETING_VERSION = 2020.12.0; 643 | PRODUCT_BUNDLE_IDENTIFIER = "com.raphaelhanneken.applejuice.Apple-Juice-Today"; 644 | PRODUCT_NAME = "$(TARGET_NAME)"; 645 | SKIP_INSTALL = YES; 646 | SWIFT_COMPILATION_MODE = wholemodule; 647 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 648 | SWIFT_VERSION = 5.0; 649 | }; 650 | name = Release; 651 | }; 652 | 8EBEB44D1C466091001DBE91 /* Debug */ = { 653 | isa = XCBuildConfiguration; 654 | buildSettings = { 655 | ALWAYS_SEARCH_USER_PATHS = NO; 656 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 657 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 658 | CLANG_CXX_LIBRARY = "libc++"; 659 | CLANG_ENABLE_MODULES = YES; 660 | CLANG_ENABLE_OBJC_ARC = YES; 661 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 662 | CLANG_WARN_BOOL_CONVERSION = YES; 663 | CLANG_WARN_COMMA = YES; 664 | CLANG_WARN_CONSTANT_CONVERSION = YES; 665 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 666 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 667 | CLANG_WARN_EMPTY_BODY = YES; 668 | CLANG_WARN_ENUM_CONVERSION = YES; 669 | CLANG_WARN_INFINITE_RECURSION = YES; 670 | CLANG_WARN_INT_CONVERSION = YES; 671 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 672 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 673 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 674 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 675 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 676 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 677 | CLANG_WARN_STRICT_PROTOTYPES = YES; 678 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 679 | CLANG_WARN_UNREACHABLE_CODE = YES; 680 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 681 | CODE_SIGN_IDENTITY = "-"; 682 | COPY_PHASE_STRIP = NO; 683 | DEBUG_INFORMATION_FORMAT = dwarf; 684 | ENABLE_STRICT_OBJC_MSGSEND = YES; 685 | ENABLE_TESTABILITY = YES; 686 | GCC_C_LANGUAGE_STANDARD = gnu99; 687 | GCC_DYNAMIC_NO_PIC = NO; 688 | GCC_NO_COMMON_BLOCKS = YES; 689 | GCC_OPTIMIZATION_LEVEL = 0; 690 | GCC_PREPROCESSOR_DEFINITIONS = ( 691 | "DEBUG=1", 692 | "$(inherited)", 693 | ); 694 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 695 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 696 | GCC_WARN_UNDECLARED_SELECTOR = YES; 697 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 698 | GCC_WARN_UNUSED_FUNCTION = YES; 699 | GCC_WARN_UNUSED_VARIABLE = YES; 700 | MACOSX_DEPLOYMENT_TARGET = 10.12; 701 | MTL_ENABLE_DEBUG_INFO = YES; 702 | ONLY_ACTIVE_ARCH = YES; 703 | SDKROOT = macosx; 704 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 705 | }; 706 | name = Debug; 707 | }; 708 | 8EBEB44E1C466091001DBE91 /* Release */ = { 709 | isa = XCBuildConfiguration; 710 | buildSettings = { 711 | ALWAYS_SEARCH_USER_PATHS = NO; 712 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 713 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 714 | CLANG_CXX_LIBRARY = "libc++"; 715 | CLANG_ENABLE_MODULES = YES; 716 | CLANG_ENABLE_OBJC_ARC = YES; 717 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 718 | CLANG_WARN_BOOL_CONVERSION = YES; 719 | CLANG_WARN_COMMA = YES; 720 | CLANG_WARN_CONSTANT_CONVERSION = YES; 721 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 722 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 723 | CLANG_WARN_EMPTY_BODY = YES; 724 | CLANG_WARN_ENUM_CONVERSION = YES; 725 | CLANG_WARN_INFINITE_RECURSION = YES; 726 | CLANG_WARN_INT_CONVERSION = YES; 727 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 728 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 729 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 730 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 731 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 732 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 733 | CLANG_WARN_STRICT_PROTOTYPES = YES; 734 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 735 | CLANG_WARN_UNREACHABLE_CODE = YES; 736 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 737 | CODE_SIGN_IDENTITY = "-"; 738 | COPY_PHASE_STRIP = NO; 739 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 740 | ENABLE_NS_ASSERTIONS = NO; 741 | ENABLE_STRICT_OBJC_MSGSEND = YES; 742 | GCC_C_LANGUAGE_STANDARD = gnu99; 743 | GCC_NO_COMMON_BLOCKS = YES; 744 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 745 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 746 | GCC_WARN_UNDECLARED_SELECTOR = YES; 747 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 748 | GCC_WARN_UNUSED_FUNCTION = YES; 749 | GCC_WARN_UNUSED_VARIABLE = YES; 750 | MACOSX_DEPLOYMENT_TARGET = 10.12; 751 | MTL_ENABLE_DEBUG_INFO = NO; 752 | SDKROOT = macosx; 753 | SWIFT_COMPILATION_MODE = wholemodule; 754 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 755 | }; 756 | name = Release; 757 | }; 758 | 8EBEB4501C466091001DBE91 /* Debug */ = { 759 | isa = XCBuildConfiguration; 760 | buildSettings = { 761 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 762 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 763 | CODE_SIGN_IDENTITY = "-"; 764 | COMBINE_HIDPI_IMAGES = YES; 765 | CURRENT_PROJECT_VERSION = 420; 766 | FRAMEWORK_SEARCH_PATHS = ( 767 | "$(inherited)", 768 | "$(PROJECT_DIR)", 769 | "$(PROJECT_DIR)/Carthage/Build/Mac", 770 | ); 771 | INFOPLIST_FILE = AppleJuice/Info.plist; 772 | LD_RUNPATH_SEARCH_PATHS = ( 773 | "$(inherited)", 774 | "@executable_path/../Frameworks", 775 | ); 776 | MACOSX_DEPLOYMENT_TARGET = 10.12; 777 | MARKETING_VERSION = 2020.12.0; 778 | PRODUCT_BUNDLE_IDENTIFIER = com.raphaelhanneken.applejuice; 779 | PRODUCT_NAME = "$(TARGET_NAME)"; 780 | SWIFT_VERSION = 5.0; 781 | }; 782 | name = Debug; 783 | }; 784 | 8EBEB4511C466091001DBE91 /* Release */ = { 785 | isa = XCBuildConfiguration; 786 | buildSettings = { 787 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 788 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 789 | CODE_SIGN_IDENTITY = "-"; 790 | COMBINE_HIDPI_IMAGES = YES; 791 | CURRENT_PROJECT_VERSION = 420; 792 | FRAMEWORK_SEARCH_PATHS = ( 793 | "$(inherited)", 794 | "$(PROJECT_DIR)", 795 | "$(PROJECT_DIR)/Carthage/Build/Mac", 796 | ); 797 | INFOPLIST_FILE = AppleJuice/Info.plist; 798 | LD_RUNPATH_SEARCH_PATHS = ( 799 | "$(inherited)", 800 | "@executable_path/../Frameworks", 801 | ); 802 | MACOSX_DEPLOYMENT_TARGET = 10.12; 803 | MARKETING_VERSION = 2020.12.0; 804 | PRODUCT_BUNDLE_IDENTIFIER = com.raphaelhanneken.applejuice; 805 | PRODUCT_NAME = "$(TARGET_NAME)"; 806 | SWIFT_VERSION = 5.0; 807 | }; 808 | name = Release; 809 | }; 810 | /* End XCBuildConfiguration section */ 811 | 812 | /* Begin XCConfigurationList section */ 813 | 8E0D33C51D3118CD002D3463 /* Build configuration list for PBXNativeTarget "Apple Juice Today" */ = { 814 | isa = XCConfigurationList; 815 | buildConfigurations = ( 816 | 8E0D33C61D3118CD002D3463 /* Debug */, 817 | 8E0D33C71D3118CD002D3463 /* Release */, 818 | ); 819 | defaultConfigurationIsVisible = 0; 820 | defaultConfigurationName = Release; 821 | }; 822 | 8EBEB43D1C466091001DBE91 /* Build configuration list for PBXProject "Apple Juice" */ = { 823 | isa = XCConfigurationList; 824 | buildConfigurations = ( 825 | 8EBEB44D1C466091001DBE91 /* Debug */, 826 | 8EBEB44E1C466091001DBE91 /* Release */, 827 | ); 828 | defaultConfigurationIsVisible = 0; 829 | defaultConfigurationName = Release; 830 | }; 831 | 8EBEB44F1C466091001DBE91 /* Build configuration list for PBXNativeTarget "Apple Juice" */ = { 832 | isa = XCConfigurationList; 833 | buildConfigurations = ( 834 | 8EBEB4501C466091001DBE91 /* Debug */, 835 | 8EBEB4511C466091001DBE91 /* Release */, 836 | ); 837 | defaultConfigurationIsVisible = 0; 838 | defaultConfigurationName = Release; 839 | }; 840 | /* End XCConfigurationList section */ 841 | }; 842 | rootObject = 8EBEB43A1C466091001DBE91 /* Project object */; 843 | } 844 | -------------------------------------------------------------------------------- /AppleJuice/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Apple Juice 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Cocoa 8 | 9 | @NSApplicationMain 10 | final class AppDelegate: NSObject, NSApplicationDelegate { 11 | func applicationDidFinishLaunching(_: Notification) { 12 | UserPreferences.registerDefaults() 13 | } 14 | 15 | func applicationWillTerminate(_: Notification) { 16 | // Insert code here to tear down your application 17 | } 18 | 19 | // Post messages to the user notification center. 20 | func userNotificationCenter(_: NSUserNotificationCenter, 21 | shouldPresentNotification _: NSUserNotification) -> Bool 22 | { 23 | true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /AppleJuice/Apple Juice.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /AppleJuice/ApplicationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ApplicationController.swift 3 | // Apple Juice 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Cocoa 8 | import LaunchAtLogin 9 | 10 | final class ApplicationController: NSObject { 11 | 12 | private var statusItem: StatusBarItem? 13 | private var battery: BatteryService! 14 | 15 | @IBOutlet weak var applicationMenu: NSMenu! 16 | 17 | @objc dynamic var launchAtLogin = LaunchAtLogin.kvo 18 | 19 | override init() { 20 | super.init() 21 | 22 | do { 23 | battery = try BatteryService() 24 | statusItem = StatusBarItem(forBattery: battery, 25 | withAction: #selector(ApplicationController.displayAppMenu(_:)), 26 | forTarget: self) 27 | statusItem?.update(batteryInfo: battery) 28 | 29 | // Register the ApplicationController as observer for power source and user preference changes 30 | UserDefaults 31 | .standard 32 | .addObserver(self, forKeyPath: PreferenceKey.showTime.rawValue, options: .new, context: nil) 33 | 34 | UserDefaults 35 | .standard 36 | .addObserver(self, forKeyPath: PreferenceKey.hideMenubarInfo.rawValue, options: .new, context: nil) 37 | 38 | UserDefaults 39 | .standard 40 | .addObserver(self, forKeyPath: PreferenceKey.hideBatteryIcon.rawValue, options: .new, context: nil) 41 | 42 | NotificationCenter.default 43 | .addObserver(self, 44 | selector: #selector(ApplicationController.powerSourceChanged(_:)), 45 | name: NSNotification.Name(rawValue: powerSourceChangedNotification), 46 | object: nil) 47 | } catch { 48 | statusItem = StatusBarItem( 49 | forError: error as? BatteryError, 50 | withAction: #selector(ApplicationController.displayAppMenu(_:)), 51 | forTarget: self) 52 | } 53 | } 54 | 55 | // MARK: Public 56 | 57 | /// This message is sent to the receiver, when a powerSourceChanged message was posted. The receiver 58 | /// must be registered as an observer for powerSourceChangedNotification's. 59 | /// 60 | /// - parameter sender: The object that posted powerSourceChanged message. 61 | @objc public func powerSourceChanged(_: AnyObject) { 62 | statusItem?.update(batteryInfo: battery) 63 | 64 | if let notification = StatusNotification(forState: battery.state) { 65 | notification.postNotification() 66 | } 67 | } 68 | 69 | /// Displays the application menu when the user clicks the menu bar item. 70 | /// 71 | /// - parameter sender: The object that sent the message. 72 | @objc public func displayAppMenu(_: AnyObject) { 73 | statusItem?.popUpMenu(applicationMenu) 74 | } 75 | 76 | // MARK: Internal 77 | 78 | /// This message is sent to the receiver when the value at the specified key path relative to the given object 79 | /// has changed. The receiver must be registered as an observer for the specified keyPath and object. 80 | /// 81 | /// - parameter keyPath: The key path, relative to object, to the value that has changed. 82 | /// - parameter object: The source object of the key path. 83 | /// - parameter change: A dictionary that describes the changes that have been made to 84 | /// the value of the property at the key path keyPath relative to object. 85 | /// Entries are described in Change Dictionary Keys. 86 | /// - parameter context: The value that was provided when the receiver was registered to receive key-value 87 | /// observation notifications. 88 | override func observeValue(forKeyPath _: String?, 89 | of _: Any?, 90 | change _: [NSKeyValueChangeKey: Any]?, 91 | context _: UnsafeMutableRawPointer?) 92 | { 93 | statusItem?.update(batteryInfo: battery) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /AppleJuice/ApplicationMenuDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuController.swift 3 | // Apple Juice 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Cocoa 8 | import SwiftUI 9 | 10 | class ApplicationMenuDelegate: NSObject, NSMenuDelegate { 11 | // MARK: Public 12 | 13 | public func menuWillOpen(_ menu: NSMenu) { 14 | updateBatteryInfoItem(menu.item(at: 0)) 15 | } 16 | 17 | override func awakeFromNib() { 18 | do { 19 | batteryService = try BatteryService() 20 | } catch { 21 | NSLog(error.localizedDescription) 22 | } 23 | } 24 | 25 | // MARK: Private 26 | 27 | private var batteryService: BatteryService! 28 | 29 | private func updateBatteryInfoItem(_ batteryInfoItem: NSMenuItem?) { 30 | guard let item = batteryInfoItem else { 31 | return 32 | } 33 | 34 | item.attributedTitle = getAttributedMenuItemTitle() 35 | if #available(OSX 11.0, *) { 36 | let hostingView = NSHostingView(rootView: MenuInfoView()) 37 | hostingView.frame = NSRect(x: 0, 38 | y: 0, 39 | width: hostingView.intrinsicContentSize.width, 40 | height: hostingView.intrinsicContentSize.height) 41 | 42 | item.view = hostingView 43 | } else { 44 | item.attributedTitle = getAttributedMenuItemTitle() 45 | } 46 | } 47 | 48 | private func getAttributedMenuItemTitle() -> NSAttributedString { 49 | guard let capacity = batteryService?.capacity, 50 | let charge = batteryService?.charge, 51 | let amperage = batteryService?.amperage, 52 | let percentage = batteryService?.percentage, 53 | let timeRemaining = batteryService?.timeRemaining, 54 | let powerSource = batteryService?.powerSource 55 | else { 56 | return NSAttributedString(string: NSLocalizedString("Unknown", comment: "Information missing")) 57 | } 58 | 59 | let paragraphStyle = NSMutableParagraphStyle() 60 | paragraphStyle.paragraphSpacing = 3.0 61 | 62 | let remaining = UserPreferences.showTime ? percentage.formatted : timeRemaining.formatted 63 | 64 | let powerSourceLabel = NSMutableAttributedString( 65 | string: powerSource.localizedDescription, 66 | attributes: [.font: NSFont.menuFont(ofSize: 13.0), .paragraphStyle: paragraphStyle]) 67 | 68 | let details = NSAttributedString( 69 | string: "\n\(remaining) \(charge) / \(capacity) mAh (\(amperage) mA)", 70 | attributes: [.font: NSFont.menuFont(ofSize: 13.0)]) 71 | 72 | powerSourceLabel.append(details) 73 | 74 | return powerSourceLabel 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon-16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "icon-32.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "icon-32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "icon-64.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "icon-128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "icon-256.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "icon-256.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "icon-512.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "icon-512.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "icon-1024.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/AppIcon.appiconset/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelhanneken/apple-juice/bb0533459846225e654ce5bb9393602f644d9628/AppleJuice/Assets.xcassets/AppIcon.appiconset/icon-1024.png -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/AppIcon.appiconset/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelhanneken/apple-juice/bb0533459846225e654ce5bb9393602f644d9628/AppleJuice/Assets.xcassets/AppIcon.appiconset/icon-128.png -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/AppIcon.appiconset/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelhanneken/apple-juice/bb0533459846225e654ce5bb9393602f644d9628/AppleJuice/Assets.xcassets/AppIcon.appiconset/icon-16.png -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/AppIcon.appiconset/icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelhanneken/apple-juice/bb0533459846225e654ce5bb9393602f644d9628/AppleJuice/Assets.xcassets/AppIcon.appiconset/icon-256.png -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/AppIcon.appiconset/icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelhanneken/apple-juice/bb0533459846225e654ce5bb9393602f644d9628/AppleJuice/Assets.xcassets/AppIcon.appiconset/icon-32.png -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/AppIcon.appiconset/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelhanneken/apple-juice/bb0533459846225e654ce5bb9393602f644d9628/AppleJuice/Assets.xcassets/AppIcon.appiconset/icon-512.png -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/AppIcon.appiconset/icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelhanneken/apple-juice/bb0533459846225e654ce5bb9393602f644d9628/AppleJuice/Assets.xcassets/AppIcon.appiconset/icon-64.png -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/BatteryFill.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Fill.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/BatteryFill.imageset/Fill.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelhanneken/apple-juice/bb0533459846225e654ce5bb9393602f644d9628/AppleJuice/Assets.xcassets/BatteryFill.imageset/Fill.pdf -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/BatteryFillCapLeft.imageset/CapLeft.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelhanneken/apple-juice/bb0533459846225e654ce5bb9393602f644d9628/AppleJuice/Assets.xcassets/BatteryFillCapLeft.imageset/CapLeft.pdf -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/BatteryFillCapLeft.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "CapLeft.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/BatteryFillCapRight.imageset/CapRight.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelhanneken/apple-juice/bb0533459846225e654ce5bb9393602f644d9628/AppleJuice/Assets.xcassets/BatteryFillCapRight.imageset/CapRight.pdf -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/BatteryFillCapRight.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "CapRight.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/BatteryOutline.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Outline.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/BatteryOutline.imageset/Outline.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelhanneken/apple-juice/bb0533459846225e654ce5bb9393602f644d9628/AppleJuice/Assets.xcassets/BatteryOutline.imageset/Outline.pdf -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/ChargedAndPlugged.imageset/Charged and Plugged.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelhanneken/apple-juice/bb0533459846225e654ce5bb9393602f644d9628/AppleJuice/Assets.xcassets/ChargedAndPlugged.imageset/Charged and Plugged.pdf -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/ChargedAndPlugged.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Charged and Plugged.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/Charging.imageset/Charging.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelhanneken/apple-juice/bb0533459846225e654ce5bb9393602f644d9628/AppleJuice/Assets.xcassets/Charging.imageset/Charging.pdf -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/Charging.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Charging.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/DeadCropped.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Dead Cropped.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/DeadCropped.imageset/Dead Cropped.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelhanneken/apple-juice/bb0533459846225e654ce5bb9393602f644d9628/AppleJuice/Assets.xcassets/DeadCropped.imageset/Dead Cropped.pdf -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/LowBattery.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Low Battery.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/LowBattery.imageset/Low Battery.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelhanneken/apple-juice/bb0533459846225e654ce5bb9393602f644d9628/AppleJuice/Assets.xcassets/LowBattery.imageset/Low Battery.pdf -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/None.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "None.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /AppleJuice/Assets.xcassets/None.imageset/None.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelhanneken/apple-juice/bb0533459846225e654ce5bb9393602f644d9628/AppleJuice/Assets.xcassets/None.imageset/None.pdf -------------------------------------------------------------------------------- /AppleJuice/Base.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | // Localizable.strings 2 | // Apple Juice 3 | // 4 | // Base 5 | 6 | 7 | // Menu Items 8 | 9 | "Charged" = "Charged"; 10 | "Unknown" = "Unknown"; 11 | "Battery" = "Battery"; 12 | "Power Adapter" = "Power Adapter"; 13 | "Calculating" = "Calculating…"; 14 | 15 | 16 | // Notifications 17 | 18 | "Charged Notification Title" = "Charged"; 19 | "Charged Notification Message" = "Your Battery is now fully charged."; 20 | "Low Battery Notification Title" = "%@ remaining"; 21 | "Low Battery Notification Message" = "Your Mac will sleep soon unless plugged into a power outlet."; 22 | 23 | 24 | // Properties 25 | 26 | "Time Remaining" = "Time Remaining:"; 27 | "Percentage" = "Percentage:"; 28 | "Power Usage" = "Power Usage:"; 29 | "Charge" = "Charge:"; 30 | "Cycle Count" = "Cycle Count:"; 31 | "Temperature" = "Temperature:"; 32 | "Power Source" = "Power Source:"; 33 | "Health" = "Condition:"; 34 | 35 | 36 | // Units 37 | 38 | "Watts" = "Watts"; 39 | -------------------------------------------------------------------------------- /AppleJuice/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | NSNegateBoolean 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | NSNegateBoolean 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /AppleJuice/BatteryError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BatteryError.swift 3 | // Apple Juice 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | /// Exceptions for the Battery class. 8 | /// 9 | /// - connectionAlreadyOpen: Get's thrown in case the connection to the battery's IOService 10 | /// is already open. Accepts an error description of type String. 11 | /// - serviceNotFound: Get's thrown in case the supplied IOService wasn't found. 12 | /// Accepts an error description of type String. 13 | enum BatteryError: Error { 14 | case connectionAlreadyOpen(String) 15 | case serviceNotFound(String) 16 | } 17 | -------------------------------------------------------------------------------- /AppleJuice/BatteryImageCache.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BatteryImageCache.swift 3 | // Apple Juice 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Cocoa 8 | 9 | struct BatteryImageCache { 10 | // MARK: Lifecycle 11 | 12 | /// Cache a battery icon alongside it's corresponding BatteryState. 13 | /// 14 | /// - parameter status: The BatteryState to cache the battery icon for. 15 | /// - parameter img: The battery icon to cache. 16 | init(forStatus status: BatteryState, withImage img: NSImage?) { 17 | batteryStatus = status 18 | image = img 19 | } 20 | 21 | // MARK: Internal 22 | 23 | /// The cached battery icon. 24 | let image: NSImage? 25 | /// The BatteryState associated with the cached battery icon. 26 | let batteryStatus: BatteryState 27 | } 28 | -------------------------------------------------------------------------------- /AppleJuice/BatteryRegistryPropertyKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SmartBatteryKeys.swift 3 | // Apple Juice 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | /// Keys to lookup information from the IO Service dictionary 'ioreg -brc AppleSmartBattery'. 8 | /// 9 | /// - isPlugged: Is the battery connected to an external power source 10 | /// - isCharging: The battery's charging state 11 | /// - currentCharge: An estimate about the current charge in mAh 12 | /// - maxCapacity: The maximun charging capacity in mAh 13 | /// - fullyCharged: Is the battery's max charging capacity reached 14 | /// - cycleCount: The number of charging cycles 15 | /// - temperature: The temperature in degrees celsius 16 | /// - voltage: The current voltage 17 | /// - amperage: The current amperage 18 | /// - timeRemaining: An estimate about the remaining time until the battery is fully charged or depleted 19 | /// - service: The service name 20 | enum BatteryRegistryPropertyKey: String { 21 | case isPlugged = "ExternalConnected" 22 | case isCharging = "IsCharging" 23 | case currentCharge = "CurrentCapacity" 24 | case maxCapacity = "MaxCapacity" 25 | case fullyCharged = "FullyCharged" 26 | case cycleCount = "CycleCount" 27 | case temperature = "Temperature" 28 | case voltage = "Voltage" 29 | case amperage = "Amperage" 30 | case timeRemaining = "TimeRemaining" 31 | case service = "AppleSmartBattery" 32 | case health = "BatteryHealth" 33 | case percentage = "Current Capacity" 34 | } 35 | -------------------------------------------------------------------------------- /AppleJuice/BatteryService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Battery.swift 3 | // Apple Juice 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Foundation 8 | import IOKit.ps 9 | 10 | /// Notification name for the power source changed callback. 11 | let powerSourceChangedNotification = "com.raphaelhanneken.apple-juice.powersourcechanged" 12 | 13 | /// Post a notification every time the power source changes. 14 | private let powerSourceCallback: IOPowerSourceCallbackType = { _ in 15 | NotificationCenter.default.post(name: Notification.Name(rawValue: powerSourceChangedNotification), 16 | object: nil) 17 | } 18 | 19 | /// Accesses the battery's IO service. 20 | final class BatteryService { 21 | // MARK: Lifecycle 22 | 23 | /// Initializes a new Battery object. 24 | init() throws { 25 | try openServiceConnection() 26 | CFRunLoopAddSource(CFRunLoopGetCurrent(), 27 | IOPSNotificationCreateRunLoopSource(powerSourceCallback, nil).takeRetainedValue(), 28 | CFRunLoopMode.defaultMode) 29 | } 30 | 31 | // MARK: Internal 32 | 33 | /// The current status of the battery, e.g. charging. 34 | var state: BatteryState? { 35 | guard let charging = isCharging, 36 | let plugged = isPlugged, 37 | let charged = isCharged 38 | else { 39 | return nil 40 | } 41 | 42 | if charged, plugged { 43 | return .chargedAndPlugged 44 | } 45 | if charging { 46 | return .charging(percentage: percentage) 47 | } 48 | 49 | return .discharging(percentage: percentage) 50 | } 51 | 52 | /// The estimated time remaining until the battery is empty or fully charged. 53 | var timeRemaining: TimeRemaining { 54 | let time = IOPSGetTimeRemainingEstimate() 55 | switch time { 56 | case kIOPSTimeRemainingUnknown: 57 | return TimeRemaining(minutes: nil, state: state) 58 | case kIOPSTimeRemainingUnlimited: 59 | // The battery is connected to a power outlet, get the remaining time 60 | // until the battery is fully charged. 61 | return TimeRemaining(minutes: getRegistryProperty(forKey: .timeRemaining) as? Int, state: state) 62 | default: 63 | return TimeRemaining(minutes: Int(time / 60), state: state) 64 | } 65 | } 66 | 67 | /// The current percentage, based on the current charge and the maximum capacity. 68 | var percentage: Percentage { 69 | Percentage(numeric: getPowerSourceProperty(forKey: .percentage) as? Int) 70 | } 71 | 72 | /// The current charge in mAh. 73 | var charge: Int? { 74 | getRegistryProperty(forKey: .currentCharge) as? Int 75 | } 76 | 77 | /// The maximum capacity in mAh. 78 | var capacity: Int? { 79 | getRegistryProperty(forKey: .maxCapacity) as? Int 80 | } 81 | 82 | /// The source from which the Mac currently draws its power. 83 | var powerSource: PowerSource { 84 | guard let plugged = isPlugged else { 85 | return .unknown 86 | } 87 | 88 | return plugged ? .powerAdapter : .battery 89 | } 90 | 91 | /// Checks whether the battery is charging and connected to a power outlet. 92 | var isCharging: Bool? { 93 | getRegistryProperty(forKey: .isCharging) as? Bool 94 | } 95 | 96 | /// Checks whether the battery is fully charged. 97 | var isCharged: Bool? { 98 | getRegistryProperty(forKey: .fullyCharged) as? Bool 99 | } 100 | 101 | /// Checks whether the battery is plugged into an unlimited power supply. 102 | var isPlugged: Bool? { 103 | getRegistryProperty(forKey: .isPlugged) as? Bool 104 | } 105 | 106 | /// Calculates the current power usage in Watts. 107 | var powerUsage: Double? { 108 | guard let voltage = getRegistryProperty(forKey: .voltage) as? Double, 109 | let amperage = getRegistryProperty(forKey: .amperage) as? Double 110 | else { 111 | return nil 112 | } 113 | return round((voltage * amperage) / 1_000_000) 114 | } 115 | 116 | /// Current flowing into or out of the battery. 117 | var amperage: Int? { 118 | return getRegistryProperty(forKey: .amperage) as? Int 119 | } 120 | 121 | /// The number of charging cycles. 122 | var cycleCount: Int? { 123 | getRegistryProperty(forKey: .cycleCount) as? Int 124 | } 125 | 126 | /// The battery's current temperature. 127 | var temperature: Double? { 128 | guard let temp = getRegistryProperty(forKey: .temperature) as? Double else { 129 | return nil 130 | } 131 | return (temp / 100) 132 | } 133 | 134 | /// The batteries' health status 135 | var health: String? { 136 | getPowerSourceProperty(forKey: .health) as? String 137 | } 138 | 139 | // MARK: Private 140 | 141 | /// Closed state value for the service connection object. 142 | private static let connectionClosed: UInt32 = 0 143 | 144 | /// An IOService object that matches battery's IO service dictionary. 145 | private var service: io_object_t = BatteryService.connectionClosed 146 | 147 | /// Open a connection to the battery's IOService object. 148 | /// 149 | /// - throws: A BatteryError if something went wrong. 150 | private func openServiceConnection() throws { 151 | service = IOServiceGetMatchingService(kIOMasterPortDefault, 152 | IOServiceNameMatching(BatteryRegistryPropertyKey.service.rawValue)) 153 | 154 | if service == BatteryService.connectionClosed { 155 | throw BatteryError.serviceNotFound("Opening (\(BatteryRegistryPropertyKey.service.rawValue)) service failed") 156 | } 157 | } 158 | 159 | /// Close the connection the to the battery's IOService object. 160 | /// 161 | /// - returns: True, when the IOService connection was successfully closed. 162 | private func closeServiceConnection() -> Bool { 163 | if kIOReturnSuccess == IOObjectRelease(service) { 164 | service = BatteryService.connectionClosed 165 | } 166 | return (service == BatteryService.connectionClosed) 167 | } 168 | 169 | /// Get a registry entry for the supplied property key. 170 | /// 171 | /// - parameter key: A BatteryRegistryPropertyKey to get the corresponding registry entry. 172 | /// - returns: The registry entry for the provided BatteryRegistryPropertyKey. 173 | private func getRegistryProperty(forKey key: BatteryRegistryPropertyKey) -> Any? { 174 | IORegistryEntryCreateCFProperty(service, key.rawValue as CFString?, nil, 0).takeRetainedValue() 175 | } 176 | 177 | /// Get a power source entry for the supplied property key. 178 | /// 179 | /// - parameter key: A BatteryRegistryPropertyKey to get the corresponding power source entry. 180 | /// - returns: The power sorce entry for the given property. 181 | private func getPowerSourceProperty(forKey key: BatteryRegistryPropertyKey) -> Any? { 182 | let psInfo = IOPSCopyPowerSourcesInfo().takeRetainedValue() 183 | let psList = IOPSCopyPowerSourcesList(psInfo).takeRetainedValue() as? [CFDictionary] 184 | guard let powerSources = psList else { 185 | return nil 186 | } 187 | let powerSource = powerSources[0] as NSDictionary 188 | return powerSource[key.rawValue] 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /AppleJuice/BatteryState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BatteryState.swift 3 | // Apple Juice 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Foundation 8 | 9 | /// Define the precision, with wich the icon can display the current charging level 10 | public let drawingPrecision = 5.4 11 | 12 | /// Defines the state the battery is currently in. 13 | /// 14 | /// - chargedAndPlugged: The battery is plugged into a power supply and charged. 15 | /// - charging: The battery is plugged into a power supply and 16 | /// charging. Takes the current percentage as argument. 17 | /// - discharging: The battery is currently discharging. Accepts the 18 | /// current percentage as argument. 19 | enum BatteryState: Equatable { 20 | case chargedAndPlugged 21 | case charging(percentage: Percentage) 22 | case discharging(percentage: Percentage) 23 | 24 | // MARK: Internal 25 | 26 | /// The current percentage. 27 | var percentage: Percentage { 28 | switch self { 29 | case .chargedAndPlugged: 30 | return Percentage(numeric: 100) 31 | case .charging(let percentage): 32 | return percentage 33 | case .discharging(let percentage): 34 | return percentage 35 | } 36 | } 37 | } 38 | 39 | /// Compares two BatteryStatusTypes for equality. 40 | /// 41 | /// - parameter lhs: A BatteryStatusType. 42 | /// - parameter rhs: Another BatteryStatusType. 43 | /// - returns: True if the supplied BatteryStatusType's are equal. Otherwise false. 44 | func == (lhs: BatteryState, rhs: BatteryState) -> Bool { 45 | switch (lhs, rhs) { 46 | case (.chargedAndPlugged, .chargedAndPlugged), 47 | (.charging, .charging): 48 | return true 49 | case (.discharging(let lhsPercentage), .discharging(let rhsPercentage)): 50 | guard let lhs = lhsPercentage.numeric, let rhs = rhsPercentage.numeric else { 51 | return false 52 | } 53 | // Divide the percentages by the defined drawing precision; So that the battery image 54 | // only gets redrawn, when it actually differs. 55 | return round(Double(lhs) / drawingPrecision) == round(Double(rhs ) / drawingPrecision) 56 | default: 57 | return false 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /AppleJuice/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | $(MARKETING_VERSION) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(CURRENT_PROJECT_VERSION) 25 | LSApplicationCategoryType 26 | public.app-category.utilities 27 | LSMinimumSystemVersion 28 | $(MACOSX_DEPLOYMENT_TARGET) 29 | LSUIElement 30 | 31 | NSAppTransportSecurity 32 | 33 | NSExceptionDomains 34 | 35 | github-cloud.s3.amazonaws.com 36 | 37 | NSIncludesSubdomains 38 | 39 | NSTemporaryExceptionAllowsInsecureHTTPLoads 40 | 41 | 42 | github.com 43 | 44 | NSIncludesSubdomains 45 | 46 | NSTemporaryExceptionAllowsInsecureHTTPLoads 47 | 48 | 49 | raphaelhanneken.com 50 | 51 | NSIncludesSubdomains 52 | 53 | NSTemporaryExceptionAllowsInsecureHTTPLoads 54 | 55 | 56 | raphaelhanneken.de 57 | 58 | NSIncludesSubdomains 59 | 60 | NSTemporaryExceptionAllowsInsecureHTTPLoads 61 | 62 | 63 | raphaelhanneken.github.io 64 | 65 | NSIncludesSubdomains 66 | 67 | NSTemporaryExceptionAllowsInsecureHTTPLoads 68 | 69 | 70 | 71 | 72 | NSHumanReadableCopyright 73 | Copyright © 2016 - 2020 Raphael Hanneken. All rights reserved. 74 | NSMainNibFile 75 | MainMenu 76 | NSPrincipalClass 77 | NSApplication 78 | SUFeedURL 79 | https://raphaelhanneken.github.io/apple-juice/sparkle/appcast.xml 80 | SUPublicDSAKeyFile 81 | dsa_pub.pem 82 | 83 | 84 | -------------------------------------------------------------------------------- /AppleJuice/MenuInfoView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuInfoView.swift 3 | // Apple Juice 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import SwiftUI 8 | 9 | @available(OSX 11.0, *) 10 | struct MenuInfoView: View { 11 | @StateObject private var model = MenuInfoViewModel() 12 | 13 | var body: some View { 14 | VStack(alignment: .leading, spacing: 0) { 15 | HStack(alignment: .bottom, spacing: 0) { 16 | Text(model.powerSource) 17 | .font(.system(size: 13, weight: .semibold)) 18 | } 19 | .padding(.bottom, 3) 20 | HStack(alignment: .top, spacing: 9) { 21 | Text(model.remaining) 22 | .foregroundColor(.secondary) 23 | .font(.system(size: 13)) 24 | Text(model.currentCharge) 25 | .foregroundColor(.secondary) 26 | .font(.system(size: 13)) 27 | } 28 | } 29 | .padding(.vertical, 7) 30 | .padding(.horizontal, 15) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /AppleJuice/MenuInfoViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuInfoViewModel.swift 3 | // Apple Juice 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Foundation 8 | 9 | @available(OSX 11.0, *) 10 | class MenuInfoViewModel: ObservableObject { 11 | @Published var powerSource = NSLocalizedString("Unknown", comment: "") 12 | @Published var remaining = NSLocalizedString("Calculating", comment: "") 13 | @Published var currentCharge = "--" 14 | 15 | private let batteryService: BatteryService? 16 | 17 | init() { 18 | do { 19 | batteryService = try BatteryService() 20 | } catch { 21 | batteryService = nil 22 | } 23 | 24 | update() 25 | 26 | NotificationCenter.default 27 | .addObserver(self, 28 | selector: #selector(MenuInfoViewModel.powerSourceChanged(_:)), 29 | name: NSNotification.Name(rawValue: powerSourceChangedNotification), 30 | object: nil) 31 | } 32 | 33 | @objc public func powerSourceChanged(_: AnyObject) { 34 | update() 35 | } 36 | 37 | private func update() { 38 | guard let percentage = batteryService?.percentage, 39 | let timeRemaining = batteryService?.timeRemaining, 40 | let currentCharge = batteryService?.charge, 41 | let maxCapacity = batteryService?.capacity, 42 | let amperage = batteryService?.amperage, 43 | let powerSource = batteryService?.powerSource 44 | else { 45 | return 46 | } 47 | 48 | self.powerSource = powerSource.localizedDescription 49 | self.currentCharge = String(format: "%i / %i mAh (%i mA)", currentCharge, maxCapacity, amperage) 50 | if UserPreferences.showTime { 51 | self.remaining = percentage.formatted 52 | } else { 53 | self.remaining = timeRemaining.formatted 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /AppleJuice/NSImageExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreferenceKeys.swift 3 | // Apple Juice 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Cocoa 8 | 9 | extension NSImage { 10 | func drawThreePartImage( 11 | withStartCap startCap: NSImage, 12 | fill: NSImage, 13 | endCap: NSImage, 14 | inFrame frame: NSRect) -> NSImage 15 | { 16 | lockFocus() 17 | NSDrawThreePartImage(frame, startCap, fill, endCap, false, .copy, 1, false) 18 | unlockFocus() 19 | 20 | return self 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /AppleJuice/NotificationKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotificationKey.swift 3 | // Apple Juice 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | /// Defines a notification at a given percentage. 8 | /// 9 | /// - invalid: Not a valid notification percentage. 10 | /// - fivePercent: Notify the user at five percent remaining charge. 11 | /// - tenPercent: Notify the user at ten percent remaining charge. 12 | /// - fifeteenPercent: Notify the user at fifetenn percent remaining charge. 13 | /// - twentyPercent: Notify the user at twenty percent remaining charge. 14 | /// - hundredPercent: Notify the user when the battery is fully charged. 15 | enum NotificationKey: Int { 16 | case invalid = 0 17 | case fivePercent = 5 18 | case tenPercent = 10 19 | case fifeteenPercent = 15 20 | case twentyPercent = 20 21 | case hundredPercent = 100 22 | } 23 | -------------------------------------------------------------------------------- /AppleJuice/Percentage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Percentage.swift 3 | // Apple Juice 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Foundation 8 | 9 | struct Percentage { 10 | var numeric: Int? 11 | 12 | /// The current percentage, formatted according to the selected client locale, e.g. 13 | /// en_US: 42% fr_FR: 42 % 14 | var formatted: String { 15 | guard let percentage = numeric else { 16 | return NSLocalizedString("Calculating", comment: "") 17 | } 18 | 19 | let percentageFormatter = NumberFormatter() 20 | percentageFormatter.numberStyle = .percent 21 | percentageFormatter.generatesDecimalNumbers = false 22 | percentageFormatter.localizesFormat = true 23 | percentageFormatter.multiplier = 1.0 24 | percentageFormatter.minimumFractionDigits = 0 25 | percentageFormatter.maximumFractionDigits = 0 26 | 27 | return percentageFormatter.string(from: percentage as NSNumber) ?? "\(percentage) %" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /AppleJuice/PowerSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PowerSource.swift 3 | // Apple Juice 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Foundation 8 | 9 | enum PowerSource: String { 10 | case unknown = "Unknown" 11 | case powerAdapter = "Power Adapter" 12 | case battery = "Battery" 13 | 14 | var localizedDescription: String { 15 | return NSLocalizedString(self.rawValue, comment: "") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /AppleJuice/PreferenceKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreferenceKeys.swift 3 | // Apple Juice 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | /// Define constants to access the users preferences. 8 | /// 9 | /// - showTime: Display time or percentage in the status bar. 10 | /// - fivePercentNotification: Notify the user at five percent remaining charge. 11 | /// - tenPercentNotification: Notify the user at ten percent remaining charge. 12 | /// - fifeteenPercentNotification: Notify the user at fifeteen percent remaining charge. 13 | /// - twentyPercentNotification: Notify the user at twenty percent remaining charge. 14 | /// - hundredPercentNotification: Notify the user when the battery is fully charged. 15 | /// - lastNotification: Store the percentage when we notified the user the last time. 16 | enum PreferenceKey: String { 17 | case showTime = "ShowTimePref" 18 | case fivePercentNotification = "FivePercentNotificationPref" 19 | case tenPercentNotification = "TenPercentNotificationPref" 20 | case fifeteenPercentNotification = "FifeteenPercentNotificationPref" 21 | case twentyPercentNotification = "TwentyPercentNotificationPref" 22 | case hundredPercentNotification = "HundredPercentNotificationPref" 23 | case lastNotification = "LastNotifiedPref" 24 | case hideMenubarInfo = "HideMenubarInfoPref" 25 | case hideBatteryIcon = "HideBatteryIconPref" 26 | } 27 | -------------------------------------------------------------------------------- /AppleJuice/StatusBarIcon.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatusBarIcon.swift 3 | // Apple Juice 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Cocoa 8 | 9 | /// Image names for the images used by the menu bar item icon. 10 | private enum BatteryImage: NSImage.Name { 11 | case left = "BatteryFillCapLeft" 12 | case right = "BatteryFillCapRight" 13 | case middle = "BatteryFill" 14 | case outline = "BatteryOutline" 15 | case charging = "Charging" 16 | case chargedAndPlugged = "ChargedAndPlugged" 17 | case deadCropped = "DeadCropped" 18 | case none = "None" 19 | case lowBattery = "LowBattery" 20 | } 21 | 22 | internal struct StatusBarIcon { 23 | // MARK: Internal 24 | 25 | /// Draws a battery icon for the given BatteryState. 26 | /// 27 | /// - parameter status: The BatteryState for the status the battery is currently in, e.g. charging 28 | /// - returns: The battery image for the provided battery status. 29 | internal mutating func drawBatteryImage(forStatus status: BatteryState) -> NSImage? { 30 | if let cache = self.cache, cache.batteryStatus == status { 31 | return cache.image 32 | } 33 | 34 | switch status { 35 | case .charging: 36 | cache = BatteryImageCache(forStatus: status, 37 | withImage: batteryImage(named: .charging)) 38 | case .chargedAndPlugged: 39 | cache = BatteryImageCache(forStatus: status, 40 | withImage: batteryImage(named: .chargedAndPlugged)) 41 | case let .discharging(percentage): 42 | cache = BatteryImageCache(forStatus: status, 43 | withImage: dischargingBatteryImage(forPercentage: Double(percentage.numeric ?? 0))) 44 | } 45 | 46 | return cache?.image 47 | } 48 | 49 | /// Draws a battery icon for the given BatteryError. 50 | /// 51 | /// - parameter err: The BatteryError object for the corresponding error that happened. 52 | /// - returns: A battery icon for the given BatteryError. 53 | internal func drawBatteryImage(forError error: BatteryError?) -> NSImage? { 54 | guard let error = error else { 55 | return nil 56 | } 57 | 58 | switch error { 59 | case .connectionAlreadyOpen: 60 | return batteryImage(named: .deadCropped) 61 | case .serviceNotFound: 62 | return batteryImage(named: .none) 63 | } 64 | } 65 | 66 | // MARK: Private 67 | 68 | /// The little margins between the battery outline and the capcity bar. 69 | private let capacityOffsetX: CGFloat = 2.0 70 | private let capacityOffsetY: CGFloat = 2.0 71 | 72 | /// Cache the last drawn battery icon. 73 | private var cache: BatteryImageCache? 74 | 75 | /// Draws a battery icon based on the battery's current percentage. 76 | /// 77 | /// - parameter percentage: The current percentage of the battery. 78 | /// - returns: A battery icon for the supplied percentage. 79 | private func dischargingBatteryImage(forPercentage percentage: Double) -> NSImage? { 80 | guard let batteryOutline = batteryImage(named: .outline), 81 | let capacityCapLeft = batteryImage(named: .left), 82 | let capacityCapRight = batteryImage(named: .right), 83 | let capacityFill = batteryImage(named: .middle) 84 | else { 85 | return nil 86 | } 87 | 88 | // Delete the image name for the battery outline to keep it's representations out of the NSCachedImageRep 89 | batteryOutline.setName(nil) 90 | let drawingRect = NSRect(x: capacityOffsetX, 91 | y: capacityOffsetY, 92 | width: CGFloat(round(percentage / drawingPrecision)) * capacityFill.size.width, 93 | height: capacityFill.size.height) 94 | 95 | // NSImage#drawThreePartImage glitchets when the width of the capacity bar drops 96 | // below the combined width of startCap and endCap. 97 | if drawingRect.width < (2 * capacityFill.size.width) { 98 | return batteryImage(named: .lowBattery) 99 | } 100 | 101 | return batteryOutline.drawThreePartImage(withStartCap: capacityCapLeft, 102 | fill: capacityFill, 103 | endCap: capacityCapRight, 104 | inFrame: drawingRect) 105 | } 106 | 107 | /// Returns the image object associated with the specified name as template. 108 | /// 109 | /// - parameter name: The name of an image in the app bundle. 110 | /// - returns: An image object associated with the specified name as template. 111 | private func batteryImage(named name: BatteryImage) -> NSImage? { 112 | guard let img = NSImage(named: name.rawValue) else { return nil } 113 | img.isTemplate = true 114 | 115 | return img 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /AppleJuice/StatusBarItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatusIcon.swift 3 | // Apple Juice 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Cocoa 8 | 9 | final class StatusBarItem: NSObject { 10 | private let item: NSStatusItem! 11 | private var icon: StatusBarIcon? 12 | 13 | /// Creates a new battery status bar item object. 14 | /// 15 | /// - parameter action: The action to be triggered, when the user clicks on the status bar item. 16 | /// - parameter target: The target that implements the supplied action. 17 | init(forBattery battery: BatteryService?, withAction action: Selector?, forTarget target: AnyObject?) { 18 | item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) 19 | item.target = target 20 | item.action = action 21 | icon = StatusBarIcon() 22 | super.init() 23 | } 24 | 25 | /// Creates a status bar item object for a specific error. 26 | /// 27 | /// - parameter error: The error that occured. 28 | /// - parameter action: The action to be triggered, when the user clicks on the status bar item. 29 | /// - parameter target: The target that implements the supplied action. 30 | convenience init(forError error: BatteryError?, withAction action: Selector?, forTarget target: AnyObject?) { 31 | self.init(forBattery: nil, withAction: action, forTarget: target) 32 | 33 | guard let btn = item.button else { 34 | return 35 | } 36 | btn.image = icon?.drawBatteryImage(forError: error) 37 | } 38 | 39 | // MARK: Public 40 | 41 | /// Update the status bar items title and icon. 42 | /// 43 | /// - parameter battery: The battery object, to update the status bar item for. 44 | public func update(batteryInfo battery: BatteryService?) { 45 | setBatteryIcon(battery) 46 | setTitle(battery) 47 | } 48 | 49 | /// Displays the supplied menu object when the user clicks on the status bar item. 50 | /// 51 | /// - parameter menu: The menu object to display to the user. 52 | public func popUpMenu(_ menu: NSMenu) { 53 | item.popUpMenu(menu) 54 | } 55 | 56 | // MARK: Private 57 | 58 | /// Sets the status bar item's battery icon. 59 | /// 60 | /// - parameter batter: The battery to render the status bar icon for. 61 | private func setBatteryIcon(_ battery: BatteryService?) { 62 | guard let batteryState = battery?.state, 63 | let button = item.button 64 | else { 65 | return 66 | } 67 | 68 | if UserPreferences.hideBatteryIcon { 69 | button.image = nil 70 | } else { 71 | button.image = icon?.drawBatteryImage(forStatus: batteryState) 72 | button.imagePosition = .imageRight 73 | } 74 | } 75 | 76 | /// Sets the status bar item's title 77 | /// 78 | /// - parameter battery: The battery to build the status bar title for. 79 | private func setTitle(_ battery: BatteryService?) { 80 | guard let button = item.button, 81 | let percentage = battery?.percentage.formatted, 82 | let timeRemaining = battery?.timeRemaining.formatted 83 | else { 84 | return 85 | } 86 | 87 | let titleAttributes = [NSAttributedString.Key.font: NSFont.menuBarFont(ofSize: 11.0)] 88 | 89 | if UserPreferences.showTime { 90 | button.attributedTitle = NSAttributedString(string: timeRemaining, attributes: titleAttributes) 91 | } else { 92 | button.attributedTitle = NSAttributedString(string: percentage, attributes: titleAttributes) 93 | } 94 | 95 | if UserPreferences.hideMenubarInfo { 96 | button.attributedTitle = NSAttributedString(string: "") 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /AppleJuice/StatusNotification.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatusNotification.swift 3 | // Apple Juice 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Foundation 8 | 9 | /// Posts user notifications about the current charging status. 10 | struct StatusNotification { 11 | // MARK: Lifecycle 12 | 13 | /// Initializes a new StatusNotification. 14 | /// 15 | /// - parameter key: The notification key which to display a user notification for. 16 | /// - returns: An optional StatusNotification; Return nil when the notificationKey 17 | /// is invalid or nil. 18 | init?(forState state: BatteryState?) { 19 | guard let state = state, state != .charging(percentage: Percentage(numeric: 0)) else { 20 | return nil 21 | } 22 | guard let key = NotificationKey(rawValue: state.percentage.numeric ?? 0), key != .invalid else { 23 | return nil 24 | } 25 | self.notificationKey = key 26 | } 27 | 28 | // MARK: Internal 29 | 30 | /// Present a notification for the current battery status to the user 31 | func postNotification() { 32 | if self.shouldPresentNotification() { 33 | NSUserNotificationCenter.default.deliver(self.createUserNotification()) 34 | UserPreferences.lastNotified = self.notificationKey 35 | } 36 | } 37 | 38 | // MARK: Private 39 | 40 | /// The current notification's key. 41 | private let notificationKey: NotificationKey 42 | 43 | /// Check whether to present a notification to the user or not. Depending on the 44 | /// users preferences and whether the user already got notified about the current 45 | /// percentage. 46 | /// 47 | /// - returns: Whether to present a notification for the current battery percentage 48 | private func shouldPresentNotification() -> Bool { 49 | (self.notificationKey != UserPreferences.lastNotified 50 | && UserPreferences.notifications.contains(self.notificationKey)) 51 | } 52 | 53 | /// Create a user notification for the current battery status 54 | /// 55 | /// - returns: The user notification to display 56 | private func createUserNotification() -> NSUserNotification { 57 | let notification = NSUserNotification() 58 | notification.title = self.getNotificationTitle() 59 | notification.informativeText = self.getNotificationText() 60 | 61 | return notification 62 | } 63 | 64 | /// Get the corresponding notification title for the current battery state 65 | /// 66 | /// - returns: The notification title 67 | private func getNotificationTitle() -> String { 68 | if self.notificationKey == .hundredPercent { 69 | return NSLocalizedString("Charged Notification Title", comment: "") 70 | } 71 | return String.localizedStringWithFormat(NSLocalizedString("Low Battery Notification Title", comment: ""), 72 | self.formattedPercentage()) 73 | } 74 | 75 | /// Get the corresponding notification text for the current battery state 76 | /// 77 | /// - returns: The notification text 78 | private func getNotificationText() -> String { 79 | if self.notificationKey == .hundredPercent { 80 | return NSLocalizedString("Charged Notification Message", comment: "") 81 | } 82 | return NSLocalizedString("Low Battery Notification Message", comment: "") 83 | } 84 | 85 | /// The current percentage, formatted according to the selected client locale, e.g. 86 | /// en_US: 42% fr_FR: 42 % 87 | /// 88 | /// - returns: The localised percentage 89 | private func formattedPercentage() -> String { 90 | let percentageFormatter = NumberFormatter() 91 | percentageFormatter.numberStyle = .percent 92 | percentageFormatter.generatesDecimalNumbers = false 93 | percentageFormatter.localizesFormat = true 94 | percentageFormatter.multiplier = 1.0 95 | percentageFormatter.minimumFractionDigits = 0 96 | percentageFormatter.maximumFractionDigits = 0 97 | 98 | return percentageFormatter.string(from: self.notificationKey.rawValue as NSNumber) 99 | ?? "\(self.notificationKey.rawValue) %" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /AppleJuice/TimeRemaining.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimeRemaining.swift 3 | // Apple Juice 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Foundation 8 | 9 | struct TimeRemaining { 10 | let minutes: Int? 11 | let state: BatteryState? 12 | 13 | var formatted: String { 14 | guard let minutesRemaining = minutes, let batteryState = state else { 15 | return NSLocalizedString("Calculating", comment: "") 16 | } 17 | 18 | if batteryState == .chargedAndPlugged { 19 | return NSLocalizedString("Charged Notification Title", comment: "") 20 | } 21 | 22 | return String(format: "%dh %02dm", arguments: [minutesRemaining / 60, minutesRemaining % 60]) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /AppleJuice/UserPreferences.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserPreferences.swift 3 | // Apple Juice 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Foundation 8 | 9 | /// Manages the user preferences. 10 | final class UserPreferences: NSObject { 11 | // MARK: Internal 12 | 13 | /// True if the user wants the remaining time to be displayed within the menu bar. 14 | static var showTime: Bool { 15 | userDefaults.bool(forKey: PreferenceKey.showTime.rawValue) 16 | } 17 | 18 | /// Hide all menu bar information 19 | static var hideMenubarInfo: Bool { 20 | userDefaults.bool(forKey: PreferenceKey.hideMenubarInfo.rawValue) 21 | } 22 | 23 | /// Hide all menu bar information 24 | static var hideBatteryIcon: Bool { 25 | userDefaults.bool(forKey: PreferenceKey.hideBatteryIcon.rawValue) 26 | } 27 | 28 | /// True if the user wants a notification at five percent. 29 | static var fivePercentNotification: Bool { 30 | userDefaults.bool(forKey: PreferenceKey.fivePercentNotification.rawValue) 31 | } 32 | 33 | /// True if the user wants a notification at ten percent. 34 | static var tenPercentNotification: Bool { 35 | userDefaults.bool(forKey: PreferenceKey.tenPercentNotification.rawValue) 36 | } 37 | 38 | /// True if the user wants a notification at fifeteen percent. 39 | static var fifeteenPercentNotification: Bool { 40 | userDefaults.bool(forKey: PreferenceKey.fifeteenPercentNotification.rawValue) 41 | } 42 | 43 | /// True if the user wants a notification at twenty percent. 44 | static var twentyPercentNotification: Bool { 45 | userDefaults.bool(forKey: PreferenceKey.twentyPercentNotification.rawValue) 46 | } 47 | 48 | /// True if the user wants a notification at hundred percent. 49 | static var hundredPercentNotification: Bool { 50 | userDefaults.bool(forKey: PreferenceKey.hundredPercentNotification.rawValue) 51 | } 52 | 53 | /// Keeps the percentage the user was last notified. 54 | static var lastNotified: NotificationKey? { 55 | get { 56 | NotificationKey(rawValue: userDefaults.integer(forKey: PreferenceKey.lastNotification.rawValue)) 57 | } 58 | set { 59 | guard let notificationKey = newValue else { 60 | return 61 | } 62 | userDefaults.set(notificationKey.rawValue, forKey: PreferenceKey.lastNotification.rawValue) 63 | } 64 | } 65 | 66 | /// A set of all percentages where the user is interested. 67 | static var notifications: Set { 68 | // Create an empty set. 69 | var result: Set = [] 70 | // Check the users notification settings and 71 | // add enabled notifications to the result set. 72 | if fivePercentNotification { 73 | result.insert(.fivePercent) 74 | } 75 | if tenPercentNotification { 76 | result.insert(.tenPercent) 77 | } 78 | if fifeteenPercentNotification { 79 | result.insert(.fifeteenPercent) 80 | } 81 | if twentyPercentNotification { 82 | result.insert(.twentyPercent) 83 | } 84 | if hundredPercentNotification { 85 | result.insert(.hundredPercent) 86 | } 87 | return result 88 | } 89 | 90 | /// Register user defaults. 91 | static func registerDefaults() { 92 | let defaultPreferences = [ 93 | PreferenceKey.showTime.rawValue: true, 94 | PreferenceKey.fivePercentNotification.rawValue: false, 95 | PreferenceKey.tenPercentNotification.rawValue: false, 96 | PreferenceKey.fifeteenPercentNotification.rawValue: true, 97 | PreferenceKey.twentyPercentNotification.rawValue: false, 98 | PreferenceKey.hundredPercentNotification.rawValue: true, 99 | PreferenceKey.lastNotification.rawValue: 0, 100 | PreferenceKey.hideMenubarInfo.rawValue: false, 101 | ] as [String: Any] 102 | 103 | userDefaults.register(defaults: defaultPreferences) 104 | } 105 | 106 | // MARK: Private 107 | 108 | /// Holds a reference to the standard user defaults. 109 | private static let userDefaults = UserDefaults.standard 110 | } 111 | -------------------------------------------------------------------------------- /AppleJuice/de.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | // Localizable.strings 2 | // Apple Juice 3 | // 4 | // German 5 | 6 | 7 | // Menu Items 8 | 9 | "Charged" = "Geladen"; 10 | "Unknown" = "Unbekannt"; 11 | "Battery" = "Batterie"; 12 | "Power Adapter" = "Netzteil"; 13 | "Calculating" = "Berechne…"; 14 | 15 | 16 | // Notifications 17 | 18 | "Charged Notification Title" = "Vollständig geladen"; 19 | "Charged Notification Message" = "Dein Mac ist jetzt vollständig geladen."; 20 | "Low Battery Notification Title" = "%@ verbleibend"; 21 | "Low Battery Notification Message" = "Ohne Stromzufuhr aktiviert dein Mac bald den Ruhezustand."; 22 | 23 | 24 | // Properties 25 | 26 | "Time Remaining" = "Restzeit:"; 27 | "Percentage" = "Prozent:"; 28 | "Power Usage" = "Stromverbrauch:"; 29 | "Charge" = "Kapazität:"; 30 | "Cycle Count" = "Ladezyklen:"; 31 | "Temperature" = "Temperatur:"; 32 | "Power Source" = "Stromversorgung:"; 33 | "Health" = "Zustand:"; 34 | 35 | 36 | // Units 37 | 38 | "Watts" = "Watt"; 39 | -------------------------------------------------------------------------------- /AppleJuice/de.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | NSNegateBoolean 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | NSNegateBoolean 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | NSNegateBoolean 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /AppleJuice/dsa_pub.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIGPDCCBC4GByqGSM44BAEwggQhAoICAQC95JHX6l8QyhPwBWuP5Oa2XQoOWzcL 3 | vL21ZhkkGEJ1IUyN0RVZKBGARRO4sRHwCTzJEL3pg4LkFDnaMDuedL/preujlV+U 4 | +YrQzbpVmSFDbGmwxhvR+D+6PPSXG4UfIBnsCxVo4weNxvLvsCQLjpV0FPoROHah 5 | C79XgVFYDE5zZxfk0ntSdVJ6faJoRYjusc8OhcigcLlW8LYM5kPWlsnWubYNO6Ju 6 | zkpzwpQrSESKcmfsbeumXc81Z2qt9y5Y1r8n/3Bu37PvHzPkTi2KA2Mmn3WYT3hX 7 | u/dm6OtKU5KaZEeAC/P2s5HZVha2z8VOYzStmjR1IkfSWIFLwL881/b5+RtViTpk 8 | nbFW9FzPI6CKdrCTDzLeVaJSRY9SWuZc0GfmN+qCO/ZnMQHXOHYj8BXwyBT12YcR 9 | pG5PiKF1dvumkesWWbZ7vwZ4OjrwIuLb3ZcwoReL4dSXpLlW1aB5PFd6efMTDINQ 10 | vWkW1FF5bSx7ezZ6XacMShJriKPLXj8YCIyIXR3nFWCDyI4ZUq9K+8RdOf8ZMMcJ 11 | bJ8tuw/iKmtHslpodZoUyfFPfWlmGASnMU37Rc2TNODwHTz7X/xjqW9MabBARkqi 12 | 6Bl5a+ft4Xqc9Tu8Gj/N06bgu+Yx0axrh/Jcbb3ONoPGKP0n9ro8TnvyENj6ox7+ 13 | DuGNGPMc5CILmQIVAIkg5DKwJjPmCiqvTnbLRDn6Kd45AoICAQCYbJBlRPrdtIlX 14 | trARo6I2tRhlrVAu6vm8vnXwYhOL1YhEZRLOHK+yJaHsHfjLM7w3PsKK4i0Pth9V 15 | 3MJpH9AxLQb2bDFJVnxOzHeWY42D+b+SpiFATn0Af0kNwcg61eejMdM0MSWivfsv 16 | zlsBoiWGvHQUkGzrSp+5iFYr6OcTYHusEVkDw/14mZ9aUBAXuh3SsX5bHS4mC7QQ 17 | uWZCMor/3IJ51Y8Ha4hkRA3DA3RkQzQBOTubcPxlSTMHY4gm9mdtdJPApyZypPfR 18 | w3h4zpWnvGZI6EXW6FNaemzyQr94iFTJa/aPydevilkRXBuDIS/I7BDyFkJ2JI/N 19 | PzI7GKoFfq4WU1t3eVl7qoN6kGZtPxjWXOKmN3odMtlvzBmXxT4ONt6uvO1dZVHI 20 | zp+AyhKkO72R9x81uovlxlM1Pi58DzWyOGhj+wmnGb4trNCP+h2mEs9Syz3L0KII 21 | Om1zyZStlaWuim4Jn7GFdtpo4pZ6dbVVKc/8irKLuj/0D2ghXYBmP6e1KKF2RgLG 22 | k/uivH0gjepj2KUhT8AFHfCnjVrHRc7f1KgTxDwLK+fuPzziHYlg33w9yIfebcxS 23 | QU+zdl+s1YfJNl7dG1K33mUD4VuBnqL1cm8YDwk8ZerASYcG3U9lQt0VvKAZf2qz 24 | YKnX9wTT5aBwmBvZOWSIMvmXujopGwOCAgYAAoICAQCkb/PLZq3Zd90Y6yzQ/Qqe 25 | aQKfTryAyBXCKbGXEZbt83h04pz9vLJukzjy3Uj0ezMs9xqIVC7nDZhYgXpWV6UJ 26 | fDJL+bJ5NyztaI7DYAXIaFYw1AheYvVyLO8PUbOJHnUytYbwGNERoXdr9gnVx0H5 27 | /3xHA466kvwLVrBeAYgMqwn6yVp3T4GGS5iGCezvegXx/+90Vl9D5TLdYdpD5I2X 28 | qTFjGyhXdpxSVWu4rogqNmUztX3JAYIwvfLjKjQjLzpao8CTxoL+6/svwBvtgK4o 29 | 4DqSIAqCx/5IXtLdwqEPVUMwXCntU0UTxbEIE/4W0T8GcgZuTwXBdZZ3fDPyu86U 30 | 59gPrOteSS6hJv8JP0e7+PtNBZNcC+8BfMA1yoNlDYA7LeXVWUsbKuq6fFXo/jwC 31 | tdGDadL1w6iBZFLzB/8JCGu+DC17hqkVzQ/5zqvP+j9NrAzE9SpkRgjdZVyLPkpB 32 | au2eTgJznGBSN/jp0yj9Mq1idGe8NVY62f/E/4P3z6iIWMvlvVdBp/HsMx388D7m 33 | eianZZ6LhjO5h1EcF9v9U7eGKb5IAwIu5/N/Tf029Q1wSv7lx+8ZrHK0fRhD1ws5 34 | P046nyF9lKGk+aKis3Gunro26ILe4OcmBatVECxCbm8iSctqv+fiYAI5UDYLiEjE 35 | 3gcOFYFblMAhcutsE+EoMQ== 36 | -----END PUBLIC KEY----- 37 | -------------------------------------------------------------------------------- /AppleJuice/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | // Localizable.strings 2 | // Apple Juice 3 | // 4 | // English 5 | 6 | 7 | // Menu Items 8 | 9 | "Charged" = "Charged"; 10 | "Unknown" = "Unknown"; 11 | "Battery" = "Battery"; 12 | "Power Adapter" = "Power Adapter"; 13 | "Calculating" = "Calculating…"; 14 | 15 | 16 | // Notifications 17 | 18 | "Charged Notification Title" = "Charged"; 19 | "Charged Notification Message" = "Your Battery is now fully charged."; 20 | "Low Battery Notification Title" = "%@ remaining"; 21 | "Low Battery Notification Message" = "Your Mac will sleep soon unless plugged into a power outlet."; 22 | 23 | 24 | // Properties 25 | 26 | "Time Remaining" = "Time Remaining:"; 27 | "Percentage" = "Percentage:"; 28 | "Power Usage" = "Power Usage:"; 29 | "Charge" = "Charge:"; 30 | "Cycle Count" = "Cycle Count:"; 31 | "Temperature" = "Temperature:"; 32 | "Power Source" = "Power Source:"; 33 | "Health" = "Condition:"; 34 | 35 | 36 | // Units 37 | 38 | "Watts" = "Watts"; 39 | -------------------------------------------------------------------------------- /AppleJuice/en.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | NSNegateBoolean 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | NSNegateBoolean 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | NSNegateBoolean 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /AppleJuice/es.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | // Localizable.strings 2 | // Apple Juice 3 | // 4 | // Spanish 5 | 6 | 7 | // Menu Items 8 | 9 | "Charged" = "Cargado"; 10 | "Unknown" = "Desconocido"; 11 | "Battery" = "Batería"; 12 | "Power Adapter" = "Adaptador de Corriente"; 13 | "Calculating" = "Calculando…"; 14 | 15 | 16 | // Notifications 17 | 18 | "Charged Notification Title" = "Cargado"; 19 | "Charged Notification Message" = "La batería está completamente cargada."; 20 | "Low Battery Notification Title" = "%@ restante"; 21 | "Low Battery Notification Message" = "Tu Mac dormirá pronto a no ser enchufado a una toma de corriente."; 22 | 23 | 24 | // Properties 25 | 26 | "Time Remaining" = "Tiempo Restante:"; 27 | "Percentage" = "Porcentaje:"; 28 | "Power Usage" = "Consumo de Energía:"; 29 | "Charge" = "Carga:"; 30 | "Cycle Count" = "Ciclos de Carga:"; 31 | "Temperature" = "Temperatura:"; 32 | "Power Source" = "Fuente de alimentación:"; 33 | "Health" = "Condición:"; 34 | 35 | 36 | // Units 37 | 38 | "Watts" = "Vatios"; 39 | -------------------------------------------------------------------------------- /AppleJuice/es.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | NSNegateBoolean 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | NSNegateBoolean 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | NSNegateBoolean 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /AppleJuice/fr.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | // Localizable.strings 2 | // Apple Juice 3 | // 4 | // French 5 | 6 | 7 | // Menu Items 8 | 9 | "Charged" = "Chargé"; 10 | "Unknown" = "Inconnu"; 11 | "Battery" = "Batterie"; 12 | "Power Adapter" = "Adaptateur Secteur"; 13 | "Calculating" = "Calcul en cours.."; 14 | 15 | 16 | // Notifications 17 | 18 | "Charged Notification Title" = "Chargé"; 19 | "Charged Notification Message" = "Votre batterie est maintenant complètement chargée.."; 20 | "Low Battery Notification Title" = "%@ restants"; 21 | "Low Battery Notification Message" = "Votre Mac va bientôt s'éteindre. Veuillez le brancher"; 22 | 23 | 24 | // Properties 25 | 26 | "Time Remaining" = "Temps Restant :"; 27 | "Percentage" = "Pourcentage :"; 28 | "Power Usage" = "Consommation :"; 29 | "Charge" = "Charge :"; 30 | "Cycle Count" = "Cycles de charge :"; 31 | "Temperature" = "Température :"; 32 | "Power Source" = "Source d'alimentation :"; 33 | "Health" = "Conditions :"; 34 | 35 | 36 | // Units 37 | 38 | "Watts" = "Watts"; 39 | -------------------------------------------------------------------------------- /AppleJuice/fr.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | NSNegateBoolean 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | NSNegateBoolean 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | NSNegateBoolean 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /AppleJuice/it.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | // Localizable.strings 2 | // Apple Juice 3 | // 4 | // Italian 5 | 6 | 7 | // Menu Items 8 | 9 | "Charged" = "Carico"; 10 | "Unknown" = "Sconosciuto"; 11 | "Battery" = "Batteria"; 12 | "Power Adapter" = "Adattatore di alimentazione"; 13 | "Calculating" = "Calcolando…"; 14 | 15 | 16 | // Notifications 17 | 18 | "Charged Notification Title" = "Carico"; 19 | "Charged Notification Message" = "La batteria è ora completamente carica."; 20 | "Low Battery Notification Title" = "%@ restante"; 21 | "Low Battery Notification Message" = "Il tuo Mac andrá presto in standby a meno che non si colleghi a una presa di corrente."; 22 | 23 | 24 | // Properties 25 | 26 | "Time Remaining" = "Tempo Rimanente:"; 27 | "Percentage" = "Percentuale:"; 28 | "Power Usage" = "Consumo di Energia:"; 29 | "Charge" = "Carica:"; 30 | "Cycle Count" = "Cicli di Carica:"; 31 | "Temperature" = "Temperatura:"; 32 | "Power Source" = "Alimentazione:"; 33 | "Health" = "Condizione:"; 34 | 35 | 36 | // Units 37 | 38 | "Watts" = "Watt"; 39 | -------------------------------------------------------------------------------- /AppleJuice/it.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | NSNegateBoolean 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | NSNegateBoolean 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | NSNegateBoolean 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /AppleJuice/pt-BR.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /** Generated by IINA Translation Utility */ 2 | 3 | "Battery" = "Bateria"; 4 | "Calculating" = "Calculando..."; 5 | "Charge" = "Carga:"; 6 | "Charged" = "Carregada"; 7 | "Charged Notification Message" = "Sua bateria está carregada completamente"; 8 | "Charged Notification Title" = "Carregada"; 9 | "Cycle Count" = "Contagem de Ciclo"; 10 | "Health" = "Condição:"; 11 | "Low Battery Notification Message" = "O Mac entrará em repouso se não for conectado a uma tomada"; 12 | "Low Battery Notification Title" = "%@ restante"; 13 | "Percentage" = "Porcentagem:"; 14 | "Power Adapter" = "Adaptador de Energia"; 15 | "Power Source" = "Fonte de Energia:"; 16 | "Power Usage" = "Uso de Energia:"; 17 | "Temperature" = "Temperatura:"; 18 | "Time Remaining" = "Tempo Restante:"; 19 | "Unknown" = "Desconhecido"; 20 | "Watts" = "Watts"; 21 | -------------------------------------------------------------------------------- /AppleJuice/pt-BR.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | NSNegateBoolean 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | NSNegateBoolean 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | NSNegateBoolean 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /AppleJuiceToday/Apple Juice Today.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AppleJuiceToday/BatteryHealthType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BatteryHealthType.swift 3 | // Apple Juice Widget 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Foundation 8 | 9 | class BatteryHealthType: NSObject, BatteryInfoTypeProtocol { 10 | var title: String 11 | var value: String 12 | 13 | init(_ battery: BatteryService?) { 14 | title = NSLocalizedString("Health", comment: "") 15 | if let health = battery?.health { 16 | value = health 17 | } else { 18 | value = "--" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AppleJuiceToday/BatteryInfoTypeProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BatteryInfoTypeProtocol.swift 3 | // Apple Juice Widget 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Foundation 8 | 9 | @objc protocol BatteryInfoTypeProtocol { 10 | var title: String { get } 11 | var value: String { get } 12 | } 13 | -------------------------------------------------------------------------------- /AppleJuiceToday/ChargeInfoType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChargeInfoType.swift 3 | // Apple Juice Widget 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Foundation 8 | 9 | class ChargeInfoType: NSObject, BatteryInfoTypeProtocol { 10 | var title: String 11 | var value: String 12 | 13 | init(_ battery: BatteryService?) { 14 | title = NSLocalizedString("Charge", comment: "") 15 | if let charge = battery?.charge, let capacity = battery?.capacity { 16 | value = "\(charge) / \(capacity) mAh" 17 | } else { 18 | value = "--" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AppleJuiceToday/CycleCountInfoType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CycleCountInfoType.swift 3 | // Apple Juice Widget 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Foundation 8 | 9 | class CycleCountInfoType: NSObject, BatteryInfoTypeProtocol { 10 | var title: String 11 | var value: String 12 | 13 | init(_ battery: BatteryService?) { 14 | title = NSLocalizedString("Cycle Count", comment: "") 15 | if let cycleCount = battery?.cycleCount { 16 | value = "\(cycleCount)" 17 | } else { 18 | value = "--" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AppleJuiceToday/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Apple Juice 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSExtension 26 | 27 | NSExtensionAttributes 28 | 29 | NSExtensionPointVersion 30 | 3.0 31 | 32 | NSExtensionMainNibFile 33 | TodayViewController 34 | NSExtensionPointIdentifier 35 | com.apple.widget-extension 36 | NSExtensionPrincipalClass 37 | $(PRODUCT_MODULE_NAME).TodayViewController 38 | com.apple.notificationcenter.widget.description 39 | Apple Juice Widget 40 | 41 | NSHumanReadableCopyright 42 | Copyright © 2016 Raphael Hanneken. All rights reserved. 43 | 44 | 45 | -------------------------------------------------------------------------------- /AppleJuiceToday/ListRowViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListRowViewController.swift 3 | // Apple Juice Widget 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Cocoa 8 | 9 | final class ListRowViewController: NSViewController { } 10 | -------------------------------------------------------------------------------- /AppleJuiceToday/ListRowViewController.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 | -------------------------------------------------------------------------------- /AppleJuiceToday/PercentageInfoType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PercentageInfoType.swift 3 | // Apple Juice Widget 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Foundation 8 | 9 | class PercentageInfoType: NSObject, BatteryInfoTypeProtocol { 10 | var title: String 11 | var value: String 12 | 13 | init(_ battery: BatteryService?) { 14 | title = NSLocalizedString("Percentage", comment: "") 15 | value = battery?.percentage.formatted ?? "--" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /AppleJuiceToday/PowerUsageInfoType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PowerUsageInfoType.swift 3 | // Apple Juice Widget 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Foundation 8 | 9 | class PowerUsageInfoType: NSObject, BatteryInfoTypeProtocol { 10 | var title: String 11 | var value: String 12 | 13 | init(_ battery: BatteryService?) { 14 | title = NSLocalizedString("Power Usage", comment: "") 15 | 16 | if let powerUsage = battery?.powerUsage, 17 | let amperage = battery?.amperage { 18 | value = "\(powerUsage) \(NSLocalizedString("Watts", comment: "")) (\(amperage) mA)" 19 | } else { 20 | value = "--" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /AppleJuiceToday/SourceInfoType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SourceInfoType.swift 3 | // Apple Juice Widget 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Foundation 8 | 9 | class SourceInfoType: NSObject, BatteryInfoTypeProtocol { 10 | // MARK: Lifecycle 11 | 12 | init(_ battery: BatteryService?) { 13 | title = NSLocalizedString("Power Source", comment: "") 14 | value = NSLocalizedString(battery?.powerSource.rawValue ?? "", value: "--", comment: "") 15 | } 16 | 17 | // MARK: Internal 18 | 19 | var title: String 20 | var value: String 21 | } 22 | -------------------------------------------------------------------------------- /AppleJuiceToday/TemperatureInfoType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TemperatureInfoType.swift 3 | // Apple Juice Widget 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Foundation 8 | 9 | class TemperatureInfoType: NSObject, BatteryInfoTypeProtocol { 10 | var title: String 11 | var value: String 12 | 13 | init(_ battery: BatteryService?) { 14 | title = NSLocalizedString("Temperature", comment: "") 15 | if let temp = battery?.temperature { 16 | value = String(format: "%.1f °C / %.1f °F", arguments: [temp, (temp * 1.8 + 32)]) 17 | } else { 18 | value = "--" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AppleJuiceToday/TimeRemainingInfoType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimeRemainingInfoType.swift 3 | // Apple Juice Widget 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Foundation 8 | 9 | class TimeRemainingInfoType: NSObject, BatteryInfoTypeProtocol { 10 | // MARK: Lifecycle 11 | 12 | init(_ battery: BatteryService?) { 13 | title = NSLocalizedString("Time Remaining", comment: "") 14 | value = battery?.timeRemaining.formatted ?? "--" 15 | } 16 | 17 | // MARK: Internal 18 | 19 | var title: String 20 | var value: String 21 | } 22 | -------------------------------------------------------------------------------- /AppleJuiceToday/TodayViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TodayViewController.swift 3 | // Apple Juice Widget 4 | // https://github.com/raphaelhanneken/apple-juice 5 | // 6 | 7 | import Cocoa 8 | import NotificationCenter 9 | 10 | final class TodayViewController: NSViewController, 11 | NCWidgetProviding, 12 | NCWidgetListViewDelegate, 13 | NCWidgetSearchViewDelegate { 14 | 15 | @IBOutlet var listViewController: NCWidgetListViewController! 16 | 17 | var searchController: NCWidgetSearchViewController? 18 | 19 | // MARK: - NSViewController 20 | 21 | override var nibName: NSNib.Name? { 22 | return "TodayViewController" 23 | } 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | do { 28 | let battery = try BatteryService() 29 | // Set up the widget list view controller. 30 | // The contents property should contain an object for each row in the list. 31 | listViewController.contents = [ 32 | TimeRemainingInfoType(battery), 33 | PercentageInfoType(battery), 34 | PowerUsageInfoType(battery), 35 | BatteryHealthType(battery), 36 | ChargeInfoType(battery), 37 | CycleCountInfoType(battery), 38 | SourceInfoType(battery), 39 | TemperatureInfoType(battery) 40 | ] 41 | } catch { 42 | NSLog("Failed initializing the battery instance.") 43 | } 44 | } 45 | 46 | override func dismiss(_ viewController: NSViewController) { 47 | super.dismiss(viewController) 48 | 49 | // The search controller has been dismissed and is no longer needed. 50 | if viewController == self.searchController { 51 | self.searchController = nil 52 | } 53 | } 54 | 55 | // MARK: - NCWidgetProviding 56 | 57 | func widgetPerformUpdate(_ completionHandler: ((NCUpdateResult) -> Void)) { 58 | // Refresh the widget's contents in preparation for a snapshot. 59 | // Call the completion handler block after the widget's contents have been 60 | // refreshed. Pass NCUpdateResultNoData to indicate that nothing has changed 61 | // or NCUpdateResultNewData to indicate that there is new data since the 62 | // last invocation of this method. 63 | completionHandler(.noData) 64 | } 65 | 66 | func widgetMarginInsets(forProposedMarginInsets defaultMarginInset: NSEdgeInsets) -> NSEdgeInsets { 67 | // Override the left margin so that the list view is flush with the edge. 68 | var newInsets = defaultMarginInset 69 | newInsets.left = 0 70 | return newInsets 71 | } 72 | 73 | var widgetAllowsEditing: Bool { 74 | // Return true to indicate that the widget supports editing of content and 75 | // that the list view should be allowed to enter an edit mode. 76 | return false 77 | } 78 | 79 | func widgetDidBeginEditing() { 80 | // The user has clicked the edit button. 81 | // Put the list view into editing mode. 82 | self.listViewController.editing = true 83 | } 84 | 85 | func widgetDidEndEditing() { 86 | // The user has clicked the Done button, begun editing another widget, 87 | // or the Notification Center has been closed. 88 | // Take the list view out of editing mode. 89 | self.listViewController.editing = false 90 | } 91 | 92 | // MARK: - NCWidgetListViewDelegate 93 | 94 | func widgetList(_ list: NCWidgetListViewController, viewControllerForRow row: Int) -> NSViewController { 95 | // Return a new view controller subclass for displaying an item of widget 96 | // content. The NCWidgetListViewController will set the representedObject 97 | // of this view controller to one of the objects in its contents array. 98 | return ListRowViewController() 99 | } 100 | 101 | func widgetListPerformAddAction(_ list: NCWidgetListViewController) { 102 | // The user has clicked the add button in the list view. 103 | // Display a search controller for adding new content to the widget. 104 | let searchController = NCWidgetSearchViewController() 105 | self.searchController = searchController 106 | searchController.delegate = self 107 | 108 | // Present the search view controller with an animation. 109 | // Implement dismissViewController to observe when the view controller 110 | // has been dismissed and is no longer needed. 111 | self.present(inWidget: searchController) 112 | } 113 | 114 | func widgetList(_ list: NCWidgetListViewController, shouldReorderRow row: Int) -> Bool { 115 | // Return true to allow the item to be reordered in the list by the user. 116 | return true 117 | } 118 | 119 | func widgetList(_ list: NCWidgetListViewController, didReorderRow row: Int, toRow newIndex: Int) { 120 | // The user has reordered an item in the list. 121 | } 122 | 123 | func widgetList(_ list: NCWidgetListViewController, shouldRemoveRow row: Int) -> Bool { 124 | // Return true to allow the item to be removed from the list by the user. 125 | return true 126 | } 127 | 128 | func widgetList(_ list: NCWidgetListViewController, didRemoveRow row: Int) { 129 | // The user has removed an item from the list. 130 | } 131 | 132 | // MARK: - NCWidgetSearchViewDelegate 133 | 134 | func widgetSearch(_ searchController: NCWidgetSearchViewController, 135 | searchForTerm searchTerm: String, maxResults max: Int) { 136 | // The user has entered a search term. 137 | // Set the controller's searchResults property to the matching items. 138 | searchController.searchResults = [] 139 | } 140 | 141 | func widgetSearchTermCleared(_ searchController: NCWidgetSearchViewController) { 142 | // The user has cleared the search field. Remove the search results. 143 | searchController.searchResults = nil 144 | } 145 | 146 | func widgetSearch(_ searchController: NCWidgetSearchViewController, resultSelected object: Any) { 147 | // The user has selected a search result from the list. 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /AppleJuiceToday/TodayViewController.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 | -------------------------------------------------------------------------------- /AppleJuiceToday/de.lproj/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Apple Juice 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSExtension 26 | 27 | NSExtensionAttributes 28 | 29 | NSExtensionPointVersion 30 | 2.0 31 | 32 | NSExtensionPointIdentifier 33 | com.apple.widget-extension 34 | NSExtensionPrincipalClass 35 | $(PRODUCT_MODULE_NAME).TodayViewController 36 | com.apple.notificationcenter.widget.description 37 | Apple Juice Widget 38 | 39 | NSHumanReadableCopyright 40 | Copyright © 2016 Raphael Hanneken. All rights reserved. 41 | 42 | 43 | -------------------------------------------------------------------------------- /AppleJuiceToday/en.lproj/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Apple Juice 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSExtension 26 | 27 | NSExtensionAttributes 28 | 29 | NSExtensionPointVersion 30 | 2.0 31 | 32 | NSExtensionPointIdentifier 33 | com.apple.widget-extension 34 | NSExtensionPrincipalClass 35 | $(PRODUCT_MODULE_NAME).TodayViewController 36 | com.apple.notificationcenter.widget.description 37 | Apple Juice Widget 38 | 39 | NSHumanReadableCopyright 40 | Copyright © 2016 Raphael Hanneken. All rights reserved. 41 | 42 | 43 | -------------------------------------------------------------------------------- /AppleJuiceToday/es.lproj/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Apple Juice 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSExtension 26 | 27 | NSExtensionAttributes 28 | 29 | NSExtensionPointVersion 30 | 2.0 31 | 32 | NSExtensionPointIdentifier 33 | com.apple.widget-extension 34 | NSExtensionPrincipalClass 35 | $(PRODUCT_MODULE_NAME).TodayViewController 36 | com.apple.notificationcenter.widget.description 37 | Apple Juice Widget 38 | 39 | NSHumanReadableCopyright 40 | Copyright © 2016 Raphael Hanneken. All rights reserved. 41 | 42 | 43 | -------------------------------------------------------------------------------- /AppleJuiceToday/fr.lproj/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Apple Juice 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSExtension 26 | 27 | NSExtensionAttributes 28 | 29 | NSExtensionPointVersion 30 | 2.0 31 | 32 | NSExtensionPointIdentifier 33 | com.apple.widget-extension 34 | NSExtensionPrincipalClass 35 | $(PRODUCT_MODULE_NAME).TodayViewController 36 | com.apple.notificationcenter.widget.description 37 | Apple Juice Widget 38 | 39 | NSHumanReadableCopyright 40 | Copyright © 2016 Raphael Hanneken. All rights reserved. 41 | 42 | 43 | -------------------------------------------------------------------------------- /AppleJuiceToday/it.lproj/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Apple Juice 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSExtension 26 | 27 | NSExtensionAttributes 28 | 29 | NSExtensionPointVersion 30 | 2.0 31 | 32 | NSExtensionPointIdentifier 33 | com.apple.widget-extension 34 | NSExtensionPrincipalClass 35 | $(PRODUCT_MODULE_NAME).TodayViewController 36 | com.apple.notificationcenter.widget.description 37 | Apple Juice Widget 38 | 39 | NSHumanReadableCopyright 40 | Copyright © 2016 Raphael Hanneken. All rights reserved. 41 | 42 | 43 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "sparkle-project/sparkle" 2 | github "sindresorhus/LaunchAtLogin" 3 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "sindresorhus/LaunchAtLogin" "v4.0.0" 2 | github "sparkle-project/sparkle" "1.24.0" 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Raphael Hanneken 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Apple Juice application icon 3 |

4 | 5 | # Apple Juice # 6 | An advanced battery gauge for macOS. *Apple Juice* can show you the estimated __battery time remaining__ and notify you, when your battery charge hits certain percentages. 7 | 8 | ![Apple Juice Appmenu](screenshot_appmenu.png) 9 | ![Apple Juice Notifications](screenshot_notifications.png) 10 | 11 | ### Today Widget ### 12 | 13 | You can get even more information about your current battery status, without cluttering your screen, with the *Apple Juice Today Widget*. Just take a quick glance at your battery’s stats, whenever you want. 14 | 15 | ![Apple Juice Today Widget](screenshot_today.png) 16 | 17 | # How do I install it? # 18 | 19 | * Install *Apple Juice* from the published binary, by downloading the [latest release](https://github.com/raphaelhanneken/apple-juice/releases/latest) and dropping it into your Applications folder. 20 | 21 | * Install *Apple Juice* using [Homebrew](https://brew.sh/), using the command `brew install --cask apple-juice` 22 | 23 | * You can also download the source code and build it yourself. You'll have to have [Carthage](https://github.com/Carthage/Carthage) installed, and run `carthage bootstrap`, inside the project folder, to pull in the required dependencies. 24 | 25 | ## ATTENTION ## 26 | __Loosen Gate Keeper Restrictions__ 27 | > As I'm not paying for an Apple Developer Account, you have to allow unsigned third party apps within the system preferences, to run *Apple Juice*. To allow unsigned apps choose `Anywhere` under `System Preferences: Security: Allow apps downloaded from`. If you don't have the option to select `Anywhere` you'll have to loosen the Gate Keeper restrictions by running `sudo spctl --master-disable` in the Terminal. [OSXDaily](http://osxdaily.com/2016/09/27/allow-apps-from-anywhere-macos-gatekeeper/) have a great step by step guide. Afterwards you should be able to select Anywhere. 28 | 29 | __Remove Quarantine Attributes__ 30 | > Alternatively you can remove the quarantine attribute from the downloaded application package, as suggested by [henrycodebreaker](https://github.com/henrycodebreaker) in [issue #18](https://github.com/raphaelhanneken/apple-juice/issues/18). By executing the following command inside your Terminal: `xattr -cr /path/to/Apple\ Juice.app`. 31 | 32 | # Why does this project exist? # 33 | There are plenty of other solutions out there, so why make another one? I wanted an app that looks like it’s part of the system. As if it were built directly into macOS. Which can show me a lot of information, but only when I need them. And, most importantly, it should display notifications for several percentages. Since I haven’t found such an app, I made one myself. 34 | 35 | # How do I contribute? # 36 | You can fork this project, make your changes and send me a pull request. Make sure [SwiftLint](https://github.com/realm/SwiftLint) succeeds and everything is translated before submitting your pull request. Or, since the whole source code is licensed under the MIT License, fork *Apple Juice* and make your own thing. :-) 37 | 38 | __________ 39 | 40 | # License # 41 | The MIT License (MIT) 42 | 43 | Copyright (c) 2015 - 2020 Raphael Hanneken 44 | 45 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 46 | 47 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 48 | 49 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 50 | -------------------------------------------------------------------------------- /applejuice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelhanneken/apple-juice/bb0533459846225e654ce5bb9393602f644d9628/applejuice.png -------------------------------------------------------------------------------- /screenshot_appmenu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelhanneken/apple-juice/bb0533459846225e654ce5bb9393602f644d9628/screenshot_appmenu.png -------------------------------------------------------------------------------- /screenshot_notifications.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelhanneken/apple-juice/bb0533459846225e654ce5bb9393602f644d9628/screenshot_notifications.png -------------------------------------------------------------------------------- /screenshot_today.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelhanneken/apple-juice/bb0533459846225e654ce5bb9393602f644d9628/screenshot_today.png --------------------------------------------------------------------------------