mirror of
https://github.com/microsoft/PowerToys.git
synced 2024-11-27 14:59:16 +08:00
[New Module] Workspaces (#34324)
* spell checker
* Adding OOBE Projects page
* changed the default hotkey
* module interface
* rename projects editor
* bug report tool
* installer
* gpo
* exit event constant
* extend search for projects by search over the containing apps' names
* [Projects] fix grammatical issue #43 (1 app - many apps)
* [Projects] Editor: Main page: fix layout if there are many apps, launch button not disappearing on the right side
* dsc
* github
* pipeline
* guid prefix
* [Projects] fixing general settings gpo handling in runner + minor changes
* arm build fix
* Do not allow saving project if name or applist is empty. Also minor UI changes
* version
* editor version
* spellcheck
* editor dll signing
* update projects names to filter them out
* shortcut saving fix
* [Projects] Editor: brining the highlighted app's icon into the foreground. + minor UI fixes
* spell checker
* spellcheck
* [Projects] re-implementing icon size calculation to have similar sized icons for every app.
* [projects] Adding info message for cases: there are no projects or no results for the search
* [Projects] Adding Edit button to the popup. + minor changes
* [Projects] Making popup having rounded corners
* changed "no projects" text color and position
* remove opening the first proj
* fix placing windows of the same app in the project
* [Projects] bringing back the breadcrumb on the editor page. Make it clickable.
* [Projects] optimizing click handlers
* [Projects] Removing not selected apps on save
* moved on thread executor to common
* moved display utils
* added convert rect
* unsigned monitor number
* set awareness
* app placement
* [Projects] Re-implementing preview drawing - one common image
* [Projects] fix boundary calculation, use DPI aware values
* fix launching with command line args
* Fix ARM64 CI build
* launch packaged apps using names when possible
* spell-check
* update packaged apps path
* projects editor single instance
* [Projects] Add Select all checkbox, Delete selected button
* Add Checkbox for per monitor selection
* modifying highlight in preview
* spell checker
* logs
* exclude help windows
https://github.com/JaneaSystems/PowerToys-DevProjects/issues/49
* Add intermediate step to project creation
* minor bugfix
* mutex fix
* modifying highlight for minimized apps
* Fixing bug: re-draw the preview on app deletion in the editor
* Adding helper class for getting the right bounds for screens
* spell checker
* spell checker
* Minor fixes in the capture dialog
* get dpi unaware screen bounds
* refactoring: added utils
* changed window filter
https://github.com/JaneaSystems/PowerToys-DevProjects/issues/2
* clean up
* refactoring
* projects common lib
* localizable default project prefix
* launcher resources
* clean up
* change snapshot project saving
https://github.com/JaneaSystems/PowerToys-DevProjects/issues/14
* changed project data
https://github.com/JaneaSystems/PowerToys-DevProjects/issues/14
* changed project creation save-cancel handles
https://github.com/JaneaSystems/PowerToys-DevProjects/issues/14
* spell-check
* Remove checkboxes, delete feature
* remove unused from the project
* get command line args in the snapshot
* minimized settings snap fix
* set window property after launching
* FZ: ignore projects launched windows
* Implementing major new features: remove button, position manipulation, arguments, admin, minimized, maximized
* modifying colors
* launcher project filters
* clean up
* Hide Admin checkbox
* hide WIP
* spell-check
* Revert "Hide Admin checkbox"
This reverts commit 3036df9d7f
.
* get app elevated property
* Implementing Launch and Edit feature
* fixing: update of listed projects on the main page after hitting save in editor
* Fix for packaged app's icons
* fixing scroll speed issue
* change scroll speed to 15
* launch elevated apps
* minor fixes
* minor fix
* enhancing shortcut handling
* can-launch-elevated check
* projects module interface telemetry
* Implementing store of setting "order by".
* minor string correction
* moved projects data parsing
* telemetry
* add move apps checkbox
* notification about elevated apps
* restart unelevated
* move existing windows
* keep opened windows at the same positions
* handle powertoys settings
* use common theme
* fix corrupted data: project id and monitor id
* project launch on "launch and edit"
* clean up
* show screen numbers instead of monitor names
* launcher error messages
* fix default shortcut
* Adding launch button to projects settings, dashboard and flyout
* Adding new app which is launched when launching a project. It shows the status of the launch process
* spell checker
* Renaming Projects to App Layouts. Replacing only string values, not the variable names
* Re-ordering modules after Renaming Projects + spell checker
* setting window size according to the screen (making it bigger)
* commenting out feature "move apps if exist"
* spell checker
* Add ProjectsLauncherUI to signing
* opening apps in minimized state which are placed on a monitor, which is not found at the moment of launching
* consistent file name
* removed unused sln
* telemetry: create event
* WindowPosition comparison
* telemetry: edit event
* fix muted Launch as admin checkbox
* telemetry: delete event
* updated Edit telemetry event
* added invoke point to launcher args
* added utils
* parse invoke point
* replaced tuple with struct
* telemetry: launch event
* MonitorRect comparison
* resources
* rename: folders
* remove outdated
* rename: window property
* rename: files and folders
* rename: common data structures
* rename: telemetry namespace
* rename: workspaces data
* rename ProjectsLib -> WorkspacesLib
* rename: gpo
* rename: settings
* rename: launcher UI
* rename: other
* rename: pt run
* rename: fz
* rename: module interface
* rename: icon
* rename: snapshot tool
* rename: editor
* rename: common files
* rename: launcher
* rename: editor resources
* fix empty file crash
* rename: json
* rename: module interface
* fix custom actions build
* added launch editor event constant
* xaml formatting
* Add missing method defition to interop::Constants idl
Remove Any CPU config
* more .sln cleanup
* [Run][PowerToys] Fix Workspaces utility (#34336)
polished workspaces utility
* build fix - align CppWinRT version
* address PR comment: fix isdigit
* indentation
* address PR comment: rename function
* address PR comment: changed version for workspaces and revision
* added supported version definition
* addressPR comment: use BringToForeground
* address PR comments: updated projects
* address PR comment: uncomment gpo in settings
* address PR comment: rename oobe view
* update OOBE image with current module name
* moved AppUtils
* launching with AppUserModel.ID
* fixed module order in settings
* fix xaml formatting
* [Workspaces] Close launcher if there are failed launches. Plus adding new spinner gif
* fix topmost LauncherUI
* clean up
* UI closing
* BugReportTool - omit cmd arg data
* Delete icon on workspace removal
* Adding cancellation to launcher UI.
* reordered launching
* fix terminating UI
* Removing old shortcut on workspace renaming
* Sentence case labels
* get process path without waiting
* comment out unused
* remove unused argument
* logs
* New icon
* fix launch and edit for the new project
* fix launch and edit: save new project
* Update exe icons
---------
Co-authored-by: donlaci <laszlo@janeasystems.com>
Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
Co-authored-by: Stefan Markovic <stefan@janeasystems.com>
Co-authored-by: Davide Giacometti <davide.giacometti@outlook.it>
Co-authored-by: Niels Laute <niels.laute@live.nl>
This commit is contained in:
parent
2a8e211cfd
commit
579619952d
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -76,6 +76,7 @@ body:
|
|||||||
- System tray interaction
|
- System tray interaction
|
||||||
- TextExtractor
|
- TextExtractor
|
||||||
- Video Conference Mute
|
- Video Conference Mute
|
||||||
|
- Workspaces
|
||||||
- Welcome / PowerToys Tour window
|
- Welcome / PowerToys Tour window
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
1
.github/ISSUE_TEMPLATE/translation_issue.yml
vendored
1
.github/ISSUE_TEMPLATE/translation_issue.yml
vendored
@ -50,6 +50,7 @@ body:
|
|||||||
- System tray interaction
|
- System tray interaction
|
||||||
- TextExtractor
|
- TextExtractor
|
||||||
- Video Conference Mute
|
- Video Conference Mute
|
||||||
|
- Workspaces
|
||||||
- Welcome / PowerToys Tour window
|
- Welcome / PowerToys Tour window
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
16
.github/actions/spell-check/expect.txt
vendored
16
.github/actions/spell-check/expect.txt
vendored
@ -56,11 +56,14 @@ APPBARDATA
|
|||||||
appdata
|
appdata
|
||||||
APPEXECLINK
|
APPEXECLINK
|
||||||
Appium
|
Appium
|
||||||
|
applayout
|
||||||
Applicationcan
|
Applicationcan
|
||||||
|
APPLICATIONFRAMEHOST
|
||||||
appmanifest
|
appmanifest
|
||||||
APPNAME
|
APPNAME
|
||||||
appref
|
appref
|
||||||
appsettings
|
appsettings
|
||||||
|
appsfolder
|
||||||
appwindow
|
appwindow
|
||||||
appwiz
|
appwiz
|
||||||
APSTUDIO
|
APSTUDIO
|
||||||
@ -240,6 +243,7 @@ CONTEXTMENUHANDLER
|
|||||||
CONTROLL
|
CONTROLL
|
||||||
CONTROLPARENT
|
CONTROLPARENT
|
||||||
copiedcolorrepresentation
|
copiedcolorrepresentation
|
||||||
|
COREWINDOW
|
||||||
cotaskmem
|
cotaskmem
|
||||||
COULDNOT
|
COULDNOT
|
||||||
countof
|
countof
|
||||||
@ -832,6 +836,7 @@ lpwcx
|
|||||||
lpwndpl
|
lpwndpl
|
||||||
LReader
|
LReader
|
||||||
LRESULT
|
LRESULT
|
||||||
|
LSTATUS
|
||||||
lstrcmp
|
lstrcmp
|
||||||
lstrcmpi
|
lstrcmpi
|
||||||
lstrlen
|
lstrlen
|
||||||
@ -1205,6 +1210,8 @@ projectname
|
|||||||
PROPBAG
|
PROPBAG
|
||||||
PROPERTYKEY
|
PROPERTYKEY
|
||||||
propkey
|
propkey
|
||||||
|
propsys
|
||||||
|
PROPVARIANT
|
||||||
propvarutil
|
propvarutil
|
||||||
prvpane
|
prvpane
|
||||||
psapi
|
psapi
|
||||||
@ -1370,6 +1377,7 @@ sddl
|
|||||||
SDKDDK
|
SDKDDK
|
||||||
sdns
|
sdns
|
||||||
searchterm
|
searchterm
|
||||||
|
SEARCHUI
|
||||||
secpol
|
secpol
|
||||||
SENDCHANGE
|
SENDCHANGE
|
||||||
sendinput
|
sendinput
|
||||||
@ -1552,7 +1560,9 @@ SYSKEYUP
|
|||||||
SYSLIB
|
SYSLIB
|
||||||
SYSMENU
|
SYSMENU
|
||||||
SYSTEMAPPS
|
SYSTEMAPPS
|
||||||
|
systemsettings
|
||||||
SYSTEMTIME
|
SYSTEMTIME
|
||||||
|
SYSTEMWOW
|
||||||
tapp
|
tapp
|
||||||
TApplication
|
TApplication
|
||||||
TApplied
|
TApplied
|
||||||
@ -1664,9 +1674,11 @@ urlmon
|
|||||||
Usb
|
Usb
|
||||||
USEDEFAULT
|
USEDEFAULT
|
||||||
USEFILEATTRIBUTES
|
USEFILEATTRIBUTES
|
||||||
|
USEPOSITION
|
||||||
USERDATA
|
USERDATA
|
||||||
Userenv
|
Userenv
|
||||||
USESHOWWINDOW
|
USESHOWWINDOW
|
||||||
|
USESIZE
|
||||||
USESTDHANDLES
|
USESTDHANDLES
|
||||||
USRDLL
|
USRDLL
|
||||||
UType
|
UType
|
||||||
@ -1734,6 +1746,7 @@ vswhere
|
|||||||
Vtbl
|
Vtbl
|
||||||
WANTPALM
|
WANTPALM
|
||||||
wbem
|
wbem
|
||||||
|
Wbemidl
|
||||||
wbemuuid
|
wbemuuid
|
||||||
WBounds
|
WBounds
|
||||||
Wca
|
Wca
|
||||||
@ -1821,6 +1834,9 @@ WNDCLASSEXW
|
|||||||
WNDCLASSW
|
WNDCLASSW
|
||||||
WNDPROC
|
WNDPROC
|
||||||
workarounds
|
workarounds
|
||||||
|
WORKSPACESEDITOR
|
||||||
|
WORKSPACESLAUNCHER
|
||||||
|
WORKSPACESSNAPSHOTTOOL
|
||||||
wox
|
wox
|
||||||
wparam
|
wparam
|
||||||
wpf
|
wpf
|
||||||
|
@ -189,6 +189,14 @@
|
|||||||
"WinUI3Apps\\PowerToys.PowerRenameContextMenu.dll",
|
"WinUI3Apps\\PowerToys.PowerRenameContextMenu.dll",
|
||||||
"WinUI3Apps\\PowerRenameContextMenuPackage.msix",
|
"WinUI3Apps\\PowerRenameContextMenuPackage.msix",
|
||||||
|
|
||||||
|
"PowerToys.WorkspacesSnapshotTool.exe",
|
||||||
|
"PowerToys.WorkspacesLauncher.exe",
|
||||||
|
"PowerToys.WorkspacesEditor.exe",
|
||||||
|
"PowerToys.WorkspacesEditor.dll",
|
||||||
|
"PowerToys.WorkspacesLauncherUI.exe",
|
||||||
|
"PowerToys.WorkspacesLauncherUI.dll",
|
||||||
|
"PowerToys.WorkspacesModuleInterface.dll",
|
||||||
|
|
||||||
"WinUI3Apps\\PowerToys.RegistryPreviewExt.dll",
|
"WinUI3Apps\\PowerToys.RegistryPreviewExt.dll",
|
||||||
"WinUI3Apps\\PowerToys.RegistryPreviewUILib.dll",
|
"WinUI3Apps\\PowerToys.RegistryPreviewUILib.dll",
|
||||||
"WinUI3Apps\\PowerToys.RegistryPreview.dll",
|
"WinUI3Apps\\PowerToys.RegistryPreview.dll",
|
||||||
|
119
PowerToys.sln
119
PowerToys.sln
@ -171,14 +171,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
src\.editorconfig = src\.editorconfig
|
src\.editorconfig = src\.editorconfig
|
||||||
.vsconfig = .vsconfig
|
.vsconfig = .vsconfig
|
||||||
|
src\Common.Dotnet.CsWinRT.props = src\Common.Dotnet.CsWinRT.props
|
||||||
|
src\Common.SelfContained.props = src\Common.SelfContained.props
|
||||||
Cpp.Build.props = Cpp.Build.props
|
Cpp.Build.props = Cpp.Build.props
|
||||||
Directory.Build.props = Directory.Build.props
|
Directory.Build.props = Directory.Build.props
|
||||||
Directory.Build.targets = Directory.Build.targets
|
Directory.Build.targets = Directory.Build.targets
|
||||||
Directory.Packages.props = Directory.Packages.props
|
Directory.Packages.props = Directory.Packages.props
|
||||||
Solution.props = Solution.props
|
Solution.props = Solution.props
|
||||||
src\Version.props = src\Version.props
|
src\Version.props = src\Version.props
|
||||||
src\Common.SelfContained.props = src\Common.SelfContained.props
|
|
||||||
src\Common.Dotnet.CsWinRT.props = src\Common.Dotnet.CsWinRT.props
|
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Settings.UI.Library", "src\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj", "{B1BCC8C6-46B5-4BFA-8F22-20F32D99EC6A}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Settings.UI.Library", "src\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj", "{B1BCC8C6-46B5-4BFA-8F22-20F32D99EC6A}"
|
||||||
@ -277,6 +277,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643
|
|||||||
src\common\utils\modulesRegistry.h = src\common\utils\modulesRegistry.h
|
src\common\utils\modulesRegistry.h = src\common\utils\modulesRegistry.h
|
||||||
src\common\utils\MsiUtils.h = src\common\utils\MsiUtils.h
|
src\common\utils\MsiUtils.h = src\common\utils\MsiUtils.h
|
||||||
src\common\utils\MsWindowsSettings.h = src\common\utils\MsWindowsSettings.h
|
src\common\utils\MsWindowsSettings.h = src\common\utils\MsWindowsSettings.h
|
||||||
|
src\common\utils\OnThreadExecutor.h = src\common\utils\OnThreadExecutor.h
|
||||||
src\common\utils\os-detect.h = src\common\utils\os-detect.h
|
src\common\utils\os-detect.h = src\common\utils\os-detect.h
|
||||||
src\common\utils\package.h = src\common\utils\package.h
|
src\common\utils\package.h = src\common\utils\package.h
|
||||||
src\common\utils\ProcessWaiter.h = src\common\utils\ProcessWaiter.h
|
src\common\utils\ProcessWaiter.h = src\common\utils\ProcessWaiter.h
|
||||||
@ -456,7 +457,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FileLocksmithExt", "src\mod
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileLocksmithUI", "src\modules\FileLocksmith\FileLocksmithUI\FileLocksmithUI.csproj", "{E69B044A-2F8A-45AA-AD0B-256C59421807}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileLocksmithUI", "src\modules\FileLocksmith\FileLocksmithUI\FileLocksmithUI.csproj", "{E69B044A-2F8A-45AA-AD0B-256C59421807}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FileLocksmithLibInterop", "src\modules\FileLocksmith\FileLocksmithLibInterop\FileLocksmithLibInterop.vcxproj", "{C604B37E-9D0E-4484-8778-E8B31B0E1B3A}"
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToys.FileLocksmithLib.Interop", "src\modules\FileLocksmith\FileLocksmithLibInterop\FileLocksmithLibInterop.vcxproj", "{C604B37E-9D0E-4484-8778-E8B31B0E1B3A}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GPOWrapper", "src\common\GPOWrapper\GPOWrapper.vcxproj", "{E599C30B-9DC8-4E5A-BF27-93D4CCEDE788}"
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GPOWrapper", "src\common\GPOWrapper\GPOWrapper.vcxproj", "{E599C30B-9DC8-4E5A-BF27-93D4CCEDE788}"
|
||||||
EndProject
|
EndProject
|
||||||
@ -583,6 +584,37 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerToys.Settings.DSC.Sche
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToys.Interop", "src\common\interop\PowerToys.Interop.vcxproj", "{F055103B-F80B-4D0C-BF48-057C55620033}"
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToys.Interop", "src\common\interop\PowerToys.Interop.vcxproj", "{F055103B-F80B-4D0C-BF48-057C55620033}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Workspaces", "Workspaces", "{A2221D7E-55E7-4BEA-90D1-4F162D670BBF}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workspaces-common", "workspaces-common", "{BE126CBB-AE12-406A-9837-A05ACFCA57A7}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
src\modules\Workspaces\workspaces-common\GuidUtils.h = src\modules\Workspaces\workspaces-common\GuidUtils.h
|
||||||
|
src\modules\Workspaces\workspaces-common\InvokePoint.h = src\modules\Workspaces\workspaces-common\InvokePoint.h
|
||||||
|
src\modules\Workspaces\workspaces-common\MonitorEnumerator.h = src\modules\Workspaces\workspaces-common\MonitorEnumerator.h
|
||||||
|
src\modules\Workspaces\workspaces-common\MonitorUtils.h = src\modules\Workspaces\workspaces-common\MonitorUtils.h
|
||||||
|
src\modules\Workspaces\workspaces-common\VirtualDesktop.h = src\modules\Workspaces\workspaces-common\VirtualDesktop.h
|
||||||
|
src\modules\Workspaces\workspaces-common\WindowEnumerator.h = src\modules\Workspaces\workspaces-common\WindowEnumerator.h
|
||||||
|
src\modules\Workspaces\workspaces-common\WindowFilter.h = src\modules\Workspaces\workspaces-common\WindowFilter.h
|
||||||
|
src\modules\Workspaces\workspaces-common\WindowUtils.h = src\modules\Workspaces\workspaces-common\WindowUtils.h
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WindowProperties", "WindowProperties", "{14CB58B7-D280-4A7A-95DE-4B2DF14EA000}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
src\modules\Workspaces\WindowProperties\WorkspacesWindowPropertyUtils.h = src\modules\Workspaces\WindowProperties\WorkspacesWindowPropertyUtils.h
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesLib", "src\modules\Workspaces\WorkspacesLib\WorkspacesLib.vcxproj", "{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkspacesLauncherUI", "src\modules\Workspaces\WorkspacesLauncherUI\WorkspacesLauncherUI.csproj", "{9C53CC25-0623-4569-95BC-B05410675EE3}"
|
||||||
|
EndProject
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesModuleInterface", "src\modules\Workspaces\WorkspacesModuleInterface\WorkspacesModuleInterface.vcxproj", "{45285DF2-9742-4ECA-9AC9-58951FC26489}"
|
||||||
|
EndProject
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesSnapshotTool", "src\modules\Workspaces\WorkspacesSnapshotTool\WorkspacesSnapshotTool.vcxproj", "{3D63307B-9D27-44FD-B033-B26F39245B85}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkspacesEditor", "src\modules\Workspaces\WorkspacesEditor\WorkspacesEditor.csproj", "{367D7543-7DBA-4381-99F1-BF6142A996C4}"
|
||||||
|
EndProject
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesLauncher", "src\modules\Workspaces\WorkspacesLauncher\WorkspacesLauncher.vcxproj", "{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|ARM64 = Debug|ARM64
|
Debug|ARM64 = Debug|ARM64
|
||||||
@ -2601,6 +2633,78 @@ Global
|
|||||||
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x64.Build.0 = Release|x64
|
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x64.Build.0 = Release|x64
|
||||||
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x86.ActiveCfg = Release|x64
|
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x86.ActiveCfg = Release|x64
|
||||||
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x86.Build.0 = Release|x64
|
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x86.Build.0 = Release|x64
|
||||||
|
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||||
|
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
|
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
|
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|x86.Build.0 = Debug|x64
|
||||||
|
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||||
|
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|ARM64.Build.0 = Release|ARM64
|
||||||
|
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|x64.Build.0 = Release|x64
|
||||||
|
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|x86.ActiveCfg = Release|x64
|
||||||
|
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|x86.Build.0 = Release|x64
|
||||||
|
{9C53CC25-0623-4569-95BC-B05410675EE3}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||||
|
{9C53CC25-0623-4569-95BC-B05410675EE3}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
|
{9C53CC25-0623-4569-95BC-B05410675EE3}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{9C53CC25-0623-4569-95BC-B05410675EE3}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{9C53CC25-0623-4569-95BC-B05410675EE3}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
|
{9C53CC25-0623-4569-95BC-B05410675EE3}.Debug|x86.Build.0 = Debug|x64
|
||||||
|
{9C53CC25-0623-4569-95BC-B05410675EE3}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||||
|
{9C53CC25-0623-4569-95BC-B05410675EE3}.Release|ARM64.Build.0 = Release|ARM64
|
||||||
|
{9C53CC25-0623-4569-95BC-B05410675EE3}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{9C53CC25-0623-4569-95BC-B05410675EE3}.Release|x64.Build.0 = Release|x64
|
||||||
|
{9C53CC25-0623-4569-95BC-B05410675EE3}.Release|x86.ActiveCfg = Release|x64
|
||||||
|
{9C53CC25-0623-4569-95BC-B05410675EE3}.Release|x86.Build.0 = Release|x64
|
||||||
|
{45285DF2-9742-4ECA-9AC9-58951FC26489}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||||
|
{45285DF2-9742-4ECA-9AC9-58951FC26489}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
|
{45285DF2-9742-4ECA-9AC9-58951FC26489}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{45285DF2-9742-4ECA-9AC9-58951FC26489}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{45285DF2-9742-4ECA-9AC9-58951FC26489}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
|
{45285DF2-9742-4ECA-9AC9-58951FC26489}.Debug|x86.Build.0 = Debug|x64
|
||||||
|
{45285DF2-9742-4ECA-9AC9-58951FC26489}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||||
|
{45285DF2-9742-4ECA-9AC9-58951FC26489}.Release|ARM64.Build.0 = Release|ARM64
|
||||||
|
{45285DF2-9742-4ECA-9AC9-58951FC26489}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{45285DF2-9742-4ECA-9AC9-58951FC26489}.Release|x64.Build.0 = Release|x64
|
||||||
|
{45285DF2-9742-4ECA-9AC9-58951FC26489}.Release|x86.ActiveCfg = Release|x64
|
||||||
|
{45285DF2-9742-4ECA-9AC9-58951FC26489}.Release|x86.Build.0 = Release|x64
|
||||||
|
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||||
|
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
|
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
|
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|x86.Build.0 = Debug|x64
|
||||||
|
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||||
|
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|ARM64.Build.0 = Release|ARM64
|
||||||
|
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|x64.Build.0 = Release|x64
|
||||||
|
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|x86.ActiveCfg = Release|x64
|
||||||
|
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|x86.Build.0 = Release|x64
|
||||||
|
{367D7543-7DBA-4381-99F1-BF6142A996C4}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||||
|
{367D7543-7DBA-4381-99F1-BF6142A996C4}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
|
{367D7543-7DBA-4381-99F1-BF6142A996C4}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{367D7543-7DBA-4381-99F1-BF6142A996C4}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{367D7543-7DBA-4381-99F1-BF6142A996C4}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
|
{367D7543-7DBA-4381-99F1-BF6142A996C4}.Debug|x86.Build.0 = Debug|x64
|
||||||
|
{367D7543-7DBA-4381-99F1-BF6142A996C4}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||||
|
{367D7543-7DBA-4381-99F1-BF6142A996C4}.Release|ARM64.Build.0 = Release|ARM64
|
||||||
|
{367D7543-7DBA-4381-99F1-BF6142A996C4}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{367D7543-7DBA-4381-99F1-BF6142A996C4}.Release|x64.Build.0 = Release|x64
|
||||||
|
{367D7543-7DBA-4381-99F1-BF6142A996C4}.Release|x86.ActiveCfg = Release|x64
|
||||||
|
{367D7543-7DBA-4381-99F1-BF6142A996C4}.Release|x86.Build.0 = Release|x64
|
||||||
|
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||||
|
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
|
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
|
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|x86.Build.0 = Debug|x64
|
||||||
|
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||||
|
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|ARM64.Build.0 = Release|ARM64
|
||||||
|
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x64.Build.0 = Release|x64
|
||||||
|
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x86.ActiveCfg = Release|x64
|
||||||
|
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x86.Build.0 = Release|x64
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -2817,6 +2921,15 @@ Global
|
|||||||
{C0974915-8A1D-4BF0-977B-9587D3807AB7} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
|
{C0974915-8A1D-4BF0-977B-9587D3807AB7} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
|
||||||
{1D6893CB-BC0C-46A8-A76C-9728706CA51A} = {557C4636-D7E1-4838-A504-7D19B725EE95}
|
{1D6893CB-BC0C-46A8-A76C-9728706CA51A} = {557C4636-D7E1-4838-A504-7D19B725EE95}
|
||||||
{F055103B-F80B-4D0C-BF48-057C55620033} = {5A7818A8-109C-4E1C-850D-1A654E234B0E}
|
{F055103B-F80B-4D0C-BF48-057C55620033} = {5A7818A8-109C-4E1C-850D-1A654E234B0E}
|
||||||
|
{A2221D7E-55E7-4BEA-90D1-4F162D670BBF} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
|
||||||
|
{BE126CBB-AE12-406A-9837-A05ACFCA57A7} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||||
|
{14CB58B7-D280-4A7A-95DE-4B2DF14EA000} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||||
|
{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||||
|
{9C53CC25-0623-4569-95BC-B05410675EE3} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||||
|
{45285DF2-9742-4ECA-9AC9-58951FC26489} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||||
|
{3D63307B-9D27-44FD-B033-B26F39245B85} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||||
|
{367D7543-7DBA-4381-99F1-BF6142A996C4} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||||
|
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
|
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
|
||||||
|
BIN
doc/images/icons/Workspaces.png
Normal file
BIN
doc/images/icons/Workspaces.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
@ -18,6 +18,7 @@
|
|||||||
<?define AdvancedPasteProjectName="AdvancedPaste"?>
|
<?define AdvancedPasteProjectName="AdvancedPaste"?>
|
||||||
<?define RegistryPreviewProjectName="RegistryPreview"?>
|
<?define RegistryPreviewProjectName="RegistryPreview"?>
|
||||||
<?define PeekProjectName="Peek"?>
|
<?define PeekProjectName="Peek"?>
|
||||||
|
<?define WorkspacesProjectName="Workspaces"?>
|
||||||
|
|
||||||
<?define RepoDir="$(var.ProjectDir)..\..\" ?>
|
<?define RepoDir="$(var.ProjectDir)..\..\" ?>
|
||||||
<?if $(var.Platform) = x64?>
|
<?if $(var.Platform) = x64?>
|
||||||
|
@ -449,6 +449,15 @@
|
|||||||
</RegistryKey>
|
</RegistryKey>
|
||||||
<File Id="PowerOCR_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\$(var.Language)\PowerToys.PowerOCR.resources.dll" />
|
<File Id="PowerOCR_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\$(var.Language)\PowerToys.PowerOCR.resources.dll" />
|
||||||
</Component>
|
</Component>
|
||||||
|
<Component
|
||||||
|
Id="WorkspacesEditor_$(var.IdSafeLanguage)_Component"
|
||||||
|
Directory="Resource$(var.IdSafeLanguage)INSTALLFOLDER"
|
||||||
|
Guid="$(var.CompGUIDPrefix)21">
|
||||||
|
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||||
|
<RegistryValue Type="string" Name="WorkspacesEditor_$(var.IdSafeLanguage)_Component" Value="" KeyPath="yes"/>
|
||||||
|
</RegistryKey>
|
||||||
|
<File Id="WorkspacesEditor_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\$(var.Language)\PowerToys.WorkspacesEditor.resources.dll" />
|
||||||
|
</Component>
|
||||||
<?undef IdSafeLanguage?>
|
<?undef IdSafeLanguage?>
|
||||||
<?undef CompGUIDPrefix?>
|
<?undef CompGUIDPrefix?>
|
||||||
<?endforeach?>
|
<?endforeach?>
|
||||||
|
@ -1223,7 +1223,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
|
|||||||
}
|
}
|
||||||
processes.resize(bytes / sizeof(processes[0]));
|
processes.resize(bytes / sizeof(processes[0]));
|
||||||
|
|
||||||
std::array<std::wstring_view, 32> processesToTerminate = {
|
std::array<std::wstring_view, 36> processesToTerminate = {
|
||||||
L"PowerToys.PowerLauncher.exe",
|
L"PowerToys.PowerLauncher.exe",
|
||||||
L"PowerToys.Settings.exe",
|
L"PowerToys.Settings.exe",
|
||||||
L"PowerToys.AdvancedPaste.exe",
|
L"PowerToys.AdvancedPaste.exe",
|
||||||
@ -1255,6 +1255,10 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
|
|||||||
L"PowerToys.MouseWithoutBordersService.exe",
|
L"PowerToys.MouseWithoutBordersService.exe",
|
||||||
L"PowerToys.CropAndLock.exe",
|
L"PowerToys.CropAndLock.exe",
|
||||||
L"PowerToys.EnvironmentVariables.exe",
|
L"PowerToys.EnvironmentVariables.exe",
|
||||||
|
L"PowerToys.WorkspacesSnapshotTool.exe",
|
||||||
|
L"PowerToys.WorkspacesLauncher.exe",
|
||||||
|
L"PowerToys.WorkspacesLauncherUI.exe",
|
||||||
|
L"PowerToys.WorkspacesEditor.exe",
|
||||||
L"PowerToys.exe",
|
L"PowerToys.exe",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ namespace Common.UI
|
|||||||
EnvironmentVariables,
|
EnvironmentVariables,
|
||||||
Dashboard,
|
Dashboard,
|
||||||
AdvancedPaste,
|
AdvancedPaste,
|
||||||
|
Workspaces,
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string SettingsWindowNameToString(SettingsWindow value)
|
private static string SettingsWindowNameToString(SettingsWindow value)
|
||||||
@ -77,6 +78,8 @@ namespace Common.UI
|
|||||||
return "Dashboard";
|
return "Dashboard";
|
||||||
case SettingsWindow.AdvancedPaste:
|
case SettingsWindow.AdvancedPaste:
|
||||||
return "AdvancedPaste";
|
return "AdvancedPaste";
|
||||||
|
case SettingsWindow.Workspaces:
|
||||||
|
return "Workspaces";
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
@ -24,15 +24,18 @@
|
|||||||
<ItemDefinitionGroup>
|
<ItemDefinitionGroup>
|
||||||
<ClCompile>
|
<ClCompile>
|
||||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||||
<AdditionalIncludeDirectories>..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>..\..\..\;..\..\common;.\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
<PreprocessorDefinitions>_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
<PreprocessorDefinitions>_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ClInclude Include="DisplayUtils.h" />
|
||||||
|
<ClInclude Include="MonitorEnumerator.h" />
|
||||||
<ClInclude Include="monitors.h" />
|
<ClInclude Include="monitors.h" />
|
||||||
<ClInclude Include="dpi_aware.h" />
|
<ClInclude Include="dpi_aware.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ClCompile Include="DisplayUtils.cpp" />
|
||||||
<ClCompile Include="monitors.cpp" />
|
<ClCompile Include="monitors.cpp" />
|
||||||
<ClCompile Include="dpi_aware.cpp" />
|
<ClCompile Include="dpi_aware.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
143
src/common/Display/DisplayUtils.cpp
Normal file
143
src/common/Display/DisplayUtils.cpp
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
#include "DisplayUtils.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cwctype>
|
||||||
|
#include <iterator>
|
||||||
|
|
||||||
|
#include <dpi_aware.h>
|
||||||
|
#include <MonitorEnumerator.h>
|
||||||
|
|
||||||
|
#include <utils/OnThreadExecutor.h>
|
||||||
|
|
||||||
|
namespace DisplayUtils
|
||||||
|
{
|
||||||
|
std::wstring remove_non_digits(const std::wstring& input)
|
||||||
|
{
|
||||||
|
std::wstring result;
|
||||||
|
std::copy_if(input.begin(), input.end(), std::back_inserter(result), [](wchar_t ch) { return std::iswdigit(ch); });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<std::wstring, std::wstring> SplitDisplayDeviceId(const std::wstring& str) noexcept
|
||||||
|
{
|
||||||
|
// format: \\?\DISPLAY#{device id}#{instance id}#{some other id}
|
||||||
|
// example: \\?\DISPLAY#GSM1388#4&125707d6&0&UID8388688#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
|
||||||
|
// output: { GSM1388, 4&125707d6&0&UID8388688 }
|
||||||
|
|
||||||
|
size_t nameStartPos = str.find_first_of('#');
|
||||||
|
size_t uidStartPos = str.find('#', nameStartPos + 1);
|
||||||
|
size_t uidEndPos = str.find('#', uidStartPos + 1);
|
||||||
|
|
||||||
|
if (nameStartPos == std::string::npos || uidStartPos == std::string::npos || uidEndPos == std::string::npos)
|
||||||
|
{
|
||||||
|
return { str, L"" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { str.substr(nameStartPos + 1, uidStartPos - nameStartPos - 1), str.substr(uidStartPos + 1, uidEndPos - uidStartPos - 1) };
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<bool, std::vector<DisplayUtils::DisplayData>> GetDisplays()
|
||||||
|
{
|
||||||
|
bool success = true;
|
||||||
|
std::vector<DisplayUtils::DisplayData> result{};
|
||||||
|
auto allMonitors = MonitorEnumerator::Enumerate();
|
||||||
|
|
||||||
|
OnThreadExecutor dpiUnawareThread;
|
||||||
|
dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] {
|
||||||
|
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE);
|
||||||
|
SetThreadDpiHostingBehavior(DPI_HOSTING_BEHAVIOR_MIXED);
|
||||||
|
} }).wait();
|
||||||
|
|
||||||
|
for (auto& monitorData : allMonitors)
|
||||||
|
{
|
||||||
|
MONITORINFOEX monitorInfo = monitorData.second;
|
||||||
|
MONITORINFOEX dpiUnawareMonitorInfo{};
|
||||||
|
|
||||||
|
dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] {
|
||||||
|
dpiUnawareMonitorInfo.cbSize = sizeof(dpiUnawareMonitorInfo);
|
||||||
|
if (!GetMonitorInfo(monitorData.first, &dpiUnawareMonitorInfo))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} }).wait();
|
||||||
|
|
||||||
|
UINT dpi = 0;
|
||||||
|
if (DPIAware::GetScreenDPIForMonitor(monitorData.first, dpi) != S_OK)
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
DisplayUtils::DisplayData data{
|
||||||
|
.monitor = monitorData.first,
|
||||||
|
.dpi = dpi,
|
||||||
|
.monitorRectDpiAware = monitorInfo.rcMonitor,
|
||||||
|
.monitorRectDpiUnaware = dpiUnawareMonitorInfo.rcMonitor,
|
||||||
|
};
|
||||||
|
|
||||||
|
bool foundActiveMonitor = false;
|
||||||
|
DISPLAY_DEVICE displayDevice{ .cb = sizeof(displayDevice) };
|
||||||
|
DWORD displayDeviceIndex = 0;
|
||||||
|
while (EnumDisplayDevicesW(monitorInfo.szDevice, displayDeviceIndex, &displayDevice, EDD_GET_DEVICE_INTERFACE_NAME))
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* if (WI_IsFlagSet(displayDevice.StateFlags, DISPLAY_DEVICE_ACTIVE) &&
|
||||||
|
WI_IsFlagClear(displayDevice.StateFlags, DISPLAY_DEVICE_MIRRORING_DRIVER))
|
||||||
|
*/
|
||||||
|
if (((displayDevice.StateFlags & DISPLAY_DEVICE_ACTIVE) == DISPLAY_DEVICE_ACTIVE) &&
|
||||||
|
(displayDevice.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) == 0)
|
||||||
|
{
|
||||||
|
// Find display devices associated with the display.
|
||||||
|
foundActiveMonitor = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
displayDeviceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundActiveMonitor)
|
||||||
|
{
|
||||||
|
auto deviceId = SplitDisplayDeviceId(displayDevice.DeviceID);
|
||||||
|
data.id = deviceId.first;
|
||||||
|
data.instanceId = deviceId.second;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::wstring numberStr = displayDevice.DeviceName; // \\.\DISPLAY1\Monitor0
|
||||||
|
numberStr = numberStr.substr(0, numberStr.find_last_of('\\')); // \\.\DISPLAY1
|
||||||
|
numberStr = remove_non_digits(numberStr);
|
||||||
|
data.number = std::stoi(numberStr);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
|
||||||
|
// Use the display name as a fallback value when no proper device was found.
|
||||||
|
data.id = monitorInfo.szDevice;
|
||||||
|
data.instanceId = L"";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::wstring numberStr = monitorInfo.szDevice; // \\.\DISPLAY1
|
||||||
|
numberStr = remove_non_digits(numberStr);
|
||||||
|
data.number = std::stoi(numberStr);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push_back(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success, result };
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
21
src/common/Display/DisplayUtils.h
Normal file
21
src/common/Display/DisplayUtils.h
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace DisplayUtils
|
||||||
|
{
|
||||||
|
struct DisplayData
|
||||||
|
{
|
||||||
|
HMONITOR monitor{};
|
||||||
|
std::wstring id;
|
||||||
|
std::wstring instanceId;
|
||||||
|
unsigned int number{};
|
||||||
|
unsigned int dpi{};
|
||||||
|
RECT monitorRectDpiAware{};
|
||||||
|
RECT monitorRectDpiUnaware{};
|
||||||
|
};
|
||||||
|
|
||||||
|
std::pair<bool, std::vector<DisplayData>> GetDisplays();
|
||||||
|
};
|
35
src/common/Display/MonitorEnumerator.h
Normal file
35
src/common/Display/MonitorEnumerator.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
class MonitorEnumerator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static std::vector<std::pair<HMONITOR, MONITORINFOEX>> Enumerate()
|
||||||
|
{
|
||||||
|
MonitorEnumerator inst;
|
||||||
|
EnumDisplayMonitors(NULL, NULL, Callback, reinterpret_cast<LPARAM>(&inst));
|
||||||
|
return inst.m_monitors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
MonitorEnumerator() = default;
|
||||||
|
~MonitorEnumerator() = default;
|
||||||
|
|
||||||
|
static BOOL CALLBACK Callback(HMONITOR monitor, HDC /*hdc*/, LPRECT /*pRect*/, LPARAM param)
|
||||||
|
{
|
||||||
|
MonitorEnumerator* inst = reinterpret_cast<MonitorEnumerator*>(param);
|
||||||
|
MONITORINFOEX mi;
|
||||||
|
mi.cbSize = sizeof(mi);
|
||||||
|
if (GetMonitorInfo(monitor, &mi))
|
||||||
|
{
|
||||||
|
inst->m_monitors.push_back({monitor, mi});
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<HMONITOR, MONITORINFOEX>> m_monitors;
|
||||||
|
};
|
@ -1,7 +1,9 @@
|
|||||||
#include "dpi_aware.h"
|
#include "dpi_aware.h"
|
||||||
|
|
||||||
#include "monitors.h"
|
#include "monitors.h"
|
||||||
#include <ShellScalingApi.h>
|
#include <ShellScalingApi.h>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
namespace DPIAware
|
namespace DPIAware
|
||||||
{
|
{
|
||||||
@ -60,6 +62,24 @@ namespace DPIAware
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Convert(HMONITOR monitor_handle, RECT& rect)
|
||||||
|
{
|
||||||
|
if (monitor_handle == NULL)
|
||||||
|
{
|
||||||
|
const POINT ptZero = { 0, 0 };
|
||||||
|
monitor_handle = MonitorFromPoint(ptZero, MONITOR_DEFAULTTOPRIMARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT dpi_x, dpi_y;
|
||||||
|
if (GetDpiForMonitor(monitor_handle, MDT_EFFECTIVE_DPI, &dpi_x, &dpi_y) == S_OK)
|
||||||
|
{
|
||||||
|
rect.left = static_cast<long>(std::round(rect.left * static_cast<float>(dpi_x) / DEFAULT_DPI));
|
||||||
|
rect.right = static_cast<long>(std::round(rect.right * static_cast<float>(dpi_x) / DEFAULT_DPI));
|
||||||
|
rect.top = static_cast<long>(std::round(rect.top * static_cast<float>(dpi_y) / DEFAULT_DPI));
|
||||||
|
rect.bottom = static_cast<long>(std::round(rect.bottom * static_cast<float>(dpi_y) / DEFAULT_DPI));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ConvertByCursorPosition(float& width, float& height)
|
void ConvertByCursorPosition(float& width, float& height)
|
||||||
{
|
{
|
||||||
HMONITOR targetMonitor = nullptr;
|
HMONITOR targetMonitor = nullptr;
|
||||||
|
@ -12,6 +12,7 @@ namespace DPIAware
|
|||||||
HRESULT GetScreenDPIForPoint(POINT p, UINT& dpi);
|
HRESULT GetScreenDPIForPoint(POINT p, UINT& dpi);
|
||||||
HRESULT GetScreenDPIForCursor(UINT& dpi);
|
HRESULT GetScreenDPIForCursor(UINT& dpi);
|
||||||
void Convert(HMONITOR monitor_handle, float& width, float& height);
|
void Convert(HMONITOR monitor_handle, float& width, float& height);
|
||||||
|
void Convert(HMONITOR monitor_handle, RECT& rect);
|
||||||
void ConvertByCursorPosition(float& width, float& height);
|
void ConvertByCursorPosition(float& width, float& height);
|
||||||
void InverseConvert(HMONITOR monitor_handle, float& width, float& height);
|
void InverseConvert(HMONITOR monitor_handle, float& width, float& height);
|
||||||
void EnableDPIAwarenessForThisProcess();
|
void EnableDPIAwarenessForThisProcess();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "GPOWrapper.h"
|
#include "GPOWrapper.h"
|
||||||
#include "GPOWrapper.g.cpp"
|
#include "GPOWrapper.g.cpp"
|
||||||
|
|
||||||
@ -176,6 +176,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation
|
|||||||
{
|
{
|
||||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getAllowedAdvancedPasteOnlineAIModelsValue());
|
return static_cast<GpoRuleConfigured>(powertoys_gpo::getAllowedAdvancedPasteOnlineAIModelsValue());
|
||||||
}
|
}
|
||||||
|
GpoRuleConfigured GPOWrapper::GetConfiguredWorkspacesEnabledValue()
|
||||||
|
{
|
||||||
|
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredWorkspacesEnabledValue());
|
||||||
|
}
|
||||||
GpoRuleConfigured GPOWrapper::GetConfiguredMwbClipboardSharingEnabledValue()
|
GpoRuleConfigured GPOWrapper::GetConfiguredMwbClipboardSharingEnabledValue()
|
||||||
{
|
{
|
||||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredMwbClipboardSharingEnabledValue());
|
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredMwbClipboardSharingEnabledValue());
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "GPOWrapper.g.h"
|
#include "GPOWrapper.g.h"
|
||||||
#include <common/utils/gpo.h>
|
#include <common/utils/gpo.h>
|
||||||
|
|
||||||
@ -50,6 +50,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation
|
|||||||
static GpoRuleConfigured GetConfiguredQoiPreviewEnabledValue();
|
static GpoRuleConfigured GetConfiguredQoiPreviewEnabledValue();
|
||||||
static GpoRuleConfigured GetConfiguredQoiThumbnailsEnabledValue();
|
static GpoRuleConfigured GetConfiguredQoiThumbnailsEnabledValue();
|
||||||
static GpoRuleConfigured GetAllowedAdvancedPasteOnlineAIModelsValue();
|
static GpoRuleConfigured GetAllowedAdvancedPasteOnlineAIModelsValue();
|
||||||
|
static GpoRuleConfigured GetConfiguredWorkspacesEnabledValue();
|
||||||
static GpoRuleConfigured GetConfiguredMwbClipboardSharingEnabledValue();
|
static GpoRuleConfigured GetConfiguredMwbClipboardSharingEnabledValue();
|
||||||
static GpoRuleConfigured GetConfiguredMwbFileTransferEnabledValue();
|
static GpoRuleConfigured GetConfiguredMwbFileTransferEnabledValue();
|
||||||
static GpoRuleConfigured GetConfiguredMwbUseOriginalUserInterfaceValue();
|
static GpoRuleConfigured GetConfiguredMwbUseOriginalUserInterfaceValue();
|
||||||
|
@ -54,6 +54,7 @@ namespace PowerToys
|
|||||||
static GpoRuleConfigured GetConfiguredQoiPreviewEnabledValue();
|
static GpoRuleConfigured GetConfiguredQoiPreviewEnabledValue();
|
||||||
static GpoRuleConfigured GetConfiguredQoiThumbnailsEnabledValue();
|
static GpoRuleConfigured GetConfiguredQoiThumbnailsEnabledValue();
|
||||||
static GpoRuleConfigured GetAllowedAdvancedPasteOnlineAIModelsValue();
|
static GpoRuleConfigured GetAllowedAdvancedPasteOnlineAIModelsValue();
|
||||||
|
static GpoRuleConfigured GetConfiguredWorkspacesEnabledValue();
|
||||||
static GpoRuleConfigured GetConfiguredMwbClipboardSharingEnabledValue();
|
static GpoRuleConfigured GetConfiguredMwbClipboardSharingEnabledValue();
|
||||||
static GpoRuleConfigured GetConfiguredMwbFileTransferEnabledValue();
|
static GpoRuleConfigured GetConfiguredMwbFileTransferEnabledValue();
|
||||||
static GpoRuleConfigured GetConfiguredMwbUseOriginalUserInterfaceValue();
|
static GpoRuleConfigured GetConfiguredMwbUseOriginalUserInterfaceValue();
|
||||||
|
@ -61,5 +61,10 @@ namespace PowerToys.GPOWrapperProjection
|
|||||||
{
|
{
|
||||||
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetRunPluginEnabledValue(pluginID);
|
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetRunPluginEnabledValue(pluginID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static GpoRuleConfigured GetConfiguredWorkspacesEnabledValue()
|
||||||
|
{
|
||||||
|
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredWorkspacesEnabledValue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,5 +30,6 @@ namespace ManagedCommon
|
|||||||
MeasureTool,
|
MeasureTool,
|
||||||
ShortcutGuide,
|
ShortcutGuide,
|
||||||
PowerOCR,
|
PowerOCR,
|
||||||
|
Workspaces,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,4 +147,8 @@ namespace winrt::PowerToys::Interop::implementation
|
|||||||
{
|
{
|
||||||
return CommonSharedConstants::SHOW_ENVIRONMENT_VARIABLES_ADMIN_EVENT;
|
return CommonSharedConstants::SHOW_ENVIRONMENT_VARIABLES_ADMIN_EVENT;
|
||||||
}
|
}
|
||||||
|
hstring Constants::WorkspacesLaunchEditorEvent()
|
||||||
|
{
|
||||||
|
return CommonSharedConstants::WORKSPACES_LAUNCH_EDITOR_EVENT;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ namespace winrt::PowerToys::Interop::implementation
|
|||||||
static hstring CropAndLockReparentEvent();
|
static hstring CropAndLockReparentEvent();
|
||||||
static hstring ShowEnvironmentVariablesSharedEvent();
|
static hstring ShowEnvironmentVariablesSharedEvent();
|
||||||
static hstring ShowEnvironmentVariablesAdminSharedEvent();
|
static hstring ShowEnvironmentVariablesAdminSharedEvent();
|
||||||
|
static hstring WorkspacesLaunchEditorEvent();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ namespace PowerToys
|
|||||||
static String CropAndLockReparentEvent();
|
static String CropAndLockReparentEvent();
|
||||||
static String ShowEnvironmentVariablesSharedEvent();
|
static String ShowEnvironmentVariablesSharedEvent();
|
||||||
static String ShowEnvironmentVariablesAdminSharedEvent();
|
static String ShowEnvironmentVariablesAdminSharedEvent();
|
||||||
|
static String WorkspacesLaunchEditorEvent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -43,6 +43,8 @@ namespace CommonSharedConstants
|
|||||||
|
|
||||||
const wchar_t FANCY_ZONES_EDITOR_TOGGLE_EVENT[] = L"Local\\FancyZones-ToggleEditorEvent-1e174338-06a3-472b-874d-073b21c62f14";
|
const wchar_t FANCY_ZONES_EDITOR_TOGGLE_EVENT[] = L"Local\\FancyZones-ToggleEditorEvent-1e174338-06a3-472b-874d-073b21c62f14";
|
||||||
|
|
||||||
|
const wchar_t WORKSPACES_LAUNCH_EDITOR_EVENT[] = L"Local\\Workspaces-LaunchEditorEvent-a55ff427-cf62-4994-a2cd-9f72139296bf";
|
||||||
|
|
||||||
const wchar_t SHOW_HOSTS_EVENT[] = L"Local\\Hosts-ShowHostsEvent-5a0c0aae-5ff5-40f5-95c2-20e37ed671f0";
|
const wchar_t SHOW_HOSTS_EVENT[] = L"Local\\Hosts-ShowHostsEvent-5a0c0aae-5ff5-40f5-95c2-20e37ed671f0";
|
||||||
|
|
||||||
const wchar_t SHOW_HOSTS_ADMIN_EVENT[] = L"Local\\Hosts-ShowHostsAdminEvent-60ff44e2-efd3-43bf-928a-f4d269f98bec";
|
const wchar_t SHOW_HOSTS_ADMIN_EVENT[] = L"Local\\Hosts-ShowHostsAdminEvent-60ff44e2-efd3-43bf-928a-f4d269f98bec";
|
||||||
@ -98,6 +100,8 @@ namespace CommonSharedConstants
|
|||||||
const wchar_t SHOW_ENVIRONMENT_VARIABLES_EVENT[] = L"Local\\PowerToysEnvironmentVariables-ShowEnvironmentVariablesEvent-1021f616-e951-4d64-b231-a8f972159978";
|
const wchar_t SHOW_ENVIRONMENT_VARIABLES_EVENT[] = L"Local\\PowerToysEnvironmentVariables-ShowEnvironmentVariablesEvent-1021f616-e951-4d64-b231-a8f972159978";
|
||||||
const wchar_t SHOW_ENVIRONMENT_VARIABLES_ADMIN_EVENT[] = L"Local\\PowerToysEnvironmentVariables-EnvironmentVariablesAdminEvent-8c95d2ad-047c-49a2-9e8b-b4656326cfb2";
|
const wchar_t SHOW_ENVIRONMENT_VARIABLES_ADMIN_EVENT[] = L"Local\\PowerToysEnvironmentVariables-EnvironmentVariablesAdminEvent-8c95d2ad-047c-49a2-9e8b-b4656326cfb2";
|
||||||
|
|
||||||
|
const wchar_t WORKSPACES_EXIT_EVENT[] = L"Local\\PowerToys-Workspaces-ExitEvent-29a1566f-f4f8-4d56-9435-d2a437f727c6";
|
||||||
|
|
||||||
// Max DWORD for key code to disable keys.
|
// Max DWORD for key code to disable keys.
|
||||||
const DWORD VK_DISABLED = 0x100;
|
const DWORD VK_DISABLED = 0x100;
|
||||||
}
|
}
|
||||||
|
@ -69,6 +69,10 @@ struct LogSettings
|
|||||||
inline const static std::string environmentVariablesLoggerName = "environment-variables";
|
inline const static std::string environmentVariablesLoggerName = "environment-variables";
|
||||||
inline const static std::wstring cmdNotFoundLogPath = L"Logs\\cmd-not-found-log.txt";
|
inline const static std::wstring cmdNotFoundLogPath = L"Logs\\cmd-not-found-log.txt";
|
||||||
inline const static std::string cmdNotFoundLoggerName = "cmd-not-found";
|
inline const static std::string cmdNotFoundLoggerName = "cmd-not-found";
|
||||||
|
inline const static std::string workspacesLauncherLoggerName = "workspaces-launcher";
|
||||||
|
inline const static std::wstring workspacesLauncherLogPath = L"workspaces-launcher-log.txt";
|
||||||
|
inline const static std::string workspacesSnapshotToolLoggerName = "workspaces-snapshot-tool";
|
||||||
|
inline const static std::wstring workspacesSnapshotToolLogPath = L"workspaces-snapshot-tool-log.txt";
|
||||||
inline const static int retention = 30;
|
inline const static int retention = 30;
|
||||||
std::wstring logLevel;
|
std::wstring logLevel;
|
||||||
LogSettings();
|
LogSettings();
|
||||||
|
72
src/common/utils/OnThreadExecutor.h
Normal file
72
src/common/utils/OnThreadExecutor.h
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <future>
|
||||||
|
#include <thread>
|
||||||
|
#include <functional>
|
||||||
|
#include <queue>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
// OnThreadExecutor allows its caller to off-load some work to a persistently running background thread.
|
||||||
|
// This might come in handy if you use the API which sets thread-wide global state and the state needs
|
||||||
|
// to be isolated.
|
||||||
|
|
||||||
|
class OnThreadExecutor final
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using task_t = std::packaged_task<void()>;
|
||||||
|
|
||||||
|
OnThreadExecutor() :
|
||||||
|
_shutdown_request{ false },
|
||||||
|
_worker_thread{ [this] { worker_thread(); } }
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~OnThreadExecutor()
|
||||||
|
{
|
||||||
|
_shutdown_request = true;
|
||||||
|
_task_cv.notify_one();
|
||||||
|
_worker_thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::future<void> submit(task_t task)
|
||||||
|
{
|
||||||
|
auto future = task.get_future();
|
||||||
|
std::lock_guard lock{ _task_mutex };
|
||||||
|
_task_queue.emplace(std::move(task));
|
||||||
|
_task_cv.notify_one();
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancel()
|
||||||
|
{
|
||||||
|
std::lock_guard lock{ _task_mutex };
|
||||||
|
_task_queue = {};
|
||||||
|
_task_cv.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void worker_thread()
|
||||||
|
{
|
||||||
|
while (!_shutdown_request)
|
||||||
|
{
|
||||||
|
task_t task;
|
||||||
|
{
|
||||||
|
std::unique_lock task_lock{ _task_mutex };
|
||||||
|
_task_cv.wait(task_lock, [this] { return !_task_queue.empty() || _shutdown_request; });
|
||||||
|
if (_shutdown_request)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
task = std::move(_task_queue.front());
|
||||||
|
_task_queue.pop();
|
||||||
|
}
|
||||||
|
task();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::mutex _task_mutex;
|
||||||
|
std::condition_variable _task_cv;
|
||||||
|
std::atomic_bool _shutdown_request;
|
||||||
|
std::queue<std::packaged_task<void()>> _task_queue;
|
||||||
|
std::thread _worker_thread;
|
||||||
|
};
|
@ -60,6 +60,7 @@ namespace powertoys_gpo {
|
|||||||
const std::wstring POLICY_CONFIGURE_ENABLED_ENVIRONMENT_VARIABLES = L"ConfigureEnabledUtilityEnvironmentVariables";
|
const std::wstring POLICY_CONFIGURE_ENABLED_ENVIRONMENT_VARIABLES = L"ConfigureEnabledUtilityEnvironmentVariables";
|
||||||
const std::wstring POLICY_CONFIGURE_ENABLED_QOI_PREVIEW = L"ConfigureEnabledUtilityFileExplorerQOIPreview";
|
const std::wstring POLICY_CONFIGURE_ENABLED_QOI_PREVIEW = L"ConfigureEnabledUtilityFileExplorerQOIPreview";
|
||||||
const std::wstring POLICY_CONFIGURE_ENABLED_QOI_THUMBNAILS = L"ConfigureEnabledUtilityFileExplorerQOIThumbnails";
|
const std::wstring POLICY_CONFIGURE_ENABLED_QOI_THUMBNAILS = L"ConfigureEnabledUtilityFileExplorerQOIThumbnails";
|
||||||
|
const std::wstring POLICY_CONFIGURE_ENABLED_WORKSPACES = L"ConfigureEnabledUtilityWorkspaces";
|
||||||
|
|
||||||
// The registry value names for PowerToys installer and update policies.
|
// The registry value names for PowerToys installer and update policies.
|
||||||
const std::wstring POLICY_DISABLE_PER_USER_INSTALLATION = L"PerUserInstallationDisabled";
|
const std::wstring POLICY_DISABLE_PER_USER_INSTALLATION = L"PerUserInstallationDisabled";
|
||||||
@ -399,6 +400,11 @@ namespace powertoys_gpo {
|
|||||||
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_ADVANCED_PASTE);
|
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_ADVANCED_PASTE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline gpo_rule_configured_t getConfiguredWorkspacesEnabledValue()
|
||||||
|
{
|
||||||
|
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_WORKSPACES);
|
||||||
|
}
|
||||||
|
|
||||||
inline gpo_rule_configured_t getConfiguredVideoConferenceMuteEnabledValue()
|
inline gpo_rule_configured_t getConfiguredVideoConferenceMuteEnabledValue()
|
||||||
{
|
{
|
||||||
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_VIDEO_CONFERENCE_MUTE);
|
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_VIDEO_CONFERENCE_MUTE);
|
||||||
|
@ -57,6 +57,8 @@ properties:
|
|||||||
EnableQoiThumbnail: false
|
EnableQoiThumbnail: false
|
||||||
PowerOcr:
|
PowerOcr:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
Workspaces:
|
||||||
|
Enabled: false
|
||||||
ShortcutGuide:
|
ShortcutGuide:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
VideoConference:
|
VideoConference:
|
||||||
|
@ -57,6 +57,8 @@ properties:
|
|||||||
EnableQoiThumbnail: true
|
EnableQoiThumbnail: true
|
||||||
PowerOcr:
|
PowerOcr:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
|
Workspaces:
|
||||||
|
Enabled: true
|
||||||
ShortcutGuide:
|
ShortcutGuide:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
VideoConference:
|
VideoConference:
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Copyright (c) Microsoft Corporation.
|
<!-- Copyright (c) Microsoft Corporation.
|
||||||
Licensed under the MIT License. -->
|
Licensed under the MIT License. -->
|
||||||
<policyDefinitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.11" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
|
<policyDefinitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.12" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
|
||||||
<policyNamespaces>
|
<policyNamespaces>
|
||||||
<target prefix="powertoys" namespace="Microsoft.Policies.PowerToys" />
|
<target prefix="powertoys" namespace="Microsoft.Policies.PowerToys" />
|
||||||
</policyNamespaces>
|
</policyNamespaces>
|
||||||
<resources minRequiredRevision="1.11"/><!-- Last changed with PowerToys v0.83.0 -->
|
<resources minRequiredRevision="1.12"/><!-- Last changed with PowerToys v0.84.0 -->
|
||||||
<supportedOn>
|
<supportedOn>
|
||||||
<definitions>
|
<definitions>
|
||||||
<definition name="SUPPORTED_POWERTOYS_0_64_0" displayName="$(string.SUPPORTED_POWERTOYS_0_64_0)"/>
|
<definition name="SUPPORTED_POWERTOYS_0_64_0" displayName="$(string.SUPPORTED_POWERTOYS_0_64_0)"/>
|
||||||
@ -20,6 +20,7 @@
|
|||||||
<definition name="SUPPORTED_POWERTOYS_0_81_0" displayName="$(string.SUPPORTED_POWERTOYS_0_81_0)"/>
|
<definition name="SUPPORTED_POWERTOYS_0_81_0" displayName="$(string.SUPPORTED_POWERTOYS_0_81_0)"/>
|
||||||
<definition name="SUPPORTED_POWERTOYS_0_81_1" displayName="$(string.SUPPORTED_POWERTOYS_0_81_1)"/>
|
<definition name="SUPPORTED_POWERTOYS_0_81_1" displayName="$(string.SUPPORTED_POWERTOYS_0_81_1)"/>
|
||||||
<definition name="SUPPORTED_POWERTOYS_0_83_0" displayName="$(string.SUPPORTED_POWERTOYS_0_83_0)"/>
|
<definition name="SUPPORTED_POWERTOYS_0_83_0" displayName="$(string.SUPPORTED_POWERTOYS_0_83_0)"/>
|
||||||
|
<definition name="SUPPORTED_POWERTOYS_0_84_0" displayName="$(string.SUPPORTED_POWERTOYS_0_84_0)"/>
|
||||||
</definitions>
|
</definitions>
|
||||||
</supportedOn>
|
</supportedOn>
|
||||||
<categories>
|
<categories>
|
||||||
@ -364,6 +365,16 @@
|
|||||||
<decimal value="0" />
|
<decimal value="0" />
|
||||||
</disabledValue>
|
</disabledValue>
|
||||||
</policy>
|
</policy>
|
||||||
|
<policy name="ConfigureEnabledUtilityWorkspaces" class="Both" displayName="$(string.ConfigureEnabledUtilityWorkspaces)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityWorkspaces">
|
||||||
|
<parentCategory ref="PowerToys" />
|
||||||
|
<supportedOn ref="SUPPORTED_POWERTOYS_0_84_0" />
|
||||||
|
<enabledValue>
|
||||||
|
<decimal value="1" />
|
||||||
|
</enabledValue>
|
||||||
|
<disabledValue>
|
||||||
|
<decimal value="0" />
|
||||||
|
</disabledValue>
|
||||||
|
</policy>
|
||||||
<policy name="ConfigureEnabledUtilityQuickAccent" class="Both" displayName="$(string.ConfigureEnabledUtilityQuickAccent)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityQuickAccent">
|
<policy name="ConfigureEnabledUtilityQuickAccent" class="Both" displayName="$(string.ConfigureEnabledUtilityQuickAccent)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityQuickAccent">
|
||||||
<parentCategory ref="PowerToys" />
|
<parentCategory ref="PowerToys" />
|
||||||
<supportedOn ref="SUPPORTED_POWERTOYS_0_64_0" />
|
<supportedOn ref="SUPPORTED_POWERTOYS_0_64_0" />
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
<string id="InstallerUpdates">Installer and Updates</string>
|
<string id="InstallerUpdates">Installer and Updates</string>
|
||||||
<string id="PowerToysRun">PowerToys Run</string>
|
<string id="PowerToysRun">PowerToys Run</string>
|
||||||
<string id="AdvancedPaste">Advanced Paste</string>
|
<string id="AdvancedPaste">Advanced Paste</string>
|
||||||
|
<string id="Workspaces">Workspaces</string>
|
||||||
<string id="MouseWithoutBorders">Mouse Without Borders</string>
|
<string id="MouseWithoutBorders">Mouse Without Borders</string>
|
||||||
<string id="GeneralSettings">General settings</string>
|
<string id="GeneralSettings">General settings</string>
|
||||||
|
|
||||||
@ -25,6 +26,7 @@
|
|||||||
<string id="SUPPORTED_POWERTOYS_0_81_0">PowerToys version 0.81.0 or later</string>
|
<string id="SUPPORTED_POWERTOYS_0_81_0">PowerToys version 0.81.0 or later</string>
|
||||||
<string id="SUPPORTED_POWERTOYS_0_81_1">PowerToys version 0.81.1 or later</string>
|
<string id="SUPPORTED_POWERTOYS_0_81_1">PowerToys version 0.81.1 or later</string>
|
||||||
<string id="SUPPORTED_POWERTOYS_0_83_0">PowerToys version 0.83.0 or later</string>
|
<string id="SUPPORTED_POWERTOYS_0_83_0">PowerToys version 0.83.0 or later</string>
|
||||||
|
<string id="SUPPORTED_POWERTOYS_0_84_0">PowerToys version 0.84.0 or later</string>
|
||||||
|
|
||||||
<string id="ConfigureAllUtilityGlobalEnabledStateDescription">This policy configures the enabled state for all PowerToys utilities.
|
<string id="ConfigureAllUtilityGlobalEnabledStateDescription">This policy configures the enabled state for all PowerToys utilities.
|
||||||
|
|
||||||
@ -110,6 +112,12 @@ If you don't configure this setting, users are able to enable or disable the plu
|
|||||||
You can override this policy for individual plugins using the policy "Configure enabled state for individual plugins".
|
You can override this policy for individual plugins using the policy "Configure enabled state for individual plugins".
|
||||||
|
|
||||||
Note: Changes require a restart of PowerToys Run.
|
Note: Changes require a restart of PowerToys Run.
|
||||||
|
</string>
|
||||||
|
<string id="ConfigureEnabledUtilityWorkspaces">This policy configures the enabled disable state for the Workspaces utility.
|
||||||
|
|
||||||
|
If you enable or don't configure this policy, the user takes control over the enabled state of the Workspaces utility.
|
||||||
|
|
||||||
|
If you disable this policy, the user won't be able to enable Enable and use the Workspaces utility.
|
||||||
</string>
|
</string>
|
||||||
<string id="PowerToysRunIndividualPluginEnabledStateDescription">With this policy you can configure an individual enabled state for each PowerToys Run plugin that you add to the list.
|
<string id="PowerToysRunIndividualPluginEnabledStateDescription">With this policy you can configure an individual enabled state for each PowerToys Run plugin that you add to the list.
|
||||||
|
|
||||||
@ -219,6 +227,7 @@ If you disable or don't configure this policy, no predefined rules are applied.
|
|||||||
<string id="ConfigureEnabledUtilityPeek">Peek: Configure enabled state</string>
|
<string id="ConfigureEnabledUtilityPeek">Peek: Configure enabled state</string>
|
||||||
<string id="ConfigureEnabledUtilityPowerRename">Power Rename: Configure enabled state</string>
|
<string id="ConfigureEnabledUtilityPowerRename">Power Rename: Configure enabled state</string>
|
||||||
<string id="ConfigureEnabledUtilityPowerLauncher">PowerToys Run: Configure enabled state</string>
|
<string id="ConfigureEnabledUtilityPowerLauncher">PowerToys Run: Configure enabled state</string>
|
||||||
|
<string id="ConfigureEnabledUtilityWorkspaces">PowerToys Workspaces: Configure enabled state</string>
|
||||||
<string id="ConfigureEnabledUtilityQuickAccent">Quick Accent: Configure enabled state</string>
|
<string id="ConfigureEnabledUtilityQuickAccent">Quick Accent: Configure enabled state</string>
|
||||||
<string id="ConfigureEnabledUtilityRegistryPreview">Registry Preview: Configure enabled state</string>
|
<string id="ConfigureEnabledUtilityRegistryPreview">Registry Preview: Configure enabled state</string>
|
||||||
<string id="ConfigureEnabledUtilityScreenRuler">Screen Ruler: Configure enabled state</string>
|
<string id="ConfigureEnabledUtilityScreenRuler">Screen Ruler: Configure enabled state</string>
|
||||||
|
BIN
src/modules/Workspaces/Assets/Workspaces.ico
Normal file
BIN
src/modules/Workspaces/Assets/Workspaces.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 456 KiB |
@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
namespace WorkspacesWindowProperties
|
||||||
|
{
|
||||||
|
namespace Properties
|
||||||
|
{
|
||||||
|
const wchar_t LaunchedByWorkspacesID[] = L"PowerToys_LaunchedByWorkspaces";
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void StampWorkspacesLaunchedProperty(HWND window)
|
||||||
|
{
|
||||||
|
::SetPropW(window, Properties::LaunchedByWorkspacesID, reinterpret_cast<HANDLE>(1));
|
||||||
|
}
|
||||||
|
}
|
9
src/modules/Workspaces/WorkspacesEditor/App.config
Normal file
9
src/modules/Workspaces/WorkspacesEditor/App.config
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<startup>
|
||||||
|
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>
|
||||||
|
</startup>
|
||||||
|
<runtime>
|
||||||
|
<AppContextSwitchOverrides value = "Switch.System.Windows.DoNotScaleForDpiChanges=false"/>
|
||||||
|
</runtime>
|
||||||
|
</configuration>
|
20
src/modules/Workspaces/WorkspacesEditor/App.xaml
Normal file
20
src/modules/Workspaces/WorkspacesEditor/App.xaml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<Application
|
||||||
|
x:Class="WorkspacesEditor.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="clr-namespace:WorkspacesEditor"
|
||||||
|
xmlns:ui="http://schemas.modernwpf.com/2019"
|
||||||
|
Exit="OnExit"
|
||||||
|
Startup="OnStartup">
|
||||||
|
<Application.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ui:ThemeResources />
|
||||||
|
<ui:XamlControlsResources />
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Styles/ButtonStyles.xaml" />
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
|
||||||
|
<Style x:Key="HeadingTextBlock" TargetType="TextBlock" />
|
||||||
|
</ResourceDictionary>
|
||||||
|
</Application.Resources>
|
||||||
|
</Application>
|
120
src/modules/Workspaces/WorkspacesEditor/App.xaml.cs
Normal file
120
src/modules/Workspaces/WorkspacesEditor/App.xaml.cs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Windows;
|
||||||
|
using Common.UI;
|
||||||
|
using ManagedCommon;
|
||||||
|
using WorkspacesEditor.Utils;
|
||||||
|
using WorkspacesEditor.ViewModels;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for App.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class App : Application, IDisposable
|
||||||
|
{
|
||||||
|
private static Mutex _instanceMutex;
|
||||||
|
|
||||||
|
public static WorkspacesEditorIO WorkspacesEditorIO { get; private set; }
|
||||||
|
|
||||||
|
private MainWindow _mainWindow;
|
||||||
|
|
||||||
|
private MainViewModel _mainViewModel;
|
||||||
|
|
||||||
|
public static ThemeManager ThemeManager { get; set; }
|
||||||
|
|
||||||
|
private bool _isDisposed;
|
||||||
|
|
||||||
|
public App()
|
||||||
|
{
|
||||||
|
WorkspacesEditorIO = new WorkspacesEditorIO();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStartup(object sender, StartupEventArgs e)
|
||||||
|
{
|
||||||
|
Logger.InitializeLogger("\\Workspaces\\Logs");
|
||||||
|
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
||||||
|
|
||||||
|
const string appName = "Local\\PowerToys_Workspaces_Editor_InstanceMutex";
|
||||||
|
bool createdNew;
|
||||||
|
_instanceMutex = new Mutex(true, appName, out createdNew);
|
||||||
|
if (!createdNew)
|
||||||
|
{
|
||||||
|
Logger.LogWarning("Another instance of Workspaces Editor is already running. Exiting this instance.");
|
||||||
|
_instanceMutex = null;
|
||||||
|
Shutdown(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PowerToys.GPOWrapperProjection.GPOWrapper.GetConfiguredWorkspacesEnabledValue() == PowerToys.GPOWrapperProjection.GpoRuleConfigured.Disabled)
|
||||||
|
{
|
||||||
|
Logger.LogWarning("Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator.");
|
||||||
|
Shutdown(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ThemeManager = new ThemeManager(this);
|
||||||
|
|
||||||
|
if (_mainViewModel == null)
|
||||||
|
{
|
||||||
|
_mainViewModel = new MainViewModel(WorkspacesEditorIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
var parseResult = WorkspacesEditorIO.ParseWorkspaces(_mainViewModel);
|
||||||
|
|
||||||
|
// normal start of editor
|
||||||
|
if (_mainWindow == null)
|
||||||
|
{
|
||||||
|
_mainWindow = new MainWindow(_mainViewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset main window owner to keep it on the top
|
||||||
|
_mainWindow.ShowActivated = true;
|
||||||
|
_mainWindow.Topmost = true;
|
||||||
|
_mainWindow.Show();
|
||||||
|
|
||||||
|
// we can reset topmost flag after it's opened
|
||||||
|
_mainWindow.Topmost = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnExit(object sender, ExitEventArgs e)
|
||||||
|
{
|
||||||
|
if (_instanceMutex != null)
|
||||||
|
{
|
||||||
|
_instanceMutex.ReleaseMutex();
|
||||||
|
}
|
||||||
|
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs args)
|
||||||
|
{
|
||||||
|
Logger.LogError("Unhandled exception occurred", args.ExceptionObject as Exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_isDisposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
ThemeManager?.Dispose();
|
||||||
|
_instanceMutex?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_isDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Controls
|
||||||
|
{
|
||||||
|
public class ResetIsEnabled : ContentControl
|
||||||
|
{
|
||||||
|
static ResetIsEnabled()
|
||||||
|
{
|
||||||
|
IsEnabledProperty.OverrideMetadata(
|
||||||
|
typeof(ResetIsEnabled),
|
||||||
|
new UIPropertyMetadata(
|
||||||
|
defaultValue: true,
|
||||||
|
propertyChangedCallback: (_, __) => { },
|
||||||
|
coerceValueCallback: (_, x) => x));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Converters
|
||||||
|
{
|
||||||
|
public class BooleanToInvertedVisibilityConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if ((bool)value)
|
||||||
|
{
|
||||||
|
return Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Visibility.Visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
src/modules/Workspaces/WorkspacesEditor/Data/InvokePoint.cs
Normal file
14
src/modules/Workspaces/WorkspacesEditor/Data/InvokePoint.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Data
|
||||||
|
{
|
||||||
|
/* sync with workspaces-common */
|
||||||
|
public enum InvokePoint
|
||||||
|
{
|
||||||
|
EditorButton = 0,
|
||||||
|
Shortcut,
|
||||||
|
LaunchAndEdit,
|
||||||
|
}
|
||||||
|
}
|
96
src/modules/Workspaces/WorkspacesEditor/Data/ProjectData.cs
Normal file
96
src/modules/Workspaces/WorkspacesEditor/Data/ProjectData.cs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Workspaces.Data;
|
||||||
|
using static WorkspacesEditor.Data.ProjectData;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Data
|
||||||
|
{
|
||||||
|
public class ProjectData : WorkspacesEditorData<ProjectWrapper>
|
||||||
|
{
|
||||||
|
public struct ApplicationWrapper
|
||||||
|
{
|
||||||
|
public struct WindowPositionWrapper
|
||||||
|
{
|
||||||
|
public int X { get; set; }
|
||||||
|
|
||||||
|
public int Y { get; set; }
|
||||||
|
|
||||||
|
public int Width { get; set; }
|
||||||
|
|
||||||
|
public int Height { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Application { get; set; }
|
||||||
|
|
||||||
|
public string ApplicationPath { get; set; }
|
||||||
|
|
||||||
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
public string PackageFullName { get; set; }
|
||||||
|
|
||||||
|
public string AppUserModelId { get; set; }
|
||||||
|
|
||||||
|
public string CommandLineArguments { get; set; }
|
||||||
|
|
||||||
|
public bool IsElevated { get; set; }
|
||||||
|
|
||||||
|
public bool CanLaunchElevated { get; set; }
|
||||||
|
|
||||||
|
public bool Minimized { get; set; }
|
||||||
|
|
||||||
|
public bool Maximized { get; set; }
|
||||||
|
|
||||||
|
public WindowPositionWrapper Position { get; set; }
|
||||||
|
|
||||||
|
public int Monitor { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct MonitorConfigurationWrapper
|
||||||
|
{
|
||||||
|
public struct MonitorRectWrapper
|
||||||
|
{
|
||||||
|
public int Top { get; set; }
|
||||||
|
|
||||||
|
public int Left { get; set; }
|
||||||
|
|
||||||
|
public int Width { get; set; }
|
||||||
|
|
||||||
|
public int Height { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
public string InstanceId { get; set; }
|
||||||
|
|
||||||
|
public int MonitorNumber { get; set; }
|
||||||
|
|
||||||
|
public int Dpi { get; set; }
|
||||||
|
|
||||||
|
public MonitorRectWrapper MonitorRectDpiAware { get; set; }
|
||||||
|
|
||||||
|
public MonitorRectWrapper MonitorRectDpiUnaware { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct ProjectWrapper
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public long CreationTime { get; set; }
|
||||||
|
|
||||||
|
public long LastLaunchedTime { get; set; }
|
||||||
|
|
||||||
|
public bool IsShortcutNeeded { get; set; }
|
||||||
|
|
||||||
|
public bool MoveExistingWindows { get; set; }
|
||||||
|
|
||||||
|
public List<MonitorConfigurationWrapper> MonitorConfiguration { get; set; }
|
||||||
|
|
||||||
|
public List<ApplicationWrapper> Applications { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using WorkspacesEditor.Utils;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Data
|
||||||
|
{
|
||||||
|
public class TempProjectData : ProjectData
|
||||||
|
{
|
||||||
|
public static string File
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return FolderUtils.DataFolder() + "\\temp-workspaces.json";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DeleteTempFile()
|
||||||
|
{
|
||||||
|
if (System.IO.File.Exists(File))
|
||||||
|
{
|
||||||
|
System.IO.File.Delete(File);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Workspaces.Data;
|
||||||
|
using WorkspacesEditor.Utils;
|
||||||
|
using static WorkspacesEditor.Data.ProjectData;
|
||||||
|
using static WorkspacesEditor.Data.WorkspacesData;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Data
|
||||||
|
{
|
||||||
|
public class WorkspacesData : WorkspacesEditorData<WorkspacesListWrapper>
|
||||||
|
{
|
||||||
|
public string File
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return FolderUtils.DataFolder() + "\\workspaces.json";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct WorkspacesListWrapper
|
||||||
|
{
|
||||||
|
public List<ProjectWrapper> Workspaces { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum OrderBy
|
||||||
|
{
|
||||||
|
LastViewed = 0,
|
||||||
|
Created = 1,
|
||||||
|
Name = 2,
|
||||||
|
Unknown = 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Text.Json;
|
||||||
|
using WorkspacesEditor.Utils;
|
||||||
|
|
||||||
|
namespace Workspaces.Data
|
||||||
|
{
|
||||||
|
public class WorkspacesEditorData<T>
|
||||||
|
{
|
||||||
|
protected JsonSerializerOptions JsonOptions
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = new DashCaseNamingPolicy(),
|
||||||
|
WriteIndented = true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Read(string file)
|
||||||
|
{
|
||||||
|
IOUtils ioUtils = new IOUtils();
|
||||||
|
string data = ioUtils.ReadFile(file);
|
||||||
|
return JsonSerializer.Deserialize<T>(data, JsonOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Serialize(T data)
|
||||||
|
{
|
||||||
|
return JsonSerializer.Serialize(data, JsonOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
src/modules/Workspaces/WorkspacesEditor/HeadingTextBlock.cs
Normal file
31
src/modules/Workspaces/WorkspacesEditor/HeadingTextBlock.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Automation.Peers;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor
|
||||||
|
{
|
||||||
|
public class HeadingTextBlock : TextBlock
|
||||||
|
{
|
||||||
|
protected override AutomationPeer OnCreateAutomationPeer()
|
||||||
|
{
|
||||||
|
return new HeadingTextBlockAutomationPeer(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class HeadingTextBlockAutomationPeer : TextBlockAutomationPeer
|
||||||
|
{
|
||||||
|
public HeadingTextBlockAutomationPeer(HeadingTextBlock owner)
|
||||||
|
: base(owner)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override AutomationControlType GetAutomationControlTypeCore()
|
||||||
|
{
|
||||||
|
return AutomationControlType.Header;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
322
src/modules/Workspaces/WorkspacesEditor/MainPage.xaml
Normal file
322
src/modules/Workspaces/WorkspacesEditor/MainPage.xaml
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
<Page
|
||||||
|
x:Class="WorkspacesEditor.MainPage"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:converters="clr-namespace:WorkspacesEditor.Converters"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="clr-namespace:WorkspacesEditor"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:props="clr-namespace:WorkspacesEditor.Properties"
|
||||||
|
Title="MainPage"
|
||||||
|
d:DesignHeight="450"
|
||||||
|
d:DesignWidth="800"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<Page.Resources>
|
||||||
|
<BooleanToVisibilityConverter x:Key="BoolToVis" />
|
||||||
|
<converters:BooleanToInvertedVisibilityConverter x:Key="BooleanToInvertedVisibilityConverter" />
|
||||||
|
<Thickness x:Key="ContentDialogPadding">24,16,0,24</Thickness>
|
||||||
|
<Thickness x:Key="ContentDialogCommandSpaceMargin">0,24,24,0</Thickness>
|
||||||
|
<Style x:Key="DeleteButtonStyle" TargetType="Button">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource TertiaryBackgroundBrush}" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type Button}">
|
||||||
|
<Border
|
||||||
|
x:Name="border"
|
||||||
|
Padding="26,6,26,6"
|
||||||
|
Background="{TemplateBinding Background}"
|
||||||
|
BorderBrush="Transparent">
|
||||||
|
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="border" Property="Background" Value="{DynamicResource TitleBarSecondaryForegroundBrush}" />
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsPressed" Value="True">
|
||||||
|
<Setter TargetName="border" Property="Background" Value="{DynamicResource TitleBarSecondaryForegroundBrush}" />
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</Page.Resources>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<local:HeadingTextBlock
|
||||||
|
x:Name="WorkspacesHeaderBlock"
|
||||||
|
Grid.Row="0"
|
||||||
|
Margin="40,20,40,20"
|
||||||
|
AutomationProperties.HeadingLevel="Level1"
|
||||||
|
FontSize="24"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||||
|
Text="{x:Static props:Resources.Workspaces}" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
x:Name="NewProjectButton"
|
||||||
|
Grid.Row="0"
|
||||||
|
Height="36"
|
||||||
|
Margin="0,20,40,20"
|
||||||
|
Padding="0"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
AutomationProperties.Name="{x:Static props:Resources.CreateWorkspace}"
|
||||||
|
Click="NewProjectButton_Click"
|
||||||
|
Style="{StaticResource AccentButtonStyle}"
|
||||||
|
TabIndex="3">
|
||||||
|
<StackPanel Margin="12,8,12,8" Orientation="Horizontal">
|
||||||
|
<TextBlock
|
||||||
|
AutomationProperties.Name="{x:Static props:Resources.CreateWorkspace}"
|
||||||
|
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||||
|
Foreground="{DynamicResource AccentButtonForeground}"
|
||||||
|
Text="" />
|
||||||
|
<TextBlock
|
||||||
|
Margin="12,-3,0,0"
|
||||||
|
Foreground="{DynamicResource AccentButtonForeground}"
|
||||||
|
Text="{x:Static props:Resources.CreateWorkspace}" />
|
||||||
|
</StackPanel>
|
||||||
|
<Button.Effect>
|
||||||
|
<DropShadowEffect
|
||||||
|
BlurRadius="6"
|
||||||
|
Opacity="0.32"
|
||||||
|
ShadowDepth="1" />
|
||||||
|
</Button.Effect>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Border
|
||||||
|
Grid.Row="1"
|
||||||
|
Margin="40,0,0,0"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
BorderThickness="2"
|
||||||
|
CornerRadius="5">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Grid>
|
||||||
|
<TextBox
|
||||||
|
x:Name="SearchTextBox"
|
||||||
|
Width="320"
|
||||||
|
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||||
|
BorderBrush="{DynamicResource PrimaryBorderBrush}"
|
||||||
|
Text="{Binding SearchTerm, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
ToolTip="{x:Static props:Resources.SearchExplanation}" />
|
||||||
|
<TextBlock
|
||||||
|
Margin="10,0,0,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{DynamicResource SecondaryForegroundBrush}"
|
||||||
|
IsHitTestVisible="False"
|
||||||
|
Text="{x:Static props:Resources.Search}"
|
||||||
|
ToolTip="{x:Static props:Resources.SearchExplanation}">
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="{x:Type TextBlock}">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Text, ElementName=SearchTextBox}" Value="">
|
||||||
|
<Setter Property="Visibility" Value="Visible" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
</Grid>
|
||||||
|
<TextBlock
|
||||||
|
Margin="-50,0,34,0"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
AutomationProperties.Name="{x:Static props:Resources.Search}"
|
||||||
|
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||||
|
Foreground="{DynamicResource SecondaryForegroundBrush}"
|
||||||
|
Text="" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<StackPanel
|
||||||
|
Grid.Row="1"
|
||||||
|
Margin="0,0,40,0"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<TextBlock
|
||||||
|
Margin="10,0,10,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||||
|
Text="{x:Static props:Resources.SortBy}" />
|
||||||
|
<ComboBox
|
||||||
|
Width="140"
|
||||||
|
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||||
|
BorderBrush="{DynamicResource PrimaryBorderBrush}"
|
||||||
|
SelectedIndex="{Binding OrderByIndex, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
|
||||||
|
<ComboBoxItem Content="{x:Static props:Resources.LastLaunched}" />
|
||||||
|
<ComboBoxItem Content="{x:Static props:Resources.Created}" />
|
||||||
|
<ComboBoxItem Content="{x:Static props:Resources.Name}" />
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
<TextBlock
|
||||||
|
Grid.Row="2"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontSize="20"
|
||||||
|
Foreground="{DynamicResource SecondaryForegroundBrush}"
|
||||||
|
Text="{Binding EmptyWorkspacesViewMessage, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
TextAlignment="Center"
|
||||||
|
Visibility="{Binding IsWorkspacesViewEmpty, Mode=OneWay, Converter={StaticResource BoolToVis}, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<ScrollViewer
|
||||||
|
Grid.Row="2"
|
||||||
|
Margin="40,15,40,40"
|
||||||
|
VerticalContentAlignment="Stretch"
|
||||||
|
VerticalScrollBarVisibility="Auto"
|
||||||
|
Visibility="{Binding IsWorkspacesViewEmpty, Mode=OneWay, Converter={StaticResource BooleanToInvertedVisibilityConverter}, UpdateSourceTrigger=PropertyChanged}">
|
||||||
|
<ItemsControl ItemsSource="{Binding WorkspacesView, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<StackPanel
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
IsItemsHost="True"
|
||||||
|
Orientation="Vertical" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate DataType="models:Project">
|
||||||
|
<Button
|
||||||
|
x:Name="EditButton"
|
||||||
|
Margin="0,12,0,0"
|
||||||
|
Padding="1"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Stretch"
|
||||||
|
AutomationProperties.Name="{x:Static props:Resources.Edit}"
|
||||||
|
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||||
|
Click="EditButtonClicked">
|
||||||
|
<Border
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||||
|
CornerRadius="5">
|
||||||
|
<Grid HorizontalAlignment="Stretch">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="110" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<StackPanel
|
||||||
|
Margin="12,14,10,10"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Orientation="Vertical">
|
||||||
|
<TextBlock
|
||||||
|
Margin="0,0,0,8"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontSize="16"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Text="{Binding Name, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<StackPanel
|
||||||
|
Margin="0,0,0,8"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<Image Height="20" Source="{Binding PreviewIcons, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<TextBlock
|
||||||
|
Margin="6,0,4,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{Binding AppsCountString}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel VerticalAlignment="Center" Orientation="Horizontal">
|
||||||
|
<TextBlock
|
||||||
|
Margin="0,3,10,0"
|
||||||
|
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||||
|
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||||
|
Text="" />
|
||||||
|
<TextBlock Text="{Binding LastLaunched, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="12,12,12,12"
|
||||||
|
Orientation="Vertical">
|
||||||
|
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
|
||||||
|
<Button
|
||||||
|
x:Name="MoreButton"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Click="MoreButton_Click"
|
||||||
|
Style="{StaticResource IconOnlyButtonStyle}">
|
||||||
|
<TextBlock
|
||||||
|
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||||
|
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||||
|
Text="" />
|
||||||
|
</Button>
|
||||||
|
<Popup
|
||||||
|
AllowsTransparency="True"
|
||||||
|
IsOpen="{Binding IsPopupVisible, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Placement="Left"
|
||||||
|
PlacementTarget="{Binding ElementName=MoreButton}"
|
||||||
|
StaysOpen="False">
|
||||||
|
<Grid Background="{DynamicResource PrimaryBackgroundBrush}">
|
||||||
|
<Grid.OpacityMask>
|
||||||
|
<VisualBrush Visual="{Binding ElementName=OpacityBorder}" />
|
||||||
|
</Grid.OpacityMask>
|
||||||
|
<Border
|
||||||
|
x:Name="OpacityBorder"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
Background="Black"
|
||||||
|
CornerRadius="5" />
|
||||||
|
<StackPanel Background="{DynamicResource PrimaryBackgroundBrush}" Orientation="Vertical">
|
||||||
|
<Button
|
||||||
|
AutomationProperties.Name="{x:Static props:Resources.Edit}"
|
||||||
|
Click="EditButtonClicked"
|
||||||
|
Style="{StaticResource DeleteButtonStyle}">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock
|
||||||
|
AutomationProperties.Name="{x:Static props:Resources.Edit}"
|
||||||
|
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||||
|
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||||
|
Text="" />
|
||||||
|
<TextBlock
|
||||||
|
Margin="10,0,0,0"
|
||||||
|
AutomationProperties.Name="{x:Static props:Resources.Edit}"
|
||||||
|
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||||
|
Text="{x:Static props:Resources.Edit}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
AutomationProperties.Name="{x:Static props:Resources.Delete}"
|
||||||
|
Click="DeleteButtonClicked"
|
||||||
|
Style="{StaticResource DeleteButtonStyle}">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock
|
||||||
|
AutomationProperties.Name="{x:Static props:Resources.Delete}"
|
||||||
|
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||||
|
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||||
|
Text="" />
|
||||||
|
<TextBlock
|
||||||
|
Margin="10,0,0,0"
|
||||||
|
AutomationProperties.Name="{x:Static props:Resources.Delete}"
|
||||||
|
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||||
|
Text="{x:Static props:Resources.Delete}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Popup>
|
||||||
|
</StackPanel>
|
||||||
|
<Button
|
||||||
|
Margin="0,6,0,0"
|
||||||
|
Padding="20,4,20,4"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
AutomationProperties.Name="{x:Static props:Resources.Launch}"
|
||||||
|
Background="{DynamicResource TertiaryBackgroundBrush}"
|
||||||
|
BorderBrush="{DynamicResource SecondaryBorderBrush}"
|
||||||
|
BorderThickness="1"
|
||||||
|
Click="LaunchButton_Click"
|
||||||
|
Content="{x:Static props:Resources.Launch}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Button>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
63
src/modules/Workspaces/WorkspacesEditor/MainPage.xaml.cs
Normal file
63
src/modules/Workspaces/WorkspacesEditor/MainPage.xaml.cs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using WorkspacesEditor.Models;
|
||||||
|
using WorkspacesEditor.ViewModels;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for MainPage.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class MainPage : Page
|
||||||
|
{
|
||||||
|
private MainViewModel _mainViewModel;
|
||||||
|
|
||||||
|
public MainPage(MainViewModel mainViewModel)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
_mainViewModel = mainViewModel;
|
||||||
|
this.DataContext = _mainViewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private /*async*/ void NewProjectButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_mainViewModel.EnterSnapshotMode(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EditButtonClicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_mainViewModel.CloseAllPopups();
|
||||||
|
Button button = sender as Button;
|
||||||
|
Project selectedProject = button.DataContext as Project;
|
||||||
|
_mainViewModel.EditProject(selectedProject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteButtonClicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
e.Handled = true;
|
||||||
|
Button button = sender as Button;
|
||||||
|
Project selectedProject = button.DataContext as Project;
|
||||||
|
_mainViewModel.DeleteProject(selectedProject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MoreButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
e.Handled = true;
|
||||||
|
Button button = sender as Button;
|
||||||
|
Project project = button.DataContext as Project;
|
||||||
|
project.IsPopupVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LaunchButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
e.Handled = true;
|
||||||
|
Button button = sender as Button;
|
||||||
|
Project project = button.DataContext as Project;
|
||||||
|
_mainViewModel.LaunchProject(project);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
src/modules/Workspaces/WorkspacesEditor/MainWindow.xaml
Normal file
29
src/modules/Workspaces/WorkspacesEditor/MainWindow.xaml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<Window
|
||||||
|
x:Class="WorkspacesEditor.MainWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:props="clr-namespace:WorkspacesEditor.Properties"
|
||||||
|
xmlns:ui="http://schemas.modernwpf.com/2019"
|
||||||
|
x:Name="WorkspacesMainWindow"
|
||||||
|
Title="{x:Static props:Resources.MainTitle}"
|
||||||
|
MinWidth="700"
|
||||||
|
MinHeight="680"
|
||||||
|
ui:TitleBar.Background="{DynamicResource PrimaryBackgroundBrush}"
|
||||||
|
ui:TitleBar.InactiveBackground="{DynamicResource TertiaryBackgroundBrush}"
|
||||||
|
ui:TitleBar.IsIconVisible="True"
|
||||||
|
ui:WindowHelper.UseModernWindowStyle="True"
|
||||||
|
AutomationProperties.Name="Workspaces Editor"
|
||||||
|
Background="{DynamicResource PrimaryBackgroundBrush}"
|
||||||
|
Closing="OnClosing"
|
||||||
|
ContentRendered="OnContentRendered"
|
||||||
|
ResizeMode="CanResize"
|
||||||
|
WindowStartupLocation="CenterOwner"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<Border BorderThickness="1" CornerRadius="20">
|
||||||
|
<Grid Margin="0,10,0,0">
|
||||||
|
<Frame x:Name="ContentFrame" NavigationUIVisibility="Hidden" />
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Window>
|
71
src/modules/Workspaces/WorkspacesEditor/MainWindow.xaml.cs
Normal file
71
src/modules/Workspaces/WorkspacesEditor/MainWindow.xaml.cs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Interop;
|
||||||
|
using ManagedCommon;
|
||||||
|
using WorkspacesEditor.Utils;
|
||||||
|
using WorkspacesEditor.ViewModels;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for MainWindow.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
public MainViewModel MainViewModel { get; set; }
|
||||||
|
|
||||||
|
private static MainPage _mainPage;
|
||||||
|
|
||||||
|
public MainWindow(MainViewModel mainViewModel)
|
||||||
|
{
|
||||||
|
MainViewModel = mainViewModel;
|
||||||
|
mainViewModel.SetMainWindow(this);
|
||||||
|
|
||||||
|
WindowInteropHelper windowInteropHelper = new WindowInteropHelper(this);
|
||||||
|
System.Windows.Forms.Screen screen = System.Windows.Forms.Screen.FromHandle(windowInteropHelper.Handle);
|
||||||
|
double dpi = MonitorHelper.GetScreenDpiFromScreen(screen);
|
||||||
|
this.Height = screen.WorkingArea.Height / dpi * 0.90;
|
||||||
|
this.Width = screen.WorkingArea.Width / dpi * 0.75;
|
||||||
|
this.Top = screen.WorkingArea.Top + (int)(screen.WorkingArea.Height / dpi * 0.05);
|
||||||
|
this.Left = screen.WorkingArea.Left + (int)(screen.WorkingArea.Width / dpi * 0.125);
|
||||||
|
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
_mainPage = new MainPage(mainViewModel);
|
||||||
|
|
||||||
|
ContentFrame.Navigate(_mainPage);
|
||||||
|
|
||||||
|
MaxWidth = SystemParameters.PrimaryScreenWidth;
|
||||||
|
MaxHeight = SystemParameters.PrimaryScreenHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClosing(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
App.Current.Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is required to fix a WPF rendering bug when using custom chrome
|
||||||
|
private void OnContentRendered(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// Get the window handle of the Workspaces Editor window
|
||||||
|
IntPtr handle = new WindowInteropHelper(this).Handle;
|
||||||
|
WindowHelpers.BringToForeground(handle);
|
||||||
|
|
||||||
|
InvalidateVisual();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowPage(ProjectEditor editPage)
|
||||||
|
{
|
||||||
|
ContentFrame.Navigate(editPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SwitchToMainView()
|
||||||
|
{
|
||||||
|
ContentFrame.GoBack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Models
|
||||||
|
{
|
||||||
|
public sealed class AppListDataTemplateSelector : System.Windows.Controls.DataTemplateSelector
|
||||||
|
{
|
||||||
|
public System.Windows.DataTemplate HeaderTemplate { get; set; }
|
||||||
|
|
||||||
|
public System.Windows.DataTemplate AppTemplate { get; set; }
|
||||||
|
|
||||||
|
public AppListDataTemplateSelector()
|
||||||
|
{
|
||||||
|
HeaderTemplate = new System.Windows.DataTemplate();
|
||||||
|
AppTemplate = new System.Windows.DataTemplate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
|
||||||
|
{
|
||||||
|
if (item is MonitorHeaderRow)
|
||||||
|
{
|
||||||
|
return HeaderTemplate;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return AppTemplate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
474
src/modules/Workspaces/WorkspacesEditor/Models/Application.cs
Normal file
474
src/modules/Workspaces/WorkspacesEditor/Models/Application.cs
Normal file
@ -0,0 +1,474 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Drawing.Drawing2D;
|
||||||
|
using System.Drawing.Imaging;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using ManagedCommon;
|
||||||
|
using Windows.Management.Deployment;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Models
|
||||||
|
{
|
||||||
|
public class Application : INotifyPropertyChanged, IDisposable
|
||||||
|
{
|
||||||
|
private bool _isInitialized;
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
|
public Application()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Application(Application other)
|
||||||
|
{
|
||||||
|
AppName = other.AppName;
|
||||||
|
AppPath = other.AppPath;
|
||||||
|
AppTitle = other.AppTitle;
|
||||||
|
PackageFullName = other.PackageFullName;
|
||||||
|
AppUserModelId = other.AppUserModelId;
|
||||||
|
CommandLineArguments = other.CommandLineArguments;
|
||||||
|
IsElevated = other.IsElevated;
|
||||||
|
CanLaunchElevated = other.CanLaunchElevated;
|
||||||
|
Minimized = other.Minimized;
|
||||||
|
Maximized = other.Maximized;
|
||||||
|
Position = other.Position;
|
||||||
|
MonitorNumber = other.MonitorNumber;
|
||||||
|
|
||||||
|
Parent = other.Parent;
|
||||||
|
IsNotFound = other.IsNotFound;
|
||||||
|
IsHighlighted = other.IsHighlighted;
|
||||||
|
RepeatIndex = other.RepeatIndex;
|
||||||
|
PackagedId = other.PackagedId;
|
||||||
|
PackagedName = other.PackagedName;
|
||||||
|
PackagedPublisherID = other.PackagedPublisherID;
|
||||||
|
Aumid = other.Aumid;
|
||||||
|
IsExpanded = other.IsExpanded;
|
||||||
|
IsIncluded = other.IsIncluded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Project Parent { get; set; }
|
||||||
|
|
||||||
|
public struct WindowPosition
|
||||||
|
{
|
||||||
|
public int X { get; set; }
|
||||||
|
|
||||||
|
public int Y { get; set; }
|
||||||
|
|
||||||
|
public int Width { get; set; }
|
||||||
|
|
||||||
|
public int Height { get; set; }
|
||||||
|
|
||||||
|
public static bool operator ==(WindowPosition left, WindowPosition right)
|
||||||
|
{
|
||||||
|
return left.X == right.X && left.Y == right.Y && left.Width == right.Width && left.Height == right.Height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(WindowPosition left, WindowPosition right)
|
||||||
|
{
|
||||||
|
return left.X != right.X || left.Y != right.Y || left.Width != right.Width || left.Height != right.Height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (obj == null || GetType() != obj.GetType())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowPosition pos = (WindowPosition)obj;
|
||||||
|
return X == pos.X && Y == pos.Y && Width == pos.Width && Height == pos.Height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return base.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string AppName { get; set; }
|
||||||
|
|
||||||
|
public string AppPath { get; set; }
|
||||||
|
|
||||||
|
public string AppTitle { get; set; }
|
||||||
|
|
||||||
|
public string PackageFullName { get; set; }
|
||||||
|
|
||||||
|
public string AppUserModelId { get; set; }
|
||||||
|
|
||||||
|
public string CommandLineArguments { get; set; }
|
||||||
|
|
||||||
|
private bool _isElevated;
|
||||||
|
|
||||||
|
public bool IsElevated
|
||||||
|
{
|
||||||
|
get => _isElevated;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isElevated = value;
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(AppMainParams)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanLaunchElevated { get; set; }
|
||||||
|
|
||||||
|
internal void SwitchDeletion()
|
||||||
|
{
|
||||||
|
IsIncluded = !IsIncluded;
|
||||||
|
RedrawPreviewImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RedrawPreviewImage()
|
||||||
|
{
|
||||||
|
if (_isInitialized)
|
||||||
|
{
|
||||||
|
Parent.Initialize(App.ThemeManager.GetCurrentTheme());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _minimized;
|
||||||
|
|
||||||
|
public bool Minimized
|
||||||
|
{
|
||||||
|
get => _minimized;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_minimized = value;
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Minimized)));
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(EditPositionEnabled)));
|
||||||
|
RedrawPreviewImage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _maximized;
|
||||||
|
|
||||||
|
public bool Maximized
|
||||||
|
{
|
||||||
|
get => _maximized;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_maximized = value;
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Maximized)));
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(EditPositionEnabled)));
|
||||||
|
RedrawPreviewImage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EditPositionEnabled { get => !Minimized && !Maximized; }
|
||||||
|
|
||||||
|
private string _appMainParams;
|
||||||
|
|
||||||
|
public string AppMainParams
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
_appMainParams = _isElevated ? Properties.Resources.Admin : string.Empty;
|
||||||
|
if (!string.IsNullOrWhiteSpace(CommandLineArguments))
|
||||||
|
{
|
||||||
|
_appMainParams += (_appMainParams == string.Empty ? string.Empty : " | ") + Properties.Resources.Args + ": " + CommandLineArguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsAppMainParamVisible)));
|
||||||
|
return _appMainParams;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsAppMainParamVisible { get => !string.IsNullOrWhiteSpace(_appMainParams); }
|
||||||
|
|
||||||
|
private bool _isNotFound;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool IsNotFound
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _isNotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_isNotFound != value)
|
||||||
|
{
|
||||||
|
_isNotFound = value;
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsNotFound)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool IsHighlighted { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public int RepeatIndex { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public string RepeatIndexString
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return RepeatIndex <= 1 ? string.Empty : RepeatIndex.ToString(CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
private Icon _icon = null;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public Icon Icon
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_icon == null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (IsPackagedApp)
|
||||||
|
{
|
||||||
|
Uri uri = GetAppLogoByPackageFamilyName();
|
||||||
|
var bitmap = new Bitmap(uri.LocalPath);
|
||||||
|
var iconHandle = bitmap.GetHicon();
|
||||||
|
_icon = Icon.FromHandle(iconHandle);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_icon = Icon.ExtractAssociatedIcon(AppPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
Logger.LogWarning($"Icon not found on app path: {AppPath}. Using default icon");
|
||||||
|
IsNotFound = true;
|
||||||
|
_icon = new Icon(@"images\DefaultIcon.ico");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _icon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri GetAppLogoByPackageFamilyName()
|
||||||
|
{
|
||||||
|
var pkgManager = new PackageManager();
|
||||||
|
var pkg = pkgManager.FindPackagesForUser(string.Empty, PackagedId).FirstOrDefault();
|
||||||
|
|
||||||
|
if (pkg == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkg.Logo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BitmapImage _iconBitmapImage;
|
||||||
|
|
||||||
|
public BitmapImage IconBitmapImage
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_iconBitmapImage == null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Bitmap previewBitmap = new Bitmap(32, 32);
|
||||||
|
using (Graphics graphics = Graphics.FromImage(previewBitmap))
|
||||||
|
{
|
||||||
|
graphics.SmoothingMode = SmoothingMode.AntiAlias;
|
||||||
|
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||||
|
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||||
|
|
||||||
|
graphics.DrawIcon(Icon, new Rectangle(0, 0, 32, 32));
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var memory = new MemoryStream())
|
||||||
|
{
|
||||||
|
previewBitmap.Save(memory, ImageFormat.Png);
|
||||||
|
memory.Position = 0;
|
||||||
|
|
||||||
|
var bitmapImage = new BitmapImage();
|
||||||
|
bitmapImage.BeginInit();
|
||||||
|
bitmapImage.StreamSource = memory;
|
||||||
|
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||||
|
bitmapImage.EndInit();
|
||||||
|
bitmapImage.Freeze();
|
||||||
|
|
||||||
|
_iconBitmapImage = bitmapImage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.LogError($"Exception while drawing icon for app with path: {AppPath}. Exception message: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _iconBitmapImage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private WindowPosition _position;
|
||||||
|
|
||||||
|
public WindowPosition Position
|
||||||
|
{
|
||||||
|
get => _position;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_position = value;
|
||||||
|
_scaledPosition = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private WindowPosition? _scaledPosition;
|
||||||
|
|
||||||
|
public WindowPosition ScaledPosition
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_scaledPosition == null)
|
||||||
|
{
|
||||||
|
double scaleFactor = MonitorSetup.Dpi / 96.0;
|
||||||
|
_scaledPosition = new WindowPosition()
|
||||||
|
{
|
||||||
|
X = (int)(scaleFactor * Position.X),
|
||||||
|
Y = (int)(scaleFactor * Position.Y),
|
||||||
|
Height = (int)(scaleFactor * Position.Height),
|
||||||
|
Width = (int)(scaleFactor * Position.Width),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return _scaledPosition.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int MonitorNumber { get; set; }
|
||||||
|
|
||||||
|
private MonitorSetup _monitorSetup;
|
||||||
|
|
||||||
|
public MonitorSetup MonitorSetup
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_monitorSetup == null)
|
||||||
|
{
|
||||||
|
_monitorSetup = Parent.Monitors.Where(x => x.MonitorNumber == MonitorNumber).FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _monitorSetup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnPropertyChanged(PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InitializationFinished()
|
||||||
|
{
|
||||||
|
_isInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool? _isPackagedApp;
|
||||||
|
|
||||||
|
public string PackagedId { get; set; }
|
||||||
|
|
||||||
|
public string PackagedName { get; set; }
|
||||||
|
|
||||||
|
public string PackagedPublisherID { get; set; }
|
||||||
|
|
||||||
|
public string Aumid { get; set; }
|
||||||
|
|
||||||
|
public bool IsPackagedApp
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_isPackagedApp == null)
|
||||||
|
{
|
||||||
|
if (!AppPath.StartsWith("C:\\Program Files\\WindowsApps\\", StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
_isPackagedApp = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string appPath = AppPath.Replace("C:\\Program Files\\WindowsApps\\", string.Empty);
|
||||||
|
Regex packagedAppPathRegex = new Regex(@"(?<APPID>[^_]*)_\d+.\d+.\d+.\d+_x64__(?<PublisherID>[^\\]*)", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||||
|
Match match = packagedAppPathRegex.Match(appPath);
|
||||||
|
_isPackagedApp = match.Success;
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
PackagedName = match.Groups["APPID"].Value;
|
||||||
|
PackagedPublisherID = match.Groups["PublisherID"].Value;
|
||||||
|
PackagedId = $"{PackagedName}_{PackagedPublisherID}";
|
||||||
|
Aumid = $"{PackagedId}!App";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _isPackagedApp.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isExpanded;
|
||||||
|
|
||||||
|
public bool IsExpanded
|
||||||
|
{
|
||||||
|
get => _isExpanded;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_isExpanded != value)
|
||||||
|
{
|
||||||
|
_isExpanded = value;
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsExpanded)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string DeleteButtonContent { get => _isIncluded ? Properties.Resources.Delete : Properties.Resources.AddBack; }
|
||||||
|
|
||||||
|
private bool _isIncluded = true;
|
||||||
|
|
||||||
|
public bool IsIncluded
|
||||||
|
{
|
||||||
|
get => _isIncluded;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_isIncluded != value)
|
||||||
|
{
|
||||||
|
_isIncluded = value;
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsIncluded)));
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(DeleteButtonContent)));
|
||||||
|
if (!_isIncluded)
|
||||||
|
{
|
||||||
|
IsExpanded = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void CommandLineTextChanged(string newCommandLineValue)
|
||||||
|
{
|
||||||
|
CommandLineArguments = newCommandLineValue;
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(AppMainParams)));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void MaximizedChecked()
|
||||||
|
{
|
||||||
|
Minimized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void MinimizedChecked()
|
||||||
|
{
|
||||||
|
Maximized = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
src/modules/Workspaces/WorkspacesEditor/Models/Monitor.cs
Normal file
33
src/modules/Workspaces/WorkspacesEditor/Models/Monitor.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Models
|
||||||
|
{
|
||||||
|
public class Monitor
|
||||||
|
{
|
||||||
|
public string MonitorName { get; private set; }
|
||||||
|
|
||||||
|
public string MonitorInstanceId { get; private set; }
|
||||||
|
|
||||||
|
public int MonitorNumber { get; private set; }
|
||||||
|
|
||||||
|
public int Dpi { get; private set; }
|
||||||
|
|
||||||
|
public Rect MonitorDpiUnawareBounds { get; private set; }
|
||||||
|
|
||||||
|
public Rect MonitorDpiAwareBounds { get; private set; }
|
||||||
|
|
||||||
|
public Monitor(string monitorName, string monitorInstanceId, int number, int dpi, Rect dpiAwareBounds, Rect dpiUnawareBounds)
|
||||||
|
{
|
||||||
|
MonitorName = monitorName;
|
||||||
|
MonitorInstanceId = monitorInstanceId;
|
||||||
|
MonitorNumber = number;
|
||||||
|
Dpi = dpi;
|
||||||
|
MonitorDpiAwareBounds = dpiAwareBounds;
|
||||||
|
MonitorDpiUnawareBounds = dpiUnawareBounds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Models
|
||||||
|
{
|
||||||
|
internal sealed class MonitorHeaderRow
|
||||||
|
{
|
||||||
|
public string MonitorName { get; set; }
|
||||||
|
|
||||||
|
public string SelectString { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Models
|
||||||
|
{
|
||||||
|
public class MonitorSetup : Monitor, INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
|
public void OnPropertyChanged(PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string MonitorInfo { get => MonitorName; }
|
||||||
|
|
||||||
|
public string MonitorInfoWithResolution { get => $"{MonitorName} {MonitorDpiAwareBounds.Width}x{MonitorDpiAwareBounds.Height}"; }
|
||||||
|
|
||||||
|
public MonitorSetup(string monitorName, string monitorInstanceId, int number, int dpi, Rect dpiAwareBounds, Rect dpiUnawareBounds)
|
||||||
|
: base(monitorName, monitorInstanceId, number, dpi, dpiAwareBounds, dpiUnawareBounds)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public MonitorSetup(MonitorSetup other)
|
||||||
|
: base(other.MonitorName, other.MonitorInstanceId, other.MonitorNumber, other.Dpi, other.MonitorDpiAwareBounds, other.MonitorDpiUnawareBounds)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
376
src/modules/Workspaces/WorkspacesEditor/Models/Project.cs
Normal file
376
src/modules/Workspaces/WorkspacesEditor/Models/Project.cs
Normal file
@ -0,0 +1,376 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using ManagedCommon;
|
||||||
|
using WorkspacesEditor.Data;
|
||||||
|
using WorkspacesEditor.Utils;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Models
|
||||||
|
{
|
||||||
|
public class Project : INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
[JsonIgnore]
|
||||||
|
public string EditorWindowTitle { get; set; }
|
||||||
|
|
||||||
|
public string Id { get; private set; }
|
||||||
|
|
||||||
|
private string _name;
|
||||||
|
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _name;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_name = value;
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Name)));
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(CanBeSaved)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long CreationTime { get; } // in seconds
|
||||||
|
|
||||||
|
public long LastLaunchedTime { get; } // in seconds
|
||||||
|
|
||||||
|
public bool IsShortcutNeeded { get; set; }
|
||||||
|
|
||||||
|
public bool MoveExistingWindows { get; set; }
|
||||||
|
|
||||||
|
public string LastLaunched
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
string lastLaunched = WorkspacesEditor.Properties.Resources.LastLaunched + ": ";
|
||||||
|
if (LastLaunchedTime == 0)
|
||||||
|
{
|
||||||
|
return lastLaunched + WorkspacesEditor.Properties.Resources.Never;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int SECOND = 1;
|
||||||
|
const int MINUTE = 60 * SECOND;
|
||||||
|
const int HOUR = 60 * MINUTE;
|
||||||
|
const int DAY = 24 * HOUR;
|
||||||
|
const int MONTH = 30 * DAY;
|
||||||
|
|
||||||
|
DateTime lastLaunchDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(LastLaunchedTime);
|
||||||
|
|
||||||
|
var now = DateTime.UtcNow.Ticks;
|
||||||
|
var ts = DateTime.UtcNow - lastLaunchDateTime;
|
||||||
|
double delta = Math.Abs(ts.TotalSeconds);
|
||||||
|
|
||||||
|
if (delta < 1 * MINUTE)
|
||||||
|
{
|
||||||
|
return lastLaunched + WorkspacesEditor.Properties.Resources.Recently;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta < 2 * MINUTE)
|
||||||
|
{
|
||||||
|
return lastLaunched + WorkspacesEditor.Properties.Resources.OneMinuteAgo;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta < 45 * MINUTE)
|
||||||
|
{
|
||||||
|
return lastLaunched + ts.Minutes + " " + WorkspacesEditor.Properties.Resources.MinutesAgo;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta < 90 * MINUTE)
|
||||||
|
{
|
||||||
|
return lastLaunched + WorkspacesEditor.Properties.Resources.OneHourAgo;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta < 24 * HOUR)
|
||||||
|
{
|
||||||
|
return lastLaunched + ts.Hours + " " + WorkspacesEditor.Properties.Resources.HoursAgo;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta < 48 * HOUR)
|
||||||
|
{
|
||||||
|
return lastLaunched + WorkspacesEditor.Properties.Resources.Yesterday;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta < 30 * DAY)
|
||||||
|
{
|
||||||
|
return lastLaunched + ts.Days + " " + WorkspacesEditor.Properties.Resources.DaysAgo;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta < 12 * MONTH)
|
||||||
|
{
|
||||||
|
int months = Convert.ToInt32(Math.Floor((double)ts.Days / 30));
|
||||||
|
return lastLaunched + (months <= 1 ? WorkspacesEditor.Properties.Resources.OneMonthAgo : months + " " + WorkspacesEditor.Properties.Resources.MonthsAgo);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int years = Convert.ToInt32(Math.Floor((double)ts.Days / 365));
|
||||||
|
return lastLaunched + (years <= 1 ? WorkspacesEditor.Properties.Resources.OneYearAgo : years + " " + WorkspacesEditor.Properties.Resources.YearsAgo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanBeSaved
|
||||||
|
{
|
||||||
|
get => Name.Length > 0 && Applications.Count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isRevertEnabled;
|
||||||
|
|
||||||
|
public bool IsRevertEnabled
|
||||||
|
{
|
||||||
|
get => _isRevertEnabled;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_isRevertEnabled != value)
|
||||||
|
{
|
||||||
|
_isRevertEnabled = value;
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsRevertEnabled)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isPopupVisible;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool IsPopupVisible
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _isPopupVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isPopupVisible = value;
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsPopupVisible)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Application> Applications { get; set; }
|
||||||
|
|
||||||
|
public List<object> ApplicationsListed
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
List<object> applicationsListed = new List<object>();
|
||||||
|
ILookup<MonitorSetup, Application> apps = Applications.Where(x => !x.Minimized).ToLookup(x => x.MonitorSetup);
|
||||||
|
foreach (var appItem in apps.OrderBy(x => x.Key.MonitorDpiUnawareBounds.Left).ThenBy(x => x.Key.MonitorDpiUnawareBounds.Top))
|
||||||
|
{
|
||||||
|
MonitorHeaderRow headerRow = new MonitorHeaderRow { MonitorName = "Screen " + appItem.Key.MonitorNumber, SelectString = Properties.Resources.SelectAllAppsOnMonitor + " " + appItem.Key.MonitorInfo };
|
||||||
|
applicationsListed.Add(headerRow);
|
||||||
|
foreach (Application app in appItem)
|
||||||
|
{
|
||||||
|
applicationsListed.Add(app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var minimizedApps = Applications.Where(x => x.Minimized);
|
||||||
|
if (minimizedApps.Any())
|
||||||
|
{
|
||||||
|
MonitorHeaderRow headerRow = new MonitorHeaderRow { MonitorName = Properties.Resources.Minimized_Apps, SelectString = Properties.Resources.SelectAllMinimizedApps };
|
||||||
|
applicationsListed.Add(headerRow);
|
||||||
|
foreach (Application app in minimizedApps)
|
||||||
|
{
|
||||||
|
applicationsListed.Add(app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return applicationsListed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public string AppsCountString
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
int count = Applications.Count;
|
||||||
|
return count.ToString(CultureInfo.InvariantCulture) + " " + (count == 1 ? Properties.Resources.App : Properties.Resources.Apps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MonitorSetup> Monitors { get; }
|
||||||
|
|
||||||
|
public bool IsPositionChangedManually { get; set; } // telemetry
|
||||||
|
|
||||||
|
private BitmapImage _previewIcons;
|
||||||
|
private BitmapImage _previewImage;
|
||||||
|
private double _previewImageWidth;
|
||||||
|
|
||||||
|
public Project(Project selectedProject)
|
||||||
|
{
|
||||||
|
Id = selectedProject.Id;
|
||||||
|
Name = selectedProject.Name;
|
||||||
|
PreviewIcons = selectedProject.PreviewIcons;
|
||||||
|
PreviewImage = selectedProject.PreviewImage;
|
||||||
|
IsShortcutNeeded = selectedProject.IsShortcutNeeded;
|
||||||
|
MoveExistingWindows = selectedProject.MoveExistingWindows;
|
||||||
|
|
||||||
|
int screenIndex = 1;
|
||||||
|
|
||||||
|
Monitors = new List<MonitorSetup>();
|
||||||
|
foreach (var item in selectedProject.Monitors.OrderBy(x => x.MonitorDpiAwareBounds.Left).ThenBy(x => x.MonitorDpiAwareBounds.Top))
|
||||||
|
{
|
||||||
|
Monitors.Add(item);
|
||||||
|
screenIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Applications = new List<Application>();
|
||||||
|
foreach (var item in selectedProject.Applications)
|
||||||
|
{
|
||||||
|
Application newApp = new Application(item);
|
||||||
|
newApp.Parent = this;
|
||||||
|
newApp.InitializationFinished();
|
||||||
|
Applications.Add(newApp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Project(ProjectData.ProjectWrapper project)
|
||||||
|
{
|
||||||
|
Id = project.Id;
|
||||||
|
Name = project.Name;
|
||||||
|
CreationTime = project.CreationTime;
|
||||||
|
LastLaunchedTime = project.LastLaunchedTime;
|
||||||
|
IsShortcutNeeded = project.IsShortcutNeeded;
|
||||||
|
MoveExistingWindows = project.MoveExistingWindows;
|
||||||
|
Monitors = new List<MonitorSetup>() { };
|
||||||
|
Applications = new List<Models.Application> { };
|
||||||
|
|
||||||
|
foreach (var app in project.Applications)
|
||||||
|
{
|
||||||
|
Models.Application newApp = new Models.Application()
|
||||||
|
{
|
||||||
|
AppName = app.Application,
|
||||||
|
AppPath = app.ApplicationPath,
|
||||||
|
AppTitle = app.Title,
|
||||||
|
PackageFullName = app.PackageFullName,
|
||||||
|
AppUserModelId = app.AppUserModelId,
|
||||||
|
Parent = this,
|
||||||
|
CommandLineArguments = app.CommandLineArguments,
|
||||||
|
IsElevated = app.IsElevated,
|
||||||
|
CanLaunchElevated = app.CanLaunchElevated,
|
||||||
|
Maximized = app.Maximized,
|
||||||
|
Minimized = app.Minimized,
|
||||||
|
IsNotFound = false,
|
||||||
|
Position = new Models.Application.WindowPosition()
|
||||||
|
{
|
||||||
|
Height = app.Position.Height,
|
||||||
|
Width = app.Position.Width,
|
||||||
|
X = app.Position.X,
|
||||||
|
Y = app.Position.Y,
|
||||||
|
},
|
||||||
|
MonitorNumber = app.Monitor,
|
||||||
|
};
|
||||||
|
newApp.InitializationFinished();
|
||||||
|
Applications.Add(newApp);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var monitor in project.MonitorConfiguration)
|
||||||
|
{
|
||||||
|
System.Windows.Rect dpiAware = new System.Windows.Rect(monitor.MonitorRectDpiAware.Left, monitor.MonitorRectDpiAware.Top, monitor.MonitorRectDpiAware.Width, monitor.MonitorRectDpiAware.Height);
|
||||||
|
System.Windows.Rect dpiUnaware = new System.Windows.Rect(monitor.MonitorRectDpiUnaware.Left, monitor.MonitorRectDpiUnaware.Top, monitor.MonitorRectDpiUnaware.Width, monitor.MonitorRectDpiUnaware.Height);
|
||||||
|
Monitors.Add(new MonitorSetup(monitor.Id, monitor.InstanceId, monitor.MonitorNumber, monitor.Dpi, dpiAware, dpiUnaware));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BitmapImage PreviewIcons
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _previewIcons;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_previewIcons = value;
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(PreviewIcons)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BitmapImage PreviewImage
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _previewImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_previewImage = value;
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(PreviewImage)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double PreviewImageWidth
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _previewImageWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_previewImageWidth = value;
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(PreviewImageWidth)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
|
public void OnPropertyChanged(PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void Initialize(Theme currentTheme)
|
||||||
|
{
|
||||||
|
PreviewIcons = await Task.Run(() => DrawHelper.DrawPreviewIcons(this));
|
||||||
|
Rectangle commonBounds = GetCommonBounds();
|
||||||
|
PreviewImage = await Task.Run(() => DrawHelper.DrawPreview(this, commonBounds, currentTheme));
|
||||||
|
PreviewImageWidth = commonBounds.Width / (commonBounds.Height * 1.2 / 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Rectangle GetCommonBounds()
|
||||||
|
{
|
||||||
|
double minX = Monitors.First().MonitorDpiAwareBounds.Left;
|
||||||
|
double minY = Monitors.First().MonitorDpiAwareBounds.Top;
|
||||||
|
double maxX = Monitors.First().MonitorDpiAwareBounds.Right;
|
||||||
|
double maxY = Monitors.First().MonitorDpiAwareBounds.Bottom;
|
||||||
|
for (int monitorIndex = 1; monitorIndex < Monitors.Count; monitorIndex++)
|
||||||
|
{
|
||||||
|
Monitor monitor = Monitors[monitorIndex];
|
||||||
|
minX = Math.Min(minX, monitor.MonitorDpiAwareBounds.Left);
|
||||||
|
minY = Math.Min(minY, monitor.MonitorDpiAwareBounds.Top);
|
||||||
|
maxX = Math.Max(maxX, monitor.MonitorDpiAwareBounds.Right);
|
||||||
|
maxY = Math.Max(maxY, monitor.MonitorDpiAwareBounds.Bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Rectangle((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateAfterLaunchAndEdit(Project other)
|
||||||
|
{
|
||||||
|
Id = other.Id;
|
||||||
|
Name = other.Name;
|
||||||
|
IsRevertEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void CloseExpanders()
|
||||||
|
{
|
||||||
|
foreach (Application app in Applications)
|
||||||
|
{
|
||||||
|
app.IsExpanded = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
src/modules/Workspaces/WorkspacesEditor/OverlayWindow.xaml
Normal file
16
src/modules/Workspaces/WorkspacesEditor/OverlayWindow.xaml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<Window
|
||||||
|
x:Class="WorkspacesEditor.OverlayWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="clr-namespace:WorkspacesEditor"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
AllowsTransparency="True"
|
||||||
|
Background="Transparent"
|
||||||
|
WindowStyle="None"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<Border
|
||||||
|
Background="Transparent"
|
||||||
|
BorderBrush="Red"
|
||||||
|
BorderThickness="3" />
|
||||||
|
</Window>
|
@ -0,0 +1,19 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for OverlayWindow.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class OverlayWindow : Window
|
||||||
|
{
|
||||||
|
public OverlayWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
693
src/modules/Workspaces/WorkspacesEditor/Properties/Resources.Designer.cs
generated
Normal file
693
src/modules/Workspaces/WorkspacesEditor/Properties/Resources.Designer.cs
generated
Normal file
@ -0,0 +1,693 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This code was generated by a tool.
|
||||||
|
// Runtime Version:4.0.30319.42000
|
||||||
|
//
|
||||||
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
|
// the code is regenerated.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Properties {
|
||||||
|
using System;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||||
|
/// </summary>
|
||||||
|
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||||
|
// class via a tool like ResGen or Visual Studio.
|
||||||
|
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||||
|
// with the /str option, or rebuild your VS project.
|
||||||
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
|
public class Resources {
|
||||||
|
|
||||||
|
private static global::System.Resources.ResourceManager resourceMan;
|
||||||
|
|
||||||
|
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||||
|
|
||||||
|
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||||
|
internal Resources() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the cached ResourceManager instance used by this class.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
|
public static global::System.Resources.ResourceManager ResourceManager {
|
||||||
|
get {
|
||||||
|
if (object.ReferenceEquals(resourceMan, null)) {
|
||||||
|
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WorkspacesEditor.Properties.Resources", typeof(Resources).Assembly);
|
||||||
|
resourceMan = temp;
|
||||||
|
}
|
||||||
|
return resourceMan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overrides the current thread's CurrentUICulture property for all
|
||||||
|
/// resource lookups using this strongly typed resource class.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
|
public static global::System.Globalization.CultureInfo Culture {
|
||||||
|
get {
|
||||||
|
return resourceCulture;
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
resourceCulture = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Add Back.
|
||||||
|
/// </summary>
|
||||||
|
public static string AddBack {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("AddBack", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Admin.
|
||||||
|
/// </summary>
|
||||||
|
public static string Admin {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Admin", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Launch new app instances.
|
||||||
|
/// </summary>
|
||||||
|
public static string AlwaysLaunch {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("AlwaysLaunch", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to app.
|
||||||
|
/// </summary>
|
||||||
|
public static string App {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("App", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to App name.
|
||||||
|
/// </summary>
|
||||||
|
public static string App_name {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("App_name", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to apps.
|
||||||
|
/// </summary>
|
||||||
|
public static string Apps {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Apps", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Are you sure?.
|
||||||
|
/// </summary>
|
||||||
|
public static string Are_You_Sure {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Are_You_Sure", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Are you sure you want to delete this Workspace?.
|
||||||
|
/// </summary>
|
||||||
|
public static string Are_You_Sure_Description {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Are_You_Sure_Description", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Args.
|
||||||
|
/// </summary>
|
||||||
|
public static string Args {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Args", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Cancel.
|
||||||
|
/// </summary>
|
||||||
|
public static string Cancel {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Cancel", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to CLI arguments.
|
||||||
|
/// </summary>
|
||||||
|
public static string CliArguments {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("CliArguments", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Created.
|
||||||
|
/// </summary>
|
||||||
|
public static string Created {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Created", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Create Desktop Shortcut.
|
||||||
|
/// </summary>
|
||||||
|
public static string CreateShortcut {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("CreateShortcut", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Create Workspace.
|
||||||
|
/// </summary>
|
||||||
|
public static string CreateWorkspace {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("CreateWorkspace", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to days ago.
|
||||||
|
/// </summary>
|
||||||
|
public static string DaysAgo {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("DaysAgo", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Workspace.
|
||||||
|
/// </summary>
|
||||||
|
public static string DefaultWorkspaceNamePrefix {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("DefaultWorkspaceNamePrefix", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Remove.
|
||||||
|
/// </summary>
|
||||||
|
public static string Delete {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Delete", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Delete Workspace dialog..
|
||||||
|
/// </summary>
|
||||||
|
public static string Delete_Workspace_Dialog_Announce {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Delete_Workspace_Dialog_Announce", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Remove Selected Apps.
|
||||||
|
/// </summary>
|
||||||
|
public static string DeleteSelected {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("DeleteSelected", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Edit.
|
||||||
|
/// </summary>
|
||||||
|
public static string Edit {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Edit", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to opened.
|
||||||
|
/// </summary>
|
||||||
|
public static string Edit_Project_Open_Announce {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Edit_Project_Open_Announce", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Edit Workspace.
|
||||||
|
/// </summary>
|
||||||
|
public static string EditWorkspace {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("EditWorkspace", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Error parsing Workspaces data..
|
||||||
|
/// </summary>
|
||||||
|
public static string Error_Parsing_Message {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Error_Parsing_Message", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Height.
|
||||||
|
/// </summary>
|
||||||
|
public static string Height {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Height", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to hours ago.
|
||||||
|
/// </summary>
|
||||||
|
public static string HoursAgo {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("HoursAgo", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Last launched.
|
||||||
|
/// </summary>
|
||||||
|
public static string LastLaunched {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("LastLaunched", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Launch.
|
||||||
|
/// </summary>
|
||||||
|
public static string Launch {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Launch", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Launch args.
|
||||||
|
/// </summary>
|
||||||
|
public static string Launch_args {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Launch_args", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Launch as Admin.
|
||||||
|
/// </summary>
|
||||||
|
public static string LaunchAsAdmin {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("LaunchAsAdmin", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Launch & Edit.
|
||||||
|
/// </summary>
|
||||||
|
public static string LaunchEdit {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("LaunchEdit", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Left.
|
||||||
|
/// </summary>
|
||||||
|
public static string Left {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Left", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Workspaces Editor.
|
||||||
|
/// </summary>
|
||||||
|
public static string MainTitle {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("MainTitle", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Maximized.
|
||||||
|
/// </summary>
|
||||||
|
public static string Maximized {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Maximized", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Minimized.
|
||||||
|
/// </summary>
|
||||||
|
public static string Minimized {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Minimized", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Minimized Apps.
|
||||||
|
/// </summary>
|
||||||
|
public static string Minimized_Apps {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Minimized_Apps", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to minutes ago.
|
||||||
|
/// </summary>
|
||||||
|
public static string MinutesAgo {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("MinutesAgo", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to months ago.
|
||||||
|
/// </summary>
|
||||||
|
public static string MonthsAgo {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("MonthsAgo", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Move apps if present.
|
||||||
|
/// </summary>
|
||||||
|
public static string MoveIfExist {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("MoveIfExist", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Name.
|
||||||
|
/// </summary>
|
||||||
|
public static string Name {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Name", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to never.
|
||||||
|
/// </summary>
|
||||||
|
public static string Never {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Never", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to New Workspace.
|
||||||
|
/// </summary>
|
||||||
|
public static string New_Workspace {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("New_Workspace", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to There are no saved Workspaces..
|
||||||
|
/// </summary>
|
||||||
|
public static string No_Workspaces_Message {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("No_Workspaces_Message", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to The application cannot be found.
|
||||||
|
/// </summary>
|
||||||
|
public static string NotFoundTooltip {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("NotFoundTooltip", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to No Workspaces match the current search..
|
||||||
|
/// </summary>
|
||||||
|
public static string NoWorkspacesMatch {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("NoWorkspacesMatch", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to an hour ago.
|
||||||
|
/// </summary>
|
||||||
|
public static string OneHourAgo {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("OneHourAgo", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to a minute ago.
|
||||||
|
/// </summary>
|
||||||
|
public static string OneMinuteAgo {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("OneMinuteAgo", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to one month ago.
|
||||||
|
/// </summary>
|
||||||
|
public static string OneMonthAgo {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("OneMonthAgo", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to one second ago.
|
||||||
|
/// </summary>
|
||||||
|
public static string OneSecondAgo {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("OneSecondAgo", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to one year ago.
|
||||||
|
/// </summary>
|
||||||
|
public static string OneYearAgo {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("OneYearAgo", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Pin Workspaces to Taskbar.
|
||||||
|
/// </summary>
|
||||||
|
public static string PinToTaskbar {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("PinToTaskbar", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to recently.
|
||||||
|
/// </summary>
|
||||||
|
public static string Recently {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Recently", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Revert.
|
||||||
|
/// </summary>
|
||||||
|
public static string Revert {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Revert", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Save Workspace.
|
||||||
|
/// </summary>
|
||||||
|
public static string Save_Workspace {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Save_Workspace", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Search.
|
||||||
|
/// </summary>
|
||||||
|
public static string Search {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Search", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Search for Workspaces or apps.
|
||||||
|
/// </summary>
|
||||||
|
public static string SearchExplanation {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SearchExplanation", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to seconds ago.
|
||||||
|
/// </summary>
|
||||||
|
public static string SecondsAgo {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SecondsAgo", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Select All Apps on.
|
||||||
|
/// </summary>
|
||||||
|
public static string SelectAllAppsOnMonitor {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SelectAllAppsOnMonitor", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Select All Minimized Apps.
|
||||||
|
/// </summary>
|
||||||
|
public static string SelectAllMinimizedApps {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SelectAllMinimizedApps", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Select All Apps in Workspace.
|
||||||
|
/// </summary>
|
||||||
|
public static string SelectedAllInWorkspace {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SelectedAllInWorkspace", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Edit your layout and click "Capture" when finished..
|
||||||
|
/// </summary>
|
||||||
|
public static string SnapshotDescription {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SnapshotDescription", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Snapshot Creator.
|
||||||
|
/// </summary>
|
||||||
|
public static string SnapshotWindowTitle {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SnapshotWindowTitle", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Sort by.
|
||||||
|
/// </summary>
|
||||||
|
public static string SortBy {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SortBy", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Capture.
|
||||||
|
/// </summary>
|
||||||
|
public static string Take_Snapshot {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Take_Snapshot", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Top.
|
||||||
|
/// </summary>
|
||||||
|
public static string Top {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Top", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Width.
|
||||||
|
/// </summary>
|
||||||
|
public static string Width {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Width", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Workspace name.
|
||||||
|
/// </summary>
|
||||||
|
public static string WorkspaceName {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("WorkspaceName", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Workspaces.
|
||||||
|
/// </summary>
|
||||||
|
public static string Workspaces {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Workspaces", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Write arguments here.
|
||||||
|
/// </summary>
|
||||||
|
public static string WriteArgs {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("WriteArgs", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to years ago.
|
||||||
|
/// </summary>
|
||||||
|
public static string YearsAgo {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("YearsAgo", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to yesterday.
|
||||||
|
/// </summary>
|
||||||
|
public static string Yesterday {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Yesterday", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,333 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="AddBack" xml:space="preserve">
|
||||||
|
<value>Add back</value>
|
||||||
|
</data>
|
||||||
|
<data name="Admin" xml:space="preserve">
|
||||||
|
<value>Admin</value>
|
||||||
|
</data>
|
||||||
|
<data name="AlwaysLaunch" xml:space="preserve">
|
||||||
|
<value>Launch new app instances</value>
|
||||||
|
</data>
|
||||||
|
<data name="App" xml:space="preserve">
|
||||||
|
<value>app</value>
|
||||||
|
</data>
|
||||||
|
<data name="Apps" xml:space="preserve">
|
||||||
|
<value>apps</value>
|
||||||
|
</data>
|
||||||
|
<data name="App_name" xml:space="preserve">
|
||||||
|
<value>App name</value>
|
||||||
|
</data>
|
||||||
|
<data name="Are_You_Sure" xml:space="preserve">
|
||||||
|
<value>Are you sure?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Are_You_Sure_Description" xml:space="preserve">
|
||||||
|
<value>Are you sure you want to delete this Workspace?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Args" xml:space="preserve">
|
||||||
|
<value>Args</value>
|
||||||
|
<comment>Arguments</comment>
|
||||||
|
</data>
|
||||||
|
<data name="Cancel" xml:space="preserve">
|
||||||
|
<value>Cancel</value>
|
||||||
|
</data>
|
||||||
|
<data name="CliArguments" xml:space="preserve">
|
||||||
|
<value>CLI arguments</value>
|
||||||
|
</data>
|
||||||
|
<data name="Created" xml:space="preserve">
|
||||||
|
<value>Created</value>
|
||||||
|
</data>
|
||||||
|
<data name="CreateWorkspace" xml:space="preserve">
|
||||||
|
<value>Create Workspace</value>
|
||||||
|
</data>
|
||||||
|
<data name="CreateShortcut" xml:space="preserve">
|
||||||
|
<value>Create desktop shortcut</value>
|
||||||
|
</data>
|
||||||
|
<data name="DaysAgo" xml:space="preserve">
|
||||||
|
<value>days ago</value>
|
||||||
|
</data>
|
||||||
|
<data name="DefaultWorkspaceNamePrefix" xml:space="preserve">
|
||||||
|
<value>Workspace</value>
|
||||||
|
</data>
|
||||||
|
<data name="Delete" xml:space="preserve">
|
||||||
|
<value>Remove</value>
|
||||||
|
</data>
|
||||||
|
<data name="DeleteSelected" xml:space="preserve">
|
||||||
|
<value>Remove selected apps</value>
|
||||||
|
</data>
|
||||||
|
<data name="Delete_Workspace_Dialog_Announce" xml:space="preserve">
|
||||||
|
<value>Delete Workspace dialog.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Edit" xml:space="preserve">
|
||||||
|
<value>Edit</value>
|
||||||
|
</data>
|
||||||
|
<data name="EditWorkspace" xml:space="preserve">
|
||||||
|
<value>Edit Workspace</value>
|
||||||
|
</data>
|
||||||
|
<data name="Edit_Project_Open_Announce" xml:space="preserve">
|
||||||
|
<value>opened</value>
|
||||||
|
</data>
|
||||||
|
<data name="Error_Parsing_Message" xml:space="preserve">
|
||||||
|
<value>Error parsing Workspaces data.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Height" xml:space="preserve">
|
||||||
|
<value>Height</value>
|
||||||
|
</data>
|
||||||
|
<data name="HoursAgo" xml:space="preserve">
|
||||||
|
<value>hours ago</value>
|
||||||
|
</data>
|
||||||
|
<data name="LastLaunched" xml:space="preserve">
|
||||||
|
<value>Last launched</value>
|
||||||
|
</data>
|
||||||
|
<data name="Launch" xml:space="preserve">
|
||||||
|
<value>Launch</value>
|
||||||
|
</data>
|
||||||
|
<data name="LaunchAsAdmin" xml:space="preserve">
|
||||||
|
<value>Launch as Admin</value>
|
||||||
|
</data>
|
||||||
|
<data name="LaunchEdit" xml:space="preserve">
|
||||||
|
<value>Launch & edit</value>
|
||||||
|
</data>
|
||||||
|
<data name="Launch_args" xml:space="preserve">
|
||||||
|
<value>Launch args</value>
|
||||||
|
</data>
|
||||||
|
<data name="Left" xml:space="preserve">
|
||||||
|
<value>Left</value>
|
||||||
|
<comment>the left x coordinate</comment>
|
||||||
|
</data>
|
||||||
|
<data name="MainTitle" xml:space="preserve">
|
||||||
|
<value>Workspaces Editor</value>
|
||||||
|
</data>
|
||||||
|
<data name="Maximized" xml:space="preserve">
|
||||||
|
<value>Maximized</value>
|
||||||
|
</data>
|
||||||
|
<data name="Minimized" xml:space="preserve">
|
||||||
|
<value>Minimized</value>
|
||||||
|
</data>
|
||||||
|
<data name="Minimized_Apps" xml:space="preserve">
|
||||||
|
<value>Minimized apps</value>
|
||||||
|
</data>
|
||||||
|
<data name="MinutesAgo" xml:space="preserve">
|
||||||
|
<value>minutes ago</value>
|
||||||
|
</data>
|
||||||
|
<data name="MonthsAgo" xml:space="preserve">
|
||||||
|
<value>months ago</value>
|
||||||
|
</data>
|
||||||
|
<data name="MoveIfExist" xml:space="preserve">
|
||||||
|
<value>Move apps if present</value>
|
||||||
|
</data>
|
||||||
|
<data name="Name" xml:space="preserve">
|
||||||
|
<value>Name</value>
|
||||||
|
</data>
|
||||||
|
<data name="Never" xml:space="preserve">
|
||||||
|
<value>never</value>
|
||||||
|
</data>
|
||||||
|
<data name="New_Workspace" xml:space="preserve">
|
||||||
|
<value>New Workspace</value>
|
||||||
|
</data>
|
||||||
|
<data name="NoWorkspacesMatch" xml:space="preserve">
|
||||||
|
<value>No Workspaces match the current search.</value>
|
||||||
|
</data>
|
||||||
|
<data name="NotFoundTooltip" xml:space="preserve">
|
||||||
|
<value>The application cannot be found</value>
|
||||||
|
</data>
|
||||||
|
<data name="No_Workspaces_Message" xml:space="preserve">
|
||||||
|
<value>There are no saved Workspaces.</value>
|
||||||
|
</data>
|
||||||
|
<data name="OneHourAgo" xml:space="preserve">
|
||||||
|
<value>an hour ago</value>
|
||||||
|
</data>
|
||||||
|
<data name="OneMinuteAgo" xml:space="preserve">
|
||||||
|
<value>a minute ago</value>
|
||||||
|
</data>
|
||||||
|
<data name="OneMonthAgo" xml:space="preserve">
|
||||||
|
<value>one month ago</value>
|
||||||
|
</data>
|
||||||
|
<data name="OneSecondAgo" xml:space="preserve">
|
||||||
|
<value>one second ago</value>
|
||||||
|
</data>
|
||||||
|
<data name="OneYearAgo" xml:space="preserve">
|
||||||
|
<value>one year ago</value>
|
||||||
|
</data>
|
||||||
|
<data name="PinToTaskbar" xml:space="preserve">
|
||||||
|
<value>Pin Workspaces to taskbar</value>
|
||||||
|
</data>
|
||||||
|
<data name="WorkspaceName" xml:space="preserve">
|
||||||
|
<value>Workspace name</value>
|
||||||
|
</data>
|
||||||
|
<data name="Workspaces" xml:space="preserve">
|
||||||
|
<value>Workspaces</value>
|
||||||
|
</data>
|
||||||
|
<data name="Recently" xml:space="preserve">
|
||||||
|
<value>recently</value>
|
||||||
|
</data>
|
||||||
|
<data name="Revert" xml:space="preserve">
|
||||||
|
<value>Revert</value>
|
||||||
|
</data>
|
||||||
|
<data name="Save_Workspace" xml:space="preserve">
|
||||||
|
<value>Save Workspace</value>
|
||||||
|
</data>
|
||||||
|
<data name="Search" xml:space="preserve">
|
||||||
|
<value>Search</value>
|
||||||
|
</data>
|
||||||
|
<data name="SearchExplanation" xml:space="preserve">
|
||||||
|
<value>Search for Workspaces or apps</value>
|
||||||
|
</data>
|
||||||
|
<data name="SecondsAgo" xml:space="preserve">
|
||||||
|
<value>seconds ago</value>
|
||||||
|
</data>
|
||||||
|
<data name="SelectAllAppsOnMonitor" xml:space="preserve">
|
||||||
|
<value>Select all apps on</value>
|
||||||
|
</data>
|
||||||
|
<data name="SelectAllMinimizedApps" xml:space="preserve">
|
||||||
|
<value>Select all minimized apps</value>
|
||||||
|
</data>
|
||||||
|
<data name="SelectedAllInWorkspace" xml:space="preserve">
|
||||||
|
<value>Select all apps in Workspace</value>
|
||||||
|
</data>
|
||||||
|
<data name="SnapshotDescription" xml:space="preserve">
|
||||||
|
<value>Edit your layout and click "Capture" when finished.</value>
|
||||||
|
</data>
|
||||||
|
<data name="SnapshotWindowTitle" xml:space="preserve">
|
||||||
|
<value>Snapshot Creator</value>
|
||||||
|
</data>
|
||||||
|
<data name="SortBy" xml:space="preserve">
|
||||||
|
<value>Sort by</value>
|
||||||
|
</data>
|
||||||
|
<data name="Take_Snapshot" xml:space="preserve">
|
||||||
|
<value>Capture</value>
|
||||||
|
</data>
|
||||||
|
<data name="Top" xml:space="preserve">
|
||||||
|
<value>Top</value>
|
||||||
|
<comment>the top y coordinate</comment>
|
||||||
|
</data>
|
||||||
|
<data name="Width" xml:space="preserve">
|
||||||
|
<value>Width</value>
|
||||||
|
</data>
|
||||||
|
<data name="WriteArgs" xml:space="preserve">
|
||||||
|
<value>Write arguments here</value>
|
||||||
|
</data>
|
||||||
|
<data name="YearsAgo" xml:space="preserve">
|
||||||
|
<value>years ago</value>
|
||||||
|
</data>
|
||||||
|
<data name="Yesterday" xml:space="preserve">
|
||||||
|
<value>yesterday</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
26
src/modules/Workspaces/WorkspacesEditor/Properties/Settings.Designer.cs
generated
Normal file
26
src/modules/Workspaces/WorkspacesEditor/Properties/Settings.Designer.cs
generated
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This code was generated by a tool.
|
||||||
|
// Runtime Version:4.0.30319.42000
|
||||||
|
//
|
||||||
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
|
// the code is regenerated.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Properties {
|
||||||
|
|
||||||
|
|
||||||
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.1.0.0")]
|
||||||
|
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||||
|
|
||||||
|
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||||
|
|
||||||
|
public static Settings Default {
|
||||||
|
get {
|
||||||
|
return defaultInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
|
||||||
|
<Profiles>
|
||||||
|
<Profile Name="(Default)" />
|
||||||
|
</Profiles>
|
||||||
|
<Settings />
|
||||||
|
</SettingsFile>
|
59
src/modules/Workspaces/WorkspacesEditor/SnapshotWindow.xaml
Normal file
59
src/modules/Workspaces/WorkspacesEditor/SnapshotWindow.xaml
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<Window
|
||||||
|
x:Class="WorkspacesEditor.SnapshotWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="clr-namespace:WorkspacesEditor"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:props="clr-namespace:WorkspacesEditor.Properties"
|
||||||
|
xmlns:ui="http://schemas.modernwpf.com/2019"
|
||||||
|
Title="{x:Static props:Resources.SnapshotWindowTitle}"
|
||||||
|
Width="350"
|
||||||
|
Height="140"
|
||||||
|
ui:TitleBar.Background="{DynamicResource PrimaryBackgroundBrush}"
|
||||||
|
ui:TitleBar.InactiveBackground="{DynamicResource TertiaryBackgroundBrush}"
|
||||||
|
ui:WindowHelper.UseModernWindowStyle="True"
|
||||||
|
Background="{DynamicResource PrimaryBackgroundBrush}"
|
||||||
|
BorderBrush="Red"
|
||||||
|
BorderThickness="5"
|
||||||
|
Closing="Window_Closing"
|
||||||
|
ResizeMode="NoResize"
|
||||||
|
WindowStartupLocation="CenterScreen"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<Grid Margin="5" Background="Transparent">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="1*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="1*" />
|
||||||
|
<ColumnDefinition Width="1*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Margin="5"
|
||||||
|
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||||
|
Text="{x:Static props:Resources.SnapshotDescription}" />
|
||||||
|
<Button
|
||||||
|
x:Name="CancelButton"
|
||||||
|
Grid.Row="1"
|
||||||
|
Height="36"
|
||||||
|
Margin="5,5,5,5"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
AutomationProperties.Name="{x:Static props:Resources.Cancel}"
|
||||||
|
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||||
|
Click="CancelButtonClicked"
|
||||||
|
Content="{x:Static props:Resources.Cancel}" />
|
||||||
|
<Button
|
||||||
|
x:Name="SnapshotButton"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="1"
|
||||||
|
Height="36"
|
||||||
|
Margin="5,5,5,5"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
AutomationProperties.Name="{x:Static props:Resources.Take_Snapshot}"
|
||||||
|
Click="SnapshotButtonClicked"
|
||||||
|
Content="{x:Static props:Resources.Take_Snapshot}"
|
||||||
|
Style="{StaticResource AccentButtonStyle}" />
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
@ -0,0 +1,40 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Windows;
|
||||||
|
using WorkspacesEditor.ViewModels;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for SnapshotWindow.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class SnapshotWindow : Window
|
||||||
|
{
|
||||||
|
private MainViewModel _mainViewModel;
|
||||||
|
|
||||||
|
public SnapshotWindow(MainViewModel mainViewModel)
|
||||||
|
{
|
||||||
|
_mainViewModel = mainViewModel;
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelButtonClicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
_mainViewModel.CancelSnapshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SnapshotButtonClicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
_mainViewModel.SnapWorkspace();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
|
||||||
|
{
|
||||||
|
_mainViewModel.CancelSnapshot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
<ResourceDictionary
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:ui="http://schemas.modernwpf.com/2019">
|
||||||
|
|
||||||
|
<Style
|
||||||
|
x:Key="IconOnlyButtonStyle"
|
||||||
|
BasedOn="{StaticResource DefaultButtonStyle}"
|
||||||
|
TargetType="Button">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="Button">
|
||||||
|
<Border
|
||||||
|
x:Name="Background"
|
||||||
|
Background="Transparent"
|
||||||
|
CornerRadius="{TemplateBinding ui:ControlHelper.CornerRadius}"
|
||||||
|
SnapsToDevicePixels="True">
|
||||||
|
<Border
|
||||||
|
x:Name="Border"
|
||||||
|
Padding="{TemplateBinding Padding}"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
CornerRadius="{TemplateBinding ui:ControlHelper.CornerRadius}">
|
||||||
|
<ContentPresenter
|
||||||
|
x:Name="ContentPresenter"
|
||||||
|
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||||
|
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||||
|
Focusable="False"
|
||||||
|
RecognizesAccessKey="True"
|
||||||
|
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||||
|
</Border>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="Background" Property="Background" Value="{DynamicResource ButtonBackgroundPointerOver}" />
|
||||||
|
<Setter TargetName="Border" Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPointerOver}" />
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsPressed" Value="True">
|
||||||
|
<Setter TargetName="Background" Property="Background" Value="{DynamicResource ButtonBackgroundPressed}" />
|
||||||
|
<Setter TargetName="Border" Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPressed}" />
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsEnabled" Value="False">
|
||||||
|
<Setter TargetName="Background" Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" />
|
||||||
|
<Setter TargetName="Border" Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" />
|
||||||
|
<Setter TargetName="ContentPresenter" Property="TextElement.Foreground" Value="{DynamicResource ButtonForegroundDisabled}" />
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
@ -0,0 +1,39 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Diagnostics.Tracing;
|
||||||
|
using Microsoft.PowerToys.Telemetry;
|
||||||
|
using Microsoft.PowerToys.Telemetry.Events;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Telemetry
|
||||||
|
{
|
||||||
|
[EventData]
|
||||||
|
public class CreateEvent : EventBase, IEvent
|
||||||
|
{
|
||||||
|
public CreateEvent()
|
||||||
|
{
|
||||||
|
EventName = "Workspaces_CreateEvent";
|
||||||
|
}
|
||||||
|
|
||||||
|
// True if operation successfully completely. False if failed
|
||||||
|
public bool Successful { get; set; }
|
||||||
|
|
||||||
|
// Number of screens present in the project
|
||||||
|
public int NumScreens { get; set; }
|
||||||
|
|
||||||
|
// Total number of apps in the project
|
||||||
|
public int AppCount { get; set; }
|
||||||
|
|
||||||
|
// Number of apps with CLI args
|
||||||
|
public int CliCount { get; set; }
|
||||||
|
|
||||||
|
// Number of apps with "Launch as admin" set
|
||||||
|
public int AdminCount { get; set; }
|
||||||
|
|
||||||
|
// True of user checked "Create Shortcut". False if not.
|
||||||
|
public bool ShortcutCreated { get; set; }
|
||||||
|
|
||||||
|
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Diagnostics.Tracing;
|
||||||
|
using Microsoft.PowerToys.Telemetry;
|
||||||
|
using Microsoft.PowerToys.Telemetry.Events;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Telemetry
|
||||||
|
{
|
||||||
|
[EventData]
|
||||||
|
public class DeleteEvent : EventBase, IEvent
|
||||||
|
{
|
||||||
|
public DeleteEvent()
|
||||||
|
{
|
||||||
|
EventName = "Workspaces_DeleteEvent";
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Successful { get; set; }
|
||||||
|
|
||||||
|
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Diagnostics.Tracing;
|
||||||
|
using Microsoft.PowerToys.Telemetry;
|
||||||
|
using Microsoft.PowerToys.Telemetry.Events;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Telemetry
|
||||||
|
{
|
||||||
|
[EventData]
|
||||||
|
public class EditEvent : EventBase, IEvent
|
||||||
|
{
|
||||||
|
public EditEvent()
|
||||||
|
{
|
||||||
|
EventName = "Workspaces_EditEvent";
|
||||||
|
}
|
||||||
|
|
||||||
|
// True if operation successfully completely. False if failed.
|
||||||
|
public bool Successful { get; set; }
|
||||||
|
|
||||||
|
// Change in number of screens in project
|
||||||
|
public int ScreenCountDelta { get; set; }
|
||||||
|
|
||||||
|
// Number of apps added to project through editing
|
||||||
|
public int AppsAdded { get; set; }
|
||||||
|
|
||||||
|
// Number of apps removed from project through editing
|
||||||
|
public int AppsRemoved { get; set; }
|
||||||
|
|
||||||
|
// Number of apps with CLI args added
|
||||||
|
public int CliAdded { get; set; }
|
||||||
|
|
||||||
|
// Number of apps with CLI args removed
|
||||||
|
public int CliRemoved { get; set; }
|
||||||
|
|
||||||
|
// Number of apps with admin added
|
||||||
|
public int AdminAdded { get; set; }
|
||||||
|
|
||||||
|
// Number of apps with admin removed
|
||||||
|
public int AdminRemoved { get; set; }
|
||||||
|
|
||||||
|
// True if used window pixel sizing boxes to adjust size
|
||||||
|
public bool PixelAdjustmentsUsed { get; set; }
|
||||||
|
|
||||||
|
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||||
|
}
|
||||||
|
}
|
26
src/modules/Workspaces/WorkspacesEditor/Themes/Dark.xaml
Normal file
26
src/modules/Workspaces/WorkspacesEditor/Themes/Dark.xaml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<ResourceDictionary
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:system="clr-namespace:System;assembly=System.Runtime">
|
||||||
|
|
||||||
|
<!-- Metadata -->
|
||||||
|
<system:String x:Key="Theme.Name">Dark.Accent1</system:String>
|
||||||
|
<system:String x:Key="Theme.Origin">Origin</system:String>
|
||||||
|
<system:String x:Key="Theme.DisplayName">Accent1 (Dark)</system:String>
|
||||||
|
<system:String x:Key="Theme.BaseColorScheme">Dark</system:String>
|
||||||
|
<system:String x:Key="Theme.ColorScheme">Accent1</system:String>
|
||||||
|
<Color x:Key="Theme.PrimaryAccentColor">Black</Color>
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="PrimaryBackgroundBrush" Color="#FF242424" />
|
||||||
|
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="#FF2b2b2b" />
|
||||||
|
<SolidColorBrush x:Key="TertiaryBackgroundBrush" Color="#FF373737" />
|
||||||
|
<SolidColorBrush x:Key="QuaternaryBackgroundBrush" Color="#FF272727" />
|
||||||
|
<SolidColorBrush x:Key="MonitorViewBackgroundBrush" Color="#FF161616" />
|
||||||
|
<SolidColorBrush x:Key="PrimaryForegroundBrush" Color="#FFFFFFFF" />
|
||||||
|
<SolidColorBrush x:Key="SecondaryForegroundBrush" Color="#FF9a9a9a" />
|
||||||
|
<SolidColorBrush x:Key="PrimaryBorderBrush" Color="#FF373737" />
|
||||||
|
<SolidColorBrush x:Key="SecondaryBorderBrush" Color="#FF494949" />
|
||||||
|
<SolidColorBrush x:Key="TertiaryBorderBrush" Color="#FF202020" />
|
||||||
|
<SolidColorBrush x:Key="BackdropBrush" Color="#40F0F0F0" />
|
||||||
|
<SolidColorBrush x:Key="TitleBarSecondaryForegroundBrush" Color="#FF9a9a9a" />
|
||||||
|
</ResourceDictionary>
|
@ -0,0 +1,26 @@
|
|||||||
|
<ResourceDictionary
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:system="clr-namespace:System;assembly=System.Runtime">
|
||||||
|
|
||||||
|
<!-- Metadata -->
|
||||||
|
<system:String x:Key="Theme.Name">HighContrast.Accent2</system:String>
|
||||||
|
<system:String x:Key="Theme.Origin">Origin</system:String>
|
||||||
|
<system:String x:Key="Theme.DisplayName">Accent2 (HighContrast)</system:String>
|
||||||
|
<system:String x:Key="Theme.BaseColorScheme">HighContrast</system:String>
|
||||||
|
<system:String x:Key="Theme.ColorScheme">Accent2</system:String>
|
||||||
|
<Color x:Key="Theme.PrimaryAccentColor">White</Color>
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="PrimaryBackgroundBrush" Color="#FF242424" />
|
||||||
|
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="#FF1c1c1c" />
|
||||||
|
<SolidColorBrush x:Key="TertiaryBackgroundBrush" Color="#FF202020" />
|
||||||
|
<SolidColorBrush x:Key="QuaternaryBackgroundBrush" Color="#FF272727" />
|
||||||
|
<SolidColorBrush x:Key="MonitorViewBackgroundBrush" Color="#FF161616" />
|
||||||
|
<SolidColorBrush x:Key="PrimaryForegroundBrush" Color="#FFffff00" />
|
||||||
|
<SolidColorBrush x:Key="SecondaryForegroundBrush" Color="#FF00ff00" />
|
||||||
|
<SolidColorBrush x:Key="PrimaryBorderBrush" Color="#FF373737" />
|
||||||
|
<SolidColorBrush x:Key="SecondaryBorderBrush" Color="#FF494949" />
|
||||||
|
<SolidColorBrush x:Key="TertiaryBorderBrush" Color="#FF202020" />
|
||||||
|
<SolidColorBrush x:Key="BackdropBrush" Color="#E55B5B5B" />
|
||||||
|
<SolidColorBrush x:Key="TitleBarSecondaryForegroundBrush" Color="#FF9a9a9a" />
|
||||||
|
</ResourceDictionary>
|
@ -0,0 +1,26 @@
|
|||||||
|
<ResourceDictionary
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:system="clr-namespace:System;assembly=System.Runtime">
|
||||||
|
|
||||||
|
<!-- Metadata -->
|
||||||
|
<system:String x:Key="Theme.Name">HighContrast.Accent3</system:String>
|
||||||
|
<system:String x:Key="Theme.Origin">Origin</system:String>
|
||||||
|
<system:String x:Key="Theme.DisplayName">Accent3 (HighContrast)</system:String>
|
||||||
|
<system:String x:Key="Theme.BaseColorScheme">HighContrast</system:String>
|
||||||
|
<system:String x:Key="Theme.ColorScheme">Accent3</system:String>
|
||||||
|
<Color x:Key="Theme.PrimaryAccentColor">White</Color>
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="PrimaryBackgroundBrush" Color="#FF242424" />
|
||||||
|
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="#FF1c1c1c" />
|
||||||
|
<SolidColorBrush x:Key="TertiaryBackgroundBrush" Color="#FF202020" />
|
||||||
|
<SolidColorBrush x:Key="QuaternaryBackgroundBrush" Color="#FF272727" />
|
||||||
|
<SolidColorBrush x:Key="MonitorViewBackgroundBrush" Color="#FF161616" />
|
||||||
|
<SolidColorBrush x:Key="PrimaryForegroundBrush" Color="#FFffff00" />
|
||||||
|
<SolidColorBrush x:Key="SecondaryForegroundBrush" Color="#FFc0c0c0" />
|
||||||
|
<SolidColorBrush x:Key="PrimaryBorderBrush" Color="#FF373737" />
|
||||||
|
<SolidColorBrush x:Key="SecondaryBorderBrush" Color="#FF494949" />
|
||||||
|
<SolidColorBrush x:Key="TertiaryBorderBrush" Color="#FF202020" />
|
||||||
|
<SolidColorBrush x:Key="BackdropBrush" Color="#E55B5B5B" />
|
||||||
|
<SolidColorBrush x:Key="TitleBarSecondaryForegroundBrush" Color="#FF9a9a9a" />
|
||||||
|
</ResourceDictionary>
|
@ -0,0 +1,26 @@
|
|||||||
|
<ResourceDictionary
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:system="clr-namespace:System;assembly=System.Runtime">
|
||||||
|
|
||||||
|
<!-- Metadata -->
|
||||||
|
<system:String x:Key="Theme.Name">HighContrast.Accent4</system:String>
|
||||||
|
<system:String x:Key="Theme.Origin">Origin</system:String>
|
||||||
|
<system:String x:Key="Theme.DisplayName">Accent4 (HighContrast)</system:String>
|
||||||
|
<system:String x:Key="Theme.BaseColorScheme">HighContrast</system:String>
|
||||||
|
<system:String x:Key="Theme.ColorScheme">Accent4</system:String>
|
||||||
|
<Color x:Key="Theme.PrimaryAccentColor">White</Color>
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="PrimaryBackgroundBrush" Color="#FF242424" />
|
||||||
|
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="#FF1c1c1c" />
|
||||||
|
<SolidColorBrush x:Key="TertiaryBackgroundBrush" Color="#FF202020" />
|
||||||
|
<SolidColorBrush x:Key="QuaternaryBackgroundBrush" Color="#FF272727" />
|
||||||
|
<SolidColorBrush x:Key="MonitorViewBackgroundBrush" Color="#FF161616" />
|
||||||
|
<SolidColorBrush x:Key="PrimaryForegroundBrush" Color="#FFffffff" />
|
||||||
|
<SolidColorBrush x:Key="SecondaryForegroundBrush" Color="#FF1aebff" />
|
||||||
|
<SolidColorBrush x:Key="PrimaryBorderBrush" Color="#FF373737" />
|
||||||
|
<SolidColorBrush x:Key="SecondaryBorderBrush" Color="#FF494949" />
|
||||||
|
<SolidColorBrush x:Key="TertiaryBorderBrush" Color="#FF202020" />
|
||||||
|
<SolidColorBrush x:Key="BackdropBrush" Color="#E55B5B5B" />
|
||||||
|
<SolidColorBrush x:Key="TitleBarSecondaryForegroundBrush" Color="#FF9a9a9a" />
|
||||||
|
</ResourceDictionary>
|
@ -0,0 +1,26 @@
|
|||||||
|
<ResourceDictionary
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:system="clr-namespace:System;assembly=System.Runtime">
|
||||||
|
|
||||||
|
<!-- Metadata -->
|
||||||
|
<system:String x:Key="Theme.Name">HighContrast.Accent5</system:String>
|
||||||
|
<system:String x:Key="Theme.Origin">Origin</system:String>
|
||||||
|
<system:String x:Key="Theme.DisplayName">Accent5 (HighContrast)</system:String>
|
||||||
|
<system:String x:Key="Theme.BaseColorScheme">HighContrast</system:String>
|
||||||
|
<system:String x:Key="Theme.ColorScheme">Accent5</system:String>
|
||||||
|
<Color x:Key="Theme.PrimaryAccentColor">White</Color>
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="PrimaryBackgroundBrush" Color="#FFf9f9f9" />
|
||||||
|
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="#FFeeeeee" />
|
||||||
|
<SolidColorBrush x:Key="TertiaryBackgroundBrush" Color="#FFF3F3F3" />
|
||||||
|
<SolidColorBrush x:Key="QuaternaryBackgroundBrush" Color="#FFf6f6f6" />
|
||||||
|
<SolidColorBrush x:Key="MonitorViewBackgroundBrush" Color="#FF161616" />
|
||||||
|
<SolidColorBrush x:Key="PrimaryForegroundBrush" Color="#FF000000" />
|
||||||
|
<SolidColorBrush x:Key="SecondaryForegroundBrush" Color="#FF37006e" />
|
||||||
|
<SolidColorBrush x:Key="PrimaryBorderBrush" Color="#FF373737" />
|
||||||
|
<SolidColorBrush x:Key="SecondaryBorderBrush" Color="#FF494949" />
|
||||||
|
<SolidColorBrush x:Key="TertiaryBorderBrush" Color="#FF202020" />
|
||||||
|
<SolidColorBrush x:Key="BackdropBrush" Color="#E5949494" />
|
||||||
|
<SolidColorBrush x:Key="TitleBarSecondaryForegroundBrush" Color="#FF949494" />
|
||||||
|
</ResourceDictionary>
|
26
src/modules/Workspaces/WorkspacesEditor/Themes/Light.xaml
Normal file
26
src/modules/Workspaces/WorkspacesEditor/Themes/Light.xaml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<ResourceDictionary
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:system="clr-namespace:System;assembly=System.Runtime">
|
||||||
|
|
||||||
|
<!-- Metadata -->
|
||||||
|
<system:String x:Key="Theme.Name">Light.Accent1</system:String>
|
||||||
|
<system:String x:Key="Theme.Origin">Origin</system:String>
|
||||||
|
<system:String x:Key="Theme.DisplayName">Accent1 (Light)</system:String>
|
||||||
|
<system:String x:Key="Theme.BaseColorScheme">Light</system:String>
|
||||||
|
<system:String x:Key="Theme.ColorScheme">Accent1</system:String>
|
||||||
|
<Color x:Key="Theme.PrimaryAccentColor">White</Color>
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="PrimaryBackgroundBrush" Color="#FFf3f3f3" />
|
||||||
|
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="#FFfbfbfb" />
|
||||||
|
<SolidColorBrush x:Key="TertiaryBackgroundBrush" Color="#FFfefefe" />
|
||||||
|
<SolidColorBrush x:Key="QuaternaryBackgroundBrush" Color="#FFf6f6f6" />
|
||||||
|
<SolidColorBrush x:Key="MonitorViewBackgroundBrush" Color="#FFF9F9F9" />
|
||||||
|
<SolidColorBrush x:Key="PrimaryForegroundBrush" Color="#FF191919" />
|
||||||
|
<SolidColorBrush x:Key="SecondaryForegroundBrush" Color="#FF6A6A6A" />
|
||||||
|
<SolidColorBrush x:Key="PrimaryBorderBrush" Color="#FFe5e5e5" />
|
||||||
|
<SolidColorBrush x:Key="SecondaryBorderBrush" Color="#FFe5e5e5" />
|
||||||
|
<SolidColorBrush x:Key="TertiaryBorderBrush" Color="#FFe5e5e5" />
|
||||||
|
<SolidColorBrush x:Key="BackdropBrush" Color="#85F0F0F0" />
|
||||||
|
<SolidColorBrush x:Key="TitleBarSecondaryForegroundBrush" Color="#FF949494" />
|
||||||
|
</ResourceDictionary>
|
@ -0,0 +1,18 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Utils
|
||||||
|
{
|
||||||
|
public class DashCaseNamingPolicy : JsonNamingPolicy
|
||||||
|
{
|
||||||
|
public static DashCaseNamingPolicy Instance { get; } = new DashCaseNamingPolicy();
|
||||||
|
|
||||||
|
public override string ConvertName(string name)
|
||||||
|
{
|
||||||
|
return name.UpperCamelCaseToDashCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
358
src/modules/Workspaces/WorkspacesEditor/Utils/DrawHelper.cs
Normal file
358
src/modules/Workspaces/WorkspacesEditor/Utils/DrawHelper.cs
Normal file
@ -0,0 +1,358 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Drawing.Drawing2D;
|
||||||
|
using System.Drawing.Imaging;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using ManagedCommon;
|
||||||
|
using WorkspacesEditor.Models;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Utils
|
||||||
|
{
|
||||||
|
public class DrawHelper
|
||||||
|
{
|
||||||
|
private static Font font = new("Tahoma", 24);
|
||||||
|
private static double scale = 0.1;
|
||||||
|
private static double gapWidth;
|
||||||
|
private static double gapHeight;
|
||||||
|
|
||||||
|
public static BitmapImage DrawPreview(Project project, Rectangle bounds, Theme currentTheme)
|
||||||
|
{
|
||||||
|
List<double> horizontalGaps = new List<double>();
|
||||||
|
List<double> verticalGaps = new List<double>();
|
||||||
|
gapWidth = bounds.Width * 0.01;
|
||||||
|
gapHeight = bounds.Height * 0.01;
|
||||||
|
|
||||||
|
int Scaled(double value)
|
||||||
|
{
|
||||||
|
return (int)(value * scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TransformX(double posX)
|
||||||
|
{
|
||||||
|
double gapTransform = verticalGaps.Where(x => x <= posX).Count() * gapWidth;
|
||||||
|
return Scaled(posX - bounds.Left + gapTransform);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TransformY(double posY)
|
||||||
|
{
|
||||||
|
double gapTransform = horizontalGaps.Where(x => x <= posY).Count() * gapHeight;
|
||||||
|
return Scaled(posY - bounds.Top + gapTransform);
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle GetAppRect(Application app)
|
||||||
|
{
|
||||||
|
if (app.Maximized)
|
||||||
|
{
|
||||||
|
Project project = app.Parent;
|
||||||
|
var monitor = project.Monitors.Where(x => x.MonitorNumber == app.MonitorNumber).FirstOrDefault();
|
||||||
|
return new Rectangle(TransformX(monitor.MonitorDpiAwareBounds.Left), TransformY(monitor.MonitorDpiAwareBounds.Top), Scaled(monitor.MonitorDpiAwareBounds.Width), Scaled(monitor.MonitorDpiAwareBounds.Height));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new Rectangle(TransformX(app.ScaledPosition.X), TransformY(app.ScaledPosition.Y), Scaled(app.ScaledPosition.Width), Scaled(app.ScaledPosition.Height));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary<string, int> repeatCounter = new Dictionary<string, int>();
|
||||||
|
|
||||||
|
var appsIncluded = project.Applications.Where(x => x.IsIncluded);
|
||||||
|
|
||||||
|
foreach (Application app in appsIncluded)
|
||||||
|
{
|
||||||
|
if (repeatCounter.TryGetValue(app.AppPath, out int value))
|
||||||
|
{
|
||||||
|
repeatCounter[app.AppPath] = ++value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
repeatCounter.Add(app.AppPath, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.RepeatIndex = repeatCounter[app.AppPath];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Application app in project.Applications.Where(x => !x.IsIncluded))
|
||||||
|
{
|
||||||
|
app.RepeatIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// now that all repeat index values are set, update the repeat index strings on UI
|
||||||
|
foreach (Application app in project.Applications)
|
||||||
|
{
|
||||||
|
app.OnPropertyChanged(new PropertyChangedEventArgs("RepeatIndexString"));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (MonitorSetup monitor in project.Monitors)
|
||||||
|
{
|
||||||
|
// check for vertical gap
|
||||||
|
if (monitor.MonitorDpiAwareBounds.Left > bounds.Left && project.Monitors.Any(x => x.MonitorDpiAwareBounds.Right <= monitor.MonitorDpiAwareBounds.Left))
|
||||||
|
{
|
||||||
|
verticalGaps.Add(monitor.MonitorDpiAwareBounds.Left);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for horizontal gap
|
||||||
|
if (monitor.MonitorDpiAwareBounds.Top > bounds.Top && project.Monitors.Any(x => x.MonitorDpiAwareBounds.Bottom <= monitor.MonitorDpiAwareBounds.Top))
|
||||||
|
{
|
||||||
|
horizontalGaps.Add(monitor.MonitorDpiAwareBounds.Top);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Bitmap previewBitmap = new Bitmap(Scaled(bounds.Width + (verticalGaps.Count * gapWidth)), Scaled((bounds.Height * 1.2) + (horizontalGaps.Count * gapHeight)));
|
||||||
|
double desiredIconSize = Scaled(Math.Min(bounds.Width, bounds.Height)) * 0.25;
|
||||||
|
using (Graphics g = Graphics.FromImage(previewBitmap))
|
||||||
|
{
|
||||||
|
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||||||
|
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||||
|
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||||
|
|
||||||
|
Brush brush = new SolidBrush(currentTheme == Theme.Dark ? Color.FromArgb(10, 255, 255, 255) : Color.FromArgb(10, 0, 0, 0));
|
||||||
|
Brush brushForHighlight = new SolidBrush(currentTheme == Theme.Dark ? Color.FromArgb(192, 255, 255, 255) : Color.FromArgb(192, 0, 0, 0));
|
||||||
|
|
||||||
|
// draw the monitors
|
||||||
|
foreach (MonitorSetup monitor in project.Monitors)
|
||||||
|
{
|
||||||
|
Brush monitorBrush = new SolidBrush(currentTheme == Theme.Dark ? Color.FromArgb(32, 7, 91, 155) : Color.FromArgb(32, 7, 91, 155));
|
||||||
|
g.FillRectangle(monitorBrush, new Rectangle(TransformX(monitor.MonitorDpiAwareBounds.Left), TransformY(monitor.MonitorDpiAwareBounds.Top), Scaled(monitor.MonitorDpiAwareBounds.Width), Scaled(monitor.MonitorDpiAwareBounds.Height)));
|
||||||
|
}
|
||||||
|
|
||||||
|
var appsToDraw = appsIncluded.Where(x => !x.Minimized);
|
||||||
|
|
||||||
|
// draw the highlighted app at the end to have its icon in the foreground for the case there are overlapping icons
|
||||||
|
foreach (Application app in appsToDraw.Where(x => !x.IsHighlighted))
|
||||||
|
{
|
||||||
|
Rectangle rect = GetAppRect(app);
|
||||||
|
DrawWindow(g, brush, rect, app, desiredIconSize, currentTheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Application app in appsToDraw.Where(x => x.IsHighlighted))
|
||||||
|
{
|
||||||
|
Rectangle rect = GetAppRect(app);
|
||||||
|
DrawWindow(g, brushForHighlight, rect, app, desiredIconSize, currentTheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw the minimized windows
|
||||||
|
Rectangle rectMinimized = new Rectangle(0, Scaled((bounds.Height * 1.02) + (horizontalGaps.Count * gapHeight)), Scaled(bounds.Width + (verticalGaps.Count * gapWidth)), Scaled(bounds.Height * 0.18));
|
||||||
|
DrawWindow(g, brush, brushForHighlight, rectMinimized, appsIncluded.Where(x => x.Minimized), currentTheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var memory = new MemoryStream())
|
||||||
|
{
|
||||||
|
previewBitmap.Save(memory, ImageFormat.Png);
|
||||||
|
memory.Position = 0;
|
||||||
|
|
||||||
|
var bitmapImage = new BitmapImage();
|
||||||
|
bitmapImage.BeginInit();
|
||||||
|
bitmapImage.StreamSource = memory;
|
||||||
|
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||||
|
bitmapImage.EndInit();
|
||||||
|
bitmapImage.Freeze();
|
||||||
|
|
||||||
|
return bitmapImage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawWindow(Graphics graphics, Brush brush, Rectangle bounds, Application app, double desiredIconSize, Theme currentTheme)
|
||||||
|
{
|
||||||
|
if (graphics == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (brush == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (GraphicsPath path = RoundedRect(bounds))
|
||||||
|
{
|
||||||
|
if (app.IsHighlighted)
|
||||||
|
{
|
||||||
|
graphics.DrawPath(new Pen(currentTheme == Theme.Dark ? Color.White : Color.DarkGray, graphics.VisibleClipBounds.Height / 50), path);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
graphics.DrawPath(new Pen(currentTheme == Theme.Dark ? Color.FromArgb(128, 82, 82, 82) : Color.FromArgb(128, 160, 160, 160), graphics.VisibleClipBounds.Height / 200), path);
|
||||||
|
}
|
||||||
|
|
||||||
|
graphics.FillPath(brush, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
double iconSize = Math.Min(Math.Min(bounds.Width - 4, bounds.Height - 4), desiredIconSize);
|
||||||
|
Rectangle iconBounds = new Rectangle((int)(bounds.Left + (bounds.Width / 2) - (iconSize / 2)), (int)(bounds.Top + (bounds.Height / 2) - (iconSize / 2)), (int)iconSize, (int)iconSize);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
graphics.DrawIcon(app.Icon, iconBounds);
|
||||||
|
if (app.RepeatIndex > 1)
|
||||||
|
{
|
||||||
|
string indexString = app.RepeatIndex.ToString(CultureInfo.InvariantCulture);
|
||||||
|
int indexSize = (int)(iconBounds.Width * 0.5);
|
||||||
|
Rectangle indexBounds = new Rectangle(iconBounds.Right - indexSize, iconBounds.Bottom - indexSize, indexSize, indexSize);
|
||||||
|
|
||||||
|
var textSize = graphics.MeasureString(indexString, font);
|
||||||
|
var state = graphics.Save();
|
||||||
|
graphics.TranslateTransform(indexBounds.Left, indexBounds.Top);
|
||||||
|
graphics.ScaleTransform(indexBounds.Width / textSize.Width, indexBounds.Height / textSize.Height);
|
||||||
|
graphics.DrawString(indexString, font, Brushes.Black, PointF.Empty);
|
||||||
|
graphics.Restore(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// sometimes drawing an icon throws an exception despite that the icon seems to be ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawWindow(Graphics graphics, Brush brush, Brush brushForHighlight, Rectangle bounds, IEnumerable<Application> apps, Theme currentTheme)
|
||||||
|
{
|
||||||
|
int appsCount = apps.Count();
|
||||||
|
if (appsCount == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (graphics == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (brush == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (GraphicsPath path = RoundedRect(bounds))
|
||||||
|
{
|
||||||
|
if (apps.Where(x => x.IsHighlighted).Any())
|
||||||
|
{
|
||||||
|
graphics.DrawPath(new Pen(currentTheme == Theme.Dark ? Color.White : Color.DarkGray, graphics.VisibleClipBounds.Height / 50), path);
|
||||||
|
graphics.FillPath(brushForHighlight, path);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
graphics.DrawPath(new Pen(currentTheme == Theme.Dark ? Color.FromArgb(128, 82, 82, 82) : Color.FromArgb(128, 160, 160, 160), graphics.VisibleClipBounds.Height / 200), path);
|
||||||
|
graphics.FillPath(brush, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double iconSize = Math.Min(bounds.Width, bounds.Height) * 0.5;
|
||||||
|
for (int iconCounter = 0; iconCounter < appsCount; iconCounter++)
|
||||||
|
{
|
||||||
|
Application app = apps.ElementAt(iconCounter);
|
||||||
|
Rectangle iconBounds = new Rectangle((int)(bounds.Left + (bounds.Width / 2) - (iconSize * ((appsCount / 2) - iconCounter))), (int)(bounds.Top + (bounds.Height / 2) - (iconSize / 2)), (int)iconSize, (int)iconSize);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
graphics.DrawIcon(app.Icon, iconBounds);
|
||||||
|
if (app.RepeatIndex > 0)
|
||||||
|
{
|
||||||
|
string indexString = app.RepeatIndex.ToString(CultureInfo.InvariantCulture);
|
||||||
|
int indexSize = (int)(iconBounds.Width * 0.5);
|
||||||
|
Rectangle indexBounds = new Rectangle(iconBounds.Right - indexSize, iconBounds.Bottom - indexSize, indexSize, indexSize);
|
||||||
|
|
||||||
|
var textSize = graphics.MeasureString(indexString, font);
|
||||||
|
var state = graphics.Save();
|
||||||
|
graphics.TranslateTransform(indexBounds.Left, indexBounds.Top);
|
||||||
|
graphics.ScaleTransform(indexBounds.Width / textSize.Width, indexBounds.Height / textSize.Height);
|
||||||
|
graphics.DrawString(indexString, font, Brushes.Black, PointF.Empty);
|
||||||
|
graphics.Restore(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// sometimes drawing an icon throws an exception despite that the icon seems to be ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BitmapImage DrawPreviewIcons(Project project)
|
||||||
|
{
|
||||||
|
int appsCount = project.Applications.Count;
|
||||||
|
if (appsCount == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bitmap previewBitmap = new Bitmap(32 * appsCount, 24);
|
||||||
|
using (Graphics graphics = Graphics.FromImage(previewBitmap))
|
||||||
|
{
|
||||||
|
graphics.SmoothingMode = SmoothingMode.AntiAlias;
|
||||||
|
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||||
|
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||||
|
int appIndex = 0;
|
||||||
|
foreach (var app in project.Applications)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
graphics.DrawIcon(app.Icon, new Rectangle(32 * appIndex, 0, 24, 24));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.LogError($"Exception while drawing the icon for app {app.AppName}. Exception message: {e.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
appIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var memory = new MemoryStream())
|
||||||
|
{
|
||||||
|
previewBitmap.Save(memory, ImageFormat.Png);
|
||||||
|
memory.Position = 0;
|
||||||
|
|
||||||
|
var bitmapImage = new BitmapImage();
|
||||||
|
bitmapImage.BeginInit();
|
||||||
|
bitmapImage.StreamSource = memory;
|
||||||
|
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||||
|
bitmapImage.EndInit();
|
||||||
|
bitmapImage.Freeze();
|
||||||
|
|
||||||
|
return bitmapImage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GraphicsPath RoundedRect(Rectangle bounds)
|
||||||
|
{
|
||||||
|
int minorSize = Math.Min(bounds.Width, bounds.Height);
|
||||||
|
int radius = (int)(minorSize / 8);
|
||||||
|
|
||||||
|
int diameter = radius * 2;
|
||||||
|
Size size = new Size(diameter, diameter);
|
||||||
|
Rectangle arc = new Rectangle(bounds.Location, size);
|
||||||
|
GraphicsPath path = new GraphicsPath();
|
||||||
|
|
||||||
|
if (radius == 0)
|
||||||
|
{
|
||||||
|
path.AddRectangle(bounds);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// top left arc
|
||||||
|
path.AddArc(arc, 180, 90);
|
||||||
|
|
||||||
|
// top right arc
|
||||||
|
arc.X = bounds.Right - diameter;
|
||||||
|
path.AddArc(arc, 270, 90);
|
||||||
|
|
||||||
|
// bottom right arc
|
||||||
|
arc.Y = bounds.Bottom - diameter;
|
||||||
|
path.AddArc(arc, 0, 90);
|
||||||
|
|
||||||
|
// bottom left arc
|
||||||
|
arc.X = bounds.Left;
|
||||||
|
path.AddArc(arc, 90, 90);
|
||||||
|
|
||||||
|
path.CloseFigure();
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
src/modules/Workspaces/WorkspacesEditor/Utils/FolderUtils.cs
Normal file
28
src/modules/Workspaces/WorkspacesEditor/Utils/FolderUtils.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Utils
|
||||||
|
{
|
||||||
|
public class FolderUtils
|
||||||
|
{
|
||||||
|
public static string Desktop()
|
||||||
|
{
|
||||||
|
return Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Temp()
|
||||||
|
{
|
||||||
|
return Path.GetTempPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: the same path should be used in SnapshotTool and Launcher
|
||||||
|
public static string DataFolder()
|
||||||
|
{
|
||||||
|
return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\Microsoft\\PowerToys\\Workspaces";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
src/modules/Workspaces/WorkspacesEditor/Utils/IOUtils.cs
Normal file
54
src/modules/Workspaces/WorkspacesEditor/Utils/IOUtils.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Abstractions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Utils
|
||||||
|
{
|
||||||
|
public class IOUtils
|
||||||
|
{
|
||||||
|
private readonly IFileSystem _fileSystem = new FileSystem();
|
||||||
|
|
||||||
|
public IOUtils()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteFile(string fileName, string data)
|
||||||
|
{
|
||||||
|
_fileSystem.File.WriteAllText(fileName, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReadFile(string fileName)
|
||||||
|
{
|
||||||
|
if (_fileSystem.File.Exists(fileName))
|
||||||
|
{
|
||||||
|
var attempts = 0;
|
||||||
|
while (attempts < 10)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (Stream inputStream = _fileSystem.File.Open(fileName, FileMode.Open))
|
||||||
|
using (StreamReader reader = new StreamReader(inputStream))
|
||||||
|
{
|
||||||
|
string data = reader.ReadToEnd();
|
||||||
|
inputStream.Close();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
Task.Delay(10).Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
attempts++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Utils
|
||||||
|
{
|
||||||
|
public class MonitorHelper
|
||||||
|
{
|
||||||
|
private const int DpiAwarenessContextUnaware = -1;
|
||||||
|
|
||||||
|
private Screen[] screens;
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern IntPtr SetThreadDpiAwarenessContext(IntPtr dpiContext);
|
||||||
|
|
||||||
|
private void SaveDpiUnawareScreens()
|
||||||
|
{
|
||||||
|
SetThreadDpiAwarenessContext(DpiAwarenessContextUnaware);
|
||||||
|
screens = Screen.AllScreens;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Screen[] GetDpiUnawareScreenBounds()
|
||||||
|
{
|
||||||
|
Thread dpiUnawareThread = new Thread(new ThreadStart(SaveDpiUnawareScreens));
|
||||||
|
dpiUnawareThread.Start();
|
||||||
|
dpiUnawareThread.Join();
|
||||||
|
|
||||||
|
return screens;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Screen[] GetDpiUnawareScreens()
|
||||||
|
{
|
||||||
|
MonitorHelper monitorHelper = new MonitorHelper();
|
||||||
|
return monitorHelper.GetDpiUnawareScreenBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static double GetScreenDpiFromScreen(Screen screen)
|
||||||
|
{
|
||||||
|
var point = new System.Drawing.Point(screen.Bounds.Left + 1, screen.Bounds.Top + 1);
|
||||||
|
var monitor = NativeMethods.MonitorFromPoint(point, NativeMethods._MONITOR_DEFAULTTONEAREST);
|
||||||
|
NativeMethods.GetDpiForMonitor(monitor, NativeMethods.DpiType.EFFECTIVE, out uint dpiX, out uint dpiY);
|
||||||
|
return dpiX / 96.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Utils
|
||||||
|
{
|
||||||
|
internal sealed class NativeMethods
|
||||||
|
{
|
||||||
|
public const int SW_RESTORE = 9;
|
||||||
|
public const int SW_NORMAL = 1;
|
||||||
|
public const int SW_MINIMIZE = 6;
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
|
||||||
|
|
||||||
|
[DllImport("USER32.DLL")]
|
||||||
|
public static extern bool SetForegroundWindow(IntPtr hWnd);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
public static extern void SwitchToThisWindow(IntPtr hWnd, bool fAltTab);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
public static extern IntPtr GetForegroundWindow();
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr processId);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
public static extern uint GetCurrentThreadId();
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
public static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
|
||||||
|
|
||||||
|
public enum DpiType
|
||||||
|
{
|
||||||
|
EFFECTIVE = 0,
|
||||||
|
ANGULAR = 1,
|
||||||
|
RAW = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("User32.dll")]
|
||||||
|
public static extern IntPtr MonitorFromPoint([In] System.Drawing.Point pt, [In] uint dwFlags);
|
||||||
|
|
||||||
|
[DllImport("Shcore.dll")]
|
||||||
|
public static extern IntPtr GetDpiForMonitor([In] IntPtr hmonitor, [In] DpiType dpiType, [Out] out uint dpiX, [Out] out uint dpiY);
|
||||||
|
|
||||||
|
public const int _S_OK = 0;
|
||||||
|
public const int _MONITOR_DEFAULTTONEAREST = 2;
|
||||||
|
public const int _E_INVALIDARG = -2147024809;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Utils
|
||||||
|
{
|
||||||
|
public struct ParsingResult
|
||||||
|
{
|
||||||
|
public bool Result { get; }
|
||||||
|
|
||||||
|
public string Message { get; }
|
||||||
|
|
||||||
|
public string MalformedData { get; }
|
||||||
|
|
||||||
|
public ParsingResult(bool result, string message = "", string data = "")
|
||||||
|
{
|
||||||
|
Result = result;
|
||||||
|
Message = message;
|
||||||
|
MalformedData = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
src/modules/Workspaces/WorkspacesEditor/Utils/Settings.cs
Normal file
27
src/modules/Workspaces/WorkspacesEditor/Utils/Settings.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using Microsoft.PowerToys.Settings.UI.Library;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Utils
|
||||||
|
{
|
||||||
|
public class Settings
|
||||||
|
{
|
||||||
|
private const string WorkspacesModuleName = "Workspaces";
|
||||||
|
private static SettingsUtils _settingsUtils = new SettingsUtils();
|
||||||
|
|
||||||
|
public static WorkspacesSettings ReadSettings()
|
||||||
|
{
|
||||||
|
if (!_settingsUtils.SettingsExists(WorkspacesModuleName))
|
||||||
|
{
|
||||||
|
var defaultWorkspacesSettings = new WorkspacesSettings();
|
||||||
|
defaultWorkspacesSettings.Save(_settingsUtils);
|
||||||
|
return defaultWorkspacesSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
WorkspacesSettings settings = _settingsUtils.GetSettingsOrDefault<WorkspacesSettings>(WorkspacesModuleName);
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
src/modules/Workspaces/WorkspacesEditor/Utils/StringUtils.cs
Normal file
22
src/modules/Workspaces/WorkspacesEditor/Utils/StringUtils.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Utils
|
||||||
|
{
|
||||||
|
public static class StringUtils
|
||||||
|
{
|
||||||
|
public static string UpperCamelCaseToDashCase(this string str)
|
||||||
|
{
|
||||||
|
// If it's single letter variable, leave it as it is
|
||||||
|
if (str.Length == 1)
|
||||||
|
{
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Concat(str.Select((x, i) => i > 0 && char.IsUpper(x) ? "-" + x.ToString() : x.ToString())).ToLowerInvariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,182 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using ManagedCommon;
|
||||||
|
using WorkspacesEditor.Data;
|
||||||
|
using WorkspacesEditor.Models;
|
||||||
|
using WorkspacesEditor.ViewModels;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Utils
|
||||||
|
{
|
||||||
|
public class WorkspacesEditorIO
|
||||||
|
{
|
||||||
|
public WorkspacesEditorIO()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParsingResult ParseWorkspaces(MainViewModel mainViewModel)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
WorkspacesData parser = new WorkspacesData();
|
||||||
|
if (!File.Exists(parser.File))
|
||||||
|
{
|
||||||
|
Logger.LogWarning($"Workspaces storage file not found: {parser.File}");
|
||||||
|
return new ParsingResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
WorkspacesData.WorkspacesListWrapper workspaces = parser.Read(parser.File);
|
||||||
|
if (workspaces.Workspaces == null)
|
||||||
|
{
|
||||||
|
return new ParsingResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SetWorkspaces(mainViewModel, workspaces))
|
||||||
|
{
|
||||||
|
Logger.LogWarning($"Workspaces storage file content could not be set. Reason: {Properties.Resources.Error_Parsing_Message}");
|
||||||
|
return new ParsingResult(false, WorkspacesEditor.Properties.Resources.Error_Parsing_Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ParsingResult(true);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.LogError($"Exception while parsing storage file: {e.Message}");
|
||||||
|
return new ParsingResult(false, e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Project ParseTempProject()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ProjectData parser = new ProjectData();
|
||||||
|
if (!File.Exists(TempProjectData.File))
|
||||||
|
{
|
||||||
|
Logger.LogWarning($"ParseProject method. Workspaces storage file not found: {TempProjectData.File}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Project project = new Project(parser.Read(TempProjectData.File));
|
||||||
|
return project;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.LogError($"ParseProject method. Exception while parsing storage file: {e.Message}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SerializeWorkspaces(List<Project> workspaces, bool useTempFile = false)
|
||||||
|
{
|
||||||
|
WorkspacesData serializer = new WorkspacesData();
|
||||||
|
WorkspacesData.WorkspacesListWrapper workspacesWrapper = new WorkspacesData.WorkspacesListWrapper { };
|
||||||
|
workspacesWrapper.Workspaces = new List<ProjectData.ProjectWrapper>();
|
||||||
|
|
||||||
|
foreach (Project project in workspaces)
|
||||||
|
{
|
||||||
|
ProjectData.ProjectWrapper wrapper = new ProjectData.ProjectWrapper
|
||||||
|
{
|
||||||
|
Id = project.Id,
|
||||||
|
Name = project.Name,
|
||||||
|
CreationTime = project.CreationTime,
|
||||||
|
IsShortcutNeeded = project.IsShortcutNeeded,
|
||||||
|
MoveExistingWindows = project.MoveExistingWindows,
|
||||||
|
LastLaunchedTime = project.LastLaunchedTime,
|
||||||
|
Applications = new List<ProjectData.ApplicationWrapper> { },
|
||||||
|
MonitorConfiguration = new List<ProjectData.MonitorConfigurationWrapper> { },
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var app in project.Applications.Where(x => x.IsIncluded))
|
||||||
|
{
|
||||||
|
wrapper.Applications.Add(new ProjectData.ApplicationWrapper
|
||||||
|
{
|
||||||
|
Application = app.AppName,
|
||||||
|
ApplicationPath = app.AppPath,
|
||||||
|
Title = app.AppTitle,
|
||||||
|
PackageFullName = app.PackageFullName,
|
||||||
|
AppUserModelId = app.AppUserModelId,
|
||||||
|
CommandLineArguments = app.CommandLineArguments,
|
||||||
|
IsElevated = app.IsElevated,
|
||||||
|
CanLaunchElevated = app.CanLaunchElevated,
|
||||||
|
Maximized = app.Maximized,
|
||||||
|
Minimized = app.Minimized,
|
||||||
|
Position = new ProjectData.ApplicationWrapper.WindowPositionWrapper
|
||||||
|
{
|
||||||
|
X = app.Position.X,
|
||||||
|
Y = app.Position.Y,
|
||||||
|
Height = app.Position.Height,
|
||||||
|
Width = app.Position.Width,
|
||||||
|
},
|
||||||
|
Monitor = app.MonitorNumber,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var monitor in project.Monitors)
|
||||||
|
{
|
||||||
|
wrapper.MonitorConfiguration.Add(new ProjectData.MonitorConfigurationWrapper
|
||||||
|
{
|
||||||
|
Id = monitor.MonitorName,
|
||||||
|
InstanceId = monitor.MonitorInstanceId,
|
||||||
|
MonitorNumber = monitor.MonitorNumber,
|
||||||
|
Dpi = monitor.Dpi,
|
||||||
|
MonitorRectDpiAware = new ProjectData.MonitorConfigurationWrapper.MonitorRectWrapper
|
||||||
|
{
|
||||||
|
Left = (int)monitor.MonitorDpiAwareBounds.Left,
|
||||||
|
Top = (int)monitor.MonitorDpiAwareBounds.Top,
|
||||||
|
Width = (int)monitor.MonitorDpiAwareBounds.Width,
|
||||||
|
Height = (int)monitor.MonitorDpiAwareBounds.Height,
|
||||||
|
},
|
||||||
|
MonitorRectDpiUnaware = new ProjectData.MonitorConfigurationWrapper.MonitorRectWrapper
|
||||||
|
{
|
||||||
|
Left = (int)monitor.MonitorDpiUnawareBounds.Left,
|
||||||
|
Top = (int)monitor.MonitorDpiUnawareBounds.Top,
|
||||||
|
Width = (int)monitor.MonitorDpiUnawareBounds.Width,
|
||||||
|
Height = (int)monitor.MonitorDpiUnawareBounds.Height,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
workspacesWrapper.Workspaces.Add(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IOUtils ioUtils = new IOUtils();
|
||||||
|
ioUtils.WriteFile(useTempFile ? TempProjectData.File : serializer.File, serializer.Serialize(workspacesWrapper));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// TODO: show error
|
||||||
|
Logger.LogError($"Exception while writing storage file: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AddWorkspaces(MainViewModel mainViewModel, WorkspacesData.WorkspacesListWrapper workspaces)
|
||||||
|
{
|
||||||
|
foreach (var project in workspaces.Workspaces)
|
||||||
|
{
|
||||||
|
mainViewModel.Workspaces.Add(new Project(project));
|
||||||
|
}
|
||||||
|
|
||||||
|
mainViewModel.Initialize();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool SetWorkspaces(MainViewModel mainViewModel, WorkspacesData.WorkspacesListWrapper workspaces)
|
||||||
|
{
|
||||||
|
mainViewModel.Workspaces = new System.Collections.ObjectModel.ObservableCollection<Project> { };
|
||||||
|
return AddWorkspaces(mainViewModel, workspaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SerializeTempProject(Project project)
|
||||||
|
{
|
||||||
|
SerializeWorkspaces(new List<Project>() { project }, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
153
src/modules/Workspaces/WorkspacesEditor/Utils/WorkspacesIcon.cs
Normal file
153
src/modules/Workspaces/WorkspacesEditor/Utils/WorkspacesIcon.cs
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Drawing.Drawing2D;
|
||||||
|
using System.Drawing.Imaging;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using ManagedCommon;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.Utils
|
||||||
|
{
|
||||||
|
public class WorkspacesIcon : IDisposable
|
||||||
|
{
|
||||||
|
private const int IconSize = 128;
|
||||||
|
|
||||||
|
public static readonly Brush LightThemeIconBackground = new SolidBrush(Color.FromArgb(255, 239, 243, 251));
|
||||||
|
public static readonly Brush LightThemeIconForeground = new SolidBrush(Color.FromArgb(255, 47, 50, 56));
|
||||||
|
public static readonly Brush DarkThemeIconBackground = new SolidBrush(Color.FromArgb(255, 55, 55, 55));
|
||||||
|
public static readonly Brush DarkThemeIconForeground = new SolidBrush(Color.FromArgb(255, 228, 228, 228));
|
||||||
|
|
||||||
|
public static readonly Font IconFont = new("Aptos", 24, FontStyle.Bold);
|
||||||
|
|
||||||
|
public static string IconTextFromProjectName(string projectName)
|
||||||
|
{
|
||||||
|
string result = string.Empty;
|
||||||
|
char[] delimiterChars = { ' ', ',', '.', ':', '-', '\t' };
|
||||||
|
string[] words = projectName.Split(delimiterChars);
|
||||||
|
|
||||||
|
foreach (string word in words)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(word))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (word.All(char.IsDigit))
|
||||||
|
{
|
||||||
|
result += word;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result += word.ToUpper(System.Globalization.CultureInfo.CurrentCulture).ToCharArray()[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Bitmap DrawIcon(string text, Theme currentTheme)
|
||||||
|
{
|
||||||
|
Brush background = currentTheme == Theme.Dark ? DarkThemeIconBackground : LightThemeIconBackground;
|
||||||
|
Brush foreground = currentTheme == Theme.Dark ? DarkThemeIconForeground : LightThemeIconForeground;
|
||||||
|
Bitmap bitmap = new Bitmap(IconSize, IconSize);
|
||||||
|
|
||||||
|
using (Graphics graphics = Graphics.FromImage(bitmap))
|
||||||
|
{
|
||||||
|
graphics.SmoothingMode = SmoothingMode.AntiAlias;
|
||||||
|
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||||
|
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||||
|
graphics.FillEllipse(background, 0, 0, IconSize, IconSize);
|
||||||
|
|
||||||
|
var textSize = graphics.MeasureString(text, IconFont);
|
||||||
|
var state = graphics.Save();
|
||||||
|
|
||||||
|
// Calculate scaling factors
|
||||||
|
float scaleX = (float)IconSize / textSize.Width;
|
||||||
|
float scaleY = (float)IconSize / textSize.Height;
|
||||||
|
float scale = Math.Min(scaleX, scaleY) * 0.8f; // Use the smaller scale factor to maintain aspect ratio
|
||||||
|
|
||||||
|
// Calculate the position to center the text
|
||||||
|
float textX = (IconSize - (textSize.Width * scale)) / 2;
|
||||||
|
float textY = ((IconSize - (textSize.Height * scale)) / 2) + 6;
|
||||||
|
|
||||||
|
graphics.TranslateTransform(textX, textY);
|
||||||
|
graphics.ScaleTransform(scale, scale);
|
||||||
|
graphics.DrawString(text, IconFont, foreground, 0, 0);
|
||||||
|
graphics.Restore(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SaveIcon(Bitmap icon, string path)
|
||||||
|
{
|
||||||
|
if (Path.Exists(path))
|
||||||
|
{
|
||||||
|
File.Delete(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileStream fileStream = new FileStream(path, FileMode.CreateNew);
|
||||||
|
using (var memoryStream = new MemoryStream())
|
||||||
|
{
|
||||||
|
icon.Save(memoryStream, ImageFormat.Png);
|
||||||
|
|
||||||
|
BinaryWriter iconWriter = new BinaryWriter(fileStream);
|
||||||
|
if (fileStream != null && iconWriter != null)
|
||||||
|
{
|
||||||
|
// 0-1 reserved, 0
|
||||||
|
iconWriter.Write((byte)0);
|
||||||
|
iconWriter.Write((byte)0);
|
||||||
|
|
||||||
|
// 2-3 image type, 1 = icon, 2 = cursor
|
||||||
|
iconWriter.Write((short)1);
|
||||||
|
|
||||||
|
// 4-5 number of images
|
||||||
|
iconWriter.Write((short)1);
|
||||||
|
|
||||||
|
// image entry 1
|
||||||
|
// 0 image width
|
||||||
|
iconWriter.Write((byte)IconSize);
|
||||||
|
|
||||||
|
// 1 image height
|
||||||
|
iconWriter.Write((byte)IconSize);
|
||||||
|
|
||||||
|
// 2 number of colors
|
||||||
|
iconWriter.Write((byte)0);
|
||||||
|
|
||||||
|
// 3 reserved
|
||||||
|
iconWriter.Write((byte)0);
|
||||||
|
|
||||||
|
// 4-5 color planes
|
||||||
|
iconWriter.Write((short)0);
|
||||||
|
|
||||||
|
// 6-7 bits per pixel
|
||||||
|
iconWriter.Write((short)32);
|
||||||
|
|
||||||
|
// 8-11 size of image data
|
||||||
|
iconWriter.Write((int)memoryStream.Length);
|
||||||
|
|
||||||
|
// 12-15 offset of image data
|
||||||
|
iconWriter.Write((int)(6 + 16));
|
||||||
|
|
||||||
|
// write image data
|
||||||
|
// png data must contain the whole png data file
|
||||||
|
iconWriter.Write(memoryStream.ToArray());
|
||||||
|
|
||||||
|
iconWriter.Flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileStream.Flush();
|
||||||
|
fileStream.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,598 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Timers;
|
||||||
|
using System.Windows;
|
||||||
|
using ManagedCommon;
|
||||||
|
using Microsoft.PowerToys.Settings.UI.Library;
|
||||||
|
using Microsoft.PowerToys.Telemetry;
|
||||||
|
using WorkspacesEditor.Data;
|
||||||
|
using WorkspacesEditor.Models;
|
||||||
|
using WorkspacesEditor.Telemetry;
|
||||||
|
using WorkspacesEditor.Utils;
|
||||||
|
using static WorkspacesEditor.Data.WorkspacesData;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor.ViewModels
|
||||||
|
{
|
||||||
|
public class MainViewModel : INotifyPropertyChanged, IDisposable
|
||||||
|
{
|
||||||
|
private WorkspacesEditorIO _workspacesEditorIO;
|
||||||
|
private ProjectEditor editPage;
|
||||||
|
private SnapshotWindow _snapshotWindow;
|
||||||
|
private List<OverlayWindow> _overlayWindows = new List<OverlayWindow>();
|
||||||
|
private Project editedProject;
|
||||||
|
private Project projectBeforeLaunch;
|
||||||
|
private string projectNameBeingEdited;
|
||||||
|
private MainWindow _mainWindow;
|
||||||
|
private Timer lastUpdatedTimer;
|
||||||
|
private WorkspacesSettings settings;
|
||||||
|
|
||||||
|
public ObservableCollection<Project> Workspaces { get; set; } = new ObservableCollection<Project>();
|
||||||
|
|
||||||
|
public IEnumerable<Project> WorkspacesView
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
IEnumerable<Project> workspaces = GetFilteredWorkspaces();
|
||||||
|
IsWorkspacesViewEmpty = !(workspaces != null && workspaces.Any());
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsWorkspacesViewEmpty)));
|
||||||
|
if (IsWorkspacesViewEmpty)
|
||||||
|
{
|
||||||
|
if (Workspaces != null && Workspaces.Any())
|
||||||
|
{
|
||||||
|
EmptyWorkspacesViewMessage = Properties.Resources.NoWorkspacesMatch;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EmptyWorkspacesViewMessage = Properties.Resources.No_Workspaces_Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(EmptyWorkspacesViewMessage)));
|
||||||
|
|
||||||
|
return Enumerable.Empty<Project>();
|
||||||
|
}
|
||||||
|
|
||||||
|
OrderBy orderBy = (OrderBy)_orderByIndex;
|
||||||
|
if (orderBy == OrderBy.LastViewed)
|
||||||
|
{
|
||||||
|
return workspaces.OrderByDescending(x => x.LastLaunchedTime);
|
||||||
|
}
|
||||||
|
else if (orderBy == OrderBy.Created)
|
||||||
|
{
|
||||||
|
return workspaces.OrderByDescending(x => x.CreationTime);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return workspaces.OrderBy(x => x.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsWorkspacesViewEmpty { get; set; }
|
||||||
|
|
||||||
|
public string EmptyWorkspacesViewMessage { get; set; }
|
||||||
|
|
||||||
|
// return those workspaces where the project name or any of the selected apps' name contains the search term
|
||||||
|
private IEnumerable<Project> GetFilteredWorkspaces()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_searchTerm))
|
||||||
|
{
|
||||||
|
return Workspaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Workspaces.Where(x =>
|
||||||
|
{
|
||||||
|
if (x.Name.Contains(_searchTerm, StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x.Applications == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return x.Applications.Any(app => app.AppName.Contains(_searchTerm, StringComparison.InvariantCultureIgnoreCase));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _searchTerm;
|
||||||
|
|
||||||
|
public string SearchTerm
|
||||||
|
{
|
||||||
|
get => _searchTerm;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_searchTerm = value;
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(WorkspacesView)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _orderByIndex;
|
||||||
|
|
||||||
|
public int OrderByIndex
|
||||||
|
{
|
||||||
|
get => _orderByIndex;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_orderByIndex = value;
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(WorkspacesView)));
|
||||||
|
settings.Properties.SortBy = (WorkspacesProperties.SortByProperty)value;
|
||||||
|
settings.Save(new SettingsUtils());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
|
public void OnPropertyChanged(PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MainViewModel(WorkspacesEditorIO workspacesEditorIO)
|
||||||
|
{
|
||||||
|
settings = Utils.Settings.ReadSettings();
|
||||||
|
_orderByIndex = (int)settings.Properties.SortBy;
|
||||||
|
_workspacesEditorIO = workspacesEditorIO;
|
||||||
|
lastUpdatedTimer = new System.Timers.Timer();
|
||||||
|
lastUpdatedTimer.Interval = 1000;
|
||||||
|
lastUpdatedTimer.Elapsed += LastUpdatedTimerElapsed;
|
||||||
|
lastUpdatedTimer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
foreach (Project project in Workspaces)
|
||||||
|
{
|
||||||
|
project.Initialize(App.ThemeManager.GetCurrentTheme());
|
||||||
|
}
|
||||||
|
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(WorkspacesView)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetEditedProject(Project editedProject)
|
||||||
|
{
|
||||||
|
this.editedProject = editedProject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveProject(Project projectToSave)
|
||||||
|
{
|
||||||
|
SendEditTelemetryEvent(projectToSave, editedProject);
|
||||||
|
|
||||||
|
if (editedProject.Name != projectToSave.Name)
|
||||||
|
{
|
||||||
|
RemoveShortcut(editedProject);
|
||||||
|
}
|
||||||
|
|
||||||
|
editedProject.Name = projectToSave.Name;
|
||||||
|
editedProject.IsShortcutNeeded = projectToSave.IsShortcutNeeded;
|
||||||
|
editedProject.MoveExistingWindows = projectToSave.MoveExistingWindows;
|
||||||
|
editedProject.PreviewIcons = projectToSave.PreviewIcons;
|
||||||
|
editedProject.PreviewImage = projectToSave.PreviewImage;
|
||||||
|
editedProject.Applications = projectToSave.Applications.Where(x => x.IsIncluded).ToList();
|
||||||
|
|
||||||
|
editedProject.OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs("AppsCountString"));
|
||||||
|
editedProject.Initialize(App.ThemeManager.GetCurrentTheme());
|
||||||
|
_workspacesEditorIO.SerializeWorkspaces(Workspaces.ToList());
|
||||||
|
ApplyShortcut(editedProject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyShortcut(Project project)
|
||||||
|
{
|
||||||
|
string basePath = AppDomain.CurrentDomain.BaseDirectory;
|
||||||
|
string shortcutAddress = Path.Combine(FolderUtils.Desktop(), project.Name + ".lnk");
|
||||||
|
string shortcutIconFilename = Path.Combine(FolderUtils.Temp(), project.Id + ".ico");
|
||||||
|
|
||||||
|
if (!project.IsShortcutNeeded)
|
||||||
|
{
|
||||||
|
if (File.Exists(shortcutIconFilename))
|
||||||
|
{
|
||||||
|
File.Delete(shortcutIconFilename);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (File.Exists(shortcutAddress))
|
||||||
|
{
|
||||||
|
File.Delete(shortcutAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bitmap icon = WorkspacesIcon.DrawIcon(WorkspacesIcon.IconTextFromProjectName(project.Name), App.ThemeManager.GetCurrentTheme());
|
||||||
|
WorkspacesIcon.SaveIcon(icon, shortcutIconFilename);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Workaround to be able to create a shortcut with unicode filename
|
||||||
|
File.WriteAllBytes(shortcutAddress, Array.Empty<byte>());
|
||||||
|
|
||||||
|
// Create a ShellLinkObject that references the .lnk file
|
||||||
|
Shell32.Shell shell = new Shell32.Shell();
|
||||||
|
Shell32.Folder dir = shell.NameSpace(FolderUtils.Desktop());
|
||||||
|
Shell32.FolderItem folderItem = dir.Items().Item($"{project.Name}.lnk");
|
||||||
|
Shell32.ShellLinkObject link = (Shell32.ShellLinkObject)folderItem.GetLink;
|
||||||
|
|
||||||
|
// Set the .lnk file properties
|
||||||
|
link.Description = $"Project Launcher {project.Id}";
|
||||||
|
link.Path = Path.Combine(basePath, "PowerToys.WorkspacesLauncher.exe");
|
||||||
|
link.Arguments = $"{project.Id.ToString()} {(int)InvokePoint.Shortcut}";
|
||||||
|
link.WorkingDirectory = basePath;
|
||||||
|
link.SetIconLocation(shortcutIconFilename, 0);
|
||||||
|
|
||||||
|
link.Save(shortcutAddress);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError($"Shortcut creation error: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveProjectName(Project project)
|
||||||
|
{
|
||||||
|
projectNameBeingEdited = project.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CancelProjectName(Project project)
|
||||||
|
{
|
||||||
|
project.Name = projectNameBeingEdited;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void SnapWorkspace()
|
||||||
|
{
|
||||||
|
CancelSnapshot();
|
||||||
|
|
||||||
|
await Task.Run(() => RunSnapshotTool());
|
||||||
|
|
||||||
|
Project project = _workspacesEditorIO.ParseTempProject();
|
||||||
|
if (project != null)
|
||||||
|
{
|
||||||
|
if (editedProject != null)
|
||||||
|
{
|
||||||
|
project.UpdateAfterLaunchAndEdit(projectBeforeLaunch);
|
||||||
|
project.EditorWindowTitle = Properties.Resources.EditWorkspace;
|
||||||
|
editPage.DataContext = project;
|
||||||
|
CheckShortcutPresence(project);
|
||||||
|
project.Initialize(App.ThemeManager.GetCurrentTheme());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EditProject(project, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RevertLaunch()
|
||||||
|
{
|
||||||
|
CheckShortcutPresence(projectBeforeLaunch);
|
||||||
|
editPage.DataContext = projectBeforeLaunch;
|
||||||
|
projectBeforeLaunch.Initialize(App.ThemeManager.GetCurrentTheme());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EditProject(Project selectedProject, bool isNewlyCreated = false)
|
||||||
|
{
|
||||||
|
editPage = new ProjectEditor(this);
|
||||||
|
|
||||||
|
SetEditedProject(selectedProject);
|
||||||
|
if (!isNewlyCreated)
|
||||||
|
{
|
||||||
|
selectedProject = new Project(selectedProject);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNewlyCreated)
|
||||||
|
{
|
||||||
|
// generate a default name for the new project
|
||||||
|
string defaultNamePrefix = Properties.Resources.DefaultWorkspaceNamePrefix;
|
||||||
|
int nextProjectIndex = 0;
|
||||||
|
foreach (var proj in Workspaces)
|
||||||
|
{
|
||||||
|
if (proj.Name.StartsWith(defaultNamePrefix, StringComparison.CurrentCulture))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int index = int.Parse(proj.Name[(defaultNamePrefix.Length + 1)..], CultureInfo.CurrentCulture);
|
||||||
|
if (nextProjectIndex < index)
|
||||||
|
{
|
||||||
|
nextProjectIndex = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedProject.Name = defaultNamePrefix + " " + (nextProjectIndex + 1).ToString(CultureInfo.CurrentCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedProject.EditorWindowTitle = isNewlyCreated ? Properties.Resources.CreateWorkspace : Properties.Resources.EditWorkspace;
|
||||||
|
selectedProject.Initialize(App.ThemeManager.GetCurrentTheme());
|
||||||
|
|
||||||
|
CheckShortcutPresence(selectedProject);
|
||||||
|
|
||||||
|
editPage.DataContext = selectedProject;
|
||||||
|
_mainWindow.ShowPage(editPage);
|
||||||
|
lastUpdatedTimer.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckShortcutPresence(Project project)
|
||||||
|
{
|
||||||
|
string basePath = AppDomain.CurrentDomain.BaseDirectory;
|
||||||
|
string shortcutAddress = Path.Combine(FolderUtils.Desktop(), project.Name + ".lnk");
|
||||||
|
project.IsShortcutNeeded = File.Exists(shortcutAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddNewProject(Project project)
|
||||||
|
{
|
||||||
|
project.Applications.RemoveAll(app => !app.IsIncluded);
|
||||||
|
project.Initialize(App.ThemeManager.GetCurrentTheme());
|
||||||
|
Workspaces.Add(project);
|
||||||
|
_workspacesEditorIO.SerializeWorkspaces(Workspaces.ToList());
|
||||||
|
TempProjectData.DeleteTempFile();
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(WorkspacesView)));
|
||||||
|
ApplyShortcut(project);
|
||||||
|
SendCreateTelemetryEvent(project);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteProject(Project selectedProject)
|
||||||
|
{
|
||||||
|
Workspaces.Remove(selectedProject);
|
||||||
|
_workspacesEditorIO.SerializeWorkspaces(Workspaces.ToList());
|
||||||
|
RemoveShortcut(selectedProject);
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(WorkspacesView)));
|
||||||
|
SendDeleteTelemetryEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveShortcut(Project selectedProject)
|
||||||
|
{
|
||||||
|
string shortcutAddress = Path.Combine(FolderUtils.Desktop(), selectedProject.Name + ".lnk");
|
||||||
|
string shortcutIconFilename = Path.Combine(FolderUtils.Temp(), selectedProject.Id + ".ico");
|
||||||
|
|
||||||
|
if (File.Exists(shortcutIconFilename))
|
||||||
|
{
|
||||||
|
File.Delete(shortcutIconFilename);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (File.Exists(shortcutAddress))
|
||||||
|
{
|
||||||
|
File.Delete(shortcutAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetMainWindow(MainWindow mainWindow)
|
||||||
|
{
|
||||||
|
_mainWindow = mainWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SwitchToMainView()
|
||||||
|
{
|
||||||
|
_mainWindow.SwitchToMainView();
|
||||||
|
SearchTerm = string.Empty;
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(SearchTerm)));
|
||||||
|
lastUpdatedTimer.Start();
|
||||||
|
editedProject = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LaunchProject(string projectId)
|
||||||
|
{
|
||||||
|
if (!Workspaces.Where(x => x.Id == projectId).Any())
|
||||||
|
{
|
||||||
|
Logger.LogWarning($"Workspace to launch not found. Id: {projectId}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchProject(Workspaces.Where(x => x.Id == projectId).First(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void LaunchProject(Project project, bool exitAfterLaunch = false)
|
||||||
|
{
|
||||||
|
await Task.Run(() => RunLauncher(project.Id, InvokePoint.EditorButton));
|
||||||
|
if (_workspacesEditorIO.ParseWorkspaces(this).Result == true)
|
||||||
|
{
|
||||||
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(WorkspacesView)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exitAfterLaunch)
|
||||||
|
{
|
||||||
|
Logger.LogInfo($"Launched the Workspace {project.Name}. Exiting.");
|
||||||
|
Environment.Exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LastUpdatedTimerElapsed(object sender, ElapsedEventArgs e)
|
||||||
|
{
|
||||||
|
if (Workspaces == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Project project in Workspaces)
|
||||||
|
{
|
||||||
|
project.OnPropertyChanged(new PropertyChangedEventArgs("LastLaunched"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RunSnapshotTool(string filename = null)
|
||||||
|
{
|
||||||
|
Process process = new Process();
|
||||||
|
process.StartInfo = new ProcessStartInfo(@".\PowerToys.WorkspacesSnapshotTool.exe");
|
||||||
|
process.StartInfo.CreateNoWindow = true;
|
||||||
|
if (!string.IsNullOrEmpty(filename))
|
||||||
|
{
|
||||||
|
process.StartInfo.Arguments = filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
process.Start();
|
||||||
|
process.WaitForExit();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"An error occurred: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RunLauncher(string projectId, InvokePoint invokePoint)
|
||||||
|
{
|
||||||
|
Process process = new Process();
|
||||||
|
process.StartInfo = new ProcessStartInfo(@".\PowerToys.WorkspacesLauncher.exe", $"{projectId} {(int)invokePoint}");
|
||||||
|
process.StartInfo.CreateNoWindow = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
process.Start();
|
||||||
|
process.WaitForExit();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"An error occurred: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void CloseAllPopups()
|
||||||
|
{
|
||||||
|
foreach (Project project in Workspaces)
|
||||||
|
{
|
||||||
|
project.IsPopupVisible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void EnterSnapshotMode(bool isExistingProjectLaunched)
|
||||||
|
{
|
||||||
|
_mainWindow.WindowState = System.Windows.WindowState.Minimized;
|
||||||
|
_overlayWindows.Clear();
|
||||||
|
foreach (var screen in MonitorHelper.GetDpiUnawareScreens())
|
||||||
|
{
|
||||||
|
var bounds = screen.Bounds;
|
||||||
|
OverlayWindow overlayWindow = new OverlayWindow();
|
||||||
|
overlayWindow.Top = bounds.Top;
|
||||||
|
overlayWindow.Left = bounds.Left;
|
||||||
|
overlayWindow.Width = bounds.Width;
|
||||||
|
overlayWindow.Height = bounds.Height;
|
||||||
|
overlayWindow.ShowActivated = true;
|
||||||
|
overlayWindow.Topmost = true;
|
||||||
|
overlayWindow.Show();
|
||||||
|
_overlayWindows.Add(overlayWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
_snapshotWindow = new SnapshotWindow(this);
|
||||||
|
_snapshotWindow.ShowActivated = true;
|
||||||
|
_snapshotWindow.Topmost = true;
|
||||||
|
_snapshotWindow.Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void CancelSnapshot()
|
||||||
|
{
|
||||||
|
foreach (OverlayWindow overlayWindow in _overlayWindows)
|
||||||
|
{
|
||||||
|
overlayWindow.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
_mainWindow.WindowState = System.Windows.WindowState.Normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async void LaunchAndEdit(Project project)
|
||||||
|
{
|
||||||
|
await Task.Run(() => RunLauncher(project.Id, InvokePoint.LaunchAndEdit));
|
||||||
|
projectBeforeLaunch = new Project(project);
|
||||||
|
EnterSnapshotMode(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendCreateTelemetryEvent(Project project)
|
||||||
|
{
|
||||||
|
var telemetryEvent = new CreateEvent();
|
||||||
|
telemetryEvent.Successful = true;
|
||||||
|
telemetryEvent.NumScreens = project.Monitors.Count;
|
||||||
|
telemetryEvent.AppCount = project.Applications.Count;
|
||||||
|
telemetryEvent.CliCount = project.Applications.FindAll(app => app.CommandLineArguments.Length > 0).Count;
|
||||||
|
telemetryEvent.ShortcutCreated = project.IsShortcutNeeded;
|
||||||
|
telemetryEvent.AdminCount = project.Applications.FindAll(app => app.IsElevated).Count;
|
||||||
|
PowerToysTelemetry.Log.WriteEvent(telemetryEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendEditTelemetryEvent(Project updatedProject, Project prevProject)
|
||||||
|
{
|
||||||
|
int appsRemovedCount = updatedProject.Applications.FindAll(val => !val.IsIncluded).Count;
|
||||||
|
foreach (var app in prevProject.Applications)
|
||||||
|
{
|
||||||
|
var updatedApp = updatedProject.Applications.Find(val => app.AppName == val.AppName && app.Position == val.Position);
|
||||||
|
if (updatedApp == null)
|
||||||
|
{
|
||||||
|
appsRemovedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int appsAddedCount = 0;
|
||||||
|
int cliAdded = 0, cliRemoved = 0;
|
||||||
|
int adminAdded = 0, adminRemoved = 0;
|
||||||
|
foreach (var app in updatedProject.Applications)
|
||||||
|
{
|
||||||
|
var prevApp = prevProject.Applications.Find(val => app.AppName == val.AppName && app.Position == val.Position);
|
||||||
|
if (prevApp == null)
|
||||||
|
{
|
||||||
|
if (app.IsIncluded)
|
||||||
|
{
|
||||||
|
appsAddedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app.CommandLineArguments.Length > 0 && prevApp.CommandLineArguments.Length == 0)
|
||||||
|
{
|
||||||
|
cliAdded++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevApp.CommandLineArguments.Length > 0 && app.CommandLineArguments.Length == 0)
|
||||||
|
{
|
||||||
|
cliRemoved++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app.IsElevated && !prevApp.IsElevated)
|
||||||
|
{
|
||||||
|
adminAdded++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!app.IsElevated && prevApp.IsElevated)
|
||||||
|
{
|
||||||
|
adminRemoved++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var telemetryEvent = new EditEvent();
|
||||||
|
telemetryEvent.Successful = true;
|
||||||
|
telemetryEvent.ScreenCountDelta = updatedProject.Monitors.Count - prevProject.Monitors.Count;
|
||||||
|
telemetryEvent.AppsAdded = appsAddedCount;
|
||||||
|
telemetryEvent.AppsRemoved = appsRemovedCount;
|
||||||
|
telemetryEvent.CliAdded = cliAdded;
|
||||||
|
telemetryEvent.CliRemoved = cliRemoved;
|
||||||
|
telemetryEvent.AdminAdded = adminAdded;
|
||||||
|
telemetryEvent.AdminRemoved = adminRemoved;
|
||||||
|
telemetryEvent.PixelAdjustmentsUsed = updatedProject.IsPositionChangedManually;
|
||||||
|
PowerToysTelemetry.Log.WriteEvent(telemetryEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendDeleteTelemetryEvent()
|
||||||
|
{
|
||||||
|
var telemetryEvent = new EditEvent();
|
||||||
|
telemetryEvent.Successful = true;
|
||||||
|
PowerToysTelemetry.Log.WriteEvent(telemetryEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
106
src/modules/Workspaces/WorkspacesEditor/WorkspacesEditor.csproj
Normal file
106
src/modules/Workspaces/WorkspacesEditor/WorkspacesEditor.csproj
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||||
|
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||||
|
<Import Project="..\..\..\Common.SelfContained.props" />
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<AssemblyTitle>PowerToys.WorkspacesEditor</AssemblyTitle>
|
||||||
|
<AssemblyDescription>PowerToys Workspaces Editor</AssemblyDescription>
|
||||||
|
<Description>PowerToys Workspaces Editor</Description>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||||
|
<GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
|
||||||
|
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)</OutputPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<ProjectGuid>{367D7543-7DBA-4381-99F1-BF6142A996C4}</ProjectGuid>
|
||||||
|
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||||
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<ApplicationIcon>images\Workspaces.ico</ApplicationIcon>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
|
<AssemblyName>PowerToys.WorkspacesEditor</AssemblyName>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="images\DefaultIcon.ico" />
|
||||||
|
<None Remove="images\Workspaces.ico" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<COMReference Include="IWshRuntimeLibrary">
|
||||||
|
<WrapperTool>tlbimp</WrapperTool>
|
||||||
|
<VersionMinor>0</VersionMinor>
|
||||||
|
<VersionMajor>1</VersionMajor>
|
||||||
|
<Guid>f935dc20-1cf0-11d0-adb9-00c04fd58a0b</Guid>
|
||||||
|
<Lcid>0</Lcid>
|
||||||
|
<Isolated>false</Isolated>
|
||||||
|
<EmbedInteropTypes>true</EmbedInteropTypes>
|
||||||
|
</COMReference>
|
||||||
|
<COMReference Include="Shell32">
|
||||||
|
<WrapperTool>tlbimp</WrapperTool>
|
||||||
|
<VersionMinor>0</VersionMinor>
|
||||||
|
<VersionMajor>1</VersionMajor>
|
||||||
|
<Guid>50a7e9b0-70ef-11d1-b75a-00a0c90564fe</Guid>
|
||||||
|
<Lcid>0</Lcid>
|
||||||
|
<Isolated>false</Isolated>
|
||||||
|
<EmbedInteropTypes>true</EmbedInteropTypes>
|
||||||
|
</COMReference>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="images\DefaultIcon.ico">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="images\Workspaces.ico" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Update="Properties\Resources.resx">
|
||||||
|
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||||
|
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||||
|
</EmbeddedResource>
|
||||||
|
<None Include="app.manifest" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="ControlzEx" />
|
||||||
|
<PackageReference Include="ModernWpfUI" />
|
||||||
|
<PackageReference Include="System.IO.Abstractions" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
|
||||||
|
<ProjectReference Include="..\..\..\common\GPOWrapperProjection\GPOWrapperProjection.csproj" />
|
||||||
|
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
|
||||||
|
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||||
|
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
|
||||||
|
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Update="Properties\Resources.Designer.cs">
|
||||||
|
<DesignTime>True</DesignTime>
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>Resources.resx</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Update="Properties\Settings.Designer.cs">
|
||||||
|
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>Settings.settings</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="Properties\Settings.settings">
|
||||||
|
<Generator>SettingsSingleFileGenerator</Generator>
|
||||||
|
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<_DeploymentManifestIconFile Remove="images\Workspaces.ico" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
@ -0,0 +1,420 @@
|
|||||||
|
<Page
|
||||||
|
x:Class="WorkspacesEditor.ProjectEditor"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:controls="clr-namespace:WorkspacesEditor.Controls"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="clr-namespace:WorkspacesEditor"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:models="clr-namespace:WorkspacesEditor.Models"
|
||||||
|
xmlns:props="clr-namespace:WorkspacesEditor.Properties"
|
||||||
|
Title="Workspaces Editor"
|
||||||
|
Background="{DynamicResource PrimaryBackgroundBrush}"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<Page.Resources>
|
||||||
|
<BooleanToVisibilityConverter x:Key="BoolToVis" />
|
||||||
|
|
||||||
|
<Style x:Key="TextBlockEnabledStyle" TargetType="TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource PrimaryForegroundBrush}" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsEnabled" Value="False">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource SecondaryForegroundBrush}" />
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
<Style x:Key="ButtonEnabledStyle" TargetType="Button">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource PrimaryForegroundBrush}" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsEnabled" Value="False">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource SecondaryForegroundBrush}" />
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<DataTemplate x:Key="headerTemplate">
|
||||||
|
<Border HorizontalAlignment="Stretch">
|
||||||
|
<TextBlock
|
||||||
|
Margin="0,0,20,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
DockPanel.Dock="Left"
|
||||||
|
FontSize="14"
|
||||||
|
FontWeight="Normal"
|
||||||
|
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||||
|
Text="{Binding MonitorName}" />
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
<DataTemplate x:Key="appTemplate">
|
||||||
|
<Border
|
||||||
|
Margin="1"
|
||||||
|
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||||
|
MouseEnter="AppBorder_MouseEnter"
|
||||||
|
MouseLeave="AppBorder_MouseLeave">
|
||||||
|
<Expander
|
||||||
|
Margin="0,0,20,0"
|
||||||
|
FlowDirection="RightToLeft"
|
||||||
|
IsEnabled="{Binding IsIncluded, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
IsExpanded="{Binding IsExpanded, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
|
||||||
|
<Expander.Header>
|
||||||
|
<Grid HorizontalAlignment="{Binding HorizontalAlignment, RelativeSource={RelativeSource AncestorType=ContentPresenter}, Mode=OneWayToSource}" FlowDirection="LeftToRight">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="20" />
|
||||||
|
<ColumnDefinition Width="60" />
|
||||||
|
<ColumnDefinition Width="20" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock
|
||||||
|
Margin="5,0,0,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||||
|
FontSize="14"
|
||||||
|
FontWeight="Normal"
|
||||||
|
Foreground="#EED202"
|
||||||
|
Text=""
|
||||||
|
ToolTip="{x:Static props:Resources.NotFoundTooltip}"
|
||||||
|
Visibility="{Binding IsNotFound, Converter={StaticResource BoolToVis}, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<Image
|
||||||
|
Grid.Column="1"
|
||||||
|
Width="20"
|
||||||
|
Height="20"
|
||||||
|
Margin="10"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Source="{Binding IconBitmapImage}" />
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="2"
|
||||||
|
Width="20"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontSize="14"
|
||||||
|
FontWeight="Normal"
|
||||||
|
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||||
|
Text="{Binding RepeatIndexString, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<StackPanel Grid.Column="3" VerticalAlignment="Center">
|
||||||
|
<TextBlock
|
||||||
|
FontSize="14"
|
||||||
|
FontWeight="Normal"
|
||||||
|
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||||
|
Text="{Binding AppName}" />
|
||||||
|
<TextBlock
|
||||||
|
FontSize="12"
|
||||||
|
FontWeight="Normal"
|
||||||
|
Foreground="{DynamicResource SecondaryForegroundBrush}"
|
||||||
|
Text="{Binding AppMainParams, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Visibility="{Binding IsAppMainParamVisible, Converter={StaticResource BoolToVis}, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
</StackPanel>
|
||||||
|
<controls:ResetIsEnabled Grid.Column="4">
|
||||||
|
<Button
|
||||||
|
Width="120"
|
||||||
|
Margin="10,5"
|
||||||
|
Padding="24,6"
|
||||||
|
AutomationProperties.Name="{x:Static props:Resources.Delete}"
|
||||||
|
Background="{DynamicResource TertiaryBackgroundBrush}"
|
||||||
|
Click="DeleteButtonClicked"
|
||||||
|
Content="{Binding DeleteButtonContent, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
IsEnabled="True" />
|
||||||
|
</controls:ResetIsEnabled>
|
||||||
|
</Grid>
|
||||||
|
</Expander.Header>
|
||||||
|
<Grid
|
||||||
|
Margin="-20,0,0,0"
|
||||||
|
HorizontalAlignment="{Binding HorizontalAlignment, RelativeSource={RelativeSource AncestorType=ContentPresenter}, Mode=OneWayToSource}"
|
||||||
|
Background="{DynamicResource QuaternaryBackgroundBrush}"
|
||||||
|
FlowDirection="LeftToRight">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<DockPanel Margin="100,5,0,0">
|
||||||
|
<TextBlock
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontSize="14"
|
||||||
|
FontWeight="Normal"
|
||||||
|
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||||
|
Text="{x:Static props:Resources.CliArguments}" />
|
||||||
|
<TextBox
|
||||||
|
x:Name="CommandLineTextBox"
|
||||||
|
Margin="15,0,50,0"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Background="{DynamicResource TertiaryBackgroundBrush}"
|
||||||
|
BorderThickness="0"
|
||||||
|
FontSize="14"
|
||||||
|
FontWeight="Normal"
|
||||||
|
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||||
|
Text="{Binding CommandLineArguments, Mode=TwoWay}"
|
||||||
|
TextChanged="CommandLineTextBox_TextChanged" />
|
||||||
|
</DockPanel>
|
||||||
|
<StackPanel
|
||||||
|
Grid.Row="1"
|
||||||
|
Margin="100,5,0,0"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<CheckBox
|
||||||
|
MinWidth="10"
|
||||||
|
Content="{x:Static props:Resources.LaunchAsAdmin}"
|
||||||
|
IsChecked="{Binding IsElevated, Mode=TwoWay}"
|
||||||
|
IsEnabled="{Binding CanLaunchElevated, Mode=OneWay}" />
|
||||||
|
<CheckBox
|
||||||
|
MinWidth="10"
|
||||||
|
Margin="15,0,0,0"
|
||||||
|
Checked="MaximizedChecked"
|
||||||
|
Content="{x:Static props:Resources.Maximized}"
|
||||||
|
IsChecked="{Binding Maximized, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<CheckBox
|
||||||
|
MinWidth="10"
|
||||||
|
Margin="15,0,0,0"
|
||||||
|
Checked="MinimizedChecked"
|
||||||
|
Content="{x:Static props:Resources.Minimized}"
|
||||||
|
IsChecked="{Binding Minimized, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel
|
||||||
|
Grid.Row="2"
|
||||||
|
Margin="100,5,0,0"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<TextBlock
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontSize="14"
|
||||||
|
FontWeight="Normal"
|
||||||
|
IsEnabled="{Binding EditPositionEnabled, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Style="{StaticResource TextBlockEnabledStyle}"
|
||||||
|
Text="{x:Static props:Resources.Left}" />
|
||||||
|
<TextBox
|
||||||
|
x:Name="LeftTextBox"
|
||||||
|
Margin="15,0,0,0"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Background="{DynamicResource TertiaryBackgroundBrush}"
|
||||||
|
BorderThickness="0"
|
||||||
|
FontSize="14"
|
||||||
|
FontWeight="Normal"
|
||||||
|
IsEnabled="{Binding EditPositionEnabled, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Text="{Binding Position.X, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
TextChanged="LeftTextBox_TextChanged" />
|
||||||
|
<TextBlock
|
||||||
|
Margin="15,0,0,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontSize="14"
|
||||||
|
FontWeight="Normal"
|
||||||
|
IsEnabled="{Binding EditPositionEnabled, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Style="{StaticResource TextBlockEnabledStyle}"
|
||||||
|
Text="{x:Static props:Resources.Top}" />
|
||||||
|
<TextBox
|
||||||
|
x:Name="TopTextBox"
|
||||||
|
Margin="15,0,0,0"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Background="{DynamicResource TertiaryBackgroundBrush}"
|
||||||
|
BorderThickness="0"
|
||||||
|
FontSize="14"
|
||||||
|
FontWeight="Normal"
|
||||||
|
IsEnabled="{Binding EditPositionEnabled, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Text="{Binding Position.Y, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
TextChanged="TopTextBox_TextChanged" />
|
||||||
|
<TextBlock
|
||||||
|
Margin="15,0,0,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontSize="14"
|
||||||
|
FontWeight="Normal"
|
||||||
|
IsEnabled="{Binding EditPositionEnabled, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Style="{StaticResource TextBlockEnabledStyle}"
|
||||||
|
Text="{x:Static props:Resources.Width}" />
|
||||||
|
<TextBox
|
||||||
|
x:Name="WidthTextBox"
|
||||||
|
Margin="15,0,0,0"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Background="{DynamicResource TertiaryBackgroundBrush}"
|
||||||
|
BorderThickness="0"
|
||||||
|
FontSize="14"
|
||||||
|
FontWeight="Normal"
|
||||||
|
IsEnabled="{Binding EditPositionEnabled, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Text="{Binding Position.Width, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
TextChanged="WidthTextBox_TextChanged" />
|
||||||
|
<TextBlock
|
||||||
|
Margin="15,0,0,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontSize="14"
|
||||||
|
FontWeight="Normal"
|
||||||
|
IsEnabled="{Binding EditPositionEnabled, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Style="{StaticResource TextBlockEnabledStyle}"
|
||||||
|
Text="{x:Static props:Resources.Height}" />
|
||||||
|
<TextBox
|
||||||
|
x:Name="HeightTextBox"
|
||||||
|
Margin="15,0,0,0"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Background="{DynamicResource TertiaryBackgroundBrush}"
|
||||||
|
BorderThickness="0"
|
||||||
|
FontSize="14"
|
||||||
|
FontWeight="Normal"
|
||||||
|
IsEnabled="{Binding EditPositionEnabled, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Text="{Binding Position.Height, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
TextChanged="HeightTextBox_TextChanged" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Expander>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
<models:AppListDataTemplateSelector
|
||||||
|
x:Key="AppListDataTemplateSelector"
|
||||||
|
AppTemplate="{StaticResource appTemplate}"
|
||||||
|
HeaderTemplate="{StaticResource headerTemplate}" />
|
||||||
|
</Page.Resources>
|
||||||
|
<Grid Margin="40,0,40,40">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<StackPanel Grid.Row="0" Orientation="Horizontal">
|
||||||
|
<Button
|
||||||
|
Margin="0,20,0,20"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Background="Transparent"
|
||||||
|
Click="CancelButtonClicked">
|
||||||
|
<TextBlock
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontSize="24"
|
||||||
|
FontWeight="Normal"
|
||||||
|
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||||
|
Text="{x:Static props:Resources.Workspaces}" />
|
||||||
|
</Button>
|
||||||
|
<TextBlock
|
||||||
|
Margin="10,0,0,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||||
|
FontSize="16"
|
||||||
|
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||||
|
Text="" />
|
||||||
|
<TextBlock
|
||||||
|
Margin="10,0,0,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontSize="24"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||||
|
Text="{Binding EditorWindowTitle}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Grid.Row="1" Orientation="Vertical">
|
||||||
|
<TextBlock
|
||||||
|
FontSize="14"
|
||||||
|
FontWeight="Normal"
|
||||||
|
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||||
|
Text="{x:Static props:Resources.WorkspaceName}" />
|
||||||
|
<TextBox
|
||||||
|
x:Name="EditNameTextBox"
|
||||||
|
Width="320"
|
||||||
|
Margin="0,6,0,6"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||||
|
BorderBrush="{DynamicResource PrimaryBorderBrush}"
|
||||||
|
BorderThickness="2"
|
||||||
|
GotFocus="EditNameTextBox_GotFocus"
|
||||||
|
KeyDown="EditNameTextBoxKeyDown"
|
||||||
|
Text="{Binding Name, Mode=TwoWay}"
|
||||||
|
TextChanged="EditNameTextBox_TextChanged" />
|
||||||
|
</StackPanel>
|
||||||
|
<Border
|
||||||
|
Grid.Row="2"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Background="{DynamicResource MonitorViewBackgroundBrush}"
|
||||||
|
CornerRadius="5">
|
||||||
|
<DockPanel>
|
||||||
|
<Image
|
||||||
|
Width="{Binding PreviewImageWidth, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Height="200"
|
||||||
|
Margin="2"
|
||||||
|
DockPanel.Dock="Top"
|
||||||
|
Source="{Binding PreviewImage, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Stretch="Fill" />
|
||||||
|
<Button
|
||||||
|
x:Name="RevertButton"
|
||||||
|
Height="36"
|
||||||
|
Margin="0,0,20,10"
|
||||||
|
Padding="24,0,24,0"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
AutomationProperties.Name="{x:Static props:Resources.Revert}"
|
||||||
|
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||||
|
Click="RevertButtonClicked"
|
||||||
|
Content="{x:Static props:Resources.Revert}"
|
||||||
|
DockPanel.Dock="Right"
|
||||||
|
IsEnabled="{Binding IsRevertEnabled, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<Button
|
||||||
|
x:Name="LaunchEditButton"
|
||||||
|
Height="36"
|
||||||
|
Margin="0,0,10,10"
|
||||||
|
Padding="24,0,24,0"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
AutomationProperties.Name="{x:Static props:Resources.LaunchEdit}"
|
||||||
|
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||||
|
Click="LaunchEditButtonClicked"
|
||||||
|
Content="{x:Static props:Resources.LaunchEdit}"
|
||||||
|
DockPanel.Dock="Right" />
|
||||||
|
</DockPanel>
|
||||||
|
</Border>
|
||||||
|
<ScrollViewer
|
||||||
|
Grid.Row="4"
|
||||||
|
Margin="0,10,0,0"
|
||||||
|
PreviewMouseWheel="ScrollViewer_PreviewMouseWheel"
|
||||||
|
VerticalScrollBarVisibility="Auto">
|
||||||
|
<StackPanel Orientation="Vertical">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<ItemsControl ItemTemplateSelector="{StaticResource AppListDataTemplateSelector}" ItemsSource="{Binding ApplicationsListed, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
<StackPanel
|
||||||
|
Grid.Row="5"
|
||||||
|
Margin="0,5,0,0"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Visibility="Collapsed">
|
||||||
|
<CheckBox
|
||||||
|
Margin="0,0,0,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Content="{x:Static props:Resources.MoveIfExist}"
|
||||||
|
FontSize="14"
|
||||||
|
FontWeight="Normal"
|
||||||
|
Foreground="{DynamicResource PrimaryForegroundBrush}"
|
||||||
|
IsChecked="{Binding MoveExistingWindows, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
</StackPanel>
|
||||||
|
<DockPanel Grid.Row="6" Margin="0,20,0,20">
|
||||||
|
<CheckBox
|
||||||
|
Content="{x:Static props:Resources.CreateShortcut}"
|
||||||
|
DockPanel.Dock="Left"
|
||||||
|
FontSize="14"
|
||||||
|
IsChecked="{Binding IsShortcutNeeded, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<StackPanel
|
||||||
|
Margin="40,0,0,0"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
DockPanel.Dock="Right"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<Button
|
||||||
|
x:Name="CancelButton"
|
||||||
|
Height="36"
|
||||||
|
Margin="20,0,0,0"
|
||||||
|
Padding="24,0,24,0"
|
||||||
|
AutomationProperties.Name="{x:Static props:Resources.Cancel}"
|
||||||
|
Background="{DynamicResource SecondaryBackgroundBrush}"
|
||||||
|
Click="CancelButtonClicked"
|
||||||
|
Content="{x:Static props:Resources.Cancel}" />
|
||||||
|
<Button
|
||||||
|
x:Name="SaveButton"
|
||||||
|
Height="36"
|
||||||
|
Margin="20,0,0,0"
|
||||||
|
Padding="24,0,24,0"
|
||||||
|
AutomationProperties.Name="{x:Static props:Resources.Save_Workspace}"
|
||||||
|
Click="SaveButtonClicked"
|
||||||
|
Content="{x:Static props:Resources.Save_Workspace}"
|
||||||
|
IsEnabled="{Binding CanBeSaved, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Style="{StaticResource AccentButtonStyle}" />
|
||||||
|
</StackPanel>
|
||||||
|
</DockPanel>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
@ -0,0 +1,221 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using WorkspacesEditor.Data;
|
||||||
|
using WorkspacesEditor.Models;
|
||||||
|
using WorkspacesEditor.ViewModels;
|
||||||
|
|
||||||
|
namespace WorkspacesEditor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for ProjectEditor.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class ProjectEditor : Page
|
||||||
|
{
|
||||||
|
private const double ScrollSpeed = 15;
|
||||||
|
private MainViewModel _mainViewModel;
|
||||||
|
|
||||||
|
public ProjectEditor(MainViewModel mainViewModel)
|
||||||
|
{
|
||||||
|
_mainViewModel = mainViewModel;
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveButtonClicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Project projectToSave = this.DataContext as Project;
|
||||||
|
projectToSave.CloseExpanders();
|
||||||
|
|
||||||
|
if (_mainViewModel.Workspaces.Any(x => x.Id == projectToSave.Id))
|
||||||
|
{
|
||||||
|
_mainViewModel.SaveProject(projectToSave);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_mainViewModel.AddNewProject(projectToSave);
|
||||||
|
}
|
||||||
|
|
||||||
|
_mainViewModel.SwitchToMainView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelButtonClicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
// delete the temp file created by the snapshot tool
|
||||||
|
TempProjectData.DeleteTempFile();
|
||||||
|
|
||||||
|
_mainViewModel.SwitchToMainView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteButtonClicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Button button = sender as Button;
|
||||||
|
Models.Application app = button.DataContext as Models.Application;
|
||||||
|
app.SwitchDeletion();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EditNameTextBoxKeyDown(object sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Key == Key.Enter)
|
||||||
|
{
|
||||||
|
e.Handled = true;
|
||||||
|
Project project = this.DataContext as Project;
|
||||||
|
TextBox textBox = sender as TextBox;
|
||||||
|
project.Name = textBox.Text;
|
||||||
|
}
|
||||||
|
else if (e.Key == Key.Escape)
|
||||||
|
{
|
||||||
|
e.Handled = true;
|
||||||
|
Project project = this.DataContext as Project;
|
||||||
|
_mainViewModel.CancelProjectName(project);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EditNameTextBox_GotFocus(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_mainViewModel.SaveProjectName(DataContext as Project);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AppBorder_MouseEnter(object sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
Border border = sender as Border;
|
||||||
|
Models.Application app = border.DataContext as Models.Application;
|
||||||
|
app.IsHighlighted = true;
|
||||||
|
Project project = app.Parent;
|
||||||
|
project.Initialize(App.ThemeManager.GetCurrentTheme());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AppBorder_MouseLeave(object sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
Border border = sender as Border;
|
||||||
|
Models.Application app = border.DataContext as Models.Application;
|
||||||
|
if (app == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
app.IsHighlighted = false;
|
||||||
|
Project project = app.Parent;
|
||||||
|
project.Initialize(App.ThemeManager.GetCurrentTheme());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EditNameTextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||||
|
{
|
||||||
|
Project project = this.DataContext as Project;
|
||||||
|
TextBox textBox = sender as TextBox;
|
||||||
|
project.Name = textBox.Text;
|
||||||
|
project.OnPropertyChanged(new PropertyChangedEventArgs(nameof(Project.CanBeSaved)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LeftTextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||||
|
{
|
||||||
|
TextBox textBox = sender as TextBox;
|
||||||
|
Models.Application application = textBox.DataContext as Models.Application;
|
||||||
|
int newPos;
|
||||||
|
if (!int.TryParse(textBox.Text, out newPos))
|
||||||
|
{
|
||||||
|
newPos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
application.Position = new Models.Application.WindowPosition() { X = newPos, Y = application.Position.Y, Width = application.Position.Width, Height = application.Position.Height };
|
||||||
|
Project project = application.Parent;
|
||||||
|
project.IsPositionChangedManually = true;
|
||||||
|
project.Initialize(App.ThemeManager.GetCurrentTheme());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TopTextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||||
|
{
|
||||||
|
TextBox textBox = sender as TextBox;
|
||||||
|
Models.Application application = textBox.DataContext as Models.Application;
|
||||||
|
int newPos;
|
||||||
|
if (!int.TryParse(textBox.Text, out newPos))
|
||||||
|
{
|
||||||
|
newPos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
application.Position = new Models.Application.WindowPosition() { X = application.Position.X, Y = newPos, Width = application.Position.Width, Height = application.Position.Height };
|
||||||
|
Project project = application.Parent;
|
||||||
|
project.IsPositionChangedManually = true;
|
||||||
|
project.Initialize(App.ThemeManager.GetCurrentTheme());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WidthTextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||||
|
{
|
||||||
|
TextBox textBox = sender as TextBox;
|
||||||
|
Models.Application application = textBox.DataContext as Models.Application;
|
||||||
|
int newPos;
|
||||||
|
if (!int.TryParse(textBox.Text, out newPos))
|
||||||
|
{
|
||||||
|
newPos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
application.Position = new Models.Application.WindowPosition() { X = application.Position.X, Y = application.Position.Y, Width = newPos, Height = application.Position.Height };
|
||||||
|
Project project = application.Parent;
|
||||||
|
project.IsPositionChangedManually = true;
|
||||||
|
project.Initialize(App.ThemeManager.GetCurrentTheme());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HeightTextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||||
|
{
|
||||||
|
TextBox textBox = sender as TextBox;
|
||||||
|
Models.Application application = textBox.DataContext as Models.Application;
|
||||||
|
int newPos;
|
||||||
|
if (!int.TryParse(textBox.Text, out newPos))
|
||||||
|
{
|
||||||
|
newPos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
application.Position = new Models.Application.WindowPosition() { X = application.Position.X, Y = application.Position.Y, Width = application.Position.Width, Height = newPos };
|
||||||
|
Project project = application.Parent;
|
||||||
|
project.IsPositionChangedManually = true;
|
||||||
|
project.Initialize(App.ThemeManager.GetCurrentTheme());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CommandLineTextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||||
|
{
|
||||||
|
TextBox textBox = sender as TextBox;
|
||||||
|
Models.Application application = textBox.DataContext as Models.Application;
|
||||||
|
application.CommandLineTextChanged(textBox.Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MaximizedChecked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
CheckBox checkBox = sender as CheckBox;
|
||||||
|
Models.Application application = checkBox.DataContext as Models.Application;
|
||||||
|
application.MaximizedChecked();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MinimizedChecked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
CheckBox checkBox = sender as CheckBox;
|
||||||
|
Models.Application application = checkBox.DataContext as Models.Application;
|
||||||
|
application.MinimizedChecked();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LaunchEditButtonClicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Button button = sender as Button;
|
||||||
|
Project project = button.DataContext as Project;
|
||||||
|
_mainViewModel.LaunchAndEdit(project);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RevertButtonClicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_mainViewModel.RevertLaunch();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
|
||||||
|
{
|
||||||
|
ScrollViewer scrollViewer = sender as ScrollViewer;
|
||||||
|
double scrollAmount = Math.Sign(e.Delta) * ScrollSpeed;
|
||||||
|
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - scrollAmount);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
74
src/modules/Workspaces/WorkspacesEditor/app.manifest
Normal file
74
src/modules/Workspaces/WorkspacesEditor/app.manifest
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<!-- UAC Manifest Options
|
||||||
|
If you want to change the Windows User Account Control level replace the
|
||||||
|
requestedExecutionLevel node with one of the following.
|
||||||
|
|
||||||
|
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||||
|
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||||
|
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
|
||||||
|
|
||||||
|
Specifying requestedExecutionLevel element will disable file and registry virtualization.
|
||||||
|
Remove this element if your application requires this virtualization for backwards
|
||||||
|
compatibility.
|
||||||
|
-->
|
||||||
|
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||||
|
</requestedPrivileges>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
|
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
<application>
|
||||||
|
<!-- A list of the Windows versions that this application has been tested on
|
||||||
|
and is designed to work with. Uncomment the appropriate elements
|
||||||
|
and Windows will automatically select the most compatible environment. -->
|
||||||
|
|
||||||
|
<!-- Windows Vista -->
|
||||||
|
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
|
||||||
|
|
||||||
|
<!-- Windows 7 -->
|
||||||
|
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
|
||||||
|
|
||||||
|
<!-- Windows 8 -->
|
||||||
|
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
|
||||||
|
|
||||||
|
<!-- Windows 8.1 -->
|
||||||
|
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
|
||||||
|
|
||||||
|
<!-- Windows 10 -->
|
||||||
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||||
|
|
||||||
|
</application>
|
||||||
|
</compatibility>
|
||||||
|
|
||||||
|
<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
|
||||||
|
DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
|
||||||
|
to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
|
||||||
|
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
|
||||||
|
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<windowsSettings>
|
||||||
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||||
|
</windowsSettings>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
|
||||||
|
<!--
|
||||||
|
<dependency>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity
|
||||||
|
type="win32"
|
||||||
|
name="Microsoft.Windows.Common-Controls"
|
||||||
|
version="6.0.0.0"
|
||||||
|
processorArchitecture="*"
|
||||||
|
publicKeyToken="6595b64144ccf1df"
|
||||||
|
language="*"
|
||||||
|
/>
|
||||||
|
</dependentAssembly>
|
||||||
|
</dependency>
|
||||||
|
-->
|
||||||
|
|
||||||
|
</assembly>
|
BIN
src/modules/Workspaces/WorkspacesEditor/images/DefaultIcon.ico
Normal file
BIN
src/modules/Workspaces/WorkspacesEditor/images/DefaultIcon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 110 KiB |
BIN
src/modules/Workspaces/WorkspacesEditor/images/Workspaces.ico
Normal file
BIN
src/modules/Workspaces/WorkspacesEditor/images/Workspaces.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 456 KiB |
479
src/modules/Workspaces/WorkspacesLauncher/AppLauncher.cpp
Normal file
479
src/modules/Workspaces/WorkspacesLauncher/AppLauncher.cpp
Normal file
@ -0,0 +1,479 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "AppLauncher.h"
|
||||||
|
|
||||||
|
#include <winrt/Windows.Management.Deployment.h>
|
||||||
|
#include <winrt/Windows.ApplicationModel.Core.h>
|
||||||
|
|
||||||
|
#include <shellapi.h>
|
||||||
|
#include <ShellScalingApi.h>
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include <workspaces-common/MonitorEnumerator.h>
|
||||||
|
#include <workspaces-common/WindowEnumerator.h>
|
||||||
|
#include <workspaces-common/WindowFilter.h>
|
||||||
|
|
||||||
|
#include <WorkspacesLib/AppUtils.h>
|
||||||
|
|
||||||
|
#include <common/Display/dpi_aware.h>
|
||||||
|
#include <common/utils/winapi_error.h>
|
||||||
|
|
||||||
|
#include <LaunchingApp.h>
|
||||||
|
#include <LauncherUIHelper.h>
|
||||||
|
#include <RegistryUtils.h>
|
||||||
|
#include <WindowProperties/WorkspacesWindowPropertyUtils.h>
|
||||||
|
|
||||||
|
using namespace winrt;
|
||||||
|
using namespace Windows::Foundation;
|
||||||
|
using namespace Windows::Management::Deployment;
|
||||||
|
|
||||||
|
namespace FancyZones
|
||||||
|
{
|
||||||
|
inline bool allMonitorsHaveSameDpiScaling()
|
||||||
|
{
|
||||||
|
auto monitors = MonitorEnumerator::Enumerate();
|
||||||
|
if (monitors.size() < 2)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT firstMonitorDpiX;
|
||||||
|
UINT firstMonitorDpiY;
|
||||||
|
|
||||||
|
if (S_OK != GetDpiForMonitor(monitors[0].first, MDT_EFFECTIVE_DPI, &firstMonitorDpiX, &firstMonitorDpiY))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i < monitors.size(); i++)
|
||||||
|
{
|
||||||
|
UINT iteratedMonitorDpiX;
|
||||||
|
UINT iteratedMonitorDpiY;
|
||||||
|
|
||||||
|
if (S_OK != GetDpiForMonitor(monitors[i].first, MDT_EFFECTIVE_DPI, &iteratedMonitorDpiX, &iteratedMonitorDpiY) ||
|
||||||
|
iteratedMonitorDpiX != firstMonitorDpiX)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ScreenToWorkAreaCoords(HWND window, HMONITOR monitor, RECT& rect)
|
||||||
|
{
|
||||||
|
MONITORINFOEXW monitorInfo{ sizeof(MONITORINFOEXW) };
|
||||||
|
GetMonitorInfoW(monitor, &monitorInfo);
|
||||||
|
|
||||||
|
auto xOffset = monitorInfo.rcWork.left - monitorInfo.rcMonitor.left;
|
||||||
|
auto yOffset = monitorInfo.rcWork.top - monitorInfo.rcMonitor.top;
|
||||||
|
|
||||||
|
DPIAware::Convert(monitor, rect);
|
||||||
|
|
||||||
|
auto referenceRect = RECT(rect.left - xOffset, rect.top - yOffset, rect.right - xOffset, rect.bottom - yOffset);
|
||||||
|
|
||||||
|
// Now, this rect should be used to determine the monitor and thus taskbar size. This fixes
|
||||||
|
// scenarios where the zone lies approximately between two monitors, and the taskbar is on the left.
|
||||||
|
monitor = MonitorFromRect(&referenceRect, MONITOR_DEFAULTTOPRIMARY);
|
||||||
|
GetMonitorInfoW(monitor, &monitorInfo);
|
||||||
|
|
||||||
|
xOffset = monitorInfo.rcWork.left - monitorInfo.rcMonitor.left;
|
||||||
|
yOffset = monitorInfo.rcWork.top - monitorInfo.rcMonitor.top;
|
||||||
|
|
||||||
|
rect.left -= xOffset;
|
||||||
|
rect.right -= xOffset;
|
||||||
|
rect.top -= yOffset;
|
||||||
|
rect.bottom -= yOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool SizeWindowToRect(HWND window, HMONITOR monitor, bool isMinimized, bool isMaximized, RECT rect) noexcept
|
||||||
|
{
|
||||||
|
WINDOWPLACEMENT placement{};
|
||||||
|
::GetWindowPlacement(window, &placement);
|
||||||
|
|
||||||
|
if (isMinimized)
|
||||||
|
{
|
||||||
|
placement.showCmd = SW_MINIMIZE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ((placement.showCmd != SW_SHOWMINIMIZED) &&
|
||||||
|
(placement.showCmd != SW_MINIMIZE))
|
||||||
|
{
|
||||||
|
if (placement.showCmd == SW_SHOWMAXIMIZED)
|
||||||
|
placement.flags &= ~WPF_RESTORETOMAXIMIZED;
|
||||||
|
|
||||||
|
placement.showCmd = SW_RESTORE;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScreenToWorkAreaCoords(window, monitor, rect);
|
||||||
|
placement.rcNormalPosition = rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
placement.flags |= WPF_ASYNCWINDOWPLACEMENT;
|
||||||
|
|
||||||
|
auto result = ::SetWindowPlacement(window, &placement);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
Logger::error(L"SetWindowPlacement failed, {}", get_last_error_or_default(GetLastError()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure window is moved to the correct monitor before maximize.
|
||||||
|
if (isMaximized)
|
||||||
|
{
|
||||||
|
placement.showCmd = SW_SHOWMAXIMIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do it again, allowing Windows to resize the window and set correct scaling
|
||||||
|
// This fixes Issue #365
|
||||||
|
result = ::SetWindowPlacement(window, &placement);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
Logger::error(L"SetWindowPlacement failed, {}", get_last_error_or_default(GetLastError()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
LaunchingApps Prepare(std::vector<WorkspacesData::WorkspacesProject::Application>& apps, const Utils::Apps::AppList& installedApps)
|
||||||
|
{
|
||||||
|
LaunchingApps launchedApps{};
|
||||||
|
launchedApps.reserve(apps.size());
|
||||||
|
|
||||||
|
for (auto& app : apps)
|
||||||
|
{
|
||||||
|
// Packaged apps have version in the path, it will be outdated after update.
|
||||||
|
// We need make sure the current package is up to date.
|
||||||
|
if (!app.packageFullName.empty())
|
||||||
|
{
|
||||||
|
auto installedApp = std::find_if(installedApps.begin(), installedApps.end(), [&](const Utils::Apps::AppData& val) { return val.name == app.name; });
|
||||||
|
if (installedApp != installedApps.end() && app.packageFullName != installedApp->packageFullName)
|
||||||
|
{
|
||||||
|
std::wstring exeFileName = app.path.substr(app.path.find_last_of(L"\\") + 1);
|
||||||
|
app.packageFullName = installedApp->packageFullName;
|
||||||
|
app.path = installedApp->installPath + L"\\" + exeFileName;
|
||||||
|
Logger::trace(L"Updated package full name for {}: {}", app.name, app.packageFullName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
launchedApps.push_back({ app, nullptr, L"waiting" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return launchedApps;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AllWindowsFound(const LaunchingApps& launchedApps)
|
||||||
|
{
|
||||||
|
return std::find_if(launchedApps.begin(), launchedApps.end(), [&](const LaunchingApp& val) {
|
||||||
|
return val.window == nullptr;
|
||||||
|
}) == launchedApps.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
bool AddOpenedWindows(LaunchingApps& launchedApps, const std::vector<HWND>& windows, const Utils::Apps::AppList& installedApps)
|
||||||
|
{
|
||||||
|
bool statusChanged = false;
|
||||||
|
for (HWND window : windows)
|
||||||
|
{
|
||||||
|
auto installedAppData = Utils::Apps::GetApp(window, installedApps);
|
||||||
|
if (!installedAppData.has_value())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto insertionIter = launchedApps.end();
|
||||||
|
for (auto iter = launchedApps.begin(); iter != launchedApps.end(); ++iter)
|
||||||
|
{
|
||||||
|
if (iter->window == nullptr && installedAppData.value().name == iter->application.name)
|
||||||
|
{
|
||||||
|
insertionIter = iter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep the window at the same position if it's already opened
|
||||||
|
WINDOWPLACEMENT placement{};
|
||||||
|
::GetWindowPlacement(window, &placement);
|
||||||
|
HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTOPRIMARY);
|
||||||
|
UINT dpi = DPIAware::DEFAULT_DPI;
|
||||||
|
DPIAware::GetScreenDPIForMonitor(monitor, dpi);
|
||||||
|
|
||||||
|
float x = static_cast<float>(placement.rcNormalPosition.left);
|
||||||
|
float y = static_cast<float>(placement.rcNormalPosition.top);
|
||||||
|
float width = static_cast<float>(placement.rcNormalPosition.right - placement.rcNormalPosition.left);
|
||||||
|
float height = static_cast<float>(placement.rcNormalPosition.bottom - placement.rcNormalPosition.top);
|
||||||
|
|
||||||
|
DPIAware::InverseConvert(monitor, x, y);
|
||||||
|
DPIAware::InverseConvert(monitor, width, height);
|
||||||
|
|
||||||
|
WorkspacesData::WorkspacesProject::Application::Position windowPosition{
|
||||||
|
.x = static_cast<int>(std::round(x)),
|
||||||
|
.y = static_cast<int>(std::round(y)),
|
||||||
|
.width = static_cast<int>(std::round(width)),
|
||||||
|
.height = static_cast<int>(std::round(height)),
|
||||||
|
};
|
||||||
|
if (iter->application.position == windowPosition)
|
||||||
|
{
|
||||||
|
Logger::debug(L"{} window already found at {} {}.", iter->application.name, iter->application.position.x, iter->application.position.y);
|
||||||
|
insertionIter = iter;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insertionIter != launchedApps.end())
|
||||||
|
{
|
||||||
|
insertionIter->window = window;
|
||||||
|
insertionIter->state = L"launched";
|
||||||
|
statusChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AllWindowsFound(launchedApps))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return statusChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated, ErrorList& launchErrors)
|
||||||
|
{
|
||||||
|
SHELLEXECUTEINFO sei = { 0 };
|
||||||
|
sei.cbSize = sizeof(SHELLEXECUTEINFO);
|
||||||
|
sei.hwnd = nullptr;
|
||||||
|
sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE;
|
||||||
|
sei.lpVerb = elevated ? L"runas" : L"open";
|
||||||
|
sei.lpFile = appPath.c_str();
|
||||||
|
sei.lpParameters = commandLineArgs.c_str();
|
||||||
|
sei.lpDirectory = nullptr;
|
||||||
|
sei.nShow = SW_SHOWNORMAL;
|
||||||
|
|
||||||
|
if (!ShellExecuteEx(&sei))
|
||||||
|
{
|
||||||
|
auto error = GetLastError();
|
||||||
|
Logger::error(L"Failed to launch process. {}", get_last_error_or_default(error));
|
||||||
|
launchErrors.push_back({ std::filesystem::path(appPath).filename(), get_last_error_or_default(error) });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LaunchPackagedApp(const std::wstring& packageFullName, ErrorList& launchErrors)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PackageManager packageManager;
|
||||||
|
for (const auto& package : packageManager.FindPackagesForUser({}))
|
||||||
|
{
|
||||||
|
if (package.Id().FullName() == packageFullName)
|
||||||
|
{
|
||||||
|
auto getAppListEntriesOperation = package.GetAppListEntriesAsync();
|
||||||
|
auto appEntries = getAppListEntriesOperation.get();
|
||||||
|
|
||||||
|
if (appEntries.Size() > 0)
|
||||||
|
{
|
||||||
|
IAsyncOperation<bool> launchOperation = appEntries.GetAt(0).LaunchAsync();
|
||||||
|
bool launchResult = launchOperation.get();
|
||||||
|
return launchResult;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::error(L"No app entries found for the package.");
|
||||||
|
launchErrors.push_back({ packageFullName, L"No app entries found for the package." });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const hresult_error& ex)
|
||||||
|
{
|
||||||
|
Logger::error(L"Packaged app launching error: {}", ex.message());
|
||||||
|
launchErrors.push_back({ packageFullName, ex.message().c_str() });
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Launch(const WorkspacesData::WorkspacesProject::Application& app, ErrorList& launchErrors)
|
||||||
|
{
|
||||||
|
bool launched{ false };
|
||||||
|
|
||||||
|
// packaged apps: check protocol in registry
|
||||||
|
// usage example: Settings with cmd args
|
||||||
|
if (!app.packageFullName.empty())
|
||||||
|
{
|
||||||
|
auto names = RegistryUtils::GetUriProtocolNames(app.packageFullName);
|
||||||
|
if (!names.empty())
|
||||||
|
{
|
||||||
|
Logger::trace(L"Launching packaged by protocol with command line args {}", app.name);
|
||||||
|
|
||||||
|
std::wstring uriProtocolName = names[0];
|
||||||
|
std::wstring command = std::wstring(uriProtocolName + (app.commandLineArgs.starts_with(L":") ? L"" : L":") + app.commandLineArgs);
|
||||||
|
|
||||||
|
launched = LaunchApp(command, L"", app.isElevated, launchErrors);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::info(L"Uri protocol names not found for {}", app.packageFullName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// packaged apps: try launching first by AppUserModel.ID
|
||||||
|
// usage example: elevated Terminal
|
||||||
|
if (!launched && !app.appUserModelId.empty() && !app.packageFullName.empty())
|
||||||
|
{
|
||||||
|
Logger::trace(L"Launching {} as {}", app.name, app.appUserModelId);
|
||||||
|
launched = LaunchApp(L"shell:AppsFolder\\" + app.appUserModelId, app.commandLineArgs, app.isElevated, launchErrors);
|
||||||
|
}
|
||||||
|
|
||||||
|
// packaged apps: try launching by package full name
|
||||||
|
// doesn't work for elevated apps or apps with command line args
|
||||||
|
if (!launched && !app.packageFullName.empty() && app.commandLineArgs.empty() && !app.isElevated)
|
||||||
|
{
|
||||||
|
Logger::trace(L"Launching packaged app {}", app.name);
|
||||||
|
launched = LaunchPackagedApp(app.packageFullName, launchErrors);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!launched)
|
||||||
|
{
|
||||||
|
Logger::trace(L"Launching {} at {}", app.name, app.path);
|
||||||
|
|
||||||
|
DWORD dwAttrib = GetFileAttributesW(app.path.c_str());
|
||||||
|
if (dwAttrib == INVALID_FILE_ATTRIBUTES)
|
||||||
|
{
|
||||||
|
Logger::error(L"File not found at {}", app.path);
|
||||||
|
launchErrors.push_back({ std::filesystem::path(app.path).filename(), L"File not found" });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
launched = LaunchApp(app.path, app.commandLineArgs, app.isElevated, launchErrors);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::trace(L"{} {} at {}", app.name, (launched ? L"launched" : L"not launched"), app.path);
|
||||||
|
return launched;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Launch(WorkspacesData::WorkspacesProject& project, const std::vector<WorkspacesData::WorkspacesProject::Monitor>& monitors, ErrorList& launchErrors)
|
||||||
|
{
|
||||||
|
bool launchedSuccessfully{ true };
|
||||||
|
|
||||||
|
LauncherUIHelper uiHelper;
|
||||||
|
uiHelper.LaunchUI();
|
||||||
|
|
||||||
|
// Get the set of windows before launching the app
|
||||||
|
std::vector<HWND> windowsBefore = WindowEnumerator::Enumerate(WindowFilter::Filter);
|
||||||
|
auto installedApps = Utils::Apps::GetAppsList();
|
||||||
|
auto launchedApps = Prepare(project.apps, installedApps);
|
||||||
|
|
||||||
|
uiHelper.UpdateLaunchStatus(launchedApps);
|
||||||
|
|
||||||
|
// Launch apps
|
||||||
|
for (auto& app : launchedApps)
|
||||||
|
{
|
||||||
|
if (!app.window)
|
||||||
|
{
|
||||||
|
if (!Launch(app.application, launchErrors))
|
||||||
|
{
|
||||||
|
Logger::error(L"Failed to launch {}", app.application.name);
|
||||||
|
app.state = L"failed";
|
||||||
|
uiHelper.UpdateLaunchStatus(launchedApps);
|
||||||
|
launchedSuccessfully = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get newly opened windows after launching apps, keep retrying for 5 seconds
|
||||||
|
Logger::trace(L"Find new windows");
|
||||||
|
for (int attempt = 0; attempt < 50 && !AllWindowsFound(launchedApps); attempt++)
|
||||||
|
{
|
||||||
|
std::vector<HWND> windowsAfter = WindowEnumerator::Enumerate(WindowFilter::Filter);
|
||||||
|
std::vector<HWND> windowsDiff{};
|
||||||
|
std::copy_if(windowsAfter.begin(), windowsAfter.end(), std::back_inserter(windowsDiff), [&](HWND window) { return std::find(windowsBefore.begin(), windowsBefore.end(), window) == windowsBefore.end(); });
|
||||||
|
if (AddOpenedWindows(launchedApps, windowsDiff, installedApps))
|
||||||
|
{
|
||||||
|
uiHelper.UpdateLaunchStatus(launchedApps);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if all windows were found
|
||||||
|
if (AllWindowsFound(launchedApps))
|
||||||
|
{
|
||||||
|
Logger::trace(L"All windows found.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::trace(L"Not all windows found, retry.");
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check single-instance app windows
|
||||||
|
Logger::trace(L"Find single-instance app windows");
|
||||||
|
if (!AllWindowsFound(launchedApps))
|
||||||
|
{
|
||||||
|
if (AddOpenedWindows(launchedApps, WindowEnumerator::Enumerate(WindowFilter::Filter), installedApps))
|
||||||
|
{
|
||||||
|
uiHelper.UpdateLaunchStatus(launchedApps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place windows
|
||||||
|
for (const auto& [app, window, status] : launchedApps)
|
||||||
|
{
|
||||||
|
if (window == nullptr)
|
||||||
|
{
|
||||||
|
Logger::warn(L"{} window not found.", app.name);
|
||||||
|
launchedSuccessfully = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto snapMonitorIter = std::find_if(project.monitors.begin(), project.monitors.end(), [&](const WorkspacesData::WorkspacesProject::Monitor& val) { return val.number == app.monitor; });
|
||||||
|
if (snapMonitorIter == project.monitors.end())
|
||||||
|
{
|
||||||
|
Logger::error(L"No monitor saved for launching the app");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool launchMinimized = app.isMinimized;
|
||||||
|
bool launchMaximized = app.isMaximized;
|
||||||
|
|
||||||
|
HMONITOR currentMonitor{};
|
||||||
|
UINT currentDpi = DPIAware::DEFAULT_DPI;
|
||||||
|
auto currentMonitorIter = std::find_if(monitors.begin(), monitors.end(), [&](const WorkspacesData::WorkspacesProject::Monitor& val) { return val.number == app.monitor; });
|
||||||
|
if (currentMonitorIter != monitors.end())
|
||||||
|
{
|
||||||
|
currentMonitor = currentMonitorIter->monitor;
|
||||||
|
currentDpi = currentMonitorIter->dpi;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentMonitor = MonitorFromPoint(POINT{ 0, 0 }, MONITOR_DEFAULTTOPRIMARY);
|
||||||
|
DPIAware::GetScreenDPIForMonitor(currentMonitor, currentDpi);
|
||||||
|
launchMinimized = true;
|
||||||
|
launchMaximized = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
RECT rect = app.position.toRect();
|
||||||
|
float mult = static_cast<float>(snapMonitorIter->dpi) / currentDpi;
|
||||||
|
rect.left = static_cast<long>(std::round(rect.left * mult));
|
||||||
|
rect.right = static_cast<long>(std::round(rect.right * mult));
|
||||||
|
rect.top = static_cast<long>(std::round(rect.top * mult));
|
||||||
|
rect.bottom = static_cast<long>(std::round(rect.bottom * mult));
|
||||||
|
|
||||||
|
if (FancyZones::SizeWindowToRect(window, currentMonitor, launchMinimized, launchMaximized, rect))
|
||||||
|
{
|
||||||
|
WorkspacesWindowProperties::StampWorkspacesLaunchedProperty(window);
|
||||||
|
Logger::trace(L"Placed {} to ({},{}) [{}x{}]", app.name, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::error(L"Failed placing {}", app.name);
|
||||||
|
launchedSuccessfully = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return launchedSuccessfully;
|
||||||
|
}
|
7
src/modules/Workspaces/WorkspacesLauncher/AppLauncher.h
Normal file
7
src/modules/Workspaces/WorkspacesLauncher/AppLauncher.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <WorkspacesLib/WorkspacesData.h>
|
||||||
|
|
||||||
|
using ErrorList = std::vector<std::pair<std::wstring, std::wstring>>;
|
||||||
|
|
||||||
|
bool Launch(WorkspacesData::WorkspacesProject& project, const std::vector<WorkspacesData::WorkspacesProject::Monitor>& monitors, ErrorList& launchErrors);
|
@ -0,0 +1,78 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "LauncherUIHelper.h"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <shellapi.h>
|
||||||
|
|
||||||
|
#include <common/utils/OnThreadExecutor.h>
|
||||||
|
#include <common/utils/winapi_error.h>
|
||||||
|
|
||||||
|
LauncherUIHelper::~LauncherUIHelper()
|
||||||
|
{
|
||||||
|
OnThreadExecutor().submit(OnThreadExecutor::task_t{ [&] {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||||
|
|
||||||
|
HANDLE uiProcess = OpenProcess(PROCESS_ALL_ACCESS, false, uiProcessId);
|
||||||
|
if (uiProcess)
|
||||||
|
{
|
||||||
|
bool res = TerminateProcess(uiProcess, 0);
|
||||||
|
if (!res)
|
||||||
|
{
|
||||||
|
Logger::error(L"Unable to terminate UI process: {}", get_last_error_or_default(GetLastError()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::error(L"Unable to find UI process: {}", get_last_error_or_default(GetLastError()));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::remove(WorkspacesData::LaunchWorkspacesFile());
|
||||||
|
} }).wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherUIHelper::LaunchUI()
|
||||||
|
{
|
||||||
|
Logger::trace(L"Starting WorkspacesLauncherUI");
|
||||||
|
|
||||||
|
STARTUPINFO info = { sizeof(info) };
|
||||||
|
PROCESS_INFORMATION pi = { 0 };
|
||||||
|
TCHAR buffer[MAX_PATH] = { 0 };
|
||||||
|
GetModuleFileName(NULL, buffer, MAX_PATH);
|
||||||
|
std::wstring path = std::filesystem::path(buffer).parent_path();
|
||||||
|
path.append(L"\\PowerToys.WorkspacesLauncherUI.exe");
|
||||||
|
auto succeeded = CreateProcessW(path.c_str(), nullptr, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &info, &pi);
|
||||||
|
if (succeeded)
|
||||||
|
{
|
||||||
|
if (pi.hProcess)
|
||||||
|
{
|
||||||
|
uiProcessId = pi.dwProcessId;
|
||||||
|
CloseHandle(pi.hProcess);
|
||||||
|
}
|
||||||
|
if (pi.hThread)
|
||||||
|
{
|
||||||
|
CloseHandle(pi.hThread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::error(L"CreateProcessW() failed. {}", get_last_error_or_default(GetLastError()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherUIHelper::UpdateLaunchStatus(LaunchingApps launchedApps)
|
||||||
|
{
|
||||||
|
WorkspacesData::AppLaunchData appData = WorkspacesData::AppLaunchData();
|
||||||
|
appData.appLaunchInfoList.reserve(launchedApps.size());
|
||||||
|
appData.launcherProcessID = GetCurrentProcessId();
|
||||||
|
for (auto& app : launchedApps)
|
||||||
|
{
|
||||||
|
WorkspacesData::AppLaunchInfo appLaunchInfo = WorkspacesData::AppLaunchInfo();
|
||||||
|
appLaunchInfo.name = app.application.name;
|
||||||
|
appLaunchInfo.path = app.application.path;
|
||||||
|
appLaunchInfo.state = app.state;
|
||||||
|
|
||||||
|
appData.appLaunchInfoList.push_back(appLaunchInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
json::to_file(WorkspacesData::LaunchWorkspacesFile(), WorkspacesData::AppLaunchDataJSON::ToJson(appData));
|
||||||
|
}
|
16
src/modules/Workspaces/WorkspacesLauncher/LauncherUIHelper.h
Normal file
16
src/modules/Workspaces/WorkspacesLauncher/LauncherUIHelper.h
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <LaunchingApp.h>
|
||||||
|
|
||||||
|
class LauncherUIHelper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LauncherUIHelper() = default;
|
||||||
|
~LauncherUIHelper();
|
||||||
|
|
||||||
|
void LaunchUI();
|
||||||
|
void UpdateLaunchStatus(LaunchingApps launchedApps);
|
||||||
|
|
||||||
|
private:
|
||||||
|
DWORD uiProcessId;
|
||||||
|
};
|
13
src/modules/Workspaces/WorkspacesLauncher/LaunchingApp.h
Normal file
13
src/modules/Workspaces/WorkspacesLauncher/LaunchingApp.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <WorkspacesLib/WorkspacesData.h>
|
||||||
|
|
||||||
|
struct LaunchingApp
|
||||||
|
{
|
||||||
|
WorkspacesData::WorkspacesProject::Application application;
|
||||||
|
HWND window;
|
||||||
|
std::wstring state;
|
||||||
|
};
|
||||||
|
|
||||||
|
using LaunchingApps = std::vector<LaunchingApp>;
|
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ImportGroup Label="PropertySheets" />
|
||||||
|
<PropertyGroup Label="UserMacros" />
|
||||||
|
<!--
|
||||||
|
To customize common C++/WinRT project properties:
|
||||||
|
* right-click the project node
|
||||||
|
* expand the Common Properties item
|
||||||
|
* select the C++/WinRT property page
|
||||||
|
|
||||||
|
For more advanced scenarios, and complete documentation, please see:
|
||||||
|
https://github.com/Microsoft/cppwinrt/tree/master/nuget
|
||||||
|
-->
|
||||||
|
<PropertyGroup />
|
||||||
|
<ItemDefinitionGroup />
|
||||||
|
</Project>
|
84
src/modules/Workspaces/WorkspacesLauncher/RegistryUtils.cpp
Normal file
84
src/modules/Workspaces/WorkspacesLauncher/RegistryUtils.cpp
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "RegistryUtils.h"
|
||||||
|
|
||||||
|
#include <strsafe.h>
|
||||||
|
|
||||||
|
#include <common/utils/winapi_error.h>
|
||||||
|
|
||||||
|
namespace RegistryUtils
|
||||||
|
{
|
||||||
|
namespace NonLocalizable
|
||||||
|
{
|
||||||
|
const wchar_t RegKeyPackageId[] = L"Extensions\\ContractId\\Windows.Protocol\\PackageId\\";
|
||||||
|
const wchar_t RegKeyPackageActivatableClassId[] = L"\\ActivatableClassId";
|
||||||
|
const wchar_t RegKeyPackageCustomProperties[] = L"\\CustomProperties";
|
||||||
|
const wchar_t RegValueName[] = L"Name";
|
||||||
|
}
|
||||||
|
|
||||||
|
HKEY OpenRootRegKey(const wchar_t* key)
|
||||||
|
{
|
||||||
|
HKEY hKey{ nullptr };
|
||||||
|
if (RegOpenKeyEx(HKEY_CLASSES_ROOT, key, 0, KEY_READ, &hKey) == ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
return hKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::wstring> GetUriProtocolNames(const std::wstring& packageFullPath)
|
||||||
|
{
|
||||||
|
std::vector<std::wstring> names{};
|
||||||
|
|
||||||
|
std::wstring keyPath = std::wstring(NonLocalizable::RegKeyPackageId) + packageFullPath + std::wstring(NonLocalizable::RegKeyPackageActivatableClassId);
|
||||||
|
HKEY key = OpenRootRegKey(keyPath.c_str());
|
||||||
|
if (key != nullptr)
|
||||||
|
{
|
||||||
|
LSTATUS result;
|
||||||
|
|
||||||
|
// iterate over all the subkeys to get the protocol names
|
||||||
|
DWORD index = 0;
|
||||||
|
wchar_t keyName[256];
|
||||||
|
DWORD keyNameSize = sizeof(keyName) / sizeof(keyName[0]);
|
||||||
|
FILETIME lastWriteTime;
|
||||||
|
|
||||||
|
while ((result = RegEnumKeyEx(key, index, keyName, &keyNameSize, NULL, NULL, NULL, &lastWriteTime)) != ERROR_NO_MORE_ITEMS)
|
||||||
|
{
|
||||||
|
if (result == ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
std::wstring subkeyPath = std::wstring(keyPath) + L"\\" + std::wstring(keyName, keyNameSize) + std::wstring(NonLocalizable::RegKeyPackageCustomProperties);
|
||||||
|
HKEY subkey = OpenRootRegKey(subkeyPath.c_str());
|
||||||
|
if (subkey != nullptr)
|
||||||
|
{
|
||||||
|
DWORD dataSize;
|
||||||
|
wchar_t value[256];
|
||||||
|
result = RegGetValueW(subkey, nullptr, NonLocalizable::RegValueName, RRF_RT_REG_SZ, nullptr, value, &dataSize);
|
||||||
|
if (result == ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
names.emplace_back(std::wstring(value, dataSize / sizeof(wchar_t) - 1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::error(L"Failed to query registry value. Error: {}", get_last_error_or_default(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
RegCloseKey(subkey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::error(L"Failed to enumerate subkey. Error: {}", get_last_error_or_default(result));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
keyNameSize = sizeof(keyName) / sizeof(keyName[0]); // Reset the buffer size
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the registry key
|
||||||
|
RegCloseKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace RegistryUtils
|
||||||
|
{
|
||||||
|
std::vector<std::wstring> GetUriProtocolNames(const std::wstring& packageFullPath);
|
||||||
|
};
|
139
src/modules/Workspaces/WorkspacesLauncher/Resource.resx
Normal file
139
src/modules/Workspaces/WorkspacesLauncher/Resource.resx
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="Empty_file" xml:space="preserve">
|
||||||
|
<value>File {0} is empty.</value>
|
||||||
|
</data>
|
||||||
|
<data name="File_reading_error" xml:space="preserve">
|
||||||
|
<value>Error reading file {0}.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Incorrect_args" xml:space="preserve">
|
||||||
|
<value>Incorrect command line arguments</value>
|
||||||
|
</data>
|
||||||
|
<data name="Incorrect_file_error" xml:space="preserve">
|
||||||
|
<value>Incorrect {0} file.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Workspaces" xml:space="preserve">
|
||||||
|
<value>Workspaces</value>
|
||||||
|
<comment>Name of the module</comment>
|
||||||
|
</data>
|
||||||
|
<data name="Project_not_found" xml:space="preserve">
|
||||||
|
<value>Workspace {0} not found.</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user