├── .gitignore ├── CONTRIBUTING.md ├── LICENSE.md ├── Makefile ├── README.md ├── icon └── traymond.ico ├── images ├── logo-sm.png ├── logo.png ├── options-hotkey.png ├── options.png ├── popup-menu.png └── rules.png ├── locale └── en_US │ ├── Traymond.rc │ ├── en_US.vcxproj │ └── en_US.vcxproj.filters ├── src ├── Traymond.rc ├── i18n.cpp ├── i18n.h ├── icons.cpp ├── icons.h ├── logging.h ├── options.cpp ├── options.h ├── resource.h ├── rules.cpp ├── rules.h ├── traymond.cpp ├── traymond.h ├── winevent.cpp └── winevent.h ├── traymond.sln ├── traymond.vcxproj └── traymond.vcxproj.filters /.gitignore: -------------------------------------------------------------------------------- 1 | # Build results from nmake 2 | /*.obj 3 | /*.res 4 | /*.exe 5 | 6 | ## Ignore Visual Studio temporary files, build results, and 7 | ## files generated by popular Visual Studio add-ons. 8 | ## 9 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.userosscache 15 | *.sln.docstates 16 | 17 | # User-specific files (MonoDevelop/Xamarin Studio) 18 | *.userprefs 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Dd]ebugPublic/ 23 | [Rr]elease/ 24 | [Rr]eleases/ 25 | x64/ 26 | x86/ 27 | bld/ 28 | [Bb]in/ 29 | [Oo]bj/ 30 | [Ll]og/ 31 | 32 | # Visual Studio 2015/2017 cache/options directory 33 | .vs/ 34 | # Uncomment if you have tasks that create the project's static files in wwwroot 35 | #wwwroot/ 36 | 37 | # Visual Studio 2017 auto generated files 38 | Generated\ Files/ 39 | 40 | # MSTest test Results 41 | [Tt]est[Rr]esult*/ 42 | [Bb]uild[Ll]og.* 43 | 44 | # NUNIT 45 | *.VisualState.xml 46 | TestResult.xml 47 | 48 | # Build Results of an ATL Project 49 | [Dd]ebugPS/ 50 | [Rr]eleasePS/ 51 | dlldata.c 52 | 53 | # Benchmark Results 54 | BenchmarkDotNet.Artifacts/ 55 | 56 | # .NET Core 57 | project.lock.json 58 | project.fragment.lock.json 59 | artifacts/ 60 | 61 | # StyleCop 62 | StyleCopReport.xml 63 | 64 | # Files built by Visual Studio 65 | *_i.c 66 | *_p.c 67 | *_i.h 68 | *.ilk 69 | *.meta 70 | *.obj 71 | *.iobj 72 | *.pch 73 | *.pdb 74 | *.ipdb 75 | *.pgc 76 | *.pgd 77 | *.rsp 78 | *.sbr 79 | *.tlb 80 | *.tli 81 | *.tlh 82 | *.tmp 83 | *.tmp_proj 84 | *.log 85 | *.vspscc 86 | *.vssscc 87 | .builds 88 | *.pidb 89 | *.svclog 90 | *.scc 91 | 92 | # Chutzpah Test files 93 | _Chutzpah* 94 | 95 | # Visual C++ cache files 96 | ipch/ 97 | *.aps 98 | *.ncb 99 | *.opendb 100 | *.opensdf 101 | *.sdf 102 | *.cachefile 103 | *.VC.db 104 | *.VC.VC.opendb 105 | 106 | # Visual Studio profiler 107 | *.psess 108 | *.vsp 109 | *.vspx 110 | *.sap 111 | 112 | # Visual Studio Trace Files 113 | *.e2e 114 | 115 | # TFS 2012 Local Workspace 116 | $tf/ 117 | 118 | # Guidance Automation Toolkit 119 | *.gpState 120 | 121 | # ReSharper is a .NET coding add-in 122 | _ReSharper*/ 123 | *.[Rr]e[Ss]harper 124 | *.DotSettings.user 125 | 126 | # JustCode is a .NET coding add-in 127 | .JustCode 128 | 129 | # TeamCity is a build add-in 130 | _TeamCity* 131 | 132 | # DotCover is a Code Coverage Tool 133 | *.dotCover 134 | 135 | # AxoCover is a Code Coverage Tool 136 | .axoCover/* 137 | !.axoCover/settings.json 138 | 139 | # Visual Studio code coverage results 140 | *.coverage 141 | *.coveragexml 142 | 143 | # NCrunch 144 | _NCrunch_* 145 | .*crunch*.local.xml 146 | nCrunchTemp_* 147 | 148 | # MightyMoose 149 | *.mm.* 150 | AutoTest.Net/ 151 | 152 | # Web workbench (sass) 153 | .sass-cache/ 154 | 155 | # Installshield output folder 156 | [Ee]xpress/ 157 | 158 | # DocProject is a documentation generator add-in 159 | DocProject/buildhelp/ 160 | DocProject/Help/*.HxT 161 | DocProject/Help/*.HxC 162 | DocProject/Help/*.hhc 163 | DocProject/Help/*.hhk 164 | DocProject/Help/*.hhp 165 | DocProject/Help/Html2 166 | DocProject/Help/html 167 | 168 | # Click-Once directory 169 | publish/ 170 | 171 | # Publish Web Output 172 | *.[Pp]ublish.xml 173 | *.azurePubxml 174 | # Note: Comment the next line if you want to checkin your web deploy settings, 175 | # but database connection strings (with potential passwords) will be unencrypted 176 | *.pubxml 177 | *.publishproj 178 | 179 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 180 | # checkin your Azure Web App publish settings, but sensitive information contained 181 | # in these scripts will be unencrypted 182 | PublishScripts/ 183 | 184 | # NuGet Packages 185 | *.nupkg 186 | # The packages folder can be ignored because of Package Restore 187 | **/[Pp]ackages/* 188 | # except build/, which is used as an MSBuild target. 189 | !**/[Pp]ackages/build/ 190 | # Uncomment if necessary however generally it will be regenerated when needed 191 | #!**/[Pp]ackages/repositories.config 192 | # NuGet v3's project.json files produces more ignorable files 193 | *.nuget.props 194 | *.nuget.targets 195 | 196 | # Microsoft Azure Build Output 197 | csx/ 198 | *.build.csdef 199 | 200 | # Microsoft Azure Emulator 201 | ecf/ 202 | rcf/ 203 | 204 | # Windows Store app package directories and files 205 | AppPackages/ 206 | BundleArtifacts/ 207 | Package.StoreAssociation.xml 208 | _pkginfo.txt 209 | *.appx 210 | 211 | # Visual Studio cache files 212 | # files ending in .cache can be ignored 213 | *.[Cc]ache 214 | # but keep track of directories ending in .cache 215 | !*.[Cc]ache/ 216 | 217 | # Others 218 | ClientBin/ 219 | ~$* 220 | *~ 221 | *.dbmdl 222 | *.dbproj.schemaview 223 | *.jfm 224 | *.pfx 225 | *.publishsettings 226 | orleans.codegen.cs 227 | 228 | # Including strong name files can present a security risk 229 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 230 | #*.snk 231 | 232 | # Since there are multiple workflows, uncomment next line to ignore bower_components 233 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 234 | #bower_components/ 235 | 236 | # RIA/Silverlight projects 237 | Generated_Code/ 238 | 239 | # Backup & report files from converting an old project file 240 | # to a newer Visual Studio version. Backup files are not needed, 241 | # because we have git ;-) 242 | _UpgradeReport_Files/ 243 | Backup*/ 244 | UpgradeLog*.XML 245 | UpgradeLog*.htm 246 | ServiceFabricBackup/ 247 | *.rptproj.bak 248 | 249 | # SQL Server files 250 | *.mdf 251 | *.ldf 252 | *.ndf 253 | 254 | # Business Intelligence projects 255 | *.rdl.data 256 | *.bim.layout 257 | *.bim_*.settings 258 | *.rptproj.rsuser 259 | 260 | # Microsoft Fakes 261 | FakesAssemblies/ 262 | 263 | # GhostDoc plugin setting file 264 | *.GhostDoc.xml 265 | 266 | # Node.js Tools for Visual Studio 267 | .ntvs_analysis.dat 268 | node_modules/ 269 | 270 | # Visual Studio 6 build log 271 | *.plg 272 | 273 | # Visual Studio 6 workspace options file 274 | *.opt 275 | 276 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 277 | *.vbw 278 | 279 | # Visual Studio LightSwitch build output 280 | **/*.HTMLClient/GeneratedArtifacts 281 | **/*.DesktopClient/GeneratedArtifacts 282 | **/*.DesktopClient/ModelManifest.xml 283 | **/*.Server/GeneratedArtifacts 284 | **/*.Server/ModelManifest.xml 285 | _Pvt_Extensions 286 | 287 | # Paket dependency manager 288 | .paket/paket.exe 289 | paket-files/ 290 | 291 | # FAKE - F# Make 292 | .fake/ 293 | 294 | # JetBrains Rider 295 | .idea/ 296 | *.sln.iml 297 | 298 | # CodeRush 299 | .cr/ 300 | 301 | # Python Tools for Visual Studio (PTVS) 302 | __pycache__/ 303 | *.pyc 304 | 305 | # Cake - Uncomment if you are using it 306 | # tools/** 307 | # !tools/packages.config 308 | 309 | # Tabs Studio 310 | *.tss 311 | 312 | # Telerik's JustMock configuration file 313 | *.jmconfig 314 | 315 | # BizTalk build output 316 | *.btp.cs 317 | *.btm.cs 318 | *.odx.cs 319 | *.xsd.cs 320 | 321 | # OpenCover UI analysis results 322 | OpenCover/ 323 | 324 | # Azure Stream Analytics local run output 325 | ASALocalRun/ 326 | 327 | # MSBuild Binary and Structured Log 328 | *.binlog 329 | 330 | # NVidia Nsight GPU debugger configuration file 331 | *.nvuser 332 | 333 | # MFractors (Xamarin productivity tool) working folder 334 | .mfractor/ 335 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Fork and make a pull request. Explain why you want it to be merged. 4 | 5 | I appreciate every bit of help since I don't have much time for this project anymore. 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 fcFn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CPPFLAGS=/nologo /EHsc /O2 /GL /GS /Oi /MD /D "_UNICODE" /D "UNICODE" /Zc:inline 2 | RFLAGS=/nologo /n /r 3 | 4 | {src\}.cpp.obj: 5 | $(CPP) $(CPPFLAGS) /c $< 6 | {src\}.rc.res: 7 | $(RC) $(RFLAGS) /fo$(@F) $< 8 | 9 | Traymond.exe: options.obj rules.obj winevent.obj icons.obj traymond.obj Traymond.res 10 | $(CPP) $(CPPFLAGS) /Fe$(@F) $** user32.lib shell32.lib comctl32.lib advapi32.lib gdi32.lib /link /MACHINE:X86 /MANIFESTDEPENDENCY:"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'" 11 | 12 | clean: 13 | del *.obj *.res Traymond.exe.manifest Traymond.exe 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Traymond](images/logo.png) Traymond(增强版) 2 | ======= 3 | 4 | Traymond(增强版)是一款能将任意窗口最小化到系统托盘的 Windows 桌面工具。此项目受 [Traymond](https://github.com/fcFn/traymond) 启发,实现以下功能: 5 | 6 | - 中文界面 7 | - 开机自动运行 8 | - 可自定义热键 9 | - 最小化窗口到系统托盘图标或右键菜单 10 | - 还原最后一个最小化窗口 11 | - 唤出最小化窗口列表 12 | - 通过自定义规则自动最小化窗口 13 | 14 | ## 安装 15 | 16 | 1. 从 https://github.com/tabris17/traymond/releases/latest 下载可执行文件直接运行; 17 | 18 | 2. 使用 Scoop 19 | 20 | ```cmd 21 | scoop install https://github.com/tabris17/traymond/releases/latest/download/traymond.json 22 | ``` 23 | 24 | ## 用法 25 | 26 | 程序运行后会常驻系统托盘。按下默认热键 Win + Shift + Z 最小化当前窗口到系统托盘。可以在“选项”中设置最小化窗口收纳至托盘图标或者右键菜单。 27 | 28 | ![popup menu](images/popup-menu.png) 29 | 30 | 鼠标双击托盘图标 ![icon](images/logo-sm.png) 或者在右键菜单中选择“选项”,打开“Traymond 选项” 对话框。 31 | 32 | ![Traymond 选项](images/options.png) 33 | 34 | ### 自定义热键 35 | 36 | 在“自定义热键”列表内选中需要设置热键的行为,在下方的热键控件中按下自定义热键组合。由于热键控件无法响应按下 Win 键,如果要使用 Win 作为修饰键,请勾选“使用 Win 键” 选择框。 37 | 38 | ![设置热键](images/options-hotkey.png) 39 | 40 | ### 自动最小化窗口 41 | 42 | 在“Traymond 选项”对话框中勾选“运行期间自动最小化指定窗口”选择框,以启用自动最小化窗口功能。点击“自定义规则”按钮设置规则。 43 | 44 | ![自定义隐藏窗口规则](images/rules.png) 45 | 46 | 具体设置方法请参考项目[维基](https://github.com/tabris17/traymond/wiki)。 -------------------------------------------------------------------------------- /icon/traymond.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabris17/traymond/9623c4eca602a05d6d593e52afaac9d44b9ef85e/icon/traymond.ico -------------------------------------------------------------------------------- /images/logo-sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabris17/traymond/9623c4eca602a05d6d593e52afaac9d44b9ef85e/images/logo-sm.png -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabris17/traymond/9623c4eca602a05d6d593e52afaac9d44b9ef85e/images/logo.png -------------------------------------------------------------------------------- /images/options-hotkey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabris17/traymond/9623c4eca602a05d6d593e52afaac9d44b9ef85e/images/options-hotkey.png -------------------------------------------------------------------------------- /images/options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabris17/traymond/9623c4eca602a05d6d593e52afaac9d44b9ef85e/images/options.png -------------------------------------------------------------------------------- /images/popup-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabris17/traymond/9623c4eca602a05d6d593e52afaac9d44b9ef85e/images/popup-menu.png -------------------------------------------------------------------------------- /images/rules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabris17/traymond/9623c4eca602a05d6d593e52afaac9d44b9ef85e/images/rules.png -------------------------------------------------------------------------------- /locale/en_US/Traymond.rc: -------------------------------------------------------------------------------- 1 | #pragma code_page(65001) 2 | 3 | #include "resource.h" 4 | 5 | 6 | ///////////////////////////////////////////////////////////////////////////// 7 | // 8 | // Menu 9 | // 10 | 11 | IDM_POPUP MENU 12 | BEGIN 13 | POPUP "" 14 | BEGIN 15 | MENUITEM "Restore all windows", IDM_RESTORE_ALL_WINDOWS 16 | MENUITEM "Restore the last window", IDM_RESTORE_LAST_WINDOW 17 | MENUITEM "Options", IDM_OPTIONS 18 | MENUITEM SEPARATOR 19 | MENUITEM "Exit", IDM_EXIT 20 | END 21 | END 22 | 23 | 24 | ///////////////////////////////////////////////////////////////////////////// 25 | // 26 | // Dialog 27 | // 28 | 29 | IDD_OPTIONS DIALOGEX 0, 0, 200, 204 30 | STYLE DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU 31 | EXSTYLE WS_EX_STATICEDGE 32 | CAPTION "Traymond Options" 33 | FONT 9, "MS Shell Dlg", 400, 0, 0x1 34 | BEGIN 35 | DEFPUSHBUTTON "OK",IDOK,49,184,50,14 36 | PUSHBUTTON "Cancel",IDCANCEL,103,184,50,14 37 | GROUPBOX "Custom hotkeys",IDC_STATIC,6,6,188,105 38 | CONTROL "",IDC_HOTKEY_LIST,"SysListView32",LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_REPORT | WS_BORDER | WS_TABSTOP,12,18,176,66 39 | CONTROL "",IDC_HOTKEY,"msctls_hotkey32",WS_BORDER | WS_TABSTOP | WS_DISABLED,12,90,84,14 40 | CONTROL "Use Win key",IDC_CHECK_USE_WIN,"Button",BS_AUTOCHECKBOX | WS_TABSTOP | WS_DISABLED,102,92,58,11 41 | LTEXT "Minimize the window to ",IDC_STATIC,6,124,85,10 42 | COMBOBOX IDC_COMBO_HIDE_TYPE,90,122,66,120,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_HASSTRINGS | WS_TABSTOP 43 | CONTROL "Autorun at startup",IDC_CHECK_AUTORUN,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,143,72,11 44 | CONTROL "Auto-minimize the specified window",IDC_CHECK_AUTO_HIDING,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,159,125,11 45 | PUSHBUTTON "Custom rules",IDC_BUTTON_RULES,134,157,60,14,WS_DISABLED 46 | END 47 | 48 | IDD_RULES DIALOGEX 0, 0, 400, 306 49 | STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | DS_SETFOREGROUND | DS_SHELLFONT | WS_POPUP | WS_CAPTION | WS_SYSMENU 50 | CAPTION "Traymond Custom Rules" 51 | FONT 9, "MS Shell Dlg", 400, 0, 0x1 52 | BEGIN 53 | DEFPUSHBUTTON "Close", IDCANCEL, 345, 286, 50, 14, BS_FLAT 54 | PUSHBUTTON "Cancel", IDABORT, 290, 286, 50, 14, BS_FLAT | WS_DISABLED 55 | PUSHBUTTON "Help", IDHELP, 236, 286, 50, 14, BS_FLAT 56 | LISTBOX IDC_LIST_RULES, 5, 5, 135, 276, LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_HSCROLL | WS_TABSTOP 57 | PUSHBUTTON "New", IDC_NEW, 5, 286, 42, 14, BS_FLAT 58 | PUSHBUTTON "Save", IDC_SAVE, 51, 286, 42, 14, BS_FLAT | WS_DISABLED 59 | PUSHBUTTON "Remove", IDC_REMOVE, 97, 286, 42, 14, BS_FLAT | WS_DISABLED 60 | LTEXT "Rule name:", IDC_STATIC, 145, 8, 264, 10 61 | EDITTEXT IDC_EDIT_NAME, 145, 20, 250, 14, ES_AUTOHSCROLL | WS_DISABLED 62 | LTEXT "Create the rule from a minimized window:", IDC_STATIC, 145, 40, 280, 10 63 | COMBOBOX IDC_COMBO_WINDOWS, 145, 52, 250, 200, CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_HASSTRINGS | WS_TABSTOP | WS_DISABLED 64 | GROUPBOX "Window title", IDC_STATIC, 145, 70, 250, 45 65 | EDITTEXT IDC_EDIT_TEXT, 153, 84, 235, 14, ES_AUTOHSCROLL | WS_DISABLED 66 | CONTROL "Regex", IDC_CHECK_REGEX_TEXT, "Button", BS_AUTOCHECKBOX | WS_TABSTOP | WS_DISABLED, 153, 99, 71, 12 67 | GROUPBOX "Window class", IDC_STATIC, 145, 119, 250, 45 68 | EDITTEXT IDC_EDIT_CLASS, 153, 133, 235, 14, ES_AUTOHSCROLL | WS_DISABLED 69 | CONTROL "Regex", IDC_CHECK_REGEX_CLASS, "Button", BS_AUTOCHECKBOX | WS_TABSTOP | WS_DISABLED, 153, 148, 71, 12 70 | GROUPBOX "Image path", IDC_STATIC, 145, 168, 250, 45 71 | EDITTEXT IDC_EDIT_PATH, 153, 182, 235, 14, ES_AUTOHSCROLL | WS_DISABLED 72 | CONTROL "Regex", IDC_CHECK_REGEX_PATH, "Button", BS_AUTOCHECKBOX | WS_TABSTOP | WS_DISABLED, 153, 197, 71, 12 73 | GROUPBOX "Rule in effect", IDC_STATIC, 145, 217, 250, 30 74 | CONTROL "Window first shown", IDC_RADIO_ON_FIRST_SHOW, "Button", BS_AUTORADIOBUTTON | WS_GROUP | WS_DISABLED, 153, 229, 80, 13 75 | CONTROL "Window minimized", IDC_RADIO_ON_MINIMIZE, "Button", BS_AUTORADIOBUTTON | WS_DISABLED, 235, 229, 75, 13 76 | CONTROL "Both", IDC_RADIO_ON_BOTH, "Button", BS_AUTORADIOBUTTON | WS_DISABLED, 315, 229, 33, 13 77 | GROUPBOX "Window minimize notification", IDC_STATIC, 145, 251, 250, 30 78 | CONTROL "Never", IDC_RADIO_NEVER_NOTIFY, "Button", BS_AUTORADIOBUTTON | WS_GROUP | WS_DISABLED, 153, 263, 28, 13 79 | CONTROL "Always", IDC_RADIO_ALWAYS_NOTIFY, "Button", BS_AUTORADIOBUTTON | WS_DISABLED, 195, 263, 35, 13 80 | CONTROL "Only first time", IDC_RADIO_NOTIFY_FIRST_TIME, "Button", BS_AUTORADIOBUTTON | WS_DISABLED, 240, 263, 100, 13 81 | END 82 | 83 | IDD_ICONS DIALOGEX 0, 0, 120, 100 84 | STYLE DS_SETFONT | DS_CENTERMOUSE | DS_SETFOREGROUND | DS_SHELLFONT | WS_POPUP 85 | FONT 12, "MS Shell Dlg", 400, 0, 0x1 86 | CLASS POPUP_CLASS 87 | BEGIN 88 | CONTROL "",IDC_ICON_LIST,"SysListView32",LVS_SINGLESEL | LVS_ALIGNLEFT | LVS_REPORT | LVS_NOCOLUMNHEADER | WS_BORDER | WS_TABSTOP,0,0,120,100 89 | END 90 | 91 | 92 | ///////////////////////////////////////////////////////////////////////////// 93 | // 94 | // String Table 95 | // 96 | 97 | STRINGTABLE 98 | BEGIN 99 | IDS_HOTKEY_ERROR "Unable to register the global hotkey %s.\nPlease select another one." 100 | IDS_MUTEX_ERROR "Fatal error: failed to create the mutex object." 101 | IDS_ALREADY_RUNNING "The program is already running." 102 | IDS_SAVE_FILE_ERROR "Fatal error: failed to create the save file." 103 | IDS_TOO_MANY_HIDDEN_WINDOWS "Too many minimized windows. Please restore some." 104 | IDS_RESTORE_FROM_UNEXPECTED_TERMINATION "Program had previously been terminated unexpectedly. Minimized %d windows." 105 | IDS_HIDING_WINDOW "Minimize window to tray" 106 | IDS_TRAY "Tray icon" 107 | IDS_MENU "Popup menu" 108 | IDS_COL_KEY "Key" 109 | IDS_COL_ACTION "Action" 110 | IDS_ACT_1 "Minimize the foreground window" 111 | IDS_ACT_2 "Popup minimized windows list" 112 | IDS_ACT_3 "Restore the last window" 113 | IDS_NEW_RULE "New Rule" 114 | IDS_UNSAVED "The rule has not been saved. Do you want to save it now?" 115 | IDS_RULE_INFO_REQUIRED "The form must be filled in completely" 116 | IDS_INVALID_REGEX """%s"" is not a valid regular expression" 117 | END 118 | -------------------------------------------------------------------------------- /locale/en_US/en_US.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 17.0 23 | Win32Proj 24 | {ee43692d-0e29-4699-9007-7f1d41bb38d5} 25 | 26 | 27 | 10.0 28 | traymond.locale.en_US 29 | 30 | 31 | 32 | DynamicLibrary 33 | 34 | 35 | v143 36 | Unicode 37 | 38 | 39 | 40 | 41 | DynamicLibrary 42 | 43 | 44 | v143 45 | 46 | 47 | Unicode 48 | 49 | 50 | 51 | DynamicLibrary 52 | true 53 | v143 54 | Unicode 55 | 56 | 57 | DynamicLibrary 58 | false 59 | v143 60 | true 61 | Unicode 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | en_US 86 | $(SolutionDir)$(Configuration)\locale\ 87 | 88 | 89 | 90 | 91 | 92 | 93 | en_US 94 | $(SolutionDir)$(Configuration)\locale\ 95 | 96 | 97 | 98 | 99 | Level3 100 | true 101 | WIN32;_DEBUG;ENUS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 102 | true 103 | 104 | 105 | Windows 106 | 107 | 108 | 109 | 110 | true 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | $(OutDir)$(TargetName) 120 | 121 | 122 | 123 | 124 | 125 | 126 | true 127 | 128 | ..\..\src 129 | 130 | 131 | 132 | 133 | Level3 134 | true 135 | true 136 | true 137 | WIN32;NDEBUG;ENUS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 138 | true 139 | 140 | 141 | Windows 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | true 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | $(OutDir)$(TargetName) 162 | 163 | 164 | 165 | 166 | 167 | true 168 | 169 | ..\..\src 170 | 171 | 172 | 173 | 174 | Level3 175 | true 176 | _DEBUG;ENUS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 177 | true 178 | 179 | 180 | Windows 181 | true 182 | false 183 | 184 | 185 | 186 | 187 | Level3 188 | true 189 | true 190 | true 191 | NDEBUG;ENUS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 192 | true 193 | 194 | 195 | Windows 196 | true 197 | true 198 | true 199 | false 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | -------------------------------------------------------------------------------- /locale/en_US/en_US.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Resource Files 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Traymond.rc: -------------------------------------------------------------------------------- 1 | #pragma code_page(65001) 2 | 3 | #include "resource.h" 4 | 5 | 6 | ///////////////////////////////////////////////////////////////////////////// 7 | // 8 | // Icon 9 | // 10 | 11 | IDI_TRAYMOND ICON "..\\icon\\traymond.ico" 12 | 13 | 14 | ///////////////////////////////////////////////////////////////////////////// 15 | // 16 | // Version 17 | // 18 | 19 | VS_VERSION_INFO VERSIONINFO 20 | FILEVERSION VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, VERSION_BUILD 21 | PRODUCTVERSION VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH 22 | FILEFLAGSMASK 0x3fL 23 | #ifdef _DEBUG 24 | FILEFLAGS 0x1L 25 | #else 26 | FILEFLAGS 0x0L 27 | #endif 28 | FILEOS VOS_NT_WINDOWS32 29 | FILETYPE VFT_APP 30 | FILESUBTYPE 0x0L 31 | BEGIN 32 | BLOCK "StringFileInfo" 33 | BEGIN 34 | BLOCK "000004b0" 35 | BEGIN 36 | VALUE "FileDescription", "Traymond" 37 | VALUE "FileVersion", MAKEFULLVERSION 38 | VALUE "InternalName", PROJECT_NAME ".exe" 39 | VALUE "OriginalFilename", PROJECT_NAME ".exe" 40 | VALUE "ProductName", PROJECT_NAME 41 | VALUE "ProductVersion", MAKEVERSION 42 | VALUE "LegalCopyright", "https://github.com/tabris17/traymond" 43 | END 44 | END 45 | BLOCK "VarFileInfo" 46 | BEGIN 47 | VALUE "Translation", LANG_NEUTRAL, 1200 48 | END 49 | END 50 | 51 | 52 | ///////////////////////////////////////////////////////////////////////////// 53 | // 54 | // Menu 55 | // 56 | 57 | IDM_POPUP MENU 58 | BEGIN 59 | POPUP "" 60 | BEGIN 61 | MENUITEM "还原所有窗口", IDM_RESTORE_ALL_WINDOWS 62 | MENUITEM "还原最后一个窗口", IDM_RESTORE_LAST_WINDOW 63 | MENUITEM "选项", IDM_OPTIONS 64 | MENUITEM SEPARATOR 65 | MENUITEM "退出", IDM_EXIT 66 | END 67 | END 68 | 69 | 70 | ///////////////////////////////////////////////////////////////////////////// 71 | // 72 | // Dialog 73 | // 74 | 75 | IDD_OPTIONS DIALOGEX 0, 0, 180, 204 76 | STYLE DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU 77 | EXSTYLE WS_EX_STATICEDGE 78 | CAPTION "Traymond 选项" 79 | FONT 9, "MS Shell Dlg", 400, 0, 0x1 80 | BEGIN 81 | DEFPUSHBUTTON "确定",IDOK,39,184,50,14 82 | PUSHBUTTON "取消",IDCANCEL,93,184,50,14 83 | GROUPBOX "自定义热键",IDC_STATIC,6,6,168,105 84 | CONTROL "",IDC_HOTKEY_LIST,"SysListView32",LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_REPORT | WS_BORDER | WS_TABSTOP,12,18,156,66 85 | CONTROL "",IDC_HOTKEY,"msctls_hotkey32",WS_BORDER | WS_TABSTOP | WS_DISABLED,12,90,84,14 86 | CONTROL "使用 Win 键",IDC_CHECK_USE_WIN,"Button",BS_AUTOCHECKBOX | WS_TABSTOP | WS_DISABLED,102,92,58,11 87 | LTEXT "最小化窗口收纳到 ",IDC_STATIC,6,124,60,10 88 | COMBOBOX IDC_COMBO_HIDE_TYPE,70,122,66,120,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_HASSTRINGS | WS_TABSTOP 89 | CONTROL "开机时自动运行",IDC_CHECK_AUTORUN,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,143,72,11 90 | CONTROL "自动将窗口最小化到托盘",IDC_CHECK_AUTO_HIDING,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,159,100,11 91 | PUSHBUTTON "自定义规则",IDC_BUTTON_RULES,110,157,60,14,WS_DISABLED 92 | END 93 | 94 | IDD_RULES DIALOGEX 0, 0, 400, 306 95 | STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | DS_SETFOREGROUND | DS_SHELLFONT | WS_POPUP | WS_CAPTION | WS_SYSMENU 96 | CAPTION "Traymond 自定义规则" 97 | FONT 9, "MS Shell Dlg", 400, 0, 0x1 98 | BEGIN 99 | DEFPUSHBUTTON "关闭",IDCANCEL,345,286,50,14,BS_FLAT 100 | PUSHBUTTON "取消",IDABORT,290,286,50,14,BS_FLAT | WS_DISABLED 101 | PUSHBUTTON "帮助",IDHELP,236,286,50,14,BS_FLAT 102 | LISTBOX IDC_LIST_RULES,5,5,135,276,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_HSCROLL | WS_TABSTOP 103 | PUSHBUTTON "新建",IDC_NEW,5,286,42,14,BS_FLAT 104 | PUSHBUTTON "保存",IDC_SAVE,51,286,42,14,BS_FLAT | WS_DISABLED 105 | PUSHBUTTON "删除",IDC_REMOVE,97,286,42,14,BS_FLAT | WS_DISABLED 106 | LTEXT "规则名称:",IDC_STATIC,145,8,264,10 107 | EDITTEXT IDC_EDIT_NAME,145,20,250,14,ES_AUTOHSCROLL | WS_DISABLED 108 | LTEXT "从已最小化的窗口创建规则:",IDC_STATIC,145,40,264,10 109 | COMBOBOX IDC_COMBO_WINDOWS,145,52,250,200,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_HASSTRINGS | WS_TABSTOP | WS_DISABLED 110 | GROUPBOX "窗口标题",IDC_STATIC,145,70,250,45 111 | EDITTEXT IDC_EDIT_TEXT,153,84,235,14,ES_AUTOHSCROLL | WS_DISABLED 112 | CONTROL "正则匹配",IDC_CHECK_REGEX_TEXT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP | WS_DISABLED,153,99,71,12 113 | GROUPBOX "窗口类名",IDC_STATIC,145,119,250,45 114 | EDITTEXT IDC_EDIT_CLASS,153,133,235,14,ES_AUTOHSCROLL | WS_DISABLED 115 | CONTROL "正则匹配",IDC_CHECK_REGEX_CLASS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP | WS_DISABLED,153,148,71,12 116 | GROUPBOX "程序路径",IDC_STATIC,145,168,250,45 117 | EDITTEXT IDC_EDIT_PATH,153,182,235,14,ES_AUTOHSCROLL | WS_DISABLED 118 | CONTROL "正则匹配",IDC_CHECK_REGEX_PATH,"Button",BS_AUTOCHECKBOX | WS_TABSTOP | WS_DISABLED,153,197,71,12 119 | GROUPBOX "生效场景",IDC_STATIC,145,217,250,30 120 | CONTROL "窗口首次出现时",IDC_RADIO_ON_FIRST_SHOW,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_DISABLED,153,229,65,13 121 | CONTROL "窗口最小化时",IDC_RADIO_ON_MINIMIZE,"Button",BS_AUTORADIOBUTTON | WS_DISABLED,218,229,60,13 122 | CONTROL "两者",IDC_RADIO_ON_BOTH,"Button",BS_AUTORADIOBUTTON | WS_DISABLED,278,229,33,13 123 | GROUPBOX "显示通知",IDC_STATIC,145,251,250,30 124 | CONTROL "从不",IDC_RADIO_NEVER_NOTIFY,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_DISABLED,153,263,28,13 125 | CONTROL "总是",IDC_RADIO_ALWAYS_NOTIFY,"Button",BS_AUTORADIOBUTTON | WS_DISABLED,183,263,28,13 126 | CONTROL "窗口首次最小化时",IDC_RADIO_NOTIFY_FIRST_TIME,"Button",BS_AUTORADIOBUTTON | WS_DISABLED,213,263,100,13 127 | END 128 | 129 | IDD_ICONS DIALOGEX 0, 0, 120, 100 130 | STYLE DS_SETFONT | DS_CENTERMOUSE | DS_SETFOREGROUND | DS_SHELLFONT | WS_POPUP 131 | FONT 12, "MS Shell Dlg", 400, 0, 0x1 132 | CLASS POPUP_CLASS 133 | BEGIN 134 | CONTROL "",IDC_ICON_LIST,"SysListView32",LVS_SINGLESEL | LVS_ALIGNLEFT | LVS_REPORT | LVS_NOCOLUMNHEADER | WS_BORDER | WS_TABSTOP,0,0,120,100 135 | END 136 | 137 | 138 | ///////////////////////////////////////////////////////////////////////////// 139 | // 140 | // String Table 141 | // 142 | 143 | STRINGTABLE 144 | BEGIN 145 | IDS_HOTKEY_ERROR "无法注册系统热键 %s,可能已被占用。\n请选择其他组合键。" 146 | IDS_MUTEX_ERROR "创建互斥对象失败,无法启动程序。" 147 | IDS_ALREADY_RUNNING "程序已在运行中。" 148 | IDS_SAVE_FILE_ERROR "无法创建保存文件。" 149 | IDS_TOO_MANY_HIDDEN_WINDOWS "隐藏太多窗口,请先释放一些。" 150 | IDS_RESTORE_FROM_UNEXPECTED_TERMINATION "程序先前意外终止。已恢复 %d 个隐藏窗口。" 151 | IDS_HIDING_WINDOW "最小化窗口至托盘" 152 | IDS_TRAY "托盘图标" 153 | IDS_MENU "右键菜单" 154 | IDS_COL_KEY "按键" 155 | IDS_COL_ACTION "行为" 156 | IDS_ACT_1 "最小化前台窗口" 157 | IDS_ACT_2 "弹出最小化窗口列表" 158 | IDS_ACT_3 "还原最后一个窗口" 159 | IDS_NEW_RULE "新规则" 160 | IDS_UNSAVED "当前编辑的规则未保存。是否要保存?" 161 | IDS_RULE_INFO_REQUIRED "必须输入完整的规则信息" 162 | IDS_INVALID_REGEX """%s"" 不是正经的正则表达式" 163 | END 164 | -------------------------------------------------------------------------------- /src/i18n.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "i18n.h" 5 | 6 | 7 | bool I18n::loadLangMod() 8 | { 9 | // override default language 10 | /* if (_tcscmp(DEFAULT_LOCALE, locale) == 0) { 11 | return false; 12 | }*/ 13 | TCHAR fileName[MAX_PATH]; 14 | auto fileNameLength = GetModuleFileName(NULL, fileName, MAX_PATH); 15 | if (fileNameLength == 0 || fileNameLength >= MAX_PATH) { 16 | return false; 17 | } 18 | for (int i = fileNameLength - 1; i >= 0; i--) { 19 | if (fileName[i] == _T("\\")[0]) { 20 | constexpr TCHAR dotTail[] = _T("."); 21 | fileName[i + 1] = _T("")[0]; 22 | if ( 23 | _tcsncat_s(fileName, MAX_PATH, LOCALE_DIR, _countof(LOCALE_DIR)) != 0 || 24 | _tcsncat_s(fileName, MAX_PATH, locale, MAX_LOCALE) != 0 || 25 | _tcsncat_s(fileName, MAX_PATH, dotTail, _countof(dotTail)) 26 | ) { 27 | return false; 28 | } 29 | langMod = LoadLangMod(fileName); 30 | if (langMod) { 31 | return true; 32 | } 33 | fileName[i + 1] = _T("")[0]; 34 | if ( 35 | _tcsncat_s(fileName, MAX_PATH, LOCALE_DIR, _countof(LOCALE_DIR)) != 0 || 36 | _tcsncat_s(fileName, MAX_PATH, fallback, MAX_LOCALE) != 0 || 37 | _tcsncat_s(fileName, MAX_PATH, dotTail, _countof(dotTail)) 38 | ) { 39 | return false; 40 | } 41 | langMod = LoadLangMod(fileName); 42 | if (langMod) { 43 | return true; 44 | } 45 | break; 46 | } 47 | } 48 | return false; 49 | } 50 | 51 | bool I18n::getLocale() 52 | { 53 | constexpr auto MAX_CTRY = 4; 54 | TCHAR country[MAX_CTRY]{ NULL }; 55 | if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME, fallback, MAX_LOCALE) == 0) { 56 | return false; 57 | } 58 | if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, country, MAX_CTRY) == 0) { 59 | return false; 60 | } 61 | _stprintf_s(locale, _T("%s_%s"), fallback, country); 62 | return true; 63 | } 64 | 65 | I18n::I18n() 66 | { 67 | if (getLocale()) { 68 | loadLangMod(); 69 | } 70 | 71 | for (int i = 0; i < _countof(stringTable); i++) { 72 | LoadString(langMod, IDS_BEGIN + i, reinterpret_cast(stringTable + i), 0); 73 | } 74 | } 75 | 76 | LPTSTR I18n::operator[](int stringId) 77 | { 78 | if (stringId < IDS_BEGIN || stringId >= IDS_END) { 79 | return nullptr; 80 | } 81 | return stringTable[stringId - IDS_BEGIN]; 82 | } 83 | 84 | HINSTANCE I18n::lang(HINSTANCE defaultInstance = NULL) 85 | { 86 | if (langMod == NULL) { 87 | return defaultInstance; 88 | } 89 | return langMod; 90 | } 91 | -------------------------------------------------------------------------------- /src/i18n.h: -------------------------------------------------------------------------------- 1 | #include "resource.h" 2 | 3 | #ifndef I18N_H 4 | #define I18N_H 5 | #define MAX_LOCALE 10 6 | #define DEFAULT_LOCALE _T("zh_CN") 7 | #define LOCALE_DIR _T("locale\\") 8 | #define LoadLangMod(lmfn) LoadLibraryEx(lmfn, NULL, LOAD_LIBRARY_AS_IMAGE_RESOURCE | LOAD_LIBRARY_AS_DATAFILE) 9 | 10 | class I18n { 11 | private: 12 | TCHAR locale[MAX_LOCALE]{}; 13 | TCHAR fallback[MAX_LOCALE]{}; 14 | LPTSTR stringTable[IDS_MAX_SIZE]{}; 15 | HINSTANCE langMod = NULL; 16 | bool loadLangMod(); 17 | bool getLocale(); 18 | public: 19 | I18n(); 20 | LPTSTR operator[](int stringId); 21 | HINSTANCE lang(HINSTANCE defaultInstance); 22 | }; 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/icons.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "resource.h" 7 | #include "icons.h" 8 | #include "logging.h" 9 | #include "traymond.h" 10 | 11 | 12 | static BOOL initDialog(HWND hwnd, IconsDialog* state) 13 | { 14 | LVITEM lvi{}; 15 | LVCOLUMN lvc{}; 16 | 17 | auto context = state->getContext(); 18 | auto listView = GetDlgItem(hwnd, IDC_ICON_LIST); 19 | auto imageList = state->getImageList(); 20 | 21 | SetWindowLongPtr(hwnd, GWL_EXSTYLE, WS_EX_TOOLWINDOW | WS_EX_TOPMOST); 22 | SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast(state)); 23 | ListView_SetExtendedListViewStyle(listView, LVS_EX_BORDERSELECT | LVS_EX_ONECLICKACTIVATE | LVS_EX_AUTOSIZECOLUMNS | LVS_EX_FULLROWSELECT | LVS_EX_TRACKSELECT); 24 | ListView_SetHoverTime(listView, 0); 25 | ListView_SetImageList(listView, imageList, LVSIL_NORMAL); 26 | ListView_SetImageList(listView, imageList, LVSIL_SMALL); 27 | 28 | RECT rect{}; 29 | GetClientRect(listView, &rect); 30 | lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_SUBITEM; 31 | lvc.fmt = LVCFMT_LEFT; 32 | lvc.cx = rect.right; 33 | lvc.iSubItem = 0; 34 | ListView_InsertColumn(listView, 0, &lvc); 35 | 36 | lvi.mask = LVIF_TEXT | LVIF_IMAGE; 37 | for (int i = 0; i < context->iconIndex; i++) { 38 | TCHAR text[MAX_WINDOW_TEXT]{}; 39 | HWND hiddenWindow = context->icons[i].window; 40 | GetWindowText(hiddenWindow, text, MAX_WINDOW_TEXT); 41 | ImageList_AddIcon(imageList, getWindowIcon(context, hiddenWindow)); 42 | lvi.iItem = i; 43 | lvi.pszText = text; 44 | lvi.iImage = i; 45 | ListView_InsertItem(listView, &lvi); 46 | } 47 | 48 | return TRUE; 49 | } 50 | 51 | 52 | static BOOL restoreSelectedWindow(HWND hwnd) 53 | { 54 | TRCONTEXT* context = IconList_GetContext(hwnd); 55 | int selectedIndex = IconList_GetSelectedIndex(hwnd); 56 | 57 | if (selectedIndex >= 0 && selectedIndex < context->iconIndex) { 58 | restoreWindow(context, 0, context->icons[selectedIndex].window); 59 | return EndDialog(hwnd, IDOK); 60 | } 61 | 62 | return FALSE; 63 | } 64 | 65 | 66 | static LRESULT CALLBACK DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 67 | { 68 | switch (uMsg) { 69 | case WM_COMMAND: 70 | switch (LOWORD(wParam)) { 71 | case IDCANCEL: 72 | return EndDialog(hwnd, IDCANCEL); 73 | case IDOK: 74 | return restoreSelectedWindow(hwnd); 75 | } 76 | break; 77 | case WM_NOTIFY: 78 | if (wParam == IDC_ICON_LIST && 79 | reinterpret_cast(lParam)->code == NM_CLICK) { 80 | 81 | return restoreSelectedWindow(hwnd); 82 | } 83 | break; 84 | case WM_NCACTIVATE: 85 | if (wParam == 0 && IsWindowVisible(hwnd)) { 86 | return EndDialog(hwnd, IDCANCEL); 87 | } 88 | break; 89 | case WM_INITDIALOG: 90 | return initDialog(hwnd, reinterpret_cast(lParam)); 91 | } 92 | return DefWindowProc(hwnd, uMsg, wParam, lParam); 93 | } 94 | 95 | INT_PTR showIconsDlg(TRCONTEXT *context) 96 | { 97 | static bool dialogOpened = false; 98 | if (dialogOpened || context->iconIndex == 0) { 99 | return FALSE; 100 | } 101 | dialogOpened = true; 102 | auto state = IconsDialog(context); 103 | auto result = DialogBoxParam( 104 | context->instance, 105 | MAKEINTRESOURCE(IDD_ICONS), 106 | HWND_DESKTOP, 107 | (DLGPROC)DialogProc, 108 | (LPARAM)&state 109 | ); 110 | dialogOpened = false; 111 | return result; 112 | } 113 | 114 | IconsDialog::IconsDialog(TRCONTEXT* context) 115 | { 116 | this->context = context; 117 | auto cx = GetSystemMetrics(SM_CXSMICON), 118 | cy = GetSystemMetrics(SM_CYSMICON); 119 | imageList = ImageList_Create(cx, cy, ILC_COLORDDB, context->iconIndex, 1); 120 | } 121 | 122 | IconsDialog::~IconsDialog() 123 | { 124 | ImageList_Destroy(imageList); 125 | } 126 | 127 | TRCONTEXT* IconsDialog::getContext() 128 | { 129 | return context; 130 | } 131 | 132 | HIMAGELIST IconsDialog::getImageList() 133 | { 134 | return imageList; 135 | } 136 | -------------------------------------------------------------------------------- /src/icons.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "traymond.h" 5 | 6 | #define IconList_GetSelectedIndex(hIconsDialog) \ 7 | ListView_GetNextItem(GetDlgItem(hIconsDialog, IDC_ICON_LIST), -1, LVNI_SELECTED) 8 | 9 | #define IconList_GetContext(hIconsDialog) \ 10 | reinterpret_cast(GetWindowLongPtr(hIconsDialog, GWLP_USERDATA))->getContext() 11 | 12 | 13 | class IconsDialog final { 14 | private: 15 | TRCONTEXT* context; 16 | HIMAGELIST imageList; 17 | public: 18 | IconsDialog(TRCONTEXT* context); 19 | ~IconsDialog(); 20 | TRCONTEXT* getContext(); 21 | HIMAGELIST getImageList(); 22 | }; 23 | 24 | INT_PTR showIconsDlg(TRCONTEXT* context); 25 | -------------------------------------------------------------------------------- /src/logging.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #ifdef DEBUG 5 | #define __LOGGING__ AllocConsole();\ 6 | auto _logging_stdout = freopen("CONOUT$", "w", stdout) 7 | #define debugf(...) printf(__VA_ARGS__) 8 | #else 9 | #define __LOGGING__ 10 | #define debugf(...) 11 | #endif 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/options.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "resource.h" 7 | #include "logging.h" 8 | #include "traymond.h" 9 | #include "options.h" 10 | #include "rules.h" 11 | 12 | 13 | UINT HotkeyToMod(UINT fsModifiers) 14 | { 15 | if ((fsModifiers & HOTKEYF_SHIFT) && !(fsModifiers & HOTKEYF_ALT)) { 16 | fsModifiers &= ~HOTKEYF_SHIFT; 17 | fsModifiers |= MOD_SHIFT; 18 | } 19 | else if (!(fsModifiers & HOTKEYF_SHIFT) && (fsModifiers & HOTKEYF_ALT)) { 20 | fsModifiers &= ~HOTKEYF_ALT; 21 | fsModifiers |= MOD_ALT; 22 | } 23 | return fsModifiers; 24 | } 25 | 26 | 27 | UINT ModToHotkey(UINT fsModifiers) 28 | { 29 | if ((fsModifiers & MOD_SHIFT) && !(fsModifiers & MOD_ALT)) { 30 | fsModifiers &= ~MOD_SHIFT; 31 | fsModifiers |= HOTKEYF_SHIFT; 32 | } else if (!(fsModifiers & MOD_SHIFT) && (fsModifiers & MOD_ALT)) { 33 | fsModifiers &= ~MOD_ALT; 34 | fsModifiers |= HOTKEYF_ALT; 35 | } 36 | return fsModifiers; 37 | } 38 | 39 | 40 | static void loadHotkey(PTCHAR key, HOTKEY *hotkey) 41 | { 42 | DWORD data = 0, size = sizeof(DWORD); 43 | if (ERROR_SUCCESS == RegGetValue(HKEY_CURRENT_USER, REG_KEY_SOFTWARE, key, RRF_RT_REG_DWORD, NULL, &data, &size)) { 44 | auto vkey = LOWORD(data), modifiers = HIWORD(data); 45 | if (vkey > 0 && modifiers > 0) { 46 | hotkey->modifiers = modifiers; 47 | hotkey->vkey = vkey; 48 | } 49 | } 50 | } 51 | 52 | 53 | void loadOptions(TRCONTEXT* context) 54 | { 55 | context->hotkey.modifiers = MOD_KEY; 56 | context->hotkey.vkey = TRAY_KEY; 57 | context->hotkey2.modifiers = 0; 58 | context->hotkey2.vkey = 0; 59 | context->hotkey3.modifiers = 0; 60 | context->hotkey3.vkey = 0; 61 | context->autorun = FALSE; 62 | context->hideType = HideTray; 63 | context->autoHiding = FALSE; 64 | context->hook = NULL; 65 | 66 | loadHotkey(_T("Hotkey"), &context->hotkey); 67 | loadHotkey(_T("Hotkey2"), &context->hotkey2); 68 | loadHotkey(_T("Hotkey3"), &context->hotkey3); 69 | 70 | DWORD data = 0, size = sizeof(DWORD); 71 | 72 | if (ERROR_SUCCESS == RegGetValue(HKEY_CURRENT_USER, REG_KEY_SOFTWARE, _T("HideType"), RRF_RT_REG_DWORD, NULL, &data, &size)) { 73 | context->hideType = data ? HideMenu : HideTray; 74 | } 75 | 76 | if (ERROR_SUCCESS == RegGetValue(HKEY_CURRENT_USER, REG_KEY_SOFTWARE, _T("AutoHiding"), RRF_RT_REG_DWORD, NULL, &data, &size)) { 77 | context->autoHiding = (BOOL)data; 78 | } 79 | 80 | if (ERROR_SUCCESS == RegGetValue(HKEY_CURRENT_USER, REG_KEY_RUN, APP_NAME, RRF_RT_REG_SZ, NULL, NULL, NULL)) { 81 | context->autorun = TRUE; 82 | } 83 | } 84 | 85 | 86 | void saveOptions(TRCONTEXT* context) 87 | { 88 | HKEY regKey = NULL; 89 | 90 | if (ERROR_SUCCESS == RegCreateKey(HKEY_CURRENT_USER, REG_KEY_SOFTWARE, ®Key)) { 91 | DWORD data = MAKELONG(context->hotkey.vkey, context->hotkey.modifiers); 92 | RegSetValueEx(regKey, _T("Hotkey"), 0, REG_DWORD, (BYTE*)&data, sizeof(DWORD)); 93 | data = MAKELONG(context->hotkey2.vkey, context->hotkey2.modifiers); 94 | RegSetValueEx(regKey, _T("Hotkey2"), 0, REG_DWORD, (BYTE*)&data, sizeof(DWORD)); 95 | data = MAKELONG(context->hotkey3.vkey, context->hotkey3.modifiers); 96 | RegSetValueEx(regKey, _T("Hotkey3"), 0, REG_DWORD, (BYTE*)&data, sizeof(DWORD)); 97 | data = context->hideType; 98 | RegSetValueEx(regKey, _T("HideType"), 0, REG_DWORD, (BYTE*)&data, sizeof(DWORD)); 99 | data = context->autoHiding; 100 | RegSetValueEx(regKey, _T("AutoHiding"), 0, REG_DWORD, (BYTE*)&data, sizeof(DWORD)); 101 | RegCloseKey(regKey); 102 | } 103 | 104 | if (ERROR_SUCCESS == RegOpenKey(HKEY_CURRENT_USER, REG_KEY_RUN, ®Key)) { 105 | if (context->autorun) { 106 | RegSetValueEx(regKey, APP_NAME, 0, REG_SZ, (BYTE*)context->cmdLine, _tcslen(context->cmdLine) * sizeof(TCHAR)); 107 | } 108 | else { 109 | RegDeleteValue(regKey, APP_NAME); 110 | } 111 | RegCloseKey(regKey); 112 | } 113 | } 114 | 115 | 116 | static BOOL setOptions(HWND hwnd, TRCONTEXT* context, WPARAM wParam) 117 | { 118 | UINT hotkeyIds[] = { IDHOT_HIDE_WINDOW, IDHOT_POPUP_ICONS, IDHOT_RESTORE_LAST_WINDOW }; 119 | HOTKEY* hotkeys[] = { &context->hotkey, &context->hotkey2, &context->hotkey3 }; 120 | HOTKEY readHotkeys[_countof(hotkeyIds)]{0}; 121 | auto listView = GetDlgItem(hwnd, IDC_HOTKEY_LIST); 122 | LVITEM lvi{}; 123 | lvi.mask = LVIF_PARAM; 124 | lvi.iSubItem = 0; 125 | for (int i = 0; i < _countof(readHotkeys); i++) { 126 | auto readHotkey = &readHotkeys[i]; 127 | auto hotkey = hotkeys[i]; 128 | lvi.iItem = i; 129 | if (ListView_GetItem(listView, &lvi)) { 130 | readHotkey->modifiers = HotkeyToMod(HIBYTE(LOWORD(lvi.lParam))); 131 | readHotkey->vkey = LOBYTE(LOWORD(lvi.lParam)); 132 | if (hotkey->modifiers == readHotkey->modifiers && hotkey->vkey == readHotkey->vkey) { 133 | hotkeys[i] = nullptr; 134 | continue; 135 | } 136 | if (lvi.lParam == 0) { 137 | continue; 138 | } 139 | if (!tryRegisterHotkey(hwnd, TEST_HOTKEY_ID, readHotkey->modifiers, readHotkey->vkey)) { 140 | return FALSE; 141 | } 142 | UnregisterHotKey(hwnd, TEST_HOTKEY_ID); 143 | } 144 | } 145 | for (int i = 0; i < _countof(hotkeys); i++) { 146 | auto hotkeyId = hotkeyIds[i]; 147 | auto readHotkey = readHotkeys[i]; 148 | auto hotkey = hotkeys[i]; 149 | if (hotkey == nullptr) { 150 | continue; 151 | } 152 | UnregisterHotKey(context->mainWindow, hotkeyId); 153 | if (readHotkey.modifiers > 0 && readHotkey.vkey > 0) { 154 | RegisterHotKey(context->mainWindow, hotkeyId, readHotkey.modifiers | MOD_NOREPEAT, readHotkey.vkey); 155 | } 156 | hotkey->modifiers = readHotkey.modifiers; 157 | hotkey->vkey = readHotkey.vkey; 158 | } 159 | context->autorun = IsDlgButtonChecked(hwnd, IDC_CHECK_AUTORUN); 160 | context->autoHiding = IsDlgButtonChecked(hwnd, IDC_CHECK_AUTO_HIDING); 161 | context->hideType = ComboBox_GetCurSel(GetDlgItem(hwnd, IDC_COMBO_HIDE_TYPE)) ? HideMenu : HideTray; 162 | reviseHiddenWindowIcon(context); 163 | saveOptions(context); 164 | return EndDialog(hwnd, wParam); 165 | } 166 | 167 | 168 | static BOOL initDialog(HWND hwnd, TRCONTEXT* context) 169 | { 170 | SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast(context)); 171 | SendMessage(hwnd, WM_SETICON, TRUE, (LPARAM)context->mainIcon); 172 | SendMessage(hwnd, WM_SETICON, FALSE, (LPARAM)context->mainIcon); 173 | 174 | auto listView = GetDlgItem(hwnd, IDC_HOTKEY_LIST); 175 | ListView_SetExtendedListViewStyle(listView, LVS_EX_BORDERSELECT | LVS_EX_FULLROWSELECT | LVS_EX_AUTOSIZECOLUMNS); 176 | LVCOLUMN lvc{}; 177 | lvc.mask = LVCF_FMT | LVCF_MINWIDTH | LVCF_TEXT; 178 | lvc.fmt = LVCFMT_LEFT; 179 | lvc.cxMin = 120; 180 | lvc.pszText = i18n[IDS_COL_KEY]; 181 | ListView_InsertColumn(listView, 0, &lvc); 182 | lvc.mask = LVCF_FMT | LVCF_MINWIDTH | LVCF_TEXT; 183 | lvc.fmt = LVCFMT_LEFT; 184 | lvc.cxMin = 120; 185 | lvc.pszText = i18n[IDS_COL_ACTION]; 186 | ListView_InsertColumn(listView, 1, &lvc); 187 | 188 | TCHAR hotkeyText[MAX_HOTKEY_TEXT]{ NULL }; 189 | PTCHAR actions[] = { i18n[IDS_ACT_1], i18n[IDS_ACT_2], i18n[IDS_ACT_3] }; 190 | LVITEM lvi{}; 191 | lvi.mask = LVIF_TEXT; 192 | for (int i = 0; i < _countof(actions); i++) { 193 | lvi.iSubItem = 0; 194 | lvi.iItem = i; 195 | lvi.pszText = NULL; 196 | ListView_InsertItem(listView, &lvi); 197 | lvi.iSubItem = 1; 198 | lvi.pszText = actions[i]; 199 | ListView_SetItem(listView, &lvi); 200 | } 201 | 202 | HOTKEY* hotkeys[] = { &context->hotkey, &context->hotkey2, &context->hotkey3 }; 203 | lvi.mask = LVIF_TEXT | LVIF_PARAM; 204 | lvi.iSubItem = 0; 205 | for (int i = 0; i < _countof(hotkeys); i++) { 206 | auto hotkey = hotkeys[i]; 207 | if (hotkey->modifiers == 0 || hotkey->vkey == 0) { 208 | continue; 209 | } 210 | getHotkeyText(hotkeyText, _countof(hotkeyText), hotkey->modifiers, hotkey->vkey); 211 | lvi.pszText = hotkeyText; 212 | lvi.iItem = i; 213 | lvi.lParam = MAKEWORD(hotkey->vkey, ModToHotkey(hotkey->modifiers)); 214 | ListView_SetItem(listView, &lvi); 215 | } 216 | ListView_SetColumnWidth(listView, 0, LVSCW_AUTOSIZE); 217 | ListView_SetColumnWidth(listView, 1, LVSCW_AUTOSIZE); 218 | 219 | CheckDlgButton(hwnd, IDC_CHECK_AUTORUN, context->autorun); 220 | CheckDlgButton(hwnd, IDC_CHECK_AUTO_HIDING, context->autoHiding); 221 | Button_Enable(GetDlgItem(hwnd, IDC_BUTTON_RULES), context->autoHiding); 222 | 223 | auto hideTypeCombo = GetDlgItem(hwnd, IDC_COMBO_HIDE_TYPE); 224 | ComboBox_AddItemData(hideTypeCombo, i18n[IDS_TRAY]); 225 | ComboBox_AddItemData(hideTypeCombo, i18n[IDS_MENU]); 226 | ComboBox_SetCurSel(hideTypeCombo, context->hideType); 227 | 228 | return TRUE; 229 | } 230 | 231 | 232 | static BOOL onHotkeyListItemChanged(HWND hwndDlg) 233 | { 234 | auto hotkeyListView = GetDlgItem(hwndDlg, IDC_HOTKEY_LIST); 235 | auto hotkeyEdit = GetDlgItem(hwndDlg, IDC_HOTKEY); 236 | auto hotkeyUseWinCheck = GetDlgItem(hwndDlg, IDC_CHECK_USE_WIN); 237 | auto selectedIndex = ListView_GetNextItem(GetDlgItem(hwndDlg, IDC_HOTKEY_LIST), -1, LVNI_SELECTED); 238 | if (selectedIndex < 0) { 239 | return FALSE; 240 | } 241 | 242 | LVITEM lvi{}; 243 | lvi.mask = LVIF_PARAM; 244 | lvi.iItem = selectedIndex; 245 | lvi.iSubItem = 0; 246 | if (!ListView_GetItem(hotkeyListView, &lvi)) { 247 | return FALSE; 248 | } 249 | 250 | EnableWindow(hotkeyEdit, TRUE); 251 | EnableWindow(hotkeyUseWinCheck, TRUE); 252 | 253 | SendMessage(hotkeyEdit, HKM_SETHOTKEY, lvi.lParam, 0); 254 | 255 | auto modifers = HotkeyToMod(HIBYTE(LOWORD(lvi.lParam))); 256 | return CheckDlgButton(hwndDlg, IDC_CHECK_USE_WIN, (modifers & MOD_WIN) ? BST_CHECKED : BST_UNCHECKED); 257 | } 258 | 259 | 260 | static BOOL onHotkeyChanged(HWND hwndDlg) 261 | { 262 | auto hotkeyListView = GetDlgItem(hwndDlg, IDC_HOTKEY_LIST); 263 | auto hotkeyEdit = GetDlgItem(hwndDlg, IDC_HOTKEY); 264 | auto selectedIndex = ListView_GetNextItem(GetDlgItem(hwndDlg, IDC_HOTKEY_LIST), -1, LVNI_SELECTED); 265 | if (selectedIndex < 0) { 266 | return FALSE; 267 | } 268 | 269 | UINT vkey, modifiers; 270 | DWORD result = SendMessage(hotkeyEdit, HKM_GETHOTKEY, 0, 0); 271 | vkey = LOBYTE(LOWORD(result)); 272 | modifiers = HotkeyToMod(HIBYTE(LOWORD(result))); 273 | if (BST_UNCHECKED != IsDlgButtonChecked(hwndDlg, IDC_CHECK_USE_WIN)) { 274 | modifiers |= MOD_WIN; 275 | } 276 | else { 277 | modifiers &= ~MOD_WIN; 278 | } 279 | 280 | if (vkey + modifiers > 0 && (vkey == 0 || modifiers == 0)) { 281 | return FALSE; 282 | } 283 | TCHAR hotkeyText[MAX_HOTKEY_TEXT]{ NULL }; 284 | LVITEM lvi{}; 285 | lvi.mask = LVIF_TEXT | LVIF_PARAM; 286 | lvi.iSubItem = 0; 287 | lvi.pszText = hotkeyText; 288 | lvi.iItem = selectedIndex; 289 | getHotkeyText(hotkeyText, _countof(hotkeyText), modifiers, vkey); 290 | lvi.lParam = MAKEWORD(vkey, ModToHotkey(modifiers)); 291 | ListView_SetItem(hotkeyListView, &lvi); 292 | return TRUE; 293 | } 294 | 295 | 296 | static BOOL CALLBACK DialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) 297 | { 298 | TRCONTEXT* context = reinterpret_cast(GetWindowLongPtr(hwndDlg, GWLP_USERDATA)); 299 | switch (message) { 300 | case WM_COMMAND: 301 | switch (LOWORD(wParam)) { 302 | case IDOK: 303 | return setOptions(hwndDlg, context, wParam); 304 | case IDCANCEL: 305 | return EndDialog(hwndDlg, wParam); 306 | case IDC_CHECK_AUTO_HIDING: 307 | return Button_Enable(GetDlgItem(hwndDlg, IDC_BUTTON_RULES), IsDlgButtonChecked(hwndDlg, IDC_CHECK_AUTO_HIDING)); 308 | case IDC_BUTTON_RULES: 309 | showRulesDlg(hwndDlg, context); 310 | return TRUE; 311 | case IDC_HOTKEY: 312 | if (HIWORD(wParam) == EN_CHANGE) { 313 | return onHotkeyChanged(hwndDlg); 314 | } 315 | break; 316 | case IDC_CHECK_USE_WIN: 317 | return onHotkeyChanged(hwndDlg); 318 | } 319 | break; 320 | case WM_NOTIFY: 321 | switch (LOWORD(wParam)) { 322 | case IDC_HOTKEY_LIST: 323 | if (LVN_ITEMCHANGED == reinterpret_cast(lParam)->code && 324 | (reinterpret_cast(lParam)->uChanged & LVIF_STATE) && 325 | (reinterpret_cast(lParam)->uNewState & LVIS_SELECTED)) { 326 | 327 | onHotkeyListItemChanged(hwndDlg); 328 | } 329 | break; 330 | } 331 | break; 332 | case WM_INITDIALOG: 333 | return initDialog(hwndDlg, reinterpret_cast(lParam)); 334 | } 335 | return FALSE; 336 | } 337 | 338 | 339 | INT_PTR showOptionsDlg(TRCONTEXT* context) 340 | { 341 | static bool dialogOpened = false; 342 | if (dialogOpened) { 343 | return FALSE; 344 | } 345 | 346 | dialogOpened = true; 347 | auto result = DialogBoxParam( 348 | i18n.lang(context->instance), 349 | MAKEINTRESOURCE(IDD_OPTIONS), 350 | HWND_DESKTOP, 351 | (DLGPROC)DialogProc, 352 | (LPARAM)context 353 | ); 354 | dialogOpened = false; 355 | return result; 356 | } 357 | -------------------------------------------------------------------------------- /src/options.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | INT_PTR showOptionsDlg(TRCONTEXT* context); 4 | void loadOptions(TRCONTEXT* context); 5 | void saveOptions(TRCONTEXT* context); 6 | UINT HotkeyToMod(UINT fsModifiers); 7 | UINT ModToHotkey(UINT fsModifiers); 8 | 9 | typedef enum { 10 | HOTKEY_OPTIONS_IGNORE, 11 | HOTKEY_OPTIONS_INVALID, 12 | HOTKEY_OPTIONS_SUCCESS, 13 | } HOTKEY_OPTIONS; 14 | -------------------------------------------------------------------------------- /src/resource.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define PROJECT_NAME "Traymond" 4 | #define PROJECT_NAME_LC "traymond" 5 | #define PROJECT_NAME_UC "TRAYMOND" 6 | #define VERSION_MAJOR 2 7 | #define VERSION_MINOR 2 8 | #define VERSION_PATCH 0 9 | #define VERSION_BUILD 1 10 | #define _VERSTR(number) __VERSTR(number) 11 | #define __VERSTR(number) #number 12 | #define MAKEVERSION _VERSTR(VERSION_MAJOR) "." _VERSTR(VERSION_MINOR) "." _VERSTR(VERSION_PATCH) 13 | #define MAKEFULLVERSION MAKEVERSION "." _VERSTR(VERSION_BUILD) 14 | #define POPUP_CLASS "POPUP_WINDOW" 15 | 16 | #define IDC_STATIC -1 17 | #define IDM_EXIT 1 18 | #define IDM_RESTORE_ALL_WINDOWS 2 19 | #define IDM_OPTIONS 3 20 | #define IDM_RESTORE_LAST_WINDOW 4 21 | #define IDC_NEW 11 22 | #define IDC_SAVE 12 23 | #define IDC_REMOVE 13 24 | #define IDD_OPTIONS 101 25 | #define IDD_RULES 103 26 | #define IDD_ICONS 104 27 | #define IDI_TRAYMOND 201 28 | #define IDC_HOTKEY 1000 29 | #define IDC_HOTKEY_LIST 1001 30 | #define IDC_CHECK_USE_WIN 1002 31 | #define IDC_CHECK_AUTORUN 1003 32 | #define IDC_COMBO_HIDE_TYPE 1004 33 | #define IDM_POPUP 1005 34 | #define IDC_LIST_RULES 1005 35 | #define IDC_CHECK_AUTO_HIDING 1006 36 | #define IDC_BUTTON_RULES 1007 37 | #define IDC_EDIT_TEXT 1009 38 | #define IDC_EDIT_CLASS 1010 39 | #define IDC_EDIT_PATH 1011 40 | #define IDC_CHECK_REGEX_TEXT 1012 41 | #define IDC_CHECK_REGEX_CLASS 1013 42 | #define IDC_CHECK_REGEX_PATH 1014 43 | #define IDC_EDIT_NAME 1015 44 | #define IDC_COMBO_WINDOWS 1016 45 | #define IDC_ICON_LIST 1019 46 | #define IDC_RADIO_ON_MINIMIZE 1021 47 | #define IDC_RADIO_ON_FIRST_SHOW 1022 48 | #define IDC_RADIO_ON_BOTH 1023 49 | #define IDC_RADIO_NEVER_NOTIFY 1024 50 | #define IDC_RADIO_ALWAYS_NOTIFY 1025 51 | #define IDC_RADIO_NOTIFY_FIRST_TIME 1026 52 | 53 | #define IDS_BEGIN 2001 54 | #define IDS_HOTKEY_ERROR 2001 55 | #define IDS_MUTEX_ERROR 2002 56 | #define IDS_ALREADY_RUNNING 2003 57 | #define IDS_SAVE_FILE_ERROR 2004 58 | #define IDS_TOO_MANY_HIDDEN_WINDOWS 2005 59 | #define IDS_RESTORE_FROM_UNEXPECTED_TERMINATION 2006 60 | #define IDS_HIDING_WINDOW 2007 61 | #define IDS_TRAY 2008 62 | #define IDS_MENU 2009 63 | #define IDS_COL_KEY 2010 64 | #define IDS_COL_ACTION 2011 65 | #define IDS_ACT_1 2012 66 | #define IDS_ACT_2 2013 67 | #define IDS_ACT_3 2014 68 | #define IDS_NEW_RULE 2015 69 | #define IDS_UNSAVED 2016 70 | #define IDS_RULE_INFO_REQUIRED 2017 71 | #define IDS_INVALID_REGEX 2018 72 | #define IDS_END 2019 73 | #define IDS_MAX_SIZE (IDS_END - IDS_BEGIN) 74 | -------------------------------------------------------------------------------- /src/rules.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "resource.h" 6 | #include "traymond.h" 7 | #include "winevent.h" 8 | #include "rules.h" 9 | 10 | static bool serializeRules(HIDING_RULES* rules, LPBYTE data, _Inout_ size_t* size) 11 | { 12 | size_t realSize = 0; 13 | 14 | for (HIDING_RULE* rule : *rules) { 15 | realSize += rule->size; 16 | } 17 | 18 | if (data) { 19 | if (realSize > *size) { 20 | return false; 21 | } 22 | auto offset = data; 23 | for (HIDING_RULE* rule : *rules) { 24 | memcpy(offset, rule, rule->size); 25 | offset += rule->size; 26 | } 27 | } 28 | else { 29 | *size = realSize; 30 | } 31 | return true; 32 | } 33 | 34 | static bool unserializeRules(HIDING_RULES* rules, LPBYTE data, size_t size) 35 | { 36 | HIDING_RULE* rule = NULL; 37 | 38 | for (size_t i = 0; i < size; i += rule->size) { 39 | rule = (HIDING_RULE*)(data + i); 40 | if (i + rule->size > size) { 41 | return false; 42 | } 43 | HIDING_RULE* ruleDest = (HIDING_RULE*) new BYTE[rule->size]; 44 | memcpy(ruleDest, rule, rule->size); 45 | rules->push_back(ruleDest); 46 | } 47 | return true; 48 | } 49 | 50 | bool saveRules(TRCONTEXT* context) 51 | { 52 | bool result = false; 53 | HKEY regKey = NULL; 54 | 55 | if (ERROR_SUCCESS == RegCreateKey(HKEY_CURRENT_USER, REG_KEY_SOFTWARE, ®Key)) { 56 | size_t rulesSize = 0; 57 | if (serializeRules(&context->hidingRules, NULL, &rulesSize)) { 58 | BYTE* rules = new BYTE[rulesSize]; 59 | if (serializeRules(&context->hidingRules, rules, &rulesSize)) { 60 | result = ERROR_SUCCESS == RegSetValueEx(regKey, _T("Rules"), 0, REG_BINARY, rules, rulesSize); 61 | } 62 | delete[] rules; 63 | } 64 | RegCloseKey(regKey); 65 | } 66 | return result; 67 | } 68 | 69 | bool loadRules(TRCONTEXT* context) 70 | { 71 | bool result = false; 72 | DWORD dataSize = 0; 73 | 74 | clearRules(context); 75 | 76 | if (ERROR_SUCCESS == RegGetValue(HKEY_CURRENT_USER, REG_KEY_SOFTWARE, _T("Rules"), RRF_RT_REG_BINARY, NULL, NULL, &dataSize)) { 77 | BYTE* data = new BYTE[dataSize]; 78 | if (ERROR_SUCCESS == RegGetValue(HKEY_CURRENT_USER, REG_KEY_SOFTWARE, _T("Rules"), RRF_RT_REG_BINARY, NULL, data, &dataSize)) { 79 | result = unserializeRules(&context->hidingRules, data, dataSize); 80 | } 81 | delete[] data; 82 | } 83 | return result; 84 | } 85 | 86 | static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) { 87 | auto context = reinterpret_cast(lParam); 88 | HIDING_RULE *rule = nullptr; 89 | if (IsWindowVisible(hwnd) && matchRule(context, hwnd, false , &rule)) { 90 | minimizeWindow(context, hwnd); 91 | if (RULE_IS_ALWAYS_NOTIFY(rule->flag) || 92 | RULE_IS_NOTIFY_FIRST_TIME(rule->flag)) { 93 | 94 | notifyHidingWindow(context, hwnd); 95 | } 96 | } 97 | return TRUE; 98 | } 99 | 100 | bool applyRules(TRCONTEXT* context) 101 | { 102 | if (!context->autoHiding) { 103 | return false; 104 | } 105 | 106 | return (bool)EnumWindows(EnumWindowsProc, reinterpret_cast(context)); 107 | } 108 | 109 | bool clearRules(TRCONTEXT* context) 110 | { 111 | if (context->hidingRules.empty()) { 112 | return false; 113 | } 114 | 115 | for (HIDING_RULE* rule : context->hidingRules) { 116 | delete[] (BYTE*)rule; 117 | } 118 | 119 | context->hidingRules.clear(); 120 | return true; 121 | } 122 | 123 | inline static bool compareText(PTCHAR text, PTCHAR pattern, bool isRegex) 124 | { 125 | if (isRegex) { 126 | try { 127 | TREGEX expr(pattern); 128 | return std::regex_match(text, expr); 129 | } 130 | catch (const std::regex_error&) { 131 | return false; 132 | } 133 | } 134 | else { 135 | return _tcscmp(text, pattern) == 0; 136 | } 137 | } 138 | 139 | _Success_(return) 140 | bool matchRule(TRCONTEXT* context, HWND hwnd, bool isMinimizing, _Out_ HIDING_RULE **rulePtr) 141 | { 142 | TCHAR windowText[MAX_WINDOW_TEXT] {}; 143 | TCHAR className[MAX_CLASS_NAME] {}; 144 | TCHAR exeFileName[MAX_PATH] {}; 145 | if (!GetWindowText(hwnd, windowText, MAX_WINDOW_TEXT) || 146 | !GetClassName(hwnd, className, MAX_CLASS_NAME) || 147 | !GetWindowExeFileName(hwnd, exeFileName, MAX_PATH)) { 148 | 149 | return false; 150 | } 151 | for (auto rule : context->hidingRules) { 152 | if (isMinimizing) { 153 | if (!(rule->flag & RULE_ON_MINIMIZE)) { 154 | continue; 155 | } 156 | } 157 | else { 158 | if (rule->flag & RULE_AUTO_OFF) { 159 | continue; 160 | } 161 | } 162 | auto text = rule->ruleData; 163 | text += _tcsclen(text) + 1; 164 | if (!compareText(windowText, text, rule->flag & RULE_REGEX_WINDOW_TEXT)) { 165 | continue; 166 | } 167 | text += _tcsclen(text) + 1; 168 | if (!compareText(className, text, rule->flag & RULE_REGEX_WINDOW_CLASS)) { 169 | continue; 170 | } 171 | text += _tcsclen(text) + 1; 172 | if (!compareText(exeFileName, text, rule->flag & RULE_REGEX_EXE_FILENAME)) { 173 | continue; 174 | } 175 | *rulePtr = rule; 176 | return true; 177 | } 178 | return false; 179 | } 180 | 181 | static BOOL CALLBACK DialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) 182 | { 183 | RuleEditor* editor; 184 | if (WM_INITDIALOG == message) { 185 | editor = reinterpret_cast(lParam); 186 | editor->initialize(hwndDlg); 187 | return TRUE; 188 | } 189 | editor = reinterpret_cast(GetWindowLongPtr(hwndDlg, GWLP_USERDATA)); 190 | return editor->dispatchMessage(message, wParam, lParam); 191 | } 192 | 193 | INT_PTR showRulesDlg(HWND parent, TRCONTEXT* context) 194 | { 195 | static bool dialogOpened = false; 196 | if (dialogOpened) { 197 | return FALSE; 198 | } 199 | dialogOpened = true; 200 | auto editor = RuleEditor(context); 201 | auto result = DialogBoxParam( 202 | i18n.lang(context->instance), 203 | MAKEINTRESOURCE(IDD_RULES), 204 | parent, 205 | (DLGPROC)DialogProc, 206 | (LPARAM)&editor 207 | ); 208 | dialogOpened = false; 209 | return result; 210 | } 211 | 212 | inline static bool testRegex(HWND hwnd, PTCHAR pattern) 213 | { 214 | TCHAR errMsg[MAX_MSG]{}; 215 | try { 216 | TREGEX expr(pattern); 217 | } 218 | catch (const std::regex_error&) { 219 | _stprintf_s(errMsg, i18n[IDS_INVALID_REGEX], pattern); 220 | MessageBox(hwnd, errMsg, APP_NAME, MB_OK | MB_ICONWARNING); 221 | return false; 222 | } 223 | return true; 224 | } 225 | 226 | HIDING_RULE* RuleEditor::newRule() 227 | { 228 | size_t size = sizeof(HIDING_RULE); 229 | TCHAR ruleName[MAX_RULE_NAME], 230 | windowText[MAX_WINDOW_TEXT], 231 | windowClassName[MAX_CLASS_NAME], 232 | exeFileName[MAX_PATH]; 233 | 234 | auto ruleNameSize = Edit_GetText(nameEdit, ruleName, MAX_RULE_NAME) + 1, 235 | windowTextSize = Edit_GetText(textEdit, windowText, MAX_WINDOW_TEXT) + 1, 236 | windowClassNameSize = Edit_GetText(classEdit, windowClassName, MAX_CLASS_NAME) + 1, 237 | exeFileNameSize = Edit_GetText(pathEdit, exeFileName, MAX_PATH) + 1; 238 | bool isWindowTextRegex = Button_GetCheck(textCheckBox) == BST_CHECKED, 239 | isWindowClassNameRegex = Button_GetCheck(classCheckBox) == BST_CHECKED, 240 | isExeFileNameRegex = Button_GetCheck(pathCheckBox) == BST_CHECKED, 241 | isAlwaysNotify = Button_GetCheck(alwaysNotifyRadioBox) == BST_CHECKED, 242 | isNotifyFirstTime = Button_GetCheck(notifyFirstTimeRadioBox) == BST_CHECKED, 243 | isOnMinimize = Button_GetCheck(onMinimizeRadioBox) == BST_CHECKED, 244 | isOnBoth = Button_GetCheck(onBothRadioBox) == BST_CHECKED; 245 | 246 | if (ruleNameSize == 1 || windowTextSize == 1 || windowClassNameSize == 1 || exeFileNameSize == 1) { 247 | MessageBox(window, i18n[IDS_RULE_INFO_REQUIRED], APP_NAME, MB_OK | MB_ICONWARNING); 248 | return NULL; 249 | } 250 | 251 | if (isWindowTextRegex && !testRegex(window, windowText)) { 252 | return NULL; 253 | } 254 | if (isWindowClassNameRegex && !testRegex(window, windowClassName)) { 255 | return NULL; 256 | } 257 | if (isExeFileNameRegex && !testRegex(window, exeFileName)) { 258 | return NULL; 259 | } 260 | 261 | size += (ruleNameSize + windowTextSize + windowClassNameSize + exeFileNameSize) * sizeof(TCHAR); 262 | HIDING_RULE* rule = (HIDING_RULE*)new BYTE[size]; 263 | rule->size = size; 264 | rule->flag = 0; 265 | if (isWindowTextRegex) rule->flag |= RULE_REGEX_WINDOW_TEXT; 266 | if (isWindowClassNameRegex) rule->flag |= RULE_REGEX_WINDOW_CLASS; 267 | if (isExeFileNameRegex) rule->flag |= RULE_REGEX_EXE_FILENAME; 268 | if (isOnMinimize) { 269 | rule->flag |= (RULE_ON_MINIMIZE | RULE_AUTO_OFF); 270 | } 271 | else if (isOnBoth) { 272 | rule->flag |= RULE_ON_MINIMIZE; 273 | } 274 | if (isAlwaysNotify) { 275 | rule->flag |= RULE_ALWAYS_NOTIFY; 276 | } 277 | else if (isNotifyFirstTime) { 278 | rule->flag |= RULE_NOTIFY_FIRST_TIME; 279 | } 280 | PTCHAR ruleDataOffset = rule->ruleData; 281 | memcpy(ruleDataOffset, ruleName, ruleNameSize * sizeof(TCHAR)); 282 | ruleDataOffset += ruleNameSize; 283 | memcpy(ruleDataOffset, windowText, windowTextSize * sizeof(TCHAR)); 284 | ruleDataOffset += windowTextSize; 285 | memcpy(ruleDataOffset, windowClassName, windowClassNameSize * sizeof(TCHAR)); 286 | ruleDataOffset += windowClassNameSize; 287 | memcpy(ruleDataOffset, exeFileName, exeFileNameSize * sizeof(TCHAR)); 288 | return rule; 289 | } 290 | 291 | RuleEditor::RuleEditor(TRCONTEXT* context) 292 | { 293 | this->context = context; 294 | } 295 | 296 | TRCONTEXT* RuleEditor::getContext() 297 | { 298 | return context; 299 | } 300 | 301 | void RuleEditor::initialize(HWND hwnd) 302 | { 303 | SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast(this)); 304 | window = hwnd; 305 | ruleList = GetDlgItem(hwnd, IDC_LIST_RULES); 306 | nameEdit = GetDlgItem(hwnd, IDC_EDIT_NAME); 307 | textEdit = GetDlgItem(hwnd, IDC_EDIT_TEXT); 308 | classEdit = GetDlgItem(hwnd, IDC_EDIT_CLASS); 309 | pathEdit = GetDlgItem(hwnd, IDC_EDIT_PATH); 310 | textCheckBox = GetDlgItem(hwnd, IDC_CHECK_REGEX_TEXT); 311 | classCheckBox = GetDlgItem(hwnd, IDC_CHECK_REGEX_CLASS); 312 | pathCheckBox = GetDlgItem(hwnd, IDC_CHECK_REGEX_PATH); 313 | saveButton = GetDlgItem(hwnd, IDC_SAVE); 314 | removeButton = GetDlgItem(hwnd, IDC_REMOVE); 315 | dropButton = GetDlgItem(hwnd, IDABORT); 316 | windowsCombo = GetDlgItem(hwnd, IDC_COMBO_WINDOWS); 317 | onMinimizeRadioBox = GetDlgItem(hwnd, IDC_RADIO_ON_MINIMIZE); 318 | onFirstShowRadioBox = GetDlgItem(hwnd, IDC_RADIO_ON_FIRST_SHOW); 319 | onBothRadioBox = GetDlgItem(hwnd, IDC_RADIO_ON_BOTH); 320 | neverNotifyRadioBox = GetDlgItem(hwnd, IDC_RADIO_NEVER_NOTIFY); 321 | alwaysNotifyRadioBox = GetDlgItem(hwnd, IDC_RADIO_ALWAYS_NOTIFY); 322 | notifyFirstTimeRadioBox = GetDlgItem(hwnd, IDC_RADIO_NOTIFY_FIRST_TIME); 323 | Edit_LimitText(nameEdit, MAX_RULE_NAME); 324 | Edit_LimitText(textEdit, MAX_WINDOW_TEXT); 325 | Edit_LimitText(classEdit, MAX_CLASS_NAME); 326 | Edit_LimitText(pathEdit, MAX_PATH); 327 | sync(); 328 | } 329 | 330 | #pragma warning(push) 331 | #pragma warning(disable:4100) 332 | bool RuleEditor::dispatchMessage(UINT message, WPARAM wParam, LPARAM lParam) 333 | #pragma warning(pop) 334 | { 335 | switch (message) { 336 | case WM_COMMAND: 337 | switch (LOWORD(wParam)) { 338 | case IDHELP: 339 | ShellExecute(window, _T("open"), HELP_URL, NULL, NULL, SW_SHOWNORMAL); 340 | break; 341 | case IDABORT: 342 | drop(); 343 | return TRUE; 344 | case IDCANCEL: 345 | return EndDialog(window, wParam); 346 | case IDC_NEW: 347 | return append(); 348 | case IDC_REMOVE: 349 | return remove(); 350 | case IDC_SAVE: 351 | return save(); 352 | case IDC_LIST_RULES: 353 | switch (HIWORD(wParam)) { 354 | case LBN_SELCANCEL: 355 | case LBN_SELCHANGE: 356 | select(); 357 | break; 358 | } 359 | break; 360 | case IDC_EDIT_NAME: 361 | case IDC_EDIT_TEXT: 362 | case IDC_EDIT_CLASS: 363 | case IDC_EDIT_PATH: 364 | if (HIWORD(wParam) == EN_CHANGE) { 365 | touch(); 366 | } 367 | break; 368 | case IDC_RADIO_ON_MINIMIZE: 369 | case IDC_RADIO_ON_FIRST_SHOW: 370 | case IDC_RADIO_ON_BOTH: 371 | case IDC_RADIO_NEVER_NOTIFY: 372 | case IDC_RADIO_ALWAYS_NOTIFY: 373 | case IDC_RADIO_NOTIFY_FIRST_TIME: 374 | case IDC_CHECK_REGEX_CLASS: 375 | case IDC_CHECK_REGEX_PATH: 376 | case IDC_CHECK_REGEX_TEXT: 377 | touch(); 378 | break; 379 | case IDC_COMBO_WINDOWS: 380 | if (HIWORD(wParam) == CBN_SELCHANGE) { 381 | fill(); 382 | } 383 | break; 384 | } 385 | break; 386 | } 387 | return FALSE; 388 | } 389 | 390 | bool RuleEditor::append() 391 | { 392 | if (dirty()) { 393 | switch (MessageBox(window, i18n[IDS_UNSAVED], APP_NAME, MB_YESNOCANCEL | MB_ICONQUESTION)) { 394 | case IDYES: 395 | save(); 396 | break; 397 | case IDNO: 398 | drop(); 399 | break; 400 | case IDCANCEL: 401 | return FALSE; 402 | } 403 | } 404 | sync(); 405 | TCHAR ruleName[MAX_RULE_NAME]; 406 | _stprintf_s(ruleName, _T("* %s"), i18n[IDS_NEW_RULE]); 407 | auto index = ListBox_AddString(ruleList, ruleName); 408 | ListBox_SetCurSel(ruleList, index); 409 | reset(true); 410 | select(); 411 | enable(); 412 | Edit_SetText(nameEdit, i18n[IDS_NEW_RULE]); 413 | return TRUE; 414 | } 415 | 416 | bool RuleEditor::remove() 417 | { 418 | auto index = ListBox_GetCurSel(ruleList); 419 | if (index < 0) { 420 | return false; 421 | } 422 | if ((size_t)index < context->hidingRules.size()) { 423 | auto rule = context->hidingRules[index]; 424 | delete[] (BYTE*)rule; 425 | VECTOR_ERASE(context->hidingRules, index); 426 | } 427 | ListBox_DeleteString(ruleList, index); 428 | ListBox_SetCurSel(ruleList, -1); 429 | select(); 430 | enable(false); 431 | reset(false); 432 | clean(); 433 | return saveRules(context); 434 | } 435 | 436 | void RuleEditor::reset(bool init) 437 | { 438 | Button_SetCheck(textCheckBox, BST_UNCHECKED); 439 | Button_SetCheck(classCheckBox, BST_UNCHECKED); 440 | Button_SetCheck(pathCheckBox, BST_UNCHECKED); 441 | Button_SetCheck(onMinimizeRadioBox, BST_UNCHECKED); 442 | Button_SetCheck(onBothRadioBox, BST_UNCHECKED); 443 | Button_SetCheck(alwaysNotifyRadioBox, BST_UNCHECKED); 444 | Button_SetCheck(notifyFirstTimeRadioBox, BST_UNCHECKED); 445 | Edit_SetText(nameEdit, NULL); 446 | Edit_SetText(textEdit, NULL); 447 | Edit_SetText(classEdit, NULL); 448 | Edit_SetText(pathEdit, NULL); 449 | if (init) { 450 | Button_SetCheck(onFirstShowRadioBox, BST_CHECKED); 451 | Button_SetCheck(neverNotifyRadioBox, BST_CHECKED); 452 | } 453 | else { 454 | Button_SetCheck(onFirstShowRadioBox, BST_UNCHECKED); 455 | Button_SetCheck(neverNotifyRadioBox, BST_UNCHECKED); 456 | } 457 | } 458 | 459 | void RuleEditor::enable(bool val) 460 | { 461 | Edit_Enable(nameEdit, val); 462 | Edit_Enable(textEdit, val); 463 | Edit_Enable(classEdit, val); 464 | Edit_Enable(pathEdit, val); 465 | Button_Enable(textCheckBox, val); 466 | Button_Enable(classCheckBox, val); 467 | Button_Enable(pathCheckBox, val); 468 | ComboBox_Enable(windowsCombo, val); 469 | Button_Enable(onMinimizeRadioBox, val); 470 | Button_Enable(onFirstShowRadioBox, val); 471 | Button_Enable(onBothRadioBox, val); 472 | Button_Enable(neverNotifyRadioBox, val); 473 | Button_Enable(alwaysNotifyRadioBox, val); 474 | Button_Enable(notifyFirstTimeRadioBox, val); 475 | } 476 | 477 | void RuleEditor::touch() 478 | { 479 | if (!isDirty && !isBusy) { 480 | isDirty = true; 481 | Button_Enable(saveButton, TRUE); 482 | Button_Enable(dropButton, TRUE); 483 | ListBox_Enable(ruleList, FALSE); 484 | } 485 | } 486 | 487 | void RuleEditor::clean() 488 | { 489 | if (isDirty) { 490 | isDirty = false; 491 | Button_Enable(saveButton, FALSE); 492 | Button_Enable(dropButton, FALSE); 493 | ListBox_Enable(ruleList, TRUE); 494 | } 495 | } 496 | 497 | void RuleEditor::sync() 498 | { 499 | ListBox_ResetContent(ruleList); 500 | for (HIDING_RULE* rule : context->hidingRules) { 501 | ListBox_AddString(ruleList, rule->ruleData); 502 | } 503 | 504 | ComboBox_ResetContent(windowsCombo); 505 | TCHAR windowText[MAX_WINDOW_TEXT]{}; 506 | for (int i = 0; i < context->iconIndex; i++) { 507 | auto hiddenWindow = context->icons[i].window; 508 | if (GetWindowText(hiddenWindow, windowText, MAX_WINDOW_TEXT) == 0) { 509 | continue; 510 | } 511 | ComboBox_SetItemData(windowsCombo, ComboBox_AddString(windowsCombo, windowText), i); 512 | } 513 | } 514 | 515 | void RuleEditor::drop() 516 | { 517 | auto index = ListBox_GetCurSel(ruleList); 518 | if ((size_t)index >= context->hidingRules.size()) { 519 | ListBox_DeleteString(ruleList, index); 520 | } 521 | ListBox_SetCurSel(ruleList, -1); 522 | select(); 523 | enable(false); 524 | reset(false); 525 | clean(); 526 | } 527 | 528 | void RuleEditor::select() 529 | { 530 | isBusy = true; 531 | auto index = ListBox_GetCurSel(ruleList); 532 | Button_Enable(removeButton, index > -1 ? TRUE : FALSE); 533 | if (index > -1 && (size_t)index < context->hidingRules.size()) { 534 | enable(); 535 | auto rule = context->hidingRules[index]; 536 | auto text = rule->ruleData; 537 | Edit_SetText(nameEdit, text); 538 | text += _tcsclen(text) + 1; 539 | Edit_SetText(textEdit, text); 540 | text += _tcsclen(text) + 1; 541 | Edit_SetText(classEdit, text); 542 | text += _tcsclen(text) + 1; 543 | Edit_SetText(pathEdit, text); 544 | Button_SetCheck(textCheckBox, rule->flag & RULE_REGEX_WINDOW_TEXT ? BST_CHECKED : BST_UNCHECKED); 545 | Button_SetCheck(classCheckBox, rule->flag & RULE_REGEX_WINDOW_CLASS ? BST_CHECKED : BST_UNCHECKED); 546 | Button_SetCheck(pathCheckBox, rule->flag & RULE_REGEX_EXE_FILENAME ? BST_CHECKED : BST_UNCHECKED); 547 | 548 | int onMinimizeChecked = BST_UNCHECKED, 549 | onFirstShowChecked = BST_UNCHECKED, 550 | onBothChecked = BST_UNCHECKED; 551 | if (rule->flag & RULE_ON_MINIMIZE) { 552 | if (rule->flag & RULE_AUTO_OFF) { 553 | onMinimizeChecked = BST_CHECKED; 554 | } 555 | else { 556 | onBothChecked = BST_CHECKED; 557 | } 558 | } 559 | else { 560 | onFirstShowChecked = BST_CHECKED; 561 | } 562 | Button_SetCheck(onMinimizeRadioBox, onMinimizeChecked); 563 | Button_SetCheck(onFirstShowRadioBox, onFirstShowChecked); 564 | Button_SetCheck(onBothRadioBox, onBothChecked); 565 | 566 | int neverNotifyChecked = BST_UNCHECKED, 567 | alwaysNotifyChecked = BST_UNCHECKED, 568 | notifyFirstTimeChecked = BST_UNCHECKED; 569 | if (rule->flag & RULE_ALWAYS_NOTIFY) { 570 | alwaysNotifyChecked = true; 571 | } else if (rule->flag & RULE_NOTIFY_FIRST_TIME) { 572 | notifyFirstTimeChecked = true; 573 | } 574 | else { 575 | neverNotifyChecked = true; 576 | } 577 | Button_SetCheck(neverNotifyRadioBox, neverNotifyChecked); 578 | Button_SetCheck(alwaysNotifyRadioBox, alwaysNotifyChecked); 579 | Button_SetCheck(notifyFirstTimeRadioBox, notifyFirstTimeChecked); 580 | } 581 | isBusy = false; 582 | } 583 | 584 | void RuleEditor::fill() 585 | { 586 | Button_SetCheck(textCheckBox, BST_UNCHECKED); 587 | Button_SetCheck(classCheckBox, BST_UNCHECKED); 588 | Button_SetCheck(pathCheckBox, BST_UNCHECKED); 589 | 590 | auto index = ComboBox_GetItemData(windowsCombo, ComboBox_GetCurSel(windowsCombo)); 591 | if (index >= context->iconIndex) { 592 | return; 593 | } 594 | 595 | auto hwnd = context->icons[index].window; 596 | TCHAR windowText[MAX_WINDOW_TEXT]{}; 597 | TCHAR className[MAX_CLASS_NAME]{}; 598 | TCHAR exeFileName[MAX_PATH]{}; 599 | if (GetWindowText(hwnd, windowText, MAX_WINDOW_TEXT)) { 600 | Edit_SetText(textEdit, windowText); 601 | } 602 | if (GetClassName(hwnd, className, MAX_CLASS_NAME)) { 603 | Edit_SetText(classEdit, className); 604 | } 605 | if (GetWindowExeFileName(hwnd, exeFileName, MAX_PATH)) { 606 | Edit_SetText(pathEdit, exeFileName); 607 | } 608 | } 609 | 610 | bool RuleEditor::dirty() 611 | { 612 | return isDirty; 613 | } 614 | 615 | bool RuleEditor::save() 616 | { 617 | auto index = ListBox_GetCurSel(ruleList); 618 | HIDING_RULE* rule = newRule(); 619 | if (NULL == rule) { 620 | return false; 621 | } 622 | if ((size_t)index >= context->hidingRules.size()) { 623 | context->hidingRules.push_back(rule); 624 | } 625 | else { 626 | auto originalRule = context->hidingRules[index]; 627 | delete[] (BYTE*)originalRule; 628 | context->hidingRules[index] = rule; 629 | } 630 | ListBox_DeleteString(ruleList, index); 631 | ListBox_InsertString(ruleList, index, rule->ruleData); 632 | ListBox_SetCurSel(ruleList, index); 633 | select(); 634 | clean(); 635 | return saveRules(context); 636 | } 637 | -------------------------------------------------------------------------------- /src/rules.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef UNICODE 4 | #define TREGEX std::wregex 5 | #else 6 | #define TREGEX std::regex 7 | #endif 8 | 9 | bool loadRules(TRCONTEXT* context); 10 | bool applyRules(TRCONTEXT* context); 11 | bool saveRules(TRCONTEXT* context); 12 | bool clearRules(TRCONTEXT* context); 13 | _Success_(return) 14 | bool matchRule(TRCONTEXT* context, HWND hwnd, bool isMinimizing, _Out_ HIDING_RULE **rulePtr); 15 | INT_PTR showRulesDlg(HWND parent, TRCONTEXT* context); 16 | 17 | class RuleEditor final { 18 | private: 19 | TRCONTEXT* context; 20 | HWND window = NULL; 21 | HWND ruleList = NULL; 22 | HWND nameEdit = NULL; 23 | HWND textEdit = NULL; 24 | HWND classEdit = NULL; 25 | HWND pathEdit = NULL; 26 | HWND textCheckBox = NULL; 27 | HWND classCheckBox = NULL; 28 | HWND pathCheckBox = NULL; 29 | HWND saveButton = NULL; 30 | HWND removeButton = NULL; 31 | HWND dropButton = NULL; 32 | HWND windowsCombo = NULL; 33 | HWND onMinimizeRadioBox = NULL; 34 | HWND onFirstShowRadioBox = NULL; 35 | HWND onBothRadioBox = NULL; 36 | HWND neverNotifyRadioBox = NULL; 37 | HWND alwaysNotifyRadioBox = NULL; 38 | HWND notifyFirstTimeRadioBox = NULL; 39 | int ruleId = -1; 40 | bool isDirty = false; 41 | bool isBusy = false; 42 | private: 43 | HIDING_RULE* newRule(); 44 | public: 45 | RuleEditor(TRCONTEXT* context); 46 | void initialize(HWND hwnd); 47 | void enable(bool val = true); 48 | void touch(); 49 | void clean(); 50 | void sync(); 51 | void drop(); 52 | void select(); 53 | void fill(); 54 | void reset(bool init = true); 55 | bool dirty(); 56 | bool save(); 57 | bool append(); 58 | bool remove(); 59 | bool dispatchMessage(UINT message, WPARAM wParam, LPARAM lParam); 60 | TRCONTEXT* getContext(); 61 | }; 62 | -------------------------------------------------------------------------------- /src/traymond.cpp: -------------------------------------------------------------------------------- 1 | #define TRAYMON_MAIN 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "traymond.h" 10 | #include "options.h" 11 | #include "winevent.h" 12 | #include "rules.h" 13 | #include "logging.h" 14 | #include "icons.h" 15 | 16 | HANDLE saveFile; 17 | TRCONTEXT appContext = {}; 18 | 19 | 20 | // Saves our hidden windows so they can be restored in case 21 | // of crashing. 22 | static void save(const TRCONTEXT *context) { 23 | DWORD numbytes; 24 | // Truncate file 25 | SetFilePointer(saveFile, 0, NULL, FILE_BEGIN); 26 | SetEndOfFile(saveFile); 27 | if (!context->iconIndex) { 28 | return; 29 | } 30 | for (int i = 0; i < context->iconIndex; i++) { 31 | if (context->icons[i].window) { 32 | std::string str; 33 | str = std::to_string((long)context->icons[i].window); 34 | str += ','; 35 | const char *handleString = str.c_str(); 36 | WriteFile(saveFile, handleString, strlen(handleString), &numbytes, NULL); 37 | } 38 | } 39 | } 40 | 41 | // Creates our tray icon menu 42 | static HMENU createTrayMenu(const TRCONTEXT* context) { 43 | HMENU popupMenu = GetSubMenu(LoadMenu(i18n.lang(context->instance), MAKEINTRESOURCE(IDM_POPUP)), 0); 44 | MENUINFO mi{}; 45 | mi.cbSize = sizeof(MENUINFO); 46 | mi.fMask = MIM_STYLE; 47 | GetMenuInfo(popupMenu, &mi); 48 | mi.dwStyle |= MNS_NOTIFYBYPOS; 49 | SetMenuInfo(popupMenu, &mi); 50 | SetMenuDefaultItem(popupMenu, IDM_OPTIONS, FALSE); 51 | return popupMenu; 52 | } 53 | 54 | // Remove menu item from tray popup menu 55 | static void removeMenuItem(TRCONTEXT* context, int index) { 56 | DestroyMenu(context->trayMenu); 57 | context->trayMenu = createTrayMenu(context); 58 | 59 | for (int i = 0; i < context->iconIndex; i++) { 60 | auto hw = context->icons + i; 61 | if (hw->hideType != HideMenu) { 62 | continue; 63 | } 64 | if (i == index) { 65 | DeleteObject(hw->menu.info.hbmpItem); 66 | continue; 67 | } 68 | hw->menu.info.dwTypeData = hw->menu.caption; 69 | InsertMenuItem(context->trayMenu, 0, TRUE, &hw->menu.info); 70 | } 71 | } 72 | 73 | HICON getWindowIcon(const TRCONTEXT* context, HWND hwnd) { 74 | HICON icon = (HICON)SendMessage(hwnd, WM_GETICON, ICON_SMALL2, NULL); 75 | if (!icon) { 76 | icon = (HICON)GetClassLongPtr(hwnd, GCLP_HICONSM); 77 | if (!icon) { 78 | return context->mainIcon; 79 | } 80 | } 81 | return icon; 82 | } 83 | 84 | static HBITMAP IconToBitmap(HICON icon, int width = 0, int height = 0) { 85 | if (!width) width = GetSystemMetrics(SM_CXSMICON); 86 | if (!height) height = GetSystemMetrics(SM_CYSMICON); 87 | HDC hdc = GetDC(NULL); 88 | HDC hdcMem = CreateCompatibleDC(hdc); 89 | BITMAPINFO bmi; 90 | memset(&bmi, 0, sizeof(BITMAPINFO)); 91 | bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); 92 | bmi.bmiHeader.biWidth = width; 93 | bmi.bmiHeader.biHeight = height; 94 | bmi.bmiHeader.biPlanes = 1; 95 | bmi.bmiHeader.biBitCount = 32; 96 | bmi.bmiHeader.biCompression = BI_RGB; 97 | #pragma warning(push) 98 | #pragma warning(disable:6387) 99 | HBITMAP hbmpMem = CreateDIBSection(hdcMem, &bmi, DIB_RGB_COLORS, NULL, NULL, 0); 100 | #pragma warning(pop) 101 | if (hbmpMem) { 102 | auto oldObj = SelectObject(hdcMem, hbmpMem); 103 | DrawIconEx(hdcMem, 0, 0, icon, width, height, 0, NULL, DI_NORMAL); 104 | SelectObject(hdcMem, oldObj); 105 | } 106 | DeleteDC(hdcMem); 107 | ReleaseDC(NULL, hdc); 108 | return hbmpMem; 109 | } 110 | 111 | // Revise hidden window icon hide type 112 | // Return the number of icons moved 113 | int reviseHiddenWindowIcon(TRCONTEXT* context) { 114 | int count = 0; 115 | HIDDEN_WINDOW hiddenWindow; 116 | for (int i = 0; i < context->iconIndex; i++) { 117 | auto hideType = context->icons[i].hideType; 118 | if (hideType == context->hideType) { 119 | continue; 120 | } 121 | memset(&hiddenWindow, 0, sizeof(HIDDEN_WINDOW)); 122 | auto currWin = hiddenWindow.window = context->icons[i].window; 123 | switch (hideType) { 124 | case HideMenu: 125 | removeMenuItem(context, i); 126 | switch (context->hideType) { 127 | case HideTray: 128 | hiddenWindow.hideType = HideTray; 129 | auto nid = &hiddenWindow.icon; 130 | nid->cbSize = sizeof(NOTIFYICONDATA); 131 | nid->hWnd = context->mainWindow; 132 | nid->hIcon = getWindowIcon(context, currWin); 133 | nid->uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_SHOWTIP; 134 | nid->uVersion = NOTIFYICON_VERSION; 135 | nid->uID = reinterpret_cast(currWin); 136 | nid->uCallbackMessage = WM_ICON; 137 | GetWindowText(currWin, hiddenWindow.icon.szTip, MAX_WINDOW_TEXT); 138 | Shell_NotifyIcon(NIM_ADD, &hiddenWindow.icon); 139 | Shell_NotifyIcon(NIM_SETVERSION, &hiddenWindow.icon); 140 | context->icons[i] = hiddenWindow; 141 | count++; 142 | break; 143 | } 144 | break; 145 | case HideTray: 146 | Shell_NotifyIcon(NIM_DELETE, &context->icons[i].icon); 147 | switch (context->hideType) { 148 | case HideMenu: 149 | auto mid = &hiddenWindow.menu; 150 | auto mii = &mid->info; 151 | hiddenWindow.hideType = HideMenu; 152 | mii->hbmpItem = IconToBitmap(getWindowIcon(context, currWin)); 153 | mii->cbSize = sizeof(MENUITEMINFO); 154 | mii->fMask = MIIM_STRING | MIIM_ID | MIIM_BITMAP | MIIM_DATA; 155 | mii->fType = MFT_STRING | MFT_BITMAP; 156 | mii->dwTypeData = mid->caption; 157 | mii->cch = GetWindowText(currWin, mii->dwTypeData, MAX_WINDOW_TEXT); 158 | mii->wID = 0; 159 | mii->dwItemData = reinterpret_cast(currWin); 160 | InsertMenuItem(context->trayMenu, 0, TRUE, mii); 161 | context->icons[i] = hiddenWindow; 162 | count++; 163 | break; 164 | } 165 | break; 166 | } 167 | } 168 | save(context); // Maybe not necessary 169 | return count; 170 | } 171 | 172 | // Restores a window 173 | bool restoreWindow(TRCONTEXT *context, UINT xID, HWND hwnd) { 174 | for (int i = 0; i < context->iconIndex; i++) { 175 | auto icon = context->icons + i; 176 | switch (icon->hideType) { 177 | case HideTray: 178 | if (icon->icon.uID != xID && icon->window != hwnd) continue; 179 | Shell_NotifyIcon(NIM_DELETE, &icon->icon); 180 | break; 181 | case HideMenu: 182 | if (icon->menu.info.dwItemData != xID && icon->window != hwnd) continue; 183 | removeMenuItem(context, i); 184 | break; 185 | default: 186 | continue; 187 | } 188 | 189 | auto currWin = icon->window; 190 | if (IsWindow(currWin)) { 191 | ShowWindow(currWin, SW_NORMAL); 192 | SetForegroundWindow(currWin); 193 | } 194 | 195 | *icon = {}; 196 | std::vector temp = std::vector(context->iconIndex); 197 | // Restructure array so there are no holes 198 | for (int j = 0, x = 0; j < context->iconIndex; j++) { 199 | if (context->icons[j].window) { 200 | temp[x] = context->icons[j]; 201 | x++; 202 | } 203 | } 204 | memcpy_s(context->icons, sizeof(context->icons), &temp.front(), sizeof(HIDDEN_WINDOW)*context->iconIndex); 205 | context->iconIndex--; 206 | save(context); 207 | context->hiddenWindows.erase(currWin); 208 | context->freeWindows.insert(currWin); 209 | return true; 210 | } 211 | return false; 212 | } 213 | 214 | // Minimizes the current window to tray or menu. 215 | // Uses currently focused window unless supplied a handle as the argument. 216 | bool minimizeWindow(TRCONTEXT *context, HWND currWin, bool restored) { 217 | // Taskbar and desktop windows are restricted from hiding. 218 | const TCHAR restrictWins[][14] = { {_T("WorkerW")}, {_T("Shell_TrayWnd")} }; 219 | 220 | if (!currWin) { 221 | currWin = GetForegroundWindow(); 222 | } 223 | 224 | DWORD processId = 0; 225 | GetWindowThreadProcessId(currWin, &processId); 226 | if (!currWin || !isTopLevelWindow(currWin) || GetCurrentProcessId() == processId) { 227 | return false; 228 | } 229 | 230 | for (int i = 0; i < context->iconIndex; i++) { 231 | if (currWin == context->icons[i].window) { 232 | return IsWindowVisible(currWin) && !ShowWindow(currWin, SW_HIDE); 233 | } 234 | } 235 | 236 | TCHAR className[MAX_CLASS_NAME]; 237 | if (!GetClassName(currWin, className, MAX_CLASS_NAME)) { 238 | return false; 239 | } 240 | for (int i = 0; i < sizeof(restrictWins) / sizeof(*restrictWins); i++) { 241 | if (_tcscmp(restrictWins[i], className) == 0) { 242 | return false; 243 | } 244 | } 245 | 246 | if (context->iconIndex == MAXIMUM_WINDOWS) { 247 | MessageBox(NULL, i18n[IDS_TOO_MANY_HIDDEN_WINDOWS], APP_NAME, MB_OK | MB_ICONERROR); 248 | return false; 249 | } 250 | 251 | if (IsWindowVisible(currWin) && !ShowWindow(currWin, SW_HIDE)) { 252 | MessageBeep(MB_ICONWARNING); 253 | return false; 254 | } 255 | switch (context->icons[context->iconIndex].hideType = context->hideType) { 256 | case HideTray: 257 | NOTIFYICONDATA nid; 258 | nid.cbSize = sizeof(NOTIFYICONDATA); 259 | nid.hWnd = context->mainWindow; 260 | nid.hIcon = getWindowIcon(context, currWin); 261 | nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_SHOWTIP; 262 | nid.uVersion = NOTIFYICON_VERSION; 263 | nid.uID = reinterpret_cast(currWin); 264 | nid.uCallbackMessage = WM_ICON; 265 | GetWindowText(currWin, nid.szTip, MAX_WINDOW_TEXT); 266 | context->icons[context->iconIndex].icon = nid; 267 | Shell_NotifyIcon(NIM_ADD, &nid); 268 | Shell_NotifyIcon(NIM_SETVERSION, &nid); 269 | break; 270 | case HideMenu: 271 | MENUITEMINFO mii; 272 | mii.hbmpItem = IconToBitmap(getWindowIcon(context, currWin)); 273 | mii.cbSize = sizeof(MENUITEMINFO); 274 | mii.fMask = MIIM_STRING | MIIM_ID | MIIM_BITMAP | MIIM_DATA; 275 | mii.fType = MFT_STRING | MFT_BITMAP; 276 | mii.dwTypeData = context->icons[context->iconIndex].menu.caption; 277 | mii.cch = GetWindowText(currWin, mii.dwTypeData, MAX_WINDOW_TEXT); 278 | mii.wID = 0; 279 | mii.dwItemData = reinterpret_cast(currWin); 280 | context->icons[context->iconIndex].menu.info = mii; 281 | InsertMenuItem(context->trayMenu, 0, TRUE, &mii); 282 | break; 283 | } 284 | context->icons[context->iconIndex].window = currWin; 285 | context->iconIndex++; 286 | if (!restored) { 287 | save(context); 288 | } 289 | context->hiddenWindows.insert(currWin); 290 | context->freeWindows.erase(currWin); 291 | return true; 292 | } 293 | 294 | // Adds our own icon to tray 295 | static void createTrayIcon(HWND mainWindow, NOTIFYICONDATA* icon) { 296 | icon->cbSize = sizeof(NOTIFYICONDATA); 297 | icon->hWnd = mainWindow; 298 | icon->uFlags = NIF_ICON | NIF_TIP | NIF_SHOWTIP | NIF_MESSAGE; 299 | icon->uVersion = NOTIFYICON_VERSION; 300 | icon->uID = reinterpret_cast(mainWindow); 301 | icon->uCallbackMessage = WM_OURICON; 302 | _tcscpy_s(icon->szTip, APP_NAME); 303 | Shell_NotifyIcon(NIM_ADD, icon); 304 | Shell_NotifyIcon(NIM_SETVERSION, icon); 305 | } 306 | 307 | BOOL notifyHidingWindow(TRCONTEXT* context, HWND hwnd) 308 | { 309 | TCHAR message[MAX_MSG]{ NULL }; 310 | TCHAR windowText[MAX_WINDOW_TEXT]{ NULL }; 311 | TCHAR exeFileName[MAX_PATH]{ NULL }; 312 | GetWindowText(hwnd, windowText, MAX_WINDOW_TEXT); 313 | GetWindowExeFileName(hwnd, exeFileName, MAX_PATH); 314 | _stprintf_s(message, _T("%s:\n%s"), windowText, exeFileName); 315 | auto mainWindow = context->mainWindow; 316 | NOTIFYICONDATA icon{}; 317 | icon.cbSize = sizeof(icon); 318 | icon.hWnd = mainWindow; 319 | icon.uFlags = NIF_INFO; 320 | icon.uVersion = NOTIFYICON_VERSION; 321 | icon.uID = reinterpret_cast(mainWindow); 322 | _tcscpy_s(icon.szInfo, message); 323 | _tcscpy_s(icon.szInfoTitle, i18n[IDS_HIDING_WINDOW]); 324 | return Shell_NotifyIcon(NIM_MODIFY, &icon); 325 | } 326 | 327 | // Shows all hidden windows; 328 | static void showAllWindows(TRCONTEXT *context) { 329 | context->hiddenWindows.clear(); 330 | for (int i = 0; i < context->iconIndex; i++) 331 | { 332 | auto currWin = context->icons[i].window; 333 | ShowWindow(context->icons[i].window, SW_NORMAL); 334 | context->freeWindows.insert(currWin); 335 | switch (context->icons[i].hideType) { 336 | case HideTray: Shell_NotifyIcon(NIM_DELETE, &context->icons[i].icon); break; 337 | case HideMenu: removeMenuItem(context, i); break; 338 | } 339 | context->icons[i] = {}; 340 | } 341 | save(context); 342 | context->iconIndex = 0; 343 | } 344 | 345 | static void exitApp() { 346 | PostQuitMessage(0); 347 | } 348 | 349 | static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) { 350 | if (hwnd == reinterpret_cast(lParam)) { 351 | SetLastError(TOP_LEVEL_WINDOW_ERROR); 352 | return FALSE; 353 | } 354 | return TRUE; 355 | } 356 | 357 | static BOOL WINAPI _IsTopLevelWindow(HWND hwnd) { 358 | if (!EnumWindows(EnumWindowsProc, reinterpret_cast(hwnd)) && GetLastError() == TOP_LEVEL_WINDOW_ERROR) { 359 | return TRUE; 360 | } 361 | return FALSE; 362 | } 363 | 364 | static void setupIsTopLevelWindow() 365 | { 366 | auto user32Module = GetModuleHandle(_T("user32.dll")); 367 | if (user32Module) { 368 | isTopLevelWindow = (IsTopLevelWindow)GetProcAddress(user32Module, "IsTopLevelWindow"); 369 | } 370 | if (NULL == isTopLevelWindow) { 371 | isTopLevelWindow = _IsTopLevelWindow; 372 | } 373 | } 374 | 375 | // Creates and reads the save file to restore hidden windows in case of unexpected termination 376 | static void startup(TRCONTEXT *context) { 377 | setupIsTopLevelWindow(); 378 | loadRules(context); 379 | applyRules(context); 380 | hookWinEvent(context); 381 | TCHAR currDir[MAX_PATH] = { NULL }; 382 | auto currDirLen = GetModuleFileName(NULL, currDir, MAX_PATH); 383 | for (int i = currDirLen; i > 0; i--) { 384 | if (currDir[i] == '\\') { 385 | currDir[i] = NULL; 386 | break; 387 | } 388 | } 389 | SetCurrentDirectory(currDir); 390 | if ((saveFile = CreateFile(SAVE_FILE_NAME, GENERIC_READ | GENERIC_WRITE, \ 391 | 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE) { 392 | MessageBox(NULL, i18n[IDS_SAVE_FILE_ERROR], APP_NAME, MB_OK | MB_ICONERROR); 393 | exitApp(); 394 | } 395 | // Check if we've crashed (i. e. there is a save file) during current uptime and 396 | // if there are windows to restore, in which case restore them and 397 | // display a reassuring message. 398 | if (GetLastError() == ERROR_ALREADY_EXISTS) { 399 | DWORD numbytes; 400 | DWORD fileSize = GetFileSize(saveFile, NULL); 401 | 402 | if (!fileSize) { 403 | return; 404 | }; 405 | 406 | FILETIME saveFileWriteTime; 407 | GetFileTime(saveFile, NULL, NULL, &saveFileWriteTime); 408 | uint64_t writeTime = ((uint64_t)saveFileWriteTime.dwHighDateTime << 32 | (uint64_t)saveFileWriteTime.dwLowDateTime) / 10000; 409 | GetSystemTimeAsFileTime(&saveFileWriteTime); 410 | writeTime = (((uint64_t)saveFileWriteTime.dwHighDateTime << 32 | (uint64_t)saveFileWriteTime.dwLowDateTime) / 10000) - writeTime; 411 | 412 | if (GetTickCount64() < writeTime) { 413 | return; 414 | } 415 | 416 | std::vector contents = std::vector(fileSize); 417 | if (ReadFile(saveFile, &contents.front(), fileSize, &numbytes, NULL)) { 418 | char handle[10] = { NULL }; 419 | int index = 0; 420 | for (size_t i = 0; i < fileSize; i++) { 421 | if (contents[i] != ',') { 422 | handle[index] = contents[i]; 423 | index++; 424 | } 425 | else { 426 | index = 0; 427 | minimizeWindow(context, reinterpret_cast(std::stoi(std::string(handle))), true); 428 | memset(handle, 0, sizeof(handle)); 429 | } 430 | } 431 | TCHAR restoreMessage[MAX_MSG]; 432 | _stprintf_s(restoreMessage, i18n[IDS_RESTORE_FROM_UNEXPECTED_TERMINATION], context->iconIndex); 433 | MessageBox(NULL, restoreMessage, APP_NAME, MB_OK); 434 | } 435 | } 436 | } 437 | 438 | static void shutdown(TRCONTEXT* context) 439 | { 440 | CloseHandle(saveFile); 441 | DeleteFile(SAVE_FILE_NAME); // No save file means we have exited gracefully 442 | unhookWinEvent(context); 443 | clearRules(context); 444 | } 445 | 446 | static void onTaskbarRestart(TRCONTEXT* context) 447 | { 448 | NOTIFYICONDATA icon {}; 449 | icon.hIcon = context->mainIcon; 450 | createTrayIcon(context->mainWindow, &icon); 451 | for (int i = 0; i < context->iconIndex; i++) { 452 | auto hiddenWindow = &context->icons[i]; 453 | if (hiddenWindow->hideType != HideTray) { 454 | continue; 455 | } 456 | Shell_NotifyIcon(NIM_ADD, &hiddenWindow->icon); 457 | Shell_NotifyIcon(NIM_SETVERSION, &hiddenWindow->icon); 458 | } 459 | } 460 | 461 | static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 462 | { 463 | static UINT taskbarRestart; 464 | TRCONTEXT* context = reinterpret_cast(GetWindowLongPtr(hwnd, GWLP_USERDATA)); 465 | POINT pt; 466 | switch (uMsg) 467 | { 468 | case WM_CREATE: 469 | taskbarRestart = RegisterWindowMessage(_T("TaskbarCreated")); 470 | break; 471 | case WM_ICON: 472 | if (lParam == WM_LBUTTONDBLCLK) { 473 | restoreWindow(context, wParam); 474 | } 475 | break; 476 | case WM_KEYDOWN:debugf("KEY DOWN"); break; 477 | case WM_OURICON: 478 | switch (lParam) { 479 | case WM_RBUTTONUP: 480 | SetForegroundWindow(hwnd); 481 | GetCursorPos(&pt); 482 | TrackPopupMenuEx( 483 | context->trayMenu, 484 | TPM_BOTTOMALIGN | (GetSystemMetrics(SM_MENUDROPALIGNMENT) ? TPM_RIGHTALIGN : TPM_LEFTALIGN), 485 | pt.x, pt.y, hwnd, NULL 486 | ); 487 | break; 488 | case WM_LBUTTONDBLCLK: 489 | showOptionsDlg(context); 490 | break; 491 | } 492 | break; 493 | case WM_MENUCOMMAND: 494 | HMENU menu; 495 | menu = reinterpret_cast(lParam); 496 | if (menu == context->trayMenu) { 497 | MENUITEMINFO mii; 498 | mii.cbSize = sizeof(MENUITEMINFO); 499 | mii.fMask = MIIM_ID | MIIM_DATA; 500 | if (GetMenuItemInfo(menu, wParam, TRUE, &mii)) { 501 | switch (mii.wID) { 502 | case IDM_EXIT: 503 | exitApp(); 504 | break; 505 | case IDM_OPTIONS: 506 | showOptionsDlg(context); 507 | break; 508 | case IDM_RESTORE_ALL_WINDOWS: 509 | showAllWindows(context); 510 | break; 511 | case IDM_RESTORE_LAST_WINDOW: 512 | restoreWindow(context, 0, context->icons[context->iconIndex - 1].window); 513 | break; 514 | default: 515 | restoreWindow(context, mii.dwItemData); 516 | break; 517 | } 518 | } 519 | } 520 | break; 521 | case WM_HOTKEY: 522 | switch (wParam) { 523 | case IDHOT_HIDE_WINDOW: 524 | minimizeWindow(context, NULL); 525 | break; 526 | case IDHOT_POPUP_ICONS: 527 | showIconsDlg(context); 528 | break; 529 | case IDHOT_RESTORE_LAST_WINDOW: 530 | restoreWindow(context, 0, context->icons[context->iconIndex - 1].window); 531 | break; 532 | } 533 | break; 534 | default: 535 | if (uMsg == taskbarRestart) { 536 | onTaskbarRestart(context); 537 | } 538 | return DefWindowProc(hwnd, uMsg, wParam, lParam); 539 | } 540 | return 0; 541 | } 542 | 543 | PTCHAR getHotkeyText(PTCHAR text, rsize_t textSize, UINT modifiers, UINT vkey) 544 | { 545 | constexpr TCHAR KEY_WIN[] = _T("Win"); 546 | constexpr TCHAR KEY_ALT[] = _T("Alt"); 547 | constexpr TCHAR KEY_CTRL[] = _T("Ctrl"); 548 | constexpr TCHAR KEY_SHIFT[] = _T("Shift"); 549 | constexpr TCHAR KEY_APPEND_ALT[] = _T(" + Alt"); 550 | constexpr TCHAR KEY_APPEND_CTRL[] = _T(" + Ctrl"); 551 | constexpr TCHAR KEY_APPEND_SHIFT[] = _T(" + Shift"); 552 | constexpr TCHAR KEY_PLUS[] = _T(" + "); 553 | 554 | text[0] = NULL; 555 | 556 | if (modifiers & MOD_WIN) { 557 | _tcsnccat_s(text, textSize, KEY_WIN, _countof(KEY_WIN)); 558 | } 559 | if (modifiers & MOD_CONTROL) { 560 | _tcsnlen(text, textSize) ? 561 | _tcsnccat_s(text, textSize, KEY_APPEND_CTRL, _countof(KEY_APPEND_CTRL)) : 562 | _tcsnccat_s(text, textSize, KEY_CTRL, _countof(KEY_CTRL)); 563 | } 564 | if (modifiers & MOD_SHIFT) { 565 | _tcsnlen(text, textSize) ? 566 | _tcsnccat_s(text, textSize, KEY_APPEND_SHIFT, _countof(KEY_APPEND_SHIFT)) : 567 | _tcsnccat_s(text, textSize, KEY_SHIFT, _countof(KEY_SHIFT)); 568 | } 569 | if (modifiers & MOD_ALT) { 570 | _tcsnlen(text, textSize) ? 571 | _tcsnccat_s(text, textSize, KEY_APPEND_ALT, _countof(KEY_APPEND_ALT)) : 572 | _tcsnccat_s(text, textSize, KEY_ALT, _countof(KEY_ALT)); 573 | } 574 | size_t len = _tcsnlen(text, textSize); 575 | if (len > 0) { 576 | _tcsnccat_s(text, textSize, KEY_PLUS, _countof(KEY_PLUS)); 577 | len += _countof(KEY_PLUS) - 1; 578 | } 579 | GetKeyNameText(MapVirtualKey(vkey, MAPVK_VK_TO_VSC) << 0x10, text + len, textSize - len); 580 | return text; 581 | } 582 | 583 | bool tryRegisterHotkey(HWND hwnd, int id, UINT modifiers, UINT vkey) 584 | { 585 | if (modifiers == 0 || vkey == 0) { 586 | return false; 587 | } 588 | if (RegisterHotKey(hwnd, id, modifiers | MOD_NOREPEAT, vkey)) { 589 | return true; 590 | } 591 | 592 | TCHAR errMsg[MAX_MSG]{ NULL }; 593 | TCHAR hotkeyText[MAX_HOTKEY_TEXT]{ NULL }; 594 | getHotkeyText(hotkeyText, _countof(hotkeyText) - 1, modifiers, vkey); 595 | _stprintf_s(errMsg, i18n[IDS_HOTKEY_ERROR], hotkeyText); 596 | MessageBox(NULL, errMsg, APP_NAME, MB_OK | MB_ICONWARNING); 597 | return false; 598 | } 599 | 600 | #pragma warning(push) 601 | #pragma warning(disable:4100) 602 | #pragma warning(disable:4189) 603 | #pragma warning(disable:4996) 604 | int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd) { 605 | __LOGGING__; 606 | #pragma warning(pop) 607 | 608 | // Mutex to allow only one instance 609 | HANDLE mutex = CreateMutex(NULL, TRUE, MUTEX_NAME); 610 | if (mutex == NULL) { 611 | MessageBox(NULL, i18n[IDS_MUTEX_ERROR], APP_NAME, MB_OK | MB_ICONERROR); 612 | return 1; 613 | } 614 | else if (GetLastError() == ERROR_ALREADY_EXISTS) { 615 | MessageBox(NULL, i18n[IDS_ALREADY_RUNNING], APP_NAME, MB_OK | MB_ICONERROR); 616 | return 1; 617 | } 618 | 619 | WNDCLASS wc = {}; 620 | wc.lpfnWndProc = WindowProc; 621 | wc.hInstance = hInstance; 622 | wc.lpszClassName = APP_NAME; 623 | if (!RegisterClass(&wc)) { 624 | return 1; 625 | } 626 | 627 | GetClassInfo(NULL, WC_DIALOG, &wc); 628 | wc.style |= CS_DROPSHADOW; 629 | wc.lpszClassName = _T(POPUP_CLASS); 630 | if (!RegisterClass(&wc)) { 631 | return 1; 632 | } 633 | 634 | auto context = &appContext; 635 | context->instance = hInstance; 636 | context->cmdLine = GetCommandLine(); 637 | loadOptions(context); 638 | 639 | context->mainWindow = CreateWindow(APP_NAME, NULL, NULL, 0, 0, 0, 0, NULL, NULL, hInstance, NULL); 640 | if (!context->mainWindow) { 641 | return 1; 642 | } 643 | ShowWindow(context->mainWindow, SW_HIDE); 644 | 645 | // Store our context in main window for retrieval by WindowProc 646 | SetWindowLongPtr(context->mainWindow, GWLP_USERDATA, reinterpret_cast(context)); 647 | 648 | NOTIFYICONDATA icon = {}; 649 | context->mainIcon = icon.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TRAYMOND)); 650 | createTrayIcon(context->mainWindow, &icon); 651 | context->trayMenu = createTrayMenu(context); 652 | startup(context); 653 | 654 | tryRegisterHotkey(context->mainWindow, IDHOT_HIDE_WINDOW, context->hotkey.modifiers, context->hotkey.vkey); 655 | tryRegisterHotkey(context->mainWindow, IDHOT_POPUP_ICONS, context->hotkey2.modifiers, context->hotkey2.vkey); 656 | tryRegisterHotkey(context->mainWindow, IDHOT_RESTORE_LAST_WINDOW, context->hotkey3.modifiers, context->hotkey3.vkey); 657 | 658 | BOOL bRet; 659 | MSG msg; 660 | while ((bRet = GetMessage(&msg, 0, 0, 0)) != 0) 661 | { 662 | if (bRet != -1) { 663 | TranslateMessage(&msg); 664 | DispatchMessage(&msg); 665 | } 666 | } 667 | // Clean up on exit; 668 | UnregisterHotKey(context->mainWindow, IDHOT_HIDE_WINDOW); 669 | UnregisterHotKey(context->mainWindow, IDHOT_POPUP_ICONS); 670 | UnregisterHotKey(context->mainWindow, IDHOT_RESTORE_LAST_WINDOW); 671 | showAllWindows(context); 672 | Shell_NotifyIcon(NIM_DELETE, &icon); 673 | DestroyMenu(context->trayMenu); 674 | DestroyWindow(context->mainWindow); 675 | shutdown(context); 676 | ReleaseMutex(mutex); 677 | CloseHandle(mutex); 678 | return msg.wParam; 679 | } 680 | 681 | TRCONTEXT* AppContext() 682 | { 683 | return &appContext; 684 | } 685 | -------------------------------------------------------------------------------- /src/traymond.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "resource.h" 8 | #include "i18n.h" 9 | 10 | 11 | #define VK_Z_KEY 0x5A 12 | #define VK_Q_KEY 0x51 13 | // These keys are used to send windows to tray 14 | #define TRAY_KEY VK_Z_KEY 15 | #define TRAY_KEY2 VK_Q_KEY 16 | #define MOD_KEY MOD_WIN + MOD_SHIFT 17 | 18 | #define WM_ICON 0x1C0A 19 | #define WM_OURICON 0x1C0B 20 | #define MAXIMUM_WINDOWS 100 21 | #define IDHOT_HIDE_WINDOW 0 22 | #define IDHOT_POPUP_ICONS 1 23 | #define IDHOT_RESTORE_LAST_WINDOW 2 24 | #define TEST_HOTKEY_ID 0xBFFF 25 | #define MAX_MSG 1024 26 | #define MAX_HOTKEY_TEXT 64 27 | #define MAX_WINDOW_TEXT 128 28 | #define MAX_CLASS_NAME 256 29 | #define MAX_RULE_NAME 128 30 | 31 | #define APP_NAME _T(PROJECT_NAME) 32 | #define SAVE_FILE_NAME _T(PROJECT_NAME_LC ".dat") 33 | #define MUTEX_NAME _T(PROJECT_NAME_LC "_mutex") 34 | #define REG_KEY_SOFTWARE _T("SOFTWARE\\" PROJECT_NAME) 35 | #define REG_KEY_RUN _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run") 36 | #define HELP_URL _T("https://github.com/tabris17/traymond/wiki") 37 | 38 | #define APPLICATION_ERROR_CODE 0x10000000 39 | #define TOP_LEVEL_WINDOW_ERROR (APPLICATION_ERROR_CODE | 1) 40 | 41 | #define SET_CONTAINS(_THE_SET_, _THE_VALUE_) ((_THE_SET_).find(_THE_VALUE_) != (_THE_SET_).end()) 42 | #define SET_NOT_CONTAINS(_THE_SET_, _THE_VALUE_) ((_THE_SET_).find(_THE_VALUE_) == (_THE_SET_).end()) 43 | #define VECTOR_ERASE(_THE_VECTOR_, _THE_INDEX_) ((_THE_VECTOR_).erase((_THE_VECTOR_).begin() + (_THE_INDEX_))) 44 | #define RULE_IS_ALWAYS_NOTIFY(_FLAG_) (_FLAG_ & RULE_ALWAYS_NOTIFY) 45 | #define RULE_IS_NOTIFY_FIRST_TIME(_FLAG_) (_FLAG_ & RULE_NOTIFY_FIRST_TIME) 46 | 47 | typedef BOOL(WINAPI *IsTopLevelWindow)(HWND hwnd); 48 | 49 | typedef struct { 50 | UINT modifiers; 51 | UINT vkey; 52 | } HOTKEY; 53 | 54 | typedef enum { 55 | HideTray = 0, 56 | HideMenu = 1, 57 | } HIDE_TYPE; 58 | 59 | typedef struct { 60 | MENUITEMINFO info; 61 | TCHAR caption[MAX_WINDOW_TEXT]; 62 | } MENUITEMDATA; 63 | 64 | // Stores hidden window record. 65 | typedef struct HIDDEN_WINDOW { 66 | HIDE_TYPE hideType; 67 | union { 68 | NOTIFYICONDATA icon; 69 | MENUITEMDATA menu; 70 | }; 71 | HWND window; 72 | } HIDDEN_WINDOW; 73 | 74 | #define RULE_REGEX_WINDOW_TEXT 1 75 | #define RULE_REGEX_WINDOW_CLASS (1 << 8) 76 | #define RULE_REGEX_EXE_FILENAME (1 << 16) 77 | #define RULE_ON_MINIMIZE (1 << 1) 78 | #define RULE_AUTO_OFF (1 << 2) 79 | #define RULE_NOTIFY_FIRST_TIME (1 << 24) 80 | #define RULE_ALWAYS_NOTIFY (1 << 23) 81 | 82 | // Auto hiding window rule 83 | #pragma warning(push) 84 | #pragma warning(disable:4200) 85 | typedef struct HIDING_RULE { 86 | size_t size; 87 | DWORD flag; 88 | TCHAR ruleData[0]; 89 | } HIDING_RULE; 90 | #pragma warning(push) 91 | 92 | typedef std::unordered_set HWND_SET; 93 | 94 | typedef std::vector HIDING_RULES; 95 | 96 | // Current execution context 97 | typedef struct TRCONTEXT { 98 | HIDE_TYPE hideType; 99 | BOOL autorun; 100 | BOOL autoHiding; 101 | HOTKEY hotkey; 102 | HOTKEY hotkey2; 103 | HOTKEY hotkey3; 104 | HWINEVENTHOOK hook; 105 | HICON mainIcon; 106 | HINSTANCE instance; 107 | LPTSTR cmdLine; 108 | HWND mainWindow; 109 | HIDDEN_WINDOW icons[MAXIMUM_WINDOWS]; 110 | HMENU trayMenu; 111 | int iconIndex; // How many windows are currently hidden 112 | HWND_SET freeWindows; 113 | HWND_SET hiddenWindows; 114 | HIDING_RULES hidingRules; 115 | } TRCONTEXT; 116 | 117 | int reviseHiddenWindowIcon(TRCONTEXT* context); 118 | bool minimizeWindow(TRCONTEXT* context, HWND currWin, bool restored = false); 119 | bool restoreWindow(TRCONTEXT* context, UINT xID, HWND hwnd = NULL); 120 | PTCHAR getHotkeyText(PTCHAR text, rsize_t textSize, UINT modifiers, UINT vkey); 121 | bool tryRegisterHotkey(HWND hwnd, int id, UINT modifiers, UINT vkey); 122 | HICON getWindowIcon(const TRCONTEXT* context, HWND hwnd); 123 | BOOL notifyHidingWindow(TRCONTEXT* context, HWND hwnd); 124 | 125 | TRCONTEXT* AppContext(); 126 | 127 | #ifdef TRAYMON_MAIN 128 | IsTopLevelWindow isTopLevelWindow = NULL; 129 | I18n i18n; 130 | #else 131 | extern IsTopLevelWindow isTopLevelWindow; 132 | extern I18n i18n; 133 | #endif 134 | -------------------------------------------------------------------------------- /src/winevent.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "winevent.h" 5 | #include "traymond.h" 6 | #include "logging.h" 7 | #include "rules.h" 8 | 9 | 10 | DWORD GetWindowExeFileName(HWND hwnd, PTCHAR fileName, DWORD size) 11 | { 12 | DWORD processId; 13 | if (0 == GetWindowThreadProcessId(hwnd, &processId)) { 14 | return FALSE; 15 | } 16 | HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId); 17 | if (NULL == process) { 18 | return FALSE; 19 | } 20 | auto result = GetModuleFileNameEx(process, NULL, fileName, size); 21 | CloseHandle(process); 22 | return result; 23 | } 24 | 25 | #pragma warning(push) 26 | #pragma warning(disable:4100) 27 | static void WINAPI DefaultWinEventProc(HWINEVENTHOOK hWinEventHook, 28 | DWORD event, HWND hwnd, 29 | LONG idObject, LONG idChild, 30 | DWORD idEventThread, DWORD dwmsEventTime) 31 | #pragma warning(pop) 32 | { 33 | if (event != EVENT_OBJECT_DESTROY) return; 34 | 35 | auto context = AppContext(); 36 | context->freeWindows.erase(hwnd); 37 | 38 | if (SET_CONTAINS(context->hiddenWindows, hwnd)) { 39 | restoreWindow(context, 0, hwnd); 40 | } 41 | } 42 | 43 | 44 | static void WINAPI AutoHidingWinEventProc(HWINEVENTHOOK hWinEventHook, 45 | DWORD event, HWND hwnd, 46 | LONG idObject, LONG idChild, 47 | DWORD idEventThread, DWORD dwmsEventTime) 48 | { 49 | bool isMinimizing = false; 50 | switch (event) { 51 | case EVENT_SYSTEM_MINIMIZESTART: 52 | isMinimizing = true; 53 | break; 54 | case EVENT_OBJECT_CREATE: 55 | break; 56 | case EVENT_OBJECT_DESTROY: 57 | return DefaultWinEventProc( 58 | hWinEventHook, 59 | event, 60 | hwnd, 61 | idObject, 62 | idChild, 63 | idEventThread, 64 | dwmsEventTime 65 | ); 66 | case EVENT_OBJECT_SHOW: 67 | break; 68 | case EVENT_OBJECT_NAMECHANGE: 69 | break; 70 | default: 71 | return; 72 | } 73 | 74 | if ((OBJID_WINDOW != idObject || CHILDID_SELF != idChild) || 75 | !isTopLevelWindow(hwnd) || !IsWindowVisible(hwnd)) { 76 | 77 | return; 78 | } 79 | 80 | auto context = AppContext(); 81 | HIDING_RULE *rule = nullptr; 82 | if ((isMinimizing || SET_NOT_CONTAINS(context->freeWindows, hwnd)) && 83 | matchRule(context, hwnd, isMinimizing, &rule)) { 84 | bool isFirstTime = SET_NOT_CONTAINS(context->freeWindows, hwnd); 85 | minimizeWindow(context, hwnd); 86 | if (RULE_IS_ALWAYS_NOTIFY(rule->flag) || 87 | RULE_IS_NOTIFY_FIRST_TIME(rule->flag) && isFirstTime) { 88 | 89 | notifyHidingWindow(context, hwnd); 90 | } 91 | } 92 | } 93 | 94 | 95 | void hookWinEvent(TRCONTEXT* context) 96 | { 97 | context->hook = SetWinEventHook( 98 | EVENT_SYSTEM_MINIMIZESTART, 99 | EVENT_OBJECT_NAMECHANGE, 100 | NULL, 101 | context->autoHiding ? AutoHidingWinEventProc : DefaultWinEventProc, 102 | 0, 0, 103 | WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS 104 | ); 105 | } 106 | 107 | void unhookWinEvent(TRCONTEXT* context) 108 | { 109 | if (context->hook) { 110 | UnhookWinEvent(context->hook); 111 | } 112 | } 113 | 114 | -------------------------------------------------------------------------------- /src/winevent.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "traymond.h" 4 | 5 | void hookWinEvent(TRCONTEXT* context); 6 | void unhookWinEvent(TRCONTEXT* context); 7 | DWORD GetWindowExeFileName(HWND hwnd, PTCHAR fileName, DWORD size); 8 | -------------------------------------------------------------------------------- /traymond.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.11.35327.3 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "traymond", "traymond.vcxproj", "{7FDF991D-57D7-48AE-BDAC-8DD9BE849241}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "traymond.locale.en_US", "locale\en_US\en_US.vcxproj", "{EE43692D-0E29-4699-9007-7F1D41BB38D5}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|x64 = Debug|x64 13 | Debug|x86 = Debug|x86 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {7FDF991D-57D7-48AE-BDAC-8DD9BE849241}.Debug|x64.ActiveCfg = Debug|x64 19 | {7FDF991D-57D7-48AE-BDAC-8DD9BE849241}.Debug|x64.Build.0 = Debug|x64 20 | {7FDF991D-57D7-48AE-BDAC-8DD9BE849241}.Debug|x86.ActiveCfg = Debug|Win32 21 | {7FDF991D-57D7-48AE-BDAC-8DD9BE849241}.Debug|x86.Build.0 = Debug|Win32 22 | {7FDF991D-57D7-48AE-BDAC-8DD9BE849241}.Release|x64.ActiveCfg = Release|x64 23 | {7FDF991D-57D7-48AE-BDAC-8DD9BE849241}.Release|x64.Build.0 = Release|x64 24 | {7FDF991D-57D7-48AE-BDAC-8DD9BE849241}.Release|x86.ActiveCfg = Release|Win32 25 | {7FDF991D-57D7-48AE-BDAC-8DD9BE849241}.Release|x86.Build.0 = Release|Win32 26 | {EE43692D-0E29-4699-9007-7F1D41BB38D5}.Debug|x64.ActiveCfg = Debug|x64 27 | {EE43692D-0E29-4699-9007-7F1D41BB38D5}.Debug|x64.Build.0 = Debug|x64 28 | {EE43692D-0E29-4699-9007-7F1D41BB38D5}.Debug|x86.ActiveCfg = Debug|Win32 29 | {EE43692D-0E29-4699-9007-7F1D41BB38D5}.Debug|x86.Build.0 = Debug|Win32 30 | {EE43692D-0E29-4699-9007-7F1D41BB38D5}.Release|x64.ActiveCfg = Release|x64 31 | {EE43692D-0E29-4699-9007-7F1D41BB38D5}.Release|x64.Build.0 = Release|x64 32 | {EE43692D-0E29-4699-9007-7F1D41BB38D5}.Release|x86.ActiveCfg = Release|Win32 33 | {EE43692D-0E29-4699-9007-7F1D41BB38D5}.Release|x86.Build.0 = Release|Win32 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | GlobalSection(ExtensibilityGlobals) = postSolution 39 | SolutionGuid = {2C35F167-6088-4BEE-84A4-473B6072CCA2} 40 | EndGlobalSection 41 | EndGlobal 42 | -------------------------------------------------------------------------------- /traymond.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | Win32 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {7FDF991D-57D7-48AE-BDAC-8DD9BE849241} 24 | Win32Proj 25 | 10.0 26 | 27 | 28 | 29 | Application 30 | true 31 | v143 32 | 33 | 34 | Application 35 | true 36 | v143 37 | 38 | 39 | Application 40 | false 41 | v143 42 | 43 | 44 | Application 45 | false 46 | v143 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | Traymond 68 | 69 | 70 | false 71 | Traymond 72 | 73 | 74 | false 75 | Traymond 76 | 77 | 78 | 79 | 80 | 81 | 82 | Level4 83 | _UNICODE;UNICODE;_DEBUG;DEBUG;%(PreprocessorDefinitions) 84 | 85 | 86 | 87 | "type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'";%(AdditionalManifestDependencies) 88 | comctl32.lib;%(AdditionalDependencies) 89 | 90 | 91 | true 92 | 93 | 94 | 95 | 96 | 97 | 98 | Level4 99 | 100 | 101 | 102 | 103 | 104 | Level4 105 | true 106 | true 107 | _UNICODE;UNICODE;%(PreprocessorDefinitions) 108 | None 109 | 110 | 111 | UseLinkTimeCodeGeneration 112 | 113 | 114 | "type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'";%(AdditionalManifestDependencies) 115 | comctl32.lib;%(AdditionalDependencies) 116 | false 117 | 118 | 119 | true 120 | 121 | 122 | 123 | 124 | 125 | 126 | Level4 127 | true 128 | true 129 | 130 | 131 | UseLinkTimeCodeGeneration 132 | 133 | 134 | true 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /traymond.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | Source Files 29 | 30 | 31 | Source Files 32 | 33 | 34 | Source Files 35 | 36 | 37 | 38 | 39 | Resource Files 40 | 41 | 42 | 43 | 44 | Header Files 45 | 46 | 47 | Header Files 48 | 49 | 50 | Header Files 51 | 52 | 53 | Header Files 54 | 55 | 56 | Header Files 57 | 58 | 59 | Header Files 60 | 61 | 62 | Header Files 63 | 64 | 65 | Header Files 66 | 67 | 68 | 69 | 70 | Resource Files 71 | 72 | 73 | --------------------------------------------------------------------------------