From b217d8c5789cba1f7f9d2185d60c7e2acb9c824b Mon Sep 17 00:00:00 2001 From: rszyma Date: Fri, 24 Jan 2025 04:05:37 +0100 Subject: [PATCH] feat: custom status icons (#49) --- .gitignore | 3 + README.md | 16 +++- app/app.go | 16 ++-- icons/icons.go | 15 ---- main.go | 10 +++ {icons => status_icons}/128x128.png | Bin {icons => status_icons}/crash.ico | Bin {icons => status_icons}/default.ico | Bin {icons => status_icons}/empty.ico | Bin {icons => status_icons}/live-reload.ico | Bin {icons => status_icons}/pause.ico | Bin status_icons/status_icons.go | 95 ++++++++++++++++++++++++ 12 files changed, 131 insertions(+), 24 deletions(-) delete mode 100644 icons/icons.go rename {icons => status_icons}/128x128.png (100%) rename {icons => status_icons}/crash.ico (100%) rename {icons => status_icons}/default.ico (100%) rename {icons => status_icons}/empty.ico (100%) rename {icons => status_icons}/live-reload.ico (100%) rename {icons => status_icons}/pause.ico (100%) create mode 100644 status_icons/status_icons.go diff --git a/.gitignore b/.gitignore index 741236b..209e4af 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ kanata-tray .envrc kanata_tray_lastrun.log result +*.kbd +kanata-tray.*toml* +kanata diff --git a/README.md b/README.md index cb7ee16..34d8800 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,6 @@ allow_concurrent_presets = false kanata_executable = '~/bin/kanata' # if empty or omitted, system $PATH will be searched. kanata_config = '' # if empty or not omitted, kanata default config locations will be used. tcp_port = 5829 # if not specified, defaults to 5829 -extra_args = ['-n', '-c=~/.config/kanata/another.kbd'] [defaults.hooks] # Hooks allow running custom commands on specific events (e.g. starting preset). @@ -91,6 +90,21 @@ Other notes: - You can use `~` in `kanata_config`, `kanata_executable` and `extra_args` to substitute to your "home" directory. - On Windows: make sure to surround paths with single-quotes `'` instead of double-quotes, otherwise paths will not work (because `\` would be treated as escape character). +### Overriding status icons + +It's possible to customize looks of status icons - i.e. reload, idle, paused. + +Similar to `icons` folder (layer icons), `status_icons` folder will be created +next to `kanata-tray.toml` if it doesn't exists. When first creating, it will +also populate with default icons. To override status icons, simply replace it +with other one of your choice. + +- Accepted file types are the same as in `icons`. +- The filename prefix must match specifc status i.e. files must start with one of: +"default", "crash", "pause", "live-reload". +- If there are multiple files matching the prefix, only one of them will be loaded, +and other ignored. + ### Hooks Hooks allow running custom commands on specific events (e.g. starting preset). diff --git a/app/app.go b/app/app.go index 680d2d0..aafbed8 100644 --- a/app/app.go +++ b/app/app.go @@ -10,8 +10,8 @@ import ( "github.com/labstack/gommon/log" "github.com/skratchdot/open-golang/open" - "github.com/rszyma/kanata-tray/icons" runner_pkg "github.com/rszyma/kanata-tray/runner" + "github.com/rszyma/kanata-tray/status_icons" ) type SystrayApp struct { @@ -45,7 +45,7 @@ type SystrayApp struct { } func NewSystrayApp(menuTemplate []PresetMenuEntry, layerIcons LayerIcons, allowConcurrentPresets bool, logFilepath string) *SystrayApp { - systray.SetIcon(icons.Default) + systray.SetIcon(status_icons.Default) systray.SetTooltip("kanata-tray") t := &SystrayApp{ @@ -125,7 +125,7 @@ func (t *SystrayApp) runPreset(presetIndex int, runner *runner_pkg.Runner) { } func (app *SystrayApp) StartProcessingLoop(runner *runner_pkg.Runner, configFolder string) { - app.setIcon(icons.Pause) + app.setIcon(status_icons.Pause) // handle autoruns autoranOnePreset := false @@ -156,7 +156,7 @@ func (app *SystrayApp) StartProcessingLoop(runner *runner_pkg.Runner, configFold if event.Item.LayerChange != nil { icon := app.layerIcons.IconForLayerName(event.PresetName, event.Item.LayerChange.NewLayer) if icon == nil { - icon = icons.Default + icon = status_icons.Default } app.setIcon(icon) } @@ -177,7 +177,7 @@ func (app *SystrayApp) StartProcessingLoop(runner *runner_pkg.Runner, configFold } if event.Item.ConfigFileReload != nil { prevIcon := app.currentIconData - app.setIcon(icons.LiveReload) + app.setIcon(status_icons.LiveReload) time.Sleep(150 * time.Millisecond) app.setIcon(prevIcon) } @@ -192,14 +192,14 @@ func (app *SystrayApp) StartProcessingLoop(runner *runner_pkg.Runner, configFold if runnerPipelineErr != nil { log.Errorf("Kanata runner terminated with an error: %v", runnerPipelineErr) app.setStatus(i, statusCrashed) - app.setIcon(icons.Crash) + app.setIcon(status_icons.Crash) } else { log.Infof("Previous kanata process terminated successfully") app.setStatus(i, statusIdle) if app.isAnyPresetRunning() { - app.setIcon(icons.Default) + app.setIcon(status_icons.Default) } else { - app.setIcon(icons.Pause) + app.setIcon(status_icons.Pause) } } if app.scheduledPresetIndex != -1 { diff --git a/icons/icons.go b/icons/icons.go deleted file mode 100644 index 444108a..0000000 --- a/icons/icons.go +++ /dev/null @@ -1,15 +0,0 @@ -package icons - -import _ "embed" - -//go:embed default.ico -var Default []byte - -//go:embed crash.ico -var Crash []byte - -//go:embed pause.ico -var Pause []byte - -//go:embed live-reload.ico -var LiveReload []byte diff --git a/main.go b/main.go index 732b699..68d6b02 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ import ( "github.com/rszyma/kanata-tray/app" "github.com/rszyma/kanata-tray/config" "github.com/rszyma/kanata-tray/runner" + "github.com/rszyma/kanata-tray/status_icons" ) var ( @@ -162,6 +163,15 @@ func mainImpl() error { } layerIcons := app.ResolveIcons(configFolder, cfg) + err = status_icons.CreateDefaultStatusIconsDirIfNotExists(configFolder) + if err != nil { + return fmt.Errorf("CreateDefaultStatusIconsDirIfNotExists: %v", err) + } + err = status_icons.LoadCustomStatusIcons(configFolder) + if err != nil { + return fmt.Errorf("LoadCustomStatusIcons: %v", err) + } + runner := runner.NewRunner() onReady := func() { diff --git a/icons/128x128.png b/status_icons/128x128.png similarity index 100% rename from icons/128x128.png rename to status_icons/128x128.png diff --git a/icons/crash.ico b/status_icons/crash.ico similarity index 100% rename from icons/crash.ico rename to status_icons/crash.ico diff --git a/icons/default.ico b/status_icons/default.ico similarity index 100% rename from icons/default.ico rename to status_icons/default.ico diff --git a/icons/empty.ico b/status_icons/empty.ico similarity index 100% rename from icons/empty.ico rename to status_icons/empty.ico diff --git a/icons/live-reload.ico b/status_icons/live-reload.ico similarity index 100% rename from icons/live-reload.ico rename to status_icons/live-reload.ico diff --git a/icons/pause.ico b/status_icons/pause.ico similarity index 100% rename from icons/pause.ico rename to status_icons/pause.ico diff --git a/status_icons/status_icons.go b/status_icons/status_icons.go new file mode 100644 index 0000000..65c1a99 --- /dev/null +++ b/status_icons/status_icons.go @@ -0,0 +1,95 @@ +package status_icons + +import ( + _ "embed" + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + + "github.com/labstack/gommon/log" +) + +//go:embed default.ico +var Default []byte + +//go:embed crash.ico +var Crash []byte + +//go:embed pause.ico +var Pause []byte + +//go:embed live-reload.ico +var LiveReload []byte + +////////////////////////////////////////////// + +var statusIconsDir string = "status_icons" + +func LoadCustomStatusIcons(configDir string) error { + prefixes := []string{"default", "crash", "pause", "live-reload"} + for i, prefix := range prefixes { + matches, err := filepath.Glob(filepath.Join( + configDir, statusIconsDir, fmt.Sprintf("%s.*", prefix), + )) + if err != nil { + return fmt.Errorf("filepath.Glob: %v", err) + } + if len(matches) < 1 { + continue + } + + // Take only first match, ignore others. There should be only 1 matching + // icon name anyway. + match := matches[0] + + log.Infof("loading status icon: %s", match) + fileContent, err := os.ReadFile(match) + if err != nil { + log.Errorf("LoadCustomStatusIcons: os.ReadFile: %v", err) + continue + } + + switch i { + case 0: + Default = fileContent + case 1: + Crash = fileContent + case 2: + Pause = fileContent + case 3: + LiveReload = fileContent + default: + panic("out of range") + } + } + + return nil +} + +func CreateDefaultStatusIconsDirIfNotExists(configDir string) error { + customIconsPath := filepath.Join(configDir, statusIconsDir) + _, err := os.Stat(customIconsPath) + + if errors.Is(err, fs.ErrNotExist) { + log.Infof("status_icons dir doesn't exist. Creating it and populating with the default icons.") + err := os.MkdirAll(customIconsPath, os.ModePerm) + if err != nil { + return fmt.Errorf("failed to create folder: %v", err) + } + names := []string{"default.ico", "crash.ico", "pause.ico", "live-reload.ico"} + data := [][]byte{Default, Crash, Pause, LiveReload} + for i, name := range names { + path := filepath.Join(customIconsPath, name) + err := os.WriteFile(path, data[i], 0o644) + if err != nil { + return fmt.Errorf("writing file %s failed", path) + } + } + } else if err != nil { + return fmt.Errorf("error checking if %s dir exists", customIconsPath) + } + // already exists, do nothing. + return nil +}