diff --git a/build.jai b/build.jai index f7bbd97..b214d5c 100644 --- a/build.jai +++ b/build.jai @@ -15,22 +15,7 @@ options := get_build_options(w); options.output_executable_name = "singbox_tray"; - // Copy libcurl.dll from compiler modules to output directory - for options.import_path { - dll_path := tprint("%Curl/windows/lib/libcurl.dll", it); - if file_exists(dll_path) { - dest_path := "libcurl.dll"; - if options.output_path { - dest_path = tprint("%/libcurl.dll", options.output_path); - } - if copy_file(dll_path, dest_path) { - print("Build: Successfully copied libcurl.dll to %\n", dest_path); - } else { - print("Build Warning: Could not copy libcurl.dll to %\n", dest_path); - } - break; - } - } + // No longer need to copy libcurl.dll since we use native Windows WinINet API. // options.additional_linker_arguments is a slice, so copy it to a dynamic array, append, and assign back new_args: [..] string; diff --git a/cache.db b/cache.db index 22c4f98..7167119 100644 Binary files a/cache.db and b/cache.db differ diff --git a/singbox_tray_plan.md b/singbox_tray_plan.md index c68a93b..49b8f7f 100644 --- a/singbox_tray_plan.md +++ b/singbox_tray_plan.md @@ -12,7 +12,7 @@ graph TD B -->|Right Click| C[Context Menu] C -->|Set URL| D[Custom Edit Dialog] C -->|Start/Stop| E[Process Manager: sing-box.exe] - C -->|Update Now| F[Auto-Update Loop: libcurl] + C -->|Update Now| F[Auto-Update Loop: WinINet] C -->|Exit| G[Shutdown & Cleanup] F -->|Timer/Thread| F ``` @@ -35,7 +35,7 @@ To avoid needing an external resource compiler (`.rc`) or complex UI libraries, - **Behavior**: Block interactions with other menus, read the edit text on OK, write it to a `url.txt` file, and trigger an immediate config update. ### C. Config Downloader (Auto-Update Engine) -- **Curl Library**: Use the standard `Curl` module to perform HTTP requests. +- **Downloader**: Use the Windows WinINet API to perform HTTP/HTTPS requests. - **Storage**: Save the downloaded JSON content to `config.json` in the same directory as the executable. - **Auto-Update Thread**: Spawn a background thread (using `Thread` module) that sleeps for a set interval (e.g., 1 hour), then downloads the URL. If the file content changes, it updates `config.json` and restarts the `sing-box` process if it was running. @@ -49,7 +49,7 @@ To avoid needing an external resource compiler (`.rc`) or complex UI libraries, 1. **`main.jai`**: Core application entry point, hidden window loop, tray icon management. 2. **`dialog.jai`**: Edit-box dialog for config URL input. -3. **`updater.jai`**: Libcurl wrapper and background auto-update thread. +3. **`updater.jai`**: WinINet HTTP/HTTPS client and background auto-update thread. 4. **`build.jai`**: Jai build script (metaprogram) to compile the app without standard console window popup (using `-subsystem windows`). ## 4. Key Questions & Decisions diff --git a/updater.jai b/updater.jai index b7b3e2c..f5a8615 100644 --- a/updater.jai +++ b/updater.jai @@ -1,5 +1,4 @@ #import "Basic"; -#import "Curl"; #import "File"; #import "Thread"; #import "Windows"; @@ -12,44 +11,65 @@ Updater_Thread_Data :: struct { stop_event: HANDLE; } -write_callback :: (ptr: *u8, size: u64, nmemb: u64, userdata: *void) -> u64 #c_call { - builder := cast(*String_Builder) userdata; - push_context { - append(builder, ptr, xx nmemb); - } - return nmemb; -} - download_url :: (url: string) -> string, bool, string { - curl := curl_easy_init(); - if !curl return "", false, "Failed to initialize Curl"; - defer curl_easy_cleanup(curl); + hInternet := InternetOpenW( + utf8_to_wide("SingboxTrayUpdater"), + INTERNET_OPEN_TYPE_PRECONFIG, + null, + null, + 0 + ); + if !hInternet { + return "", false, "Failed to initialize WinINet session"; + } + defer InternetCloseHandle(hInternet); - curl_easy_setopt(curl, .URL, temp_c_string(url)); - curl_easy_setopt(curl, .FOLLOWLOCATION, 1); - curl_easy_setopt(curl, .TIMEOUT, 30); + flags: DWORD = INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE; + if starts_with(url, "https") { + flags |= INTERNET_FLAG_SECURE; + } - // Disable SSL verification to prevent issues with missing CA cert bundles on Windows - curl_easy_setopt(curl, .SSL_VERIFYPEER, 0); - curl_easy_setopt(curl, .SSL_VERIFYHOST, 0); + hUrl := InternetOpenUrlW( + hInternet, + utf8_to_wide(url), + null, + 0, + flags, + 0 + ); + if !hUrl { + err := GetLastError(); + return "", false, tprint("Failed to open URL (Win32 Error: %)", err); + } + defer InternetCloseHandle(hUrl); + + // Query response code + response_code: DWORD; + response_code_size: DWORD = size_of(type_of(response_code)); + if HttpQueryInfoW(hUrl, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, *response_code, *response_code_size, null) { + if response_code != 200 { + return "", false, tprint("HTTP request failed with status code %", response_code); + } + } else { + err := GetLastError(); + return "", false, tprint("Failed to query HTTP status code (Win32 Error: %)", err); + } builder: String_Builder; defer free_buffers(*builder); - curl_easy_setopt(curl, .WRITEFUNCTION, write_callback); - curl_easy_setopt(curl, .WRITEDATA, *builder); - - curl_code := curl_easy_perform(curl); - if curl_code != .OK { - err_msg := tprint("Curl perform failed: %", to_string(curl_easy_strerror(curl_code))); - return "", false, err_msg; - } - - response_code: int; - curl_easy_getinfo(curl, .RESPONSE_CODE, *response_code); - if response_code != 200 { - err_msg := tprint("HTTP request failed with status code %", response_code); - return "", false, err_msg; + buffer: [4096] u8; + bytes_read: DWORD; + while true { + ok := InternetReadFile(hUrl, buffer.data, xx buffer.count, *bytes_read); + if !ok { + err := GetLastError(); + return "", false, tprint("Error reading from URL (Win32 Error: %)", err); + } + if bytes_read == 0 { + break; + } + append(*builder, buffer.data, xx bytes_read); } return builder_to_string(*builder), true, ""; diff --git a/win32.jai b/win32.jai index bb835b7..4c62375 100644 --- a/win32.jai +++ b/win32.jai @@ -153,6 +153,52 @@ RegCloseKey :: (hKey: HKEY) -> LSTATUS #foreign advapi32; InternetSetOptionW :: (hInternet: HANDLE, dwOption: DWORD, lpBuffer: *void, dwBufferLength: DWORD) -> BOOL #foreign wininet; +HINTERNET :: *void; + +INTERNET_OPEN_TYPE_PRECONFIG :: 0; +INTERNET_FLAG_RELOAD :: 0x80000000; +INTERNET_FLAG_SECURE :: 0x00800000; +INTERNET_FLAG_NO_CACHE_WRITE :: 0x04000000; + +HTTP_QUERY_STATUS_CODE :: 19; +HTTP_QUERY_FLAG_NUMBER :: 0x20000000; + +InternetOpenW :: ( + lpszAgent: LPCWSTR, + dwAccessType: DWORD, + lpszProxy: LPCWSTR, + lpszProxyBypass: LPCWSTR, + dwFlags: DWORD +) -> HINTERNET #foreign wininet; + +InternetOpenUrlW :: ( + hInternet: HINTERNET, + lpszUrl: LPCWSTR, + lpszHeaders: LPCWSTR, + dwHeadersLength: DWORD, + dwFlags: DWORD, + dwContext: u64 +) -> HINTERNET #foreign wininet; + +InternetReadFile :: ( + hFile: HINTERNET, + lpBuffer: *void, + dwNumberOfBytesToRead: DWORD, + lpdwNumberOfBytesRead: *DWORD +) -> BOOL #foreign wininet; + +InternetCloseHandle :: ( + hInternet: HINTERNET +) -> BOOL #foreign wininet; + +HttpQueryInfoW :: ( + hRequest: HINTERNET, + dwInfoLevel: DWORD, + lpBuffer: *void, + lpdwBufferLength: *DWORD, + lpdwIndex: *DWORD +) -> BOOL #foreign wininet; + wcslen :: (str: *u16) -> int { len := 0; while str[len] != 0 {