mirror of
https://github.com/Theaninova/TheaninovOS.git
synced 2026-07-25 10:04:44 +00:00
feat: audio cast
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
username,
|
||||
...
|
||||
}:
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.hardware.audio.audio-share;
|
||||
in
|
||||
{
|
||||
options.hardware.audio.audio-share = {
|
||||
enable = mkEnableOption "LAN Audio Sharing";
|
||||
partner = mkOption {
|
||||
type = types.submodule {
|
||||
options = {
|
||||
ip = mkOption {
|
||||
type = types.str;
|
||||
description = "The IP address of the partner machine to share audio with.";
|
||||
};
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
description = "Name of the partner machine (for display purposes)";
|
||||
};
|
||||
combine = mkOption {
|
||||
type = types.str;
|
||||
description = "Name of the sink to combine the shared output with";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config =
|
||||
let
|
||||
sourcePort = 10001;
|
||||
repairPort = 10002;
|
||||
controlPort = 10003;
|
||||
sinkName = "combined_output_3";
|
||||
in
|
||||
mkIf cfg.enable {
|
||||
networking.firewall.allowedUDPPorts = [
|
||||
sourcePort
|
||||
repairPort
|
||||
controlPort
|
||||
];
|
||||
home-manager.users.${username} = {
|
||||
programs.waybar.settings.mainBar = {
|
||||
"pulseaudio" = {
|
||||
format-icons.${sinkName} = "";
|
||||
on-click-right = lib.getExe (
|
||||
pkgs.writeShellApplication {
|
||||
name = "toggle-audio-share";
|
||||
runtimeEnv = {
|
||||
SINK_A = sinkName;
|
||||
SINK_B = cfg.partner.combine;
|
||||
};
|
||||
text = builtins.readFile ./audio-share.sh;
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
xdg.configFile = {
|
||||
"pipewire/pipewire.conf.d/52-audio-share.conf".text = builtins.toJSON {
|
||||
"context.modules" = [
|
||||
{
|
||||
name = "libpipewire-module-roc-source";
|
||||
args = {
|
||||
"local.ip" = "0.0.0.0";
|
||||
"roc.resampler.backend" = "default";
|
||||
"roc.resampler.profile" = "default";
|
||||
"roc.latency-tuner.backend" = "default";
|
||||
"roc.latency-tuner.profile" = "default";
|
||||
"fec.code" = "default";
|
||||
"sess.latency.msec" = 100;
|
||||
"local.source.port" = sourcePort;
|
||||
"local.repair.port" = repairPort;
|
||||
"local.control.port" = controlPort;
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
"pipewire/pipewire.conf.d/52-audio-share-sink.conf".text = builtins.toJSON {
|
||||
"context.modules" = [
|
||||
{
|
||||
name = "libpipewire-module-roc-sink";
|
||||
args = {
|
||||
"fec.code" = "default";
|
||||
"remote.ip" = cfg.partner.ip;
|
||||
"remote.source.port" = sourcePort;
|
||||
"remote.repair.port" = repairPort;
|
||||
"remote.control.port" = controlPort;
|
||||
"sink.props" = {
|
||||
"node.name" = cfg.partner.name;
|
||||
"node.description" = cfg.partner.name;
|
||||
};
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "libpipewire-module-combine-stream";
|
||||
args = {
|
||||
"combine.mode" = "sink";
|
||||
"node.name" = sinkName;
|
||||
"node.description" = "Shared Audio Output";
|
||||
"combine.latency-compensate" = true;
|
||||
"combine.props" = {
|
||||
audio.position = [
|
||||
"FL"
|
||||
"FR"
|
||||
];
|
||||
};
|
||||
|
||||
"stream.rules" =
|
||||
let
|
||||
actions = {
|
||||
create-stream = {
|
||||
"combine.audio.position" = [
|
||||
"FL"
|
||||
"FR"
|
||||
];
|
||||
"audio.position" = [
|
||||
"FL"
|
||||
"FR"
|
||||
];
|
||||
};
|
||||
};
|
||||
in
|
||||
[
|
||||
{
|
||||
matches = [
|
||||
{
|
||||
"media.class" = "Audio/Sink";
|
||||
"node.name" = cfg.partner.combine;
|
||||
}
|
||||
];
|
||||
inherit actions;
|
||||
}
|
||||
{
|
||||
matches = [
|
||||
{
|
||||
"media.class" = "Audio/Sink";
|
||||
"node.name" = cfg.partner.name;
|
||||
}
|
||||
];
|
||||
inherit actions;
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
set -eu
|
||||
|
||||
get_id() {
|
||||
pw-dump | jq -r --arg name "$1" '
|
||||
.[]
|
||||
| select(.type == "PipeWire:Interface:Node")
|
||||
| select(.info.props["node.name"] == $name)
|
||||
| .id
|
||||
'
|
||||
}
|
||||
|
||||
ID_A=$(get_id "$SINK_A")
|
||||
ID_B=$(get_id "$SINK_B")
|
||||
|
||||
CURRENT_ID=$(wpctl inspect @DEFAULT_AUDIO_SINK@ |
|
||||
awk '$1 == "id" { gsub(",", "", $2); print $2 }')
|
||||
|
||||
if [ "$CURRENT_ID" = "$ID_A" ]; then
|
||||
wpctl set-default "$ID_B"
|
||||
else
|
||||
wpctl set-default "$ID_A"
|
||||
fi
|
||||
@@ -66,7 +66,7 @@ in
|
||||
sdr_min_luminance = 0.25;
|
||||
sdr_max_luminance = 250;
|
||||
icc = toString ./fv43u.icc;
|
||||
cm = "dcip3";
|
||||
# cm = "dcip3";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -18,18 +18,31 @@ in
|
||||
fonts.fontconfig.subpixel.rgba = "bgr";
|
||||
|
||||
home-manager.users.${username}.wayland.windowManager.hyprland.settings = {
|
||||
general.layout = "master";
|
||||
master = {
|
||||
orientation = "right";
|
||||
mfact = 0.65;
|
||||
always_keep_position = true;
|
||||
config = {
|
||||
general.layout = "master";
|
||||
master = {
|
||||
orientation = "right";
|
||||
mfact = 0.65;
|
||||
always_keep_position = true;
|
||||
};
|
||||
xwayland.force_zero_scaling = true;
|
||||
misc.vrr = 0; # VA suffers from VRR flicker
|
||||
};
|
||||
monitor = [
|
||||
"HDMI-A-1,2560x1440@75,0x0,1"
|
||||
"DP-1,1920x1080@144,auto-center-right,1,transform,3"
|
||||
{
|
||||
output = "HDMI-A-1";
|
||||
mode = "2560x1440@75";
|
||||
position = "0x0";
|
||||
scale = 1;
|
||||
}
|
||||
{
|
||||
output = "DP-1";
|
||||
mode = "1920x1080@144";
|
||||
position = "auto-center-right";
|
||||
scale = 1;
|
||||
transform = 3;
|
||||
}
|
||||
];
|
||||
xwayland.force_zero_scaling = true;
|
||||
misc.vrr = 0; # VA suffers from VRR flicker
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user