Compare commits

...

10 Commits

10 changed files with 1096 additions and 162 deletions

View File

@ -1,39 +0,0 @@
# Jai Developer Agent Guidelines
These guidelines apply to any agent working with the Jai programming language.
## 1. Syntax & Core Idioms
- **Declarations**: Use `:=` for type inference, `: Type =` for explicit types, and `::` for compile-time constants (functions, structs, constants).
- **Procedures**: Declare procedures as `name :: (params) -> return_type { ... }`. Use `#expand` for macros that compile into the caller's scope and access caller variables using backticks (e.g., `` `x ``).
- **Memory Management**: Jai does not have RAII or garbage collection. Memory is managed manually, typically using `defer` to clean up resources right after allocation:
```jai
data := alloc(size);
defer free(data);
```
- **Context Allocators**: Jai uses an implicit `context` parameter containing the current allocator (`context.allocator`).
- Use `context.allocator = temp_allocator;` for temporary/arena allocations that don't need manual freeing (cleared at the end of the frame/loop iteration via `reset_temporary_storage()`).
- When writing reusable libraries or procedures, accept an optional allocator parameter or use `context.allocator` by default.
## 2. Standard Libraries & Imports
- **Anonymous Imports**: Use `#import "Basic";` anonymously to bring standard utilities like `print`, `alloc`, `free`, `tprint` directly into the scope.
- **Named Imports**: Use named imports `String :: #import "String";` to namespace helper libraries, which keeps the scope clean. Note that operator overloads do not automatically propagate through namespaces.
- **No Package Manager**: Since Jai does not have a package manager, vendor any external libraries directly into the project's `modules/` folder or implement them yourself.
## 3. Platform Bindings & Win32 API
- **Windows Bindings**: The standard `Windows` module (`#import "Windows";`) provides native bindings to Win32 APIs.
- **Wide Strings**: Many Windows APIs require wide UTF-16 strings (e.g., `LPCWSTR`). Use helper utilities to convert Jai UTF-8 strings (`string`) to UTF-16 wide strings when calling Windows APIs (e.g. using `utf8_to_wide` or similar functions in the runtime or writing a custom converter using `MultiByteToWideChar`).
- **Callback Conventions**: When defining callbacks for Win32 API (such as Window Procedures or Thread Procedures), you must mark the procedure with `#c_call` calling convention and usually `#no_context` to prevent context mismatches when called from OS threads:
```jai
window_proc :: (hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT #c_call {
// If context is needed inside the callback, re-push it:
ctx: Context; // or get from global/user data
push_context ctx {
// Jai code here
}
}
```
## 4. Code Quality & Compilation
- **No Compiler Warnings**: Always ensure the code compiles without warnings or errors.
- **Compile-Time Execution**: Utilize `#run` blocks to execute code at compile-time for code generation, building lookups, or validating configurations.
- **Linting & Safety**: Avoid using uninitialized variables unless performance-critical (explicitly mark as `---` if needed).

5
.gitignore vendored
View File

@ -1,4 +1,4 @@
# Jai compiler output
# Jai compiler output
*.exe
*.pdb
*.lib
@ -6,8 +6,7 @@
.build/
# Local configurations & logs
config.json
config_run.json
config*.json
*.log
# Third-party binaries

View File

