2020-04-09 04:53:09 +08:00
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
2020-04-15 00:24:11 +08:00
2020-04-20 23:22:36 +08:00
using System ;
2020-04-23 05:55:45 +08:00
using System.Collections.Generic ;
2020-04-20 23:22:36 +08:00
using System.IO ;
2020-04-23 05:55:45 +08:00
using System.Linq ;
2020-04-20 23:22:36 +08:00
using System.Threading ;
2020-04-15 00:24:11 +08:00
using System.Threading.Tasks ;
using System.Windows.Input ;
using Microsoft.PowerToys.Settings.UI.Helpers ;
2020-04-20 23:22:36 +08:00
using Microsoft.PowerToys.Settings.UI.Lib ;
2020-04-15 00:24:11 +08:00
using Microsoft.PowerToys.Settings.UI.Lib.Utilities ;
using Microsoft.PowerToys.Settings.UI.Views ;
2020-04-23 05:55:45 +08:00
using Microsoft.Toolkit.Uwp.Helpers ;
2020-05-12 08:18:12 +08:00
using Windows.System ;
2020-04-23 05:55:45 +08:00
using Windows.UI.Core ;
using Windows.UI.Xaml ;
2020-04-15 00:24:11 +08:00
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
public class KeyboardManagerViewModel : Observable
{
2020-04-20 23:22:36 +08:00
private const string PowerToyName = "Keyboard Manager" ;
private const string RemapKeyboardActionName = "RemapKeyboard" ;
private const string RemapKeyboardActionValue = "Open Remap Keyboard Window" ;
private const string EditShortcutActionName = "EditShortcut" ;
private const string EditShortcutActionValue = "Open Edit Shortcut Window" ;
private const string JsonFileType = ".json" ;
2020-04-23 05:55:45 +08:00
private const string ProfileFileMutexName = "PowerToys.KeyboardManager.ConfigMutex" ;
private const int ProfileFileMutexWaitTimeoutMilliseconds = 1000 ;
private readonly CoreDispatcher dispatcher ;
private readonly FileSystemWatcher watcher ;
2020-04-20 23:22:36 +08:00
2020-04-15 00:24:11 +08:00
private ICommand remapKeyboardCommand ;
private ICommand editShortcutCommand ;
2020-04-20 23:22:36 +08:00
private KeyboardManagerSettings settings ;
2020-04-23 05:55:45 +08:00
private KeyboardManagerProfile profile ;
private GeneralSettings generalSettings ;
2020-04-15 00:24:11 +08:00
public KeyboardManagerViewModel ( )
{
2020-04-23 05:55:45 +08:00
dispatcher = Window . Current . Dispatcher ;
2020-04-20 23:22:36 +08:00
if ( SettingsUtils . SettingsExists ( PowerToyName ) )
{
// Todo: Be more resillent while reading and saving settings.
settings = SettingsUtils . GetSettings < KeyboardManagerSettings > ( PowerToyName ) ;
2020-04-23 05:55:45 +08:00
// Load profile.
if ( ! LoadProfile ( ) )
{
profile = new KeyboardManagerProfile ( ) ;
}
2020-04-20 23:22:36 +08:00
}
else
{
settings = new KeyboardManagerSettings ( PowerToyName ) ;
SettingsUtils . SaveSettings ( settings . ToJsonString ( ) , PowerToyName ) ;
}
2020-04-23 05:55:45 +08:00
if ( SettingsUtils . SettingsExists ( ) )
{
generalSettings = SettingsUtils . GetSettings < GeneralSettings > ( string . Empty ) ;
}
else
{
generalSettings = new GeneralSettings ( ) ;
SettingsUtils . SaveSettings ( generalSettings . ToJsonString ( ) , string . Empty ) ;
}
watcher = Helper . GetFileWatcher (
PowerToyName ,
settings . Properties . ActiveConfiguration . Value + JsonFileType ,
OnConfigFileUpdate ) ;
}
public bool Enabled
{
get
{
return generalSettings . Enabled . KeyboardManager ;
}
set
{
if ( generalSettings . Enabled . KeyboardManager ! = value )
{
generalSettings . Enabled . KeyboardManager = value ;
OnPropertyChanged ( nameof ( Enabled ) ) ;
OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings ( generalSettings ) ;
ShellPage . DefaultSndMSGCallback ( outgoing . ToString ( ) ) ;
}
}
2020-04-15 00:24:11 +08:00
}
2020-04-23 05:55:45 +08:00
// store remappings
public List < KeysDataModel > RemapKeys
{
get
{
if ( profile ! = null )
{
return profile . RemapKeys . InProcessRemapKeys ;
}
else
{
return new List < KeysDataModel > ( ) ;
}
}
}
public List < KeysDataModel > RemapShortcuts
{
get
{
if ( profile ! = null )
{
return profile . RemapShortcuts . GlobalRemapShortcuts ;
}
else
{
return new List < KeysDataModel > ( ) ;
}
}
}
public ICommand RemapKeyboardCommand = > remapKeyboardCommand ? ? ( remapKeyboardCommand = new RelayCommand ( OnRemapKeyboard ) ) ;
public ICommand EditShortcutCommand = > editShortcutCommand ? ? ( editShortcutCommand = new RelayCommand ( OnEditShortcut ) ) ;
2020-04-15 00:24:11 +08:00
private async void OnRemapKeyboard ( )
{
await Task . Run ( ( ) = > OnRemapKeyboardBackground ( ) ) ;
}
private async void OnEditShortcut ( )
{
await Task . Run ( ( ) = > OnEditShortcutBackground ( ) ) ;
}
private async Task OnRemapKeyboardBackground ( )
{
Helper . AllowRunnerToForeground ( ) ;
2020-04-20 23:22:36 +08:00
ShellPage . DefaultSndMSGCallback ( Helper . GetSerializedCustomAction ( PowerToyName , RemapKeyboardActionName , RemapKeyboardActionValue ) ) ;
2020-04-15 00:24:11 +08:00
await Task . CompletedTask ;
}
private async Task OnEditShortcutBackground ( )
{
Helper . AllowRunnerToForeground ( ) ;
2020-04-20 23:22:36 +08:00
ShellPage . DefaultSndMSGCallback ( Helper . GetSerializedCustomAction ( PowerToyName , EditShortcutActionName , EditShortcutActionValue ) ) ;
2020-04-15 00:24:11 +08:00
await Task . CompletedTask ;
}
2020-04-20 23:22:36 +08:00
2020-04-23 05:55:45 +08:00
private async void OnConfigFileUpdate ( )
2020-04-20 23:22:36 +08:00
{
// Note: FileSystemWatcher raise notification mutiple times for single update operation.
// Todo: Handle duplicate events either by somehow supress them or re-read the configuration everytime since we will be updating the UI only if something is changed.
2020-04-23 05:55:45 +08:00
if ( LoadProfile ( ) )
{
await dispatcher . RunAsync ( CoreDispatcherPriority . Normal , ( ) = >
{
OnPropertyChanged ( nameof ( RemapKeys ) ) ;
OnPropertyChanged ( nameof ( RemapShortcuts ) ) ;
} ) ;
}
2020-04-20 23:22:36 +08:00
}
2020-04-23 05:55:45 +08:00
private bool LoadProfile ( )
2020-04-20 23:22:36 +08:00
{
2020-04-23 05:55:45 +08:00
var success = true ;
2020-04-20 23:22:36 +08:00
try
{
2020-04-23 05:55:45 +08:00
using ( var profileFileMutex = Mutex . OpenExisting ( ProfileFileMutexName ) )
2020-04-20 23:22:36 +08:00
{
2020-04-23 05:55:45 +08:00
if ( profileFileMutex . WaitOne ( ProfileFileMutexWaitTimeoutMilliseconds ) )
2020-04-20 23:22:36 +08:00
{
// update the UI element here.
try
{
2020-04-23 05:55:45 +08:00
profile = SettingsUtils . GetSettings < KeyboardManagerProfile > ( PowerToyName , settings . Properties . ActiveConfiguration . Value + JsonFileType ) ;
2020-05-12 08:18:12 +08:00
FilterRemapKeysList ( profile . RemapKeys . InProcessRemapKeys ) ;
2020-04-20 23:22:36 +08:00
}
finally
{
// Make sure to release the mutex.
2020-04-23 05:55:45 +08:00
profileFileMutex . ReleaseMutex ( ) ;
2020-04-20 23:22:36 +08:00
}
}
2020-04-23 05:55:45 +08:00
else
{
success = false ;
}
2020-04-20 23:22:36 +08:00
}
}
catch ( Exception )
{
// Failed to load the configuration.
2020-04-23 05:55:45 +08:00
success = false ;
2020-04-20 23:22:36 +08:00
}
2020-04-23 05:55:45 +08:00
return success ;
2020-04-20 23:22:36 +08:00
}
2020-05-12 08:18:12 +08:00
private void FilterRemapKeysList ( List < KeysDataModel > remapKeysList )
{
CombineRemappings ( remapKeysList , ( uint ) VirtualKey . LeftControl , ( uint ) VirtualKey . RightControl , ( uint ) VirtualKey . Control ) ;
CombineRemappings ( remapKeysList , ( uint ) VirtualKey . LeftMenu , ( uint ) VirtualKey . RightMenu , ( uint ) VirtualKey . Menu ) ;
CombineRemappings ( remapKeysList , ( uint ) VirtualKey . LeftShift , ( uint ) VirtualKey . RightShift , ( uint ) VirtualKey . Shift ) ;
CombineRemappings ( remapKeysList , ( uint ) VirtualKey . LeftWindows , ( uint ) VirtualKey . RightWindows , Helper . VirtualKeyWindows ) ;
}
private void CombineRemappings ( List < KeysDataModel > remapKeysList , uint leftKey , uint rightKey , uint combinedKey )
{
KeysDataModel firstRemap = remapKeysList . Find ( x = > uint . Parse ( x . OriginalKeys ) = = leftKey ) ;
KeysDataModel secondRemap = remapKeysList . Find ( x = > uint . Parse ( x . OriginalKeys ) = = rightKey ) ;
if ( firstRemap ! = null & & secondRemap ! = null )
{
if ( firstRemap . NewRemapKeys = = secondRemap . NewRemapKeys )
{
KeysDataModel combinedRemap = new KeysDataModel
{
OriginalKeys = combinedKey . ToString ( ) ,
NewRemapKeys = firstRemap . NewRemapKeys ,
} ;
remapKeysList . Insert ( remapKeysList . IndexOf ( firstRemap ) , combinedRemap ) ;
remapKeysList . Remove ( firstRemap ) ;
remapKeysList . Remove ( secondRemap ) ;
}
}
}
2020-04-15 00:24:11 +08:00
}
}