diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index fb6e47a..5bb7943 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -14,7 +14,7 @@ body: attributes: label: ProtonShift version description: "Check the app's About dialog or `electron/package.json`." - placeholder: "0.8.7" + placeholder: "0.8.8" validations: required: true diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 5003f9c..ca75268 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -27,10 +27,20 @@ jobs: cache: pnpm cache-dependency-path: electron/pnpm-lock.yaml + - name: Install Linux packaging tools (rpm, flatpak) + run: | + sudo apt-get update + sudo apt-get install -y rpm flatpak flatpak-builder + flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo + flatpak install --user -y flathub \ + org.freedesktop.Platform//24.08 \ + org.freedesktop.Sdk//24.08 \ + org.electronjs.Electron2.BaseApp//24.08 + - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Build AppImage & .deb + - name: Build Linux packages (AppImage, deb, rpm, flatpak) run: pnpm run dist - name: Upload artifacts to release @@ -39,3 +49,5 @@ jobs: files: | build/*.AppImage build/*.deb + build/*.rpm + build/*.flatpak diff --git a/assets/io.github.protonshift.metainfo.xml b/assets/io.github.protonshift.metainfo.xml index dc14419..ec991ac 100644 --- a/assets/io.github.protonshift.metainfo.xml +++ b/assets/io.github.protonshift.metainfo.xml @@ -26,6 +26,7 @@ io.github.protonshift.desktop https://github.com/I4cTime/protonshift + diff --git a/electron/package.json b/electron/package.json index 448b24c..76ae4e1 100644 --- a/electron/package.json +++ b/electron/package.json @@ -1,6 +1,6 @@ { "name": "protonshift", - "version": "0.8.7", + "version": "0.8.8", "description": "Linux game configuration toolkit", "main": "dist/main.js", "scripts": { @@ -73,7 +73,7 @@ } ], "linux": { - "target": ["AppImage", "deb"], + "target": ["AppImage", "deb", "rpm", "flatpak"], "category": "Game", "icon": "../assets", "desktop": { diff --git a/electron/renderer/package.json b/electron/renderer/package.json index be42761..1d46514 100644 --- a/electron/renderer/package.json +++ b/electron/renderer/package.json @@ -1,6 +1,6 @@ { "name": "protonshift-renderer", - "version": "0.8.7", + "version": "0.8.8", "private": true, "scripts": { "dev": "next dev --turbopack", diff --git a/electron/renderer/src/app/system/page.tsx b/electron/renderer/src/app/system/page.tsx index 54fcd90..b9a8372 100644 --- a/electron/renderer/src/app/system/page.tsx +++ b/electron/renderer/src/app/system/page.tsx @@ -15,6 +15,7 @@ import { Info, Layers, Trash2, + AlertCircle, } from "lucide-react"; import { Button, Chip, Spinner, Tooltip } from "@heroui/react"; import { PageShell } from "@/components/page-shell"; @@ -38,6 +39,11 @@ const POWER_PROFILE_META: Record< color: "text-yellow-400", description: "Dynamic clocks and fan curves — good balance for most games", }, + battery: { + icon: Leaf, + color: "text-emerald-400", + description: "Low power draw and quieter fans — best on battery or light desktop use", + }, "power-saver": { icon: Leaf, color: "text-green-400", @@ -204,12 +210,12 @@ export default function SystemPage() { )} {/* Power profile */} - {data && data.power_profiles.length > 0 && ( + {data && (
- -

- Switch between system power profiles. Performance maximises - clocks for gaming, balanced adapts dynamically, and - power-saver prioritises battery life and quiet fans. The - change takes effect immediately. -

