1#[cfg(windows)]
2use widestring::U16CString;
3#[cfg(windows)]
4pub use windows::{
5 core::HRESULT,
6 Win32::{
7 Foundation::{HWND, LPARAM, S_FALSE, S_OK, WPARAM},
8 UI::Controls::TASKDIALOG_NOTIFICATIONS,
9 },
10};
11#[cfg(windows)]
12use windows::{
13 core::{BOOL, PCWSTR},
14 Win32::{
15 Foundation::{FALSE, HMODULE},
16 UI::{
17 Controls::{
18 TaskDialogIndirect, TASKDIALOGCONFIG, TASKDIALOGCONFIG_0, TASKDIALOGCONFIG_1,
19 TASKDIALOG_BUTTON, TASKDIALOG_COMMON_BUTTON_FLAGS, TASKDIALOG_FLAGS, TDE_CONTENT,
20 TDE_EXPANDED_INFORMATION, TDM_NAVIGATE_PAGE,
21 TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE, TDM_UPDATE_ELEMENT_TEXT,
22 },
23 WindowsAndMessaging::SendMessageA,
24 },
25 },
26};
27
28#[cfg(not(windows))]
29pub type HWND = *mut usize;
30
31#[cfg(not(windows))]
32type HMODULE = *mut usize;
33
34#[cfg(not(windows))]
35type PWSTR = *mut u16;
36
37#[cfg(not(windows))]
38type WPARAM = usize;
39
40#[cfg(not(windows))]
41type LPARAM = isize;
42
43#[cfg(not(windows))]
44type HRESULT = i32;
45
46type UINT = u32;
47
48#[cfg(not(windows))]
49#[allow(non_camel_case_types)]
50type TASKDIALOG_FLAGS = u32;
51
52#[cfg(not(windows))]
53#[allow(non_camel_case_types)]
54type TASKDIALOG_COMMON_BUTTON_FLAGS = u32;
55
56use std::{io::Error, usize};
57
58pub type TaskDialogHyperlinkCallback = Option<fn(context: &str) -> ()>;
59
60pub type TaskDialogWndProcCallback = Option<
61 unsafe extern "system" fn(
62 hwnd: HWND,
63 msg: TASKDIALOG_NOTIFICATIONS,
64 w_param: WPARAM,
65 l_param: LPARAM,
66 ref_data: *mut TaskDialogConfig,
67 ) -> HRESULT,
68>;
69
70pub enum ExecuteOption {
71 TaskDialogIndirect,
72 TaskDialogNavigate,
73}
74
75mod constants;
76pub use constants::*;
77
78pub struct TaskDialogConfig {
79 pub parent: HWND,
80 pub instance: HMODULE,
81 pub flags: TASKDIALOG_FLAGS,
82 pub common_buttons: TASKDIALOG_COMMON_BUTTON_FLAGS,
83 pub window_title: String,
84 pub main_instruction: String,
85 pub content: String,
86 pub verification_text: String,
87 pub expanded_information: String,
88 pub expanded_control_text: String,
89 pub collapsed_control_text: String,
90 pub footer: String,
91 pub buttons: Vec<TaskDialogButton>,
92 pub default_button: i32,
93 pub radio_buttons: Vec<TaskDialogButton>,
94 pub default_radio_buttons: i32,
95 pub main_icon: PCWSTR,
96 pub footer_icon: PCWSTR,
97 pub dialog_hwnd: HWND,
99 pub is_destroyed: bool,
101 pub hyperlink_callback: TaskDialogHyperlinkCallback,
102 pub callback: TaskDialogWndProcCallback,
103 pub cx_width: u32,
104}
105
106impl Default for TaskDialogConfig {
107 fn default() -> Self {
108 TaskDialogConfig {
109 parent: HWND::default(),
110 instance: HMODULE::default(),
111 flags: TASKDIALOG_FLAGS::default(),
112 common_buttons: TDCBF_CANCEL_BUTTON,
113 window_title: "".to_string(),
114 main_instruction: "".to_string(),
115 content: "".to_string(),
116 verification_text: "".to_string(),
117 expanded_information: "".to_string(),
118 expanded_control_text: "".to_string(),
119 collapsed_control_text: "".to_string(),
120 footer: "".to_string(),
121 buttons: vec![],
122 default_button: 0,
123 radio_buttons: vec![],
124 default_radio_buttons: 0,
125 main_icon: PCWSTR::null(),
126 footer_icon: PCWSTR::null(),
127 dialog_hwnd: HWND::default(),
128 is_destroyed: false,
129 hyperlink_callback: None,
130 callback: None,
131 cx_width: 0,
132 }
133 }
134}
135
136#[cfg(windows)]
137impl TaskDialogConfig {
138 pub fn enable_process_bar(&mut self, marquee: bool) {
143 if marquee {
144 if self.flags & TDF_SHOW_MARQUEE_PROGRESS_BAR != TDF_SHOW_MARQUEE_PROGRESS_BAR {
145 self.flags |= TDF_SHOW_MARQUEE_PROGRESS_BAR;
146 }
147 } else {
148 if self.flags & TDF_SHOW_PROGRESS_BAR != TDF_SHOW_PROGRESS_BAR {
149 self.flags |= TDF_SHOW_PROGRESS_BAR;
150 }
151 }
152 }
153
154 pub fn disable_process_bar(&mut self, marquee: bool) {
156 if marquee {
157 if self.flags & TDF_SHOW_MARQUEE_PROGRESS_BAR == TDF_SHOW_MARQUEE_PROGRESS_BAR {
158 self.flags &= !TDF_SHOW_MARQUEE_PROGRESS_BAR;
159 }
160 } else {
161 if self.flags & TDF_SHOW_PROGRESS_BAR == TDF_SHOW_PROGRESS_BAR {
162 self.flags &= !TDF_SHOW_PROGRESS_BAR;
163 }
164 }
165 }
166
167 pub fn set_process_bar_marquee(&mut self, enable: bool, time: isize) {
169 if self.dialog_hwnd.is_invalid() {
170 return;
171 }
172 unsafe {
173 SendMessageA(
174 self.dialog_hwnd,
175 TDM_SET_PROGRESS_BAR_MARQUEE.0 as _,
176 WPARAM(if enable { 1 } else { 0 }),
177 LPARAM(time),
178 );
179 }
180 }
181
182 pub fn set_process_bar(&mut self, percentage: usize) {
184 if self.dialog_hwnd.is_invalid() {
185 return;
186 }
187 unsafe {
188 SendMessageA(
189 self.dialog_hwnd,
190 TDM_SET_PROGRESS_BAR_POS.0 as _,
191 WPARAM(percentage),
192 LPARAM(0),
193 );
194 }
195 }
196
197 pub fn set_content(&mut self, content: &str) {
199 if self.dialog_hwnd.is_invalid() {
200 return;
201 }
202 self.content = content.to_string();
203 unsafe {
204 let content_wchar = U16CString::from_str_unchecked(content);
205 SendMessageA(
206 self.dialog_hwnd,
207 TDM_UPDATE_ELEMENT_TEXT.0 as _,
208 WPARAM(TDE_CONTENT.0 as _),
209 LPARAM(content_wchar.as_ptr() as _),
210 );
211 }
212 }
213
214 pub fn set_main_instruction(&mut self, main_instruction: &str) {
216 if self.dialog_hwnd.is_invalid() {
217 return;
218 }
219 self.main_instruction = main_instruction.to_string();
220 unsafe {
221 use windows::Win32::UI::Controls::TDE_MAIN_INSTRUCTION;
222
223 let main_instruction_wchar = U16CString::from_str_unchecked(main_instruction);
224 SendMessageA(
225 self.dialog_hwnd,
226 TDM_UPDATE_ELEMENT_TEXT.0 as _,
227 WPARAM(TDE_MAIN_INSTRUCTION.0 as _),
228 LPARAM(main_instruction_wchar.as_ptr() as _),
229 );
230 }
231 }
232
233 pub fn set_footer(&mut self, footer: &str) {
235 if self.dialog_hwnd.is_invalid() {
236 return;
237 }
238 self.footer = footer.to_string();
239 unsafe {
240 use windows::Win32::UI::{
241 Controls::{TDE_FOOTER, TDM_UPDATE_ELEMENT_TEXT},
242 WindowsAndMessaging::SendMessageA,
243 };
244
245 let footer_wchar = U16CString::from_str_unchecked(footer);
246 SendMessageA(
247 self.dialog_hwnd,
248 TDM_UPDATE_ELEMENT_TEXT.0 as _,
249 WPARAM(TDE_FOOTER.0 as _),
250 LPARAM(footer_wchar.as_ptr() as _),
251 );
252 }
253 }
254
255 pub fn set_expanded_information(&mut self, expanded_information: &str) {
257 if self.dialog_hwnd.is_invalid() {
258 return;
259 }
260 self.expanded_information = expanded_information.to_string();
261 unsafe {
262 let expanded_information_wchar = U16CString::from_str_unchecked(expanded_information);
263 SendMessageA(
264 self.dialog_hwnd,
265 TDM_UPDATE_ELEMENT_TEXT.0 as _,
266 WPARAM(TDE_EXPANDED_INFORMATION.0 as _),
267 LPARAM(expanded_information_wchar.as_ptr() as _),
268 );
269 }
270 }
271
272 pub fn set_button_elevation_required_state(&mut self, button_id: usize, enable: bool) {
274 if self.dialog_hwnd.is_invalid() {
275 return;
276 }
277 unsafe {
278 SendMessageA(
279 self.dialog_hwnd,
280 TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE.0 as _,
281 WPARAM(button_id),
282 LPARAM(if enable { 1 } else { 0 }),
283 );
284 }
285 }
286
287 pub fn navigate_page(&mut self, conf: &mut TaskDialogConfig) {
289 if self.dialog_hwnd.is_invalid() {
290 return;
291 }
292 execute_task_dialog(conf, ExecuteOption::TaskDialogNavigate).ok();
293 }
294}
295
296#[cfg(not(windows))]
297impl TaskDialogConfig {
298 pub fn enable_process_bar(&mut self, _marquee: bool) {}
299 pub fn disable_process_bar(&mut self, marquee: bool) {}
300 pub fn set_process_bar_marquee(&mut self, _enable: bool, _time: isize) {}
301 pub fn set_process_bar(&mut self, _percentage: usize) {}
302 pub fn set_content(&mut self, content: &str) {}
303 pub fn set_main_instruction(&mut self, main_instruction: &str) {}
304 pub fn set_footer(&mut self, footer: &str) {}
305 pub fn set_expanded_information(&mut self, expanded_information: &str) {}
306 pub fn set_button_elevation_required_state(&mut self, button_id: usize, enable: bool) {}
307 pub fn navigate_page(&mut self, conf: &mut TaskDialogConfig) {}
308}
309
310pub struct TaskDialogButton {
311 pub id: i32,
312 pub text: String,
313}
314
315pub struct TaskDialogResult {
316 pub button_id: i32,
317 pub radio_button_id: i32,
318 pub checked: bool,
319}
320
321impl Default for TaskDialogResult {
322 fn default() -> Self {
323 TaskDialogResult {
324 button_id: 0,
325 radio_button_id: 0,
326 checked: false,
327 }
328 }
329}
330
331#[cfg(windows)]
333pub fn show_task_dialog(conf: &mut TaskDialogConfig) -> Result<TaskDialogResult, Error> {
334 execute_task_dialog(conf, ExecuteOption::TaskDialogIndirect)
335}
336
337#[cfg(windows)]
339pub fn execute_task_dialog(
340 conf: &mut TaskDialogConfig,
341 opt: ExecuteOption,
342) -> Result<TaskDialogResult, Error> {
343 use std::ptr::addr_of_mut;
344
345 let mut result = TaskDialogResult::default();
346 let conf_ptr: *mut TaskDialogConfig = conf;
347 let conf_long_ptr = conf_ptr as isize;
348
349 let ret = unsafe {
350 let instance = if conf.instance.is_invalid() {
353 use windows::Win32::System::LibraryLoader::GetModuleHandleA;
354
355 GetModuleHandleA(None).unwrap()
358 } else {
359 conf.instance
360 };
361
362 let window_title: U16CString = U16CString::from_str_unchecked(&conf.window_title);
364 let main_instruction: U16CString = U16CString::from_str_unchecked(&conf.main_instruction);
365 let content: U16CString = U16CString::from_str_unchecked(&conf.content);
366 let verification_text: U16CString = U16CString::from_str_unchecked(&conf.verification_text);
367 let expanded_information: U16CString =
368 U16CString::from_str_unchecked(&conf.expanded_information);
369 let expanded_control_text: U16CString =
370 U16CString::from_str_unchecked(&conf.expanded_control_text);
371 let collapsed_control_text: U16CString =
372 U16CString::from_str_unchecked(&conf.collapsed_control_text);
373 let footer: U16CString = U16CString::from_str_unchecked(&conf.footer);
374
375 let btn_text: Vec<U16CString> = conf
377 .buttons
378 .iter()
379 .map(|btn| U16CString::from_str_unchecked(&btn.text))
380 .collect();
381 let buttons: Vec<TASKDIALOG_BUTTON> = conf
382 .buttons
383 .iter()
384 .enumerate()
385 .map(|(i, btn)| TASKDIALOG_BUTTON {
386 nButtonID: btn.id,
387 pszButtonText: PCWSTR(btn_text[i].as_ptr()),
388 })
389 .collect();
390
391 let radio_btn_text: Vec<U16CString> = conf
393 .radio_buttons
394 .iter()
395 .map(|btn| U16CString::from_str_unchecked(&btn.text))
396 .collect();
397 let radio_buttons: Vec<TASKDIALOG_BUTTON> = conf
398 .radio_buttons
399 .iter()
400 .enumerate()
401 .map(|(i, btn)| TASKDIALOG_BUTTON {
402 nButtonID: btn.id,
403 pszButtonText: PCWSTR(radio_btn_text[i].as_ptr()),
404 })
405 .collect();
406
407 let mut u1: TASKDIALOGCONFIG_0 = Default::default();
409 let mut u2: TASKDIALOGCONFIG_1 = Default::default();
410 if !conf.main_icon.is_null() {
411 u1.pszMainIcon = conf.main_icon;
412 }
413 if !conf.footer_icon.is_null() {
414 u2.pszFooterIcon = conf.footer_icon;
415 }
416
417 unsafe extern "system" fn callback(
418 hwnd: HWND,
419 msg: TASKDIALOG_NOTIFICATIONS,
420 _w_param: WPARAM,
421 _l_param: LPARAM,
422 lp_ref_data: isize,
423 ) -> HRESULT {
424 use windows::Win32::{
425 Foundation::S_OK,
426 UI::Controls::{TDN_CREATED, TDN_DESTROYED, TDN_HYPERLINK_CLICKED},
427 };
428
429 let conf = std::mem::transmute::<isize, *mut TaskDialogConfig>(lp_ref_data);
430 match msg {
431 TDN_CREATED => {
432 (*conf).dialog_hwnd = hwnd;
433 }
434 TDN_DESTROYED => {
435 (*conf).is_destroyed = true;
436 }
437 TDN_HYPERLINK_CLICKED => {
438 let link = U16CString::from_ptr_str(_l_param.0 as *const u16)
439 .to_string()
440 .unwrap();
441 if let Some(callback) = (*conf).hyperlink_callback {
442 callback(&link);
443 }
444 }
445 _ => {}
446 };
447 if let Some(callback) = (*conf).callback {
448 return callback(hwnd, msg, _w_param, _l_param, lp_ref_data as _);
449 }
450
451 S_OK
452 }
453
454 let mut config = TASKDIALOGCONFIG {
455 cbSize: std::mem::size_of::<TASKDIALOGCONFIG>() as UINT,
456 hwndParent: conf.parent,
457 hInstance: instance.into(),
458 dwFlags: conf.flags,
459 dwCommonButtons: conf.common_buttons,
460 pszWindowTitle: PCWSTR::from_raw(window_title.as_ptr()),
461 pszMainInstruction: PCWSTR::from_raw(main_instruction.as_ptr()),
462 pszContent: PCWSTR::from_raw(content.as_ptr()),
463 pszVerificationText: PCWSTR::from_raw(verification_text.as_ptr()),
464 pszExpandedInformation: PCWSTR::from_raw(expanded_information.as_ptr()),
465 pszExpandedControlText: PCWSTR::from_raw(expanded_control_text.as_ptr()),
466 pszCollapsedControlText: PCWSTR::from_raw(collapsed_control_text.as_ptr()),
467 pszFooter: PCWSTR::from_raw(footer.as_ptr()),
468 cButtons: buttons.len() as UINT,
469 pButtons: buttons.as_slice().as_ptr(),
470 nDefaultButton: conf.default_button,
471 cRadioButtons: radio_buttons.len() as UINT,
472 pRadioButtons: radio_buttons.as_slice().as_ptr(),
473 nDefaultRadioButton: conf.default_radio_buttons,
474 Anonymous1: u1,
475 Anonymous2: u2,
476 pfCallback: Some(callback),
477 lpCallbackData: conf_long_ptr,
478 cxWidth: conf.cx_width,
479 };
480
481 match opt {
482 ExecuteOption::TaskDialogIndirect => {
483 let mut verify: BOOL = FALSE;
485 let dialog_result = TaskDialogIndirect(
486 &config,
487 Some(&mut result.button_id),
488 Some(&mut result.radio_button_id),
489 Some(&mut verify),
490 )
491 .map_or_else(|e| e.code().0, |_| 0);
492 result.checked = verify != FALSE;
493
494 dialog_result
495 }
496 ExecuteOption::TaskDialogNavigate => {
497 SendMessageA(
498 conf.dialog_hwnd,
499 TDM_NAVIGATE_PAGE.0 as _,
500 WPARAM(0),
501 LPARAM(addr_of_mut!(config) as _),
502 );
503
504 0
505 }
506 }
507 };
508
509 if ret != 0 {
510 Err(Error::last_os_error())
511 } else {
512 Ok(result)
513 }
514}
515
516#[cfg(windows)]
518pub fn show_msg_dialog(
519 title: &str,
520 main_instruction: &str,
521 content: &str,
522 icon: PCWSTR,
523) -> Option<Error> {
524 let mut conf = TaskDialogConfig {
525 common_buttons: TDCBF_OK_BUTTON,
526 window_title: title.to_string(),
527 main_instruction: main_instruction.to_string(),
528 content: content.to_string(),
529 main_icon: icon,
530 ..Default::default()
531 };
532 show_task_dialog(&mut conf).err()
533}
534
535#[cfg(not(windows))]
536pub fn show_task_dialog(_conf: &TaskDialogConfig) -> Result<TaskDialogResult, Error> {
537 Ok(TaskDialogResult::default())
538}
539
540#[cfg(not(windows))]
541pub fn show_msg_dialog(
542 _title: &str,
543 _main_instruction: &str,
544 _content: &str,
545 _icon: *mut u16,
546) -> Option<Error> {
547 None
548}