commit 7fa06dd8ce849ef9e7231c44ed0be73ab399ee9e Author: Niklas Date: Tue Jan 26 23:32:19 2021 +0100 initial commit diff --git a/exampleLayout.yaml b/exampleLayout.yaml new file mode 100644 index 0000000..25484ba --- /dev/null +++ b/exampleLayout.yaml @@ -0,0 +1,10 @@ +buttons: + - title: Click me + exec: notify-send "Click me" + pos: 0 + - title: Also click me + exec: notify-send "Also click me" + pos: 1 + - title: Click click + exec: notify-send "Click click" + pos: 2 \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..917c6ca --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module git.kapelle.org/niklas/opendock-backend + +go 1.15 + +require ( + github.com/gorilla/websocket v1.4.2 + gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..18c5e9f --- /dev/null +++ b/go.sum @@ -0,0 +1,7 @@ +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/opendock.go b/opendock.go new file mode 100644 index 0000000..a2f7db9 --- /dev/null +++ b/opendock.go @@ -0,0 +1,143 @@ +package main + +import ( + "encoding/json" + "errors" + "io/ioutil" + "log" + "net/http" + "os/exec" + + "github.com/gorilla/websocket" + "gopkg.in/yaml.v2" +) + +type baseMessage struct { + MessageType string `json:"messageType"` +} + +// message type "buttonPress" +type buttonPressMessage struct { + ButtonIndex int `json:"buttonIndex"` + Long bool `json:"long"` +} + +type buttonLayoutConfig struct { + Buttons []buttonConfig `yaml:"buttons" json:"buttons"` +} + +type buttonConfig struct { + Title string `yaml:"title" json:"title"` + Exec string `yaml:"exec"` + Pos int `yaml:"pos" json:"pos"` +} + +var upgrader = websocket.Upgrader{} +var buttonLayout buttonLayoutConfig + +func upgradeWebsocket(w http.ResponseWriter, r *http.Request) { + c, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Print("Failed to upgrade connection:", err) + return + } + log.Print("New device connected") + eventLoop(c) +} + +func eventLoop(c *websocket.Conn) { + defer c.Close() + for { + _, rawMessage, err := c.ReadMessage() + if err != nil { + log.Println("Failed to read message: ", err) + break + } + + var base baseMessage + + err = json.Unmarshal(rawMessage, &base) + if err != nil { + log.Printf("Failed to parse base message: %s", err.Error()) + continue + } + + response, err := handleMessage(base, rawMessage) + if err != nil { + log.Printf("Failed to handle message: %s", err.Error()) + } else { + if response != nil { + c.WriteMessage(websocket.TextMessage, *response) + } + } + } +} + +// handleMessage forwards the message to the handler +func handleMessage(base baseMessage, raw []byte) (*[]byte, error) { + switch base.MessageType { + case "buttonPress": + var msg buttonPressMessage + err := json.Unmarshal(raw, &msg) + if err != nil { + return nil, err + } + handleButtonPress(msg) + case "layout": + val, err := json.Marshal(buttonLayout) + return &val, err + + default: + return nil, errors.New("Unmachted message type: " + base.MessageType) + } + + return nil, nil +} + +// handleButtonPress handle the press of a button on the app +func handleButtonPress(msg buttonPressMessage) { + log.Printf("Button press. Index: %d", msg.ButtonIndex) + // Find button in layout + for i := 0; i < len(buttonLayout.Buttons); i++ { + if buttonLayout.Buttons[i].Pos == msg.ButtonIndex { + command := buttonLayout.Buttons[i].Exec + go runCommand(command) + break + } + } + +} + +func runCommand(command string) { + cmd := exec.Command("sh", "-c", command) + cmd.Run() +} + +// loadLayout loads the layout file +func loadLayout(filePath string) (*buttonLayoutConfig, error) { + file, err := ioutil.ReadFile(filePath) + if err != nil { + return nil, err + } + + var layout buttonLayoutConfig + + err = yaml.Unmarshal(file, &layout) + if err != nil { + return nil, err + } + + return &layout, nil +} + +func main() { + loadedLayout, err := loadLayout("exampleLayout.yaml") + if err != nil { + log.Fatalf("Failed to load layout: %s", err.Error()) + } + + buttonLayout = *loadedLayout + + http.HandleFunc("/ws", upgradeWebsocket) + log.Fatal(http.ListenAndServe(":8069", nil)) +}