Musa-Cpp-Lib-V2/lib/OS/OS_Win32.cpp

1490 lines
47 KiB
C++

// #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.
// Or we can move child threads up to the parent?
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);
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<Monitor> monitors; // Back with GPAllocator
// #Drives
Table<string, OS_Drive*> 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<string> module_load_paths;
Array<string> environment_paths;
b32 window_class_initialized;
Arena* event_arena;
Array<Window_Info> 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<string, OS_Drive*>* get_drive_table () {
return &global_win32_state.system_info.drives;
}
ArrayView<OS_Drive*> os_get_available_drives () {
// @Allocates: Recommended to set context allocator to temp().
auto drive_table = get_drive_table();
Array<OS_Drive*> 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<string, OS_Drive*>* 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<string, OS_Drive*>* 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<u8>(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<u16>(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();
}
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>();
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<u8> read_entire_file (File file) {
ArrayView<u8> 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<u8>(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<u8> 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<u8> 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<u8> 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<u8> 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<Monitor> 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<Window_Info> 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<string, OS_Drive*>* 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<OS_Drive>();
(*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<u32>* offsets;
ArenaArray<s16>* lengths;
ArenaArray<u64>* sizes;
ArenaArray<u64>* modtimes;
};
void init (STFE_Results* results) {
results->strings = (Serializer*)arena_array_new<u8> (1024*1024*4*16, Arena_Reserve::Size_2G);
results->offsets = arena_array_new<u32>(1024*1024*4, Arena_Reserve::Size_2G);
results->lengths = arena_array_new<s16>(1024*1024*4, Arena_Reserve::Size_2G);
results->sizes = arena_array_new<u64>(1024*1024*4, Arena_Reserve::Size_2G);
results->modtimes = arena_array_new<u64>(1024*1024*4, Arena_Reserve::Size_2G);
}
void STFE_Results_Free (STFE_Results* results) {
arena_array_free(*results->strings);
arena_array_free(*results->offsets);
arena_array_free(*results->lengths);
arena_array_free(*results->sizes);
arena_array_free(*results->modtimes);
}
struct ST_File_Enumeration { // global state
ArrayView<OS_Drive*> 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;
void free_stfe_and_reset () {
push_allocator(GPAllocator());
array_free(stfe->drives);
internal_free(stfe->master_thread);
STFE_Results_Free(&stfe->dirs);
STFE_Results_Free(&stfe->files);
internal_free(stfe);
stfe = nullptr; // final step
}
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);
// Allocates to thread_context->arena, which is cleaned up
// when the thread completes. see: thread_deinit
Array<string> 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<ST_File_Enumeration>();
(*stfe) = {
os_get_available_drives(),
New<Thread>(),
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<ST_File_Enumeration>();
(*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<OS_Drive*> 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;
}
}
}
void Query_USN_Journal (ArrayView<OS_Drive*> 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
}
}