wip
This commit is contained in:
parent
21404eea15
commit
4a68a385bc
8 changed files with 157 additions and 2 deletions
|
|
@ -28,7 +28,7 @@ tauri-build = { version = "2", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
once_cell = "1"
|
once_cell = "1"
|
||||||
tauri = { version = "2", features = ["tray-icon", 'image-png'] }
|
tauri = { version = "2", features = [ "macos-private-api", "tray-icon", 'image-png'] }
|
||||||
tauri-plugin-opener = "2"
|
tauri-plugin-opener = "2"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use crate::settings::get_settings;
|
||||||
use log::{debug, info};
|
use log::{debug, info};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use tauri::{App, Manager};
|
use tauri::{App, LogicalSize, Manager, WebviewWindowBuilder};
|
||||||
|
|
||||||
const WHISPER_SAMPLE_RATE: usize = 16000;
|
const WHISPER_SAMPLE_RATE: usize = 16000;
|
||||||
|
|
||||||
|
|
@ -195,6 +195,7 @@ impl AudioRecordingManager {
|
||||||
binding_id: binding_id.to_string(),
|
binding_id: binding_id.to_string(),
|
||||||
};
|
};
|
||||||
debug!("Recording started for binding {binding_id}");
|
debug!("Recording started for binding {binding_id}");
|
||||||
|
self.show_overlay();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -239,6 +240,9 @@ impl AudioRecordingManager {
|
||||||
|
|
||||||
*self.is_recording.lock().unwrap() = false;
|
*self.is_recording.lock().unwrap() = false;
|
||||||
|
|
||||||
|
// Hide the overlay
|
||||||
|
self.hide_overlay();
|
||||||
|
|
||||||
// In on-demand mode turn the mic off again
|
// In on-demand mode turn the mic off again
|
||||||
if matches!(*self.mode.lock().unwrap(), MicrophoneMode::OnDemand) {
|
if matches!(*self.mode.lock().unwrap(), MicrophoneMode::OnDemand) {
|
||||||
self.stop_microphone_stream();
|
self.stop_microphone_stream();
|
||||||
|
|
@ -258,4 +262,54 @@ impl AudioRecordingManager {
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- overlay window -------------------------------------------- */
|
||||||
|
|
||||||
|
fn show_overlay(&self) {
|
||||||
|
if let Some(overlay_window) = self.app_handle.get_webview_window("recording_overlay") {
|
||||||
|
let _ = overlay_window.show();
|
||||||
|
let _ = overlay_window.set_focus();
|
||||||
|
} else {
|
||||||
|
// Get screen dimensions for positioning
|
||||||
|
if let Ok(monitors) = self.app_handle.primary_monitor() {
|
||||||
|
if let Some(monitor) = monitors {
|
||||||
|
let size = monitor.size();
|
||||||
|
let scale = monitor.scale_factor();
|
||||||
|
let screen_width = (size.width as f64 / scale) as i32;
|
||||||
|
let screen_height = (size.height as f64 / scale) as i32;
|
||||||
|
|
||||||
|
// Position at bottom center, 30px from bottom
|
||||||
|
let x = (screen_width - 150) / 2;
|
||||||
|
let y = screen_height - 50 - 30;
|
||||||
|
|
||||||
|
match WebviewWindowBuilder::new(&self.app_handle, "recording_overlay")
|
||||||
|
.title("Recording")
|
||||||
|
.position(x as f64, y as f64)
|
||||||
|
.resizable(false)
|
||||||
|
.maximizable(false)
|
||||||
|
.minimizable(false)
|
||||||
|
.closable(false)
|
||||||
|
.decorations(false)
|
||||||
|
.always_on_top(true)
|
||||||
|
.skip_taskbar(true)
|
||||||
|
.transparent(true)
|
||||||
|
.build()
|
||||||
|
{
|
||||||
|
Ok(window) => {
|
||||||
|
debug!("Recording overlay window created successfully");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
debug!("Failed to create recording overlay window: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hide_overlay(&self) {
|
||||||
|
if let Some(overlay_window) = self.app_handle.get_webview_window("recording_overlay") {
|
||||||
|
let _ = overlay_window.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
"frontendDist": "../dist"
|
"frontendDist": "../dist"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
|
"macOSPrivateApi": true,
|
||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
"title": "Handy",
|
"title": "Handy",
|
||||||
|
|
|
||||||
38
src/overlay/RecordingOverlay.css
Normal file
38
src/overlay/RecordingOverlay.css
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
.recording-overlay {
|
||||||
|
width: 150px;
|
||||||
|
height: 50px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
border-radius: 8px;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
-webkit-backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bars-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: end;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 3px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar {
|
||||||
|
width: 4px;
|
||||||
|
background: linear-gradient(to top, #FAA2CA, #F28CBB);
|
||||||
|
border-radius: 2px;
|
||||||
|
animation: pulse infinite ease-in-out;
|
||||||
|
min-height: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% {
|
||||||
|
height: 4px;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
height: 30px;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/overlay/RecordingOverlay.tsx
Normal file
23
src/overlay/RecordingOverlay.tsx
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import React from 'react';
|
||||||
|
import './RecordingOverlay.css';
|
||||||
|
|
||||||
|
const RecordingOverlay: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<div className="recording-overlay">
|
||||||
|
<div className="bars-container">
|
||||||
|
{Array.from({ length: 8 }, (_, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className="bar"
|
||||||
|
style={{
|
||||||
|
animationDelay: `${i * 100}ms`,
|
||||||
|
animationDuration: `${800 + Math.random() * 400}ms`
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RecordingOverlay;
|
||||||
19
src/overlay/index.html
Normal file
19
src/overlay/index.html
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Recording Overlay</title>
|
||||||
|
<style>
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/overlay/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
9
src/overlay/main.tsx
Normal file
9
src/overlay/main.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom/client";
|
||||||
|
import RecordingOverlay from "./RecordingOverlay";
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<RecordingOverlay />
|
||||||
|
</React.StrictMode>,
|
||||||
|
);
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
import { resolve } from "path";
|
||||||
|
|
||||||
// @ts-expect-error process is a nodejs global
|
// @ts-expect-error process is a nodejs global
|
||||||
const host = process.env.TAURI_DEV_HOST;
|
const host = process.env.TAURI_DEV_HOST;
|
||||||
|
|
@ -9,6 +10,16 @@ const host = process.env.TAURI_DEV_HOST;
|
||||||
export default defineConfig(async () => ({
|
export default defineConfig(async () => ({
|
||||||
plugins: [react(), tailwindcss()],
|
plugins: [react(), tailwindcss()],
|
||||||
|
|
||||||
|
// Multiple entry points for main app and overlay
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
input: {
|
||||||
|
main: resolve(__dirname, "index.html"),
|
||||||
|
overlay: resolve(__dirname, "src/overlay/index.html"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||||
//
|
//
|
||||||
// 1. prevent vite from obscuring rust errors
|
// 1. prevent vite from obscuring rust errors
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue