From 98705066e272b171787f2fdea3d149746dacda36 Mon Sep 17 00:00:00 2001 From: William Petit Date: Tue, 16 Jan 2024 09:27:04 +0100 Subject: [PATCH] feat: embed optional apps in player server --- .env.dist | 3 +- Makefile | 7 +- apps/home/app.js | 19 +++++ apps/home/index.html | 16 ++++ apps/home/manifest.json | 10 +++ apps/home/style.css | 23 +++++ apps/lib/manifest.json | 3 + apps/lib/style.css | 77 +++++++++++++++++ apps/remote-control/app.js | 66 +++++++++++++++ apps/remote-control/index.html | 35 ++++++++ apps/remote-control/manifest.json | 10 +++ apps/screen-sharing/app.js | 116 ++++++++++++++++++++++++++ apps/screen-sharing/index.html | 18 ++++ apps/screen-sharing/manifest.json | 10 +++ cmd/mobile/main.go | 9 +- default_apps.go | 70 ++++++++++++++++ go.mod | 2 + go.sum | 2 + internal/command/player/run.go | 21 ++++- modd.conf | 1 + pkg/server/apps.go | 63 ++++++++++++++ pkg/server/http.go | 28 +++++++ pkg/server/options.go | 40 +++++++-- pkg/server/server.go | 20 +++-- pkg/server/templates/idle.html.gotmpl | 16 +++- 25 files changed, 666 insertions(+), 19 deletions(-) create mode 100644 apps/home/app.js create mode 100644 apps/home/index.html create mode 100644 apps/home/manifest.json create mode 100644 apps/home/style.css create mode 100644 apps/lib/manifest.json create mode 100644 apps/lib/style.css create mode 100644 apps/remote-control/app.js create mode 100644 apps/remote-control/index.html create mode 100644 apps/remote-control/manifest.json create mode 100644 apps/screen-sharing/app.js create mode 100644 apps/screen-sharing/index.html create mode 100644 apps/screen-sharing/manifest.json create mode 100644 default_apps.go create mode 100644 pkg/server/apps.go diff --git a/.env.dist b/.env.dist index df10f3e..f8a5fca 100644 --- a/.env.dist +++ b/.env.dist @@ -1,2 +1,3 @@ ARCAST_DESKTOP_ADDITIONAL_CHROME_ARGS= -ARCAST_DESKTOP_INSTANCE_ID= \ No newline at end of file +ARCAST_DESKTOP_INSTANCE_ID= +ARCAST_DESKTOP_APPS=true \ No newline at end of file diff --git a/Makefile b/Makefile index ca19aca..c0e2d4e 100644 --- a/Makefile +++ b/Makefile @@ -36,9 +36,14 @@ build-client: deps ## Build executable build-android: tools/gogio/bin/gogio deps ## Build executable mkdir -p dist - GOOS=android CGO_CFLAGS="-I${JDK_PATH}/include -I${JDK_PATH}/include/linux -w" tools/gogio/bin/gogio -target android -buildmode archive -o android/app/libs/mobile.aar -x ./cmd/mobile + CGO_ENABLED=1 GOOS=android CGO_CFLAGS="-I${JDK_PATH}/include -I${JDK_PATH}/include/linux -w" tools/gogio/bin/gogio -target android -buildmode archive -o android/app/libs/mobile.aar -x ./cmd/mobile ( cd android && ./gradlew assembleDebug ) +release-android: tools/gogio/bin/gogio deps ## Build executable + mkdir -p dist + CGO_ENABLED=1 GOOS=android CGO_CFLAGS="-I${JDK_PATH}/include -I${JDK_PATH}/include/linux -w" tools/gogio/bin/gogio -target android -buildmode archive -o android/app/libs/mobile.aar -x ./cmd/mobile + ( cd android && ./gradlew assemble ) + install-android: build-android adb install android/app/build/outputs/apk/debug/app-debug.apk adb shell monkey -p com.cadoles.arcast_player -c android.intent.category.LAUNCHER 1 diff --git a/apps/home/app.js b/apps/home/app.js new file mode 100644 index 0000000..d164015 --- /dev/null +++ b/apps/home/app.js @@ -0,0 +1,19 @@ +fetch("/api/v1/apps") + .then((res) => res.json()) + .then((res) => { + const defaultApp = res.data.defaultApp; + const apps = res.data.apps; + + const container = document.createElement("div"); + container.className = "container"; + apps.forEach((app) => { + if (app.id === defaultApp || app.hidden) return; + const appLink = document.createElement("a"); + appLink.className = "app-link"; + appLink.href = `/apps/${app.id}/`; + appLink.innerText = app.title["fr"]; + container.appendChild(appLink); + }); + + document.getElementById("main").replaceWith(container); + }); diff --git a/apps/home/index.html b/apps/home/index.html new file mode 100644 index 0000000..2671835 --- /dev/null +++ b/apps/home/index.html @@ -0,0 +1,16 @@ + + + + + + Arcast - Home + + + + + +
+

