Busqer: Simple Spotify client via DBus/MPRIS interface

Developing Notes

  • Using Lwt (cooperative thread-based concurrency, stands for lightweight threads), GTK3 (Desktop GUI), and React (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 as React FRP 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_glib library.
  • 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 and QCheck2
  • React has considerations for concurrent updates
    • To concurrently update values p1 and p2, 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
  • 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 Bigarrays that are generally used to interface with C & Fortran arrays
    • They don’t have any ways to map, only get/set
  • GTK does styling and theming with CSS
    • In 3.0, it uses CSSProvider and StyleContext
    • 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
  • 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
  • To use Lwt with blocking IO:
  • I profiled using perf flamegraphs and the Firefox Profiler viewer
    • A lot of time was spent on Bihist.make_freqs, which was a naive immutable IntMap fold
    • I converted this to a mutable Hashtbl fold with a parallelized Lwt list iteration with some Seq
    • 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 file
    • perf script -F +pid > test.perf - convert to file that Firefox Profiler can read

Random Notes