use super::autostart;
use super::combo::{close_combo, open_combo, ComboKind};
use super::layout::relayout;
use super::pages::ControlId;
use super::{recorder, with_win, Win, TIMER_BANNER, TIMER_CAPTURE, WM_APP_FILE_DIALOG};
use windows_sys::Win32::Foundation::HWND;
use windows_sys::Win32::Graphics::Gdi::InvalidateRect;
use windows_sys::Win32::UI::WindowsAndMessaging::{PostMessageW, SetTimer};
use winspaces_common::i18n::t;
use winspaces_common::{log_info, Msg};
pub(crate) const FILE_DIALOG_EXPORT: usize = 0;
pub(crate) const FILE_DIALOG_IMPORT: usize = 1;
fn config_filter() -> String {
format!(
"{}\0*.json\0{}\0*.*\0\0",
t(Msg::DialogFilterJson),
t(Msg::DialogFilterAll)
)
}
pub(crate) unsafe fn run_file_dialog(hwnd: HWND, kind: usize) {
let import = kind == FILE_DIALOG_IMPORT;
let filter = config_filter();
let picked = if import {
winspaces_win32::dialogs::open_file_dialog(hwnd, &filter, t(Msg::DialogImportTitle))
} else {
winspaces_win32::dialogs::save_file_dialog(
hwnd,
"winspaces-settings.json",
&filter,
t(Msg::DialogExportTitle),
)
};
let Some(path) = picked else {
return;
};
with_win(|win| {
if import {
win.state.import_from_file(&path);
} else {
win.state.export_to_file(&path);
}
unsafe { after_action(win) };
});
}
pub(crate) unsafe fn after_action(win: &mut Win) {
if win.state.banner.is_some() {
SetTimer(win.hwnd, TIMER_BANNER, 4000, None);
}
relayout(win);
InvalidateRect(win.hwnd, std::ptr::null(), 0);
}
pub(crate) unsafe fn stop_recording(win: &mut Win) {
if win.state.capturing.is_some() {
recorder::stop_capture();
win.state.capturing = None;
InvalidateRect(win.hwnd, std::ptr::null(), 0);
}
}
pub(crate) unsafe fn activate(win: &mut Win, id: ControlId) {
log_info!("Settings: activate {:?}", id);
if win.state.capturing.is_some() && !matches!(id, ControlId::Hotkey(_)) {
stop_recording(win);
}
match id {
ControlId::Nav(page) | ControlId::NavCard(page, _) => {
if win.state.page != page {
win.state.page = page;
win.scroll = 0;
win.hover = None;
relayout(win);
InvalidateRect(win.hwnd, std::ptr::null(), 0);
}
}
ControlId::ToggleShowAll => {
win.state.config.show_all_taskbar = !win.state.config.show_all_taskbar;
win.state.autosave(t(Msg::ReasonTaskbarVisibility));
after_action(win);
}
ControlId::ToggleWinTab => {
win.state.config.intercept_win_tab = !win.state.config.intercept_win_tab;
win.state.autosave(t(Msg::ReasonWinTab));
after_action(win);
}
ControlId::ToggleSpaceIndicator => {
win.state.config.space_indicator = !win.state.config.space_indicator;
win.state.autosave(t(Msg::ReasonIndicator));
after_action(win);
}
ControlId::ToggleAutoRestore => {
win.state.config.auto_restore_workspaces = !win.state.config.auto_restore_workspaces;
win.state.autosave(t(Msg::ReasonAutoRestore));
after_action(win);
}
ControlId::ToggleAutostart => {
win.state.toggle_autostart();
after_action(win);
}
ControlId::ToggleElevated => {
win.state.toggle_elevated();
after_action(win);
}
ControlId::ToggleTiling => {
win.state.config.tiling.enabled = !win.state.config.tiling.enabled;
win.state.autosave(t(Msg::ReasonTilingEnabled));
after_action(win);
}
ControlId::ComboTheme => {
if win
.combo
.as_ref()
.is_some_and(|c| c.kind == ComboKind::Theme)
{
close_combo(win);
} else {
open_combo(win, ComboKind::Theme);
}
}
ControlId::ComboLanguage => {
if win
.combo
.as_ref()
.is_some_and(|c| c.kind == ComboKind::Language)
{
close_combo(win);
} else {
open_combo(win, ComboKind::Language);
}
}
ControlId::ComboInnerGap => {
if win
.combo
.as_ref()
.is_some_and(|c| c.kind == ComboKind::InnerGap)
{
close_combo(win);
} else {
open_combo(win, ComboKind::InnerGap);
}
}
ControlId::ComboOuterGap => {
if win
.combo
.as_ref()
.is_some_and(|c| c.kind == ComboKind::OuterGap)
{
close_combo(win);
} else {
open_combo(win, ComboKind::OuterGap);
}
}
ControlId::BtnReload => {
use std::os::windows::process::CommandExt;
if win.state.daemon_running {
if let Ok(exe) = std::env::current_exe() {
let _ = std::process::Command::new(&exe)
.arg("--restart")
.creation_flags(0x08000000)
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn();
}
std::thread::sleep(std::time::Duration::from_millis(600));
win.state.refresh_daemon_status();
win.state.show_banner(
t(Msg::BannerDaemonRestartedTitle),
t(Msg::BannerDaemonRestartedMessage),
true,
);
} else {
let started = if win.state.elevated_mode {
std::process::Command::new("schtasks.exe")
.args(["/run", "/tn", autostart::ELEVATED_TASK_NAME])
.creation_flags(0x08000000)
.output()
.map(|o| o.status.success())
.unwrap_or(false)
} else {
false
};
if !started {
if let Ok(exe) = std::env::current_exe() {
let _ = std::process::Command::new(&exe)
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn();
}
}
std::thread::sleep(std::time::Duration::from_millis(500));
win.state.refresh_daemon_status();
win.state.show_banner(
t(Msg::BannerDaemonStartedTitle),
t(Msg::BannerDaemonStartedMessage),
true,
);
}
after_action(win);
}
ControlId::BtnCapture => {
if win.state.request_capture() {
SetTimer(win.hwnd, TIMER_CAPTURE, 300, None);
} else {
win.state.show_banner(
t(Msg::BannerDaemonMissingTitle),
t(Msg::BannerDaemonMissingCapture),
false,
);
after_action(win);
}
}
ControlId::BtnRestore => {
win.state.request_restore();
after_action(win);
}
ControlId::BtnReset => {
win.state.reset_defaults();
after_action(win);
}
ControlId::BtnExport => {
PostMessageW(win.hwnd, WM_APP_FILE_DIALOG, FILE_DIALOG_EXPORT, 0);
}
ControlId::BtnImport => {
PostMessageW(win.hwnd, WM_APP_FILE_DIALOG, FILE_DIALOG_IMPORT, 0);
}
ControlId::Hotkey(target) => {
if win.state.capturing == Some(target) {
stop_recording(win);
} else {
recorder::stop_capture();
win.state.capture_hook = recorder::start_capture(win.hwnd);
win.state.capturing = Some(target);
InvalidateRect(win.hwnd, std::ptr::null(), 0);
}
}
ControlId::RuleDelete(index) => {
win.state.delete_rule(index);
after_action(win);
}
ControlId::FloatRuleDelete(index) => {
win.state.delete_float_rule(index);
after_action(win);
}
}
}
pub(crate) unsafe fn handle_recorder_key(win: &mut Win, vk: u32) {
let Some(target) = win.state.capturing else {
return;
};
match recorder::translate_current(vk) {
recorder::Outcome::Ignore => {}
recorder::Outcome::Cancel => {
stop_recording(win);
}
recorder::Outcome::Commit(hk) => {
recorder::stop_capture();
win.state.capturing = None;
win.state.set_hotkey(target, hk);
after_action(win);
}
}
}