28 Commits

Author SHA1 Message Date
01bf493c77 Fix capital letters for Armenian transliteration 2021-10-05 09:09:19 -07:00
b6e9ad6160 Add Chinese transliteration via Pinyin conversion library 2021-10-04 22:26:16 -07:00
c56c0ae198 Update variable names and comments for interface-based transliteration 2021-10-04 20:23:54 -07:00
6b94030b83 Fix Korean transliteration 2021-10-04 20:06:08 -07:00
2bbd722ecd Add korean transliteration 2021-10-04 19:07:54 -07:00
73f16fcfef Use interface to allow for more complex transliteration implementations 2021-10-04 17:45:26 -07:00
9df6531023 Fix German transliteration for Ü and add attribution 2021-10-04 13:17:48 -07:00
1db2ca3395 Add transliteration 2021-10-04 01:05:01 -07:00
419b2f5a79 Break transfer loops after refreshing progress bar 2021-08-27 09:01:46 -07:00
44607ba9e2 Add fatal error dialog 2021-08-27 08:47:24 -07:00
f4d2f4e6eb Mention GUI in README 2021-08-26 09:01:03 -07:00
0721b7f9d4 Add comments to gui 2021-08-26 08:47:17 -07:00
b7bd385c43 Add GUI frontend 2021-08-25 21:18:24 -07:00
cbcefb149e Fix indentation in config 2021-08-24 20:35:25 -07:00
cb8d207249 Switch to iota for request types and move to types package 2021-08-24 20:32:17 -07:00
7786ea1d58 Fix debug config paths 2021-08-24 08:55:22 -07:00
6e16aa7a7a Add config defaults and run go fmt 2021-08-24 08:54:08 -07:00
91f7132d5e Create new config format 2021-08-24 08:33:41 -07:00
b186f77bea Remove replace directive 2021-08-22 15:07:45 -07:00
adb297c6dd Fix find and replace error 2021-08-22 13:53:32 -07:00
b4992cb393 Update infinitime library to fix connection bug 2021-08-22 13:13:37 -07:00
a5490b8364 Use new pair timeout option 2021-08-21 20:35:21 -07:00
44d1f5552b Mention interactive mode in readme 2021-08-21 19:07:59 -07:00
5e34f656b3 Disable completion command 2021-08-21 19:03:18 -07:00
560d19860e Fix binary download link 2021-08-21 18:52:11 -07:00
ea1a7fa9f4 Add badges 2021-08-21 18:48:43 -07:00
81fe634ed8 Add uninstall rule to makefile 2021-08-21 17:17:25 -07:00
281e1dcbac Add CI status to readme 2021-08-21 16:36:10 -07:00
31 changed files with 1915 additions and 132 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
/itctl /itctl
/itd /itd
/itgui

View File

@@ -7,14 +7,20 @@ all:
go build $(GOFLAGS) go build $(GOFLAGS)
go build ./cmd/itctl $(GOFLAGS) go build ./cmd/itctl $(GOFLAGS)
clean:
rm -f itctl
rm -f itd
install: install:
install -Dm755 ./itd $(BIN_PREFIX)/itd install -Dm755 ./itd $(BIN_PREFIX)/itd
install -Dm755 ./itctl $(BIN_PREFIX)/itctl install -Dm755 ./itctl $(BIN_PREFIX)/itctl
install -Dm644 ./itd.service $(SERVICE_PREFIX)/itd.service install -Dm644 ./itd.service $(SERVICE_PREFIX)/itd.service
install -Dm644 ./itd.toml $(CFG_PREFIX)/itd.toml install -Dm644 ./itd.toml $(CFG_PREFIX)/itd.toml
clean: uninstall:
rm -f itctl rm $(BIN_PREFIX)/itd
rm -f itd rm $(BIN_PREFIX)/itctl
rm $(SERVICE_PREFIX)/itd.service
rm $(CFG_PREFIX)/itd.toml
.PHONY: all install clean .PHONY: all clean install uninstall

View File

