From 40602121eaadbabbe97ddd21fe7e16cd69f8dce7 Mon Sep 17 00:00:00 2001 From: Ixniy Evonniy Date: Wed, 1 Jul 2026 10:00:09 +0300 Subject: [PATCH] feat: move temp files to userspace folder --- cache.db | Bin 32768 -> 0 bytes dialog.jai | 10 ++--- main.jai | 110 ++++++++++++++++++++++++++++++++++++++++++---------- updater.jai | 2 +- win32.jai | 12 ++++++ 5 files changed, 108 insertions(+), 26 deletions(-) delete mode 100644 cache.db diff --git a/cache.db b/cache.db deleted file mode 100644 index 89d9d502c73c6f4d60c067b06b0dc836cd79905c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeI&yA8rH5C-7z>Oe}*1W2$24P!6>W2B~IfQ-cqq^0H&|4kYyq(u7CT>CuiSGbbTHfB*pk1PBlyK!5-N0t6zEdcLmuPC)tq z`7RP5K!5-N0t5&UAV7cs0Rqz($p8K6157_784w^qfB*pk1PBlyK!5-N0z-k^_vij! zA7ChCOn?9Z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs c0RjXF5FkK+009C72oNAZfB*pk1pY7Z0zFv{J^%m! diff --git a/dialog.jai b/dialog.jai index 4183958..e7846de 100644 --- a/dialog.jai +++ b/dialog.jai @@ -55,7 +55,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 +112,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 +142,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; } @@ -279,7 +279,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 +350,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"; diff --git a/main.jai b/main.jai index f79642e..76f626c 100644 --- a/main.jai +++ b/main.jai @@ -8,6 +8,67 @@ #load "dialog.jai"; #load "updater.jai"; +global_userspace_dir: string; +global_config_path: string; +global_config_run_path: string; +global_log_path: string; + +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("%\\singbox-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 "singbox_tray_test.log" else "singbox_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("%\\singbox_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; @@ -58,7 +119,7 @@ append_to_log_file :: (text: string) { OPEN_ALWAYS :: 4; FILE_END :: 2; - filename := ifx global_is_test_mode then "singbox_tray_test.log" else "singbox_tray.log"; + filename := ifx global_log_path.count > 0 then global_log_path else (ifx global_is_test_mode then "singbox_tray_test.log" else "singbox_tray.log"); wide_path := utf8_to_wide(filename); hFile := CreateFileW( @@ -147,7 +208,7 @@ log_singbox :: (message: string, is_test_mode: bool) { OutputDebugStringW(utf8_to_wide(colored_log)); // Append to the correct log file - filename := ifx is_test_mode then "singbox_tray_test.log" else "singbox_tray.log"; + filename := ifx global_log_path.count > 0 then global_log_path else (ifx is_test_mode then "singbox_tray_test.log" else "singbox_tray.log"); wide_path := utf8_to_wide(filename); hFile := CreateFileW( @@ -267,7 +328,7 @@ set_autostart :: (enable: bool) -> bool { } // Hidden Process Spawning with Job Object configuration and stdout/stderr redirection -create_process_hidden :: (cmd: string, stdout_handle: HANDLE = INVALID_HANDLE_VALUE) -> HANDLE, PROCESS_INFORMATION, bool { +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; @@ -294,6 +355,11 @@ create_process_hidden :: (cmd: string, stdout_handle: HANDLE = INVALID_HANDLE_VA 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, @@ -302,7 +368,7 @@ create_process_hidden :: (cmd: string, stdout_handle: HANDLE = INVALID_HANDLE_VA inherit_handles, CREATE_NO_WINDOW, null, - null, + working_dir_wide, *startup_info, *process_info ); @@ -351,9 +417,8 @@ update_tray :: (app: *App_State) { start_singbox :: (app: *App_State) -> bool { if app.singbox_running return true; - config_filename := ifx app.is_test_mode then "config_test.json" else "config.json"; - config_run_filename := ifx app.is_test_mode then "config_run_test.json" else "config_run.json"; - singbox_log_filename := ifx app.is_test_mode then "sing-box_test.log" else "sing-box.log"; + config_filename := global_config_path; + config_run_filename := global_config_run_path; has_config := file_exists(config_filename); url_present := false; @@ -431,13 +496,14 @@ start_singbox :: (app: *App_State) -> bool { } } - exe_path := "sing-box.exe"; + relative_exe_path := "sing-box.exe"; // Check if sing-box.exe is not in current folder, check in the sing-box subfolder if !file_exists("sing-box.exe") { if file_exists("sing-box/sing-box.exe") { - exe_path = "sing-box\\sing-box.exe"; + relative_exe_path = "sing-box\\sing-box.exe"; } } + exe_path := get_absolute_path(relative_exe_path); hReadPipe, hWritePipe: HANDLE; sa: SECURITY_ATTRIBUTES; @@ -454,7 +520,7 @@ start_singbox :: (app: *App_State) -> bool { } cmd := tprint("% run -c %", exe_path, config_run_filename); - job, pi, ok := create_process_hidden(cmd, hWritePipe); + job, pi, ok := create_process_hidden(cmd, hWritePipe, global_userspace_dir); if hWritePipe != INVALID_HANDLE_VALUE { CloseHandle(hWritePipe); @@ -528,9 +594,7 @@ stop_singbox :: (app: *App_State) { log_info("Test Mode: System proxy bypass kept on stop.\n"); } - // Clean up temporary run config - config_run_filename := ifx app.is_test_mode then "config_run_test.json" else "config_run.json"; - DeleteFileW(utf8_to_wide(config_run_filename)); + DeleteFileW(utf8_to_wide(global_config_run_path)); update_tray(app); log_print("Sing-box stopped.\n"); @@ -567,8 +631,7 @@ check_process_status :: (app: *App_State) { log_info("Test Mode: System proxy bypass kept on unexpected exit.\n"); } - config_run_filename := ifx app.is_test_mode then "config_run_test.json" else "config_run.json"; - DeleteFileW(utf8_to_wide(config_run_filename)); + DeleteFileW(utf8_to_wide(global_config_run_path)); update_tray(app); } @@ -644,6 +707,8 @@ show_context_menu :: (app: *App_State) { 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")); @@ -691,7 +756,7 @@ show_context_menu :: (app: *App_State) { if changed { log_print("Port configured, updating state...\n"); - config_filename := ifx app.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 { port_str := get_json_val_field(config_data, "\"_port\""); @@ -718,7 +783,7 @@ show_context_menu :: (app: *App_State) { if app.use_system_proxy { app.use_system_proxy = false; - config_filename := ifx app.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 { url := get_json_string_field(config_data, "\"_url\""); @@ -743,7 +808,7 @@ show_context_menu :: (app: *App_State) { if !app.use_system_proxy { app.use_system_proxy = true; - config_filename := ifx app.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 { url := get_json_string_field(config_data, "\"_url\""); @@ -775,6 +840,9 @@ show_context_menu :: (app: *App_State) { 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_NEVER; { save_update_interval(app, 0); log_print("Auto-update interval set to: Never\n"); @@ -971,7 +1039,7 @@ global_is_test_mode: bool = false; save_update_interval :: (app: *App_State, interval: s32) { app.updater_data.update_interval_seconds = interval; - config_filename := ifx app.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 { url := get_json_string_field(config_data, "\"_url\""); @@ -1017,6 +1085,8 @@ main :: () { } } + init_userspace_paths(is_test); + hInstance := GetModuleHandleW(null); class_name_str := ifx is_test then "SingboxTrayControllerClassTest" else "SingboxTrayControllerClass"; class_name := utf8_to_wide(class_name_str); @@ -1060,7 +1130,7 @@ main :: () { app_state.hwnd = hwnd; - config_filename := ifx app_state.is_test_mode then "config_test.json" else "config.json"; + config_filename := global_config_path; app_state.updater_data.update_interval_seconds = 3600; // default diff --git a/updater.jai b/updater.jai index 8a6f6bf..72465e8 100644 --- a/updater.jai +++ b/updater.jai @@ -81,7 +81,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 { diff --git a/win32.jai b/win32.jai index 19faaa5..cd75a4b 100644 --- a/win32.jai +++ b/win32.jai @@ -164,6 +164,7 @@ CMD_UPDATE_12H :: 1014; CMD_UPDATE_DAILY :: 1015; CMD_UPDATE_3D :: 1016; CMD_UPDATE_WEEKLY :: 1017; +CMD_CONFIG_DIR :: 1018; // Boolean constants FALSE :: 0; @@ -204,6 +205,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 { @@ -314,6 +317,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 {