From 71597a97cee67e64e2507f0e79fe78e43d4a343c Mon Sep 17 00:00:00 2001 From: DevilXD Date: Mon, 29 May 2023 14:25:17 +0200 Subject: [PATCH 01/13] Implement autostart via desktop file on Linux --- gui.py | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/gui.py b/gui.py index b7593ce5..df4b7809 100644 --- a/gui.py +++ b/gui.py @@ -1,5 +1,6 @@ from __future__ import annotations +import os import re import sys import ctypes @@ -7,7 +8,9 @@ import logging import webbrowser import tkinter as tk +from pathlib import Path from collections import abc +from textwrap import dedent from math import log10, ceil from dataclasses import dataclass from tkinter.font import Font, nametofont @@ -1635,6 +1638,12 @@ def clear_selection(self) -> None: def update_notifications(self) -> None: self._settings.tray_notifications = bool(self._vars["tray_notifications"].get()) + def _get_autostart_path(self, tray: bool) -> str: + self_path = f'"{SELF_PATH.resolve()!s}"' + if tray: + self_path += " --tray" + return self_path + def update_autostart(self) -> None: enabled = bool(self._vars["autostart"].get()) tray = bool(self._vars["tray"].get()) @@ -1643,14 +1652,34 @@ def update_autostart(self) -> None: if sys.platform == "win32": if enabled: # NOTE: we need double quotes in case the path contains spaces - self_path = f'"{SELF_PATH.resolve()!s}"' - if tray: - self_path += " --tray" + autostart_path = self._get_autostart_path(tray) with RegistryKey(self.AUTOSTART_KEY) as key: - key.set(self.AUTOSTART_NAME, ValueType.REG_SZ, self_path) + key.set(self.AUTOSTART_NAME, ValueType.REG_SZ, autostart_path) else: with RegistryKey(self.AUTOSTART_KEY) as key: key.delete(self.AUTOSTART_NAME, silent=True) + elif sys.platform == "linux": + autostart_folder: Path = Path("~/.config/autostart").expanduser() + if (config_home := os.environ.get("XDG_CONFIG_HOME")) is not None: + config_autostart: Path = Path(config_home, "autostart").expanduser() + if config_autostart.exists(): + autostart_folder = config_autostart + autostart_file: Path = autostart_folder / f"{self.AUTOSTART_NAME}.desktop" + if enabled: + autostart_path = self._get_autostart_path(tray) + file_contents = dedent( + f""" + [Desktop Entry] + Type=Application + Name=Twitch Drops Miner + Description=Mine timed drops on Twitch + Exec=sh -c '{autostart_path}' + """ + ) + with autostart_file.open("w", encoding="utf8") as file: + file.write(file_contents) + else: + autostart_file.unlink(missing_ok=True) def set_games(self, games: abc.Iterable[Game]) -> None: games_list = sorted(map(str, games)) From cf59e7a11962357937c6cbd3a176e55aa060dfa7 Mon Sep 17 00:00:00 2001 From: DevilXD Date: Mon, 29 May 2023 14:32:03 +0200 Subject: [PATCH 02/13] Use run_in_executor for the tray icon thread --- gui.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gui.py b/gui.py index df4b7809..fd7eea2c 100644 --- a/gui.py +++ b/gui.py @@ -1079,7 +1079,8 @@ def bridge(func): pystray.MenuItem(_("gui", "tray", "quit"), bridge(self.quit)), ) self.icon = pystray.Icon("twitch_miner", self.icon_image, self.get_title(drop), menu) - self.icon.run_detached() + # self.icon.run_detached() + loop.run_in_executor(None, self.icon.run) def stop(self): if self.icon is not None: From 52af7cd399739971c4508c6439d692ee717c13e9 Mon Sep 17 00:00:00 2001 From: guihkx <626206+guihkx@users.noreply.github.com> Date: Sun, 4 Jun 2023 02:05:37 -0300 Subject: [PATCH 03/13] Use icon.visible to show or hide the tray icon on all systems On Linux, pystray's icon.stop() currently doesn't work well with the AppIndicator backend. So, instead of destroying the icon when the user restores from tray (only to later re-create it when the user minimizes to tray again), we have the option to control the icon's visibility instead! Luckily, that works on Windows too! Note that for this change to work properly on Linux, we need to switch to the master version of pystray, because we need this specific fix: https://github.com/moses-palmer/pystray/commit/5943ed4e19be48224cf39b70444de8ffe22c23ce After pystray releases a version newer than 0.19.4, we can switch back. Co-authored-by: DevilXD --- gui.py | 48 ++++++++++++++++++++++++------------------------ requirements.txt | 3 ++- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/gui.py b/gui.py index fd7eea2c..1bfb1d08 100644 --- a/gui.py +++ b/gui.py @@ -1048,11 +1048,9 @@ def __init__(self, manager: GUIManager, master: ttk.Widget): self._button.grid(column=0, row=0, sticky="ne") def __del__(self) -> None: + self.stop() self.icon_image.close() - def is_tray(self) -> bool: - return self.icon is not None - def get_title(self, drop: TimedDrop | None) -> str: if drop is None: return self.TITLE @@ -1064,23 +1062,22 @@ def get_title(self, drop: TimedDrop | None) -> str: f"{drop.progress:.1%} ({campaign.claimed_drops}/{campaign.total_drops})" ) - def start(self): - if self.icon is None: - loop = asyncio.get_running_loop() - drop = self._manager.progress._drop + def _start(self): + loop = asyncio.get_running_loop() + drop = self._manager.progress._drop - # we need this because tray icon lives in a separate thread - def bridge(func): - return lambda: loop.call_soon_threadsafe(func) + # we need this because tray icon lives in a separate thread + def bridge(func): + return lambda: loop.call_soon_threadsafe(func) - menu = pystray.Menu( - pystray.MenuItem(_("gui", "tray", "show"), bridge(self.restore), default=True), - pystray.Menu.SEPARATOR, - pystray.MenuItem(_("gui", "tray", "quit"), bridge(self.quit)), - ) - self.icon = pystray.Icon("twitch_miner", self.icon_image, self.get_title(drop), menu) - # self.icon.run_detached() - loop.run_in_executor(None, self.icon.run) + menu = pystray.Menu( + pystray.MenuItem(_("gui", "tray", "show"), bridge(self.restore), default=True), + pystray.Menu.SEPARATOR, + pystray.MenuItem(_("gui", "tray", "quit"), bridge(self.quit)), + ) + self.icon = pystray.Icon("twitch_miner", self.icon_image, self.get_title(drop), menu) + # self.icon.run_detached() + loop.run_in_executor(None, self.icon.run) def stop(self): if self.icon is not None: @@ -1091,13 +1088,16 @@ def quit(self): self._manager.close() def minimize(self): - if not self.is_tray(): - self.start() - self._manager._root.withdraw() + if self.icon is None: + self._start() + else: + self.icon.visible = True + self._manager._root.withdraw() def restore(self): - if self.is_tray(): - self.stop() + if self.icon is not None: + # self.stop() + self.icon.visible = False self._manager._root.deiconify() def notify( @@ -1107,7 +1107,7 @@ def notify( if not self._manager._twitch.settings.tray_notifications: return None if self.icon is not None: - icon = self.icon + icon = self.icon # nonlocal scope bind async def notifier(): icon.notify(message, title) diff --git a/requirements.txt b/requirements.txt index fffb45f1..3708b68c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ aiohttp>2.0,<4.0 Pillow -pystray +# TODO: switch back to a stable pystray release when a version newer than v0.19.4 is out +pystray@git+https://github.com/moses-palmer/pystray.git@903836104f8b1a2979f4fa5a333870ef7b075992 # selenium-wire # undetected-chromedriver # this is installed only on windows From 1daf366f096d850af94688f0f77a239006bdf64f Mon Sep 17 00:00:00 2001 From: guihkx <626206+guihkx@users.noreply.github.com> Date: Sun, 4 Jun 2023 16:45:18 -0300 Subject: [PATCH 04/13] tk: set classPath upon instantiation This fixes an issue on Linux (more specifically, on GNOME Shell 44), where the window would be identified as "Tk". --- gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui.py b/gui.py index 1bfb1d08..6004a97e 100644 --- a/gui.py +++ b/gui.py @@ -1876,7 +1876,7 @@ def __init__(self, twitch: Twitch): self._twitch: Twitch = twitch self._poll_task: asyncio.Task[NoReturn] | None = None self._close_requested = asyncio.Event() - self._root = root = Tk() + self._root = root = Tk(className=WINDOW_TITLE) # withdraw immediately to prevent the window from flashing self._root.withdraw() # root.resizable(False, True) From a6088da1885026e94b09d1dff334e4fa87aef546 Mon Sep 17 00:00:00 2001 From: guihkx <626206+guihkx@users.noreply.github.com> Date: Sun, 28 May 2023 14:04:10 -0300 Subject: [PATCH 05/13] tk: also hook into WM_DELETE_WINDOW to quit the app Makes the app exit correctly on Linux when the main window is closed. --- gui.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gui.py b/gui.py index 6004a97e..12c56cc5 100644 --- a/gui.py +++ b/gui.py @@ -1994,6 +1994,7 @@ def __init__(self, twitch: Twitch): # ctypes.windll.user32.ShutdownBlockReasonDestroy(self._handle) else: # use old-style window closing protocol for non-windows platforms + root.protocol("WM_DELETE_WINDOW", self.close) root.protocol("WM_DESTROY_WINDOW", self.close) # stay hidden in tray if needed, otherwise show the window when everything's ready if self._twitch.settings.tray: From 7abf26fd9f3fa305277c81f4696acf10aaf158f2 Mon Sep 17 00:00:00 2001 From: guihkx <626206+guihkx@users.noreply.github.com> Date: Sat, 3 Jun 2023 07:42:10 -0300 Subject: [PATCH 06/13] linux: add PyGObject as a dependency This dependency is required for proper support of the AppIndicator backend from pystray: https://github.com/moses-palmer/pystray/blob/v0.19.4/docs/faq.rst#how-do-i-use-pystray-in-a-virtualenv-on-linux --- build.spec | 29 ++++++++++++++++++++--------- requirements.txt | 1 + 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/build.spec b/build.spec index b2a6aa0f..c6d8d46b 100644 --- a/build.spec +++ b/build.spec @@ -34,28 +34,39 @@ for source_path, dest_path, required in to_add: elif required: raise FileNotFoundError(str(source_path)) +binaries: list[tuple[Path, str]] = [] +hiddenimports: list[str] = [ + "PIL._tkinter_finder", + "setuptools._distutils.log", + "setuptools._distutils.dir_util", + "setuptools._distutils.file_util", + "setuptools._distutils.archive_util", +] + +if sys.platform == "linux": + # Needed files for better system tray support on Linux via pystray (AppIndicator backend). + datas.append((Path("/usr/lib/girepository-1.0/AppIndicator3-0.1.typelib"), "gi_typelibs")) + binaries.append((Path("/lib/x86_64-linux-gnu/libappindicator3.so.1"), ".")) + hiddenimports.extend([ + "gi.repository.Gtk", + "gi.repository.GObject", + ]) block_cipher = None a = Analysis( ["main.py"], pathex=[], datas=datas, - binaries=[], excludes=[], hookspath=[], hooksconfig={}, noarchive=False, - hiddenimports=[ - "setuptools._distutils.log", - "setuptools._distutils.dir_util", - "setuptools._distutils.file_util", - "setuptools._distutils.archive_util", - "PIL._tkinter_finder", - ], runtime_hooks=[], + binaries=binaries, cipher=block_cipher, - win_no_prefer_redirects=False, + hiddenimports=hiddenimports, win_private_assemblies=False, + win_no_prefer_redirects=False, ) pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) exe = EXE( diff --git a/requirements.txt b/requirements.txt index 3708b68c..92bf9a9f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ aiohttp>2.0,<4.0 Pillow # TODO: switch back to a stable pystray release when a version newer than v0.19.4 is out pystray@git+https://github.com/moses-palmer/pystray.git@903836104f8b1a2979f4fa5a333870ef7b075992 +PyGObject; sys_platform == "linux" # required for better system tray support on Linux # selenium-wire # undetected-chromedriver # this is installed only on windows From a8cf3cf6c1a471c15a86a95bfaeca3c19b355f57 Mon Sep 17 00:00:00 2001 From: guihkx <626206+guihkx@users.noreply.github.com> Date: Sun, 21 May 2023 07:14:15 -0300 Subject: [PATCH 07/13] linux: add truststore as a dependency Because Linux distributions don't have an 'universal' path for the system certificate store, bundling libssl built on distro A might not work on distro B, because distros tend to change path to the certificate store at build time. This means that creating a PyInstaller package on Ubuntu will include the libssl built there, and if you try to run this package on a different distro (e.g. Arch Linux), any attempt to create an SSL connection will fail, because Arch Linux has a different path for the certificate store than Ubuntu. This issue is usually worked around by bundling certificates with the app (usually using certifi), and then pointing the SSL_CERT_FILE environment to it. However, truststore seems to be a better alternative, since it tries to use the certificate store from the system instead. The list of benefits are listed on the project's page on GitHub: https://github.com/sethmlarson/truststore --- main.py | 4 ++++ requirements.txt | 1 + 2 files changed, 5 insertions(+) diff --git a/main.py b/main.py index be2b2f62..78b379a7 100644 --- a/main.py +++ b/main.py @@ -21,6 +21,10 @@ if sys.platform == "win32": import win32gui + if sys.platform == "linux" and sys.version_info >= (3, 10): + import truststore + truststore.inject_into_ssl() + from translate import _ from twitch import Twitch from settings import Settings diff --git a/requirements.txt b/requirements.txt index 92bf9a9f..1ea5c545 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ PyGObject; sys_platform == "linux" # required for better system tray support on # undetected-chromedriver # this is installed only on windows pywin32; sys_platform == "win32" +truststore; sys_platform == "linux" and python_version >= "3.10" From 8cdd8fbbabe35c56aba4c016c97a8d63a4dd4ad6 Mon Sep 17 00:00:00 2001 From: guihkx <626206+guihkx@users.noreply.github.com> Date: Sun, 28 May 2023 17:13:27 -0300 Subject: [PATCH 08/13] linux: fix URLs not opening in PyInstaller package Because PyInstaller modifies the LD_LIBRARY_PATH environment variable to make things work correctly on Linux, when we try to launch any subprocess (such as the web browser), that modified variable gets picked up as well, causing things to go south. To work around this Linux-only problem, we have to: 1. Save the current value of LD_LIBRARY_PATH 2. Move the value of LD_LIBRARY_PATH_ORIG to LD_LIBRARY_PATH 3. Launch subprocess 4. Restore the saved LD_LIBRARY_PATH again Reference: https://pyinstaller.org/en/stable/runtime-information.html#ld-library-path-libpath-considerations Co-authored-by: DevilXD --- gui.py | 10 +++------- utils.py | 30 +++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/gui.py b/gui.py index 12c56cc5..863bb4b1 100644 --- a/gui.py +++ b/gui.py @@ -6,7 +6,6 @@ import ctypes import asyncio import logging -import webbrowser import tkinter as tk from pathlib import Path from collections import abc @@ -32,7 +31,7 @@ from translate import _ from cache import ImageCache from exceptions import ExitRequest -from utils import resource_path, set_root_icon, Game, _T +from utils import resource_path, set_root_icon, webopen, Game, _T from constants import ( SELF_PATH, OUTPUT_FORMATTER, WS_TOPICS_LIMIT, MAX_WEBSOCKETS, WINDOW_TITLE, State ) @@ -344,10 +343,7 @@ def __init__(self, *args, link: str, **kwargs) -> None: # W, N, E, S kwargs["padding"] = (0, 2, 0, 2) super().__init__(*args, **kwargs) - self.bind("", self.webopen(self._link)) - - def webopen(self, url: str): - return lambda e: webbrowser.open_new_tab(url) + self.bind("", lambda e: webopen(self._link)) class SelectMenu(tk.Menubutton, Generic[_T]): @@ -558,7 +554,7 @@ async def ask_enter_code(self, user_code: str) -> None: self._manager.print(_("gui", "login", "request")) await self.wait_for_login_press() self._manager.print(f"Enter this code on the Twitch's device activation page: {user_code}") - webbrowser.open_new_tab("https://www.twitch.tv/activate") + webopen("https://www.twitch.tv/activate") def update(self, status: str, user_id: int | None): if user_id is not None: diff --git a/utils.py b/utils.py index 85155f0c..bf525c1c 100644 --- a/utils.py +++ b/utils.py @@ -1,11 +1,14 @@ from __future__ import annotations +import os +import sys import json import random import string import asyncio import logging import traceback +import webbrowser import tkinter as tk from enum import Enum from pathlib import Path @@ -21,7 +24,7 @@ from PIL.ImageTk import PhotoImage from PIL import Image as Image_module -from constants import JsonType +from constants import JsonType, IS_PACKAGED from exceptions import ExitRequest, ReloadRequest from constants import _resource_path as resource_path # noqa @@ -213,6 +216,31 @@ def json_save(path: Path, contents: Mapping[Any, Any], *, sort: bool = False) -> json.dump(contents, file, default=_serialize, sort_keys=sort, indent=4) +def webopen(url: str): + if IS_PACKAGED and sys.platform == "linux": + # https://pyinstaller.org/en/stable/ + # runtime-information.html#ld-library-path-libpath-considerations + # NOTE: All 4 cases need to be handled here: either of the two values can be there or not. + ld_env = "LD_LIBRARY_PATH" + ld_path_curr = os.environ.get(ld_env) + ld_path_orig = os.environ.get(f"{ld_env}_ORIG") + if ld_path_orig is not None: + os.environ[ld_env] = ld_path_orig + elif ld_path_curr is not None: + # pop current + os.environ.pop(ld_env) + + webbrowser.open_new_tab(url) + + if ld_path_curr is not None: + os.environ[ld_env] = ld_path_curr + elif ld_path_orig is not None: + # pop original + os.environ.pop(ld_env) + else: + webbrowser.open_new_tab(url) + + class ExponentialBackoff: def __init__( self, From 754800d17869fbee83b2524e87f4492db51391bd Mon Sep 17 00:00:00 2001 From: guihkx <626206+guihkx@users.noreply.github.com> Date: Sat, 10 Jun 2023 08:32:35 -0300 Subject: [PATCH 09/13] pyinstaller: avoid bundling icon and themes on Linux This greatly reduces the final size of the UPX-compressed binary, from 78.7 MiB to 54.9 MiB (a 23.8 MiB difference)! Co-authored-by: DevilXD --- build.spec | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/build.spec b/build.spec index c6d8d46b..e9d235fa 100644 --- a/build.spec +++ b/build.spec @@ -3,7 +3,7 @@ from __future__ import annotations import sys from pathlib import Path -from typing import TYPE_CHECKING +from typing import Any, TYPE_CHECKING SELF_PATH = str(Path(".").absolute()) if SELF_PATH not in sys.path: @@ -34,6 +34,7 @@ for source_path, dest_path, required in to_add: elif required: raise FileNotFoundError(str(source_path)) +hooksconfig: dict[str, Any] = {} binaries: list[tuple[Path, str]] = [] hiddenimports: list[str] = [ "PIL._tkinter_finder", @@ -51,6 +52,13 @@ if sys.platform == "linux": "gi.repository.Gtk", "gi.repository.GObject", ]) + hooksconfig = { + "gi": { + "icons": [], + "themes": [], + "languages": ["en_US"] + } + } block_cipher = None a = Analysis( @@ -59,11 +67,11 @@ a = Analysis( datas=datas, excludes=[], hookspath=[], - hooksconfig={}, noarchive=False, runtime_hooks=[], binaries=binaries, cipher=block_cipher, + hooksconfig=hooksconfig, hiddenimports=hiddenimports, win_private_assemblies=False, win_no_prefer_redirects=False, From 4613f9d26e00997680e33544c64dafb244b72291 Mon Sep 17 00:00:00 2001 From: guihkx <626206+guihkx@users.noreply.github.com> Date: Wed, 19 Apr 2023 12:40:04 -0300 Subject: [PATCH 10/13] ci: provide development builds for Linux --- .github/workflows/ci.yml | 87 +++++++++++++++++++++++++++++++++++----- 1 file changed, 76 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c9a4bdd..b88e635b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,25 +53,90 @@ jobs: - name: Create release folder run: | - $FolderName = "Twitch Drops Miner" - if (!(Test-Path $FolderName)) { - New-Item $FolderName -ItemType Directory - } - Copy-Item dist\*.exe -Destination $FolderName + $FolderName = 'Twitch Drops Miner' + New-Item $FolderName -ItemType Directory + Copy-Item dist\*.exe $FolderName Copy-Item manual.txt $FolderName - Compress-Archive -Path $FolderName -DestinationPath Twitch.Drops.Miner.zip + Compress-Archive -Path $FolderName -DestinationPath Twitch.Drops.Miner.Windows.zip - name: Upload build artifact uses: actions/upload-artifact@v3 with: if-no-files-found: error - name: Twitch.Drops.Miner - path: Twitch.Drops.Miner.zip + name: Twitch.Drops.Miner.Windows + path: Twitch.Drops.Miner.Windows.zip + + linux: + name: Linux + runs-on: ubuntu-20.04 + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - 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 -ri "s/^__version__\s*=\s*\"[^\"]+/\0.${{ steps.vars.outputs.sha_short }}/" version.py + + # NOTE: We're only use a custom version of Python here because truststore requires at least Python 3.10, but Ubuntu 20.04 has Python 3.8. + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install system dependencies + run: | + sudo apt update + sudo apt install gir1.2-appindicator3-0.1 libgirepository1.0-dev python3-tk + + - name: Set up Python virtual environment + run: | + python3 -m venv env + + - name: Install project dependencies + run: | + source ./env/bin/activate + python3 -m pip install wheel -r requirements.txt + + - name: Install PyInstaller + run: | + source ./env/bin/activate + python3 -m pip install pyinstaller + + - name: Create portable executable + run: | + source ./env/bin/activate + xvfb-run --auto-servernum pyinstaller build.spec + + - name: Show PyInstaller warnings + run: | + cat build/build/warn-build.txt || true + + - name: Create release folder + run: | + folder='Twitch Drops Miner' + mkdir "${folder}" + cp manual.txt dist/* "${folder}" + 7z a Twitch.Drops.Miner.Linux.zip "${folder}" + + - name: Upload build artifact + uses: actions/upload-artifact@v3 + with: + if-no-files-found: error + name: Twitch.Drops.Miner.Linux + path: Twitch.Drops.Miner.Linux.zip update_releases_page: - name: Upload build to Releases + name: Upload builds to Releases if: github.event_name != 'pull_request' - needs: windows + needs: + - windows + - linux runs-on: ubuntu-22.04 permissions: contents: write @@ -87,7 +152,7 @@ jobs: with: path: artifacts - - name: Upload build to Releases + - name: Upload builds to Releases uses: ncipollo/release-action@v1 with: allowUpdates: true From f3af6d15e37f3572315ba237319393a7cd71963c Mon Sep 17 00:00:00 2001 From: guihkx <626206+guihkx@users.noreply.github.com> Date: Sun, 21 May 2023 08:02:33 -0300 Subject: [PATCH 11/13] ci/linux: bundle an up to date version of libXft This fixes the following error when you launch the app with an emoji font installed (e.g. Noto Emoji): X Error of failed request: BadLength (poly request too large or internal Xlib length error) Major opcode of failed request: 139 (RENDER) Minor opcode of failed request: 20 (RenderAddGlyphs) Serial number of failed request: 277 Current serial number in output stream: 300 This has been fixed[1] in libXft 2.3.5[2], but unfortunately Ubuntu 20.04 (which we currently use in the Linux CI workflow), still has version 2.3.3, so we have to build it ourselves. [1] https://gitlab.freedesktop.org/xorg/lib/libxft/-/merge_requests/12 [2] https://lists.freedesktop.org/archives/xorg-announce/2022-September/003209.html --- .github/workflows/ci.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b88e635b..eaf3aedc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -108,10 +108,22 @@ jobs: source ./env/bin/activate python3 -m pip install pyinstaller + # NOTE: Remove this step if/once libxft gets updated to 2.3.5 or newer on Ubuntu 20.04, which currently has 2.3.3. + - name: Build a recent version of libXft + run: | + mkdir -p /tmp/libXft + cd /tmp/libXft + curl -L https://xorg.freedesktop.org/releases/individual/lib/libXft-2.3.8.tar.xz -o libXft.tar.xz + tar xvf libXft.tar.xz + cd libXft-* + ./configure --prefix=/tmp/libXft --sysconfdir=/etc --disable-static + make + make install-strip + - name: Create portable executable run: | source ./env/bin/activate - xvfb-run --auto-servernum pyinstaller build.spec + LD_LIBRARY_PATH=/tmp/libXft/lib xvfb-run --auto-servernum pyinstaller build.spec - name: Show PyInstaller warnings run: | From 2748f6e8cf31876e59a56be8b0415b9b1996eed4 Mon Sep 17 00:00:00 2001 From: guihkx <626206+guihkx@users.noreply.github.com> Date: Sat, 10 Jun 2023 16:57:49 -0300 Subject: [PATCH 12/13] readme: add notes about the linux build Co-authored-by: DevilXD --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 909f5f9e..46a34924 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,14 @@ Every ~60 seconds, the application sends a "minute watched" event to the channel - Successfully logging into your Twitch account in the application, may cause Twitch to send you a "New Login" notification email. This is normal - you can verify that it comes from your own IP address. The application uses the Twitch's SmartTV account linking process, so the detected browser during the login should signify that as well. - The time remaining timer always countdowns a single minute and then stops - it is then restarted only after the application redetermines the remaining time. This "redetermination" can happen as early as at 10 seconds in a minute remaining, and as late as 20 seconds after the timer reaches zero (especially when finishing mining a drop), but is generally only an approximation and does not represent nor affect actual mining speed. The time variations are due to Twitch sometimes not reporting drop progress at all, or reporting progress for the wrong drop - these cases have all been accounted for in the application though. +### Notes about the Linux build: + +- The Linux app is built and distributed in a portable-executable format (similar to [AppImages](https://appimage.org/)). +- The Linux app should work out of the box on any modern distribution (as long as it has `glibc>=2.31` and a working display server). +- Every feature of the app is expected to work on Linux just as well as it does on Windows. If you find something that's broken, please [open a new issue](https://github.com/DevilXD/TwitchDropsMiner/issues/new). +- 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! + ### Support
From e71ce787c7c979aea33382c00794004e8adec3f6 Mon Sep 17 00:00:00 2001 From: DevilXD Date: Sun, 11 Jun 2023 00:07:07 +0200 Subject: [PATCH 13/13] Add Windows build notes --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 46a34924..ca53dbe7 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,12 @@ Every ~60 seconds, the application sends a "minute watched" event to the channel - Successfully logging into your Twitch account in the application, may cause Twitch to send you a "New Login" notification email. This is normal - you can verify that it comes from your own IP address. The application uses the Twitch's SmartTV account linking process, so the detected browser during the login should signify that as well. - The time remaining timer always countdowns a single minute and then stops - it is then restarted only after the application redetermines the remaining time. This "redetermination" can happen as early as at 10 seconds in a minute remaining, and as late as 20 seconds after the timer reaches zero (especially when finishing mining a drop), but is generally only an approximation and does not represent nor affect actual mining speed. The time variations are due to Twitch sometimes not reporting drop progress at all, or reporting progress for the wrong drop - these cases have all been accounted for in the application though. +### Notes about the Windows build: + +- To achieve a portable-executable format, the application is packaged with PyInstaller into an `EXE`. Some non-mainstream antivirus engines might report the packaged executable as a trojan, because PyInstaller has been used by others to package malicious Python code in the past. These reports can be safely ignored. If you absolutely do not trust the executable, you'll have to install Python yourself and run everything from source. +- The executable uses the `%TEMP%` directory for temporary runtime storage of files, that don't need to be exposed to the user (like compiled code and translation files). For persistent storage, the directory the executable resides in is used instead. +- The autostart feature is implemented as a registry entry to the current user's (`HKCU`) autostart key. It is only altered when toggling the respective option. If you relocate the app to a different directory, the autostart feature will stop working, until you toggle the option off and back on again + ### Notes about the Linux build: - The Linux app is built and distributed in a portable-executable format (similar to [AppImages](https://appimage.org/)).