fix(windows): reinstall low level keyboard hook if it gets removed

This change improves the stability of Keyman for Windows by monitoring
the health of the low level keyboard hook. If keyman.exe is unresponsive
at any time, Windows can silently uninstall its low level keyboard hook,
which results in (at least) two problems:

* Keyman's hotkeys stop working
* A modifier key can become stuck, if it was pressed around the time
  Keyman became unresponsive.

The most common scenario in which Keyman can become unresponsive is high
system load, e.g. rendering graphics, videoconference calls, compiling
software.

Restarting Keyman always resolved both of these two issues in the past,
but with this patch, I hope that this will no longer be necessary.

A related 'fakefreeze' project is included for simulating a keyman.exe
hang by posting a `wm_keyman_control:KMC_WATCHDOG_FAKEFREEZE` message to
it, which keyman.exe responds to by `Sleep()`ing for 5 seconds. While
keyman.exe is freezing, any keystroke will cause the low level hook to
be uninstalled by Windows.

Logging has been updated; look for "LowLevelHookWatchDog" in the log for
related events.

One final small change in keyman32.cpp, as I refactored the
WH_KEYBOARD_LL hook installation/uninstallation, was to always clear out
hook variables when uninstalling a hook, because if the hook fails to
uninstall, there's really nothing we can do about it anyway, and we
probably shouldn't be trying again.

Fixes: #8064
This commit is contained in:
Marc Durdin 2025-11-17 16:30:43 +01:00
parent 9e991a14e0
commit 711541be60
18 changed files with 484 additions and 16 deletions

View file

@ -292,6 +292,8 @@ type
procedure UnregisterControllerWindows; // I4731
function IsSysTrayWindow(AHandle: THandle): Boolean;
procedure GetTrayIconHandle; // I4731
procedure WatchDogKeyEvent;
protected
procedure DoInterfaceHotkey(Target: Integer);
@ -849,6 +851,14 @@ begin
if (TKeymanHint(lParam) = KH_EXITPRODUCT) and (wParam = mrOk) then
UnloadProduct;
end;
KMC_WATCHDOG_KEYEVENT:
WatchDogKeyEvent;
KMC_WATCHDOG_FAKEFREEZE:
begin
TDebugLogClient.Instance.WriteMessage('kmc_fakefreeze begin', []);
Sleep(5000);
TDebugLogClient.Instance.WriteMessage('kmc_fakefreeze end', []);
end;
//TOUCH KMC_CONTEXT:
//TOUCH begin
//TOUCH if LParam <> 0 then
@ -857,6 +867,13 @@ begin
end;
end;
procedure TfrmKeyman7Main.WatchDogKeyEvent;
begin
TDebugLogClient.Instance.WriteMessage('Attempting to send WatchDogKeyEvent', []);
if kmint.KeymanEngineControl <> nil then
kmint.KeymanEngineControl.WatchDogKeyEvent;
end;
procedure TfrmKeyman7Main.DoInterfaceHotkey(Target: Integer);
begin
if not Assigned(FRunningProduct) then

View file

