From 0fb8ce6f7ebfff1dbcd8ff6b426f3b63f7190217 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 7 Feb 2022 00:33:41 +0100 Subject: [PATCH] Add part of a Windows event loop implementation --- Cargo.lock | 54 +++++++++++-- Cargo.toml | 10 +++ src/context.rs | 8 +- src/context/windows.rs | 167 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 233 insertions(+), 6 deletions(-) create mode 100644 src/context/windows.rs diff --git a/Cargo.lock b/Cargo.lock index 8cc8d914..039544e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -515,6 +515,7 @@ dependencies = [ "serde_json", "vst3-sys", "widestring", + "windows", ] [[package]] @@ -983,17 +984,30 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbedf6db9096bc2364adce0ae0aa636dcd89f3c3f2cd67947062aaf0ca2a10ec" +dependencies = [ + "windows_aarch64_msvc 0.32.0", + "windows_i686_gnu 0.32.0", + "windows_i686_msvc 0.32.0", + "windows_x86_64_gnu 0.32.0", + "windows_x86_64_msvc 0.32.0", +] + [[package]] name = "windows-sys" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ceb069ac8b2117d36924190469735767f0990833935ab430155e71a44bafe148" dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", + "windows_aarch64_msvc 0.29.0", + "windows_i686_gnu 0.29.0", + "windows_i686_msvc 0.29.0", + "windows_x86_64_gnu 0.29.0", + "windows_x86_64_msvc 0.29.0", ] [[package]] @@ -1002,30 +1016,60 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d027175d00b01e0cbeb97d6ab6ebe03b12330a35786cbaca5252b1c4bf5d9b" +[[package]] +name = "windows_aarch64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" + [[package]] name = "windows_i686_gnu" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8793f59f7b8e8b01eda1a652b2697d87b93097198ae85f823b969ca5b89bba58" +[[package]] +name = "windows_i686_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" + [[package]] name = "windows_i686_msvc" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8602f6c418b67024be2996c512f5f995de3ba417f4c75af68401ab8756796ae4" +[[package]] +name = "windows_i686_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" + [[package]] name = "windows_x86_64_gnu" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3d615f419543e0bd7d2b3323af0d86ff19cbc4f816e6453f36a2c2ce889c354" +[[package]] +name = "windows_x86_64_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" + [[package]] name = "windows_x86_64_msvc" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d95421d9ed3672c280884da53201a5c46b7b2765ca6faf34b0d71cf34a3561" +[[package]] +name = "windows_x86_64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" + [[package]] name = "x11" version = "2.19.1" diff --git a/Cargo.toml b/Cargo.toml index 14920524..2c86d231 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,16 @@ widestring = "1.0.0-beta.1" assert_no_alloc = { version = "1.1", optional = true } +[target.'cfg(windows)'.dependencies.windows] +version = "0.32" +features = [ + "Win32_Foundation", + "Win32_Graphics_Gdi", + "Win32_UI_WindowsAndMessaging", + "Win32_System_LibraryLoader", + "Win32_System_Performance", +] + [features] default = [] # Enabling this feature will cause the plugin to terminate when allocations diff --git a/src/context.rs b/src/context.rs index e7eda993..ee1ed6be 100644 --- a/src/context.rs +++ b/src/context.rs @@ -20,9 +20,15 @@ use std::sync::Weak; #[cfg(all(target_family = "unix", not(target_os = "macos")))] mod linux; +#[cfg(target_os = "windows")] +mod windows; #[cfg(all(target_family = "unix", not(target_os = "macos")))] -pub(crate) use linux::LinuxEventLoop as OsEventLoop; +pub(crate) use self::linux::LinuxEventLoop as OsEventLoop; +#[cfg(target_os = "windows")] +pub(crate) use self::windows::WindowsEventLoop as OsEventLoop; +#[cfg(target_os = "macos")] +compile_error!("The macOS event loop has not yet been implemented"); use crate::param::internals::ParamPtr; use crate::param::Param; diff --git a/src/context/windows.rs b/src/context/windows.rs new file mode 100644 index 00000000..0c930a27 --- /dev/null +++ b/src/context/windows.rs @@ -0,0 +1,167 @@ +// nih-plug: plugins, but rewritten in Rust +// Copyright (C) 2022 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! An event loop for windows, using an invisible window to hook into the host's message loop. This +//! has only been tested under Wine. + +use crossbeam::queue::ArrayQueue; +use std::ffi::{c_void, CString}; +use std::mem; +use std::ptr; +use std::sync::Arc; +use std::sync::Weak; +use std::thread::{self, ThreadId}; +use windows::Win32::Foundation::{HINSTANCE, HWND, LPARAM, LRESULT, PSTR, WPARAM}; +use windows::Win32::System::{ + LibraryLoader::GetModuleHandleA, Performance::QueryPerformanceCounter, +}; +use windows::Win32::UI::WindowsAndMessaging::{ + CloseWindow, CreateWindowExA, DefWindowProcA, RegisterClassExA, UnregisterClassA, HMENU, + WINDOW_EX_STYLE, WINDOW_STYLE, WNDCLASSEXA, +}; + +use super::{EventLoop, MainThreadExecutor}; +use crate::nih_log; + +compile_error!("The Windows event loop has not yet been fully implemented"); + +/// See [super::EventLoop]. +pub(crate) struct WindowsEventLoop { + /// The thing that ends up executing these tasks. The tasks are usually executed from the worker + /// thread, but if the current thread is the main thread then the task cna also be executed + /// directly. + executor: Weak, + + /// The ID of the main thread. In practice this is the ID of the thread that created this task + /// queue. + main_thread_id: ThreadId, + + /// An invisible window that we can post a message to when we need to do something on the main + /// thread. The host's message loop will then cause our message to be proceded. + message_window: HWND, + /// The unique class for the message window, we'll clean this up together with the window. + message_window_class_name: CString, + /// A queue of tasks that still need to be performed. When something gets added to this queue + /// we'll wake up the window, which then continues to pop tasks off this queue until it is + /// empty. + tasks: Arc>, +} + +impl EventLoop for WindowsEventLoop +where + T: Send + 'static, + E: MainThreadExecutor + 'static, +{ + fn new_and_spawn(executor: Weak) -> Self { + // We'll pass one copy of the this to the window, and we'll keep the other copy here + let tasks = Arc::new(ArrayQueue::new(super::TASK_QUEUE_CAPACITY)); + + // Window classes need to have unique names or else multiple plugins loaded into the same + // process will end up calling the other plugin's callbacks + let mut ticks = 0i64; + assert!(unsafe { QueryPerformanceCounter(&mut ticks).as_bool() }); + let class_name = CString::new(format!("nih-event-loop-{ticks}")) + .expect("Where did these null bytes come from?"); + let class_name_ptr = PSTR(class_name.as_bytes_with_nul().as_ptr()); + + let class = WNDCLASSEXA { + cbSize: mem::size_of::() as u32, + lpfnWndProc: Some(window_proc), + hInstance: unsafe { GetModuleHandleA(PSTR(ptr::null())) }, + lpszClassName: class_name_ptr, + ..Default::default() + }; + assert_ne!(unsafe { RegisterClassExA(&class) }, 0); + + let window = unsafe { + CreateWindowExA( + WINDOW_EX_STYLE(0), + class_name_ptr, + PSTR(b"NIH-plug event loop\0".as_ptr()), + WINDOW_STYLE(0), + 0, + 0, + 0, + 0, + HWND(0), + HMENU(0), + HINSTANCE(0), + Arc::into_raw(tasks.clone()) as *const c_void, + ) + }; + assert!(!window.is_invalid()); + + Self { + executor, + main_thread_id: thread::current().id(), + message_window: window, + message_window_class_name: class_name, + tasks, + } + } + + fn do_maybe_async(&self, task: T) -> bool { + if self.is_main_thread() { + match self.executor.upgrade() { + Some(e) => { + unsafe { e.execute(task) }; + true + } + None => { + nih_log!("The executor doesn't exist but somehow it's still submitting tasks, this shouldn't be possible!"); + false + } + } + } else { + let success = self.tasks.push(task).is_ok(); + if success { + todo!("Wake up the window"); + } + + success + } + } + + fn is_main_thread(&self) -> bool { + thread::current().id() == self.main_thread_id + } +} + +impl Drop for WindowsEventLoop { + fn drop(&mut self) { + unsafe { CloseWindow(self.message_window) }; + unsafe { + UnregisterClassA( + PSTR(self.message_window_class_name.as_bytes_with_nul().as_ptr()), + HINSTANCE(0), + ) + }; + } +} + +unsafe extern "system" fn window_proc( + handle: HWND, + message: u32, + wparam: WPARAM, + lparam: LPARAM, +) -> LRESULT { + eprintln!("Hello from the window proc!"); + + todo!("Clean up the Arc (that got turned into a raw pointe) with the window"); + todo!("Handle messages"); + + DefWindowProcA(handle, message, wparam, lparam) +}