/ main.go
main.go
1 package main 2 3 import ( 4 "fmt" 5 "os" 6 "time" 7 8 "github.com/acaloiaro/di-tui/app" 9 "github.com/acaloiaro/di-tui/config" 10 "github.com/acaloiaro/di-tui/context" 11 "github.com/acaloiaro/di-tui/difm" 12 "github.com/acaloiaro/di-tui/mpris" 13 "github.com/acaloiaro/di-tui/views" 14 "github.com/gdamore/tcell/v2" 15 "github.com/rivo/tview" 16 "github.com/spf13/pflag" 17 "github.com/spf13/viper" 18 ) 19 20 var ctx *context.AppContext 21 22 func main() { 23 pflag.String("username", "", "your di.fm username") 24 pflag.String("password", "", "your di.fm password") 25 26 pflag.Parse() 27 viper.BindPFlags(pflag.CommandLine) 28 29 username := viper.GetString("username") 30 password := viper.GetString("password") 31 32 var token string 33 if len(username) > 0 && len(password) > 0 { 34 difm.Authenticate(ctx, username, password) 35 } 36 37 token = config.GetToken() 38 if token == "" { 39 fmt.Println("Authenticate by running: di-tui --username USER --password PASSWORD\n\n" + 40 "Or, visit https://github.com/acaloiaro/di-tui#authenticate for other options.") 41 os.Exit(1) 42 } 43 44 ctx = context.CreateAppContext(views.CreateViewContext()) 45 ctx.DifmToken = token 46 47 run() 48 } 49 50 func run() { 51 configureEventHandling() 52 updateScreenLayout() 53 FetchFavoritesAndChannels() 54 55 ctx.View.Keybindings.Bindings = views.GetKeybindings() 56 57 err := ctx.View.App.Run() 58 if err != nil { 59 panic(err) 60 } 61 } 62 63 func updateScreenLayout() { 64 focusView := ctx.View.App.GetFocus() 65 66 main := tview.NewFlex() 67 main.SetDirection(tview.FlexRow) 68 69 favsAndChannels := tview.NewFlex(). 70 AddItem(ctx.View.FavoriteList, 0, 1, false). 71 AddItem(ctx.View.ChannelList, 0, 2, false). 72 SetDirection(tview.FlexRow) 73 74 primaryView := tview.NewFlex() 75 primaryView. 76 AddItem(favsAndChannels, 30, 0, false). 77 AddItem(ctx.View.NowPlaying, 0, 4, false) 78 79 if ctx.ShowStatus { 80 main.AddItem(ctx.View.Status, 4, 0, false) 81 } 82 83 main. 84 AddItem(primaryView, 0, 3, false). 85 AddItem(ctx.View.Keybindings, 4, 0, false) 86 87 if focusView == nil { 88 focusView = ctx.View.FavoriteList 89 } 90 91 ctx.View.App. 92 SetRoot(main, true). 93 SetFocus(focusView) 94 } 95 96 // configureEventHandling handles key press events, and regular UI updates such as the currently playing track 97 func configureEventHandling() { 98 ctx.View.App.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { 99 focus := ctx.View.App.GetFocus().(*tview.List) 100 101 switch event.Key() { 102 case tcell.KeyEnter: 103 current := focus.GetCurrentItem() 104 if focus != ctx.View.ChannelList { 105 highlightedFavorite := ctx.FavoriteList[current] 106 ctx.HighlightedChannel = difm.FavoriteItemChannel(ctx, highlightedFavorite) 107 } else { 108 ctx.HighlightedChannel = &ctx.ChannelList[current] 109 } 110 app.Play(ctx) 111 case tcell.KeyRune: 112 switch event.Rune() { 113 case 'c': 114 if focus != ctx.View.ChannelList { 115 ctx.View.App.SetFocus(ctx.View.ChannelList) 116 } 117 case 'f': 118 if focus != ctx.View.FavoriteList { 119 ctx.View.App.SetFocus(ctx.View.FavoriteList) 120 } 121 case 'F': 122 current := focus.GetCurrentItem() 123 if focus == ctx.View.ChannelList { 124 ctx.HighlightedChannel = &ctx.ChannelList[current] 125 } else { 126 highlightedFavorite := ctx.FavoriteList[current] 127 ctx.HighlightedChannel = difm.FavoriteItemChannel(ctx, highlightedFavorite) 128 } 129 difm.ToggleFavorite(ctx) 130 FetchFavoritesAndChannels() 131 case 'q': 132 ctx.View.App.Stop() 133 case 'j': // scroll down 134 current := focus.GetCurrentItem() + 1 135 focus.SetCurrentItem(current) 136 case 'k': // scroll up 137 current := focus.GetCurrentItem() 138 if current > 0 { 139 focus.SetCurrentItem(current - 1) 140 } 141 case 'p', 32: // tcell has no constant for the space bar rune (32) 142 app.TogglePause(ctx) 143 } 144 } 145 146 return event 147 }) 148 149 // keep 'now playing' up to date second-by-second 150 go func() { 151 c := time.Tick(1 * time.Second) 152 for range c { 153 elapsed := time.Since(ctx.View.NowPlaying.Track.StartTime) 154 155 // If the current time is past the end of the track, then a new track is playing and the now playing track needs 156 // to be refreshed. 157 if ctx.View.NowPlaying.Track.Duration > 0 && ctx.View.NowPlaying.Track.Duration < elapsed.Seconds() { 158 app.UpdateNowPlaying(ctx, ctx.CurrentChannel) 159 } 160 161 if ctx.CurrentChannel != nil && elapsed.Seconds() > 0 { 162 ctx.View.NowPlaying.Elapsed = elapsed.Seconds() 163 } 164 165 ctx.View.App.QueueUpdateDraw(func() {}) 166 } 167 }() 168 169 // display the status pane when new status messages arrive 170 go func() { 171 for { 172 status := <-ctx.StatusChannel 173 ctx.View.Status.Message = status.Message 174 updateScreenLayout() // add the status pane to the screen 175 176 <-time.Tick(status.Duration) 177 ctx.ShowStatus = false 178 updateScreenLayout() // remove the status pane from the screen 179 } 180 }() 181 182 // Start the mpris server for d-bus support 183 mpris.Start(ctx) 184 } 185 186 func FetchFavoritesAndChannels() { 187 ctx.View.ChannelList.Clear() 188 ctx.View.FavoriteList.Clear() 189 190 channels := difm.ListChannels(ctx) 191 for _, chn := range channels { 192 ctx.View.ChannelList.AddItem(chn.Name, "", 0, func() { 193 }) 194 } 195 196 favorites := difm.ListFavorites(ctx) 197 for _, fav := range favorites { 198 ctx.View.FavoriteList.AddItem(fav.Name, "", 0, func() {}) 199 } 200 ctx.ChannelList = channels 201 ctx.FavoriteList = favorites 202 203 // default the highlighted channel to the first favorite; even before users select a channel manually. This way, 204 // when di-tui starts and the user presses the "Play" media key, di-tui will start playing the first favorite 205 // instead of requiring them to choose the channel to be played 206 highlightedFavorite := ctx.FavoriteList[0] 207 ctx.HighlightedChannel = difm.FavoriteItemChannel(ctx, highlightedFavorite) 208 }