Loading...

+
+ + \ No newline at end of file diff --git a/apps/home/manifest.json b/apps/home/manifest.json new file mode 100644 index 0000000..c132d33 --- /dev/null +++ b/apps/home/manifest.json @@ -0,0 +1,10 @@ +{ + "title": { + "fr": "Accueil", + "en": "Home" + }, + "description": { + "fr": "Voir la liste des applications", + "en": "See apps list" + } +} \ No newline at end of file diff --git a/apps/home/style.css b/apps/home/style.css new file mode 100644 index 0000000..dc151dd --- /dev/null +++ b/apps/home/style.css @@ -0,0 +1,23 @@ +.mt { + margin-top: 1em; + display: block; +} + +.app-link { + display: block; + background: white; + border-radius: 5px; + width: 100px; + height: 100px; + box-shadow: 1px 1px 3px #ccc; + color: #333; + text-decoration: none; + text-align: center; + padding-top: 30px; +} + +.app-link:hover { + background-color: #abdbdb; + box-shadow: 1px 1px 3px #aaa; + text-shadow: 1px 1px white; +} \ No newline at end of file diff --git a/apps/lib/manifest.json b/apps/lib/manifest.json new file mode 100644 index 0000000..9279f8f --- /dev/null +++ b/apps/lib/manifest.json @@ -0,0 +1,3 @@ +{ + "hidden": true +} \ No newline at end of file diff --git a/apps/lib/style.css b/apps/lib/style.css new file mode 100644 index 0000000..4121a75 --- /dev/null +++ b/apps/lib/style.css @@ -0,0 +1,77 @@ +html { + box-sizing: border-box; + font-size: 16px; + width: 100%; + height: 100%; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + width: 100%; + height: 100%; + background-color: #e1e1e1; +} + +*, +*:before, +*:after { + box-sizing: inherit; +} + +ol, +ul { + list-style: none; +} + +body, +h1, +h2, +h3, +h4, +h5, +h6, +p, +ol, +ul { + margin: 0; + padding: 0; + font-weight: normal; +} + +.container { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + flex-wrap: wrap; + gap: 10px; +} + +.panel { + display: block; + background-color: #fff; + border-radius: 5px; + padding: 10px 20px; + box-shadow: 2px 2px #3333331d; +} + +.panel p, .panel ul { + margin-top: 10px; +} + +.text-centered { + text-align: center; +} + +.text-italic { + font-style: italic; +} + +.text-small { + font-size: 0.8em; +} + +.fullwidth { + width: 100%; +} \ No newline at end of file diff --git a/apps/remote-control/app.js b/apps/remote-control/app.js new file mode 100644 index 0000000..d1efeb3 --- /dev/null +++ b/apps/remote-control/app.js @@ -0,0 +1,66 @@ +function main() { + refreshStatus(); + setInterval(refreshStatus, 10000) +} + +function refreshStatus() { + return fetch("/api/v1/status") + .then(res => res.json()) + .then(res => { + const newStatus = document.createElement("tr") + newStatus.id = "status" + + let td = document.createElement("td") + td.innerText = res.data.status + newStatus.appendChild(td) + + td = document.createElement("td") + td.innerText = res.data.title + newStatus.appendChild(td) + + td = document.createElement("td") + td.innerText = res.data.url + document.getElementById("url-input").placeholder = res.data.url + newStatus.appendChild(td) + + document.getElementById("status").replaceWith(newStatus) + }) + .catch(err => { + console.error(err); + window.location.reload() + }) +} + +function castUrl() { + const urlInput = document.getElementById("url-input") + const url = urlInput.value + if (url === "") return Promise.resolve() + return fetch("/api/v1/cast", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + "url": url, + }) + }) + .then(res => res.json()) + .then(() => refreshStatus()) + .then(() => { + urlInput.value = "" + }) +} + +function reset() { + const urlInput = document.getElementById("url-input") + return fetch("/api/v1/cast", { + method: "DELETE", + }) + .then(res => res.json()) + .then(() => refreshStatus()) + .then(() => { + urlInput.value = "" + }) +} + +main() \ No newline at end of file diff --git a/apps/remote-control/index.html b/apps/remote-control/index.html new file mode 100644 index 0000000..990ffbb --- /dev/null +++ b/apps/remote-control/index.html @@ -0,0 +1,35 @@ + + + + + + Arcast - Remote Control + + + + +
+
+

Remote control

+ + + + + + + + + + + + + +
StatusTitleURL
Refreshing...
+ + + + +
+
+ + \ No newline at end of file diff --git a/apps/remote-control/manifest.json b/apps/remote-control/manifest.json new file mode 100644 index 0000000..65837ea --- /dev/null +++ b/apps/remote-control/manifest.json @@ -0,0 +1,10 @@ +{ + "title": { + "fr": "Contrôle à distance", + "en": "Remote control" + }, + "description": { + "fr": "Contrôler l'afficheur numérique", + "en": "Control the cast player" + } +} \ No newline at end of file diff --git a/apps/screen-sharing/app.js b/apps/screen-sharing/app.js new file mode 100644 index 0000000..6184c9e --- /dev/null +++ b/apps/screen-sharing/app.js @@ -0,0 +1,116 @@ +const displayMediaOptions = { + video: { + displaySurface: "browser", + }, + audio: { + suppressLocalAudioPlayback: false, + }, + preferCurrentTab: false, + selfBrowserSurface: "exclude", + systemAudio: "include", + surfaceSwitching: "include", + monitorTypeSurfaces: "include", +}; + +const peerConnection = new RTCPeerConnection(); +const broadcast = new BroadcastChannel("screen_sharing"); + +function main() { + peerConnection.onconnectionstatechange = (evt) => { + console.log("Connection state changed", evt) + } + + peerConnection.oniceconnectionstatechange = () => { + console.log('ICE state: ',peerConn.iceConnectionState); + } + + peerConnection.ontrack = function (e) { + var vid = document.createElement("video"); + document.body.appendChild(vid); + vid.srcObject = e.streams[0]; + }; + + broadcast.onmessage = (evt) => { + const message = JSON.parse(evt.data); + console.log("Received message", message) + if (message.type === "answer") { + peerConnection.setRemoteDescription( + new RTCSessionDescription(message.payload), + () => {}, + error, + ); + } + }; + + const urlParams = new URLSearchParams(window.location.search); + const b64Offer = urlParams.get('offer'); + if (!b64Offer) return; + + const offer = JSON.parse(atob(b64Offer)); + peerConnection.setRemoteDescription( + new RTCSessionDescription(offer), + () => { + peerConnection.createAnswer(function (answer) { + peerConnection.setLocalDescription( + answer, + function () { + broadcast.postMessage(JSON.stringify({ + type: 'answer', + payload: answer + })) + }, + error, + ); + }, error); + }, + error, + ); +} + +function shareScreen() { + return navigator.mediaDevices + .getDisplayMedia(displayMediaOptions) + .then(captureStream => { + peerConnection.addStream(captureStream); + + peerConnection.createOffer(function (offer) { + peerConnection.setLocalDescription( + offer, + () => { + const b64Offer = btoa(JSON.stringify(offer)) + const url = "https://127.0.0.1:" + window.location.port + "?offer=" + encodeURIComponent(b64Offer) + console.log(url) + fetch("/api/v1/cast", { + method: 'POST', + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + "url": url, + }) + }) + }, + error, + ); + }, error); + }) + .catch(err => { + console.error(err) + }) +} + +function endCall() { + var videos = document.getElementsByTagName("video"); + for (var i = 0; i < videos.length; i++) { + videos[i].pause(); + } + + peerConnection.close(); +} + +function error(err) { + console.error(err); + endCall(); +} + +main() \ No newline at end of file diff --git a/apps/screen-sharing/index.html b/apps/screen-sharing/index.html new file mode 100644 index 0000000..f813843 --- /dev/null +++ b/apps/screen-sharing/index.html @@ -0,0 +1,18 @@ + + + + + + Arcast - Screen sharing + + + + +
+
+

