Skip to content

Commit

Permalink
GH-81: Add initial screen sharing test
Browse files Browse the repository at this point in the history
  • Loading branch information
SetZero committed Jan 14, 2024
1 parent 7b6f33d commit e73a7b7
Show file tree
Hide file tree
Showing 15 changed files with 511 additions and 16 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
"react-easy-crop": "^5.0.4",
"react-redux": "^8.0.5",
"react-router-dom": "^6.10.0",
"socket.io": "^4.7.4",
"socket.io-client": "^4.7.4",
"tinycolor2": "^1.6.0"
},
"devDependencies": {
Expand Down
34 changes: 33 additions & 1 deletion src-tauri/src/commands/settings_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ use std::{
sync::RwLock,
};

use reqwest::Identity;
use tauri::State;
use tracing::{info, trace};

use crate::utils::{constants::get_project_dirs, server::Server};
use crate::{utils::{constants::get_project_dirs, server::{Server, UserIdentity}}, errors::certificate_error::CertificateError};

use super::utils::settings::FrontendSettings;

Expand Down Expand Up @@ -45,6 +46,7 @@ pub fn save_server(
server_host: &str,
server_port: u16,
username: &str,
identity: Option<UserIdentity>,
) -> Result<(), String> {
info!("Saving server: {server_host}:{server_port}");
let mut server_file = get_settings_file(SERVER_SETTINS_FILE)?;
Expand All @@ -65,6 +67,7 @@ pub fn save_server(
host: server_host.to_string(),
port: server_port,
username: username.to_string(),
identity,
});

trace!("Server list: {:#?}", server_list);
Expand Down Expand Up @@ -164,3 +167,32 @@ pub fn get_frontend_settings(

Ok(settings_data)
}


#[tauri::command]
pub fn get_identity_certs() -> Result<Vec<String>, String> {
let project_dirs = get_project_dirs()
.ok_or_else(|| CertificateError::new("Unable to load project dir")).map_err(|e| format!("{:?}", e))?;
let data_dir = project_dirs.data_dir();

if !data_dir.exists() {
std::fs::create_dir_all(&data_dir).map_err(|e| format!("{:?}", e))?;
}

let mut certs = Vec::new();

let dir_entries = fs::read_dir(&data_dir)
.map_err(|e| format!("Error reading directory: {}", e))?;

for entry in dir_entries {
let entry = entry.map_err(|e| format!("Error reading directory entry: {}", e))?;
let file_name = entry.file_name();
let file_name_str = file_name.to_string_lossy();

if file_name_str.starts_with("cert_") && file_name_str.ends_with(".pem") {
certs.push(file_name_str.into_owned());
}
}

Ok(certs)
}
1 change: 1 addition & 0 deletions src-tauri/src/connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ impl Connection {
.store_to_project_dir(true)
.build()?;

//TODO: Check for ECDHE-RSA-AES256-GCM-SHA384
let socket = TcpStream::connect(server_uri).await?;
let cx = TlsConnector::builder()
.identity(certificate_store.get_client_certificate()?)
Expand Down
6 changes: 5 additions & 1 deletion src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ use crate::commands::{
change_user_state, connect_to_server, crop_and_store_image, disable_audio_info,
enable_audio_info, get_audio_devices, like_message, logout, send_message,
set_audio_input_setting, set_audio_output_setting, set_user_image,
settings_cmd::{get_frontend_settings, get_server_list, save_frontend_settings, save_server},
settings_cmd::{
get_identity_certs, get_frontend_settings, get_server_list, save_frontend_settings,
save_server,
},
web_cmd::{get_open_graph_data_from_website, open_browser},
zip_cmd::{convert_to_base64, unzip_data_from_utf8, zip_data_to_utf8},
};
Expand Down Expand Up @@ -102,6 +105,7 @@ async fn main() {
get_open_graph_data_from_website,
save_frontend_settings,
get_frontend_settings,
get_identity_certs,
set_audio_input_setting,
set_audio_output_setting,
enable_audio_info,
Expand Down
2 changes: 2 additions & 0 deletions src-tauri/src/utils/audio/processing/voice_activation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,13 @@ impl<T: VoiceActivationType> VoiceActivation<T> {
max.map_or(Some(x), |max| Some(if x > max { x } else { max }))
})
.unwrap_or_else(T::zero); // Apply the VAD logic

max_amplitude = if amplitude > max_amplitude {
amplitude
} else {
max_amplitude
};

if vad.update(&amplitude) {
// If the VAD is on, reset the fade out counter
self.fade_out_count = 0;
Expand Down
23 changes: 23 additions & 0 deletions src-tauri/src/utils/audio/recorder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use std::{
time::Duration,
};

use serde::de::IntoDeserializer;
use tokio::sync::broadcast::{self, Receiver};
use tracing::{error, info, trace, warn};

Expand All @@ -23,6 +24,28 @@ use super::{
processing::voice_activation::{VoiceActivation, VoiceActivationType},
};

struct GlobalMaxAvg {
max_avg: f32,
max_avg_count: u64,
}

impl Default for GlobalMaxAvg {
fn default() -> Self {
Self {
max_avg: 0.0,
max_avg_count: 0,
}
}
}

impl GlobalMaxAvg {
fn update(&mut self, value: f32) {
self.max_avg_count += 1;
self.max_avg =
(self.max_avg * (self.max_avg_count - 1) as f32 + value) / self.max_avg_count as f32;
}
}

pub struct Recorder {
audio_thread: Option<thread::JoinHandle<()>>,
playing: Arc<AtomicBool>,
Expand Down
7 changes: 7 additions & 0 deletions src-tauri/src/utils/server.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct UserIdentity {
pub name: String,
pub file: String,
}

#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct Server {
pub description: String,
pub host: String,
pub port: u16,
pub username: String,
pub identity: Option<UserIdentity>
}
3 changes: 2 additions & 1 deletion src/components/ChatInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ function ChatInput() {
chatMessageHandler.sendCustomChatMessage("[[ Image too large ( " + formatBytes((reader.result as string).length) + " out of " + formatBytes(0x7fffff) + ") ]]", currentUser);
return;
}
let img = '<img src="' + reader.result + '" />';
const legacyImageSize = 600; // Adapt image size for legacy clients
let img = `<img src="${reader.result}" width="${legacyImageSize}" />`;
chatMessageHandler.sendCustomChatMessage(img, currentUser);
};
}
Expand Down
18 changes: 17 additions & 1 deletion src/components/ChatMessageContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Avatar, Box, Card, CardContent, Grid, List } from "@mui/material";
import { Avatar, Box, Card, CardContent, Grid, List, Typography } from "@mui/material";
import React, { ReactElement, useEffect, useMemo, useRef, useState } from "react";
import { MemoChatMessage } from "./ChatMessage";
import { useSelector } from "react-redux";
Expand Down Expand Up @@ -133,6 +133,21 @@ const ChatMessageContainer = (props: ChatMessageContainerProps) => {
return popoverMap;
}, [userIdToUserMap, userInfoAnchor]);

