1 /* 2 * Copyright 2003, 2004, 2005 Martin Fuchs 3 * 4 * This library is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU Lesser General Public 6 * License as published by the Free Software Foundation; either 7 * version 2.1 of the License, or (at your option) any later version. 8 * 9 * This library is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 * Lesser General Public License for more details. 13 * 14 * You should have received a copy of the GNU Lesser General Public 15 * License along with this library; if not, write to the Free Software 16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 */ 18 19 20 // 21 // Explorer clone 22 // 23 // taskbar.cpp 24 // 25 // Martin Fuchs, 16.08.2003 26 // 27 28 29 #include <precomp.h> 30 31 #include "taskbar.h" 32 #include "traynotify.h" // for NOTIFYAREA_WIDTH_DEF 33 34 35 DynamicFct<BOOL (WINAPI*)(HWND hwnd)> g_SetTaskmanWindow(TEXT("user32"), "SetTaskmanWindow"); 36 DynamicFct<BOOL (WINAPI*)(HWND hwnd)> g_RegisterShellHookWindow(TEXT("user32"), "RegisterShellHookWindow"); 37 DynamicFct<BOOL (WINAPI*)(HWND hwnd)> g_DeregisterShellHookWindow(TEXT("user32"), "DeregisterShellHookWindow"); 38 39 /* 40 DynamicFct<BOOL (WINAPI*)(HWND hWnd, DWORD dwType)> g_RegisterShellHook(TEXT("shell32"), (LPCSTR)0xb5); 41 42 // constants for RegisterShellHook() 43 #define RSH_UNREGISTER 0 44 #define RSH_REGISTER 1 45 #define RSH_REGISTER_PROGMAN 2 46 #define RSH_REGISTER_TASKMAN 3 47 */ 48 49 50 TaskBarEntry::TaskBarEntry() 51 { 52 _id = 0; 53 _hbmp = 0; 54 _bmp_idx = 0; 55 _used = 0; 56 _btn_idx = 0; 57 _fsState = 0; 58 } 59 60 TaskBarMap::~TaskBarMap() 61 { 62 while(!empty()) { 63 iterator it = begin(); 64 DeleteBitmap(it->second._hbmp); 65 erase(it); 66 } 67 } 68 69 70 TaskBar::TaskBar(HWND hwnd) 71 : super(hwnd), 72 WM_SHELLHOOK(RegisterWindowMessage(WINMSG_SHELLHOOK)) 73 { 74 _last_btn_width = 0; 75 76 _mmMetrics_org.cbSize = sizeof(MINIMIZEDMETRICS); 77 78 SystemParametersInfo(SPI_GETMINIMIZEDMETRICS, sizeof(_mmMetrics_org), &_mmMetrics_org, 0); 79 80 // configure the window manager to hide windows when they are minimized 81 // This is neccessary to enable shell hook messages. 82 if (!(_mmMetrics_org.iArrange & ARW_HIDE)) { 83 MINIMIZEDMETRICS _mmMetrics_new = _mmMetrics_org; 84 85 _mmMetrics_new.iArrange |= ARW_HIDE; 86 87 SystemParametersInfo(SPI_SETMINIMIZEDMETRICS, sizeof(_mmMetrics_new), &_mmMetrics_new, 0); 88 } 89 } 90 91 TaskBar::~TaskBar() 92 { 93 // if (g_RegisterShellHook) 94 // (*g_RegisterShellHook)(_hwnd, RSH_UNREGISTER); 95 96 if (g_DeregisterShellHookWindow) 97 (*g_DeregisterShellHookWindow)(_hwnd); 98 else 99 KillTimer(_hwnd, 0); 100 101 if (g_SetTaskmanWindow) 102 (*g_SetTaskmanWindow)(0); 103 104 SystemParametersInfo(SPI_GETMINIMIZEDMETRICS, sizeof(_mmMetrics_org), &_mmMetrics_org, 0); 105 } 106 107 HWND TaskBar::Create(HWND hwndParent) 108 { 109 ClientRect clnt(hwndParent); 110 111 int taskbar_pos = 80; // This start position will be adjusted in DesktopBar::Resize(). 112 113 return Window::Create(WINDOW_CREATOR(TaskBar), 0, 114 BtnWindowClass(CLASSNAME_TASKBAR), TITLE_TASKBAR, 115 WS_CHILD|WS_VISIBLE | CCS_TOP|CCS_NODIVIDER|CCS_NORESIZE, 116 taskbar_pos, 0, clnt.right-taskbar_pos-(NOTIFYAREA_WIDTH_DEF+1), clnt.bottom, hwndParent); 117 } 118 119 LRESULT TaskBar::Init(LPCREATESTRUCT pcs) 120 { 121 if (super::Init(pcs)) 122 return 1; 123 124 /* FIXME: There's an internal padding for non-flat toolbar. Get rid of it somehow. */ 125 _htoolbar = CreateToolbarEx(_hwnd, 126 WS_CHILD|WS_VISIBLE|WS_CLIPSIBLINGS|WS_CLIPCHILDREN| 127 CCS_TOP|CCS_NODIVIDER|TBSTYLE_LIST|TBSTYLE_TOOLTIPS|TBSTYLE_WRAPABLE,//|TBSTYLE_AUTOSIZE 128 IDW_TASKTOOLBAR, 0, 0, 0, NULL, 0, 0, 0, 16, 16, sizeof(TBBUTTON)); 129 130 SendMessage(_htoolbar, TB_SETBUTTONWIDTH, 0, MAKELONG(TASKBUTTONWIDTH_MAX,TASKBUTTONWIDTH_MAX)); 131 //SendMessage(_htoolbar, TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_MIXEDBUTTONS); 132 //SendMessage(_htoolbar, TB_SETDRAWTEXTFLAGS, DT_CENTER|DT_VCENTER, DT_CENTER|DT_VCENTER); 133 //SetWindowFont(_htoolbar, GetStockFont(ANSI_VAR_FONT), FALSE); 134 //SendMessage(_htoolbar, TB_SETPADDING, 0, MAKELPARAM(8,8)); 135 136 // set metrics for the Taskbar toolbar to enable button spacing 137 TBMETRICS metrics; 138 139 metrics.cbSize = sizeof(TBMETRICS); 140 metrics.dwMask = TBMF_BARPAD | TBMF_BUTTONSPACING; 141 metrics.cxBarPad = 0; 142 metrics.cyBarPad = 0; 143 metrics.cxButtonSpacing = 3; 144 metrics.cyButtonSpacing = 3; 145 146 SendMessage(_htoolbar, TB_SETMETRICS, 0, (LPARAM)&metrics); 147 148 _next_id = IDC_FIRST_APP; 149 150 // register the taskbar window as task manager window to make the following call to RegisterShellHookWindow working 151 if (g_SetTaskmanWindow) 152 (*g_SetTaskmanWindow)(_hwnd); 153 154 if (g_RegisterShellHookWindow) { 155 LOG(TEXT("Using shell hooks for notification of shell events.")); 156 157 (*g_RegisterShellHookWindow)(_hwnd); 158 } else { 159 LOG(TEXT("Shell hooks not available.")); 160 161 SetTimer(_hwnd, 0, 200, NULL); 162 } 163 164 /* Alternatively we could use the RegisterShellHook() function in SHELL32, but this is not yet implemented in the WINE code. 165 if (g_RegisterShellHook) { 166 (*g_RegisterShellHook)(0, RSH_REGISTER); 167 168 if ((HIWORD(GetVersion())>>14) == W_VER_NT) 169 (*g_RegisterShellHook)(_hwnd, RSH_REGISTER_TASKMAN); 170 else 171 (*g_RegisterShellHook)(_hwnd, RSH_REGISTER); 172 } 173 */ 174 Refresh(); 175 176 return 0; 177 } 178 179 LRESULT TaskBar::WndProc(UINT nmsg, WPARAM wparam, LPARAM lparam) 180 { 181 switch(nmsg) { 182 case WM_SIZE: 183 SendMessage(_htoolbar, WM_SIZE, 0, 0); 184 ResizeButtons(); 185 break; 186 187 case WM_TIMER: 188 Refresh(); 189 return 0; 190 191 case WM_CONTEXTMENU: { 192 Point pt(lparam); 193 ScreenToClient(_htoolbar, &pt); 194 195 if ((HWND)wparam==_htoolbar && SendMessage(_htoolbar, TB_HITTEST, 0, (LPARAM)&pt)>=0) 196 break; // avoid displaying context menu for application button _and_ desktop bar at the same time 197 198 goto def;} 199 200 case PM_GET_LAST_ACTIVE: 201 return (LRESULT)(HWND)_last_foreground_wnd; 202 203 case WM_SYSCOLORCHANGE: 204 SendMessage(_htoolbar, WM_SYSCOLORCHANGE, 0, 0); 205 break; 206 207 default: def: 208 if (nmsg == WM_SHELLHOOK) { 209 switch(wparam) { 210 case HSHELL_WINDOWCREATED: 211 case HSHELL_WINDOWDESTROYED: 212 case HSHELL_WINDOWACTIVATED: 213 case HSHELL_REDRAW: 214 #ifdef HSHELL_FLASH 215 case HSHELL_FLASH: 216 #endif 217 #ifdef HSHELL_RUDEAPPACTIVATED 218 case HSHELL_RUDEAPPACTIVATED: 219 #endif 220 Refresh(); 221 break; 222 } 223 } else { 224 return super::WndProc(nmsg, wparam, lparam); 225 } 226 } 227 228 return 0; 229 } 230 231 int TaskBar::Command(int id, int code) 232 { 233 TaskBarMap::iterator found = _map.find_id(id); 234 235 if (found != _map.end()) { 236 ActivateApp(found); 237 return 0; 238 } 239 240 return super::Command(id, code); 241 } 242 243 int TaskBar::Notify(int id, NMHDR* pnmh) 244 { 245 if (pnmh->hwndFrom == _htoolbar) 246 switch(pnmh->code) { 247 case NM_RCLICK: { 248 TBBUTTONINFO btninfo; 249 TaskBarMap::iterator it; 250 Point pt(GetMessagePos()); 251 ScreenToClient(_htoolbar, &pt); 252 253 btninfo.cbSize = sizeof(TBBUTTONINFO); 254 btninfo.dwMask = TBIF_BYINDEX|TBIF_COMMAND; 255 256 int idx = SendMessage(_htoolbar, TB_HITTEST, 0, (LPARAM)&pt); 257 258 if (idx>=0 && 259 SendMessage(_htoolbar, TB_GETBUTTONINFO, idx, (LPARAM)&btninfo)!=-1 && 260 (it=_map.find_id(btninfo.idCommand))!=_map.end()) { 261 //TaskBarEntry& entry = it->second; 262 263 ActivateApp(it, false, false); // don't restore minimized windows on right button click 264 265 static DynamicFct<DWORD(STDAPICALLTYPE*)(RESTRICTIONS)> pSHRestricted(TEXT("SHELL32"), "SHRestricted"); 266 267 if (pSHRestricted && !(*pSHRestricted)(REST_NOTRAYCONTEXTMENU)) 268 ShowAppSystemMenu(it); 269 } 270 break;} 271 272 default: 273 return super::Notify(id, pnmh); 274 } 275 276 return 0; 277 } 278 279 280 void TaskBar::ActivateApp(TaskBarMap::iterator it, bool can_minimize, bool can_restore) 281 { 282 HWND hwnd = it->first; 283 284 bool minimize_it = can_minimize && !IsIconic(hwnd) && 285 (hwnd==GetForegroundWindow() || hwnd==_last_foreground_wnd); 286 287 // switch to selected application window 288 if (can_restore && !minimize_it) 289 if (IsIconic(hwnd)) 290 PostMessage(hwnd, WM_SYSCOMMAND, SC_RESTORE, 0); 291 292 // In case minimize_it is true, we _have_ to switch to the app before 293 // posting SW_MINIMIZE to be compatible with some applications (e.g. "Sleipnir") 294 SetForegroundWindow(hwnd); 295 296 if (minimize_it) { 297 PostMessage(hwnd, WM_SYSCOMMAND, SC_MINIMIZE, 0); 298 _last_foreground_wnd = 0; 299 } else 300 _last_foreground_wnd = hwnd; 301 302 Refresh(); 303 } 304 305 void TaskBar::ShowAppSystemMenu(TaskBarMap::iterator it) 306 { 307 HMENU hmenu = GetSystemMenu(it->first, FALSE); 308 309 if (hmenu) { 310 POINT pt; 311 312 GetCursorPos(&pt); 313 int cmd = TrackPopupMenu(hmenu, TPM_LEFTBUTTON|TPM_RIGHTBUTTON|TPM_RETURNCMD, pt.x, pt.y, 0, _hwnd, NULL); 314 315 if (cmd) { 316 ActivateApp(it, false, false); // reactivate window after the context menu has closed 317 PostMessage(it->first, WM_SYSCOMMAND, cmd, 0); 318 } 319 } 320 } 321 322 323 HICON get_window_icon_small(HWND hwnd) 324 { 325 HICON hIcon = 0; 326 327 SendMessageTimeout(hwnd, WM_GETICON, ICON_SMALL2, 0, SMTO_ABORTIFHUNG, 1000, (PDWORD_PTR)&hIcon); 328 329 if (!hIcon) 330 SendMessageTimeout(hwnd, WM_GETICON, ICON_SMALL, 0, SMTO_ABORTIFHUNG, 1000, (PDWORD_PTR)&hIcon); 331 332 if (!hIcon) 333 SendMessageTimeout(hwnd, WM_GETICON, ICON_BIG, 0, SMTO_ABORTIFHUNG, 1000, (PDWORD_PTR)&hIcon); 334 335 if (!hIcon) 336 hIcon = (HICON)GetClassLongPtr(hwnd, GCL_HICONSM); 337 338 if (!hIcon) 339 hIcon = (HICON)GetClassLongPtr(hwnd, GCL_HICON); 340 341 if (!hIcon) 342 SendMessageTimeout(hwnd, WM_QUERYDRAGICON, 0, 0, 0, 1000, (PDWORD_PTR)&hIcon); 343 344 return hIcon; 345 } 346 347 HICON get_window_icon_big(HWND hwnd, bool allow_from_class) 348 { 349 HICON hIcon = 0; 350 351 SendMessageTimeout(hwnd, WM_GETICON, ICON_BIG, 0, SMTO_ABORTIFHUNG, 1000, (PDWORD_PTR)&hIcon); 352 353 if (!hIcon) 354 SendMessageTimeout(hwnd, WM_GETICON, ICON_SMALL2, 0, SMTO_ABORTIFHUNG, 1000, (PDWORD_PTR)&hIcon); 355 356 if (!hIcon) 357 SendMessageTimeout(hwnd, WM_GETICON, ICON_SMALL, 0, SMTO_ABORTIFHUNG, 1000, (PDWORD_PTR)&hIcon); 358 359 if (allow_from_class) { 360 if (!hIcon) 361 hIcon = (HICON)GetClassLongPtr(hwnd, GCL_HICON); 362 363 if (!hIcon) 364 hIcon = (HICON)GetClassLongPtr(hwnd, GCL_HICONSM); 365 } 366 367 if (!hIcon) 368 SendMessageTimeout(hwnd, WM_QUERYDRAGICON, 0, 0, 0, 1000, (PDWORD_PTR)&hIcon); 369 370 return hIcon; 371 } 372 373 // fill task bar with buttons for enumerated top level windows 374 BOOL CALLBACK TaskBar::EnumWndProc(HWND hwnd, LPARAM lparam) 375 { 376 TaskBar* pThis = (TaskBar*)lparam; 377 378 DWORD style = GetWindowStyle(hwnd); 379 DWORD ex_style = GetWindowExStyle(hwnd); 380 381 if ((style&WS_VISIBLE) && !(ex_style&WS_EX_TOOLWINDOW) && 382 !GetParent(hwnd) && !GetWindow(hwnd,GW_OWNER)) { 383 TCHAR title[BUFFER_LEN]; 384 385 if (!GetWindowText(hwnd, title, BUFFER_LEN)) 386 title[0] = '\0'; 387 388 TaskBarMap::iterator found = pThis->_map.find(hwnd); 389 int last_id = 0; 390 391 if (found != pThis->_map.end()) { 392 last_id = found->second._id; 393 394 if (!last_id) 395 found->second._id = pThis->_next_id++; 396 } else { 397 HBITMAP hbmp; 398 HICON hIcon = get_window_icon_small(hwnd); 399 BOOL delete_icon = FALSE; 400 401 if (!hIcon) { 402 hIcon = LoadIcon(0, IDI_APPLICATION); 403 delete_icon = TRUE; 404 } 405 406 if (hIcon) { 407 hbmp = create_bitmap_from_icon(hIcon, GetSysColorBrush(COLOR_BTNFACE), WindowCanvas(pThis->_htoolbar)); 408 if (delete_icon) 409 DestroyIcon(hIcon); // some icons can be freed, some not - so ignore any error return of DestroyIcon() 410 } else 411 hbmp = 0; 412 413 TBADDBITMAP ab = {0, (UINT_PTR)hbmp}; 414 int bmp_idx = SendMessage(pThis->_htoolbar, TB_ADDBITMAP, 1, (LPARAM)&ab); 415 416 TaskBarEntry entry; 417 418 entry._id = pThis->_next_id++; 419 entry._hbmp = hbmp; 420 entry._bmp_idx = bmp_idx; 421 entry._title = title; 422 423 pThis->_map[hwnd] = entry; 424 found = pThis->_map.find(hwnd); 425 } 426 427 TBBUTTON btn = {-2/*I_IMAGENONE*/, 0, TBSTATE_ENABLED/*|TBSTATE_ELLIPSES*/, BTNS_BUTTON, {0, 0}, 0, 0}; 428 TaskBarEntry& entry = found->second; 429 430 ++entry._used; 431 btn.idCommand = entry._id; 432 433 HWND foreground = GetForegroundWindow(); 434 HWND foreground_owner = GetWindow(foreground, GW_OWNER); 435 436 if (hwnd==foreground || hwnd==foreground_owner) { 437 btn.fsState |= TBSTATE_PRESSED|TBSTATE_CHECKED; 438 pThis->_last_foreground_wnd = hwnd; 439 } 440 441 if (!last_id) { 442 // create new toolbar buttons for new windows 443 if (title[0]) 444 btn.iString = (INT_PTR)title; 445 446 btn.iBitmap = entry._bmp_idx; 447 entry._btn_idx = SendMessage(pThis->_htoolbar, TB_BUTTONCOUNT, 0, 0); 448 449 SendMessage(pThis->_htoolbar, TB_INSERTBUTTON, entry._btn_idx, (LPARAM)&btn); 450 451 pThis->ResizeButtons(); 452 } else { 453 // refresh attributes of existing buttons 454 if (btn.fsState != entry._fsState) 455 SendMessage(pThis->_htoolbar, TB_SETSTATE, entry._id, MAKELONG(btn.fsState,0)); 456 457 if (entry._title != title) { 458 TBBUTTONINFO info; 459 460 info.cbSize = sizeof(TBBUTTONINFO); 461 info.dwMask = TBIF_TEXT; 462 info.pszText = title; 463 464 SendMessage(pThis->_htoolbar, TB_SETBUTTONINFO, entry._id, (LPARAM)&info); 465 466 entry._title = title; 467 } 468 } 469 470 entry._fsState = btn.fsState; 471 472 #ifdef __REACTOS__ // now handled by activating the ARW_HIDE flag with SystemParametersInfo(SPI_SETMINIMIZEDMETRICS) 473 // move minimized windows out of sight 474 if (IsIconic(hwnd)) { 475 RECT rect; 476 477 GetWindowRect(hwnd, &rect); 478 479 if (rect.bottom > 0) 480 SetWindowPos(hwnd, 0, -32000, -32000, 0, 0, SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE); 481 } 482 #endif 483 } 484 485 return TRUE; 486 } 487 488 void TaskBar::Refresh() 489 { 490 for(TaskBarMap::iterator it=_map.begin(); it!=_map.end(); ++it) 491 it->second._used = 0; 492 493 EnumWindows(EnumWndProc, (LPARAM)this); 494 //EnumDesktopWindows(GetThreadDesktop(GetCurrentThreadId()), EnumWndProc, (LPARAM)_htoolbar); 495 496 set<int> btn_idx_to_delete; 497 set<HBITMAP> hbmp_to_delete; 498 499 for(TaskBarMap::iterator it=_map.begin(); it!=_map.end(); ++it) { 500 TaskBarEntry& entry = it->second; 501 502 if (!entry._used && entry._id) { 503 // store button indexes to remove 504 btn_idx_to_delete.insert(entry._btn_idx); 505 hbmp_to_delete.insert(entry._hbmp); 506 entry._id = 0; 507 } 508 } 509 510 if (!btn_idx_to_delete.empty()) { 511 // remove buttons from right to left 512 for(set<int>::reverse_iterator it=btn_idx_to_delete.rbegin(); it!=btn_idx_to_delete.rend(); ++it) { 513 int idx = *it; 514 515 if (!SendMessage(_htoolbar, TB_DELETEBUTTON, idx, 0)) 516 MessageBoxW(NULL, L"failed to delete button", NULL, MB_OK); 517 518 519 for(TaskBarMap::iterator it2=_map.begin(); it2!=_map.end(); ++it2) { 520 TaskBarEntry& entry = it2->second; 521 522 // adjust button indexes 523 if (entry._btn_idx > idx) { 524 --entry._btn_idx; 525 #if 0 526 --entry._bmp_idx; 527 528 TBBUTTONINFO info; 529 530 info.cbSize = sizeof(TBBUTTONINFO); 531 info.dwMask = TBIF_IMAGE; 532 info.iImage = entry._bmp_idx; 533 534 if (!SendMessage(_htoolbar, TB_SETBUTTONINFO, entry._id, (LPARAM)&info)) 535 MessageBoxW(NULL, L"failed to set button info", NULL, MB_OK); 536 #endif 537 } 538 } 539 540 } 541 542 for(set<HBITMAP>::iterator it=hbmp_to_delete.begin(); it!=hbmp_to_delete.end(); ++it) { 543 HBITMAP hbmp = *it; 544 #if 0 545 TBREPLACEBITMAP tbrepl = {0, (UINT_PTR)hbmp, 0, 0}; 546 SendMessage(_htoolbar, TB_REPLACEBITMAP, 0, (LPARAM)&tbrepl); 547 #endif 548 DeleteObject(hbmp); 549 550 for(TaskBarMap::iterator it=_map.begin(); it!=_map.end(); ++it) 551 if (it->second._hbmp == hbmp) { 552 _map.erase(it); 553 break; 554 } 555 } 556 557 ResizeButtons(); 558 } 559 } 560 561 TaskBarMap::iterator TaskBarMap::find_id(int id) 562 { 563 for(iterator it=begin(); it!=end(); ++it) 564 if (it->second._id == id) 565 return it; 566 567 return end(); 568 } 569 570 void TaskBar::ResizeButtons() 571 { 572 int btns = _map.size(); 573 574 if (btns > 0) { 575 int bar_width = ClientRect(_hwnd).right; 576 int btn_width = (bar_width / btns) - 3; 577 578 if (btn_width < TASKBUTTONWIDTH_MIN) 579 btn_width = TASKBUTTONWIDTH_MIN; 580 else if (btn_width > TASKBUTTONWIDTH_MAX) 581 btn_width = TASKBUTTONWIDTH_MAX; 582 583 if (btn_width != _last_btn_width) { 584 _last_btn_width = btn_width; 585 586 SendMessage(_htoolbar, TB_SETBUTTONWIDTH, 0, MAKELONG(btn_width,btn_width)); 587 SendMessage(_htoolbar, TB_AUTOSIZE, 0, 0); 588 } 589 } 590 } 591