basic cursor moving
This commit is contained in:
parent
e4d51f09a1
commit
966fdbbd50
29 changed files with 7052 additions and 52 deletions
2
crates/cursor-move-webapp/src/components/mod.rs
Normal file
2
crates/cursor-move-webapp/src/components/mod.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
mod mouse_area;
|
||||
pub use mouse_area::MouseArea;
|
||||
79
crates/cursor-move-webapp/src/components/mouse_area.rs
Normal file
79
crates/cursor-move-webapp/src/components/mouse_area.rs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
use dioxus::{
|
||||
fullstack::{CborEncoding, WebSocketOptions, Websocket, extract::State, use_websocket},
|
||||
html::geometry::{ElementSpace, euclid::Point2D},
|
||||
logger::tracing,
|
||||
prelude::*,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[component]
|
||||
pub fn MouseArea() -> Element {
|
||||
#[css_module("/assets/styling/mouse_area.module.css")]
|
||||
struct Styles;
|
||||
|
||||
let mut last_cursor_position = use_signal::<Option<Point2D<f64, ElementSpace>>>(|| None);
|
||||
|
||||
let mut socket = use_websocket(move || mouse_move(WebSocketOptions::new()));
|
||||
|
||||
use_future(move || async move {
|
||||
loop {
|
||||
// Wait for the socket to connect
|
||||
_ = socket.connect().await;
|
||||
|
||||
// Loop poll with recv. Throws an error when the connection closes, making it possible
|
||||
// to run code before the socket re-connects when the name input changes
|
||||
while let Ok(message) = socket.recv().await {
|
||||
tracing::info!("Received message: {:?}", message);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
class: Styles::mouse_area,
|
||||
|
||||
onpointermove: move |evt| {
|
||||
evt.prevent_default();
|
||||
let point = evt.element_coordinates();
|
||||
let last_position = last_cursor_position.write().replace(point);
|
||||
|
||||
if let Some(last_position) = last_position {
|
||||
let delta = point - last_position;
|
||||
|
||||
spawn(async move {
|
||||
_ = socket.send(ClientEvent::MouseMove { dx: delta.x, dy: delta.y }).await;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onpointerdown: move |evt| {
|
||||
let point = evt.element_coordinates();
|
||||
*last_cursor_position.write() = Some(point);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
enum ClientEvent {
|
||||
MouseMove { dx: f64, dy: f64 },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
enum ServerEvent {
|
||||
Ping,
|
||||
}
|
||||
|
||||
#[expect(clippy::unused_async)]
|
||||
#[get("/api/mouse_move_ws", mouse_service: State<crate::server::mouse_service::MouseService>)]
|
||||
async fn mouse_move(
|
||||
options: WebSocketOptions
|
||||
) -> Result<Websocket<ClientEvent, ServerEvent, CborEncoding>> {
|
||||
Ok(options.on_upgrade(move |mut socket| async move {
|
||||
_ = socket.send(ServerEvent::Ping).await;
|
||||
|
||||
while let Ok(ClientEvent::MouseMove { dx, dy }) = socket.recv().await {
|
||||
mouse_service.move_mouse(dx, dy).await;
|
||||
}
|
||||
}))
|
||||
}
|
||||
51
crates/cursor-move-webapp/src/main.rs
Normal file
51
crates/cursor-move-webapp/src/main.rs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#![expect(clippy::volatile_composites)]
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use views::{Home, Navbar};
|
||||
|
||||
mod components;
|
||||
mod views;
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
mod server;
|
||||
|
||||
#[derive(Debug, Clone, Routable, PartialEq)]
|
||||
#[rustfmt::skip]
|
||||
enum Route {
|
||||
#[layout(Navbar)]
|
||||
#[route("/")]
|
||||
Home {},
|
||||
// #[route("/blog/:id")]
|
||||
// Blog { id: i32 },
|
||||
}
|
||||
|
||||
const FAVICON: Asset = asset!("/assets/favicon.ico");
|
||||
const MAIN_CSS: Asset = asset!("/assets/styling/main.css");
|
||||
|
||||
fn main() {
|
||||
#[cfg(not(feature = "server"))]
|
||||
dioxus::launch(App);
|
||||
|
||||
// When using `Lazy` items, or axum `Extension`s, we need to initialize them in `dioxus::serve`
|
||||
// before launching our app.
|
||||
#[cfg(feature = "server")]
|
||||
dioxus::serve(|| async move {
|
||||
use crate::server::mouse_service::MouseService;
|
||||
use dioxus::server::axum::Extension;
|
||||
|
||||
let router = dioxus::server::router(App);
|
||||
let router = router.layer(Extension(MouseService::start()));
|
||||
|
||||
Ok(router)
|
||||
});
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn App() -> Element {
|
||||
rsx! {
|
||||
document::Link { rel: "icon", href: FAVICON }
|
||||
document::Link { rel: "stylesheet", href: MAIN_CSS }
|
||||
|
||||
Router::<Route> {}
|
||||
}
|
||||
}
|
||||
1
crates/cursor-move-webapp/src/server/mod.rs
Normal file
1
crates/cursor-move-webapp/src/server/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub mod mouse_service;
|
||||
143
crates/cursor-move-webapp/src/server/mouse_service.rs
Normal file
143
crates/cursor-move-webapp/src/server/mouse_service.rs
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
use std::{
|
||||
sync::Arc,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use dioxus::{
|
||||
fullstack::{FullstackContext, extract::FromRef},
|
||||
logger::tracing,
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
use wayland_client::{Connection, Dispatch, EventQueue, Proxy, QueueHandle, protocol::wl_registry};
|
||||
use wayland_protocols_wlr::virtual_pointer::v1::client::{
|
||||
zwlr_virtual_pointer_manager_v1::ZwlrVirtualPointerManagerV1,
|
||||
zwlr_virtual_pointer_v1::ZwlrVirtualPointerV1,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MouseService {
|
||||
input_proxy_service_state: Arc<Mutex<InputProxy>>,
|
||||
}
|
||||
|
||||
impl MouseService {
|
||||
pub fn start() -> Self {
|
||||
Self {
|
||||
input_proxy_service_state: Arc::new(Mutex::new(InputProxy::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn move_mouse(
|
||||
&self,
|
||||
dx: f64,
|
||||
dy: f64,
|
||||
) {
|
||||
let guard = self.input_proxy_service_state.lock().await;
|
||||
if let Some(pointer) = &guard.state.virtual_pointer {
|
||||
let time = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis();
|
||||
|
||||
pointer.motion(time as u32, dx, dy);
|
||||
pointer.frame();
|
||||
guard.event_queue.flush().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRef<FullstackContext> for MouseService {
|
||||
fn from_ref(state: &FullstackContext) -> Self {
|
||||
state.extension::<Self>().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InputProxy {
|
||||
state: InputProxyServiceState,
|
||||
event_queue: EventQueue<InputProxyServiceState>,
|
||||
}
|
||||
|
||||
impl InputProxy {
|
||||
pub fn new() -> Self {
|
||||
let connection = Connection::connect_to_env().unwrap();
|
||||
let display = connection.display();
|
||||
|
||||
let mut event_queue = connection.new_event_queue();
|
||||
let queue_handle = event_queue.handle();
|
||||
let _ = display.get_registry(&queue_handle, ());
|
||||
|
||||
let mut input_proxy_service_state = InputProxyServiceState::default();
|
||||
event_queue
|
||||
.roundtrip(&mut input_proxy_service_state)
|
||||
.unwrap();
|
||||
|
||||
Self {
|
||||
state: input_proxy_service_state,
|
||||
event_queue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct InputProxyServiceState {
|
||||
virtual_pointer: Option<ZwlrVirtualPointerV1>,
|
||||
}
|
||||
|
||||
impl Dispatch<ZwlrVirtualPointerV1, ()> for InputProxyServiceState {
|
||||
fn event(
|
||||
_state: &mut Self,
|
||||
_proxy: &ZwlrVirtualPointerV1,
|
||||
_event: <ZwlrVirtualPointerV1 as Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<Self>,
|
||||
) {
|
||||
tracing::info!("VPointerData");
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwlrVirtualPointerManagerV1, ()> for InputProxyServiceState {
|
||||
fn event(
|
||||
_state: &mut Self,
|
||||
_proxy: &ZwlrVirtualPointerManagerV1,
|
||||
_event: <ZwlrVirtualPointerManagerV1 as Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<Self>,
|
||||
) {
|
||||
tracing::info!("ZwlrEvent");
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<wl_registry::WlRegistry, ()> for InputProxyServiceState {
|
||||
fn event(
|
||||
app_data: &mut Self,
|
||||
registry: &wl_registry::WlRegistry,
|
||||
event: wl_registry::Event,
|
||||
_udata: &(),
|
||||
_conn: &Connection,
|
||||
queue_handle: &QueueHandle<Self>,
|
||||
) {
|
||||
println!("WlRegistry Event");
|
||||
if let wl_registry::Event::Global {
|
||||
name,
|
||||
interface,
|
||||
version,
|
||||
} = event
|
||||
&& interface == "zwlr_virtual_pointer_manager_v1"
|
||||
{
|
||||
app_data.virtual_pointer.get_or_insert_with(|| {
|
||||
let manager = registry.bind::<ZwlrVirtualPointerManagerV1, _, _>(
|
||||
name,
|
||||
version,
|
||||
queue_handle,
|
||||
(),
|
||||
);
|
||||
|
||||
let pointer = manager.create_virtual_pointer(None, queue_handle, ());
|
||||
|
||||
println!("Virtual pointer manager created");
|
||||
pointer
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
16
crates/cursor-move-webapp/src/views/home.rs
Normal file
16
crates/cursor-move-webapp/src/views/home.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
use crate::components::MouseArea;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
/// The Home page component that will be rendered when the current route is `[Route::Home]`
|
||||
#[component]
|
||||
pub fn Home() -> Element {
|
||||
#[css_module("/assets/styling/home.module.css")]
|
||||
struct Styles;
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
class: Styles::container,
|
||||
MouseArea { }
|
||||
}
|
||||
}
|
||||
}
|
||||
15
crates/cursor-move-webapp/src/views/mod.rs
Normal file
15
crates/cursor-move-webapp/src/views/mod.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
//! The views module contains the components for all Layouts and Routes for our app. Each layout and route in our [`Route`]
|
||||
//! enum will render one of these components.
|
||||
//!
|
||||
//!
|
||||
//! The [`Home`] and [`Blog`] components will be rendered when the current route is [`Route::Home`] or [`Route::Blog`] respectively.
|
||||
//!
|
||||
//!
|
||||
//! The [`Navbar`] component will be rendered on all pages of our app since every page is under the layout. The layout defines
|
||||
//! a common wrapper around all child routes.
|
||||
|
||||
mod home;
|
||||
pub use home::Home;
|
||||
|
||||
mod navbar;
|
||||
pub use navbar::Navbar;
|
||||
22
crates/cursor-move-webapp/src/views/navbar.rs
Normal file
22
crates/cursor-move-webapp/src/views/navbar.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
use crate::Route;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[component]
|
||||
pub fn Navbar() -> Element {
|
||||
#[css_module("/assets/styling/navbar.module.css")]
|
||||
struct Styles;
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
class: Styles::navbar,
|
||||
Link {
|
||||
to: Route::Home {},
|
||||
"Home"
|
||||
}
|
||||
}
|
||||
|
||||
// The `Outlet` component is used to render the next component inside the layout. In this case, it will render either
|
||||
// the [`Home`] or [`Blog`] component depending on the current route.
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue