diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index eb0527ab..00000000
--- a/.gitignore
+++ /dev/null
@@ -1,9 +0,0 @@
-.vs/
-*.csproj.user
-bin/
-obj/
-Morphic.*Setup/Generated.wxs
-Morphic.Client/appsettings.Local.json
-Morphic.Client/appsettings.json
-Morphic.Client/BuildVersion.txt
-Morphic.Bar
diff --git a/LICENSE.txt b/LICENSE.txt
index d19159d7..e948c92f 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,10 +1,34 @@
-Copyright 2020 Raising the Floor - International
+BSD 3-Clause License
+
+Copyright 2020-2021, Raising the Floor - US
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-Licensed under the New BSD license. You may not use this file except in
-compliance with this License.
-You may obtain a copy of the License at
-https://github.com/GPII/universal/blob/master/LICENSE.txt
The R&D leading to these results received funding from the:
* Rehabilitation Services Administration, US Dept. of Education under
@@ -19,4 +43,4 @@ The R&D leading to these results received funding from the:
* Ontario Ministry of Research and Innovation
* Canadian Foundation for Innovation
* Adobe Foundation
-* Consumer Electronics Association Foundation
\ No newline at end of file
+* Consumer Electronics Association Foundation
diff --git a/Morphic.Client/App.xaml b/Morphic.Client/App.xaml
deleted file mode 100644
index eca72e28..00000000
--- a/Morphic.Client/App.xaml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
diff --git a/Morphic.Client/App.xaml.cs b/Morphic.Client/App.xaml.cs
deleted file mode 100644
index 4079828e..00000000
--- a/Morphic.Client/App.xaml.cs
+++ /dev/null
@@ -1,935 +0,0 @@
-// Copyright 2020-2021 Raising the Floor - International
-//
-// Licensed under the New BSD license. You may not use this file except in
-// compliance with this License.
-//
-// You may obtain a copy of the License at
-// https://github.com/GPII/universal/blob/master/LICENSE.txt
-//
-// The R&D leading to these results received funding from the:
-// * Rehabilitation Services Administration, US Dept. of Education under
-// grant H421A150006 (APCP)
-// * National Institute on Disability, Independent Living, and
-// Rehabilitation Research (NIDILRR)
-// * Administration for Independent Living & Dept. of Education under grants
-// H133E080022 (RERC-IT) and H133E130028/90RE5003-01-00 (UIITA-RERC)
-// * European Union's Seventh Framework Programme (FP7/2007-2013) grant
-// agreement nos. 289016 (Cloud4all) and 610510 (Prosperity4All)
-// * William and Flora Hewlett Foundation
-// * Ontario Ministry of Research and Innovation
-// * Canadian Foundation for Innovation
-// * Adobe Foundation
-// * Consumer Electronics Association Foundation
-
-using AutoUpdaterDotNET;
-using CountlySDK;
-using CountlySDK.Entities;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
-using Morphic.Core;
-using Morphic.Service;
-using NHotkey.Wpf;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Reflection;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Input;
-using System.Windows.Threading;
-
-namespace Morphic.Client
-{
- using Bar;
- using Bar.Data;
- using Config;
- using CountlySDK.CountlyCommon;
- using Dialogs;
- using Menu;
- using Microsoft.Win32;
- using Settings.SettingsHandlers;
- using Settings.SolutionsRegistry;
- using System.Text.Json;
-
- public class AppMain
- {
- private static Mutex _singleInstanceMutex;
- private static uint _singleInstanceMessageId;
-
- // NOTE: we created our own Main function so that we can use a mutex to enforce running only one instance of Morphic at a time
- [STAThread]
- public static void Main()
- {
- // create a message which we can send/receive to indicate that a secondary instance has been started; use the application ID as its backing unique string
- _singleInstanceMessageId = WinApi.RegisterWindowMessage(App.ApplicationId);
-
- // create a mutex which we will use to make sure only one copy of Morphic runs at a time
- bool mutexCreatedNew;
- _singleInstanceMutex = new Mutex(true, App.ApplicationId, out mutexCreatedNew);
-
- // if the mutex already existed (i.e. the application is already running), send a message to it now asking it to show its MorphicBar
- if (mutexCreatedNew == false)
- {
- // send the "single instance" message to the main instance; leave both parameters as zero
- MessageWatcherNativeWindow.PostMessage(_singleInstanceMessageId, IntPtr.Zero, IntPtr.Zero);
-
- // shut down our application (gracefully by returning from Main)
- return;
- }
-
- // Ensure the current directory is the same as the executable, so relative paths work.
- Directory.SetCurrentDirectory(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
-
- App.Main();
- }
-
- internal static void ReleaseSingleInstanceMutex()
- {
- _singleInstanceMutex.ReleaseMutex();
- }
-
- internal static uint SingleInstanceMessageId
- {
- get
- {
- return _singleInstanceMessageId;
- }
- }
- }
-
- ///
- /// Interaction logic for App.xaml
- ///
- public partial class App : Application
- {
- /// Current application instance.
- public new static App Current { get; private set; } = null!;
-
- public IServiceProvider ServiceProvider { get; private set; } = null!;
- public IConfiguration Configuration { get; private set; } = null!;
- public ILogger Logger { get; private set; } = null!;
-
- public MorphicSession MorphicSession { get; private set; } = null!;
-
- public AppOptions AppOptions => AppOptions.Current;
-
- public DialogManager Dialogs { get; } = new DialogManager();
- public BarManager BarManager { get; } = new BarManager();
-
- public const string ApplicationId = "A6E8092B-51F4-4CAA-A874-A791152B5698";
-
- #region Configuration & Startup
-
- public App()
- {
- App.Current = this;
- }
-
- public class MorphicBarExtraItem
- {
- public string? type { get; set; }
- public string? label { get; set; }
- public string? tooltipHeader { get; set; }
- public string? tooltipText { get; set; }
- // for type: link
- public string? url { get; set; }
- // for type: action
- public string? function { get; set; }
- }
-
- public class ConfigFileContents
- {
- public class FeaturesConfigSection
- {
- public class EnabledFeature
- {
- public bool? enabled { get; set; }
- public string? scope { get; set; }
- }
- //
- public EnabledFeature? autorunAfterLogin { get; set; }
- public EnabledFeature? checkForUpdates { get; set; }
- public EnabledFeature? cloudSettingsTransfer { get; set; }
- public EnabledFeature? resetSettings { get; set; }
- }
- public class MorphicBarConfigSection
- {
- public string? visibilityAfterLogin { get; set; }
- public List? extraItems { get; set; }
- }
- //
- public int? version { get; set; }
- public FeaturesConfigSection? features { get; set; }
- public MorphicBarConfigSection? morphicBar { get; set; }
- }
-
- private struct CommonConfigurationContents
- {
- public ConfigurableFeatures.AutorunConfigOption? AutorunConfig;
- public bool CheckForUpdatesIsEnabled;
- public bool CloudSettingsTransferIsEnabled;
- public bool ResetSettingsIsEnabled;
- public ConfigurableFeatures.MorphicBarVisibilityAfterLoginOption? MorphicBarVisibilityAfterLogin;
- public List ExtraMorphicBarItems;
- }
- private async Task GetCommonConfigurationAsync()
- {
- // set up default configuration
- var result = new CommonConfigurationContents();
- //
- // autorun
- result.AutorunConfig = null;
- //
- // check for updates
- result.CheckForUpdatesIsEnabled = true;
- //
- // copy settings to/from cloud
- result.CloudSettingsTransferIsEnabled = true;
- //
- // reset settings (to standard)
- result.ResetSettingsIsEnabled = false;
- //
- // morphic bar (visibility and extra items)
- result.MorphicBarVisibilityAfterLogin = null;
- result.ExtraMorphicBarItems = new List();
-
- // NOTE: we have intentionally chosen not to create the CommonConfigDir (e.g. "C:\ProgramData\Morphic") since Morphic does not currently create files in this folder.
- var morphicCommonConfigPath = AppPaths.GetCommonConfigDir("", false);
- if (Directory.Exists(morphicCommonConfigPath) == false)
- {
- // no config file; return defaults
- return result;
- }
-
- var morphicConfigFilePath = Path.Combine(morphicCommonConfigPath, "config.json");
- if (File.Exists(morphicConfigFilePath) == false)
- {
- // no config file; return defaults
- return result;
- }
-
- string json;
- try
- {
- json = await File.ReadAllTextAsync(morphicConfigFilePath);
- }
- catch (Exception ex)
- {
- // error reading config file; return defaults
- // NOTE: consider refusing to start up (for security reasons) if the configuration file cannot be read
- Logger?.LogError("Could not read configuration file: " + morphicConfigFilePath + "; error: " + ex.Message);
- return result;
- }
-
- ConfigFileContents deserializedJson;
- try
- {
- deserializedJson = JsonSerializer.Deserialize(json);
- }
- catch (Exception ex)
- {
- // NOTE: consider refusing to start up (for security reasons) if the configuration file cannot be read
- Logger?.LogError("Could not deserialize json configuration file: " + morphicConfigFilePath + "; error: " + ex.Message);
- return result;
- }
-
- if ((deserializedJson.version == null) || (deserializedJson.version.Value < 0) || (deserializedJson.version.Value > 0))
- {
- // sorry, we don't understand this version of the file
- // NOTE: consider refusing to start up (for security reasons) if the configuration file cannot be read
- Logger?.LogError("Unknown config file version: " + deserializedJson.version.ToString());
- return result;
- }
-
- // capture the autorun setting
- if (deserializedJson.features?.autorunAfterLogin?.enabled != null)
- {
- if (deserializedJson.features!.autorunAfterLogin!.enabled == false)
- {
- result.AutorunConfig = ConfigurableFeatures.AutorunConfigOption.Disabled;
- }
- else
- {
- switch (deserializedJson.features!.autorunAfterLogin!.scope)
- {
- case "allLocalUsers":
- result.AutorunConfig = ConfigurableFeatures.AutorunConfigOption.AllLocalUsers;
- break;
- case "currentUser":
- result.AutorunConfig = ConfigurableFeatures.AutorunConfigOption.CurrentUser;
- break;
- case null:
- // no scope present; use the default scope
- break;
- default:
- // sorry, we don't understand this scope setting
- // NOTE: consider refusing to start up (for security reasons) if the configuration file cannot be read
- Logger?.LogError("Unknown autorunAfterLogin scope: " + deserializedJson.features!.autorunAfterLogin!.scope);
- return result;
- }
- }
- }
-
- // capture the check for updates "is enabled" setting
- if (deserializedJson.features?.checkForUpdates?.enabled != null)
- {
- result.CheckForUpdatesIsEnabled = deserializedJson.features.checkForUpdates.enabled.Value;
- }
-
- // capture the cloud settings transfer "is enabled" setting
- if (deserializedJson.features?.cloudSettingsTransfer?.enabled != null)
- {
- result.CloudSettingsTransferIsEnabled = deserializedJson.features.cloudSettingsTransfer.enabled.Value;
- }
-
- // capture the reset settings (to standard) "is enabled" setting
- if (deserializedJson.features?.resetSettings?.enabled != null)
- {
- result.ResetSettingsIsEnabled = deserializedJson.features.resetSettings.enabled.Value;
- }
-
- // capture the desired after-login (autorun) visibility of the MorphicBar
- switch (deserializedJson.morphicBar?.visibilityAfterLogin)
- {
- case "restore":
- result.MorphicBarVisibilityAfterLogin = ConfigurableFeatures.MorphicBarVisibilityAfterLoginOption.Restore;
- break;
- case "show":
- result.MorphicBarVisibilityAfterLogin = ConfigurableFeatures.MorphicBarVisibilityAfterLoginOption.Show;
- break;
- case "hide":
- result.MorphicBarVisibilityAfterLogin = ConfigurableFeatures.MorphicBarVisibilityAfterLoginOption.Hide;
- break;
- case null:
- // no setting present; use the default setting
- break;
- default:
- // sorry, we don't understand this visibility setting
- // NOTE: consider refusing to start up (for security reasons) if the configuration file cannot be read
- Logger?.LogError("Unknown morphicBar.visibilityAfterLogin setting: " + deserializedJson.morphicBar?.visibilityAfterLogin);
- return result;
- }
-
-
- // capture any extra items (up to 3)
- if (deserializedJson.morphicBar?.extraItems != null)
- {
- foreach (var extraItem in deserializedJson.morphicBar!.extraItems)
- {
- // if we already captured 3 extra items, skip this one
- if (result.ExtraMorphicBarItems.Count >= 3)
- {
- continue;
- }
-
- var extraItemType = extraItem.type;
- var extraItemLabel = extraItem.label;
- var extraItemTooltipHeader = extraItem.tooltipHeader;
- var extraItemTooltipText = extraItem.tooltipText;
- // for type: link
- var extraItemUrl = extraItem.url;
- // for type: action
- var extraItemFunction = extraItem.function;
-
- // if the item is invalid, log the error and skip this item
- if ((extraItemType == null) || (extraItemLabel == null) || (extraItemTooltipHeader == null))
- {
- // NOTE: consider refusing to start up (for security reasons) if the configuration file cannot be read
- Logger?.LogError("Invalid MorphicBar item: " + extraItem.ToString());
- continue;
- }
-
- // if the "link" is missing its url, log the error and skip this item
- if ((extraItemType == "link") && (extraItemUrl == null))
- {
- // NOTE: consider refusing to start up (for security reasons) if the configuration file cannot be read
- Logger?.LogError("Invalid MorphicBar item: " + extraItem.ToString());
- continue;
- }
-
- // if the "action" is missing its function, log the error and skip this item
- if ((extraItemType == "action") && (extraItemFunction == null || extraItemFunction == ""))
- {
- // NOTE: consider refusing to start up (for security reasons) if the configuration file cannot be read
- Logger?.LogError("Invalid MorphicBar item: " + extraItem.ToString());
- continue;
- }
-
- var extraMorphicBarItem = new MorphicBarExtraItem();
- extraMorphicBarItem.type = extraItemType;
- extraMorphicBarItem.label = extraItemLabel;
- extraMorphicBarItem.tooltipHeader = extraItemTooltipHeader;
- extraMorphicBarItem.tooltipText = extraItemTooltipText;
- extraMorphicBarItem.url = extraItemUrl;
- extraMorphicBarItem.function = extraItemFunction;
- result.ExtraMorphicBarItems.Add(extraMorphicBarItem);
- }
- }
-
- return result;
- }
-
- ///
- /// Create a Configuration from appsettings.json
- ///
- ///
- private IConfiguration GetConfiguration()
- {
- ConfigurationBuilder builder = new ConfigurationBuilder();
- builder.SetBasePath(Directory.GetCurrentDirectory());
- builder.AddJsonFile("appsettings.json", optional: false);
- if (this.AppOptions.Launch.Debug)
- {
- builder.AddJsonFile("appsettings.Debug.json", optional: true);
- builder.AddJsonFile("appsettings.Local.json", optional: true);
- }
- builder.AddEnvironmentVariables();
- return builder.Build();
- }
-
- ///
- /// Configure the dependency injection system with services
- ///
- ///
- private void ConfigureServices(IServiceCollection services)
- {
- services.AddLogging(this.ConfigureLogging);
- services.Configure(this.Configuration.GetSection("MorphicService"));
- services.Configure(this.Configuration.GetSection("Update"));
- services.AddSingleton(services);
- services.AddSingleton(provider => provider);
- services.AddSingleton(serviceProvider => serviceProvider.GetRequiredService>().Value);
- services.AddSingleton(new StorageOptions { RootPath = AppPaths.GetUserLocalConfigDir("Data") });
- services.AddSingleton(new KeychainOptions { Path = AppPaths.GetUserLocalConfigDir("keychain") });
- services.AddSingleton(serviceProvider => serviceProvider.GetRequiredService>().Value);
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddSingleton();
- services.AddTransient();
- services.AddSingleton(s => BarPresets.Default);
- services.AddSolutionsRegistryServices();
- services.AddSingleton(s => Solutions.FromFile(s, AppPaths.GetAppFile("solutions.json5")));
- }
-
- private async Task ConfigureCountlyAsync()
- {
- // TODO: Move metrics related things to own class.
-
- // retrieve the telemetry device ID for this device; if it doesn't exist then create a new one
- var telemetryDeviceUuid = AppOptions.TelemetryDeviceUuid;
- if (telemetryDeviceUuid == null)
- {
- telemetryDeviceUuid = "D_" + Guid.NewGuid().ToString();
- AppOptions.TelemetryDeviceUuid = telemetryDeviceUuid;
- }
-
- IConfigurationSection? section = this.Configuration.GetSection("Countly");
- CountlyConfig cc = new CountlyConfig
- {
- serverUrl = section["ServerUrl"],
- appKey = section["AppKey"],
- appVersion = BuildInfo.Current.InformationalVersion,
- developerProvidedDeviceId = telemetryDeviceUuid,
- };
-
- await Countly.Instance.Init(cc);
- await Countly.Instance.SessionBegin();
- CountlyBase.IsLoggingEnabled = true;
- }
-
- private void RecordedException(Task task)
- {
- if (task.Exception is Exception e)
- {
- this.Logger.LogError("exception thrown while countly recording exception: {msg}", e.Message);
- throw e;
- }
- this.Logger.LogDebug("successfully recorded countly exception");
- }
-
- void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
- {
- // TODO: Improve error logging/reporting.
-
- Exception ex = e.Exception;
-
- try
- {
- this.Logger.LogError("handled uncaught exception: {msg}", ex.Message);
- this.Logger.LogError(ex.StackTrace);
-
- Dictionary extraData = new Dictionary();
- CountlyBase.RecordException(ex.Message, ex.StackTrace, extraData, true)
- .ContinueWith(this.RecordedException, TaskScheduler.FromCurrentSynchronizationContext());
- }
- catch (Exception)
- {
- // ignore
- }
-
- Console.WriteLine(ex);
-
- MessageBox.Show($"Morphic ran into a problem:\n\n{e.Exception.Message}\n\nFurther information:\n{e.Exception}", "Morphic", MessageBoxButton.OK, MessageBoxImage.Warning);
-
- // This prevents the exception from crashing the application
- e.Handled = true;
- }
-
- ///
- /// Configure the logging for the application
- ///
- ///
- private void ConfigureLogging(ILoggingBuilder logging)
- {
- logging.AddConfiguration(this.Configuration);
- logging.AddConsole();
- logging.AddFile(this.AppOptions.Launch.Logfile, options =>
- {
- options.Append = true;
- options.FileSizeLimitBytes = 0x100000;
- options.MaxRollingFiles = 3;
- });
- logging.SetMinimumLevel(LogLevel.Debug);
- logging.AddDebug();
- }
-
- protected override async void OnStartup(StartupEventArgs e)
- {
- this.Dispatcher.UnhandledException += this.App_DispatcherUnhandledException;
-
- this.Configuration = this.GetConfiguration();
- ServiceCollection collection = new ServiceCollection();
- this.ConfigureServices(collection);
- this.ServiceProvider = collection.BuildServiceProvider();
-
- base.OnStartup(e);
- this.Logger = this.ServiceProvider.GetRequiredService>();
-
- // load (optional) common configuration file
- // NOTE: we currently load this AFTER setting up the logger because the GetCommonConfigurationAsync function logs config file errors to the logger
- var commonConfiguration = await this.GetCommonConfigurationAsync();
- ConfigurableFeatures.SetFeatures(
- autorunConfig: commonConfiguration.AutorunConfig,
- checkForUpdatesIsEnabled: commonConfiguration.CheckForUpdatesIsEnabled,
- cloudSettingsTransferIsEnabled: commonConfiguration.CloudSettingsTransferIsEnabled,
- resetSettingsIsEnabled: commonConfiguration.ResetSettingsIsEnabled,
- morphicBarvisibilityAfterLogin: commonConfiguration.MorphicBarVisibilityAfterLogin,
- morphicBarExtraItems: commonConfiguration.ExtraMorphicBarItems
- );
-
- this.MorphicSession = this.ServiceProvider.GetRequiredService();
- this.MorphicSession.UserChangedAsync += this.Session_UserChangedAsync;
-
- this.Logger.LogInformation("App Started");
-
- this.morphicMenu = new MorphicMenu();
-
- this.RegisterGlobalHotKeys();
- await this.ConfigureCountlyAsync();
-
- if (ConfigurableFeatures.CheckForUpdatesIsEnabled == true)
- {
- this.StartCheckingForUpdates();
- }
-
- this.AddSettingsListener();
-
- this.BarManager.BarLoaded += BarManager_BarLoaded;
-
- await this.OpenSessionAsync();
-
- // Make settings displayed on the UI update when a system setting has changed, or when the app is focused.
- this.SystemSettingChanged += (sender, args) => SettingsHandler.SystemSettingChanged();
- AppFocus.Current.MouseEnter += (sender, args) => SettingsHandler.SystemSettingChanged();
- AppFocus.Current.Activated += (sender, args) => SettingsHandler.SystemSettingChanged();
- }
-
- ///
- /// Actions to perform when this instance is the first since installation.
- ///
- private async Task OnFirstRun()
- {
- this.Logger.LogInformation("Performing first-run tasks");
-
- // Set the magnifier to lens mode at 200%
- Registry.SetValue(@"HKEY_CURRENT_USER\Software\Microsoft\ScreenMagnifier", "Magnification", 200);
- Registry.SetValue(@"HKEY_CURRENT_USER\Software\Microsoft\ScreenMagnifier", "MagnificationMode", 3);
-
- // Set the colour filter type - if it's not currently enabled.
- //bool filterOn = this.MorphicSession.GetBool(SettingsManager.Keys.WindowsDisplayColorFilterEnabled) == true;
- bool filterOn =
- await this.MorphicSession.GetSetting(SettingId.ColorFiltersEnabled);
- if (!filterOn)
- {
- await this.MorphicSession.SetSetting(SettingId.ColorFiltersFilterType, 5);
- }
-
- // Set the high-contrast theme, if high-contrast is off.
- bool highcontrastOn = await this.MorphicSession.GetSetting(SettingId.HighContrastEnabled);
- if (!highcontrastOn)
- {
- Registry.SetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes",
- "LastHighContrastTheme", @"%SystemRoot\resources\Ease of Access Themes\hcwhite.theme",
- RegistryValueKind.ExpandString);
-
- // For windows 10 1809+
- Registry.SetValue(@"HKEY_CURRENT_USER\Control Panel\Accessibility\HighContrast",
- "High Contrast Scheme", "High Contrast White");
- }
- }
-
- private async Task ResetSettingsAsync()
- {
- // NOTE: we want to move these defaults to config.json, and we want to modify the solutions registry to allow _all_ settings to be specified,
- // with defaults, in config.json.
-
- // default values
- var colorFiltersEnabledDefault = false;
- var darkModeEnabledDefault = false;
- var highContrastEnabledDefault = false;
- //
- // NOTE: displayDpiOffsetDefault realistically needs to be a fixed value ("recommended value") until we have logic to adjust by a relative %
- int displayDpiOffsetDefault = 0;
- //
- var nightModeIsEnabled = false;
-
- // verify that settings are reset to their default values; if they are not, then set them now
- // NOTE: we do these in an order that makes sense during logout (i.e. we try to do as much as we can before Windows wants to close us, so we push
- // settings like screen scaling, dark mode and high contrast to the end since they take much longer to change)
- //
- // color filters
- if (await this.MorphicSession.GetSetting(SettingId.ColorFiltersEnabled) != colorFiltersEnabledDefault)
- {
- await this.MorphicSession.SetSetting(SettingId.ColorFiltersEnabled, colorFiltersEnabledDefault);
- }
- //
- // night mode
- if (await this.MorphicSession.GetSetting(SettingId.NightModeEnabled) != nightModeIsEnabled)
- {
- await this.MorphicSession.SetSetting(SettingId.NightModeEnabled, nightModeIsEnabled);
- }
- //
- // screen scaling
- var monitorName = Morphic.Windows.Native.Display.Display.GetMonitorName(null);
- if (monitorName != null)
- {
- // get the adapterId and sourceId for this monitor
- var adapterIdAndSourceId = Morphic.Windows.Native.Display.Display.GetAdapterIdAndSourceId(monitorName);
- if (adapterIdAndSourceId != null)
- {
- // get the current DPI offset
- var currentDisplayDpiOffset = Morphic.Windows.Native.Display.Display.GetCurrentDpiOffsetAndRange(adapterIdAndSourceId.Value.adapterId, adapterIdAndSourceId.Value.sourceId);
- if (currentDisplayDpiOffset != null)
- {
- if (currentDisplayDpiOffset.Value.currentDpiOffset != displayDpiOffsetDefault)
- {
- _ = Morphic.Windows.Native.Display.Display.SetDpiOffset(displayDpiOffsetDefault, adapterIdAndSourceId.Value);
- }
- }
- }
- }
- //
- //
- // high contrast
- if (await this.MorphicSession.GetSetting(SettingId.HighContrastEnabled) != highContrastEnabledDefault)
- {
- await this.MorphicSession.SetSetting(SettingId.HighContrastEnabled, highContrastEnabledDefault);
- }
- //
- // dark mode
- // NOTE: due to the interrelation between high contrast and dark mode, we reset dark mode AFTER resetting high contrast mode
- if (await this.MorphicSession.GetSetting(SettingId.LightThemeSystem) != !darkModeEnabledDefault)
- {
- await this.MorphicSession.SetSetting(SettingId.LightThemeSystem, !darkModeEnabledDefault);
- await this.MorphicSession.SetSetting(SettingId.LightThemeApps, !darkModeEnabledDefault);
- }
- }
-
- private async Task Session_UserChangedAsync(object? sender, EventArgs e)
- {
- if (sender is MorphicSession morphicSession)
- {
- if (morphicSession.SignedIn)
- {
- var lastCommunityId = AppOptions.Current.LastCommunity;
- if (lastCommunityId != null)
- {
- // if the user previously selected a community bar, show that one now
- await this.BarManager.LoadSessionBarAsync(morphicSession, lastCommunityId);
- }
- else
- {
- // if the user has not selected a community bar, show the basic bar
- this.BarManager.LoadBasicMorphicBar();
- }
- }
- else
- {
- // if no user is signed in, clear out the last community tag
- AppOptions.Current.LastCommunity = null;
-
- // if no user is signed in, load the basic bar
- this.BarManager.LoadBasicMorphicBar();
- }
-
- // reload our list of communities and re-select the current bar
- ResyncCustomMorphicBarMenuItems();
- }
- }
-
- private void BarManager_BarLoaded(object? sender, BarEventArgs e)
- {
- ResyncCustomMorphicBarMenuItems();
- }
-
- private void ResyncCustomMorphicBarMenuItems()
- {
- // clear all communities in the menu (before basic)
- var changeMorphicBarMenuItems = this.morphicMenu.ChangeMorphicBar.Items;
- for (int i = 0; i < changeMorphicBarMenuItems.Count; i++)
- {
- var submenuItem = (MenuItem)changeMorphicBarMenuItems[0];
- if (submenuItem.Name == "SelectBasicMorphicBar")
- {
- // when we reach the basic MorphicBar entry, exit our loop (so that we don't clear out any remaining items)
- break;
- }
- else
- {
- this.morphicMenu.ChangeMorphicBar.Items.RemoveAt(0);
- }
- }
-
- bool addedCheckmarkByCurrentCommunityBar = false;
-
- for (int iCommunity = 0; iCommunity < this.MorphicSession.Communities.Length; iCommunity++)
- {
- var community = this.MorphicSession.Communities[iCommunity];
- //
- var newMenuItem = new MenuItem();
- newMenuItem.Header = community.Name;
- newMenuItem.Tag = community.Id;
- if (community.Id == AppOptions.Current.LastCommunity)
- {
- newMenuItem.IsChecked = true;
- addedCheckmarkByCurrentCommunityBar = true;
- }
- newMenuItem.Click += CustomMorphicBarMenuItem_Click;
- //
- this.morphicMenu.ChangeMorphicBar.Items.Insert(iCommunity, newMenuItem);
- }
-
- // if no custom bar was checked, check the community bar instead
- this.morphicMenu.SelectBasicMorphicBar.IsChecked = (addedCheckmarkByCurrentCommunityBar == false);
- }
-
- private async void CustomMorphicBarMenuItem_Click(object sender, RoutedEventArgs e)
- {
- var senderAsMenuItem = (MenuItem)sender;
- //var communityName = senderAsMenuItem.Header;
- var communityId = (string)senderAsMenuItem.Tag;
-
- await this.BarManager.LoadSessionBarAsync(this.MorphicSession, communityId);
- }
-
- private void RegisterGlobalHotKeys()
- {
- HotkeyManager.Current.AddOrReplace("Login with Morphic", Key.M, ModifierKeys.Control | ModifierKeys.Shift, (sender, e) =>
- {
- this.Dialogs.OpenDialog();
- });
- HotkeyManager.Current.AddOrReplace("Show Morphic", Key.M, ModifierKeys.Control | ModifierKeys.Shift | ModifierKeys.Alt, (sender, e) =>
- {
- this.BarManager.ShowBar();
- });
- }
-
- public async Task OpenSessionAsync()
- {
- await this.MorphicSession.OpenAsync();
-
- // TODO: when the user first runs Morphic, we probably want to open a welcome window (where the user could then log in)
- //await this.Dialogs.OpenDialog();
-
- this.OnSessionOpened();
- }
-
- ///
- /// Called when the session open task completes
- ///
- ///
- private async void OnSessionOpened()
- {
- this.Logger.LogInformation("Session Open");
-
- if (ConfigurableFeatures.ResetSettingsIsEnabled == true)
- {
- await this.ResetSettingsAsync();
- }
-
- if (this.AppOptions.FirstRun)
- {
- await this.OnFirstRun();
- }
-
- // if no bar was already loaded, load the Basic bar
- if (this.BarManager.BarIsLoaded == false) {
- this.BarManager.LoadBasicMorphicBar();
- }
- }
-
- #endregion
-
- ///
- /// The main menu shown from the system tray icon
- ///
- private MorphicMenu? morphicMenu;
-
- internal async Task ShowMenuAsync(Control? control = null, MorphicMenu.MenuOpenedSource? menuOpenedSource = null)
- {
- await this.morphicMenu?.ShowAsync(control, menuOpenedSource);
- }
-
- #region Updates
-
- void StartCheckingForUpdates()
- {
- UpdateOptions? options = this.ServiceProvider.GetRequiredService();
- if (options.AppCastUrl != "")
- {
- AutoUpdater.Start(options.AppCastUrl);
- }
- }
-
- #endregion
-
- private MessageWatcherNativeWindow? _messageWatcherNativeWindow;
-
- protected override void OnActivated(EventArgs e)
- {
- if (_messageWatcherNativeWindow == null)
- {
- // create a list of the messages we want to watch for
- List messagesToWatch = new List();
- messagesToWatch.Add(AppMain.SingleInstanceMessageId); // this is the message that lets us know that another instance of Morphic was started up
-
- _messageWatcherNativeWindow = new MessageWatcherNativeWindow(messagesToWatch);
- _messageWatcherNativeWindow.WatchedMessageEvent += _messageWatcherNativeWindow_WatchedMessageEvent;
- try
- {
- _messageWatcherNativeWindow.Initialize();
- }
- catch (Exception ex)
- {
- this.Logger.LogError("could not create messages watcher window: {msg}", ex.Message);
- }
- }
-
- base.OnActivated(e);
- }
-
- private void _messageWatcherNativeWindow_WatchedMessageEvent(object sender, MessageWatcherNativeWindow.WatchedMessageEventArgs args)
- {
- this.BarManager.ShowBar();
- }
-
- #region Shutdown
-
- protected override async void OnExit(ExitEventArgs e)
- {
- _messageWatcherNativeWindow?.Dispose();
- await Countly.Instance.SessionEnd();
-
- if (ConfigurableFeatures.ResetSettingsIsEnabled == true)
- {
- await this.ResetSettingsAsync();
- }
-
- AppMain.ReleaseSingleInstanceMutex();
-
- base.OnExit(e);
- }
-
- #endregion
-
- #region SystemEvents
-
- public event EventHandler? SystemSettingChanged;
-
- private bool addedSystemEvents;
- private DispatcherTimer? systemSettingTimer;
-
- ///
- /// Start listening to some changes to system settings.
- ///
- private void AddSettingsListener()
- {
- if (this.addedSystemEvents)
- {
- return;
- }
-
- this.addedSystemEvents = true;
- this.systemSettingTimer = new DispatcherTimer(DispatcherPriority.Render)
- {
- Interval = TimeSpan.FromMilliseconds(500)
- };
-
- this.systemSettingTimer.Tick += (sender, args) =>
- {
- this.systemSettingTimer.Stop();
- this.SystemSettingChanged?.Invoke(this, EventArgs.Empty);
- };
-
- SystemEvents.DisplaySettingsChanged += this.SystemEventsOnDisplaySettingsChanged;
- SystemEvents.UserPreferenceChanged += this.SystemEventsOnDisplaySettingsChanged;
-
- SystemEvents.SessionEnding += SystemEvents_SessionEnding;
-
- this.Exit += (sender, args) =>
- {
- SystemEvents.DisplaySettingsChanged -= this.SystemEventsOnDisplaySettingsChanged;
- SystemEvents.UserPreferenceChanged -= this.SystemEventsOnDisplaySettingsChanged;
- };
- }
-
- private void SystemEventsOnDisplaySettingsChanged(object? sender, EventArgs e)
- {
- // Wait a bit, to see if any other events have been raised.
- this.systemSettingTimer?.Start();
- }
-
- private async void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e)
- {
- // NOTE: in our preliminary testing, we do not have enough time during shutdown
- // to call/complete this function; we should look for a way to keep Windows from
- // forcibly logging out until we have completed our settings reset (or at least a few
- // critical 'reset settings' items)
- if (ConfigurableFeatures.ResetSettingsIsEnabled == true)
- {
- await this.ResetSettingsAsync();
- }
- }
-
- #endregion
- }
-}
diff --git a/Morphic.Client/AssemblyInfo.cs b/Morphic.Client/AssemblyInfo.cs
deleted file mode 100644
index 22112342..00000000
--- a/Morphic.Client/AssemblyInfo.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using System.Windows;
-
-[assembly:ThemeInfo(
- ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
- //(used if a resource is not found in the page,
- // or application resource dictionaries)
- ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
- //(used if a resource is not found in the page,
- // app, or any theme specific resource dictionaries)
-)]
diff --git a/Morphic.Client/Assets/bar-icons/amazon-brands.svg b/Morphic.Client/Assets/bar-icons/amazon-brands.svg
deleted file mode 100644
index e9ff652e..00000000
--- a/Morphic.Client/Assets/bar-icons/amazon-brands.svg
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/calendar-solid.svg b/Morphic.Client/Assets/bar-icons/calendar-solid.svg
deleted file mode 100644
index 1e6bd8ce..00000000
--- a/Morphic.Client/Assets/bar-icons/calendar-solid.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/camera-solid.svg b/Morphic.Client/Assets/bar-icons/camera-solid.svg
deleted file mode 100644
index f65c158d..00000000
--- a/Morphic.Client/Assets/bar-icons/camera-solid.svg
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/comment-solid.svg b/Morphic.Client/Assets/bar-icons/comment-solid.svg
deleted file mode 100644
index d8e3c5db..00000000
--- a/Morphic.Client/Assets/bar-icons/comment-solid.svg
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/envelope-open-text.svg b/Morphic.Client/Assets/bar-icons/envelope-open-text.svg
deleted file mode 100644
index 821168c0..00000000
--- a/Morphic.Client/Assets/bar-icons/envelope-open-text.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/envelope-open.svg b/Morphic.Client/Assets/bar-icons/envelope-open.svg
deleted file mode 100644
index 947ae25a..00000000
--- a/Morphic.Client/Assets/bar-icons/envelope-open.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/envelope-outline-open.svg b/Morphic.Client/Assets/bar-icons/envelope-outline-open.svg
deleted file mode 100644
index 0b71db8f..00000000
--- a/Morphic.Client/Assets/bar-icons/envelope-outline-open.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/envelope-outline.svg b/Morphic.Client/Assets/bar-icons/envelope-outline.svg
deleted file mode 100644
index a2557ef2..00000000
--- a/Morphic.Client/Assets/bar-icons/envelope-outline.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/envelope-solid.svg b/Morphic.Client/Assets/bar-icons/envelope-solid.svg
deleted file mode 100644
index 1473865a..00000000
--- a/Morphic.Client/Assets/bar-icons/envelope-solid.svg
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/envelope.svg b/Morphic.Client/Assets/bar-icons/envelope.svg
deleted file mode 100644
index edbcad3d..00000000
--- a/Morphic.Client/Assets/bar-icons/envelope.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/facebook.svg b/Morphic.Client/Assets/bar-icons/facebook.svg
deleted file mode 100644
index 67dec276..00000000
--- a/Morphic.Client/Assets/bar-icons/facebook.svg
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/gmail.svg b/Morphic.Client/Assets/bar-icons/gmail.svg
deleted file mode 100644
index 2898d0ef..00000000
--- a/Morphic.Client/Assets/bar-icons/gmail.svg
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/google-brands.svg b/Morphic.Client/Assets/bar-icons/google-brands.svg
deleted file mode 100644
index 6937fe11..00000000
--- a/Morphic.Client/Assets/bar-icons/google-brands.svg
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/google-drive-brands.svg b/Morphic.Client/Assets/bar-icons/google-drive-brands.svg
deleted file mode 100644
index fbc456f7..00000000
--- a/Morphic.Client/Assets/bar-icons/google-drive-brands.svg
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/images-solid.svg b/Morphic.Client/Assets/bar-icons/images-solid.svg
deleted file mode 100644
index 6c02f470..00000000
--- a/Morphic.Client/Assets/bar-icons/images-solid.svg
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/instagram.svg b/Morphic.Client/Assets/bar-icons/instagram.svg
deleted file mode 100644
index e9c09fdf..00000000
--- a/Morphic.Client/Assets/bar-icons/instagram.svg
+++ /dev/null
@@ -1,57 +0,0 @@
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/link-solid.svg b/Morphic.Client/Assets/bar-icons/link-solid.svg
deleted file mode 100644
index ef99977b..00000000
--- a/Morphic.Client/Assets/bar-icons/link-solid.svg
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/minus.svg b/Morphic.Client/Assets/bar-icons/minus.svg
deleted file mode 100644
index 3c9e3861..00000000
--- a/Morphic.Client/Assets/bar-icons/minus.svg
+++ /dev/null
@@ -1,69 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/morphic-logo.svg b/Morphic.Client/Assets/bar-icons/morphic-logo.svg
deleted file mode 100644
index e573ae47..00000000
--- a/Morphic.Client/Assets/bar-icons/morphic-logo.svg
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/music-solid.svg b/Morphic.Client/Assets/bar-icons/music-solid.svg
deleted file mode 100644
index 3289b3a7..00000000
--- a/Morphic.Client/Assets/bar-icons/music-solid.svg
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/newspaper-solid.svg b/Morphic.Client/Assets/bar-icons/newspaper-solid.svg
deleted file mode 100644
index 3fc9afa5..00000000
--- a/Morphic.Client/Assets/bar-icons/newspaper-solid.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/outlook.svg b/Morphic.Client/Assets/bar-icons/outlook.svg
deleted file mode 100644
index 5592b366..00000000
--- a/Morphic.Client/Assets/bar-icons/outlook.svg
+++ /dev/null
@@ -1,36 +0,0 @@
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/paypal.svg b/Morphic.Client/Assets/bar-icons/paypal.svg
deleted file mode 100644
index d41a85a6..00000000
--- a/Morphic.Client/Assets/bar-icons/paypal.svg
+++ /dev/null
@@ -1,44 +0,0 @@
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/plus.svg b/Morphic.Client/Assets/bar-icons/plus.svg
deleted file mode 100644
index e8681531..00000000
--- a/Morphic.Client/Assets/bar-icons/plus.svg
+++ /dev/null
@@ -1,59 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/question-solid.svg b/Morphic.Client/Assets/bar-icons/question-solid.svg
deleted file mode 100644
index 2a3a5c66..00000000
--- a/Morphic.Client/Assets/bar-icons/question-solid.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/reddit.svg b/Morphic.Client/Assets/bar-icons/reddit.svg
deleted file mode 100644
index ac7a0fb0..00000000
--- a/Morphic.Client/Assets/bar-icons/reddit.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/shopping-cart-solid.svg b/Morphic.Client/Assets/bar-icons/shopping-cart-solid.svg
deleted file mode 100644
index 155dcd30..00000000
--- a/Morphic.Client/Assets/bar-icons/shopping-cart-solid.svg
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/skype-brands.svg b/Morphic.Client/Assets/bar-icons/skype-brands.svg
deleted file mode 100644
index 75554c29..00000000
--- a/Morphic.Client/Assets/bar-icons/skype-brands.svg
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/skype.svg b/Morphic.Client/Assets/bar-icons/skype.svg
deleted file mode 100644
index ab315dfd..00000000
--- a/Morphic.Client/Assets/bar-icons/skype.svg
+++ /dev/null
@@ -1,46 +0,0 @@
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/telegram.svg b/Morphic.Client/Assets/bar-icons/telegram.svg
deleted file mode 100644
index 22cd21d7..00000000
--- a/Morphic.Client/Assets/bar-icons/telegram.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/test/grid.svg b/Morphic.Client/Assets/bar-icons/test/grid.svg
deleted file mode 100644
index b482016f..00000000
--- a/Morphic.Client/Assets/bar-icons/test/grid.svg
+++ /dev/null
@@ -1,89 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/twitter.svg b/Morphic.Client/Assets/bar-icons/twitter.svg
deleted file mode 100644
index 304965ff..00000000
--- a/Morphic.Client/Assets/bar-icons/twitter.svg
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/viber.svg b/Morphic.Client/Assets/bar-icons/viber.svg
deleted file mode 100644
index f507a1e3..00000000
--- a/Morphic.Client/Assets/bar-icons/viber.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/video-solid.svg b/Morphic.Client/Assets/bar-icons/video-solid.svg
deleted file mode 100644
index 6a7e1903..00000000
--- a/Morphic.Client/Assets/bar-icons/video-solid.svg
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/whatsapp.svg b/Morphic.Client/Assets/bar-icons/whatsapp.svg
deleted file mode 100644
index e6d6a3b9..00000000
--- a/Morphic.Client/Assets/bar-icons/whatsapp.svg
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/yahoo-mail.svg b/Morphic.Client/Assets/bar-icons/yahoo-mail.svg
deleted file mode 100644
index 9e0e0200..00000000
--- a/Morphic.Client/Assets/bar-icons/yahoo-mail.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/bar-icons/youtube.svg b/Morphic.Client/Assets/bar-icons/youtube.svg
deleted file mode 100644
index 65c3108f..00000000
--- a/Morphic.Client/Assets/bar-icons/youtube.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/Morphic.Client/Assets/morphic-icon.ico b/Morphic.Client/Assets/morphic-icon.ico
deleted file mode 100644
index 39f8ed22..00000000
Binary files a/Morphic.Client/Assets/morphic-icon.ico and /dev/null differ
diff --git a/Morphic.Client/Backups.cs b/Morphic.Client/Backups.cs
deleted file mode 100644
index 22a73dcd..00000000
--- a/Morphic.Client/Backups.cs
+++ /dev/null
@@ -1,94 +0,0 @@
-namespace Morphic.Client
-{
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Text.Json;
- using System.Threading.Tasks;
- using Config;
- using Core;
- using Microsoft.Extensions.Logging;
- using Service;
- using Path = System.IO.Path;
-
- public class Backups
- {
- private readonly MorphicSession morphicSession;
- private readonly ILogger logger;
- private readonly IServiceProvider serviceProvider;
-
- public static string BackupDirectory => AppPaths.GetUserLocalConfigDir("backups");
- private static readonly string BackupExtension = ".preferences";
-
- public Backups(MorphicSession morphicSession, ILogger logger, IServiceProvider serviceProvider)
- {
- this.morphicSession = morphicSession;
- this.logger = logger;
- this.serviceProvider = serviceProvider;
- }
-
-
- ///
- /// Stores some preferences to a file, for a backup.
- ///
- /// Short description for display (one or two words, file-safe characters)
- /// The preferences to store - null to capture them.
- public async Task Store(Preferences? preferences = null)
- {
- this.logger.LogInformation("Making backup");
- if (preferences == null)
- {
- preferences = new Preferences();
- await this.morphicSession.Solutions.CapturePreferences(preferences);
- }
-
- string json = JsonSerializer.Serialize(preferences);
- string filename = DateTime.Now.ToString("yyyy-MM-dd_HH.mm.ss") + BackupExtension;
- string path = Path.Combine(BackupDirectory, filename);
-
- Directory.CreateDirectory(BackupDirectory);
- await File.WriteAllTextAsync(path, json);
-
- this.logger.LogInformation($"Stored backup to {path}");
- }
-
- ///
- /// Gets the list of backup files.
- ///
- /// filename:date
- public IDictionary GetBackups()
- {
- Dictionary backups = new Dictionary();
-
- if (Directory.Exists(BackupDirectory))
- {
- foreach (string path in Directory.EnumerateFiles(BackupDirectory, "*" + BackupExtension)
- .OrderBy(f => f))
- {
- // Get the date from the filename.
- string dateString = Path.ChangeExtension(Path.GetFileName(path), null);
- if (DateTime.TryParse(dateString.Replace('_', ' ').Replace('.', ':'), out DateTime date))
- {
- backups.Add(path, date.ToString("g"));
- }
- }
- }
-
- return backups;
- }
-
- ///
- /// Applies a back-up.
- ///
- /// The backup file.
- public async Task Apply(string path)
- {
- string json = await File.ReadAllTextAsync(path);
- JsonSerializerOptions options = new JsonSerializerOptions();
- options.Converters.Add(new JsonElementInferredTypeConverter());
- this.morphicSession.Preferences = JsonSerializer.Deserialize(json, options);
- await this.morphicSession.ApplyAllPreferences();
- }
- }
-}
diff --git a/Morphic.Client/Bar/AppFocus.cs b/Morphic.Client/Bar/AppFocus.cs
deleted file mode 100644
index 55847987..00000000
--- a/Morphic.Client/Bar/AppFocus.cs
+++ /dev/null
@@ -1,121 +0,0 @@
-namespace Morphic.Client.Bar
-{
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Windows;
- using System.Windows.Threading;
- using UI.AppBarWindow;
-
- public class AppFocus
- {
- public static AppFocus Current { get; } = new AppFocus();
-
- ///
- /// true if the current application is active.
- ///
- public bool IsActive { get; private set; }
-
- /// The mouse has entered any window belonging to the application.
- public event EventHandler? MouseEnter;
- /// The mouse has left any window belonging to the application.
- public event EventHandler? MouseLeave;
-
- public event EventHandler? Activated;
- public event EventHandler? Deactivated;
-
- protected AppFocus()
- {
- App.Current.Activated += (o, args) => this.Activated?.Invoke(o, args);
- App.Current.Activated += (o, args) => this.Deactivated?.Invoke(o, args);
- this.Activated += (sender, args) => this.IsActive = true;
- this.Deactivated += (sender, args) => this.IsActive = false;
- }
-
- // The mouse is over any window in mouseOverWindows
- private bool mouseOver;
-
- // The windows where the mouse-over status is needed.
- private readonly List mouseOverWindows = new List();
- private DispatcherTimer? mouseTimer;
-
- ///
- /// Register interest in observing the mouse-over state of a window.
- ///
- ///
- public void AddMouseOverWindow(Window window)
- {
- this.mouseOverWindows.Add(window);
- window.MouseEnter += this.CheckMouseOver;
- window.MouseLeave += this.CheckMouseOver;
- }
-
- private void CheckMouseOver(object? sender, EventArgs e)
- {
- if (this.mouseOverWindows.Count == 0)
- {
- return;
- }
-
- bool isOver = false;
- IEnumerable windows = this.mouseOverWindows.Where(w => w.IsVisible && w.Opacity > 0);
-
- Point? cursor = null;
-
- // Window.IsMouseOver is false if the mouse is over the window border, check if that's the case.
- foreach (Window window in windows)
- {
- if (window.IsMouseOver)
- {
- isOver = true;
- break;
- }
-
- cursor ??= PresentationSource.FromVisual(window)?.CompositionTarget.TransformFromDevice
- .Transform(WindowMovement.GetCursorPos());
-
- if (cursor != null)
- {
- System.Windows.Rect rc = window.GetRect();
- rc.Inflate(10, 10);
- if (rc.Contains(cursor.Value))
- {
- isOver = true;
- if (this.mouseTimer == null)
- {
- // Keep an eye on the current position.
- this.mouseTimer = new DispatcherTimer(DispatcherPriority.Input)
- {
- Interval = TimeSpan.FromMilliseconds(100),
- };
- this.mouseTimer.Tick += this.CheckMouseOver;
- this.mouseTimer.Start();
- }
-
- break;
- }
- }
- }
-
- if (!isOver)
- {
- this.mouseTimer?.Stop();
- this.mouseTimer = null;
- }
-
- if (this.mouseOver != isOver)
- {
- this.mouseOver = isOver;
- if (isOver)
- {
- this.MouseEnter?.Invoke(sender, new EventArgs());
- }
- else
- {
- this.MouseLeave?.Invoke(sender, new EventArgs());
- }
- }
- }
-
- }
-}
diff --git a/Morphic.Client/Bar/BarData.md b/Morphic.Client/Bar/BarData.md
deleted file mode 100644
index 89f62832..00000000
--- a/Morphic.Client/Bar/BarData.md
+++ /dev/null
@@ -1,519 +0,0 @@
-# Data structure for Morphic Bar
-
-## `Bar`
-
-This is what the client can handle for the bar, which is a super-set of what is provided by the web app.
-
-There is initial data, in `default-bar.json5`. This is loaded first, then the data from the web app is merged over it.
-
-Not all fields are required, as the client will already have its own predefined defaults. Assume fields to be optional, unless stated.
-
-```js
-Bar = {
- // Bar identifier
- id: "bar1",
- // Bar name
- name: "Example bar",
-
- // Initial position
- position: {
- // Dock it to an edge, reserving desktop space
- docked: "left", // "left", "right", "top", "bottom", "none" (default), or "disable".
-
- horizontal: false, // true for horizontal orientation.
- restricted: false, // true to restrict the position to just the corners
-
- // Position of the bar. Can be "Left"/"Top", "Middle", "Right"/"Bottom", a number, or a percentage.
- // Numbers or percentages can be negative (including -0), meaning distance from the right.
- // Percentages specify the position of the middle of the bar.
- // Ignored if `docked` is used.
- x: "50%",
- y: "Bottom",
-
- // Position of the secondary bar, relative to the primary bar. Same syntax as `x`/`y` above.
- // (can be split with `secondaryX` and `secondaryY`)
- secondary: "Middle",
-
- // Position of the expander button (the thing that opens the secondary bar)
- // (can be split with `expanderX` and `expanderY`)
- expander: "Middle",
- // What the position in `expander` is relative to.
- // "primary", "secondary", or "both" (secondary if the secondary bar is open, otherwise primary)
- expanderRelative: "Both",
- },
-
- // Settings for the secondary bar
- secondaryBar: {
- // Close the secondary bar when another application takes focus.
- autohide: true,
- // Hide the expander button when another application takes focus (shown on mouse-over).
- autohideExpander: false
- },
-
- // Size of everything
- scale: 1,
-
- // What happens when all buttons do not fit.
- // "resize": shrinks some items until it fits
- // "wrap": Adds another column
- // "secondary": Move over-flow items to the secondary bar.
- // "hide": Do nothing.
- overflow: "resize",
-
- // Bar theme
- theme: {Theme},
-
- // Theme for bar items
- itemTheme: {ItemTheme},
-
- // The bar items
- items: [
- {BarItem}
- ],
-
- sizes: {
- // Padding between edge of bar and items.
- windowPadding: "10 15",
- // Spacing between items.
- itemSpacing: 10,
- // Item width.
- itemWidth: 100,
- // Maximum Button Item title lines.
- buttonTextLines: 2,
- // Button Item padding between edge and title. And for the top, between circle and title.
- buttonPadding: "10",
- // Button Item circle image diameter (a fraction relative to the itemWidth).
- buttonCircleDiameter: 0.66,
- // Button Item circle overlap with rectangle (a fraction relative to buttonImageSize).
- buttonImageOverlap: 0.33,
- buttonFontSize: 14,
- buttonFontWeight: "normal",
- circleBorderWidth: 2,
- buttonCornerRadius: 10
- }
-}
-```
-
-## `BarItem`
-
-Describes an individual bar item.
-
-```js
-BarItem = {
- // email|calendar|videocall|photos|...
- // Currently ignored by the client
- category: "calendar",
-
- // unique identifier (currently ignored by client)
- id: "calendar-button",
-
- // `true` if the item is shown on the primary bar. `false` to show on the secondary bar.
- is_primary: true,
-
- // `true` to never move this item to the secondary bar (for Bar.overflow == "secondary")
- no_overflow: false,
-
- // Per-button theme, overrides the `Bar.itemTheme` field from above.
- // If unset, this is generated using `configuration.color`
- theme: {Theme},
-
- // `true` to not show this button. While it's expected that the client will only receive the items which should be
- // shown, this field provides the ability to show or hide items depending on the platform, using the platform
- // identifier, described later. For example, `hidden$win: true` will make the item only available for macOS.
- hidden: false,
-
- // Items are sorted by this (higher values at the top).
- priority: 0,
-
- // The kind of item (see Item kinds below) [REQUIRED]
- // "link", "application", "action"
- kind: "link",
-
- // "button" (default), "image", "multi" (see Widgets below)
- widget: "button",
-
- // Specific to the item kind.
- configuration: {
- // ...
- }
-}
-```
-
-## Button items
-
-```js
-/** @mixes BarItem */
-ButtonItem = {
- kind: "",
- configuration: {
- // Displayed on the button [REQUIRED]
- label: "Calendar",
-
- // Tooltip.
- tooltipHeader: "Open the calendar",
- // More details.
- tooltip: "Displays your google calendar",
-
- // Automation UI name - this is used by narrator. default is the label.
- uiName: "Calendar",
-
- // local/remote url of the icon. For values without a directory, a matching file in ./Assets/bar-icons/`) is
- // discovered. If this value is omitted (or not working), an image is detected depending on the kind of item:
- // - link: favicon of the site.
- // - application: The application icon.
- image_url: "calendar.png",
-
- // Item color (overrides BarItem.theme, generates the different shades for the states)
- color: '#002957',
-
- // Size of the item. "textonly", "small", "medium", or "large" (default)
- size: "large",
-
- // Context menu
- menu: {ContextMenu}
-
- }
-}
-```
-
-### `kind = "link"`
-
-Opens a web page.
-
-```js
-/** @extends ButtonItem */
-LinkButton = {
- kind: "link",
- /** @mixes LinkAction */
- configuration: {
- url: "https://example.com"
- }
-}
-```
-
-### `kind = "application"`
-
-Opens an application.
-
-```js
-/** @extends ButtonItem */
-ApplicationButton = {
- kind: "application",
- /** @mixes ApplicationAction */
- configuration: {
- // Executable name (or full path). Full path is discovered via `App Paths` registry or the PATH environment variable.
- // To pass arguments, surround the executable with quotes and append the arguments after (or use the args field)
- exe: "notepad.exe",
- // Arguments to pass to the process
- args: [ "arg1", "arg2" ],
- // Extra environment variables
- env: {
- name: "value"
- },
- // Always start a new instance (otherwise, activate the existing instance if one is found)
- newInstance: true,
- // Initial state of the window (not all apps honour this)
- windowStyle: "normal" // "normal" (default), "maximized", "minimized" or "hidden"
- }
-}
-```
-
-Or, run a default application. Use the `default` field to identify an entry in [`default-apps.json5`](#default-appsjson5).
-
-```js
-/** @extends ButtonItem */
-ApplicationButton = {
- kind: "application",
- /** @mixes ApplicationAction */
- configuration: {
- // The key to lookup in default-apps.json5.
- default: "email",
- }
-}
-```
-
-### `kind = "internal"`
-
-Invokes a built-in routine.
-
-```js
-/** @extends ButtonItem */
-InternalButton = {
- kind: "internal",
- /** @mixes InternalAction */
- configuration: {
- // Name of the internal function.
- function: "fname",
- // Arguments to pass.
- args: ["a1", "a2"]
- }
-}
-```
-
-### `kind = "shellExec"`
-
-Executes a command via the windows shell (similar to the `start` command or the run dialog box).
-
-```js
-/** @extends ButtonItem */
-ShellExecButton = {
- kind: "shellExec",
- /** @mixes ApplicationAction */
- configuration: {
- // The command
- default: "ms-settings:"
- }
-}
-```
-
-### `kind = "setting"`
-
-Changes a setting. Currently, only boolean or integer settings are supported.
-
-```js
-/** @extends ButtonItem */
-SettingButton = {
- kind: "setting",
- /** @mixes SettingAction */
- configuration: {
- // The setting path
- settingId: "com.microsoft.windows.magnifier/enabled"
- }
-}
-```
-
-
-### `kind = "action"`
-
-This performs a lookup of an `action` object in [`presets.json5`](#presetsjson5), using `configuration.identifier`.
-The object in `presets.json5` will be merged onto this one.
-
-This allows for a richer set of data than what the web app provides.
-
-```js
-/** @extends ButtonItem */
-ActionButton = {
- kind: "action",
- /** @mixes PresetAction */
- configuration: {
- identifier: "example-action"
- }
-}
-```
-
-## Widgets
-
-### `widget = "button"`
-
-Standard button.
-
-### `widget = "image"`
-
-Behaves like a button, but only displays an image. Used for the logo button.
-
-```js
-/** @extends ButtonItem */
-ImageItem = {
- widget: "image"
-}
-```
-
-### `widget = "multi"`
-
-Displays multiple buttons in a single item. Used by the settings items.
-
-```js
-/** @extends BarItem */
-MultiButtonItem = {
- widget: "multi",
- configuration: {
- // How the buttons are interracted with via the keyboard: "buttons", "additive", "toggle", "auto" (default)
- // "additive" and "toggle" cause the bar item to behave as a single control (for keyboard navigation), and
- // the button pair is accessed via -/+ keys.
- // For "buttons", each button is a tab stop. "auto" (default) will detect, based on the button names.
- type: "auto",
- buttons: {
- // First button
- button1: {
- // Display text
- label: "day",
- // A value that replaces "{button}" in any action payload (eg, `exe: "app.exe {button}"`).
- value: "b1",
-
- tooltip: "Tooltip header|Tooltip text",
- uiName: "Button one",
- menu: {ContextMenu}
- },
- // next button
- button2: {
- label: "night"
- },
- // ...
- }
- }
-}
-```
-
-Example:
-
-```json5
- {
- // Pass either ^c or ^v to the `sendKeys` internal function.
- kind: "internal",
- widget: "multi",
- configuration: {
- defaultLabel: "Clipboard",
- function: "sendKeys",
- args: {
- keys: "{button}"
- },
- buttons: {
- copy: {
- label: "Copy",
- value: "^c"
- },
- paste: {
- label: "Paste",
- value: "^v"
- }
- }
- }
- }
-```
-
-
-## `Theme`
-
-Used to specify the theme of the bar or an item.
-
-```js
-Theme = {
- color: "white",
- background: "#002957",
- // Only used by bar items
- borderColor: "#ff0",
- focusDotColor: "#000",
- borderSize: 2
-}
-```
-
-## `ItemTheme : Theme`
-
-```js
-/** @extends Theme */
-ItemTheme = {
- // from Theme
- color: "white",
- background: "#002957",
- borderColor: "#ff0",
- focusDotColor: "#000",
- borderSize: 2,
-
- // Optional, will use the above style.
- hover: {Theme}, // Mouse is over the item.
- focus: {Theme}, // Item has keyboard focus.
- active: {Theme} // Item is being clicked (mouse is down).
-}
-```
-
-## `ContextMenu`
-
-```js
-ContextMenu = {
- "setting": "easeofaccess-colorfilter",
- "learn": "color",
- "demo": "color"
-}
-
-```
-
-## presets.json5
-
-This file contains additional data for certain bar items. This allows for additional bar information provided by the client.
-A bar item, from the web app, which points to an object in this file will have this object merged onto it.
-
-For bar items with `kind = "action"`, the value of `configuration.identifier` identifies a key in `actions`.
-For bar items with `kind = "application"`, the value of `configuration.default` identifies a key in `defaults`.
-
-```js
-presets.json5 = {
- actions: {
- "identifier": {BarItem},
-
- // start task manager
- "taskManager": {
- kind: "application",
- configuration: {
- exe: "taskmgr.exe"
- }
- },
-
- // Example: invoke an internal function
- "example": {
- kind: "internal",
- configuration: {
- function: "hello"
- }
- },
-
- // Real example
- "high-contrast": {
- kind: "application",
- widget: "multi",
- configuration: {
- defaultLabel: "High-Contrast",
- exe: "sethc.exe",
- args: [ "{button}" ],
- buttons: {
- on: {
- label: "On",
- value: "100"
- },
- off: {
- label: "Off",
- value: "1"
- }
- }
- }
- }
- },
-
- defaults: {
- // Same as actions.
- "identifier": {BarItem},
-
- "email": {
- configuration: {
- exe: "mailto:"
- }
- }
- }
-}
-```
-
-## Cross-platform
-
-All fields in the bar json and `presets.json5` can be suffixed with an OS identifier (`$mac` or `$win`), which will take precedence over the non-suffixed field. This pre-processing would be done on the client.
-
-examples:
-
-```js
-[
- {
- command: "default command",
- command$mac: "macOS command",
-
- label$win: "on windows",
- labelText: "not windows"
- },
- {
- command: "default command",
- command$win: "windows command"
- },
- {
- command: "default command (ignored)",
- command$win: "windows command",
- command$mac: "macOS command"
- },
-]
-```
diff --git a/Morphic.Client/Bar/BarImages.cs b/Morphic.Client/Bar/BarImages.cs
deleted file mode 100644
index aaa30767..00000000
--- a/Morphic.Client/Bar/BarImages.cs
+++ /dev/null
@@ -1,169 +0,0 @@
-namespace Morphic.Client.Bar
-{
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Text.RegularExpressions;
- using System.Windows.Media;
- using System.Windows.Media.Imaging;
- using System.Xml;
- using Config;
- using SharpVectors.Converters;
- using SharpVectors.Dom.Svg;
- using SharpVectors.Renderers.Wpf;
-
- public class BarImages
- {
- ///
- /// Gets the full path to a bar icon in the assets directory, based on its name (with or without the extension).
- ///
- /// Name of the icon.
- ///
- public static string? GetBarIconFile(string name)
- {
- string safe = new Regex(@"\.\.|[^-a-zA-Z0-9./]+", RegexOptions.Compiled)
- .Replace(name, "_")
- .Trim('/')
- .Replace('/', Path.DirectorySeparatorChar);
- string assetFile = AppPaths.GetAssetFile("bar-icons\\" + safe);
- string[] extensions = { "", ".svg", ".png", ".ico", ".jpg", ".jpeg", ".gif" };
-
- string? foundFile = extensions.Select(extension => assetFile + extension)
- .FirstOrDefault(File.Exists);
-
- return foundFile;
- }
-
- ///
- /// Creates an image source from a local image.
- ///
- /// The path to the image, or the name of the icon in the assets directory.
- /// The color, for monochrome vectors.
- /// null if the image is not supported.
- public static ImageSource? CreateImageSource(string imagePath, Color? color = null)
- {
- ImageSource? result;
-
- // Attempt to load an SVG image.
- ImageSource? TrySvg()
- {
- try
- {
- using FileSvgReader svg = new FileSvgReader(new WpfDrawingSettings());
- DrawingGroup drawingGroup = svg.Read(imagePath);
- if (color.HasValue)
- {
- ChangeDrawingColor(drawingGroup, color.Value);
- }
-
- return new DrawingImage(drawingGroup);
- }
- catch (Exception e) when (e is NotSupportedException || e is XmlException || e is SvgException)
- {
- return null;
- }
- }
-
- // Attempt to load a bitmap image.
- ImageSource? TryBitmap()
- {
- try
- {
- BitmapImage image = new BitmapImage();
- image.BeginInit();
- image.CacheOption = BitmapCacheOption.OnLoad;
- image.UriSource = new Uri(imagePath);
- image.EndInit();
- return image;
- }
- catch (Exception e) when (e is NotSupportedException || e is XmlException || e is SvgException)
- {
- return null;
- }
- }
-
- if (!imagePath.Contains('/'))
- {
- imagePath = GetBarIconFile(imagePath) ?? imagePath;
- }
-
- if (Path.GetExtension(imagePath) == ".svg")
- {
- result = TrySvg() ?? TryBitmap();
- }
- else
- {
- result = TryBitmap() ?? TrySvg();
- }
-
- return result;
- }
-
- ///
- /// Replaces the brushes used in a monochrome drawing with a new one, which can be set to a specific colour.
- ///
- /// The drawing to change.
- /// The new colour to set (if brush is null).
- /// The brush to use.
- /// The brush used (null if the drawing isn't monochrome).
- public static SolidColorBrush? ChangeDrawingColor(Drawing drawing, Color color, SolidColorBrush? brush = null)
- {
- List? geometryDrawings;
-
- // Get all the geometries in the drawing.
- if (drawing is DrawingGroup drawingGroup)
- {
- geometryDrawings = GetDrawings(drawingGroup).OfType().ToList();
- }
- else
- {
- geometryDrawings = new List();
- if (drawing is GeometryDrawing gd)
- {
- geometryDrawings.Add(gd);
- }
- }
-
- // If there's only 1 colour, it's mono.
- bool mono = geometryDrawings.Count > 0
- && geometryDrawings
- .Select(gd => gd.Brush)
- .OfType()
- .Where(b => b.Opacity > 0)
- .Select(b => b.Color)
- .Where(c => c.A != 0)
- .Distinct()
- .Count() == 1;
-
- if (!mono)
- {
- return null;
- }
- else
- {
- brush ??= new SolidColorBrush(color);
- geometryDrawings.ForEach(gd =>
- {
- if (gd.Brush is SolidColorBrush && gd.Brush.Opacity > 0)
- {
- gd.Brush = brush;
- }
- });
- return brush;
- }
- }
-
- ///
- /// Gets all drawings within a drawing group.
- ///
- ///
- ///
- private static IEnumerable GetDrawings(DrawingGroup drawingGroup)
- {
- return drawingGroup.Children.OfType()
- .SelectMany(GetDrawings)
- .Concat(drawingGroup.Children.OfType());
- }
- }
-}
diff --git a/Morphic.Client/Bar/BarManager.cs b/Morphic.Client/Bar/BarManager.cs
deleted file mode 100644
index 89ad11ee..00000000
--- a/Morphic.Client/Bar/BarManager.cs
+++ /dev/null
@@ -1,362 +0,0 @@
-// BarManager.cs: Loads and shows bar.
-//
-// Copyright 2020 Raising the Floor - International
-//
-// Licensed under the New BSD license. You may not use this file except in
-// compliance with this License.
-//
-// You may obtain a copy of the License at
-// https://github.com/GPII/universal/blob/master/LICENSE.txt
-
-
-namespace Morphic.Client.Bar
-{
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.IO;
- using System.Linq;
- using System.Runtime.CompilerServices;
- using System.Threading.Tasks;
- using System.Windows;
- using Config;
- using Core;
- using Core.Community;
- using Data;
- using Dialogs;
- using Microsoft.Extensions.Logging;
- using Service;
- using UI;
- using MessageBox = System.Windows.Forms.MessageBox;
- using SystemJson = System.Text.Json;
-
- ///
- /// Looks after the bar.
- ///
- public class BarManager : INotifyPropertyChanged
- {
- private PrimaryBarWindow? barWindow;
- private ILogger Logger => App.Current.Logger;
-
- public event EventHandler? BarLoaded;
- public event EventHandler? BarUnloaded;
-
- private bool firstBar = true;
-
- public bool BarVisible => this.barWindow?.Visibility == Visibility.Visible;
-
- public BarManager()
- {
- }
-
- public bool BarIsLoaded { get; private set; } = false;
-
- ///
- /// Show a bar that's already loaded.
- ///
- public void ShowBar()
- {
- if (this.barWindow != null)
- {
- AppOptions.Current.MorphicBarIsVisible = true;
- this.barWindow.Visibility = Visibility.Visible;
- this.barWindow.Focus();
- }
- }
-
- public void HideBar()
- {
- AppOptions.Current.MorphicBarIsVisible = false;
- this.barWindow?.Hide();
- this.barWindow?.OtherWindow?.Hide();
- }
-
- ///
- /// Closes the bar.
- ///
- public void CloseBar()
- {
- this.BarIsLoaded = false;
-
- if (this.barWindow != null)
- {
- this.OnBarUnloaded(this.barWindow);
- BarData bar = this.barWindow.Bar;
- this.barWindow.IsClosing = true;
- this.barWindow.Close();
- this.barWindow = null;
- bar.Dispose();
- }
- }
-
- public BarWindow CreateBarWindow(BarData bar)
- {
- this.barWindow = new PrimaryBarWindow(bar);
- this.barWindow.BarLoaded += this.OnBarLoaded;
- this.barWindow.IsVisibleChanged += this.BarWindowOnIsVisibleChanged;
-
- bool showMorphicBar = false;
- if (AppOptions.Current.AutoShow == true)
- {
- showMorphicBar = true;
- }
- if (this.firstBar == false)
- {
- showMorphicBar = true;
- }
- if (AppOptions.Current.MorphicBarIsVisible == true)
- {
- showMorphicBar = true;
- }
- if (ConfigurableFeatures.MorphicBarVisibilityAfterLogin != null)
- {
- switch (ConfigurableFeatures.MorphicBarVisibilityAfterLogin.Value)
- {
- case ConfigurableFeatures.MorphicBarVisibilityAfterLoginOption.Show:
- showMorphicBar = true;
- break;
- case ConfigurableFeatures.MorphicBarVisibilityAfterLoginOption.Restore:
- // if the bar has not been shown before, show it now; if it has been shown/hidden before, use the last known visibility state
- showMorphicBar = AppOptions.Current.MorphicBarIsVisible ?? true;
- break;
- case ConfigurableFeatures.MorphicBarVisibilityAfterLoginOption.Hide:
- showMorphicBar = false;
- break;
- }
- }
-
- // if we were started up manually, always show the MorphicBar
- if (Environment.GetCommandLineArgs().Contains("--run-after-login") == false)
- {
- showMorphicBar = true;
- }
-
- if (showMorphicBar == true)
- {
- AppOptions.Current.MorphicBarIsVisible = true;
- this.barWindow.Show();
- }
-
- this.firstBar = false;
- return this.barWindow;
- }
-
- private void BarWindowOnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
- {
- this.OnPropertyChanged(nameof(this.BarVisible));
- }
-
- ///
- /// Called when a bar has loaded.
- ///
- protected virtual void OnBarLoaded(object sender, EventArgs? args = null)
- {
- if (sender is PrimaryBarWindow window)
- {
- this.BarLoaded?.Invoke(this, new BarEventArgs(window));
- }
- }
-
- ///
- /// Called when a bar has closed.
- ///
- protected virtual void OnBarUnloaded(object sender, EventArgs? args = null)
- {
- if (sender is PrimaryBarWindow window)
- {
- this.BarUnloaded?.Invoke(this, new BarEventArgs(window));
- }
- }
-
- #region DataLoading
- private void OnBarOnReloadRequired(object? sender, EventArgs args)
- {
- if (sender is BarData bar)
- {
- string source = bar.Source;
-
- this.CloseBar();
- this.LoadFromBarJson(source);
- }
- }
-
- public BarData? LoadBasicMorphicBar()
- {
- var result = LoadFromBarJson(AppPaths.GetConfigFile("basic-bar.json5", true));
- AppOptions.Current.LastCommunity = null;
- return result;
- }
-
- ///
- /// Loads and shows a bar.
- ///
- /// JSON file containing the bar data.
- /// The file content (if it's already loaded).
- ///
- public BarData? LoadFromBarJson(string path, string? content = null, IServiceProvider? serviceProvider = null)
- {
- if (this.firstBar && AppOptions.Current.Launch.BarFile != null)
- {
- path = AppOptions.Current.Launch.BarFile;
- }
-
- BarData? bar = null;
- try
- {
- bar = BarData.Load(serviceProvider ?? App.Current.ServiceProvider, path, content);
- }
- catch (Exception e) when (!(e is OutOfMemoryException))
- {
- this.Logger.LogError(e, "Problem loading the bar.");
- }
-
- if (this.barWindow != null)
- {
- this.CloseBar();
- }
-
- this.BarIsLoaded = true;
-
- if (bar != null)
- {
- this.CreateBarWindow(bar);
- bar.ReloadRequired += this.OnBarOnReloadRequired;
- }
-
- return bar;
- }
-
- ///
- /// Loads the bar for the given session. If the user is a member of several, either the last one is used,
- /// or a selection dialog is presented.
- ///
- /// The current session.
- /// Force this community to show.
- public async Task LoadSessionBarAsync(MorphicSession session, string communityId)
- {
- if (this.firstBar && AppOptions.Current.Launch.BarFile != null)
- {
- this.LoadFromBarJson(AppOptions.Current.Launch.BarFile);
- return;
- }
-
- this.Logger.LogInformation($"Loading a bar ({session.Communities.Length} communities)");
-
- UserBar? bar;
-
- UserCommunity? community = null;
- UserBar? userBar = null;
-
- //if (session.Communities.Length == 0)
- //{
- // MessageBox.Show("You are not part of a Morphic community yet.", "Morphic");
- //}
- //else if (session.Communities.Length == 1)
- //{
- // community = session.Communities.First();
- //}
- //else
- //{
- // The user is a member of multiple communities.
-
- //// See if any membership has changed
- //bool changed = session.Communities.Length != lastCommunities.Length
- // || !session.Communities.Select(c => c.Id).OrderBy(id => id)
- // .SequenceEqual(lastCommunities.OrderBy(id => id));
-
- if (/*!changed &&*/ communityId != null)
- {
- community = session.Communities.FirstOrDefault(c => c.Id == communityId);
- }
-
- //if (community == null)
- //{
- // this.Logger.LogInformation("Showing community picker");
-
- // // Load the bars while the picker is shown
- // Dictionary> bars =
- // session.Communities.ToDictionary(c => c.Id, c => session.GetBar(c.Id));
-
- // // Show the picker
- // CommunityPickerWindow picker = new CommunityPickerWindow(session.Communities);
- // bool gotCommunity = picker.ShowDialog() == true;
- // community = gotCommunity ? picker.SelectedCommunity : null;
-
- // if (community != null)
- // {
- // userBar = await bars[community.Id];
- // }
- //}
- //}
-
- if (community != null)
- {
- userBar ??= await session.GetBar(community.Id);
-
- this.Logger.LogInformation($"Showing bar for community {community.Id} {community.Name}");
- string barJson = this.GetUserBarJson(userBar);
- BarData? barData = this.LoadFromBarJson(userBar.Id, barJson);
- if (barData != null)
- {
- barData.CommunityId = community.Id;
- }
-
- AppOptions.Current.LastCommunity = community?.Id;
- }
- else
- {
- // if the community could not be found, show the Basic MorphicBar instead
- this.LoadBasicMorphicBar();
-
- AppOptions.Current.LastCommunity = null;
- }
-
- AppOptions.Current.Communities = session.Communities.Select(c => c.Id).ToArray();
- }
-
- ///
- /// Gets the json for a , so it can be loaded with a better deserialiser.
- ///
- /// Bar data object from Morphic.Core
- private string GetUserBarJson(UserBar userBar)
- {
- // Serialise the bar data so it can be loaded with a better deserialiser.
- SystemJson.JsonSerializerOptions serializerOptions = new SystemJson.JsonSerializerOptions();
- serializerOptions.Converters.Add(new JsonElementInferredTypeConverter());
- serializerOptions.Converters.Add(
- new SystemJson.Serialization.JsonStringEnumConverter(SystemJson.JsonNamingPolicy.CamelCase));
- string barJson = SystemJson.JsonSerializer.Serialize(userBar, serializerOptions);
-
- // Dump to a file, for debugging.
- string barFile = AppPaths.GetConfigFile("last-bar.json5");
- File.WriteAllText(barFile, barJson);
-
- return barJson;
- }
-
- #endregion
-
- #region INotifyPropertyChanged
- public event PropertyChangedEventHandler? PropertyChanged;
-
- protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
- {
- this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
- }
- #endregion
-
- }
-
- public class BarEventArgs : EventArgs
- {
- public BarEventArgs(PrimaryBarWindow window)
- {
- this.Window = window;
- this.Bar = this.Window.Bar;
- }
-
- public BarData Bar { get; private set; }
- public PrimaryBarWindow Window { get; private set; }
-
- }
-}
diff --git a/Morphic.Client/Bar/Data/Actions/ApplicationAction.cs b/Morphic.Client/Bar/Data/Actions/ApplicationAction.cs
deleted file mode 100644
index 5f31b030..00000000
--- a/Morphic.Client/Bar/Data/Actions/ApplicationAction.cs
+++ /dev/null
@@ -1,356 +0,0 @@
-// ApplicationAction.cs: A bar action that starts an application.
-//
-// Copyright 2020 Raising the Floor - International
-//
-// Licensed under the New BSD license. You may not use this file except in
-// compliance with this License.
-//
-// You may obtain a copy of the License at
-// https://github.com/GPII/universal/blob/master/LICENSE.txt
-
-using Morphic.Windows.Native.WindowsCom;
-
-namespace Morphic.Client.Bar.Data.Actions
-{
- using Microsoft.Extensions.Logging;
- using Microsoft.Win32;
- using Morphic.Core;
- using Newtonsoft.Json;
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.IO;
- using System.Linq;
- using System.Threading.Tasks;
- using System.Windows;
- using System.Windows.Input;
- using System.Windows.Interop;
- using System.Windows.Media;
- using System.Windows.Media.Imaging;
-
- ///
- /// Action to start an application.
- ///
- [JsonTypeName("application")]
- public class ApplicationAction : BarAction
- {
- private string? exeNameValue;
-
- ///
- /// The actual path to the executable.
- ///
- public string? AppPath { get; set; }
-
- public override ImageSource? DefaultImageSource
- {
- get
- {
- if (this.AppPath != null)
- {
- return Imaging.CreateBitmapSourceFromHIcon(
- System.Drawing.Icon.ExtractAssociatedIcon(this.AppPath).Handle,
- Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
- }
- else
- {
- return null;
- }
- }
- }
-
- ///
- /// Start a default application. This value points to an entry in default-apps.json5.
- ///
- [JsonProperty("default")]
- public string? DefaultAppName { get; set; }
-
- public BarAction? DefaultApp { get; private set; }
-
- ///
- /// Invoke the value in `exe` as-is, via the shell (explorer). Don't resolve the path.
- ///
- [JsonProperty("shell")]
- public bool Shell { get; set; }
-
- ///
- /// This is a windows store app. The value of `exe` is the Application User Model ID of the app.
- /// For example, `Microsoft.WindowsCalculator_8wekyb3d8bbwe!App`
- ///
- [JsonProperty("appx")]
- public bool AppX { get; set; }
-
- ///
- /// true to always start a new instance. false to activate an existing instance.
- ///
- [JsonProperty("newInstance")]
- public bool NewInstance { get; set; }
-
- ///
- /// The initial state of the window.
- ///
- [JsonProperty("windowStyle")]
- public ProcessWindowStyle WindowStyle { get; set; } = ProcessWindowStyle.Normal;
-
-
- ///
- /// Executable name, or the full path to it. If also providing arguments, surround the executable path with quotes.
- ///
- [JsonProperty("exe", Required = Required.Always)]
- public string ExeName
- {
- get => this.exeNameValue ?? string.Empty;
- set
- {
- this.exeNameValue = value;
-
- // A url like "mailto:"
- bool isUrl = this.exeNameValue.Length > 3 && this.exeNameValue.EndsWith(':');
- if (isUrl)
- {
- this.Shell = true;
- }
-
- if (this.exeNameValue.StartsWith("appx:", StringComparison.InvariantCultureIgnoreCase))
- {
- this.AppX = true;
- this.exeNameValue = this.exeNameValue.Substring(5);
- }
-
- if (this.Shell || this.AppX || this.exeNameValue.Length == 0)
- {
- this.AppPath = null;
- }
- else
- {
- if (this.ExeName.StartsWith('"'))
- {
- int nextQuote = this.exeNameValue.IndexOf('"', 1);
- if (nextQuote < 0)
- {
- App.Current.Logger.LogWarning($"Executable path [{this.ExeName}] has mismatching quote");
- this.AppPath = this.ExeName.Substring(1);
- }
- else
- {
- this.AppPath = this.ExeName.Substring(1, nextQuote - 1);
- this.ArgumentsString = this.ExeName.Substring(nextQuote + 1).Trim();
- }
- }
-
- this.AppPath = this.ResolveAppPath(this.exeNameValue);
- App.Current.Logger.LogDebug($"Resolved exe file '{this.exeNameValue}' to '{this.AppPath ?? "(null)"}'");
- }
-
- this.IsAvailable = this.AppPath != null;
- }
- }
-
- ///
- /// Array of arguments.
- ///
- [JsonProperty("args")]
- public List Arguments { get; set; } = new List();
-
- ///
- /// The arguments, if they're passed after the exe name.
- ///
- public string? ArgumentsString { get; set; }
-
- ///
- /// Environment variables to set
- ///
- [JsonProperty("env")]
- public Dictionary EnvironmentVariables { get; set; } = new Dictionary();
-
- ///
- /// Resolves the path of an executable, by looking in the "App Paths" registry key or the PATH environment.
- /// If a full path is provided, and it doesn't exist, then the path for the file name alone is resolved.
- ///
- /// Environment variables in the file path are also resolved.
- ///
- /// The `exeName` input value.
- /// Full path to the executable if found, or null.
- private string? ResolveAppPath(string exeName)
- {
- string file = Environment.ExpandEnvironmentVariables(exeName);
- string ext = Path.GetExtension(file).ToLower();
- string withExe, withoutExe;
-
- // Try with the .exe extension first, then without, but if the file ends with a '.', then try that first.
- // (similar to CreateProcess)
- if (file.EndsWith("."))
- {
- string? result1 = this.ResolveAppPathAsIs(file);
- if (result1 != null)
- {
- return result1;
- }
-
- withExe = Path.ChangeExtension(file, ".exe");
- withoutExe = Path.ChangeExtension(file, null);
- }
- else if (ext == ".exe")
- {
- withExe = file;
- withoutExe = Path.ChangeExtension(file, null);
- }
- else
- {
- withExe = file + ".exe";
- withoutExe = file;
- }
-
- string? result = this.ResolveAppPathAsIs(withExe);
- if (result != null)
- {
- return result;
- }
- else
- {
- return this.ResolveAppPathAsIs(withoutExe);
- }
- }
-
- ///
- /// Called by ResolveAppPath to perform the actual resolving work.
- ///
- ///
- /// Full path to the executable if found, or null.
- private string? ResolveAppPathAsIs(string file)
- {
- string? fullPath = null;
-
- if (Path.IsPathRooted(file))
- {
- if (File.Exists(file))
- {
- fullPath = file;
- }
-
- file = Path.GetFileName(file);
- }
-
- return fullPath ?? this.SearchAppPaths(file) ?? this.SearchPathEnv(file);
- }
-
- ///
- /// Searches the directories in the PATH environment variable.
- ///
- ///
- /// null if not found.
- private string? SearchPathEnv(string file)
- {
- // Alternative: https://docs.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-pathfindonpathw
- return Environment.GetEnvironmentVariable("PATH")?
- .Split(Path.PathSeparator)
- .Select(p => Path.Combine(p, file))
- .FirstOrDefault(File.Exists);
- }
-
- ///
- /// Searches SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths (in both HKCU and HKLM) for an executable.
- ///
- ///
- /// null if not found.
- private string? SearchAppPaths(string file)
- {
- string? fullPath = null;
-
- // Look in *\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths
- foreach (RegistryKey rootKey in new[] {Registry.CurrentUser, Registry.LocalMachine})
- {
- RegistryKey? key =
- rootKey.OpenSubKey($@"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\{file}");
- if (key != null)
- {
- fullPath = key.GetValue(string.Empty) as string;
- if (fullPath != null)
- {
- break;
- }
- }
- }
-
- return fullPath;
- }
-
- protected override Task InvokeAsyncImpl(string? source = null, bool? toggleState = null)
- {
- if (this.DefaultApp != null && string.IsNullOrEmpty(this.ExeName))
- {
- return this.DefaultApp.InvokeAsync(source);
- }
-
- if (this.AppX)
- {
- var pid = Appx.Start(this.ExeName);
- return Task.FromResult(pid > 0 ? IMorphicResult.SuccessResult : IMorphicResult.ErrorResult);
- }
-
- if (!this.NewInstance && (Keyboard.Modifiers & ModifierKeys.Shift) != ModifierKeys.Shift)
- {
- bool activated = this.ActivateInstance().IsSuccess;
- if (activated)
- {
- return Task.FromResult(IMorphicResult.SuccessResult);
- }
- }
-
- ProcessStartInfo startInfo = new ProcessStartInfo()
- {
- FileName = this.AppPath ?? this.ExeName,
- ErrorDialog = true,
- // This is required to start taskmgr (the UAC prompt)
- UseShellExecute = true,
- WindowStyle = this.WindowStyle
-
- };
-
- if (this.Shell)
- {
- startInfo.UseShellExecute = true;
- }
-
- if (this.Arguments.Count > 0)
- {
- foreach (string argument in this.Arguments)
- {
- startInfo.ArgumentList.Add(this.ResolveString(argument, source));
- }
- }
- else
- {
- startInfo.Arguments = this.ResolveString(this.ArgumentsString, source);
- }
-
- foreach (var (key, value) in this.EnvironmentVariables)
- {
- startInfo.EnvironmentVariables.Add(key, this.ResolveString(value, source));
- }
-
- Process? process = Process.Start(startInfo);
-
- return Task.FromResult(process != null ? IMorphicResult.SuccessResult : IMorphicResult.ErrorResult);
- }
-
- ///
- /// Activates a running instance of the application.
- ///
- /// false if it could not be done.
- ///
- private IMorphicResult ActivateInstance()
- {
- bool success = false;
- string? friendlyName = Path.GetFileNameWithoutExtension(this.AppPath);
- if (!string.IsNullOrEmpty(friendlyName))
- {
- success = Process.GetProcessesByName(friendlyName)
- .Where(p => p.MainWindowHandle != IntPtr.Zero)
- .OrderByDescending(p => p.StartTime)
- .Any(process => WinApi.ActivateWindow(process.MainWindowHandle));
- }
-
- return success ? IMorphicResult.SuccessResult : IMorphicResult.ErrorResult;
- }
- }
-}
diff --git a/Morphic.Client/Bar/Data/Actions/BarAction.cs b/Morphic.Client/Bar/Data/Actions/BarAction.cs
deleted file mode 100644
index f5b725d4..00000000
--- a/Morphic.Client/Bar/Data/Actions/BarAction.cs
+++ /dev/null
@@ -1,342 +0,0 @@
-// BarAction.cs: Actions performed by bar items.
-//
-// Copyright 2020 Raising the Floor - International
-//
-// Licensed under the New BSD license. You may not use this file except in
-// compliance with this License.
-//
-// You may obtain a copy of the License at
-// https://github.com/GPII/universal/blob/master/LICENSE.txt
-
-namespace Morphic.Client.Bar.Data.Actions
-{
- using CountlySDK;
- using Microsoft.Extensions.Logging;
- using Morphic.Core;
- using Newtonsoft.Json;
- using Newtonsoft.Json.Linq;
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Linq;
- using System.Net.WebSockets;
- using System.Text;
- using System.Threading;
- using System.Threading.Tasks;
- using System.Windows.Forms;
- using System.Windows.Media;
-
- ///
- /// An action for a bar item.
- ///
- [JsonObject(MemberSerialization.OptIn)]
- [JsonConverter(typeof(TypedJsonConverter), "kind", "shellExec")]
- public abstract class BarAction
- {
- [JsonProperty("identifier")]
- public string Id { get; set; } = string.Empty;
-
- ///
- /// Called by Invoke to perform the implementation-specific action invocation.
- ///
- /// Button ID, for multi-button bar items.
- /// New state, if the button is a toggle.
- ///
- protected abstract Task InvokeAsyncImpl(string? source = null, bool? toggleState = null);
-
- ///
- /// Invokes the action.
- ///
- /// Button ID, for multi-button bar items.
- /// New state, if the button is a toggle.
- ///
- public async Task InvokeAsync(string? source = null, bool? toggleState = null)
- {
- IMorphicResult result;
- try
- {
- try
- {
- result = await this.InvokeAsyncImpl(source, toggleState);
- }
- catch (Exception e) when (!(e is ActionException || e is OutOfMemoryException))
- {
- throw new ActionException(e.Message, e);
- }
- }
- catch (ActionException e)
- {
- App.Current.Logger.LogError(e, $"Error while invoking action for bar {this.Id} {this}");
-
- if (e.UserMessage != null)
- {
- MessageBox.Show($"There was a problem performing the action:\n\n{e.UserMessage}",
- "Custom MorphicBar", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
- }
-
- result = IMorphicResult.ErrorResult;
- }
- finally
- {
- // record telemetry data for this action
- await this.SendTelemetryForBarAction(source, toggleState);
- }
-
- return result;
- }
-
- // NOTE: we should refactor this functionality to functions attached to each button (similar to how action callbacks are invoked)
- private async Task SendTelemetryForBarAction(string? source = null, bool? toggleState = null)
- {
- // handle actions which must be filted by id
- switch (this.Id)
- {
- case "magnify":
- {
- if (source == "on")
- {
- await Countly.RecordEvent("magnifierShow");
- }
- else if (source == "off")
- {
- await Countly.RecordEvent("magnifierHide");
- }
- }
- break;
- case "read-aloud":
- {
- if (source == "play")
- {
- await Countly.RecordEvent("readSelectedPlay");
- }
- else if (source == "stop")
- {
- await Countly.RecordEvent("readSelectedStop");
- break;
- }
- }
- break;
- case "":
- switch (source)
- {
- case "com.microsoft.windows.colorFilters/enabled":
- {
- if (toggleState == true)
- {
- await Countly.RecordEvent("colorFiltersOn");
- return;
- }
- else
- {
- await Countly.RecordEvent("colorFiltersOff");
- return;
- }
- }
- break;
- case "com.microsoft.windows.highContrast/enabled":
- {
- if (toggleState == true)
- {
- await Countly.RecordEvent("highContrastOn");
- return;
- }
- else
- {
- await Countly.RecordEvent("highContrastOff");
- return;
- }
- }
- break;
- case "com.microsoft.windows.nightMode/enabled":
- {
- if (toggleState == true)
- {
- await Countly.RecordEvent("nightModeOn");
- return;
- }
- else
- {
- await Countly.RecordEvent("nightModeOff");
- return;
- }
- }
- break;
- case "copy":
- {
- await Countly.RecordEvent("screenSnip");
- }
- break;
- case "dark-mode":
- {
- if (toggleState == true)
- {
- await Countly.RecordEvent("darkModeOn");
- }
- else
- {
- await Countly.RecordEvent("darkModeOff");
- }
- }
- break;
- case null:
- // no tags; this is the Morphie button or another custom element with no known tags
- break;
- default:
- // we do not understand this action type (for telemetry logging purposes)
- Debug.Assert(false, "Unknown Action ID (missing telemetry hooks)");
- break;
- }
- break;
- case "screen-zoom":
- // this action type's telemetry is logged elsewhere
- break;
- default:
- // we do not understand this action type (for telemetry logging purposes)
- Debug.Assert(false, "Unknown Action ID (missing telemetry hooks)");
- break;
- }
- }
-
- ///
- /// Resolves "{identifiers}" in a string with its value.
- ///
- ///
- ///
- /// null if arg is null
- protected string? ResolveString(string? arg, string? source)
- {
- // Today, there is only "{button}".
- return arg?.Replace("{button}", source ?? string.Empty);
- }
-
- public virtual Uri? DefaultImageUri { get; }
- public virtual ImageSource? DefaultImageSource { get; }
- public virtual bool IsAvailable { get; protected set; } = true;
-
- public virtual void Deserialized(BarData barData)
- {
- }
- }
-
- [JsonTypeName("null")]
- public class NoOpAction : BarAction
- {
- protected override Task InvokeAsyncImpl(string? source = null, bool? toggleState = null)
- {
- return Task.FromResult(IMorphicResult.SuccessResult);
- }
- }
-
- [JsonTypeName("internal")]
- public class InternalAction : BarAction
- {
- [JsonProperty("function", Required = Required.Always)]
- public string? FunctionName { get; set; }
-
- [JsonProperty("args")]
- public Dictionary Arguments { get; set; } = new Dictionary();
-
- public string? TelemetryEventName { get; set; }
-
- protected override Task InvokeAsyncImpl(string? source = null, bool? toggleState = null)
- {
- try
- {
- if (this.FunctionName == null)
- {
- return Task.FromResult(IMorphicResult.SuccessResult);
- }
-
- Dictionary resolvedArgs = this.Arguments
- .ToDictionary(kv => kv.Key, kv => this.ResolveString(kv.Value, source) ?? string.Empty);
-
- resolvedArgs.Add("state", toggleState == true ? "on" : "off");
-
- return InternalFunctions.Default.InvokeFunction(this.FunctionName, resolvedArgs);
- }
- finally
- {
- if (this.TelemetryEventName != null)
- {
- Countly.RecordEvent(this.TelemetryEventName!);
- }
- }
- }
- }
-
- [JsonTypeName("gpii")]
- public class GpiiAction : BarAction
- {
- [JsonProperty("data", Required = Required.Always)]
- public JObject RequestObject { get; set; } = null!;
-
- protected override async Task InvokeAsyncImpl(string? source = null, bool? toggleState = null)
- {
- ClientWebSocket socket = new ClientWebSocket();
- CancellationTokenSource cancel = new CancellationTokenSource();
- await socket.ConnectAsync(new Uri("ws://localhost:8081/pspChannel"), cancel.Token);
-
- string requestString = this.RequestObject.ToString();
- byte[] bytes = Encoding.UTF8.GetBytes(requestString);
-
- ArraySegment sendBuffer = new ArraySegment(bytes);
- await socket.SendAsync(sendBuffer, WebSocketMessageType.Text, true, cancel.Token);
-
- return IMorphicResult.SuccessResult;
- }
- }
-
- [JsonTypeName("shellExec")]
- public class ShellExecuteAction : BarAction
- {
- [JsonProperty("run")]
- public string? ShellCommand { get; set; }
-
- protected override Task InvokeAsyncImpl(string? source = null, bool? toggleState = null)
- {
- bool success = true;
- if (!string.IsNullOrEmpty(this.ShellCommand))
- {
- Process? process = Process.Start(new ProcessStartInfo()
- {
- FileName = this.ResolveString(this.ShellCommand, source),
- UseShellExecute = true
- });
- success = process != null;
- }
-
- return Task.FromResult(success ? IMorphicResult.SuccessResult : IMorphicResult.ErrorResult);
- }
-
- public override void Deserialized(BarData barData)
- {
- }
- }
-
- ///
- /// Exception that gets thrown by action invokers.
- ///
- public class ActionException : ApplicationException
- {
- ///
- /// The message displayed to the user. null to not display a message.
- ///
- public string? UserMessage { get; set; }
-
- public ActionException(string? userMessage)
- : this(userMessage, userMessage, null)
- {
- }
- public ActionException(string? userMessage, Exception innerException)
- : this(userMessage, userMessage, innerException)
- {
- }
-
- public ActionException(string? userMessage, string? internalMessage = null, Exception? innerException = null)
- : base(internalMessage ?? userMessage ?? innerException?.Message, innerException)
- {
- this.UserMessage = userMessage;
- }
- }
-
-}
\ No newline at end of file
diff --git a/Morphic.Client/Bar/Data/Actions/Functions.cs b/Morphic.Client/Bar/Data/Actions/Functions.cs
deleted file mode 100644
index bfebc993..00000000
--- a/Morphic.Client/Bar/Data/Actions/Functions.cs
+++ /dev/null
@@ -1,251 +0,0 @@
-namespace Morphic.Client.Bar.Data.Actions
-{
- using System;
- using System.Collections.Generic;
- using System.Diagnostics.CodeAnalysis;
- using System.IO;
- using System.Linq;
- using System.Media;
- using System.Runtime.InteropServices;
- using System.Threading.Tasks;
- using System.Windows;
- using Windows.Native.Input;
- using Windows.Native.Speech;
- using global::Windows.Media.SpeechSynthesis;
- using Microsoft.Extensions.Logging;
- using Settings.SettingsHandlers;
- using Settings.SolutionsRegistry;
- using UI;
- using Clipboard = System.Windows.Forms.Clipboard;
- using IDataObject = System.Windows.Forms.IDataObject;
-
- [HasInternalFunctions]
- // ReSharper disable once UnusedType.Global - accessed via reflection.
- public class Functions
- {
- [InternalFunction("screenshot")]
- public static async Task Screenshot(FunctionArgs args)
- {
- // Hide all application windows
- Dictionary opacity = new Dictionary();
- HashSet visible = new HashSet();
- try
- {
- foreach (Window window in App.Current.Windows)
- {
- if (window is BarWindow || window is QuickHelpWindow) {
- if (window.AllowsTransparency)
- {
- opacity[window] = window.Opacity;
- window.Opacity = 0;
- }
- else
- {
- visible.Add(window);
- window.Visibility = Visibility.Collapsed;
- }
- }
- }
-
- // Give enough time for the windows to disappear
- await Task.Delay(500);
-
- // Hold down the windows key while pressing shift + s
- const uint windowsKey = 0x5b; // VK_LWIN
- Keyboard.PressKey(windowsKey, true);
- System.Windows.Forms.SendKeys.SendWait("+s");
- Keyboard.PressKey(windowsKey, false);
-
- }
- finally
- {
- // Give enough time for snip tool to grab the screen without the morphic UI.
- await Task.Delay(3000);
-
- // Restore the windows
- foreach ((Window window, double o) in opacity)
- {
- window.Opacity = o;
- }
-
- foreach (Window window in visible)
- {
- window.Visibility = Visibility.Visible;
- }
- }
-
- return true;
- }
-
- [InternalFunction("menu", "key=Morphic")]
- public static Task ShowMenu(FunctionArgs args)
- {
- // NOTE: this internal function is only called by the MorphicBar's Morphie menu button
- App.Current.ShowMenuAsync(null, Morphic.Client.Menu.MorphicMenu.MenuOpenedSource.morphicBarIcon);
- return Task.FromResult(true);
- }
-
- ///
- /// Lowers or raises the volume.
- ///
- /// direction: "up"/"down", amount: number of 1/100 to move
- ///
- [InternalFunction("volume", "direction", "amount=10")]
- public static Task Volume(FunctionArgs args)
- {
- IntPtr taskTray = WinApi.FindWindow("Shell_TrayWnd", IntPtr.Zero);
- if (taskTray != IntPtr.Zero)
- {
- int action = args["direction"] == "up"
- ? WinApi.APPCOMMAND_VOLUME_UP
- : WinApi.APPCOMMAND_VOLUME_DOWN;
-
- // Each command moves the volume by 2 notches.
- int times = Math.Clamp(Convert.ToInt32(args["amount"]), 1, 20) / 2;
- for (int n = 0; n < times; n++)
- {
- WinApi.SendMessage(taskTray, WinApi.WM_APPCOMMAND, IntPtr.Zero,
- (IntPtr)WinApi.MakeLong(0, (short)action));
- }
- }
-
- return Task.FromResult(true);
- }
-
- // Plays the speech sound.
- private static SoundPlayer? speechPlayer;
-
- ///
- /// Reads the selected text.
- ///
- /// action: "play", "pause", or "stop"
- ///
- [InternalFunction("readAloud", "action")]
- public static async Task ReadAloud(FunctionArgs args)
- {
- string action = args["action"];
- switch (action)
- {
- case "pause":
- App.Current.Logger.LogError("ReadAloud: pause not supported");
- break;
-
- case "stop":
- case "play":
- Functions.speechPlayer?.Stop();
- Functions.speechPlayer?.Dispose();
- Functions.speechPlayer = null;
-
- if (action == "stop")
- {
- break;
- }
-
- App.Current.Logger.LogDebug("ReadAloud: Storing clipboard");
- IDataObject? clipboardData = Clipboard.GetDataObject();
- Dictionary? dataStored = null;
- if (clipboardData != null)
- {
- dataStored = clipboardData.GetFormats()
- .ToDictionary(format => format, format => (object?)clipboardData.GetData(format, false));
- }
-
- Clipboard.Clear();
-
- // Get the selection
- App.Current.Logger.LogDebug("ReadAloud: Getting selected text");
- await SelectionReader.Default.GetSelectedText(System.Windows.Forms.SendKeys.SendWait);
- string text = Clipboard.GetText();
-
- // Restore the clipboard
- App.Current.Logger.LogDebug("ReadAloud: Restoring clipboard");
- Clipboard.Clear();
- dataStored?.Where(kv => kv.Value != null).ToList()
- .ForEach(kv => Clipboard.SetData(kv.Key, kv.Value));
-
- // Talk the talk
- SpeechSynthesizer synth = new SpeechSynthesizer();
- SpeechSynthesisStream stream = await synth.SynthesizeTextToStreamAsync(text);
- speechPlayer = new SoundPlayer(stream.AsStream());
- speechPlayer.LoadCompleted += (o, args) =>
- {
- speechPlayer.Play();
- };
-
- speechPlayer.LoadAsync();
-
- break;
-
- }
-
- return true;
- }
-
- ///
- /// Sends key strokes to the active application.
- ///
- /// keys: the keys (see MSDN for SendKeys.Send())
- ///
- [InternalFunction("sendKeys", "keys")]
- public static async Task SendKeys(FunctionArgs args)
- {
- await SelectionReader.Default.ActivateLastActiveWindow();
- System.Windows.Forms.SendKeys.SendWait(args["keys"]);
- return true;
- }
-
- [InternalFunction("signOut")]
- public static async Task SignOut(FunctionArgs args)
- {
- var success = Morphic.Windows.Native.WindowsSession.WindowsSession.LogOff();
- return success;
- }
-
- [InternalFunction("darkMode")]
- public static async Task DarkMode(FunctionArgs args)
- {
- bool on = args["state"] == "on";
-
- /*
- * NOTE: in addition to the SPI implementation (in code, below), we could also turn on/off the dark theme (via powershell...or possibly via direct registry access); here are the corresponding PowerShell commands
- *
- * SWITCH TO LIGHT MODE:
- * New-ItemProperty -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize -Name SystemUsesLightTheme -Value 1 -Type Dword -Force
- * New-ItemProperty -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize -Name AppsUseLightTheme -Value 1 -Type Dword -Force
- *
- * SWITCH TO DARK MODE:
- * New-ItemProperty -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize -Name SystemUsesLightTheme -Value 0 -Type Dword -Force
- * New-ItemProperty -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize -Name AppsUseLightTheme -Value 0 -Type Dword -Force
- */
-
- // set system dark/light theme
- Setting systemThemeSetting = App.Current.MorphicSession.Solutions.GetSetting(SettingId.LightThemeSystem);
- await systemThemeSetting.SetValueAsync(!on);
-
- // set apps dark/light theme
- Setting appsThemeSetting = App.Current.MorphicSession.Solutions.GetSetting(SettingId.LightThemeApps);
- await appsThemeSetting.SetValueAsync(!on);
- return true;
- }
-
- [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Windows API naming")]
- [SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Windows API naming")]
- private static class WinApi
- {
- public const int APPCOMMAND_VOLUME_DOWN = 9;
- public const int APPCOMMAND_VOLUME_UP = 10;
- public const int WM_APPCOMMAND = 0x319;
-
- [DllImport("user32.dll")]
- public static extern IntPtr FindWindow(string lpClassName, IntPtr lpWindowName);
-
- [DllImport("user32.dll")]
- public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
-
- public static int MakeLong(short low, short high)
- {
- return (low & 0xffff) | ((high & 0xffff) << 16);
- }
- }
- }
-}
diff --git a/Morphic.Client/Bar/Data/Actions/InternalFunctions.cs b/Morphic.Client/Bar/Data/Actions/InternalFunctions.cs
deleted file mode 100644
index 5a1a382c..00000000
--- a/Morphic.Client/Bar/Data/Actions/InternalFunctions.cs
+++ /dev/null
@@ -1,183 +0,0 @@
-// InternalFunctions.cs: Handles the internal functions for bar items.
-//
-// Copyright 2020 Raising the Floor - International
-//
-// Licensed under the New BSD license. You may not use this file except in
-// compliance with this License.
-//
-// You may obtain a copy of the License at
-// https://github.com/GPII/universal/blob/master/LICENSE.txt
-
-namespace Morphic.Client.Bar.Data.Actions
-{
- using Microsoft.Extensions.Logging;
- using Morphic.Core;
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Reflection;
- using System.Threading.Tasks;
-
- ///
- /// Handles the invocation of internal functions, used by the InternalAction class.
- ///
- /// The functions are public static methods decorated with [InternalFunction("fname")], in any class in this
- /// assembly (which also has the HasInternalFunctions attribute).
- ///
- public class InternalFunctions
- {
- /// Default singleton instance.
- public static InternalFunctions Default = new InternalFunctions();
-
- /// All internal functions.
- private readonly Dictionary all;
-
- public delegate Task InternalFunction(FunctionArgs args);
-
- protected InternalFunctions()
- {
- this.all = InternalFunctions.FindAllFunctions()
- .ToDictionary(attr => attr.FunctionName.ToLowerInvariant(), attr => attr);
- }
-
- ///
- /// Gets the methods that handle the built-in functions.
- ///
- ///
- private static IEnumerable FindAllFunctions()
- {
- // Get all public static methods in all public classes in this assembly, which both have the InternalFunction
- // attribute
- IEnumerable methods = typeof(InternalFunctions).Assembly.GetTypes()
- .Where(t => t.IsClass && t.IsPublic && t.GetCustomAttributes().Any())
- .SelectMany(t => t.GetMethods(BindingFlags.Public | BindingFlags.Static));
-
- // Add the methods decorated with [InternalFunction]
- foreach (MethodInfo method in methods)
- {
- InternalFunctionAttribute? attr = method.GetCustomAttribute();
- if (attr != null)
- {
- attr.SetFunction((InternalFunction)method.CreateDelegate(typeof(InternalFunction)));
- yield return attr;
- }
- }
- }
-
- ///
- /// Invokes a built-in function.
- ///
- /// The function name.
- /// The parameters.
- ///
- public Task InvokeFunction(string functionName, Dictionary functionArgs)
- {
- App.Current.Logger.LogDebug($"Invoking built-in function '{functionName}'");
-
- Task result;
-
- if (this.all.TryGetValue(functionName.ToLowerInvariant(),
- out InternalFunctionAttribute? functionAttribute))
- {
- FunctionArgs args = new FunctionArgs(functionAttribute, functionArgs);
- result = functionAttribute.Function(args);
- }
- else
- {
- throw new ActionException($"No internal function found for '{functionName}");
- }
-
- return result;
- }
- }
-
- ///
- /// Marks a method (or a class containing such methods) that's a built-in function for bar actions.
- ///
- [AttributeUsage(AttributeTargets.Method)]
- public class InternalFunctionAttribute : Attribute
- {
- public string FunctionName { get; }
- public string[] RequiredArguments { get; }
- public InternalFunctions.InternalFunction Function { get; private set; } = null!;
-
- ///
- /// Defines an internal function for the bar.
- ///
- /// Name of the function..
- ///
- /// Name of each required argument, if any. For optional parameters, use "name=default".
- ///
- public InternalFunctionAttribute(string functionName, params string[] requiredArgs)
- {
- this.RequiredArguments = requiredArgs;
- this.FunctionName = functionName;
- }
-
- public void SetFunction(InternalFunctions.InternalFunction internalFunction)
- {
- this.Function = internalFunction;
- }
-
- ///
- /// Checks a given arguments dictionary for require values, and adding the value for those that are missing.
- ///
- /// The arguments (gets modified).
- ///
- public void CheckRequiredArguments(Dictionary arguments)
- {
- foreach (string required in this.RequiredArguments)
- {
- string[] split = required.Split('=', 2);
- string name = split[0];
-
- if (!arguments.ContainsKey(name))
- {
- string? defaultValue = split.Length > 1 ? split[1] : null;
- if (defaultValue == null)
- {
- throw new ActionException(
- $"Internal function {this.FunctionName} invoked without parameter {name}");
- }
-
- arguments.Add(name, defaultValue);
- }
- }
- }
- }
-
- ///
- /// Identifies a class having internal functions.
- ///
- [AttributeUsage(AttributeTargets.Class)]
- public class HasInternalFunctionsAttribute : Attribute
- {
- }
-
- public class FunctionArgs
- {
- public string FunctionName { get; }
- public Dictionary Arguments { get; }
-
- ///
- /// Gets an argument value by its name, or an empty string if there's no such argument.
- ///
- ///
- public string this[string argumentName] => this.Arguments.TryGetValue(argumentName, out string? value)
- ? value
- : string.Empty;
-
- ///
- /// Creates arguments for a function.
- ///
- /// The function attribute of the method that handles the internal function.
- /// The arguments.
- public FunctionArgs(InternalFunctionAttribute functionAttribute, Dictionary args)
- {
- this.FunctionName = functionAttribute.FunctionName;
- this.Arguments = args.ToDictionary(kv => kv.Key, kv => kv.Value);
-
- functionAttribute.CheckRequiredArguments(this.Arguments);
- }
- }
-}
diff --git a/Morphic.Client/Bar/Data/Actions/SettingAction.cs b/Morphic.Client/Bar/Data/Actions/SettingAction.cs
deleted file mode 100644
index ca1519ad..00000000
--- a/Morphic.Client/Bar/Data/Actions/SettingAction.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-namespace Morphic.Client.Bar.Data.Actions
-{
- using Microsoft.Extensions.DependencyInjection;
- using Morphic.Core;
- using Newtonsoft.Json;
- using Settings.SettingsHandlers;
- using Settings.SolutionsRegistry;
- using System.Threading.Tasks;
-
- [JsonTypeName("setting")]
- public class SettingAction : BarAction
- {
- [JsonProperty("settingId", Required = Required.Always)]
- public string SettingId { get; set; } = string.Empty;
-
- public Setting? Setting { get; private set; }
- public Solutions Solutions { get; private set; } = null!;
-
- protected override Task InvokeAsyncImpl(string? source = null, bool? toggleState = null)
- {
- Setting? setting;
-
- if (this.Setting == null && !string.IsNullOrEmpty(source))
- {
- setting = this.Solutions.GetSetting(source);
- setting.SetValueAsync(toggleState);
- }
- else
- {
- setting = this.Setting;
- }
-
- if (setting == null)
- {
- return Task.FromResult(IMorphicResult.SuccessResult);
- }
-
- switch (source)
- {
- case "inc":
- return setting.Increment(1);
- case "dec":
- return setting.Increment(-1);
- case "on":
- return setting.SetValueAsync(true);
- case "off":
- return setting.SetValueAsync(false);
- }
-
- return Task.FromResult(IMorphicResult.ErrorResult);
- }
-
- public override void Deserialized(BarData bar)
- {
- base.Deserialized(bar);
-
- this.Solutions = bar.ServiceProvider.GetRequiredService();
- if (!string.IsNullOrEmpty(this.SettingId))
- {
- this.Setting = this.Solutions.GetSetting(this.SettingId);
- }
- }
- }
-}
diff --git a/Morphic.Client/Bar/Data/Actions/WebAction.cs b/Morphic.Client/Bar/Data/Actions/WebAction.cs
deleted file mode 100644
index deb98729..00000000
--- a/Morphic.Client/Bar/Data/Actions/WebAction.cs
+++ /dev/null
@@ -1,89 +0,0 @@
-// WebAction.cs: Bar action that opens a website.
-//
-// Copyright 2020 Raising the Floor - International
-//
-// Licensed under the New BSD license. You may not use this file except in
-// compliance with this License.
-//
-// You may obtain a copy of the License at
-// https://github.com/GPII/universal/blob/master/LICENSE.txt
-
-namespace Morphic.Client.Bar.Data.Actions
-{
- using Microsoft.Extensions.Logging;
- using Morphic.Core;
- using Newtonsoft.Json;
- using System;
- using System.Diagnostics;
- using System.Threading.Tasks;
-
- ///
- /// A web-link action.
- ///
- [JsonTypeName("link")]
- public class WebAction : BarAction
- {
- private string? urlString;
-
- [JsonProperty("url", Required = Required.Always)]
- public string UrlString
- {
- get => this.Uri?.ToString() ?? this.urlString ?? string.Empty;
- set
- {
- if (Uri.TryCreate(value, UriKind.Absolute, out Uri? uri))
- {
- // validate our uri
- switch (uri?.Scheme.ToLowerInvariant()) {
- case "http":
- case "https":
- // allowed
- break;
- default:
- // all other schemes (as well as a null scheme) are disallowed
- uri = null;
- break;
- }
-
- // save our validated uri
- this.Uri = uri;
- }
- else
- {
- this.urlString = value;
- App.Current.Logger.LogWarning($"Unable to parse url '{this.urlString}'");
- }
- }
- }
-
- public Uri? Uri { get; set; }
-
- ///
- /// Use the site's favicon as the default.
- ///
- public override Uri? DefaultImageUri
- {
- get
- {
- return null;
-// this.Uri != null ? new Uri($"https://icons.duckduckgo.com/ip2/{this.Uri.Host}.ico") : null;
- }
- }
-
- protected override Task InvokeAsyncImpl(string? source = null, bool? toggleState = null)
- {
- bool success = true;
- if (this.Uri != null)
- {
- Process? process = Process.Start(new ProcessStartInfo()
- {
- FileName = this.ResolveString(this.Uri?.ToString(), source),
- UseShellExecute = true
- });
- success = process != null;
- }
-
- return Task.FromResult(success ? IMorphicResult.SuccessResult : IMorphicResult.ErrorResult);
- }
- }
-}
diff --git a/Morphic.Client/Bar/Data/BarButton.cs b/Morphic.Client/Bar/Data/BarButton.cs
deleted file mode 100644
index 9dbbcbab..00000000
--- a/Morphic.Client/Bar/Data/BarButton.cs
+++ /dev/null
@@ -1,242 +0,0 @@
-// BarButton.cs: Button widget on the bar
-//
-// Copyright 2020 Raising the Floor - International
-//
-// Licensed under the New BSD license. You may not use this file except in
-// compliance with this License.
-//
-// You may obtain a copy of the License at
-// https://github.com/GPII/universal/blob/master/LICENSE.txt
-
-namespace Morphic.Client.Bar.Data
-{
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.IO;
- using System.Net;
- using System.Runtime.CompilerServices;
- using System.Threading;
- using System.Threading.Tasks;
- using System.Windows.Media;
- using Config;
- using Microsoft.Extensions.Logging;
- using Newtonsoft.Json;
- using UI.BarControls;
-
- ///
- /// Button bar item.
- ///
- [JsonTypeName("button")]
- [BarControl(typeof(ButtonBarControl))]
- public class BarButton : BarItem, INotifyPropertyChanged
- {
- private string? imagePath;
- private string? imageValue;
- private ImageSource? imageSource;
- private Uri? remoteImage;
-
- public BarButton(BarData bar) : base(bar)
- {
- }
-
- [JsonProperty("configuration.image_path")]
- public string? FrontendImagePath { get; set; }
-
- ///
- /// The original image, as defined in json.
- ///
- [JsonProperty("configuration.image_url")]
- public string? ImageValue
- {
- get => this.imageValue;
- set
- {
- this.imageValue = value ?? string.Empty;
- if (string.IsNullOrEmpty(this.imageValue))
- {
- this.ImagePath = string.Empty;
- }
- else
- {
- Uri.TryCreate(this.imageValue, UriKind.Absolute, out Uri? uri);
- string? localPath = null;
- if (uri == null || uri.IsFile)
- {
- localPath = BarImages.GetBarIconFile(this.imageValue);
- if (localPath == null)
- {
- uri = new Uri(this.Bar.FrontEndUri, this.FrontendImagePath);
- }
- }
-
- if (localPath != null)
- {
- this.ImagePath = localPath;
- }
- else if (uri != null)
- {
- // Download later.
- this.RemoteImage = uri;
- }
- }
- }
- }
-
- ///
- /// The image to use.
- ///
- public ImageSource? ImageSource
- {
- get => this.imageSource;
- set
- {
- this.imageSource = value;
- this.OnPropertyChanged();
- }
- }
-
- ///
- /// The real local path of the item's image.
- ///
- public string ImagePath
- {
- get => this.imagePath ?? string.Empty;
- private set => this.imagePath = value;
- }
-
- // Limit the concurrent downloads.
- private static SemaphoreSlim downloads = new SemaphoreSlim(8);
- private static HashSet downloading = new HashSet();
- private static HashSet downloadComplete = new HashSet();
-
- ///
- /// Loads the image specified by ImagePath.
- ///
- /// true on success.
- public async Task LoadImage()
- {
- bool success = false;
-
- // Download the remote image.
- if (this.DownloadRequired && this.RemoteImage != null)
- {
- using WebClient wc = new WebClient();
- string tempFile = this.ImagePath + ".new";
- try
- {
- try
- {
- await downloads.WaitAsync();
-
- // Check if the image is being downloaded by another bar item.
- bool downloadRequired = downloading.Add(this.ImagePath);
-
- if (downloadRequired)
- {
- // Download it
- this.Logger.LogDebug("Downloading {remoteImage}", this.RemoteImage);
- await wc.DownloadFileTaskAsync(this.RemoteImage, tempFile);
- }
- else
- {
- // wait for the other bar's download to complete
- while (!downloadComplete.Contains(this.ImagePath))
- {
- await Task.Delay(500);
- }
- }
- }
- finally
- {
- downloads.Release();
- }
- FileInfo fileInfo = new FileInfo(tempFile);
-
- if (fileInfo.Exists && fileInfo.Length > 0)
- {
- File.Move(tempFile, this.ImagePath, true);
- }
- }
- catch (Exception e) when (!(e is OutOfMemoryException))
- {
- // Ignore
- this.Logger.LogWarning(e, "Download failed {remoteImage}", this.RemoteImage);
- }
- finally
- {
- File.Delete(tempFile);
- downloadComplete.Add(this.ImagePath);
- }
- }
-
- // Load the local image.
- if (!string.IsNullOrEmpty(this.ImagePath) && File.Exists(this.ImagePath))
- {
- this.ImageSource = BarImages.CreateImageSource(this.ImagePath);
- success = this.ImageValue != null;
- }
-
- // Fallback to a default image.
- if (!success)
- {
- ImageSource? source = this.Action.DefaultImageSource;
- if (source != null)
- {
- this.ImageSource = source;
- success = true;
- }
- else
- {
- Uri? defaultUri = this.Action.DefaultImageUri;
- if (defaultUri != null && this.RemoteImage != defaultUri)
- {
- this.RemoteImage = defaultUri;
- success = await this.LoadImage();
- }
- }
- }
-
- return success;
- }
-
- ///
- /// true if downloading a new copy of a remote image is needed.
- ///
- public bool DownloadRequired { get; set; }
-
- ///
- /// The URL to the remote image.
- ///
- public Uri? RemoteImage
- {
- get => this.remoteImage;
- private set
- {
- this.remoteImage = value;
- if (this.remoteImage != null)
- {
- this.ImagePath = AppPaths.GetCacheFile(this.remoteImage, out bool exists);
- this.DownloadRequired = !exists
- || (DateTime.Now - File.GetLastWriteTime(this.ImagePath)).TotalDays > 2;
- }
- }
- }
-
- public override void Deserialized()
- {
- base.Deserialized();
-
- _ = this.LoadImage();
- }
-
- public bool ShowIcon => true;// string.IsNullOrEmpty(this.IconPath);
-
- public event PropertyChangedEventHandler? PropertyChanged;
-
- protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
- {
- this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
- }
- }
-}
diff --git a/Morphic.Client/Bar/Data/BarData.cs b/Morphic.Client/Bar/Data/BarData.cs
deleted file mode 100644
index 7e9f8149..00000000
--- a/Morphic.Client/Bar/Data/BarData.cs
+++ /dev/null
@@ -1,355 +0,0 @@
-// BarData.cs: Information about a bar.
-//
-// Copyright 2020 Raising the Floor - International
-//
-// Licensed under the New BSD license. You may not use this file except in
-// compliance with this License.
-//
-// You may obtain a copy of the License at
-// https://github.com/GPII/universal/blob/master/LICENSE.txt
-
-using Morphic.Service;
-
-namespace Morphic.Client.Bar.Data
-{
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Threading;
- using System.Threading.Tasks;
- using Config;
- using Microsoft.Extensions.DependencyInjection;
- using Microsoft.Extensions.Logging;
- using Newtonsoft.Json;
-
- ///
- /// Describes a bar.
- ///
- [JsonObject(MemberSerialization.OptIn)]
- public class BarData : IDisposable, IDeserializable
- {
- private List fileWatchers = new List();
-
- public event EventHandler? ReloadRequired;
-
- public BarData() : this(null)
- {
- }
-
- public BarData(IServiceProvider? serviceProvider)
- {
- this.ServiceProvider = serviceProvider ?? App.Current.ServiceProvider;
- SessionOptions sessionOptions = this.ServiceProvider.GetRequiredService();
- this.FrontEndUri = sessionOptions.FrontEndUri;
- this.BarEditorWebAppUri = sessionOptions.BarEditorWebAppUri;
- }
-
- public IServiceProvider ServiceProvider { get; set; }
-
- public Uri FrontEndUri { get; }
-
- public Uri BarEditorWebAppUri { get; }
-
- ///
- /// Where the bar data was loaded from (a url or path).
- ///
- public string Source { get; set; } = string.Empty;
-
- ///
- /// Bar identifier (currently unused by the client)
- ///
- [JsonProperty("id")]
- public string? Id { get; set; }
-
- ///
- /// Name of the bar (currently unused by the client)
- ///
- [JsonProperty("name")]
- public string? Name { get; set; }
-
- ///
- /// Title of the bar (the window caption)
- ///
- [JsonProperty("title")]
- public string? Title { get; set; } = "Custom MorphicBar";
-
- ///
- /// Size of everything.
- ///
- [JsonProperty("scale")]
- public double Scale { get; set; } = 1;
-
- ///
- /// What to do if all buttons do not fit.
- ///
- [JsonProperty("overflow")]
- public BarOverflow Overflow { get; set; } = BarOverflow.Resize;
-
- /// Initial bar positions.
- [JsonProperty("position", ObjectCreationHandling = ObjectCreationHandling.Reuse)]
- public BarPosition Position { get; set; } = new BarPosition();
-
- /// Initial bar positions.
- [JsonProperty("secondaryBar", ObjectCreationHandling = ObjectCreationHandling.Reuse)]
- public SecondaryBar SecondaryBar { get; set; } = new SecondaryBar();
-
- ///
- /// Base theme for bar items - items will take values from this if they haven't got their own.
- ///
- [JsonProperty("itemTheme", ObjectCreationHandling = ObjectCreationHandling.Reuse)]
- public BarItemTheme DefaultTheme { get; set; } = new BarItemTheme();
-
- ///
- /// Base theme for the buttons in the multi-button bar items.
- ///
- [JsonProperty("controlTheme", ObjectCreationHandling = ObjectCreationHandling.Reuse)]
- public BarItemTheme ControlTheme { get; set; } = new BarItemTheme();
-
- ///
- /// Theme for the bar.
- ///
- [JsonProperty("barTheme", ObjectCreationHandling = ObjectCreationHandling.Reuse)]
- public Theme BarTheme { get; set; } = new Theme();
-
- [JsonProperty("sizes", ObjectCreationHandling = ObjectCreationHandling.Reuse)]
- public BarSizes Sizes { get; set; } = new BarSizes();
-
- ///
- /// Gets all items.
- ///
- [JsonProperty("items")]
- public List AllItems { get; set; } = new List();
-
- ///
- /// Determines if an item should be on the primary bar.
- ///
- /// The item.
- /// true if the item belongs on the primary bar.
- private bool IsPrimaryItem(BarItem item)
- {
- return !item.Hidden && item.IsPrimary && !item.Overflow;
- }
-
- ///
- /// Determines if an item should be on the secondary bar.
- ///
- /// The item.
- /// true if the item belongs on the secondary bar.
- private bool IsSecondaryItem(BarItem item)
- {
- return !item.Hidden && !this.IsPrimaryItem(item);
- }
-
- ///
- /// Gets the items for the main bar.
- ///
- public IEnumerable PrimaryItems => this.AllItems.Where(this.IsPrimaryItem)
- .OrderByDescending(item => item.Priority);
-
- ///
- /// Gets the items for the additional buttons.
- ///
- public IEnumerable SecondaryItems => this.AllItems.Where(this.IsSecondaryItem)
- .OrderByDescending(item => item.IsPrimary)
- .ThenByDescending(item => item.Priority);
-
- public string? CommunityId { get; set; }
-
- private ILogger logger = App.Current.ServiceProvider.GetRequiredService>();
-
- ///
- /// Loads bar data from either a local file, or a url.
- ///
- /// The service provider/
- /// The local path or remote url.
- /// The json content, if already loaded.
- /// true to also include the default bar data.
- /// The bar data
- public static BarData? Load(IServiceProvider serviceProvider, string barSource, string? content = null, bool includeDefault = true)
- {
- BarData? defaultBar;
- if (includeDefault)
- {
- defaultBar = BarData.Load(serviceProvider, AppPaths.GetConfigFile("default-bar.json5", true), null, false);
- // Mark the items as being from the default specification
- defaultBar?.AllItems.ForEach(item => item.IsDefault = true);
-
- // if extra bar items were specified in the config file, add them to the left side of the MorphicBar now
- var morphicBarExtraItems = ConfigurableFeatures.MorphicBarExtraItems;
- if (morphicBarExtraItems.Count > 0)
- {
- List extraBarItems = new List();
- foreach (var extraItemData in morphicBarExtraItems)
- {
-
- BarButton extraBarItem = new BarButton(defaultBar);
- extraBarItem.ToolTipHeader = extraItemData.tooltipHeader;
- extraBarItem.ToolTip = extraItemData.tooltipText;
- extraBarItem.Text = extraItemData.label ?? "";
- switch (extraItemData.type)
- {
- case "link":
- extraBarItem.Action = new Morphic.Client.Bar.Data.Actions.WebAction();
- ((Morphic.Client.Bar.Data.Actions.WebAction)extraBarItem.Action).UrlString = extraItemData.url ?? "";
- break;
- case "action":
- var extraBarItemInternalAction = new Morphic.Client.Bar.Data.Actions.InternalAction();
- extraBarItemInternalAction.TelemetryEventName = "morphicBarExtraItem";
- extraBarItem.Action = extraBarItemInternalAction;
- ((Morphic.Client.Bar.Data.Actions.InternalAction)extraBarItem.Action).FunctionName = extraItemData.function!;
- break;
- default:
- // unknown type; this should be an impossible code path
- throw new NotImplementedException();
- }
- //extraBarItem.ColorValue = "#00FF00";
- extraBarItem.IsPrimary = true;
- //
- defaultBar?.AllItems.Add(extraBarItem);
- }
-
- // add a spacer entry
- BarButton spacerBarItem = new BarButton(defaultBar);
- spacerBarItem.ToolTipHeader = "";
- spacerBarItem.ToolTip = "";
- spacerBarItem.Text = "";
- spacerBarItem.ColorValue = "#FFFFFF";
- spacerBarItem.IsPrimary = true;
- //
- defaultBar?.AllItems.Add(spacerBarItem);
- }
- }
- else
- {
- defaultBar = null;
- }
-
- App.Current.Logger.LogInformation("Loading bar from {source}", barSource);
-
- BarData? bar;
-
- using (TextReader reader = content == null
- ? (TextReader)File.OpenText(barSource)
- : new StringReader(content))
- {
- bar = BarJson.Load(serviceProvider, reader, defaultBar);
- }
-
- bar.Source = barSource;
- if (File.Exists(barSource))
- {
- bar.AddWatcher(barSource);
- }
-
- return bar;
- }
-
- private bool hasDeserialized;
-
- ///
- /// Called when the bar has been deserialised. This can be called twice, for the default bar and the user's bar.
- ///
- public void Deserialized()
- {
- // Make the theme of each item inherit the default theme.
- this.BarTheme.Apply(Theme.DefaultBar());
- this.DefaultTheme.Apply(Theme.DefaultItem());
- this.ControlTheme.Apply(Theme.DefaultControl()).Apply(this.DefaultTheme);
-
- if (this.hasDeserialized)
- {
- // If a bar has no primary items, it looks stupid. Make all the items primary, and make them over-flow to
- // the secondary bar.
- bool hasPrimary = this.PrimaryItems.Any(item => !item.IsDefault);
- if (!hasPrimary)
- {
- foreach (BarItem item in this.SecondaryItems)
- {
- item.IsPrimary = true;
- }
-
- this.Overflow = BarOverflow.Secondary;
- }
- }
-
- this.AllItems.ForEach(item =>
- {
- item.IsDefault = !this.hasDeserialized;
- item.Deserialized();
- });
-
-
-
- this.hasDeserialized = true;
- }
-
- ///
- /// Makes a url from a string containing a url or a local path (absolute or relative).
- ///
- ///
- ///
- public static Uri MakeUrl(string input)
- {
- if (!Uri.TryCreate(input, UriKind.Absolute, out Uri? uri))
- {
- // Assume it's a relative path.
- string fullPath = Path.GetFullPath(input);
- uri = new Uri(fullPath);
- }
-
- return uri;
- }
-
- private void AddWatcher(string file)
- {
- string fullPath = Path.GetFullPath(file);
- string dir = Path.GetDirectoryName(fullPath)!;
- string filename = Path.GetFileName(fullPath);
-
- FileSystemWatcher watcher = new FileSystemWatcher(dir)
- {
- Filter = filename,
- NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite | NotifyFilters.Size
- | NotifyFilters.FileName,
- EnableRaisingEvents = true
- };
-
- watcher.Changed += this.WatcherOnChanged;
- watcher.Created += this.WatcherOnChanged;
- watcher.Renamed += this.WatcherOnChanged;
-
- this.fileWatchers.Add(watcher);
- }
-
- private CancellationTokenSource? changed;
-
- private async void WatcherOnChanged(object sender, FileSystemEventArgs e)
- {
- this.changed?.Cancel();
- this.changed = new CancellationTokenSource();
-
- try
- {
- // Wait for the change events to finish.
- await Task.Delay(1000, this.changed.Token);
- this.changed = null;
- App.Current.Dispatcher.Invoke(() => this.ReloadRequired?.Invoke(this, e));
- }
- catch (TaskCanceledException)
- {
- // Do nothing.
- }
- }
-
- public void Dispose()
- {
- this.fileWatchers.ForEach(fileWatcher =>
- {
- fileWatcher.EnableRaisingEvents = false;
- fileWatcher.Dispose();
- });
- this.fileWatchers.Clear();
- }
- }
-}
\ No newline at end of file
diff --git a/Morphic.Client/Bar/Data/BarEnums.cs b/Morphic.Client/Bar/Data/BarEnums.cs
deleted file mode 100644
index 7efd16f1..00000000
--- a/Morphic.Client/Bar/Data/BarEnums.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-// BarEnums.cs: Enumerations used by the bar.
-//
-// Copyright 2020 Raising the Floor - International
-//
-// Licensed under the New BSD license. You may not use this file except in
-// compliance with this License.
-//
-// You may obtain a copy of the License at
-// https://github.com/GPII/universal/blob/master/LICENSE.txt
-
-
-namespace Morphic.Client.Bar.Data
-{
- public enum Position
- {
- Absolute = 0,
- Percent = 1,
- Left = 2,
- Top = 3,
- Right = 4,
- Bottom = 5,
- Center = 6,
- Centre = 6,
- Middle = 6
- }
-
- public enum ExpanderRelative
- {
- Both = 0,
- Primary,
- Secondary
- }
-
- public enum BarOverflow
- {
- Resize = 0,
- Wrap,
- Scale,
- Hide,
- Secondary
- }
-
- public enum BarItemSize
- {
- TextOnly = 0,
- Small,
- Medium,
- Large
- }
-}
diff --git a/Morphic.Client/Bar/Data/BarItem.cs b/Morphic.Client/Bar/Data/BarItem.cs
deleted file mode 100644
index 74bdee71..00000000
--- a/Morphic.Client/Bar/Data/BarItem.cs
+++ /dev/null
@@ -1,226 +0,0 @@
-// BarItem.cs: An item on a bar.
-//
-// Copyright 2020 Raising the Floor - International
-//
-// Licensed under the New BSD license. You may not use this file except in
-// compliance with this License.
-//
-// You may obtain a copy of the License at
-// https://github.com/GPII/universal/blob/master/LICENSE.txt
-
-namespace Morphic.Client.Bar.Data
-{
- using System;
- using System.Collections.Generic;
- using System.Reflection;
- using System.Windows.Media;
- using Actions;
- using Microsoft.Extensions.DependencyInjection;
- using Microsoft.Extensions.Logging;
- using Newtonsoft.Json;
- using UI.BarControls;
-
- ///
- /// A bar item.
- ///
- /// For items of kind == "action", configuration.identifier is used to lookup an item from actions.js. The object
- /// from there is merged onto this, just before deserialisation.
- ///
- [JsonObject(MemberSerialization.OptIn)]
- [JsonConverter(typeof(TypedJsonConverter), "widget", "button")]
- public class BarItem
- {
- public BarItem(BarData bar)
- {
- this.Bar = bar;
- }
-
- protected ILogger Logger = App.Current.ServiceProvider.GetRequiredService>();
- private string? text;
- private string? uiName;
-
- ///
- /// The bar that owns this item.
- ///
- public BarData Bar { get; set; }
-
- ///
- /// true if the item is to be displayed on the pull-out bar.
- ///
- [JsonProperty("is_primary")]
- public bool IsPrimary { get; set; }
-
- ///
- /// true if the item should over-flow to the secondary bar, because it doesn't fit.
- ///
- public bool Overflow { get; set; }
-
- ///
- /// true if this item is a built-in item, from the default bar json.
- ///
- public bool IsDefault { get; set; }
-
- ///
- /// Don't over-flow this item to the secondary bar.
- ///
- [JsonProperty("no_overflow")]
- public bool NoOverflow { get; set; }
-
- [JsonProperty("configuration", ObjectCreationHandling = ObjectCreationHandling.Replace)]
- [JsonConverter(typeof(TypedJsonConverter), "kind", "null")]
- public BarAction Action { get; set; } = new NoOpAction();
-
- ///
- /// The text displayed on the item.
- ///
- [JsonProperty("configuration.label")]
- public string Text
- {
- get => this.text ?? this.DefaultText ?? string.Empty;
- set => this.text = value;
- }
-
- ///
- /// The text used by UI automation - this is what narrator reads.
- ///
- [JsonProperty("configuration.uiName")]
- public string UiName
- {
- get
- {
- string name = this.uiName ?? this.Text;
- return string.IsNullOrEmpty(name)
- ? this.ToolTipHeader ?? this.ToolTip ?? string.Empty
- : name;
- }
- set => this.uiName = value;
- }
-
- ///
- /// The text displayed on the item, if Text is not set.
- ///
- [JsonProperty("configuration.defaultLabel")]
- public string? DefaultText { get; set; }
-
- ///
- /// Tooltip header text (default is the this.Text).
- ///
- [JsonProperty("configuration.tooltipHeader")]
- public string? ToolTipHeader { get; set; }
-
- ///
- /// Tooltip smaller text.
- ///
- [JsonProperty("configuration.tooltip")]
- public string? ToolTip { get; set; }
-
- ///
- /// The background colour (setter from json to allow empty strings).
- ///
- [JsonProperty("configuration.color")]
- public string ColorValue
- {
- set
- {
- if (!string.IsNullOrEmpty(value))
- {
- if (ColorConverter.ConvertFromString(value) is Color color)
- {
- this.Color = color;
- }
- }
- }
- get => "";
- }
-
- ///
- /// The background colour.
- ///
- public Color Color
- {
- get => this.Theme.Background ?? Colors.Transparent;
- set
- {
- this.Theme.Background = value;
- this.Theme.InferStateThemes(true);
- }
- }
-
- ///
- /// Don't display this item.
- ///
- [JsonProperty("hidden")]
- public bool Hidden { get; set; }
-
- ///
- /// Theme for the item.
- ///
- [JsonProperty("theme", DefaultValueHandling = DefaultValueHandling.Populate)]
- public BarItemTheme Theme { get; set; } = new BarItemTheme();
-
- ///
- /// Theme for the control buttons.
- ///
- [JsonProperty("controlTheme", DefaultValueHandling = DefaultValueHandling.Populate)]
- public BarItemTheme ControlTheme { get; set; } = new BarItemTheme();
-
- ///
- /// Items are sorted by this.
- ///
- [JsonProperty("priority")]
- public int Priority { get; set; }
-
- [JsonProperty("configuration.size")]
- public BarItemSize Size { get; set; } = BarItemSize.Large;
-
- [JsonProperty("configuration.menu")]
- public Dictionary Menu { get; set; } = new Dictionary();
-
- [JsonProperty("configuration.telemetryCategory")]
- public string? TelemetryCategory { get; set; }
-
- ///
- /// The type of control used. This is specified by using BarControl attribute in a subclass of this.
- ///
- public Type ControlType => this.GetType().GetCustomAttribute()?.Type!;
-
- ///
- /// Called when the bar has loaded.
- ///
- public virtual void Deserialized()
- {
- // Inherit the default theme
- this.Theme.Inherit(this.Bar.DefaultTheme);
- this.Theme.InferStateThemes();
- this.ControlTheme.Inherit(this.Bar.ControlTheme).Inherit(this.Bar.DefaultTheme);
- this.ControlTheme.InferStateThemes();
-
- this.Action.Deserialized(this.Bar);
- }
- }
-
- ///
- /// Image bar item.
- ///
- [JsonTypeName("image")]
- [BarControl(typeof(ImageBarControl))]
- public class BarImage : BarButton
- {
- public BarImage(BarData bar) : base(bar)
- {
- }
- }
-
- ///
- /// Used by a BarItem subclass to identify the control used to display the item.
- ///
- public class BarControlAttribute : Attribute
- {
- public Type Type { get; }
-
- public BarControlAttribute(Type type)
- {
- this.Type = type;
- }
- }
-}
\ No newline at end of file
diff --git a/Morphic.Client/Bar/Data/BarItemTheme.cs b/Morphic.Client/Bar/Data/BarItemTheme.cs
deleted file mode 100644
index 767b6893..00000000
--- a/Morphic.Client/Bar/Data/BarItemTheme.cs
+++ /dev/null
@@ -1,183 +0,0 @@
-// BarItemTheme.cs: Describes the visual appearance of a bar and its items.
-//
-// Copyright 2020 Raising the Floor - International
-//
-// Licensed under the New BSD license. You may not use this file except in
-// compliance with this License.
-//
-// You may obtain a copy of the License at
-// https://github.com/GPII/universal/blob/master/LICENSE.txt
-
-namespace Morphic.Client.Bar.Data
-{
- using System.ComponentModel;
- using System.Linq;
- using System.Reflection;
- using System.Windows.Forms;
- using System.Windows.Media;
- using Newtonsoft.Json;
-
- ///
- /// Theme for a bar item.
- ///
- [JsonObject(MemberSerialization.OptIn)]
- public class BarItemTheme : Theme
- {
- /// The theme for when the mouse is over the item.
- [JsonProperty("hover", ObjectCreationHandling = ObjectCreationHandling.Replace)]
- public Theme Hover { get; set; } = new Theme();
-
- /// The theme for when the item has keyboard focus.
- [JsonProperty("focus", ObjectCreationHandling = ObjectCreationHandling.Replace)]
- public Theme Focus { get; set; } = new Theme();
-
- /// The theme for when the item is being clicked (mouse is down).
- [JsonProperty("active", ObjectCreationHandling = ObjectCreationHandling.Replace)]
- public Theme Active { get; set; } = new Theme();
-
- /// The theme for when the item is checked (toggle buttons).
- [JsonProperty("checked", ObjectCreationHandling = ObjectCreationHandling.Replace)]
- public Theme Checked { get; set; } = new Theme();
-
- public BarItemTheme()
- {
- }
-
- public BarItemTheme(Theme theme)
- {
- this.Apply(theme);
- }
-
- public BarItemTheme Inherit(BarItemTheme theme)
- {
- this.Apply(theme);
- this.Hover.Apply(theme.Hover);
- this.Focus.Apply(theme.Focus);
- this.Active.Apply(theme.Active);
- this.Checked.Apply(theme.Checked);
- return this;
- }
-
- ///
- /// Generate the themes for the different states that are unset, based on the colour.
- ///
- ///
- public void InferStateThemes(bool force = false)
- {
- if (force || this.Hover.Background == null)
- {
- this.Hover.Background = this.LightenColor(this.Background ?? Colors.Transparent, 0.25f);
- }
-
- if (force || this.Active.Background == null)
- {
- this.Active.Background = this.LightenColor(this.Background ?? Colors.Transparent, 0.5f);
- }
-
- if (force || this.Focus.Background == null)
- {
- this.Focus.Background = this.Hover.Background;
- }
- }
-
- private Color LightenColor(Color color, float amount)
- {
- System.Drawing.Color c =
- ControlPaint.Light(System.Drawing.Color.FromArgb(color.A, color.R, color.G, color.B), amount);
- return Color.FromArgb(c.A, c.R, c.G, c.B);
- }
- }
-
- ///
- /// Theme for the bar.
- ///
- [JsonObject(MemberSerialization.OptIn)]
- public class BarTheme : Theme
- {
-
- }
-
- ///
- /// A theme.
- ///
- [JsonObject(MemberSerialization.OptIn)]
- public class Theme : INotifyPropertyChanged
- {
- /// Text colour.
- [JsonProperty("color")]
- public Color? TextColor { get; set; }
-
- [JsonProperty("background")]
- public Color? Background { get; set; }
-
- [JsonProperty("borderColor")]
- public Color? BorderColor { get; set; }
-
- [JsonProperty("focusDotColor")]
- public Color? FocusDotColor { get; set; }
-
- [JsonProperty("borderSize")]
- public double BorderSize { get; set; } = double.NaN;
-
- public static Theme DefaultBar()
- {
- return new Theme()
- {
- Background = Colors.White,
- TextColor = Colors.Black,
- BorderColor = Colors.Black,
- BorderSize = 1
- };
- }
-
- ///
- /// Default item theme.
- ///
- ///
- public static Theme DefaultItem()
- {
- return new Theme()
- {
- Background = ColorConverter.ConvertFromString("#002957") as Color?,
- TextColor = Colors.White
- };
- }
-
- ///
- /// Default control button theme.
- ///
- ///
- public static Theme DefaultControl()
- {
- return new Theme()
- {
- Background = Color.FromRgb(0, 129, 69),
- TextColor = Colors.White
- };
- }
-
- ///
- /// Sets the unset values of this instance using values of another.
- ///
- /// The instance to read values from.
- /// true to set all values, false to set only values in this instance that are null.
- public Theme Apply(Theme source, bool all = false)
- {
- foreach (PropertyInfo property in typeof(Theme).GetProperties().Where(p => p.CanWrite))
- {
- object? origValue = all ? null : property.GetValue(this);
- if (origValue == null || (origValue is double d && double.IsNaN(d)))
- {
- object? newValue = property.GetValue(source);
- property.SetValue(this, newValue);
- }
-
- this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property.Name));
- }
-
- return this;
- }
-
- public event PropertyChangedEventHandler? PropertyChanged;
- }
-}
\ No newline at end of file
diff --git a/Morphic.Client/Bar/Data/BarJson.cs b/Morphic.Client/Bar/Data/BarJson.cs
deleted file mode 100644
index 1678b0bd..00000000
--- a/Morphic.Client/Bar/Data/BarJson.cs
+++ /dev/null
@@ -1,378 +0,0 @@
-// BarJson.cs: Bar deserialisation.
-//
-// Copyright 2020 Raising the Floor - International
-//
-// Licensed under the New BSD license. You may not use this file except in
-// compliance with this License.
-//
-// You may obtain a copy of the License at
-// https://github.com/GPII/universal/blob/master/LICENSE.txt
-
-namespace Morphic.Client.Bar.Data
-{
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Reflection;
- using System.Runtime.Serialization;
- using Microsoft.Extensions.DependencyInjection;
- using Newtonsoft.Json;
- using Newtonsoft.Json.Linq;
- using Newtonsoft.Json.Serialization;
-
- public interface IDeserializable
- {
- public void Deserialized();
- }
-
- public static class BarJson
- {
- ///
- /// Loads some json data.
- ///
- /// The service provider.
- /// The input json.
- /// An existing bar to populate.
- ///
- ///
- public static T Load(IServiceProvider serviceProvider, TextReader reader, T? existingBar = null)
- where T : class, IDeserializable, new()
- {
-
- T? bar = existingBar ?? serviceProvider.GetService() ?? new T();
-
- JsonSerializerSettings settings = new JsonSerializerSettings()
- {
- Context = new StreamingContext(StreamingContextStates.Other, bar),
- Error = (sender, args) =>
- {
- args.ToString();
- }
- };
-
- JsonSerializer jsonSerializer = JsonSerializer.Create(settings);
- BarJsonTextReader barJsonTextReader = new BarJsonTextReader(reader, "win");
-
- jsonSerializer.Populate(barJsonTextReader, bar);
-
-
- bar?.Deserialized();
-
- return bar!;
- }
-
- ///
- /// Customised JSON reader which handles platform specific fields. The platform for which a field is used,
- /// is identified by a '$id' suffix. A field with a platform identifier of the current platform will be
- /// used instead of one without.
- ///
- /// For example:
- ///
- /// "value": "default value",
- /// "value$win": "windows-specific value",
- /// "value$mac": "macOS-specific value
- ///
- ///
- public class BarJsonTextReader : JsonTextReader
- {
- ///
- /// Field paths visited which have the platform identifier.
- ///
- private readonly HashSet overridden = new HashSet();
-
- public BarJsonTextReader(TextReader reader) : base(reader)
- {
- }
-
- public BarJsonTextReader(TextReader reader, string platformId) : this(reader)
- {
- this.PlatformId = platformId;
- }
-
- public string PlatformId { get; } = "win";
-
- public override object? Value
- {
- get
- {
- if (this.TokenType == JsonToken.PropertyName)
- {
- string name = base.Value?.ToString() ?? string.Empty;
- string platformId = string.Empty;
- string path = this.Path;
-
- // Take the platform identifier from the name.
- if (name.Contains('$'))
- {
- string[]? parts = name.Split("$", 2);
- if (parts.Length == 2)
- {
- name = parts[0];
- platformId = parts[1].ToLowerInvariant();
- path = path.Substring(0, path.Length - platformId.Length - 1);
- }
- }
-
- if (platformId == this.PlatformId)
- {
- // It's for this platform - use this field, and mark as over-ridden so it takes
- // precedence over subsequent fields with no platform ID.
- this.overridden.Add(path);
- }
- else if (platformId == string.Empty)
- {
- // No platform ID on this field name - use it only if there hasn't already been a
- // field with a platform ID.
- if (this.overridden.Contains(path))
- {
- // Rename it so it's not used.
- name = "_overridden:" + base.Value;
- }
- }
- else
- {
- // Not for this platform - ignore this field.
- name = "_ignored:" + base.Value;
- }
-
- return name;
- }
- else
- {
- return base.Value;
- }
- }
- }
- }
- }
-
- ///
- /// Used by a class to specify, by name, the type of item it supports.
- ///
- [AttributeUsage(AttributeTargets.Class)]
- public class JsonTypeNameAttribute : Attribute
- {
- public JsonTypeNameAttribute(string name)
- {
- this.Name = name;
- }
-
- public string Name { get; }
- }
-
- ///
- /// Provides support for a polymorphic json object, while also allowing properties to deserialise with values
- /// from a child object.
- ///
- /// The base class identifies the JSON field which specifies the type name via the 2nd parameter of the
- /// JsonConverter attribute.
- ///
- /// The subclass specifies the type name which is supports via the JsonTypeName attribute.
- ///
- public class TypedJsonConverter : JsonConverter
- {
- private readonly string typeFieldName;
- private readonly string defaultValue;
-
- public TypedJsonConverter(string typeFieldName, string defaultValue)
- {
- this.typeFieldName = typeFieldName;
- this.defaultValue = defaultValue;
- }
-
- ///
- /// Creates an instance of the class inheriting baseType which has the JsonTypeName attribute
- /// with the specified name.
- ///
- ///
- /// The base type.
- /// The name of the type.
- ///
- /// A class which inherits baseType.
- private object CreateInstance(JObject jObject, Type baseType, string name, BarData? barData)
- {
- // Find the class which has the JsonTypeName attribute with the given name.
- Type? type = GetJsonType(baseType, name);
-
- if (type == null)
- {
- if (baseType.GetCustomAttributes().Any())
- {
- // The type has already been resolved at the property.
- type = baseType;
- }
- else
- {
- throw new JsonSerializationException(
- $"Unable to get type of {baseType.Name} from '{this.typeFieldName} = ${name}'.");
- }
- }
-
- List