diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c1fdabd12..b50ca7b22 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -322,6 +322,8 @@ windows: - curl -sL --url "https://github.com/ethcore/win-build/raw/master/SimpleFC.dll" -o nsis\SimpleFC.dll - curl -sL --url "https://github.com/ethcore/win-build/raw/master/vc_redist.x64.exe" -o nsis\vc_redist.x64.exe - signtool sign /f %keyfile% /p %certpass% target\release\parity.exe + - msbuild windows\ptray\ptray.vcxproj /p:Platform=x86 /p:Configuration=Release + - signtool sign /f %keyfile% /p %certpass% windows\ptray\release\ptray.exe - cd nsis - makensis.exe installer.nsi - copy installer.exe InstallParity.exe diff --git a/appveyor.yml b/appveyor.yml index 75a2da7cb..e04caf233 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -38,6 +38,8 @@ after_test: - cargo build --verbose --release - ps: if($env:cert) { Start-FileDownload $env:cert -FileName $env:keyfile } - ps: if($env:cert) { signtool sign /f $env:keyfile /p $env:certpass target\release\parity.exe } + - msbuild windows\ptray\ptray.vcxproj /p:Platform=x86 /p:Configuration=Release + - ps: if($env:cert) { signtool sign /f $env:keyfile /p $env:certpass windows\ptray\release\ptray.exe } - makensis.exe nsis\installer.nsi - ps: if($env:cert) { signtool sign /f $env:keyfile /p $env:certpass nsis\installer.exe } diff --git a/nsis/installer.nsi b/nsis/installer.nsi index 2c7c37428..448618bbf 100644 --- a/nsis/installer.nsi +++ b/nsis/installer.nsi @@ -1,10 +1,17 @@ +!include WinMessages.nsh +!define WND_CLASS "Parity" +!define WND_TITLE "Parity" +!define WAIT_MS 5000 +!define SYNC_TERM 0x00100001 + !define APPNAME "Parity" !define COMPANYNAME "Ethcore" !define DESCRIPTION "Fast, light, robust Ethereum implementation" !define VERSIONMAJOR 1 !define VERSIONMINOR 4 !define VERSIONBUILD 0 +!define ARGS "--warp --mode=passive" !addplugindir .\ @@ -13,6 +20,10 @@ !define ABOUTURL "https://github.com/ethcore/parity" # "Publisher" link !define INSTALLSIZE 26120 +!define termMsg "Installer cannot stop running ${WND_TITLE}.$\nDo you want to terminate process?" +!define stopMsg "Stopping ${WND_TITLE} Application" + + RequestExecutionLevel admin ;Require admin rights on NT6+ (When UAC is turned on) InstallDir "$PROGRAMFILES64\${COMPANYNAME}\${APPNAME}" @@ -26,7 +37,7 @@ outFile "installer.exe" page license page directory -Page instfiles +page instfiles !macro VerifyUserIsAdmin UserInfo::GetAccountType @@ -38,6 +49,31 @@ ${If} $0 != "admin" ;Require admin rights on NT4+ ${EndIf} !macroend +!macro TerminateApp + Push $0 ; window handle + Push $1 + Push $2 ; process handle + DetailPrint "$(stopMsg)" + FindWindow $0 '${WND_CLASS}' '' + IntCmp $0 0 done + System::Call 'user32.dll::GetWindowThreadProcessId(i r0, *i .r1) i .r2' + System::Call 'kernel32.dll::OpenProcess(i ${SYNC_TERM}, i 0, i r1) i .r2' + SendMessage $0 ${WM_CLOSE} 0 0 /TIMEOUT=${TO_MS} + System::Call 'kernel32.dll::WaitForSingleObject(i r2, i ${WAIT_MS}) i .r1' + IntCmp $1 0 close + MessageBox MB_YESNOCANCEL|MB_ICONEXCLAMATION "$(termMsg)" /SD IDYES IDYES terminate IDNO close + System::Call 'kernel32.dll::CloseHandle(i r2) i .r1' + Quit + terminate: + System::Call 'kernel32.dll::TerminateProcess(i r2, i 0) i .r1' + close: + System::Call 'kernel32.dll::CloseHandle(i r2) i .r1' + done: + Pop $2 + Pop $1 + Pop $0 +!macroend + function .onInit setShellVarContext all !insertmacro VerifyUserIsAdmin @@ -48,10 +84,13 @@ section "install" setOutPath $INSTDIR # Files added here should be removed by the uninstaller (see section "uninstall") file /oname=parity.exe ..\target\release\parity.exe + file /oname=ptray.exe ..\windows\ptray\Release\ptray.exe + file "logo.ico" file vc_redist.x64.exe ExecWait '"$INSTDIR\vc_redist.x64.exe" /passive /norestart' + delete $INSTDIR\vc_redist.x64.exe # Add any other files for the install directory (license files, app data, etc) here # Uninstaller - See function un.onInit and section "uninstall" for configuration @@ -79,11 +118,11 @@ section "install" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "InstallLocation" "$\"$INSTDIR$\"" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "DisplayIcon" "$\"$INSTDIR\logo.ico$\"" - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "Publisher" "$\"${COMPANYNAME}$\"" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "Publisher" "${COMPANYNAME}" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "HelpLink" "$\"${HELPURL}$\"" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "URLUpdateInfo" "$\"${UPDATEURL}$\"" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "URLInfoAbout" "$\"${ABOUTURL}$\"" - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "DisplayVersion" "$\"${VERSIONMAJOR}.${VERSIONMINOR}.${VERSIONBUILD}$\"" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "DisplayVersion" "${VERSIONMAJOR}.${VERSIONMINOR}.${VERSIONBUILD}" WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "VersionMajor" ${VERSIONMAJOR} WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "VersionMinor" ${VERSIONMINOR} # There is no option for modifying or repairing the install @@ -91,6 +130,9 @@ section "install" WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "NoRepair" 1 # Set the INSTALLSIZE constant (!defined at the top of this script) so Add/Remove Programs can accurately report the size WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "EstimatedSize" ${INSTALLSIZE} + + WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Run" ${APPNAME} "$INSTDIR\ptray.exe ${ARGS}" + ExecShell "" "$INSTDIR\ptray.exe" "${ARGS}" sectionEnd # Uninstaller @@ -107,7 +149,7 @@ function un.onInit functionEnd section "uninstall" - + !insertmacro TerminateApp # Remove Start Menu launcher delete "$SMPROGRAMS\${COMPANYNAME}\${APPNAME}.lnk" # Try to remove the Start Menu folder - this will only happen if it is empty @@ -115,6 +157,7 @@ section "uninstall" # Remove files delete $INSTDIR\parity.exe + delete $INSTDIR\ptray.exe delete $INSTDIR\logo.ico # Always delete uninstaller as the last action @@ -131,5 +174,6 @@ section "uninstall" # Remove uninstaller information from the registry DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" - + DeleteRegValue HKLM "Software\Microsoft\Windows\CurrentVersion\Run" "${APPNAME}" sectionEnd + diff --git a/nsis/logo.ico b/nsis/logo.ico index 4fbaa4d39..61e68b90b 100644 Binary files a/nsis/logo.ico and b/nsis/logo.ico differ diff --git a/parity/run.rs b/parity/run.rs index b0f8dce68..de73ecb45 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -15,6 +15,7 @@ // along with Parity. If not, see . use std::sync::{Arc, Mutex, Condvar}; +use std::net::{TcpListener}; use ctrlc::CtrlC; use fdlimit::raise_fd_limit; use ethcore_rpc::{NetworkSettings, is_major_importing}; @@ -94,6 +95,15 @@ pub struct RunCmd { } pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { + if cmd.ui && cmd.dapps_conf.enabled { + // Check if Parity is already running + let addr = format!("{}:{}", cmd.dapps_conf.interface, cmd.dapps_conf.port); + if !TcpListener::bind(&addr as &str).is_ok() { + url::open(&format!("http://{}:{}/", cmd.dapps_conf.interface, cmd.dapps_conf.port)); + return Ok(()); + } + } + // set up panic handler let panic_handler = PanicHandler::new_in_arc(); diff --git a/windows/ptray/ptray.cpp b/windows/ptray/ptray.cpp new file mode 100644 index 000000000..ac2f197a7 --- /dev/null +++ b/windows/ptray/ptray.cpp @@ -0,0 +1,274 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "resource.h" + +#pragma comment(lib, "shlwapi.lib") + +#define MAX_LOADSTRING 100 +#define IDM_EXIT 100 +#define IDM_OPEN 101 +#define WM_USER_SHELLICON WM_USER + 1 + +HANDLE parityHandle = INVALID_HANDLE_VALUE; +DWORD parityProcId = 0; +NOTIFYICONDATA nidApp; +WCHAR szTitle[MAX_LOADSTRING]; +WCHAR szWindowClass[MAX_LOADSTRING]; + +LPCWSTR cParityExe = _T("parity.exe"); + +ATOM MyRegisterClass(HINSTANCE hInstance); +bool InitInstance(HINSTANCE, int, LPWSTR cmdLine); +LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); +void KillParity(); +void OpenUI(); +bool ParityIsRunning(); + +bool GetParityExePath(TCHAR* dest, size_t destSize) +{ + if (!dest || MAX_PATH > destSize) + return false; + GetModuleFileName(NULL, dest, destSize); + if (!PathRemoveFileSpec(dest)) + return false; + return PathAppend(dest, _T("parity.exe")) == TRUE; +} + +int APIENTRY wWinMain(_In_ HINSTANCE hInstance, + _In_opt_ HINSTANCE hPrevInstance, + _In_ LPWSTR lpCmdLine, + _In_ int nCmdShow) +{ + UNREFERENCED_PARAMETER(hPrevInstance); + UNREFERENCED_PARAMETER(lpCmdLine); + + CreateMutex(0, FALSE, _T("Local\\ParityTray")); + if (GetLastError() == ERROR_ALREADY_EXISTS) + return -1; + + LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); + LoadStringW(hInstance, IDC_PTRAY, szWindowClass, MAX_LOADSTRING); + MyRegisterClass(hInstance); + + if (!InitInstance(hInstance, nCmdShow, lpCmdLine)) + return FALSE; + + HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_PTRAY)); + MSG msg; + // Main message loop: + while (GetMessage(&msg, nullptr, 0, 0)) + { + if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) + { + 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.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_PTRAY)); + wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); + wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_PTRAY); + wcex.lpszClassName = szWindowClass; + wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); + + return RegisterClassExW(&wcex); +} + + +bool InitInstance(HINSTANCE hInstance, int nCmdShow, LPWSTR cmdLine) +{ + // Check if already running + PROCESSENTRY32 entry; + entry.dwSize = sizeof(PROCESSENTRY32); + + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); + if (Process32First(snapshot, &entry) == TRUE) + { + while (Process32Next(snapshot, &entry) == TRUE) + { + if (lstrcmp(entry.szExeFile, cParityExe) == 0) + { + parityHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID); + parityProcId = entry.th32ProcessID; + break; + } + } + } + + CloseHandle(snapshot); + + if (parityHandle == INVALID_HANDLE_VALUE) + { + // Launch parity + TCHAR path[MAX_PATH] = { 0 }; + if (!GetParityExePath(path, MAX_PATH)) + return false; + + PROCESS_INFORMATION procInfo = { 0 }; + STARTUPINFO startupInfo = { sizeof(STARTUPINFO) }; + + LPWSTR cmd = new WCHAR[lstrlen(cmdLine) + lstrlen(path) + 2]; + lstrcpy(cmd, path); + lstrcat(cmd, _T(" ")); + lstrcat(cmd, cmdLine); + if (!CreateProcess(nullptr, cmd, nullptr, nullptr, false, CREATE_NO_WINDOW, nullptr, nullptr, &startupInfo, &procInfo)) + return false; + delete[] cmd; + parityHandle = procInfo.hProcess; + parityProcId = procInfo.dwProcessId; + } + + HWND hWnd = CreateWindowW(szWindowClass, szTitle, 0, + CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr); + + if (!hWnd) + return false; + + HICON hMainIcon = LoadIcon(hInstance, (LPCTSTR)MAKEINTRESOURCE(IDI_PTRAY)); + + nidApp.cbSize = sizeof(NOTIFYICONDATA); // sizeof the struct in bytes + nidApp.hWnd = (HWND)hWnd; //handle of the window which will process this app. messages + nidApp.uID = IDI_PTRAY; //ID of the icon that willl appear in the system tray + nidApp.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; //ORing of all the flags + nidApp.hIcon = hMainIcon; // handle of the Icon to be displayed, obtained from LoadIcon + nidApp.uCallbackMessage = WM_USER_SHELLICON; + LoadString(hInstance, IDS_CONTROL_PARITY, nidApp.szTip, MAX_LOADSTRING); + Shell_NotifyIcon(NIM_ADD, &nidApp); + return TRUE; + +} + +LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case WM_USER_SHELLICON: + // systray msg callback + POINT lpClickPoint; + switch (LOWORD(lParam)) + { + case WM_LBUTTONDOWN: + OpenUI(); + break; + case WM_RBUTTONDOWN: + UINT uFlag = MF_BYPOSITION | MF_STRING; + GetCursorPos(&lpClickPoint); + HMENU hPopMenu = CreatePopupMenu(); + InsertMenu(hPopMenu, 0xFFFFFFFF, MF_BYPOSITION | MF_STRING, IDM_EXIT, _T("Open")); + InsertMenu(hPopMenu, 0xFFFFFFFF, MF_SEPARATOR | MF_BYPOSITION, 0, nullptr); + InsertMenu(hPopMenu, 0xFFFFFFFF, MF_BYPOSITION | MF_STRING, IDM_EXIT, _T("Exit")); + + SetForegroundWindow(hWnd); + TrackPopupMenu(hPopMenu, TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_BOTTOMALIGN, lpClickPoint.x, lpClickPoint.y, 0, hWnd, NULL); + return TRUE; + + } + break; + case WM_COMMAND: + { + int wmId = LOWORD(wParam); + // Parse the menu selections: + switch (wmId) + { + case IDM_EXIT: + DestroyWindow(hWnd); + break; + case IDM_OPEN: + OpenUI(); + break; + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } + } + break; + case WM_DESTROY: + Shell_NotifyIcon(NIM_DELETE, &nidApp); + KillParity(); + PostQuitMessage(0); + break; + case WM_TIMER: + if (!ParityIsRunning()) + DestroyWindow(hWnd); + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } + return 0; +} + +void KillParity() +{ + DWORD procId = parityProcId; + //This does not require the console window to be visible. + if (AttachConsole(procId)) + { + // Disable Ctrl-C handling for our program + SetConsoleCtrlHandler(nullptr, true); + GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); + FreeConsole(); + + //Re-enable Ctrl-C handling or any subsequently started + //programs will inherit the disabled state. + SetConsoleCtrlHandler(nullptr, false); + } + WaitForSingleObject(parityHandle, INFINITE); +} + +bool ParityIsRunning() +{ + return WaitForSingleObject(parityHandle, 0) == WAIT_TIMEOUT; +} + +void OpenUI() +{ + // Launch parity + TCHAR path[MAX_PATH] = { 0 }; + if (!GetParityExePath(path, MAX_PATH)) + return; + + PROCESS_INFORMATION procInfo = { 0 }; + STARTUPINFO startupInfo = { sizeof(STARTUPINFO) }; + + LPWSTR cmd = _T("parity.exe ui"); + CreateProcess(path, cmd, nullptr, nullptr, false, CREATE_NO_WINDOW, nullptr, nullptr, &startupInfo, &procInfo); +} \ No newline at end of file diff --git a/windows/ptray/ptray.ico b/windows/ptray/ptray.ico new file mode 100644 index 000000000..61e68b90b Binary files /dev/null and b/windows/ptray/ptray.ico differ diff --git a/windows/ptray/ptray.rc b/windows/ptray/ptray.rc new file mode 100644 index 000000000..9f10e0aa8 Binary files /dev/null and b/windows/ptray/ptray.rc differ diff --git a/windows/ptray/ptray.vcxproj b/windows/ptray/ptray.vcxproj new file mode 100644 index 000000000..efe1d2e48 --- /dev/null +++ b/windows/ptray/ptray.vcxproj @@ -0,0 +1,155 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {37C89E90-8C9E-4FFC-AAE7-B3695D5EB6F4} + Win32Proj + ptray + 8.1 + + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + NotUsing + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + + + Windows + true + + + + + Use + Level3 + Disabled + _DEBUG;_WINDOWS;%(PreprocessorDefinitions) + + + Windows + true + + + + + Level3 + NotUsing + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + + + Windows + true + true + true + + + + + Level3 + Use + MaxSpeed + true + true + NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + + + Windows + true + true + true + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/windows/ptray/resource.h b/windows/ptray/resource.h new file mode 100644 index 000000000..1f4b02343 Binary files /dev/null and b/windows/ptray/resource.h differ diff --git a/windows/ptray/targetver.h b/windows/ptray/targetver.h new file mode 100644 index 000000000..87c0086de --- /dev/null +++ b/windows/ptray/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include