feat: remove curllib dependency

This commit is contained in:
Ixniy Evonniy 2026-07-01 00:23:09 +03:00
parent 1c030cfa09
commit 6ea2cfd4b8
5 changed files with 102 additions and 51 deletions

View File

@ -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;

BIN
cache.db

Binary file not shown.

View File

@ -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

View File

@ -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, "";

View File

@ -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 {