/* * Copyright 2003, 2004, 2005 Martin Fuchs * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ // // Explorer clone // // desktop.cpp // // Martin Fuchs, 09.08.2003 // #include #include "../resource.h" #include "../taskbar/desktopbar.h" #include "../taskbar/taskbar.h" // for PM_GET_LAST_ACTIVE static BOOL (WINAPI*SetShellWindow)(HWND); static BOOL (WINAPI*SetShellWindowEx)(HWND, HWND); #ifdef _USE_HDESK Desktop::Desktop(HDESK hdesktop/*, HWINSTA hwinsta*/) : _hdesktop(hdesktop) // _hwinsta(hwinsta) { } Desktop::~Desktop() { if (_hdesktop) CloseDesktop(_hdesktop); // if (_hwinsta) // CloseWindowStation(_hwinsta); if (_pThread.get()) { _pThread->Stop(); _pThread.release(); } } #endif Desktops::Desktops() : _current_desktop(0) { } Desktops::~Desktops() { // show all hidden windows for(iterator it_dsk=begin(); it_dsk!=end(); ++it_dsk) for(WindowSet::iterator it=it_dsk->_windows.begin(); it!=it_dsk->_windows.end(); ++it) ShowWindowAsync(*it, SW_SHOW); } void Desktops::init() { resize(DESKTOP_COUNT); #ifdef _USE_HDESK DesktopPtr& desktop = (*this)[0]; desktop = DesktopPtr(new Desktop(OpenInputDesktop(0, FALSE, DESKTOP_SWITCHDESKTOP))); #endif } #ifdef _USE_HDESK void Desktops::SwitchToDesktop(int idx) { if (_current_desktop == idx) return; DesktopPtr& desktop = (*this)[idx]; DesktopThread* pThread = NULL; if (desktop.get()) { if (desktop->_hdesktop) if (!SwitchDesktop(desktop->_hdesktop)) return; } else { FmtString desktop_name(TEXT("Desktop %d"), idx); SECURITY_ATTRIBUTES saAttr = {sizeof(SECURITY_ATTRIBUTES), 0, TRUE}; /* HWINSTA hwinsta = CreateWindowStation(TEXT("ExplorerWinStation"), 0, GENERIC_ALL, &saAttr); if (!SetProcessWindowStation(hwinsta)) return; */ HDESK hdesktop = CreateDesktop(desktop_name, NULL, NULL, 0, GENERIC_ALL, &saAttr); if (!hdesktop) return; desktop = DesktopPtr(new Desktop(hdesktop/*, hwinsta*/)); pThread = new DesktopThread(*desktop); } _current_desktop = idx; if (pThread) { desktop->_pThread = DesktopThreadPtr(pThread); pThread->Start(); } } int DesktopThread::Run() { if (!SetThreadDesktop(_desktop._hdesktop)) return -1; HDESK hDesk_old = OpenInputDesktop(0, FALSE, DESKTOP_SWITCHDESKTOP); if (!SwitchDesktop(_desktop._hdesktop)) return -1; if (!_desktop._hwndDesktop) _desktop._hwndDesktop = DesktopWindow::Create(); int ret = Window::MessageLoop(); SwitchDesktop(hDesk_old); return ret; } #else // _USE_HDESK static BOOL CALLBACK SwitchDesktopEnumFct(HWND hwnd, LPARAM lparam) { WindowSet& windows = *(WindowSet*)lparam; if (hwnd!=g_Globals._hwndDesktopBar && hwnd!=g_Globals._hwndDesktop) if (IsWindowVisible(hwnd)) windows.insert(hwnd); return TRUE; } void Desktops::SwitchToDesktop(int idx) { if (_current_desktop == idx) return; Desktop& old_desktop = (*this)[_current_desktop]; WindowSet& windows = old_desktop._windows; Desktop& desktop = (*this)[idx]; windows.clear(); // collect window handles of all other desktops WindowSet other_wnds; for(const_iterator it1=begin(); it1!=end(); ++it1) for(WindowSet::const_iterator it2=it1->_windows.begin(); it2!=it1->_windows.end(); ++it2) other_wnds.insert(*it2); // save currently visible application windows EnumWindows(SwitchDesktopEnumFct, (LPARAM)&windows); old_desktop._hwndForeground = (HWND)SendMessage(g_Globals._hwndDesktopBar, PM_GET_LAST_ACTIVE, 0, 0); // hide all windows of the previous desktop for(WindowSet::iterator it=windows.begin(); it!=windows.end(); ++it) ShowWindowAsync(*it, SW_HIDE); // show all windows of the new desktop for(WindowSet::iterator it=desktop._windows.begin(); it!=desktop._windows.end(); ++it) ShowWindowAsync(*it, SW_SHOW); if (desktop._hwndForeground) SetForegroundWindow(desktop._hwndForeground); // remove the window handles of the other desktops from what we found on the previous desktop for(WindowSet::const_iterator it=other_wnds.begin(); it!=other_wnds.end(); ++it) windows.erase(*it); // We don't need to store the window handles of what's now visible the now current desktop. desktop._windows.clear(); _current_desktop = idx; } #endif // _USE_HDESK static BOOL CALLBACK MinimizeDesktopEnumFct(HWND hwnd, LPARAM lparam) { list& minimized = *(list*)lparam; if (hwnd!=g_Globals._hwndDesktopBar && hwnd!=g_Globals._hwndDesktop) if (IsWindowVisible(hwnd) && !IsIconic(hwnd)) { RECT rect; if (GetWindowRect(hwnd,&rect)) if (rect.right>0 && rect.bottom>0 && rect.right>rect.left && rect.bottom>rect.top) { minimized.push_back(MinimizeStruct(hwnd, GetWindowStyle(hwnd))); ShowWindowAsync(hwnd, SW_MINIMIZE); } } return TRUE; } /// minimize/restore all windows on the desktop void Desktops::ToggleMinimize() { list& minimized = (*this)[_current_desktop]._minimized; if (minimized.empty()) { EnumWindows(MinimizeDesktopEnumFct, (LPARAM)&minimized); } else { for(list::const_iterator it=minimized.begin(); it!=minimized.end(); ++it) ShowWindowAsync(it->first, it->second&WS_MAXIMIZE? SW_MAXIMIZE: SW_RESTORE); minimized.clear(); } } BOOL IsAnyDesktopRunning() { HINSTANCE hUser32 = GetModuleHandle(TEXT("user32")); SetShellWindow = (BOOL(WINAPI*)(HWND)) GetProcAddress(hUser32, "SetShellWindow"); SetShellWindowEx = (BOOL(WINAPI*)(HWND,HWND)) GetProcAddress(hUser32, "SetShellWindowEx"); return GetShellWindow() != 0; } BackgroundWindow::BackgroundWindow(HWND hwnd) : super(hwnd) { // set background brush for the short moment of displaying the // background color while moving foreground windows SetClassLong(hwnd, GCL_HBRBACKGROUND, COLOR_BACKGROUND+1); _display_version = RegGetDWORDValue(HKEY_CURRENT_USER, TEXT("Control Panel\\Desktop"), TEXT("PaintDesktopVersion"), 1); } LRESULT BackgroundWindow::WndProc(UINT nmsg, WPARAM wparam, LPARAM lparam) { switch(nmsg) { case WM_ERASEBKGND: DrawDesktopBkgnd((HDC)wparam); return TRUE; case WM_MBUTTONDBLCLK: /* Imagelist icons are missing if MainFrame::Create() is called directly from here! explorer_show_frame(SW_SHOWNORMAL); */ PostMessage(g_Globals._hwndDesktop, nmsg, wparam, lparam); break; case PM_DISPLAY_VERSION: if (lparam || wparam) { DWORD or_mask = wparam; DWORD reset_mask = LOWORD(lparam); DWORD xor_mask = HIWORD(lparam); _display_version = ((_display_version&~reset_mask) | or_mask) ^ xor_mask; RegSetDWORDValue(HKEY_CURRENT_USER, TEXT("Control Panel\\Desktop"), TEXT("PaintDesktopVersion"), _display_version); ///@todo Changing the PaintDesktopVersion-Flag needs a restart of the shell -> display a message box InvalidateRect(_hwnd, NULL, TRUE); } return _display_version; default: return super::WndProc(nmsg, wparam, lparam); } return 0; } void BackgroundWindow::DrawDesktopBkgnd(HDC hdc) { PaintDesktop(hdc); /* special solid background HBRUSH bkgndBrush = CreateSolidBrush(RGB(0,32,160)); // dark blue FillRect(hdc, &rect, bkgndBrush); DeleteBrush(bkgndBrush); */ } DesktopWindow::DesktopWindow(HWND hwnd) : super(hwnd) { _pShellView = NULL; } DesktopWindow::~DesktopWindow() { if (_pShellView) _pShellView->Release(); } HWND DesktopWindow::Create() { static IconWindowClass wcDesktop(TEXT("Progman"), IDI_REACTOS, CS_DBLCLKS); /* (disabled because of small ugly temporary artefacts when hiding start menu) wcDesktop.hbrBackground = (HBRUSH)(COLOR_BACKGROUND+1); */ int width = GetSystemMetrics(SM_CXSCREEN); int height = GetSystemMetrics(SM_CYSCREEN); HWND hwndDesktop = Window::Create(WINDOW_CREATOR(DesktopWindow), WS_EX_TOOLWINDOW, wcDesktop, TEXT("Program Manager"), WS_POPUP|WS_VISIBLE, //|WS_CLIPCHILDREN for SDI frames 0, 0, width, height, 0); // work around to display desktop bar in Wine ShowWindow(GET_WINDOW(DesktopWindow, hwndDesktop)->_desktopBar, SW_SHOW); // work around for Windows NT, Win 98, ... // Without this the desktop has mysteriously only a size of 800x600 pixels. MoveWindow(hwndDesktop, 0, 0, width, height, TRUE); return hwndDesktop; } LRESULT DesktopWindow::Init(LPCREATESTRUCT pcs) { if (super::Init(pcs)) return 1; HRESULT hr = GetDesktopFolder()->CreateViewObject(_hwnd, IID_IShellView, (void**)&_pShellView); /* also possible: SFV_CREATE sfv_create; sfv_create.cbSize = sizeof(SFV_CREATE); sfv_create.pshf = GetDesktopFolder(); sfv_create.psvOuter = NULL; sfv_create.psfvcb = NULL; HRESULT hr = SHCreateShellFolderView(&sfv_create, &_pShellView); */ HWND hWndView = 0; if (SUCCEEDED(hr)) { FOLDERSETTINGS fs; fs.ViewMode = FVM_ICON; fs.fFlags = FWF_DESKTOP|FWF_NOCLIENTEDGE|FWF_NOSCROLL|FWF_BESTFITWINDOW|FWF_SNAPTOGRID; //|FWF_AUTOARRANGE; ClientRect rect(_hwnd); hr = _pShellView->CreateViewWindow(NULL, &fs, this, &rect, &hWndView); ///@todo use IShellBrowser::GetViewStateStream() to restore previous view state -> see SHOpenRegStream() if (SUCCEEDED(hr)) { g_Globals._hwndShellView = hWndView; // subclass shellview window new DesktopShellView(hWndView, _pShellView); _pShellView->UIActivate(SVUIA_ACTIVATE_FOCUS); /* IShellView2* pShellView2; hr = _pShellView->QueryInterface(IID_IShellView2, (void**)&pShellView2); SV2CVW2_PARAMS params; params.cbSize = sizeof(SV2CVW2_PARAMS); params.psvPrev = _pShellView; params.pfs = &fs; params.psbOwner = this; params.prcView = ▭ params.pvid = params.pvid;//@@ hr = pShellView2->CreateViewWindow2(¶ms); params.pvid; */ /* IFolderView* pFolderView; hr = _pShellView->QueryInterface(IID_IFolderView, (void**)&pFolderView); if (SUCCEEDED(hr)) { hr = pFolderView->GetAutoArrange(); hr = pFolderView->SetCurrentViewMode(FVM_DETAILS); } */ } } if (hWndView && SetShellWindowEx) SetShellWindowEx(_hwnd, hWndView); else if (SetShellWindow) SetShellWindow(_hwnd); // create the explorer bar _desktopBar = DesktopBar::Create(); g_Globals._hwndDesktopBar = _desktopBar; return 0; } LRESULT DesktopWindow::WndProc(UINT nmsg, WPARAM wparam, LPARAM lparam) { switch(nmsg) { case WM_LBUTTONDBLCLK: case WM_RBUTTONDBLCLK: case WM_MBUTTONDBLCLK: explorer_show_frame(SW_SHOWNORMAL); break; case WM_GETISHELLBROWSER: return (LRESULT)static_cast(this); case WM_DESTROY: ///@todo use IShellBrowser::GetViewStateStream() and _pShellView->SaveViewState() to store view state if (SetShellWindow) SetShellWindow(0); break; case WM_CLOSE: ShowExitWindowsDialog(_hwnd); break; case WM_SYSCOMMAND: if (wparam == SC_TASKLIST) { if (_desktopBar) SendMessage(_desktopBar, nmsg, wparam, lparam); } goto def; case WM_SYSCOLORCHANGE: // redraw background window InvalidateRect(g_Globals._hwndShellView, NULL, TRUE); // forward message to shell view window to redraw icon backgrounds SendMessage(g_Globals._hwndShellView, WM_SYSCOLORCHANGE, wparam, lparam); break; default: def: return super::WndProc(nmsg, wparam, lparam); } return 0; } HRESULT DesktopWindow::OnDefaultCommand(LPIDA pida) { #ifndef ROSSHELL // in shell-only-mode fall through and let shell32 handle the command if (MainFrameBase::OpenShellFolders(pida, 0)) return S_OK; #endif return E_NOTIMPL; } DesktopShellView::DesktopShellView(HWND hwnd, IShellView* pShellView) : super(hwnd), _pShellView(pShellView) { _hwndListView = GetNextWindow(hwnd, GW_CHILD); SetWindowStyle(_hwndListView, GetWindowStyle(_hwndListView)&~LVS_ALIGNMASK);//|LVS_ALIGNTOP|LVS_AUTOARRANGE); // work around for Windows NT, Win 98, ... // Without this the desktop has mysteriously only a size of 800x600 pixels. ClientRect rect(hwnd); MoveWindow(_hwndListView, 0, 0, rect.right, rect.bottom, TRUE); // subclass background window new BackgroundWindow(_hwndListView); _icon_algo = 1; // default icon arrangement PositionIcons(); InitDragDrop(); } bool DesktopShellView::InitDragDrop() { CONTEXT("DesktopShellView::InitDragDrop()"); _pDropTarget = new DesktopDropTarget(_hwnd); if (!_pDropTarget) return false; _pDropTarget->AddRef(); if (FAILED(RegisterDragDrop(_hwnd, _pDropTarget))) { _pDropTarget->Release(); _pDropTarget = NULL; return false; } else _pDropTarget->Release(); FORMATETC ftetc; ftetc.dwAspect = DVASPECT_CONTENT; ftetc.lindex = -1; ftetc.tymed = TYMED_HGLOBAL; ftetc.cfFormat = CF_HDROP; _pDropTarget->AddSuportedFormat(ftetc); return true; } LRESULT DesktopShellView::WndProc(UINT nmsg, WPARAM wparam, LPARAM lparam) { switch(nmsg) { case WM_CONTEXTMENU: if (!DoContextMenu(GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam))) DoDesktopContextMenu(GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam)); break; case PM_SET_ICON_ALGORITHM: _icon_algo = wparam; PositionIcons(); break; case PM_GET_ICON_ALGORITHM: return _icon_algo; case PM_DISPLAY_VERSION: return SendMessage(_hwndListView, nmsg, wparam, lparam); default: return super::WndProc(nmsg, wparam, lparam); } return 0; } int DesktopShellView::Command(int id, int code) { return super::Command(id, code); } int DesktopShellView::Notify(int id, NMHDR* pnmh) { return super::Notify(id, pnmh); } bool DesktopShellView::DoContextMenu(int x, int y) { IDataObject* selection; HRESULT hr = _pShellView->GetItemObject(SVGIO_SELECTION, IID_IDataObject, (void**)&selection); if (FAILED(hr)) return false; PIDList pidList; hr = pidList.GetData(selection); if (FAILED(hr)) { selection->Release(); //CHECKERROR(hr); return false; } LPIDA pida = pidList; if (!pida->cidl) { selection->Release(); return false; } LPCITEMIDLIST parent_pidl = (LPCITEMIDLIST) ((LPBYTE)pida+pida->aoffset[0]); LPCITEMIDLIST* apidl = (LPCITEMIDLIST*) alloca(pida->cidl*sizeof(LPCITEMIDLIST)); for(int i=pida->cidl; i>0; --i) apidl[i-1] = (LPCITEMIDLIST) ((LPBYTE)pida+pida->aoffset[i]); hr = ShellFolderContextMenu(ShellFolder(parent_pidl), _hwnd, pida->cidl, apidl, x, y, _cm_ifs); selection->Release(); if (SUCCEEDED(hr)) refresh(); else CHECKERROR(hr); return true; } HRESULT DesktopShellView::DoDesktopContextMenu(int x, int y) { IContextMenu* pcm; HRESULT hr = DesktopFolder()->GetUIObjectOf(_hwnd, 0, NULL, IID_IContextMenu, NULL, (LPVOID*)&pcm); if (SUCCEEDED(hr)) { pcm = _cm_ifs.query_interfaces(pcm); HMENU hmenu = CreatePopupMenu(); if (hmenu) { hr = pcm->QueryContextMenu(hmenu, 0, FCIDM_SHVIEWFIRST, FCIDM_SHVIEWLAST-1, CMF_NORMAL|CMF_EXPLORE); if (SUCCEEDED(hr)) { AppendMenu(hmenu, MF_SEPARATOR, 0, NULL); AppendMenu(hmenu, 0, FCIDM_SHVIEWLAST-1, ResString(IDS_ABOUT_EXPLORER)); UINT idCmd = TrackPopupMenu(hmenu, TPM_LEFTALIGN|TPM_RETURNCMD|TPM_RIGHTBUTTON, x, y, 0, _hwnd, NULL); _cm_ifs.reset(); if (idCmd == FCIDM_SHVIEWLAST-1) { explorer_about(_hwnd); } else if (idCmd) { CMINVOKECOMMANDINFO cmi; cmi.cbSize = sizeof(CMINVOKECOMMANDINFO); cmi.fMask = 0; cmi.hwnd = _hwnd; cmi.lpVerb = (LPCSTR)(INT_PTR)(idCmd - FCIDM_SHVIEWFIRST); cmi.lpParameters = NULL; cmi.lpDirectory = NULL; cmi.nShow = SW_SHOWNORMAL; cmi.dwHotKey = 0; cmi.hIcon = 0; hr = pcm->InvokeCommand(&cmi); } } else _cm_ifs.reset(); DestroyMenu(hmenu); } pcm->Release(); } return hr; } #define ARRANGE_BORDER_DOWN 8 #define ARRANGE_BORDER_HV 9 #define ARRANGE_ROUNDABOUT 10 static const POINTS s_align_start[] = { {0, 0}, // left/top {0, 0}, {1, 0}, // right/top {1, 0}, {0, 1}, // left/bottom {0, 1}, {1, 1}, // right/bottom {1, 1}, {0, 0}, // left/top {0, 0}, {0, 0} }; static const POINTS s_align_dir1[] = { { 0, +1}, // down {+1, 0}, // right {-1, 0}, // left { 0, +1}, // down { 0, -1}, // up {+1, 0}, // right {-1, 0}, // left { 0, -1}, // up { 0, +1}, // down {+1, 0}, // right {+1, 0} // right }; static const POINTS s_align_dir2[] = { {+1, 0}, // right { 0, +1}, // down { 0, +1}, // down {-1, 0}, // left {+1, 0}, // right { 0, -1}, // up { 0, -1}, // up {-1, 0}, // left {+1, 0}, // right { 0, +1}, // down { 0, +1} // down }; typedef pair IconPos; typedef map IconMap; void DesktopShellView::PositionIcons(int dir) { DWORD spacing = ListView_GetItemSpacing(_hwndListView, FALSE); RECT work_area; SystemParametersInfo(SPI_GETWORKAREA, 0, &work_area, 0); const POINTS& dir1 = s_align_dir1[_icon_algo]; const POINTS& dir2 = s_align_dir2[_icon_algo]; const POINTS& start_pos = s_align_start[_icon_algo]; int dir_x1 = dir1.x; int dir_y1 = dir1.y; int dir_x2 = dir2.x; int dir_y2 = dir2.y; int cx = LOWORD(spacing); int cy = HIWORD(spacing); int dx1 = dir_x1 * cx; int dy1 = dir_y1 * cy; int dx2 = dir_x2 * cx; int dy2 = dir_y2 * cy; int xoffset = (cx-32)/2; int yoffset = 4/*(cy-32)/2*/; int start_x = start_pos.x * (work_area.right - cx) + xoffset; int start_y = start_pos.y * (work_area.bottom - cy) + yoffset; int x = start_x; int y = start_y; int all = ListView_GetItemCount(_hwndListView); int i1, i2; if (dir > 0) { i1 = 0; i2 = all; } else { i1 = all-1; i2 = -1; } IconMap pos_idx; int cnt = 0; int xhv = start_x; int yhv = start_y; for(int idx=i1; idx!=i2; idx+=dir) { pos_idx[IconPos(y, x)] = idx; if (_icon_algo == ARRANGE_BORDER_DOWN) { if (++cnt & 1) x = work_area.right - x - cx + 2*xoffset; else { y += dy1; if (y + cy - 2 * yoffset > work_area.bottom) { y = start_y; start_x += dx2; x = start_x; } } continue; } else if (_icon_algo == ARRANGE_BORDER_HV) { if (++cnt & 1) x = work_area.right - x - cx + 2*xoffset; else if (cnt & 2) { yhv += cy; y = yhv; x = start_x; if (y + cy - 2 * yoffset > work_area.bottom) { start_x += cx; xhv = start_x; x = xhv; start_y += cy; yhv = start_y; y = yhv; } } else { xhv += cx; x = xhv; y = start_y; if (x + cx - 2 * xoffset > work_area.right) { start_x += cx; xhv = start_x; x = xhv; start_y += cy; yhv = start_y; y = yhv; } } continue; } else if (_icon_algo == ARRANGE_ROUNDABOUT) { ///@todo } x += dx1; y += dy1; if (x<0 || x+cx-2*xoffset>work_area.right) { x = start_x; y += dy2; } else if (y<0 || y+cy-2*yoffset>work_area.bottom) { y = start_y; x += dx2; } } // use a little trick to get the icons where we want them to be... //for(IconMap::const_iterator it=pos_idx.end(); --it!=pos_idx.begin(); ) { // const IconPos& pos = it->first; // ListView_SetItemPosition32(_hwndListView, it->second, pos.second, pos.first); //} for(IconMap::const_iterator it=pos_idx.begin(); it!=pos_idx.end(); ++it) { const IconPos& pos = it->first; ListView_SetItemPosition32(_hwndListView, it->second, pos.second, pos.first); } } void DesktopShellView::refresh() { ///@todo }