diff --git a/src/windows/README.txt b/src/windows/README.txt index a332a48b5..7cce83eff 100644 --- a/src/windows/README.txt +++ b/src/windows/README.txt @@ -22,7 +22,11 @@ I tested with NSIS 3.0a0. "!define PROG_VERSION "3.0.3"" to the version of qbittorrent you just built. 2. Extract the plugins found in the folder "nsis plugins" into your NSIS's unicode Plugin directory(usually C:\Program Files\NSIS\Plugins\x86-unicode). - Only the *.dll files are needed. + Only the *.dll files are needed. Don't use the .dll from UAC.zip, use the one from "UAC Unicode.zip". + NOTE: To build the unicode version of UAC with MSVC2008 you need: + a) the sources from UAC.zip + b) apply the util.cpp.diff from "UAC Unicode.zip" to util.cpp + c) in a msvc command prompt issue: cl.exe /O1s /GS- /GR- /EHs-c- /Zl /LD /DUNICODE RunAs.cpp uac.cpp util.cpp /link kernel32.lib user32.lib shell32.lib advapi32.lib ole32.lib /DLL /MANIFEST:NO /OUT:uac.dll 3. The script you need to compile is "qbittorrent.nsi". It includes all other necessary scripts. 4. The script expects the following file tree: @@ -59,5 +63,5 @@ If you add any new LangString variable to the scripts you NEED to provide "translations" of it to all the .nsi files inside "installer-translations. You can always leave the english string but you have to use all the LANG_ for the given variable. Otherwise, if the user chooses a language that you -haven't provide a LANG_ for your variable then your string will be empty. +haven't provided a LANG_ for your variable then your string will be empty. Don't worry though, NSIS throws warnings for this when compiling the scripts. diff --git a/src/windows/UAC.nsh b/src/windows/UAC.nsh new file mode 100644 index 000000000..b496d7011 --- /dev/null +++ b/src/windows/UAC.nsh @@ -0,0 +1,297 @@ +/*** UAC Plug-in *** + +Interactive User (MediumIL) Admin user (HighIL) +***[Setup.exe]************* ***[Setup.exe]************** +* * * * +* +++[.OnInit]+++++++++++ * * +++[.OnInit]++++++++++++ * +* + UAC_RunElevated >---+-+----> * + + * +* + NSIS.Quit + * * + + * +* +++++++++++++++++++++++ * * ++++++++++++++++++++++++ * +* * * * +* * * * +* +++[Section]+++++++++++ * * +++[Section]++++++++++++ * +* + + * /--+-+- +** +** Get integrity level of current process +** +**/ +!macro UAC_GetIntegrityLevel outvar +UAC::_ 6 +!if "${outvar}" != "s" + Pop ${outvar} +!endif +!macroend + + + +/* UAC_IsAdmin +** +** Is the current process running with administrator privileges? Result in $0 +** +** ${If} ${UAC_IsAdmin} ... +** +**/ +!macro UAC_IsAdmin +UAC::_ 2 +!macroend +!define UAC_IsAdmin `"" UAC_IsAdmin ""` +!macro _UAC_IsAdmin _a _b _t _f +!insertmacro _UAC_MakeLL_Cmp _!= 0 2s +!macroend + + + +/* UAC_IsInnerInstance +** +** Does the current process have a NSIS/UAC parent process that is part of the elevation operation? +** +** ${If} ${UAC_IsInnerInstance} ... +** +**/ +!macro UAC_IsInnerInstance +UAC::_ 3 +!macroend +!define UAC_IsInnerInstance `"" UAC_IsInnerInstance ""` +!macro _UAC_IsInnerInstance _a _b _t _f +!insertmacro _UAC_MakeLL_Cmp _!= 0 3s +!macroend + + + +/* UAC_PageElevation_OnInit, UAC_PageElevation_OnGuiInit, +** +** Helper macros for elevation on a custom elevation page, see the DualMode example for more information. +** +**/ +!macro UAC_Notify_OnGuiInit +UAC::_ 4 +!macroend +!macro UAC_PageElevation_OnGuiInit +!insertmacro UAC_Notify_OnGuiInit +!macroend +!macro UAC_PageElevation_OnInit +UAC::_ 5 +${IfThen} ${Errors} ${|} Quit ${|} +!macroend + + + +/* UAC_AsUser_Call +** +** Calls a function or label in the user process instance. +** All the UAC_AsUser_* macros use this helper macro. +** +**/ +!define UAC_SYNCREGISTERS 0x1 +;define UAC_SYNCSTACK 0x2 +!define UAC_SYNCOUTDIR 0x4 +!define UAC_SYNCINSTDIR 0x8 +;define UAC_CLEARERRFLAG 0x10 +!macro UAC_AsUser_Call type name flags +push $0 +Get${type}Address $0 ${name} +!verbose push +!verbose ${UAC_VERBOSE} +!insertmacro _UAC_ParseDefineFlagsToInt _UAC_AsUser_Call__flags ${flags} +!verbose pop +StrCpy $0 "1$0:${_UAC_AsUser_Call__flags}" +!undef _UAC_AsUser_Call__flags +Exch $0 +UAC::_ +!macroend + + + +/* +** UAC_AsUser_GetSection +*/ +!macro UAC_AsUser_GetSection secprop secidx outvar +!insertmacro _UAC_AsUser_GenOp ${outvar} SectionGet${secprop} ${secidx} "" +!macroend + + + +/* +** UAC_AsUser_GetGlobalVar +** UAC_AsUser_GetGlobal +*/ +!macro UAC_AsUser_GetGlobalVar var +!insertmacro _UAC_AsUser_GenOp ${var} StrCpy "" ${var} +!macroend +!macro UAC_AsUser_GetGlobal outvar srcvar +!insertmacro _UAC_AsUser_GenOp ${outvar} StrCpy "" ${srcvar} +!macroend + + + +/* +** UAC_AsUser_ExecShell +** +** Call ExecShell in the user process instance. +** +*/ +!macro UAC_AsUser_ExecShell verb command params workdir show +!insertmacro _UAC_IncL +goto _UAC_L_E_${__UAC_L} +_UAC_L_F_${__UAC_L}: +ExecShell "${verb}" "${command}" '${params}' ${show} +return +_UAC_L_E_${__UAC_L}: +!if "${workdir}" != "" + push $outdir + SetOutPath "${workdir}" +!endif +!insertmacro UAC_AsUser_Call Label _UAC_L_F_${__UAC_L} ${UAC_SYNCREGISTERS}|${UAC_SYNCOUTDIR}|${UAC_SYNCINSTDIR} #|${UAC_CLEARERRFLAG} +!if "${workdir}" != "" + pop $outdir + SetOutPath $outdir +!endif +!macroend + + + +!macro _UAC_MakeLL_Cmp cmpop cmp pluginparams +!insertmacro _LOGICLIB_TEMP +UAC::_ ${pluginparams} +pop $_LOGICLIB_TEMP +!insertmacro ${cmpop} $_LOGICLIB_TEMP ${cmp} `${_t}` `${_f}` +!macroend +!macro _UAC_definemath def val1 op val2 +!define /math _UAC_definemath "${val1}" ${op} ${val2} +!ifdef ${def} + !undef ${def} +!endif +!define ${def} "${_UAC_definemath}" +!undef _UAC_definemath +!macroend +!macro _UAC_ParseDefineFlags_orin parse outflags +!searchparse /noerrors ${${parse}} "" _UAC_ParseDefineFlags_orin_f1 "|" _UAC_ParseDefineFlags_orin_f2 +!define _UAC_ParseDefineFlags_orin_this ${_UAC_ParseDefineFlags_orin_f1} +!undef ${parse} +!define ${parse} ${_UAC_ParseDefineFlags_orin_f2} +!define _UAC_ParseDefineFlags_orin_saveout ${${outflags}} +!undef ${outflags} +!define /math ${outflags} "${_UAC_ParseDefineFlags_orin_saveout}" | "${_UAC_ParseDefineFlags_orin_this}" +!undef _UAC_ParseDefineFlags_orin_saveout +!undef _UAC_ParseDefineFlags_orin_this +!ifdef _UAC_ParseDefineFlags_orin_f1 + !undef _UAC_ParseDefineFlags_orin_f1 + !undef _UAC_ParseDefineFlags_orin_f2 +!endif +!macroend +!macro _UAC_ParseDefineFlags_Begin _outdef _in +!define _UAC_PDF${_outdef}_parse "${_in}" +!define _UAC_PDF${_outdef}_flags "" +!define _UAC_PDF${_outdef}_r 0 +!insertmacro _UAC_ParseDefineFlags_orin _UAC_PDF${_outdef}_parse _UAC_PDF${_outdef}_flags ;0x1 +!insertmacro _UAC_ParseDefineFlags_orin _UAC_PDF${_outdef}_parse _UAC_PDF${_outdef}_flags ;0x2 +!insertmacro _UAC_ParseDefineFlags_orin _UAC_PDF${_outdef}_parse _UAC_PDF${_outdef}_flags ;0x4 +!insertmacro _UAC_ParseDefineFlags_orin _UAC_PDF${_outdef}_parse _UAC_PDF${_outdef}_flags ;0x8 +!insertmacro _UAC_ParseDefineFlags_orin _UAC_PDF${_outdef}_parse _UAC_PDF${_outdef}_flags ;0x10 +!macroend +!macro _UAC_ParseDefineFlags_End _outdef +!define ${_outdef} ${_UAC_PDF${_outdef}_r} +!undef _UAC_PDF${_outdef}_r +!undef _UAC_PDF${_outdef}_flags +!undef _UAC_PDF${_outdef}_parse +!macroend +!macro _UAC_ParseDefineFlags_IncludeFlag _outdef flag +!if ${_UAC_PDF${_outdef}_flags} & ${flag} + !insertmacro _UAC_definemath _UAC_PDF${_outdef}_r ${_UAC_PDF${_outdef}_r} | ${flag} +!endif +!macroend +!macro _UAC_ParseDefineFlagsToInt _outdef _in +!insertmacro _UAC_ParseDefineFlags_Begin _UAC_ParseDefineFlagsToInt_tmp "${_in}" +!define ${_outdef} ${_UAC_PDF_UAC_ParseDefineFlagsToInt_tmp_flags} +!insertmacro _UAC_ParseDefineFlags_End _UAC_ParseDefineFlagsToInt_tmp +!undef _UAC_ParseDefineFlagsToInt_tmp +!macroend +!macro _UAC_IncL +!insertmacro _UAC_definemath __UAC_L "${__UAC_L}" + 1 +!macroend +!macro _UAC_AsUser_GenOp outvar op opparam1 opparam2 +!define _UAC_AUGOGR_ID _UAC_AUGOGR_OP${outvar}${op}${opparam1}${opparam2} +!ifndef ${_UAC_AUGOGR_ID} ;Has this exact action been done before? + !if ${outvar} == $0 + !define ${_UAC_AUGOGR_ID} $1 + !else + !define ${_UAC_AUGOGR_ID} $0 + !endif + !if "${opparam1}" == "" + !define _UAC_AUGOGR_OPP1 ${${_UAC_AUGOGR_ID}} + !define _UAC_AUGOGR_OPP2 ${opparam2} + !else + !define _UAC_AUGOGR_OPP1 ${opparam1} + !define _UAC_AUGOGR_OPP2 ${${_UAC_AUGOGR_ID}} + !endif + goto ${_UAC_AUGOGR_ID}_C + ${_UAC_AUGOGR_ID}_F: + ${op} ${_UAC_AUGOGR_OPP1} ${_UAC_AUGOGR_OPP2} + return + ${_UAC_AUGOGR_ID}_C: + !undef _UAC_AUGOGR_OPP1 + !undef _UAC_AUGOGR_OPP2 +!endif +push ${${_UAC_AUGOGR_ID}} +!insertmacro UAC_AsUser_Call Label ${_UAC_AUGOGR_ID}_F ${UAC_SYNCREGISTERS} +StrCpy ${outvar} ${${_UAC_AUGOGR_ID}} +pop ${${_UAC_AUGOGR_ID}} +!undef _UAC_AUGOGR_ID +!macroend + + + +!verbose pop +!endif /* UAC_HDR__INC */ \ No newline at end of file diff --git a/src/windows/installer.nsi b/src/windows/installer.nsi index 407726cb2..783b7ef65 100644 --- a/src/windows/installer.nsi +++ b/src/windows/installer.nsi @@ -100,6 +100,72 @@ Section $(inst_startmenu) ;"Create Start Menu Shortcut" SectionEnd +Section $(inst_torrent) ;"Open .torrent files with qBittorrent" + + ReadRegStr $0 HKLM "Software\Classes\.torrent" "" + + StrCmp $0 "qBittorrent" clear_errors 0 + ;Check if empty string + StrCmp $0 "" clear_errors 0 + ;Write old value to OpenWithProgIds + WriteRegStr HKLM "Software\Classes\.torrent\OpenWithProgIds" $0 "" + + clear_errors: + ClearErrors + + WriteRegStr HKLM "Software\Classes\.torrent" "" "qBittorrent" + WriteRegStr HKLM "Software\Classes\.torrent" "Content Type" "application/x-bittorrent" + + !insertmacro UAC_AsUser_Call Function inst_torrent_user ${UAC_SYNCREGISTERS}|${UAC_SYNCOUTDIR}|${UAC_SYNCINSTDIR} + + System::Call 'Shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_IDLIST}, i 0, i 0)' + +SectionEnd + +Function inst_torrent_user + + ReadRegStr $0 HKCU "Software\Classes\.torrent" "" + + StrCmp $0 "qBittorrent" clear_errors 0 + ;Check if empty string + StrCmp $0 "" clear_errors 0 + ;Write old value to OpenWithProgIds + WriteRegStr HKCU "Software\Classes\.torrent\OpenWithProgIds" $0 "" + + clear_errors: + ClearErrors + + WriteRegStr HKCU "Software\Classes\.torrent" "" "qBittorrent" + WriteRegStr HKCU "Software\Classes\.torrent" "Content Type" "application/x-bittorrent" + +FunctionEnd + +Section $(inst_magnet) ;"Open magnet links with qBittorrent" + + WriteRegStr HKLM "Software\Classes\magnet" "" "URL:Magnet link" + WriteRegStr HKLM "Software\Classes\magnet" "Content Type" "application/x-magnet" + WriteRegStr HKLM "Software\Classes\magnet" "URL Protocol" "" + WriteRegStr HKLM "Software\Classes\magnet\DefaultIcon" "" '"$INSTDIR\qbittorrent.exe",1' + WriteRegStr HKLM "Software\Classes\magnet\shell" "" "open" + WriteRegStr HKLM "Software\Classes\magnet\shell\open\command" "" '"$INSTDIR\qbittorrent.exe" "%1"' + + !insertmacro UAC_AsUser_Call Function inst_magnet_user ${UAC_SYNCREGISTERS}|${UAC_SYNCOUTDIR}|${UAC_SYNCINSTDIR} + + System::Call 'Shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_IDLIST}, i 0, i 0)' + +SectionEnd + +Function inst_magnet_user + + WriteRegStr HKCU "Software\Classes\magnet" "" "URL:Magnet link" + WriteRegStr HKCU "Software\Classes\magnet" "Content Type" "application/x-magnet" + WriteRegStr HKCU "Software\Classes\magnet" "URL Protocol" "" + WriteRegStr HKCU "Software\Classes\magnet\DefaultIcon" "" '"$INSTDIR\qbittorrent.exe",1' + WriteRegStr HKCU "Software\Classes\magnet\shell" "" "open" + WriteRegStr HKCU "Software\Classes\magnet\shell\open\command" "" '"$INSTDIR\qbittorrent.exe" "%1"' + +FunctionEnd + Section $(inst_firewall) DetailPrint $(inst_firewallinfo) @@ -109,22 +175,29 @@ SectionEnd ;-------------------------------- -Function .onInit - - !insertmacro MUI_LANGDLL_DISPLAY +Function .onInit + + !insertmacro Init "installer" + !insertmacro MUI_LANGDLL_DISPLAY FunctionEnd Function check_instance - check: - FindProcDLL::FindProc "qbittorrent.exe" - StrCmp $R0 "1" 0 notfound - MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION $(inst_warning) IDRETRY check IDCANCEL done + check: + FindProcDLL::FindProc "qbittorrent.exe" + StrCmp $R0 "1" 0 notfound + MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION $(inst_warning) IDRETRY check IDCANCEL done + + done: + Abort + + notfound: + +FunctionEnd + +Function PageFinishRun + + !insertmacro UAC_AsUser_ExecShell "" "$INSTDIR\qbittorrent.exe" "" "" "" - done: - Abort - - notfound: - FunctionEnd diff --git a/src/windows/nsis plugins/UAC Unicode.zip b/src/windows/nsis plugins/UAC Unicode.zip new file mode 100644 index 000000000..f3c6d6628 Binary files /dev/null and b/src/windows/nsis plugins/UAC Unicode.zip differ diff --git a/src/windows/nsis plugins/UAC.zip b/src/windows/nsis plugins/UAC.zip new file mode 100644 index 000000000..4e205d410 Binary files /dev/null and b/src/windows/nsis plugins/UAC.zip differ diff --git a/src/windows/options.nsi b/src/windows/options.nsi index 384043b0a..1f476ed18 100644 --- a/src/windows/options.nsi +++ b/src/windows/options.nsi @@ -7,7 +7,8 @@ SetCompressor /SOLID LZMA SetCompressorDictSize 64 XPStyle on -!include "MUI.nsh" +!include "MUI.nsh" +!include "UAC.nsh" !include "FileFunc.nsh" ;For the file association @@ -19,7 +20,8 @@ XPStyle on !define CSIDL_LOCALAPPDATA '0x1C' ;Local Application Data path !define PROG_VERSION "3.2.0" -!define MUI_FINISHPAGE_RUN "$INSTDIR\qbittorrent.exe" +!define MUI_FINISHPAGE_RUN +!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun !define MUI_FINISHPAGE_RUN_TEXT $(launch_qbt) ; The name of the installer @@ -31,7 +33,7 @@ OutFile "qbittorrent_${PROG_VERSION}_setup.exe" ;Installer Version Information VIAddVersionKey "ProductName" "qBittorrent" VIAddVersionKey "CompanyName" "The qBittorrent project" -VIAddVersionKey "LegalCopyright" "Copyright ©2006-2013 The qBittorrent project" +VIAddVersionKey "LegalCopyright" "Copyright ©2006-2014 The qBittorrent project" VIAddVersionKey "FileDescription" "qBittorrent - A Bittorrent Client" VIAddVersionKey "FileVersion" "${PROG_VERSION}" @@ -45,7 +47,7 @@ InstallDir $PROGRAMFILES\qBittorrent InstallDirRegKey HKLM Software\qbittorrent InstallLocation ; Request application privileges for Windows Vista -RequestExecutionLevel admin +RequestExecutionLevel user ;-------------------------------- ;General Settings @@ -81,3 +83,29 @@ RequestExecutionLevel admin !insertmacro MUI_RESERVEFILE_LANGDLL ReserveFile "${NSISDIR}\Plugins\x86-unicode\FindProcDLL.dll" +ReserveFile "${NSISDIR}\Plugins\x86-unicode\UAC.dll" + +!macro Init thing +uac_tryagain: +!insertmacro UAC_RunElevated +${Switch} $0 +${Case} 0 + ${IfThen} $1 = 1 ${|} Quit ${|} ;we are the outer process, the inner process has done its work, we are done + ${IfThen} $3 <> 0 ${|} ${Break} ${|} ;we are admin, let the show go on + ${If} $1 = 3 ;RunAs completed successfully, but with a non-admin user + MessageBox mb_YesNo|mb_IconExclamation|mb_TopMost|mb_SetForeground "This ${thing} requires admin privileges, try again" /SD IDNO IDYES uac_tryagain IDNO 0 + ${EndIf} + ;fall-through and die +${Case} 1223 + MessageBox mb_IconStop|mb_TopMost|mb_SetForeground "This ${thing} requires admin privileges, aborting!" + Quit +${Case} 1062 + MessageBox mb_IconStop|mb_TopMost|mb_SetForeground "Logon service not running, aborting!" + Quit +${Default} + MessageBox mb_IconStop|mb_TopMost|mb_SetForeground "Unable to elevate , error $0" + Quit +${EndSwitch} + +SetShellVarContext all +!macroend diff --git a/src/windows/uninstaller.nsi b/src/windows/uninstaller.nsi index 7d64294f4..9615d4abf 100644 --- a/src/windows/uninstaller.nsi +++ b/src/windows/uninstaller.nsi @@ -49,6 +49,42 @@ Section "un.$(remove_shortcuts)" ;"un.Remove shortcuts" Delete "$DESKTOP\qBittorrent.lnk" SectionEnd +Section "un.$(remove_associations)" ;"un.Remove file associations" + SectionIn RO + ReadRegStr $0 HKLM "Software\Classes\.torrent" "" + StrCmp $0 "qBittorrent" 0 torrent_end + DetailPrint "$(uninst_tor_warn) $0" + DeleteRegValue HKLM "Software\Classes\.torrent" "" + DeleteRegKey /ifempty HKLM "Software\Classes\.torrent" + + torrent_end: + ReadRegStr $0 HKLM "Software\Classes\magnet\shell\open\command" "" + StrCmp $0 '"$INSTDIR\qbittorrent.exe" "%1"' 0 magnet_end + DetailPrint "$(uninst_mag_warn) $0" + DeleteRegKey HKLM "Software\Classes\magnet" + + magnet_end: + !insertmacro UAC_AsUser_Call Function un.remove_associations_user ${UAC_SYNCREGISTERS}|${UAC_SYNCOUTDIR}|${UAC_SYNCINSTDIR} + + System::Call 'Shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_IDLIST}, i 0, i 0)' +SectionEnd + +Function un.remove_associations_user + ReadRegStr $0 HKCU "Software\Classes\.torrent" "" + StrCmp $0 "qBittorrent" 0 torrent_end + DetailPrint "$(uninst_tor_warn) $0" + DeleteRegValue HKCU "Software\Classes\.torrent" "" + DeleteRegKey /ifempty HKCU "Software\Classes\.torrent" + + torrent_end: + ReadRegStr $0 HKCU "Software\Classes\magnet\shell\open\command" "" + StrCmp $0 '"$INSTDIR\qbittorrent.exe" "%1"' 0 magnet_end + DetailPrint "$(uninst_mag_warn) $0" + DeleteRegKey HKCU "Software\Classes\magnet" + + magnet_end: +FunctionEnd + Section "un.$(remove_registry)" ;"un.Remove registry keys" SectionIn RO ; Remove registry keys @@ -68,23 +104,36 @@ SectionEnd Section /o "un.$(remove_conf)" ;"un.Remove configuration files" - System::Call 'shell32::SHGetSpecialFolderPath(i $HWNDPARENT, t .r1, i ${CSIDL_APPDATA}, i0)i.r0' - RMDir /r "$1\qBittorrent" - + !insertmacro UAC_AsUser_Call Function un.remove_conf_user ${UAC_SYNCREGISTERS}|${UAC_SYNCOUTDIR}|${UAC_SYNCINSTDIR} + SectionEnd -Section /o "un.$(remove_cache)" - +Function un.remove_conf_user + + System::Call 'shell32::SHGetSpecialFolderPath(i $HWNDPARENT, t .r1, i ${CSIDL_APPDATA}, i0)i.r0' + RMDir /r "$1\qBittorrent" + +FunctionEnd + +Section /o "un.$(remove_cache)" + + !insertmacro UAC_AsUser_Call Function un.remove_cache_user ${UAC_SYNCREGISTERS}|${UAC_SYNCOUTDIR}|${UAC_SYNCINSTDIR} + +SectionEnd + +Function un.remove_cache_user + System::Call 'shell32::SHGetSpecialFolderPath(i $HWNDPARENT, t .r1, i ${CSIDL_LOCALAPPDATA}, i0)i.r0' RMDir /r "$1\qBittorrent\" - -SectionEnd + +FunctionEnd ;-------------------------------- ;Uninstaller Functions Function un.onInit + !insertmacro Init "uninstaller" !insertmacro MUI_UNGETLANGUAGE FunctionEnd