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