diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 76e552029..f1ae540f7 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -27,17 +27,15 @@ body: id: logs attributes: label: Logs - description: Please provide the logs from the launched Wine process. These can be found by pressing `CMD + L` in Whisky. + description: Please provide the logs from the launched Wine process. These can be found by pressing `CMD + L` in Brandy. render: bash validations: required: true - - type: dropdown - id: whisky-version + - type: input + id: brandy-version attributes: - label: What version of Whisky are you using? - options: - - "2.3.2" - - "<2.3.2" + label: What version of Brandy are you using? + description: Shown in Brandy > About Brandy. validations: required: true - type: dropdown @@ -45,6 +43,8 @@ body: attributes: label: What version of macOS are you using? options: + - "Tahoe (macOS 26)" + - "Sequoia (macOS 15)" - "Sonoma (macOS 14)" validations: required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index bbbeb5e3f..3ba13e0ce 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,4 +1 @@ blank_issues_enabled: false -- name: Discord Support - url: https://discord.gg/K4AQukwAke - about: Please ask for support first to confirm the issue before submitting a report. \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 70d749330..ad87c1ccd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,20 +1,20 @@ # How to contribute -Thanks for your interest! First, make a fork of Whisky, make a new branch for your changes, and get coding! +Thanks for your interest! First, make a fork of Brandy, make a new branch for your changes, and get coding! # Build environment -Whisky is built using Xcode 15 on macOS Sonoma. All external dependencies are handled through the Swift Package Manager. +Brandy is built using Xcode 26 on macOS Sequoia or later. All external dependencies are handled through the Swift Package Manager. # Code style -Every Whisky commit is automatically linted using SwiftLint. You can run these checks locally simply by building in Xcode, violations will appear as errors or warnings. For your pull request to be merged, you must meet all the requirements outlined by SwiftLint and have no violations. +Every Brandy commit is automatically linted using SwiftLint. You can run these checks locally simply by building in Xcode, violations will appear as errors or warnings. For your pull request to be merged, you must meet all the requirements outlined by SwiftLint and have no violations. Generally, it is not advised to disable a SwiftLint rule, but there are certain situations where it is necessary. Please use your discretion when disabling rules temporarily. SwiftLint does not fully check indentation, but we ask that you indent with 4-width spaces. This can be automatically configured in Xcode's settings. -All added strings must be properly localised and added to the EN strings file. Do not add keys for other languages or translate within your PR. All translations should be handled on [Crowdin](https://crowdin.com/project/whisky). +All added strings must be properly localised and added to the English strings in `Whisky/Localizable.xcstrings`. Do not add keys for other languages or translate within your PR. # Making your PR diff --git a/README.md b/README.md index d2910cbf4..ce6dfed00 100644 --- a/README.md +++ b/README.md @@ -1,81 +1,48 @@
- # Whisky 🥃 - *Wine but a bit stronger* - - ![](https://img.shields.io/github/actions/workflow/status/iStorry/Whisky/CI.yml?branch=main&style=for-the-badge) - [![](https://img.shields.io/discord/1115955071549702235?style=for-the-badge)](https://discord.gg/CsqAfs9CnM) -
- -## Maintenance Notice - -[Whisky is no longer actively maintained](https://docs.getwhisky.app/maintenance-notice). Apps and games may break at any time. + Brandy icon -Config + # Brandy -Familiar UI that integrates seamlessly with macOS + *Windows games on your Mac, set up in a few clicks* -
- New Bottle - - One-click bottle creation and management + ![](https://img.shields.io/github/actions/workflow/status/iStorry/Whisky/CI.yml?branch=main&style=for-the-badge)
-debug - -Debug and profile with ease - --- -Whisky provides a clean and easy to use graphical wrapper for Wine built in native SwiftUI. You can make and manage bottles, install and run Windows apps and games, and unlock the full potential of your Mac with no technical knowledge required. Whisky is built on top of CrossOver 22.1.1, and Apple's own `Game Porting Toolkit`. +Brandy is a native SwiftUI app for running Windows games and apps on your Mac, made for people who don't want to learn how Wine works. Open it, click **Set Up Steam**, and Brandy prepares your Mac, downloads its Windows layer, creates a place for your games, and installs Steam from Valve. After that, it's one button: **Open Steam**. -Translated on [Crowdin](https://crowdin.com/project/whisky). +Everything that used to be required is still there for those who want it. Turn on **Show advanced options** in Settings to manage bottles, installed programs, Windows versions, DXVK, Winetricks and Wine's own tools. ---- +Brandy is a fork of [Whisky](https://github.com/Whisky-App/Whisky), which is no longer maintained. Brandy is distilled from wine, just as this app is distilled from Whisky. ## System Requirements + - CPU: Apple Silicon (M-series chips) - OS: macOS Sonoma 14.0 or later +- About 3 GB of free space for the Windows layer and Steam -## Homebrew - -Whisky is on homebrew! Install with -`brew install --cask whisky`. - -## My game isn't working! +## What's Inside -Some games need special steps to get working. Check out the [wiki](https://github.com/IsaacMarovitz/Whisky/wiki/Game-Support). +- **Wine 11.0**, the official WineHQ macOS build by Gcenx +- **DXVK-macOS** for Direct3D 10 and 11 through Vulkan, off by default +- **Windows 10 or 11** for each bottle ---- +The Windows layer is published as the `whiskywine-3` release of this repository. `scripts/build-whiskywine.sh` rebuilds it from pinned, checksum-verified upstream releases. ## Credits & Acknowledgments -Whisky is possible thanks to the magic of several projects: +Brandy exists thanks to: -- [msync](https://github.com/marzent/wine-msync) by marzent -- [DXVK-macOS](https://github.com/Gcenx/DXVK-macOS) by Gcenx and doitsujin +- [Whisky](https://github.com/Whisky-App/Whisky) by Isaac Marovitz and contributors, which Brandy is built on +- [Wine](https://www.winehq.org) by the Wine project and CodeWeavers +- [macOS Wine builds](https://github.com/Gcenx/macOS_Wine_builds) and [DXVK-macOS](https://github.com/Gcenx/DXVK-macOS) by Gcenx and doitsujin - [MoltenVK](https://github.com/KhronosGroup/MoltenVK) by KhronosGroup +- [Winetricks](https://github.com/Winetricks/winetricks) - [Sparkle](https://github.com/sparkle-project/Sparkle) by sparkle-project - [SemanticVersion](https://github.com/SwiftPackageIndex/SemanticVersion) by SwiftPackageIndex - [swift-argument-parser](https://github.com/apple/swift-argument-parser) by Apple -- [SwiftTextTable](https://github.com/scottrhoyt/SwiftyTextTable) by scottrhoyt -- [CrossOver 22.1.1](https://www.codeweavers.com/crossover) by CodeWeavers and WineHQ -- D3DMetal by Apple - -Special thanks to Gcenx, ohaiibuzzle, and Nat Brown for their support and contributions! - ---- +- [SwiftyTextTable](https://github.com/scottrhoyt/SwiftyTextTable) by scottrhoyt - - - - - -
- - - - - - Whisky doesn't exist without CrossOver. Support the work of CodeWeavers using our affiliate link. -
+Brandy is free software under the [GNU General Public License v3](LICENSE). diff --git a/Whisky.xcodeproj/project.pbxproj b/Whisky.xcodeproj/project.pbxproj index bac6cee2a..37a67e0e3 100644 --- a/Whisky.xcodeproj/project.pbxproj +++ b/Whisky.xcodeproj/project.pbxproj @@ -10,7 +10,6 @@ 6330DD962B1B0EA4007A625A /* RenameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6330DD952B1B0EA4007A625A /* RenameView.swift */; }; 6365C4C12B1AA69D00AAE1FD /* Animation+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6365C4C02B1AA69D00AAE1FD /* Animation+Extensions.swift */; }; 6365C4C32B1AA8CD00AAE1FD /* BottleListEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6365C4C22B1AA8CD00AAE1FD /* BottleListEntry.swift */; }; - 63FFDE862ADF0C7700178665 /* BottomBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63FFDE852ADF0C7700178665 /* BottomBar.swift */; }; 6763D8F62BC6314100651D27 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6763D8F52BC6314100651D27 /* Constants.swift */; }; 67D278512BC5907E006F9A1E /* ActionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67D278502BC5907E006F9A1E /* ActionView.swift */; }; 6E064B1229DD32A200D9A2D2 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 6E064B1129DD32A200D9A2D2 /* Sparkle */; }; @@ -35,11 +34,6 @@ 6E59676B2A9A424900D64F70 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = 6E59676A2A9A424900D64F70 /* ArgumentParser */; }; 6E5B61B62AC34E6D007B1913 /* SemanticVersion in Frameworks */ = {isa = PBXBuildFile; productRef = 6E5B61B52AC34E6D007B1913 /* SemanticVersion */; }; 6E621CEF2A5F631300C9AAB3 /* Winetricks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E621CEE2A5F631200C9AAB3 /* Winetricks.swift */; }; - 6E6C0CF22A419A6800356232 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6C0CF12A419A6800356232 /* WelcomeView.swift */; }; - 6E7A1F012F00000000000001 /* SetupFailureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7A1F012F00000000000002 /* SetupFailureView.swift */; }; - 6E6C0CF42A419A7600356232 /* RosettaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6C0CF32A419A7600356232 /* RosettaView.swift */; }; - 6E6C0CF62A419A8300356232 /* WhiskyWineDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6C0CF52A419A8300356232 /* WhiskyWineDownloadView.swift */; }; - 6E6C0CF82A419A8C00356232 /* WhiskyWineInstallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6C0CF72A419A8C00356232 /* WhiskyWineInstallView.swift */; }; 6E70A49B2A9A2197007799E9 /* Main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E70A49A2A9A2197007799E9 /* Main.swift */; }; 6E70A4A12A9A280C007799E9 /* WhiskyCmd.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E70A4A02A9A280C007799E9 /* WhiskyCmd.swift */; }; 6E7C07BE2AAE7B0100F6E66B /* ProgramShortcut.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7C07BD2AAE7B0100F6E66B /* ProgramShortcut.swift */; }; @@ -48,7 +42,6 @@ 6E95F6702AB3F33C00D585D1 /* SwiftyTextTable in Frameworks */ = {isa = PBXBuildFile; productRef = 6E95F66F2AB3F33C00D585D1 /* SwiftyTextTable */; }; 6E95F6732AB3F67200D585D1 /* Progress in Frameworks */ = {isa = PBXBuildFile; productRef = 6E95F6722AB3F67200D585D1 /* Progress */; }; 6EB58AC92AEACE5E00B1ED3F /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 6EB58AC82AEACE5E00B1ED3F /* Localizable.xcstrings */; }; - 6EF557982A410599001A4F09 /* SetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EF557972A410599001A4F09 /* SetupView.swift */; }; 6EFDF64C2AAE2DA800EF622F /* QuickLookThumbnailing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EFDF64B2AAE2DA800EF622F /* QuickLookThumbnailing.framework */; }; 6EFDF64E2AAE2DA800EF622F /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EFDF64D2AAE2DA800EF622F /* Quartz.framework */; }; 6EFDF6512AAE2DA800EF622F /* ThumbnailProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFDF6502AAE2DA800EF622F /* ThumbnailProvider.swift */; }; @@ -59,10 +52,18 @@ 8C73E1342AF472FC00B6FB45 /* ProgramMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C73E1332AF472FC00B6FB45 /* ProgramMenuView.swift */; }; 8CB681E52AED7C6F0018D319 /* WhiskyKit in Resources */ = {isa = PBXBuildFile; fileRef = 8CB681E42AED7C6F0018D319 /* WhiskyKit */; }; 8CB681E72AED7CD00018D319 /* WhiskyKit in Frameworks */ = {isa = PBXBuildFile; productRef = 8CB681E62AED7CD00018D319 /* WhiskyKit */; }; - AB0BCABD2B61036E00E21C31 /* RunningProcessesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB0BCABC2B61036E00E21C31 /* RunningProcessesView.swift */; }; DB696FC82AFAE5DA0037EB2F /* PinCreationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB696FC72AFAE5DA0037EB2F /* PinCreationView.swift */; }; EB58FB552A499896002DC184 /* SemanticVersion in Frameworks */ = {isa = PBXBuildFile; productRef = EB58FB542A499896002DC184 /* SemanticVersion */; }; EEA5A2462A31DD65008274AE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA5A2452A31DD65008274AE /* AppDelegate.swift */; }; + 7AC35FF857263758125C7A31 /* ProgramIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3FC8B99010FD02742231AE /* ProgramIconView.swift */; }; + 7A4C2D88025658F76A141654 /* Program+Display.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A93F021F55DAD2BC6722D54 /* Program+Display.swift */; }; + 7ABF25C1A893C7716A1C83DB /* PinnedProgramsGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A110F88A1199B4E9C0C8F3A /* PinnedProgramsGrid.swift */; }; + 7A69955CE36D4C4767FE307A /* OnboardingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A30D4DC6FC022EF1446508D /* OnboardingModel.swift */; }; + 7A3FA122329C4A8846CA0588 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7B1B81885C080827CC7B0F /* OnboardingView.swift */; }; + 7A43CB6C325CAA981E94DDE7 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A53D33606FF388802121151 /* HomeView.swift */; }; + 7A14B0D5D96E14098322DFC0 /* WhiskyWineUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3E5CD4F34E72EE880BF131 /* WhiskyWineUpdate.swift */; }; + 7AA2C89FB1934624B6FFA190 /* SteamSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ACB6C31E47142A637015EA4 /* SteamSession.swift */; }; + 7A5E34BEDDA87936C50FB0A2 /* ErrorAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5CAFC811A7E1E60978D49E /* ErrorAlert.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -114,7 +115,6 @@ 6330DD952B1B0EA4007A625A /* RenameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenameView.swift; sourceTree = ""; }; 6365C4C02B1AA69D00AAE1FD /* Animation+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Animation+Extensions.swift"; sourceTree = ""; }; 6365C4C22B1AA8CD00AAE1FD /* BottleListEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottleListEntry.swift; sourceTree = ""; }; - 63FFDE852ADF0C7700178665 /* BottomBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BottomBar.swift; sourceTree = ""; }; 6763D8F52BC6314100651D27 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 67D278502BC5907E006F9A1E /* ActionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionView.swift; sourceTree = ""; }; 6E064B1329DD331F00D9A2D2 /* SparkleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SparkleView.swift; sourceTree = ""; }; @@ -138,11 +138,6 @@ 6E50D98229CD6066008C39F6 /* BottleVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottleVM.swift; sourceTree = ""; }; 6E50D98429CDF25B008C39F6 /* BottleCreationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottleCreationView.swift; sourceTree = ""; }; 6E621CEE2A5F631200C9AAB3 /* Winetricks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Winetricks.swift; sourceTree = ""; }; - 6E6C0CF12A419A6800356232 /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = ""; }; - 6E7A1F012F00000000000002 /* SetupFailureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupFailureView.swift; sourceTree = ""; }; - 6E6C0CF32A419A7600356232 /* RosettaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RosettaView.swift; sourceTree = ""; }; - 6E6C0CF52A419A8300356232 /* WhiskyWineDownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhiskyWineDownloadView.swift; sourceTree = ""; }; - 6E6C0CF72A419A8C00356232 /* WhiskyWineInstallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhiskyWineInstallView.swift; sourceTree = ""; }; 6E70A4982A9A2197007799E9 /* WhiskyCmd */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = WhiskyCmd; sourceTree = BUILT_PRODUCTS_DIR; }; 6E70A49A2A9A2197007799E9 /* Main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Main.swift; sourceTree = ""; }; 6E70A4A02A9A280C007799E9 /* WhiskyCmd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhiskyCmd.swift; sourceTree = ""; }; @@ -150,7 +145,6 @@ 6E7C07BF2AAF570100F6E66B /* FileOpenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileOpenView.swift; sourceTree = ""; }; 6E899D1129CFC41D00A4A083 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 6EB58AC82AEACE5E00B1ED3F /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; - 6EF557972A410599001A4F09 /* SetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupView.swift; sourceTree = ""; }; 6EFDF64A2AAE2DA800EF622F /* WhiskyThumbnail.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WhiskyThumbnail.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 6EFDF64B2AAE2DA800EF622F /* QuickLookThumbnailing.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLookThumbnailing.framework; path = System/Library/Frameworks/QuickLookThumbnailing.framework; sourceTree = SDKROOT; }; 6EFDF64D2AAE2DA800EF622F /* Quartz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quartz.framework; path = System/Library/Frameworks/Quartz.framework; sourceTree = SDKROOT; }; @@ -160,9 +154,17 @@ 6EFDF6652AAE303300EF622F /* Icons.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Icons.xcassets; sourceTree = ""; }; 8C73E1332AF472FC00B6FB45 /* ProgramMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgramMenuView.swift; sourceTree = ""; }; 8CB681E42AED7C6F0018D319 /* WhiskyKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = WhiskyKit; sourceTree = ""; }; - AB0BCABC2B61036E00E21C31 /* RunningProcessesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunningProcessesView.swift; sourceTree = ""; }; DB696FC72AFAE5DA0037EB2F /* PinCreationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinCreationView.swift; sourceTree = ""; }; EEA5A2452A31DD65008274AE /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7A3FC8B99010FD02742231AE /* ProgramIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgramIconView.swift; sourceTree = ""; }; + 7A93F021F55DAD2BC6722D54 /* Program+Display.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Program+Display.swift"; sourceTree = ""; }; + 7A110F88A1199B4E9C0C8F3A /* PinnedProgramsGrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedProgramsGrid.swift; sourceTree = ""; }; + 7A30D4DC6FC022EF1446508D /* OnboardingModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingModel.swift; sourceTree = ""; }; + 7A7B1B81885C080827CC7B0F /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; + 7A53D33606FF388802121151 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; + 7A3E5CD4F34E72EE880BF131 /* WhiskyWineUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhiskyWineUpdate.swift; sourceTree = ""; }; + 7ACB6C31E47142A637015EA4 /* SteamSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SteamSession.swift; sourceTree = ""; }; + 7A5CAFC811A7E1E60978D49E /* ErrorAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorAlert.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -206,6 +208,7 @@ children = ( 6E40498229CCA91B006E3F1B /* Bottle+Extensions.swift */, 6365C4C02B1AA69D00AAE1FD /* Animation+Extensions.swift */, + 7A93F021F55DAD2BC6722D54 /* Program+Display.swift */, ); path = Extensions; sourceTree = ""; @@ -213,9 +216,9 @@ 63FFDE822ADEFADF00178665 /* Common */ = { isa = PBXGroup; children = ( - 63FFDE852ADF0C7700178665 /* BottomBar.swift */, 6330DD952B1B0EA4007A625A /* RenameView.swift */, 67D278502BC5907E006F9A1E /* ActionView.swift */, + 7A3FC8B99010FD02742231AE /* ProgramIconView.swift */, ); path = Common; sourceTree = ""; @@ -226,6 +229,7 @@ 6E17B6452AF3FDC100831173 /* PinView.swift */, 6E2B25C02B0E20F50084A67A /* PinAddView.swift */, DB696FC72AFAE5DA0037EB2F /* PinCreationView.swift */, + 7A110F88A1199B4E9C0C8F3A /* PinnedProgramsGrid.swift */, ); path = Pins; sourceTree = ""; @@ -257,7 +261,6 @@ 6E50D98429CDF25B008C39F6 /* BottleCreationView.swift */, 6E355E5729D78249002D83BE /* ConfigView.swift */, 6E182FC92B0BF64E00AADE81 /* WinetricksView.swift */, - AB0BCABC2B61036E00E21C31 /* RunningProcessesView.swift */, 6E17B6422AF3FD6E00831173 /* Pins */, ); path = Bottle; @@ -326,13 +329,14 @@ children = ( 63FFDE822ADEFADF00178665 /* Common */, 6E49E01F2AECB7D000009CAC /* Settings */, - 6E6C0CF02A419A5800356232 /* Setup */, 6E17B6472AF3FE2E00831173 /* Programs */, 6E355E5629D78202002D83BE /* Bottle */, 6E40495529CCA19C006E3F1B /* WhiskyApp.swift */, 6E40495729CCA19C006E3F1B /* ContentView.swift */, 6E064B1329DD331F00D9A2D2 /* SparkleView.swift */, 6E7C07BF2AAF570100F6E66B /* FileOpenView.swift */, + 7A99CC47BF0D98721EA18788 /* Onboarding */, + 7A4D66163040AFD339A6F343 /* Home */, ); path = Views; sourceTree = ""; @@ -352,6 +356,8 @@ 6E70A4A02A9A280C007799E9 /* WhiskyCmd.swift */, 6E7C07BD2AAE7B0100F6E66B /* ProgramShortcut.swift */, 6763D8F52BC6314100651D27 /* Constants.swift */, + 7A3E5CD4F34E72EE880BF131 /* WhiskyWineUpdate.swift */, + 7A5CAFC811A7E1E60978D49E /* ErrorAlert.swift */, ); path = Utils; sourceTree = ""; @@ -365,19 +371,6 @@ name = Frameworks; sourceTree = ""; }; - 6E6C0CF02A419A5800356232 /* Setup */ = { - isa = PBXGroup; - children = ( - 6EF557972A410599001A4F09 /* SetupView.swift */, - 6E6C0CF12A419A6800356232 /* WelcomeView.swift */, - 6E7A1F012F00000000000002 /* SetupFailureView.swift */, - 6E6C0CF32A419A7600356232 /* RosettaView.swift */, - 6E6C0CF52A419A8300356232 /* WhiskyWineDownloadView.swift */, - 6E6C0CF72A419A8C00356232 /* WhiskyWineInstallView.swift */, - ); - path = Setup; - sourceTree = ""; - }; 6E70A4992A9A2197007799E9 /* WhiskyCmd */ = { isa = PBXGroup; children = ( @@ -397,6 +390,24 @@ path = WhiskyThumbnail; sourceTree = ""; }; + 7A99CC47BF0D98721EA18788 /* Onboarding */ = { + isa = PBXGroup; + children = ( + 7A30D4DC6FC022EF1446508D /* OnboardingModel.swift */, + 7A7B1B81885C080827CC7B0F /* OnboardingView.swift */, + ); + path = Onboarding; + sourceTree = ""; + }; + 7A4D66163040AFD339A6F343 /* Home */ = { + isa = PBXGroup; + children = ( + 7A53D33606FF388802121151 /* HomeView.swift */, + 7ACB6C31E47142A637015EA4 /* SteamSession.swift */, + ); + path = Home; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -594,21 +605,15 @@ 6E70A4A12A9A280C007799E9 /* WhiskyCmd.swift in Sources */, 6E40495829CCA19C006E3F1B /* ContentView.swift in Sources */, 67D278512BC5907E006F9A1E /* ActionView.swift in Sources */, - 6EF557982A410599001A4F09 /* SetupView.swift in Sources */, 6E355E5A29D782B2002D83BE /* ProgramsView.swift in Sources */, DB696FC82AFAE5DA0037EB2F /* PinCreationView.swift in Sources */, 6E7C07BE2AAE7B0100F6E66B /* ProgramShortcut.swift in Sources */, 6E355E5829D78249002D83BE /* ConfigView.swift in Sources */, - 63FFDE862ADF0C7700178665 /* BottomBar.swift in Sources */, - 6E6C0CF62A419A8300356232 /* WhiskyWineDownloadView.swift in Sources */, 6365C4C32B1AA8CD00AAE1FD /* BottleListEntry.swift in Sources */, 6E50D98529CDF25B008C39F6 /* BottleCreationView.swift in Sources */, 6E182FCA2B0BF64E00AADE81 /* WinetricksView.swift in Sources */, 6330DD962B1B0EA4007A625A /* RenameView.swift in Sources */, - AB0BCABD2B61036E00E21C31 /* RunningProcessesView.swift in Sources */, 6E17B6492AF4118F00831173 /* EnvironmentArgView.swift in Sources */, - 6E6C0CF42A419A7600356232 /* RosettaView.swift in Sources */, - 6E6C0CF82A419A8C00356232 /* WhiskyWineInstallView.swift in Sources */, 6365C4C12B1AA69D00AAE1FD /* Animation+Extensions.swift in Sources */, 6E40498329CCA91B006E3F1B /* Bottle+Extensions.swift in Sources */, 6E621CEF2A5F631300C9AAB3 /* Winetricks.swift in Sources */, @@ -620,10 +625,17 @@ 6E2B25C12B0E20F50084A67A /* PinAddView.swift in Sources */, 6E49E0212AECB7DB00009CAC /* SettingsView.swift in Sources */, 6E7C07C02AAF570100F6E66B /* FileOpenView.swift in Sources */, - 6E6C0CF22A419A6800356232 /* WelcomeView.swift in Sources */, - 6E7A1F012F00000000000001 /* SetupFailureView.swift in Sources */, 6E40498129CCA8B0006E3F1B /* BottleView.swift in Sources */, 6E355E5E29D7D85D002D83BE /* ProgramView.swift in Sources */, + 7AC35FF857263758125C7A31 /* ProgramIconView.swift in Sources */, + 7A4C2D88025658F76A141654 /* Program+Display.swift in Sources */, + 7ABF25C1A893C7716A1C83DB /* PinnedProgramsGrid.swift in Sources */, + 7A69955CE36D4C4767FE307A /* OnboardingModel.swift in Sources */, + 7A3FA122329C4A8846CA0588 /* OnboardingView.swift in Sources */, + 7A43CB6C325CAA981E94DDE7 /* HomeView.swift in Sources */, + 7A14B0D5D96E14098322DFC0 /* WhiskyWineUpdate.swift in Sources */, + 7AA2C89FB1934624B6FFA190 /* SteamSession.swift in Sources */, + 7A5E34BEDDA87936C50FB0A2 /* ErrorAlert.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -799,9 +811,9 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Whisky/Info.plist; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; - INFOPLIST_KEY_NSAppleEventsUsageDescription = "Whisky needs to access Terminal to launcher processes in a Terminal window."; + INFOPLIST_KEY_NSAppleEventsUsageDescription = "Brandy needs to control Terminal to run programs in a Terminal window."; INFOPLIST_KEY_NSCameraUsageDescription = "A Windows application is trying to access the camera."; - INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © Whisky"; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © Brandy and Whisky contributors"; INFOPLIST_KEY_NSMicrophoneUsageDescription = "A Windows application is trying to access the microphone."; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -809,8 +821,9 @@ ); MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 2.3.5; - PRODUCT_BUNDLE_IDENTIFIER = com.istorry.Whisky; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_BUNDLE_IDENTIFIER = com.istorry.Brandy; + PRODUCT_MODULE_NAME = Whisky; + PRODUCT_NAME = Brandy; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_STRICT_CONCURRENCY = complete; @@ -838,9 +851,9 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Whisky/Info.plist; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; - INFOPLIST_KEY_NSAppleEventsUsageDescription = "Whisky needs to access Terminal to launcher processes in a Terminal window."; + INFOPLIST_KEY_NSAppleEventsUsageDescription = "Brandy needs to control Terminal to run programs in a Terminal window."; INFOPLIST_KEY_NSCameraUsageDescription = "A Windows application is trying to access the camera."; - INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © Whisky"; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © Brandy and Whisky contributors"; INFOPLIST_KEY_NSMicrophoneUsageDescription = "A Windows application is trying to access the microphone."; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -848,8 +861,9 @@ ); MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 2.3.5; - PRODUCT_BUNDLE_IDENTIFIER = com.istorry.Whisky; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_BUNDLE_IDENTIFIER = com.istorry.Brandy; + PRODUCT_MODULE_NAME = Whisky; + PRODUCT_NAME = Brandy; PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = Whisky; SWIFT_EMIT_LOC_STRINGS = YES; @@ -874,7 +888,7 @@ LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MACOSX_DEPLOYMENT_TARGET = 14.0; - PRODUCT_BUNDLE_IDENTIFIER = com.istorry.WhiskyCmd; + PRODUCT_BUNDLE_IDENTIFIER = com.istorry.BrandyCmd; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -900,7 +914,7 @@ LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MACOSX_DEPLOYMENT_TARGET = 14.0; - PRODUCT_BUNDLE_IDENTIFIER = com.istorry.WhiskyCmd; + PRODUCT_BUNDLE_IDENTIFIER = com.istorry.BrandyCmd; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -933,7 +947,7 @@ ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 2.3.5; - PRODUCT_BUNDLE_IDENTIFIER = com.istorry.Whisky.WhiskyThumbnail; + PRODUCT_BUNDLE_IDENTIFIER = com.istorry.Brandy.Thumbnail; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -968,7 +982,7 @@ ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 2.3.5; - PRODUCT_BUNDLE_IDENTIFIER = com.istorry.Whisky.WhiskyThumbnail; + PRODUCT_BUNDLE_IDENTIFIER = com.istorry.Brandy.Thumbnail; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; diff --git a/Whisky/AppDelegate.swift b/Whisky/AppDelegate.swift index 5eb58cc46..7962ac0fc 100644 --- a/Whisky/AppDelegate.swift +++ b/Whisky/AppDelegate.swift @@ -32,6 +32,11 @@ class AppDelegate: NSObject, NSApplicationDelegate { } } + func applicationWillFinishLaunching(_ notification: Notification) { + // Matches the default SettingsView shows, which `UserDefaults.bool(forKey:)` would otherwise read as off + UserDefaults.standard.register(defaults: [SettingsView.killOnTerminateKey: true]) + } + func applicationDidFinishLaunching(_ notification: Notification) { if !hasShownMoveToApplicationsAlert && !AppDelegate.insideAppsFolder { DispatchQueue.main.asyncAfter(deadline: .now()) { @@ -43,8 +48,8 @@ class AppDelegate: NSObject, NSApplicationDelegate { } func applicationWillTerminate(_ notification: Notification) { - if UserDefaults.standard.bool(forKey: "killOnTerminate") { - WhiskyApp.killBottles() + if UserDefaults.standard.bool(forKey: SettingsView.killOnTerminateKey) { + WhiskyApp.killBottlesBeforeExit() } } @@ -56,7 +61,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { Bundle.main.resourceURL?.deletingLastPathComponent().deletingLastPathComponent() } - private static let expectedUrl = URL(fileURLWithPath: "/Applications/Whisky.app") + private static let expectedUrl = URL(fileURLWithPath: "/Applications/Brandy.app") private static var insideAppsFolder: Bool { if let url = appUrl { @@ -82,7 +87,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { _ = try FileManager.default.replaceItemAt(AppDelegate.expectedUrl, withItemAt: appURL) NSWorkspace.shared.open(AppDelegate.expectedUrl) } catch { - print("Failed to move the app: \(error)") + ErrorAlert.show(String(localized: "error.moveApp"), error: error) } } } diff --git a/Whisky/Assets.xcassets/AppIcon.appiconset/128@2x.png b/Whisky/Assets.xcassets/AppIcon.appiconset/128@2x.png deleted file mode 100644 index a23d868e9..000000000 Binary files a/Whisky/Assets.xcassets/AppIcon.appiconset/128@2x.png and /dev/null differ diff --git a/Whisky/Assets.xcassets/AppIcon.appiconset/128R128x1.png b/Whisky/Assets.xcassets/AppIcon.appiconset/128R128x1.png deleted file mode 100644 index 7f1eef5b6..000000000 Binary files a/Whisky/Assets.xcassets/AppIcon.appiconset/128R128x1.png and /dev/null differ diff --git a/Whisky/Assets.xcassets/AppIcon.appiconset/16@2x.png b/Whisky/Assets.xcassets/AppIcon.appiconset/16@2x.png deleted file mode 100644 index 04fb84354..000000000 Binary files a/Whisky/Assets.xcassets/AppIcon.appiconset/16@2x.png and /dev/null differ diff --git a/Whisky/Assets.xcassets/AppIcon.appiconset/16R64x1.png b/Whisky/Assets.xcassets/AppIcon.appiconset/16R64x1.png deleted file mode 100644 index bda6c8924..000000000 Binary files a/Whisky/Assets.xcassets/AppIcon.appiconset/16R64x1.png and /dev/null differ diff --git a/Whisky/Assets.xcassets/AppIcon.appiconset/256@2x.png b/Whisky/Assets.xcassets/AppIcon.appiconset/256@2x.png deleted file mode 100644 index c89c5d927..000000000 Binary files a/Whisky/Assets.xcassets/AppIcon.appiconset/256@2x.png and /dev/null differ diff --git a/Whisky/Assets.xcassets/AppIcon.appiconset/256R256x1.png b/Whisky/Assets.xcassets/AppIcon.appiconset/256R256x1.png deleted file mode 100644 index 02cdd20b4..000000000 Binary files a/Whisky/Assets.xcassets/AppIcon.appiconset/256R256x1.png and /dev/null differ diff --git a/Whisky/Assets.xcassets/AppIcon.appiconset/32@2x.png b/Whisky/Assets.xcassets/AppIcon.appiconset/32@2x.png deleted file mode 100644 index df2211a6d..000000000 Binary files a/Whisky/Assets.xcassets/AppIcon.appiconset/32@2x.png and /dev/null differ diff --git a/Whisky/Assets.xcassets/AppIcon.appiconset/32R64x1.png b/Whisky/Assets.xcassets/AppIcon.appiconset/32R64x1.png deleted file mode 100644 index c0dc44d64..000000000 Binary files a/Whisky/Assets.xcassets/AppIcon.appiconset/32R64x1.png and /dev/null differ diff --git a/Whisky/Assets.xcassets/AppIcon.appiconset/512@2x.png b/Whisky/Assets.xcassets/AppIcon.appiconset/512@2x.png deleted file mode 100644 index 1fa623786..000000000 Binary files a/Whisky/Assets.xcassets/AppIcon.appiconset/512@2x.png and /dev/null differ diff --git a/Whisky/Assets.xcassets/AppIcon.appiconset/512R512x1.png b/Whisky/Assets.xcassets/AppIcon.appiconset/512R512x1.png deleted file mode 100644 index 3793b5fd9..000000000 Binary files a/Whisky/Assets.xcassets/AppIcon.appiconset/512R512x1.png and /dev/null differ diff --git a/Whisky/Assets.xcassets/AppIcon.appiconset/Contents.json b/Whisky/Assets.xcassets/AppIcon.appiconset/Contents.json index af42c34da..64dc11ee7 100644 --- a/Whisky/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Whisky/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,61 +1,61 @@ { "images" : [ { - "filename" : "16R64x1.png", + "filename" : "icon_16x16.png", "idiom" : "mac", "scale" : "1x", "size" : "16x16" }, { - "filename" : "16@2x.png", + "filename" : "icon_16x16@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "16x16" }, { - "filename" : "32R64x1.png", + "filename" : "icon_32x32.png", "idiom" : "mac", "scale" : "1x", "size" : "32x32" }, { - "filename" : "32@2x.png", + "filename" : "icon_32x32@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "32x32" }, { - "filename" : "128R128x1.png", + "filename" : "icon_128x128.png", "idiom" : "mac", "scale" : "1x", "size" : "128x128" }, { - "filename" : "128@2x.png", + "filename" : "icon_128x128@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "128x128" }, { - "filename" : "256R256x1.png", + "filename" : "icon_256x256.png", "idiom" : "mac", "scale" : "1x", "size" : "256x256" }, { - "filename" : "256@2x.png", + "filename" : "icon_256x256@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "256x256" }, { - "filename" : "512R512x1.png", + "filename" : "icon_512x512.png", "idiom" : "mac", "scale" : "1x", "size" : "512x512" }, { - "filename" : "512@2x.png", + "filename" : "icon_512x512@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "512x512" diff --git a/Whisky/Assets.xcassets/AppIcon.appiconset/icon_128x128.png b/Whisky/Assets.xcassets/AppIcon.appiconset/icon_128x128.png new file mode 100644 index 000000000..d97747d70 Binary files /dev/null and b/Whisky/Assets.xcassets/AppIcon.appiconset/icon_128x128.png differ diff --git a/Whisky/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png b/Whisky/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png new file mode 100644 index 000000000..543bb52a4 Binary files /dev/null and b/Whisky/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png differ diff --git a/Whisky/Assets.xcassets/AppIcon.appiconset/icon_16x16.png b/Whisky/Assets.xcassets/AppIcon.appiconset/icon_16x16.png new file mode 100644 index 000000000..90d063fba Binary files /dev/null and b/Whisky/Assets.xcassets/AppIcon.appiconset/icon_16x16.png differ diff --git a/Whisky/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png b/Whisky/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png new file mode 100644 index 000000000..25f96f32e Binary files /dev/null and b/Whisky/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png differ diff --git a/Whisky/Assets.xcassets/AppIcon.appiconset/icon_256x256.png b/Whisky/Assets.xcassets/AppIcon.appiconset/icon_256x256.png new file mode 100644 index 000000000..543bb52a4 Binary files /dev/null and b/Whisky/Assets.xcassets/AppIcon.appiconset/icon_256x256.png differ diff --git a/Whisky/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png b/Whisky/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png new file mode 100644 index 000000000..5f13526ea Binary files /dev/null and b/Whisky/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png differ diff --git a/Whisky/Assets.xcassets/AppIcon.appiconset/icon_32x32.png b/Whisky/Assets.xcassets/AppIcon.appiconset/icon_32x32.png new file mode 100644 index 000000000..25f96f32e Binary files /dev/null and b/Whisky/Assets.xcassets/AppIcon.appiconset/icon_32x32.png differ diff --git a/Whisky/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png b/Whisky/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png new file mode 100644 index 000000000..1c28a9e48 Binary files /dev/null and b/Whisky/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png differ diff --git a/Whisky/Assets.xcassets/AppIcon.appiconset/icon_512x512.png b/Whisky/Assets.xcassets/AppIcon.appiconset/icon_512x512.png new file mode 100644 index 000000000..5f13526ea Binary files /dev/null and b/Whisky/Assets.xcassets/AppIcon.appiconset/icon_512x512.png differ diff --git a/Whisky/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png b/Whisky/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png new file mode 100644 index 000000000..f4f67562d Binary files /dev/null and b/Whisky/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png differ diff --git a/Whisky/Extensions/Bottle+Extensions.swift b/Whisky/Extensions/Bottle+Extensions.swift index 06bb41fba..30767c066 100644 --- a/Whisky/Extensions/Bottle+Extensions.swift +++ b/Whisky/Extensions/Bottle+Extensions.swift @@ -22,75 +22,35 @@ import WhiskyKit import os.log extension Bottle { + /// Windows version, Wine version and program count, e.g. `Windows 10 · Wine 7.7 · 23 programs` + var summary: String { + let wineVersion = "\(settings.wineVersion.major).\(settings.wineVersion.minor)" + return [ + settings.windowsVersion.pretty(), + String(localized: "bottle.wineVersion.\(wineVersion)"), + String(localized: "bottle.programCount.\(programs.count)") + ].joined(separator: " · ") + } + func openCDrive() { NSWorkspace.shared.open(url.appending(path: "drive_c")) } + @MainActor func openTerminal() { - let whiskyCmdURL = Bundle.main.url(forResource: "WhiskyCmd", withExtension: nil) - if let whiskyCmdURL = whiskyCmdURL { - let whiskyCmd = whiskyCmdURL.path(percentEncoded: false) - let cmd = "eval \\\"$(\\\"\(whiskyCmd)\\\" shellenv \\\"\(settings.name)\\\")\\\"" - - let script = """ - tell application "Terminal" - activate - do script "\(cmd)" - end tell - """ - - Task.detached(priority: .userInitiated) { - var error: NSDictionary? - guard let appleScript = NSAppleScript(source: script) else { return } - appleScript.executeAndReturnError(&error) - - if let error = error { - Logger.wineKit.error("Failed to run terminal script \(error)") - guard let description = error["NSAppleScriptErrorMessage"] as? String else { return } - await self.showRunError(message: String(describing: description)) - } - } + guard let brandyCmd = Bundle.main.url(forResource: "WhiskyCmd", withExtension: nil) else { return } + let shellenv = "\(brandyCmd.path(percentEncoded: false).shellQuoted) shellenv \(settings.name.shellQuoted)" + do { + try Terminal.run("eval \"$(\(shellenv))\"") + } catch { + ErrorAlert.show(String(localized: "alert.message"), error: error) } } @discardableResult func getStartMenuPrograms() -> [Program] { - let globalStartMenu = url - .appending(path: "drive_c") - .appending(path: "ProgramData") - .appending(path: "Microsoft") - .appending(path: "Windows") - .appending(path: "Start Menu") - - let userStartMenu = url - .appending(path: "drive_c") - .appending(path: "users") - .appending(path: "crossover") - .appending(path: "AppData") - .appending(path: "Roaming") - .appending(path: "Microsoft") - .appending(path: "Windows") - .appending(path: "Start Menu") - var startMenuPrograms: [Program] = [] - var linkURLs: [URL] = [] - let globalEnumerator = FileManager.default.enumerator(at: globalStartMenu, - includingPropertiesForKeys: [.isRegularFileKey], - options: [.skipsHiddenFiles]) - while let url = globalEnumerator?.nextObject() as? URL { - if url.pathExtension == "lnk" { - linkURLs.append(url) - } - } - - let userEnumerator = FileManager.default.enumerator(at: userStartMenu, - includingPropertiesForKeys: [.isRegularFileKey], - options: [.skipsHiddenFiles]) - while let url = userEnumerator?.nextObject() as? URL { - if url.pathExtension == "lnk" { - linkURLs.append(url) - } - } + var linkURLs = startMenuFolders.flatMap(Self.shortcuts(in:)) linkURLs.sort(by: { $0.lastPathComponent.lowercased() < $1.lastPathComponent.lowercased() }) @@ -105,97 +65,126 @@ extension Bottle { } } } catch { - print(error) + Logger.wineKit.error("Failed to read Start Menu shortcut `\(link.lastPathComponent)`: \(error)") } } return startMenuPrograms } - func updateInstalledPrograms() { + /// The shared Start Menu and the user's; Wine names the profile after the macOS user, + /// CrossOver-based runtimes named it `crossover` + private var startMenuFolders: [URL] { + let driveC = url.appending(path: "drive_c") + let shared = driveC.appending(path: "ProgramData") + let users = [NSUserName(), "crossover"].map { profile in + driveC + .appending(path: "users") + .appending(path: profile) + .appending(path: "AppData") + .appending(path: "Roaming") + } + return ([shared] + users).map { folder in + folder + .appending(path: "Microsoft") + .appending(path: "Windows") + .appending(path: "Start Menu") + } + } + + private static func shortcuts(in folder: URL) -> [URL] { + let enumerator = FileManager.default.enumerator(at: folder, includingPropertiesForKeys: [.isRegularFileKey], + options: [.skipsHiddenFiles]) + var shortcuts: [URL] = [] + while let url = enumerator?.nextObject() as? URL { + if url.pathExtension == "lnk" { + shortcuts.append(url) + } + } + return shortcuts + } + + /// Finds the bottle's programs. The walk runs in the background: Program Files includes Steam's whole + /// game library, which can be thousands of folders. + @MainActor + func updateInstalledPrograms() async { let driveC = url.appending(path: "drive_c") - var programs: [Program] = [] - var foundURLS: Set = [] + let hidden = Set(settings.blocklist) + let installed = await Task.detached(priority: .userInitiated) { + Self.executables(in: driveC, excluding: hidden) + }.value + + let pinnedElsewhere = settings.pins.compactMap(\.url).filter { !installed.contains($0) } + programs = (installed + pinnedElsewhere) + .map { Program(url: $0, bottle: self) } + .sorted { $0.name.lowercased() < $1.name.lowercased() } + } + private nonisolated static func executables(in driveC: URL, excluding hidden: Set) -> [URL] { + var executables: [URL] = [] for folderName in ["Program Files", "Program Files (x86)"] { - let folderURL = driveC.appending(path: folderName) let enumerator = FileManager.default.enumerator( - at: folderURL, includingPropertiesForKeys: [.isExecutableKey], options: [.skipsHiddenFiles] + at: driveC.appending(path: folderName), includingPropertiesForKeys: nil, options: [.skipsHiddenFiles] ) - - while let url = enumerator?.nextObject() as? URL { - guard !url.hasDirectoryPath && url.pathExtension == "exe" else { continue } - guard !settings.blocklist.contains(url) else { continue } - foundURLS.insert(url) - programs.append(Program(url: url, bottle: self)) + while let file = enumerator?.nextObject() as? URL { + guard file.pathExtension.lowercased() == "exe", !file.hasDirectoryPath, !hidden.contains(file) + else { continue } + executables.append(file) } } - - // Add missing programs from pins - for pin in settings.pins { - guard let url = pin.url else { continue } - guard !foundURLS.contains(url) else { continue } - programs.append(Program(url: url, bottle: self)) - } - - self.programs = programs.sorted { $0.name.lowercased() < $1.name.lowercased() } + return executables } @MainActor func move(destination: URL) { + guard let bottle = BottleVM.shared.bottles.first(where: { $0.url == url }) else { return } + bottle.inFlight = true do { - if let bottle = BottleVM.shared.bottles.first(where: { $0.url == url }) { - bottle.inFlight = true - for index in 0..