package main import ( "encoding/json" "errors" "fmt" "io" "io/fs" "log" "net/http" "net/url" "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" "fyne.io/fyne/v2/cmd/fyne_settings/settings" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/storage" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" ) var TvStatus string = "Disconnected" // Need refresh var TvIp string = "192.168.254.75" // This isn't reliable but it works at least var TvMac string = "cc:a1:2b:46:7a:0b" // Used for WOL var WOLs map[string]string // IP = Mac func LoadWOLs(w fyne.Window) { WOLs = make(map[string]string) var file fyne.URI = storage.NewFileURI("wol.txt") if file == nil { dialog.ShowError(errors.New("failed obtaining WOL file"), w) return } var err error var wol_file fyne.URIReadCloser wol_file, err = storage.Reader(file) if err != nil { if !errors.Is(err, fs.ErrNotExist) { dialog.ShowError(err, w) } return } defer wol_file.Close() var data []byte data, err = io.ReadAll(wol_file) if err != nil { dialog.ShowError(err, w) return } err = json.Unmarshal(data, &WOLs) if err != nil { dialog.ShowError(err, w) return } } func SaveWOLs(w fyne.Window) { var file fyne.URI = storage.NewFileURI("wol.txt") if file == nil { dialog.ShowError(errors.New("failed obtaining WOL file"), w) return } var err error var wol_file fyne.URIWriteCloser wol_file, err = storage.Writer(file) if err != nil { dialog.ShowError(fmt.Errorf("save %v", err), w) return } defer wol_file.Close() var data []byte data, err = json.Marshal(WOLs) if err != nil { dialog.ShowError(err, w) return } var bytesWrote int bytesWrote, err = wol_file.Write(data) if err != nil { dialog.ShowError(err, w) return } else if bytesWrote != len(data) { dialog.ShowError(fmt.Errorf("failed completely writing WOL file, wrote %d bytes but needed to write %d bytes", bytesWrote, len(data)), w) } } func WOLString() []string { var result []string for ip := range WOLs { result = append(result, ip) } return result } // Depreciated as mobile users will use the settings button rather than the menu /*func make_mainMnu(a fyne.App, w fyne.Window) *fyne.MainMenu { var settingsItem *fyne.MenuItem = fyne.NewMenuItem("Settings", func() { var w2 fyne.Window = a.NewWindow("myRoku Settings") w2.SetContent(settings.NewSettings().LoadAppearanceScreen(w2)) w2.CenterOnScreen() w2.Resize(fyne.NewSize(480, 480)) w2.Show() }) var mnu *fyne.MainMenu = fyne.NewMainMenu(fyne.NewMenu("File", settingsItem, fyne.NewMenuItemSeparator())) return mnu }*/ func etherWake() { SendMagicPacket(TvIp, TvMac) } func main() { var a fyne.App = app.NewWithID("my-roku") var version = a.Metadata().Version log.Printf("myRoku v%s", version) var home fyne.Window = a.NewWindow("myRoku") /*if a.Driver().Device().HasKeyboard() { // Desktop only home.SetMainMenu(make_mainMnu(a, home)) } */ home.CenterOnScreen() home.SetMaster() home.Resize(fyne.NewSize(420, 620)) var tvStatus *widget.Label = widget.NewLabel(TvStatus) home.SetContent(container.NewVBox( container.NewHBox( widget.NewLabel(fmt.Sprintf("myRoku - v%s", version)), widget.NewButtonWithIcon("", theme.SettingsIcon(), func() { var w2 fyne.Window = a.NewWindow("myRoku Settings") w2.SetContent(container.NewVBox( settings.NewSettings().LoadAppearanceScreen(w2), widget.NewButton("Quit", func() { w2.Close() }), )) w2.CenterOnScreen() w2.Resize(fyne.NewSize(480, 480)) w2.Show() }), ), container.NewHBox( tvStatus, widget.NewButtonWithIcon("Connections", theme.ComputerIcon(), func() { var w2 fyne.Window = a.NewWindow("myRoku Connections") // Change this from text entries to a choice list var mac_entry *widget.Label = widget.NewLabel(TvMac) // Issue the command to find devices when the user presses a refresh button (updating the list of devices to choose) var ip_entry *widget.Select = widget.NewSelect(WOLString(), func(change string) { TvIp = change TvMac = WOLs[change] mac_entry.Text = TvMac mac_entry.Refresh() }) if TvIp != "" { ip_entry.Selected = TvIp ip_entry.Refresh() TvMac = WOLs[TvIp] mac_entry.Text = TvMac mac_entry.Refresh() } /*ip_entry.OnSubmitted = func(s string) { if TvIp != s { // It's the same thing!?! log.Printf("TvIp='%s' Now '%s'", TvIp, s) TvIp = s ip_entry.Refresh() } ip_entry.FocusLost() // Lose focus so the user can enter the next field } var mac_entry *widget.Entry = widget.NewEntry() mac_entry.SetText(TvMac) mac_entry.OnSubmitted = func(s string) { if TvMac != s { // It's the same thing!?! log.Printf("TvMac='%s' Now '%s'", TvMac, s) TvMac = s mac_entry.Refresh() } mac_entry.FocusLost() // Lose focus so the user can enter the next field }*/ w2.SetContent(container.NewVBox( widget.NewButtonWithIcon("Refresh", theme.ViewRefreshIcon(), func() { _, WOLs = GetDevices(WOLs, w2) ip_entry.Options = WOLString() ip_entry.Refresh() }), container.NewHBox( widget.NewLabel("Tv Ip Address"), widget.NewButtonWithIcon("", theme.HelpIcon(), func() { var u *url.URL var err error u, err = url.Parse("https://en.wikipedia.org/wiki/IP_address") if err != nil { log.Println("Failed sending the user to Wiki=IP_address", err) return } a.OpenURL(u) }), ), ip_entry, container.NewHBox( widget.NewLabel("Tv Mac Address"), widget.NewButtonWithIcon("", theme.HelpIcon(), func() { var u *url.URL var err error u, err = url.Parse("https://en.wikipedia.org/wiki/MAC_address") if err != nil { log.Println("Failed sending the user to Wiki=MAC_address", err) return } a.OpenURL(u) }), ), mac_entry, widget.NewButtonWithIcon("Back", theme.NavigateBackIcon(), func() { SaveWOLs(home) w2.Close() }), )) w2.CenterOnScreen() w2.Resize(fyne.NewSize(480, 480)) w2.Show() }), ), container.NewHBox( widget.NewButton("Power", func() { var r bool = PerformKey(TvIp, "power") if !r { etherWake() PerformKey(TvIp, "power") } }), widget.NewButton("Display", func() { var stat string = DebugInfo(TvIp) if stat == "" { etherWake() stat = DebugInfo(TvIp) } TvStatus = stat tvStatus.Text = TvStatus tvStatus.Refresh() /*var device *DeviceInfo = GetDeviceInfo(TvIp) if device.PowerMode == "PowerOn" { // Tv's on } else { // Tv's off }*/ }), // Connect moved to Connections above ), container.NewHBox( widget.NewButtonWithIcon("", theme.MailReplyIcon(), func() { var r bool = PerformKey(TvIp, "back") if !r { etherWake() PerformKey(TvIp, "back") } }), widget.NewButtonWithIcon("", theme.HomeIcon(), func() { var r bool = PerformKey(TvIp, "home") if !r { etherWake() PerformKey(TvIp, "home") } }), ), widget.NewButtonWithIcon("", theme.MoveUpIcon(), func() { var r bool = PerformKey(TvIp, "up") if !r { etherWake() PerformKey(TvIp, "up") } }), container.NewHBox( widget.NewButtonWithIcon("", theme.NavigateBackIcon(), func() { var r bool = PerformKey(TvIp, "left") if !r { etherWake() PerformKey(TvIp, "left") } }), widget.NewButton("Ok", func() { var r bool = PerformKey(TvIp, "select") if !r { etherWake() PerformKey(TvIp, "select") } }), widget.NewButtonWithIcon("", theme.NavigateNextIcon(), func() { var r bool = PerformKey(TvIp, "right") if !r { etherWake() PerformKey(TvIp, "right") } }), ), widget.NewButtonWithIcon("", theme.MoveDownIcon(), func() { var r bool = PerformKey(TvIp, "down") if !r { etherWake() PerformKey(TvIp, "down") } }), container.NewHBox( widget.NewButtonWithIcon("", theme.ViewRefreshIcon(), func() { var r bool = PerformKey(TvIp, "instantreplay") if !r { etherWake() PerformKey(TvIp, "instantreplay") } }), widget.NewButton("Info", func() { var r bool = PerformKey(TvIp, "info") if !r { etherWake() PerformKey(TvIp, "info") } }), ), container.NewHBox( widget.NewButtonWithIcon("", theme.MediaFastRewindIcon(), func() { var r bool = PerformKey(TvIp, "rev") if !r { etherWake() PerformKey(TvIp, "rev") } }), widget.NewButtonWithIcon("", theme.MediaPauseIcon(), func() { var r bool = PerformKey(TvIp, "play") if !r { etherWake() PerformKey(TvIp, "play") } }), widget.NewButtonWithIcon("", theme.MediaFastForwardIcon(), func() { var r bool = PerformKey(TvIp, "fwd") if !r { etherWake() PerformKey(TvIp, "fwd") } }), ), container.NewHBox( widget.NewButtonWithIcon("", theme.VolumeUpIcon(), func() { var r bool = PerformKey(TvIp, "volumeup") if !r { etherWake() PerformKey(TvIp, "volumeup") } }), widget.NewButtonWithIcon("", theme.VolumeMuteIcon(), func() { var r bool = PerformKey(TvIp, "volumemute") if !r { etherWake() PerformKey(TvIp, "volumemute") } }), widget.NewButtonWithIcon("", theme.VolumeDownIcon(), func() { var r bool = PerformKey(TvIp, "volumedown") if !r { etherWake() PerformKey(TvIp, "volumedown") } }), ), widget.NewButton("Pluto Tv", func() { var r *http.Response = Post(fmt.Sprintf("http://%s:8060/launch/%d", TvIp, 74519)) if r == nil { etherWake() Post(fmt.Sprintf("http://%s:8060/launch/%d", TvIp, 74519)) } }), /*widget.NewButton("Debug", func() { log.Printf("myRoku v%s wol=%t", version, UseWake) log.Printf("TvStatus='%s' TvIp='%s' TvMac='%s'", TvStatus, TvIp, TvMac) log.Printf("driver=(mobile:%t, desktop:%t, browser:%t)", a.Driver().Device().IsMobile(), a.Driver().Device().HasKeyboard(), a.Driver().Device().IsBrowser()) }),*/ )) home.Show() LoadWOLs(home) a.Run() }