diff --git a/bypy/windows/__main__.py b/bypy/windows/__main__.py index 93814ed986..635feb0199 100644 --- a/bypy/windows/__main__.py +++ b/bypy/windows/__main__.py @@ -336,14 +336,14 @@ def build_portable(env): shutil.rmtree(base) os.makedirs(base) root = d(a(__file__)) - src = j(root, 'portable.c') + src = j(root, 'portable.cpp') obj = j(env.obj_dir, b(src) + '.obj') cflags = '/c /EHsc /MT /W3 /Ox /nologo /D_UNICODE /DUNICODE'.split() for exe_name in ('calibre.exe', 'ebook-viewer.exe', 'ebook-edit.exe'): exe = j(base, exe_name.replace('.exe', '-portable.exe')) printf('Compiling', exe) - cmd = [CL] + cflags + ['/DEXE_NAME="%s"' % exe_name, '/Fo' + obj, '/Tc' + src] + cmd = [CL] + cflags + ['/Fo' + obj, '/Tp' + src] run_compiler(env, *cmd) printf('Linking', exe) desc = { @@ -357,7 +357,7 @@ def build_portable(env): '/RELEASE', '/ENTRY:wWinMainCRTStartup', '/OUT:' + exe, embed_resources(env, exe, desc=desc, product_description=desc), - obj, 'User32.lib'] + obj, 'User32.lib', 'Shell32.lib'] run(*cmd) printf('Creating portable installer') diff --git a/bypy/windows/portable.c b/bypy/windows/portable.c deleted file mode 100644 index ff3121d079..0000000000 --- a/bypy/windows/portable.c +++ /dev/null @@ -1,158 +0,0 @@ -#ifndef UNICODE -#define UNICODE -#endif - -#ifndef _UNICODE -#define _UNICODE -#endif - - -#include -#include -#include -#include - -#define BUFSIZE 4096 - -void show_error(LPCTSTR msg) { - MessageBeep(MB_ICONERROR); - MessageBox(NULL, msg, _T("Error"), MB_OK|MB_ICONERROR); -} - -void show_detailed_error(LPCTSTR preamble, LPCTSTR msg, int code) { - LPTSTR buf; - buf = (LPTSTR)LocalAlloc(LMEM_ZEROINIT, sizeof(TCHAR)* - (_tcslen(msg) + _tcslen(preamble) + 80)); - - _sntprintf_s(buf, - LocalSize(buf) / sizeof(TCHAR), _TRUNCATE, - _T("%s\r\n %s (Error Code: %d)\r\n"), - preamble, msg, code); - - show_error(buf); - LocalFree(buf); -} - -void show_last_error_crt(LPCTSTR preamble) { - TCHAR buf[BUFSIZE]; - int err = 0; - - _get_errno(&err); - _tcserror_s(buf, BUFSIZE, err); - show_detailed_error(preamble, buf, err); -} - -void show_last_error(LPCTSTR preamble) { - TCHAR *msg = NULL; - DWORD dw = GetLastError(); - - FormatMessage( - FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - dw, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR)&msg, - 0, NULL ); - - show_detailed_error(preamble, msg, (int)dw); -} - - -LPTSTR get_app_dir() { - LPTSTR buf, buf2, buf3; - DWORD sz; - TCHAR drive[4] = _T("\0\0\0"); - errno_t err; - - buf = (LPTSTR)calloc(BUFSIZE, sizeof(TCHAR)); - buf2 = (LPTSTR)calloc(BUFSIZE, sizeof(TCHAR)); - buf3 = (LPTSTR)calloc(BUFSIZE, sizeof(TCHAR)); - - sz = GetModuleFileName(NULL, buf, BUFSIZE); - - if (sz == 0 || sz > BUFSIZE-1) { - show_error(_T("Failed to get path to calibre-portable.exe")); - ExitProcess(1); - } - - err = _tsplitpath_s(buf, drive, 4, buf2, BUFSIZE, NULL, 0, NULL, 0); - - if (err != 0) { - show_last_error_crt(_T("Failed to split path to calibre-portable.exe")); - ExitProcess(1); - } - - _sntprintf_s(buf3, BUFSIZE-1, _TRUNCATE, _T("%s%s"), drive, buf2); - if (_tcslen(buf3) > 58) { - _snwprintf_s(buf, 4*MAX_PATH, _TRUNCATE, - L"Path to Calibre Portable (%s) too long. Must be less than 59 characters.", buf3); - show_error(buf); - ExitProcess(1); - } - free(buf); free(buf2); - return buf3; -} - -void launch_calibre(LPCTSTR exe, LPCTSTR config_dir) { - DWORD dwFlags=0; - STARTUPINFO si; - PROCESS_INFORMATION pi; - BOOL fSuccess; - - if (! SetEnvironmentVariable(_T("CALIBRE_CONFIG_DIRECTORY"), config_dir)) { - show_last_error(_T("Failed to set environment variables")); - ExitProcess(1); - } - - if (! SetEnvironmentVariable(_T("CALIBRE_PORTABLE_BUILD"), exe)) { - show_last_error(_T("Failed to set environment variables")); - ExitProcess(1); - } - - dwFlags = CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_PROCESS_GROUP; - - ZeroMemory( &si, sizeof(si) ); - si.cb = sizeof(si); - ZeroMemory( &pi, sizeof(pi) ); - - fSuccess = CreateProcess(exe, NULL, - NULL, // Process handle not inheritable - NULL, // Thread handle not inheritable - FALSE, // Set handle inheritance to FALSE - dwFlags, // Creation flags http://msdn.microsoft.com/en-us/library/ms684863(v=vs.85).aspx - NULL, // Use parent's environment block - NULL, // Use parent's starting directory - &si, // Pointer to STARTUPINFO structure - &pi // Pointer to PROCESS_INFORMATION structure - ); - - if (fSuccess == 0) { - show_last_error(_T("Failed to launch the calibre program")); - } - - // Close process and thread handles. - CloseHandle( pi.hProcess ); - CloseHandle( pi.hThread ); - -} - - -int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) -{ - LPTSTR app_dir, config_dir, exe; - - app_dir = get_app_dir(); - config_dir = (LPTSTR)calloc(BUFSIZE, sizeof(TCHAR)); - exe = (LPTSTR)calloc(BUFSIZE, sizeof(TCHAR)); - - _sntprintf_s(config_dir, BUFSIZE, _TRUNCATE, _T("%sCalibre Settings"), app_dir); - _sntprintf_s(exe, BUFSIZE, _TRUNCATE, _T("%sCalibre\\%s"), app_dir, _T(EXE_NAME)); - - launch_calibre(exe, config_dir); - - free(app_dir); free(config_dir); free(exe); - - return 0; -} diff --git a/bypy/windows/portable.cpp b/bypy/windows/portable.cpp new file mode 100644 index 0000000000..47b910844e --- /dev/null +++ b/bypy/windows/portable.cpp @@ -0,0 +1,238 @@ +/* + * portable.cpp + * Copyright (C) 2020 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#ifndef UNICODE +#define UNICODE +#endif + +#ifndef _UNICODE +#define _UNICODE +#endif + +#include +#include +#include +#include +#include +#include + +#define BUFSIZE 4096 + + +// error handling {{{ +void show_error(LPCWSTR msg) { + MessageBeep(MB_ICONERROR); + MessageBoxW(NULL, msg, L"Error", MB_OK|MB_ICONERROR); +} + +void show_detailed_error(LPCTSTR preamble, LPCTSTR msg, int code) { + LPTSTR buf; + buf = (LPTSTR)LocalAlloc(LMEM_ZEROINIT, sizeof(TCHAR)* + (_tcslen(msg) + _tcslen(preamble) + 80)); + + _sntprintf_s(buf, + LocalSize(buf) / sizeof(TCHAR), _TRUNCATE, + _T("%s\r\n %s (Error Code: %d)\r\n"), + preamble, msg, code); + + show_error(buf); + LocalFree(buf); +} + +void show_last_error_crt(LPCTSTR preamble) { + TCHAR buf[BUFSIZE]; + int err = 0; + + _get_errno(&err); + _tcserror_s(buf, BUFSIZE, err); + show_detailed_error(preamble, buf, err); +} + +void show_last_error(LPCTSTR preamble) { + TCHAR *msg = NULL; + DWORD dw = GetLastError(); + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + dw, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&msg, + 0, NULL ); + + show_detailed_error(preamble, msg, (int)dw); +} + + +// }}} + + + +static bool +get_app_dir(std::wstring& ans, std::wstring& exe_name) { + DWORD sz; + static wchar_t drive[_MAX_DRIVE] = {0}; + static wchar_t buf[BUFSIZE] = {0}, dirpath[_MAX_DIR] = {0}, fname[_MAX_FNAME] = {0}, ext[_MAX_EXT] = {0}; + + sz = GetModuleFileName(NULL, buf, BUFSIZE); + + if (sz == 0 || sz > BUFSIZE-1) { + show_error(L"Failed to get path to portable launcher"); + return false; + } + + errno_t err = _wsplitpath_s(buf, drive, _MAX_DRIVE, dirpath, _MAX_DIR, fname, _MAX_FNAME, ext, _MAX_EXT); + + if (err != 0) { + show_last_error_crt(L"Failed to split path to portable launcher"); + return false; + } + ans.append(drive); ans.append(dirpath); + if (ans.length() > 58) { + std::wstring msg; + msg.append(L"Path to Calibre Portable ("); + msg.append(ans); + msg.append(L") too long. Must be less than 59 characters."); + show_error(msg.c_str()); + return false; + } + exe_name.append(fname); + exe_name.erase(exe_name.length() - sizeof("portable"), sizeof("portable")); + exe_name.append(ext); + return true; +} + + +static void +quote_argv(const std::wstring& arg, std::wstring& cmd_line) { + if (!arg.empty() && arg.find_first_of(L" \t\n\v\"") == arg.npos) { + cmd_line.append(arg); + return; + } + cmd_line.push_back(L'"'); + + for (auto iterator = arg.begin() ; ; ++iterator) { + unsigned num_back_slashes = 0; + + while (iterator != arg.end() && *iterator == L'\\') { + ++iterator; + ++num_back_slashes; + } + + if (iterator == arg.end()) { + + // + // Escape all backslashes, but let the terminating + // double quotation mark we add below be interpreted + // as a metacharacter. + // + + cmd_line.append (num_back_slashes * 2, L'\\'); + break; + } + else if (*iterator == L'"') { + + // + // Escape all backslashes and the following + // double quotation mark. + // + + cmd_line.append (num_back_slashes * 2 + 1, L'\\'); + cmd_line.push_back (*iterator); + } + else { + + // + // Backslashes aren't special here. + // + + cmd_line.append (num_back_slashes, L'\\'); + cmd_line.push_back (*iterator); + } + } + + cmd_line.push_back (L'"'); +} + +void +launch_exe(LPCWSTR exe_path, const std::wstring &cmd_line, LPCWSTR config_dir) { + DWORD dwFlags=0; + STARTUPINFO si; + PROCESS_INFORMATION pi; + BOOL fSuccess; + if (cmd_line.length() > BUFSIZE - 4) { + show_error(L"Path to executable in portable folder too long."); + ExitProcess(1); + } + + if (!SetEnvironmentVariable(_T("CALIBRE_CONFIG_DIRECTORY"), config_dir)) { + show_last_error(_T("Failed to set environment variables")); + ExitProcess(1); + } + + if (!SetEnvironmentVariable(_T("CALIBRE_PORTABLE_BUILD"), exe_path)) { + show_last_error(_T("Failed to set environment variables")); + ExitProcess(1); + } + + dwFlags = CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_PROCESS_GROUP; + + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof(si); + ZeroMemory( &pi, sizeof(pi) ); + static wchar_t mutable_cmdline[BUFSIZE] = {0}; + cmd_line.copy(mutable_cmdline, BUFSIZE-1); + + fSuccess = CreateProcess(NULL, mutable_cmdline, + NULL, // Process handle not inheritable + NULL, // Thread handle not inheritable + FALSE, // Set handle inheritance to FALSE + dwFlags, // Creation flags http://msdn.microsoft.com/en-us/library/ms684863(v=vs.85).aspx + NULL, // Use parent's environment block + NULL, // Use parent's starting directory + &si, // Pointer to STARTUPINFO structure + &pi // Pointer to PROCESS_INFORMATION structure + ); + + if (fSuccess == 0) { + show_last_error(_T("Failed to launch the calibre program")); + } + + // Close process and thread handles. + CloseHandle( pi.hProcess ); + CloseHandle( pi.hThread ); +} + + +int WINAPI +wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR orig_cmd_line, int nCmdShow) { + std::wstring exe, config_dir, cmd_line, application_dir, exe_name; + + if (!get_app_dir(application_dir, exe_name)) return 1; + config_dir.append(application_dir); config_dir.append(L"Calibre Settings"); + exe.append(application_dir); exe.append(L"Calibre\\"); exe.append(exe_name); + + // Note that orig_cmd_line does not have argv[0] as the executable name + int argc; + wchar_t **argv = CommandLineToArgvW(orig_cmd_line, &argc); + if (argv == NULL) { + show_last_error(L"Failed to convert cmdline to argv array"); + return 1; + } + quote_argv(exe, cmd_line); + for (int i = 0; i < argc; i++) { + std::wstring arg(argv[i]); + cmd_line.push_back(L' '); + quote_argv(arg, cmd_line); + } + LocalFree(argv); + launch_exe(exe.c_str(), cmd_line.c_str(), config_dir.c_str()); + + return 0; +}