Skip to content

Instantly share code, notes, and snippets.

@raylee
Created March 24, 2025 00:00
Show Gist options
  • Save raylee/283c0299e32926334f3ff5bf7f3e280d to your computer and use it in GitHub Desktop.
Save raylee/283c0299e32926334f3ff5bf7f3e280d to your computer and use it in GitHub Desktop.
ghost-rider rotates through user-defined options in a Ghostty config file.
// ghost-rider rotates options inside a Ghostty config file.
// It expects the config file to have a specific format, where options are defined
// in a comment line starting with "# gr: " followed by a comma-separated list of
// options, and the actual option line that follows it. The script will rotate
// the option to the next one in the list each time it is run.
// The specific option to rotate can be specified with the -opt flag.
// Invoking this with a hotkey manager such as skhd allows for easy
// cycling through options like themes or background opacity in ghostty.
// The script tells Ghostty to reload its configuration after modifying the file.
// The compiled version of this binary and the skhd helper will need to be added
// to the assistive access list in System Settings >> Privacy & Security >> Accessibility.
// Config for https://github.com/koekeishiya/skhd
// cmd + shift - p : ~/.local/bin/ghost-rider -opt theme
// cmd + shift - b : ~/.local/bin/ghost-rider -opt background-opacity
/*
Example ghostty config snippet:
# gr: 0.75,0.9,1.0,0.5,0.6
background-opacity = 0.75
# gr: Adwaita,Japanesque
theme = Japanesque
*/
package main
import (
"flag"
"fmt"
"os"
"strings"
"github.com/andybrewer/mack"
)
var (
cfg = flag.String("fn", "~/.config/ghostty/config", "config file path")
opt = flag.String("opt", "theme", "option to rotate")
)
func main() {
flag.Parse()
// Expand the config file path, subsituting ~ with the home directory
homeDir, err := os.UserHomeDir()
if err != nil {
fmt.Fprintf(os.Stderr, "Error getting home directory: %v\n", err)
os.Exit(1)
}
*cfg = strings.ReplaceAll(*cfg, "~", homeDir)
conf, err := os.ReadFile(*cfg)
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading config file: %v\n", err)
os.Exit(1)
}
lines := strings.Split(string(conf), "\n")
// Example stanza from the ghostty config file:
// # gr: 0.75,0.9,1.0,0.5,0.6
// background-opacity = 0.75
// Look for the line that starts with the option.
for i := range len(lines) - 1 {
if !strings.HasPrefix(lines[i], "# gr: ") {
continue
}
opts := strings.Split(lines[i][6:], ",")
parts := strings.Split(lines[i+1], "=")
if len(parts) != 2 {
continue
}
parts[0], parts[1] = strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
// check if the line before the comment is the option we want to rotate
if parts[0] != *opt {
// fmt.Fprintln(os.Stderr, "Skipping line:", lines[i-1])
continue
}
for j, o := range opts {
if o == parts[1] {
// rotate to the next option
nextOpt := opts[(j+1)%len(opts)]
lines[i+1] = fmt.Sprintf("%s = %s", parts[0], nextOpt)
break
}
}
}
// Write the modified config back to the file
err = os.WriteFile(*cfg, []byte(strings.Join(lines, "\n")), 0o644)
if err != nil {
fmt.Fprintf(os.Stderr, "Error writing config file: %v\n", err)
os.Exit(1)
}
mack.Tell("System Events",
`tell process "Ghostty"`,
`click menu item "Reload Configuration" of menu "Ghostty" of menu bar item "Ghostty" of menu bar 1`,
`end tell`)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment