474 lines
18 KiB
Plaintext
474 lines
18 KiB
Plaintext
#import "Basic";
|
|
#import "Windows";
|
|
#import "Windows_Utf8";
|
|
#import "File";
|
|
#import "String";
|
|
|
|
// Win32 declarations are provided by win32.jai loaded in the main workspace.
|
|
|
|
Dialog_State :: struct {
|
|
edit_hwnd: HWND;
|
|
ok_hwnd: HWND;
|
|
cancel_hwnd: HWND;
|
|
dialog_done: bool;
|
|
url_saved: bool;
|
|
is_test_mode: bool;
|
|
}
|
|
|
|
dialog_proc :: (hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT #c_call {
|
|
ctx: #Context;
|
|
push_context ctx {
|
|
state := cast(*Dialog_State) GetWindowLongPtrW(hwnd, GWLP_USERDATA);
|
|
|
|
if msg == {
|
|
case WM_CREATE; {
|
|
create_struct := cast(*CREATESTRUCTW) lparam;
|
|
state = cast(*Dialog_State) create_struct.lpCreateParams;
|
|
SetWindowLongPtrW(hwnd, GWLP_USERDATA, cast(LONG_PTR) state);
|
|
|
|
hFont := GetStockObject(DEFAULT_GUI_FONT);
|
|
|
|
static_label := CreateWindowExW(
|
|
0,
|
|
utf8_to_wide("STATIC"),
|
|
utf8_to_wide("Enter Sing-box Config URL:"),
|
|
WS_CHILD | WS_VISIBLE | SS_LEFT,
|
|
20, 20, 410, 20,
|
|
hwnd,
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
SendMessageW(static_label, WM_SETFONT, cast(WPARAM) hFont, TRUE);
|
|
|
|
state.edit_hwnd = CreateWindowExW(
|
|
WS_EX_CLIENTEDGE,
|
|
utf8_to_wide("EDIT"),
|
|
utf8_to_wide(""),
|
|
WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER | ES_AUTOHSCROLL,
|
|
20, 45, 410, 25,
|
|
hwnd,
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
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_data, read_ok := read_entire_file(config_filename);
|
|
if read_ok {
|
|
existing_url := get_json_string_field(config_data, "\"_url\"");
|
|
if existing_url {
|
|
wide_url := utf8_to_wide(existing_url);
|
|
SetWindowTextW(state.edit_hwnd, wide_url);
|
|
}
|
|
free(config_data);
|
|
}
|
|
|
|
state.ok_hwnd = CreateWindowExW(
|
|
0,
|
|
utf8_to_wide("BUTTON"),
|
|
utf8_to_wide("OK"),
|
|
WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON,
|
|
220, 85, 100, 30,
|
|
hwnd,
|
|
cast(HMENU) IDOK,
|
|
null,
|
|
null
|
|
);
|
|
SendMessageW(state.ok_hwnd, WM_SETFONT, cast(WPARAM) hFont, TRUE);
|
|
|
|
state.cancel_hwnd = CreateWindowExW(
|
|
0,
|
|
utf8_to_wide("BUTTON"),
|
|
utf8_to_wide("Cancel"),
|
|
WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
|
|
330, 85, 100, 30,
|
|
hwnd,
|
|
cast(HMENU) IDCANCEL,
|
|
null,
|
|
null
|
|
);
|
|
SendMessageW(state.cancel_hwnd, WM_SETFONT, cast(WPARAM) hFont, TRUE);
|
|
|
|
SetFocus(state.edit_hwnd);
|
|
return 0;
|
|
}
|
|
case WM_COMMAND; {
|
|
control_id := LOWORD(wparam);
|
|
if control_id == IDOK {
|
|
length := GetWindowTextLengthW(state.edit_hwnd);
|
|
if length > 0 {
|
|
buffer := cast(*u16) alloc((length + 1) * size_of(u16));
|
|
defer free(buffer);
|
|
|
|
GetWindowTextW(state.edit_hwnd, buffer, length + 1);
|
|
url_utf8 := wide_to_utf8(buffer);
|
|
defer free(url_utf8);
|
|
|
|
trimmed_url := trim(url_utf8);
|
|
if trimmed_url {
|
|
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_data, read_ok := read_entire_file(config_filename);
|
|
if read_ok {
|
|
extracted_mode := get_json_string_field(config_data, "\"_mode\"");
|
|
if extracted_mode {
|
|
mode = copy_string(extracted_mode);
|
|
} else {
|
|
mode = copy_string("system_proxy");
|
|
}
|
|
port_str := get_json_val_field(config_data, "\"_port\"");
|
|
if port_str {
|
|
val, ok := to_integer(port_str);
|
|
if ok port = xx val;
|
|
}
|
|
interval_str := get_json_val_field(config_data, "\"_update_interval\"");
|
|
if interval_str {
|
|
val, ok := to_integer(interval_str);
|
|
if ok update_interval = xx val;
|
|
}
|
|
free(config_data);
|
|
} else {
|
|
mode = copy_string("system_proxy");
|
|
}
|
|
defer free(mode);
|
|
|
|
minimal_json := tprint("{\n \"_url\": \"%\",\n \"_mode\": \"%\",\n \"_port\": %,\n \"_update_interval\": %,\n \"inbounds\": [],\n \"outbounds\": []\n}", trimmed_url, mode, port, update_interval);
|
|
write_entire_file(config_filename, minimal_json);
|
|
state.url_saved = true;
|
|
}
|
|
} else {
|
|
config_filename := ifx state.is_test_mode then "config_test.json" else "config.json";
|
|
DeleteFileW(utf8_to_wide(config_filename));
|
|
state.url_saved = true;
|
|
}
|
|
DestroyWindow(hwnd);
|
|
return 0;
|
|
} else if control_id == IDCANCEL {
|
|
DestroyWindow(hwnd);
|
|
return 0;
|
|
}
|
|
}
|
|
case WM_CLOSE; {
|
|
DestroyWindow(hwnd);
|
|
return 0;
|
|
}
|
|
case WM_DESTROY; {
|
|
if state {
|
|
state.dialog_done = true;
|
|
}
|
|
PostThreadMessageW(GetCurrentThreadId(), WM_NULL, 0, 0);
|
|
return 0;
|
|
}
|
|
}
|
|
return DefWindowProcW(hwnd, msg, wparam, lparam);
|
|
}
|
|
}
|
|
|
|
show_config_url_dialog :: (parent_hwnd: HWND, is_test_mode := false) -> bool {
|
|
hInstance := GetModuleHandleW(null);
|
|
class_name := utf8_to_wide("SingboxConfigUrlDialogClass");
|
|
|
|
wclass: WNDCLASSEXW;
|
|
wclass.cbSize = size_of(WNDCLASSEXW);
|
|
wclass.style = CS_HREDRAW | CS_VREDRAW;
|
|
wclass.lpfnWndProc = dialog_proc;
|
|
wclass.hInstance = hInstance;
|
|
wclass.hCursor = LoadCursorW(null, IDC_ARROW);
|
|
wclass.hbrBackground = cast(HBRUSH) (COLOR_WINDOW + 1);
|
|
wclass.lpszClassName = class_name;
|
|
|
|
RegisterClassExW(*wclass);
|
|
defer UnregisterClassW(class_name, hInstance);
|
|
|
|
screen_w := GetSystemMetrics(SM_CXSCREEN);
|
|
screen_h := GetSystemMetrics(SM_CYSCREEN);
|
|
dialog_w: s32 = 460;
|
|
dialog_h: s32 = 170;
|
|
dialog_x := (screen_w - dialog_w) / 2;
|
|
dialog_y := (screen_h - dialog_h) / 2;
|
|
|
|
state: Dialog_State;
|
|
state.is_test_mode = is_test_mode;
|
|
|
|
dialog_hwnd := CreateWindowExW(
|
|
WS_EX_DLGMODALFRAME,
|
|
class_name,
|
|
utf8_to_wide("Configure Sing-box URL"),
|
|
WS_POPUP | WS_CAPTION | WS_SYSMENU,
|
|
dialog_x, dialog_y, dialog_w, dialog_h,
|
|
parent_hwnd,
|
|
null,
|
|
hInstance,
|
|
*state
|
|
);
|
|
|
|
if !dialog_hwnd return false;
|
|
|
|
if parent_hwnd {
|
|
EnableWindow(parent_hwnd, FALSE);
|
|
}
|
|
|
|
ShowWindow(dialog_hwnd, SW_SHOW);
|
|
UpdateWindow(dialog_hwnd);
|
|
|
|
while !state.dialog_done {
|
|
msg: MSG;
|
|
if GetMessageW(*msg, null, 0, 0) {
|
|
if !IsDialogMessageW(dialog_hwnd, *msg) {
|
|
TranslateMessage(*msg);
|
|
DispatchMessageW(*msg);
|
|
}
|
|
} else {
|
|
PostQuitMessage(cast(s32) msg.wParam);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if parent_hwnd {
|
|
EnableWindow(parent_hwnd, TRUE);
|
|
SetFocus(parent_hwnd);
|
|
}
|
|
|
|
return state.url_saved;
|
|
}
|
|
|
|
show_config_port_dialog :: (parent_hwnd: HWND, is_test_mode := false) -> bool {
|
|
// Port Dialog Proc
|
|
port_dialog_proc :: (hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT #c_call {
|
|
ctx: #Context;
|
|
push_context ctx {
|
|
state := cast(*Dialog_State) GetWindowLongPtrW(hwnd, GWLP_USERDATA);
|
|
|
|
if msg == {
|
|
case WM_CREATE; {
|
|
create_struct := cast(*CREATESTRUCTW) lparam;
|
|
state = cast(*Dialog_State) create_struct.lpCreateParams;
|
|
SetWindowLongPtrW(hwnd, GWLP_USERDATA, cast(LONG_PTR) state);
|
|
|
|
hFont := GetStockObject(DEFAULT_GUI_FONT);
|
|
|
|
label_hwnd := CreateWindowExW(
|
|
0,
|
|
utf8_to_wide("STATIC"),
|
|
utf8_to_wide("Enter SOCKS/HTTP proxy port (default 10801):"),
|
|
WS_CHILD | WS_VISIBLE,
|
|
20, 20, 410, 20,
|
|
hwnd,
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
SendMessageW(label_hwnd, WM_SETFONT, cast(WPARAM) hFont, TRUE);
|
|
|
|
state.edit_hwnd = CreateWindowExW(
|
|
0,
|
|
utf8_to_wide("EDIT"),
|
|
utf8_to_wide(""),
|
|
WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER | ES_AUTOHSCROLL | ES_NUMBER,
|
|
20, 45, 410, 25,
|
|
hwnd,
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
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_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 {
|
|
port_str := get_json_val_field(config_data, "\"_port\"");
|
|
if port_str {
|
|
val, ok := to_integer(port_str);
|
|
if ok {
|
|
port = xx val;
|
|
}
|
|
}
|
|
free(config_data);
|
|
} else if state.is_test_mode && read_ok {
|
|
port_str := get_json_val_field(config_data, "\"_port\"");
|
|
if port_str {
|
|
val, ok := to_integer(port_str);
|
|
if ok {
|
|
port = xx val;
|
|
}
|
|
}
|
|
free(config_data);
|
|
}
|
|
|
|
wide_port := utf8_to_wide(tprint("%", port));
|
|
SetWindowTextW(state.edit_hwnd, wide_port);
|
|
|
|
state.ok_hwnd = CreateWindowExW(
|
|
0,
|
|
utf8_to_wide("BUTTON"),
|
|
utf8_to_wide("OK"),
|
|
WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON,
|
|
220, 85, 100, 30,
|
|
hwnd,
|
|
cast(HMENU) IDOK,
|
|
null,
|
|
null
|
|
);
|
|
SendMessageW(state.ok_hwnd, WM_SETFONT, cast(WPARAM) hFont, TRUE);
|
|
|
|
state.cancel_hwnd = CreateWindowExW(
|
|
0,
|
|
utf8_to_wide("BUTTON"),
|
|
utf8_to_wide("Cancel"),
|
|
WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
|
|
330, 85, 100, 30,
|
|
hwnd,
|
|
cast(HMENU) IDCANCEL,
|
|
null,
|
|
null
|
|
);
|
|
SendMessageW(state.cancel_hwnd, WM_SETFONT, cast(WPARAM) hFont, TRUE);
|
|
|
|
SetFocus(state.edit_hwnd);
|
|
return 0;
|
|
}
|
|
case WM_COMMAND; {
|
|
control_id := LOWORD(wparam);
|
|
if control_id == IDOK {
|
|
length := GetWindowTextLengthW(state.edit_hwnd);
|
|
if length > 0 {
|
|
buffer := cast(*u16) alloc((length + 1) * size_of(u16));
|
|
defer free(buffer);
|
|
|
|
GetWindowTextW(state.edit_hwnd, buffer, length + 1);
|
|
port_utf8 := wide_to_utf8(buffer);
|
|
defer free(port_utf8);
|
|
|
|
val, ok := to_integer(trim(port_utf8));
|
|
if ok && val > 0 && val <= 65535 {
|
|
port := xx val;
|
|
|
|
config_filename := ifx state.is_test_mode then "config_test.json" else "config.json";
|
|
config_data, read_ok := read_entire_file(config_filename);
|
|
url := "";
|
|
mode := "system_proxy";
|
|
update_interval := 3600;
|
|
if read_ok {
|
|
extracted_url := get_json_string_field(config_data, "\"_url\"");
|
|
if extracted_url url = copy_string(extracted_url);
|
|
|
|
extracted_mode := get_json_string_field(config_data, "\"_mode\"");
|
|
if extracted_mode {
|
|
mode = copy_string(extracted_mode);
|
|
} else {
|
|
mode = copy_string("system_proxy");
|
|
}
|
|
|
|
interval_str := get_json_val_field(config_data, "\"_update_interval\"");
|
|
if interval_str {
|
|
val, ok := to_integer(interval_str);
|
|
if ok update_interval = xx val;
|
|
}
|
|
|
|
updated := set_json_metadata(config_data, url, mode, port, update_interval);
|
|
write_entire_file(config_filename, updated);
|
|
free(config_data);
|
|
free(updated);
|
|
} else {
|
|
minimal_json := tprint("{\n \"_url\": \"\",\n \"_mode\": \"system_proxy\",\n \"_port\": %,\n \"_update_interval\": %,\n \"inbounds\": [],\n \"outbounds\": []\n}", port, update_interval);
|
|
write_entire_file(config_filename, minimal_json);
|
|
}
|
|
if url free(url);
|
|
free(mode);
|
|
state.url_saved = true;
|
|
}
|
|
}
|
|
DestroyWindow(hwnd);
|
|
return 0;
|
|
} else if control_id == IDCANCEL {
|
|
DestroyWindow(hwnd);
|
|
return 0;
|
|
}
|
|
}
|
|
case WM_CLOSE; {
|
|
DestroyWindow(hwnd);
|
|
return 0;
|
|
}
|
|
case WM_DESTROY; {
|
|
state.dialog_done = true;
|
|
// Wake up the dialog loop
|
|
PostThreadMessageW(GetCurrentThreadId(), WM_NULL, 0, 0);
|
|
return 0;
|
|
}
|
|
}
|
|
return DefWindowProcW(hwnd, msg, wparam, lparam);
|
|
}
|
|
}
|
|
|
|
hInstance := GetModuleHandleW(null);
|
|
class_name := utf8_to_wide("SingboxPortDialogClass");
|
|
|
|
wclass: WNDCLASSEXW;
|
|
wclass.cbSize = size_of(WNDCLASSEXW);
|
|
wclass.lpfnWndProc = port_dialog_proc;
|
|
wclass.hInstance = hInstance;
|
|
wclass.hCursor = LoadCursorW(null, IDC_ARROW);
|
|
wclass.lpszClassName = class_name;
|
|
|
|
RegisterClassExW(*wclass);
|
|
defer UnregisterClassW(class_name, hInstance);
|
|
|
|
screen_w := GetSystemMetrics(SM_CXSCREEN);
|
|
screen_h := GetSystemMetrics(SM_CYSCREEN);
|
|
dialog_w: s32 = 460;
|
|
dialog_h: s32 = 170;
|
|
dialog_x := (screen_w - dialog_w) / 2;
|
|
dialog_y := (screen_h - dialog_h) / 2;
|
|
|
|
state: Dialog_State;
|
|
state.is_test_mode = is_test_mode;
|
|
|
|
dialog_hwnd := CreateWindowExW(
|
|
WS_EX_DLGMODALFRAME,
|
|
class_name,
|
|
utf8_to_wide("Configure Sing-box Port"),
|
|
WS_POPUP | WS_CAPTION | WS_SYSMENU,
|
|
dialog_x, dialog_y, dialog_w, dialog_h,
|
|
parent_hwnd,
|
|
null,
|
|
hInstance,
|
|
*state
|
|
);
|
|
|
|
if !dialog_hwnd return false;
|
|
|
|
if parent_hwnd {
|
|
EnableWindow(parent_hwnd, FALSE);
|
|
}
|
|
|
|
ShowWindow(dialog_hwnd, SW_SHOW);
|
|
UpdateWindow(dialog_hwnd);
|
|
|
|
while !state.dialog_done {
|
|
msg: MSG;
|
|
if GetMessageW(*msg, null, 0, 0) {
|
|
if !IsDialogMessageW(dialog_hwnd, *msg) {
|
|
TranslateMessage(*msg);
|
|
DispatchMessageW(*msg);
|
|
}
|
|
} else {
|
|
PostQuitMessage(cast(s32) msg.wParam);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if parent_hwnd {
|
|
EnableWindow(parent_hwnd, TRUE);
|
|
SetFocus(parent_hwnd);
|
|
}
|
|
|
|
return state.url_saved;
|
|
}
|