fix(windows): Trigger language sync after changes

Fixes #4447.
Fixes #4222.

The symptoms for these two issues are related: the language associations
change and keyboards stop functioning correctly. The issue is described
in detail in #4447.

Although this fix should be considered 'experimental', we should
probably include it in the release of 14.0, because it fixes a
longstanding issue with Keyman and Windows languages.
This commit is contained in:
Marc Durdin 2021-03-15 09:12:36 +11:00
parent 493bb945b6
commit 6155b544ea
11 changed files with 440 additions and 1 deletions

View file

@ -13,6 +13,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "kmtip", "kmtip\kmtip.vcxpro
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mcompile", "mcompile\mcompile.vcxproj", "{5C22DDDA-CEAD-45F1-96B0-1473111156AA}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "kmrefresh", "kmrefresh\kmrefresh.vcxproj", "{A3B8F75F-E63E-434E-8A2A-034AD9B66571}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@ -53,6 +55,14 @@ Global
{5C22DDDA-CEAD-45F1-96B0-1473111156AA}.Release|x64.ActiveCfg = Release|Win32
{5C22DDDA-CEAD-45F1-96B0-1473111156AA}.Release|x86.ActiveCfg = Release|Win32
{5C22DDDA-CEAD-45F1-96B0-1473111156AA}.Release|x86.Build.0 = Release|Win32
{A3B8F75F-E63E-434E-8A2A-034AD9B66571}.Debug|x64.ActiveCfg = Debug|x64
{A3B8F75F-E63E-434E-8A2A-034AD9B66571}.Debug|x64.Build.0 = Debug|x64
{A3B8F75F-E63E-434E-8A2A-034AD9B66571}.Debug|x86.ActiveCfg = Debug|Win32
{A3B8F75F-E63E-434E-8A2A-034AD9B66571}.Debug|x86.Build.0 = Debug|Win32
{A3B8F75F-E63E-434E-8A2A-034AD9B66571}.Release|x64.ActiveCfg = Release|x64
{A3B8F75F-E63E-434E-8A2A-034AD9B66571}.Release|x64.Build.0 = Release|x64
{A3B8F75F-E63E-434E-8A2A-034AD9B66571}.Release|x86.ActiveCfg = Release|Win32
{A3B8F75F-E63E-434E-8A2A-034AD9B66571}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View file

@ -156,6 +156,14 @@
/>
</Component>
<Component Guid="*">
<File Id="kmrefresh.x86.exe" KeyPath="yes" />
</Component>
<Component Guid="*">
<File Id="kmrefresh.x64.exe" KeyPath="yes" />
</Component>
<Component Guid="{FA89BC70-11F3-4DAF-A0B4-6EBA64FC6C5C}" Id="tsysinfo.xslts">
<File Id="base.xslt" Name='base.xslt' Source='..\..\engine\tsysinfo\base.xslt' />
<File Id="si_browsers.xslt" Name='si_browsers.xslt' Source='..\..\engine\tsysinfo\si_browsers.xslt' />

View file

@ -40,6 +40,7 @@ type
TKeymanKeyboardsInstalled = class(TKeymanAutoCollectionObject, IKeymanKeyboardsInstalled, IKeymanKeyboardsInstalled2, IIntKeymanKeyboardsInstalled) // I4376
private
FKeyboards: TKeyboardList;
procedure TriggerWindowsLanguageSync;
protected
procedure DoRefresh; override;
@ -69,14 +70,18 @@ uses
System.Variants,
Winapi.msctf,
custinterfaces,
DebugPaths,
keymankeyboardfile,
keymanpackageinstalled,
keymanpackagesinstalled,
KeymanPaths,
KLog,
kpinstallkeyboard,
keyman_msctf,
OnlineConstants,
regkeyboards,
RegistryKeys,
utilexecute,
utilfiletypes;
constructor TKeymanKeyboardsInstalled.Create(AContext: TKeymanContext);
@ -182,6 +187,46 @@ begin
end;
Context.Control.AutoApplyKeyman;
TriggerWindowsLanguageSync;
end;
// This triggers a synchronisation of the language settings to the
// cloud, which avoids an issue where language setting changes are
// overwritten on a subsequent reboot of the system.
//
// See #4447 and kmrefresh.cpp for additional detail.
//
// Although the sync function is not supported on Windows 7, 8.1,
// we'll call kmrefresh.exe anyway, as we cannot be sure which version
// of Windows we are running on -- the calling process may be lied to
// if it does not have an appropriate manifest. kmrefresh.exe is safe
// to run as it will simply exit if it does not find the appropriate
// endpoint to call.
//
// On x64 systems, we must call the x64 version of the process, as the
// x86 version does not appear to work.
procedure TKeymanKeyboardsInstalled.TriggerWindowsLanguageSync;
var
processName: string;
FWow64: LongBool;
begin
if not Reg_GetDebugFlag(SRegValue_Flag_SyncLanguagesToCloud, True) then
Exit;
{$IFDEF WIN64}
processName := 'kmrefresh.x64.exe';
{$ELSE}
if IsWow64Process(GetCurrentProcess, FWow64) and FWow64
then processName := 'kmrefresh.x64.exe'
else processName := 'kmrefresh.x86.exe';
{$ENDIF}
TUtilExecute.Execute(
TKeymanPaths.KeymanEngineInstallPath(processName),
ExtractFileDir(ParamStr(0)),
SW_HIDE
);
end;
procedure TKeymanKeyboardsInstalled.DoRefresh;

