1/*
2 *Copyright (C) 2003-2004 Harold L Hunt II All Rights Reserved.
3 *
4 *Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 *"Software"), to deal in the Software without restriction, including
7 *without limitation the rights to use, copy, modify, merge, publish,
8 *distribute, sublicense, and/or sell copies of the Software, and to
9 *permit persons to whom the Software is furnished to do so, subject to
10 *the following conditions:
11 *
12 *The above copyright notice and this permission notice shall be
13 *included in all copies or substantial portions of the Software.
14 *
15 *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 *EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 *MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 *NONINFRINGEMENT. IN NO EVENT SHALL HAROLD L HUNT II BE LIABLE FOR
19 *ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
20 *CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 *WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 *
23 *Except as contained in this notice, the name of Harold L Hunt II
24 *shall not be used in advertising or otherwise to promote the sale, use
25 *or other dealings in this Software without prior written authorization
26 *from Harold L Hunt II.
27 *
28 * Authors:	Harold L Hunt II
29 *              Earle F. Philhower III
30 */
31
32#ifdef HAVE_XWIN_CONFIG_H
33#include <xwin-config.h>
34#endif
35#include "win.h"
36#include <shellapi.h>
37#include "winprefs.h"
38
39/*
40 * Local function prototypes
41 */
42
43static INT_PTR CALLBACK
44winExitDlgProc(HWND hDialog, UINT message, WPARAM wParam, LPARAM lParam);
45
46static INT_PTR CALLBACK
47winChangeDepthDlgProc(HWND hDialog, UINT message, WPARAM wParam, LPARAM lParam);
48
49static INT_PTR CALLBACK
50winAboutDlgProc(HWND hDialog, UINT message, WPARAM wParam, LPARAM lParam);
51
52static void
53 winDrawURLWindow(LPARAM lParam);
54
55static LRESULT CALLBACK
56winURLWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
57
58static void
59 winOverrideURLButton(HWND hdlg, int id);
60
61static void
62 winUnoverrideURLButton(HWND hdlg, int id);
63
64/*
65 * Owner-draw a button as a URL
66 */
67
68static void
69winDrawURLWindow(LPARAM lParam)
70{
71    DRAWITEMSTRUCT *draw;
72    char str[256];
73    RECT rect;
74    HFONT font;
75    COLORREF crText;
76
77    draw = (DRAWITEMSTRUCT *) lParam;
78    GetWindowText(draw->hwndItem, str, sizeof(str));
79    str[255] = 0;
80    GetClientRect(draw->hwndItem, &rect);
81
82    /* Color the button depending upon its state */
83    if (draw->itemState & ODS_SELECTED)
84        crText = RGB(128 + 64, 0, 0);
85    else if (draw->itemState & ODS_FOCUS)
86        crText = RGB(0, 128 + 64, 0);
87    else
88        crText = RGB(0, 0, 128 + 64);
89    SetTextColor(draw->hDC, crText);
90
91    /* Create font 8 high, standard dialog font */
92    font = CreateFont(-8, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE,
93                      0, 0, 0, 0, 0, "MS Sans Serif");
94    if (!font) {
95        ErrorF("winDrawURLWindow: Unable to create URL font, bailing.\n");
96        return;
97    }
98    /* Draw it */
99    SetBkMode(draw->hDC, OPAQUE);
100    SelectObject(draw->hDC, font);
101    DrawText(draw->hDC, str, strlen(str), &rect, DT_LEFT | DT_VCENTER);
102    /* Delete the created font, replace it with stock font */
103    DeleteObject(SelectObject(draw->hDC, GetStockObject(ANSI_VAR_FONT)));
104}
105
106/*
107 * WndProc for overridden buttons
108 */
109
110static LRESULT CALLBACK
111winURLWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
112{
113    WNDPROC origCB = NULL;
114    HCURSOR cursor;
115
116    /* If it's a SetCursor message, tell it to the hand */
117    if (msg == WM_SETCURSOR) {
118        cursor = LoadCursor(NULL, IDC_HAND);
119        if (cursor)
120            SetCursor(cursor);
121        return TRUE;
122    }
123    origCB = (WNDPROC) GetWindowLongPtr(hwnd, GWLP_USERDATA);
124    /* Otherwise fall through to original WndProc */
125    if (origCB)
126        return CallWindowProc(origCB, hwnd, msg, wParam, lParam);
127    else
128        return FALSE;
129}
130
131/*
132 * Register and unregister the custom WndProc
133 */
134
135static void
136winOverrideURLButton(HWND hwnd, int id)
137{
138    WNDPROC origCB;
139
140    origCB = (WNDPROC) SetWindowLongPtr(GetDlgItem(hwnd, id),
141                                        GWLP_WNDPROC, (LONG_PTR) winURLWndProc);
142    SetWindowLongPtr(GetDlgItem(hwnd, id), GWLP_USERDATA, (LONG_PTR) origCB);
143}
144
145static void
146winUnoverrideURLButton(HWND hwnd, int id)
147{
148    WNDPROC origCB;
149
150    origCB = (WNDPROC) SetWindowLongPtr(GetDlgItem(hwnd, id), GWLP_USERDATA, 0);
151    if (origCB)
152        SetWindowLongPtr(GetDlgItem(hwnd, id), GWLP_WNDPROC, (LONG_PTR) origCB);
153}
154
155/*
156 * Center a dialog window in the desktop window
157 * and set small and large icons to X icons.
158 */
159
160static void
161winInitDialog(HWND hwndDlg)
162{
163    HWND hwndDesk;
164    RECT rc, rcDlg, rcDesk;
165    HICON hIcon, hIconSmall;
166
167    hwndDesk = GetParent(hwndDlg);
168    if (!hwndDesk || IsIconic(hwndDesk))
169        hwndDesk = GetDesktopWindow();
170
171    /* Remove minimize and maximize buttons */
172    SetWindowLongPtr(hwndDlg, GWL_STYLE, GetWindowLongPtr(hwndDlg, GWL_STYLE)
173                     & ~(WS_MAXIMIZEBOX | WS_MINIMIZEBOX));
174
175    /* Set Window not to show in the task bar */
176    SetWindowLongPtr(hwndDlg, GWL_EXSTYLE,
177                     GetWindowLongPtr(hwndDlg, GWL_EXSTYLE) & ~WS_EX_APPWINDOW);
178
179    /* Center dialog window in the screen. Not done for multi-monitor systems, where
180     * it is likely to end up split across the screens. In that case, it appears
181     * near the Tray icon.
182     */
183    if (GetSystemMetrics(SM_CMONITORS) > 1) {
184        /* Still need to refresh the frame change. */
185        SetWindowPos(hwndDlg, HWND_TOPMOST, 0, 0, 0, 0,
186                     SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
187    }
188    else {
189        GetWindowRect(hwndDesk, &rcDesk);
190        GetWindowRect(hwndDlg, &rcDlg);
191        CopyRect(&rc, &rcDesk);
192
193        OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top);
194        OffsetRect(&rc, -rc.left, -rc.top);
195        OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom);
196
197        SetWindowPos(hwndDlg,
198                     HWND_TOPMOST,
199                     rcDesk.left + (rc.right / 2),
200                     rcDesk.top + (rc.bottom / 2),
201                     0, 0, SWP_NOSIZE | SWP_FRAMECHANGED);
202    }
203
204    if (g_hIconX)
205        hIcon = g_hIconX;
206    else
207        hIcon = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_XWIN));
208
209    if (g_hSmallIconX)
210        hIconSmall = g_hSmallIconX;
211    else
212        hIconSmall = LoadImage(g_hInstance,
213                               MAKEINTRESOURCE(IDI_XWIN), IMAGE_ICON,
214                               GetSystemMetrics(SM_CXSMICON),
215                               GetSystemMetrics(SM_CYSMICON), LR_SHARED);
216
217    PostMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM) hIcon);
218    PostMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM) hIconSmall);
219}
220
221/*
222 * Display the Exit dialog box
223 */
224
225void
226winDisplayExitDialog(winPrivScreenPtr pScreenPriv)
227{
228    int i;
229    int liveClients = 0;
230
231    /* Count up running clients (clients[0] is serverClient) */
232    for (i = 1; i < currentMaxClients; i++)
233        if (clients[i] != NullClient)
234            liveClients++;
235    /* Count down server internal clients */
236    if (pScreenPriv->pScreenInfo->fMultiWindow)
237        liveClients -= 2;       /* multiwindow window manager & XMsgProc  */
238    if (g_fClipboardStarted)
239        liveClients--;          /* clipboard manager */
240
241    /* A user reported that this sometimes drops below zero. just eye-candy. */
242    if (liveClients < 0)
243        liveClients = 0;
244
245    /* Don't show the exit confirmation dialog if SilentExit & no clients,
246       or ForceExit, is enabled */
247    if ((pref.fSilentExit && liveClients <= 0) || pref.fForceExit) {
248        if (g_hDlgExit != NULL) {
249            DestroyWindow(g_hDlgExit);
250            g_hDlgExit = NULL;
251        }
252        PostMessage(pScreenPriv->hwndScreen, WM_GIVEUP, 0, 0);
253        return;
254    }
255
256    pScreenPriv->iConnectedClients = liveClients;
257
258    /* Check if dialog already exists */
259    if (g_hDlgExit != NULL) {
260        /* Dialog box already exists, display it */
261        ShowWindow(g_hDlgExit, SW_SHOWDEFAULT);
262
263        /* User has lost the dialog.  Show them where it is. */
264        SetForegroundWindow(g_hDlgExit);
265
266        return;
267    }
268
269    /* Create dialog box */
270    g_hDlgExit = CreateDialogParam(g_hInstance,
271                                   "EXIT_DIALOG",
272                                   pScreenPriv->hwndScreen,
273                                   winExitDlgProc, (LPARAM) pScreenPriv);
274
275    /* Show the dialog box */
276    ShowWindow(g_hDlgExit, SW_SHOW);
277
278    /* Needed to get keyboard controls (tab, arrows, enter, esc) to work */
279    SetForegroundWindow(g_hDlgExit);
280
281    /* Set focus to the Cancel button */
282    PostMessage(g_hDlgExit, WM_NEXTDLGCTL,
283                (WPARAM) GetDlgItem(g_hDlgExit, IDCANCEL), TRUE);
284}
285
286#define CONNECTED_CLIENTS_FORMAT	"There %s currently %d client%s connected."
287
288/*
289 * Exit dialog window procedure
290 */
291
292static INT_PTR CALLBACK
293winExitDlgProc(HWND hDialog, UINT message, WPARAM wParam, LPARAM lParam)
294{
295    static winPrivScreenPtr s_pScreenPriv = NULL;
296
297    /* Branch on message type */
298    switch (message) {
299    case WM_INITDIALOG:
300    {
301        char *pszConnectedClients;
302
303        /* Store pointers to private structures for future use */
304        s_pScreenPriv = (winPrivScreenPtr) lParam;
305
306        winInitDialog(hDialog);
307
308        /* Format the connected clients string */
309        if (asprintf(&pszConnectedClients, CONNECTED_CLIENTS_FORMAT,
310                     (s_pScreenPriv->iConnectedClients == 1) ? "is" : "are",
311                     s_pScreenPriv->iConnectedClients,
312                     (s_pScreenPriv->iConnectedClients == 1) ? "" : "s") == -1)
313            return TRUE;
314
315        /* Set the number of connected clients */
316        SetWindowText(GetDlgItem(hDialog, IDC_CLIENTS_CONNECTED),
317                      pszConnectedClients);
318        free(pszConnectedClients);
319    }
320        return TRUE;
321
322    case WM_COMMAND:
323        switch (LOWORD(wParam)) {
324        case IDOK:
325            /* Send message to call the GiveUp function */
326            PostMessage(s_pScreenPriv->hwndScreen, WM_GIVEUP, 0, 0);
327            DestroyWindow(g_hDlgExit);
328            g_hDlgExit = NULL;
329
330            /* Fix to make sure keyboard focus isn't trapped */
331            PostMessage(s_pScreenPriv->hwndScreen, WM_NULL, 0, 0);
332            return TRUE;
333
334        case IDCANCEL:
335            DestroyWindow(g_hDlgExit);
336            g_hDlgExit = NULL;
337
338            /* Fix to make sure keyboard focus isn't trapped */
339            PostMessage(s_pScreenPriv->hwndScreen, WM_NULL, 0, 0);
340            return TRUE;
341        }
342        break;
343
344    case WM_MOUSEMOVE:
345    case WM_NCMOUSEMOVE:
346        /* Show the cursor if it is hidden */
347        if (g_fSoftwareCursor && !g_fCursor) {
348            g_fCursor = TRUE;
349            ShowCursor(TRUE);
350        }
351        return TRUE;
352
353    case WM_CLOSE:
354        DestroyWindow(g_hDlgExit);
355        g_hDlgExit = NULL;
356
357        /* Fix to make sure keyboard focus isn't trapped */
358        PostMessage(s_pScreenPriv->hwndScreen, WM_NULL, 0, 0);
359        return TRUE;
360    }
361
362    return FALSE;
363}
364
365/*
366 * Display the Depth Change dialog box
367 */
368
369void
370winDisplayDepthChangeDialog(winPrivScreenPtr pScreenPriv)
371{
372    /* Check if dialog already exists */
373    if (g_hDlgDepthChange != NULL) {
374        /* Dialog box already exists, display it */
375        ShowWindow(g_hDlgDepthChange, SW_SHOWDEFAULT);
376
377        /* User has lost the dialog.  Show them where it is. */
378        SetForegroundWindow(g_hDlgDepthChange);
379
380        return;
381    }
382
383    /*
384     * Display a notification to the user that the visual
385     * will not be displayed until the Windows display depth
386     * is restored to the original value.
387     */
388    g_hDlgDepthChange = CreateDialogParam(g_hInstance,
389                                          "DEPTH_CHANGE_BOX",
390                                          pScreenPriv->hwndScreen,
391                                          winChangeDepthDlgProc,
392                                          (LPARAM) pScreenPriv);
393    /* Show the dialog box */
394    ShowWindow(g_hDlgDepthChange, SW_SHOW);
395
396    if (!g_hDlgDepthChange)
397        ErrorF("winDisplayDepthChangeDialog - GetLastError: %d\n",
398                (int) GetLastError());
399
400    /* Minimize the display window */
401    ShowWindow(pScreenPriv->hwndScreen, SW_MINIMIZE);
402}
403
404/*
405 * Process messages for the dialog that is displayed for
406 * disruptive screen depth changes.
407 */
408
409static INT_PTR CALLBACK
410winChangeDepthDlgProc(HWND hwndDialog, UINT message,
411                      WPARAM wParam, LPARAM lParam)
412{
413    static winPrivScreenPtr s_pScreenPriv = NULL;
414    static winScreenInfo *s_pScreenInfo = NULL;
415
416#if CYGDEBUG
417    winDebug("winChangeDepthDlgProc\n");
418#endif
419
420    /* Branch on message type */
421    switch (message) {
422    case WM_INITDIALOG:
423#if CYGDEBUG
424        winDebug("winChangeDepthDlgProc - WM_INITDIALOG\n");
425#endif
426
427        /* Store pointers to private structures for future use */
428        s_pScreenPriv = (winPrivScreenPtr) lParam;
429        s_pScreenInfo = s_pScreenPriv->pScreenInfo;
430
431#if CYGDEBUG
432        winDebug("winChangeDepthDlgProc - WM_INITDIALOG - s_pScreenPriv: %p, "
433                 "s_pScreenInfo: %p\n",
434                 s_pScreenPriv, s_pScreenInfo);
435#endif
436
437#if CYGDEBUG
438        winDebug("winChangeDepthDlgProc - WM_INITDIALOG - orig bpp: %u, "
439                 "current bpp: %d\n",
440                 (unsigned int)s_pScreenInfo->dwBPP,
441                 GetDeviceCaps(s_pScreenPriv->hdcScreen, BITSPIXEL));
442#endif
443
444        winInitDialog(hwndDialog);
445
446        return TRUE;
447
448    case WM_DISPLAYCHANGE:
449#if CYGDEBUG
450        winDebug("winChangeDepthDlgProc - WM_DISPLAYCHANGE - orig bpp: %u, "
451                 "new bpp: %d\n",
452                 (unsigned int)s_pScreenInfo->dwBPP,
453                 GetDeviceCaps(s_pScreenPriv->hdcScreen, BITSPIXEL));
454#endif
455
456        /* Dismiss the dialog if the display returns to the original depth */
457        if (GetDeviceCaps(s_pScreenPriv->hdcScreen, BITSPIXEL) ==
458            s_pScreenInfo->dwBPP) {
459            ErrorF("winChangeDelthDlgProc - wParam == s_pScreenInfo->dwBPP\n");
460
461            /* Depth has been restored, dismiss dialog */
462            DestroyWindow(g_hDlgDepthChange);
463            g_hDlgDepthChange = NULL;
464
465            /* Flag that we have a valid screen depth */
466            s_pScreenPriv->fBadDepth = FALSE;
467        }
468        return TRUE;
469
470    case WM_COMMAND:
471        switch (LOWORD(wParam)) {
472        case IDOK:
473        case IDCANCEL:
474            winDebug("winChangeDepthDlgProc - WM_COMMAND - IDOK or IDCANCEL\n");
475
476            /*
477             * User dismissed the dialog, hide it until the
478             * display mode is restored.
479             */
480            ShowWindow(g_hDlgDepthChange, SW_HIDE);
481            return TRUE;
482        }
483        break;
484
485    case WM_CLOSE:
486        winDebug("winChangeDepthDlgProc - WM_CLOSE\n");
487
488        DestroyWindow(g_hDlgAbout);
489        g_hDlgAbout = NULL;
490
491        /* Fix to make sure keyboard focus isn't trapped */
492        PostMessage(s_pScreenPriv->hwndScreen, WM_NULL, 0, 0);
493        return TRUE;
494    }
495
496    return FALSE;
497}
498
499/*
500 * Display the About dialog box
501 */
502
503void
504winDisplayAboutDialog(winPrivScreenPtr pScreenPriv)
505{
506    /* Check if dialog already exists */
507    if (g_hDlgAbout != NULL) {
508        /* Dialog box already exists, display it */
509        ShowWindow(g_hDlgAbout, SW_SHOWDEFAULT);
510
511        /* User has lost the dialog.  Show them where it is. */
512        SetForegroundWindow(g_hDlgAbout);
513
514        return;
515    }
516
517    /*
518     * Display the about box
519     */
520    g_hDlgAbout = CreateDialogParam(g_hInstance,
521                                    "ABOUT_BOX",
522                                    pScreenPriv->hwndScreen,
523                                    winAboutDlgProc, (LPARAM) pScreenPriv);
524
525    /* Show the dialog box */
526    ShowWindow(g_hDlgAbout, SW_SHOW);
527
528    /* Needed to get keyboard controls (tab, arrows, enter, esc) to work */
529    SetForegroundWindow(g_hDlgAbout);
530
531    /* Set focus to the OK button */
532    PostMessage(g_hDlgAbout, WM_NEXTDLGCTL,
533                (WPARAM) GetDlgItem(g_hDlgAbout, IDOK), TRUE);
534}
535
536/*
537 * Process messages for the about dialog.
538 */
539
540static INT_PTR CALLBACK
541winAboutDlgProc(HWND hwndDialog, UINT message, WPARAM wParam, LPARAM lParam)
542{
543    static winPrivScreenPtr s_pScreenPriv = NULL;
544
545#if CYGDEBUG
546    winDebug("winAboutDlgProc\n");
547#endif
548
549    /* Branch on message type */
550    switch (message) {
551    case WM_INITDIALOG:
552#if CYGDEBUG
553        winDebug("winAboutDlgProc - WM_INITDIALOG\n");
554#endif
555
556        /* Store pointer to private structure for future use */
557        s_pScreenPriv = (winPrivScreenPtr) lParam;
558
559        winInitDialog(hwndDialog);
560
561        /* Override the URL buttons */
562        winOverrideURLButton(hwndDialog, ID_ABOUT_WEBSITE);
563
564        return TRUE;
565
566    case WM_DRAWITEM:
567        /* Draw the URL buttons as needed */
568        winDrawURLWindow(lParam);
569        return TRUE;
570
571    case WM_MOUSEMOVE:
572    case WM_NCMOUSEMOVE:
573        /* Show the cursor if it is hidden */
574        if (g_fSoftwareCursor && !g_fCursor) {
575            g_fCursor = TRUE;
576            ShowCursor(TRUE);
577        }
578        return TRUE;
579
580    case WM_COMMAND:
581        switch (LOWORD(wParam)) {
582        case IDOK:
583        case IDCANCEL:
584            winDebug("winAboutDlgProc - WM_COMMAND - IDOK or IDCANCEL\n");
585
586            DestroyWindow(g_hDlgAbout);
587            g_hDlgAbout = NULL;
588
589            /* Fix to make sure keyboard focus isn't trapped */
590            PostMessage(s_pScreenPriv->hwndScreen, WM_NULL, 0, 0);
591
592            /* Restore window procedures for URL buttons */
593            winUnoverrideURLButton(hwndDialog, ID_ABOUT_WEBSITE);
594
595            return TRUE;
596
597        case ID_ABOUT_WEBSITE:
598        {
599            const char *pszPath = __VENDORDWEBSUPPORT__;
600            INT_PTR iReturn;
601
602            iReturn = (INT_PTR) ShellExecute(NULL,
603                                         "open",
604                                         pszPath, NULL, NULL, SW_MAXIMIZE);
605            if (iReturn < 32) {
606                ErrorF("winAboutDlgProc - WM_COMMAND - ID_ABOUT_WEBSITE - "
607                       "ShellExecute failed: %d\n", (int)iReturn);
608
609            }
610        }
611            return TRUE;
612        }
613        break;
614
615    case WM_CLOSE:
616        winDebug("winAboutDlgProc - WM_CLOSE\n");
617
618        DestroyWindow(g_hDlgAbout);
619        g_hDlgAbout = NULL;
620
621        /* Fix to make sure keyboard focus isn't trapped */
622        PostMessage(s_pScreenPriv->hwndScreen, WM_NULL, 0, 0);
623
624        /* Restore window procedures for URL buttons */
625        winUnoverrideURLButton(hwndDialog, ID_ABOUT_WEBSITE);
626
627        return TRUE;
628    }
629
630    return FALSE;
631}
632