diff --git a/src/common/sysinternals/Eula/Eula.txt b/src/common/sysinternals/Eula/Eula.txt new file mode 100644 index 0000000000..8efa71167c --- /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 0000000000..02754dfd4e --- /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 0000000000..450031583e --- /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 0000000000..4f8c495bf3 --- /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 0000000000..6b12d86d2a --- /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 0000000000..de06ab7e56 --- /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 0000000000..d9e11695aa --- /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 0000000000..3ecfbbeb7d --- /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/modules/ZoomIt/ZoomIt/AudioSampleGenerator.cpp b/src/modules/ZoomIt/ZoomIt/AudioSampleGenerator.cpp new file mode 100644 index 0000000000..21e9883bb7 --- /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 0000000000..8e279f3b58 --- /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 0000000000..d4d3fa1750 --- /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 0000000000..5029b9085f --- /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 0000000000..f29ae95660 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/DemoType.cpp @@ -0,0 +1,1446 @@ +//============================================================================ +// +// Zoomit +// Copyright (C) Mark Russinovich +// Sysinternals - www.sysinternals.com +// +// DemoType allows the presenter to synthesize keystrokes from a script +// +//============================================================================ + +#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 indendation + 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 += 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 ); + } + } + } + + 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 occurence of `control` in `g_Text` + size_t nextControl = g_Text.find( control ); + + // Loop through each occurence 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 occurence 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 ) + { + size_t i = lastEnd + END_CONTROL_LEN; + 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 0000000000..08ad9ab1d5 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/DemoType.h @@ -0,0 +1,24 @@ +//============================================================================ +// +// Zoomit +// Copyright (C) Mark Russinovich +// Sysinternals - www.sysinternals.com +// +// DemoType allows the presenter to synthesize keystrokes from a script +// +//============================================================================ + +#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 0000000000..1096e5ba86 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/Registry.h @@ -0,0 +1,323 @@ +//============================================================================ +// +// Process Explorer +// Copyright (C) 1999-2005 Mark Russinovich +// Sysinternals - www.sysinternals.com +// +// Registry.h +// +//============================================================================ + +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 CRegistry { + +private: + PTCHAR m_Keyname; + HKEY hKey; + +public: + CRegistry( PCTSTR Keyname ) + { + m_Keyname = _tcsdup( Keyname ); + hKey = NULL; + } + + ~CRegistry() + { + 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, (PDWORD) curSetting->Setting, + (DWORD) curSetting->DefaultSetting ); + break; + case SETTING_TYPE_BOOLEAN: + ReadValue( curSetting->Valuename, (PBOOLEAN) curSetting->Setting, + (BOOLEAN) curSetting->DefaultSetting ); + break; + case SETTING_TYPE_DOUBLE: + ReadValue( curSetting->Valuename, (double *) curSetting->Setting, + curSetting->DefaultSetting ); + break; + case SETTING_TYPE_WORD: + ReadValue( curSetting->Valuename, (short *) curSetting->Setting, + (WORD) curSetting->DefaultSetting ); + break; + case SETTING_TYPE_STRING: + ReadValue( curSetting->Valuename, (PTCHAR) curSetting->Setting, + curSetting->Size, (PTCHAR) (DWORD_PTR) curSetting->DefaultSetting ); + break; + case SETTING_TYPE_DWORD_ARRAY: + ReadValueArray( curSetting->Valuename, curSetting->Size/sizeof DWORD, + (PDWORD) curSetting->Setting ); + break; + case SETTING_TYPE_WORD_ARRAY: + ReadValueArray( curSetting->Valuename, curSetting->Size/sizeof(short), + (PWORD) curSetting->Setting ); + break; + case SETTING_TYPE_BINARY: + ReadValueBinary( curSetting->Valuename, (PBYTE) 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, *(PDWORD) curSetting->Setting ); + break; + case SETTING_TYPE_BOOLEAN: + WriteValue( curSetting->Valuename, *(PBOOLEAN) curSetting->Setting ); + break; + case SETTING_TYPE_DOUBLE: + WriteValue( curSetting->Valuename, *(double *) curSetting->Setting ); + break; + case SETTING_TYPE_WORD: + WriteValue( curSetting->Valuename, *(short *) curSetting->Setting ); + break; + case SETTING_TYPE_STRING: + WriteValue( curSetting->Valuename, (PTCHAR) curSetting->Setting ); + break; + case SETTING_TYPE_DWORD_ARRAY: + WriteValueArray( curSetting->Valuename, curSetting->Size/sizeof DWORD, + (PDWORD) curSetting->Setting ); + break; + case SETTING_TYPE_WORD_ARRAY: + WriteValueArray( curSetting->Valuename, curSetting->Size/sizeof(short), + (PWORD) curSetting->Setting ); + break; + case SETTING_TYPE_BINARY: + WriteValueBinary( curSetting->Valuename, (PBYTE) 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, (PBYTE) Value, + &length )) { + + *Value = Default; + } + } + void ReadValue( PCTSTR Valuename, PBOOLEAN Value, BOOLEAN Default = FALSE ) + { + DWORD length = sizeof(DWORD); + DWORD val = (DWORD) *Value; + if( RegQueryValueEx( hKey, Valuename, NULL, NULL, (PBYTE) &val, + &length )) { + + *Value = Default; + + } else { + + *Value = (BOOLEAN) val; + } + } + void ReadValue( PCTSTR Valuename, short *Value, short Default = 0 ) + { + DWORD length = sizeof(DWORD); + DWORD val = (DWORD) *Value; + if( RegQueryValueEx( hKey, Valuename, NULL, NULL, (PBYTE) &val, + &length )) { + + *Value = Default; + + } else { + + *Value = (short) val; + } + } + void ReadValue( PCTSTR Valuename, double *Value, double Default = 0.0 ) + { + DWORD length = sizeof(double); + if( RegQueryValueEx( hKey, Valuename, NULL, NULL, (PBYTE) Value, + &length )) { + + *Value = Default; + + } + } + void ReadValue( PCTSTR Valuename, PTCHAR Value, DWORD Length, PCTSTR Default ) + { + if( RegQueryValueEx( hKey, Valuename, NULL, NULL, (PBYTE) 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, (PBYTE) &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, (PBYTE) &val, &length)) { + + Entries[i] = (WORD) val; + + } + } + RegCloseKey( hSubKey ); + } + } + + // Writes + void WriteValue( PCTSTR Valuename, DWORD Value ) + { + RegSetValueEx( hKey, Valuename, 0, REG_DWORD, (PBYTE) &Value, + sizeof(DWORD)); + } + void WriteValue( PCTSTR Valuename, short Value ) + { + DWORD val = (DWORD) Value; + RegSetValueEx( hKey, Valuename, 0, REG_DWORD, (PBYTE) &val, + sizeof(DWORD)); + } + void WriteValue( PCTSTR Valuename, BOOLEAN Value ) + { + DWORD val = (DWORD) Value; + RegSetValueEx( hKey, Valuename, 0, REG_DWORD, (PBYTE) &val, + sizeof(DWORD)); + } + void WriteValue( PCTSTR Valuename, double Value ) + { + RegSetValueEx( hKey, Valuename, 0, REG_BINARY, (PBYTE) &Value, + sizeof(double)); + } + void WriteValue( PCTSTR Valuename, PTCHAR Value ) + { + RegSetValueEx( hKey, Valuename, 0, REG_SZ, (PBYTE) Value, + (DWORD) _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, (PBYTE) &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 = (DWORD) Entries[i]; + if( Entries[i] ) + RegSetValueEx( hSubKey, subVal, 0, REG_DWORD, (PBYTE) &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 0000000000..cfab73996c --- /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 0000000000..6244067ec6 --- /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 0000000000..38334b9e8e --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/Utility.cpp @@ -0,0 +1,145 @@ +//============================================================================== +// +// 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 +// +//---------------------------------------------------------------------------- +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; +} + +//---------------------------------------------------------------------------- +// +// ScaleForDpi +// +//---------------------------------------------------------------------------- +int ScaleForDpi( int value, UINT dpi ) +{ + return MulDiv( value, (int) 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 0000000000..a78ecdf14e --- /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/VideoRecordingSession.cpp b/src/modules/ZoomIt/ZoomIt/VideoRecordingSession.cpp new file mode 100644 index 0000000000..d4634488e3 --- /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 CLEARCOLOR[] = { 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(), CLEARCOLOR); + 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 0000000000..960ac36444 --- /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 0000000000..f71f31e9c3 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/ZoomIt.h @@ -0,0 +1,218 @@ +//============================================================================ +// +// Zoomit +// Copyright (C) Mark Russinovich +// Sysinternals - www.sysinternals.com +// +// Screen zoom and annotation tool. +// +//============================================================================ + +// 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 ZOOMLEVEL_MIN 1 +#define ZOOMLEVEL_INIT 2 +#define ZOOMLEVEL_STEPIN ((float) 1.1) +#define ZOOMLEVEL_STEPOUT ((float) 0.8) +#define ZOOMLEVEL_MAX 32 +#define ZOOMLEVEL_STEPTIME 20 + +#define LIVEZOOM_MOVEREGIONS 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_PENWIDTH 2 +#define MAX_PENWIDTH 40 +#define MAX_LIVEPENWIDTH 600 + +#define APPNAME L"ZoomIt" +#define WM_USER_TRAYACTIVATE WM_USER+100 +#define WM_USER_TYPINGOFF WM_USER+101 +#define WM_USER_GETZOOMLEVEL WM_USER+102 +#define WM_USER_GETSOURCERECT WM_USER+103 +#define WM_USER_SETZOOM WM_USER+104 +#define WM_USER_STOPRECORDING WM_USER+105 +#define WM_USER_SAVECURSOR WM_USER+106 +#define WM_USER_RESTORECURSOR WM_USER+107 +#define WM_USER_MAGNIFYCURSOR WM_USER+108 +#define WM_USER_EXITMODE WM_USER+109 + +typedef struct _TYPED_KEY { + RECT rc; + struct _TYPED_KEY *Next; +} TYPED_KEY, *PTYPED_KEY; + +typedef struct _DRAW_UNDO { + HDC hDc; + HBITMAP hBitmap; + struct _DRAW_UNDO *Next; +} DRAW_UNDO, *PDRAW_UNDO; + +typedef struct { + TCHAR TabTitle[64]; + HWND hPage; +} OPTION_TABS, *POPTIONS_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 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 *pquns +); + +typedef BOOL (__stdcall *type_pSystemParametersInfoForDpi)( + UINT uiAction, + UINT uiParam, + PVOID pvParam, + UINT fWinIni, + UINT dpi +); + +typedef UINT (__stdcall *type_pGetDpiForWindow)( + HWND hwnd +); + +class CGraphicsInit +{ + ULONG_PTR m_Token; +public: + CGraphicsInit() + { + Gdiplus::GdiplusStartupOutput startupOut; + Gdiplus::GdiplusStartupInput startupIn; + Gdiplus::GdiplusStartup( &m_Token, &startupIn, &startupOut ); + } + ~CGraphicsInit() + { + Gdiplus::GdiplusShutdown( m_Token ); + } +}; + +// Direct3D +typedef HRESULT (__stdcall *type_pCreateDirect3D11DeviceFromDXGIDevice)( + IDXGIDevice *dxgiDevice, + IInspectable **graphicsDevice +); +typedef HRESULT (__stdcall *type_pCreateDirect3D11SurfaceFromDXGISurface)( + IDXGISurface *dgxiSurface, + 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 0000000000..5bb02ecab7 --- /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 0000000000..31cf5d038b --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/ZoomIt.rc @@ -0,0 +1,476 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.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 ""afxres.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 8,1,0,0 + PRODUCTVERSION 8,1,0,0 + 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", "Sysinternals - www.sysinternals.com" + VALUE "FileDescription", "Sysinternals Screen Magnifier" + VALUE "FileVersion", "8.01" + VALUE "InternalName", "ZoomIt" + VALUE "LegalCopyright", "Copyright 2006-2024 Mark Russinovich" + VALUE "OriginalFilename", "ZoomIt.exe" + VALUE "ProductName", "Sysinternals ZoomIt" + VALUE "ProductVersion", "8.01" + 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 v8.01",IDC_TITLE,42,7,73,10 + LTEXT "Copyright 2006-2024 Mark Russinovich",IDC_STATIC,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_SHOWTRAYICON,"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 + +ADVANCEDBREAK 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_CHECKSOUNDFILE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,3,11,98,10,WS_EX_RIGHT + EDITTEXT IDC_SOUNDFILE,61,38,125,12,ES_AUTOHSCROLL | ES_READONLY + PUSHBUTTON "&...",IDC_SOUNDBROWSE,187,38,13,11 + COMBOBOX IDC_OPACITY,62,58,38,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "",IDC_TIMERPOS1,"Button",BS_AUTORADIOBUTTON,63,78,10,10 + CONTROL "",IDC_TIMERPOS2,"Button",BS_AUTORADIOBUTTON,79,78,10,10 + CONTROL "",IDC_TIMERPOS3,"Button",BS_AUTORADIOBUTTON,97,78,10,10 + CONTROL "",IDC_TIMERPOS4,"Button",BS_AUTORADIOBUTTON,63,93,10,10 + CONTROL "",IDC_TIMERPOS5,"Button",BS_AUTORADIOBUTTON,79,93,10,10 + CONTROL "",IDC_TIMERPOS6,"Button",BS_AUTORADIOBUTTON,97,93,10,10 + CONTROL "",IDC_TIMERPOS7,"Button",BS_AUTORADIOBUTTON,63,108,10,10 + CONTROL "",IDC_TIMERPOS8,"Button",BS_AUTORADIOBUTTON,79,108,10,10 + CONTROL "",IDC_TIMERPOS9,"Button",BS_AUTORADIOBUTTON,97,108,10,10 + CONTROL "Show background bitmap:",IDC_CHECKBACKGROUNDFILE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,3,122,99,10,WS_EX_RIGHT + CONTROL "Use faded desktop as background",IDC_STATIC_DESKTOPBACKGROUND, + "Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,46,135,125,10 + CONTROL "Use image file as background",IDC_STATIC_BACKROUNDFILE, + "Button",BS_AUTORADIOBUTTON | WS_TABSTOP,46,149,109,10 + EDITTEXT IDC_BACKROUNDFILE,62,164,125,12,ES_AUTOHSCROLL | ES_READONLY + PUSHBUTTON "&...",IDC_BACKGROUNDBROWSE,188,164,13,11 + CONTROL "Scale to screen:",IDC_CHECKBACKGROUNDSTRETCH,"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_SOUNDFILE,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_ZOOMSLIDER,"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_PENCONTROL,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_HIGHLIGHTANDBLUR,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_DRAWHOTKEY,"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_TEXTFONT,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_BREAKHOTKEY,"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_SPINTIMER,"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_ADVANCEDBREAK,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_SHOWEXPIRED, + "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, 108 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_LIVEHOTKEY,"msctls_hotkey32",WS_BORDER | WS_TABSTOP,69,76,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,79,62,8 + LTEXT "To enter and exit LiveZoom, enter the hotkey specified below.",IDC_STATIC,7,63,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,29,246,34 +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_RECORDHOTKEY,"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_RECORDSCALING,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_RECORDFRAMERATE,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_CAPTUREAUDIO,"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_SNIPHOTKEY,"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_DEMOTYPEHOTKEY,"msctls_hotkey32",WS_BORDER | WS_TABSTOP,74,154,80,12 + LTEXT "DemoType toggle:",IDC_STATIC,7,157,63,8 + PUSHBUTTON "&...",IDC_DEMOTYPEBROWSE,231,137,16,13 + CONTROL "",IDC_DEMOTYPESPEEDSLIDER,"msctls_trackbar32",TBS_AUTOTICKS | TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,52,202,150,11,WS_EX_TRANSPARENT + CONTROL "Drive input with typing:",IDC_DEMOTYPEUSERDRIVEN,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,7,173,88,10 + LTEXT "DemoType typing speed:",IDC_STATIC,7,189,215,10 + LTEXT "Slow",IDC_DEMOTYPESTATIC1,51,213,18,8 + LTEXT "Fast",IDC_DEMOTYPESTATIC2,186,213,17,8 + EDITTEXT IDC_DEMOTYPEFILE,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 beggining. 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 + + "ADVANCEDBREAK", 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, 101 + 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_COPYCROP, VIRTKEY, SHIFT, CONTROL, NOINVERT + "S", IDC_SAVECROP, 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 0000000000..acec731158 --- /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 0000000000..59e9b6f06e --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj @@ -0,0 +1,381 @@ + + + + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {0A84F764-3A88-44CD-AA96-41BDBD48627B} + ZoomIt + true + true + true + + + + false + + + false + + + false + + + false + + + false + + + false + + + + false + $(ProjectDir)$(Configuration)\InterPlatform\ + true + + + false + $(ProjectDir)$(Configuration)\InterPlatform\ + RCZOOMIT + true + + + false + RCZOOMIT + true + + + false + $(ProjectDir)$(Configuration)\InterPlatform\ + true + + + false + $(ProjectDir)$(Configuration)\InterPlatform\ + RCZOOMIT + true + + + false + RCZOOMIT + true + + + + MaxSpeed + _UNICODE;UNICODE;WINVER=0x602;NDEBUG;_WIN32_WINNT=0x602;_WIN32_WINDOWS=0x501;WIN32;_WINDOWS;MSVC6=1;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + true + MultiThreaded + true + 4100;4091 + .\modules\Common + Create + pch.h + stdcpp17 + + + NDEBUG;_M_IX86;%(PreprocessorDefinitions) + 0x0409 + $(InterPlatformDir) + + + comctl32.lib;odbc32.lib;odbccp32.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 + 4100;4091 + .\modules\Common + Create + pch.h + stdcpp17 + + + NDEBUG;_M_X64;%(PreprocessorDefinitions) + 0x0409 + + + comctl32.lib;odbc32.lib;odbccp32.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 + 4100;4091 + .\modules\Common + Create + pch.h + stdcpp17 + + + NDEBUG;_M_ARM64;%(PreprocessorDefinitions) + 0x0409 + + + comctl32.lib;odbc32.lib;odbccp32.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 + 4100;4091 + .\modules\Common + Create + pch.h + stdcpp17 + + + _DEBUG;_M_IX86;%(PreprocessorDefinitions) + 0x0409 + $(InterPlatformDir) + + + comctl32.lib;odbc32.lib;odbccp32.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 + 4100;4091 + .\modules\Common + Create + pch.h + stdcpp17 + + + _DEBUG;_M_X64;%(PreprocessorDefinitions) + 0x0409 + + + comctl32.lib;odbc32.lib;odbccp32.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 + 4100;4091 + .\modules\Common + Create + pch.h + stdcpp17 + + + _DEBUG;_M_ARM64;%(PreprocessorDefinitions) + 0x0409 + + + comctl32.lib;odbc32.lib;odbccp32.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 + + + + + true + true + true + true + true + true + + + + + + PreserveNewest + + + + + + + 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 0000000000..1f5c3f9618 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj.filters @@ -0,0 +1,115 @@ + + + + + {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 + + + + + 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 + + + Header Files + + + + + + Resource Files + + + Resource Files + + + + + + \ No newline at end of file diff --git a/src/modules/ZoomIt/ZoomIt/Zoomit.cpp b/src/modules/ZoomIt/ZoomIt/Zoomit.cpp new file mode 100644 index 0000000000..fa714334bd --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/Zoomit.cpp @@ -0,0 +1,7070 @@ +//============================================================================ +// +// Zoomit +// Copyright (C) Mark Russinovich +// Sysinternals - www.sysinternals.com +// +// Screen zoom and annotation tool. +// +//============================================================================ +#include "pch.h" + +#include "zoomit.h" +#include "Utility.h" +#include "WindowsVersions.h" + + +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 RECORD_HOTKEY 4 +#define RECORDCROP_HOTKEY 5 +#define RECORDWINDOW_HOTKEY 6 +#define SNIP_HOTKEY 7 +#define SNIPSAVE_HOTKEY 8 +#define DEMOTYPE_HOTKEY 9 +#define DEMOTYPERESET_HOTKEY 10 + +#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 CURSORARMLENGTH = 4; + +const float NORMAL_BLUR_RADIUS = 20; +const float STRONG_BLUR_RADIUS = 40; + +DWORD g_ToggleKey = (HOTKEYF_CONTROL << 8)| '1'; +DWORD g_ToggleMod; +DWORD g_LiveZoomToggleKey = ((HOTKEYF_CONTROL) << 8)| '4'; +DWORD g_LiveZoomToggleMod; +DWORD g_DrawToggleKey = ((HOTKEYF_CONTROL) << 8)| '2'; +DWORD g_DrawToggleMod; +DWORD g_BreakToggleKey = ((HOTKEYF_CONTROL) << 8)| '3'; +DWORD g_BreakToggleMod; +DWORD g_DemoTypeToggleKey = ((HOTKEYF_CONTROL) << 8) | '7'; +DWORD g_DemoTypeToggleMod; +DWORD g_RecordToggleKey = ((HOTKEYF_CONTROL) << 8) | '5'; +DWORD g_RecordToggleMod; +DWORD g_SnipToggleKey = ((HOTKEYF_CONTROL) << 8) | '6'; +DWORD g_SnipToggleMod; + +DWORD g_ShowExpiredTime = 1; +DWORD g_SliderZoomLevel = 3; +BOOLEAN g_AnimateZoom = TRUE; +BOOLEAN g_ZoomOnLiveZoom = FALSE; +DWORD g_PenColor = COLOR_RED; +DWORD g_BreakPenColor = COLOR_RED; +DWORD g_PenWidth = PEN_WIDTH; +DWORD g_RootPenWidth = PEN_WIDTH; +float g_BlurRadius = NORMAL_BLUR_RADIUS; +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_TelescopeZoomeOut = TRUE; +BOOLEAN g_BreakOnSecondary = FALSE; +LOGFONT g_LogFont; +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; +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); +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 + +// Screen recording globals +#define DEFAULT_RECORDING_FILE L"Recording.mp4" +DWORD g_RecordFrameRate = 30; +// Divide by 100 to get actual scaling +DWORD g_RecordScaling = 100; +BOOL g_RecordToggle = FALSE; +BOOL g_RecordCropping = FALSE; +BOOLEAN g_CaptureAudio = FALSE; +TCHAR g_MicrophoneDeviceId[MAX_PATH] = {0}; +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; + + +REG_SETTING RegSettings[] = { + { L"ToggleKey", SETTING_TYPE_DWORD, 0, &g_ToggleKey, (DOUBLE)g_ToggleKey }, + { L"LiveZoomToggleKey", SETTING_TYPE_DWORD, 0, &g_LiveZoomToggleKey, (DOUBLE)g_LiveZoomToggleKey }, + { L"DrawToggleKey", SETTING_TYPE_DWORD, 0, &g_DrawToggleKey, (DOUBLE)g_DrawToggleKey }, + { L"RecordToggleKey", SETTING_TYPE_DWORD, 0, &g_RecordToggleKey, (DOUBLE)g_RecordToggleKey }, + { L"SnipToggleKey", SETTING_TYPE_DWORD, 0, &g_SnipToggleKey, (DOUBLE)g_SnipToggleKey }, + { L"PenColor", SETTING_TYPE_DWORD, 0, &g_PenColor, (DOUBLE)g_PenColor }, + { L"PenWidth", SETTING_TYPE_DWORD, 0, &g_RootPenWidth, (DOUBLE)g_RootPenWidth }, + { L"OptionsShown", SETTING_TYPE_BOOLEAN, 0, &g_OptionsShown, (DOUBLE)g_OptionsShown }, + { L"BreakPenColor", SETTING_TYPE_DWORD, 0, &g_BreakPenColor, (DOUBLE)g_BreakPenColor }, + { L"BreakTimerKey", SETTING_TYPE_DWORD, 0, &g_BreakToggleKey, (DOUBLE)g_BreakToggleKey }, + { L"DemoTypeToggleKey", SETTING_TYPE_DWORD, 0, &g_DemoTypeToggleKey, (DOUBLE)g_DemoTypeToggleKey }, + { L"DemoTypeFile", SETTING_TYPE_STRING, sizeof( g_DemoTypeFile ), g_DemoTypeFile, (DOUBLE)0 }, + { L"DemoTypeSpeedSlider", SETTING_TYPE_DWORD, 0, &g_DemoTypeSpeedSlider, (DOUBLE)g_DemoTypeSpeedSlider }, + { L"DemoTypeUserDrivenMode", SETTING_TYPE_BOOLEAN, 0, &g_DemoTypeUserDriven, (DOUBLE)g_DemoTypeUserDriven }, + { L"BreakTimeout", SETTING_TYPE_BOOLEAN, 0, &g_BreakTimeout, (DOUBLE)g_BreakTimeout }, + { L"BreakOpacity", SETTING_TYPE_DWORD, 0, &g_BreakOpacity, (DOUBLE)g_BreakOpacity }, + { L"BreakPlaySoundFile", SETTING_TYPE_BOOLEAN, 0, &g_BreakPlaySoundFile, (DOUBLE)0}, + { L"BreakSoundFile", SETTING_TYPE_STRING, sizeof(g_BreakSoundFile), g_BreakSoundFile, (DOUBLE)0 }, + { L"BreakShowBackgroundFile", SETTING_TYPE_BOOLEAN, 0, &g_BreakShowBackgroundFile, (DOUBLE)g_BreakShowBackgroundFile }, + { L"BreakBackgroundStretch", SETTING_TYPE_BOOLEAN, 0, &g_BreakBackgroundStretch,(DOUBLE)g_BreakBackgroundStretch }, + { L"BreakBackgroundFile", SETTING_TYPE_STRING, sizeof(g_BreakBackgroundFile), g_BreakBackgroundFile, (DOUBLE)0 }, + { L"BreakTimerPosition", SETTING_TYPE_DWORD, 0, &g_BreakTimerPosition, (DOUBLE)g_BreakTimerPosition }, + { L"BreakShowDesktop", SETTING_TYPE_BOOLEAN, 0, &g_BreakShowDesktop, (DOUBLE)g_BreakShowDesktop }, + { L"BreakOnSecondary", SETTING_TYPE_BOOLEAN, 0, &g_BreakOnSecondary,(DOUBLE)g_BreakOnSecondary }, + { L"FontScale", SETTING_TYPE_DWORD, 0, &g_FontScale, (DOUBLE)g_FontScale }, + { L"ShowExpiredTime", SETTING_TYPE_BOOLEAN, 0, &g_ShowExpiredTime, (DOUBLE)g_ShowExpiredTime }, + { L"ShowTrayIcon", SETTING_TYPE_BOOLEAN, 0, &g_ShowTrayIcon, (DOUBLE)g_ShowTrayIcon }, + { L"AnimnateZoom", SETTING_TYPE_BOOLEAN, 0, &g_AnimateZoom, (DOUBLE)g_AnimateZoom }, + { L"TelescopeZoomOut", SETTING_TYPE_BOOLEAN, 0, &g_TelescopeZoomeOut, (DOUBLE)g_TelescopeZoomeOut }, + { L"SnapToGrid", SETTING_TYPE_BOOLEAN, 0, &g_SnapToGrid, (DOUBLE)g_SnapToGrid }, + { L"ZoominSliderLevel", SETTING_TYPE_DWORD, 0, &g_SliderZoomLevel, (DOUBLE)g_SliderZoomLevel }, + { L"Font", SETTING_TYPE_BINARY, sizeof g_LogFont, &g_LogFont, (DOUBLE)0 }, + { L"RecordFrameRate", SETTING_TYPE_DWORD, 0, &g_RecordFrameRate, (DOUBLE)g_RecordFrameRate }, + { L"RecordScaling", SETTING_TYPE_DWORD, 0, &g_RecordScaling, (DOUBLE)g_RecordScaling }, + { L"CaptureAudio", SETTING_TYPE_BOOLEAN, 0, &g_CaptureAudio, (DOUBLE)g_CaptureAudio }, + { L"MicrophoneDeviceId", SETTING_TYPE_STRING, sizeof(g_MicrophoneDeviceId), &g_MicrophoneDeviceId, (DOUBLE)0 }, + { NULL, SETTING_TYPE_DWORD, 0, NULL, (DOUBLE)0 } +}; +CRegistry reg( _T("Software\\Sysinternals\\") APPNAME ); + +CGraphicsInit g_GraphicsInit; + + +//---------------------------------------------------------------------------- +// +// Saves specified filePath to clipboard. +// +//---------------------------------------------------------------------------- +bool SaveToClipboard( 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 = (HDROP) GlobalAlloc( GHND, size ); + if (hDrop == NULL) + { + return false; + } + + DROPFILES* dFiles = (DROPFILES*) GlobalLock( hDrop ); + if (dFiles == NULL) + { + GlobalFree( hDrop ); + return false; + } + + dFiles->pFiles = sizeof(DROPFILES); + dFiles->fWide = TRUE; + + WCHAR* pStart = (WCHAR*) &dFiles[1]; + wcscpy( (WCHAR*) & 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 ); + } +} + +//---------------------------------------------------------------------------- +// +// 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), + (LPTSTR) &lpMsgBuf, 0, NULL ); + _stprintf( errmsg, L"%s: %s", message, lpMsgBuf ); + 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(); + 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, + (DWORD) (_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, + (DWORD) (_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 *PISWOW64PROCESS)( + HANDLE hProcess, + PBOOL Wow64Process + ); +BOOL +RunningOnWin64( + VOID + ) +{ + PISWOW64PROCESS pIsWow64Process; + BOOL isWow64 = FALSE; + + pIsWow64Process = (PISWOW64PROCESS) 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 hRsrc; + HGLOBAL hImageResource; + DWORD dwImageSize; + LPVOID lpvImage; + FILE *hFile; + + // Locate the resource + hRsrc = FindResource( NULL, ResourceName, _T("BINRES") ); + if( !hRsrc ) + return FALSE; + + hImageResource = LoadResource( NULL, hRsrc ); + dwImageSize = SizeofResource( NULL, hRsrc ); + 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 = (HBITMAP)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. +// +//---------------------------------------------------------------------------- +Gdiplus::BitmapData *LockGdiPlusBitmap( Gdiplus::Bitmap *Bitmap) +{ + Gdiplus::BitmapData *lineData = new Gdiplus::BitmapData(); + Gdiplus::PixelFormat linePixelFormat = Bitmap->GetPixelFormat(); + Gdiplus::Rect lineBitmapBounds(0, 0, Bitmap->GetWidth(), Bitmap->GetHeight()); + Gdiplus::Status status = Bitmap->LockBits(&lineBitmapBounds, Gdiplus::ImageLockModeRead, + Bitmap->GetPixelFormat(), lineData); + return lineData; +} + + +//---------------------------------------------------------------------------- +// +// 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( (int)(g_PenWidth / 2), (int)(g_PenWidth / 2) ); + + Gdiplus::Bitmap* lineBitmap = new Gdiplus::Bitmap(lineBounds.Width, lineBounds.Height, PixelFormat32bppARGB); + Gdiplus::Graphics lineGraphics(lineBitmap); + switch (Shape) { + case DRAW_RECTANGLE: + lineGraphics.FillRectangle(&Gdiplus::SolidBrush(Gdiplus::Color::Black), 0, 0, lineBounds.Width, lineBounds.Height); + break; + case DRAW_ELLIPSE: + lineGraphics.FillEllipse(&Gdiplus::SolidBrush(Gdiplus::Color::Black), 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((int)(g_PenWidth / 2), (int)(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 = (HBITMAP) 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 = (int) ((float)size/ (float) LIVEZOOM_MOVEREGIONS); + 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, (int) scaledWidth, width ); + *y = max( 0, min( (int) (height - scaledHeight), (int) (cursorPos->y - (int) (((float) cursorPos->y/ (float) height)*scaledHeight)))); + AdjustToMoveBoundary( zoomLevel, y, cursorPos->y, (int) 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 = (ImageCodecInfo*)(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, (int) 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 tnid; + + memset( &tnid, 0, sizeof(tnid)); + tnid.cbSize = sizeof(NOTIFYICONDATA); + tnid.hWnd = hWnd; + tnid.uID = 1; + tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; + tnid.uCallbackMessage = WM_USER_TRAYACTIVATE; + tnid.hIcon = LoadIcon( g_hInstance, L"APPICON" ); + lstrcpyn(tnid.szTip, APPNAME, sizeof(APPNAME)); + Shell_NotifyIcon(Enable ? NIM_ADD : NIM_DELETE, &tnid); +} + +//---------------------------------------------------------------------------- +// +// 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, (BYTE) ((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 +// +//---------------------------------------------------------------------------- +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_SOUNDFILE), SHACF_FILESYSTEM ); + pSHAutoComplete( GetDlgItem( hDlg, IDC_BACKROUNDFILE), SHACF_FILESYSTEM ); + } + CheckDlgButton( hDlg, IDC_CHECKBACKGROUNDFILE, + g_BreakShowBackgroundFile ? BST_CHECKED: BST_UNCHECKED ); + CheckDlgButton( hDlg, IDC_CHECKSOUNDFILE, + g_BreakPlaySoundFile ? BST_CHECKED: BST_UNCHECKED ); + CheckDlgButton( hDlg, IDC_CHECK_SHOWEXPIRED, + g_ShowExpiredTime ? BST_CHECKED : BST_UNCHECKED ); + CheckDlgButton( hDlg, IDC_CHECKBACKGROUNDSTRETCH, + 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_SOUNDFILE ), FALSE ); + EnableWindow( GetDlgItem( hDlg, IDC_SOUNDFILE ), FALSE ); + EnableWindow( GetDlgItem( hDlg, IDC_SOUNDBROWSE ), 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_SOUNDFILE, filePath ); + + // background file + if( !g_BreakShowBackgroundFile ) { + + EnableWindow( GetDlgItem( hDlg, IDC_STATIC_DESKTOPBACKGROUND ), FALSE ); + EnableWindow( GetDlgItem( hDlg, IDC_STATIC_DESKTOPBACKGROUND ), FALSE ); + EnableWindow( GetDlgItem( hDlg, IDC_STATIC_BACKROUNDFILE ), FALSE ); + EnableWindow( GetDlgItem( hDlg, IDC_BACKROUNDFILE ), FALSE ); + EnableWindow( GetDlgItem( hDlg, IDC_BACKGROUNDBROWSE ), FALSE ); + EnableWindow( GetDlgItem( hDlg, IDC_CHECKBACKGROUNDSTRETCH ), FALSE ); + } + CheckDlgButton( hDlg, + g_BreakShowDesktop ? IDC_STATIC_DESKTOPBACKGROUND : IDC_STATIC_BACKROUNDFILE, BST_CHECKED ); + _tcscpy( newBackgroundFile, g_BreakBackgroundFile ); + SetDlgItemText( hDlg, IDC_BACKROUNDFILE, g_BreakBackgroundFile ); + + CheckDlgButton( hDlg, IDC_TIMERPOS1 + g_BreakTimerPosition, BST_CHECKED ); + + for( i = 10; i <= 100; i += 10) { + + _stprintf( opacity, L"%d%%", i ); + SendMessage( GetDlgItem( hDlg, IDC_OPACITY ), CB_ADDSTRING, 0, + (LPARAM) 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_CHECKSOUNDFILE ) { + + EnableWindow( GetDlgItem( hDlg, IDC_STATIC_SOUNDFILE ), + IsDlgButtonChecked( hDlg, IDC_CHECKSOUNDFILE) == BST_CHECKED ); + EnableWindow( GetDlgItem( hDlg, IDC_SOUNDFILE ), + IsDlgButtonChecked( hDlg, IDC_CHECKSOUNDFILE) == BST_CHECKED ); + EnableWindow( GetDlgItem( hDlg, IDC_SOUNDBROWSE ), + IsDlgButtonChecked( hDlg, IDC_CHECKSOUNDFILE) == BST_CHECKED ); + } + if( LOWORD( wParam ) == IDC_CHECKBACKGROUNDFILE ) { + + EnableWindow( GetDlgItem( hDlg, IDC_CHECKBACKGROUNDSTRETCH ), + IsDlgButtonChecked( hDlg, IDC_CHECKBACKGROUNDFILE) == BST_CHECKED ); + EnableWindow( GetDlgItem( hDlg, IDC_STATIC_DESKTOPBACKGROUND ), + IsDlgButtonChecked( hDlg, IDC_CHECKBACKGROUNDFILE) == BST_CHECKED ); + EnableWindow( GetDlgItem( hDlg, IDC_STATIC_BACKROUNDFILE ), + IsDlgButtonChecked( hDlg, IDC_CHECKBACKGROUNDFILE) == BST_CHECKED ); + EnableWindow( GetDlgItem( hDlg, IDC_BACKROUNDFILE ), + IsDlgButtonChecked( hDlg, IDC_CHECKBACKGROUNDFILE) == BST_CHECKED ); + EnableWindow( GetDlgItem( hDlg, IDC_BACKGROUNDBROWSE ), + IsDlgButtonChecked( hDlg, IDC_CHECKBACKGROUNDFILE) == BST_CHECKED ); + } + break; + } + switch ( LOWORD( wParam )) { + case IDC_SOUNDBROWSE: + memset( &openFileName, 0, sizeof(openFileName )); + openFileName.lStructSize = OPENFILENAME_SIZE_VERSION_400; + openFileName.hwndOwner = hDlg; + openFileName.hInstance = (HINSTANCE) 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_SOUNDFILE, 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_SOUNDFILE, 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_SOUNDFILE, filePath ); + } + break; + + case IDC_BACKGROUNDBROWSE: + memset( &openFileName, 0, sizeof(openFileName )); + openFileName.lStructSize = OPENFILENAME_SIZE_VERSION_400; + openFileName.hwndOwner = hDlg; + openFileName.hInstance = (HINSTANCE) 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_BACKROUNDFILE, 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_BACKROUNDFILE, filePath, sizeof(filePath )); + } + openFileName.lpstrInitialDir = initDir; + openFileName.lpstrFile = filePath; + if( GetOpenFileName( &openFileName )) { + + _tcscpy( newBackgroundFile, filePath ); + SetDlgItemText( hDlg, IDC_BACKROUNDFILE, filePath ); + } + break; + + case IDOK: + + // sound file has to be valid + g_BreakPlaySoundFile = IsDlgButtonChecked( hDlg, IDC_CHECKSOUNDFILE ) == BST_CHECKED; + g_BreakShowBackgroundFile = IsDlgButtonChecked( hDlg, IDC_CHECKBACKGROUNDFILE ) == BST_CHECKED; + g_BreakBackgroundStretch = IsDlgButtonChecked( hDlg, IDC_CHECKBACKGROUNDSTRETCH ) == 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 inacessible", + L"Adanced Break Options Error", MB_ICONERROR ); + break; + } + _tcscpy( g_BreakSoundFile, newSoundFile ); + + // Background file + g_BreakShowDesktop = IsDlgButtonChecked( hDlg, IDC_STATIC_DESKTOPBACKGROUND ) == BST_CHECKED; + + if( !g_BreakShowDesktop && g_BreakShowBackgroundFile && GetFileAttributes( newBackgroundFile ) == -1 ) { + + MessageBox( hDlg, L"The specified background file is inacessible", + L"Adanced Break Options Error", MB_ICONERROR ); + break; + } + _tcscpy( g_BreakBackgroundFile, newBackgroundFile ); + + for( i = 0; i < 10; i++ ) { + + if( IsDlgButtonChecked( hDlg, IDC_TIMERPOS1+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 chf; + 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_ADVANCEDBREAK: + DialogBox( g_hInstance, L"ADVANCEDBREAK", hDlg, AdvancedBreakProc ); + break; + case IDC_FONT: + hDC = GetDC (hDlg ); + lf = g_LogFont; + lf.lfHeight = -21; + chf.hDC = CreateCompatibleDC (hDC); + ReleaseDC (hDlg, hDC); + chf.lStructSize = sizeof (CHOOSEFONT); + chf.hwndOwner = hDlg; + chf.lpLogFont = &lf; + chf.Flags = CF_SCREENFONTS|CF_ENABLETEMPLATE| + CF_INITTOLOGFONTSTRUCT|CF_LIMITSIZE; + chf.rgbColors = RGB (0, 0, 0); + chf.lCustData = 0; + chf.nSizeMin = 16; + chf.nSizeMax = 16; + chf.hInstance = g_hInstance; + chf.lpszStyle = (LPTSTR)NULL; + chf.nFontType = SCREEN_FONTTYPE; + chf.lpfnHook = (LPCFHOOKPROC)(FARPROC)NULL; + chf.lpTemplateName = (LPTSTR)MAKEINTRESOURCE (FORMATDLGORD31); + if( ChooseFont( &chf ) ) { + g_LogFont = lf; + InvalidateRect( hDlg, NULL, TRUE ); + } + break; + case IDC_DEMOTYPEBROWSE: + memset( &openFileName, 0, sizeof( openFileName ) ); + openFileName.lStructSize = OPENFILENAME_SIZE_VERSION_400; + openFileName.hwndOwner = hDlg; + openFileName.hInstance = (HINSTANCE)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 inacessible", APPNAME, MB_ICONERROR ); + } + else + { + SetDlgItemText( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPEFILE, filePath ); + _tcscpy( g_DemoTypeFile, filePath ); + } + } + break; + } + break; + + case WM_PAINT: + if( (hTextPreview = GetDlgItem( hDlg, IDC_TEXTFONT ))) { + + // 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, (LPPOINT)&previewRc, 2); + + previewRc.top += 6; + DrawText( hDc, L"Sample", (int) _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, (LPPOINT)&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, DRAW_HOTKEY); + UnregisterHotKey( hWnd, BREAK_HOTKEY); + UnregisterHotKey( hWnd, RECORD_HOTKEY); + UnregisterHotKey( hWnd, RECORDCROP_HOTKEY ); + UnregisterHotKey( hWnd, RECORDWINDOW_HOTKEY ); + UnregisterHotKey( hWnd, SNIP_HOTKEY ); + UnregisterHotKey( hWnd, SNIPSAVE_HOTKEY); + UnregisterHotKey( hWnd, DEMOTYPE_HOTKEY ); + UnregisterHotKey( hWnd, DEMOTYPERESET_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); + 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, DEMOTYPERESET_HOTKEY, (g_DemoTypeToggleMod ^ MOD_SHIFT), g_DemoTypeToggleKey & 0xFF); + } + if (g_SnipToggleKey) { + RegisterHotKey(hWnd, SNIP_HOTKEY, g_SnipToggleMod, g_SnipToggleKey & 0xFF); + RegisterHotKey(hWnd, SNIPSAVE_HOTKEY, (g_SnipToggleMod ^ MOD_SHIFT), g_SnipToggleKey & 0xFF); + } + if (g_RecordToggleKey) { + RegisterHotKey(hWnd, RECORD_HOTKEY, g_RecordToggleMod | MOD_NOREPEAT, g_RecordToggleKey & 0xFF); + RegisterHotKey(hWnd, RECORDCROP_HOTKEY, (g_RecordToggleMod ^ MOD_SHIFT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF); + RegisterHotKey(hWnd, RECORDWINDOW_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_PENCONTROL, IDC_COLORS, IDC_HIGHLIGHTANDBLUR, 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 = (HFONT) SendMessage( hHeader, WM_GETFONT, 0, 0 ); + LOGFONT lf = {}; + GetObject( hFont, sizeof( LOGFONT ), &lf ); + lf.lfWeight = FW_BOLD; + headerFont = CreateFontIndirect( &lf ); + } + SendMessage( hHeader, WM_SETFONT, (WPARAM) 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, (LPPOINT) &rc, 2 ); + HDC hDC = GetDC( hHeader ); + SelectFont( hDC, headerFont ); + DrawText( hDC, text, (int) _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]; + int i; + 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 ); + + // 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, + (WPARAM) 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_LIVEHOTKEY), HKM_SETHOTKEY, g_LiveZoomToggleKey, 0 ); + + } else { + + EnableWindow( GetDlgItem( g_OptionsTabs[LIVE_PAGE].hPage, IDC_LIVEHOTKEY), FALSE ); + EnableWindow( GetDlgItem( g_OptionsTabs[LIVE_PAGE].hPage, IDC_ZOOMLEVEL), FALSE ); + EnableWindow( GetDlgItem( g_OptionsTabs[LIVE_PAGE].hPage, IDC_ZOOMSPIN), FALSE ); + } + if( g_DrawToggleKey ) SendMessage( GetDlgItem( g_OptionsTabs[DRAW_PAGE].hPage, IDC_DRAWHOTKEY), HKM_SETHOTKEY, g_DrawToggleKey, 0 ); + if( g_BreakToggleKey ) SendMessage( GetDlgItem( g_OptionsTabs[BREAK_PAGE].hPage, IDC_BREAKHOTKEY), HKM_SETHOTKEY, g_BreakToggleKey, 0 ); + if( g_DemoTypeToggleKey ) SendMessage( GetDlgItem( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPEHOTKEY ), HKM_SETHOTKEY, g_DemoTypeToggleKey, 0 ); + if( g_RecordToggleKey ) SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORDHOTKEY), HKM_SETHOTKEY, g_RecordToggleKey, 0 ); + if( g_SnipToggleKey) SendMessage( GetDlgItem( g_OptionsTabs[SNIP_PAGE].hPage, IDC_SNIPHOTKEY), HKM_SETHOTKEY, g_SnipToggleKey, 0 ); + CheckDlgButton( hDlg, IDC_SHOWTRAYICON, + 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_ZOOMSLIDER), TBM_SETRANGE, false, MAKELONG(0,_countof(g_ZoomLevels)-1) ); + SendMessage( GetDlgItem(g_OptionsTabs[ZOOM_PAGE].hPage, IDC_ZOOMSLIDER), TBM_SETPOS, true, g_SliderZoomLevel ); + + _stprintf( text, L"%d", g_PenWidth ); + SetDlgItemText( g_OptionsTabs[DRAW_PAGE].hPage, IDC_PENWIDTH, text ); + SendMessage( GetDlgItem( g_OptionsTabs[DRAW_PAGE].hPage, IDC_PENWIDTH ), 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_SPINTIMER), UDM_SETRANGE, 0L, + MAKELPARAM (99, 1)); + CheckDlgButton( g_OptionsTabs[BREAK_PAGE].hPage, IDC_CHECK_SHOWEXPIRED, + g_ShowExpiredTime ? BST_CHECKED : BST_UNCHECKED ); + + CheckDlgButton( g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTUREAUDIO, + g_CaptureAudio ? BST_CHECKED: BST_UNCHECKED ); + + for (i = 0; i < _countof(g_FramerateOptions); i++) { + + _stprintf(text, L"%d", g_FramerateOptions[i]); + SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORDFRAMERATE), (UINT)CB_ADDSTRING, + (WPARAM)0, (LPARAM)text); + if (g_RecordFrameRate == g_FramerateOptions[i]) { + + SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORDFRAMERATE), CB_SETCURSEL, (WPARAM)i, (LPARAM)0); + } + } + for(int i = 1; i < 11; i++) { + + _stprintf(text, L"%2.1f", ((double) i) / 10 ); + SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORDSCALING), (UINT)CB_ADDSTRING, + (WPARAM)0, (LPARAM)text); + if (g_RecordScaling == i*10 ) { + + SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORDSCALING), CB_SETCURSEL, (WPARAM)i-1, (LPARAM)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 ), (UINT) CB_ADDSTRING, (WPARAM) 0, (LPARAM) L"Default" ); + size_t selection = 0; + for( size_t i = 0; i < microphones.size(); i++ ) + { + SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), (UINT) CB_ADDSTRING, (WPARAM) 0, (LPARAM) 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, (WPARAM) selection, (LPARAM) 0 ); + + if( GetFileAttributes( g_DemoTypeFile ) == -1 ) + { + memset( g_DemoTypeFile, 0, sizeof( g_DemoTypeFile ) ); + } + else + { + SetDlgItemText( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPEFILE, g_DemoTypeFile ); + } + SendMessage( GetDlgItem( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPESPEEDSLIDER ), TBM_SETRANGE, false, MAKELONG( MAX_TYPING_SPEED, MIN_TYPING_SPEED ) ); + SendMessage( GetDlgItem( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPESPEEDSLIDER ), TBM_SETPOS, true, g_DemoTypeSpeedSlider ); + CheckDlgButton( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPEUSERDRIVEN, 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( (HWND) lParam == GetDlgItem( hDlg, IDC_TITLE ) || + (HWND) lParam == GetDlgItem( hDlg, IDC_DRAWING ) || + (HWND) lParam == GetDlgItem( hDlg, IDC_ZOOM ) || + (HWND) lParam == GetDlgItem( hDlg, IDC_BREAK ) || + (HWND) lParam == GetDlgItem( hDlg, IDC_TYPE )) { + + HDC hdc = (HDC)wParam; + SetBkMode( hdc, TRANSPARENT ); + SelectObject( hdc, hFontBold ); + return PtrToLong(GetSysColorBrush( COLOR_BTNFACE )); + } + break; + + case WM_NOTIFY: + notify = (PNMLINK) 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_SHOWTRAYICON ) == BST_CHECKED; + g_AnimateZoom = IsDlgButtonChecked( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_ANIMATE_ZOOM ) == BST_CHECKED; + g_DemoTypeUserDriven = IsDlgButtonChecked( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPEUSERDRIVEN ) == BST_CHECKED; + + newToggleKey = (DWORD) SendMessage( GetDlgItem( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_HOTKEY), HKM_GETHOTKEY, 0, 0 ); + newLiveZoomToggleKey = (DWORD) SendMessage( GetDlgItem( g_OptionsTabs[LIVE_PAGE].hPage, IDC_LIVEHOTKEY), HKM_GETHOTKEY, 0, 0 ); + newDrawToggleKey = (DWORD) SendMessage( GetDlgItem( g_OptionsTabs[DRAW_PAGE].hPage, IDC_DRAWHOTKEY), HKM_GETHOTKEY, 0, 0 ); + newBreakToggleKey = (DWORD) SendMessage( GetDlgItem( g_OptionsTabs[BREAK_PAGE].hPage, IDC_BREAKHOTKEY), HKM_GETHOTKEY, 0, 0 ); + newDemoTypeToggleKey = (DWORD)SendMessage( GetDlgItem( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPEHOTKEY ), HKM_GETHOTKEY, 0, 0 ); + newRecordToggleKey = (DWORD)SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORDHOTKEY), HKM_GETHOTKEY, 0, 0); + newSnipToggleKey = (DWORD) SendMessage( GetDlgItem( g_OptionsTabs[SNIP_PAGE].hPage, IDC_SNIPHOTKEY), 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 = (int) SendMessage( GetDlgItem(g_OptionsTabs[ZOOM_PAGE].hPage, IDC_ZOOMSLIDER), TBM_GETPOS, 0, 0 ); + g_DemoTypeSpeedSlider = (int) SendMessage( GetDlgItem( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPESPEEDSLIDER ), TBM_GETPOS, 0, 0 ); + + g_ShowExpiredTime = IsDlgButtonChecked( g_OptionsTabs[BREAK_PAGE].hPage, IDC_CHECK_SHOWEXPIRED ) == BST_CHECKED; + g_CaptureAudio = IsDlgButtonChecked(g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTUREAUDIO) == 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_RECORDFRAMERATE), (UINT)CB_GETCURSEL, (WPARAM)0, (LPARAM)0)]; + g_RecordScaling = (int) SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORDSCALING), (UINT)CB_GETCURSEL, (WPARAM)0, (LPARAM)0) * 10 + 10; + + // Get the selected microphone + int index = (int) SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), (UINT) CB_GETCURSEL, (WPARAM) 0, (LPARAM) 0 ); + _tcscpy( g_MicrophoneDeviceId, index == 0 ? L"" : microphones[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 )) { + + 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), DEMOTYPERESET_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), SNIPSAVE_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), RECORDCROP_HOTKEY, (newRecordToggleMod ^ MOD_SHIFT) | MOD_NOREPEAT, newRecordToggleKey & 0xFF) || + !RegisterHotKey(GetParent(hDlg), RECORDWINDOW_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( PDRAW_UNDO *DrawUndoList ) +{ + PDRAW_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, PDRAW_UNDO *DrawUndoList, + int width, int height ) +{ + PDRAW_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( PDRAW_UNDO *DrawUndoList ) +{ + PDRAW_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 +// +//---------------------------------------------------------------------------- +PDRAW_UNDO GetOldestUndo(PDRAW_UNDO DrawUndoList) +{ + PDRAW_UNDO nextUndo, oldestUndo = NULL; + + nextUndo = DrawUndoList; + oldestUndo = nextUndo; + do { + + oldestUndo = nextUndo; + nextUndo = nextUndo->Next; + + } while( nextUndo ); + return oldestUndo; +} + + +//---------------------------------------------------------------------------- +// +// PushDrawUndo +// +//---------------------------------------------------------------------------- +void PushDrawUndo( HDC hDc, PDRAW_UNDO *DrawUndoList, int width, int height ) +{ + PDRAW_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 = (PDRAW_UNDO) 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( PTYPED_KEY *TypedKeyList ) +{ + PTYPED_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( (HGDIOBJ) 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 ) +{ + // Draw the typing cursor + rc->left = textPt.x; + rc->top = textPt.y; + TCHAR vKey = '|'; + DrawText( hdcScreenCompat, (PTCHAR) &vKey, 1, rc, DT_CALCRECT ); + + BitBlt(hdcScreenCursorCompat, 0, 0, rc->right -rc->left, rc->bottom - rc->top, + hdcScreenCompat, rc->left, rc->top, SRCCOPY|CAPTUREBLT ); + + DrawText( hdcScreenCompat, (PTCHAR) &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 + (int) (width/zoomLevel); + rc.top = monInfo->rcMonitor.top + y; + rc.bottom = rc.top + (int) (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 = x2 - x1; + double dy = 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 - (int)(length*dx+0.5); + int yMid = y2 - (int)(length*dy+0.5); + + // get left wing + int xLeft = xMid - (int)(dy*width+0.5); + int yLeft = yMid + (int)(dx*width+0.5); + + // get right wing + int xRight = xMid + (int)(dy*width+0.5); + int yRight = yMid - (int)(dx*width+0.5); + + // Bring midpoint in to make a nicer arrow + xMid = x2 - (int)(length/2*dx+0.5); + yMid = y2 - (int)(length/2*dy+0.5); + if (UseGdiplus) { + + Gdiplus::Graphics dstGraphics(hdc); + + dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + Gdiplus::Color color = ColorFromCOLORREF(g_PenColor); + Gdiplus::Pen pen(color, (Gdiplus::REAL)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((INT)x1, (INT)y1, (INT)x2, (INT)y2); + path.AddLine((INT)x2, (INT)y2, (INT)xMid, (INT)yMid); + path.AddLine((INT)xMid, (INT)yMid, (INT)xLeft, (INT)yLeft); + path.AddLine((INT)xLeft, (INT)yLeft, (INT)x2, (INT)y2); + path.AddLine((INT)x2, (INT)y2, (INT)xRight, (INT)yRight); + path.AddLine((INT)xRight, (INT)yRight, (INT)xMid, (INT)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); + + dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + Gdiplus::Color color = ColorFromCOLORREF(g_PenColor); + Gdiplus::Pen pen(color, (Gdiplus::REAL)g_PenWidth); + pen.SetLineCap(Gdiplus::LineCapRound, Gdiplus::LineCapRound, Gdiplus::DashCapRound); + + // Check for hihglighting or blur + Gdiplus::Brush *pBrush = NULL; + if (PEN_COLOR_HIGHLIGHT(g_PenColor)) { + // Use half the alpha for higher contrast + DWORD color = g_PenColor & 0xFFFFFF | ((g_AlphaBlend / 2) << 24); + pBrush = new Gdiplus::SolidBrush( ColorFromCOLORREF( color ) ); + 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, + (int)(Rect->left - 1), (int)(Rect->top - 1), + (int)Rect->right, (int)Rect->bottom); + else if (isblur) + DrawBlurredShape( DRAW_RECTANGLE, &pen, hDc, &dstGraphics, + (int)(Rect->left - 1), (int)(Rect->top - 1), + (int)Rect->right, (int)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, + (int)(Rect->left - 1), (int)(Rect->top - 1), + (int)Rect->right, (int)Rect->bottom); + else if (isblur) + DrawBlurredShape( DRAW_ELLIPSE, &pen, hDc, &dstGraphics, + (int)(Rect->left - 1), (int)(Rect->top - 1), + (int)Rect->right, (int)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, + (int)Rect->left, (int)Rect->top, + (int)Rect->right, (int)Rect->bottom); + else if (isblur) + DrawBlurredShape(DRAW_LINE, &pen, hDc, &dstGraphics, + (int)Rect->left, (int)Rect->top, + (int)Rect->right, (int)Rect->bottom); + else + dstGraphics.DrawLine(&pen, + (INT)(Rect->left - 1), (INT)(Rect->top - 1), + (INT)Rect->right, (INT)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, + (double)g_PenWidth * 2.5, (double)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((LPARAM)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 = (WORD)((x - (WORD) monInfo->rcMonitor.left)/ zoomLevel) + (WORD) (boundRc.left - monInfo->rcMonitor.left); + y = (WORD)((y - (WORD) monInfo->rcMonitor.top) / zoomLevel) + (WORD) (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 = (DWORD) ((float) Width/ZoomLevel); + DWORD zoomHeight = (DWORD) ((float) 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 = (int) max( 0, (int) ((min( prevPt.x, currentPt.x)-invWidth - x) * zoomLevel)); + rc.right = (int) ((max( prevPt.x, currentPt.x)+invWidth - x) * zoomLevel); + rc.top = (int) max( 0, (int) ((min( prevPt.y, currentPt.y)-invWidth - y) * zoomLevel)); + rc.bottom = (int) ((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 +CURSORARMLENGTH*2, penWidth +CURSORARMLENGTH*2, + hDcSource, (INT) (pt.x- penWidth /2)-CURSORARMLENGTH, + (INT) (pt.y- penWidth /2)-CURSORARMLENGTH, SRCCOPY|CAPTUREBLT ); +} + +//---------------------------------------------------------------------------- +// +// RestoreCursorArea +// +//---------------------------------------------------------------------------- +void RestoreCursorArea( HDC hDcTarget, HDC hDcSource, POINT pt ) +{ + OutputDebug( L"RestoreCursorArea\n"); + int penWidth = g_PenWidth + 2; + BitBlt( hDcTarget, (INT) (pt.x- penWidth /2)-CURSORARMLENGTH, + (INT) (pt.y- penWidth /2)-CURSORARMLENGTH, penWidth +CURSORARMLENGTH*2, + penWidth + CURSORARMLENGTH*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); + dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + Gdiplus::Color color = ColorFromCOLORREF(g_PenColor); + Gdiplus::Pen pen(color, (Gdiplus::REAL)g_PenWidth); + + rc.left = pt.x - CURSORARMLENGTH; + rc.right = pt.x + CURSORARMLENGTH; + rc.top = pt.y - CURSORARMLENGTH; + rc.bottom = pt.y + CURSORARMLENGTH; + + Gdiplus::GraphicsPath path; + path.StartFigure(); + path.AddLine((INT)rc.left-1, (INT)rc.top-1, (INT)rc.right, (INT)rc.bottom); + path.AddLine((INT)rc.left - 2, (INT)rc.top - 1, rc.left + (rc.right - rc.left) / 2, rc.top - 1); + path.AddLine((INT)rc.left - 1, (INT)rc.top - 2, rc.left - 1, rc.top + (rc.bottom - rc.top) / 2); + path.AddLine((INT)rc.left - 1, (INT)rc.top - 2, rc.left - 1, rc.top + (rc.bottom - rc.top) / 2); + path.AddLine((INT)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); + dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + Gdiplus::Color color = ColorFromCOLORREF(g_PenColor); + Gdiplus::Pen pen(color, (Gdiplus::REAL)g_PenWidth); + Gdiplus::GraphicsPath path; + path.StartFigure(); + pen.SetLineJoin(Gdiplus::LineJoinRound); + path.AddLine(pt.x - CURSORARMLENGTH, pt.y, pt.x + CURSORARMLENGTH, pt.y); + path.CloseFigure(); + path.StartFigure(); + pen.SetLineJoin(Gdiplus::LineJoinRound); + path.AddLine(pt.x, pt.y - CURSORARMLENGTH, pt.x, pt.y + CURSORARMLENGTH); + path.CloseFigure(); + dstGraphics.DrawPath(&pen, &path); + + } else { + + Gdiplus::Graphics dstGraphics(hDcTarget); + { + dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + Gdiplus::Color color = ColorFromCOLORREF(g_PenColor); + + Gdiplus::SolidBrush solidBrush(color); + + dstGraphics.FillEllipse(&solidBrush, (INT) (pt.x-g_PenWidth/2), (INT) (pt.y-g_PenWidth/2), + (INT) (g_PenWidth), (INT) (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"RESIZEPEN-PRE: penWidth: %d ", g_PenWidth ); + int prevWidth = g_PenWidth; + if( g_ZoomOnLiveZoom ) + { + if( isUser ) + { + // Amplify user delta proportional to LiveZoomLevel + newWidth = g_PenWidth + (int) ((newWidth - (int) g_PenWidth)*g_LiveZoomLevel); + } + + g_PenWidth = min( max( newWidth, MIN_PENWIDTH ), + min( (int) (MAX_PENWIDTH * g_LiveZoomLevel), MAX_LIVEPENWIDTH ) ); + g_RootPenWidth = (int) (g_PenWidth / g_LiveZoomLevel); + } + else + { + g_PenWidth = min( max( newWidth, MIN_PENWIDTH ), MAX_PENWIDTH ); + g_RootPenWidth = g_PenWidth; + } + + if(prevWidth == g_PenWidth ) { + // No change + return; + } + + OutputDebug( L"newWidth: %d\nRESIZEPEN-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::CreateD3DDevice(); + 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 = reinterpret_cast(mapped.pData); + auto dest = bytes.data(); + for (auto i = 0; i < (int)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::CreateD3DDevice(); + 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_SAVECURSOR, 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 item; + wil::unique_cotaskmem_string folderPath; + if( SUCCEEDED( saveDialog->GetFolder( item.put() ) ) && SUCCEEDED( item->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 item; + THROW_IF_FAILED( saveDialog->GetResult( item.put() ) ); + wil::unique_cotaskmem_string filePath; + THROW_IF_FAILED( item->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((WCHAR*)g_RecordingSaveLocation.c_str(), hWnd); + } + g_bSaveInProgress = false; + + SendMessage( g_hWndMain, WM_USER_RESTORECURSOR, 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_STOPRECORDING, 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 ); + } + } +} + +//---------------------------------------------------------------------------- +// +// 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 PDRAW_UNDO drawUndoList = NULL; + static PTYPED_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 tnid; + + 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(), (int)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(), (int)line.length(), &rc, DT_LEFT ); + rc.top += lineHeight; + } + if( !g_TextBuffer.empty() ) + { + if( doPop ) + { + g_TextBuffer.pop_back(); + } + DrawText( hdcScreenCompat, g_TextBuffer.c_str(), (int)g_TextBuffer.length(), &rc, DT_CALCRECT ); + rc.left = textPt.x - (rc.right - rc.left); + rc.right = textPt.x; + DrawText( hdcScreenCompat, g_TextBuffer.c_str(), (int)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 ) { + + 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)) { + + 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, DEMOTYPERESET_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, SNIPSAVE_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, RECORDCROP_HOTKEY, (g_RecordToggleMod ^ MOD_SHIFT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF) || + !RegisterHotKey(hWnd, RECORDWINDOW_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 != RECORDCROP_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 DRAW_HOTKEY: + // + // Enter drawing mode without zoom + // + if( !g_Zoomed ) { + +#if WINDOWS_CURSOR_RECORDING_WORKAROUND + if( IsWindowVisible( g_hWndLiveZoom ) && !g_LiveZoomLevelOne ) { +#else + if( IsWindowVisible( g_hWndLiveZoom )) { +#endif + + SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 ); + + } else { + SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 ); + zoomLevel = zoomTelescopeTarget = 1; + SendMessage( hWnd, WM_LBUTTONDOWN, 0, MAKELPARAM( cursorPos.x, cursorPos.y )); + } + } + break; + + case SNIPSAVE_HOTKEY: + case SNIP_HOTKEY: + { + bool zoomed = true; + + // 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, 0 ); + } + zoomLevel = zoomTelescopeTarget = 1; + } + else if( g_Drawing ) + { + // Exit drawing mode to hide the drawing cursor + SendMessage( hWnd, WM_USER_EXITMODE, 0, 0 ); + + // Exit again if still in drawing mode, which happens from type mode + if( g_Drawing ) + { + SendMessage( hWnd, WM_USER_EXITMODE, 0, 0 ); + } + } + + // Now copy crop or copy+save + if( LOWORD( wParam ) == SNIPSAVE_HOTKEY ) + { + SendMessage( hWnd, WM_COMMAND, IDC_SAVECROP, ( zoomed ? 0 : SHALLOW_ZOOM ) ); + } + else + { + SendMessage( hWnd, WM_COMMAND, IDC_COPYCROP, ( 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 ) + { + // 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 DEMOTYPERESET_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; + } + break; + } + + case LIVE_HOTKEY: + // + // Live zoom + // + if( !g_Zoomed && !g_TimerActive && ( !g_fullScreenWorkaround || !g_RecordToggle ) ) { + + if( g_hWndLiveZoom == NULL ) { + + 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, (PVOID) 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 ) { + + SendMessage( g_hWndLiveZoom, WM_USER_SETZOOM, (WPARAM) 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 { + + ShowWindow( g_hWndLiveZoom, SW_SHOW ); + } +#if WINDOWS_CURSOR_RECORDING_WORKAROUND + } +#endif + } + + if ( g_RecordToggle ) + { + g_SelectRectangle.UpdateOwner( g_hWndLiveZoom ); + } + } + break; + + case RECORD_HOTKEY: + case RECORDCROP_HOTKEY: + case RECORDWINDOW_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 == RECORDCROP_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_MAGNIFYCURSOR, FALSE, 0 ); + + // Show the system cursor where the magnified was. + g_LiveZoomSourceRect = *reinterpret_cast( SendMessage( g_hWndLiveZoom, WM_USER_GETSOURCERECT, 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_MAGNIFYCURSOR, 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 == RECORDWINDOW_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; + 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"Hotkey\n"); + 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 ); + DeleteObject( hbmpCompat ); + EnableDisableScreenSaver( TRUE ); + EnableDisableOpacity( hWnd, FALSE ); + + } else { + + SendMessage( hWnd, WM_USER_TYPINGOFF, 0, 0 ); + if( !g_Zoomed ) { + + g_Zoomed = TRUE; + g_DrawingShape = FALSE; + OutputDebug( L"Zoom on\n"); + + // Hide the cursor before capturing if in live zoom + if( g_hWndLiveZoom != nullptr ) + { + SendMessage( g_hWndLiveZoom, WM_USER_MAGNIFYCURSOR, FALSE, 0 ); + SendMessage( g_hWndLiveZoom, WM_TIMER, 0, 0 ); + SendMessage( g_hWndLiveZoom, WM_USER_MAGNIFYCURSOR, FALSE, 0 ); + } + + // Get screen DCs + hdcScreen = CreateDC(L"DISPLAY", (PTCHAR) NULL, + (PTCHAR) NULL, (CONST DEVMODE *) 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 = (BYTE) GetDeviceCaps(hdcScreen, BITSPIXEL); + bmp.bmPlanes = (BYTE) 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, (CONST VOID *) NULL); + SelectObject(hdcScreenCompat, hbmpCompat); + + // Create saved bitmap + hbmpDrawingCompat = CreateBitmap(bmp.bmWidth, bmp.bmHeight, + bmp.bmPlanes, bmp.bmBitsPixel, (CONST VOID *) NULL); + SelectObject(hdcScreenSaveCompat, hbmpDrawingCompat); + + // Create cursor save bitmap + // (have to accomodate large fonts and LiveZoom pen scaling) + hbmpCursorCompat = CreateBitmap( MAX_LIVEPENWIDTH+CURSORARMLENGTH*2, + MAX_LIVEPENWIDTH+CURSORARMLENGTH*2, bmp.bmPlanes, + bmp.bmBitsPixel, (CONST VOID *) 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 = -(LONG) 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 ) + { + 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) ); + + RedrawWindow( hWnd, nullptr, nullptr, RDW_ALLCHILDREN | RDW_UPDATENOW ); + SetWindowPos( hWnd, HWND_TOPMOST, monInfo.rcMonitor.left, monInfo.rcMonitor.top, + width, height, SWP_SHOWWINDOW ); + SetForegroundWindow( hWnd ); + SetActiveWindow( hWnd ); + + // 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 + g_LiveZoomSourceRect = *(RECT *) SendMessage( g_hWndLiveZoom, WM_USER_GETSOURCERECT, 0, 0 ); + g_LiveZoomLevel = *(float *) SendMessage( g_hWndLiveZoom, WM_USER_GETZOOMLEVEL, 0, 0 ); + + // Set live zoom level to 1 in preparation of us being full screen static + zoomLevel = 1.0; + zoomTelescopeTarget = 1.0; + g_ZoomOnLiveZoom = TRUE; + + UpdateWindow( hWnd ); // overwrites where cursor erased + if( lParam != SHALLOW_ZOOM ) + { + // Put the drawing cursor where the magnified cursor was + 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 ); + } + ShowWindow( g_hWndLiveZoom, SW_HIDE ); + + } else if( lParam != 0 ) { + + zoomTelescopeStep = ZOOMLEVEL_STEPIN; + zoomTelescopeTarget = g_ZoomLevels[g_SliderZoomLevel]; + if( g_AnimateZoom ) + zoomLevel = (float) 1.0 * zoomTelescopeStep; + else + zoomLevel = zoomTelescopeTarget; + SetTimer( hWnd, 1, ZOOMLEVEL_STEPTIME, NULL ); + } + + } else { + + OutputDebug( L"Zoom off: don't animate=%d\n", lParam ); + if( lParam != SHALLOW_DESTROY && !g_ZoomOnLiveZoom && g_AnimateZoom && + g_TelescopeZoomeOut && zoomTelescopeTarget != 1 ) { + + // Start telescoping zoom. + zoomTelescopeStep = ZOOMLEVEL_STEPOUT; + zoomTelescopeTarget = 1.0; + SetTimer( hWnd, 2, ZOOMLEVEL_STEPTIME, 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 + 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 ); + + } else { + + if( delta > 0 ) zoomIn = TRUE; + else { + zoomIn = FALSE; + delta = -delta; + } + while( delta-- ) { + + if( zoomIn ) { + + if( zoomTelescopeTarget < ZOOMLEVEL_MAX ) { + + if( zoomTelescopeTarget < 2 ) { + + zoomTelescopeTarget = 2; + + } else { + + // Start telescoping zoom + zoomTelescopeTarget = zoomTelescopeTarget * 2; + } + zoomTelescopeStep = ZOOMLEVEL_STEPIN; + if( g_AnimateZoom ) + zoomLevel *= zoomTelescopeStep; + else + zoomLevel = zoomTelescopeTarget; + + if( zoomLevel > zoomTelescopeTarget ) + zoomLevel = zoomTelescopeTarget; + else + SetTimer( hWnd, 1, ZOOMLEVEL_STEPTIME, NULL ); + } + + } else if( zoomTelescopeTarget > ZOOMLEVEL_MIN ) { + + // Let them more gradually zoom out from 2x to 1x + if( zoomTelescopeTarget <= 2 ) { + + zoomTelescopeTarget *= .75; + if( zoomTelescopeTarget < ZOOMLEVEL_MIN ) + zoomTelescopeTarget = ZOOMLEVEL_MIN; + + } else { + + zoomTelescopeTarget = zoomTelescopeTarget/2; + } + zoomTelescopeStep = ZOOMLEVEL_STEPOUT; + 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, ZOOMLEVEL_STEPTIME, 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((TCHAR)wParam) || ((TCHAR) wParam == L'&')) { + g_HaveTyped = TRUE; + + TCHAR vKey = (TCHAR) 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, (PTCHAR)&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 + PTYPED_KEY newKey = (PTYPED_KEY)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 reseting 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 ); + } + break; + + case WM_KEYDOWN: + + if( (g_TypeMode != TypeModeOff) && g_HaveTyped && (char) wParam != VK_UP && (char) wParam != VK_DOWN && + (isprint( (char) 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. + PTYPED_KEY newKey = (PTYPED_KEY)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, (PTCHAR) &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 ) { + + PTYPED_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, (PTCHAR)&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 rc = deletedKey->rc; + if (g_BlankedScreen) { + + BlankScreenArea( hdcScreenCompat, &rc, g_BlankedScreen ); + + } + else { + + BitBlt( hdcScreenCompat, rc.left, rc.top, rc.right - rc.left, + rc.bottom - rc.top, hdcScreenSaveCompat, rc.left, rc.top, SRCCOPY | CAPTUREBLT ); + } + InvalidateRect( hWnd, NULL, FALSE ); + + textPt.x = rc.left; + textPt.y = rc.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' ) *penColor = COLOR_BLUR; + reg.WriteRegSettings( RegSettings ); + DeleteObject( hDrawingPen ); + SetTextColor( hdcScreenCompat, *penColor ); + + // Highlight and blur level + bool shift = GetKeyState( VK_SHIFT ) & 0x8000; + 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': + // 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 = (int) 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"); + 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_TYPINGOFF, 0, 0 ); + + } else { + + forcePenResize = TRUE; + PostMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 ); + } + break; + } + return TRUE; + + case WM_RBUTTONDOWN: + SendMessage( hWnd, WM_USER_EXITMODE, 0, 0 ); + break; + + case WM_MOUSEMOVE: + 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 + 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 = (LONG) LOWORD(lParam); + g_rcRectangle.bottom = (LONG) 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) { + + g_HaveDrawn = TRUE; + Gdiplus::Graphics dstGraphics(hdcScreenCompat); + dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + Gdiplus::Color color = ColorFromCOLORREF(g_PenColor); + Gdiplus::Pen pen(color, (Gdiplus::REAL)g_PenWidth); + pen.SetLineCap(Gdiplus::LineCapRound, Gdiplus::LineCapRound, Gdiplus::DashCapRound); + + // If hlighting, 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 + PDRAW_UNDO oldestUndo = 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; + PDRAW_UNDO oldestUndo = GetOldestUndo(drawUndoList); + BYTE* pDestPixels2 = CreateBitmapMemoryDIB(hdcScreenCompat, oldestUndo->hDc, &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); + } + else { + + // Normal tracing + dstGraphics.DrawLine(&pen, (INT)(prevPt.x), (INT)(prevPt.y), + (INT)(currentPt.x), (INT)(currentPt.y)); + } + + } else { + + // 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; + + } 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 ); + 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); + dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + Gdiplus::Color color = ColorFromCOLORREF(g_PenColor); + Gdiplus::Pen pen(color, (Gdiplus::REAL)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); + dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + Gdiplus::Color color = ColorFromCOLORREF(g_PenColor); + Gdiplus::Pen pen(color, (Gdiplus::REAL)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( (int) (g_LiveZoomLevel * g_RootPenWidth), + (int) (g_LiveZoomLevel * MAX_PENWIDTH) ) ); + OutputDebug( L"LIVEZOOMDRAW: zoomlevel: %d rootPenWidth: %d penWidth: %d\n", + (int) 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_TYPINGOFF, 0, 0 ); + } + } + return TRUE; + + case WM_LBUTTONUP: + if( g_Zoomed && g_Drawing && g_Tracing ) { + + OutputDebug(L"LBUTTONUP\n"); + + // Are we in pen mode on a tablet? + lParam = ScalePenPosition( zoomLevel, &monInfo, boundRc, + message, 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); + dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + Gdiplus::Color color = ColorFromCOLORREF(g_PenColor); + Gdiplus::Pen pen(color, (Gdiplus::REAL)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 = (HBRUSH) GetStockObject( NULL_BRUSH ); + HBRUSH oldHbrush = (HBRUSH) 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: + + ((MINMAXINFO *) lParam)->ptMaxSize.x = width; + ((MINMAXINFO *) lParam)->ptMaxSize.y = height; + ((MINMAXINFO *) lParam)->ptMaxPosition.x = 0; + ((MINMAXINFO *) lParam)->ptMaxPosition.y = 0; + return TRUE; + + case WM_USER_TYPINGOFF: { + + 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) { + + // FIXFIX 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_TRAYACTIVATE: + + 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(); + 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" ); + 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_STOPRECORDING: + StopRecording(); + break; + + case WM_USER_SAVECURSOR: + if( g_Zoomed == TRUE ) + { + GetCursorPos( &savedCursorPos ); + if( g_Drawing == TRUE ) + { + ClipCursor( NULL ); + } + } + break; + + case WM_USER_RESTORECURSOR: + if( g_Zoomed == TRUE ) + { + if( g_Drawing == TRUE ) + { + boundRc = BoundMouse( zoomLevel, &monInfo, width, height, &cursorPos ); + } + SetCursorPos( savedCursorPos.x, savedCursorPos.y ); + } + break; + + case WM_USER_EXITMODE: + if( g_Zoomed ) + { + // Turn off + if( g_TypeMode != TypeModeOff ) + { + SendMessage( hWnd, WM_USER_TYPINGOFF, 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 ); + } + 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_TYPINGOFF, 0, 0 ); + + // Unclip cursor + ClipCursor( NULL ); + } + } + else if( g_TimerActive ) + { + // Turn off + PostMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 ); + } + break; + + case WM_COMMAND: + + switch(LOWORD( wParam )) { + + case IDC_SAVECROP: + case IDC_SAVE: + { + POINT savedCursorPos; + if( lParam != SHALLOW_ZOOM ) + { + GetCursorPos( &savedCursorPos ); + } + + int saveWidth, saveHeight; + int copyWidth, copyHeight; + + GetZoomedTopLeftCoordinates(zoomLevel, &cursorPos, &x, width, &y, height); + + if ( LOWORD( wParam ) == IDC_SAVECROP ) + { + g_RecordCropping = TRUE; + SelectRectangle selectRectangle; + if( !selectRectangle.Start( hWnd ) ) + { + g_RecordCropping = FALSE; + if( lParam != SHALLOW_ZOOM ) + { + SetCursorPos( savedCursorPos.x, savedCursorPos.y ); + } + break; + } + auto copyRc = selectRectangle.SelectedRect(); + selectRectangle.Stop(); + g_RecordCropping = FALSE; + x = (int) (x + copyRc.left / zoomLevel); + y = (int) (y + copyRc.top / zoomLevel); + copyWidth = copyRc.right - copyRc.left; + copyHeight = copyRc.bottom - copyRc.top; + } + else + { + copyWidth = width; + copyHeight = height; + } + + RECT oldClipRect{}; + GetClipCursor( &oldClipRect ); + ClipCursor( NULL ); + + g_bSaveInProgress = true; + memset( &openFileName, 0, sizeof(openFileName )); + openFileName.lStructSize = OPENFILENAME_SIZE_VERSION_400; + openFileName.hwndOwner = hWnd; + openFileName.hInstance = (HINSTANCE) 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 )) { + + // Save the bitmap + HBITMAP hSaveBitmap; + HDC hSaveDc; + + if( openFileName.nFilterIndex == 1 ) { + + saveWidth = copyWidth; + saveHeight = copyHeight; + + } else { + + saveWidth = (int) (copyWidth /zoomLevel); + saveHeight = (int) (copyHeight /zoomLevel); + } + hSaveBitmap = CreateCompatibleBitmap( hdcScreenCompat, saveWidth, saveHeight ); + hSaveDc = CreateCompatibleDC(hdcScreenCompat); + SelectObject( hSaveDc, hSaveBitmap ); +#if SCALE_HALFTONE + SetStretchBltMode( hSaveDc, HALFTONE ); +#else + SetStretchBltMode( hSaveDc, COLORONCOLOR ); +#endif + StretchBlt( hSaveDc, + 0, 0, + saveWidth, saveHeight, + hdcScreenCompat, + x, y, + (int) (copyWidth / zoomLevel), (int)(copyHeight/ zoomLevel), + SRCCOPY|CAPTUREBLT ); + + TCHAR targetFilePath[MAX_PATH]; + _tcscpy( targetFilePath, filePath ); + if( !_tcsrchr( targetFilePath, '.' )) _tcscat( targetFilePath, L".png" ); + SavePng( targetFilePath, hSaveBitmap ); + DeleteDC( hSaveDc ); + } + g_bSaveInProgress = false; + if( lParam != SHALLOW_ZOOM ) + { + SetCursorPos( savedCursorPos.x, savedCursorPos.y ); + } + ClipCursor( &oldClipRect ); + break; + } + + case IDC_COPYCROP: + case IDC_COPY: { + HBITMAP hSaveBitmap; + HDC hSaveDc; + int saveWidth, saveHeight; + + GetZoomedTopLeftCoordinates(zoomLevel, &cursorPos, &x, width, &y, height); + if( LOWORD( wParam ) == IDC_COPYCROP ) + { + g_RecordCropping = TRUE; + POINT savedCursorPos; + if( lParam != SHALLOW_ZOOM ) + { + GetCursorPos( &savedCursorPos ); + } + SelectRectangle selectRectangle; + if( !selectRectangle.Start( hWnd ) ) + { + g_RecordCropping = FALSE; + break; + } + auto copyRc = selectRectangle.SelectedRect(); + selectRectangle.Stop(); + if( lParam != SHALLOW_ZOOM ) + { + SetCursorPos( savedCursorPos.x, savedCursorPos.y ); + } + g_RecordCropping = FALSE; + + x = (int) (x+ copyRc.left/zoomLevel); + y = (int) (y+ copyRc.top/zoomLevel); + saveWidth = copyRc.right - copyRc.left; + saveHeight = copyRc.bottom - copyRc.top; + } + else { + + saveWidth = width; + saveHeight = height; + } + OutputDebug(L"***x: %d, y: %d, width: %d, height: %d\n", x, y, saveWidth, saveHeight); + + hSaveBitmap = CreateCompatibleBitmap( hdcScreenCompat, saveWidth, saveHeight ); + hSaveDc = CreateCompatibleDC(hdcScreenCompat); + SelectObject( hSaveDc, hSaveBitmap ); +#if SCALE_HALFTONE + SetStretchBltMode( hSaveDc, HALFTONE ); +#else + SetStretchBltMode( hSaveDc, COLORONCOLOR ); +#endif + StretchBlt( hSaveDc, + 0, 0, + saveWidth, saveHeight, + hdcScreenCompat, + x, y, + (int) (saveWidth/zoomLevel), (int) (saveHeight/zoomLevel), + 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: + 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", (PTCHAR)NULL, (PTCHAR)NULL, (CONST DEVMODE*)NULL ); + + // toggle second monitor + // FIXFIX: 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; + 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 = (BYTE) GetDeviceCaps(hdcScreen, BITSPIXEL); + bmp.bmPlanes = (BYTE) 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, (CONST VOID *) 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( &tnid, 0, sizeof(tnid)); + tnid.cbSize = sizeof(NOTIFYICONDATA); + tnid.hWnd = hWnd; + tnid.uID = 1; + Shell_NotifyIcon(NIM_DELETE, &tnid); + 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 monleft: %x montop: %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 + (int) ((cursorPos.x - x) * zoomLevel); + cursorPos.y = monInfo.rcMonitor.top + y + (int)((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_SETZOOM, (WPARAM) g_LiveZoomLevel, (LPARAM) &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; + } + 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, + (int) (width/zoomLevel), (int) (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 bmp; + GetObject( g_hBackgroundBmp, sizeof(bmp), &bmp ); + SetStretchBltMode( hdcScreenCompat, HALFTONE ); + if( g_BreakBackgroundStretch ) { + StretchBlt( hdcScreenCompat, 0, 0, width, height, + g_hDcBackgroundFile, 0, 0, bmp.bmWidth, bmp.bmHeight, SRCCOPY|CAPTUREBLT ); + } else { + BitBlt( hdcScreenCompat, width/2 - bmp.bmWidth/2, height/2 - bmp.bmHeight/2, + bmp.bmWidth, 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 = (HFONT) 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 = (HFONT) 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((HWND)((LPCREATESTRUCT)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 = (float ) 1.9; + zoomTelescopeTarget = 2.0; + zoomTelescopeStep = 2.0; + + } else { + + zoomTelescopeStep = ZOOMLEVEL_STEPIN; + zoomTelescopeTarget = g_ZoomLevels[g_SliderZoomLevel]; + + prevZoomStepTickCount = 0; + if( dwmEnabled ) { + + zoomLevel = (float) 1; + + } else { + + zoomLevel = (float) 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, ZOOMLEVEL_STEPTIME, 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); + } + + sourceRectWidth = lastSourceRect.right - lastSourceRect.left; + sourceRectHeight = lastSourceRect.bottom - lastSourceRect.top; + moveWidth = sourceRectWidth/LIVEZOOM_MOVEREGIONS; + moveHeight = sourceRectHeight/LIVEZOOM_MOVEREGIONS; + curTickCount = GetTickCount(); + if( zoomLevel != zoomTelescopeTarget && + (prevZoomStepTickCount == 0 || (curTickCount - prevZoomStepTickCount > ZOOMLEVEL_STEPTIME)) ) { + + 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] = ((float) -lastSourceRect.left * zoomLevel); + matrix.v[1][1] = zoomLevel; + matrix.v[1][2] = ((float) -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, (int *) &zoomCenterPos.x, width, + (int *) &zoomCenterPos.y, height ); + + // + // Add back monitor boundary + // + zoomCenterPos.x += monInfo.rcMonitor.left + (LONG) (width/zoomLevel/2); + zoomCenterPos.y += monInfo.rcMonitor.top + (LONG) (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_MOVEREGIONS-1) ) + zoomCenterPos.x = lastSourceRect.left + sourceRectWidth/2 + (xOffset - moveWidth*(LIVEZOOM_MOVEREGIONS-1)); + if( yOffset < moveHeight ) + zoomCenterPos.y = lastSourceRect.top + sourceRectHeight/2 - (moveHeight - yOffset); + else if( yOffset > moveHeight * (LIVEZOOM_MOVEREGIONS-1) ) + zoomCenterPos.y = lastSourceRect.top + sourceRectHeight/2 + (yOffset - moveHeight*(LIVEZOOM_MOVEREGIONS-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 = (int)(width / zoomLevel); + int zoomHeight = (int)(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] = (float) 1.0; + matrix.v[0][2] = ((float) -monInfo.rcMonitor.left); + + matrix.v[1][1] = (float) 1.0; + matrix.v[1][2] = ((float) -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] = ((float) -sourceRect.left * zoomLevel); + + matrix.v[1][1] = zoomLevel; + matrix.v[1][2] = ((float) -sourceRect.top * zoomLevel); + + matrix.v[2][2] = 1.0f; + } + lastSourceRect = sourceRect; + } + lastCursorPos = cursorPos; + + // Update source and zoom if necessary + if( matrix.v[0][0] ) { + + 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 < ZOOMLEVEL_MAX ) + newZoomLevel *= 2; + zoomTelescopeStep = ZOOMLEVEL_STEPIN; + break; + + case 1: + if( newZoomLevel > 2 ) + newZoomLevel /= 2; + else { + + newZoomLevel *= .75; + if( newZoomLevel < ZOOMLEVEL_MIN ) + newZoomLevel = ZOOMLEVEL_MIN; + } + zoomTelescopeStep = ZOOMLEVEL_STEPOUT; + 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 = ZOOMLEVEL_STEPOUT; + zoomTelescopeTarget = 1.0; + if( !dwmEnabled ) { + + zoomLevel = (float) 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_GETZOOMLEVEL: + return (LRESULT) &zoomLevel; + + case WM_USER_GETSOURCERECT: + return (LRESULT) &lastSourceRect; + + case WM_USER_MAGNIFYCURSOR: + { + 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_SETZOOM: + { + if( g_RecordToggle ) + { + g_SelectRectangle.UpdateOwner( hWnd ); + } + + if( lParam != NULL ) { + + lastSourceRect = *(RECT *) lParam; + } +#if WINDOWS_CURSOR_RECORDING_WORKAROUND + if( g_LiveZoomLevelOne ) { + + g_LiveZoomLevelOne = FALSE; + + zoomTelescopeTarget = (float) wParam; + zoomTelescopeStep = ZOOMLEVEL_STEPIN; + prevZoomStepTickCount = 0; + zoomLevel = 1.0; + + break; + } +#endif + zoomLevel = (float) wParam; + zoomTelescopeTarget = zoomLevel; + matrix.v[0][0] = zoomLevel; + matrix.v[0][2] = ((float) -lastSourceRect.left * (float)wParam); + + matrix.v[1][1] = zoomLevel; + matrix.v[1][2] = ((float) -lastSourceRect.top * (float)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 *dgxiSurface, + IInspectable **graphicsSurface) +{ + if( pCreateDirect3D11SurfaceFromDXGISurface == nullptr ) + return E_NOINTERFACE; + + return pCreateDirect3D11SurfaceFromDXGISurface( dgxiSurface, 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 WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, + _In_ LPSTR lpCmdLine, _In_ int nCmdShow ) +{ + MSG msg; + HACCEL hAccel; + + if( !ShowEula( APPNAME, NULL, NULL )) return 1; + +#ifndef _WIN64 + // 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 ) { + + // 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 hWndOptions = FindWindow( NULL, L"ZoomIt - Sysinternals: www.sysinternals.com" ); + if( hWndOptions ) { + + SetForegroundWindow( hWndOptions ); + SetWindowPos( 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; + + /* 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); + } + } + return (int) msg.wParam; +} diff --git a/src/modules/ZoomIt/ZoomIt/Zoomit.exe.manifest b/src/modules/ZoomIt/ZoomIt/Zoomit.exe.manifest new file mode 100644 index 0000000000..17abe5f212 --- /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 0000000000..41feb27525 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 0000000000..8e741d7c0c --- /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 0000000000..9585c75bc4 --- /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 0000000000..048f06b4ae 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 0000000000..b5db12d014 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 0000000000..8ba93283ac 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 0000000000..fcde8dfc93 --- /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 0000000000..f694542105 --- /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 0000000000..17305716aa --- /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 0000000000..2f095c5565 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/pch.h @@ -0,0 +1,96 @@ +#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" + +// 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 0000000000..4d4fe19b8e --- /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 0000000000..3d8d7a8b80 --- /dev/null +++ b/src/modules/ZoomIt/ZoomIt/resource.h @@ -0,0 +1,104 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by ZoomIt.rc +// +#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_COLORPICK 1005 +#define IDC_COLOR 1006 +#define IDC_DRAW 1007 +#define IDC_TITLE 1008 +#define IDC_ZOOM 1009 +#define IDC_DRAWING 1010 +#define IDC_BREAK 1011 +#define IDC_HOTKEY 1014 +#define IDC_DRAWHOTKEY 1015 +#define IDC_HOTKEY2 1015 +#define IDC_LIVEHOTKEY 1015 +#define IDC_BREAKHOTKEY 1016 +#define IDC_SOUNDFILE 1017 +#define IDC_BACKROUNDFILE 1018 +#define IDC_SPIN 1022 +#define IDC_SPINTIMER 1023 +#define IDC_SOUNDBROWSE 1025 +#define IDC_OPACITY 1026 +#define IDC_CHECK1 1027 +#define IDC_ADVANCEDBREAK 1027 +#define IDC_CHECKSOUNDFILE 1027 +#define IDC_CHECKBACKGROUNDFILE 1028 +#define IDC_BACKGROUNDBROWSE 1029 +#define IDC_TIMERPOS1 1030 +#define IDC_TIMERPOS2 1031 +#define IDC_TIMERPOS3 1032 +#define IDC_TIMERPOS4 1033 +#define IDC_TIMERPOS5 1034 +#define IDC_TIMERPOS6 1035 +#define IDC_TIMERPOS7 1036 +#define IDC_TIMERPOS8 1037 +#define IDC_TIMERPOS9 1038 +#define IDC_STATIC_SOUNDFILE 1039 +#define IDC_EDIT1 1040 +#define IDC_STATIC_BACKROUNDFILE 1040 +#define IDC_TYPE 1041 +#define IDC_CHECK2 1042 +#define IDC_CHECK_SHOWEXPIRED 1042 +#define IDC_TRYICON 1042 +#define IDC_HIDETRAYICON 1042 +#define IDC_SHOWTRAYICON 1042 +#define IDC_AUTOSTART 1043 +#define IDC_CHECKBACKGROUNDSTRETCH 1046 +#define IDC_STATIC_DESKTOP_BACKGROUND 1047 +#define IDC_STATIC_DESKTOPBACKGROUND 1047 +#define IDC_TAB 1050 +#define IDC_FONT 1051 +#define IDC_ZOOMSPINTIMER 1052 +#define IDC_ZOOMSPIN 1052 +#define IDC_ZOOMLEVEL 1053 +#define IDC_TEXTFONT 1054 +#define IDC_ZOOMSLIDER 1056 +#define IDC_ANIMATE_ZOOM 1057 +#define IDC_COMBO1 1058 +#define IDC_RECORDFRAMERATE 1058 +#define IDC_SPIN1 1059 +#define IDC_RECORDFRAMERATE2 1059 +#define IDC_RECORDSCALING 1059 +#define IDC_SNIPHOTKEY 1060 +#define IDC_CAPTUREAUDIO 1061 +#define IDC_MICROPHONE 1062 +#define IDC_PENCONTROL 1063 +#define IDC_COLORS 1064 +#define IDC_HIGHLIGHTANDBLUR 1065 +#define IDC_SHAPES 1066 +#define IDC_SCREEN 1067 +#define IDC_DEMOTYPETEXT 1068 +#define IDC_DEMOTYPEBROWSE 1069 +#define IDC_DEMOTYPEFILE 1070 +#define IDC_DEMOTYPESPEEDSLIDER 1071 +#define IDC_DEMOTYPEUSERDRIVEN 1072 +#define IDC_DEMOTYPESTATIC1 1073 +#define IDC_DEMOTYPESLIDER2 1074 +#define IDC_DEMOTYPESTATIC2 1074 +#define IDC_PENWIDTH 1105 +#define IDC_TIMER 1106 +#define IDC_SAVE 40002 +#define IDC_COPY 40004 +#define IDC_RECORD 40006 +#define IDC_RECORDHOTKEY 40007 +#define IDC_COPYCROP 40008 +#define IDC_SAVECROP 40009 +#define IDC_DEMOTYPEHOTKEY 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 1075 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif