Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ We are using [Task](https://taskfile.dev/) as our build tool.

### Build and run
```bash
task build_dev
task dev
```

#### Logging
For logging core is using [env-logger](https://docs.rs/env_logger/latest/env_logger/). For example debug logs can be enabled by setting the `RUST_LOG` environment variable to `hopp_core=debug`.
It is recommended to set the log level to `info` and enable `debug` only if needed, because `debug` logs are spamming.

### Testing

Currently rust unit tests are missing (it's on our TODOs), but we have created a few visual integration
Expand Down Expand Up @@ -315,4 +320,4 @@ sequenceDiagram
'mainBkg': '#e1f5fe'
}
}}%%
```
```
2 changes: 1 addition & 1 deletion core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1043,6 +1043,6 @@ fn set_fullscreen(

window.set_fullscreen(Some(Fullscreen::Borderless(Some(selected_monitor))));

return Ok(());
Ok(())
}
}
1 change: 1 addition & 0 deletions docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export default defineConfig({
{ label: "Prerequisites", slug: "quick-start/local-development/prerequisites" },
{ label: "Repository Structure", slug: "quick-start/local-development/repository-structure" },
{ label: "Development Workflow", slug: "quick-start/local-development/development-workflow" },
{ label: "FAQ", slug: "quick-start/local-development/faq" },
],
}
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,17 @@ This configuration will:
Don't forget to update the server's configuration in `env-files/.env.local` when you expose the services to the internet.

Then you have to set the `VITE_API_BASE_URL` environment variable before running the Tauri app.

## Logging

For the tauri app you can enable logging by setting the `RUST_LOG` environment variable.
The values are the same as the ones from [env-logger](https://docs.rs/env_logger/latest/env_logger/#enabling-logging)
, which is what `hopp_core` is using.

For example you could run the app with:

```bash
RUST_LOG=info task app:dev
```

The tauri backend when launches `hopp_core` forwards the log level to it.
7 changes: 7 additions & 0 deletions docs/src/content/docs/quick-start/local-development/faq.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: FAQ
description: Frequently asked questions for Hopp local development
---

### fetching `libyuv` hangs
Sometimes the build seems to hang when fetching `libyuv`. Ignore it and just wait for it to finish.
9 changes: 4 additions & 5 deletions tauri/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,11 @@ async fn show_stdout(mut receiver: Receiver<CommandEvent>, app_handle: AppHandle
while let Some(event) = receiver.recv().await {
match event {
CommandEvent::Stdout(line) => {
log::error!("{}", String::from_utf8(line).unwrap_or_default());
log::info!("{}", String::from_utf8(line).unwrap_or_default());
}
CommandEvent::Stderr(line) => {
log::error!("{}", String::from_utf8(line).unwrap_or_default());
/* For some reason the sidecar process logs to stderr. */
log::info!("{}", String::from_utf8(line).unwrap_or_default());
}
CommandEvent::Terminated(payload) => {
log::error!("show_stdout: Terminated {payload:?}");
Expand Down Expand Up @@ -498,7 +499,5 @@ pub fn setup_start_on_launch(manager: &AutoLaunchManager, first_run: bool) {
}

pub fn get_sentry_dsn() -> String {
std::env::var("SENTRY_DSN_RUST")
.unwrap_or_default()
.to_string()
env!("SENTRY_DSN_RUST").to_string()
}
17 changes: 14 additions & 3 deletions tauri/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ use std::time::Duration;
#[cfg(any(target_os = "windows", target_os = "linux"))]
use tauri::PhysicalPosition;

//testab

#[tauri::command]
async fn screenshare(
app: tauri::AppHandle,
Expand All @@ -38,6 +36,16 @@ async fn screenshare(
resolution: Extent,
) -> bool {
log::info!("screenshare: content: {content:?}, token: {token}, resolution: {resolution:?}");
/*
* If the user was previously a controller, we need to hide the viewing
* window, to hide the delay from requesting the screen share to
* screen share starting and the viewing window automatically being closed.
*/
let window = app.get_webview_window("screenshare");
if let Some(window) = window {
log::info!("screenshare: closing window");
let _ = window.hide();
}

let data = app.state::<Mutex<AppData>>();
let mut data = data.lock().unwrap();
Expand Down Expand Up @@ -476,8 +484,11 @@ fn main() {
let dock_enabled = Arc::new(Mutex::new(false));

/* This is used to guard against showing the main window if the location is not set. */
#[allow(unused_variables)]
let location_set = Arc::new(Mutex::new(false));
#[allow(unused_variables)]
let location_set_clone = location_set.clone();
#[allow(unused_variables)]
let location_set_setup = location_set.clone();

let log_level = get_log_level();
Expand Down Expand Up @@ -679,7 +690,7 @@ fn main() {
}
}

if permissions::has_ungranted_permissions() && !cfg!(debug_assertions) {
if permissions::has_ungranted_permissions() {
log::info!("Opening permissions window");
let permissions_window = tauri::WebviewWindowBuilder::new(
app,
Expand Down
5 changes: 2 additions & 3 deletions tauri/src/components/ui/call-banner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import toast from "react-hot-toast";
import { HiMiniPhoneArrowDownLeft, HiMiniPhoneXMark } from "react-icons/hi2";
import { Button } from "./button";
import useStore from "@/store/store";
import useStore, { ParticipantRole } from "@/store/store";
import { useCallback, useEffect } from "react";
import { socketService } from "@/services/socket";
import { TWebSocketMessage } from "@/payloads";
Expand Down Expand Up @@ -60,8 +60,7 @@ export const CallBanner = ({ callerId, toastId }: { callerId: string; toastId: s
...data.payload,
timeStarted: new Date(),
hasAudioEnabled: true,
isSharer: false,
isController: false,
role: ParticipantRole.NONE,
isRemoteControlEnabled: true,
});

Expand Down
106 changes: 42 additions & 64 deletions tauri/src/components/ui/call-center.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { formatDistanceToNow } from "date-fns";
import { HiMiniComputerDesktop, HiOutlineMicrophone, HiOutlinePhoneXMark } from "react-icons/hi2";
import useStore, { CallState } from "@/store/store";
import useStore, { CallState, ParticipantRole } from "@/store/store";
import { useKrispNoiseFilter } from "@livekit/components-react/krisp";
import { Separator } from "@/components/ui/separator";
import { ToggleIconButton } from "@/components/ui/toggle-icon-button";
Expand Down Expand Up @@ -83,7 +83,7 @@ export function ConnectedActions({ token }: { token: string }) {
sounds.callAccepted.play();

// Clear call tokens
if (callTokens.isSharer) {
if (callTokens.role === ParticipantRole.SHARER) {
tauriUtils.stopSharing();
}
tauriUtils.endCallCleanup();
Expand All @@ -101,33 +101,13 @@ export function ConnectedActions({ token }: { token: string }) {
});
}, [callTokens, setCallTokens]);

const handleControllerChange = useCallback(
(value: boolean) => {
if (!callTokens) return;

setCallTokens({
...callTokens,
isController: value,
isSharer: !value,
isRemoteControlEnabled: true,
});
},
[callTokens],
);

const handleIsSharerChange = useCallback(
(value: boolean) => {
if (!callTokens) return;

setCallTokens({
...callTokens,
isSharer: value,
isController: !value,
isRemoteControlEnabled: true,
});
},
[callTokens],
);
const handleRoleChange = useCallback((value: ParticipantRole) => {
if (!callTokens) return;
setCallTokens({
...callTokens,
role: value,
});
}, [callTokens]);

// Stop call when teammate disconnects
useEffect(() => {
Expand All @@ -142,8 +122,7 @@ export function ConnectedActions({ token }: { token: string }) {
<>
<ScreensharingEventListener
callTokens={callTokens}
updateState={handleControllerChange}
setIsSharer={handleIsSharerChange}
updateRole={handleRoleChange}
/>
{/* <ConnectionsHealthDebug /> */}
<div
Expand Down Expand Up @@ -175,7 +154,7 @@ export function ConnectedActions({ token }: { token: string }) {
<ScreenShareIcon callTokens={callTokens} setCallTokens={setCallTokens} />
</div>
<div className="flex flex-col gap-2 w-full">
{callTokens?.isController && (
{callTokens?.role === ParticipantRole.CONTROLLER && (
<Button
className="w-full border-gray-500 text-gray-600 flex flex-row gap-2"
variant="gradient-white"
Expand All @@ -188,7 +167,7 @@ export function ConnectedActions({ token }: { token: string }) {
</Button>
)}
<div className="w-full flex flex-row gap-2">
{callTokens?.isSharer && (
{callTokens?.role === ParticipantRole.SHARER && (
<TooltipProvider>
<Tooltip delayDuration={100}>
<TooltipTrigger>
Expand Down Expand Up @@ -365,31 +344,31 @@ function ScreenShareIcon({ callTokens, setCallTokens }: { callTokens: CallState
const toggleScreenShare = useCallback(() => {
if (!callTokens || !callTokens.videoToken) return;

if ((!callTokens.isSharer && !callTokens.isController) || callTokens.isController) {
if (callTokens.role === ParticipantRole.NONE || callTokens.role === ParticipantRole.CONTROLLER) {
// On success it will update CallState.hasVideoEnabled and State.isController
tauriUtils.createContentPickerWindow(callTokens.videoToken);
} else if (callTokens.isSharer) {
} else if (callTokens.role === ParticipantRole.SHARER) {
setCallTokens({
...callTokens,
isSharer: false,
role: ParticipantRole.NONE,
isRemoteControlEnabled: true,
});
tauriUtils.stopSharing();
}
}, [callTokens, callTokens?.videoToken]);

const changeScreenShare = useCallback(() => {
if (!callTokens || !callTokens.videoToken || !callTokens.isSharer) return;
if (!callTokens || !callTokens.videoToken) return;
tauriUtils.createContentPickerWindow(callTokens.videoToken);
}, [callTokens, callTokens?.videoToken]);

return (
<ToggleIconButton
onClick={toggleScreenShare}
icon={<HiMiniComputerDesktop className="size-5" />}
state={callTokens?.isSharer ? "active" : "neutral"}
state={callTokens?.role === ParticipantRole.SHARER ? "active" : "neutral"}
cornerIcon={
callTokens?.isSharer && (
callTokens?.role === ParticipantRole.SHARER && (
<button
onClick={changeScreenShare}
className="hover:outline hover:outline-1 hover:outline-slate-300 focus:ring-0 focus-visible:ring-0 hover:bg-slate-200 size-4 rounded-sm p-0 border-0 shadow-none hover:shadow-sm"
Expand All @@ -399,7 +378,7 @@ function ScreenShareIcon({ callTokens, setCallTokens }: { callTokens: CallState
)
}
>
{callTokens?.isSharer ? "Stop sharing" : "Share screen"}
{callTokens?.role === ParticipantRole.SHARER ? "Stop sharing" : "Share screen"}
</ToggleIconButton>
)
}
Expand All @@ -425,49 +404,48 @@ const ListenToRemoteAudio = () => {

function ScreensharingEventListener({
callTokens,
updateState,
setIsSharer,
updateRole,
}: {
callTokens: CallState | null;
updateState: (value: boolean) => void;
setIsSharer: (value: boolean) => void;
updateRole: (value: ParticipantRole) => void;
}) {
if (!callTokens || !callTokens.videoToken) return null;

const tracks = useTracks([Track.Source.ScreenShare]);
const localParticipant = useLocalParticipant();
useEffect(() => {
const localParticipantId = localParticipant?.localParticipant.identity.split(":").slice(0, -1).join(":") || "";
let trackFound = false;
let screenshare_track_found = false;
let screenshareTrackFound = false;
for (const track of tracks) {
const trackParticipantId = track.participant.identity.split(":").slice(0, -1).join(":");

if (track.source === "screen_share" && trackParticipantId === localParticipantId) {
screenshare_track_found = true;
setIsSharer(true);
screenshareTrackFound = true;
break;
}

if (track.source === "screen_share" && trackParticipantId !== localParticipantId) {
trackFound = true;
if (!callTokens?.isController) {
updateState(true);
if (callTokens?.videoToken) {
tauriUtils.createScreenShareWindow(callTokens.videoToken);
}
break;
}
break;
}
}

if (!trackFound && callTokens?.isController) {
updateState(false);
tauriUtils.closeScreenShareWindow();
tauriUtils.setDockIconVisible(false);
}

// When the stream is stopped outside of the app,
// we need to update the sharer state
if (callTokens?.isSharer && !screenshare_track_found) {
setIsSharer(false);
if (trackFound) {
updateRole(ParticipantRole.CONTROLLER);
tauriUtils.createScreenShareWindow(callTokens.videoToken);
} else if (screenshareTrackFound) {
if (!trackFound && callTokens?.role === ParticipantRole.CONTROLLER) {
tauriUtils.closeScreenShareWindow();
tauriUtils.setDockIconVisible(false);
}
updateRole(ParticipantRole.SHARER);
} else {
if (!trackFound && callTokens?.role === ParticipantRole.CONTROLLER) {
tauriUtils.closeScreenShareWindow();
tauriUtils.setDockIconVisible(false);
}
updateRole(ParticipantRole.NONE);
}
}, [tracks]);
return <div />;
Expand Down
5 changes: 2 additions & 3 deletions tauri/src/components/ui/participant-row-wo-livekit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
import toast from "react-hot-toast";
import { sleep } from "@/lib/utils";
import { TCallRequestMessage, TWebSocketMessage } from "@/payloads";
import useStore from "@/store/store";
import useStore, { ParticipantRole } from "@/store/store";
import { sounds } from "@/constants/sounds";
import { usePostHog } from "posthog-js/react";
import { HoppAvatar } from "@/components/ui/hopp-avatar";
Expand Down Expand Up @@ -97,8 +97,7 @@ export const ParticipantRow = (props: { user: components["schemas"]["BaseUser"]
...data.payload,
timeStarted: new Date(),
hasAudioEnabled: true,
isSharer: false,
isController: false,
role: ParticipantRole.NONE,
isRemoteControlEnabled: true,
});
break;
Expand Down
9 changes: 7 additions & 2 deletions tauri/src/store/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@ const windowName = getCurrentWindow().label;
export const SidebarTabs = ["user-list", "invite", "debug", "login", "report-issue", "rooms"] as const;
export type Tab = (typeof SidebarTabs)[number];

export enum ParticipantRole {
SHARER = "sharer",
CONTROLLER = "controller",
NONE = "none",
};

export type CallState = {
timeStarted: Date;
hasAudioEnabled: boolean;
// Managing buttons for starting/joining/terminating screenshare streams
isSharer: boolean;
isController: boolean;
role: ParticipantRole;
isRemoteControlEnabled: boolean;
isRoomCall?: boolean;
} & TCallTokensMessage["payload"];
Expand Down
Loading
Loading