diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index f2e16261..f363389f 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -144,6 +144,7 @@ set(SRC_FILES "./src/session/shuangpin_input_session.h" "./src/utils/common_utils.cpp" "./src/utils/common_utils.h" + "./src/utils/ime_paths.cpp" "./src/utils/ime_utils.cpp" "./src/utils/ime_utils.h" "./src/utils/webview_utils.cpp" @@ -278,12 +279,13 @@ add_executable(${EMOJI_EXECUTABLE_NAME} WIN32 "./src/emoji-panel/resource.h" "./src/clipboard/clipboard_history.cpp" "./src/clipboard/clipboard_history.h" + "./src/utils/ime_paths.cpp" "./src/utils/surface_theme_config.cpp" "./src/utils/surface_theme_config.h" ) add_dependencies(${MY_EXECUTABLE_NAME} ${WATCHDOG_EXECUTABLE_NAME}) target_compile_definitions(${PROJECT_NAME} PRIVATE NOMINMAX=1) -target_link_libraries(${EMOJI_EXECUTABLE_NAME} PRIVATE msimeui unofficial::sqlite3::sqlite3) +target_link_libraries(${EMOJI_EXECUTABLE_NAME} PRIVATE msimeui unofficial::sqlite3::sqlite3 shell32 advapi32 ole32) target_compile_definitions(${EMOJI_EXECUTABLE_NAME} PRIVATE NOMINMAX=1 WIN32_LEAN_AND_MEAN=1 @@ -305,10 +307,11 @@ add_executable(${KEYBOARD_EXECUTABLE_NAME} WIN32 "./src/keyboard-panel/main.cpp" "./src/keyboard-panel/keyboard_panel.rc" "./src/keyboard-panel/resource.h" + "./src/utils/ime_paths.cpp" "./src/utils/surface_theme_config.cpp" "./src/utils/surface_theme_config.h" ) -target_link_libraries(${KEYBOARD_EXECUTABLE_NAME} PRIVATE msimeui) +target_link_libraries(${KEYBOARD_EXECUTABLE_NAME} PRIVATE msimeui shell32 advapi32 ole32) target_compile_definitions(${KEYBOARD_EXECUTABLE_NAME} PRIVATE NOMINMAX=1 WIN32_LEAN_AND_MEAN=1 @@ -325,10 +328,11 @@ add_executable(${HANDWRITING_EXECUTABLE_NAME} WIN32 "./src/handwriting-panel/main.cpp" "./src/handwriting-panel/handwriting_panel.rc" "./src/handwriting-panel/resource.h" + "./src/utils/ime_paths.cpp" "./src/utils/surface_theme_config.cpp" "./src/utils/surface_theme_config.h" ) -target_link_libraries(${HANDWRITING_EXECUTABLE_NAME} PRIVATE msimeui windowsapp.lib) +target_link_libraries(${HANDWRITING_EXECUTABLE_NAME} PRIVATE msimeui windowsapp.lib shell32 advapi32 ole32) target_compile_definitions(${HANDWRITING_EXECUTABLE_NAME} PRIVATE NOMINMAX=1 WIN32_LEAN_AND_MEAN=1 @@ -370,6 +374,7 @@ add_executable(${SETTINGS_EXECUTABLE_NAME} WIN32 "./src/clipboard/clipboard_history.h" "./src/utils/common_utils.cpp" "./src/utils/common_utils.h" + "./src/utils/ime_paths.cpp" "./MetasequoiaImeEngine/common/helpcode_utils.cpp" "./MetasequoiaImeEngine/quanpin/quanpin_utils.cpp" "./MetasequoiaImeEngine/quanpin/quanpin_query.cpp" diff --git a/server/MetasequoiaImeEngine b/server/MetasequoiaImeEngine index 99d29173..ddcb3227 160000 --- a/server/MetasequoiaImeEngine +++ b/server/MetasequoiaImeEngine @@ -1 +1 @@ -Subproject commit 99d29173ae1dbbaca6ed8fb81c738aa3e1be593d +Subproject commit ddcb3227742dfeb0cc24800e17f219dadfb0a575 diff --git a/server/assets/config/config.toml b/server/assets/config/config.toml index 9053bb40..3b4b94c2 100644 --- a/server/assets/config/config.toml +++ b/server/assets/config/config.toml @@ -11,7 +11,7 @@ theme = "dark" # [general] # 界面诊断日志总开关(默认关闭;不记录输入内容或候选文本) -diagnostic_log = true +diagnostic_log = false # 悬浮工具栏 floating_toolbar = true # 悬浮工具栏组件(中英文切换始终显示) diff --git a/server/scripts/config_files/CMakeLists.txt b/server/scripts/config_files/CMakeLists.txt index 2fdb5676..9b862f86 100644 --- a/server/scripts/config_files/CMakeLists.txt +++ b/server/scripts/config_files/CMakeLists.txt @@ -76,6 +76,7 @@ set(SRC_FILES "./src/session/shuangpin_input_session.h" "./src/utils/common_utils.cpp" "./src/utils/common_utils.h" + "./src/utils/ime_paths.cpp" "./src/utils/ime_utils.cpp" "./src/utils/ime_utils.h" "./src/utils/webview_utils.cpp" diff --git a/server/src/clipboard/clipboard_history.cpp b/server/src/clipboard/clipboard_history.cpp index b9fa1a45..a1cf1596 100644 --- a/server/src/clipboard/clipboard_history.cpp +++ b/server/src/clipboard/clipboard_history.cpp @@ -1,4 +1,5 @@ #include "clipboard/clipboard_history.h" +#include "utils/common_utils.h" #include @@ -49,11 +50,10 @@ class NamedMutex std::filesystem::path LocalAppDataDir() { - wchar_t buffer[MAX_PATH] = {}; - const DWORD length = GetEnvironmentVariableW(L"LOCALAPPDATA", buffer, MAX_PATH); - if (length == 0 || length >= MAX_PATH) + const std::wstring path = CommonUtils::get_ime_data_path_w(); + if (path.empty()) return {}; - return std::filesystem::path(buffer) / L"metasequoiaime"; + return std::filesystem::path(path); } std::filesystem::path ConfigPath() diff --git a/server/src/config/ime_config.cpp b/server/src/config/ime_config.cpp index 36f08a69..61f7c87b 100644 --- a/server/src/config/ime_config.cpp +++ b/server/src/config/ime_config.cpp @@ -56,7 +56,7 @@ std::string g_candidate_english_font = "Segoe UI"; std::string g_candidate_default_font = "Microsoft YaHei"; int g_candidate_font_size = 16; int g_candidate_window_preedit_font_size = 16; -std::atomic_bool g_diagnostic_log_enabled{true}; +std::atomic_bool g_diagnostic_log_enabled{false}; std::string g_candidate_text_color = "auto"; std::string g_shuangpin_schema = "xiaohe"; std::string g_wubi_schema = "wubi86"; @@ -99,7 +99,7 @@ bool g_paired_punctuation_enabled = true; std::string g_candidate_window_layout = "vertical"; std::string g_candidate_skin = "fluent"; std::string g_candidate_window_preedit_style = "pinyin"; -std::string g_theme_mode = "dark"; +std::string g_theme_mode = "system"; std::string g_theme_settings = "follow"; std::string g_theme_cand = "follow"; std::string g_theme_ftb = "follow"; @@ -656,7 +656,7 @@ bool LoadImeConfig() // use the unified key. g_diagnostic_log_enabled.store( tbl["general"]["diagnostic_log"].value_or( - tbl["general"]["candidate_window_diagnostic_log"].value_or(true)), + tbl["general"]["candidate_window_diagnostic_log"].value_or(false)), std::memory_order_relaxed); g_floating_toolbar_items.fullwidth = tbl["general"]["floating_toolbar_fullwidth"].value_or(true); g_floating_toolbar_items.punctuation = tbl["general"]["floating_toolbar_punctuation"].value_or(true); @@ -765,7 +765,7 @@ bool LoadImeConfig() g_candidate_window_preedit_style = preedit_style == "empty" ? "empty" : "pinyin"; } { - const std::string theme_mode = tbl["appearance"]["theme_mode"].value_or(std::string("dark")); + const std::string theme_mode = tbl["appearance"]["theme_mode"].value_or(std::string("system")); if (theme_mode == "light" || theme_mode == "system" || theme_mode == "auto") g_theme_mode = theme_mode == "auto" ? "system" : theme_mode; else diff --git a/server/src/emoji-panel/EmojiPanel.cpp b/server/src/emoji-panel/EmojiPanel.cpp index 17a9731b..ee9c1fd6 100644 --- a/server/src/emoji-panel/EmojiPanel.cpp +++ b/server/src/emoji-panel/EmojiPanel.cpp @@ -2,6 +2,7 @@ #include "emoji_panel_icons.h" #include "emoji_panel_splash.h" #include "clipboard/clipboard_history.h" +#include "utils/common_utils.h" #include "msimeui/DeviceResources.h" #include "msimeui/Theme.h" @@ -282,15 +283,12 @@ std::wstring Utf8ToWide(const char *text) std::filesystem::path OthersDatabasePath() { - std::wstring localAppData(32768, L'\0'); - const DWORD length = - GetEnvironmentVariableW(L"LOCALAPPDATA", localAppData.data(), static_cast(localAppData.size())); - if (length == 0 || length >= localAppData.size()) + const std::wstring path = CommonUtils::get_ime_data_path_w(); + if (path.empty()) { return {}; } - localAppData.resize(length); - return std::filesystem::path(localAppData) / L"metasequoiaime" / L"others.db"; + return std::filesystem::path(path) / L"others.db"; } float GroupBodyHeight(size_t itemCount, size_t columns, float cellSize) diff --git a/server/src/settings/settings_app.cpp b/server/src/settings/settings_app.cpp index d3714606..b63be897 100644 --- a/server/src/settings/settings_app.cpp +++ b/server/src/settings/settings_app.cpp @@ -784,8 +784,7 @@ HRESULT OnControllerCreated(HWND hwnd, HRESULT result, ICoreWebView2CompositionC void InitWebView(HWND hwnd) { - std::filesystem::path user_data = std::filesystem::path(CommonUtils::get_local_appdata_path()) / - GlobalIme::AppName / "webview2-settings"; + std::filesystem::path user_data = CommonUtils::get_webview2_user_data_path(L"webview2-settings"); std::error_code ec; std::filesystem::create_directories(user_data, ec); CreateCoreWebView2EnvironmentWithOptions( diff --git a/server/src/utils/common_utils.cpp b/server/src/utils/common_utils.cpp index 6ede7eb5..9a347d15 100644 --- a/server/src/utils/common_utils.cpp +++ b/server/src/utils/common_utils.cpp @@ -1,7 +1,5 @@ #include "common_utils.h" #include -#include "global/globals.h" -#include #include using namespace std; @@ -72,34 +70,12 @@ namespace CommonUtils { string get_local_appdata_path() { - char *localAppDataDir = nullptr; - std::string localAppDataDirStr; - errno_t err = _dupenv_s(&localAppDataDir, nullptr, "LOCALAPPDATA"); - if (err == 0 && localAppDataDir != nullptr) - { - localAppDataDirStr = std::string(localAppDataDir); - } - std::unique_ptr dirPtr(localAppDataDir, free); - if (!localAppDataDirStr.empty()) - { - return localAppDataDirStr; - } - - // ShellExecute / early logon can occasionally leave LOCALAPPDATA unset while - // the known-folder API still resolves the profile path correctly. - PWSTR known_path = nullptr; - if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_DEFAULT, nullptr, &known_path)) && - known_path) - { - localAppDataDirStr = wstring_to_string(known_path); - CoTaskMemFree(known_path); - } - return localAppDataDirStr; + return wstring_to_string(get_local_appdata_path_w()); } string get_ime_data_path() { - return get_local_appdata_path() + "\\" + wstring_to_string(GlobalIme::AppName); + return wstring_to_string(get_ime_data_path_w()); } string get_username() diff --git a/server/src/utils/common_utils.h b/server/src/utils/common_utils.h index d4084397..a737ad9e 100644 --- a/server/src/utils/common_utils.h +++ b/server/src/utils/common_utils.h @@ -12,6 +12,11 @@ void SendImeInputs(std::wstring words); namespace CommonUtils { +std::wstring get_local_appdata_path_w(); +std::wstring get_ime_data_path_w(); +// WebView2 cache lives under ProgramData so Medium-IL Edge children can write +// it whether the host is a normal user or elevated. Falls back to LocalAppData. +std::wstring get_webview2_user_data_path(const std::wstring &folder_name); std::string get_local_appdata_path(); std::string get_ime_data_path(); std::string get_username(); diff --git a/server/src/utils/ime_paths.cpp b/server/src/utils/ime_paths.cpp new file mode 100644 index 00000000..ea4f53c4 --- /dev/null +++ b/server/src/utils/ime_paths.cpp @@ -0,0 +1,203 @@ +#include "common_utils.h" + +#include +#include +#include +#include +#include +#include + +namespace +{ +constexpr wchar_t kAppName[] = L"metasequoiaime"; + +bool PathHasEmptyComponent(const std::wstring &path) +{ + if (path.empty()) + { + return true; + } + size_t index = 0; + if (path.size() >= 2 && path[0] == L'\\' && path[1] == L'\\') + { + index = 2; + } + else if (path[0] == L'\\') + { + return true; + } + for (; index + 1 < path.size(); ++index) + { + if (path[index] == L'\\' && path[index + 1] == L'\\') + { + return true; + } + } + return false; +} + +bool IsUsableAbsolutePath(const std::wstring &path) +{ + if (PathHasEmptyComponent(path)) + { + return false; + } + if (path.size() >= 2 && path[1] == L':') + { + return true; + } + return path.size() >= 2 && path[0] == L'\\' && path[1] == L'\\'; +} + +std::wstring QueryEnvironmentW(const wchar_t *name) +{ + const DWORD needed = GetEnvironmentVariableW(name, nullptr, 0); + if (needed == 0) + { + return {}; + } + std::wstring value(needed, L'\0'); + const DWORD written = GetEnvironmentVariableW(name, value.data(), needed); + if (written == 0 || written >= needed) + { + return {}; + } + value.resize(written); + return value; +} + +std::wstring QueryKnownFolder(REFKNOWNFOLDERID folder_id) +{ + PWSTR known_path = nullptr; + if (FAILED(SHGetKnownFolderPath(folder_id, KF_FLAG_DEFAULT, nullptr, &known_path)) || !known_path) + { + return {}; + } + std::wstring result(known_path); + CoTaskMemFree(known_path); + return result; +} + +bool DirectoryIsWritable(const std::wstring &path) +{ + std::error_code ec; + std::filesystem::create_directories(path, ec); + if (ec) + { + return false; + } + const std::filesystem::path probe = std::filesystem::path(path) / L".ime-write-probe"; + { + std::ofstream out(probe, std::ios::binary | std::ios::trunc); + if (!out) + { + return false; + } + } + std::filesystem::remove(probe, ec); + return true; +} + +void EnsureMediumIntegrityWritableDirectory(const std::wstring &path) +{ + std::error_code ec; + std::filesystem::create_directories(path, ec); + + PSID users_sid = nullptr; + if (ConvertStringSidToSidW(L"S-1-5-32-545", &users_sid)) + { + EXPLICIT_ACCESSW access{}; + access.grfAccessPermissions = GENERIC_ALL; + access.grfAccessMode = GRANT_ACCESS; + access.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; + access.Trustee.TrusteeForm = TRUSTEE_IS_SID; + access.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; + access.Trustee.ptstrName = static_cast(users_sid); + + PACL old_dacl = nullptr; + PSECURITY_DESCRIPTOR descriptor = nullptr; + PACL new_dacl = nullptr; + if (GetNamedSecurityInfoW(path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, + &old_dacl, nullptr, &descriptor) == ERROR_SUCCESS) + { + if (SetEntriesInAclW(1, &access, old_dacl, &new_dacl) == ERROR_SUCCESS) + { + SetNamedSecurityInfoW(const_cast(path.c_str()), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, + nullptr, nullptr, new_dacl, nullptr); + LocalFree(new_dacl); + } + LocalFree(descriptor); + } + LocalFree(users_sid); + } + + PSID medium_sid = nullptr; + if (!ConvertStringSidToSidW(L"S-1-16-8192", &medium_sid)) + { + return; + } + const DWORD sacl_size = + sizeof(ACL) + GetLengthSid(medium_sid) + sizeof(SYSTEM_MANDATORY_LABEL_ACE) + 32; + std::vector sacl_buffer(sacl_size); + auto *sacl = reinterpret_cast(sacl_buffer.data()); + if (InitializeAcl(sacl, sacl_size, ACL_REVISION) && + AddMandatoryAce(sacl, ACL_REVISION, 0, SYSTEM_MANDATORY_LABEL_NO_WRITE_UP, medium_sid)) + { + SetNamedSecurityInfoW(const_cast(path.c_str()), SE_FILE_OBJECT, LABEL_SECURITY_INFORMATION, + nullptr, nullptr, nullptr, sacl); + } + LocalFree(medium_sid); +} +} // namespace + +namespace CommonUtils +{ +std::wstring get_local_appdata_path_w() +{ + const std::wstring from_env = QueryEnvironmentW(L"LOCALAPPDATA"); + if (IsUsableAbsolutePath(from_env)) + { + return from_env; + } + + const std::wstring known = QueryKnownFolder(FOLDERID_LocalAppData); + if (IsUsableAbsolutePath(known)) + { + return known; + } + return {}; +} + +std::wstring get_ime_data_path_w() +{ + return get_local_appdata_path_w() + L"\\" + kAppName; +} + +std::wstring get_webview2_user_data_path(const std::wstring &folder_name) +{ + std::wstring program_data = QueryKnownFolder(FOLDERID_ProgramData); + if (!IsUsableAbsolutePath(program_data)) + { + program_data = QueryEnvironmentW(L"ProgramData"); + } + if (!IsUsableAbsolutePath(program_data)) + { + program_data = L"C:\\ProgramData"; + } + const std::wstring root = program_data + L"\\" + kAppName; + const std::wstring path = root + L"\\" + folder_name; + EnsureMediumIntegrityWritableDirectory(root); + EnsureMediumIntegrityWritableDirectory(path); + if (DirectoryIsWritable(path)) + { + return path; + } + + const std::wstring local = get_local_appdata_path_w(); + if (!IsUsableAbsolutePath(local)) + { + return path; + } + return local + L"\\" + kAppName + L"\\" + folder_name; +} +} // namespace CommonUtils diff --git a/server/src/utils/surface_theme_config.cpp b/server/src/utils/surface_theme_config.cpp index 5165260a..2b41b5d4 100644 --- a/server/src/utils/surface_theme_config.cpp +++ b/server/src/utils/surface_theme_config.cpp @@ -1,4 +1,5 @@ #include "surface_theme_config.h" +#include "utils/common_utils.h" #include #include @@ -27,13 +28,10 @@ std::string Unquote(std::string value) std::unordered_map ReadAppearance() { std::unordered_map values; - std::wstring local_app_data(32768, L'\0'); - const DWORD length = - GetEnvironmentVariableW(L"LOCALAPPDATA", local_app_data.data(), static_cast(local_app_data.size())); - if (length == 0 || length >= local_app_data.size()) return values; - local_app_data.resize(length); + const std::wstring ime_data = CommonUtils::get_ime_data_path_w(); + if (ime_data.empty()) return values; - std::ifstream input(std::filesystem::path(local_app_data) / L"metasequoiaime" / L"config.toml"); + std::ifstream input(std::filesystem::path(ime_data) / L"config.toml"); bool in_appearance = false; for (std::string line; std::getline(input, line);) { diff --git a/server/src/voice-input/voice_input_service.cpp b/server/src/voice-input/voice_input_service.cpp index ac6920fc..137901ec 100644 --- a/server/src/voice-input/voice_input_service.cpp +++ b/server/src/voice-input/voice_input_service.cpp @@ -81,11 +81,10 @@ constexpr wchar_t kMessageWindowClass[] = L"MetasequoiaImeVoiceInputMessageWindo std::wstring ResolveCuePath(const wchar_t *filename) { - wchar_t local_app_data[MAX_PATH]{}; - const DWORD size = GetEnvironmentVariableW(L"LOCALAPPDATA", local_app_data, MAX_PATH); - if (size && size < MAX_PATH) + const std::wstring ime_data = CommonUtils::get_ime_data_path_w(); + if (!ime_data.empty()) { - const std::wstring installed = std::wstring(local_app_data) + L"\\metasequoiaime\\assets\\audios\\" + filename; + const std::wstring installed = ime_data + L"\\assets\\audios\\" + filename; if (GetFileAttributesW(installed.c_str()) != INVALID_FILE_ATTRIBUTES) return installed; } wchar_t executable[MAX_PATH]{}; diff --git a/server/src/webview2/windows_webview2.cpp b/server/src/webview2/windows_webview2.cpp index 7d7edcbb..2a306122 100644 --- a/server/src/webview2/windows_webview2.cpp +++ b/server/src/webview2/windows_webview2.cpp @@ -1212,11 +1212,7 @@ std::wstring ReadHtmlFile(const std::wstring &filePath) std::wstring GetAppdataPath() { - return string_to_wstring(CommonUtils::get_local_appdata_path()) + // - LR"(\)" + // - GlobalIme::AppName + // - LR"(\)" + // - LR"(webview2)"; + return CommonUtils::get_webview2_user_data_path(L"webview2"); } // An unreadable themed asset must never turn into an empty NavigateToString: diff --git a/server/tests/CMakeLists.txt b/server/tests/CMakeLists.txt index 977afd42..8353233a 100644 --- a/server/tests/CMakeLists.txt +++ b/server/tests/CMakeLists.txt @@ -44,6 +44,7 @@ set(TEST_SOURCE_FILES "${CMAKE_SOURCE_DIR}/src/config/ime_config.cpp" "${CMAKE_SOURCE_DIR}/src/session/engine_input_session.cpp" "${CMAKE_SOURCE_DIR}/src/utils/common_utils.cpp" + "${CMAKE_SOURCE_DIR}/src/utils/ime_paths.cpp" "${CMAKE_SOURCE_DIR}/src/settings/dictionary_validation.cpp" "${CMAKE_SOURCE_DIR}/MetasequoiaImeEngine/common/helpcode_utils.cpp" "${CMAKE_SOURCE_DIR}/MetasequoiaImeEngine/core/ime_session.cpp" @@ -104,6 +105,9 @@ target_link_libraries(${TEST_EXECUTABLE_NAME} Boost::json unofficial::sqlite3::sqlite3 dwrite + shell32 + advapi32 + ole32 ) add_test(NAME ${TEST_EXECUTABLE_NAME} COMMAND ${TEST_EXECUTABLE_NAME})