@@ -3,11 +3,16 @@
`itd` is a daemon that uses my infinitime [library](https://go.arsenm.dev/infinitime) to interact with the [PineTime](https://www.pine64.org/pinetime/) running [InfiniTime](https://infinitime.io). `itd` is a daemon that uses my infinitime [library](https://go.arsenm.dev/infinitime) to interact with the [PineTime](https://www.pine64.org/pinetime/) running [InfiniTime](https://infinitime.io).
[![Build status](https://ci.appveyor.com/api/projects/status/xgj5sobw76ndqaod?svg=true)](https://ci.appveyor.com/project/moussaelianarsen/itd)
[![Binary downloads](https://img.shields.io/badge/download-binary-orange)](https://minio.arsenm.dev/minio/itd/)
[![AUR package](https://img.shields.io/aur/version/itd-git?label=itd-git&logo=archlinux)](https://aur.archlinux.org/packages/itd-git/)
--- ---
### Features ### Features
- Notification relay - Notification relay
- Notificstion transliteration
- Music control - Music control
- Get info from watch (HRM, Battery level, Firmware version) - Get info from watch (HRM, Battery level, Firmware version)
- Set current time - Set current time
@@ -32,6 +37,42 @@ The various request types and their data requirements can be seen in `internal/t
--- ---
### Transliteration
Since the PineTime does not have enough space to store all unicode glyphs, it only stores the ASCII space and Cyrillic. Therefore, this daemon can transliterate unsupported characters into supported ones. Since some languages have different transliterations, the transliterators to be used must be specified in the config. Here are the available transliterators:
- eASCII
- Scandinavian
- German
- Hebrew
- Greek
- Russian
- Ukranian
- Arabic
- Farsi
- Polish
- Lithuanian
- Estonian
- Icelandic
- Czeck
- French
- Armenian
- Korean
- Chinese
- Emoji
Place the desired map names in an array as `notifs.translit.use`. They will be evaluated in order. You can also put custom transliterations in `notifs.translit.custom`. These take priority over any other maps. The `notifs.translit` config section should look like this:
```toml
[notifs.translit]
use = ["eASCII", "Russian", "Emoji"]
custom = [
"test", "replaced"
]
```
---
### `itctl` ### `itctl`
This daemon comes with a binary called `itctl` which uses the socket to control the daemon from the command line. As such, it can be scripted using bash. This daemon comes with a binary called `itctl` which uses the socket to control the daemon from the command line. As such, it can be scripted using bash.
@@ -56,6 +97,48 @@ Flags:
Use "itctl [command] --help" for more information about a command. Use "itctl [command] --help" for more information about a command.
``` ```
---
### `itgui`
In `cmd/itgui`, there is a gui frontend to the socket of `itd`. It uses the [fyne library](https://fyne.io/) for Go. It can be compiled by running:
```shell
go build ./cmd/itgui
```
#### Screenshots
![Info tab](https://i.imgur.com/okxG9EI.png)
![Notify tab](https://i.imgur.com/DrVhOAq.png)
![Set time tab](https://i.imgur.com/j9civeY.png)
![Upgrade tab](https://i.imgur.com/1KY6fG4.png)
![Upgrade in progress](https://i.imgur.com/w5qbWAw.png)
---
#### Interactive mode
Running `itctl` by itself will open interactive mode. It's essentially a shell where you can enter commands. For example:
```
$ itctl
itctl> fw ver
1.3.0
itctl> get batt
81%
itctl> get heart
92 BPM
itctl> set time 2021-08-22T00:06:18-07:00
itctl> set time now
itctl> exit
```
--- ---
### Installation ### Installation

View File

@@ -26,6 +26,7 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
"go.arsenm.dev/itd/internal/types" "go.arsenm.dev/itd/internal/types"
) )
@@ -36,7 +37,7 @@ var addressCmd = &cobra.Command{
Short: "Get InfiniTime's bluetooth address", Short: "Get InfiniTime's bluetooth address",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// Connect to itd UNIX socket // Connect to itd UNIX socket
conn, err := net.Dial("unix", SockPath) conn, err := net.Dial("unix", viper.GetString("sockPath"))
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?") log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
} }
@@ -44,7 +45,7 @@ var addressCmd = &cobra.Command{
// Encode request into connection // Encode request into connection
err = json.NewEncoder(conn).Encode(types.Request{ err = json.NewEncoder(conn).Encode(types.Request{
Type: ReqTypeBtAddress, Type: types.ReqTypeBtAddress,
}) })
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Error making request") log.Fatal().Err(err).Msg("Error making request")

View File

@@ -26,6 +26,7 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
"go.arsenm.dev/itd/internal/types" "go.arsenm.dev/itd/internal/types"
) )
@@ -36,7 +37,7 @@ var batteryCmd = &cobra.Command{
Short: "Get battery level from InfiniTime", Short: "Get battery level from InfiniTime",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// Connect to itd UNIX socket // Connect to itd UNIX socket
conn, err := net.Dial("unix", SockPath) conn, err := net.Dial("unix", viper.GetString("sockPath"))
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?") log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
} }
@@ -44,7 +45,7 @@ var batteryCmd = &cobra.Command{
// Encode request into connection // Encode request into connection
err = json.NewEncoder(conn).Encode(types.Request{ err = json.NewEncoder(conn).Encode(types.Request{
Type: ReqTypeBattLevel, Type: types.ReqTypeBattLevel,
}) })
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Error making request") log.Fatal().Err(err).Msg("Error making request")

View File

@@ -18,23 +18,6 @@
package cmd package cmd
const SockPath = "/tmp/itd/socket"
const (
ReqTypeHeartRate = "hrt"
ReqTypeBattLevel = "battlvl"
ReqTypeFwVersion = "fwver"
ReqTypeFwUpgrade = "fwupg"
ReqTypeBtAddress = "btaddr"
ReqTypeNotify = "notify"
ReqTypeSetTime = "settime"
)
const (
UpgradeTypeArchive = iota
UpgradeTypeFiles
)
type DFUProgress struct { type DFUProgress struct {
Received int64 `mapstructure:"recvd"` Received int64 `mapstructure:"recvd"`
Total int64 `mapstructure:"total"` Total int64 `mapstructure:"total"`

View File

@@ -26,6 +26,7 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
"go.arsenm.dev/itd/internal/types" "go.arsenm.dev/itd/internal/types"
) )
@@ -35,7 +36,7 @@ var heartCmd = &cobra.Command{
Short: "Get heart rate from InfiniTime", Short: "Get heart rate from InfiniTime",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// Connect to itd UNIX socket // Connect to itd UNIX socket
conn, err := net.Dial("unix", SockPath) conn, err := net.Dial("unix", viper.GetString("sockPath"))
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?") log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
} }
@@ -43,7 +44,7 @@ var heartCmd = &cobra.Command{
// Encode request into connection // Encode request into connection
err = json.NewEncoder(conn).Encode(types.Request{ err = json.NewEncoder(conn).Encode(types.Request{
Type: ReqTypeHeartRate, Type: types.ReqTypeHeartRate,
}) })
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Error making request") log.Fatal().Err(err).Msg("Error making request")

View File

@@ -25,6 +25,7 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
"go.arsenm.dev/itd/internal/types" "go.arsenm.dev/itd/internal/types"
) )
@@ -40,7 +41,7 @@ var notifyCmd = &cobra.Command{
} }
// Connect to itd UNIX socket // Connect to itd UNIX socket
conn, err := net.Dial("unix", SockPath) conn, err := net.Dial("unix", viper.GetString("sockPath"))
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?") log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
} }
@@ -48,7 +49,7 @@ var notifyCmd = &cobra.Command{
// Encode request into connection // Encode request into connection
err = json.NewEncoder(conn).Encode(types.Request{ err = json.NewEncoder(conn).Encode(types.Request{
Type: ReqTypeNotify, Type: types.ReqTypeNotify,
Data: types.ReqDataNotify{ Data: types.ReqDataNotify{
Title: args[0], Title: args[0],
Body: args[1], Body: args[1],

View File

@@ -21,6 +21,7 @@ package cmd
import ( import (
"github.com/abiosoft/ishell" "github.com/abiosoft/ishell"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
) )
// rootCmd represents the base command when called without any subcommands // rootCmd represents the base command when called without any subcommands
@@ -60,5 +61,18 @@ var rootCmd = &cobra.Command{
// Execute adds all child commands to the root command and sets flags appropriately. // Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd. // This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() { func Execute() {
rootCmd.CompletionOptions.DisableDefaultCmd = true
cobra.CheckErr(rootCmd.Execute()) cobra.CheckErr(rootCmd.Execute())
} }
func init() {
// Register flag for socket path
rootCmd.Flags().StringP("socket-path", "s", "", "Path to itd socket")
// Bind flag and environment variable to viper key
viper.BindPFlag("sockPath", rootCmd.Flags().Lookup("socket-path"))
viper.BindEnv("sockPath", "ITCTL_SOCKET_PATH")
// Set default value for socket path
viper.SetDefault("sockPath", "/tmp/itd/socket")
}

View File

@@ -25,6 +25,7 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
"go.arsenm.dev/itd/internal/types" "go.arsenm.dev/itd/internal/types"
) )
@@ -41,7 +42,7 @@ var timeCmd = &cobra.Command{
} }
// Connect to itd UNIX socket // Connect to itd UNIX socket
conn, err := net.Dial("unix", SockPath) conn, err := net.Dial("unix", viper.GetString("sockPath"))
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?") log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
} }
@@ -49,7 +50,7 @@ var timeCmd = &cobra.Command{
// Encode request into connection // Encode request into connection
err = json.NewEncoder(conn).Encode(types.Request{ err = json.NewEncoder(conn).Encode(types.Request{
Type: ReqTypeSetTime, Type: types.ReqTypeSetTime,
Data: args[0], Data: args[0],
}) })
if err != nil { if err != nil {

View File

@@ -38,7 +38,7 @@ var upgradeCmd = &cobra.Command{
Aliases: []string{"upg"}, Aliases: []string{"upg"},
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// Connect to itd UNIX socket // Connect to itd UNIX socket
conn, err := net.Dial("unix", SockPath) conn, err := net.Dial("unix", viper.GetString("sockPath"))
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?") log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
} }
@@ -49,13 +49,13 @@ var upgradeCmd = &cobra.Command{
if viper.GetString("archive") != "" { if viper.GetString("archive") != "" {
// Get archive data struct // Get archive data struct
data = types.ReqDataFwUpgrade{ data = types.ReqDataFwUpgrade{
Type: UpgradeTypeArchive, Type: types.UpgradeTypeArchive,
Files: []string{viper.GetString("archive")}, Files: []string{viper.GetString("archive")},
} }
} else if viper.GetString("initPkt") != "" && viper.GetString("firmware") != "" { } else if viper.GetString("initPkt") != "" && viper.GetString("firmware") != "" {
// Get files data struct // Get files data struct
data = types.ReqDataFwUpgrade{ data = types.ReqDataFwUpgrade{
Type: UpgradeTypeFiles, Type: types.UpgradeTypeFiles,
Files: []string{viper.GetString("initPkt"), viper.GetString("firmware")}, Files: []string{viper.GetString("initPkt"), viper.GetString("firmware")},
} }
} else { } else {
@@ -66,7 +66,7 @@ var upgradeCmd = &cobra.Command{
// Encode response into connection // Encode response into connection
err = json.NewEncoder(conn).Encode(types.Request{ err = json.NewEncoder(conn).Encode(types.Request{
Type: ReqTypeFwUpgrade, Type: types.ReqTypeFwUpgrade,
Data: data, Data: data,
}) })
if err != nil { if err != nil {
@@ -95,14 +95,14 @@ var upgradeCmd = &cobra.Command{
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Error decoding response data") log.Fatal().Err(err).Msg("Error decoding response data")
} }
// If transfer finished, break
if event.Received == event.Total {
break
}
// Set total bytes in progress bar // Set total bytes in progress bar
bar.SetTotal(event.Total) bar.SetTotal(event.Total)
// Set amount of bytes received in progress bar // Set amount of bytes received in progress bar
bar.SetCurrent(event.Received) bar.SetCurrent(event.Received)
// If transfer finished, break
if event.Received == event.Total {
break
}
} }
// Finish progress bar // Finish progress bar
bar.Finish() bar.Finish()

View File

@@ -26,6 +26,7 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
"go.arsenm.dev/itd/internal/types" "go.arsenm.dev/itd/internal/types"
) )
@@ -36,7 +37,7 @@ var versionCmd = &cobra.Command{
Short: "Get firmware version of InfiniTime", Short: "Get firmware version of InfiniTime",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// Connect to itd UNIX socket // Connect to itd UNIX socket
conn, err := net.Dial("unix", SockPath) conn, err := net.Dial("unix", viper.GetString("sockPath"))
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?") log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
} }
@@ -44,7 +45,7 @@ var versionCmd = &cobra.Command{
// Encode request into connection // Encode request into connection
err = json.NewEncoder(conn).Encode(types.Request{ err = json.NewEncoder(conn).Encode(types.Request{
Type: ReqTypeFwVersion, Type: types.ReqTypeFwVersion,
}) })
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Error making request") log.Fatal().Err(err).Msg("Error making request")

53
cmd/itgui/error.go Normal file
View File

@@ -0,0 +1,53 @@
package main
import (
"image/color"
"os"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/widget"
)
func guiErr(err error, msg string, fatal bool, parent fyne.Window) {
// Create new label containing message
msgLbl := widget.NewLabel(msg)
// Text formatting settings
msgLbl.Wrapping = fyne.TextWrapWord
msgLbl.Alignment = fyne.TextAlignCenter
// Create new rectangle to set the size of the dialog
rect := canvas.NewRectangle(color.Transparent)
// Set minimum size of rectangle to 350x0
rect.SetMinSize(fyne.NewSize(350, 0))
// Create new container containing message and rectangle
content := container.NewVBox(
msgLbl,
rect,
)
if err != nil {
// Create new label containing error text
errLbl := widget.NewLabel(err.Error())
// Create new dropdown containing error label
content.Add(widget.NewAccordion(
widget.NewAccordionItem("More Details", errLbl),
))
}
if fatal {
// Create new error dialog
errDlg := dialog.NewCustom("Error", "Close", content, parent)
// On close, exit with code 1
errDlg.SetOnClosed(func() {
os.Exit(1)
})
// Show dialog
errDlg.Show()
// Run app prematurely to stop further execution
parent.ShowAndRun()
} else {
// Show error dialog
dialog.NewCustom("Error", "Ok", content, parent).Show()
}
}

149
cmd/itgui/info.go Normal file
View File

@@ -0,0 +1,149 @@
package main
import (
"bufio"
"errors"
"fmt"
"image/color"
"net"
"encoding/json"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/theme"
"go.arsenm.dev/itd/internal/types"
)
func infoTab(parent fyne.Window) *fyne.Container {
infoLayout := container.NewVBox(
// Add rectangle for a bit of padding
canvas.NewRectangle(color.Transparent),
)
// Create label for heart rate
heartRateLbl := newText("0 BPM", 24)
// Creae container to store heart rate section
heartRate := container.NewVBox(
newText("Heart Rate", 12),
heartRateLbl,
canvas.NewLine(theme.ShadowColor()),
)
infoLayout.Add(heartRate)
// Watch for heart rate updates
go watch(types.ReqTypeWatchHeartRate, func(data interface{}) {
// Change text of heart rate label
heartRateLbl.Text = fmt.Sprintf("%d BPM", int(data.(float64)))
// Refresh label
heartRateLbl.Refresh()
}, parent)
// Create label for battery level
battLevelLbl := newText("0%", 24)
// Create container to store battery level section
battLevel := container.NewVBox(
newText("Battery Level", 12),
battLevelLbl,
canvas.NewLine(theme.ShadowColor()),
)
infoLayout.Add(battLevel)
// Watch for changes in battery level
go watch(types.ReqTypeWatchBattLevel, func(data interface{}) {
battLevelLbl.Text = fmt.Sprintf("%d%%", int(data.(float64)))
battLevelLbl.Refresh()
}, parent)
fwVerString, err := get(types.ReqTypeFwVersion)
if err != nil {
guiErr(err, "Error getting firmware string", true, parent)
}
fwVer := container.NewVBox(
newText("Firmware Version", 12),
newText(fwVerString.(string), 24),
canvas.NewLine(theme.ShadowColor()),
)
infoLayout.Add(fwVer)
btAddrString, err := get(types.ReqTypeBtAddress)
if err != nil {
panic(err)
}
btAddr := container.NewVBox(
newText("Bluetooth Address", 12),
newText(btAddrString.(string), 24),
canvas.NewLine(theme.ShadowColor()),
)
infoLayout.Add(btAddr)
return infoLayout
}
func watch(req int, onRecv func(data interface{}), parent fyne.Window) error {
conn, err := net.Dial("unix", SockPath)
if err != nil {
return err
}
defer conn.Close()
err = json.NewEncoder(conn).Encode(types.Request{
Type: req,
})
if err != nil {
return err
}
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
res, err := getResp(scanner.Bytes())
if err != nil {
guiErr(err, "Error getting response from connection", false, parent)
continue
}
onRecv(res.Value)
}
return nil
}
func get(req int) (interface{}, error) {
conn, err := net.Dial("unix", SockPath)
if err != nil {
return nil, err
}
defer conn.Close()
err = json.NewEncoder(conn).Encode(types.Request{
Type: req,
})
if err != nil {
return nil, err
}
line, _, err := bufio.NewReader(conn).ReadLine()
if err != nil {
return nil, err
}
res, err := getResp(line)
if err != nil {
return nil, err
}
return res.Value, nil
}
func getResp(line []byte) (*types.Response, error) {
var res types.Response
err := json.Unmarshal(line, &res)
if err != nil {
return nil, err
}
if res.Error {
return nil, errors.New(res.Message)
}
return &res, nil
}
func newText(t string, size float32) *canvas.Text {
text := canvas.NewText(t, theme.ForegroundColor())
text.TextSize = size
return text
}

35
cmd/itgui/main.go Normal file
View File

@@ -0,0 +1,35 @@
package main
import (
"net"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
)
var SockPath = "/tmp/itd/socket"
func main() {
// Create new app
a := app.New()
// Create new window with title "itgui"
window := a.NewWindow("itgui")
_, err := net.Dial("unix", SockPath)
if err != nil {
guiErr(err, "Error dialing itd socket", true, window)
}
// Create new app tabs container
tabs := container.NewAppTabs(
container.NewTabItem("Info", infoTab(window)),
container.NewTabItem("Notify", notifyTab(window)),
container.NewTabItem("Set Time", timeTab(window)),
container.NewTabItem("Upgrade", upgradeTab(window)),
)
// Set tabs as window content
window.SetContent(tabs)
// Show window and run app
window.ShowAndRun()
}

49
cmd/itgui/notify.go Normal file
View File

@@ -0,0 +1,49 @@
package main
import (
"encoding/json"
"net"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/widget"
"go.arsenm.dev/itd/internal/types"
)
func notifyTab(parent fyne.Window) *fyne.Container {
// Create new entry for notification title
titleEntry := widget.NewEntry()
titleEntry.SetPlaceHolder("Title")
// Create multiline entry for notification body
bodyEntry := widget.NewMultiLineEntry()
bodyEntry.SetPlaceHolder("Body")
// Create new button to send notification
sendBtn := widget.NewButton("Send", func() {
// Dial itd UNIX socket
conn, err := net.Dial("unix", SockPath)
if err != nil {
guiErr(err, "Error dialing socket", false, parent)
return
}
// Encode notify request on connection
json.NewEncoder(conn).Encode(types.Request{
Type: types.ReqTypeNotify,
Data: types.ReqDataNotify{
Title: titleEntry.Text,
Body: bodyEntry.Text,
},
})
})
// Return new container containing all elements
return container.NewVBox(
layout.NewSpacer(),
titleEntry,
bodyEntry,
sendBtn,
layout.NewSpacer(),
)
}

79
cmd/itgui/time.go Normal file
View File

@@ -0,0 +1,79 @@
package main
import (
"encoding/json"
"net"
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/widget"
"go.arsenm.dev/itd/internal/types"
)
func timeTab(parent fyne.Window) *fyne.Container {
// Create new entry for time string
timeEntry := widget.NewEntry()
// Set text to current time formatter properly
timeEntry.SetText(time.Now().Format(time.RFC1123))
// Create button to set current time
currentBtn := widget.NewButton("Set Current", func() {
timeEntry.SetText(time.Now().Format(time.RFC1123))
setTime(true)
})
// Create button to set time inside entry
timeBtn := widget.NewButton("Set", func() {
// Parse time as RFC1123 string
parsedTime, err := time.Parse(time.RFC1123, timeEntry.Text)
if err != nil {
guiErr(err, "Error parsing time string", false, parent)
return
}
// Set time to parsed time
setTime(false, parsedTime)
})
// Return new container with all elements centered
return container.NewVBox(
layout.NewSpacer(),
timeEntry,
currentBtn,
timeBtn,
layout.NewSpacer(),
)
}
// setTime sets the first element in the variadic parameter
// if current is false, otherwise, it sets the current time.
func setTime(current bool, t ...time.Time) error {
// Dial UNIX socket
conn, err := net.Dial("unix", SockPath)
if err != nil {
return err
}
defer conn.Close()
var data string
// If current is true, use the string "now"
// otherwise, use the formatted time from the
// first element in the variadic parameter.
// "now" is more accurate than formatting
// current time as only seconds are preserved
// in that case.
if current {
data = "now"
} else {
data = t[0].Format(time.RFC3339)
}
// Encode SetTime request with above data
err = json.NewEncoder(conn).Encode(types.Request{
Type: types.ReqTypeSetTime,
Data: data,
})
if err != nil {
return err
}
return nil
}

191
cmd/itgui/upgrade.go Normal file
View File

@@ -0,0 +1,191 @@
package main
import (
"bufio"
"encoding/json"
"fmt"
"net"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/storage"
"fyne.io/fyne/v2/widget"
"github.com/mitchellh/mapstructure"
"go.arsenm.dev/itd/internal/types"
)
func upgradeTab(parent fyne.Window) *fyne.Container {
var (
archivePath string
fiwmarePath string
initPktPath string
)
// Create archive selection dialog
archiveDialog := dialog.NewFileOpen(func(uc fyne.URIReadCloser, e error) {
if e != nil || uc == nil {
return
}
uc.Close()
archivePath = uc.URI().Path()
}, parent)
// Limit dialog to .zip files
archiveDialog.SetFilter(storage.NewExtensionFileFilter([]string{".zip"}))
// Create button to show dialog
archiveBtn := widget.NewButton("Select archive (.zip)", archiveDialog.Show)
// Create firmware selection dialog
firmwareDialog := dialog.NewFileOpen(func(uc fyne.URIReadCloser, e error) {
if e != nil || uc == nil {
return
}
uc.Close()
fiwmarePath = uc.URI().Path()
}, parent)
// Limit dialog to .bin files
firmwareDialog.SetFilter(storage.NewExtensionFileFilter([]string{".bin"}))
// Create button to show dialog
firmwareBtn := widget.NewButton("Select init packet (.bin)", firmwareDialog.Show)
// Create init packet selection dialog
initPktDialog := dialog.NewFileOpen(func(uc fyne.URIReadCloser, e error) {
if e != nil || uc == nil {
return
}
uc.Close()
initPktPath = uc.URI().Path()
}, parent)
// Limit dialog to .dat files
initPktDialog.SetFilter(storage.NewExtensionFileFilter([]string{".dat"}))
// Create button to show dialog
initPktBtn := widget.NewButton("Select init packet (.dat)", initPktDialog.Show)
// Hide init packet and firmware buttons
initPktBtn.Hide()
firmwareBtn.Hide()
// Create dropdown to select upgrade type
upgradeTypeSelect := widget.NewSelect([]string{
"Archive",
"Files",
}, func(s string) {
// Hide all buttons
archiveBtn.Hide()
initPktBtn.Hide()
firmwareBtn.Hide()
// Unhide appropriate button(s)
switch s {
case "Archive":
archiveBtn.Show()
case "Files":
initPktBtn.Show()
firmwareBtn.Show()
}
})
// Select first elemetn
upgradeTypeSelect.SetSelectedIndex(0)
// Create new button to start DFU
startBtn := widget.NewButton("Start", func() {
// If archive path does not exist and both init packet and firmware paths
// also do not exist, return error
if archivePath == "" && (initPktPath == "" && fiwmarePath == "") {
guiErr(nil, "Upgrade requires archive or files selected", false, parent)
return
}
// Create new label for byte progress
progressLbl := widget.NewLabelWithStyle("0 / 0 B", fyne.TextAlignCenter, fyne.TextStyle{})
// Create new progress bar
progressBar := widget.NewProgressBar()
// Create modal dialog containing label and progress bar
progressDlg := widget.NewModalPopUp(container.NewVBox(
layout.NewSpacer(),
progressLbl,
progressBar,
layout.NewSpacer(),
), parent.Canvas())
// Resize modal to 300x100
progressDlg.Resize(fyne.NewSize(300, 100))
var fwUpgType int
var files []string
// Get appropriate upgrade type and file paths
switch upgradeTypeSelect.Selected {
case "Archive":
fwUpgType = types.UpgradeTypeArchive
files = append(files, archivePath)
case "Files":
fwUpgType = types.UpgradeTypeFiles
files = append(files, initPktPath, fiwmarePath)
}
// Dial itd UNIX socket
conn, err := net.Dial("unix", SockPath)
if err != nil {
guiErr(err, "Error dialing socket", false, parent)
return
}
defer conn.Close()
// Encode firmware upgrade request to connection
json.NewEncoder(conn).Encode(types.Request{
Type: types.ReqTypeFwUpgrade,
Data: types.ReqDataFwUpgrade{
Type: fwUpgType,
Files: files,
},
})
// Show progress dialog
progressDlg.Show()
// Hide progress dialog after completion
defer progressDlg.Hide()
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
var res types.Response
// Decode scanned line into response struct
err = json.Unmarshal(scanner.Bytes(), &res)
if err != nil {
guiErr(err, "Error decoding response", false, parent)
return
}
if res.Error {
guiErr(err, "Error returned in response", false, parent)
return
}
var event types.DFUProgress
// Decode response data into progress struct
err = mapstructure.Decode(res.Value, &event)
if err != nil {
guiErr(err, "Error decoding response value", false, parent)
return
}
// Set label text to received / total B
progressLbl.SetText(fmt.Sprintf("%d / %d B", event.Received, event.Total))
// Set progress bar values
progressBar.Max = float64(event.Total)
progressBar.Value = float64(event.Received)
// Refresh progress bar
progressBar.Refresh()
// If transfer finished, break
if event.Received == event.Total {
break
}
}
})
// Return container containing all elements
return container.NewVBox(
layout.NewSpacer(),
upgradeTypeSelect,
archiveBtn,
firmwareBtn,
initPktBtn,
startBtn,
layout.NewSpacer(),
)
}

47
config.go Normal file
View File

@@ -0,0 +1,47 @@
package main
import (
"os"
"strings"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/viper"
)
func init() {
// Set up logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
// Set config settings
setCfgDefaults()
viper.AddConfigPath("$HOME/.config")
viper.AddConfigPath("/etc")
viper.SetConfigName("itd")
viper.SetConfigType("toml")
viper.WatchConfig()
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.SetEnvPrefix("itd")
// Ignore error because defaults set
viper.ReadInConfig()
viper.AutomaticEnv()
}
func setCfgDefaults() {
viper.SetDefault("cfg.version", 2)
viper.SetDefault("socket.path", "/tmp/itd/socket")
viper.SetDefault("conn.reconnect", true)
viper.SetDefault("on.connect.notify", true)
viper.SetDefault("on.reconnect.notify", true)
viper.SetDefault("on.reconnect.setTime", true)
viper.SetDefault("notifs.ignore.sender", []string{})
viper.SetDefault("notifs.ignore.summary", []string{"InfiniTime"})
viper.SetDefault("notifs.ignore.body", []string{})
viper.SetDefault("music.vol.interval", 5)
}

8
go.mod
View File

@@ -3,6 +3,7 @@ module go.arsenm.dev/itd
go 1.16 go 1.16
require ( require (
fyne.io/fyne/v2 v2.0.4
github.com/VividCortex/ewma v1.2.0 // indirect github.com/VividCortex/ewma v1.2.0 // indirect
github.com/abiosoft/ishell v2.0.0+incompatible github.com/abiosoft/ishell v2.0.0+incompatible
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect
@@ -14,12 +15,13 @@ require (
github.com/mattn/go-isatty v0.0.13 // indirect github.com/mattn/go-isatty v0.0.13 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mitchellh/mapstructure v1.4.1 github.com/mitchellh/mapstructure v1.4.1
github.com/mozillazg/go-pinyin v0.18.0
github.com/rs/zerolog v1.23.0 github.com/rs/zerolog v1.23.0
github.com/sirupsen/logrus v1.8.1 // indirect github.com/sirupsen/logrus v1.8.1 // indirect
github.com/spf13/cast v1.4.1 // indirect github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/cobra v1.2.1 github.com/spf13/cobra v1.2.1
github.com/spf13/viper v1.8.1 github.com/spf13/viper v1.8.1
go.arsenm.dev/infinitime v0.0.0-20210821070429-ea488067fb9b go.arsenm.dev/infinitime v0.0.0-20210825051734-745b4bd37cf4
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55 // indirect golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.3.7
) )

42
go.sum
View File

@@ -37,8 +37,11 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
fyne.io/fyne/v2 v2.0.4 h1:eDGaPGzeR4qNqWuAp9Li1kY4eVIHldCkf42KMakKIK4=
fyne.io/fyne/v2 v2.0.4/go.mod h1:nNpgL7sZkDVLraGtQII2ArNRnnl6kHup/KfQRxIhbvs=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
@@ -46,6 +49,7 @@ github.com/abiosoft/ishell v2.0.0+incompatible h1:zpwIuEHc37EzrsIYah3cpevrIc8Oma
github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzgHf/aS6+zVXRj14cVk9qg= github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzgHf/aS6+zVXRj14cVk9qg=
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db h1:CjPUSXOiYptLbTdr1RceuZgSFDQ7U15ITERUGrUORx8= github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db h1:CjPUSXOiYptLbTdr1RceuZgSFDQ7U15ITERUGrUORx8=
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db/go.mod h1:rB3B4rKii8V21ydCbIzH5hZiCQE7f5E9SzUb/ZZx530= github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db/go.mod h1:rB3B4rKii8V21ydCbIzH5hZiCQE7f5E9SzUb/ZZx530=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
@@ -85,17 +89,28 @@ github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWTLOJKlh+lOBt6nUQgXAfB7oVIQt5cNreqSLI= github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWTLOJKlh+lOBt6nUQgXAfB7oVIQt5cNreqSLI=
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M= github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M=
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA=
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.0 h1:NO5hkcB+srp1x6QmwvNZLeaOgbM8cmBTN32THzjvu2k= github.com/fsnotify/fsnotify v1.5.0 h1:NO5hkcB+srp1x6QmwvNZLeaOgbM8cmBTN32THzjvu2k=
github.com/fsnotify/fsnotify v1.5.0/go.mod h1:BX0DCEr5pT4jm2CnQdVP1lFV521fcCNcyEeNp4DQQDk= github.com/fsnotify/fsnotify v1.5.0/go.mod h1:BX0DCEr5pT4jm2CnQdVP1lFV521fcCNcyEeNp4DQQDk=
github.com/fyne-io/mobile v0.1.3-0.20210412090810-650a3139866a h1:3TAJhl8vXyli0tooKB0vd6gLCyBdWL4QEYbDoJpHEZk=
github.com/fyne-io/mobile v0.1.3-0.20210412090810-650a3139866a/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw=
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb h1:T6gaWBvRzJjuOrdCtg8fXXjKai2xSDqWTcKFUPuw8Tw=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -186,6 +201,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
@@ -199,6 +216,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lucor/goinfo v0.0.0-20200401173949-526b5363a13a/go.mod h1:ORP3/rB5IsulLEBwQZCJyyV6niqmI7P4EWSmkug+1Ng=
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
@@ -224,14 +242,18 @@ github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mozillazg/go-pinyin v0.18.0 h1:hQompXO23/0ohH8YNjvfsAITnCQImCiR/Fny8EhIeW0=
github.com/mozillazg/go-pinyin v0.18.0/go.mod h1:iR4EnMMRXkfpFVV5FMi4FNB6wGq9NV6uDWbUuPhP4Yc=
github.com/muka/go-bluetooth v0.0.0-20210812063148-b6c83362e27d h1:EG/xyWjHT19rkUpwsWSkyiCCmyqNwFovr9m10rhyOxU= github.com/muka/go-bluetooth v0.0.0-20210812063148-b6c83362e27d h1:EG/xyWjHT19rkUpwsWSkyiCCmyqNwFovr9m10rhyOxU=
github.com/muka/go-bluetooth v0.0.0-20210812063148-b6c83362e27d/go.mod h1:dMCjicU6vRBk34dqOmIZm0aod6gUwZXOXzBROqGous0= github.com/muka/go-bluetooth v0.0.0-20210812063148-b6c83362e27d/go.mod h1:dMCjicU6vRBk34dqOmIZm0aod6gUwZXOXzBROqGous0=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/paypal/gatt v0.0.0-20151011220935-4ae819d591cf/go.mod h1:+AwQL2mK3Pd3S+TUwg0tYQjid0q1txyNUJuuSmz8Kdk= github.com/paypal/gatt v0.0.0-20151011220935-4ae819d591cf/go.mod h1:+AwQL2mK3Pd3S+TUwg0tYQjid0q1txyNUJuuSmz8Kdk=
github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
@@ -258,6 +280,7 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykE
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
@@ -267,10 +290,15 @@ github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44=
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM=
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 h1:m59mIOBO4kfcNCEzJNy71UkeF4XIx2EVmL9KLwDQdmM=
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@@ -287,8 +315,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.arsenm.dev/infinitime v0.0.0-20210821070429-ea488067fb9b h1:Wwj7F0gqYHUx+9H8fCCIy5JZTlCusJRpPuzeSFM0EoU= go.arsenm.dev/infinitime v0.0.0-20210825051734-745b4bd37cf4 h1:XZyynxrvGxP0mwyhdiuMrvj5SkiK6N+MDiC6DiGzgWU=
go.arsenm.dev/infinitime v0.0.0-20210821070429-ea488067fb9b/go.mod h1:gaepaueUz4J5FfxuV19B4w5pi+V3mD0LTef50ryxr/Q= go.arsenm.dev/infinitime v0.0.0-20210825051734-745b4bd37cf4/go.mod h1:gaepaueUz4J5FfxuV19B4w5pi+V3mD0LTef50ryxr/Q=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
@@ -321,6 +349,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -379,6 +409,7 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -433,6 +464,7 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -449,8 +481,8 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55 h1:rw6UNGRMfarCepjI8qOepea/SXwIBVfTKjztZ5gBbq4= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k=
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -477,6 +509,7 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190808195139-e713427fea3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -498,6 +531,7 @@ golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200328031815-3db5fc6bac03/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=

View File

@@ -1,5 +1,22 @@
package types package types
const (
ReqTypeHeartRate = iota
ReqTypeBattLevel
ReqTypeFwVersion
ReqTypeFwUpgrade
ReqTypeBtAddress
ReqTypeNotify
ReqTypeSetTime
ReqTypeWatchHeartRate
ReqTypeWatchBattLevel
)
const (
UpgradeTypeArchive = iota
UpgradeTypeFiles
)
type ReqDataFwUpgrade struct { type ReqDataFwUpgrade struct {
Type int Type int
Files []string Files []string
@@ -12,7 +29,7 @@ type Response struct {
} }
type Request struct { type Request struct {
Type string `json:"type"` Type int `json:"type"`
Data interface{} `json:"data,omitempty"` Data interface{} `json:"data,omitempty"`
} }
@@ -20,3 +37,8 @@ type ReqDataNotify struct {
Title string Title string
Body string Body string
} }
type DFUProgress struct {
Received int64 `mapstructure:"recvd"`
Total int64 `mapstructure:"total"`
}

View File

@@ -1,14 +1,28 @@
# This is temporary, it is to show a notice
# to people still using the old config
cfg.version = 2
[socket]
path = "/tmp/itd/socket"
[conn] [conn]
reconnect = true reconnect = true
[notify] [on.connect]
onConnect = true notify = true
onReconnect = true
[notifications.ignore] [on.reconnect]
notify = true
setTime = true
[notifs.translit]
use = ["eASCII", "Russian", "Emoji"]
[notifs.ignore]
sender = [] sender = []
summary = ["InfiniTime"] summary = ["InfiniTime"]
body = [] body = []
[music] [music]
volInterval = 5 vol.interval = 5

39
main.go
View File

@@ -19,11 +19,8 @@
package main package main
import ( import (
"os"
"strings"
"time" "time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/spf13/viper" "github.com/spf13/viper"
"go.arsenm.dev/infinitime" "go.arsenm.dev/infinitime"
@@ -31,25 +28,11 @@ import (
var firmwareUpdating = false var firmwareUpdating = false
func init() {
// Set up logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
// Set config settings
viper.AddConfigPath("$HOME/.config")
viper.AddConfigPath("/etc")
viper.SetConfigName("itd")
viper.SetConfigType("toml")
viper.WatchConfig()
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.SetEnvPrefix("itd")
if err := viper.ReadInConfig(); err != nil {
log.Warn().Err(err).Msg("Could not read in config")
}
viper.AutomaticEnv()
}
func main() { func main() {
if viper.GetInt("cfg.version") != 2 {
log.Fatal().Msg("Please update your config to the newest format, only v2 configs supported.")
}
// Cleanly exit after function // Cleanly exit after function
defer infinitime.Exit() defer infinitime.Exit()
@@ -63,14 +46,16 @@ func main() {
// When InfiniTime reconnects // When InfiniTime reconnects
dev.OnReconnect(func() { dev.OnReconnect(func() {
// Set time to current time if viper.GetBool("on.reconnect.setTime") {
err = dev.SetTime(time.Now()) // Set time to current time
if err != nil { err = dev.SetTime(time.Now())
log.Error().Err(err).Msg("Error setting current time on connected InfiniTime") if err != nil {
log.Error().Err(err).Msg("Error setting current time on connected InfiniTime")
}
} }
// If config specifies to notify on reconnect // If config specifies to notify on reconnect
if viper.GetBool("notify.onReconnect") { if viper.GetBool("on.reconnect.notify") {
// Send notification to InfiniTime // Send notification to InfiniTime
err = dev.Notify("itd", "Successfully reconnected") err = dev.Notify("itd", "Successfully reconnected")
if err != nil { if err != nil {
@@ -89,7 +74,7 @@ func main() {
log.Info().Str("version", ver).Msg("Connected to InfiniTime") log.Info().Str("version", ver).Msg("Connected to InfiniTime")
// If config specifies to notify on connect // If config specifies to notify on connect
if viper.GetBool("notify.onConnect") { if viper.GetBool("on.connect.notify") {
// Send notification to InfiniTime // Send notification to InfiniTime
err = dev.Notify("itd", "Successfully connected") err = dev.Notify("itd", "Successfully connected")
if err != nil { if err != nil {

View File

@@ -19,10 +19,10 @@
package main package main
import ( import (
"go.arsenm.dev/infinitime"
"go.arsenm.dev/infinitime/pkg/player"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/spf13/viper" "github.com/spf13/viper"
"go.arsenm.dev/infinitime"
"go.arsenm.dev/infinitime/pkg/player"
) )
func initMusicCtrl(dev *infinitime.Device) error { func initMusicCtrl(dev *infinitime.Device) error {
@@ -85,9 +85,9 @@ func initMusicCtrl(dev *infinitime.Device) error {
case infinitime.MusicEventPrev: case infinitime.MusicEventPrev:
player.Prev() player.Prev()
case infinitime.MusicEventVolUp: case infinitime.MusicEventVolUp:
player.VolUp(viper.GetUint("music.volInterval")) player.VolUp(viper.GetUint("music.vol.interval"))
case infinitime.MusicEventVolDown: case infinitime.MusicEventVolDown:
player.VolDown(viper.GetUint("music.volInterval")) player.VolDown(viper.GetUint("music.vol.interval"))
} }
} }
}() }()

View File

@@ -25,6 +25,7 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/spf13/viper" "github.com/spf13/viper"
"go.arsenm.dev/infinitime" "go.arsenm.dev/infinitime"
"go.arsenm.dev/itd/translit"
) )
func initNotifRelay(dev *infinitime.Device) error { func initNotifRelay(dev *infinitime.Device) error {
@@ -71,6 +72,12 @@ func initNotifRelay(dev *infinitime.Device) error {
continue continue
} }
maps := viper.GetStringSlice("notifs.translit.use")
translit.Transliterators["custom"] = translit.Map(viper.GetStringSlice("notifs.translit.custom"))
sender = translit.Transliterate(sender, maps...)
summary = translit.Transliterate(summary, maps...)
body = translit.Transliterate(body, maps...)
var msg string var msg string
// If summary does not exist, set message to body. // If summary does not exist, set message to body.
// If it does, set message to summary, two newlines, and then body // If it does, set message to summary, two newlines, and then body
@@ -90,9 +97,9 @@ func initNotifRelay(dev *infinitime.Device) error {
// ignored checks whether any fields were ignored in the config // ignored checks whether any fields were ignored in the config
func ignored(sender, summary, body string) bool { func ignored(sender, summary, body string) bool {
ignoreSender := viper.GetStringSlice("notifications.ignore.sender") ignoreSender := viper.GetStringSlice("notifs.ignore.sender")
ignoreSummary := viper.GetStringSlice("notifications.ignore.summary") ignoreSummary := viper.GetStringSlice("notifs.ignore.summary")
ignoreBody := viper.GetStringSlice("notifications.ignore.body") ignoreBody := viper.GetStringSlice("notifs.ignore.body")
return strSlcContains(ignoreSender, sender) || return strSlcContains(ignoreSender, sender) ||
strSlcContains(ignoreSummary, summary) || strSlcContains(ignoreSummary, summary) ||
strSlcContains(ignoreBody, body) strSlcContains(ignoreBody, body)

View File

@@ -29,42 +29,27 @@ import (
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/spf13/viper"
"go.arsenm.dev/infinitime" "go.arsenm.dev/infinitime"
"go.arsenm.dev/itd/internal/types" "go.arsenm.dev/itd/internal/types"
) "go.arsenm.dev/itd/translit"
const SockPath = "/tmp/itd/socket"
const (
ReqTypeHeartRate = "hrt"
ReqTypeBattLevel = "battlvl"
ReqTypeFwVersion = "fwver"
ReqTypeFwUpgrade = "fwupg"
ReqTypeBtAddress = "btaddr"
ReqTypeNotify = "notify"
ReqTypeSetTime = "settime"
)
const (
UpgradeTypeArchive = iota
UpgradeTypeFiles
) )
func startSocket(dev *infinitime.Device) error { func startSocket(dev *infinitime.Device) error {
// Make socket directory if non existant // Make socket directory if non-existent
err := os.MkdirAll(filepath.Dir(SockPath), 0755) err := os.MkdirAll(filepath.Dir(viper.GetString("socket.path")), 0755)
if err != nil { if err != nil {
return err return err
} }
// Remove old socket if it exists // Remove old socket if it exists
err = os.RemoveAll(SockPath) err = os.RemoveAll(viper.GetString("socket.path"))
if err != nil { if err != nil {
return err return err
} }
// Listen on socket path // Listen on socket path
ln, err := net.Listen("unix", SockPath) ln, err := net.Listen("unix", viper.GetString("socket.path"))
if err != nil { if err != nil {
return err return err
} }
@@ -83,7 +68,7 @@ func startSocket(dev *infinitime.Device) error {
}() }()
// Log socket start // Log socket start
log.Info().Str("path", SockPath).Msg("Started control socket") log.Info().Str("path", viper.GetString("socket.path")).Msg("Started control socket")
return nil return nil
} }
@@ -108,7 +93,7 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
} }
switch req.Type { switch req.Type {
case ReqTypeHeartRate: case types.ReqTypeHeartRate:
// Get heart rate from watch // Get heart rate from watch
heartRate, err := dev.HeartRate() heartRate, err := dev.HeartRate()
if err != nil { if err != nil {
@@ -119,7 +104,33 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
json.NewEncoder(conn).Encode(types.Response{ json.NewEncoder(conn).Encode(types.Response{
Value: heartRate, Value: heartRate,
}) })
case ReqTypeBattLevel: case types.ReqTypeWatchHeartRate:
heartRateCh, err := dev.WatchHeartRate()
if err != nil {
connErr(conn, err, "Error getting heart rate channel")
break
}
go func() {
for heartRate := range heartRateCh {
json.NewEncoder(conn).Encode(types.Response{
Value: heartRate,
})
}
}()
case types.ReqTypeWatchBattLevel:
battLevelCh, err := dev.WatchBatteryLevel()
if err != nil {
connErr(conn, err, "Error getting heart rate channel")
break
}
go func() {
for battLevel := range battLevelCh {
json.NewEncoder(conn).Encode(types.Response{
Value: battLevel,
})
}
}()
case types.ReqTypeBattLevel:
// Get battery level from watch // Get battery level from watch
battLevel, err := dev.BatteryLevel() battLevel, err := dev.BatteryLevel()
if err != nil { if err != nil {
@@ -130,7 +141,7 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
json.NewEncoder(conn).Encode(types.Response{ json.NewEncoder(conn).Encode(types.Response{
Value: battLevel, Value: battLevel,
}) })
case ReqTypeFwVersion: case types.ReqTypeFwVersion:
// Get firmware version from watch // Get firmware version from watch
version, err := dev.Version() version, err := dev.Version()
if err != nil { if err != nil {
@@ -141,42 +152,46 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
json.NewEncoder(conn).Encode(types.Response{ json.NewEncoder(conn).Encode(types.Response{
Value: version, Value: version,
}) })
case ReqTypeBtAddress: case types.ReqTypeBtAddress:
// Encode bluetooth address to connection // Encode bluetooth address to connection
json.NewEncoder(conn).Encode(types.Response{ json.NewEncoder(conn).Encode(types.Response{
Value: dev.Address(), Value: dev.Address(),
}) })
case ReqTypeNotify: case types.ReqTypeNotify:
// If no data, return error // If no data, return error
if req.Data == nil { if req.Data == nil {
connErr(conn, nil, "Data required for notify types.Request") connErr(conn, nil, "Data required for notify request")
break break
} }
var reqData types.ReqDataNotify var reqData types.ReqDataNotify
// Decode data map to notify types.Request data // Decode data map to notify request data
err = mapstructure.Decode(req.Data, &reqData) err = mapstructure.Decode(req.Data, &reqData)
if err != nil { if err != nil {
connErr(conn, err, "Error decoding types.Request data") connErr(conn, err, "Error decoding request data")
break break
} }
maps := viper.GetStringSlice("notifs.translit.use")
translit.Transliterators["custom"] = translit.Map(viper.GetStringSlice("notifs.translit.custom"))
title := translit.Transliterate(reqData.Title, maps...)
body := translit.Transliterate(reqData.Body, maps...)
// Send notification to watch // Send notification to watch
err = dev.Notify(reqData.Title, reqData.Body) err = dev.Notify(title, body)
if err != nil { if err != nil {
connErr(conn, err, "Error sending notification") connErr(conn, err, "Error sending notification")
break break
} }
// Encode empty types.Response to connection // Encode empty types.Response to connection
json.NewEncoder(conn).Encode(types.Response{}) json.NewEncoder(conn).Encode(types.Response{})
case ReqTypeSetTime: case types.ReqTypeSetTime:
// If no data, return error // If no data, return error
if req.Data == nil { if req.Data == nil {
connErr(conn, nil, "Data required for settime types.Request") connErr(conn, nil, "Data required for settime request")
break break
} }
// Get string from data or return error // Get string from data or return error
reqTimeStr, ok := req.Data.(string) reqTimeStr, ok := req.Data.(string)
if !ok { if !ok {
connErr(conn, nil, "Data for settime types.Request must be RFC3339 formatted time string") connErr(conn, nil, "Data for settime request must be RFC3339 formatted time string")
break break
} }
@@ -184,7 +199,7 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
if reqTimeStr == "now" { if reqTimeStr == "now" {
reqTime = time.Now() reqTime = time.Now()
} else { } else {
// Parse time as RFC3339/ISO9601 // Parse time as RFC3339/ISO8601
reqTime, err = time.Parse(time.RFC3339, reqTimeStr) reqTime, err = time.Parse(time.RFC3339, reqTimeStr)
if err != nil { if err != nil {
connErr(conn, err, "Invalid time format. Time string must be formatted as ISO8601 or the word `now`") connErr(conn, err, "Invalid time format. Time string must be formatted as ISO8601 or the word `now`")
@@ -199,21 +214,21 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
} }
// Encode empty types.Response to connection // Encode empty types.Response to connection
json.NewEncoder(conn).Encode(types.Response{}) json.NewEncoder(conn).Encode(types.Response{})
case ReqTypeFwUpgrade: case types.ReqTypeFwUpgrade:
// If no data, return error // If no data, return error
if req.Data == nil { if req.Data == nil {
connErr(conn, nil, "Data required for firmware upgrade types.Request") connErr(conn, nil, "Data required for firmware upgrade request")
break break
} }
var reqData types.ReqDataFwUpgrade var reqData types.ReqDataFwUpgrade
// Decode data map to firmware upgrade types.Request data // Decode data map to firmware upgrade request data
err = mapstructure.Decode(req.Data, &reqData) err = mapstructure.Decode(req.Data, &reqData)
if err != nil { if err != nil {
connErr(conn, err, "Error decoding types.Request data") connErr(conn, err, "Error decoding request data")
break break
} }
switch reqData.Type { switch reqData.Type {
case UpgradeTypeArchive: case types.UpgradeTypeArchive:
// If less than one file, return error // If less than one file, return error
if len(reqData.Files) < 1 { if len(reqData.Files) < 1 {
connErr(conn, nil, "Archive upgrade requires one file with .zip extension") connErr(conn, nil, "Archive upgrade requires one file with .zip extension")
@@ -230,7 +245,7 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
connErr(conn, err, "Error loading archive file") connErr(conn, err, "Error loading archive file")
break break
} }
case UpgradeTypeFiles: case types.UpgradeTypeFiles:
// If less than two files, return error // If less than two files, return error
if len(reqData.Files) < 2 { if len(reqData.Files) < 2 {
connErr(conn, nil, "Files upgrade requires two files. First with .dat and second with .bin extension.") connErr(conn, nil, "Files upgrade requires two files. First with .dat and second with .bin extension.")
@@ -272,6 +287,7 @@ func handleConnection(conn net.Conn, dev *infinitime.Device) {
err = dev.DFU.Start() err = dev.DFU.Start()
if err != nil { if err != nil {
connErr(conn, err, "Error performing upgrade") connErr(conn, err, "Error performing upgrade")
firmwareUpdating = false
break break
} }
firmwareUpdating = false firmwareUpdating = false

138
translit/armenian.go Normal file
View File

@@ -0,0 +1,138 @@
package translit
import (
"strings"
)
type ArmenianTranslit struct{}
var armenianMap = []string{
"աու", "au",
"բու", "bu",
"գու", "gu",
"դու", "du",
"եու", "eu",
"զու", "zu",
"էու", "eu",
"ըու", "yu",
"թու", "tu",
"ժու", "ju",
"իու", "iu",
"լու", "lu",
"խու", "xu",
"ծու", "cu",
"կու", "ku",
"հու", "hu",
"ձու", "dzu",
"ղու", "xu",
"ճու", "cu",
"մու", "mu",
"յու", "yu",
"նու", "nu",
"շու", "shu",
"չու", "chu",
"պու", "pu",
"ջու", "ju",
"ռու", "ru",
"սու", "su",
"վու", "vu",
"տու", "tu",
"րու", "ru",
"ցու", "cu",
"փու", "pu",
"քու", "qu",
"օու", "ou",
"ևու", "eu",
"ֆու", "fu",
"ոու", "vou",
"ու", "u",
"բո", "bo",
"գո", "go",
"դո", "do",
"զո", "zo",
"թո", "to",
"ժո", "jo",
"լո", "lo",
"խո", "xo",
"ծո", "co",
"կո", "ko",
"հո", "ho",
"ձո", "dzo",
"ղո", "xo",
"ճո", "co",
"մո", "mo",
"յո", "yo",
"նո", "no",
"շո", "so",
"չո", "co",
"պո", "po",
"ջո", "jo",
"ռո", "ro",
"սո", "so",
"վո", "vo",
"տո", "to",
"րո", "ro",
"ցո", "co",
"փո", "po",
"քո", "qo",
"ևո", "eo",
"ֆո", "fo",
"ո", "vo",
"եւ", "ev",
"եվ", "ev",
"ա", "a",
"բ", "b",
"գ", "g",
"դ", "d",
"ե", "e",
"զ", "z",
"է", "e",
"ը", "y",
"թ", "t",
"ժ", "j",
"ի", "i",
"լ", "l",
"խ", "x",
"ծ", "c",
"կ", "k",
"հ", "h",
"ձ", "dz",
"ղ", "x",
"ճ", "c",
"մ", "m",
"յ", "y",
"ն", "n",
"շ", "sh",
"չ", "ch",
"պ", "p",
"ջ", "j",
"ռ", "r",
"ս", "s",
"վ", "v",
"տ", "t",
"ր", "r",
"ց", "c",
"փ", "p",
"ք", "q",
"օ", "o",
"և", "ev",
"ֆ", "f",
"ւ", "",
}
func init() {
lower := armenianMap
for i, val := range lower {
if i%2 == 1 {
continue
}
capital := strings.Title(val)
if capital != val {
armenianMap = append(armenianMap, capital, strings.Title(armenianMap[i+1]))
}
}
}
func (at *ArmenianTranslit) Transliterate(s string) string {
return strings.NewReplacer(armenianMap...).Replace(s)
}

42
translit/chinese.go Normal file
View File

@@ -0,0 +1,42 @@
package translit
import (
"bytes"
"strings"
"unicode"
"github.com/mozillazg/go-pinyin"
)
// ChineseTranslit implements Transliterator using a pinyin
// conversion library.
type ChineseTranslit struct{}
func (ct *ChineseTranslit) Transliterate(s string) string {
// Create buffer for final output
outBuf := &bytes.Buffer{}
// Create buffer to temporarily store chinese characters
tmpBuf := &bytes.Buffer{}
// For every character in string
for _, char := range s {
// If character in Han range
if unicode.Is(unicode.Han, char) {
// Write character to temporary buffer
tmpBuf.WriteRune(char)
} else {
// If buffer contains characters
if tmpBuf.Len() > 0 {
// Convert to pinyin (without tones)
out := pinyin.LazyConvert(tmpBuf.String(), nil)
// Write space-separated string to output
outBuf.WriteString(strings.Join(out, " "))
// Reset temporary buffer
tmpBuf.Reset()
}
// Write character to output
outBuf.WriteRune(char)
}
}
// Return output string
return outBuf.String()
}

470
translit/korean.go Normal file
View File

@@ -0,0 +1,470 @@
package translit
import (
"strings"
"unicode"
"golang.org/x/text/unicode/norm"
)
// https://en.wikipedia.org/wiki/Hangul_Jamo_%28Unicode_block%29
var jamoBlock = &unicode.RangeTable{
R16: []unicode.Range16{{
Lo: 0x1100,
Hi: 0x11FF,
Stride: 1,
}},
}
// https://en.wikipedia.org/wiki/Hangul_Syllables
var syllablesBlock = &unicode.RangeTable{
R16: []unicode.Range16{{
Lo: 0xAC00,
Hi: 0xD7A3,
Stride: 1,
}},
}
// https://en.wikipedia.org/wiki/Hangul_Compatibility_Jamo
var compatJamoBlock = &unicode.RangeTable{
R16: []unicode.Range16{{
Lo: 0x3131,
Hi: 0x318E,
Stride: 1,
}},
}
// KoreanTranslit implements transliteration for Korean.
//
// This was translated to Go from the code in https://codeberg.org/Freeyourgadget/Gadgetbridge
type KoreanTranslit struct{}
// User input consisting of isolated jamo is usually mapped to the KS X 1001 compatibility
// block, but jamo resulting from decomposed syllables are mapped to the modern one. This
// function maps compat jamo to modern ones where possible and returns all other characters
// unmodified.
//
// https://en.wikipedia.org/wiki/Hangul_Compatibility_Jamo
// https://en.wikipedia.org/wiki/Hangul_Jamo_%28Unicode_block%29
func decompatJamo(jamo rune) rune {
// KS X 1001 Hangul filler, not used in modern Unicode. A useful landmark in the
// compatibility jamo block.
// https://en.wikipedia.org/wiki/KS_X_1001#Hangul_Filler
var hangulFiller rune = 0x3164
// Ignore characters outside compatibility jamo block
if !unicode.In(jamo, compatJamoBlock) {
return jamo
}
// Vowels are contiguous, in the same order, and unambiguous so it's a simple offset.
if jamo >= 0x314F && jamo < hangulFiller {
return jamo - 0x1FEE
}
// Consonants are organized differently. No clean way to do this.
// The compatibility jamo block doesn't distinguish between Choseong (leading) and Jongseong
// (final) positions, but the modern block does. We map to Choseong here.
switch jamo {
case 0x3131:
return 0x1100 // ㄱ
case 0x3132:
return 0x1101 // ㄲ
case 0x3134:
return 0x1102 // ㄴ
case 0x3137:
return 0x1103 // ㄷ
case 0x3138:
return 0x1104 // ㄸ
case 0x3139:
return 0x1105 // ㄹ
case 0x3141:
return 0x1106 // ㅁ
case 0x3142:
return 0x1107 // ㅂ
case 0x3143:
return 0x1108 // ㅃ
case 0x3145:
return 0x1109 // ㅅ
case 0x3146:
return 0x110A // ㅆ
case 0x3147:
return 0x110B // ㅇ
case 0x3148:
return 0x110C // ㅈ
case 0x3149:
return 0x110D // ㅉ
case 0x314A:
return 0x110E // ㅊ
case 0x314B:
return 0x110F // ㅋ
case 0x314C:
return 0x1110 // ㅌ
case 0x314D:
return 0x1111 // ㅍ
case 0x314E:
return 0x1112 // ㅎ
}
// The rest of the compatibility block consists of archaic compounds that are
// unlikely to be encountered in modern systems. Just leave them alone.
return jamo
}
// Transliterates one jamo at a time.
// Does nothing if it isn't in the modern jamo block.
func translitSingleJamo(jamo rune) string {
jamo = decompatJamo(jamo)
switch jamo {
// Choseong (leading position consonants)
case 0x1100:
return "g" // ㄱ
case 0x1101:
return "kk" // ㄲ
case 0x1102:
return "n" // ㄴ
case 0x1103:
return "d" // ㄷ
case 0x1104:
return "tt" // ㄸ
case 0x1105:
return "r" // ㄹ
case 0x1106:
return "m" // ㅁ
case 0x1107:
return "b" // ㅂ
case 0x1108:
return "pp" // ㅃ
case 0x1109:
return "s" // ㅅ
case 0x110A:
return "ss" // ㅆ
case 0x110B:
return "" // ㅇ
case 0x110C:
return "j" // ㅈ
case 0x110D:
return "jj" // ㅉ
case 0x110E:
return "ch" // ㅊ
case 0x110F:
return "k" // ㅋ
case 0x1110:
return "t" // ㅌ
case 0x1111:
return "p" // ㅍ
case 0x1112:
return "h" // ㅎ
// Jungseong (vowels)
case 0x1161:
return "a" // ㅏ
case 0x1162:
return "ae" // ㅐ
case 0x1163:
return "ya" // ㅑ
case 0x1164:
return "yae" // ㅒ
case 0x1165:
return "eo" // ㅓ
case 0x1166:
return "e" // ㅔ
case 0x1167:
return "yeo" // ㅕ
case 0x1168:
return "ye" // ㅖ
case 0x1169:
return "o" // ㅗ
case 0x116A:
return "wa" // ㅘ
case 0x116B:
return "wae" // ㅙ
case 0x116C:
return "oe" // ㅚ
case 0x116D:
return "yo" // ㅛ
case 0x116E:
return "u" // ㅜ
case 0x116F:
return "wo" // ㅝ
case 0x1170:
return "we" // ㅞ
case 0x1171:
return "wi" // ㅟ
case 0x1172:
return "yu" // ㅠ
case 0x1173:
return "eu" // ㅡ
case 0x1174:
return "ui" // ㅢ
case 0x1175:
return "i" // ㅣ
// Jongseong (final position consonants)
case 0x11A8:
return "k" // ㄱ
case 0x11A9:
return "k" // ㄲ
case 0x11AB:
return "n" // ㄴ
case 0x11AE:
return "t" // ㄷ
case 0x11AF:
return "l" // ㄹ
case 0x11B7:
return "m" // ㅁ
case 0x11B8:
return "p" // ㅂ
case 0x11BA:
return "t" // ㅅ
case 0x11BB:
return "t" // ㅆ
case 0x11BC:
return "ng" // ㅇ
case 0x11BD:
return "t" // ㅈ
case 0x11BE:
return "t" // ㅊ
case 0x11BF:
return "k" // ㅋ
case 0x11C0:
return "t" // ㅌ
case 0x11C1:
return "p" // ㅍ
case 0x11C2:
return "t" // ㅎ
}
return string(jamo)
}
// Some combinations of ending jamo in one syllable and initial jamo in the next are romanized
// irregularly. These exceptions are called "special provisions". In cases where multiple
// romanizations are permitted, we use the one that's least commonly used elsewhere.
//
// Returns empty strring and false if either character is not in the modern jamo block,
// or if there is no special provision for that pair of jamo.
func translitSpecialProvisions(previousEnding rune, nextInitial rune) (string, bool) {
// Return false if previousEnding not in modern jamo block
if !unicode.In(previousEnding, jamoBlock) {
return "", false
}
// Return false if nextInitial not in modern jamo block
if !unicode.In(nextInitial, jamoBlock) {
return "", false
}
// Jongseong (final position) ㅎ has a number of special provisions.
if previousEnding == 0x11C2 {
switch nextInitial {
case 0x110B:
return "h", true // ㅇ
case 0x1100:
return "k", true // ㄱ
case 0x1102:
return "nn", true // ㄴ
case 0x1103:
return "t", true // ㄷ
case 0x1105:
return "nn", true // ㄹ
case 0x1106:
return "nm", true // ㅁ
case 0x1107:
return "p", true // ㅂ
case 0x1109:
return "hs", true // ㅅ
case 0x110C:
return "ch", true // ㅈ
case 0x1112:
return "t", true // ㅎ
default:
return "", false
}
}
// Otherwise, special provisions are denser when grouped by the second jamo.
switch nextInitial {
case 0x1100: // ㄱ
switch previousEnding {
case 0x11AB:
return "n-g", true // ㄴ
default:
return "", false
}
case 0x1102: // ㄴ
switch previousEnding {
case 0x11A8:
return "ngn", true // ㄱ
case 0x11AE:
fallthrough // ㄷ
case 0x11BA:
fallthrough // ㅅ
case 0x11BD:
fallthrough // ㅈ
case 0x11BE:
fallthrough // ㅊ
case 0x11C0: // ㅌ
return "nn", true
case 0x11AF:
return "ll", true // ㄹ
case 0x11B8:
return "mn", true // ㅂ
default:
return "", false
}
case 0x1105: // ㄹ
switch previousEnding {
case 0x11A8:
fallthrough // ㄱ
case 0x11AB:
fallthrough // ㄴ
case 0x11AF: // ㄹ
return "ll", true
case 0x11AE:
fallthrough // ㄷ
case 0x11BA:
fallthrough // ㅅ
case 0x11BD:
fallthrough // ㅈ
case 0x11BE:
fallthrough // ㅊ
case 0x11C0: // ㅌ
return "nn", true
case 0x11B7:
fallthrough // ㅁ
case 0x11B8: // ㅂ
return "mn", true
case 0x11BC:
return "ngn", true // ㅇ
default:
return "", false
}
case 0x1106: // ㅁ
switch previousEnding {
case 0x11A8:
return "ngm", true // ㄱ
case 0x11AE:
fallthrough // ㄷ
case 0x11BA:
fallthrough // ㅅ
case 0x11BD:
fallthrough // ㅈ
case 0x11BE:
fallthrough // ㅊ
case 0x11C0: // ㅌ
return "nm", true
case 0x11B8:
return "mm", true // ㅂ
default:
return "", false
}
case 0x110B: // ㅇ
switch previousEnding {
case 0x11A8:
return "g", true // ㄱ
case 0x11AE:
return "d", true // ㄷ
case 0x11AF:
return "r", true // ㄹ
case 0x11B8:
return "b", true // ㅂ
case 0x11BA:
return "s", true // ㅅ
case 0x11BC:
return "ng-", true // ㅇ
case 0x11BD:
return "j", true // ㅈ
case 0x11BE:
return "ch", true // ㅊ
default:
return "", false
}
case 0x110F: // ㅋ
switch previousEnding {
case 0x11A8:
return "k-k", true // ㄱ
default:
return "", false
}
case 0x1110: // ㅌ
switch previousEnding {
case 0x11AE:
fallthrough // ㄷ
case 0x11BA:
fallthrough // ㅅ
case 0x11BD:
fallthrough // ㅈ
case 0x11BE:
fallthrough // ㅊ
case 0x11C0: // ㅌ
return "t-t", true
default:
return "", false
}
case 0x1111: // ㅍ
switch previousEnding {
case 0x11B8:
return "p-p", true // ㅂ
default:
return "", false
}
default:
return "", false
}
}
// Decompose a syllable into several jamo. Does nothing if that isn't possible.
func decompose(syllable rune) string {
return norm.NFD.String(string(syllable))
}
// Transliterate any Hangul in the given string.
// Leaves any non-Hangul characters unmodified.
func (kt *KoreanTranslit) Transliterate(s string) string {
if len(s) == 0 {
return s
}
builder := &strings.Builder{}
nextInitialJamoConsumed := false
for i, syllable := range s {
// If character not in blocks, leave it unmodified
if !unicode.In(syllable, jamoBlock, syllablesBlock, compatJamoBlock) {
builder.WriteRune(syllable)
continue
}
jamo := decompose(syllable)
for j, char := range jamo {
// If we already transliterated the first jamo of this syllable as part of a special
// provision, skip it. Otherwise, handle it in the unconditional else branch.
if j == 0 && nextInitialJamoConsumed {
nextInitialJamoConsumed = false
continue
}
// If this is the last jamo of this syllable and not the last syllable of the
// string, check for special provisions. If the next char is whitespace or not
// Hangul, run translitSpecialProvisions() should return no value.
if j == len(jamo)-1 && i < len(s)-1 {
nextSyllable := s[i+1]
nextJamo := decompose(rune(nextSyllable))[0]
// Attempt to handle special provision
specialProvision, ok := translitSpecialProvisions(char, rune(nextJamo))
if ok {
builder.WriteString(specialProvision)
nextInitialJamoConsumed = true
} else {
// Not a special provision, transliterate normally
builder.WriteString(translitSingleJamo(char))
}
continue
}
// Transliterate normally
builder.WriteString(translitSingleJamo(char))
}
}
return builder.String()
}

357
translit/translit.go Normal file
View File

@@ -0,0 +1,357 @@
package translit
import (
"strings"
)
// Transliterate runs the given maps on s and returns the result
func Transliterate(s string, useMaps ...string) string {
// Create variable to store modified string
out := s
// If custom map exists
if customMap, ok := Transliterators["custom"]; ok {
// Perform transliteration with it
out = customMap.Transliterate(out)
}
// For every map to use
for _, useMap := range useMaps {
// If custom, skip
if useMap == "custom" {
continue
}
// Get requested map
translitMap, ok := Transliterators[useMap]
if !ok {
continue
}
// Perform transliteration
out = translitMap.Transliterate(out)
}
// Return result
return out
}
// Transliterator is implemented by anything with a
// Transliterate method, which performs transliteration
// and returns the resulting string.
type Transliterator interface {
Transliterate(string) string
}
// Map implements Transliterator using a slice where
// every odd element is a key and every even one is a value
// which replaces the key.
type Map []string
func (mt Map) Transliterate(s string) string {
return strings.NewReplacer(mt...).Replace(s)
}
// Transliterators stores transliterator implementations for each supported language.
// Some of these were sourced from https://codeberg.org/Freeyourgadget/Gadgetbridge
var Transliterators = map[string]Transliterator{
"eASCII": Map{
"œ", "oe",
"ª", "a",
"°", "o",
"«", `"`,
"»", `"`,
},
"Scandinavian": Map{
"Æ", "Ae",
"æ", "ae",
"Ø", "Oe",
"ø", "oe",
"Å", "Aa",
"å", "aa",
},
"German": Map{
"ä", "ae",
"ö", "oe",
"ü", "ue",
"Ä", "Ae",
"Ö", "Oe",
"Ü", "Ue",
"ß", "ss",
"ẞ", "SS",
},
"Hebrew": Map{
"א", "a",
"ב", "b",
"ג", "g",
"ד", "d",
"ה", "h",
"ו", "u",
"ז", "z",
"ח", "kh",
"ט", "t",
"י", "y",
"כ", "c",
"ל", "l",
"מ", "m",
"נ", "n",
"ס", "s",
"ע", "'",
"פ", "p",
"צ", "ts",
"ק", "k",
"ר", "r",
"ש", "sh",
"ת", "th",
"ף", "f",
"ץ", "ts",
"ך", "ch",
"ם", "m",
"ן", "n",
},
"Greek": Map{
"α", "a",
"ά", "a",
"β", "v",
"γ", "g",
"δ", "d",
"ε", "e",
"έ", "e",
"ζ", "z",
"η", "i",
"ή", "i",
"θ", "th",
"ι", "i",
"ί", "i",
"ϊ", "i",
"ΐ", "i",
"κ", "k",
"λ", "l",
"μ", "m",
"ν", "n",
"ξ", "ks",
"ο", "o",
"ό", "o",
"π", "p",
"ρ", "r",
"σ", "s",
"ς", "s",
"τ", "t",
"υ", "y",
"ύ", "y",
"ϋ", "y",
"ΰ", "y",
"φ", "f",
"χ", "ch",
"ψ", "ps",
"ω", "o",
"ώ", "o",
"Α", "A",
"Ά", "A",
"Β", "B",
"Γ", "G",
"Δ", "D",
"Ε", "E",
"Έ", "E",
"Ζ", "Z",
"Η", "I",
"Ή", "I",
"Θ", "TH",
"Ι", "I",
"Ί", "I",
"Ϊ", "I",
"Κ", "K",
"Λ", "L",
"Μ", "M",
"Ν", "N",
"Ξ", "KS",
"Ο", "O",
"Ό", "O",
"Π", "P",
"Ρ", "R",
"Σ", "S",
"Τ", "T",
"Υ", "Y",
"Ύ", "Y",
"Ϋ", "Y",
"Φ", "F",
"Χ", "CH",
"Ψ", "PS",
"Ω", "O",
"Ώ", "O",
},
"Russian": Map{
"Ё", "Йo",
"ё", "йo",
},
"Ukranian": Map{
"ґ", "gh",
"є", "je",
"і", "i",
"ї", "ji",
"Ґ", "GH",
"Є", "JE",
"І", "I",
"Ї", "JI",
},
"Arabic": Map{
"ا", "a",
"ب", "b",
"ت", "t",
"ث", "th",
"ج", "j",
"ح", "7",
"خ", "5",
"د", "d",
"ذ", "th",
"ر", "r",
"ز", "z",
"س", "s",
"ش", "sh",
"ص", "9",
"ض", "9'",
"ط", "6",
"ظ", "6'",
"ع", "3",
"غ", "3'",
"ف", "f",
"ق", "q",
"ك", "k",
"ل", "l",
"م", "m",
"ن", "n",
"ه", "h",
"و", "w",
"ي", "y",
"ى", "a",
"ﺓ", "",
"آ", "2",
"ئ", "2",
"إ", "2",
"ؤ", "2",
"أ", "2",
"ء", "2",
"٠", "0",
"١", "1",
"٢", "2",
"٣", "3",
"٤", "4",
"٥", "5",
"٦", "6",
"٧", "7",
"٨", "8",
"٩", "9",
},
"Farsi": Map{
"پ", "p",
"چ", "ch",
"ژ", "zh",
"ک", "k",
"گ", "g",
"ی", "y",
"\u200c", " ",
"؟", "?",
"٪", "%",
"؛", ";",
"،", ":",
"۱", "1",
"۲", "2",
"۳", "3",
"۴", "4",
"۵", "5",
"۶", "6",
"۷", "7",
"۸", "8",
"۹", "9",
"۰", "0",
"»", "<",
"«", ">",
"ِ", "e",
"َ", "a",
"ُ", "o",
"ّ", "",
},
"Polish": Map{
"Ł", "L",
"ł", "l",
},
"Lithuanian": Map{
"ą", "a",
"č", "c",
"ę", "e",
"ė", "e",
"į", "i",
"š", "s",
"ų", "u",
"ū", "u",
"ž", "z",
},
"Estonian": Map{
"ä", "a",
"Ä", "A",
"ö", "o",
"õ", "o",
"Ö", "O",
"Õ", "O",
"ü", "u",
"Ü", "U",
},
"Icelandic": Map{
"Þ", "Th",
"þ", "th",
"Ð", "D",
"ð", "d",
},
"Czeck": Map{
"ř", "r",
"ě", "e",
"ý", "y",
"á", "a",
"í", "i",
"é", "e",
"ó", "o",
"ú", "u",
"ů", "u",
"ď", "d",
"ť", "t",
"ň", "n",
},
"French": Map{
"à", "a",
"â", "a",
"é", "e",
"è", "e",
"ê", "e",
"ë", "e",
"ù", "u",
"ü", "u",
"ÿ", "y",
"ç", "c",
},
"Emoji": Map{
"😂", ":')",
"😊", ":)",
"😃", ":)",
"😩", "-_-",
"😏", ":J",
"💜", "<3",
"💖", "<3",
"💗", "<3",
"❤️", "<3",
"💕", "<3",
"💞", "<3",
"💘", "<3",
"💓", "<3",
"💚", "<3",
"💙", "<3",
"💔", "</3",
"😱", "D:",
"😮", ":O",
"😝", ":P",
"😍", ":x",
"😢", ":(",
"💯", ":100:",
"🔥", ":fire:",
"😉", ";)",
"😴", ":zzz:",
"💤", ":zzz:",
},
"Korean": &KoreanTranslit{},
"Chinese": &ChineseTranslit{},
"Armenian": &ArmenianTranslit{},
}