@ -1,3 +1,16 @@
# Project-Specific Agent Guidelines: jai-sing-box
This project implements a lightweight Windows system tray controller for `sing-box` (a universal proxy platform) written in the **Jai** programming language.
**Key Project Notes**:
- The built executable is `sing-box-tray.exe`.
- sing-box core is downloaded on-demand and installed to `%LOCALAPPDATA%\sing-box-tray\`.
- **CRITICAL**: Never terminate sing-box or sing-box-tray processes (see section 5).
- Uses only native Win32 APIs (WinINet, no libcurl dependency).
- Build via `build.jai` metaprogram (sets windows subsystem, custom entry).
---
# Jai Developer Agent Guidelines
These guidelines apply to any agent working with the Jai programming language.
@ -37,3 +50,7 @@ These guidelines apply to any agent working with the Jai programming language.
- **No Compiler Warnings**: Always ensure the code compiles without warnings or errors.
- **Compile-Time Execution**: Utilize `#run` blocks to execute code at compile-time for code generation, building lookups, or validating configurations.
- **Linting & Safety**: Avoid using uninitialized variables unless performance-critical (explicitly mark as `---` if needed).
## 5. Process Management Warning (VPN Connectivity)
- **CRITICAL WARNING**: Do NOT kill, terminate, or stop the `sing-box` or `sing-box-tray` processes during runtime unless explicitly instructed by the user. The agent's own network connection and container/sandbox internet access might be routed through this active VPN. Killing it will sever the agent's connection.
- **Rebuilding & Locked Executables**: Always try to compile the project first. Do not preemptively ask the user to close the executable unless compilation actually fails with a file lock error (e.g. `LNK1104` or similar) and you have verified that the locked file is indeed the output binary in the build directory. Only ask the user to close/free the executable in that specific failure case.

View File

@ -13,7 +13,7 @@
if !w return;
options := get_build_options(w);
options.output_executable_name = "singbox_tray";
options.output_executable_name = "sing-box-tray";
// No longer need to copy libcurl.dll since we use native Windows WinINet API.

BIN
cache.db

Binary file not shown.

View File

@ -3,6 +3,7 @@
#import "Windows_Utf8";
#import "File";
#import "String";
#import "Thread";
// Win32 declarations are provided by win32.jai loaded in the main workspace.
@ -55,7 +56,7 @@ dialog_proc :: (hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT
SendMessageW(state.edit_hwnd, WM_SETFONT, cast(WPARAM) hFont, TRUE);
// Populate with existing URL if present
config_filename := ifx state.is_test_mode then "config_test.json" else "config.json";
config_filename := global_config_path;
config_data, read_ok := read_entire_file(config_filename);
if read_ok {
existing_url := get_json_string_field(config_data, "\"_url\"");
@ -112,7 +113,7 @@ dialog_proc :: (hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT
mode: string;
port := ifx state.is_test_mode then 10899 else 10801;
update_interval := 3600;
config_filename := ifx state.is_test_mode then "config_test.json" else "config.json";
config_filename := global_config_path;
config_data, read_ok := read_entire_file(config_filename);
if read_ok {
extracted_mode := get_json_string_field(config_data, "\"_mode\"");
@ -142,7 +143,7 @@ dialog_proc :: (hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT
state.url_saved = true;
}
} else {
config_filename := ifx state.is_test_mode then "config_test.json" else "config.json";
config_filename := global_config_path;
DeleteFileW(utf8_to_wide(config_filename));
state.url_saved = true;
}
@ -171,7 +172,7 @@ dialog_proc :: (hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT
show_config_url_dialog :: (parent_hwnd: HWND, is_test_mode := false) -> bool {
hInstance := GetModuleHandleW(null);
class_name := utf8_to_wide("SingboxConfigUrlDialogClass");
class_name := utf8_to_wide("Sing-boxConfigUrlDialogClass");
wclass: WNDCLASSEXW;
wclass.cbSize = size_of(WNDCLASSEXW);
@ -279,7 +280,7 @@ show_config_port_dialog :: (parent_hwnd: HWND, is_test_mode := false) -> bool {
SendMessageW(state.edit_hwnd, WM_SETFONT, cast(WPARAM) hFont, TRUE);
// Populate with existing port if present
config_filename := ifx state.is_test_mode then "config_test.json" else "config.json";
config_filename := global_config_path;
config_data, read_ok := read_entire_file(config_filename);
port := ifx state.is_test_mode then 10899 else 10801;
if !state.is_test_mode && read_ok {
@ -350,7 +351,7 @@ show_config_port_dialog :: (parent_hwnd: HWND, is_test_mode := false) -> bool {
if ok && val > 0 && val <= 65535 {
port := xx val;
config_filename := ifx state.is_test_mode then "config_test.json" else "config.json";
config_filename := global_config_path;
config_data, read_ok := read_entire_file(config_filename);
url := "";
mode := "system_proxy";
@ -408,7 +409,7 @@ show_config_port_dialog :: (parent_hwnd: HWND, is_test_mode := false) -> bool {
}
hInstance := GetModuleHandleW(null);
class_name := utf8_to_wide("SingboxPortDialogClass");
class_name := utf8_to_wide("Sing-boxPortDialogClass");
wclass: WNDCLASSEXW;
wclass.cbSize = size_of(WNDCLASSEXW);
@ -471,3 +472,178 @@ show_config_port_dialog :: (parent_hwnd: HWND, is_test_mode := false) -> bool {
return state.url_saved;
}
Download_Thread_Data :: struct {
url: string;
dest_path: string;
success: bool;
error_msg: string;
done: bool;
}
download_thread_proc :: (thread: *Thread) -> s64 {
data := cast(*Download_Thread_Data) thread.data;
data.success, data.error_msg = download_file(data.url, data.dest_path);
data.done = true;
return 0;
}
Download_Dialog_State :: struct {
dialog_done: bool;
thread: Thread;
thread_data: Download_Thread_Data;
static_hwnd: HWND;
file_size: u64;
is_update: bool;
}
download_dialog_proc :: (hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT #c_call {
ctx: #Context;
push_context ctx {
state := cast(*Download_Dialog_State) GetWindowLongPtrW(hwnd, GWLP_USERDATA);
if msg == {
case WM_CREATE; {
create_struct := cast(*CREATESTRUCTW) lparam;
state = cast(*Download_Dialog_State) create_struct.lpCreateParams;
SetWindowLongPtrW(hwnd, GWLP_USERDATA, cast(LONG_PTR) state);
hFont := GetStockObject(DEFAULT_GUI_FONT);
operation := ifx state.is_update then "update" else "installation";
size_text := format_file_size(state.file_size);
status_text := tprint("Downloading Sing-box core %...\nDownload size: %\nThis may take a moment, please wait.", operation, size_text);
state.static_hwnd = CreateWindowExW(
0,
utf8_to_wide("STATIC"),
utf8_to_wide(status_text),
WS_CHILD | WS_VISIBLE | SS_LEFT,
30, 25, 400, 65,
hwnd,
null,
null,
null
);
SendMessageW(state.static_hwnd, WM_SETFONT, cast(WPARAM) hFont, TRUE);
thread_init(*state.thread, download_thread_proc);
state.thread.data = *state.thread_data;
thread_start(*state.thread);
SetTimer(hwnd, 1, 100, null);
return 0;
}
case WM_TIMER; {
if wparam == 1 {
if state.thread_data.done {
KillTimer(hwnd, 1);
thread_is_done(*state.thread, -1);
// Capture error_msg (which may be temp-allocated inside download thread) BEFORE deinit frees the thread temp storage.
captured_success := state.thread_data.success;
captured_err := copy_string(state.thread_data.error_msg);
thread_deinit(*state.thread);
// Overwrite with safe copies (heap allocated) so that the post-loop return sees valid data.
state.thread_data.success = captured_success;
state.thread_data.error_msg = captured_err;
// Free the (copied) input paths now that thread is done with them.
if state.thread_data.url.count > 0 { free(state.thread_data.url); state.thread_data.url = ""; }
if state.thread_data.dest_path.count > 0 { free(state.thread_data.dest_path); state.thread_data.dest_path = ""; }
DestroyWindow(hwnd);
}
}
return 0;
}
case WM_CLOSE; {
if state.thread_data.done {
DestroyWindow(hwnd);
}
return 0;
}
case WM_DESTROY; {
if state {
state.dialog_done = true;
}
PostThreadMessageW(GetCurrentThreadId(), WM_NULL, 0, 0);
return 0;
}
}
return DefWindowProcW(hwnd, msg, wparam, lparam);
}
}
show_download_progress_dialog :: (parent_hwnd: HWND, url: string, dest_path: string, file_size: u64, is_update: bool) -> bool, string {
hInstance := GetModuleHandleW(null);
class_name := utf8_to_wide("Sing-boxDownloadDialogClass");
wclass: WNDCLASSEXW;
wclass.cbSize = size_of(WNDCLASSEXW);
wclass.style = CS_HREDRAW | CS_VREDRAW;
wclass.lpfnWndProc = download_dialog_proc;
wclass.hInstance = hInstance;
wclass.hCursor = LoadCursorW(null, IDC_ARROW);
wclass.hbrBackground = cast(HBRUSH) (COLOR_WINDOW + 1);
wclass.lpszClassName = class_name;
RegisterClassExW(*wclass);
defer UnregisterClassW(class_name, hInstance);
screen_w := GetSystemMetrics(SM_CXSCREEN);
screen_h := GetSystemMetrics(SM_CYSCREEN);
dialog_w: s32 = 460;
dialog_h: s32 = 150;
dialog_x := (screen_w - dialog_w) / 2;
dialog_y := (screen_h - dialog_h) / 2;
state: Download_Dialog_State;
state.thread_data.url = copy_string(url);
state.thread_data.dest_path = copy_string(dest_path);
state.file_size = file_size;
state.is_update = is_update;
dialog_title := ifx is_update then "Updating Sing-box Core" else "Installing Sing-box Core";
dialog_hwnd := CreateWindowExW(
WS_EX_DLGMODALFRAME,
class_name,
utf8_to_wide(dialog_title),
WS_POPUP | WS_CAPTION,
dialog_x, dialog_y, dialog_w, dialog_h,
parent_hwnd,
null,
hInstance,
*state
);
if !dialog_hwnd {
free(state.thread_data.url);
free(state.thread_data.dest_path);
return false, "Failed to create dialog window";
}
if parent_hwnd {
EnableWindow(parent_hwnd, FALSE);
}
ShowWindow(dialog_hwnd, SW_SHOW);
UpdateWindow(dialog_hwnd);
while !state.dialog_done {
msg: MSG;
if GetMessageW(*msg, null, 0, 0) {
if !IsDialogMessageW(dialog_hwnd, *msg) {
TranslateMessage(*msg);
DispatchMessageW(*msg);
}
} else {
PostQuitMessage(cast(s32) msg.wParam);
break;
}
}
if parent_hwnd {
EnableWindow(parent_hwnd, TRUE);
SetFocus(parent_hwnd);
}
return state.thread_data.success, state.thread_data.error_msg;
}

760
main.jai

File diff suppressed because it is too large Load Diff

View File

@ -1,58 +0,0 @@
# Planning: Sing-box Windows Tray Controller in Jai
We will implement a lightweight, headless Windows tray application in the **Jai** language to control the `sing-box` core.
## 1. Core Architecture
The application will run with a hidden/message-only window to remain headless, handling tray interaction, background updates, and process management.
```mermaid
graph TD
A[Main Loop / Hidden Window] --> B[System Tray Icon]
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: WinINet]
C -->|Exit| G[Shutdown & Cleanup]
F -->|Timer/Thread| F
```
## 2. Component Design
### A. Headless Window & System Tray Icon
- **Win32 Window**: We register a window class and create a hidden utility window using `CreateWindowExW`. This window serves as the message receiver.
- **Tray Icon**: We register a system tray icon via `Shell_NotifyIconW` (using `NOTIFYICONDATAW`).
- **Events**: We define a custom window message `WM_TRAY_CALLBACK` (`WM_USER + 1`). When the tray icon receives input (e.g. mouse clicks), Windows sends `WM_TRAY_CALLBACK` to our hidden window.
- **Context Menu**: On `WM_RBUTTONUP` (right click) or `WM_LBUTTONUP` (left click) over the tray icon, we load a dynamic popup menu using `CreatePopupMenu`, `AppendMenuW`, and `TrackPopupMenu`.
### B. Custom Modal Dialog (Set URL)
To avoid needing an external resource compiler (`.rc`) or complex UI libraries, we will implement a clean, lightweight modal window:
- **Dialog Window**: A popup window using `WS_POPUP | WS_CAPTION | WS_SYSMENU` centered on screen.
- **Controls**:
- A static label: "Enter Sing-box Config URL:"
- An edit control (`EDIT` window class) to input the URL.
- An OK button and a Cancel button (`BUTTON` window class).
- **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)
- **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.
### D. Process Management (`sing-box.exe`)
- **Process Spawning**: Use the standard `Process` module's `create_process` to run:
`sing-box.exe run -c config.json`
- **Job Object**: Windows processes started via `create_process` will be assigned to a Job Object with `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`. This ensures `sing-box.exe` automatically shuts down when the controller application is closed.
- **Status Checking**: Use `get_process_result` with 0 timeout to check if sing-box is actively running.
## 3. Implementation Steps
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`**: 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
- **Tray Icon**: Do we need a default/builtin icon? Yes, we will use a standard Windows system icon (like `IDI_APPLICATION` or `IDI_SHIELD`) so that the application works out-of-the-box without requiring an external `.ico` asset.
- **sing-box Location**: We will assume `sing-box.exe` is in the same directory as the controller. If not found there, we will search the system PATH.

View File

@ -12,11 +12,87 @@ Updater_Thread_Data :: struct {
is_test_mode: bool;
}
get_remote_file_size :: (url: string) -> u64, bool, string {
hInternet := InternetOpenW(
utf8_to_wide("Sing-boxTrayDownloader"),
INTERNET_OPEN_TYPE_PRECONFIG,
null,
null,
0
);
if !hInternet {
return 0, false, "Failed to initialize WinINet session";
}
defer InternetCloseHandle(hInternet);
flags: DWORD = INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE;
if starts_with(url, "https") {
flags |= INTERNET_FLAG_SECURE;
}
headers := "User-Agent: Sing-boxTray\r\n";
hUrl := InternetOpenUrlW(
hInternet,
utf8_to_wide(url),
utf8_to_wide(headers),
cast(u32) headers.count,
flags,
0
);
if !hUrl {
err := GetLastError();
return 0, false, tprint("Failed to open URL (Win32 Error: %)", err);
}
defer InternetCloseHandle(hUrl);
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) {
err := GetLastError();
return 0, false, tprint("Failed to query HTTP status code (Win32 Error: %)", err);
}
if response_code != 200 {
return 0, false, tprint("HTTP request failed with status code %", response_code);
}
content_length: DWORD;
content_length_size: DWORD = size_of(type_of(content_length));
if !HttpQueryInfoW(hUrl, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, *content_length, *content_length_size, null) {
err := GetLastError();
return 0, false, tprint("Failed to query download size (Win32 Error: %)", err);
}
return cast(u64) content_length, true, "";
}
format_file_size :: (bytes: u64) -> string {
mib: u64 = 1024 * 1024;
kib: u64 = 1024;
if bytes >= mib {
whole := bytes / mib;
tenths := (bytes % mib) * 10 / mib;
return tprint("%.% MiB", whole, tenths);
}
if bytes >= kib {
whole := bytes / kib;
tenths := (bytes % kib) * 10 / kib;
return tprint("%.% KiB", whole, tenths);
}
return tprint("% bytes", bytes);
}
download_url :: (url: string) -> string, bool, string {
log_info("Downloading config from URL: %\n", url);
if file_exists(url) {
content, ok := read_entire_file(url);
if ok return content, true, "";
return "", false, tprint("Failed to read local file: %", url);
}
hInternet := InternetOpenW(
utf8_to_wide("SingboxTrayUpdater"),
utf8_to_wide("Sing-boxTrayUpdater"),
INTERNET_OPEN_TYPE_PRECONFIG,
null,
null,
@ -32,11 +108,12 @@ download_url :: (url: string) -> string, bool, string {
flags |= INTERNET_FLAG_SECURE;
}
headers := "User-Agent: Sing-boxTray\r\n";
hUrl := InternetOpenUrlW(
hInternet,
utf8_to_wide(url),
null,
0,
utf8_to_wide(headers),
cast(u32) headers.count,
flags,
0
);
@ -81,7 +158,7 @@ download_url :: (url: string) -> string, bool, string {
}
perform_update :: (is_test_mode := false) -> changed: bool, success: bool, error_msg: string {
config_filename := ifx is_test_mode then "config_test.json" else "config.json";
config_filename := global_config_path;
config_data, read_ok := read_entire_file(config_filename);
if !read_ok {
@ -340,3 +417,88 @@ strip_json_metadata :: (json: string) -> string {
return builder_to_string(*builder);
}
download_file :: (url: string, dest_path: string) -> bool, string {
log_info("Downloading file from URL: % to %\n", url, dest_path);
hInternet := InternetOpenW(
utf8_to_wide("Sing-boxTrayDownloader"),
INTERNET_OPEN_TYPE_PRECONFIG,
null,
null,
0
);
if !hInternet {
return false, "Failed to initialize WinINet session";
}
defer InternetCloseHandle(hInternet);
flags: DWORD = INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE;
if starts_with(url, "https") {
flags |= INTERNET_FLAG_SECURE;
}
headers := "User-Agent: Sing-boxTray\r\n";
hUrl := InternetOpenUrlW(
hInternet,
utf8_to_wide(url),
utf8_to_wide(headers),
cast(u32) headers.count,
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);
}
// Open destination file for writing
hFile := CreateFileW(
utf8_to_wide(dest_path),
GENERIC_WRITE,
0,
null,
2, // CREATE_ALWAYS
0x80, // FILE_ATTRIBUTE_NORMAL
null
);
if hFile == INVALID_HANDLE_VALUE {
err := GetLastError();
return false, tprint("Failed to create local file (Win32 Error: %)", err);
}
defer CloseHandle(hFile);
buffer: [8192] u8;
bytes_read: DWORD;
bytes_written: 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;
}
write_ok := WriteFile(hFile, buffer.data, bytes_read, *bytes_written, null);
if !write_ok || bytes_written != bytes_read {
err := GetLastError();
return false, tprint("Error writing to local file (Win32 Error: %)", err);
}
}
return true, "";
}

View File

@ -164,6 +164,10 @@ CMD_UPDATE_12H :: 1014;
CMD_UPDATE_DAILY :: 1015;
CMD_UPDATE_3D :: 1016;
CMD_UPDATE_WEEKLY :: 1017;
CMD_CONFIG_DIR :: 1018;
CMD_UPDATE_CORE :: 1019;
CMD_RESTART :: 1020;
// Boolean constants
FALSE :: 0;
@ -204,6 +208,8 @@ GetWindowTextLengthW :: (hWnd: HWND) -> s32 #foreign user32;
SendMessageW :: (hWnd: HWND, Msg: u32, wParam: WPARAM, lParam: LPARAM) -> LRESULT #foreign user32;
SetEvent :: (hEvent: HANDLE) -> BOOL #foreign kernel32;
GetEnvironmentVariableW :: (lpName: *u16, lpBuffer: *u16, nSize: u32) -> u32 #foreign kernel32;
CreateDirectoryW :: (lpPathName: *u16, lpSecurityAttributes: *void) -> BOOL #foreign kernel32;
SetFocus :: (hWnd: HWND) -> HWND #foreign user32;
LOWORD :: (val: WPARAM) -> u16 {
@ -253,6 +259,7 @@ INTERNET_FLAG_SECURE :: 0x00800000;
INTERNET_FLAG_NO_CACHE_WRITE :: 0x04000000;
HTTP_QUERY_STATUS_CODE :: 19;
HTTP_QUERY_CONTENT_LENGTH :: 5;
HTTP_QUERY_FLAG_NUMBER :: 0x20000000;
InternetOpenW :: (
@ -314,6 +321,15 @@ SHGetStockIconInfo :: (
psii: *SHSTOCKICONINFO
) -> s32 #foreign shell32;
ShellExecuteW :: (
hwnd: HWND,
lpOperation: LPCWSTR,
lpFile: LPCWSTR,
lpParameters: LPCWSTR,
lpDirectory: LPCWSTR,
nShowCmd: s32
) -> HANDLE #foreign shell32;
DestroyIcon :: (hIcon: HICON) -> BOOL #foreign user32;
wcslen :: (str: *u16) -> int {
@ -361,4 +377,3 @@ RGB :: (r: u32, g: u32, b: u32) -> u32 {