diff --git a/.gitignore b/.gitignore index adf8f72..67211f9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,15 @@ # ---> Go -# If you prefer the allow list template instead of the deny list, see community template: -# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore -# # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib +Sonically +*Sonically +Sonically* +*Sonically.debug +Sonically.debug* # Test binary, built with `go test -c` *.test @@ -18,6 +20,11 @@ # Dependency directories (remove the comment below to include it) # vendor/ -# Go workspace file -go.work +# Personal data dir dynamicly created by App +.Sonically/** +.Sonically + +# Build Environment Test and Backup Dir +build_environment/backup +build_environment/test diff --git a/.woodpecker/01_linux_musl_aarch64.yml b/.woodpecker/01_linux_musl_aarch64.yml new file mode 100644 index 0000000..4e1378b --- /dev/null +++ b/.woodpecker/01_linux_musl_aarch64.yml @@ -0,0 +1,38 @@ +platform: linux/arm64 +pipeline: + build: + image: golang:alpine + when: + event: tag + environment: + GOOS: linux + GOARCH: arm64 + CGO_ENABLED: "1" + commands: + - "apk -U upgrade" + - "apk add \ + protobuf-dev \ + musl-dev \ + protoc \ + build-base \ + pkgconfig \ + gtk4.0-dev \ + glib-dev \ + gdk-pixbuf-dev \ + opusfile-dev \ + libnotify-dev \ + portaudio-dev \ + gobject-introspection-dev" + - "go build -tags netgo -o sonically_linux_musl-${CI_COMMIT_TAG##v}~aarch64" + - "sha512sum sonically_linux_musl-${CI_COMMIT_TAG##v}~aarch64 > sonically_linux_musl-${CI_COMMIT_TAG##v}~aarch64-sha512.sum" + - "sha256sum sonically_linux_musl-${CI_COMMIT_TAG##v}~aarch64 > sonically_linux_musl-${CI_COMMIT_TAG##v}~aarch64-sha256.sum" + - "md5sum sonically_linux_musl-${CI_COMMIT_TAG##v}~aarch64 > sonically_linux_musl-${CI_COMMIT_TAG##v}~aarch64-md5.sum" + publish: + image: plugins/gitea-release + settings: + base_url: https://git.itmodulo.eu + api_key: + from_secret: gitea_akey + files: sonically_linux_musl* + when: + event: tag diff --git a/.woodpecker/02_linux_glibc_x86_64.yml b/.woodpecker/02_linux_glibc_x86_64.yml new file mode 100644 index 0000000..b4ffbc9 --- /dev/null +++ b/.woodpecker/02_linux_glibc_x86_64.yml @@ -0,0 +1,40 @@ +platform: linux/amd64 +pipeline: + build: + image: debian:bookworm + when: + event: tag + environment: + GOOS: linux + GOARCH: amd64 + CGO_ENABLED: "1" + PATH: /usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/usr/local/go/bin + commands: + - "apt-get update && apt-get upgrade -y && apt-get install wget git -y" + - "wget -q https://go.dev/dl/go1.18.3.linux-amd64.tar.gz && tar -zxf go1.18.3.linux-amd64.tar.gz -C /usr/local/ && go version" + - "apt-get install -y \ + build-essential \ + protobuf-compiler \ + gcc \ + upx \ + pkg-config \ + libgtk-4-dev \ + libglib2.0-dev \ + libopusfile-dev \ + libnotify-dev \ + libportaudio2 \ + portaudio19-dev \ + libgraphene-1.0-dev" + - "go build -tags netgo -o sonically_linux_glibc-${CI_COMMIT_TAG##v}~x86_64" + - "sha512sum sonically_linux_glibc-${CI_COMMIT_TAG##v}~x86_64 > sonically_linux_glibc-${CI_COMMIT_TAG##v}~x86_64-sha512.sum" + - "sha256sum sonically_linux_glibc-${CI_COMMIT_TAG##v}~x86_64 > sonically_linux_glibc-${CI_COMMIT_TAG##v}~x86_64-sha256.sum" + - "md5sum sonically_linux_glibc-${CI_COMMIT_TAG##v}~x86_64 > sonically_linux_glibc-${CI_COMMIT_TAG##v}~x86_64-md5.sum" + publish: + image: plugins/gitea-release + settings: + base_url: https://git.itmodulo.eu + api_key: + from_secret: gitea_akey + files: sonically_linux_glibc* + when: + event: tag diff --git a/.woodpecker/03_linux_musl_x86_64.yml b/.woodpecker/03_linux_musl_x86_64.yml new file mode 100644 index 0000000..2e1a539 --- /dev/null +++ b/.woodpecker/03_linux_musl_x86_64.yml @@ -0,0 +1,38 @@ +platform: linux/amd64 +pipeline: + build: + image: golang:alpine + when: + event: tag + environment: + GOOS: linux + GOARCH: amd64 + CGO_ENABLED: "1" + commands: + - "apk -U upgrade" + - "apk add \ + protobuf-dev \ + musl-dev \ + protoc \ + build-base \ + pkgconfig \ + gtk4.0-dev \ + glib-dev \ + gdk-pixbuf-dev \ + opusfile-dev \ + libnotify-dev \ + portaudio-dev \ + gobject-introspection-dev" + - "go build -tags netgo -o sonically_linux_musl-${CI_COMMIT_TAG##v}~x86_64" + - "sha512sum sonically_linux_musl-${CI_COMMIT_TAG##v}~x86_64 > sonically_linux_musl-${CI_COMMIT_TAG##v}~x86_64-sha512.sum" + - "sha256sum sonically_linux_musl-${CI_COMMIT_TAG##v}~x86_64 > sonically_linux_musl-${CI_COMMIT_TAG##v}~x86_64-sha256.sum" + - "md5sum sonically_linux_musl-${CI_COMMIT_TAG##v}~x86_64 > sonically_linux_musl-${CI_COMMIT_TAG##v}~x86_64-md5.sum" + publish: + image: plugins/gitea-release + settings: + base_url: https://git.itmodulo.eu + api_key: + from_secret: gitea_akey + files: sonically_linux_musl* + when: + event: tag diff --git a/.woodpecker/04_linux_glibc_aarch64.yml b/.woodpecker/04_linux_glibc_aarch64.yml new file mode 100644 index 0000000..93c902f --- /dev/null +++ b/.woodpecker/04_linux_glibc_aarch64.yml @@ -0,0 +1,40 @@ +platform: linux/arm64 +pipeline: + build: + image: debian:bookworm + when: + event: tag + environment: + GOOS: linux + GOARCH: arm64 + CGO_ENABLED: "1" + PATH: /usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/usr/local/go/bin + commands: + - "apt-get update && apt-get upgrade -y && apt-get install wget git -y" + - "wget -q https://go.dev/dl/go1.18.3.linux-arm64.tar.gz && tar -zxf go1.18.3.linux-arm64.tar.gz -C /usr/local/ && go version" + - "apt-get install -y \ + build-essential \ + protobuf-compiler \ + gcc \ + upx \ + pkg-config \ + libgtk-4-dev \ + libglib2.0-dev \ + libopusfile-dev \ + libnotify-dev \ + libportaudio2 \ + portaudio19-dev \ + libgraphene-1.0-dev" + - "go build -tags netgo -o sonically_linux_glibc-${CI_COMMIT_TAG##v}~aarch64" + - "sha512sum sonically_linux_glibc-${CI_COMMIT_TAG##v}~aarch64 > sonically_linux_glibc-${CI_COMMIT_TAG##v}~aarch64-sha512.sum" + - "sha256sum sonically_linux_glibc-${CI_COMMIT_TAG##v}~aarch64 > sonically_linux_glibc-${CI_COMMIT_TAG##v}~aarch64-sha256.sum" + - "md5sum sonically_linux_glibc-${CI_COMMIT_TAG##v}~aarch64 > sonically_linux_glibc-${CI_COMMIT_TAG##v}~aarch64-md5.sum" + publish: + image: plugins/gitea-release + settings: + base_url: https://git.itmodulo.eu + api_key: + from_secret: gitea_akey + files: sonically_linux_glibc* + when: + event: tag diff --git a/README.md b/README.md index 918cf45..fc478f3 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,29 @@ -# Sonically +![Sonically logo](https://static.itmodulo.eu/files/l/sonically.svg) -GUI native Subsonic API client optimized for mobile outdoor usage on *nix platforms like pmOS, Mobian. \ No newline at end of file +# Sonically [BETA] + +## Status +***!!! SUSPENDED !!!*** + +I was forced to go back to Android, Sonically is not priority there. +I'm looking forwared to switch to pmOS again, I'll probably do so in the mid of 2023 and development will restart ;) + +## Info +1. **Priority**: GTK4 is cool but not ideal, especially in go. I'll switch to something else. Probably native (for go) Fyne UI Toolkit. This should unlock seeking. +2. **Priority**: Clean code +3. Proxy settings, Lyrics self-fetching will be removed. This is non-goal now. You self-host you trust your server, you trust this app as it **should** connect only to this server. If you want more goodies, use advanced server implementation. +4. Since upcoming fix [*read important below]* I'll start using branches, at least 2, master and develop. +5. Packaging probably own repo. + +## Installation +I lost somwhere working all binaries from 2022. Forgive me that. +Please compile yourself for now. + +**Important**. First commit on this repo probably won't compile as it depends on LyricsApiGoExtneded that I don't longer care, also fscache may be broken. I'll try to fix that in the meantime. + +**Hint** You may look into APKBUILD if you use pmOS, it should contain reciepe for building apkand + +## Configuration +Ensure you have keyring named login. If no create it using e.g. seahorse. + +Recommended settings are opus 128, buffer 4096 (probably only working, as some values may be hardcoded) diff --git a/README.md.backup b/README.md.backup new file mode 100644 index 0000000..fc478f3 --- /dev/null +++ b/README.md.backup @@ -0,0 +1,29 @@ +![Sonically logo](https://static.itmodulo.eu/files/l/sonically.svg) + +# Sonically [BETA] + +## Status +***!!! SUSPENDED !!!*** + +I was forced to go back to Android, Sonically is not priority there. +I'm looking forwared to switch to pmOS again, I'll probably do so in the mid of 2023 and development will restart ;) + +## Info +1. **Priority**: GTK4 is cool but not ideal, especially in go. I'll switch to something else. Probably native (for go) Fyne UI Toolkit. This should unlock seeking. +2. **Priority**: Clean code +3. Proxy settings, Lyrics self-fetching will be removed. This is non-goal now. You self-host you trust your server, you trust this app as it **should** connect only to this server. If you want more goodies, use advanced server implementation. +4. Since upcoming fix [*read important below]* I'll start using branches, at least 2, master and develop. +5. Packaging probably own repo. + +## Installation +I lost somwhere working all binaries from 2022. Forgive me that. +Please compile yourself for now. + +**Important**. First commit on this repo probably won't compile as it depends on LyricsApiGoExtneded that I don't longer care, also fscache may be broken. I'll try to fix that in the meantime. + +**Hint** You may look into APKBUILD if you use pmOS, it should contain reciepe for building apkand + +## Configuration +Ensure you have keyring named login. If no create it using e.g. seahorse. + +Recommended settings are opus 128, buffer 4096 (probably only working, as some values may be hardcoded) diff --git a/audio_player.go b/audio_player.go new file mode 100644 index 0000000..be2c5b4 --- /dev/null +++ b/audio_player.go @@ -0,0 +1,381 @@ +package main + +import ( + "errors" + "fmt" + "io" + "os" + "os/signal" + "runtime" + "time" + // "math" + "strconv" + + "git.itmodulo.eu/LibrariesGo/lyrics-api-go-extended" + "github.com/TheCreeper/go-notify" + // "github.com/disintegration/imaging" + "github.com/diamondburned/gotk4/pkg/core/glib" + // "github.com/djherbis/fscache" + "github.com/gordonklaus/portaudio" + // "github.com/diamondburned/gotk4/pkg/gdk/v4" + // "github.com/delucks/go-subsonic" + "github.com/hraban/opus" +) + +const rate float64 = 48000 +const channels uint8 = 2 + +var PlayAudio bool = true // Used for play pause +var EndPlayer bool = false // Used for ending recursion +var StreamChanged bool = false + +func PlayPort() { + + nodeID := MainQueue[NowPlayingIndex] + songdata, _ := CurrentClient.GetSong(nodeID) + albmdata, _ := CurrentClient.GetAlbum(songdata.AlbumID) + + // go glib.IdleAdd(FindSubtitles(songdata.Artist, songdata.Title)) + TotalTime = songdata.Duration + + // Async Gui update needed by GTK + + var SetPlayingGuiData = func() { + MainArtistLabel.SetLabel(songdata.Artist) + MainSongTitleLabel.SetLabel(songdata.Title) + MainAlbumLabel.SetLabel(songdata.Album) + MainTotalTimeLabel.SetLabel(DurationToReadable(songdata.Duration)) + + DownloadCoverArtFullSize(albmdata.CoverArt) + var imgpath string = GetRunDir() + MainDir + DataDir + "/" + albmdata.CoverArt + ".jpg" + MainAlbumCover.SetFromFile(imgpath) + + // path2 := os.UserHomeDir + MainDir + DataDir + "/" + albmdata.CoverArt + "-b.jpg" + + // blu, err := os.Open(imgpath) + // if err != nil { + // LogOnError(err) + // } + // defer blu.Close() + + // img, _, err := image.Decode(blu) + // if err != nil { + // LogOnError(err) + // } + + // dstImage := imaging.Blur(img, MainSettings.BlurRadius) + // err = imaging.Save(dstImage, path2) + // if err != nil { + // LogOnError(err) + // } + + // AD1 + // MainAlbumCover.SetFromPixbuf(pixbuf) + + MainPlayPauseButton.SetLabel("❚❚") + } + + sig := make(chan os.Signal, 1) + signal.Notify(sig, os.Interrupt, os.Kill) + // param := map[string]string{ + // "maxBitRate": strconv.Itoa(int(MainSettings.Bitrate)), + // "MainFormat": MainSettings.Codec, + // "transcodings": MainSettings.Codec, + // "estimateContentLength": "true", + // } + portaudio.Initialize() + defer portaudio.Terminate() + + // create the cache, keys expire after 1 hour. + + // var ReadMaster fscache.ReadAtCloser + // var WriteMaster io.WriteCloser + + // IsCached := CurrentCache.Exists(nodeID) + + ReadMaster, _, _ = CurrentCache.Get(nodeID) + + // if !IsCached { + // reader, _ := CurrentClient.Stream(nodeID, param) + // go Backgroundread(reader, WriteMaster) + // } else { + // fmt.Println("Playing ", songdata.Artist, " - ", songdata.Title, " from cache") + // } + + // Writing needs to be implemented in diffrenet place + // Here we use only reader + + //AD2 + + OpusStr, err := opus.NewStream(ReadMaster) + chk(err) + result := <-AudioControlChan + fmt.Println("Result of channel is: ", result) + CurrentStream, _ := portaudio.OpenDefaultStream(0, 2, rate, portaudio.FramesPerBufferUnspecified, &Out) + // LogOnError(err) + EndPlayer = false + StreamChanged = true + PlayAudio = true + MainNotification := notify.NewNotification("Playing", songdata.Artist+": "+songdata.Title) + MainNotification.Timeout = 2000 + if _, err := MainNotification.Show(); err != nil { + return + } + go glib.IdleAdd(SetPlayingGuiData) + MainAdjustment.Configure(float64(0), float64(0), float64(1), float64(1), float64(10), float64(0)) + // chk(CurrentStream.Start()) + + for !EndPlayer { + if PlayAudio { + if StreamChanged { + CurrentStream.Start() + StreamChanged = false + } + _, err := OpusStr.Read(Out) + DrawnTime += float64(1920 / rate / 2) + glib.IdleAdd(UpdatePBar()) + if (err == io.EOF) || (err == io.ErrUnexpectedEOF) || (TimeInPrecent >= 1.01) || (err == portaudio.StreamIsStopped) || (err == portaudio.TimedOut) { + fmt.Println("EOF", NowPlayingIndex, len(MainQueue)) // TODO Stram recover when unexpected + err = CurrentStream.Stop() + glib.IdleAdd(SetZeroTime()) + defer CurrentStream.Close() + Out = make([]int16, 1920) + notify.CloseNotification(MainNotification.ReplacesID) + if NowPlayingIndex+1 < len(MainQueue) { + fmt.Println("Playing next song") + NowPlayingIndex++ + go StartCaching(MainQueue[NowPlayingIndex]) + if NowPlayingIndex+1 <= len(MainQueue)-1 { + // fmt.Println(songdata.Title, " +1 needs to be downloaded") + go CachingWaiter(MainQueue[NowPlayingIndex], MainQueue[NowPlayingIndex+1], 5) + } + if NowPlayingIndex+2 <= len(MainQueue)-1 { + // fmt.Println(songdata.Title, " +2 needs to be downloaded") + go CachingWaiter(MainQueue[NowPlayingIndex+1], MainQueue[NowPlayingIndex+2], 5) + } + go PlayPort() // Run new gouroutine to let func complete + EndPlayer = true + break + } else if (NowPlayingIndex + 1) == len(MainQueue) { + fmt.Println("End playing") + CurrentStream.Close() + EndPlayer = true + break + } + } else { + // if (err != portaudio.OutputUnderflowed && err != nil) { + // fmt.Println(err) + // } + err := CurrentStream.Write() + if err != nil { + // LogOnError(err) + } + + } + } else { + StreamChanged = true + CurrentStream.Stop() + } + + select { + case <-sig: + CurrentStream.Close() + EndPlayer = true + break + default: + } + + } + PlayAudio = false + glib.IdleAdd(SetZeroTime()) + fmt.Println("End playport") + AudioControlChan <- true +} +func Backgroundread(reader io.Reader, w io.WriteCloser, id string) { + fmt.Println("DEBUG: backgroundread ", id) + DownloadSyncGroup.Add(1) + readBuffer := make([]byte, MainSettings.DownladBufferSize) + for { + n, err := reader.Read(readBuffer) + if err != nil { + if err == io.EOF || err == io.ErrUnexpectedEOF { + w.Write(readBuffer[:n]) + break + } else { + fmt.Println(err) + } + } + w.Write(readBuffer[:n]) + } + w.Close() + runtime.GC() + DownloadSyncGroup.Done() + fmt.Println("Ended downloading ", id) + CachingMap[id] = true +} + +func CachingWaiter(idBlocking string, idNext string, waitTime int) { + isDownloaded, exists := CachingMap[idBlocking] + if !isDownloaded || !exists { + if waitTime == 0 { + waitTime = 3 + } + time.Sleep(time.Duration(waitTime) * time.Second) + CachingWaiter(idBlocking, idNext, waitTime-1) + } else if isDownloaded && exists { + go StartCaching(idNext) + } else if !isDownloaded && exists { + return + } +} + +func StartCaching(songID string) { + + // songdata, _ := CurrentClient.GetSong(songID) + // albmdata, _ := CurrentClient.GetAlbum(songdata.AlbumID) + + param := map[string]string{ + "maxBitRate": strconv.Itoa(int(MainSettings.Bitrate)), + "MainFormat": MainSettings.Codec, + "transcodings": MainSettings.Codec, + "estimateContentLength": "true", + } + + cacheExists := CurrentCache.Exists(songID) + _, WriteMaster, _ = CurrentCache.Get(songID) + + if !cacheExists { + reader, _ := CurrentClient.Stream(songID, param) + // go DownloadCoverArtFullSize(albmdata.CoverArt) + Backgroundread(reader, WriteMaster, songID) + } else { + fmt.Println("Playing from cache") + } +} + +// GTK Async gui update +func UpdatePBar() func() { + var up = func() { + TimeInPrecent = float64(DrawnTime / float64(TotalTime)) + MainTimeLeft.SetText(DurationToReadable(TotalTime - int(float64(DrawnTime)))) + MainTimeDrawnLabel.SetText(DurationToReadable(int(float64(DrawnTime)))) + MainAdjustment.SetValue(float64(DrawnTime / float64(TotalTime))) + } + return up +} + +// GTK Async gui update +func SetZeroTime() func() { + TotalTime = 0 + DrawnTime = 0 + TimeInPrecent = 0.0 + var up = func() { + MainPlayPauseButton.SetLabel("▶") + MainTimeLeft.SetText(":") + MainTimeDrawnLabel.SetText(":") + MainAdjustment.SetValue(TimeInPrecent) + MainTotalTimeLabel.SetLabel(":") + MainArtistLabel.SetLabel(":") + MainSongTitleLabel.SetLabel(":") + MainAlbumLabel.SetLabel(":") + + } + return up +} + +func chk(err error) { + if err != nil { + panic(err) + } + +} + +func DurationToReadable(dur int) string { + + min := dur / 60 + sec := dur % 60 + + if min >= 60 { + hr := dur / 3600 + min = (dur - (hr * 3600)) / 60 + sec = (dur - (hr * 3600) - (min * 60)) + return (PrefixZeroLook(hr) + ":" + PrefixZeroLook(min) + ":" + PrefixZeroLook(sec)) + } + return (PrefixZeroLook(min) + ":" + PrefixZeroLook(sec)) + +} + +func PrefixZeroLook(localInt int) string { + if localInt < 10 { + return ("0" + strconv.Itoa(localInt)) + } else { + return strconv.Itoa(localInt) + } + +} + +func Exists(name string) bool { + _, err := os.Stat(name) + if err == nil { + return true + } + if errors.Is(err, os.ErrNotExist) { + return false + } + return false +} + +func FindSubtitles(artist string, song string) func() { + // TODO Repair + var fun = func() { + l := lyrics.New(*MetadataClient, lyrics.WithAllProviders()) + lyric, err := l.Search(artist, song) + buf := MainSubtitlesTextView.Buffer() + if err != nil { + fmt.Printf("Lyrics for %v-%v were not found", artist, song) + buf.SetText("Not found") + } + buf.SetText(lyric) + MainSubtitlesTextView.SetBuffer(buf) + } + return fun + +} + +// AD1 +// pixbuf2, err = gdk.PixbufNewFromFileAtSize(path2, w, h) + +// gtk.LayoutNew() + +// var css string = ` +// GtkWindow { +// background-color: red; +// background-repeat: no-repeat; +// background-size: cover; +// background-position: center center; +// ` + +// var csscustom = `background-image: url("` + imgpath + `"); +// }` +// var csscustom = `background-size: cover; background-color: red; +// }` +// fmt.Println(css + csscustom) +// prov, err := gtk.CssProviderNew() +// LogOnError(err) +// err = prov.LoadFromData(css + csscustom) +// LogOnError(err) +// scrn, err := gdk.ScreenGetDefault() +// LogOnError(err) +// gtk.AddProviderForScreen(scrn, prov, 1) +// MainWindow.ShowAll() +// LogFatalOnError(err) + +// AD2 +// path := os.UserHomeDir + MainDir + DataDir + "/" + nodeID.(string) + ".opus" +// var OpusStr *opus.Stream +// if !Exists(path) { +// f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) +// chk(err) +// tee := io.TeeReader(reader, f) +// OpusStr, err = opus.NewStream(tee) +// } else { diff --git a/error_handling.go b/error_handling.go new file mode 100644 index 0000000..0b05c19 --- /dev/null +++ b/error_handling.go @@ -0,0 +1,24 @@ +package main + +import "log" + +func PanicOnError(err error) { + if err != nil { + panic(err) + } +} + +func LogFatalOnError(err error) { + if err != nil { + // log.Fatalln(err) + panic(err) + } + +} + +func LogOnError(err error) { + if err != nil { + log.Println(err) + // panic(err) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..bd631b7 --- /dev/null +++ b/go.mod @@ -0,0 +1,33 @@ +module git.itmodulo.eu/LinuxOnMobile/Sonically + +go 1.18 + +require ( + git.itmodulo.eu/LibrariesGo/lyrics-api-go-extended v0.1.45-0.20220502211740-bdde16173c5c + github.com/TheCreeper/go-notify v0.2.0 + github.com/delucks/go-subsonic v0.0.0-20220623171311-8b0662f9f006 + github.com/diamondburned/gotk4/pkg v0.0.0-20220529201008-66c7fe5d2b7c + github.com/djherbis/fscache v0.10.1 + github.com/gordonklaus/portaudio v0.0.0-20220320131553-cc649ad523c1 + github.com/hashicorp/go-retryablehttp v0.7.1 + github.com/hraban/opus v0.0.0-20220302220929-eeacdbcb92d0 + github.com/zalando/go-keyring v0.2.1 +) + +require ( + github.com/PuerkitoBio/goquery v1.8.0 // indirect + github.com/alessio/shellescape v1.4.1 // indirect + github.com/andybalholm/cascadia v1.3.1 // indirect + github.com/danieljoos/wincred v1.1.2 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/gosimple/slug v1.12.0 // indirect + github.com/gosimple/unidecode v1.0.1 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect + golang.org/x/net v0.0.0-20220630215102-69896b714898 // indirect + golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect + golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b // indirect + gopkg.in/djherbis/atime.v1 v1.0.0 // indirect + gopkg.in/djherbis/stream.v1 v1.3.1 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..59bfabb --- /dev/null +++ b/go.sum @@ -0,0 +1,87 @@ +git.itmodulo.eu/LibrariesGo/lyrics-api-go-extended v0.1.45-0.20220502211740-bdde16173c5c h1:rMlCwAUtUci5aHwQ/vnN8x5k6veHEQ39qstNTBqDZOg= +git.itmodulo.eu/LibrariesGo/lyrics-api-go-extended v0.1.45-0.20220502211740-bdde16173c5c/go.mod h1:UvHix+BErO9l3dealuksQRc2RYWXSCqxUo3HurXPGvs= +github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U= +github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI= +github.com/TheCreeper/go-notify v0.2.0 h1:akzlSD8IWx+uOZqGNwS0FYsThYuw11JkXsYiQXY5kgo= +github.com/TheCreeper/go-notify v0.2.0/go.mod h1:paZnY8fMbaOyZLQWJitGWAMrO5ot3Ow7id47cyEL1KA= +github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= +github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= +github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= +github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= +github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= +github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/delucks/go-subsonic v0.0.0-20220623171311-8b0662f9f006 h1:hFXcR3r0Vsnr+mMMwMrgm+mlAR18k/AuuEvNuMbNuSQ= +github.com/delucks/go-subsonic v0.0.0-20220623171311-8b0662f9f006/go.mod h1:vnbEuj6Z20PLcHB4rrLQAOXGMjtULfMGhRVSFPcSdUo= +github.com/diamondburned/gotk4/pkg v0.0.0-20220529201008-66c7fe5d2b7c h1:d09K0Y9HQ6/WLlFlH11dPt9TdDLIOxlztHaLUOpKa04= +github.com/diamondburned/gotk4/pkg v0.0.0-20220529201008-66c7fe5d2b7c/go.mod h1:rLH6FHos690jFgAM/GYEpMykuE/9NmN6zOvFlr8JTvE= +github.com/djherbis/fscache v0.10.1 h1:hDv+RGyvD+UDKyRYuLoVNbuRTnf2SrA2K3VyR1br9lk= +github.com/djherbis/fscache v0.10.1/go.mod h1:yyPYtkNnnPXsW+81lAcQS6yab3G2CRfnPLotBvtbf0c= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= +github.com/gordonklaus/portaudio v0.0.0-20220320131553-cc649ad523c1 h1:FgUJ91JoMbS5qWXdIpnHta1hLtw1X8n2ek5JRED3R1I= +github.com/gordonklaus/portaudio v0.0.0-20220320131553-cc649ad523c1/go.mod h1:HfYnZi/ARQKG0dwH5HNDmPCHdLiFiBf+SI7DbhW7et4= +github.com/gosimple/slug v1.12.0 h1:xzuhj7G7cGtd34NXnW/yF0l+AGNfWqwgh/IXgFy7dnc= +github.com/gosimple/slug v1.12.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ= +github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o= +github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc= +github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= +github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hraban/opus v0.0.0-20220302220929-eeacdbcb92d0 h1:kWEAL53h9DdQ2Utz2vKhgLutpSS1L6WDB37xv1VMKwU= +github.com/hraban/opus v0.0.0-20220302220929-eeacdbcb92d0/go.mod h1:YQQXrWHN3JEvCtw5ImyTCcPeU/ZLo/YMA+TpB64XdrU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/smartystreets/assertions v1.13.0 h1:Dx1kYM01xsSqKPno3aqLnrwac2LetPvN23diwyr69Qs= +github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/zalando/go-keyring v0.2.1 h1:MBRN/Z8H4U5wEKXiD67YbDAr5cj/DOStmSga70/2qKc= +github.com/zalando/go-keyring v0.2.1/go.mod h1:g63M2PPn0w5vjmEbwAX3ib5I+41zdm4esSETOn9Y6Dw= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220622184535-263ec571b305 h1:dAgbJ2SP4jD6XYfMNLVj0BF21jo2PjChrtGaAvF5M3I= +golang.org/x/net v0.0.0-20220622184535-263ec571b305/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw= +golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664 h1:wEZYwx+kK+KlZ0hpvP2Ls1Xr4+RWnlzGFwPP0aiDjIU= +golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b h1:2n253B2r0pYSmEV+UNCQoPfU/FiaizQEK5Gu4Bq4JE8= +golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/djherbis/atime.v1 v1.0.0 h1:eMRqB/JrLKocla2PBPKgQYg/p5UG4L6AUAs92aP7F60= +gopkg.in/djherbis/atime.v1 v1.0.0/go.mod h1:hQIUStKmJfvf7xdh/wtK84qe+DsTV5LnA9lzxxtPpJ8= +gopkg.in/djherbis/stream.v1 v1.3.1 h1:uGfmsOY1qqMjQQphhRBSGLyA9qumJ56exkRu9ASTjCw= +gopkg.in/djherbis/stream.v1 v1.3.1/go.mod h1:aEV8CBVRmSpLamVJfM903Npic1IKmb2qS30VAZ+sssg= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/gui.go b/gui.go new file mode 100644 index 0000000..1178161 --- /dev/null +++ b/gui.go @@ -0,0 +1,1452 @@ +package main + +import ( + "fmt" + + "github.com/TheCreeper/go-notify" + "github.com/delucks/go-subsonic" + "github.com/diamondburned/gotk4/pkg/gtk/v4" + "github.com/zalando/go-keyring" + + // "github.com/diamondburned/gotk4/pkg/gdk/v4" + "github.com/diamondburned/gotk4/pkg/core/glib" +) + +func SetUpGui() { + SettingsTabUserUsernameEntry = gtk.NewEntry() + SettingsTabUserServerEntry = gtk.NewEntry() + SettingsTabUserDeviceNameEntry = gtk.NewEntry() + SettingsTabUserPasswordEntry = gtk.NewEntry() + SettingsTabUserStatusLabel = gtk.NewLabel("- ") + SearchTabSearchEntry = gtk.NewSearchEntry() + SearchTabSongCheckButton = gtk.NewCheckButton() + SearchTabAlbumCheckButton = gtk.NewCheckButton() + SearchTabArtistCheckButton = gtk.NewCheckButton() + SearchListBoxResult = gtk.NewListBox() + MainSongTitleLabel = gtk.NewLabel(":") + MainArtistLabel = gtk.NewLabel(":") + MainAlbumLabel = gtk.NewLabel(":") + MainTimeDrawnLabel = gtk.NewLabel(":") + MainTimeLeft = gtk.NewLabel(":") + MainTotalTimeLabel = gtk.NewLabel(":") + SettingsTabAppDeleteSwitch = gtk.NewSwitch() + MainAdjustment = gtk.NewAdjustment(float64(0), float64(0), float64(1), float64(1), float64(10), float64(0)) + SettingsTabAppCodecComboBoxTest = gtk.NewComboBoxText() + QueueListBox = gtk.NewListBox() + PlayListListBox = gtk.NewListBox() + MainAlbumCover = gtk.NewImage() + MainPlayPauseButton = gtk.NewButton() // gtk.NewLabel("▶") + MainSubtitlesTextView = gtk.NewTextView() + MainNotebook = gtk.NewNotebook() + LibraryNotebook = gtk.NewNotebook() + QueueListBoxLibrary = gtk.NewListBox() + SettingsTabAppCoverSizeEntry = gtk.NewEntry() + SettingsTabAppCoverSizeEntry = gtk.NewEntry() + SettingsTabAppBlurRadiusEntry = gtk.NewEntry() + SettingsTabAppBufferSizeEntry = gtk.NewEntry() + SettingsTabAppStreamingProxySwitch = gtk.NewSwitch() + SettingsTabAppStreamingProxyEntry = gtk.NewEntry() + SettingsTabAppAuthStreamingProxySwitch = gtk.NewSwitch() + SettingsTabAppStreamingProxyUsernameEntry = gtk.NewEntry() + SettingsTabAppStreamingProxyPassEntry = gtk.NewEntry() + SettingsTabAppMetadataProxySwitch = gtk.NewSwitch() + SettingsTabAppMetadataProxyEntry = gtk.NewEntry() + SettingsTabAppAuthMetadataProxySwitch = gtk.NewSwitch() + SettingsTabAppMetadataProxyUsernameEntry = gtk.NewEntry() + SettingsTabAppMetadataProxyPassEntry = gtk.NewEntry() + + MainNotebook = gtk.NewNotebook() + LyricsNotebook = gtk.NewNotebook() + MainFlowbox = gtk.NewFlowBox() + + LyricsScrolledWindow := gtk.NewScrolledWindow() + MainFlowBoxChild1 = gtk.NewFlowBoxChild() + MainFlowBoxChild2 = gtk.NewFlowBoxChild() + MainGridFlowBoxChild2 = gtk.NewGrid() + MainGridFlowBoxChild2Grid = gtk.NewGrid() + MainLeftSeparator = gtk.NewSeparator(gtk.OrientationHorizontal) + MainRightSeparator = gtk.NewSeparator(gtk.OrientationHorizontal) + MainFlowBoxChild3 = gtk.NewFlowBoxChild() + MainAdjustment.SetUpper(100) + MainAdjustment.SetStepIncrement(1) + MainAdjustment.SetPageIncrement(10) + MainScale = gtk.NewScale(gtk.OrientationHorizontal, MainAdjustment) + + MainScaleGrid := gtk.NewGrid() + + MainFlowBoxChild4 = gtk.NewFlowBoxChild() + MainGridFlowBoxChild4 = gtk.NewGrid() + MainPreviousButton = gtk.NewButton() + MainNextButton = gtk.NewButton() + MainRepeatButton = gtk.NewButton() + MainStarredButton = gtk.NewButton() + + QueueMasterBox = gtk.NewBox(gtk.OrientationVertical, 2) + QueueSearchControlBox = gtk.NewGrid() + QueueScrolledWindow = gtk.NewScrolledWindow() + QueueSaveButton = gtk.NewButton() // + QueueClearButton = gtk.NewButton() // + QueueShuffleButton = gtk.NewButton() // + SearchControlBox = gtk.NewGrid() + QueueFlowBox := gtk.NewFlowBox() + QueueFlowBoxChild1 = gtk.NewFlowBoxChild() + + LibraryFlowBox1 = gtk.NewFlowBox() + LibraryFlowBox2 = gtk.NewFlowBox() + LibraryFlowBox3 = gtk.NewFlowBox() + LibraryFlowBox4 = gtk.NewFlowBox() + LibraryPlaylistMainBox = gtk.NewBox(gtk.OrientationVertical, 0) + LibraryStarredMainBox = gtk.NewBox(gtk.OrientationVertical, 0) + LibraryLibraryMainBox = gtk.NewBox(gtk.OrientationVertical, 0) + LibraryPodcastMainBox = gtk.NewBox(gtk.OrientationVertical, 0) + LibraryFlowBox1Child1 = gtk.NewFlowBoxChild() + LibraryFlowBox2Child1 = gtk.NewFlowBoxChild() + LibraryFlowBox3Child1 = gtk.NewFlowBoxChild() + LibraryFlowBox4Child1 = gtk.NewFlowBoxChild() + LibraryPlaylistMainControlGrid = gtk.NewGrid() + LibraryStarredMainControlGrid = gtk.NewGrid() + LibraryLibraryMainControlGrid = gtk.NewGrid() + LibraryPodcastMainControlGrid = gtk.NewGrid() + LibraryPlaylistBackButton = gtk.NewButton() + LibraryPlaylistRefreshButton = gtk.NewButton() + LibraryStarredBackButton = gtk.NewButton() + LibraryStarredRefreshButton = gtk.NewButton() + LibraryLibraryBackButton = gtk.NewButton() + LibraryLibraryRefreshButton = gtk.NewButton() + LibraryPodcastBackButton = gtk.NewButton() + LibraryPodcastRefreshButton = gtk.NewButton() + LibraryStarredListBox = gtk.NewListBox() + LibraryPodcastListBox = gtk.NewListBox() + LibraryPlaylistMainScrolledWindow = gtk.NewScrolledWindow() + LibraryPodcastListBoxScrolledWindow = gtk.NewScrolledWindow() + LibraryStarredListBoxScrolledWindow = gtk.NewScrolledWindow() + QueueListBoxLibraryScrolledWindow = gtk.NewScrolledWindow() + + SettingsAccountScrolledWindow = gtk.NewScrolledWindow() + SettingsAppScrolledWindow = gtk.NewScrolledWindow() + SettingsCreditsScrolledWindow = gtk.NewScrolledWindow() + + SettingsAccountsFlowBox = gtk.NewFlowBox() + SettingsAppFlowBox = gtk.NewFlowBox() + SettingsCreditsFlowBox = gtk.NewFlowBox() + + SettingsAccountFlowboxChild1 = gtk.NewFlowBoxChild() + SettingsAccountFlowboxChild2 = gtk.NewFlowBoxChild() + + SettingsNotebook = gtk.NewNotebook() + SettingsAccountBox = gtk.NewBox(gtk.OrientationVertical, 3) + SettingsAccountLoginGrid = gtk.NewGrid() + SettingsAccountButtonsGrid = gtk.NewGrid() + SettingsAccountUserListbox = gtk.NewListBox() + + SettingsAccountUsernameLabel = gtk.NewLabel("Username: ") + SettingsAccountPasswordLabel = gtk.NewLabel("Password: ") + SettingsAccountPLayerNameLabel = gtk.NewLabel("Player Name: ") + SettingsAccountServerLabel = gtk.NewLabel("Server: ") + SettingsAccountTestButton = gtk.NewButton() + SettingsAccountSaveButton = gtk.NewButton() + SettingsAccountLoadButton = gtk.NewButton() + + SettingsAppScrolledWindow = gtk.NewScrolledWindow() + SettingsAppFlowBoxChild1 = gtk.NewFlowBoxChild() + SettingsAppFlowBoxChild2 = gtk.NewFlowBoxChild() + SettingsAppFlowBoxChild3 = gtk.NewFlowBoxChild() + SettingsAppFlowBoxChild4 = gtk.NewFlowBoxChild() + SettingsAppGrid1 = gtk.NewGrid() + SettingsAppGrid2 = gtk.NewGrid() + SettingsAppGrid3 = gtk.NewGrid() + SettingsAppGrid4 = gtk.NewGrid() + SettingsTabAppBitrateEntry = gtk.NewEntry() + + SettingsAppLabelGrid1 = gtk.NewLabel("Local") + SettingsAppLabelGrid2 = gtk.NewLabel("Transcoding") + SettingsAppLabelGrid3 = gtk.NewLabel("Net") + + SettingsAppLabelGrid1DeleteOnClose = gtk.NewLabel("Clear cache on close") + SettingsAppLabelGrid1CoverSideSize = gtk.NewLabel("Cover Side Size (px)") + SettingsAppLabelGrid1BackgroundBlurRadius = gtk.NewLabel("Cover blur radius") + SettingsTabAppHideHeaderBarSwitch = gtk.NewSwitch() + SettingsAppLabelHideHeaderBar = gtk.NewLabel("Hide Headerbar") + + SettingsAppLabelGrid2Codec = gtk.NewLabel("Codec") + SettingsAppLabelGrid2Bitrate = gtk.NewLabel("Bitrate") + SettingsAppLabelGrid2DownloadBufferSize = gtk.NewLabel("Download Buffer Size") + + SettingsAppLabelGrid3UseProxyForStreaming = gtk.NewLabel("Use proxy for streaming") + SettingsAppLabelGrid3AuthenticateStreamingProxy = gtk.NewLabel("Authenticate") + SettingsAppLabelGrid3UseProxyForMetadata = gtk.NewLabel("User proxy for Metadata") + SettingsAppLabelGrid3AuthenticateMetadataProxy = gtk.NewLabel("Authenticate") + + SettingsAppLabelGrid4 = gtk.NewLabel("External Services") + SettingsAppLabelGrid4Metadata = gtk.NewLabel("Metadata") + SettingsTabAppMetadataComboBoxTest = gtk.NewComboBoxText() + + SearchFlowBox := gtk.NewFlowBox() + SearchMasterBox = gtk.NewBox(gtk.OrientationVertical, 0) + SearchFlowBoxChild1 = gtk.NewFlowBoxChild() + SearchScrolledWindow = gtk.NewScrolledWindow() + + // BELOW + + // Main Notebook + + MainNotebook.SetTabPos(gtk.PosBottom) + MainWindow.SetChild(MainNotebook) + + // Home page + MainFlowbox.SetOrientation(gtk.OrientationVertical) + MainNotebook.AppendPage(LyricsNotebook, gtk.NewLabel("⌂")) + LyricsNotebook.AppendPage(MainFlowbox, gtk.NewLabel("⌂")) + LyricsNotebook.AppendPage(LyricsScrolledWindow, gtk.NewLabel("♪")) + MainFlowBoxChild1.SetChild(MainAlbumCover) + MainFlowBoxChild2.SetChild(MainGridFlowBoxChild2) + MainGridFlowBoxChild2.Attach(MainSongTitleLabel, 0, 0, 1, 1) + MainGridFlowBoxChild2.Attach(MainArtistLabel, 0, 1, 1, 1) + MainGridFlowBoxChild2.Attach(MainAlbumLabel, 0, 2, 1, 1) + MainGridFlowBoxChild2.Attach(MainGridFlowBoxChild2Grid, 0, 3, 1, 1) + // MainGridFlowBoxChild2Grid.Attach(MainTimeDrawnLabel, 0, 0, 1, 1) + MainGridFlowBoxChild2Grid.Attach(MainLeftSeparator, 1, 0, 1, 1) + // MainGridFlowBoxChild2Grid.Attach(MainTotalTimeLabel, 2, 0, 1, 1) + MainGridFlowBoxChild2Grid.Attach(MainRightSeparator, 3, 0, 1, 1) + // MainGridFlowBoxChild2Grid.Attach(MainTimeLeft, 4, 0, 1, 1) + MainFlowBoxChild3.SetChild(MainScaleGrid) + MainFlowBoxChild4.SetChild(MainGridFlowBoxChild4) + MainFlowbox.SetMaxChildrenPerLine(2) + MainFlowbox.SetMinChildrenPerLine(0) + MainPreviousButton.SetChild(gtk.NewLabel("◀◀")) + MainNextButton.SetChild(gtk.NewLabel("▶▶")) + MainRepeatButton.SetChild(gtk.NewLabel("⟳")) + MainStarredButton.SetChild(gtk.NewLabel("☆")) + MainGridFlowBoxChild4.Attach(MainPreviousButton, 1, 0, 1, 1) + MainGridFlowBoxChild4.Attach(MainPlayPauseButton, 2, 0, 1, 1) + MainGridFlowBoxChild4.Attach(MainNextButton, 3, 0, 1, 1) + MainGridFlowBoxChild4.Attach(MainRepeatButton, 0, 0, 1, 1) + MainGridFlowBoxChild4.Attach(MainStarredButton, 4, 0, 1, 1) + MainFlowbox.SetOrientation(gtk.OrientationHorizontal) + MainFlowbox.Insert(MainFlowBoxChild1, 0) + MainFlowbox.Insert(MainFlowBoxChild2, 1) + MainFlowbox.Insert(MainFlowBoxChild3, 2) + MainFlowbox.Insert(MainFlowBoxChild4, 3) + MainFlowbox.SetVAlign(gtk.AlignFill) + MainFlowbox.SetHAlign(gtk.AlignFill) + MainScale.SetHExpand(true) + MainScale.SetShowFillLevel(true) + MainScale.SetDrawValue(false) + MainScale.SetSizeRequest(294, 1) + // MainAdjustment.SetUpper(100) + // MainAdjustment.SetStepIncrement(float64(1.0)) + // MainAdjustment.SetPageIncrement(float64(10.0)) + MainAlbumCover.SetSizeRequest(300, 300) + MainAlbumCover.SetHAlign(gtk.AlignFill) + MainAlbumCover.SetVAlign(gtk.AlignFill) + MainGridFlowBoxChild2.SetHAlign(gtk.AlignCenter) + MainGridFlowBoxChild2.SetVAlign(gtk.AlignCenter) + MainGridFlowBoxChild2Grid.SetHAlign(gtk.AlignCenter) + MainGridFlowBoxChild2Grid.SetVAlign(gtk.AlignCenter) + MainLeftSeparator.SetHExpand(true) + MainRightSeparator.SetVExpand(true) + MainLeftSeparator.SetOpacity(0.0) + MainRightSeparator.SetOpacity(0.0) + MainLeftSeparator.SetSizeRequest(90, 0) + MainRightSeparator.SetSizeRequest(90, 0) + MainGridFlowBoxChild4.SetHAlign(gtk.AlignCenter) + MainGridFlowBoxChild4.SetVAlign(gtk.AlignCenter) + MainScale.SetShowFillLevel(true) + + MainScaleGrid.Attach(MainScale, 0, 1, 3, 1) + MainScaleGrid.Attach(MainTimeDrawnLabel, 0, 0, 1, 1) + MainScaleGrid.Attach(MainTotalTimeLabel, 1, 0, 1, 1) + MainScaleGrid.Attach(MainTimeLeft, 2, 0, 1, 1) + + MainFlowbox.SetSelectionMode(gtk.SelectionNone) + MainPlayPauseButton.SetLabel("▶") + // MainAdjustment + + // Queue page + + MainNotebook.AppendPage(QueueFlowBox, gtk.NewLabel("☰")) + QueueFlowBox.SetSelectionMode(gtk.SelectionNone) + QueueMasterBox.Append(QueueSearchControlBox) + QueueMasterBox.Append(QueueScrolledWindow) + QueueScrolledWindow.SetChild(QueueListBox) + QueueSearchControlBox.Attach(QueueSaveButton, 0, 0, 1, 1) + QueueSearchControlBox.Attach(QueueClearButton, 1, 0, 1, 1) + QueueSearchControlBox.Attach(QueueShuffleButton, 2, 0, 1, 1) + QueueSaveButton.SetChild(gtk.NewLabel("↧")) + QueueClearButton.SetChild(gtk.NewLabel("✘")) + QueueShuffleButton.SetChild(gtk.NewLabel("shuffle")) + + QueueFlowBox.SetOrientation(gtk.OrientationVertical) + QueueFlowBox.Insert(QueueFlowBoxChild1, 0) + QueueFlowBoxChild1.SetChild(QueueMasterBox) + QueueSearchControlBox.SetHAlign(gtk.AlignEnd) + QueueSearchControlBox.SetVAlign(gtk.AlignFill) + QueueFlowBox.SetMaxChildrenPerLine(1) + QueueFlowBox.SetMinChildrenPerLine(0) + QueueListBox.SetSelectionMode(gtk.SelectionSingle) + QueueFlowBox.SetHAlign(gtk.AlignFill) + QueueFlowBox.SetVAlign(gtk.AlignFill) + QueueFlowBox.SetHExpand(true) + QueueFlowBox.SetVExpand(true) + QueueScrolledWindow.SetHExpand(true) + QueueScrolledWindow.SetVExpand(true) + QueueScrolledWindow.SetVAlign(gtk.AlignFill) + QueueScrolledWindow.SetHAlign(gtk.AlignFill) + + // Search page + + MainNotebook.AppendPage(SearchFlowBox, gtk.NewLabel("⌕")) + SearchFlowBox.SetSelectionMode(gtk.SelectionNone) + SearchMasterBox.Append(SearchControlBox) + SearchControlBox.Attach(SearchTabAlbumCheckButton, 1, 1, 1, 1) + SearchControlBox.Attach(SearchTabSongCheckButton, 0, 1, 1, 1) + SearchControlBox.Attach(SearchTabArtistCheckButton, 2, 1, 1, 1) + SearchControlBox.Attach(SearchTabSearchEntry, 0, 0, 2, 1) + SearchMasterBox.Append(SearchScrolledWindow) + SearchFlowBoxChild1.SetChild(SearchMasterBox) + SearchScrolledWindow.SetChild(SearchListBoxResult) + SearchFlowBox.Insert(SearchFlowBoxChild1, 0) + SearchFlowBox.SetOrientation(gtk.OrientationVertical) + SearchFlowBoxChild1.SetHAlign(gtk.AlignFill) + SearchFlowBoxChild1.SetVAlign(gtk.AlignFill) + SearchFlowBox.SetHAlign(gtk.AlignFill) + SearchFlowBox.SetVAlign(gtk.AlignFill) + SearchFlowBox.SetMaxChildrenPerLine(1) + SearchFlowBox.SetMinChildrenPerLine(0) + SearchListBoxResult.SetHExpand(true) + SearchListBoxResult.SetVExpand(true) + SearchListBoxResult.SetHAlign(gtk.AlignFill) + SearchListBoxResult.SetVAlign(gtk.AlignFill) + SearchFlowBoxChild1.SetHExpand(true) + SearchFlowBoxChild1.SetVExpand(true) + SearchFlowBox.SetHExpand(true) + SearchFlowBox.SetVExpand(true) + SearchFlowBox.SetVAlign(gtk.AlignFill) + SearchFlowBox.SetHAlign(gtk.AlignFill) + SearchControlBox.SetHAlign(gtk.AlignCenter) + SearchControlBox.SetVAlign(gtk.AlignCenter) + SearchTabSongCheckButton.SetActive(true) + SearchTabSongCheckButton.SetLabel("Songs") + SearchTabArtistCheckButton.SetLabel("Artists") + SearchTabAlbumCheckButton.SetLabel("Albums") + + // Library page + + MainNotebook.AppendPage(LibraryNotebook, gtk.NewLabel("||\\")) + LibraryNotebook.AppendPage(LibraryFlowBox1, gtk.NewLabel("playlists")) + LibraryNotebook.AppendPage(LibraryFlowBox2, gtk.NewLabel("☆")) + LibraryNotebook.AppendPage(LibraryFlowBox3, gtk.NewLabel("||\\")) + LibraryNotebook.AppendPage(LibraryFlowBox4, gtk.NewLabel("podcasts")) + LibraryFlowBox1.Insert(LibraryFlowBox1Child1, 0) + LibraryFlowBox2.Insert(LibraryFlowBox2Child1, 0) + LibraryFlowBox3.Insert(LibraryFlowBox3Child1, 0) + LibraryFlowBox4.Insert(LibraryFlowBox4Child1, 0) + LibraryFlowBox1.SetSelectionMode(gtk.SelectionNone) + LibraryFlowBox2.SetSelectionMode(gtk.SelectionNone) + LibraryFlowBox3.SetSelectionMode(gtk.SelectionNone) + LibraryFlowBox4.SetSelectionMode(gtk.SelectionNone) + LibraryFlowBox1Child1.SetChild(LibraryPlaylistMainBox) + LibraryFlowBox2Child1.SetChild(LibraryStarredMainBox) + LibraryFlowBox3Child1.SetChild(LibraryLibraryMainBox) + LibraryFlowBox4Child1.SetChild(LibraryPodcastMainBox) + LibraryPlaylistMainBox.Append(LibraryPlaylistMainControlGrid) + LibraryStarredMainBox.Append(LibraryStarredMainControlGrid) + LibraryLibraryMainBox.Append(LibraryLibraryMainControlGrid) + LibraryPodcastMainBox.Append(LibraryPodcastMainControlGrid) + LibraryPlaylistMainControlGrid.Attach(LibraryPlaylistBackButton, 0, 0, 1, 1) + LibraryPlaylistMainControlGrid.Attach(LibraryPlaylistRefreshButton, 1, 0, 1, 1) + LibraryPlaylistBackButton.SetLabel("↵") + LibraryPlaylistRefreshButton.SetLabel("⟳") + + LibraryPlaylistMainScrolledWindow.SetHExpand(true) + LibraryPlaylistMainScrolledWindow.SetVExpand(true) + LibraryPlaylistMainScrolledWindow.SetVAlign(gtk.AlignFill) + LibraryPlaylistMainScrolledWindow.SetHAlign(gtk.AlignFill) + LibraryPlaylistMainScrolledWindow.SetChild(PlayListListBox) + LibraryPlaylistMainBox.Append(LibraryPlaylistMainScrolledWindow) + LibraryStarredMainControlGrid.Attach(LibraryStarredBackButton, 0, 0, 1, 1) + LibraryStarredMainControlGrid.Attach(LibraryStarredRefreshButton, 1, 0, 1, 1) + LibraryStarredBackButton.SetLabel("↵") + LibraryStarredRefreshButton.SetLabel("⟳") + + LibraryStarredListBoxScrolledWindow.SetHExpand(true) + LibraryStarredListBoxScrolledWindow.SetVExpand(true) + LibraryStarredListBoxScrolledWindow.SetVAlign(gtk.AlignFill) + LibraryStarredListBoxScrolledWindow.SetHAlign(gtk.AlignFill) + + LibraryStarredListBoxScrolledWindow.SetChild(LibraryStarredListBox) + LibraryStarredMainBox.Append(LibraryStarredListBoxScrolledWindow) + LibraryLibraryMainControlGrid.Attach(LibraryLibraryBackButton, 0, 0, 1, 1) + LibraryLibraryMainControlGrid.Attach(LibraryLibraryRefreshButton, 1, 0, 1, 1) + LibraryLibraryBackButton.SetLabel("↵") + LibraryLibraryRefreshButton.SetLabel("⟳") + + QueueListBoxLibraryScrolledWindow.SetHExpand(true) + QueueListBoxLibraryScrolledWindow.SetVExpand(true) + QueueListBoxLibraryScrolledWindow.SetVAlign(gtk.AlignFill) + QueueListBoxLibraryScrolledWindow.SetHAlign(gtk.AlignFill) + QueueListBoxLibraryScrolledWindow.SetChild(QueueListBoxLibrary) + + LibraryLibraryMainBox.Append(QueueListBoxLibraryScrolledWindow) + LibraryPodcastMainControlGrid.Attach(LibraryPodcastBackButton, 0, 0, 1, 1) + LibraryPodcastMainControlGrid.Attach(LibraryPodcastRefreshButton, 1, 0, 1, 1) + LibraryPodcastBackButton.SetLabel("↵") + LibraryPodcastRefreshButton.SetLabel("⟳") + + LibraryPodcastListBoxScrolledWindow.SetHExpand(true) + LibraryPodcastListBoxScrolledWindow.SetVExpand(true) + LibraryPodcastListBoxScrolledWindow.SetVAlign(gtk.AlignFill) + LibraryPodcastListBoxScrolledWindow.SetHAlign(gtk.AlignFill) + LibraryPodcastListBoxScrolledWindow.SetChild(LibraryPodcastListBox) + LibraryPodcastMainBox.Append(LibraryPodcastListBoxScrolledWindow) + + LibraryFlowBox1.SetHExpand(true) + LibraryFlowBox1.SetVExpand(true) + LibraryFlowBox2.SetHExpand(true) + LibraryFlowBox2.SetVExpand(true) + LibraryFlowBox3.SetHExpand(true) + LibraryFlowBox3.SetVExpand(true) + LibraryFlowBox4.SetHExpand(true) + LibraryFlowBox4.SetVExpand(true) + LibraryFlowBox1Child1.SetHAlign(gtk.AlignFill) + LibraryFlowBox1Child1.SetVAlign(gtk.AlignFill) + LibraryFlowBox2Child1.SetHAlign(gtk.AlignFill) + LibraryFlowBox2Child1.SetVAlign(gtk.AlignFill) + LibraryFlowBox3Child1.SetHAlign(gtk.AlignFill) + LibraryFlowBox3Child1.SetVAlign(gtk.AlignFill) + LibraryFlowBox4Child1.SetHAlign(gtk.AlignFill) + LibraryFlowBox4Child1.SetVAlign(gtk.AlignFill) + LibraryPlaylistMainBox.SetHExpand(true) + LibraryStarredMainBox.SetHExpand(true) + LibraryLibraryMainBox.SetHExpand(true) + LibraryPodcastMainBox.SetHExpand(true) + LibraryPlaylistMainBox.SetVExpand(true) + LibraryStarredMainBox.SetVExpand(true) + LibraryLibraryMainBox.SetVExpand(true) + LibraryPodcastMainBox.SetVExpand(true) + PlayListListBox.SetVAlign(gtk.AlignFill) + LibraryStarredListBox.SetVAlign(gtk.AlignFill) + QueueListBoxLibrary.SetVAlign(gtk.AlignFill) + LibraryPodcastListBox.SetVAlign(gtk.AlignFill) + PlayListListBox.SetHAlign(gtk.AlignFill) + LibraryStarredListBox.SetHAlign(gtk.AlignFill) + QueueListBoxLibrary.SetHAlign(gtk.AlignFill) + LibraryPodcastListBox.SetHAlign(gtk.AlignFill) + LibraryPlaylistMainControlGrid.SetHAlign(gtk.AlignEnd) + LibraryPlaylistMainControlGrid.SetVAlign(gtk.AlignFill) + LibraryStarredMainControlGrid.SetHAlign(gtk.AlignEnd) + LibraryStarredMainControlGrid.SetVAlign(gtk.AlignFill) + LibraryLibraryMainControlGrid.SetHAlign(gtk.AlignEnd) + LibraryLibraryMainControlGrid.SetVAlign(gtk.AlignFill) + LibraryPodcastMainControlGrid.SetHAlign(gtk.AlignEnd) + LibraryPodcastMainControlGrid.SetVAlign(gtk.AlignFill) + LibraryFlowBox1.SetMaxChildrenPerLine(1) + LibraryFlowBox1.SetMinChildrenPerLine(0) + LibraryFlowBox2.SetMaxChildrenPerLine(1) + LibraryFlowBox2.SetMinChildrenPerLine(0) + LibraryFlowBox3.SetMaxChildrenPerLine(1) + LibraryFlowBox3.SetMinChildrenPerLine(0) + LibraryFlowBox4.SetMaxChildrenPerLine(1) + LibraryFlowBox4.SetMinChildrenPerLine(0) + + // Settings page + + SettingsAccountFlowboxChild1.SetHExpand(true) + SettingsAccountFlowboxChild1.SetVExpand(true) + SettingsAccountFlowboxChild2.SetHExpand(true) + SettingsAccountFlowboxChild2.SetVExpand(true) + SettingsAccountUserListbox.SetHExpand(true) + SettingsAccountUserListbox.SetVExpand(true) + SettingsAccountLoginGrid.SetHExpand(true) + SettingsAccountLoginGrid.SetVExpand(true) + SettingsAccountUserListbox.SetHExpand(true) + SettingsAccountUserListbox.SetVExpand(true) + + SettingsAccountScrolledWindow.SetChild(SettingsAccountsFlowBox) + SettingsAppScrolledWindow.SetChild(SettingsAppFlowBox) + SettingsCreditsScrolledWindow.SetChild(SettingsCreditsFlowBox) + + MainNotebook.AppendPage(SettingsNotebook, gtk.NewLabel("⚙")) + SettingsNotebook.AppendPage(SettingsAccountScrolledWindow, gtk.NewLabel("Account")) + SettingsNotebook.AppendPage(SettingsAppScrolledWindow, gtk.NewLabel("App")) + SettingsNotebook.AppendPage(SettingsCreditsScrolledWindow, gtk.NewLabel("Credits")) + + SettingsAccountsFlowBox.SetSelectionMode(gtk.SelectionNone) + SettingsAppFlowBox.SetSelectionMode(gtk.SelectionNone) + SettingsCreditsFlowBox.SetSelectionMode(gtk.SelectionNone) + + SettingsAccountsFlowBox.SetMaxChildrenPerLine(1) + SettingsAccountsFlowBox.SetMinChildrenPerLine(0) + SettingsAccountsFlowBox.SetHAlign(gtk.AlignFill) + SettingsAccountsFlowBox.SetVAlign(gtk.AlignFill) + SettingsAccountFlowboxChild1.SetVAlign(gtk.AlignFill) + SettingsAccountFlowboxChild1.SetHAlign(gtk.AlignCenter) + SettingsAccountFlowboxChild2.SetHExpand(true) + + SettingsAccountTestButton.SetLabel("Test") + SettingsAccountSaveButton.SetLabel("Save") + SettingsAccountLoadButton.SetLabel("Load") + + SettingsAppFlowBox.SetMaxChildrenPerLine(1) + SettingsAppFlowBox.SetMinChildrenPerLine(0) + SettingsCreditsFlowBox.SetMaxChildrenPerLine(1) + SettingsCreditsFlowBox.SetMinChildrenPerLine(0) + SettingsAccountBox.Append(SettingsAccountLoginGrid) + SettingsAccountBox.Append(SettingsAccountButtonsGrid) + SettingsAccountFlowboxChild1.SetChild(SettingsAccountBox) + SettingsAccountFlowboxChild2.SetChild(SettingsAccountUserListbox) + SettingsAccountsFlowBox.Insert(SettingsAccountFlowboxChild1, 0) + SettingsAccountsFlowBox.Insert(SettingsAccountFlowboxChild2, 1) + SettingsAccountLoginGrid.Attach(SettingsTabUserUsernameEntry, 1, 0, 1, 1) + SettingsAccountLoginGrid.Attach(SettingsTabUserPasswordEntry, 1, 1, 1, 1) + SettingsAccountLoginGrid.Attach(SettingsTabUserDeviceNameEntry, 1, 2, 1, 1) + SettingsAccountLoginGrid.Attach(SettingsTabUserServerEntry, 1, 3, 1, 1) + SettingsAccountLoginGrid.Attach(SettingsAccountUsernameLabel, 0, 0, 1, 1) + SettingsAccountLoginGrid.Attach(SettingsAccountPasswordLabel, 0, 1, 1, 1) + SettingsAccountLoginGrid.Attach(SettingsAccountPLayerNameLabel, 0, 2, 1, 1) + SettingsAccountLoginGrid.Attach(SettingsAccountServerLabel, 0, 3, 1, 1) + SettingsAccountButtonsGrid.Attach(SettingsTabUserStatusLabel, 0, 0, 1, 1) + SettingsAccountButtonsGrid.Attach(SettingsAccountTestButton, 1, 0, 1, 1) + SettingsAccountButtonsGrid.Attach(SettingsAccountSaveButton, 2, 0, 1, 1) + SettingsAccountButtonsGrid.Attach(SettingsAccountLoadButton, 3, 0, 1, 1) + SettingsTabUserPasswordEntry.SetInputPurpose(gtk.InputPurposePassword) + SettingsTabUserPasswordEntry.SetInvisibleChar('●') + SettingsTabUserPasswordEntry.SetVisibility(false) + + SettingsTabAppAuthMetadataProxySwitch.SetHAlign(gtk.AlignEnd) + SettingsTabAppAuthStreamingProxySwitch.SetHAlign(gtk.AlignEnd) + SettingsTabAppDeleteSwitch.SetHAlign(gtk.AlignEnd) + SettingsTabAppHideHeaderBarSwitch.SetHAlign(gtk.AlignEnd) + SettingsTabAppMetadataProxySwitch.SetHAlign(gtk.AlignEnd) + SettingsTabAppStreamingProxySwitch.SetHAlign(gtk.AlignEnd) + + SettingsTabAppAuthMetadataProxySwitch.SetHExpand(false) + SettingsTabAppAuthStreamingProxySwitch.SetHExpand(false) + SettingsTabAppDeleteSwitch.SetHExpand(false) + SettingsTabAppHideHeaderBarSwitch.SetHExpand(false) + SettingsTabAppMetadataProxySwitch.SetHExpand(false) + SettingsTabAppStreamingProxySwitch.SetHExpand(false) + + SettingsAppLabelGrid1DeleteOnClose.SetHAlign(gtk.AlignStart) + SettingsAppLabelGrid1CoverSideSize.SetHAlign(gtk.AlignStart) + SettingsAppLabelGrid1BackgroundBlurRadius.SetHAlign(gtk.AlignStart) + SettingsAppLabelHideHeaderBar.SetHAlign(gtk.AlignStart) + SettingsAppLabelGrid2Codec.SetHAlign(gtk.AlignStart) + SettingsAppLabelGrid2Bitrate.SetHAlign(gtk.AlignStart) + SettingsAppLabelGrid2DownloadBufferSize.SetHAlign(gtk.AlignStart) + SettingsAppLabelGrid3UseProxyForStreaming.SetHAlign(gtk.AlignStart) + SettingsAppLabelGrid3UseProxyForMetadata.SetHAlign(gtk.AlignStart) + SettingsAppLabelGrid3AuthenticateMetadataProxy.SetHAlign(gtk.AlignStart) + SettingsAppLabelGrid3AuthenticateStreamingProxy.SetHAlign(gtk.AlignStart) + SettingsAppLabelGrid4Metadata.SetHAlign(gtk.AlignStart) + + SettingsAppLabelGrid1DeleteOnClose.SetHExpand(true) + SettingsAppLabelGrid1CoverSideSize.SetHExpand(true) + SettingsAppLabelGrid1BackgroundBlurRadius.SetHExpand(true) + SettingsAppLabelHideHeaderBar.SetHExpand(true) + SettingsAppLabelGrid2Codec.SetHExpand(true) + SettingsAppLabelGrid2Bitrate.SetHExpand(true) + SettingsAppLabelGrid2DownloadBufferSize.SetHExpand(true) + SettingsAppLabelGrid3UseProxyForStreaming.SetHExpand(true) + SettingsAppLabelGrid3UseProxyForMetadata.SetHExpand(true) + SettingsAppLabelGrid3AuthenticateMetadataProxy.SetHExpand(true) + SettingsAppLabelGrid3AuthenticateStreamingProxy.SetHExpand(true) + SettingsAppLabelGrid4Metadata.SetHExpand(true) + + SettingsAppFlowBox.Insert(SettingsAppFlowBoxChild1, 0) + SettingsAppFlowBox.Insert(SettingsAppFlowBoxChild2, 1) + SettingsAppFlowBox.Insert(SettingsAppFlowBoxChild4, 2) + SettingsAppFlowBox.Insert(SettingsAppFlowBoxChild3, 3) + + SettingsAppFlowBoxChild1.SetChild(SettingsAppGrid1) + SettingsAppFlowBoxChild2.SetChild(SettingsAppGrid2) + SettingsAppFlowBoxChild3.SetChild(SettingsAppGrid3) + SettingsAppFlowBoxChild4.SetChild(SettingsAppGrid4) + + SettingsAppGrid1.Attach(SettingsAppLabelGrid1, 0, 0, 2, 1) + SettingsAppGrid1.Attach(SettingsAppLabelGrid1DeleteOnClose, 0, 1, 1, 1) + SettingsAppGrid1.Attach(SettingsAppLabelGrid1CoverSideSize, 0, 2, 1, 1) + SettingsAppGrid1.Attach(SettingsAppLabelGrid1BackgroundBlurRadius, 0, 3, 1, 1) + SettingsAppGrid1.Attach(SettingsTabAppDeleteSwitch, 1, 1, 1, 1) + SettingsAppGrid1.Attach(SettingsTabAppCoverSizeEntry, 1, 2, 1, 1) + SettingsAppGrid1.Attach(SettingsTabAppBlurRadiusEntry, 1, 3, 1, 1) + SettingsAppGrid1.Attach(SettingsAppLabelHideHeaderBar, 0, 4, 1, 1) + SettingsAppGrid1.Attach(SettingsTabAppHideHeaderBarSwitch, 1, 4, 1, 1) + + SettingsAppGrid2.Attach(SettingsAppLabelGrid2, 0, 0, 2, 1) + SettingsAppGrid2.Attach(SettingsAppLabelGrid2Codec, 0, 1, 1, 1) + SettingsAppGrid2.Attach(SettingsAppLabelGrid2Bitrate, 0, 2, 1, 1) + SettingsAppGrid2.Attach(SettingsAppLabelGrid2DownloadBufferSize, 0, 3, 1, 1) + SettingsAppGrid2.Attach(SettingsTabAppCodecComboBoxTest, 1, 1, 1, 1) + SettingsAppGrid2.Attach(SettingsTabAppBitrateEntry, 1, 2, 1, 1) + SettingsAppGrid2.Attach(SettingsTabAppBufferSizeEntry, 1, 3, 1, 1) + + SettingsAppGrid3.Attach(SettingsAppLabelGrid3, 0, 0, 2, 1) + + SettingsAppGrid3.Attach(SettingsTabAppStreamingProxySwitch, 1, 1, 1, 1) + SettingsAppGrid3.Attach(SettingsAppLabelGrid3UseProxyForStreaming, 0, 1, 1, 1) + SettingsAppGrid3.Attach(SettingsTabAppStreamingProxyEntry, 0, 2, 2, 1) + SettingsAppGrid3.Attach(SettingsAppLabelGrid3AuthenticateStreamingProxy, 0, 3, 1, 1) + SettingsAppGrid3.Attach(SettingsTabAppAuthStreamingProxySwitch, 1, 3, 1, 1) + SettingsAppGrid3.Attach(SettingsTabAppStreamingProxyUsernameEntry, 0, 4, 1, 1) + SettingsAppGrid3.Attach(SettingsTabAppStreamingProxyPassEntry, 1, 4, 1, 1) + + SettingsTabAppMetadataComboBoxTest.InsertText(0, "None") + SettingsTabAppMetadataComboBoxTest.InsertText(1, "Server") + SettingsTabAppMetadataComboBoxTest.InsertText(2, "Sonically") + + SettingsAppGrid4.Attach(SettingsAppLabelGrid4, 0, 0, 2, 1) + SettingsAppGrid4.Attach(SettingsAppLabelGrid4Metadata, 0, 1, 1, 1) + SettingsAppGrid4.Attach(SettingsTabAppMetadataComboBoxTest, 1, 1, 1, 1) + SettingsAppGrid4.Attach(SettingsAppLabelGrid3UseProxyForMetadata, 0, 2, 1, 1) + SettingsAppGrid4.Attach(SettingsTabAppMetadataProxySwitch, 1, 2, 1, 1) + SettingsAppGrid4.Attach(SettingsTabAppMetadataProxyEntry, 0, 3, 2, 1) + SettingsAppGrid4.Attach(SettingsAppLabelGrid3AuthenticateMetadataProxy, 0, 4, 1, 1) + SettingsAppGrid4.Attach(SettingsTabAppAuthMetadataProxySwitch, 1, 4, 1, 1) + SettingsAppGrid4.Attach(SettingsTabAppMetadataProxyUsernameEntry, 0, 5, 1, 1) + SettingsAppGrid4.Attach(SettingsTabAppMetadataProxyPassEntry, 1, 5, 1, 1) + + SettingsCreditsFlowBoxChild1 = gtk.NewFlowBoxChild() + SettingsCreditsLabel1 = gtk.NewLabel("") + // "Website: https://git.itmodulo.eu/LinuxOnMobile/Sonically \nIssue Tracker: https://codeberg.org/LinuxOnMobile/Sonically/issues \nLicense: GPL3.0 \nAuthor: https://itmodulo.eu/" + + SettingsCreditsFlowBox.Insert(SettingsCreditsFlowBoxChild1, 0) + SettingsCreditsFlowBoxChild1.SetChild(SettingsCreditsLabel1) + + SettingsTabAppCodecComboBoxTest.InsertText(0, "opus") + SettingsTabAppCodecComboBoxTest.InsertText(1, "flac") + SettingsTabAppCodecComboBoxTest.InsertText(2, "ogg") + SettingsTabAppCodecComboBoxTest.InsertText(3, "mp3") + + SettingsTabUserUsernameEntry.SetPlaceholderText("Sonic user") + SettingsTabUserServerEntry.SetPlaceholderText("https://my-sonic.server") + SettingsTabUserDeviceNameEntry.SetPlaceholderText("Sonically") + SettingsTabUserPasswordEntry.SetPlaceholderText("●●●●●●●●●●") + SettingsTabAppCoverSizeEntry.SetPlaceholderText("600") + SettingsTabAppBlurRadiusEntry.SetPlaceholderText("12") + SettingsTabAppBitrateEntry.SetPlaceholderText("128") + SettingsTabAppBufferSizeEntry.SetPlaceholderText("4096") + SettingsTabAppStreamingProxyEntry.SetPlaceholderText("https://my-proxy.server") + SettingsTabAppStreamingProxyUsernameEntry.SetPlaceholderText("Proxy username") + SettingsTabAppStreamingProxyPassEntry.SetPlaceholderText("●●●●●●●●●●") + SettingsTabAppMetadataProxyEntry.SetPlaceholderText("socks5://my-proxy.server") + SettingsTabAppMetadataProxyUsernameEntry.SetPlaceholderText("Proxy username") + SettingsTabAppMetadataProxyPassEntry.SetPlaceholderText("●●●●●●●●●●") + + SettingsTabAppStreamingProxyPassEntry.SetInputPurpose(gtk.InputPurposePassword) + SettingsTabAppStreamingProxyPassEntry.SetInvisibleChar('●') + SettingsTabAppStreamingProxyPassEntry.SetVisibility(false) + + SettingsTabAppMetadataProxyPassEntry.SetInputPurpose(gtk.InputPurposePassword) + SettingsTabAppMetadataProxyPassEntry.SetInvisibleChar('●') + SettingsTabAppMetadataProxyPassEntry.SetVisibility(false) + + // SettingsTabAppCodecComboBoxTest.AddAttribute(gtk.CellRendererFocused) + + // Signals + MainPlayPauseButton.ConnectClicked(MainPlayPauseButtonClicked) + MainPreviousButton.ConnectClicked(MainPreviousButtonClicked) + MainNextButton.ConnectClicked(MainNextButtonClicked) + + QueueClearButton.ConnectClicked(ClearQueue) + QueueSaveButton.ConnectClicked(CacheAllQueue) + + SettingsAccountTestButton.ConnectClicked(TestConnButtonClicked) + SettingsAccountSaveButton.ConnectClicked(SettingsTabUserSaveButtonClicked) + SearchTabSearchEntry.ConnectSearchChanged(SearchTabSearchButtonClicked) + + LibraryLibraryBackButton.ConnectClicked(LibraryTabBackButtonClicked) + LibraryLibraryRefreshButton.ConnectClicked(LibraryTabRefreshButtonClicked) + + LibraryPlaylistBackButton.ConnectClicked(LibraryTabLibraryBackButton) + LibraryPlaylistRefreshButton.ConnectClicked(LibraryTabLibraryRefreshButton) + // MainScale.ConnectValueChanged(ValueChanged) + +} + +func CreateImagelessSongListBoxRow(lBox *gtk.ListBox, title string, artist string, duration string, postition int, id string) { + MainListboxRow := gtk.NewListBoxRow() + MainGrid := gtk.NewGrid() + Frame := gtk.NewFrame("") + + QueueButton := gtk.NewButton() + PLaybutton := gtk.NewButton() + TitleScrolledWindow := gtk.NewScrolledWindow() + ArtistScrolledWindow := gtk.NewScrolledWindow() + TitleLabel := gtk.NewLabel(title) + ArtistLabel := gtk.NewLabel(artist) + DurationLabel := gtk.NewLabel(duration) + + PLaybutton.SetLabel("▶") + QueueButton.SetLabel("☰") + MainListboxRow.SetSizeRequest(150, 30) + MainGrid.Attach(DurationLabel, 0, 0, 1, 2) + MainGrid.Attach(TitleScrolledWindow, 1, 0, 1, 1) + MainGrid.Attach(ArtistScrolledWindow, 1, 1, 1, 1) + TitleScrolledWindow.SetChild(TitleLabel) + ArtistScrolledWindow.SetChild(ArtistLabel) + MainGrid.Attach(QueueButton, 2, 0, 1, 2) + MainGrid.Attach(PLaybutton, 3, 0, 1, 2) + + QueueButton.SetSizeRequest(15, 30) + PLaybutton.SetSizeRequest(15, 30) + DurationLabel.SetSizeRequest(30, 30) + DurationLabel.SetHAlign(gtk.AlignCenter) + DurationLabel.SetVAlign(gtk.AlignCenter) + TitleLabel.SetHAlign(gtk.AlignStart) + ArtistLabel.SetHAlign(gtk.AlignStart) + TitleLabel.SetSizeRequest(150, 15) + ArtistLabel.SetSizeRequest(150, 15) + + TitleScrolledWindow.SetHExpand(true) + ArtistScrolledWindow.SetHExpand(true) + // MainListboxRow.SetSizeRequest(300,50) + MainListboxRow.SetHAlign(gtk.AlignFill) + MainGrid.SetHAlign(gtk.AlignFill) + MainListboxRow.SetHExpand(true) + MainGrid.SetHExpand(true) + TitleScrolledWindow.SetSizeRequest(110, 15) + ArtistScrolledWindow.SetSizeRequest(110, 15) + QueueButton.SetHAlign(gtk.AlignEnd) + PLaybutton.SetHAlign(gtk.AlignEnd) + + // TitleScrolledWindow.SetHAlign(gtk.AlignStart) + // ArtistScrolledWindow.SetHAlign(gtk.AlignStart) + + // Signals + + var PlayButtonClicked = func() { + var upstream = func() { + if !SliceContains(MainQueue, id) { + // fmt.Println(len(MainQueue)) + // MainQueue.Add(id) + MainQueue = append(MainQueue, id) + // fmt.Println(MainQueue.Size()) + // fmt.Println(MainQueue) + NowPlayingIndex = SliceIndexOf(MainQueue, id) + go StartCaching(id) + CreateImagelessQueueListBoxRow(QueueListBox, title, artist, duration, NowPlayingIndex, id) + // CreateQueueChild(ttl, "album", artst, durat, id) + PlayAudio = false + EndPlayer = true + go PlayPort() + select { + case AudioControlChan <- true: + } + + } + } + glib.IdleAdd(upstream) + } + + var QueueButtonClicked = func() { + var upstream = func() { + if !SliceContains(MainQueue, id) { + MainQueue = append(MainQueue, id) + // NowPlayingIndex = MainQueue.IndexOf(id) + + if SliceIndexOf(MainQueue, id)-1 == NowPlayingIndex || SliceIndexOf(MainQueue, id)-2 == NowPlayingIndex { + fmt.Println("Song is next or 2 next in queue, caching") + go StartCaching(id) + } + + CreateImagelessQueueListBoxRow(QueueListBox, title, artist, duration, NowPlayingIndex, id) + } + } + glib.IdleAdd(upstream) + } + + PLaybutton.ConnectClicked(PlayButtonClicked) + QueueButton.ConnectClicked(QueueButtonClicked) + + MainListboxRow.SetChild(Frame) + Frame.SetChild(MainGrid) + lBox.Append(MainListboxRow) + +} + +func CreateImagelessQueueListBoxRow(lBox *gtk.ListBox, title string, artist string, duration string, postition int, id string) { + MainListboxRow := gtk.NewListBoxRow() + MainGrid := gtk.NewGrid() + Frame := gtk.NewFrame("") + DelButton := gtk.NewButton() + PLaybutton := gtk.NewButton() + ArrUPButton := gtk.NewButton() + ArrDownButton := gtk.NewButton() + TitleScrolledWindow := gtk.NewScrolledWindow() + ArtistScrolledWindow := gtk.NewScrolledWindow() + TitleLabel := gtk.NewLabel(title) + ArtistLabel := gtk.NewLabel(artist) + DurationLabel := gtk.NewLabel(duration) + + DelButton.SetLabel("✗") + PLaybutton.SetLabel("▶") + ArrUPButton.SetLabel("▲") + ArrDownButton.SetLabel("▼") + + Frame.SetChild(MainGrid) + MainListboxRow.SetChild(Frame) + MainGrid.Attach(DurationLabel, 0, 0, 1, 2) + MainGrid.Attach(TitleScrolledWindow, 1, 0, 3, 1) + MainGrid.Attach(ArtistScrolledWindow, 1, 1, 1, 1) + MainGrid.Attach(DelButton, 2, 1, 1, 1) + MainGrid.Attach(PLaybutton, 3, 1, 1, 1) + MainGrid.Attach(ArrUPButton, 4, 0, 1, 1) + MainGrid.Attach(ArrDownButton, 4, 1, 1, 1) + TitleScrolledWindow.SetChild(TitleLabel) + ArtistScrolledWindow.SetChild(ArtistLabel) + + TitleScrolledWindow.SetHExpand(true) + ArtistScrolledWindow.SetHExpand(true) + // MainListboxRow.SetSizeRequest(300,50) + MainListboxRow.SetHAlign(gtk.AlignFill) + MainGrid.SetHAlign(gtk.AlignFill) + MainListboxRow.SetHExpand(true) + MainGrid.SetHExpand(true) + TitleScrolledWindow.SetSizeRequest(110, 25) + ArtistScrolledWindow.SetSizeRequest(110, 25) + + // Frame.SetChild(MainListboxRow) + + var PlayButtonClicked = func() { + var upstream = func() { + MainQueue = append(MainQueue, id) + // MasterStramSlice[id] + // go StartCaching(id) + // MainQueue.Add(id) + // NowPlayingIndex = MainQueue.IndexOf(id) + NowPlayingIndex = SliceIndexOf(MainQueue, id) + PlayAudio = false + EndPlayer = true + go PlayPort() + select { + case AudioControlChan <- true: + } + + } + glib.IdleAdd(upstream) + } + + var UpButtonClicked = func() { + var show = func() { + indx := SliceIndexOf(MainQueue, id) + rowId := MainListboxRow.Index() + if rowId > 0 { + SliceSwapAtIndexes(MainQueue, indx, (indx - 1)) + QueueListBox.Remove(MainListboxRow) + QueueListBox.Insert(MainListboxRow, rowId-1) + if indx == NowPlayingIndex { + NowPlayingIndex-- + } + } + } + go glib.IdleAdd(show) + + } + + var DownButtonClicked = func() { + + var show = func() { + indx := SliceIndexOf(MainQueue, id) + rowId := MainListboxRow.Index() + if !(indx+1 > len(MainQueue)-1) { + if indx == NowPlayingIndex { + NowPlayingIndex++ + } + SliceSwapAtIndexes(MainQueue, indx, (indx + 1)) + QueueListBox.Remove(MainListboxRow) + QueueListBox.Insert(MainListboxRow, rowId+1) + } + } + go glib.IdleAdd(show) + + } + + var RemoveButtonClicked = func() { + var rm = func() { + if SliceIndexOf(MainQueue, id) == NowPlayingIndex { + PlayAudio = false + EndPlayer = true + } + // MainQueue.Remove(MainQueue.IndexOf(id)) + SliceRemove(MainQueue, id) + QueueListBox.Remove(MainListboxRow) + } + go glib.IdleAdd(rm) + + } + + PLaybutton.ConnectClicked(PlayButtonClicked) + ArrUPButton.ConnectClicked(UpButtonClicked) + ArrDownButton.ConnectClicked(DownButtonClicked) + DelButton.ConnectClicked(RemoveButtonClicked) + + lBox.Append(MainListboxRow) +} + +// func ValueChanged(){ +// PlayAudio = false +// if MainScale.Value() > TimeInPrecent { +// // CurrentStream.Stop() +// for (TimeInPrecent < MainScale.Value() && TimeInPrecent < float64(TotalTime)) { +// _, _ = OpusStr.Read(Out) +// DrawnTime += float64(1920 / rate / 2) +// TimeInPrecent = float64(DrawnTime / float64(TotalTime)) +// } +// // CurrentStream.Start() +// CurrentStream.Write() +// glib.IdleAdd(UpdatePBar()) +// PlayAudio = true +// } else { +// fmt.Println("Not yet implemented") +// MainScale.SetValue(TimeInPrecent) +// PlayAudio = true +// } + +// } + +func CreatelessListArtistBoxRow(lBox *gtk.ListBox, name string, postition int, albums []*subsonic.AlbumID3, itemid string) { + MainListboxRow := gtk.NewListBoxRow() + MainGrid := gtk.NewGrid() + Frame := gtk.NewFrame("") + + InfoButton := gtk.NewButton() + NameScrolledWindow := gtk.NewScrolledWindow() + NameLabel := gtk.NewLabel(name) + + MainGrid.Attach(NameScrolledWindow, 0, 0, 1, 1) + MainGrid.Attach(InfoButton, 1, 0, 1, 1) + NameScrolledWindow.SetChild(NameLabel) + Frame.SetChild(MainGrid) + MainListboxRow.SetChild(Frame) + + InfoButton.SetLabel("i") + + MainListboxRow.SetSizeRequest(150, 30) + InfoButton.SetSizeRequest(15, 30) + NameLabel.SetHAlign(gtk.AlignStart) + NameLabel.SetSizeRequest(150, 15) + + NameScrolledWindow.SetHExpand(true) + // MainListboxRow.SetSizeRequest(300,50) + MainListboxRow.SetHAlign(gtk.AlignFill) + MainGrid.SetHAlign(gtk.AlignFill) + MainListboxRow.SetHExpand(true) + + MainGrid.SetHExpand(true) + NameScrolledWindow.SetSizeRequest(110, 15) + InfoButton.SetHAlign(gtk.AlignEnd) + + var InfoButtonClicked = func() { + + TempArtist = "" + + var upstream = func() { + for child := lBox.FirstChild(); child != nil; { + lBox.Remove(child) + child = lBox.FirstChild() + } + + for i, album := range albums { + alb, _ := CurrentClient.GetAlbum(album.ID) + CreateImagelessAlbumListBoxRow(lBox, alb.Name, alb.Artist, DurationToReadable(alb.Duration), i, alb.Song) + } + } + glib.IdleAdd(upstream) + } + InfoButton.ConnectClicked(InfoButtonClicked) + lBox.Append(MainListboxRow) +} + +func CreateImagelessAlbumListBoxRow(lBox *gtk.ListBox, title string, artist string, duration string, postition int, songs []*subsonic.Child) { + MainListboxRow := gtk.NewListBoxRow() + MainGrid := gtk.NewGrid() + Frame := gtk.NewFrame("") + + QueueButton := gtk.NewButton() + InfoButton := gtk.NewButton() + PLaybutton := gtk.NewButton() + TitleScrolledWindow := gtk.NewScrolledWindow() + ArtistScrolledWindow := gtk.NewScrolledWindow() + TitleLabel := gtk.NewLabel(title) + ArtistLabel := gtk.NewLabel(artist) + DurationLabel := gtk.NewLabel(duration) + + InfoButton.SetLabel("i") + QueueButton.SetLabel("☰") + PLaybutton.SetLabel("▶") + + MainGrid.Attach(DurationLabel, 0, 0, 1, 2) + MainGrid.Attach(TitleScrolledWindow, 1, 0, 1, 1) + MainGrid.Attach(ArtistScrolledWindow, 1, 1, 1, 1) + MainGrid.Attach(InfoButton, 2, 0, 1, 2) + MainGrid.Attach(QueueButton, 3, 0, 1, 2) + MainGrid.Attach(PLaybutton, 4, 0, 1, 2) + TitleScrolledWindow.SetChild(TitleLabel) + ArtistScrolledWindow.SetChild(ArtistLabel) + Frame.SetChild(MainGrid) + MainListboxRow.SetChild(Frame) + + QueueButton.SetSizeRequest(15, 30) + PLaybutton.SetSizeRequest(15, 30) + DurationLabel.SetSizeRequest(30, 30) + DurationLabel.SetHAlign(gtk.AlignCenter) + DurationLabel.SetVAlign(gtk.AlignCenter) + TitleLabel.SetHAlign(gtk.AlignStart) + ArtistLabel.SetHAlign(gtk.AlignStart) + TitleLabel.SetSizeRequest(150, 15) + ArtistLabel.SetSizeRequest(150, 15) + + TitleScrolledWindow.SetHExpand(true) + ArtistScrolledWindow.SetHExpand(true) + // MainListboxRow.SetSizeRequest(300,50) + MainListboxRow.SetHAlign(gtk.AlignFill) + MainGrid.SetHAlign(gtk.AlignFill) + MainListboxRow.SetHExpand(true) + MainGrid.SetHExpand(true) + TitleScrolledWindow.SetSizeRequest(110, 15) + ArtistScrolledWindow.SetSizeRequest(110, 15) + QueueButton.SetHAlign(gtk.AlignEnd) + PLaybutton.SetHAlign(gtk.AlignEnd) + + InfoButton.SetSizeRequest(15, 30) + InfoButton.SetHAlign(gtk.AlignEnd) + + var InfoButtonClicked = func() { + // TempArtist = itemid + + var upstream = func() { + for child := lBox.FirstChild(); child != nil; { + lBox.Remove(child) + child = lBox.FirstChild() + + } + for i, song := range songs { + chil, _ := CurrentClient.GetSong(song.ID) + CreateImagelessSongListBoxRow(lBox, chil.Title, chil.Artist, DurationToReadable(chil.Duration), i, chil.ID) + } + } + glib.IdleAdd(upstream) + } + + var PlayButtonClicked = func() { + var upstream = func() { + PlayAudio = false + EndPlayer = true + NowPlayingIndex = 0 + // MainQueue.Clear() //! + MainQueue = SliceClear(MainQueue) + + for child := QueueListBox.FirstChild(); child != nil; { + QueueListBox.Remove(child) + child = QueueListBox.FirstChild() + } + + for i, song := range songs { + MainQueue = append(MainQueue, song.ID) + // MainQueue.Add(song.ID) + CreateImagelessQueueListBoxRow(QueueListBox, song.Title, song.Artist, DurationToReadable(song.Duration), i, song.ID) + } + // NowPlayingIndex = MainQueue.IndexOf(songs[0].ID) + NowPlayingIndex = SliceIndexOf(MainQueue, songs[0].ID) + go StartCaching(MainQueue[NowPlayingIndex]) + if NowPlayingIndex+1 <= len(MainQueue)-1 { + // fmt.Println(songdata.Title, " +1 needs to be downloaded") + go CachingWaiter(MainQueue[NowPlayingIndex], MainQueue[NowPlayingIndex+1], 5) + } + if NowPlayingIndex+2 <= len(MainQueue)-1 { + // fmt.Println(songdata.Title, " +2 needs to be downloaded") + go CachingWaiter(MainQueue[NowPlayingIndex+1], MainQueue[NowPlayingIndex+2], 5) + } + go PlayPort() + select { + case AudioControlChan <- true: + } + + } + glib.IdleAdd(upstream) + } + + var QueueButtonClicked = func() { + var upstream = func() { + var j = len(MainQueue) + j-- + for _, song := range songs { + // MainQueue.Add(song.ID) + MainQueue = append(MainQueue, song.ID) + CreateImagelessQueueListBoxRow(QueueListBox, song.Title, song.Artist, DurationToReadable(song.Duration), j, song.ID) + } + } + glib.IdleAdd(upstream) + } + + InfoButton.ConnectClicked(InfoButtonClicked) + PLaybutton.ConnectClicked(PlayButtonClicked) + QueueButton.ConnectClicked(QueueButtonClicked) + + lBox.Append(MainListboxRow) +} + +func CreateImagelessPlaylistListBoxRow(lBox *gtk.ListBox, title string, duration string, postition int, songs []*subsonic.Child) { + MainListboxRow := gtk.NewListBoxRow() + MainGrid := gtk.NewGrid() + Frame := gtk.NewFrame("") + InfoButton := gtk.NewButton() + + QueueButton := gtk.NewButton() + PLaybutton := gtk.NewButton() + TitleScrolledWindow := gtk.NewScrolledWindow() + TitleLabel := gtk.NewLabel(title) + DurationLabel := gtk.NewLabel(duration) + + QueueButton.SetLabel("☰") + PLaybutton.SetLabel("▶") + InfoButton.SetLabel("i") + + MainGrid.Attach(DurationLabel, 0, 0, 1, 2) + MainGrid.Attach(TitleScrolledWindow, 1, 0, 1, 2) + MainGrid.Attach(QueueButton, 3, 0, 1, 2) + MainGrid.Attach(PLaybutton, 4, 0, 1, 2) + MainGrid.Attach(InfoButton, 2, 0, 1, 2) + TitleScrolledWindow.SetChild(TitleLabel) + MainListboxRow.SetChild(Frame) + Frame.SetChild(MainGrid) + + QueueButton.SetSizeRequest(15, 30) + PLaybutton.SetSizeRequest(15, 30) + DurationLabel.SetSizeRequest(30, 30) + DurationLabel.SetHAlign(gtk.AlignCenter) + DurationLabel.SetVAlign(gtk.AlignCenter) + TitleLabel.SetHAlign(gtk.AlignStart) + TitleLabel.SetSizeRequest(150, 15) + InfoButton.SetSizeRequest(15, 30) + InfoButton.SetHAlign(gtk.AlignEnd) + TitleScrolledWindow.SetHExpand(true) + // MainListboxRow.SetSizeRequest(300,50) + MainListboxRow.SetHAlign(gtk.AlignFill) + MainGrid.SetHAlign(gtk.AlignFill) + MainListboxRow.SetHExpand(true) + MainGrid.SetHExpand(true) + TitleScrolledWindow.SetSizeRequest(110, 15) + QueueButton.SetHAlign(gtk.AlignEnd) + PLaybutton.SetHAlign(gtk.AlignEnd) + + var PlayButtonClicked = func() { + var upstream = func() { + PlayAudio = false + EndPlayer = true + NowPlayingIndex = 0 + // MainQueue.Clear() //! + MainQueue = SliceClear(MainQueue) + + for child := QueueListBox.FirstChild(); child != nil; { + QueueListBox.Remove(child) + child = QueueListBox.FirstChild() + } + + for i, song := range songs { + // MainQueue.Add(song.ID) + MainQueue = append(MainQueue, song.ID) + CreateImagelessQueueListBoxRow(QueueListBox, song.Title, song.Artist, DurationToReadable(song.Duration), i, song.ID) + } + // NowPlayingIndex = MainQueue.IndexOf(songs[0].ID) + NowPlayingIndex = SliceIndexOf(MainQueue, songs[0].ID) + go StartCaching(MainQueue[NowPlayingIndex]) + if NowPlayingIndex+1 <= len(MainQueue)-1 { + // fmt.Println(songdata.Title, " +1 needs to be downloaded") + go CachingWaiter(MainQueue[NowPlayingIndex], MainQueue[NowPlayingIndex+1], 5) + } + if NowPlayingIndex+2 <= len(MainQueue)-1 { + // fmt.Println(songdata.Title, " +2 needs to be downloaded") + go CachingWaiter(MainQueue[NowPlayingIndex+1], MainQueue[NowPlayingIndex+2], 5) + } + go PlayPort() + select { + case AudioControlChan <- true: + } + + } + glib.IdleAdd(upstream) + } + + var QueueButtonClicked = func() { + var upstream = func() { + var j = len(MainQueue) + j-- + for _, song := range songs { + // MainQueue.Add(song.ID) + MainQueue = append(MainQueue, song.ID) + CreateImagelessQueueListBoxRow(QueueListBox, song.Title, song.Artist, DurationToReadable(song.Duration), j, song.ID) + } + } + glib.IdleAdd(upstream) + } + + var InfoButtonClicked = func() { + var upstream = func() { + for child := lBox.FirstChild(); child != nil; { + lBox.Remove(child) + child = lBox.FirstChild() + + } + for i, song := range songs { + chil, _ := CurrentClient.GetSong(song.ID) + CreateImagelessSongListBoxRow(lBox, chil.Title, chil.Artist, DurationToReadable(chil.Duration), i, chil.ID) + } + } + glib.IdleAdd(upstream) + } + InfoButton.ConnectClicked(InfoButtonClicked) + + PLaybutton.ConnectClicked(PlayButtonClicked) + QueueButton.ConnectClicked(QueueButtonClicked) + + lBox.Append(MainListboxRow) +} + +// var TestConnButtonClicked = func() { +// // TestConnButtonClicked() +// } + +var SettingsTabUserSaveButtonClicked = func() { + CurrentClient = CreateUser(HttpSettings) + + usr := SettingsTabUserUsernameEntry.Text() + serv := SettingsTabUserServerEntry.Text() + plr := SettingsTabUserDeviceNameEntry.Text() + + coversize := SettingsTabAppCoverSizeEntry.Text() + blur := SettingsTabAppBlurRadiusEntry.Text() + bitr := SettingsTabAppBitrateEntry.Text() + downbuff := SettingsTabAppBufferSizeEntry.Text() + + var streamURL, metaURL, streamUser, streamPass, metaUser, metaPass string + + if SettingsTabAppStreamingProxySwitch.Active() == true { + streamURL = SettingsTabAppStreamingProxyEntry.Text() + + if SettingsTabAppAuthStreamingProxySwitch.Active() == true { + streamUser = SettingsTabAppStreamingProxyUsernameEntry.Text() + streamPass = SettingsTabAppStreamingProxyPassEntry.Text() + keyring.Set(streamURL, streamUser, streamPass) + } else { + streamUser = "" + streamPass = "" + } + + } else { + streamURL = "" + streamUser = "" + streamPass = "" + + } + + if SettingsTabAppMetadataProxySwitch.Active() == true { + + metaURL = SettingsTabAppMetadataProxyEntry.Text() + + if SettingsTabAppAuthMetadataProxySwitch.Active() == true { + metaUser = SettingsTabAppMetadataProxyUsernameEntry.Text() + metaPass = SettingsTabAppMetadataProxyPassEntry.Text() + keyring.Set(metaURL, metaUser, metaPass) + } + + } else { + metaURL = "" + metaUser = "" + metaPass = "" + + } + + UserDeserialized = User{ + User: usr, + Url: serv, + } + + UserList[0] = UserDeserialized + + var coversizeInt int16 + fmt.Sscan(coversize, &coversizeInt) + + var blurInt int8 + fmt.Sscan(blur, &blurInt) + + var bitrInt int16 + fmt.Sscan(bitr, &bitrInt) + + var downbuffInt int16 + fmt.Sscan(downbuff, &downbuffInt) + + MainSettings = Settings{ + Users: UserList, + PlayerName: plr, + + DeleteOnClose: SettingsTabAppDeleteSwitch.Active(), + Coversize: coversizeInt, + BlurRadius: blurInt, + HideHeaderBar: SettingsTabAppHideHeaderBarSwitch.Active(), + + Codec: SettingsTabAppCodecComboBoxTest.ActiveText(), + Bitrate: bitrInt, + DownladBufferSize: downbuffInt, + + UseStreamingProxy: SettingsTabAppStreamingProxySwitch.Active(), + StreamingProxy: streamURL, + AuthStreamingProxy: SettingsTabAppAuthStreamingProxySwitch.Active(), + StreamingProxyUser: streamUser, + + MetadataType: SettingsTabAppMetadataComboBoxTest.ActiveText(), + UseMetadataProxy: SettingsTabAppMetadataProxySwitch.Active(), + MetadataProxy: metaURL, + AuthMetadataProxy: SettingsTabAppAuthMetadataProxySwitch.Active(), + MetadataProxyUser: metaUser, + } + + server := SettingsTabUserServerEntry.Text() + username := SettingsTabUserUsernameEntry.Text() + pass := SettingsTabUserPasswordEntry.Text() + err := keyring.Set(server, username, pass) + if err != nil { + + } + fmt.Println("") + fmt.Println(UserDeserialized) + fmt.Println(MainSettings) + fmt.Println(settingsPath) + fmt.Println("") + + Serialize(MainSettings, settingsPath) +} + +var SearchTabSearchButtonClicked = func() { + Search() + // fmt.Println("end searching") +} + +var MainSliderUserValueChange = func() { + +} + +var MainPlayPauseButtonClicked = func() { + + // // if NowPlayingIndex == 0 && PlayAudio == false && TotalTime == 0 { + // // go PlayPort() + // // } + + PlayAudio = !PlayAudio + if PlayAudio == true { + MainPlayPauseButton.SetLabel("❚❚") + } else { + MainPlayPauseButton.SetLabel("▶") + } + +} + +var LibraryTabBackButtonClicked = func() { + + // if TempArtist != "" { + // var upstream = func() { + // for child := QueueListBoxLibrary.FirstChild(); child != nil; { + // QueueListBoxLibrary.Remove(child) + // child = QueueListBoxLibrary.FirstChild() + // } + // artist, _ := CurrentClient.GetArtist(TempArtist) + // albums := artist.Album + + // for i, album := range albums { + // alb, _ := CurrentClient.GetAlbum(album.ID) + // CreateImagelessAlbumListBoxRow(QueueListBoxLibrary, alb.Name, alb.Artist, DurationToReadable(alb.Duration), i, alb.Song) + // } + // } + // glib.IdleAdd(upstream) + + // TempArtist = "" + // } else { + // GetLib() + // } + + GetLib() + +} +var LibraryTabRefreshButtonClicked = func() { + GetLib() +} + +var LibraryTabLibraryRefreshButton = func() { + GetPlaylist() +} + +var LibraryTabLibraryBackButton = func() { + GetPlaylist() +} + +var MainPreviousButtonClicked = func() { + PlayAudio = false + EndPlayer = true + + // CurrentStream = nil + if NowPlayingIndex-1 >= 0 { + NowPlayingIndex-- + } + notify.CloseNotification(MainNotification.ReplacesID) + UpdatePBar() + go PlayPort() +} + +var MainNextButtonClicked = func() { + PlayAudio = false + EndPlayer = true + // CurrentStream = nil + if !(NowPlayingIndex+1 > (len(MainQueue) - 1)) { + NowPlayingIndex++ + go StartCaching(MainQueue[NowPlayingIndex]) + if NowPlayingIndex+1 <= len(MainQueue)-1 { + // fmt.Println(songdata.Title, " +1 needs to be downloaded") + go CachingWaiter(MainQueue[NowPlayingIndex], MainQueue[NowPlayingIndex+1], 5) + } + if NowPlayingIndex+2 <= len(MainQueue)-1 { + // fmt.Println(songdata.Title, " +2 needs to be downloaded") + go CachingWaiter(MainQueue[NowPlayingIndex+1], MainQueue[NowPlayingIndex+2], 5) + } + } + notify.CloseNotification(MainNotification.ReplacesID) + UpdatePBar() + go PlayPort() +} + +// func MoveListBoxChildren(lb *gtk.ListBox, position int){ +// // row := lb.RowAtIndex(position +1 ) +// // lb.Remove(row) +// // lb.Insert(row, position) +// // position++ + +// for row := lb.RowAtIndex(position +1 ); position < len(MainQueue); { +// lb.Remove(row) +// lb.Insert(row, position) +// position++ +// } +// } + +var ClearQueue = func() { + + PlayAudio = false + EndPlayer = true + + NowPlayingIndex = 0 + + MainQueue = MainQueue[:0] + + SetZeroTime() + + for child := QueueListBox.FirstChild(); child != nil; { + QueueListBox.Remove(child) + child = QueueListBox.FirstChild() + } +} + +var CacheAllQueue = func() { + StartCaching(MainQueue[0]) + for i := 1; i < len(MainQueue); i++ { + go CachingWaiter(MainQueue[i-1], MainQueue[i], 5) + } +} diff --git a/librarybox_actions.go b/librarybox_actions.go new file mode 100644 index 0000000..d7c9fae --- /dev/null +++ b/librarybox_actions.go @@ -0,0 +1,116 @@ +package main + +import ( + // "github.com/delucks/go-subsonic" + // "fmt" + "github.com/diamondburned/gotk4/pkg/core/glib" +) + +func DownloadPlaylist() { + for child := PlayListListBox.FirstChild(); child != nil; { + PlayListListBox.Remove(child) + child = PlayListListBox.FirstChild() + } + param := map[string]string{} + playlists, err := CurrentClient.GetPlaylists(param) + LogOnError(err) + + for i, item := range playlists { + playlist, _ := CurrentClient.GetPlaylist(item.ID) + PlaylistCached = append(PlaylistCached, item.ID) + + pat := GetRunDir() + MainDir + InfDir + "/" + item.ID + SerializePlaylist(*playlist, pat) + + var upstream = func() { + CreateImagelessPlaylistListBoxRow(PlayListListBox, playlist.Name, DurationToReadable(playlist.Duration), i, playlist.Entry) + } + glib.IdleAdd(upstream) + } +} + +func GetPlaylist() { + + for child := PlayListListBox.FirstChild(); child != nil; { + PlayListListBox.Remove(child) + child = PlayListListBox.FirstChild() + } + + for i, id := range PlaylistCached { + // fmt.Printf("{}",item) + + item := DeSerializePlaylist(GetRunDir() + MainDir + InfDir + "/" + id) + + var upstream = func() { + CreateImagelessPlaylistListBoxRow(PlayListListBox, item.Name, DurationToReadable(item.Duration), i, item.Entry) + } + glib.IdleAdd(upstream) + } +} + +// func RefreshPlaylist() { +// for child := PlayListListBox.FirstChild(); child != nil; { +// PlayListListBox.Remove(child) +// child = PlayListListBox.FirstChild() +// } +// param := map[string]string{} +// playlists, err := CurrentClient.GetPlaylists(param) +// LogOnError(err) +// for i, item := range playlists { +// playlist, _ := CurrentClient.GetPlaylist(item.ID) +// CreateImagelessPlaylistListBoxRow(PlayListListBox, playlist.Name, DurationToReadable(playlist.Duration), i, playlist.Entry) +// } +// } + +func DownloadLib() { + param := map[string]string{} + artistID3, err := CurrentClient.GetArtists(param) + LogFatalOnError(err) + + for _, indx := range artistID3.Index { + for j, artist := range indx.Artist { + art, _ := CurrentClient.GetArtist(artist.ID) + ArtistsCached = append(ArtistsCached, artist.ID) + + SerializeArtist(*art, GetRunDir()+MainDir+InfDir+"/"+artist.ID) + + var upstream = func() { + CreatelessListArtistBoxRow(QueueListBoxLibrary, art.Name, j, art.Album, art.ID) + } + glib.IdleAdd(upstream) + } + } +} + +func GetLib() { + + for child := QueueListBoxLibrary.FirstChild(); child != nil; { + QueueListBoxLibrary.Remove(child) + child = QueueListBoxLibrary.FirstChild() + } + + for j, id := range ArtistsCached { + art := DeSerializeArtist(GetRunDir() + MainDir + InfDir + "/" + id) + var upstream = func() { + CreatelessListArtistBoxRow(QueueListBoxLibrary, art.Name, j, art.Album, art.ID) + } + glib.IdleAdd(upstream) + } +} + +// func RefreshLib() { +// for child := QueueListBoxLibrary.FirstChild(); child != nil; { +// QueueListBoxLibrary.Remove(child) +// child = QueueListBoxLibrary.FirstChild() +// } +// param := map[string]string{} +// artistID3, err := CurrentClient.GetArtists(param) +// LogFatalOnError(err) + +// for _, indx := range artistID3.Index { +// for j, artist := range indx.Artist { +// art, _ := CurrentClient.GetArtist(artist.ID) +// CreatelessListArtistBoxRow(QueueListBoxLibrary, art.Name, j, art.Album) +// } +// } +// } diff --git a/package/alpine/APKBUILD b/package/alpine/APKBUILD new file mode 100644 index 0000000..334aac9 --- /dev/null +++ b/package/alpine/APKBUILD @@ -0,0 +1,39 @@ +# Contributor: ITmodulo +# Maintainer: ITmodulo +pkgname="sonically" +reponame="Sonically" +organization="LinuxOnMobile" +githost="https://git.itmodulo.eu" +pkgver=0.4.1 +pkgrel=0 +pkgdesc="Subsonic API compatible streaming client made in GTK. It is suitable for desktop and mobile" +url="$githost/$organization/$reponame" +arch="x86_64 aarch64" +options="net !check" # for downloading Go modules, no checks available +license="GPL-3.0-or-later" +depends="portaudio opusfile libnotify glib gtk4.0 gobject-introspection" +makedepends="go portaudio-dev opusfile-dev libnotify-dev glib-dev gtk4.0-dev gobject-introspection-dev" +source="$reponame-$pkgver.tar.gz::$githost/$organization/$reponame/archive/$pkgver.tar.gz" +builddir="$srcdir/$pkgname-$pkgver" + +prepare() { + default_prepare() + mkdir -p "$builddir" +} + +build() { + # GOARCH should be set automaticaly by OS + cd "$srcdir"/"$pkgname" + CGO_ENABLED="1" GOOS="linux" go build -tags netgo -o "$builddir"/"$pkgname" +} + +package() { + # Place built executable in PATH + install -Dm755 "$builddir"/"$pkgname" "$pkgdir"/usr/bin/"$pkgname" + # Copy .svg logo to system + install -Dm644 "$srcdir"/"$pkgname"/"$pkgname".svg "$pkgdir"/usr/share/pixmaps/"$pkgname".svg + # Copy .desktop file to to system + install -Dm755 "$srcdir"/"$pkgname"/"$pkgname".desktop "$pkgdir"/usr/share/applications/eu.itmodulo."$pkgname".desktop +} + + diff --git a/pictures_actions.go b/pictures_actions.go new file mode 100644 index 0000000..0cb1956 --- /dev/null +++ b/pictures_actions.go @@ -0,0 +1,89 @@ +package main + +import ( + "fmt" + // "image" + "image/jpeg" + // "bytes" + "log" + "os" + "strconv" + // "github.com/esimov/stackblur-go" + // "github.com/diamondburned/gotk4/pkg/gtk/v4" + // "github.com/diamondburned/gotk4/pkg/gdkpixbuf/v2" + // "github.com/diamondburned/gotk4/pkg/cairo" + // "github.com/diamondburned/gotk4/pkg/gdk/v4" + // "github.com/diamondburned/gotkit/gtkutil/imgutil" +) + +func DownloadCoverArtFullSize(coverID string) { + + path := GetRunDir() + MainDir + DataDir + "/" + coverID + ".jpg" + + fil, err1 := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0664) + defer fil.Close() + if err1 != nil { + // + param := map[string]string{"size": strconv.Itoa(int(MainSettings.Coversize))} + + img, _ := CurrentClient.GetCoverArt(coverID, param) + fmt.Println("No such album downloaded: ", coverID) + f, err := os.Create(path) + // fb, err := os.Create(path2) + PanicOnError(err) + defer f.Close() + if err = jpeg.Encode(f, img, nil); err != nil { + log.Printf("failed to encode: %v", err) + + } + + // w, h := MainAlbumCover.GetSizeRequest() + + // catFile, err := os.Open(path) + // if err != nil { + // panic(err) + // } + + // defer catFile.Close() + // _, _, err = image.Decode(catFile) + // if err != nil { + // panic(err) + // } + + // cat, err := jpeg.Decode(catFile) + // if err != nil { + // panic(err) + // } + + // fmt.Println(cat) + // sig := make(chan struct{}) + // img2 := stackblur.Process(cat, uint32(w), uint32(h), uint32(30), sig) + // select { + + // case <-sig: + // jpeg.Encode(fb, img2, nil) + // } + } else { + + fmt.Println("Album present: ", coverID) + } + +} + +// func LoadImageToByteArray(path string) []byte { + +// f, err := os.Open(path) +// if err != nil { +// LogOnError(err) +// } +// defer f.Close() +// buf := new(bytes.Buffer) +// img, err := jpeg.Decode(f) +// err = jpeg.Encode(buf,img,nil) + +// if err != nil { +// LogOnError(err) +// } + +// return buf.Bytes() +// } diff --git a/searchbox_actions.go b/searchbox_actions.go new file mode 100644 index 0000000..7fe1c27 --- /dev/null +++ b/searchbox_actions.go @@ -0,0 +1,65 @@ +package main + +import ( + // "log" + "strconv" + // "fmt" + // "github.com/diamondburned/gotk4/pkg/core/glib" + // "github.com/diamondburned/gotk4/pkg/gdk/v4" + // "github.com/diamondburned/gotk4/pkg/gtk/v4" + // "github.com/diamondburned/gotkit/gtkutil" +) + +// var PlayNode string +// var pool *parallel = parallel.NewParallel() + +func Search() { + + for child := SearchListBoxResult.FirstChild(); child != nil; { + SearchListBoxResult.Remove(child) + child = SearchListBoxResult.FirstChild() + } + + artistCount := 0 + albumCount := 0 + songCount := 0 + + if SearchTabSongCheckButton.Active() { + songCount = 20 + } + if SearchTabAlbumCheckButton.Active() { + albumCount = 20 + } + if SearchTabArtistCheckButton.Active() { + artistCount = 20 + } + q := SearchTabSearchEntry.Text() + param := map[string]string{ + "artistCount": strconv.Itoa(artistCount), + // "artistOffset": "0", + "albumCount": strconv.Itoa(albumCount), + // "albumOffset": "0", + "songCount": strconv.Itoa(songCount), + // "songOffset": "0", + // "musicFolderId": "", + } + if q != "" { + result, err := CurrentClient.Search3(q, param) + LogFatalOnError(err) + var j = 0 + for _, item := range result.Song { + j++ + CreateImagelessSongListBoxRow(SearchListBoxResult, item.Title, item.Artist, DurationToReadable(item.Duration), j, item.ID) + } + for _, item := range result.Album { + j++ + album, _ := CurrentClient.GetAlbum(item.ID) + CreateImagelessAlbumListBoxRow(SearchListBoxResult, album.Name, album.Artist, DurationToReadable(album.Duration), j, album.Song) + } + for _, item := range result.Artist { + j++ + artist, _ := CurrentClient.GetArtist(item.ID) + CreatelessListArtistBoxRow(SearchListBoxResult, artist.Name, j, artist.Album, item.ID) + } + } +} diff --git a/settings.go b/settings.go new file mode 100644 index 0000000..fd9a103 --- /dev/null +++ b/settings.go @@ -0,0 +1,351 @@ +package main + +import ( + "encoding/gob" + "fmt" + "github.com/delucks/go-subsonic" + "github.com/diamondburned/gotk4/pkg/core/glib" + // "io" + "log" + "net/http" + "net/url" + "os" + "strconv" + "strings" + "time" + // "github.com/diamondburned/gotk4/pkg/gtk/v4" + "github.com/djherbis/fscache" + "github.com/zalando/go-keyring" +) + +func TestConnButtonClicked() { + clientt := CreateUser(HttpSettings) + _, err := clientt.GetGenres() + if err != nil { + var up = func() bool { + SettingsTabUserStatusLabel.SetText("✗ ") + return false + } + glib.IdleAdd(up) + + } else { + var up = func() bool { + SettingsTabUserStatusLabel.SetText("✓ ") + return false + } + glib.IdleAdd(up) + + } +} + +func CreateUser(httpsettings *http.Client) subsonic.Client { + server := SettingsTabUserServerEntry.Text() + username := SettingsTabUserUsernameEntry.Text() + device := SettingsTabUserDeviceNameEntry.Text() + pass := SettingsTabUserPasswordEntry.Text() + + clientt := subsonic.Client{ + Client: httpsettings, + BaseUrl: server, + User: username, + ClientName: device, + } + clientt.Authenticate(pass) + + return clientt +} + +type User struct { + User string + Url string +} + +type Settings struct { + //User + Users [10]User + PlayerName string + + //Local + DeleteOnClose bool + Coversize int16 + BlurRadius int8 + HideHeaderBar bool + + //Transcoding + Codec string + Bitrate int16 + DownladBufferSize int16 + + //Net + UseStreamingProxy bool + StreamingProxy string + AuthStreamingProxy bool + StreamingProxyUser string + + // Metadata + MetadataType string + UseMetadataProxy bool + MetadataProxy string + AuthMetadataProxy bool + MetadataProxyUser string +} + +// type SonicallyStream struct { +// ReadMaster fscache.ReadAtCloser +// WriteMaster io.WriteCloser +// } + +func Serialize(obj Settings, path string) { + dataFile, err1 := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0664) + defer dataFile.Close() + if err1 == nil { + os.Remove(path) + } + dataFile, _ = os.Create(path) + e := gob.NewEncoder(dataFile) + if err := e.Encode(obj); err != nil { + LogFatalOnError(err) + } + dataFile.Close() +} + +func DeSerialize(path string) Settings { + dataFile, err := os.Open(path) + if err != nil { + PanicOnError(err) + } + var sett Settings + dataDecoder := gob.NewDecoder(dataFile) + err = dataDecoder.Decode(&sett) + + if err != nil { + PanicOnError(err) + } + dataFile.Close() + return sett +} + +func SerializeArtist(obj subsonic.ArtistID3, path string) { + dataFile, err1 := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0664) + defer dataFile.Close() + if err1 == nil { + os.Remove(path) + } + dataFile, _ = os.Create(path) + e := gob.NewEncoder(dataFile) + if err := e.Encode(obj); err != nil { + LogFatalOnError(err) + } + dataFile.Close() +} + +func DeSerializeArtist(path string) subsonic.ArtistID3 { + dataFile, err := os.Open(path) + if err != nil { + PanicOnError(err) + } + var sett subsonic.ArtistID3 + dataDecoder := gob.NewDecoder(dataFile) + err = dataDecoder.Decode(&sett) + + if err != nil { + PanicOnError(err) + } + dataFile.Close() + return sett +} + +func SerializePlaylist(obj subsonic.Playlist, path string) { + dataFile, err1 := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0664) + defer dataFile.Close() + if err1 == nil { + os.Remove(path) + } + dataFile, _ = os.Create(path) + e := gob.NewEncoder(dataFile) + if err := e.Encode(obj); err != nil { + LogFatalOnError(err) + } + dataFile.Close() +} + +func DeSerializePlaylist(path string) subsonic.Playlist { + dataFile, err := os.Open(path) + if err != nil { + PanicOnError(err) + } + var sett subsonic.Playlist + dataDecoder := gob.NewDecoder(dataFile) + err = dataDecoder.Decode(&sett) + + if err != nil { + PanicOnError(err) + } + dataFile.Close() + return sett +} + +func Init() { + fil, err1 := os.OpenFile(settingsPath, os.O_APPEND|os.O_WRONLY, 0664) + defer fil.Close() + if err1 == nil { + MainSettings = DeSerialize(settingsPath) + UserList = MainSettings.Users + UserDeserialized = MainSettings.Users[0] + + clientt := subsonic.Client{ + Client: HttpSettings, + BaseUrl: UserDeserialized.Url, + User: UserDeserialized.User, + ClientName: MainSettings.PlayerName, + } + + sec, err := keyring.Get(UserDeserialized.Url, UserDeserialized.User) + if err == nil { + clientt.Authenticate(sec) + CurrentClient = clientt + SettingsTabUserUsernameEntry.SetText(UserDeserialized.User) + SettingsTabUserPasswordEntry.SetText(sec) + SettingsTabUserDeviceNameEntry.SetText(MainSettings.PlayerName) + SettingsTabUserServerEntry.SetText(UserDeserialized.Url) + SettingsTabAppDeleteSwitch.SetActive(MainSettings.DeleteOnClose) + SettingsTabAppHideHeaderBarSwitch.SetActive(MainSettings.HideHeaderBar) + + SettingsTabAppCoverSizeEntry.SetText(strconv.Itoa(int(MainSettings.Coversize))) + SettingsTabAppBlurRadiusEntry.SetText(strconv.Itoa(int(MainSettings.BlurRadius))) + SettingsTabAppBitrateEntry.SetText(strconv.Itoa(int(MainSettings.Bitrate))) + SettingsTabAppBufferSizeEntry.SetText(strconv.Itoa(int(MainSettings.DownladBufferSize))) + SettingsTabAppStreamingProxySwitch.SetActive(MainSettings.UseStreamingProxy) + if MainSettings.UseStreamingProxy == true { + SettingsTabAppStreamingProxyEntry.SetText(MainSettings.StreamingProxy) + + // var temptrans *http.Transport + var protocol, parsed string + + if strings.Contains(MainSettings.StreamingProxy, "https") { + protocol = "https://" + parsed = MainSettings.StreamingProxy[8:] + } else if strings.Contains(MainSettings.StreamingProxy, "socks5") { + protocol = "socks5://" + parsed = MainSettings.StreamingProxy[9:] + } else if strings.Contains(MainSettings.StreamingProxy, "http") && !strings.Contains(MainSettings.StreamingProxy, "https") { + protocol = "http://" + parsed = MainSettings.StreamingProxy[7:] + } else { + log.Fatal("Unsupported protocol") + } + + if SettingsTabAppAuthStreamingProxySwitch.Active() == true { + sec2, err := keyring.Get(MainSettings.StreamingProxy, MainSettings.StreamingProxyUser) + if err != nil { + log.Fatal("Error getting from keyring") + } + SettingsTabAppStreamingProxyUsernameEntry.SetText(MainSettings.StreamingProxyUser) + SettingsTabAppStreamingProxyPassEntry.SetText(sec2) + + fullURL := protocol + MainSettings.StreamingProxyUser + ":" + sec2 + "@" + parsed + //adding the proxy settings to the Transport object + parsedurl, _ := url.Parse(fullURL) + + HttpSettings = &http.Client{Timeout: time.Second * 10, Transport: &http.Transport{Proxy: http.ProxyURL(parsedurl)}} + + } else { + // fullURL := protocol + MainSettings.StreamingProxyUser + "@" + parsed + parsedurl, _ := url.Parse(MainSettings.StreamingProxy) + // temptrans := + HttpSettings = &http.Client{Timeout: time.Second * 10, Transport: &http.Transport{Proxy: http.ProxyURL(parsedurl)}} + } + + //adding the Transport object to the http Client + + // HttpSettings.Transport + + } + + SettingsTabAppMetadataProxySwitch.SetActive(MainSettings.UseMetadataProxy) + if MainSettings.UseMetadataProxy == true { + SettingsTabAppMetadataProxyEntry.SetText(MainSettings.MetadataProxy) + + // var temptrans2 *http.Transport + var protocol, parsed string + + if strings.Contains(MainSettings.MetadataProxy, "https") { + protocol = "https://" + parsed = MainSettings.MetadataProxy[8:] + } else if strings.Contains(MainSettings.MetadataProxy, "socks5") { + protocol = "socks5://" + parsed = MainSettings.MetadataProxy[9:] + } else if strings.Contains(MainSettings.MetadataProxy, "http") && !strings.Contains(MainSettings.MetadataProxy, "https") { + protocol = "http://" + parsed = MainSettings.MetadataProxy[7:] + } else { + log.Fatal("Unsupported protocol") + } + + if SettingsTabAppAuthMetadataProxySwitch.Active() == true { + sec3, err := keyring.Get(MainSettings.MetadataProxy, MainSettings.MetadataProxyUser) + if err != nil { + log.Fatal("Error getting from keyring") + } + SettingsTabAppMetadataProxyUsernameEntry.SetText(MainSettings.MetadataProxyUser) + SettingsTabAppMetadataProxyPassEntry.SetText(sec3) + + fullURL := protocol + MainSettings.MetadataProxyUser + ":" + sec3 + "@" + parsed + //adding the proxy settings to the Transport object + parsedurl2, err := url.Parse(fullURL) + MetadataClient = &http.Client{Timeout: time.Second * 10, Transport: &http.Transport{Proxy: http.ProxyURL(parsedurl2)}} + + } else { + // fullURL := protocol + MainSettings.StreamingProxyUser + "@" + parsed + parsedurl2, _ := url.Parse(MainSettings.MetadataProxy) + MetadataClient = &http.Client{Timeout: time.Second * 10, Transport: &http.Transport{Proxy: http.ProxyURL(parsedurl2)}} + + } + + //adding the Transport object to the http Client + + } + if MainSettings.Codec == "opus" { + SettingsTabAppCodecComboBoxTest.SetActive(0) + } else if MainSettings.Codec == "flac" { + SettingsTabAppCodecComboBoxTest.SetActive(1) + } else if MainSettings.Codec == "ogg" { + SettingsTabAppCodecComboBoxTest.SetActive(2) + } else if MainSettings.Codec == "mp3" { + SettingsTabAppCodecComboBoxTest.SetActive(3) + } + + // HERE + + if MainSettings.MetadataType == "None" { + SettingsTabAppMetadataComboBoxTest.SetActive(0) + } else if MainSettings.MetadataType == "Server" { + SettingsTabAppMetadataComboBoxTest.SetActive(1) + } else if MainSettings.MetadataType == "Sonically" { + SettingsTabAppMetadataComboBoxTest.SetActive(2) + } + + if MainSettings.HideHeaderBar { + MainWindow.SetDecorated(false) + } + + log.Print("Sucessfully set user from deserialzed content") + cacheDir := GetRunDir() + MainDir + DataDir + "/.cache" + CurrentCache, err = fscache.New(cacheDir, 0755, time.Hour*24) + if err != nil { + log.Fatal(err.Error()) + } + } + } else { + MainDirC := GetRunDir() + MainDir + if _, err := os.Stat(MainDir); os.IsNotExist(err) { + os.Mkdir(MainDirC, 0744) + os.Mkdir(MainDirC+DataDir, 0744) + os.Mkdir(MainDirC+InfDir, 0744) + os.Mkdir(MainDirC+SettingsDir, 0744) + fmt.Println("Directory tree created") + } + + } + +} diff --git a/shared_objects.go b/shared_objects.go new file mode 100644 index 0000000..4a9b758 --- /dev/null +++ b/shared_objects.go @@ -0,0 +1,243 @@ +package main + +import ( + "io" + "net/http" + "os" + "sync" + + "github.com/TheCreeper/go-notify" + "github.com/delucks/go-subsonic" + "github.com/diamondburned/gotk4/pkg/gtk/v4" + // "github.com/diamondburned/gotk4/pkg/gdk/v4" + // "github.com/diamondburned/gotk4/pkg/core/glib" + "github.com/djherbis/fscache" + // "github.com/emirpasic/gods/lists/arraylist" + "github.com/gordonklaus/portaudio" + "github.com/hraban/opus" +) + +func GetRunDir() string { + rundirGet, _ := os.UserHomeDir() + return rundirGet +} + +// Dirs +const MainDir string = "/.Sonically" +const DataDir string = "/data" +const SettingsDir string = "/Settings" +const InfDir string = "/inf" + +var CurrentClient subsonic.Client +var CurrentStream *portaudio.Stream +var MainQueue []string +var NowPlayingIndex int = 0 +var UserList [10]User +var UserDeserialized User +var MainSettings Settings +var DownloadSyncGroup sync.WaitGroup +var TotalTime int = 0 +var DrawnTime float64 = float64(0.0) +var Out []int16 = make([]int16, 1920) + +// var ReadBuffer []byte +// var MasterStramSlice map[string]SonicallyStream +var CachingMap map[string]bool = make(map[string]bool) + +var TimeInPrecent float64 = 0.0 +var CurrentCache fscache.Cache + +var AudioControlChan chan bool +var OpusStr *opus.Stream +var ReadMaster fscache.ReadAtCloser +var WriteMaster io.WriteCloser + +var ArtistsCached []string +var PlaylistCached []string +var TempArtist string = "" +var EnterArtist bool + +// UI widgets declaration +var MainWindow *gtk.ApplicationWindow +var LyricsNotebook *gtk.Notebook +var LyricsScrolledWindow *gtk.ScrolledWindow +var MainFlowbox *gtk.FlowBox +var MainFlowBoxChild1 *gtk.FlowBoxChild +var MainFlowBoxChild2 *gtk.FlowBoxChild +var MainFlowBoxChild3 *gtk.FlowBoxChild +var MainFlowBoxChild4 *gtk.FlowBoxChild +var MainGridFlowBoxChild2 *gtk.Grid +var MainGridFlowBoxChild4 *gtk.Grid +var MainGridFlowBoxChild2Grid *gtk.Grid +var MainLeftSeparator *gtk.Separator +var MainRightSeparator *gtk.Separator +var MainScale *gtk.Scale +var MainScaleGrid *gtk.Grid + +var QueueMasterBox *gtk.Box +var QueueSearchControlBox *gtk.Grid +var QueueFlowBox *gtk.FlowBox +var QueueScrolledWindow *gtk.ScrolledWindow +var QueueSaveButton *gtk.Button +var QueueClearButton *gtk.Button +var QueueShuffleButton *gtk.Button +var QueueFlowBoxChild1 *gtk.FlowBoxChild + +var SearchFlowBox *gtk.FlowBox +var SearchFlowBoxChild1 *gtk.FlowBoxChild +var SearchMasterBox *gtk.Box +var SearchControlBox *gtk.Grid +var SearchScrolledWindow *gtk.ScrolledWindow + +var LibraryFlowBox1 *gtk.FlowBox +var LibraryFlowBox2 *gtk.FlowBox +var LibraryFlowBox3 *gtk.FlowBox +var LibraryFlowBox4 *gtk.FlowBox + +var LibraryPlaylistMainBox *gtk.Box +var LibraryStarredMainBox *gtk.Box +var LibraryLibraryMainBox *gtk.Box +var LibraryPodcastMainBox *gtk.Box +var LibraryFlowBox1Child1 *gtk.FlowBoxChild +var LibraryFlowBox2Child1 *gtk.FlowBoxChild +var LibraryFlowBox3Child1 *gtk.FlowBoxChild +var LibraryFlowBox4Child1 *gtk.FlowBoxChild +var LibraryPlaylistMainControlGrid *gtk.Grid +var LibraryStarredMainControlGrid *gtk.Grid +var LibraryLibraryMainControlGrid *gtk.Grid +var LibraryPodcastMainControlGrid *gtk.Grid +var LibraryPlaylistBackButton *gtk.Button +var LibraryPlaylistRefreshButton *gtk.Button +var LibraryStarredBackButton *gtk.Button +var LibraryStarredRefreshButton *gtk.Button +var LibraryLibraryBackButton *gtk.Button +var LibraryLibraryRefreshButton *gtk.Button +var LibraryPodcastBackButton *gtk.Button +var LibraryPodcastRefreshButton *gtk.Button +var LibraryStarredListBox *gtk.ListBox +var LibraryPodcastListBox *gtk.ListBox +var LibraryPlaylistMainScrolledWindow *gtk.ScrolledWindow +var LibraryPodcastListBoxScrolledWindow *gtk.ScrolledWindow +var LibraryStarredListBoxScrolledWindow *gtk.ScrolledWindow +var QueueListBoxLibraryScrolledWindow *gtk.ScrolledWindow + +var SettingsFlowBox *gtk.FlowBox +var SettingsTabUserUsernameEntry *gtk.Entry +var SettingsTabUserServerEntry *gtk.Entry +var SettingsTabUserDeviceNameEntry *gtk.Entry +var SettingsTabUserPasswordEntry *gtk.Entry +var SettingsTabUserStatusLabel *gtk.Label +var SearchTabSearchEntry *gtk.SearchEntry +var SearchTabSongCheckButton *gtk.CheckButton +var SearchTabAlbumCheckButton *gtk.CheckButton +var SearchTabArtistCheckButton *gtk.CheckButton +var SearchListBoxResult *gtk.ListBox +var PlayListListBox *gtk.ListBox +var MainSongTitleLabel *gtk.Label +var MainArtistLabel *gtk.Label +var MainAlbumLabel *gtk.Label +var MainTimeDrawnLabel *gtk.Label +var MainTimeLeft *gtk.Label +var MainAdjustment *gtk.Adjustment +var SettingsTabAppDeleteSwitch *gtk.Switch +var SettingsTabAppCodecComboBoxTest *gtk.ComboBoxText + +// var SettingsTabAppRetrievingComboBoxText *gtk.ComboBoxText +var SettingsTabAppCacheEntry *gtk.Entry +var QueueListBox *gtk.ListBox +var MainAlbumCover *gtk.Image +var MainPlayPauseButton *gtk.Button +var MainPreviousButton *gtk.Button +var MainNextButton *gtk.Button +var MainRepeatButton *gtk.Button +var MainStarredButton *gtk.Button +var MainSubtitlesTextView *gtk.TextView +var MainTotalTimeLabel *gtk.Label +var LibraryNotebook *gtk.Notebook +var MainNotebook *gtk.Notebook +var QueueListBoxLibrary *gtk.ListBox +var SettingsTabAppCoverSizeEntry *gtk.Entry +var SettingsTabAppBlurRadiusEntry *gtk.Entry +var SettingsTabAppBitrateEntry *gtk.Entry +var SettingsTabAppBufferSizeEntry *gtk.Entry +var SettingsTabAppStreamingProxySwitch *gtk.Switch +var SettingsTabAppStreamingProxyEntry *gtk.Entry +var SettingsTabAppAuthStreamingProxySwitch *gtk.Switch +var SettingsTabAppStreamingProxyUsernameEntry *gtk.Entry +var SettingsTabAppStreamingProxyPassEntry *gtk.Entry + +var SettingsTabAppMetadataProxySwitch *gtk.Switch +var SettingsTabAppMetadataProxyEntry *gtk.Entry +var SettingsTabAppAuthMetadataProxySwitch *gtk.Switch +var SettingsTabAppMetadataProxyUsernameEntry *gtk.Entry +var SettingsTabAppMetadataProxyPassEntry *gtk.Entry + +var SettingsAccountsFlowBox *gtk.FlowBox +var SettingsAccountFlowboxChild1 *gtk.FlowBoxChild +var SettingsAccountFlowboxChild2 *gtk.FlowBoxChild + +var SettingsNotebook *gtk.Notebook +var SettingsAccountBox *gtk.Box +var SettingsAccountLoginGrid *gtk.Grid +var SettingsAccountButtonsGrid *gtk.Grid +var SettingsAccountUserListbox *gtk.ListBox +var SettingsAppFlowBox *gtk.FlowBox +var SettingsCreditsFlowBox *gtk.FlowBox + +var SettingsAccountUsernameLabel *gtk.Label +var SettingsAccountPasswordLabel *gtk.Label +var SettingsAccountPLayerNameLabel *gtk.Label +var SettingsAccountServerLabel *gtk.Label + +var SettingsAccountTestButton *gtk.Button +var SettingsAccountSaveButton *gtk.Button +var SettingsAccountLoadButton *gtk.Button + +// var SettingsAppScrolledWindow *gtk.ScrolledWindow +var SettingsAppFlowBoxChild1 *gtk.FlowBoxChild +var SettingsAppFlowBoxChild2 *gtk.FlowBoxChild +var SettingsAppFlowBoxChild3 *gtk.FlowBoxChild +var SettingsAppFlowBoxChild4 *gtk.FlowBoxChild +var SettingsAppGrid1 *gtk.Grid +var SettingsAppGrid2 *gtk.Grid +var SettingsAppGrid3 *gtk.Grid +var SettingsAppGrid4 *gtk.Grid + +var SettingsAppLabelGrid4 *gtk.Label +var SettingsAppLabelGrid4Metadata *gtk.Label +var SettingsTabAppMetadataComboBoxTest *gtk.ComboBoxText + +var SettingsAppLabelGrid1 *gtk.Label +var SettingsAppLabelGrid2 *gtk.Label +var SettingsAppLabelGrid3 *gtk.Label + +var SettingsAppLabelGrid1DeleteOnClose *gtk.Label +var SettingsAppLabelGrid1CoverSideSize *gtk.Label +var SettingsAppLabelGrid1BackgroundBlurRadius *gtk.Label + +var SettingsAppLabelGrid2Codec *gtk.Label +var SettingsAppLabelGrid2Bitrate *gtk.Label +var SettingsAppLabelGrid2DownloadBufferSize *gtk.Label + +var SettingsTabAppHideHeaderBarSwitch *gtk.Switch +var SettingsAppLabelHideHeaderBar *gtk.Label + +var SettingsAccountScrolledWindow *gtk.ScrolledWindow +var SettingsAppScrolledWindow *gtk.ScrolledWindow +var SettingsCreditsScrolledWindow *gtk.ScrolledWindow + +var SettingsAppLabelGrid3UseProxyForStreaming *gtk.Label +var SettingsAppLabelGrid3AuthenticateStreamingProxy *gtk.Label +var SettingsAppLabelGrid3UseProxyForMetadata *gtk.Label +var SettingsAppLabelGrid3AuthenticateMetadataProxy *gtk.Label +var SettingsCreditsFlowBoxChild1 *gtk.FlowBoxChild +var SettingsCreditsLabel1 *gtk.Label + +var MetadataClient *http.Client +var HttpSettings *http.Client + +// var one gtk.EventBox + +// LIBNOTIFY + +var MainNotification notify.Notification diff --git a/slice_utils.go b/slice_utils.go new file mode 100644 index 0000000..710aa30 --- /dev/null +++ b/slice_utils.go @@ -0,0 +1,59 @@ +package main + +// Slice Utils instead of ArrayList + +// Returns Index of T +func SliceIndexOf[T comparable](slc []T, el T) int { + for i, x := range slc { + if x == el { + return i + } + } + return -1 +} + +// Swap T1 and T2 at indexes +func SliceSwapAtIndexes[T comparable](slc []T, el int, el2 int) { + x := slc[el] + slc[el] = slc[el2] + slc[el2] = x +} + +// Returns true if Slice contains T +func SliceContains[T comparable](slc []T, el T) bool { + for _, x := range slc { + if x == el { + return true + } + } + return false +} + +// Remove T if exists +func SliceRemove[T comparable](slc []T, el T) []T { + res := SliceIndexOf(slc, el) + if res != -1 { + return SliceRemoveAt(slc, res) + } + return slc +} + +func SliceRemoveAt[T comparable](slc []T, el int) []T { + for i := el; i <= len(slc)-2; i++ { + slc[i] = slc[i+1] + } + // x:= (len(slc)-2) + // slc = append([]T(nil), slc[:x]...) + return slc[:(len(slc) - 1)] +} + +func SliceInsertAt[T comparable](slc []T, el T, pos int) { + slc = append(slc, slc[len(slc)-1]) + for i := len(slc) - 2; i > pos; i-- { + slc[i] = slc[i-1] + } +} + +func SliceClear[T comparable](slc []T) []T { + return []T{} +} diff --git a/sonically.desktop b/sonically.desktop new file mode 100644 index 0000000..17379f8 --- /dev/null +++ b/sonically.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Encoding=UTF-8 +Version=0.4.1 +Type=Application +Terminal=false +Exec=/usr/bin/sonically +Name=Sonically +Icon=/usr/share/pixmaps/sonically.svg diff --git a/sonically.go b/sonically.go new file mode 100644 index 0000000..d36e815 --- /dev/null +++ b/sonically.go @@ -0,0 +1,110 @@ +package main + +import ( + // "net/http" + "os" + "path/filepath" + "runtime" + + "github.com/TheCreeper/go-notify" + "github.com/diamondburned/gotk4/pkg/gtk/v4" + "github.com/hashicorp/go-retryablehttp" + // "github.com/diamondburned/gotk4/pkg/core/glib" + // "github.com/hashicorp/go-retryablehttp" +) + +// Widely used, conditionally created objects +var ifDelete bool = true + +var delDir string +var settingsPath string = (GetRunDir() + MainDir + SettingsDir + "/gob") + +// var mainWaitGroup sync.WaitGroup + +// This is where app starts +func main() { + app := gtk.NewApplication("eu.itmodulo.sonically", 0) + app.ConnectActivate(func() { activate(app) }) + + if code := app.Run(os.Args); code > 0 { + os.Exit(code) + } +} + +// This is func that initates GUI, deserializez data +func activate(app *gtk.Application) { + AudioControlChan = make(chan bool) // Initate goroutine channel + MainWindow = gtk.NewApplicationWindow(app) + MainWindow.SetTitle("Sonically") + SetUpGui() + + x := retryablehttp.NewClient() + x.RetryMax = 15 + x.Logger = nil + + y := retryablehttp.NewClient() + y.RetryMax = 20 + y.Logger = nil + // All widgets put together + HttpSettings = x.StandardClient() // Default client for connection + MetadataClient = y.StandardClient() // Default client for connection + Init() // Set up account + MainWindow.ConnectCloseRequest(closeApp) + MainWindow.Resizable() + + MainWindow.Show() + + // Background Read User Library, to speedup later loading + if MainSettings.Bitrate != 0 { + go DownloadLib() + go DownloadPlaylist() + } + +} + +func closeApp() bool { + if CurrentCache != nil { + CurrentCache.Clean() + } + if MainSettings.DeleteOnClose { + delDir = GetRunDir() + MainDir + DataDir + "/" + + d, err := os.Open(delDir) + if err != nil { + PanicOnError(err) + } + defer d.Close() + names, err := d.Readdirnames(-1) + if err != nil { + PanicOnError(err) + } + for _, name := range names { + err = os.RemoveAll(filepath.Join(delDir, name)) + if err != nil { + PanicOnError(err) + } + } + + } + delDir = GetRunDir() + MainDir + InfDir + "/" + + d, err := os.Open(delDir) + if err != nil { + PanicOnError(err) + } + defer d.Close() + names, err := d.Readdirnames(-1) + if err != nil { + PanicOnError(err) + } + for _, name := range names { + err = os.RemoveAll(filepath.Join(delDir, name)) + if err != nil { + PanicOnError(err) + } + } + + notify.CloseNotification(MainNotification.ReplacesID) + runtime.GC() + return false +} diff --git a/sonically.svg b/sonically.svg new file mode 100644 index 0000000..d435df8 --- /dev/null +++ b/sonically.svg @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + +