platform_load_platform_information :: () { // load_platform_information_shared(); } platform_get_path_program_data :: inline () -> string { // #NOTE: This is slow due to loading shell32 - see comment attached to get_path_os_program_data(); return copy_string(get_path_os_program_data()); } platform_get_path_config_data :: inline () -> string { return platform_get_path_program_data(); } Platform :: struct { // Windows registered_global_hotkey_ids: [GLOBAL_HOTKEY_ACTION_COUNT * MAX_HOTKEY_BINDINGS] s32; registered_global_hotkey_count: s64; global_hotkeys_failed: string; // the combinations that could not be registered, for the main window; heap tray_icon_configured: bool; // nid and its icon are ready; adding it can still fail while Explorer is not tray_icon_failed: bool; // minimizing then goes to the taskbar; the main window says so hwnd: HWND; // WM_PAINT draws frames with this context while the modal size/move loop blocks the main loop. main_context: *#Context; pumping_messages: bool; in_size_move: bool; in_live_resize_frame: bool; live_resize_frame_drawn: bool; // during this pump; see Windows_Read_Messages swallow_hotkey_character: bool; // see win32_key_down_is_bound resetting_window_placement: bool; pending_dpi_scale: float32; // from WM_DPICHANGED, applied by win32_apply_dpi_change nid: NOTIFYICONDATAW; icon_menu: HMENU; tray_icon: HICON; taskbar_created_message: u32; // broadcast when Explorer restarts, which drops every tray icon all_cpus: []Windows_CPUID; performance_cpus: []Windows_CPUID; efficiency_cpus: []Windows_CPUID; } windows_open_directory :: (path: string) -> bool { push_allocator(temp); quoted_path := windows_quote_argument(path); result := cast(s64)ShellExecuteW(null, utf8_to_wide("open"), utf8_to_wide("explorer.exe"), utf8_to_wide(quoted_path), null, SW_SHOWNORMAL); if (result <= 32) { log_error("[windows_open_directory] ShellExecuteW failed with args `open explorer.exe %`", quoted_path); os_log_error(); return false; } return true; } windows_open_file_in_default_program :: (path: string, working_directory: string, args := "") -> bool { if is_directory(path) { return windows_open_directory(path); } push_allocator(temp); path_copy := copy_string(path,, temp); path_overwrite_separators(path_copy, #char "\\"); working_dir_copy := copy_string(working_directory,, temp); path_overwrite_separators(working_dir_copy, #char "\\"); // We use ShellExecuteExW so we can properly detach from new launched process: full_path_w := utf8_to_wide(path_copy); working_dir_w := utf8_to_wide(working_dir_copy); SEE_MASK_NOCLOSEPROCESS :: 0x00000040; sei: SHELLEXECUTEINFOW; defer CloseHandle(sei.hProcess); sei.cbSize = size_of(SHELLEXECUTEINFOW); sei.fMask = SEE_MASK_NOCLOSEPROCESS; // | SEE_MASK_NO_CONSOLE sei.lpFile = full_path_w; sei.lpDirectory = working_dir_w; sei.nShow = SW_SHOWNORMAL; if args.count sei.lpParameters = utf8_to_wide(args); result := ShellExecuteExW(*sei); if !result { log_error("[windows_open_file_in_default_program] ShellExecuteExW failed with args % % %", path, working_directory, args); os_log_error(); } return cast(bool)result; } // Quote one argument for a Windows command line, by the CommandLineToArgvW rules that // ShellExecuteW's parameter string is parsed with: a run of backslashes is literal except before a // quote, where it escapes. The backslash rule is the one that matters for paths -- without it // "D:\TempLocal\" has its closing quote escaped by the trailing separator. windows_quote_argument :: (s: string) -> string { sb: String_Builder; append(*sb, #char "\""); backslashes := 0; for i: 0..s.count-1 { c := s[i]; if c == #char "\\" { backslashes += 1; continue; } run := ifx c == #char "\"" then backslashes * 2 + 1 else backslashes; for 1..run append(*sb, #char "\\"); backslashes = 0; append(*sb, c); } for 1..backslashes * 2 append(*sb, #char "\\"); append(*sb, #char "\""); return builder_to_string(*sb); } platform_open_file_in_default_program :: (path: string, working_directory: string) { windows_open_file_in_default_program(path, working_directory); } platform_open_directory :: windows_open_directory; // Called every time the application receives a message WndProc :: (hWnd: HWND, message: u32, wParam: WPARAM, lParam: LPARAM) -> LRESULT #c_call { using platform; TEMPORARY_STORAGE_SIZE :: 1000; storage_buf: [TEMPORARY_STORAGE_SIZE] u8; storage: Temporary_Storage; storage.data = storage_buf.data; storage.size = TEMPORARY_STORAGE_SIZE; new_context: #Context; new_context.temporary_storage = *storage; use_app_logger(*new_context); push_context,defer_pop new_context; defer reset_temporary_storage(); // frees any overflow pages; the buffer itself is on the stack push_allocator(temp); if message == { case WM_KEYDOWN; #through; case WM_SYSKEYDOWN; swallow_hotkey_character = win32_key_down_is_bound(reset_window_hotkey, wParam); case WM_KEYUP; #through; case WM_SYSKEYUP; swallow_hotkey_character = false; case WM_CHAR; #through; case WM_SYSCHAR; if swallow_hotkey_character return 0; case WM_DPICHANGED; // The X and Y DPI are always equal for a window. if hWnd == hwnd pending_dpi_scale = cast(float32) (wParam & 0xFFFF) / 96.0; // ImGui would apply the suggested rect, the old size scaled by the DPI ratio, over the // reset's rect, which is already in the new monitor's pixels. if resetting_window_placement return 0; } if (ImGui.ImplWin32_WndProcHandler(hwnd, message, wParam, lParam)) { return cast(LRESULT) true; } if swallow_hotkey_character && (message == WM_KEYDOWN || message == WM_SYSKEYDOWN) return 0; if taskbar_created_message && message == taskbar_created_message { // Without the icon, a window hidden in the tray could only come back by hotkey. if app.minimized_to_tray && !Win32_Show_Tray_Icon() { ShowWindow(hwnd, SW_MINIMIZE); app.minimized_to_tray = false; } return 0; } if message == { case WM_SYSCOMMAND; SC_MINIMIZE :: 0xF020; SC_CLOSE :: 0xF060; // View mode (app/open_file.jai) has no tray: close ends it, and minimize is the system's own. if app.view_mode && (wParam & 0xFFF0) == SC_CLOSE { app.should_exit = true; return 0; } if app.view_mode && (wParam & 0xFFF0) == SC_MINIMIZE return DefWindowProcW(hWnd, message, wParam, lParam); if (wParam & 0xFFF0) == { case SC_CLOSE; #through; case SC_MINIMIZE; app.minimize_to_tray_this_frame = true; case SC_KEYMENU; // A tapped Alt would otherwise enter the system menu's modal loop, which stops frames. case; return DefWindowProcW(hWnd, message, wParam, lParam); } return 0; case WM_SIZE; if (wParam == SIZE_MINIMIZED) { return 0; } g_resizeWidth = cast(u32)LOWORD(lParam); g_resizeHeight = cast(u32)HIWORD(lParam); return 0; case WM_ENTERSIZEMOVE; in_size_move = true; SetTimer(hWnd, LIVE_RESIZE_TIMER_ID, LIVE_RESIZE_TIMER_MS, null); return 0; case WM_EXITSIZEMOVE; in_size_move = false; KillTimer(hWnd, LIVE_RESIZE_TIMER_ID); return 0; case WM_TIMER; if wParam != LIVE_RESIZE_TIMER_ID return DefWindowProcW(hWnd, message, wParam, lParam); if in_size_move InvalidateRect(hWnd, null, .FALSE); return 0; case WM_PAINT; if !in_size_move || !pumping_messages || in_live_resize_frame || !main_context { return DefWindowProcW(hWnd, message, wParam, lParam); } ValidateRect(hWnd, null); in_live_resize_frame = true; live_resize_frame_drawn = true; push_context main_context.* { auto_release_temp(); app_frame(in_live_resize = true); } in_live_resize_frame = false; return 0; case WM_CREATE; icon_menu = CreatePopupMenu(); AppendMenuW(icon_menu, MF_STRING, xx THIS_ICON_CONTEXT_MENU_ITEM_ID, utf8_to_wide("Exit")); return 0; case WM_DESTROY; PostQuitMessage(0); return 0; // Registered to the window rather than the thread: a modal loop (a live resize, the tray menu, // a message box) dispatches window messages but discards the thread's own. case WM_HOTKEY; global_hotkey_fired(cast(s64) wParam); return 0; // wParam is true when the session really ends; Windows may end the app as soon as this returns. case WM_ENDSESSION; if wParam { app_exit_shutdown_run_now(); logger_flush(2000); // close_logger may never run } return 0; case WM_TRAYICON; // Most applications perform actions on button up (after click), but we do // on button down to make it feel as responsive as possible. if lParam == WM_LBUTTONDOWN { #if DEBUG { log("Restoring application"); } app.restore_window_this_frame = true; } if lParam == WM_RBUTTONDOWN { curPos: POINT; GetCursorPos(*curPos); // SetForegroundWindow so the popup shows on top SetForegroundWindow(hwnd); TPM_NONOTIFY :: 0x0080; TPM_RETURNCMD :: 0x0100; clicked := TrackPopupMenu(icon_menu, TPM_RETURNCMD | TPM_NONOTIFY, curPos.x, curPos.y, 0, hwnd, null); if clicked == THIS_ICON_CONTEXT_MENU_ITEM_ID { #if DEBUG { log("Posting Quit Message."); } PostQuitMessage(0); } } return 0; case; return DefWindowProcW(hWnd, message, wParam, lParam); } return 0; } // =-----------PROCEDURES-------------------------------------------------------= // // Registers the [global_hotkeys] bindings, replacing whatever was registered before. Each binding // is registered on its own: Alt+Space is PowerToys Run's default, and losing it must not cost the // others. platform_apply_global_hotkeys :: (actions: [] Hotkey_Action) { using platform; platform_unregister_global_hotkeys(); failed: [..] string; failed.allocator = temp; for action, action_index: actions { for combo, binding_index: array_view(action.hotkey.bindings, 0, action.hotkey.binding_count) { vk := win32_virtual_key(combo.key); if !vk { log_error("[hotkeys] % cannot be a global hotkey (%); its key is not supported.", to_string(combo), action.name); array_add(*failed, to_string(combo)); continue; } modifiers: u32 = MOD_NOREPEAT; if combo.ctrl modifiers |= MOD_CONTROL; if combo.shift modifiers |= MOD_SHIFT; if combo.alt modifiers |= MOD_ALT; id := global_hotkey_id(action_index, binding_index); if !RegisterHotKey(hwnd, id, modifiers, vk) { log_error("[hotkeys] Could not register the global hotkey % (%); another program may be using it.", to_string(combo), action.name); if vk == VK_SNAPSHOT log_error("[hotkeys] PrintScreen is taken by Snagit when it runs, and by Windows when Settings > Accessibility > Keyboard > \"Use the Print screen key to open screen capture\" is on."); array_add(*failed, to_string(combo)); continue; } registered_global_hotkey_ids[registered_global_hotkey_count] = id; registered_global_hotkey_count += 1; log("[hotkeys] Global hotkey % registered (%).", to_string(combo), action.name); } } free(global_hotkeys_failed,, context.default_allocator); global_hotkeys_failed = join(..failed, " and ",, context.default_allocator); } platform_unregister_global_hotkeys :: () { using platform; for 0..registered_global_hotkey_count - 1 UnregisterHotKey(hwnd, registered_global_hotkey_ids[it]); registered_global_hotkey_count = 0; } // The keys a global hotkey may use; 0 for any other. win32_virtual_key :: (key: ImGui.Key) -> u32 { k := cast(s64) key; if k >= cast(s64) ImGui.Key.A && k <= cast(s64) ImGui.Key.Z return cast(u32) (#char "A" + k - cast(s64) ImGui.Key.A); if k >= cast(s64) ImGui.Key._0 && k <= cast(s64) ImGui.Key._9 return cast(u32) (#char "0" + k - cast(s64) ImGui.Key._0); if k >= cast(s64) ImGui.Key.F1 && k <= cast(s64) ImGui.Key.F24 return cast(u32) (VK_F1 + k - cast(s64) ImGui.Key.F1); if key == { case .PrintScreen; return VK_SNAPSHOT; case .Space; return VK_SPACE; case .Enter; return VK_RETURN; case .Tab; return VK_TAB; case .Escape; return VK_ESCAPE; case .LeftArrow; return VK_LEFT; case .RightArrow; return VK_RIGHT; case .UpArrow; return VK_UP; case .DownArrow; return VK_DOWN; case .PageUp; return VK_PRIOR; case .PageDown; return VK_NEXT; case .Home; return VK_HOME; case .End; return VK_END; case .Insert; return VK_INSERT; case .Delete; return VK_DELETE; } return 0; } // Also fails while Explorer is not running, so it is checked on every call. Win32_Show_Tray_Icon :: () -> bool { using platform; if !tray_icon_configured return false; if !cast(bool) Shell_NotifyIconW(NIM_ADD, *nid) { log_error("Shell_NotifyIconW(NIM_ADD) failed; minimizing to the taskbar instead of the tray."); os_log_error(); tray_icon_failed = true; return false; } tray_icon_failed = false; // an earlier failure while Explorer was down has passed return true; } Win32_Hide_Tray_Icon :: () { using platform; if tray_icon_configured Shell_NotifyIconW(NIM_DELETE, *nid); } Win32_Set_Tray_Icon :: () { using platform; taskbar_created_message = RegisterWindowMessageW(utf8_to_wide("TaskbarCreated",, temp)); NIF_MESSAGE :: 0x0001; NIF_ICON :: 0x0002; NIF_TIP :: 0x0004; // Tooltip text must be less than 64 chars for some reason... Tooltip_Text :: APP_TRAY_TOOLTIP_TEXT; Tooltip_Text_u16 := utf8_to_wide(Tooltip_Text,, temp); nid.cbSize = size_of(NOTIFYICONDATAW); nid.hWnd = hwnd; nid.uID = THIS_ICON_ID; // ID of the icon nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; nid.uCallbackMessage = WM_TRAYICON; // Custom message when interacting with the tray icon tray_icon = LoadImageW(null, utf8_to_wide(get_app_tray_icon_path(),, temp), IMAGE_ICON, 0, 0, LR_LOADFROMFILE); if tray_icon == null { log_error("LoadImageW failed for the tray icon; minimizing to the taskbar instead of the tray."); os_log_error(); tray_icon_failed = true; return; } nid.hIcon = tray_icon; memcpy(nid.szTip.data, Tooltip_Text_u16, Tooltip_Text.count * size_of(u16)); // Configured even if this first add fails: at autostart Explorer's notification area may not be // ready yet, and Win32_Show_Tray_Icon adds the icon again on every minimize. tray_icon_configured = true; if !cast(bool) Shell_NotifyIconW(NIM_ADD, *nid) { log_error("Shell_NotifyIconW(NIM_ADD) failed; minimizing to the taskbar until it succeeds."); os_log_error(); tray_icon_failed = true; } } // ImGui rescales the fonts per monitor by itself, but the theme's sizes are baked at one scale, so // they are baked again for the monitor the main window is now on. Must run outside a frame. win32_apply_dpi_change :: () { using platform; scale := pending_dpi_scale; pending_dpi_scale = 0; if scale <= 0 || scale == app.win_scale return; log("[dpi] The window is now on a monitor with DPI scale % (was %); rescaling the layout.", scale, app.win_scale); app.win_scale = scale; app_theme_bake(scale); } // Returns as soon as a message is queued (a hotkey, a tray click, the window being uncovered), or // after timeout_ms. platform_wait_while_hidden :: (timeout_ms: u32) { MsgWaitForMultipleObjectsEx(0, null, timeout_ms, QS_ALLINPUT, MWMO_INPUTAVAILABLE); } platform_minimize_to_tray :: Win32_Minimize_Window_To_Tray; Win32_Minimize_Window_To_Tray :: () { using platform; // Minimize normally if we're not able to show the tray icon. if !Win32_Show_Tray_Icon() { ShowWindow(hwnd, SW_MINIMIZE); return; } ShowWindow(hwnd, SW_HIDE); app.minimized_to_tray = true; } // Win32_Bring_Window_To_Foreground's first step; does nothing unless the window is in the tray. Win32_Restore_Window_From_Tray :: () { using platform; if app.minimized_to_tray { ShowWindow(hwnd, SW_SHOW); app.minimized_to_tray = false; Win32_Hide_Tray_Icon(); } } platform_restore_window :: Win32_Bring_Window_To_Foreground; Win32_Bring_Window_To_Foreground :: () { using platform; #if DEBUG { log("Attempting to bring window to foreground"); } Win32_Restore_Window_From_Tray(); if Win32_Window_Is_Minimized() { ShowWindow(hwnd, SW_RESTORE); } SetForegroundWindow(hwnd); } Win32_Window_Is_Minimized :: () -> bool { return cast(bool) IsIconic(platform.hwnd); } // Returns false when a live-resize frame drew during the pump. That frame rebuilt the per-frame // state (anything app_ui_frame_start builds) in temporary storage it then released, so the calling // frame must not go on to draw with what its own #frameStart built. Windows_Read_Messages :: () -> frame_state_valid: bool { platform.pumping_messages = true; platform.live_resize_frame_drawn = false; defer platform.pumping_messages = false; msg: MSG; while (PeekMessageW(*msg, null, 0, 0, PM_REMOVE)) { TranslateMessage(*msg); DispatchMessageW(*msg); if (msg.message == WM_QUIT) { app.should_exit = true; } } return !platform.live_resize_frame_drawn; } windows_enable_large_pages_if_possible :: () { token: HANDLE = ---; if OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, *token) { luid: u64; if LookupPrivilegeValue(null, SE_LOCK_MEMORY_NAME, *luid) { priv : TOKEN_PRIVILEGES; priv.PrivilegeCount = 1; priv.Privileges[0].Luid = luid; priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // A nonzero return only means the call itself worked, not that the privilege was // granted: an account without SeLockMemoryPrivilege succeeds here and reports // ERROR_NOT_ALL_ASSIGNED. AdjustTokenPrivileges sets the last error to ERROR_SUCCESS // when it assigned everything, so the error code has to be read as well or this flag // claims a privilege the process does not hold. adjusted := AdjustTokenPrivileges(token, 0, *priv, size_of(TOKEN_PRIVILEGES), null, null) != 0; error_code := GetLastError(); app.large_pages_allowed = adjusted && error_code == 0; // The code is logged from the captured value rather than through os_log_error, because // logging itself can reset the thread's last error before it could be read again. if !adjusted { log_error("[windows_enable_large_pages_if_possible] AdjustTokenPrivileges failed with code %: %", error_code, get_error_string(error_code)); } else if error_code == ERROR_NOT_ALL_ASSIGNED { // Granted under secpol.msc > Local Policies > User Rights Assignment > Lock pages in // memory, and it only takes effect after a new logon. Not having it is not an error: // arenas asking for large pages fall back to 4 KB ones. // NOTE: (IMPORTANT) Lock pages in memory (SeLockMemoryPrivilege) is assigned to // accounts/groups, not automatically to everyone who happens to have administrator privileges. log("[windows_enable_large_pages_if_possible] SeLockMemoryPrivilege is not held by this account; large pages are unavailable."); } else if error_code != 0 { log_error("[windows_enable_large_pages_if_possible] AdjustTokenPrivileges reported an unexpected code %: %", error_code, get_error_string(error_code)); } log("app.large_pages_allowed = %", app.large_pages_allowed); } else { log_error("[windows_enable_large_pages_if_possible] LookupPrivilegeValue failed."); } CloseHandle(token); } else { log_error("[windows_enable_large_pages_if_possible] OpenProcessToken failed."); } } platform_set_thread_affinity_all :: #bake_arguments platform_set_thread_affinity(affinity=.All); platform_set_thread_affinity :: (thread: *Thread, affinity: CPUID_Type) { using platform; cpus: []Windows_CPUID; if #complete affinity == { case .All; cpus = all_cpus; case .Performance; cpus = performance_cpus; case .Efficiency; cpus = efficiency_cpus; // case .Top_Performance; cpus = top_cpus; } windows_set_thread_affinity(thread.windows_thread, cpus); } windows_set_thread_affinity :: (thread: HANDLE, cpus: []Windows_CPUID) { if !cpus.count || thread == INVALID_HANDLE_VALUE return; success := SetThreadSelectedCpuSets(thread, cpus.data, xx cpus.count); if !success { log_error("SetThreadSelectedCpuSets failed on thread %!", thread); } } windows_get_cpu_information :: () { push_allocator(context.default_allocator); using platform; all_cpus = array_copy(windows_get_cpu_ids(.All,, temp)); performance_cpus = array_copy(windows_get_cpu_ids(.Performance,, temp)); efficiency_cpus = array_copy(windows_get_cpu_ids(.Efficiency,, temp)); // top_cpus = array_copy(windows_get_cpu_ids(.Top_Performance,, temp)); } Windows_CPUID :: #type,isa u32; windows_get_cpu_ids :: ($type: CPUID_Type) -> []Windows_CPUID { length: DWORD; GetSystemCpuSetInformation(null, 0, *length, GetCurrentProcess(), 0); buffer := NewArray(length, u8,, temp); GetSystemCpuSetInformation(xx buffer.data, length, *length, GetCurrentProcess(), 0); item_count := length / size_of(SYSTEM_CPU_SET_INFORMATION); info_array: []SYSTEM_CPU_SET_INFORMATION = .{item_count, cast(*SYSTEM_CPU_SET_INFORMATION)buffer.data}; core_ids: [..]Windows_CPUID; for 0..info_array.count-1 { // EfficiencyClass == 0 usually means E-Core, and 1 means P-core, 2 means best P-cores (usually 2 CPUs have this value) #if type == .All { type_matches := info_array[it].CpuSet.EfficiencyClass >= 0; } #if type == .Performance { type_matches := info_array[it].CpuSet.EfficiencyClass >= 1; } #if type == .Efficiency { type_matches := info_array[it].CpuSet.EfficiencyClass == 0; } // #if type == .Top_Performance { type_matches := info_array[it].CpuSet.EfficiencyClass == 2; } if info_array[it].Type == .CpuSetInformation && type_matches { cpu := info_array[it].CpuSet; array_add(*core_ids, xx cpu.Id); } } return core_ids; } platform_update_memory_usage_metrics :: () { using perf.memory_usage; pmc: PROCESS_MEMORY_COUNTERS; if (GetProcessMemoryInfo(GetCurrentProcess(), *pmc, size_of(PROCESS_MEMORY_COUNTERS))) { working_set = xx pmc.WorkingSetSize; virtual_memory = xx pmc.PagefileUsage; peak_working_set = xx pmc.PeakWorkingSetSize; peak_virtual_memory = xx pmc.PeakPagefileUsage; } } windows_check_if_we_are_running_as_administrator :: () { token: HANDLE; if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, *token) { elevation: TOKEN_ELEVATION = {}; size: DWORD = size_of(TOKEN_ELEVATION); ok := GetTokenInformation(token, .TokenElevation, *elevation, size_of(TOKEN_ELEVATION), *size); CloseHandle(token); app.is_admin = ok && elevation.TokenIsElevated; } } // %NAME% expanded as cmd does. The path is returned as is when it has none or expansion fails. Temp. windows_expand_environment :: (path: string) -> string { if !contains(path, "%") return path; ExpandEnvironmentStringsW :: (lpSrc: LPCWSTR, lpDst: LPWSTR, nSize: DWORD) -> DWORD #foreign kernel32; buffer: [4096] u16; count := ExpandEnvironmentStringsW(utf8_to_wide(path,, temp), buffer.data, buffer.count); if count == 0 || count > buffer.count return path; expanded, ok := wide_to_utf8(buffer.data,, temp); return ifx ok then expanded else path; } // A BCP 47 tag such as ja-JP, or "". Temp. platform_preferred_language :: () -> string { buffer: [LOCALE_NAME_MAX_LENGTH] u16; if GetUserDefaultLocaleName(buffer.data, buffer.count) <= 1 return ""; name, ok := wide_to_utf8(buffer.data,, temp); return ifx ok then name else ""; } // Temp. platform_cjk_font_candidates :: () -> [] Cjk_Font { fonts := windows_expand_environment("%WINDIR%\\Fonts"); list: [..] Cjk_Font; list.allocator = temp; cjk_candidate(*list, .SIMPLIFIED, tprint("%\\msyh.ttc", fonts), "Microsoft YaHei"); cjk_candidate(*list, .TRADITIONAL, tprint("%\\msjh.ttc", fonts), "Microsoft JhengHei"); cjk_candidate(*list, .JAPANESE, tprint("%\\YuGothM.ttc", fonts), "Yu Gothic"); cjk_candidate(*list, .JAPANESE, tprint("%\\meiryo.ttc", fonts), "Meiryo"); cjk_candidate(*list, .KOREAN, tprint("%\\malgun.ttf", fonts), "Malgun Gothic"); return list; } // Read-only for the life of the process; pages are read from the file only as they are touched. platform_map_file_read_only :: (path: string) -> (data: string, ok: bool) { file, opened := file_open(path); if !opened return "", false; defer file_close(*file); size, size_ok := file_length(file); if !size_ok || size <= 0 return "", false; mapping := CreateFileMappingW(file.handle, null, PAGE_READONLY, 0, 0, null); if !mapping return "", false; defer CloseHandle(mapping); // the view keeps the mapping alive view := MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0); if !view return "", false; return string.{size, cast(*u8) view}, true; } // Drops what the system's file cache holds of the file, so the next read comes from the disk (-cold). // Opening a file unbuffered makes the cache manager flush and purge its pages, when nothing has the // file mapped. False when it cannot be opened. platform_evict_file_cache :: (path: string) -> bool { FILE_FLAG_NO_BUFFERING :: 0x20000000; wide := utf8_to_wide(path,, temp); handle := CreateFileW(wide, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, null, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING); if handle == INVALID_HANDLE_VALUE return false; CloseHandle(handle); return true; } // "" when the variable is unset or empty. In the current allocator. windows_environment_variable :: (name: string) -> string { name_w := utf8_to_wide(name,, temp); length := GetEnvironmentVariableW(name_w, null, 0); if !length return ""; buffer := NewArray(length, u16,, temp); written := GetEnvironmentVariableW(name_w, buffer.data, length); if !written || written >= length return ""; value, ok := wide_to_utf8(buffer.data, cast(s32) written); return ifx ok then value else ""; } platform_get_centered_window_dimensions :: (open_on_biggest: bool) -> s32, s32, s32, s32 { if !monitors_enumerated { enumerate_monitors(); monitors_enumerated = true; } assert(monitors.count > 0, "Unexpectedly no monitors found. This is a bug."); monitor := monitors[0]; if open_on_biggest { max_area := 0; for monitors { width := it.right - it.left; height := it.bottom - it.top; area := width * height; if max_area < area { monitor = it; max_area = area; } else if max_area == area && it.primary { monitor = it; // prefer primary if there's no biggest monitor } } } else { for monitors { if it.primary { monitor = it; } } } monitor_width := monitor.right - monitor.left; monitor_height := monitor.bottom - monitor.top; // aspect_ratio := monitor_width / monitor_height; w := cast(s32) (monitor_width * app_user_settings.window_width_fraction); h := cast(s32) (monitor_height * app_user_settings.window_height_fraction); x := cast(s32) (monitor.left + monitor_width / 2 - w / 2); y := cast(s32) (monitor.top + monitor_height / 2 - h / 2); return x, y, w, h; } // Back to the size and place the window starts at. platform_reset_window_placement :: () -> bool { using platform; // Displays may have been attached, removed or rearranged since startup. monitors_enumerated = false; monitor_index = 0; for *monitors it.* = .{}; x, y, w, h := platform_get_centered_window_dimensions(open_on_biggest=true); if IsZoomed(hwnd) != .FALSE ShowWindow(hwnd, SW_RESTORE); // otherwise it stays maximized at the new place resetting_window_placement = true; placed := SetWindowPos(hwnd, null, x, y, w, h, SWP_NOZORDER | SWP_NOACTIVATE) != .FALSE; resetting_window_placement = false; if !placed { log_error("SetWindowPos failed; the window was not reset."); os_log_error(); return false; } log("Window reset: x: %, y: %, size: %x% px", x, y, w, h); return true; } // A key combination that types a character, such as Ctrl+Alt+Space, would also type it into the // focused text field, and Alt+Space opens the window menu, so a bound key's character is dropped. win32_key_down_is_bound :: (hotkey: Hotkey, virtual_key: WPARAM) -> bool { key: ImGui.Key; if virtual_key == VK_SPACE key = .Space; else if virtual_key >= #char "A" && virtual_key <= #char "Z" key = cast(ImGui.Key) (cast(s64) ImGui.Key.A + cast(s64) (virtual_key - #char "A")); else if virtual_key >= #char "0" && virtual_key <= #char "9" key = cast(ImGui.Key) (cast(s64) ImGui.Key._0 + cast(s64) (virtual_key - #char "0")); else return false; ctrl := GetKeyState(VK_CONTROL) < 0; shift := GetKeyState(VK_SHIFT) < 0; alt := GetKeyState(VK_MENU) < 0; for array_view(hotkey.bindings, 0, hotkey.binding_count) { if it.key == key && it.ctrl == ctrl && it.shift == shift && it.alt == alt return true; } return false; } get_primary_monitor :: () -> *Monitor { if !monitors_enumerated { enumerate_monitors(); monitors_enumerated = true; } for 0..monitors.count-1 { if monitors[it].primary return *monitors[it]; } return null; } // A monitor unplugged mid-enumeration fails GetMonitorInfoW; it is skipped, and only no monitors at // all is fatal. enumerate_monitors :: () { EnumDisplayMonitors(null, null, monitor_enum_proc, 0); if monitor_index == 0 panic("Couldn't enumerate monitors"); } monitor_enum_proc :: (hMonitor: HMONITOR, hdc: HDC, rect: *RECT, data: LPARAM) -> BOOL #c_call { using monitor : Monitor = ---; monitor.left = rect.left; monitor.top = rect.top; monitor.right = rect.right; monitor.bottom = rect.bottom; monitorInfo.cbSize = size_of(MONITORINFO); success := GetMonitorInfoW(hMonitor, *monitorInfo) != 0; new_context : #Context; use_app_logger(*new_context); push_context new_context { if !success { log_error("[monitor_enum_proc] GetMonitorInfoW failed, monitor info unavailable; skipping that monitor."); return xx true; } monitor.primary = !!(monitorInfo.dwFlags & 0x1); monitor.present = true; if monitor_index >= monitors.count { log_error("[monitor_enum_proc] More than % monitors; ignoring the rest.", monitors.count); return xx true; } monitors[monitor_index] = monitor; monitor_index += 1; } return xx true; } // =-----------DATA-INTERNAL----------------------------------------------------= // Monitors_Count :: () -> int { count:=0; for monitors { count += xx it.present; } return count; } monitors_enumerated : bool; monitors: [8]Monitor; monitor_index: int = 0; Monitor :: struct { monitorInfo: MONITORINFO; left : int; top : int; right : int; bottom : int; primary: bool; present: bool; }; // =-----------CONSTANTS--------------------------------------------------------= // THIS_ICON_ID :: 5001; THIS_ICON_CONTEXT_MENU_ITEM_ID :: 5010; LIVE_RESIZE_TIMER_ID :: 1; // Windows raises anything below USER_TIMER_MINIMUM (10 ms) to it; Present's vsync paces the frames. LIVE_RESIZE_TIMER_MS :: 1; os_clear_unused_pages :: () { SetProcessWorkingSetSize(GetCurrentProcess(), cast,no_check(SIZE_T)-1, cast,no_check(SIZE_T)-1); log("[clear_unused_pages] Clearing unused pages.\n"); } os_log_error :: () { error_code := GetLastError(); log_error(" > GetLastError code: %, %", error_code, get_error_string(error_code)); } // Asks for a folder with Explorer's own picker, over the app's window: its path (temp), or "" when // none was chosen. windows_choose_folder :: (title: string) -> string { initialized := CoInitializeEx(null, COINIT_APARTMENTTHREADED); defer if initialized >= 0 CoUninitialize(); dialog: *IFileOpenDialog; if CoCreateInstance(*uid(CLSID_FileOpenDialog_UUID), null, CLSCTX_INPROC_SERVER, *uid(IFileOpenDialog_UUID), xx *dialog) < 0 return ""; defer IUnknown_Release(dialog); options: u32; dialog.GetOptions(dialog, *options); dialog.SetOptions(dialog, options | FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM); wide_title := utf8_to_wide(title,, temp); dialog.SetTitle(dialog, wide_title); if dialog.Show(dialog, platform.hwnd) < 0 return ""; // cancelled item: *IShellItem; if dialog.GetResult(dialog, *item) < 0 return ""; defer IUnknown_Release(item); name: *u16; if item.GetDisplayName(item, SIGDN_FILESYSPATH, *name) < 0 return ""; defer CoTaskMemFree(name); path, ok := wide_to_utf8(name,, temp); return ifx ok then path else ""; }