Skip to main content

pacenotes_installer\wizard/
sheet.rs

1use windows_sys::Win32::UI::Controls as ctl;
2use windows_sys::Win32::UI::WindowsAndMessaging as wm;
3
4/// Scene 3's index in `pages`.
5const GAME_FOLDER_PAGE: u32 = 2;
6
7const SHEET_SUBCLASS: usize = 1;
8
9/// Runs the wizard until the user finishes or cancels; a restart as
10/// administrator opens on scene 3, where it was asked for.
11pub fn run(
12    base_url: &str,
13    cache_override: Option<std::path::PathBuf>,
14    restart: Option<crate::elevation::Restart>,
15) {
16    let mut state = crate::wizard::state::WizardState::new(base_url.to_string(), cache_override);
17    let start_page = match restart {
18        Some(restart) => {
19            state.resume(restart);
20            GAME_FOLDER_PAGE
21        }
22        None => 0,
23    };
24    let state_pointer = &mut state as *mut _ as windows_sys::Win32::Foundation::LPARAM;
25
26    let pages = [
27        page(
28            crate::wizard::control_ids::IDD_S1_LANGUAGE,
29            Some(crate::wizard::scene::language_page),
30            state_pointer,
31        ),
32        page(
33            crate::wizard::control_ids::IDD_S2_MOTD,
34            Some(crate::wizard::scene::motd_page),
35            state_pointer,
36        ),
37        page(
38            crate::wizard::control_ids::IDD_S3_FOLDER,
39            Some(crate::wizard::scene::game_folder_page),
40            state_pointer,
41        ),
42        page(
43            crate::wizard::control_ids::IDD_S4_DOWNLOAD,
44            Some(crate::wizard::scene::download_page),
45            state_pointer,
46        ),
47        page(
48            crate::wizard::control_ids::IDD_S4B_FETCH,
49            Some(crate::wizard::scene::fetching_page),
50            state_pointer,
51        ),
52        page(
53            crate::wizard::control_ids::IDD_S5_CONFIG,
54            Some(crate::wizard::scene::configuration_page),
55            state_pointer,
56        ),
57        page(
58            crate::wizard::control_ids::IDD_S6_INSTALL,
59            Some(crate::wizard::scene::installation_page),
60            state_pointer,
61        ),
62        page(
63            crate::wizard::control_ids::IDD_S7_SUMMARY,
64            Some(crate::wizard::scene::summary_page),
65            state_pointer,
66        ),
67        // Last, because page order is what Next means: placed where it sits in the
68        // story, Install walked onto it. It is reached and left by id.
69        page(
70            crate::wizard::control_ids::IDD_RESTORE,
71            Some(crate::wizard::scene::restore_page),
72            state_pointer,
73        ),
74    ];
75
76    let caption = crate::i18n::to_wide(&crate::i18n::load_string(crate::i18n::IDS_APP_TITLE));
77    unsafe {
78        let mut header: ctl::PROPSHEETHEADERW_V2 = core::mem::zeroed();
79        header.dwSize = core::mem::size_of::<ctl::PROPSHEETHEADERW_V2>() as u32;
80        header.dwFlags =
81            ctl::PSH_WIZARD | ctl::PSH_PROPSHEETPAGE | ctl::PSH_USEHICON | ctl::PSH_USECALLBACK;
82        header.pfnCallback = Some(on_sheet_event);
83        header.hInstance = crate::i18n::instance();
84        header.pszCaption = caption.as_ptr();
85        header.nPages = pages.len() as u32;
86        header.Anonymous1.hIcon = crate::ui::load_app_icon();
87        header.Anonymous2.nStartPage = start_page;
88        header.Anonymous3.ppsp = pages.as_ptr() as *mut ctl::PROPSHEETPAGEW;
89        ctl::PropertySheetW(&mut header);
90    }
91}
92
93/// Next and Finish belong to the sheet, which exists from here on, so this is
94/// where their painting is hooked in.
95unsafe extern "system" fn on_sheet_event(
96    sheet: windows_sys::Win32::Foundation::HWND,
97    message: u32,
98    _lparam: windows_sys::Win32::Foundation::LPARAM,
99) -> i32 {
100    if message == ctl::PSCB_INITIALIZED {
101        windows_sys::Win32::UI::Shell::SetWindowSubclass(
102            sheet,
103            Some(paint_forward_buttons),
104            SHEET_SUBCLASS,
105            0,
106        );
107    }
108    0
109}
110
111/// Paints Next and Finish green; the sheet's own procedure does everything else.
112unsafe extern "system" fn paint_forward_buttons(
113    sheet: windows_sys::Win32::Foundation::HWND,
114    message: u32,
115    wparam: windows_sys::Win32::Foundation::WPARAM,
116    lparam: windows_sys::Win32::Foundation::LPARAM,
117    _subclass: usize,
118    _reference: usize,
119) -> windows_sys::Win32::Foundation::LRESULT {
120    if message == wm::WM_NOTIFY {
121        let forward = [
122            crate::wizard::page::ID_WIZNEXT,
123            crate::wizard::page::ID_WIZFINISH,
124        ];
125        // Every notification starts with the header; only a custom-draw one is an NMCUSTOMDRAW.
126        let header = &*(lparam as *const ctl::NMHDR);
127        if header.code == ctl::NM_CUSTOMDRAW && forward.contains(&(header.idFrom as u16)) {
128            return crate::ui::paint_forward_button(&*(lparam as *const ctl::NMCUSTOMDRAW));
129        }
130    }
131    windows_sys::Win32::UI::Shell::DefSubclassProc(sheet, message, wparam, lparam)
132}
133
134/// A page from a dialog template in this executable, carrying the shared state as its `lParam`.
135fn page(
136    template: u16,
137    dialog_proc: wm::DLGPROC,
138    state: windows_sys::Win32::Foundation::LPARAM,
139) -> ctl::PROPSHEETPAGEW {
140    unsafe {
141        let mut page: ctl::PROPSHEETPAGEW = core::mem::zeroed();
142        page.dwSize = core::mem::size_of::<ctl::PROPSHEETPAGEW>() as u32;
143        page.dwFlags = ctl::PSP_DEFAULT;
144        page.hInstance = crate::i18n::instance();
145        page.Anonymous1.pszTemplate = crate::ui::make_int_resource(template);
146        page.pfnDlgProc = dialog_proc;
147        page.lParam = state;
148        page
149    }
150}