(feat): migrate experimental tauri cef to electron
(feat): add flake to expose tensamin desktop package (qol): update todo
This commit is contained in:
parent
be80e2f387
commit
a209ade10b
32 changed files with 1649 additions and 459 deletions
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
description = "Tauri development environment";
|
||||
description = "Tauri mobile development environment";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
|
|
@ -53,34 +53,6 @@
|
|||
];
|
||||
};
|
||||
|
||||
desktopRuntimeLibs = with pkgs; [
|
||||
glib
|
||||
gtk3
|
||||
gdk-pixbuf
|
||||
pango
|
||||
cairo
|
||||
atk
|
||||
at-spi2-atk
|
||||
alsa-lib
|
||||
dbus
|
||||
cups
|
||||
expat
|
||||
libdrm
|
||||
libgbm
|
||||
libglvnd
|
||||
mesa
|
||||
libxkbcommon
|
||||
libx11
|
||||
libxcomposite
|
||||
libxdamage
|
||||
libxext
|
||||
libxfixes
|
||||
libxrandr
|
||||
libxcb
|
||||
systemd
|
||||
nspr
|
||||
nss
|
||||
];
|
||||
in {
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = with pkgs;
|
||||
|
|
@ -91,7 +63,6 @@
|
|||
nodejs
|
||||
pkg-config
|
||||
]
|
||||
++ desktopRuntimeLibs
|
||||
++ [
|
||||
android.androidsdk
|
||||
pkgs.android-studio-tools
|
||||
|
|
@ -130,7 +101,6 @@
|
|||
export NDK_HOME="$ANDROID_NDK_ROOT"
|
||||
export NDK_PATH="$ANDROID_NDK_ROOT"
|
||||
export JAVA_HOME="${pkgs.jdk17}"
|
||||
export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath desktopRuntimeLibs}:$LD_LIBRARY_PATH"
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,6 @@
|
|||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./context": {
|
||||
"types": "./src/context.tsx",
|
||||
"default": "./src/context.tsx"
|
||||
},
|
||||
"./controls": {
|
||||
"types": "./src/windowControls.tsx",
|
||||
"default": "./src/windowControls.tsx"
|
||||
|
|
@ -26,10 +22,6 @@
|
|||
"build:mobile:raw": "tauri android build",
|
||||
"dev:mobile": "if command -v nix >/dev/null 2>&1; then nix develop --command bun dev:mobile:raw; else bun dev:mobile:raw; fi",
|
||||
"build:mobile": "bun run render-version.ts && if command -v nix >/dev/null 2>&1; then nix develop --command bun build:mobile:raw; else bun build:mobile:raw; fi && bun run render-version.ts --unrender",
|
||||
"dev:desktop:raw": "tauri dev",
|
||||
"build:desktop:raw": "tauri build",
|
||||
"dev:desktop": "if command -v nix >/dev/null 2>&1; then nix develop --command bun dev:desktop:raw; else bun dev:desktop:raw; fi",
|
||||
"build:desktop": "if command -v nix >/dev/null 2>&1; then nix develop --command bun build:desktop:raw; else bun build:desktop:raw; fi",
|
||||
"gen-icons": "tauri icon ./logo.json",
|
||||
"format": "bunx prettier --write .",
|
||||
"lint": "eslint src"
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
# Outputs
|
||||
|
||||
- ./src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release.apk
|
||||
- ./src-tauri/target/release/bundle/deb/Tensamin*{version}*{arch}.deb
|
||||
- ./src-tauri/target/release/bundle/rpm/Tensamin-{version}-1.{arch}.rpm
|
||||
|
|
@ -18,36 +18,16 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
|||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.22"
|
||||
image = { version = "0.25", default-features = false, features = ["jpeg"] }
|
||||
tauri-plugin-opener = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
tauri-plugin-deep-link = "2"
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))'.dependencies]
|
||||
xcap = "0.4.1"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies.tauri]
|
||||
version = "2"
|
||||
features = []
|
||||
default-features = true
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies.tauri]
|
||||
version = "2"
|
||||
features = ["compression", "common-controls-v6", "dynamic-acl"]
|
||||
default-features = true
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies.tauri]
|
||||
version = "2"
|
||||
features = ["common-controls-v6", "cef", "compression", "dynamic-acl", "x11"]
|
||||
default-features = false
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies.tauri]
|
||||
version = "2"
|
||||
features = ["x11", "common-controls-v6", "cef", "compression", "dynamic-acl"]
|
||||
default-features = false
|
||||
|
||||
[target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies]
|
||||
tauri-plugin-barcode-scanner = "2"
|
||||
tauri-plugin-app-events = "0.2"
|
||||
|
|
|
|||
|
|
@ -1,204 +1,7 @@
|
|||
use serde::Serialize;
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ScreenShareSource {
|
||||
id: String,
|
||||
kind: String,
|
||||
name: String,
|
||||
subtitle: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ScreenShareAudioOutput {
|
||||
id: String,
|
||||
name: String,
|
||||
is_default: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ScreenShareCapabilities {
|
||||
platform: String,
|
||||
show_audio_output_selector: bool,
|
||||
show_audio_switch: bool,
|
||||
has_reliable_system_audio: bool,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn list_screen_share_sources() -> Result<Vec<ScreenShareSource>, String> {
|
||||
#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
|
||||
{
|
||||
use xcap::{Monitor, Window};
|
||||
|
||||
let mut sources = Vec::new();
|
||||
let mut errors = Vec::new();
|
||||
|
||||
match Monitor::all() {
|
||||
Ok(monitors) => {
|
||||
for (index, monitor) in monitors.into_iter().enumerate() {
|
||||
let mut subtitle = None;
|
||||
|
||||
if monitor.is_primary().unwrap_or(false) {
|
||||
subtitle = Some("Primary display".to_string());
|
||||
}
|
||||
|
||||
sources.push(ScreenShareSource {
|
||||
id: format!("screen:{index}"),
|
||||
kind: "screen".to_string(),
|
||||
name: monitor
|
||||
.name()
|
||||
.unwrap_or_else(|_| format!("Display {}", index + 1)),
|
||||
subtitle,
|
||||
});
|
||||
}
|
||||
}
|
||||
Err(error) => errors.push(format!("display listing failed: {error}")),
|
||||
}
|
||||
|
||||
match Window::all() {
|
||||
Ok(windows) => {
|
||||
for (index, window) in windows.into_iter().enumerate() {
|
||||
if window.is_minimized().unwrap_or(false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let title = window.title().unwrap_or_default();
|
||||
|
||||
if title.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
sources.push(ScreenShareSource {
|
||||
id: format!("window:{index}"),
|
||||
kind: "window".to_string(),
|
||||
name: title,
|
||||
subtitle: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
Err(error) => errors.push(format!("window listing failed: {error}")),
|
||||
}
|
||||
|
||||
if sources.is_empty() {
|
||||
if errors.is_empty() {
|
||||
return Ok(sources);
|
||||
}
|
||||
|
||||
return Err(errors.join("; "));
|
||||
}
|
||||
|
||||
return Ok(sources);
|
||||
}
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn list_audio_outputs() -> Result<Vec<ScreenShareAudioOutput>, String> {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
use serde_json::Value;
|
||||
use std::process::Command;
|
||||
|
||||
let output = Command::new("pactl")
|
||||
.args(["--format=json", "list", "sinks"])
|
||||
.output()
|
||||
.map_err(|error| error.to_string())?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(String::from_utf8_lossy(&output.stderr).trim().to_string());
|
||||
}
|
||||
|
||||
let default_sink = Command::new("pactl")
|
||||
.arg("get-default-sink")
|
||||
.output()
|
||||
.ok()
|
||||
.filter(|result| result.status.success())
|
||||
.map(|result| String::from_utf8_lossy(&result.stdout).trim().to_string());
|
||||
|
||||
let sinks: Value = serde_json::from_slice(&output.stdout).map_err(|error| error.to_string())?;
|
||||
let sink_entries = sinks
|
||||
.as_array()
|
||||
.ok_or_else(|| "Unexpected pactl sink response".to_string())?;
|
||||
|
||||
let mut outputs = Vec::new();
|
||||
|
||||
for sink in sink_entries {
|
||||
let Some(index) = sink.get("index").and_then(Value::as_i64) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(name) = sink.get("name").and_then(Value::as_str) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let description = sink
|
||||
.get("description")
|
||||
.and_then(Value::as_str)
|
||||
.or_else(|| {
|
||||
sink.get("properties")
|
||||
.and_then(|properties| properties.get("device.description"))
|
||||
.and_then(Value::as_str)
|
||||
})
|
||||
.unwrap_or(name)
|
||||
.to_string();
|
||||
|
||||
let is_default = default_sink.as_deref() == Some(name);
|
||||
|
||||
outputs.push(ScreenShareAudioOutput {
|
||||
id: index.to_string(),
|
||||
name: description,
|
||||
is_default,
|
||||
});
|
||||
}
|
||||
|
||||
outputs.sort_by_key(|output| !output.is_default);
|
||||
|
||||
return Ok(outputs);
|
||||
}
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_screen_share_capabilities() -> ScreenShareCapabilities {
|
||||
ScreenShareCapabilities {
|
||||
platform: if cfg!(target_os = "linux") {
|
||||
"linux"
|
||||
} else if cfg!(target_os = "windows") {
|
||||
"windows"
|
||||
} else if cfg!(target_os = "macos") {
|
||||
"macos"
|
||||
} else {
|
||||
"other"
|
||||
}
|
||||
.to_string(),
|
||||
show_audio_output_selector: cfg!(target_os = "linux"),
|
||||
show_audio_switch: cfg!(any(target_os = "windows", target_os = "macos")),
|
||||
has_reliable_system_audio: cfg!(target_os = "windows"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
let builder = tauri::Builder::default();
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
let builder = builder.command_line_args([
|
||||
("enable-media-stream", None::<String>),
|
||||
("enable-usermedia-screen-capturing", None::<String>),
|
||||
("allow-http-screen-capture", None::<String>),
|
||||
#[cfg(target_os = "linux")]
|
||||
(
|
||||
"enable-features",
|
||||
Some("WebRTCPipeWireCapturer".to_string()),
|
||||
),
|
||||
]);
|
||||
|
||||
let builder = builder
|
||||
.plugin(tauri_plugin_deep_link::init())
|
||||
.plugin(tauri_plugin_opener::init());
|
||||
|
|
@ -225,11 +28,6 @@ pub fn run() {
|
|||
}
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
list_screen_share_sources,
|
||||
list_audio_outputs,
|
||||
get_screen_share_capabilities
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
{
|
||||
eprintln!("error while running tauri application: {error}");
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": ["app", "deb", "rpm", "nsis", "msi", "dmg"],
|
||||
"targets": [],
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
|
|
@ -37,9 +37,6 @@
|
|||
},
|
||||
"plugins": {
|
||||
"deep-link": {
|
||||
"desktop": {
|
||||
"schemes": ["tensamin"]
|
||||
},
|
||||
"mobile": [
|
||||
{
|
||||
"scheme": ["tensamin"],
|
||||
|
|
|
|||
|
|
@ -1,96 +0,0 @@
|
|||
import { createContext, useContext, useMemo, type ReactNode } from "react";
|
||||
import { invoke, isTauri } from "@tauri-apps/api/core";
|
||||
|
||||
export type DesktopScreenShareSource = {
|
||||
id: string;
|
||||
kind: "screen" | "window";
|
||||
name: string;
|
||||
subtitle?: string | null;
|
||||
};
|
||||
|
||||
export type DesktopScreenShareAudioOutput = {
|
||||
id: string;
|
||||
name: string;
|
||||
isDefault: boolean;
|
||||
};
|
||||
|
||||
export type DesktopScreenShareCapabilities = {
|
||||
platform: "linux" | "macos" | "windows" | "other";
|
||||
showAudioOutputSelector: boolean;
|
||||
showAudioSwitch: boolean;
|
||||
hasReliableSystemAudio: boolean;
|
||||
};
|
||||
|
||||
type DesktopMediaContextValue = {
|
||||
getScreenShareCapabilities: () => Promise<DesktopScreenShareCapabilities>;
|
||||
listScreenShareSources: () => Promise<DesktopScreenShareSource[]>;
|
||||
listScreenShareAudioOutputs: () => Promise<DesktopScreenShareAudioOutput[]>;
|
||||
};
|
||||
|
||||
const defaultCapabilities: DesktopScreenShareCapabilities = {
|
||||
platform: "other",
|
||||
showAudioOutputSelector: false,
|
||||
showAudioSwitch: false,
|
||||
hasReliableSystemAudio: false,
|
||||
};
|
||||
|
||||
const desktopMediaContext = createContext<DesktopMediaContextValue | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
async function listScreenShareSources(): Promise<DesktopScreenShareSource[]> {
|
||||
if (!isTauri()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return invoke<DesktopScreenShareSource[]>("list_screen_share_sources");
|
||||
}
|
||||
|
||||
async function listScreenShareAudioOutputs(): Promise<
|
||||
DesktopScreenShareAudioOutput[]
|
||||
> {
|
||||
if (!isTauri()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return invoke<DesktopScreenShareAudioOutput[]>(
|
||||
"list_screen_share_audio_outputs",
|
||||
);
|
||||
}
|
||||
|
||||
async function getScreenShareCapabilities(): Promise<DesktopScreenShareCapabilities> {
|
||||
if (!isTauri()) {
|
||||
return defaultCapabilities;
|
||||
}
|
||||
|
||||
return invoke<DesktopScreenShareCapabilities>(
|
||||
"get_screen_share_capabilities",
|
||||
);
|
||||
}
|
||||
|
||||
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 Provider({ children }: { children: ReactNode }) {
|
||||
const value = useMemo<DesktopMediaContextValue>(
|
||||
() => ({
|
||||
getScreenShareCapabilities,
|
||||
listScreenShareSources,
|
||||
listScreenShareAudioOutputs,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<desktopMediaContext.Provider value={value}>
|
||||
{children}
|
||||
</desktopMediaContext.Provider>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue