feat: move temp files to userspace folder

This commit is contained in:
Ixniy Evonniy 2026-07-01 10:00:09 +03:00
parent 1d349d6692
commit 40602121ea
5 changed files with 108 additions and 26 deletions

BIN
cache.db

Binary file not shown.

View File

@ -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); SendMessageW(state.edit_hwnd, WM_SETFONT, cast(WPARAM) hFont, TRUE);
// Populate with existing URL if present // 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); config_data, read_ok := read_entire_file(config_filename);
if read_ok { if read_ok {
existing_url := get_json_string_field(config_data, "\"_url\""); 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; mode: string;
port := ifx state.is_test_mode then 10899 else 10801; port := ifx state.is_test_mode then 10899 else 10801;
update_interval := 3600; 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); config_data, read_ok := read_entire_file(config_filename);
if read_ok { if read_ok {
extracted_mode := get_json_string_field(config_data, "\"_mode\""); 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; state.url_saved = true;
} }
} else { } 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)); DeleteFileW(utf8_to_wide(config_filename));
state.url_saved = true; 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); SendMessageW(state.edit_hwnd, WM_SETFONT, cast(WPARAM) hFont, TRUE);
// Populate with existing port if present // 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); config_data, read_ok := read_entire_file(config_filename);
port := ifx state.is_test_mode then 10899 else 10801; port := ifx state.is_test_mode then 10899 else 10801;
if !state.is_test_mode && read_ok { 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 { if ok && val > 0 && val <= 65535 {
port := xx val; 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); config_data, read_ok := read_entire_file(config_filename);
url := ""; url := "";
mode := "system_proxy"; mode := "system_proxy";

110
main.jai
View File

@ -8,6 +8,67 @@
#load "dialog.jai"; #load "dialog.jai";
#load "updater.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 // Custom constants
TIMER_PROCESS_CHECK :: 1; TIMER_PROCESS_CHECK :: 1;
TIMER_ANIMATION :: 2; TIMER_ANIMATION :: 2;
@ -58,7 +119,7 @@ append_to_log_file :: (text: string) {
OPEN_ALWAYS :: 4; OPEN_ALWAYS :: 4;
FILE_END :: 2; 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); wide_path := utf8_to_wide(filename);
hFile := CreateFileW( hFile := CreateFileW(
@ -147,7 +208,7 @@ log_singbox :: (message: string, is_test_mode: bool) {
OutputDebugStringW(utf8_to_wide(colored_log)); OutputDebugStringW(utf8_to_wide(colored_log));
// Append to the correct log file // 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); wide_path := utf8_to_wide(filename);
hFile := CreateFileW( hFile := CreateFileW(
@ -267,7 +328,7 @@ set_autostart :: (enable: bool) -> bool {
} }
// Hidden Process Spawning with Job Object configuration and stdout/stderr redirection // 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: STARTUPINFOW;
startup_info.cb = size_of(STARTUPINFOW); startup_info.cb = size_of(STARTUPINFOW);
startup_info.dwFlags = STARTF_USESHOWWINDOW; 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; 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( success := CreateProcessW(
null, null,
cmd_wide, cmd_wide,
@ -302,7 +368,7 @@ create_process_hidden :: (cmd: string, stdout_handle: HANDLE = INVALID_HANDLE_VA
inherit_handles, inherit_handles,
CREATE_NO_WINDOW, CREATE_NO_WINDOW,
null, null,
null, working_dir_wide,
*startup_info, *startup_info,
*process_info *process_info
); );
@ -351,9 +417,8 @@ update_tray :: (app: *App_State) {
start_singbox :: (app: *App_State) -> bool { start_singbox :: (app: *App_State) -> bool {
if app.singbox_running return true; if app.singbox_running return true;
config_filename := ifx app.is_test_mode then "config_test.json" else "config.json"; config_filename := global_config_path;
config_run_filename := ifx app.is_test_mode then "config_run_test.json" else "config_run.json"; config_run_filename := global_config_run_path;
singbox_log_filename := ifx app.is_test_mode then "sing-box_test.log" else "sing-box.log";
has_config := file_exists(config_filename); has_config := file_exists(config_filename);
url_present := false; 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 // 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.exe") {
if file_exists("sing-box/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; hReadPipe, hWritePipe: HANDLE;
sa: SECURITY_ATTRIBUTES; sa: SECURITY_ATTRIBUTES;
@ -454,7 +520,7 @@ start_singbox :: (app: *App_State) -> bool {
} }
cmd := tprint("% run -c %", exe_path, config_run_filename); 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 { if hWritePipe != INVALID_HANDLE_VALUE {
CloseHandle(hWritePipe); CloseHandle(hWritePipe);
@ -528,9 +594,7 @@ stop_singbox :: (app: *App_State) {
log_info("Test Mode: System proxy bypass kept on stop.\n"); log_info("Test Mode: System proxy bypass kept on stop.\n");
} }
// Clean up temporary run config DeleteFileW(utf8_to_wide(global_config_run_path));
config_run_filename := ifx app.is_test_mode then "config_run_test.json" else "config_run.json";
DeleteFileW(utf8_to_wide(config_run_filename));
update_tray(app); update_tray(app);
log_print("Sing-box stopped.\n"); 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"); 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(global_config_run_path));
DeleteFileW(utf8_to_wide(config_run_filename));
update_tray(app); 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 == 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(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_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_POPUP, cast(s64) hConfigureMenu, utf8_to_wide("Configure"));
@ -691,7 +756,7 @@ show_context_menu :: (app: *App_State) {
if changed { if changed {
log_print("Port configured, updating state...\n"); 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); config_data, read_ok := read_entire_file(config_filename);
if read_ok { if read_ok {
port_str := get_json_val_field(config_data, "\"_port\""); port_str := get_json_val_field(config_data, "\"_port\"");
@ -718,7 +783,7 @@ show_context_menu :: (app: *App_State) {
if app.use_system_proxy { if app.use_system_proxy {
app.use_system_proxy = false; 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); config_data, read_ok := read_entire_file(config_filename);
if read_ok { if read_ok {
url := get_json_string_field(config_data, "\"_url\""); url := get_json_string_field(config_data, "\"_url\"");
@ -743,7 +808,7 @@ show_context_menu :: (app: *App_State) {
if !app.use_system_proxy { if !app.use_system_proxy {
app.use_system_proxy = true; 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); config_data, read_ok := read_entire_file(config_filename);
if read_ok { if read_ok {
url := get_json_string_field(config_data, "\"_url\""); 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); 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; { case CMD_UPDATE_NEVER; {
save_update_interval(app, 0); save_update_interval(app, 0);
log_print("Auto-update interval set to: Never\n"); 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) { save_update_interval :: (app: *App_State, interval: s32) {
app.updater_data.update_interval_seconds = interval; 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); config_data, read_ok := read_entire_file(config_filename);
if read_ok { if read_ok {
url := get_json_string_field(config_data, "\"_url\""); url := get_json_string_field(config_data, "\"_url\"");
@ -1017,6 +1085,8 @@ main :: () {
} }
} }
init_userspace_paths(is_test);
hInstance := GetModuleHandleW(null); hInstance := GetModuleHandleW(null);
class_name_str := ifx is_test then "SingboxTrayControllerClassTest" else "SingboxTrayControllerClass"; class_name_str := ifx is_test then "SingboxTrayControllerClassTest" else "SingboxTrayControllerClass";
class_name := utf8_to_wide(class_name_str); class_name := utf8_to_wide(class_name_str);
@ -1060,7 +1130,7 @@ main :: () {
app_state.hwnd = hwnd; 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 app_state.updater_data.update_interval_seconds = 3600; // default

View File

@ -81,7 +81,7 @@ download_url :: (url: string) -> string, bool, string {
} }
perform_update :: (is_test_mode := false) -> changed: bool, success: bool, error_msg: 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); config_data, read_ok := read_entire_file(config_filename);
if !read_ok { if !read_ok {

View File

@ -164,6 +164,7 @@ CMD_UPDATE_12H :: 1014;
CMD_UPDATE_DAILY :: 1015; CMD_UPDATE_DAILY :: 1015;
CMD_UPDATE_3D :: 1016; CMD_UPDATE_3D :: 1016;
CMD_UPDATE_WEEKLY :: 1017; CMD_UPDATE_WEEKLY :: 1017;
CMD_CONFIG_DIR :: 1018;
// Boolean constants // Boolean constants
FALSE :: 0; FALSE :: 0;
@ -204,6 +205,8 @@ GetWindowTextLengthW :: (hWnd: HWND) -> s32 #foreign user32;
SendMessageW :: (hWnd: HWND, Msg: u32, wParam: WPARAM, lParam: LPARAM) -> LRESULT #foreign user32; SendMessageW :: (hWnd: HWND, Msg: u32, wParam: WPARAM, lParam: LPARAM) -> LRESULT #foreign user32;
SetEvent :: (hEvent: HANDLE) -> BOOL #foreign kernel32; 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; SetFocus :: (hWnd: HWND) -> HWND #foreign user32;
LOWORD :: (val: WPARAM) -> u16 { LOWORD :: (val: WPARAM) -> u16 {
@ -314,6 +317,15 @@ SHGetStockIconInfo :: (
psii: *SHSTOCKICONINFO psii: *SHSTOCKICONINFO
) -> s32 #foreign shell32; ) -> 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; DestroyIcon :: (hIcon: HICON) -> BOOL #foreign user32;
wcslen :: (str: *u16) -> int { wcslen :: (str: *u16) -> int {