const emptyChatMessageContainer = useMemo(() => {
if (props.messages.length === 0) {
return (
<Grid container sx={{ height: '100%', width: '100%' }} justifyContent="center" alignItems="center">
<Grid item>
<Box sx={{ backgroundColor: 'transparent' }}>
<Typography variant="h2" sx={{ color: 'transparent', textShadow: '2px 2px 3px rgba(50,50,50,0.5)', backgroundClip: 'text', backgroundColor: '#333' }}>Write something :)</Typography>
</Box>
</Grid>
</Grid>
);
}
return null;
}, [props.messages]);

return (
<Box sx={{ flex: 1, overflowY: 'auto' }} ref={chatContainer}>
<List sx={{ width: '100%', maxWidth: '100%' }}>
Expand All @@ -154,6 +169,7 @@ const ChatMessageContainer = (props: ChatMessageContainerProps) => {

))}
</List>
{emptyChatMessageContainer}
{currentPopoverUserId && userIdToPopoverMap.get(currentPopoverUserId)}
<div ref={messagesEndRef} />
</Box>
Expand Down
69 changes: 66 additions & 3 deletions src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,28 @@ import LogoutIcon from '@mui/icons-material/Logout';
import InfoIcon from '@mui/icons-material/Info';
import { invoke } from "@tauri-apps/api";
import { useNavigate } from "react-router-dom";
import { useCallback, useState } from "react";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { LoadingButton } from "@mui/lab";
import './Sidebar.css'
import ChannelViewer from "./ChannelViewer";
import CurrentUserInfo from "./CurrentUserInfo";
import SettingsIcon from '@mui/icons-material/Settings';
import React from "react";
import CastIcon from '@mui/icons-material/Cast';
import { useSelector } from 'react-redux';
import { RootState } from "../store/store";
import { WebRTCStreamer, WebRTCViewer } from "../helper/webrtc/WebRTC";

