win32: single-instance support

This commit is contained in:
Matthew Flatt 2010-10-17 15:19:00 -06:00
parent 045da06ace
commit 4360a45fa6
2 changed files with 245 additions and 0 deletions

View File

@ -2,6 +2,7 @@
(require ffi/unsafe (require ffi/unsafe
racket/class racket/class
racket/draw racket/draw
racket/draw/bstr
"../../syntax.rkt" "../../syntax.rkt"
"../common/freeze.rkt" "../common/freeze.rkt"
"../common/queue.rkt" "../common/queue.rkt"
@ -102,6 +103,17 @@
(define-user32 BeginPaint (_wfun _HWND _pointer -> _HDC)) (define-user32 BeginPaint (_wfun _HWND _pointer -> _HDC))
(define-user32 EndPaint (_wfun _HDC _pointer -> _BOOL)) (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% (defclass window% object%
(init-field parent hwnd) (init-field parent hwnd)
(init style (init style
@ -189,6 +201,15 @@
[(= msg WM_DROPFILES) [(= msg WM_DROPFILES)
(handle-drop-files wParam) (handle-drop-files wParam)
0] 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 [else
(default w msg wParam lParam)]))) (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) (define (queue-window-event win thunk)
(queue-event (send win get-eventspace) thunk)) (queue-event (send win get-eventspace) thunk))

View File

@ -13,6 +13,8 @@
char *check_for_another = "yes, please check for another"; char *check_for_another = "yes, please check for another";
static int wx_in_terminal = 0; static int wx_in_terminal = 0;
# define MZ_DEFINE_UTF8_MAIN # define MZ_DEFINE_UTF8_MAIN
# define PRE_FILTER_CMDLINE_ARGUMENTS
static void pre_filter_cmdline_arguments(int *argc, char ***argv);
#endif #endif
struct Scheme_Env; struct Scheme_Env;
@ -508,6 +510,182 @@ static int parse_command_line(char ***_command, char *buf)
return count; 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 */ /* 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) { if (!wx_in_terminal) {
scheme_set_stdio_makers(MrEdMakeStdIn, scheme_set_stdio_makers(MrEdMakeStdIn,
MrEdMakeStdOut, MrEdMakeStdOut,