-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
sni: app config file ~/.sni/config.yaml; add Applications menu driven…
… from ~/.sni/app.yaml file viper: watcher implementation is broken with multiple vipers watching the same folder
- v0.0.99
- v0.0.98
- v0.0.97
- v0.0.96
- v0.0.95
- v0.0.94
- v0.0.93
- v0.0.92
- v0.0.91
- v0.0.90
- v0.0.89
- v0.0.88
- v0.0.88-test
- v0.0.87
- v0.0.86
- v0.0.85
- v0.0.84
- v0.0.83
- v0.0.82
- v0.0.81
- v0.0.81-pre
- v0.0.80
- v0.0.80-alpha
- v0.0.79
- v0.0.78
- v0.0.78-2
- v0.0.77
- v0.0.76
- v0.0.75
- v0.0.75-rc7
- v0.0.75-rc6
- v0.0.75-rc5
- v0.0.75-rc4
- v0.0.75-rc3
- v0.0.75-rc2
- v0.0.75-rc1
- v0.0.74
- v0.0.73
- v0.0.72
- v0.0.71
- v0.0.70
- v0.0.69
- v0.0.68
- v0.0.67
- v0.0.66
- v0.0.65
- v0.0.64
- v0.0.63
- v0.0.62
- v0.0.61
- v0.0.60
- v0.0.59
- v0.0.58
- v0.0.57
- v0.0.56
- v0.0.55
- v0.0.54
- v0.0.53
- v0.0.52
- v0.0.51
- v0.0.50
- v0.0.49
- v0.0.48
- v0.0.47
- v0.0.46
- v0.0.45
- v0.0.44
- v0.0.43
- v0.0.42
- v0.0.41
- v0.0.40
- v0.0.39
- v0.0.38
- v0.0.37
- v0.0.36
- v0.0.35
- v0.0.34
- v0.0.33
- v0.0.32
- v0.0.31
- v0.0.30
- v0.0.29
- v0.0.28
- v0.0.27
- v0.0.26
- test
1 parent
17b2373
commit 2c7edac
Showing
12 changed files
with
1,231 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# This file defines application shortcuts for the Applications menu. | ||
# To install, copy this file to your configuration directory: | ||
# On MacOS and Linux, this is "$HOME/.sni/" | ||
# On Windows, this is "%LOCALAPPDATA%/sni/" | ||
|
||
# `apps` is a list of application shortcuts | ||
apps: | ||
|
||
- # `name` is what is displayed in the Applications menu: | ||
name: RetroArch | ||
# `os` specifies that this app is only launchable on this particular operating system | ||
# if `os` is missing/empty, then the Application will appear on all operating systems. | ||
# valid values are: | ||
# "windows" = Windows | ||
# "linux" = Linux | ||
# "darwin" = MacOS | ||
os: darwin | ||
# `path` is the location of the executable to open: | ||
# On MacOS, app bundle folders that end in .app are launched via `open -a <path> <args...>` | ||
# On Windows and Linux, executable files are launched normally | ||
path: /Applications/RetroArch.app | ||
|
||
- name: RetroArch | ||
os: windows | ||
path: C:\RetroArch-Win64\RetroArch.exe | ||
|
||
- name: OpenTracker | ||
os: darwin | ||
# `dir` is the current directory that the executable is launched from: | ||
dir: $HOME/Developer/me/alttpo/OpenTracker/OpenTracker/bin/Debug/net5.0 | ||
# `path` does not have to be an absolute path, and could be found in the system lookup $PATH | ||
path: dotnet | ||
# `args` is the list of arguments passed to the application: | ||
args: | ||
- OpenTracker.dll | ||
|
||
- name: SNI Home Page | ||
# `url` will open the given URL when clicked in the menu: | ||
# On MacOS, URLs are launched via `open <url>` | ||
# On Windows, URLs are launched via `start <url>` | ||
# On Linux, URLs are launched via `xdg-open <url>` | ||
url: https://github.com/alttpo/sni |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
package config | ||
|
||
import ( | ||
"fmt" | ||
"github.com/fsnotify/fsnotify" | ||
"github.com/spf13/viper" | ||
"log" | ||
"os" | ||
"path/filepath" | ||
"runtime" | ||
"sni/ob" | ||
) | ||
|
||
var ( | ||
ConfigObservable ob.Observable | ||
configObservable = ob.NewObservable() | ||
ConfigPath string | ||
|
||
AppsObservable ob.Observable | ||
appsObservable = ob.NewObservable() | ||
AppsPath string | ||
) | ||
|
||
var VerboseLogging bool = false | ||
|
||
var ( | ||
Config *viper.Viper = viper.New() | ||
Apps *viper.Viper = viper.New() | ||
) | ||
|
||
func Load() { | ||
log.Printf("config: load\n") | ||
|
||
loadConfig() | ||
loadApps() | ||
} | ||
|
||
func Save() { | ||
var err error | ||
|
||
log.Printf("config: save\n") | ||
|
||
err = Config.WriteConfigAs(ConfigPath) | ||
if err != nil { | ||
log.Printf("config: save: %s\n", err) | ||
return | ||
} | ||
} | ||
|
||
func loadConfig() { | ||
ConfigObservable = configObservable | ||
|
||
// load configuration: | ||
Config.SetEnvPrefix("SNI") | ||
configFilename := "config" | ||
Config.SetConfigName(configFilename) | ||
Config.SetConfigType("yaml") | ||
if runtime.GOOS == "windows" { | ||
ConfigPath = os.ExpandEnv("$LOCALAPPDATA/sni/") | ||
_ = os.Mkdir(ConfigPath, 0644|os.ModeDir) | ||
Config.AddConfigPath(ConfigPath) | ||
} else { | ||
ConfigPath = os.ExpandEnv("$HOME/.sni/") | ||
Config.AddConfigPath(ConfigPath) | ||
} | ||
ConfigPath = filepath.Join(ConfigPath, fmt.Sprintf("%s.yaml", configFilename)) | ||
|
||
Config.OnConfigChange(func(_ fsnotify.Event) { | ||
log.Printf("config: %s.yaml modified\n", configFilename) | ||
configObservable.ObjectPublish(Config) | ||
}) | ||
Config.WatchConfig() | ||
|
||
err := Config.ReadInConfig() | ||
if err != nil { | ||
if _, ok := err.(viper.ConfigFileNotFoundError); ok { | ||
// no problem. | ||
} else { | ||
log.Printf("%s\n", err) | ||
} | ||
return | ||
} | ||
|
||
configObservable.ObjectPublish(Config) | ||
} | ||
|
||
func loadApps() { | ||
AppsObservable = appsObservable | ||
|
||
// load configuration: | ||
appsFilename := "apps" | ||
Apps.SetConfigName(appsFilename) | ||
Apps.SetConfigType("yaml") | ||
if runtime.GOOS == "windows" { | ||
AppsPath = os.ExpandEnv("$LOCALAPPDATA/sni/") | ||
_ = os.Mkdir(AppsPath, 0644|os.ModeDir) | ||
Apps.AddConfigPath(AppsPath) | ||
} else { | ||
AppsPath = os.ExpandEnv("$HOME/.sni/") | ||
Apps.AddConfigPath(AppsPath) | ||
} | ||
AppsPath = filepath.Join(ConfigPath, fmt.Sprintf("%s.yaml", appsFilename)) | ||
|
||
Apps.OnConfigChange(func(_ fsnotify.Event) { | ||
log.Printf("config: %s.yaml modified\n", appsFilename) | ||
appsObservable.ObjectPublish(Apps) | ||
}) | ||
Apps.WatchConfig() | ||
|
||
err := Apps.ReadInConfig() | ||
if err != nil { | ||
if _, ok := err.(viper.ConfigFileNotFoundError); ok { | ||
// no problem. | ||
} else { | ||
log.Printf("%s\n", err) | ||
} | ||
return | ||
} | ||
|
||
appsObservable.ObjectPublish(Apps) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package tray | ||
|
||
import ( | ||
"log" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"runtime" | ||
) | ||
|
||
type appConfig struct { | ||
Name string | ||
Tooltip string | ||
|
||
Os string | ||
|
||
Dir string | ||
Path string | ||
Args []string | ||
|
||
Url string | ||
} | ||
|
||
func launch(app *appConfig) { | ||
path := app.Path | ||
path = os.ExpandEnv(path) | ||
cleanPath := filepath.Clean(path) | ||
|
||
args := app.Args[:] | ||
// expand environment variables like `$SNI_USB2SNES_LISTEN_HOST`: | ||
for j, arg := range args { | ||
args[j] = os.ExpandEnv(arg) | ||
} | ||
|
||
dir := app.Dir | ||
dir = os.ExpandEnv(dir) | ||
|
||
if app.Url != "" { | ||
log.Printf("open: %s\n", app.Url) | ||
|
||
var cmd *exec.Cmd | ||
if runtime.GOOS == "darwin" { | ||
cmd = exec.Command("open", app.Url) | ||
} else if runtime.GOOS == "windows" { | ||
cmd = exec.Command("start", app.Url) | ||
} else { | ||
cmd = exec.Command("xdg-open", app.Url) | ||
} | ||
|
||
err := cmd.Start() | ||
if err != nil { | ||
log.Printf("open: %s\n", err) | ||
return | ||
} | ||
|
||
return | ||
} | ||
|
||
if runtime.GOOS == "darwin" { | ||
if filepath.Ext(cleanPath) == ".app" { | ||
// open app bundles with "open" command: | ||
if fi, err := os.Stat(cleanPath); err == nil && fi.IsDir() { | ||
args = append([]string{"-a", path}, args...) | ||
path = "open" | ||
} | ||
} | ||
} | ||
|
||
log.Printf("open: %s %s\n", path, args) | ||
cmd := exec.Command(path, args...) | ||
cmd.Dir = dir | ||
err := cmd.Start() | ||
if err != nil { | ||
log.Printf("open: %s\n", err) | ||
return | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package ob | ||
|
||
type Observable interface { | ||
Type() ObservableType | ||
Subscribe(observer Observer) | ||
Unsubscribe(observer Observer) | ||
} | ||
|
||
type Observer interface { | ||
Observe(object interface{}) | ||
|
||
Equals(other Observer) bool | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
package ob | ||
|
||
import ( | ||
"fmt" | ||
"sync" | ||
) | ||
|
||
type ObservableType int | ||
|
||
func (o ObservableType) String() string { | ||
switch o { | ||
case ObservableUnknown: | ||
return "Unknown" | ||
case ObservableObject: | ||
return "Object" | ||
case ObservableList: | ||
return "List" | ||
} | ||
return fmt.Sprintf("(unexpected ObservableType value %d)", o) | ||
} | ||
|
||
const ( | ||
ObservableUnknown ObservableType = iota | ||
ObservableObject | ||
ObservableList | ||
) | ||
|
||
type ListOperation string | ||
|
||
const ( | ||
// ListInit initializes the contents of the list (or replaces an existing list with new contents) | ||
ListInit ListOperation = "init" | ||
// ListConcat appends elements to the end of the list | ||
ListConcat ListOperation = "concat" | ||
) | ||
|
||
type ListEvent struct { | ||
// Operation denotes what happened to the list | ||
Operation ListOperation `json:"op"` | ||
// Elements is the data supporting the operation | ||
Elements []interface{} `json:"e"` | ||
} | ||
|
||
// ObservableImpl represents either an observable Object or observable List. | ||
// The first call to ObjectPublish() establishes the type as Object. | ||
// The first call to ListAppendOne(), ListConcat(), or ListInit() establishes the type as List. | ||
// The observable type cannot be changed once initialized; the call will panic if attempted. | ||
// Calling Object() or List() will return nil until the type is established. | ||
// List types publish ListEvent instances, reflecting the type of change made to the List. | ||
// On first Subscribe(), an Object type will publish its last published state. | ||
// On first Subscribe(), a List type will publish the entire list. | ||
type ObservableImpl struct { | ||
lock sync.Mutex | ||
observers []Observer | ||
|
||
observableType ObservableType | ||
|
||
object interface{} | ||
list []interface{} | ||
} | ||
|
||
func NewObservable() *ObservableImpl { | ||
return &ObservableImpl{} | ||
} | ||
|
||
func (o *ObservableImpl) Type() ObservableType { | ||
return o.observableType | ||
} | ||
|
||
func (o *ObservableImpl) Subscribe(observer Observer) { | ||
if observer == nil { | ||
return | ||
} | ||
|
||
defer o.lock.Unlock() | ||
o.lock.Lock() | ||
|
||
// make sure only one instance is subscribed: | ||
o.unsubscribe(observer) | ||
o.observers = append(o.observers, observer) | ||
|
||
// send last published state: | ||
switch o.observableType { | ||
case ObservableUnknown: | ||
// intentionally do nothing here since the observable is not initialized yet | ||
break | ||
case ObservableObject: | ||
// objects publish the last published state on first subscribe: | ||
observer.Observe(o.object) | ||
break | ||
case ObservableList: | ||
// lists publish the entire list contents on first subscribe: | ||
observer.Observe(ListEvent{ | ||
Operation: ListInit, | ||
Elements: o.list, | ||
}) | ||
break | ||
} | ||
} | ||
|
||
func (o *ObservableImpl) Unsubscribe(observer Observer) { | ||
if observer == nil { | ||
return | ||
} | ||
|
||
defer o.lock.Unlock() | ||
o.lock.Lock() | ||
|
||
if o.observers == nil { | ||
return | ||
} | ||
|
||
o.unsubscribe(observer) | ||
} | ||
|
||
func (o *ObservableImpl) unsubscribe(observer Observer) { | ||
for i := len(o.observers) - 1; i >= 0; i-- { | ||
if observer.Equals(o.observers[i]) { | ||
o.observers = append(o.observers[0:i], o.observers[i+1:]...) | ||
} | ||
} | ||
} | ||
|
||
func (o *ObservableImpl) enforceType(mustType ObservableType) { | ||
if o.observableType == ObservableUnknown { | ||
// set new type: | ||
o.observableType = mustType | ||
} else if o.observableType != mustType { | ||
// panic otherwise: | ||
panic(fmt.Errorf("observable attempted to change type from %s to %s", o.observableType, mustType)) | ||
} | ||
} | ||
|
||
func (o *ObservableImpl) Object() interface{} { | ||
return o.object | ||
} | ||
|
||
func (o *ObservableImpl) ObjectPublish(object interface{}) { | ||
defer o.lock.Unlock() | ||
o.lock.Lock() | ||
|
||
o.enforceType(ObservableObject) | ||
o.object = object | ||
for _, observer := range o.observers { | ||
observer.Observe(object) | ||
} | ||
} | ||
|
||
func (o *ObservableImpl) List() []interface{} { | ||
return o.list | ||
} | ||
|
||
func (o *ObservableImpl) ListAppendOne(newElement interface{}) { | ||
defer o.lock.Unlock() | ||
o.lock.Lock() | ||
|
||
o.enforceType(ObservableList) | ||
o.list = append(o.list, newElement) | ||
newElements := []interface{}{newElement} | ||
for _, observer := range o.observers { | ||
observer.Observe(ListEvent{ | ||
Operation: ListConcat, | ||
Elements: newElements, | ||
}) | ||
} | ||
} | ||
|
||
func (o *ObservableImpl) ListAppendMany(newElements []interface{}) { | ||
defer o.lock.Unlock() | ||
o.lock.Lock() | ||
|
||
o.enforceType(ObservableList) | ||
o.list = append(o.list, newElements...) | ||
for _, observer := range o.observers { | ||
observer.Observe(ListEvent{ | ||
Operation: ListConcat, | ||
Elements: newElements, | ||
}) | ||
} | ||
} | ||
|
||
func (o *ObservableImpl) ListInit(newList []interface{}) { | ||
defer o.lock.Unlock() | ||
o.lock.Lock() | ||
|
||
o.enforceType(ObservableList) | ||
o.list = newList | ||
for _, observer := range o.observers { | ||
observer.Observe(ListEvent{ | ||
Operation: ListInit, | ||
Elements: newList, | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package ob | ||
|
||
type ObserverFunc func(object interface{}) | ||
|
||
type observerImpl struct { | ||
key string | ||
observer ObserverFunc | ||
} | ||
|
||
func NewObserver(key string, observer ObserverFunc) Observer { | ||
return &observerImpl{ | ||
key: key, | ||
observer: observer, | ||
} | ||
} | ||
|
||
func (o *observerImpl) Equals(other Observer) bool { | ||
if otherImpl, ok := other.(*observerImpl); ok { | ||
return o.key == otherImpl.key | ||
} | ||
return false | ||
} | ||
|
||
func (o *observerImpl) Observe(object interface{}) { | ||
o.observer(object) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters