From 4360a45fa6ee0a42656c61fd294be9b878fbc002 Mon Sep 17 00:00:00 2001 From: Matthew Flatt Date: Sun, 17 Oct 2010 15:19:00 -0600 Subject: [PATCH] win32: single-instance support --- collects/mred/private/wx/win32/window.rkt | 64 ++++++++ src/gracket/grmain.c | 181 ++++++++++++++++++++++ 2 files changed, 245 insertions(+) diff --git a/collects/mred/private/wx/win32/window.rkt b/collects/mred/private/wx/win32/window.rkt index eea9ba4218..ab0a742852 100644 --- a/collects/mred/private/wx/win32/window.rkt +++ b/collects/mred/private/wx/win32/window.rkt @@ -2,6 +2,7 @@ (require ffi/unsafe racket/class racket/draw + racket/draw/bstr "../../syntax.rkt" "../common/freeze.rkt" "../common/queue.rkt" @@ -102,6 +103,17 @@ (define-user32 BeginPaint (_wfun _HWND _pointer -> _HDC)) (define-user32 EndPaint (_wfun _HDC _pointer -> _BOOL)) +(define WM_IS_GRACKET (cast (scheme_register_process_global "PLT_WM_IS_GRACKET" #f) + _pointer + _UINT_PTR)) +(define GRACKET_GUID (cast (scheme_register_process_global "PLT_GRACKET_GUID" #f) + _pointer + _bytes)) +(define-cstruct _COPYDATASTRUCT + ([dwData _pointer] + [cbData _DWORD] + [lpData _pointer])) + (defclass window% object% (init-field parent hwnd) (init style @@ -189,6 +201,15 @@ [(= msg WM_DROPFILES) (handle-drop-files wParam) 0] + ;; for single-instance applications: + [(and (= msg WM_IS_GRACKET) + (positive? WM_IS_GRACKET)) + ;; return 79 to indicate that this is a GRacket window + 79] + ;; also for single-instance: + [(= msg WM_COPYDATA) + (handle-copydata lParam) + 0] [else (default w msg wParam lParam)]))) @@ -621,6 +642,49 @@ ;; ---------------------------------------- +(define (handle-copydata lParam) + (let* ([cd (cast lParam _LPARAM _COPYDATASTRUCT-pointer)] + [data (COPYDATASTRUCT-lpData cd)] + [guid-len (bytes-length GRACKET_GUID)] + [data-len (COPYDATASTRUCT-cbData cd)]) + (when (and (data-len + . > . + (+ guid-len (ctype-sizeof _DWORD))) + (bytes=? GRACKET_GUID + (scheme_make_sized_byte_string data + guid-len + 0)) + (bytes=? #"OPEN" + (scheme_make_sized_byte_string (ptr-add data guid-len) + 4 + 0))) + ;; The command line's argv (sans argv[0]) is + ;; expressed as a DWORD for the number of args, + ;; followed by each arg. Each arg is a DWORD + ;; for the number of chars and then the chars + (let ([args + (let ([count (ptr-ref data _DWORD 'abs (+ guid-len 4))]) + (let loop ([i 0] [delta (+ guid-len 4 (ctype-sizeof _DWORD))]) + (if (or (= i count) + ((+ delta (ctype-sizeof _DWORD)) . > . data-len)) + null + (let ([len (ptr-ref (ptr-add data delta) _DWORD)] + [delta (+ delta (ctype-sizeof _DWORD))]) + (if ((+ delta len) . > . data-len) + null + (let ([s (scheme_make_sized_byte_string + (ptr-add data delta) + len + 1)]) + (if (or (bytes=? s #"") + (regexp-match? #rx"\0" s)) + null + (cons (bytes->path s) + (loop (add1 i) (+ delta len))))))))))]) + (map queue-file-event args))))) + +;; ---------------------------------------- + (define (queue-window-event win thunk) (queue-event (send win get-eventspace) thunk)) diff --git a/src/gracket/grmain.c b/src/gracket/grmain.c index e9f2db6feb..22f82f505a 100644 --- a/src/gracket/grmain.c +++ b/src/gracket/grmain.c @@ -13,6 +13,8 @@ char *check_for_another = "yes, please check for another"; static int wx_in_terminal = 0; # define MZ_DEFINE_UTF8_MAIN +# define PRE_FILTER_CMDLINE_ARGUMENTS +static void pre_filter_cmdline_arguments(int *argc, char ***argv); #endif struct Scheme_Env; @@ -508,6 +510,182 @@ static int parse_command_line(char ***_command, char *buf) return count; } +/* ---------------------------------------- */ +/* single-instance detection */ +/* ---------------------------------------- */ + +static char *CreateUniqueName() +{ + char desktop[MAX_PATH], session[32], *together; + int dlen, slen; + + { + // Name should be desktop unique, so add current desktop name + HDESK hDesk; + ULONG cchDesk = MAX_PATH - 1; + + hDesk = GetThreadDesktop(GetCurrentThreadId()); + + if (!GetUserObjectInformation( hDesk, UOI_NAME, desktop, cchDesk, &cchDesk)) + desktop[0] = 0; + else + desktop[MAX_PATH - 1] = 0; + } + + { + // Name should be session unique, so add current session id + HANDLE hToken = NULL; + // Try to open the token (fails on Win9x) and check necessary buffer size + if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) { + DWORD cbBytes = 0; + + if(!GetTokenInformation( hToken, TokenStatistics, NULL, cbBytes, &cbBytes ) + && GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + PTOKEN_STATISTICS pTS; + + pTS = (PTOKEN_STATISTICS)malloc(cbBytes); + + if(GetTokenInformation(hToken, TokenStatistics, (LPVOID)pTS, cbBytes, &cbBytes)) { + sprintf(session, "-%08x%08x-", + pTS->AuthenticationId.HighPart, + pTS->AuthenticationId.LowPart); + } else + session[0] = 0; + free(pTS); + } else { + session[0] = 0; + } + } else + session[0] = 0; + } + + dlen = strlen(desktop); + slen = strlen(session); + together = (char *)malloc(slen + dlen + 1); + memcpy(together, desktop, dlen); + memcpy(together + dlen, session, slen); + together[dlen + slen] = 0; + + return together; +} + +#define GRACKET_GUID "B2261834-D535-44dd-8511-A26FC8F97DD0" + +static int wm_is_gracket; + +static BOOL CALLBACK CheckWindow(HWND wnd, LPARAM param) +{ + int i, len, gl; + DWORD w; + char **argv, *v; + COPYDATASTRUCT cd; + DWORD result; + LRESULT ok; + + ok = SendMessageTimeout(wnd, wm_is_gracket, + 0, 0, + SMTO_BLOCK | + SMTO_ABORTIFHUNG, + 200, + &result); + + printf("try %p result %d\n", wnd, result); + + if (ok == 0) + return TRUE; /* ignore and continue */ + if (result == 79) { + /* found it */ + } else + return TRUE; /* continue search */ + + /* wnd is owned by another instance of this application */ + + SetForegroundWindow(wnd); + if (IsIconic(wnd)) + ShowWindow(wnd, SW_RESTORE); + + argv = (char **)param; + + len = gl = strlen(GRACKET_GUID); + len += 4 + sizeof(DWORD); + for (i = 1; argv[i]; i++) { + len += sizeof(DWORD) + strlen(argv[i]); + } + w = i - 1; + + v = (char *)malloc(len); + memcpy(v, GRACKET_GUID, gl); + memcpy(v + gl, "OPEN", 4); + memcpy(v + gl + 4, &w, sizeof(DWORD)); + len = gl + 4 + sizeof(DWORD); + for (i = 1; argv[i]; i++) { + w = strlen(argv[i]); + memcpy(v + len, &w, sizeof(DWORD)); + len += sizeof(DWORD); + memcpy(v + len, argv[i], w); + len += w; + } + + cd.dwData = 79; + cd.cbData = len; + cd.lpData = v; + + SendMessage(wnd, WM_COPYDATA, (WPARAM)wnd, (LPARAM)&cd); + + free(v); + + return FALSE; +} + +static int CheckSingleInstance(char *normalized_path, char **argv) +{ + /* Check for an existing instance: */ + if (check_for_another[0] != 'n') { + int alreadyrunning; + HANDLE mutex; + int j, l, i; + char *a, *b; + + /* This mutex creation synchronizes multiple instances of + the application that may have been started. */ + j = strlen(normalized_path); + + b = CreateUniqueName(); + l = strlen(b); + a = (char *)malloc(j + l + 50); + memcpy(a, normalized_path, j); + for (i = 0; i < j; i++) { + /* backslashes are not allowed in mutex names */ + if (a[i] == '\\') a[i] = '/'; + } + memcpy(a + j, b, l); + memcpy(a + j + l, "GRacket-" GRACKET_GUID, strlen(GRACKET_GUID) + 9); + mutex = CreateMutex(NULL, FALSE, a); + alreadyrunning = (GetLastError() == ERROR_ALREADY_EXISTS || + GetLastError() == ERROR_ACCESS_DENIED); + /* The call fails with ERROR_ACCESS_DENIED if the Mutex was + created in a different users session because of passing + NULL for the SECURITY_ATTRIBUTES on Mutex creation. */ + wm_is_gracket = RegisterWindowMessage(a); + free(a); + + if (alreadyrunning) { + /* If another instance has been started, try to find it. */ + if (!EnumWindows((WNDENUMPROC)CheckWindow, (LPARAM)argv)) { + return 1; + } + } + } + + return 0; +} + +static void pre_filter_cmdline_arguments(int *argc, char ***argv) +{ + scheme_register_process_global("PLT_WM_IS_GRACKET", (void *)(long)wm_is_gracket); + scheme_register_process_global("PLT_GRACKET_GUID", GRACKET_GUID); +} + /* ---------------------------------------- */ /* command-line parsing */ /* ---------------------------------------- */ @@ -577,6 +755,9 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR ignored } } + if (CheckSingleInstance(normalized_path, argv)) + return 0; + if (!wx_in_terminal) { scheme_set_stdio_makers(MrEdMakeStdIn, MrEdMakeStdOut,