function Sidebar() {

const signalingServerUrl = "http://127.0.0.1:4000";
const navigate = useNavigate();
const [logoutInProgress, setLogoutInProgress] = useState(false);
const [showWebRtcWindow, setShowWebRtcWindow] = useState(false);
const [webRtcStreamer, setWebRtcStreamer] = useState<WebRTCStreamer | undefined>(undefined);
const [webRtcViewer, setWebRtcViewer] = useState<WebRTCViewer | undefined>(undefined);

const userList = useSelector((state: RootState) => state.reducer.userInfo);
let currentUserId = userList.currentUser?.id;
let currentChannelId = userList.currentUser?.channel_id;

const triggerLogout = useCallback((event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
event.preventDefault();
Expand All @@ -30,17 +40,70 @@ function Sidebar() {
navigate("/settings");
}, [navigate]); // add dependencies here

useEffect(() => {
const viewer = new WebRTCViewer(signalingServerUrl, currentUserId ?? 0, currentChannelId ?? 0);
viewer.listen();
viewer.onStream((stream) => {
setShowWebRtcWindow(true);
});
setWebRtcViewer(viewer);
return () => {
if (webRtcStreamer) {
webRtcStreamer.stop();
}
viewer.stop();
setShowWebRtcWindow(false);
}
}, []);

const streamPreview = useMemo(() => {
if (showWebRtcWindow) {
return (
<Box sx={{ overflowY: 'auto', width: '100%', display: 'flex', flexDirection: 'column' }} >
<video
ref={streamElement => {
if (streamElement) {
webRtcViewer?.onStream((stream) => {
streamElement.srcObject = stream;
});
}
}}
autoPlay
playsInline
controls>
</video>
</Box>)
}
return null;
}, [webRtcStreamer]);


const castScreen = async (event: React.MouseEvent<HTMLButtonElement, MouseEvent>): Promise<void> => {

if (currentUserId === undefined || currentChannelId === undefined) {
return;
}
const rtc = new WebRTCStreamer(signalingServerUrl, currentUserId ?? 0, currentChannelId ?? 0);
setWebRtcStreamer(rtc);
setShowWebRtcWindow(true);
await rtc?.start();
}

return (
<Box sx={{ height: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', alignContent: 'center', width: '250px' }} className="sidebar">
<Box sx={{ flex: 1, overflowY: 'auto', width: '100%', display: 'flex', flexDirection: 'column' }} >
<CurrentUserInfo />
{streamPreview}
<ChannelViewer />
<Box m={3} sx={{ display: 'flex', justifyContent: 'center' }}>
<ButtonGroup variant="text">
<LoadingButton loading={logoutInProgress} onClick={triggerLogout} color="error"><LogoutIcon /></LoadingButton >
<Button color="inherit">
<InfoIcon />
</Button>
<Button onClick={castScreen} color="inherit">
<CastIcon />
</Button>
<Button onClick={openSettings} color="inherit">
<SettingsIcon />
</Button>
Expand Down
6 changes: 3 additions & 3 deletions src/helper/ChatMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ export class ChatMessageHandler {
invoke('send_message', { chatMessage: data, channelId: userInfo?.channel_id });
console.log("customChatMessage", data);
this.pushChatMessage({
actor: userInfo?.id || 0,
actor: userInfo?.id ?? 0,
sender: {
user_id: userInfo?.id || 0,
user_name: userInfo?.name || 'unknown'
user_id: userInfo?.id ?? 0,
user_name: userInfo?.name ?? 'unknown'
},
channel_id: [0],
tree_id: [0],
Expand Down
5 changes: 3 additions & 2 deletions src/helper/MessageParser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class DOMMessageParser {
Array.from(this.document.querySelectorAll('a')).forEach(e => {
let replaced = !this.replacementUrl.every(r => {
if (e.getAttribute('href')?.match(r.regex)) {
let replaced = e.getAttribute('href')?.replace(r.regex, r.replacement) || '';
let replaced = e.getAttribute('href')?.replace(r.regex, r.replacement) ?? '';

if (!r.inline) {
e.setAttribute('href', replaced);
Expand Down Expand Up @@ -71,6 +71,7 @@ class MessageParser {
}

parseDOM(dom: (value: DOMMessageParser) => DOMMessageParser) {
console.log("Parsing DOM");
this.input = dom(new DOMMessageParser(this.input)).build();

return this;
Expand Down Expand Up @@ -111,7 +112,7 @@ class MessageParser {
return this;
}

waitAndExecute(timeStr: string, callback: (remainingStr: string) => void): void {
private waitAndExecute(timeStr: string, callback: (remainingStr: string) => void): void {
let time = 0;
let remainingStr = '';
let timeUnits = timeStr.split(' ');
Expand Down
Loading

0 comments on commit e73a7b7

Please sign in to comment.