Initial import
This commit is contained in:
@@ -0,0 +1,455 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
OUTPUT = ROOT / "data" / "game_config_db.toml"
|
||||
|
||||
IGNORE_CANDIDATES = {
|
||||
"steam_autocloud",
|
||||
"unitycrashhandler64",
|
||||
"unitycrashhandler32",
|
||||
"crashhandler",
|
||||
"crashreporter",
|
||||
"launcher",
|
||||
"launch",
|
||||
"start",
|
||||
"start_protected_game",
|
||||
"eac_launcher",
|
||||
"eosbootstrapper",
|
||||
"eossdk-win64-shipping",
|
||||
"notification_helper",
|
||||
"steamerrorreporter",
|
||||
"steam-launch-wrapper",
|
||||
"proton",
|
||||
"wine",
|
||||
"wine64-preloader",
|
||||
"wine-preloader",
|
||||
"winetricks",
|
||||
"dxsetup",
|
||||
"vc_redist.x64",
|
||||
"vc_redist.x86",
|
||||
"game",
|
||||
"app",
|
||||
"boot",
|
||||
"data",
|
||||
"options",
|
||||
"quickref",
|
||||
"language",
|
||||
"settings",
|
||||
"config",
|
||||
"global",
|
||||
"system",
|
||||
"resources",
|
||||
"resource",
|
||||
"readme",
|
||||
"desc",
|
||||
"icon",
|
||||
"logo",
|
||||
"catalog",
|
||||
"test",
|
||||
"tests",
|
||||
"steam",
|
||||
"lang",
|
||||
"langs",
|
||||
"level0",
|
||||
"level1",
|
||||
"level2",
|
||||
"level3",
|
||||
"level4",
|
||||
"level5",
|
||||
"level6",
|
||||
"level7",
|
||||
"level8",
|
||||
"level9",
|
||||
"unityplayer",
|
||||
"gameassembly",
|
||||
"baselib",
|
||||
"il2cpp",
|
||||
"openimagedenoise",
|
||||
"epicwebhelper",
|
||||
"steam_api",
|
||||
"steam_api64",
|
||||
"sonynp",
|
||||
"unirx",
|
||||
"demilib",
|
||||
"tbb",
|
||||
"tbb12",
|
||||
"tbbmalloc",
|
||||
}
|
||||
|
||||
IGNORE_PREFIXES = (
|
||||
"pakchunk",
|
||||
"steam_api",
|
||||
"unitycrashhandler",
|
||||
"crashreport",
|
||||
"crashpad",
|
||||
"openxr",
|
||||
"fmod",
|
||||
"lib",
|
||||
)
|
||||
|
||||
IGNORE_EXACT_CASEFOLD = {
|
||||
"readme",
|
||||
"readme.txt",
|
||||
"eula",
|
||||
"license",
|
||||
"version",
|
||||
"preview",
|
||||
"developer",
|
||||
"server",
|
||||
"console",
|
||||
"temp",
|
||||
"meta",
|
||||
"init",
|
||||
"core",
|
||||
"path",
|
||||
"base",
|
||||
"keys",
|
||||
"header",
|
||||
"music",
|
||||
"sfx_ui",
|
||||
"master",
|
||||
"env",
|
||||
"static",
|
||||
"wiki",
|
||||
"mod",
|
||||
"fish",
|
||||
"bees",
|
||||
"note",
|
||||
"shine",
|
||||
"track",
|
||||
"glyph",
|
||||
"death",
|
||||
"prog",
|
||||
"build",
|
||||
"run",
|
||||
"dbdata",
|
||||
"work",
|
||||
"menus",
|
||||
"items",
|
||||
"skills",
|
||||
"input",
|
||||
"bgs",
|
||||
}
|
||||
|
||||
CURATED_OVERRIDES = {
|
||||
"730": {
|
||||
"title": "Counter-Strike 2",
|
||||
"aliases": ["cs2", "counter strike 2", "counter-strike 2", "csgo"],
|
||||
"candidates": ["cs2", "csgo", "wine-cs2"],
|
||||
"preferred": "cs2",
|
||||
"verification": "verified",
|
||||
},
|
||||
"570": {
|
||||
"title": "Dota 2",
|
||||
"aliases": ["dota2", "dota"],
|
||||
"candidates": ["dota", "wine-dota2"],
|
||||
"preferred": "dota",
|
||||
"verification": "verified",
|
||||
},
|
||||
"440": {
|
||||
"title": "Team Fortress 2",
|
||||
"aliases": ["tf2", "team fortress 2"],
|
||||
"candidates": ["tf", "wine-tf2"],
|
||||
"preferred": "tf",
|
||||
"verification": "verified",
|
||||
},
|
||||
"620": {
|
||||
"title": "Portal 2",
|
||||
"aliases": ["portal2", "portal 2"],
|
||||
"candidates": ["portal2", "wine-portal2"],
|
||||
"preferred": "portal2",
|
||||
"verification": "verified",
|
||||
},
|
||||
"892970": {
|
||||
"title": "Valheim",
|
||||
"aliases": ["valheim"],
|
||||
"candidates": ["valheim", "wine-valheim"],
|
||||
"preferred": "valheim",
|
||||
"verification": "verified",
|
||||
},
|
||||
"238960": {
|
||||
"title": "Path of Exile",
|
||||
"aliases": ["path of exile", "poe"],
|
||||
"candidates": ["PathOfExileSteam", "wine-PathOfExileSteam"],
|
||||
"preferred": "PathOfExileSteam",
|
||||
"verification": "verified",
|
||||
},
|
||||
"105600": {
|
||||
"title": "Terraria",
|
||||
"aliases": ["terraria"],
|
||||
"candidates": ["Terraria", "wine-Terraria"],
|
||||
"preferred": "Terraria",
|
||||
"verification": "verified",
|
||||
},
|
||||
"1145360": {
|
||||
"title": "Hades",
|
||||
"aliases": ["hades"],
|
||||
"candidates": ["Hades", "wine-Hades"],
|
||||
"preferred": "Hades",
|
||||
"verification": "verified",
|
||||
},
|
||||
"212680": {
|
||||
"title": "FTL: Faster Than Light",
|
||||
"aliases": ["ftl", "faster than light"],
|
||||
"candidates": ["FTL", "wine-FTLGame"],
|
||||
"preferred": "FTL",
|
||||
"verification": "verified",
|
||||
},
|
||||
"283160": {
|
||||
"title": "House of the Dying Sun",
|
||||
"aliases": ["house of the dying sun", "dyingsun"],
|
||||
"candidates": ["dyingsun", "wine-dyingsun"],
|
||||
"preferred": "dyingsun",
|
||||
"verification": "verified",
|
||||
},
|
||||
"1794680": {
|
||||
"title": "Vampire Survivors",
|
||||
"aliases": ["vampire survivors"],
|
||||
"candidates": ["VampireSurvivors", "wine-VampireSurvivors"],
|
||||
"preferred": "VampireSurvivors",
|
||||
"verification": "verified",
|
||||
},
|
||||
"1091500": {
|
||||
"title": "Cyberpunk 2077",
|
||||
"aliases": ["cyberpunk", "cyberpunk 2077"],
|
||||
"candidates": ["Cyberpunk2077", "wine-Cyberpunk2077"],
|
||||
"preferred": "Cyberpunk2077",
|
||||
"verification": "verified",
|
||||
},
|
||||
"305620": {
|
||||
"title": "The Long Dark",
|
||||
"aliases": ["the long dark", "long dark"],
|
||||
"candidates": ["tld", "wine-tld"],
|
||||
"preferred": "tld",
|
||||
"verification": "verified",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def normalize(text: str) -> str:
|
||||
return re.sub(r"[^a-z0-9]+", "", text.lower())
|
||||
|
||||
|
||||
def parse_library_paths() -> list[Path]:
|
||||
candidates = [
|
||||
Path.home() / ".local/share/Steam/steamapps/libraryfolders.vdf",
|
||||
Path.home() / ".steam/steam/steamapps/libraryfolders.vdf",
|
||||
]
|
||||
for candidate in candidates:
|
||||
if candidate.exists():
|
||||
text = candidate.read_text(errors="ignore")
|
||||
paths = [
|
||||
Path(match.group(1).replace("\\\\", "/"))
|
||||
for match in re.finditer(r'"path"\s*\t\t"([^"]+)"', text)
|
||||
]
|
||||
return list(dict.fromkeys(paths))
|
||||
return []
|
||||
|
||||
|
||||
def parse_manifest(path: Path) -> dict[str, str] | None:
|
||||
text = path.read_text(errors="ignore")
|
||||
appid = re.search(r'"appid"\s*\t+"(\d+)"', text)
|
||||
name = re.search(r'"name"\s*\t+"([^"]+)"', text)
|
||||
installdir = re.search(r'"installdir"\s*\t+"([^"]+)"', text)
|
||||
if not appid or not name or not installdir:
|
||||
return None
|
||||
return {
|
||||
"appid": appid.group(1),
|
||||
"title": name.group(1),
|
||||
"installdir": installdir.group(1),
|
||||
}
|
||||
|
||||
|
||||
def candidate_score(candidate: str, title: str, installdir: str) -> tuple[int, int]:
|
||||
norm_candidate = normalize(candidate)
|
||||
norm_title = normalize(title)
|
||||
norm_installdir = normalize(installdir)
|
||||
score = 0
|
||||
if norm_candidate == norm_installdir:
|
||||
score += 100
|
||||
if norm_candidate == norm_title:
|
||||
score += 90
|
||||
if len(norm_candidate) >= 4 and (norm_candidate in norm_title or norm_title in norm_candidate):
|
||||
score += 50
|
||||
if len(norm_candidate) >= 4 and (
|
||||
norm_candidate in norm_installdir or norm_installdir in norm_candidate
|
||||
):
|
||||
score += 35
|
||||
if candidate.lower().endswith((".sh", ".exe")):
|
||||
score -= 10
|
||||
return (score, -len(candidate))
|
||||
|
||||
|
||||
def is_noise_candidate(stem: str) -> bool:
|
||||
lowered = stem.casefold()
|
||||
norm = normalize(stem)
|
||||
if not norm or norm in IGNORE_CANDIDATES:
|
||||
return True
|
||||
if lowered in IGNORE_EXACT_CASEFOLD:
|
||||
return True
|
||||
if any(norm.startswith(prefix) for prefix in IGNORE_PREFIXES):
|
||||
return True
|
||||
if re.fullmatch(r"level\d+", lowered):
|
||||
return True
|
||||
if re.fullmatch(r"pakchunk\d+.*", lowered):
|
||||
return True
|
||||
if re.fullmatch(r"sfx[_-].*", lowered):
|
||||
return True
|
||||
if re.fullmatch(r"audiogroup\d+", lowered):
|
||||
return True
|
||||
if re.fullmatch(r"steamworks(_x64)?", lowered):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def collect_candidates(install_dir: Path, title: str, installdir: str) -> list[str]:
|
||||
if not install_dir.exists():
|
||||
return []
|
||||
|
||||
candidates: list[str] = []
|
||||
for path in install_dir.rglob("*"):
|
||||
if not path.is_file():
|
||||
continue
|
||||
if len(path.relative_to(install_dir).parts) > 4:
|
||||
continue
|
||||
suffix = path.suffix.lower()
|
||||
if suffix not in {".exe", ".x86_64", ".x86", ".sh", ".app", ""} and not os.access(path, os.X_OK):
|
||||
continue
|
||||
if suffix == "" and not os.access(path, os.X_OK):
|
||||
continue
|
||||
if suffix == ".app" and not os.access(path, os.X_OK):
|
||||
continue
|
||||
stem = path.stem if suffix else path.name
|
||||
if is_noise_candidate(stem):
|
||||
continue
|
||||
if stem.lower().startswith(("unins", "setup", "install", "crash")):
|
||||
continue
|
||||
candidates.append(stem)
|
||||
|
||||
unique: list[str] = []
|
||||
seen: set[str] = set()
|
||||
for candidate in candidates:
|
||||
key = candidate.lower()
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
unique.append(candidate)
|
||||
|
||||
if not unique:
|
||||
fallback = re.sub(r"[^A-Za-z0-9_-]+", "", installdir)
|
||||
if fallback:
|
||||
unique.append(fallback)
|
||||
|
||||
ranked = sorted(unique, key=lambda item: candidate_score(item, title, installdir), reverse=True)
|
||||
strong = [item for item in ranked if candidate_score(item, title, installdir)[0] > 0]
|
||||
|
||||
if strong:
|
||||
return strong[:8]
|
||||
|
||||
fallback = re.sub(r"[^A-Za-z0-9_-]+", "", installdir)
|
||||
if fallback:
|
||||
return [fallback]
|
||||
|
||||
return ranked[:1]
|
||||
|
||||
|
||||
def build_entries() -> list[dict[str, object]]:
|
||||
manifests: list[Path] = []
|
||||
for library in parse_library_paths():
|
||||
steamapps = library / "steamapps"
|
||||
if steamapps.exists():
|
||||
manifests.extend(sorted(steamapps.glob("appmanifest_*.acf")))
|
||||
|
||||
entries_by_appid: dict[str, dict[str, object]] = {}
|
||||
for manifest in manifests:
|
||||
parsed = parse_manifest(manifest)
|
||||
if not parsed:
|
||||
continue
|
||||
appid = parsed["appid"]
|
||||
install_dir = manifest.parent / "common" / parsed["installdir"]
|
||||
candidates = collect_candidates(install_dir, parsed["title"], parsed["installdir"])
|
||||
preferred = candidates[0] if candidates else re.sub(r"[^A-Za-z0-9_-]+", "", parsed["installdir"])
|
||||
entries_by_appid[appid] = {
|
||||
"appid": int(appid),
|
||||
"title": parsed["title"],
|
||||
"aliases": [parsed["installdir"]],
|
||||
"candidates": candidates,
|
||||
"preferred": preferred,
|
||||
"verification": "heuristic",
|
||||
}
|
||||
|
||||
for appid, override in CURATED_OVERRIDES.items():
|
||||
base = entries_by_appid.get(appid, {
|
||||
"appid": int(appid),
|
||||
"title": override["title"],
|
||||
"aliases": [],
|
||||
"candidates": [],
|
||||
"preferred": override["preferred"],
|
||||
"verification": override["verification"],
|
||||
})
|
||||
alias_set = {alias for alias in base.get("aliases", [])}
|
||||
alias_set.update(override.get("aliases", []))
|
||||
candidate_list = []
|
||||
seen_candidates = set()
|
||||
for candidate in [*override.get("candidates", []), *base.get("candidates", [])]:
|
||||
key = candidate.lower()
|
||||
if key in seen_candidates:
|
||||
continue
|
||||
seen_candidates.add(key)
|
||||
candidate_list.append(candidate)
|
||||
base.update({
|
||||
"title": override["title"],
|
||||
"aliases": sorted(alias_set),
|
||||
"candidates": candidate_list,
|
||||
"preferred": override["preferred"],
|
||||
"verification": override["verification"],
|
||||
})
|
||||
entries_by_appid[appid] = base
|
||||
|
||||
entries = list(entries_by_appid.values())
|
||||
entries.sort(key=lambda entry: entry["title"].lower())
|
||||
return entries
|
||||
|
||||
|
||||
def toml_escape(value: str) -> str:
|
||||
return value.replace("\\", "\\\\").replace("\"", "\\\"")
|
||||
|
||||
|
||||
def render_toml(entries: list[dict[str, object]]) -> str:
|
||||
lines = [
|
||||
"# MangoTune game/executable hint database",
|
||||
"# verification = \"verified\" means manually confirmed",
|
||||
"# verification = \"heuristic\" means derived from local library scan and should be treated as a suggestion",
|
||||
"",
|
||||
]
|
||||
for entry in entries:
|
||||
lines.append("[[game]]")
|
||||
lines.append(f"appid = {entry['appid']}")
|
||||
lines.append(f"title = \"{toml_escape(entry['title'])}\"")
|
||||
aliases = ", ".join(f'\"{toml_escape(alias)}\"' for alias in entry.get("aliases", []))
|
||||
candidates = ", ".join(
|
||||
f'\"{toml_escape(candidate)}\"' for candidate in entry.get("candidates", [])
|
||||
)
|
||||
lines.append(f"aliases = [{aliases}]")
|
||||
lines.append(f"candidates = [{candidates}]")
|
||||
lines.append(f"preferred = \"{toml_escape(entry['preferred'])}\"")
|
||||
lines.append(f"verification = \"{entry['verification']}\"")
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
entries = build_entries()
|
||||
OUTPUT.write_text(render_toml(entries))
|
||||
print(f"Wrote {len(entries)} entries to {OUTPUT}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+116
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
mangohud-position-lab.sh <config> [app] [output_dir]
|
||||
|
||||
Examples:
|
||||
mangohud-position-lab.sh ~/.config/mangotune/profiles/zz_test_right_sparse_top.conf glxgears
|
||||
mangohud-position-lab.sh ~/.config/mangotune/profiles/zz_test_right_full_top.conf vkcube /tmp/mh-lab
|
||||
|
||||
Notes:
|
||||
- This runs MangoHud directly, outside MangoTune's preview pipeline.
|
||||
- OpenGL uses MANGOHUD_DLSYM=1.
|
||||
- If xdotool/import are installed and a real X11 session is available, a screenshot
|
||||
and basic window geometry will be written to the output directory.
|
||||
- Override DISPLAY/XAUTHORITY/WAIT_SECS/WIDTH/HEIGHT from the environment when needed.
|
||||
EOF
|
||||
}
|
||||
|
||||
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" || $# -lt 1 ]]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
CONFIG_PATH="$1"
|
||||
APP="${2:-glxgears}"
|
||||
OUTPUT_DIR="${3:-/tmp/mangohud-position-lab}"
|
||||
DISPLAY_VALUE="${DISPLAY:-:0}"
|
||||
XAUTH_VALUE="${XAUTHORITY:-$HOME/.Xauthority}"
|
||||
WAIT_SECS="${WAIT_SECS:-4}"
|
||||
WIDTH="${WIDTH:-1400}"
|
||||
HEIGHT="${HEIGHT:-760}"
|
||||
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
rm -f "$OUTPUT_DIR"/run.log "$OUTPUT_DIR"/window.geom "$OUTPUT_DIR"/window.png \
|
||||
"$OUTPUT_DIR"/screen.png "$OUTPUT_DIR"/window_from_screen.png
|
||||
|
||||
if [[ ! -f "$CONFIG_PATH" ]]; then
|
||||
echo "Config not found: $CONFIG_PATH" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
cleanup() {
|
||||
if [[ -n "${PID:-}" ]]; then
|
||||
kill "$PID" 2>/dev/null || true
|
||||
wait "$PID" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
case "$APP" in
|
||||
glxgears)
|
||||
MATCH_NAME="glxgears"
|
||||
DISPLAY="$DISPLAY_VALUE" \
|
||||
XAUTHORITY="$XAUTH_VALUE" \
|
||||
MANGOHUD_CONFIGFILE="$CONFIG_PATH" \
|
||||
MANGOHUD_DLSYM=1 \
|
||||
mangohud glxgears -geometry "${WIDTH}x${HEIGHT}" >"$OUTPUT_DIR/run.log" 2>&1 &
|
||||
;;
|
||||
vkcube)
|
||||
MATCH_NAME="Vkcube"
|
||||
DISPLAY="$DISPLAY_VALUE" \
|
||||
XAUTHORITY="$XAUTH_VALUE" \
|
||||
MANGOHUD_CONFIGFILE="$CONFIG_PATH" \
|
||||
WGPU_BACKEND="${WGPU_BACKEND:-gl}" \
|
||||
mangohud vkcube --width "$WIDTH" --height "$HEIGHT" >"$OUTPUT_DIR/run.log" 2>&1 &
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported app: $APP" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
PID=$!
|
||||
echo "pid=$PID" >>"$OUTPUT_DIR/run.log"
|
||||
printf 'display=%s\nxauthority=%s\nwidth=%s\nheight=%s\n' \
|
||||
"$DISPLAY_VALUE" "$XAUTH_VALUE" "$WIDTH" "$HEIGHT" >>"$OUTPUT_DIR/run.log"
|
||||
sleep "$WAIT_SECS"
|
||||
|
||||
if command -v xwininfo >/dev/null 2>&1; then
|
||||
DISPLAY="$DISPLAY_VALUE" XAUTHORITY="$XAUTH_VALUE" \
|
||||
xwininfo -root -tree >"$OUTPUT_DIR/xwininfo.tree" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if command -v xdotool >/dev/null 2>&1; then
|
||||
WINDOW_ID="$(DISPLAY="$DISPLAY_VALUE" XAUTHORITY="$XAUTH_VALUE" xdotool search --name "$MATCH_NAME" 2>/dev/null | tail -n1 || true)"
|
||||
else
|
||||
WINDOW_ID=""
|
||||
fi
|
||||
|
||||
if [[ -n "$WINDOW_ID" ]]; then
|
||||
echo "window_id=$WINDOW_ID" >>"$OUTPUT_DIR/run.log"
|
||||
DISPLAY="$DISPLAY_VALUE" XAUTHORITY="$XAUTH_VALUE" \
|
||||
xdotool getwindowgeometry --shell "$WINDOW_ID" >"$OUTPUT_DIR/window.geom" 2>/dev/null || true
|
||||
if command -v import >/dev/null 2>&1; then
|
||||
DISPLAY="$DISPLAY_VALUE" XAUTHORITY="$XAUTH_VALUE" \
|
||||
import -window "$WINDOW_ID" "$OUTPUT_DIR/window.png" 2>/dev/null || true
|
||||
DISPLAY="$DISPLAY_VALUE" XAUTHORITY="$XAUTH_VALUE" \
|
||||
import -window root "$OUTPUT_DIR/screen.png" 2>/dev/null || true
|
||||
if [[ -f "$OUTPUT_DIR/window.geom" ]] && [[ -f "$OUTPUT_DIR/screen.png" ]] && command -v convert >/dev/null 2>&1; then
|
||||
# Crop the full-screen capture back down to the app window bounds. This preserves overlays
|
||||
# when the compositor draws them separately from the client window content.
|
||||
# shellcheck disable=SC1090
|
||||
. "$OUTPUT_DIR/window.geom"
|
||||
if [[ -n "${WIDTH:-}" && -n "${HEIGHT:-}" && -n "${X:-}" && -n "${Y:-}" ]]; then
|
||||
convert "$OUTPUT_DIR/screen.png" -crop "${WIDTH}x${HEIGHT}+${X}+${Y}" +repage \
|
||||
"$OUTPUT_DIR/window_from_screen.png" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Wrote artifacts to $OUTPUT_DIR"
|
||||
Executable
+97
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
mangohud-position-matrix.sh [app] [output_dir] [profile_dir]
|
||||
|
||||
Examples:
|
||||
mangohud-position-matrix.sh vkcube /tmp/mh-matrix
|
||||
DISPLAY=:1 XAUTHORITY=/root/.Xauthority mangohud-position-matrix.sh glxgears /tmp/mh-matrix /home/aaron/mangotune-test-profiles
|
||||
|
||||
What it does:
|
||||
- finds the standard MangoTune right-alignment test profiles
|
||||
- generates margin-on and margin-off variants
|
||||
- runs MangoHud directly for each case using mangohud-position-lab.sh
|
||||
- stores generated configs and capture artifacts under the output dir
|
||||
EOF
|
||||
}
|
||||
|
||||
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
APP="${1:-vkcube}"
|
||||
OUTPUT_DIR="${2:-/tmp/mangohud-position-matrix}"
|
||||
PROFILE_DIR="${3:-$HOME/.config/mangotune/profiles}"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
LAB_SCRIPT="$SCRIPT_DIR/mangohud-position-lab.sh"
|
||||
|
||||
if [[ ! -x "$LAB_SCRIPT" ]]; then
|
||||
chmod +x "$LAB_SCRIPT" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
mkdir -p "$OUTPUT_DIR/generated"
|
||||
|
||||
profiles=(
|
||||
"zz_test_right_full_top"
|
||||
"zz_test_right_full_middle"
|
||||
"zz_test_right_sparse_top"
|
||||
"zz_test_right_sparse_middle"
|
||||
"zz_test_right_sparse_compact_top"
|
||||
"zz_test_right_sparse_compact_middle"
|
||||
)
|
||||
|
||||
ensure_flag_state() {
|
||||
local input_file="$1"
|
||||
local output_file="$2"
|
||||
local flag="$3"
|
||||
local enabled="$4"
|
||||
awk -v flag="$flag" -v enabled="$enabled" '
|
||||
BEGIN { saw = 0 }
|
||||
{
|
||||
trimmed = $0
|
||||
gsub(/^[[:space:]]+/, "", trimmed)
|
||||
if (trimmed == flag || trimmed == "# " flag || trimmed == "#" flag) {
|
||||
if (enabled == "1") {
|
||||
print flag
|
||||
} else {
|
||||
print "# " flag
|
||||
}
|
||||
saw = 1
|
||||
next
|
||||
}
|
||||
print $0
|
||||
}
|
||||
END {
|
||||
if (!saw && enabled == "1") {
|
||||
print flag
|
||||
}
|
||||
}
|
||||
' "$input_file" >"$output_file"
|
||||
}
|
||||
|
||||
for profile in "${profiles[@]}"; do
|
||||
base="$PROFILE_DIR/$profile.conf"
|
||||
[[ -f "$base" ]] || continue
|
||||
|
||||
for margin_state in margin_on margin_off; do
|
||||
generated="$OUTPUT_DIR/generated/${profile}_${margin_state}.conf"
|
||||
case "$margin_state" in
|
||||
margin_on)
|
||||
ensure_flag_state "$base" "$generated" "hud_no_margin" 0
|
||||
;;
|
||||
margin_off)
|
||||
ensure_flag_state "$base" "$generated" "hud_no_margin" 1
|
||||
;;
|
||||
esac
|
||||
|
||||
case_out="$OUTPUT_DIR/${profile}_${margin_state}_${APP}"
|
||||
"$LAB_SCRIPT" "$generated" "$APP" "$case_out"
|
||||
done
|
||||
done
|
||||
|
||||
echo "Matrix artifacts written to $OUTPUT_DIR"
|
||||
Reference in New Issue
Block a user