feat: add sing-box updater
This commit is contained in:
parent
b011fcb346
commit
7e26b24030
19
dialog.jai
19
dialog.jai
@ -533,7 +533,16 @@ download_dialog_proc :: (hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -581,8 +590,8 @@ show_download_progress_dialog :: (parent_hwnd: HWND, url: string, dest_path: str
|
||||
dialog_y := (screen_h - dialog_h) / 2;
|
||||
|
||||
state: Download_Dialog_State;
|
||||
state.thread_data.url = url;
|
||||
state.thread_data.dest_path = dest_path;
|
||||
state.thread_data.url = copy_string(url);
|
||||
state.thread_data.dest_path = copy_string(dest_path);
|
||||
|
||||
dialog_hwnd := CreateWindowExW(
|
||||
WS_EX_DLGMODALFRAME,
|
||||
@ -596,7 +605,11 @@ show_download_progress_dialog :: (parent_hwnd: HWND, url: string, dest_path: str
|
||||
*state
|
||||
);
|
||||
|
||||
if !dialog_hwnd return false, "Failed to create dialog window";
|
||||
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);
|
||||
|
||||
471
main.jai
471
main.jai
@ -111,6 +111,11 @@ App_State :: struct {
|
||||
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 {
|
||||
@ -457,11 +462,6 @@ get_last_singbox_logs_string :: () -> string {
|
||||
}
|
||||
|
||||
find_singbox_in_userspace :: () -> string {
|
||||
target_direct := tprint("%\\sing-box.exe", global_userspace_dir);
|
||||
if file_exists(target_direct) {
|
||||
return target_direct;
|
||||
}
|
||||
|
||||
search_path := tprint("%\\*", global_userspace_dir);
|
||||
find_data: WIN32_FIND_DATAW;
|
||||
hFind := FindFirstFileW(utf8_to_wide(search_path), *find_data);
|
||||
@ -528,6 +528,311 @@ run_command_blocking :: (cmd: string, working_dir: string = "") -> success: bool
|
||||
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("SingboxTray"),
|
||||
INTERNET_OPEN_TYPE_PRECONFIG,
|
||||
null,
|
||||
null,
|
||||
0
|
||||
);
|
||||
if !hInternet return "";
|
||||
defer InternetCloseHandle(hInternet);
|
||||
|
||||
headers := "User-Agent: SingboxTray\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;
|
||||
|
||||
@ -601,12 +906,21 @@ start_singbox :: (app: *App_State) -> bool {
|
||||
// Strip custom fields (_url, _mode, _port) to avoid sing-box decoding error
|
||||
clean_config := strip_json_metadata(modified);
|
||||
|
||||
write_entire_file(config_run_filename, clean_config);
|
||||
// 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(clean_config);
|
||||
free(final_config);
|
||||
}
|
||||
}
|
||||
|
||||
@ -614,60 +928,8 @@ start_singbox :: (app: *App_State) -> bool {
|
||||
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 {
|
||||
clear_last_singbox_logs();
|
||||
|
||||
download_url := "https://github.com/SagerNet/sing-box/releases/download/v1.13.14/sing-box-1.13.14-windows-amd64.zip";
|
||||
temp_zip := tprint("%\\sing-box-temp.zip", global_userspace_dir);
|
||||
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
found_exe := find_singbox_in_userspace();
|
||||
if found_exe.count > 0 {
|
||||
if found_exe != exe_path {
|
||||
MoveFileW(utf8_to_wide(found_exe), utf8_to_wide(exe_path));
|
||||
|
||||
// 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);
|
||||
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));
|
||||
return false;
|
||||
}
|
||||
|
||||
DeleteFileW(utf8_to_wide(temp_zip));
|
||||
MessageBoxW(app.hwnd, utf8_to_wide("Sing-box core installed successfully!"), utf8_to_wide("Sing-box Tray"), MB_ICONINFORMATION);
|
||||
installed := install_or_update_singbox_core(app, false);
|
||||
return installed; // install_or_update_singbox_core handles starting sing-box
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@ -742,6 +1004,7 @@ stop_singbox :: (app: *App_State) {
|
||||
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);
|
||||
@ -850,6 +1113,32 @@ trigger_immediate_update :: (app: *App_State) {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@ -872,6 +1161,33 @@ show_context_menu :: (app: *App_State) {
|
||||
}
|
||||
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"));
|
||||
@ -1019,6 +1335,9 @@ show_context_menu :: (app: *App_State) {
|
||||
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");
|
||||
@ -1265,6 +1584,15 @@ main :: () {
|
||||
|
||||
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 "SingboxTrayControllerClassTest" else "SingboxTrayControllerClass";
|
||||
class_name := utf8_to_wide(class_name_str);
|
||||
@ -1351,6 +1679,15 @@ main :: () {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@ -1398,6 +1735,18 @@ main :: () {
|
||||
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);
|
||||
|
||||
16
updater.jai
16
updater.jai
@ -15,6 +15,12 @@ Updater_Thread_Data :: struct {
|
||||
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"),
|
||||
INTERNET_OPEN_TYPE_PRECONFIG,
|
||||
@ -32,11 +38,12 @@ download_url :: (url: string) -> string, bool, string {
|
||||
flags |= INTERNET_FLAG_SECURE;
|
||||
}
|
||||
|
||||
headers := "User-Agent: SingboxTray\r\n";
|
||||
hUrl := InternetOpenUrlW(
|
||||
hInternet,
|
||||
utf8_to_wide(url),
|
||||
null,
|
||||
0,
|
||||
utf8_to_wide(headers),
|
||||
cast(u32) headers.count,
|
||||
flags,
|
||||
0
|
||||
);
|
||||
@ -361,11 +368,12 @@ download_file :: (url: string, dest_path: string) -> bool, string {
|
||||
flags |= INTERNET_FLAG_SECURE;
|
||||
}
|
||||
|
||||
headers := "User-Agent: SingboxTray\r\n";
|
||||
hUrl := InternetOpenUrlW(
|
||||
hInternet,
|
||||
utf8_to_wide(url),
|
||||
null,
|
||||
0,
|
||||
utf8_to_wide(headers),
|
||||
cast(u32) headers.count,
|
||||
flags,
|
||||
0
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user