#8332 - File association on Windows: reimplemented to allow setting PrusaSlicer as default application for .3mf and .stl files and GCodeViewer as default application for .gcode files
This commit is contained in:
parent
fcd3966a5b
commit
1b09628b0d
@ -246,6 +246,8 @@ set(SLIC3R_GUI_SOURCES
|
|||||||
Utils/TCPConsole.hpp
|
Utils/TCPConsole.hpp
|
||||||
Utils/MKS.cpp
|
Utils/MKS.cpp
|
||||||
Utils/MKS.hpp
|
Utils/MKS.hpp
|
||||||
|
Utils/WinRegistry.cpp
|
||||||
|
Utils/WinRegistry.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
|
@ -59,6 +59,7 @@
|
|||||||
#include "../Utils/Process.hpp"
|
#include "../Utils/Process.hpp"
|
||||||
#include "../Utils/MacDarkMode.hpp"
|
#include "../Utils/MacDarkMode.hpp"
|
||||||
#include "../Utils/AppUpdater.hpp"
|
#include "../Utils/AppUpdater.hpp"
|
||||||
|
#include "../Utils/WinRegistry.hpp"
|
||||||
#include "slic3r/Config/Snapshot.hpp"
|
#include "slic3r/Config/Snapshot.hpp"
|
||||||
#include "ConfigSnapshotDialog.hpp"
|
#include "ConfigSnapshotDialog.hpp"
|
||||||
#include "FirmwareDialog.hpp"
|
#include "FirmwareDialog.hpp"
|
||||||
@ -3135,119 +3136,22 @@ bool GUI_App::open_browser_with_warning_dialog(const wxString& url, wxWindow* pa
|
|||||||
|
|
||||||
|
|
||||||
#ifdef __WXMSW__
|
#ifdef __WXMSW__
|
||||||
static bool set_into_win_registry(HKEY hkeyHive, const wchar_t* pszVar, const wchar_t* pszValue)
|
|
||||||
{
|
|
||||||
// see as reference: https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association
|
|
||||||
wchar_t szValueCurrent[1000];
|
|
||||||
DWORD dwType;
|
|
||||||
DWORD dwSize = sizeof(szValueCurrent);
|
|
||||||
|
|
||||||
int iRC = ::RegGetValueW(hkeyHive, pszVar, nullptr, RRF_RT_ANY, &dwType, szValueCurrent, &dwSize);
|
|
||||||
|
|
||||||
bool bDidntExist = iRC == ERROR_FILE_NOT_FOUND;
|
|
||||||
|
|
||||||
if ((iRC != ERROR_SUCCESS) && !bDidntExist)
|
|
||||||
// an error occurred
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!bDidntExist) {
|
|
||||||
if (dwType != REG_SZ)
|
|
||||||
// invalid type
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (::wcscmp(szValueCurrent, pszValue) == 0)
|
|
||||||
// value already set
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
DWORD dwDisposition;
|
|
||||||
HKEY hkey;
|
|
||||||
iRC = ::RegCreateKeyExW(hkeyHive, pszVar, 0, 0, 0, KEY_ALL_ACCESS, nullptr, &hkey, &dwDisposition);
|
|
||||||
bool ret = false;
|
|
||||||
if (iRC == ERROR_SUCCESS) {
|
|
||||||
iRC = ::RegSetValueExW(hkey, L"", 0, REG_SZ, (BYTE*)pszValue, (::wcslen(pszValue) + 1) * sizeof(wchar_t));
|
|
||||||
if (iRC == ERROR_SUCCESS)
|
|
||||||
ret = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
RegCloseKey(hkey);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GUI_App::associate_3mf_files()
|
void GUI_App::associate_3mf_files()
|
||||||
{
|
{
|
||||||
wchar_t app_path[MAX_PATH];
|
associate_file_type(L".3mf", L"Prusa.Slicer.1", L"PrusaSlicer", true);
|
||||||
::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
|
|
||||||
|
|
||||||
std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\"";
|
|
||||||
std::wstring prog_id = L"Prusa.Slicer.1";
|
|
||||||
std::wstring prog_desc = L"PrusaSlicer";
|
|
||||||
std::wstring prog_command = prog_path + L" \"%1\"";
|
|
||||||
std::wstring reg_base = L"Software\\Classes";
|
|
||||||
std::wstring reg_extension = reg_base + L"\\.3mf";
|
|
||||||
std::wstring reg_prog_id = reg_base + L"\\" + prog_id;
|
|
||||||
std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command";
|
|
||||||
|
|
||||||
bool is_new = false;
|
|
||||||
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str());
|
|
||||||
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str());
|
|
||||||
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str());
|
|
||||||
|
|
||||||
if (is_new)
|
|
||||||
// notify Windows only when any of the values gets changed
|
|
||||||
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GUI_App::associate_stl_files()
|
void GUI_App::associate_stl_files()
|
||||||
{
|
{
|
||||||
wchar_t app_path[MAX_PATH];
|
associate_file_type(L".stl", L"Prusa.Slicer.1", L"PrusaSlicer", true);
|
||||||
::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
|
|
||||||
|
|
||||||
std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\"";
|
|
||||||
std::wstring prog_id = L"Prusa.Slicer.1";
|
|
||||||
std::wstring prog_desc = L"PrusaSlicer";
|
|
||||||
std::wstring prog_command = prog_path + L" \"%1\"";
|
|
||||||
std::wstring reg_base = L"Software\\Classes";
|
|
||||||
std::wstring reg_extension = reg_base + L"\\.stl";
|
|
||||||
std::wstring reg_prog_id = reg_base + L"\\" + prog_id;
|
|
||||||
std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command";
|
|
||||||
|
|
||||||
bool is_new = false;
|
|
||||||
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str());
|
|
||||||
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str());
|
|
||||||
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str());
|
|
||||||
|
|
||||||
if (is_new)
|
|
||||||
// notify Windows only when any of the values gets changed
|
|
||||||
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GUI_App::associate_gcode_files()
|
void GUI_App::associate_gcode_files()
|
||||||
{
|
{
|
||||||
wchar_t app_path[MAX_PATH];
|
associate_file_type(L".gcode", L"PrusaSlicer.GCodeViewer.1", L"PrusaSlicerGCodeViewer", true);
|
||||||
::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
|
|
||||||
|
|
||||||
std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\"";
|
|
||||||
std::wstring prog_id = L"PrusaSlicer.GCodeViewer.1";
|
|
||||||
std::wstring prog_desc = L"PrusaSlicerGCodeViewer";
|
|
||||||
std::wstring prog_command = prog_path + L" \"%1\"";
|
|
||||||
std::wstring reg_base = L"Software\\Classes";
|
|
||||||
std::wstring reg_extension = reg_base + L"\\.gcode";
|
|
||||||
std::wstring reg_prog_id = reg_base + L"\\" + prog_id;
|
|
||||||
std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command";
|
|
||||||
|
|
||||||
bool is_new = false;
|
|
||||||
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str());
|
|
||||||
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str());
|
|
||||||
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str());
|
|
||||||
|
|
||||||
if (is_new)
|
|
||||||
// notify Windows only when any of the values gets changed
|
|
||||||
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
|
|
||||||
}
|
}
|
||||||
#endif // __WXMSW__
|
#endif // __WXMSW__
|
||||||
|
|
||||||
|
|
||||||
void GUI_App::on_version_read(wxCommandEvent& evt)
|
void GUI_App::on_version_read(wxCommandEvent& evt)
|
||||||
{
|
{
|
||||||
app_config->set("version_online", into_u8(evt.GetString()));
|
app_config->set("version_online", into_u8(evt.GetString()));
|
||||||
|
490
src/slic3r/Utils/WinRegistry.cpp
Normal file
490
src/slic3r/Utils/WinRegistry.cpp
Normal file
@ -0,0 +1,490 @@
|
|||||||
|
#include "libslic3r/Technologies.hpp"
|
||||||
|
#include "WinRegistry.hpp"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <shlobj.h>
|
||||||
|
#include <wincrypt.h>
|
||||||
|
#include <winternl.h>
|
||||||
|
#include <sddl.h>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
// Helper class which automatically closes the handle when
|
||||||
|
// going out of scope
|
||||||
|
class AutoHandle
|
||||||
|
{
|
||||||
|
HANDLE m_handle{ nullptr };
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AutoHandle(HANDLE handle) : m_handle(handle) {}
|
||||||
|
~AutoHandle() { if (m_handle != nullptr) ::CloseHandle(m_handle); }
|
||||||
|
HANDLE get() { return m_handle; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper class which automatically closes the key when
|
||||||
|
// going out of scope
|
||||||
|
class AutoRegKey
|
||||||
|
{
|
||||||
|
HKEY m_key{ nullptr };
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AutoRegKey(HKEY key) : m_key(key) {}
|
||||||
|
~AutoRegKey() { if (m_key != nullptr) ::RegCloseKey(m_key); }
|
||||||
|
HKEY get() { return m_key; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// returns true if the given value is set/modified into Windows registry
|
||||||
|
static bool set_into_win_registry(HKEY hkeyHive, const wchar_t* pszVar, const wchar_t* pszValue)
|
||||||
|
{
|
||||||
|
// see as reference: https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association
|
||||||
|
wchar_t szValueCurrent[1000];
|
||||||
|
DWORD dwType;
|
||||||
|
DWORD dwSize = sizeof(szValueCurrent);
|
||||||
|
|
||||||
|
LSTATUS iRC = ::RegGetValueW(hkeyHive, pszVar, nullptr, RRF_RT_ANY, &dwType, szValueCurrent, &dwSize);
|
||||||
|
|
||||||
|
const bool bDidntExist = iRC == ERROR_FILE_NOT_FOUND;
|
||||||
|
|
||||||
|
if (iRC != ERROR_SUCCESS && !bDidntExist)
|
||||||
|
// an error occurred
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!bDidntExist) {
|
||||||
|
if (dwType != REG_SZ)
|
||||||
|
// invalid type
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (::wcscmp(szValueCurrent, pszValue) == 0)
|
||||||
|
// value already set
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD dwDisposition;
|
||||||
|
HKEY hkey;
|
||||||
|
iRC = ::RegCreateKeyExW(hkeyHive, pszVar, 0, 0, 0, KEY_ALL_ACCESS, nullptr, &hkey, &dwDisposition);
|
||||||
|
bool ret = false;
|
||||||
|
if (iRC == ERROR_SUCCESS) {
|
||||||
|
iRC = ::RegSetValueExW(hkey, L"", 0, REG_SZ, (BYTE*)pszValue, (::wcslen(pszValue) + 1) * sizeof(wchar_t));
|
||||||
|
if (iRC == ERROR_SUCCESS)
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegCloseKey(hkey);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::wstring get_current_user_string_sid()
|
||||||
|
{
|
||||||
|
HANDLE rawProcessToken;
|
||||||
|
if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY,
|
||||||
|
&rawProcessToken))
|
||||||
|
return L"";
|
||||||
|
|
||||||
|
AutoHandle processToken(rawProcessToken);
|
||||||
|
|
||||||
|
DWORD userSize = 0;
|
||||||
|
if (!(!::GetTokenInformation(processToken.get(), TokenUser, nullptr, 0, &userSize) &&
|
||||||
|
GetLastError() == ERROR_INSUFFICIENT_BUFFER))
|
||||||
|
return L"";
|
||||||
|
|
||||||
|
std::vector<unsigned char> userBytes(userSize);
|
||||||
|
if (!::GetTokenInformation(processToken.get(), TokenUser, userBytes.data(), userSize, &userSize))
|
||||||
|
return L"";
|
||||||
|
|
||||||
|
wchar_t* rawSid = nullptr;
|
||||||
|
if (!::ConvertSidToStringSidW(reinterpret_cast<PTOKEN_USER>(userBytes.data())->User.Sid, &rawSid))
|
||||||
|
return L"";
|
||||||
|
|
||||||
|
return rawSid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the string which becomes the input to the UserChoice hash.
|
||||||
|
*
|
||||||
|
* @see generate_user_choice_hash() for parameters.
|
||||||
|
*
|
||||||
|
* @return The formatted string, empty string on failure.
|
||||||
|
*
|
||||||
|
* NOTE: This uses the format as of Windows 10 20H2 (latest as of this writing),
|
||||||
|
* used at least since 1803.
|
||||||
|
* There was at least one older version, not currently supported: On Win10 RTM
|
||||||
|
* (build 10240, aka 1507) the hash function is the same, but the timestamp and
|
||||||
|
* User Experience string aren't included; instead (for protocols) the string
|
||||||
|
* ends with the exe path. The changelog of SetUserFTA suggests the algorithm
|
||||||
|
* changed in 1703, so there may be two versions: before 1703, and 1703 to now.
|
||||||
|
*/
|
||||||
|
static std::wstring format_user_choice_string(const wchar_t* aExt, const wchar_t* aUserSid, const wchar_t* aProgId, SYSTEMTIME aTimestamp)
|
||||||
|
{
|
||||||
|
aTimestamp.wSecond = 0;
|
||||||
|
aTimestamp.wMilliseconds = 0;
|
||||||
|
|
||||||
|
FILETIME fileTime = { 0 };
|
||||||
|
if (!::SystemTimeToFileTime(&aTimestamp, &fileTime))
|
||||||
|
return L"";
|
||||||
|
|
||||||
|
// This string is built into Windows as part of the UserChoice hash algorithm.
|
||||||
|
// It might vary across Windows SKUs (e.g. Windows 10 vs. Windows Server), or
|
||||||
|
// across builds of the same SKU, but this is the only currently known
|
||||||
|
// version. There isn't any known way of deriving it, so we assume this
|
||||||
|
// constant value. If we are wrong, we will not be able to generate correct
|
||||||
|
// UserChoice hashes.
|
||||||
|
const wchar_t* userExperience =
|
||||||
|
L"User Choice set via Windows User Experience "
|
||||||
|
L"{D18B6DD5-6124-4341-9318-804003BAFA0B}";
|
||||||
|
|
||||||
|
const wchar_t* userChoiceFmt =
|
||||||
|
L"%s%s%s"
|
||||||
|
L"%08lx"
|
||||||
|
L"%08lx"
|
||||||
|
L"%s";
|
||||||
|
|
||||||
|
int userChoiceLen = _scwprintf(userChoiceFmt, aExt, aUserSid, aProgId,
|
||||||
|
fileTime.dwHighDateTime, fileTime.dwLowDateTime, userExperience);
|
||||||
|
userChoiceLen += 1; // _scwprintf does not include the terminator
|
||||||
|
|
||||||
|
std::wstring userChoice(userChoiceLen, L'\0');
|
||||||
|
_snwprintf_s(userChoice.data(), userChoiceLen, _TRUNCATE, userChoiceFmt, aExt,
|
||||||
|
aUserSid, aProgId, fileTime.dwHighDateTime, fileTime.dwLowDateTime, userExperience);
|
||||||
|
|
||||||
|
::CharLowerW(userChoice.data());
|
||||||
|
return userChoice;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @return The MD5 hash of the input, nullptr on failure.
|
||||||
|
static std::vector<DWORD> cng_md5(const unsigned char* bytes, ULONG bytesLen) {
|
||||||
|
constexpr ULONG MD5_BYTES = 16;
|
||||||
|
constexpr ULONG MD5_DWORDS = MD5_BYTES / sizeof(DWORD);
|
||||||
|
std::vector<DWORD> hash;
|
||||||
|
|
||||||
|
BCRYPT_ALG_HANDLE hAlg = nullptr;
|
||||||
|
if (NT_SUCCESS(::BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_MD5_ALGORITHM, nullptr, 0))) {
|
||||||
|
BCRYPT_HASH_HANDLE hHash = nullptr;
|
||||||
|
// As of Windows 7 the hash handle will manage its own object buffer when
|
||||||
|
// pbHashObject is nullptr and cbHashObject is 0.
|
||||||
|
if (NT_SUCCESS(::BCryptCreateHash(hAlg, &hHash, nullptr, 0, nullptr, 0, 0))) {
|
||||||
|
// BCryptHashData promises not to modify pbInput.
|
||||||
|
if (NT_SUCCESS(::BCryptHashData(hHash, const_cast<unsigned char*>(bytes), bytesLen, 0))) {
|
||||||
|
hash.resize(MD5_DWORDS);
|
||||||
|
if (!NT_SUCCESS(::BCryptFinishHash(hHash, reinterpret_cast<unsigned char*>(hash.data()),
|
||||||
|
MD5_DWORDS * sizeof(DWORD), 0)))
|
||||||
|
hash.clear();
|
||||||
|
}
|
||||||
|
::BCryptDestroyHash(hHash);
|
||||||
|
}
|
||||||
|
::BCryptCloseAlgorithmProvider(hAlg, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline DWORD word_swap(DWORD v)
|
||||||
|
{
|
||||||
|
return (v >> 16) | (v << 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @return The input bytes encoded as base64, nullptr on failure.
|
||||||
|
static std::wstring crypto_api_base64_encode(const unsigned char* bytes, DWORD bytesLen) {
|
||||||
|
DWORD base64Len = 0;
|
||||||
|
if (!::CryptBinaryToStringW(bytes, bytesLen, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, nullptr, &base64Len))
|
||||||
|
return L"";
|
||||||
|
|
||||||
|
std::wstring base64(base64Len, L'\0');
|
||||||
|
if (!::CryptBinaryToStringW(bytes, bytesLen, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, base64.data(), &base64Len))
|
||||||
|
return L"";
|
||||||
|
|
||||||
|
return base64;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Generate the UserChoice Hash.
|
||||||
|
*
|
||||||
|
* This implementation is based on the references listed above.
|
||||||
|
* It is organized to show the logic as clearly as possible, but at some
|
||||||
|
* point the reasoning is just "this is how it works".
|
||||||
|
*
|
||||||
|
* @param inputString A null-terminated string to hash.
|
||||||
|
*
|
||||||
|
* @return The base64-encoded hash, or empty string on failure.
|
||||||
|
*/
|
||||||
|
static std::wstring hash_string(const wchar_t* inputString)
|
||||||
|
{
|
||||||
|
auto inputBytes = reinterpret_cast<const unsigned char*>(inputString);
|
||||||
|
int inputByteCount = (::lstrlenW(inputString) + 1) * sizeof(wchar_t);
|
||||||
|
|
||||||
|
constexpr size_t DWORDS_PER_BLOCK = 2;
|
||||||
|
constexpr size_t BLOCK_SIZE = sizeof(DWORD) * DWORDS_PER_BLOCK;
|
||||||
|
// Incomplete blocks are ignored.
|
||||||
|
int blockCount = inputByteCount / BLOCK_SIZE;
|
||||||
|
|
||||||
|
if (blockCount == 0)
|
||||||
|
return L"";
|
||||||
|
|
||||||
|
// Compute an MD5 hash. md5[0] and md5[1] will be used as constant multipliers
|
||||||
|
// in the scramble below.
|
||||||
|
auto md5 = cng_md5(inputBytes, inputByteCount);
|
||||||
|
if (md5.empty())
|
||||||
|
return L"";
|
||||||
|
|
||||||
|
// The following loop effectively computes two checksums, scrambled like a
|
||||||
|
// hash after every DWORD is added.
|
||||||
|
|
||||||
|
// Constant multipliers for the scramble, one set for each DWORD in a block.
|
||||||
|
const DWORD C0s[DWORDS_PER_BLOCK][5] = {
|
||||||
|
{md5[0] | 1, 0xCF98B111uL, 0x87085B9FuL, 0x12CEB96DuL, 0x257E1D83uL},
|
||||||
|
{md5[1] | 1, 0xA27416F5uL, 0xD38396FFuL, 0x7C932B89uL, 0xBFA49F69uL} };
|
||||||
|
const DWORD C1s[DWORDS_PER_BLOCK][5] = {
|
||||||
|
{md5[0] | 1, 0xEF0569FBuL, 0x689B6B9FuL, 0x79F8A395uL, 0xC3EFEA97uL},
|
||||||
|
{md5[1] | 1, 0xC31713DBuL, 0xDDCD1F0FuL, 0x59C3AF2DuL, 0x35BD1EC9uL} };
|
||||||
|
|
||||||
|
// The checksums.
|
||||||
|
DWORD h0 = 0;
|
||||||
|
DWORD h1 = 0;
|
||||||
|
// Accumulated total of the checksum after each DWORD.
|
||||||
|
DWORD h0Acc = 0;
|
||||||
|
DWORD h1Acc = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < blockCount; ++i) {
|
||||||
|
for (int j = 0; j < DWORDS_PER_BLOCK; ++j) {
|
||||||
|
const DWORD* C0 = C0s[j];
|
||||||
|
const DWORD* C1 = C1s[j];
|
||||||
|
|
||||||
|
DWORD input;
|
||||||
|
memcpy(&input, &inputBytes[(i * DWORDS_PER_BLOCK + j) * sizeof(DWORD)], sizeof(DWORD));
|
||||||
|
|
||||||
|
h0 += input;
|
||||||
|
// Scramble 0
|
||||||
|
h0 *= C0[0];
|
||||||
|
h0 = word_swap(h0) * C0[1];
|
||||||
|
h0 = word_swap(h0) * C0[2];
|
||||||
|
h0 = word_swap(h0) * C0[3];
|
||||||
|
h0 = word_swap(h0) * C0[4];
|
||||||
|
h0Acc += h0;
|
||||||
|
|
||||||
|
h1 += input;
|
||||||
|
// Scramble 1
|
||||||
|
h1 = word_swap(h1) * C1[1] + h1 * C1[0];
|
||||||
|
h1 = (h1 >> 16) * C1[2] + h1 * C1[3];
|
||||||
|
h1 = word_swap(h1) * C1[4] + h1;
|
||||||
|
h1Acc += h1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD hash[2] = { h0 ^ h1, h0Acc ^ h1Acc };
|
||||||
|
return crypto_api_base64_encode(reinterpret_cast<const unsigned char*>(hash), sizeof(hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::wstring generate_user_choice_hash(const wchar_t* aExt, const wchar_t* aUserSid, const wchar_t* aProgId, SYSTEMTIME aTimestamp)
|
||||||
|
{
|
||||||
|
const std::wstring userChoice = format_user_choice_string(aExt, aUserSid, aProgId, aTimestamp);
|
||||||
|
if (userChoice.empty())
|
||||||
|
return L"";
|
||||||
|
|
||||||
|
return hash_string(userChoice.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool add_milliseconds_to_system_time(SYSTEMTIME& aSystemTime, ULONGLONG aIncrementMS)
|
||||||
|
{
|
||||||
|
FILETIME fileTime;
|
||||||
|
ULARGE_INTEGER fileTimeInt;
|
||||||
|
if (!::SystemTimeToFileTime(&aSystemTime, &fileTime))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
fileTimeInt.LowPart = fileTime.dwLowDateTime;
|
||||||
|
fileTimeInt.HighPart = fileTime.dwHighDateTime;
|
||||||
|
|
||||||
|
// FILETIME is in units of 100ns.
|
||||||
|
fileTimeInt.QuadPart += aIncrementMS * 1000 * 10;
|
||||||
|
|
||||||
|
fileTime.dwLowDateTime = fileTimeInt.LowPart;
|
||||||
|
fileTime.dwHighDateTime = fileTimeInt.HighPart;
|
||||||
|
SYSTEMTIME tmpSystemTime;
|
||||||
|
if (!::FileTimeToSystemTime(&fileTime, &tmpSystemTime))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
aSystemTime = tmpSystemTime;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare two SYSTEMTIMEs as FILETIME after clearing everything
|
||||||
|
// below minutes.
|
||||||
|
static bool check_equal_minutes(SYSTEMTIME aSystemTime1, SYSTEMTIME aSystemTime2)
|
||||||
|
{
|
||||||
|
aSystemTime1.wSecond = 0;
|
||||||
|
aSystemTime1.wMilliseconds = 0;
|
||||||
|
|
||||||
|
aSystemTime2.wSecond = 0;
|
||||||
|
aSystemTime2.wMilliseconds = 0;
|
||||||
|
|
||||||
|
FILETIME fileTime1;
|
||||||
|
FILETIME fileTime2;
|
||||||
|
if (!::SystemTimeToFileTime(&aSystemTime1, &fileTime1) || !::SystemTimeToFileTime(&aSystemTime2, &fileTime2))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return (fileTime1.dwLowDateTime == fileTime2.dwLowDateTime) && (fileTime1.dwHighDateTime == fileTime2.dwHighDateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::wstring get_association_key_path(const wchar_t* aExt)
|
||||||
|
{
|
||||||
|
const wchar_t* keyPathFmt;
|
||||||
|
if (aExt[0] == L'.')
|
||||||
|
keyPathFmt = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\%s";
|
||||||
|
else
|
||||||
|
keyPathFmt = L"SOFTWARE\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\%s";
|
||||||
|
|
||||||
|
int keyPathLen = _scwprintf(keyPathFmt, aExt);
|
||||||
|
keyPathLen += 1; // _scwprintf does not include the terminator
|
||||||
|
|
||||||
|
std::wstring keyPath(keyPathLen, '\0');
|
||||||
|
_snwprintf_s(keyPath.data(), keyPathLen, _TRUNCATE, keyPathFmt, aExt);
|
||||||
|
return keyPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set an association with a UserChoice key
|
||||||
|
*
|
||||||
|
* Removes the old key, creates a new one with ProgID and Hash set to
|
||||||
|
* enable a new asociation.
|
||||||
|
*
|
||||||
|
* @param aExt File type or protocol to associate
|
||||||
|
* @param aProgID ProgID to use for the asociation
|
||||||
|
*
|
||||||
|
* @return true if successful, false on error.
|
||||||
|
*/
|
||||||
|
static bool set_user_choice(const wchar_t* aExt, const wchar_t* aProgID) {
|
||||||
|
|
||||||
|
const std::wstring aSid = get_current_user_string_sid();
|
||||||
|
if (aSid.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
SYSTEMTIME hashTimestamp;
|
||||||
|
::GetSystemTime(&hashTimestamp);
|
||||||
|
std::wstring hash = generate_user_choice_hash(aExt, aSid.c_str(), aProgID, hashTimestamp);
|
||||||
|
if (hash.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// The hash changes at the end of each minute, so check that the hash should
|
||||||
|
// be the same by the time we're done writing.
|
||||||
|
const ULONGLONG kWriteTimingThresholdMilliseconds = 100;
|
||||||
|
// Generating the hash could have taken some time, so start from now.
|
||||||
|
SYSTEMTIME writeEndTimestamp;
|
||||||
|
::GetSystemTime(&writeEndTimestamp);
|
||||||
|
if (!add_milliseconds_to_system_time(writeEndTimestamp, +kWriteTimingThresholdMilliseconds))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!check_equal_minutes(hashTimestamp, writeEndTimestamp)) {
|
||||||
|
::Sleep(kWriteTimingThresholdMilliseconds * 2);
|
||||||
|
|
||||||
|
// For consistency, use the current time.
|
||||||
|
::GetSystemTime(&hashTimestamp);
|
||||||
|
hash = generate_user_choice_hash(aExt, aSid.c_str(), aProgID, hashTimestamp);
|
||||||
|
if (hash.empty())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::wstring assocKeyPath = get_association_key_path(aExt);
|
||||||
|
if (assocKeyPath.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
LSTATUS ls;
|
||||||
|
HKEY rawAssocKey;
|
||||||
|
ls = ::RegOpenKeyExW(HKEY_CURRENT_USER, assocKeyPath.data(), 0, KEY_READ | KEY_WRITE, &rawAssocKey);
|
||||||
|
if (ls != ERROR_SUCCESS)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
AutoRegKey assocKey(rawAssocKey);
|
||||||
|
|
||||||
|
HKEY currUserChoiceKey;
|
||||||
|
ls = ::RegOpenKeyExW(assocKey.get(), L"UserChoice", 0, KEY_READ, &currUserChoiceKey);
|
||||||
|
if (ls == ERROR_SUCCESS) {
|
||||||
|
::RegCloseKey(currUserChoiceKey);
|
||||||
|
// When Windows creates this key, it is read-only (Deny Set Value), so we need
|
||||||
|
// to delete it first.
|
||||||
|
// We don't set any similar special permissions.
|
||||||
|
ls = ::RegDeleteKeyW(assocKey.get(), L"UserChoice");
|
||||||
|
if (ls != ERROR_SUCCESS)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
HKEY rawUserChoiceKey;
|
||||||
|
ls = ::RegCreateKeyExW(assocKey.get(), L"UserChoice", 0, nullptr,
|
||||||
|
0 /* options */, KEY_READ | KEY_WRITE,
|
||||||
|
0 /* security attributes */, &rawUserChoiceKey,
|
||||||
|
nullptr);
|
||||||
|
if (ls != ERROR_SUCCESS)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
AutoRegKey userChoiceKey(rawUserChoiceKey);
|
||||||
|
DWORD progIdByteCount = (::lstrlenW(aProgID) + 1) * sizeof(wchar_t);
|
||||||
|
ls = ::RegSetValueExW(userChoiceKey.get(), L"ProgID", 0, REG_SZ, reinterpret_cast<const unsigned char*>(aProgID), progIdByteCount);
|
||||||
|
if (ls != ERROR_SUCCESS)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
DWORD hashByteCount = (::lstrlenW(hash.data()) + 1) * sizeof(wchar_t);
|
||||||
|
ls = ::RegSetValueExW(userChoiceKey.get(), L"Hash", 0, REG_SZ, reinterpret_cast<const unsigned char*>(hash.data()), hashByteCount);
|
||||||
|
if (ls != ERROR_SUCCESS)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool set_as_default_per_file_type(const std::wstring& extension, const std::wstring& prog_id)
|
||||||
|
{
|
||||||
|
const std::wstring reg_extension = get_association_key_path(extension.c_str());
|
||||||
|
if (reg_extension.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool needs_update = true;
|
||||||
|
bool modified = false;
|
||||||
|
HKEY rawAssocKey = nullptr;
|
||||||
|
LSTATUS res = ::RegOpenKeyExW(HKEY_CURRENT_USER, reg_extension.c_str(), 0, KEY_READ, &rawAssocKey);
|
||||||
|
AutoRegKey assoc_key(rawAssocKey);
|
||||||
|
if (res == ERROR_SUCCESS) {
|
||||||
|
DWORD data_size_bytes = 0;
|
||||||
|
res = ::RegGetValueW(assoc_key.get(), L"UserChoice", L"ProgId", RRF_RT_REG_SZ, nullptr, nullptr, &data_size_bytes);
|
||||||
|
if (res == ERROR_SUCCESS) {
|
||||||
|
// +1 in case dataSizeBytes was odd, +1 to ensure termination
|
||||||
|
DWORD data_size_chars = (data_size_bytes / sizeof(wchar_t)) + 2;
|
||||||
|
std::wstring curr_prog_id(data_size_chars, L'\0');
|
||||||
|
res = ::RegGetValueW(assoc_key.get(), L"UserChoice", L"ProgId", RRF_RT_REG_SZ, nullptr, curr_prog_id.data(), &data_size_bytes);
|
||||||
|
if (res == ERROR_SUCCESS) {
|
||||||
|
const std::wstring::size_type pos = curr_prog_id.find_first_of(L'\0');
|
||||||
|
if (pos != std::wstring::npos)
|
||||||
|
curr_prog_id = curr_prog_id.substr(0, pos);
|
||||||
|
needs_update = !boost::algorithm::iequals(curr_prog_id, prog_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needs_update)
|
||||||
|
modified = set_user_choice(extension.c_str(), prog_id.c_str());
|
||||||
|
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
void associate_file_type(const std::wstring& extension, const std::wstring& prog_id, const std::wstring& prog_desc, bool set_as_default)
|
||||||
|
{
|
||||||
|
assert(!extension.empty() && extension.front() == L'.');
|
||||||
|
|
||||||
|
const std::wstring reg_extension = L"SOFTWARE\\Classes\\" + extension;
|
||||||
|
const std::wstring reg_prog_id = L"SOFTWARE\\Classes\\" + prog_id;
|
||||||
|
const std::wstring reg_prog_id_command = L"SOFTWARE\\Classes\\" + prog_id + +L"\\Shell\\Open\\Command";
|
||||||
|
|
||||||
|
wchar_t app_path[1040];
|
||||||
|
::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
|
||||||
|
const std::wstring prog_command = L"\"" + std::wstring(app_path) + L"\"" + L" \"%1\"";
|
||||||
|
|
||||||
|
bool modified = false;
|
||||||
|
modified |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str());
|
||||||
|
modified |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str());
|
||||||
|
modified |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str());
|
||||||
|
if (set_as_default)
|
||||||
|
modified |= set_as_default_per_file_type(extension, prog_id);
|
||||||
|
|
||||||
|
// notify Windows only when any of the values gets changed
|
||||||
|
if (modified)
|
||||||
|
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif // _WIN32
|
23
src/slic3r/Utils/WinRegistry.hpp
Normal file
23
src/slic3r/Utils/WinRegistry.hpp
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#ifndef slic3r_Utils_WinRegistry_hpp_
|
||||||
|
#define slic3r_Utils_WinRegistry_hpp_
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
// Creates a Windows registry key for the files with the given 'extension' and associates them to the application 'prog_id'.
|
||||||
|
// If 'set_as_default' is true, the application 'prog_id' is set ad default application for the file type 'extension'.
|
||||||
|
// The file type registration implementation is based on code taken from:
|
||||||
|
// https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association
|
||||||
|
// The set as default implementation is based on code taken from:
|
||||||
|
// https://hg.mozilla.org/mozilla-central/rev/e928b3e95a6c3b7257d0ba475fc2303bfbad1874
|
||||||
|
// https://hg.mozilla.org/releases/mozilla-release/diff/7e775ce432b599c6daf7ac379aa42f1e9b3b33ed/browser/components/shell/WindowsUserChoice.cpp
|
||||||
|
void associate_file_type(const std::wstring& extension, const std::wstring& prog_id, const std::wstring& prog_desc, bool set_as_default);
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif // _WIN32
|
||||||
|
|
||||||
|
#endif // slic3r_Utils_WinRegistry_hpp_
|
Loading…
Reference in New Issue
Block a user