@ -0,0 +1,105 @@
/*
* Keyman is copyright (C) SIL Global. MIT License.
*
* Created by mcdurdin on 2025-11-17
*
* Handle situations where Windows silently uninstalls our low level keyboard
* hook, and dynamically reinstall it. The hook can be uninstalled when
* keyman.exe becomes unresponsive for more than 200msec (default timeout) --
* this can be due to something Keyman is doing, but it could also happen during
* high system load. The hook will only be uninstalled if a key is pressed while
* Keyman is unresponsive.
*
* Per Windows documentation:
* https://learn.microsoft.com/en-us/windows/win32/winmsg/lowlevelkeyboardproc#remarks
*
* This works by tracking last event times for both the WH_KEYBOARD_LL hook and
* the WH_GETMESSAGE hook. These two hooks both receive key events, but the
* WH_GETMESSAGE hook runs in the focused thread context, whereas WH_KEYBOARD_LL
* runs in keyman.exe main thread context. The WH_KEYBOARD_LL hook receives the
* key event first.
*
* This (static) class is only used in the keyman.exe main thread context.
*
* Notification of HookIsAlive is a straightforward call from the low level
* keyboard hook procedure, but the KeyEventReceivedInGetMessageProc function
* must be signalled across processes. We have chosen to do this with a posted
* message to the master controller, which is then handled by
* UfrmKeyman7Main.pas, and passed through Keyman_WatchDogKeyEvent.
*
* The message handler was implemented in UfrmKeyman7Main.pas rather than in
* kmhook_getmessage.cpp, because it appears that kmnGetMessageProc does not see
* messages that were posted by it or child functions (this is by observation, not
* documentation -- I was not able to find documentation on this, but surmise it
* is to prevent deadlock/infinite loop scenarios, which could easily lockup
* Windows entirely).
*
* No key data is passed in the event, only the information that a key was
* pressed.
*/
#include "pch.h"
/**
* minimum number of milliseconds between the last LowLevel and GetMessage
* events before we assume the low level hook has been uninstalled, and we need
* to reinstall it.
*/
#define WATCHDOG_THRESHOLD 1000
static ULONGLONG LastLowLevelEventTick = 0;
static ULONGLONG LastGetMessageEventTick = 0;
void LowLevelHookWatchDog::HookIsAlive() {
// ULONGLONG Previous = LastLowLevelEventTick;
LastLowLevelEventTick = GetTickCount64();
// SendDebugMessageFormat("LowLevelHookWatchDog::HookIsAlive currentLL=%llu currentGM=%llu (lastLL=%llu)", LastLowLevelEventTick, LastGetMessageEventTick, Previous);
}
void LowLevelHookWatchDog::KeyEventReceivedInGetMessageProc() {
// ULONGLONG Previous = LastGetMessageEventTick;
LastGetMessageEventTick = GetTickCount64();
// SendDebugMessageFormat("LowLevelHookWatchDog::KeyEventReceivedInGetMessageProc currentLL=%llu currentGM=%llu (lastGM=%llu)", LastLowLevelEventTick, LastGetMessageEventTick, Previous);
// This is a good place to check if we are still alive -- shortly after each
// keystroke event in the GetMessage hook, as this means at worst we'll have
// one or two keystrokes where Keyman must recover
if(!CheckIfHookIsAlive()) {
ReinstallHook();
}
}
bool LowLevelHookWatchDog::CheckIfHookIsAlive() {
if(LastGetMessageEventTick < LastLowLevelEventTick) {
// this shouldn't be possible but rather safe than sorry
return true;
}
return LastGetMessageEventTick - LastLowLevelEventTick < WATCHDOG_THRESHOLD;
}
void LowLevelHookWatchDog::ReinstallHook() {
//keyman32.cpp:
SendDebugMessageFormat(
"Attempting to reinstall hook because watchdog threshold exceeded by %llu msec (last LL=%llu last GM=%llu)",
LastGetMessageEventTick - LastLowLevelEventTick,
LastLowLevelEventTick,
LastGetMessageEventTick
);
if(!RestartLowLevelHook()) {
SendDebugMessage("Attempt to reinstall low level hook may have failed, see previous log messages");
} else {
SendDebugMessage("Attempt to reinstall low level hook succeeded");
}
// We should assume the hook is alive at this point to avoid repeated resets
HookIsAlive();
}
#ifndef _WIN64
extern "C" void __declspec(dllexport) WINAPI Keyman_WatchDogKeyEvent() {
SendDebugMessageFormat("Keyman_WatchDogKeyEvent");
LowLevelHookWatchDog::KeyEventReceivedInGetMessageProc();
}
#endif

View file

@ -0,0 +1,32 @@
/*
* Keyman is copyright (C) SIL Global. MIT License.
*
* Created by mcdurdin on 2025-11-17
*/
/**
* @brief Watch for scenarios where the low level keyboard hook may be
* uninstalled by Windows, e.g. when there is heavy system load, and
* reinstall it.
*/
class LowLevelHookWatchDog {
public:
/**
* @brief Update the watchdog timestamp to current time, because the low
* level hook is receiving messages successfully
*/
static void HookIsAlive();
/**
* @brief Update the watchdog GetMessageProc timestamp to current time;
* this is called by the Keyman_WatchDogKeyEvent function, when
* a message posted from the GetMessageProc hook is received by
* keyman.exe.
*/
static void KeyEventReceivedInGetMessageProc();
private:
static bool CheckIfHookIsAlive();
static void ReinstallHook();
};

View file

