Compare commits
5 commits
af21cd3686
...
96329923f6
Author | SHA1 | Date | |
---|---|---|---|
96329923f6 | |||
0024f49df9 | |||
51af8d2f14 | |||
e0e56026cb | |||
90054015a2 |
21 changed files with 264 additions and 174 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,4 +1,5 @@
|
||||||
/target
|
/client/target
|
||||||
|
/testclient/target
|
||||||
|
|
||||||
# agent
|
# agent
|
||||||
/agent/.cache
|
/agent/.cache
|
||||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -1,10 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "fbcserver"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
minifb = "0.27.0"
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
cc = "1.0.99"
|
|
|
@ -6,10 +6,13 @@ Currently NVIDIA-specific (since we use NvFBC); support for DXGI duplication (fo
|
||||||
|
|
||||||
The agent runs on Windows 7 x64 onwards. Lower (or x86) is currently not supported as a target.
|
The agent runs on Windows 7 x64 onwards. Lower (or x86) is currently not supported as a target.
|
||||||
|
|
||||||
The agent uses IVSHMEM to provide frames to the client in a low-latency fashion. Later on virtio-serial may be considered for low-bandwidth additional functionality, like input. For now, however, the agent is display only.
|
The agent uses IVSHMEM to provide frames to the client in a low-latency fashion.
|
||||||
|
|
||||||
|
Later on virtio-serial may be considered for low-bandwidth additional functionality, like input. For now, however, the agent is display only.
|
||||||
|
|
||||||
This repository contains:
|
This repository contains:
|
||||||
|
|
||||||
- The agent. Written in C++20.
|
- The agent. Written in C++20.
|
||||||
- Shared headers and utility code, used between the agent and the client. Written in C++20, mostly so it can be shared between the agent and client.
|
- Shared headers and utility code, used between the agent and the client. Written in C++20, mostly so it can be shared between the agent and client.
|
||||||
- A simple client, written mostly in Rust (with some C++ glue), which displays the framebuffer (in a quick-and-dirty fashion.). Mostly test-only code. Some of it isn't though!
|
- A Rust crate for Hazelnut clients to use. Wraps the Shared code.
|
||||||
|
- A test client written in Rust which displays the framebuffer (in a quick-and-dirty fashion.).
|
|
@ -6,13 +6,13 @@ namespace hazelnut {
|
||||||
|
|
||||||
enum class DisplayCaptureResult {
|
enum class DisplayCaptureResult {
|
||||||
Ok,
|
Ok,
|
||||||
|
OkUnchanged, // OK but nothing changed
|
||||||
OkButResized, // OK, but grab the buffer information again. It's different due to a resize
|
OkButResized, // OK, but grab the buffer information again. It's different due to a resize
|
||||||
Fail, // epic fail
|
Fail, // epic fail
|
||||||
};
|
};
|
||||||
|
|
||||||
// Framebuffer information
|
// Framebuffer information
|
||||||
struct FramebufferInformation {
|
struct FramebufferInformation {
|
||||||
u32* pFramebuffer;
|
|
||||||
u32 width;
|
u32 width;
|
||||||
u32 height;
|
u32 height;
|
||||||
};
|
};
|
||||||
|
@ -30,12 +30,8 @@ namespace hazelnut {
|
||||||
|
|
||||||
virtual bool Initialize() = 0;
|
virtual bool Initialize() = 0;
|
||||||
|
|
||||||
/// New CaptureFrame() API; now capture is copied directly to output buffer
|
/// Captures frame to provided output buffer directly.
|
||||||
/// instead of copy->copy.
|
virtual DisplayCaptureResult CaptureFrame(u32* pOut) = 0;
|
||||||
virtual DisplayCaptureResult CaptureFrameTemp(u32* pOut) = 0;
|
|
||||||
|
|
||||||
/// Performs the capture. THIS VERSION IS DEPRECATED AND WILL BE REMOVED
|
|
||||||
virtual DisplayCaptureResult CaptureFrame() = 0;
|
|
||||||
|
|
||||||
/// Get framebuffer information.
|
/// Get framebuffer information.
|
||||||
virtual FramebufferInformation GetFramebufferInformation() = 0;
|
virtual FramebufferInformation GetFramebufferInformation() = 0;
|
||||||
|
|
|
@ -68,8 +68,6 @@ namespace hazelnut {
|
||||||
u32 height;
|
u32 height;
|
||||||
|
|
||||||
u32* pRawFramebuffer;
|
u32* pRawFramebuffer;
|
||||||
unique_buffer<u32> convertedFramebuffer;
|
|
||||||
|
|
||||||
u8* pDiffMap;
|
u8* pDiffMap;
|
||||||
|
|
||||||
u32 diffmapWidth;
|
u32 diffmapWidth;
|
||||||
|
@ -169,7 +167,7 @@ namespace hazelnut {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PaintWithTileOptimization(u32* pBufferData) {
|
DisplayCaptureResult PaintWithTileOptimization(u32* pBufferData) {
|
||||||
bool copiedTiles = false;
|
bool copiedTiles = false;
|
||||||
|
|
||||||
for(u32 dy = 0; dy < diffmapHeight; ++dy) {
|
for(u32 dy = 0; dy < diffmapHeight; ++dy) {
|
||||||
|
@ -198,6 +196,11 @@ namespace hazelnut {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!copiedTiles)
|
||||||
|
return DisplayCaptureResult::OkUnchanged;
|
||||||
|
|
||||||
|
return DisplayCaptureResult::Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PaintFull(u32* pBufferData) {
|
void PaintFull(u32* pBufferData) {
|
||||||
|
@ -219,7 +222,7 @@ namespace hazelnut {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DisplayCaptureResult CaptureFrameTemp(u32* pOut) override {
|
DisplayCaptureResult CaptureFrame(u32* pOut) override {
|
||||||
auto nvStatus = nvfbc->NvFBCToSysGrabFrame(&this->fbcSysGrabParams);
|
auto nvStatus = nvfbc->NvFBCToSysGrabFrame(&this->fbcSysGrabParams);
|
||||||
|
|
||||||
switch(nvStatus) {
|
switch(nvStatus) {
|
||||||
|
@ -232,7 +235,7 @@ namespace hazelnut {
|
||||||
|
|
||||||
// Recurse. This looks naughty, but will allow us to directly retry
|
// Recurse. This looks naughty, but will allow us to directly retry
|
||||||
// the capture. (Plus if this causes issues whatever has happened is probably beyond saving)
|
// the capture. (Plus if this causes issues whatever has happened is probably beyond saving)
|
||||||
return CaptureFrame();
|
return CaptureFrame(pOut);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
default: return DisplayCaptureResult::Fail;
|
default: return DisplayCaptureResult::Fail;
|
||||||
|
@ -259,7 +262,7 @@ namespace hazelnut {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(useTilePainting) [[likely]] {
|
if(useTilePainting) [[likely]] {
|
||||||
PaintWithTileOptimization(&pOut[0]);
|
result = PaintWithTileOptimization(&pOut[0]);
|
||||||
} else {
|
} else {
|
||||||
PaintFull(&pOut[0]);
|
PaintFull(&pOut[0]);
|
||||||
}
|
}
|
||||||
|
@ -267,62 +270,7 @@ namespace hazelnut {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
DisplayCaptureResult CaptureFrame() override {
|
FramebufferInformation GetFramebufferInformation() override { return FramebufferInformation { width, height }; }
|
||||||
auto nvStatus = nvfbc->NvFBCToSysGrabFrame(&this->fbcSysGrabParams);
|
|
||||||
|
|
||||||
switch(nvStatus) {
|
|
||||||
case NVFBC_SUCCESS: break;
|
|
||||||
|
|
||||||
// Need to recreate the session. If it fails then we fail too.
|
|
||||||
case NVFBC_ERROR_INVALIDATED_SESSION: {
|
|
||||||
if(!NvfbcInitSession())
|
|
||||||
return DisplayCaptureResult::Fail;
|
|
||||||
|
|
||||||
// Recurse. This looks naughty, but will allow us to directly retry
|
|
||||||
// the capture. (Plus if this causes issues whatever has happened is probably beyond saving)
|
|
||||||
return CaptureFrame();
|
|
||||||
} break;
|
|
||||||
|
|
||||||
default: return DisplayCaptureResult::Fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(width != grabInfo.dwWidth || height != grabInfo.dwHeight) {
|
|
||||||
width = grabInfo.dwWidth;
|
|
||||||
height = grabInfo.dwHeight;
|
|
||||||
|
|
||||||
convertedFramebuffer.resize(width * height);
|
|
||||||
|
|
||||||
return DisplayCaptureResult::OkButResized;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Splat to the converted framebuffer. It doesn't have padding on it.
|
|
||||||
auto* pBufferData = (u8*)convertedFramebuffer.data();
|
|
||||||
auto* pSrcData = (u8*)&pRawFramebuffer[0];
|
|
||||||
|
|
||||||
for(u32 y = 0; y < grabInfo.dwHeight; ++y) {
|
|
||||||
// Convert to BGRA
|
|
||||||
// FIXME: Make this SIMD. I can't into this very well
|
|
||||||
#if 0
|
|
||||||
usize srcStart = (y * grabInfo.dwBufferWidth) * 4;
|
|
||||||
usize dstStart = (y * width) * 4;
|
|
||||||
for(u32 x = 0; x < grabInfo.dwWidth * 4; x += 4) {
|
|
||||||
pBufferData[(dstStart + x) + 0] = pSrcData[(srcStart + x) + 2]; // B
|
|
||||||
pBufferData[(dstStart + x) + 1] = pSrcData[(srcStart + x) + 1]; // G
|
|
||||||
pBufferData[(dstStart + x) + 2] = pSrcData[(srcStart + x) + 0]; // R
|
|
||||||
pBufferData[(dstStart + x) + 3] = 0xff; // A
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
memcpy(&pBufferData[(y * width) * 4], &pRawFramebuffer[(y * grabInfo.dwBufferWidth)], grabInfo.dwWidth * 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
// sleep
|
|
||||||
// nvfbc->NvFBCToSysGPUBasedCPUSleep(16000);
|
|
||||||
|
|
||||||
return DisplayCaptureResult::Ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
FramebufferInformation GetFramebufferInformation() override { return FramebufferInformation { convertedFramebuffer.data(), width, height }; }
|
|
||||||
|
|
||||||
DiffInformation GetDiffInformation() override {
|
DiffInformation GetDiffInformation() override {
|
||||||
// diffmapWidth = (u32)ceil((f32)width / 32);
|
// diffmapWidth = (u32)ceil((f32)width / 32);
|
||||||
|
|
|
@ -51,22 +51,28 @@ int main(int argc, char** argv) {
|
||||||
while(true) {
|
while(true) {
|
||||||
{
|
{
|
||||||
auto guard = pHeader->lock.lock();
|
auto guard = pHeader->lock.lock();
|
||||||
auto result = pCaptureInterface->CaptureFrameTemp(pFrameHeader->bits());
|
auto result = pCaptureInterface->CaptureFrame(pFrameHeader->bits());
|
||||||
|
|
||||||
if(result == hazelnut::DisplayCaptureResult::Ok) {
|
switch(result) {
|
||||||
// Do nothing.
|
case hazelnut::DisplayCaptureResult::Ok:
|
||||||
} else if(result == hazelnut::DisplayCaptureResult::OkButResized) {
|
case hazelnut::DisplayCaptureResult::OkButResized:
|
||||||
// We resized. Notify of that
|
if(result == hazelnut::DisplayCaptureResult::OkButResized) {
|
||||||
framebuffer = pCaptureInterface->GetFramebufferInformation();
|
framebuffer = pCaptureInterface->GetFramebufferInformation();
|
||||||
diff = pCaptureInterface->GetDiffInformation();
|
diff = pCaptureInterface->GetDiffInformation();
|
||||||
} else {
|
}
|
||||||
printf("Failed to capture\n");
|
|
||||||
break;
|
pFrameHeader->serial.fetch_add(1);
|
||||||
|
pFrameHeader->width.store(framebuffer.width);
|
||||||
|
pFrameHeader->height.store(framebuffer.height);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case hazelnut::DisplayCaptureResult::OkUnchanged: continue; break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
printf("Failed to capture");
|
||||||
|
return 1;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
pFrameHeader->serial.fetch_add(1);
|
|
||||||
pFrameHeader->width.store(framebuffer.width);
|
|
||||||
pFrameHeader->height.store(framebuffer.height);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
14
build.rs
14
build.rs
|
@ -1,14 +0,0 @@
|
||||||
use cc;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut build = cc::Build::new();
|
|
||||||
|
|
||||||
build
|
|
||||||
.emit_rerun_if_env_changed(true)
|
|
||||||
.cpp(true)
|
|
||||||
.std("c++20")
|
|
||||||
.include("shared/src")
|
|
||||||
.file("shared/src/ivshmem.cpp")
|
|
||||||
.file("src/rust_wrapper.cpp")
|
|
||||||
.compile("rust_ivshmem_bare");
|
|
||||||
}
|
|
69
client/Cargo.lock
generated
Normal file
69
client/Cargo.lock
generated
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.93"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc"
|
||||||
|
dependencies = [
|
||||||
|
"shlex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg_aliases"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hazelnut_client"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"cc",
|
||||||
|
"nix",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.167"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.29.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cfg-if",
|
||||||
|
"cfg_aliases",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
12
client/Cargo.toml
Normal file
12
client/Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "hazelnut_client"
|
||||||
|
description = "Low-level client for the Hazelnut IVSHMEM display protocol"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.93"
|
||||||
|
nix = { version = "0.29.0", features = [ "fs" ] }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
cc = "1.0.99"
|
18
client/build.rs
Normal file
18
client/build.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
use cc;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut build = cc::Build::new();
|
||||||
|
|
||||||
|
// HACK: cc sucks
|
||||||
|
println!("cargo:rerun-if-changed=src/rust_wrapper.cpp");
|
||||||
|
println!("cargo:rerun-if-changed=../shared/src");
|
||||||
|
|
||||||
|
build
|
||||||
|
.emit_rerun_if_env_changed(true)
|
||||||
|
.cpp(true)
|
||||||
|
.std("c++20")
|
||||||
|
.include("../shared/src")
|
||||||
|
.file("../shared/src/ivshmem.cpp")
|
||||||
|
.file("src/rust_wrapper.cpp")
|
||||||
|
.compile("hazelnut_client_cpp_native");
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
//! Hazelnut C++ client bindings.
|
//! Hazelnut C++ client bindings.
|
||||||
use std::ffi;
|
use nix::{fcntl::OFlag, sys::stat::Mode, NixPath};
|
||||||
|
|
||||||
pub(crate) mod sys {
|
pub(crate) mod sys {
|
||||||
use std::ffi;
|
use std::ffi;
|
||||||
|
@ -13,6 +13,9 @@ pub(crate) mod sys {
|
||||||
/// The frame has been changed.
|
/// The frame has been changed.
|
||||||
Changed,
|
Changed,
|
||||||
|
|
||||||
|
/// The Hazelnut lock was contended, so we couldn't lock it ourselves.
|
||||||
|
LockContended,
|
||||||
|
|
||||||
/// A failure occured during the tick.
|
/// A failure occured during the tick.
|
||||||
Fail,
|
Fail,
|
||||||
}
|
}
|
||||||
|
@ -21,10 +24,7 @@ pub(crate) mod sys {
|
||||||
pub(crate) fn rust_new_hazelnut_client() -> *mut ffi::c_void;
|
pub(crate) fn rust_new_hazelnut_client() -> *mut ffi::c_void;
|
||||||
pub(crate) fn rust_destroy_hazelnut_client(client: *mut ffi::c_void);
|
pub(crate) fn rust_destroy_hazelnut_client(client: *mut ffi::c_void);
|
||||||
|
|
||||||
pub(crate) fn rust_hazelnut_client_open(
|
pub(crate) fn rust_hazelnut_client_open(client: *mut ffi::c_void, fd: ffi::c_int) -> bool;
|
||||||
client: *mut ffi::c_void,
|
|
||||||
url: *const ffi::c_char,
|
|
||||||
) -> bool;
|
|
||||||
|
|
||||||
pub(crate) fn rust_hazelnut_client_tick(client: *mut ffi::c_void) -> ResultCode;
|
pub(crate) fn rust_hazelnut_client_tick(client: *mut ffi::c_void) -> ResultCode;
|
||||||
|
|
||||||
|
@ -62,14 +62,19 @@ impl HazelnutClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Opens the given IVSHMEM shmem file.
|
/// Opens the given IVSHMEM shmem file.
|
||||||
pub fn open(&mut self, path: &String) -> bool {
|
pub fn open<P: ?Sized + NixPath>(&mut self, path: &P) -> anyhow::Result<()> {
|
||||||
let cstr = ffi::CString::new(path.clone()).expect("dumbass");
|
// NOTE: `fd` is owned by the hazelnut client once it is provided to it, so we do not close it ourselves.
|
||||||
|
let fd = nix::fcntl::open(path, OFlag::O_RDWR, Mode::S_IRUSR | Mode::S_IWUSR)?;
|
||||||
|
|
||||||
// FIXME: this really should work by FD so we can just return io::Result<> or something, but bleh
|
// FIXME: this really should work by FD so we can just return io::Result<> or something, but bleh
|
||||||
// for now it's "fine", also it's a path in the shared sources currently.
|
// for now it's "fine", also it's a path in the shared sources currently.
|
||||||
unsafe {
|
unsafe {
|
||||||
return sys::rust_hazelnut_client_open(self.0, cstr.as_ptr());
|
if !sys::rust_hazelnut_client_open(self.0, fd) {
|
||||||
|
return Err(anyhow::anyhow!("Failed to open Hazelnut client"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ticks this client.
|
/// Ticks this client.
|
2
client/src/lib.rs
Normal file
2
client/src/lib.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod client;
|
||||||
|
pub use client::*;
|
|
@ -2,11 +2,11 @@
|
||||||
#include "ivshmem_protocol.hpp"
|
#include "ivshmem_protocol.hpp"
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
|
|
||||||
enum class ResultCode : u32 { Unchanged, Changed, Fail };
|
enum class ResultCode : u32 { Unchanged, Changed, LockContended, Fail };
|
||||||
|
|
||||||
struct HazelnutIvshmemClient {
|
struct HazelnutIvshmemClient {
|
||||||
bool Open(const char* path) {
|
bool Open(int fd) {
|
||||||
if(!ivshmemDevice.Open(path))
|
if(!ivshmemDevice.Open(fd))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
auto* ptr = (u8*)ivshmemDevice.GetPointer();
|
auto* ptr = (u8*)ivshmemDevice.GetPointer();
|
||||||
|
@ -14,34 +14,30 @@ struct HazelnutIvshmemClient {
|
||||||
pHeader = (hazelnut::IvshHeader*)&ptr[0];
|
pHeader = (hazelnut::IvshHeader*)&ptr[0];
|
||||||
pFrameHeader = (hazelnut::FrameHeader*)&ptr[0x1000];
|
pFrameHeader = (hazelnut::FrameHeader*)&ptr[0x1000];
|
||||||
|
|
||||||
serial = pHeader->serverSessionId.load();
|
sessionId = pHeader->serverSessionId.load();
|
||||||
lastFrameSerial = 0;
|
lastFrameSerial = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// THIS LEAVES THE LOCK HELD SO YOU CAN READ ON CHANGE!!!!
|
// NOTE: This function leaves the lock held on successful returns.
|
||||||
ResultCode TickOne() {
|
ResultCode TickOne() {
|
||||||
if(!pHeader)
|
if(!pHeader)
|
||||||
return ResultCode::Fail;
|
return ResultCode::Fail;
|
||||||
|
|
||||||
if(serial != pHeader->serverSessionId.load())
|
if(sessionId != pHeader->serverSessionId.load())
|
||||||
return ResultCode::Fail;
|
return ResultCode::Fail;
|
||||||
|
|
||||||
if(pHeader->lock.try_lock_manually()) {
|
if(pHeader->lock.try_lock_manually()) {
|
||||||
// failed to lock
|
// failed to lock
|
||||||
return ResultCode::Unchanged;
|
return ResultCode::LockContended;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto current = pFrameHeader->serial.load();
|
auto current = pFrameHeader->serial.load();
|
||||||
|
|
||||||
if(current == lastFrameSerial) {
|
if(current == lastFrameSerial) {
|
||||||
pHeader->lock.unlock();
|
|
||||||
return ResultCode::Unchanged;
|
return ResultCode::Unchanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastFrameSerial = current;
|
lastFrameSerial = current;
|
||||||
// printf("Frame with serial %u. Width %ux%u\n", lastSerial, pFrameHeader->width.load(), pFrameHeader->height.load());
|
|
||||||
// pHeader->lock.unlock();
|
|
||||||
return ResultCode::Changed;
|
return ResultCode::Changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +59,7 @@ struct HazelnutIvshmemClient {
|
||||||
hazelnut::IvshHeader* pHeader;
|
hazelnut::IvshHeader* pHeader;
|
||||||
hazelnut::FrameHeader* pFrameHeader;
|
hazelnut::FrameHeader* pFrameHeader;
|
||||||
|
|
||||||
u32 serial {};
|
u32 sessionId {};
|
||||||
|
|
||||||
u32 lastFrameSerial {};
|
u32 lastFrameSerial {};
|
||||||
};
|
};
|
||||||
|
@ -80,9 +76,9 @@ void rust_destroy_hazelnut_client(void* pClient) {
|
||||||
delete(HazelnutIvshmemClient*)pClient;
|
delete(HazelnutIvshmemClient*)pClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool rust_hazelnut_client_open(void* pClient, const char* pszPath) {
|
bool rust_hazelnut_client_open(void* pClient, int fd) {
|
||||||
if(pClient) {
|
if(pClient) {
|
||||||
return ((HazelnutIvshmemClient*)pClient)->Open(pszPath);
|
return ((HazelnutIvshmemClient*)pClient)->Open(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
|
@ -21,8 +21,8 @@ namespace hazelnut {
|
||||||
delete pImpl;
|
delete pImpl;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IvshmemDevice::Open(const char* devName) {
|
bool IvshmemDevice::Open(int fd) {
|
||||||
return pImpl->Open(devName);
|
return pImpl->Open(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IvshmemDevice::Close() {
|
void IvshmemDevice::Close() {
|
||||||
|
|
|
@ -14,8 +14,8 @@ namespace hazelnut {
|
||||||
|
|
||||||
~IvshmemDevice();
|
~IvshmemDevice();
|
||||||
|
|
||||||
/// Opens the device. On Linux [devName] MUST point to a SHMEM file.
|
/// Opens the device. On Linux [fd] MUST be a valid file descriptor for a SHMEM file.
|
||||||
bool Open(const char* devName = nullptr);
|
bool Open(int fd = -1);
|
||||||
|
|
||||||
void Close();
|
void Close();
|
||||||
|
|
||||||
|
|
|
@ -15,17 +15,12 @@ namespace hazelnut {
|
||||||
void* pMap = nullptr;
|
void* pMap = nullptr;
|
||||||
usize size = 0;
|
usize size = 0;
|
||||||
|
|
||||||
bool OpenDevice(const char* devName) {
|
bool OpenDevice(int fd) {
|
||||||
// devName is not optional on Linux.
|
// fd is not optional on Linux.
|
||||||
if(devName == nullptr)
|
if(fd == -1)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
int devFd = open(devName, O_RDWR, static_cast<mode_t>(0600));
|
this->fd = fd;
|
||||||
|
|
||||||
if(devFd == -1)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
fd = devFd;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +36,6 @@ namespace hazelnut {
|
||||||
|
|
||||||
void* map = mmap(0, devSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
void* map = mmap(0, devSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||||
if(map == MAP_FAILED) {
|
if(map == MAP_FAILED) {
|
||||||
printf("map failed\n");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,8 +50,8 @@ namespace hazelnut {
|
||||||
munmap(pMap, size);
|
munmap(pMap, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Open(const char* pDevName) {
|
bool Open(int fd) {
|
||||||
if(!OpenDevice(pDevName))
|
if(!OpenDevice(fd))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if(!MapMemory()) {
|
if(!MapMemory()) {
|
||||||
|
|
|
@ -138,7 +138,7 @@ namespace hazelnut {
|
||||||
memset(&ivshmemMmapStruct, 0, sizeof(IVSHMEM_MMAP));
|
memset(&ivshmemMmapStruct, 0, sizeof(IVSHMEM_MMAP));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Open(const char* pDevName) {
|
bool Open(int fd) {
|
||||||
if(!OpenDevice())
|
if(!OpenDevice())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include "atomic_spinlock.hpp"
|
#include "atomic_spinlock.hpp"
|
||||||
|
@ -11,7 +15,13 @@ int main(int argc, char** argv) {
|
||||||
|
|
||||||
hazelnut::IvshmemDevice dev;
|
hazelnut::IvshmemDevice dev;
|
||||||
|
|
||||||
if(!dev.Open("/dev/shm/lg-win7")) {
|
int fd = open("/dev/shm/lg-win7", O_RDWR, (mode_t)0600);
|
||||||
|
|
||||||
|
if(fd == -1) {
|
||||||
|
printf("Failed to open ivshmem device file\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!dev.Open(fd)) {
|
||||||
printf("Failed to open ivshmem device\n");
|
printf("Failed to open ivshmem device\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -21,6 +31,11 @@ int main(int argc, char** argv) {
|
||||||
|
|
||||||
printf("opened a %zu MB large ivshmem\n", (size / (1024 * 1024)));
|
printf("opened a %zu MB large ivshmem\n", (size / (1024 * 1024)));
|
||||||
|
|
||||||
|
memset(ptr, 0, size);
|
||||||
|
|
||||||
|
printf("zeroed :)\n");
|
||||||
|
|
||||||
|
#if 0
|
||||||
auto* pHeader = (hazelnut::IvshHeader*)&ptr[0];;
|
auto* pHeader = (hazelnut::IvshHeader*)&ptr[0];;
|
||||||
auto* pFrameHeader = (hazelnut::FrameHeader*)&ptr[0x1000];
|
auto* pFrameHeader = (hazelnut::FrameHeader*)&ptr[0x1000];
|
||||||
|
|
||||||
|
@ -59,6 +74,6 @@ int main(int argc, char** argv) {
|
||||||
// allow the vm some time to do whatever it is it wants to do
|
// allow the vm some time to do whatever it is it wants to do
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
56
Cargo.lock → testclient/Cargo.lock
generated
56
Cargo.lock → testclient/Cargo.lock
generated
|
@ -2,6 +2,12 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.93"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
@ -41,6 +47,12 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg_aliases"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dlib"
|
name = "dlib"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
|
@ -72,14 +84,6 @@ version = "2.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4"
|
checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fbcserver"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
"minifb",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
|
@ -169,6 +173,24 @@ dependencies = [
|
||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hazelnut_client"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"cc",
|
||||||
|
"nix 0.29.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hazelnut_testclient"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"hazelnut_client",
|
||||||
|
"minifb",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "instant"
|
name = "instant"
|
||||||
version = "0.1.13"
|
version = "0.1.13"
|
||||||
|
@ -288,6 +310,18 @@ dependencies = [
|
||||||
"memoffset",
|
"memoffset",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.29.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.6.0",
|
||||||
|
"cfg-if",
|
||||||
|
"cfg_aliases",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.20.2"
|
version = "1.20.2"
|
||||||
|
@ -552,7 +586,7 @@ dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"downcast-rs",
|
"downcast-rs",
|
||||||
"libc",
|
"libc",
|
||||||
"nix",
|
"nix 0.24.3",
|
||||||
"scoped-tls",
|
"scoped-tls",
|
||||||
"wayland-commons",
|
"wayland-commons",
|
||||||
"wayland-scanner",
|
"wayland-scanner",
|
||||||
|
@ -565,7 +599,7 @@ version = "0.29.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902"
|
checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nix",
|
"nix 0.24.3",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"wayland-sys",
|
"wayland-sys",
|
||||||
|
@ -577,7 +611,7 @@ version = "0.29.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661"
|
checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nix",
|
"nix 0.24.3",
|
||||||
"wayland-client",
|
"wayland-client",
|
||||||
"xcursor",
|
"xcursor",
|
||||||
]
|
]
|
9
testclient/Cargo.toml
Normal file
9
testclient/Cargo.toml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "hazelnut_testclient"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.93"
|
||||||
|
minifb = "0.27.0"
|
||||||
|
hazelnut_client = { path = "../client" }
|
|
@ -1,22 +1,19 @@
|
||||||
use minifb::{Window, WindowOptions};
|
use minifb::{Window, WindowOptions};
|
||||||
|
|
||||||
mod hzclient;
|
use hazelnut_client::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() -> anyhow::Result<()> {
|
||||||
let mut screen_width: u32 = 320;
|
let mut screen_width: u32 = 320;
|
||||||
let mut screen_height: u32 = 200;
|
let mut screen_height: u32 = 200;
|
||||||
let mut window: Option<Window> = None;
|
let mut window: Option<Window> = None;
|
||||||
|
|
||||||
let mut client = hzclient::HazelnutClient::new();
|
let mut client = HazelnutClient::new();
|
||||||
|
|
||||||
let socket_path: String = "/dev/shm/lg-win7".into();
|
let socket_path: String = "/dev/shm/lg-win7".into();
|
||||||
|
|
||||||
if !client.open(&socket_path) {
|
client.open(&socket_path[..])?;
|
||||||
println!("Could not open IVSHMEM.");
|
|
||||||
return ();
|
println!("Opened IVSHMEM device.");
|
||||||
} else {
|
|
||||||
println!("Opened IVSHMEM device.");
|
|
||||||
}
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match window.as_mut() {
|
match window.as_mut() {
|
||||||
|
@ -29,8 +26,8 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
match client.tick_one() {
|
match client.tick_one() {
|
||||||
hzclient::ResultCode::Fail => break,
|
ResultCode::Fail => break,
|
||||||
hzclient::ResultCode::Changed => {
|
ResultCode::Changed => {
|
||||||
let dims = client.dimensions();
|
let dims = client.dimensions();
|
||||||
|
|
||||||
if screen_width != dims.0 && screen_height != dims.1 {
|
if screen_width != dims.0 && screen_height != dims.1 {
|
||||||
|
@ -59,13 +56,22 @@ fn main() {
|
||||||
.expect("Failed to update");
|
.expect("Failed to update");
|
||||||
|
|
||||||
client.unlock();
|
client.unlock();
|
||||||
|
},
|
||||||
|
|
||||||
|
ResultCode::LockContended => {
|
||||||
|
// contended
|
||||||
}
|
}
|
||||||
hzclient::ResultCode::Unchanged => match window.as_mut() {
|
|
||||||
|
ResultCode::Unchanged => match window.as_mut() {
|
||||||
Some(window) => {
|
Some(window) => {
|
||||||
|
client.unlock();
|
||||||
|
|
||||||
window.update();
|
window.update();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
Loading…
Reference in a new issue