From 069382c11999e9e40c97b8a5edb4624663c1896c Mon Sep 17 00:00:00 2001 From: "louis.pontoise" Date: Tue, 11 Feb 2020 23:08:39 +0900 Subject: [PATCH] feat: integrate sparkle for auto-updates (closes #131) --- .travis.yml | 4 +- Podfile | 2 +- Podfile.lock | 8 +- README.md | 5 +- alt-tab-macos.xcodeproj/project.pbxproj | 12 +++ alt-tab-macos/Info.plist | 6 ++ alt-tab-macos/logic/Preferences.swift | 80 +++++++++---------- alt-tab-macos/logic/Screen.swift | 4 +- alt-tab-macos/resources/MainMenu.xib | 8 +- alt-tab-macos/ui/App.swift | 5 ++ alt-tab-macos/ui/FeedbackWindow.swift | 4 +- alt-tab-macos/ui/Menubar.swift | 8 +- .../PreferencesWindow.swift | 1 + .../ui/preferences-window/tabs/AboutTab.swift | 9 ++- .../tabs/ShortcutsTab.swift | 2 +- .../preferences-window/tabs/UpdatesTab.swift | 69 ++++++++++++++++ appcast.xml | 28 +++++++ ci/build_release.sh | 2 +- ci/determine_version.sh | 2 + ci/update_appcast.sh | 31 +++++++ package-lock.json | 12 ++- package.json | 1 + release.config.js | 5 +- 23 files changed, 241 insertions(+), 67 deletions(-) create mode 100644 alt-tab-macos/ui/preferences-window/tabs/UpdatesTab.swift create mode 100644 appcast.xml create mode 100755 ci/update_appcast.sh diff --git a/.travis.yml b/.travis.yml index f1bbd5547..21538483b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,8 @@ script: - if [ $IS_RELEASE ]; then ci/set_version_in_app.sh; fi - ci/build_release.sh - if [ $IS_RELEASE ]; then ci/package_release.sh; fi + - if [ $IS_RELEASE ]; then ci/update_appcast.sh; fi + - if [ $IS_RELEASE ]; then ci/update_homebrew_cask.sh; fi - if [ $IS_RELEASE ]; then npx semantic-release; fi deploy: provider: releases @@ -26,8 +28,6 @@ deploy: cleanup: false on: repo: lwouis/alt-tab-macos -after_deploy: - - ci/update_homebrew_cask.sh branches: except: - "/^v\\d+\\.\\d+\\.\\d+$/" diff --git a/Podfile b/Podfile index d49320c06..de4594bd6 100644 --- a/Podfile +++ b/Podfile @@ -3,6 +3,6 @@ platform :osx, '10.12' target 'alt-tab-macos' do use_frameworks! pod 'LetsMove', '1.24' - pod 'Sparkle', '1.22.0' + pod 'Sparkle', '1.23.0' end diff --git a/Podfile.lock b/Podfile.lock index 43d2fdc37..8ee8bfa77 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,10 +1,10 @@ PODS: - LetsMove (1.24) - - Sparkle (1.22.0) + - Sparkle (1.23.0) DEPENDENCIES: - LetsMove (= 1.24) - - Sparkle (= 1.22.0) + - Sparkle (= 1.23.0) SPEC REPOS: trunk: @@ -13,8 +13,8 @@ SPEC REPOS: SPEC CHECKSUMS: LetsMove: fefe56bc7bc7fb7d37049e28a14f297961229fc5 - Sparkle: 593ac2e677c07bcb6c3b22d621240e7cbedaab57 + Sparkle: 55b1a87ba69d56913375a281546b7c82dec95bb0 -PODFILE CHECKSUM: d7356c4f4fea94deb26f1654c141fcd800355daa +PODFILE CHECKSUM: 435b7bc84413df100dee2cabc99746bf7c670f1b COCOAPODS: 1.8.4 diff --git a/README.md b/README.md index 2b0035123..8dbf60c50 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,10 @@ Before building my own app, I looked around at similar apps. However, none was c ## Building the project locally -This project has minimal dependency on Xcode-only features (e.g. InterfaceBuilder, Playgrounds). You can build it using 1 command `xcodebuild`. +This project has minimal dependency on Xcode-only features (e.g. InterfaceBuilder, Playgrounds). You can build it using 2 commands: + +* `pod install` to fetch the dependencies with [CocoaPods](https://cocoapods.org/) +* `xcodebuild -workspace alt-tab-macos.xcworkspace -scheme Release` to build the .app Note that on debug builds, to avoid having to re-check the `System Preferences > Security & Privacy` permissions on every build, we use a code-signing certificate. You can generate one on your local machine in one step by running `ci/generate_debug_certificate.sh`. diff --git a/alt-tab-macos.xcodeproj/project.pbxproj b/alt-tab-macos.xcodeproj/project.pbxproj index 5b002f029..adaee9e77 100644 --- a/alt-tab-macos.xcodeproj/project.pbxproj +++ b/alt-tab-macos.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ D04BA2378832FD7E5DE3BC23 /* Applications.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA66B5B4143D2238F50B9 /* Applications.swift */; }; D04BA26B4E9B4378FA7995DF /* HyperlinkLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA2D2BCBA4C47E25315AF /* HyperlinkLabel.swift */; }; D04BA278D9EFA568C8D18A4C /* Windows.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAD1BED44EAEB77FED8A4 /* Windows.swift */; }; + D04BA29A372E8A644273E7B3 /* update_appcast.sh in Resources */ = {isa = PBXBuildFile; fileRef = D04BA0AAAE82C72855DBBA26 /* update_appcast.sh */; }; D04BA2CBF0EFA04CC80EC1BC /* Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAE80772D25834E440975 /* Window.swift */; }; D04BA308162F8043F8561D03 /* AXUIElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA40A4291E4F310527DBF /* AXUIElement.swift */; }; D04BA30F92801F5960ACC844 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = D04BA7A48641612933710091 /* MainMenu.xib */; }; @@ -37,10 +38,12 @@ D04BA744F626B2E89331390B /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BA10F57DA97A1D0320122 /* InfoPlist.strings */; }; D04BA79B891E9C89C015D6DD /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BAEE5AEEB8692856B45E4 /* InfoPlist.strings */; }; D04BA7F212CDB1B7E101D7A3 /* GridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAFE3AC6E14F394956586 /* GridView.swift */; }; + D04BA826A1745BCC7E8C7B26 /* appcast.xml in Resources */ = {isa = PBXBuildFile; fileRef = D04BAAF760E3A8A22BDA84D6 /* appcast.xml */; }; D04BA8373D4DE452C0C081ED /* SF-Pro-Text-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = D04BABC654F40BE74DA25BC7 /* SF-Pro-Text-Regular.otf */; }; D04BA84074E5FD6221720BC7 /* CollectionViewFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAACB6648E7C2A4E0339D /* CollectionViewFlowLayout.swift */; }; D04BA8EBC0365A019A27C7EA /* Screen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA3F15EAE8D8C39B6F2CF /* Screen.swift */; }; D04BA99DE72CE77BA6CE5A56 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BAF762580D0252489A11A /* InfoPlist.strings */; }; + D04BA9AB730AB1AF4055929D /* UpdatesTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA837A9E0A82D54EF4DB0 /* UpdatesTab.swift */; }; D04BA9CCE02D30C8164A552A /* SystemPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA2D2AD6B1CCA3F3A4DD7 /* SystemPermissions.swift */; }; D04BA9EE5D34A2789DCB0EE2 /* Sysctl.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA896E37EFD27CAB61DF0 /* Sysctl.swift */; }; D04BAA0F39E6160F5703FC33 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BAC0D42D7FED725746DA5 /* Localizable.strings */; }; @@ -69,6 +72,7 @@ BF12DEA89785CA78B0FE2706 /* Pods-alt-tab-macos.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-alt-tab-macos.debug.xcconfig"; path = "Target Support Files/Pods-alt-tab-macos/Pods-alt-tab-macos.debug.xcconfig"; sourceTree = ""; }; C0712B3BEA2B3780398C0999 /* Pods_alt_tab_macos.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_alt_tab_macos.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D04BA05F3F36A6D71DCD8475 /* en */ = {isa = PBXFileReference; fileEncoding = 2483028224; lastKnownFileType = text.plist.strings; name = en; path = Localizable.strings; sourceTree = ""; }; + D04BA0AAAE82C72855DBBA26 /* update_appcast.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = update_appcast.sh; sourceTree = ""; }; D04BA0CE87BE264C52987ED1 /* 7 windows - 2 lines - wide window.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "7 windows - 2 lines - wide window.jpg"; sourceTree = ""; }; D04BA0E071D2EDFDB9A20523 /* Podfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Podfile; sourceTree = ""; }; D04BA0E1C5DBC07108AC2F54 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; @@ -112,6 +116,7 @@ D04BA8011143819B48F204C2 /* TextArea.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextArea.swift; sourceTree = ""; }; D04BA81CA2D9818FCA9E5024 /* fr */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = fr; path = InfoPlist.strings; sourceTree = ""; }; D04BA82F792DF53958D92572 /* alt-tab-macos.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "alt-tab-macos.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + D04BA837A9E0A82D54EF4DB0 /* UpdatesTab.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatesTab.swift; sourceTree = ""; }; D04BA896E37EFD27CAB61DF0 /* Sysctl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sysctl.swift; sourceTree = ""; }; D04BA89FAEC4A5734D892C4B /* build_release.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = build_release.sh; sourceTree = ""; }; D04BA8BEE821E2062F23AA97 /* CollectionViewItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewItem.swift; sourceTree = ""; }; @@ -122,6 +127,7 @@ D04BAA34E0CB00DED7C04B4F /* 2-rows.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "2-rows.jpg"; sourceTree = ""; }; D04BAA44C837F3A67403B9DB /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; D04BAACB6648E7C2A4E0339D /* CollectionViewFlowLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewFlowLayout.swift; sourceTree = ""; }; + D04BAAF760E3A8A22BDA84D6 /* appcast.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = appcast.xml; sourceTree = ""; }; D04BAB6652494D7575057E86 /* 14 windows - 3 lines.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "14 windows - 3 lines.jpg"; sourceTree = ""; }; D04BABC180117F8785D250E1 /* TextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; }; D04BABC654F40BE74DA25BC7 /* SF-Pro-Text-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file.otf; path = "SF-Pro-Text-Regular.otf"; sourceTree = ""; }; @@ -220,6 +226,7 @@ 4A443501F57D4759B190C07E /* Pods */, D04BA2A4F257F4DCE1421758 /* Podfile.lock */, D04BA0E071D2EDFDB9A20523 /* Podfile */, + D04BAAF760E3A8A22BDA84D6 /* appcast.xml */, ); sourceTree = ""; }; @@ -324,6 +331,7 @@ D04BAE93A5854C501639C640 /* update_homebrew_cask.sh */, D04BA5E819181CB83C5602C7 /* generate_debug_certificate.sh */, D04BA89FAEC4A5734D892C4B /* build_release.sh */, + D04BA0AAAE82C72855DBBA26 /* update_appcast.sh */, ); path = ci; sourceTree = ""; @@ -352,6 +360,7 @@ D04BACD85D3966B4C9482E52 /* AppearanceTab.swift */, D04BAE23C37E0F3B07EEE7B1 /* AboutTab.swift */, D04BABD0C7A6DBA235C650A5 /* ShortcutsTab.swift */, + D04BA837A9E0A82D54EF4DB0 /* UpdatesTab.swift */, ); path = tabs; sourceTree = ""; @@ -539,6 +548,8 @@ D04BA3AE19F48237BDCDDA4F /* Localizable.strings in Resources */, D04BA3D87405EE52518CCC3E /* Podfile.lock in Resources */, D04BAE4A31689CCF132372B7 /* Podfile in Resources */, + D04BA29A372E8A644273E7B3 /* update_appcast.sh in Resources */, + D04BA826A1745BCC7E8C7B26 /* appcast.xml in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -636,6 +647,7 @@ D04BA9EE5D34A2789DCB0EE2 /* Sysctl.swift in Sources */, D04BAC4F69FE9563BC1C5E9C /* DebugProfile.swift in Sources */, D04BAE4CE37C303DDD0347B8 /* CollectionViewItemView.swift in Sources */, + D04BA9AB730AB1AF4055929D /* UpdatesTab.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/alt-tab-macos/Info.plist b/alt-tab-macos/Info.plist index de7595e8d..bbd647a62 100644 --- a/alt-tab-macos/Info.plist +++ b/alt-tab-macos/Info.plist @@ -32,5 +32,11 @@ NSSupportsSuddenTermination true + SUFeedURL + https://raw.githubusercontent.com/lwouis/alt-tab-macos/v3-poc/appcast.xml + SUEnableAutomaticChecks + true + SUPublicEDKey + 2e9SQOBoaKElchSa/4QDli/nvYkyuDNfynfzBF6vJK4= diff --git a/alt-tab-macos/logic/Preferences.swift b/alt-tab-macos/logic/Preferences.swift index cfdd3a060..f4e7dc4c4 100644 --- a/alt-tab-macos/logic/Preferences.swift +++ b/alt-tab-macos/logic/Preferences.swift @@ -20,8 +20,8 @@ class Preferences { MacroPreference("⌘ command", ([kVK_Command, kVK_RightCommand], .command)) ]) static let showOnScreenMacro = MacroPreferenceHelper([ - MacroPreference("Main screen", ShowOnScreenPreference.MAIN), - MacroPreference("Screen including mouse", ShowOnScreenPreference.MOUSE), + MacroPreference("Main screen", ShowOnScreenPreference.main), + MacroPreference("Screen including mouse", ShowOnScreenPreference.mouse), ]) static var defaults: [String: String] = [ @@ -36,7 +36,7 @@ class Preferences { "windowDisplayDelay": "0", "theme": themeMacro.macros[0].label, "showOnScreen": showOnScreenMacro.macros[0].label, - "hideSpaceNumberLabels": String(false) + "hideSpaceNumberLabels": String(false), ] static var rawValues = [String: String]() @@ -89,41 +89,41 @@ class Preferences { static func updateAndValidateFromString(_ valueName: String, _ value: String) throws { switch valueName { - case "maxScreenUsage": - maxScreenUsage = try CGFloat(CGFloat(value).orThrow() / 100) - case "minCellsPerRow": - minCellsPerRow = try CGFloat(value).orThrow() - case "maxCellsPerRow": - maxCellsPerRow = try CGFloat(value).orThrow() - case "minRows": - minRows = try CGFloat(value).orThrow() - case "iconSize": - iconSize = try CGFloat(value).orThrow() - case "fontHeight": - fontHeight = try CGFloat(value).orThrow() - font = NSFont.systemFont(ofSize: fontHeight) - case "tabKeyCode": - tabKeyCode = try UInt16(value).orThrow() - case "metaKey": - let p = try metaKeyMacro.labelToMacro[value].orThrow() - metaKeyCodes = p.preferences.0.map { UInt16($0) } - metaModifierFlag = p.preferences.1 - case "theme": - let p = try themeMacro.labelToMacro[value].orThrow() - cellBorderWidth = p.preferences.0 - cellCornerRadius = p.preferences.1 - windowCornerRadius = p.preferences.2 - highlightBorderColor = p.preferences.3 - highlightBackgroundColor = p.preferences.4 - case "windowDisplayDelay": - windowDisplayDelay = DispatchTimeInterval.milliseconds(try Int(value).orThrow()) - case "showOnScreen": - let p = try showOnScreenMacro.labelToMacro[value].orThrow() - showOnScreen = p.preferences - case "hideSpaceNumberLabels": - hideSpaceNumberLabels = try Bool(value).orThrow() - default: - throw NSError.make(domain: "Preferences", message: "Tried to update an unknown preference: '\(valueName)' = '\(value)'") + case "maxScreenUsage": + maxScreenUsage = try CGFloat(CGFloat(value).orThrow() / 100) + case "minCellsPerRow": + minCellsPerRow = try CGFloat(value).orThrow() + case "maxCellsPerRow": + maxCellsPerRow = try CGFloat(value).orThrow() + case "minRows": + minRows = try CGFloat(value).orThrow() + case "iconSize": + iconSize = try CGFloat(value).orThrow() + case "fontHeight": + fontHeight = try CGFloat(value).orThrow() + font = NSFont.systemFont(ofSize: fontHeight) + case "tabKeyCode": + tabKeyCode = try UInt16(value).orThrow() + case "metaKey": + let p = try metaKeyMacro.labelToMacro[value].orThrow() + metaKeyCodes = p.preferences.0.map { UInt16($0) } + metaModifierFlag = p.preferences.1 + case "theme": + let p = try themeMacro.labelToMacro[value].orThrow() + cellBorderWidth = p.preferences.0 + cellCornerRadius = p.preferences.1 + windowCornerRadius = p.preferences.2 + highlightBorderColor = p.preferences.3 + highlightBackgroundColor = p.preferences.4 + case "windowDisplayDelay": + windowDisplayDelay = DispatchTimeInterval.milliseconds(try Int(value).orThrow()) + case "showOnScreen": + let p = try showOnScreenMacro.labelToMacro[value].orThrow() + showOnScreen = p.preferences + case "hideSpaceNumberLabels": + hideSpaceNumberLabels = try Bool(value).orThrow() + default: + throw NSError.make(domain: "Preferences", message: "Tried to update an unknown preference: '\(valueName)' = '\(value)'") } rawValues[valueName] = value } @@ -188,6 +188,6 @@ class MacroPreferenceHelper { } enum ShowOnScreenPreference { - case MAIN - case MOUSE + case main + case mouse } diff --git a/alt-tab-macos/logic/Screen.swift b/alt-tab-macos/logic/Screen.swift index ab18bd404..a3ab74669 100644 --- a/alt-tab-macos/logic/Screen.swift +++ b/alt-tab-macos/logic/Screen.swift @@ -3,8 +3,8 @@ import Cocoa class Screen { static func preferred() -> NSScreen { switch Preferences.showOnScreen! { - case .MOUSE: return withMouse() ?? NSScreen.main!; // .main as fall-back - case .MAIN: return NSScreen.main!; + case .mouse: return withMouse() ?? NSScreen.main!; // .main as fall-back + case .main: return NSScreen.main!; } } diff --git a/alt-tab-macos/resources/MainMenu.xib b/alt-tab-macos/resources/MainMenu.xib index 7138adbae..5c2e23806 100644 --- a/alt-tab-macos/resources/MainMenu.xib +++ b/alt-tab-macos/resources/MainMenu.xib @@ -1,7 +1,8 @@ - - + + - + + @@ -669,5 +670,6 @@ + diff --git a/alt-tab-macos/ui/App.swift b/alt-tab-macos/ui/App.swift index ebdb2a334..fa5b89898 100644 --- a/alt-tab-macos/ui/App.swift +++ b/alt-tab-macos/ui/App.swift @@ -70,6 +70,11 @@ class App: NSApplication, NSApplicationDelegate, NSWindowDelegate { } } + @objc + func checkForUpdatesNow(_ sender: NSMenuItem) { + UpdatesTab.checkForUpdatesNow(sender) + } + @objc func showPreferencesPanel() { if preferencesWindow == nil { diff --git a/alt-tab-macos/ui/FeedbackWindow.swift b/alt-tab-macos/ui/FeedbackWindow.swift index 894e8841e..f7504ad15 100644 --- a/alt-tab-macos/ui/FeedbackWindow.swift +++ b/alt-tab-macos/ui/FeedbackWindow.swift @@ -63,12 +63,12 @@ class FeedbackWindow: NSWindow { } @objc - private func cancelCallback(senderControl: NSControl) { + private func cancelCallback() { close() } @objc - private func sendCallback(senderControl: NSControl) { + private func sendCallback() { URLSession.shared.dataTask(with: prepareRequest(), completionHandler: { data, response, error in if error != nil || response == nil || (response as! HTTPURLResponse).statusCode != 201 { debugPrint("HTTP call failed:", response ?? "nil", error ?? "nil") diff --git a/alt-tab-macos/ui/Menubar.swift b/alt-tab-macos/ui/Menubar.swift index 3aaf2590c..27c833070 100644 --- a/alt-tab-macos/ui/Menubar.swift +++ b/alt-tab-macos/ui/Menubar.swift @@ -8,16 +8,20 @@ class Menubar { item.menu!.addItem( withTitle: NSLocalizedString("Show", comment: ""), action: #selector(app.showUi), - keyEquivalent: "s" + keyEquivalent: "" ) item.menu!.addItem( withTitle: NSLocalizedString("Preferences…", comment: ""), action: #selector(app.showPreferencesPanel), keyEquivalent: ",") + item.menu!.addItem( + withTitle: NSLocalizedString("Check for updates…", comment: ""), + action: #selector(app.checkForUpdatesNow), + keyEquivalent: "") item.menu!.addItem( withTitle: NSLocalizedString("Send feedback…", comment: ""), action: #selector(app.showFeedbackPanel), - keyEquivalent: ",") + keyEquivalent: "") item.menu!.addItem(NSMenuItem.separator()) item.menu!.addItem( withTitle: NSLocalizedString("Quit", comment: "") + " " + App.name, diff --git a/alt-tab-macos/ui/preferences-window/PreferencesWindow.swift b/alt-tab-macos/ui/preferences-window/PreferencesWindow.swift index 626ee746a..969e75f7e 100644 --- a/alt-tab-macos/ui/preferences-window/PreferencesWindow.swift +++ b/alt-tab-macos/ui/preferences-window/PreferencesWindow.swift @@ -53,6 +53,7 @@ class PreferencesWindow: NSWindow { tabViewController.tabStyle = .toolbar tabViewController.addTabViewItem(ShortcutsTab.make()) tabViewController.addTabViewItem(AppearanceTab.make()) + tabViewController.addTabViewItem(UpdatesTab.make()) tabViewController.addTabViewItem(AboutTab.make()) } diff --git a/alt-tab-macos/ui/preferences-window/tabs/AboutTab.swift b/alt-tab-macos/ui/preferences-window/tabs/AboutTab.swift index 39c757207..c716ff0e3 100644 --- a/alt-tab-macos/ui/preferences-window/tabs/AboutTab.swift +++ b/alt-tab-macos/ui/preferences-window/tabs/AboutTab.swift @@ -24,16 +24,17 @@ class AboutTab: NSObject { appInfo.spacing = GridView.interPadding let view = GridView.make([ [appInfo], - [NSButton(title: NSLocalizedString("Send feedback", comment: ""), target: self, action: #selector(feedbackCallback))], + [NSButton(title: NSLocalizedString("Send feedback…", comment: ""), target: self, action: #selector(feedbackCallback))], ]) - view.row(at: 1).topPadding = GridView.interPadding * 2 - view.cell(atColumnIndex: 0, rowIndex: 1).xPlacement = .center + let sendFeedbackCell = view.cell(atColumnIndex: 0, rowIndex: 1) + sendFeedbackCell.xPlacement = .center + sendFeedbackCell.row!.topPadding = GridView.interPadding view.fit() return view } @objc - static func feedbackCallback(senderControl: NSControl) { + static func feedbackCallback() { (App.shared as! App).showFeedbackPanel() } } diff --git a/alt-tab-macos/ui/preferences-window/tabs/ShortcutsTab.swift b/alt-tab-macos/ui/preferences-window/tabs/ShortcutsTab.swift index 401956e62..0d4586636 100644 --- a/alt-tab-macos/ui/preferences-window/tabs/ShortcutsTab.swift +++ b/alt-tab-macos/ui/preferences-window/tabs/ShortcutsTab.swift @@ -1,7 +1,7 @@ import Cocoa class ShortcutsTab { - private static let rowHeight = CGFloat(20) + private static let rowHeight = CGFloat(22) // height of the "Tab key" input static func make() -> NSTabViewItem { return TabViewItem.make(NSLocalizedString("Shortcuts", comment: ""), NSImage.preferencesGeneralName, makeView()) diff --git a/alt-tab-macos/ui/preferences-window/tabs/UpdatesTab.swift b/alt-tab-macos/ui/preferences-window/tabs/UpdatesTab.swift new file mode 100644 index 000000000..1982c9d68 --- /dev/null +++ b/alt-tab-macos/ui/preferences-window/tabs/UpdatesTab.swift @@ -0,0 +1,69 @@ +import Cocoa +import Sparkle + +class UpdatesTab: NSObject { + static var dontPeriodicallyCheck: NSButton! + static var periodicallyCheck: NSButton! + static var periodicallyInstall: NSButton! + static var policyObserver = PolicyObserver() + + static func make() -> NSTabViewItem { + return TabViewItem.make(NSLocalizedString("Updates", comment: ""), NSImage.refreshTemplateName, makeView()) + } + + static func makeView() -> NSGridView { + dontPeriodicallyCheck = NSButton(radioButtonWithTitle: NSLocalizedString("Don't check for updates periodically", comment: ""), target: self, action: #selector(updatePolicyCallback)) + dontPeriodicallyCheck.fit() + periodicallyCheck = NSButton(radioButtonWithTitle: NSLocalizedString("Check for updates periodically", comment: ""), target: self, action: #selector(updatePolicyCallback)) + periodicallyCheck.fit() + periodicallyInstall = NSButton(radioButtonWithTitle: NSLocalizedString("Auto-install updates periodically", comment: ""), target: self, action: #selector(updatePolicyCallback)) + periodicallyInstall.fit() + let policyLabel = NSTextField(wrappingLabelWithString: NSLocalizedString("Updates policy:", comment: "")) + let policies = NSStackView(views: [dontPeriodicallyCheck, periodicallyCheck, periodicallyInstall]) + policies.alignment = .left + policies.orientation = .vertical + policies.spacing = GridView.interPadding / 2 + observePolicy() + let view = GridView.make([ + [policyLabel, policies], + [NSButton(title: NSLocalizedString("Check for updates now…", comment: ""), target: self, action: #selector(checkForUpdatesNow))], + ]) + view.cell(atColumnIndex: 0, rowIndex: 0).xPlacement = .trailing + let row1 = view.row(at: 1) + row1.mergeCells(in: NSRange(location: 0, length: 2)) + row1.topPadding = GridView.interPadding + row1.cell(at: 0).xPlacement = .center + view.fit() + return view + } + + private static func observePolicy() { + UserDefaults.standard.addObserver(UpdatesTab.policyObserver, forKeyPath: "SUAutomaticallyUpdate", options: [.initial, .new], context: nil) + UserDefaults.standard.addObserver(UpdatesTab.policyObserver, forKeyPath: "SUEnableAutomaticChecks", options: [.initial, .new], context: nil) + } + + @objc + static func checkForUpdatesNow(_ sender: Any) { + SUUpdater.shared().checkForUpdates(sender) + } + + @objc + static func updatePolicyCallback() { + SUUpdater.shared().automaticallyDownloadsUpdates = periodicallyInstall.state == .on + SUUpdater.shared().automaticallyChecksForUpdates = periodicallyInstall.state == .on || periodicallyCheck.state == .on + } +} + +class PolicyObserver: NSObject { + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { + if SUUpdater.shared().automaticallyDownloadsUpdates { + UpdatesTab.periodicallyInstall.state = .on + // Sparkle UI "Automatically download and install updates in the future" doesn't activate periodical checks; we do it manually + SUUpdater.shared().automaticallyChecksForUpdates = true + } else if SUUpdater.shared().automaticallyChecksForUpdates { + UpdatesTab.periodicallyCheck.state = .on + } else { + UpdatesTab.dontPeriodicallyCheck.state = .on + } + } +} diff --git a/appcast.xml b/appcast.xml new file mode 100644 index 000000000..74a8744cb --- /dev/null +++ b/appcast.xml @@ -0,0 +1,28 @@ + + + + alt-tab-macos + en + + + Version 2.3.4 + Wed, 22 Jan 2020 07:56:00 +0900 + 10.12 + 2.3.4 (2020-01-22) +

Bug Fixes

+
    +
  • escape key was absorbed by the inactive app (closes #123) (5260619)
  • +
+ ]]> +
+ +
+ +
+
diff --git a/ci/build_release.sh b/ci/build_release.sh index c909d25f8..6750e20d1 100755 --- a/ci/build_release.sh +++ b/ci/build_release.sh @@ -18,4 +18,4 @@ echo "$APPLE_P12_CERTIFICATE" | base64 --decode > $certificateFile.p12 security import $certificateFile.p12 -P "$APPLE_P12_CERTIFICATE_PASSWORD" -T /usr/bin/codesign security set-key-partition-list -S apple-tool:,apple: -s -k $keychainPassword $keychain # build release .app -xcodebuild CODE_SIGN_IDENTITY="Developer ID Application: Louis Pontoise (QXD7GW8FHY)" +xcodebuild CODE_SIGN_IDENTITY="Developer ID Application: Louis Pontoise (QXD7GW8FHY)" -workspace alt-tab-macos.xcworkspace -scheme Release diff --git a/ci/determine_version.sh b/ci/determine_version.sh index 4242bcb5b..ae4dc0ede 100755 --- a/ci/determine_version.sh +++ b/ci/determine_version.sh @@ -4,5 +4,7 @@ set -exu semanticRelease=$(npx semantic-release --dry-run --ci false) version=$(echo "$semanticRelease" | sed -nE 's/.+The next release version is (.+)/\1/p') +changelogDelta=$(echo "$semanticRelease" | sed -n '/Release note for version/,$p' | sed '1d') echo "$version" > VERSION.txt +echo "$changelogDelta" > CHANGELOG_DELTA.txt diff --git a/ci/update_appcast.sh b/ci/update_appcast.sh new file mode 100755 index 000000000..7a38ee357 --- /dev/null +++ b/ci/update_appcast.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +set -exu + +version="$(cat VERSION.txt)" +changelogDelta="$(npx marked < CHANGELOG_DELTA.txt)" +date="$(date +'%a, %d %b %Y %H:%M:%S %z')" +minimumSystemVersion="$(sed -En 's/MACOSX_DEPLOYMENT_TARGET = (.+);/\1/p' alt-tab-macos.xcodeproj/project.pbxproj | head -n 1 | awk '{$1=$1};1')" +version="$(cat VERSION.txt)" +zipName="AltTab-$version.zip" +edSignatureAndLength=$(Pods/Sparkle/bin/sign_update -s $SPARKLE_ED_PRIVATE_KEY "build/Release/$zipName") + +echo " + + Version $version + $date + $minimumSystemVersion + + + + +" > ITEM.txt + +sed -i '' -e "/<\/language>/r ITEM.txt" appcast.xml diff --git a/package-lock.json b/package-lock.json index 6ae4eaf81..20f1b43c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2380,9 +2380,9 @@ "dev": true }, "marked": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", - "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.8.0.tgz", + "integrity": "sha512-MyUe+T/Pw4TZufHkzAfDj6HarCBWia2y27/bhuYkTaiUnfDYFnCP3KUN+9oM7Wi6JA2rymtVYbQu3spE0GCmxQ==", "dev": true }, "marked-terminal": { @@ -6669,6 +6669,12 @@ "yallist": "^3.0.2" } }, + "marked": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", + "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", + "dev": true + }, "p-limit": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", diff --git a/package.json b/package.json index 572d330a8..5e95a119c 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@semantic-release/changelog": "^3.0.4", "@semantic-release/git": "^7.0.16", "husky": "^3.0.4", + "marked": "^0.8.0", "semantic-release": "^15.13.24" } } diff --git a/release.config.js b/release.config.js index 7a9de8814..8200cedd5 100644 --- a/release.config.js +++ b/release.config.js @@ -40,7 +40,10 @@ module.exports = { '@semantic-release/changelog', [ '@semantic-release/git', { - 'assets': ['CHANGELOG.md'], + 'assets': [ + 'CHANGELOG.md', + 'appcast.xml' + ], }, ], ],