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,307 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
#![allow(dead_code)]
pub const GAME_CAPTURE_MAGIC: u32 = 0x4658_4743;
pub const GAME_CAPTURE_ABI_VERSION: u32 = 4;
pub const GAME_CAPTURE_ABI_VERSION_PRESENT_CLOCK: u32 = 4;
pub const GAME_CAPTURE_PRESENT_CLOCK_WALL: u32 = 0;
pub const GAME_CAPTURE_PRESENT_CLOCK_QPC: u32 = 1;
pub const GAME_CAPTURE_STATE_INIT: u32 = 0;
pub const GAME_CAPTURE_STATE_ACTIVE: u32 = 1;
pub const GAME_CAPTURE_STATE_STOPPED: u32 = 2;
pub const GAME_CAPTURE_STATE_ERROR: u32 = 3;
pub const GAME_CAPTURE_STATE_RESIZE_REQUIRED: u32 = 4;
pub const GAME_CAPTURE_BUFFER_COUNT: usize = 2;
pub const GAME_CAPTURE_BYTES_PER_PIXEL: u32 = 4;
pub const GAME_CAPTURE_TRANSPORT_MEMORY: u32 = 0;
pub const GAME_CAPTURE_TRANSPORT_SHARED_TEXTURE: u32 = 1;
pub const GAME_CAPTURE_API_UNKNOWN: u32 = 0;
pub const GAME_CAPTURE_API_OPENGL: u32 = 1;
pub const GAME_CAPTURE_API_D3D8: u32 = 2;
pub const GAME_CAPTURE_API_D3D9: u32 = 3;
pub const GAME_CAPTURE_API_D3D10: u32 = 4;
pub const GAME_CAPTURE_API_D3D11: u32 = 5;
pub const GAME_CAPTURE_API_D3D12: u32 = 6;
pub const GAME_CAPTURE_API_VULKAN: u32 = 7;
pub const GAME_CAPTURE_FALLBACK_NONE: u32 = 0;
pub const GAME_CAPTURE_FALLBACK_SHARED_TEXTURE_UNSUPPORTED: u32 = 1;
pub const GAME_CAPTURE_FALLBACK_FORMAT_UNSUPPORTED: u32 = 2;
pub const GAME_CAPTURE_FALLBACK_FORCED_CPU: u32 = 3;
pub const GAME_CAPTURE_FALLBACK_EXTERNAL_MEMORY_UNSUPPORTED: u32 = 4;
pub const GAME_CAPTURE_FALLBACK_MULTISAMPLED: u32 = 5;
pub const GAME_CAPTURE_FALLBACK_DEVICE_LOST: u32 = 6;
pub const GAME_CAPTURE_FLAG_HDR: u32 = 1 << 0;
pub const GAME_CAPTURE_FLAG_MULTISAMPLED: u32 = 1 << 1;
pub const GAME_CAPTURE_FLAG_FLIP_VERTICAL: u32 = 1 << 2;
pub const GAME_CAPTURE_FLAG_PROTECTED: u32 = 1 << 3;
pub const GAME_CAPTURE_FLAG_TEN_BIT: u32 = 1 << 4;
pub const ENV_DISABLE_HOOK: &str = "FLUXER_GAME_CAPTURE_DISABLE_HOOK";
pub const ENV_DISABLE_D3D12: &str = "FLUXER_GAME_CAPTURE_DISABLE_D3D12";
pub const ENV_DISABLE_VULKAN: &str = "DISABLE_FLUXER_VULKAN_CAPTURE";
pub const ENV_FORCE_CPU: &str = "FLUXER_GAME_CAPTURE_FORCE_CPU";
pub const ENV_FORCE_SHARED_TEXTURE: &str = "FLUXER_GAME_CAPTURE_FORCE_SHARED_TEXTURE";
pub const ENV_ENABLE_OPENGL_SHARED_TEXTURE: &str =
"FLUXER_GAME_CAPTURE_ENABLE_OPENGL_SHARED_TEXTURE";
pub const ENV_DISABLE_OPENGL_SHARED_TEXTURE: &str =
"FLUXER_GAME_CAPTURE_DISABLE_OPENGL_SHARED_TEXTURE";
pub const ENV_VERBOSE: &str = "FLUXER_GAME_CAPTURE_VERBOSE";
pub const ENV_INJECT_METHOD: &str = "FLUXER_GAME_CAPTURE_INJECT_METHOD";
pub const GAME_CAPTURE_CONTROL_DISABLE_SHARED_TEXTURE: u32 = 1 << 0;
pub fn env_flag_enabled(name: &str) -> bool {
std::env::var_os(name)
.map(|value| !value.is_empty())
.unwrap_or(false)
}
pub const GAME_CAPTURE_INFO_PREFIX: &str = "FluxerGameCapture_Info_";
pub const GAME_CAPTURE_FRAME_PREFIX: &str = "FluxerGameCapture_Frame_";
pub const GAME_CAPTURE_READY_PREFIX: &str = "FluxerGameCapture_Ready_";
pub const GAME_CAPTURE_STOP_PREFIX: &str = "FluxerGameCapture_Stop_";
pub const GAME_CAPTURE_KEEPALIVE_PREFIX: &str = "FluxerGameCapture_KeepAlive_";
pub const GAME_CAPTURE_MUTEX_PREFIX: &str = "FluxerGameCapture_FrameMutex_";
#[repr(C)]
#[derive(Clone, Copy)]
pub struct GameCaptureSharedInfo {
pub magic: u32,
pub version: u32,
pub state: u32,
pub last_error: u32,
pub hwnd: u64,
pub max_width: u32,
pub max_height: u32,
pub width: u32,
pub height: u32,
pub pitch: u32,
pub frame_index: u32,
pub frame_counter: u64,
pub timestamp_us: i64,
pub target_frame_interval_ns: u64,
pub transport: u32,
pub dxgi_format: u32,
pub texture_handle: u64,
pub api_type: u32,
pub fallback_reason: u32,
pub capture_flags: u32,
pub dropped_frame_counter: u64,
pub last_present_timestamp_us: i64,
pub control: u32,
pub present_clock: u32,
pub reserved: [u32; 23],
}
const _: () = assert!(std::mem::size_of::<GameCaptureSharedInfo>() == 224);
impl GameCaptureSharedInfo {
pub fn new(hwnd: u64, max_width: u32, max_height: u32, frame_rate: u32) -> Self {
Self {
magic: GAME_CAPTURE_MAGIC,
version: GAME_CAPTURE_ABI_VERSION,
state: GAME_CAPTURE_STATE_INIT,
last_error: 0,
hwnd,
max_width,
max_height,
width: 0,
height: 0,
pitch: max_width.saturating_mul(GAME_CAPTURE_BYTES_PER_PIXEL),
frame_index: 0,
frame_counter: 0,
timestamp_us: 0,
target_frame_interval_ns: frame_interval_ns(frame_rate),
transport: GAME_CAPTURE_TRANSPORT_MEMORY,
dxgi_format: 0,
texture_handle: 0,
api_type: GAME_CAPTURE_API_UNKNOWN,
fallback_reason: GAME_CAPTURE_FALLBACK_NONE,
capture_flags: 0,
dropped_frame_counter: 0,
last_present_timestamp_us: 0,
control: 0,
present_clock: GAME_CAPTURE_PRESENT_CLOCK_WALL,
reserved: [0; 23],
}
}
}
pub fn host_supports_present_clock(version: u32) -> bool {
version >= GAME_CAPTURE_ABI_VERSION_PRESENT_CLOCK
}
pub fn qpc_ticks_to_us(ticks: i64, frequency: i64) -> i64 {
if frequency <= 0 {
return 0;
}
if ticks <= 0 {
return 0;
}
let whole_second_us = (ticks / frequency).saturating_mul(1_000_000);
let fractional_us = (ticks % frequency).saturating_mul(1_000_000) / frequency;
whole_second_us.saturating_add(fractional_us)
}
pub fn presented_recently(
present_clock: u32,
last_present_us: i64,
wall_now_us: i64,
qpc_now_us: i64,
window_us: i64,
) -> bool {
assert!(window_us > 0);
if last_present_us <= 0 {
return false;
}
let now_us = if present_clock == GAME_CAPTURE_PRESENT_CLOCK_QPC {
qpc_now_us
} else {
wall_now_us
};
now_us.saturating_sub(last_present_us) < window_us
}
#[cfg(target_os = "windows")]
pub fn qpc_now_us() -> i64 {
use windows_sys::Win32::System::Performance::{
QueryPerformanceCounter, QueryPerformanceFrequency,
};
let mut ticks = 0i64;
let mut frequency = 0i64;
let counter_ok = unsafe { QueryPerformanceCounter(&mut ticks) };
let frequency_ok = unsafe { QueryPerformanceFrequency(&mut frequency) };
if counter_ok == 0 || frequency_ok == 0 {
return 0;
}
qpc_ticks_to_us(ticks, frequency)
}
pub fn frame_interval_ns(frame_rate: u32) -> u64 {
let rate = frame_rate.clamp(1, 144) as u64;
1_000_000_000 / rate
}
pub fn frame_buffer_stride(width: u32) -> u32 {
width.saturating_mul(GAME_CAPTURE_BYTES_PER_PIXEL)
}
pub fn frame_buffer_size(width: u32, height: u32) -> Option<usize> {
frame_buffer_stride(width)
.checked_mul(height)?
.try_into()
.ok()
}
pub fn shared_frame_mapping_size(width: u32, height: u32) -> Option<usize> {
frame_buffer_size(width, height)?.checked_mul(GAME_CAPTURE_BUFFER_COUNT)
}
pub fn object_name(prefix: &str, pid: u32) -> String {
format!("{prefix}{pid}")
}
pub fn mutex_name(pid: u32, index: usize) -> String {
format!("{GAME_CAPTURE_MUTEX_PREFIX}{index}_{pid}")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn qpc_ticks_to_us_converts_whole_and_fractional_seconds() {
assert_eq!(qpc_ticks_to_us(10_000_000, 10_000_000), 1_000_000);
assert_eq!(qpc_ticks_to_us(15_000_000, 10_000_000), 1_500_000);
assert_eq!(qpc_ticks_to_us(1, 10_000_000), 0);
assert_eq!(qpc_ticks_to_us(10, 10_000_000), 1);
}
#[test]
fn qpc_ticks_to_us_does_not_overflow_for_long_uptimes() {
let frequency = 3_800_000_000i64;
let uptime_seconds = 400 * 24 * 60 * 60i64;
let ticks = frequency * uptime_seconds;
assert_eq!(
qpc_ticks_to_us(ticks, frequency),
uptime_seconds * 1_000_000
);
}
#[test]
fn qpc_ticks_to_us_rejects_invalid_inputs() {
assert_eq!(qpc_ticks_to_us(123, 0), 0);
assert_eq!(qpc_ticks_to_us(123, -1), 0);
assert_eq!(qpc_ticks_to_us(-123, 10_000_000), 0);
assert_eq!(qpc_ticks_to_us(0, 10_000_000), 0);
}
#[test]
fn host_supports_present_clock_gates_on_abi_version() {
assert!(!host_supports_present_clock(0));
assert!(!host_supports_present_clock(3));
assert!(host_supports_present_clock(4));
assert!(host_supports_present_clock(5));
}
#[test]
fn presented_recently_uses_wall_clock_for_legacy_hooks() {
assert!(presented_recently(
GAME_CAPTURE_PRESENT_CLOCK_WALL,
1_000_000,
1_500_000,
i64::MAX,
1_000_000
));
assert!(!presented_recently(
GAME_CAPTURE_PRESENT_CLOCK_WALL,
1_000_000,
2_000_000,
0,
1_000_000
));
}
#[test]
fn presented_recently_uses_qpc_for_present_clock_hooks() {
assert!(presented_recently(
GAME_CAPTURE_PRESENT_CLOCK_QPC,
1_000_000,
i64::MAX,
1_500_000,
1_000_000
));
assert!(!presented_recently(
GAME_CAPTURE_PRESENT_CLOCK_QPC,
1_000_000,
0,
2_000_000,
1_000_000
));
}
#[test]
fn presented_recently_requires_an_observed_present() {
assert!(!presented_recently(
GAME_CAPTURE_PRESENT_CLOCK_WALL,
0,
1,
1,
1_000_000
));
assert!(!presented_recently(
GAME_CAPTURE_PRESENT_CLOCK_QPC,
-1,
1,
1,
1_000_000
));
}
}