feat: websocket server
This commit is contained in:
parent
edc9db64de
commit
bac19ec2b8
7 changed files with 1178 additions and 26 deletions
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"files.exclude": {
|
||||||
|
"target": true
|
||||||
|
}
|
||||||
|
}
|
1091
Cargo.lock
generated
1091
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -6,7 +6,10 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
futures-util = { version = "0.3.31", default-features = false }
|
||||||
thiserror = "2.0.3"
|
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]
|
[dependencies.windows]
|
||||||
version = "0.58.0"
|
version = "0.58.0"
|
||||||
|
@ -14,7 +17,6 @@ features = [
|
||||||
"Win32_Media_Audio_Endpoints",
|
"Win32_Media_Audio_Endpoints",
|
||||||
|
|
||||||
"Win32_System_Com",
|
"Win32_System_Com",
|
||||||
"Win32_System_Threading",
|
|
||||||
|
|
||||||
"Win32_UI_Input_KeyboardAndMouse",
|
"Win32_UI_Input_KeyboardAndMouse",
|
||||||
"Win32_UI_WindowsAndMessaging",
|
"Win32_UI_WindowsAndMessaging",
|
||||||
|
|
|
@ -1,4 +1,2 @@
|
||||||
Keyboard does not emit raw event, it is handled by firmware.
|
todo:
|
||||||
|
- custom port
|
||||||
Tested with:
|
|
||||||
- ASUS TUF F15
|
|
||||||
|
|
|
@ -2,4 +2,8 @@
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Failed to initialize COM library: {0}")]
|
#[error("Failed to initialize COM library: {0}")]
|
||||||
Windows(#[from] windows::core::Error),
|
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;
|
||||||
|
|
52
src/main.rs
52
src/main.rs
|
@ -1,11 +1,11 @@
|
||||||
use std::sync::atomic::{AtomicPtr, Ordering};
|
use muter::ORDERING;
|
||||||
use std::time::Duration;
|
use std::{
|
||||||
use std::{ptr, thread};
|
ptr,
|
||||||
use windows::Win32::UI::Input::KeyboardAndMouse::{
|
sync::atomic::{AtomicBool, AtomicPtr},
|
||||||
MapVirtualKeyW, SendInput, INPUT, INPUT_0, INPUT_KEYBOARD, KEYBDINPUT, KEYBD_EVENT_FLAGS,
|
thread,
|
||||||
KEYEVENTF_KEYUP, KEYEVENTF_SCANCODE, MAPVK_VK_TO_VSC,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use windows::Win32::UI::WindowsAndMessaging::{FindWindowExW, GetClassNameW, SetForegroundWindow};
|
use warp::Filter;
|
||||||
use windows::Win32::{
|
use windows::Win32::{
|
||||||
Foundation::{BOOL, HWND, LPARAM, WPARAM},
|
Foundation::{BOOL, HWND, LPARAM, WPARAM},
|
||||||
Media::Audio::{
|
Media::Audio::{
|
||||||
|
@ -14,12 +14,15 @@ use windows::Win32::{
|
||||||
},
|
},
|
||||||
System::Com::{CoCreateInstance, CoInitializeEx, CLSCTX_ALL, COINIT_MULTITHREADED},
|
System::Com::{CoCreateInstance, CoInitializeEx, CLSCTX_ALL, COINIT_MULTITHREADED},
|
||||||
UI::{
|
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},
|
WindowsAndMessaging::{EnumWindows, GetWindowTextW, PostMessageW, WM_KEYDOWN, WM_KEYUP},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod ws;
|
||||||
|
|
||||||
static mut DISCORD_WHND: AtomicPtr<HWND> = AtomicPtr::new(ptr::null_mut());
|
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 {
|
unsafe extern "system" fn search_windows(hwnd: HWND, _: LPARAM) -> BOOL {
|
||||||
let mut name = [0u16; 1024];
|
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()) {
|
if name.to_lowercase().contains(&"- discord".to_string()) {
|
||||||
println!("Discord window found: {:?}", name);
|
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)
|
BOOL(1)
|
||||||
|
@ -63,7 +66,8 @@ unsafe fn press_keys(window: HWND, keys: &[VIRTUAL_KEY]) -> windows::core::Resul
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), muter::Error> {
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), muter::Error> {
|
||||||
unsafe {
|
unsafe {
|
||||||
// Initialize the COM library
|
// Initialize the COM library
|
||||||
CoInitializeEx(None, COINIT_MULTITHREADED).unwrap();
|
CoInitializeEx(None, COINIT_MULTITHREADED).unwrap();
|
||||||
|
@ -82,7 +86,7 @@ fn main() -> Result<(), muter::Error> {
|
||||||
EnumWindows(Some(search_windows), LPARAM(0))?;
|
EnumWindows(Some(search_windows), LPARAM(0))?;
|
||||||
|
|
||||||
let discord = {
|
let discord = {
|
||||||
let hwnd = DISCORD_WHND.load(Ordering::SeqCst);
|
let hwnd = DISCORD_WHND.load(ORDERING);
|
||||||
|
|
||||||
if hwnd.is_null() {
|
if hwnd.is_null() {
|
||||||
println!("Discord window not found");
|
println!("Discord window not found");
|
||||||
|
@ -92,20 +96,30 @@ fn main() -> Result<(), muter::Error> {
|
||||||
*hwnd
|
*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
|
// 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 {
|
loop {
|
||||||
|
// check if the mute status has changed
|
||||||
|
let muted = IS_MUTED.load(ORDERING);
|
||||||
let is_new_muted = endpoint_volume.GetMute()?.as_bool();
|
let is_new_muted = endpoint_volume.GetMute()?.as_bool();
|
||||||
if is_new_muted != is_muted {
|
if is_new_muted != muted {
|
||||||
is_muted = is_new_muted;
|
IS_MUTED.store(is_new_muted, ORDERING);
|
||||||
println!(
|
println!("Microphone is {}", if muted { "muted" } else { "unmuted" });
|
||||||
"Microphone is {}",
|
|
||||||
if is_muted { "muted" } else { "unmuted" }
|
|
||||||
);
|
|
||||||
|
|
||||||
// send keybind to discord (ctrl+shift+m)
|
// 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
42
src/ws.rs
Normal 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");
|
||||||
|
}
|
Loading…
Reference in a new issue