// #TODO: #OS_Win32 // [ ] #Exception handling code in `Win32_Exception_Filter` // [~] #Thread cleanup: in `thread_deinit` is there any requirement to cleanup child threads? // - I think no? Threads should handle their own lifetimes, and the parent threads should ensure child threads are complete // before terminating. constexpr s64 FILETIME_TO_UNIX = 116444736000000000i64; f64 GetUnixTimestamp () { FILETIME fileTime; GetSystemTimePreciseAsFileTime(&fileTime); s64 ticks = ((s64)fileTime.dwHighDateTime << (s64)32) | (s64)fileTime.dwLowDateTime; return (ticks - FILETIME_TO_UNIX) / (10.0 * 1000.0 * 1000.0); } s64 GetUnixTimestampNanoseconds () { FILETIME fileTime; GetSystemTimePreciseAsFileTime(&fileTime); s64 ticks = ((s64)fileTime.dwHighDateTime << (s64)32) | (s64)fileTime.dwLowDateTime; // in 100ns ticks s64 unix_time = (ticks - FILETIME_TO_UNIX); // in 100ns ticks s64 unix_time_nanoseconds = unix_time * 100; return unix_time_nanoseconds; } u64 FILETIME_to_ticks (FILETIME fileTime) { u64 ticks = ((u64)fileTime.dwHighDateTime << (u64)32) | (u64)fileTime.dwLowDateTime; // in 100ns ticks return ticks; } string format_time_datetime (FILETIME ft) { SYSTEMTIME stUTC, st; FileTimeToSystemTime(&ft, &stUTC); SystemTimeToTzSpecificLocalTime(nullptr, &stUTC, &st); // YYYY-MM-DD- return format_string("%04u-%02u-%02u %02u:%02u:%02u.%03u", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds); } struct OS_System_Info { // #cpuid s32 logical_processor_count; s32 physical_core_count; s32 primary_core_count; s32 secondary_core_count; // Any weaker or "Efficiency" cores. u64 page_size; u64 large_page_size; u64 allocation_granularity; string machine_name; // #Monitors b32 monitors_enumerated; Array monitors; // Back with GPAllocator // #Drives Table drives; // should we just store ptrs to OS_Drive? I think so.. // That way we can fetch the OS_Drive* and have the pointer be stable. Especially if it's b // backed by an arena. }; struct OS_Process_Info { u32 process_id; b32 large_pages_allowed; string binary_path; string working_path; string user_program_data_path; Array module_load_paths; Array environment_paths; b32 window_class_initialized; Arena* event_arena; Array windows; }; struct OS_State_Win32 { Arena* arena; OS_System_Info system_info; OS_Process_Info process_info; }; global OS_State_Win32 global_win32_state; internal b32 global_win32_is_quiet = 0; // No console output (`quiet` flag passed) Table* get_drive_table () { return &global_win32_state.system_info.drives; } ArrayView os_get_available_drives () { // @Allocates: Recommended to set context allocator to temp(). auto drive_table = get_drive_table(); Array drives; // #hash_table_iterator : instead of writing this everywhere, just use this function // to get an Array of drives. for (s64 i = 0; i < drive_table->allocated; i += 1) { Table_Entry* entry = &drive_table->entries[i]; // we should take ptr here if we want to modify? if (entry->hash > HASH_TABLE_FIRST_VALID_HASH) { if (entry->value->label.data == nullptr || !entry->value->is_present) continue; // Some volumes may not be real and therefore have no label. array_add(drives, entry->value); } } return drives; } OS_Drive* get_drive_from_label (string drive_label) { // #TODO: Validate the input is in the correct format. We're looking for something like `C:\` // not just the drive letter! Table* drive_table = get_drive_table(); OS_Drive** drive_ptr = table_find_pointer(drive_table, drive_label); Assert(drive_ptr != nullptr); return *drive_ptr; } internal LONG WINAPI Win32_Exception_Filter (EXCEPTION_POINTERS* exception_ptrs) { if (global_win32_is_quiet) { ExitProcess(1); } local_persist volatile LONG first = 0; if(InterlockedCompareExchange(&first, 1, 0) != 0) { // prevent failures in other threads to popup same message box // this handler just shows first thread that crashes // we are terminating afterwards anyway for (;;) Sleep(1000); } // #Exception handling code (TODO) ExitProcess(1); return 0; } // internal void Main_Entry_Point (int argc, WCHAR **argv); internal void Win32_Entry_Point (int argc, WCHAR **argv) { // Timed_Block_Print("Win32_Entry_Point"); // See: w32_entry_point_caller(); (raddebugger) SetUnhandledExceptionFilter(&Win32_Exception_Filter); SYSTEM_INFO sysinfo = {0}; GetSystemInfo(&sysinfo); // Try to allow large pages if we can. // b32 large_pages_allowed = 0; // { // HANDLE token; // if(OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) // { // LUID luid; // if(LookupPrivilegeValue(0, SE_LOCK_MEMORY_NAME, &luid)) // { // TOKEN_PRIVILEGES priv; // priv.PrivilegeCount = 1; // priv.Privileges[0].Luid = luid; // priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // large_pages_allowed = !!AdjustTokenPrivileges(token, 0, &priv, sizeof(priv), 0, 0); // } // CloseHandle(token); // } // } push_arena(thread_context()->arena); { OS_System_Info* info = &global_win32_state.system_info; info->logical_processor_count = (s32)sysinfo.dwNumberOfProcessors; info->page_size = sysinfo.dwPageSize; info->large_page_size = GetLargePageMinimum(); info->allocation_granularity = sysinfo.dwAllocationGranularity; } { OS_Process_Info* info = &global_win32_state.process_info; info->large_pages_allowed = false; info->process_id = GetCurrentProcessId(); } // #cpuid { OS_System_Info* info = &global_win32_state.system_info; u32 length = 0; GetLogicalProcessorInformationEx(RelationProcessorCore, nullptr, (PDWORD)&length); u8* cpu_information_buffer = NewArray(length); GetLogicalProcessorInformationEx(RelationProcessorCore, // *sigh* (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)cpu_information_buffer, (PDWORD)&length); u32 offset = 0; u32 all_cpus_count = 0; // all logical cpus u32 physical_cpu_count = 0; u32 max_performance = 0; u32 performance_core_count = 0; while (offset < length) { SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* cpu_information = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)(cpu_information_buffer + offset); offset += cpu_information->Size; u32 count_per_group_physical = 1; u32 value = (u32)cpu_information->Processor.GroupMask->Mask; u32 count_per_group = __popcnt(value); // logical if (cpu_information->Relationship != RelationProcessorCore) continue; if (cpu_information->Processor.EfficiencyClass > max_performance) { max_performance = cpu_information->Processor.EfficiencyClass; performance_core_count = count_per_group_physical; } else if (cpu_information->Processor.EfficiencyClass == max_performance) { performance_core_count += count_per_group_physical; } physical_cpu_count += count_per_group_physical; all_cpus_count += count_per_group; } info->physical_core_count = (s32)physical_cpu_count; info->primary_core_count = (s32)performance_core_count; info->secondary_core_count = info->physical_core_count - info->primary_core_count; } { OS_System_Info* info = &global_win32_state.system_info; info->monitors.allocator = GPAllocator(); u8 buffer[MAX_COMPUTERNAME_LENGTH + 1] = {0}; DWORD size = MAX_COMPUTERNAME_LENGTH + 1; if(GetComputerNameA((char*)buffer, &size)) { string machine_name_temp = string(size, buffer); info->machine_name = copy_string(machine_name_temp); } } { OS_Process_Info* info = &global_win32_state.process_info; info->windows.allocator = GPAllocator(); DWORD length = GetCurrentDirectoryW(0, 0); // This can be freed later when we call temp_reset(); u16* memory = NewArray(temp(), length + 1); length = GetCurrentDirectoryW(length + 1, (WCHAR*)memory); info->working_path = wide_to_utf8(memory, length); Assert(is_valid(info->working_path)); } // Setup event arena, allocators for Array<> types. if (!global_win32_state.process_info.event_arena) { global_win32_state.process_info.event_arena = next_arena(Arena_Reserve::Size_64K); } // [ ] Get Working directory (info->working_path) // [ ] GetEnvironmentStringsW temp_reset(); printf("Hello there!\n\n"); } C_LINKAGE DWORD OS_Windows_Thread_Entry_Point (void* parameter) { Thread* thread = (Thread*)parameter; set_thread_context(thread->context); DWORD result = (DWORD)thread->proc(thread); return result; } // Individual Thread API #define thread_task(T) (T*)thread->data; internal bool thread_init (Thread* thread, Thread_Proc proc, string thread_name) { Assert(thread != nullptr && proc != nullptr); DWORD windows_thread_id = 0; HANDLE windows_thread = CreateThread(nullptr, 0, OS_Windows_Thread_Entry_Point, thread, CREATE_SUSPENDED, &windows_thread_id); if (windows_thread == 0 || windows_thread == INVALID_HANDLE_VALUE) { return false; } s64 this_thread_index = InterlockedIncrement(&next_thread_index); // 2. #NewContext Setup NEW thread local context ExpandableArena* arena_ex = expandable_arena_new(Arena_Reserve::Size_64M, 16); push_arena(arena_ex); // #NOTE: we don't assign thread_local_context until we hit the #thread_entry_point thread->context = New(); thread->context->temp = expandable_arena_new(Arena_Reserve::Size_2M, 16); thread->context->arena = arena_ex; thread->context->allocator = allocator(arena_ex); thread->context->thread_idx = (s32)this_thread_index; // #NOTE: This will disappear once the thread is de-initted. If we want this string, copy it! thread->context->thread_name = copy_string(thread_name); thread->context->log_builder = new_string_builder(Arena_Reserve::Size_64M); thread->context->error_arena = next_arena(Arena_Reserve::Size_64M); thread->context->logger = {default_logger_proc, &default_logger}; thread->context->parent_thread_context = thread_context(); thread->os_thread.windows_thread = windows_thread; thread->os_thread.windows_thread_id = windows_thread_id; thread->proc = proc; thread->index = this_thread_index; thread_context()->child_threads.allocator = allocator(thread_context()->arena); array_add(thread_context()->child_threads, thread); return true; } internal void thread_deinit (Thread* thread,bool zero_thread) { // Move errors from thread to parent thread push_errors_to_parent_thread(thread->context); if (thread->os_thread.windows_thread) { CloseHandle(thread->os_thread.windows_thread); thread->os_thread.windows_thread = nullptr; } array_reset(*thread->context->log_builder); free_string_builder(thread->context->log_builder); release_arena(thread->context->error_arena); arena_delete(thread->context->temp); arena_delete(thread->context->arena); // must come last because thread->context is allocated with this arena! if (zero_thread) memset(thread, 0, sizeof(Thread)); } internal void thread_start (Thread* thread, void* thread_data) { if (thread_data) thread->data = thread_data; ResumeThread(thread->os_thread.windows_thread); } internal bool thread_is_done (Thread* thread, s32 milliseconds) { Assert(milliseconds >= -1); DWORD result = WaitForSingleObject(thread->os_thread.windows_thread, (DWORD)milliseconds); return result != WAIT_TIMEOUT; } // #thread_primitives internal void mutex_init (Mutex* mutex) { InitializeCriticalSection(&mutex->csection); } internal void mutex_destroy (Mutex* mutex) { DeleteCriticalSection(&mutex->csection); } internal void lock (Mutex* mutex) { EnterCriticalSection(&mutex->csection); } internal void unlock (Mutex* mutex) { LeaveCriticalSection(&mutex->csection); } internal void semaphore_init (Semaphore* sem, s32 initial_value) { Assert(initial_value >= 0); sem->event = CreateSemaphoreW(nullptr, initial_value, 0x7fffffff, nullptr); } internal void semaphore_destroy (Semaphore* sem) { CloseHandle(sem->event); } internal void signal (Semaphore* sem) { ReleaseSemaphore(sem->event, 1, nullptr); } internal Wait_For_Result wait_for (Semaphore* sem, s32 milliseconds) { DWORD res = 0; if (milliseconds < 0) { res = WaitForSingleObject(sem->event, INFINITE); } else { res = WaitForSingleObject(sem->event, (u32)milliseconds); } Assert(res != WAIT_FAILED); if (res == WAIT_OBJECT_0) return Wait_For_Result::SUCCESS; if (res == WAIT_TIMEOUT) return Wait_For_Result::TIMEOUT; return Wait_For_Result::ERROR; } internal void condition_variable_init (Condition_Variable* cv) { InitializeConditionVariable(&cv->condition_variable); } internal void condition_variable_destroy (Condition_Variable* cv) { // No action required. } internal void wait (Condition_Variable* cv, Mutex* mutex, s32 wait_time_ms) { SleepConditionVariableCS(&cv->condition_variable, &mutex->csection, (DWORD)wait_time_ms); } internal void wake (Condition_Variable* cv) { WakeConditionVariable(&cv->condition_variable); } internal void wake_all (Condition_Variable* cv) { WakeAllConditionVariable(&cv->condition_variable); } internal string get_error_string (OS_Error_Code error_code) { u16* lpMsgBuf; bool success = (bool)FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPWSTR)&lpMsgBuf, 0, nullptr); if (!success) { return ""; } push_allocator(temp()); string result = wide_to_utf8(lpMsgBuf); LocalFree(lpMsgBuf); return trim_right(result); } internal void os_log_error () { OS_Error_Code error_code = GetLastError(); log_error(" > GetLastError code: %d, %s", error_code, get_error_string(error_code).data); } internal bool file_is_valid (File file) { if (file.handle == INVALID_HANDLE_VALUE) return false; if (file.handle == 0) return false; return true; } internal File file_open (string file_path, bool for_writing, bool keep_existing_content, bool log_errors) { HANDLE handle; push_allocator(temp()); // for utf8 -> wide conversions: if (for_writing) { u32 creation = (keep_existing_content) ? OPEN_ALWAYS : CREATE_ALWAYS; handle = CreateFileW( (LPCWSTR)utf8_to_wide(file_path).data, FILE_GENERIC_READ | FILE_GENERIC_WRITE, FILE_SHARE_READ, nullptr, creation, 0, nullptr); } else { u32 creation = OPEN_EXISTING; handle = CreateFileW( (LPCWSTR)utf8_to_wide(file_path).data, FILE_GENERIC_READ, FILE_SHARE_READ, nullptr, creation, 0, nullptr); } if (handle == INVALID_HANDLE_VALUE && log_errors) { // OS_Error_Code error_code = GetLastError(); log_error("Could not open file `%s`", file_path); os_log_error(); } File file; file.handle = handle; return file; } internal void file_close (File* file) { CloseHandle(file->handle); } internal bool file_read (File file, u8* data, s64 bytes_to_read_count, s64* bytes_read_count) { // ignore bytes_read_count if null. if (data == nullptr) { log_error("file_read called with null destination pointer."); os_log_error(); if (bytes_read_count) (*bytes_read_count) = 0; return false; } if (bytes_to_read_count <= 0) { if (bytes_read_count) (*bytes_read_count) = 0; return false; } bool read_success = false; s64 total_read = 0; // loop to read more data than can be specified by the DWORD param ReadFile takes: while (total_read < bytes_to_read_count) { s64 remaining = bytes_to_read_count - total_read; DWORD to_read; if (remaining <= 0x7FFFFFFF) { to_read = (DWORD)remaining; } else { to_read = 0x7FFFFFFF; // 2147483647 bytes ~2GB } DWORD single_read_length = 0; read_success = (bool)ReadFile(file.handle, data + total_read, to_read, &single_read_length, nullptr); total_read += single_read_length; if (!read_success || single_read_length == 0) { break; } } if (bytes_read_count) (*bytes_read_count) = total_read; return read_success; } internal bool file_length (File file, s64* length) { if (length == nullptr) { log_error("Calling file_length with null `length` param!"); return false; } if (!file_is_valid(file)) { return false; } s64 size; bool success = (bool)GetFileSizeEx(file.handle, (PLARGE_INTEGER)&size); (*length) = size; return true; } internal bool file_length (string file_path, s64* length) { if (length == nullptr) { log_error("Calling file_length with null `length` param!"); return false; } File f = file_open(file_path); if (!file_is_valid(f)) { return false; } bool success = file_length(f, length); file_close(&f); return success; } internal s64 file_current_position (File file) { constexpr s64 invalid_file_position = -1; if (!file_is_valid(file)) { return invalid_file_position; } s64 offset = 0; LARGE_INTEGER liDistanceToMove; bool result = (bool)SetFilePointerEx(file.handle, liDistanceToMove, (PLARGE_INTEGER)&offset, FILE_CURRENT); if (!result) { return invalid_file_position; } return (s64)offset; } internal bool file_set_position (File file, s64 position) { if (!file_is_valid(file)) { return false; } if (position < 0) { Assert(false); return false; } LARGE_INTEGER position_li; position_li.QuadPart = position; return (bool)SetFilePointerEx(file.handle, position_li, nullptr, FILE_BEGIN); } internal ArrayView read_entire_file (File file) { ArrayView file_data; bool result = file_length(file, &file_data.count); if (!result) return {}; result = file_set_position(file, 0); if (!result) return {}; file_data.data = NewArray(file_data.count, false); if (file_data.data == nullptr) return {}; s64 bytes_read = 0; result = file_read(file, file_data.data, file_data.count, &bytes_read); if (!result) { array_free(file_data); return {}; } Assert(bytes_read == file_data.count); file_data.count = bytes_read; return file_data; } internal ArrayView read_entire_file (string file_path, bool log_errors) { File f = file_open(file_path, false, false, log_errors); if (!file_is_valid(f)) return {}; ArrayView file_data = read_entire_file(f); file_close(&f); return file_data; } internal bool file_write (File* file, void* data, s64 length) { // @incomplete - deal with inputs > 32 bits (>2GB) u32 length_u32 = (u32)length; Assert(length == length_u32); u32 bytes_written; bool result = (bool)WriteFile(file->handle, data, length_u32, (LPDWORD)&bytes_written, nullptr); return result; } force_inline bool file_write (File* file, ArrayView view) { return file_write(file, view.data, view.count); } internal bool write_entire_file (string file_path, void* file_data, s64 count) { File f = file_open(file_path, true, false); if (!file_is_valid(f)) return false; bool result = file_write(&f, file_data, count); file_close(&f); return result; } internal bool write_entire_file (string file_path, ArrayView file_data) { return write_entire_file(file_path, file_data.data, file_data.count); } internal LRESULT // see os_w32_wnd_proc from raddebugger. win32_wnd_proc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { LRESULT result = 0; bool good = true; Arena* event_arena = global_win32_state.process_info.event_arena; Assert(event_arena != nullptr); switch (uMsg) { default: { result = DefWindowProcW(hwnd, uMsg, wParam, lParam); } break; case WM_CLOSE: { ExitProcess(0); // #temp. } break; } return result; } internal bool file_exists (string file_path) { push_allocator(temp()); DWORD result = GetFileAttributesW((LPCWSTR)utf8_to_wide(file_path).data); return (result != INVALID_FILE_ATTRIBUTES); } internal BOOL monitor_enum_proc (HMONITOR hMonitor, HDC hdc, RECT* rect, LPARAM data) { Monitor monitor = {}; monitor.left = rect->left; monitor.top = rect->top; monitor.right = rect->right; monitor.bottom = rect->bottom; monitor.monitor_info.cbSize = sizeof(MONITORINFO); GetMonitorInfoW(hMonitor, &monitor.monitor_info); monitor.primary = !!(monitor.monitor_info.dwFlags & 0x1); monitor.present = true; array_add(global_win32_state.system_info.monitors, monitor); return true; } // #TODO(Low Priority): how do I setup a callback if monitors configuration is changed? // we handle WM_DISPLAYCHANGE or WM_DEVICECHANGE and call EnumDisplayMonitors again. internal void os_enumerate_monitors () { if (!global_win32_state.system_info.monitors_enumerated) { // should reset array? if (!EnumDisplayMonitors(nullptr, nullptr, monitor_enum_proc, 0)) { log_fatal_error("Failed to enumerate monitors\n"); Assert(false); // Failed to enumerate monitors ExitProcess(1); } global_win32_state.system_info.monitors_enumerated = true; } } Window_Dimensions platform_get_centered_window_dimensions (bool open_on_largest_monitor=false) { os_enumerate_monitors(); Assert(global_win32_state.system_info.monitors.count > 0); // must have at least 1 monitor! Array monitors = global_win32_state.system_info.monitors; Monitor monitor = monitors[0]; if (open_on_largest_monitor) { s64 max_area = 0; for (s64 i = 0; i < monitors.count; i += 1) { s64 width = monitors[i].right - monitors[i].left; s64 height = monitors[i].bottom - monitors[i].top; s64 area = width * height; if (max_area < area) { monitor = monitors[i]; max_area = area; } else if (max_area == area && monitors[i].primary) { // if monitors are the same dimension, then just use primary monitor monitor = monitors[i]; } } } else { // Opens on whatever monitor is marked as "primary." for (s64 i = 0; i < monitors.count; i += 1) { if (monitors[i].primary) { monitor = monitors[i]; break; } } } s64 monitor_width = monitor.right - monitor.left; s64 monitor_height = monitor.bottom - monitor.top; s64 window_width = (s64)((f64)monitor_width / 1.125); s64 window_height = (s64)((f64)monitor_height / 1.25); s64 window_x = (monitor.left + (monitor_width / 2) - (window_width / 2)); s64 window_y = (monitor.top + (monitor_height / 2) - (window_height / 2)); Window_Dimensions dimensions = { (s32)window_x, (s32)window_y, (s32)window_width, (s32)window_height, }; return dimensions; } bool Win32_Set_Main_Icon () { Window_Info* info = get_main_window_pointer(); if (info->icon) { HICON old_icon = (HICON)SendMessage(info->window, WM_SETICON, ICON_BIG, (LPARAM)info->icon); if (old_icon) DestroyIcon(old_icon); return true; } return false; } #define ICON_CONTEXT_MENU_ITEM_ID 5011 #define MAIN_WINDOW_TRAY_ICON_ID 5001 #define WM_TRAYICON WM_USER + 1 // our own value to identify when receiving a message. bool Win32_Set_Tray_Icon (string tooltip_text) { Window_Info* info = get_main_window_pointer(); if (info->icon_minimized) { push_allocator(temp()); wstring tooltip_text_wide = utf8_to_wide(tooltip_text); Assert(tooltip_text_wide.count < 128); info->nid.cbSize = sizeof(NOTIFYICONDATAW); info->nid.hWnd = info->window; info->nid.uID = MAIN_WINDOW_TRAY_ICON_ID; info->nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; info->nid.uCallbackMessage = WM_TRAYICON; // Custom message when interacting with the tray icon info->nid.hIcon = info->icon_minimized; memcpy(info->nid.szTip, tooltip_text_wide.data, tooltip_text_wide.count); bool success = Shell_NotifyIconW(NIM_ADD, &info->nid); if (success) { info->tray_icon_added = true; return true; } } return false; } bool Win32_Show_Tray_Icon () { Window_Info* info = get_main_window_pointer(); if (info && info->tray_icon_added) { return Shell_NotifyIconW(NIM_ADD, &info->nid); } return false; } bool Win32_Hide_Tray_Icon () { Window_Info* info = get_main_window_pointer(); if (info && info->tray_icon_added) { return Shell_NotifyIconW(NIM_DELETE, &info->nid); } return false; } bool Win32_Hide_Window_Titlebar () { Window_Info* info = get_main_window_pointer(); if (info && info->window) { LONG style = GetWindowLongW(info->window, GWL_STYLE); style &= (LONG)(~(WS_CAPTION | WS_SYSMENU)); SetWindowLongW(info->window, GWL_STYLE, style); SetWindowPos(info->window, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER); return true; } return false; } bool Win32_Show_Window_Titlebar () { Window_Info* info = get_main_window_pointer(); if (info && info->window) { LONG style = GetWindowLongW(info->window, GWL_STYLE); style |= (LONG)(WS_CAPTION | WS_SYSMENU); SetWindowLongW(info->window, GWL_STYLE, style); SetWindowPos(info->window, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER); return true; } return false; } void Win32_Minimize_Window_To_Tray (Window_Info* info) { if (info && info->window) { ShowWindow(info->window, SW_HIDE); Win32_Show_Tray_Icon(); info->minimized_to_tray = true; } } void Win32_Restore_Window_From_Tray (Window_Info* info) { if (info && info->window && info->minimized_to_tray) { ShowWindow(info->window, SW_RESTORE); Win32_Hide_Tray_Icon(); info->minimized_to_tray = false; } } // Win32_Restore_Window_From_Tray(); void Win32_Bring_Window_To_Foreground (Window_Info* info) { Win32_Restore_Window_From_Tray(info); if (info && info->window) { if (os_window_is_minimized(info->window)) { ShowWindow(info->window, SW_RESTORE); } SetForegroundWindow(info->window); } } bool Win32_Load_Main_Window_Icon_Minimized (string icon_path) { HICON result = (HICON)LoadImageW(nullptr, (LPCWSTR)utf8_to_wide(icon_path).data, IMAGE_ICON, 0, 0, LR_LOADFROMFILE); Window_Info* info = get_main_window_pointer(); info->icon_minimized = result; return (result != nullptr); } bool Win32_Load_Main_Window_Icon (string icon_path) { HICON result = (HICON)LoadImageW(nullptr, (LPCWSTR)utf8_to_wide(icon_path).data, IMAGE_ICON, 0, 0, LR_LOADFROMFILE); Window_Info* info = get_main_window_pointer(); info->icon = result; return (result != nullptr); } // #window_creation -> put API in OS_Win32.h // Instead of returning WindowType, return the handle + other information. bool os_create_window (string new_window_name, Window_Type parent, bool center_window, bool open_on_largest_monitor, bool display_window, void* wnd_proc_override) { local_persist string class_name = "Win32_Window_Class"; WNDCLASSEXW wc = {}; if (!global_win32_state.process_info.window_class_initialized) { global_win32_state.process_info.window_class_initialized = true; wc.cbSize = sizeof(WNDCLASSEXW); wc.style = CS_HREDRAW | CS_VREDRAW; if (!wnd_proc_override) { wc.lpfnWndProc = win32_wnd_proc; } else { wc.lpfnWndProc = (WNDPROC)wnd_proc_override; } wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = nullptr; wc.hIcon = get_main_window().icon; wc.hCursor = LoadCursorW(nullptr, (LPCWSTR)IDC_ARROW); wc.hbrBackground = nullptr; wc.lpszMenuName = nullptr; wc.lpszClassName = (LPCWSTR)utf8_to_wide(class_name).data; if (RegisterClassExW(&wc) == 0) { log_error("RegisterClassExW Failed."); return false; } } Window_Dimensions wd = { 100, 100, 640, 480 }; if (center_window) { wd = platform_get_centered_window_dimensions(open_on_largest_monitor); log("[Window_Dimensions] location: (%d, %d); size: %dx%d px", wd.window_x, wd.window_y, wd.window_width, wd.window_height); } HWND hwnd = CreateWindowExW( WS_EX_APPWINDOW, wc.lpszClassName, (LPCWSTR)utf8_to_wide(new_window_name).data, WS_OVERLAPPEDWINDOW, wd.window_x, wd.window_y, wd.window_width, wd.window_height, nullptr, nullptr, nullptr, nullptr ); if (!hwnd) { Assert(false); DestroyWindow(hwnd); return false; } // Display the window: if (display_window) { UpdateWindow(hwnd); ShowWindow(hwnd, SW_SHOW); } // Save window to stack Window_Info info = {}; info.window = hwnd; info.initial_dimensions = wd; info.is_main_window = (parent == nullptr); // Should we mutex this? Seems very unlikely we'll ever need to call this // from multiple threads at the same time. array_add(global_win32_state.process_info.windows, info); return true; } Window_Info get_main_window () { Array windows = global_win32_state.process_info.windows; if (windows.count <= 0) { return {}; } for (s64 i = 0; i < windows.count; i += 1) { if (windows[i].is_main_window) { return windows[i]; } } return {}; } Window_Info* get_main_window_pointer () { s64 window_count = global_win32_state.process_info.windows.count; for (s64 i = 0; i < window_count; i += 1) { if (global_win32_state.process_info.windows[i].is_main_window) { return &global_win32_state.process_info.windows[i]; } } return nullptr; } Window_Info* get_window_info (Window_Type window) { s64 window_count = global_win32_state.process_info.windows.count; for (s64 i = 0; i < window_count; i += 1) { if (global_win32_state.process_info.windows[i].window == window) { return &global_win32_state.process_info.windows[i]; } } return nullptr; } bool get_window_dimensions(Window_Info* info, s32* width, s32* height) { if (info == nullptr || width == nullptr || height == nullptr) return false; Assert(width && height); RECT c = {}; bool success = GetClientRect(info->window, &c); if (!success) { (*width) = 0; (*height) = 0; return false; } (*width) = (s32)(c.right - c.left); (*height) = (s32)(c.bottom - c.top); return true; } bool os_window_is_minimized (Window_Type window) { return (bool)IsIconic(window); } bool os_main_window_is_minimized () { return os_window_is_minimized(get_main_window().window); } s32 os_cpu_logical_core_count () { OS_System_Info* info = &global_win32_state.system_info; return info->logical_processor_count; } s32 os_cpu_physical_core_count () { OS_System_Info* info = &global_win32_state.system_info; return info->physical_core_count; } s32 os_cpu_primary_core_count () { OS_System_Info* info = &global_win32_state.system_info; return info->primary_core_count; } s32 os_cpu_secondary_core_count () { OS_System_Info* info = &global_win32_state.system_info; return info->secondary_core_count; } // #Drives constexpr u64 Win32_Max_Path_Length = 260; bool Win32_Discover_Drives () { push_allocator(GPAllocator()); // Initialize drive_table if necessary. Table* drive_table = get_drive_table(); if (!drive_table->allocated) { drive_table->allocator = GPAllocator(); // #TODO(Low priority): #hash_table need a macro for initializing with string keys! drive_table->hash_function = string_hash_function_fnv1a; drive_table->compare_function = string_keys_match; s64 slots_to_allocate = 64; table_init(drive_table, slots_to_allocate); } u16 lpBuf[1024]; u32 result_length = GetLogicalDriveStringsW(1024, (LPWSTR)lpBuf); if (!result_length) { log_error("GetLogicalDriveStringsW failed!"); os_log_error(); return false; } bool completed = false; s32 current_index = 0; while (true) { completed = completed || (current_index >= 1024) || lpBuf[current_index] == 0; if (completed) break; u16* logical_drive = lpBuf + current_index; string drive_label = wide_to_utf8(logical_drive); u64 total_number_of_bytes;u64 total_number_of_free_bytes; bool result = GetDiskFreeSpaceExW((LPCWSTR)logical_drive, nullptr, (PULARGE_INTEGER)&total_number_of_bytes, (PULARGE_INTEGER)&total_number_of_free_bytes); Win32_Drive_Type drive_type = (Win32_Drive_Type)GetDriveTypeW((LPCWSTR)logical_drive); log("Found %s drive, type %s", drive_label.data, to_string(drive_type).data); current_index += (s32)(drive_label.count + 1); bool just_added = false; OS_Drive** drive_ptr = table_find_or_add(drive_table, drive_label, &just_added); OS_Drive* drive = New(); (*drive_ptr) = drive; // fill table slot with data. if (!just_added) { // delete old strings before updating // This is silly, but there's a small chance the volume has been renamed so... string_free(drive->label); // this is actually just stupid. string_free(drive->volume_name); } u16 volume_name[Win32_Max_Path_Length] = {}; u16 file_system_name[Win32_Max_Path_Length] = {}; DWORD serial_number = 0; DWORD max_comp_len = 0; DWORD file_system_flags = 0; if (GetVolumeInformationW((LPCWSTR)logical_drive, (LPWSTR)volume_name, Win32_Max_Path_Length, &serial_number, &max_comp_len, &file_system_flags, (LPWSTR)file_system_name, Win32_Max_Path_Length)) { drive->label = drive_label; drive->volume_name = wide_to_utf8(volume_name); if (drive->volume_name == "") { drive->volume_name = copy_string("Local Disk"); } drive->type = (Win32_Drive_Type)drive_type; { push_allocator(temp()); drive->file_system = Win32_filesystem_from_string(wide_to_utf8(file_system_name)); } drive->serial_number = serial_number; drive->max_component_length = max_comp_len; drive->file_system_flags = file_system_flags; drive->is_present = true; push_allocator(temp()); log(" - volume name: %s", drive->volume_name.data); log(" - file_system: %s", wide_to_utf8(file_system_name).data); } else { log_error("GetVolumeInformationW failed! (drive label: %s)", drive_label.data); os_log_error(); drive->is_present = false; } } return true; } // Drive label includes `:\` bool Win32_Drive_Exists (string drive_label) { push_allocator(temp()); LPCWSTR drive_label_wide = (LPCWSTR)utf8_to_wide(drive_label).data; UINT type = GetDriveTypeW(drive_label_wide); return (type != DRIVE_UNKNOWN && type != DRIVE_NO_ROOT_DIR); // Alternative method: // return (bool)GetVolumeInformationW(drive_label_wide, nullptr, 0, nullptr, nullptr, nullptr, nullptr, 0); } string Win32_drive_letter (string any_path) { // #TODO: remove leading `\\.\` if present, assert if drive letter is invalid. // we copy so it is null-terminated, and can be used as %s in format_string. return copy_string({1, any_path.data}); } string os_get_machine_name () { constexpr u8 WIN32_MAX_COMPUTER_LENGTH_NAME = 31; u16 buffer[WIN32_MAX_COMPUTER_LENGTH_NAME + 1]; u32 count = WIN32_MAX_COMPUTER_LENGTH_NAME + 1; if (GetComputerNameW((LPWSTR)buffer, (LPDWORD)&count)) { return wide_to_utf8(buffer); } return ""; } // #TODO: #window_creation #window_manipulation // [ ] resize_window // [ ] position_window // [ ] toggle_fullscreen // [ ] get_dimensions // #TODO: #window_interaction (mouse/keyboard) // [ ] get_mouse_pointer_position // [ ] ... What APIs do I need for Keyboard // #FileEnumerationST struct STFE_Results { Serializer* strings; // Serializer? ArenaArray* offsets; ArenaArray* lengths; ArenaArray* sizes; ArenaArray* modtimes; }; void init (STFE_Results* results) { results->strings = (Serializer*)arena_array_new (1024*1024*4*16, Arena_Reserve::Size_2G); results->offsets = arena_array_new(1024*1024*4, Arena_Reserve::Size_2G); results->lengths = arena_array_new(1024*1024*4, Arena_Reserve::Size_2G); results->sizes = arena_array_new(1024*1024*4, Arena_Reserve::Size_2G); results->modtimes = arena_array_new(1024*1024*4, Arena_Reserve::Size_2G); } struct ST_File_Enumeration { // global state ArrayView drives; Thread* master_thread; STFE_Results dirs; STFE_Results files; s32 directories_enumerated; // going sequentially bool thread_started; bool thread_completed; f64 start_time; f64 end_time; }; global ST_File_Enumeration* stfe; string add_record (ST_File_Enumeration* stfe, string full_path, bool is_directory, WIN32_FIND_DATAW* find_data) { // return the string copy! if (is_directory) { STFE_Results* r = &stfe->dirs; u32 offset = AddString_NoCount(r->strings, full_path.data, (s16)full_path.count); array_add((*r->offsets), offset); array_add((*r->lengths), (s16)full_path.count); // No size for directories. u64 modtime = FILETIME_to_ticks(find_data->ftLastWriteTime); array_add((*r->modtimes), modtime); string path_copy = {full_path.count, &r->strings->data[offset]}; return path_copy; } else { STFE_Results* r = &stfe->files; u32 offset = AddString_NoCount(r->strings, full_path.data, (s16)full_path.count); array_add((*r->offsets), offset); array_add((*r->lengths), (s16)full_path.count); u64 size = ((u64)find_data->nFileSizeHigh << 32) | ((u64)find_data->nFileSizeLow & 0xFFFFFFFF); u64 modtime = FILETIME_to_ticks(find_data->ftLastWriteTime); array_add((*r->sizes), size); array_add((*r->modtimes), modtime); string path_copy = {full_path.count, &r->strings->data[offset]}; return path_copy; } Assert(false); return {}; } s32 count_paths (ST_File_Enumeration* stfe) { STFE_Results* r = &stfe->dirs; return (s32)r->offsets->count; } s32 count_files (ST_File_Enumeration* stfe) { STFE_Results* r = &stfe->files; return (s32)r->offsets->count; } // #UI #TEMP - just for visualization! string get_file_copy (ST_File_Enumeration* stfe, s64 index) { STFE_Results* r = &stfe->files; Assert(index >= 0 && index < count_files(stfe)); s64 strlength = (*r->lengths)[index]; u32 offset = (*r->offsets)[index]; u8* string_ptr = &r->strings->data[offset]; string file = {strlength, string_ptr}; return copy_string(file); } string get_file_string_view (ST_File_Enumeration* stfe, s64 index) { STFE_Results* r = &stfe->files; s64 strlength = (*r->lengths)[index]; u32 offset = (*r->offsets)[index]; u8* string_ptr = &r->strings->data[offset]; string file = {strlength, string_ptr}; return file; } string get_path_copy (ST_File_Enumeration* stfe, s64 index) { STFE_Results* r = &stfe->dirs; Assert(index >= 0 && index < count_paths(stfe)); s64 strlength = (*r->lengths)[index]; u32 offset = (*r->offsets)[index]; u8* string_ptr = &r->strings->data[offset]; string path = {strlength, string_ptr}; return copy_string(path); } s64 get_file_size_bytes (ST_File_Enumeration* stfe, s64 index) { STFE_Results* r = &stfe->files; return (s64)(*r->sizes)[index]; } FILETIME get_file_modtime (ST_File_Enumeration* stfe, s64 index) { STFE_Results* r = &stfe->files; FILETIME ft; memcpy(&ft, &(*r->modtimes)[index], sizeof(u64)); return ft; } FILETIME get_path_modtime (ST_File_Enumeration* stfe, s64 index) { STFE_Results* r = &stfe->dirs; FILETIME ft; memcpy(&ft, &(*r->modtimes)[index], sizeof(u64)); return ft; } s64 win32_file_enum_thread_proc (Thread* thread) { auto task = thread_task(ST_File_Enumeration); init(&task->dirs); init(&task->files); // while files available? Array paths_to_enumerate; for_each(d, task->drives) { string parent_directory = task->drives[d]->label; // includes a trailing slash if (parent_directory.data[2] == (u8)'\\') { parent_directory.count -= 1; //#hack to quickly remove trailing slash. } array_add(paths_to_enumerate, parent_directory); while (paths_to_enumerate.count > 0) { push_allocator(temp()); auto_release_temp(); // This needs to be null-terminated: // #TODO: Replace this #LIFO array with an arena-backed FIFO stack (singly linked-list). string next_directory = copy_string(pop(paths_to_enumerate)); // LIFO. maybe not the best way? wstring wildcard_name = utf8_to_wide(format_string("%s\\*", next_directory.data)); WIN32_FIND_DATAW find_data; HANDLE h = FindFirstFileExW((LPCWSTR)wildcard_name.data, FindExInfoBasic, &find_data, FindExSearchNameMatch, nullptr, FIND_FIRST_EX_LARGE_FETCH); if (h == INVALID_HANDLE_VALUE) { log_error("FindFirstFileExW failed for %s", wide_to_utf8(wildcard_name.data, (s32)wildcard_name.count).data); os_log_error(); continue; } while (true) { auto_release_temp(); string name = wide_to_utf8((u16*)find_data.cFileName); bool should_continue = (name.count == 0 || name == "." || name == ".."); if (should_continue) { bool success = FindNextFileW(h, &find_data); if (!success) { break; } continue; } bool is_directory = (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; string full_path = format_string("%s\\%s", next_directory.data, name.data); string full_path_copy = add_record(task, full_path, is_directory, &find_data); if (is_directory) { array_add(paths_to_enumerate, full_path_copy); } bool success = FindNextFileW(h, &find_data); if (!success) break; } // while (true) -> FindNextFileW FindClose(h); } // while (parent_directory) } // for_each(d, drives) task->end_time = GetUnixTimestamp(); return 0; } void os_run_file_enumeration_single_threaded () { push_allocator(GPAllocator()); stfe = New(); (*stfe) = { os_get_available_drives(), New(), STFE_Results(), STFE_Results(), 0, true, false, GetUnixTimestamp(), 0 }; string thread_name = "Single Thread Enumeration - Master Thread"; bool success = thread_init(stfe->master_thread, win32_file_enum_thread_proc, thread_name); if (!success) { log_error("Failed to initialize thread (stft->master_thread)"); os_log_error(); } Assert(success); thread_start(stfe->master_thread, stfe); } constexpr u32 STFE_Magic_Number = 0x19075fee; bool Serialize_ST_File_Enumeration (string file_path) { Timed_Block_Print("Serialize_ST_File_Enumeration"); File f = file_open(file_path, true, false, true); if (!file_is_valid(f)) return false; bool success = true; // #TODO #Serialization Unfortunately, there's a lot of needless copying here // it would be a lot nicer if we could just write-file in place. idk how to do that though ;_; Serializer* s = new_serializer(Arena_Reserve::Size_64G); Add(s, (u32)STFE_Magic_Number); Add(s, (s32)stfe->drives.count); // Dirs: STFE_Results* r = &stfe->dirs; AddArray(s, to_view(*r->strings)); AddArray(s, to_view(*r->offsets)); AddArray(s, to_view(*r->lengths)); AddArray(s, to_view(*r->modtimes)); // Files: r = &stfe->files; AddArray(s, to_view(*r->strings)); AddArray(s, to_view(*r->offsets)); AddArray(s, to_view(*r->lengths)); AddArray(s, to_view(*r->sizes)); AddArray(s, to_view(*r->modtimes)); success = file_write(&f, to_view(*s)); reset_serializer(s); file_close(&f); free_serializer(s); return success; } bool Deserialize_ST_File_Enumeration (string file_path) { Timed_Block_Print("Deserialize_ST_File_Enumeration"); push_allocator(GPAllocator()); if (!stfe) stfe = New(); (*stfe) = { {}, {}, STFE_Results(), STFE_Results(), 0, false, false, GetUnixTimestamp(), 0 }; push_allocator(temp()); auto_release_temp(); Deserializer deserializer = read_entire_file(file_path, true); if (deserializer.count == 0) return false; auto d = &deserializer; u32 magic_number; s32 drive_count; Read(d, &magic_number); Assert(magic_number == STFE_Magic_Number); Read(d, &drive_count); init(&stfe->dirs); init(&stfe->files); STFE_Results* r = &stfe->dirs; ReadToArenaArray(d, r->strings); ReadToArenaArray(d, r->offsets); ReadToArenaArray(d, r->lengths); ReadToArenaArray(d, r->modtimes); r = &stfe->files; ReadToArenaArray(d, r->strings); ReadToArenaArray(d, r->offsets); ReadToArenaArray(d, r->lengths); ReadToArenaArray(d, r->sizes); ReadToArenaArray(d, r->modtimes); stfe->thread_started = true; stfe->thread_completed = true; stfe->end_time = GetUnixTimestamp(); return true; } // #USNJrnl stuff: // This should work even if our other indices are not ready yet! bool USN_Journal_Monitoring_Ready(OS_Drive* drive) { return (drive->jrnl.hVol != nullptr && drive->jrnl.hVol != INVALID_HANDLE_VALUE); } void Win32_Enable_USN_Journal_Monitoring (ArrayView drives) { push_allocator(temp()); // #TODO: Put any relevant data into Win32_Drive. for_each(d, drives) { OS_Drive* drive = drives[d]; if (drive->jrnl.no_permission) continue; if (USN_Journal_Monitoring_Ready(drive)) continue; string drive_letter = Win32_drive_letter(drive->label); string create_file_target = format_string("\\\\.\\%s:", drive_letter.data); drive->jrnl.hVol = CreateFileA((LPCSTR)create_file_target.data, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr); if (drive->jrnl.hVol == INVALID_HANDLE_VALUE) { log_error("CreateFileA failed on target %s", create_file_target.data); os_log_error(); drive->jrnl.no_permission = true; } } } #include void Query_USN_Journal (ArrayView drives) { Win32_Enable_USN_Journal_Monitoring(drives); for_each(d, drives) { OS_Drive* drive = drives[d]; if (!USN_Journal_Monitoring_Ready(drive)) continue; USN_JOURNAL_DATA_V0 usn_jd; DWORD bytes_returned; BOOL ok = DeviceIoControl(drive->jrnl.hVol, FSCTL_QUERY_USN_JOURNAL, nullptr, 0, &usn_jd, sizeof(usn_jd), &bytes_returned, nullptr); if (!ok) { log_error("DeviceIoControl failed on target %s", drive->label.data); os_log_error(); return; } log("[DeviceIoControl] target %s", drive->label.data); log(" > Journal ID: %llu", usn_jd.UsnJournalID); log(" > First USN: %llu", usn_jd.FirstUsn); debug_break(); // #TODO #continue } }