main.go 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. package main
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "io"
  7. "io/fs"
  8. "log"
  9. "net/http"
  10. "net/url"
  11. "fyne.io/fyne/v2"
  12. "fyne.io/fyne/v2/app"
  13. "fyne.io/fyne/v2/cmd/fyne_settings/settings"
  14. "fyne.io/fyne/v2/container"
  15. "fyne.io/fyne/v2/dialog"
  16. "fyne.io/fyne/v2/storage"
  17. "fyne.io/fyne/v2/theme"
  18. "fyne.io/fyne/v2/widget"
  19. )
  20. var TvStatus string = "Disconnected" // Need refresh
  21. var TvIp string = "192.168.254.75" // This isn't reliable but it works at least
  22. var TvMac string = "cc:a1:2b:46:7a:0b" // Used for WOL
  23. var WOLs map[string]string // IP = Mac
  24. func LoadWOLs(w fyne.Window) {
  25. WOLs = make(map[string]string)
  26. var file fyne.URI = storage.NewFileURI("wol.txt")
  27. if file == nil {
  28. dialog.ShowError(errors.New("failed obtaining WOL file"), w)
  29. return
  30. }
  31. var err error
  32. var wol_file fyne.URIReadCloser
  33. wol_file, err = storage.Reader(file)
  34. if err != nil {
  35. if !errors.Is(err, fs.ErrNotExist) {
  36. dialog.ShowError(err, w)
  37. }
  38. return
  39. }
  40. defer wol_file.Close()
  41. var data []byte
  42. data, err = io.ReadAll(wol_file)
  43. if err != nil {
  44. dialog.ShowError(err, w)
  45. return
  46. }
  47. err = json.Unmarshal(data, &WOLs)
  48. if err != nil {
  49. dialog.ShowError(err, w)
  50. return
  51. }
  52. }
  53. func SaveWOLs(w fyne.Window) {
  54. var file fyne.URI = storage.NewFileURI("wol.txt")
  55. if file == nil {
  56. dialog.ShowError(errors.New("failed obtaining WOL file"), w)
  57. return
  58. }
  59. var err error
  60. var wol_file fyne.URIWriteCloser
  61. wol_file, err = storage.Writer(file)
  62. if err != nil {
  63. dialog.ShowError(fmt.Errorf("save %v", err), w)
  64. return
  65. }
  66. defer wol_file.Close()
  67. var data []byte
  68. data, err = json.Marshal(WOLs)
  69. if err != nil {
  70. dialog.ShowError(err, w)
  71. return
  72. }
  73. var bytesWrote int
  74. bytesWrote, err = wol_file.Write(data)
  75. if err != nil {
  76. dialog.ShowError(err, w)
  77. return
  78. } else if bytesWrote != len(data) {
  79. dialog.ShowError(fmt.Errorf("failed completely writing WOL file, wrote %d bytes but needed to write %d bytes", bytesWrote, len(data)), w)
  80. }
  81. }
  82. func WOLString() []string {
  83. var result []string
  84. for ip := range WOLs {
  85. result = append(result, ip)
  86. }
  87. return result
  88. }
  89. // Depreciated as mobile users will use the settings button rather than the menu
  90. /*func make_mainMnu(a fyne.App, w fyne.Window) *fyne.MainMenu {
  91. var settingsItem *fyne.MenuItem = fyne.NewMenuItem("Settings", func() {
  92. var w2 fyne.Window = a.NewWindow("myRoku Settings")
  93. w2.SetContent(settings.NewSettings().LoadAppearanceScreen(w2))
  94. w2.CenterOnScreen()
  95. w2.Resize(fyne.NewSize(480, 480))
  96. w2.Show()
  97. })
  98. var mnu *fyne.MainMenu = fyne.NewMainMenu(fyne.NewMenu("File", settingsItem, fyne.NewMenuItemSeparator()))
  99. return mnu
  100. }*/
  101. func etherWake() {
  102. SendMagicPacket(TvIp, TvMac)
  103. }
  104. func main() {
  105. var a fyne.App = app.NewWithID("my-roku")
  106. var version = a.Metadata().Version
  107. log.Printf("myRoku v%s", version)
  108. var home fyne.Window = a.NewWindow("myRoku")
  109. /*if a.Driver().Device().HasKeyboard() { // Desktop only
  110. home.SetMainMenu(make_mainMnu(a, home))
  111. } */
  112. home.CenterOnScreen()
  113. home.SetMaster()
  114. home.Resize(fyne.NewSize(420, 620))
  115. var tvStatus *widget.Label = widget.NewLabel(TvStatus)
  116. home.SetContent(container.NewVBox(
  117. container.NewHBox(
  118. widget.NewLabel(fmt.Sprintf("myRoku - v%s", version)),
  119. widget.NewButtonWithIcon("", theme.SettingsIcon(), func() {
  120. var w2 fyne.Window = a.NewWindow("myRoku Settings")
  121. w2.SetContent(container.NewVBox(
  122. settings.NewSettings().LoadAppearanceScreen(w2),
  123. widget.NewButton("Quit", func() {
  124. w2.Close()
  125. }),
  126. ))
  127. w2.CenterOnScreen()
  128. w2.Resize(fyne.NewSize(480, 480))
  129. w2.Show()
  130. }),
  131. ),
  132. container.NewHBox(
  133. tvStatus,
  134. widget.NewButtonWithIcon("Connections", theme.ComputerIcon(), func() {
  135. var w2 fyne.Window = a.NewWindow("myRoku Connections")
  136. // Change this from text entries to a choice list
  137. var mac_entry *widget.Label = widget.NewLabel(TvMac)
  138. // Issue the command to find devices when the user presses a refresh button (updating the list of devices to choose)
  139. var ip_entry *widget.Select = widget.NewSelect(WOLString(), func(change string) {
  140. TvIp = change
  141. TvMac = WOLs[change]
  142. mac_entry.Text = TvMac
  143. mac_entry.Refresh()
  144. })
  145. if TvIp != "" {
  146. ip_entry.Selected = TvIp
  147. ip_entry.Refresh()
  148. TvMac = WOLs[TvIp]
  149. mac_entry.Text = TvMac
  150. mac_entry.Refresh()
  151. }
  152. /*ip_entry.OnSubmitted = func(s string) {
  153. if TvIp != s { // It's the same thing!?!
  154. log.Printf("TvIp='%s' Now '%s'", TvIp, s)
  155. TvIp = s
  156. ip_entry.Refresh()
  157. }
  158. ip_entry.FocusLost() // Lose focus so the user can enter the next field
  159. }
  160. var mac_entry *widget.Entry = widget.NewEntry()
  161. mac_entry.SetText(TvMac)
  162. mac_entry.OnSubmitted = func(s string) {
  163. if TvMac != s { // It's the same thing!?!
  164. log.Printf("TvMac='%s' Now '%s'", TvMac, s)
  165. TvMac = s
  166. mac_entry.Refresh()
  167. }
  168. mac_entry.FocusLost() // Lose focus so the user can enter the next field
  169. }*/
  170. w2.SetContent(container.NewVBox(
  171. widget.NewButtonWithIcon("Refresh", theme.ViewRefreshIcon(), func() {
  172. _, WOLs = GetDevices(WOLs, w2)
  173. ip_entry.Options = WOLString()
  174. ip_entry.Refresh()
  175. }),
  176. container.NewHBox(
  177. widget.NewLabel("Tv Ip Address"),
  178. widget.NewButtonWithIcon("", theme.HelpIcon(), func() {
  179. var u *url.URL
  180. var err error
  181. u, err = url.Parse("https://en.wikipedia.org/wiki/IP_address")
  182. if err != nil {
  183. log.Println("Failed sending the user to Wiki=IP_address", err)
  184. return
  185. }
  186. a.OpenURL(u)
  187. }),
  188. ),
  189. ip_entry,
  190. container.NewHBox(
  191. widget.NewLabel("Tv Mac Address"),
  192. widget.NewButtonWithIcon("", theme.HelpIcon(), func() {
  193. var u *url.URL
  194. var err error
  195. u, err = url.Parse("https://en.wikipedia.org/wiki/MAC_address")
  196. if err != nil {
  197. log.Println("Failed sending the user to Wiki=MAC_address", err)
  198. return
  199. }
  200. a.OpenURL(u)
  201. }),
  202. ),
  203. mac_entry,
  204. widget.NewButtonWithIcon("Back", theme.NavigateBackIcon(), func() {
  205. SaveWOLs(home)
  206. w2.Close()
  207. }),
  208. ))
  209. w2.CenterOnScreen()
  210. w2.Resize(fyne.NewSize(480, 480))
  211. w2.Show()
  212. }),
  213. ),
  214. container.NewHBox(
  215. widget.NewButton("Power", func() {
  216. var r bool = PerformKey(TvIp, "power")
  217. if !r {
  218. etherWake()
  219. PerformKey(TvIp, "power")
  220. }
  221. }),
  222. widget.NewButton("Display", func() {
  223. var stat string = DebugInfo(TvIp)
  224. if stat == "" {
  225. etherWake()
  226. stat = DebugInfo(TvIp)
  227. }
  228. TvStatus = stat
  229. tvStatus.Text = TvStatus
  230. tvStatus.Refresh()
  231. /*var device *DeviceInfo = GetDeviceInfo(TvIp)
  232. if device.PowerMode == "PowerOn" {
  233. // Tv's on
  234. } else {
  235. // Tv's off
  236. }*/
  237. }),
  238. // Connect moved to Connections above
  239. ),
  240. container.NewHBox(
  241. widget.NewButtonWithIcon("", theme.MailReplyIcon(), func() {
  242. var r bool = PerformKey(TvIp, "back")
  243. if !r {
  244. etherWake()
  245. PerformKey(TvIp, "back")
  246. }
  247. }),
  248. widget.NewButtonWithIcon("", theme.HomeIcon(), func() {
  249. var r bool = PerformKey(TvIp, "home")
  250. if !r {
  251. etherWake()
  252. PerformKey(TvIp, "home")
  253. }
  254. }),
  255. ),
  256. widget.NewButtonWithIcon("", theme.MoveUpIcon(), func() {
  257. var r bool = PerformKey(TvIp, "up")
  258. if !r {
  259. etherWake()
  260. PerformKey(TvIp, "up")
  261. }
  262. }),
  263. container.NewHBox(
  264. widget.NewButtonWithIcon("", theme.NavigateBackIcon(), func() {
  265. var r bool = PerformKey(TvIp, "left")
  266. if !r {
  267. etherWake()
  268. PerformKey(TvIp, "left")
  269. }
  270. }),
  271. widget.NewButton("Ok", func() {
  272. var r bool = PerformKey(TvIp, "select")
  273. if !r {
  274. etherWake()
  275. PerformKey(TvIp, "select")
  276. }
  277. }),
  278. widget.NewButtonWithIcon("", theme.NavigateNextIcon(), func() {
  279. var r bool = PerformKey(TvIp, "right")
  280. if !r {
  281. etherWake()
  282. PerformKey(TvIp, "right")
  283. }
  284. }),
  285. ),
  286. widget.NewButtonWithIcon("", theme.MoveDownIcon(), func() {
  287. var r bool = PerformKey(TvIp, "down")
  288. if !r {
  289. etherWake()
  290. PerformKey(TvIp, "down")
  291. }
  292. }),
  293. container.NewHBox(
  294. widget.NewButtonWithIcon("", theme.ViewRefreshIcon(), func() {
  295. var r bool = PerformKey(TvIp, "instantreplay")
  296. if !r {
  297. etherWake()
  298. PerformKey(TvIp, "instantreplay")
  299. }
  300. }),
  301. widget.NewButton("Info", func() {
  302. var r bool = PerformKey(TvIp, "info")
  303. if !r {
  304. etherWake()
  305. PerformKey(TvIp, "info")
  306. }
  307. }),
  308. ),
  309. container.NewHBox(
  310. widget.NewButtonWithIcon("", theme.MediaFastRewindIcon(), func() {
  311. var r bool = PerformKey(TvIp, "rev")
  312. if !r {
  313. etherWake()
  314. PerformKey(TvIp, "rev")
  315. }
  316. }),
  317. widget.NewButtonWithIcon("", theme.MediaPauseIcon(), func() {
  318. var r bool = PerformKey(TvIp, "play")
  319. if !r {
  320. etherWake()
  321. PerformKey(TvIp, "play")
  322. }
  323. }),
  324. widget.NewButtonWithIcon("", theme.MediaFastForwardIcon(), func() {
  325. var r bool = PerformKey(TvIp, "fwd")
  326. if !r {
  327. etherWake()
  328. PerformKey(TvIp, "fwd")
  329. }
  330. }),
  331. ),
  332. container.NewHBox(
  333. widget.NewButtonWithIcon("", theme.VolumeUpIcon(), func() {
  334. var r bool = PerformKey(TvIp, "volumeup")
  335. if !r {
  336. etherWake()
  337. PerformKey(TvIp, "volumeup")
  338. }
  339. }),
  340. widget.NewButtonWithIcon("", theme.VolumeMuteIcon(), func() {
  341. var r bool = PerformKey(TvIp, "volumemute")
  342. if !r {
  343. etherWake()
  344. PerformKey(TvIp, "volumemute")
  345. }
  346. }),
  347. widget.NewButtonWithIcon("", theme.VolumeDownIcon(), func() {
  348. var r bool = PerformKey(TvIp, "volumedown")
  349. if !r {
  350. etherWake()
  351. PerformKey(TvIp, "volumedown")
  352. }
  353. }),
  354. ),
  355. widget.NewButton("Pluto Tv", func() {
  356. var r *http.Response = Post(fmt.Sprintf("http://%s:8060/launch/%d", TvIp, 74519))
  357. if r == nil {
  358. etherWake()
  359. Post(fmt.Sprintf("http://%s:8060/launch/%d", TvIp, 74519))
  360. }
  361. }),
  362. /*widget.NewButton("Debug", func() {
  363. log.Printf("myRoku v%s wol=%t", version, UseWake)
  364. log.Printf("TvStatus='%s' TvIp='%s' TvMac='%s'", TvStatus, TvIp, TvMac)
  365. log.Printf("driver=(mobile:%t, desktop:%t, browser:%t)", a.Driver().Device().IsMobile(), a.Driver().Device().HasKeyboard(), a.Driver().Device().IsBrowser())
  366. }),*/
  367. ))
  368. home.Show()
  369. LoadWOLs(home)
  370. a.Run()
  371. }