From 0a5e3e30581d4ff469e24e0ccb0d1f1923fed798 Mon Sep 17 00:00:00 2001 From: wu-json Date: Fri, 9 Oct 2026 10:59:25 -0700 Subject: [PATCH 1/2] feat: manage Tinycast settings and app shortcuts --- README.md | 19 +++ scripts/bootstrap.sh | 43 +++++- tests/integration.py | 6 +- tinycast/.config/tinycast/settings.json | 167 +++++++++++++++++++++++ tinycast/.config/tinycast/shortcuts.json | 24 ++++ 5 files changed, 254 insertions(+), 5 deletions(-) create mode 100644 tinycast/.config/tinycast/settings.json create mode 100644 tinycast/.config/tinycast/shortcuts.json diff --git a/README.md b/README.md index 72ef122..142546c 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ This is where I tweak config files till 4am like a goblin. It's pretty cozy in h - **[wezterm](https://wezterm.org/index.html)** - **[stow](https://www.gnu.org/software/stow/)** - **[SwiftBar](https://github.com/swiftbar/SwiftBar)** +- **[Tinycast](https://github.com/abue-ammar/tinycast)** - **[yazi](https://yazi-rs.github.io/)** ## Setup @@ -22,3 +23,21 @@ This is where I tweak config files till 4am like a goblin. It's pretty cozy in h ```bash bash scripts/bootstrap.sh ``` + +Tinycast's native [settings.json](tinycast/.config/tinycast/settings.json) is linked +to `~/.config/tinycast/settings.json` with Stow. On a new Mac, enable +**Settings → Backup → Settings File** and choose **Import**. Tinycast reads file +edits and saves UI preference changes back through the link. + +Tinycast 0.11.12 excludes app and launcher shortcuts from its native file, so +[shortcuts.json](tinycast/.config/tinycast/shortcuts.json) stores those separately: +Command+Space for the launcher (key code 49, modifier 256), Option+W for WezTerm +(key code 13), Option+L for Linear (key code 37), and Option+H for Helium +(key code 4). All Option shortcuts use modifier 2048. +This is a dotfiles helper config, read by bootstrap. +Add app entries by bundle ID under `apps`; copy UI shortcut changes here before +rerunning setup. It manages the app shortcut list and preserves other preferences. + +Full setup links the files and applies shortcuts, or run `just bootstrap tinycast` +on macOS. The recipe quits Tinycast before writing shortcuts and reopens it if it +was running. diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh index f69dd1d..8b54e64 100644 --- a/scripts/bootstrap.sh +++ b/scripts/bootstrap.sh @@ -96,7 +96,7 @@ link_configs() { ok "Configs already linked ($*)" fi } -bootstrap_stow() { section 'Dotfiles'; link_configs fish gh-dash nvim pi wezterm yazi; } +bootstrap_stow() { section 'Dotfiles'; link_configs fish gh-dash nvim pi tinycast wezterm yazi; } bootstrap_gh() { section 'GitHub extensions' require_tool gh gh @@ -216,6 +216,42 @@ bootstrap_insomnia() { ok 'SwiftBar already running' fi } +bootstrap_tinycast() { + section 'Tinycast' + if [[ "$(uname -s)" != Darwin ]]; then warn 'Tinycast skipped: macOS only'; return; fi + local restart=0 shortcuts bundle_id + local app_ids=() + need defaults 'Run this recipe on macOS.' + require_tool jq jq + link_configs tinycast + shortcuts="$ROOT/tinycast/.config/tinycast/shortcuts.json" + jq -e '.launcher | type == "object"' "$shortcuts" >/dev/null + jq -e '.apps | type == "object"' "$shortcuts" >/dev/null + while IFS= read -r bundle_id; do app_ids+=("$bundle_id"); done < <(jq -r '.apps | keys[]' "$shortcuts") + if pgrep -x Tinycast >/dev/null 2>&1; then + run 'Quit Tinycast to apply settings' osascript -e 'tell application "Tinycast" to quit' + restart=1 + # Wait for shutdown so a final preference save cannot overwrite the config. + local attempt + for ((attempt = 0; attempt < 50; attempt++)); do + if ! pgrep -x Tinycast >/dev/null 2>&1; then break; fi + sleep 0.1 + done + if pgrep -x Tinycast >/dev/null 2>&1; then + fail 'Tinycast is still running. Quit it, then rerun just bootstrap tinycast.' + fi + fi + run 'Apply Tinycast launcher shortcut' defaults write com.tinycast.app hotkey.togglePalette -string "$(jq -c .launcher "$shortcuts")" + run 'Apply Tinycast app shortcut index' defaults write com.tinycast.app boundAppBundleIDs -array "${app_ids[@]}" + for bundle_id in "${app_ids[@]}"; do + run "Apply $bundle_id shortcut" defaults write com.tinycast.app "hotkey.app.$bundle_id" -string \ + "$(jq -c --arg id "$bundle_id" '.apps[$id]' "$shortcuts")" + done + if [[ "$(defaults read com.tinycast.app settingsFileEnabled 2>/dev/null || true)" != 1 ]]; then + warn 'Enable Settings → Backup → Settings File in Tinycast and choose Import to load the linked settings.json' + fi + if [[ "$restart" == 1 ]]; then run 'Restart Tinycast' open -a Tinycast; fi +} bootstrap_obscura() { section 'Obscura' local version=0.1.8 os arch asset tmp @@ -255,10 +291,11 @@ bootstrap_tailscale() { require_tool just just case "${1:-all}" in - all) bootstrap_brew; bootstrap_stow; bootstrap_gh; bootstrap_pi; bootstrap_fish; bootstrap_insomnia ;; + all) bootstrap_brew; bootstrap_stow; bootstrap_gh; bootstrap_pi; bootstrap_fish; bootstrap_insomnia; bootstrap_tinycast ;; brew) bootstrap_brew ;; stow) bootstrap_stow ;; gh) bootstrap_gh ;; pi) bootstrap_pi ;; fish) bootstrap_fish ;; insomnia) bootstrap_insomnia ;; obscura) bootstrap_obscura ;; tailscale) bootstrap_tailscale ;; - *) fail "Unknown setup target: $1. Use all, brew, stow, gh, pi, fish, insomnia, obscura, or tailscale." ;; + tinycast) bootstrap_tinycast ;; + *) fail "Unknown setup target: $1. Use all, brew, stow, gh, pi, fish, insomnia, tinycast, obscura, or tailscale." ;; esac printf '\n' if [[ "$skipped" -gt 0 ]]; then diff --git a/tests/integration.py b/tests/integration.py index 6fe63d3..00b9688 100644 --- a/tests/integration.py +++ b/tests/integration.py @@ -40,7 +40,7 @@ def main(): repo = base / 'repo with spaces' home = base / 'home' home.mkdir() - for directory in ('scripts', 'homebrew', 'fish', 'gh-dash', 'nvim', 'pi', 'wezterm', 'yazi', 'swiftbar'): + for directory in ('scripts', 'homebrew', 'fish', 'gh-dash', 'nvim', 'pi', 'wezterm', 'yazi', 'swiftbar', 'tinycast'): shutil.copytree(ROOT / directory, repo / directory, ignore=shutil.ignore_patterns('node_modules', '__pycache__')) shutil.copy2(ROOT / 'justfile', repo / 'justfile') @@ -102,7 +102,9 @@ def run(*args, success=True, show_output=True): 'const require = createRequire(process.argv[1]); ' 'await import(require.resolve("typebox")); console.log("Pi dependency import passed")', str(pi / 'package.json')) - for source in (repo / 'fish/.config/fish/config.fish', pi / 'package.json', + for source in (repo / 'fish/.config/fish/config.fish', + repo / 'tinycast/.config/tinycast/settings.json', + repo / 'tinycast/.config/tinycast/shortcuts.json', pi / 'package.json', pi / 'node_modules/typebox/package.json'): package = source.relative_to(repo).parts[0] target = home / source.relative_to(repo / package) diff --git a/tinycast/.config/tinycast/settings.json b/tinycast/.config/tinycast/settings.json new file mode 100644 index 0000000..213c75c --- /dev/null +++ b/tinycast/.config/tinycast/settings.json @@ -0,0 +1,167 @@ +{ + "general": { + "showInMenuBar": true, + "popToRootSeconds": 0, + "escapeKeyBehavior": "navigateBackOrClose", + "autoSwitchInputSource": null, + "supportReminders": true + }, + "appearance": { + "theme": "system", + "interfaceSize": "standard", + "compactMode": false, + "showFavoritesInCompactMode": true, + "followCursorAcrossDisplays": true, + "dragToReposition": false + }, + "hyperKey": { + "key": "none", + "includesShift": true, + "quickPress": "none" + }, + "calculator": { + "numberStyle": "system" + }, + "search": { + "showsSuggestions": true, + "sensitivity": "medium" + }, + "applications": { + "searchScopes": [ + "/Applications", + "/Applications/Utilities", + "/System/Applications", + "/System/Applications/Utilities", + "/System/Library/CoreServices/Applications", + "/System/Volumes/Preboot/Cryptexes/App/System/Applications", + "/System/Library/CoreServices/Finder.app", + "~/Applications" + ] + }, + "commands": { + "enabled": false, + "showInLauncher": true + }, + "quicklinks": { + "enabled": false, + "showInLauncher": true, + "opensNewWindow": false, + "selectionFallback": "ask", + "confirmsBeforeDelete": true + }, + "appleShortcuts": { + "enabled": false + }, + "ai": { + "enabled": false, + "webSearch": false, + "systemPrompt": "", + "systemPromptEnabled": true, + "retentionDays": "forever", + "opensTo": "recent", + "newChatAfterMinutes": 5, + "toolRounds": 25 + }, + "quickActions": { + "targetLanguage": "" + }, + "fileSearch": { + "enabled": false, + "scopes": [ + "~" + ], + "ignorePatterns": [] + }, + "notes": { + "enabled": false, + "rendersMarkdown": true, + "showsFormattingBar": true, + "folder": null + }, + "snippets": { + "showInLauncher": true, + "folder": null + }, + "navigation": { + "enabled": false, + "menuSearchShowsAppleMenu": false, + "menuSearchDisabledApps": [] + }, + "windowManagement": { + "enabled": false, + "showInLauncher": true, + "gap": 0, + "cycle": "off", + "layoutsShowInLauncher": true, + "roomsShowInLauncher": true, + "shortcuts": { + "left-half": null, + "right-half": null, + "top-half": null, + "bottom-half": null, + "top-left-quarter": null, + "top-right-quarter": null, + "bottom-left-quarter": null, + "bottom-right-quarter": null, + "first-three-fourths": null, + "last-three-fourths": null, + "first-third": null, + "center-third": null, + "last-third": null, + "first-two-thirds": null, + "last-two-thirds": null, + "maximize": null, + "almost-maximize": null, + "reasonable-size": null, + "maximize-height": null, + "maximize-width": null, + "center": null, + "center-half": null, + "center-two-thirds": null, + "make-larger": null, + "make-smaller": null, + "restore": null, + "move-left": null, + "move-right": null, + "move-up": null, + "move-down": null, + "next-display": null, + "previous-display": null, + "toggle-fullscreen": null, + "previous-space": null, + "next-space": null + }, + "customSizes": [], + "layouts": [], + "rooms": [] + }, + "clipboard": { + "enabled": true, + "retentionDays": 90, + "defaultAction": "paste", + "disabledApps": [ + "com.apple.keychainaccess", + "com.apple.Passwords" + ] + }, + "emoji": { + "skinTone": "none", + "gridColumns": 8 + }, + "calendar": { + "showInLauncher": true, + "launcherLimit": 5, + "span": "todayAndTomorrow", + "joinWindowMinutes": 5, + "autoJoinConfirms": true, + "meetingBrowser": null, + "menuBar": "disabled", + "menuBarUpcomingEvents": "today", + "menuBarLinkedEventsOnly": true, + "menuBarHidesWhenEmpty": false, + "hideCurrentEventAfterMinutes": "never" + }, + "extensions": { + "showInLauncher": true + } +} diff --git a/tinycast/.config/tinycast/shortcuts.json b/tinycast/.config/tinycast/shortcuts.json new file mode 100644 index 0000000..01aca43 --- /dev/null +++ b/tinycast/.config/tinycast/shortcuts.json @@ -0,0 +1,24 @@ +{ + "launcher": { + "combo": { + "_0": { "carbonKeyCode": 49, "carbonModifiers": 256 } + } + }, + "apps": { + "com.github.wez.wezterm": { + "combo": { + "_0": { "carbonKeyCode": 13, "carbonModifiers": 2048 } + } + }, + "com.linear": { + "combo": { + "_0": { "carbonKeyCode": 37, "carbonModifiers": 2048 } + } + }, + "net.imput.helium": { + "combo": { + "_0": { "carbonKeyCode": 4, "carbonModifiers": 2048 } + } + } + } +} From de287528930e1fd18159b8a0adce909f5739896e Mon Sep 17 00:00:00 2001 From: wu-json Date: Fri, 9 Oct 2026 11:04:06 -0700 Subject: [PATCH 2/2] fix: handle empty Tinycast app shortcuts on Bash 3.2 --- scripts/bootstrap.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh index 8b54e64..bedecf1 100644 --- a/scripts/bootstrap.sh +++ b/scripts/bootstrap.sh @@ -242,8 +242,9 @@ bootstrap_tinycast() { fi fi run 'Apply Tinycast launcher shortcut' defaults write com.tinycast.app hotkey.togglePalette -string "$(jq -c .launcher "$shortcuts")" - run 'Apply Tinycast app shortcut index' defaults write com.tinycast.app boundAppBundleIDs -array "${app_ids[@]}" - for bundle_id in "${app_ids[@]}"; do + # Bash 3.2 treats empty arrays as unset under nounset; expand only populated arrays. + run 'Apply Tinycast app shortcut index' defaults write com.tinycast.app boundAppBundleIDs -array ${app_ids[@]+"${app_ids[@]}"} + for bundle_id in ${app_ids[@]+"${app_ids[@]}"}; do run "Apply $bundle_id shortcut" defaults write com.tinycast.app "hotkey.app.$bundle_id" -string \ "$(jq -c --arg id "$bundle_id" '.apps[$id]' "$shortcuts")" done