-
- {data.power_profiles.map((profile) => { - const active = - profile.toLowerCase() === - data.current_power_profile?.toLowerCase(); - const meta = - POWER_PROFILE_META[profile.toLowerCase()] ?? - POWER_PROFILE_META["balanced"]; - const ProfileIcon = meta?.icon ?? Zap; + {data.power_profiles.length > 0 ? ( + +

+ Switch between system power profiles. Performance maximises + clocks for gaming, balanced adapts dynamically, and + power-saver (or battery on Pop!_OS) prioritises lower draw + and quieter fans. The change takes effect immediately. +

+
+ {data.power_profiles.map((profile) => { + const active = + profile.toLowerCase() === + data.current_power_profile?.toLowerCase(); + const meta = + POWER_PROFILE_META[profile.toLowerCase()] ?? + POWER_PROFILE_META["balanced"]; + const ProfileIcon = meta?.icon ?? Zap; - return ( - - ); - })} -
-
+ + ); + })} +
+
+ ) : ( + +
+ +
+

+ Profile switching unavailable +

+

+ No supported power tool responded. ProtonShift uses{" "} + + system76-power + {" "} + on Pop!_OS (query:{" "} + + system76-power profile + + ) or{" "} + + powerprofilesctl + {" "} + from power-profiles-daemon elsewhere. +

+

+ Sandboxed installs (for example Flatpak) may not see + those commands on the host unless permissions allow it. +

+
+
+
+ )}
)} diff --git a/pyproject.toml b/pyproject.toml index ee519fd..a89465b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "protonshift" -version = "0.8.7" +version = "0.8.8" description = "Linux game configuration toolkit: GPU, launch options, Proton, env vars" readme = "README.md" requires-python = ">=3.12" diff --git a/src/game_setup_hub/__init__.py b/src/game_setup_hub/__init__.py index 75fd889..f6ceb2f 100644 --- a/src/game_setup_hub/__init__.py +++ b/src/game_setup_hub/__init__.py @@ -1,3 +1,3 @@ """ProtonShift — game configuration for Pop!_OS, Ubuntu, and related distros.""" -__version__ = "0.8.7" +__version__ = "0.8.8" diff --git a/src/game_setup_hub/api.py b/src/game_setup_hub/api.py index 7bceac4..30771d2 100644 --- a/src/game_setup_hub/api.py +++ b/src/game_setup_hub/api.py @@ -324,7 +324,7 @@ class StatusResponse(BaseModel): # App setup # --------------------------------------------------------------------------- -app = FastAPI(title="ProtonShift API", version="0.8.7") +app = FastAPI(title="ProtonShift API", version="0.8.8") app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]) # Locks for serializing writes to shared resources diff --git a/src/game_setup_hub/app.py b/src/game_setup_hub/app.py index b497aec..fb79e75 100644 --- a/src/game_setup_hub/app.py +++ b/src/game_setup_hub/app.py @@ -1032,7 +1032,7 @@ def on_about(_action, _param): about = Adw.AboutWindow( transient_for=win, application_name="ProtonShift", - version="0.8.7", + version="0.8.8", developer_name="ProtonShift", website="https://github.com/protonshift/protonshift", application_icon="io.github.protonshift", diff --git a/src/game_setup_hub/gpu.py b/src/game_setup_hub/gpu.py index c36f641..283b908 100644 --- a/src/game_setup_hub/gpu.py +++ b/src/game_setup_hub/gpu.py @@ -88,23 +88,25 @@ def _read_hwmon_temp(device_path: Path) -> float | None: return None -def get_power_profiles() -> list[str]: - """Get available power profiles.""" - # system76-power (Pop) +def _system76_power_profile_query() -> subprocess.CompletedProcess[str] | None: + """Run `system76-power profile` (no args). Newer CLI prints `Power Profile: …` plus details.""" try: - r = subprocess.run(["system76-power", "profile", "list"], capture_output=True, text=True, timeout=3) - if r.returncode == 0: - profiles = [] - for line in r.stdout.split("\n"): - if re.match(r"^\s*\*\s*\w+", line): - # * Performance, * Balanced, etc. - m = re.search(r"\*\s*(\w+)", line) - if m: - profiles.append(m.group(1).lower().capitalize()) - if profiles: - return profiles + return subprocess.run( + ["system76-power", "profile"], + capture_output=True, + text=True, + timeout=3, + ) except FileNotFoundError: - pass + return None + + +def get_power_profiles() -> list[str]: + """Get available power profiles.""" + # system76-power (Pop!_OS) — modern CLI has no `profile list`; values are battery|balanced|performance + r = _system76_power_profile_query() + if r is not None and r.returncode == 0 and r.stdout.strip(): + return ["battery", "balanced", "performance"] # power-profiles-daemon (Ubuntu) try: @@ -126,12 +128,15 @@ def get_power_profiles() -> list[str]: def get_current_power_profile() -> str | None: """Get current power profile.""" - try: - r = subprocess.run(["system76-power", "profile"], capture_output=True, text=True, timeout=3) - if r.returncode == 0 and r.stdout: - return r.stdout.strip().lower().capitalize() - except FileNotFoundError: - pass + r = _system76_power_profile_query() + if r is not None and r.returncode == 0 and r.stdout: + for line in r.stdout.splitlines(): + m = re.search(r"Power Profile:\s*(\S+)", line, re.IGNORECASE) + if m: + return m.group(1).lower() + first = r.stdout.strip().split("\n", 1)[0].strip() + if first and ":" not in first and len(first) < 40: + return first.lower() try: r = subprocess.run(["powerprofilesctl", "get"], capture_output=True, text=True, timeout=3) if r.returncode == 0 and r.stdout: