#import "Basic"; #import "Windows"; #import "Windows_Utf8"; #import "Thread"; #import "String"; #load "win32.jai"; #load "dialog.jai"; #load "updater.jai"; global_userspace_dir: string; global_config_path: string; global_config_run_path: string; global_log_path: string; global_singbox_log_mutex: CRITICAL_SECTION; global_last_singbox_lines: [8] string; global_last_singbox_lines_count: int = 0; init_userspace_paths :: (is_test: bool) { name_wide := utf8_to_wide("LOCALAPPDATA"); len := GetEnvironmentVariableW(name_wide, null, 0); if len > 0 { buf := alloc(cast(s64) (len * 2)); defer free(buf); GetEnvironmentVariableW(name_wide, cast(*u16) buf, len); local_app_data := wide_to_utf8(cast(*u16) buf); defer free(local_app_data); global_userspace_dir = sprint("%\\sing-box-tray", local_app_data); } else { global_userspace_dir = copy_string("."); } // Ensure directory exists dir_wide := utf8_to_wide(global_userspace_dir); CreateDirectoryW(dir_wide, null); config_name := ifx is_test then "config_test.json" else "config.json"; config_run_name := ifx is_test then "config_run_test.json" else "config_run.json"; log_name := ifx is_test then "sing-box_tray_test.log" else "sing-box_tray.log"; global_config_path = sprint("%\\%", global_userspace_dir, config_name); global_config_run_path = sprint("%\\%", global_userspace_dir, config_run_name); global_log_path = sprint("%\\%", global_userspace_dir, log_name); // Migrate existing configuration if it exists in the executable's directory // but not in the userspace directory yet. if file_exists(config_name) { if !file_exists(global_config_path) { data, ok := read_entire_file(config_name); if ok { write_entire_file(global_config_path, data); free(data); } } } if !is_test { test_config := sprint("%\\config_test.json", global_userspace_dir); defer free(test_config); test_config_run := sprint("%\\config_run_test.json", global_userspace_dir); defer free(test_config_run); test_log := sprint("%\\sing-box_tray_test.log", global_userspace_dir); defer free(test_log); test_singbox_log := sprint("%\\sing-box_test.log", global_userspace_dir); defer free(test_singbox_log); DeleteFileW(utf8_to_wide(test_config)); DeleteFileW(utf8_to_wide(test_config_run)); DeleteFileW(utf8_to_wide(test_log)); DeleteFileW(utf8_to_wide(test_singbox_log)); } } // Custom constants TIMER_PROCESS_CHECK :: 1; TIMER_ANIMATION :: 2; IDI_APPLICATION :: cast(*u16) 32512; IDI_SHIELD :: cast(*u16) 32518; Log_Reader_Data :: struct { pipe_read_handle: HANDLE; is_test_mode: bool; } App_State :: struct { hwnd: HWND; singbox_running: bool; singbox_process_handle: HANDLE; singbox_thread_handle: HANDLE; singbox_job_handle: HANDLE; updater_thread: Thread; updater_data: Updater_Thread_Data; use_system_proxy: bool = true; port: int = 10801; icon_running: HICON; icon_stopped: HICON; is_connecting: bool; connecting_ticks: int; is_updating: bool; animation_frame: int; icon_frames: [4] HICON; is_test_mode: bool; log_reader_thread: Thread; log_reader_data: Log_Reader_Data; log_reader_started: bool; local_version: string; latest_web_version: string; version_checker_thread: Thread; version_checker_data: Version_Checker_Data; } Log_Level :: enum { INFO; WARN; ERROR; DEBUG; } append_to_log_file :: (text: string) { OPEN_ALWAYS :: 4; FILE_END :: 2; filename := ifx global_log_path.count > 0 then global_log_path else (ifx global_is_test_mode then "sing-box_tray_test.log" else "sing-box_tray.log"); wide_path := utf8_to_wide(filename); hFile := CreateFileW( wide_path, GENERIC_WRITE, FILE_SHARE_READ, null, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, null ); if hFile != INVALID_HANDLE_VALUE { SetFilePointer(hFile, 0, null, FILE_END); bytes_written: DWORD; WriteFile(hFile, text.data, xx text.count, *bytes_written, null); CloseHandle(hFile); } } log_write :: (level: Log_Level, format_string: string, args: .. Any) { st: SYSTEMTIME; GetLocalTime(*st); time_str := tprint("[%-%-% %:%:%.%]", formatInt(st.wYear, minimum_digits = 4), formatInt(st.wMonth, minimum_digits = 2), formatInt(st.wDay, minimum_digits = 2), formatInt(st.wHour, minimum_digits = 2), formatInt(st.wMinute, minimum_digits = 2), formatInt(st.wSecond, minimum_digits = 2), formatInt(st.wMilliseconds, minimum_digits = 3) ); level_str := ""; color_prefix := ""; if level == { case .INFO; level_str = "[TRAY] [INFO]"; color_prefix = "\x1b[32m"; // Green case .WARN; level_str = "[TRAY] [WARN]"; color_prefix = "\x1b[33m"; // Yellow case .ERROR; level_str = "[TRAY] [ERROR]"; color_prefix = "\x1b[31m"; // Red case .DEBUG; level_str = "[TRAY] [DEBUG]"; color_prefix = "\x1b[36m"; // Cyan } message := tprint(format_string, .. args); while message.count > 0 && (message[message.count - 1] == #char "\n" || message[message.count - 1] == #char "\r") { message.count -= 1; } // Colored format for log viewers that support ANSI escape codes // \x1b[90m is Dark Grey/Dim for timestamps, \x1b[0m resets color colored_log := tprint("\x1b[90m%\x1b[0m %%\x1b[0m %\n", time_str, color_prefix, level_str, message); // Write to OutputDebugStringW (useful for live debugging in VS / DebugView) OutputDebugStringW(utf8_to_wide(colored_log)); // Append to sing-box_tray.log file append_to_log_file(colored_log); } log_singbox :: (message: string, is_test_mode: bool) { st: SYSTEMTIME; GetLocalTime(*st); time_str := tprint("[%-%-% %:%:%.%]", formatInt(st.wYear, minimum_digits = 4), formatInt(st.wMonth, minimum_digits = 2), formatInt(st.wDay, minimum_digits = 2), formatInt(st.wHour, minimum_digits = 2), formatInt(st.wMinute, minimum_digits = 2), formatInt(st.wSecond, minimum_digits = 2), formatInt(st.wMilliseconds, minimum_digits = 3) ); // [SING-BOX] tag in Magenta (\x1b[35m) colored_log := tprint("\x1b[90m%\x1b[0m \x1b[35m[SING-BOX]\x1b[0m %\n", time_str, message); OutputDebugStringW(utf8_to_wide(colored_log)); // Append to the correct log file filename := ifx global_log_path.count > 0 then global_log_path else (ifx is_test_mode then "sing-box_tray_test.log" else "sing-box_tray.log"); wide_path := utf8_to_wide(filename); hFile := CreateFileW( wide_path, GENERIC_WRITE, FILE_SHARE_READ, null, 4, // OPEN_ALWAYS 0x80, // FILE_ATTRIBUTE_NORMAL null ); if hFile != INVALID_HANDLE_VALUE { SetFilePointer(hFile, 0, null, 2); // FILE_END bytes_written: DWORD; WriteFile(hFile, colored_log.data, xx colored_log.count, *bytes_written, null); CloseHandle(hFile); } // Store in ring buffer EnterCriticalSection(*global_singbox_log_mutex); defer LeaveCriticalSection(*global_singbox_log_mutex); if global_last_singbox_lines_count < 8 { global_last_singbox_lines[global_last_singbox_lines_count] = copy_string(message); global_last_singbox_lines_count += 1; } else { free(global_last_singbox_lines[0]); for i: 0..6 { global_last_singbox_lines[i] = global_last_singbox_lines[i+1]; } global_last_singbox_lines[7] = copy_string(message); } } log_reader_thread_proc :: (thread: *Thread) -> s64 { data := cast(*Log_Reader_Data) thread.data; if !data return 1; buffer: [1024] u8 = ---; bytes_read: DWORD; line_builder: String_Builder; defer free_buffers(*line_builder); while true { ok := ReadFile(data.pipe_read_handle, buffer.data, xx buffer.count, *bytes_read, null); if !ok || bytes_read == 0 { break; } for i: 0..bytes_read-1 { char := buffer[i]; if char == #char "\n" { line := builder_to_string(*line_builder); // strip trailing \r if present if line.count > 0 && line[line.count - 1] == #char "\r" { line.count -= 1; } log_singbox(line, data.is_test_mode); free(line); } else { append(*line_builder, char); } } } // Process any remaining bytes remaining := builder_to_string(*line_builder); if remaining.count > 0 { if remaining[remaining.count - 1] == #char "\r" { remaining.count -= 1; } log_singbox(remaining, data.is_test_mode); } free(remaining); CloseHandle(data.pipe_read_handle); return 0; } log_info :: (format_string: string, args: .. Any) { log_write(.INFO, format_string, ..args); } log_warn :: (format_string: string, args: .. Any) { log_write(.WARN, format_string, ..args); } log_error :: (format_string: string, args: .. Any) { log_write(.ERROR, format_string, ..args); } log_debug :: (format_string: string, args: .. Any) { log_write(.DEBUG, format_string, ..args); } log_print :: (format_string: string, args: .. Any) { log_info(format_string, ..args); } file_exists :: (path: string) -> bool { INVALID_FILE_ATTRIBUTES :: 0xFFFFFFFF; wide_path := utf8_to_wide(path); attribs := GetFileAttributesW(wide_path); 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("Sing-boxTray"); 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("Sing-boxTray"); 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, working_dir: string = "") -> HANDLE, PROCESS_INFORMATION, bool { startup_info: STARTUPINFOW; startup_info.cb = size_of(STARTUPINFOW); startup_info.dwFlags = STARTF_USESHOWWINDOW; startup_info.wShowWindow = SW_HIDE; if stdout_handle != INVALID_HANDLE_VALUE { startup_info.dwFlags |= STARTF_USESTDHANDLES; startup_info.hStdOutput = stdout_handle; startup_info.hStdError = stdout_handle; } process_info: PROCESS_INFORMATION; CREATE_NO_WINDOW :: 0x08000000; job_handle := CreateJobObjectA(null, null); if job_handle { job_info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION; job_info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; SetInformationJobObject(job_handle, .ExtendedLimitInformation, *job_info, size_of(type_of(job_info))); } cmd_wide := utf8_to_wide(cmd); inherit_handles := ifx stdout_handle != INVALID_HANDLE_VALUE then cast(BOOL) 1 else cast(BOOL) 0; working_dir_wide: *u16 = null; if working_dir.count > 0 { working_dir_wide = utf8_to_wide(working_dir); } success := CreateProcessW( null, cmd_wide, null, null, inherit_handles, CREATE_NO_WINDOW, null, working_dir_wide, *startup_info, *process_info ); if success && job_handle { AssignProcessToJobObject(job_handle, process_info.hProcess); } return job_handle, process_info, cast(bool) success; } set_tray_tip :: (nid: *NOTIFYICONDATAW, text: string) { wide_text := utf8_to_wide(text); i := 0; while wide_text[i] != 0 && i < 127 { nid.szTip[i] = wide_text[i]; i += 1; } nid.szTip[i] = 0; } update_tray :: (app: *App_State) { nid: NOTIFYICONDATAW; nid.cbSize = size_of(NOTIFYICONDATAW); nid.hWnd = app.hwnd; nid.uID = 1; nid.uFlags = NIF_ICON | NIF_TIP; if app.is_connecting { nid.hIcon = app.icon_frames[app.animation_frame]; set_tray_tip(*nid, "Sing-box: Connecting..."); } else if app.is_updating { nid.hIcon = app.icon_frames[app.animation_frame]; set_tray_tip(*nid, "Sing-box: Updating Config..."); } else if app.singbox_running { nid.hIcon = app.icon_running; set_tray_tip(*nid, "Sing-box: Running"); } else { nid.hIcon = app.icon_stopped; set_tray_tip(*nid, "Sing-box: Stopped"); } Shell_NotifyIconW(NIM_MODIFY, *nid); } clear_last_singbox_logs :: () { EnterCriticalSection(*global_singbox_log_mutex); defer LeaveCriticalSection(*global_singbox_log_mutex); for i: 0..global_last_singbox_lines_count-1 { free(global_last_singbox_lines[i]); } global_last_singbox_lines_count = 0; } get_last_singbox_logs_string :: () -> string { EnterCriticalSection(*global_singbox_log_mutex); defer LeaveCriticalSection(*global_singbox_log_mutex); builder: String_Builder; for i: 0..global_last_singbox_lines_count-1 { append(*builder, global_last_singbox_lines[i]); append(*builder, "\n"); } return builder_to_string(*builder); } find_singbox_in_userspace :: () -> string { search_path := tprint("%\\*", global_userspace_dir); find_data: WIN32_FIND_DATAW; hFind := FindFirstFileW(utf8_to_wide(search_path), *find_data); if hFind != INVALID_HANDLE_VALUE { defer FindClose(hFind); while true { filename := wide_to_utf8(find_data.cFileName.data); defer free(filename); if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if filename != "." && filename != ".." { sub_exe := tprint("%\\%\\sing-box.exe", global_userspace_dir, filename); if file_exists(sub_exe) { return sub_exe; } } } if !FindNextFileW(hFind, *find_data) break; } } return ""; } run_command_blocking :: (cmd: string, working_dir: string = "") -> success: bool, exit_code: u32 { startup_info: STARTUPINFOW; startup_info.cb = size_of(STARTUPINFOW); startup_info.dwFlags = STARTF_USESHOWWINDOW; startup_info.wShowWindow = SW_HIDE; process_info: PROCESS_INFORMATION; CREATE_NO_WINDOW :: 0x08000000; cmd_wide := utf8_to_wide(cmd); working_dir_wide: *u16 = null; if working_dir.count > 0 { working_dir_wide = utf8_to_wide(working_dir); } success := CreateProcessW( null, cmd_wide, null, null, 0, CREATE_NO_WINDOW, null, working_dir_wide, *startup_info, *process_info ); if !success return false, 0; WaitForSingleObject(process_info.hProcess, INFINITE); exit_code: u32; GetExitCodeProcess(process_info.hProcess, *exit_code); CloseHandle(process_info.hProcess); CloseHandle(process_info.hThread); return true, exit_code; } get_command_output :: (cmd: string, working_dir: string = "") -> string { hReadPipe, hWritePipe: HANDLE; sa: SECURITY_ATTRIBUTES; sa.nLength = size_of(SECURITY_ATTRIBUTES); sa.bInheritHandle = 1; // TRUE sa.lpSecurityDescriptor = null; if !CreatePipe(*hReadPipe, *hWritePipe, *sa, 0) return ""; SetHandleInformation(hReadPipe, HANDLE_FLAG_INHERIT, 0); startup_info: STARTUPINFOW; startup_info.cb = size_of(STARTUPINFOW); startup_info.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; startup_info.wShowWindow = SW_HIDE; startup_info.hStdOutput = hWritePipe; startup_info.hStdError = hWritePipe; process_info: PROCESS_INFORMATION; CREATE_NO_WINDOW :: 0x08000000; cmd_wide := utf8_to_wide(cmd); working_dir_wide: *u16 = null; if working_dir.count > 0 { working_dir_wide = utf8_to_wide(working_dir); } success := CreateProcessW( null, cmd_wide, null, null, 1, // TRUE to inherit handles CREATE_NO_WINDOW, null, working_dir_wide, *startup_info, *process_info ); CloseHandle(hWritePipe); // Close write end so read returns 0 on EOF if !success { CloseHandle(hReadPipe); return ""; } builder: String_Builder; defer free_buffers(*builder); buffer: [1024] u8; bytes_read: DWORD; while true { ok := ReadFile(hReadPipe, buffer.data, xx buffer.count, *bytes_read, null); if !ok || bytes_read == 0 break; append(*builder, buffer.data, xx bytes_read); } WaitForSingleObject(process_info.hProcess, INFINITE); CloseHandle(process_info.hProcess); CloseHandle(process_info.hThread); CloseHandle(hReadPipe); return builder_to_string(*builder); } get_local_singbox_version :: () -> string { exe_path := tprint("%\\sing-box.exe", global_userspace_dir); if !file_exists(exe_path) return copy_string("Not Installed"); cmd := tprint("\"%\" version", exe_path); output := get_command_output(cmd, global_userspace_dir); defer free(output); if output.count == 0 return copy_string("Unknown"); lines := split(output, "\n"); defer array_free(lines); if lines.count > 0 { first_line := trim(lines[0]); prefix := "sing-box version "; if starts_with(first_line, prefix) { ver := slice(first_line, prefix.count, first_line.count - prefix.count); space_idx := find_index_from_left(ver, " "); if space_idx != -1 { ver = slice(ver, 0, space_idx); } return copy_string(ver); } return copy_string(first_line); } return copy_string("Unknown"); } fetch_latest_web_version :: () -> string { url := "https://api.github.com/repos/SagerNet/sing-box/releases/latest"; hInternet := InternetOpenW( utf8_to_wide("Sing-boxTray"), INTERNET_OPEN_TYPE_PRECONFIG, null, null, 0 ); if !hInternet return ""; defer InternetCloseHandle(hInternet); headers := "User-Agent: Sing-boxTray\r\n"; hUrl := InternetOpenUrlW( hInternet, utf8_to_wide(url), utf8_to_wide(headers), cast(u32) headers.count, INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_SECURE, 0 ); if !hUrl return ""; 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) { if response_code != 200 return ""; } else { return ""; } builder: String_Builder; defer free_buffers(*builder); buffer: [4096] u8; bytes_read: DWORD; while true { ok := InternetReadFile(hUrl, buffer.data, xx buffer.count, *bytes_read); if !ok || bytes_read == 0 break; append(*builder, buffer.data, xx bytes_read); } json_data := builder_to_string(*builder); defer free(json_data); tag := get_json_string_field(json_data, "\"tag_name\""); if tag.count > 0 { if tag[0] == #char "v" { return copy_string(slice(tag, 1, tag.count - 1)); } return copy_string(tag); } return ""; } Version_Checker_Data :: struct { app: *App_State; } version_checker_thread_proc :: (thread: *Thread) -> s64 { data := cast(*Version_Checker_Data) thread.data; if !data return 1; latest := fetch_latest_web_version(); if latest.count > 0 { EnterCriticalSection(*global_singbox_log_mutex); if data.app.latest_web_version.count > 0 { free(data.app.latest_web_version); } data.app.latest_web_version = latest; LeaveCriticalSection(*global_singbox_log_mutex); } else { EnterCriticalSection(*global_singbox_log_mutex); if data.app.latest_web_version.count > 0 { free(data.app.latest_web_version); } data.app.latest_web_version = copy_string("Unknown"); LeaveCriticalSection(*global_singbox_log_mutex); } return 0; } install_or_update_singbox_core :: (app: *App_State, is_update: bool) -> bool { clear_last_singbox_logs(); version_to_download := "1.13.14"; // fallback EnterCriticalSection(*global_singbox_log_mutex); if app.latest_web_version != "Checking..." && app.latest_web_version != "Unknown" && app.latest_web_version.count > 0 { version_to_download = app.latest_web_version; } LeaveCriticalSection(*global_singbox_log_mutex); download_url := tprint("https://github.com/SagerNet/sing-box/releases/download/v%/sing-box-%-windows-amd64.zip", version_to_download, version_to_download); temp_zip := tprint("%\\sing-box-temp.zip", global_userspace_dir); DeleteFileW(utf8_to_wide(temp_zip)); // remove any stale/partial from previous failed attempt exe_path := tprint("%\\sing-box.exe", global_userspace_dir); prompt_title := ifx is_update then "Update Sing-box" else "Sing-box Core Installation"; success_msg := tprint("Sing-box core % successfully!", ifx is_update then "updated" else "installed"); // 1. Download while VPN is still active success, err_msg := show_download_progress_dialog(app.hwnd, download_url, temp_zip); if !success { msg := tprint("Failed to download Sing-box core.\nError: %\n\nPlease ensure you have an active internet connection.", err_msg); MessageBoxW(app.hwnd, utf8_to_wide(msg), utf8_to_wide("Sing-box Tray"), MB_ICONERROR); if err_msg.count > 0 { free(err_msg); } return false; } if err_msg.count > 0 { free(err_msg); } // free the (copied) error string returned from dialog on success path too (usually "") // 2. Stop and forcefully terminate any running sing-box instances to unlock the files was_running := app.singbox_running; if was_running { stop_singbox(app); } // Kill any other running instances of sing-box.exe (e.g. from the main tray app or orphaned processes) run_command_blocking("taskkill /f /im sing-box.exe"); Sleep(500); // Give OS some time to release file locks // 3. Extract the zip file tar_cmd := tprint("tar -xf \"%\" -C \"%\"", temp_zip, global_userspace_dir); tar_ok, exit_code := run_command_blocking(tar_cmd); if !tar_ok || exit_code != 0 { // Fallback to powershell ps_cmd := tprint("powershell.exe -NoProfile -NonInteractive -Command \"Expand-Archive -Path '%' -DestinationPath '%' -Force\"", temp_zip, global_userspace_dir); ps_ok, ps_exit := run_command_blocking(ps_cmd); if !ps_ok || ps_exit != 0 { MessageBoxW(app.hwnd, utf8_to_wide("Failed to extract Sing-box core zip archive using tar or PowerShell."), utf8_to_wide("Sing-box Tray"), MB_ICONERROR); DeleteFileW(utf8_to_wide(temp_zip)); if was_running start_singbox(app); return false; } } found_exe := find_singbox_in_userspace(); if found_exe.count > 0 { if found_exe != exe_path { // Delete old files first to make sure MoveFileW does not fail DeleteFileW(utf8_to_wide(exe_path)); move_ok := MoveFileW(utf8_to_wide(found_exe), utf8_to_wide(exe_path)); if !move_ok { err := GetLastError(); msg := tprint("Failed to replace sing-box.exe. Error code: %", err); MessageBoxW(app.hwnd, utf8_to_wide(msg), utf8_to_wide("Sing-box Tray"), MB_ICONERROR); DeleteFileW(utf8_to_wide(temp_zip)); if was_running start_singbox(app); return false; } // Also move libcronet.dll if present dir_part := found_exe; if ends_with(dir_part, "\\sing-box.exe") { dir_part.count -= 13; } lib_path := tprint("%\\libcronet.dll", dir_part); if file_exists(lib_path) { dest_lib := tprint("%\\libcronet.dll", global_userspace_dir); DeleteFileW(utf8_to_wide(dest_lib)); MoveFileW(utf8_to_wide(lib_path), utf8_to_wide(dest_lib)); } // Clean up the extracted subdirectory rm_cmd := tprint("cmd.exe /c rmdir /s /q \"%\"", dir_part); run_command_blocking(rm_cmd); } } else { MessageBoxW(app.hwnd, utf8_to_wide("Could not locate sing-box.exe in the extracted files."), utf8_to_wide("Sing-box Tray"), MB_ICONERROR); DeleteFileW(utf8_to_wide(temp_zip)); if was_running start_singbox(app); return false; } DeleteFileW(utf8_to_wide(temp_zip)); // Update cached local version new_ver := get_local_singbox_version(); EnterCriticalSection(*global_singbox_log_mutex); if app.local_version.count > 0 { free(app.local_version); } app.local_version = new_ver; LeaveCriticalSection(*global_singbox_log_mutex); MessageBoxW(app.hwnd, utf8_to_wide(success_msg), utf8_to_wide("Sing-box Tray"), MB_ICONINFORMATION); // Always start/restart to ensure the connection is restored immediately start_singbox(app); return true; } modify_config_cache_file :: (json: string, cache_path: string) -> string { replace_target :: "\"cache_file\": {"; replace_idx := find_index_from_left(json, replace_target); if replace_idx != -1 { builder: String_Builder; append(*builder, slice(json, 0, replace_idx + replace_target.count)); append(*builder, tprint(" \"path\": \"%\",", cache_path)); append(*builder, slice(json, replace_idx + replace_target.count, json.count - (replace_idx + replace_target.count))); return builder_to_string(*builder); } return copy_string(json); } start_singbox :: (app: *App_State) -> bool { if app.singbox_running return true; config_filename := global_config_path; config_run_filename := global_config_run_path; has_config := file_exists(config_filename); url_present := false; has_outbounds := false; if has_config { config_data, read_ok := read_entire_file(config_filename); if read_ok { url := get_json_string_field(config_data, "\"_url\""); if url { url_present = true; } if find_index_from_left(config_data, "\"outbounds\"") != -1 && find_index_from_left(config_data, "\"route\"") != -1 { has_outbounds = true; } free(config_data); } } if !has_config || !has_outbounds { if !url_present { // Prompt configuration dialog changed := show_config_url_dialog(app.hwnd, app.is_test_mode); if changed { // reload config to verify and fetch URL config_data, read_ok := read_entire_file(config_filename); if read_ok { url := get_json_string_field(config_data, "\"_url\""); if url { url_present = true; } free(config_data); } } } if url_present { // Trigger animation for downloading config app.is_updating = true; app.animation_frame = 0; SetTimer(app.hwnd, TIMER_ANIMATION, 150, null); update_tray(app); changed, success, err_msg := perform_update(app.is_test_mode); app.is_updating = false; KillTimer(app.hwnd, TIMER_ANIMATION); update_tray(app); if !success { msg := tprint("Could not download configuration.\nError: %", err_msg); MessageBoxW(app.hwnd, utf8_to_wide(msg), utf8_to_wide("Sing-box Tray"), MB_ICONERROR); return false; } } else { return false; } } if file_exists(config_filename) { config_data, read_ok := read_entire_file(config_filename); if read_ok { // Modify inbounds in memory with the custom port modified := modify_config_inbounds(config_data, app.port); // Strip custom fields (_url, _mode, _port) to avoid sing-box decoding error clean_config := strip_json_metadata(modified); // Redirect cache file in test mode to avoid database locking collisions final_config: string; if app.is_test_mode { final_config = modify_config_cache_file(clean_config, "cache_test.db"); free(clean_config); } else { final_config = clean_config; } write_entire_file(config_run_filename, final_config); log_print("Generated clean % for sing-box (port %).\n", config_run_filename, app.port); free(config_data); free(modified); free(final_config); } } exe_path := tprint("%\\sing-box.exe", global_userspace_dir); if !file_exists(exe_path) { response := MessageBoxW(app.hwnd, utf8_to_wide("Sing-box core executable (sing-box.exe) was not found in your userspace folder.\n\nWould you like to download and install it now?"), utf8_to_wide("Sing-box Tray"), MB_YESNO | MB_ICONQUESTION); if response == IDYES { installed := install_or_update_singbox_core(app, false); return installed; // install_or_update_singbox_core handles starting sing-box } else { return false; } } hReadPipe, hWritePipe: HANDLE; sa: SECURITY_ATTRIBUTES; sa.nLength = size_of(SECURITY_ATTRIBUTES); sa.bInheritHandle = 1; // TRUE sa.lpSecurityDescriptor = null; if !CreatePipe(*hReadPipe, *hWritePipe, *sa, 0) { log_error("Failed to create pipe for sing-box output redirection.\n"); hWritePipe = INVALID_HANDLE_VALUE; hReadPipe = INVALID_HANDLE_VALUE; } else { SetHandleInformation(hReadPipe, HANDLE_FLAG_INHERIT, 0); } clear_last_singbox_logs(); cmd := tprint("% run -c %", exe_path, config_run_filename); job, pi, ok := create_process_hidden(cmd, hWritePipe, global_userspace_dir); if hWritePipe != INVALID_HANDLE_VALUE { CloseHandle(hWritePipe); } if !ok { if hReadPipe != INVALID_HANDLE_VALUE CloseHandle(hReadPipe); msg := tprint("Failed to start Sing-box process.\n\nError: Could not spawn the process.\n\nPlease check the logs for more details at:\n%", global_log_path); MessageBoxW(app.hwnd, utf8_to_wide(msg), utf8_to_wide("Sing-box Error"), MB_ICONERROR); return false; } app.singbox_process_handle = pi.hProcess; app.singbox_thread_handle = pi.hThread; app.singbox_job_handle = job; app.singbox_running = true; if hReadPipe != INVALID_HANDLE_VALUE { app.log_reader_data.pipe_read_handle = hReadPipe; app.log_reader_data.is_test_mode = app.is_test_mode; thread_init(*app.log_reader_thread, log_reader_thread_proc); app.log_reader_thread.data = *app.log_reader_data; app.log_reader_started = true; thread_start(*app.log_reader_thread); } app.is_connecting = true; app.connecting_ticks = 0; app.animation_frame = 0; SetTimer(app.hwnd, TIMER_ANIMATION, 250, null); if app.use_system_proxy && !app.is_test_mode { set_windows_system_proxy(true, tprint("127.0.0.1:%", app.port)); log_print("System proxy enabled on port %.\n", app.port); } else if app.is_test_mode { log_info("Test Mode: System proxy configuration bypassed.\n"); } update_tray(app); log_print("Sing-box started successfully.\n"); return true; } stop_singbox :: (app: *App_State) { app.is_connecting = false; KillTimer(app.hwnd, TIMER_ANIMATION); if !app.singbox_running return; TerminateProcess(app.singbox_process_handle, 0); WaitForSingleObject(app.singbox_process_handle, 3000); CloseHandle(app.singbox_process_handle); CloseHandle(app.singbox_thread_handle); CloseHandle(app.singbox_job_handle); app.singbox_process_handle = null; app.singbox_thread_handle = null; app.singbox_job_handle = null; app.singbox_running = false; if app.log_reader_started { thread_is_done(*app.log_reader_thread, -1); thread_deinit(*app.log_reader_thread); app.log_reader_started = false; } if !app.is_test_mode { set_windows_system_proxy(false, ""); log_print("System proxy disabled.\n"); } else { log_info("Test Mode: System proxy bypass kept on stop.\n"); } DeleteFileW(utf8_to_wide(global_config_run_path)); update_tray(app); log_print("Sing-box stopped.\n"); } check_process_status :: (app: *App_State) { if !app.singbox_running return; exit_code: u32; success := GetExitCodeProcess(app.singbox_process_handle, *exit_code); STILL_ACTIVE :: 259; if success && exit_code != STILL_ACTIVE { log_error("Sing-box process exited unexpectedly with code %.\n", exit_code); CloseHandle(app.singbox_process_handle); CloseHandle(app.singbox_thread_handle); CloseHandle(app.singbox_job_handle); app.singbox_process_handle = null; app.singbox_thread_handle = null; app.singbox_job_handle = null; app.singbox_running = false; if app.log_reader_started { thread_is_done(*app.log_reader_thread, -1); thread_deinit(*app.log_reader_thread); app.log_reader_started = false; } if !app.is_test_mode { set_windows_system_proxy(false, ""); log_warn("System proxy disabled due to unexpected exit.\n"); } else { log_info("Test Mode: System proxy bypass kept on unexpected exit.\n"); } DeleteFileW(utf8_to_wide(global_config_run_path)); update_tray(app); logs_str := get_last_singbox_logs_string(); defer free(logs_str); msg := tprint("Sing-box process exited unexpectedly with code %.\n\nLast output logs:\n%\nFor more details, please view the full log file at:\n%", exit_code, logs_str, global_log_path); MessageBoxW(app.hwnd, utf8_to_wide(msg), utf8_to_wide("Sing-box Process Error"), MB_ICONERROR); } } trigger_immediate_update :: (app: *App_State) { app.is_updating = true; app.animation_frame = 0; SetTimer(app.hwnd, TIMER_ANIMATION, 150, null); update_tray(app); changed, success, err_msg := perform_update(app.is_test_mode); app.is_updating = false; if success { if changed { log_print("Config updated, restarting Sing-box...\n"); if app.singbox_running { stop_singbox(app); start_singbox(app); } else { start_singbox(app); } MessageBoxW(app.hwnd, utf8_to_wide("Configuration updated and Sing-box restarted successfully."), utf8_to_wide("Sing-box Tray"), MB_ICONINFORMATION); } else { if !app.singbox_running { start_singbox(app); } else { KillTimer(app.hwnd, TIMER_ANIMATION); update_tray(app); } MessageBoxW(app.hwnd, utf8_to_wide("Configuration is already up-to-date."), utf8_to_wide("Sing-box Tray"), MB_ICONINFORMATION); } } else { KillTimer(app.hwnd, TIMER_ANIMATION); update_tray(app); msg := tprint("Failed to download configuration.\nError: %", err_msg); MessageBoxW(app.hwnd, utf8_to_wide(msg), utf8_to_wide("Sing-box Tray"), MB_ICONERROR); } } is_newer_version :: (current: string, latest: string) -> bool { if current.count == 0 || latest.count == 0 return false; if current == "Unknown" || latest == "Unknown" || latest == "Checking..." return false; if current == "Not Installed" return false; c_parts := split(current, "."); defer array_free(c_parts); l_parts := split(latest, "."); defer array_free(l_parts); min_parts := ifx c_parts.count < l_parts.count then c_parts.count else l_parts.count; for i: 0..min_parts-1 { c_val, c_ok := to_integer(trim(c_parts[i])); l_val, l_ok := to_integer(trim(l_parts[i])); if c_ok && l_ok { if l_val > c_val return true; if c_val > l_val return false; } } if l_parts.count > c_parts.count return true; return false; } show_context_menu :: (app: *App_State) { hMenu := CreatePopupMenu(); if !hMenu return; defer DestroyMenu(hMenu); AppendMenuW(hMenu, MF_OWNERDRAW, CMD_STATUS, null); AppendMenuW(hMenu, MF_SEPARATOR, 0, null); AppendMenuW(hMenu, MF_STRING, CMD_RESTART, utf8_to_wide("Restart")); toggle_str := ifx app.singbox_running then "Stop Sing-box" else "Start Sing-box"; AppendMenuW(hMenu, MF_STRING, CMD_START_STOP, utf8_to_wide(toggle_str)); AppendMenuW(hMenu, MF_STRING, CMD_UPDATE_NOW, utf8_to_wide("Update Config Now")); hConfigureMenu := CreatePopupMenu(); AppendMenuW(hConfigureMenu, MF_STRING, CMD_SET_URL, utf8_to_wide("Configure URL...")); AppendMenuW(hConfigureMenu, MF_STRING, CMD_SET_PORT, utf8_to_wide("Configure Port...")); autostart_flags := MF_STRING; if is_autostart_enabled() { autostart_flags |= MF_CHECKED; } AppendMenuW(hConfigureMenu, autostart_flags, CMD_TOGGLE_AUTOSTART, utf8_to_wide("Start on Windows boot")); AppendMenuW(hConfigureMenu, MF_SEPARATOR, 0, null); EnterCriticalSection(*global_singbox_log_mutex); local_ver := copy_string(app.local_version); latest_ver := copy_string(app.latest_web_version); LeaveCriticalSection(*global_singbox_log_mutex); defer free(local_ver); defer free(latest_ver); local_ver_str := tprint("Current Core Version: %", local_ver); AppendMenuW(hConfigureMenu, MF_STRING | MF_GRAYED | MF_DISABLED, 0, utf8_to_wide(local_ver_str)); is_newer := is_newer_version(local_ver, latest_ver); if is_newer { latest_ver_str := tprint("✨ Update Available: %! 🚀", latest_ver); AppendMenuW(hConfigureMenu, MF_STRING, CMD_UPDATE_CORE, utf8_to_wide(latest_ver_str)); AppendMenuW(hConfigureMenu, MF_STRING, CMD_UPDATE_CORE, utf8_to_wide("👉 Update Sing-box Core Now!")); } else { latest_ver_str := tprint("Latest Core Version: %", latest_ver); AppendMenuW(hConfigureMenu, MF_STRING | MF_GRAYED | MF_DISABLED, 0, utf8_to_wide(latest_ver_str)); AppendMenuW(hConfigureMenu, MF_STRING, CMD_UPDATE_CORE, utf8_to_wide("Update Sing-box Core")); } AppendMenuW(hConfigureMenu, MF_SEPARATOR, 0, null); hUpdateMenu := CreatePopupMenu(); current_interval := app.updater_data.update_interval_seconds; AppendMenuW(hUpdateMenu, MF_STRING | (ifx current_interval == 0 then MF_CHECKED else 0), CMD_UPDATE_NEVER, utf8_to_wide("Never")); AppendMenuW(hUpdateMenu, MF_STRING | (ifx current_interval == 1800 then MF_CHECKED else 0), CMD_UPDATE_30M, utf8_to_wide("30 minutes")); AppendMenuW(hUpdateMenu, MF_STRING | (ifx current_interval == 3600 then MF_CHECKED else 0), CMD_UPDATE_1H, utf8_to_wide("1 hour")); AppendMenuW(hUpdateMenu, MF_STRING | (ifx current_interval == 21600 then MF_CHECKED else 0), CMD_UPDATE_6H, utf8_to_wide("6 hours")); AppendMenuW(hUpdateMenu, MF_STRING | (ifx current_interval == 43200 then MF_CHECKED else 0), CMD_UPDATE_12H, utf8_to_wide("12 hours")); AppendMenuW(hUpdateMenu, MF_STRING | (ifx current_interval == 86400 then MF_CHECKED else 0), CMD_UPDATE_DAILY, utf8_to_wide("Daily")); AppendMenuW(hUpdateMenu, MF_STRING | (ifx current_interval == 259200 then MF_CHECKED else 0), CMD_UPDATE_3D, utf8_to_wide("3 days")); AppendMenuW(hUpdateMenu, MF_STRING | (ifx current_interval == 604800 then MF_CHECKED else 0), CMD_UPDATE_WEEKLY, utf8_to_wide("weekly")); AppendMenuW(hConfigureMenu, MF_POPUP, cast(s64) hUpdateMenu, utf8_to_wide("Auto-update")); AppendMenuW(hConfigureMenu, MF_SEPARATOR, 0, null); AppendMenuW(hConfigureMenu, MF_STRING, CMD_CONFIG_DIR, utf8_to_wide("Config directory...")); AppendMenuW(hMenu, MF_POPUP, cast(s64) hConfigureMenu, utf8_to_wide("Configure")); AppendMenuW(hMenu, MF_SEPARATOR, 0, null); proxy_flags := MF_STRING; sys_proxy_flags := MF_STRING; if app.use_system_proxy { sys_proxy_flags |= MF_CHECKED; } else { proxy_flags |= MF_CHECKED; } AppendMenuW(hMenu, proxy_flags, CMD_MODE_PROXY, utf8_to_wide("Proxy Mode (Local only)")); AppendMenuW(hMenu, sys_proxy_flags, CMD_MODE_SYS_PROXY, utf8_to_wide("System Proxy Mode")); AppendMenuW(hMenu, MF_SEPARATOR, 0, null); AppendMenuW(hMenu, MF_STRING, CMD_EXIT, utf8_to_wide("Exit")); cursor_pos: POINT; GetCursorPos(*cursor_pos); SetForegroundWindow(app.hwnd); track_flags := TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD; cmd := TrackPopupMenu(hMenu, track_flags, cursor_pos.x, cursor_pos.y, 0, app.hwnd, null); if cmd == { case CMD_RESTART; { log_print("Restart requested, forcing config refresh to select new server.\n"); if app.singbox_running { stop_singbox(app); } // Force re-download of config (remote may assign different server) then (re)start sing-box. app.is_updating = true; app.animation_frame = 0; SetTimer(app.hwnd, TIMER_ANIMATION, 150, null); update_tray(app); _, success, err_msg := perform_update(app.is_test_mode); app.is_updating = false; KillTimer(app.hwnd, TIMER_ANIMATION); update_tray(app); if success { start_singbox(app); } else { msg := tprint("Failed to refresh configuration for restart.\nError: %", err_msg); MessageBoxW(app.hwnd, utf8_to_wide(msg), utf8_to_wide("Sing-box Tray"), MB_ICONERROR); } } case CMD_START_STOP; { if app.singbox_running { stop_singbox(app); } else { start_singbox(app); } } case CMD_SET_URL; { changed := show_config_url_dialog(app.hwnd, app.is_test_mode); if changed { log_print("URL configured, triggering download...\n"); trigger_immediate_update(app); } } case CMD_SET_PORT; { changed := show_config_port_dialog(app.hwnd, app.is_test_mode); if changed { log_print("Port configured, updating state...\n"); config_filename := global_config_path; config_data, read_ok := read_entire_file(config_filename); if read_ok { port_str := get_json_val_field(config_data, "\"_port\""); if port_str { val, ok := to_integer(port_str); if ok { app.port = xx val; log_print("Port updated to %.\n", app.port); } } free(config_data); } if app.singbox_running { stop_singbox(app); start_singbox(app); } } } case CMD_UPDATE_NOW; { trigger_immediate_update(app); } case CMD_MODE_PROXY; { if app.use_system_proxy { app.use_system_proxy = false; config_filename := global_config_path; config_data, read_ok := read_entire_file(config_filename); if read_ok { url := get_json_string_field(config_data, "\"_url\""); updated := set_json_metadata(config_data, url, "proxy", app.port, app.updater_data.update_interval_seconds); write_entire_file(config_filename, updated); free(config_data); free(updated); } log_print("Switched to Proxy Mode.\n"); if app.singbox_running { if !app.is_test_mode { set_windows_system_proxy(false, ""); log_print("System proxy disabled.\n"); } stop_singbox(app); start_singbox(app); } } } case CMD_MODE_SYS_PROXY; { if !app.use_system_proxy { app.use_system_proxy = true; config_filename := global_config_path; config_data, read_ok := read_entire_file(config_filename); if read_ok { url := get_json_string_field(config_data, "\"_url\""); updated := set_json_metadata(config_data, url, "system_proxy", app.port, app.updater_data.update_interval_seconds); write_entire_file(config_filename, updated); free(config_data); free(updated); } log_print("Switched to System Proxy Mode.\n"); if app.singbox_running { stop_singbox(app); start_singbox(app); } } } 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_CONFIG_DIR; { ShellExecuteW(null, utf8_to_wide("open"), utf8_to_wide(global_userspace_dir), null, null, 1); } case CMD_UPDATE_CORE; { install_or_update_singbox_core(app, true); } case CMD_UPDATE_NEVER; { save_update_interval(app, 0); log_print("Auto-update interval set to: Never\n"); } case CMD_UPDATE_30M; { save_update_interval(app, 1800); log_print("Auto-update interval set to: 30 minutes\n"); } case CMD_UPDATE_1H; { save_update_interval(app, 3600); log_print("Auto-update interval set to: 1 hour\n"); } case CMD_UPDATE_6H; { save_update_interval(app, 21600); log_print("Auto-update interval set to: 6 hours\n"); } case CMD_UPDATE_12H; { save_update_interval(app, 43200); log_print("Auto-update interval set to: 12 hours\n"); } case CMD_UPDATE_DAILY; { save_update_interval(app, 86400); log_print("Auto-update interval set to: Daily\n"); } case CMD_UPDATE_3D; { save_update_interval(app, 259200); log_print("Auto-update interval set to: 3 days\n"); } case CMD_UPDATE_WEEKLY; { save_update_interval(app, 604800); log_print("Auto-update interval set to: weekly\n"); } case CMD_EXIT; { DestroyWindow(app.hwnd); } } PostMessageW(app.hwnd, WM_NULL, 0, 0); } app_wnd_proc :: (hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT #c_call { ctx: #Context; push_context ctx { app := cast(*App_State) GetWindowLongPtrW(hwnd, GWLP_USERDATA); if msg == { case WM_CREATE; { create_struct := cast(*CREATESTRUCTW) lparam; app = cast(*App_State) create_struct.lpCreateParams; SetWindowLongPtrW(hwnd, GWLP_USERDATA, cast(LONG_PTR) app); return 0; } case WM_MEASUREITEM; { lpmis := cast(*MEASUREITEMSTRUCT) lparam; if lpmis.CtlType == ODT_MENU { lpmis.itemWidth = 160; lpmis.itemHeight = 22; return TRUE; } } case WM_DRAWITEM; { lpdis := cast(*DRAWITEMSTRUCT) lparam; if lpdis.CtlType == ODT_MENU { if app { hDC := lpdis.hDC; rect := lpdis.rcItem; // Draw menu background hMenuBrush := GetSysColorBrush(COLOR_MENU); FillRect(hDC, *rect, hMenuBrush); // Create a bold Segoe UI font for high readability face_name := utf8_to_wide("Segoe UI"); hBoldFont := CreateFontW( -13, 0, 0, 0, FW_BOLD, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_DONTCARE, face_name ); hOldFont := SelectObject(hDC, hBoldFont); defer { SelectObject(hDC, hOldFont); DeleteObject(hBoldFont); } // Status and status dot color selection status_text := ""; dot_color: u32 = 0; if app.is_connecting { status_text = "Sing-box: Connecting..."; dot_color = RGB(241, 196, 15); // Yellow/Orange } else if app.is_updating { status_text = "Sing-box: Updating..."; dot_color = RGB(241, 196, 15); // Yellow/Orange } else if app.singbox_running { status_text = "Sing-box: Running"; dot_color = RGB(39, 174, 96); // Green } else { status_text = "Sing-box: Stopped"; dot_color = RGB(219, 68, 85); // Red } // Draw status dot (filled circle) in GDI in the 16x16 area // Centered 10x10 dot at x: rect.left+9, y: rect.top+6 hBrush := CreateSolidBrush(dot_color); hOldBrush := SelectObject(hDC, hBrush); hPen := CreatePen(PS_NULL, 0, 0); hOldPen := SelectObject(hDC, hPen); dot_left := rect.left + 9; dot_top := rect.top + 6; Ellipse(hDC, dot_left, dot_top, dot_left + 10, dot_top + 10); SelectObject(hDC, hOldPen); DeleteObject(hPen); SelectObject(hDC, hOldBrush); DeleteObject(hBrush); // Draw status text next to the dot (using standard menu text color, which is highly readable) text_color := GetSysColor(COLOR_MENUTEXT); SetTextColor(hDC, text_color); SetBkMode(hDC, TRANSPARENT); text_rect: RECT; text_rect.left = rect.left + 28; text_rect.top = rect.top + 3; text_rect.right = rect.right; text_rect.bottom = rect.bottom; DrawTextW(hDC, utf8_to_wide(status_text), -1, *text_rect, DT_SINGLELINE | DT_VCENTER); } return TRUE; } } case WM_TRAY_CALLBACK; { if lparam == WM_RBUTTONUP || lparam == WM_LBUTTONUP { show_context_menu(app); } return 0; } case WM_RESTART_SINGBOX; { log_print("WM_RESTART_SINGBOX received, restarting sing-box...\n"); if app.singbox_running { stop_singbox(app); start_singbox(app); } return 0; } case WM_TIMER; { if wparam == TIMER_PROCESS_CHECK { check_process_status(app); } else if wparam == TIMER_ANIMATION { if app.is_connecting { app.connecting_ticks += 1; app.animation_frame = (app.animation_frame + 1) % 4; update_tray(app); // Stop connecting animation after 12 ticks (3 seconds at 250ms) if app.connecting_ticks >= 12 { app.is_connecting = false; KillTimer(hwnd, TIMER_ANIMATION); update_tray(app); } } else if app.is_updating { app.animation_frame = (app.animation_frame + 1) % 4; update_tray(app); } } return 0; } case WM_DESTROY; { PostQuitMessage(0); return 0; } } return DefWindowProcW(hwnd, msg, wparam, lparam); } } load_stock_icon :: (siid: s32) -> HICON { sii: SHSTOCKICONINFO; sii.cbSize = size_of(SHSTOCKICONINFO); hr := SHGetStockIconInfo(siid, SHGSI_ICON | SHGSI_SMALLICON, *sii); if hr == 0 { // S_OK return sii.hIcon; } // Fallback to default application icon return LoadIconW(null, IDI_APPLICATION); } global_is_test_mode: bool = false; save_update_interval :: (app: *App_State, interval: s32) { app.updater_data.update_interval_seconds = interval; config_filename := global_config_path; config_data, read_ok := read_entire_file(config_filename); if read_ok { url := get_json_string_field(config_data, "\"_url\""); mode := get_json_string_field(config_data, "\"_mode\""); mode_val := ifx mode then mode else "system_proxy"; updated := set_json_metadata(config_data, url, mode_val, app.port, interval); write_entire_file(config_filename, updated); free(config_data); free(updated); } } main :: () { args := get_command_line_arguments(); is_test := false; for args { if it == "-test" || it == "--test" { is_test = true; break; } } global_is_test_mode = is_test; InitializeCriticalSection(*global_singbox_log_mutex); // 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); } } } init_userspace_paths(is_test); if is_test { config_test := global_config_path; config_prod := sprint("%\\config.json", global_userspace_dir); defer free(config_prod); if !file_exists(config_test) && file_exists(config_prod) { CopyFileW(utf8_to_wide(config_prod), utf8_to_wide(config_test), FALSE); } } hInstance := GetModuleHandleW(null); class_name_str := ifx is_test then "Sing-boxTrayControllerClassTest" else "Sing-boxTrayControllerClass"; class_name := utf8_to_wide(class_name_str); wclass: WNDCLASSEXW; wclass.cbSize = size_of(WNDCLASSEXW); wclass.lpfnWndProc = app_wnd_proc; wclass.hInstance = hInstance; wclass.hCursor = LoadCursorW(null, IDC_ARROW); wclass.lpszClassName = class_name; RegisterClassExW(*wclass); defer UnregisterClassW(class_name, hInstance); app_state: App_State; app_state.is_test_mode = is_test; app_state.icon_running = load_stock_icon(SIID_WORLD); app_state.icon_stopped = load_stock_icon(SIID_ERROR); app_state.icon_frames[0] = load_stock_icon(SIID_SERVER); app_state.icon_frames[1] = load_stock_icon(SIID_DRIVENET); app_state.icon_frames[2] = load_stock_icon(SIID_WORLD); app_state.icon_frames[3] = load_stock_icon(SIID_INTERNET); title_str := ifx is_test then "Sing-box Tray Controller (Test Mode)" else "Sing-box Tray Controller"; hwnd := CreateWindowExW( 0, class_name, utf8_to_wide(title_str), 0, 0, 0, 0, 0, null, null, hInstance, *app_state ); if !hwnd { log_error("Failed to create hidden window!\n"); return; } app_state.hwnd = hwnd; config_filename := global_config_path; app_state.updater_data.update_interval_seconds = 3600; // default // Load persisted mode, port, and update interval from config file config_data, read_ok := read_entire_file(config_filename); if read_ok { extracted_mode := get_json_string_field(config_data, "\"_mode\""); if extracted_mode && trim(extracted_mode) == "proxy" { app_state.use_system_proxy = false; } interval_str := get_json_val_field(config_data, "\"_update_interval\""); if interval_str { val, ok := to_integer(interval_str); if ok { app_state.updater_data.update_interval_seconds = xx val; log_print("Loaded update interval: % seconds\n", app_state.updater_data.update_interval_seconds); } } if app_state.is_test_mode { app_state.port = 10899; // force test port in test mode log_info("Loaded config from %. Test mode: forcing port to %.\n", config_filename, app_state.port); } else { port_str := get_json_val_field(config_data, "\"_port\""); if port_str { val, ok := to_integer(port_str); if ok { app_state.port = xx val; log_print("Loaded port: %\n", app_state.port); } } } free(config_data); } else { if app_state.is_test_mode { app_state.port = 10899; log_info("No % found. Initialized with test port %.\n", config_filename, app_state.port); } } app_state.local_version = get_local_singbox_version(); app_state.latest_web_version = copy_string("Checking..."); app_state.version_checker_data.app = *app_state; thread_init(*app_state.version_checker_thread, version_checker_thread_proc); app_state.version_checker_thread.data = *app_state.version_checker_data; thread_start(*app_state.version_checker_thread); nid: NOTIFYICONDATAW; nid.cbSize = size_of(NOTIFYICONDATAW); nid.hWnd = hwnd; nid.uID = 1; nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; nid.uCallbackMessage = WM_TRAY_CALLBACK; nid.hIcon = app_state.icon_stopped; tip_prefix := ifx app_state.is_test_mode then "Sing-box (Test Mode)" else "Sing-box"; set_tray_tip(*nid, tprint("%: Stopped", tip_prefix)); if !Shell_NotifyIconW(NIM_ADD, *nid) { log_error("Failed to register tray icon!\n"); return; } defer Shell_NotifyIconW(NIM_DELETE, *nid); // Try to run / start sing-box. On first start (or if config/outbounds are missing), // this will prompt the configuration dialog instead of silently failing or erroring. start_singbox(*app_state); SetTimer(hwnd, TIMER_PROCESS_CHECK, 1000, null); defer KillTimer(hwnd, TIMER_PROCESS_CHECK); app_state.updater_data.hwnd = hwnd; app_state.updater_data.stop_event = CreateEventW(null, TRUE, FALSE, null); app_state.updater_data.is_test_mode = app_state.is_test_mode; thread_init(*app_state.updater_thread, updater_thread_proc); app_state.updater_thread.data = *app_state.updater_data; thread_start(*app_state.updater_thread); msg: MSG; while GetMessageW(*msg, null, 0, 0) { TranslateMessage(*msg); DispatchMessageW(*msg); } log_print("Exiting application...\n"); if app_state.updater_data.stop_event { SetEvent(app_state.updater_data.stop_event); thread_is_done(*app_state.updater_thread, -1); thread_deinit(*app_state.updater_thread); CloseHandle(app_state.updater_data.stop_event); } if app_state.version_checker_thread.data { thread_is_done(*app_state.version_checker_thread, -1); thread_deinit(*app_state.version_checker_thread); } if app_state.local_version.count > 0 { free(app_state.local_version); } if app_state.latest_web_version.count > 0 { free(app_state.latest_web_version); } stop_singbox(*app_state); if app_state.icon_running DestroyIcon(app_state.icon_running); if app_state.icon_stopped DestroyIcon(app_state.icon_stopped); for app_state.icon_frames { if it DestroyIcon(it); } clear_last_singbox_logs(); DeleteCriticalSection(*global_singbox_log_mutex); }