4 Commits
Author SHA1 Message Date
oxpoe 76790e0321 feat(autostart): tray ikut autostart saat login, tersembunyi (--tray-only)
- checkbox 'Mulai otomatis saat login' kini mengurus layanan (sender) DAN entri autostart tray
- entri autostart milik tool integrasi AppImage yang menunjuk AppImage sama ditandai Hidden=true
  supaya saat login hanya satu instance tray yang jalan, tanpa jendela
- install.sh ikut memasang entri autostart tray + menghapusnya saat --uninstall
- sender: retry 1 detik dan pesan galat izin tidak diulang-ulang (ACL udev baru aktif
  beberapa detik setelah login) — sebelumnya ~12 detik penuh galat beruntun saat boot
- versi 1.3.3
2026-10-13 10:27:05 +07:00
oxpoe c3d34a2788 fix(packaging): nama entri menu di dalam AppImage jadi 'IceBurg LCD'
AppImage manager menyalin .desktop dari dalam bundel, sehingga entri di app drawer
masih bernama merek vendor walau UI aplikasinya sudah diganti. Kini bundel membawa
Name=IceBurg LCD (GenericName/Comment juga tanpa merek). Versi 1.3.2
2026-10-13 09:51:06 +07:00
oxpoe 4711ef5532 refactor(ui): nama aplikasi jadi 'IceBurg LCD' (tanpa merek vendor di UI)
- judul jendela, tooltip tray, notifikasi, nama aplikasi Qt, dan deskripsi unit systemd
- entri menu di install.sh + petunjuk di jendela
- README: catatan bahwa UI tidak memakai merek; repo tetap menyebut merek hanya untuk
  menjelaskan hardware yang didukung
- versi 1.3.1
2026-10-13 09:44:17 +07:00
oxpoe c384df7cac feat: install.sh — pemasangan/pelepasan satu perintah
- unduh rilis terbaru (+ vX.Y.Z), verifikasi sha256, pasang AppImage, sender, unit systemd,
  entri menu, ikon, dan aturan udev
- opsi: --uninstall, --remove-data, --no-service, --version
- udev memakai sudo; kalau dijalankan lewat pipa tanpa kata sandi, perintahnya dicetak
- README: cara satu perintah di bagian Install
2026-10-13 09:25:11 +07:00
6 changed files with 297 additions and 22 deletions

No files matched your search

