词库、用户配置和皮肤此前固定在 %LOCALAPPDATA%\metasequoiaime,整包几百 MB 全压在 C 盘。安装向导新增一页,允许把这部分数据放到任意盘。 程序本体仍固定装在 Program Files:MetasequoiaImeServer.exe 带 uiAccess=true, 只有在受信任目录里这个标志才生效。 运行期的权威来源是 HKLM\Software\Metasequoia\MetasequoiaIME 的 DataDir。 Server、TSF DLL 和引擎三个组件各自按同一顺序解析:METASEQUOIA_IME_DATA_DIR 环境变量 -> 该注册表值 -> %LOCALAPPDATA%\metasequoiaime。三处各持一份实现, 因为组件边界规则禁止 windows/ 与 server/ 互相 include。32 位 TSF DLL 用 KEY_WOW64_64KEY 读,避开 WOW64 重定向。 安全相关的两个约束: - 安装器在数据目录里写 .metasequoiaime-data 标记。覆盖安装的清理和卸载的整目录 删除只在看到这个标记(或目录就是历史默认位置)时才执行——用户可能把数据目录 指到一个本来就有自己文件的文件夹。 - 选择页与 /DATADIR= 走同一套校验:拒绝相对路径、盘符根目录、不存在的盘、程序 目录内部,以及 Windows/Program Files/各 AppData/用户主目录本身。 升级时改了位置,PrepareToInstall 在停掉输入法进程后用 robocopy /MOVE 搬运用户 拥有的数据(msime_user.db 及其 WAL/SHM/journal、config.toml、config.base.toml、 skins\),词库和前端资源由本次安装重新写入。跨卷移动目录 MoveFile 会失败,所以用 robocopy。用户词库搬运失败会中止安装:否则新版本对着空的 msime_user.db 启动, ssPostInstall 的回放只记一句 skipped,用户的自造词就静悄悄没了。 测试侧新增 METASEQUOIA_IME_CONFIG_DIR,只改 config.toml 的位置而不搬词库。原先 靠重定向 LOCALAPPDATA 做隔离的办法在装了正式版的机器上已不再成立——注册表值 的优先级高于它。
192 lines
5.4 KiB
C++
192 lines
5.4 KiB
C++
#pragma once
|
|
|
|
#include <cstdlib>
|
|
#include <cwchar>
|
|
#include <filesystem>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
|
|
#ifdef _WIN32
|
|
#include <Windows.h>
|
|
#include <shlobj.h>
|
|
#endif
|
|
|
|
namespace metasequoia
|
|
{
|
|
#ifdef _WIN32
|
|
namespace detail
|
|
{
|
|
inline std::optional<std::wstring> wide_environment_variable(const wchar_t *name)
|
|
{
|
|
wchar_t *buffer = nullptr;
|
|
size_t size = 0;
|
|
if (_wdupenv_s(&buffer, &size, name) != 0 || buffer == nullptr)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
const std::unique_ptr<wchar_t, decltype(&std::free)> owned_buffer(buffer, &std::free);
|
|
return std::wstring(owned_buffer.get());
|
|
}
|
|
|
|
// The installer lets the user put user data on another volume and records that choice here.
|
|
// KEY_WOW64_64KEY is mandatory: the 32-bit TSF DLL and any 32-bit host would otherwise be
|
|
// redirected to Wow6432Node, where the 64-bit setup never wrote anything.
|
|
inline std::optional<std::wstring> read_installed_data_directory()
|
|
{
|
|
HKEY key = nullptr;
|
|
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Metasequoia\\MetasequoiaIME", 0,
|
|
KEY_QUERY_VALUE | KEY_WOW64_64KEY, &key) != ERROR_SUCCESS)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
DWORD type = 0;
|
|
DWORD bytes = 0;
|
|
if (RegQueryValueExW(key, L"DataDir", nullptr, &type, nullptr, &bytes) != ERROR_SUCCESS || type != REG_SZ ||
|
|
bytes < sizeof(wchar_t))
|
|
{
|
|
RegCloseKey(key);
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::wstring value(bytes / sizeof(wchar_t), L'\0');
|
|
const LSTATUS status =
|
|
RegQueryValueExW(key, L"DataDir", nullptr, &type, reinterpret_cast<LPBYTE>(value.data()), &bytes);
|
|
RegCloseKey(key);
|
|
if (status != ERROR_SUCCESS)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
// The stored length may or may not include the terminator, and REG_SZ is not required to be
|
|
// terminated at all; cut the string at the first NUL that is actually present.
|
|
value.resize(std::wcslen(value.c_str()));
|
|
if (value.empty())
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
inline std::optional<std::wstring> installed_data_directory()
|
|
{
|
|
// data_directory() sits on dictionary lookup paths; the install location cannot change while a
|
|
// process lives, so read it once.
|
|
static const std::optional<std::wstring> cached = read_installed_data_directory();
|
|
return cached;
|
|
}
|
|
} // namespace detail
|
|
#endif
|
|
|
|
inline std::filesystem::path path_from_utf8(const char *path)
|
|
{
|
|
#if defined(__cpp_lib_char8_t)
|
|
return std::filesystem::path(std::u8string(reinterpret_cast<const char8_t *>(path)));
|
|
#else
|
|
return std::filesystem::u8path(path);
|
|
#endif
|
|
}
|
|
|
|
inline std::filesystem::path data_directory()
|
|
{
|
|
#ifdef _WIN32
|
|
if (const auto override_path = detail::wide_environment_variable(L"METASEQUOIA_IME_DATA_DIR"))
|
|
{
|
|
const std::filesystem::path path(*override_path);
|
|
if (path.is_absolute())
|
|
{
|
|
return path;
|
|
}
|
|
}
|
|
#else
|
|
if (const char *override_path = std::getenv("METASEQUOIA_IME_DATA_DIR"))
|
|
{
|
|
const std::filesystem::path path = path_from_utf8(override_path);
|
|
if (path.is_absolute())
|
|
{
|
|
return path;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
// Chosen during setup. It outranks LOCALAPPDATA but not the environment override, so tests and
|
|
// isolated runs keep working on a machine that has the product installed.
|
|
if (const auto installed_path = detail::installed_data_directory())
|
|
{
|
|
const std::filesystem::path path(*installed_path);
|
|
if (path.is_absolute())
|
|
{
|
|
return path;
|
|
}
|
|
}
|
|
|
|
if (const auto local_app_data = detail::wide_environment_variable(L"LOCALAPPDATA"))
|
|
{
|
|
const std::filesystem::path root(*local_app_data);
|
|
if (root.is_absolute())
|
|
{
|
|
return root / L"metasequoiaime";
|
|
}
|
|
}
|
|
|
|
PWSTR known_path = nullptr;
|
|
if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_DEFAULT, nullptr, &known_path)) && known_path)
|
|
{
|
|
const std::filesystem::path result = std::filesystem::path(known_path) / L"metasequoiaime";
|
|
CoTaskMemFree(known_path);
|
|
return result;
|
|
}
|
|
#elif defined(__APPLE__)
|
|
if (const char *home = std::getenv("HOME"))
|
|
{
|
|
const std::filesystem::path root(home);
|
|
if (root.is_absolute())
|
|
{
|
|
return root / "Library" / "Application Support" / "metasequoiaime";
|
|
}
|
|
}
|
|
#else
|
|
if (const char *xdg_data_home = std::getenv("XDG_DATA_HOME"))
|
|
{
|
|
const std::filesystem::path root(xdg_data_home);
|
|
if (root.is_absolute())
|
|
{
|
|
return root / "metasequoiaime";
|
|
}
|
|
}
|
|
if (const char *home = std::getenv("HOME"))
|
|
{
|
|
const std::filesystem::path root(home);
|
|
if (root.is_absolute())
|
|
{
|
|
return root / ".local" / "share" / "metasequoiaime";
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return {};
|
|
}
|
|
|
|
inline std::filesystem::path data_file_path(const std::filesystem::path &relative_path)
|
|
{
|
|
const std::filesystem::path directory = data_directory();
|
|
if (directory.empty() || relative_path.empty() || relative_path.is_absolute())
|
|
{
|
|
return {};
|
|
}
|
|
return directory / relative_path;
|
|
}
|
|
|
|
inline std::string path_to_utf8(const std::filesystem::path &path)
|
|
{
|
|
const auto utf8_path = path.u8string();
|
|
#if defined(__cpp_lib_char8_t)
|
|
return {reinterpret_cast<const char *>(utf8_path.data()), utf8_path.size()};
|
|
#else
|
|
return utf8_path;
|
|
#endif
|
|
}
|
|
} // namespace metasequoia
|