1 /*
2  * Shell Desktop
3  *
4  * Copyright 2008 Thomas Bluemel
5  * Copyright 2020 Katayama Hirofumi MZ
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21 
22 #include "shelldesktop.h"
23 
24 // Support for multiple monitors is disabled till LVM_SETWORKAREAS gets implemented
25 #ifdef MULTIMONITOR_SUPPORT
26 #include <atlcoll.h>
27 #endif
28 
29 WINE_DEFAULT_DEBUG_CHANNEL(desktop);
30 
31 static const WCHAR szProgmanClassName[]  = L"Progman";
32 static const WCHAR szProgmanWindowName[] = L"Program Manager";
33 
34 class CDesktopBrowser :
35     public CWindowImpl<CDesktopBrowser, CWindow, CFrameWinTraits>,
36     public CComObjectRootEx<CComMultiThreadModelNoCS>,
37     public IShellBrowser,
38     public IServiceProvider
39 {
40 private:
41     HACCEL m_hAccel;
42     HWND m_hWndShellView;
43     CComPtr<IShellDesktopTray> m_Tray;
44     CComPtr<IShellView>        m_ShellView;
45 
46     CComPtr<IOleWindow>        m_ChangeNotifyServer;
47     HWND                       m_hwndChangeNotifyServer;
48 
49     LRESULT _NotifyTray(UINT uMsg, WPARAM wParam, LPARAM lParam);
50     HRESULT _Resize();
51 
52 public:
53     CDesktopBrowser();
54     ~CDesktopBrowser();
55     HRESULT Initialize(IShellDesktopTray *ShellDeskx);
56 
57     // *** IOleWindow methods ***
58     virtual HRESULT STDMETHODCALLTYPE GetWindow(HWND *lphwnd);
59     virtual HRESULT STDMETHODCALLTYPE ContextSensitiveHelp(BOOL fEnterMode);
60 
61     // *** IShellBrowser methods ***
62     virtual HRESULT STDMETHODCALLTYPE InsertMenusSB(HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths);
63     virtual HRESULT STDMETHODCALLTYPE SetMenuSB(HMENU hmenuShared, HOLEMENU holemenuRes, HWND hwndActiveObject);
64     virtual HRESULT STDMETHODCALLTYPE RemoveMenusSB(HMENU hmenuShared);
65     virtual HRESULT STDMETHODCALLTYPE SetStatusTextSB(LPCOLESTR pszStatusText);
66     virtual HRESULT STDMETHODCALLTYPE EnableModelessSB(BOOL fEnable);
67     virtual HRESULT STDMETHODCALLTYPE TranslateAcceleratorSB(MSG *pmsg, WORD wID);
68     virtual HRESULT STDMETHODCALLTYPE BrowseObject(LPCITEMIDLIST pidl, UINT wFlags);
69     virtual HRESULT STDMETHODCALLTYPE GetViewStateStream(DWORD grfMode, IStream **ppStrm);
70     virtual HRESULT STDMETHODCALLTYPE GetControlWindow(UINT id, HWND *lphwnd);
71     virtual HRESULT STDMETHODCALLTYPE SendControlMsg(UINT id, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *pret);
72     virtual HRESULT STDMETHODCALLTYPE QueryActiveShellView(struct IShellView **ppshv);
73     virtual HRESULT STDMETHODCALLTYPE OnViewWindowActive(struct IShellView *ppshv);
74     virtual HRESULT STDMETHODCALLTYPE SetToolbarItems(LPTBBUTTON lpButtons, UINT nButtons, UINT uFlags);
75 
76     // *** IServiceProvider methods ***
77     virtual HRESULT STDMETHODCALLTYPE QueryService(REFGUID guidService, REFIID riid, void **ppvObject);
78 
79     // message handlers
80     LRESULT OnEraseBkgnd(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
81     LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
82     LRESULT OnSettingChange(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
83     LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
84     LRESULT OnOpenNewWindow(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
85     LRESULT OnCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
86     LRESULT OnSetFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
87     LRESULT OnGetChangeNotifyServer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
88 
89 DECLARE_WND_CLASS_EX(szProgmanClassName, CS_DBLCLKS, COLOR_DESKTOP)
90 
91 BEGIN_MSG_MAP(CBaseBar)
92     MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
93     MESSAGE_HANDLER(WM_SIZE, OnSize)
94     MESSAGE_HANDLER(WM_SYSCOLORCHANGE, OnSettingChange)
95     MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChange)
96     MESSAGE_HANDLER(WM_CLOSE, OnClose)
97     MESSAGE_HANDLER(WM_EXPLORER_OPEN_NEW_WINDOW, OnOpenNewWindow)
98     MESSAGE_HANDLER(WM_COMMAND, OnCommand)
99     MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
100     MESSAGE_HANDLER(WM_DESKTOP_GET_CNOTIFY_SERVER, OnGetChangeNotifyServer)
101 END_MSG_MAP()
102 
103 BEGIN_COM_MAP(CDesktopBrowser)
104     COM_INTERFACE_ENTRY_IID(IID_IOleWindow, IOleWindow)
105     COM_INTERFACE_ENTRY_IID(IID_IShellBrowser, IShellBrowser)
106     COM_INTERFACE_ENTRY_IID(IID_IServiceProvider, IServiceProvider)
107 END_COM_MAP()
108 };
109 
110 CDesktopBrowser::CDesktopBrowser():
111     m_hAccel(NULL),
112     m_hWndShellView(NULL),
113     m_hwndChangeNotifyServer(NULL)
114 {
115 }
116 
117 CDesktopBrowser::~CDesktopBrowser()
118 {
119     if (m_ShellView.p != NULL && m_hWndShellView != NULL)
120     {
121         m_ShellView->DestroyViewWindow();
122     }
123 
124     if (m_hwndChangeNotifyServer)
125     {
126         ::DestroyWindow(m_hwndChangeNotifyServer);
127     }
128 }
129 
130 #ifdef MULTIMONITOR_SUPPORT
131 BOOL CALLBACK MonitorEnumProc(
132   _In_ HMONITOR hMonitor,
133   _In_ HDC      hdcMonitor,
134   _In_ LPRECT   lprcMonitor,
135   _In_ LPARAM   dwData
136 )
137 {
138     CAtlList<RECT> *list = (CAtlList<RECT>*)dwData;
139     MONITORINFO MonitorInfo;
140     MonitorInfo.cbSize = sizeof(MonitorInfo);
141     if (::GetMonitorInfoW(hMonitor, &MonitorInfo))
142     {
143         list->AddTail(MonitorInfo.rcWork);
144     }
145 
146     return TRUE;
147 }
148 #endif
149 
150 HRESULT CDesktopBrowser::_Resize()
151 {
152     RECT rcNewSize;
153 
154 #ifdef MULTIMONITOR_SUPPORT
155 
156     UINT cMonitors = GetSystemMetrics(SM_CMONITORS);
157     if (cMonitors == 1)
158     {
159         SystemParametersInfoW(SPI_GETWORKAREA, 0, &rcNewSize, 0);
160     }
161     else
162     {
163         SetRect(&rcNewSize,
164                 GetSystemMetrics(SM_XVIRTUALSCREEN),
165                 GetSystemMetrics(SM_YVIRTUALSCREEN),
166                 GetSystemMetrics(SM_XVIRTUALSCREEN) + GetSystemMetrics(SM_CXVIRTUALSCREEN),
167                 GetSystemMetrics(SM_YVIRTUALSCREEN) + GetSystemMetrics(SM_CYVIRTUALSCREEN));
168     }
169 
170     ::MoveWindow(m_hWnd, rcNewSize.left, rcNewSize.top, rcNewSize.right - rcNewSize.left, rcNewSize.bottom - rcNewSize.top, TRUE);
171     ::MoveWindow(m_hWndShellView, 0, 0, rcNewSize.right - rcNewSize.left, rcNewSize.bottom - rcNewSize.top, TRUE);
172 
173     if (cMonitors != 1)
174     {
175         CAtlList<RECT> list;
176         EnumDisplayMonitors(NULL, NULL, MonitorEnumProc, (LPARAM)&list);
177         RECT* prcWorkAreas = new RECT[list.GetCount()];
178         int i = 0;
179         for (POSITION it = list.GetHeadPosition(); it; list.GetNext(it))
180             prcWorkAreas[i++] = list.GetAt(it);
181 
182         HWND hwndListView = FindWindowExW(m_hWndShellView, NULL, WC_LISTVIEW, NULL);
183 
184         ::SendMessageW(hwndListView, LVM_SETWORKAREAS , i, (LPARAM)prcWorkAreas);
185     }
186 
187 #else
188      SystemParametersInfoW(SPI_GETWORKAREA, 0, &rcNewSize, 0);
189     ::MoveWindow(m_hWnd, rcNewSize.left, rcNewSize.top, rcNewSize.right - rcNewSize.left, rcNewSize.bottom - rcNewSize.top, TRUE);
190     ::MoveWindow(m_hWndShellView, 0, 0, rcNewSize.right - rcNewSize.left, rcNewSize.bottom - rcNewSize.top, TRUE);
191 
192 #endif
193     return S_OK;
194 }
195 
196 HRESULT CDesktopBrowser::Initialize(IShellDesktopTray *ShellDesk)
197 {
198     CComPtr<IShellFolder> psfDesktop;
199     HRESULT hRet;
200     hRet = SHGetDesktopFolder(&psfDesktop);
201     if (FAILED_UNEXPECTEDLY(hRet))
202         return hRet;
203 
204     m_Tray = ShellDesk;
205 
206     Create(NULL, NULL, szProgmanWindowName, WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, WS_EX_TOOLWINDOW);
207     if (!m_hWnd)
208         return E_FAIL;
209 
210     CSFV csfv = {sizeof(CSFV), psfDesktop};
211     hRet = SHCreateShellFolderViewEx(&csfv, &m_ShellView);
212     if (FAILED_UNEXPECTEDLY(hRet))
213         return hRet;
214 
215     m_Tray->RegisterDesktopWindow(m_hWnd);
216     if (FAILED_UNEXPECTEDLY(hRet))
217         return hRet;
218 
219     FOLDERSETTINGS fs;
220     RECT rcShellView = {0,0,0,0};
221     fs.ViewMode = FVM_ICON;
222     fs.fFlags = FWF_DESKTOP | FWF_NOCLIENTEDGE | FWF_NOSCROLL | FWF_TRANSPARENT;
223     hRet = m_ShellView->CreateViewWindow(NULL, &fs, (IShellBrowser *)this, &rcShellView, &m_hWndShellView);
224     if (FAILED_UNEXPECTEDLY(hRet))
225         return hRet;
226 
227     _Resize();
228 
229     HWND hwndListView = FindWindowExW(m_hWndShellView, NULL, WC_LISTVIEW, NULL);
230 
231     m_hAccel = LoadAcceleratorsW(shell32_hInstance, MAKEINTRESOURCEW(IDA_DESKBROWSER));
232 
233 #if 1
234     /* A Windows8+ specific hack */
235     ::ShowWindow(m_hWndShellView, SW_SHOW);
236     ::ShowWindow(hwndListView, SW_SHOW);
237 #endif
238     ShowWindow(SW_SHOW);
239     UpdateWindow();
240 
241     return hRet;
242 }
243 
244 HRESULT STDMETHODCALLTYPE CDesktopBrowser::GetWindow(HWND *lphwnd)
245 {
246     if (lphwnd == NULL)
247         return E_POINTER;
248     *lphwnd = m_hWnd;
249     return S_OK;
250 }
251 
252 HRESULT STDMETHODCALLTYPE CDesktopBrowser::ContextSensitiveHelp(BOOL fEnterMode)
253 {
254     return E_NOTIMPL;
255 }
256 
257 HRESULT STDMETHODCALLTYPE CDesktopBrowser::InsertMenusSB(HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths)
258 {
259     return E_NOTIMPL;
260 }
261 
262 HRESULT STDMETHODCALLTYPE CDesktopBrowser::SetMenuSB(HMENU hmenuShared, HOLEMENU holemenuRes, HWND hwndActiveObject)
263 {
264     return E_NOTIMPL;
265 }
266 
267 HRESULT STDMETHODCALLTYPE CDesktopBrowser::RemoveMenusSB(HMENU hmenuShared)
268 {
269     return E_NOTIMPL;
270 }
271 
272 HRESULT STDMETHODCALLTYPE CDesktopBrowser::SetStatusTextSB(LPCOLESTR lpszStatusText)
273 {
274     return E_NOTIMPL;
275 }
276 
277 HRESULT STDMETHODCALLTYPE CDesktopBrowser::EnableModelessSB(BOOL fEnable)
278 {
279     return E_NOTIMPL;
280 }
281 
282 HRESULT STDMETHODCALLTYPE CDesktopBrowser::TranslateAcceleratorSB(LPMSG lpmsg, WORD wID)
283 {
284     if (!::TranslateAcceleratorW(m_hWnd, m_hAccel, lpmsg))
285         return S_FALSE;
286     return S_OK;
287 }
288 
289 HRESULT STDMETHODCALLTYPE CDesktopBrowser::BrowseObject(LPCITEMIDLIST pidl, UINT wFlags)
290 {
291     /*
292      * We should use IShellWindows interface here in order to attempt to
293      * find an open shell window that shows the requested pidl and activate it
294      */
295 
296     DWORD dwFlags = ((wFlags & SBSP_EXPLOREMODE) != 0) ? SH_EXPLORER_CMDLINE_FLAG_E : 0;
297     return SHOpenNewFrame(ILClone(pidl), NULL, 0, dwFlags);
298 }
299 
300 HRESULT STDMETHODCALLTYPE CDesktopBrowser::GetViewStateStream(DWORD grfMode, IStream **ppStrm)
301 {
302     return E_NOTIMPL;
303 }
304 
305 HRESULT STDMETHODCALLTYPE CDesktopBrowser::GetControlWindow(UINT id, HWND *lphwnd)
306 {
307     if (lphwnd == NULL)
308         return E_POINTER;
309     return E_NOTIMPL;
310 }
311 
312 HRESULT STDMETHODCALLTYPE CDesktopBrowser::SendControlMsg(UINT id, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *pret)
313 {
314     if (pret == NULL)
315         return E_POINTER;
316     return E_NOTIMPL;
317 }
318 
319 HRESULT STDMETHODCALLTYPE CDesktopBrowser::QueryActiveShellView(IShellView **ppshv)
320 {
321     if (ppshv == NULL)
322         return E_POINTER;
323     *ppshv = m_ShellView;
324     if (m_ShellView != NULL)
325         m_ShellView->AddRef();
326 
327     return S_OK;
328 }
329 
330 HRESULT STDMETHODCALLTYPE CDesktopBrowser::OnViewWindowActive(IShellView *ppshv)
331 {
332     return E_NOTIMPL;
333 }
334 
335 HRESULT STDMETHODCALLTYPE CDesktopBrowser::SetToolbarItems(LPTBBUTTON lpButtons, UINT nButtons, UINT uFlags)
336 {
337     return E_NOTIMPL;
338 }
339 
340 HRESULT STDMETHODCALLTYPE CDesktopBrowser::QueryService(REFGUID guidService, REFIID riid, PVOID *ppv)
341 {
342     /* FIXME - handle guidService */
343     return QueryInterface(riid, ppv);
344 }
345 
346 LRESULT CDesktopBrowser::_NotifyTray(UINT uMsg, WPARAM wParam, LPARAM lParam)
347 {
348     HWND hWndTray;
349     HRESULT hRet;
350 
351     hRet = m_Tray->GetTrayWindow(&hWndTray);
352     if (SUCCEEDED(hRet))
353         ::PostMessageW(hWndTray, uMsg, wParam, lParam);
354 
355     return 0;
356 }
357 
358 LRESULT CDesktopBrowser::OnCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
359 {
360     switch (LOWORD(wParam))
361     {
362         case FCIDM_DESKBROWSER_CLOSE:
363             return _NotifyTray(TWM_DOEXITWINDOWS, 0, 0);
364         case FCIDM_DESKBROWSER_FOCUS:
365             if (GetKeyState(VK_SHIFT))
366                 return _NotifyTray(TWM_CYCLEFOCUS, 1, 0xFFFFFFFF);
367             else
368                 return _NotifyTray(TWM_CYCLEFOCUS, 1, 1);
369         case FCIDM_DESKBROWSER_SEARCH:
370             SHFindFiles(NULL, NULL);
371             break;
372         case FCIDM_DESKBROWSER_REFRESH:
373             if (m_ShellView)
374                 m_ShellView->Refresh();
375             break;
376     }
377 
378     return 0;
379 }
380 
381 
382 LRESULT CDesktopBrowser::OnEraseBkgnd(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
383 {
384     return (LRESULT)PaintDesktop((HDC)wParam);
385 }
386 
387 LRESULT CDesktopBrowser::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
388 {
389     if (wParam == SIZE_MINIMIZED)
390     {
391         /* Hey, we're the desktop!!! */
392         ::ShowWindow(m_hWnd, SW_RESTORE);
393     }
394 
395     ::InvalidateRect(m_hWndShellView, NULL, TRUE);
396 
397     return 0;
398 }
399 
400 LRESULT CDesktopBrowser::OnSettingChange(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
401 {
402     if (uMsg == WM_SETTINGCHANGE /* == WM_WININICHANGE */ &&
403         lstrcmpiW((LPCWSTR)lParam, L"Environment") == 0)
404     {
405         LPVOID lpEnvironment;
406         RegenerateUserEnvironment(&lpEnvironment, TRUE);
407     }
408 
409     if (m_hWndShellView)
410     {
411         /* Forward the message */
412         ::SendMessageW(m_hWndShellView, uMsg, wParam, lParam);
413     }
414 
415     if (uMsg == WM_SETTINGCHANGE && wParam == SPI_SETWORKAREA && m_hWndShellView != NULL)
416     {
417         _Resize();
418     }
419 
420     return 0;
421 }
422 
423 LRESULT CDesktopBrowser::OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
424 {
425     return _NotifyTray(TWM_DOEXITWINDOWS, 0, 0);
426 }
427 
428 LRESULT CDesktopBrowser::OnOpenNewWindow(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
429 {
430     TRACE("Proxy Desktop message 1035 received.\n");
431     SHOnCWMCommandLine((HANDLE)lParam);
432     return 0;
433 }
434 
435 LRESULT CDesktopBrowser::OnSetFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
436 {
437     ::SetFocus(m_hWndShellView);
438     return 0;
439 }
440 
441 // Message WM_DESKTOP_GET_CNOTIFY_SERVER: Get or create the change notification server.
442 //   wParam: BOOL bCreate; The flag whether it creates or not.
443 //   lParam: Ignored.
444 //   return: The window handle of the server window.
445 LRESULT CDesktopBrowser::OnGetChangeNotifyServer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
446 {
447     BOOL bCreate = (BOOL)wParam;
448     if (bCreate && !::IsWindow(m_hwndChangeNotifyServer))
449     {
450         HRESULT hres = CChangeNotifyServer_CreateInstance(IID_PPV_ARG(IOleWindow, &m_ChangeNotifyServer));
451         if (FAILED_UNEXPECTEDLY(hres))
452             return NULL;
453 
454         hres = m_ChangeNotifyServer->GetWindow(&m_hwndChangeNotifyServer);
455         if (FAILED_UNEXPECTEDLY(hres))
456             return NULL;
457     }
458     return (LRESULT)m_hwndChangeNotifyServer;
459 }
460 
461 HRESULT CDesktopBrowser_CreateInstance(IShellDesktopTray *Tray, REFIID riid, void **ppv)
462 {
463     return ShellObjectCreatorInit<CDesktopBrowser, IShellDesktopTray*>(Tray, riid, ppv);
464 }
465 
466 /*************************************************************************
467  * SHCreateDesktop            [SHELL32.200]
468  *
469  */
470 HANDLE WINAPI SHCreateDesktop(IShellDesktopTray *Tray)
471 {
472     if (Tray == NULL)
473     {
474         SetLastError(ERROR_INVALID_PARAMETER);
475         return NULL;
476     }
477 
478     CComPtr<IShellBrowser> Browser;
479     HRESULT hr = CDesktopBrowser_CreateInstance(Tray, IID_PPV_ARG(IShellBrowser, &Browser));
480     if (FAILED_UNEXPECTEDLY(hr))
481         return NULL;
482 
483     return static_cast<HANDLE>(Browser.Detach());
484 }
485 
486 /*************************************************************************
487  * SHCreateDesktop            [SHELL32.201]
488  *
489  */
490 BOOL WINAPI SHDesktopMessageLoop(HANDLE hDesktop)
491 {
492     if (hDesktop == NULL)
493     {
494         SetLastError(ERROR_INVALID_PARAMETER);
495         return FALSE;
496     }
497 
498     MSG Msg;
499     BOOL bRet;
500 
501     CComPtr<IShellBrowser> browser;
502     CComPtr<IShellView> shellView;
503 
504     browser.Attach(static_cast<IShellBrowser*>(hDesktop));
505     HRESULT hr = browser->QueryActiveShellView(&shellView);
506     if (FAILED_UNEXPECTEDLY(hr))
507         return FALSE;
508 
509     while ((bRet = ::GetMessageW(&Msg, NULL, 0, 0)) != 0)
510     {
511         if (bRet != -1)
512         {
513             if (shellView->TranslateAcceleratorW(&Msg) != S_OK)
514             {
515                 ::TranslateMessage(&Msg);
516                 ::DispatchMessageW(&Msg);
517             }
518         }
519     }
520 
521     return TRUE;
522 }
523