diff --git a/.gitignore b/.gitignore
index 2437536c..93fce949 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,459 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
.vs/
-*.csproj.user
-bin/
-obj/
-Morphic.*Setup/Generated.wxs
-Morphic.Client/appsettings.Local.json
-Morphic.Client/appsettings.json
-Morphic.Client/app.manifest
-Morphic.Client/BuildVersion.txt
-Morphic.Bar
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# Tye
+.tye/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+# NOTE: Packaged apps have publish profiles (.pubxml files) and our WinUI/WinAppSDK dependencies won't be included in our MSIX installer if we don't check them in
+#*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+##
+## Visual studio for Mac
+##
+
+
+# globs
+Makefile.in
+*.userprefs
+*.usertasks
+config.make
+config.status
+aclocal.m4
+install-sh
+autom4te.cache/
+*.tar.gz
+tarballs/
+test-results/
+
+# Mac bundle stuff
+*.dmg
+*.app
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+##
+## Visual Studio Code
+##
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+##
+## Repository-specific files
+##
diff --git a/LICENSE.txt b/LICENSE.txt
index 60f5bcf5..8a775bee 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,10 +1,4 @@
-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/raisingthefloor/morphic-windows/blob/master/LICENSE.txt
+Copyright 2020-2025 Raising the Floor - US, Inc.
The R&D leading to these results received funding from the:
* Rehabilitation Services Administration, US Dept. of Education under
@@ -19,4 +13,16 @@ 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
+
+Licensed under the New BSD License.
+
+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.
diff --git a/Morphic (Package)/Images/LargeTile.scale-100.png b/Morphic (Package)/Images/LargeTile.scale-100.png
new file mode 100644
index 00000000..12af4710
Binary files /dev/null and b/Morphic (Package)/Images/LargeTile.scale-100.png differ
diff --git a/Morphic (Package)/Images/LargeTile.scale-125.png b/Morphic (Package)/Images/LargeTile.scale-125.png
new file mode 100644
index 00000000..fda5644f
Binary files /dev/null and b/Morphic (Package)/Images/LargeTile.scale-125.png differ
diff --git a/Morphic (Package)/Images/LargeTile.scale-150.png b/Morphic (Package)/Images/LargeTile.scale-150.png
new file mode 100644
index 00000000..b27ceb94
Binary files /dev/null and b/Morphic (Package)/Images/LargeTile.scale-150.png differ
diff --git a/Morphic (Package)/Images/LargeTile.scale-200.png b/Morphic (Package)/Images/LargeTile.scale-200.png
new file mode 100644
index 00000000..27ed2d2f
Binary files /dev/null and b/Morphic (Package)/Images/LargeTile.scale-200.png differ
diff --git a/Morphic (Package)/Images/LargeTile.scale-400.png b/Morphic (Package)/Images/LargeTile.scale-400.png
new file mode 100644
index 00000000..c687c747
Binary files /dev/null and b/Morphic (Package)/Images/LargeTile.scale-400.png differ
diff --git a/Morphic (Package)/Images/LockScreenLogo.scale-200.png b/Morphic (Package)/Images/LockScreenLogo.scale-200.png
new file mode 100644
index 00000000..79f38efe
Binary files /dev/null and b/Morphic (Package)/Images/LockScreenLogo.scale-200.png differ
diff --git a/Morphic (Package)/Images/SmallTile.scale-100.png b/Morphic (Package)/Images/SmallTile.scale-100.png
new file mode 100644
index 00000000..2c42253b
Binary files /dev/null and b/Morphic (Package)/Images/SmallTile.scale-100.png differ
diff --git a/Morphic (Package)/Images/SmallTile.scale-125.png b/Morphic (Package)/Images/SmallTile.scale-125.png
new file mode 100644
index 00000000..732c4daa
Binary files /dev/null and b/Morphic (Package)/Images/SmallTile.scale-125.png differ
diff --git a/Morphic (Package)/Images/SmallTile.scale-150.png b/Morphic (Package)/Images/SmallTile.scale-150.png
new file mode 100644
index 00000000..2c2a1325
Binary files /dev/null and b/Morphic (Package)/Images/SmallTile.scale-150.png differ
diff --git a/Morphic (Package)/Images/SmallTile.scale-200.png b/Morphic (Package)/Images/SmallTile.scale-200.png
new file mode 100644
index 00000000..c22722a6
Binary files /dev/null and b/Morphic (Package)/Images/SmallTile.scale-200.png differ
diff --git a/Morphic (Package)/Images/SmallTile.scale-400.png b/Morphic (Package)/Images/SmallTile.scale-400.png
new file mode 100644
index 00000000..13d10003
Binary files /dev/null and b/Morphic (Package)/Images/SmallTile.scale-400.png differ
diff --git a/Morphic (Package)/Images/SplashScreen.scale-100.png b/Morphic (Package)/Images/SplashScreen.scale-100.png
new file mode 100644
index 00000000..1d9e88c0
Binary files /dev/null and b/Morphic (Package)/Images/SplashScreen.scale-100.png differ
diff --git a/Morphic (Package)/Images/SplashScreen.scale-125.png b/Morphic (Package)/Images/SplashScreen.scale-125.png
new file mode 100644
index 00000000..d662b882
Binary files /dev/null and b/Morphic (Package)/Images/SplashScreen.scale-125.png differ
diff --git a/Morphic (Package)/Images/SplashScreen.scale-150.png b/Morphic (Package)/Images/SplashScreen.scale-150.png
new file mode 100644
index 00000000..c3d899b2
Binary files /dev/null and b/Morphic (Package)/Images/SplashScreen.scale-150.png differ
diff --git a/Morphic (Package)/Images/SplashScreen.scale-200.png b/Morphic (Package)/Images/SplashScreen.scale-200.png
new file mode 100644
index 00000000..a6b09e57
Binary files /dev/null and b/Morphic (Package)/Images/SplashScreen.scale-200.png differ
diff --git a/Morphic (Package)/Images/SplashScreen.scale-400.png b/Morphic (Package)/Images/SplashScreen.scale-400.png
new file mode 100644
index 00000000..330fa2fe
Binary files /dev/null and b/Morphic (Package)/Images/SplashScreen.scale-400.png differ
diff --git a/Morphic (Package)/Images/Square150x150Logo.scale-100.png b/Morphic (Package)/Images/Square150x150Logo.scale-100.png
new file mode 100644
index 00000000..6ae7c7ea
Binary files /dev/null and b/Morphic (Package)/Images/Square150x150Logo.scale-100.png differ
diff --git a/Morphic (Package)/Images/Square150x150Logo.scale-125.png b/Morphic (Package)/Images/Square150x150Logo.scale-125.png
new file mode 100644
index 00000000..3d5e9074
Binary files /dev/null and b/Morphic (Package)/Images/Square150x150Logo.scale-125.png differ
diff --git a/Morphic (Package)/Images/Square150x150Logo.scale-150.png b/Morphic (Package)/Images/Square150x150Logo.scale-150.png
new file mode 100644
index 00000000..62fa8339
Binary files /dev/null and b/Morphic (Package)/Images/Square150x150Logo.scale-150.png differ
diff --git a/Morphic (Package)/Images/Square150x150Logo.scale-200.png b/Morphic (Package)/Images/Square150x150Logo.scale-200.png
new file mode 100644
index 00000000..6f998621
Binary files /dev/null and b/Morphic (Package)/Images/Square150x150Logo.scale-200.png differ
diff --git a/Morphic (Package)/Images/Square150x150Logo.scale-400.png b/Morphic (Package)/Images/Square150x150Logo.scale-400.png
new file mode 100644
index 00000000..e6712394
Binary files /dev/null and b/Morphic (Package)/Images/Square150x150Logo.scale-400.png differ
diff --git a/Morphic (Package)/Images/StoreLogo.scale-100.png b/Morphic (Package)/Images/StoreLogo.scale-100.png
new file mode 100644
index 00000000..d106da1a
Binary files /dev/null and b/Morphic (Package)/Images/StoreLogo.scale-100.png differ
diff --git a/Morphic (Package)/Images/StoreLogo.scale-125.png b/Morphic (Package)/Images/StoreLogo.scale-125.png
new file mode 100644
index 00000000..faa312f0
Binary files /dev/null and b/Morphic (Package)/Images/StoreLogo.scale-125.png differ
diff --git a/Morphic (Package)/Images/StoreLogo.scale-150.png b/Morphic (Package)/Images/StoreLogo.scale-150.png
new file mode 100644
index 00000000..9c5df07c
Binary files /dev/null and b/Morphic (Package)/Images/StoreLogo.scale-150.png differ
diff --git a/Morphic (Package)/Images/StoreLogo.scale-200.png b/Morphic (Package)/Images/StoreLogo.scale-200.png
new file mode 100644
index 00000000..26bd8fe0
Binary files /dev/null and b/Morphic (Package)/Images/StoreLogo.scale-200.png differ
diff --git a/Morphic (Package)/Images/StoreLogo.scale-400.png b/Morphic (Package)/Images/StoreLogo.scale-400.png
new file mode 100644
index 00000000..622639c1
Binary files /dev/null and b/Morphic (Package)/Images/StoreLogo.scale-400.png differ
diff --git a/Morphic (Package)/Images/Wide310x150Logo.scale-100.png b/Morphic (Package)/Images/Wide310x150Logo.scale-100.png
new file mode 100644
index 00000000..2fd23a5b
Binary files /dev/null and b/Morphic (Package)/Images/Wide310x150Logo.scale-100.png differ
diff --git a/Morphic (Package)/Images/Wide310x150Logo.scale-125.png b/Morphic (Package)/Images/Wide310x150Logo.scale-125.png
new file mode 100644
index 00000000..ef2dbfbe
Binary files /dev/null and b/Morphic (Package)/Images/Wide310x150Logo.scale-125.png differ
diff --git a/Morphic (Package)/Images/Wide310x150Logo.scale-150.png b/Morphic (Package)/Images/Wide310x150Logo.scale-150.png
new file mode 100644
index 00000000..145fdba0
Binary files /dev/null and b/Morphic (Package)/Images/Wide310x150Logo.scale-150.png differ
diff --git a/Morphic (Package)/Images/Wide310x150Logo.scale-200.png b/Morphic (Package)/Images/Wide310x150Logo.scale-200.png
new file mode 100644
index 00000000..002c377c
Binary files /dev/null and b/Morphic (Package)/Images/Wide310x150Logo.scale-200.png differ
diff --git a/Morphic (Package)/Images/Wide310x150Logo.scale-400.png b/Morphic (Package)/Images/Wide310x150Logo.scale-400.png
new file mode 100644
index 00000000..b24bc4e7
Binary files /dev/null and b/Morphic (Package)/Images/Wide310x150Logo.scale-400.png differ
diff --git a/Morphic (Package)/Images/contrast-black/Square44x44Logo.altform-unplated_targetsize-16.png b/Morphic (Package)/Images/contrast-black/Square44x44Logo.altform-unplated_targetsize-16.png
new file mode 100644
index 00000000..b00844ea
Binary files /dev/null and b/Morphic (Package)/Images/contrast-black/Square44x44Logo.altform-unplated_targetsize-16.png differ
diff --git a/Morphic (Package)/Images/contrast-black/Square44x44Logo.altform-unplated_targetsize-256.png b/Morphic (Package)/Images/contrast-black/Square44x44Logo.altform-unplated_targetsize-256.png
new file mode 100644
index 00000000..b41f06a8
Binary files /dev/null and b/Morphic (Package)/Images/contrast-black/Square44x44Logo.altform-unplated_targetsize-256.png differ
diff --git a/Morphic (Package)/Images/contrast-black/Square44x44Logo.altform-unplated_targetsize-32.png b/Morphic (Package)/Images/contrast-black/Square44x44Logo.altform-unplated_targetsize-32.png
new file mode 100644
index 00000000..82a9f531
Binary files /dev/null and b/Morphic (Package)/Images/contrast-black/Square44x44Logo.altform-unplated_targetsize-32.png differ
diff --git a/Morphic (Package)/Images/contrast-black/Square44x44Logo.altform-unplated_targetsize-48.png b/Morphic (Package)/Images/contrast-black/Square44x44Logo.altform-unplated_targetsize-48.png
new file mode 100644
index 00000000..d74bf9bc
Binary files /dev/null and b/Morphic (Package)/Images/contrast-black/Square44x44Logo.altform-unplated_targetsize-48.png differ
diff --git a/Morphic (Package)/Images/contrast-black/Square44x44Logo.scale-100.png b/Morphic (Package)/Images/contrast-black/Square44x44Logo.scale-100.png
new file mode 100644
index 00000000..aa6a0823
Binary files /dev/null and b/Morphic (Package)/Images/contrast-black/Square44x44Logo.scale-100.png differ
diff --git a/Morphic (Package)/Images/contrast-black/Square44x44Logo.scale-125.png b/Morphic (Package)/Images/contrast-black/Square44x44Logo.scale-125.png
new file mode 100644
index 00000000..a3cf2164
Binary files /dev/null and b/Morphic (Package)/Images/contrast-black/Square44x44Logo.scale-125.png differ
diff --git a/Morphic (Package)/Images/contrast-black/Square44x44Logo.scale-150.png b/Morphic (Package)/Images/contrast-black/Square44x44Logo.scale-150.png
new file mode 100644
index 00000000..4bf212d7
Binary files /dev/null and b/Morphic (Package)/Images/contrast-black/Square44x44Logo.scale-150.png differ
diff --git a/Morphic (Package)/Images/contrast-black/Square44x44Logo.scale-200.png b/Morphic (Package)/Images/contrast-black/Square44x44Logo.scale-200.png
new file mode 100644
index 00000000..cb57e40c
Binary files /dev/null and b/Morphic (Package)/Images/contrast-black/Square44x44Logo.scale-200.png differ
diff --git a/Morphic (Package)/Images/contrast-black/Square44x44Logo.scale-400.png b/Morphic (Package)/Images/contrast-black/Square44x44Logo.scale-400.png
new file mode 100644
index 00000000..ff6fbf41
Binary files /dev/null and b/Morphic (Package)/Images/contrast-black/Square44x44Logo.scale-400.png differ
diff --git a/Morphic (Package)/Images/contrast-black/Square44x44Logo.targetsize-128.png b/Morphic (Package)/Images/contrast-black/Square44x44Logo.targetsize-128.png
new file mode 100644
index 00000000..942c33a2
Binary files /dev/null and b/Morphic (Package)/Images/contrast-black/Square44x44Logo.targetsize-128.png differ
diff --git a/Morphic (Package)/Images/contrast-black/Square44x44Logo.targetsize-16.png b/Morphic (Package)/Images/contrast-black/Square44x44Logo.targetsize-16.png
new file mode 100644
index 00000000..219d811d
Binary files /dev/null and b/Morphic (Package)/Images/contrast-black/Square44x44Logo.targetsize-16.png differ
diff --git a/Morphic (Package)/Images/contrast-black/Square44x44Logo.targetsize-24.png b/Morphic (Package)/Images/contrast-black/Square44x44Logo.targetsize-24.png
new file mode 100644
index 00000000..32ce302e
Binary files /dev/null and b/Morphic (Package)/Images/contrast-black/Square44x44Logo.targetsize-24.png differ
diff --git a/Morphic (Package)/Images/contrast-black/Square44x44Logo.targetsize-256.png b/Morphic (Package)/Images/contrast-black/Square44x44Logo.targetsize-256.png
new file mode 100644
index 00000000..ca6eb74b
Binary files /dev/null and b/Morphic (Package)/Images/contrast-black/Square44x44Logo.targetsize-256.png differ
diff --git a/Morphic (Package)/Images/contrast-black/Square44x44Logo.targetsize-32.png b/Morphic (Package)/Images/contrast-black/Square44x44Logo.targetsize-32.png
new file mode 100644
index 00000000..468cc148
Binary files /dev/null and b/Morphic (Package)/Images/contrast-black/Square44x44Logo.targetsize-32.png differ
diff --git a/Morphic (Package)/Images/contrast-black/Square44x44Logo.targetsize-48.png b/Morphic (Package)/Images/contrast-black/Square44x44Logo.targetsize-48.png
new file mode 100644
index 00000000..0f8cc057
Binary files /dev/null and b/Morphic (Package)/Images/contrast-black/Square44x44Logo.targetsize-48.png differ
diff --git a/Morphic (Package)/Images/contrast-black/Square44x44Logo.targetsize-64.png b/Morphic (Package)/Images/contrast-black/Square44x44Logo.targetsize-64.png
new file mode 100644
index 00000000..e78e13c6
Binary files /dev/null and b/Morphic (Package)/Images/contrast-black/Square44x44Logo.targetsize-64.png differ
diff --git a/Morphic (Package)/Images/contrast-black/Square44x44Logo.targetsize-96.png b/Morphic (Package)/Images/contrast-black/Square44x44Logo.targetsize-96.png
new file mode 100644
index 00000000..d067c5f0
Binary files /dev/null and b/Morphic (Package)/Images/contrast-black/Square44x44Logo.targetsize-96.png differ
diff --git a/Morphic (Package)/Images/contrast-standard/Square44x44Logo.altform-unplated_targetsize-16.png b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.altform-unplated_targetsize-16.png
new file mode 100644
index 00000000..cb3b6b07
Binary files /dev/null and b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.altform-unplated_targetsize-16.png differ
diff --git a/Morphic (Package)/Images/contrast-standard/Square44x44Logo.altform-unplated_targetsize-256.png b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.altform-unplated_targetsize-256.png
new file mode 100644
index 00000000..f6563c0e
Binary files /dev/null and b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.altform-unplated_targetsize-256.png differ
diff --git a/Morphic (Package)/Images/contrast-standard/Square44x44Logo.altform-unplated_targetsize-32.png b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.altform-unplated_targetsize-32.png
new file mode 100644
index 00000000..67f25c50
Binary files /dev/null and b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.altform-unplated_targetsize-32.png differ
diff --git a/Morphic (Package)/Images/contrast-standard/Square44x44Logo.altform-unplated_targetsize-48.png b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.altform-unplated_targetsize-48.png
new file mode 100644
index 00000000..45515853
Binary files /dev/null and b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.altform-unplated_targetsize-48.png differ
diff --git a/Morphic (Package)/Images/contrast-standard/Square44x44Logo.scale-100.png b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.scale-100.png
new file mode 100644
index 00000000..baabfba4
Binary files /dev/null and b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.scale-100.png differ
diff --git a/Morphic (Package)/Images/contrast-standard/Square44x44Logo.scale-125.png b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.scale-125.png
new file mode 100644
index 00000000..1ecb550c
Binary files /dev/null and b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.scale-125.png differ
diff --git a/Morphic (Package)/Images/contrast-standard/Square44x44Logo.scale-150.png b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.scale-150.png
new file mode 100644
index 00000000..d1ccf3a5
Binary files /dev/null and b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.scale-150.png differ
diff --git a/Morphic (Package)/Images/contrast-standard/Square44x44Logo.scale-200.png b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.scale-200.png
new file mode 100644
index 00000000..1039dfc3
Binary files /dev/null and b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.scale-200.png differ
diff --git a/Morphic (Package)/Images/contrast-standard/Square44x44Logo.scale-400.png b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.scale-400.png
new file mode 100644
index 00000000..be0de596
Binary files /dev/null and b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.scale-400.png differ
diff --git a/Morphic (Package)/Images/contrast-standard/Square44x44Logo.targetsize-16.png b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.targetsize-16.png
new file mode 100644
index 00000000..8c66b9f6
Binary files /dev/null and b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.targetsize-16.png differ
diff --git a/Morphic (Package)/Images/contrast-standard/Square44x44Logo.targetsize-24.png b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.targetsize-24.png
new file mode 100644
index 00000000..b1d09a7d
Binary files /dev/null and b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.targetsize-24.png differ
diff --git a/Morphic (Package)/Images/contrast-standard/Square44x44Logo.targetsize-24_altform-unplated.png b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.targetsize-24_altform-unplated.png
new file mode 100644
index 00000000..e0b2adf5
Binary files /dev/null and b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.targetsize-24_altform-unplated.png differ
diff --git a/Morphic (Package)/Images/contrast-standard/Square44x44Logo.targetsize-256.png b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.targetsize-256.png
new file mode 100644
index 00000000..529578b7
Binary files /dev/null and b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.targetsize-256.png differ
diff --git a/Morphic (Package)/Images/contrast-standard/Square44x44Logo.targetsize-32.png b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.targetsize-32.png
new file mode 100644
index 00000000..f3cf5151
Binary files /dev/null and b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.targetsize-32.png differ
diff --git a/Morphic (Package)/Images/contrast-standard/Square44x44Logo.targetsize-48.png b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.targetsize-48.png
new file mode 100644
index 00000000..a512470c
Binary files /dev/null and b/Morphic (Package)/Images/contrast-standard/Square44x44Logo.targetsize-48.png differ
diff --git a/Morphic (Package)/Images/contrast-white/Square44x44Logo.altform-unplated_targetsize-16.png b/Morphic (Package)/Images/contrast-white/Square44x44Logo.altform-unplated_targetsize-16.png
new file mode 100644
index 00000000..d8db7382
Binary files /dev/null and b/Morphic (Package)/Images/contrast-white/Square44x44Logo.altform-unplated_targetsize-16.png differ
diff --git a/Morphic (Package)/Images/contrast-white/Square44x44Logo.altform-unplated_targetsize-256.png b/Morphic (Package)/Images/contrast-white/Square44x44Logo.altform-unplated_targetsize-256.png
new file mode 100644
index 00000000..8bfd7a1b
Binary files /dev/null and b/Morphic (Package)/Images/contrast-white/Square44x44Logo.altform-unplated_targetsize-256.png differ
diff --git a/Morphic (Package)/Images/contrast-white/Square44x44Logo.altform-unplated_targetsize-32.png b/Morphic (Package)/Images/contrast-white/Square44x44Logo.altform-unplated_targetsize-32.png
new file mode 100644
index 00000000..46a0852d
Binary files /dev/null and b/Morphic (Package)/Images/contrast-white/Square44x44Logo.altform-unplated_targetsize-32.png differ
diff --git a/Morphic (Package)/Images/contrast-white/Square44x44Logo.altform-unplated_targetsize-48.png b/Morphic (Package)/Images/contrast-white/Square44x44Logo.altform-unplated_targetsize-48.png
new file mode 100644
index 00000000..2f809041
Binary files /dev/null and b/Morphic (Package)/Images/contrast-white/Square44x44Logo.altform-unplated_targetsize-48.png differ
diff --git a/Morphic (Package)/Images/contrast-white/Square44x44Logo.scale-100.png b/Morphic (Package)/Images/contrast-white/Square44x44Logo.scale-100.png
new file mode 100644
index 00000000..eb23f173
Binary files /dev/null and b/Morphic (Package)/Images/contrast-white/Square44x44Logo.scale-100.png differ
diff --git a/Morphic (Package)/Images/contrast-white/Square44x44Logo.scale-125.png b/Morphic (Package)/Images/contrast-white/Square44x44Logo.scale-125.png
new file mode 100644
index 00000000..2e191478
Binary files /dev/null and b/Morphic (Package)/Images/contrast-white/Square44x44Logo.scale-125.png differ
diff --git a/Morphic (Package)/Images/contrast-white/Square44x44Logo.scale-150.png b/Morphic (Package)/Images/contrast-white/Square44x44Logo.scale-150.png
new file mode 100644
index 00000000..6c287bdf
Binary files /dev/null and b/Morphic (Package)/Images/contrast-white/Square44x44Logo.scale-150.png differ
diff --git a/Morphic (Package)/Images/contrast-white/Square44x44Logo.scale-200.png b/Morphic (Package)/Images/contrast-white/Square44x44Logo.scale-200.png
new file mode 100644
index 00000000..e64ad4d5
Binary files /dev/null and b/Morphic (Package)/Images/contrast-white/Square44x44Logo.scale-200.png differ
diff --git a/Morphic (Package)/Images/contrast-white/Square44x44Logo.scale-400.png b/Morphic (Package)/Images/contrast-white/Square44x44Logo.scale-400.png
new file mode 100644
index 00000000..7e1b025e
Binary files /dev/null and b/Morphic (Package)/Images/contrast-white/Square44x44Logo.scale-400.png differ
diff --git a/Morphic (Package)/Images/contrast-white/Square44x44Logo.targetsize-128.png b/Morphic (Package)/Images/contrast-white/Square44x44Logo.targetsize-128.png
new file mode 100644
index 00000000..83298a2d
Binary files /dev/null and b/Morphic (Package)/Images/contrast-white/Square44x44Logo.targetsize-128.png differ
diff --git a/Morphic (Package)/Images/contrast-white/Square44x44Logo.targetsize-16.png b/Morphic (Package)/Images/contrast-white/Square44x44Logo.targetsize-16.png
new file mode 100644
index 00000000..f4fe2252
Binary files /dev/null and b/Morphic (Package)/Images/contrast-white/Square44x44Logo.targetsize-16.png differ
diff --git a/Morphic (Package)/Images/contrast-white/Square44x44Logo.targetsize-24.png b/Morphic (Package)/Images/contrast-white/Square44x44Logo.targetsize-24.png
new file mode 100644
index 00000000..66a1e79d
Binary files /dev/null and b/Morphic (Package)/Images/contrast-white/Square44x44Logo.targetsize-24.png differ
diff --git a/Morphic (Package)/Images/contrast-white/Square44x44Logo.targetsize-256.png b/Morphic (Package)/Images/contrast-white/Square44x44Logo.targetsize-256.png
new file mode 100644
index 00000000..98b784ef
Binary files /dev/null and b/Morphic (Package)/Images/contrast-white/Square44x44Logo.targetsize-256.png differ
diff --git a/Morphic (Package)/Images/contrast-white/Square44x44Logo.targetsize-32.png b/Morphic (Package)/Images/contrast-white/Square44x44Logo.targetsize-32.png
new file mode 100644
index 00000000..659f2478
Binary files /dev/null and b/Morphic (Package)/Images/contrast-white/Square44x44Logo.targetsize-32.png differ
diff --git a/Morphic (Package)/Images/contrast-white/Square44x44Logo.targetsize-48.png b/Morphic (Package)/Images/contrast-white/Square44x44Logo.targetsize-48.png
new file mode 100644
index 00000000..96a558b7
Binary files /dev/null and b/Morphic (Package)/Images/contrast-white/Square44x44Logo.targetsize-48.png differ
diff --git a/Morphic (Package)/Images/contrast-white/Square44x44Logo.targetsize-64.png b/Morphic (Package)/Images/contrast-white/Square44x44Logo.targetsize-64.png
new file mode 100644
index 00000000..3532b089
Binary files /dev/null and b/Morphic (Package)/Images/contrast-white/Square44x44Logo.targetsize-64.png differ
diff --git a/Morphic (Package)/Images/contrast-white/Square44x44Logo.targetsize-96.png b/Morphic (Package)/Images/contrast-white/Square44x44Logo.targetsize-96.png
new file mode 100644
index 00000000..3e17f453
Binary files /dev/null and b/Morphic (Package)/Images/contrast-white/Square44x44Logo.targetsize-96.png differ
diff --git a/Morphic (Package)/Morphic (Package).wapproj b/Morphic (Package)/Morphic (Package).wapproj
new file mode 100644
index 00000000..264aae28
--- /dev/null
+++ b/Morphic (Package)/Morphic (Package).wapproj
@@ -0,0 +1,153 @@
+
+
+
+ 15.0
+
+
+
+ Debug
+ x86
+
+
+ Release
+ x86
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+ Debug
+ ARM64
+
+
+ Release
+ ARM64
+
+
+
+ $(MSBuildExtensionsPath)\Microsoft\DesktopBridge\
+
+
+
+ 623e4ccf-6c54-4acd-80e7-951e62780a84
+ 10.0.22621.0
+ 10.0.19041.0
+ net8.0-windows$(TargetPlatformVersion);$(AssetTargetFallback)
+ en-US
+ false
+ $(NoWarn);NU1702
+ ..\Morphic\Morphic.csproj
+
+
+
+ Designer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ Properties\PublishProfiles\win-$(Platform).pubxml
+
+
+
+
+
+
+
+ build
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Morphic (Package)/Package.appxmanifest b/Morphic (Package)/Package.appxmanifest
new file mode 100644
index 00000000..17b08b60
--- /dev/null
+++ b/Morphic (Package)/Package.appxmanifest
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+ Morphic
+ Raising the Floor - US Inc
+ Images\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 6646a922..00000000
--- a/Morphic.Client/App.xaml.cs
+++ /dev/null
@@ -1,1375 +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 Morphic.Telemetry;
- using Morphic.Windows.Native.OsVersion;
- using Settings.SettingsHandlers;
- using Settings.SolutionsRegistry;
- using System.Diagnostics;
- 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!;
-
- private MorphicTelemetryClient? _telemetryClient = 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; }
- // for type: control
- public string? feature { get; set; }
- }
- //
- public class TelemetryConfigSection
- {
- public string? siteId { 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; }
- public TelemetryConfigSection? telemetry { 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;
- public string? TelemetrySiteId;
- }
- 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 is 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 is not 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 is not null)
- {
- result.CheckForUpdatesIsEnabled = deserializedJson.features.checkForUpdates.enabled.Value;
- }
-
- // capture the cloud settings transfer "is enabled" setting
- if (deserializedJson.features?.cloudSettingsTransfer?.enabled is not null)
- {
- result.CloudSettingsTransferIsEnabled = deserializedJson.features.cloudSettingsTransfer.enabled.Value;
- }
-
- // capture the reset settings (to standard) "is enabled" setting
- if (deserializedJson.features?.resetSettings?.enabled is not 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 is not 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;
- // for type: control
- var extraItemFeature = extraItem.feature;
-
- // if the item is invalid, log the error and skip this item
- if (extraItemType is 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 ((extraItemType != "control") && ((extraItemLabel is null) || (extraItemTooltipHeader is 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 is 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 is 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;
- }
-
- // if the "control" is missing its feature, log the error and skip this item
- if ((extraItem.type == "control") && (extraItemFeature is null || extraItemFeature == "")) {
- // 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;
- extraMorphicBarItem.feature = extraItemFeature;
- result.ExtraMorphicBarItems.Add(extraMorphicBarItem);
- }
- }
-
- // capture telemetry site id
- result.TelemetrySiteId = deserializedJson.telemetry?.siteId;
-
- return result;
- }
-
- private bool ShouldTelemetryBeDisabled()
- {
- // 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)
- {
- // if the Morphic common config path doesn't exist, there's definitely no file
- return false;
- }
- //
- var disableTelemetryFilePath = Path.Combine(morphicCommonConfigPath, "disable_telemetry.txt");
-
- // if disable_telemetry.txt exists, disable telemetry
- var disableTelemetryFileExists = File.Exists(disableTelemetryFilePath);
- return disableTelemetryFileExists;
- }
-
- ///
- /// 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.AddSingleton();
- services.AddTransient();
- services.AddSingleton(s => BarPresets.Default);
- services.AddSolutionsRegistryServices();
- services.AddSingleton(s => Solutions.FromFile(s, AppPaths.GetAppFile("solutions.json5")));
- }
-
- internal async Task Countly_RecordEventAsync(string Key) {
- if (ConfigurableFeatures.TelemetryIsEnabled == true)
- {
- await Countly.RecordEvent(Key);
-
- _telemetryClient?.EnqueueActionMessage(Key);
- }
- }
-
- internal async Task Countly_RecordEventAsync(string Key, int Count, Segmentation Segmentation)
- {
- if (ConfigurableFeatures.TelemetryIsEnabled == true)
- {
- await Countly.RecordEvent(Key, Count, Segmentation);
-
- _telemetryClient?.EnqueueActionMessage(Key);
- }
- }
-
- private struct TelemetryIdComponents
- {
- public string CompositeId;
- public string? SiteId;
- public string DeviceUuid;
- }
- private TelemetryIdComponents GetOrCreateTelemetryIdComponents()
- {
- // retrieve the telemetry device ID for this device; if it doesn't exist then create a new one
- var telemetryCompositeId = AppOptions.TelemetryDeviceUuid;
- if ((telemetryCompositeId is null) || (telemetryCompositeId == String.Empty) || (telemetryCompositeId.IndexOf("D_") < 0))
- {
- telemetryCompositeId = "D_" + Guid.NewGuid().ToString();
- AppOptions.TelemetryDeviceUuid = telemetryCompositeId;
- }
-
- // if a site id is (or is not) configured, modify the telemetry device uuid accordingly
- // NOTE: we handle cases of site ids changing, site IDs being added post-deployment, and site IDs being removed post-deployment
- var unmodifiedTelemetryDeviceCompositeId = telemetryCompositeId;
- var telemetrySiteId = ConfigurableFeatures.TelemetrySiteId;
- if ((telemetrySiteId is not null) && (telemetrySiteId != String.Empty))
- {
- // NOTE: in the future, consider reporting or throwing an error if the site id required sanitization (i.e. wasn't valid)
- var sanitizedTelemetrySiteId = this.SanitizeSiteId(telemetrySiteId);
- if (sanitizedTelemetrySiteId != "")
- {
- // we have a telemetry site id; prepend it
- telemetryCompositeId = this.PrependSiteIdToTelemetryCompositeId(telemetryCompositeId, sanitizedTelemetrySiteId);
- }
- else
- {
- // the supplied site id isn't valid; strip off the site id; In the future consider logging/reporting an error
- telemetryCompositeId = this.RemoveSiteIdFromTelemetryCompositeId(telemetryCompositeId);
- }
- }
- else
- {
- // no telemetry site id is configured; strip off any site id which might have already been part of our telemetry id
- // TODO: in the future, make sure that the telemetry ID wasn't _just_ the site id (as it cannot be allowed to be empty)
- telemetryCompositeId = this.RemoveSiteIdFromTelemetryCompositeId(telemetryCompositeId);
- }
- // if the telemetry uuid has changed (because of the site id), update our stored telemetry uuid now
- if (telemetryCompositeId != unmodifiedTelemetryDeviceCompositeId)
- {
- AppOptions.TelemetryDeviceUuid = telemetryCompositeId;
- }
-
- // capture the raw device UUID
- var indexOfTelemetryDeviceUuid = telemetryCompositeId.IndexOf("D_") + 2;
- var telemetryDeviceUuid = telemetryCompositeId.Substring(indexOfTelemetryDeviceUuid);
-
- return new TelemetryIdComponents() { CompositeId = telemetryCompositeId, SiteId = telemetrySiteId, DeviceUuid = telemetryDeviceUuid };
- }
-
- private async Task ConfigureCountlyAsync()
- {
- // TODO: Move metrics related things to own class.
-
- // retrieve the telemetry composite ID for this device; if it doesn't exist then create a new one
- var telemetryDeviceCompositeId = this.GetOrCreateTelemetryIdComponents().CompositeId;
-
- IConfigurationSection? section = this.Configuration.GetSection("Countly");
- CountlyConfig cc = new CountlyConfig
- {
- serverUrl = section["ServerUrl"],
- appKey = section["AppKey"],
- appVersion = BuildInfo.Current.InformationalVersion,
- developerProvidedDeviceId = telemetryDeviceCompositeId
- };
-
- await Countly.Instance.Init(cc);
- await Countly.Instance.SessionBegin();
- CountlyBase.IsLoggingEnabled = true;
- }
-
- private string PrependSiteIdToTelemetryCompositeId(string value, string telemetrySiteId)
- {
- var telemetryDeviceUuid = value;
-
- if (telemetryDeviceUuid.StartsWith("S_"))
- {
- // if the telemetry device uuid already starts with a site id, strip it off now
- telemetryDeviceUuid = telemetryDeviceUuid.Remove(0, 2);
- var indexOfForwardSlash = telemetryDeviceUuid.IndexOf('/');
- if (indexOfForwardSlash >= 0)
- {
- // strip the site id off the front
- telemetryDeviceUuid = telemetryDeviceUuid.Substring(indexOfForwardSlash + 1);
- }
- else
- {
- // the site ID was the only contents; return null
- telemetryDeviceUuid = "";
- }
- }
-
- // prepend the site id to the telemetry device uuid
- telemetryDeviceUuid = "S_" + telemetrySiteId + "/" + telemetryDeviceUuid;
- return telemetryDeviceUuid;
- }
-
- private string RemoveSiteIdFromTelemetryCompositeId(string value)
- {
- var telemetryDeviceUuid = value;
-
- if (telemetryDeviceUuid.StartsWith("S_"))
- {
- // if the telemetry device uuid starts with a site id, strip it off now
- telemetryDeviceUuid = telemetryDeviceUuid.Remove(0, 2);
- var indexOfForwardSlash = telemetryDeviceUuid.IndexOf('/');
- if (indexOfForwardSlash >= 0)
- {
- // strip the site id off the front
- telemetryDeviceUuid = telemetryDeviceUuid.Substring(indexOfForwardSlash + 1);
- }
- else
- {
- // the site ID is the only contents
- telemetryDeviceUuid = "";
- }
- }
-
- return telemetryDeviceUuid;
- }
-
- private string SanitizeSiteId(string siteId)
- {
- var siteIdAsCharacters = siteId.ToCharArray();
- var resultAsCharacters = new List();
- foreach (var character in siteIdAsCharacters)
- {
- if ((character >= 'a' && character <= 'z') ||
- (character >= 'A' && character <= 'Z') ||
- (character >= '0' && character <= '9'))
- {
- resultAsCharacters.Add(character);
- }
- else
- {
- // filter out this character
- }
-
- }
-
- return new string(resultAsCharacters.ToArray());
- }
-
- #region Telemetry
-
- private void ConfigureTelemetry()
- {
- // 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 telemetryIds = this.GetOrCreateTelemetryIdComponents();
- var telemetryCompositeId = telemetryIds.CompositeId;
- var telemetrySiteId = telemetryIds.SiteId;
- var telemetryDeviceUuid = telemetryIds.DeviceUuid;
-
- // configure our telemetry uplink
- IConfigurationSection? section = this.Configuration.GetSection("Telemetry");
- var mqttHostname = section["ServerHostname"];
- var mqttClientId = telemetryDeviceUuid;
- var mqttUsername = section["AppName"];
- var mqttAnonymousPassword = section["AppKey"];
-
- var mqttConfig = new MorphicTelemetryClient.WebsocketTelemetryClientConfig()
- {
- Hostname = mqttHostname,
- Port = 443,
- Path = "/ws",
- ClientId = mqttClientId,
- Username = mqttUsername,
- Password = mqttAnonymousPassword,
- UseTls = true
- };
- var telemetryClient = new MorphicTelemetryClient(mqttConfig);
- telemetryClient.SiteId = telemetrySiteId;
- _telemetryClient = telemetryClient;
-
- Task.Run(async () =>
- {
- await telemetryClient.StartSessionAsync();
- });
- }
-
- #endregion Telemetry
-
- 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);
-
- // in case of unhandled exception, attempt a graceful shutdown
- //
- // uncomment the following line (if it's useful) for (and only during) debugging
-// MessageBox.Show($"Morphic ran into a problem:\n\n{e.Exception.Message}\n\nFurther information:\n{e.Exception}", "Morphic", MessageBoxButton.OK, MessageBoxImage.Warning);
- //
- try
- {
- this.BarManager.CloseBar();
- }
- catch { }
- //
- try
- {
- this.Shutdown();
- }
- catch
- {
- // if we were unable to shutdown the application, hard-exit instead
- System.Environment.Exit(1);
- }
-
- // 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();
- }
-
- private static List CompatibleWindowsVersions = new List()
- {
- // NOTE: the first entry in this list represents the "minimum" version of Windows which we support
- WindowsVersion.Win10_v1809,
- WindowsVersion.Win10_v1903,
- WindowsVersion.Win10_v1909,
- WindowsVersion.Win10_v2004,
- WindowsVersion.Win10_v20H2,
- WindowsVersion.Win10_v21H1,
- WindowsVersion.Win10_v21H2,
- WindowsVersion.Win10_vFuture,
- //
- WindowsVersion.Win11_v21H2,
- WindowsVersion.Win11_vFuture
- };
- private static bool IsOsCompatibleWithMorphic()
- {
- var windowsVersion = OsVersion.GetWindowsVersion();
-
- if (windowsVersion is null)
- {
- // not a valid version
- return false;
- }
- else if (App.CompatibleWindowsVersions.Contains(windowsVersion.Value) == true)
- {
- return true;
- }
- else
- {
- // either this is an old verison of Windows or it's one we missed that we do support
- Debug.Assert(false, "Incompatible or unknown version of Windows");
- return false;
- }
- }
-
- protected override async void OnStartup(StartupEventArgs e)
- {
- this.Dispatcher.UnhandledException += this.App_DispatcherUnhandledException;
-
- if (App.IsOsCompatibleWithMorphic() == false)
- {
- MessageBox.Show($"Morphic is not compatible with the current version of Windows.\r\n\r\nPlease upgrade to Windows 10 " + App.CompatibleWindowsVersions[0] + " or newer.");
-
- this.Shutdown();
- return;
- }
-
- this.Configuration = this.GetConfiguration();
- ServiceCollection collection = new ServiceCollection();
- this.ConfigureServices(collection);
- this.ServiceProvider = collection.BuildServiceProvider();
-
- base.OnStartup(e);
- this.Logger = this.ServiceProvider.GetRequiredService>();
-
- // determine if telemetry should be enabled
- var telemetryShouldBeDisabled = this.ShouldTelemetryBeDisabled();
- var telemetryIsEnabled = (telemetryShouldBeDisabled == false);
-
- // 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,
- telemetryIsEnabled: telemetryIsEnabled,
- morphicBarvisibilityAfterLogin: commonConfiguration.MorphicBarVisibilityAfterLogin,
- morphicBarExtraItems: commonConfiguration.ExtraMorphicBarItems,
- telemetrySiteId: commonConfiguration.TelemetrySiteId
- );
-
- this.MorphicSession = this.ServiceProvider.GetRequiredService();
- this.MorphicSession.UserChangedAsync += this.Session_UserChangedAsync;
-
- this.Logger.LogInformation("App Started");
-
- this.morphicMenu = new MorphicMenu();
-
- this.RegisterGlobalHotKeys();
-
- if (ConfigurableFeatures.TelemetryIsEnabled == true)
- {
- await this.ConfigureCountlyAsync();
- this.ConfigureTelemetry();
- }
-
- 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)
- {
- // change the user's high contrast theme to the yellow-on-black high contrast theme (theme #1)
- Registry.SetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes",
- "LastHighContrastTheme", @"%SystemRoot\resources\Ease of Access Themes\hc1.theme",
- RegistryValueKind.ExpandString);
- //
- // For windows 10 1809+
- Registry.SetValue(@"HKEY_CURRENT_USER\Control Panel\Accessibility\HighContrast",
- "High Contrast Scheme", "High Contrast #1");
- }
- }
-
- 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 is not null)
- {
- // get the adapterId and sourceId for this monitor
- var adapterIdAndSourceId = Morphic.Windows.Native.Display.Display.GetAdapterIdAndSourceId(monitorName);
- if (adapterIdAndSourceId is not null)
- {
- // get the current DPI offset
- var currentDisplayDpiOffset = Morphic.Windows.Native.Display.Display.GetCurrentDpiOffsetAndRange(adapterIdAndSourceId.Value.adapterId, adapterIdAndSourceId.Value.sourceId);
- if (currentDisplayDpiOffset is not 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
- var darkModeIsEnabledResult = await Morphic.Client.Bar.Data.Actions.Functions.GetDarkModeStateAsync();
- if (darkModeIsEnabledResult.IsSuccess == true)
- {
- var darkModeIsEnabled = darkModeIsEnabledResult.Value!;
-
- if (darkModeIsEnabled != darkModeEnabledDefault)
- {
- await Morphic.Client.Bar.Data.Actions.Functions.SetDarkModeStateAsync(darkModeEnabledDefault);
- }
- }
- //
- //
- // word simplify
- var isOfficeInstalled = Morphic.Integrations.Office.WordRibbon.IsOfficeInstalled();
- if (isOfficeInstalled == true)
- {
- var isBasicSimplifyRibbonEnabledResult = Morphic.Integrations.Office.WordRibbon.IsBasicSimplifyRibbonEnabled();
- if (isBasicSimplifyRibbonEnabledResult.IsSuccess == true)
- {
- var isBasicSimplifyRibbonEnabled = isBasicSimplifyRibbonEnabledResult.Value!;
- if (isBasicSimplifyRibbonEnabled == true)
- {
- _ = Morphic.Integrations.Office.WordRibbon.DisableBasicSimplifyRibbon();
- }
- }
-
- var isEssentialsSimplifyRibbonEnabledResult = Morphic.Integrations.Office.WordRibbon.IsEssentialsSimplifyRibbonEnabled();
- if (isEssentialsSimplifyRibbonEnabledResult.IsSuccess == true)
- {
- var isEssentialsSimplifyRibbonEnabled = isEssentialsSimplifyRibbonEnabledResult.Value!;
- if (isEssentialsSimplifyRibbonEnabled == true)
- {
- _ = Morphic.Integrations.Office.WordRibbon.DisableEssentialsSimplifyRibbon();
- }
- }
- }
- }
-
- private async Task Session_UserChangedAsync(object? sender, MorphicSession.MorphicSessionSignInOrOutEventArgs e)
- {
- if (sender is MorphicSession morphicSession)
- {
- if (morphicSession.SignedIn)
- {
- var lastCommunityId = AppOptions.Current.LastCommunity;
- var lastMorphicbarId = AppOptions.Current.LastMorphicbarId;
- if (lastCommunityId is not null)
- {
- // if the user previously selected a community bar, show that one now
- // NOTE: the behavior here may be inconsistent with Morphic on macOS. If the previously-selected bar is no longer valid (e.g. the user was removed from the community),
- // then we should select the first bar in their list (or the next one...depending on what the design spec says); we should do this consistently on both Windows and macOS
- await this.BarManager.LoadSessionBarAsync(morphicSession, lastCommunityId, lastMorphicbarId);
- }
- else
- {
- string? newUserSelectedCommunityId = null;
- if (e.SignedInViaLoginForm == true)
- {
- // if the user just signed in and they have not previously selected a community bar on this computer, select their first-available bar in the list (and fall-back to the Basic bar)
- if (morphicSession.Communities.Length > 0) {
- newUserSelectedCommunityId = morphicSession.Communities[0].Id;
- }
- }
-
- if (newUserSelectedCommunityId is not null)
- {
- await this.BarManager.LoadSessionBarAsync(morphicSession, newUserSelectedCommunityId, null);
- }
- 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;
- AppOptions.Current.LastMorphicbarId = 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;
- var numberOfMenuItems = changeMorphicBarMenuItems.Count;
- for (int i = 0; i < numberOfMenuItems; 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 allBarsForCommunity = this.MorphicSession.MorphicBarsByCommunityId[community.Id];
- if (allBarsForCommunity is null)
- {
- // NOTE: this scenario shouldn't happen, but it's a gracefully-degrading failsafe just in case
- continue;
- }
- foreach (var communityBar in allBarsForCommunity)
- {
- var newMenuItem = new MenuItem();
- newMenuItem.Header = communityBar.Name + " (from " + community.Name + ")";
- newMenuItem.Tag = community.Id + "/" + communityBar.Id;
- //
- if (community.Id == AppOptions.Current.LastCommunity)
- {
- var markThisBar = false;
- if (AppOptions.Current.LastMorphicbarId is null && addedCheckmarkByCurrentCommunityBar == false)
- {
- markThisBar = true;
- }
- else if (AppOptions.Current.LastMorphicbarId == communityBar.Id)
- {
- markThisBar = true;
- }
- if (markThisBar == true)
- {
- newMenuItem.IsChecked = true;
- addedCheckmarkByCurrentCommunityBar = true;
- }
- }
- newMenuItem.Click += CustomMorphicBarMenuItem_Click;
- //
- this.morphicMenu.ChangeMorphicBar.Items.Insert(Math.Max(this.morphicMenu.ChangeMorphicBar.Items.Count - 1, 0), newMenuItem);
- }
- }
-
- // if no custom bar was checked, mark the basic 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, morphicbarId) = this.ParseMorphicbarMenuItemTag(senderAsMenuItem);
-
- await this.BarManager.LoadSessionBarAsync(this.MorphicSession, communityId, morphicbarId);
- }
-
- private (string communityId, string? morphicbarId) ParseMorphicbarMenuItemTag(MenuItem menuItem)
- {
- string communityId;
- string? morphicbarId;
-
- string tag = (string)menuItem.Tag;
- if (tag.IndexOf("/") >= 0)
- {
- communityId = tag.Substring(0, tag.IndexOf('/'));
- morphicbarId = tag.Substring(tag.IndexOf('/') + 1);
- }
- else
- {
- communityId = tag;
- morphicbarId = null;
- }
-
- return (communityId, morphicbarId);
- }
-
- private void RegisterGlobalHotKeys()
- {
- EventHandler loginHotKeyPressed = async (sender, e) =>
- {
- // NOTE: if we want the login menu item to apply cloud-saved preferences after login, we should set this flag to true
- var applyPreferencesAfterLogin = ConfigurableFeatures.CloudSettingsTransferIsEnabled;
- var args = new Dictionary() { { "applyPreferencesAfterLogin", applyPreferencesAfterLogin } };
- await this.Dialogs.OpenDialogAsync(args);
- };
- try
- {
- HotkeyManager.Current.AddOrReplace("Login with Morphic", Key.M, ModifierKeys.Control | ModifierKeys.Shift, loginHotKeyPressed);
- }
- catch
- {
- this.Logger.LogError("Could not register hotkey Ctrl+Shift+M for 'Login with Morphic'");
- }
-
- EventHandler showMorphicBarHotKeyPressed = (sender, e) =>
- {
- this.BarManager.ShowBar();
- };
- try
- {
- // TODO: should this hotkey be titled "Show MorphicBar" instead?
- //HotkeyManager.Current.AddOrReplace("Show Morphic", Key.M, ModifierKeys.Control | ModifierKeys.Shift | ModifierKeys.Alt, showMorphicBarHotKeyPressed);
- //
- // NOTE: per request on 10-May-2021, this hotkey has been changed from Ctrl+Shift+Alt+M to Ctrl+Shift+Alt+Windows+M
- // TODO: consider changing this modifier key sequence back to Ctrl+Shift+Alt+M (and providing a dialog for the user to decide what key combo they wish to use)
- HotkeyManager.Current.AddOrReplace("Show Morphic", Key.M, ModifierKeys.Control | ModifierKeys.Shift | ModifierKeys.Alt | ModifierKeys.Windows, showMorphicBarHotKeyPressed);
- }
- catch
- {
- this.Logger.LogError("Could not register hotkey Ctrl+Shift+Alt+M for 'Show Morphic'");
- }
- }
-
- 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 is 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();
- if (ConfigurableFeatures.TelemetryIsEnabled == true)
- {
- try
- {
- await Countly.Instance.SessionEnd();
- }
- catch { }
- //
- try
- {
- if (_telemetryClient is not null)
- {
- _telemetryClient.StopSessionAsync();
- }
- }
- catch { }
- }
-
- 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/calendar.xaml b/Morphic.Client/Assets/bar-icons/calendar.xaml
deleted file mode 100644
index 5e49ca7a..00000000
--- a/Morphic.Client/Assets/bar-icons/calendar.xaml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/comments.xaml b/Morphic.Client/Assets/bar-icons/comments.xaml
deleted file mode 100644
index fb833d57..00000000
--- a/Morphic.Client/Assets/bar-icons/comments.xaml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/envelope-open-text.xaml b/Morphic.Client/Assets/bar-icons/envelope-open-text.xaml
deleted file mode 100644
index 0205cd79..00000000
--- a/Morphic.Client/Assets/bar-icons/envelope-open-text.xaml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/envelope-open.xaml b/Morphic.Client/Assets/bar-icons/envelope-open.xaml
deleted file mode 100644
index 906f9967..00000000
--- a/Morphic.Client/Assets/bar-icons/envelope-open.xaml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/envelope-outline-open.xaml b/Morphic.Client/Assets/bar-icons/envelope-outline-open.xaml
deleted file mode 100644
index afc45deb..00000000
--- a/Morphic.Client/Assets/bar-icons/envelope-outline-open.xaml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/envelope-outline.xaml b/Morphic.Client/Assets/bar-icons/envelope-outline.xaml
deleted file mode 100644
index 68c05832..00000000
--- a/Morphic.Client/Assets/bar-icons/envelope-outline.xaml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/envelope.xaml b/Morphic.Client/Assets/bar-icons/envelope.xaml
deleted file mode 100644
index 7a4e12e4..00000000
--- a/Morphic.Client/Assets/bar-icons/envelope.xaml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/favicon_foxNews.xaml b/Morphic.Client/Assets/bar-icons/favicon_foxNews.xaml
deleted file mode 100644
index a86c78f4..00000000
--- a/Morphic.Client/Assets/bar-icons/favicon_foxNews.xaml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/globe.xaml b/Morphic.Client/Assets/bar-icons/globe.xaml
deleted file mode 100644
index 303ec870..00000000
--- a/Morphic.Client/Assets/bar-icons/globe.xaml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_abcNews.xaml b/Morphic.Client/Assets/bar-icons/logo_abcNews.xaml
deleted file mode 100644
index 956f71bb..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_abcNews.xaml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_alJazeera.jpg b/Morphic.Client/Assets/bar-icons/logo_alJazeera.jpg
deleted file mode 100644
index f9ff023d..00000000
Binary files a/Morphic.Client/Assets/bar-icons/logo_alJazeera.jpg and /dev/null differ
diff --git a/Morphic.Client/Assets/bar-icons/logo_amazon.xaml b/Morphic.Client/Assets/bar-icons/logo_amazon.xaml
deleted file mode 100644
index 1b8d6d11..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_amazon.xaml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_amazonMusic.png b/Morphic.Client/Assets/bar-icons/logo_amazonMusic.png
deleted file mode 100644
index b6f681bf..00000000
Binary files a/Morphic.Client/Assets/bar-icons/logo_amazonMusic.png and /dev/null differ
diff --git a/Morphic.Client/Assets/bar-icons/logo_aolOld.png b/Morphic.Client/Assets/bar-icons/logo_aolOld.png
deleted file mode 100644
index f70bfa85..00000000
Binary files a/Morphic.Client/Assets/bar-icons/logo_aolOld.png and /dev/null differ
diff --git a/Morphic.Client/Assets/bar-icons/logo_bbc.xaml b/Morphic.Client/Assets/bar-icons/logo_bbc.xaml
deleted file mode 100644
index db3f2b98..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_bbc.xaml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_bestBuy.xaml b/Morphic.Client/Assets/bar-icons/logo_bestBuy.xaml
deleted file mode 100644
index 214e35c7..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_bestBuy.xaml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_bloomberg.xaml b/Morphic.Client/Assets/bar-icons/logo_bloomberg.xaml
deleted file mode 100644
index 2d933343..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_bloomberg.xaml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_box.xaml b/Morphic.Client/Assets/bar-icons/logo_box.xaml
deleted file mode 100644
index 0870ee81..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_box.xaml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_calculator.png b/Morphic.Client/Assets/bar-icons/logo_calculator.png
deleted file mode 100644
index b78dd1bd..00000000
Binary files a/Morphic.Client/Assets/bar-icons/logo_calculator.png and /dev/null differ
diff --git a/Morphic.Client/Assets/bar-icons/logo_cbsNews.xaml b/Morphic.Client/Assets/bar-icons/logo_cbsNews.xaml
deleted file mode 100644
index 82af5013..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_cbsNews.xaml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_chrome.png b/Morphic.Client/Assets/bar-icons/logo_chrome.png
deleted file mode 100644
index e8eaa48b..00000000
Binary files a/Morphic.Client/Assets/bar-icons/logo_chrome.png and /dev/null differ
diff --git a/Morphic.Client/Assets/bar-icons/logo_cnbc.xaml b/Morphic.Client/Assets/bar-icons/logo_cnbc.xaml
deleted file mode 100644
index ba3e437b..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_cnbc.xaml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_cnn.xaml b/Morphic.Client/Assets/bar-icons/logo_cnn.xaml
deleted file mode 100644
index fe912cc8..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_cnn.xaml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_craigslist.xaml b/Morphic.Client/Assets/bar-icons/logo_craigslist.xaml
deleted file mode 100644
index 3e1b8132..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_craigslist.xaml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_deezer.xaml b/Morphic.Client/Assets/bar-icons/logo_deezer.xaml
deleted file mode 100644
index 0470a9da..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_deezer.xaml
+++ /dev/null
@@ -1,133 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_disneyPlus.xaml b/Morphic.Client/Assets/bar-icons/logo_disneyPlus.xaml
deleted file mode 100644
index 57a23e0f..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_disneyPlus.xaml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_dropbox.xaml b/Morphic.Client/Assets/bar-icons/logo_dropbox.xaml
deleted file mode 100644
index f26fa7fc..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_dropbox.xaml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_drudgeReport.xaml b/Morphic.Client/Assets/bar-icons/logo_drudgeReport.xaml
deleted file mode 100644
index c63f60d9..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_drudgeReport.xaml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_ebay.png b/Morphic.Client/Assets/bar-icons/logo_ebay.png
deleted file mode 100644
index cfefe385..00000000
Binary files a/Morphic.Client/Assets/bar-icons/logo_ebay.png and /dev/null differ
diff --git a/Morphic.Client/Assets/bar-icons/logo_etsy.xaml b/Morphic.Client/Assets/bar-icons/logo_etsy.xaml
deleted file mode 100644
index db57aaa0..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_etsy.xaml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_facebook.xaml b/Morphic.Client/Assets/bar-icons/logo_facebook.xaml
deleted file mode 100644
index 47046f14..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_facebook.xaml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_firefox.png b/Morphic.Client/Assets/bar-icons/logo_firefox.png
deleted file mode 100644
index 98bc7e49..00000000
Binary files a/Morphic.Client/Assets/bar-icons/logo_firefox.png and /dev/null differ
diff --git a/Morphic.Client/Assets/bar-icons/logo_forbes.png b/Morphic.Client/Assets/bar-icons/logo_forbes.png
deleted file mode 100644
index 3717ad94..00000000
Binary files a/Morphic.Client/Assets/bar-icons/logo_forbes.png and /dev/null differ
diff --git a/Morphic.Client/Assets/bar-icons/logo_foxNews.xaml b/Morphic.Client/Assets/bar-icons/logo_foxNews.xaml
deleted file mode 100644
index 24a0819e..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_foxNews.xaml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_gmail.png b/Morphic.Client/Assets/bar-icons/logo_gmail.png
deleted file mode 100644
index 7d150332..00000000
Binary files a/Morphic.Client/Assets/bar-icons/logo_gmail.png and /dev/null differ
diff --git a/Morphic.Client/Assets/bar-icons/logo_googleDrive.png b/Morphic.Client/Assets/bar-icons/logo_googleDrive.png
deleted file mode 100644
index b2511a63..00000000
Binary files a/Morphic.Client/Assets/bar-icons/logo_googleDrive.png and /dev/null differ
diff --git a/Morphic.Client/Assets/bar-icons/logo_googleNews.png b/Morphic.Client/Assets/bar-icons/logo_googleNews.png
deleted file mode 100644
index ca2e41c6..00000000
Binary files a/Morphic.Client/Assets/bar-icons/logo_googleNews.png and /dev/null differ
diff --git a/Morphic.Client/Assets/bar-icons/logo_huffpost.xaml b/Morphic.Client/Assets/bar-icons/logo_huffpost.xaml
deleted file mode 100644
index 139122e5..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_huffpost.xaml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_hulu.xaml b/Morphic.Client/Assets/bar-icons/logo_hulu.xaml
deleted file mode 100644
index f08dd283..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_hulu.xaml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_icloud.xaml b/Morphic.Client/Assets/bar-icons/logo_icloud.xaml
deleted file mode 100644
index 784c1c21..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_icloud.xaml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_iheartRadio.xaml b/Morphic.Client/Assets/bar-icons/logo_iheartRadio.xaml
deleted file mode 100644
index 199b5b24..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_iheartRadio.xaml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_imgur.xaml b/Morphic.Client/Assets/bar-icons/logo_imgur.xaml
deleted file mode 100644
index 3e2d6136..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_imgur.xaml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_instagram.png b/Morphic.Client/Assets/bar-icons/logo_instagram.png
deleted file mode 100644
index 1cb1819a..00000000
Binary files a/Morphic.Client/Assets/bar-icons/logo_instagram.png and /dev/null differ
diff --git a/Morphic.Client/Assets/bar-icons/logo_itunes.png b/Morphic.Client/Assets/bar-icons/logo_itunes.png
deleted file mode 100644
index db8465ea..00000000
Binary files a/Morphic.Client/Assets/bar-icons/logo_itunes.png and /dev/null differ
diff --git a/Morphic.Client/Assets/bar-icons/logo_kohls.xaml b/Morphic.Client/Assets/bar-icons/logo_kohls.xaml
deleted file mode 100644
index ead8cf68..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_kohls.xaml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_laTimes.xaml b/Morphic.Client/Assets/bar-icons/logo_laTimes.xaml
deleted file mode 100644
index 4e396410..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_laTimes.xaml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_linkedIn.xaml b/Morphic.Client/Assets/bar-icons/logo_linkedIn.xaml
deleted file mode 100644
index b09b994e..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_linkedIn.xaml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_macys.xaml b/Morphic.Client/Assets/bar-icons/logo_macys.xaml
deleted file mode 100644
index b398d46f..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_macys.xaml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_mail.jpg b/Morphic.Client/Assets/bar-icons/logo_mail.jpg
deleted file mode 100644
index d71e59fa..00000000
Binary files a/Morphic.Client/Assets/bar-icons/logo_mail.jpg and /dev/null differ
diff --git a/Morphic.Client/Assets/bar-icons/logo_msaccess.xaml b/Morphic.Client/Assets/bar-icons/logo_msaccess.xaml
deleted file mode 100644
index 694f7851..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_msaccess.xaml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_msedge.png b/Morphic.Client/Assets/bar-icons/logo_msedge.png
deleted file mode 100644
index 18941eb8..00000000
Binary files a/Morphic.Client/Assets/bar-icons/logo_msedge.png and /dev/null differ
diff --git a/Morphic.Client/Assets/bar-icons/logo_msexcel.xaml b/Morphic.Client/Assets/bar-icons/logo_msexcel.xaml
deleted file mode 100644
index 66610e57..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_msexcel.xaml
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_msonenote.xaml b/Morphic.Client/Assets/bar-icons/logo_msonenote.xaml
deleted file mode 100644
index 591c22fc..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_msonenote.xaml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_msoutlook.xaml b/Morphic.Client/Assets/bar-icons/logo_msoutlook.xaml
deleted file mode 100644
index be498e3e..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_msoutlook.xaml
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_mspowerpoint.xaml b/Morphic.Client/Assets/bar-icons/logo_mspowerpoint.xaml
deleted file mode 100644
index 2e98457e..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_mspowerpoint.xaml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_msquickassist.png b/Morphic.Client/Assets/bar-icons/logo_msquickassist.png
deleted file mode 100644
index 2b14bae7..00000000
Binary files a/Morphic.Client/Assets/bar-icons/logo_msquickassist.png and /dev/null differ
diff --git a/Morphic.Client/Assets/bar-icons/logo_msteams.xaml b/Morphic.Client/Assets/bar-icons/logo_msteams.xaml
deleted file mode 100644
index fd555103..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_msteams.xaml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_msword.xaml b/Morphic.Client/Assets/bar-icons/logo_msword.xaml
deleted file mode 100644
index 081b57bb..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_msword.xaml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_nbcNews.xaml b/Morphic.Client/Assets/bar-icons/logo_nbcNews.xaml
deleted file mode 100644
index 61659f9c..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_nbcNews.xaml
+++ /dev/null
@@ -1,101 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_netflix.png b/Morphic.Client/Assets/bar-icons/logo_netflix.png
deleted file mode 100644
index 51bea609..00000000
Binary files a/Morphic.Client/Assets/bar-icons/logo_netflix.png and /dev/null differ
diff --git a/Morphic.Client/Assets/bar-icons/logo_newYorkTimes.xaml b/Morphic.Client/Assets/bar-icons/logo_newYorkTimes.xaml
deleted file mode 100644
index defb8398..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_newYorkTimes.xaml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_nextdoor.xaml b/Morphic.Client/Assets/bar-icons/logo_nextdoor.xaml
deleted file mode 100644
index 640319d2..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_nextdoor.xaml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_npr.xaml b/Morphic.Client/Assets/bar-icons/logo_npr.xaml
deleted file mode 100644
index 54b7ea63..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_npr.xaml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_onedrive.xaml b/Morphic.Client/Assets/bar-icons/logo_onedrive.xaml
deleted file mode 100644
index c1b396ec..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_onedrive.xaml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_opera.png b/Morphic.Client/Assets/bar-icons/logo_opera.png
deleted file mode 100644
index eb545cf5..00000000
Binary files a/Morphic.Client/Assets/bar-icons/logo_opera.png and /dev/null differ
diff --git a/Morphic.Client/Assets/bar-icons/logo_outlook.xaml b/Morphic.Client/Assets/bar-icons/logo_outlook.xaml
deleted file mode 100644
index ed1945c6..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_outlook.xaml
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_pandora.jpg b/Morphic.Client/Assets/bar-icons/logo_pandora.jpg
deleted file mode 100644
index 6dfd2418..00000000
Binary files a/Morphic.Client/Assets/bar-icons/logo_pandora.jpg and /dev/null differ
diff --git a/Morphic.Client/Assets/bar-icons/logo_pinterest.xaml b/Morphic.Client/Assets/bar-icons/logo_pinterest.xaml
deleted file mode 100644
index 96262551..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_pinterest.xaml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_reddit.xaml b/Morphic.Client/Assets/bar-icons/logo_reddit.xaml
deleted file mode 100644
index 1ee76552..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_reddit.xaml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_reuters.xaml b/Morphic.Client/Assets/bar-icons/logo_reuters.xaml
deleted file mode 100644
index fa55edbf..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_reuters.xaml
+++ /dev/null
@@ -1,82 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_skype.xaml b/Morphic.Client/Assets/bar-icons/logo_skype.xaml
deleted file mode 100644
index 68b69f98..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_skype.xaml
+++ /dev/null
@@ -1,65 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_soundcloud.xaml b/Morphic.Client/Assets/bar-icons/logo_soundcloud.xaml
deleted file mode 100644
index d84f3bb6..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_soundcloud.xaml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_spotify.xaml b/Morphic.Client/Assets/bar-icons/logo_spotify.xaml
deleted file mode 100644
index 95f71999..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_spotify.xaml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_target.xaml b/Morphic.Client/Assets/bar-icons/logo_target.xaml
deleted file mode 100644
index f39a10b6..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_target.xaml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_theGuardian.xaml b/Morphic.Client/Assets/bar-icons/logo_theGuardian.xaml
deleted file mode 100644
index 1971ed5f..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_theGuardian.xaml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_theHill.jpeg b/Morphic.Client/Assets/bar-icons/logo_theHill.jpeg
deleted file mode 100644
index 387e910d..00000000
Binary files a/Morphic.Client/Assets/bar-icons/logo_theHill.jpeg and /dev/null differ
diff --git a/Morphic.Client/Assets/bar-icons/logo_tidal.png b/Morphic.Client/Assets/bar-icons/logo_tidal.png
deleted file mode 100644
index 00f57da2..00000000
Binary files a/Morphic.Client/Assets/bar-icons/logo_tidal.png and /dev/null differ
diff --git a/Morphic.Client/Assets/bar-icons/logo_tumblr.xaml b/Morphic.Client/Assets/bar-icons/logo_tumblr.xaml
deleted file mode 100644
index de33575c..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_tumblr.xaml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_twitter.xaml b/Morphic.Client/Assets/bar-icons/logo_twitter.xaml
deleted file mode 100644
index 9cf25d59..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_twitter.xaml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_usaToday.xaml b/Morphic.Client/Assets/bar-icons/logo_usaToday.xaml
deleted file mode 100644
index 71c6b71d..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_usaToday.xaml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_vimeo.xaml b/Morphic.Client/Assets/bar-icons/logo_vimeo.xaml
deleted file mode 100644
index 4d6aebe0..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_vimeo.xaml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_walmart.xaml b/Morphic.Client/Assets/bar-icons/logo_walmart.xaml
deleted file mode 100644
index 21cdfb00..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_walmart.xaml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_washingtonPost.xaml b/Morphic.Client/Assets/bar-icons/logo_washingtonPost.xaml
deleted file mode 100644
index 5c4372bc..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_washingtonPost.xaml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_wayfair.png b/Morphic.Client/Assets/bar-icons/logo_wayfair.png
deleted file mode 100644
index 2cc568bf..00000000
Binary files a/Morphic.Client/Assets/bar-icons/logo_wayfair.png and /dev/null differ
diff --git a/Morphic.Client/Assets/bar-icons/logo_wsj.xaml b/Morphic.Client/Assets/bar-icons/logo_wsj.xaml
deleted file mode 100644
index 3d818fcd..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_wsj.xaml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_yahoo.xaml b/Morphic.Client/Assets/bar-icons/logo_yahoo.xaml
deleted file mode 100644
index 7c5b406a..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_yahoo.xaml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_yahoomail.xaml b/Morphic.Client/Assets/bar-icons/logo_yahoomail.xaml
deleted file mode 100644
index 13972f76..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_yahoomail.xaml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_youtube.xaml b/Morphic.Client/Assets/bar-icons/logo_youtube.xaml
deleted file mode 100644
index 9aca5e05..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_youtube.xaml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/logo_youtubeMusic.xaml b/Morphic.Client/Assets/bar-icons/logo_youtubeMusic.xaml
deleted file mode 100644
index c3e689b7..00000000
--- a/Morphic.Client/Assets/bar-icons/logo_youtubeMusic.xaml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/morphic-logo.xaml b/Morphic.Client/Assets/bar-icons/morphic-logo.xaml
deleted file mode 100644
index d5ab7628..00000000
--- a/Morphic.Client/Assets/bar-icons/morphic-logo.xaml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/newspaper.xaml b/Morphic.Client/Assets/bar-icons/newspaper.xaml
deleted file mode 100644
index fb530352..00000000
--- a/Morphic.Client/Assets/bar-icons/newspaper.xaml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/Morphic.Client/Assets/bar-icons/window-maximize.xaml b/Morphic.Client/Assets/bar-icons/window-maximize.xaml
deleted file mode 100644
index 9b48ed8b..00000000
--- a/Morphic.Client/Assets/bar-icons/window-maximize.xaml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
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 542732f7..00000000
--- a/Morphic.Client/Backups.cs
+++ /dev/null
@@ -1,95 +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 is null)
- {
- preferences = new Preferences();
- // NOTE: we are not returning any kind of error if the preferences were not captured
- _ = await this.morphicSession.Solutions.CapturePreferencesAsync(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 10af6b04..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 is not null)
- {
- System.Windows.Rect rc = window.GetRect();
- rc.Inflate(10, 10);
- if (rc.Contains(cursor.Value))
- {
- isOver = true;
- if (this.mouseTimer is 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 32be75b8..00000000
--- a/Morphic.Client/Bar/BarImages.cs
+++ /dev/null
@@ -1,337 +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;
- using System.Windows.Controls;
- using System.Windows.Media;
- using System.Windows.Media.Imaging;
- using System.Windows.Resources;
- using System.Xml;
- using Config;
-
- 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)
- {
- var translatedName = BarImages.TranslateImageUrlToFileName(name);
- if (translatedName is not null)
- {
- name = translatedName;
- }
-
- 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 = { "", ".xaml", ".png", ".ico", ".jpg", ".jpeg", ".gif" };
-
- string? foundFile = extensions.Select(extension => assetFile + extension)
- .FirstOrDefault(File.Exists);
-
- return foundFile;
- }
-
- // NOTE: the image_url values we get back from the v1 API do not always represent the filename, so we need to map them here
- // in the (very-near-term) future, we must standardize on URLs or another form via the API; manual mapping is not sustainable
- public static string? TranslateImageUrlToFileName(string? imageUrl) {
- switch (imageUrl) {
- case "abcnews":
- return "logo_abcNews";
- case "aljazeera":
- return "logo_alJazeera";
- case "amazon":
- return "logo_amazon";
- case "amazonmusic":
- return "logo_amazonMusic";
- case "aolold":
- return "logo_aolOld";
- case "bbc":
- return "logo_bbc";
- case "bestbuy":
- return "logo_bestBuy";
- case "bloomberg":
- return "logo_bloomberg";
- case "box":
- return "logo_box";
- case "calculator":
- return "logo_calculator";
- case "calendar$calendar":
- return "calendar";
- case "cbsnews":
- return "logo_cbsNews";
- case "chrome":
- return "logo_chrome";
- case "cnbc":
- return "logo_cnbc";
- case "cnn":
- return "logo_cnn";
- case "craigslist":
- return "logo_craigslist";
- case "deezer":
- return "logo_deezer";
- case "disneyplus":
- return "logo_disneyPlus";
- case "dropbox":
- return "logo_dropbox";
- case "drudgereport":
- return "logo_drudgeReport";
- case "ebay":
- return "logo_ebay";
- case "etsy":
- return "logo_etsy";
- case "email$envelope":
- return "envelope";
- case "email$envelopeopen":
- return "envelope-open";
- case "email$envelopeopentext":
- return "envelope-open-text";
- case "email$envelopeoutline":
- return "envelope-outline";
- case "email$envelopeoutlineopen":
- return "envelope-outline-open";
- case "facebook":
- return "logo_facebook";
- case "faviconfoxnews":
- return "favicon_foxNews";
- case "firefox":
- return "logo_firefox";
- case "forbes":
- return "logo_forbes";
- case "foxnews":
- return "logo_foxNews";
- case "gmail":
- return "logo_gmail";
- case "googledrive":
- return "logo_googleDrive";
- case "googlenews":
- return "logo_googleNews";
- case "huffpost":
- return "logo_huffpost";
- case "hulu":
- return "logo_hulu";
- case "icloud":
- return "logo_icloud";
- case "news$newspaper":
- return "newspaper";
- case "iheartradio":
- return "logo_iheartRadio";
- case "imgur":
- return "logo_imgur";
- case "instagram":
- return "logo_instagram";
- case "itunes":
- return "logo_itunes";
- case "kohls":
- return "logo_kohls";
- case "latimes":
- return "logo_laTimes";
- case "linkedin":
- return "logo_linkedIn";
- case "macys":
- return "logo_macys";
- case "mail":
- return "logo_mail";
- case "msaccess":
- return "logo_msaccess";
- case "msedge":
- return "logo_msedge";
- case "msexcel":
- return "logo_msexcel";
- case "msonenote":
- return "logo_msonenote";
- case "msoutlook":
- return "logo_msoutlook";
- case "mspowerpoint":
- return "logo_mspowerpoint";
- case "msquickassist":
- return "logo_msquickassist";
- case "msteams":
- return "logo_msteams";
- case "msword":
- return "logo_msword";
- case "nbcnews":
- return "logo_nbcNews";
- case "netflix":
- return "logo_netflix";
- case "nextdoor":
- return "logo_nextdoor";
- case "newyorktimes":
- return "logo_newYorkTimes";
- case "npr":
- return "logo_npr";
- case "onedrive":
- return "logo_onedrive";
- case "opera":
- return "logo_opera";
- case "outlook":
- return "logo_outlook";
- case "pandora":
- return "logo_pandora";
- case "pinterest":
- return "logo_pinterest";
- case "reddit":
- return "logo_reddit";
- case "reuters":
- return "logo_reuters";
- case "skype":
- return "logo_skype";
- case "spotify":
- return "logo_spotify";
- case "soundcloud":
- return "logo_soundcloud";
- case "target":
- return "logo_target";
- case "theguardian":
- return "logo_theGuardian";
- case "thehill":
- return "logo_theHill";
- case "tidal":
- return "logo_tidal";
- case "tumblr":
- return "logo_tumblr";
- case "twitter":
- return "logo_twitter";
- case "usatoday":
- return "logo_usaToday";
- case "vimeo":
- return "logo_vimeo";
- case "walmart":
- return "logo_walmart";
- case "washingtonpost":
- return "logo_washingtonPost";
- case "wayfair":
- return "logo_wayfair";
- case "windowmaximize":
- return "window-maximize";
- case "wsj":
- return "logo_wsj";
- case "yahoo":
- return "logo_yahoo";
- case "yahoomail":
- return "logo_yahoomail";
- case "youtube":
- return "logo_youtube";
- case "youtubemusic":
- return "logo_youtubeMusic";
- case null:
- default:
- return imageUrl;
- }
- }
-
-
- ///
- /// 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 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)
- {
- return null;
- }
- }
-
- if ((imagePath.Contains('/') == false) && (imagePath.Contains('\\') == false))
- {
- imagePath = GetBarIconFile(imagePath) ?? imagePath;
- }
-
- result = TryBitmap();
-
- 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 c7c12e87..00000000
--- a/Morphic.Client/Bar/BarManager.cs
+++ /dev/null
@@ -1,416 +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 is not 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 is not 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 is not 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;
- AppOptions.Current.LastMorphicbarId = 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 is not 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 is not null)
- {
- this.CloseBar();
- }
-
- this.BarIsLoaded = true;
-
- if (bar is not null)
- {
- // if any of the items are application actions and the application isn't available, remove the button from the items collection
- var index = 0;
- while (index < bar.AllItems.Count)
- {
- var actionAsApplicationAction = bar.AllItems[index].Action as Data.Actions.ApplicationAction;
- if (actionAsApplicationAction is not null)
- {
- if (actionAsApplicationAction.IsAvailable == false)
- {
- bar.AllItems.RemoveAt(index);
- continue;
- }
- }
-
- index += 1;
- }
-
- 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, string? morphicbarId)
- {
- if (this.firstBar && AppOptions.Current.Launch.BarFile is not 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 is not null)
- {
- community = session.Communities.FirstOrDefault(c => c.Id == communityId);
- }
-
- //if (community is 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 is not null)
- // {
- // userBar = await bars[community.Id];
- // }
- //}
- //}
-
- if (community is not null)
- {
- var legacyBars = await session.GetBarsAsync(community.Id);
- foreach (var legacyBar in legacyBars) {
- var useThisBar = false;
- if (morphicbarId is null)
- {
- // if the user selected this community id instead of a specific morphicbar (Morphic v1.0-v1.2), then use the first bar
- useThisBar = true;
- }
- else if (legacyBar.Id == morphicbarId)
- {
- // if the user previously selected this specific morphicbar, use it
- useThisBar = true;
- }
-
- if (useThisBar == true)
- {
- // OBSERVATION: not sure why the "??=" (userBar is null) check is done here; this logic seems brittle
- if (userBar is null)
- {
- userBar = legacyBar;
- break;
- }
- }
- }
- // NOTE: if the morphicbar was not found, we do not set it to null (to remain consistent with previous code logic)
-
- // added to protect against not finding the specific community bar
- if (userBar is not null)
- {
- 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 is not null)
- {
- barData.CommunityId = community.Id;
- }
-
- AppOptions.Current.LastCommunity = community?.Id;
- AppOptions.Current.LastMorphicbarId = userBar.Id;
- }
- else
- {
- // if the community or the specific community bar could not be found, show the Basic MorphicBar instead
- this.LoadBasicMorphicBar();
-
- AppOptions.Current.LastCommunity = null;
- AppOptions.Current.LastMorphicbarId = null;
- }
- }
- else
- {
- // if the community could not be found, show the Basic MorphicBar instead
- this.LoadBasicMorphicBar();
-
- AppOptions.Current.LastCommunity = null;
- AppOptions.Current.LastMorphicbarId = null;
- }
- }
-
- ///
- /// 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 6014277c..00000000
--- a/Morphic.Client/Bar/Data/Actions/ApplicationAction.cs
+++ /dev/null
@@ -1,743 +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 is not null)
- {
- return Imaging.CreateBitmapSourceFromHIcon(
- System.Drawing.Icon.ExtractAssociatedIcon(this.AppPath).Handle,
- Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
- }
- else
- {
- return null;
- }
- }
- }
-
- ///
- /// Start a default application. This value will be mapped locally
- ///
- [JsonProperty("default")]
- public string? DefaultAppName { get; 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;
-
- internal struct MorphicExecutablePathInfo
- {
- public bool IsAppx;
- public string Path;
- }
- //
- // NOTE: we should consider returning error details (e.g. "executable not found", "unknown exeId", "win32 error") from this function
- private static MorphicResult ConvertExeIdToExecutablePath(string exeId)
- {
- bool isAppX = false;
- string? appPath = null;
-
- switch (exeId)
- {
- case "calculator":
- {
- // option #1: calc.exe in system folder
- //appPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "calc.exe");
- //
- // option #2: find calc.exe in paths
- appPath = ApplicationAction.SearchPathEnv("calc.exe");
- }
- break;
- case "firefox":
- {
- appPath = ApplicationAction.SearchAppPaths("firefox.exe");
- }
- break;
- case "googleChrome":
- {
- appPath = ApplicationAction.SearchAppPaths("chrome.exe");
- }
- break;
- case "microsoftAccess":
- {
- appPath = ApplicationAction.SearchAppPaths("MSACCESS.EXE");
- }
- break;
- case "microsoftExcel":
- {
- appPath = ApplicationAction.SearchAppPaths("excel.exe");
- }
- break;
- case "microsoftEdge":
- {
- appPath = ApplicationAction.SearchAppPaths("msedge.exe");
- }
- break;
- case "microsoftOneNote":
- {
- appPath = ApplicationAction.SearchAppPaths("OneNote.exe");
- }
- break;
- case "microsoftOutlook":
- {
- appPath = ApplicationAction.SearchAppPaths("OUTLOOK.EXE");
- }
- break;
- case "microsoftPowerPoint":
- {
- appPath = ApplicationAction.SearchAppPaths("powerpnt.exe");
- }
- break;
- case "microsoftQuickAssist":
- {
- // option #1: exactly as written in Quick Assist shortcut (as of 18-Apr-2021): %WINDIR%\system32\quickassist.exe
- //var appPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), @"system32\quickassist.exe");
- //
- // option #2: quickassist.exe in system folder
- appPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "quickassist.exe");
- //
- // option #3: ms url shortcut (NOTE: we have intentionally not used this in case Quick Assist is removed from the system...as the URL does not give us a way to detect that scenario via "file does not exist" checks)
- //var appPath = "ms-quick-assist:";
- }
- break;
- case "microsoftSkype":
- {
- appPath = ApplicationAction.SearchAppPaths("Skype.exe");
- }
- break;
- case "microsoftTeams":
- {
- // NOTE: this is a very odd path, and it's not in the "AppPaths"; if we can find another way to determine the proper launch path or launch programatically, we should consider another method
- var userAppDataLocalPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
- var fixedAppPath = Path.Combine(new string[] { userAppDataLocalPath, "Microsoft", "Teams", "current", "Teams.exe" });
-
- // NOTE: we could also probably launch Teams via the "ms-teams:" URI, but that wouldn't necessarily help us detect if it's installed
-
- // NOTE: in the future, it would be better to launch Update.exe and have it then start Teams (as Microsoft does for their shortcut) -- but we'd probably want to
- // detect the Teams.exe file itself to detect if it was installed
- //var fixedAppPath = Path.Combine(new string[] { userAppDataLocalPath, "Microsoft", "Teams", "Update.exe" });
- //var params = new string[] { "--processStart", "Teams.exe" };
-
- // NOTE: in Windows 11 (and perhaps in recent releases of Windows 10), Microsoft Teams is installed as an Appx package with the name:
- // MicrosoftTeams_8wekyb3d8bbwe
- // ...so we might refer to this as appx:MicrosoftTeams_8wekyb3d8bbwe!MicrosoftTeams
- // NOTE: this path, including "!MicrosoftTeams" at the end, is the Application User Model ID (AUMID) of Microsoft Teams
-
- if (File.Exists(fixedAppPath) == true)
- {
- // if the file exists, set appPath to the fixed path
- appPath = fixedAppPath;
- }
- else
- {
- // if Teams was not installed as an EXE, check to see if it's installed as an APPX package
- var packageFamilyName = "MicrosoftTeams_8wekyb3d8bbwe";
- var isPackageInstalledResult = Appx.IsPackageInstalled(packageFamilyName);
- if (isPackageInstalledResult.IsError == true) {
- return MorphicResult.ErrorResult();
- }
- var isPackageInstalled = isPackageInstalledResult.Value!;
-
- if (isPackageInstalled == true)
- {
- appPath = packageFamilyName + "!MicrosoftTeams";
- isAppX = true;
- }
- }
- }
- break;
- case "microsoftWord":
- {
- appPath = ApplicationAction.SearchAppPaths("Winword.exe");
- }
- break;
- case "opera":
- {
- appPath = ApplicationAction.SearchAppPaths("opera.exe");
- }
- break;
- default:
- {
- appPath = null;
- }
- break;
- }
-
- if (appPath is not null)
- {
- var result = new MorphicExecutablePathInfo()
- {
- IsAppx = isAppX,
- Path = appPath
- };
- return MorphicResult.OkResult(result);
- }
- else
- {
- return MorphicResult.ErrorResult();
- }
- }
-
- ///
- /// Executable name, or the full path to it. If also providing arguments, surround the executable path with quotes.
- ///
- [JsonProperty("exe")]
- public string? ExeName
- {
- get => this.exeNameValue ?? null;
- set
- {
- this.exeNameValue = value;
- this.AppPath = null; // until we find the executable id'd by exeNameValue, AppPath should be null; it will be set to the actual executable path (or AppX identity)
-
- // OBSERVATION: we currently do not check if a package is installed; we simply assume that it is installed (i.e. available) if the EXE name beings with "appx:"
- // NOTE: the "exename" for an appx is "appx:" followed by the AUMID (which includes the package name, plus additional information after the package name)
- if (value is not null && value.StartsWith("appx:", StringComparison.InvariantCultureIgnoreCase))
- {
- this.AppX = true;
- this.AppPath = value.Substring(5);
- }
-
- if (value is not null && value.Length > 0)
- {
- var convertExeToPathResult = ApplicationAction.ConvertExeIdToExecutablePath(value);
- if (convertExeToPathResult.IsError == false)
- {
- this.AppX = convertExeToPathResult.Value!.IsAppx;
- this.AppPath = convertExeToPathResult.Value!.Path;
- App.Current.Logger.LogDebug($"Resolved exe file '{this.exeNameValue}' to '{this.AppPath ?? "(null)"}'");
- }
- }
-
- this.IsAvailable = this.AppPath is not 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();
-
- ///
- /// Searches the directories in the PATH environment variable.
- ///
- ///
- /// null if not found.
- private static string? SearchPathEnv(string file)
- {
- // OBSERVATION: the noted alternative may search some standard system directories outside of the PATH if the path env var isn't explicitly provided as an array of strings
- // 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 static string? SearchAppPaths(string file)
- {
- string? fullPath = null;
-
- // Look in *\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths (giving priority to the current user, in case an entry exists in both locations)
- foreach (RegistryKey rootKey in new[] {Registry.CurrentUser, Registry.LocalMachine})
- {
- RegistryKey? key =
- rootKey.OpenSubKey($@"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\{file}");
- if (key is not null)
- {
- // capture the default key (which should be the full path with executable)
- fullPath = key.GetValue(null) as string;
- if (fullPath is not null)
- {
- break;
- }
- }
- }
-
- return fullPath;
- }
-
- // NOTE: we should not use GetAppPathForProgId until we have dealt with removing command-line arguments (e.g. "C:\Program Files\Microsoft Office\Root\Office16\WINWORD.EXE /Automation")
- private static MorphicResult GetAppPathForProgId(string progId)
- {
- var getClassIdResult = ApplicationAction.GetClassIdForProgId(progId);
- if (getClassIdResult.IsError == true)
- {
- return MorphicResult.ErrorResult();
- }
- var classId = getClassIdResult.Value!;
-
- var getPathResult = ApplicationAction.GetLocalServer32PathForClassId(classId);
- if (getPathResult.IsError == true)
- {
- return MorphicResult.ErrorResult();
- }
- var path = getPathResult.Value!;
-
- // if the command is enclosed by quotes, strip out the actual executable name
- if (path.Length > 0 && path.Substring(0, 1) == "\"")
- {
- var indexOfClosingQuote = path.IndexOf('\"', 1);
- if (indexOfClosingQuote > 0)
- {
- path = path.Substring(1, indexOfClosingQuote - 1);
- }
- }
-
- // NOTE: we should check the actual path and remove any command-line arguments (in case the path includes the executable name and also command-line arguments)
- // [this could be tricky]
-
- return MorphicResult.OkResult(path);
- }
-
- // NOTE: this function should return the path to run an executable, given its classId (which can be retrieved from FindClassIdForProgId)
- private static MorphicResult GetLocalServer32PathForClassId(string classId)
- {
- string? result = null;
-
- bool is64Bit;
- switch (IntPtr.Size)
- {
- case 4:
- is64Bit = false;
- break;
- case 8:
- is64Bit = true;
- break;
- default:
- Debug.Assert(false, "OS is not 32-bit or 64-bit");
- return MorphicResult.ErrorResult();
- }
-
- RegistryKey? key;
- if (is64Bit == true)
- {
- key = Registry.ClassesRoot.OpenSubKey($@"Wow6432Node\CLSID\{classId}\LocalServer32");
- }
- else
- {
- key = Registry.ClassesRoot.OpenSubKey($@"CLSID\{classId}\LocalServer32");
- }
-
- if (key is not null)
- {
- var localServer32 = key.GetValue(null) as string;
- if (localServer32 is not null)
- {
- result = localServer32;
- }
- }
-
- if (result is not null)
- {
- return MorphicResult.OkResult(result);
- }
- else
- {
- // if we could not find the key or if its default value was null or invalid, return an error
- return MorphicResult.ErrorResult();
- }
- }
-
- private static MorphicResult GetClassIdForProgId(string progId)
- {
- RegistryKey? key = Registry.ClassesRoot.OpenSubKey($@"{progId}\CLSID");
- if (key is not null)
- {
- var classId = key.GetValue(null) as string;
- if (classId is not null)
- {
- return MorphicResult.OkResult(classId);
- }
- }
-
- // if we could not find the key or if its default value was null, return an error
- return MorphicResult.ErrorResult();
- }
-
- private static MorphicResult StripExecutableFromCommand(string command)
- {
- // we need to split off the executable name from any arguments; it is either enclosed in quotes or it's everything before a space
- //
- // option 1: enclosed in quotes
- // NOTE: there may be other ways of addressing this (such as looking for a file extension on the executable file name)
- var indexOfFirstDoubleQuote = command.IndexOf('\"');
- if (indexOfFirstDoubleQuote == 0)
- {
- var indexOfSecondDoubleQuote = command.IndexOf('\"', indexOfFirstDoubleQuote + 1);
- if (indexOfSecondDoubleQuote > 1)
- {
- command = command.Substring(indexOfFirstDoubleQuote + 1, indexOfSecondDoubleQuote - indexOfFirstDoubleQuote - 1);
- return MorphicResult.OkResult(command);
- }
- else
- {
- return MorphicResult.ErrorResult();
- }
- }
- //
- // option 2: everything before a space (or everything, if there are no spaces)
- var indexOfFirstSpace = command.IndexOf(' ');
- if (indexOfFirstSpace > 0)
- {
- command = command.Substring(0, indexOfFirstSpace);
- return MorphicResult.OkResult(command);
- }
- else
- {
- return MorphicResult.OkResult(command);
- }
- }
-
- private static MorphicResult GetOpenCommandForProgIdClass(string progId)
- {
- // look up the browser progId's actual executable path (e.g. path to Edge, instead of "MSEdgeHtm")
- var browserOpenCommandRegistryKey = Registry.ClassesRoot.OpenSubKey(progId + @"\shell\open\command");
- if (browserOpenCommandRegistryKey is not null)
- {
- // get the string to launch the browser (e.g. the default registry key value); this result may include arguments
- var browserOpenCommand = browserOpenCommandRegistryKey.GetValue(null) as string;
- if (browserOpenCommand is not null)
- {
- var stripExecutableFromCommandResult = ApplicationAction.StripExecutableFromCommand(browserOpenCommand);
- if (stripExecutableFromCommandResult.IsError == true)
- {
- return MorphicResult.ErrorResult();
- }
- browserOpenCommand = stripExecutableFromCommandResult.Value!;
-
- return MorphicResult.OkResult(browserOpenCommand);
- }
- }
-
- // if we could not get the open command, return failure
- return MorphicResult.ErrorResult();
- }
-
- private static MorphicResult GetPathToExecutableForUrlAssociation(string urlAssociation)
- {
- var userSelectedBrowserRegistryKey = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\Shell\Associations\UrlAssociations\" + urlAssociation + @"\UserChoice", false);
- if (userSelectedBrowserRegistryKey is not null)
- {
- var progId = userSelectedBrowserRegistryKey.GetValue("ProgId") as string;
- if (progId is not null)
- {
- var getOpenCommandForProgIdClassResult = ApplicationAction.GetOpenCommandForProgIdClass(progId);
- if (getOpenCommandForProgIdClassResult.IsError == false)
- {
- var browserOpenCommand = getOpenCommandForProgIdClassResult.Value!;
- return MorphicResult.OkResult(browserOpenCommand);
- }
- }
- }
-
- // if we could not get the open command, return failure
- return MorphicResult.ErrorResult();
- }
-
- private static MorphicResult GetPathToExecutableForFileExtension(string fileExtension)
- {
- var userSelectedBrowserRegistryKey = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\FileExts\" + fileExtension + @"\UserChoice", false);
- if (userSelectedBrowserRegistryKey is not null)
- {
- var progId = userSelectedBrowserRegistryKey.GetValue("ProgId") as string;
- if (progId is not null)
- {
- var getOpenCommandForProgIdClassResult = ApplicationAction.GetOpenCommandForProgIdClass(progId);
- if (getOpenCommandForProgIdClassResult.IsError == false)
- {
- var browserOpenCommand = getOpenCommandForProgIdClassResult.Value!;
- return MorphicResult.OkResult(browserOpenCommand);
- }
- }
- }
-
- // if we could not get the open command, return failure
- return MorphicResult.ErrorResult();
- }
-
- private static MorphicResult GetDefaultBrowserPath()
- {
- var userSelectedBrowserRegistryKey = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice", false);
- if (userSelectedBrowserRegistryKey is not null)
- {
- var browserProgId = userSelectedBrowserRegistryKey.GetValue("ProgId") as string;
- if (browserProgId is not null)
- {
- var getOpenCommandForProgIdClassResult = ApplicationAction.GetOpenCommandForProgIdClass(browserProgId);
- if (getOpenCommandForProgIdClassResult.IsError == false)
- {
- var browserOpenCommand = getOpenCommandForProgIdClassResult.Value!;
- return MorphicResult.OkResult(browserOpenCommand);
- }
- }
- }
-
- // if we could not get the browser using the UserChoice registry key, try looking up the file association for an ".htm" file instead
-
-
-
-
- // if no path could be found, return failure
- return MorphicResult.ErrorResult();
- }
-
- protected override Task> InvokeAsyncImpl(string? source = null, bool? toggleState = null)
- {
- if (this.DefaultAppName is not null)
- {
- // use the default application for this type
- switch (this.DefaultAppName!)
- {
- case "browser":
- {
- string? associatedExecutablePath = null;
-
- // try to get the executable for https:// urls
- var getAssociatedExecutableForHttpUrlsResult = ApplicationAction.GetPathToExecutableForUrlAssociation("https");
- if (getAssociatedExecutableForHttpUrlsResult.IsError == false)
- {
- associatedExecutablePath = getAssociatedExecutableForHttpUrlsResult.Value!;
- }
-
- // if we haven't found the default browser yet, look for the default application to open ".htm" files
- if (associatedExecutablePath is null)
- {
- var getAssociatedExecutableForHtmFilesResult = ApplicationAction.GetPathToExecutableForFileExtension(".htm");
- if (getAssociatedExecutableForHtmFilesResult.IsError == false)
- {
- associatedExecutablePath = getAssociatedExecutableForHtmFilesResult.Value!;
- }
- }
-
- // if we still haven't found the default browser, gracefully degrade by trying to use the launch process executable shortcut "https:" instead
- if (associatedExecutablePath is null)
- {
- associatedExecutablePath = "https:";
- }
-
- var launchBrowserProcessResult = ApplicationAction.LaunchProcess(associatedExecutablePath, new List(), new Dictionary(), this.WindowStyle);
- return Task.FromResult(launchBrowserProcessResult);
- }
- case "email":
- {
- var launchTarget = "mailto:";
-
- var launchMailProcessResult = ApplicationAction.LaunchProcess(launchTarget, new List(), new Dictionary(), this.WindowStyle, true /* useShellExecute should be true for all protocols (e.g. 'mailto:') */);
- return Task.FromResult(launchMailProcessResult);
- }
- default:
- {
- // unknown
- Debug.Assert(false, "Unknown 'default' application type: " + this.DefaultAppName!);
- //
- MorphicResult result = MorphicResult.ErrorResult();
- return Task.FromResult(result);
- }
- }
- }
-
- // if we reach here, we need to launch the executable related to this "exe" ID
- if (string.IsNullOrEmpty(this.ExeName) || string.IsNullOrEmpty(this.AppPath))
- {
- // if we don't have an exeName ID tag, we have failed
- //
- MorphicResult result = MorphicResult.ErrorResult();
- return Task.FromResult(result);
- }
-
- if (this.AppX)
- {
- var pid = Appx.Start(this.AppPath);
- //
- MorphicResult result = pid > 0 ? MorphicResult.OkResult() : MorphicResult.ErrorResult();
- return Task.FromResult(result);
- }
-
- // for all other processes, launch the executable
- // pathToExecutable
- var pathToExecutable = this.AppPath;
- //
- // useShellExecute
- var useShellExecute = true; // default
- if (this.Shell)
- {
- useShellExecute = true;
- }
- // arguments
- List arguments = new List();
- if (this.Arguments.Count > 0)
- {
- foreach (string argument in this.Arguments)
- {
- var resolvedString = this.ResolveString(argument, source);
- if (resolvedString is not null)
- {
- arguments.Add(resolvedString);
- }
- }
- }
- else
- {
- var resolvedString = this.ResolveString(this.ArgumentsString, source);
- if (resolvedString is not null)
- {
- arguments.Add(resolvedString);
- }
- }
- //
- // environmentVariables
- Dictionary environmentVariables = new Dictionary();
- foreach (var (key, value) in this.EnvironmentVariables)
- {
- var resolvedString = this.ResolveString(value, source);
- if (resolvedString is not null)
- {
- environmentVariables.Add(key, resolvedString);
- }
- else
- {
- Debug.Assert(false, "Could not resolve environment variable: key = " + key + ", value = '" + value + "'");
- }
- }
- //
- // windowStyle
- var windowStyle = this.WindowStyle;
-
- var launchProcessResult = ApplicationAction.LaunchProcess(pathToExecutable, arguments, environmentVariables, windowStyle, useShellExecute);
- return Task.FromResult(launchProcessResult);
- }
-
- private static MorphicResult LaunchProcess(string pathToExecutable, List arguments, Dictionary environmentVariables, ProcessWindowStyle windowStyle, bool useShellExecute = true)
- {
- ProcessStartInfo startInfo = new ProcessStartInfo()
- {
- FileName = pathToExecutable,
- ErrorDialog = true,
- // This is required to start taskmgr (the UAC prompt)
- UseShellExecute = useShellExecute,
- WindowStyle = windowStyle
- };
-
- foreach (string argument in arguments)
- {
- startInfo.ArgumentList.Add(argument);
- }
-
- foreach (var (key, value) in environmentVariables)
- {
- startInfo.EnvironmentVariables.Add(key, value);
- }
-
- Process? process = Process.Start(startInfo);
- if (process is not null)
- {
- return MorphicResult.OkResult();
- }
- else
- {
- return MorphicResult.ErrorResult();
- }
- }
-
- ///
- /// Activates a running instance of the application.
- ///
- /// false if it could not be done.
- ///
- private MorphicResult 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 ? MorphicResult.OkResult() : MorphicResult.ErrorResult();
- }
- }
-}
diff --git a/Morphic.Client/Bar/Data/Actions/BarAction.cs b/Morphic.Client/Bar/Data/Actions/BarAction.cs
deleted file mode 100644
index fef26ec1..00000000
--- a/Morphic.Client/Bar/Data/Actions/BarAction.cs
+++ /dev/null
@@ -1,412 +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)
- {
- MorphicResult 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 is not null)
- {
- MessageBox.Show($"There was a problem performing the action:\n\n{e.UserMessage}",
- "Custom MorphicBar", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
- }
-
- result = MorphicResult.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 "log-off":
- await App.Current.Countly_RecordEventAsync("SignOut");
- break;
- case "volume":
- {
- if (source == "up")
- {
- await App.Current.Countly_RecordEventAsync("volumeUp");
- }
- else if (source == "down")
- {
- await App.Current.Countly_RecordEventAsync("volumeDown");
- }
- }
- break;
- case "magnify":
- {
- if (source == "on")
- {
- await App.Current.Countly_RecordEventAsync("magnifierShow");
- }
- else if (source == "off")
- {
- await App.Current.Countly_RecordEventAsync("magnifierHide");
- }
- }
- break;
- case "read-aloud":
- {
- if (source == "play")
- {
- await App.Current.Countly_RecordEventAsync("readSelectedPlay");
- }
- else if (source == "stop")
- {
- await App.Current.Countly_RecordEventAsync("readSelectedStop");
- }
- }
- break;
- case "color-vision":
- switch (source)
- {
- case "on":
- await App.Current.Countly_RecordEventAsync("colorFiltersOn");
- break;
- case "off":
- await App.Current.Countly_RecordEventAsync("colorFiltersOff");
- break;
- }
- break;
- case "dark-mode":
- switch (source)
- {
- case "on":
- await App.Current.Countly_RecordEventAsync("darkModeOn");
- break;
- case "off":
- await App.Current.Countly_RecordEventAsync("darkModeOff");
- break;
- }
- break;
- case "high-contrast":
- switch (source)
- {
- case "100":
- await App.Current.Countly_RecordEventAsync("highContrastOn");
- break;
- case "1":
- await App.Current.Countly_RecordEventAsync("highContrastOff");
- break;
- }
- break;
- case "night-mode":
- switch (source)
- {
- case "on":
- await App.Current.Countly_RecordEventAsync("nightModeOn");
- break;
- case "off":
- await App.Current.Countly_RecordEventAsync("nightModeOff");
- break;
- }
- break;
- case "":
- switch (source)
- {
- case "com.microsoft.windows.colorFilters/enabled":
- {
- if (toggleState == true)
- {
- await App.Current.Countly_RecordEventAsync("colorFiltersOn");
- }
- else
- {
- await App.Current.Countly_RecordEventAsync("colorFiltersOff");
- }
- }
- break;
- case "com.microsoft.windows.highContrast/enabled":
- {
- if (toggleState == true)
- {
- await App.Current.Countly_RecordEventAsync("highContrastOn");
- }
- else
- {
- await App.Current.Countly_RecordEventAsync("highContrastOff");
- }
- }
- break;
- case "com.microsoft.windows.nightMode/enabled":
- {
- if (toggleState == true)
- {
- await App.Current.Countly_RecordEventAsync("nightModeOn");
- }
- else
- {
- await App.Current.Countly_RecordEventAsync("nightModeOff");
- }
- }
- break;
- case "copy":
- // NOTE: this is the basic bar variant of "copy" (below)
- {
- await App.Current.Countly_RecordEventAsync("screenSnip");
- }
- break;
- case "dark-mode":
- {
- if (toggleState == true)
- {
- await App.Current.Countly_RecordEventAsync("darkModeOn");
- }
- else
- {
- await App.Current.Countly_RecordEventAsync("darkModeOff");
- }
- }
- break;
- case "openallusb":
- await App.Current.Countly_RecordEventAsync("openUsbDrives");
- break;
- case "ejectallusb":
- await App.Current.Countly_RecordEventAsync("ejectUsbDrives");
- 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;
- case "snip":
- // NOTE: this is the custom bar variant of "copy" (above)
- {
- await App.Current.Countly_RecordEventAsync("screenSnip");
- }
- 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)
- {
- MorphicResult result = MorphicResult.OkResult();
- return Task.FromResult(result);
- }
- }
-
- [JsonTypeName("internal")]
- public class InternalAction : BarAction
- {
- [JsonProperty("function", Required = Required.Always)]
- public string? FunctionName { get; set; }
-
- [JsonProperty("function_on_right_click_also", Required = Required.AllowNull)]
- public bool? FunctionOnRightClickAlso { get; set; }
-
- [JsonProperty("args")]
- public Dictionary Arguments { get; set; } = new Dictionary();
-
- public string? TelemetryEventName { get; set; }
-
- protected async override Task> InvokeAsyncImpl(string? source = null, bool? toggleState = null)
- {
- try
- {
- if (this.FunctionName is null)
- {
- return MorphicResult.OkResult();
- }
-
- Dictionary resolvedArgs = this.Arguments
- .ToDictionary(kv => kv.Key, kv => this.ResolveString(kv.Value, source) ?? string.Empty);
-
- resolvedArgs.Add("state", toggleState == true ? "on" : "off");
-
- return await InternalFunctions.Default.InvokeFunctionAsync(this.FunctionName, resolvedArgs);
- }
- finally
- {
- if (this.TelemetryEventName is not null)
- {
- await App.Current.Countly_RecordEventAsync(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 MorphicResult.OkResult();
- }
- }
-
- [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 is not null;
- }
-
- MorphicResult result = success ? MorphicResult.OkResult() : MorphicResult.ErrorResult();
- return Task.FromResult(result);
- }
-
- 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 389e8efe..00000000
--- a/Morphic.Client/Bar/Data/Actions/Functions.cs
+++ /dev/null
@@ -1,1028 +0,0 @@
-namespace Morphic.Client.Bar.Data.Actions
-{
- using Microsoft.Extensions.Logging;
- using Morphic.Core;
- using Settings.SettingsHandlers;
- using Settings.SolutionsRegistry;
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Diagnostics.CodeAnalysis;
- using System.Linq;
- using System.Runtime.InteropServices;
- using System.Text;
- using System.Threading;
- using System.Threading.Tasks;
- using System.Windows;
- using System.Windows.Automation;
- using System.Windows.Automation.Text;
- using UI;
- using Windows.Native.Input;
- using Windows.Native.Speech;
-
- [HasInternalFunctions]
- // ReSharper disable once UnusedType.Global - accessed via reflection.
- public class Functions
- {
- private readonly static SemaphoreSlim s_captureTextSemaphore = new SemaphoreSlim(1, 1);
-
- [InternalFunction("snip")]
- public static async Task> ScreenSnipAsync(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);
-
- //// method 1: hold down the windows key while pressing shift + s
- //// NOTE: this method does not seem to work when we have uiAccess set to true in our manifest (oddly)
- //const uint windowsKey = 0x5b; // VK_LWIN
- //Keyboard.PressKey(windowsKey, true);
- //System.Windows.Forms.SendKeys.SendWait("+s");
- //Keyboard.PressKey(windowsKey, false);
-
- // method 2: open up the special windows URI of ms-screenclip:
- var openPath = "ms-screenclip:";
- Process.Start(new ProcessStartInfo(openPath)
- {
- UseShellExecute = true
- });
- }
- 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 MorphicResult.OkResult();
- }
-
- [InternalFunction("menu", "key=Morphic")]
- public async static Task> ShowMenuAsync(FunctionArgs args)
- {
- // NOTE: this internal function is only called by the MorphicBar's Morphie menu button
- await App.Current.ShowMenuAsync(null, Morphic.Client.Menu.MorphicMenu.MenuOpenedSource.morphicBarIcon);
- return MorphicResult.OkResult();
- }
-
- [InternalFunction("volumeUp")]
- public static async Task> VolumeUpAsync(FunctionArgs args)
- {
- args.Arguments.Add("direction", "up");
- args.Arguments.Add("amount", "6");
- return await SetVolumeAsync(args);
- }
-
- [InternalFunction("volumeDown")]
- public static async Task> VolumeDownAsync(FunctionArgs args)
- {
- args.Arguments.Add("direction", "down");
- args.Arguments.Add("amount", "6");
- return await SetVolumeAsync(args);
- }
-
- internal static MorphicResult GetMuteState()
- {
- try
- {
- var audioEndpoint = Windows.Native.Audio.AudioEndpoint.GetDefaultAudioOutputEndpoint();
-
- // if we didn't get a state in the request, try to reverse the state
- var state = audioEndpoint.GetMasterMuteState();
-
- return MorphicResult.OkResult(state);
- }
- catch
- {
- return MorphicResult.ErrorResult();
- }
- }
-
- [InternalFunction("volumeMute")]
- public static async Task> VolumeMuteAsync(FunctionArgs args)
- {
- bool newState;
- if (args.Arguments.Keys.Contains("state"))
- {
- newState = (args["state"] == "on");
- }
- else
- {
- var getMuteStateResult = Functions.GetMuteState();
- if (getMuteStateResult.IsSuccess == true)
- {
- newState = getMuteStateResult.Value!;
- }
- else
- {
- // if we cannot get the current value, gracefully degrade (i.e. assume that the volume is not muted)
- newState = false;
- }
- }
-
- try
- {
- // set the mute state to the new state value
- var audioEndpoint = Windows.Native.Audio.AudioEndpoint.GetDefaultAudioOutputEndpoint();
- audioEndpoint.SetMasterMuteState(newState);
- }
- catch
- {
- return MorphicResult.ErrorResult();
- }
-
- return MorphicResult.OkResult();
- }
-
- ///
- /// Lowers or raises the volume.
- ///
- /// direction: "up"/"down", amount: number of 1/100 to move
- ///
- [InternalFunction("volume", "direction", "amount=6")]
- public static async Task> SetVolumeAsync(FunctionArgs args)
- {
- // NOTE: ideally we should switch this functionality to use AudioEndpoint.SetMasterVolumeLevel instead
-
- 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 MorphicResult.OkResult();
- }
-
- private static async Task> ClearClipboardAsync(uint numberOfRetries, TimeSpan interval)
- {
- // NOTE from Microsoft documentation (something to think about when working on this in the future...and perhaps something we need to handle):
- /* "The Clipboard class can only be used in threads set to single thread apartment (STA) mode.
- * To use this class, ensure that your Main method is marked with the STAThreadAttribute attribute."
- * https://docs.microsoft.com/es-es/dotnet/api/system.windows.forms.clipboard.clear?view=net-5.0
- */
- for (var i = 0; i < numberOfRetries; i++)
- {
- try
- {
- // NOTE: some developers have reported unhandled exceptions with this function call, even when inside a try...catch block. If we experience that, we may need to look at our threading model, UWP alternatives, and Win32 API alternatives.
- Clipboard.Clear();
- return MorphicResult.OkResult();
- }
- catch
- {
- // failed to copy to clipboard; wait an interval and then try again
- await Task.Delay(interval);
- }
- }
-
- App.Current.Logger.LogDebug("ReadAloud: Could not clear selected text from the clipboard.");
- return MorphicResult.ErrorResult();
- }
-
- ///
- /// Reads the selected text.
- ///
- /// action: "play", "pause", or "stop"
- ///
- [InternalFunction("readAloud", "action")]
- public static async Task> ReadAloudAsync(FunctionArgs args)
- {
- string action = args["action"];
- switch (action)
- {
- case "pause":
- App.Current.Logger.LogError("ReadAloud: pause not supported.");
-
- return MorphicResult.ErrorResult();
-
- case "stop":
- App.Current.Logger.LogDebug("ReadAloud: Stop reading selected text.");
- TextToSpeechHelper.Instance.Stop();
-
- return MorphicResult.OkResult();
-
- case "play":
- string? selectedText = null;
-
- try
- {
- App.Current.Logger.LogDebug("ReadAloud: Getting selected text.");
-
- // activate the target window (i.e. topmost/last-active window, rather than the MorphicBar); we will then capture the current selection in that window
- // NOTE: ideally we would activate the last window as part of our atomic operation, but we really have no control over whether or not another application
- // or the user changes the activated window (and our internal code is also not set up to block us from moving activation/focus temporarily).
- await SelectionReader.Default.ActivateLastActiveWindow();
-
- // as a primary strategy, try using the built-in Windows functionality for capturing the current selection via UI automation
- // NOTE: this does not work with some apps (such as Internet Explorer...but also others)
- bool captureTextViaAutomationSucceeded = false;
- //
- TextPatternRange[]? textRangeCollection = null;
- //
- // capture (or wait on) our "capture text" semaphore; we'll release this in the finally block
- await s_captureTextSemaphore.WaitAsync();
- //
- try
- {
- var focusedElement = AutomationElement.FocusedElement;
- if (focusedElement is not null)
- {
- object? pattern = null;
- if (focusedElement.TryGetCurrentPattern(TextPattern.Pattern, out pattern))
- {
- if ((pattern is not null) && (pattern is TextPattern textPattern))
- {
- // App.Current.Logger.LogDebug("ReadAloud: Capturing select text range(s).");
-
- // get the collection of text ranges in the selection; note that this can be a disjoint collection if multiple disjoint items were selected
- textRangeCollection = textPattern.GetSelection();
- }
- }
- else
- {
- App.Current.Logger.LogDebug("ReadAloud: Selected element is not text.");
- }
- }
- else
- {
- App.Current.Logger.LogDebug("ReadAloud: No element is currently selected.");
- }
- }
- finally
- {
- s_captureTextSemaphore.Release();
- }
- //
- // if we just captured a text range collection (i.e. were able to copy the current selection), convert that capture into a string now
- StringBuilder? selectedTextBuilder = null;
- if (textRangeCollection is not null)
- {
- // we have captured a range (presumably either an empty or non-empty selection)
- selectedTextBuilder = new StringBuilder();
-
- // append each text range
- foreach (var textRange in textRangeCollection)
- {
- if (textRange is not null)
- {
- selectedTextBuilder.Append(textRange.GetText(-1 /* maximumRange */));
- }
- }
-
- //if (selectedTextBuilder is not null /* && stringBuilder.Length > 0 */)
- //{
- selectedText = selectedTextBuilder.ToString();
- captureTextViaAutomationSucceeded = true;
-
- if (selectedText != String.Empty)
- {
- App.Current.Logger.LogDebug("ReadAloud: Captured selected text.");
- }
- else
- {
- App.Current.Logger.LogDebug("ReadAloud: Captured empty selection.");
- }
- //}
- }
-
- // as a backup strategy, use the clipboard and send ctrl+c to the target window to capture the text contents (while preserving as much of the previous
- // clipboard's contents as possible); this is necessary in Internet Explorer and some other programs
- if (captureTextViaAutomationSucceeded == false)
- {
- // capture (or wait on) our "capture text" semaphore; we'll release this in the finally block
- await s_captureTextSemaphore.WaitAsync();
- //
- try
- {
- // App.Current.Logger.LogDebug("ReadAloud: Attempting to back up current clipboard.");
-
- Dictionary clipboardContentsToRestore = new Dictionary();
-
- var previousClipboardData = Clipboard.GetDataObject();
- if (previousClipboardData is not null)
- {
- // App.Current.Logger.LogDebug("ReadAloud: Current clipboard has contents; attempting to capture format(s) of contents.");
- string[]? previousClipboardFormats = previousClipboardData.GetFormats();
- if (previousClipboardFormats is not null)
- {
- // App.Current.Logger.LogDebug("ReadAloud: Current clipboard has contents; attempting to back up current clipboard.");
-
- foreach (var format in previousClipboardFormats)
- {
- object? dataObject;
- try
- {
- dataObject = previousClipboardData.GetData(format, false /* autoConvert */);
- }
- catch
- {
- // NOTE: in the future, we should look at using Project Reunion to use the UWP APIs (if they can deal with this scenario better)
- // see: https://docs.microsoft.com/en-us/uwp/api/windows.applicationmodel.datatransfer.clipboard?view=winrt-19041
- // see: https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/desktop-to-uwp-enhance
- App.Current.Logger.LogDebug("ReadAloud: Unable to back up clipboard contents; this can happen with files copied to the clipboard, etc.");
-
- return MorphicResult.ErrorResult();
- }
- clipboardContentsToRestore[format] = dataObject;
- }
- }
- else
- {
- App.Current.Logger.LogDebug("ReadAloud: Current clipboard has contents, but we were unable to obtain their formats.");
- }
- }
- else
- {
- App.Current.Logger.LogDebug("ReadAloud: Current clipboard has no contents.");
- }
-
- // clear the current clipboard
- App.Current.Logger.LogDebug("ReadAloud: Clearing the current clipboard.");
- try
- {
- // try to clear the clipboard for up to 500ms (4 delays of 125ms)
- await Functions.ClearClipboardAsync(5, new TimeSpan(0, 0, 0, 0, 125));
- }
- catch
- {
- App.Current.Logger.LogDebug("ReadAloud: Could not clear the current clipboard.");
- }
-
- // copy the current selection to the clipboard
- App.Current.Logger.LogDebug("ReadAloud: Sending Ctrl+C to copy the current selection to the clipboard.");
- await SelectionReader.Default.GetSelectedTextAsync(System.Windows.Forms.SendKeys.SendWait);
-
- // wait 100ms (an arbitrary amount of time, but in our testing some wait is necessary...even with the WM-triggered copy logic above)
- // NOTE: perhaps, in the future, we should only do this if our first call to Clipboard.GetText() returns (null? or) an empty string;
- // or perhaps we should wait up to a certain number of milliseconds to receive a SECOND WM (the one that GetSelectedTextAsync
- // waited for).
- await Task.Delay(100);
-
- // capture the current selection
- var selectionWasCopiedToClipboard = false;
- var textCopiedToClipboard = Clipboard.GetText();
- if (textCopiedToClipboard is not null)
- {
- selectionWasCopiedToClipboard = true;
-
- // we now have our selected text
- selectedText = textCopiedToClipboard;
-
- if (selectedText is not null)
- {
- App.Current.Logger.LogDebug("ReadAloud: Captured selected text.");
- }
- else
- {
- App.Current.Logger.LogDebug("ReadAloud: Captured empty selection.");
- }
- }
- else
- {
- var copiedDataFormats = Clipboard.GetDataObject()?.GetFormats();
- if (copiedDataFormats is not null)
- {
- selectionWasCopiedToClipboard = true;
-
- // var formatsCsvBuilder = new StringBuilder();
- // formatsCsvBuilder.Append("[");
- // if (copiedDataFormats.Length > 0)
- // {
- // formatsCsvBuilder.Append("\"");
- // formatsCsvBuilder.Append(String.Join("\", \"", copiedDataFormats));
- // formatsCsvBuilder.Append("\"");
- // }
- // formatsCsvBuilder.Append("]");
-
- // App.Current.Logger.LogDebug("ReadAloud: Ctrl+C did not copy text; instead it copied data in these format(s): " + formatsCsvBuilder.ToString());
- App.Current.Logger.LogDebug("ReadAloud: Ctrl+C copied non-text (un-speakable) contents to the clipboard.");
- }
- else
- {
- App.Current.Logger.LogDebug("ReadAloud: Ctrl+C did not copy anything to the clipboard.");
- }
- }
-
- // restore the previous clipboard's contents
- // App.Current.Logger.LogDebug("ReadAloud: Attempting to restore the previous clipboard's contents");
- //
- if (selectionWasCopiedToClipboard == true)
- {
- // App.Current.Logger.LogDebug("ReadAloud: Clearing the selected text from the clipboard.");
- try
- {
- // try to clear the clipboard for up to 500ms (4 delays of 125ms)
- await Functions.ClearClipboardAsync(5, new TimeSpan(0,0,0,0,125));
- }
- catch
- {
- App.Current.Logger.LogDebug("ReadAloud: Could not clear selected text from the clipboard.");
- }
- }
- //
- if (clipboardContentsToRestore.Count > 0)
- {
- // App.Current.Logger.LogDebug("ReadAloud: Attempting to restore " + clipboardContentsToRestore.Count.ToString() + " item(s) to the clipboard.");
- }
- else
- {
- // App.Current.Logger.LogDebug("ReadAloud: there is nothing to restore to the clipboard.");
- }
- //
- foreach (var (format, data) in clipboardContentsToRestore)
- {
- // NOTE: sometimes, data is null (which is not something that SetData can accept) so we have to just skip that element
- if (data is not null)
- {
- Clipboard.SetData(format, data);
- }
- }
- //
- App.Current.Logger.LogDebug("ReadAloud: Clipboard restoration complete");
- }
- finally
- {
- s_captureTextSemaphore.Release();
- }
- }
- }
- catch (Exception ex)
- {
- App.Current.Logger.LogError(ex, "ReadAloud: Error reading selected text.");
-
- return MorphicResult.ErrorResult();
- }
-
- if (selectedText is not null)
- {
- if (selectedText != String.Empty)
- {
- try
- {
- App.Current.Logger.LogDebug("ReadAloud: Saying selected text.");
-
- var sayResult = await TextToSpeechHelper.Instance.Say(selectedText);
- if (sayResult.IsError == true)
- {
- App.Current.Logger.LogError("ReadAloud: Error saying selected text.");
-
- return MorphicResult.ErrorResult();
- }
-
- return MorphicResult.OkResult();
- }
- catch (Exception ex)
- {
- App.Current.Logger.LogError(ex, "ReadAloud: Error reading selected text.");
-
- return MorphicResult.ErrorResult();
- }
- }
- else
- {
- App.Current.Logger.LogDebug("ReadAloud: No text to say; skipping 'say' command.");
-
- return MorphicResult.OkResult();
- }
- } else {
- // could not capture any text
- // App.Current.Logger.LogError("ReadAloud: Could not capture any selected text; this may or may not be an error.");
-
- return MorphicResult.ErrorResult();
- }
- default:
- throw new Exception("invalid code path");
- }
- }
-
- ///
- /// Sends key strokes to the active application.
- ///
- /// keys: the keys (see MSDN for SendKeys.Send())
- ///
- [InternalFunction("sendKeys", "keys")]
- public static async Task> SendKeysAsync(FunctionArgs args)
- {
- await SelectionReader.Default.ActivateLastActiveWindow();
- System.Windows.Forms.SendKeys.SendWait(args["keys"]);
- return MorphicResult.OkResult();
- }
-
- [InternalFunction("signOut")]
- public static async Task> SignOutAsync(FunctionArgs args)
- {
- var success = Morphic.Windows.Native.WindowsSession.WindowsSession.LogOff();
- return success ? MorphicResult.OkResult() : MorphicResult.ErrorResult();
- }
-
- [InternalFunction("openAllUsbDrives")]
- public static async Task> OpenAllUsbDrivesAsync(FunctionArgs args)
- {
- App.Current.Logger.LogError("OpenAllUsbDrives");
-
- var getRemovableDisksAndDrivesResult = await Functions.GetRemovableDisksAndDrivesAsync();
- if (getRemovableDisksAndDrivesResult.IsError == true)
- {
- Debug.Assert(false, "Could not get list of removable drives");
- App.Current.Logger.LogError("Could not get list of removable drives");
- return MorphicResult.ErrorResult();
- }
- var removableDrives = getRemovableDisksAndDrivesResult.Value!.RemovableDrives;
-
- // as we only want to open usb drives which are mounted (i.e. not USB drives which have had their "media" ejected but who still have drive letters assigned)...
- var mountedRemovableDrives = new List();
- foreach (var drive in removableDrives)
- {
- var getIsMountedResult = await drive.GetIsMountedAsync();
- if (getIsMountedResult.IsError == true)
- {
- Debug.Assert(false, "Could not determine if drive is mounted");
- App.Current.Logger.LogError("Could not determine if drive is mounted");
- // gracefully degrade; skip this disk
- continue;
- }
- var driveIsMounted = getIsMountedResult.Value!;
-
- if (driveIsMounted)
- {
- mountedRemovableDrives.Add(drive);
- }
- }
-
- // now open all the *mounted* removable disks
- foreach (var drive in mountedRemovableDrives)
- {
- // get the drive's root path (e.g. "E:\"); note that we intentionally get the root path WITH the backslash so that we don't launch autoplay, etc.
- var tryGetDriveRootPathResult = await drive.TryGetDriveRootPathAsync();
- if (tryGetDriveRootPathResult.IsError == true)
- {
- Debug.Assert(false, "Could not get removable drive's root path");
- App.Current.Logger.LogError("Could not get removable drive's root path");
- // gracefully degrade; skip this disk
- continue;
- }
- var driveRootPath = tryGetDriveRootPathResult.Value!;
-
- // NOTE: there is also an API call which may be able to do this more directly
- // see: https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shopenfolderandselectitems
-
- // NOTE: we might also consider getting the current process for Explorer.exe and then asking it to "explore" the drive
-
- App.Current.Logger.LogError("Opening USB drive");
-
- Process.Start(new ProcessStartInfo()
- {
- FileName = driveRootPath,
- UseShellExecute = true
- });
- }
-
- return MorphicResult.OkResult();
- }
-
- [InternalFunction("ejectAllUsbDrives")]
- public static async Task> EjectAllUsbDrivesAsync(FunctionArgs args)
- {
- App.Current.Logger.LogError("EjectAllUsbDrives");
-
- var getRemovableDisksAndDrivesResult = await Functions.GetRemovableDisksAndDrivesAsync();
- if (getRemovableDisksAndDrivesResult.IsError == true)
- {
- Debug.Assert(false, "Could not get list of removable disks");
- App.Current.Logger.LogError("Could not get list of removable disks");
- return MorphicResult.ErrorResult();
- }
- var removableDisks = getRemovableDisksAndDrivesResult.Value!.RemovableDisks;
-
- // now eject all the removable disks
- var allDisksRemoved = true;
- foreach (var disk in removableDisks)
- {
- App.Current.Logger.LogError("Safely ejecting drive");
-
- // NOTE: "safe eject" in this circumstance means to safely eject the usb device (removing it from the PnP system, not physically ejecting media)
- var safeEjectResult = disk.SafelyRemoveDevice();
- if (safeEjectResult.IsError == true)
- {
- allDisksRemoved = false;
- }
-
- // wait 50ms between ejection
- await Task.Delay(50);
- }
-
- if (allDisksRemoved == false)
- {
- return MorphicResult.ErrorResult();
- }
-
- return allDisksRemoved ? MorphicResult.OkResult() : MorphicResult.ErrorResult();
- }
-
- private struct GetRemovableDisksAndDrivesResult
- {
- public List AllDisks;
- public List RemovableDisks; // physical volumes
- public List RemovableDrives; // logical volumes (media / partition); these can have drive letters
- }
- //
- private static async Task> GetRemovableDisksAndDrivesAsync()
- {
- // get a list of all disks (but not non-disks such as CD-ROM drives)
- var getAllDisksResult = await Morphic.Windows.Native.Devices.Disk.GetAllDisksAsync();
- if (getAllDisksResult.IsError == true)
- {
- Debug.Assert(false, "Cannot get list of disks");
- return MorphicResult.ErrorResult();
- }
-
- // filter out all disks which are not removable
- var allDisks = getAllDisksResult.Value!;
- var removableDisks = new List();
- foreach (var disk in allDisks)
- {
- var getIsRemovableResult = disk.GetIsRemovable();
- if (getIsRemovableResult.IsError == true)
- {
- Debug.Assert(false, "Cannot determine if disk is removable");
- return MorphicResult.ErrorResult();
- }
- var diskIsRemovable = getIsRemovableResult.Value!;
- if (diskIsRemovable)
- {
- removableDisks.Add(disk);
- }
- }
-
- // now get all the drives associated with our removable disks
- var removableDrives = new List();
- foreach (var removableDisk in removableDisks)
- {
- var getDrivesForDiskResult = await removableDisk.GetDrivesAsync();
- if (getDrivesForDiskResult.IsError == true)
- {
- Debug.Assert(false, "Cannot get list of drives for removable disk");
- // gracefully degrade; skip this disk
- continue;
- }
- var drivesForRemovableDisk = getDrivesForDiskResult.Value!;
-
- removableDrives.AddRange(drivesForRemovableDisk);
- }
-
- var result = new GetRemovableDisksAndDrivesResult
- {
- AllDisks = allDisks,
- RemovableDisks = removableDisks,
- RemovableDrives = removableDrives
- };
-
- return MorphicResult.OkResult(result);
- }
-
- internal async static Task> GetDarkModeStateAsync()
- {
- var osVersion = Morphic.Windows.Native.OsVersion.OsVersion.GetWindowsVersion();
- if (osVersion == Windows.Native.OsVersion.WindowsVersion.Win10_v1809)
- {
- // Windows 10 v1809
-
- // NOTE: this is hard-coded, as a patch, because the solutions registry does not yet understand how to capture/apply settings across incompatible handlers
- // [and trying to call the Windows 10 v1903+ handlers for apps/system "light theme" will result in a memory access exception under v1809]
- // [also: only "AppsUseLightTheme" (and not "SystemUsesLightTheme") existed properly under Windows 10 v1809]
-
- var openPersonalizeKeyResult = Morphic.Windows.Native.Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize", true);
- if (openPersonalizeKeyResult.IsError == true)
- {
- return MorphicResult.ErrorResult();
- }
- var personalizeKey = openPersonalizeKeyResult.Value!;
-
- // get the current setting
- bool appsUseLightThemeAsBool;
- var getAppsUseLightThemeResult = personalizeKey.GetValue("AppsUseLightTheme");
- if (getAppsUseLightThemeResult.IsError == true)
- {
- if (getAppsUseLightThemeResult.Error == Windows.Native.Registry.RegistryKey.RegistryValueError.ValueDoesNotExist)
- {
- // default AppsUseLightTheme (inverse of dark mode state) on Windows 10 v1809 is true
- appsUseLightThemeAsBool = true;
- }
- else
- {
- return MorphicResult.ErrorResult();
- }
- }
- else
- {
- var appsUseLightThemeAsUInt32 = getAppsUseLightThemeResult.Value!;
- appsUseLightThemeAsBool = (appsUseLightThemeAsUInt32 != 0) ? true : false;
- }
-
- // dark theme state is the inverse of AppsUseLightTheme
- var darkThemeState = !appsUseLightThemeAsBool;
-
- return MorphicResult.OkResult(darkThemeState);
- }
- else if (osVersion is null)
- {
- // error
- return MorphicResult.ErrorResult();
- }
- else
- {
- // Windows 10 v1903+
-
- // get system dark/light theme
- Setting systemThemeSetting = App.Current.MorphicSession.Solutions.GetSetting(SettingId.LightThemeSystem);
- var getSystemThemeValueResult = await systemThemeSetting.GetValueAsync();
- if (getSystemThemeValueResult.IsError == true)
- {
- return MorphicResult.ErrorResult();
- }
- var lightThemeSystemAsObject = getSystemThemeValueResult.Value!;
- var lightThemeSystemAsBool = (bool)lightThemeSystemAsObject;
-
- // set apps dark/light theme
- Setting appsThemeSetting = App.Current.MorphicSession.Solutions.GetSetting(SettingId.LightThemeApps);
- var getAppsThemeValueResult = await appsThemeSetting.GetValueAsync();
- if (getAppsThemeValueResult.IsError == true)
- {
- return MorphicResult.ErrorResult();
- }
- var lightThemeAppsAsObject = getAppsThemeValueResult.Value!;
- var lightThemeAppsAsBool = (bool)lightThemeSystemAsObject;
-
- // if either apps or system theme is set to "not light", then return true
- var darkModeIsEnabled = ((lightThemeSystemAsBool == false) || (lightThemeAppsAsBool == false));
- return MorphicResult.OkResult(darkModeIsEnabled);
- }
- }
-
- internal async static Task> SetDarkModeStateAsync(bool state)
- {
- var osVersion = Morphic.Windows.Native.OsVersion.OsVersion.GetWindowsVersion();
- if (osVersion == Windows.Native.OsVersion.WindowsVersion.Win10_v1809)
- {
- // Windows 10 v1809
-
- // NOTE: this is hard-coded, as a patch, because the solutions registry does not yet understand how to capture/apply settings across incompatible handlers
- // [and trying to call the Windows 10 v1903+ handlers for apps/system "light theme" will result in a memory access exception under v1809]
- // [also: only "AppsUseLightTheme" (and not "SystemUsesLightTheme") existed properly under Windows 10 v1809]
-
- var openPersonalizeKeyResult = Morphic.Windows.Native.Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize", true);
- if (openPersonalizeKeyResult.IsError == true)
- {
- return MorphicResult.ErrorResult();
- }
- var personalizeKey = openPersonalizeKeyResult.Value!;
-
- // set apps dark/light theme
- //
- uint newAppsUseLightThemeAsUInt32 = state ? (uint)0 : (uint)1; // NOTE: these are inverted (because we are setting "light state" using the inverse of the "dark state" parameter
- //
- // set the setting to the inverted state
- var setAppsUseLightThemeResult = personalizeKey.SetValue("AppsUseLightTheme", newAppsUseLightThemeAsUInt32);
- if (setAppsUseLightThemeResult.IsError == true)
- {
- return MorphicResult.ErrorResult();
- }
-
- // see: https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-wininichange
- var pointerToImmersiveColorSetString = Marshal.StringToHGlobalUni("ImmersiveColorSet");
- try
- {
- // notify all windows that we have changed a setting in the "win ini" settings
- _ = PInvoke.User32.SendMessage(PInvoke.User32.HWND_BROADCAST, PInvoke.User32.WindowMessage.WM_WININICHANGE, IntPtr.Zero, pointerToImmersiveColorSetString);
- }
- finally
- {
- Marshal.FreeHGlobal(pointerToImmersiveColorSetString);
- }
- }
- else if (osVersion is null)
- {
- // error
- return MorphicResult.ErrorResult();
- }
- else
- {
- // Windows 10 v1903+
-
- /*
- * 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
- * NOTE: we use registry access to get/set dark mode under Windows 10 <=v1809 (see code above); the "system dark theme" was introduced in Windows 10 v1903
- *
- * 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(!state);
-
- // set apps dark/light theme
- Setting appsThemeSetting = App.Current.MorphicSession.Solutions.GetSetting(SettingId.LightThemeApps);
- await appsThemeSetting.SetValueAsync(!state);
- }
-
- return MorphicResult.OkResult();
- }
-
- [InternalFunction("darkMode")]
- public static async Task> DarkModeAsync(FunctionArgs args)
- {
- // if we have a "value" property, this is a multi-segmented button and we should use "value" instead of "state"
- bool on;
- if (args.Arguments.Keys.Contains("value"))
- {
- on = (args["value"] == "on");
- }
- else if (args.Arguments.Keys.Contains("state"))
- {
- on = (args["state"] == "on");
- }
- else
- {
- System.Diagnostics.Debug.Assert(false, "Function 'darkMode' did not receive a new state");
- on = false;
- }
-
- var setDarkModeStateResult = await Functions.SetDarkModeStateAsync(on);
- if (setDarkModeStateResult.IsError == true)
- {
- return MorphicResult.ErrorResult();
- }
-
- return MorphicResult.OkResult();
- }
-
- //
-
- const string WORD_RUNNING_MESSAGE = "You need to exit Word in order to use the Word Simplify buttons.\n\n(1) Quit Word.\n(2) Use the Word Simplify buttons to add or remove the simplified ribbon(s) you want.\n(3) Re-launch Word.";
-
- private static bool IsSafeToModifyRibbonFile_WarnUser()
- {
- // make sure Word is not running before attempting to change the word ribbon enable/disable state
- var isWordRunningResult = Morphic.Integrations.Office.WordRibbon.IsWordRunning();
- if (isWordRunningResult.IsError == true)
- {
- // NOTE: realistically, we might not want to create a modal message box during an async function.
- MessageBox.Show("Sorry, we cannot detect if Word is running.\n\nThis feature is currently unavailable.");
- }
- var wordIsRunning = isWordRunningResult.Value!;
- //
- if (wordIsRunning == true)
- {
- MessageBox.Show(Functions.WORD_RUNNING_MESSAGE);
- return false;
- }
-
- // if Word is not running, it's safe to proceed
- return true;
- }
-
- [InternalFunction("basicWordRibbon")]
- public static async Task> ToggleBasicWordRibbonAsync(FunctionArgs args)
- {
- // if we have a "value" property, this is a multi-segmented button and we should use "value" instead of "state"
- bool on;
- if (args.Arguments.Keys.Contains("value"))
- {
- on = (args["value"] == "on");
- }
- else if (args.Arguments.Keys.Contains("state"))
- {
- on = (args["state"] == "on");
- }
- else
- {
- System.Diagnostics.Debug.Assert(false, "Function 'basicWordRibbon' did not receive a new state");
- on = false;
- }
-
- if (Functions.IsSafeToModifyRibbonFile_WarnUser() == false)
- {
- // Word is running, so we are choosing not to execute this function
- return MorphicResult.ErrorResult();
- }
-
- if (on == true)
- {
- var enableRibbonResult = Morphic.Integrations.Office.WordRibbon.EnableBasicSimplifyRibbon();
- return enableRibbonResult.IsSuccess ? MorphicResult.OkResult() : MorphicResult.ErrorResult();
- }
- else
- {
- var disableRibbonResult = Morphic.Integrations.Office.WordRibbon.DisableBasicSimplifyRibbon();
- return disableRibbonResult.IsSuccess ? MorphicResult.OkResult() : MorphicResult.ErrorResult();
- }
- }
-
- [InternalFunction("essentialsWordRibbon")]
- public static async Task> ToggleEssentialsWordRibbonAsync(FunctionArgs args)
- {
- // if we have a "value" property, this is a multi-segmented button and we should use "value" instead of "state"
- bool on;
- if (args.Arguments.Keys.Contains("value"))
- {
- on = (args["value"] == "on");
- }
- else if (args.Arguments.Keys.Contains("state"))
- {
- on = (args["state"] == "on");
- }
- else
- {
- System.Diagnostics.Debug.Assert(false, "Function 'essentialsWordRibbon' did not receive a new state");
- on = false;
- }
-
- if (Functions.IsSafeToModifyRibbonFile_WarnUser() == false)
- {
- // Word is running, so we are choosing not to execute this function
- return MorphicResult.ErrorResult();
- }
-
- if (on == true)
- {
- var enableRibbonResult = Morphic.Integrations.Office.WordRibbon.EnableEssentialsSimplifyRibbon();
- return enableRibbonResult.IsSuccess ? MorphicResult.OkResult() : MorphicResult.ErrorResult();
- }
- else
- {
- var disableRibbonResult = Morphic.Integrations.Office.WordRibbon.DisableEssentialsSimplifyRibbon();
- return disableRibbonResult.IsSuccess ? MorphicResult.OkResult() : MorphicResult.ErrorResult();
- }
- }
-
- //
-
- [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Windows API naming")]
- [SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Windows API naming")]
- private static class WinApi
- {
- //public const int APPCOMMAND_VOLUME_MUTE = 8;
- 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 2f38824c..00000000
--- a/Morphic.Client/Bar/Data/Actions/InternalFunctions.cs
+++ /dev/null
@@ -1,190 +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 is not null)
- {
- try
- {
- attr.SetFunction((InternalFunction)method.CreateDelegate(typeof(InternalFunction)));
- }
- catch
- {
- System.Diagnostics.Debug.Assert(false, "Could not wire up delegate to internal function");
- }
- yield return attr;
- }
- }
- }
-
- ///
- /// Invokes a built-in function.
- ///
- /// The function name.
- /// The parameters.
- ///
- public Task> InvokeFunctionAsync(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 is 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 4d6d9d39..00000000
--- a/Morphic.Client/Bar/Data/Actions/SettingAction.cs
+++ /dev/null
@@ -1,94 +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 is null && !string.IsNullOrEmpty(source))
- {
- setting = this.Solutions.GetSetting(source);
- // OBSERVATION: we do not await on this call; we may want to do so
- setting.SetValueAsync(toggleState);
- }
- else
- {
- setting = this.Setting;
- }
-
- if (setting is null)
- {
- MorphicResult okResult = MorphicResult.OkResult();
- return Task.FromResult(okResult);
- }
-
- 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);
- }
-
- MorphicResult errorResult = MorphicResult.ErrorResult();
- return Task.FromResult(errorResult);
- }
-
- public async Task CanExecute(string id)
- {
- bool canExecute = true;
-
- if (Setting?.Range is not null)
- {
- var range = Setting.Range;
- var idRequiresCountRefresh = Setting.Id == "zoom";
-
- var min = await range.GetMin(0, idRequiresCountRefresh);
- var max = await range.GetMax(0, idRequiresCountRefresh) - 1;
-
- var currentValue = (int)(Setting.CurrentValue ?? 0);
-
- if (id == "inc" && currentValue >= max)
- {
- canExecute = false;
- }
- else if (id == "dec" && currentValue <= min)
- {
- canExecute = false;
- }
- }
-
- return canExecute;
- }
-
- 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 16993c0f..00000000
--- a/Morphic.Client/Bar/Data/Actions/WebAction.cs
+++ /dev/null
@@ -1,93 +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;
- case "skype":
- // allowed for now, but in the future we may want to launch Skype directly and handle this information seperately
- 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 is not 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 is not null)
- {
- Process? process = Process.Start(new ProcessStartInfo()
- {
- FileName = this.ResolveString(this.Uri?.ToString(), source),
- UseShellExecute = true
- });
- success = process is not null;
- }
-
- MorphicResult result = success ? MorphicResult.OkResult() : MorphicResult.ErrorResult();
- return Task.FromResult(result);
- }
- }
-}
diff --git a/Morphic.Client/Bar/Data/BarButton.cs b/Morphic.Client/Bar/Data/BarButton.cs
deleted file mode 100644
index 3b800f86..00000000
--- a/Morphic.Client/Bar/Data/BarButton.cs
+++ /dev/null
@@ -1,268 +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.Controls;
- using System.Windows.Media;
- using System.Xml;
- 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 is null || uri.IsFile)
- {
- localPath = BarImages.GetBarIconFile(this.imageValue);
- if (localPath is null)
- {
- uri = new Uri(this.Bar.FrontEndUri, this.FrontendImagePath);
- }
- }
-
- if (localPath is not null)
- {
- this.ImagePath = localPath;
- }
- else if (uri is not null)
- {
- // Download later.
- this.RemoteImage = uri;
- }
- }
- }
- }
-
- ///
- /// The image to use.
- ///
- public ImageSource? ImageSource
- {
- get => this.imageSource;
- set
- {
- this.imageSource = value;
- this.OnPropertyChanged();
- }
- }
-
- private Canvas? _xamlContent;
- public Canvas? XamlContent
- {
- get => _xamlContent;
- set
- {
- _xamlContent = 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 is not 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))
- {
- if (Path.GetExtension(imagePath)?.ToLowerInvariant() == ".xaml")
- {
- var xamlFileStream = new FileStream(this.ImagePath, FileMode.Open, FileAccess.Read);
- // TODO: when we move to .NET 5, set useRestrictiveXamlReader to true
- var xamlAsCanvas = (Canvas)System.Windows.Markup.XamlReader.Load(new XmlTextReader(xamlFileStream) /*, true */);
- this.XamlContent = xamlAsCanvas;
-
- success = true;
- }
- else
- {
- this.ImageSource = BarImages.CreateImageSource(this.ImagePath);
-
- success = this.ImageValue is not null;
- }
- }
-
- // Fallback to a default image.
- if (!success)
- {
- ImageSource? source = this.Action?.DefaultImageSource;
- if (source is not null)
- {
- this.ImageSource = source;
- success = true;
- }
- else
- {
- Uri? defaultUri = this.Action?.DefaultImageUri;
- if (defaultUri is not 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 is not 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 954ca86c..00000000
--- a/Morphic.Client/Bar/Data/BarData.cs
+++ /dev/null
@@ -1,520 +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.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);
-
- // OBSERVATION: the usage of the "Overflow" bool in sorting and the general nomenclature of "Overflow" and "Priority" leave room for bugs;
- // at first glance, "Overflow" should be a filter for secondary items rather than a descending sort order and
- // "Priority" (which is the index value) seems like it should ordered in ascending order;
- // we should rethink our terminology and refactor the logic here and the variable naming and logic related to these variables
- //
- ///
- /// Gets the items for the additional buttons.
- ///
- public IEnumerable SecondaryItems => this.AllItems.Where(this.IsSecondaryItem)
- .OrderByDescending(item => item.Overflow)
- .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);
-
- // OBSERVATION: we need a better way to determine if this is the basic bar or another bar
- var isBasicBar = (barSource == AppPaths.GetConfigFile("basic-bar.json5", true));
- //
- if (isBasicBar == 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)
- {
- BarItem extraBarItem;
- var extraBarItemShouldBeAdded = false;
-
- switch (extraItemData.type)
- {
- case "link":
- {
- extraBarItem = new BarButton(defaultBar);
- extraBarItem.ToolTipHeader = extraItemData.tooltipHeader;
- extraBarItem.ToolTip = extraItemData.tooltipText;
- extraBarItem.Text = extraItemData.label ?? "";
- //
- extraBarItem.Action = new Morphic.Client.Bar.Data.Actions.WebAction();
- ((Morphic.Client.Bar.Data.Actions.WebAction)extraBarItem.Action!).UrlString = extraItemData.url ?? "";
- extraBarItemShouldBeAdded = true;
- }
- break;
- case "action":
- {
- extraBarItem = new BarButton(defaultBar);
- extraBarItem.ToolTipHeader = extraItemData.tooltipHeader;
- extraBarItem.ToolTip = extraItemData.tooltipText;
- extraBarItem.Text = extraItemData.label ?? "";
- //
- 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!;
- extraBarItemShouldBeAdded = true;
- }
- break;
- case "control":
- {
- extraBarItem = new BarMultiButton(defaultBar);
- extraBarItem.ToolTipHeader = extraItemData.tooltipHeader;
- extraBarItem.ToolTip = extraItemData.tooltipText;
- //
- switch (extraItemData.feature)
- {
- case "usbopeneject":
- {
- extraBarItem.Text = extraItemData.label ?? "USB Drives (All)";
- //
- var openAllUsbAction = new Morphic.Client.Bar.Data.Actions.InternalAction();
- openAllUsbAction.TelemetryEventName = "morphicBarExtraItem";
- openAllUsbAction.FunctionName = "openAllUsbDrives";
- var openButton = new BarMultiButton.ButtonInfo
- {
- Text = "Open",
- Action = openAllUsbAction,
- TelemetryCategory = "morphicBarExtraItem",
- Tooltip = "Open All USB Drives",
- Value = "openallusb"
- };
- //
- var ejectAllUsbAction = new Morphic.Client.Bar.Data.Actions.InternalAction();
- ejectAllUsbAction.TelemetryEventName = "morphicBarExtraItem";
- ejectAllUsbAction.FunctionName = "ejectAllUsbDrives";
- var ejectButton = new BarMultiButton.ButtonInfo
- {
- Text = "Eject",
- Action = ejectAllUsbAction,
- TelemetryCategory = "morphicBarExtraItem",
- Tooltip = "Eject All USB Drives",
- Value = "ejectallusb"
- };
- //
- ((BarMultiButton)extraBarItem).Buttons = new Dictionary
- {
- { "open", openButton },
- { "eject", ejectButton }
- };
- //
- extraBarItemShouldBeAdded = true;
- }
- break;
- case "volume":
- {
- extraBarItem.Text = extraItemData.label ?? "Volume";
- //
- var volumeUpAction = new Morphic.Client.Bar.Data.Actions.InternalAction();
- volumeUpAction.TelemetryEventName = "volumeUp";
- volumeUpAction.FunctionName = "volumeUp";
- var volumeUpButton = new BarMultiButton.ButtonInfo
- {
- Text = "+",
- Action = volumeUpAction,
- TelemetryCategory = "volumeUp",
- Tooltip = "Increases the volume|Makes all sounds louder.|Volume cannot go louder",
- Value = "volumeUp"
- };
- //
- var volumeDownAction = new Morphic.Client.Bar.Data.Actions.InternalAction();
- volumeDownAction.TelemetryEventName = "volumeDown";
- volumeDownAction.FunctionName = "volumeDown";
- var volumeDownButton = new BarMultiButton.ButtonInfo
- {
- Text = "-",
- Action = volumeDownAction,
- TelemetryCategory = "volumeDown",
- Tooltip = "Decreases the volume|Makes all sounds quieter.|Volume cannot go quieter",
- Value = "volumeDown"
- };
- //
- var volumeMuteAction = new Morphic.Client.Bar.Data.Actions.InternalAction();
- volumeMuteAction.TelemetryEventName = "volumeMute";
- volumeMuteAction.FunctionName = "volumeMute";
- var volumeMuteButton = new BarMultiButton.ButtonInfo
- {
- Text = "Mute",
- Action = volumeMuteAction,
- TelemetryCategory = "volumeMute",
- Toggle = true,
- Tooltip = "Mutes all sounds from your computer|Mutes your speakers - but does NOT mute your microphone.",
- Value = "volumeMute"
- };
- //
- ((BarMultiButton)extraBarItem).Buttons = new Dictionary
- {
- { "volumeUp", volumeUpButton },
- { "volumeDown", volumeDownButton },
- { "volumeMute", volumeMuteButton }
- };
- ((BarMultiButton)extraBarItem).Menu = new Dictionary()
- {
- { "setting", "sound" },
- { "learn", "volmute" },
- { "demo", "volmute" }
- };
- ((BarMultiButton)extraBarItem).AutoSize = true;
- //
- extraBarItemShouldBeAdded = true;
- }
- break;
- case "wordsimplify":
- {
- extraBarItem.Text = extraItemData.label ?? "Word Simplify";
- //
- var basicWordRibbonAction = new Morphic.Client.Bar.Data.Actions.InternalAction();
- basicWordRibbonAction.TelemetryEventName = "morphicBarExtraItem"; // basicWordRibbonToggle
- basicWordRibbonAction.FunctionName = "basicWordRibbon";
- var basicWordRibbonButton = new BarMultiButton.ButtonInfo
- {
- Text = "Basic",
- Action = basicWordRibbonAction,
- TelemetryCategory = "morphicBarExtraItem",
- Toggle = true,
- Tooltip = "Adds a new 'Basic Items' ribbon to Word|Gives you a new simpler ribbon with just the basic items on it.",
- Value = "basicwordribbon"
- };
- //
- var essentialsWordRibbonAction = new Morphic.Client.Bar.Data.Actions.InternalAction();
- essentialsWordRibbonAction.TelemetryEventName = "morphicBarExtraItem"; // essentialsWordRibbonToggle
- essentialsWordRibbonAction.FunctionName = "essentialsWordRibbon";
- var essentialsWordRibbonButton = new BarMultiButton.ButtonInfo
- {
- Text = "Essentials",
- Action = essentialsWordRibbonAction,
- TelemetryCategory = "morphicBarExtraItem",
- Toggle = true,
- Tooltip = "Adds a new 'Essential Items' ribbon to Word|Gives you a new ribbon with essential items gathered from all other ribbons.",
- Value = "essentialswordribbon"
- };
- //
- ((BarMultiButton)extraBarItem).Buttons = new Dictionary
- {
- { "basic", basicWordRibbonButton },
- { "essentials", essentialsWordRibbonButton }
- };
- ((BarMultiButton)extraBarItem).Menu = new Dictionary()
- {
- { "learn", "wordsimplify" },
- { "demo", "wordsimplify" }
- };
- ((BarMultiButton)extraBarItem).AutoSize = true;
- //
- // NOTE: we shouldonly show this item if Word is actually installed
- extraBarItemShouldBeAdded = Morphic.Integrations.Office.WordRibbon.IsOfficeInstalled();
- }
- break;
- default:
- extraBarItem.Text = extraItemData.label ?? "";
- // NOTE: we don't know what this button is, so do not show it
- extraBarItemShouldBeAdded = false;
- break;
- }
- }
- break;
- default:
- // unknown type; this should be an impossible code path
- throw new NotImplementedException();
- }
- //extraBarItem.ColorValue = "#00FF00";
- //
- if (extraBarItemShouldBeAdded == true)
- {
- defaultBar?.AllItems.Add(extraBarItem);
- }
- }
-
- // add a spacer entry
- BarButton spacerBarItem = new BarButton(defaultBar);
- spacerBarItem.ToolTipHeader = "";
- spacerBarItem.ToolTip = "";
- spacerBarItem.Text = "";
- spacerBarItem.ColorValue = "#FFFFFF";
- //
- defaultBar?.AllItems.Add(spacerBarItem);
- }
- }
- }
- else
- {
- defaultBar = null;
- }
-
- App.Current.Logger.LogInformation("Loading bar from {source}", barSource);
-
- BarData? bar;
-
- using (TextReader reader = content is 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);
-
- 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 87966186..00000000
--- a/Morphic.Client/Bar/Data/BarItem.cs
+++ /dev/null
@@ -1,236 +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; }
-
- // NOTE: this property has been deprecated, as the determination of if an item should be on the bar (and not in the overflow drawer) is now based on position and available room
- //
- /////
- ///// 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; }
-
- ///
- /// 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))
- {
- try
- {
- if (ColorConverter.ConvertFromString(value) is Color color)
- {
- this.Color = color;
- }
- }
- catch (FormatException)
- {
- // invalid value
- // NOTE: we should log this error
- }
- }
- }
- 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 da98bb02..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 is null)
- {
- this.Hover.Background = this.LightenColor(this.Background ?? Colors.Transparent, 0.25f);
- }
-
- if (force || this.Active.Background is null)
- {
- this.Active.Background = this.LightenColor(this.Background ?? Colors.Transparent, 0.5f);
- }
-
- if (force || this.Focus.Background is 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 is 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 8b79e597..00000000
--- a/Morphic.Client/Bar/Data/BarJson.cs
+++ /dev/null
@@ -1,382 +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 is null)
- {
- if (baseType.GetCustomAttributes().Any())
- {
- // The type has already been resolved at the property.
- type = baseType;
- }
- else
- {
- System.Diagnostics.Debug.Assert(false, $"Unable to get type of {baseType.Name} from '{this.typeFieldName} = ${name}'.");
- return null;
- }
- }
-
- List