Compare commits

...

7 commits

Author SHA1 Message Date
Simon Vieille 82f5db3890 Merge branch 'feature/config' into develop 2023-08-26 11:42:03 +02:00
Simon Vieille aa4f359349
update rice file 2023-08-26 11:42:00 +02:00
Simon Vieille 9339cdbd93
run the server using the configuration 2023-08-26 11:41:43 +02:00
Simon Vieille d1cefa4131
fix template using new configuration 2023-08-26 11:41:23 +02:00
Simon Vieille 7915a0dbe4
change configuration format 2023-08-26 11:40:58 +02:00
Simon Vieille 7128ba2425
[wip] fix pointer 2023-08-25 11:23:44 +02:00
Simon Vieille a3ee86f81b
add config 2023-08-25 11:19:54 +02:00
12 changed files with 345 additions and 212 deletions

View file

@ -19,4 +19,4 @@ build: deps
CGO_ENABLED=$(CGO_ENABLED) GOARCH=amd64 GOOS=linux $(CC) $(CFLAGS) -o $(BUILD_DIR)/$(LINUX_BIN) -ldflags="$(LDFLAGS)" -gcflags="$(GCFLAGS)" -asmflags="$(ASMFLAGS)"
watch:
gowatch -o build/app-live-linux-amd64
gowatch -o build/app-live-linux-amd64 -args='./config.yaml'

Binary file not shown.

57
config.go Normal file
View file

@ -0,0 +1,57 @@
package main
import (
"fmt"
"gopkg.in/yaml.v3"
"os"
)
type ServerAuthConfig struct {
Username string `yaml:"username"`
Password string `yaml:"password"`
}
type ServerConfig struct {
Listen string `yaml:"listen"`
Auth ServerAuthConfig `yaml:"auth"`
}
type RemoteItemConfigItem struct {
Label string `yaml:"label"`
Message string `yaml:"message"`
}
type RemoteItemConfig struct {
Type string `yaml:"type"`
Label string `yaml:"label"`
Items []RemoteItemConfigItem `yaml:"items"`
}
type RemoteItem struct {
Label string `yaml:"label"`
Items []RemoteItemConfig `yaml:"items"`
}
type Config struct {
Server ServerConfig `yaml:"server"`
Remote []RemoteItem `yaml:"remote"`
}
func createConfigFromFile(file string) (Config, error) {
data, err := os.ReadFile(file)
value := Config{}
if err != nil {
return value, err
}
err = yaml.Unmarshal(data, &value)
if err != nil {
return value, err
}
fmt.Printf("%+v\n", value)
return value, nil
}

104
config.yaml Normal file
View file

