diff --git a/windows/src/desktop/Makefile b/windows/src/desktop/Makefile
index 77f84360aa..eaacdf319b 100644
--- a/windows/src/desktop/Makefile
+++ b/windows/src/desktop/Makefile
@@ -2,7 +2,7 @@
# Keyman Desktop Makefile
#
-TARGETS=kmshell kmbrowserhost setup insthelp
+TARGETS=kmshell kmconfig kmbrowserhost setup insthelp
RELEASE_TARGETS=help
CLEANS=inst clean-desktop
MANIFESTS=kmshell setup insthelp
@@ -15,6 +15,10 @@ kmshell:
cd $(ROOT)\src\desktop\kmshell
$(MAKE) $(TARGET)
+kmconfig:
+ cd $(ROOT)\src\desktop\kmconfig
+ $(MAKE) $(TARGET)
+
kmbrowserhost:
cd $(ROOT)\src\desktop\kmbrowserhost
$(MAKE) $(TARGET)
diff --git a/windows/src/desktop/desktop.groupproj b/windows/src/desktop/desktop.groupproj
index 0c9296cd8c..7c6e5525e0 100644
--- a/windows/src/desktop/desktop.groupproj
+++ b/windows/src/desktop/desktop.groupproj
@@ -12,6 +12,9 @@
+
+
+
Default.Personality.12
@@ -47,14 +50,23 @@
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
diff --git a/windows/src/desktop/inst/keymandesktop.wxs b/windows/src/desktop/inst/keymandesktop.wxs
index 0423191d4f..5adf32af7b 100644
--- a/windows/src/desktop/inst/keymandesktop.wxs
+++ b/windows/src/desktop/inst/keymandesktop.wxs
@@ -145,6 +145,10 @@
+
+
+
+
diff --git a/windows/src/desktop/kmconfig/Keyman.System.KMConfigMain.pas b/windows/src/desktop/kmconfig/Keyman.System.KMConfigMain.pas
new file mode 100644
index 0000000000..f5aa517fb7
--- /dev/null
+++ b/windows/src/desktop/kmconfig/Keyman.System.KMConfigMain.pas
@@ -0,0 +1,333 @@
+unit Keyman.System.KMConfigMain;
+
+interface
+
+uses
+ Keyman.System.Settings;
+
+type
+ TKMConfig = class
+ private
+ class var IsAdmin: Boolean;
+ class var Settings: TKeymanSettings;
+ class function Import(const Filename: string): Boolean; static;
+ class function Export(const Filename: string): Boolean; static;
+ class function Reset(const ID: string): Boolean; static;
+ class function SetValue(const ID, Value: string): Boolean; static;
+ class function Show(const P2: string): Boolean; static;
+ class procedure Log(const message: string);
+ class procedure LogError(const message: string);
+ class procedure LogWarning(const message: string);
+ public
+ class constructor ClassCreate;
+ class destructor ClassDestroy;
+ class procedure Run; static;
+ end;
+
+implementation
+
+uses
+ System.SysUtils,
+ System.Win.Registry,
+ Winapi.Windows,
+
+ Keyman.System.SettingsManager,
+ Keyman.System.SettingsManagerFile,
+ RegistryKeys;
+
+type
+ TANSIColor = record
+ Green, Grey, Default, Yellow, Red, White: string;
+ end;
+
+var
+ ANSIColor: TANSIColor;
+
+class constructor TKMConfig.ClassCreate;
+var
+ r: TRegistry;
+begin
+ inherited;
+ r := TRegistry.Create;
+ try
+ r.RootKey := HKEY_LOCAL_MACHINE;
+ IsAdmin := r.OpenKey(SRegKey_KeymanEngine_LM, True);
+ finally
+ r.Free;
+ end;
+
+ Settings := TKeymanSettings.Create;
+end;
+
+class destructor TKMConfig.ClassDestroy;
+begin
+ Settings.Free;
+ inherited;
+end;
+
+class procedure TKMConfig.Run;
+var
+ Command: string;
+ Result: Boolean;
+begin
+ Settings := TKeymanSettings.Create;
+ Command := ParamStr(1).ToLower;
+ if Command = 'import' then Result := Import(ParamStr(2))
+ else if Command = 'export' then Result := Export(ParamStr(2))
+ else if Command = 'reset' then Result := Reset(ParamStr(2))
+ else if Command = 'set' then Result := SetValue(ParamStr(2), ParamStr(3))
+ else if Command = 'show' then Result := Show(ParamStr(2))
+ else
+ begin
+ Result := False;
+ writeln('Usage: kmconfig command [options]');
+ writeln('Commands:');
+ writeln(' import Imports settings into local machine registry');
+ writeln(' export Exports settings from local machine registry to file');
+ writeln(' reset [id] Resets one or all settings to default');
+ writeln(' set id value Sets setting identified by id to value');
+ writeln(' show [-a] [id] Lists one or all settings; -a to show all, even empty settings');
+ writeln('Note: registry changes may require elevation to succeed.');
+ end;
+
+ if Result
+ then ExitCode := 0
+ else ExitCode := 1;
+end;
+
+class procedure TKMConfig.Log(const message: string);
+begin
+ writeln(message);
+end;
+
+class procedure TKMConfig.LogError(const message: string);
+begin
+ Log(AnsiColor.Red+'ERROR: '+message+AnsiColor.Default);
+end;
+
+class procedure TKMConfig.LogWarning(const message: string);
+begin
+ Log(AnsiColor.Yellow+'WARNING: '+message+AnsiColor.Default);
+end;
+
+class function TKMConfig.Export(const Filename: string): Boolean;
+begin
+ TKeymanSettingsManager.Load(Settings);
+ TKeymanSettingsManagerFile.Export(Settings, Filename);
+ Log('All Keyman settings have been exported to '+Filename);
+ Result := True;
+end;
+
+class function TKMConfig.Import(const Filename: string): Boolean;
+begin
+ TKeymanSettingsManager.Load(Settings);
+ TKeymanSettingsManagerFile.Import(Settings, Filename);
+ TKeymanSettingsManager.Save(Settings, IsAdmin);
+ Log('Keyman settings in '+Filename+' have been imported');
+ if not IsAdmin then
+ LogWarning('Some settings that require Administrator permissions may not have been updated');
+ Result := True;
+end;
+
+class function TKMConfig.Reset(const ID: string): Boolean;
+var
+ Setting: TKeymanSetting;
+begin
+ if ID = '' then
+ begin
+ Settings.Reset;
+ TKeymanSettingsManager.Load(Settings);
+ if Settings.Modified then
+ begin
+ TKeymanSettingsManager.Save(Settings, IsAdmin);
+ Log('Keyman settings have been reset to default');
+ if not IsAdmin then
+ LogWarning('Some settings that require Administrator permissions may not have been updated');
+ end
+ else
+ begin
+ Log('All Keyman settings were already default');
+ end;
+ end
+ else
+ begin
+ TKeymanSettingsManager.Load(Settings);
+ Setting := Settings.Find(ID);
+ if not Assigned(Setting) then
+ begin
+ // Custom value, do we support it?
+ if ID.StartsWith(CustomKeymanSetting_TSFApp.ID, True) then
+ begin
+ LogWarning('Setting '+ID+' does not exist; not updating.');
+ Exit(True);
+ end
+ else
+ begin
+ LogError('Setting '+ID+' is not a valid Keyman setting.');
+ Exit(False);
+ end;
+ end;
+ if Setting.IsEmpty then
+ begin
+ LogWarning('Setting '+ID+' is already default; not updating.');
+ Exit(True);
+ end;
+ if (Setting.Base.RootKey = HKEY_LOCAL_MACHINE) and not IsAdmin then
+ begin
+ LogError('Setting '+ID+' requires Administrator permissions to update.');
+ Exit(False);
+ end;
+
+ Setting.Reset;
+ if Setting.Modified then
+ begin
+ TKeymanSettingsManager.Save(Settings, IsAdmin);
+ Log('Keyman setting '+ID+' has been reset to default');
+ end
+ else
+ begin
+ LogWarning('Keyman setting '+ID+' was already default; not updating');
+ end;
+ end;
+
+ Result := True;
+end;
+
+class function TKMConfig.SetValue(const ID, Value: string): Boolean;
+var
+ Setting: TKeymanSetting;
+begin
+ Setting := Settings.Find(ID);
+
+ if not Assigned(Setting) then
+ begin
+ // Custom value, do we support it?
+ if ID.StartsWith(CustomKeymanSetting_TSFApp.ID, True) then
+ begin
+ Setting := TKeymanSetting.CreateCustom_TSFApp(ID);
+ Settings.Add(Setting);
+ end
+ else
+ begin
+ LogError(ID+' is not a valid Keyman setting.');
+ Exit(False);
+ end;
+ end;
+
+ if (Setting.Base.RootKey = HKEY_LOCAL_MACHINE) and not IsAdmin then
+ begin
+ LogError('Setting '+ID+' requires Administrator permissions to update.');
+ Exit(False);
+ end;
+
+ case Setting.Base.ValueType of
+ kstString: Setting.ValueStr := Value;
+ kstInteger: Setting.ValueInt := StrToInt(Value);
+ end;
+
+ TKeymanSettingsManager.Save(Settings, IsAdmin);
+ Log('Keyman setting '+ID+' has been updated to '+Value);
+ Result := True;
+end;
+
+class function TKMConfig.Show(const P2: string): Boolean;
+
+ function ShowOneSetting(const ID: string): Boolean;
+ var
+ Setting: TKeymanSetting;
+ v: string;
+ begin
+ Setting := Settings.Find(ID);
+ if not Assigned(Setting) then
+ begin
+ LogError('Setting '+ID+' is not a valid Keyman setting.');
+ Exit(False);
+ end;
+ case Setting.Base.ValueType of
+ kstString: v := Setting.ValueStr;
+ kstInteger: v := Setting.ValueInt.ToString;
+ end;
+ if Setting.IsEmpty
+ then Log(AnsiColor.Green+Setting.Base.ID+AnsiColor.Grey+'='+AnsiColor.Grey+v+AnsiColor.Default)
+ else Log(AnsiColor.Green+Setting.Base.ID+AnsiColor.Grey+'='+AnsiColor.White+v+AnsiColor.Default);
+ Result := True;
+ end;
+
+ procedure ShowAllSettings(ShowAll: Boolean);
+ var
+ Setting: TKeymanSetting;
+ begin
+ for Setting in Settings do
+ begin
+ if ShowAll or not Setting.IsEmpty then
+ ShowOneSetting(Setting.Base.ID);
+ end;
+ end;
+begin
+ Result := True;
+ TKeymanSettingsManager.Load(Settings);
+ if SameText(P2, '-a') then
+ ShowAllSettings(True)
+ else if P2 = '' then
+ ShowAllSettings(False)
+ else
+ Result := ShowOneSetting(P2);
+end;
+
+function DetectColorMode: Boolean;
+var
+ mode: DWORD;
+ hConsole: THandle;
+const
+ ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4;
+begin
+ Result := False;
+ mode := 0;
+ hConsole := GetStdHandle(STD_OUTPUT_HANDLE);
+ if hConsole = INVALID_HANDLE_VALUE then
+ begin
+ //writeln(Format('GetStdHandle failed with %d %s', [GetLastError, SysErrorMessage(GetLastError)]));
+ Exit;
+ end;
+
+ if GetEnvironmentVariable('MSYSTEM') = 'MINGW64' then
+ begin
+ // MinGW64 test
+ // Use colour mode only with a non-redirected console. This test fails with pipes.
+ // For pipe use, explicitly use -no-color parameter
+ Result := GetFileType(hConsole) = 3;
+ end
+ else
+ begin
+ // Win32 console color mode test
+ if not GetConsoleMode(hConsole, mode) then
+ Exit;
+
+ mode := mode or ENABLE_VIRTUAL_TERMINAL_PROCESSING;
+ if not SetConsoleMode(hConsole, mode) then
+ Exit;
+
+ Result := True;
+ end;
+end;
+
+const
+ ESC=#$1b;
+ ESC_BRIGHT_YELLOW=ESC+'[38;2;255;255;0m';
+ ESC_RED=ESC+'[38;2;255;0;0m';
+ ESC_GREEN=ESC+'[38;2;0;255;0m';
+ ESC_GREY=ESC+'[38;2;128;132;128m';
+ ESC_WHITE=ESC+'[38;2;255;255;255m';
+ ESC_DEFAULT=ESC+'[0m';
+
+initialization
+ if DetectColorMode then
+ begin
+ ANSIColor.Green := ESC_GREEN;
+ ANSIColor.Default := ESC_DEFAULT;
+ ANSIColor.Red := ESC_RED;
+ ANSIColor.Yellow := ESC_BRIGHT_YELLOW;
+ ANSIColor.Grey := ESC_GREY;
+ ANSIColor.White := ESC_WHITE;
+ end;
+end.
diff --git a/windows/src/desktop/kmconfig/Makefile b/windows/src/desktop/kmconfig/Makefile
new file mode 100644
index 0000000000..d7ef95a126
--- /dev/null
+++ b/windows/src/desktop/kmconfig/Makefile
@@ -0,0 +1,32 @@
+#
+# KMConfig Makefile
+#
+
+!include ..\..\Defines.mak
+
+build: version.res manifest.res #icons
+ $(DELPHI_MSBUILD) kmconfig.dproj /p:Platform=Win32
+ $(TDS2DBG) $(WIN32_TARGET_PATH)\kmconfig.exe
+ $(SENTRYTOOL_DELPHIPREP) $(WIN32_TARGET_PATH)\kmconfig.exe -dpr kmconfig.exe
+ $(COPY) $(WIN32_TARGET_PATH)\kmconfig.exe $(PROGRAM)\desktop
+ if exist $(WIN32_TARGET_PATH)\kmconfig.exe $(COPY) $(WIN32_TARGET_PATH)\kmconfig.exe $(DEBUGPATH)\desktop
+
+#icons:
+ #rc icons.rc
+
+clean: def-clean
+
+signcode:
+ $(SIGNCODE) /d "Keyman for Windows" $(PROGRAM)\desktop\kmconfig.exe
+
+backup:
+ $(WZZIP) $(BUILD)\desktop\kmconfig.zip $(BACKUPDEFAULTS) kmconfig.exe
+
+test-manifest:
+ # test that (a) linked manifest exists and correct, and (b) has uiAccess=true
+ $(MT) -nologo -inputresource:$(PROGRAM)\desktop\kmconfig.exe -validate_manifest
+
+install:
+ copy $(ROOT)\bin\desktop\kmconfig.exe "$(INSTALLPATH_KEYMANDESKTOP)"
+
+!include ..\..\Target.mak
diff --git a/windows/src/desktop/kmconfig/elevation.png b/windows/src/desktop/kmconfig/elevation.png
new file mode 100644
index 0000000000..45b02ced86
Binary files /dev/null and b/windows/src/desktop/kmconfig/elevation.png differ
diff --git a/windows/src/desktop/kmconfig/kmconfig.dpr b/windows/src/desktop/kmconfig/kmconfig.dpr
new file mode 100644
index 0000000000..dc9b15843d
--- /dev/null
+++ b/windows/src/desktop/kmconfig/kmconfig.dpr
@@ -0,0 +1,43 @@
+program kmconfig;
+
+uses
+ System.SysUtils,
+ Keyman.System.SettingsManager in '..\..\global\delphi\general\Keyman.System.SettingsManager.pas',
+ RegistryKeys in '..\..\global\delphi\general\RegistryKeys.pas',
+ KeymanVersion in '..\..\global\delphi\general\KeymanVersion.pas',
+ Keyman.System.Settings in '..\..\global\delphi\general\Keyman.System.Settings.pas',
+ Keyman.System.KeymanSentryClient in '..\..\global\delphi\general\Keyman.System.KeymanSentryClient.pas',
+ Sentry.Client.Console in '..\..\ext\sentry\Sentry.Client.Console.pas',
+ Sentry.Client in '..\..\ext\sentry\Sentry.Client.pas',
+ sentry in '..\..\ext\sentry\sentry.pas',
+ KeymanPaths in '..\..\global\delphi\general\KeymanPaths.pas',
+ DebugPaths in '..\..\global\delphi\general\DebugPaths.pas',
+ utilexecute in '..\..\global\delphi\general\utilexecute.pas',
+ unicode in '..\..\global\delphi\general\unicode.pas',
+ ErrorControlledRegistry in '..\..\global\delphi\vcl\ErrorControlledRegistry.pas',
+ Keyman.System.KMConfigMain in 'Keyman.System.KMConfigMain.pas',
+ Keyman.System.SettingsManagerFile in '..\..\global\delphi\general\Keyman.System.SettingsManagerFile.pas';
+
+{$R manifest.res}
+{$R version.res}
+{$APPTYPE CONSOLE}
+
+const
+ LOGGER_DESKTOP_KMCONFIG = TKeymanSentryClient.LOGGER_DESKTOP + '.kmconfig';
+begin
+ TKeymanSentryClient.Start(TSentryClientConsole, kscpDesktop, LOGGER_DESKTOP_KMCONFIG);
+ try
+ try
+ TKeymanSentryClient.Validate;
+ TKMConfig.Run;
+ except
+ on E: Exception do
+ begin
+ SentryHandleException(E);
+ ExitCode := 1;
+ end;
+ end;
+ finally
+ TKeymanSentryClient.Stop;
+ end;
+end.
diff --git a/windows/src/desktop/kmconfig/kmconfig.dproj b/windows/src/desktop/kmconfig/kmconfig.dproj
new file mode 100644
index 0000000000..f2b1664a93
--- /dev/null
+++ b/windows/src/desktop/kmconfig/kmconfig.dproj
@@ -0,0 +1,1000 @@
+
+
+ {6C5626E9-4DA0-4A60-93CB-D5BDE0B3D29E}
+ 18.8
+ VCL
+ kmconfig.dpr
+ True
+ Debug
+ Win32
+ 1
+ Application
+
+
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Cfg_1
+ true
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Cfg_2
+ true
+ true
+
+
+ .\$(Platform)\$(Config)
+ .\$(Platform)\$(Config)
+ false
+ false
+ false
+ false
+ false
+ System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace)
+ $(BDS)\bin\delphi_PROJECTICON.ico
+ $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png
+ $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png
+ kmconfig
+
+
+ DBXSqliteDriver;IndyIPCommon;RESTComponents;bindcompdbx;DBXInterBaseDriver;vcl;IndyIPServer;vclactnband;vclFireDAC;IndySystem;tethering;svnui;mbColorLibD10;dsnapcon;FireDACADSDriver;scFontCombo;DCPdelphi2009;FireDACMSAccDriver;fmxFireDAC;vclimg;Jcl;FireDAC;vcltouch;JvCore;vcldb;bindcompfmx;svn;FireDACSqliteDriver;FireDACPgDriver;inetdb;CEF4Delphi;soaprtl;DbxCommonDriver;fmx;FireDACIBDriver;fmxdae;xmlrtl;soapmidas;fmxobj;vclwinx;rtl;DbxClientDriver;CustomIPTransport;vcldsnap;dbexpress;IndyCore;vclx;bindcomp;appanalytics;dsnap;FireDACCommon;IndyIPClient;bindcompvcl;RESTBackendComponents;EmbeddedWebBrowser_XE;VCLRESTComponents;soapserver;dbxcds;VclSmp;JvDocking;adortl;JclVcl;vclie;bindengine;DBXMySQLDriver;CloudService;dsnapxml;FireDACMySQLDriver;dbrtl;IndyProtocols;inetdbxpress;keyman_components;FireDACCommonODBC;FireDACCommonDriver;inet;fmxase;$(DCC_UsePackage)
+ Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)
+ Debug
+ true
+ CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=
+ 1033
+ $(BDS)\bin\default_app.manifest
+
+
+ DBXSqliteDriver;IndyIPCommon;RESTComponents;bindcompdbx;DBXInterBaseDriver;vcl;IndyIPServer;vclactnband;vclFireDAC;IndySystem;tethering;dsnapcon;FireDACADSDriver;FireDACMSAccDriver;fmxFireDAC;vclimg;Jcl;FireDAC;vcltouch;vcldb;bindcompfmx;FireDACSqliteDriver;FireDACPgDriver;inetdb;soaprtl;DbxCommonDriver;fmx;FireDACIBDriver;fmxdae;xmlrtl;soapmidas;fmxobj;vclwinx;rtl;DbxClientDriver;CustomIPTransport;vcldsnap;dbexpress;IndyCore;vclx;bindcomp;appanalytics;dsnap;FireDACCommon;IndyIPClient;bindcompvcl;RESTBackendComponents;VCLRESTComponents;soapserver;dbxcds;VclSmp;adortl;JclVcl;vclie;bindengine;DBXMySQLDriver;CloudService;dsnapxml;FireDACMySQLDriver;dbrtl;IndyProtocols;inetdbxpress;FireDACCommonODBC;FireDACCommonDriver;inet;fmxase;$(DCC_UsePackage)
+
+
+ DEBUG;$(DCC_Define)
+ true
+ false
+ true
+ true
+ true
+
+
+ true
+ PerMonitorV2
+ true
+ 1033
+ true
+ 3
+ reset engine.compatibility.text_services_framework.notepad.exe 0
+ true
+ Keyman Config
+
+
+ false
+ RELEASE;$(DCC_Define)
+ 0
+ 0
+
+
+ true
+ PerMonitorV2
+ true
+ 3
+ true
+ 1033
+ 2
+ Keyman Config
+ true
+ true
+ true
+
+
+
+ MainSource
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Cfg_2
+ Base
+
+
+ Base
+
+
+ Cfg_1
+ Base
+
+
+
+ Delphi.Personality.12
+ Application
+
+
+
+ kmconfig.dpr
+
+
+ Microsoft Office 2000 Sample Automation Server Wrapper Components
+ Microsoft Office XP Sample Automation Server Wrapper Components
+
+
+
+
+
+ kmconfig.exe
+ true
+
+
+
+
+ 1
+
+
+ Contents\MacOS
+ 1
+
+
+ 0
+
+
+
+
+ classes
+ 1
+
+
+ classes
+ 1
+
+
+
+
+ res\xml
+ 1
+
+
+ res\xml
+ 1
+
+
+
+
+ library\lib\armeabi-v7a
+ 1
+
+
+
+
+ library\lib\armeabi
+ 1
+
+
+ library\lib\armeabi
+ 1
+
+
+
+
+ library\lib\armeabi-v7a
+ 1
+
+
+
+
+ library\lib\mips
+ 1
+
+
+ library\lib\mips
+ 1
+
+
+
+
+ library\lib\armeabi-v7a
+ 1
+
+
+ library\lib\arm64-v8a
+ 1
+
+
+
+
+ library\lib\armeabi-v7a
+ 1
+
+
+
+
+ res\drawable
+ 1
+
+
+ res\drawable
+ 1
+
+
+
+
+ res\values
+ 1
+
+
+ res\values
+ 1
+
+
+
+
+ res\values-v21
+ 1
+
+
+ res\values-v21
+ 1
+
+
+
+
+ res\values
+ 1
+
+
+ res\values
+ 1
+
+
+
+
+ res\drawable
+ 1
+
+
+ res\drawable
+ 1
+
+
+
+
+ res\drawable-xxhdpi
+ 1
+
+
+ res\drawable-xxhdpi
+ 1
+
+
+
+
+ res\drawable-ldpi
+ 1
+
+
+ res\drawable-ldpi
+ 1
+
+
+
+
+ res\drawable-mdpi
+ 1
+
+
+ res\drawable-mdpi
+ 1
+
+
+
+
+ res\drawable-hdpi
+ 1
+
+
+ res\drawable-hdpi
+ 1
+
+
+
+
+ res\drawable-xhdpi
+ 1
+
+
+ res\drawable-xhdpi
+ 1
+
+
+
+
+ res\drawable-mdpi
+ 1
+
+
+ res\drawable-mdpi
+ 1
+
+
+
+
+ res\drawable-hdpi
+ 1
+
+
+ res\drawable-hdpi
+ 1
+
+
+
+
+ res\drawable-xhdpi
+ 1
+
+
+ res\drawable-xhdpi
+ 1
+
+
+
+
+ res\drawable-xxhdpi
+ 1
+
+
+ res\drawable-xxhdpi
+ 1
+
+
+
+
+ res\drawable-xxxhdpi
+ 1
+
+
+ res\drawable-xxxhdpi
+ 1
+
+
+
+
+ res\drawable-small
+ 1
+
+
+ res\drawable-small
+ 1
+
+
+
+
+ res\drawable-normal
+ 1
+
+
+ res\drawable-normal
+ 1
+
+
+
+
+ res\drawable-large
+ 1
+
+
+ res\drawable-large
+ 1
+
+
+
+
+ res\drawable-xlarge
+ 1
+
+
+ res\drawable-xlarge
+ 1
+
+
+
+
+ res\values
+ 1
+
+
+ res\values
+ 1
+
+
+
+
+ 1
+
+
+ Contents\MacOS
+ 1
+
+
+ 0
+
+
+
+
+ Contents\MacOS
+ 1
+ .framework
+
+
+ Contents\MacOS
+ 1
+ .framework
+
+
+ 0
+
+
+
+
+ 1
+ .dylib
+
+
+ 1
+ .dylib
+
+
+ 1
+ .dylib
+
+
+ Contents\MacOS
+ 1
+ .dylib
+
+
+ Contents\MacOS
+ 1
+ .dylib
+
+
+ 0
+ .dll;.bpl
+
+
+
+
+ 1
+ .dylib
+
+
+ 1
+ .dylib
+
+
+ 1
+ .dylib
+
+
+ Contents\MacOS
+ 1
+ .dylib
+
+
+ Contents\MacOS
+ 1
+ .dylib
+
+
+ 0
+ .bpl
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ Contents\Resources\StartUp\
+ 0
+
+
+ Contents\Resources\StartUp\
+ 0
+
+
+ 0
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF
+ 1
+
+
+ ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+
+
+ ..\
+ 1
+
+
+ ..\
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF
+ 1
+
+
+
+
+ ..\
+ 1
+
+
+ ..\
+ 1
+
+
+
+
+ Contents
+ 1
+
+
+ Contents
+ 1
+
+
+
+
+ Contents\Resources
+ 1
+
+
+ Contents\Resources
+ 1
+
+
+
+
+ library\lib\armeabi-v7a
+ 1
+
+
+ library\lib\arm64-v8a
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ Contents\MacOS
+ 1
+
+
+ Contents\MacOS
+ 1
+
+
+ 0
+
+
+
+
+ library\lib\armeabi-v7a
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+
+
+ Assets
+ 1
+
+
+ Assets
+ 1
+
+
+
+
+ Assets
+ 1
+
+
+ Assets
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ False
+
+
+ 12
+
+
+
+
+
diff --git a/windows/src/desktop/kmconfig/kmconfig.res b/windows/src/desktop/kmconfig/kmconfig.res
new file mode 100644
index 0000000000..18c1ffa18f
Binary files /dev/null and b/windows/src/desktop/kmconfig/kmconfig.res differ
diff --git a/windows/src/desktop/kmconfig/manifest.in b/windows/src/desktop/kmconfig/manifest.in
new file mode 100644
index 0000000000..718cf61b55
--- /dev/null
+++ b/windows/src/desktop/kmconfig/manifest.in
@@ -0,0 +1,28 @@
+
+
+
+ Keyman for Windows Config
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/windows/src/desktop/kmconfig/manifest.rc b/windows/src/desktop/kmconfig/manifest.rc
new file mode 100644
index 0000000000..984fce31b2
--- /dev/null
+++ b/windows/src/desktop/kmconfig/manifest.rc
@@ -0,0 +1 @@
+1 24 manifest.xml
diff --git a/windows/src/desktop/kmconfig/version.in b/windows/src/desktop/kmconfig/version.in
new file mode 100644
index 0000000000..6b4e6dfe97
--- /dev/null
+++ b/windows/src/desktop/kmconfig/version.in
@@ -0,0 +1,30 @@
+1 VERSIONINFO
+ FILEVERSION $VERSIONNUM
+ PRODUCTVERSION $VERSIONNUM
+ FILEFLAGSMASK 0x3fL
+ FILEFLAGS 0x0L
+ FILEOS 0x4L
+ FILETYPE 0x1L
+ FILESUBTYPE 0x0L
+ BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "0C0904E4"
+ BEGIN
+ VALUE "CompanyName", "SIL International\0"
+ VALUE "FileDescription", "Keyman Config\0"
+ VALUE "FileVersion", "$VERSION\0"
+ VALUE "InternalName", "KMCONFIG\0"
+ VALUE "LegalCopyright", "© SIL International\0"
+ VALUE "LegalTrademarks", "\0"
+ VALUE "OriginalFilename", "KMCONFIG.EXE\0"
+ VALUE "ProductName", "Keyman for Windows\0"
+ VALUE "ProductVersion", "$VERSION\0"
+ VALUE "Comments", "\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0xc09, 1252
+ END
+ END