rewrote client, crashes currently
This commit is contained in:
parent
2058014ac6
commit
53e9f69356
@ -1,16 +1,22 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "navka-client"
|
name = "navka_client"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "1.45", features = ["full", "fs"] }
|
tokio = { version = "1.45", features = ["full", "fs"] }
|
||||||
tonic = "0.11"
|
tonic = { version = "0.13", features = ["transport"] }
|
||||||
tokio-stream = { version = "0.1", features = ["sync"] }
|
tokio-stream = { version = "0.1", features = ["sync"] }
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
navka-common = { path = "../common" }
|
navka_common = { path = "../common" }
|
||||||
tracing = { version = "0.1", features = ["std"] }
|
tracing = { version = "0.1", features = ["std"] }
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
|
futures = "0.3"
|
||||||
|
evdev = "0.13"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
prost-build = "0.12"
|
prost-build = "0.13"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "navka_client"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|||||||
56
client/src/app.rs
Normal file
56
client/src/app.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
use anyhow::{Context, Result};
|
||||||
|
use navka_common::navka::{relay_client::RelayClient, HidReport};
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
// use tokio_stream::{wrappers::ReceiverStream, StreamExt};
|
||||||
|
// use tonic::{Status, Request};
|
||||||
|
use tokio_stream::wrappers::ReceiverStream;
|
||||||
|
use tonic::Request;
|
||||||
|
use crate::input::keyboard::KeyboardAggregator;
|
||||||
|
|
||||||
|
pub struct NavkaClientApp {
|
||||||
|
server_addr: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NavkaClientApp {
|
||||||
|
pub fn new() -> Result<Self> {
|
||||||
|
let addr = std::env::args()
|
||||||
|
.nth(1)
|
||||||
|
.or_else(|| std::env::var("NAVKA_SERVER_ADDR").ok())
|
||||||
|
.unwrap_or_else(|| "http://127.0.0.1:50051".to_owned());
|
||||||
|
|
||||||
|
Ok(Self { server_addr: addr })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(&mut self) -> Result<()> {
|
||||||
|
// 1) Connect to navka-server
|
||||||
|
let mut client = RelayClient::connect(self.server_addr.clone())
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("failed to connect to {}", self.server_addr))?;
|
||||||
|
|
||||||
|
// 2) Create a bidirectional streaming stub
|
||||||
|
let (tx, rx) = mpsc::channel::<HidReport>(32);
|
||||||
|
// let outbound = ReceiverStream::new(rx).map(|report| Ok(report) as Result<HidReport, Status>);
|
||||||
|
// let response = client.stream(Request::new(outbound)).await?;
|
||||||
|
let outbound = ReceiverStream::new(rx);
|
||||||
|
let response = client.stream(Request::new(outbound)).await?;
|
||||||
|
let mut inbound = response.into_inner();
|
||||||
|
|
||||||
|
// 3) Start reading from all keyboards in a background task
|
||||||
|
let mut aggregator = KeyboardAggregator::new(tx.clone());
|
||||||
|
aggregator.init_devices()?; // discover & grab
|
||||||
|
let input_task = tokio::spawn(async move {
|
||||||
|
if let Err(e) = aggregator.run().await {
|
||||||
|
tracing::error!("KeyboardAggregator failed: {e}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4) Inbound loop: we do something with reports from the server, e.g. logging:
|
||||||
|
while let Some(report) = inbound.message().await? {
|
||||||
|
tracing::info!(?report.data, "echo from server");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5) If inbound stream ends, stop the input task
|
||||||
|
input_task.abort();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
160
client/src/input/keyboard.rs
Normal file
160
client/src/input/keyboard.rs
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
use anyhow::{bail, Context, Result};
|
||||||
|
use evdev::{Device, InputEvent, KeyCode, EventType};
|
||||||
|
use tokio::sync::mpsc::Sender;
|
||||||
|
use tokio::time::{interval, Duration};
|
||||||
|
|
||||||
|
use navka_common::navka::HidReport;
|
||||||
|
use crate::input::keymap::{keycode_to_usage, is_modifier};
|
||||||
|
|
||||||
|
/// Up to 6 normal keys. Byte[0] = modifiers, Byte[1] = reserved, Byte[2..7] = pressed keys.
|
||||||
|
/// Magic chord example: LeftCtrl + LeftAlt + LeftShift + Esc
|
||||||
|
const MAGIC_CHORD: &[KeyCode] = &[
|
||||||
|
KeyCode::KEY_LEFTCTRL,
|
||||||
|
KeyCode::KEY_LEFTSHIFT,
|
||||||
|
KeyCode::KEY_LEFTALT,
|
||||||
|
KeyCode::KEY_ESC,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub struct KeyboardAggregator {
|
||||||
|
// MPSC channel to server
|
||||||
|
tx: Sender<HidReport>,
|
||||||
|
// The list of evdev devices we are reading
|
||||||
|
devices: Vec<Device>,
|
||||||
|
// Pressed keys for building HID reports
|
||||||
|
pressed_keys: HashSet<KeyCode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyboardAggregator {
|
||||||
|
pub fn new(tx: Sender<HidReport>) -> Self {
|
||||||
|
Self {
|
||||||
|
tx,
|
||||||
|
devices: Vec::new(),
|
||||||
|
pressed_keys: HashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Discover all /dev/input/event* devices, filter ones with EV_KEY, and grab them.
|
||||||
|
pub fn init_devices(&mut self) -> Result<()> {
|
||||||
|
let paths = std::fs::read_dir("/dev/input")
|
||||||
|
.context("Failed to read /dev/input")?;
|
||||||
|
|
||||||
|
for entry in paths {
|
||||||
|
let entry = entry?;
|
||||||
|
let path = entry.path();
|
||||||
|
|
||||||
|
// skip anything that isn't "event*"
|
||||||
|
if !path.file_name().unwrap_or_default().to_string_lossy().starts_with("event") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut dev = Device::open(&path).with_context(|| format!("opening {:?}", path))?;
|
||||||
|
|
||||||
|
let maybe_keys = dev.supported_keys();
|
||||||
|
if let Some(supported) = maybe_keys {
|
||||||
|
if supported.iter().next().is_none() {
|
||||||
|
// no real keys
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Not a keyboard at all
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to grab
|
||||||
|
dev.grab().with_context(|| format!("grabbing {:?}", path))?;
|
||||||
|
|
||||||
|
tracing::info!("Grabbed keyboard device: {:?}", path);
|
||||||
|
self.devices.push(dev);
|
||||||
|
}
|
||||||
|
if self.devices.is_empty() {
|
||||||
|
bail!("No keyboard devices found or none grabbed successfully.");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Main loop: read events from all devices in a round-robin style
|
||||||
|
/// building HID reports as needed. If MAGIC_CHORD is detected, exit.
|
||||||
|
pub async fn run(&mut self) -> Result<()> {
|
||||||
|
let mut tick = interval(Duration::from_millis(10));
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// We'll poll each device in turn
|
||||||
|
for i in 0..self.devices.len() {
|
||||||
|
// Non-blocking fetch
|
||||||
|
let evs = match self.devices[i].fetch_events() {
|
||||||
|
Ok(iter) => iter.collect::<Vec<_>>(),
|
||||||
|
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => Vec::new(),
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Device read error: {e}");
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for ev in evs {
|
||||||
|
self.handle_event(ev)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.is_magic_chord() {
|
||||||
|
tracing::warn!("Magic chord pressed; stopping navka-client.");
|
||||||
|
// ungrab all before exit
|
||||||
|
for dev in &mut self.devices {
|
||||||
|
let _ = dev.ungrab();
|
||||||
|
}
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
tick.tick().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_event(&mut self, ev: InputEvent) -> Result<()> {
|
||||||
|
// We only care about KEY events
|
||||||
|
if ev.event_type() == EventType::KEY {
|
||||||
|
let code = KeyCode::new(ev.code());
|
||||||
|
let value = ev.value();
|
||||||
|
match value {
|
||||||
|
1 => { self.pressed_keys.insert(code); }
|
||||||
|
0 => { self.pressed_keys.remove(&code); }
|
||||||
|
2 => { /* repeats, if needed */ }
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build new HID report
|
||||||
|
let report_bytes = self.build_hid_report();
|
||||||
|
let _ = self.tx.try_send(HidReport {
|
||||||
|
data: report_bytes.to_vec(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_hid_report(&self) -> [u8; 8] {
|
||||||
|
// Byte 0: modifiers (bitmask)
|
||||||
|
// Byte 1: reserved
|
||||||
|
// Byte 2..7: up to 6 keys
|
||||||
|
// We'll do a naive approach: gather up to 6 non-modifier codes.
|
||||||
|
let mut report = [0u8; 8];
|
||||||
|
let mut normal_keys = Vec::new();
|
||||||
|
let mut modifier_mask = 0u8;
|
||||||
|
|
||||||
|
for &key in &self.pressed_keys {
|
||||||
|
if let Some(modbits) = is_modifier(key) {
|
||||||
|
// e.g. KEY_LEFTSHIFT => 0x02
|
||||||
|
modifier_mask |= modbits;
|
||||||
|
} else if let Some(usage) = keycode_to_usage(key) {
|
||||||
|
// Normal key
|
||||||
|
normal_keys.push(usage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
report[0] = modifier_mask;
|
||||||
|
// Byte[1] is reserved 0
|
||||||
|
for (i, code) in normal_keys.into_iter().take(6).enumerate() {
|
||||||
|
report[2 + i] = code;
|
||||||
|
}
|
||||||
|
report
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_magic_chord(&self) -> bool {
|
||||||
|
// All the keys in MAGIC_CHORD must be pressed
|
||||||
|
MAGIC_CHORD.iter().all(|k| self.pressed_keys.contains(k))
|
||||||
|
}
|
||||||
|
}
|
||||||
99
client/src/input/keymap.rs
Normal file
99
client/src/input/keymap.rs
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
// client/src/input/keymap.rs
|
||||||
|
|
||||||
|
use evdev::KeyCode;
|
||||||
|
|
||||||
|
/// Return Some(usage) if we have a known mapping from evdev::KeyCode -> HID usage code
|
||||||
|
pub fn keycode_to_usage(key: KeyCode) -> Option<u8> {
|
||||||
|
match key {
|
||||||
|
// Letters
|
||||||
|
KeyCode::KEY_A => Some(0x04),
|
||||||
|
KeyCode::KEY_B => Some(0x05),
|
||||||
|
KeyCode::KEY_C => Some(0x06),
|
||||||
|
KeyCode::KEY_D => Some(0x07),
|
||||||
|
KeyCode::KEY_E => Some(0x08),
|
||||||
|
KeyCode::KEY_F => Some(0x09),
|
||||||
|
KeyCode::KEY_G => Some(0x0A),
|
||||||
|
KeyCode::KEY_H => Some(0x0B),
|
||||||
|
KeyCode::KEY_I => Some(0x0C),
|
||||||
|
KeyCode::KEY_J => Some(0x0D),
|
||||||
|
KeyCode::KEY_K => Some(0x0E),
|
||||||
|
KeyCode::KEY_L => Some(0x0F),
|
||||||
|
KeyCode::KEY_M => Some(0x10),
|
||||||
|
KeyCode::KEY_N => Some(0x11),
|
||||||
|
KeyCode::KEY_O => Some(0x12),
|
||||||
|
KeyCode::KEY_P => Some(0x13),
|
||||||
|
KeyCode::KEY_Q => Some(0x14),
|
||||||
|
KeyCode::KEY_R => Some(0x15),
|
||||||
|
KeyCode::KEY_S => Some(0x16),
|
||||||
|
KeyCode::KEY_T => Some(0x17),
|
||||||
|
KeyCode::KEY_U => Some(0x18),
|
||||||
|
KeyCode::KEY_V => Some(0x19),
|
||||||
|
KeyCode::KEY_W => Some(0x1A),
|
||||||
|
KeyCode::KEY_X => Some(0x1B),
|
||||||
|
KeyCode::KEY_Y => Some(0x1C),
|
||||||
|
KeyCode::KEY_Z => Some(0x1D),
|
||||||
|
|
||||||
|
// Number row
|
||||||
|
KeyCode::KEY_1 => Some(0x1E),
|
||||||
|
KeyCode::KEY_2 => Some(0x1F),
|
||||||
|
KeyCode::KEY_3 => Some(0x20),
|
||||||
|
KeyCode::KEY_4 => Some(0x21),
|
||||||
|
KeyCode::KEY_5 => Some(0x22),
|
||||||
|
KeyCode::KEY_6 => Some(0x23),
|
||||||
|
KeyCode::KEY_7 => Some(0x24),
|
||||||
|
KeyCode::KEY_8 => Some(0x25),
|
||||||
|
KeyCode::KEY_9 => Some(0x26),
|
||||||
|
KeyCode::KEY_0 => Some(0x27),
|
||||||
|
|
||||||
|
// Common punctuation
|
||||||
|
KeyCode::KEY_ENTER => Some(0x28),
|
||||||
|
KeyCode::KEY_ESC => Some(0x29),
|
||||||
|
KeyCode::KEY_BACKSPACE => Some(0x2A),
|
||||||
|
KeyCode::KEY_TAB => Some(0x2B),
|
||||||
|
KeyCode::KEY_SPACE => Some(0x2C),
|
||||||
|
KeyCode::KEY_MINUS => Some(0x2D),
|
||||||
|
KeyCode::KEY_EQUAL => Some(0x2E),
|
||||||
|
KeyCode::KEY_LEFTBRACE => Some(0x2F),
|
||||||
|
KeyCode::KEY_RIGHTBRACE => Some(0x30),
|
||||||
|
KeyCode::KEY_BACKSLASH => Some(0x31),
|
||||||
|
KeyCode::KEY_SEMICOLON => Some(0x33),
|
||||||
|
KeyCode::KEY_APOSTROPHE => Some(0x34),
|
||||||
|
KeyCode::KEY_GRAVE => Some(0x35),
|
||||||
|
KeyCode::KEY_COMMA => Some(0x36),
|
||||||
|
KeyCode::KEY_DOT => Some(0x37),
|
||||||
|
KeyCode::KEY_SLASH => Some(0x38),
|
||||||
|
|
||||||
|
// Function keys
|
||||||
|
KeyCode::KEY_CAPSLOCK => Some(0x39),
|
||||||
|
KeyCode::KEY_F1 => Some(0x3A),
|
||||||
|
KeyCode::KEY_F2 => Some(0x3B),
|
||||||
|
KeyCode::KEY_F3 => Some(0x3C),
|
||||||
|
KeyCode::KEY_F4 => Some(0x3D),
|
||||||
|
KeyCode::KEY_F5 => Some(0x3E),
|
||||||
|
KeyCode::KEY_F6 => Some(0x3F),
|
||||||
|
KeyCode::KEY_F7 => Some(0x40),
|
||||||
|
KeyCode::KEY_F8 => Some(0x41),
|
||||||
|
KeyCode::KEY_F9 => Some(0x42),
|
||||||
|
KeyCode::KEY_F10 => Some(0x43),
|
||||||
|
KeyCode::KEY_F11 => Some(0x44),
|
||||||
|
KeyCode::KEY_F12 => Some(0x45),
|
||||||
|
|
||||||
|
// We'll handle modifiers (ctrl, shift, alt, meta) in `is_modifier()`
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If a key is a modifier, return the bit(s) to set in HID byte[0].
|
||||||
|
pub fn is_modifier(key: KeyCode) -> Option<u8> {
|
||||||
|
match key {
|
||||||
|
KeyCode::KEY_LEFTCTRL => Some(0x01),
|
||||||
|
KeyCode::KEY_LEFTSHIFT => Some(0x02),
|
||||||
|
KeyCode::KEY_LEFTALT => Some(0x04),
|
||||||
|
KeyCode::KEY_LEFTMETA => Some(0x08),
|
||||||
|
KeyCode::KEY_RIGHTCTRL => Some(0x10),
|
||||||
|
KeyCode::KEY_RIGHTSHIFT => Some(0x20),
|
||||||
|
KeyCode::KEY_RIGHTALT => Some(0x40),
|
||||||
|
KeyCode::KEY_RIGHTMETA => Some(0x80),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
2
client/src/input/mod.rs
Normal file
2
client/src/input/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod keyboard;
|
||||||
|
pub mod keymap;
|
||||||
6
client/src/lib.rs
Normal file
6
client/src/lib.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
|
pub mod app;
|
||||||
|
pub mod input;
|
||||||
|
|
||||||
|
pub use app::NavkaClientApp;
|
||||||
@ -1,45 +1,13 @@
|
|||||||
//! navka-client – forward keyboard/mouse HID reports to navka-server
|
//! main.rs — entry point for navka-client
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use navka_common::navka::{relay_client::RelayClient, HidReport};
|
use navka_client::NavkaClientApp;
|
||||||
use tokio::{sync::mpsc, time::sleep};
|
|
||||||
use tokio_stream::wrappers::ReceiverStream;
|
|
||||||
use tonic::{transport::Channel, Request};
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
// -- server address comes from CLI arg, env or falls back to localhost
|
let mut app = NavkaClientApp::new()?;
|
||||||
let addr = std::env::args()
|
app.run().await
|
||||||
.nth(1)
|
|
||||||
.or_else(|| std::env::var("NAVKA_SERVER_ADDR").ok())
|
|
||||||
.unwrap_or_else(|| "http://127.0.0.1:50051".to_owned());
|
|
||||||
|
|
||||||
let channel: Channel = Channel::from_shared(addr)?
|
|
||||||
.connect()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// mpsc -> ReceiverStream -> bidirectional gRPC
|
|
||||||
let (tx, rx) = mpsc::channel::<HidReport>(32);
|
|
||||||
let outbound = ReceiverStream::new(rx);
|
|
||||||
let mut inbound = RelayClient::new(channel)
|
|
||||||
.stream(Request::new(outbound))
|
|
||||||
.await?
|
|
||||||
.into_inner();
|
|
||||||
|
|
||||||
// demo: press & release ‘a’
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let press_a = HidReport { data: vec![0, 0, 0x04, 0, 0, 0, 0, 0] };
|
|
||||||
let release = HidReport { data: vec![0; 8] };
|
|
||||||
tx.send(press_a).await.ok();
|
|
||||||
sleep(std::time::Duration::from_millis(100)).await;
|
|
||||||
tx.send(release).await.ok();
|
|
||||||
});
|
|
||||||
|
|
||||||
while let Some(report) = inbound.message().await? {
|
|
||||||
tracing::info!(bytes=?report.data, "echo from server");
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
13
client/src/tests/integration_test.rs
Normal file
13
client/src/tests/integration_test.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::input::keymap::{keycode_to_usage, is_modifier};
|
||||||
|
use evdev::Key;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_keycode_mapping() {
|
||||||
|
assert_eq!(keycode_to_usage(Key::KEY_A), Some(0x04));
|
||||||
|
assert_eq!(keycode_to_usage(Key::KEY_Z), Some(0x1D));
|
||||||
|
assert_eq!(keycode_to_usage(Key::KEY_LEFTCTRL), Some(0)); // this is "handled" by is_modifier
|
||||||
|
assert!(is_modifier(Key::KEY_LEFTCTRL).is_some());
|
||||||
|
}
|
||||||
|
}
|
||||||
1
client/src/tests/mod.rs
Normal file
1
client/src/tests/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod integration_test;
|
||||||
@ -1,16 +1,16 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "navka-common"
|
name = "navka_common"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tonic = "0.11"
|
tonic = "0.13"
|
||||||
prost = "0.12"
|
prost = "0.13"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tonic-build = "0.11"
|
tonic-build = "0.13"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "navka_common"
|
name = "navka_common"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "navka-server"
|
name = "navka_server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "1.45", features = ["full", "fs"] }
|
tokio = { version = "1.45", features = ["full", "fs"] }
|
||||||
tokio-stream = "0.1"
|
tokio-stream = "0.1"
|
||||||
tonic = { version = "0.11", features = ["transport"] }
|
tonic = { version = "0.13", features = ["transport"] }
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
navka-common = { path = "../common" }
|
navka_common = { path = "../common" }
|
||||||
tracing = { version = "0.1", features = ["std"] }
|
tracing = { version = "0.1", features = ["std"] }
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user