Add native self-hosted instance connection to fluxer_desktop

Trimmed monorepo checkout (fluxer_desktop + packages/voice_engine_v2 +
tools/ci) with a "Connect to a Different Server" menu item and popout
that lets the desktop app switch to any self-hosted Fluxer instance,
plus fixes for well-known discovery on single-domain self-hosted
deployments and a false-positive ERR_ABORTED on same-origin client
redirects during the switch. Defaults to chat.fluxr.chat and uses an
isolated userData directory from the official build.
This commit is contained in:
2026-07-01 18:22:43 -04:00
commit 682afacd30
1763 changed files with 613720 additions and 0 deletions
@@ -0,0 +1,485 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
#![allow(dead_code)]
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use pipewire as pw;
use pw::channel::Receiver as PwReceiver;
use pw::context::ContextRc;
use pw::main_loop::MainLoopRc;
use fluxer_rt_thread::MonotonicClock;
use crate::direct_buffer::DirectAudioBuffer;
use crate::routing::{RoutingRule, SelfIdentity};
use super::common::{
DIRECT_SINK_DESCRIPTION, InventorySnapshot, LinkKey, MetadataWatch, OwnedLink,
SINK_NODE_DESCRIPTION, SINK_NODE_NAME, VirtualSinkKind, acquire_audio_rt_guard,
destroy_owned_links, ensure_virtual_sink,
};
use super::device_enum::{
DirectGlobalAddedArgs, GlobalAddedContext, handle_direct_global_added,
handle_direct_global_removed, handle_global_added, handle_global_removed,
};
use super::routing::{
DirectRoutingState, RoutingState, clear_pinned_capture_targets, recompute_direct_links,
recompute_routing, refresh_direct_sink_input_ports,
};
use super::stream_ops::{
BuildDirectStreamArgs, DirectStreamRuntime, ScreenAudioSinkSlot, build_direct_stream,
};
pub(crate) enum BridgeCommand {
Apply(RoutingRule),
Release,
SetIdentity(SelfIdentity),
Shutdown,
}
struct BridgeWorkerState {
snapshot: Arc<Mutex<InventorySnapshot>>,
owned_link_snapshot: Arc<Mutex<Vec<LinkKey>>>,
state: std::rc::Rc<std::cell::RefCell<RoutingState>>,
sink_proxy: std::rc::Rc<std::cell::RefCell<Option<pw::node::Node>>>,
owned_links: std::rc::Rc<std::cell::RefCell<Vec<OwnedLink>>>,
metadata_watchers: std::rc::Rc<std::cell::RefCell<Vec<MetadataWatch>>>,
}
fn init_bridge_state(
snapshot: Arc<Mutex<InventorySnapshot>>,
owned_link_snapshot: Arc<Mutex<Vec<LinkKey>>>,
) -> BridgeWorkerState {
BridgeWorkerState {
snapshot,
owned_link_snapshot,
state: std::rc::Rc::new(std::cell::RefCell::new(RoutingState::default())),
sink_proxy: std::rc::Rc::new(std::cell::RefCell::new(None)),
owned_links: std::rc::Rc::new(std::cell::RefCell::new(Vec::new())),
metadata_watchers: std::rc::Rc::new(std::cell::RefCell::new(Vec::new())),
}
}
fn install_bridge_registry(
registry: &pw::registry::RegistryRc,
core: &pw::core::CoreRc,
worker: &BridgeWorkerState,
) -> pw::registry::Listener {
let inv_for_global = worker.snapshot.clone();
let link_snapshot_for_global = worker.owned_link_snapshot.clone();
let state_for_global = worker.state.clone();
let core_for_global = core.clone();
let registry_for_global = registry.clone();
let owned_for_global = worker.owned_links.clone();
let metadata_for_global = worker.metadata_watchers.clone();
let inv_rm = worker.snapshot.clone();
let state_rm = worker.state.clone();
let core_rm = core.clone();
let owned_rm = worker.owned_links.clone();
let link_snapshot_rm = worker.owned_link_snapshot.clone();
let metadata_rm = worker.metadata_watchers.clone();
registry
.add_listener_local()
.global(move |obj| {
handle_global_added(
obj,
GlobalAddedContext {
registry: &registry_for_global,
inventory: &inv_for_global,
state: &state_for_global,
core: &core_for_global,
owned_links: &owned_for_global,
owned_link_snapshot: &link_snapshot_for_global,
metadata_watchers: &metadata_for_global,
sink_node_name: SINK_NODE_NAME,
},
);
})
.global_remove(move |id| {
let changed = handle_global_removed(id, &inv_rm, &state_rm);
if changed && state_rm.borrow().active_rule.is_some() {
recompute_routing(
&inv_rm,
&state_rm,
&core_rm,
&owned_rm,
&link_snapshot_rm,
&metadata_rm,
SINK_NODE_NAME,
);
}
})
.register()
}
fn install_bridge_command_handler<'a>(
mainloop: &'a MainLoopRc,
rx: PwReceiver<BridgeCommand>,
core: &pw::core::CoreRc,
worker: &BridgeWorkerState,
) -> pw::channel::AttachedReceiver<'a, BridgeCommand> {
let inv_for_cmd = worker.snapshot.clone();
let link_snapshot_for_cmd = worker.owned_link_snapshot.clone();
let state_for_cmd = worker.state.clone();
let core_for_cmd = core.clone();
let owned_for_cmd = worker.owned_links.clone();
let metadata_for_cmd = worker.metadata_watchers.clone();
let sink_for_cmd = worker.sink_proxy.clone();
let mainloop_weak = mainloop.downgrade();
rx.attach(mainloop.loop_(), move |cmd| match cmd {
BridgeCommand::Apply(rule) => {
state_for_cmd.borrow_mut().active_rule = Some(rule);
ensure_virtual_sink(
&core_for_cmd,
&sink_for_cmd,
SINK_NODE_NAME,
SINK_NODE_DESCRIPTION,
VirtualSinkKind::LegacyVirtualSource,
);
recompute_routing(
&inv_for_cmd,
&state_for_cmd,
&core_for_cmd,
&owned_for_cmd,
&link_snapshot_for_cmd,
&metadata_for_cmd,
SINK_NODE_NAME,
);
}
BridgeCommand::Release => {
clear_pinned_capture_targets(&state_for_cmd, &metadata_for_cmd);
state_for_cmd.borrow_mut().active_rule = None;
destroy_owned_links(&core_for_cmd, &owned_for_cmd, &link_snapshot_for_cmd);
}
BridgeCommand::SetIdentity(id) => {
state_for_cmd.borrow_mut().identity = id;
}
BridgeCommand::Shutdown => {
clear_pinned_capture_targets(&state_for_cmd, &metadata_for_cmd);
destroy_owned_links(&core_for_cmd, &owned_for_cmd, &link_snapshot_for_cmd);
sink_for_cmd.borrow_mut().take();
if let Some(ml) = mainloop_weak.upgrade() {
ml.quit();
}
}
})
}
pub(crate) fn run_bridge_worker(
snapshot: Arc<Mutex<InventorySnapshot>>,
owned_link_snapshot: Arc<Mutex<Vec<LinkKey>>>,
rx: PwReceiver<BridgeCommand>,
ready_tx: std::sync::mpsc::SyncSender<bool>,
) {
let _rt_guard = acquire_audio_rt_guard();
pw::init();
let Ok(mainloop) = MainLoopRc::new(None) else {
let _ = ready_tx.send(false);
return;
};
let Ok(context) = ContextRc::new(&mainloop, None) else {
let _ = ready_tx.send(false);
return;
};
let Ok(core) = context.connect_rc(None) else {
let _ = ready_tx.send(false);
return;
};
let Ok(registry) = core.get_registry_rc() else {
let _ = ready_tx.send(false);
return;
};
let worker = init_bridge_state(snapshot, owned_link_snapshot);
let _registry_listener = install_bridge_registry(&registry, &core, &worker);
let _attached_rx = install_bridge_command_handler(&mainloop, rx, &core, &worker);
let _ = ready_tx.send(true);
mainloop.run();
}
pub(crate) enum DirectCommand {
Start {
rule: RoutingRule,
identity: Box<SelfIdentity>,
},
UpdateRule {
rule: RoutingRule,
},
Stop,
Shutdown,
}
#[derive(Clone, Copy)]
pub(crate) enum DirectSinkRetention {
Preserve,
Drop,
}
pub(crate) fn stop_direct_streams(
core: &pw::core::CoreRc,
runtime: &DirectStreamRuntime,
sink_retention: DirectSinkRetention,
) {
*runtime.active_listener.borrow_mut() = None;
*runtime.active_stream.borrow_mut() = None;
destroy_owned_links(core, &runtime.owned_links, &runtime.owned_link_snapshot);
if matches!(sink_retention, DirectSinkRetention::Drop) {
runtime.sink_proxy.borrow_mut().take();
}
runtime.running.store(false, Ordering::Relaxed);
}
pub(crate) fn clear_direct_samples(samples: &Arc<Mutex<DirectAudioBuffer>>) {
if let Ok(mut guard) = samples.lock() {
guard.clear();
}
}
pub(crate) fn recompute_direct_streams(
core: &pw::core::CoreRc,
inventory: &Arc<Mutex<InventorySnapshot>>,
state: &std::rc::Rc<std::cell::RefCell<DirectRoutingState>>,
runtime: &DirectStreamRuntime,
) {
if state.borrow().active_rule.is_none() {
stop_direct_streams(core, runtime, DirectSinkRetention::Preserve);
return;
}
let updated = recompute_direct_links(
core,
inventory,
state,
&runtime.owned_links,
&runtime.owned_link_snapshot,
);
if !updated {
return;
}
ensure_or_promote_direct_stream(core, runtime);
}
fn ensure_or_promote_direct_stream(core: &pw::core::CoreRc, runtime: &DirectStreamRuntime) {
if runtime.active_stream.borrow().is_some() {
runtime.running.store(true, Ordering::Relaxed);
return;
}
let args = BuildDirectStreamArgs {
core,
samples: runtime.samples.clone(),
target_sink_name: &runtime.sink_node_name,
stream_node_name: &runtime.stream_node_name,
last_push_ns: runtime.last_push_ns.clone(),
clock: runtime.clock.clone(),
screen_audio_sink: runtime.screen_audio_sink.clone(),
};
match build_direct_stream(args) {
Ok((stream, listener)) => {
*runtime.active_stream.borrow_mut() = Some(stream);
*runtime.active_listener.borrow_mut() = Some(listener);
runtime.running.store(true, Ordering::Relaxed);
}
Err(_) => {
runtime.running.store(false, Ordering::Relaxed);
}
}
}
pub(crate) struct DirectWorkerInputs {
pub(crate) samples: Arc<Mutex<DirectAudioBuffer>>,
pub(crate) inventory: Arc<Mutex<InventorySnapshot>>,
pub(crate) owned_link_snapshot: Arc<Mutex<Vec<LinkKey>>>,
pub(crate) running: Arc<AtomicBool>,
pub(crate) sink_node_name: String,
pub(crate) last_push_ns: Arc<AtomicU64>,
pub(crate) clock: Arc<dyn MonotonicClock>,
pub(crate) screen_audio_sink: ScreenAudioSinkSlot,
}
fn build_direct_runtime(inputs: &DirectWorkerInputs) -> std::rc::Rc<DirectStreamRuntime> {
let stream_node_name = format!("{}-stream", inputs.sink_node_name);
std::rc::Rc::new(DirectStreamRuntime {
active_stream: std::rc::Rc::new(std::cell::RefCell::new(None)),
active_listener: std::rc::Rc::new(std::cell::RefCell::new(None)),
owned_links: std::rc::Rc::new(std::cell::RefCell::new(Vec::new())),
owned_link_snapshot: inputs.owned_link_snapshot.clone(),
sink_proxy: std::rc::Rc::new(std::cell::RefCell::new(None)),
samples: inputs.samples.clone(),
running: inputs.running.clone(),
sink_node_name: inputs.sink_node_name.clone(),
stream_node_name,
last_push_ns: inputs.last_push_ns.clone(),
clock: inputs.clock.clone(),
screen_audio_sink: inputs.screen_audio_sink.clone(),
})
}
fn install_direct_registry(
registry: &pw::registry::RegistryRc,
core: &pw::core::CoreRc,
inventory: &Arc<Mutex<InventorySnapshot>>,
state: &std::rc::Rc<std::cell::RefCell<DirectRoutingState>>,
runtime: &std::rc::Rc<DirectStreamRuntime>,
metadata_watchers: &std::rc::Rc<std::cell::RefCell<Vec<MetadataWatch>>>,
) -> pw::registry::Listener {
let inv_added = inventory.clone();
let state_added = state.clone();
let core_added = core.clone();
let runtime_added = runtime.clone();
let registry_added = registry.clone();
let metadata_added = metadata_watchers.clone();
let inv_rm = inventory.clone();
let state_rm = state.clone();
let core_rm = core.clone();
let runtime_rm = runtime.clone();
registry
.add_listener_local()
.global(move |obj| {
let inv_for_cb = inv_added.clone();
let state_for_cb = state_added.clone();
let core_for_cb = core_added.clone();
let runtime_for_cb = runtime_added.clone();
let on_default_change = move || {
recompute_direct_streams(&core_for_cb, &inv_for_cb, &state_for_cb, &runtime_for_cb);
};
let changed = handle_direct_global_added(
obj,
DirectGlobalAddedArgs {
registry: &registry_added,
inventory: &inv_added,
state: &state_added,
core: &core_added,
runtime: &runtime_added,
metadata_watchers: &metadata_added,
},
on_default_change,
);
if changed {
recompute_direct_streams(&core_added, &inv_added, &state_added, &runtime_added);
}
})
.global_remove(move |id| {
let changed = handle_direct_global_removed(id, &inv_rm, &state_rm, &runtime_rm);
if changed {
recompute_direct_streams(&core_rm, &inv_rm, &state_rm, &runtime_rm);
}
})
.register()
}
fn install_direct_command_handler<'a>(
mainloop: &'a MainLoopRc,
rx: PwReceiver<DirectCommand>,
core: &pw::core::CoreRc,
inventory: &Arc<Mutex<InventorySnapshot>>,
state: &std::rc::Rc<std::cell::RefCell<DirectRoutingState>>,
runtime: &std::rc::Rc<DirectStreamRuntime>,
) -> pw::channel::AttachedReceiver<'a, DirectCommand> {
let core_for_cmd = core.clone();
let inv_for_cmd = inventory.clone();
let state_for_cmd = state.clone();
let runtime_for_cmd = runtime.clone();
let mainloop_weak = mainloop.downgrade();
rx.attach(mainloop.loop_(), move |cmd| match cmd {
DirectCommand::Start { rule, identity } => {
clear_direct_samples(&runtime_for_cmd.samples);
{
let mut st = state_for_cmd.borrow_mut();
st.identity = *identity;
st.active_rule = Some(rule);
}
ensure_virtual_sink(
&core_for_cmd,
&runtime_for_cmd.sink_proxy,
&runtime_for_cmd.sink_node_name,
DIRECT_SINK_DESCRIPTION,
VirtualSinkKind::PrivateAudioSink,
);
refresh_direct_sink_input_ports(&inv_for_cmd, &state_for_cmd);
recompute_direct_streams(
&core_for_cmd,
&inv_for_cmd,
&state_for_cmd,
&runtime_for_cmd,
);
}
DirectCommand::UpdateRule { rule } => {
let active = state_for_cmd.borrow().active_rule.is_some();
if !active {
return;
}
state_for_cmd.borrow_mut().active_rule = Some(rule);
recompute_direct_streams(
&core_for_cmd,
&inv_for_cmd,
&state_for_cmd,
&runtime_for_cmd,
);
}
DirectCommand::Stop => {
stop_direct_streams(
&core_for_cmd,
&runtime_for_cmd,
DirectSinkRetention::Preserve,
);
{
let mut st = state_for_cmd.borrow_mut();
st.active_rule = None;
}
clear_direct_samples(&runtime_for_cmd.samples);
}
DirectCommand::Shutdown => {
stop_direct_streams(&core_for_cmd, &runtime_for_cmd, DirectSinkRetention::Drop);
clear_direct_samples(&runtime_for_cmd.samples);
if let Some(ml) = mainloop_weak.upgrade() {
ml.quit();
}
}
})
}
pub(crate) fn run_direct_worker(
inputs: DirectWorkerInputs,
rx: PwReceiver<DirectCommand>,
ready_tx: std::sync::mpsc::SyncSender<bool>,
) {
let _rt_guard = acquire_audio_rt_guard();
pw::init();
let Ok(mainloop) = MainLoopRc::new(None) else {
let _ = ready_tx.send(false);
return;
};
let Ok(context) = ContextRc::new(&mainloop, None) else {
let _ = ready_tx.send(false);
return;
};
let Ok(core) = context.connect_rc(None) else {
let _ = ready_tx.send(false);
return;
};
let Ok(registry) = core.get_registry_rc() else {
let _ = ready_tx.send(false);
return;
};
let state: std::rc::Rc<std::cell::RefCell<DirectRoutingState>> =
std::rc::Rc::new(std::cell::RefCell::new(DirectRoutingState::default()));
let metadata_watchers: std::rc::Rc<std::cell::RefCell<Vec<MetadataWatch>>> =
std::rc::Rc::new(std::cell::RefCell::new(Vec::new()));
let runtime = build_direct_runtime(&inputs);
let _registry_listener = install_direct_registry(
&registry,
&core,
&inputs.inventory,
&state,
&runtime,
&metadata_watchers,
);
let _attached_rx =
install_direct_command_handler(&mainloop, rx, &core, &inputs.inventory, &state, &runtime);
let _ = ready_tx.send(true);
mainloop.run();
}