small adjustments, many enhancements to rktplayer

This commit is contained in:
2026-04-16 22:22:25 +02:00
parent 82fa80746c
commit 4bef5cf94c
8 changed files with 492 additions and 49 deletions

212
gui.rkt
View File

@@ -4,6 +4,8 @@
racket/runtime-path
racket/gui
racket-sprintf
open-app
xml
"utils.rkt"
"music-library.rkt"
"translate.rkt"
@@ -24,7 +26,9 @@
(wv-menu 'main-menu
(wv-menu-item 'm-file (tr "File")
#:submenu
(wv-menu (wv-menu-item 'm-select-library-dir (tr "Select Music Library Folder"))
(wv-menu
(wv-menu-item 'm-add-tab (tr "Add Playlist"))
(wv-menu-item 'm-select-library-dir (tr "Select Music Library Folder"))
(wv-menu-item 'm-set-lang (tr "Set language"))
(wv-menu-item 'm-quit (tr "Quit") #:separator #t)))
)
@@ -33,18 +37,26 @@
(define rktplayer%
(class wv-window%
(inherit-field settings icon)
(super-new
[html-path "rktplayer.html"]
[title "Racket Music Player"]
[icon (build-path rkt-gui-dir "rktplayer.png")]
)
(define initialized (make-semaphore 0))
(define closed #f)
(define el-seeker #f)
(define el-library #f)
(define el-playlist #f)
(define el-at #f)
(define el-length #f)
(define el-rate #f)
(define el-channels #f)
(define el-bits #f)
(define current-tab 0)
(define music-library
(let ((path (format "~a" (send settings get 'music-library (find-system-path 'home-dir)))))
@@ -123,7 +135,7 @@
(dbg-rktplayer "Setting album art")
(let ((el (send this element 'album-art)))
(let ((html (format "<img src=\"/get-image?~a&~a\" />"
(string-replace (format "~a" stored-file) "\\" "/")
(format "~a" stored-file)
(current-milliseconds))))
(dbg-rktplayer "Html = ~a" html)
(send el set-innerHTML! html)
@@ -148,11 +160,12 @@
)
(define (update-state st)
(dbg-rktplayer "state: ~a" st)
(unless (eq? st state)
(dbg-rktplayer "Changing to state ~a" st)
(unless (eq? state #f) ; Prevent setting src twice very fast
(if (eq? st 'playing)
(set-play-button "buttons/stop.svg")
(set-play-button "buttons/pause.svg")
(set-play-button "buttons/play.svg")
)
)
@@ -160,20 +173,150 @@
)
)
(define/public (update-tabs)
(displayln (format "playlist = ~a" playlist))
(let* ((tabs (send playlist tab-count))
(html "")
(tab-el (send this element 'tabs))
(idx 0)
)
(while (< idx tabs)
(let ((tab-name (send playlist get-tab-name idx)))
(set! html (string-append
html
(xexpr->string
(list 'span (list (list 'id (format "tab~a" idx))
'(class "tab"))
tab-name))))
)
(set! idx (+ idx 1)))
(send tab-el set-innerHTML! html)
(send this bind! "#tabs > span" 'click
(λ (el evt data)
(let* ((tab-id (send el id))
(tab-idx (string->number (substring (format "~a" tab-id) 3)))
)
(send this set-tab! tab-idx))))
(send this bind! "#tabs > span" 'contextmenu
(λ (el evt data)
(let* ((tab-id (send el id))
(tab-idx (string->number (substring (format "~a" tab-id) 3)))
)
(send this tab-context data tab-id tab-idx))))
(let ((id (string->symbol (format "tab~a" current-tab))))
(let ((el (send this element id)))
(send el add-class! 'current))
)
)
)
(define/public (tab-context evt tab-id tab-idx)
(let ((items (list
(wv-menu-item 'm-tab-rename (tr "Rename playlist") #:callback (λ () (send this rename-tab! tab-id tab-idx)))
(wv-menu-item 'm-tab-drop (tr "Remove playlist") #:callback (λ () (send this drop-tab! tab-id tab-idx)))
(wv-menu-item 'm-tab-add (tr "Add playlist") #:callback (λ () (send this add-tab)))
)
)
)
(let* ((mnu (wv-menu 'tab-popup items))
(clientX (hash-ref evt 'clientX 60))
(clientY (hash-ref evt 'clientY 60))
)
(send this popup-menu! mnu clientX clientY)
)
)
)
(define/public (drop-tab! tab-id tab-idx)
(when (= current-tab tab-idx)
(send this stop))
(send playlist drop-tab! tab-idx)
(send this set-tab! 0)
)
(define/public (rename-tab! tab-id tab-idx)
(let* ((inp-id (string->symbol (format "tab-input~a" tab-idx)))
(tab-el-id (string->symbol (format "tab~a" tab-idx)))
(html (list 'input (list (list 'id (format "~a" inp-id))
(list 'name (format "~a" inp-id))
'(type "text")
(list 'value (send playlist get-tab-name tab-idx))
)))
(tab-el (send this element tab-el-id))
(unbind-events (λ ()
(send this unbind! inp-id 'change)
(send this unbind! inp-id 'blur)))
)
(send tab-el set-innerHTML! html)
(send this unbind! tab-el-id '(click contextmenu))
(send this bind! inp-id 'change
(λ (el evt data)
(let ((tab-name (hash-ref data 'value (send playlist get-tab-name tab-idx))))
(send playlist set-tab-name! tab-idx tab-name)
(unbind-events)
(send this update-tabs))))
(send this bind! inp-id 'blur
(λ (el evt data)
(unbind-events)
(send this update-tabs)))
(let ((inp-el (send this element inp-id)))
(send inp-el focus!))
)
)
(define/public (set-tab! tab-idx)
(send this stop)
(set! current-tab tab-idx)
(send playlist load-tab tab-idx)
(send this update-tabs)
(send this update-playlist)
)
(define/public (add-tab)
(send playlist add-tab!)
(send this update-tabs))
(define (update-audio-info samples rate channels bits)
(send el-bits set-innerHTML! (format "~a ~a" bits (tr "bits")))
(send el-channels set-innerHTML! (format "~a ~a" channels (tr "channels")))
(send el-rate set-innerHTML! (format "~a Hz" rate))
)
(define (update-repeat state)
(let ((img (if (eq? state 'no-repeat)
"buttons/repeat-off.svg"
(if (eq? state 'repeat-one)
"buttons/repeat-one.svg"
"buttons/repeat.svg"))))
(let ((el (send this element 'repeat-img)))
(send el set-attr! (list 'src img)))
)
)
(define player (new player%
[time-updater update-time]
[track-nr-updater update-track-nr]
[state-updater update-state]
[repeat-updater update-repeat]
[audio-info-cb update-audio-info]
[settings settings]
))
(define inner-html-handlers (make-hash))
(define/override (page-loaded oke)
(semaphore-wait initialized)
(semaphore-post initialized)
(super page-loaded oke)
(ww-connect 'play play-or-stop)
(ww-connect 'pause pause)
(ww-connect 'play play-or-pause)
(ww-connect 'stop stop)
(ww-connect 'prev previous-track)
(ww-connect 'next next-track)
(ww-connect 'repeat repeat)
@@ -190,11 +333,19 @@
(set! el-at (send this element 'time))
(set! el-length (send this element 'totaltime))
(set! el-rate (send this element 'rate))
(set! el-bits (send this element 'bits))
(set! el-channels (send this element 'channels))
(send this set-menu! (player-menu))
(send this connect-menu! 'm-quit (λ () (send this quit)))
(send this connect-menu! 'm-select-library-dir (λ () (send this select-library)))
(send this connect-menu! 'm-add-tab (λ () (send this add-tab)))
(displayln (format "page-loaded, playlist = ~a" playlist))
(send this update-tabs)
(send this update-library)
(send this update-playlist)
)
@@ -284,9 +435,14 @@
(set! items (append items
(list
(wv-menu-item 'm-add-this (tr "Add this") #:callback (λ () (send this add-path path)))))))
(when (file-exists? (build-path path "booklet.pdf"))
(set! items (append items
(list
(wv-menu-item 'm-booklet (tr "Open booklet") #:callback (λ () (send this open-booklet path))) ;; todo check if pdf file exists
))))
(set! items (append items
(list
(wv-menu-item 'm-folder (tr "Open containing folder") #:callback (λ () (send this open-folder path)))
)))
(let* ((mnu (wv-menu 'library-popup items))
@@ -300,7 +456,7 @@
(define/public (play-path path)
(dbg-rktplayer "Playing ~a" path)
(let ((pl (new playlist% [start-map path])))
(let ((pl (new playlist% [start-map path] [settings (send settings clone 'playlists)] [id current-tab])))
(set! current-track-nr #f)
(send pl read-tracks)
(set! playlist pl)
@@ -316,20 +472,31 @@
)
(define/public (open-booklet path)
(dbg-rktplayer "Open booklet ~a" path))
(let ((booklet (build-path path "booklet.pdf")))
(dbg-rktplayer "Open booklet ~a" path)
(open-app booklet)))
(define/public (open-folder path)
(dbg-rktplayer "path: ~a" path)
(let ((folder (if (file-exists? path) (path-only path) path)))
(open-file-manager folder)))
(open-file-manager path))
;(let ((folder (if (file-exists? path) (path-only path) path)))
; (open-file-manager folder)))
(define/public (play-or-stop)
(if (eq? state 'playing)
(begin
(send player stop)
(update-time 0.0 0.0))
(send player play-track current-track-nr))
(define/public (play-or-pause)
(cond
((eq? state 'playing)
(send player pause!))
((eq? state 'pauzed)
(send player play!))
(else
(play-track 0))
)
)
(define/public (stop)
(dbg-rktplayer "Stop")
(send player stop)
(update-track-nr #f))
(define/public (play-track idx)
(send player play-track idx))
@@ -346,7 +513,14 @@
)
(define/public (repeat)
(dbg-rktplayer "Repeat")
(let ((r (send player get-repeat)))
(let ((nr (cond
((eq? r 'no-repeat) 'repeat-all)
((eq? r 'repeat-all) 'repeat-one)
(else 'no-repeat))))
(send player repeat! nr)
)
)
)
(define/public (volume)
@@ -355,6 +529,7 @@
(define/public (seek-to percentage)
(dbg-rktplayer "Seeking to percentage: ~a" percentage)
(send player seek percentage)
)
(define/public (quit)
@@ -381,9 +556,14 @@
)
(begin
(displayln "Initalizing gui")
(dbg-rktplayer "ICON: ~a" (get-field icon this))
(let ((lang (send settings get 'lang 'en)))
(dbg-rktplayer "RktPlayer started, current language: ~a" lang))
(set! playlist (new playlist% [settings (send settings clone 'playlists)]))
(send player set-list! playlist)
(displayln (format "playlist = ~a" playlist))
(semaphore-post initialized)
)
)
)

View File

@@ -11,8 +11,8 @@
<div class="pane">
<div class="buttons">
<button id="prev" class="command"><img id="previous-img" src="buttons/previous.svg" /></button>
<button id="pause" class="command"><img id="pause-img" src="buttons/pause.svg" /></button>
<button id="play" class="command"><img id="play-img" src="buttons/play.svg" /></button>
<button id="stop" class="command"><img id="stop-img" src="buttons/stop.svg" /></button>
<button id="next" class="command"><img id="next-img" src="buttons/next.svg" /></button>
<input type="range" min="0" max="100" value="0" class="h-slider" id="seek" step="0.01" />
<div class="text-block"><span id="time" class="time">00:00:00</span></div>
@@ -34,11 +34,17 @@
</div>
</div>
<div class="music-playing">
<div id="tabs" class="tabs">
<span id="tab1" class="tab">Default</span>
</div>
<div id="tracks" class="content scrolly">
<!-- Music playing -->
</div>
</div>
</div>
<div class="status">
<span class="info" id="bits"></span><span class="info" id="rate"></span><span class="info" id="channels"></span>
</div>
</div>
</body>
</html>

View File

@@ -6,7 +6,7 @@ body {
}
.pane {
height: calc(100vh - 40px - 2em - 10px);
height: calc(100vh - 2em - 20px);
width: calc(100% - 10px);
display: flex;
flex-direction: column;
@@ -31,6 +31,7 @@ button {
button:hover {
background: #909090;
transition: all 0.5s ease-in;
}
@@ -69,7 +70,7 @@ input.h-slider {
}
.hpane {
height: 100%;
height: calc(100% - 40px - 2.5em);
width: 100%;
display: flex;
}
@@ -99,12 +100,45 @@ input.h-slider {
height: 100%;
}
.tabs {
width: 100%;
height: 1.5em;
border-bottom: 1px solid #505050;
}
.tabs span.tab {
width: 50px;
overflow: hide;
text-overflow: hide;
white-space: nowrap;
border: 1px solid #505050;
cursor: default;
padding-left: 5px;
padding-right: 5px;
border-bottom: none;
}
.tabs span.tab.current {
font-weight: bold;
color: #f3961e;
}
.tabs span.tab:hover {
background: #e0e0e0;
color: black;
transition: all 0.5s ease-in;
}
.content {
width: 100%;
height: 100%;
overflow: hidden;
}
.music-playing .content {
height: calc(100% - 1.5em);
}
.scrolly {
overflow-y: auto;
}
@@ -127,6 +161,7 @@ table.music-library tr td {
table.music-library tr td:hover {
background: #e0e0e0;
color: black;
transition: all 0.5s ease-in;
}
.popup-menu, .popup-submenu {
@@ -195,6 +230,7 @@ table.tracks tr, table.tracks td {
table.tracks tr:hover {
background: #e0e0e0;
color: black;
transition: all 0.5s ease-in;
}
table.tracks tr:hover.current {
@@ -218,4 +254,18 @@ table.tracks tr.current {
justify-content: center;
}
div.status {
width: 100%;
height: 2em;
padding-top: 0.25em;
border-left: 1px solid #505050;
}
div.status span.info {
padding-left: 5px;
padding-right: 5px;
border-right: 1px solid #505050;
color: #d0d0d0;
}

View File

@@ -15,6 +15,8 @@
[time-updater (λ (time-s length-s) #t)]
[track-nr-updater (λ (nr) #t)]
[state-updater (λ (state) #t)]
[repeat-updater (λ (state) #t)]
[audio-info-cb (λ (current-sample rate channels bits) #t)]
[buffer-max-seconds 10]
[buffer-min-seconds 4]
)
@@ -27,10 +29,14 @@
(define ct-data #f)
(define closing #f)
(define pause #f)
(define repeat-state 'no-repeat)
(define ao-handle #f)
(define flac-handle #f)
(define current-music-id -1)
(define current-track-id -1)
(define current-rate 0)
(define current-bits 0)
(define current-channels 0)
@@ -53,10 +59,9 @@
current-rate current-bits current-channels ao-handle)
(dbg-rktplayer "Opening ao-handle")
(when use-ao
(let ((fmt (ao-mk-format current-bits current-rate current-channels 'big-endian)))
(set! ao-handle (ao-open-live #f fmt))
(set! ao-handle (ao-open-live current-bits current-rate current-channels 'big-endian))
(start-play-time-updater)
))
)
)
)
)
@@ -74,10 +79,15 @@
'done)
(let ((seconds (ao-at-second ao-handle))
(duration (ao-music-duration ao-handle))
(music-id (ao-at-music-id ao-handle))
)
(set! current-seconds seconds)
(time-updater current-seconds duration)
(sleep 0.1)
(unless (= music-id current-music-id)
(dbg-rktplayer "a ~a ~a ~a" music-id current-track-id seconds)
(set! current-music-id music-id)
(track-nr-updater track))
(sleep 0.2)
(updater))))
(updater)
)
@@ -85,6 +95,12 @@
)
)
(define (stream-equal? rate bits channels)
(and (= current-rate rate)
(= current-bits bits)
(= current-channels channels)))
(define (flac-play frame buffer buf-len)
(unless (eq? state 'quitted)
(let* ((sample (hash-ref frame 'number))
@@ -96,11 +112,28 @@
(bytes-per-sample-all-channels (* channels bytes-per-sample))
(duration (hash-ref frame 'duration))
)
(unless (stream-equal? rate bits-per-sample channels)
(dbg-rktplayer "Stream has changed to ~a ~a ~a" rate bits-per-sample channels)
(unless (eq? ao-handle #f)
(dbg-rktplayer "Waiting for play buffer to reach empty state")
(while (> (ao-bufsize-async ao-handle) 0)
(sleep 0.25)
)
(dbg-rktplayer "Closing ao-handle")
(ao-close ao-handle)
(set! ao-handle #f))
)
(set! current-rate rate)
(set! current-bits bits-per-sample)
(set! current-channels channels)
(set! current-length duration)
(when (eq? ao-handle #f)
(audio-info-cb sample current-rate current-channels current-bits)
)
(check-ao-handle)
(when (not (eq? ao-handle #f))
(let ((buf-seconds-left (λ () (exact->inexact
@@ -115,19 +148,21 @@
(sleep 0.25))))
(when (not (eq? ao-handle #f))
(ao-play ao-handle second duration buffer buf-len 'flac)
(ao-play ao-handle current-track-id second duration buffer buf-len 'flac)
)
)
(when pause
(dbg-rktplayer "Pauzing now...")
(dbg-rktplayer "Pausing now...")
(set-state! 'pauzed)
(ao-pause ao-handle #t)
(while (and (not (eq? ao-handle #f))
(not closing)
pause)
(sleep 0.25))
(sleep 0.5))
(ao-pause ao-handle #f)
(dbg-rktplayer "Playing on...")
(set-state! 'playing)
)
)
)
@@ -144,6 +179,7 @@
(let ((file (send ct-data get-file)))
(dbg-rktplayer "opening flac handle for file: ~a" file)
(set! flac-handle (flac-open file flac-meta flac-play))
(set! current-track-id (send ct-data get-id))
(dbg-rktplayer "Starting flac-read")
(let ((result (flac-read flac-handle)))
(if (eq? result 'end-of-stream)
@@ -202,7 +238,13 @@
)
(define/public (next-track)
(set! track (+ track 1))
(unless (eq? repeat-state 'repeat-one)
(set! track (+ track 1)))
(when (eq? repeat-state 'repeat-all)
(when (>= track (send pl length))
(set! track 0)))
(if (>= track (send pl length))
(begin
(set-state! 'stopped)
@@ -210,7 +252,7 @@
(begin
(set! ct-data (send pl track track))
(set-state! 'play)
(track-nr-updater track)
;(track-nr-updater track)
)
)
)
@@ -257,6 +299,24 @@
(dbg-rktplayer "pauzed: ~a" pause)
)
(define/public (pause!)
(set! pause #t))
(define/public (play!)
(set! pause #f))
(define/public (get-repeat)
repeat-state)
(define/public (repeat! state) ; no-repeat, repeat-all, repeat-one
(set! repeat-state state)
(repeat-updater state)
)
(define/public (seek percentage)
(ao-clear-async ao-handle)
(flac-seek flac-handle percentage))
(define (state-machine)
(let ((st (orig-current-seconds))
(s (orig-current-seconds)))
@@ -268,15 +328,17 @@
(begin
(cond
((eq? state 'stopped)
(sleep 0.01))
(sleep 0.1))
((eq? state 'play)
(if (eq? pl #f)
(set-state! 'stoppped)
(play-track-worker)))
((eq? state 'playing)
(sleep 0.01))
(sleep 0.1))
((eq? state 'track-feeded)
(send this next-track))
(else
(sleep 0.1))
)
;(let ((ns (orig-current-seconds)))
; (when (> (- ns 5) s)
@@ -287,11 +349,13 @@
))
(worker)))
(define/public (play playlist)
(define/public (set-list! playlist)
(stop-and-clear)
;(unless (eq? pl #f) (send pl display-tracks))
(set! pl playlist)
;(unless (eq? pl #f) (send pl display-tracks))
)
(define/public (play playlist)
(send this set-list! playlist)
(send this play-track 0)
)

View File

@@ -5,6 +5,7 @@
racket-sound
"utils.rkt"
racket-sprintf
"keystore.rkt"
)
(provide track%
@@ -12,6 +13,10 @@
)
(define the-displayln displayln)
(define list-for-each for-each)
(define list-length length)
(define next-track-id 0)
(define track%
(class object%
@@ -31,12 +36,19 @@
album
length)))
(define my-id (begin
(set! next-track-id (+ next-track-id 1))
(when (> next-track-id 10000000)
(set! next-track-id 1))
next-track-id))
(define/public (get-file) file)
(define/public (get-title) title)
(define/public (get-artist) artist)
(define/public (get-album) album)
(define/public (get-number) number)
(define/public (get-length) length)
(define/public (get-id) my-id)
(define (read-tags)
(let* ((f (if (path? file) (path->string file) file))
@@ -148,8 +160,12 @@
(init-field
[start-map #f]
[max-tracks 100]
[name "Default"]
[id #f]
[settings #f]
)
(define store (new keystore% [filename "rktplayer.store"]))
(define tracks '())
(define (can-add? file)
@@ -183,16 +199,125 @@
)
)
;(define/public (set-name! n)
; (set! name n))
;(define/public (set-id! id*)
; (set! id id*))
;(define/public (get-id)
; id)
;(define/public (get-name)
; name)
(define/public (tabs)
(map (λ (k)
(if (string? k)
(string->symbol k)
k))
(send store get 'tabs '(tabkey-default)))
)
(define/public (tab-count)
(list-length (tabs)))
(define/public (make-tab-key)
(string->symbol
(format "tabkey-~a-~a" (current-milliseconds) (random 10000))))
(define/public (get-tab-name idx)
(let* ((t (tabs))
(entry (list-ref t idx)))
(let ((v (send store get entry (list (format "Playlist-~a" idx) '()))))
(car v))))
(define/public (set-tab-name! idx name)
(let* ((t (tabs))
(entry (list-ref t idx))
(v (send store get entry (list (format "Playlist-~a" idx) '())))
)
(send store set! entry (list name (cadr v)))
)
)
(define/public (tab-id idx)
(let ((t (tabs)))
(list-ref t idx)))
(define/public (tab-index id)
(let ((t (tabs)))
(letrec ((f (λ (t idx)
(if (null? t)
#f
(if (eq? (car t) id)
idx
(f (cdr t) (+ idx 1)))))))
(f t 0))))
(define/public (drop-tab! idx)
(let* ((t (tabs))
(entry (list-ref t idx))
)
(send store set! 'tabs (list-drop! t idx))
(send store drop! entry)
))
(define/public (add-tab!)
(let* ((t (tabs))
(new-entry (send this make-tab-key)))
(send store set! 'tabs (append t (list new-entry)))
)
)
(define/public (save-tab!)
(let* ((entry id)
(idx (send this tab-index entry))
)
(displayln (format "entry id = ~a, ~a" entry idx))
(if (eq? idx #f)
(err-rktplayer "Cannot get tab for id ~a" entry)
(let ((value (list (send this get-tab-name idx)
(map (λ (track)
(format "~a" (send track get-file)))
tracks))))
(send store set! entry value)
)
)
)
)
(define/public (load-tab idx)
(let* ((t (tabs))
(entry (list-ref t idx))
)
(displayln (format "loading ~a" entry))
(set! id entry)
(set! tracks '())
(let ((value (send store get entry (list "Default" '()))))
(set! name (car value))
(list-for-each (λ (file)
(send this add-track file #f))
(cadr value))
)
)
#t
)
(define/public (read-tracks)
(set! tracks '())
(read-tracks-internal start-map)
(send this save-tab!)
)
(define/public (length)
(list-len tracks))
(define/public (add-track file)
(add-track* file))
(define/public (add-track file . args)
(add-track* file)
(when (null? args)
(send this save-tab!))
)
(define/public (track i)
(list-ref tracks i))
@@ -244,9 +369,11 @@
(mktable rows 'tracks formatter))))
(super-new)
(begin
(when (eq? start-map #f)
(error "Initialize playlist% with a starting map"))
(if (eq? start-map #f)
(send this load-tab 0)
(set! id (send this tab-id id)))
)
)
)

View File

@@ -44,7 +44,7 @@
))
(window (new rktplayer% [wv-context context]))
)
(send window devtools)
;(send window devtools)
window)
)

View File

@@ -51,3 +51,5 @@
('nl "Kies de map met de Muziek Bibliotheek"))
(add "Quit"
('nl "Beëindigen"))
(add "channels"
('nl "kanalen"))

View File

@@ -19,6 +19,7 @@
warn-rktplayer
fatal-rktplayer
(all-from-out simple-log)
list-drop!
)
@@ -50,6 +51,13 @@
)
)
(define (list-drop! l idx)
(if (null? l)
l
(if (= idx 0)
(cdr l)
(cons (car l) (list-drop! (cdr l) (- idx 1))))))
(define (make-delayed-reactor seconds closure)
(let* ((last-val #f)
(last-time -1)
@@ -87,11 +95,17 @@
)
(define (open-file-manager path)
(let ((folder (if (path? path) (path->string path) path)))
(let ((folder (if (path? path) (path->string path) path))
(do-open (λ (prg arg)
(let ((exe (find-executable-path prg)))
(dbg-rktplayer "(process* ~a ~a)" exe arg)
(process* exe arg))))
)
(dbg-rktplayer "open-file-manager ~a" folder)
(case (system-type 'os)
[(windows) (process (string-append "explorer.exe " folder))]
[(macosx) (process (string-append "open " folder))]
[else (process (string-append "xdg-open " folder))]))
[(windows) (do-open "explorer.exe" folder)]
[(macosx) (do-open "open" folder)]
[else (do-open "xdg-open" folder)]))
)
(define (basedir file)