mirror of
https://github.com/Macrame-App/Macrame
synced 2025-12-29 07:19:26 +00:00
Refactor: Moved Go app to the root and fixed links.
This commit is contained in:
parent
d1de67910d
commit
7157d43168
28 changed files with 100 additions and 164 deletions
137
app/api.go
Normal file
137
app/api.go
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
Macrame is a program that enables the user to create keyboard macros and button panels.
|
||||
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
|
||||
be created with HTML and CSS.
|
||||
|
||||
Copyright (C) 2025 Jesse Malotaux
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"macrame/app/helper"
|
||||
"mime"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ApiCORS(w http.ResponseWriter, r *http.Request) (http.ResponseWriter, *http.Request) {
|
||||
origin := r.Header.Get("Origin")
|
||||
|
||||
w.Header().Set("Access-Control-Allow-Origin", "http://localhost:5173")
|
||||
|
||||
if strings.HasPrefix(r.Host, "192.168.") {
|
||||
w.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
}
|
||||
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Accept, Accept-Language, Accept-Encoding")
|
||||
|
||||
return w, r
|
||||
}
|
||||
|
||||
func ApiGet(w http.ResponseWriter, r *http.Request) {
|
||||
root, err := filepath.Abs("public")
|
||||
if err != nil {
|
||||
MCRMLog("ApiGet Abs Error: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
var file string
|
||||
|
||||
if strings.Contains(r.URL.Path, "/config.js") {
|
||||
file = filepath.Join(root, "config.js")
|
||||
w.Header().Set("Content-Type", "text/javascript") // set content type header
|
||||
} else if r.URL.Path != "/" {
|
||||
file = filepath.Join(root, r.URL.Path)
|
||||
}
|
||||
|
||||
contentType := mime.TypeByExtension(filepath.Ext(file)) // get content type
|
||||
|
||||
if contentType == "" {
|
||||
file = filepath.Join(root, "index.html")
|
||||
}
|
||||
|
||||
http.ServeFile(w, r, file)
|
||||
}
|
||||
|
||||
func ApiPost(w http.ResponseWriter, r *http.Request) {
|
||||
access, data, err := helper.EndpointAccess(w, r)
|
||||
|
||||
if !access || err != nil {
|
||||
MCRMLog("ApiPost EndPointAccess Error: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
if data != "" {
|
||||
ApiAuth(data, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
switch r.URL.Path {
|
||||
case "/macro/check":
|
||||
CheckMacro(w, r)
|
||||
case "/macro/record":
|
||||
SaveMacro(w, r)
|
||||
case "/macro/list":
|
||||
ListMacros(w, r)
|
||||
case "/macro/open":
|
||||
OpenMacro(w, r)
|
||||
case "/macro/delete":
|
||||
DeleteMacro(w, r)
|
||||
case "/macro/play":
|
||||
PlayMacro("", w, r)
|
||||
case "/device/server/ip":
|
||||
ListServerIP(w)
|
||||
case "/device/list":
|
||||
DeviceList(w, r)
|
||||
case "/device/access/check":
|
||||
DeviceAccessCheck(w, r)
|
||||
case "/device/access/request":
|
||||
DeviceAccessRequest(w, r)
|
||||
case "/device/link/ping":
|
||||
PingLink(w, r)
|
||||
case "/device/link/start":
|
||||
StartLink(w, r)
|
||||
case "/device/link/poll":
|
||||
PollLink(w, r)
|
||||
case "/device/link/remove":
|
||||
RemoveLink("", w, r)
|
||||
case "/device/handshake":
|
||||
Handshake(w, r)
|
||||
case "/panel/list":
|
||||
PanelList(w, r)
|
||||
case "/panel/get":
|
||||
GetPanel("", w, r)
|
||||
case "/panel/save/json":
|
||||
SavePanelJSON(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func ApiAuth(data string, w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/macro/play":
|
||||
PlayMacro(data, w, r)
|
||||
case "/device/link/remove":
|
||||
RemoveLink(data, w, r)
|
||||
case "/panel/list":
|
||||
MCRMLog("Authenticated Panellist")
|
||||
PanelList(w, r)
|
||||
case "/panel/get":
|
||||
GetPanel(data, w, r)
|
||||
}
|
||||
}
|
||||
329
app/device.go
Normal file
329
app/device.go
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
/*
|
||||
Macrame is a program that enables the user to create keyboard macros and button panels.
|
||||
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
|
||||
be created with HTML and CSS.
|
||||
|
||||
Copyright (C) 2025 Jesse Malotaux
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"macrame/app/helper"
|
||||
"macrame/app/structs"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ListServerIP(w http.ResponseWriter) {
|
||||
ip, err := GetServerIp()
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("GetServerIP err: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(ip)
|
||||
}
|
||||
|
||||
func GetServerIp() (string, error) {
|
||||
ifs, err := net.Interfaces()
|
||||
if err != nil {
|
||||
MCRMLog(err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, ifi := range ifs {
|
||||
// Skip interfaces that are down
|
||||
if ifi.Flags&net.FlagUp == 0 {
|
||||
continue
|
||||
}
|
||||
// Skip loopback interfaces
|
||||
if ifi.Flags&net.FlagLoopback != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
addrs, err := ifi.Addrs()
|
||||
if err != nil {
|
||||
MCRMLog(err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
var ip net.IP
|
||||
|
||||
switch v := addr.(type) {
|
||||
case *net.IPNet:
|
||||
ip = v.IP
|
||||
case *net.IPAddr:
|
||||
ip = v.IP
|
||||
}
|
||||
|
||||
if ip == nil || ip.To4() == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip APIPA (169.254.x.x) addresses
|
||||
if ip.IsLinkLocalUnicast() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Found a good IP, return it
|
||||
return ip.String(), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("No IP found")
|
||||
}
|
||||
|
||||
func DeviceList(w http.ResponseWriter, r *http.Request) {
|
||||
dir := "devices"
|
||||
|
||||
files, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
os.MkdirAll(dir, 0600)
|
||||
files = nil
|
||||
MCRMLog("DeviceList Error: ", err)
|
||||
}
|
||||
|
||||
devices := make(map[string]map[string]interface{})
|
||||
|
||||
for _, file := range files {
|
||||
filePath := dir + "/" + file.Name()
|
||||
ext := filepath.Ext(filePath)
|
||||
device := strings.TrimSuffix(file.Name(), ext)
|
||||
|
||||
if _, ok := devices[device]; !ok {
|
||||
devices[device] = make(map[string]interface{})
|
||||
}
|
||||
|
||||
if ext == ".json" {
|
||||
devices[device]["settings"] = readDeviceSettings(filePath)
|
||||
}
|
||||
if ext == ".key" {
|
||||
devices[device]["key"] = true
|
||||
}
|
||||
}
|
||||
|
||||
result := map[string]interface{}{
|
||||
"devices": devices,
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(result)
|
||||
}
|
||||
|
||||
func readDeviceSettings(filepath string) (settings structs.Settings) {
|
||||
data, err := os.ReadFile(filepath)
|
||||
if err != nil {
|
||||
MCRMLog("readDeviceSettings Error: ", err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, &settings)
|
||||
if err != nil {
|
||||
MCRMLog("readDeviceSettings JSON Error: ", err)
|
||||
}
|
||||
|
||||
return settings
|
||||
}
|
||||
|
||||
func DeviceAccessCheck(w http.ResponseWriter, r *http.Request) {
|
||||
var req structs.Check
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("DeviceAccessCheck Error: ", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
_, errSett := os.Stat("devices/" + req.Uuid + ".json")
|
||||
_, errKey := os.Stat("devices/" + req.Uuid + ".key")
|
||||
|
||||
if (errSett == nil) && (errKey == nil) {
|
||||
json.NewEncoder(w).Encode("authorized")
|
||||
} else if (errSett == nil) && (errKey != nil) {
|
||||
MCRMLog("DeviceAccessCheck: UUID: ", req.Uuid, "; Access: Unauthorized")
|
||||
json.NewEncoder(w).Encode("unauthorized")
|
||||
} else {
|
||||
MCRMLog("DeviceAccessCheck: UUID: ", req.Uuid, "; Access: Unlinked")
|
||||
json.NewEncoder(w).Encode("unlinked")
|
||||
}
|
||||
}
|
||||
|
||||
func DeviceAccessRequest(w http.ResponseWriter, r *http.Request) {
|
||||
var req structs.Request
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
deviceSettings := structs.Settings{Name: req.Name, Type: req.Type}
|
||||
|
||||
settingsJSON, err := json.Marshal(deviceSettings)
|
||||
if err != nil {
|
||||
MCRMLog("DeviceAccessRequest JSON Error: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.WriteFile("devices/"+req.Uuid+".json", settingsJSON, 0644)
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("DeviceAccessRequest Error: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode("unauthorized")
|
||||
}
|
||||
|
||||
func PingLink(w http.ResponseWriter, r *http.Request) {
|
||||
var req structs.Check
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("PingLink Error: ", err)
|
||||
json.NewEncoder(w).Encode(false)
|
||||
return
|
||||
}
|
||||
|
||||
key, keyErr := os.ReadFile("devices/" + req.Uuid + ".key")
|
||||
pin, pinErr := os.ReadFile("devices/" + req.Uuid + ".tmp")
|
||||
|
||||
encryptedKey, encErr := helper.EncryptAES(string(pin), string(key))
|
||||
|
||||
if keyErr == nil && pinErr == nil && encErr == nil {
|
||||
MCRMLog("PINGLINK FIXED")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(encryptedKey))
|
||||
return
|
||||
} else {
|
||||
MCRMLog("PingLink Error: keyErr:", keyErr, "; pinErr:", pinErr, "; encErr:", encErr)
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(false)
|
||||
}
|
||||
|
||||
func StartLink(w http.ResponseWriter, r *http.Request) {
|
||||
var req structs.Check
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("StartLink Error: ", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
pin := fmt.Sprintf("%04d", rand.Intn(10000))
|
||||
|
||||
deviceKey := helper.GenerateKey()
|
||||
|
||||
errKey := helper.SaveDeviceKey(req.Uuid, deviceKey)
|
||||
savedPin, errPin := helper.TempPinFile(req.Uuid, pin)
|
||||
|
||||
if errKey == nil && errPin == nil && savedPin {
|
||||
json.NewEncoder(w).Encode(pin)
|
||||
} else {
|
||||
MCRMLog("StartLink Error: errKey:", errKey, "; errPin:", errPin)
|
||||
}
|
||||
}
|
||||
|
||||
func PollLink(w http.ResponseWriter, r *http.Request) {
|
||||
var req structs.Check
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
if err != nil {
|
||||
json.NewEncoder(w).Encode(false)
|
||||
return
|
||||
}
|
||||
|
||||
if helper.CheckPinFile(req.Uuid) {
|
||||
json.NewEncoder(w).Encode(true)
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(false)
|
||||
}
|
||||
|
||||
func RemoveLink(data string, w http.ResponseWriter, r *http.Request) {
|
||||
req := &structs.Check{}
|
||||
_, err := helper.ParseRequest(req, data, r)
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("RemoveLink ParseRequest Error: ", err)
|
||||
json.NewEncoder(w).Encode(false)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.Remove("devices/" + req.Uuid + ".key")
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("RemoveLink Remove Error: ", err)
|
||||
json.NewEncoder(w).Encode(false)
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(true)
|
||||
}
|
||||
|
||||
func Handshake(w http.ResponseWriter, r *http.Request) {
|
||||
var req structs.Handshake
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
deviceKey, err := helper.GetKeyByUuid(req.Uuid)
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("Handshake GetKeyByUuid Error: ", err)
|
||||
json.NewEncoder(w).Encode(false)
|
||||
return
|
||||
}
|
||||
|
||||
decryptShake, _ := helper.DecryptAES(deviceKey, req.Shake)
|
||||
|
||||
helper.RemovePinFile(req.Uuid)
|
||||
|
||||
if decryptShake == getDateStr() {
|
||||
json.NewEncoder(w).Encode(true)
|
||||
return
|
||||
} else {
|
||||
os.Remove("devices/" + req.Uuid + ".key")
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(false)
|
||||
}
|
||||
|
||||
func getDateStr() string {
|
||||
date := time.Now()
|
||||
year, month, day := date.Date()
|
||||
formattedDate := fmt.Sprintf("%04d%02d%02d", year, month, day)
|
||||
return formattedDate
|
||||
}
|
||||
123
app/helper/api-helper.go
Normal file
123
app/helper/api-helper.go
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
Macrame is a program that enables the user to create keyboard macros and button panels.
|
||||
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
|
||||
be created with HTML and CSS.
|
||||
|
||||
Copyright (C) 2025 Jesse Malotaux
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package helper
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"macrame/app/structs"
|
||||
. "macrame/app/structs"
|
||||
)
|
||||
|
||||
func EndpointAccess(w http.ResponseWriter, r *http.Request) (bool, string, error) {
|
||||
ip, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
|
||||
if err != nil {
|
||||
return false, "", errors.New(r.URL.Path + ": SplitHostPort error: " + err.Error())
|
||||
}
|
||||
|
||||
if (isLocal(ip) && isEndpointAllowed("Local", r.URL.Path)) ||
|
||||
(isLanRemote(ip) && isEndpointAllowed("Remote", r.URL.Path)) {
|
||||
return true, "", nil
|
||||
} else if isLanRemote(ip) && isEndpointAllowed("Auth", r.URL.Path) {
|
||||
data, err := decryptAuth(r)
|
||||
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
|
||||
return data != "", data, nil
|
||||
}
|
||||
|
||||
return false, "", errors.New(r.URL.Path + ": not authorized or accessible")
|
||||
}
|
||||
|
||||
func isLocal(ip string) bool {
|
||||
return ip == "127.0.0.1" || ip == "::1"
|
||||
}
|
||||
|
||||
func isLanRemote(ip string) bool {
|
||||
return strings.HasPrefix(ip, "192.168.")
|
||||
}
|
||||
|
||||
func isEndpointAllowed(source string, endpoint string) bool {
|
||||
var endpoints, err = getAllowedEndpoints(source)
|
||||
if err != "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if (endpoints != nil) && (len(endpoints) > 0) {
|
||||
for _, e := range endpoints {
|
||||
if e == endpoint {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func getAllowedEndpoints(source string) (endpoints []string, err string) {
|
||||
if source == "Local" {
|
||||
return Endpoints.Local, ""
|
||||
}
|
||||
if source == "Remote" {
|
||||
return Endpoints.Remote, ""
|
||||
}
|
||||
if source == "Auth" {
|
||||
return Endpoints.Auth, ""
|
||||
}
|
||||
|
||||
return []string{}, "No allowed endpoints"
|
||||
}
|
||||
|
||||
func decryptAuth(r *http.Request) (string, error) {
|
||||
var req structs.Authcall
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
if err != nil || req.Uuid == "" || req.Data == "" {
|
||||
return "", errors.New("DecryptAuth Error: " + err.Error() + "; UUID: " + req.Uuid + "; Data: " + req.Data)
|
||||
}
|
||||
|
||||
deviceKey, errKey := GetKeyByUuid(req.Uuid)
|
||||
decryptData, errDec := DecryptAES(deviceKey, req.Data)
|
||||
|
||||
if errKey != nil && errDec != nil || decryptData == "" {
|
||||
return "", errors.New("DecryptAuth Error: " + errKey.Error() + "; " + errDec.Error() + "; UUID: " + req.Uuid + "; Data: " + req.Data)
|
||||
}
|
||||
|
||||
return decryptData, nil
|
||||
}
|
||||
|
||||
func ParseRequest(req interface{}, data string, r *http.Request) (d interface{}, err error) {
|
||||
if data != "" {
|
||||
dataBytes := []byte(data)
|
||||
return req, json.Unmarshal(dataBytes, &req)
|
||||
} else {
|
||||
return req, json.NewDecoder(r.Body).Decode(&req)
|
||||
}
|
||||
}
|
||||
44
app/helper/browser-helper.go
Normal file
44
app/helper/browser-helper.go
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
Macrame is a program that enables the user to create keyboard macros and button panels.
|
||||
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
|
||||
be created with HTML and CSS.
|
||||
|
||||
Copyright (C) 2025 Jesse Malotaux
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package helper
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func OpenBrowser(url string) bool {
|
||||
var args []string
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
args = []string{"open"}
|
||||
case "windows":
|
||||
args = []string{"cmd", "/c", "start"}
|
||||
default:
|
||||
args = []string{"xdg-open"}
|
||||
}
|
||||
|
||||
cmd := exec.Command(args[0], append(args[1:], url)...)
|
||||
|
||||
return cmd.Start() == nil
|
||||
}
|
||||
66
app/helper/device-helper.go
Normal file
66
app/helper/device-helper.go
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
Macrame is a program that enables the user to create keyboard macros and button panels.
|
||||
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
|
||||
be created with HTML and CSS.
|
||||
|
||||
Copyright (C) 2025 Jesse Malotaux
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package helper
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TempPinFile(Uuid string, pin string) (bool, error) {
|
||||
err := os.WriteFile("devices/"+Uuid+".tmp", []byte(pin), 0644)
|
||||
if err != nil {
|
||||
return false, errors.New("TempPinFile Error: " + err.Error())
|
||||
}
|
||||
|
||||
time.AfterFunc(1*time.Minute, func() {
|
||||
os.Remove("devices/" + Uuid + ".tmp")
|
||||
})
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func RemovePinFile(Uuid string) error { return os.Remove("devices/" + Uuid + ".tmp") }
|
||||
|
||||
func CheckPinFile(Uuid string) bool {
|
||||
_, err := os.Stat("devices/" + Uuid + ".tmp")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func SaveDeviceKey(Uuid string, key string) error {
|
||||
err := os.WriteFile("devices/"+Uuid+".key", []byte(key), 0644)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetKeyByUuid(Uuid string) (string, error) {
|
||||
data, err := os.ReadFile("devices/" + Uuid + ".key")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
135
app/helper/encrypt-helper.go
Normal file
135
app/helper/encrypt-helper.go
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
Macrame is a program that enables the user to create keyboard macros and button panels.
|
||||
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
|
||||
be created with HTML and CSS.
|
||||
|
||||
Copyright (C) 2025 Jesse Malotaux
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package helper
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
mathRand "math/rand"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func EncryptAES(key string, plaintext string) (string, error) {
|
||||
origData := []byte(plaintext)
|
||||
|
||||
// Create AES cipher
|
||||
block, err := aes.NewCipher(keyToBytes(key))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
blockSize := block.BlockSize()
|
||||
|
||||
origData = PKCS5Padding(origData, blockSize)
|
||||
|
||||
iv := []byte(EnvGet("MCRM__IV"))
|
||||
blockMode := cipher.NewCBCEncrypter(block, iv)
|
||||
|
||||
crypted := make([]byte, len(origData))
|
||||
blockMode.CryptBlocks(crypted, origData)
|
||||
|
||||
cryptedString := base64.StdEncoding.EncodeToString(crypted)
|
||||
|
||||
return cryptedString, nil
|
||||
}
|
||||
|
||||
func DecryptAES(key string, cryptedText string) (string, error) {
|
||||
crypted, err := base64.StdEncoding.DecodeString(cryptedText)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(keyToBytes(key))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
iv := []byte(EnvGet("MCRM__IV"))
|
||||
blockMode := cipher.NewCBCDecrypter(block, iv)
|
||||
|
||||
origData := make([]byte, len(crypted))
|
||||
|
||||
blockMode.CryptBlocks(origData, crypted)
|
||||
origData, err = PKCS5UnPadding(origData)
|
||||
|
||||
if err != nil || len(origData) <= 3 {
|
||||
return "", errors.New("invalid key")
|
||||
}
|
||||
|
||||
origDataString := string(origData)
|
||||
|
||||
return origDataString, nil
|
||||
}
|
||||
|
||||
func GenerateRandomString(length int) string {
|
||||
b := make([]byte, length)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(b)
|
||||
}
|
||||
|
||||
func GenerateRandomIntegerString(length int) string {
|
||||
var sb strings.Builder
|
||||
for i := 0; i < length; i++ {
|
||||
sb.WriteByte('0' + byte(mathRand.Intn(10)))
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func GenerateKey() string {
|
||||
return strings.Replace(GenerateRandomString(24), "=", "", -1)
|
||||
}
|
||||
|
||||
func keyToBytes(key string) []byte {
|
||||
// Convert key to bytes
|
||||
keyBytes := []byte(key)
|
||||
|
||||
// If key is 4 characters, append salt
|
||||
if len(key) == 4 {
|
||||
keyBytes = []byte(key + EnvGet("MCRM__SALT"))
|
||||
}
|
||||
|
||||
return keyBytes
|
||||
}
|
||||
|
||||
func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(ciphertext)%blockSize
|
||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(ciphertext, padtext...)
|
||||
}
|
||||
|
||||
func PKCS5UnPadding(origData []byte) ([]byte, error) {
|
||||
length := len(origData)
|
||||
unpadding := int(origData[length-1])
|
||||
|
||||
if (unpadding >= length) || (unpadding == 0) {
|
||||
return nil, errors.New("unpadding error")
|
||||
}
|
||||
|
||||
return origData[:(length - unpadding)], nil
|
||||
}
|
||||
128
app/helper/env-helper.go
Normal file
128
app/helper/env-helper.go
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
Macrame is a program that enables the user to create keyboard macros and button panels.
|
||||
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
|
||||
be created with HTML and CSS.
|
||||
|
||||
Copyright (C) 2025 Jesse Malotaux
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package helper
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var configPath = "public/config.js"
|
||||
|
||||
func EnvGet(key string) string {
|
||||
|
||||
if !configFileExists() {
|
||||
CreateConfigFile(configPath)
|
||||
CheckFeDevDir()
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
log.Println("Error reading config.js:", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
raw := strings.TrimSpace(string(data))
|
||||
raw = strings.TrimPrefix(raw, "window.__CONFIG__ = ")
|
||||
raw = strings.TrimSuffix(raw, ";")
|
||||
|
||||
var config map[string]string
|
||||
if err := json.Unmarshal([]byte(raw), &config); err != nil {
|
||||
log.Println("Error parsing config.js:", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
return config[key]
|
||||
}
|
||||
|
||||
func configFileExists() bool {
|
||||
_, err := os.Stat(configPath)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func CheckFeDevDir() {
|
||||
log.Println("Checking FE dev directory...")
|
||||
_, err := os.Stat("fe")
|
||||
|
||||
if err != nil {
|
||||
log.Println("Error checking FE dev directory:", err)
|
||||
return
|
||||
}
|
||||
|
||||
copyConfigToFe()
|
||||
}
|
||||
|
||||
func copyConfigToFe() {
|
||||
data, err := os.ReadFile(configPath)
|
||||
|
||||
if err != nil {
|
||||
log.Println("Error reading config.js:", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := os.WriteFile("fe/config.js", data, 0644); err != nil {
|
||||
log.Println("Error writing config.js:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func CreateConfigFile(filename string) {
|
||||
port, _ := findOpenPort()
|
||||
saltKey := GenerateKey()
|
||||
salt := saltKey[:28]
|
||||
iv := GenerateRandomIntegerString(16)
|
||||
|
||||
config := map[string]string{
|
||||
"MCRM__PORT": port,
|
||||
"MCRM__SALT": salt,
|
||||
"MCRM__IV": iv,
|
||||
}
|
||||
|
||||
jsonData, err := json.MarshalIndent(config, "", " ")
|
||||
if err != nil {
|
||||
log.Println("Error creating config JSON:", err)
|
||||
return
|
||||
}
|
||||
jsData := "window.__CONFIG__ = " + string(jsonData) + ";"
|
||||
|
||||
log.Println("Created JS config:", jsData)
|
||||
|
||||
if err := os.WriteFile(filename, []byte(jsData), 0644); err != nil {
|
||||
log.Println("Error writing config.js:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func findOpenPort() (string, error) {
|
||||
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
l, err := net.ListenTCP("tcp", addr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer l.Close()
|
||||
return strconv.Itoa(l.Addr().(*net.TCPAddr).Port), nil
|
||||
}
|
||||
114
app/helper/macro-helper.go
Normal file
114
app/helper/macro-helper.go
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
Macrame is a program that enables the user to create keyboard macros and button panels.
|
||||
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
|
||||
be created with HTML and CSS.
|
||||
|
||||
Copyright (C) 2025 Jesse Malotaux
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package helper
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-vgo/robotgo"
|
||||
)
|
||||
|
||||
func FormatMacroFileName(s string) string {
|
||||
// Remove invalid characters
|
||||
re := regexp.MustCompile(`[\/\?\*\>\<\:\"\|
|
||||
]`)
|
||||
s = re.ReplaceAllString(s, "")
|
||||
|
||||
// Replace spaces with underscores
|
||||
s = strings.ReplaceAll(s, " ", "_")
|
||||
|
||||
// Remove special characters
|
||||
re = regexp.MustCompile(`[!@#$%^&\(\)\[\]\{\}\~]`)
|
||||
s = re.ReplaceAllString(s, "")
|
||||
|
||||
// Truncate the string
|
||||
if len(s) > 255 {
|
||||
s = s[:255]
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func ReadMacroFile(filename string) (steps []map[string]interface{}, err error) {
|
||||
content, err := os.ReadFile(filename)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(content, &steps)
|
||||
|
||||
return steps, err
|
||||
}
|
||||
|
||||
func RunMacroSteps(steps []map[string]interface{}) error {
|
||||
for _, step := range steps {
|
||||
switch step["type"] {
|
||||
case "key":
|
||||
keyCode := step["code"].(string)
|
||||
|
||||
if strings.Contains(keyCode, "|") {
|
||||
keyCode = handleToggleCode(keyCode, step["direction"].(string))
|
||||
}
|
||||
|
||||
robotgo.KeyToggle(keyCode, step["direction"].(string))
|
||||
case "delay":
|
||||
|
||||
time.Sleep(time.Duration(step["value"].(float64)) * time.Millisecond)
|
||||
|
||||
default:
|
||||
return errors.New("RunMacroSteps Unknown step type: %v" + fmt.Sprint(step["type"]))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var toggleCodes = map[string]bool{}
|
||||
|
||||
func handleToggleCode(keyCode string, direction string) string {
|
||||
splitCodes := strings.Split(keyCode, "|")
|
||||
|
||||
if direction == "down" {
|
||||
if _, ok := toggleCodes[splitCodes[0]]; !ok {
|
||||
toggleCodes[splitCodes[0]] = true
|
||||
return splitCodes[0]
|
||||
}
|
||||
return splitCodes[1]
|
||||
}
|
||||
|
||||
if direction == "up" {
|
||||
if toggleCodes[splitCodes[0]] {
|
||||
toggleCodes[splitCodes[0]] = false
|
||||
return splitCodes[0]
|
||||
}
|
||||
delete(toggleCodes, splitCodes[0])
|
||||
return splitCodes[1]
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
101
app/helper/translation-helper.go
Normal file
101
app/helper/translation-helper.go
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
Macrame is a program that enables the user to create keyboard macros and button panels.
|
||||
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
|
||||
be created with HTML and CSS.
|
||||
|
||||
Copyright (C) 2025 Jesse Malotaux
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package helper
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var translations = map[string]string{
|
||||
"ArrowUp": "up",
|
||||
"ArrowDown": "down",
|
||||
"ArrowRight": "right",
|
||||
"ArrowLeft": "left",
|
||||
"Meta": "cmd",
|
||||
"MetaLeft": "lcmd",
|
||||
"MetaRight": "rcmd",
|
||||
"Alt": "alt",
|
||||
"AltLeft": "lalt",
|
||||
"AltRight": "ralt",
|
||||
"Control": "ctrl",
|
||||
"ControlLeft": "lctrl",
|
||||
"ControlRight": "rctrl",
|
||||
"Shift": "shift",
|
||||
"ShiftLeft": "lshift",
|
||||
"ShiftRight": "rshift",
|
||||
"AudioVolumeMute": "audio_mute",
|
||||
"AudioVolumeDown": "audio_vol_down",
|
||||
"AudioVolumeUp": "audio_vol_up",
|
||||
"MediaTrackPrevious": "audio_prev",
|
||||
"MediaTrackNext": "audio_next",
|
||||
"MediaPlayPause": "audio_play|audio_pause",
|
||||
"Numpad0": "num0",
|
||||
"Numpad1": "num1",
|
||||
"Numpad2": "num2",
|
||||
"Numpad3": "num3",
|
||||
"Numpad4": "num4",
|
||||
"Numpad5": "num5",
|
||||
"Numpad6": "num6",
|
||||
"Numpad7": "num7",
|
||||
"Numpad8": "num8",
|
||||
"Numpad9": "num9",
|
||||
"NumLock": "num_lock",
|
||||
"NumpadDecimal": "num.",
|
||||
"NumpadAdd": "num+",
|
||||
"NumpadSubtract": "num-",
|
||||
"NumpadMultiply": "num*",
|
||||
"NumpadDivide": "num/",
|
||||
"NumpadEnter": "num_enter",
|
||||
"Clear": "num_clear",
|
||||
"BracketLeft": "[",
|
||||
"BracketRight": "]",
|
||||
"Quote": "'",
|
||||
"Semicolon": ";",
|
||||
"Backquote": "`",
|
||||
"Backslash": "\\",
|
||||
"IntlBackslash": "\\",
|
||||
"Slash": "/",
|
||||
"Comma": ",",
|
||||
"Period": ".",
|
||||
"Equal": "=",
|
||||
"Minus": "-",
|
||||
}
|
||||
|
||||
func Translate(code string) string {
|
||||
if val, ok := translations[code]; ok {
|
||||
return val
|
||||
}
|
||||
return strings.ToLower(code)
|
||||
}
|
||||
|
||||
func ReverseTranslate(name string) string {
|
||||
if name == "\\" {
|
||||
return "Backslash"
|
||||
}
|
||||
|
||||
for key, value := range translations {
|
||||
if value == name {
|
||||
return key
|
||||
}
|
||||
}
|
||||
return name
|
||||
}
|
||||
45
app/log.go
Normal file
45
app/log.go
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
Macrame is a program that enables the user to create keyboard macros and button panels.
|
||||
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
|
||||
be created with HTML and CSS.
|
||||
|
||||
Copyright (C) 2025 Jesse Malotaux
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var logFile *os.File
|
||||
|
||||
func MCRMLogInit() {
|
||||
var err error
|
||||
// Open or create the log file with append permissions
|
||||
logFile, err = os.OpenFile("log.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
log.Fatal(err) // Exit if we can't open the log file
|
||||
}
|
||||
|
||||
// Optionally set log to write to file in addition to standard log output
|
||||
log.SetOutput(logFile)
|
||||
}
|
||||
|
||||
func MCRMLog(v ...interface{}) {
|
||||
log.Println(v...) // Logs to terminal as well
|
||||
}
|
||||
229
app/macro.go
Normal file
229
app/macro.go
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
/*
|
||||
Macrame is a program that enables the user to create keyboard macros and button panels.
|
||||
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
|
||||
be created with HTML and CSS.
|
||||
|
||||
Copyright (C) 2025 Jesse Malotaux
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"macrame/app/helper"
|
||||
"macrame/app/structs"
|
||||
)
|
||||
|
||||
func CheckMacro(w http.ResponseWriter, r *http.Request) {
|
||||
var req structs.MacroRequest
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("OpenMacro Decode Error: ", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var filename = helper.FormatMacroFileName(req.Macro)
|
||||
|
||||
macroFile, err := helper.ReadMacroFile(fmt.Sprintf("macros/%s.json", filename))
|
||||
|
||||
if macroFile != nil && err == nil {
|
||||
json.NewEncoder(w).Encode(true)
|
||||
return
|
||||
} else {
|
||||
MCRMLog("OpenMacro ReadMacroFile Error: ", err)
|
||||
json.NewEncoder(w).Encode(false)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func SaveMacro(w http.ResponseWriter, r *http.Request) {
|
||||
var newMacro structs.NewMacro
|
||||
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
MCRMLog("SaveMacro ReadAll Error: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &newMacro)
|
||||
if err != nil {
|
||||
MCRMLog("SaveMacro Unmarshal Error: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
simplifiedSteps := make([]map[string]interface{}, 0)
|
||||
for _, step := range newMacro.Steps {
|
||||
simplifiedSteps = append(simplifiedSteps, simplifyMacro(step))
|
||||
}
|
||||
|
||||
stepsJSON, err := json.Marshal(simplifiedSteps)
|
||||
if err != nil {
|
||||
MCRMLog("SaveMacro Marshal Error: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.WriteFile("macros/"+helper.FormatMacroFileName(newMacro.Name)+".json", stepsJSON, 0644)
|
||||
if err != nil {
|
||||
MCRMLog("SaveMacro WriteFile Error: ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func simplifyMacro(step structs.Step) map[string]interface{} {
|
||||
simplified := make(map[string]interface{})
|
||||
|
||||
simplified["type"] = step.Type
|
||||
|
||||
switch step.Type {
|
||||
case "delay":
|
||||
simplified["value"] = step.Value
|
||||
case "key":
|
||||
keyCode := step.Code
|
||||
|
||||
if keyCode == "" || (strings.Contains(keyCode, "Digit")) {
|
||||
keyCode = step.Key
|
||||
} else if strings.Contains(keyCode, "Key") {
|
||||
keyCode = strings.Replace(keyCode, "Key", "", 1)
|
||||
}
|
||||
|
||||
simplified["code"] = helper.Translate(keyCode)
|
||||
simplified["direction"] = step.Direction
|
||||
}
|
||||
|
||||
return simplified
|
||||
}
|
||||
|
||||
func ListMacros(w http.ResponseWriter, r *http.Request) {
|
||||
dir := "macros"
|
||||
files, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
MCRMLog("ListMacros ReadDir Error: ", err)
|
||||
json.NewEncoder(w).Encode(false)
|
||||
return
|
||||
}
|
||||
|
||||
var macroList []structs.MacroInfo
|
||||
|
||||
for _, file := range files {
|
||||
filename := filepath.Base(file.Name())
|
||||
macroname := strings.TrimSuffix(filename, filepath.Ext(filename))
|
||||
nicename := strings.Replace(macroname, "_", " ", -1)
|
||||
|
||||
macroList = append(macroList, structs.MacroInfo{
|
||||
Name: nicename,
|
||||
Macroname: macroname,
|
||||
})
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(macroList)
|
||||
}
|
||||
|
||||
func DeleteMacro(w http.ResponseWriter, r *http.Request) {
|
||||
var req structs.MacroRequest
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("DeleteMacro Decode Error: ", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var filename = helper.FormatMacroFileName(req.Macro)
|
||||
|
||||
err = os.Remove("macros/" + filename + ".json")
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("DeleteMacro Remove Error: ", err)
|
||||
json.NewEncoder(w).Encode(false)
|
||||
return
|
||||
}
|
||||
log.Println("Deleted Macro:", req.Macro)
|
||||
json.NewEncoder(w).Encode(true)
|
||||
}
|
||||
|
||||
func PlayMacro(data string, w http.ResponseWriter, r *http.Request) {
|
||||
req := &structs.MacroRequest{}
|
||||
_, err := helper.ParseRequest(req, data, r)
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("PlayMacro ParseRequest Error: ", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
macro := req.Macro
|
||||
|
||||
MCRMLog("Playing Macro: ", macro)
|
||||
|
||||
var filename = helper.FormatMacroFileName(macro)
|
||||
var filepath = fmt.Sprintf("macros/%s.json", filename)
|
||||
|
||||
macroFile, err := helper.ReadMacroFile(filepath)
|
||||
if err != nil {
|
||||
MCRMLog("PlayMacro ReadMacroFile Error: ", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = helper.RunMacroSteps(macroFile)
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("PlayMacro RunMacroSteps Error: ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func OpenMacro(w http.ResponseWriter, r *http.Request) {
|
||||
var req structs.MacroRequest
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("OpenMacro Decode Error: ", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
macroFile, err := helper.ReadMacroFile(fmt.Sprintf("macros/%s.json", req.Macro))
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("OpenMacro ReadMacroFile Error: ", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Walk through the macro file and reverse translate codes
|
||||
for i, action := range macroFile {
|
||||
if actionType, ok := action["type"].(string); ok && actionType == "key" {
|
||||
if code, ok := action["code"].(string); ok {
|
||||
macroFile[i]["code"] = helper.ReverseTranslate(code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(macroFile)
|
||||
}
|
||||
176
app/panel.go
Normal file
176
app/panel.go
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
Macrame is a program that enables the user to create keyboard macros and button panels.
|
||||
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
|
||||
be created with HTML and CSS.
|
||||
|
||||
Copyright (C) 2025 Jesse Malotaux
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"macrame/app/helper"
|
||||
"macrame/app/structs"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func PanelList(w http.ResponseWriter, r *http.Request) {
|
||||
panelDirs, err := os.ReadDir("panels")
|
||||
if err != nil {
|
||||
MCRMLog("PanelList ReadDir Error: ", err)
|
||||
json.NewEncoder(w).Encode(false)
|
||||
return
|
||||
}
|
||||
|
||||
var panelList []interface{}
|
||||
|
||||
for _, panelDir := range panelDirs {
|
||||
if panelDir.IsDir() {
|
||||
panelList = append(panelList, getPanelInfo(panelDir.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
if len(panelList) == 0 {
|
||||
MCRMLog("PanelList: No panels found")
|
||||
json.NewEncoder(w).Encode(false)
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(panelList)
|
||||
}
|
||||
|
||||
func getPanelInfo(dirname string) structs.PanelInfo {
|
||||
var panelInfo structs.PanelInfo
|
||||
|
||||
jsonFile, err := os.ReadFile("panels/" + dirname + "/panel.json")
|
||||
|
||||
if err != nil {
|
||||
panelInfo.Name = strings.Replace(dirname, "_", " ", -1)
|
||||
panelInfo.Description = "null"
|
||||
panelInfo.AspectRatio = "null"
|
||||
panelInfo.Macros = make(map[string]string)
|
||||
} else {
|
||||
err = json.Unmarshal(jsonFile, &panelInfo)
|
||||
if err != nil {
|
||||
MCRMLog("getPanelInfo Unmarshal Error: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
panelInfo.Dir = dirname
|
||||
|
||||
thumb := getPanelThumb(dirname)
|
||||
panelInfo.Thumb = thumb
|
||||
|
||||
return panelInfo
|
||||
}
|
||||
|
||||
func getPanelThumb(dirname string) string {
|
||||
extensions := []string{".jpg", ".jpeg", ".png", ".webp"}
|
||||
|
||||
for _, ext := range extensions {
|
||||
filename := "thumbnail" + ext
|
||||
file, err := os.Open("panels/" + dirname + "/" + filename)
|
||||
if err != nil {
|
||||
MCRMLog("getPanelThumb Open Error: ", err)
|
||||
continue
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
return encodeImg(file)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func getPanelCode(dirname string) (html string, css string) {
|
||||
htmlBytes, _ := os.ReadFile("panels/" + dirname + "/index.html")
|
||||
cssBytes, _ := os.ReadFile("panels/" + dirname + "/output.css")
|
||||
|
||||
return string(htmlBytes), string(cssBytes)
|
||||
}
|
||||
|
||||
func encodeImg(file *os.File) string {
|
||||
contents, err := os.ReadFile(file.Name())
|
||||
if err != nil {
|
||||
MCRMLog("encodeImg ReadFile Error: ", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
encoded := base64.StdEncoding.EncodeToString(contents)
|
||||
return encoded
|
||||
}
|
||||
|
||||
func GetPanel(data string, w http.ResponseWriter, r *http.Request) {
|
||||
req := &structs.PanelRequest{}
|
||||
|
||||
_, err := helper.ParseRequest(req, data, r)
|
||||
if err != nil {
|
||||
MCRMLog("GetPanel ParseRequest Error: ", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var response = structs.PanelResponse{}
|
||||
|
||||
panelInfo := getPanelInfo(req.Dir)
|
||||
panelHtml, panelCss := getPanelCode(req.Dir)
|
||||
|
||||
response.Dir = panelInfo.Dir
|
||||
response.Name = panelInfo.Name
|
||||
response.Description = panelInfo.Description
|
||||
response.AspectRatio = panelInfo.AspectRatio
|
||||
response.Macros = panelInfo.Macros
|
||||
response.Thumb = panelInfo.Thumb
|
||||
response.HTML = panelHtml
|
||||
response.Style = panelCss
|
||||
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
func SavePanelJSON(w http.ResponseWriter, r *http.Request) {
|
||||
var req structs.PanelSaveJSON
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
MCRMLog("SavePanelJSON Decode Error: ", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
filePath := "panels/" + req.Dir + "/panel.json"
|
||||
|
||||
req.Dir = ""
|
||||
|
||||
// Marshal the data to JSON without the dir field
|
||||
jsonData, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
MCRMLog("SavePanelJSON Marshal Error: ", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.WriteFile(filePath, jsonData, 0644)
|
||||
if err != nil {
|
||||
MCRMLog("SavePanelJSON WriteFile Error: ", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
66
app/structs/api-struct.go
Normal file
66
app/structs/api-struct.go
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
Macrame is a program that enables the user to create keyboard macros and button panels.
|
||||
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
|
||||
be created with HTML and CSS.
|
||||
|
||||
Copyright (C) 2025 Jesse Malotaux
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package structs
|
||||
|
||||
type Allowed struct {
|
||||
Local []string
|
||||
Remote []string
|
||||
Auth []string
|
||||
}
|
||||
|
||||
var Endpoints = Allowed{
|
||||
Local: []string{
|
||||
"/macro/check",
|
||||
"/macro/record",
|
||||
"/macro/list",
|
||||
"/macro/open",
|
||||
"/macro/delete",
|
||||
"/macro/play",
|
||||
"/device/server/ip",
|
||||
"/device/list",
|
||||
"/device/access/check",
|
||||
"/device/access/request",
|
||||
"/device/link/ping",
|
||||
"/device/link/start",
|
||||
"/device/link/poll",
|
||||
"/device/link/remove",
|
||||
"/device/handshake",
|
||||
"/panel/get",
|
||||
"/panel/list",
|
||||
"/panel/save/json",
|
||||
},
|
||||
Remote: []string{
|
||||
"/device/access/check",
|
||||
"/device/access/request",
|
||||
"/device/server/ip",
|
||||
"/device/link/ping",
|
||||
"/device/link/end",
|
||||
"/device/handshake",
|
||||
"/device/auth",
|
||||
},
|
||||
Auth: []string{
|
||||
"/macro/play",
|
||||
"/device/link/remove",
|
||||
"/panel/get",
|
||||
"/panel/list",
|
||||
},
|
||||
}
|
||||
52
app/structs/device-struct.go
Normal file
52
app/structs/device-struct.go
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
Macrame is a program that enables the user to create keyboard macros and button panels.
|
||||
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
|
||||
be created with HTML and CSS.
|
||||
|
||||
Copyright (C) 2025 Jesse Malotaux
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package structs
|
||||
|
||||
type Settings struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type RemoteWebhook struct {
|
||||
Event string `json:"event"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
type Check struct {
|
||||
Uuid string `json:"uuid"`
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
Uuid string `json:"uuid"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type Handshake struct {
|
||||
Uuid string `json:"uuid"`
|
||||
Shake string `json:"shake"`
|
||||
}
|
||||
|
||||
type Authcall struct {
|
||||
Uuid string `json:"uuid"`
|
||||
Data string `json:"d"`
|
||||
}
|
||||
58
app/structs/macro-struct.go
Normal file
58
app/structs/macro-struct.go
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
Macrame is a program that enables the user to create keyboard macros and button panels.
|
||||
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
|
||||
be created with HTML and CSS.
|
||||
|
||||
Copyright (C) 2025 Jesse Malotaux
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package structs
|
||||
|
||||
type MacroRequest struct {
|
||||
Macro string `json:"macro"`
|
||||
}
|
||||
|
||||
type Step struct {
|
||||
Type string `json:"type"`
|
||||
Key string `json:"key"`
|
||||
Code string `json:"code"`
|
||||
Location int `json:"location"`
|
||||
Direction string `json:"direction"`
|
||||
Value int `json:"value"`
|
||||
}
|
||||
|
||||
type NewMacro struct {
|
||||
Name string `json:"name"`
|
||||
Steps []Step `json:"steps"`
|
||||
}
|
||||
|
||||
type MacroInfo struct {
|
||||
Name string `json:"name"`
|
||||
Macroname string `json:"macroname"`
|
||||
}
|
||||
|
||||
type MacroKey struct {
|
||||
Type string `json:"type"`
|
||||
Key string `json:"key"`
|
||||
Code string `json:"code"`
|
||||
Location int `json:"location"`
|
||||
Direction string `json:"direction"`
|
||||
}
|
||||
|
||||
type MacroDelay struct {
|
||||
Type string `json:"type"`
|
||||
Value int `json:"value"`
|
||||
}
|
||||
65
app/structs/panel-struct.go
Normal file
65
app/structs/panel-struct.go
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
Macrame is a program that enables the user to create keyboard macros and button panels.
|
||||
The macros are saved as simple JSON files and can be linked to the button panels. The panels can
|
||||
be created with HTML and CSS.
|
||||
|
||||
Copyright (C) 2025 Jesse Malotaux
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package structs
|
||||
|
||||
type PanelJSON struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
AspectRatio string `json:"aspectRatio"`
|
||||
Macros map[string]string `json:"macros"`
|
||||
}
|
||||
|
||||
type PanelSaveJSON struct {
|
||||
// Name string `json:"name"`
|
||||
// Description string `json:"description"`
|
||||
// AspectRatio string `json:"aspectRatio"`
|
||||
// Macros map[string]string `json:"macros"`
|
||||
Dir string `json:"dir"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
AspectRatio string `json:"aspectRatio"`
|
||||
Macros interface{} `json:"macros"`
|
||||
}
|
||||
|
||||
type PanelInfo struct {
|
||||
Dir string `json:"dir"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
AspectRatio string `json:"aspectRatio"`
|
||||
Macros map[string]string `json:"macros"`
|
||||
Thumb string `json:"thumb"`
|
||||
}
|
||||
|
||||
type PanelRequest struct {
|
||||
Dir string `json:"dir"`
|
||||
}
|
||||
|
||||
type PanelResponse struct {
|
||||
Dir string `json:"dir"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
AspectRatio string `json:"aspectRatio"`
|
||||
Macros map[string]string `json:"macros"`
|
||||
Thumb string `json:"thumb"`
|
||||
HTML string `json:"html"`
|
||||
Style string `json:"style"`
|
||||
}
|
||||
64
app/systray.go
Normal file
64
app/systray.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"macrame/app/helper"
|
||||
"os"
|
||||
|
||||
"github.com/getlantern/systray"
|
||||
)
|
||||
|
||||
func InitSystray() {
|
||||
go func() {
|
||||
systray.Run(OnReady, OnExit)
|
||||
}()
|
||||
}
|
||||
|
||||
func OnReady() {
|
||||
systray.SetIcon(getIcon("favicon.ico"))
|
||||
systray.SetTitle("Macrame")
|
||||
systray.SetTooltip("Macrame - Server")
|
||||
|
||||
ip, err := GetServerIp()
|
||||
|
||||
if err == nil {
|
||||
systray.AddMenuItem("IP: "+ip+":"+helper.EnvGet("MCRM__PORT"), "Server IP")
|
||||
}
|
||||
|
||||
systray.AddSeparator()
|
||||
|
||||
addMCRMItem("Dashboard", "/")
|
||||
addMCRMItem("Panels", "/panels")
|
||||
addMCRMItem("Macros", "/macros")
|
||||
addMCRMItem("Devices", "/devices")
|
||||
|
||||
systray.AddSeparator()
|
||||
|
||||
mQuit := systray.AddMenuItem("Quit Macrame", "Quit Macrame")
|
||||
go func() {
|
||||
<-mQuit.ClickedCh
|
||||
os.Exit(0)
|
||||
}()
|
||||
}
|
||||
|
||||
func addMCRMItem(name, urlPath string) {
|
||||
m := systray.AddMenuItem(name, name)
|
||||
|
||||
go func() {
|
||||
<-m.ClickedCh
|
||||
helper.OpenBrowser("http://localhost:" + helper.EnvGet("MCRM__PORT") + urlPath)
|
||||
}()
|
||||
}
|
||||
|
||||
func OnExit() {
|
||||
systray.Quit()
|
||||
}
|
||||
|
||||
func getIcon(path string) []byte {
|
||||
icon, err := os.ReadFile(path)
|
||||
|
||||
if err != nil {
|
||||
MCRMLog("getIcon Error: ", err)
|
||||
}
|
||||
|
||||
return icon
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue