diff --git a/cache.db b/cache.db index 51b4a12..8d6e153 100644 Binary files a/cache.db and b/cache.db differ diff --git a/main.jai b/main.jai index d3a2b59..fd039c9 100644 --- a/main.jai +++ b/main.jai @@ -229,6 +229,43 @@ file_exists :: (path: string) -> bool { return attribs != INVALID_FILE_ATTRIBUTES && !(attribs & FILE_ATTRIBUTE_DIRECTORY); } +is_autostart_enabled :: () -> bool { + hKey: HKEY; + subkey := utf8_to_wide("Software\\Microsoft\\Windows\\CurrentVersion\\Run"); + status := RegOpenKeyExW(HKEY_CURRENT_USER, subkey, 0, KEY_READ, *hKey); + if status != 0 return false; + defer RegCloseKey(hKey); + + value_name := utf8_to_wide("SingboxTray"); + + type: DWORD; + status = RegQueryValueExW(hKey, value_name, null, *type, null, null); + return status == 0; +} + +set_autostart :: (enable: bool) -> bool { + hKey: HKEY; + subkey := utf8_to_wide("Software\\Microsoft\\Windows\\CurrentVersion\\Run"); + status := RegOpenKeyExW(HKEY_CURRENT_USER, subkey, 0, KEY_WRITE, *hKey); + if status != 0 return false; + defer RegCloseKey(hKey); + + value_name := utf8_to_wide("SingboxTray"); + + if enable { + path_buffer: [MAX_PATH] u16; + len := GetModuleFileNameW(null, path_buffer.data, MAX_PATH); + if len == 0 return false; + + path_len_bytes := cast(DWORD) ((len + 1) * 2); + status = RegSetValueExW(hKey, value_name, 0, REG_SZ, cast(*u8) path_buffer.data, path_len_bytes); + return status == 0; + } else { + status = RegDeleteValueW(hKey, value_name); + return status == 0 || status == 2; // ERROR_FILE_NOT_FOUND is 2 + } +} + // Hidden Process Spawning with Job Object configuration and stdout/stderr redirection create_process_hidden :: (cmd: string, stdout_handle: HANDLE = INVALID_HANDLE_VALUE) -> HANDLE, PROCESS_INFORMATION, bool { startup_info: STARTUPINFOW; @@ -579,6 +616,14 @@ show_context_menu :: (app: *App_State) { AppendMenuW(hMenu, MF_SEPARATOR, 0, null); + autostart_flags := MF_STRING; + if is_autostart_enabled() { + autostart_flags |= MF_CHECKED; + } + AppendMenuW(hMenu, autostart_flags, CMD_TOGGLE_AUTOSTART, utf8_to_wide("Start on Windows boot")); + + AppendMenuW(hMenu, MF_SEPARATOR, 0, null); + AppendMenuW(hMenu, MF_STRING, CMD_EXIT, utf8_to_wide("Exit")); cursor_pos: POINT; @@ -678,6 +723,21 @@ show_context_menu :: (app: *App_State) { } } } + case CMD_TOGGLE_AUTOSTART; { + currently_enabled := is_autostart_enabled(); + success := set_autostart(!currently_enabled); + if success { + new_state := !currently_enabled; + if new_state { + log_info("Autostart on boot enabled.\n"); + } else { + log_info("Autostart on boot disabled.\n"); + } + } else { + log_error("Failed to toggle autostart registry configuration.\n"); + MessageBoxW(app.hwnd, utf8_to_wide("Failed to update Windows registry autostart configuration."), utf8_to_wide("Sing-box Tray"), MB_ICONERROR); + } + } case CMD_EXIT; { DestroyWindow(app.hwnd); } @@ -767,6 +827,25 @@ main :: () { } global_is_test_mode = is_test; + // Change working directory to the executable's directory to resolve relative paths + // correctly when started via Windows autostart/registry. + { + buffer: [2048] u16; + len := GetModuleFileNameW(null, buffer.data, 2048); + if len > 0 { + last_backslash_idx := -1; + for i: 0..len-1 { + if buffer[i] == #char "\\" || buffer[i] == #char "/" { + last_backslash_idx = i; + } + } + if last_backslash_idx != -1 { + buffer[last_backslash_idx] = 0; + SetCurrentDirectoryW(buffer.data); + } + } + } + hInstance := GetModuleHandleW(null); class_name_str := ifx is_test then "SingboxTrayControllerClassTest" else "SingboxTrayControllerClass"; class_name := utf8_to_wide(class_name_str); diff --git a/win32.jai b/win32.jai index 0c6e1d8..d63a100 100644 --- a/win32.jai +++ b/win32.jai @@ -82,6 +82,7 @@ CMD_EXIT :: 1005; CMD_MODE_PROXY :: 1006; CMD_MODE_SYS_PROXY :: 1007; CMD_SET_PORT :: 1008; +CMD_TOGGLE_AUTOSTART:: 1009; // Boolean constants FALSE :: 0; @@ -143,6 +144,7 @@ REGSAM :: u32; HKEY_CURRENT_USER :: cast(HKEY) 0x80000001; KEY_WRITE :: 0x20006; +KEY_READ :: 0x20019; INTERNET_OPTION_SETTINGS_CHANGED :: 39; INTERNET_OPTION_REFRESH :: 37; @@ -150,6 +152,15 @@ INTERNET_OPTION_REFRESH :: 37; RegOpenKeyExW :: (hKey: HKEY, lpSubKey: LPCWSTR, ulOptions: DWORD, samDesired: REGSAM, phkResult: *HKEY) -> LSTATUS #foreign advapi32; RegSetValueExW :: (hKey: HKEY, lpValueName: LPCWSTR, Reserved: DWORD, dwType: DWORD, lpData: *u8, cbData: DWORD) -> LSTATUS #foreign advapi32; RegCloseKey :: (hKey: HKEY) -> LSTATUS #foreign advapi32; +RegDeleteValueW :: (hKey: HKEY, lpValueName: LPCWSTR) -> LSTATUS #foreign advapi32; +RegQueryValueExW :: ( + hKey: HKEY, + lpValueName: LPCWSTR, + lpReserved: *DWORD, + lpType: *DWORD, + lpData: *u8, + lpcbData: *DWORD +) -> LSTATUS #foreign advapi32; InternetSetOptionW :: (hInternet: HANDLE, dwOption: DWORD, lpBuffer: *void, dwBufferLength: DWORD) -> BOOL #foreign wininet; @@ -265,3 +276,4 @@ set_windows_system_proxy :: (enable: bool, server: string) -> bool { +