feat: websocket server

This commit is contained in:
newt 2024-11-17 09:49:22 +00:00
parent edc9db64de
commit bac19ec2b8
7 changed files with 1178 additions and 26 deletions

5
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,5 @@
{
"files.exclude": {
"target": true
}
}

1091
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -6,7 +6,10 @@ version = "0.1.0"
edition = "2021"
[dependencies]
futures-util = { version = "0.3.31", default-features = false }
thiserror = "2.0.3"
tokio = { version = "1.41.1", default-features = false, features = ["macros", "rt-multi-thread"] }
warp = { version = "0.3.7", default-features = false, features = ["websocket"] }
[dependencies.windows]
version = "0.58.0"
@ -14,7 +17,6 @@ features = [
"Win32_Media_Audio_Endpoints",
"Win32_System_Com",
"Win32_System_Threading",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_WindowsAndMessaging",

View file

@ -1,4 +1,2 @@
Keyboard does not emit raw event, it is handled by firmware.
Tested with:
- ASUS TUF F15
todo:
- custom port

View file

@ -2,4 +2,8 @@
pub enum Error {
#[error("Failed to initialize COM library: {0}")]
Windows(#[from] windows::core::Error),
#[error("Failed to share microphone state")]
Send(#[from] tokio::sync::mpsc::error::SendError<bool>),
}
pub const ORDERING: std::sync::atomic::Ordering = std::sync::atomic::Ordering::SeqCst;

View file

@ -1,11 +1,11 @@
use std::sync::atomic::{AtomicPtr, Ordering};
use std::time::Duration;
use std::{ptr, thread};
use windows::Win32::UI::Input::KeyboardAndMouse::{
MapVirtualKeyW, SendInput, INPUT, INPUT_0, INPUT_KEYBOARD, KEYBDINPUT, KEYBD_EVENT_FLAGS,
KEYEVENTF_KEYUP, KEYEVENTF_SCANCODE, MAPVK_VK_TO_VSC,
use muter::ORDERING;
use std::{
ptr,
sync::atomic::{AtomicBool, AtomicPtr},
thread,
time::Duration,
};
use windows::Win32::UI::WindowsAndMessaging::{FindWindowExW, GetClassNameW, SetForegroundWindow};
use warp::Filter;
use windows::Win32::{
Foundation::{BOOL, HWND, LPARAM, WPARAM},
Media::Audio::{
@ -14,12 +14,15 @@ use windows::Win32::{
},
System::Com::{CoCreateInstance, CoInitializeEx, CLSCTX_ALL, COINIT_MULTITHREADED},
UI::{
Input::KeyboardAndMouse::{VIRTUAL_KEY, VK_CONTROL, VK_M, VK_SHIFT},
Input::KeyboardAndMouse::{MapVirtualKeyW, MAPVK_VK_TO_VSC, VIRTUAL_KEY},
WindowsAndMessaging::{EnumWindows, GetWindowTextW, PostMessageW, WM_KEYDOWN, WM_KEYUP},
},
};
mod ws;
static mut DISCORD_WHND: AtomicPtr<HWND> = AtomicPtr::new(ptr::null_mut());
static mut IS_MUTED: AtomicBool = AtomicBool::new(false);
unsafe extern "system" fn search_windows(hwnd: HWND, _: LPARAM) -> BOOL {
let mut name = [0u16; 1024];
@ -28,7 +31,7 @@ unsafe extern "system" fn search_windows(hwnd: HWND, _: LPARAM) -> BOOL {
if name.to_lowercase().contains(&"- discord".to_string()) {
println!("Discord window found: {:?}", name);
DISCORD_WHND.store(Box::into_raw(Box::new(hwnd)), Ordering::SeqCst);
DISCORD_WHND.store(Box::into_raw(Box::new(hwnd)), ORDERING);
}
BOOL(1)
@ -63,7 +66,8 @@ unsafe fn press_keys(window: HWND, keys: &[VIRTUAL_KEY]) -> windows::core::Resul
Ok(())
}
fn main() -> Result<(), muter::Error> {
#[tokio::main]
async fn main() -> Result<(), muter::Error> {
unsafe {
// Initialize the COM library
CoInitializeEx(None, COINIT_MULTITHREADED).unwrap();
@ -82,7 +86,7 @@ fn main() -> Result<(), muter::Error> {
EnumWindows(Some(search_windows), LPARAM(0))?;
let discord = {
let hwnd = DISCORD_WHND.load(Ordering::SeqCst);
let hwnd = DISCORD_WHND.load(ORDERING);
if hwnd.is_null() {
println!("Discord window not found");
@ -92,20 +96,30 @@ fn main() -> Result<(), muter::Error> {
*hwnd
};
// Setup WSS serve
let server = warp::serve(
warp::path::end()
.and(warp::ws())
.map(move |ws: warp::ws::Ws| {
ws.on_upgrade(move |socket| ws::user_connected(socket))
}),
)
.run(([127, 0, 0, 1], 3034));
tokio::task::spawn(server);
// Check if the microphone is muted
let mut is_muted = endpoint_volume.GetMute()?.as_bool();
IS_MUTED.store(endpoint_volume.GetMute()?.as_bool(), ORDERING);
loop {
// check if the mute status has changed
let muted = IS_MUTED.load(ORDERING);
let is_new_muted = endpoint_volume.GetMute()?.as_bool();
if is_new_muted != is_muted {
is_muted = is_new_muted;
println!(
"Microphone is {}",
if is_muted { "muted" } else { "unmuted" }
);
if is_new_muted != muted {
IS_MUTED.store(is_new_muted, ORDERING);
println!("Microphone is {}", if muted { "muted" } else { "unmuted" });
// send keybind to discord (ctrl+shift+m)
press_keys(discord, &[VK_CONTROL, VK_SHIFT, VK_M])?;
// press_keys(discord, &[VK_CONTROL, VK_SHIFT, VK_M])?;
}
}
}

42
src/ws.rs Normal file
View file

@ -0,0 +1,42 @@
use futures_util::{SinkExt, StreamExt};
use muter::ORDERING;
use std::time::Duration;
use tokio::time::timeout;
use warp::{filters::ws::Message, ws::WebSocket};
use crate::IS_MUTED;
pub async fn user_connected(socket: WebSocket) {
println!("User connected");
let (mut tx, mut rx) = socket.split();
let mut prev_mute = unsafe { !IS_MUTED.load(ORDERING) };
loop {
match timeout(Duration::from_millis(100), rx.next()).await {
Ok(Some(Ok(msg))) => {
if msg.is_close() {
break;
}
}
Ok(Some(Err(e))) => {
eprintln!("Error receiving message: {}", e);
break;
}
Ok(None) => break,
Err(_) => {}
};
let mute = unsafe { IS_MUTED.load(ORDERING) };
if mute != prev_mute {
tx.send(Message::binary(vec![if mute { 1 } else { 0 }]))
.await
.unwrap();
prev_mute = mute;
}
}
println!("User disconnected");
}