Skip to content
Closed
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions apps/desktop/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Route as AppIndexImport } from './routes/app.index'
import { Route as AppSettingsImport } from './routes/app.settings'
import { Route as AppPlansImport } from './routes/app.plans'
import { Route as AppNewImport } from './routes/app.new'
import { Route as AppControlImport } from './routes/app.control'
import { Route as AppCalendarImport } from './routes/app.calendar'
import { Route as AppOrganizationIdImport } from './routes/app.organization.$id'
import { Route as AppNoteIdImport } from './routes/app.note.$id'
Expand Down Expand Up @@ -61,6 +62,12 @@ const AppNewRoute = AppNewImport.update({
getParentRoute: () => AppRoute,
} as any)

const AppControlRoute = AppControlImport.update({
id: '/control',
path: '/control',
getParentRoute: () => AppRoute,
} as any)

const AppCalendarRoute = AppCalendarImport.update({
id: '/calendar',
path: '/calendar',
Expand Down Expand Up @@ -116,6 +123,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AppCalendarImport
parentRoute: typeof AppImport
}
'/app/control': {
id: '/app/control'
path: '/control'
fullPath: '/app/control'
preLoaderRoute: typeof AppControlImport
parentRoute: typeof AppImport
}
'/app/new': {
id: '/app/new'
path: '/new'
Expand Down Expand Up @@ -179,6 +193,7 @@ declare module '@tanstack/react-router' {

interface AppRouteChildren {
AppCalendarRoute: typeof AppCalendarRoute
AppControlRoute: typeof AppControlRoute
AppNewRoute: typeof AppNewRoute
AppPlansRoute: typeof AppPlansRoute
AppSettingsRoute: typeof AppSettingsRoute
Expand All @@ -191,6 +206,7 @@ interface AppRouteChildren {

const AppRouteChildren: AppRouteChildren = {
AppCalendarRoute: AppCalendarRoute,
AppControlRoute: AppControlRoute,
AppNewRoute: AppNewRoute,
AppPlansRoute: AppPlansRoute,
AppSettingsRoute: AppSettingsRoute,
Expand All @@ -207,6 +223,7 @@ export interface FileRoutesByFullPath {
'/app': typeof AppRouteWithChildren
'/video': typeof VideoRoute
'/app/calendar': typeof AppCalendarRoute
'/app/control': typeof AppControlRoute
'/app/new': typeof AppNewRoute
'/app/plans': typeof AppPlansRoute
'/app/settings': typeof AppSettingsRoute
Expand All @@ -220,6 +237,7 @@ export interface FileRoutesByFullPath {
export interface FileRoutesByTo {
'/video': typeof VideoRoute
'/app/calendar': typeof AppCalendarRoute
'/app/control': typeof AppControlRoute
'/app/new': typeof AppNewRoute
'/app/plans': typeof AppPlansRoute
'/app/settings': typeof AppSettingsRoute
Expand All @@ -235,6 +253,7 @@ export interface FileRoutesById {
'/app': typeof AppRouteWithChildren
'/video': typeof VideoRoute
'/app/calendar': typeof AppCalendarRoute
'/app/control': typeof AppControlRoute
'/app/new': typeof AppNewRoute
'/app/plans': typeof AppPlansRoute
'/app/settings': typeof AppSettingsRoute
Expand All @@ -251,6 +270,7 @@ export interface FileRouteTypes {
| '/app'
| '/video'
| '/app/calendar'
| '/app/control'
| '/app/new'
| '/app/plans'
| '/app/settings'
Expand All @@ -263,6 +283,7 @@ export interface FileRouteTypes {
to:
| '/video'
| '/app/calendar'
| '/app/control'
| '/app/new'
| '/app/plans'
| '/app/settings'
Expand All @@ -276,6 +297,7 @@ export interface FileRouteTypes {
| '/app'
| '/video'
| '/app/calendar'
| '/app/control'
| '/app/new'
| '/app/plans'
| '/app/settings'
Expand Down Expand Up @@ -315,6 +337,7 @@ export const routeTree = rootRoute
"filePath": "app.tsx",
"children": [
"/app/calendar",
"/app/control",
"/app/new",
"/app/plans",
"/app/settings",
Expand All @@ -332,6 +355,10 @@ export const routeTree = rootRoute
"filePath": "app.calendar.tsx",
"parent": "/app"
},
"/app/control": {
"filePath": "app.control.tsx",
"parent": "/app"
},
"/app/new": {
"filePath": "app.new.tsx",
"parent": "/app"
Expand Down
155 changes: 155 additions & 0 deletions apps/desktop/src/routes/app.control.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { createFileRoute } from "@tanstack/react-router";
import { getCurrentWindow } from "@tauri-apps/api/window";
import { Camera, Circle, Grip, Settings, Square } from "lucide-react";
import { useEffect, useMemo, useRef, useState } from "react";

import { commands as windowsCommands } from "@hypr/plugin-windows";

export const Route = createFileRoute("/app/control")({
component: Component,
});

function Component() {
const currentWindowLabel = useMemo(() => getCurrentWindow().label, []);

const [position, setPosition] = useState(() => {
const windowWidth = window.innerWidth;
const initialX = (windowWidth - 200) / 2;
return { x: initialX, y: window.innerHeight - 80 };
});

const [isDragging, setIsDragging] = useState(false);
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
const [isRecording, setIsRecording] = useState(false);
const controlRef = useRef<HTMLDivElement>(null);

const updateOverlayBounds = async () => {
if (controlRef.current) {
const rect = controlRef.current.getBoundingClientRect();
await windowsCommands.windowSetOverlayBounds(currentWindowLabel, {
x: rect.left,
y: rect.top,
width: rect.width,
height: rect.height,
});
}
};

useEffect(() => {
document.body.style.background = "transparent";
document.documentElement.setAttribute("data-transparent-window", "true");

const handleMouseMove = (e: MouseEvent) => {
if (isDragging) {
setPosition({
x: e.clientX - dragOffset.x,
y: e.clientY - dragOffset.y,
});
}
};

const handleMouseUp = () => {
setIsDragging(false);
};

window.addEventListener("mousemove", handleMouseMove);
window.addEventListener("mouseup", handleMouseUp);

updateOverlayBounds();

return () => {
window.removeEventListener("mousemove", handleMouseMove);
window.removeEventListener("mouseup", handleMouseUp);
windowsCommands.windowRemoveOverlayBounds(currentWindowLabel);
};
}, [isDragging, dragOffset]);

useEffect(() => {
updateOverlayBounds();
}, [position]);

const handleMouseDown = (e: React.MouseEvent) => {
setIsDragging(true);
setDragOffset({
x: e.clientX - position.x,
y: e.clientY - position.y,
});
};

const captureScreenshot = () => {
console.log("Capture screenshot");
setTimeout(updateOverlayBounds, 0);
};

const toggleRecording = () => {
setIsRecording(!isRecording);
console.log(isRecording ? "Stop recording" : "Start recording");
setTimeout(updateOverlayBounds, 0);
};

const openSettings = () => {
console.log("Open settings");
setTimeout(updateOverlayBounds, 0);
};

return (
<div
className="w-screen h-[100vh] bg-transparent relative overflow-y-hidden"
style={{ scrollbarColor: "auto transparent" }}
>
<div
className="absolute"
style={{
left: position.x,
top: position.y,
transition: isDragging ? "none" : "all 0.1s ease",
}}
ref={controlRef}
>
<div
className="bg-black/10 backdrop-blur-sm rounded-xl border border-white/30 shadow-lg cursor-move flex items-center justify-center transition-all duration-200 p-2"
onMouseDown={handleMouseDown}
>
<div className="flex gap-2 items-center">
<IconButton onClick={captureScreenshot} tooltip="Take Screenshot">
<Camera size={16} />
</IconButton>
<IconButton
onClick={toggleRecording}
tooltip={isRecording ? "Stop Recording" : "Start Recording"}
className={isRecording ? "bg-red-500/50 hover:bg-red-500/70" : ""}
>
{isRecording ? <Square size={16} /> : <Circle size={16} />}
</IconButton>
<IconButton onClick={openSettings} tooltip="Settings">
<Settings size={16} />
</IconButton>
<div
className="ml-1 text-white/50 cursor-move"
onMouseDown={handleMouseDown}
>
<Grip size={16} />
</div>
</div>
</div>
</div>
</div>
);
}

function IconButton({ onClick, children, className = "", tooltip = "" }: {
onClick?: ((e: React.MouseEvent<HTMLButtonElement>) => void) | (() => void);
children: React.ReactNode;
className?: string;
tooltip?: string;
}) {
return (
<button
onClick={onClick}
className={`p-1.5 bg-white/20 backdrop-blur-sm rounded-full text-xs shadow-md hover:bg-white/30 transition-colors duration-200 flex items-center justify-center ${className}`}
title={tooltip}
>
{children}
</button>
);
}
15 changes: 14 additions & 1 deletion plugins/listener/src/fsm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ impl Session {
}
}

#[state(superstate = "common")]
#[state(superstate = "common", entry_action = "enter_running_active")]
async fn running_active(&mut self, event: &StateEvent) -> Response<State> {
match event {
StateEvent::Start(incoming_session_id) => match &self.session_id {
Expand Down Expand Up @@ -470,13 +470,26 @@ impl Session {
}
}

#[action]
async fn enter_running_active(&mut self) {
{
use tauri_plugin_windows::{HyprWindow, WindowsPluginExt};
let _ = self.app.window_show(HyprWindow::Control);
}
}

#[action]
async fn enter_inactive(&mut self) {
{
use tauri_plugin_tray::TrayPluginExt;
let _ = self.app.set_start_disabled(false);
}

{
use tauri_plugin_windows::{HyprWindow, WindowsPluginExt};
let _ = self.app.window_hide(HyprWindow::Control);
}

self.teardown_resources().await;
}

Expand Down
1 change: 1 addition & 0 deletions plugins/windows/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ tauri-plugin-analytics = { workspace = true }
tauri-plugin-auth = { workspace = true }

thiserror = { workspace = true }
tokio = { workspace = true, features = ["time"] }
tracing = { workspace = true }
3 changes: 3 additions & 0 deletions plugins/windows/build.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const COMMANDS: &[&str] = &[
"window_show",
"window_close",
"window_hide",
"window_destroy",
"window_position",
Expand All @@ -9,6 +10,8 @@ const COMMANDS: &[&str] = &[
"window_emit_navigate",
"window_is_visible",
"window_resize_default",
"window_set_overlay_bounds",
"window_remove_overlay_bounds",
];

fn main() {
Expand Down
12 changes: 11 additions & 1 deletion plugins/windows/js/bindings.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ export const commands = {
async windowShow(window: HyprWindow) : Promise<null> {
return await TAURI_INVOKE("plugin:windows|window_show", { window });
},
async windowClose(window: HyprWindow) : Promise<null> {
return await TAURI_INVOKE("plugin:windows|window_close", { window });
},
async windowHide(window: HyprWindow) : Promise<null> {
return await TAURI_INVOKE("plugin:windows|window_hide", { window });
},
Expand All @@ -36,6 +39,12 @@ async windowEmitNavigate(window: HyprWindow, path: string) : Promise<null> {
},
async windowIsVisible(window: HyprWindow) : Promise<boolean> {
return await TAURI_INVOKE("plugin:windows|window_is_visible", { window });
},
async windowSetOverlayBounds(name: string, bounds: OverlayBound) : Promise<null> {
return await TAURI_INVOKE("plugin:windows|window_set_overlay_bounds", { name, bounds });
},
async windowRemoveOverlayBounds(name: string) : Promise<null> {
return await TAURI_INVOKE("plugin:windows|window_remove_overlay_bounds", { name });
}
}

Expand All @@ -58,10 +67,11 @@ windowDestroyed: "plugin:windows:window-destroyed"

/** user-defined types **/

export type HyprWindow = { type: "main" } | { type: "note"; value: string } | { type: "human"; value: string } | { type: "organization"; value: string } | { type: "calendar" } | { type: "settings" } | { type: "video"; value: string } | { type: "plans" }
export type HyprWindow = { type: "main" } | { type: "note"; value: string } | { type: "human"; value: string } | { type: "organization"; value: string } | { type: "calendar" } | { type: "settings" } | { type: "video"; value: string } | { type: "plans" } | { type: "control" }
export type KnownPosition = "left-half" | "right-half" | "center"
export type MainWindowState = { left_sidebar_expanded: boolean | null; right_panel_expanded: boolean | null }
export type Navigate = { path: string }
export type OverlayBound = { x: number; y: number; width: number; height: number }
export type WindowDestroyed = { window: HyprWindow }

/** tauri-specta globals **/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!

"$schema" = "../../schemas/schema.json"

[[permission]]
identifier = "allow-window-close"
description = "Enables the window_close command without any pre-configured scope."
commands.allow = ["window_close"]

[[permission]]
identifier = "deny-window-close"
description = "Denies the window_close command without any pre-configured scope."
commands.deny = ["window_close"]
Loading
Loading