/ 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  }