@ -0,0 +1,104 @@
server:
auth:
username: admin
password: admin
listen: 0.0.0.0:4000
remote:
- label: Keyboard
items:
- type: live_text
label: Live text
- type: text
label: Text
- type: keys
label: Keys
- type: shortcuts
label: Shortcuts
- label: i3
items:
- type: messages
label: Workspaces
items:
- label: '1. DBL'
message: '{"type":"workspace","value":"1. DBL"}'
- label: '2. WWW'
message: '{"type":"workspace","value":"2. WWW"}'
- label: '3. MAIL'
message: '{"type":"workspace","value":"3. MAIL"}'
- label: '4. IM'
message: '{"type":"workspace","value":"4. IM"}'
- label: '5'
message: '{"type":"workspace","value":"5"}'
- label: '6. MEDIA'
message: '{"type":"workspace","value":"6. MEDIA"}'
- label: '7. DEV'
message: '{"type":"workspace","value":"7. DEV"}'
- label: '8. DEV'
message: '{"type":"workspace","value":"8. DEV"}'
- label: '9. DEV'
message: '{"type":"workspace","value":"9. DEV"}'
- label: '10'
message: '{"type":"workspace","value":"10"}'
- label: '11'
message: '{"type":"workspace","value":"11"}'
- label: '12'
message: '{"type":"workspace","value":"12"}'
- type: messages
label: Software
items:
- label: 'alacritty'
message: '{"type":"messages","value":[{"type":"keys","value":"win,d"},{"type":"text","value":"alacritty"},{"type":"key","value":"enter"}]}'
- label: 'dmenu'
message: '{"type":"keys","value":"win,d"}'
- type: messages
label: UI
items:
- label: 'win+z'
message: '{"type":"keys","value":"win,z"}'
- label: 'win+e'
message: '{"type":"keys","value":"win,e"}'
- label: 'zp'
message: '{"type":"messages","value":[{"type":"text","value":"zp"},{"type":"key","value":"enter"}]}'
- label: 'zm'
message: '{"type":"messages","value":[{"type":"text","value":"zm"},{"type":"key","value":"enter"}]}'
- type: messages
label: Screensaver
items:
- label: 'ON'
message: '{"type":"messages","value":[{"type":"keys","value":"win,d"},{"type":"text","value":"no-screensaver"},{"type":"key","value":"enter"}]}'
- label: 'OFF'
message: '{"type":"messages","value":[{"type":"keys","value":"win,d"},{"type":"text","value":"pkill no-screensaver"},{"type":"key","value":"enter"}]}'
- type: messages
label: Movie
items:
- label: 'v;mll'
message: '{"type":"messages","value":[{"type":"text","value":"v;mll"},{"type":"key","value":"enter"}]}'
- label: 'mup'
message: '{"type":"messages","value":[{"type":"text","value":"mup"},{"type":"key","value":"enter"}]}'
- label: 'mug'
message: '{"type":"messages","value":[{"type":"text","value":"mug"},{"type":"key","value":"enter"}]}'
- label: 'mup 1'
message: '{"type":"messages","value":[{"type":"text","value":"mup 1"},{"type":"key","value":"enter"}]}'
- label: 'mug 1'
message: '{"type":"messages","value":[{"type":"text","value":"mug 1"},{"type":"key","value":"enter"}]}'
- label: Mouse
items:
- type: mouse
- label: Media
items:
- type: spotify
label: Spotify
- type: volume
label: Volume
- label: Desktop
items:
- type: screenshot
label: Screenshot
- type: live_video
label: Live video

1
go.mod
View file

