(feat): migrate experimental tauri cef to electron
Some checks failed
/ build-web (push) Successful in 1m19s
/ build-desktop (linux) (push) Failing after 2m33s
/ release (push) Has been cancelled
/ build-mobile (push) Has been cancelled

(feat): add flake to expose tensamin desktop package
(qol): update todo
This commit is contained in:
Alois 2026-05-25 13:16:56 +02:00
commit a209ade10b
32 changed files with 1649 additions and 459 deletions

View file

@ -20,7 +20,7 @@ import {
type DesktopScreenShareCapabilities,
type DesktopScreenShareSource,
useDesktopMedia,
} from "@tensamin/tauri/context";
} from "@tensamin/shared/desktopMedia";
import { toast } from "@tensamin/shared/log";
import { AppWindow, Loader2, MonitorUp } from "lucide-react";
import type { ScreenShareCaptureOptions } from "livekit-client";
@ -150,7 +150,9 @@ export default function ScreenShareDialog({
setLoading(true);
try {
if (capabilities.platform === "linux") {
if (capabilities.runtime === "electron") {
await startLinuxDesktopScreenShare(selectedSource.id);
} else if (capabilities.platform === "linux") {
await startLinuxDesktopScreenShare(selectedSource.id);
} else {
await setScreenShareEnabled(

View file

@ -1,4 +1,3 @@
import { invoke } from "@tauri-apps/api/core";
import { log } from "@tensamin/shared/log";
import {
type LocalTrack,
@ -22,6 +21,39 @@ type ScreenShareStoreSetState = (
| ((state: ScreenShareStoreState) => Partial<ScreenShareStoreState>),
) => void;
declare global {
interface Window {
tensaminDesktop?: {
media?: {
getScreenShareCapabilities?: () => Promise<{
runtime?: "electron" | "tauri";
platform: "linux" | "macos" | "windows" | "other";
showAudioOutputSelector: boolean;
showAudioSwitch: boolean;
hasReliableSystemAudio: boolean;
}>;
listScreenShareSources?: () => Promise<
Array<{
id: string;
kind: "screen" | "window";
name: string;
subtitle?: string | null;
thumbnail?: string | null;
}>
>;
listScreenShareAudioOutputs?: () => Promise<
Array<{
id: string;
name: string;
isDefault: boolean;
}>
>;
selectScreenShareSource?: (sourceId: string) => Promise<boolean>;
};
};
}
}
type ScreenShareControllerOptions = {
room: Room;
getState: () => ScreenShareStoreState;
@ -128,84 +160,26 @@ export function createScreenShareController({
async function startLinuxDesktopScreenShare(sourceId: string) {
await clearPublishedScreenShare();
const canvas = document.createElement("canvas");
canvas.width = 1280;
canvas.height = 720;
canvas.style.display = "none";
document.body.appendChild(canvas);
const context = canvas.getContext("2d");
if (!context) {
canvas.remove();
throw new Error("Failed to initialize the screen share canvas.");
}
const stream = canvas.captureStream(8);
const videoTrack = stream.getVideoTracks()[0];
if (!videoTrack) {
canvas.remove();
throw new Error("Failed to create a video track for screen sharing.");
}
const image = new Image();
let stopped = false;
let frameRequestInFlight = false;
const renderFrame = async () => {
if (stopped || frameRequestInFlight) {
return;
}
frameRequestInFlight = true;
try {
const dataUrl = await invoke<string>("capture_screen_share_frame", {
sourceId,
});
await new Promise<void>((resolve, reject) => {
image.onload = () => resolve();
image.onerror = () =>
reject(new Error("Failed to decode screen share frame."));
image.src = dataUrl;
});
if (
canvas.width !== image.naturalWidth ||
canvas.height !== image.naturalHeight
) {
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
}
context.drawImage(image, 0, 0, canvas.width, canvas.height);
} finally {
frameRequestInFlight = false;
}
};
await renderFrame();
const interval = window.setInterval(() => {
void renderFrame().catch((error) => {
log(
1,
"call",
"red",
"Failed to capture Linux screen share frame",
error,
);
if (window.tensaminDesktop?.media?.selectScreenShareSource) {
await window.tensaminDesktop.media.selectScreenShareSource(sourceId);
const tracks = await room.localParticipant.createScreenTracks({
audio: false,
video: true,
systemAudio: "exclude",
surfaceSwitching: "exclude",
selfBrowserSurface: "exclude",
contentHint: "detail",
});
}, 125);
await publishScreenShareTracks([videoTrack], () => {
stopped = true;
window.clearInterval(interval);
stream.getTracks().forEach((track) => track.stop());
canvas.remove();
});
await publishScreenShareTracks(tracks, () => {
tracks.forEach((track) => track.stop());
});
return;
}
throw new Error(
`Electron desktop media bridge is unavailable. Cannot capture ${sourceId}.`,
);
}
async function stopScreenShare() {

View file

@ -741,6 +741,14 @@ let screenShareController: ReturnType<
typeof createScreenShareController
> | null = null;
function getNoiseFilterAssetBaseUrl() {
if (window.location.protocol === "file:") {
return new URL("./assets", document.baseURI).href.replace(/\/$/, "");
}
return "/assets";
}
function getScreenShareController() {
if (!screenShareController) {
screenShareController = createScreenShareController({
@ -1086,7 +1094,7 @@ export function useInitializeCall() {
noiseReductionLevel: 60,
sampleRate: 48000,
assetConfig: {
cdnUrl: "/assets",
cdnUrl: getNoiseFilterAssetBaseUrl(),
},
}),
[],

View file

@ -8,6 +8,6 @@
- Admin call actions
- Timeout
- Disconnect
- Desktop-App screenshares
- Context menus
- Popout Window
- Add quality selection

View file

@ -5,6 +5,7 @@
"type": "module",
"exports": {
"./data": "./src/data.ts",
"./desktopMedia": "./src/desktopMedia.tsx",
"./log": "./src/log.tsx",
"./settings": "./src/settings.ts",
"./features/legal/schema": "./src/features/legal/schema.ts",

View file

@ -0,0 +1,113 @@
import { createContext, useContext, useMemo, type ReactNode } from "react";
export type DesktopScreenShareSource = {
id: string;
kind: "screen" | "window";
name: string;
subtitle?: string | null;
thumbnail?: string | null;
};
export type DesktopScreenShareAudioOutput = {
id: string;
name: string;
isDefault: boolean;
};
export type DesktopScreenShareCapabilities = {
runtime?: "electron" | "tauri";
platform: "linux" | "macos" | "windows" | "other";
showAudioOutputSelector: boolean;
showAudioSwitch: boolean;
hasReliableSystemAudio: boolean;
};
type ElectronDesktopApi = {
media?: {
getScreenShareCapabilities?: () => Promise<DesktopScreenShareCapabilities>;
listScreenShareSources?: () => Promise<DesktopScreenShareSource[]>;
listScreenShareAudioOutputs?: () => Promise<DesktopScreenShareAudioOutput[]>;
selectScreenShareSource?: (sourceId: string) => Promise<boolean>;
};
};
declare global {
interface Window {
tensaminDesktop?: ElectronDesktopApi;
}
}
type DesktopMediaContextValue = {
getScreenShareCapabilities: () => Promise<DesktopScreenShareCapabilities>;
listScreenShareSources: () => Promise<DesktopScreenShareSource[]>;
listScreenShareAudioOutputs: () => Promise<DesktopScreenShareAudioOutput[]>;
};
const defaultCapabilities: DesktopScreenShareCapabilities = {
runtime: undefined,
platform: "other",
showAudioOutputSelector: false,
showAudioSwitch: false,
hasReliableSystemAudio: false,
};
const desktopMediaContext = createContext<DesktopMediaContextValue | undefined>(
undefined,
);
async function listScreenShareSources(): Promise<DesktopScreenShareSource[]> {
if (window.tensaminDesktop?.media?.listScreenShareSources) {
return window.tensaminDesktop.media.listScreenShareSources();
}
return [];
}
async function listScreenShareAudioOutputs(): Promise<
DesktopScreenShareAudioOutput[]
> {
if (window.tensaminDesktop?.media?.listScreenShareAudioOutputs) {
return window.tensaminDesktop.media.listScreenShareAudioOutputs();
}
return [];
}
async function getScreenShareCapabilities(): Promise<DesktopScreenShareCapabilities> {
if (window.tensaminDesktop?.media?.getScreenShareCapabilities) {
return window.tensaminDesktop.media.getScreenShareCapabilities();
}
return defaultCapabilities;
}
export function useDesktopMedia() {
const value = useContext(desktopMediaContext);
if (!value) {
throw new Error("useDesktopMedia must be used within the desktop provider");
}
return value;
}
export default function DesktopMediaProvider({
children,
}: {
children: ReactNode;
}) {
const value = useMemo<DesktopMediaContextValue>(
() => ({
getScreenShareCapabilities,
listScreenShareSources,
listScreenShareAudioOutputs,
}),
[],
);
return (
<desktopMediaContext.Provider value={value}>
{children}
</desktopMediaContext.Provider>
);
}