Screen sharing

+ +
+
+ + \ No newline at end of file diff --git a/apps/screen-sharing/manifest.json b/apps/screen-sharing/manifest.json new file mode 100644 index 0000000..a621713 --- /dev/null +++ b/apps/screen-sharing/manifest.json @@ -0,0 +1,10 @@ +{ + "title": { + "fr": "Partage d'écran", + "en": "Screen sharing" + }, + "description": { + "fr": "Partager son écran", + "en": "Share your screen" + } +} \ No newline at end of file diff --git a/cmd/mobile/main.go b/cmd/mobile/main.go index ca85e5a..ecbef83 100644 --- a/cmd/mobile/main.go +++ b/cmd/mobile/main.go @@ -8,6 +8,7 @@ import ( "os" "sync" + "forge.cadoles.com/arcad/arcast" "forge.cadoles.com/arcad/arcast/pkg/browser/gioui" "forge.cadoles.com/arcad/arcast/pkg/server" "gioui.org/app" @@ -77,7 +78,13 @@ func main() { logger.Fatal(ctx, "could not retrieve instance id", logger.CapturedE(errors.WithStack(err))) } - server := server.New(browser, server.WithInstanceID(instanceID)) + server := server.New( + browser, + server.WithInstanceID(instanceID), + server.WithAppsEnabled(true), + server.WithDefautApp("home"), + server.WithApps(arcast.DefaultApps...), + ) if err := server.Start(); err != nil { logger.Fatal(ctx, "could not start server", logger.CapturedE(errors.WithStack(err))) diff --git a/default_apps.go b/default_apps.go new file mode 100644 index 0000000..fb9a7a6 --- /dev/null +++ b/default_apps.go @@ -0,0 +1,70 @@ +package arcast + +import ( + "embed" + "encoding/json" + "io/fs" + "path/filepath" + + "forge.cadoles.com/arcad/arcast/pkg/server" + "github.com/pkg/errors" +) + +var ( + DefaultApps []server.App + //go:embed apps/** + appsFS embed.FS +) + +func init() { + defaultApps, err := loadApps("apps/*") + if err != nil { + panic(errors.WithStack(err)) + } + + DefaultApps = defaultApps +} + +func loadApps(dirPattern string) ([]server.App, error) { + apps := make([]server.App, 0) + + files, err := fs.Glob(appsFS, dirPattern) + if err != nil { + return nil, errors.WithStack(err) + } + + for _, f := range files { + stat, err := fs.Stat(appsFS, f) + if err != nil { + return nil, errors.WithStack(err) + } + + if !stat.IsDir() { + continue + } + + rawManifest, err := fs.ReadFile(appsFS, filepath.Join(f, "manifest.json")) + if err != nil { + return nil, errors.WithStack(err) + } + + var app server.App + + if err := json.Unmarshal(rawManifest, &app); err != nil { + return nil, errors.WithStack(err) + } + + app.ID = filepath.Base(f) + + fs, err := fs.Sub(appsFS, "apps/"+app.ID) + if err != nil { + return nil, errors.WithStack(err) + } + + app.FS = fs + + apps = append(apps, app) + } + + return apps, nil +} diff --git a/go.mod b/go.mod index 69a75ec..e0e7791 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,9 @@ go 1.21.4 require ( gioui.org v0.4.1 + github.com/davecgh/go-spew v1.1.1 github.com/gioui-plugins/gio-plugins v0.0.0-20230625001848-8f18aae6c91c + github.com/go-chi/cors v1.2.1 github.com/grandcat/zeroconf v1.0.1-0.20230119201135-e4f60f8407b1 github.com/jaevor/go-nanoid v1.3.0 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 49184b7..a4d6c9e 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,8 @@ github.com/gioui-plugins/gio-plugins v0.0.0-20230625001848-8f18aae6c91c h1:naFDa github.com/gioui-plugins/gio-plugins v0.0.0-20230625001848-8f18aae6c91c/go.mod h1:nBuRsi6udr2x6eorarLHtRkoRaWBICt+WzaE7zQXgYY= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= +github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= diff --git a/internal/command/player/run.go b/internal/command/player/run.go index f5c1eae..4351d37 100644 --- a/internal/command/player/run.go +++ b/internal/command/player/run.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "forge.cadoles.com/arcad/arcast" "forge.cadoles.com/arcad/arcast/pkg/browser/lorca" "forge.cadoles.com/arcad/arcast/pkg/server" "github.com/pkg/errors" @@ -26,11 +27,21 @@ func Run() *cli.Command { EnvVars: []string{"ARCAST_DESKTOP_INSTANCE_ID"}, Value: "", }, + &cli.StringFlag{ + Name: "address", + EnvVars: []string{"ARCAST_DESKTOP_ADDRESS"}, + Value: ":", + }, &cli.IntFlag{ Name: "window-height", EnvVars: []string{"ARCAST_DESKTOP_WINDOW_HEIGHT"}, Value: defaults.Height, }, + &cli.BoolFlag{ + Name: "apps", + EnvVars: []string{"ARCAST_DESKTOP_APPS"}, + Value: false, + }, &cli.IntFlag{ Name: "window-width", EnvVars: []string{"ARCAST_DESKTOP_WINDOW_WIDTH"}, @@ -41,6 +52,8 @@ func Run() *cli.Command { windowHeight := ctx.Int("window-height") windowWidth := ctx.Int("window-width") chromeArgs := addFlagsPrefix(ctx.StringSlice("additional-chrome-arg")...) + enableApps := ctx.Bool("apps") + serverAddress := ctx.String("address") browser := lorca.NewBrowser( lorca.WithAdditionalChromeArgs(chromeArgs...), @@ -69,7 +82,13 @@ func Run() *cli.Command { instanceID = server.NewRandomInstanceID() } - server := server.New(browser, server.WithInstanceID(instanceID)) + server := server.New(browser, + server.WithInstanceID(instanceID), + server.WithAppsEnabled(enableApps), + server.WithDefaultApp("home"), + server.WithApps(arcast.DefaultApps...), + server.WithAddress(serverAddress), + ) if err := server.Start(); err != nil { return errors.Wrap(err, "could not start server") diff --git a/modd.conf b/modd.conf index 199c707..3062cfa 100644 --- a/modd.conf +++ b/modd.conf @@ -1,5 +1,6 @@ **/*.go pkg/server/templates/**.gotmpl +apps/** modd.conf .env { prep: make build-client diff --git a/pkg/server/apps.go b/pkg/server/apps.go new file mode 100644 index 0000000..a455c98 --- /dev/null +++ b/pkg/server/apps.go @@ -0,0 +1,63 @@ +package server + +import ( + "io/fs" + "net/http" + "strings" + "sync" + + "github.com/go-chi/chi/v5" + "gitlab.com/wpetit/goweb/api" + + _ "embed" +) + +type App struct { + ID string `json:"id"` + Title map[string]string `json:"title"` + Description map[string]string `json:"description"` + Icon string `json:"icon"` + FS fs.FS `json:"-"` + Hidden bool `json:"hidden"` +} + +func (s *Server) handleDefaultApp(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, "/apps/"+s.defaultApp+"/", http.StatusTemporaryRedirect) +} + +func (s *Server) handleApps(w http.ResponseWriter, r *http.Request) { + api.DataResponse(w, http.StatusOK, struct { + DefaultApp string `json:"defaultApp"` + Apps []App `json:"apps"` + }{ + DefaultApp: s.defaultApp, + Apps: s.apps, + }) +} + +var ( + indexedAppFilesystems map[string]fs.FS + indexAppsOnce sync.Once +) + +func (s *Server) handleAppFilesystem(w http.ResponseWriter, r *http.Request) { + indexAppsOnce.Do(func() { + indexedAppFilesystems = make(map[string]fs.FS, len(s.apps)) + for _, app := range s.apps { + indexedAppFilesystems[app.ID] = app.FS + } + }) + + appID := chi.URLParam(r, "appID") + + fs, exists := indexedAppFilesystems[appID] + if !exists { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + + return + } + + name := strings.TrimPrefix(r.URL.Path, "/apps/"+appID) + + http.ServeFileFS(w, r, fs, name) +} diff --git a/pkg/server/http.go b/pkg/server/http.go index 36ffdc0..96d26aa 100644 --- a/pkg/server/http.go +++ b/pkg/server/http.go @@ -9,6 +9,7 @@ import ( "strconv" "github.com/go-chi/chi/v5" + "github.com/go-chi/cors" "github.com/pkg/errors" "gitlab.com/wpetit/goweb/api" "gitlab.com/wpetit/goweb/logger" @@ -34,11 +35,36 @@ func init() { func (s *Server) startHTTPServer(ctx context.Context) error { router := chi.NewRouter() + if s.appsEnabled { + ips, err := getLANIPv4Addrs() + if err != nil { + return errors.WithStack(err) + } + + allowedOrigins := make([]string, len(ips)) + for idx, ip := range ips { + allowedOrigins[idx] = fmt.Sprintf("http://%s:%d", ip, s.port) + } + + router.Use(cors.Handler(cors.Options{ + AllowedOrigins: allowedOrigins, + AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, + AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"}, + AllowCredentials: false, + })) + } + router.Get("/", s.handleHome) router.Post("/api/v1/cast", s.handleCast) router.Delete("/api/v1/cast", s.handleReset) router.Get("/api/v1/status", s.handleStatus) + if s.appsEnabled { + router.Get("/apps", s.handleDefaultApp) + router.Get("/api/v1/apps", s.handleApps) + router.Handle("/apps/{appID}/*", http.HandlerFunc(s.handleAppFilesystem)) + } + server := http.Server{ Addr: s.address, Handler: router, @@ -170,6 +196,7 @@ func (s *Server) handleHome(w http.ResponseWriter, r *http.Request) { IPs []string Port int ID string + Apps bool } ips, err := getLANIPv4Addrs() @@ -183,6 +210,7 @@ func (s *Server) handleHome(w http.ResponseWriter, r *http.Request) { ID: s.instanceID, IPs: ips, Port: s.port, + Apps: s.appsEnabled, } if err := idleTemplate.Execute(w, d); err != nil { diff --git a/pkg/server/options.go b/pkg/server/options.go index 4e0f632..24eb6b6 100644 --- a/pkg/server/options.go +++ b/pkg/server/options.go @@ -17,18 +17,24 @@ func init() { } type Options struct { - InstanceID string - Address string - DisableServiceDiscovery bool + InstanceID string + Address string + EnableServiceDiscovery bool + EnableApps bool + DefaultApp string + Apps []App } type OptionFunc func(opts *Options) func NewOptions(funcs ...OptionFunc) *Options { opts := &Options{ - InstanceID: NewRandomInstanceID(), - Address: ":", - DisableServiceDiscovery: false, + InstanceID: NewRandomInstanceID(), + Address: ":", + EnableServiceDiscovery: true, + EnableApps: false, + DefaultApp: "", + Apps: make([]App, 0), } for _, fn := range funcs { @@ -38,6 +44,24 @@ func NewOptions(funcs ...OptionFunc) *Options { return opts } +func WithAppsEnabled(enabled bool) OptionFunc { + return func(opts *Options) { + opts.EnableApps = enabled + } +} + +func WithDefaultApp(defaultApp string) OptionFunc { + return func(opts *Options) { + opts.DefaultApp = defaultApp + } +} + +func WithApps(apps ...App) OptionFunc { + return func(opts *Options) { + opts.Apps = apps + } +} + func WithAddress(addr string) OptionFunc { return func(opts *Options) { opts.Address = addr @@ -50,9 +74,9 @@ func WithInstanceID(id string) OptionFunc { } } -func WithServiceDiscoveryDisabled(disabled bool) OptionFunc { +func WithServiceDiscoveryEnabled(enabled bool) OptionFunc { return func(opts *Options) { - opts.DisableServiceDiscovery = disabled + opts.EnableServiceDiscovery = enabled } } diff --git a/pkg/server/server.go b/pkg/server/server.go index 3ea403c..0d3a1a8 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -11,10 +11,15 @@ import ( type Server struct { browser browser.Browser - instanceID string - address string - port int - disableServiceDiscovery bool + instanceID string + address string + port int + + serviceDiscoveryEnabled bool + + appsEnabled bool + defaultApp string + apps []App ctx context.Context cancel context.CancelFunc @@ -32,7 +37,7 @@ func (s *Server) Start() error { return errors.WithStack(err) } - if !s.disableServiceDiscovery { + if s.serviceDiscoveryEnabled { mdnsServerCtx, cancelMDNSServer := context.WithCancel(serverCtx) if err := s.startMDNServer(mdnsServerCtx); err != nil { cancelHTTPServer() @@ -71,6 +76,9 @@ func New(browser browser.Browser, funcs ...OptionFunc) *Server { browser: browser, instanceID: opts.InstanceID, address: opts.Address, - disableServiceDiscovery: opts.DisableServiceDiscovery, + appsEnabled: opts.EnableApps, + defaultApp: opts.DefaultApp, + apps: opts.Apps, + serviceDiscoveryEnabled: opts.EnableServiceDiscovery, } } diff --git a/pkg/server/templates/idle.html.gotmpl b/pkg/server/templates/idle.html.gotmpl index 3ea0931..929ead7 100644 --- a/pkg/server/templates/idle.html.gotmpl +++ b/pkg/server/templates/idle.html.gotmpl @@ -76,13 +76,18 @@ .text-small { font-size: 0.8em; } + + .mt { + margin-top: 1em; + display: block; + }
-

Arcast

+

Arcast - Idle

Instance ID:

{{ .ID }} @@ -94,6 +99,15 @@

  • {{ . }}:{{ $port }}
  • {{end}} + {{if .Apps }} +

    Apps:

    + + {{end}}