View file

@ -0,0 +1,40 @@
#
# kmrefresh Makefile
#
!include ..\..\Defines.mak
PLATFORM=x64
build: version.res dirs
$(MSBUILD) kmrefresh.vcxproj /p:Platform=Win32 $(MSBUILD_BUILD)
$(MSBUILD) kmrefresh.vcxproj /p:Platform=x64 $(MSBUILD_BUILD)
$(COPY) $(WIN32_TARGET_PATH)\kmrefresh.x86.exe $(PROGRAM)\engine
$(COPY) $(WIN32_TARGET_PATH)\kmrefresh.x86.pdb $(DEBUGPATH)\engine
$(COPY) $(X64_TARGET_PATH)\kmrefresh.x64.exe $(PROGRAM)\engine
$(COPY) $(X64_TARGET_PATH)\kmrefresh.x64.pdb $(DEBUGPATH)\engine
clean: def-clean
$(MSBUILD) $(MSBUILD_CLEAN) kmrefresh.vcxproj
-rd /s/q x64
-rd /s/q Win32
signcode:
$(SIGNCODE) /d "Keyman Engine Refresh x86" $(PROGRAM)\engine\kmrefresh.x86.exe
$(SIGNCODE) /d "Keyman Engine Refresh x64" $(PROGRAM)\engine\kmrefresh.x64.exe
wrap-symbols:
$(SYMSTORE) $(PROGRAM)\engine\kmrefresh.x86.exe /t keyman-engine-windows
$(SYMSTORE) $(DEBUGPATH)\engine\kmrefresh.x86.pdb /t keyman-engine-windows
$(SYMSTORE) $(PROGRAM)\engine\kmrefresh.x64.exe /t keyman-engine-windows
$(SYMSTORE) $(DEBUGPATH)\engine\kmrefresh.x64.pdb /t keyman-engine-windows
backup:
$(WZZIP) $(BUILD)\engine\kmrefresh.x86.exe $(BACKUPDEFAULTS) kmrefresh.x86.exe
$(WZZIP) $(BUILD)\engine\kmrefresh.x64.exe $(BACKUPDEFAULTS) kmrefresh.x64.exe
install:
$(COPY) $(PROGRAM)\engine\kmrefresh.x86.exe "$(INSTALLPATH_KEYMANENGINE)"
$(COPY) $(PROGRAM)\engine\kmrefresh.x64.exe "$(INSTALLPATH_KEYMANENGINE)"
!include ..\..\Target.mak

View file

@ -0,0 +1,74 @@
/*
kmrefresh triggers a synchronisation of Windows language data to
Microsoft's 'cloud'. This process is required to ensure that Windows
does not stomp over the current language configuration when it
attempts to synchronise the data back from the cloud, for instance
shortly after logging into the system.
This cloud functionality was introduced in Windows 10 1809? It is
not present in Windows 7, 8.1 or Windows 10 RTM. The process should
run without issues on all platforms, however, and just be a no-op
where the endpoint is not available.
The SyncLanguageDataToCloud API endpoint is currently undocumented,
so this should be considered experimental. Alternative approaches
have been considered in corresponding issue #4447, but this is both
lightweight and respectful of user preferences, so this is the
approach we've opted to take for now. Plus, it works really well.
One trick to be aware of is that the sync function fails if run from
WoW64. So make sure you run the version with the correct bitness.
I've put this check into the caller app (kmcomapi) rather than in
this process. kmcomapi does not currently check exit codes but you
could track them e.g. with procmon if you wanted to verify behaviour.
See #4447 for additional information.
*/
#define STRICT
#include <Windows.h>
typedef void (WINAPI * PSYNCLANGUAGEDATATOCLOUD)();
int APIENTRY wWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PWSTR lpCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(hInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
UNREFERENCED_PARAMETER(nCmdShow);
int nExitCode = 0;
if(!SUCCEEDED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)))
return 1;
HMODULE handle = LoadLibrary(L"coreglobconfig.dll");
if (handle == NULL) {
CoUninitialize();
return 2;
}
PSYNCLANGUAGEDATATOCLOUD pSync = (PSYNCLANGUAGEDATATOCLOUD)GetProcAddress(handle, "SyncLanguageDataToCloud");
if (pSync == NULL) {
FreeLibrary(handle);
CoUninitialize();
return 3;
}
pSync();
// The call to SyncLanguageDataToCloud triggers a worker thread to do
// the work. As of writing, I am unaware of a way to determine that the
// worker thread has completed, so the sleep gives plenty of time for
// it to finish. This process is invisible and silent so the fact that
// it lives in the background for a few seconds should not be a problem.
Sleep(10000);
FreeLibrary(handle);
CoUninitialize();
return 0;
}