@ -141,6 +141,8 @@ LRESULT _kmnLowLevelKeyboardProc(
SendDebugEntry();
LowLevelHookWatchDog::HookIsAlive();
PKBDLLHOOKSTRUCT hs = (PKBDLLHOOKSTRUCT) lParam;
BOOL extended = hs->flags & LLKHF_EXTENDED ? TRUE : FALSE;

View file

@ -279,6 +279,24 @@ extern "C" BOOL _declspec(dllexport) WINAPI Keyman_GetInitialised(BOOL *FSingleA
return Globals::get_Keyman_Initialised();
}
#ifndef _WIN64
BOOL InitLowLevelHook() {
HINSTANCE hinst = GetModuleHandle(LIBRARY_NAME);
*Globals::hhookLowLevelKeyboardProc() = SetWindowsHookExW(WH_KEYBOARD_LL, (HOOKPROC) kmnLowLevelKeyboardProc, hinst, Globals::get_FSingleThread()); // I4124
return Globals::get_hhookLowLevelKeyboardProc() != NULL;
}
BOOL UninitLowLevelHook() {
BOOL RetVal = TRUE;
if(Globals::get_hhookLowLevelKeyboardProc() && !UnhookWindowsHookEx(Globals::get_hhookLowLevelKeyboardProc())) // I4124
RetVal = FALSE;
*Globals::hhookLowLevelKeyboardProc() = NULL;
return RetVal;
}
#endif
BOOL InitHooks()
{
HINSTANCE hinst = GetModuleHandle(LIBRARY_NAME);
@ -286,7 +304,7 @@ BOOL InitHooks()
*Globals::hhookGetMessage() = SetWindowsHookExW(WH_GETMESSAGE, (HOOKPROC) kmnGetMessageProc, hinst, Globals::get_FSingleThread());
*Globals::hhookCallWndProc() = SetWindowsHookExW(WH_CALLWNDPROC, (HOOKPROC) kmnCallWndProc, hinst, Globals::get_FSingleThread());
#ifndef _WIN64
*Globals::hhookLowLevelKeyboardProc() = SetWindowsHookExW(WH_KEYBOARD_LL, (HOOKPROC) kmnLowLevelKeyboardProc, hinst, Globals::get_FSingleThread()); // I4124
InitLowLevelHook();
#endif;
return
@ -304,19 +322,14 @@ BOOL UninitHooks()
if(Globals::get_hhookGetMessage() && !UnhookWindowsHookEx(Globals::get_hhookGetMessage()))
RetVal = FALSE;
else
*Globals::hhookGetMessage() = NULL;
*Globals::hhookGetMessage() = NULL;
if(Globals::get_hhookCallWndProc() && !UnhookWindowsHookEx(Globals::get_hhookCallWndProc()))
RetVal = FALSE;
else
*Globals::hhookCallWndProc() = NULL;
*Globals::hhookCallWndProc() = NULL;
#ifndef _WIN64
if(Globals::get_hhookLowLevelKeyboardProc() && !UnhookWindowsHookEx(Globals::get_hhookLowLevelKeyboardProc())) // I4124
RetVal = FALSE;
else
*Globals::hhookLowLevelKeyboardProc() = NULL;
RetVal = UninitLowLevelHook() && RetVal;
#endif
return RetVal;
@ -473,6 +486,27 @@ extern "C" BOOL _declspec(dllexport) WINAPI Keyman_RestartEngine()
return TRUE;
}
BOOL RestartLowLevelHook() {
BOOL result=true;
#ifndef _WIN64
if(!Globals::get_Keyman_Initialised()) {
return FALSE;
}
if(!UninitLowLevelHook()) {
SendDebugMessageFormat("Failed to uninstall low level hook. GetLastError = %d", GetLastError());
result = FALSE;
}
if(!InitLowLevelHook()) {
SendDebugMessageFormat("Failed to install low level hook. GetLastError = %d", GetLastError());
result = FALSE;
}
#endif
return result;
}
//---------------------------------------------------------------------------------------------------------
//
// Utility guff functions

View file

@ -49,3 +49,5 @@ EXPORTS
Keyman_UnregisterControllerThread
SetCustomPostKeyCallback
Keyman_WatchDogKeyEvent

View file

@ -361,6 +361,7 @@
<ClCompile Include="kmprocess.cpp" />
<ClCompile Include="$(KEYMAN_ROOT)\common\windows\cpp\src\registry.cpp" />
<ClCompile Include="kmprocessactions.cpp" />
<ClCompile Include="LowLevelHookWatchDog.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
@ -450,6 +451,7 @@
<ClInclude Include="keyman-debug-etw.h" />
<ClInclude Include="keyeventsenderthread.h" />
<ClInclude Include="keystate.h" />
<ClInclude Include="LowLevelHookWatchDog.h" />
<ClInclude Include="$(KEYMAN_ROOT)\common\windows\cpp\include\registry.h" />
<ClInclude Include="security.h" />
<ClInclude Include="serialkeyeventclient.h" />

View file

@ -47,6 +47,12 @@
#define KMC_PROFILECHANGED 18 // 9.0.426.0 // I3933
#define KMC_HINTRESPONSE 19 // 14.0 HIWORD(wParam) = ModalResult, lParam = hint enum
#define KMC_WATCHDOG_FAKEFREEZE 20 // 19.0 - pause Keyman for 5 seconds for debug purposes to test stability
#define KMC_WATCHDOG_KEYEVENT 21 // 19.0 - let the LowLevelHookWatchDog know that input has happened on another thread
#define PC_UPDATE 0 // Tell Keyman to update its display of active keyboard
#define PC_UPDATE_LANGUAGESWITCH 1 // Tell Keyman to update its display of active keyboard and then open language switch form
#define PC_HOTKEYCHANGE 2 // Tell Keyman that a hotkey was pressed to switch keyboard

View file

@ -263,6 +263,7 @@ void keybd_shift(LPINPUT pInputs, int* n, BOOL isReset, LPBYTE const kbd);
#include "k32_tsf.h"
#include "k32_visualkeyboardinterface.h"
#include "LowLevelHookWatchDog.h"
void ReportActiveKeyboard(WORD wCommand); // I3933 // I3949
void SelectKeyboardHKL(DWORD hkl, BOOL foreground); // I3933 // I3949 // I4271
@ -273,4 +274,6 @@ void ProcessModifierChange(UINT key, BOOL isUp, BOOL isExtended); // I4793
BOOL SetupCoreEnvironment(km_core_option_item **test_env_opts);
void DeleteCoreEnvironment(km_core_option_item *test_env_opts);
BOOL RestartLowLevelHook();
#endif // _KEYMANENGINE_H

View file

@ -146,6 +146,15 @@ LRESULT _kmnGetMessageProc(int nCode, WPARAM wParam, LPARAM lParam)
}
if ((mp->message == WM_KEYDOWN || mp->message == WM_SYSKEYDOWN || mp->message == WM_KEYUP || mp->message == WM_SYSKEYUP)) { // I4642
// To help our low level keyboard hook recover if system load causes it to
// be uninstalled, we tell the controller process that we should have seen a
// keystroke event in the low level keyboard hook; this gets passed into the
// LowLevelHookWatchDog via keyman.exe
if(mp->message == WM_KEYDOWN || mp->message == WM_SYSKEYDOWN) {
Globals::PostMasterController(wm_keyman_control, KMC_WATCHDOG_KEYEVENT, 0);
}
BYTE scan = KEYMSG_LPARAM_SCAN(mp->lParam);
CheckScheduledRefresh();
_td->LastScanCode = scan;

View file

@ -76,6 +76,7 @@ type
TKeyman32ControllerSendMessageFunction = function(msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
TKeyman32ControllerPostMessageFunction = procedure(msg: UINT; wParam: WPARAM; lParam: LPARAM); stdcall;
TKeyman32UpdateTouchPanelVisibilityFunction = procedure(Value: BOOL); stdcall;
TKeyman32WatchDogKeyEvent = procedure; stdcall;
TKeymanControl = class(TKeymanAutoObject, IKeymanCustomisationAccess, IIntKeymanControl, IKeymanControl, IKeymanEngineControl)
private
@ -97,6 +98,7 @@ type
FKeyman_PostMasterController: TKeyman32ControllerPostMessageFunction;
FKeyman_StartExit: TKeyman32ExitFunction; // I3092
FKeyman_UpdateTouchPanelVisibility: TKeyman32UpdateTouchPanelVisibilityFunction;
FKeyman_WatchDogKeyEvent: TKeyman32WatchDogKeyEvent;
procedure LoadKeyman32;
procedure StartKeyman32;
procedure Do_Keyman_Exit;
@ -148,6 +150,7 @@ type
procedure DisableUserInterface; safecall;
procedure EnableUserInterface; safecall;
procedure UpdateTouchPanelVisibility(Value: Boolean); safecall;
procedure WatchDogKeyEvent; safecall; // 32 bit only
procedure DiagnosticTestException; safecall;
@ -524,6 +527,25 @@ begin
{$ENDIF}
end;
(**
* Tell the LowLevelHookWatchDog that a key event has been received
*)
procedure TKeymanControl.WatchDogKeyEvent;
begin
{$IFDEF WIN64}
Error(Cardinal(E_NOTIMPL));
{$ELSE}
KL.MethodEnter(Self, 'WatchDogKeyEvent', []);
try
LoadKeyman32;
FKeyman_WatchDogKeyEvent;
finally
KL.MethodExit(Self, 'WatchDogKeyEvent');
end;
{$ENDIF}
end;
procedure TKeymanControl.RegisterControllerThread(Value: LongWord);
begin
{$IFDEF WIN64}
@ -750,6 +772,9 @@ begin
@FKeyman_SendMasterController := ProcAddr('Keyman_SendMasterController');
@FKeyman_PostMasterController := ProcAddr('Keyman_PostMasterController');
@FKeyman_UpdateTouchPanelVisibility := ProcAddr('Keyman_UpdateTouchPanelVisibility');
{$IFNDEF WIN64}
@FKeyman_WatchDogKeyEvent := ProcAddr('Keyman_WatchDogKeyEvent');
{$ENDIF}
end;
end;

View file

@ -54,6 +54,9 @@ const
KMC_HINTRESPONSE = 19; // 14.0 HIWORD(wParam) = ModalResult, lParam = hint enum
KMC_WATCHDOG_FAKEFREEZE = 20; // 19.0 - pause Keyman for 5 seconds for debug purposes to test stability
KMC_WATCHDOG_KEYEVENT = 21; // 19.0 - let the LowLevelHookWatchDog know that input has happened on another thread
PC_UPDATE = 0;
PC_UPDATE_LANGUAGESWITCH = 1;
PC_HOTKEYCHANGE = 2;

View file

@ -1,18 +1,18 @@
(*
Name: KeymanControlRestart
Copyright: Copyright (C) SIL International.
Documentation:
Description:
Documentation:
Description:
Create Date: 19 Jun 2007
Modified Date: 19 Jun 2007
Authors: mcdurdin
Related Files:
Dependencies:
Related Files:
Dependencies:
Bugs:
Todo:
Notes:
Bugs:
Todo:
Notes:
History: 19 Jun 2007 - mcdurdin - Initial version
*)
unit KeymanEngineControl;
@ -43,6 +43,10 @@ type
procedure UnregisterMasterController(Value: LongWord); safecall;
procedure RegisterControllerThread(Value: LongWord); safecall;
procedure UnregisterControllerThread(Value: LongWord); safecall;
// New in 19.0
procedure WatchDogKeyEvent; safecall; // 32 bit only
end;
implementation

View file

@ -0,0 +1 @@
x64/

View file

@ -0,0 +1,39 @@
/*
* Keyman is copyright (C) SIL Global. MIT License.
*
* Created by mcdurdin on 2025-11-17
*
* Tell keyman.exe to pause for 5 seconds to force Windows to silently uninstall
* the low level keyboard hook, per Windows documentation at
* https://learn.microsoft.com/en-us/windows/win32/winmsg/lowlevelkeyboardproc#remarks
*
* See LowLevelHookWatchDog.cpp for more detail
*/
#include <windows.h>
#include <iostream>
#include <stdlib.h>
#define KMC_WATCHDOG_FAKEFREEZE 20
int main() {
std::cout << "Posting a freeze message to Keyman master controller\n";
UINT wm_keyman_control = RegisterWindowMessage(L"WM_KEYMAN_CONTROL");
HWND hwnd = FindWindow(L"TfrmKeyman7Main", NULL);
if (hwnd == NULL) {
std::cout << "Keyman master controller window not found\n";
return 1;
}
if (!PostMessage(hwnd, wm_keyman_control, KMC_WATCHDOG_FAKEFREEZE, 0)) {
std::cout << "Error calling Keyman KMC_WATCHDOG_FAKEFREEZE\n";
}
std::cout << "Sleeping 5 seconds...\n";
for (int i = 1; i <= 5; i++) {
Sleep(1000);
std::cout << "..." << i << "\n";
}
std::cout << "Keyman should be responsive again now\n";
return 0;
}

View file

@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.36616.10 d17.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fakefreeze", "fakefreeze.vcxproj", "{BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}.Debug|x64.ActiveCfg = Debug|x64
{BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}.Debug|x64.Build.0 = Debug|x64
{BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}.Debug|x86.ActiveCfg = Debug|Win32
{BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}.Debug|x86.Build.0 = Debug|Win32
{BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}.Release|x64.ActiveCfg = Release|x64
{BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}.Release|x64.Build.0 = Release|x64
{BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}.Release|x86.ActiveCfg = Release|Win32
{BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BA12682B-94DC-4E10-AE3F-743D64317F12}
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,131 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" 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>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{bdde094f-b409-4df5-aa9e-6dec09b362ce}</ProjectGuid>
<RootNamespace>fakefreeze</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</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" />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="fakefreeze.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View file

@ -0,0 +1,22 @@
<?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;c++;cppm;ixx;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;h++;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="fakefreeze.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>