diff --git a/.github/actions/spell-check/allow/names.txt b/.github/actions/spell-check/allow/names.txt index fb7ff648b2bc..3aeaa9b86a52 100644 --- a/.github/actions/spell-check/allow/names.txt +++ b/.github/actions/spell-check/allow/names.txt @@ -34,6 +34,7 @@ Adoumie Advaith alekhyareddy Aleks +amihaiuc angularsen Anirudha arjunbalgovind @@ -115,6 +116,7 @@ martinchrzan martinmoene Melman Mengyuan +Mihaiuc Mikhayelyan msft Mykhailo @@ -142,6 +144,7 @@ ricardosantos riri ritchielawrence robmikh +Russinovich Rutkas ryanbodrug saahmedm @@ -186,6 +189,7 @@ Zykova Bilibili BVID +capturevideosample cmdow Controlz cortana diff --git a/.github/actions/spell-check/excludes.txt b/.github/actions/spell-check/excludes.txt index 80fee9ff244b..912d72d472ec 100644 --- a/.github/actions/spell-check/excludes.txt +++ b/.github/actions/spell-check/excludes.txt @@ -16,6 +16,7 @@ (?:|$^ 92.31% - excluded 12/13)/editor/[^/]+$ /images/launcher/[^/]+$ /TestFiles/ +[^/]\.cur$ [^/]\.gcode$ [^/]\.rgs$ \.a$ @@ -119,5 +120,6 @@ ^src/modules/MouseWithoutBorders/App/Helper/.*\.resx$ ^src/modules/previewpane/UnitTests-MarkdownPreviewHandler/HelperFiles/MarkdownWithHTMLImageTag\.txt$ ^src/Monaco/ +^src/common/sysinternals/Eula/ ^tools/Verification scripts/Check preview handler registration\.ps1$ ignore$ diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 0e8491dc44af..4c47211a0ace 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -15,6 +15,7 @@ AColumn acrt ACTIVATEAPP activationaction +ADDSTRING ADDUNDORECORD ADifferent adml @@ -27,8 +28,10 @@ AGGREGATABLE ahk AHybrid akv +ALIGNRIGHT ALarger ALLAPPS +ALLCHILDREN ALLINPUT ALLOWUNDO ALLVIEW @@ -39,6 +42,7 @@ AMPROPSETID amr ANDSCANS animatedvisuals +Animnate ansicolor ANull AOC @@ -69,6 +73,7 @@ ARemapped ARPINSTALLLOCATION ARPPRODUCTICON ARRAYSIZE +ARROWKEYS asf AShortcut ASingle @@ -86,9 +91,14 @@ atlstr ATRIOX aumid Authenticode +AUTOBUDDY +AUTOCHECKBOX AUTOHIDE +AUTOHSCROLL AUTOMATIONPROPERTIES +AUTORADIOBUTTON Autorun +AUTOTICKS AUTOUPDATE AValid awakeness @@ -105,12 +115,17 @@ BIF bigbar bigobj binlog +binres BITMAPFILEHEADER bitmapimage BITMAPINFO BITMAPINFOHEADER +Bitmaps +BITSPERPEL BITSPIXEL bla +BLACKFRAME +BLENDFUNCTION Blockquotes blogs Blt @@ -143,6 +158,7 @@ BVal BValue byapp BYPOSITION +CALCRECT CALG callbackptr calpwstr @@ -171,6 +187,7 @@ CHANGECBCHAIN changecursor CHILDACTIVATE CHILDWINDOW +CHOOSEFONT cidl cim CImage @@ -209,6 +226,7 @@ colorformat colorhistory colorhistorylimit COLORKEY +comctl comdef comdlg comexp @@ -231,14 +249,17 @@ CONTEXTMENUHANDLER CONTROLL CONTROLPARENT copiedcolorrepresentation +COPYPEN COREWINDOW cotaskmem COULDNOT countof cph +cplusplus CPower cppwinrt createdump +CREATEPROCESS CREATESCHEDULEDTASK CREATESTRUCT CREATEWINDOWFAILED @@ -254,6 +275,8 @@ cso CSRW CStyle CTest +CTEXT +CTLCOLORSTATIC currentculture CURRENTDIR CURSORINFO @@ -313,10 +336,12 @@ DELA DELETEDKEYIMAGE DELETESCANS deletethis +DEMOTYPE DENORMAL depersist deprioritized DESELECTOTHERS +DESIGNINFO DESKTOPABSOLUTEEDITING DESKTOPABSOLUTEPARSING desktopshorcutinstalled @@ -331,12 +356,17 @@ DEVMON devpkey DEVSOURCE DGR +DIALOGEX DIIRFLAG dimm DISABLEASACTIONKEY +DISABLENOSCROLL diskmgmt DISPLAYCHANGE DISPLAYCONFIG +DISPLAYFLAGS +DISPLAYFREQUENCY +DISPLAYORIENTATION displayname divyan Dlg @@ -360,8 +390,11 @@ DRAWFRAME drawingcolor dreamsofameaningfullife drivedetectionwarning +DROPFILES dshow DSTINVERT +DSurface +DTexture DUMMYUNIONNAME Dutil DVASPECT @@ -398,12 +431,15 @@ EData Edid EDITKEYBOARD EDITSHORTCUTS +EDITTEXT EFile ekus emmintrin Emoji ENABLEDELAYEDEXPANSION ENABLEDPOPUP +ENABLETAB +ENABLETEMPLATE encodedlaunch encryptor endpointvolume @@ -423,8 +459,10 @@ ERRORTITLE erwrite ESettings esrp +ETDT etl etw +eula eurochange eventlog eventvwr @@ -454,6 +492,7 @@ exsb exstyle EXTENDEDKEY EXTENDEDVERBS +EXTRALIGHT EXTRINSICPROPERTIES eyetracker FANCYZONESDRAWLAYOUTTEST @@ -464,12 +503,14 @@ fff FILEEXPLORER FILEFLAGS FILEFLAGSMASK +FILEINFOSIG FILELOCKSMITH FILELOCKSMITHCONTEXTMENU FILELOCKSMITHEXT FILELOCKSMITHLIBINTEROP FILEMUSTEXIST FILEOP +FILEOPENDIALOGOPTIONS FILEOS FILESUBTYPE FILESYSPATH @@ -477,9 +518,11 @@ Filetime FILEVERSION Filtergraph Filterkeyboard +FILTERMODE Filterx findfast FIXEDFILEINFO +FIXEDSYS flac flyouts FMask @@ -487,7 +530,10 @@ FOF FOFX FOLDERID folderpath +FONTTYPE +FORCEFILESYSTEM FORCEMINIMIZE +FORMATDLGORD formatetc FORPARSING FRAMECHANGED @@ -504,18 +550,23 @@ GC'ed GCLP gdi gdiplus +GDIPVER GDISCALED GEmoji GETCLIENTAREAANIMATION +GETCURSEL GETDESKWALLPAPER GETDLGCODE GETDPISCALEDSIZE getfilesiginforedist GETICON +GETHOTKEY GETMINMAXINFO +GETNONCLIENTMETRICS GETPROPERTYSTOREFLAGS GETSCREENSAVERRUNNING GETSECKEY +GETSTICKYKEYS GETTEXTLENGTH GHND GMEM @@ -548,6 +599,7 @@ hbm hbmp hbr HBRBACKGROUND +hbrush hcblack HCERTSTORE HCRYPTHASH @@ -555,6 +607,7 @@ HCRYPTPROV hcursor hcwhite hdc +hdr hdrop hdwwiz Helpline @@ -567,6 +620,7 @@ Hiber Hiberboot HIBYTE hicon +HIDEREADONLY HIDEWINDOW Hif HIMAGELIST @@ -575,10 +629,12 @@ hinst hinstance HIWORD HKCC +HKCOMB HKCR HKCU hkey HKLM +HKM HKPD HKU HMD @@ -586,9 +642,11 @@ hmenu hmodule hmonitor homljgmgpmcbpjbnjpfijnhipfkiclkd +HORZRES HORZSIZE Hostbackdropbrush hotkeycontrol +HOTKEYF hotkeys hotlight hotspot @@ -616,15 +674,19 @@ hwnd HWNDFIRST HWNDLAST HWNDNEXT +HWNDPARENT HWNDPREV hyjiacan IAI IBeam ICONERROR ICONLOCATION +idc +IDCANCEL IDD idl idlist +IDOK IDR IDXGI ietf @@ -645,12 +707,15 @@ imageresizerinput imageresizersettings imagingdevices ime +INCONTACT Indo inetcpl Infobar INFOEXAMPLE Infotip +INITDIALOG INITGUID +INITTOLOGFONTSTRUCT inorder INPC inproc @@ -733,18 +798,23 @@ lcb LCIDTo Lclean Ldone +Ldr ldx LEFTSCROLLBAR +LEFTTEXT LError LEVELID LExit lhwnd LIBID +LIMITSIZE +LIMITTEXT lindex linkid LINKOVERLAY LINQTo listview +LIVEZOOM lld LLKH llkhf @@ -762,12 +832,15 @@ LOGFONT LOGFONTW logon LOGPIXELSX +LOGPIXELSY longdate LONGLONG +LONGNAMES lowlevel LOWORD lparam LPBITMAPINFOHEADER +LPCFHOOKPROC LPCITEMIDLIST lpcmi LPCMINVOKECOMMANDINFO @@ -796,12 +869,15 @@ LPTSTR LPW lpwcx lpwndpl +lpv LReader LRESULT LSTATUS lstrcmp lstrcmpi +lstrcpyn lstrlen +LTEXT LTRB LTRREADING luid @@ -811,12 +887,16 @@ LVal LWA lwin LZero +MAGTRANSFORM majortype makecab MAKEINTRESOURCE MAKEINTRESOURCEA MAKEINTRESOURCEW MAKELANGID +MAKELONG +MAKELPARAM +MAKEWPARAM manifestdependency MAPPEDTOSAMEKEY MAPTOSAMESHORTCUT @@ -856,6 +936,7 @@ MINIMIZEBOX MINIMIZEEND MINIMIZESTART miniz +MINMAXINFO Mip Miracast mjpg @@ -869,6 +950,7 @@ mmi mmsys mmsystem mockapi +MODALFRAME MODESPRUNED MONITORENUMPROC MONITORINFO @@ -890,6 +972,7 @@ MRT mru msc mscorlib +msctls msdata MSDL msedge @@ -897,6 +980,7 @@ MSGFLT msiexec MSIFASTINSTALL MSIHANDLE +Msimg msiquery MSIRESTARTMANAGERCONTROL msixbundle @@ -908,6 +992,7 @@ msrc msstore mst msvcp +msvsmon MTND MULTIPLEUSE multizone @@ -957,33 +1042,40 @@ newsgroups NIF NLog NLSTEXT +NMAKE NNN NOACTIVATE NOAGGREGATION NOASYNC +NOCLIP NOCLOSEPROCESS NOCOALESCE NOCOMM NOCONFIRMMKDIR NOCOPYBITS NOCOPYSECURITYATTRIBS +NOCRLF nodeca nodoc NODRAWCAPTION NODRAWICON NOINHERITLAYOUT NOINTERFACE +NOINVERT NOLINKINFO NOMCX NOMINMAX NOMIRRORBITMAP NOMOVE +NONANTIALIASED nonclient +NONCLIENTMETRICSW NONELEVATED NONINFRINGEMENT nonstd NOOWNERZORDER NOPARENTNOTIFY +NOPREFIX NOREDIRECTIONBITMAP NOREDRAW NOREMOVE @@ -996,6 +1088,8 @@ NORMALUSER NOSEARCH NOSENDCHANGING NOSIZE +NOTHOUSANDS +NOTICKS NOTIFICATIONSDLL NOTIFYICONDATA NOTIFYICONDATAW @@ -1005,6 +1099,7 @@ NOTOPMOST NOTRACK NOTSRCCOPY NOTSRCERASE +NOTXORPEN NOZORDER NPH npmjs @@ -1014,6 +1109,8 @@ NTAPI ntdll ntfs NTSTATUS +NTSYSAPI +NULLCURSOR nullonfailure numberbox nwc @@ -1022,6 +1119,7 @@ objidl ocr Ocrsettings odbccp +OEMCONVERT officehubintl OFN ofs @@ -1041,8 +1139,10 @@ ORPHANEDDIALOGTITLE ORSCANS oss ostr +OSVERSIONINFO OSVERSIONINFOEX OSVERSIONINFOEXW +OSVERSIONINFOW osvi OUTOFCONTEXT outpin @@ -1052,7 +1152,9 @@ outsettings OVERLAPPEDWINDOW overlaywindow Oversampling +OVERWRITEPROMPT OWNDC +OWNERDRAWFIXED Packagemanager PACL PAINTSTRUCT @@ -1081,7 +1183,9 @@ PCIDLIST PCTSTR PCWSTR pdbs +PDEVMODE pdisp +PDLL pdo pdto pdtobj @@ -1089,6 +1193,8 @@ pdw Peb PElems Pels +PELSHEIGHT +PELSWIDTH PERCEIVEDFLAG perfmon pesi @@ -1115,9 +1221,13 @@ ploc ploca plocm pluginsmodel +PMAGTRANSFORM PMSIHANDLE pnid +PNMLINK Pnp +POINTERID +POINTERUPDATE Popups POPUPWINDOW POSITIONITEM @@ -1163,6 +1273,7 @@ PROCESSENTRY PROCESSKEY processthreadsapi PROCESSTRACE +procmon PRODEXT PRODUCTVERSION Progman @@ -1173,6 +1284,7 @@ PROPERTYKEY propkey PROPVARIANT propvarutil +PRTL prvpane psapi pscid @@ -1191,6 +1303,7 @@ PSYSTEM psz ptb ptc +PTCHAR ptd PTOKEN PToy @@ -1222,6 +1335,8 @@ RAWMODE RAWPATH rbhid rclsid +RCZOOMIT +RDW READMODE READOBJECTS recents @@ -1324,6 +1439,7 @@ SCID Scip scipbe Scode +SCREENFONTS screensaver screenshots scrollviewer @@ -1332,16 +1448,27 @@ SDKDDK sdns searchterm SEARCHUI +SECONDARYDISPLAY secpol +SELCHANGE SENDCHANGE sendvirtualinput serverside +SETBUDDYINT SETCONTEXT +SETCURSEL setcursor SETFOCUS SETFOREGROUND +SETHOTKEY SETICON +SETLOWPOWERACTIVE +SETPOWEROFFACTIVE +SETRANGE SETREDRAW +SETRULES +SETSCREENSAVEACTIVE +SETSTICKYKEYS SETTEXT SETTINGCHANGE SETTINGSCHANGED @@ -1351,6 +1478,7 @@ setvariable SETWORKAREA sfgao SFGAOF +SHACF SHANDLE sharpkeys SHCNE @@ -1388,6 +1516,7 @@ shortsplit showcolorname SHOWDEFAULT SHOWELEVATIONPROMPT +SHOWMAGNIFIEDCURSOR SHOWMAXIMIZED SHOWMINIMIZED SHOWMINNOACTIVE @@ -1462,6 +1591,7 @@ STATICEDGE STATSTG stdafx STDAPI +stdc stdcpplatest STDMETHODCALLTYPE STDMETHODIMP @@ -1469,8 +1599,10 @@ STGC STGM STGMEDIUM sticpl +STICKYKEYS stl storelogo +stprintf streamjsonrpc STRINGIZE stringtable @@ -1479,12 +1611,14 @@ Strm strret strsafe strutil +stscanf sttngs Stubless STYLECHANGED STYLECHANGING subkeys sublang +SUBMODULEUPDATE subquery Superbar sut @@ -1515,6 +1649,7 @@ SYSKEYUP SYSLIB SYSMENU SYSTEMAPPS +SYSTEMMODAL SYSTEMTIME tailwindcss tapp @@ -1529,9 +1664,18 @@ targetver taskkill taskschd TCHAR +TCIF +TCITEM +TCN Tcollab tcs +tcscat +tcschr +tcscmp tcscpy +tcsdup +tcslen +tcsrchr TCustom tdbuild TDefault @@ -1542,6 +1686,7 @@ testprocess TEXCOORD TEXTEXTRACTOR TEXTINCLUDE +tfopen tgz themeresources THH @@ -1555,6 +1700,7 @@ timedate timediff timeunion timeutil +TITLEBARINFO Titlecase tkcontrols tkconverters @@ -1575,6 +1721,7 @@ touchpad TRACEHANDLE tracelogging tracerpt +trackbar trafficmanager traies transicc @@ -1585,6 +1732,7 @@ trx tsa Tsd TServer +tstoi TStr tweakme TWF @@ -1593,8 +1741,10 @@ TYPEKEYBOARD TYPEMOUSE TYPESHORTCUT UAC +UACUI UAL uap +UBR UCallback udit uefi @@ -1608,6 +1758,7 @@ ums uncompilable UNCPRIORITY UNDNAME +unhiding UNICODETEXT uninstantiated uniquifier @@ -1621,9 +1772,13 @@ unregistering unremapped unvirtualized unwide +unzoom UOffset UOI Updatelayout +UPDATENOW +UPDATEREGISTRY +updown UPGRADINGPRODUCTCODE Uptool urld @@ -1638,7 +1793,7 @@ USRDLL UType uuidv uwp -Uxtheme +uxtheme vabdq validmodulename valuegenerator @@ -1650,6 +1805,7 @@ VCINSTALLDIR vcm Vcpkg VCRT +VCENTER vcruntime vcvars VDesktop @@ -1660,6 +1816,7 @@ VERBW VERIFYCONTEXT verrsrc VERSIONINFO +VERTRES VERTSIZE VFT vget @@ -1670,6 +1827,7 @@ videoconferencevirtualdriver VIDEOINFOHEADER viewmodel vih +VIRTKEY VIRTUALDESK VISEGRADRELAY visiblecolorformats @@ -1696,6 +1854,7 @@ vsonline vstemplate vstest VSTHRD +vstprintf VSTT vswhere Vtbl @@ -1712,6 +1871,7 @@ wcsicmp wcsncpy wcsnicmp WDA +wdm wdp wdupenv webbrowsers @@ -1732,6 +1892,7 @@ windef windir WINDOWCREATED WINDOWEDGE +WINDOWINFO WINDOWNAME WINDOWPLACEMENT WINDOWPOSCHANGED @@ -1848,4 +2009,7 @@ ZEROINIT zonable zoneset Zoneszonabletester +Zoomin +zoomit +ZOOMITX zzz diff --git a/.github/actions/spell-check/patterns.txt b/.github/actions/spell-check/patterns.txt index 12c1ef6b08d2..f8c9761933e8 100644 --- a/.github/actions/spell-check/patterns.txt +++ b/.github/actions/spell-check/patterns.txt @@ -231,3 +231,7 @@ _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING # ignore long runs of a single character: \b([A-Za-z])\g{-1}{3,}\b + +# ZoomIt menu items with accelerator keys +E&xit +St&yle diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json index 1f84b4d4157b..81d4e9145303 100644 --- a/.pipelines/ESRPSigning_core.json +++ b/.pipelines/ESRPSigning_core.json @@ -217,6 +217,10 @@ "PowerToys.VideoConferenceProxyFilter_x64.dll", "PowerToys.VideoConferenceProxyFilter_arm64.dll", + "PowerToys.ZoomIt.exe", + "PowerToys.ZoomItModuleInterface.dll", + "PowerToys.ZoomItSettingsInterop.dll", + "WinUI3Apps\\PowerToys.Settings.dll", "WinUI3Apps\\PowerToys.Settings.exe" ], diff --git a/COMMUNITY.md b/COMMUNITY.md index 64d77c9f37c8..476ce6235d86 100644 --- a/COMMUNITY.md +++ b/COMMUNITY.md @@ -121,6 +121,8 @@ Find My Mouse is based on Raymond Chen's SuperSonar. Crop And Lock is based on the original work of Robert Mikhayelyan, with Program Manager support from [@kevinguo305](https://github.com/kevinguo305) - Kevin Guo. +ZoomIt's Video Recording Session code is based on Robert Mikhayelyan's https://github.com/robmikh/capturevideosample code. + ### Microsoft InVEST team This amazing team helped PowerToys develop PowerToys Run and Keyboard manager as well as update our Settings to v2. @alekhyareddy28, @arjunbalgovind, @jyuwono @laviusmotileng-ms, @ryanbodrug-microsoft, @saahmedm, @somil55, @traies, @udit3333 @@ -160,6 +162,14 @@ Other contributors: * Paul Schmitt - WWL * And many other Users! +## ZoomIt original contributors + +ZoomIt source code was originally implemented by [Sysinternals](https://sysinternals.com): + +- [@markrussinovich](https://github.com/markrussinovich) - Mark Russinovich +- [@foxmsft](https://github.com/foxmsft) - Alex Mihaiuc +- [@johnstep](https://github.com/johnstep) - John Stephens + ## PowerToys core team - [@crutkas](https://github.com/crutkas/) - Clint Rutkas - Lead diff --git a/PowerToys.sln b/PowerToys.sln index ca7fa06db7b0..7627ee124472 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -639,6 +639,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdvancedPaste.UnitTests", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdvancedPaste.FuzzTests", "src\modules\AdvancedPaste\AdvancedPaste.FuzzTests\AdvancedPaste.FuzzTests.csproj", "{7F5B9557-5878-4438-A721-3E28296BA193}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ZoomIt", "ZoomIt", "{DD6E12FE-5509-4ABC-ACC2-3D6DC98A238C}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ZoomIt", "src\modules\ZoomIt\ZoomIt\ZoomIt.vcxproj", "{0A84F764-3A88-44CD-AA96-41BDBD48627B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ZoomItModuleInterface", "src\modules\ZoomIt\ZoomItModuleInterface\ZoomItModuleInterface.vcxproj", "{E4585179-2AC1-4D5F-A3FF-CFC5392F694C}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ZoomItSettingsInterop", "src\modules\ZoomIt\ZoomItSettingsInterop\ZoomItSettingsInterop.vcxproj", "{CA7D8106-30B9-4AEC-9D05-B69B31B8C461}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -2835,6 +2843,42 @@ Global {7F5B9557-5878-4438-A721-3E28296BA193}.Release|x64.Build.0 = Release|x64 {7F5B9557-5878-4438-A721-3E28296BA193}.Release|x86.ActiveCfg = Release|x64 {7F5B9557-5878-4438-A721-3E28296BA193}.Release|x86.Build.0 = Release|x64 + {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Debug|ARM64.Build.0 = Debug|ARM64 + {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Debug|x64.ActiveCfg = Debug|x64 + {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Debug|x64.Build.0 = Debug|x64 + {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Debug|x86.ActiveCfg = Debug|x64 + {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Debug|x86.Build.0 = Debug|x64 + {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Release|ARM64.ActiveCfg = Release|ARM64 + {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Release|ARM64.Build.0 = Release|ARM64 + {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Release|x64.ActiveCfg = Release|x64 + {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Release|x64.Build.0 = Release|x64 + {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Release|x86.ActiveCfg = Release|x64 + {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Release|x86.Build.0 = Release|x64 + {E4585179-2AC1-4D5F-A3FF-CFC5392F694C}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {E4585179-2AC1-4D5F-A3FF-CFC5392F694C}.Debug|ARM64.Build.0 = Debug|ARM64 + {E4585179-2AC1-4D5F-A3FF-CFC5392F694C}.Debug|x64.ActiveCfg = Debug|x64 + {E4585179-2AC1-4D5F-A3FF-CFC5392F694C}.Debug|x64.Build.0 = Debug|x64 + {E4585179-2AC1-4D5F-A3FF-CFC5392F694C}.Debug|x86.ActiveCfg = Debug|x64 + {E4585179-2AC1-4D5F-A3FF-CFC5392F694C}.Debug|x86.Build.0 = Debug|x64 + {E4585179-2AC1-4D5F-A3FF-CFC5392F694C}.Release|ARM64.ActiveCfg = Release|ARM64 + {E4585179-2AC1-4D5F-A3FF-CFC5392F694C}.Release|ARM64.Build.0 = Release|ARM64 + {E4585179-2AC1-4D5F-A3FF-CFC5392F694C}.Release|x64.ActiveCfg = Release|x64 + {E4585179-2AC1-4D5F-A3FF-CFC5392F694C}.Release|x64.Build.0 = Release|x64 + {E4585179-2AC1-4D5F-A3FF-CFC5392F694C}.Release|x86.ActiveCfg = Release|x64 + {E4585179-2AC1-4D5F-A3FF-CFC5392F694C}.Release|x86.Build.0 = Release|x64 + {CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Debug|ARM64.Build.0 = Debug|ARM64 + {CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Debug|x64.ActiveCfg = Debug|x64 + {CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Debug|x64.Build.0 = Debug|x64 + {CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Debug|x86.ActiveCfg = Debug|x64 + {CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Debug|x86.Build.0 = Debug|x64 + {CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Release|ARM64.ActiveCfg = Release|ARM64 + {CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Release|ARM64.Build.0 = Release|ARM64 + {CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Release|x64.ActiveCfg = Release|x64 + {CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Release|x64.Build.0 = Release|x64 + {CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Release|x86.ActiveCfg = Release|x64 + {CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Release|x86.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -3070,6 +3114,10 @@ Global {0DB0F63A-D2F8-4DA3-A650-2D0B8724218E} = {CA716AE6-FE5C-40AC-BB8F-2C87912687AC} {D5E5F5EA-1B6C-4A73-88BE-304F36C9E4EE} = {9873BA05-4C41-4819-9283-CF45D795431B} {7F5B9557-5878-4438-A721-3E28296BA193} = {9873BA05-4C41-4819-9283-CF45D795431B} + {DD6E12FE-5509-4ABC-ACC2-3D6DC98A238C} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} + {0A84F764-3A88-44CD-AA96-41BDBD48627B} = {DD6E12FE-5509-4ABC-ACC2-3D6DC98A238C} + {E4585179-2AC1-4D5F-A3FF-CFC5392F694C} = {DD6E12FE-5509-4ABC-ACC2-3D6DC98A238C} + {CA7D8106-30B9-4AEC-9D05-B69B31B8C461} = {DD6E12FE-5509-4ABC-ACC2-3D6DC98A238C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/installer/PowerToysSetupCustomActions/CustomAction.cpp b/installer/PowerToysSetupCustomActions/CustomAction.cpp index 8995d2f9b62f..de0096269585 100644 --- a/installer/PowerToysSetupCustomActions/CustomAction.cpp +++ b/installer/PowerToysSetupCustomActions/CustomAction.cpp @@ -1272,7 +1272,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall) } processes.resize(bytes / sizeof(processes[0])); - std::array processesToTerminate = { + std::array processesToTerminate = { L"PowerToys.PowerLauncher.exe", L"PowerToys.Settings.exe", L"PowerToys.AdvancedPaste.exe", @@ -1309,6 +1309,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall) L"PowerToys.WorkspacesLauncherUI.exe", L"PowerToys.WorkspacesEditor.exe", L"PowerToys.WorkspacesWindowArranger.exe", + L"PowerToys.ZoomIt.exe", L"PowerToys.exe", }; diff --git a/src/common/Common.UI/SettingsDeepLink.cs b/src/common/Common.UI/SettingsDeepLink.cs index 41bd2c012e94..73b1824bf1ef 100644 --- a/src/common/Common.UI/SettingsDeepLink.cs +++ b/src/common/Common.UI/SettingsDeepLink.cs @@ -32,6 +32,7 @@ public enum SettingsWindow Dashboard, AdvancedPaste, Workspaces, + ZoomIt, } private static string SettingsWindowNameToString(SettingsWindow value) @@ -80,6 +81,8 @@ private static string SettingsWindowNameToString(SettingsWindow value) return "AdvancedPaste"; case SettingsWindow.Workspaces: return "Workspaces"; + case SettingsWindow.ZoomIt: + return "ZoomIt"; default: { return string.Empty; diff --git a/src/common/GPOWrapper/GPOWrapper.cpp b/src/common/GPOWrapper/GPOWrapper.cpp index a9ecb43818e6..fa8615fbc804 100644 --- a/src/common/GPOWrapper/GPOWrapper.cpp +++ b/src/common/GPOWrapper/GPOWrapper.cpp @@ -132,6 +132,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation { return static_cast(powertoys_gpo::getConfiguredVideoConferenceMuteEnabledValue()); } + GpoRuleConfigured GPOWrapper::GetConfiguredZoomItEnabledValue() + { + return static_cast(powertoys_gpo::getConfiguredZoomItEnabledValue()); + } GpoRuleConfigured GPOWrapper::GetConfiguredMouseWithoutBordersEnabledValue() { return static_cast(powertoys_gpo::getConfiguredMouseWithoutBordersEnabledValue()); diff --git a/src/common/GPOWrapper/GPOWrapper.h b/src/common/GPOWrapper/GPOWrapper.h index 34c1e3646be2..fea1df52f613 100644 --- a/src/common/GPOWrapper/GPOWrapper.h +++ b/src/common/GPOWrapper/GPOWrapper.h @@ -40,6 +40,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation static GpoRuleConfigured GetConfiguredTextExtractorEnabledValue(); static GpoRuleConfigured GetConfiguredAdvancedPasteEnabledValue(); static GpoRuleConfigured GetConfiguredVideoConferenceMuteEnabledValue(); + static GpoRuleConfigured GetConfiguredZoomItEnabledValue(); static GpoRuleConfigured GetConfiguredPeekEnabledValue(); static GpoRuleConfigured GetDisableNewUpdateToastValue(); static GpoRuleConfigured GetDisableAutomaticUpdateDownloadValue(); diff --git a/src/common/GPOWrapper/GPOWrapper.idl b/src/common/GPOWrapper/GPOWrapper.idl index af58834a0c13..f0704969a349 100644 --- a/src/common/GPOWrapper/GPOWrapper.idl +++ b/src/common/GPOWrapper/GPOWrapper.idl @@ -44,6 +44,7 @@ namespace PowerToys static GpoRuleConfigured GetConfiguredTextExtractorEnabledValue(); static GpoRuleConfigured GetConfiguredAdvancedPasteEnabledValue(); static GpoRuleConfigured GetConfiguredVideoConferenceMuteEnabledValue(); + static GpoRuleConfigured GetConfiguredZoomItEnabledValue(); static GpoRuleConfigured GetConfiguredPeekEnabledValue(); static GpoRuleConfigured GetDisableNewUpdateToastValue(); static GpoRuleConfigured GetDisableAutomaticUpdateDownloadValue(); diff --git a/src/common/ManagedCommon/ModuleType.cs b/src/common/ManagedCommon/ModuleType.cs index 7a1913c05c73..5b95af43d83b 100644 --- a/src/common/ManagedCommon/ModuleType.cs +++ b/src/common/ManagedCommon/ModuleType.cs @@ -32,5 +32,6 @@ public enum ModuleType ShortcutGuide, PowerOCR, Workspaces, + ZoomIt, } } diff --git a/src/common/SettingsAPI/settings_objects.cpp b/src/common/SettingsAPI/settings_objects.cpp index 6f3aa4c792dc..f9a72cee6244 100644 --- a/src/common/SettingsAPI/settings_objects.cpp +++ b/src/common/SettingsAPI/settings_objects.cpp @@ -331,6 +331,15 @@ namespace PowerToysSettings return static_cast(m_json.GetNamedObject(L"properties").GetNamedObject(property_name).GetNamedNumber(L"value")); } + std::optional PowerToyValues::get_uint_value(std::wstring_view property_name) const + { + if (!has_property(m_json, property_name, json::JsonValueType::Number)) + { + return std::nullopt; + } + return static_cast(m_json.GetNamedObject(L"properties").GetNamedObject(property_name).GetNamedNumber(L"value")); + } + std::optional PowerToyValues::get_string_value(std::wstring_view property_name) const { if (!has_property(m_json, property_name, json::JsonValueType::String)) diff --git a/src/common/SettingsAPI/settings_objects.h b/src/common/SettingsAPI/settings_objects.h index 1c84ac19b7d5..84b064d5af55 100644 --- a/src/common/SettingsAPI/settings_objects.h +++ b/src/common/SettingsAPI/settings_objects.h @@ -83,6 +83,7 @@ namespace PowerToysSettings std::optional get_bool_value(std::wstring_view property_name) const; std::optional get_int_value(std::wstring_view property_name) const; + std::optional get_uint_value(std::wstring_view property_name) const; std::optional get_string_value(std::wstring_view property_name) const; std::optional get_json(std::wstring_view property_name) const; json::JsonObject get_raw_json(); diff --git a/src/common/interop/shared_constants.h b/src/common/interop/shared_constants.h index d16f49d8f1a9..1c4808ad6a21 100644 --- a/src/common/interop/shared_constants.h +++ b/src/common/interop/shared_constants.h @@ -124,6 +124,10 @@ namespace CommonSharedConstants const wchar_t SHOW_ENVIRONMENT_VARIABLES_EVENT[] = L"Local\\PowerToysEnvironmentVariables-ShowEnvironmentVariablesEvent-1021f616-e951-4d64-b231-a8f972159978"; const wchar_t SHOW_ENVIRONMENT_VARIABLES_ADMIN_EVENT[] = L"Local\\PowerToysEnvironmentVariables-EnvironmentVariablesAdminEvent-8c95d2ad-047c-49a2-9e8b-b4656326cfb2"; + // Path to the events used by ZoomIt + const wchar_t ZOOMIT_REFRESH_SETTINGS_EVENT[] = L"Local\\PowerToysZoomIt-RefreshSettingsEvent-f053a563-d519-4b0d-8152-a54489c13324"; + const wchar_t ZOOMIT_EXIT_EVENT[] = L"Local\\PowerToysZoomIt-ExitEvent-36641ce6-df02-4eac-abea-a3fbf9138220"; + // Max DWORD for key code to disable keys. const DWORD VK_DISABLED = 0x100; } diff --git a/src/common/logger/logger_settings.h b/src/common/logger/logger_settings.h index e20bc999d9c5..dab7ad64a22e 100644 --- a/src/common/logger/logger_settings.h +++ b/src/common/logger/logger_settings.h @@ -76,6 +76,7 @@ struct LogSettings inline const static std::wstring workspacesWindowArrangerLogPath = L"workspaces-window-arranger-log.txt"; inline const static std::string workspacesSnapshotToolLoggerName = "workspaces-snapshot-tool"; inline const static std::wstring workspacesSnapshotToolLogPath = L"workspaces-snapshot-tool-log.txt"; + inline const static std::string zoomItLoggerName = "zoom-it"; inline const static int retention = 30; std::wstring logLevel; LogSettings(); diff --git a/src/common/sysinternals/Eula/Eula.txt b/src/common/sysinternals/Eula/Eula.txt new file mode 100644 index 000000000000..8efa71167cc3 --- /dev/null +++ b/src/common/sysinternals/Eula/Eula.txt @@ -0,0 +1,75 @@ +Sysinternals Software License Terms +These license terms are an agreement between Sysinternals (a wholly owned subsidiary of Microsoft Corporation) and you. Please read them. They apply to the software you are downloading from technet.microsoft.com/sysinternals, which includes the media on which you received it, if any. The terms also apply to any Sysinternals +* updates, +* supplements, +* Internet-based services, +* and support services +for this software, unless other terms accompany those items. If so, those terms apply. +BY USING THE SOFTWARE, YOU ACCEPT THESE TERMS. IF YOU DO NOT ACCEPT THEM, DO NOT USE THE SOFTWARE. +If you comply with these license terms, you have the rights below. + +Installation and User Rights + +You may install and use any number of copies of the software on your devices. + +Scope of License + +The software is licensed, not sold. This agreement only gives you some rights to use the software. Sysinternals reserves all other rights. Unless applicable law gives you more rights despite this limitation, you may use the software only as expressly permitted in this agreement. In doing so, you must comply with any technical limitations in the software that only allow you to use it in certain ways. You may not +* work around any technical limitations in the software; +* reverse engineer, decompile or disassemble the software, except and only to the extent that applicable law expressly permits, despite this limitation; +* make more copies of the software than specified in this agreement or allowed by applicable law, despite this limitation; +* publish the software for others to copy; +* rent, lease or lend the software; +* transfer the software or this agreement to any third party; or +* use the software for commercial software hosting services. + +Sensitive Information + +Please be aware that, similar to other debug tools that capture “process state” information, files saved by Sysinternals tools may include personally identifiable or other sensitive information (such as usernames, passwords, paths to files accessed, and paths to registry accessed). By using this software, you acknowledge that you are aware of this and take sole responsibility for any personally identifiable or other sensitive information provided to Microsoft or any other party through your use of the software. + +Documentation + +Any person that has valid access to your computer or internal network may copy and use the documentation for your internal, reference purposes. + +Export Restrictions + +The software is subject to United States export laws and regulations. You must comply with all domestic and international export laws and regulations that apply to the software. These laws include restrictions on destinations, end users and end use. For additional information, see www.microsoft.com/exporting . + +Support Services + +Because this software is "as is," we may not provide support services for it. + +Entire Agreement + +This agreement, and the terms for supplements, updates, Internet-based services and support services that you use, are the entire agreement for the software and support services. + +Applicable Law + +United States . If you acquired the software in the United States , Washington state law governs the interpretation of this agreement and applies to claims for breach of it, regardless of conflict of laws principles. The laws of the state where you live govern all other claims, including claims under state consumer protection laws, unfair competition laws, and in tort. +Outside the United States . If you acquired the software in any other country, the laws of that country apply. + +Legal Effect + +This agreement describes certain legal rights. You may have other rights under the laws of your country. You may also have rights with respect to the party from whom you acquired the software. This agreement does not change your rights under the laws of your country if the laws of your country do not permit it to do so. + +Disclaimer of Warranty + +The software is licensed "as-is." You bear the risk of using it. Sysinternals gives no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this agreement cannot change. To the extent permitted under your local laws, sysinternals excludes the implied warranties of merchantability, fitness for a particular purpose and non-infringement. + +Limitation on and Exclusion of Remedies and Damages + +You can recover from sysinternals and its suppliers only direct damages up to U.S. $5.00. You cannot recover any other damages, including consequential, lost profits, special, indirect or incidental damages. +This limitation applies to +* anything related to the software, services, content (including code) on third party Internet sites, or third party programs; and +* claims for breach of contract, breach of warranty, guarantee or condition, strict liability, negligence, or other tort to the extent permitted by applicable law. + +It also applies even if Sysinternals knew or should have known about the possibility of the damages. The above limitation or exclusion may not apply to you because your country may not allow the exclusion or limitation of incidental, consequential or other damages. +Please note: As this software is distributed in Quebec , Canada , some of the clauses in this agreement are provided below in French. +Remarque : Ce logiciel étant distribué au Québec, Canada, certaines des clauses dans ce contrat sont fournies ci-dessous en français. +EXONÉRATION DE GARANTIE. Le logiciel visé par une licence est offert « tel quel ». Toute utilisation de ce logiciel est à votre seule risque et péril. Sysinternals n'accorde aucune autre garantie expresse. Vous pouvez bénéficier de droits additionnels en vertu du droit local sur la protection dues consommateurs, que ce contrat ne peut modifier. La ou elles sont permises par le droit locale, les garanties implicites de qualité marchande, d'adéquation à un usage particulier et d'absence de contrefaçon sont exclues. +LIMITATION DES DOMMAGES-INTÉRÊTS ET EXCLUSION DE RESPONSABILITÉ POUR LES DOMMAGES. Vous pouvez obtenir de Sysinternals et de ses fournisseurs une indemnisation en cas de dommages directs uniquement à hauteur de 5,00 $ US. Vous ne pouvez prétendre à aucune indemnisation pour les autres dommages, y compris les dommages spéciaux, indirects ou accessoires et pertes de bénéfices. +Cette limitation concerne : +tout ce qui est relié au logiciel, aux services ou au contenu (y compris le code) figurant sur des sites Internet tiers ou dans des programmes tiers ; et +les réclamations au titre de violation de contrat ou de garantie, ou au titre de responsabilité stricte, de négligence ou d'une autre faute dans la limite autorisée par la loi en vigueur. +Elle s'applique également, même si Sysinternals connaissait ou devrait connaître l'éventualité d'un tel dommage. Si votre pays n'autorise pas l'exclusion ou la limitation de responsabilité pour les dommages indirects, accessoires ou de quelque nature que ce soit, il se peut que la limitation ou l'exclusion ci-dessus ne s'appliquera pas à votre égard. +EFFET JURIDIQUE. Le présent contrat décrit certains droits juridiques. Vous pourriez avoir d'autres droits prévus par les lois de votre pays. Le présent contrat ne modifie pas les droits que vous confèrent les lois de votre pays si celles-ci ne le permettent pas. diff --git a/src/common/sysinternals/Eula/eula.c b/src/common/sysinternals/Eula/eula.c new file mode 100644 index 000000000000..eaf35e9be54c --- /dev/null +++ b/src/common/sysinternals/Eula/eula.c @@ -0,0 +1,702 @@ +#pragma once + +#pragma warning( disable: 4996) + +#include +#include +#include +#include +#include +#include +#include +#include "Eula.h" +#include "dll.h" + +#define IDC_TEXT 500 +#define IDC_PRINT 501 +#define IDC_TEXT1 502 + +static const char * EulaText[] = { +"{\\rtf1\\ansi\\ansicpg1252\\deff0\\nouicompat\\deflang1033{\\fonttbl{\\f0\\fswiss\\fprq2\\fcharset0 Tahoma;}{\\f1\\fnil\\fcharset0 Calibri;}}", +"{\\colortbl ;\\red0\\green0\\blue255;\\red0\\green0\\blue0;}", +"{\\*\\generator Riched20 10.0.10240}\\viewkind4\\uc1 ", +"\\pard\\brdrb\\brdrs\\brdrw10\\brsp20 \\sb120\\sa120\\b\\f0\\fs24 SYSINTERNALS SOFTWARE LICENSE TERMS\\fs28\\par", +"\\pard\\sb120\\sa120\\b0\\fs19 These license terms are an agreement between Sysinternals (a wholly owned subsidiary of Microsoft Corporation) and you. Please read them. They apply to the software you are downloading from Sysinternals.com, which includes the media on which you received it, if any. The terms also apply to any Sysinternals\\par", +"\\pard\\fi-363\\li720\\sb120\\sa120\\tx720\\'b7\\tab updates,\\par", +"\\pard\\fi-363\\li720\\sb120\\sa120\\'b7\\tab supplements,\\par", +"\\'b7\\tab Internet-based services, and \\par", +"\\'b7\\tab support services\\par", +"\\pard\\sb120\\sa120 for this software, unless other terms accompany those items. If so, those terms apply.\\par", +"\\b BY USING THE SOFTWARE, YOU ACCEPT THESE TERMS. IF YOU DO NOT ACCEPT THEM, DO NOT USE THE SOFTWARE.\\par", +"\\pard\\brdrt\\brdrs\\brdrw10\\brsp20 \\sb120\\sa120 If you comply with these license terms, you have the rights below.\\par", +"\\pard\\fi-357\\li357\\sb120\\sa120\\tx360\\fs20 1.\\tab\\fs19 INSTALLATION AND USE RIGHTS. \\b0 You may install and use any number of copies of the software on your devices.\\b\\par", +"\\caps\\fs20 2.\\tab\\fs19 Scope of License\\caps0 .\\b0 The software is licensed, not sold. This agreement only gives you some rights to use the software. Sysinternals reserves all other rights. Unless applicable law gives you more rights despite this limitation, you may use the software only as expressly permitted in this agreement. In doing so, you must comply with any technical limitations in the software that only allow you to use it in certain ways. You may not\\b\\par", +"\\pard\\fi-363\\li720\\sb120\\sa120\\tx720\\b0\\'b7\\tab work around any technical limitations in the binary versions of the software;\\par", +"\\pard\\fi-363\\li720\\sb120\\sa120\\'b7\\tab reverse engineer, decompile or disassemble the binary versions of the software, except and only to the extent that applicable law expressly permits, despite this limitation;\\par", +"\\'b7\\tab make more copies of the software than specified in this agreement or allowed by applicable law, despite this limitation;\\par", +"\\'b7\\tab publish the software for others to copy;\\par", +"\\'b7\\tab rent, lease or lend the software;\\par", +"\\'b7\\tab transfer the software or this agreement to any third party; or\\par", +"\\'b7\\tab use the software for commercial software hosting services.\\par", +"\\pard\\fi-357\\li357\\sb120\\sa120\\tx360\\b\\fs20 3.\\tab SENSITIVE INFORMATION. \\b0 Please be aware that, similar to other debug tools that capture \\ldblquote process state\\rdblquote information, files saved by Sysinternals tools may include personally identifiable or other sensitive information (such as usernames, passwords, paths to files accessed, and paths to registry accessed). By using this software, you acknowledge that you are aware of this and take sole responsibility for any personally identifiable or other sensitive information provided to Microsoft or any other party through your use of the software.\\b\\par", +"5. \\tab\\fs19 DOCUMENTATION.\\b0 Any person that has valid access to your computer or internal network may copy and use the documentation for your internal, reference purposes.\\b\\par", +"\\caps\\fs20 6.\\tab\\fs19 Export Restrictions\\caps0 .\\b0 The software is subject to United States export laws and regulations. You must comply with all domestic and international export laws and regulations that apply to the software. These laws include restrictions on destinations, end users and end use. For additional information, see {\\cf1\\ul{\\field{\\*\\fldinst{HYPERLINK www.microsoft.com/exporting }}{\\fldrslt{www.microsoft.com/exporting}}}}\\cf1\\ul\\f0\\fs19 <{{\\field{\\*\\fldinst{HYPERLINK \"http://www.microsoft.com/exporting\"}}{\\fldrslt{http://www.microsoft.com/exporting}}}}\\f0\\fs19 >\\cf0\\ulnone .\\b\\par", +"\\caps\\fs20 7.\\tab\\fs19 SUPPORT SERVICES.\\caps0 \\b0 Because this software is \"as is, \" we may not provide support services for it.\\b\\par", +"\\caps\\fs20 8.\\tab\\fs19 Entire Agreement.\\b0\\caps0 This agreement, and the terms for supplements, updates, Internet-based services and support services that you use, are the entire agreement for the software and support services.\\par", +"\\pard\\keepn\\fi-360\\li360\\sb120\\sa120\\tx360\\cf2\\b\\caps\\fs20 9.\\tab\\fs19 Applicable Law\\caps0 .\\par", +"\\pard\\fi-363\\li720\\sb120\\sa120\\tx720\\cf0\\fs20 a.\\tab\\fs19 United States.\\b0 If you acquired the software in the United States, Washington state law governs the interpretation of this agreement and applies to claims for breach of it, regardless of conflict of laws principles. The laws of the state where you live govern all other claims, including claims under state consumer protection laws, unfair competition laws, and in tort.\\b\\par", +"\\pard\\fi-363\\li720\\sb120\\sa120\\fs20 b.\\tab\\fs19 Outside the United States.\\b0 If you acquired the software in any other country, the laws of that country apply.\\b\\par", +"\\pard\\fi-357\\li357\\sb120\\sa120\\tx360\\caps\\fs20 10.\\tab\\fs19 Legal Effect.\\b0\\caps0 This agreement describes certain legal rights. You may have other rights under the laws of your country. You may also have rights with respect to the party from whom you acquired the software. This agreement does not change your rights under the laws of your country if the laws of your country do not permit it to do so.\\b\\caps\\par", +"\\fs20 11.\\tab\\fs19 Disclaimer of Warranty.\\caps0 \\caps The software is licensed \"as - is.\" You bear the risk of using it. SYSINTERNALS gives no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this agreement cannot change. To the extent permitted under your local laws, SYSINTERNALS excludes the implied warranties of merchantability, fitness for a particular purpose and non-infringement.\\par", +"\\pard\\fi-360\\li360\\sb120\\sa120\\tx360\\fs20 12.\\tab\\fs19 Limitation on and Exclusion of Remedies and Damages. You can recover from SYSINTERNALS and its suppliers only direct damages up to U.S. $5.00. You cannot recover any other damages, including consequential, lost profits, special, indirect or incidental damages.\\par", +"\\pard\\li357\\sb120\\sa120\\b0\\caps0 This limitation applies to\\par", +"\\pard\\fi-363\\li720\\sb120\\sa120\\tx720\\'b7\\tab anything related to the software, services, content (including code) on third party Internet sites, or third party programs; and\\par", +"\\pard\\fi-363\\li720\\sb120\\sa120\\'b7\\tab claims for breach of contract, breach of warranty, guarantee or condition, strict liability, negligence, or other tort to the extent permitted by applicable law.\\par", +"\\pard\\li360\\sb120\\sa120 It also applies even if Sysinternals knew or should have known about the possibility of the damages. The above limitation or exclusion may not apply to you because your country may not allow the exclusion or limitation of incidental, consequential or other damages.\\par", +"\\pard\\b Please note: As this software is distributed in Quebec, Canada, some of the clauses in this agreement are provided below in French.\\par", +"\\pard\\sb240\\lang1036 Remarque : Ce logiciel \\'e9tant distribu\\'e9 au Qu\\'e9bec, Canada, certaines des clauses dans ce contrat sont fournies ci-dessous en fran\\'e7ais.\\par", +"\\pard\\sb120\\sa120 EXON\\'c9RATION DE GARANTIE.\\b0 Le logiciel vis\\'e9 par une licence est offert \\'ab tel quel \\'bb. Toute utilisation de ce logiciel est \\'e0 votre seule risque et p\\'e9ril. Sysinternals n'accorde aucune autre garantie expresse. Vous pouvez b\\'e9n\\'e9ficier de droits additionnels en vertu du droit local sur la protection dues consommateurs, que ce contrat ne peut modifier. La ou elles sont permises par le droit locale, les garanties implicites de qualit\\'e9 marchande, d'ad\\'e9quation \\'e0 un usage particulier et d'absence de contrefa\\'e7on sont exclues.\\par", +"\\pard\\keepn\\sb120\\sa120\\b LIMITATION DES DOMMAGES-INT\\'c9R\\'caTS ET EXCLUSION DE RESPONSABILIT\\'c9 POUR LES DOMMAGES.\\b0 Vous pouvez obtenir de Sysinternals et de ses fournisseurs une indemnisation en cas de dommages directs uniquement \\'e0 hauteur de 5,00 $ US. Vous ne pouvez pr\\'e9tendre \\'e0 aucune indemnisation pour les autres dommages, y compris les dommages sp\\'e9ciaux, indirects ou accessoires et pertes de b\\'e9n\\'e9fices.\\par", +"\\lang1033 Cette limitation concerne :\\par", +"\\pard\\keepn\\fi-360\\li720\\sb120\\sa120\\tx720\\lang1036\\'b7\\tab tout ce qui est reli\\'e9 au logiciel, aux services ou au contenu (y compris le code) figurant sur des sites Internet tiers ou dans des programmes tiers ; et\\par", +"\\pard\\fi-363\\li720\\sb120\\sa120\\tx720\\'b7\\tab les r\\'e9clamations au titre de violation de contrat ou de garantie, ou au titre de responsabilit\\'e9 stricte, de n\\'e9gligence ou d'une autre faute dans la limite autoris\\'e9e par la loi en vigueur.\\par", +"\\pard\\sb120\\sa120 Elle s'applique \\'e9galement, m\\'eame si Sysinternals connaissait ou devrait conna\\'eetre l'\\'e9ventualit\\'e9 d'un tel dommage. Si votre pays n'autorise pas l'exclusion ou la limitation de responsabilit\\'e9 pour les dommages indirects, accessoires ou de quelque nature que ce soit, il se peut que la limitation ou l'exclusion ci-dessus ne s'appliquera pas \\'e0 votre \\'e9gard.\\par", +"\\b EFFET JURIDIQUE.\\b0 Le pr\\'e9sent contrat d\\'e9crit certains droits juridiques. Vous pourriez avoir d'autres droits pr\\'e9vus par les lois de votre pays. Le pr\\'e9sent contrat ne modifie pas les droits que vous conf\\'e8rent les lois de votre pays si celles-ci ne le permettent pas.\\b\\par", +"\\pard\\b0\\fs20\\lang1033\\par", +"\\pard\\sa200\\sl276\\slmult1\\f1\\fs22\\lang9\\par", +"}", +NULL +}; + +static const wchar_t *Raw_EulaText = L"SYSINTERNALS SOFTWARE LICENSE TERMS\nThese license terms are an agreement between Sysinternals(a wholly owned subsidiary of Microsoft Corporation) and you.Please read them.They apply to the software you are downloading from technet.microsoft.com / sysinternals, which includes the media on which you received it, if any.The terms also apply to any Sysinternals\n* updates,\n*supplements,\n*Internet - based services,\n*and support services\nfor this software, unless other terms accompany those items.If so, those terms apply.\nBY USING THE SOFTWARE, YOU ACCEPT THESE TERMS.IF YOU DO NOT ACCEPT THEM, DO NOT USE THE SOFTWARE.\n\nIf you comply with these license terms, you have the rights below.\nINSTALLATION AND USER RIGHTS\nYou may install and use any number of copies of the software on your devices.\n\nSCOPE OF LICENSE\nThe software is licensed, not sold.This agreement only gives you some rights to use the software.Sysinternals reserves all other rights.Unless applicable law gives you more rights despite this limitation, you may use the software only as expressly permitted in this agreement.In doing so, you must comply with any technical limitations in the software that only allow you to use it in certain ways.You may not\n* work around any technical limitations in the software;\n*reverse engineer, decompile or disassemble the software, except and only to the extent that applicable law expressly permits, despite this limitation;\n*make more copies of the software than specified in this agreement or allowed by applicable law, despite this limitation;\n*publish the software for others to copy;\n*rent, lease or lend the software;\n*transfer the software or this agreement to any third party; or\n* use the software for commercial software hosting services.\n\nSENSITIVE INFORMATION\nPlease be aware that, similar to other debug tools that capture “process state” information, files saved by Sysinternals tools may include personally identifiable or other sensitive information(such as usernames, passwords, paths to files accessed, and paths to registry accessed).By using this software, you acknowledge that you are aware of this and take sole responsibility for any personally identifiable or other sensitive information provided to Microsoft or any other party through your use of the software.\n\nDOCUMENTATION\nAny person that has valid access to your computer or internal network may copy and use the documentation for your internal, reference purposes.\n\nEXPORT RESTRICTIONS\nThe software is subject to United States export laws and regulations.You must comply with all domestic and international export laws and regulations that apply to the software.These laws include restrictions on destinations, end users and end use.For additional information, see www.microsoft.com / exporting .\n\nSUPPORT SERVICES\nBecause this software is \"as is, \" we may not provide support services for it.\n\nENTIRE AGREEMENT\nThis agreement, and the terms for supplements, updates, Internet - based services and support services that you use, are the entire agreement for the software and support services.\n\nAPPLICABLE LAW\nUnited States.If you acquired the software in the United States, Washington state law governs the interpretation of this agreement and applies to claims for breach of it, regardless of conflict of laws principles.The laws of the state where you live govern all other claims, including claims under state consumer protection laws, unfair competition laws, and in tort.\nOutside the United States.If you acquired the software in any other country, the laws of that country apply.\n\nLEGAL EFFECT\nThis agreement describes certain legal rights.You may have other rights under the laws of your country.You may also have rights with respect to the party from whom you acquired the software.This agreement does not change your rights under the laws of your country if the laws of your country do not permit it to do so.\n\nDISCLAIMER OF WARRANTY\nThe software is licensed \"as - is.\" You bear the risk of using it.Sysinternals gives no express warranties, guarantees or conditions.You may have additional consumer rights under your local laws which this agreement cannot change.To the extent permitted under your local laws, sysinternals excludes the implied warranties of merchantability, fitness for a particular purpose and non - infringement.\n\nLIMITATION ON AND EXCLUSION OF REMEDIES AND DAMAGES\nYou can recover from sysinternals and its suppliers only direct damages up to U.S.$5.00.You cannot recover any other damages, including consequential, lost profits, special, indirect or incidental damages.\nThis limitation applies to\n* anything related to the software, services, content(including code) on third party Internet sites, or third party programs; and\n* claims for breach of contract, breach of warranty, guarantee or condition, strict liability, negligence, or other tort to the extent permitted by applicable law.\nIt also applies even if Sysinternals knew or should have known about the possibility of the damages.The above limitation or exclusion may not apply to you because your country may not allow the exclusion or limitation of incidental, consequential or other damages.\nPlease note : As this software is distributed in Quebec, Canada, some of the clauses in this agreement are provided below in French.\nRemarque : Ce logiciel étant distribué au Québec, Canada, certaines des clauses dans ce contrat sont fournies ci - dessous en français.\n EXONÉRATION DE GARANTIE.Le logiciel visé par une licence est offert « tel quel ».Toute utilisation de ce logiciel est à votre seule risque et péril.Sysinternals n'accorde aucune autre garantie expresse. Vous pouvez bénéficier de droits additionnels en vertu du droit local sur la protection dues consommateurs, que ce contrat ne peut modifier. La ou elles sont permises par le droit locale, les garanties implicites de qualité marchande, d'adéquation à un usage particulier et d'absence de contrefaçon sont exclues.\n LIMITATION DES DOMMAGES - INTÉRÊTS ET EXCLUSION DE RESPONSABILITÉ POUR LES DOMMAGES.Vous pouvez obtenir de Sysinternals et de ses fournisseurs une indemnisation en cas de dommages directs uniquement à hauteur de 5, 00 $ US.Vous ne pouvez prétendre à aucune indemnisation pour les autres dommages, y compris les dommages spéciaux, indirects ou accessoires et pertes de bénéfices.\n\n Cette limitation concerne :\ntout ce qui est relié au logiciel, aux services ou au contenu(y compris le code) figurant sur des sites Internet tiers ou dans des programmes tiers; et\nles réclamations au titre de violation de contrat ou de garantie, ou au titre de responsabilité stricte, de négligence ou d'une autre faute dans la limite autorisée par la loi en vigueur.\n\nElle s'applique également, même si Sysinternals connaissait ou devrait connaître l'éventualité d'un tel dommage. Si votre pays n'autorise pas l'exclusion ou la limitation de responsabilité pour les dommages indirects, accessoires ou de quelque nature que ce soit, il se peut que la limitation ou l'exclusion ci - dessus ne s'appliquera pas à votre égard.\nEFFET JURIDIQUE.Le présent contrat décrit certains droits juridiques.Vous pourriez avoir d'autres droits prévus par les lois de votre pays. Le présent contrat ne modifie pas les droits que vous confèrent les lois de votre pays si celles-ci ne le permettent pas.\n\n"; + +BOOL IsEulaRegkeyAdded(const TCHAR * ToolName); + +static BOOL EulaCenter( HWND hwndChild, HWND hwndParent ) +{ + RECT rcChild, rcParent; + int cxChild, cyChild, cxParent, cyParent; + int cxScreen, cyScreen, xNew, yNew; + HDC hdc; + + // Get the Height and Width of the child window + GetWindowRect(hwndChild, &rcChild); + cxChild = rcChild.right - rcChild.left; + cyChild = rcChild.bottom - rcChild.top; + + // Get the Height and Width of the parent window + GetWindowRect(hwndParent, &rcParent); + cxParent = rcParent.right - rcParent.left; + cyParent = rcParent.bottom - rcParent.top; + + // Get the display limits + hdc = GetDC(hwndChild); + cxScreen = GetDeviceCaps(hdc, HORZRES); + cyScreen = GetDeviceCaps(hdc, VERTRES); + ReleaseDC(hwndChild, hdc); + + // Calculate new X position, then adjust for screen + xNew = rcParent.left + ((cxParent - cxChild) / 2); + if (xNew < 0) + { + xNew = 0; + } + else if ((xNew + cxChild) > cxScreen) + { + xNew = cxScreen - cxChild; + } + + // Calculate new Y position, then adjust for screen + yNew = rcParent.top + ((cyParent - cyChild) / 2); + if (yNew < 0) + { + yNew = 0; + } + else if ((yNew + cyChild) > cyScreen) + { + yNew = cyScreen - cyChild; + } + + // Set it, and return + return SetWindowPos(hwndChild, + NULL, + xNew, yNew, + 0, 0, + SWP_NOSIZE | SWP_NOZORDER); +} + + + +static BOOL PrintRichedit( HWND hRichedit ) +{ + // Get the printer. + PRINTDLG pd = { 0 }; + + pd.lStructSize = sizeof pd; + pd.hwndOwner = hRichedit; + pd.hInstance = GetModuleHandle(NULL); + pd.Flags = PD_RETURNDC | PD_NOPAGENUMS | PD_NOSELECTION | PD_PRINTSETUP; + if ( !PrintDlg( &pd ) ) + return FALSE; + + { + HCURSOR oldCursor = SetCursor( LoadCursor( NULL, IDC_WAIT ) ); + int nHorzRes = GetDeviceCaps( pd.hDC, HORZRES ); + int nVertRes = GetDeviceCaps( pd.hDC, VERTRES ); + int nLogPixelsX = GetDeviceCaps( pd.hDC, LOGPIXELSX ); + int nLogPixelsY = GetDeviceCaps( pd.hDC, LOGPIXELSY ); + FORMATRANGE fr = { 0 }; + DOCINFO di = { 0 }; + int TotalLength; + + // Ensure the printer DC is in MM_TEXT mode. + SetMapMode( pd.hDC, MM_TEXT ); + + // Rendering to the same DC we are measuring. + fr.hdc = pd.hDC; + fr.hdcTarget = pd.hDC; + + // Set up the page. + fr.rcPage.top = 0; + fr.rcPage.left = 0; + fr.rcPage.bottom = (nVertRes/nLogPixelsY) * 1440; + fr.rcPage.right = (nHorzRes/nLogPixelsX) * 1440; + + // Set up 1" margins all around. + fr.rc = fr.rcPage; + InflateRect( &fr.rc, -1440, -1440 ); + + // Default the range of text to print as the entire document. + fr.chrg.cpMin = 0; + fr.chrg.cpMax = -1; + + // Set up the print job (standard printing stuff here). + di.cbSize = sizeof di; + di.lpszDocName = _T("Sysinternals License"); + + // Start the document. + StartDoc( pd.hDC, &di ); + + // Find out real size of document in characters. + TotalLength = (int) SendMessage ( hRichedit, WM_GETTEXTLENGTH, 0, 0 ); + for (;;) { + int NextPage; + + // Start the page. + StartPage( pd.hDC ); + + // Print as much text as can fit on a page. The return value is + // the index of the first character on the next page. + NextPage = (int) SendMessage( hRichedit, EM_FORMATRANGE, TRUE, (LPARAM)&fr ); + + // Print last page. + EndPage( pd.hDC ); + + if ( NextPage >= TotalLength ) + break; + + // Adjust the range of characters to start printing at the first character of the next page. + fr.chrg.cpMin = NextPage; + fr.chrg.cpMax = -1; + } + + // Tell the control to release cached information. + SendMessage( hRichedit, EM_FORMATRANGE, 0, (LPARAM)NULL ); + EndDoc( pd.hDC ); + + SetCursor( oldCursor ); + } + + return TRUE; +} + +// combine all text strings into a single string +char * GetEulaText() +{ + char * text; + DWORD len = 1; + int i; + for ( i = 0; EulaText[i]; ++i ) + len += (DWORD) strlen( EulaText[i] ); + text = (char *) malloc( len ); + len = 0; + for ( i = 0; EulaText[i]; ++i ) { + strcpy( text+len, EulaText[i] ); + len += (DWORD) strlen( EulaText[i] ); + } + text[len] = 0; + return text; +} + +DWORD CALLBACK StreamCallback( DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG * pcb ) +{ + const char ** ptr = (const char **) dwCookie; + LONG_PTR len = strlen(*ptr); + if ( cb > len ) + cb = (int) len; + memcpy( pbBuff, *ptr, cb ); + *pcb = cb; + *ptr += cb; + return 0; +} + +static INT_PTR CALLBACK EulaProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + switch ( uMsg ) { + case WM_INITDIALOG: + { + TCHAR title[MAX_PATH]; + char * text = GetEulaText(); + char * textptr = text; + EDITSTREAM stream = { 0, 0, StreamCallback }; + stream.dwCookie = (DWORD_PTR) &textptr; + _stprintf_s( title, MAX_PATH, _T("%s License Agreement"), (TCHAR *) lParam ); + SetWindowText( hwndDlg, title ); + + // enter RTF into edit box + SendMessage( GetDlgItem(hwndDlg,IDC_TEXT), EM_EXLIMITTEXT, 0, 1024*1024 ); + SendMessage( GetDlgItem(hwndDlg,IDC_TEXT), EM_STREAMIN, SF_RTF, (LPARAM)&stream ); + free( text ); + } + return TRUE; + + case WM_CTLCOLORSTATIC: + // force background of read-only text window to be white + if ( (HWND)lParam == GetDlgItem( hwndDlg, IDC_TEXT) ) { + return (INT_PTR)GetSysColorBrush( COLOR_WINDOW ); + } + break; + + case WM_COMMAND: + switch( LOWORD( wParam )) { + case IDOK: + EndDialog( hwndDlg, TRUE ); + return TRUE; + case IDCANCEL: + EndDialog( hwndDlg, FALSE ); + return TRUE; + case IDC_PRINT: + PrintRichedit( GetDlgItem(hwndDlg,IDC_TEXT) ); + return TRUE; + } + break; + } + return FALSE; +} + + +static WORD * Align2( WORD * pos ) +{ + return (WORD *)(((DWORD_PTR)pos + 1) & ~((DWORD_PTR) 1)); +} +static WORD * Align4( WORD * pos ) +{ + return (WORD *)(((DWORD_PTR)pos + 3) & ~((DWORD_PTR) 3)); +} + +static int CopyText( WORD * pos, const WCHAR * text ) +{ + int len = (int) wcslen( text ) + 1; + wcscpy( (PWCHAR) pos, text ); + return len; +} + +BOOL ShowEulaInternal( const TCHAR * ToolName, DWORD eulaAccepted ) +{ +#if !defined(SYSMON_SHARED) + HKEY hKey = NULL; + TCHAR keyName[MAX_PATH]; + + _stprintf_s( keyName, MAX_PATH, _T("Software\\Sysinternals\\%s"), ToolName ); + + // + // check the regkey value if no -accepteula switch append + // + if (!eulaAccepted) + { + eulaAccepted = IsEulaRegkeyAdded(ToolName); + } +#endif + + if( !eulaAccepted ) { + if (IsIoTEdition()) + { + eulaAccepted = ShowEulaConsole(); // display Eula to console and prompt for Eula Accepted. + { + } + } + else if (IsRemoteOnlyEdition() || IsRunningRemotely()) // Nano and in remote session will not be able to accept eula from prompt + { + ShowEulaConsoleNoPrompt(); + } + else + { + DLGTEMPLATE * dlg = (DLGTEMPLATE *)LocalAlloc(LPTR, 1000); + WORD * extra = (WORD *)(dlg + 1); + DLGITEMTEMPLATE * item; + +#if defined(SYSMON_SHARED) + printf( "Displaying EULA Gui dialog box ... (use -accepteula to avoid).\n" ); +#endif + + LoadLibrarySafe(_T("Riched32.dll"), DLL_LOAD_LOCATION_SYSTEM ); // Richedit 1.0 library + + // header + dlg->style = DS_MODALFRAME | DS_CENTER | DS_SETFONT | WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_NOFAILCREATE; + dlg->x = 0; + dlg->y = 0; + dlg->cx = 312; + dlg->cy = 180; + dlg->cdit = 0; // number of controls + + *extra++ = 0; // menu + *extra++ = 0; // class + extra += CopyText(extra, L"License Agreement"); + *extra++ = 8; // font size + extra += CopyText(extra, L"MS Shell Dlg"); + + // Command-line message + item = (DLGITEMTEMPLATE *)Align4(extra); + item->x = 7; + item->y = 3; + item->cx = 298; + item->cy = 14; + item->id = IDC_TEXT1; + item->style = WS_CHILD | WS_VISIBLE; + extra = (WORD *)(item + 1); + *extra++ = 0xFFFF; // class is ordinal + *extra++ = 0x0082; // class is static + extra += CopyText(extra, L"You can also use the /accepteula command-line switch to accept the EULA."); + *extra++ = 0; // creation data + dlg->cdit++; + + // Agree button + item = (DLGITEMTEMPLATE *)Align4(extra); + item->x = 201; + item->y = 159; + item->cx = 50; + item->cy = 14; + item->id = IDOK; + item->style = BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP; // | WS_DEFAULT; + extra = (WORD *)(item + 1); + *extra++ = 0xFFFF; // class is ordinal + *extra++ = 0x0080; // class is button + extra += CopyText(extra, L"&Agree"); + *extra++ = 0; // creation data + dlg->cdit++; + + // Decline button + item = (DLGITEMTEMPLATE *)Align4(extra); + item->x = 255; + item->y = 159; + item->cx = 50; + item->cy = 14; + item->id = IDCANCEL; + item->style = BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP; + extra = (WORD *)(item + 1); + *extra++ = 0xFFFF; // class is ordinal + *extra++ = 0x0080; // class is button + extra += CopyText(extra, L"&Decline"); + *extra++ = 0; // creation data + dlg->cdit++; + + // Print button + item = (DLGITEMTEMPLATE *)Align4(extra); + item->x = 7; + item->y = 159; + item->cx = 50; + item->cy = 14; + item->id = IDC_PRINT; + item->style = BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP; + extra = (WORD *)(item + 1); + *extra++ = 0xFFFF; // class is ordinal + *extra++ = 0x0080; // class is button + extra += CopyText(extra, L"&Print"); + *extra++ = 0; // creation data + dlg->cdit++; + + // Edit box + item = (DLGITEMTEMPLATE *)Align4(extra); + item->x = 7; + item->y = 14; + item->cx = 298; + item->cy = 140; + item->id = IDC_TEXT; + item->style = WS_BORDER | ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN | WS_VSCROLL | ES_READONLY | WS_CHILD | WS_VISIBLE | WS_TABSTOP; + extra = (WORD *)(item + 1); + extra += CopyText(extra, L"RICHEDIT"); + extra += CopyText(extra, L"&Decline"); + *extra++ = 0; // creation data + dlg->cdit++; + + eulaAccepted = (DWORD)DialogBoxIndirectParam(NULL, dlg, NULL, EulaProc, (LPARAM)ToolName); + LocalFree(dlg); + } + } +#if !defined(SYSMON_SHARED) + if ( eulaAccepted ) { + if (RegCreateKey(HKEY_CURRENT_USER, keyName, &hKey) == ERROR_SUCCESS) { + RegSetValueEx(hKey, _T("EulaAccepted"), 0, REG_DWORD, (BYTE *)&eulaAccepted, sizeof(eulaAccepted)); + RegCloseKey(hKey); + } + } +#endif + + return eulaAccepted != 0; +} + +BOOL ShowEulaW( const TCHAR * ToolName, int *argc, PWCHAR argv[] ) +{ + DWORD eulaAccepted = 0; + int i; + + if ( argc == NULL || argv == NULL ) { + typedef LPWSTR * (WINAPI * type_CommandLineToArgvW)( LPCWSTR lpCmdLine, int *pNumArgs ); + type_CommandLineToArgvW pCommandLineToArgvW = (type_CommandLineToArgvW) GetProcAddress( LoadLibrarySafe(_T("Shell32.dll"), DLL_LOAD_LOCATION_SYSTEM), "CommandLineToArgvW" ); + if ( pCommandLineToArgvW ) { + static int argc2; + argc = &argc2; + argv = (*pCommandLineToArgvW)( GetCommandLineW(), argc ); + } else { + argc = NULL; + } + } + + + // + // See if its accepted via command line switch + // + if( argc ) { + + for( i = 0; i < *argc; i++ ) { + + eulaAccepted = (!_wcsicmp( argv[i], L"/accepteula") || + !_wcsicmp( argv[i], L"-accepteula")); + if( eulaAccepted ) { + + for( ; i < *argc - 1; i++ ) { + + argv[i] = argv[i+1]; + } + (*argc)--; + break; + } + } + } + if( ShowEulaInternal( ToolName, eulaAccepted )) { + + eulaAccepted = 1; + } + return eulaAccepted != 0; +} + + +BOOL ShowEula( const TCHAR * ToolName, int *argc, PTCHAR argv[] ) +{ + DWORD eulaAccepted = 0; + int i; + + if ( argc == NULL || argv == NULL ) { + return ShowEulaW( ToolName, NULL, NULL ); + } + + // + // See if its accepted via command line switch + // + if( argc ) { + + for( i = 0; i < *argc; i++ ) { + + eulaAccepted = (!_tcsicmp( argv[i], _T("/accepteula")) || + !_tcsicmp( argv[i], _T("-accepteula"))); + if( eulaAccepted ) { + + for( ; i < *argc - 1; i++ ) { + + argv[i] = argv[i+1]; + } + (*argc)--; + break; + } + } + } + if( ShowEulaInternal( ToolName, eulaAccepted )) { + + eulaAccepted = 1; + } + return eulaAccepted != 0; +} + +// Determine whether we are on the IoT SKU by looking at the ProductName. +BOOL IsIoTEdition() +{ + HKEY hKey = NULL; + wchar_t ProductName[MAX_PATH]; + BOOL bRet = FALSE; // assume "not" IoT Edition + DWORD dwSize = sizeof(ProductName); + DWORD type = 0; + + if (ERROR_SUCCESS == RegOpenKey(HKEY_LOCAL_MACHINE, _T("Software\\Microsoft\\windows nt\\currentversion"), &hKey)) + { + if (ERROR_SUCCESS == RegQueryValueExW(hKey, L"ProductName", 0, &type, (LPBYTE)ProductName, &dwSize)) + { + if (!_wcsicmp(L"iotuap", ProductName)) + bRet = TRUE; + } + RegCloseKey(hKey); + } + + return bRet; +} + +// Determine whether we are on the remote only edition, where we cannot prompt for user input. +BOOL IsRemoteOnlyEdition() +{ + HKEY hKey = NULL; + DWORD dwNanoServer = 0; + BOOL bRet = FALSE; + DWORD dwSize = sizeof(dwNanoServer); + DWORD type = 0; + + // Currently Nano is the only remote only edtion. + if (ERROR_SUCCESS == RegOpenKey(HKEY_LOCAL_MACHINE, _T("Software\\Microsoft\\Windows NT\\CurrentVersion\\Server\\ServerLevels"), &hKey)) + { + if (ERROR_SUCCESS == RegQueryValueEx(hKey, _T("NanoServer"), 0, &type, (LPBYTE)&dwNanoServer, &dwSize)) + { + if (type == REG_DWORD && dwNanoServer == 1) + bRet = TRUE; + } + RegCloseKey(hKey); + } + + return bRet; +} + +BOOL IsRunningRemotely() +{ + // running from a remote session will not support input interaction + DWORD fileType = GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)); + return fileType == FILE_TYPE_PIPE; +} + +DWORD ShowEulaConsole() +{ + DWORD dwRet = 0; + char ch; + BOOLEAN eulaAcknowledged = FALSE; + + wprintf(Raw_EulaText); + + while( eulaAcknowledged != TRUE ) + { + printf("Accept Eula (Y/N)?"); + ch = (char) _getch(); + printf("%c\n", ch); + if ('y' == ch || 'Y' == ch) + { + dwRet = 1; // EULA Accepted. + eulaAcknowledged = TRUE; + } + + if ('n' == ch || 'N' == ch) + { + // EULA not accepted. + eulaAcknowledged = TRUE; + } + } + return dwRet; +} + +void ShowEulaConsoleNoPrompt() +{ + wprintf_s(L"%ls", Raw_EulaText); + wprintf_s(L"This is the first run of this program. You must accept EULA to continue.\n"); + wprintf_s(L"Use -accepteula to accept EULA.\n\n"); + + // exit here to avoid printing the misleading "Eula declined". + exit(1); +} + +BOOL IsEulaAcceptedValueExist(HKEY hKeyRoot, LPCTSTR lpSubKey) +{ + HKEY hKey = NULL; + DWORD length; + DWORD eulaAccepted = 0; + DWORD ret; + + // + // check if it is set by external channel for all tools + // assuming external channel do not set to WOW6432Node + // + if (RegOpenKeyEx(hKeyRoot, lpSubKey, 0, KEY_QUERY_VALUE | KEY_WOW64_64KEY, &hKey) == ERROR_SUCCESS) + { + length = sizeof(eulaAccepted); + ret = RegQueryValueEx(hKey, _T("EulaAccepted"), NULL, NULL, (LPBYTE)&eulaAccepted, &length); + RegCloseKey(hKey); + + if (ret == ERROR_SUCCESS && eulaAccepted) + { + return TRUE; + } + } + + return FALSE; +} + +BOOL IsEulaRegkeyAdded(const TCHAR * ToolName) +{ + TCHAR perToolRegKey[MAX_PATH]; + PTCHAR suiteRegKey = _T("Software\\Sysinternals"); + + _stprintf_s(perToolRegKey, MAX_PATH, _T("%s\\%s"), suiteRegKey, ToolName); + + // + // check if it is set by external channel for all tools + // assuming external channel do not set to WOW6432Node + // + if (IsEulaAcceptedValueExist(HKEY_LOCAL_MACHINE, suiteRegKey) || + IsEulaAcceptedValueExist(HKEY_CURRENT_USER, suiteRegKey)) + { + return TRUE; + } + + // + // per tool check + // + if (IsEulaAcceptedValueExist(HKEY_CURRENT_USER, perToolRegKey)) + { + return TRUE; + } + + return FALSE; +} + +BOOL IsEulaSwitchAppended(int *argc, PTCHAR argv[]) +{ + DWORD eulaAccepted = 0; + int i; + + // + // See if its accepted via command line switch + // + if (*argc > 1) { + for (i = 1; i < *argc; i++) { + eulaAccepted = (!_tcsicmp(argv[i], _T("/accepteula")) || + !_tcsicmp(argv[i], _T("-accepteula"))); + if (eulaAccepted) { + break; + } + } + } + + return eulaAccepted; +} + +// +// Determine if Eula is accepted, either already have regkey added +// or have -accepteula switch appended +// +BOOL IsEulaAccepted(const TCHAR * ToolName, int *argc, PTCHAR argv[]) +{ + return IsEulaRegkeyAdded(ToolName) || IsEulaSwitchAppended(argc, argv); +} diff --git a/src/common/sysinternals/Eula/eula.h b/src/common/sysinternals/Eula/eula.h new file mode 100644 index 000000000000..450031583e95 --- /dev/null +++ b/src/common/sysinternals/Eula/eula.h @@ -0,0 +1,17 @@ +#ifdef __cplusplus +extern "C" { +#endif + + +BOOL ShowEulaW( const TCHAR * ToolName, int *argc, PWCHAR argv[] ); +BOOL ShowEula( const TCHAR * ToolName, int *argc, TCHAR *argv[] ); +DWORD ShowEulaConsole(); +void ShowEulaConsoleNoPrompt(); +BOOL IsIoTEdition(); +BOOL IsRemoteOnlyEdition(); +BOOL IsRunningRemotely(); +BOOL IsEulaAccepted(const TCHAR * ToolName, int *argc, PTCHAR argv[]); + +#ifdef __cplusplus +} +#endif diff --git a/src/common/sysinternals/Eula/eula.rtf b/src/common/sysinternals/Eula/eula.rtf new file mode 100644 index 000000000000..4f8c495bf30d --- /dev/null +++ b/src/common/sysinternals/Eula/eula.rtf @@ -0,0 +1,76 @@ +{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1033{\fonttbl{\f0\fswiss\fprq2\fcharset0 Tahoma;}{\f1\fnil\fcharset0 Calibri;}} +{\colortbl ;\red0\green0\blue255;\red0\green0\blue0;} +{\*\generator Riched20 10.0.10240}\viewkind4\uc1 +\pard\brdrb\brdrs\brdrw10\brsp20 \sb120\sa120\b\f0\fs24 SYSINTERNALS SOFTWARE LICENSE TERMS\fs28\par + +\pard\sb120\sa120\b0\fs19 These license terms are an agreement between Sysinternals (a wholly owned subsidiary of Microsoft Corporation) and you. Please read them. They apply to the software you are downloading from Sysinternals.com, which includes the media on which you received it, if any. The terms also apply to any Sysinternals\par + +\pard\fi-363\li720\sb120\sa120\tx720\'b7\tab updates,\par + +\pard\fi-363\li720\sb120\sa120\'b7\tab supplements,\par +\'b7\tab Internet-based services, and \par +\'b7\tab support services\par + +\pard\sb120\sa120 for this software, unless other terms accompany those items. If so, those terms apply.\par +\b BY USING THE SOFTWARE, YOU ACCEPT THESE TERMS. IF YOU DO NOT ACCEPT THEM, DO NOT USE THE SOFTWARE.\par + +\pard\brdrt\brdrs\brdrw10\brsp20 \sb120\sa120 If you comply with these license terms, you have the rights below.\par + +\pard\fi-357\li357\sb120\sa120\tx360\fs20 1.\tab\fs19 INSTALLATION AND USE RIGHTS. \b0 You may install and use any number of copies of the software on your devices.\b\par +\caps\fs20 2.\tab\fs19 Scope of License\caps0 .\b0 The software is licensed, not sold. This agreement only gives you some rights to use the software. Sysinternals reserves all other rights. Unless applicable law gives you more rights despite this limitation, you may use the software only as expressly permitted in this agreement. In doing so, you must comply with any technical limitations in the software that only allow you to use it in certain ways. You may not\b\par + +\pard\fi-363\li720\sb120\sa120\tx720\b0\'b7\tab work around any technical limitations in the binary versions of the software;\par + +\pard\fi-363\li720\sb120\sa120\'b7\tab reverse engineer, decompile or disassemble the binary versions of the software, except and only to the extent that applicable law expressly permits, despite this limitation;\par +\'b7\tab make more copies of the software than specified in this agreement or allowed by applicable law, despite this limitation;\par +\'b7\tab publish the software for others to copy;\par +\'b7\tab rent, lease or lend the software;\par +\'b7\tab transfer the software or this agreement to any third party; or\par +\'b7\tab use the software for commercial software hosting services.\par + +\pard\fi-357\li357\sb120\sa120\tx360\b\fs20 3.\tab SENSITIVE INFORMATION. \b0 Please be aware that, similar to other debug tools that capture \ldblquote process state\rdblquote information, files saved by Sysinternals tools may include personally identifiable or other sensitive information (such as usernames, passwords, paths to files accessed, and paths to registry accessed). By using this software, you acknowledge that you are aware of this and take sole responsibility for any personally identifiable or other sensitive information provided to Microsoft or any other party through your use of the software.\b\par +5. \tab\fs19 DOCUMENTATION.\b0 Any person that has valid access to your computer or internal network may copy and use the documentation for your internal, reference purposes.\b\par +\caps\fs20 6.\tab\fs19 Export Restrictions\caps0 .\b0 The software is subject to United States export laws and regulations. You must comply with all domestic and international export laws and regulations that apply to the software. These laws include restrictions on destinations, end users and end use. For additional information, see {\cf1\ul{\field{\*\fldinst{HYPERLINK www.microsoft.com/exporting }}{\fldrslt{www.microsoft.com/exporting}}}}\cf1\ul\f0\fs19 <{{\field{\*\fldinst{HYPERLINK "http://www.microsoft.com/exporting"}}{\fldrslt{http://www.microsoft.com/exporting}}}}\f0\fs19 >\cf0\ulnone .\b\par +\caps\fs20 7.\tab\fs19 SUPPORT SERVICES.\caps0 \b0 Because this software is "as is," we may not provide support services for it.\b\par +\caps\fs20 8.\tab\fs19 Entire Agreement.\b0\caps0 This agreement, and the terms for supplements, updates, Internet-based services and support services that you use, are the entire agreement for the software and support services.\par + +\pard\keepn\fi-360\li360\sb120\sa120\tx360\cf2\b\caps\fs20 9.\tab\fs19 Applicable Law\caps0 .\par + +\pard\fi-363\li720\sb120\sa120\tx720\cf0\fs20 a.\tab\fs19 United States.\b0 If you acquired the software in the United States, Washington state law governs the interpretation of this agreement and applies to claims for breach of it, regardless of conflict of laws principles. The laws of the state where you live govern all other claims, including claims under state consumer protection laws, unfair competition laws, and in tort.\b\par + +\pard\fi-363\li720\sb120\sa120\fs20 b.\tab\fs19 Outside the United States.\b0 If you acquired the software in any other country, the laws of that country apply.\b\par + +\pard\fi-357\li357\sb120\sa120\tx360\caps\fs20 10.\tab\fs19 Legal Effect.\b0\caps0 This agreement describes certain legal rights. You may have other rights under the laws of your country. You may also have rights with respect to the party from whom you acquired the software. This agreement does not change your rights under the laws of your country if the laws of your country do not permit it to do so.\b\caps\par +\fs20 11.\tab\fs19 Disclaimer of Warranty.\caps0 \caps The software is licensed "as-is." You bear the risk of using it. SYSINTERNALS gives no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this agreement cannot change. To the extent permitted under your local laws, SYSINTERNALS excludes the implied warranties of merchantability, fitness for a particular purpose and non-infringement.\par + +\pard\fi-360\li360\sb120\sa120\tx360\fs20 12.\tab\fs19 Limitation on and Exclusion of Remedies and Damages. You can recover from SYSINTERNALS and its suppliers only direct damages up to U.S. $5.00. You cannot recover any other damages, including consequential, lost profits, special, indirect or incidental damages.\par + +\pard\li357\sb120\sa120\b0\caps0 This limitation applies to\par + +\pard\fi-363\li720\sb120\sa120\tx720\'b7\tab anything related to the software, services, content (including code) on third party Internet sites, or third party programs; and\par + +\pard\fi-363\li720\sb120\sa120\'b7\tab claims for breach of contract, breach of warranty, guarantee or condition, strict liability, negligence, or other tort to the extent permitted by applicable law.\par + +\pard\li360\sb120\sa120 It also applies even if Sysinternals knew or should have known about the possibility of the damages. The above limitation or exclusion may not apply to you because your country may not allow the exclusion or limitation of incidental, consequential or other damages.\par + +\pard\b Please note: As this software is distributed in Quebec, Canada, some of the clauses in this agreement are provided below in French.\par + +\pard\sb240\lang1036 Remarque : Ce logiciel \'e9tant distribu\'e9 au Qu\'e9bec, Canada, certaines des clauses dans ce contrat sont fournies ci-dessous en fran\'e7ais.\par + +\pard\sb120\sa120 EXON\'c9RATION DE GARANTIE.\b0 Le logiciel vis\'e9 par une licence est offert \'ab tel quel \'bb. Toute utilisation de ce logiciel est \'e0 votre seule risque et p\'e9ril. Sysinternals n'accorde aucune autre garantie expresse. Vous pouvez b\'e9n\'e9ficier de droits additionnels en vertu du droit local sur la protection dues consommateurs, que ce contrat ne peut modifier. La ou elles sont permises par le droit locale, les garanties implicites de qualit\'e9 marchande, d'ad\'e9quation \'e0 un usage particulier et d'absence de contrefa\'e7on sont exclues.\par + +\pard\keepn\sb120\sa120\b LIMITATION DES DOMMAGES-INT\'c9R\'caTS ET EXCLUSION DE RESPONSABILIT\'c9 POUR LES DOMMAGES.\b0 Vous pouvez obtenir de Sysinternals et de ses fournisseurs une indemnisation en cas de dommages directs uniquement \'e0 hauteur de 5,00 $ US. Vous ne pouvez pr\'e9tendre \'e0 aucune indemnisation pour les autres dommages, y compris les dommages sp\'e9ciaux, indirects ou accessoires et pertes de b\'e9n\'e9fices.\par +\lang1033 Cette limitation concerne :\par + +\pard\keepn\fi-360\li720\sb120\sa120\tx720\lang1036\'b7\tab tout ce qui est reli\'e9 au logiciel, aux services ou au contenu (y compris le code) figurant sur des sites Internet tiers ou dans des programmes tiers ; et\par + +\pard\fi-363\li720\sb120\sa120\tx720\'b7\tab les r\'e9clamations au titre de violation de contrat ou de garantie, ou au titre de responsabilit\'e9 stricte, de n\'e9gligence ou d'une autre faute dans la limite autoris\'e9e par la loi en vigueur.\par + +\pard\sb120\sa120 Elle s'applique \'e9galement, m\'eame si Sysinternals connaissait ou devrait conna\'eetre l'\'e9ventualit\'e9 d'un tel dommage. Si votre pays n'autorise pas l'exclusion ou la limitation de responsabilit\'e9 pour les dommages indirects, accessoires ou de quelque nature que ce soit, il se peut que la limitation ou l'exclusion ci-dessus ne s'appliquera pas \'e0 votre \'e9gard.\par +\b EFFET JURIDIQUE.\b0 Le pr\'e9sent contrat d\'e9crit certains droits juridiques. Vous pourriez avoir d'autres droits pr\'e9vus par les lois de votre pays. Le pr\'e9sent contrat ne modifie pas les droits que vous conf\'e8rent les lois de votre pays si celles-ci ne le permettent pas.\b\par + +\pard\b0\fs20\lang1033\par + +\pard\sa200\sl276\slmult1\f1\fs22\lang9\par +} + \ No newline at end of file diff --git a/src/common/sysinternals/WindowsVersions.cpp b/src/common/sysinternals/WindowsVersions.cpp new file mode 100644 index 000000000000..6b12d86d2a80 --- /dev/null +++ b/src/common/sysinternals/WindowsVersions.cpp @@ -0,0 +1,24 @@ +#include + +#include "WindowsVersions.h" + +// Declared in wdm.h +typedef NTSYSAPI NTSTATUS (NTAPI *RtlGetVersionType)( PRTL_OSVERSIONINFOW ); + +DWORD GetWindowsBuild( DWORD* revision ) +{ + if( revision ) { + + DWORD size = sizeof( *revision ); + if( RegGetValueW( HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", L"UBR", RRF_RT_REG_DWORD, NULL, revision, &size ) != ERROR_SUCCESS ) { + + *revision = 0; + } + } + + RtlGetVersionType pRtlGetVersion = reinterpret_cast(GetProcAddress( GetModuleHandleW( L"ntdll.dll" ), "RtlGetVersion" )); + + RTL_OSVERSIONINFOW version; + pRtlGetVersion( &version ); + return version.dwBuildNumber; +} diff --git a/src/common/sysinternals/WindowsVersions.h b/src/common/sysinternals/WindowsVersions.h new file mode 100644 index 000000000000..de06ab7e562f --- /dev/null +++ b/src/common/sysinternals/WindowsVersions.h @@ -0,0 +1,32 @@ +//---------------------------------------------------------------------- +// +// WindowsVersions.h +// +// Provides helpers for Windows builds and versions. +// +//---------------------------------------------------------------------- + +#pragma once + +#define BUILD_WINDOWS_SERVER_2008 6003 +#define BUILD_WINDOWS_SERVER_2008_R2 7601 +#define BUILD_WINDOWS_SERVER_2012 9200 +#define BUILD_WINDOWS_8_1 9600 +#define BUILD_WINDOWS_SERVER_2012_R2 9600 +#define BUILD_WINDOWS_10_1507 10240 +#define BUILD_WINDOWS_10_1607 14393 +#define BUILD_WINDOWS_SERVER_2016 14393 +#define BUILD_WINDOWS_10_1809 17763 +#define BUILD_WINDOWS_SERVER_2019 17763 +#define BUILD_WINDOWS_10_1903 18362 +#define BUILD_WINDOWS_10_1909 18363 +#define BUILD_WINDOWS_10_2004 19041 +#define BUILD_WINDOWS_10_20H2 19042 +#define BUILD_WINDOWS_SERVER_20H2 19042 +#define BUILD_WINDOWS_10_21H1 19043 +#define BUILD_WINDOWS_10_21H2 19044 +#define BUILD_WINDOWS_SERVER_2022 20348 +#define BUILD_WINDOWS_11_21H2 22000 +#define BUILD_WINDOWS_11_22H2 22621 + +DWORD GetWindowsBuild( DWORD* revision ); diff --git a/src/common/sysinternals/dll.c b/src/common/sysinternals/dll.c new file mode 100644 index 000000000000..8498db813c18 --- /dev/null +++ b/src/common/sysinternals/dll.c @@ -0,0 +1,74 @@ +//=========================================================================-== +// +// dll.c +// +// DLL support functions +// +//============================================================================ + +#include +#include +#include +#include +#include "dll.h" + +#ifndef LOAD_LIBRARY_SEARCH_SYSTEM32 + #define LOAD_LIBRARY_SEARCH_SYSTEM32 0x800 +#endif + + +//=========================================================================-== +// +// ExtendedFlagsSupported +// +// Returns TRUE if running on Windows 7 or later and FALSE otherwise +// +//============================================================================ +static BOOLEAN ExtendedFlagsSupported() +{ + OSVERSIONINFO osInfo; + BOOLEAN rc = FALSE; + + ZeroMemory(&osInfo, sizeof(OSVERSIONINFO)); + osInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + +#pragma warning ( disable : 4996 ) // deprecated in favour of version helper functions which we can't use + + if (GetVersionEx(&osInfo) && (osInfo.dwMajorVersion > 6 || (osInfo.dwMajorVersion == 6 && osInfo.dwMinorVersion > 0))) + rc = TRUE; + +#pragma warning ( default : 4996 ) + + return rc; +} + +//=========================================================================-== +// +// LoadLibrarySafe +// +// Loads a DLL from the system folder in a way that mitigates DLL spoofing / +// side-loading attacks +// +//============================================================================ +HMODULE LoadLibrarySafe(LPCTSTR libraryName, DLL_LOAD_LOCATION location) +{ + HMODULE hMod = NULL; + + if (NULL == libraryName || location <= DLL_LOAD_LOCATION_MIN || location >= DLL_LOAD_LOCATION_MAX) { + + SetLastError(ERROR_INVALID_PARAMETER); + return NULL; + } + + // LOAD_LIBRARY_SEARCH_SYSTEM32 is only supported on Window 7 or later. On earlier SKUs we could use a fully + // qualified path to the system folder but specifying a path causes Ldr to skip SxS file redirection. This can + // cause the wrong library to be loaded if the application is using a manifest that defines a specific version + // of Microsoft.Windows.Common-Controls when loading comctl32.dll + if (DLL_LOAD_LOCATION_SYSTEM == location) { + + DWORD flags = ExtendedFlagsSupported() ? LOAD_LIBRARY_SEARCH_SYSTEM32 : 0; + hMod = LoadLibraryEx(libraryName, NULL, flags); + } + + return hMod; +} diff --git a/src/common/sysinternals/dll.h b/src/common/sysinternals/dll.h new file mode 100644 index 000000000000..80c60a72ca07 --- /dev/null +++ b/src/common/sysinternals/dll.h @@ -0,0 +1,26 @@ +//=========================================================================-== +// +// dll.h +// +// DLL support functions +// +//============================================================================ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + + typedef enum + { + DLL_LOAD_LOCATION_MIN = 0, + DLL_LOAD_LOCATION_SYSTEM = 1, + DLL_LOAD_LOCATION_MAX + } DLL_LOAD_LOCATION, *PDLL_LOAD_LOCATION; + + HMODULE LoadLibrarySafe(LPCTSTR libraryName, DLL_LOAD_LOCATION location); + +#ifdef __cplusplus +} +#endif + diff --git a/src/common/utils/gpo.h b/src/common/utils/gpo.h index 04e03b476764..81e7415dff0e 100644 --- a/src/common/utils/gpo.h +++ b/src/common/utils/gpo.h @@ -54,6 +54,7 @@ namespace powertoys_gpo { const std::wstring POLICY_CONFIGURE_ENABLED_TEXT_EXTRACTOR = L"ConfigureEnabledUtilityTextExtractor"; const std::wstring POLICY_CONFIGURE_ENABLED_ADVANCED_PASTE = L"ConfigureEnabledUtilityAdvancedPaste"; const std::wstring POLICY_CONFIGURE_ENABLED_VIDEO_CONFERENCE_MUTE = L"ConfigureEnabledUtilityVideoConferenceMute"; + const std::wstring POLICY_CONFIGURE_ENABLED_ZOOM_IT = L"ConfigureEnabledUtilityZoomIt"; const std::wstring POLICY_CONFIGURE_ENABLED_REGISTRY_PREVIEW = L"ConfigureEnabledUtilityRegistryPreview"; const std::wstring POLICY_CONFIGURE_ENABLED_MOUSE_WITHOUT_BORDERS = L"ConfigureEnabledUtilityMouseWithoutBorders"; const std::wstring POLICY_CONFIGURE_ENABLED_PEEK = L"ConfigureEnabledUtilityPeek"; @@ -419,6 +420,11 @@ namespace powertoys_gpo { return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_VIDEO_CONFERENCE_MUTE); } + inline gpo_rule_configured_t getConfiguredZoomItEnabledValue() + { + return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_ZOOM_IT); + } + inline gpo_rule_configured_t getConfiguredMouseWithoutBordersEnabledValue() { return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_MOUSE_WITHOUT_BORDERS); diff --git a/src/gpo/assets/PowerToys.admx b/src/gpo/assets/PowerToys.admx index 799b1f20f36d..b4d427601097 100644 --- a/src/gpo/assets/PowerToys.admx +++ b/src/gpo/assets/PowerToys.admx @@ -5,7 +5,7 @@ - + @@ -23,6 +23,7 @@ + @@ -450,6 +451,16 @@ + + + + + + + + + + diff --git a/src/gpo/assets/en-US/PowerToys.adml b/src/gpo/assets/en-US/PowerToys.adml index 7a8f037eee5c..8b82e6609521 100644 --- a/src/gpo/assets/en-US/PowerToys.adml +++ b/src/gpo/assets/en-US/PowerToys.adml @@ -1,7 +1,7 @@ - + PowerToys PowerToys @@ -29,6 +29,7 @@ PowerToys version 0.84.0 or later PowerToys version 0.85.0 or later PowerToys version 0.86.0 or later + PowerToys version 0.87.0 or later This policy configures the enabled state for all PowerToys utilities. @@ -245,6 +246,7 @@ If you don't configure this policy, the user takes control over the setting and Shortcut Guide: Configure enabled state Text Extractor: Configure enabled state Video Conference Mute: Configure enabled state + Zoom It: Configure enabled state Disable per-user installation Disable automatic downloads Do not show the release notes after updates diff --git a/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj b/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj index 89177bd56f2d..f3ce71829ffd 100644 --- a/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj +++ b/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj @@ -159,7 +159,7 @@ - + @@ -168,7 +168,7 @@ - + \ No newline at end of file diff --git a/src/modules/CropAndLock/CropAndLock/packages.config b/src/modules/CropAndLock/CropAndLock/packages.config index f53ff9a92d4f..691158d1b2a7 100644 --- a/src/modules/CropAndLock/CropAndLock/packages.config +++ b/src/modules/CropAndLock/CropAndLock/packages.config @@ -2,5 +2,5 @@ - + \ No newline at end of file diff --git a/src/modules/CropAndLock/CropAndLock/pch.h b/src/modules/CropAndLock/CropAndLock/pch.h index 75bb5f4a616f..c159b433a112 100644 --- a/src/modules/CropAndLock/CropAndLock/pch.h +++ b/src/modules/CropAndLock/CropAndLock/pch.h @@ -52,13 +52,13 @@ // robmikh.common #include #include -#include +#include #include // robmikh.common needs to be updated to support newer versions of C++/WinRT https://github.com/robmikh/robmikh.common/issues/2 // Applying workaround from https://github.com/robmikh/Win32CaptureSample/commit/fc758e343ca886795b05af5003d9a3bb85ff4da2 // #include #include "DispatcherQueue.desktop.interop.h" -#include +#include #include #include #include diff --git a/src/modules/CropAndLock/CropAndLockModuleInterface/CropAndLockModuleInterface.vcxproj b/src/modules/CropAndLock/CropAndLockModuleInterface/CropAndLockModuleInterface.vcxproj index 0917a14602f7..c7fccf462a95 100644 --- a/src/modules/CropAndLock/CropAndLockModuleInterface/CropAndLockModuleInterface.vcxproj +++ b/src/modules/CropAndLock/CropAndLockModuleInterface/CropAndLockModuleInterface.vcxproj @@ -102,7 +102,7 @@ - + @@ -112,7 +112,7 @@ - + \ No newline at end of file diff --git a/src/modules/ZoomIt/ZoomIt/AudioSampleGenerator.cpp b/src/modules/ZoomIt/ZoomIt/AudioSampleGenerator.cpp new file mode 100644 index 000000000000..21e9883bb738 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/AudioSampleGenerator.cpp @@ -0,0 +1,168 @@ +#include "pch.h" +#include "AudioSampleGenerator.h" +#include "CaptureFrameWait.h" + +extern TCHAR g_MicrophoneDeviceId[]; + +namespace winrt +{ + using namespace Windows::Foundation; + using namespace Windows::Storage; + using namespace Windows::Storage::Streams; + using namespace Windows::Media; + using namespace Windows::Media::Audio; + using namespace Windows::Media::Capture; + using namespace Windows::Media::Core; + using namespace Windows::Media::Render; + using namespace Windows::Media::MediaProperties; + using namespace Windows::Media::Devices; + using namespace Windows::Devices::Enumeration; +} + +AudioSampleGenerator::AudioSampleGenerator() +{ + m_audioEvent.create(wil::EventOptions::ManualReset); + m_endEvent.create(wil::EventOptions::ManualReset); + m_asyncInitialized.create(wil::EventOptions::ManualReset); +} + +AudioSampleGenerator::~AudioSampleGenerator() +{ + Stop(); + if (m_started.load()) + { + m_audioGraph.Close(); + } +} + +winrt::IAsyncAction AudioSampleGenerator::InitializeAsync() +{ + auto expected = false; + if (m_initialized.compare_exchange_strong(expected, true)) + { + // Initialize the audio graph + auto audioGraphSettings = winrt::AudioGraphSettings(winrt::AudioRenderCategory::Media); + auto audioGraphResult = co_await winrt::AudioGraph::CreateAsync(audioGraphSettings); + if (audioGraphResult.Status() != winrt::AudioGraphCreationStatus::Success) + { + throw winrt::hresult_error(E_FAIL, L"Failed to initialize AudioGraph!"); + } + m_audioGraph = audioGraphResult.Graph(); + + // Initialize the selected microphone + auto defaultMicrophoneId = winrt::MediaDevice::GetDefaultAudioCaptureId(winrt::AudioDeviceRole::Default); + auto microphoneId = (g_MicrophoneDeviceId[0] == 0) ? defaultMicrophoneId : winrt::to_hstring(g_MicrophoneDeviceId); + auto microphone = co_await winrt::DeviceInformation::CreateFromIdAsync(microphoneId); + + // Initialize audio input and output nodes + auto inputNodeResult = co_await m_audioGraph.CreateDeviceInputNodeAsync(winrt::MediaCategory::Media, m_audioGraph.EncodingProperties(), microphone); + if (inputNodeResult.Status() != winrt::AudioDeviceNodeCreationStatus::Success && microphoneId != defaultMicrophoneId) + { + // If the selected microphone failed, try again with the default + microphone = co_await winrt::DeviceInformation::CreateFromIdAsync(defaultMicrophoneId); + inputNodeResult = co_await m_audioGraph.CreateDeviceInputNodeAsync(winrt::MediaCategory::Media, m_audioGraph.EncodingProperties(), microphone); + } + if (inputNodeResult.Status() != winrt::AudioDeviceNodeCreationStatus::Success) + { + throw winrt::hresult_error(E_FAIL, L"Failed to initialize input audio node!"); + } + m_audioInputNode = inputNodeResult.DeviceInputNode(); + m_audioOutputNode = m_audioGraph.CreateFrameOutputNode(); + + // Hookup audio nodes + m_audioInputNode.AddOutgoingConnection(m_audioOutputNode); + m_audioGraph.QuantumStarted({ this, &AudioSampleGenerator::OnAudioQuantumStarted }); + + m_asyncInitialized.SetEvent(); + } +} + +winrt::AudioEncodingProperties AudioSampleGenerator::GetEncodingProperties() +{ + CheckInitialized(); + return m_audioOutputNode.EncodingProperties(); +} + +std::optional AudioSampleGenerator::TryGetNextSample() +{ + CheckInitialized(); + CheckStarted(); + + { + auto lock = m_lock.lock_exclusive(); + if (m_samples.empty() && m_endEvent.is_signaled()) + { + return std::nullopt; + } + else if (!m_samples.empty()) + { + std::optional result(m_samples.front()); + m_samples.pop_front(); + return result; + } + } + + m_audioEvent.ResetEvent(); + std::vector events = { m_endEvent.get(), m_audioEvent.get() }; + auto waitResult = WaitForMultipleObjectsEx(static_cast(events.size()), events.data(), false, INFINITE, false); + auto eventIndex = -1; + switch (waitResult) + { + case WAIT_OBJECT_0: + case WAIT_OBJECT_0 + 1: + eventIndex = waitResult - WAIT_OBJECT_0; + break; + } + WINRT_VERIFY(eventIndex >= 0); + + auto signaledEvent = events[eventIndex]; + if (signaledEvent == m_endEvent.get()) + { + return std::nullopt; + } + else + { + auto lock = m_lock.lock_exclusive(); + std::optional result(m_samples.front()); + m_samples.pop_front(); + return result; + } +} + +void AudioSampleGenerator::Start() +{ + CheckInitialized(); + auto expected = false; + if (m_started.compare_exchange_strong(expected, true)) + { + m_audioGraph.Start(); + } +} + +void AudioSampleGenerator::Stop() +{ + CheckInitialized(); + if (m_started.load()) + { + m_asyncInitialized.wait(); + m_audioGraph.Stop(); + m_endEvent.SetEvent(); + } +} + +void AudioSampleGenerator::OnAudioQuantumStarted(winrt::AudioGraph const& sender, winrt::IInspectable const& args) +{ + { + auto lock = m_lock.lock_exclusive(); + + auto frame = m_audioOutputNode.GetFrame(); + std::optional timestamp = frame.RelativeTime(); + auto audioBuffer = frame.LockBuffer(winrt::AudioBufferAccessMode::Read); + + auto sampleBuffer = winrt::Buffer::CreateCopyFromMemoryBuffer(audioBuffer); + sampleBuffer.Length(audioBuffer.Length()); + auto sample = winrt::MediaStreamSample::CreateFromBuffer(sampleBuffer, timestamp.value()); + m_samples.push_back(sample); + } + m_audioEvent.SetEvent(); +} diff --git a/src/modules/ZoomIt/ZoomIt/AudioSampleGenerator.h b/src/modules/ZoomIt/ZoomIt/AudioSampleGenerator.h new file mode 100644 index 000000000000..8e279f3b5895 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/AudioSampleGenerator.h @@ -0,0 +1,48 @@ +#pragma once + +class AudioSampleGenerator +{ +public: + AudioSampleGenerator(); + ~AudioSampleGenerator(); + + winrt::Windows::Foundation::IAsyncAction InitializeAsync(); + winrt::Windows::Media::MediaProperties::AudioEncodingProperties GetEncodingProperties(); + + std::optional TryGetNextSample(); + void Start(); + void Stop(); + +private: + void OnAudioQuantumStarted( + winrt::Windows::Media::Audio::AudioGraph const& sender, + winrt::Windows::Foundation::IInspectable const& args); + + void CheckInitialized() + { + if (!m_initialized.load()) + { + throw winrt::hresult_error(E_FAIL, L"Must initialize audio sample generator before use!"); + } + } + + void CheckStarted() + { + if (!m_started.load()) + { + throw winrt::hresult_error(E_FAIL, L"Must start audio sample generator before calling this method!"); + } + } + +private: + winrt::Windows::Media::Audio::AudioGraph m_audioGraph{ nullptr }; + winrt::Windows::Media::Audio::AudioDeviceInputNode m_audioInputNode{ nullptr }; + winrt::Windows::Media::Audio::AudioFrameOutputNode m_audioOutputNode{ nullptr }; + wil::srwlock m_lock; + wil::unique_event m_audioEvent; + wil::unique_event m_endEvent; + wil::unique_event m_asyncInitialized; + std::deque m_samples; + std::atomic m_initialized = false; + std::atomic m_started = false; +}; \ No newline at end of file diff --git a/src/modules/ZoomIt/ZoomIt/CaptureFrameWait.cpp b/src/modules/ZoomIt/ZoomIt/CaptureFrameWait.cpp new file mode 100644 index 000000000000..d4d3fa17502d --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/CaptureFrameWait.cpp @@ -0,0 +1,147 @@ +//============================================================================== +// +// Zoomit +// Sysinternals - www.sysinternals.com +// +// Video capture code derived from https://github.com/robmikh/capturevideosample +// +//============================================================================== +#include "pch.h" +#include "CaptureFrameWait.h" + +namespace winrt +{ + using namespace Windows::Foundation; + using namespace Windows::Graphics; + using namespace Windows::Graphics::Capture; + using namespace Windows::Graphics::DirectX; + using namespace Windows::Graphics::DirectX::Direct3D11; + using namespace Windows::Storage; + using namespace Windows::UI::Composition; +} + +namespace util +{ + using namespace robmikh::common::uwp; +} + +//---------------------------------------------------------------------------- +// +// CaptureFrameWait::CaptureFrameWait +// +//---------------------------------------------------------------------------- +CaptureFrameWait::CaptureFrameWait( + winrt::IDirect3DDevice const& device, + winrt::GraphicsCaptureItem const& item, + winrt::SizeInt32 const& size) +{ + m_device = device; + m_item = item; + + m_nextFrameEvent = wil::shared_event(wil::EventOptions::ManualReset); + m_endEvent = wil::shared_event(wil::EventOptions::ManualReset); + m_closedEvent = wil::shared_event(wil::EventOptions::ManualReset); + + m_framePool = winrt::Direct3D11CaptureFramePool::CreateFreeThreaded( + m_device, + winrt::DirectXPixelFormat::B8G8R8A8UIntNormalized, + 1, + size); + m_session = m_framePool.CreateCaptureSession(m_item); + + m_framePool.FrameArrived({ this, &CaptureFrameWait::OnFrameArrived }); + m_session.StartCapture(); +} + +//---------------------------------------------------------------------------- +// +// CaptureFrameWait::~CaptureFrameWait +// +//---------------------------------------------------------------------------- +CaptureFrameWait::~CaptureFrameWait() +{ + StopCapture(); + // We might end the capture before we ever get another frame. + m_closedEvent.wait(200); +} + +//---------------------------------------------------------------------------- +// +// CaptureFrameWait::TryGetNextFrame +// +// Fetches next available frame +// +//---------------------------------------------------------------------------- +std::optional CaptureFrameWait::TryGetNextFrame() +{ + if (m_currentFrame != nullptr) + { + m_currentFrame.Close(); + } + m_nextFrameEvent.ResetEvent(); + + std::vector events = { m_endEvent.get(), m_nextFrameEvent.get() }; + auto waitResult = WaitForMultipleObjectsEx(static_cast(events.size()), events.data(), false, INFINITE, false); + auto eventIndex = -1; + switch (waitResult) + { + case WAIT_OBJECT_0: + case WAIT_OBJECT_0 + 1: + eventIndex = waitResult - WAIT_OBJECT_0; + break; + } + WINRT_VERIFY(eventIndex >= 0); + + auto signaledEvent = events[eventIndex]; + if (signaledEvent == m_endEvent.get()) + { + return std::nullopt; + } + + return std::optional( + { + m_currentFrame.Surface(), + m_currentFrame.ContentSize(), + m_currentFrame.SystemRelativeTime(), + }); +} + + +//---------------------------------------------------------------------------- +// +// CaptureFrameWait::StopCapture +// +// Stops frame capture and notified any frame waiters +// +//---------------------------------------------------------------------------- +void CaptureFrameWait::StopCapture() +{ + auto lock = m_lock.lock_exclusive(); + m_endEvent.SetEvent(); + m_framePool.Close(); + m_session.Close(); +} + +//---------------------------------------------------------------------------- +// +// CaptureFrameWait::OnFrameArrived +// +// Callback for new frames +// +//---------------------------------------------------------------------------- +void CaptureFrameWait::OnFrameArrived( + winrt::Direct3D11CaptureFramePool const& sender, + winrt::IInspectable const&) +{ + auto lock = m_lock.lock_exclusive(); + if (m_endEvent.is_signaled()) + { + m_closedEvent.SetEvent(); + return; + } + auto frame = sender.TryGetNextFrame(); + if( frame ) { + m_currentFrame = frame; + m_nextFrameEvent.SetEvent(); + } +} diff --git a/src/modules/ZoomIt/ZoomIt/CaptureFrameWait.h b/src/modules/ZoomIt/ZoomIt/CaptureFrameWait.h new file mode 100644 index 000000000000..560acde3ba63 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/CaptureFrameWait.h @@ -0,0 +1,142 @@ +//============================================================================== +// +// Zoomit +// Sysinternals - www.sysinternals.com +// +// Video capture code derived from https://github.com/robmikh/capturevideosample +// +//============================================================================== +#pragma once + +// Must come before C++/WinRT +#include + +// WinRT +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// WIL +#include + +// DirectX +#include +#include +#include +#include + +// STL +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// robmikh.common +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace winrt +{ + using namespace Windows::Foundation; + using namespace Windows::Foundation::Metadata; + using namespace Windows::Graphics; + using namespace Windows::Graphics::Capture; + using namespace Windows::Graphics::DirectX; + using namespace Windows::Graphics::DirectX::Direct3D11; + using namespace Windows::Storage; + using namespace Windows::UI::Composition; + using namespace Windows::Media::Core; + using namespace Windows::Media::Transcoding; + using namespace Windows::Media::MediaProperties; +} + +namespace util +{ + using namespace robmikh::common::uwp; +} + +struct CaptureFrame +{ + winrt::Direct3D11::IDirect3DSurface FrameTexture; + winrt::SizeInt32 ContentSize; + winrt::TimeSpan SystemRelativeTime; +}; + +class CaptureFrameWait +{ +public: + CaptureFrameWait( + winrt::Direct3D11::IDirect3DDevice const& device, + winrt::GraphicsCaptureItem const& item, + winrt::SizeInt32 const& size ); + ~CaptureFrameWait(); + + std::optional TryGetNextFrame(); + void StopCapture(); + void EnableCursorCapture( bool enable = true ) + { + if( winrt::ApiInformation::IsPropertyPresent( winrt::name_of(), L"IsCursorCaptureEnabled" ) ) + { + m_session.IsCursorCaptureEnabled( enable ); + } + } + void ShowCaptureBorder( bool show = true ) + { + if( winrt::ApiInformation::IsPropertyPresent( winrt::name_of(), L"IsBorderRequired" ) ) + { + m_session.IsBorderRequired( show ); + } + } + +private: + void OnFrameArrived( + winrt::Direct3D11CaptureFramePool const& sender, + winrt::IInspectable const& args ); + +private: + winrt::Direct3D11::IDirect3DDevice m_device{ nullptr }; + winrt::GraphicsCaptureItem m_item{ nullptr }; + winrt::Direct3D11CaptureFramePool m_framePool{ nullptr }; + winrt::GraphicsCaptureSession m_session{ nullptr }; + wil::shared_event m_nextFrameEvent; + wil::shared_event m_endEvent; + wil::shared_event m_closedEvent; + wil::srwlock m_lock; + + winrt::Direct3D11CaptureFrame m_currentFrame{ nullptr }; +}; \ No newline at end of file diff --git a/src/modules/ZoomIt/ZoomIt/DemoType.cpp b/src/modules/ZoomIt/ZoomIt/DemoType.cpp new file mode 100644 index 000000000000..40284a795bb1 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/DemoType.cpp @@ -0,0 +1,1447 @@ +//============================================================================ +// +// Zoomit +// Copyright (C) Mark Russinovich +// Sysinternals - www.sysinternals.com +// +// DemoType allows the presenter to synthesize keystrokes from a script +// +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +//============================================================================ + +#include "pch.h" +#include "DemoType.h" + +#define MAX_INDENT_DEPTH 100 + +#define INDENT_SEEK_FLAG L"x" + +#define END_CONTROL_LEN 5 +// Longest accepted control: [pause:000] +#define MAX_CONTROL_LEN 11 + +#define THIRD_TYPING_SPEED static_cast((MIN_TYPING_SPEED - MAX_TYPING_SPEED) / 3) +#define TYPING_VARIANCE ((float) 1.0) + +#define NOTEPAD_REFRESH 1 // ms +#define DEMOTYPE_REFRESH 50 // ms +#define CLIPBOARD_REFRESH 100 // ms +#define DEMOTYPE_TIMEOUT 1000 // ms + +#define INACTIVE_STATE 0 +#define START_STATE 1 +#define INIT_STATE 2 +#define ACTIVE_STATE 3 +#define BLOCK_STATE 4 +#define KILL_STATE 5 + +// Each injection is tracked so that the hook +// procedure can identify injections and allow them +// to pass through while blocking accidental keystrokes. +// +// Each injection is identified by either a virtual +// key code or a unicode character passed as a scan code +// which is wrapped into a VK_PACKET by SendInput when +// the KEYEVENTF_UNICODE flag is specified. +// +// VK_PACKET allows us to synthesize keystrokes which are +// not mapped to virtual-key codes (e.g. foreign characters). +struct Injection +{ + DWORD vkCode; + DWORD scanCode; + + Injection( DWORD vkCode, DWORD scanCode ) + : vkCode(vkCode), scanCode(scanCode) {} +}; + +bool g_UserDriven = false; +bool g_Notepad = false; +bool g_Clipboard = false; +TCHAR g_LastFilePath[MAX_PATH] = {0}; +HHOOK g_hHook = nullptr; +size_t g_TextLen = 0; +wchar_t* g_ClipboardCache = nullptr; +std::wstring g_Text = L""; +std::vector g_TextSegments; +std::wstring g_BaselineIndentation = L""; +std::atomic g_Index = 0; +std::condition_variable g_EpochReady; +std::mutex g_EpochMutex; +std::deque g_Injections; +std::mutex g_InjectionsMutex; +std::atomic g_Active = false; +std::atomic g_End = false; +std::atomic g_Kill = false; +std::atomic g_HookState = INACTIVE_STATE; +std::atomic g_EmitterState = INACTIVE_STATE; +DWORD g_LastClipboardSeq = 0; +DWORD g_SpeedSlider = static_cast(( + (MIN_TYPING_SPEED - MAX_TYPING_SPEED) / 2) + MAX_TYPING_SPEED); + +//---------------------------------------------------------------------------- +// +// IsWindowNotepad +// +//---------------------------------------------------------------------------- +bool IsWindowNotepad( const HWND hwnd ) +{ + const int CLASS_NAME_LEN = 256; + WCHAR className[CLASS_NAME_LEN]; + if( GetClassName( hwnd, className, CLASS_NAME_LEN ) > 0 ) + { + if( wcscmp( className, L"Notepad" ) == 0 ) + { + return true; + } + } + return false; +} + +//---------------------------------------------------------------------------- +// +// IsInjected +// +//---------------------------------------------------------------------------- +bool IsInjected( const DWORD vkCode, const DWORD scanCode ) +{ + bool injected = false; + bool locked = false; + if( g_EmitterState == ACTIVE_STATE ) + { + g_InjectionsMutex.lock(); + locked = true; + } + + if( !g_Injections.empty() ) + { + if( (g_Injections.front().vkCode != NULL && g_Injections.front().vkCode == vkCode) + || (g_Injections.front().vkCode == NULL && g_Injections.front().scanCode == scanCode) ) + { + injected = true; + } + } + + if( locked ) + { + g_InjectionsMutex.unlock(); + } + return injected; +} + +//---------------------------------------------------------------------------- +// +// IsAutoFormatTrigger +// +//---------------------------------------------------------------------------- +bool IsAutoFormatTrigger( wchar_t lastCh, wchar_t ch ) +{ + // Will trigger auto-indentation in smart editors + // '\t' check also handles possible auto-completion + if( ch == L'\n' || ch == L'\t' || (ch == L' ' && lastCh == L'\n') ) + { + return true; + } + + // Will trigger auto-close character(s) in smart editors + if( ch == L'{' || ch == L'[' || ch == L'(' || (ch == L'*' && lastCh == L'/') ) + { + return true; + } + + return false; +} + +//---------------------------------------------------------------------------- +// +// PopInjection +// +// See comments above `Injection` struct definition +// +//---------------------------------------------------------------------------- +void PopInjection() +{ + bool locked = false; + if( g_EmitterState == ACTIVE_STATE ) + { + g_InjectionsMutex.lock(); + locked = true; + } + + g_Injections.pop_front(); + + if( locked ) + { + g_InjectionsMutex.unlock(); + } +} + +//---------------------------------------------------------------------------- +// +// PushInjection +// +// See comments above `Injection` struct definition +// +//---------------------------------------------------------------------------- +void PushInjection( const WORD vK, const wchar_t ch ) +{ + bool locked = false; + if( g_EmitterState == ACTIVE_STATE ) + { + g_InjectionsMutex.lock(); + locked = true; + } + + g_Injections.push_back( Injection( static_cast(vK), static_cast(ch) ) ); + + if( locked ) + { + g_InjectionsMutex.unlock(); + } +} + +//---------------------------------------------------------------------------- +// +// IsNotPrintable +// +//---------------------------------------------------------------------------- +bool IsNotPrintable( wchar_t ch ) +{ + return ch != L'\n' && ch != L'\t' && !iswprint( ch ); +} + +//---------------------------------------------------------------------------- +// +// SendKeyInput +// +//---------------------------------------------------------------------------- +void SendKeyInput( const WORD vK, const wchar_t ch, const bool keyup = false ) +{ + INPUT input = {0}; + input.type = INPUT_KEYBOARD; + + // Send unicode character via VK_PACKET + if( vK == NULL ) + { + input.ki.wScan = ch; + input.ki.dwFlags = KEYEVENTF_UNICODE; + } + // Send virtual-key code + else + { + input.ki.wVk = vK; + + if( vK == VK_RCONTROL || vK == VK_RMENU || vK == VK_LEFT + || vK == VK_RIGHT || vK == VK_UP || vK == VK_DOWN ) + { + input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; + } + } + + if( keyup ) + { + input.ki.dwFlags |= KEYEVENTF_KEYUP; + } + + SendInput( 1, &input, sizeof( INPUT ) ); + + // Add latency between keydown/up to accomodate notepad input handling + if( !keyup && g_Notepad ) + { + std::this_thread::sleep_for( std::chrono::milliseconds( NOTEPAD_REFRESH ) ); + } +} + +//---------------------------------------------------------------------------- +// +// SendUnicodeKeyDown +// +//---------------------------------------------------------------------------- +void SendUnicodeKeyDown( const wchar_t ch ) +{ + PushInjection( NULL, ch ); + SendKeyInput ( NULL, ch ); +} + +//---------------------------------------------------------------------------- +// +// SendUnicodeKeyUp +// +//---------------------------------------------------------------------------- +void SendUnicodeKeyUp( const wchar_t ch ) +{ + PushInjection( NULL, ch ); + SendKeyInput ( NULL, ch, true ); +} + +//---------------------------------------------------------------------------- +// +// SendVirtualKeyDown +// +//---------------------------------------------------------------------------- +void SendVirtualKeyDown( const WORD vK ) +{ + PushInjection( vK, NULL ); + SendKeyInput ( vK, NULL ); +} + +//---------------------------------------------------------------------------- +// +// SendVirtualKeyUp +// +//---------------------------------------------------------------------------- +void SendVirtualKeyUp( const WORD vK ) +{ + PushInjection( vK, NULL ); + SendKeyInput ( vK, NULL, true ); +} + +//---------------------------------------------------------------------------- +// +// GetRandomNumber +// +//---------------------------------------------------------------------------- +unsigned int GetRandomNumber( unsigned int lower, unsigned int upper ) +{ + return lower + std::rand() % (upper - lower + 1); +} + +//---------------------------------------------------------------------------- +// +// BlockModifierKeys +// +//---------------------------------------------------------------------------- +int BlockModifierKeys() +{ + int blockDepth = 0; + const std::vector MODIFIERS = { VK_LSHIFT, VK_RSHIFT, + VK_LCONTROL, VK_RCONTROL, VK_LMENU, VK_RMENU }; + + if( (GetKeyState( VK_CAPITAL ) & 0x0001) != 0 ) + { + SendVirtualKeyDown( VK_CAPITAL ); + SendVirtualKeyUp ( VK_CAPITAL ); + } + for( auto modifier : MODIFIERS ) + { + if( (GetKeyState( modifier ) & 0x8000) != 0 ) + { + blockDepth++; + SendVirtualKeyUp( modifier ); + } + } + + return blockDepth; +} + +//---------------------------------------------------------------------------- +// +// GetClipboardSequence +// +//---------------------------------------------------------------------------- +DWORD GetClipboardSequence() +{ + DWORD sequence; + if( !OpenClipboard( nullptr ) ) + { + CloseClipboard(); + return 0; + } + sequence = GetClipboardSequenceNumber(); + CloseClipboard(); + return sequence; +} + +//---------------------------------------------------------------------------- +// +// GetClipboard +// +//---------------------------------------------------------------------------- +wchar_t* GetClipboard() +{ + // Confirm clipboard accessibility and data format + if( !OpenClipboard( nullptr ) && !IsClipboardFormatAvailable( CF_UNICODETEXT ) ) + { + CloseClipboard(); + return nullptr; + } + HANDLE hData = GetClipboardData( CF_UNICODETEXT ); + if( hData == nullptr ) + { + CloseClipboard(); + return nullptr; + } + + // Confirm clipboard size doesn't exceed MAX_INPUT_SIZE + size_t size = GlobalSize( hData ); + if( size <= 0 || size > MAX_INPUT_SIZE ) + { + GlobalUnlock( hData ); + CloseClipboard(); + return nullptr; + } + + const wchar_t* pData = static_cast(GlobalLock( hData )); + if( pData == nullptr ) + { + GlobalUnlock( hData ); + CloseClipboard(); + return nullptr; + } + + wchar_t* data = new wchar_t[size / sizeof(wchar_t)]; + wcscpy( data, pData ); + GlobalUnlock( hData ); + CloseClipboard(); + return data; +} + +//---------------------------------------------------------------------------- +// +// SetClipboard +// +//---------------------------------------------------------------------------- +bool SetClipboard( const wchar_t* data ) +{ + if( data == nullptr ) + { + return false; + } + if( !OpenClipboard( nullptr ) ) + { + CloseClipboard(); + return false; + } + EmptyClipboard(); + + size_t size = (wcslen( data ) + 1) * sizeof( wchar_t ); + HGLOBAL hData = GlobalAlloc( GMEM_MOVEABLE, size ); + if( hData == nullptr ) + { + CloseClipboard(); + return false; + } + + wchar_t* pData = static_cast(GlobalLock( hData )); + if( pData == nullptr ) + { + GlobalUnlock( hData ); + GlobalFree( hData ); + CloseClipboard(); + return false; + } + + wcscpy( pData, data ); + GlobalUnlock( hData ); + SetClipboardData( CF_UNICODETEXT, hData ); + CloseClipboard(); + return true; +} + +//---------------------------------------------------------------------------- +// +// GetBaselineIndentation +// +//---------------------------------------------------------------------------- +void GetBaselineIndentation() +{ + size_t len = 0; + size_t lastLen = 0; + bool resetCursor = true; + wchar_t* seekBuffer = nullptr; + static const WORD VK_C = static_cast(LOBYTE( VkKeyScan( L'c' ) )); + + // VS fakes newline indentation until the user adds input + SendVirtualKeyDown( VK_SPACE ); + SendVirtualKeyUp ( VK_SPACE ); + SendVirtualKeyDown( VK_BACK ); + SendVirtualKeyUp ( VK_BACK ); + + for( int i = 0; i < MAX_INDENT_DEPTH; i++ ) + { + SendVirtualKeyDown( VK_LSHIFT ); + SendVirtualKeyDown( VK_LEFT ); + SendVirtualKeyUp ( VK_LEFT ); + SendVirtualKeyUp ( VK_LSHIFT ); + + SendVirtualKeyDown( VK_LCONTROL ); + SendVirtualKeyDown( VK_C ); + SendVirtualKeyUp ( VK_C ); + SendVirtualKeyUp ( VK_LCONTROL ); + + std::this_thread::sleep_for( std::chrono::milliseconds( CLIPBOARD_REFRESH ) ); + + len = 0; + delete[] seekBuffer; + seekBuffer = GetClipboard(); + if( seekBuffer == nullptr ) + { + resetCursor = false; + break; + } + len = wcslen( seekBuffer ); + + if( g_LastClipboardSeq == GetClipboardSequence() ) + { + resetCursor = false; + break; + } + else if( seekBuffer[0] == L'\n' || seekBuffer[0] == L'\r' ) + { + break; + } + else if( len == lastLen ) + { + if( len == 0 ) + { + resetCursor = false; + } + break; + } + lastLen = len; + } + + if( resetCursor ) + { + SendVirtualKeyDown( VK_RIGHT ); + SendVirtualKeyUp ( VK_RIGHT ); + } + + // Extract line indentation + g_BaselineIndentation.clear(); + for( size_t i = 0; i < len; i++ ) + { + if( iswprint( seekBuffer[i] ) && seekBuffer[i] != L' ' ) + { + break; + } + else if( seekBuffer[i] == L'\t' || seekBuffer[i] == L' ' ) + { + g_BaselineIndentation.push_back( seekBuffer[i] ); + } + } + + delete[] seekBuffer; +} + +//---------------------------------------------------------------------------- +// +// InjectByClipboard +// +// Editors handle paste operations slowly so we use this method sparingly +// +//---------------------------------------------------------------------------- +wchar_t InjectByClipboard( wchar_t lastCh, wchar_t ch, const std::wstring& override = L"" ) +{ + int i = 0; + bool trim = false; + bool chunk = false; + std::wstring injection(1, ch); + static const WORD VK_V = static_cast(LOBYTE( VkKeyScan( L'v' ) )); + + if( override == L"" ) + { + if( ch == L'\n' && g_BaselineIndentation != L"" && g_BaselineIndentation != L"x" ) + { + injection.append( g_BaselineIndentation ); + } + + // VS absorbs pasted line indentation so we inject it as a chunk of indents and the first printable ch + if( lastCh == L'\n' && (ch == L'\t' || ch == L' ') ) + { + chunk = true; + for( i = 1; g_Index + i < g_TextLen; i++ ) + { + injection.push_back( g_Text[g_Index + i] ); + if( g_Text[g_Index + i] != L' ' && iswprint( g_Text[g_Index + i] ) ) + { + if( g_Text[g_Index + i] == L'[' ) + { + trim = true; + } + break; + } + } + } + } + + std::this_thread::sleep_for( std::chrono::milliseconds( CLIPBOARD_REFRESH ) ); + if( !SetClipboard( override == L"" ? injection.c_str() : override.c_str() ) ) + { + std::this_thread::sleep_for( std::chrono::milliseconds( CLIPBOARD_REFRESH ) ); + SetClipboard( override == L"" ? injection.c_str() : override.c_str() ); + } + std::this_thread::sleep_for( std::chrono::milliseconds( CLIPBOARD_REFRESH ) ); + + SendVirtualKeyDown( VK_LCONTROL ); + SendVirtualKeyDown( VK_V ); + SendVirtualKeyUp ( VK_V ); + SendVirtualKeyUp ( VK_LCONTROL ); + + std::this_thread::sleep_for( std::chrono::milliseconds( CLIPBOARD_REFRESH ) ); + + // Trim the last character from our chunk if it was [ + // Because it might be the start of a control keyword + if( trim ) + { + SendVirtualKeyDown( VK_BACK ); + SendVirtualKeyUp ( VK_BACK ); + + g_Index += static_cast(i) - 1; + return g_Text[g_Index]; + } + else if( chunk ) + { + g_Index += i; + return g_Text[g_Index]; + } + return NULL; +} + +//---------------------------------------------------------------------------- +// +// HandleControlKeyword +// +//---------------------------------------------------------------------------- +bool HandleControlKeyword() +{ + size_t controlClose = g_Text.find( L']', g_Index ); + size_t controlLen = controlClose - g_Index + 1; + + if( controlLen <= MAX_CONTROL_LEN ) + { + std::wstring control = g_Text.substr( g_Index, controlLen ); + + if( control == L"[end]" ) + { + g_End = true; + g_Index += controlLen; + g_TextSegments.push_back( g_Index ); + + // In standard mode, [end] is interpreted as an immediate kill signal + if( !g_UserDriven ) + { + g_EmitterState = KILL_STATE; + } + + return true; + } + else if( control.substr( 0, 7 ) == L"[pause:" ) + { + g_Index += controlLen; + + if( g_UserDriven ) + { + return true; + } + + std::wistringstream iss(control.substr( 7, control.length() - 2 )); + unsigned int time; + + if( iss >> time ) + { + if( time > 0 ) + { + // Pause but poll for termination + for( int i = 0; i < static_cast(1000 / DEMOTYPE_REFRESH * time); i++ ) + { + if( g_EmitterState == KILL_STATE ) + { + break; + } + std::this_thread::sleep_for( std::chrono::milliseconds( 50 ) ); + } + return true; + } + } + } + else if( control == L"[paste]" ) + { + size_t endControlOpen = g_Text.find( L"[/paste]", controlClose ); + if( endControlOpen != std::wstring::npos ) + { + size_t endControlClose = g_Text.find( L']', endControlOpen ); + size_t endControlLen = endControlClose - g_Index + 1; + + std::wstring pasteData = g_Text.substr( controlClose + 1, endControlOpen - controlClose - 1 ); + InjectByClipboard( NULL, NULL, pasteData ); + + g_Index += endControlLen; + return true; + } + } + else + { + if( control == L"[enter]" ) + { + SendVirtualKeyDown( VK_RETURN ); + SendVirtualKeyUp ( VK_RETURN ); + } + else if( control == L"[up]" ) + { + SendVirtualKeyDown( VK_UP ); + SendVirtualKeyUp ( VK_UP ); + } + else if( control == L"[down]" ) + { + SendVirtualKeyDown( VK_DOWN ); + SendVirtualKeyUp ( VK_DOWN ); + } + else if( control == L"[left]" ) + { + SendVirtualKeyDown( VK_LEFT ); + SendVirtualKeyUp ( VK_LEFT ); + } + else if( control == L"[right]" ) + { + SendVirtualKeyDown( VK_RIGHT ); + SendVirtualKeyUp ( VK_RIGHT ); + } + else + { + return false; + } + g_Index += controlLen; + return true; + } + } + return false; +} + +//---------------------------------------------------------------------------- +// +// HandleInjection +// +//---------------------------------------------------------------------------- +void HandleInjection( bool init = false ) +{ + static wchar_t lastCh = NULL; + + if( init ) + { + if( g_Index == 0 ) + { + g_TextSegments.clear(); + } + + lastCh = NULL; + GetBaselineIndentation(); + return; + } + + wchar_t ch = g_Text[g_Index]; + + if( ch == L'[' ) + { + if( HandleControlKeyword() ) + { + return; + } + } + + if( IsAutoFormatTrigger( lastCh, ch ) ) + { + wchar_t newCh = InjectByClipboard( lastCh, ch ); + if( newCh != NULL ) + { + ch = newCh; + } + } + else + { + SendUnicodeKeyDown( ch ); + SendUnicodeKeyUp ( ch ); + } + lastCh = ch; + g_Index++; +} + +//---------------------------------------------------------------------------- +// +// DemoTypeEmitter +// +//---------------------------------------------------------------------------- +void DemoTypeEmitter() +{ + const unsigned int speed = static_cast((MIN_TYPING_SPEED + MAX_TYPING_SPEED) - g_SpeedSlider); + const unsigned int variance = static_cast(speed * TYPING_VARIANCE); + + // Initialize the injection handler + HandleInjection( true ); + + while( g_EmitterState == ACTIVE_STATE && g_Index < g_TextLen ) + { + HandleInjection(); + + std::this_thread::sleep_for( std::chrono::milliseconds( + GetRandomNumber( max( speed - variance, 1 ), max( speed + variance, 1 ) ) ) ); + } + if( g_Index >= g_TextLen ) + { + g_Index = 0; + + // Synthesize [end] at end of script if no [end] is present + if( !g_End ) + { + g_End = true; + g_TextSegments.push_back( g_Index ); + } + } + + g_EmitterState = INACTIVE_STATE; + g_Kill = true; + { + std::lock_guard epochLock(g_EpochMutex); + } + g_EpochReady.notify_one(); +} + +//---------------------------------------------------------------------------- +// +// DemoTypeHookProc +// +//---------------------------------------------------------------------------- +LRESULT CALLBACK DemoTypeHookProc( int nCode, WPARAM wParam, LPARAM lParam ) +{ + static HWND hWndFocus = nullptr; + static int injectionRatio = 1; + static int blockDepth = 0; + + if( g_HookState == KILL_STATE ) + { + PostQuitMessage( 0 ); + return 1; + } + else if( g_HookState == START_STATE ) + { + g_HookState = INIT_STATE; + if( g_UserDriven ) + { + injectionRatio = min( 3, max( 1, static_cast(g_SpeedSlider / THIRD_TYPING_SPEED) + 1 ) ); + } + + hWndFocus = GetForegroundWindow(); + g_Notepad = IsWindowNotepad( hWndFocus ); + blockDepth = BlockModifierKeys(); + } + + if( nCode == HC_ACTION ) + { + KBDLLHOOKSTRUCT* pKbdStruct = reinterpret_cast(lParam); + + bool injected = IsInjected( pKbdStruct->vkCode, pKbdStruct->scanCode ); + + // Block non-injected input until we've negated all modifiers + if( g_HookState == INIT_STATE ) + { + if( g_Injections.empty() ) + { + g_HookState = ACTIVE_STATE; + { + std::lock_guard epochLock(g_EpochMutex); + } + g_EpochReady.notify_one(); + + if( g_UserDriven ) + { + // Set baseline indentation to a blocking flag + // Otherwise indentation seeking will trigger user-driven injection events + g_BaselineIndentation = INDENT_SEEK_FLAG; + + // Initialize the injection handler + HandleInjection( true ); + } + return 1; + } + } + else if( g_HookState == BLOCK_STATE ) + { + return 1; + } + + // Handle two possible kill signals: user inputted escape or focus change + if( (pKbdStruct->vkCode == VK_ESCAPE && !injected) || hWndFocus != GetForegroundWindow() ) + { + // Notify the controller that the hook is going to BLOCK_STATE and requesting kill + g_HookState = BLOCK_STATE; + g_Kill = true; + { + std::lock_guard epochLock(g_EpochMutex); + } + g_EpochReady.notify_one(); + + // In user-driven mode, we can kill the hook without controller approval + // In standard mode, we need to stay alive until the emitter is terminated + if( g_UserDriven ) + { + PostQuitMessage( 0 ); + } + + // Only pass through if the kill signal was user input after a focus change + if( hWndFocus != GetForegroundWindow() && injected ) + { + return 1; + } + } + else if( injected ) + { + PopInjection(); + } + else + { + switch( wParam ) + { + case WM_KEYUP: + // In user-driven mode, [end] needs to be acknowledged by the user with a space before proceeding to kill + if( pKbdStruct->vkCode == VK_SPACE && g_UserDriven && g_End ) + { + // Notify the controller that the hook is going to BLOCK_STATE and requesting kill + g_HookState = BLOCK_STATE; + g_Kill = true; + { + std::lock_guard epochLock(g_EpochMutex); + } + g_EpochReady.notify_one(); + + PostQuitMessage( 0 ); + } + else if( g_UserDriven ) + { + // Block up to the number of DEMOTYPE_HOTKEY keys + if( blockDepth > 0 ) + { + blockDepth--; + return 1; + } + else if( g_BaselineIndentation == INDENT_SEEK_FLAG ) + { + return 1; + } + + // Inject n keys per 1 input keys where n is injectionRatio + for( int i = 0; i < injectionRatio && g_Index < g_TextLen && !g_End; i++ ) + { + HandleInjection(); + } + if( g_Index >= g_TextLen ) + { + g_Index = 0; + + // Synthesize [end] at end of script if no [end] is present + if( !g_End ) + { + g_End = true; + g_TextSegments.push_back( g_Index ); + } + } + } + [[fallthrough]]; + case WM_KEYDOWN: + case WM_SYSKEYUP: + case WM_SYSKEYDOWN: + return 1; + } + } + } + return CallNextHookEx( g_hHook, nCode, wParam, lParam ); +} + +//---------------------------------------------------------------------------- +// +// StartDemoTypeHook +// +//---------------------------------------------------------------------------- +void StartDemoTypeHook() +{ + g_hHook = SetWindowsHookEx( WH_KEYBOARD_LL, DemoTypeHookProc, GetModuleHandle( nullptr ), 0 ); + if( g_hHook == nullptr ) + { + g_HookState = INACTIVE_STATE; + return; + } + + // Jump start the hook with an inert message to prevent a stall + KBDLLHOOKSTRUCT KbdStruct{}; + DemoTypeHookProc( HC_ACTION, 0, reinterpret_cast(&KbdStruct) ); + + MSG msg; + while( GetMessage( &msg, nullptr, 0, 0 ) ) + { + TranslateMessage( &msg ); + DispatchMessage( &msg ); + } + + UnhookWindowsHookEx( g_hHook ); + g_hHook = nullptr; + + // Clean up any trailing shift modifier from our injections + if( (GetKeyState( VK_LSHIFT ) & 0x8000) != 0 ) + { + SendVirtualKeyUp( VK_LSHIFT ); + } + + g_HookState = INACTIVE_STATE; +} + +//---------------------------------------------------------------------------- +// +// KillDemoTypeHook +// +//---------------------------------------------------------------------------- +void KillDemoTypeHook() +{ + if( g_HookState != INACTIVE_STATE ) + { + g_HookState = KILL_STATE; + SendVirtualKeyUp( VK_ESCAPE ); + } +} + +//---------------------------------------------------------------------------- +// +// DemoTypeController +// +//---------------------------------------------------------------------------- +void DemoTypeController() +{ + std::chrono::milliseconds timeout(DEMOTYPE_TIMEOUT); + + g_Injections.clear(); + g_End = false; + g_Kill = false; + g_Active = true; + g_HookState = START_STATE; + + std::thread( StartDemoTypeHook ).detach(); + + // Spool up the emitter + if( !g_UserDriven ) + { + std::unique_lock epochLock(g_EpochMutex); + if( g_EpochReady.wait_for( epochLock, timeout, + [] { return g_HookState == ACTIVE_STATE; } ) ) + { + g_EmitterState = ACTIVE_STATE; + std::thread( DemoTypeEmitter ).detach(); + } + else + { + KillDemoTypeHook(); + g_Active = false; + return; + } + } + + // Wait for kill request + { + std::unique_lock epochLock(g_EpochMutex); + g_EpochReady.wait( epochLock, [] { return g_Kill == true; } ); + } + + // Send kill messages + if( !g_UserDriven ) + { + if( g_EmitterState != INACTIVE_STATE ) + { + g_EmitterState = KILL_STATE; + g_HookState = BLOCK_STATE; + + std::unique_lock epochLock(g_EpochMutex); + g_EpochReady.wait_for( epochLock, timeout, [] { return g_EmitterState == INACTIVE_STATE; } ); + } + } + KillDemoTypeHook(); + + if( g_ClipboardCache != nullptr ) + { + SetClipboard( g_ClipboardCache ); + g_LastClipboardSeq = GetClipboardSequence(); + } + + // Upon kill, hop to the next text segment if kill wasn't triggered by an [end] + if( g_Index != 0 && !g_End ) + { + size_t nextEnd = g_Text.find( L"[end]", g_Index ); + if( nextEnd == std::wstring::npos ) + { + g_Index = 0; + } + else + { + g_Index = nextEnd + END_CONTROL_LEN; + g_TextSegments.push_back( g_Index ); + if( g_Index >= g_TextLen ) + { + g_Index = 0; + } + } + } + + g_Active = false; +} + +//---------------------------------------------------------------------------- +// +// TrimNewlineAroundControl +// +//---------------------------------------------------------------------------- +void TrimNewlineAroundControl( const std::wstring control, const bool trimLeft, const bool trimRight ) +{ + const size_t controlLen = control.length(); + + // Seek first occurrence of `control` in `g_Text` + size_t nextControl = g_Text.find( control ); + + // Loop through each occurrence of `control` in `g_Text` + while( nextControl != std::wstring::npos ) + { + // Erase the character to the left of `control` if it is a newline + if( trimLeft && nextControl > 0 && g_Text[nextControl - 1] == L'\n' ) + { + g_Text.erase( nextControl - 1, 1 ); + // Decrement `nextControl` to account for `g_Text` shrinking to left of `nextControl` + nextControl--; + } + + // Erase the character to the right of `control` if it is a newline + if( trimRight && (nextControl + controlLen) < g_Text.length() && g_Text[nextControl + controlLen] == L'\n' ) + { + g_Text.erase( nextControl + controlLen, 1 ); + } + + // Seek next occurrence of `control` in `g_Text` + nextControl = g_Text.find( control, nextControl + controlLen); + + // Shrink `g_Text` to new size on last pass + if( nextControl == std::wstring::npos ) + { + g_Text.shrink_to_fit(); + } + } +} + +//---------------------------------------------------------------------------- +// +// CleanDemoTypeText +// +//---------------------------------------------------------------------------- +bool CleanDemoTypeText() +{ + // Remove all unsupported characters from our text buffer + g_Text.erase( std::remove_if( g_Text.begin(), g_Text.end(), IsNotPrintable ), g_Text.end() ); + g_Text.shrink_to_fit(); + + // Remove the first character if it is a newline + if( g_Text.length() > 0 && g_Text[0] == L'\n' ) + { + g_Text.erase( 0, 1 ); + g_Text.shrink_to_fit(); + } + + // Trim a newline character to the left and right of each [end] control + TrimNewlineAroundControl( L"[end]", true, true ); + + // Trim a newline character to the right of each [paste] control + TrimNewlineAroundControl( L"[paste]", false, true ); + + // Trim a newline character to the left of each [/paste] control + TrimNewlineAroundControl( L"[/paste]", true, false ); + + // Remove any dangling whitespace after the last [end] + size_t lastEnd = g_Text.rfind( L"[end]" ); + if( lastEnd != std::wstring::npos ) + { + for( size_t i = lastEnd + END_CONTROL_LEN; i < g_Text.length(); i++ ) + { + if( iswprint( g_Text[i] ) && g_Text[i] != L' ' ) + { + break; + } + else if( i >= g_Text.length() - 1 ) + { + g_Text.erase( lastEnd + END_CONTROL_LEN ); + g_Text.shrink_to_fit(); + } + } + } + + g_TextLen = g_Text.length(); + if( g_TextLen > 0 ) + { + return true; + } + else + { + return false; + } +} + +//---------------------------------------------------------------------------- +// +// ResetDemoTypeClipboard +// +//---------------------------------------------------------------------------- +void ResetDemoTypeClipboard() +{ + if( g_Clipboard ) + { + g_Text.clear(); + g_Clipboard = false; + } +} + +//---------------------------------------------------------------------------- +// +// GetDemoTypeClipboard +// +//---------------------------------------------------------------------------- +bool GetDemoTypeClipboard() +{ + const int safetyPrefixLen = 7; + const wchar_t safetyPrefix[] = L"[start]"; + + // Check if we can reuse the clipboard cache + DWORD sequenceNum = GetClipboardSequence(); + if( g_LastClipboardSeq == sequenceNum && g_Clipboard ) + { + return true; + } + g_LastClipboardSeq = sequenceNum; + + delete[] g_ClipboardCache; + g_ClipboardCache = GetClipboard(); + + // Confirm clipboard data begins with the safety prefix + if( g_ClipboardCache == nullptr || g_ClipboardCache[0] != g_ClipboardCache[0] || g_ClipboardCache[safetyPrefixLen] == '\0' ) + { + ResetDemoTypeClipboard(); + return false; + } + for( int i = 1; i < safetyPrefixLen; i++ ) + { + if( g_ClipboardCache[i] != safetyPrefix[i] || g_ClipboardCache[i] == '\0' ) + { + ResetDemoTypeClipboard(); + return false; + } + } + + g_Text.assign( g_ClipboardCache + safetyPrefixLen ); + g_Clipboard = true; + g_Index = 0; + return CleanDemoTypeText(); +} + +//---------------------------------------------------------------------------- +// +// GetDemoTypeFile +// +// Supported encoding: UTF-8, UTF-8 with BOM, UTF-16LE, UTF-16BE +// +//---------------------------------------------------------------------------- +int GetDemoTypeFile( const TCHAR* filePath ) +{ + std::ifstream file(filePath, std::ios::binary); + if( !file.is_open() ) + { + return ERROR_LOADING_FILE; + } + + // Confirm file size doesn't exceed MAX_INPUT_SIZE + file.seekg( 0, std::ios::end ); + std::streampos size = file.tellg(); + file.seekg( 0, std::ios::beg ); + if( size <= 0 || size > MAX_INPUT_SIZE ) + { + return FILE_SIZE_OVERFLOW; + } + + // Grab the potential Byte Order Mark + // Which identifies the encoding pattern + char byteOrderMark[3]; + file.read( byteOrderMark, 3 ); + file.seekg( 0, std::ios::beg ); + + // UTF-16 is a variable-length character encoding pattern + // - code points are encoded with one or two 16-bit code units + // - 16-bit code units are composed of byte pairs subject to endianness + // - Little-endian Byte Order Mark {0xFF, 0xFE, ...} + // - Big-endian Byte Order Mark {0xFE, 0xFF, ...} + + // UTF-8 is a variable-length character encoding pattern + // - code points are encoded with one to four 8-bit code units + // - optional Byte Order Mark {0xEF, 0xBB, 0xBF, ...} + + // UTF-16LE + if( byteOrderMark[0] == static_cast(0xFF) + && byteOrderMark[1] == static_cast(0xFE) ) + { + // Truncate the Byte Order Mark + file.seekg( 2 ); + + char bytePair[2]; + wchar_t codeUnit; + while( file.read( bytePair, 2 ) ) + { + // Squash each little-endian byte pair into a 2-byte code unit + // if bytePair[0] = 0xff + // if bytePair[1] = 0x00 + // codeUnit = 0x00ff + codeUnit = (static_cast(bytePair[1]) << 8) + | static_cast(bytePair[0]); + + g_Text += codeUnit; + } + } + // UTF-16BE + else if( byteOrderMark[0] == static_cast(0xFE) + && byteOrderMark[1] == static_cast(0xFF) ) + { + // Truncate the Byte Order Mark + file.seekg( 2 ); + + char bytePair[2]; + wchar_t codeUnit; + while( file.read( bytePair, 2 ) ) + { + // Squash each big-endian byte pair into a 2-byte code unit + // if bytePair[0] = 0xff + // if bytePair[1] = 0x00 + // codeUnit = 0xff00 + codeUnit = (static_cast(bytePair[0]) << 8) + | static_cast(bytePair[1]); + + g_Text += codeUnit; + } + } + // UTF-8 + else + { + // If UTF-8 with BOM, truncate the Byte Order Mark + if( byteOrderMark[0] == static_cast(0xEF) + && byteOrderMark[1] == static_cast(0xBB) + && byteOrderMark[2] == static_cast(0xBF) ) + { + file.seekg( 3 ); + } + + std::stringstream buffer; + buffer << file.rdbuf(); + std::string narrowText = buffer.str(); + + // Determine the size our wide string will need to be to accomodate the conversion + int wideSize = MultiByteToWideChar( CP_UTF8, 0, narrowText.c_str(), -1, nullptr, 0 ); + if( wideSize <= 0 ) + { + return ERROR_LOADING_FILE; + } + + g_Text.resize( wideSize ); + + // Map the multi-byte capable char string onto the wide char string + if( MultiByteToWideChar( CP_UTF8, 0, narrowText.c_str(), -1, &g_Text[0], wideSize ) <= 0 ) + { + return ERROR_LOADING_FILE; + } + } + + g_Index = 0; + return CleanDemoTypeText() ? 0 : UNKNOWN_FILE_DATA; +} + +//---------------------------------------------------------------------------- +// +// ResetDemoTypeIndex +// +//---------------------------------------------------------------------------- +void ResetDemoTypeIndex() +{ + size_t newIndex = 0; + + if( !g_TextSegments.empty() && g_Index <= g_TextSegments.back() ) + { + g_TextSegments.pop_back(); + } + if( !g_TextSegments.empty() ) + { + newIndex = g_TextSegments.back(); + } + + g_Index = newIndex; +} + +//---------------------------------------------------------------------------- +// +// StartDemoType +// +//---------------------------------------------------------------------------- +int StartDemoType( const TCHAR* filePath, const DWORD speedSlider, const BOOLEAN userDriven ) +{ + static FILETIME lastFileWrite = {0}; + + if( g_Active ) + { + return -1; + } + + if( !GetDemoTypeClipboard() ) + { + if( _tcslen( filePath ) == 0 ) + { + return NO_FILE_SPECIFIED; + } + + if( _tcscmp( g_LastFilePath, filePath ) != 0 ) + { + _tcscpy( g_LastFilePath, filePath ); + // Trigger (re)capture of lastFileWrite + g_Text = L"x"; + g_Index = 0; + lastFileWrite = {0}; + } + + // Check if the file has been updated since last read + if( !g_Text.empty() ) + { + HANDLE hFile = CreateFile( filePath, GENERIC_READ, FILE_SHARE_READ, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr ); + if( hFile == INVALID_HANDLE_VALUE ) + { + CloseHandle( hFile ); + return ERROR_LOADING_FILE; + } + + FILETIME latestFileWrite; + if( GetFileTime( hFile, nullptr, nullptr, &latestFileWrite ) ) + { + if( CompareFileTime( &latestFileWrite, &lastFileWrite ) == 1 ) + { + g_Text.clear(); + lastFileWrite = latestFileWrite; + } + } + CloseHandle( hFile ); + } + + if( g_Text.empty() ) + { + switch( GetDemoTypeFile( filePath ) ) + { + case ERROR_LOADING_FILE: + return ERROR_LOADING_FILE; + + case FILE_SIZE_OVERFLOW: + return FILE_SIZE_OVERFLOW; + + case UNKNOWN_FILE_DATA: + return UNKNOWN_FILE_DATA; + } + } + } + + g_UserDriven = userDriven; + g_SpeedSlider = speedSlider; + std::thread( DemoTypeController ).detach(); + return 0; +} \ No newline at end of file diff --git a/src/modules/ZoomIt/ZoomIt/DemoType.h b/src/modules/ZoomIt/ZoomIt/DemoType.h new file mode 100644 index 000000000000..dbfdd3500515 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/DemoType.h @@ -0,0 +1,26 @@ +//============================================================================ +// +// Zoomit +// Copyright (C) Mark Russinovich +// Sysinternals - www.sysinternals.com +// +// DemoType allows the presenter to synthesize keystrokes from a script +// +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +//============================================================================ + +#pragma once + +#define MAX_INPUT_SIZE 1048576 // 1 MiB + +#define MAX_TYPING_SPEED 10 // ms +#define MIN_TYPING_SPEED 100 // ms + +#define ERROR_LOADING_FILE 1 +#define NO_FILE_SPECIFIED 2 +#define FILE_SIZE_OVERFLOW 3 +#define UNKNOWN_FILE_DATA 4 + +void ResetDemoTypeIndex(); +int StartDemoType( const TCHAR* filePath, const DWORD speedSlider, const BOOLEAN userDriven ); \ No newline at end of file diff --git a/src/modules/ZoomIt/ZoomIt/Registry.h b/src/modules/ZoomIt/ZoomIt/Registry.h new file mode 100644 index 000000000000..1a2f8f2fc883 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/Registry.h @@ -0,0 +1,324 @@ +//============================================================================ +// +// Process Explorer +// Copyright (C) 1999-2005 Mark Russinovich +// Sysinternals - www.sysinternals.com +// +// Registry.h +// +//============================================================================ +#pragma once + +typedef enum { + SETTING_TYPE_DWORD, + SETTING_TYPE_BOOLEAN, + SETTING_TYPE_DOUBLE, + SETTING_TYPE_WORD, + SETTING_TYPE_STRING, + SETTING_TYPE_DWORD_ARRAY, + SETTING_TYPE_WORD_ARRAY, + SETTING_TYPE_BINARY +} REG_SETTING_TYPE; + +typedef struct { + PCTSTR ValueName; + REG_SETTING_TYPE Type; + DWORD Size; // Optional + PVOID Setting; + double DefaultSetting; +} REG_SETTING, *PREG_SETTING; + + +class ClassRegistry { + +private: + PTCHAR m_KeyName; + HKEY hKey; + +public: + ClassRegistry( PCTSTR KeyName ) + { + m_KeyName = _tcsdup( KeyName ); + hKey = NULL; + } + + ~ClassRegistry() + { + free( m_KeyName ); + } + + void ReadRegSettings( PREG_SETTING Settings ) + { + PREG_SETTING curSetting; + + hKey = NULL; + RegOpenKeyEx(HKEY_CURRENT_USER, + m_KeyName, 0, KEY_READ, &hKey ); + curSetting = Settings; + while( curSetting->ValueName ) { + + switch( curSetting->Type ) { + case SETTING_TYPE_DWORD: + ReadValue( curSetting->ValueName, static_cast(curSetting->Setting), + static_cast(curSetting->DefaultSetting)); + break; + case SETTING_TYPE_BOOLEAN: + ReadValue( curSetting->ValueName, static_cast(curSetting->Setting), + static_cast(curSetting->DefaultSetting)); + break; + case SETTING_TYPE_DOUBLE: + ReadValue( curSetting->ValueName, static_cast(curSetting->Setting), + curSetting->DefaultSetting ); + break; + case SETTING_TYPE_WORD: + ReadValue( curSetting->ValueName, static_cast(curSetting->Setting), + static_cast(curSetting->DefaultSetting)); + break; + case SETTING_TYPE_STRING: + ReadValue( curSetting->ValueName, static_cast(curSetting->Setting), + curSetting->Size, reinterpret_cast(static_cast(curSetting->DefaultSetting))); + break; + case SETTING_TYPE_DWORD_ARRAY: + ReadValueArray( curSetting->ValueName, curSetting->Size/sizeof DWORD, + static_cast(curSetting->Setting)); + break; + case SETTING_TYPE_WORD_ARRAY: + ReadValueArray( curSetting->ValueName, curSetting->Size/sizeof(short), + static_cast(curSetting->Setting)); + break; + case SETTING_TYPE_BINARY: + ReadValueBinary( curSetting->ValueName, static_cast(curSetting->Setting), + curSetting->Size ); + break; + } + curSetting++; + } + if( hKey ) { + + RegCloseKey( hKey ); + } + } + void WriteRegSettings( PREG_SETTING Settings ) + { + PREG_SETTING curSetting; + + if( !RegCreateKeyEx(HKEY_CURRENT_USER, + m_KeyName, NULL, NULL, 0, KEY_WRITE, NULL, &hKey, NULL )) { + + curSetting = Settings; + while( curSetting->ValueName ) { + + switch( curSetting->Type ) { + case SETTING_TYPE_DWORD: + WriteValue( curSetting->ValueName, *static_cast(curSetting->Setting)); + break; + case SETTING_TYPE_BOOLEAN: + WriteValue( curSetting->ValueName, *static_cast(curSetting->Setting)); + break; + case SETTING_TYPE_DOUBLE: + WriteValue( curSetting->ValueName, *static_cast(curSetting->Setting)); + break; + case SETTING_TYPE_WORD: + WriteValue( curSetting->ValueName, *static_cast(curSetting->Setting)); + break; + case SETTING_TYPE_STRING: + WriteValue( curSetting->ValueName, static_cast(curSetting->Setting)); + break; + case SETTING_TYPE_DWORD_ARRAY: + WriteValueArray( curSetting->ValueName, curSetting->Size/sizeof DWORD, + static_cast(curSetting->Setting)); + break; + case SETTING_TYPE_WORD_ARRAY: + WriteValueArray( curSetting->ValueName, curSetting->Size/sizeof(short), + static_cast(curSetting->Setting)); + break; + case SETTING_TYPE_BINARY: + WriteValueBinary( curSetting->ValueName, static_cast(curSetting->Setting), + curSetting->Size ); + break; + } + curSetting++; + } + RegCloseKey( hKey ); + } + } + +private: + // Reads + void ReadValue( PCTSTR ValueName, PDWORD Value, DWORD Default = 0 ) + { + DWORD length = sizeof(DWORD); + if( RegQueryValueEx( hKey, ValueName, NULL, NULL, reinterpret_cast(Value), + &length )) { + + *Value = Default; + } + } + void ReadValue( PCTSTR ValueName, PBOOLEAN Value, BOOLEAN Default = FALSE ) + { + DWORD length = sizeof(DWORD); + DWORD val = static_cast(*Value); + if( RegQueryValueEx( hKey, ValueName, NULL, NULL, reinterpret_cast(&val), + &length )) { + + *Value = Default; + + } else { + + *Value = static_cast(val); + } + } + void ReadValue( PCTSTR ValueName, short *Value, short Default = 0 ) + { + DWORD length = sizeof(DWORD); + DWORD val = static_cast(*Value); + if( RegQueryValueEx( hKey, ValueName, NULL, NULL, reinterpret_cast(&val), + &length )) { + + *Value = Default; + + } else { + + *Value = static_cast(val); + } + } + void ReadValue( PCTSTR ValueName, double *Value, double Default = 0.0 ) + { + DWORD length = sizeof(double); + if( RegQueryValueEx( hKey, ValueName, NULL, NULL, reinterpret_cast(Value), + &length )) { + + *Value = Default; + + } + } + void ReadValue( PCTSTR ValueName, PTCHAR Value, DWORD Length, PCTSTR Default ) + { + if( RegQueryValueEx( hKey, ValueName, NULL, NULL, reinterpret_cast(Value), + &Length ) && Default ) { + + _tcscpy_s( Value, Length, Default ); + } + } + void ReadValueBinary( PCTSTR ValueName, PBYTE Value, DWORD Length ) + { + RegQueryValueEx( hKey, ValueName, NULL, NULL, Value, + &Length ); + } + void ReadValueArray( PCTSTR ValueName, DWORD Number, PDWORD Entries ) + { + HKEY hSubKey; + TCHAR subVal[16]; + DWORD length; + + if( !RegOpenKeyEx(hKey, + ValueName, 0, KEY_READ, &hSubKey )) { + + for( DWORD i = 0; i < Number; i++ ) { + + length = sizeof(DWORD); + _stprintf_s( subVal, _countof(subVal), _T("%d"), i ); + RegQueryValueEx( hSubKey, subVal, NULL, NULL, reinterpret_cast(&Entries[i]), &length); + } + RegCloseKey( hSubKey ); + } + } + void ReadValueArray( PCTSTR ValueName, DWORD Number, PWORD Entries ) + { + HKEY hSubKey; + TCHAR subVal[16]; + DWORD length; + DWORD val; + + if( !RegOpenKeyEx(hKey, + ValueName, 0, KEY_READ, &hSubKey )) { + + for( DWORD i = 0; i < Number; i++ ) { + + length = sizeof(DWORD); + _stprintf_s( subVal, _countof(subVal), _T("%d"), i ); + if( !RegQueryValueEx( hSubKey, subVal, NULL, NULL, reinterpret_cast(&val), &length)) { + + Entries[i] = static_cast(val); + + } + } + RegCloseKey( hSubKey ); + } + } + + // Writes + void WriteValue( PCTSTR ValueName, DWORD Value ) + { + RegSetValueEx( hKey, ValueName, 0, REG_DWORD, reinterpret_cast(&Value), + sizeof(DWORD)); + } + void WriteValue( PCTSTR ValueName, short Value ) + { + DWORD val = static_cast(Value); + RegSetValueEx( hKey, ValueName, 0, REG_DWORD, reinterpret_cast(&val), + sizeof(DWORD)); + } + void WriteValue( PCTSTR ValueName, BOOLEAN Value ) + { + DWORD val = static_cast(Value); + RegSetValueEx( hKey, ValueName, 0, REG_DWORD, reinterpret_cast(&val), + sizeof(DWORD)); + } + void WriteValue( PCTSTR ValueName, double Value ) + { + RegSetValueEx( hKey, ValueName, 0, REG_BINARY, reinterpret_cast(&Value), + sizeof(double)); + } + void WriteValue( PCTSTR ValueName, PTCHAR Value ) + { + RegSetValueEx( hKey, ValueName, 0, REG_SZ, reinterpret_cast(Value), + static_cast(_tcslen( Value )) * sizeof(TCHAR)); + } + void WriteValueBinary( PCTSTR ValueName, PBYTE Value, DWORD Length ) + { + RegSetValueEx( hKey, ValueName, 0, REG_BINARY, Value, + Length ); + } + void WriteValueArray( PCTSTR ValueName, DWORD Number, PDWORD Entries ) + { + HKEY hSubKey; + TCHAR subVal[16]; + + if( !RegCreateKeyEx(hKey, + ValueName, NULL, NULL, 0, KEY_WRITE, NULL, &hSubKey, NULL )) { + + for( DWORD i = 0; i < Number; i++ ) { + + _stprintf_s( subVal, _countof(subVal), _T("%d"), i ); + if( Entries[i] ) + RegSetValueEx( hSubKey, subVal, 0, REG_DWORD, reinterpret_cast(&Entries[i]), sizeof(DWORD)); + else + RegDeleteValue( hSubKey, subVal ); + } + RegCloseKey( hSubKey ); + } + } + void WriteValueArray( PCTSTR ValueName, DWORD Number, PWORD Entries ) + { + HKEY hSubKey; + TCHAR subVal[16]; + DWORD val; + + if( !RegCreateKeyEx(hKey, + ValueName, NULL, NULL, 0, KEY_WRITE, NULL, &hSubKey, NULL )) { + + for( DWORD i = 0; i < Number; i++ ) { + + _stprintf_s( subVal, _countof(subVal), _T("%d"), i ); + val = static_cast(Entries[i]); + if( Entries[i] ) + RegSetValueEx( hSubKey, subVal, 0, REG_DWORD, reinterpret_cast(&val), sizeof(DWORD)); + else + RegDeleteValue( hSubKey, subVal ); + } + RegCloseKey( hSubKey ); + } + } + +}; diff --git a/src/modules/ZoomIt/ZoomIt/SelectRectangle.cpp b/src/modules/ZoomIt/ZoomIt/SelectRectangle.cpp new file mode 100644 index 000000000000..cfab73996c96 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/SelectRectangle.cpp @@ -0,0 +1,269 @@ +//============================================================================== +// +// Zoomit +// Sysinternals - www.sysinternals.com +// +// Class to select a recording rectangle and show it while recording +// +//============================================================================== +#include "pch.h" +#include "SelectRectangle.h" +#include "Utility.h" +#include "WindowsVersions.h" + +//---------------------------------------------------------------------------- +// +// SelectRectangle::Start +// +//---------------------------------------------------------------------------- +bool SelectRectangle::Start( HWND ownerWindow, bool fullMonitor ) +{ + WNDCLASSW windowClass{}; + windowClass.lpfnWndProc = []( HWND window, UINT message, WPARAM wordParam, LPARAM longParam ) -> LRESULT + { + if( message == WM_NCCREATE ) + { + auto createStruct = reinterpret_cast(longParam); + SetWindowLongPtrW( window, GWLP_USERDATA, reinterpret_cast(createStruct->lpCreateParams) ); + return TRUE; + } + + auto self = reinterpret_cast(GetWindowLongPtrW( window, GWLP_USERDATA )); + return self->WindowProc( window, message, wordParam, longParam ); + }; + windowClass.hInstance = GetModuleHandle( nullptr ); + windowClass.hCursor = LoadCursorW( nullptr, IDC_CROSS ); + windowClass.hbrBackground = static_cast(GetStockObject( BLACK_BRUSH )); + windowClass.lpszClassName = m_className; + if( RegisterClassW( &windowClass ) == 0 ) + { + THROW_LAST_ERROR_IF( GetLastError() != ERROR_CLASS_ALREADY_EXISTS ); + + WNDCLASSW existingClass{}; + THROW_IF_WIN32_BOOL_FALSE( GetClassInfoW( GetModuleHandle( nullptr ), m_className, &existingClass ) ); + THROW_LAST_ERROR_IF( existingClass.lpfnWndProc != windowClass.lpfnWndProc ); + } + + m_cancel = false; + auto rect = GetMonitorRectFromCursor(); + m_window = wil::unique_hwnd( CreateWindowExW( WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TOPMOST, m_className, nullptr, WS_POPUP, + rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, ownerWindow, + nullptr, nullptr, this ) ); + THROW_LAST_ERROR_IF_NULL( m_window.get() ); + + if( fullMonitor ) + { + m_selectedRect = rect; + ShowSelected(); + } + else + { + SetLayeredWindowAttributes( m_window.get(), 0, Alpha(), LWA_ALPHA ); + } + + ShowWindow( m_window.get(), SW_SHOW ); + SetForegroundWindow( m_window.get() ); + + if( !fullMonitor ) + { + GetClipCursor( &m_oldClipRect ); + ClipCursor( &rect ); + m_setClip = true; + } + + MSG message; + while( GetMessageW( &message, nullptr, 0, 0 ) != 0 ) + { + TranslateMessage( &message ); + DispatchMessageW( &message ); + if( m_cancel ) + { + return false; + } + if( m_selected ) + { + break; + } + } + return true; +} + +//---------------------------------------------------------------------------- +// +// SelectRectangle::Stop +// +//---------------------------------------------------------------------------- +void SelectRectangle::Stop() +{ + if( m_setClip ) + { + ClipCursor( &m_oldClipRect ); + m_setClip = false; + } + m_window.reset(); + m_selected = false; + m_selectedRect = {}; + m_cancel = true; +} + +//---------------------------------------------------------------------------- +// +// SelectRectangle::ShowSelected +// +//---------------------------------------------------------------------------- +void SelectRectangle::ShowSelected() +{ + m_selected = true; + + // Set the alpha to match the Windows graphics capture API yellow border + // and set the window to be transparent and disabled, so it will be skipped + // for hit testing and as a candidate for the next foreground window. + SetLayeredWindowAttributes( m_window.get(), 0, 191, LWA_ALPHA ); + SetWindowLong( m_window.get(), GWL_EXSTYLE, GetWindowLong( m_window.get(), GWL_EXSTYLE ) | WS_EX_TRANSPARENT ); + EnableWindow( m_window.get(), FALSE ); + + POINT point{ m_selectedRect.left, m_selectedRect.top }; + auto rect = m_selectedRect; + OffsetRect( &rect, -rect.left, -rect.top ); + int width = ScaleForDpi( 2, m_dpi ); + + // Draw the selection border outside the selected rectangle on builds lower + // than Windows 11 22H2 because the graphics capture API does not skip + // windows if layered, meaning this yellow border will be captured. + if( GetWindowsBuild( nullptr ) < BUILD_WINDOWS_11_22H2 ) + { + InflateRect( &rect, width, width ); + OffsetRect( &rect, -rect.left, -rect.top ); + point.x -= width; + point.y -= width; + } + + // Resize the window to the selection rectangle and translate the position. + RECT windowRect; + GetWindowRect( m_window.get(), &windowRect ); + point.x += windowRect.left; + point.y += windowRect.top; + MoveWindow( m_window.get(), point.x, point.y, rect.right, rect.bottom, true ); + + // Use a region to keep everything but the border transparent. + wil::unique_hrgn region{CreateRectRgnIndirect( &rect )}; + InflateRect( &rect, -width, -width ); + wil::unique_hrgn insideRegion{CreateRectRgnIndirect( &rect )}; + CombineRgn( region.get(), region.get(), insideRegion.get(), RGN_XOR ); + SetWindowRgn( m_window.get(), region.release(), true ); +} + +//---------------------------------------------------------------------------- +// +// SelectRectangle::UpdateOwner +// +//---------------------------------------------------------------------------- +void SelectRectangle::UpdateOwner( HWND window ) +{ + if( m_window != nullptr ) + { + SetWindowLongPtr( m_window.get(), GWLP_HWNDPARENT, reinterpret_cast(window) ); + SetWindowPos( m_window.get(), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE ); + } +} + +//---------------------------------------------------------------------------- +// +// SelectRectangle::WindowProc +// +//---------------------------------------------------------------------------- +LRESULT SelectRectangle::WindowProc( HWND window, UINT message, WPARAM wordParam, LPARAM longParam ) +{ + switch( message ) + { + case WM_CREATE: + m_dpi = GetDpiForWindowHelper( window ); + SetWindowDisplayAffinity( window, WDA_EXCLUDEFROMCAPTURE ); + return 0; + + case WM_DESTROY: + Stop(); + return 0; + + case WM_LBUTTONDOWN: + { + SetCapture( window ); + + m_startPoint = { GET_X_LPARAM( longParam ), GET_Y_LPARAM( longParam ) }; + [[fallthrough]]; + } + case WM_MOUSEMOVE: + if( GetCapture() == window ) + { + RECT rect; + GetClientRect( window, &rect ); + POINT point{ GET_X_LPARAM( longParam ), GET_Y_LPARAM( longParam ) }; + m_selectedRect = ForceRectInBounds( RectFromPointsMinSize( m_startPoint, point, MinSize() ), rect ); + + // Use a region to carve out the selected rectangle. + wil::unique_hrgn region{CreateRectRgnIndirect( &m_selectedRect )}; + wil::unique_hrgn clientRegion{CreateRectRgnIndirect( &rect )}; + CombineRgn( region.get(), region.get(), clientRegion.get(), RGN_XOR ); + SetWindowRgn( window, region.release(), true ); + } + return 0; + + case WM_KEYDOWN: + if( wordParam == VK_ESCAPE ) + { + Stop(); + } + return 0; + + case WM_KILLFOCUS: + if( !m_selected ) + { + Stop(); + } + return 0; + + case WM_LBUTTONUP: + { + if( m_setClip ) + { + ClipCursor( &m_oldClipRect ); + m_setClip = false; + } + ReleaseCapture(); + + ShowSelected(); + return 0; + } + case WM_NCHITTEST: + if( m_selected ) + { + return HTTRANSPARENT; + } + break; + + case WM_PAINT: + if( m_selected ) + { + PAINTSTRUCT paint; + auto deviceContext = BeginPaint( window, &paint ); + + RECT rect; + GetClientRect( window, &rect ); + + // Draw a border matching the Windows graphics capture API border. + // The outer frame is yellow and two logical pixels wide, while the + // inner is black and 1 logical pixel wide. + wil::unique_hbrush brush{CreateSolidBrush( RGB( 255, 222, 0 ) )}; + FillRect( deviceContext, &rect, brush.get() ); + int width = ScaleForDpi( 1, m_dpi ); + InflateRect( &rect, -width, -width ); + FillRect( deviceContext, &rect, static_cast(GetStockObject( BLACK_BRUSH )) ); + + EndPaint( window, &paint ); + return 0; + } + break; + } + + return DefWindowProcW( window, message, wordParam, longParam ); +} diff --git a/src/modules/ZoomIt/ZoomIt/SelectRectangle.h b/src/modules/ZoomIt/ZoomIt/SelectRectangle.h new file mode 100644 index 000000000000..aa31b6c53041 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/SelectRectangle.h @@ -0,0 +1,44 @@ +//============================================================================== +// +// Zoomit +// Sysinternals - www.sysinternals.com +// +// Class to select a recording rectangle and show it while recording +// +//============================================================================== +#pragma once + +#include "pch.h" + +class SelectRectangle +{ +public: + ~SelectRectangle() { Stop(); }; + + void Alpha( BYTE alpha ) { m_alpha = alpha; } + BYTE Alpha() const { return m_alpha; } + void MinSize( int minSize ) { m_minSize = minSize; } + int MinSize() const { return m_minSize; } + RECT SelectedRect() const { return m_selectedRect; } + + bool Start( HWND ownerWindow = nullptr, bool fullMonitor = false ); + void Stop(); + void UpdateOwner( HWND window ); + +private: + BYTE m_alpha = 176; + int m_minSize = 34; + RECT m_selectedRect{}; + + bool m_cancel = false; + const wchar_t* m_className = L"ZoomitSelectRectangle"; + UINT m_dpi{}; + RECT m_oldClipRect{}; + bool m_selected{ false }; + bool m_setClip{ false }; + POINT m_startPoint{}; + wil::unique_hwnd m_window; + + void ShowSelected(); + LRESULT WindowProc( HWND window, UINT message, WPARAM wordParam, LPARAM longParam ); +}; diff --git a/src/modules/ZoomIt/ZoomIt/Utility.cpp b/src/modules/ZoomIt/ZoomIt/Utility.cpp new file mode 100644 index 000000000000..ccc72ad752e3 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/Utility.cpp @@ -0,0 +1,153 @@ +//============================================================================== +// +// Zoomit +// Sysinternals - www.sysinternals.com +// +// Utility functions +// +//============================================================================== +#include "pch.h" +#include "Utility.h" + +//---------------------------------------------------------------------------- +// +// ForceRectInBounds +// +//---------------------------------------------------------------------------- +RECT ForceRectInBounds( RECT rect, const RECT& bounds ) +{ + if( rect.left < bounds.left ) + { + rect.right += bounds.left - rect.left; + rect.left = bounds.left; + } + if( rect.top < bounds.top ) + { + rect.bottom += bounds.top - rect.top; + rect.top = bounds.top; + } + if( rect.right > bounds.right ) + { + rect.left -= rect.right - bounds.right; + rect.right = bounds.right; + } + if( rect.bottom > bounds.bottom ) + { + rect.top -= rect.bottom - bounds.bottom; + rect.bottom = bounds.bottom; + } + return rect; +} + +//---------------------------------------------------------------------------- +// +// GetDpiForWindow +// +//---------------------------------------------------------------------------- +UINT GetDpiForWindowHelper( HWND window ) +{ + auto function = reinterpret_cast(GetProcAddress( GetModuleHandleW( L"user32.dll" ), "GetDpiForWindow" )); + if( function ) + { + return function( window ); + } + + wil::unique_hdc hdc{GetDC( nullptr )}; + return static_cast(GetDeviceCaps( hdc.get(), LOGPIXELSX )); +} + +//---------------------------------------------------------------------------- +// +// GetMonitorRectFromCursor +// +//---------------------------------------------------------------------------- +RECT GetMonitorRectFromCursor() +{ + POINT point; + GetCursorPos( &point ); + MONITORINFO monitorInfo{}; + monitorInfo.cbSize = sizeof( monitorInfo ); + GetMonitorInfoW( MonitorFromPoint( point, MONITOR_DEFAULTTONEAREST ), &monitorInfo ); + return monitorInfo.rcMonitor; +} + +//---------------------------------------------------------------------------- +// +// RectFromPointsMinSize +// +//---------------------------------------------------------------------------- +#ifdef _MSC_VER + // avoid making RectFromPointsMinSize constexpr since that leads to link errors + #pragma warning(push) + #pragma warning(disable: 26497) +#endif + +RECT RectFromPointsMinSize( POINT a, POINT b, LONG minSize ) +{ + RECT rect; + if( a.x <= b.x ) + { + rect.left = a.x; + rect.right = b.x + 1; + if( (rect.right - rect.left) < minSize ) + { + rect.right = rect.left + minSize; + } + } + else + { + rect.left = b.x; + rect.right = a.x + 1; + if( (rect.right - rect.left) < minSize ) + { + rect.left = rect.right - minSize; + } + } + if( a.y <= b.y ) + { + rect.top = a.y; + rect.bottom = b.y + 1; + if( (rect.bottom - rect.top) < minSize ) + { + rect.bottom = rect.top + minSize; + } + } + else + { + rect.top = b.y; + rect.bottom = a.y + 1; + if( (rect.bottom - rect.top) < minSize ) + { + rect.top = rect.bottom - minSize; + } + } + return rect; +} +#ifdef _MSC_VER + #pragma warning(pop) +#endif +//---------------------------------------------------------------------------- +// +// ScaleForDpi +// +//---------------------------------------------------------------------------- +int ScaleForDpi( int value, UINT dpi ) +{ + return MulDiv( value, static_cast(dpi), USER_DEFAULT_SCREEN_DPI ); +} + +//---------------------------------------------------------------------------- +// +// ScalePointInRects +// +//---------------------------------------------------------------------------- +POINT ScalePointInRects( POINT point, const RECT& source, const RECT& target ) +{ + const SIZE sourceSize{ source.right - source.left, source.bottom - source.top }; + const POINT sourceCenter{ source.left + sourceSize.cx / 2, source.top + sourceSize.cy / 2 }; + const SIZE targetSize{ target.right - target.left, target.bottom - target.top }; + const POINT targetCenter{ target.left + targetSize.cx / 2, target.top + targetSize.cy / 2 }; + + return { targetCenter.x + MulDiv( point.x - sourceCenter.x, targetSize.cx, sourceSize.cx ), + targetCenter.y + MulDiv( point.y - sourceCenter.y, targetSize.cy, sourceSize.cy ) }; +} diff --git a/src/modules/ZoomIt/ZoomIt/Utility.h b/src/modules/ZoomIt/ZoomIt/Utility.h new file mode 100644 index 000000000000..a78ecdf14e20 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/Utility.h @@ -0,0 +1,18 @@ +//============================================================================== +// +// Zoomit +// Sysinternals - www.sysinternals.com +// +// Utility functions +// +//============================================================================== +#pragma once + +#include "pch.h" + +RECT ForceRectInBounds( RECT rect, const RECT& bounds ); +UINT GetDpiForWindowHelper( HWND window ); +RECT GetMonitorRectFromCursor(); +RECT RectFromPointsMinSize( POINT a, POINT b, LONG minSize ); +int ScaleForDpi( int value, UINT dpi ); +POINT ScalePointInRects( POINT point, const RECT& source, const RECT& target ); diff --git a/src/modules/ZoomIt/ZoomIt/VersionHelper.cpp b/src/modules/ZoomIt/ZoomIt/VersionHelper.cpp new file mode 100644 index 000000000000..bbd758e4603e --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/VersionHelper.cpp @@ -0,0 +1,129 @@ + + +//-------------------------------------------------------------------- +// +// GetLanguageVersionString +// +// Gets version information for a particular code page. +// +//-------------------------------------------------------------------- +#include +#include "pch.h" +#include "VersionHelper.h" + +PTCHAR GetLanguageVersionString(P_VERSION_INFO VersionInfo, + LANGID LanguageId, WORD Charset, + LPCTSTR VersionString) +{ + TCHAR szQueryStr[0x100]; + PTCHAR pszVerRetVal; + UINT cbReturn; + BOOL fFound = FALSE; + + // Format the string + _stprintf_s(szQueryStr, ARRAYSIZE(szQueryStr), _T("\\StringFileInfo\\%04X%04X\\%s"), + LanguageId, Charset, + VersionString); + + fFound = VerQueryValue(VersionInfo, szQueryStr, + reinterpret_cast(&pszVerRetVal), &cbReturn); + + if (!fFound) { + return NULL; + } + else { + return pszVerRetVal; + } +} + +// +// explicitly use wide char version +// this is used when show banner in remote session before display eula from console which requires unicode for French +// it's needed for multibyte app +// +PWCHAR GetLanguageVersionStringW(P_VERSION_INFO VersionInfo, + LANGID LanguageId, WORD Charset, + LPCWSTR VersionString) +{ + WCHAR szQueryStr[0x100]; + PWCHAR pszVerRetVal; + UINT cbReturn; + BOOL fFound = FALSE; + + // Format the string + swprintf_s(szQueryStr, ARRAYSIZE(szQueryStr), L"\\StringFileInfo\\%04X%04X\\%s", + LanguageId, Charset, + VersionString); + + fFound = VerQueryValueW(VersionInfo, szQueryStr, + reinterpret_cast(&pszVerRetVal), &cbReturn); + + if (!fFound) { + return NULL; + } + else { + return pszVerRetVal; + } +} + +//-------------------------------------------------------------------- +// +// GetVersionString +// +// Gets a version string. +// +//-------------------------------------------------------------------- +PTCHAR GetVersionString(P_VERSION_INFO VersionInfo, + LPCTSTR VersionString) +{ + PTCHAR pszVerRetVal; + P_VERSION_TRANSLATION pTranslation; + VERSION_TRANSLATION translation; + unsigned int length; + + // + // Get the language id + // + translation.langID = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT); + pTranslation = &translation; + VerQueryValue(VersionInfo, + _T("\\VarFileInfo\\Translation"), + reinterpret_cast(&pTranslation), + &length); + + pszVerRetVal = GetLanguageVersionString(VersionInfo, + pTranslation->langID, + pTranslation->charset, + VersionString); + return pszVerRetVal; +} + +// +// explicitly use wide char version +// this is used when show banner in remote session before display eula from console which requires unicode for French +// it's needed for multibyte app +// +PWCHAR GetVersionStringW(P_VERSION_INFO VersionInfo, + LPCWSTR VersionString) +{ + PWCHAR pszVerRetVal; + P_VERSION_TRANSLATION pTranslation; + VERSION_TRANSLATION translation; + unsigned int length; + + // + // Get the language id + // + translation.langID = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT); + pTranslation = &translation; + VerQueryValueW(VersionInfo, + L"\\VarFileInfo\\Translation", + reinterpret_cast(&pTranslation), + &length); + + pszVerRetVal = GetLanguageVersionStringW(VersionInfo, + pTranslation->langID, + pTranslation->charset, + VersionString); + return pszVerRetVal; +} diff --git a/src/modules/ZoomIt/ZoomIt/VersionHelper.h b/src/modules/ZoomIt/ZoomIt/VersionHelper.h new file mode 100644 index 000000000000..fb92a8759457 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/VersionHelper.h @@ -0,0 +1,48 @@ +//============================================================================ +// +// VersionHelper.h +// +// Functions to retrieve version information +// +//============================================================================ + +#pragma once + +#include +#include +#include "winver.h" + +// +// File version information +// +typedef struct { + WORD wLength; + WORD wValueLength; + WORD wType; + WCHAR szKey[16]; + WORD Padding1; + VS_FIXEDFILEINFO Value; +} VERSION_INFO, *P_VERSION_INFO; + +// +// Version translation +// +typedef struct { + WORD langID; // language ID + WORD charset; // character set (code page) +} VERSION_TRANSLATION, *P_VERSION_TRANSLATION; + +PTCHAR GetVersionString(P_VERSION_INFO VersionInfo, LPCTSTR VersionString); + +PTCHAR GetLanguageVersionString(P_VERSION_INFO VersionInfo, + LANGID LanguageId, + WORD Charset, + LPCTSTR VersionString); + +PWCHAR GetLanguageVersionStringW(P_VERSION_INFO VersionInfo, + LANGID LanguageId, WORD Charset, + LPCWSTR VersionString); + +PWCHAR GetVersionStringW(P_VERSION_INFO VersionInfo, + LPCWSTR VersionString); + diff --git a/src/modules/ZoomIt/ZoomIt/VideoRecordingSession.cpp b/src/modules/ZoomIt/ZoomIt/VideoRecordingSession.cpp new file mode 100644 index 000000000000..086c8bfb2a2c --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/VideoRecordingSession.cpp @@ -0,0 +1,391 @@ +//============================================================================== +// +// Zoomit +// Sysinternals - www.sysinternals.com +// +// Video capture code derived from https://github.com/robmikh/capturevideosample +// +//============================================================================== +#include "pch.h" +#include "VideoRecordingSession.h" +#include "CaptureFrameWait.h" + +extern DWORD g_RecordScaling; + +namespace winrt +{ + using namespace Windows::Foundation; + using namespace Windows::Graphics; + using namespace Windows::Graphics::Capture; + using namespace Windows::Graphics::DirectX; + using namespace Windows::Graphics::DirectX::Direct3D11; + using namespace Windows::Storage; + using namespace Windows::UI::Composition; + using namespace Windows::Media::Core; + using namespace Windows::Media::Transcoding; + using namespace Windows::Media::MediaProperties; +} + +namespace util +{ + using namespace robmikh::common::uwp; +} + +const float CLEAR_COLOR[] = { 0.0f, 0.0f, 0.0f, 1.0f }; + +int32_t EnsureEven(int32_t value) +{ + if (value % 2 == 0) + { + return value; + } + else + { + return value + 1; + } +} + +//---------------------------------------------------------------------------- +// +// VideoRecordingSession::VideoRecordingSession +// +//---------------------------------------------------------------------------- +VideoRecordingSession::VideoRecordingSession( + winrt::IDirect3DDevice const& device, + winrt::GraphicsCaptureItem const& item, + RECT const cropRect, + uint32_t frameRate, + bool captureAudio, + winrt::Streams::IRandomAccessStream const& stream) +{ + m_device = device; + m_d3dDevice = GetDXGIInterfaceFromObject(m_device); + m_d3dDevice->GetImmediateContext(m_d3dContext.put()); + m_item = item; + auto itemSize = item.Size(); + auto inputWidth = EnsureEven(itemSize.Width); + auto inputHeight = EnsureEven(itemSize.Height); + m_frameWait = std::make_shared(m_device, m_item, winrt::SizeInt32{ inputWidth, inputHeight }); + auto weakPointer{ std::weak_ptr{ m_frameWait } }; + m_itemClosed = item.Closed(winrt::auto_revoke, [weakPointer](auto&, auto&) + { + auto sharedPointer{ weakPointer.lock() }; + + if (sharedPointer) + { + sharedPointer->StopCapture(); + } + }); + + // Get crop dimension + if( (cropRect.right - cropRect.left) != 0 ) + { + m_rcCrop = cropRect; + m_frameWait->ShowCaptureBorder( false ); + } + else + { + m_rcCrop.left = 0; + m_rcCrop.top = 0; + m_rcCrop.right = inputWidth; + m_rcCrop.bottom = inputHeight; + } + + // Ensure the video is not too small and try to maintain the aspect ratio + constexpr int c_minimumSize = 34; + auto scaledWidth = MulDiv(m_rcCrop.right - m_rcCrop.left, g_RecordScaling, 100); + auto scaledHeight = MulDiv(m_rcCrop.bottom - m_rcCrop.top, g_RecordScaling, 100); + auto outputWidth = scaledWidth; + auto outputHeight = scaledHeight; + if (outputWidth < c_minimumSize) + { + outputWidth = c_minimumSize; + outputHeight = MulDiv(outputHeight, outputWidth, scaledWidth); + } + if (outputHeight < c_minimumSize) + { + outputHeight = c_minimumSize; + outputWidth = MulDiv(outputWidth, outputHeight, scaledHeight); + } + if (outputWidth > inputWidth) + { + outputWidth = inputWidth; + outputHeight = c_minimumSize, MulDiv(outputHeight, scaledWidth, outputWidth); + } + if (outputHeight > inputHeight) + { + outputHeight = inputHeight; + outputWidth = c_minimumSize, MulDiv(outputWidth, scaledHeight, outputHeight); + } + outputWidth = EnsureEven(outputWidth); + outputHeight = EnsureEven(outputHeight); + + // Describe out output: H264 video with an MP4 container + m_encodingProfile = winrt::MediaEncodingProfile(); + m_encodingProfile.Container().Subtype(L"MPEG4"); + auto video = m_encodingProfile.Video(); + video.Subtype(L"H264"); + video.Width(outputWidth); + video.Height(outputHeight); + video.Bitrate(static_cast(outputWidth * outputHeight * frameRate * 2 * 0.07)); + video.FrameRate().Numerator(frameRate); + video.FrameRate().Denominator(1); + video.PixelAspectRatio().Numerator(1); + video.PixelAspectRatio().Denominator(1); + m_encodingProfile.Video(video); + + // if audio capture, set up audio profile + if (captureAudio) + { + auto audio = m_encodingProfile.Audio(); + audio = winrt::AudioEncodingProperties::CreateAac(48000, 1, 16); + m_encodingProfile.Audio(audio); + } + + // Describe our input: uncompressed BGRA8 buffers + auto properties = winrt::VideoEncodingProperties::CreateUncompressed( + winrt::MediaEncodingSubtypes::Bgra8(), + static_cast(m_rcCrop.right - m_rcCrop.left), + static_cast(m_rcCrop.bottom - m_rcCrop.top)); + m_videoDescriptor = winrt::VideoStreamDescriptor(properties); + + m_stream = stream; + + m_previewSwapChain = util::CreateDXGISwapChain( + m_d3dDevice, + static_cast(m_rcCrop.right - m_rcCrop.left), + static_cast(m_rcCrop.bottom - m_rcCrop.top), + DXGI_FORMAT_B8G8R8A8_UNORM, + 2); + winrt::com_ptr backBuffer; + winrt::check_hresult(m_previewSwapChain->GetBuffer(0, winrt::guid_of(), backBuffer.put_void())); + winrt::check_hresult(m_d3dDevice->CreateRenderTargetView(backBuffer.get(), nullptr, m_renderTargetView.put())); + + if( captureAudio ) { + + m_audioGenerator = std::make_unique(); + } + else { + + m_audioGenerator = nullptr; + } +} + + +//---------------------------------------------------------------------------- +// +// VideoRecordingSession::~VideoRecordingSession +// +//---------------------------------------------------------------------------- +VideoRecordingSession::~VideoRecordingSession() +{ + Close(); +} + + +//---------------------------------------------------------------------------- +// +// VideoRecordingSession::StartAsync +// +//---------------------------------------------------------------------------- +winrt::IAsyncAction VideoRecordingSession::StartAsync() +{ + auto expected = false; + if (m_isRecording.compare_exchange_strong(expected, true)) + { + + // Create our MediaStreamSource + if(m_audioGenerator) { + + co_await m_audioGenerator->InitializeAsync(); + m_streamSource = winrt::MediaStreamSource(m_videoDescriptor, winrt::AudioStreamDescriptor(m_audioGenerator->GetEncodingProperties())); + } + else { + + m_streamSource = winrt::MediaStreamSource(m_videoDescriptor); + } + m_streamSource.BufferTime(std::chrono::seconds(0)); + m_streamSource.Starting({ this, &VideoRecordingSession::OnMediaStreamSourceStarting }); + m_streamSource.SampleRequested({ this, &VideoRecordingSession::OnMediaStreamSourceSampleRequested }); + + // Create our transcoder + m_transcoder = winrt::MediaTranscoder(); + m_transcoder.HardwareAccelerationEnabled(true); + + auto self = shared_from_this(); + + // Start encoding + auto transcode = co_await m_transcoder.PrepareMediaStreamSourceTranscodeAsync(m_streamSource, m_stream, m_encodingProfile); + co_await transcode.TranscodeAsync(); + } + co_return; +} + + +//---------------------------------------------------------------------------- +// +// VideoRecordingSession::Close +// +//---------------------------------------------------------------------------- +void VideoRecordingSession::Close() +{ + auto expected = false; + if (m_closed.compare_exchange_strong(expected, true)) + { + expected = true; + if (!m_isRecording.compare_exchange_strong(expected, false)) + { + CloseInternal(); + } + else + { + m_frameWait->StopCapture(); + } + } +} + +//---------------------------------------------------------------------------- +// +// VideoRecordingSession::CloseInternal +// +//---------------------------------------------------------------------------- +void VideoRecordingSession::CloseInternal() +{ + if(m_audioGenerator) { + m_audioGenerator->Stop(); + } + m_frameWait->StopCapture(); + m_itemClosed.revoke(); +} + + +//---------------------------------------------------------------------------- +// +// VideoRecordingSession::OnMediaStreamSourceStarting +// +//---------------------------------------------------------------------------- +void VideoRecordingSession::OnMediaStreamSourceStarting( + winrt::MediaStreamSource const&, + winrt::MediaStreamSourceStartingEventArgs const& args) +{ + auto frame = m_frameWait->TryGetNextFrame(); + if (frame) { + args.Request().SetActualStartPosition(frame->SystemRelativeTime); + if (m_audioGenerator) { + + m_audioGenerator->Start(); + } + } +} + +//---------------------------------------------------------------------------- +// +// VideoRecordingSession::Create +// +//---------------------------------------------------------------------------- +std::shared_ptr VideoRecordingSession::Create( + winrt::IDirect3DDevice const& device, + winrt::GraphicsCaptureItem const& item, + RECT const& crop, + uint32_t frameRate, + bool captureAudio, + winrt::Streams::IRandomAccessStream const& stream) +{ + return std::shared_ptr(new VideoRecordingSession(device, item, crop, frameRate, captureAudio, stream)); +} + +//---------------------------------------------------------------------------- +// +// VideoRecordingSession::OnMediaStreamSourceSampleRequested +// +//---------------------------------------------------------------------------- +void VideoRecordingSession::OnMediaStreamSourceSampleRequested( + winrt::MediaStreamSource const&, + winrt::MediaStreamSourceSampleRequestedEventArgs const& args) +{ + auto request = args.Request(); + auto streamDescriptor = request.StreamDescriptor(); + if (auto videoStreamDescriptor = streamDescriptor.try_as()) + { + if (auto frame = m_frameWait->TryGetNextFrame()) + { + try + { + auto timeStamp = frame->SystemRelativeTime; + auto contentSize = frame->ContentSize; + auto frameTexture = GetDXGIInterfaceFromObject(frame->FrameTexture); + D3D11_TEXTURE2D_DESC desc = {}; + frameTexture->GetDesc(&desc); + + winrt::com_ptr backBuffer; + winrt::check_hresult(m_previewSwapChain->GetBuffer(0, winrt::guid_of(), backBuffer.put_void())); + + // Use the smaller of the crop size or content size. The content + // size can change while recording, for example by resizing the + // window. This ensures that only valid content is copied. + auto width = min(m_rcCrop.right - m_rcCrop.left, contentSize.Width); + auto height = min(m_rcCrop.bottom - m_rcCrop.top, contentSize.Height); + + // Set the content region to copy and clamp the coordinates to the + // texture surface. + D3D11_BOX region = {}; + region.left = std::clamp(m_rcCrop.left, static_cast(0), static_cast(desc.Width)); + region.right = std::clamp(m_rcCrop.left + width, static_cast(0), static_cast(desc.Width)); + region.top = std::clamp(m_rcCrop.top, static_cast(0), static_cast(desc.Height)); + region.bottom = std::clamp(m_rcCrop.top + height, static_cast(0), static_cast(desc.Height)); + region.back = 1; + + m_d3dContext->ClearRenderTargetView(m_renderTargetView.get(), CLEAR_COLOR); + m_d3dContext->CopySubresourceRegion( + backBuffer.get(), + 0, + 0, 0, 0, + frameTexture.get(), + 0, + ®ion); + + desc = {}; + backBuffer->GetDesc(&desc); + + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; + desc.CPUAccessFlags = 0; + desc.MiscFlags = 0; + winrt::com_ptr sampleTexture; + winrt::check_hresult(m_d3dDevice->CreateTexture2D(&desc, nullptr, sampleTexture.put())); + m_d3dContext->CopyResource(sampleTexture.get(), backBuffer.get()); + auto dxgiSurface = sampleTexture.as(); + auto sampleSurface = CreateDirect3DSurface(dxgiSurface.get()); + + DXGI_PRESENT_PARAMETERS presentParameters{}; + winrt::check_hresult(m_previewSwapChain->Present1(0, 0, &presentParameters)); + + auto sample = winrt::MediaStreamSample::CreateFromDirect3D11Surface(sampleSurface, timeStamp); + request.Sample(sample); + } + catch (winrt::hresult_error const& error) + { + OutputDebugStringW(error.message().c_str()); + request.Sample(nullptr); + CloseInternal(); + return; + } + } + else + { + request.Sample(nullptr); + CloseInternal(); + } + } + else if (auto audioStreamDescriptor = streamDescriptor.try_as()) + { + if (auto sample = m_audioGenerator->TryGetNextSample()) + { + request.Sample(sample.value()); + } + else + { + request.Sample(nullptr); + } + } +} diff --git a/src/modules/ZoomIt/ZoomIt/VideoRecordingSession.h b/src/modules/ZoomIt/ZoomIt/VideoRecordingSession.h new file mode 100644 index 000000000000..960ac36444a6 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/VideoRecordingSession.h @@ -0,0 +1,71 @@ +//============================================================================== +// +// Zoomit +// Sysinternals - www.sysinternals.com +// +// Video capture code derived from https://github.com/robmikh/capturevideosample +// +//============================================================================== +#pragma once + +#include "CaptureFrameWait.h" +#include "AudioSampleGenerator.h" +#include + +class VideoRecordingSession : public std::enable_shared_from_this +{ +public: + [[nodiscard]] static std::shared_ptr Create( + winrt::Direct3D11::IDirect3DDevice const& device, + winrt::GraphicsCaptureItem const& item, + RECT const& cropRect, + uint32_t frameRate, + bool captureAudio, + winrt::Streams::IRandomAccessStream const& stream); + ~VideoRecordingSession(); + + winrt::IAsyncAction StartAsync(); + void EnableCursorCapture(bool enable = true) { m_frameWait->EnableCursorCapture(enable); } + void Close(); + +private: + VideoRecordingSession( + winrt::Direct3D11::IDirect3DDevice const& device, + winrt::Capture::GraphicsCaptureItem const& item, + RECT const cropRect, + uint32_t frameRate, + bool captureAudio, + winrt::Streams::IRandomAccessStream const& stream); + void CloseInternal(); + + void OnMediaStreamSourceStarting( + winrt::MediaStreamSource const& sender, + winrt::MediaStreamSourceStartingEventArgs const& args); + void OnMediaStreamSourceSampleRequested( + winrt::MediaStreamSource const& sender, + winrt::MediaStreamSourceSampleRequestedEventArgs const& args); + +private: + winrt::Direct3D11::IDirect3DDevice m_device{ nullptr }; + winrt::com_ptr m_d3dDevice; + winrt::com_ptr m_d3dContext; + RECT m_rcCrop; + + winrt::GraphicsCaptureItem m_item{ nullptr }; + winrt::GraphicsCaptureItem::Closed_revoker m_itemClosed; + std::shared_ptr m_frameWait; + + winrt::Streams::IRandomAccessStream m_stream{ nullptr }; + winrt::MediaEncodingProfile m_encodingProfile{ nullptr }; + winrt::VideoStreamDescriptor m_videoDescriptor{ nullptr }; + winrt::MediaStreamSource m_streamSource{ nullptr }; + winrt::MediaTranscoder m_transcoder{ nullptr }; + + std::unique_ptr m_audioGenerator; + + winrt::com_ptr m_previewSwapChain; + winrt::com_ptr m_renderTargetView; + + std::atomic m_isRecording = false; + std::atomic m_closed = false; +}; \ No newline at end of file diff --git a/src/modules/ZoomIt/ZoomIt/ZoomIt.h b/src/modules/ZoomIt/ZoomIt/ZoomIt.h new file mode 100644 index 000000000000..5abbc2103996 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/ZoomIt.h @@ -0,0 +1,223 @@ +//============================================================================ +// +// Zoomit +// Copyright (C) Mark Russinovich +// Sysinternals - www.sysinternals.com +// +// Screen zoom and annotation tool. +// +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +//============================================================================ +#pragma once + +// Ignore getversion deprecation warning +#pragma warning( disable: 4996 ) + +typedef HRESULT (__stdcall * type_pEnableThemeDialogTexture)( + HWND hwnd, + DWORD dwFlags + ); +type_pEnableThemeDialogTexture pEnableThemeDialogTexture; + +// For testing anti-aliased bitmap stretching +#define SCALE_GDIPLUS 0 +#define SCALE_HALFTONE 0 + +// sent in mouse message when coming from tablet pen +#define MI_WP_SIGNATURE 0xFF515700 + +#define ZOOM_LEVEL_MIN 1 +#define ZOOM_LEVEL_INIT 2 +#define ZOOM_LEVEL_STEP_IN ((float) 1.1) +#define ZOOM_LEVEL_STEP_OUT ((float) 0.8) +#define ZOOM_LEVEL_MAX 32 +#define ZOOM_LEVEL_STEP_TIME 20 + +#define LIVEZOOM_MOVE_REGIONS 8 + +#define WIN7_VERSION 0x106 +#define WIN10_VERSION 0x206 + +// Time that we'll cache live zoom window to avoid flicker +// of live zooming on Vista/ws2k8 +#define LIVEZOOM_WINDOW_TIMEOUT 2*3600*1000 + +#define MAX_UNDO_HISTORY 32 + +#define PEN_WIDTH 5 +#define MIN_PEN_WIDTH 2 +#define MAX_PEN_WIDTH 40 +#define MAX_LIVE_PEN_WIDTH 600 + +#define APPNAME L"ZoomIt" +#define WM_USER_TRAY_ACTIVATE WM_USER+100 +#define WM_USER_TYPING_OFF WM_USER+101 +#define WM_USER_GET_ZOOM_LEVEL WM_USER+102 +#define WM_USER_GET_SOURCE_RECT WM_USER+103 +#define WM_USER_SET_ZOOM WM_USER+104 +#define WM_USER_STOP_RECORDING WM_USER+105 +#define WM_USER_SAVE_CURSOR WM_USER+106 +#define WM_USER_RESTORE_CURSOR WM_USER+107 +#define WM_USER_MAGNIFY_CURSOR WM_USER+108 +#define WM_USER_EXIT_MODE WM_USER+109 +#define WM_USER_RELOAD_SETTINGS WM_USER+110 + +typedef struct _TYPED_KEY { + RECT rc; + struct _TYPED_KEY *Next; +} TYPED_KEY, *P_TYPED_KEY; + +typedef struct _DRAW_UNDO { + HDC hDc; + HBITMAP hBitmap; + struct _DRAW_UNDO *Next; +} DRAW_UNDO, *P_DRAW_UNDO; + +typedef struct { + TCHAR TabTitle[64]; + HWND hPage; +} OPTION_TABS, *P_OPTIONS_TABS; + +#define COLOR_RED RGB(255, 0, 0) +#define COLOR_GREEN RGB(0, 255, 0) +#define COLOR_BLUE RGB(0, 0, 255) +#define COLOR_ORANGE RGB(255,128,0) +#define COLOR_YELLOW RGB(255, 255, 0 ) +#define COLOR_PINK RGB(255,128,255) +#define COLOR_BLUR RGB(112,112,112) + +#define DRAW_RECTANGLE 1 +#define DRAW_ELLIPSE 2 +#define DRAW_LINE 3 +#define DRAW_ARROW 4 + +#define SHALLOW_ZOOM 1 +#define SHALLOW_DESTROY 2 +#define LIVE_DRAW_ZOOM 3 + +#define PEN_COLOR_HIGHLIGHT(Pencolor) (Pencolor >> 24) != 0xFF + + +typedef BOOL (__stdcall *type_pGetMonitorInfo)( + HMONITOR hMonitor, // handle to display monitor + LPMONITORINFO lpmi // display monitor information +); + +typedef HMONITOR (__stdcall *type_MonitorFromPoint)( + POINT pt, // point + DWORD dwFlags // determine return value +); + +typedef HRESULT (__stdcall *type_pSHAutoComplete)( + HWND hwndEdit, + DWORD dwFlags +); + +// DPI awareness +typedef BOOL (__stdcall *type_pSetProcessDPIAware)(void); + +// Live zoom +typedef BOOL (__stdcall *type_pMagSetWindowSource)(HWND hwnd, + RECT rect +); +typedef BOOL (__stdcall *type_pMagSetWindowTransform)(HWND hwnd, + PMAGTRANSFORM pTransform +); +typedef BOOL(__stdcall* type_pMagSetFullscreenTransform)( + float magLevel, + int xOffset, + int yOffset +); +typedef BOOL(__stdcall* type_pMagSetInputTransform)( + BOOL fEnabled, + const LPRECT pRectSource, + const LPRECT pRectDest +); +typedef BOOL (__stdcall *type_pMagShowSystemCursor)( + BOOL fShowCursor +); +typedef BOOL(__stdcall *type_pMagSetWindowFilterList)( + HWND hwnd, + DWORD dwFilterMode, + int count, + HWND* pHWND +); +typedef BOOL (__stdcall *type_pMagInitialize)(VOID); + +typedef BOOL(__stdcall *type_pGetPointerType)( + _In_ UINT32 pointerId, + _Out_ POINTER_INPUT_TYPE *pointerType + ); + +typedef BOOL(__stdcall *type_pGetPointerPenInfo)( + _In_ UINT32 pointerId, + _Out_ POINTER_PEN_INFO *penInfo + ); + +typedef HRESULT (__stdcall *type_pDwmIsCompositionEnabled)( + BOOL *pfEnabled +); + +// opacity +typedef BOOL (__stdcall *type_pSetLayeredWindowAttributes)( + HWND hwnd, // handle to the layered window + COLORREF crKey, // specifies the color key + BYTE bAlpha, // value for the blend function + DWORD dwFlags // action +); + +// Presentation mode check +typedef HRESULT (__stdcall *type_pSHQueryUserNotificationState)( + QUERY_USER_NOTIFICATION_STATE *pQueryUserNotificationState +); + +typedef BOOL (__stdcall *type_pSystemParametersInfoForDpi)( + UINT uiAction, + UINT uiParam, + PVOID pvParam, + UINT fWinIni, + UINT dpi +); + +typedef UINT (__stdcall *type_pGetDpiForWindow)( + HWND hwnd +); + +class ComputerGraphicsInit +{ + ULONG_PTR m_Token; +public: + ComputerGraphicsInit() + { + Gdiplus::GdiplusStartupOutput startupOut; + Gdiplus::GdiplusStartupInput startupIn; + Gdiplus::GdiplusStartup( &m_Token, &startupIn, &startupOut ); + } + ~ComputerGraphicsInit() + { + Gdiplus::GdiplusShutdown( m_Token ); + } +}; + +// Direct3D +typedef HRESULT (__stdcall *type_pCreateDirect3D11DeviceFromDXGIDevice)( + IDXGIDevice *dxgiDevice, + IInspectable **graphicsDevice +); +typedef HRESULT (__stdcall *type_pCreateDirect3D11SurfaceFromDXGISurface)( + IDXGISurface *dxgiSurface, + IInspectable **graphicsSurface +); +typedef HRESULT (__stdcall *type_pD3D11CreateDevice)( + IDXGIAdapter *pAdapter, + D3D_DRIVER_TYPE DriverType, + HMODULE Software, + UINT Flags, + const D3D_FEATURE_LEVEL *pFeatureLevels, + UINT FeatureLevels, + UINT SDKVersion, + ID3D11Device **ppDevice, + D3D_FEATURE_LEVEL *pFeatureLevel, + ID3D11DeviceContext **ppImmediateContext +); diff --git a/src/modules/ZoomIt/ZoomIt/ZoomIt.idc b/src/modules/ZoomIt/ZoomIt/ZoomIt.idc new file mode 100644 index 000000000000..5bb02ecab73d --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/ZoomIt.idc @@ -0,0 +1 @@ + diff --git a/src/modules/ZoomIt/ZoomIt/ZoomIt.rc b/src/modules/ZoomIt/ZoomIt/ZoomIt.rc new file mode 100644 index 000000000000..04b7c37f0841 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/ZoomIt.rc @@ -0,0 +1,478 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" +#include "../../../common/version/version.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "#include ""binres.rc""\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Cursor +// + +NULLCURSOR CURSOR "cursor1.cur" + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +APPICON ICON "appicon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION FILE_VERSION + PRODUCTVERSION PRODUCT_VERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", COMPANY_NAME + VALUE "FileDescription", FILE_DESCRIPTION + VALUE "FileVersion", FILE_VERSION_STRING + VALUE "InternalName", INTERNAL_NAME + VALUE "LegalCopyright", COPYRIGHT_NOTE + VALUE "OriginalFilename", ORIGINAL_FILENAME + VALUE "ProductName", "PowerToys Sysinternals ZoomIt" + VALUE "ProductVersion", PRODUCT_VERSION_STRING + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +OPTIONS DIALOGEX 0, 0, 279, 325 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CLIPSIBLINGS | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_CONTROLPARENT +CAPTION "ZoomIt - Sysinternals: www.sysinternals.com" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + DEFPUSHBUTTON "OK",IDOK,166,306,50,14 + PUSHBUTTON "Cancel",IDCANCEL,223,306,50,14 + LTEXT "ZoomIt v9.0",IDC_VERSION,42,7,73,10 + LTEXT "Copyright � 2006-2024 Mark Russinovich",IDC_COPYRIGHT,42,17,166,8 + CONTROL "Sysinternals - www.sysinternals.com",IDC_LINK, + "SysLink",WS_TABSTOP,42,26,150,9 + ICON "APPICON",IDC_STATIC,12,9,20,20 + CONTROL "Show tray icon",IDC_SHOW_TRAY_ICON,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,295,105,10 + CONTROL "",IDC_TAB,"SysTabControl32",TCS_MULTILINE | WS_TABSTOP,8,46,265,245 + CONTROL "Run ZoomIt when Windows starts",IDC_AUTOSTART,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,309,122,10 +END + +ADVANCED_BREAK DIALOGEX 0, 0, 209, 219 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Advanced Break Options" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "Play Sound on Expiration:",IDC_CHECK_SOUND_FILE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,3,11,98,10,WS_EX_RIGHT + EDITTEXT IDC_SOUND_FILE,61,38,125,12,ES_AUTOHSCROLL | ES_READONLY + PUSHBUTTON "&...",IDC_SOUND_BROWSE,187,38,13,11 + COMBOBOX IDC_OPACITY,62,58,38,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "",IDC_TIMER_POS1,"Button",BS_AUTORADIOBUTTON,63,78,10,10 + CONTROL "",IDC_TIMER_POS2,"Button",BS_AUTORADIOBUTTON,79,78,10,10 + CONTROL "",IDC_TIMER_POS3,"Button",BS_AUTORADIOBUTTON,97,78,10,10 + CONTROL "",IDC_TIMER_POS4,"Button",BS_AUTORADIOBUTTON,63,93,10,10 + CONTROL "",IDC_TIMER_POS5,"Button",BS_AUTORADIOBUTTON,79,93,10,10 + CONTROL "",IDC_TIMER_POS6,"Button",BS_AUTORADIOBUTTON,97,93,10,10 + CONTROL "",IDC_TIMER_POS7,"Button",BS_AUTORADIOBUTTON,63,108,10,10 + CONTROL "",IDC_TIMER_POS8,"Button",BS_AUTORADIOBUTTON,79,108,10,10 + CONTROL "",IDC_TIMER_POS9,"Button",BS_AUTORADIOBUTTON,97,108,10,10 + CONTROL "Show background bitmap:",IDC_CHECK_BACKGROUND_FILE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,3,122,99,10,WS_EX_RIGHT + CONTROL "Use faded desktop as background",IDC_STATIC_DESKTOP_BACKGROUND, + "Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,46,135,125,10 + CONTROL "Use image file as background",IDC_STATIC_BACKGROUND_FILE, + "Button",BS_AUTORADIOBUTTON | WS_TABSTOP,46,149,109,10 + EDITTEXT IDC_BACKGROUND_FILE,62,164,125,12,ES_AUTOHSCROLL | ES_READONLY + PUSHBUTTON "&...",IDC_BACKGROUND_BROWSE,188,164,13,11 + CONTROL "Scale to screen:",IDC_CHECK_BACKGROUND_STRETCH,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,58,180,67,10,WS_EX_RIGHT + DEFPUSHBUTTON "OK",IDOK,97,201,50,14 + PUSHBUTTON "Cancel",IDCANCEL,150,201,50,14 + LTEXT "Alarm Sound File:",IDC_STATIC_SOUND_FILE,61,26,56,8 + LTEXT "Timer Opacity:",IDC_STATIC,8,59,48,8 + LTEXT "Timer Position:",IDC_STATIC,8,77,48,8 + CONTROL "",IDC_STATIC,"Static",SS_BLACKFRAME | SS_SUNKEN,7,196,193,1,WS_EX_CLIENTEDGE +END + +ZOOM DIALOGEX 0, 0, 260, 158 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_HOTKEY,"msctls_hotkey32",WS_BORDER | WS_TABSTOP,59,57,80,12 + LTEXT "After toggling ZoomIt you can zoom in with the mouse wheel or up and down arrow keys. Exit zoom mode with Escape or by pressing the right mouse button.",IDC_STATIC,7,6,246,26 + LTEXT "Zoom Toggle:",IDC_STATIC,7,59,51,8 + CONTROL "",IDC_ZOOM_SLIDER,"msctls_trackbar32",TBS_AUTOTICKS | TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,53,104,150,15,WS_EX_TRANSPARENT + LTEXT "Specify the initial level of magnification when zooming in:",IDC_STATIC,7,91,215,10 + LTEXT "1.25",IDC_STATIC,52,122,16,8 + LTEXT "1.5",IDC_STATIC,82,122,12,8 + LTEXT "1.75",IDC_STATIC,108,122,16,8 + LTEXT "2.0",IDC_STATIC,138,122,12,8 + LTEXT "3.0",IDC_STATIC,164,122,12,8 + LTEXT "4.0",IDC_STATIC,190,122,12,8 + CONTROL "Animate zoom in and zoom out:",IDC_ANIMATE_ZOOM,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,7,74,116,10 + LTEXT "Copy a zoomed screen with Ctrl+C or save it by typing Ctrl+S. Crop the copy or save region by entering Ctrl+Shift instead of Ctrl.",IDC_STATIC,7,34,246,17 +END + +DRAW DIALOGEX 0, 0, 260, 228 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Once zoomed, toggle drawing mode by pressing the left mouse button. Undo with Ctrl+Z and all drawing by pressing E. Center the cursor with the space bar. Exit drawing mode by pressing the right mouse button.",IDC_STATIC,7,7,246,24 + LTEXT "Pen Control ",IDC_PEN_CONTROL,7,38,40,8 + LTEXT "Change the pen width by pressing left Ctrl and using the mouse wheel or the up and down arrow keys.",IDC_STATIC,19,48,233,16 + LTEXT "Colors",IDC_COLORS,7,70,21,8 + LTEXT "Change the pen color by pressing R (red), G (green), B (blue),\nO (orange), Y (yellow) or P (pink).",IDC_STATIC,19,80,233,16 + LTEXT "Highlight and Blur",IDC_HIGHLIGHT_AND_BLUR,7,102,58,8 + LTEXT "Hold Shift while pressing a color key for a translucent highlighter color. Press X for blur or Shift+X for a stronger blur.",IDC_STATIC,19,113,233,16 + LTEXT "Shapes",IDC_SHAPES,7,134,23,8 + LTEXT "Draw a line by holding down the Shift key, a rectangle with the Ctrl key, an ellipse with the Tab key and an arrow with Shift+Ctrl.",IDC_STATIC,19,144,233,16 + LTEXT "Screen",IDC_SCREEN,7,166,22,8 + LTEXT "Clear the screen for a sketch pad by pressing W (white) or K (black). Copy a zoomed screen with Ctrl+C or save it by typing Ctrl+S. Crop the copy or save region by entering Ctrl+Shift instead of Ctrl.",IDC_STATIC,19,176,233,24 + CONTROL "",IDC_DRAW_HOTKEY,"msctls_hotkey32",WS_BORDER | WS_TABSTOP,73,207,80,12 + LTEXT "Draw w/out Zoom:",IDC_STATIC,7,210,63,11 +END + +TYPE DIALOGEX 0, 0, 260, 104 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Once in drawing mode, type 't' to enter typing mode or shift+'t' to enter typing mode with right-aligned input. Exit typing mode by pressing escape or the left mouse button. Use the mouse wheel or up and down arrow keys to change the font size.",IDC_STATIC,7,7,246,32 + LTEXT "The text color is the current drawing color.",IDC_STATIC,7,47,211,9 + PUSHBUTTON "&Font",IDC_FONT,112,69,41,14 + GROUPBOX "Text Font",IDC_TEXT_FONT,8,61,99,28 +END + +BREAK DIALOGEX 0, 0, 260, 123 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_BREAK_HOTKEY,"msctls_hotkey32",WS_BORDER | WS_TABSTOP,52,67,80,12 + EDITTEXT IDC_TIMER,31,86,31,13,ES_RIGHT | ES_AUTOHSCROLL | ES_NUMBER + CONTROL "",IDC_SPIN_TIMER,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS | UDS_NOTHOUSANDS,45,86,11,12 + LTEXT "minutes",IDC_STATIC,67,88,25,8 + PUSHBUTTON "&Advanced",IDC_ADVANCED_BREAK,212,102,41,14 + LTEXT "Enter timer mode by using the ZoomIt tray icon's Break menu item. Increase and decrease time with the arrow keys. If you Alt-Tab away from the timer window, reactivate it by left-clicking on the ZoomIt tray icon. Exit timer mode with Escape. ",IDC_STATIC,7,7,246,33 + LTEXT "Start Timer:",IDC_STATIC,7,70,39,8 + LTEXT "Timer:",IDC_STATIC,7,88,20,8 + LTEXT "Change the break timer color using the same keys that the drawing color. The break timer font is the same as text font.",IDC_STATIC,7,45,219,20 + CONTROL "Show Time Elapsed After Expiration:",IDC_CHECK_SHOW_EXPIRED, + "Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,8,104,132,10 +END + +1543 DIALOGEX 100, 50, 216, 131 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "ZoomIt Font" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + LTEXT "&Font:",1088,6,0,40,9 + COMBOBOX 1136,6,10,94,64,CBS_SIMPLE | CBS_OWNERDRAWFIXED | CBS_AUTOHSCROLL | CBS_SORT | CBS_HASSTRINGS | CBS_DISABLENOSCROLL | WS_VSCROLL | WS_TABSTOP + LTEXT "Font St&yle:",1089,108,0,44,9 + COMBOBOX 1137,108,10,64,64,CBS_SIMPLE | CBS_DISABLENOSCROLL | WS_VSCROLL | WS_TABSTOP + LTEXT "&Size:",1090,179,0,30,9 + COMBOBOX 1138,179,10,32,64,CBS_SIMPLE | CBS_OWNERDRAWFIXED | CBS_SORT | CBS_HASSTRINGS | CBS_DISABLENOSCROLL | WS_VSCROLL | WS_TABSTOP + DEFPUSHBUTTON "OK",IDOK,166,94,45,14,WS_GROUP + PUSHBUTTON "Cancel",IDCANCEL,166,111,45,14,WS_GROUP + GROUPBOX "Sample",1073,7,75,143,51,WS_GROUP + CTEXT "AaBbYyZz",1092,16,88,127,31,SS_NOPREFIX | NOT WS_VISIBLE +END + +LIVEZOOM DIALOGEX 0, 0, 260, 134 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_LIVE_HOTKEY,"msctls_hotkey32",WS_BORDER | WS_TABSTOP,69,108,80,12 + LTEXT "LiveZoom mode is supported on Windows 7 and higher where window updates show while zoomed. ",IDC_STATIC,7,7,246,18 + LTEXT "LiveZoom Toggle:",IDC_STATIC,7,110,62,8 + LTEXT "To enter and exit LiveZoom, enter the hotkey specified below.",IDC_STATIC,7,94,218,13 + LTEXT "Note that in LiveZoom you must use Ctrl+Up and Ctrl+Down to control the zoom level. To enter drawing mode, use the standard zoom-without-draw hotkey and then escape to go back to LiveZoom.",IDC_STATIC,7,30,246,27 + LTEXT "Use LiveDraw to draw and annotate the live desktop. To activate LiveDraw, enter the hotkey with the Shift key in the opposite mode. You can remove LiveDraw annotations by activating LiveDraw and enter the escape key",IDC_STATIC,7,62,246,32 +END + +RECORD DIALOGEX 0, 0, 260, 169 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_RECORD_HOTKEY,"msctls_hotkey32",WS_BORDER | WS_TABSTOP,61,96,80,12 + LTEXT "Record Toggle:",IDC_STATIC,7,98,54,8 + LTEXT "Record video of the unzoomed live screen or a static zoomed session by entering the recording hot key and finish the recording by entering it again. ",IDC_STATIC,7,7,246,28 + LTEXT "Note: Recording is only available on Windows 10 (version 1903) and higher.",IDC_STATIC,7,77,246,19 + LTEXT "Scaling:",IDC_STATIC,30,115,26,8 + COMBOBOX IDC_RECORD_SCALING,61,114,26,30,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_OEMCONVERT | CBS_SORT | WS_VSCROLL | WS_TABSTOP + LTEXT "Frame Rate:",IDC_STATIC,119,115,44,8,NOT WS_VISIBLE + COMBOBOX IDC_RECORD_FRAME_RATE,166,114,42,30,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_OEMCONVERT | CBS_SORT | NOT WS_VISIBLE | WS_VSCROLL | WS_TABSTOP + LTEXT "To crop the portion of the screen that will be recorded, enter the hotkey with the Shift key in the opposite mode. ",IDC_STATIC,7,32,246,19 + LTEXT "To record a specific window, enter the hotkey with the Alt key in the opposite mode.",IDC_STATIC,7,55,246,19 + CONTROL "&Capture audio input:",IDC_CAPTURE_AUDIO,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,7,137,83,10 + COMBOBOX IDC_MICROPHONE,81,152,172,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Microphone:",IDC_STATIC,32,154,47,8 +END + +SNIP DIALOGEX 0, 0, 260, 68 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_SNIP_HOTKEY,"msctls_hotkey32",WS_BORDER | WS_TABSTOP,55,32,80,12 + LTEXT "Snip Toggle:",IDC_STATIC,7,33,45,8 + LTEXT "Copy a region of the screen to the clipboard or enter the hotkey with the Shift key in the opposite mode to save it to a file. ",IDC_STATIC,7,7,246,19 +END + +DEMOTYPE DIALOGEX 0, 0, 259, 249 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_DEMOTYPE_HOTKEY,"msctls_hotkey32",WS_BORDER | WS_TABSTOP,74,154,80,12 + LTEXT "DemoType toggle:",IDC_STATIC,7,157,63,8 + PUSHBUTTON "&...",IDC_DEMOTYPE_BROWSE,231,137,16,13 + CONTROL "",IDC_DEMOTYPE_SPEED_SLIDER,"msctls_trackbar32",TBS_AUTOTICKS | TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,52,202,150,11,WS_EX_TRANSPARENT + CONTROL "Drive input with typing:",IDC_DEMOTYPE_USER_DRIVEN,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,7,173,88,10 + LTEXT "DemoType typing speed:",IDC_STATIC,7,189,215,10 + LTEXT "Slow",IDC_DEMOTYPE_STATIC1,51,213,18,8 + LTEXT "Fast",IDC_DEMOTYPE_STATIC2,186,213,17,8 + EDITTEXT IDC_DEMOTYPE_FILE,44,137,187,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "Input file:",IDC_STATIC,7,139,32,8 + LTEXT "When you reach the end of the file, ZoomIt will reload the file and start at the beginning. Enter the hotkey with the Shift key in the opposite mode to step back to the last [end].",IDC_STATIC,7,108,248,24 + LTEXT "DemoType has ZoomIt type text specified in the input file when you enter the DemoType toggle. Simply separate snippets with the [end] keyword, or you can insert text from the clipboard if it is prefixed with the [start].",IDC_STATIC,7,7,248,24 + LTEXT " - Insert pauses with the [pause:n] keyword where 'n' is seconds. ",IDC_STATIC,19,34,212,11 + LTEXT "You can have ZoomIt send text automatically, or select the option to drive input with typing. ZoomIt will block keyboard input while sending output.",IDC_STATIC,7,68,248,16 + LTEXT "When driving input, hit the space bar to unblock keyboard input at the end of a snippet. In auto mode, control will be returned upon completion.",IDC_STATIC,7,88,248,16 + LTEXT "- Send text via the clipboard with [paste] and [/paste]. ",IDC_STATIC,23,45,178,8 + LTEXT "- Send keystrokes with [enter], [up], [down], [left], and [right].",IDC_STATIC,23,56,211,8 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + "OPTIONS", DIALOG + BEGIN + RIGHTMARGIN, 273 + BOTTOMMARGIN, 320 + END + + "ADVANCED_BREAK", DIALOG + BEGIN + RIGHTMARGIN, 207 + BOTTOMMARGIN, 215 + END + + "ZOOM", DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 253 + TOPMARGIN, 7 + BOTTOMMARGIN, 151 + END + + "DRAW", DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 253 + TOPMARGIN, 7 + BOTTOMMARGIN, 221 + END + + "TYPE", DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 253 + TOPMARGIN, 7 + BOTTOMMARGIN, 97 + END + + "BREAK", DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 253 + TOPMARGIN, 7 + BOTTOMMARGIN, 116 + END + + 1543, DIALOG + BEGIN + RIGHTMARGIN, 211 + BOTTOMMARGIN, 127 + END + + "LIVEZOOM", DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 253 + TOPMARGIN, 7 + BOTTOMMARGIN, 127 + END + + "RECORD", DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 253 + TOPMARGIN, 7 + BOTTOMMARGIN, 164 + END + + "SNIP", DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 253 + TOPMARGIN, 7 + BOTTOMMARGIN, 61 + END + + "DEMOTYPE", DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 255 + TOPMARGIN, 7 + BOTTOMMARGIN, 205 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Accelerator +// + +ACCELERATORS ACCELERATORS +BEGIN + "C", IDC_COPY, VIRTKEY, CONTROL, NOINVERT + "S", IDC_SAVE, VIRTKEY, CONTROL, NOINVERT + "C", IDC_COPY_CROP, VIRTKEY, SHIFT, CONTROL, NOINVERT + "S", IDC_SAVE_CROP, VIRTKEY, SHIFT, CONTROL, NOINVERT +END + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +OPTIONS AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +LIVEZOOM AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +DRAW AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +RECORD AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +TYPE AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +ZOOM AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +SNIP AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +BREAK AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +DEMOTYPE AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#include "binres.rc" +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/src/modules/ZoomIt/ZoomIt/ZoomIt.sln b/src/modules/ZoomIt/ZoomIt/ZoomIt.sln new file mode 100644 index 000000000000..acec73115839 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/ZoomIt.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.271 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ZoomIt", "ZoomIt.vcxproj", "{0A84F764-3A88-44CD-AA96-41BDBD48627B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM64 = Debug|ARM64 + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|ARM64 = Release|ARM64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Debug|ARM64.Build.0 = Debug|ARM64 + {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Debug|Win32.ActiveCfg = Debug|Win32 + {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Debug|Win32.Build.0 = Debug|Win32 + {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Debug|x64.ActiveCfg = Debug|x64 + {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Debug|x64.Build.0 = Debug|x64 + {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Release|ARM64.ActiveCfg = Release|ARM64 + {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Release|ARM64.Build.0 = Release|ARM64 + {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Release|Win32.ActiveCfg = Release|Win32 + {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Release|Win32.Build.0 = Release|Win32 + {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Release|x64.ActiveCfg = Release|x64 + {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9DD749A9-1354-48BC-8392-E01440AE3714} + EndGlobalSection +EndGlobal diff --git a/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj b/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj new file mode 100644 index 000000000000..44724cbbbfad --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj @@ -0,0 +1,371 @@ + + + + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {0A84F764-3A88-44CD-AA96-41BDBD48627B} + ZoomIt + true + true + true + PowerToys.$(MSBuildProjectName) + + + false + + + + + false + $(ProjectDir)$(Configuration)\InterPlatform\ + + + false + $(ProjectDir)$(Configuration)\InterPlatform\ + RCZOOMIT + + + false + RCZOOMIT + + + false + $(ProjectDir)$(Configuration)\InterPlatform\ + + + false + $(ProjectDir)$(Configuration)\InterPlatform\ + RCZOOMIT + + + false + RCZOOMIT + + + + 4100;4091;4245 + ..\..\..\;$(MSBuildThisFileDirectory)..\..\..\common\sysinternals;%(AdditionalIncludeDirectories); + Create + pch.h + stdcpplatest + stdc17 + + + + + MaxSpeed + _UNICODE;UNICODE;WINVER=0x602;NDEBUG;_WIN32_WINNT=0x602;_WIN32_WINDOWS=0x501;WIN32;_WINDOWS;MSVC6=1;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + true + MultiThreaded + true + + + NDEBUG;_M_IX86;%(PreprocessorDefinitions) + 0x0409 + $(InterPlatformDir) + + + Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;%(AdditionalDependencies) + true + Windows + true + true + MachineX86 + + + + + MaxSpeed + _UNICODE;UNICODE;WINVER=0x602;NDEBUG;_WIN32_WINNT=0x602;_WIN32_WINDOWS=0x501;WIN32;_WINDOWS;MSVC6=1;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + true + MultiThreaded + true + + + NDEBUG;_M_X64;%(PreprocessorDefinitions) + 0x0409 + + + Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;%(AdditionalDependencies) + true + Windows + true + + true + MachineX64 + + + + + MaxSpeed + _UNICODE;UNICODE;WINVER=0x602;NDEBUG;_WIN32_WINNT=0x602;_WIN32_WINDOWS=0x501;WIN32;_WINDOWS;MSVC6=1;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + true + MultiThreaded + true + + + NDEBUG;_M_ARM64;%(PreprocessorDefinitions) + 0x0409 + + + Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;%(AdditionalDependencies) + true + Windows + + + true + + + + + Disabled + _UNICODE;UNICODE;WINVER=0x0602;_DEBUG;_WIN32_WINNT=0x602.MSVC6;_WIN32_WINDOWS=0x600;WIN32;_WINDOWS;_WIN32_WINNT=0x602;MSVC6=1;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + + + _DEBUG;_M_IX86;%(PreprocessorDefinitions) + 0x0409 + $(InterPlatformDir) + + + Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;%(AdditionalDependencies) + true + Windows + false + + MachineX86 + + + + + Disabled + _UNICODE;UNICODE;WINVER=0x0602;_DEBUG;_WIN32_WINNT=0x602.MSVC6;_WIN32_WINDOWS=0x600;WIN32;_WINDOWS;_WIN32_WINNT=0x602;MSVC6=1;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + + + _DEBUG;_M_X64;%(PreprocessorDefinitions) + 0x0409 + + + Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;%(AdditionalDependencies) + true + true + Windows + false + + MachineX64 + + + + + Disabled + _UNICODE;UNICODE;WINVER=0x0602;_DEBUG;_WIN32_WINNT=0x602.MSVC6;_WIN32_WINDOWS=0x600;WIN32;_WINDOWS;_WIN32_WINNT=0x602;MSVC6=1;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + + + _DEBUG;_M_ARM64;%(PreprocessorDefinitions) + 0x0409 + + + Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;%(AdditionalDependencies) + true + true + Windows + + + + + + + false + false + false + false + false + false + + + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + + Use + Use + Use + Use + Use + Use + + + Use + Use + Use + Use + Use + Use + + + Use + Use + Use + Use + Use + Use + + + Use + Use + Use + Use + Use + Use + + + Use + Use + Use + Use + Use + Use + + + Use + Use + Use + Use + Use + Use + + + + + + + + + + + + + + + + + + + + + + + + Use + Use + Use + Use + Use + Use + + + Use + Use + Use + Use + Use + Use + + + + + + + + + true + true + true + true + true + true + + + + + + PreserveNewest + + + + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + + {6955446d-23f7-4023-9bb3-8657f904af99} + + + {8f021b46-362b-485c-bfba-ccf83e820cbd} + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj.filters b/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj.filters new file mode 100644 index 000000000000..1754412d2daa --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj.filters @@ -0,0 +1,130 @@ + + + + + {73afce48-6609-48fb-86f2-db7b72a1c1ec} + cpp;c;cxx;rc;def;r;odl;idl;hpj;bat + + + {db948f16-61f7-47ab-96c8-57914076a38a} + h;hpp;hxx;hm;inl + + + {e1fa606f-a2e6-40c8-8779-8ca1813d9f01} + ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + Resource Files + + + + + Resource Files + + + Resource Files + + + Header Files + + + + + + Resource Files + + + Resource Files + + + + + + \ No newline at end of file diff --git a/src/modules/ZoomIt/ZoomIt/ZoomItSettings.h b/src/modules/ZoomIt/ZoomIt/ZoomItSettings.h new file mode 100644 index 000000000000..d53136cf0a54 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/ZoomItSettings.h @@ -0,0 +1,83 @@ +#pragma once +#include "zoomit.h" +#include "Registry.h" +#include "DemoType.h" + +DWORD g_ToggleKey = (HOTKEYF_CONTROL << 8)| '1'; +DWORD g_LiveZoomToggleKey = ((HOTKEYF_CONTROL) << 8)| '4'; +DWORD g_DrawToggleKey = ((HOTKEYF_CONTROL) << 8)| '2'; +DWORD g_BreakToggleKey = ((HOTKEYF_CONTROL) << 8)| '3'; +DWORD g_DemoTypeToggleKey = ((HOTKEYF_CONTROL) << 8) | '7'; +DWORD g_RecordToggleKey = ((HOTKEYF_CONTROL) << 8) | '5'; +DWORD g_SnipToggleKey = ((HOTKEYF_CONTROL) << 8) | '6'; + +DWORD g_ShowExpiredTime = 1; +DWORD g_SliderZoomLevel = 3; +BOOLEAN g_AnimateZoom = TRUE; +DWORD g_PenColor = COLOR_RED; +DWORD g_BreakPenColor = COLOR_RED; +DWORD g_RootPenWidth = PEN_WIDTH; +int g_FontScale = 10; +DWORD g_BreakTimeout = 10; +DWORD g_BreakOpacity = 100; +DWORD g_BreakTimerPosition = 4; +BOOLEAN g_BreakPlaySoundFile = FALSE; +TCHAR g_BreakSoundFile[MAX_PATH] = {0}; +BOOLEAN g_BreakShowDesktop = TRUE; +BOOLEAN g_BreakShowBackgroundFile = FALSE; +BOOLEAN g_BreakBackgroundStretch = FALSE; +TCHAR g_BreakBackgroundFile[MAX_PATH] = {0}; +BOOLEAN g_OptionsShown = FALSE; +BOOLEAN g_ShowTrayIcon = TRUE; +BOOLEAN g_SnapToGrid = TRUE; +BOOLEAN g_TelescopeZoomOut = TRUE; +BOOLEAN g_BreakOnSecondary = FALSE; +LOGFONT g_LogFont; +BOOLEAN g_DemoTypeUserDriven = false; +TCHAR g_DemoTypeFile[MAX_PATH] = {0}; +DWORD g_DemoTypeSpeedSlider = static_cast(((MIN_TYPING_SPEED - MAX_TYPING_SPEED) / 2) + MAX_TYPING_SPEED); +DWORD g_RecordFrameRate = 30; +// Divide by 100 to get actual scaling +DWORD g_RecordScaling = 100; +BOOLEAN g_CaptureAudio = FALSE; +TCHAR g_MicrophoneDeviceId[MAX_PATH] = {0}; + +REG_SETTING RegSettings[] = { + { L"ToggleKey", SETTING_TYPE_DWORD, 0, &g_ToggleKey, static_cast(g_ToggleKey) }, + { L"LiveZoomToggleKey", SETTING_TYPE_DWORD, 0, &g_LiveZoomToggleKey, static_cast(g_LiveZoomToggleKey) }, + { L"DrawToggleKey", SETTING_TYPE_DWORD, 0, &g_DrawToggleKey, static_cast(g_DrawToggleKey) }, + { L"RecordToggleKey", SETTING_TYPE_DWORD, 0, &g_RecordToggleKey, static_cast(g_RecordToggleKey) }, + { L"SnipToggleKey", SETTING_TYPE_DWORD, 0, &g_SnipToggleKey, static_cast(g_SnipToggleKey) }, + { L"PenColor", SETTING_TYPE_DWORD, 0, &g_PenColor, static_cast(g_PenColor) }, + { L"PenWidth", SETTING_TYPE_DWORD, 0, &g_RootPenWidth, static_cast(g_RootPenWidth) }, + { L"OptionsShown", SETTING_TYPE_BOOLEAN, 0, &g_OptionsShown, static_cast(g_OptionsShown) }, + { L"BreakPenColor", SETTING_TYPE_DWORD, 0, &g_BreakPenColor, static_cast(g_BreakPenColor) }, + { L"BreakTimerKey", SETTING_TYPE_DWORD, 0, &g_BreakToggleKey, static_cast(g_BreakToggleKey) }, + { L"DemoTypeToggleKey", SETTING_TYPE_DWORD, 0, &g_DemoTypeToggleKey, static_cast(g_DemoTypeToggleKey) }, + { L"DemoTypeFile", SETTING_TYPE_STRING, sizeof( g_DemoTypeFile ), g_DemoTypeFile, static_cast(0) }, + { L"DemoTypeSpeedSlider", SETTING_TYPE_DWORD, 0, &g_DemoTypeSpeedSlider, static_cast(g_DemoTypeSpeedSlider) }, + { L"DemoTypeUserDrivenMode", SETTING_TYPE_BOOLEAN, 0, &g_DemoTypeUserDriven, static_cast(g_DemoTypeUserDriven) }, + { L"BreakTimeout", SETTING_TYPE_DWORD, 0, &g_BreakTimeout, static_cast(g_BreakTimeout) }, + { L"BreakOpacity", SETTING_TYPE_DWORD, 0, &g_BreakOpacity, static_cast(g_BreakOpacity) }, + { L"BreakPlaySoundFile", SETTING_TYPE_BOOLEAN, 0, &g_BreakPlaySoundFile, static_cast(0) }, + { L"BreakSoundFile", SETTING_TYPE_STRING, sizeof(g_BreakSoundFile), g_BreakSoundFile, static_cast(0) }, + { L"BreakShowBackgroundFile", SETTING_TYPE_BOOLEAN, 0, &g_BreakShowBackgroundFile, static_cast(g_BreakShowBackgroundFile) }, + { L"BreakBackgroundStretch", SETTING_TYPE_BOOLEAN, 0, &g_BreakBackgroundStretch,static_cast(g_BreakBackgroundStretch) }, + { L"BreakBackgroundFile", SETTING_TYPE_STRING, sizeof(g_BreakBackgroundFile), g_BreakBackgroundFile, static_cast(0) }, + { L"BreakTimerPosition", SETTING_TYPE_DWORD, 0, &g_BreakTimerPosition, static_cast(g_BreakTimerPosition) }, + { L"BreakShowDesktop", SETTING_TYPE_BOOLEAN, 0, &g_BreakShowDesktop, static_cast(g_BreakShowDesktop) }, + { L"BreakOnSecondary", SETTING_TYPE_BOOLEAN, 0, &g_BreakOnSecondary,static_cast(g_BreakOnSecondary) }, + { L"FontScale", SETTING_TYPE_DWORD, 0, &g_FontScale, static_cast(g_FontScale) }, + { L"ShowExpiredTime", SETTING_TYPE_BOOLEAN, 0, &g_ShowExpiredTime, static_cast(g_ShowExpiredTime) }, + { L"ShowTrayIcon", SETTING_TYPE_BOOLEAN, 0, &g_ShowTrayIcon, static_cast(g_ShowTrayIcon) }, + { L"AnimnateZoom", SETTING_TYPE_BOOLEAN, 0, &g_AnimateZoom, static_cast(g_AnimateZoom) }, + { L"TelescopeZoomOut", SETTING_TYPE_BOOLEAN, 0, &g_TelescopeZoomOut, static_cast(g_TelescopeZoomOut) }, + { L"SnapToGrid", SETTING_TYPE_BOOLEAN, 0, &g_SnapToGrid, static_cast(g_SnapToGrid) }, + { L"ZoominSliderLevel", SETTING_TYPE_DWORD, 0, &g_SliderZoomLevel, static_cast(g_SliderZoomLevel) }, + { L"Font", SETTING_TYPE_BINARY, sizeof g_LogFont, &g_LogFont, static_cast(0) }, + { L"RecordFrameRate", SETTING_TYPE_DWORD, 0, &g_RecordFrameRate, static_cast(g_RecordFrameRate) }, + { L"RecordScaling", SETTING_TYPE_DWORD, 0, &g_RecordScaling, static_cast(g_RecordScaling) }, + { L"CaptureAudio", SETTING_TYPE_BOOLEAN, 0, &g_CaptureAudio, static_cast(g_CaptureAudio) }, + { L"MicrophoneDeviceId", SETTING_TYPE_STRING, sizeof(g_MicrophoneDeviceId), g_MicrophoneDeviceId, static_cast(0) }, + { NULL, SETTING_TYPE_DWORD, 0, NULL, static_cast(0) } +}; diff --git a/src/modules/ZoomIt/ZoomIt/Zoomit.cpp b/src/modules/ZoomIt/ZoomIt/Zoomit.cpp new file mode 100644 index 000000000000..36b205a44f70 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/Zoomit.cpp @@ -0,0 +1,7616 @@ +//============================================================================ +// +// Zoomit +// Copyright (C) Mark Russinovich +// Sysinternals - www.sysinternals.com +// +// Screen zoom and annotation tool. +// +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +//============================================================================ +#include "pch.h" + +#include "zoomit.h" +#include "Utility.h" +#include "WindowsVersions.h" +#include "ZoomItSettings.h" +#include +#include +#include + +#include "../ZoomItModuleInterface/trace.h" +#include +#include +#include +#include +#include + +namespace winrt +{ + using namespace Windows::Foundation; + using namespace Windows::Graphics; + using namespace Windows::Graphics::Capture; + using namespace Windows::Graphics::Imaging; + using namespace Windows::Storage; + using namespace Windows::UI::Composition; + using namespace Windows::Storage::Pickers; + using namespace Windows::System; + using namespace Windows::Devices::Enumeration; +} + +namespace util +{ + using namespace robmikh::common::uwp; + using namespace robmikh::common::desktop; +} + +// This workaround keeps live zoom enabled after zooming out at level 1 (not zoomed) and disables +// live zoom when recording is stopped. +#define WINDOWS_CURSOR_RECORDING_WORKAROUND 1 + +HINSTANCE g_hInstance; + +COLORREF g_CustomColors[16]; + +#define ZOOM_HOTKEY 0 +#define DRAW_HOTKEY 1 +#define BREAK_HOTKEY 2 +#define LIVE_HOTKEY 3 +#define LIVE_DRAW_HOTKEY 4 +#define RECORD_HOTKEY 5 +#define RECORD_CROP_HOTKEY 6 +#define RECORD_WINDOW_HOTKEY 7 +#define SNIP_HOTKEY 8 +#define SNIP_SAVE_HOTKEY 9 +#define DEMOTYPE_HOTKEY 10 +#define DEMOTYPE_RESET_HOTKEY 11 + +#define ZOOM_PAGE 0 +#define LIVE_PAGE 1 +#define DRAW_PAGE 2 +#define TYPE_PAGE 3 +#define DEMOTYPE_PAGE 4 +#define BREAK_PAGE 5 +#define RECORD_PAGE 6 +#define SNIP_PAGE 7 + +OPTION_TABS g_OptionsTabs[] = { + { _T("Zoom"), NULL }, + { _T("LiveZoom"), NULL }, + { _T("Draw"), NULL }, + { _T("Type"), NULL }, + { _T("DemoType"), NULL }, + { _T("Break"), NULL }, + { _T("Record"), NULL }, + { _T("Snip"), NULL } +}; + +float g_ZoomLevels[] = { + 1.25, + 1.50, + 1.75, + 2.00, + 3.00, + 4.00 +}; + +DWORD g_FramerateOptions[] = { + 30, + 60 +}; + +// +// For typing mode +// +typedef enum { + TypeModeOff = 0, + TypeModeLeftJustify, + TypeModeRightJustify +} TypeModeState; + +const DWORD CURSOR_ARM_LENGTH = 4; + +const float NORMAL_BLUR_RADIUS = 20; +const float STRONG_BLUR_RADIUS = 40; + +DWORD g_ToggleMod; +DWORD g_LiveZoomToggleMod; +DWORD g_DrawToggleMod; +DWORD g_BreakToggleMod; +DWORD g_DemoTypeToggleMod; +DWORD g_RecordToggleMod; +DWORD g_SnipToggleMod; + +BOOLEAN g_ZoomOnLiveZoom = FALSE; +DWORD g_PenWidth = PEN_WIDTH; +float g_BlurRadius = NORMAL_BLUR_RADIUS; +HWND hWndOptions = NULL; +BOOLEAN g_DrawPointer = FALSE; +BOOLEAN g_PenDown = FALSE; +BOOLEAN g_PenInverted = FALSE; +DWORD g_OsVersion; +HWND g_hWndLiveZoom = NULL; +HWND g_hWndLiveZoomMag = NULL; +HWND g_hWndMain; +int g_AlphaBlend = 0x80; +BOOL g_fullScreenWorkaround = FALSE; +bool g_bSaveInProgress = false; +std::wstring g_TextBuffer; +// This is useful in the context of right-justified text only. +std::list g_TextBufferPreviousLines; +#if WINDOWS_CURSOR_RECORDING_WORKAROUND +bool g_LiveZoomLevelOne = false; +#endif + +// True if ZoomIt was started by PowerToys instead of standalone. +BOOLEAN g_StartedByPowerToys = FALSE; +BOOLEAN g_running = TRUE; + +// Screen recording globals +#define DEFAULT_RECORDING_FILE L"Recording.mp4" +BOOL g_RecordToggle = FALSE; +BOOL g_RecordCropping = FALSE; +SelectRectangle g_SelectRectangle; +std::wstring g_RecordingSaveLocation; +winrt::IDirect3DDevice g_RecordDevice{ nullptr }; +std::shared_ptr g_RecordingSession = nullptr; + +type_pGetMonitorInfo pGetMonitorInfo; +type_MonitorFromPoint pMonitorFromPoint; +type_pSHAutoComplete pSHAutoComplete; +type_pSetLayeredWindowAttributes pSetLayeredWindowAttributes; +type_pSetProcessDPIAware pSetProcessDPIAware; +type_pMagSetWindowSource pMagSetWindowSource; +type_pMagSetWindowTransform pMagSetWindowTransform; +type_pMagSetFullscreenTransform pMagSetFullscreenTransform; +type_pMagSetInputTransform pMagSetInputTransform; +type_pMagShowSystemCursor pMagShowSystemCursor; +type_pMagSetWindowFilterList pMagSetWindowFilterList; +type_pMagInitialize pMagInitialize; +type_pDwmIsCompositionEnabled pDwmIsCompositionEnabled; +type_pGetPointerType pGetPointerType; +type_pGetPointerPenInfo pGetPointerPenInfo; +type_pSystemParametersInfoForDpi pSystemParametersInfoForDpi; +type_pGetDpiForWindow pGetDpiForWindow; + +type_pSHQueryUserNotificationState pSHQueryUserNotificationState; + +type_pCreateDirect3D11DeviceFromDXGIDevice pCreateDirect3D11DeviceFromDXGIDevice; +type_pCreateDirect3D11SurfaceFromDXGISurface pCreateDirect3D11SurfaceFromDXGISurface; +type_pD3D11CreateDevice pD3D11CreateDevice; + +ClassRegistry reg( _T("Software\\Sysinternals\\") APPNAME ); + +ComputerGraphicsInit g_GraphicsInit; + + +//---------------------------------------------------------------------------- +// +// Saves specified filePath to clipboard. +// +//---------------------------------------------------------------------------- +bool SaveToClipboard( const WCHAR* filePath, HWND hwnd ) +{ + if( filePath == NULL || hwnd == NULL || wcslen( filePath ) == 0 ) + { + return false; + } + + size_t size = sizeof(DROPFILES) + sizeof(WCHAR) * ( _tcslen( filePath ) + 1 ) + sizeof(WCHAR); + + HDROP hDrop = static_cast(GlobalAlloc( GHND, size )); + if (hDrop == NULL) + { + return false; + } + + DROPFILES* dFiles = static_cast(GlobalLock( hDrop )); + if (dFiles == NULL) + { + GlobalFree( hDrop ); + return false; + } + + dFiles->pFiles = sizeof(DROPFILES); + dFiles->fWide = TRUE; + + wcscpy( reinterpret_cast(& dFiles[1]), filePath); + GlobalUnlock( hDrop ); + + if( OpenClipboard( hwnd ) ) + { + EmptyClipboard(); + SetClipboardData( CF_HDROP, hDrop ); + CloseClipboard(); + } + + GlobalFree( hDrop ); + + return true; +} + +//---------------------------------------------------------------------- +// +// OutputDebug +// +//---------------------------------------------------------------------- +void OutputDebug(const TCHAR* format, ...) +{ +#if _DEBUG + TCHAR msg[1024]; + va_list va; + + va_start(va, format); + _vstprintf_s(msg, format, va); + va_end(va); + + OutputDebugString(msg); +#endif +} + +//---------------------------------------------------------------------------- +// +// InitializeFonts +// +// Return a bold equivalent of either a DPI aware font face for GUI text or +// just the stock object for DEFAULT_GUI_FONT. +// +//---------------------------------------------------------------------------- +void InitializeFonts( HWND hwnd, HFONT *bold ) +{ + LOGFONT logFont; + bool haveLogFont = false; + + if( *bold ) + { + DeleteObject( *bold ); + *bold = nullptr; + } + + if( pSystemParametersInfoForDpi && pGetDpiForWindow ) + { + NONCLIENTMETRICSW metrics{}; + metrics.cbSize = sizeof( metrics ); + + if( pSystemParametersInfoForDpi( SPI_GETNONCLIENTMETRICS, sizeof( metrics ), &metrics, 0, pGetDpiForWindow( hwnd ) ) ) + { + CopyMemory( &logFont, &metrics.lfMessageFont, sizeof( logFont ) ); + haveLogFont = true; + } + } + + if( !haveLogFont ) + { + auto normal = static_cast(GetStockObject( DEFAULT_GUI_FONT )); + GetObject( normal, sizeof( logFont ), &logFont ); + haveLogFont = true; // for correctness + } + + logFont.lfWeight = FW_BOLD; + *bold = CreateFontIndirect( &logFont ); +} + +//---------------------------------------------------------------------------- +// +// EnsureForeground +// +//---------------------------------------------------------------------------- +void EnsureForeground() +{ + if( !IsWindowVisible( g_hWndMain ) ) + SetForegroundWindow( g_hWndMain ); +} + +//---------------------------------------------------------------------------- +// +// RestoreForeground +// +//---------------------------------------------------------------------------- +void RestoreForeground() +{ + // If the main window is not visible, move foreground to the next window + if( !IsWindowVisible( g_hWndMain ) ) { + + // Activate the next window by unhiding and hiding the main window + MoveWindow( g_hWndMain, 0, 0, 0, 0, FALSE ); + ShowWindow( g_hWndMain, SW_SHOWNA ); + ShowWindow( g_hWndMain, SW_HIDE ); + + OutputDebug(L"RESTORE FOREGROUND\n"); + } +} + +//---------------------------------------------------------------------------- +// +// ErrorDialog +// +//---------------------------------------------------------------------------- +VOID ErrorDialog( HWND hParent, PCTSTR message, DWORD _Error ) +{ + LPTSTR lpMsgBuf; + TCHAR errmsg[1024]; + + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, _Error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&lpMsgBuf), 0, NULL ); + _stprintf( errmsg, L"%s: %s", message, lpMsgBuf ); + if (g_StartedByPowerToys) + { + Logger::error(errmsg); + } + MessageBox( hParent, errmsg, APPNAME, MB_OK|MB_ICONERROR); +} + +//---------------------------------------------------------------------------- +// +// ErrorDialogString +// +//---------------------------------------------------------------------------- +VOID ErrorDialogString( HWND hParent, PCTSTR Message, const wchar_t *_Error ) +{ + TCHAR errmsg[1024]; + + _stprintf( errmsg, L"%s: %s", Message, _Error ); + if( hParent == g_hWndMain ) + EnsureForeground(); + if (g_StartedByPowerToys) + { + Logger::error(errmsg); + } + MessageBox(hParent, errmsg, APPNAME, MB_OK | MB_ICONERROR); + if( hParent == g_hWndMain ) + RestoreForeground(); +} + + +//-------------------------------------------------------------------- +// +// SetAutostartFilePath +// +// Sets the file path for later autostart config. +// +//-------------------------------------------------------------------- +void SetAutostartFilePath() +{ + HKEY hZoomit; + DWORD error; + TCHAR imageFile[MAX_PATH] = { 0 }; + + error = RegCreateKeyEx( HKEY_CURRENT_USER, _T( "Software\\Sysinternals\\Zoomit" ), 0, + 0, 0, KEY_SET_VALUE, NULL, &hZoomit, NULL ); + if( error == ERROR_SUCCESS ) { + + GetModuleFileName( NULL, imageFile + 1, _countof( imageFile ) - 2 ); + imageFile[0] = '"'; + *(_tcschr( imageFile, 0 )) = '"'; + error = RegSetValueEx( hZoomit, L"FilePath", 0, REG_SZ, (BYTE *) imageFile, + static_cast(_tcslen( imageFile ) + 1)* sizeof( TCHAR )); + RegCloseKey( hZoomit ); + } +} + +//-------------------------------------------------------------------- +// +// ConfigureAutostart +// +// Enables or disables Zoomit autostart for the current image file. +// +//-------------------------------------------------------------------- +bool ConfigureAutostart( HWND hParent, bool Enable ) +{ + HKEY hRunKey, hZoomit; + DWORD error, length, type; + TCHAR imageFile[MAX_PATH]; + + error = RegOpenKeyEx( HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", + 0, KEY_SET_VALUE, &hRunKey ); + if( error == ERROR_SUCCESS ) { + + if( Enable ) { + + error = RegOpenKeyEx( HKEY_CURRENT_USER, _T("Software\\Sysinternals\\Zoomit"), 0, + KEY_QUERY_VALUE, &hZoomit ); + if( error == ERROR_SUCCESS ) { + + length = sizeof(imageFile); +#ifdef _WIN64 + // Unconditionally reset filepath in case this was already set by 32 bit version + SetAutostartFilePath(); +#endif + error = RegQueryValueEx( hZoomit, _T( "Filepath" ), 0, &type, (BYTE *) imageFile, &length ); + RegCloseKey( hZoomit ); + if( error == ERROR_SUCCESS ) { + + error = RegSetValueEx( hRunKey, APPNAME, 0, REG_SZ, (BYTE *) imageFile, + static_cast(_tcslen(imageFile)+1) * sizeof(TCHAR)); + } + } + } else { + + error = RegDeleteValue( hRunKey, APPNAME ); + if( error == ERROR_FILE_NOT_FOUND ) error = ERROR_SUCCESS; + } + RegCloseKey( hRunKey ); + } + if( error != ERROR_SUCCESS ) { + + ErrorDialog( hParent, L"Error configuring auto start", error ); + } + return error == ERROR_SUCCESS; +} + + +//-------------------------------------------------------------------- +// +// IsAutostartConfigured +// +// Is this version of zoomit configured to autostart. +// +//-------------------------------------------------------------------- +bool IsAutostartConfigured() +{ + HKEY hRunKey; + TCHAR imageFile[MAX_PATH]; + DWORD error, imageFileLength, type; + + error = RegOpenKeyEx( HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", + 0, KEY_QUERY_VALUE, &hRunKey ); + if( error == ERROR_SUCCESS ) { + + imageFileLength = sizeof(imageFile); + error = RegQueryValueEx( hRunKey, _T("Zoomit"), 0, &type, (BYTE *) imageFile, &imageFileLength ); + RegCloseKey( hRunKey ); + } + return error == ERROR_SUCCESS; +} + + +#ifndef _WIN64 + +//-------------------------------------------------------------------- +// +// RunningOnWin64 +// +// Returns true if this is the 32-bit version of the executable +// and we're on 64-bit Windows +// +//-------------------------------------------------------------------- +typedef BOOL (__stdcall *P_IS_WOW64PROCESS)( + HANDLE hProcess, + PBOOL Wow64Process + ); +BOOL +RunningOnWin64( + VOID + ) +{ + P_IS_WOW64PROCESS pIsWow64Process; + BOOL isWow64 = FALSE; + + pIsWow64Process = (P_IS_WOW64PROCESS) GetProcAddress(GetModuleHandle(_T("kernel32.dll")), + "IsWow64Process"); + if( pIsWow64Process ) { + + pIsWow64Process( GetCurrentProcess(), &isWow64 ); + } + return isWow64; +} + + +//-------------------------------------------------------------------- +// +// ExtractImageResource +// +// Extracts the specified file that is located in a resource for +// this executable. +// +//-------------------------------------------------------------------- +BOOLEAN ExtractImageResource( PTCHAR ResourceName, PTCHAR TargetFile ) +{ + HRSRC hResource; + HGLOBAL hImageResource; + DWORD dwImageSize; + LPVOID lpvImage; + FILE *hFile; + + // Locate the resource + hResource = FindResource( NULL, ResourceName, _T("BINRES") ); + if( !hResource ) + return FALSE; + + hImageResource = LoadResource( NULL, hResource ); + dwImageSize = SizeofResource( NULL, hResource ); + lpvImage = LockResource( hImageResource ); + + // Now copy it out + _tfopen_s( &hFile, TargetFile, _T("wb") ); + if( hFile == NULL ) return FALSE; + + fwrite( lpvImage, 1, dwImageSize, hFile ); + fclose( hFile ); + return TRUE; +} + + + +//-------------------------------------------------------------------- +// +// Run64bitVersion +// +// Returns true if this is the 32-bit version of the executable +// and we're on 64-bit Windows +// +//-------------------------------------------------------------------- +DWORD +Run64bitVersion( + void + ) +{ + TCHAR szPath[MAX_PATH]; + TCHAR originalPath[MAX_PATH]; + TCHAR tmpPath[MAX_PATH]; + SHELLEXECUTEINFO info = { 0 }; + + if ( GetModuleFileName( NULL, szPath, sizeof(szPath)/sizeof(TCHAR)) == 0 ) { + + return -1; + } + _tcscpy_s( originalPath, _countof(originalPath), szPath ); + + *_tcsrchr( originalPath, '.') = 0; + _tcscat_s( originalPath, _countof(szPath), _T("64.exe")); + + // + // Extract the 64-bit version + // + ExpandEnvironmentStrings( L"%TEMP%", tmpPath, sizeof tmpPath / sizeof ( TCHAR)); + _tcscat_s( tmpPath, _countof(tmpPath), _tcsrchr( originalPath, '\\')); + _tcscpy_s( szPath, _countof(szPath), tmpPath ); + if( !ExtractImageResource( _T("RCZOOMIT64"), szPath )) { + + if( GetFileAttributes( szPath ) == INVALID_FILE_ATTRIBUTES ) { + + ErrorDialog( NULL,_T("Error launching 64-bit version"), GetLastError()); + return -1; + } + } + + info.cbSize = sizeof(info); + info.fMask = SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS; + info.lpFile = szPath; + info.lpParameters = GetCommandLine(); + info.nShow = SW_SHOWNORMAL; + if( !ShellExecuteEx( &info ) ) { + + ErrorDialog( NULL,_T("Error launching 64-bit version"), GetLastError()); + DeleteFile( szPath ); + return -1; + } + WaitForSingleObject( info.hProcess, INFINITE ); + + DWORD result; + GetExitCodeProcess( info.hProcess, &result ); + CloseHandle( info.hProcess ); + DeleteFile( szPath ); + return result; +} +#endif + + +//---------------------------------------------------------------------------- +// +// IsPresentationMode +// +//---------------------------------------------------------------------------- +BOOLEAN IsPresentationMode() +{ + QUERY_USER_NOTIFICATION_STATE pUserState; + + pSHQueryUserNotificationState( &pUserState ); + return pUserState == QUNS_PRESENTATION_MODE; +} + +//---------------------------------------------------------------------------- +// +// EnableDisableSecondaryDisplay +// +// Creates a second display on the secondary monitor for displaying the +// break timer. +// +//---------------------------------------------------------------------------- +LONG EnableDisableSecondaryDisplay( HWND hWnd, BOOLEAN Enable, + PDEVMODE OriginalDevMode ) +{ + LONG result; + DEVMODE devMode{}; + + if( Enable ) { + + // + // Prepare the position of Display 2 to be right to the right of Display 1 + // + devMode.dmSize = sizeof(devMode); + devMode.dmDriverExtra = 0; + EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &devMode); + *OriginalDevMode = devMode; + + // + // Enable display 2 in the registry + // + devMode.dmPosition.x = devMode.dmPelsWidth; + devMode.dmFields = DM_POSITION | + DM_DISPLAYORIENTATION | + DM_BITSPERPEL | + DM_PELSWIDTH | + DM_PELSHEIGHT | + DM_DISPLAYFLAGS | + DM_DISPLAYFREQUENCY; + result = ChangeDisplaySettingsEx( L"\\\\.\\DISPLAY2", + &devMode, + NULL, + CDS_NORESET | CDS_UPDATEREGISTRY, + NULL); + + } else { + + OriginalDevMode->dmFields = DM_POSITION | + DM_DISPLAYORIENTATION | + DM_BITSPERPEL | + DM_PELSWIDTH | + DM_PELSHEIGHT | + DM_DISPLAYFLAGS | + DM_DISPLAYFREQUENCY; + result = ChangeDisplaySettingsEx( L"\\\\.\\DISPLAY2", + OriginalDevMode, + NULL, + CDS_NORESET | CDS_UPDATEREGISTRY, + NULL); + } + + // + // Update the hardware + // + if( result == DISP_CHANGE_SUCCESSFUL ) { + + if( !ChangeDisplaySettingsEx(NULL, NULL, NULL, 0, NULL)) { + + result = GetLastError(); + } + + // + // If enabling, move zoomit to the second monitor + // + if( Enable && result == DISP_CHANGE_SUCCESSFUL ) { + + SetWindowPos(FindWindowW(L"ZoomitClass", NULL), + NULL, + devMode.dmPosition.x, + 0, + 0, + 0, + SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + SetCursorPos( devMode.dmPosition.x+1, devMode.dmPosition.y+1 ); + } + } + return result; +} + +//---------------------------------------------------------------------------- +// +// GetLineBounds +// +// Gets the rectangle bounding a line, taking into account pen width +// +//---------------------------------------------------------------------------- +Gdiplus::Rect GetLineBounds( POINT p1, POINT p2, int penWidth ) +{ + Gdiplus::Rect rect( min(p1.x, p2.x), min(p1.y, p2.y), + abs(p1.x - p2.x), abs( p1.y - p2.y)); + rect.Inflate( penWidth, penWidth ); + return rect; +} + +//---------------------------------------------------------------------------- +// +// InvalidateGdiplusRect +// +// Invalidate portion of window specified by Gdiplus::Rect +// +//---------------------------------------------------------------------------- +void InvalidateGdiplusRect(HWND hWnd, Gdiplus::Rect BoundsRect) +{ + RECT lineBoundsGdi; + lineBoundsGdi.left = BoundsRect.X; + lineBoundsGdi.top = BoundsRect.Y; + lineBoundsGdi.right = BoundsRect.X + BoundsRect.Width; + lineBoundsGdi.bottom = BoundsRect.Y + BoundsRect.Height; + InvalidateRect(hWnd, &lineBoundsGdi, FALSE); +} + + + +//---------------------------------------------------------------------------- +// +// CreateGdiplusBitmap +// +// Creates a gdiplus bitmap of the specified region of the HDC. +// +//---------------------------------------------------------------------------- +Gdiplus::Bitmap *CreateGdiplusBitmap( HDC hDc, int x, int y, int Width, int Height ) +{ + HBITMAP hBitmap = CreateCompatibleBitmap(hDc, Width, Height); + + // Create a device context for the new bitmap + HDC hdcNewBitmap = CreateCompatibleDC(hDc); + SelectObject(hdcNewBitmap, hBitmap); + + // Copy from the oldest undo bitmap to the new bitmap using the lineBounds as the source rectangle + BitBlt(hdcNewBitmap, 0, 0, Width, Height, hDc, x, y, SRCCOPY); + Gdiplus::Bitmap *blurBitmap = new Gdiplus::Bitmap(hBitmap, NULL); + DeleteDC(hdcNewBitmap); + DeleteObject(hBitmap); + return blurBitmap; +} + + +//---------------------------------------------------------------------------- +// +// CreateBitmapMemoryDIB +// +// Creates a memory DC and DIB for the specified region of the screen. +// +//---------------------------------------------------------------------------- +BYTE* CreateBitmapMemoryDIB(HDC hdcScreenCompat, HDC hBitmapDc, Gdiplus::Rect* lineBounds, + HDC* hdcMem, HBITMAP* hDIBOrig, HBITMAP* hPreviousBitmap) +{ + // Create a memory DIB for the relevant region of the original bitmap + BITMAPINFO bmiOrig = { 0 }; + bmiOrig.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmiOrig.bmiHeader.biWidth = lineBounds->Width; + bmiOrig.bmiHeader.biHeight = -lineBounds->Height; // Top-down DIB + bmiOrig.bmiHeader.biPlanes = 1; + bmiOrig.bmiHeader.biBitCount = 32; // 32 bits per pixel + bmiOrig.bmiHeader.biCompression = BI_RGB; + + VOID* pDIBBitsOrig; + *hDIBOrig = CreateDIBSection(hdcScreenCompat, &bmiOrig, DIB_RGB_COLORS, &pDIBBitsOrig, NULL, 0); + + if( *hDIBOrig == NULL ) { + + OutputDebug(L"NULL DIB: %d\n", GetLastError()); + OutputDebug(L"lineBounds: %d %d %d %d\n", lineBounds->X, lineBounds->Y, lineBounds->Width, lineBounds->Height); + return NULL; + } + + *hdcMem = CreateCompatibleDC(hdcScreenCompat); + *hPreviousBitmap = static_cast(SelectObject(*hdcMem, *hDIBOrig)); + + // Copy the relevant part of hdcScreenCompat to the DIB + BitBlt(*hdcMem, 0, 0, lineBounds->Width, lineBounds->Height, hBitmapDc, lineBounds->X, lineBounds->Y, SRCCOPY); + + // Pointer to the DIB bits + return static_cast(pDIBBitsOrig); +} + +//---------------------------------------------------------------------------- +// +// LockGdiPlusBitmap +// +// Locks the Gdi+ bitmap so that we can access its pixels in memory. +// +//---------------------------------------------------------------------------- +#ifdef _MSC_VER + // Analyzers want us to use a scoped object instead of new. But given all the operations done in Bitmaps it seems better to leave it as a heap object. + #pragma warning(push) + #pragma warning(disable : 26402) +#endif + +Gdiplus::BitmapData* LockGdiPlusBitmap(Gdiplus::Bitmap* Bitmap) +{ + Gdiplus::BitmapData *lineData = new Gdiplus::BitmapData(); + Bitmap->GetPixelFormat(); + Gdiplus::Rect lineBitmapBounds(0, 0, Bitmap->GetWidth(), Bitmap->GetHeight()); + Bitmap->LockBits(&lineBitmapBounds, Gdiplus::ImageLockModeRead, + Bitmap->GetPixelFormat(), lineData); + return lineData; +} +#ifdef _MSC_VER + #pragma warning(pop) +#endif + + +//---------------------------------------------------------------------------- +// +// BlurScreen +// +// Blur the portion of the screen by copying a blurred bitmap with the +// specified shape. +// +//---------------------------------------------------------------------------- +void BlurScreen(HDC hdcScreenCompat, Gdiplus::Rect* lineBounds, + Gdiplus::Bitmap *BlurBitmap, BYTE* pPixels) +{ + HDC hdcDIB; + HBITMAP hDibOrigBitmap, hDibBitmap; + BYTE* pDestPixels = CreateBitmapMemoryDIB(hdcScreenCompat, hdcScreenCompat, lineBounds, + &hdcDIB, &hDibBitmap, &hDibOrigBitmap); + + // Iterate through the pixels + for (int y = 0; y < lineBounds->Height; ++y) { + for (int x = 0; x < lineBounds->Width; ++x) { + int index = (y * lineBounds->Width * 4) + (x * 4); // Assuming 4 bytes per pixel + // BYTE b = pPixels[index + 0]; // Blue channel + // BYTE g = pPixels[index + 1]; // Green channel + // BYTE r = pPixels[index + 2]; // Red channel + BYTE a = pPixels[index + 3]; // Alpha channel + + // Check if this is a drawn pixel + if (a != 0) { + // get the blur pixel + Gdiplus::Color pixel; + BlurBitmap->GetPixel(x, y, &pixel); + + COLORREF newPixel = pixel.GetValue() & 0xFFFFFF; + pDestPixels[index + 0] = GetRValue(newPixel); + pDestPixels[index + 1] = GetGValue(newPixel); + pDestPixels[index + 2] = GetBValue(newPixel); + } + } + } + + // Copy the updated DIB back to hdcScreenCompat + BitBlt(hdcScreenCompat, lineBounds->X, lineBounds->Y, lineBounds->Width, lineBounds->Height, hdcDIB, 0, 0, SRCCOPY); + + // Clean up + SelectObject(hdcDIB, hDibOrigBitmap); + DeleteObject(hDibBitmap); + DeleteDC(hdcDIB); +} + + + +//---------------------------------------------------------------------------- +// +// BitmapBlur +// +// Blurs the bitmap. +// +//---------------------------------------------------------------------------- +void BitmapBlur(Gdiplus::Bitmap* hBitmap) +{ + // Git bitmap size + Gdiplus::Size bitmapSize; + bitmapSize.Width = hBitmap->GetWidth(); + bitmapSize.Height = hBitmap->GetHeight(); + + // Blur the new bitmap + Gdiplus::Blur blurObject; + Gdiplus::BlurParams blurParams; + blurParams.radius = g_BlurRadius; + blurParams.expandEdge = FALSE; + blurObject.SetParameters(&blurParams); + + // Apply blur to image + RECT linesRect; + linesRect.left = 0; + linesRect.top = 0; + linesRect.right = bitmapSize.Width; + linesRect.bottom = bitmapSize.Height; + hBitmap->ApplyEffect(&blurObject, &linesRect); +} + + +//---------------------------------------------------------------------------- +// +// DrawBlurredShape +// +// Blur a shaped region of the screen. +// +//---------------------------------------------------------------------------- +void DrawBlurredShape( DWORD Shape, Gdiplus::Pen *pen, HDC hdcScreenCompat, Gdiplus::Graphics *dstGraphics, + int x1, int y1, int x2, int y2) +{ + // Create a new bitmap that's the size of the area covered by the line + 2 * g_PenWidth + Gdiplus::Rect lineBounds( min( x1, x2 ), min( y1, y2 ), abs( x2 - x1 ), abs( y2 - y1 ) ); + + // Expand for line drawing + if (Shape == DRAW_LINE) + lineBounds.Inflate( static_cast(g_PenWidth / 2), static_cast(g_PenWidth / 2) ); + + Gdiplus::Bitmap* lineBitmap = new Gdiplus::Bitmap(lineBounds.Width, lineBounds.Height, PixelFormat32bppARGB); + Gdiplus::Graphics lineGraphics(lineBitmap); + static const auto blackBrush = Gdiplus::SolidBrush(Gdiplus::Color::Black); + switch (Shape) { + case DRAW_RECTANGLE: + lineGraphics.FillRectangle(&blackBrush, 0, 0, lineBounds.Width, lineBounds.Height); + break; + case DRAW_ELLIPSE: + lineGraphics.FillEllipse(&blackBrush, 0, 0, lineBounds.Width, lineBounds.Height); + break; + case DRAW_LINE: + OutputDebug(L"BLUR_LINE: %d %d\n", lineBounds.Width, lineBounds.Height); + lineGraphics.DrawLine( pen, x1 - lineBounds.X, y1 - lineBounds.Y, x2 - lineBounds.X, y2 - lineBounds.Y ); + break; + } + + Gdiplus::BitmapData* lineData = LockGdiPlusBitmap(lineBitmap); + BYTE* pPixels = static_cast(lineData->Scan0); + + // Create a GDI bitmap that's the size of the lineBounds rectangle + Gdiplus::Bitmap* blurBitmap = CreateGdiplusBitmap(hdcScreenCompat, + lineBounds.X, lineBounds.Y, lineBounds.Width, lineBounds.Height); + + // Blur it + BitmapBlur(blurBitmap); + BlurScreen(hdcScreenCompat, &lineBounds, blurBitmap, pPixels); + + // Unlock the bits + lineBitmap->UnlockBits(lineData); + delete lineBitmap; + delete blurBitmap; +} + +//---------------------------------------------------------------------------- +// +// CreateDrawingBitmap +// +// Create a bitmap to draw on. +// +//---------------------------------------------------------------------------- +Gdiplus::Bitmap* CreateDrawingBitmap(Gdiplus::Rect lineBounds ) +{ + Gdiplus::Bitmap* lineBitmap = new Gdiplus::Bitmap(lineBounds.Width, lineBounds.Height, PixelFormat32bppARGB); + Gdiplus::Graphics lineGraphics(lineBitmap); + return lineBitmap; +} + + +//---------------------------------------------------------------------------- +// +// DrawBitmapLine +// +// Creates a bitmap and draws a line on it. +// +//---------------------------------------------------------------------------- +Gdiplus::Bitmap* DrawBitmapLine(Gdiplus::Rect lineBounds, POINT p1, POINT p2, Gdiplus::Pen *pen) +{ + Gdiplus::Bitmap* lineBitmap = new Gdiplus::Bitmap(lineBounds.Width, lineBounds.Height, PixelFormat32bppARGB); + Gdiplus::Graphics lineGraphics(lineBitmap); + + // Draw the line on the temporary bitmap + lineGraphics.DrawLine(pen, p1.x - lineBounds.X, p1.y - lineBounds.Y, + p2.x - lineBounds.X, p2.y - lineBounds.Y); + + return lineBitmap; +} + + +//---------------------------------------------------------------------------- +// +// ColorFromColorRef +// +// Returns a color object from the colorRef that includes the alpha channel +// +//---------------------------------------------------------------------------- +Gdiplus::Color ColorFromColorRef(DWORD colorRef) { + BYTE a = (colorRef >> 24) & 0xFF; // Extract the alpha channel value + BYTE b = (colorRef >> 16) & 0xFF; // Extract the red channel value + BYTE g = (colorRef >> 8) & 0xFF; // Extract the green channel value + BYTE r = colorRef & 0xFF; // Extract the blue channel value + OutputDebug( L"ColorFromColorRef: %d %d %d %d\n", a, r, g, b ); + return Gdiplus::Color(a, r, g, b); +} + +//---------------------------------------------------------------------------- +// +// AdjustHighlighterColor +// +// Lighten the color. +// +//---------------------------------------------------------------------------- +void AdjustHighlighterColor(BYTE* red, BYTE* green, BYTE* blue) { + + // Adjust the color to be more visible + *red = min( 0xFF, *red ? *red + 0x40 : *red + 0x80 ); + *green = min( 0xFF, *green ? *green + 0x40 : *green + 0x80); + *blue = min( 0xFF, *blue ? *blue + 0x40 : *blue + 0x80); +} + +//---------------------------------------------------------------------------- +// +// BlendColors +// +// Blends two colors together using the alpha channel of the second color. +// The highlighter is the second color. +// +//---------------------------------------------------------------------------- +COLORREF BlendColors(COLORREF color1, const Gdiplus::Color& color2) { + + BYTE redResult, greenResult, blueResult; + + // Extract the channels from the COLORREF + BYTE red1 = GetRValue(color1); + BYTE green1 = GetGValue(color1); + BYTE blue1 = GetBValue(color1); + + // Get the channels and alpha from the Gdiplus::Color + BYTE blue2 = color2.GetRed(); + BYTE green2 = color2.GetGreen(); + BYTE red2 = color2.GetBlue(); + float alpha2 = color2.GetAlpha() / 255.0f; // Normalize to [0, 1] + //alpha2 /= 2; // Use half the alpha for higher contrast + + // Don't blend grey's as much + // int minValue = min(red1, min(green1, blue1)); + // int maxValue = max(red1, max(green1, blue1)); + if(TRUE) { // red1 > 0x10 && red1 < 0xC0 && (maxValue - minValue < 0x40)) { + + // This does a standard bright highlight + alpha2 = 0; + AdjustHighlighterColor( &red2, &green2, &blue2 ); + redResult = red2 & red1; + greenResult = green2 & green1; + blueResult = blue2 & blue1; + } + else { + + // Blend each channel + redResult = static_cast(red2 * alpha2 + red1 * (1 - alpha2)); + greenResult = static_cast(green2 * alpha2 + green1 * (1 - alpha2)); + blueResult = static_cast(blue2 * alpha2 + blue1 * (1 - alpha2)); + } + // Combine the result channels back into a COLORREF + return RGB(redResult, greenResult, blueResult); +} + + + +//---------------------------------------------------------------------------- +// +// DrawHighlightedShape +// +// Draws the shape with the highlighter color. +// +//---------------------------------------------------------------------------- +void DrawHighlightedShape( DWORD Shape, HDC hdcScreenCompat, Gdiplus::Brush *pBrush, + Gdiplus::Pen *pPen, int x1, int y1, int x2, int y2) +{ + // Create a new bitmap that's the size of the area covered by the line + 2 * g_PenWidth + Gdiplus::Rect lineBounds(min(x1, x2), min(y1, y2), abs(x2 - x1), abs(y2 - y1)); + + // Expand for line drawing + if (Shape == DRAW_LINE) + lineBounds.Inflate(static_cast(g_PenWidth / 2), static_cast(g_PenWidth / 2)); + + Gdiplus::Bitmap* lineBitmap = CreateDrawingBitmap(lineBounds); + Gdiplus::Graphics lineGraphics(lineBitmap); + switch (Shape) { + case DRAW_RECTANGLE: + lineGraphics.FillRectangle(pBrush, 0, 0, lineBounds.Width, lineBounds.Height); + break; + case DRAW_ELLIPSE: + lineGraphics.FillEllipse( pBrush, 0, 0, lineBounds.Width, lineBounds.Height); + break; + case DRAW_LINE: + lineGraphics.DrawLine(pPen, x1 - lineBounds.X, y1 - lineBounds.Y, x2 - lineBounds.X, y2 - lineBounds.Y); + break; + } + + Gdiplus::BitmapData* lineData = LockGdiPlusBitmap(lineBitmap); + BYTE* pPixels = static_cast(lineData->Scan0); + + // Create a DIB section for efficient pixel manipulation + BITMAPINFO bmi = { 0 }; + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = lineBounds.Width; + bmi.bmiHeader.biHeight = -lineBounds.Height; // Top-down DIB + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; // 32 bits per pixel + bmi.bmiHeader.biCompression = BI_RGB; + + VOID* pDIBBits; + HBITMAP hDIB = CreateDIBSection(hdcScreenCompat, &bmi, DIB_RGB_COLORS, &pDIBBits, NULL, 0); + + HDC hdcDIB = CreateCompatibleDC(hdcScreenCompat); + SelectObject(hdcDIB, hDIB); + + // Copy the relevant part of hdcScreenCompat to the DIB + BitBlt(hdcDIB, 0, 0, lineBounds.Width, lineBounds.Height, hdcScreenCompat, lineBounds.X, lineBounds.Y, SRCCOPY); + + // Pointer to the DIB bits + BYTE* pDestPixels = static_cast(pDIBBits); + + // Pointer to screen bits + HDC hdcDIBOrig; + HBITMAP hDibOrigBitmap, hDibBitmap; + BYTE* pDestPixels2 = CreateBitmapMemoryDIB(hdcScreenCompat, hdcScreenCompat, &lineBounds, + &hdcDIBOrig, &hDibBitmap, &hDibOrigBitmap); + + for (int y = 0; y < lineBounds.Height; ++y) { + for (int x = 0; x < lineBounds.Width; ++x) { + int index = (y * lineBounds.Width * 4) + (x * 4); // Assuming 4 bytes per pixel + // BYTE b = pPixels[index + 0]; // Blue channel + // BYTE g = pPixels[index + 1]; // Green channel + // BYTE r = pPixels[index + 2]; // Red channel + BYTE a = pPixels[index + 3]; // Alpha channel + + // Check if this is a drawn pixel + if (a != 0) { + // Assuming pDestPixels is a valid pointer to the destination bitmap's pixel data + BYTE destB = pDestPixels2[index + 0]; // Blue channel + BYTE destG = pDestPixels2[index + 1]; // Green channel + BYTE destR = pDestPixels2[index + 2]; // Red channel + + // Create a COLORREF value from the destination pixel data + COLORREF currentPixel = RGB(destR, destG, destB); + // Blend the colors + COLORREF newPixel = BlendColors(currentPixel, g_PenColor); + // Update the destination pixel data with the new color + pDestPixels[index + 0] = GetBValue(newPixel); + pDestPixels[index + 1] = GetGValue(newPixel); + pDestPixels[index + 2] = GetRValue(newPixel); + } + } + } + + // Copy the updated DIB back to hdcScreenCompat + BitBlt(hdcScreenCompat, lineBounds.X, lineBounds.Y, lineBounds.Width, lineBounds.Height, hdcDIB, 0, 0, SRCCOPY); + + // Clean up + DeleteObject(hDIB); + DeleteDC(hdcDIB); + + SelectObject(hdcDIBOrig, hDibOrigBitmap); + DeleteObject(hDibBitmap); + DeleteDC(hdcDIBOrig); + + // Invalidate the updated rectangle + // InvalidateGdiplusRect(hWnd, lineBounds); +} + +//---------------------------------------------------------------------------- +// +// CreateFadedDesktopBackground +// +// Creates a snapshot of the desktop that's faded and alpha blended with +// black. +// +//---------------------------------------------------------------------------- +HBITMAP CreateFadedDesktopBackground( HDC hdc, LPRECT rcScreen, LPRECT rcCrop ) +{ + // create bitmap + int width = rcScreen->right - rcScreen->left; + int height = rcScreen->bottom - rcScreen->top; + HDC hdcScreen = hdc; + HDC hdcMem = CreateCompatibleDC( hdcScreen ); + HBITMAP hBitmap = CreateCompatibleBitmap( hdcScreen, width, height ); + HBITMAP hOld = static_cast(SelectObject( hdcMem, hBitmap )); + HBRUSH hBrush = CreateSolidBrush(RGB(0, 0, 0)); + + // start with black background + FillRect( hdcMem, rcScreen, hBrush ); + if(rcCrop != NULL && rcCrop->left != -1 ) { + + // copy screen contents that are not cropped + BitBlt(hdcMem, rcCrop->left, rcCrop->top, rcCrop->right - rcCrop->left, + rcCrop->bottom - rcCrop->top, hdcScreen, rcCrop->left, rcCrop->top, SRCCOPY); + } + + // blend screen contents into it + BLENDFUNCTION blend = { 0 }; + blend.BlendOp = AC_SRC_OVER; + blend.BlendFlags = 0; + blend.SourceConstantAlpha = 0x4F; + blend.AlphaFormat = 0; + AlphaBlend( hdcMem,0, 0, width, height, + hdcScreen, rcScreen->left, rcScreen->top, + width, height, blend ); + + SelectObject( hdcMem, hOld ); + DeleteDC( hdcMem ); + DeleteObject(hBrush); + ReleaseDC( NULL, hdcScreen ); + + return hBitmap; +} + +//---------------------------------------------------------------------------- +// +// AdjustToMoveBoundary +// +// Shifts to accomodate move boundary. +// +//---------------------------------------------------------------------------- +void AdjustToMoveBoundary( float zoomLevel, int *coordinate, int cursor, int size, int max ) +{ + int diff = static_cast (static_cast(size)/ static_cast(LIVEZOOM_MOVE_REGIONS)); + if( cursor - *coordinate < diff ) + *coordinate = max( 0, cursor - diff ); + else if( (*coordinate + size) - cursor < diff ) + *coordinate = min( cursor + diff - size, max - size ); +} + +//---------------------------------------------------------------------------- +// +// GetZoomedTopLeftCoordinates +// +// Gets the left top coordinate of the zoomed area of the screen +// +//---------------------------------------------------------------------------- +void GetZoomedTopLeftCoordinates( float zoomLevel, POINT *cursorPos, int *x, int width, int *y, int height ) +{ + // smoother and more natural zoom in + float scaledWidth = width/zoomLevel; + float scaledHeight = height/zoomLevel; + *x = max( 0, min( (int) (width - scaledWidth), (int) (cursorPos->x - (int) (((float) cursorPos->x/ (float) width)*scaledWidth)))); + AdjustToMoveBoundary( zoomLevel, x, cursorPos->x, static_cast(scaledWidth), width ); + *y = max( 0, min( (int) (height - scaledHeight), (int) (cursorPos->y - (int) (((float) cursorPos->y/ (float) height)*scaledHeight)))); + AdjustToMoveBoundary( zoomLevel, y, cursorPos->y, static_cast(scaledHeight), height ); +} + + +//---------------------------------------------------------------------------- +// +// ScaleImage +// +// Use gdi+ for anti-aliased bitmap stretching. +// +//---------------------------------------------------------------------------- +void ScaleImage( HDC hdcDst, float xDst, float yDst, float wDst, float hDst, + HBITMAP bmSrc, float xSrc, float ySrc, float wSrc, float hSrc ) +{ + Gdiplus::Graphics dstGraphics( hdcDst ); + { + Gdiplus::Bitmap srcBitmap( bmSrc, NULL ); + + dstGraphics.SetInterpolationMode( Gdiplus::InterpolationModeLowQuality ); + dstGraphics.SetPixelOffsetMode( Gdiplus::PixelOffsetModeHalf ); + + dstGraphics.DrawImage( &srcBitmap, Gdiplus::RectF(xDst,yDst,wDst,hDst), xSrc, ySrc, wSrc, hSrc, Gdiplus::UnitPixel ); + } +} + + +//---------------------------------------------------------------------------- +// +// GetEncoderClsid +// +//---------------------------------------------------------------------------- +int GetEncoderClsid(const WCHAR* format, CLSID* pClsid) +{ + UINT num = 0; // number of image encoders + UINT size = 0; // size of the image encoder array in bytes +using namespace Gdiplus; + + ImageCodecInfo* pImageCodecInfo = NULL; + + GetImageEncodersSize(&num, &size); + if(size == 0) + return -1; // Failure + + pImageCodecInfo = static_cast(malloc(size)); + if(pImageCodecInfo == NULL) + return -1; // Failure + + GetImageEncoders(num, size, pImageCodecInfo); + + for(UINT j = 0; j < num; ++j) + { + if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 ) + { + *pClsid = pImageCodecInfo[j].Clsid; + free(pImageCodecInfo); + return j; // Success + } + } + + free(pImageCodecInfo); + return -1; // Failure +} + +//---------------------------------------------------------------------- +// +// ConvertToUnicode +// +//---------------------------------------------------------------------- +void +ConvertToUnicode( + PCHAR aString, + PWCHAR wString, + DWORD wStringLength + ) +{ + size_t len; + + len = MultiByteToWideChar( CP_ACP, 0, aString, static_cast(strlen(aString)), + wString, wStringLength ); + wString[len] = 0; +} + + +//---------------------------------------------------------------------------- +// +// LoadImageFile +// +// Use gdi+ to load an image. +// +//---------------------------------------------------------------------------- +HBITMAP LoadImageFile( PTCHAR Filename ) +{ + HBITMAP hBmp; + + Gdiplus::Bitmap *bitmap; + + bitmap = Gdiplus::Bitmap::FromFile(Filename); + if( bitmap->GetHBITMAP( NULL, &hBmp )) { + + return NULL; + } + delete bitmap; + return hBmp; +} + + +//---------------------------------------------------------------------------- +// +// SavePng +// +// Use gdi+ to save a PNG. +// +//---------------------------------------------------------------------------- +DWORD SavePng( PTCHAR Filename, HBITMAP hBitmap ) +{ + Gdiplus::Bitmap bitmap( hBitmap, NULL ); + CLSID pngClsid; + GetEncoderClsid(L"image/png", &pngClsid); + if( bitmap.Save( Filename, &pngClsid, NULL )) { + + return GetLastError(); + } + return ERROR_SUCCESS; +} + + +//---------------------------------------------------------------------------- +// +// EnableDisableTrayIcon +// +//---------------------------------------------------------------------------- +void EnableDisableTrayIcon( HWND hWnd, BOOLEAN Enable ) +{ + NOTIFYICONDATA tNotifyIconData; + + memset( &tNotifyIconData, 0, sizeof(tNotifyIconData)); + tNotifyIconData.cbSize = sizeof(NOTIFYICONDATA); + tNotifyIconData.hWnd = hWnd; + tNotifyIconData.uID = 1; + tNotifyIconData.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; + tNotifyIconData.uCallbackMessage = WM_USER_TRAY_ACTIVATE; + tNotifyIconData.hIcon = LoadIcon( g_hInstance, L"APPICON" ); + lstrcpyn(tNotifyIconData.szTip, APPNAME, sizeof(APPNAME)); + Shell_NotifyIcon(Enable ? NIM_ADD : NIM_DELETE, &tNotifyIconData); +} + +//---------------------------------------------------------------------------- +// +// EnableDisableOpacity +// +//---------------------------------------------------------------------------- +void EnableDisableOpacity( HWND hWnd, BOOLEAN Enable ) +{ + DWORD exStyle; + + if( pSetLayeredWindowAttributes && g_BreakOpacity != 100 ) { + + if( Enable ) { + + exStyle = GetWindowLong(hWnd, GWL_EXSTYLE); + SetWindowLong(hWnd, GWL_EXSTYLE, (exStyle | WS_EX_LAYERED)); + + pSetLayeredWindowAttributes(hWnd, 0, static_cast ((255 * g_BreakOpacity) / 100), LWA_ALPHA); + RedrawWindow(hWnd, 0, 0, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN); + + } else { + + exStyle = GetWindowLong(hWnd, GWL_EXSTYLE); + SetWindowLong(hWnd, GWL_EXSTYLE, (exStyle & ~WS_EX_LAYERED)); + } + } +} + +//---------------------------------------------------------------------------- +// +// EnableDisableScreenSaver +// +//---------------------------------------------------------------------------- +void EnableDisableScreenSaver( BOOLEAN Enable ) +{ + SystemParametersInfo(SPI_SETSCREENSAVEACTIVE,Enable,0,0); + SystemParametersInfo(SPI_SETPOWEROFFACTIVE,Enable,0,0); + SystemParametersInfo(SPI_SETLOWPOWERACTIVE,Enable,0,0); +} + +//---------------------------------------------------------------------------- +// +// EnableDisableStickyKeys +// +//---------------------------------------------------------------------------- +void EnableDisableStickyKeys( BOOLEAN Enable ) +{ + static STICKYKEYS prevStickyKeyValue = {0}; + STICKYKEYS newStickyKeyValue = {0}; + + // Need to do this on Vista tablet to stop sticky key popup when you + // hold down the shift key and draw with the pen. + if( Enable ) { + + if( prevStickyKeyValue.cbSize == sizeof(STICKYKEYS)) { + + SystemParametersInfo(SPI_SETSTICKYKEYS, + sizeof(STICKYKEYS), &prevStickyKeyValue, SPIF_SENDCHANGE); + } + + } else { + + prevStickyKeyValue.cbSize = sizeof(STICKYKEYS); + if (SystemParametersInfo(SPI_GETSTICKYKEYS, sizeof(STICKYKEYS), + &prevStickyKeyValue, 0)) { + + newStickyKeyValue.cbSize = sizeof(STICKYKEYS); + newStickyKeyValue.dwFlags = 0; + if( !SystemParametersInfo(SPI_SETSTICKYKEYS, + sizeof(STICKYKEYS), &newStickyKeyValue, SPIF_SENDCHANGE)) { + + // DWORD error = GetLastError(); + + } + } + } +} + + +//---------------------------------------------------------------------------- +// +// GetKeyMod +// +//---------------------------------------------------------------------------- +constexpr DWORD GetKeyMod( DWORD Key ) +{ + DWORD keyMod = 0; + if( (Key >> 8) & HOTKEYF_ALT ) keyMod |= MOD_ALT; + if( (Key >> 8) & HOTKEYF_CONTROL) keyMod |= MOD_CONTROL; + if( (Key >> 8) & HOTKEYF_SHIFT) keyMod |= MOD_SHIFT; + if( (Key >> 8) & HOTKEYF_EXT) keyMod |= MOD_WIN; + return keyMod; +} + + +//---------------------------------------------------------------------------- +// +// AdvancedBreakProc +// +//---------------------------------------------------------------------------- +INT_PTR CALLBACK AdvancedBreakProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam ) +{ + TCHAR opacity[10]; + static TCHAR newSoundFile[MAX_PATH]; + static TCHAR newBackgroundFile[MAX_PATH]; + TCHAR filePath[MAX_PATH], initDir[MAX_PATH]; + DWORD i; + OPENFILENAME openFileName; + + switch ( message ) { + case WM_INITDIALOG: + if( pSHAutoComplete ) { + pSHAutoComplete( GetDlgItem( hDlg, IDC_SOUND_FILE), SHACF_FILESYSTEM ); + pSHAutoComplete( GetDlgItem( hDlg, IDC_BACKGROUND_FILE), SHACF_FILESYSTEM ); + } + CheckDlgButton( hDlg, IDC_CHECK_BACKGROUND_FILE, + g_BreakShowBackgroundFile ? BST_CHECKED: BST_UNCHECKED ); + CheckDlgButton( hDlg, IDC_CHECK_SOUND_FILE, + g_BreakPlaySoundFile ? BST_CHECKED: BST_UNCHECKED ); + CheckDlgButton( hDlg, IDC_CHECK_SHOW_EXPIRED, + g_ShowExpiredTime ? BST_CHECKED : BST_UNCHECKED ); + CheckDlgButton( hDlg, IDC_CHECK_BACKGROUND_STRETCH, + g_BreakBackgroundStretch ? BST_CHECKED : BST_UNCHECKED ); +#if 0 + CheckDlgButton( hDlg, IDC_CHECK_SECONDARYDISPLAY, + g_BreakOnSecondary ? BST_CHECKED : BST_UNCHECKED ); +#endif + if( pSetLayeredWindowAttributes == NULL ) { + + EnableWindow( GetDlgItem( hDlg, IDC_OPACITY ), FALSE ); + } + + // sound file + if( !g_BreakPlaySoundFile ) { + + EnableWindow( GetDlgItem( hDlg, IDC_STATIC_SOUND_FILE ), FALSE ); + EnableWindow( GetDlgItem( hDlg, IDC_SOUND_FILE ), FALSE ); + EnableWindow( GetDlgItem( hDlg, IDC_SOUND_BROWSE ), FALSE ); + } + _tcscpy( newSoundFile, g_BreakSoundFile ); + _tcscpy( filePath, g_BreakSoundFile ); + if( _tcsrchr( filePath, '\\' )) _tcscpy( filePath, _tcsrchr( g_BreakSoundFile, '\\' )+1); + if( _tcsrchr( filePath, '.' )) *_tcsrchr( filePath, '.' ) = 0; + SetDlgItemText( hDlg, IDC_SOUND_FILE, filePath ); + + // background file + if( !g_BreakShowBackgroundFile ) { + + EnableWindow( GetDlgItem( hDlg, IDC_STATIC_DESKTOP_BACKGROUND ), FALSE ); + EnableWindow( GetDlgItem( hDlg, IDC_STATIC_DESKTOP_BACKGROUND ), FALSE ); + EnableWindow( GetDlgItem( hDlg, IDC_STATIC_BACKGROUND_FILE ), FALSE ); + EnableWindow( GetDlgItem( hDlg, IDC_BACKGROUND_FILE ), FALSE ); + EnableWindow( GetDlgItem( hDlg, IDC_BACKGROUND_BROWSE ), FALSE ); + EnableWindow( GetDlgItem( hDlg, IDC_CHECK_BACKGROUND_STRETCH ), FALSE ); + } + CheckDlgButton( hDlg, + g_BreakShowDesktop ? IDC_STATIC_DESKTOP_BACKGROUND : IDC_STATIC_BACKGROUND_FILE, BST_CHECKED ); + _tcscpy( newBackgroundFile, g_BreakBackgroundFile ); + SetDlgItemText( hDlg, IDC_BACKGROUND_FILE, g_BreakBackgroundFile ); + + CheckDlgButton( hDlg, IDC_TIMER_POS1 + g_BreakTimerPosition, BST_CHECKED ); + + for( i = 10; i <= 100; i += 10) { + + _stprintf( opacity, L"%d%%", i ); + SendMessage( GetDlgItem( hDlg, IDC_OPACITY ), CB_ADDSTRING, 0, + reinterpret_cast(opacity)); + } + SendMessage( GetDlgItem( hDlg, IDC_OPACITY ), CB_SETCURSEL, + g_BreakOpacity / 10 - 1, 0 ); + return TRUE; + + case WM_COMMAND: + switch ( HIWORD( wParam )) { + case BN_CLICKED: + if( LOWORD( wParam ) == IDC_CHECK_SOUND_FILE ) { + + EnableWindow( GetDlgItem( hDlg, IDC_STATIC_SOUND_FILE ), + IsDlgButtonChecked( hDlg, IDC_CHECK_SOUND_FILE) == BST_CHECKED ); + EnableWindow( GetDlgItem( hDlg, IDC_SOUND_FILE ), + IsDlgButtonChecked( hDlg, IDC_CHECK_SOUND_FILE) == BST_CHECKED ); + EnableWindow( GetDlgItem( hDlg, IDC_SOUND_BROWSE ), + IsDlgButtonChecked( hDlg, IDC_CHECK_SOUND_FILE) == BST_CHECKED ); + } + if( LOWORD( wParam ) == IDC_CHECK_BACKGROUND_FILE ) { + + EnableWindow( GetDlgItem( hDlg, IDC_CHECK_BACKGROUND_STRETCH ), + IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED ); + EnableWindow( GetDlgItem( hDlg, IDC_STATIC_DESKTOP_BACKGROUND ), + IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED ); + EnableWindow( GetDlgItem( hDlg, IDC_STATIC_BACKGROUND_FILE ), + IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED ); + EnableWindow( GetDlgItem( hDlg, IDC_BACKGROUND_FILE ), + IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED ); + EnableWindow( GetDlgItem( hDlg, IDC_BACKGROUND_BROWSE ), + IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED ); + } + break; + } + switch ( LOWORD( wParam )) { + case IDC_SOUND_BROWSE: + memset( &openFileName, 0, sizeof(openFileName )); + openFileName.lStructSize = OPENFILENAME_SIZE_VERSION_400; + openFileName.hwndOwner = hDlg; + openFileName.hInstance = static_cast(g_hInstance); + openFileName.nMaxFile = sizeof(filePath)/sizeof(filePath[0]); + openFileName.Flags = OFN_LONGNAMES; + openFileName.lpstrTitle = L"Specify sound file..."; + openFileName.lpstrDefExt = L"*.wav"; + openFileName.nFilterIndex = 1; + openFileName.lpstrFilter = L"Sounds\0*.wav\0All Files\0*.*\0"; + + GetDlgItemText( hDlg, IDC_SOUND_FILE, filePath, sizeof(filePath )); + if( _tcsrchr( filePath, '\\' )) { + + _tcscpy( initDir, filePath ); + _tcscpy( filePath, _tcsrchr( initDir, '\\' )+1); + *(_tcsrchr( initDir, '\\' )+1) = 0; + } else { + + _tcscpy( filePath, L"%WINDIR%\\Media" ); + ExpandEnvironmentStrings( filePath, initDir, sizeof(initDir)/sizeof(initDir[0])); + GetDlgItemText( hDlg, IDC_SOUND_FILE, filePath, sizeof(filePath )); + } + openFileName.lpstrInitialDir = initDir; + openFileName.lpstrFile = filePath; + if( GetOpenFileName( &openFileName )) { + + _tcscpy( newSoundFile, filePath ); + if(_tcsrchr( filePath, '\\' )) _tcscpy( filePath, _tcsrchr( newSoundFile, '\\' )+1); + if(_tcsrchr( filePath, '.' )) *_tcsrchr( filePath, '.' ) = 0; + SetDlgItemText( hDlg, IDC_SOUND_FILE, filePath ); + } + break; + + case IDC_BACKGROUND_BROWSE: + memset( &openFileName, 0, sizeof(openFileName )); + openFileName.lStructSize = OPENFILENAME_SIZE_VERSION_400; + openFileName.hwndOwner = hDlg; + openFileName.hInstance = static_cast(g_hInstance); + openFileName.nMaxFile = sizeof(filePath)/sizeof(filePath[0]); + openFileName.Flags = OFN_LONGNAMES; + openFileName.lpstrTitle = L"Specify background file..."; + openFileName.lpstrDefExt = L"*.bmp"; + openFileName.nFilterIndex = 5; + openFileName.lpstrFilter = L"Bitmap Files (*.bmp;*.dib)\0*.bmp;*.dib\0" + "PNG (*.png)\0*.png\0" + "JPEG (*.jpg;*.jpeg;*.jpe;*.jfif)\0*.jpg;*.jpeg;*.jpe;*.jfif\0" + "GIF (*.gif)\0*.gif\0" + "All Picture Files\0.bmp;*.dib;*.png;*.jpg;*.jpeg;*.jpe;*.jfif;*.gif)\0" + "All Files\0*.*\0\0"; + + GetDlgItemText( hDlg, IDC_BACKGROUND_FILE, filePath, sizeof(filePath )); + if(_tcsrchr( filePath, '\\' )) { + + _tcscpy( initDir, filePath ); + _tcscpy( filePath, _tcsrchr( initDir, '\\' )+1); + *(_tcsrchr( initDir, '\\' )+1) = 0; + } else { + + _tcscpy( filePath, L"%USERPROFILE%\\Pictures" ); + ExpandEnvironmentStrings( filePath, initDir, sizeof(initDir)/sizeof(initDir[0])); + GetDlgItemText( hDlg, IDC_BACKGROUND_FILE, filePath, sizeof(filePath )); + } + openFileName.lpstrInitialDir = initDir; + openFileName.lpstrFile = filePath; + if( GetOpenFileName( &openFileName )) { + + _tcscpy( newBackgroundFile, filePath ); + SetDlgItemText( hDlg, IDC_BACKGROUND_FILE, filePath ); + } + break; + + case IDOK: + + // sound file has to be valid + g_BreakPlaySoundFile = IsDlgButtonChecked( hDlg, IDC_CHECK_SOUND_FILE ) == BST_CHECKED; + g_BreakShowBackgroundFile = IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE ) == BST_CHECKED; + g_BreakBackgroundStretch = IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_STRETCH ) == BST_CHECKED; +#if 0 + g_BreakOnSecondary = IsDlgButtonChecked( hDlg, IDC_CHECK_SECONDARYDISPLAY ) == BST_CHECKED; +#endif + if( g_BreakPlaySoundFile && GetFileAttributes( newSoundFile ) == -1 ) { + + MessageBox( hDlg, L"The specified sound file is inaccessible", + L"Advanced Break Options Error", MB_ICONERROR ); + break; + } + _tcscpy( g_BreakSoundFile, newSoundFile ); + + // Background file + g_BreakShowDesktop = IsDlgButtonChecked( hDlg, IDC_STATIC_DESKTOP_BACKGROUND ) == BST_CHECKED; + + if( !g_BreakShowDesktop && g_BreakShowBackgroundFile && GetFileAttributes( newBackgroundFile ) == -1 ) { + + MessageBox( hDlg, L"The specified background file is inaccessible", + L"Advanced Break Options Error", MB_ICONERROR ); + break; + } + _tcscpy( g_BreakBackgroundFile, newBackgroundFile ); + + for( i = 0; i < 10; i++ ) { + + if( IsDlgButtonChecked( hDlg, IDC_TIMER_POS1+i) == BST_CHECKED ) { + + g_BreakTimerPosition = i; + break; + } + } + GetDlgItemText( hDlg, IDC_OPACITY, opacity, sizeof(opacity)/sizeof(opacity[0])); + _stscanf( opacity, L"%d%%", &g_BreakOpacity ); + reg.WriteRegSettings( RegSettings ); + EndDialog(hDlg, 0); + break; + + case IDCANCEL: + EndDialog( hDlg, 0 ); + return TRUE; + } + break; + + default: + break; + } + return FALSE; +} + + +//---------------------------------------------------------------------------- +// +// OptionsTabProc +// +//---------------------------------------------------------------------------- +INT_PTR CALLBACK OptionsTabProc( HWND hDlg, UINT message, + WPARAM wParam, LPARAM lParam ) +{ + HDC hDC; + LOGFONT lf; + CHOOSEFONT chooseFont; + HFONT hFont; + PAINTSTRUCT ps; + HWND hTextPreview; + HDC hDc; + RECT previewRc; + TCHAR filePath[MAX_PATH] = {0}; + OPENFILENAME openFileName; + + switch ( message ) { + case WM_INITDIALOG: + return TRUE; + case WM_COMMAND: + switch ( LOWORD( wParam )) { + case IDC_ADVANCED_BREAK: + DialogBox( g_hInstance, L"ADVANCED_BREAK", hDlg, AdvancedBreakProc ); + break; + case IDC_FONT: + hDC = GetDC (hDlg ); + lf = g_LogFont; + lf.lfHeight = -21; + chooseFont.hDC = CreateCompatibleDC (hDC); + ReleaseDC (hDlg, hDC); + chooseFont.lStructSize = sizeof (CHOOSEFONT); + chooseFont.hwndOwner = hDlg; + chooseFont.lpLogFont = &lf; + chooseFont.Flags = CF_SCREENFONTS|CF_ENABLETEMPLATE| + CF_INITTOLOGFONTSTRUCT|CF_LIMITSIZE; + chooseFont.rgbColors = RGB (0, 0, 0); + chooseFont.lCustData = 0; + chooseFont.nSizeMin = 16; + chooseFont.nSizeMax = 16; + chooseFont.hInstance = g_hInstance; + chooseFont.lpszStyle = static_cast(NULL); + chooseFont.nFontType = SCREEN_FONTTYPE; + chooseFont.lpfnHook = reinterpret_cast(static_cast(NULL)); + chooseFont.lpTemplateName = static_cast(MAKEINTRESOURCE (FORMATDLGORD31)); + if( ChooseFont( &chooseFont ) ) { + g_LogFont = lf; + InvalidateRect( hDlg, NULL, TRUE ); + } + break; + case IDC_DEMOTYPE_BROWSE: + memset( &openFileName, 0, sizeof( openFileName ) ); + openFileName.lStructSize = OPENFILENAME_SIZE_VERSION_400; + openFileName.hwndOwner = hDlg; + openFileName.hInstance = static_cast(g_hInstance); + openFileName.nMaxFile = sizeof( filePath ) / sizeof( filePath[0] ); + openFileName.Flags = OFN_LONGNAMES; + openFileName.lpstrTitle = L"Specify DemoType file..."; + openFileName.nFilterIndex = 1; + openFileName.lpstrFilter = L"All Files\0*.*\0\0"; + openFileName.lpstrFile = filePath; + + if( GetOpenFileName( &openFileName ) ) + { + if( GetFileAttributes( filePath ) == -1 ) + { + MessageBox( hDlg, L"The specified file is inaccessible", APPNAME, MB_ICONERROR ); + } + else + { + SetDlgItemText( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_FILE, filePath ); + _tcscpy( g_DemoTypeFile, filePath ); + } + } + break; + } + break; + + case WM_PAINT: + if( (hTextPreview = GetDlgItem( hDlg, IDC_TEXT_FONT ))) { + + // 16-pt preview + LOGFONT _lf = g_LogFont; + _lf.lfHeight = -21; + hFont = CreateFontIndirect( &_lf); + hDc = BeginPaint(hDlg, &ps); + SelectObject( hDc, hFont ); + + GetWindowRect( hTextPreview, &previewRc ); + MapWindowPoints( NULL, hDlg, reinterpret_cast(&previewRc), 2); + + previewRc.top += 6; + DrawText( hDc, L"Sample", static_cast(_tcslen(L"Sample")), &previewRc, + DT_CENTER|DT_VCENTER|DT_SINGLELINE ); + + EndPaint( hDlg, &ps ); + DeleteObject( hFont ); + } + break; + default: + break; + } + return FALSE; +} + + +//---------------------------------------------------------------------------- +// +// OptionsAddTabs +// +//---------------------------------------------------------------------------- +VOID OptionsAddTabs( HWND hOptionsDlg, HWND hTabCtrl ) +{ + int i; + TCITEM tcItem; + RECT rc, pageRc; + + GetWindowRect( hTabCtrl, &rc ); + for( i = 0; i < sizeof( g_OptionsTabs )/sizeof(g_OptionsTabs[0]); i++ ) { + + tcItem.mask = TCIF_TEXT; + tcItem.pszText = g_OptionsTabs[i].TabTitle; + TabCtrl_InsertItem( hTabCtrl, i, &tcItem ); + g_OptionsTabs[i].hPage = CreateDialog( g_hInstance, g_OptionsTabs[i].TabTitle, + hOptionsDlg, OptionsTabProc ); + } + TabCtrl_AdjustRect( hTabCtrl, FALSE, &rc ); + for( i = 0; i < sizeof( g_OptionsTabs )/sizeof(g_OptionsTabs[0]); i++ ) { + + pageRc = rc; + MapWindowPoints( NULL, g_OptionsTabs[i].hPage, reinterpret_cast(&pageRc), 2); + + SetWindowPos( g_OptionsTabs[i].hPage, + HWND_TOP, + pageRc.left, pageRc.top, + pageRc.right - pageRc.left, pageRc.bottom - pageRc.top, + SWP_NOACTIVATE|(i == 0 ? SWP_SHOWWINDOW : SWP_HIDEWINDOW)); + + if( pEnableThemeDialogTexture ) { + + pEnableThemeDialogTexture( g_OptionsTabs[i].hPage, ETDT_ENABLETAB ); + } + } +} + +//---------------------------------------------------------------------------- +// +// UnregisterAllHotkeys +// +//---------------------------------------------------------------------------- +void UnregisterAllHotkeys( HWND hWnd ) +{ + UnregisterHotKey( hWnd, ZOOM_HOTKEY); + UnregisterHotKey( hWnd, LIVE_HOTKEY); + UnregisterHotKey( hWnd, LIVE_DRAW_HOTKEY); + UnregisterHotKey( hWnd, DRAW_HOTKEY); + UnregisterHotKey( hWnd, BREAK_HOTKEY); + UnregisterHotKey( hWnd, RECORD_HOTKEY); + UnregisterHotKey( hWnd, RECORD_CROP_HOTKEY ); + UnregisterHotKey( hWnd, RECORD_WINDOW_HOTKEY ); + UnregisterHotKey( hWnd, SNIP_HOTKEY ); + UnregisterHotKey( hWnd, SNIP_SAVE_HOTKEY); + UnregisterHotKey( hWnd, DEMOTYPE_HOTKEY ); + UnregisterHotKey( hWnd, DEMOTYPE_RESET_HOTKEY ); +} + +//---------------------------------------------------------------------------- +// +// RegisterAllHotkeys +// +//---------------------------------------------------------------------------- +void RegisterAllHotkeys(HWND hWnd) +{ + if (g_ToggleKey) RegisterHotKey(hWnd, ZOOM_HOTKEY, g_ToggleMod, g_ToggleKey & 0xFF); + if (g_LiveZoomToggleKey) { + RegisterHotKey(hWnd, LIVE_HOTKEY, g_LiveZoomToggleMod, g_LiveZoomToggleKey & 0xFF); + RegisterHotKey(hWnd, LIVE_DRAW_HOTKEY, (g_LiveZoomToggleMod ^ MOD_SHIFT), g_LiveZoomToggleKey & 0xFF); + } + if (g_DrawToggleKey) RegisterHotKey(hWnd, DRAW_HOTKEY, g_DrawToggleMod, g_DrawToggleKey & 0xFF); + if (g_BreakToggleKey) RegisterHotKey(hWnd, BREAK_HOTKEY, g_BreakToggleMod, g_BreakToggleKey & 0xFF); + if (g_DemoTypeToggleKey) { + RegisterHotKey(hWnd, DEMOTYPE_HOTKEY, g_DemoTypeToggleMod, g_DemoTypeToggleKey & 0xFF); + RegisterHotKey(hWnd, DEMOTYPE_RESET_HOTKEY, (g_DemoTypeToggleMod ^ MOD_SHIFT), g_DemoTypeToggleKey & 0xFF); + } + if (g_SnipToggleKey) { + RegisterHotKey(hWnd, SNIP_HOTKEY, g_SnipToggleMod, g_SnipToggleKey & 0xFF); + RegisterHotKey(hWnd, SNIP_SAVE_HOTKEY, (g_SnipToggleMod ^ MOD_SHIFT), g_SnipToggleKey & 0xFF); + } + if (g_RecordToggleKey) { + RegisterHotKey(hWnd, RECORD_HOTKEY, g_RecordToggleMod | MOD_NOREPEAT, g_RecordToggleKey & 0xFF); + RegisterHotKey(hWnd, RECORD_CROP_HOTKEY, (g_RecordToggleMod ^ MOD_SHIFT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF); + RegisterHotKey(hWnd, RECORD_WINDOW_HOTKEY, (g_RecordToggleMod ^ MOD_ALT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF); + } +} + + + +//---------------------------------------------------------------------------- +// +// UpdateDrawTabHeaderFont +// +//---------------------------------------------------------------------------- +void UpdateDrawTabHeaderFont() +{ + static HFONT headerFont = nullptr; + TCHAR text[64]; + + if( headerFont != nullptr ) + { + DeleteObject( headerFont ); + headerFont = nullptr; + } + + constexpr int headers[] = { IDC_PEN_CONTROL, IDC_COLORS, IDC_HIGHLIGHT_AND_BLUR, IDC_SHAPES, IDC_SCREEN }; + for( int i = 0; i < _countof( headers ); i++ ) + { + // Change the header font to bold + HWND hHeader = GetDlgItem( g_OptionsTabs[DRAW_PAGE].hPage, headers[i] ); + if( headerFont == nullptr ) + { + HFONT hFont = reinterpret_cast(SendMessage( hHeader, WM_GETFONT, 0, 0 )); + LOGFONT lf = {}; + GetObject( hFont, sizeof( LOGFONT ), &lf ); + lf.lfWeight = FW_BOLD; + headerFont = CreateFontIndirect( &lf ); + } + SendMessage( hHeader, WM_SETFONT, reinterpret_cast(headerFont), 0 ); + + // Resize the control to fit the text + GetWindowText( hHeader, text, sizeof( text ) / sizeof( text[0] ) ); + RECT rc; + GetWindowRect( hHeader, &rc ); + MapWindowPoints( NULL, g_OptionsTabs[DRAW_PAGE].hPage, reinterpret_cast(&rc), 2 ); + HDC hDC = GetDC( hHeader ); + SelectFont( hDC, headerFont ); + DrawText( hDC, text, static_cast(_tcslen( text )), &rc, DT_CALCRECT | DT_SINGLELINE | DT_LEFT | DT_VCENTER ); + ReleaseDC( hHeader, hDC ); + SetWindowPos( hHeader, nullptr, 0, 0, rc.right - rc.left + ScaleForDpi( 4, GetDpiForWindowHelper( hHeader ) ), rc.bottom - rc.top, SWP_NOMOVE | SWP_NOZORDER ); + } +} + +//---------------------------------------------------------------------------- +// +// OptionsProc +// +//---------------------------------------------------------------------------- +INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message, + WPARAM wParam, LPARAM lParam ) +{ + static HFONT hFontBold = nullptr; + PNMLINK notify = nullptr; + static int curTabSel = 0; + static HWND hTabCtrl; + static HWND hOpacity; + static HWND hToggleKey; + TCHAR text[32]; + DWORD newToggleKey, newTimeout, newToggleMod, newBreakToggleKey, newDemoTypeToggleKey, newRecordToggleKey, newSnipToggleKey; + DWORD newDrawToggleKey, newDrawToggleMod, newBreakToggleMod, newDemoTypeToggleMod, newRecordToggleMod, newSnipToggleMod; + DWORD newLiveZoomToggleKey, newLiveZoomToggleMod; + static std::vector> microphones; + + switch ( message ) { + case WM_INITDIALOG: + { + if( hWndOptions ) { + + BringWindowToTop( hWndOptions ); + SetFocus( hWndOptions ); + SetForegroundWindow( hWndOptions ); + EndDialog( hDlg, 0 ); + return FALSE; + } + hWndOptions = hDlg; + + SetForegroundWindow( hDlg ); + SetActiveWindow( hDlg ); + SetWindowPos( hDlg, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_SHOWWINDOW ); +#if 1 + // set version info + TCHAR filePath[MAX_PATH]; + const TCHAR* verString; + + GetModuleFileName(NULL, filePath, _countof(filePath)); + DWORD zero = 0; + DWORD infoSize = GetFileVersionInfoSize(filePath, &zero); + void* versionInfo = malloc(infoSize); + GetFileVersionInfo(filePath, 0, infoSize, versionInfo); + + verString = GetVersionString(static_cast(versionInfo), _T("FileVersion")); + SetDlgItemText(hDlg, IDC_VERSION, (std::wstring(L"ZoomIt v") + verString).c_str()); + + verString = GetVersionString(static_cast(versionInfo), _T("LegalCopyright")); + SetDlgItemText(hDlg, IDC_COPYRIGHT, verString); + + free(versionInfo); +#endif + // Add tabs + hTabCtrl = GetDlgItem( hDlg, IDC_TAB ); + OptionsAddTabs( hDlg, hTabCtrl ); + + InitializeFonts( hDlg, &hFontBold ); + UpdateDrawTabHeaderFont(); + + // Configure options + SendMessage( GetDlgItem( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_HOTKEY), HKM_SETRULES, + static_cast(HKCOMB_NONE), // invalid key combinations + MAKELPARAM(HOTKEYF_ALT, 0)); // add ALT to invalid entries + + if( g_ToggleKey ) SendMessage( GetDlgItem( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_HOTKEY), HKM_SETHOTKEY, g_ToggleKey, 0 ); + if( pMagInitialize ) { + + if( g_LiveZoomToggleKey ) SendMessage( GetDlgItem( g_OptionsTabs[LIVE_PAGE].hPage, IDC_LIVE_HOTKEY), HKM_SETHOTKEY, g_LiveZoomToggleKey, 0 ); + + } else { + + EnableWindow( GetDlgItem( g_OptionsTabs[LIVE_PAGE].hPage, IDC_LIVE_HOTKEY), FALSE ); + EnableWindow( GetDlgItem( g_OptionsTabs[LIVE_PAGE].hPage, IDC_ZOOM_LEVEL), FALSE ); + EnableWindow( GetDlgItem( g_OptionsTabs[LIVE_PAGE].hPage, IDC_ZOOM_SPIN), FALSE ); + } + if( g_DrawToggleKey ) SendMessage( GetDlgItem( g_OptionsTabs[DRAW_PAGE].hPage, IDC_DRAW_HOTKEY), HKM_SETHOTKEY, g_DrawToggleKey, 0 ); + if( g_BreakToggleKey ) SendMessage( GetDlgItem( g_OptionsTabs[BREAK_PAGE].hPage, IDC_BREAK_HOTKEY), HKM_SETHOTKEY, g_BreakToggleKey, 0 ); + if( g_DemoTypeToggleKey ) SendMessage( GetDlgItem( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_HOTKEY ), HKM_SETHOTKEY, g_DemoTypeToggleKey, 0 ); + if( g_RecordToggleKey ) SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_HOTKEY), HKM_SETHOTKEY, g_RecordToggleKey, 0 ); + if( g_SnipToggleKey) SendMessage( GetDlgItem( g_OptionsTabs[SNIP_PAGE].hPage, IDC_SNIP_HOTKEY), HKM_SETHOTKEY, g_SnipToggleKey, 0 ); + CheckDlgButton( hDlg, IDC_SHOW_TRAY_ICON, + g_ShowTrayIcon ? BST_CHECKED: BST_UNCHECKED ); + CheckDlgButton( hDlg, IDC_AUTOSTART, + IsAutostartConfigured() ? BST_CHECKED: BST_UNCHECKED ); + CheckDlgButton( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_ANIMATE_ZOOM, + g_AnimateZoom ? BST_CHECKED: BST_UNCHECKED ); + + SendMessage( GetDlgItem(g_OptionsTabs[ZOOM_PAGE].hPage, IDC_ZOOM_SLIDER), TBM_SETRANGE, false, MAKELONG(0,_countof(g_ZoomLevels)-1) ); + SendMessage( GetDlgItem(g_OptionsTabs[ZOOM_PAGE].hPage, IDC_ZOOM_SLIDER), TBM_SETPOS, true, g_SliderZoomLevel ); + + _stprintf( text, L"%d", g_PenWidth ); + SetDlgItemText( g_OptionsTabs[DRAW_PAGE].hPage, IDC_PEN_WIDTH, text ); + SendMessage( GetDlgItem( g_OptionsTabs[DRAW_PAGE].hPage, IDC_PEN_WIDTH ), EM_LIMITTEXT, 1, 0 ); + SendMessage (GetDlgItem( g_OptionsTabs[DRAW_PAGE].hPage, IDC_SPIN), UDM_SETRANGE, 0L, + MAKELPARAM (19, 1)); + + _stprintf( text, L"%d", g_BreakTimeout ); + SetDlgItemText( g_OptionsTabs[BREAK_PAGE].hPage, IDC_TIMER, text ); + SendMessage( GetDlgItem( g_OptionsTabs[BREAK_PAGE].hPage, IDC_TIMER ), EM_LIMITTEXT, 2, 0 ); + SendMessage (GetDlgItem( g_OptionsTabs[BREAK_PAGE].hPage, IDC_SPIN_TIMER), UDM_SETRANGE, 0L, + MAKELPARAM (99, 1)); + CheckDlgButton( g_OptionsTabs[BREAK_PAGE].hPage, IDC_CHECK_SHOW_EXPIRED, + g_ShowExpiredTime ? BST_CHECKED : BST_UNCHECKED ); + + CheckDlgButton( g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_AUDIO, + g_CaptureAudio ? BST_CHECKED: BST_UNCHECKED ); + + for (int i = 0; i < _countof(g_FramerateOptions); i++) { + + _stprintf(text, L"%d", g_FramerateOptions[i]); + SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FRAME_RATE), static_cast(CB_ADDSTRING), + static_cast(0), reinterpret_cast(text)); + if (g_RecordFrameRate == g_FramerateOptions[i]) { + + SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FRAME_RATE), CB_SETCURSEL, static_cast(i), static_cast(0)); + } + } + for(unsigned int i = 1; i < 11; i++) { + + _stprintf(text, L"%2.1f", (static_cast(i)) / 10 ); + SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), static_cast(CB_ADDSTRING), + static_cast(0), reinterpret_cast(text)); + if (g_RecordScaling == i*10 ) { + + SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), CB_SETCURSEL, static_cast(i)-1, static_cast(0)); + } + } + + // Get the current set of microphones + microphones.clear(); + concurrency::create_task([]{ + auto devices = winrt::DeviceInformation::FindAllAsync( winrt::DeviceClass::AudioCapture ).get(); + for( auto device : devices ) + { + microphones.emplace_back( device.Id().c_str(), device.Name().c_str() ); + } + }).get(); + + // Add the microphone devices to the combo box and set the current selection + SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), static_cast(CB_ADDSTRING), static_cast(0), reinterpret_cast(L"Default")); + size_t selection = 0; + for( size_t i = 0; i < microphones.size(); i++ ) + { + SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), static_cast(CB_ADDSTRING), static_cast(0), reinterpret_cast(microphones[i].second.c_str()) ); + if( selection == 0 && wcscmp( microphones[i].first.c_str(), g_MicrophoneDeviceId ) == 0 ) + { + selection = i + 1; + } + } + SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), CB_SETCURSEL, static_cast(selection), static_cast(0) ); + + if( GetFileAttributes( g_DemoTypeFile ) == -1 ) + { + memset( g_DemoTypeFile, 0, sizeof( g_DemoTypeFile ) ); + } + else + { + SetDlgItemText( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_FILE, g_DemoTypeFile ); + } + SendMessage( GetDlgItem( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_SPEED_SLIDER ), TBM_SETRANGE, false, MAKELONG( MAX_TYPING_SPEED, MIN_TYPING_SPEED ) ); + SendMessage( GetDlgItem( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_SPEED_SLIDER ), TBM_SETPOS, true, g_DemoTypeSpeedSlider ); + CheckDlgButton( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_USER_DRIVEN, g_DemoTypeUserDriven ? BST_CHECKED: BST_UNCHECKED ); + + UnregisterAllHotkeys(GetParent( hDlg )); + PostMessage( hDlg, WM_USER, 0, 0 ); + return TRUE; + } + + case WM_USER+100: + BringWindowToTop( hDlg ); + SetFocus( hDlg ); + SetForegroundWindow( hDlg ); + return TRUE; + + case WM_DPICHANGED: + InitializeFonts( hDlg, &hFontBold ); + UpdateDrawTabHeaderFont(); + break; + + case WM_CTLCOLORSTATIC: + if( reinterpret_cast(lParam) == GetDlgItem( hDlg, IDC_TITLE ) || + reinterpret_cast(lParam) == GetDlgItem(hDlg, IDC_DRAWING) || + reinterpret_cast(lParam) == GetDlgItem(hDlg, IDC_ZOOM) || + reinterpret_cast(lParam) == GetDlgItem(hDlg, IDC_BREAK) || + reinterpret_cast(lParam) == GetDlgItem( hDlg, IDC_TYPE )) { + + HDC hdc = reinterpret_cast(wParam); + SetBkMode( hdc, TRANSPARENT ); + SelectObject( hdc, hFontBold ); + return PtrToLong(GetSysColorBrush( COLOR_BTNFACE )); + } + break; + + case WM_NOTIFY: + notify = reinterpret_cast(lParam); + if( notify->hdr.idFrom == IDC_LINK ) + { + switch( notify->hdr.code ) + { + case NM_CLICK: + case NM_RETURN: + ShellExecute( hDlg, _T("open"), notify->item.szUrl, NULL, NULL, SW_SHOWNORMAL ); + break; + } + } + else switch( notify->hdr.code ) + { + case TCN_SELCHANGE: + ShowWindow( g_OptionsTabs[curTabSel].hPage, SW_HIDE ); + curTabSel = TabCtrl_GetCurSel(hTabCtrl); + ShowWindow( g_OptionsTabs[curTabSel].hPage, SW_SHOW ); + break; + } + break; + + case WM_COMMAND: + switch ( LOWORD( wParam )) { + case IDOK: + { + if( !ConfigureAutostart( hDlg, IsDlgButtonChecked( hDlg, IDC_AUTOSTART) == BST_CHECKED )) { + + break; + } + g_ShowTrayIcon = IsDlgButtonChecked( hDlg, IDC_SHOW_TRAY_ICON ) == BST_CHECKED; + g_AnimateZoom = IsDlgButtonChecked( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_ANIMATE_ZOOM ) == BST_CHECKED; + g_DemoTypeUserDriven = IsDlgButtonChecked( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_USER_DRIVEN ) == BST_CHECKED; + + newToggleKey = static_cast(SendMessage( GetDlgItem( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_HOTKEY), HKM_GETHOTKEY, 0, 0 )); + newLiveZoomToggleKey = static_cast(SendMessage( GetDlgItem( g_OptionsTabs[LIVE_PAGE].hPage, IDC_LIVE_HOTKEY), HKM_GETHOTKEY, 0, 0 )); + newDrawToggleKey = static_cast(SendMessage( GetDlgItem( g_OptionsTabs[DRAW_PAGE].hPage, IDC_DRAW_HOTKEY), HKM_GETHOTKEY, 0, 0 )); + newBreakToggleKey = static_cast(SendMessage( GetDlgItem( g_OptionsTabs[BREAK_PAGE].hPage, IDC_BREAK_HOTKEY), HKM_GETHOTKEY, 0, 0 )); + newDemoTypeToggleKey = static_cast(SendMessage( GetDlgItem( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_HOTKEY ), HKM_GETHOTKEY, 0, 0 )); + newRecordToggleKey = static_cast(SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_HOTKEY), HKM_GETHOTKEY, 0, 0)); + newSnipToggleKey = static_cast(SendMessage( GetDlgItem( g_OptionsTabs[SNIP_PAGE].hPage, IDC_SNIP_HOTKEY), HKM_GETHOTKEY, 0, 0 )); + + newToggleMod = GetKeyMod( newToggleKey ); + newLiveZoomToggleMod = GetKeyMod( newLiveZoomToggleKey ); + newDrawToggleMod = GetKeyMod( newDrawToggleKey ); + newBreakToggleMod = GetKeyMod( newBreakToggleKey ); + newDemoTypeToggleMod = GetKeyMod( newDemoTypeToggleKey ); + newRecordToggleMod = GetKeyMod(newRecordToggleKey); + newSnipToggleMod = GetKeyMod( newSnipToggleKey ); + + g_SliderZoomLevel = static_cast(SendMessage( GetDlgItem(g_OptionsTabs[ZOOM_PAGE].hPage, IDC_ZOOM_SLIDER), TBM_GETPOS, 0, 0 )); + g_DemoTypeSpeedSlider = static_cast(SendMessage( GetDlgItem( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_SPEED_SLIDER ), TBM_GETPOS, 0, 0 )); + + g_ShowExpiredTime = IsDlgButtonChecked( g_OptionsTabs[BREAK_PAGE].hPage, IDC_CHECK_SHOW_EXPIRED ) == BST_CHECKED; + g_CaptureAudio = IsDlgButtonChecked(g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_AUDIO) == BST_CHECKED; + GetDlgItemText( g_OptionsTabs[BREAK_PAGE].hPage, IDC_TIMER, text, 3 ); + text[2] = 0; + newTimeout = _tstoi( text ); + + g_RecordFrameRate = g_FramerateOptions[SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FRAME_RATE), static_cast(CB_GETCURSEL), static_cast(0), static_cast(0))]; + g_RecordScaling = static_cast(SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), static_cast(CB_GETCURSEL), static_cast(0), static_cast(0)) * 10 + 10); + + // Get the selected microphone + int index = static_cast(SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), static_cast(CB_GETCURSEL), static_cast(0), static_cast(0) )); + _tcscpy( g_MicrophoneDeviceId, index == 0 ? L"" : microphones[static_cast(index) - 1].first.c_str() ); + + if( newToggleKey && !RegisterHotKey( GetParent( hDlg ), ZOOM_HOTKEY, newToggleMod, newToggleKey & 0xFF )) { + + MessageBox( hDlg, L"The specified zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.", + APPNAME, MB_ICONERROR ); + UnregisterAllHotkeys(GetParent( hDlg )); + break; + + } else if(newLiveZoomToggleKey && + (!RegisterHotKey( GetParent( hDlg ), LIVE_HOTKEY, newLiveZoomToggleMod, newLiveZoomToggleKey & 0xFF ) || + !RegisterHotKey(GetParent(hDlg), LIVE_DRAW_HOTKEY, (newLiveZoomToggleMod ^ MOD_SHIFT), newLiveZoomToggleKey & 0xFF))) { + + MessageBox( hDlg, L"The specified live-zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.", + APPNAME, MB_ICONERROR ); + UnregisterAllHotkeys(GetParent( hDlg )); + break; + + } else if( newDrawToggleKey && !RegisterHotKey( GetParent( hDlg ), DRAW_HOTKEY, newDrawToggleMod, newDrawToggleKey & 0xFF )) { + + MessageBox( hDlg, L"The specified draw w/out zoom hotkey is already in use.\nSelect a different draw w/out zoom hotkey.", + APPNAME, MB_ICONERROR ); + UnregisterAllHotkeys(GetParent( hDlg )); + break; + + } else if( newBreakToggleKey && !RegisterHotKey( GetParent( hDlg ), BREAK_HOTKEY, newBreakToggleMod, newBreakToggleKey & 0xFF )) { + + MessageBox( hDlg, L"The specified break timer hotkey is already in use.\nSelect a different break timer hotkey.", + APPNAME, MB_ICONERROR ); + UnregisterAllHotkeys(GetParent( hDlg )); + break; + + } else if( newDemoTypeToggleKey && + (!RegisterHotKey( GetParent( hDlg ), DEMOTYPE_HOTKEY, newDemoTypeToggleMod, newDemoTypeToggleKey & 0xFF ) || + !RegisterHotKey(GetParent(hDlg), DEMOTYPE_RESET_HOTKEY, (newDemoTypeToggleMod ^ MOD_SHIFT), newDemoTypeToggleKey & 0xFF))) { + + MessageBox( hDlg, L"The specified live-type hotkey is already in use.\nSelect a different live-type hotkey.", + APPNAME, MB_ICONERROR ); + UnregisterAllHotkeys( GetParent( hDlg ) ); + break; + + } + else if (newSnipToggleKey && + (!RegisterHotKey(GetParent(hDlg), SNIP_HOTKEY, newSnipToggleMod, newSnipToggleKey & 0xFF) || + !RegisterHotKey(GetParent(hDlg), SNIP_SAVE_HOTKEY, (newSnipToggleMod ^ MOD_SHIFT), newSnipToggleKey & 0xFF))) { + + MessageBox(hDlg, L"The specified snip hotkey is already in use.\nSelect a different snip hotkey.", + APPNAME, MB_ICONERROR); + UnregisterAllHotkeys(GetParent(hDlg)); + break; + + } + else if( newRecordToggleKey && + (!RegisterHotKey(GetParent(hDlg), RECORD_HOTKEY, newRecordToggleMod | MOD_NOREPEAT, newRecordToggleKey & 0xFF) || + !RegisterHotKey(GetParent(hDlg), RECORD_CROP_HOTKEY, (newRecordToggleMod ^ MOD_SHIFT) | MOD_NOREPEAT, newRecordToggleKey & 0xFF) || + !RegisterHotKey(GetParent(hDlg), RECORD_WINDOW_HOTKEY, (newRecordToggleMod ^ MOD_ALT) | MOD_NOREPEAT, newRecordToggleKey & 0xFF))) { + + MessageBox(hDlg, L"The specified record hotkey is already in use.\nSelect a different record hotkey.", + APPNAME, MB_ICONERROR); + UnregisterAllHotkeys(GetParent(hDlg)); + break; + + } else { + + g_BreakTimeout = newTimeout; + g_ToggleKey = newToggleKey; + g_LiveZoomToggleKey = newLiveZoomToggleKey; + g_ToggleMod = newToggleMod; + g_DrawToggleKey = newDrawToggleKey; + g_DrawToggleMod = newDrawToggleMod; + g_BreakToggleKey = newBreakToggleKey; + g_BreakToggleMod = newBreakToggleMod; + g_DemoTypeToggleKey = newDemoTypeToggleKey; + g_DemoTypeToggleMod = newDemoTypeToggleMod; + g_RecordToggleKey = newRecordToggleKey; + g_RecordToggleMod = newRecordToggleMod; + g_SnipToggleKey = newSnipToggleKey; + g_SnipToggleMod = newSnipToggleMod; + reg.WriteRegSettings( RegSettings ); + EnableDisableTrayIcon( GetParent( hDlg ), g_ShowTrayIcon ); + + hWndOptions = NULL; + EndDialog( hDlg, 0 ); + return TRUE; + } + break; + } + + case IDCANCEL: + RegisterAllHotkeys(GetParent(hDlg)); + hWndOptions = NULL; + EndDialog( hDlg, 0 ); + return TRUE; + } + break; + + case WM_CLOSE: + hWndOptions = NULL; + RegisterAllHotkeys(GetParent(hDlg)); + EndDialog( hDlg, 0 ); + return TRUE; + + default: + break; + } + return FALSE; +} + +//---------------------------------------------------------------------------- +// +// DeleteDrawUndoList +// +//---------------------------------------------------------------------------- +void DeleteDrawUndoList( P_DRAW_UNDO *DrawUndoList ) +{ + P_DRAW_UNDO nextUndo; + + nextUndo = *DrawUndoList; + while( nextUndo ) { + + *DrawUndoList = nextUndo->Next; + DeleteObject( nextUndo->hBitmap ); + DeleteDC( nextUndo->hDc ); + free( nextUndo ); + nextUndo = *DrawUndoList; + } + *DrawUndoList = NULL; +} + +//---------------------------------------------------------------------------- +// +// PopDrawUndo +// +//---------------------------------------------------------------------------- +BOOLEAN PopDrawUndo( HDC hDc, P_DRAW_UNDO *DrawUndoList, + int width, int height ) +{ + P_DRAW_UNDO nextUndo; + + nextUndo = *DrawUndoList; + if( nextUndo ) { + + BitBlt( hDc, 0, 0, width, height, + nextUndo->hDc, 0, 0, SRCCOPY|CAPTUREBLT ); + *DrawUndoList = nextUndo->Next; + DeleteObject( nextUndo->hBitmap ); + DeleteDC( nextUndo->hDc ); + free( nextUndo ); + return TRUE; + + } else { + + Beep( 700, 200 ); + return FALSE; + } +} + + +//---------------------------------------------------------------------------- +// +// DeleteOldestUndo +// +//---------------------------------------------------------------------------- +void DeleteOldestUndo( P_DRAW_UNDO *DrawUndoList ) +{ + P_DRAW_UNDO nextUndo, freeUndo = NULL, prevUndo = NULL; + + nextUndo = *DrawUndoList; + freeUndo = nextUndo; + do { + + prevUndo = freeUndo; + freeUndo = nextUndo; + nextUndo = nextUndo->Next; + + } while( nextUndo ); + + if( freeUndo ) { + + DeleteObject( freeUndo->hBitmap ); + DeleteDC( freeUndo->hDc ); + free( freeUndo ); + if( prevUndo != *DrawUndoList ) prevUndo->Next = NULL; + else *DrawUndoList = NULL; + } +} + +//---------------------------------------------------------------------------- +// +// GetOldestUndo +// +//---------------------------------------------------------------------------- +P_DRAW_UNDO GetOldestUndo(P_DRAW_UNDO DrawUndoList) +{ + P_DRAW_UNDO nextUndo, oldestUndo = NULL; + + nextUndo = DrawUndoList; + oldestUndo = nextUndo; + do { + + oldestUndo = nextUndo; + nextUndo = nextUndo->Next; + + } while( nextUndo ); + return oldestUndo; +} + + +//---------------------------------------------------------------------------- +// +// PushDrawUndo +// +//---------------------------------------------------------------------------- +void PushDrawUndo( HDC hDc, P_DRAW_UNDO *DrawUndoList, int width, int height ) +{ + P_DRAW_UNDO nextUndo, newUndo; + int i = 0; + HBITMAP hUndoBitmap; + + OutputDebug(L"PushDrawUndo\n"); + + // Don't store more than 8 undo's (XP gets really upset when we + // exhaust heap with them) + nextUndo = *DrawUndoList; + do { + + i++; + if( i == MAX_UNDO_HISTORY ) { + + DeleteOldestUndo( DrawUndoList ); + break; + } + if( nextUndo ) nextUndo = nextUndo->Next; + + } while( nextUndo ); + + hUndoBitmap = CreateCompatibleBitmap( hDc, width, height ); + if( !hUndoBitmap && *DrawUndoList ) { + + // delete the oldest and try again + DeleteOldestUndo( DrawUndoList ); + hUndoBitmap = CreateCompatibleBitmap( hDc, width, height ); + } + if( hUndoBitmap ) { + + newUndo = static_cast(malloc( sizeof( DRAW_UNDO ))); + if (newUndo != NULL) + { + newUndo->hDc = CreateCompatibleDC(hDc); + newUndo->hBitmap = hUndoBitmap; + SelectObject(newUndo->hDc, newUndo->hBitmap); + BitBlt(newUndo->hDc, 0, 0, width, height, hDc, 0, 0, SRCCOPY | CAPTUREBLT); + newUndo->Next = *DrawUndoList; + *DrawUndoList = newUndo; + } + } +} + +//---------------------------------------------------------------------------- +// +// DeleteTypedText +// +//---------------------------------------------------------------------------- +void DeleteTypedText( P_TYPED_KEY *TypedKeyList ) +{ + P_TYPED_KEY nextKey; + + while( *TypedKeyList ) { + + nextKey = (*TypedKeyList)->Next; + free( *TypedKeyList ); + *TypedKeyList = nextKey; + } +} + +//---------------------------------------------------------------------------- +// +// BlankScreenArea +// +//---------------------------------------------------------------------------- +void BlankScreenArea( HDC hDc, PRECT Rc, int BlankMode ) +{ + if( BlankMode == 'K' ) { + + HBRUSH hBrush = CreateSolidBrush( RGB( 0, 0, 0 )); + FillRect( hDc, Rc, hBrush ); + DeleteObject( static_cast(hBrush) ); + + } else { + + FillRect( hDc, Rc, GetSysColorBrush( COLOR_WINDOW )); + } +} + +//---------------------------------------------------------------------------- +// +// ClearTypingCursor +// +//---------------------------------------------------------------------------- +void ClearTypingCursor( HDC hdcScreenCompat, HDC hdcScreenCursorCompat, RECT rc, + int BlankMode ) +{ + if( false ) { // BlankMode ) { + + BlankScreenArea( hdcScreenCompat, &rc, BlankMode ); + + } else { + + BitBlt(hdcScreenCompat, rc.left, rc.top, rc.right - rc.left, + rc.bottom - rc.top, hdcScreenCursorCompat,0, 0, SRCCOPY|CAPTUREBLT ); + } +} + +//---------------------------------------------------------------------------- +// +// DrawTypingCursor +// +//---------------------------------------------------------------------------- +void DrawTypingCursor( HWND hWnd, POINT *textPt, HDC hdcScreenCompat, + HDC hdcScreenCursorCompat, RECT *rc, bool centerUnderSystemCursor = false ) +{ + // Draw the typing cursor + rc->left = textPt->x; + rc->top = textPt->y; + TCHAR vKey = '|'; + DrawText( hdcScreenCompat, static_cast(&vKey), 1, rc, DT_CALCRECT ); + + // LiveDraw uses a layered window which means mouse messages pass through + // to lower windows unless the system cursor is above a painted area. + // Centering the typing cursor directly under the system cursor allows + // us to capture the mouse wheel input required to change font size. + if( centerUnderSystemCursor ) + { + const LONG halfWidth = static_cast( (rc->right - rc->left) / 2 ); + const LONG halfHeight = static_cast( (rc->bottom - rc->top) / 2 ); + + rc->left -= halfWidth; + rc->right -= halfWidth; + rc->top -= halfHeight; + rc->bottom -= halfHeight; + + textPt->x = rc->left; + textPt->y = rc->top; + } + + BitBlt(hdcScreenCursorCompat, 0, 0, rc->right -rc->left, rc->bottom - rc->top, + hdcScreenCompat, rc->left, rc->top, SRCCOPY|CAPTUREBLT ); + + DrawText( hdcScreenCompat, static_cast(&vKey), 1, rc, DT_LEFT ); + InvalidateRect( hWnd, NULL, TRUE ); +} + +//---------------------------------------------------------------------------- +// +// BoundMouse +// +//---------------------------------------------------------------------------- +RECT BoundMouse( float zoomLevel, MONITORINFO *monInfo, int width, int height, + POINT *cursorPos ) +{ + RECT rc; + int x, y; + + GetZoomedTopLeftCoordinates( zoomLevel, cursorPos, &x, width, &y, height ); + rc.left = monInfo->rcMonitor.left + x; + rc.right = rc.left + static_cast(width/zoomLevel); + rc.top = monInfo->rcMonitor.top + y; + rc.bottom = rc.top + static_cast(height/zoomLevel); + + OutputDebug( L"x: %d y: %d width: %d height: %d zoomLevel: %g\n", + cursorPos->x, cursorPos->y, width, height, zoomLevel); + OutputDebug( L"left: %d top: %d right: %d bottom: %d\n", + rc.left, rc.top, rc.right, rc.bottom); + OutputDebug( L"mon.left: %d mon.top: %d mon.right: %d mon.bottom: %d\n", + monInfo->rcMonitor.left, monInfo->rcMonitor.top, monInfo->rcMonitor.right, monInfo->rcMonitor.bottom); + + ClipCursor( &rc ); + return rc; +} + +//---------------------------------------------------------------------------- +// +// DrawArrow +// +//---------------------------------------------------------------------------- +void DrawArrow( HDC hdc, int x1, int y1, int x2, int y2, double length, double width, + bool UseGdiplus ) +{ + // get normalized dx/dy + double dx = static_cast(x2) - x1; + double dy = static_cast(y2) - y1; + double bodyLen = sqrt( dx*dx + dy*dy ); + if ( bodyLen ) { + dx /= bodyLen; + dy /= bodyLen; + } else { + dx = 1; + dy = 0; + } + + // get midpoint of base + int xMid = x2 - static_cast(length*dx+0.5); + int yMid = y2 - static_cast(length*dy+0.5); + + // get left wing + int xLeft = xMid - static_cast(dy*width+0.5); + int yLeft = yMid + static_cast(dx*width+0.5); + + // get right wing + int xRight = xMid + static_cast(dy*width+0.5); + int yRight = yMid - static_cast(dx*width+0.5); + + // Bring in midpoint to make a nicer arrow + xMid = x2 - static_cast(length/2*dx+0.5); + yMid = y2 - static_cast(length/2*dy+0.5); + if (UseGdiplus) { + + Gdiplus::Graphics dstGraphics(hdc); + + if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 ) + { + dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + } + Gdiplus::Color color = ColorFromColorRef(g_PenColor); + Gdiplus::Pen pen(color, static_cast(g_PenWidth)); + pen.SetLineCap(Gdiplus::LineCapRound, Gdiplus::LineCapRound, Gdiplus::DashCapRound); +#if 0 + Gdiplus::PointF pts[] = { + {(Gdiplus::REAL)x1, (Gdiplus::REAL)y1}, + {(Gdiplus::REAL)xMid, (Gdiplus::REAL)yMid}, + {(Gdiplus::REAL)xLeft, (Gdiplus::REAL)yLeft}, + {(Gdiplus::REAL)x2, (Gdiplus::REAL)y2}, + {(Gdiplus::REAL)xRight, (Gdiplus::REAL)yRight}, + {(Gdiplus::REAL)xMid, (Gdiplus::REAL)yMid} + }; + dstGraphics.DrawPolygon(&pen, pts, _countof(pts)); +#else + Gdiplus::GraphicsPath path; + path.StartFigure(); + path.AddLine(static_cast(x1), static_cast(y1), static_cast(x2), static_cast(y2)); + path.AddLine(static_cast(x2), static_cast(y2), static_cast(xMid), static_cast(yMid)); + path.AddLine(static_cast(xMid), static_cast(yMid), static_cast(xLeft), static_cast(yLeft)); + path.AddLine(static_cast(xLeft), static_cast(yLeft), static_cast(x2), static_cast(y2)); + path.AddLine(static_cast(x2), static_cast(y2), static_cast(xRight), static_cast(yRight)); + path.AddLine(static_cast(xRight), static_cast(yRight), static_cast(xMid), static_cast(yMid)); + pen.SetLineJoin(Gdiplus::LineJoinRound); + dstGraphics.DrawPath(&pen, &path); +#endif + } + else { + POINT pts[] = { + x1, y1, + xMid, yMid, + xLeft, yLeft, + x2, y2, + xRight, yRight, + xMid, yMid + }; + + // draw arrow head filled with current color + HBRUSH hBrush = CreateSolidBrush(g_PenColor); + HBRUSH hOldBrush = SelectBrush(hdc, hBrush); + Polygon(hdc, pts, sizeof(pts) / sizeof(pts[0])); + + DeleteObject(hBrush); + SelectObject(hdc, hOldBrush); + } +} + + + +//---------------------------------------------------------------------------- +// +// DrawShape +// +//---------------------------------------------------------------------------- +VOID DrawShape( DWORD Shape, HDC hDc, RECT *Rect, bool UseGdiPlus = false ) +{ + bool isBlur = false; + + Gdiplus::Graphics dstGraphics(hDc); + + if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 ) + { + dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + } + Gdiplus::Color color = ColorFromColorRef(g_PenColor); + Gdiplus::Pen pen(color, static_cast(g_PenWidth)); + pen.SetLineCap(Gdiplus::LineCapRound, Gdiplus::LineCapRound, Gdiplus::DashCapRound); + + // Check for highlighting or blur + Gdiplus::Brush *pBrush = NULL; + if (PEN_COLOR_HIGHLIGHT(g_PenColor)) { + // Use half the alpha for higher contrast + DWORD newColor = g_PenColor & 0xFFFFFF | ((g_AlphaBlend / 2) << 24); + pBrush = new Gdiplus::SolidBrush(ColorFromColorRef(newColor)); + if(UseGdiPlus && Shape != DRAW_LINE && Shape != DRAW_ARROW) + InflateRect(Rect, g_PenWidth/2, g_PenWidth/2); + } + else if ((g_PenColor & 0xFFFFFF) == COLOR_BLUR) { + if (UseGdiPlus && Shape != DRAW_LINE && Shape != DRAW_ARROW) + InflateRect(Rect, g_PenWidth / 2, g_PenWidth / 2); + isBlur = true; + } + + switch (Shape) { + case DRAW_RECTANGLE: + if (UseGdiPlus) + if(pBrush) + DrawHighlightedShape(DRAW_RECTANGLE, hDc, pBrush, NULL, + static_cast(Rect->left - 1), static_cast(Rect->top - 1), + static_cast(Rect->right), static_cast(Rect->bottom)); + else if (isBlur) + DrawBlurredShape( DRAW_RECTANGLE, &pen, hDc, &dstGraphics, + static_cast(Rect->left - 1), static_cast(Rect->top - 1), + static_cast(Rect->right), static_cast(Rect->bottom) ); + else + dstGraphics.DrawRectangle(&pen, + Gdiplus::Rect::Rect(Rect->left - 1, Rect->top - 1, + Rect->right - Rect->left, Rect->bottom - Rect->top)); + else + Rectangle(hDc, Rect->left, Rect->top, + Rect->right, Rect->bottom); + break; + case DRAW_ELLIPSE: + if (UseGdiPlus) + if (pBrush) + DrawHighlightedShape(DRAW_ELLIPSE, hDc, pBrush, NULL, + static_cast(Rect->left - 1), static_cast(Rect->top - 1), + static_cast(Rect->right), static_cast(Rect->bottom)); + else if (isBlur) + DrawBlurredShape( DRAW_ELLIPSE, &pen, hDc, &dstGraphics, + static_cast(Rect->left - 1), static_cast(Rect->top - 1), + static_cast(Rect->right), static_cast(Rect->bottom)); + else + dstGraphics.DrawEllipse(&pen, + Gdiplus::Rect::Rect(Rect->left - 1, Rect->top - 1, + Rect->right - Rect->left, Rect->bottom - Rect->top)); + else + Ellipse(hDc, Rect->left, Rect->top, + Rect->right, Rect->bottom); + break; + case DRAW_LINE: + if (UseGdiPlus) + if (pBrush) + DrawHighlightedShape(DRAW_LINE, hDc, NULL, &pen, + static_cast(Rect->left), static_cast(Rect->top), + static_cast(Rect->right), static_cast(Rect->bottom)); + else if (isBlur) + DrawBlurredShape(DRAW_LINE, &pen, hDc, &dstGraphics, + static_cast(Rect->left), static_cast(Rect->top), + static_cast(Rect->right), static_cast(Rect->bottom)); + else + dstGraphics.DrawLine(&pen, + static_cast(Rect->left - 1), static_cast(Rect->top - 1), + static_cast(Rect->right), static_cast(Rect->bottom)); + else { + MoveToEx(hDc, Rect->left, Rect->top, NULL); + LineTo(hDc, Rect->right + 1, Rect->bottom + 1); + } + break; + case DRAW_ARROW: + DrawArrow(hDc, Rect->right + 1, Rect->bottom + 1, + Rect->left, Rect->top, + static_cast(g_PenWidth) * 2.5, static_cast(g_PenWidth) * 1.5, UseGdiPlus); + break; + } + if( pBrush ) delete pBrush; +} + +//---------------------------------------------------------------------------- +// +// SendPenMessage +// +// Inserts the pen message marker. +// +//---------------------------------------------------------------------------- +VOID SendPenMessage(HWND hWnd, UINT Message, LPARAM lParam) +{ + WPARAM wParam = 0; + // + // Get key states + // + if(GetKeyState(VK_LCONTROL) < 0 ) { + + wParam |= MK_CONTROL; + } + if( GetKeyState( VK_LSHIFT) < 0 || GetKeyState( VK_RSHIFT) < 0 ) { + + wParam |= MK_SHIFT; + } + SetMessageExtraInfo(static_cast(MI_WP_SIGNATURE)); + SendMessage(hWnd, Message, wParam, lParam); +} + + +//---------------------------------------------------------------------------- +// +// ScalePenPosition +// +// Maps pen input to mouse input coordinates based on zoom level. Returns +// 0 if pen is active but we didn't send this message to ourselves (pen +// signature will be missing). +// +//---------------------------------------------------------------------------- +LPARAM ScalePenPosition( float zoomLevel, MONITORINFO *monInfo, RECT boundRc, + UINT message, LPARAM lParam ) +{ + RECT rc; + WORD x, y; + LPARAM extraInfo; + + extraInfo = GetMessageExtraInfo(); + if( g_PenDown ) { + + // ignore messages we didn't tag as pen + if (extraInfo == MI_WP_SIGNATURE) { + + OutputDebug( L"Tablet Pen message\n"); + + // tablet input: don't bound the cursor + ClipCursor(NULL); + + x = LOWORD(lParam); + y = HIWORD(lParam); + + x = static_cast((x - static_cast(monInfo->rcMonitor.left))/ zoomLevel) + static_cast(boundRc.left - monInfo->rcMonitor.left); + y = static_cast((y - static_cast(monInfo->rcMonitor.top)) / zoomLevel) + static_cast(boundRc.top - monInfo->rcMonitor.top); + + lParam = MAKELPARAM(x, y); + } + else { + + OutputDebug(L"Ignore pen message we didn't send\n"); + lParam = 0; + } + + } else { + + if( !GetClipCursor( &rc )) { + + ClipCursor( &boundRc ); + } + OutputDebug( L"Mouse message\n"); + } + return lParam; +} + + +//---------------------------------------------------------------------------- +// +// DrawHighlightedCursor +// +//---------------------------------------------------------------------------- +BOOLEAN DrawHighlightedCursor( float ZoomLevel, int Width, int Height ) +{ + DWORD zoomWidth = static_cast (static_cast(Width)/ZoomLevel); + DWORD zoomHeight = static_cast (static_cast(Height)/ZoomLevel); + if( g_PenWidth < 5 && zoomWidth > g_PenWidth * 100 && zoomHeight > g_PenWidth * 100 ) { + + return TRUE; + + } else { + + return FALSE; + } +} + +//---------------------------------------------------------------------------- +// +// InvalidateCursorMoveArea +// +//---------------------------------------------------------------------------- +void InvalidateCursorMoveArea( HWND hWnd, float zoomLevel, int width, int height, + POINT currentPt, POINT prevPt, POINT cursorPos ) +{ + int x, y; + RECT rc; + int invWidth = g_PenWidth; + + if( DrawHighlightedCursor( zoomLevel, width, height ) ) { + + invWidth = g_PenWidth * 3 + 1; + } + GetZoomedTopLeftCoordinates( zoomLevel, &cursorPos, &x, width, &y, height ); + rc.left = static_cast(max( 0, (int) ((min( prevPt.x, currentPt.x)-invWidth - x) * zoomLevel))); + rc.right = static_cast((max( prevPt.x, currentPt.x)+invWidth - x) * zoomLevel); + rc.top = static_cast(max( 0, (int) ((min( prevPt.y, currentPt.y)-invWidth - y) * zoomLevel))); + rc.bottom = static_cast((max( prevPt.y, currentPt.y)+invWidth -y) * zoomLevel); + InvalidateRect( hWnd, &rc, FALSE ); + + OutputDebug( L"INVALIDATE: (%d, %d) - (%d, %d)\n", rc.left, rc.top, rc.right, rc.bottom); +} + + +//---------------------------------------------------------------------------- +// +// SavCursorArea +// +//---------------------------------------------------------------------------- +void SaveCursorArea( HDC hDcTarget, HDC hDcSource, POINT pt ) +{ + OutputDebug( L"SaveCursorArea\n"); + int penWidth = g_PenWidth + 2; + BitBlt( hDcTarget, 0, 0, penWidth +CURSOR_ARM_LENGTH*2, penWidth +CURSOR_ARM_LENGTH*2, + hDcSource, static_cast (pt.x- penWidth /2)-CURSOR_ARM_LENGTH, + static_cast(pt.y- penWidth /2)-CURSOR_ARM_LENGTH, SRCCOPY|CAPTUREBLT ); +} + +//---------------------------------------------------------------------------- +// +// RestoreCursorArea +// +//---------------------------------------------------------------------------- +void RestoreCursorArea( HDC hDcTarget, HDC hDcSource, POINT pt ) +{ + OutputDebug( L"RestoreCursorArea\n"); + int penWidth = g_PenWidth + 2; + BitBlt( hDcTarget, static_cast(pt.x- penWidth /2)-CURSOR_ARM_LENGTH, + static_cast(pt.y- penWidth /2)-CURSOR_ARM_LENGTH, penWidth +CURSOR_ARM_LENGTH*2, + penWidth + CURSOR_ARM_LENGTH*2, hDcSource, 0, 0, SRCCOPY|CAPTUREBLT ); +} + + +//---------------------------------------------------------------------------- +// +// DrawCursor +// +//---------------------------------------------------------------------------- +void DrawCursor( HDC hDcTarget, POINT pt, float ZoomLevel, int Width, int Height ) +{ + RECT rc; + + if( g_DrawPointer ) { + + Gdiplus::Graphics dstGraphics(hDcTarget); + if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 ) + { + dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + } + Gdiplus::Color color = ColorFromColorRef(g_PenColor); + Gdiplus::Pen pen(color, static_cast(g_PenWidth)); + + rc.left = pt.x - CURSOR_ARM_LENGTH; + rc.right = pt.x + CURSOR_ARM_LENGTH; + rc.top = pt.y - CURSOR_ARM_LENGTH; + rc.bottom = pt.y + CURSOR_ARM_LENGTH; + + Gdiplus::GraphicsPath path; + path.StartFigure(); + path.AddLine(static_cast(rc.left) - 1, static_cast(rc.top) - 1, static_cast(rc.right), static_cast(rc.bottom)); + path.AddLine(static_cast(rc.left) - 2, static_cast(rc.top) - 1, rc.left + (rc.right - rc.left) / 2, rc.top - 1); + path.AddLine(static_cast(rc.left) - 1, static_cast(rc.top) - 2, rc.left - 1, rc.top + (rc.bottom - rc.top) / 2); + path.AddLine(static_cast(rc.left) - 1, static_cast(rc.top) - 2, rc.left - 1, rc.top + (rc.bottom - rc.top) / 2); + path.AddLine(static_cast(rc.left) + (rc.right - rc.left) / 2, rc.top - 1, rc.left - 1, rc.top + (rc.bottom - rc.top) / 2); + pen.SetLineJoin(Gdiplus::LineJoinRound); + dstGraphics.DrawPath(&pen, &path); + OutputDebug(L"DrawPointer: %d %d %d %d\n", rc.left, rc.top, rc.right, rc.bottom); + + } else if( DrawHighlightedCursor( ZoomLevel, Width, Height )) { + + OutputDebug(L"DrawHighlightedCursor: %d %d %d %d\n", pt.x, pt.y, g_PenWidth, g_PenWidth); + Gdiplus::Graphics dstGraphics(hDcTarget); + if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 ) + { + dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + } + Gdiplus::Color color = ColorFromColorRef(g_PenColor); + Gdiplus::Pen pen(color, static_cast(g_PenWidth)); + Gdiplus::GraphicsPath path; + path.StartFigure(); + pen.SetLineJoin(Gdiplus::LineJoinRound); + path.AddLine(pt.x - CURSOR_ARM_LENGTH, pt.y, pt.x + CURSOR_ARM_LENGTH, pt.y); + path.CloseFigure(); + path.StartFigure(); + pen.SetLineJoin(Gdiplus::LineJoinRound); + path.AddLine(pt.x, pt.y - CURSOR_ARM_LENGTH, pt.x, pt.y + CURSOR_ARM_LENGTH); + path.CloseFigure(); + dstGraphics.DrawPath(&pen, &path); + + } else { + + Gdiplus::Graphics dstGraphics(hDcTarget); + { + if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 ) + { + dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + } + Gdiplus::Color color = ColorFromColorRef(g_PenColor); + + Gdiplus::SolidBrush solidBrush(color); + + dstGraphics.FillEllipse(&solidBrush, static_cast(pt.x-g_PenWidth/2), static_cast(pt.y-g_PenWidth/2), + static_cast(g_PenWidth), static_cast(g_PenWidth)); + } + } +} + +//---------------------------------------------------------------------------- +// +// ResizePen +// +//---------------------------------------------------------------------------- +void ResizePen( HWND hWnd, HDC hdcScreenCompat, HDC hdcScreenCursorCompat, POINT prevPt, + BOOLEAN g_Tracing, BOOLEAN *g_Drawing, float g_LiveZoomLevel, + BOOLEAN isUser, int newWidth ) +{ + if( !g_Tracing ) { + + RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt ); + } + + OutputDebug( L"RESIZE_PEN-PRE: penWidth: %d ", g_PenWidth ); + int prevWidth = g_PenWidth; + if( g_ZoomOnLiveZoom ) + { + if( isUser ) + { + // Amplify user delta proportional to LiveZoomLevel + newWidth = g_PenWidth + static_cast ((newWidth - static_cast(g_PenWidth))*g_LiveZoomLevel); + } + + g_PenWidth = min( max( newWidth, MIN_PEN_WIDTH ), + min( static_cast(MAX_PEN_WIDTH * g_LiveZoomLevel), MAX_LIVE_PEN_WIDTH ) ); + g_RootPenWidth = static_cast(g_PenWidth / g_LiveZoomLevel); + } + else + { + g_PenWidth = min( max( newWidth, MIN_PEN_WIDTH ), MAX_PEN_WIDTH ); + g_RootPenWidth = g_PenWidth; + } + + if(prevWidth == static_cast(g_PenWidth) ) { + // No change + return; + } + + OutputDebug( L"newWidth: %d\nRESIZE_PEN-POST: penWidth: %d\n", newWidth, g_PenWidth ); + reg.WriteRegSettings( RegSettings ); + SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt ); + *g_Drawing = FALSE; + EnableDisableStickyKeys( TRUE ); + SendMessage( hWnd, WM_LBUTTONDOWN, -1, MAKELPARAM(prevPt.x, prevPt.y) ); +} + +//---------------------------------------------------------------------------- +// +// IsPenInverted +// +//---------------------------------------------------------------------------- +bool IsPenInverted( WPARAM wParam ) +{ + POINTER_INPUT_TYPE pointerType; + POINTER_PEN_INFO penInfo; + return + pGetPointerType( GET_POINTERID_WPARAM( wParam ), &pointerType ) && ( pointerType == PT_PEN ) && + pGetPointerPenInfo( GET_POINTERID_WPARAM( wParam ), &penInfo ) && ( penInfo.penFlags & PEN_FLAG_INVERTED ); +} + + +//---------------------------------------------------------------------------- +// +// CaptureScreenshotAsync +// +// Captures the specified screen using the capture APIs +// +//---------------------------------------------------------------------------- +std::future> CaptureScreenshotAsync(winrt::IDirect3DDevice const& device, winrt::GraphicsCaptureItem const& item, winrt::DirectXPixelFormat const& pixelFormat) +{ + auto d3dDevice = GetDXGIInterfaceFromObject(device); + winrt::com_ptr d3dContext; + d3dDevice->GetImmediateContext(d3dContext.put()); + + // Creating our frame pool with CreateFreeThreaded means that we + // will be called back from the frame pool's internal worker thread + // instead of the thread we are currently on. It also disables the + // DispatcherQueue requirement. + auto framePool = winrt::Direct3D11CaptureFramePool::CreateFreeThreaded( + device, + pixelFormat, + 1, + item.Size()); + auto session = framePool.CreateCaptureSession(item); + + wil::shared_event captureEvent(wil::EventOptions::ManualReset); + winrt::Direct3D11CaptureFrame frame{ nullptr }; + framePool.FrameArrived([&frame, captureEvent](auto& framePool, auto&) + { + frame = framePool.TryGetNextFrame(); + + // Complete the operation + captureEvent.SetEvent(); + }); + + session.IsCursorCaptureEnabled( false ); + session.StartCapture(); + co_await winrt::resume_on_signal(captureEvent.get()); + + // End the capture + session.Close(); + framePool.Close(); + + auto texture = GetDXGIInterfaceFromObject(frame.Surface()); + auto result = util::CopyD3DTexture(d3dDevice, texture, true); + + co_return result; +} + +//---------------------------------------------------------------------------- +// +// CaptureScreenshot +// +// Captures the specified screen using the capture APIs +// +//---------------------------------------------------------------------------- +winrt::com_ptrCaptureScreenshot(winrt::DirectXPixelFormat const& pixelFormat) +{ + auto d3dDevice = util::CreateD3D11Device(); + auto dxgiDevice = d3dDevice.as(); + auto device = CreateDirect3DDevice(dxgiDevice.get()); + + // Get the active MONITOR capture device + HMONITOR hMon = NULL; + POINT cursorPos = { 0, 0 }; + if (pMonitorFromPoint) { + + GetCursorPos(&cursorPos); + hMon = pMonitorFromPoint(cursorPos, MONITOR_DEFAULTTONEAREST); + } + + auto item = util::CreateCaptureItemForMonitor(hMon); + + auto capture = CaptureScreenshotAsync(device, item, pixelFormat); + capture.wait(); + + return capture.get(); +} + + +//---------------------------------------------------------------------------- +// +// CopyD3DTexture +// +//---------------------------------------------------------------------------- +inline auto CopyD3DTexture(winrt::com_ptr const& device, + winrt::com_ptr const& texture, bool asStagingTexture) +{ + winrt::com_ptr context; + device->GetImmediateContext(context.put()); + + D3D11_TEXTURE2D_DESC desc = {}; + texture->GetDesc(&desc); + // Clear flags that we don't need + desc.Usage = asStagingTexture ? D3D11_USAGE_STAGING : D3D11_USAGE_DEFAULT; + desc.BindFlags = asStagingTexture ? 0 : D3D11_BIND_SHADER_RESOURCE; + desc.CPUAccessFlags = asStagingTexture ? D3D11_CPU_ACCESS_READ : 0; + desc.MiscFlags = 0; + + // Create and fill the texture copy + winrt::com_ptr textureCopy; + winrt::check_hresult(device->CreateTexture2D(&desc, nullptr, textureCopy.put())); + context->CopyResource(textureCopy.get(), texture.get()); + + return textureCopy; +} + + +//---------------------------------------------------------------------------- +// +// PrepareStagingTexture +// +//---------------------------------------------------------------------------- +inline auto PrepareStagingTexture(winrt::com_ptr const& device, + winrt::com_ptr const& texture) +{ + // If our texture is already set up for staging, then use it. + // Otherwise, create a staging texture. + D3D11_TEXTURE2D_DESC desc = {}; + texture->GetDesc(&desc); + if (desc.Usage == D3D11_USAGE_STAGING && desc.CPUAccessFlags & D3D11_CPU_ACCESS_READ) + { + return texture; + } + + return CopyD3DTexture(device, texture, true); +} + +//---------------------------------------------------------------------------- +// +// GetBytesPerPixel +// +//---------------------------------------------------------------------------- +inline size_t +GetBytesPerPixel(DXGI_FORMAT pixelFormat) +{ + switch (pixelFormat) + { + case DXGI_FORMAT_R32G32B32A32_TYPELESS: + case DXGI_FORMAT_R32G32B32A32_FLOAT: + case DXGI_FORMAT_R32G32B32A32_UINT: + case DXGI_FORMAT_R32G32B32A32_SINT: + return 16; + case DXGI_FORMAT_R32G32B32_TYPELESS: + case DXGI_FORMAT_R32G32B32_FLOAT: + case DXGI_FORMAT_R32G32B32_UINT: + case DXGI_FORMAT_R32G32B32_SINT: + return 12; + case DXGI_FORMAT_R16G16B16A16_TYPELESS: + case DXGI_FORMAT_R16G16B16A16_FLOAT: + case DXGI_FORMAT_R16G16B16A16_UNORM: + case DXGI_FORMAT_R16G16B16A16_UINT: + case DXGI_FORMAT_R16G16B16A16_SNORM: + case DXGI_FORMAT_R16G16B16A16_SINT: + case DXGI_FORMAT_R32G32_TYPELESS: + case DXGI_FORMAT_R32G32_FLOAT: + case DXGI_FORMAT_R32G32_UINT: + case DXGI_FORMAT_R32G32_SINT: + case DXGI_FORMAT_R32G8X24_TYPELESS: + return 8; + case DXGI_FORMAT_D32_FLOAT_S8X24_UINT: + case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS: + case DXGI_FORMAT_X32_TYPELESS_G8X24_UINT: + case DXGI_FORMAT_R10G10B10A2_TYPELESS: + case DXGI_FORMAT_R10G10B10A2_UNORM: + case DXGI_FORMAT_R10G10B10A2_UINT: + case DXGI_FORMAT_R11G11B10_FLOAT: + case DXGI_FORMAT_R8G8B8A8_TYPELESS: + case DXGI_FORMAT_R8G8B8A8_UNORM: + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + case DXGI_FORMAT_R8G8B8A8_UINT: + case DXGI_FORMAT_R8G8B8A8_SNORM: + case DXGI_FORMAT_R8G8B8A8_SINT: + case DXGI_FORMAT_R16G16_TYPELESS: + case DXGI_FORMAT_R16G16_FLOAT: + case DXGI_FORMAT_UNKNOWN: + case DXGI_FORMAT_R16G16_UINT: + case DXGI_FORMAT_R16G16_SNORM: + case DXGI_FORMAT_R16G16_SINT: + case DXGI_FORMAT_R32_TYPELESS: + case DXGI_FORMAT_D32_FLOAT: + case DXGI_FORMAT_R32_FLOAT: + case DXGI_FORMAT_R32_UINT: + case DXGI_FORMAT_R32_SINT: + case DXGI_FORMAT_R24G8_TYPELESS: + case DXGI_FORMAT_D24_UNORM_S8_UINT: + case DXGI_FORMAT_R24_UNORM_X8_TYPELESS: + case DXGI_FORMAT_X24_TYPELESS_G8_UINT: + case DXGI_FORMAT_R8G8_B8G8_UNORM: + case DXGI_FORMAT_G8R8_G8B8_UNORM: + case DXGI_FORMAT_B8G8R8A8_UNORM: + case DXGI_FORMAT_B8G8R8X8_UNORM: + case DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM: + case DXGI_FORMAT_B8G8R8A8_TYPELESS: + case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: + case DXGI_FORMAT_B8G8R8X8_TYPELESS: + case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB: + return 4; + case DXGI_FORMAT_R8G8_TYPELESS: + case DXGI_FORMAT_R8G8_UNORM: + case DXGI_FORMAT_R8G8_UINT: + case DXGI_FORMAT_R8G8_SNORM: + case DXGI_FORMAT_R8G8_SINT: + case DXGI_FORMAT_R16_TYPELESS: + case DXGI_FORMAT_R16_FLOAT: + case DXGI_FORMAT_D16_UNORM: + case DXGI_FORMAT_R16_UNORM: + case DXGI_FORMAT_R16_UINT: + case DXGI_FORMAT_R16_SNORM: + case DXGI_FORMAT_R16_SINT: + case DXGI_FORMAT_B5G6R5_UNORM: + case DXGI_FORMAT_B5G5R5A1_UNORM: + case DXGI_FORMAT_B4G4R4A4_UNORM: + return 2; + case DXGI_FORMAT_R8_TYPELESS: + case DXGI_FORMAT_R8_UNORM: + case DXGI_FORMAT_R8_UINT: + case DXGI_FORMAT_R8_SNORM: + case DXGI_FORMAT_R8_SINT: + case DXGI_FORMAT_A8_UNORM: + return 1; + default: + throw winrt::hresult_invalid_argument(L"Invalid pixel format!"); + } +} + +//---------------------------------------------------------------------------- +// +// CopyBytesFromTexture +// +//---------------------------------------------------------------------------- +inline auto CopyBytesFromTexture(winrt::com_ptr const& texture, uint32_t subresource = 0) +{ + winrt::com_ptr device; + texture->GetDevice(device.put()); + winrt::com_ptr context; + device->GetImmediateContext(context.put()); + + auto stagingTexture = PrepareStagingTexture(device, texture); + + D3D11_TEXTURE2D_DESC desc = {}; + stagingTexture->GetDesc(&desc); + auto bytesPerPixel = GetBytesPerPixel(desc.Format); + + // Copy the bits + D3D11_MAPPED_SUBRESOURCE mapped = {}; + winrt::check_hresult(context->Map(stagingTexture.get(), subresource, D3D11_MAP_READ, 0, &mapped)); + + auto bytesStride = static_cast(desc.Width) * bytesPerPixel; + std::vector bytes(bytesStride * static_cast(desc.Height), 0); + auto source = static_cast(mapped.pData); + auto dest = bytes.data(); + for (auto i = 0; i < static_cast(desc.Height); i++) + { + memcpy(dest, source, bytesStride); + + source += mapped.RowPitch; + dest += bytesStride; + } + context->Unmap(stagingTexture.get(), 0); + + return bytes; +} + + +//---------------------------------------------------------------------------- +// +// StopRecording +// +//---------------------------------------------------------------------------- +void StopRecording() +{ + if( g_RecordToggle == TRUE ) { + + g_SelectRectangle.Stop(); + + if ( g_RecordingSession != nullptr ) { + + g_RecordingSession->Close(); + g_RecordingSession = nullptr; + } + + g_RecordToggle = FALSE; +#if WINDOWS_CURSOR_RECORDING_WORKAROUND + + if( g_hWndLiveZoom != NULL && g_LiveZoomLevelOne ) { + + if( IsWindowVisible( g_hWndLiveZoom ) ) { + + ShowWindow( g_hWndLiveZoom, SW_HIDE ); + DestroyWindow( g_hWndLiveZoom ); + g_LiveZoomLevelOne = false; + } + } +#endif + } +} + + +//---------------------------------------------------------------------------- +// +// GetUniqueRecordingFilename +// +// Gets a unique file name for recording saves, using the " (N)" suffix +// approach so that the user can hit OK without worrying about overwriting +// if they are making multiple recordings in one session or don't want to +// always see an overwrite dialog or stop to clean up files. +// +//---------------------------------------------------------------------------- +auto GetUniqueRecordingFilename() +{ + std::filesystem::path path{ g_RecordingSaveLocation }; + + // Chop off index if it's there + auto base = std::regex_replace( path.stem().wstring(), std::wregex( L" [(][0-9]+[)]$" ), L"" ); + path.replace_filename( base + path.extension().wstring() ); + + for( int index = 1; std::filesystem::exists( path ); index++ ) + { + + // File exists, so increment number to avoid collision + path.replace_filename( base + L" (" + std::to_wstring(index) + L')' + path.extension().wstring() ); + } + return path.stem().wstring() + path.extension().wstring(); +} + +//---------------------------------------------------------------------------- +// +// StartRecordingAsync +// +// Starts the screen recording. +// +//---------------------------------------------------------------------------- +winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndRecord ) try +{ + auto tempFolderPath = std::filesystem::temp_directory_path().wstring(); + auto tempFolder = co_await winrt::StorageFolder::GetFolderFromPathAsync( tempFolderPath ); + auto appFolder = co_await tempFolder.CreateFolderAsync( L"ZoomIt", winrt::CreationCollisionOption::OpenIfExists ); + auto file = co_await appFolder.CreateFileAsync( L"zoomit.mp4", winrt::CreationCollisionOption::ReplaceExisting ); + + // Get the device + auto d3dDevice = util::CreateD3D11Device(); + auto dxgiDevice = d3dDevice.as(); + g_RecordDevice = CreateDirect3DDevice( dxgiDevice.get() ); + + // Get the active MONITOR capture device + HMONITOR hMon = NULL; + POINT cursorPos = { 0, 0 }; + if( pMonitorFromPoint ) { + + GetCursorPos( &cursorPos ); + hMon = pMonitorFromPoint( cursorPos, MONITOR_DEFAULTTONEAREST ); + } + + winrt::Windows::Graphics::Capture::GraphicsCaptureItem item{ nullptr }; + if( hWndRecord ) + item = util::CreateCaptureItemForWindow( hWndRecord ); + else + item = util::CreateCaptureItemForMonitor( hMon ); + + auto stream = co_await file.OpenAsync( winrt::FileAccessMode::ReadWrite ); + g_RecordingSession = VideoRecordingSession::Create( + g_RecordDevice, + item, + *rcCrop, + g_RecordFrameRate, + g_CaptureAudio, + stream ); + + if( g_hWndLiveZoom != NULL ) + g_RecordingSession->EnableCursorCapture( false ); + + co_await g_RecordingSession->StartAsync(); + + // g_RecordingSession isn't null if we're aborting a recording + if( g_RecordingSession == nullptr ) { + + g_bSaveInProgress = true; + + SendMessage( g_hWndMain, WM_USER_SAVE_CURSOR, 0, 0 ); + + winrt::StorageFile destFile = nullptr; + HRESULT hr = S_OK; + try { + auto saveDialog = wil::CoCreateInstance( CLSID_FileSaveDialog ); + FILEOPENDIALOGOPTIONS options; + if( SUCCEEDED( saveDialog->GetOptions( &options ) ) ) + saveDialog->SetOptions( options | FOS_FORCEFILESYSTEM ); + wil::com_ptr videosItem; + if( SUCCEEDED ( SHGetKnownFolderItem( FOLDERID_Videos, KF_FLAG_DEFAULT, nullptr, IID_IShellItem, (void**) videosItem.put() ) ) ) + saveDialog->SetDefaultFolder( videosItem.get() ); + saveDialog->SetDefaultExtension( L".mp4" ); + COMDLG_FILTERSPEC fileTypes[] = { + { L"MP4 Video", L"*.mp4" } + }; + saveDialog->SetFileTypes( _countof( fileTypes ), fileTypes ); + + if( g_RecordingSaveLocation.size() == 0) { + + wil::com_ptr shellItem; + wil::unique_cotaskmem_string folderPath; + if (SUCCEEDED(saveDialog->GetFolder(shellItem.put())) && SUCCEEDED(shellItem->GetDisplayName(SIGDN_FILESYSPATH, folderPath.put()))) + g_RecordingSaveLocation = folderPath.get(); + g_RecordingSaveLocation = std::filesystem::path{ g_RecordingSaveLocation } /= DEFAULT_RECORDING_FILE; + } + auto suggestedName = GetUniqueRecordingFilename(); + saveDialog->SetFileName( suggestedName.c_str() ); + + THROW_IF_FAILED( saveDialog->Show( hWnd ) ); + wil::com_ptr shellItem; + THROW_IF_FAILED(saveDialog->GetResult(shellItem.put())); + wil::unique_cotaskmem_string filePath; + THROW_IF_FAILED(shellItem->GetDisplayName(SIGDN_FILESYSPATH, filePath.put())); + auto path = std::filesystem::path( filePath.get() ); + + winrt::StorageFolder folder{ co_await winrt::StorageFolder::GetFolderFromPathAsync( path.parent_path().c_str() ) }; + destFile = co_await folder.CreateFileAsync( path.filename().c_str(), winrt::CreationCollisionOption::ReplaceExisting ); + } + catch( const wil::ResultException& error ) { + + hr = error.GetErrorCode(); + } + if( destFile == nullptr ) { + + co_await file.DeleteAsync(); + } + else { + + co_await file.MoveAndReplaceAsync( destFile ); + g_RecordingSaveLocation = file.Path(); + SaveToClipboard(g_RecordingSaveLocation.c_str(), hWnd); + } + g_bSaveInProgress = false; + + SendMessage( g_hWndMain, WM_USER_RESTORE_CURSOR, 0, 0 ); + if( hWnd == g_hWndMain ) + RestoreForeground(); + + if( FAILED( hr ) ) + throw winrt::hresult_error( hr ); + } + else { + + co_await file.DeleteAsync(); + g_RecordingSession = nullptr; + } +} catch( const winrt::hresult_error& error ) { + + PostMessage( g_hWndMain, WM_USER_STOP_RECORDING, 0, 0 ); + + // Suppress the error from canceling the save dialog + if( error.code() == HRESULT_FROM_WIN32( ERROR_CANCELLED )) + co_return; + + if (g_RecordToggle == FALSE) { + + MessageBox(g_hWndMain, L"Recording cancelled before started", APPNAME, MB_OK | MB_ICONERROR | MB_SYSTEMMODAL); + } + else { + + ErrorDialogString(g_hWndMain, L"Error starting recording", error.message().c_str()); + } +} + +//---------------------------------------------------------------------------- +// +// UpdateMonitorInfo +// +//---------------------------------------------------------------------------- +void UpdateMonitorInfo( POINT point, MONITORINFO* monInfo ) +{ + HMONITOR hMon{}; + if( pMonitorFromPoint != nullptr ) + { + hMon = pMonitorFromPoint( point, MONITOR_DEFAULTTONEAREST ); + } + if( hMon != nullptr ) + { + monInfo->cbSize = sizeof *monInfo; + pGetMonitorInfo( hMon, monInfo ); + } + else + { + *monInfo = {}; + HDC hdcScreen = CreateDC( L"DISPLAY", nullptr, nullptr, nullptr ); + if( hdcScreen != nullptr ) + { + monInfo->rcMonitor.right = GetDeviceCaps( hdcScreen, HORZRES ); + monInfo->rcMonitor.bottom = GetDeviceCaps( hdcScreen, VERTRES ); + DeleteDC( hdcScreen ); + } + } +} + + HRESULT OpenPowerToysSettingsApp() + { + std::wstring path = get_module_folderpath(g_hInstance); + path += L"\\PowerToys.exe"; + + std::wstring openSettings = L"--open-settings=ZoomIt"; + + std::wstring full_command_path = path + L" " + openSettings; + + STARTUPINFO startupInfo; + ZeroMemory(&startupInfo, sizeof(STARTUPINFO)); + startupInfo.cb = sizeof(STARTUPINFO); + startupInfo.wShowWindow = SW_SHOWNORMAL; + + PROCESS_INFORMATION processInformation; + + CreateProcess( + path.c_str(), + full_command_path.data(), + NULL, + NULL, + TRUE, + 0, + NULL, + NULL, + &startupInfo, + &processInformation); + + if (!CloseHandle(processInformation.hProcess)) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + if (!CloseHandle(processInformation.hThread)) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + return S_OK; + } + +//---------------------------------------------------------------------------- +// +// ShowMainWindow +// +//---------------------------------------------------------------------------- +void ShowMainWindow(HWND hWnd, const MONITORINFO& monInfo, int width, int height) +{ + // Show the window first + SetWindowPos(hWnd, HWND_TOPMOST, monInfo.rcMonitor.left, monInfo.rcMonitor.top, + width, height, SWP_SHOWWINDOW | SWP_NOCOPYBITS); + + // Now invalidate and update the window + InvalidateRect(hWnd, NULL, TRUE); + UpdateWindow(hWnd); + + SetForegroundWindow(hWnd); + SetActiveWindow(hWnd); +} + +//---------------------------------------------------------------------------- +// +// MainWndProc +// +//---------------------------------------------------------------------------- +LRESULT APIENTRY MainWndProc( + HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam) +{ + static int width, height; + static HDC hdcScreen, hdcScreenCompat, hdcScreenCursorCompat, hdcScreenSaveCompat; + static HBITMAP hbmpCompat, hbmpDrawingCompat, hbmpCursorCompat; + static RECT cropRc{}; + static BITMAP bmp; + static BOOLEAN g_TimerActive = FALSE; + static BOOLEAN g_Zoomed = FALSE; + static TypeModeState g_TypeMode = TypeModeOff; + static BOOLEAN g_HaveTyped = FALSE; + static DEVMODE secondaryDevMode; + static RECT g_LiveZoomSourceRect; + static float g_LiveZoomLevel; + static float zoomLevel; + static float zoomTelescopeStep; + static float zoomTelescopeTarget; + static POINT cursorPos; + static POINT savedCursorPos; + static RECT cursorRc; + static RECT boundRc; + static POINT prevPt; + static POINT textStartPt; + static POINT textPt; + static P_DRAW_UNDO drawUndoList = NULL; + static P_TYPED_KEY typedKeyList = NULL; + static BOOLEAN g_HaveDrawn = FALSE; + static DWORD g_DrawingShape = 0; + static DWORD prevPenWidth = g_PenWidth; + static POINT g_RectangleAnchor; + static RECT g_rcRectangle; + static BOOLEAN g_Tracing = FALSE; + static int g_BlankedScreen = 0; + static int g_StraightDirection = 0; + static BOOLEAN g_Drawing = FALSE; + static HWND g_ActiveWindow = NULL; + static int breakTimeout; + static HBITMAP g_hBackgroundBmp = NULL; + static HDC g_hDcBackgroundFile; + static HPEN hDrawingPen; + static HFONT hTimerFont; + static HFONT hNegativeTimerFont; + static HFONT hTypingFont; + static MONITORINFO monInfo; + static MONITORINFO lastMonInfo; + static HWND hTargetWindow = NULL; + static RECT rcTargetWindow; + static BOOLEAN forcePenResize = TRUE; + static BOOLEAN activeBreakShowDesktop = g_BreakShowDesktop; + static BOOLEAN activeBreakShowBackgroundFile = g_BreakShowBackgroundFile; + static TCHAR activeBreakBackgroundFile[MAX_PATH] = {0}; + static UINT wmTaskbarCreated; +#if 0 + TITLEBARINFO titleBarInfo; + WINDOWINFO targetWindowInfo; +#endif + bool isCaptureSupported = false; + RECT rc, rc1; + PAINTSTRUCT ps; + TCHAR timerText[16]; + TCHAR negativeTimerText[16]; + BOOLEAN penInverted; + BOOLEAN zoomIn; + HDC hDc; + HWND hWndRecord; + int x, y, delta; + HMENU hPopupMenu; + OPENFILENAME openFileName; + static TCHAR filePath[MAX_PATH] = {L"zoomit"}; + NOTIFYICONDATA tNotifyIconData; + + const auto drawAllRightJustifiedLines = [&rc]( long lineHeight, bool doPop = false ) { + rc.top = textPt.y - static_cast(g_TextBufferPreviousLines.size()) * lineHeight; + + for( const auto& line : g_TextBufferPreviousLines ) + { + DrawText( hdcScreenCompat, line.c_str(), static_cast(line.length()), &rc, DT_CALCRECT ); + const auto textWidth = rc.right - rc.left; + rc.left = textPt.x - textWidth; + rc.right = textPt.x; + DrawText( hdcScreenCompat, line.c_str(), static_cast(line.length()), &rc, DT_LEFT ); + rc.top += lineHeight; + } + if( !g_TextBuffer.empty() ) + { + if( doPop ) + { + g_TextBuffer.pop_back(); + } + DrawText( hdcScreenCompat, g_TextBuffer.c_str(), static_cast(g_TextBuffer.length()), &rc, DT_CALCRECT ); + rc.left = textPt.x - (rc.right - rc.left); + rc.right = textPt.x; + DrawText( hdcScreenCompat, g_TextBuffer.c_str(), static_cast(g_TextBuffer.length()), &rc, DT_LEFT ); + } + }; + + switch (message) { + case WM_CREATE: + + // get default font + GetObject( GetStockObject(DEFAULT_GUI_FONT), sizeof g_LogFont, &g_LogFont ); + g_LogFont.lfWeight = FW_NORMAL; + hDc = CreateCompatibleDC( NULL ); + g_LogFont.lfHeight = -MulDiv(8, GetDeviceCaps(hDc, LOGPIXELSY), 72); + DeleteDC( hDc ); + + reg.ReadRegSettings( RegSettings ); + + // to support migrating from + if ((g_PenColor >> 24) == 0) { + g_PenColor |= 0xFF << 24; + } + + g_PenWidth = g_RootPenWidth; + + g_ToggleMod = GetKeyMod( g_ToggleKey ); + g_LiveZoomToggleMod = GetKeyMod( g_LiveZoomToggleKey ); + g_DrawToggleMod = GetKeyMod( g_DrawToggleKey ); + g_BreakToggleMod = GetKeyMod( g_BreakToggleKey ); + g_DemoTypeToggleMod = GetKeyMod( g_DemoTypeToggleKey ); + g_SnipToggleMod = GetKeyMod( g_SnipToggleKey ); + g_RecordToggleMod = GetKeyMod( g_RecordToggleKey ); + + if( !g_OptionsShown && !g_StartedByPowerToys ) { + // First run should show options when running as standalone. If not running as standalone, + // options screen won't show and we should register keys instead. + SendMessage( hWnd, WM_COMMAND, IDC_OPTIONS, 0 ); + g_OptionsShown = TRUE; + reg.WriteRegSettings( RegSettings ); + } else { + BOOL showOptions = FALSE; + + if( g_ToggleKey && !RegisterHotKey( hWnd, ZOOM_HOTKEY, g_ToggleMod, g_ToggleKey & 0xFF)) { + + MessageBox( hWnd, L"The specified zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.", + APPNAME, MB_ICONERROR ); + showOptions = TRUE; + + } else if( g_LiveZoomToggleKey && + (!RegisterHotKey( hWnd, LIVE_HOTKEY, g_LiveZoomToggleMod, g_LiveZoomToggleKey & 0xFF) || + !RegisterHotKey(hWnd, LIVE_DRAW_HOTKEY, (g_LiveZoomToggleMod ^ MOD_SHIFT), g_LiveZoomToggleKey & 0xFF))) { + + MessageBox( hWnd, L"The specified live-zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.", + APPNAME, MB_ICONERROR ); + showOptions = TRUE; + + } else if( g_DrawToggleKey && !RegisterHotKey( hWnd, DRAW_HOTKEY, g_DrawToggleMod, g_DrawToggleKey & 0xFF )) { + + MessageBox( hWnd, L"The specified draw w/out zoom hotkey is already in use.\nSelect a different draw w/out zoom hotkey.", + APPNAME, MB_ICONERROR ); + showOptions = TRUE; + + } + else if (g_BreakToggleKey && !RegisterHotKey(hWnd, BREAK_HOTKEY, g_BreakToggleMod, g_BreakToggleKey & 0xFF)) { + + MessageBox(hWnd, L"The specified break timer hotkey is already in use.\nSelect a different break timer hotkey.", + APPNAME, MB_ICONERROR); + showOptions = TRUE; + + } + else if( g_DemoTypeToggleKey && + (!RegisterHotKey( hWnd, DEMOTYPE_HOTKEY, g_DemoTypeToggleMod, g_DemoTypeToggleKey & 0xFF ) || + !RegisterHotKey(hWnd, DEMOTYPE_RESET_HOTKEY, (g_DemoTypeToggleMod ^ MOD_SHIFT), g_DemoTypeToggleKey & 0xFF))) { + + MessageBox( hWnd, L"The specified live-type hotkey is already in use.\nSelect a different live-type hotkey.", + APPNAME, MB_ICONERROR ); + showOptions = TRUE; + + } + else if (g_SnipToggleKey && + (!RegisterHotKey(hWnd, SNIP_HOTKEY, g_SnipToggleMod, g_SnipToggleKey & 0xFF) || + !RegisterHotKey(hWnd, SNIP_SAVE_HOTKEY, (g_SnipToggleMod ^ MOD_SHIFT), g_SnipToggleKey & 0xFF))) { + + MessageBox(hWnd, L"The specified snip hotkey is already in use.\nSelect a different snip hotkey.", + APPNAME, MB_ICONERROR); + showOptions = TRUE; + + } + else if (g_RecordToggleKey && + (!RegisterHotKey(hWnd, RECORD_HOTKEY, g_RecordToggleMod | MOD_NOREPEAT, g_RecordToggleKey & 0xFF) || + !RegisterHotKey(hWnd, RECORD_CROP_HOTKEY, (g_RecordToggleMod ^ MOD_SHIFT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF) || + !RegisterHotKey(hWnd, RECORD_WINDOW_HOTKEY, (g_RecordToggleMod ^ MOD_ALT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF))) { + + MessageBox(hWnd, L"The specified record hotkey is already in use.\nSelect a different record hotkey.", + APPNAME, MB_ICONERROR); + showOptions = TRUE; + } + if( showOptions ) { + + SendMessage( hWnd, WM_COMMAND, IDC_OPTIONS, 0 ); + } + } + SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL ); + wmTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated")); + return TRUE; + + case WM_CLOSE: + // Do not allow users to close the main window, for example with Alt-F4. + return 0; + + case WM_HOTKEY: + if( g_RecordCropping == TRUE ) + { + if( wParam != RECORD_CROP_HOTKEY ) + { + // Cancel cropping on any hotkey. + g_SelectRectangle.Stop(); + g_RecordCropping = FALSE; + + // Cropping is handled by a blocking call in WM_HOTKEY, so post + // this message to the window for processing after the previous + // WM_HOTKEY message completes processing. + PostMessage( hWnd, message, wParam, lParam ); + } + return 0; + } + + // + // Magic value that comes from tray context menu + // + if (lParam == 1) { + + // + // Sleep to let context menu dismiss + // + Sleep(250); + } + switch( wParam ) { + case LIVE_DRAW_HOTKEY: + { + OutputDebug(L"LIVE_DRAW_HOTKEY\n"); + LONG_PTR exStyle = GetWindowLongPtr(hWnd, GWL_EXSTYLE); + + if ((exStyle & WS_EX_LAYERED)) { + OutputDebug(L"LiveDraw reactivate\n"); + + // Just focus on the window and re-enter drawing mode + SetFocus(hWnd); + SetForegroundWindow(hWnd); + SendMessage(hWnd, WM_LBUTTONDOWN, 0, MAKELPARAM(cursorPos.x, cursorPos.y)); + SendMessage(hWnd, WM_MOUSEMOVE, 0, MAKELPARAM(cursorPos.x, cursorPos.y)); + if( IsWindowVisible( g_hWndLiveZoom ) ) + { + SendMessage( g_hWndLiveZoom, WM_USER_MAGNIFY_CURSOR, FALSE, 0 ); + } + break; + } + else { + OutputDebug(L"LiveDraw create\n"); + + exStyle = GetWindowLongPtr(hWnd, GWL_EXSTYLE); + SetWindowLongPtr(hWnd, GWL_EXSTYLE, exStyle | WS_EX_LAYERED); + SetLayeredWindowAttributes(hWnd, COLORREF(RGB(0, 0, 0)), 0, LWA_COLORKEY); + pMagSetWindowFilterList( g_hWndLiveZoomMag, MW_FILTERMODE_EXCLUDE, 0, nullptr ); + } + [[fallthrough]]; + } + case DRAW_HOTKEY: + // + // Enter drawing mode without zoom + // + if (g_StartedByPowerToys) + Trace::ZoomItActivateDraw(); + + if( !g_Zoomed ) { + OutputDebug(L"LiveDraw: %d (%d)\n", wParam, (wParam == LIVE_DRAW_HOTKEY)); + +#if WINDOWS_CURSOR_RECORDING_WORKAROUND + if( IsWindowVisible( g_hWndLiveZoom ) && !g_LiveZoomLevelOne ) { +#else + if( IsWindowVisible( g_hWndLiveZoom )) { +#endif + + OutputDebug(L" In Live zoom\n"); + SendMessage(hWnd, WM_HOTKEY, ZOOM_HOTKEY, wParam == LIVE_DRAW_HOTKEY ? LIVE_DRAW_ZOOM : 0); + + } else { + OutputDebug(L" Not in Live zoom\n"); + SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, wParam == LIVE_DRAW_HOTKEY ? LIVE_DRAW_ZOOM : 0 ); + zoomLevel = zoomTelescopeTarget = 1; + SendMessage( hWnd, WM_LBUTTONDOWN, 0, MAKELPARAM( cursorPos.x, cursorPos.y )); + } + if(wParam == LIVE_DRAW_HOTKEY) { + + SetLayeredWindowAttributes(hWnd, COLORREF(RGB(0, 0, 0)), 0, LWA_COLORKEY); + SendMessage(hWnd, WM_KEYDOWN, 'K', LIVE_DRAW_ZOOM); + SetTimer(hWnd, 3, 10, NULL); + SendMessage(hWnd, WM_MOUSEMOVE, 0, MAKELPARAM(cursorPos.x, cursorPos.y)); + ShowMainWindow(hWnd, monInfo, width, height); + if( ( g_PenColor & 0xFFFFFF ) == COLOR_BLUR ) + { + // Blur is not supported in LiveDraw + g_PenColor = COLOR_RED; + } + // Highlight is not supported in LiveDraw + g_PenColor |= 0xFF << 24; + } + } + break; + + case SNIP_SAVE_HOTKEY: + case SNIP_HOTKEY: + { + // Block liveZoom liveDraw snip due to mirroring bug + if( IsWindowVisible( g_hWndLiveZoom ) + && ( GetWindowLongPtr( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED ) ) + { + break; + } + + bool zoomed = true; + if (g_StartedByPowerToys) + Trace::ZoomItActivateSnip(); + + // First, static zoom + if( !g_Zoomed ) + { + zoomed = false; + if( IsWindowVisible( g_hWndLiveZoom ) && !g_LiveZoomLevelOne ) + { + SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, SHALLOW_ZOOM ); + } + else + { + SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, LIVE_DRAW_ZOOM); + } + zoomLevel = zoomTelescopeTarget = 1; + } + else if( g_Drawing ) + { + // Exit drawing mode to hide the drawing cursor + SendMessage( hWnd, WM_USER_EXIT_MODE, 0, 0 ); + + // Exit again if still in drawing mode, which happens from type mode + if( g_Drawing ) + { + SendMessage( hWnd, WM_USER_EXIT_MODE, 0, 0 ); + } + } + ShowMainWindow(hWnd, monInfo, width, height); + + // Now copy crop or copy+save + if( LOWORD( wParam ) == SNIP_SAVE_HOTKEY ) + { + SendMessage( hWnd, WM_COMMAND, IDC_SAVE_CROP, ( zoomed ? 0 : SHALLOW_ZOOM ) ); + } + else + { + SendMessage( hWnd, WM_COMMAND, IDC_COPY_CROP, ( zoomed ? 0 : SHALLOW_ZOOM ) ); + } + + // Now if we weren't zoomed, unzoom + if( !zoomed ) + { + if( g_ZoomOnLiveZoom ) + { + // hiding the cursor allows for a cleaner transition back to the magnified cursor + ShowCursor( false ); + SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 ); + ShowCursor( true ); + } + else + { + SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, SHALLOW_ZOOM ); + } + } + + // exit zoom + if( g_Zoomed ) + { + // If from liveDraw, extra care is needed to destruct + if( GetWindowLong( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED ) + { + OutputDebug( L"Exiting liveDraw after snip\n" ); + SendMessage( hWnd, WM_KEYDOWN, VK_ESCAPE, 0 ); + } + else + { + // Set wparam to 1 to exit without animation + OutputDebug(L"Exiting zoom after snip\n" ); + SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, SHALLOW_DESTROY ); + } + } + break; + } + + case BREAK_HOTKEY: + // + // Go to break timer + // +#if WINDOWS_CURSOR_RECORDING_WORKAROUND + if( !g_Zoomed && ( !IsWindowVisible( g_hWndLiveZoom ) || g_LiveZoomLevelOne ) ) { +#else + if( !g_Zoomed && !IsWindowVisible( g_hWndLiveZoom )) { +#endif + + SendMessage( hWnd, WM_COMMAND, IDC_BREAK, 0 ); + } + break; + + case DEMOTYPE_RESET_HOTKEY: + ResetDemoTypeIndex(); + break; + + case DEMOTYPE_HOTKEY: + { + // + // Live type + // + switch( StartDemoType( g_DemoTypeFile, g_DemoTypeSpeedSlider, g_DemoTypeUserDriven ) ) + { + case ERROR_LOADING_FILE: + ErrorDialog( hWnd, L"Error loading DemoType file", GetLastError() ); + break; + + case NO_FILE_SPECIFIED: + MessageBox( hWnd, L"No DemoType file specified", APPNAME, MB_OK ); + break; + + case FILE_SIZE_OVERFLOW: + { + std::wstring msg = L"Unsupported DemoType file size (" + + std::to_wstring( MAX_INPUT_SIZE ) + L" byte limit)"; + MessageBox( hWnd, msg.c_str(), APPNAME, MB_OK ); + break; + } + + case UNKNOWN_FILE_DATA: + MessageBox( hWnd, L"Unrecognized DemoType file content", APPNAME, MB_OK ); + break; + default: + if (g_StartedByPowerToys) + Trace::ZoomItActivateDemoType(); + break; + } + break; + } + + case LIVE_HOTKEY: + // + // Live zoom + // + OutputDebug(L"*** LIVE_HOTKEY\n"); + + // If LiveZoom and LiveDraw are active then exit both + if( g_Zoomed && IsWindowVisible( g_hWndLiveZoom ) && ( GetWindowLongPtr( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED ) ) + { + SendMessage( hWnd, WM_KEYDOWN, VK_ESCAPE, 0 ); + PostMessage(hWnd, WM_HOTKEY, LIVE_HOTKEY, 0); + break; + } + + if( !g_Zoomed && !g_TimerActive && ( !g_fullScreenWorkaround || !g_RecordToggle ) ) { + if (g_StartedByPowerToys) + Trace::ZoomItActivateLiveZoom(); + + if( g_hWndLiveZoom == NULL ) { + OutputDebug(L"Create LIVEZOOM\n"); + g_hWndLiveZoom = CreateWindowEx( WS_EX_TOOLWINDOW | WS_EX_LAYERED | WS_EX_TRANSPARENT, + L"MagnifierClass", L"ZoomIt Live Zoom", + WS_POPUP | WS_CLIPSIBLINGS, + 0, 0, 0, 0, NULL, NULL, g_hInstance, static_cast(GetForegroundWindow()) ); + pSetLayeredWindowAttributes( hWnd, 0, 0, LWA_ALPHA ); + EnableWindow( g_hWndLiveZoom, FALSE ); + pMagSetWindowFilterList( g_hWndLiveZoomMag, MW_FILTERMODE_EXCLUDE, 1, &hWnd ); + + } else { +#if WINDOWS_CURSOR_RECORDING_WORKAROUND + if( g_LiveZoomLevelOne ) { + OutputDebug(L"liveZoom level one\n"); + SendMessage( g_hWndLiveZoom, WM_USER_SET_ZOOM, static_cast(g_LiveZoomLevel), 0 ); + } + else { +#endif + + if( IsWindowVisible( g_hWndLiveZoom )) { +#if WINDOWS_CURSOR_RECORDING_WORKAROUND + + if( g_RecordToggle ) + g_LiveZoomLevel = g_ZoomLevels[g_SliderZoomLevel]; +#endif + // Unzoom + SendMessage( g_hWndLiveZoom, WM_KEYDOWN, VK_ESCAPE, 0 ); + + } else { + + OutputDebug(L"Show liveZoom\n"); + ShowWindow( g_hWndLiveZoom, SW_SHOW ); + } +#if WINDOWS_CURSOR_RECORDING_WORKAROUND + } +#endif + } + + if ( g_RecordToggle ) + { + g_SelectRectangle.UpdateOwner( g_hWndLiveZoom ); + } + } + break; + + case RECORD_HOTKEY: + case RECORD_CROP_HOTKEY: + case RECORD_WINDOW_HOTKEY: + + // + // Recording + // This gets entered twice per recording: + // 1. When the hotkey is pressed to start recording + // 2. When the hotkey is pressed to stop recording + // + if( g_fullScreenWorkaround && g_hWndLiveZoom != NULL && IsWindowVisible( g_hWndLiveZoom ) != FALSE ) + { + break; + } + + if( g_RecordCropping == TRUE ) + { + break; + } + + // Start screen recording + try + { + isCaptureSupported = winrt::GraphicsCaptureSession::IsSupported(); + } + catch( const winrt::hresult_error& ) {} + if( !isCaptureSupported ) + { + MessageBox( hWnd, L"Screen recording requires Windows 10, May 2019 Update or higher.", APPNAME, MB_OK ); + break; + } + + // If shift, then we're cropping + hWndRecord = 0; + if( wParam == RECORD_CROP_HOTKEY ) + { + if( g_RecordToggle == TRUE ) + { + // Already recording + break; + } + + g_RecordCropping = TRUE; + + POINT savedPoint{}; + RECT savedClip = {}; + + // Handle the cursor for live zoom and static zoom modes. + if( ( g_hWndLiveZoom != nullptr ) || ( g_Zoomed == TRUE ) ) + { + GetCursorPos( &savedPoint ); + UpdateMonitorInfo( savedPoint, &monInfo ); + } + if( g_hWndLiveZoom != nullptr ) + { + // Hide the magnified cursor. + SendMessage( g_hWndLiveZoom, WM_USER_MAGNIFY_CURSOR, FALSE, 0 ); + + // Show the system cursor where the magnified was. + g_LiveZoomSourceRect = *reinterpret_cast( SendMessage( g_hWndLiveZoom, WM_USER_GET_SOURCE_RECT, 0, 0 ) ); + savedPoint = ScalePointInRects( savedPoint, g_LiveZoomSourceRect, monInfo.rcMonitor ); + SetCursorPos( savedPoint.x, savedPoint.y ); + if ( pMagShowSystemCursor != nullptr ) + { + pMagShowSystemCursor( TRUE ); + } + } + else if( ( g_Zoomed == TRUE ) && ( g_Drawing == TRUE ) ) + { + // Unclip the cursor. + GetClipCursor( &savedClip ); + ClipCursor( nullptr ); + + // Scale the cursor position to the zoomed and move it. + auto point = ScalePointInRects( savedPoint, boundRc, monInfo.rcMonitor ); + SetCursorPos( point.x, point.y ); + } + + if( g_Zoomed == FALSE ) + { + SetWindowPos( hWnd, HWND_TOPMOST, monInfo.rcMonitor.left, monInfo.rcMonitor.top, width, height, SWP_SHOWWINDOW ); + } + + // This call blocks with a message loop while cropping. + auto canceled = !g_SelectRectangle.Start( ( g_hWndLiveZoom != nullptr ) ? g_hWndLiveZoom : hWnd ); + g_RecordCropping = FALSE; + + // Restore the cursor if applicable. + if( g_hWndLiveZoom != nullptr ) + { + // Hide the system cursor. + if ( pMagShowSystemCursor != nullptr ) + { + pMagShowSystemCursor( FALSE ); + } + + // Show the magnified cursor where the system cursor was. + GetCursorPos( &savedPoint ); + savedPoint = ScalePointInRects( savedPoint, monInfo.rcMonitor, g_LiveZoomSourceRect ); + SetCursorPos( savedPoint.x, savedPoint.y ); + SendMessage( g_hWndLiveZoom, WM_USER_MAGNIFY_CURSOR, TRUE, 0 ); + } + else if( g_Zoomed == TRUE ) + { + SetCursorPos( savedPoint.x, savedPoint.y ); + + if ( g_Drawing == TRUE ) + { + ClipCursor( &savedClip ); + } + } + + SetForegroundWindow( hWnd ); + if( g_Zoomed == FALSE ) + { + SetActiveWindow( hWnd ); + ShowWindow( hWnd, SW_HIDE ); + } + + if( canceled ) + { + break; + } + + g_SelectRectangle.UpdateOwner( ( g_hWndLiveZoom != nullptr ) ? g_hWndLiveZoom : hWnd ); + cropRc = g_SelectRectangle.SelectedRect(); + } + else + { + cropRc = {}; + + // if we're recording a window, get the window + if (wParam == RECORD_WINDOW_HOTKEY) + { + GetCursorPos(&cursorPos); + hWndRecord = WindowFromPoint(cursorPos); + while( GetParent(hWndRecord) != NULL) + { + hWndRecord = GetParent(hWndRecord); + } + if( hWndRecord == GetDesktopWindow()) { + + hWndRecord = NULL; + } + } + } + + if( g_RecordToggle == FALSE ) + { + g_RecordToggle = TRUE; + if (g_StartedByPowerToys) + Trace::ZoomItActivateRecord(); + + StartRecordingAsync( hWnd, &cropRc, hWndRecord ); + } + else + { + StopRecording(); + } + break; + + case ZOOM_HOTKEY: + // + // Zoom + // + // Don't react to hotkey while options are open or we're + // saving the screen or live zoom is active + // + if( hWndOptions ) { + + break; + } + + OutputDebug( L"ZOOM HOTKEY: %d\n", lParam); + if( g_TimerActive ) { + + // + // Finished with break timer + // + if( g_BreakOnSecondary ) + { + EnableDisableSecondaryDisplay( hWnd, FALSE, &secondaryDevMode ); + } + + if( lParam != SHALLOW_DESTROY ) + { + ShowWindow( hWnd, SW_HIDE ); + if( g_hBackgroundBmp ) + { + DeleteObject( g_hBackgroundBmp ); + DeleteDC( g_hDcBackgroundFile ); + g_hBackgroundBmp = NULL; + } + } + + SetFocus( GetDesktopWindow() ); + KillTimer( hWnd, 0 ); + g_TimerActive = FALSE; + + DeleteObject( hTimerFont ); + DeleteObject( hNegativeTimerFont ); + DeleteDC( hdcScreen ); + DeleteDC( hdcScreenCompat ); + DeleteDC( hdcScreenSaveCompat ); + DeleteDC( hdcScreenCursorCompat ); + DeleteObject( hbmpCompat ); + EnableDisableScreenSaver( TRUE ); + EnableDisableOpacity( hWnd, FALSE ); + + } else { + + SendMessage( hWnd, WM_USER_TYPING_OFF, 0, 0 ); + if( !g_Zoomed ) { + + g_Zoomed = TRUE; + g_DrawingShape = FALSE; + OutputDebug( L"Zoom on\n"); + + if(g_StartedByPowerToys) + Trace::ZoomItActivateZoom(); + + // Hide the cursor before capturing if in live zoom + if( g_hWndLiveZoom != nullptr ) + { + OutputDebug(L"Hide cursor\n"); + SendMessage( g_hWndLiveZoom, WM_USER_MAGNIFY_CURSOR, FALSE, 0 ); + SendMessage( g_hWndLiveZoom, WM_TIMER, 0, 0 ); + SendMessage( g_hWndLiveZoom, WM_USER_MAGNIFY_CURSOR, FALSE, 0 ); + } + + // Get screen DCs + hdcScreen = CreateDC(L"DISPLAY", static_cast(NULL), + static_cast(NULL), static_cast(NULL)); + hdcScreenCompat = CreateCompatibleDC(hdcScreen); + hdcScreenSaveCompat = CreateCompatibleDC(hdcScreen); + hdcScreenCursorCompat = CreateCompatibleDC(hdcScreen); + + // Determine what monitor we're on + GetCursorPos(&cursorPos); + UpdateMonitorInfo( cursorPos, &monInfo ); + width = monInfo.rcMonitor.right - monInfo.rcMonitor.left; + height = monInfo.rcMonitor.bottom - monInfo.rcMonitor.top; + OutputDebug( L"ZOOM x: %d y: %d width: %d height: %d zoomLevel: %g\n", + cursorPos.x, cursorPos.y, width, height, zoomLevel ); + + // Create display bitmap + bmp.bmBitsPixel = static_cast(GetDeviceCaps(hdcScreen, BITSPIXEL)); + bmp.bmPlanes = static_cast(GetDeviceCaps(hdcScreen, PLANES)); + bmp.bmWidth = width; + bmp.bmHeight = height; + bmp.bmWidthBytes = ((bmp.bmWidth + 15) &~15)/8; + hbmpCompat = CreateBitmap(bmp.bmWidth, bmp.bmHeight, + bmp.bmPlanes, bmp.bmBitsPixel, static_cast(NULL)); + SelectObject(hdcScreenCompat, hbmpCompat); + + // Create saved bitmap + hbmpDrawingCompat = CreateBitmap(bmp.bmWidth, bmp.bmHeight, + bmp.bmPlanes, bmp.bmBitsPixel, static_cast(NULL)); + SelectObject(hdcScreenSaveCompat, hbmpDrawingCompat); + + // Create cursor save bitmap + // (have to accomodate large fonts and LiveZoom pen scaling) + hbmpCursorCompat = CreateBitmap( MAX_LIVE_PEN_WIDTH+CURSOR_ARM_LENGTH*2, + MAX_LIVE_PEN_WIDTH+CURSOR_ARM_LENGTH*2, bmp.bmPlanes, + bmp.bmBitsPixel, static_cast(NULL)); + SelectObject(hdcScreenCursorCompat, hbmpCursorCompat); + + // Create typing font + g_LogFont.lfHeight = height / 15; + if (g_LogFont.lfHeight < 20) + g_LogFont.lfQuality = NONANTIALIASED_QUALITY; + else + g_LogFont.lfQuality = ANTIALIASED_QUALITY; + hTypingFont = CreateFontIndirect(&g_LogFont); + SelectObject(hdcScreenCompat, hTypingFont); + SetTextColor(hdcScreenCompat, g_PenColor & 0xFFFFFF); + SetBkMode(hdcScreenCompat, TRANSPARENT); + + // Use the screen DC unless recording, because it contains the yellow border + HDC hdcSource = hdcScreen; + if( g_RecordToggle ) try { + + auto capture = CaptureScreenshot( winrt::DirectXPixelFormat::B8G8R8A8UIntNormalized ); + auto bytes = CopyBytesFromTexture( capture ); + + D3D11_TEXTURE2D_DESC desc; + capture->GetDesc( &desc ); + BITMAPINFO bitmapInfo = {}; + bitmapInfo.bmiHeader.biSize = sizeof bitmapInfo.bmiHeader; + bitmapInfo.bmiHeader.biWidth = desc.Width; + bitmapInfo.bmiHeader.biHeight = -static_cast(desc.Height); + bitmapInfo.bmiHeader.biPlanes = 1; + bitmapInfo.bmiHeader.biBitCount = 32; + bitmapInfo.bmiHeader.biCompression = BI_RGB; + void *bits; + auto dib = CreateDIBSection( NULL, &bitmapInfo, DIB_RGB_COLORS, &bits, nullptr, 0 ); + if( dib ) { + + CopyMemory( bits, bytes.data(), bytes.size() ); + auto hdcCapture = CreateCompatibleDC( hdcScreen ); + SelectObject( hdcCapture, dib ); + hdcSource = hdcCapture; + } + + } catch( const winrt::hresult_error& ) {} // on any failure, fall back to the screen DC + + bool captured = hdcSource != hdcScreen; + + // paint the initial bitmap + BitBlt( hdcScreenCompat, 0, 0, bmp.bmWidth, bmp.bmHeight, hdcSource, + captured ? 0 : monInfo.rcMonitor.left, captured ? 0 : monInfo.rcMonitor.top, SRCCOPY|CAPTUREBLT ); + BitBlt( hdcScreenSaveCompat, 0, 0, bmp.bmWidth, bmp.bmHeight, hdcSource, + captured ? 0 : monInfo.rcMonitor.left, captured ? 0 : monInfo.rcMonitor.top, SRCCOPY|CAPTUREBLT ); + + if( captured ) + { + OutputDebug(L"Captured screen\n"); + auto bitmap = GetCurrentObject( hdcSource, OBJ_BITMAP ); + DeleteObject( bitmap ); + DeleteDC( hdcSource ); + } + + // Create drawing pen + hDrawingPen = CreatePen(PS_SOLID, g_PenWidth, g_PenColor & 0xFFFFFF); + + g_BlankedScreen = FALSE; + g_HaveTyped = FALSE; + g_Drawing = FALSE; + g_TypeMode = TypeModeOff; + g_HaveDrawn = FALSE; + EnableDisableStickyKeys( TRUE ); + + // Go full screen + g_ActiveWindow = GetForegroundWindow(); + OutputDebug( L"active window: %x\n", PtrToLong(g_ActiveWindow) ); + + if( lParam != LIVE_DRAW_ZOOM) { + + OutputDebug(L"Calling ShowMainWindow\n"); + ShowMainWindow(hWnd, monInfo, width, height); + } + + // Start telescoping zoom. Lparam is non-zero if this + // was a real hotkey and not the message we send ourself to enter + // unzoomed drawing mode. + + // + // Are we switching from live zoom to standard zoom? + // +#if WINDOWS_CURSOR_RECORDING_WORKAROUND + if( IsWindowVisible( g_hWndLiveZoom ) && !g_LiveZoomLevelOne ) { +#else + if( IsWindowVisible( g_hWndLiveZoom )) { +#endif + + // Enter drawing mode + OutputDebug(L"Enter liveZoom draw\n"); + g_LiveZoomSourceRect = *reinterpret_cast(SendMessage( g_hWndLiveZoom, WM_USER_GET_SOURCE_RECT, 0, 0 )); + g_LiveZoomLevel = *reinterpret_cast(SendMessage(g_hWndLiveZoom, WM_USER_GET_ZOOM_LEVEL, 0, 0)); + + // Set live zoom level to 1 in preparation of us being full screen static + zoomLevel = 1.0; + zoomTelescopeTarget = 1.0; + if (lParam != LIVE_DRAW_ZOOM) { + + g_ZoomOnLiveZoom = TRUE; + } + + UpdateWindow( hWnd ); // overwrites where cursor erased + if( lParam != SHALLOW_ZOOM ) + { + // Put the drawing cursor where the magnified cursor was + OutputDebug(L"Setting cursor\n"); + + if (lParam != LIVE_DRAW_ZOOM) + { + cursorPos = ScalePointInRects( cursorPos, g_LiveZoomSourceRect, monInfo.rcMonitor ); + SetCursorPos( cursorPos.x, cursorPos.y ); + UpdateWindow( hWnd ); // overwrites where cursor erased + SendMessage( hWnd, WM_LBUTTONDOWN, 0, MAKELPARAM( cursorPos.x, cursorPos.y )); + } + } + else + { + InvalidateRect( hWnd, NULL, FALSE ); + } + UpdateWindow( hWnd ); + if( g_RecordToggle ) + { + g_SelectRectangle.UpdateOwner( hWnd ); + } + if( lParam != LIVE_DRAW_ZOOM ) { + + OutputDebug(L"Calling ShowMainWindow 2\n"); + + ShowWindow( g_hWndLiveZoom, SW_HIDE ); + } + + } else if( lParam != 0 && lParam != LIVE_DRAW_ZOOM ) { + + zoomTelescopeStep = ZOOM_LEVEL_STEP_IN; + zoomTelescopeTarget = g_ZoomLevels[g_SliderZoomLevel]; + if( g_AnimateZoom ) + zoomLevel = static_cast(1.0) * zoomTelescopeStep; + else + zoomLevel = zoomTelescopeTarget; + SetTimer( hWnd, 1, ZOOM_LEVEL_STEP_TIME, NULL ); + } + + } else { + + OutputDebug( L"Zoom off: don't animate=%d\n", lParam ); + // turn off liveDraw + SetLayeredWindowAttributes(hWnd, 0, 255, LWA_ALPHA); + + if( lParam != SHALLOW_DESTROY && !g_ZoomOnLiveZoom && g_AnimateZoom && + g_TelescopeZoomOut && zoomTelescopeTarget != 1 ) { + + // Start telescoping zoom. + zoomTelescopeStep = ZOOM_LEVEL_STEP_OUT; + zoomTelescopeTarget = 1.0; + SetTimer( hWnd, 2, ZOOM_LEVEL_STEP_TIME, NULL ); + + } else { + + // Simulate timer expiration + zoomTelescopeStep = 0; + zoomTelescopeTarget = zoomLevel = 1.0; + SendMessage( hWnd, WM_TIMER, 2, lParam ); + } + } + } + break; + } + return TRUE; + + case WM_POINTERUPDATE: { + penInverted = IsPenInverted(wParam); + OutputDebug( L"WM_POINTERUPDATE: contact: %d button down: %d X: %d Y: %d\n", + IS_POINTER_INCONTACT_WPARAM(wParam), + penInverted, + GET_X_LPARAM(lParam), + GET_Y_LPARAM(lParam)); + if( penInverted != g_PenInverted) { + + g_PenInverted = penInverted; + if (g_PenInverted) { + if (PopDrawUndo(hdcScreenCompat, &drawUndoList, width, height)) { + + SaveCursorArea(hdcScreenCursorCompat, hdcScreenCompat, prevPt); + InvalidateRect(hWnd, NULL, FALSE); + } + } + } else if( g_PenDown && !penInverted) { + + SendPenMessage(hWnd, WM_MOUSEMOVE, lParam); + } + } + return TRUE; + + case WM_POINTERUP: + OutputDebug(L"WM_POINTERUP\n"); + penInverted = IsPenInverted(wParam); + if (!penInverted) { + + SendPenMessage(hWnd, WM_LBUTTONUP, lParam); + SendPenMessage(hWnd, WM_RBUTTONDOWN, lParam); + g_PenDown = FALSE; + } + break; + + case WM_POINTERDOWN: + OutputDebug(L"WM_POINTERDOWN\n"); + penInverted = IsPenInverted(wParam); + if (!penInverted) { + + g_PenDown = TRUE; + + // Enter drawing mode + SendPenMessage(hWnd, WM_LBUTTONDOWN, lParam); + SendPenMessage(hWnd, WM_MOUSEMOVE, lParam); + SendPenMessage(hWnd, WM_LBUTTONUP, lParam); + SendPenMessage(hWnd, WM_MOUSEMOVE, lParam); + PopDrawUndo(hdcScreenCompat, &drawUndoList, width, height); + + // Enter tracing mode + SendPenMessage(hWnd, WM_LBUTTONDOWN, lParam); + } + break; + + case WM_KILLFOCUS: + if( ( g_RecordCropping == FALSE ) && g_Zoomed && !g_bSaveInProgress ) { + + // Turn off zoom if not in liveDraw + DWORD layeringFlag; + GetLayeredWindowAttributes(hWnd, NULL, NULL, &layeringFlag); + if( !(layeringFlag & LWA_COLORKEY)) { + + PostMessage(hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0); + } + } + break; + + case WM_MOUSEWHEEL: + + // + // Zoom or modify break timer + // + if( GET_WHEEL_DELTA_WPARAM(wParam) < 0 ) + wParam -= (WHEEL_DELTA-1) << 16; + else + wParam += (WHEEL_DELTA-1) << 16; + delta = GET_WHEEL_DELTA_WPARAM(wParam)/WHEEL_DELTA; + OutputDebug( L"mousewheel: wParam: %d delta: %d\n", + GET_WHEEL_DELTA_WPARAM(wParam), delta ); + if( g_Zoomed ) { + + if( g_TypeMode == TypeModeOff ) { + + if( g_Drawing && (LOWORD( wParam ) & MK_CONTROL) ) { + + ResizePen( hWnd, hdcScreenCompat, hdcScreenCursorCompat, prevPt, + g_Tracing, &g_Drawing, g_LiveZoomLevel, TRUE, g_PenWidth + delta ); + + // Perform static zoom unless in liveDraw + } else if( !( GetWindowLongPtr( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED ) ) { + + if( delta > 0 ) zoomIn = TRUE; + else { + zoomIn = FALSE; + delta = -delta; + } + while( delta-- ) { + + if( zoomIn ) { + + if( zoomTelescopeTarget < ZOOM_LEVEL_MAX ) { + + if( zoomTelescopeTarget < 2 ) { + + zoomTelescopeTarget = 2; + + } else { + + // Start telescoping zoom + zoomTelescopeTarget = zoomTelescopeTarget * 2; + } + zoomTelescopeStep = ZOOM_LEVEL_STEP_IN; + if( g_AnimateZoom ) + zoomLevel *= zoomTelescopeStep; + else + zoomLevel = zoomTelescopeTarget; + + if( zoomLevel > zoomTelescopeTarget ) + zoomLevel = zoomTelescopeTarget; + else + SetTimer( hWnd, 1, ZOOM_LEVEL_STEP_TIME, NULL ); + } + + } else if( zoomTelescopeTarget > ZOOM_LEVEL_MIN ) { + + // Let them more gradually zoom out from 2x to 1x + if( zoomTelescopeTarget <= 2 ) { + + zoomTelescopeTarget *= .75; + if( zoomTelescopeTarget < ZOOM_LEVEL_MIN ) + zoomTelescopeTarget = ZOOM_LEVEL_MIN; + + } else { + + zoomTelescopeTarget = zoomTelescopeTarget/2; + } + zoomTelescopeStep = ZOOM_LEVEL_STEP_OUT; + if( g_AnimateZoom ) + zoomLevel *= zoomTelescopeStep; + else + zoomLevel = zoomTelescopeTarget; + + if( zoomLevel < zoomTelescopeTarget ) + { + zoomLevel = zoomTelescopeTarget; + // Force update on final step out + InvalidateRect( hWnd, NULL, FALSE ); + } + else + { + SetTimer( hWnd, 1, ZOOM_LEVEL_STEP_TIME, NULL ); + } + } + } + if( zoomLevel != zoomTelescopeTarget ) { + + if( g_Drawing ) { + + if( !g_Tracing ) { + + RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt ); + } + //SetCursorPos( monInfo.rcMonitor.left + cursorPos.x, + // monInfo.rcMonitor.top + cursorPos.y ); + } + InvalidateRect( hWnd, NULL, FALSE ); + } + } + } else { + + // Resize the text font + if( (delta > 0 && g_FontScale > -20) || (delta < 0 && g_FontScale < 50 )) { + + ClearTypingCursor(hdcScreenCompat, hdcScreenCursorCompat, cursorRc, g_BlankedScreen); + + g_FontScale -= delta; + if( g_FontScale == 0 ) g_FontScale = 1; + // Set lParam to 0 as part of message to keyup hander + DeleteObject(hTypingFont); + g_LogFont.lfHeight = max((int)(height / zoomLevel) / g_FontScale, 12); + if (g_LogFont.lfHeight < 20) + g_LogFont.lfQuality = NONANTIALIASED_QUALITY; + else + g_LogFont.lfQuality = ANTIALIASED_QUALITY; + hTypingFont = CreateFontIndirect(&g_LogFont); + SelectObject(hdcScreenCompat, hTypingFont); + + DrawTypingCursor( hWnd, &textPt, hdcScreenCompat, hdcScreenCursorCompat, &cursorRc ); + } + } + } else if( g_TimerActive && (breakTimeout > 0 || delta )) { + + if( delta ) { + + if( breakTimeout < 0 ) breakTimeout = 0; + if( breakTimeout % 60 ) { + breakTimeout += (60 - breakTimeout % 60); + delta--; + } + breakTimeout += delta * 60; + + } else { + + if( breakTimeout % 60 ) { + breakTimeout -= breakTimeout % 60; + delta--; + } + breakTimeout -= delta * 60; + } + if( breakTimeout < 0 ) breakTimeout = 0; + KillTimer( hWnd, 0 ); + SetTimer( hWnd, 0, 1000, NULL ); + InvalidateRect( hWnd, NULL, TRUE ); + } + + if( zoomLevel != 1 && g_Drawing ) { + + // Constrain the mouse to the visible region + boundRc = BoundMouse( zoomTelescopeTarget, &monInfo, width, height, &cursorPos ); + + } else { + + ClipCursor( NULL ); + } + return TRUE; + + case WM_IME_CHAR: + case WM_CHAR: + + if( (g_TypeMode != TypeModeOff) && iswprint(static_cast(wParam)) || (static_cast(wParam) == L'&')) { + g_HaveTyped = TRUE; + + TCHAR vKey = static_cast(wParam); + + g_HaveDrawn = TRUE; + + // Clear typing cursor + rc.left = textPt.x; + rc.top = textPt.y; + ClearTypingCursor( hdcScreenCompat, hdcScreenCursorCompat, cursorRc, g_BlankedScreen ); + if (g_TypeMode == TypeModeRightJustify) { + + if( !g_TextBuffer.empty() || !g_TextBufferPreviousLines.empty() ) { + + PopDrawUndo(hdcScreenCompat, &drawUndoList, width, height); //*** + } + PushDrawUndo(hdcScreenCompat, &drawUndoList, width, height); + + // Restore previous lines. + wParam = 'X'; + DrawText(hdcScreenCompat, reinterpret_cast(&wParam), 1, &rc, DT_CALCRECT); + const auto lineHeight = rc.bottom - rc.top; + + rc.top -= static_cast< LONG >( g_TextBufferPreviousLines.size() ) * lineHeight; + + // Draw the current character on the current line. + g_TextBuffer += vKey; + drawAllRightJustifiedLines( lineHeight ); + } + else { + DrawText( hdcScreenCompat, &vKey, 1, &rc, DT_CALCRECT|DT_NOPREFIX); + DrawText( hdcScreenCompat, &vKey, 1, &rc, DT_LEFT|DT_NOPREFIX); + textPt.x += rc.right - rc.left; + } + InvalidateRect( hWnd, NULL, TRUE ); + + // Save the key for undo + P_TYPED_KEY newKey = static_cast(malloc( sizeof(TYPED_KEY) )); + newKey->rc = rc; + newKey->Next = typedKeyList; + typedKeyList = newKey; + + // Draw the typing cursor + DrawTypingCursor( hWnd, &textPt, hdcScreenCompat, hdcScreenCursorCompat, &cursorRc ); + return FALSE; + } + break; + + case WM_KEYUP: + if( wParam == 'T' && (g_TypeMode == TypeModeOff)) { + + // lParam is 0 when we're resizing the font and so don't have a cursor that + // we need to restore + if( !g_Drawing && lParam == 0 ) { + + OutputDebug(L"Entering typing mode and resetting cursor position\n"); + SendMessage( hWnd, WM_LBUTTONDOWN, 0, MAKELPARAM( cursorPos.x, cursorPos.y)); + } + + // Do they want to right-justify text? + OutputDebug(L"Keyup Shift: %x\n", GetAsyncKeyState(VK_SHIFT)); + if(GetAsyncKeyState(VK_SHIFT) != 0 ) { + + g_TypeMode = TypeModeRightJustify; + g_TextBuffer.clear(); + + // Also empty all previous lines + g_TextBufferPreviousLines = {}; + } + else { + + g_TypeMode = TypeModeLeftJustify; + } + textStartPt = cursorPos; + textPt = prevPt; + + g_HaveTyped = FALSE; + + // Get a font of a decent size + DeleteObject( hTypingFont ); + g_LogFont.lfHeight = max( (int) (height / zoomLevel)/g_FontScale, 12 ); + if (g_LogFont.lfHeight < 20) + g_LogFont.lfQuality = NONANTIALIASED_QUALITY; + else + g_LogFont.lfQuality = ANTIALIASED_QUALITY; + hTypingFont = CreateFontIndirect( &g_LogFont ); + SelectObject( hdcScreenCompat, hTypingFont ); + + // If lparam == 0 that means that we sent the message as part of a font resize + if( g_Drawing && lParam != 0) { + + RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt ); + PushDrawUndo( hdcScreenCompat, &drawUndoList, width, height ); + + } else if( !g_Drawing ) { + + textPt = cursorPos; + } + + // Draw the typing cursor + DrawTypingCursor( hWnd, &textPt, hdcScreenCompat, hdcScreenCursorCompat, &cursorRc, true ); + prevPt = textPt; + } + break; + + case WM_KEYDOWN: + + if( (g_TypeMode != TypeModeOff) && g_HaveTyped && static_cast(wParam) != VK_UP && static_cast(wParam) != VK_DOWN && + (isprint( static_cast(wParam)) || + wParam == VK_RETURN || wParam == VK_DELETE || wParam == VK_BACK )) { + + if( wParam == VK_RETURN ) { + + // Clear the typing cursor + ClearTypingCursor( hdcScreenCompat, hdcScreenCursorCompat, cursorRc, g_BlankedScreen ); + + if( g_TypeMode == TypeModeRightJustify ) + { + g_TextBufferPreviousLines.push_back( g_TextBuffer ); + g_TextBuffer.clear(); + } + else + { + // Insert a fake return key in the list to undo. + P_TYPED_KEY newKey = static_cast(malloc(sizeof(TYPED_KEY))); + newKey->rc.left = textPt.x; + newKey->rc.top = textPt.y; + newKey->rc.right = newKey->rc.left; + newKey->rc.bottom = newKey->rc.top; + newKey->Next = typedKeyList; + typedKeyList = newKey; + } + + wParam = 'X'; + DrawText( hdcScreenCompat, reinterpret_cast(&wParam), 1, &rc, DT_CALCRECT ); + textPt.x = prevPt.x; // + g_PenWidth; + textPt.y += rc.bottom - rc.top; + + // Draw the typing cursor + DrawTypingCursor( hWnd, &textPt, hdcScreenCompat, hdcScreenCursorCompat, &cursorRc ); + } else if( wParam == VK_DELETE || wParam == VK_BACK ) { + + P_TYPED_KEY deletedKey = typedKeyList; + if( deletedKey ) { + + // Clear the typing cursor + ClearTypingCursor( hdcScreenCompat, hdcScreenCursorCompat, cursorRc, g_BlankedScreen ); + + if( g_TypeMode == TypeModeRightJustify ) { + + if( !g_TextBuffer.empty() || !g_TextBufferPreviousLines.empty() ) { + + PopDrawUndo( hdcScreenCompat, &drawUndoList, width, height ); + } + PushDrawUndo( hdcScreenCompat, &drawUndoList, width, height ); + + rc.left = textPt.x; + rc.top = textPt.y; + + // Restore the previous lines. + wParam = 'X'; + DrawText( hdcScreenCompat, reinterpret_cast(&wParam), 1, &rc, DT_CALCRECT ); + const auto lineHeight = rc.bottom - rc.top; + + const bool lineWasEmpty = g_TextBuffer.empty(); + drawAllRightJustifiedLines( lineHeight, true ); + if( lineWasEmpty && !g_TextBufferPreviousLines.empty() ) + { + g_TextBuffer = g_TextBufferPreviousLines.back(); + g_TextBufferPreviousLines.pop_back(); + textPt.y -= lineHeight; + } + } + else { + RECT rect = deletedKey->rc; + if (g_BlankedScreen) { + + BlankScreenArea(hdcScreenCompat, &rect, g_BlankedScreen); + } + else { + + BitBlt(hdcScreenCompat, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, hdcScreenSaveCompat, rect.left, rect.top, SRCCOPY | CAPTUREBLT ); + } + InvalidateRect( hWnd, NULL, FALSE ); + + textPt.x = rect.left; + textPt.y = rect.top; + + typedKeyList = deletedKey->Next; + free(deletedKey); + + // Refresh cursor if we deleted the last key + if( typedKeyList == NULL ) { + + SendMessage( hWnd, WM_MOUSEMOVE, 0, MAKELPARAM( prevPt.x, prevPt.y ) ); + } + } + DrawTypingCursor( hWnd, &textPt, hdcScreenCompat, hdcScreenCursorCompat, &cursorRc ); + } + } + break; + } + switch (wParam) { + case 'R': + case 'B': + case 'Y': + case 'O': + case 'G': + case 'X': + case 'P': + if( (g_Zoomed || g_TimerActive) && (g_TypeMode == TypeModeOff)) { + + PDWORD penColor; + if( g_TimerActive ) + penColor = &g_BreakPenColor; + else + penColor = &g_PenColor; + + if( wParam == 'R' ) *penColor = COLOR_RED; + else if( wParam == 'G' ) *penColor = COLOR_GREEN; + else if( wParam == 'B' ) *penColor = COLOR_BLUE; + else if( wParam == 'Y' ) *penColor = COLOR_YELLOW; + else if( wParam == 'O' ) *penColor = COLOR_ORANGE; + else if( wParam == 'P' ) *penColor = COLOR_PINK; + else if( wParam == 'X' ) + { + if( GetWindowLong( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED ) + { + // Blur is not supported in LiveDraw + break; + } + *penColor = COLOR_BLUR; + } + + bool shift = GetKeyState( VK_SHIFT ) & 0x8000; + if( shift && ( GetWindowLong( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED ) ) + { + // Highlight is not supported in LiveDraw + break; + } + + reg.WriteRegSettings( RegSettings ); + DeleteObject( hDrawingPen ); + SetTextColor( hdcScreenCompat, *penColor ); + + // Highlight and blur level + if( shift && *penColor != COLOR_BLUR ) + { + *penColor |= (g_AlphaBlend << 24); + } + else + { + if( *penColor == COLOR_BLUR ) + { + g_BlurRadius = shift ? STRONG_BLUR_RADIUS : NORMAL_BLUR_RADIUS; + } + *penColor |= (0xFF << 24); + } + hDrawingPen = CreatePen(PS_SOLID, g_PenWidth, *penColor & 0xFFFFFF); + + SelectObject( hdcScreenCompat, hDrawingPen ); + if( g_Drawing ) { + + SendMessage( hWnd, WM_MOUSEMOVE, 0, MAKELPARAM( prevPt.x, prevPt.y )); + + } else if( g_TimerActive ) { + + InvalidateRect( hWnd, NULL, FALSE ); + + } else if( g_TypeMode != TypeModeOff ) { + + ClearTypingCursor( hdcScreenCompat, hdcScreenCursorCompat, cursorRc, g_BlankedScreen ); + DrawTypingCursor( hWnd, &textPt, hdcScreenCompat, hdcScreenCursorCompat, &cursorRc ); + InvalidateRect( hWnd, NULL, FALSE ); + } + } + break; + + case 'Z': + if( (GetKeyState( VK_CONTROL ) & 0x8000 ) && g_HaveDrawn && !g_Tracing ) { + + if( PopDrawUndo( hdcScreenCompat, &drawUndoList, width, height )) { + + if( g_Drawing ) { + + SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt ); + SendMessage( hWnd, WM_MOUSEMOVE, 0, MAKELPARAM( prevPt.x, prevPt.y )); + } + else { + + SaveCursorArea(hdcScreenCursorCompat, hdcScreenCompat, prevPt); + } + InvalidateRect( hWnd, NULL, FALSE ); + } + } + break; + + case VK_SPACE: + if( g_Drawing && !g_Tracing ) { + + SetCursorPos( boundRc.left + (boundRc.right - boundRc.left)/2, + boundRc.top + (boundRc.bottom - boundRc.top)/2 ); + SendMessage( hWnd, WM_MOUSEMOVE, 0, + MAKELPARAM( (boundRc.right - boundRc.left)/2, + (boundRc.bottom - boundRc.top)/2 )); + } + break; + + case 'W': + case 'K': + // Block user-driven sketch pad in liveDraw + if( lParam != LIVE_DRAW_ZOOM + && ( GetWindowLongPtr( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED ) ) + { + break; + } + + // Don't allow screen blanking while we've got the typing cursor active + // because we don't really handle going from white to black. + if( g_Zoomed && (g_TypeMode == TypeModeOff)) { + + if( !g_Drawing ) { + + SendMessage( hWnd, WM_LBUTTONDOWN, 0, MAKELPARAM( cursorPos.x, cursorPos.y)); + } + // Restore area where cursor was previously + RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt ); + PushDrawUndo( hdcScreenCompat, &drawUndoList, width, height ); + g_BlankedScreen = static_cast(wParam); + rc.top = rc.left = 0; + rc.bottom = height; + rc.right = width; + BlankScreenArea( hdcScreenCompat, &rc, g_BlankedScreen ); + InvalidateRect( hWnd, NULL, FALSE ); + + // Save area that's going to be occupied by new cursor position + SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt ); + SendMessage( hWnd, WM_MOUSEMOVE, 0, MAKELPARAM( prevPt.x, prevPt.y )); + } + break; + + case 'E': + // Don't allow erase while we have the typing cursor active + if( g_HaveDrawn && (g_TypeMode == TypeModeOff)) { + + DeleteDrawUndoList( &drawUndoList ); + g_HaveDrawn = FALSE; + OutputDebug(L"Erase\n"); + if(GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_LAYERED) { + SendMessage(hWnd, WM_KEYDOWN, 'K', 0); + } + else { + BitBlt(hdcScreenCompat, 0, 0, bmp.bmWidth, + bmp.bmHeight, hdcScreenSaveCompat, 0, 0, SRCCOPY | CAPTUREBLT); + + if (g_Drawing) { + + OutputDebug(L"Erase: draw cursor\n"); + SaveCursorArea(hdcScreenCursorCompat, hdcScreenCompat, prevPt); + DrawCursor(hdcScreenCompat, prevPt, zoomLevel, width, height); + g_HaveDrawn = TRUE; + } + } + InvalidateRect( hWnd, NULL, FALSE ); + g_BlankedScreen = FALSE; + } + break; + + case VK_UP: + SendMessage( hWnd, WM_MOUSEWHEEL, + MAKEWPARAM( GetAsyncKeyState( VK_LCONTROL ) != 0 || GetAsyncKeyState( VK_RCONTROL ) != 0 ? + MK_CONTROL: 0, WHEEL_DELTA), 0 ); + return TRUE; + + case VK_DOWN: + SendMessage( hWnd, WM_MOUSEWHEEL, + MAKEWPARAM( GetAsyncKeyState( VK_LCONTROL ) != 0 || GetAsyncKeyState( VK_RCONTROL ) != 0 ? + MK_CONTROL: 0, -WHEEL_DELTA), 0 ); + return TRUE; + + case VK_LEFT: + case VK_RIGHT: + if( wParam == VK_RIGHT ) delta = 10; + else delta = -10; + if( g_TimerActive && (breakTimeout > 0 || delta )) { + + if( breakTimeout < 0 ) breakTimeout = 0; + breakTimeout += delta; + breakTimeout -= (breakTimeout % 10); + if( breakTimeout < 0 ) breakTimeout = 0; + KillTimer( hWnd, 0 ); + SetTimer( hWnd, 0, 1000, NULL ); + InvalidateRect( hWnd, NULL, TRUE ); + } + break; + + case VK_ESCAPE: + if( g_TypeMode != TypeModeOff) { + + // Turn off + SendMessage( hWnd, WM_USER_TYPING_OFF, 0, 0 ); + + } else { + + forcePenResize = TRUE; + PostMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 ); + + // In case we were in liveDraw + if( GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_LAYERED) { + + KillTimer(hWnd, 3); + LONG_PTR exStyle = GetWindowLongPtr(hWnd, GWL_EXSTYLE); + SetWindowLongPtr(hWnd, GWL_EXSTYLE, exStyle & ~WS_EX_LAYERED); + pMagSetWindowFilterList( g_hWndLiveZoomMag, MW_FILTERMODE_EXCLUDE, 1, &hWnd ); + SendMessage( g_hWndLiveZoom, WM_USER_MAGNIFY_CURSOR, TRUE, 0 ); + } + } + break; + } + return TRUE; + + case WM_RBUTTONDOWN: + SendMessage( hWnd, WM_USER_EXIT_MODE, 0, 0 ); + break; + + case WM_MOUSEMOVE: + OutputDebug(L"MOUSEMOVE: zoomed: %d drawing: %d tracing: %d\n", + g_Zoomed, g_Drawing, g_Tracing); + + OutputDebug(L"Window visible: %d Topmost: %d\n", IsWindowVisible(hWnd), GetWindowLong(hWnd, GWL_EXSTYLE)& WS_EX_TOPMOST); + + if( g_Zoomed && (g_TypeMode == TypeModeOff) && !g_bSaveInProgress ) { + + if( g_Drawing ) { + + POINT currentPt; + + // Are we in pen mode on a tablet? + lParam = ScalePenPosition( zoomLevel, &monInfo, boundRc, message, lParam); + currentPt.x = LOWORD(lParam); + currentPt.y = HIWORD(lParam); + + if(lParam == 0) { + + // Drop it + OutputDebug(L"Mousemove: Dropping\n"); + break; + + } else if(g_DrawingShape) { + + SetROP2(hdcScreenCompat, R2_NOTXORPEN); + + // If a previous target rectangle exists, erase + // it by drawing another rectangle on top. + if( g_rcRectangle.top != g_rcRectangle.bottom || + g_rcRectangle.left != g_rcRectangle.right ) + { + if( prevPenWidth != g_PenWidth ) + { + auto penWidth = g_PenWidth; + g_PenWidth = prevPenWidth; + + auto prevPen = CreatePen( PS_SOLID, g_PenWidth, g_PenColor & 0xFFFFFF ); + SelectObject( hdcScreenCompat, prevPen ); + + DrawShape( g_DrawingShape, hdcScreenCompat, &g_rcRectangle ); + + g_PenWidth = penWidth; + SelectObject( hdcScreenCompat, hDrawingPen ); + DeleteObject( prevPen ); + } + else + { + DrawShape( g_DrawingShape, hdcScreenCompat, &g_rcRectangle ); + } + } + + // Save the coordinates of the target rectangle. + // Avoid invalid rectangles by ensuring that the + // value of the left coordinate is greater than + // that of the right, and that the value of the + // bottom coordinate is greater than that of + // the top. + if( g_DrawingShape == DRAW_LINE || + g_DrawingShape == DRAW_ARROW ) { + + g_rcRectangle.right = static_cast(LOWORD(lParam)); + g_rcRectangle.bottom = static_cast(HIWORD(lParam)); + + } else { + + if ((g_RectangleAnchor.x < currentPt.x) && + (g_RectangleAnchor.y > currentPt.y)) { + + SetRect(&g_rcRectangle, g_RectangleAnchor.x, currentPt.y, + currentPt.x, g_RectangleAnchor.y); + + } else if ((g_RectangleAnchor.x > currentPt.x) && + (g_RectangleAnchor.y > currentPt.y )) { + + SetRect(&g_rcRectangle, currentPt.x, + currentPt.y, g_RectangleAnchor.x,g_RectangleAnchor.y); + + } else if ((g_RectangleAnchor.x > currentPt.x) && + (g_RectangleAnchor.y < currentPt.y )) { + + SetRect(&g_rcRectangle, currentPt.x, g_RectangleAnchor.y, + g_RectangleAnchor.x, currentPt.y ); + } else { + + SetRect(&g_rcRectangle, g_RectangleAnchor.x, g_RectangleAnchor.y, + currentPt.x, currentPt.y ); + } + } + + if (g_rcRectangle.left != g_rcRectangle.right || + g_rcRectangle.top != g_rcRectangle.bottom) { + + // Draw the new target rectangle. + DrawShape(g_DrawingShape, hdcScreenCompat, &g_rcRectangle); + OutputDebug(L"SHAPE: (%d, %d) - (%d, %d)\n", g_rcRectangle.left, g_rcRectangle.top, + g_rcRectangle.right, g_rcRectangle.bottom); + } + + prevPenWidth = g_PenWidth; + SetROP2( hdcScreenCompat, R2_NOP ); + } + else if (g_Tracing) { + + OutputDebug(L"Mousemove: Tracing\n"); + + g_HaveDrawn = TRUE; + Gdiplus::Graphics dstGraphics(hdcScreenCompat); + if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 ) + { + dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + } + Gdiplus::Color color = ColorFromColorRef(g_PenColor); + Gdiplus::Pen pen(color, static_cast(g_PenWidth)); + pen.SetLineCap(Gdiplus::LineCapRound, Gdiplus::LineCapRound, Gdiplus::DashCapRound); + + // If highlighting, use a double layer approach + OutputDebug(L"PenColor: %x\n", g_PenColor); + OutputDebug(L"Blur color: %x\n", COLOR_BLUR); + if( (g_PenColor & 0xFFFFFF) == COLOR_BLUR) { + + OutputDebug(L"BLUR\n"); + + // Restore area where cursor was previously + RestoreCursorArea(hdcScreenCompat, hdcScreenCursorCompat, prevPt); + + // Create a new bitmap that's the size of the area covered by the line + 2 * g_PenWidth + Gdiplus::Rect lineBounds = GetLineBounds( prevPt, currentPt, g_PenWidth ); + Gdiplus::Bitmap* lineBitmap = DrawBitmapLine(lineBounds, prevPt, currentPt, &pen); + Gdiplus::BitmapData* lineData = LockGdiPlusBitmap(lineBitmap); + BYTE* pPixels = static_cast(lineData->Scan0); + + // Copy the contents of the screen bitmap to the temporary bitmap + GetOldestUndo(drawUndoList); + + // Create a GDI bitmap that's the size of the lineBounds rectangle + Gdiplus::Bitmap *blurBitmap = CreateGdiplusBitmap( hdcScreenCompat, // oldestUndo->hDc, + lineBounds.X, lineBounds.Y, lineBounds.Width, lineBounds.Height); + + // Blur it + BitmapBlur(blurBitmap); + BlurScreen(hdcScreenCompat, &lineBounds, blurBitmap, pPixels); + + // Unlock the bits + lineBitmap->UnlockBits(lineData); + delete lineBitmap; + delete blurBitmap; + + // Invalidate the updated rectangle + InvalidateGdiplusRect( hWnd, lineBounds ); + + // Save area that's going to be occupied by new cursor position + SaveCursorArea(hdcScreenCursorCompat, hdcScreenCompat, currentPt); + + // Draw new cursor + DrawCursor(hdcScreenCompat, currentPt, zoomLevel, width, height); + } + else if(PEN_COLOR_HIGHLIGHT(g_PenColor)) { + + // This is a highlighting pen color + Gdiplus::Rect lineBounds = GetLineBounds(prevPt, currentPt, g_PenWidth); + Gdiplus::Bitmap* lineBitmap = DrawBitmapLine(lineBounds, prevPt, currentPt, &pen); + Gdiplus::BitmapData* lineData = LockGdiPlusBitmap(lineBitmap); + BYTE* pPixels = static_cast(lineData->Scan0); + + // Create a DIB section for efficient pixel manipulation + BITMAPINFO bmi = { 0 }; + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = lineBounds.Width; + bmi.bmiHeader.biHeight = -lineBounds.Height; // Top-down DIB + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; // 32 bits per pixel + bmi.bmiHeader.biCompression = BI_RGB; + + VOID* pDIBBits; + HBITMAP hDIB = CreateDIBSection(hdcScreenCompat, &bmi, DIB_RGB_COLORS, &pDIBBits, NULL, 0); + + HDC hdcDIB = CreateCompatibleDC(hdcScreenCompat); + SelectObject(hdcDIB, hDIB); + + // Copy the relevant part of hdcScreenCompat to the DIB + BitBlt(hdcDIB, 0, 0, lineBounds.Width, lineBounds.Height, hdcScreenCompat, lineBounds.X, lineBounds.Y, SRCCOPY); + + // Pointer to the DIB bits + BYTE* pDestPixels = static_cast(pDIBBits); + + // Pointer to screen bits + HDC hdcDIBOrig; + HBITMAP hDibOrigBitmap, hDibBitmap; + P_DRAW_UNDO oldestUndo = GetOldestUndo(drawUndoList); + BYTE* pDestPixels2 = CreateBitmapMemoryDIB(hdcScreenCompat, oldestUndo->hDc, &lineBounds, + &hdcDIBOrig, &hDibBitmap, &hDibOrigBitmap); + + for (int local_y = 0; local_y < lineBounds.Height; ++local_y) { + for (int local_x = 0; local_x < lineBounds.Width; ++local_x) { + int index = (local_y * lineBounds.Width * 4) + (local_x * 4); // Assuming 4 bytes per pixel + // BYTE b = pPixels[index + 0]; // Blue channel + // BYTE g = pPixels[index + 1]; // Green channel + // BYTE r = pPixels[index + 2]; // Red channel + BYTE a = pPixels[index + 3]; // Alpha channel + + // Check if this is a drawn pixel + if (a != 0) { + // Assuming pDestPixels is a valid pointer to the destination bitmap's pixel data + BYTE destB = pDestPixels2[index + 0]; // Blue channel + BYTE destG = pDestPixels2[index + 1]; // Green channel + BYTE destR = pDestPixels2[index + 2]; // Red channel + + // Create a COLORREF value from the destination pixel data + COLORREF currentPixel = RGB(destR, destG, destB); + // Blend the colors + COLORREF newPixel = BlendColors(currentPixel, g_PenColor); + // Update the destination pixel data with the new color + pDestPixels[index + 0] = GetBValue(newPixel); + pDestPixels[index + 1] = GetGValue(newPixel); + pDestPixels[index + 2] = GetRValue(newPixel); + } + } + } + + // Copy the updated DIB back to hdcScreenCompat + BitBlt(hdcScreenCompat, lineBounds.X, lineBounds.Y, lineBounds.Width, lineBounds.Height, hdcDIB, 0, 0, SRCCOPY); + + // Clean up + DeleteObject(hDIB); + DeleteDC(hdcDIB); + + SelectObject(hdcDIBOrig, hDibOrigBitmap); + DeleteObject(hDibBitmap); + DeleteDC(hdcDIBOrig); + + // Invalidate the updated rectangle + InvalidateGdiplusRect(hWnd, lineBounds); + } + else { + + // Normal tracing + dstGraphics.DrawLine(&pen, static_cast(prevPt.x), static_cast(prevPt.y), + static_cast(currentPt.x), static_cast(currentPt.y)); + } + + } else { + + OutputDebug(L"Mousemove: Moving cursor\n"); + + // Restore area where cursor was previously + RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt ); + + // Save area that's going to be occupied by new cursor position + SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, currentPt ); + + // Draw new cursor + DrawCursor( hdcScreenCompat, currentPt, zoomLevel, width, height ); + } + + if( g_DrawingShape ) { + + InvalidateRect( hWnd, NULL, FALSE ); + + } else { + + // Invalidate area just modified + InvalidateCursorMoveArea( hWnd, zoomLevel, width, height, currentPt, prevPt, cursorPos ); + } + prevPt = currentPt; + + // In liveDraw we an miss the mouse up + if( GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_LAYERED) { + + if((GetAsyncKeyState(VK_LBUTTON) & 0x8000) == 0) { + + OutputDebug(L"LIVE_DRAW missed mouse up. Sending synthetic.\n"); + SendMessage(hWnd, WM_LBUTTONUP, wParam, lParam); + } + } + + } else { + + cursorPos.x = LOWORD( lParam ); + cursorPos.y = HIWORD( lParam ); + InvalidateRect( hWnd, NULL, FALSE ); + } + } else if( g_Zoomed && (g_TypeMode != TypeModeOff) && !g_HaveTyped ) { + + ClearTypingCursor( hdcScreenCompat, hdcScreenCursorCompat, cursorRc, g_BlankedScreen ); + textPt.x = prevPt.x = LOWORD( lParam ); + textPt.y = prevPt.y = HIWORD( lParam ); + + // Draw the typing cursor + DrawTypingCursor( hWnd, &textPt, hdcScreenCompat, hdcScreenCursorCompat, &cursorRc, true ); + prevPt = textPt; + InvalidateRect( hWnd, NULL, FALSE ); + } +#if 0 + { + static int index = 0; + OutputDebug( L"%d: foreground: %x focus: %x (hwnd: %x)\n", + index++, (DWORD) PtrToUlong(GetForegroundWindow()), PtrToUlong(GetFocus()), PtrToUlong(hWnd)); + } +#endif + return TRUE; + + case WM_LBUTTONDOWN: + g_StraightDirection = 0; + + if( g_Zoomed && (g_TypeMode == TypeModeOff) && zoomTelescopeTarget == zoomLevel ) { + + OutputDebug(L"LBUTTONDOWN: drawing\n"); + + // Save current bitmap to undo history + if( g_HaveDrawn ) { + + RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt ); + } + + // don't push undo if we sent this to ourselves for a pen resize + if( wParam != -1 ) { + + PushDrawUndo( hdcScreenCompat, &drawUndoList, width, height ); + + } else { + + wParam = 0; + } + + // Are we in pen mode on a tablet? + lParam = ScalePenPosition( zoomLevel, &monInfo, boundRc, + message, lParam); + + if (lParam == 0) { + + // Drop it + break; + + } else if( g_Drawing ) { + + // is the user drawing a rectangle? + if( wParam & MK_CONTROL || + wParam & MK_SHIFT || + GetKeyState( VK_TAB ) < 0 ) { + + // Restore area where cursor was previously + RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt ); + + if( wParam & MK_SHIFT && wParam & MK_CONTROL ) + g_DrawingShape = DRAW_ARROW; + else if( wParam & MK_CONTROL ) + g_DrawingShape = DRAW_RECTANGLE; + else if( wParam & MK_SHIFT ) + g_DrawingShape = DRAW_LINE; + else + g_DrawingShape = DRAW_ELLIPSE; + g_RectangleAnchor.x = LOWORD(lParam); + g_RectangleAnchor.y = HIWORD(lParam); + SetRect(&g_rcRectangle, g_RectangleAnchor.x, g_RectangleAnchor.y, + g_RectangleAnchor.x, g_RectangleAnchor.y); + + } else { + + Gdiplus::Graphics dstGraphics(hdcScreenCompat); + if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 ) + { + dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + } + Gdiplus::Color color = ColorFromColorRef(g_PenColor); + Gdiplus::Pen pen(color, static_cast(g_PenWidth)); + Gdiplus::GraphicsPath path; + pen.SetLineJoin(Gdiplus::LineJoinRound); + path.AddLine(prevPt.x, prevPt.y, prevPt.x, prevPt.y); + dstGraphics.DrawPath(&pen, &path); + } + g_Tracing = TRUE; + SetROP2( hdcScreenCompat, R2_COPYPEN ); + prevPt.x = LOWORD(lParam); + prevPt.y = HIWORD(lParam); + g_HaveDrawn = TRUE; + + } else { + + OutputDebug(L"Tracing on\n"); + + // Turn on drawing + if( !g_HaveDrawn ) { + + // refresh drawing bitmap with original screen image + BitBlt(hdcScreenCompat, 0, 0, bmp.bmWidth, + bmp.bmHeight, hdcScreenSaveCompat, 0, 0, SRCCOPY|CAPTUREBLT ); + g_HaveDrawn = TRUE; + } + DeleteObject( hDrawingPen ); + hDrawingPen = CreatePen(PS_SOLID, g_PenWidth, g_PenColor & 0xFFFFFF); + SelectObject( hdcScreenCompat, hDrawingPen ); + + // is the user drawing a rectangle? + if( wParam & MK_CONTROL && g_Drawing ) { + + // Restore area where cursor was previously + RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt ); + + // Configure rectangle drawing + g_DrawingShape = TRUE; + g_RectangleAnchor.x = LOWORD(lParam); + g_RectangleAnchor.y = HIWORD(lParam); + SetRect(&g_rcRectangle, g_RectangleAnchor.x, g_RectangleAnchor.y, + g_RectangleAnchor.x, g_RectangleAnchor.y); + OutputDebug( L"RECTANGLE: %d, %d\n", prevPt.x, prevPt.y ); + + } else { + + prevPt.x = LOWORD( lParam ); + prevPt.y = HIWORD( lParam ); + SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt ); + + Gdiplus::Graphics dstGraphics(hdcScreenCursorCompat); + if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 ) + { + dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + } + Gdiplus::Color color = ColorFromColorRef(g_PenColor); + Gdiplus::Pen pen(color, static_cast(g_PenWidth)); + Gdiplus::GraphicsPath path; + pen.SetLineJoin(Gdiplus::LineJoinRound); + path.AddLine(prevPt.x, prevPt.y, prevPt.x, prevPt.y); + dstGraphics.DrawPath(&pen, &path); + } + InvalidateRect( hWnd, NULL, FALSE ); + + // If we're in live zoom, make the drawing pen larger to compensate + if( g_ZoomOnLiveZoom && forcePenResize ) + { + forcePenResize = FALSE; + + ResizePen( hWnd, hdcScreenCompat, hdcScreenCursorCompat, prevPt, g_Tracing, + &g_Drawing, g_LiveZoomLevel, FALSE, min( static_cast(g_LiveZoomLevel * g_RootPenWidth), + static_cast(g_LiveZoomLevel * MAX_PEN_WIDTH) ) ); + OutputDebug( L"LIVEZOOM_DRAW: zoomLevel: %d rootPenWidth: %d penWidth: %d\n", + static_cast(g_LiveZoomLevel), g_RootPenWidth, g_PenWidth ); + } + else if( !g_ZoomOnLiveZoom && forcePenResize ) + { + forcePenResize = FALSE; + // Scale pen down to root for regular drawing mode + ResizePen( hWnd, hdcScreenCompat, hdcScreenCursorCompat, prevPt, g_Tracing, + &g_Drawing, g_LiveZoomLevel, FALSE, g_RootPenWidth ); + } + g_Drawing = TRUE; + + EnableDisableStickyKeys( FALSE ); + OutputDebug( L"LBUTTONDOWN: %d, %d\n", prevPt.x, prevPt.y ); + + // Constrain the mouse to the visible region + boundRc = BoundMouse( zoomLevel, &monInfo, width, height, &cursorPos ); + } + } else if( g_TypeMode != TypeModeOff ) { + + if( !g_HaveTyped ) { + + g_HaveTyped = TRUE; + + } else { + + SendMessage( hWnd, WM_USER_TYPING_OFF, 0, 0 ); + } + } + return TRUE; + + case WM_LBUTTONUP: + OutputDebug(L"LBUTTONUP: zoomed: %d drawing: %d tracing: %d\n", + g_Zoomed, g_Drawing, g_Tracing); + + if( g_Zoomed && g_Drawing && g_Tracing ) { + + // Are we in pen mode on a tablet? + lParam = ScalePenPosition( zoomLevel, &monInfo, boundRc, + message, lParam); + OutputDebug(L"LBUTTONUP: %d, %d\n", LOWORD(lParam), HIWORD(lParam)); + if (lParam == 0) { + + // Drop it + break; + } + + POINT adjustPos; + adjustPos.x = LOWORD(lParam); + adjustPos.y = HIWORD(lParam); + if( g_StraightDirection == -1 ) { + + adjustPos.x = prevPt.x; + + } else { + + adjustPos.y = prevPt.y; + } + lParam = MAKELPARAM( adjustPos.x, adjustPos.y ); + + if( !g_DrawingShape ) { + + Gdiplus::Graphics dstGraphics(hdcScreenCompat); + if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 ) + { + dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + } + Gdiplus::Color color = ColorFromColorRef(g_PenColor); + Gdiplus::Pen pen(color, static_cast(g_PenWidth)); + Gdiplus::GraphicsPath path; + pen.SetLineJoin(Gdiplus::LineJoinRound); + path.AddLine(prevPt.x, prevPt.y, LOWORD(lParam), HIWORD(lParam)); + dstGraphics.DrawPath(&pen, &path); + + prevPt.x = LOWORD( lParam ); + prevPt.y = HIWORD( lParam ); + + if ((g_PenColor & 0xFFFFFF) == COLOR_BLUR) { + + RestoreCursorArea(hdcScreenCompat, hdcScreenCursorCompat, prevPt); + } + SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt ); + DrawCursor( hdcScreenCompat, prevPt, zoomLevel, width, height ); + + } else if (g_rcRectangle.top != g_rcRectangle.bottom || + g_rcRectangle.left != g_rcRectangle.right ) { + + // erase previous + SetROP2(hdcScreenCompat, R2_NOTXORPEN); + DrawShape( g_DrawingShape, hdcScreenCompat, &g_rcRectangle ); + + // Draw the final shape + HBRUSH hBrush = static_cast(GetStockObject( NULL_BRUSH )); + HBRUSH oldHbrush = static_cast(SelectObject( hdcScreenCompat, hBrush )); + SetROP2( hdcScreenCompat, R2_COPYPEN ); + + // smooth line + if( g_SnapToGrid ) { + + if( g_DrawingShape == DRAW_LINE || + g_DrawingShape == DRAW_ARROW ) { + + if( abs(g_rcRectangle.bottom - g_rcRectangle.top) < + abs(g_rcRectangle.right - g_rcRectangle.left)/10 ) { + + g_rcRectangle.bottom = g_rcRectangle.top-1; + } + if( abs(g_rcRectangle.right - g_rcRectangle.left) < + abs(g_rcRectangle.bottom - g_rcRectangle.top)/10 ) { + + g_rcRectangle.right = g_rcRectangle.left-1; + } + } + } + // Draw final one using Gdi+ + DrawShape( g_DrawingShape, hdcScreenCompat, &g_rcRectangle, true ); + + InvalidateRect( hWnd, NULL, FALSE ); + DeleteObject( hBrush ); + SelectObject( hdcScreenCompat, oldHbrush ); + + prevPt.x = LOWORD( lParam ); + prevPt.y = HIWORD( lParam ); + SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt ); + } + g_Tracing = FALSE; + g_DrawingShape = FALSE; + OutputDebug( L"LBUTTONUP:" ); + } + return TRUE; + + case WM_GETMINMAXINFO: + + reinterpret_cast(lParam)->ptMaxSize.x = width; + reinterpret_cast(lParam)->ptMaxSize.y = height; + reinterpret_cast(lParam)->ptMaxPosition.x = 0; + reinterpret_cast(lParam)->ptMaxPosition.y = 0; + return TRUE; + + case WM_USER_TYPING_OFF: { + + if( g_TypeMode != TypeModeOff ) { + + g_TypeMode = TypeModeOff; + ClearTypingCursor( hdcScreenCompat, hdcScreenCursorCompat, cursorRc, g_BlankedScreen ); + InvalidateRect( hWnd, NULL, FALSE ); + DeleteTypedText( &typedKeyList ); + + // 1 means don't reset the cursor. We get that for font resizing + // Only move the cursor if we're drawing, because otherwise the screen moves to center + // on the new cursor position + if( wParam != 1 && g_Drawing ) { + + prevPt.x = cursorRc.left; + prevPt.y = cursorRc.top; + SetCursorPos( monInfo.rcMonitor.left + prevPt.x, + monInfo.rcMonitor.top + prevPt.y ); + + SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt ); + SendMessage( hWnd, WM_MOUSEMOVE, 0, MAKELPARAM( prevPt.x, prevPt.y )); + + } else if( !g_Drawing) { + + // FIX: would be nice to reset cursor so screen doesn't move + prevPt = textStartPt; + SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt ); + SetCursorPos( prevPt.x, prevPt.y ); + SendMessage( hWnd, WM_MOUSEMOVE, 0, MAKELPARAM( prevPt.x, prevPt.y )); + } + } + } + return TRUE; + + case WM_USER_TRAY_ACTIVATE: + + switch( lParam ) { + case WM_RBUTTONUP: + case WM_LBUTTONUP: + case WM_CONTEXTMENU: + { + // Set the foreground window so the menu can be closed by clicking elsewhere when + // opened via right click, and so keyboard navigation works when opened with the menu + // key or Shift-F10. + SetForegroundWindow( hWndOptions ? hWndOptions : hWnd ); + + // Pop up context menu + POINT pt; + GetCursorPos( &pt ); + hPopupMenu = CreatePopupMenu(); + if(!g_StartedByPowerToys) { + // Exiting will happen through disabling in PowerToys, not the context menu. + InsertMenu( hPopupMenu, 0, MF_BYPOSITION, IDCANCEL, L"E&xit" ); + InsertMenu( hPopupMenu, 0, MF_BYPOSITION|MF_SEPARATOR, 0, NULL ); + } + InsertMenu( hPopupMenu, 0, MF_BYPOSITION | ( g_RecordToggle ? MF_CHECKED : 0 ), IDC_RECORD, L"&Record" ); + InsertMenu( hPopupMenu, 0, MF_BYPOSITION, IDC_ZOOM, L"&Zoom" ); + InsertMenu( hPopupMenu, 0, MF_BYPOSITION, IDC_DRAW, L"&Draw" ); + InsertMenu( hPopupMenu, 0, MF_BYPOSITION, IDC_BREAK, L"&Break Timer" ); + if(!g_StartedByPowerToys) { + // When started by PowerToys, options are configured through the PowerToys Settings. + InsertMenu( hPopupMenu, 0, MF_BYPOSITION|MF_SEPARATOR, 0, NULL ); + InsertMenu( hPopupMenu, 0, MF_BYPOSITION, IDC_OPTIONS, L"&Options" ); + } + TrackPopupMenu( hPopupMenu, 0, pt.x , pt.y, 0, hWnd, NULL ); + DestroyMenu( hPopupMenu ); + break; + } + case WM_LBUTTONDBLCLK: + if( !g_TimerActive ) { + + SendMessage( hWnd, WM_COMMAND, IDC_OPTIONS, 0 ); + + } else { + + SetForegroundWindow( hWnd ); + } + break; + } + break; + + case WM_USER_STOP_RECORDING: + StopRecording(); + break; + + case WM_USER_SAVE_CURSOR: + if( g_Zoomed == TRUE ) + { + GetCursorPos( &savedCursorPos ); + if( g_Drawing == TRUE ) + { + ClipCursor( NULL ); + } + } + break; + + case WM_USER_RESTORE_CURSOR: + if( g_Zoomed == TRUE ) + { + if( g_Drawing == TRUE ) + { + boundRc = BoundMouse( zoomLevel, &monInfo, width, height, &cursorPos ); + } + SetCursorPos( savedCursorPos.x, savedCursorPos.y ); + } + break; + + case WM_USER_EXIT_MODE: + if( g_Zoomed ) + { + // Turn off + if( g_TypeMode != TypeModeOff ) + { + SendMessage( hWnd, WM_USER_TYPING_OFF, 0, 0 ); + } + else if( !g_Drawing ) + { + // Turn off + PostMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 ); + } + else + { + if( !g_Tracing ) + { + RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt ); + + // Ensure the cursor area is painted before returning + InvalidateRect( hWnd, NULL, FALSE ); + UpdateWindow( hWnd ); + + // Make the magnified cursor visible again if LiveDraw is on in LiveZoom + if( GetWindowLong( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED ) + { + if( IsWindowVisible( g_hWndLiveZoom ) ) + { + SendMessage( g_hWndLiveZoom, WM_USER_MAGNIFY_CURSOR, TRUE, 0 ); + } + } + } + if( zoomLevel != 1 ) + { + // Restore the cursor position to prevent moving the view in static zoom + SetCursorPos( monInfo.rcMonitor.left + cursorPos.x, monInfo.rcMonitor.top + cursorPos.y ); + } + g_Drawing = FALSE; + g_Tracing = FALSE; + EnableDisableStickyKeys( TRUE ); + SendMessage( hWnd, WM_USER_TYPING_OFF, 0, 0 ); + + // Unclip cursor + ClipCursor( NULL ); + } + } + else if( g_TimerActive ) + { + // Turn off + PostMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 ); + } + break; + + case WM_USER_RELOAD_SETTINGS: + { + // Reload the settings. This message is called from PowerToys after a setting is changed by the user. + reg.ReadRegSettings(RegSettings); + + // Apply tray icon setting + EnableDisableTrayIcon(hWnd, g_ShowTrayIcon); + + // Apply hotkey settings + UnregisterAllHotkeys(hWnd); + g_ToggleMod = GetKeyMod(g_ToggleKey); + g_LiveZoomToggleMod = GetKeyMod(g_LiveZoomToggleKey); + g_DrawToggleMod = GetKeyMod(g_DrawToggleKey); + g_BreakToggleMod = GetKeyMod(g_BreakToggleKey); + g_DemoTypeToggleMod = GetKeyMod(g_DemoTypeToggleKey); + g_SnipToggleMod = GetKeyMod(g_SnipToggleKey); + g_RecordToggleMod = GetKeyMod(g_RecordToggleKey); + BOOL showOptions = FALSE; + if (g_ToggleKey) + { + if (!RegisterHotKey(hWnd, ZOOM_HOTKEY, g_ToggleMod, g_ToggleKey & 0xFF)) + { + MessageBox(hWnd, L"The specified zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.", APPNAME, MB_ICONERROR); + showOptions = TRUE; + } + } + if (g_LiveZoomToggleKey) + { + if (!RegisterHotKey(hWnd, LIVE_HOTKEY, g_LiveZoomToggleMod, g_LiveZoomToggleKey & 0xFF) || + !RegisterHotKey(hWnd, LIVE_DRAW_HOTKEY, g_LiveZoomToggleMod ^ MOD_SHIFT, g_LiveZoomToggleKey & 0xFF)) + { + MessageBox(hWnd, L"The specified live-zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.", APPNAME, MB_ICONERROR); + showOptions = TRUE; + } + } + if (g_DrawToggleKey) + { + if (!RegisterHotKey(hWnd, DRAW_HOTKEY, g_DrawToggleMod, g_DrawToggleKey & 0xFF)) + { + MessageBox(hWnd, L"The specified draw w/out zoom hotkey is already in use.\nSelect a different draw w/out zoom hotkey.", APPNAME, MB_ICONERROR); + showOptions = TRUE; + } + } + if (g_BreakToggleKey) + { + if (!RegisterHotKey(hWnd, BREAK_HOTKEY, g_BreakToggleMod, g_BreakToggleKey & 0xFF)) + { + MessageBox(hWnd, L"The specified break timer hotkey is already in use.\nSelect a different break timer hotkey.", APPNAME, MB_ICONERROR); + showOptions = TRUE; + } + } + if (g_DemoTypeToggleKey) + { + if (!RegisterHotKey(hWnd, DEMOTYPE_HOTKEY, g_DemoTypeToggleMod, g_DemoTypeToggleKey & 0xFF) || + !RegisterHotKey(hWnd, DEMOTYPE_RESET_HOTKEY, (g_DemoTypeToggleMod ^ MOD_SHIFT), g_DemoTypeToggleKey & 0xFF)) + { + MessageBox(hWnd, L"The specified live-type hotkey is already in use.\nSelect a different live-type hotkey.", APPNAME, MB_ICONERROR); + showOptions = TRUE; + } + } + if (g_SnipToggleKey) + { + if (!RegisterHotKey(hWnd, SNIP_HOTKEY, g_SnipToggleMod, g_SnipToggleKey & 0xFF) || + !RegisterHotKey(hWnd, SNIP_SAVE_HOTKEY, (g_SnipToggleMod ^ MOD_SHIFT), g_SnipToggleKey & 0xFF)) + { + MessageBox(hWnd, L"The specified snip hotkey is already in use.\nSelect a different snip hotkey.", APPNAME, MB_ICONERROR); + showOptions = TRUE; + } + } + if (g_RecordToggleKey) + { + if (!RegisterHotKey(hWnd, RECORD_HOTKEY, g_RecordToggleMod | MOD_NOREPEAT, g_RecordToggleKey & 0xFF) || + !RegisterHotKey(hWnd, RECORD_CROP_HOTKEY, (g_RecordToggleMod ^ MOD_SHIFT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF) || + !RegisterHotKey(hWnd, RECORD_WINDOW_HOTKEY, (g_RecordToggleMod ^ MOD_ALT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF)) + { + MessageBox(hWnd, L"The specified record hotkey is already in use.\nSelect a different record hotkey.", APPNAME, MB_ICONERROR); + showOptions = TRUE; + } + } + if (showOptions) + { + // To open the PowerToys settings in the ZoomIt page. + SendMessage(hWnd, WM_COMMAND, IDC_OPTIONS, 0); + } + break; + } + case WM_COMMAND: + + switch(LOWORD( wParam )) { + + case IDC_SAVE_CROP: + case IDC_SAVE: + { + POINT local_savedCursorPos{}; + if( lParam != SHALLOW_ZOOM ) + { + GetCursorPos(&local_savedCursorPos); + } + + HBITMAP hInterimSaveBitmap; + HDC hInterimSaveDc; + HBITMAP hSaveBitmap; + HDC hSaveDc; + int copyX, copyY; + int copyWidth, copyHeight; + + if ( LOWORD( wParam ) == IDC_SAVE_CROP ) + { + g_RecordCropping = TRUE; + SelectRectangle selectRectangle; + if( !selectRectangle.Start( hWnd ) ) + { + g_RecordCropping = FALSE; + if( lParam != SHALLOW_ZOOM ) + { + SetCursorPos(local_savedCursorPos.x, local_savedCursorPos.y); + } + break; + } + auto copyRc = selectRectangle.SelectedRect(); + selectRectangle.Stop(); + g_RecordCropping = FALSE; + copyX = copyRc.left; + copyY = copyRc.top; + copyWidth = copyRc.right - copyRc.left; + copyHeight = copyRc.bottom - copyRc.top; + } + else + { + copyX = 0; + copyY = 0; + copyWidth = width; + copyHeight = height; + } + OutputDebug( L"***x: %d, y: %d, width: %d, height: %d\n", copyX, copyY, copyWidth, copyHeight ); + + RECT oldClipRect{}; + GetClipCursor( &oldClipRect ); + ClipCursor( NULL ); + + // Capture the screen before displaying the save dialog + hInterimSaveDc = CreateCompatibleDC( hdcScreen ); + hInterimSaveBitmap = CreateCompatibleBitmap( hdcScreen, copyWidth, copyHeight ); + SelectObject( hInterimSaveDc, hInterimSaveBitmap ); + + hSaveDc = CreateCompatibleDC( hdcScreen ); +#if SCALE_HALFTONE + SetStretchBltMode( hInterimSaveDc, HALFTONE ); + SetStretchBltMode( hSaveDc, HALFTONE ); +#else + SetStretchBltMode( hInterimSaveDc, COLORONCOLOR ); + SetStretchBltMode( hSaveDc, COLORONCOLOR ); +#endif + StretchBlt( hInterimSaveDc, + 0, 0, + copyWidth, copyHeight, + hdcScreen, + monInfo.rcMonitor.left + copyX, + monInfo.rcMonitor.top + copyY, + copyWidth, copyHeight, + SRCCOPY|CAPTUREBLT ); + + g_bSaveInProgress = true; + memset( &openFileName, 0, sizeof(openFileName )); + openFileName.lStructSize = OPENFILENAME_SIZE_VERSION_400; + openFileName.hwndOwner = hWnd; + openFileName.hInstance = static_cast(g_hInstance); + openFileName.nMaxFile = sizeof(filePath)/sizeof(filePath[0]); + openFileName.Flags = OFN_LONGNAMES|OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT; + openFileName.lpstrTitle = L"Save zoomed screen..."; + openFileName.lpstrDefExt = NULL; // "*.png"; + openFileName.nFilterIndex = 1; + openFileName.lpstrFilter = L"Zoomed PNG\0*.png\0" + //"Zoomed BMP\0*.bmp\0" + "Actual size PNG\0*.png\0\0"; + //"Actual size BMP\0*.bmp\0\0"; + openFileName.lpstrFile = filePath; + if( GetSaveFileName( &openFileName ) ) + { + TCHAR targetFilePath[MAX_PATH]; + _tcscpy( targetFilePath, filePath ); + if( !_tcsrchr( targetFilePath, '.' ) ) + { + _tcscat( targetFilePath, L".png" ); + } + + // Save image at screen size + if( openFileName.nFilterIndex == 1 ) + { + SavePng( targetFilePath, hInterimSaveBitmap ); + } + // Save image scaled down to actual size + else + { + int saveWidth = static_cast( copyWidth / zoomLevel ); + int saveHeight = static_cast( copyHeight / zoomLevel ); + + hSaveBitmap = CreateCompatibleBitmap( hdcScreen, saveWidth, saveHeight ); + SelectObject( hSaveDc, hSaveBitmap ); + + StretchBlt( hSaveDc, + 0, 0, + saveWidth, saveHeight, + hInterimSaveDc, + 0, + 0, + copyWidth, copyHeight, + SRCCOPY | CAPTUREBLT ); + + SavePng( targetFilePath, hSaveBitmap ); + } + } + g_bSaveInProgress = false; + + DeleteDC( hInterimSaveDc ); + DeleteDC( hSaveDc ); + + if( lParam != SHALLOW_ZOOM ) + { + SetCursorPos(local_savedCursorPos.x, local_savedCursorPos.y); + } + ClipCursor( &oldClipRect ); + break; + } + + case IDC_COPY_CROP: + case IDC_COPY: { + HBITMAP hSaveBitmap; + HDC hSaveDc; + int copyX, copyY; + int copyWidth, copyHeight; + + if( LOWORD( wParam ) == IDC_COPY_CROP ) + { + g_RecordCropping = TRUE; + POINT local_savedCursorPos{}; + if( lParam != SHALLOW_ZOOM ) + { + GetCursorPos(&local_savedCursorPos); + } + SelectRectangle selectRectangle; + if( !selectRectangle.Start( hWnd ) ) + { + g_RecordCropping = FALSE; + break; + } + auto copyRc = selectRectangle.SelectedRect(); + selectRectangle.Stop(); + if( lParam != SHALLOW_ZOOM ) + { + SetCursorPos(local_savedCursorPos.x, local_savedCursorPos.y); + } + g_RecordCropping = FALSE; + + copyX = copyRc.left; + copyY = copyRc.top; + copyWidth = copyRc.right - copyRc.left; + copyHeight = copyRc.bottom - copyRc.top; + } + else + { + copyX = 0; + copyY = 0; + copyWidth = width; + copyHeight = height; + } + OutputDebug( L"***x: %d, y: %d, width: %d, height: %d\n", copyX, copyY, copyWidth, copyHeight ); + + hSaveBitmap = CreateCompatibleBitmap( hdcScreen, copyWidth, copyHeight ); + hSaveDc = CreateCompatibleDC( hdcScreen ); + SelectObject( hSaveDc, hSaveBitmap ); +#if SCALE_HALFTONE + SetStretchBltMode( hSaveDc, HALFTONE ); +#else + SetStretchBltMode( hSaveDc, COLORONCOLOR ); +#endif + StretchBlt( hSaveDc, + 0, 0, + copyWidth, copyHeight, + hdcScreen, + monInfo.rcMonitor.left + copyX, + monInfo.rcMonitor.top + copyY, + copyWidth, copyHeight, + SRCCOPY|CAPTUREBLT ); + + if( OpenClipboard( hWnd )) { + + EmptyClipboard(); + SetClipboardData( CF_BITMAP, hSaveBitmap ); + CloseClipboard(); + } + + DeleteDC( hSaveDc ); + } + break; + + case IDC_DRAW: + PostMessage( hWnd, WM_HOTKEY, DRAW_HOTKEY, 1 ); + break; + + case IDC_ZOOM: + PostMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 1 ); + break; + + case IDC_RECORD: + PostMessage( hWnd, WM_HOTKEY, RECORD_HOTKEY, 1 ); + break; + + case IDC_OPTIONS: + // Don't show win32 forms options if started by PowerToys. + // Show the PowerToys Settings application instead. + if (g_StartedByPowerToys) + { + OpenPowerToysSettingsApp(); + } + else + { + DialogBox( g_hInstance, L"OPTIONS", hWnd, OptionsProc ); + } + break; + + case IDC_BREAK: + { + // Manage handles, clean visual transitions, and Options delta + if( g_TimerActive ) + { + if( activeBreakShowBackgroundFile != g_BreakShowBackgroundFile || + activeBreakShowDesktop != g_BreakShowDesktop ) + { + if( g_BreakShowBackgroundFile && !g_BreakShowDesktop ) + { + SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, SHALLOW_DESTROY ); + } + else + { + SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 ); + } + } + else + { + SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, SHALLOW_DESTROY ); + g_TimerActive = TRUE; + } + } + + hdcScreen = CreateDC( L"DISPLAY", static_cast(NULL), static_cast(NULL), static_cast(NULL) ); + + // toggle second monitor + // FIX: we should save whether or not we've switched to a second monitor + // rather than just assume that the setting hasn't changed since the break timer + // became active + if( g_BreakOnSecondary ) + { + EnableDisableSecondaryDisplay( hWnd, TRUE, &secondaryDevMode ); + } + + // Determine what monitor we're on + GetCursorPos( &cursorPos ); + UpdateMonitorInfo( cursorPos, &monInfo ); + width = monInfo.rcMonitor.right - monInfo.rcMonitor.left; + height = monInfo.rcMonitor.bottom - monInfo.rcMonitor.top; + + // Trigger desktop recapture as necessary when switching monitors + if( g_TimerActive && g_BreakShowDesktop && lastMonInfo.rcMonitor != monInfo.rcMonitor ) + { + lastMonInfo = monInfo; + SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 ); + PostMessage( hWnd, WM_COMMAND, IDC_BREAK, 0 ); + break; + } + lastMonInfo = monInfo; + + // If the background is a file that hasn't been collected, grab it now + if( g_BreakShowBackgroundFile && !g_BreakShowDesktop && + ( !g_TimerActive || wcscmp( activeBreakBackgroundFile, g_BreakBackgroundFile ) ) ) + { + _tcscpy( activeBreakBackgroundFile, g_BreakBackgroundFile ); + + DeleteObject( g_hBackgroundBmp ); + DeleteDC( g_hDcBackgroundFile ); + + g_hBackgroundBmp = NULL; + g_hBackgroundBmp = LoadImageFile( g_BreakBackgroundFile ); + if( g_hBackgroundBmp == NULL ) + { + // Clean up hanging handles + SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 ); + ErrorDialog( hWnd, L"Error loading background bitmap", GetLastError() ); + break; + } + g_hDcBackgroundFile = CreateCompatibleDC( hdcScreen ); + SelectObject( g_hDcBackgroundFile, g_hBackgroundBmp ); + } + // If the background is a desktop that hasn't been collected, grab it now + else if( g_BreakShowBackgroundFile && g_BreakShowDesktop && !g_TimerActive ) + { + g_hBackgroundBmp = CreateFadedDesktopBackground( GetDC(NULL), & monInfo.rcMonitor, NULL ); + g_hDcBackgroundFile = CreateCompatibleDC( hdcScreen ); + SelectObject( g_hDcBackgroundFile, g_hBackgroundBmp ); + } + + // Track Options.Break delta + activeBreakShowBackgroundFile = g_BreakShowBackgroundFile; + activeBreakShowDesktop = g_BreakShowDesktop; + + g_TimerActive = TRUE; + if (g_StartedByPowerToys) + Trace::ZoomItActivateBreak(); + + breakTimeout = g_BreakTimeout * 60 + 1; + + // Create font + g_LogFont.lfHeight = height / 5; + hTimerFont = CreateFontIndirect( &g_LogFont ); + g_LogFont.lfHeight = height / 8; + hNegativeTimerFont = CreateFontIndirect( &g_LogFont ); + + // Create backing bitmap + hdcScreenCompat = CreateCompatibleDC(hdcScreen); + bmp.bmBitsPixel = static_cast(GetDeviceCaps(hdcScreen, BITSPIXEL)); + bmp.bmPlanes = static_cast(GetDeviceCaps(hdcScreen, PLANES)); + bmp.bmWidth = width; + bmp.bmHeight = height; + bmp.bmWidthBytes = ((bmp.bmWidth + 15) &~15)/8; + hbmpCompat = CreateBitmap(bmp.bmWidth, bmp.bmHeight, + bmp.bmPlanes, bmp.bmBitsPixel, static_cast(NULL)); + SelectObject(hdcScreenCompat, hbmpCompat); + + SetTextColor( hdcScreenCompat, g_BreakPenColor ); + SetBkMode( hdcScreenCompat, TRANSPARENT ); + SelectObject( hdcScreenCompat, hTimerFont ); + + EnableDisableOpacity( hWnd, TRUE ); + EnableDisableScreenSaver( FALSE ); + + SendMessage( hWnd, WM_TIMER, 0, 0 ); + SetTimer( hWnd, 0, 1000, NULL ); + + BringWindowToTop( hWnd ); + SetForegroundWindow( hWnd ); + SetActiveWindow( hWnd ); + SetWindowPos( hWnd, HWND_NOTOPMOST, monInfo.rcMonitor.left, monInfo.rcMonitor.top, + width, height, SWP_SHOWWINDOW ); + } + break; + + case IDCANCEL: + + memset( &tNotifyIconData, 0, sizeof(tNotifyIconData)); + tNotifyIconData.cbSize = sizeof(NOTIFYICONDATA); + tNotifyIconData.hWnd = hWnd; + tNotifyIconData.uID = 1; + Shell_NotifyIcon(NIM_DELETE, &tNotifyIconData); + reg.WriteRegSettings( RegSettings ); + + if( hWndOptions ) + { + DestroyWindow( hWndOptions ); + } + DestroyWindow( hWnd ); + break; + } + break; + + case WM_TIMER: + switch( wParam ) { + case 0: + // + // Break timer + // + breakTimeout -= 1; + InvalidateRect( hWnd, NULL, FALSE ); + if( breakTimeout == 0 && g_BreakPlaySoundFile ) { + + PlaySound( g_BreakSoundFile, NULL, SND_FILENAME|SND_ASYNC ); + } + break; + + case 2: + case 1: + // + // Telescoping zoom timer + // + if( zoomTelescopeStep ) { + + zoomLevel *= zoomTelescopeStep; + if( (zoomTelescopeStep > 1 && zoomLevel >= zoomTelescopeTarget ) || + (zoomTelescopeStep < 1 && zoomLevel <= zoomTelescopeTarget )) { + + zoomLevel = zoomTelescopeTarget; + KillTimer( hWnd, wParam ); + OutputDebug( L"SETCURSOR mon_left: %x mon_top: %x x: %d y: %d\n", + monInfo.rcMonitor.left, monInfo.rcMonitor.top, cursorPos.x, cursorPos.y ); + SetCursorPos( monInfo.rcMonitor.left + cursorPos.x, + monInfo.rcMonitor.top + cursorPos.y ); + } + + } else { + + // Case where we didn't zoom at all + KillTimer( hWnd, wParam ); + } + if( wParam == 2 && zoomLevel == 1 ) { + + g_Zoomed = FALSE; + if( g_ZoomOnLiveZoom ) + { + GetCursorPos( &cursorPos ); + cursorPos = ScalePointInRects( cursorPos, monInfo.rcMonitor, g_LiveZoomSourceRect ); + SetCursorPos( cursorPos.x, cursorPos.y ); + SendMessage(hWnd, WM_HOTKEY, LIVE_HOTKEY, 0); + } + else if( lParam != SHALLOW_ZOOM ) + { + // Figure out where final unzoomed cursor should be + if (g_Drawing) { + cursorPos = prevPt; + } + OutputDebug(L"FINAL MOUSE: x: %d y: %d\n", cursorPos.x, cursorPos.y ); + GetZoomedTopLeftCoordinates(zoomLevel, &cursorPos, &x, width, &y, height); + cursorPos.x = monInfo.rcMonitor.left + x + static_cast((cursorPos.x - x) * zoomLevel); + cursorPos.y = monInfo.rcMonitor.top + y + static_cast((cursorPos.y - y) * zoomLevel); + SetCursorPos(cursorPos.x, cursorPos.y); + } + if( hTargetWindow ) { + + SetWindowPos( hTargetWindow, HWND_BOTTOM, rcTargetWindow.left, rcTargetWindow.top, + rcTargetWindow.right - rcTargetWindow.left, + rcTargetWindow.bottom - rcTargetWindow.top, 0 ); + hTargetWindow = NULL; + } + DeleteDrawUndoList( &drawUndoList ); + + // Restore live zoom if we came from that mode + if( g_ZoomOnLiveZoom ) { + + SendMessage( g_hWndLiveZoom, WM_USER_SET_ZOOM, static_cast(g_LiveZoomLevel), reinterpret_cast(&g_LiveZoomSourceRect) ); + g_ZoomOnLiveZoom = FALSE; + forcePenResize = TRUE; + } + + SetForegroundWindow( g_ActiveWindow ); + ClipCursor( NULL ); + g_HaveDrawn = FALSE; + g_TypeMode = TypeModeOff; + g_HaveTyped = FALSE; + g_Drawing = FALSE; + EnableDisableStickyKeys( TRUE ); + DeleteObject( hTypingFont ); + DeleteDC( hdcScreen ); + DeleteDC( hdcScreenCompat ); + DeleteDC( hdcScreenCursorCompat ); + DeleteDC( hdcScreenSaveCompat ); + DeleteObject( hbmpCompat ); + DeleteObject( hbmpCursorCompat ); + DeleteObject( hbmpDrawingCompat ); + DeleteObject( hDrawingPen ); + + SetFocus( g_ActiveWindow ); + ShowWindow( hWnd, SW_HIDE ); + } + InvalidateRect( hWnd, NULL, FALSE ); + break; + + case 3: + POINT mousePos; + GetCursorPos(&mousePos); + if (mousePos.x != cursorPos.x || mousePos.y != cursorPos.y) + { + MONITORINFO monitorInfo = { sizeof(MONITORINFO) }; + UpdateMonitorInfo(mousePos, &monitorInfo); + + mousePos.x -= monitorInfo.rcMonitor.left; + mousePos.y -= monitorInfo.rcMonitor.top; + + OutputDebug(L"RETRACKING MOUSE: x: %d y: %d\n", mousePos.x, mousePos.y); + SendMessage(hWnd, WM_MOUSEMOVE, 0, MAKELPARAM(mousePos.x, mousePos.y)); + } + break; + } + break; + + case WM_PAINT: + + hDc = BeginPaint(hWnd, &ps); + + if( ( ( g_RecordCropping == FALSE ) || ( zoomLevel == 1 ) ) && g_Zoomed ) { + + OutputDebug( L"PAINT x: %d y: %d width: %d height: %d zoomLevel: %g\n", + cursorPos.x, cursorPos.y, width, height, zoomLevel ); + GetZoomedTopLeftCoordinates( zoomLevel, &cursorPos, &x, width, &y, height ); +#if SCALE_GDIPLUS + if ( zoomLevel >= zoomTelescopeTarget ) { + // do a high-quality render + extern void ScaleImage( HDC hdcDst, float xDst, float yDst, float wDst, float hDst, + HBITMAP bmSrc, float xSrc, float ySrc, float wSrc, float hSrc ); + + ScaleImage( ps.hdc, + 0, 0, + (float)bmp.bmWidth, (float)bmp.bmHeight, + hbmpCompat, + (float)x, (float)y, + width/zoomLevel, height/zoomLevel ); + } else { + // do a fast, less accurate render + SetStretchBltMode( hDc, HALFTONE ); + StretchBlt( ps.hdc, + 0, 0, + bmp.bmWidth, bmp.bmHeight, + hdcScreenCompat, + x, y, + (int) (width/zoomLevel), (int) (height/zoomLevel), + SRCCOPY); + } +#else +#if SCALE_HALFTONE + SetStretchBltMode( hDc, zoomLevel == zoomTelescopeTarget ? HALFTONE : COLORONCOLOR ); +#else + SetStretchBltMode( hDc, COLORONCOLOR ); +#endif + StretchBlt( ps.hdc, + 0, 0, + bmp.bmWidth, bmp.bmHeight, + hdcScreenCompat, + x, y, + static_cast(width/zoomLevel), static_cast(height/zoomLevel), + SRCCOPY|CAPTUREBLT ); +#endif + } else if( g_TimerActive ) { + + // Fill bitmap with white + rc.top = rc.left = 0; + rc.bottom = height; + rc.right = width; + FillRect( hdcScreenCompat, &rc, GetSysColorBrush( COLOR_WINDOW )); + + // If there's a background bitmap, draw it in the center + if( g_hBackgroundBmp ) { + + BITMAP local_bmp; + GetObject(g_hBackgroundBmp, sizeof(local_bmp), &local_bmp); + SetStretchBltMode( hdcScreenCompat, HALFTONE ); + if( g_BreakBackgroundStretch ) { + StretchBlt( hdcScreenCompat, 0, 0, width, height, + g_hDcBackgroundFile, 0, 0, local_bmp.bmWidth, local_bmp.bmHeight, SRCCOPY|CAPTUREBLT ); + } else { + BitBlt( hdcScreenCompat, width/2 - local_bmp.bmWidth/2, height/2 - local_bmp.bmHeight/2, + local_bmp.bmWidth, local_bmp.bmHeight, g_hDcBackgroundFile, 0, 0, SRCCOPY|CAPTUREBLT ); + } + } + + // Draw time + if( breakTimeout > 0 ) { + + _stprintf( timerText, L"% 2d:%02d", breakTimeout/60, breakTimeout % 60 ); + + } else { + + _tcscpy( timerText, L"0:00" ); + } + rc.left = rc.top = 0; + DrawText( hdcScreenCompat, timerText, -1, &rc, + DT_NOCLIP|DT_LEFT|DT_NOPREFIX|DT_CALCRECT ); + + rc1.left = rc1.right = rc1.bottom = rc1.top = 0; + if( g_ShowExpiredTime && breakTimeout < 0 ) { + + _stprintf( negativeTimerText, L"(-% 2d:%02d)", + -breakTimeout/60, -breakTimeout % 60 ); + HFONT prevFont = static_cast(SelectObject( hdcScreenCompat, hNegativeTimerFont )); + DrawText( hdcScreenCompat, negativeTimerText, -1, &rc1, + DT_NOCLIP|DT_LEFT|DT_NOPREFIX|DT_CALCRECT ); + SelectObject( hdcScreenCompat, prevFont ); + } + + // Position time vertically + switch( g_BreakTimerPosition ) { + case 0: + case 1: + case 2: + rc.top = 50; + break; + case 3: + case 4: + case 5: + rc.top = (height - (rc.bottom - rc.top))/2; + break; + case 6: + case 7: + case 8: + rc.top = height - rc.bottom - 50 - rc1.bottom; + break; + } + + // Position time horizontally + switch( g_BreakTimerPosition ) { + case 0: + case 3: + case 6: + rc.left = 50; + break; + case 1: + case 4: + case 7: + rc.left = (width - (rc.right - rc.left))/2; + break; + case 2: + case 5: + case 8: + rc.left = width - rc.right - 50; + break; + } + rc.bottom += rc.top; + rc.right += rc.left; + + DrawText( hdcScreenCompat, timerText, -1, &rc, DT_NOCLIP|DT_LEFT|DT_NOPREFIX ); + + if( g_ShowExpiredTime && breakTimeout < 0 ) { + + rc1.top = rc.bottom + 10; + rc1.left = rc.left + ((rc.right - rc.left)-(rc1.right-rc1.left))/2; + HFONT prevFont = static_cast(SelectObject( hdcScreenCompat, hNegativeTimerFont )); + DrawText( hdcScreenCompat, negativeTimerText, -1, &rc1, + DT_NOCLIP|DT_LEFT|DT_NOPREFIX ); + SelectObject( hdcScreenCompat, prevFont ); + } + + // Copy to screen + BitBlt( ps.hdc, 0, 0, width, height, hdcScreenCompat, 0, 0, SRCCOPY|CAPTUREBLT ); + } + EndPaint(hWnd, &ps); + return TRUE; + + case WM_DESTROY: + + PostQuitMessage( 0 ); + break; + + default: + if( message == wmTaskbarCreated ) + { + if( g_ShowTrayIcon ) + { + EnableDisableTrayIcon( hWnd, TRUE ); + } + return TRUE; + } + return DefWindowProc(hWnd, message, wParam, lParam ); + } + return 0; +} + + +//---------------------------------------------------------------------------- +// +// LiveZoomWndProc +// +//---------------------------------------------------------------------------- +LRESULT CALLBACK LiveZoomWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + RECT rc; + POINT cursorPos; + static int width, height; + static MONITORINFO monInfo; + HDC hdcScreen; +#if 0 + int delta; + BOOLEAN zoomIn; +#endif + static POINT lastCursorPos; + POINT adjustedCursorPos, zoomCenterPos; + int moveWidth, moveHeight; + int sourceRectHeight, sourceRectWidth; + DWORD curTickCount; + RECT sourceRect{}; + static RECT lastSourceRect; + static float zoomLevel; + static float zoomTelescopeStep; + static float zoomTelescopeTarget; + static DWORD prevZoomStepTickCount = 0; + static BOOL dwmEnabled = FALSE; + static BOOLEAN startedInPresentationMode = FALSE; + MAGTRANSFORM matrix; + + switch (message) { + case WM_CREATE: + + // Initialize + pMagInitialize(); + if (pDwmIsCompositionEnabled) pDwmIsCompositionEnabled(&dwmEnabled); + + // Create the zoom window + if( !g_fullScreenWorkaround ) { + + g_hWndLiveZoomMag = CreateWindowEx( 0, + WC_MAGNIFIER, + TEXT("MagnifierWindow"), + WS_CHILD | MS_SHOWMAGNIFIEDCURSOR | WS_VISIBLE, + 0, 0, 0, 0, hWnd, NULL, g_hInstance, NULL ); + } + + ShowWindow( hWnd, SW_SHOW ); + InvalidateRect( g_hWndLiveZoomMag, NULL, TRUE ); + + if( !g_fullScreenWorkaround ) + SetForegroundWindow(static_cast(reinterpret_cast(lParam)->lpCreateParams)); + + // If we're not on Win7+, then set a timer to go off two hours from + // now + if( g_OsVersion < WIN7_VERSION ) { + + startedInPresentationMode = IsPresentationMode(); + // if we're not in presentation mode, kill ourselves after a timeout + if( !startedInPresentationMode ) { + + SetTimer( hWnd, 1, LIVEZOOM_WINDOW_TIMEOUT, NULL ); + } + } + break; + + case WM_SHOWWINDOW: + if( wParam == TRUE ) { + + // Determine what monitor we're on + lastCursorPos.x = -1; + hdcScreen = GetDC( NULL ); + GetCursorPos( &cursorPos ); + UpdateMonitorInfo( cursorPos, &monInfo ); + width = monInfo.rcMonitor.right - monInfo.rcMonitor.left; + height = monInfo.rcMonitor.bottom - monInfo.rcMonitor.top; + lastSourceRect.left = lastSourceRect.right = 0; + lastSourceRect.right = width; + lastSourceRect.bottom = height; + + // Set window size + if( !g_fullScreenWorkaround ) { + + SetWindowPos( hWnd, NULL, monInfo.rcMonitor.left, monInfo.rcMonitor.top, + monInfo.rcMonitor.right - monInfo.rcMonitor.left, + monInfo.rcMonitor.bottom - monInfo.rcMonitor.top, + SWP_NOACTIVATE | SWP_NOZORDER ); + UpdateWindow(hWnd); + } + + // Are we coming back from a static zoom that + // was started while we were live zoomed? + if( g_ZoomOnLiveZoom ) { + + // Force a zoom to 2x without telescope + prevZoomStepTickCount = 0; + zoomLevel = static_cast(1.9); + zoomTelescopeTarget = 2.0; + zoomTelescopeStep = 2.0; + + } else { + + zoomTelescopeStep = ZOOM_LEVEL_STEP_IN; + zoomTelescopeTarget = g_ZoomLevels[g_SliderZoomLevel]; + + prevZoomStepTickCount = 0; + if( dwmEnabled ) { + + zoomLevel = static_cast(1); + + } else { + + zoomLevel = static_cast(1.9); + } + } + RegisterHotKey( hWnd, 0, MOD_CONTROL, VK_UP ); + RegisterHotKey( hWnd, 1, MOD_CONTROL, VK_DOWN ); + + // Hide hardware cursor + if( !g_fullScreenWorkaround ) + if( pMagShowSystemCursor ) pMagShowSystemCursor( FALSE ); + + if( g_RecordToggle ) + g_RecordingSession->EnableCursorCapture( false ); + + GetCursorPos( &lastCursorPos ); + SetCursorPos( lastCursorPos.x, lastCursorPos.y ); + + SendMessage( hWnd, WM_TIMER, 0, 0); + SetTimer( hWnd, 0, ZOOM_LEVEL_STEP_TIME, NULL ); + + } else { + + KillTimer( hWnd, 0 ); + + if( g_RecordToggle ) + g_RecordingSession->EnableCursorCapture(); + + if( !g_fullScreenWorkaround ) + if( pMagShowSystemCursor ) pMagShowSystemCursor( TRUE ); + + // Reset the timer to expire two hours from now + if( g_OsVersion < WIN7_VERSION && !IsPresentationMode()) { + + KillTimer( hWnd, 1 ); + SetTimer( hWnd, 1, LIVEZOOM_WINDOW_TIMEOUT, NULL ); + } else { + + DestroyWindow( hWnd ); + } + UnregisterHotKey( hWnd, 0 ); + UnregisterHotKey( hWnd, 1 ); + } + break; + + case WM_TIMER: + switch( wParam ) { + case 0: { + // if we're cropping, do not move + if( g_RecordCropping == TRUE ) + { + // Still redraw to keep the contents live + InvalidateRect( g_hWndLiveZoomMag, nullptr, TRUE ); + break; + } + + GetCursorPos(&cursorPos); + + // Reclaim topmost status, to prevent unmagnified menus from remaining in view. + memset(&matrix, 0, sizeof(matrix)); + if( !g_fullScreenWorkaround ) { + + pSetLayeredWindowAttributes( hWnd, 0, 255, LWA_ALPHA ); + SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); + + OutputDebug(L"LIVEZOOM RECLAIM\n"); + } + + sourceRectWidth = lastSourceRect.right - lastSourceRect.left; + sourceRectHeight = lastSourceRect.bottom - lastSourceRect.top; + moveWidth = sourceRectWidth/LIVEZOOM_MOVE_REGIONS; + moveHeight = sourceRectHeight/LIVEZOOM_MOVE_REGIONS; + curTickCount = GetTickCount(); + if( zoomLevel != zoomTelescopeTarget && + (prevZoomStepTickCount == 0 || (curTickCount - prevZoomStepTickCount > ZOOM_LEVEL_STEP_TIME)) ) { + + prevZoomStepTickCount = curTickCount; + if( (zoomTelescopeStep > 1 && zoomLevel*zoomTelescopeStep >= zoomTelescopeTarget ) || + (zoomTelescopeStep < 1 && zoomLevel*zoomTelescopeStep <= zoomTelescopeTarget )) { + + zoomLevel = zoomTelescopeTarget; + + } else { + + zoomLevel *= zoomTelescopeStep; + } + // Time to exit zoom mode? + if( zoomTelescopeTarget == 1 && zoomLevel == 1 ) { + +#if WINDOWS_CURSOR_RECORDING_WORKAROUND + if( g_RecordToggle ) + g_LiveZoomLevelOne = true; + else +#endif + ShowWindow( hWnd, SW_HIDE ); + + } else { + + matrix.v[0][0] = zoomLevel; + matrix.v[0][2] = (static_cast(-lastSourceRect.left) * zoomLevel); + matrix.v[1][1] = zoomLevel; + matrix.v[1][2] = (static_cast(-lastSourceRect.top) * zoomLevel ); + matrix.v[2][2] = 1.0f; + } + + // + // Pre-adjust for monitor boundary + // + adjustedCursorPos.x = cursorPos.x - monInfo.rcMonitor.left; + adjustedCursorPos.y = cursorPos.y - monInfo.rcMonitor.top; + GetZoomedTopLeftCoordinates( zoomLevel, &adjustedCursorPos, reinterpret_cast(&zoomCenterPos.x), width, + reinterpret_cast(&zoomCenterPos.y), height ); + + // + // Add back monitor boundary + // + zoomCenterPos.x += monInfo.rcMonitor.left + static_cast(width/zoomLevel/2); + zoomCenterPos.y += monInfo.rcMonitor.top + static_cast(height/zoomLevel/2); + + } else { + + int xOffset = cursorPos.x - lastSourceRect.left; + int yOffset = cursorPos.y - lastSourceRect.top; + zoomCenterPos.x = 0; + zoomCenterPos.y = 0; + if( xOffset < moveWidth ) + zoomCenterPos.x = lastSourceRect.left + sourceRectWidth/2 - (moveWidth - xOffset); + else if( xOffset > moveWidth * (LIVEZOOM_MOVE_REGIONS-1) ) + zoomCenterPos.x = lastSourceRect.left + sourceRectWidth/2 + (xOffset - moveWidth*(LIVEZOOM_MOVE_REGIONS-1)); + if( yOffset < moveHeight ) + zoomCenterPos.y = lastSourceRect.top + sourceRectHeight/2 - (moveHeight - yOffset); + else if( yOffset > moveHeight * (LIVEZOOM_MOVE_REGIONS-1) ) + zoomCenterPos.y = lastSourceRect.top + sourceRectHeight/2 + (yOffset - moveHeight*(LIVEZOOM_MOVE_REGIONS-1)); + } + if( matrix.v[0][0] || zoomCenterPos.x || zoomCenterPos.y ) { + + if( zoomCenterPos.y == 0 ) + zoomCenterPos.y = lastSourceRect.top + sourceRectHeight/2; + if( zoomCenterPos.x == 0 ) + zoomCenterPos.x = lastSourceRect.left + sourceRectWidth/2; + + int zoomWidth = static_cast(width / zoomLevel); + int zoomHeight = static_cast(height/ zoomLevel); + sourceRect.left = zoomCenterPos.x - zoomWidth / 2; + sourceRect.top = zoomCenterPos.y - zoomHeight / 2; + + // Don't scroll outside desktop area. + if (sourceRect.left < monInfo.rcMonitor.left) + sourceRect.left = monInfo.rcMonitor.left; + else if (sourceRect.left > monInfo.rcMonitor.right - zoomWidth ) + sourceRect.left = monInfo.rcMonitor.right - zoomWidth; + sourceRect.right = sourceRect.left + zoomWidth; + if (sourceRect.top < monInfo.rcMonitor.top) + sourceRect.top = monInfo.rcMonitor.top; + else if (sourceRect.top > monInfo.rcMonitor.bottom - zoomHeight) + sourceRect.top = monInfo.rcMonitor.bottom - zoomHeight; + sourceRect.bottom = sourceRect.top + zoomHeight; + + if( g_ZoomOnLiveZoom ) { + + matrix.v[0][0] = static_cast(1.0); + matrix.v[0][2] = (static_cast(-monInfo.rcMonitor.left)); + + matrix.v[1][1] = static_cast(1.0); + matrix.v[1][2] = (static_cast(-monInfo.rcMonitor.top)); + + matrix.v[2][2] = 1.0f; + + } else if( lastSourceRect.left != sourceRect.left || + lastSourceRect.top != sourceRect.top ) { + + matrix.v[0][0] = zoomLevel; + matrix.v[0][2] = (static_cast(-sourceRect.left) * zoomLevel); + + matrix.v[1][1] = zoomLevel; + matrix.v[1][2] = (static_cast(-sourceRect.top) * zoomLevel); + + matrix.v[2][2] = 1.0f; + } + lastSourceRect = sourceRect; + } + lastCursorPos = cursorPos; + + // Update source and zoom if necessary + if( matrix.v[0][0] ) { + + OutputDebug(L"LIVEZOOM update\n"); + if( g_fullScreenWorkaround ) { + + pMagSetFullscreenTransform(zoomLevel, sourceRect.left, sourceRect.top); + pMagSetInputTransform(TRUE, &sourceRect, &monInfo.rcMonitor); + } + else { + + pMagSetWindowTransform(g_hWndLiveZoomMag, &matrix); + } + } + + if( !g_fullScreenWorkaround ) { + + // Force redraw to refresh screen contents + InvalidateRect(g_hWndLiveZoomMag, NULL, TRUE); + } + + // are we done zooming? + if( zoomLevel == 1 ) { + +#if WINDOWS_CURSOR_RECORDING_WORKAROUND + if( g_RecordToggle ) { + + g_LiveZoomLevelOne = true; + } + else { + +#endif + if( g_OsVersion < WIN7_VERSION ) { + + ShowWindow( hWnd, SW_HIDE ); + + } else { + + DestroyWindow( hWnd ); + } + } +#if WINDOWS_CURSOR_RECORDING_WORKAROUND + } +#endif + } + break; + case 1: { + + if( !IsWindowVisible( hWnd )) { + + // This is the cached window timeout. If not in presentation mode, + // time to exit + if( !IsPresentationMode()) { + + DestroyWindow( hWnd ); + } + } + } + break; + } + break; + + case WM_SETTINGCHANGE: + if( g_OsVersion < WIN7_VERSION ) { + + if( startedInPresentationMode && !IsPresentationMode()) { + + // Existing presentation mode + DestroyWindow( hWnd ); + + } else if( !startedInPresentationMode && IsPresentationMode()) { + + // Kill the timer if one was configured, because now + // we're going to go away when they exit presentation mode + KillTimer( hWnd, 1 ); + } + } + break; + + case WM_HOTKEY: { + float newZoomLevel = zoomLevel; + switch( wParam ) { + case 0: + // zoom in + if( newZoomLevel < ZOOM_LEVEL_MAX ) + newZoomLevel *= 2; + zoomTelescopeStep = ZOOM_LEVEL_STEP_IN; + break; + + case 1: + if( newZoomLevel > 2 ) + newZoomLevel /= 2; + else { + + newZoomLevel *= .75; + if( newZoomLevel < ZOOM_LEVEL_MIN ) + newZoomLevel = ZOOM_LEVEL_MIN; + } + zoomTelescopeStep = ZOOM_LEVEL_STEP_OUT; + break; + } + zoomTelescopeTarget = newZoomLevel; + if( !dwmEnabled ) { + + zoomLevel = newZoomLevel; + } + } + break; + + // NOTE: keyboard and mouse input actually don't get sent to us at all when in live zoom mode + case WM_KEYDOWN: + switch( wParam ) { + case VK_ESCAPE: + zoomTelescopeStep = ZOOM_LEVEL_STEP_OUT; + zoomTelescopeTarget = 1.0; + if( !dwmEnabled ) { + + zoomLevel = static_cast(1.1); + } + break; + + case VK_UP: + SendMessage( hWnd, WM_MOUSEWHEEL, + MAKEWPARAM( GetAsyncKeyState( VK_LCONTROL ) != 0 ? MK_CONTROL: 0, WHEEL_DELTA), 0 ); + return TRUE; + + case VK_DOWN: + SendMessage( hWnd, WM_MOUSEWHEEL, + MAKEWPARAM( GetAsyncKeyState( VK_LCONTROL ) != 0 ? MK_CONTROL: 0, -WHEEL_DELTA), 0 ); + return TRUE; + } + break; + case WM_DESTROY: + g_hWndLiveZoom = NULL; + break; + + case WM_SIZE: + GetClientRect(hWnd, &rc); + SetWindowPos(g_hWndLiveZoomMag, NULL, + rc.left, rc.top, rc.right, rc.bottom, 0 ); + break; + + case WM_USER_GET_ZOOM_LEVEL: + return reinterpret_cast(&zoomLevel); + + case WM_USER_GET_SOURCE_RECT: + return reinterpret_cast(&lastSourceRect); + + case WM_USER_MAGNIFY_CURSOR: + { + auto style = GetWindowLong( g_hWndLiveZoomMag, GWL_STYLE ); + if( wParam == TRUE ) + { + style |= MS_SHOWMAGNIFIEDCURSOR; + } + else + { + style &= ~MS_SHOWMAGNIFIEDCURSOR; + } + SetWindowLong( g_hWndLiveZoomMag, GWL_STYLE, style ); + InvalidateRect( g_hWndLiveZoomMag, nullptr, TRUE ); + RedrawWindow( hWnd, nullptr, nullptr, RDW_ALLCHILDREN | RDW_UPDATENOW ); + } + break; + + case WM_USER_SET_ZOOM: + { + if( g_RecordToggle ) + { + g_SelectRectangle.UpdateOwner( hWnd ); + } + + if( lParam != NULL ) { + + lastSourceRect = *reinterpret_cast(lParam); + } +#if WINDOWS_CURSOR_RECORDING_WORKAROUND + if( g_LiveZoomLevelOne ) { + + g_LiveZoomLevelOne = FALSE; + + zoomTelescopeTarget = static_cast(wParam); + zoomTelescopeStep = ZOOM_LEVEL_STEP_IN; + prevZoomStepTickCount = 0; + zoomLevel = 1.0; + + break; + } +#endif + zoomLevel = static_cast(wParam); + zoomTelescopeTarget = zoomLevel; + matrix.v[0][0] = zoomLevel; + matrix.v[0][2] = (static_cast(-lastSourceRect.left) * static_cast(wParam)); + + matrix.v[1][1] = zoomLevel; + matrix.v[1][2] = (static_cast(-lastSourceRect.top) * static_cast(wParam)); + + matrix.v[2][2] = 1.0f; + + if( g_fullScreenWorkaround ) { + + pMagSetFullscreenTransform(zoomLevel, lastSourceRect.left, lastSourceRect.top); + pMagSetInputTransform(TRUE, &lastSourceRect, &monInfo.rcMonitor); + } + else { + + pMagSetWindowTransform(g_hWndLiveZoomMag, &matrix); + } + } + break; + + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } + return 0; +} + + +//---------------------------------------------------------------------------- +// +// Wrapper functions for explicit linking to d3d11.dll +// +//---------------------------------------------------------------------------- + +HRESULT __stdcall WrapCreateDirect3D11DeviceFromDXGIDevice( + IDXGIDevice *dxgiDevice, + IInspectable **graphicsDevice) +{ + if( pCreateDirect3D11DeviceFromDXGIDevice == nullptr ) + return E_NOINTERFACE; + + return pCreateDirect3D11DeviceFromDXGIDevice( dxgiDevice, graphicsDevice ); +} + +HRESULT __stdcall WrapCreateDirect3D11SurfaceFromDXGISurface( + IDXGISurface *dxgiSurface, + IInspectable **graphicsSurface) +{ + if( pCreateDirect3D11SurfaceFromDXGISurface == nullptr ) + return E_NOINTERFACE; + + return pCreateDirect3D11SurfaceFromDXGISurface( dxgiSurface, graphicsSurface ); +} + +HRESULT __stdcall WrapD3D11CreateDevice( + IDXGIAdapter *pAdapter, + D3D_DRIVER_TYPE DriverType, + HMODULE Software, + UINT Flags, + const D3D_FEATURE_LEVEL *pFeatureLevels, + UINT FeatureLevels, + UINT SDKVersion, + ID3D11Device **ppDevice, + D3D_FEATURE_LEVEL *pFeatureLevel, + ID3D11DeviceContext **ppImmediateContext) +{ + if( pD3D11CreateDevice == nullptr ) + return E_NOINTERFACE; + + return pD3D11CreateDevice( pAdapter, DriverType, Software, Flags, pFeatureLevels, + FeatureLevels, SDKVersion, ppDevice, pFeatureLevel, ppImmediateContext ); +} + + +//---------------------------------------------------------------------------- +// +// InitInstance +// +//---------------------------------------------------------------------------- +HWND InitInstance( HINSTANCE hInstance, int nCmdShow ) +{ + WNDCLASS wcZoomIt; + HWND hWndMain; + + g_hInstance = hInstance; + + // If magnification, set default hotkey for live zoom + if( pMagInitialize ) { + + // register live zoom host window + wcZoomIt.style = CS_HREDRAW | CS_VREDRAW; + wcZoomIt.lpfnWndProc = LiveZoomWndProc; + wcZoomIt.cbClsExtra = 0; + wcZoomIt.cbWndExtra = 0; + wcZoomIt.hInstance = hInstance; + wcZoomIt.hIcon = 0; + wcZoomIt.hCursor = LoadCursor(NULL, IDC_ARROW); + wcZoomIt.hbrBackground = NULL; + wcZoomIt.lpszMenuName = NULL; + wcZoomIt.lpszClassName = L"MagnifierClass"; + RegisterClass(&wcZoomIt); + + } else { + + g_LiveZoomToggleKey = 0; + } + + wcZoomIt.style = 0; + wcZoomIt.lpfnWndProc = (WNDPROC)MainWndProc; + wcZoomIt.cbClsExtra = 0; + wcZoomIt.cbWndExtra = 0; + wcZoomIt.hInstance = hInstance; wcZoomIt.hIcon = NULL; + wcZoomIt.hCursor = LoadCursor( hInstance, L"NULLCURSOR" ); + wcZoomIt.hbrBackground = NULL; + wcZoomIt.lpszMenuName = NULL; + wcZoomIt.lpszClassName = L"ZoomitClass"; + if ( ! RegisterClass(&wcZoomIt) ) + return FALSE; + + hWndMain = CreateWindowEx( WS_EX_TOOLWINDOW, L"ZoomitClass", + L"Zoomit Zoom Window", + WS_POPUP, + 0, 0, + 0, 0, + NULL, + NULL, + hInstance, + NULL); + + // If window could not be created, return "failure" + if (!hWndMain ) + return NULL; + + // Make the window visible; update its client area; and return "success" + ShowWindow(hWndMain, SW_HIDE); + + // Add tray icon + EnableDisableTrayIcon( hWndMain, g_ShowTrayIcon ); + return hWndMain; + +} + +//---------------------------------------------------------------------------- +// +// WinMain +// +//---------------------------------------------------------------------------- +int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, + _In_ PWSTR lpCmdLine, _In_ int nCmdShow ) +{ + MSG msg; + HACCEL hAccel; + Shared::Trace::ETWTrace* trace = nullptr; + + if( !ShowEula( APPNAME, NULL, NULL )) return 1; + + std::wstring pid = std::wstring(lpCmdLine); // The PowerToys pid is the argument to the process. + auto mainThreadId = GetCurrentThreadId(); + if (!pid.empty()) + { + g_StartedByPowerToys = TRUE; + + if (powertoys_gpo::getConfiguredZoomItEnabledValue() == powertoys_gpo::gpo_rule_configured_disabled) + { + Logger::warn(L"Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator."); + return 1; + } + + trace = new Shared::Trace::ETWTrace(); + Trace::RegisterProvider(); + trace->UpdateState(true); + Trace::ZoomItStarted(); + + // Initialize logger + LoggerHelpers::init_logger(L"ZoomIt", L"", LogSettings::zoomItLoggerName); + + ProcessWaiter::OnProcessTerminate(pid, [mainThreadId](int err) { + if (err != ERROR_SUCCESS) + { + Logger::error(L"Failed to wait for parent process exit. {}", get_last_error_or_default(err)); + } + else + { + Logger::trace(L"PowerToys runner exited."); + } + + Logger::trace(L"Exiting ZoomIt"); + PostThreadMessage(mainThreadId, WM_QUIT, 0, 0); + }); + } + + +#ifndef _WIN64 + + if(!g_StartedByPowerToys) + { + // Launch 64-bit version if necessary + SetAutostartFilePath(); + if( RunningOnWin64()) { + + // Record where we are if we're the 32-bit version + return Run64bitVersion(); + } + } +#endif + + // Single instance per desktop + + if( !CreateEvent( NULL, FALSE, FALSE, _T("Local\\ZoomitActive"))) { + + CreateEvent( NULL, FALSE, FALSE, _T("ZoomitActive")); + } + if( GetLastError() == ERROR_ALREADY_EXISTS ) { + if (g_StartedByPowerToys) + { + MessageBox(NULL, L"We've detected another instance of ZoomIt is already running.\nCan't start a new ZoomIt instance from PowerToys.", + APPNAME, MB_ICONERROR | MB_SETFOREGROUND); + return 1; + } + + // Tell the other instance to show the options dialog + g_hWndMain = FindWindow( L"ZoomitClass", NULL ); + if( g_hWndMain != NULL ) { + + PostMessage( g_hWndMain, WM_COMMAND, IDC_OPTIONS, 0 ); + int count = 0; + while( count++ < 5 ) { + + HWND local_hWndOptions = FindWindow( NULL, L"ZoomIt - Sysinternals: www.sysinternals.com" ); + if( local_hWndOptions ) { + + SetForegroundWindow( local_hWndOptions ); + SetWindowPos( local_hWndOptions, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_SHOWWINDOW ); + break; + } + Sleep( 100 ); + } + } + return 0; + } + + g_OsVersion = GetVersion() & 0xFFFF; + + // load accelerators + hAccel = LoadAccelerators( hInstance, TEXT("ACCELERATORS")); + + if (FAILED(CoInitialize(0))) + { + return 0; + } + + pEnableThemeDialogTexture = (type_pEnableThemeDialogTexture) GetProcAddress( GetModuleHandle( L"uxtheme.dll" ), + "EnableThemeDialogTexture" ); + pMonitorFromPoint = (type_MonitorFromPoint) GetProcAddress( LoadLibrarySafe( L"User32.dll", DLL_LOAD_LOCATION_SYSTEM), + "MonitorFromPoint" ); + pGetMonitorInfo = (type_pGetMonitorInfo) GetProcAddress( LoadLibrarySafe( L"User32.dll", DLL_LOAD_LOCATION_SYSTEM), + "GetMonitorInfoA" ); + pSHAutoComplete = (type_pSHAutoComplete) GetProcAddress( LoadLibrarySafe(L"Shlwapi.dll", DLL_LOAD_LOCATION_SYSTEM), + "SHAutoComplete" ); + pSetLayeredWindowAttributes = (type_pSetLayeredWindowAttributes) GetProcAddress( LoadLibrarySafe(L"user32.dll", DLL_LOAD_LOCATION_SYSTEM), + "SetLayeredWindowAttributes" ); + pMagSetWindowSource = (type_pMagSetWindowSource) GetProcAddress( LoadLibrarySafe(L"magnification.dll", DLL_LOAD_LOCATION_SYSTEM), + "MagSetWindowSource" ); + pGetPointerType = (type_pGetPointerType)GetProcAddress(LoadLibrarySafe(L"user32.dll", DLL_LOAD_LOCATION_SYSTEM), + "GetPointerType" ); + pGetPointerPenInfo = (type_pGetPointerPenInfo)GetProcAddress(LoadLibrarySafe(L"user32.dll", DLL_LOAD_LOCATION_SYSTEM), + "GetPointerPenInfo" ); + pMagInitialize = (type_pMagInitialize)GetProcAddress(LoadLibrarySafe(L"magnification.dll", DLL_LOAD_LOCATION_SYSTEM), + "MagInitialize"); + pMagSetWindowTransform = (type_pMagSetWindowTransform) GetProcAddress( LoadLibrarySafe(L"magnification.dll", DLL_LOAD_LOCATION_SYSTEM), + "MagSetWindowTransform" ); + pMagSetFullscreenTransform = (type_pMagSetFullscreenTransform)GetProcAddress(LoadLibrarySafe(L"magnification.dll", DLL_LOAD_LOCATION_SYSTEM), + "MagSetFullscreenTransform"); + pMagSetInputTransform = (type_pMagSetInputTransform)GetProcAddress(LoadLibrarySafe(L"magnification.dll", DLL_LOAD_LOCATION_SYSTEM), + "MagSetInputTransform"); + pMagShowSystemCursor = (type_pMagShowSystemCursor)GetProcAddress(LoadLibrarySafe(L"magnification.dll", DLL_LOAD_LOCATION_SYSTEM), + "MagShowSystemCursor"); + pMagSetWindowFilterList = (type_pMagSetWindowFilterList)GetProcAddress( LoadLibrarySafe( L"magnification.dll", DLL_LOAD_LOCATION_SYSTEM ), + "MagSetWindowFilterList" ); + pSHQueryUserNotificationState = (type_pSHQueryUserNotificationState) GetProcAddress( LoadLibrarySafe(L"shell32.dll", DLL_LOAD_LOCATION_SYSTEM), + "SHQueryUserNotificationState" ); + pDwmIsCompositionEnabled = (type_pDwmIsCompositionEnabled) GetProcAddress( LoadLibrarySafe(L"dwmapi.dll", DLL_LOAD_LOCATION_SYSTEM), + "DwmIsCompositionEnabled" ); + pSetProcessDPIAware = (type_pSetProcessDPIAware) GetProcAddress( LoadLibrarySafe(L"User32.dll", DLL_LOAD_LOCATION_SYSTEM), + "SetProcessDPIAware"); + pSystemParametersInfoForDpi = (type_pSystemParametersInfoForDpi)GetProcAddress(LoadLibrarySafe(L"User32.dll", DLL_LOAD_LOCATION_SYSTEM), + "SystemParametersInfoForDpi"); + pGetDpiForWindow = (type_pGetDpiForWindow)GetProcAddress(LoadLibrarySafe(L"User32.dll", DLL_LOAD_LOCATION_SYSTEM), + "GetDpiForWindow" ); + pCreateDirect3D11DeviceFromDXGIDevice = (type_pCreateDirect3D11DeviceFromDXGIDevice) GetProcAddress( LoadLibrarySafe(L"d3d11.dll", DLL_LOAD_LOCATION_SYSTEM), + "CreateDirect3D11DeviceFromDXGIDevice" ); + pCreateDirect3D11SurfaceFromDXGISurface = (type_pCreateDirect3D11SurfaceFromDXGISurface) GetProcAddress( LoadLibrarySafe(L"d3d11.dll", DLL_LOAD_LOCATION_SYSTEM), + "CreateDirect3D11SurfaceFromDXGISurface" ); + pD3D11CreateDevice = (type_pD3D11CreateDevice) GetProcAddress( LoadLibrarySafe(L"d3d11.dll", DLL_LOAD_LOCATION_SYSTEM), + "D3D11CreateDevice" ); + + // Windows Server 2022 (and including Windows 11) introduced a bug where the cursor disappears + // in live zoom. Use the full-screen magnifier as a workaround on those versions only. It is + // currently impractical as a replacement; it requires calling MagSetInputTransform for all + // input to be transformed. Otherwise, some hit-testing is misdirected. MagSetInputTransform + // fails without token UI access, which is impractical; it requires copying the executable + // under either %ProgramFiles% or %SystemRoot%, which requires elevation. + // + // TODO: Update the Windows 11 21H2 revision check when the final number is known. Also add a + // check for the Windows Server 2022 revision if that bug (https://task.ms/38611091) is + // fixed. + DWORD windowsRevision, windowsBuild = GetWindowsBuild( &windowsRevision ); + if( ( windowsBuild == BUILD_WINDOWS_SERVER_2022 ) || + ( ( windowsBuild == BUILD_WINDOWS_11_21H2 ) && ( windowsRevision < 829 ) ) ) { + + if( pMagSetFullscreenTransform && pMagSetInputTransform ) + g_fullScreenWorkaround = TRUE; + } + +#if 1 + // Calling this causes Windows to mess with our query of monitor height and width + if( pSetProcessDPIAware ) { + + pSetProcessDPIAware(); + } +#endif + /* Perform initializations that apply to a specific instance */ + g_hWndMain = InitInstance(hInstance, nCmdShow); + if (!g_hWndMain ) + return FALSE; + + HANDLE m_reload_settings_event_handle = NULL; + HANDLE m_exit_event_handle = NULL; + std::thread m_event_triggers_thread; + + if (g_StartedByPowerToys) { + // Start a thread to listen to PowerToys Events. + m_reload_settings_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_REFRESH_SETTINGS_EVENT); + m_exit_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_EXIT_EVENT); + if (!m_reload_settings_event_handle || !m_exit_event_handle) + { + Logger::warn(L"Failed to create events. {}", get_last_error_or_default(GetLastError())); + return 1; + } + m_event_triggers_thread = std::thread([&]() { + MSG msg; + HANDLE event_handles[2] = {m_reload_settings_event_handle, m_exit_event_handle}; + while (g_running) + { + DWORD dwEvt = MsgWaitForMultipleObjects(2, event_handles, false, INFINITE, QS_ALLINPUT); + if (!g_running) + { + break; + } + switch (dwEvt) + { + case WAIT_OBJECT_0: + { + // Reload Settings Event + Logger::trace(L"Received a reload settings event."); + PostMessage(g_hWndMain, WM_USER_RELOAD_SETTINGS, 0, 0); + break; + } + case WAIT_OBJECT_0 + 1: + { + // Exit Event + Logger::trace(L"Received an exit event."); + PostMessage(g_hWndMain, WM_QUIT, 0, 0); + break; + } + case WAIT_OBJECT_0 + 2: + if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + break; + default: + break; + } + } + }); + } + + /* Acquire and dispatch messages until a WM_QUIT message is received. */ + while (GetMessage(&msg, NULL, 0, 0 )) { + if( !TranslateAccelerator( g_hWndMain, hAccel, &msg )) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + int retCode = (int) msg.wParam; + + g_running = FALSE; + + if(g_StartedByPowerToys) + { + if (trace!=nullptr) { + trace->Flush(); + delete trace; + } + Trace::UnregisterProvider(); + // Needed to unblock MsgWaitForMultipleObjects one last time + SetEvent(m_reload_settings_event_handle); + CloseHandle(m_reload_settings_event_handle); + CloseHandle(m_exit_event_handle); + m_event_triggers_thread.join(); + } + + return retCode; +} diff --git a/src/modules/ZoomIt/ZoomIt/Zoomit.exe.manifest b/src/modules/ZoomIt/ZoomIt/Zoomit.exe.manifest new file mode 100644 index 000000000000..17abe5f21295 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/Zoomit.exe.manifest @@ -0,0 +1,36 @@ + + + + Sysinternals Zoomit + + + + + + + + + + + + + + + per monitor + PerMonitorV2 + + + diff --git a/src/modules/ZoomIt/ZoomIt/appicon.ico b/src/modules/ZoomIt/ZoomIt/appicon.ico new file mode 100644 index 000000000000..41feb27525e6 Binary files /dev/null and b/src/modules/ZoomIt/ZoomIt/appicon.ico differ diff --git a/src/modules/ZoomIt/ZoomIt/azure-pipelines.yml b/src/modules/ZoomIt/ZoomIt/azure-pipelines.yml new file mode 100644 index 000000000000..8e741d7c0cb2 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/azure-pipelines.yml @@ -0,0 +1,38 @@ +resources: + repositories: + - repository: templates + type: git + name: Pipelines + ref: refs/heads/amihaiuc/1b + - repository: oneBranchPipelines + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + +variables: +- template: templates/variables.yml@templates +- name: ToolName + value: 'ZoomIt' +- name: ToolBasePath + value: '' # Generally leave empty for non-PSTools + +trigger: +- main + +extends: + template: ${{ variables.originalTemplate }} + parameters: + globalSdl: + tsa: + enabled: false + stages: + - stage: build + displayName: 'Build and Sign' + jobs: + - template: jobs/build-and-sign-cpp.yml@templates + - stage: deploy_internal + displayName: 'Deploy Internal' + dependsOn: build + condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) + jobs: + - template: templates/deploy-tool.yml@templates diff --git a/src/modules/ZoomIt/ZoomIt/binres.rc b/src/modules/ZoomIt/ZoomIt/binres.rc new file mode 100644 index 000000000000..9585c75bc414 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/binres.rc @@ -0,0 +1,18 @@ +// +// This file allows for CPU architecture dependencies + +#ifdef _M_IX86 + +// +// x86 +// + +// To prevent Visual Studio dependency tracking logic from assuming that the x64 .res depend on the .exe that contain them, trick it with the macros +// The .rc's included below will be touched every time any of the .exe's mentioned in them is rebuilt thus guaranteeing the Win32 .rc will be recompiled too +#include "Dependency-x64.rc" + +RCZOOMIT64 BINRES MOVEABLE PURE RCZOOMIT_x64_path + +#endif + +CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "ZoomIt.exe.manifest" diff --git a/src/modules/ZoomIt/ZoomIt/cursor1.cur b/src/modules/ZoomIt/ZoomIt/cursor1.cur new file mode 100644 index 000000000000..048f06b4aefd Binary files /dev/null and b/src/modules/ZoomIt/ZoomIt/cursor1.cur differ diff --git a/src/modules/ZoomIt/ZoomIt/drawingc.cur b/src/modules/ZoomIt/ZoomIt/drawingc.cur new file mode 100644 index 000000000000..b5db12d01445 Binary files /dev/null and b/src/modules/ZoomIt/ZoomIt/drawingc.cur differ diff --git a/src/modules/ZoomIt/ZoomIt/icon1.ico b/src/modules/ZoomIt/ZoomIt/icon1.ico new file mode 100644 index 000000000000..8ba93283ac13 Binary files /dev/null and b/src/modules/ZoomIt/ZoomIt/icon1.ico differ diff --git a/src/modules/ZoomIt/ZoomIt/makefile b/src/modules/ZoomIt/ZoomIt/makefile new file mode 100644 index 000000000000..fcde8dfc9373 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/makefile @@ -0,0 +1,58 @@ + # Microsoft Developer Studio Generated NMAKE File, Based on procmon.dsp +!IF "$(CFG)" == "" +CFG=release +!MESSAGE No configuration specified. Defaulting to procmon - Release. +!ENDIF + +!IF "$(CFG)" != "release" && "$(CFG)" != "debug" +!MESSAGE Invalid configuration "$(CFG)" specified. +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE CFG="release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "release" +!MESSAGE "debug" +!MESSAGE +!ERROR An invalid configuration is specified. +!ENDIF + +!IF "$(OS)" == "Windows_NT" +NULL= +!ELSE +NULL=nul +!ENDIF + + +!IF "$(CFG)" == "release" + +ALL : SUBMODULEUPDATE ZOOMITX64 ZOOMIT64A ZOOMIT32 + +ZOOMITX64: + msbuild.exe -m ZoomIt.sln /p:Configuration=Release /p:Platform=x64 + +ZOOMIT64A: + msbuild.exe -m ZoomIt.sln /p:Configuration=Release /p:Platform=ARM64 + +ZOOMIT32: + msbuild.exe -m ZoomIt.sln /p:Configuration=Release /p:Platform=Win32 + +!ELSEIF "$(CFG)" == "debug" + +ALL : SUBMODULEUPDATE ZOOMITX64 ZOOMIT64A ZOOMIT32 + +ZOOMITX64: + msbuild.exe -m ZoomIt.sln /p:Configuration=Debug /p:Platform=x64 + +ZOOMIT64A: + msbuild.exe -m ZoomIt.sln /p:Configuration=Debug /p:Platform=ARM64 + +ZOOMIT32: + msbuild.exe -m ZoomIt.sln /p:Configuration=Debug /p:Platform=Win32 + +!ENDIF + +SUBMODULEUPDATE: + if not exist modules\Build\.git call modules\update.cmd diff --git a/src/modules/ZoomIt/ZoomIt/packages.config b/src/modules/ZoomIt/ZoomIt/packages.config new file mode 100644 index 000000000000..691158d1b2a7 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/modules/ZoomIt/ZoomIt/pch.cpp b/src/modules/ZoomIt/ZoomIt/pch.cpp new file mode 100644 index 000000000000..17305716aacd --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" \ No newline at end of file diff --git a/src/modules/ZoomIt/ZoomIt/pch.h b/src/modules/ZoomIt/ZoomIt/pch.h new file mode 100644 index 000000000000..2afdc4e5423b --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/pch.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Eula/eula.h" +#include "registry.h" +#include "resource.h" +#include "dll.h" +#define GDIPVER 0x0110 +#include + +// Must come before C++/WinRT +#include + +#include + +// WinRT +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// Direct3D wrappers to avoid implicitly linking to d3d11.dll; must come before declaration +#define CreateDirect3D11DeviceFromDXGIDevice WrapCreateDirect3D11DeviceFromDXGIDevice +#define CreateDirect3D11SurfaceFromDXGISurface WrapCreateDirect3D11SurfaceFromDXGISurface +#define D3D11CreateDevice WrapD3D11CreateDevice + +#include "VideoRecordingSession.h" +#include "SelectRectangle.h" +#include "DemoType.h" +#include "versionhelper.h" + +// WIL +#include +#include + +// DirectX +#include +#include +#include + + +// STL +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// robmikh.common +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/src/modules/ZoomIt/ZoomIt/publish_config.json b/src/modules/ZoomIt/ZoomIt/publish_config.json new file mode 100644 index 000000000000..4d4fe19b8e7e --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/publish_config.json @@ -0,0 +1,21 @@ +{ + "packageName": "ZoomIt.zip", + "files": [ + { + "name": "ZoomIt.exe", + "platform": "Win32", + "configuration": "Release" + }, + { + "name": "ZoomIt64.exe", + "platform": "x64", + "configuration": "Release" + }, + { + "name": "ZoomIt64a.exe", + "platform": "ARM64", + "configuration": "Release", + "skus": ["ARM64"] + } + ] +} \ No newline at end of file diff --git a/src/modules/ZoomIt/ZoomIt/resource.h b/src/modules/ZoomIt/ZoomIt/resource.h new file mode 100644 index 000000000000..c4c0cfad8747 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/resource.h @@ -0,0 +1,115 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by ZoomIt.rc +// +////////////////////////////// +// Non-localizable + +#define FILE_DESCRIPTION "Sysinternals Screen Magnifier" +#define INTERNAL_NAME "ZoomIt" +#define ORIGINAL_FILENAME "PowerToys.ZoomIt.exe" + +// Non-localizable +////////////////////////////// +#define IDC_AUDIO 117 +#define IDC_LINK 1000 +#define IDC_ALT 1001 +#define IDC_CTRL 1002 +#define IDC_TOGGLE 1003 +#define IDC_OPTIONS 1004 +#define IDC_COLOR_PICK 1005 +#define IDC_COLOR 1006 +#define IDC_DRAW 1007 +#define IDC_TITLE 1008 +#define IDC_VERSION 1008 +#define IDC_ZOOM 1009 +#define IDC_DRAWING 1010 +#define IDC_BREAK 1011 +#define IDC_HOTKEY 1014 +#define IDC_DRAW_HOTKEY 1015 +#define IDC_HOTKEY2 1015 +#define IDC_LIVE_HOTKEY 1015 +#define IDC_BREAK_HOTKEY 1016 +#define IDC_SOUND_FILE 1017 +#define IDC_BACKGROUND_FILE 1018 +#define IDC_SPIN 1022 +#define IDC_SPIN_TIMER 1023 +#define IDC_SOUND_BROWSE 1025 +#define IDC_OPACITY 1026 +#define IDC_CHECK1 1027 +#define IDC_ADVANCED_BREAK 1027 +#define IDC_CHECK_SOUND_FILE 1027 +#define IDC_CHECK_BACKGROUND_FILE 1028 +#define IDC_BACKGROUND_BROWSE 1029 +#define IDC_TIMER_POS1 1030 +#define IDC_TIMER_POS2 1031 +#define IDC_TIMER_POS3 1032 +#define IDC_TIMER_POS4 1033 +#define IDC_TIMER_POS5 1034 +#define IDC_TIMER_POS6 1035 +#define IDC_TIMER_POS7 1036 +#define IDC_TIMER_POS8 1037 +#define IDC_TIMER_POS9 1038 +#define IDC_STATIC_SOUND_FILE 1039 +#define IDC_EDIT1 1040 +#define IDC_STATIC_BACKGROUND_FILE 1040 +#define IDC_TYPE 1041 +#define IDC_CHECK2 1042 +#define IDC_CHECK_SHOW_EXPIRED 1042 +#define IDC_TRAY_ICON 1042 +#define IDC_HIDE_TRAY_ICON 1042 +#define IDC_SHOW_TRAY_ICON 1042 +#define IDC_AUTOSTART 1043 +#define IDC_CHECK_BACKGROUND_STRETCH 1046 +#define IDC_STATIC_DESKTOP_BACKGROUND 1047 +#define IDC_STATIC_DESKTOP_BACKGROUND 1047 +#define IDC_TAB 1050 +#define IDC_FONT 1051 +#define IDC_ZOOM_SPIN_TIMER 1052 +#define IDC_ZOOM_SPIN 1052 +#define IDC_ZOOM_LEVEL 1053 +#define IDC_TEXT_FONT 1054 +#define IDC_ZOOM_SLIDER 1056 +#define IDC_ANIMATE_ZOOM 1057 +#define IDC_COMBO1 1058 +#define IDC_RECORD_FRAME_RATE 1058 +#define IDC_SPIN1 1059 +#define IDC_RECORD_FRAME_RATE2 1059 +#define IDC_RECORD_SCALING 1059 +#define IDC_SNIP_HOTKEY 1060 +#define IDC_CAPTURE_AUDIO 1061 +#define IDC_MICROPHONE 1062 +#define IDC_PEN_CONTROL 1063 +#define IDC_COLORS 1064 +#define IDC_HIGHLIGHT_AND_BLUR 1065 +#define IDC_SHAPES 1066 +#define IDC_SCREEN 1067 +#define IDC_DEMOTYPE_TEXT 1068 +#define IDC_DEMOTYPE_BROWSE 1069 +#define IDC_DEMOTYPE_FILE 1070 +#define IDC_DEMOTYPE_SPEED_SLIDER 1071 +#define IDC_DEMOTYPE_USER_DRIVEN 1072 +#define IDC_DEMOTYPE_STATIC1 1073 +#define IDC_DEMOTYPE_SLIDER2 1074 +#define IDC_DEMOTYPE_STATIC2 1074 +#define IDC_COPYRIGHT 1075 +#define IDC_PEN_WIDTH 1105 +#define IDC_TIMER 1106 +#define IDC_SAVE 40002 +#define IDC_COPY 40004 +#define IDC_RECORD 40006 +#define IDC_RECORD_HOTKEY 40007 +#define IDC_COPY_CROP 40008 +#define IDC_SAVE_CROP 40009 +#define IDC_DEMOTYPE_HOTKEY 40011 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 118 +#define _APS_NEXT_COMMAND_VALUE 40013 +#define _APS_NEXT_CONTROL_VALUE 1076 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/modules/ZoomIt/ZoomIt/version.h b/src/modules/ZoomIt/ZoomIt/version.h new file mode 100644 index 000000000000..3324b4ccc07d --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/version.h @@ -0,0 +1,39 @@ +#pragma once +//============================================================================\ +// +// Version +// +// File version information functions. +// +//============================================================================ + + +// +// File version information +// +typedef struct { + WORD wLength; + WORD wValueLength; + WORD wType; + WCHAR szKey[16]; + WORD Padding1; + VS_FIXEDFILEINFO Value; +} VERSION_INFO, * P_VERSION_INFO; + + +// +// Version translation +// +typedef struct { + WORD langID; // language ID + WORD charset; // character set (code page) +} VERSION_TRANSLATION, * P_VERSION_TRANSLATION; + +typedef VS_FIXEDFILEINFO* P_VS_FIXEDFILEINFO; + +#define FILEINFOSIG 0xFEEF04BD + + +const TCHAR* GetVersionString(const VERSION_INFO* VersionInfo, + const TCHAR* VersionString); + diff --git a/src/modules/ZoomIt/ZoomItModuleInterface/ZoomItModuleInterface.rc b/src/modules/ZoomIt/ZoomItModuleInterface/ZoomItModuleInterface.rc new file mode 100644 index 000000000000..5fa3c8b90d58 --- /dev/null +++ b/src/modules/ZoomIt/ZoomItModuleInterface/ZoomItModuleInterface.rc @@ -0,0 +1,40 @@ +#include +#include "resource.h" +#include "../../../common/version/version.h" + +#define APSTUDIO_READONLY_SYMBOLS +#include "winres.h" +#undef APSTUDIO_READONLY_SYMBOLS + +1 VERSIONINFO +FILEVERSION FILE_VERSION +PRODUCTVERSION PRODUCT_VERSION +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG +FILEFLAGS VS_FF_DEBUG +#else +FILEFLAGS 0x0L +#endif +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_DLL +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset + BEGIN + VALUE "CompanyName", COMPANY_NAME + VALUE "FileDescription", FILE_DESCRIPTION + VALUE "FileVersion", FILE_VERSION_STRING + VALUE "InternalName", INTERNAL_NAME + VALUE "LegalCopyright", COPYRIGHT_NOTE + VALUE "OriginalFilename", ORIGINAL_FILENAME + VALUE "ProductName", PRODUCT_NAME + VALUE "ProductVersion", PRODUCT_VERSION_STRING + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset + END +END diff --git a/src/modules/ZoomIt/ZoomItModuleInterface/ZoomItModuleInterface.vcxproj b/src/modules/ZoomIt/ZoomItModuleInterface/ZoomItModuleInterface.vcxproj new file mode 100644 index 000000000000..c922d389693a --- /dev/null +++ b/src/modules/ZoomIt/ZoomItModuleInterface/ZoomItModuleInterface.vcxproj @@ -0,0 +1,113 @@ + + + + + 17.0 + Win32Proj + {e4585179-2ac1-4d5f-a3ff-cfc5392f694c} + ZoomItModuleInterface + ZoomItModuleInterface + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + ..\..\..\..\$(Platform)\$(Configuration)\ + PowerToys.ZoomItModuleInterface + + + + ..\;$(SolutionDir)src\common\Telemetry;$(SolutionDir)src;%(AdditionalIncludeDirectories) + + + $(OutDir)$(TargetName)$(TargetExt) + %(AdditionalDependencies) + + + + + true + _DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + + + Windows + true + false + + + + + true + true + true + NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + false + + + + + + + + + + + + + + + + + Create + + + + + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + + {6955446d-23f7-4023-9bb3-8657f904af99} + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/src/modules/ZoomIt/ZoomItModuleInterface/ZoomItModuleInterface.vcxproj.filters b/src/modules/ZoomIt/ZoomItModuleInterface/ZoomItModuleInterface.vcxproj.filters new file mode 100644 index 000000000000..176397300361 --- /dev/null +++ b/src/modules/ZoomIt/ZoomItModuleInterface/ZoomItModuleInterface.vcxproj.filters @@ -0,0 +1,47 @@ + + + + + {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 + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + + + + Resource Files + + + Header Files + + + Header Files + + + + + Resource Files + + + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/src/modules/ZoomIt/ZoomItModuleInterface/dllmain.cpp b/src/modules/ZoomIt/ZoomItModuleInterface/dllmain.cpp new file mode 100644 index 000000000000..eea809a0a2b5 --- /dev/null +++ b/src/modules/ZoomIt/ZoomItModuleInterface/dllmain.cpp @@ -0,0 +1,233 @@ +#include "pch.h" + +#include +#include + +#include "trace.h" +#include +#include +#include +#include + +#include +#include + +namespace NonLocalizable +{ + const wchar_t ModulePath[] = L"PowerToys.ZoomIt.exe"; + const inline wchar_t ModuleKey[] = L"ZoomIt"; +} + +BOOL APIENTRY DllMain( HMODULE /*hModule*/, + DWORD ul_reason_for_call, + LPVOID /*lpReserved*/ + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + Trace::RegisterProvider(); + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + case DLL_PROCESS_DETACH: + Trace::UnregisterProvider(); + break; + } + return TRUE; + +} + +class ZoomItModuleInterface : public PowertoyModuleIface +{ +public: + // Return the localized display name of the powertoy + virtual PCWSTR get_name() override + { + return app_name.c_str(); + } + + // Return the non localized key of the powertoy, this will be cached by the runner + virtual const wchar_t* get_key() override + { + return app_key.c_str(); + } + + // Return the configured status for the gpo policy for the module + virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override + { + return powertoys_gpo::getConfiguredZoomItEnabledValue(); + } + + // Return JSON with the configuration options. + // These are the settings shown on the settings page along with their current values. + virtual bool get_config(wchar_t* buffer, int* buffer_size) override + { + HINSTANCE hinstance = reinterpret_cast(&__ImageBase); + + // TODO: Read settings from Registry. + + // Create a Settings object. + PowerToysSettings::Settings settings(hinstance, get_name()); + + return settings.serialize_to_buffer(buffer, buffer_size); + } + + // Passes JSON with the configuration settings for the powertoy. + // This is called when the user hits Save on the settings page. + virtual void set_config(const wchar_t*) override + { + try + { + // Parse the input JSON string. + // TODO: Save settings to registry. + } + catch (std::exception&) + { + // Improper JSON. + } + } + + // Enable the powertoy + virtual void enable() + { + Logger::info("ZoomIt enabling"); + Enable(); + } + + // Disable the powertoy + virtual void disable() + { + Logger::info("ZoomIt disabling"); + Disable(true); + } + + // Returns if the powertoy is enabled + virtual bool is_enabled() override + { + return m_enabled; + } + + // Destroy the powertoy and free memory + virtual void destroy() override + { + Disable(false); + delete this; + } + + ZoomItModuleInterface() + { + app_name = L"ZoomIt"; + app_key = NonLocalizable::ModuleKey; + LoggerHelpers::init_logger(app_key, L"ModuleInterface", LogSettings::zoomItLoggerName); + m_reload_settings_event_handle = CreateDefaultEvent(CommonSharedConstants::ZOOMIT_REFRESH_SETTINGS_EVENT); + m_exit_event_handle = CreateDefaultEvent(CommonSharedConstants::ZOOMIT_EXIT_EVENT); + } + +private: + bool is_enabled_by_default() const override + { + return false; + } + + void Enable() + { + m_enabled = true; + + // Log telemetry + Trace::EnableZoomIt(true); + + // Pass the PID. + unsigned long powertoys_pid = GetCurrentProcessId(); + std::wstring executable_args = L""; + executable_args.append(std::to_wstring(powertoys_pid)); + + ResetEvent(m_reload_settings_event_handle); + ResetEvent(m_exit_event_handle); + + SHELLEXECUTEINFOW sei{ sizeof(sei) }; + sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI }; + sei.lpFile = NonLocalizable::ModulePath; + sei.nShow = SW_SHOWNORMAL; + sei.lpParameters = executable_args.data(); + if (ShellExecuteExW(&sei) == false) + { + Logger::error(L"Failed to start zoomIt"); + auto message = get_last_error_message(GetLastError()); + if (message.has_value()) + { + Logger::error(message.value()); + } + } + else + { + m_hProcess = sei.hProcess; + } + + } + + void Disable(bool const traceEvent) + { + m_enabled = false; + + // Log telemetry + if (traceEvent) + { + Trace::EnableZoomIt(false); + } + + // Tell the ZoomIt process to exit. + SetEvent(m_exit_event_handle); + + ResetEvent(m_reload_settings_event_handle); + + // Wait for 1.5 seconds for the process to end correctly and stop etw tracer + WaitForSingleObject(m_hProcess, 1500); + + // If process is still running, terminate it + if (m_hProcess) + { + TerminateProcess(m_hProcess, 0); + m_hProcess = nullptr; + } + + } + + bool is_process_running() + { + return WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT; + } + + virtual void call_custom_action(const wchar_t* action) override + { + try + { + PowerToysSettings::CustomActionObject action_object = + PowerToysSettings::CustomActionObject::from_json_string(action); + + if (action_object.get_name() == L"refresh_settings") + { + SetEvent(m_reload_settings_event_handle); + } + } + catch (std::exception&) + { + Logger::error(L"Failed to parse action. {}", action); + } + } + + std::wstring app_name; + std::wstring app_key; //contains the non localized key of the powertoy + + bool m_enabled = false; + HANDLE m_hProcess = nullptr; + + HANDLE m_reload_settings_event_handle = NULL; + HANDLE m_exit_event_handle = NULL; +}; + +extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() +{ + return new ZoomItModuleInterface(); +} diff --git a/src/modules/ZoomIt/ZoomItModuleInterface/packages.config b/src/modules/ZoomIt/ZoomItModuleInterface/packages.config new file mode 100644 index 000000000000..09bfc449e21c --- /dev/null +++ b/src/modules/ZoomIt/ZoomItModuleInterface/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/modules/ZoomIt/ZoomItModuleInterface/pch.cpp b/src/modules/ZoomIt/ZoomItModuleInterface/pch.cpp new file mode 100644 index 000000000000..64b7eef6d6b9 --- /dev/null +++ b/src/modules/ZoomIt/ZoomItModuleInterface/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/src/modules/ZoomIt/ZoomItModuleInterface/pch.h b/src/modules/ZoomIt/ZoomItModuleInterface/pch.h new file mode 100644 index 000000000000..93ddcefad54c --- /dev/null +++ b/src/modules/ZoomIt/ZoomItModuleInterface/pch.h @@ -0,0 +1,9 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#include +#include +#include +#include +#include +#include diff --git a/src/modules/ZoomIt/ZoomItModuleInterface/resource.h b/src/modules/ZoomIt/ZoomItModuleInterface/resource.h new file mode 100644 index 000000000000..cf73380cafea --- /dev/null +++ b/src/modules/ZoomIt/ZoomItModuleInterface/resource.h @@ -0,0 +1,13 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by ZoomItModuleInterface.rc + +////////////////////////////// +// Non-localizable + +#define FILE_DESCRIPTION "PowerToys ZoomItModuleInterface" +#define INTERNAL_NAME "PowerToys.ZoomItModuleInterface" +#define ORIGINAL_FILENAME "PowerToys.ZoomItModuleInterface.dll" + +// Non-localizable +////////////////////////////// diff --git a/src/modules/ZoomIt/ZoomItModuleInterface/trace.cpp b/src/modules/ZoomIt/ZoomItModuleInterface/trace.cpp new file mode 100644 index 000000000000..575c9d3338e3 --- /dev/null +++ b/src/modules/ZoomIt/ZoomItModuleInterface/trace.cpp @@ -0,0 +1,93 @@ +#include "pch.h" +#include "trace.h" +#include + +TRACELOGGING_DEFINE_PROVIDER( + g_hProvider, + "Microsoft.PowerToys", + // {38e8889b-9731-53f5-e901-e8a7c1753074} + (0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74), + TraceLoggingOptionProjectTelemetry()); + +// Log if the user has ZoomIt enabled or disabled +void Trace::EnableZoomIt(const bool enabled) noexcept +{ + TraceLoggingWriteWrapper( + g_hProvider, + "ZoomIt_EnableZoomIt", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), + TraceLoggingBoolean(enabled, "Enabled")); +} + +void Trace::ZoomItStarted() noexcept +{ + TraceLoggingWriteWrapper( + g_hProvider, + "ZoomIt_Started", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); +} + +void Trace::ZoomItActivateBreak() noexcept +{ + TraceLoggingWriteWrapper( + g_hProvider, + "ZoomIt_ActivateBreak", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); +} + +void Trace::ZoomItActivateDraw() noexcept +{ + TraceLoggingWriteWrapper( + g_hProvider, + "ZoomIt_ActivateDraw", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); +} + +void Trace::ZoomItActivateZoom() noexcept +{ + TraceLoggingWriteWrapper( + g_hProvider, + "ZoomIt_ActivateZoom", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); +} + +void Trace::ZoomItActivateLiveZoom() noexcept +{ + TraceLoggingWriteWrapper( + g_hProvider, + "ZoomIt_ActivateLiveZoom", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); +} + +void Trace::ZoomItActivateDemoType() noexcept +{ + TraceLoggingWriteWrapper( + g_hProvider, + "ZoomIt_ActivateDemoType", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); +} + +void Trace::ZoomItActivateRecord() noexcept +{ + TraceLoggingWriteWrapper( + g_hProvider, + "ZoomIt_ActivateRecord", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); +} + +void Trace::ZoomItActivateSnip() noexcept +{ + TraceLoggingWriteWrapper( + g_hProvider, + "ZoomIt_ActivateSnip", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); +} diff --git a/src/modules/ZoomIt/ZoomItModuleInterface/trace.h b/src/modules/ZoomIt/ZoomItModuleInterface/trace.h new file mode 100644 index 000000000000..fafd579a0e65 --- /dev/null +++ b/src/modules/ZoomIt/ZoomItModuleInterface/trace.h @@ -0,0 +1,17 @@ +#pragma once + +#include +class Trace : public telemetry::TraceBase +{ +public: + // Log if the user has ZoomIt enabled or disabled + static void EnableZoomIt(const bool enabled) noexcept; + static void ZoomItStarted() noexcept; + static void ZoomItActivateBreak() noexcept; + static void ZoomItActivateDraw() noexcept; + static void ZoomItActivateZoom() noexcept; + static void ZoomItActivateLiveZoom() noexcept; + static void ZoomItActivateDemoType() noexcept; + static void ZoomItActivateRecord() noexcept; + static void ZoomItActivateSnip() noexcept; +}; diff --git a/src/modules/ZoomIt/ZoomItSettingsInterop/PropertySheet.props b/src/modules/ZoomIt/ZoomItSettingsInterop/PropertySheet.props new file mode 100644 index 000000000000..e34141b019cc --- /dev/null +++ b/src/modules/ZoomIt/ZoomItSettingsInterop/PropertySheet.props @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettings.cpp b/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettings.cpp new file mode 100644 index 000000000000..3a197714b782 --- /dev/null +++ b/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettings.cpp @@ -0,0 +1,274 @@ +#include "pch.h" +#include "ZoomItSettings.h" +#include "ZoomItSettings.g.cpp" +#include "../ZoomIt/ZoomItSettings.h" +#include +#include +#include +#pragma comment(lib, "Crypt32.lib") // For the CryptStringToBinaryW and CryptBinaryToStringW functions + +namespace winrt::PowerToys::ZoomItSettingsInterop::implementation +{ + ClassRegistry reg(_T("Software\\Sysinternals\\") APPNAME); + + const unsigned int SPECIAL_SEMANTICS_SHORTCUT = 1; + const unsigned int SPECIAL_SEMANTICS_COLOR = 2; + const unsigned int SPECIAL_SEMANTICS_LOG_FONT = 3; + + std::vector base64_decode(const std::wstring& base64_string) + { + DWORD binary_len = 0; + // Get the required buffer size for the binary data + if (!CryptStringToBinaryW(base64_string.c_str(), 0, CRYPT_STRING_BASE64, nullptr, &binary_len, nullptr, nullptr)) + { + throw std::runtime_error("Error in CryptStringToBinaryW (getting size)"); + } + + std::vector binary_data(binary_len); + + // Decode the Base64 string into binary data + if (!CryptStringToBinaryW(base64_string.c_str(), 0, CRYPT_STRING_BASE64, binary_data.data(), &binary_len, nullptr, nullptr)) + { + throw std::runtime_error("Error in CryptStringToBinaryW (decoding)"); + } + + return binary_data; + } + + std::wstring base64_encode(const unsigned char* data, size_t length) + { + DWORD base64_len = 0; + // Get the required buffer size for Base64 string + if (!CryptBinaryToStringW(data, static_cast(length), CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, nullptr, &base64_len)) + { + throw std::runtime_error("Error in CryptBinaryToStringW (getting size)"); + } + + std::wstring base64_string(base64_len, '\0'); + + // Encode the binary data to Base64 + if (!CryptBinaryToStringW(data, static_cast(length), CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, &base64_string[0], &base64_len)) + { + throw std::runtime_error("Error in CryptBinaryToStringW (encoding)"); + } + + // Resize the wstring to remove any trailing null character. + if (!base64_string.empty() && base64_string.back() == L'\0') + { + base64_string.pop_back(); + } + + return base64_string; + } + + std::map settings_with_special_semantics = { + { L"ToggleKey", SPECIAL_SEMANTICS_SHORTCUT }, + { L"LiveZoomToggleKey", SPECIAL_SEMANTICS_SHORTCUT }, + { L"DrawToggleKey", SPECIAL_SEMANTICS_SHORTCUT }, + { L"RecordToggleKey", SPECIAL_SEMANTICS_SHORTCUT }, + { L"SnipToggleKey", SPECIAL_SEMANTICS_SHORTCUT }, + { L"BreakTimerKey", SPECIAL_SEMANTICS_SHORTCUT }, + { L"DemoTypeToggleKey", SPECIAL_SEMANTICS_SHORTCUT }, + { L"PenColor", SPECIAL_SEMANTICS_COLOR }, + { L"BreakPenColor", SPECIAL_SEMANTICS_COLOR }, + { L"Font", SPECIAL_SEMANTICS_LOG_FONT }, + }; + + hstring ZoomItSettings::LoadSettingsJson() + { + PowerToysSettings::PowerToyValues _settings(L"ZoomIt",L"ZoomIt"); + reg.ReadRegSettings(RegSettings); + PREG_SETTING curSetting = RegSettings; + while (curSetting->ValueName) + { + switch (curSetting->Type) + { + case SETTING_TYPE_DWORD: + { + auto special_semantics = settings_with_special_semantics.find(curSetting->ValueName); + DWORD value = *static_cast(curSetting->Setting); + if (special_semantics == settings_with_special_semantics.end()) + { + _settings.add_property(curSetting->ValueName, value); + } + else + { + if (special_semantics->second == SPECIAL_SEMANTICS_SHORTCUT) + { + auto hotkey = PowerToysSettings::HotkeyObject::from_settings( + value & (HOTKEYF_EXT << 8), //WIN + value & (HOTKEYF_CONTROL << 8), + value & (HOTKEYF_ALT << 8), + value & (HOTKEYF_SHIFT << 8), + value & 0xFF); + _settings.add_property(curSetting->ValueName, hotkey.get_json()); + } + else if (special_semantics->second == SPECIAL_SEMANTICS_COLOR) + { + // PowerToys settings likes colors as #FFFFFF strings. + hstring s = winrt::to_hstring(std::format("#{:02x}{:02x}{:02x}", value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF)); + _settings.add_property(curSetting->ValueName, s); + } + } + break; + } + case SETTING_TYPE_BOOLEAN: + _settings.add_property(curSetting->ValueName, *static_cast(curSetting->Setting)); + break; + case SETTING_TYPE_DOUBLE: + assert(false); // ZoomIt doesn't use this type of setting. + break; + case SETTING_TYPE_WORD: + assert(false); // ZoomIt doesn't use this type of setting. + break; + case SETTING_TYPE_STRING: + _settings.add_property(curSetting->ValueName, static_cast(curSetting->Setting)); + break; + case SETTING_TYPE_DWORD_ARRAY: + assert(false); // ZoomIt doesn't use this type of setting. + break; + case SETTING_TYPE_WORD_ARRAY: + assert(false); // ZoomIt doesn't use this type of setting. + break; + case SETTING_TYPE_BINARY: + auto special_semantics = settings_with_special_semantics.find(curSetting->ValueName); + if (special_semantics != settings_with_special_semantics.end() && special_semantics->second == SPECIAL_SEMANTICS_LOG_FONT) + { + // This is the font setting. It's a special case where the default value needs to be calculated if it's still 0. + if (g_LogFont.lfFaceName[0] == L'\0') + { + GetObject(GetStockObject(DEFAULT_GUI_FONT), sizeof g_LogFont, &g_LogFont); + g_LogFont.lfWeight = FW_NORMAL; + auto hDc = CreateCompatibleDC(NULL); + g_LogFont.lfHeight = -MulDiv(8, GetDeviceCaps(hDc, LOGPIXELSY), 72); + DeleteDC(hDc); + } + } + + // Base64 encoding is likely the best way to serialize a byte array into JSON. + auto encodedFont = base64_encode(static_cast(curSetting->Setting), curSetting->Size); + _settings.add_property(curSetting->ValueName, encodedFont); + break; + } + curSetting++; + } + + return _settings.get_raw_json().Stringify(); + } + + void ZoomItSettings::SaveSettingsJson(hstring json) + { + reg.ReadRegSettings(RegSettings); + + // Parse the input JSON string. + PowerToysSettings::PowerToyValues valuesFromSettings = + PowerToysSettings::PowerToyValues::from_json_string(json, L"ZoomIt"); + + PREG_SETTING curSetting = RegSettings; + while (curSetting->ValueName) + { + switch (curSetting->Type) + { + case SETTING_TYPE_DWORD: + { + auto special_semantics = settings_with_special_semantics.find(curSetting->ValueName); + if (special_semantics == settings_with_special_semantics.end()) + { + auto possibleValue = valuesFromSettings.get_uint_value(curSetting->ValueName); + if (possibleValue.has_value()) + { + *static_cast(curSetting->Setting) = possibleValue.value(); + } + } + else + { + if (special_semantics->second == SPECIAL_SEMANTICS_SHORTCUT) + { + auto possibleValue = valuesFromSettings.get_json(curSetting->ValueName); + if (possibleValue.has_value()) + { + auto hotkey = PowerToysSettings::HotkeyObject::from_json(possibleValue.value()); + unsigned int value = 0; + value |= hotkey.get_code(); + if (hotkey.ctrl_pressed()) + { + value |= (HOTKEYF_CONTROL << 8); + } + if (hotkey.alt_pressed()) + { + value |= (HOTKEYF_ALT << 8); + } + if (hotkey.shift_pressed()) + { + value |= (HOTKEYF_SHIFT << 8); + } + if (hotkey.win_pressed()) + { + value |= (HOTKEYF_EXT << 8); + } + *static_cast(curSetting->Setting) = value; + } + } + else if (special_semantics->second == SPECIAL_SEMANTICS_COLOR) + { + auto possibleValue = valuesFromSettings.get_string_value(curSetting->ValueName); + if (possibleValue.has_value()) + { + uint8_t r, g, b; + if (checkValidRGB(possibleValue.value(), &r, &g, &b)) + { + *static_cast(curSetting->Setting) = RGB(r, g, b); + } + + } + } + } + break; + } + case SETTING_TYPE_BOOLEAN: + { + auto possibleValue = valuesFromSettings.get_bool_value(curSetting->ValueName); + if (possibleValue.has_value()) + { + *static_cast(curSetting->Setting) = static_cast(possibleValue.value()); + } + break; + } + case SETTING_TYPE_DOUBLE: + assert(false); // ZoomIt doesn't use this type of setting. + break; + case SETTING_TYPE_WORD: + assert(false); // ZoomIt doesn't use this type of setting. + break; + case SETTING_TYPE_STRING: + { + auto possibleValue = valuesFromSettings.get_string_value(curSetting->ValueName); + if (possibleValue.has_value()) + { + const TCHAR* value = possibleValue.value().c_str(); + _tcscpy_s(static_cast(curSetting->Setting), curSetting->Size / sizeof(TCHAR), value); + } + break; + } + case SETTING_TYPE_DWORD_ARRAY: + assert(false); // ZoomIt doesn't use this type of setting. + break; + case SETTING_TYPE_WORD_ARRAY: + assert(false); // ZoomIt doesn't use this type of setting. + break; + case SETTING_TYPE_BINARY: + auto possibleValue = valuesFromSettings.get_string_value(curSetting->ValueName); + if (possibleValue.has_value()) + { + // Base64 encoding is likely the best way to serialize a byte array into JSON. + auto decodedValue = base64_decode(possibleValue.value()); + assert(curSetting->Size == decodedValue.size()); // Should right now only be used for LOGFONT, so let's hard check it to avoid any insecure overflows. + memcpy(static_cast(curSetting->Setting), decodedValue.data(), decodedValue.size()); + } + break; + } + curSetting++; + } + reg.WriteRegSettings(RegSettings); + } +} diff --git a/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettings.h b/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettings.h new file mode 100644 index 000000000000..9ea60451c002 --- /dev/null +++ b/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettings.h @@ -0,0 +1,20 @@ +#pragma once + +#include "ZoomItSettings.g.h" + +namespace winrt::PowerToys::ZoomItSettingsInterop::implementation +{ + struct ZoomItSettings : ZoomItSettingsT + { + ZoomItSettings() = default; + static hstring LoadSettingsJson(); + static void SaveSettingsJson(hstring json); + }; +} + +namespace winrt::PowerToys::ZoomItSettingsInterop::factory_implementation +{ + struct ZoomItSettings : ZoomItSettingsT + { + }; +} diff --git a/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettings.idl b/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettings.idl new file mode 100644 index 000000000000..bd4d1915db20 --- /dev/null +++ b/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettings.idl @@ -0,0 +1,10 @@ +namespace PowerToys +{ + namespace ZoomItSettingsInterop + { + [default_interface] static runtimeclass ZoomItSettings { + static String LoadSettingsJson(); + static void SaveSettingsJson(String json); + } + } +} diff --git a/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettingsInterop.def b/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettingsInterop.def new file mode 100644 index 000000000000..24e7c1235c39 --- /dev/null +++ b/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettingsInterop.def @@ -0,0 +1,3 @@ +EXPORTS +DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE +DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE diff --git a/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettingsInterop.rc b/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettingsInterop.rc new file mode 100644 index 000000000000..5fa3c8b90d58 --- /dev/null +++ b/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettingsInterop.rc @@ -0,0 +1,40 @@ +#include +#include "resource.h" +#include "../../../common/version/version.h" + +#define APSTUDIO_READONLY_SYMBOLS +#include "winres.h" +#undef APSTUDIO_READONLY_SYMBOLS + +1 VERSIONINFO +FILEVERSION FILE_VERSION +PRODUCTVERSION PRODUCT_VERSION +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG +FILEFLAGS VS_FF_DEBUG +#else +FILEFLAGS 0x0L +#endif +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_DLL +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset + BEGIN + VALUE "CompanyName", COMPANY_NAME + VALUE "FileDescription", FILE_DESCRIPTION + VALUE "FileVersion", FILE_VERSION_STRING + VALUE "InternalName", INTERNAL_NAME + VALUE "LegalCopyright", COPYRIGHT_NOTE + VALUE "OriginalFilename", ORIGINAL_FILENAME + VALUE "ProductName", PRODUCT_NAME + VALUE "ProductVersion", PRODUCT_VERSION_STRING + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset + END +END diff --git a/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettingsInterop.vcxproj b/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettingsInterop.vcxproj new file mode 100644 index 000000000000..21998a40dae8 --- /dev/null +++ b/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettingsInterop.vcxproj @@ -0,0 +1,132 @@ + + + + + true + true + true + true + {ca7d8106-30B9-4aec-9d05-b69b31b8c461} + ZoomItSettingsInterop + PowerToys.ZoomItSettingsInterop + en-US + 14.0 + false + false + Windows Store + 10.0 + + + + DynamicLibrary + v143 + Unicode + false + + + true + true + + + false + true + false + + + + + + + + + + + + + + + PowerToys.ZoomItSettingsInterop + ..\..\..\..\$(Platform)\$(Configuration)\ + + + + $(IntDir)pch.pch + Level4 + %(AdditionalOptions) /bigobj + _WINRT_DLL;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) + ../../..;%(AdditionalIncludeDirectories) + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + + + Console + false + ZoomItSettingsInterop.def + Shell32.lib;gdi32.lib;%(AdditionalDependencies) + + + + + _DEBUG;%(PreprocessorDefinitions) + + + + + NDEBUG;%(PreprocessorDefinitions) + + + true + true + + + + + + ZoomItSettings.idl + + + + + + Create + + + ZoomItSettings.idl + + + + + + + + + + + + + + + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + + {6955446d-23f7-4023-9bb3-8657f904af99} + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettingsInterop.vcxproj.filters b/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettingsInterop.vcxproj.filters new file mode 100644 index 000000000000..056f948c9572 --- /dev/null +++ b/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettingsInterop.vcxproj.filters @@ -0,0 +1,35 @@ + + + + + {de682ddf-17ab-471d-9761-82b42e6baa70} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tga;tiff;tif;png;wav;mfcribbon-ms + + + {c02d42a3-682e-499a-8b28-638a0802d43f} + + + + + + + + + + + + + + + + + + + + + + + Resources + + + \ No newline at end of file diff --git a/src/modules/ZoomIt/ZoomItSettingsInterop/packages.config b/src/modules/ZoomIt/ZoomItSettingsInterop/packages.config new file mode 100644 index 000000000000..ff4b05964868 --- /dev/null +++ b/src/modules/ZoomIt/ZoomItSettingsInterop/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/modules/ZoomIt/ZoomItSettingsInterop/pch.cpp b/src/modules/ZoomIt/ZoomItSettingsInterop/pch.cpp new file mode 100644 index 000000000000..bcb5590be1b3 --- /dev/null +++ b/src/modules/ZoomIt/ZoomItSettingsInterop/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/src/modules/ZoomIt/ZoomItSettingsInterop/pch.h b/src/modules/ZoomIt/ZoomItSettingsInterop/pch.h new file mode 100644 index 000000000000..ea0275969f54 --- /dev/null +++ b/src/modules/ZoomIt/ZoomItSettingsInterop/pch.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include +#include +#include + +#define GDIPVER 0x0110 +#include +// DirectX +#include +#include +#include + +// Must come before C++/WinRT +#include + +#include + +#include +#include diff --git a/src/modules/ZoomIt/ZoomItSettingsInterop/resource.h b/src/modules/ZoomIt/ZoomItSettingsInterop/resource.h new file mode 100644 index 000000000000..73711ef9a9a7 --- /dev/null +++ b/src/modules/ZoomIt/ZoomItSettingsInterop/resource.h @@ -0,0 +1,13 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by ZoomItSettingsInterop.rc + +////////////////////////////// +// Non-localizable + +#define FILE_DESCRIPTION "PowerToys ZoomItSettingsInterop" +#define INTERNAL_NAME "PowerToys.ZoomItSettingsInterop" +#define ORIGINAL_FILENAME "PowerToys.ZoomItSettingsInterop.dll" + +// Non-localizable +////////////////////////////// diff --git a/src/runner/main.cpp b/src/runner/main.cpp index ba893678c1a9..5950a3bcfc39 100644 --- a/src/runner/main.cpp +++ b/src/runner/main.cpp @@ -163,6 +163,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow L"PowerToys.CropAndLockModuleInterface.dll", L"PowerToys.CmdNotFoundModuleInterface.dll", L"PowerToys.WorkspacesModuleInterface.dll", + L"PowerToys.ZoomItModuleInterface.dll", }; const auto VCM_PATH = L"PowerToys.VideoConferenceModule.dll"; if (const auto mf = LoadLibraryA("mf.dll")) diff --git a/src/runner/settings_window.cpp b/src/runner/settings_window.cpp index 4c2787194eda..ace230c1fb71 100644 --- a/src/runner/settings_window.cpp +++ b/src/runner/settings_window.cpp @@ -696,6 +696,8 @@ std::string ESettingsWindowNames_to_string(ESettingsWindowNames value) return "AdvancedPaste"; case ESettingsWindowNames::NewPlus: return "NewPlus"; + case ESettingsWindowNames::ZoomIt: + return "ZoomIt"; default: { Logger::error(L"Can't convert ESettingsWindowNames value={} to string", static_cast(value)); @@ -795,6 +797,10 @@ ESettingsWindowNames ESettingsWindowNames_from_string(std::string value) { return ESettingsWindowNames::NewPlus; } + else if (value == "ZoomIt") + { + return ESettingsWindowNames::ZoomIt; + } else { Logger::error(L"Can't convert string value={} to ESettingsWindowNames", winrt::to_hstring(value)); diff --git a/src/runner/settings_window.h b/src/runner/settings_window.h index 6fd5afca0ff3..8941a4ba2bb3 100644 --- a/src/runner/settings_window.h +++ b/src/runner/settings_window.h @@ -26,6 +26,7 @@ enum class ESettingsWindowNames EnvironmentVariables, AdvancedPaste, NewPlus, + ZoomIt, }; std::string ESettingsWindowNames_to_string(ESettingsWindowNames value); diff --git a/src/settings-ui/Settings.UI.Library/EnabledModules.cs b/src/settings-ui/Settings.UI.Library/EnabledModules.cs index 604b4c46abde..4daa2c532380 100644 --- a/src/settings-ui/Settings.UI.Library/EnabledModules.cs +++ b/src/settings-ui/Settings.UI.Library/EnabledModules.cs @@ -496,6 +496,23 @@ public bool Workspaces } } + private bool zoomIt; + + [JsonPropertyName("ZoomIt")] + public bool ZoomIt + { + get => zoomIt; + set + { + if (zoomIt != value) + { + LogTelemetryEvent(value); + zoomIt = value; + NotifyChange(); + } + } + } + private void NotifyChange() { notifyEnabledChangedAction?.Invoke(); diff --git a/src/settings-ui/Settings.UI.Library/Settings.UI.Library.csproj b/src/settings-ui/Settings.UI.Library/Settings.UI.Library.csproj index a967dd28a770..c3832b11c508 100644 --- a/src/settings-ui/Settings.UI.Library/Settings.UI.Library.csproj +++ b/src/settings-ui/Settings.UI.Library/Settings.UI.Library.csproj @@ -19,6 +19,7 @@ + diff --git a/src/settings-ui/Settings.UI.Library/ZoomItProperties.cs b/src/settings-ui/Settings.UI.Library/ZoomItProperties.cs new file mode 100644 index 000000000000..3cd756267368 --- /dev/null +++ b/src/settings-ui/Settings.UI.Library/ZoomItProperties.cs @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text.Json.Serialization; +using Settings.UI.Library.Attributes; + +namespace Microsoft.PowerToys.Settings.UI.Library +{ + public class ZoomItProperties + { + public ZoomItProperties() + { + } + + [CmdConfigureIgnore] + public static HotkeySettings DefaultToggleKey => new HotkeySettings(false, true, false, false, '1'); // Ctrl+1 + + [CmdConfigureIgnore] + public static HotkeySettings DefaultLiveZoomToggleKey => new HotkeySettings(false, true, false, false, '4'); // Ctrl+4 + + [CmdConfigureIgnore] + public static HotkeySettings DefaultDrawToggleKey => new HotkeySettings(false, true, false, false, '2'); // Ctrl+2 + + [CmdConfigureIgnore] + public static HotkeySettings DefaultRecordToggleKey => new HotkeySettings(false, true, false, false, '5'); // Ctrl+5 + + [CmdConfigureIgnore] + public static HotkeySettings DefaultSnipToggleKey => new HotkeySettings(false, true, false, false, '6'); // Ctrl+6 + + [CmdConfigureIgnore] + public static HotkeySettings DefaultBreakTimerKey => new HotkeySettings(false, true, false, false, '3'); // Ctrl+3 + + [CmdConfigureIgnore] + public static HotkeySettings DefaultDemoTypeToggleKey => new HotkeySettings(false, true, false, false, '7'); // Ctrl+7 + + public KeyboardKeysProperty ToggleKey { get; set; } + + public KeyboardKeysProperty LiveZoomToggleKey { get; set; } + + public KeyboardKeysProperty DrawToggleKey { get; set; } + + public KeyboardKeysProperty RecordToggleKey { get; set; } + + public KeyboardKeysProperty SnipToggleKey { get; set; } + + public StringProperty PenColor { get; set; } + + public IntProperty PenWidth { get; set; } + + public StringProperty BreakPenColor { get; set; } + + public KeyboardKeysProperty BreakTimerKey { get; set; } + + public StringProperty Font { get; set; } + + public KeyboardKeysProperty DemoTypeToggleKey { get; set; } + + public StringProperty DemoTypeFile { get; set; } + + public IntProperty DemoTypeSpeedSlider { get; set; } + + public BoolProperty DemoTypeUserDrivenMode { get; set; } + + public IntProperty BreakTimeout { get; set; } + + public IntProperty BreakOpacity { get; set; } + + public BoolProperty BreakPlaySoundFile { get; set; } + + public StringProperty BreakSoundFile { get; set; } + + public BoolProperty BreakShowBackgroundFile { get; set; } + + public BoolProperty BreakBackgroundStretch { get; set; } + + public StringProperty BreakBackgroundFile { get; set; } + + public IntProperty BreakTimerPosition { get; set; } + + public BoolProperty BreakShowDesktop { get; set; } + + public BoolProperty BreakOnSecondary { get; set; } + + public IntProperty FontScale { get; set; } + + public BoolProperty ShowExpiredTime { get; set; } + + public BoolProperty ShowTrayIcon { get; set; } + + public BoolProperty AnimnateZoom { get; set; } + + public BoolProperty TelescopeZoomOut { get; set; } + + public BoolProperty SnapToGrid { get; set; } + + public IntProperty ZoominSliderLevel { get; set; } + + public IntProperty RecordFrameRate { get; set; } + + public IntProperty RecordScaling { get; set; } + + public BoolProperty CaptureAudio { get; set; } + + public StringProperty MicrophoneDeviceId { get; set; } + } +} diff --git a/src/settings-ui/Settings.UI.Library/ZoomItSettings.cs b/src/settings-ui/Settings.UI.Library/ZoomItSettings.cs new file mode 100644 index 000000000000..db3b07f36365 --- /dev/null +++ b/src/settings-ui/Settings.UI.Library/ZoomItSettings.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text.Json.Serialization; +using Microsoft.PowerToys.Settings.UI.Library.Interfaces; + +namespace Microsoft.PowerToys.Settings.UI.Library +{ + public class ZoomItSettings : BasePTModuleSettings, ISettingsConfig + { + public const string ModuleName = "ZoomIt"; + + [JsonPropertyName("properties")] + public ZoomItProperties Properties { get; set; } + + public ZoomItSettings() + { + Name = ModuleName; + Properties = new ZoomItProperties(); + Version = "1.0"; + } + + public string GetModuleName() + { + return Name; + } + + // This can be utilized in the future if the settings.json file is to be modified/deleted. + public bool UpgradeSettingsConfiguration() + { + return false; + } + } +} diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Icons/ZoomIt.png b/src/settings-ui/Settings.UI/Assets/Settings/Icons/ZoomIt.png new file mode 100644 index 000000000000..f3c8aff3dedd Binary files /dev/null and b/src/settings-ui/Settings.UI/Assets/Settings/Icons/ZoomIt.png differ diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/ZoomIt.gif b/src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/ZoomIt.gif new file mode 100644 index 000000000000..74c024ae8177 Binary files /dev/null and b/src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/ZoomIt.gif differ diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Modules/ZoomIt.png b/src/settings-ui/Settings.UI/Assets/Settings/Modules/ZoomIt.png new file mode 100644 index 000000000000..c4fb8b00f6ee Binary files /dev/null and b/src/settings-ui/Settings.UI/Assets/Settings/Modules/ZoomIt.png differ diff --git a/src/settings-ui/Settings.UI/Converters/ZoomItInitialZoomConverter.cs b/src/settings-ui/Settings.UI/Converters/ZoomItInitialZoomConverter.cs new file mode 100644 index 000000000000..2107caa39147 --- /dev/null +++ b/src/settings-ui/Settings.UI/Converters/ZoomItInitialZoomConverter.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.PowerToys.Settings.UI.Library; +using Microsoft.UI.Xaml.Data; + +namespace Microsoft.PowerToys.Settings.UI.Converters +{ + public sealed partial class ZoomItInitialZoomConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + string targetValue = string.Empty; + int zoomLevel = System.Convert.ToInt32((double)value); + + // Should match the zoom values expected by ZoomIt internal logic. + switch (zoomLevel) + { + case 0: targetValue = "1.25"; break; + case 1: targetValue = "1.5"; break; + case 2: targetValue = "1.75"; break; + case 3: targetValue = "2.0"; break; + case 4: targetValue = "3.0"; break; + case 5: targetValue = "4.0"; break; + } + + return targetValue; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); + } +} diff --git a/src/settings-ui/Settings.UI/Converters/ZoomItTypeSpeedSliderConverter.cs b/src/settings-ui/Settings.UI/Converters/ZoomItTypeSpeedSliderConverter.cs new file mode 100644 index 000000000000..61208b00b353 --- /dev/null +++ b/src/settings-ui/Settings.UI/Converters/ZoomItTypeSpeedSliderConverter.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.PowerToys.Settings.UI.Helpers; +using Microsoft.PowerToys.Settings.UI.Library; +using Microsoft.UI.Xaml.Data; + +namespace Microsoft.PowerToys.Settings.UI.Converters +{ + public sealed partial class ZoomItTypeSpeedSliderConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + string targetValue = string.Empty; + int zoomLevel = System.Convert.ToInt32((double)value); + string explanation = ResourceLoaderInstance.ResourceLoader.GetString("ZoomIt_DemoType_SpeedSlider_Thumbnail_Explanation"); + + targetValue = $"{zoomLevel} ({explanation})"; + + return targetValue; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); + } +} diff --git a/src/settings-ui/Settings.UI/Helpers/CHOOSEFONT.cs b/src/settings-ui/Settings.UI/Helpers/CHOOSEFONT.cs new file mode 100644 index 000000000000..026a9f01a192 --- /dev/null +++ b/src/settings-ui/Settings.UI/Helpers/CHOOSEFONT.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.PowerToys.Settings.UI.Helpers +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Keep original names from original structure")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:Field names should begin with lower-case letter", Justification = "Keep original names from original structure")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Structure used for win32 interop. We need to access the fields")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1805:Do not initialize unnecessarily", Justification = "Let's have initializations be explicit for these win32 interop types")] + + // Class to select the Dialog options to call ChooseFont. + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public class CHOOSEFONT + { + public int lStructSize = Marshal.SizeOf(typeof(CHOOSEFONT)); + public IntPtr hwndOwner = IntPtr.Zero; + public IntPtr hDC = IntPtr.Zero; + public IntPtr lpLogFont = IntPtr.Zero; + public int iPointSize = 0; + public int Flags = 0; + public int rgbColors = 0; + public IntPtr lCustData = IntPtr.Zero; + public IntPtr lpfnHook = IntPtr.Zero; + public string lpTemplateName = null; + public IntPtr hInstance = IntPtr.Zero; + public string lpszStyle = null; + public short nFontType; + private short __MISSING_ALIGNMENT__; + public int nSizeMin; + public int nSizeMax; + } + + [Flags] + public enum CHOOSE_FONT_FLAGS + { + CF_SCREENFONTS = 0x00000001, + CF_PRINTER_FONTS = 0x00000002, + CF_BOTH = CF_SCREENFONTS | CF_PRINTER_FONTS, + CF_SHOW_HELP = 0x00000004, + CF_ENABLE_HOOK = 0x00000008, + CF_ENABLETEMPLATE = 0x00000010, + CF_ENABLETEMPLATE_HANDLE = 0x00000020, + CF_INITTOLOGFONTSTRUCT = 0x00000040, + CF_USE_STYLE = 0x00000080, + CF_EFFECTS = 0x00000100, + CF_APPLY = 0x00000200, + CF_ANSI_ONLY = 0x00000400, + CF_SCRIPTS_ONLY = CF_ANSI_ONLY, + CF_NO_VECTOR_FONTS = 0x00000800, + CF_NO_OEM_FONTS = CF_NO_VECTOR_FONTS, + CF_NO_SIMULATIONS = 0x00001000, + CF_LIMITSIZE = 0x00002000, + CF_FIXED_PITCH_ONLY = 0x00004000, + CF_WYSIWYG = 0x00008000, + CF_FORCE_FONT_EXIST = 0x00010000, + CF_SCALABLE_ONLY = 0x00020000, + CF_TT_ONLY = 0x00040000, + CF_NO_FACE_SEL = 0x00080000, + CF_NO_STYLE_SEL = 0x00100000, + CF_NO_SIZE_SEL = 0x00200000, + CF_SELECT_SCRIPT = 0x00400000, + CF_NO_SCRIPT_SEL = 0x00800000, + CF_NO_VERT_FONTS = 0x01000000, + CF_INACTIVE_FONTS = 0x02000000, + } +} diff --git a/src/settings-ui/Settings.UI/Helpers/LOGFONT.cs b/src/settings-ui/Settings.UI/Helpers/LOGFONT.cs new file mode 100644 index 000000000000..575f91d117d3 --- /dev/null +++ b/src/settings-ui/Settings.UI/Helpers/LOGFONT.cs @@ -0,0 +1,125 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.PowerToys.Settings.UI.Helpers +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Keep original names from original structure")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Structure used for win32 interop. We need to access the fields")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1805:Do not initialize unnecessarily", Justification = "Let's have initializations be explicit for these win32 interop types")] + + // Result from calling ChooseFont. + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public class LOGFONT + { + public int lfHeight = 0; + public int lfWidth = 0; + public int lfEscapement = 0; + public int lfOrientation = 0; + public int lfWeight = 0; + public byte lfItalic = 0; + public byte lfUnderline = 0; + public byte lfStrikeOut = 0; + public byte lfCharSet = 0; + public byte lfOutPrecision = 0; + public byte lfClipPrecision = 0; + public byte lfQuality = 0; + public byte lfPitchAndFamily = 0; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string lfFaceName = string.Empty; + } + + public enum FontWeight : int + { + FW_DONT_CARE = 0, + FW_THIN = 100, + FW_EXTRALIGHT = 200, + FW_LIGHT = 300, + FW_NORMAL = 400, + FW_MEDIUM = 500, + FW_SEMIBOLD = 600, + FW_BOLD = 700, + FW_EXTRABOLD = 800, + FW_HEAVY = 900, + } + + public enum FontCharSet : byte + { + ANSI_CHARSET = 0, + DEFAULT_CHARSET = 1, + SYMBOL_CHARSET = 2, + SHIFT_JIS_CHARSET = 128, + HANGEUL_CHARSET = 129, + HANGUL_CHARSET = HANGEUL_CHARSET, + GB2312_CHARSET = 134, + CHINESE_BIG5_CHARSET = 136, + OEM_CHARSET = 255, + JOHAB_CHARSET = 130, + HEBREW_CHARSET = 177, + ARABIC_CHARSET = 178, + GREEK_CHARSET = 161, + TURKISH_CHARSET = 162, + VIETNAMESE_CHARSET = 163, + THAI_CHARSET = 222, + EAST_EUROPE_CHARSET = 238, + RUSSIAN_CHARSET = 204, + MAC_CHARSET = 77, + BALTIC_CHARSET = 186, + } + + public enum FontPrecision : byte + { + OUT_DEFAULT_PRECIS = 0, + OUT_STRING_PRECIS = 1, + OUT_CHARACTER_PRECIS = 2, + OUT_STROKE_PRECIS = 3, + OUT_TT_PRECIS = 4, + OUT_DEVICE_PRECIS = 5, + OUT_RASTER_PRECIS = 6, + OUT_TT_ONLY_PRECIS = 7, + OUT_OUTLINE_PRECIS = 8, + OUT_SCREEN_OUTLINE_PRECIS = 9, + OUT_PS_ONLY_PRECIS = 10, + } + + public enum FontClipPrecision : byte + { + CLIP_DEFAULT_PRECIS = 0, + CLIP_CHARACTER_PRECIS = 1, + CLIP_STROKE_PRECIS = 2, + CLIP_MASK = 0xf, + CLIP_LH_ANGLES = 1 << 4, + CLIP_TT_ALWAYS = 2 << 4, + CLIP_DFA_DISABLE = 4 << 4, + CLIP_EMBEDDED = 8 << 4, + } + + public enum FontQuality : byte + { + DEFAULT_QUALITY = 0, + DRAFT_QUALITY = 1, + PROOF_QUALITY = 2, + NONANTIALIASED_QUALITY = 3, + ANTIALIASED_QUALITY = 4, + CLEAR_TYPE_QUALITY = 5, + CLEAR_TYPE_NATURAL_QUALITY = 6, + } + + [Flags] + public enum FontPitchAndFamily : byte + { + DEFAULT_PITCH = 0, + FIXED_PITCH = 1, + VARIABLE_PITCH = 2, + FF_DONT_CARE = DEFAULT_PITCH, + FF_ROMAN = 1 << 4, + FF_SWISS = 2 << 4, + FF_MODERN = 3 << 4, + FF_SCRIPT = 4 << 4, + FF_DECORATIVE = 5 << 4, + } +} diff --git a/src/settings-ui/Settings.UI/Helpers/ModuleHelper.cs b/src/settings-ui/Settings.UI/Helpers/ModuleHelper.cs index c3eef509c033..60de12a4c98b 100644 --- a/src/settings-ui/Settings.UI/Helpers/ModuleHelper.cs +++ b/src/settings-ui/Settings.UI/Helpers/ModuleHelper.cs @@ -72,6 +72,7 @@ public static bool GetIsModuleEnabled(Library.GeneralSettings generalSettingsCon case ModuleType.MeasureTool: return generalSettingsConfig.Enabled.MeasureTool; case ModuleType.ShortcutGuide: return generalSettingsConfig.Enabled.ShortcutGuide; case ModuleType.PowerOCR: return generalSettingsConfig.Enabled.PowerOcr; + case ModuleType.ZoomIt: return generalSettingsConfig.Enabled.ZoomIt; default: return false; } } @@ -106,6 +107,7 @@ internal static void SetIsModuleEnabled(GeneralSettings generalSettingsConfig, M case ModuleType.MeasureTool: generalSettingsConfig.Enabled.MeasureTool = isEnabled; break; case ModuleType.ShortcutGuide: generalSettingsConfig.Enabled.ShortcutGuide = isEnabled; break; case ModuleType.PowerOCR: generalSettingsConfig.Enabled.PowerOcr = isEnabled; break; + case ModuleType.ZoomIt: generalSettingsConfig.Enabled.ZoomIt = isEnabled; break; } } @@ -139,6 +141,7 @@ public static GpoRuleConfigured GetModuleGpoConfiguration(ModuleType moduleType) case ModuleType.MeasureTool: return GPOWrapper.GetConfiguredScreenRulerEnabledValue(); case ModuleType.ShortcutGuide: return GPOWrapper.GetConfiguredShortcutGuideEnabledValue(); case ModuleType.PowerOCR: return GPOWrapper.GetConfiguredTextExtractorEnabledValue(); + case ModuleType.ZoomIt: return GPOWrapper.GetConfiguredZoomItEnabledValue(); default: return GpoRuleConfigured.Unavailable; } } @@ -173,6 +176,7 @@ public static System.Type GetModulePageType(ModuleType moduleType) ModuleType.MeasureTool => typeof(MeasureToolPage), ModuleType.ShortcutGuide => typeof(ShortcutGuidePage), ModuleType.PowerOCR => typeof(PowerOcrPage), + ModuleType.ZoomIt => typeof(ZoomItPage), _ => typeof(DashboardPage), // never called, all values listed above }; } diff --git a/src/settings-ui/Settings.UI/Helpers/NativeMethods.cs b/src/settings-ui/Settings.UI/Helpers/NativeMethods.cs index c895866c1ebc..072f12b00cd3 100644 --- a/src/settings-ui/Settings.UI/Helpers/NativeMethods.cs +++ b/src/settings-ui/Settings.UI/Helpers/NativeMethods.cs @@ -48,6 +48,12 @@ public static class NativeMethods [DllImport("Comdlg32.dll", CharSet = CharSet.Unicode)] internal static extern bool GetOpenFileName([In, Out] OpenFileName openFileName); + [DllImport("comdlg32.dll", CharSet = CharSet.Auto, EntryPoint = "ChooseFont", SetLastError = true)] + internal static extern bool ChooseFont(IntPtr lpChooseFont); + + [DllImport("comdlg32.dll", SetLastError = true)] + internal static extern int CommDlgExtendedError(); + #pragma warning disable CA1401 // P/Invokes should not be visible [DllImport("user32.dll")] public static extern bool ShowWindow(System.IntPtr hWnd, int nCmdShow); diff --git a/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs b/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs index 14646c664709..4364711a1744 100644 --- a/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs +++ b/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs @@ -34,5 +34,6 @@ public enum PowerToysModules WhatsNew, RegistryPreview, NewPlus, + ZoomIt, } } diff --git a/src/settings-ui/Settings.UI/PowerToys.Settings.csproj b/src/settings-ui/Settings.UI/PowerToys.Settings.csproj index 7a465fbd01f6..1e460af13d7f 100644 --- a/src/settings-ui/Settings.UI/PowerToys.Settings.csproj +++ b/src/settings-ui/Settings.UI/PowerToys.Settings.csproj @@ -33,7 +33,7 @@ - PowerToys.GPOWrapper + PowerToys.GPOWrapper;PowerToys.ZoomItSettingsInterop $(OutDir) false @@ -87,6 +87,7 @@ + diff --git a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs index 85f64f62cc79..53138743d61c 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs @@ -442,6 +442,7 @@ public static Type GetPage(string settingWindow) case "EnvironmentVariables": return typeof(EnvironmentVariablesPage); case "NewPlus": return typeof(NewPlusPage); case "Workspaces": return typeof(WorkspacesPage); + case "ZoomIt": return typeof(ZoomItPage); default: // Fallback to Dashboard Debug.Assert(false, "Unexpected SettingsWindow argument value"); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShellPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShellPage.xaml index b765b7d963b7..569ebfc7a184 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShellPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShellPage.xaml @@ -165,6 +165,10 @@ x:Uid="Shell_Workspaces" Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Workspaces.png}" Tag="Workspaces" /> + + + + + + + + + + +