diff --git a/samples/CPlusPlusWin32/VeloCppWinSample.cpp b/samples/CPlusPlusWin32/VeloCppWinSample.cpp new file mode 100644 index 00000000..c33691e9 --- /dev/null +++ b/samples/CPlusPlusWin32/VeloCppWinSample.cpp @@ -0,0 +1,265 @@ +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include +#include "constants.h" +#include "../../Velopack.hpp" + +#pragma comment(linker, \ + "\"/manifestdependency:type='Win32' "\ + "name='Microsoft.Windows.Common-Controls' "\ + "version='6.0.0.0' "\ + "processorArchitecture='*' "\ + "publicKeyToken='6595b64144ccf1df' "\ + "language='*'\"") +#pragma comment(lib, "ComCtl32.lib") + +HINSTANCE hInst; +const WCHAR szTitle[] = L"Velopack C++ Sample App"; +const WCHAR szWindowClass[] = L"VeloCppWinSample"; +std::shared_ptr updInfo{}; +bool downloaded = false; +Velopack::UpdateManagerSync manager{}; +std::string currentVersion = ""; + +// Forward declarations of functions included in this code module: +int MessageBoxCentered(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType); +ATOM MyRegisterClass(HINSTANCE hInstance); +BOOL InitInstance(HINSTANCE, int); +LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); +INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); +std::wstring utf8_to_wstring(std::string const& str); +std::string wstring_to_utf8(std::wstring const& wstr); + +int APIENTRY wWinMain(_In_ HINSTANCE hInstance, + _In_opt_ HINSTANCE hPrevInstance, + _In_ LPWSTR lpCmdLine, + _In_ int nCmdShow) +{ + UNREFERENCED_PARAMETER(hPrevInstance); + + try + { + // the first thing we need to do in our app is initialise the velopack sdk + int pNumArgs = 0; + wchar_t** args = CommandLineToArgvW(lpCmdLine, &pNumArgs); + Velopack::startup(args, pNumArgs); + manager.setUrlOrPath(UPDATE_URL); + currentVersion = manager.getCurrentVersion(); + } + catch (std::exception& e) + { + std::string what = e.what(); + std::wstring wideWhat(what.begin(), what.end()); + MessageBoxCentered(nullptr, wideWhat.c_str(), szTitle, MB_OK | MB_ICONERROR); + return 1; + } + + MyRegisterClass(hInstance); + if (!InitInstance(hInstance, nCmdShow)) + { + return FALSE; + } + + MSG msg; + + // Main message loop: + while (GetMessage(&msg, nullptr, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return (int)msg.wParam; +} + +ATOM MyRegisterClass(HINSTANCE hInstance) +{ + WNDCLASSEXW wcex; + + wcex.cbSize = sizeof(WNDCLASSEX); + + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = WndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = hInstance; + wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); + wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + wcex.lpszClassName = szWindowClass; + wcex.hIcon = 0; + wcex.hIconSm = 0; + wcex.lpszMenuName = 0; + + return RegisterClassExW(&wcex); +} + +HWND hCheckButton; +HWND hDownloadButton; +HWND hRestartButton; + +BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) +{ + hInst = hInstance; // Store instance handle in our global variable + + HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, 0, 300, 260, nullptr, nullptr, hInstance, nullptr); + + hCheckButton = CreateWindowW(L"BUTTON", L"Check for updates", + WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, + 40, 50, 200, 40, + hWnd, NULL, (HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE), NULL); + + hDownloadButton = CreateWindowW(L"BUTTON", L"Download update", + WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, + 40, 100, 200, 40, + hWnd, NULL, (HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE), NULL); + + hRestartButton = CreateWindowW(L"BUTTON", L"Apply / Restart", + WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, + 40, 150, 200, 40, + hWnd, NULL, (HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE), NULL); + + if (!hWnd) + { + return FALSE; + } + + ShowWindow(hWnd, nCmdShow); + UpdateWindow(hWnd); + + return TRUE; +} + +LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case WM_COMMAND: + { + if (LOWORD(wParam) == BN_CLICKED) + { + if ((HWND)lParam == hCheckButton) + { + try { + updInfo = manager.checkForUpdates(); + if (updInfo != nullptr) { + // this is a hack to convert ascii to wide string + auto version = updInfo->targetFullRelease->version; + std::wstring message = L"Update available: " + utf8_to_wstring(version); + MessageBoxCentered(hWnd, message.c_str(), szTitle, MB_OK); + } + else { + MessageBoxCentered(hWnd, L"No updates available.", szTitle, MB_OK); + } + } + catch (std::exception& e) { + std::wstring wideWhat = utf8_to_wstring(e.what()); + MessageBoxCentered(hWnd, wideWhat.c_str(), szTitle, MB_OK | MB_ICONERROR); + } + } + else if ((HWND)lParam == hDownloadButton) + { + if (updInfo != nullptr) { + try { + manager.downloadUpdates(updInfo->targetFullRelease.get()); + downloaded = true; + MessageBoxCentered(hWnd, L"Download completed successfully.", szTitle, MB_OK); + } + catch (std::exception& e) { + std::wstring wideWhat = utf8_to_wstring(e.what()); + MessageBoxCentered(hWnd, wideWhat.c_str(), szTitle, MB_OK | MB_ICONERROR); + } + } + else { + MessageBoxCentered(hWnd, L"Check for updates first.", szTitle, MB_OK); + } + } + else if ((HWND)lParam == hRestartButton) + { + if (!downloaded) { + MessageBoxCentered(hWnd, L"Download an update first.", szTitle, MB_OK); + } + else { + manager.applyUpdatesAndRestart(updInfo->targetFullRelease.get()); + } + } + } + break; + } + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hWnd, &ps); + RECT r{ 0, 5, ps.rcPaint.right, ps.rcPaint.bottom }; + auto ver = utf8_to_wstring(currentVersion); + std::wstring text = L"Welcome to v" + ver + L" of the\nVelopack C++ Sample App."; + DrawText(hdc, text.c_str(), -1, &r, DT_BOTTOM | DT_CENTER); + EndPaint(hWnd, &ps); + break; + } + case WM_DESTROY: + PostQuitMessage(0); + break; + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } + return 0; +} + +int MessageBoxCentered(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) +{ + if (hWnd == nullptr) + { + return MessageBox(hWnd, lpText, lpCaption, uType); + } + else + { + // Center message box at its parent window + static HHOOK hHookCBT{}; + hHookCBT = SetWindowsHookEx(WH_CBT, + [](int nCode, WPARAM wParam, LPARAM lParam) -> LRESULT + { + if (nCode == HCBT_CREATEWND) + { + if (((LPCBT_CREATEWND)lParam)->lpcs->lpszClass == (LPWSTR)(ATOM)32770) // #32770 = dialog box class + { + RECT rcParent{}; + GetWindowRect(((LPCBT_CREATEWND)lParam)->lpcs->hwndParent, &rcParent); + ((LPCBT_CREATEWND)lParam)->lpcs->x = rcParent.left + ((rcParent.right - rcParent.left) - ((LPCBT_CREATEWND)lParam)->lpcs->cx) / 2; + ((LPCBT_CREATEWND)lParam)->lpcs->y = rcParent.top + ((rcParent.bottom - rcParent.top) - ((LPCBT_CREATEWND)lParam)->lpcs->cy) / 2; + } + } + + return CallNextHookEx(hHookCBT, nCode, wParam, lParam); + }, + 0, GetCurrentThreadId()); + + int iRet{ MessageBox(hWnd, lpText, lpCaption, uType) }; + + UnhookWindowsHookEx(hHookCBT); + + return iRet; + } +} + +std::string wstring_to_utf8(std::wstring const& wstr) +{ + if (wstr.empty()) return std::string(); + int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL); + std::string strTo(size_needed, 0); + WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL); + return strTo; +} + +std::wstring utf8_to_wstring(std::string const& str) +{ + if (str.empty()) return std::wstring(); + int size_needed = MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), NULL, 0); + std::wstring strTo(size_needed, 0); + MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), &strTo[0], size_needed); + return strTo; +} \ No newline at end of file diff --git a/samples/CPlusPlusWin32/VeloCppWinSample.sln b/samples/CPlusPlusWin32/VeloCppWinSample.sln new file mode 100644 index 00000000..0f9a0467 --- /dev/null +++ b/samples/CPlusPlusWin32/VeloCppWinSample.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34525.116 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VeloCppWinSample", "VeloCppWinSample.vcxproj", "{F9BB1F11-3827-4745-B11B-77FA5DBE1195}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F9BB1F11-3827-4745-B11B-77FA5DBE1195}.Debug|x64.ActiveCfg = Debug|x64 + {F9BB1F11-3827-4745-B11B-77FA5DBE1195}.Debug|x64.Build.0 = Debug|x64 + {F9BB1F11-3827-4745-B11B-77FA5DBE1195}.Debug|x86.ActiveCfg = Debug|Win32 + {F9BB1F11-3827-4745-B11B-77FA5DBE1195}.Debug|x86.Build.0 = Debug|Win32 + {F9BB1F11-3827-4745-B11B-77FA5DBE1195}.Release|x64.ActiveCfg = Release|x64 + {F9BB1F11-3827-4745-B11B-77FA5DBE1195}.Release|x64.Build.0 = Release|x64 + {F9BB1F11-3827-4745-B11B-77FA5DBE1195}.Release|x86.ActiveCfg = Release|Win32 + {F9BB1F11-3827-4745-B11B-77FA5DBE1195}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BE15ADBD-1862-47E7-8704-0721D758269F} + EndGlobalSection +EndGlobal diff --git a/samples/CPlusPlusWin32/VeloCppWinSample.vcxproj b/samples/CPlusPlusWin32/VeloCppWinSample.vcxproj new file mode 100644 index 00000000..b75e1044 --- /dev/null +++ b/samples/CPlusPlusWin32/VeloCppWinSample.vcxproj @@ -0,0 +1,146 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {f9bb1f11-3827-4745-b11b-77fa5dbe1195} + VeloCppWinSample + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + stdcpp20 + stdc17 + + + Windows + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + stdcpp20 + stdc17 + + + Windows + true + true + true + + + + + Level3 + true + _DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + stdcpp20 + stdc17 + + + Windows + true + + + + + Level3 + true + true + true + NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + stdcpp20 + stdc17 + + + Windows + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/CPlusPlusWin32/VeloCppWinSample.vcxproj.filters b/samples/CPlusPlusWin32/VeloCppWinSample.vcxproj.filters new file mode 100644 index 00000000..8ce257ca --- /dev/null +++ b/samples/CPlusPlusWin32/VeloCppWinSample.vcxproj.filters @@ -0,0 +1,29 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/samples/CPlusPlusWin32/constants.h b/samples/CPlusPlusWin32/constants.h new file mode 100644 index 00000000..ea33c78c --- /dev/null +++ b/samples/CPlusPlusWin32/constants.h @@ -0,0 +1 @@ +#define UPDATE_URL "REPLACE_ME" diff --git a/samples/CPlusPlusWin32/dev-build.bat b/samples/CPlusPlusWin32/dev-build.bat new file mode 100644 index 00000000..c0809b4a --- /dev/null +++ b/samples/CPlusPlusWin32/dev-build.bat @@ -0,0 +1,50 @@ +@echo off +REM This script requires several tools to be installed for it to work: +REM cargo (rust): winget install Rustlang.Rustup +REM Nerdbank.GitVersioning (nbgv): dotnet tool install --global nbgv +REM C++ Build Tools, typically installed via "Desktop development with C++" workload. + +setlocal enabledelayedexpansion + +if "%~1"=="" ( + echo Version number is required. + echo Usage: build.bat [version] [extra_args...] + exit /b 1 +) + +echo. +echo Building Velopack Rust +cd %~dp0..\..\..\for-rust +cargo build --features cli -r + +cd %~dp0 + +set VSWHERE_PATH="%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" + +echo. +echo Locating MSBuild using vswhere +for /f "usebackq tokens=*" %%i in (`%VSWHERE_PATH% -latest -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe`) do ( + set "MSBUILD_PATH=%%i" + goto :buildCpp +) + +:buildCpp +if not defined MSBUILD_PATH ( + echo MSBuild not found, make sure Visual Studio is installed with C++ Build Tools. + exit /b 1 +) + +echo #define UPDATE_URL R"(%~dp0releases)" > constants.h + +echo. +echo Building VeloCppWinSample +cd %~dp0 +"%MSBUILD_PATH%" VeloCppWinSample.sln + +echo #define UPDATE_URL "REPLACE_ME" > constants.h + +copy %~dp0..\..\..\for-rust\target\release\vfusion.exe x64\Debug + +echo. +echo Building Velopack Release v%~1 +vpk pack -u VeloCppWinSample -o releases -p x64\Debug -v %* \ No newline at end of file diff --git a/samples/CPlusPlusWin32/readme.md b/samples/CPlusPlusWin32/readme.md new file mode 100644 index 00000000..3b0a5694 --- /dev/null +++ b/samples/CPlusPlusWin32/readme.md @@ -0,0 +1,8 @@ +# VeloCppWinSample +_Prerequisites: Rust/Cargo, Dotnet, Msbuild_ + +This app is purely a proof of concept at this time, `velopack.hpp` currently only works on windows and needs testing / fixes for other operating systems, and probably also needs fixing for unicode/strings. This sample is made up of a simple Win32 desktop app, generated via a Visual Studio template, and it includes `velopack.hpp` and [`subprocess.h`](https://github.com/sheredom/subprocess.h). + +Also, this is using command line features not currently exposed in the main Velopack releases, so you can only build releases from source by using the `dev-build.bat [version]` script. Trying to build/use this sample with the mainstream `vpk` tool is not guarenteed to work at this time. + +If you are interested in using Velopack for your C++ project and are willing to help with the design of `velopack.hpp` (either with suggestions, or code), please drop in our Discord and I would be happy to discuss / work with you to polish this up. \ No newline at end of file