Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
14 changes: 13 additions & 1 deletion .github/workflows/build-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -39,3 +49,5 @@ jobs:
files: |
build/*.AppImage
build/*.deb
build/*.rpm
build/*.flatpak
1 change: 1 addition & 0 deletions assets/io.github.protonshift.metainfo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<launchable type="desktop-id">io.github.protonshift.desktop</launchable>
<url type="homepage">https://github.com/I4cTime/protonshift</url>
<releases>
<release version="0.8.8" date="2026-04-15"/>
<release version="0.8.7" date="2026-04-15"/>
<release version="0.8.6" date="2026-04-15"/>
<release version="0.8.5" date="2026-04-15"/>
Expand Down
4 changes: 2 additions & 2 deletions electron/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "protonshift",
"version": "0.8.7",
"version": "0.8.8",
"description": "Linux game configuration toolkit",
"main": "dist/main.js",
"scripts": {
Expand Down Expand Up @@ -73,7 +73,7 @@
}
],
"linux": {
"target": ["AppImage", "deb"],
"target": ["AppImage", "deb", "rpm", "flatpak"],
"category": "Game",
"icon": "../assets",
"desktop": {
Expand Down
2 changes: 1 addition & 1 deletion electron/renderer/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "protonshift-renderer",
"version": "0.8.7",
"version": "0.8.8",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
Expand Down
152 changes: 95 additions & 57 deletions electron/renderer/src/app/system/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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",
Expand Down Expand Up @@ -204,12 +210,12 @@ export default function SystemPage() {
)}

{/* Power profile */}
{data && data.power_profiles.length > 0 && (
{data && (
<div className="space-y-3">
<label className="flex items-center gap-2 text-sm font-medium text-text-secondary">
<BatteryCharging className="size-4 text-neon-cyan" />
Power Profile
{data.current_power_profile && (
{data.power_profiles.length > 0 && data.current_power_profile && (
<Chip
size="sm"
color="accent"
Expand All @@ -220,65 +226,97 @@ export default function SystemPage() {
</Chip>
)}
</label>
<GlowCard className="p-5">
<p className="mb-4 text-xs leading-relaxed text-text-muted">
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.
</p>
<div className="flex flex-col gap-2">
{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 ? (
<GlowCard className="p-5">
<p className="mb-4 text-xs leading-relaxed text-text-muted">
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.
</p>
<div className="flex flex-col gap-2">
{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 (
<button
key={profile}
onClick={() => handleSetProfile(profile)}
className={`flex items-center gap-3 rounded-xl border p-3.5 text-left transition-all ${
active
? "border-neon-cyan/40 bg-neon-cyan/10"
: "border-border bg-surface-secondary/30 hover:border-border-secondary"
}`}
>
<div
className={`flex size-9 shrink-0 items-center justify-center rounded-lg bg-surface-secondary ${meta?.color ?? "text-text-secondary"}`}
return (
<button
key={profile}
onClick={() => handleSetProfile(profile)}
className={`flex items-center gap-3 rounded-xl border p-3.5 text-left transition-all ${
active
? "border-neon-cyan/40 bg-neon-cyan/10"
: "border-border bg-surface-secondary/30 hover:border-border-secondary"
}`}
>
<ProfileIcon className="size-5" />
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<span className="text-sm font-semibold capitalize text-text-primary">
{profile}
</span>
{active && (
<Chip
size="sm"
color="accent"
variant="soft"
className="text-[10px]"
>
Active
</Chip>
<div
className={`flex size-9 shrink-0 items-center justify-center rounded-lg bg-surface-secondary ${meta?.color ?? "text-text-secondary"}`}
>
<ProfileIcon className="size-5" />
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<span className="text-sm font-semibold capitalize text-text-primary">
{profile}
</span>
{active && (
<Chip
size="sm"
color="accent"
variant="soft"
className="text-[10px]"
>
Active
</Chip>
)}
</div>
{meta?.description && (
<p className="mt-0.5 text-xs text-text-muted">
{meta.description}
</p>
)}
</div>
{meta?.description && (
<p className="mt-0.5 text-xs text-text-muted">
{meta.description}
</p>
)}
</div>
</button>
);
})}
</div>
</GlowCard>
</button>
);
})}
</div>
</GlowCard>
) : (
<GlowCard className="p-5">
<div className="flex gap-3">
<AlertCircle className="mt-0.5 size-5 shrink-0 text-text-muted" />
<div className="min-w-0 space-y-2 text-xs leading-relaxed text-text-muted">
<p className="font-medium text-text-secondary">
Profile switching unavailable
</p>
<p>
No supported power tool responded. ProtonShift uses{" "}
<code className="rounded bg-surface-deep px-1 py-0.5 font-mono text-[10px] text-neon-cyan">
system76-power
</code>{" "}
on Pop!_OS (query:{" "}
<code className="rounded bg-surface-deep px-1 py-0.5 font-mono text-[10px] text-neon-cyan">
system76-power profile
</code>
) or{" "}
<code className="rounded bg-surface-deep px-1 py-0.5 font-mono text-[10px] text-neon-cyan">
powerprofilesctl
</code>{" "}
from power-profiles-daemon elsewhere.
</p>
<p>
Sandboxed installs (for example Flatpak) may not see
those commands on the host unless permissions allow it.
</p>
</div>
</div>
</GlowCard>
)}
</div>
)}

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion src/game_setup_hub/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""ProtonShift — game configuration for Pop!_OS, Ubuntu, and related distros."""

__version__ = "0.8.7"
__version__ = "0.8.8"
2 changes: 1 addition & 1 deletion src/game_setup_hub/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/game_setup_hub/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
47 changes: 26 additions & 21 deletions src/game_setup_hub/gpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down
Loading