1use std::{
9 cell::UnsafeCell,
10 ffi::{CString, c_void},
11 fmt::Debug,
12 mem,
13};
14
15use bon::Builder;
16use futures_channel::mpsc;
17use tracing::{debug, trace, warn};
18use windows_sys::Win32::{
19 Foundation::HWND,
20 UI::WindowsAndMessaging::{
21 GA_ROOT, GetAncestor, SWP_NOZORDER, SendMessageW, SetWindowPos, WM_CLOSE, WM_CREATE,
22 WM_CTLCOLORDLG, WM_LBUTTONDOWN, WM_MOVE, WM_PARENTNOTIFY, WM_SIZE,
23 },
24};
25
26use crate::{PluginApp, PluginHandler, PluginHost, sys};
27
28#[cfg(feature = "winio")]
29#[cfg_attr(feature = "winio-07", path = "winio_07.rs")]
30pub mod winio;
31
32#[derive(Builder)]
35pub struct OptionsPage<A: PluginApp> {
36 #[builder(into)]
38 name: String,
39 #[builder(with = |x: impl FnMut(OptionsPageLoadArgs) -> PageHandle<A> + 'static| UnsafeCell::new(Box::new(x)))]
40 load: UnsafeCell<Box<dyn FnMut(OptionsPageLoadArgs) -> PageHandle<A>>>,
41 #[builder(default)]
42 handle: UnsafeCell<Option<PageHandle<A>>>,
43}
44
45impl<A: PluginApp> OptionsPage<A> {
46 fn load_mut(&self) -> &mut dyn FnMut(OptionsPageLoadArgs) -> PageHandle<A> {
47 unsafe { &mut *self.load.get() }
48 }
49
50 fn handle(&self) -> &Option<PageHandle<A>> {
51 unsafe { &*self.handle.get() }
52 }
53
54 fn handle_mut(&self) -> &mut Option<PageHandle<A>> {
55 unsafe { &mut *self.handle.get() }
56 }
57}
58
59#[derive(Debug)]
60pub struct OptionsPageLoadArgs {
61 parent: HWND,
62}
63
64enum OptionsPageInternalMessage<A: PluginApp> {
65 Msg(OptionsPageMessage<A>),
66 Size((i32, i32)),
67 Kill,
69}
70
71impl<A: PluginApp> From<OptionsPageMessage<A>> for OptionsPageInternalMessage<A> {
72 fn from(msg: OptionsPageMessage<A>) -> Self {
73 OptionsPageInternalMessage::Msg(msg)
74 }
75}
76
77impl<A: PluginApp> OptionsPageInternalMessage<A> {
78 pub fn try_into(self, window: HWND) -> Option<OptionsPageMessage<A>> {
79 match self {
80 OptionsPageInternalMessage::Msg(msg) => Some(msg),
81 OptionsPageInternalMessage::Size(v) => {
82 debug!(?v, "OptionsPageInternalMessage::Size");
83 unsafe { SetWindowPos(window, 0 as _, 0, 0, v.0, v.1, SWP_NOZORDER) };
85 Some(OptionsPageMessage::Redraw)
86 }
87 OptionsPageInternalMessage::Kill => {
88 unsafe { SendMessageW(window, WM_CLOSE, 0, 0) };
89 Some(OptionsPageMessage::Close)
90 }
91 }
92 }
93}
94
95pub enum OptionsPageMessage<A: PluginApp> {
96 Redraw,
97 Close,
98
99 Save(
105 &'static mut A::Config,
106 std::sync::mpsc::SyncSender<&'static mut A::Config>,
107 ),
108}
109
110impl<A: PluginApp> Debug for OptionsPageMessage<A> {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 match self {
113 OptionsPageMessage::Redraw => write!(f, "OptionsPageMessage::Redraw"),
114 OptionsPageMessage::Close => write!(f, "OptionsPageMessage::Close"),
115 OptionsPageMessage::Save(_config, _tx) => write!(f, "OptionsPageMessage::Save"),
116 }
117 }
118}
119
120pub struct PageHandle<A: PluginApp> {
121 #[allow(dead_code)]
122 thread_handle: std::thread::JoinHandle<()>,
123 tx: mpsc::UnboundedSender<OptionsPageInternalMessage<A>>,
124}
125
126impl<A: PluginApp> PluginHandler<A> {
127 pub fn add_options_pages(&self, data: *mut c_void) -> *mut c_void {
128 debug!("Plugin add options pages");
129 if self.options_pages.is_empty() {
130 0 as _
131 } else {
132 for (i, page) in self.options_pages.iter().enumerate() {
133 self.host()
134 .ui_options_add_plugin_page(data, i as _, &page.name);
135 }
136 1 as _
137 }
138 }
139
140 pub fn load_options_page(&self, data: *mut c_void) -> *mut c_void {
144 debug_assert!(!self.options_pages.is_empty());
145
146 let data = unsafe { &mut *(data as *mut sys::everything_plugin_load_options_page_s) };
147 {
148 let page_hwnd = data.page_hwnd as *const c_void;
149 debug!(?page_hwnd, "Plugin load options page");
150 }
151 let page_hwnd: HWND = unsafe { mem::transmute(data.page_hwnd) };
152
153 let page = &self.options_pages[data.user_data as usize];
154
155 *page.handle_mut() = Some((page.load_mut())(OptionsPageLoadArgs { parent: page_hwnd }));
156
157 1 as _
165 }
166
167 #[cfg(feature = "winio")]
168 pub fn load_options_page_winio<'a, T: winio::OptionsPageComponent<'a>>(
169 &self,
170 data: *mut c_void,
171 ) -> *mut c_void {
172 let data = unsafe { &mut *(data as *mut sys::everything_plugin_load_options_page_s) };
173 {
174 let page_hwnd = data.page_hwnd as *const c_void;
175 debug!(?page_hwnd, "Plugin load options page");
176 }
177 let page_hwnd: HWND = unsafe { mem::transmute(data.page_hwnd) };
178
179 winio::spawn::<T>(OptionsPageLoadArgs { parent: page_hwnd });
180
181 1 as _
182 }
183
184 pub fn save_options_page(&self, data: *mut c_void) -> *mut c_void {
185 let data = unsafe { &mut *(data as *mut sys::everything_plugin_save_options_page_s) };
186 debug!(?data, "Plugin save options page");
187
188 if self.options_pages.is_empty() {
189 return 0 as _;
190 }
191
192 let page = &self.options_pages[data.user_data as usize];
193 match page.handle() {
194 Some(handle) => {
195 debug!(is_closed = handle.tx.is_closed(), "Saving options page");
196
197 let (tx, rx) = std::sync::mpsc::sync_channel(1);
198
199 let mut config = self.app_into_config();
200 let config_static: &'static mut A::Config = unsafe { mem::transmute(&mut config) };
201 match handle
202 .tx
203 .unbounded_send(OptionsPageMessage::Save(config_static, tx).into())
204 {
205 Ok(()) => {
206 if let Ok(_config) = rx.recv() {
207 debug!(?config, "Options page config");
208 self.app_new(Some(config));
209 }
210 }
211 Err(_) => (),
212 }
213 }
214 None => warn!("Options page handle is None, can't save"),
215 }
216
217 self.options_message.set(OptionsMessage::EnableApply(true));
219
220 1 as _
221 }
222
223 pub fn get_options_page_minmax(&self, _data: *mut c_void) -> *mut c_void {
224 0 as _
226 }
227
228 pub fn size_options_page(&self, _data: *mut c_void) -> *mut c_void {
229 0 as _
231 }
232
233 pub fn options_page_proc(&self, data: *mut c_void) -> *mut c_void {
234 let data = unsafe { &mut *(data as *mut sys::everything_plugin_options_page_proc_s) };
235 trace!(?data, "Plugin options page proc");
236
237 if self.options_pages.is_empty() {
238 return 0 as _;
239 }
240 let page = &self.options_pages[data.user_data as usize];
241
242 let msg = data.msg as u32;
243 let w_param = data.wParam;
244 let l_param = data.lParam;
245
246 let options_hwnd = unsafe { mem::transmute(data.options_hwnd) };
247 match msg {
275 WM_MOVE | WM_CLOSE => {
276 debug!(
277 msg,
278 lParam = ?l_param as *const c_void,
279 lParam = ?w_param as *const c_void,
280 );
281 }
282 WM_SIZE => {
283 if let Some(handle) = page.handle() {
284 _ = handle.tx.unbounded_send(OptionsPageInternalMessage::Size((
285 (l_param & 0xFFFF) as i32,
286 (l_param >> 16) as i32,
287 )));
288 }
289 }
290 WM_PARENTNOTIFY => {
291 debug!(wParam = data.wParam, "WM_PARENTNOTIFY");
292 match data.wParam as u32 {
293 WM_CREATE => {
294 }
298 WM_LBUTTONDOWN => {
299 self.host()
301 .ui_options_enable_or_disable_apply_button(options_hwnd, true);
302 }
303 _ => (),
304 }
305 }
306 WM_CTLCOLORDLG => {
307 debug!(lParam = ?data.lParam as *const c_void, "WM_CTLCOLORDLG");
308 self.host()
309 .ui_options_enable_or_disable_apply_button(options_hwnd, true);
310 }
311 _ => (),
312 }
313
314 match self.options_message.take() {
315 OptionsMessage::Noop => (),
316 OptionsMessage::EnableApply(enable) => self
317 .host()
318 .ui_options_enable_or_disable_apply_button(options_hwnd, enable),
319 }
320
321 1 as _
322 }
323
324 pub fn kill_options_page(&self, data: *mut c_void) -> *mut c_void {
325 debug!(?data, "Plugin kill options page");
326
327 if self.options_pages.is_empty() {
328 return 0 as _;
329 }
330
331 let page = &self.options_pages[data as usize];
332 match page.handle_mut().take() {
333 Some(handle) => {
334 debug!(is_closed = handle.tx.is_closed(), "Killing options page");
335 _ = handle.tx.unbounded_send(OptionsPageInternalMessage::Kill);
336 #[cfg(debug_assertions)]
338 std::thread::spawn(|| {
339 handle.thread_handle.join().unwrap();
341 debug!("Options page thread finished");
342 });
343 }
344 None => warn!("Options page handle is None, can't kill"),
345 }
346 1 as _
347 }
348}
349
350#[derive(Default)]
351pub enum OptionsMessage {
352 #[default]
353 Noop,
354 EnableApply(bool),
355}
356
357#[repr(i32)]
359pub enum OptionsDlgItem {
360 ApplyButton = 1001,
361}
362
363impl PluginHost {
364 pub fn ui_options_add_plugin_page(
365 &self,
366 data: *mut c_void,
367 user_data: *mut c_void,
368 name: &str,
369 ) {
370 let ui_options_add_plugin_page: unsafe extern "system" fn(
372 add_custom_page: *mut c_void,
373 user_data: *mut c_void,
374 name: *const sys::everything_plugin_utf8_t,
375 )
376 -> *mut ::std::os::raw::c_void =
377 unsafe { self.get("ui_options_add_plugin_page").unwrap_unchecked() };
378 let name = CString::new(name).unwrap();
379 unsafe { ui_options_add_plugin_page(data, user_data, name.as_ptr() as _) };
380 }
381
382 pub fn ui_options_from_page_hwnd(page_hwnd: HWND) -> HWND {
383 unsafe { GetAncestor(page_hwnd, GA_ROOT) }
384 }
385
386 pub fn ui_options_enable_or_disable_apply_button(&self, options_hwnd: HWND, enable: bool) {
387 self.os_enable_or_disable_dlg_item(
388 options_hwnd,
389 OptionsDlgItem::ApplyButton as i32,
390 enable,
391 );
392 }
393
394 pub fn os_enable_or_disable_dlg_item(&self, parent_hwnd: HWND, id: i32, enable: bool) {
399 let os_enable_or_disable_dlg_item: unsafe extern "system" fn(
400 parent_hwnd: HWND,
401 id: i32,
402 enable: i32,
403 ) = unsafe { self.get("os_enable_or_disable_dlg_item").unwrap_unchecked() };
404 unsafe { os_enable_or_disable_dlg_item(parent_hwnd, id, enable as i32) };
405 }
406}