View file

@ -0,0 +1,155 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{A3B8F75F-E63E-434E-8A2A-034AD9B66571}</ProjectGuid>
<RootNamespace>kmrefresh</RootNamespace>
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v141</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v141</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v141</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v141</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<OutDir>$(Platform)\$(Configuration)\</OutDir>
<TargetName>$(ProjectName).x64</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<OutDir>$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(Platform)\$(Configuration)\</IntDir>
<TargetName>$(ProjectName).x86</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<OutDir>$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(Platform)\$(Configuration)\</IntDir>
<TargetName>$(ProjectName).x86</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<OutDir>$(Platform)\$(Configuration)\</OutDir>
<TargetName>$(ProjectName).x64</TargetName>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="kmrefresh.cpp" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="version.rc" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="kmrefresh.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="version.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
</Project>

View file

@ -0,0 +1,30 @@
1 VERSIONINFO
FILEVERSION $VERSIONNUM
PRODUCTVERSION $VERSIONNUM
FILEFLAGSMASK 0x3fL
FILEFLAGS 0x0L
FILEOS 0x4L
FILETYPE 0x2L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "0C0904E4"
BEGIN
VALUE "CompanyName", "SIL International\0"
VALUE "FileDescription", "Keyman Engine x64\0"
VALUE "FileVersion", "$VERSION\0"
VALUE "InternalName", "KEYMANX64\0"
VALUE "LegalCopyright", "© SIL International\0"
VALUE "LegalTrademarks", "\0"
VALUE "OriginalFilename", "KEYMANX64.EXE\0"
VALUE "ProductName", "Keyman Engine\0"
VALUE "ProductVersion", "$VERSION\0"
VALUE "Comments", "\0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0xc09, 1252
END
END

View file

@ -95,7 +95,7 @@ const
ValueType: kstInteger
);
BaseKeymanSettings: array[0..32] of TKeymanSettingBase = (
BaseKeymanSettings: array[0..33] of TKeymanSettingBase = (
// TIKE:UTikeDebugMode.TikeDebugMode
(
@ -423,6 +423,20 @@ const
ValueType: kstInteger
),
// kmcomapi::TKeymanKeyboardsInstalled.TriggerWindowsLanguageSync
(
ID: 'engine.compatibility.sync_languages_to_cloud';
Name: SRegValue_Flag_SyncLanguagesToCloud;
RootKey: HKCU;
Key: SRegKey_KeymanEngineDebug_CU;
Description: 'Set to 0 to prevent Keyman from calling the experimental '+
'kmrefresh program that triggers Windows language '+
'synchronisation in Windows 10. This program ensures that '+
'language settings are not lost when you restart Windows.';
DefaultInt: 1;
ValueType: kstInteger
),
//
// engine.diagnostics
//

View file

@ -448,6 +448,7 @@ const
SRegValue_Flag_UseRegisterHotkey = 'Flag_UseRegisterHotkey';
SRegValue_Flag_ShouldSerializeInput = 'Flag_ShouldSerializeInput';
SRegValue_Flag_UseAutoStartTask = 'Flag_UseAutoStartTask';
SRegValue_Flag_SyncLanguagesToCloud = 'Flag_SyncLanguagesToCloud';
// Fixed path names
const

View file

@ -40,6 +40,8 @@ type
class function CreateProcessAsShellUser(const process, cmdline: WideString; Wait: Boolean): Boolean; overload;
class function CreateProcessAsShellUser(const process, cmdline: WideString; Wait: Boolean; var AExitCode: Cardinal): Boolean; overload;
class function Execute(const cmdline, curdir: string; ShowWindow: Integer): Boolean; static;
end;
implementation
@ -256,6 +258,39 @@ begin
end;
end;
class function TUtilExecute.Execute(const cmdline, curdir: string; ShowWindow: Integer): Boolean;
var
si: TStartupInfoW;
pi: TProcessInformation;
buf: PChar;
begin
Result := False;
si.cb := SizeOf(TStartupInfo);
si.lpReserved := nil;
si.lpDesktop := nil;
si.lpTitle := nil;
si.dwFlags := STARTF_USESHOWWINDOW;
si.wShowWindow := ShowWindow;
si.cbReserved2 := 0;
si.lpReserved2 := nil;
buf := AllocMem((Length(cmdline)+1)*sizeof(Char));
try
StrPCopy(buf, cmdline);
if CreateProcess(nil, buf,
nil, nil, True, NORMAL_PRIORITY_CLASS, nil, PWideChar(curdir),
si, pi) then
begin
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
Result := True;
end;
finally
FreeMem(buf);
end;
end;
// Refactored from UCreateProcessAsShellUser
function GetShellWindow: HWND; stdcall; external user32;