Added macOS support (#968)
Co-authored-by: DevilXD <4180725+DevilXD@users.noreply.github.com>
This commit is contained in:
1 parent
b685b445e9
commit
1166170fae
6 files changed
+126
-8
No files matched your search
@@ -106,6 +106,58 @@ jobs:
|
||||
name: Twitch.Drops.Miner.Windows
|
||||
path: Twitch.Drops.Miner.Windows.zip
|
||||
|
||||
macos:
|
||||
name: macOS
|
||||
runs-on: macos-latest
|
||||
needs:
|
||||
- validate
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up variables
|
||||
id: vars
|
||||
run: |
|
||||
echo "sha_short=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Append git revision to project version
|
||||
run: |
|
||||
sed -i '' -E "s/^(__version__\s*=\s*\"[^\"]+)/\1.${{steps.vars.outputs.sha_short}}/" version.py
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{env.PYTHON_VERSION}}
|
||||
|
||||
- name: Install project dependencies
|
||||
run: |
|
||||
python3 -m pip install wheel
|
||||
python3 -m pip install -r requirements.txt
|
||||
|
||||
- name: Install PyInstaller
|
||||
run: |
|
||||
python3 -m pip install pyinstaller
|
||||
|
||||
- name: Create portable executable
|
||||
run: |
|
||||
pyinstaller build.spec --noconfirm
|
||||
|
||||
- name: Create release folder and Zip
|
||||
run: |
|
||||
FOLDER="Twitch Drops Miner"
|
||||
mkdir -p "$FOLDER"
|
||||
cp -r "dist/Twitch Drops Miner (by DevilXD).app" "$FOLDER/"
|
||||
cp manual.txt "$FOLDER/"
|
||||
zip -r Twitch.Drops.Miner.MacOS.zip "$FOLDER"
|
||||
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
if-no-files-found: error
|
||||
name: Twitch.Drops.Miner.MacOS
|
||||
path: Twitch.Drops.Miner.MacOS.zip
|
||||
|
||||
linux-pyinstaller:
|
||||
name: Linux (PyInstaller) (${{matrix.arch}})
|
||||
strategy:
|
||||
@@ -256,6 +308,7 @@ jobs:
|
||||
- windows
|
||||
- linux-pyinstaller
|
||||
- linux-appimage
|
||||
- macos
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
@@ -23,3 +23,5 @@ settings.json
|
||||
/appimage-build
|
||||
/*.AppImage
|
||||
/*.AppImage.zsync
|
||||
.DS_Store
|
||||
*.app
|
||||
@@ -65,6 +65,13 @@ Every several seconds, the application pretends to watch a particular stream by
|
||||
- The size of the Linux app is significantly larger than the Windows app due to the inclusion of the `gtk3` library (and its dependencies), which is required for proper system tray/notifications support.
|
||||
- As an alternative to the native Linux app, you can run the Windows app via [Wine](https://www.winehq.org/) instead. It works really well!
|
||||
|
||||
### Notes about the macOS build:
|
||||
|
||||
- The macOS version is packaged using PyInstaller into a standalone `.app` bundle, distributed as a ZIP archive.
|
||||
- Since this application is not signed with a paid Apple Developer Certificate, **macOS Gatekeeper will block it** on the first run (saying it "The application is damaged and can't be opened").
|
||||
- **To fix this**: Either open the Terminal in the folder the app is in (or navigating with `cd path/to/folder`) and enter `xattr -cr Twitch Drops Miner (by DevilXD).app` or just type `xattr -cr ` (make sure to put a space at the end), drag and drop the `Twitch Drops Miner (by DevilXD).app` file into the terminal window (this will auto-fill the path) and enter
|
||||
- Persistent files (like `cookies.jar`, `settings.json`, `lock.file` and the `cache` folder) are stored inside the application bundle in `Twitch Drops Miner (by DevilXD).app/Contents/MacOS` (to access them Right-click the application and select `Show Package Contents`)
|
||||
|
||||
### Advanced Usage:
|
||||
|
||||
If you'd be interested in running the latest master from source or building your own executable, see the wiki page explaining how to do so: https://github.com/DevilXD/TwitchDropsMiner/wiki/Setting-up-the-environment,-building-and-running
|
||||
@@ -135,6 +142,7 @@ placed past the period character at the end.
|
||||
|
||||
@guihkx - For the CI script, CI maintenance, and everything related to Linux builds.
|
||||
@kWAYTV - For the implementation of the dark mode theme.
|
||||
@crocchetto - For the macOS port.
|
||||
|
||||
@Bamboozul - For the entirety of the Arabic (العربية) translation.
|
||||
@Suz1e - For the entirety of the Chinese (简体中文) translation and revisions.
|
||||
|
||||
Executable → Regular
+11
-1
@@ -19,7 +19,7 @@ if TYPE_CHECKING:
|
||||
from PyInstaller.building.splash import Splash
|
||||
from PyInstaller.building.build_main import Analysis
|
||||
from PyInstaller.building.datastruct import _TOCTuple
|
||||
from PyInstaller.building.api import PYZ, EXE, COLLECT
|
||||
from PyInstaller.building.api import PYZ, EXE, COLLECT, BUNDLE
|
||||
|
||||
|
||||
PYZTypeCOLLECT: TypeAlias = "abc.Iterable[_TOCTuple] | PYZ"
|
||||
@@ -142,3 +142,13 @@ if one_dir:
|
||||
upx=upx,
|
||||
name=app_name,
|
||||
)
|
||||
|
||||
# macOS bundle support
|
||||
if sys.platform == "darwin":
|
||||
source = coll if one_dir else exe
|
||||
app = BUNDLE(
|
||||
source,
|
||||
name=f'{app_name}.app',
|
||||
icon="icons/pickaxe.ico",
|
||||
bundle_identifier='com.twitchdrops.miner',
|
||||
)
|
||||
@@ -3,9 +3,11 @@ from __future__ import annotations
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import shlex
|
||||
import ctypes
|
||||
import asyncio
|
||||
import logging
|
||||
import plistlib
|
||||
import tkinter as tk
|
||||
from pathlib import Path
|
||||
from collections import abc
|
||||
@@ -28,6 +30,9 @@ if sys.platform == "win32":
|
||||
import win32con
|
||||
import win32gui
|
||||
|
||||
if sys.platform == "darwin":
|
||||
import AppKit
|
||||
|
||||
from translate import _
|
||||
from cache import ImageCache
|
||||
from exceptions import MinerException, ExitRequest
|
||||
@@ -1087,6 +1092,9 @@ class TrayIcon:
|
||||
}
|
||||
self._icon_state: str = "pickaxe"
|
||||
self._button = ttk.Button(master, command=self.minimize, text=_("gui", "tray", "minimize"))
|
||||
|
||||
# Hides Tray button for macOS
|
||||
if sys.platform != "darwin":
|
||||
self._button.grid(column=0, row=0, sticky="ne")
|
||||
|
||||
def __del__(self) -> None:
|
||||
@@ -1153,6 +1161,8 @@ class TrayIcon:
|
||||
self._manager.close()
|
||||
|
||||
def minimize(self):
|
||||
if sys.platform == "darwin":
|
||||
return
|
||||
if self.icon is None:
|
||||
self._start()
|
||||
else:
|
||||
@@ -1635,6 +1645,7 @@ class SettingsPanel:
|
||||
ttk.Checkbutton(
|
||||
checkboxes_frame, variable=self._vars["autostart"], command=self.update_autostart
|
||||
).grid(column=1, row=irow, sticky="w")
|
||||
if sys.platform != "darwin":
|
||||
ttk.Label(
|
||||
checkboxes_frame, text=_("gui", "settings", "general", "tray")
|
||||
).grid(column=0, row=(irow := irow + 1), sticky="e")
|
||||
@@ -1757,7 +1768,7 @@ class SettingsPanel:
|
||||
ttk.Button( # Move to top
|
||||
priority_frame,
|
||||
width=2,
|
||||
text="⭱",
|
||||
text="⇈",
|
||||
style="Arrow.TButton",
|
||||
command=partial(self.priority_move, MAX_INT),
|
||||
).grid(column=1, row=1, sticky="nsew")
|
||||
@@ -1765,7 +1776,7 @@ class SettingsPanel:
|
||||
ttk.Button( # Move up
|
||||
priority_frame,
|
||||
width=2,
|
||||
text="🠙",
|
||||
text="↑",
|
||||
style="Arrow.TButton",
|
||||
command=partial(self.priority_move, 1),
|
||||
).grid(column=1, row=2, sticky="nsew")
|
||||
@@ -1773,7 +1784,7 @@ class SettingsPanel:
|
||||
ttk.Button( # Move down
|
||||
priority_frame,
|
||||
width=2,
|
||||
text="🠛",
|
||||
text="↓",
|
||||
style="Arrow.TButton",
|
||||
command=partial(self.priority_move, -1),
|
||||
).grid(column=1, row=3, sticky="nsew")
|
||||
@@ -1781,7 +1792,7 @@ class SettingsPanel:
|
||||
ttk.Button( # Move to bottom
|
||||
priority_frame,
|
||||
width=2,
|
||||
text="⭳",
|
||||
text="⇊",
|
||||
style="Arrow.TButton",
|
||||
command=partial(self.priority_move, -MAX_INT),
|
||||
).grid(column=1, row=4, sticky="nsew")
|
||||
@@ -1867,6 +1878,9 @@ class SettingsPanel:
|
||||
autostart_folder = config_autostart
|
||||
return autostart_folder / f"{self.AUTOSTART_NAME}.desktop"
|
||||
|
||||
def _get_mac_autostart_filepath(self) -> Path:
|
||||
return Path(Path.home(), f"Library/LaunchAgents/com.devilxd.{self.AUTOSTART_NAME.lower()}.plist")
|
||||
|
||||
def _query_autostart(self) -> bool:
|
||||
if sys.platform == "win32":
|
||||
with RegistryKey(self.AUTOSTART_KEY, read_only=True) as key:
|
||||
@@ -1886,6 +1900,12 @@ class SettingsPanel:
|
||||
with autostart_file.open('r', encoding="utf8") as file:
|
||||
# TODO: Consider deleting the old file to avoid autostart errors
|
||||
return self._get_self_path() in file.read()
|
||||
elif sys.platform == "darwin":
|
||||
plist_file = self._get_mac_autostart_filepath()
|
||||
if not plist_file.exists():
|
||||
return False
|
||||
with plist_file.open('r', encoding="utf8") as file:
|
||||
return str(SELF_PATH.resolve()) in file.read()
|
||||
|
||||
def update_autostart(self) -> None:
|
||||
enabled = bool(self._vars["autostart"].get())
|
||||
@@ -1917,6 +1937,21 @@ class SettingsPanel:
|
||||
file.write(file_contents)
|
||||
else:
|
||||
autostart_file.unlink(missing_ok=True)
|
||||
elif sys.platform == "darwin":
|
||||
plist_file = self._get_mac_autostart_filepath()
|
||||
|
||||
if enabled:
|
||||
command_parts = shlex.split(self._get_autostart_path())
|
||||
plist_data = {
|
||||
"Label": f"com.devilxd.{self.AUTOSTART_NAME.lower()}",
|
||||
"ProgramArguments": command_parts,
|
||||
"RunAtLoad": True,
|
||||
}
|
||||
plist_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
with plist_file.open("wb") as file:
|
||||
plistlib.dump(plist_data, file)
|
||||
else:
|
||||
plist_file.unlink(missing_ok=True)
|
||||
|
||||
def update_excluded_choices(self) -> None:
|
||||
self._exclude_entry.config(
|
||||
@@ -2250,7 +2285,7 @@ class GUIManager:
|
||||
self._orig_theme_name = ''
|
||||
self.apply_theme(self._twitch.settings.dark_mode)
|
||||
# stay hidden in tray if needed, otherwise show the window when everything's ready
|
||||
if self._twitch.settings.tray:
|
||||
if self._twitch.settings.tray and sys.platform != "darwin":
|
||||
# NOTE: this starts the tray icon thread
|
||||
self._root.after_idle(self.tray.minimize)
|
||||
else:
|
||||
@@ -2422,7 +2457,7 @@ class GUIManager:
|
||||
# Palette
|
||||
if dark:
|
||||
# Switch to a configurable ttk theme for better color control
|
||||
if self._style.theme_use() != "clam":
|
||||
if sys.platform != "darwin" and self._style.theme_use() != "clam":
|
||||
self._style.theme_use("clam")
|
||||
bg = "#1e1e1e"
|
||||
fg = "#e6e6e6"
|
||||
@@ -2452,6 +2487,16 @@ class GUIManager:
|
||||
muted = "#404040"
|
||||
accent = "#0a84ff"
|
||||
|
||||
|
||||
# Setting theme for macOS
|
||||
if sys.platform == "darwin":
|
||||
app = AppKit.NSApplication.sharedApplication()
|
||||
if dark:
|
||||
appearance = AppKit.NSAppearance.appearanceNamed_(AppKit.NSAppearanceNameDarkAqua)
|
||||
else:
|
||||
appearance = AppKit.NSAppearance.appearanceNamed_(AppKit.NSAppearanceNameAqua)
|
||||
app.setAppearance_(appearance)
|
||||
|
||||
s = self._style
|
||||
# Fonts
|
||||
default_font = nametofont("TkDefaultFont")
|
||||
|
||||
Reference in new issue
Block a user