Busqer: Simple Spotify client via DBus/MPRIS interface
Developing Notes
- Using
Lwt
(cooperative thread-based concurrency, stands for lightweight threads),GTK3
(Desktop GUI), andReact
(Functional Reactive Programming,Lwt_react
is lwt-friendly bindings) - Brief overview
- Write/Read Spotify’s media player info through DBus/MPRIS
- e.g. Song Length, Track Title, Album Art Url, If current track changes
OBus
library exposes DBus properties asReactFRP signals.
- Display this info in a simple GUI
- Leverage FRP signals: Trigger GTK calls if underlying FRP property changes.
- Extract colors from Album Art to color GUI
- Lwt and GTK both require their own infinite main loop, combined via
lwt_gliblibrary.
- Write/Read Spotify’s media player info through DBus/MPRIS
- Be careful about mixing Lwt with normal blocking IO (src: https://discuss.ocaml.org/t/concurrent-tasks-in-lwt/14229/5)
(* This immediately returns *)
let () = Lwt_main.run @@
let open Lwt.Syntax in
let p1 = (let* () = Lwt_unix.sleep 4. in print_endline "waited 4"; Lwt.return ()) in
let p2 = (let* () = Lwt_unix.sleep 2. in print_endline "waited 2"; Lwt.return ()) in
Lwt.return ();;
(* Changing last line to `Lwt.join [p1;p2];;` properly blocks *)
- MPRIS spec: https://specifications.freedesktop.org/mpris-spec/latest/Player_Interface.html
- DBus is based on Methods, Signals, and Errors. Basically an OOP language.
- Testing with
OUnit2
andQCheck2
- https://cs3110.github.io/textbook/chapters/data/ounit.html
QCheck2
has integrated shrinking compared to version 1- Dune has features for testing, like Diffing the Result
React
has considerations for concurrent updates- To concurrently update values
p1
andp2
, their respective sets of “descendents” must be disjoint from each other- “descendents” are signals and events that are created from
p
- I believe this is because the
update
step of a value can only handle 1 input
- “descendents” are signals and events that are created from
- To concurrently update values
- GTK handles images through
GdkPixBufs
- PixBufs are raw representations, i.e. each pixel is three 8-bit values and a row is N pixels
- rowstride refers to the byte-width of a row, e.g.
N channels * row_pixel_width * byte_size
- OCaml has
Bigarray
s that are generally used to interface with C & Fortran arrays- They don’t have any ways to map, only
get/set
- They don’t have any ways to map, only
- GTK does styling and theming with CSS
- In 3.0, it uses
CSSProvider
andStyleContext
- Usually a widget has CSS classes/ids added/removed to correspond to different UI states and Theme states
- There is no easy way to change the specific color inside a CSS class at runtime
- In 3.0, it uses
Lablgtk
are the bindings I use for GTK3- the Rocq IDE also uses it (sourceview is for complex multi-line text such as a text editor)
- However, it was some rough edges Unison proposes to move away from lablgtk
- I have had issues with
GdkPixbuf.get_pixels
seg-faulting for no apparent reason, even before manipulating the pointer
- I have had issues with
- To use Lwt with blocking IO:
- Wrap in
Lwt_preemptive.detach
, “It is mostly useful when making calls to a third-party library that does not provide Lwt-aware system primitives.” https://raphael-proust.github.io/code/lwt-part-2.html
- Wrap in
- I profiled using
perf
flamegraphs and the Firefox Profiler viewer- A lot of time was spent on
Bihist.make_freqs
, which was a naive immutableIntMap
fold - I converted this to a mutable
Hashtbl
fold with a parallelizedLwt
list iteration with someSeq
- runtime was reduced from ~15s to ~6.5s (
Hashtbl
) to ~40ms (Hashtbl
&Lwt
) - commit: 2f9fdfaadb8b12067224fd14f55cb93b3e6b84a5
perf record --call-graph=dwarf -- ./_build/default/bin/main.exe
- standard perf.data fileperf script -F +pid > test.perf
- convert to file that Firefox Profiler can read
- A lot of time was spent on
Random Notes
- OCaml’s effects are untyped, i.e. “the compiler does not statically ensure that all the effects performed by the program are handled”
- As of 250514: Future of OCaml states that Typed Algebraic Effects are a long-term plan
- Blog post on effects: https://lewinb.net/posts/17_playing_with_ocaml_effects/
- It seems that EIO is the de facto for effects-based IO
- OCaml has polymorphic variants
- OCaml has
StdLabels
which are versions of some modules where functions liberally use labeled arguments - Jane Street has a library Incremental
- They say it is self-adjusting computations, i.e., computations that can be updated efficiently when their inputs change..
- FRP and SAC are subtely different
- Umut A. Acar is the researcher behind these ideas
- Reddit: OCaml Features + First-class modules vs Functors
- Discuss Ocaml: How to organize functor-heavy code?
- User ivg gives good comments like using existentials (simple GADTs)
type gps = Gps : 'gps service -> gps
- Existentials are variables in GADT constructors that are in arguments but not the final return type
- User ivg gives good comments like using existentials (simple GADTs)