Add desktop shortcuts for installed games.
This commit is contained in:
1 parent
fbafaed846
commit
40803a7162
4 files changed
+152
-19
No files matched your search
@@ -1,6 +1,6 @@
|
||||
# Linux Game Manager Core Map
|
||||
|
||||
Last refreshed: 2026-10-31
|
||||
Last refreshed: 2026-11-05
|
||||
|
||||
## Top-Level Structure
|
||||
|
||||
@@ -9,6 +9,7 @@ Last refreshed: 2026-10-31
|
||||
- `.launch/`: Per-game launch scripts (`.game`) and runnable entries (`.sh`, usually symlinks).
|
||||
- `.update/`: Optional per-game update scripts expected to define `run_update()`.
|
||||
- `.includes/ipfs.sh`: Central associative array of IPFS download URLs, expanded against the configured gateway at startup.
|
||||
- `.includes/desktop.sh`: Game-specific desktop shortcuts with Desktop Entry and Exec argument escaping.
|
||||
- `.includes/wickets.sh`: Shared Wickets installer/updater; resolves the latest GitHub release and preserves portable saves.
|
||||
- `speech/speak_window_title.sh`: Accessibility helper for announcing focused window titles.
|
||||
- `README.md`: Project summary and high-level behavior notes.
|
||||
@@ -43,6 +44,10 @@ python3 .codex/skills/linux-game-manager-dev/scripts/audit_game_catalog.py
|
||||
- `game_launcher` enumerates `.launch/*.sh` entries.
|
||||
- Entries with first line `#//` are skipped.
|
||||
- Selected launcher is sourced/executed.
|
||||
- The launch menu offers Desktop Shortcut in both Yad and dialog. It writes an executable `~/Desktop/linux-game-manager-<Game Name>.desktop`, refuses to overwrite existing files, and returns to the menu. The shortcut runs the manager with `-l` and sets its working directory to the repository root.
|
||||
- `game_launcher <Game Name>` skips the menu and selects an installed, enabled entry by its exact display name. Missing or disabled entries fail with a message. Launcher discovery uses `scriptRoot` even when called from another working directory.
|
||||
- `ui_menu --desktop-shortcut` adds the launch-menu buttons and returns status 3 for shortcut creation. Ordinary `ui_menu` calls keep their standard buttons. Both modes preserve cancellation status and discard any cancelled selection.
|
||||
- Run `bash tests/game_shortcut_tests.sh` for isolated menu, direct-launch, CLI, and Desktop Entry checks; these require Python with PyGObject and `desktop-file-validate`. They do not launch installed games or use real user state. Live keyboard, screen-reader, and real-game acceptance remains separate.
|
||||
- Monkey Spank runs from its repository directory so relative sound and text assets resolve; its explicit directory assignment also identifies the removal root.
|
||||
- Text-terminal launchers can fall back to `xterm`, which inherits the requested working directory before executing the game command.
|
||||
|
||||
@@ -60,6 +65,7 @@ python3 .codex/skills/linux-game-manager-dev/scripts/audit_game_catalog.py
|
||||
## CLI Surface
|
||||
|
||||
- `-i`: install game
|
||||
- `-l <Game Name>`: launch an installed, enabled game directly by exact menu name
|
||||
- `-r`: remove game
|
||||
- `-u`: update game
|
||||
- `-t`: show number of available games
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# shellcheck shell=bash
|
||||
|
||||
# Desktop Entry escaping is separate from Exec argument quoting.
|
||||
desktop_entry_value() {
|
||||
local value="$1"
|
||||
value="${value//\\/\\\\}"
|
||||
value="${value//$'\n'/\\n}"
|
||||
value="${value//$'\r'/\\r}"
|
||||
value="${value//$'\t'/\\t}"
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
desktop_exec_argument() {
|
||||
local value="$1"
|
||||
value="${value//%/%%}"
|
||||
value="${value//\\/\\\\}"
|
||||
value="${value//\"/\\\"}"
|
||||
value="${value//\$/\\\$}"
|
||||
value="${value//\`/\\\`}"
|
||||
desktop_entry_value "\"${value}\""
|
||||
}
|
||||
|
||||
game_desktop_shortcut() {
|
||||
local gameName="$1"
|
||||
local fileName="${gameName//\//_}"
|
||||
fileName="${fileName//[$'\n\r\t']/_}"
|
||||
local desktopFile="${HOME}/Desktop/linux-game-manager-${fileName}.desktop"
|
||||
local launcherPath
|
||||
# shellcheck disable=SC2154 # Set by the manager before sourcing this helper.
|
||||
launcherPath="$(readlink -e "${scriptRoot}/linux-game-manager.sh")" || return 1
|
||||
|
||||
if ! mkdir -p "${HOME}/Desktop"; then
|
||||
ui_msgbox "Desktop Shortcut" "" "Could not create ~/Desktop."
|
||||
return 1
|
||||
fi
|
||||
# Refuse to overwrite files or follow existing symbolic links.
|
||||
if ! (
|
||||
set -o noclobber
|
||||
printf '%s\n' '[Desktop Entry]' \
|
||||
"Name=$(desktop_entry_value "$gameName")" \
|
||||
"Exec=$(desktop_exec_argument "$launcherPath") -l $(desktop_exec_argument "$gameName")" \
|
||||
"Path=$(desktop_entry_value "$scriptRoot")" \
|
||||
'Terminal=true' 'Type=Application' 'Categories=Game;' \
|
||||
> "$desktopFile"
|
||||
); then
|
||||
ui_msgbox "Desktop Shortcut" "" "Could not create ${desktopFile}. Check permissions and whether a shortcut already exists."
|
||||
return 1
|
||||
fi
|
||||
if ! chmod +x "$desktopFile"; then
|
||||
ui_msgbox "Desktop Shortcut" "" "Could not make ${desktopFile} executable."
|
||||
return 1
|
||||
fi
|
||||
ui_msgbox "Desktop Shortcut" "" "Created ${desktopFile}."
|
||||
}
|
||||
@@ -9,6 +9,23 @@ As the project grows, more dependencies will most likely be added. When LGM is l
|
||||
|
||||
Current base dependencies checked on launch: `7z`, `curl`, `dialog`, `jq`, `yad`, and `unzip`.
|
||||
|
||||
## Desktop shortcuts
|
||||
|
||||
Open `./linux-game-manager.sh`, select an installed game, and choose **Desktop
|
||||
Shortcut**. This works in both the graphical and console menus and creates an
|
||||
executable `.desktop` file in `~/Desktop`. Existing files are left intact, and
|
||||
the game list opens again after creation. Your desktop environment may ask you
|
||||
to trust the shortcut before launching it.
|
||||
|
||||
The shortcut launches that game directly through the manager's `-l` option.
|
||||
You can also use this option from a terminal with the exact game menu name:
|
||||
|
||||
```bash
|
||||
./linux-game-manager.sh -l "RS Games"
|
||||
```
|
||||
|
||||
Only installed, enabled launcher entries can be launched this way.
|
||||
|
||||
## Wickets
|
||||
|
||||
Install Wickets on x86_64 Linux by selecting it under `./linux-game-manager.sh -i`.
|
||||
|
||||
+74
-18
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# shellcheck disable=SC2329 # Shared helpers are called by sourced game scripts.
|
||||
|
||||
scriptRoot="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||
|
||||
@@ -50,14 +51,25 @@ export DIALOGOPTS='--no-lines --visit-items'
|
||||
# Automatically switches between dialog (console) and yad (GUI) based on DISPLAY environment
|
||||
|
||||
# Wrapper function for menu selection
|
||||
# Usage: ui_menu "title" "backtitle" "text" option1 "description1" option2 "description2" ...
|
||||
# Usage: ui_menu [--desktop-shortcut] "title" "backtitle" "text" option "description" ...
|
||||
# Returns 3 for Desktop Shortcut; other nonzero statuses cancel the menu.
|
||||
ui_menu() {
|
||||
local shortcutMenu=false
|
||||
local menuButtons=()
|
||||
local menuCode=0
|
||||
if [[ "$1" == --desktop-shortcut ]]; then
|
||||
shortcutMenu=true
|
||||
shift
|
||||
fi
|
||||
local title="$1"
|
||||
local back_title="$2"
|
||||
local text="$3"
|
||||
shift 3
|
||||
|
||||
if [[ "$dialog_type" == "yad" ]]; then
|
||||
if [[ "$shortcutMenu" == true ]]; then
|
||||
menuButtons=(--button="Launch:0" --button="Desktop Shortcut:2" --button="Cancel:1")
|
||||
fi
|
||||
# Build yad list format: Display only, then map back to value
|
||||
local yad_list=""
|
||||
declare -A value_map
|
||||
@@ -81,7 +93,13 @@ ui_menu() {
|
||||
--selectable-labels \
|
||||
--search-column=1 \
|
||||
--height=400 \
|
||||
--width=600)
|
||||
--width=600 "${menuButtons[@]}") || menuCode=$?
|
||||
if [[ "$shortcutMenu" == true && $menuCode -eq 2 ]]; then
|
||||
menuCode=3
|
||||
fi
|
||||
if [[ $menuCode -ne 0 && ( "$shortcutMenu" != true || $menuCode -ne 3 ) ]]; then
|
||||
return "$menuCode"
|
||||
fi
|
||||
# Strip trailing newline, pipe, and any other whitespace
|
||||
selected_description=$(echo "$selected_description" | tr -d '\n\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/|$//')
|
||||
|
||||
@@ -89,7 +107,11 @@ ui_menu() {
|
||||
if [[ -n "$selected_description" ]]; then
|
||||
echo "${value_map["$selected_description"]}"
|
||||
fi
|
||||
return "$menuCode"
|
||||
else
|
||||
if [[ "$shortcutMenu" == true ]]; then
|
||||
menuButtons=(--extra-button --extra-label "Desktop Shortcut")
|
||||
fi
|
||||
# Build dialog menu format with mapping (same approach as yad)
|
||||
local dialog_args=()
|
||||
declare -A value_map
|
||||
@@ -104,15 +126,20 @@ ui_menu() {
|
||||
local selected_description
|
||||
selected_description=$(dialog --backtitle "$back_title" \
|
||||
--title "$title" \
|
||||
"${menuButtons[@]}" \
|
||||
--no-tags \
|
||||
--menu "$text" 0 0 0 \
|
||||
"${dialog_args[@]}" \
|
||||
--stdout)
|
||||
--stdout) || menuCode=$?
|
||||
if [[ $menuCode -ne 0 && ( "$shortcutMenu" != true || $menuCode -ne 3 ) ]]; then
|
||||
return "$menuCode"
|
||||
fi
|
||||
|
||||
# Return the mapped value
|
||||
if [[ -n "$selected_description" ]]; then
|
||||
echo "${value_map["$selected_description"]}"
|
||||
fi
|
||||
return "$menuCode"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -878,7 +905,7 @@ game_update() {
|
||||
game_launcher() {
|
||||
# Initialize array for menu construction
|
||||
mapfile -t menuList < <(
|
||||
if [[ -d ".launch" ]]; then
|
||||
if [[ -d "${scriptRoot}/.launch" ]]; then
|
||||
# shellcheck disable=SC2016 # variables expand in the child bash, not this single-quoted string
|
||||
find -L "${scriptRoot}/.launch" -maxdepth 1 -type f -iname "*.sh" -print0 | sort -z | xargs -0 bash -c '
|
||||
for f; do
|
||||
@@ -889,20 +916,45 @@ game_launcher() {
|
||||
done' _
|
||||
fi
|
||||
)
|
||||
if [[ ${#menuList} -eq 0 ]]; then
|
||||
ui_msgbox "Linux Game Manager" "Linux Game Manager" "No games found."
|
||||
exit 0
|
||||
fi
|
||||
# Create the menu of all games
|
||||
# menuList has alternating full_path, display_name pairs
|
||||
declare -a menuArgs
|
||||
for ((i=0; i<${#menuList[@]}; i+=2)); do
|
||||
menuArgs+=("${menuList[i]}" "${menuList[i+1]}")
|
||||
done
|
||||
selectedGame="$(ui_menu "Linux Game Launcher" "Linux Game Launcher" "Please select a game to play" "${menuArgs[@]}")"
|
||||
local menuCode=$?
|
||||
if [[ $menuCode -ne 0 ]] || [[ -z "$selectedGame" ]]; then
|
||||
exit 0
|
||||
local selectedGame=""
|
||||
if [[ $# -gt 0 ]]; then
|
||||
for ((i=0; i<${#menuList[@]}; i+=2)); do
|
||||
if [[ "$1" == "${menuList[i+1]}" ]]; then
|
||||
selectedGame="${menuList[i]}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ -z "$selectedGame" ]]; then
|
||||
ui_msgbox "Linux Game Launcher" "Linux Game Manager" "Game $1 is not installed or is disabled."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
if [[ ${#menuList[@]} -eq 0 ]]; then
|
||||
ui_msgbox "Linux Game Manager" "Linux Game Manager" "No games found."
|
||||
exit 0
|
||||
fi
|
||||
# Create the menu of all games
|
||||
# menuList has alternating full_path, display_name pairs
|
||||
declare -a menuArgs
|
||||
for ((i=0; i<${#menuList[@]}; i+=2)); do
|
||||
menuArgs+=("${menuList[i]}" "${menuList[i+1]}")
|
||||
done
|
||||
local menuCode
|
||||
local gameName
|
||||
while true; do
|
||||
selectedGame="$(ui_menu --desktop-shortcut "Linux Game Launcher" "Linux Game Launcher" "Please select a game to play" "${menuArgs[@]}")"
|
||||
menuCode=$?
|
||||
if [[ $menuCode -eq 3 ]]; then
|
||||
if [[ -n "$selectedGame" ]]; then
|
||||
gameName="${selectedGame##*/}"
|
||||
game_desktop_shortcut "${gameName%.sh}"
|
||||
fi
|
||||
continue
|
||||
elif [[ $menuCode -ne 0 || -z "$selectedGame" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
break
|
||||
done
|
||||
fi
|
||||
# shellcheck disable=SC1090 # launcher scripts are selected dynamically from .launch
|
||||
. "${selectedGame}"
|
||||
@@ -969,6 +1021,8 @@ export ipfsGateway="${ipfsGateway:-https://ipfs.stormux.org}"
|
||||
source "${scriptRoot}/.includes/ipfs.sh"
|
||||
# shellcheck source=.includes/gamemode.sh
|
||||
source "${scriptRoot}/.includes/gamemode.sh"
|
||||
# shellcheck source=.includes/desktop.sh
|
||||
source "${scriptRoot}/.includes/desktop.sh"
|
||||
export spd_module="${spd_module:+ -o ${spd_module}}"
|
||||
export spd_pitch="${spd_pitch:+ -p ${spd_pitch}}"
|
||||
export spd_rate="${spd_rate:+ -r ${spd_rate}}"
|
||||
@@ -1006,6 +1060,7 @@ declare -A command=(
|
||||
[h]="This help screen."
|
||||
[i]="Install games."
|
||||
[L]="Display license information."
|
||||
[l:]="Launch an installed game directly by its menu name."
|
||||
[N]="No cache, delete the installer after it has been extracted."
|
||||
[R]="Redownload. Removes old versions of packages from cache before installing."
|
||||
[r]="Remove game. Remove a game and its menu entry."
|
||||
@@ -1023,6 +1078,7 @@ while getopts "${args}" i ; do
|
||||
h) help ;;
|
||||
i) game_installer ;;
|
||||
L) license ;;
|
||||
l) game_launcher "$OPTARG" ;;
|
||||
N) noCache="true" ;;
|
||||
R) redownload="true" ;;
|
||||
r) game_removal ;;
|
||||
|
||||
Reference in new issue
Block a user