+16 -2
View File
@@ -101,7 +101,10 @@ temperature source (Tctl vs Tccd).
send/sample interval — saved to `~/.config/gamemax-report/config.json`
- **Clear mode indicators**: green "showing sensor data" / orange "blank (zeros)" /
grey "sending stopped", dimmed tray icon and a desktop notification per change
- **Autostart** via a systemd user unit (equivalent of the vendor's Task Scheduler trick)
- **Autostart** at login: a systemd user unit for the sender **and** an XDG autostart entry that
starts the tray app with `--tray-only` (no window pops up; open it from the tray menu). The
*Mulai otomatis saat login* checkbox manages both, and disables any other autostart entry that
would launch the same AppImage with a window.
- **Plug and play**: the service waits for the device, streams while present and keeps
waiting through unplug/replug; a udev rule grants access to the logged-in user
- **Single instance**: launching the app again just raises the running window
@@ -109,7 +112,8 @@ temperature source (Tctl vs Tccd).
- **Donate / GitHub buttons** in the app (window and tray menu)
- **AppImage build** script (`packaging/build_appimage.sh`, PySide6 bundled)
The GUI labels are in Indonesian.
The GUI labels are in Indonesian. The app itself is named **IceBurg LCD** (no vendor brand in
the UI); the repository keeps the hardware and brand name only to describe what it supports.
## Requirements
@@ -123,6 +127,16 @@ The GUI labels are in Indonesian.
## Install
One command (downloads the latest release, verifies its SHA-256, installs the app, the
systemd user unit, the menu entry and the udev rule):
```bash
curl -fsSL https://raw.githubusercontent.com/oxpoe/Gamemax-linux-apps/main/install.sh | bash
# uninstall: bash install.sh --uninstall (add --remove-data to drop settings too)
```
Manual steps, if you prefer to see every command:
```bash
# 1. device access for the logged-in user (once, needs root)
sudo cp packaging/70-gamemax.rules /etc/udev/rules.d/
Executable
+194
View File
@@ -0,0 +1,194 @@
#!/usr/bin/env bash
# GAMEMAX LCD — pemasangan / pelepasan satu perintah.
#
# curl -fsSL https://raw.githubusercontent.com/oxpoe/Gamemax-linux-apps/main/install.sh | bash
#
# Opsi:
# --uninstall lepaskan pemasangan (layanan, aplikasi, config, ikon, entri menu)
# --no-service pasang aplikasi saja, jangan aktifkan layanan/autostart
# --version vX.Y.Z pasang versi rilis tertentu (default: rilis terbaru)
# --remove-data saat --uninstall: hapus juga ~/.config/gamemax-report
#
# UdeV: aturan izin device dipasang dengan sudo. Kalau skrip dijalankan lewat pipa
# (curl | bash) dan sudo butuh kata sandi, perintahnya dicetak untuk dijalankan manual.
set -euo pipefail
REPO="oxpoe/Gamemax-linux-apps"
ASSET="GAMEMAX-LCD-x86_64.AppImage"
RAW="https://raw.githubusercontent.com/$REPO/main"
OPT_DIR="$HOME/.local/opt/gamemax-lcd"
APP="$OPT_DIR/GAMEMAX-LCD.AppImage"
DATA_DIR="$HOME/.local/share/gamemax-report"
UNIT="$HOME/.config/systemd/user/gamemax-report.service"
DESKTOP_DIR="$HOME/.local/share/applications"
ICON_DIR="$HOME/.local/share/icons/hicolor/128x128/apps"
RULE=/etc/udev/rules.d/70-gamemax.rules
SERVICE="gamemax-report.service"
UNINSTALL=0 WITH_SERVICE=1 REMOVE_DATA=0 VERSION="latest"
say() { printf '%s\n' "$*"; }
die() { printf 'galat: %s\n' "$*" >&2; exit 1; }
while [ $# -gt 0 ]; do
case "$1" in
--uninstall) UNINSTALL=1 ;;
--no-service) WITH_SERVICE=0 ;;
--remove-data) REMOVE_DATA=1 ;;
--version) shift; VERSION="${1:?--version butuh nilai seperti v1.3.0}" ;;
-h|--help) sed -n '2,14p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
*) die "opsi tidak dikenal: $1" ;;
esac
shift
done
need() { command -v "$1" >/dev/null 2>&1 || die "perintah '$1' tidak tersedia"; }
# ---------------------------------------------------------------- uninstall
if [ "$UNINSTALL" = 1 ]; then
say "== melepaskan GAMEMAX LCD =="
command -v systemctl >/dev/null 2>&1 && systemctl --user disable --now "$SERVICE" 2>/dev/null || true
rm -f "$UNIT"
command -v systemctl >/dev/null 2>&1 && { systemctl --user daemon-reload 2>/dev/null || true; \
systemctl --user reset-failed "$SERVICE" 2>/dev/null || true; }
rm -rf "$OPT_DIR" "$DATA_DIR"
rm -f "$DESKTOP_DIR/gamemax-lcd.desktop" "$ICON_DIR/gamemax-lcd.png"
rm -f "$HOME/.config/autostart/iceburg-lcd.desktop"
[ "$REMOVE_DATA" = 1 ] && rm -rf "$HOME/.config/gamemax-report"
command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$DESKTOP_DIR" 2>/dev/null || true
if [ -f "$RULE" ]; then
if sudo -n rm -f "$RULE" 2>/dev/null; then
sudo -n udevadm control --reload 2>/dev/null || true
say " aturan udev dihapus"
else
say " aturan udev masih ada (butuh sudo): sudo rm -f $RULE && sudo udevadm control --reload"
fi
fi
say "selesai. Aplikasi, layanan, dan berkas pendukung sudah dilepas."
say "catatan: konfigurasi ~/.config/gamemax-report hanya dihapus dengan --remove-data."
exit 0
fi
# ---------------------------------------------------------------- install
need curl
need sha256sum
command -v systemctl >/dev/null 2>&1 || die "systemd (systemctl) dibutuhkan untuk layanan"
if [ "$VERSION" = "latest" ]; then
BASE="https://github.com/$REPO/releases/latest/download"
else
BASE="https://github.com/$REPO/releases/download/$VERSION"
fi
say "== GAMEMAX LCD: pemasangan ($VERSION) =="
mkdir -p "$OPT_DIR" "$DATA_DIR" "$DESKTOP_DIR" "$ICON_DIR" "$HOME/.config/systemd/user"
say "1/6 mengunduh aplikasi …"
curl -fsSL -o "$APP.part" "$BASE/$ASSET"
curl -fsSL -o "$APP.sha256" "$BASE/$ASSET.sha256"
say "2/6 memverifikasi checksum …"
EXPECTED="$(awk '{print $1}' "$APP.sha256")"
ACTUAL="$(sha256sum "$APP.part" | awk '{print $1}')"
[ "$EXPECTED" = "$ACTUAL" ] || { rm -f "$APP.part"; die "checksum tidak cocok — unduhan dibatalkan"; }
say " ok: ${ACTUAL:0:16}…"
mv -f "$APP.part" "$APP"
chmod +x "$APP"
rm -f "$APP.sha256"
say "3/6 mengambil sender + ikon dari bundel …"
TMP="$(mktemp -d)"
( cd "$TMP" && "$APP" --appimage-extract >/dev/null 2>&1 ) || true
INTERNAL="$TMP/squashfs-root/usr/bin/_internal"
if [ -f "$INTERNAL/gamemax_report.py" ]; then
cp "$INTERNAL/gamemax_report.py" "$DATA_DIR/gamemax_report.py"
else
say " ! sender tidak ditemukan di bundel — unduh dari repo"
curl -fsSL -o "$DATA_DIR/gamemax_report.py" "$RAW/src/gamemax_report.py"
fi
[ -f "$INTERNAL/icon.png" ] && cp "$INTERNAL/icon.png" "$ICON_DIR/gamemax-lcd.png" \
|| curl -fsSL -o "$ICON_DIR/gamemax-lcd.png" "$RAW/src/icon.png"
rm -rf "$TMP"
say "4/6 memasang layanan systemd (autostart + plug and play) …"
cat > "$UNIT" <<EOF
[Unit]
Description=IceBurg LCD — HID sensor display bridge (Linux)
After=graphical-session.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 $DATA_DIR/gamemax_report.py --wait --interval 0.5 --sample 0.5
Restart=always
RestartSec=3
[Install]
WantedBy=default.target
EOF
systemctl --user daemon-reload
if [ "$WITH_SERVICE" = 1 ]; then
systemctl --user enable --now "$SERVICE"
sleep 2
say " layanan: $(systemctl --user is-active "$SERVICE") / $(systemctl --user is-enabled "$SERVICE")"
else
say " dilewati (--no-service)"
fi
say "5/6 memasang entri menu, ikon, dan autostart tray …"
cat > "$DESKTOP_DIR/gamemax-lcd.desktop" <<EOF
[Desktop Entry]
Type=Application
Name=IceBurg LCD
GenericName=Kontrol LCD AIO
Comment=Status, nyala/mati, pemantauan, dan pengaturan LCD AIO IceBurg Digital
Exec="$APP"
Icon=gamemax-lcd
Terminal=false
Categories=System;Utility;
EOF
command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$DESKTOP_DIR" 2>/dev/null || true
command -v gtk-update-icon-cache >/dev/null 2>&1 && \
gtk-update-icon-cache -f -t "$HOME/.local/share/icons/hicolor" 2>/dev/null || true
mkdir -p "$HOME/.config/autostart"
cat > "$HOME/.config/autostart/iceburg-lcd.desktop" <<EOF
[Desktop Entry]
Type=Application
Name=IceBurg LCD (tray)
Comment=Layar LCD AIO — berjalan di tray saat login (tanpa jendela)
Exec="$APP" --tray-only
Icon=iceburg-lcd
Terminal=false
X-GNOME-Autostart-enabled=true
EOF
say " autostart tray: ~/.config/autostart/iceburg-lcd.desktop (--tray-only)"
say "6/6 aturan udev (izin device) …"
RULE_TMP="$(mktemp)"
curl -fsSL -o "$RULE_TMP" "$RAW/packaging/70-gamemax.rules"
if [ -f "$RULE" ] && cmp -s "$RULE" "$RULE_TMP"; then
say " sudah terpasang"
elif sudo -n install -m 644 "$RULE_TMP" "$RULE" 2>/dev/null; then
sudo -n udevadm control --reload 2>/dev/null || true
sudo -n udevadm trigger --subsystem-match=hidraw 2>/dev/null || true
say " terpasang"
elif [ -t 0 ]; then
sudo install -m 644 "$RULE_TMP" "$RULE" && sudo udevadm control --reload && \
sudo udevadm trigger --subsystem-match=hidraw && say " terpasang"
else
say " ! butuh sudo — jalankan sekali:"
say " sudo install -m 644 $RULE_TMP $RULE && sudo udevadm control --reload && sudo udevadm trigger --subsystem-match=hidraw"
fi
rm -f "$RULE_TMP"
say
say "== selesai =="
say " aplikasi : $APP"
say " sender : $DATA_DIR/gamemax_report.py"
say " menu : GAMEMAX IceBurg LCD"
say " layanan : systemctl --user status $SERVICE"
say " log : journalctl --user -u $SERVICE -f"
say
say "buka aplikasinya untuk mengatur slot kipas/sumber suhu, atau:"
say " $APP --window"
+3 -3
View File
@@ -42,9 +42,9 @@ cp "$ROOT_DIR/src/icon.png" "$APPDIR/gamemax-lcd.png"
cat > "$APPDIR/gamemax-lcd.desktop" <<'EOF'
[Desktop Entry]
Type=Application
Name=GAMEMAX IceBurg LCD
GenericName=AI LCD controller
Comment=Status, on/off, monitor, and settings for the GAMEMAX IceBurg AIO LCD
Name=IceBurg LCD
GenericName=Kontrol LCD AIO
Comment=Status, nyala/mati, pemantauan, dan pengaturan LCD AIO IceBurg Digital
Exec=gamemax-lcd
Icon=gamemax-lcd
Terminal=false
+1 -1
View File
@@ -1,5 +1,5 @@
[Unit]
Description=GAMEMAX HID sensor display bridge (PcInfoMonitorPro replacement)
Description=IceBurg LCD — HID sensor display bridge (Linux)
Documentation=file:///mnt/SSD/Work/REVERSE-EGENERING/analysis/01-recon.md
After=graphical-session.target
+76 -13
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""GAMEMAX IceBurg LCD — GUI tray: status, nyala/mati, pemantauan beranimasi, pengaturan.
"""IceBurg LCD — GUI tray: status, nyala/mati, pemantauan beranimasi, pengaturan.
Streaming tetap dijalankan oleh layanan systemd (gamemax-report.service); GUI ini
mengendalikannya dan memakai gamemax_report.py sebagai pengirim.
@@ -8,6 +8,7 @@ mengendalikannya dan memakai gamemax_report.py sebagai pengirim.
gamemax_gui.py --tray-only # tanpa jendela
gamemax_gui.py --selftest # uji muat tanpa tampilan (untuk build AppImage)
"""
import glob
import hashlib
import json
import math
@@ -32,7 +33,8 @@ from PySide6.QtWidgets import (
)
from PySide6.QtNetwork import QLocalServer, QLocalSocket
APP_VERSION = "1.3.0"
APP_VERSION = "1.3.3"
APP_NAME = "IceBurg LCD"
REPO = "oxpoe/Gamemax-linux-apps"
RELEASES_API = f"https://api.github.com/repos/{REPO}/releases/latest"
UPDATE_ASSET = "GAMEMAX-LCD-x86_64.AppImage"
@@ -48,9 +50,11 @@ CONFIG_PATH = gr.CONFIG_PATH
INSTALL_DIR = os.path.expanduser("~/.local/share/gamemax-report")
UNIT_PATH = os.path.expanduser(f"~/.config/systemd/user/{SERVICE}")
RULE_PATH = "/etc/udev/rules.d/70-gamemax.rules"
AUTOSTART_DIR = os.path.expanduser("~/.config/autostart")
AUTOSTART_FILE = os.path.join(AUTOSTART_DIR, "iceburg-lcd.desktop")
UNIT_TEMPLATE = """[Unit]
Description=GAMEMAX HID sensor display bridge (PcInfoMonitorPro replacement)
Description=IceBurg LCD — HID sensor display bridge (Linux)
After=graphical-session.target
[Service]
@@ -162,6 +166,63 @@ def open_link(url, parent=None):
parent.log(f"tidak bisa membuka tautan: {url}")
def other_autostart_entries(appimage):
"""Entri autostart lain yang menjalankan AppImage yang sama (mis. buatan tool integrasi)."""
found = []
for path in glob.glob(os.path.join(AUTOSTART_DIR, "*.desktop")):
if path == AUTOSTART_FILE:
continue
try:
text = open(path).read()
except OSError:
continue
if appimage and appimage in text:
found.append(path)
return found
def set_tray_autostart(enable, appimage):
"""Autostart ikon tray saat login, tersembunyi (tanpa jendela)."""
if not appimage:
return "tidak berjalan dari AppImage — autostart tray dilewati"
os.makedirs(AUTOSTART_DIR, exist_ok=True)
notes = []
if enable:
with open(AUTOSTART_FILE, "w") as fh:
fh.write(f"""[Desktop Entry]
Type=Application
Name={APP_NAME} (tray)
Comment=Layar LCD AIO — berjalan di tray saat login (tanpa jendela)
Exec="{appimage}" --tray-only
Icon=iceburg-lcd
Terminal=false
X-GNOME-Autostart-enabled=true
""")
notes.append("autostart tray dibuat (--tray-only, tanpa jendela)")
for path in other_autostart_entries(appimage):
try:
text = open(path).read()
if "Hidden=true" not in text:
with open(path, "a") as fh:
fh.write("\nHidden=true\n")
notes.append(f"entri autostart lain dinonaktifkan: {os.path.basename(path)}")
except OSError:
pass
else:
if os.path.exists(AUTOSTART_FILE):
os.unlink(AUTOSTART_FILE)
notes.append("autostart tray dihapus")
for path in other_autostart_entries(appimage):
try:
lines = [l for l in open(path).read().splitlines() if l.strip() != "Hidden=true"]
with open(path, "w") as fh:
fh.write("\n".join(lines) + "\n")
notes.append(f"entri autostart lain diaktifkan kembali: {os.path.basename(path)}")
except OSError:
pass
return "; ".join(notes) or "tidak ada perubahan autostart tray"
def systemctl(*args):
return subprocess.run(["systemctl", "--user", *args], capture_output=True, text=True)
@@ -331,7 +392,7 @@ class Window(QWidget):
super().__init__()
self.tray = tray
self.prev_stat = gr.stat_snapshot()
self.setWindowTitle(f"GAMEMAX IceBurg LCD v{APP_VERSION}")
self.setWindowTitle(f"{APP_NAME} v{APP_VERSION}")
self.setWindowIcon(QIcon(icon_path()))
self.setMinimumWidth(640)
root = QVBoxLayout(self)
@@ -341,7 +402,7 @@ class Window(QWidget):
if isinstance(geom, list) and len(geom) == 4:
self.setGeometry(*[int(g) for g in geom])
hint = QLabel("Kontrol layar LCD AIO GAMEMAX. Pilih slot kipas/sumber suhu di "
hint = QLabel("Kontrol layar LCD AIO IceBurg Digital. Pilih slot kipas/sumber suhu di "
"<b>Pengaturan</b> lalu klik <b>Simpan &amp; terapkan</b>.")
hint.setObjectName("hint")
hint.setWordWrap(True)
@@ -563,7 +624,7 @@ class Window(QWidget):
return
box = QMessageBox(self)
box.setWindowTitle("Tutup GAMEMAX LCD")
box.setWindowTitle(f"Tutup {APP_NAME}")
box.setText("Sembunyikan ke tray atau keluar sepenuhnya?")
box.setInformativeText("Selama tersembunyi di tray, data sensor tetap dikirim ke layar.")
remember = QCheckBox("Ingat pilihan ini")
@@ -687,8 +748,10 @@ class Window(QWidget):
systemctl("daemon-reload")
res = systemctl("enable" if checked else "disable", SERVICE)
state = service_enabled()
note = set_tray_autostart(checked, installed_appimage())
return (f"mulai otomatis saat login: {'AKTIF' if state else 'nonaktif'}"
+ ("" if res.returncode == 0 else f" ({res.stderr.strip()})"))
+ ("" if res.returncode == 0 else f" ({res.stderr.strip()})")
+ f" | {note}")
except Exception as exc: # noqa: BLE001
return f"galat autostart: {exc}"
@@ -846,7 +909,7 @@ class Window(QWidget):
"kosong": "Layar dikosongkan (nilai nol)",
"berhenti": "Pengiriman data dihentikan"}
if mode != "normal" or getattr(self, "_mode_seen", False):
subprocess.run(["notify-send", "-a", "GAMEMAX LCD", "Status layar",
subprocess.run(["notify-send", "-a", APP_NAME, "Status layar",
labels[mode]], capture_output=True)
self._mode_seen = True
dot = "🟢" if active else "⚪"
@@ -945,7 +1008,7 @@ class Tray:
blank = bool(gr.load_config(CONFIG_PATH).get("blank"))
state = ("layar kosong (nilai nol)" if blank and active
else ("pengiriman dihentikan" if not active else "normal"))
self.icon.setToolTip(f"GAMEMAX LCD: {state}")
self.icon.setToolTip(f"{APP_NAME}: {state}")
base = icon_path()
self.icon.setIcon(dimmed_icon(base) if state != "normal" else QIcon(base))
window = getattr(self, "window", None)
@@ -959,7 +1022,7 @@ class Tray:
self.window.raise_()
self.window.activateWindow()
if QSystemTrayIcon.isSystemTrayAvailable():
self.icon.showMessage("GAMEMAX LCD", "Jendela dibuka dari permintaan kedua.",
self.icon.showMessage(APP_NAME, "Jendela dibuka dari permintaan kedua.",
QSystemTrayIcon.Information, 3000)
@@ -976,7 +1039,7 @@ def main():
def _main():
app = QApplication(sys.argv)
app.setApplicationName("GAMEMAX LCD")
app.setApplicationName(APP_NAME)
app.setStyleSheet(APP_QSS)
app.setQuitOnLastWindowClosed(False)
@@ -986,10 +1049,10 @@ def _main():
if probe.waitForConnected(400):
probe.write(b"raise")
probe.waitForBytesWritten(400)
print("GAMEMAX LCD sudah berjalan — instance pertama diminta membuka jendelanya.")
print(f"{APP_NAME} sudah berjalan — instance pertama diminta membuka jendelanya.")
# Beri tahu lewat notifikasi desktop juga, karena keluarnya proses kedua bisa
# terlihat seperti crash saat diluncurkan dari menu.
subprocess.run(["notify-send", "-a", "GAMEMAX LCD", "Sudah berjalan",
subprocess.run(["notify-send", "-a", APP_NAME, "Sudah berjalan",
"Aplikasi sudah aktif — jendelanya dibuka, cek taskbar/tray."],
capture_output=True)
return 0
+7 -3
View File
@@ -423,9 +423,10 @@ def main():
signal.signal(signal.SIGTERM, lambda *_: stop.set())
signal.signal(signal.SIGINT, lambda *_: stop.set())
print(f"menunggu {VID:#06x}:{PID:#06x} (Ctrl-C atau SIGTERM untuk berhenti)", flush=True)
last_message = None
while not stop.is_set():
if not find_device():
stop.wait(2)
stop.wait(1)
continue
try:
result = stream(a, overrides)
@@ -433,8 +434,11 @@ def main():
sent, dt = result
print(f"perangkat terlepas setelah {sent} report ({sent / dt:.1f}/detik), menunggu lagi", flush=True)
except OSError as exc:
print(f"galat perangkat ({exc}), menunggu lagi", flush=True)
stop.wait(2)
message = f"galat perangkat ({exc}), menunggu lagi"
if message != last_message: # ACL udev baru aktif setelah sesi login
print(message, flush=True)
last_message = message
stop.wait(1)
print("berhenti")
return 0