@ -21,4 +21,5 @@ require (
golang.org/x/sys v0.11.0 // indirect
golang.org/x/text v0.12.0 // indirect
golang.org/x/time v0.3.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

2
go.sum
View file

@ -65,3 +65,5 @@ golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -5,6 +5,12 @@ import (
"net/http"
)
func homeController(c echo.Context) error {
return c.HTML(http.StatusOK, view("views/page/home.html", nil))
type HomeViewParams struct {
Config Config
}
func homeController(c echo.Context) error {
return c.HTML(http.StatusOK, view("views/page/home.html", HomeViewParams{
Config: config,
}))
}

34
main.go
View file

@ -3,6 +3,7 @@ package main
import (
"crypto/subtle"
"embed"
"fmt"
rice "github.com/GeertJohan/go.rice"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
@ -16,41 +17,48 @@ var (
//go:embed static
staticFiles embed.FS
//go:embed views/layout views/page
views embed.FS
views embed.FS
config Config
)
func main() {
e := echo.New()
e.HideBanner = true
RI3_USERNAME := os.Getenv("RI3_USERNAME")
RI3_PASSWORD := os.Getenv("RI3_PASSWORD")
RI3_BIND := os.Getenv("RI3_BIND")
if RI3_BIND == "" {
RI3_BIND = "0.0.0.0:4000"
if len(os.Args) != 2 {
fmt.Errorf("Configuration required!")
os.Exit(1)
}
assetHandler := http.FileServer(rice.MustFindBox("static").HTTPBox())
value, err := createConfigFromFile(os.Args[1])
if err != nil {
fmt.Printf("Configuration error:")
fmt.Printf("%+v\n", err)
os.Exit(1)
}
config = value
e.Use(middleware.BasicAuth(func(username, password string, c echo.Context) (bool, error) {
if RI3_USERNAME == "" && RI3_PASSWORD == "" {
if config.Server.Auth.Username == "" && config.Server.Auth.Password == "" {
return true, nil
}
isValidUsername := subtle.ConstantTimeCompare([]byte(username), []byte(RI3_USERNAME)) == 1
isValidPassword := subtle.ConstantTimeCompare([]byte(password), []byte(RI3_PASSWORD)) == 1
isValidUsername := subtle.ConstantTimeCompare([]byte(username), []byte(config.Server.Auth.Username)) == 1
isValidPassword := subtle.ConstantTimeCompare([]byte(password), []byte(config.Server.Auth.Password)) == 1
if isValidUsername && isValidPassword {
return true, nil
}
return false, nil
}))
e.GET("/", echo.WrapHandler(assetHandler))
assetHandler := http.FileServer(rice.MustFindBox("static").HTTPBox())
e.GET("/static/*", echo.WrapHandler(http.StripPrefix("/static/", assetHandler)))
e.GET("/", homeController)
e.GET("/ws", wsController)
e.Logger.Fatal(e.Start(RI3_BIND))
e.Logger.Fatal(e.Start(config.Server.Listen))
}

File diff suppressed because one or more lines are too long

View file

@ -58,9 +58,12 @@ a {
}
#pointer {
height: calc(100vh - 80px);
height: calc(100vh - 33px - 38px);
top: calc(33px + 38px);
margin: auto;
background: #ccc;
position: absolute;
width: 100%;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
@ -78,24 +81,17 @@ a {
right: 0;
}
.fullscreen #scrollbar {
height: calc(100vh - 150px);
}
.fullscreen #pointer {
height: calc(100vh - 150px);
}
#pane-pointer .form-group {
padding: 0;
margin: 0;
}
#pointer-buttons {
position: absolute;
margin-top: -42px;
width: 100%;
z-index: 110;
position: fixed;
bottom: 0;
}
#pointer-buttons .btn {

View file

@ -257,11 +257,9 @@ var documentHashHandler = function() {
var hash = window.location.hash;
if (hash) {
$(hash).show();
$('a[href="' + hash + '"]').addClass('active');
$('a[href="' + hash + '"]').click();
} else {
$('#pane-keyboard').show();
$('#nav a').first().addClass('active');
$('#nav > li:first-child a').click();
}
}
@ -294,10 +292,10 @@ var addListeners = function() {
}
var bootstrap = function() {
documentHashHandler();
shortcutsSpecialKeysOnChangeHandler();
createWebSocketConnection();
addListeners();
documentHashHandler();
}
$(function() {

View file

@ -4,21 +4,11 @@
<div class="row no-margin">
<div class="col-12 no-padding">
<ul class="nav nav-pills nav-fill" id="nav">
<li class="nav-item">
<a class="nav-link no-radius" href="#pane-keyboard">KEYBOARD</a>
</li>
<li class="nav-item">
<a class="nav-link no-radius" href="#pane-i3">I3</a>
</li>
<li class="nav-item">
<a class="nav-link no-radius" href="#pane-pointer">MOUSE</a>
</li>
<li class="nav-item">
<a class="nav-link no-radius" href="#pane-media">MEDIA</a>
</li>
<li class="nav-item">
<a class="nav-link no-radius" href="#pane-desktop">DESKTOP</a>
</li>
{{range $key, $tab := .Config.Remote}}
<li class="nav-item">
<a class="nav-link no-radius" href="#tab-{{$key}}">{{$tab.Label}}</a>
</li>
{{end}}
<li class="nav-item">
<a class="nav-link no-radius btn-fullscreen" data-target="html" href="#">💻</a>
</li>
@ -28,76 +18,138 @@
</div>
<div class="container-fluid">
<div id="pane-keyboard" class="pane">
<div class="row">
<div class="col-12">
<p class="legend">Live text</p>
</div>
<div class="form-group col-12">
<input type="text" class="form-control live-text" name="text">
</div>
{{range $key, $tab := .Config.Remote}}
<div id="tab-{{$key}}" class="pane">
{{range $key2, $value := $tab.Items}}
<div class="row" {{if eq $value.Type "mouse"}} id="pane-pointer"{{end}}>
{{if ne $value.Label ""}}
<div class="col-12">
<p class="legend">{{$value.Label}}</p>
</div>
{{end}}
{{if eq $value.Type "live_text"}}
<div class="form-group col-12">
<input type="text" class="form-control live-text" name="text">
</div>
{{end}}
{{if eq $value.Type "text"}}
<div class="form-group col-6">
<input type="text" class="form-control" id="text">
</div>
<div class="col-6">
<button type="button" id="text-send" class="btn btn-primary">Send</button>
<button type="button" id="text-clear" class="btn btn-secondary">Clear</button>
</div>
{{end}}
{{if eq $value.Type "keys"}}
<div class="col-12">
<button type="button" data-msg='{"type":"key","value":"left"}' class="btn btn-secondary"></button>
<button type="button" data-msg='{"type":"key","value":"up"}' class="btn btn-secondary"></button>
<button type="button" data-msg='{"type":"key","value":"down"}' class="btn btn-secondary"></button>
<button type="button" data-msg='{"type":"key","value":"right"}' class="btn btn-secondary"></button>
</div>
<div class="line col-12"></div>
<div class="col-12">
<button type="button" data-msg='{"type":"key","value":"escape"}' class="btn btn-secondary">Escape</button>
<button type="button" data-msg='{"type":"key","value":"tab"}' class="btn btn-secondary">TAB</button>
<button type="button" data-msg='{"type":"key","value":"backspace"}' class="btn btn-secondary">Backspace</button>
<button type="button" data-msg='{"type":"key","value":"enter"}' class="btn btn-secondary">Enter</button>
</div>
{{end}}
{{if eq $value.Type "shortcuts"}}
<div class="col-9" id="shortcuts_special_keys">
<label class="btn btn-secondary" for="shortcuts_special_key_ctrl">
<input type="checkbox" value="ctrl" id="shortcuts_special_key_ctrl">
ctrl
</label>
<label class="btn btn-secondary" for="shortcuts_special_key_shift">
<input type="checkbox" value="shift" id="shortcuts_special_key_shift">
shift
</label>
<label class="btn btn-secondary" for="shortcuts_special_key_alt">
<input type="checkbox" value="alt" id="shortcuts_special_key_alt">
alt
</label>
<label class="btn btn-secondary" for="shortcuts_special_key_win">
<input type="checkbox" value="win" id="shortcuts_special_key_win">
win
</label>
</div>
<div class="form-group col-3">
<input type="text" id="shortcut-key" class="form-control" name="shortcuts_char">
</div>
<div class="col-12">
<button type="button" id="shortcut-send" class="btn btn-primary">Send</button>
<button type="button" id="shortcut-clear" class="btn btn-secondary">Clear</button>
</div>
{{end}}
{{if eq $value.Type "spotify"}}
<div class="col-12">
<button type="button" data-msg='{"type":"media","value":"playpause"}' class="btn btn-secondary">Play/Pause</button>
<button type="button" data-msg='{"type":"media","value":"next"}' class="btn btn-secondary">Next</button>
<button type="button" data-msg='{"type":"media","value":"prev"}' class="btn btn-secondary">Previous</button>
</div>
{{end}}
{{if eq $value.Type "volume"}}
<div class="col-12">
<button type="button" data-msg='{"type":"volume","value":"0"}' class="btn btn-secondary">0%</button>
<button type="button" data-msg='{"type":"volume","value":"25"}' class="btn btn-secondary">25%</button>
<button type="button" data-msg='{"type":"volume","value":"50"}' class="btn btn-secondary">50%</button>
<button type="button" data-msg='{"type":"volume","value":"75"}' class="btn btn-secondary">75%</button>
<button type="button" data-msg='{"type":"volume","value":"100"}' class="btn btn-secondary">100%</button>
</div>
<div class="line col-12"></div>
<div class="col-12">
<button type="button" data-msg='{"type":"volume","value":"down"}' class="btn btn-secondary">-</button>
<button type="button" data-msg='{"type":"volume","value":"up"}' class="btn btn-secondary">+</button>
</div>
{{end}}
{{if eq $value.Type "mouse"}}
<div class="form-group col-12">
<input type="text" class="form-control live-text" placeholder="Live text" name="text">
</div/>
<div id="scrollbar"></div>
<div id="pointer"></div>
<div id="pointer-buttons">
<button type="button" data-msg='{"type":"pointer","click":"left"}' class="btn btn-primary no-radius col-5">&nbsp;</button><button type="button no-margin" data-msg='{"type":"pointer","click":"middle"}' class="btn btn-secondary no-radius col-2">&nbsp;</button><button type="button no-margin" data-msg='{"type":"pointer","click":"right"}' class="btn btn-primary no-radius col-5">&nbsp;</button>
</div>
{{end}}
{{if eq $value.Type "screenshot"}}
<div class="col-12">
<button type="button" data-msg='{"type":"screenshot","quality":"hq"}' class="btn btn-secondary">Screenshot HQ</button>
<button type="button" data-msg='{"type":"screenshot","quality":"lq"}' class="btn btn-secondary">Screenshot LQ</button>
</div>
{{end}}
{{if eq $value.Type "live_video"}}
<div class="col-12">
<button type="button" id="live-hq" class="btn btn-secondary">Live HQ</button>
<button type="button" id="live-lq" class="btn btn-secondary">Live LQ</button>
<div id="screenshot"><img class="btn-fullscreen" data-target="#screenshot img" src="data:image/png; base64, iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gIJDjc3srQk8gAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAADElEQVQI12P48+cPAAXsAvVTWDc6AAAAAElFTkSuQmCC"></div>
</div>
{{end}}
{{if eq $value.Type "messages"}}
<div class="col-12">
{{range $key3, $item := $value.Items}}
<button type="button" data-msg='{{$item.Message}}' class="btn btn-secondary mb-1">{{$item.Label}}</button>
{{end}}
</div>
{{end}}
<div class="line col-12"></div>
</div>
{{end}}
</div>
<div class="row">
<div class="col-12">
<p class="legend">TEXT</p>
</div>
<div class="form-group col-6">
<input type="text" class="form-control" id="text">
</div>
<div class="col-6">
<button type="button" id="text-send" class="btn btn-primary">Send</button>
<button type="button" id="text-clear" class="btn btn-secondary">Clear</button>
</div>
</div>
<div class="row">
<div class="col-12">
<p class="legend">Keys</p>
</div>
<div class="col-12">
<button type="button" data-msg='{"type":"key","value":"left"}' class="btn btn-secondary"></button>
<button type="button" data-msg='{"type":"key","value":"up"}' class="btn btn-secondary"></button>
<button type="button" data-msg='{"type":"key","value":"down"}' class="btn btn-secondary"></button>
<button type="button" data-msg='{"type":"key","value":"right"}' class="btn btn-secondary"></button>
</div>
<div class="line col-12"></div>
<div class="col-12">
<button type="button" data-msg='{"type":"key","value":"escape"}' class="btn btn-secondary">Escape</button>
<button type="button" data-msg='{"type":"key","value":"tab"}' class="btn btn-secondary">TAB</button>
<button type="button" data-msg='{"type":"key","value":"backspace"}' class="btn btn-secondary">Backspace</button>
<button type="button" data-msg='{"type":"key","value":"enter"}' class="btn btn-secondary">Enter</button>
</div>
</div>
<div class="row">
<div class="col-12">
<p class="legend">Shortcuts</p>
</div>
<div class="col-9" id="shortcuts_special_keys">
<label class="btn btn-secondary" for="shortcuts_special_key_ctrl">
<input type="checkbox" value="ctrl" id="shortcuts_special_key_ctrl">
ctrl
</label>
<label class="btn btn-secondary" for="shortcuts_special_key_shift">
<input type="checkbox" value="shift" id="shortcuts_special_key_shift">
shift
</label>
<label class="btn btn-secondary" for="shortcuts_special_key_alt">
<input type="checkbox" value="alt" id="shortcuts_special_key_alt">
alt
</label>
<label class="btn btn-secondary" for="shortcuts_special_key_win">
<input type="checkbox" value="win" id="shortcuts_special_key_win">
win
</label>
</div>
<div class="form-group col-3">
<input type="text" id="shortcut-key" class="form-control" name="shortcuts_char">
</div>
<div class="col-12">
<button type="button" id="shortcut-send" class="btn btn-primary">Send</button>
<button type="button" id="shortcut-clear" class="btn btn-secondary">Clear</button>
</div>
</div>
</div>
{{end}}
<div id="pane-i3" class="pane">
<div class="row">
@ -121,97 +173,6 @@
<button type="button" data-msg='{"type":"workspace","value":"12"}' class="btn btn-secondary btn-sm">12</button>
</div>
</div>
<div class="row">
<div class="col-12">
<p class="legend">Software</p>
</div>
<div class="col-12">
<button type="button" data-msg='{"type":"messages","value":[{"type":"keys","value":"win,d"},{"type":"text","value":"urxvt"},{"type":"key","value":"enter"}]}' class="btn btn-secondary">urxvt</button>
<button type="button" data-msg='{"type":"keys","value":"win,d"}' class="btn btn-secondary">dmenu</button>
</div>
<div class="col-12">
<p class="legend">UI</p>
</div>
<div class="col-12">
<button type="button" data-msg='{"type":"keys","value":"win,z"}' class="btn btn-secondary">win+z</button>
<button type="button" data-msg='{"type":"keys","value":"win,x"}' class="btn btn-secondary">win+x</button>
<button type="button" data-msg='{"type":"keys","value":"win,c"}' class="btn btn-secondary">win+c</button>
<button type="button" data-msg='{"type":"messages","value":[{"type":"text","value":"zp"},{"type":"key","value":"enter"}]}' class="btn btn-secondary">zp</button>
<button type="button" data-msg='{"type":"messages","value":[{"type":"text","value":"zm"},{"type":"key","value":"enter"}]}' class="btn btn-secondary">zm</button>
</div>
<div class="line col-12"></div>
<div class="col-12">
<button type="button" data-msg='{"type":"messages","value":[{"type":"keys","value":"win,d"},{"type":"text","value":"no-screensaver"},{"type":"key","value":"enter"}]}' class="btn btn-secondary">no-screensaver[on]</button>
<button type="button" data-msg='{"type":"messages","value":[{"type":"keys","value":"win,d"},{"type":"text","value":"pkill no-screensaver"},{"type":"key","value":"enter"}]}' class="btn btn-secondary">no-screensaver[off]</button>
</div>
<div class="col-12">
<p class="legend">Movie</p>
</div>
<div class="col-12">
<button type="button" data-msg='{"type":"messages","value":[{"type":"text","value":"v;mll"},{"type":"key","value":"enter"}]}' class="btn btn-secondary">v;mll</button>
<button type="button" data-msg='{"type":"messages","value":[{"type":"text","value":"rt -l"},{"type":"key","value":"enter"}]}' class="btn btn-secondary">rt -l</button>
</div>
<div class="line col-12"></div>
<div class="col-12">
<button type="button" data-msg='{"type":"messages","value":[{"type":"text","value":"mug"},{"type":"key","value":"enter"}]}' class="btn btn-secondary">mug</button>
<button type="button" data-msg='{"type":"messages","value":[{"type":"text","value":"mup"},{"type":"key","value":"enter"}]}' class="btn btn-secondary">mup</button>
<button type="button" data-msg='{"type":"messages","value":[{"type":"text","value":"mug 1"},{"type":"key","value":"enter"}]}' class="btn btn-secondary">mug 1</button>
<button type="button" data-msg='{"type":"messages","value":[{"type":"text","value":"mup 1"},{"type":"key","value":"enter"}]}' class="btn btn-secondary">mup 1</button>
</div>
</div>
</div>
<div class="row pane" id="pane-pointer">
<div class="form-group col-12">
<input type="text" class="form-control live-text" placeholder="Live text" name="text">
</div/>
<div id="scrollbar"></div>
<div id="pointer"></div>
<div id="pointer-buttons">
<button type="button" data-msg='{"type":"pointer","click":"left"}' class="btn btn-primary no-radius col-5">&nbsp;</button><button type="button no-margin" data-msg='{"type":"pointer","click":"middle"}' class="btn btn-secondary no-radius col-2">&nbsp;</button><button type="button no-margin" data-msg='{"type":"pointer","click":"right"}' class="btn btn-primary no-radius col-5">&nbsp;</button>
</div>
</div>
<div class="row pane" id="pane-media">
<div class="col-12">
<p class="legend">Spotify</p>
</div>
<div class="col-12">
<button type="button" data-msg='{"type":"media","value":"playpause"}' class="btn btn-secondary">Play/Pause</button>
<button type="button" data-msg='{"type":"media","value":"next"}' class="btn btn-secondary">Next</button>
<button type="button" data-msg='{"type":"media","value":"prev"}' class="btn btn-secondary">Previous</button>
</div>
<div class="col-12">
<p class="legend">Volume</p>
</div>
<div class="col-12">
<button type="button" data-msg='{"type":"volume","value":"0"}' class="btn btn-secondary">0%</button>
<button type="button" data-msg='{"type":"volume","value":"25"}' class="btn btn-secondary">25%</button>
<button type="button" data-msg='{"type":"volume","value":"50"}' class="btn btn-secondary">50%</button>
<button type="button" data-msg='{"type":"volume","value":"75"}' class="btn btn-secondary">75%</button>
<button type="button" data-msg='{"type":"volume","value":"100"}' class="btn btn-secondary">100%</button>
</div>
<div class="line col-12"></div>
<div class="col-12">
<button type="button" data-msg='{"type":"volume","value":"down"}' class="btn btn-secondary">-</button>
<button type="button" data-msg='{"type":"volume","value":"up"}' class="btn btn-secondary">+</button>
</div>
</div>
<div class="row pane" id="pane-desktop">
<div class="col-12">
<p class="legend">Desktop</p>
</div>
<div class="col-12">
<button type="button" data-msg='{"type":"screenshot","quality":"hq"}' class="btn btn-sm btn-secondary">Screenshot HQ</button>
<button type="button" data-msg='{"type":"screenshot","quality":"lq"}' class="btn btn-sm btn-secondary">Screenshot LQ</button>
<button type="button" id="live-hq" class="btn btn-sm btn-secondary">Live HQ</button>
<button type="button" id="live-lq" class="btn btn-sm btn-secondary">Live LQ</button>
<div id="screenshot"><img class="btn-fullscreen" data-target="#screenshot img" src="data:image/png; base64, iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gIJDjc3srQk8gAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAADElEQVQI12P48+cPAAXsAvVTWDc6AAAAAElFTkSuQmCC"></div>
</div>
<div class="col-12">
</div>
</div>
</div>
{{end}}