unit LibDesktop;
interface
uses
Windows, SysUtils, Messages, ShellApi;
const
FEATURE_DEBUG_LOG = FALSE;
function WINDOW_STATION_FindVisibleStation(var Station: HWINSTA) : BOOL;
function WINDOW_STATION_AttachToVisibleStation(var Attached: BOOL; var HomeStation: HWINSTA) : BOOL;
function WINDOW_STATION_AttachToStation(Station: HWINSTA) : BOOL;
// Window를 가지거나 Desktop Hooks 을 가진 쓰레드는 다른 데스크탑으로 Attach 될 수 없다.
function DESKTOP_GetInputDesktopName(NameBuf : PChar; BufLen : Integer) : BOOL; // Current Active Desktop
function DESKTOP_GetThreadDesktopName(NameBuf : PChar; BufLen : Integer; const ThreadID : DWORD = 0) : BOOL;
function DESKTOP_CheckThreadDesktopIsInputDesktop(var Equal : BOOL; const ThreadID : DWORD = 0) : BOOL;
function DESKTOP_AttachThreadToInputDesktop(const ThreadID : DWORD = 0) : BOOL;
function DESKTOP_Select(Handle : HDESK) : HDESK; overload;
function DESKTOP_Select(Name : PChar) : HDESK; overload;
procedure DESKTOP_ForceCtrlAltDel;
function DESKTOP_AttachThreadToInputDesktopOfVisibleStation(const ThreadID : DWORD = 0) : BOOL;
function DESKTOP_RegisterChangeNotification(WND: HWND; MessageID: DWORD) : THandle;
procedure DESKTOP_UnregisterChangeNotification(Handle: THandle);
function DESKTOP_AttachThreadToInputDesktopOfVisibleStation(const ThreadID : DWORD = 0) : BOOL;
var
Attached: BOOL;
OldStation: HWINSTA;
begin
if Win32Platform <> VER_PLATFORM_WIN32_NT then begin
RESULT := TRUE;
Exit;
end;
{$IF FEATURE_DEBUG_LOG}
TRACE('BGN: DESKTOP_AttachThreadToInputDesktopOfVisibleStation');
{$IFEND}
RESULT := WINDOW_STATION_AttachToVisibleStation(Attached, OldStation);
if RESULT and Attached then CloseWindowStation(OldStation);
if RESULT then RESULT := DESKTOP_AttachThreadToInputDesktop(ThreadID);
{$IF FEATURE_DEBUG_LOG}
TRACE('END: DESKTOP_AttachThreadToInputDesktopOfVisibleStation', RESULT);
{$IFEND}
end;
function EnumWindowStationProc(WindowStationName: PChar; var WS: HWINSTA) : BOOL; stdcall;
var
CWS : HWINSTA;
Flag : USEROBJECTFLAGS;
dummy : DWORD;
begin
RESULT := TRUE; // Continue;
CWS := OpenWindowStation(WindowStationName, FALSE, GENERIC_ALL);
if CWS = 0 then Exit;
if (not GetUserObjectInformation(CWS, UOI_FLAGS, @Flag, Sizeof(Flag), dummy)) or
((Flag.dwFlags and WSF_VISIBLE) <> WSF_VISIBLE) then begin
CloseWindowStation(CWS);
Exit;
end;
WS := CWS;
RESULT := FALSE;
end;
function WINDOW_STATION_FindVisibleStation(var Station: HWINSTA) : BOOL;
begin
if Win32Platform <> VER_PLATFORM_WIN32_NT then begin
RESULT := FALSE;
Exit;
end;
{$IF FEATURE_DEBUG_LOG}
TRACE('BGN: WINDOW_STATION_FindVisibleStation');
{$IFEND}
RESULT := (Win32Platform = VER_PLATFORM_WIN32_NT);
if RESULT then begin
Station := 0;
EnumWindowStations(@EnumWindowStationProc, DWORD(@Station));
RESULT := Station <> 0;
end;
{$IF FEATURE_DEBUG_LOG}
TRACE('END: WINDOW_STATION_FindVisibleStation', RESULT);
{$IFEND}
end;
function WINDOW_STATION_AttachToVisibleStation(var Attached: BOOL; var HomeStation: HWINSTA) : BOOL;
var
VWS : HWINSTA;
begin
if Win32Platform <> VER_PLATFORM_WIN32_NT then begin
RESULT := TRUE;
Exit;
end;
{$IF FEATURE_DEBUG_LOG}
TRACE('BGN: WINDOW_STATION_AttachToVisibleStation');
{$IFEND}
if Win32Platform <> VER_PLATFORM_WIN32_NT then begin
Attached := FALSE;
RESULT := TRUE;
Exit;
end;
RESULT := WINDOW_STATION_FindVisibleStation(VWS);
if not RESULT then Exit;
HomeStation := GetProcessWindowStation();
Attached := VWS <> HomeStation;
if not Attached then Exit;
RESULT := SetProcessWindowStation(VWS);
if not RESULT then CloseWindowStation(VWS);
{$IF FEATURE_DEBUG_LOG}
TRACE('END: WINDOW_STATION_AttachToVisibleStation', RESULT);
{$IFEND}
end;
function WINDOW_STATION_AttachToStation(Station: HWINSTA) : BOOL;
var
CWS : HWINSTA;
begin
if Win32Platform <> VER_PLATFORM_WIN32_NT then begin
RESULT := TRUE;
Exit;
end;
{$IF FEATURE_DEBUG_LOG}
TRACE('BGN: WINDOW_STATION_AttachToStation');
{$IFEND}
CWS := GetProcessWindowStation();
RESULT := (CWS = Station);
if not RESULT then begin
RESULT := SetProcessWindowStation(Station);
if RESULT then CloseWindowStation(CWS);
end;
{$IF FEATURE_DEBUG_LOG}
TRACE('END: WINDOW_STATION_AttachToStation', RESULT);
{$IFEND}
end;
function DESKTOP_Select(Handle : HDESK) : HDESK; overload;
var
Buf : Array[0..MAX_PATH] of Char;
dummy : DWORD;
Old : HDESK;
begin
RESULT := 0;
if Win32Platform <> VER_PLATFORM_WIN32_NT then Exit;
Old := GetThreadDesktop(GetCurrentThreadId());
if not GetUserObjectInformation(Handle, UOI_NAME, @(Buf[0]), MAX_PATH, dummy) then Exit;
if not SetThreadDesktop(Handle) then Exit;
RESULT := Old;
end;
function DESKTOP_Select(Name : PChar) : HDESK; overload;
var
DT : HDESK;
begin
RESULT := 0;
if Win32Platform <> VER_PLATFORM_WIN32_NT then Exit;
if Assigned(Name)
then DT := OpenDesktop(Name, 0, FALSE,
DESKTOP_CREATEMENU or DESKTOP_CREATEWINDOW or
DESKTOP_ENUMERATE or DESKTOP_HOOKCONTROL or
DESKTOP_WRITEOBJECTS or DESKTOP_READOBJECTS or
DESKTOP_SWITCHDESKTOP or GENERIC_WRITE)
else DT := OpenInputDesktop(0, FALSE,
DESKTOP_CREATEMENU or DESKTOP_CREATEWINDOW or
DESKTOP_ENUMERATE or DESKTOP_HOOKCONTROL or
DESKTOP_WRITEOBJECTS or DESKTOP_READOBJECTS or
DESKTOP_SWITCHDESKTOP or GENERIC_WRITE);
if DT = 0 then Exit;
RESULT := DESKTOP_Select(DT);
end;
// 현재 상태가 XP의 새로운 로그온 화면이거나, 데스크탑이 전환중인 상태인 경우 OpenInputDesktop은 실패한다.
function DESKTOP_GetInputDesktopName(NameBuf : PChar; BufLen : Integer) : BOOL; // Current Active Desktop
var
DT : HDESK;
dummy : DWORD;
begin
if Win32Platform <> VER_PLATFORM_WIN32_NT then begin
RESULT := FALSE;
Exit;
end;
{$IF FEATURE_DEBUG_LOG}
TRACE('BGN: DESKTOP_GetInputDesktopName');
{$IFEND}
DT := OpenInputDesktop(0, FALSE,
DESKTOP_CREATEMENU or DESKTOP_CREATEWINDOW or
DESKTOP_ENUMERATE or DESKTOP_HOOKCONTROL or
DESKTOP_WRITEOBJECTS or DESKTOP_READOBJECTS or
DESKTOP_SWITCHDESKTOP or GENERIC_WRITE);
RESULT := DT <> 0;
if RESULT then begin
RESULT := GetUserObjectInformation(DT, UOI_NAME, NameBuf, BufLen, dummy);
CloseDesktop(DT);
end;
{$IF FEATURE_DEBUG_LOG}
TRACE('END: DESKTOP_GetInputDesktopName', RESULT);
{$IFEND}
end;
function DESKTOP_GetThreadDesktopHandle(const ThreadID : DWORD = 0) : HDESK;
var
ID : DWORD;
begin
if Win32Platform <> VER_PLATFORM_WIN32_NT then begin
RESULT := 0;
Exit;
end;
{$IF FEATURE_DEBUG_LOG}
TRACE('BGN: DESKTOP_GetThreadDesktopHandle');
{$IFEND}
if ThreadID = 0
then ID := GetCurrentThreadID
else ID := ThreadID;
RESULT := GetThreadDesktop(ID);
// 여기서 리턴되는 값은 CloseDesktop 할 필요없다.
{$IF FEATURE_DEBUG_LOG}
TRACE('END: DESKTOP_GetThreadDesktopHandle', RESULT);
{$IFEND}
end;
function DESKTOP_GetThreadDesktopName(NameBuf : PChar; BufLen : Integer; const ThreadID : DWORD = 0) : BOOL; // Current Active Desktop
var
DT : HDESK;
dummy : DWORD;
begin
if Win32Platform <> VER_PLATFORM_WIN32_NT then begin
RESULT := FALSE;
Exit;
end;
{$IF FEATURE_DEBUG_LOG}
TRACE('BGN: DESKTOP_GetThreadDesktopName');
{$IFEND}
DT := DESKTOP_GetThreadDesktopHandle(ThreadID);
RESULT := (DT <> 0) and GetUserObjectInformation(DT, UOI_NAME, NameBuf, BufLen, dummy);
{$IF FEATURE_DEBUG_LOG}
TRACE('END: DESKTOP_GetThreadDesktopName', RESULT);
{$IFEND}
end;
function DESKTOP_CheckThreadDesktopIsInputDesktop(var Equal : BOOL; const ThreadID : DWORD = 0) : BOOL;
var
CDESK, IDESK : array[0..MAX_PATH-1] of Char;
begin
if Win32Platform <> VER_PLATFORM_WIN32_NT then begin
RESULT := TRUE;
Equal := TRUE;
Exit;
end;
{$IF FEATURE_DEBUG_LOG}
TRACE('BGN: DESKTOP_CheckThreadDesktopIsInputDesktop');
{$IFEND}
RESULT := DESKTOP_GetInputDesktopName(@IDESK[0], MAX_PATH)
and DESKTOP_GetThreadDesktopName(@CDESK[0], MAX_PATH, ThreadID);
if RESULT then Equal := StrComp(CDESK, IDESK) = 0;
{$IF FEATURE_DEBUG_LOG}
TRACE('END: DESKTOP_CheckThreadDesktopIsInputDesktop', RESULT);
{$IFEND}
end;
function DESKTOP_AttachThreadToInputDesktop(const ThreadID : DWORD = 0) : BOOL;
var
CH, IH : HDESK;
CanSkip : BOOL;
begin
if Win32Platform <> VER_PLATFORM_WIN32_NT then begin
RESULT := TRUE;
Exit;
end;
{$IF FEATURE_DEBUG_LOG}
TRACE('BGN: DESKTOP_AttachThreadToInputDesktop');
{$IFEND}
RESULT := DESKTOP_CheckThreadDesktopIsInputDesktop(CanSkip, ThreadID);
if RESULT and (not CanSkip) then begin
IH := OpenInputDesktop(0, FALSE,
DESKTOP_CREATEMENU or DESKTOP_CREATEWINDOW or
DESKTOP_ENUMERATE or DESKTOP_HOOKCONTROL or
DESKTOP_WRITEOBJECTS or DESKTOP_READOBJECTS or
DESKTOP_SWITCHDESKTOP or GENERIC_WRITE);
RESULT := IH <> 0;
if RESULT then begin
CH := DESKTOP_GetThreadDesktopHandle(ThreadID);
RESULT := SetThreadDesktop(IH);
if RESULT
then CloseDesktop(CH) // old desktop handle
else CloseDesktop(IH);
end;
end;
{$IF FEATURE_DEBUG_LOG}
TRACE('END: DESKTOP_AttachThreadToInputDesktop', RESULT);
{$IFEND}
end;
procedure DESKTOP_ForceCtrlAltDel_95;
begin
end;
procedure DESKTOP_ForceCtrlAltDel_NT;
var
DT : HDESK;
Buf : String;
begin
DT := DESKTOP_Select('Winlogon');
if DT<>0
then PostMessage(HWND_BROADCAST, WM_HOTKEY, 0, MAKELONG(MOD_ALT or MOD_CONTROL, VK_DELETE))
else begin
SetLength(Buf, MAX_PATH);
SetLength(Buf, GetSystemDirectory(PChar(Buf), MAX_PATH));
Buf := Buf + '\taskmgr.exe';
if FileExists(Buf) then ShellExecute(0, 'open', PChar(Buf), nil, nil, SW_NORMAL);
SetLength(Buf, 0);
end;
if DT<>0 then DESKTOP_Select(DT);
end;
procedure DESKTOP_ForceCtrlAltDel;
begin
if Win32Platform = VER_PLATFORM_WIN32_NT
then DESKTOP_ForceCtrlAltDel_NT
else DESKTOP_ForceCtrlAltDel_95;
end;
type
PWaitNotifyStruct = ^TWaitNotifyStruct;
TWaitNotifyStruct = packed record
EventHandle : THandle;
Receiver : HWND;
MessageID : DWORD;
ThreadID : DWORD;
ThreadHandle: THandle;
Terminating : BOOL;
end;
function __DESKTOP_RegisterChangeNotificationThread(Para : PWaitNotifyStruct) : DWORD; stdcall;
var
R : DWORD;
begin
while TRUE do begin
R := WaitForSingleObject(Para^.EventHandle, 1000);
if Para^.Terminating then Break;
if R = WAIT_TIMEOUT then Continue;
if R = WAIT_OBJECT_0 then begin
PostMessage(Para^.Receiver, Para^.MessageID, 0, 0);
Continue;
end;
Break;
end;
RESULT := 0;
end;
function DESKTOP_RegisterChangeNotification(WND: HWND; MessageID: DWORD) : THandle;
var
NS : PWaitNotifyStruct;
begin
if Win32Platform <> VER_PLATFORM_WIN32_NT then begin
RESULT := 0;
Exit;
end;
{$IF FEATURE_DEBUG_LOG}
TRACE('BGN: DESKTOP_RegisterChangeNotification');
{$IFEND}
RESULT := OpenEvent(SYNCHRONIZE, FALSE, 'WinSta0_DesktopSwitch');
if RESULT = 0 then begin
{$IF FEATURE_DEBUG_LOG}
TRACE('END: DESKTOP_RegisterChangeNotification', RESULT);
{$IFEND}
Exit;
end;
GetMem(NS, SizeOf(TWaitNotifyStruct));
NS^.EventHandle := RESULT;
NS^.Receiver := WND;
NS^.MessageID := MessageID;
NS^.Terminating := FALSE;
RESULT := CreateThread(nil, 1024, @__DESKTOP_RegisterChangeNotificationThread, NS, 0, NS^.ThreadID);
{$IF FEATURE_DEBUG_LOG}
TRACE('---: DESKTOP_RegisterChangeNotification. ThreadHandle', RESULT);
{$IFEND}
if RESULT = 0 then begin
CloseHandle(NS^.EventHandle);
FreeMem(NS);
end
else begin
NS^.ThreadHandle := RESULT;
RESULT := THandle(NS);
end;
{$IF FEATURE_DEBUG_LOG}
TRACE('END: DESKTOP_RegisterChangeNotification', RESULT);
{$IFEND}
end;
procedure DESKTOP_UnregisterChangeNotification(Handle: THandle);
var
NS : PWaitNotifyStruct;
begin
if Win32Platform <> VER_PLATFORM_WIN32_NT then begin
Exit;
end;
{$IF FEATURE_DEBUG_LOG}
TRACE('BGN: DESKTOP_UnregisterChangeNotification', Handle);
{$IFEND}
if Handle <> 0 then begin
NS := Pointer(Handle);
NS^.Terminating := TRUE;
CloseHandle(NS^.EventHandle);
if WaitForSingleObject(NS^.ThreadHandle, 3000) = WAIT_TIMEOUT then begin
{$IF FEATURE_DEBUG_LOG}
TRACE('---: DESKTOP_UnregisterChangeNotification. Terminate Force!!', NS^.ThreadHandle);
{$IFEND}
TerminateThread(NS^.ThreadHandle, 0);
end;
FreeMem(NS);
end;
{$IF FEATURE_DEBUG_LOG}
TRACE('END: DESKTOP_UnregisterChangeNotification', Handle);
{$IFEND}
end;
{$IF FEATURE_DEBUG_LOG}
initialization
LOG_HANDLE := LOG_OPEN(ExtractFilePath(ParamStr(0)) + 'LibDesktop.LOG.TXT');
finalization
LOG_CLOSE(LOG_HANDLE);
{$IFEND}
end.