sane-cast: add a menu for choosing which device to cast to whenever there are multiple
This commit is contained in:
@@ -184,7 +184,6 @@ in
|
|||||||
|
|
||||||
suggestedPrograms = [
|
suggestedPrograms = [
|
||||||
"blast-to-default"
|
"blast-to-default"
|
||||||
"go2tv"
|
|
||||||
"sane-cast"
|
"sane-cast"
|
||||||
"sane-die-with-parent"
|
"sane-die-with-parent"
|
||||||
"xdg-terminal-exec"
|
"xdg-terminal-exec"
|
||||||
@@ -199,7 +198,7 @@ in
|
|||||||
sandbox.whitelistWayland = true;
|
sandbox.whitelistWayland = true;
|
||||||
sandbox.extraHomePaths = [
|
sandbox.extraHomePaths = [
|
||||||
".config/mpv" #< else mpris plugin crashes on launch
|
".config/mpv" #< else mpris plugin crashes on launch
|
||||||
".local/share/applications" #< for xdg-terminal-exec (go2tv)
|
".local/share/applications" #< for xdg-terminal-exec (sane-cast)
|
||||||
# it's common for album (or audiobook, podcast) images/lyrics/metadata to live adjacent to the primary file.
|
# it's common for album (or audiobook, podcast) images/lyrics/metadata to live adjacent to the primary file.
|
||||||
# CLI detection is too poor to pick those up, so expose the common media dirs to the sandbox to make that *mostly* work.
|
# CLI detection is too poor to pick those up, so expose the common media dirs to the sandbox to make that *mostly* work.
|
||||||
"Books/local"
|
"Books/local"
|
||||||
|
@@ -35,4 +35,3 @@ O script-binding uosc/show-in-directory #! Utils > Show in directory
|
|||||||
# script-binding uosc/open-config-directory #! Utils > Open config directory
|
# script-binding uosc/open-config-directory #! Utils > Open config directory
|
||||||
ctrl+r script-binding sane_cast/blast #! Audiocast
|
ctrl+r script-binding sane_cast/blast #! Audiocast
|
||||||
ctrl+t script-binding sane_cast/sane-cast #! Cast
|
ctrl+t script-binding sane_cast/sane-cast #! Cast
|
||||||
# script-binding sane_cast/go2tv-gui #! Cast (...) > GUI
|
|
||||||
|
@@ -29,14 +29,12 @@ function invoke_paused(in_terminal, args)
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- invoke blast in a way where it dies when we die, because:
|
-- invoke blast in a way where it dies when we die, because:
|
||||||
-- 1. when mpv exits, it `SIGKILL`s this toplevel subprocess.
|
-- 1. when mpv exits, it `SIGKILL`s this toplevel subprocess.
|
||||||
-- 2. `blast-to-default` could be a sandbox wrapper.
|
-- 2. `blast-to-default` could be a sandbox wrapper.
|
||||||
-- 3. bwrap does not pass SIGKILL or SIGTERM to its child.
|
-- 3. bwrap does not pass SIGKILL or SIGTERM to its child.
|
||||||
-- 4. hence, to properly kill blast, we have to kill all the descendants.
|
-- 4. hence, to properly kill blast, we have to kill all the descendants.
|
||||||
mp.add_key_binding(nil, "blast", function() subprocess(false, { "sane-die-with-parent", "--descendants", "--use-pgroup", "--catch-sigkill", "blast-to-default" }) end)
|
mp.add_key_binding(nil, "blast", function() subprocess(false, { "sane-die-with-parent", "--descendants", "--use-pgroup", "--catch-sigkill", "blast-to-default" }) end)
|
||||||
mp.add_key_binding(nil, "go2tv-gui", function() invoke_paused(false, { "go2tv" }) end)
|
|
||||||
mp.add_key_binding(nil, "sane-cast", function() invoke_paused(true, { "sane-cast", "--verbose", "@FILE@" }) end)
|
mp.add_key_binding(nil, "sane-cast", function() invoke_paused(true, { "sane-cast", "--verbose", "@FILE@" }) end)
|
||||||
|
|
||||||
msg.trace("load: complete")
|
msg.trace("load: complete")
|
||||||
|
@@ -3,7 +3,8 @@
|
|||||||
# vim: set filetype=python :
|
# vim: set filetype=python :
|
||||||
"""
|
"""
|
||||||
cast media (local video or audio files) to a device on the same network
|
cast media (local video or audio files) to a device on the same network
|
||||||
with some awareness of device-specific quirks.
|
with some awareness of device-specific quirks
|
||||||
|
and a menu to select a device if there's more than one online
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
@@ -133,6 +134,34 @@ class Go2TvDriver:
|
|||||||
os.execvp("go2tv", cli_args)
|
os.execvp("go2tv", cli_args)
|
||||||
|
|
||||||
|
|
||||||
|
def choose_device(devices: list[Device]) -> Device | None:
|
||||||
|
if not devices:
|
||||||
|
logger.info("no devices found!")
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(devices) == 1:
|
||||||
|
return devices[0]
|
||||||
|
|
||||||
|
dev = None
|
||||||
|
while dev is None:
|
||||||
|
# TODO: use a GUI menu like zenity?
|
||||||
|
print("choose a device:")
|
||||||
|
for i, d in enumerate(devices):
|
||||||
|
print(f"[{i + 1}] {d.model}")
|
||||||
|
print("[q] quit")
|
||||||
|
print("")
|
||||||
|
print("> ", end="")
|
||||||
|
|
||||||
|
choice = input()
|
||||||
|
if choice.strip() == "q":
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
dev = devices[int(choice.strip()) - 1]
|
||||||
|
except:
|
||||||
|
print(f"invalid choice {choice!r}")
|
||||||
|
|
||||||
|
return dev
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
logging.basicConfig()
|
logging.basicConfig()
|
||||||
logging.getLogger().setLevel(logging.INFO)
|
logging.getLogger().setLevel(logging.INFO)
|
||||||
@@ -149,19 +178,10 @@ def main():
|
|||||||
go2tv = Go2TvDriver()
|
go2tv = Go2TvDriver()
|
||||||
go2tv.scan_devices()
|
go2tv.scan_devices()
|
||||||
devices = go2tv.rank_devices()
|
devices = go2tv.rank_devices()
|
||||||
if not devices:
|
|
||||||
logger.info("no devices found! exiting")
|
|
||||||
return
|
|
||||||
|
|
||||||
for d in devices:
|
dev = choose_device(devices)
|
||||||
logger.info(f"found: {d.model}")
|
if dev:
|
||||||
|
go2tv.cast_to(dev, args.media)
|
||||||
if len(devices) != 1:
|
|
||||||
logger.info("found multiple devices! exiting")
|
|
||||||
# TODO: allow choosing, or a "--yes" option
|
|
||||||
return
|
|
||||||
|
|
||||||
go2tv.cast_to(devices[0], args.media)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
Reference in New Issue
Block a user