mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-07-02 08:28:55 +02:00
Compare commits
24 Commits
copilot/im
...
v0.98.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df23546c0b | ||
|
|
25f44bc6d9 | ||
|
|
dc533fbdb3 | ||
|
|
c05ba4e2c8 | ||
|
|
c83dd972a0 | ||
|
|
c33053b26b | ||
|
|
2cf7d0f5ec | ||
|
|
7cb0f3861a | ||
|
|
1106ac61f5 | ||
|
|
107bf3882c | ||
|
|
3f35b11cee | ||
|
|
1a9fcdcd1f | ||
|
|
6cf1d32e5a | ||
|
|
33497e59cc | ||
|
|
3d2f069c43 | ||
|
|
79d9b0e667 | ||
|
|
e2f611a7fc | ||
|
|
84ce86c573 | ||
|
|
735ea01a93 | ||
|
|
93f80f5f61 | ||
|
|
21f06b8bd0 | ||
|
|
fa78cc8ea7 | ||
|
|
cb9d54317a | ||
|
|
5d0eabed15 |
124
.github/actions/spell-check/allow/zoomit.txt
vendored
124
.github/actions/spell-check/allow/zoomit.txt
vendored
@@ -1,9 +1,23 @@
|
||||
accelscroll
|
||||
acq
|
||||
adr
|
||||
Adr
|
||||
APPLYTOSUBMENUS
|
||||
AUDCLNT
|
||||
axisdefer
|
||||
axisflip
|
||||
axisstart
|
||||
bitmaps
|
||||
BREAKSCR
|
||||
BUFFERFLAGS
|
||||
Cands
|
||||
capturepath
|
||||
centiseconds
|
||||
CLASSW
|
||||
coeffs
|
||||
coprime
|
||||
CREATEDIBSECTION
|
||||
crossfades
|
||||
Ctl
|
||||
CTLCOLOR
|
||||
CTLCOLORBTN
|
||||
@@ -11,53 +25,163 @@ CTLCOLORDLG
|
||||
CTLCOLOREDIT
|
||||
CTLCOLORLISTBOX
|
||||
CTrim
|
||||
ddy
|
||||
DFCS
|
||||
dlg
|
||||
dlu
|
||||
DONTCARE
|
||||
downsample
|
||||
DRAWITEM
|
||||
DRAWITEMSTRUCT
|
||||
droppedband
|
||||
Droppedband
|
||||
dsum
|
||||
dupburst
|
||||
dupsegments
|
||||
DWLP
|
||||
EDITCONTROL
|
||||
ENABLEHOOK
|
||||
expectedlock
|
||||
fastscroll
|
||||
FDE
|
||||
GETCHANNELRECT
|
||||
GETCHECK
|
||||
GETSCREENSAVEACTIVE
|
||||
GETSCREENSAVETIMEOUT
|
||||
GETTHUMBRECT
|
||||
GIFs
|
||||
hcfdark
|
||||
hcfwhitespace
|
||||
HTBOTTOMRIGHT
|
||||
HTHEME
|
||||
htol
|
||||
ICONINFORMATION
|
||||
ICONWARNING
|
||||
Inj
|
||||
jumprecover
|
||||
KSDATAFORMAT
|
||||
latestcapture
|
||||
ldx
|
||||
LEFTNOWORDWRAP
|
||||
legitjumps
|
||||
letterbox
|
||||
lld
|
||||
llu
|
||||
llums
|
||||
logfont
|
||||
lookback
|
||||
lround
|
||||
lte
|
||||
luma
|
||||
Luma
|
||||
manualdrop
|
||||
maskcache
|
||||
maxstep
|
||||
MENUINFO
|
||||
mic
|
||||
middledrop
|
||||
middledrop
|
||||
MMRESULT
|
||||
momentumreversal
|
||||
mrate
|
||||
mrt
|
||||
narrowstrip
|
||||
ncapture
|
||||
ncm
|
||||
nduplicates
|
||||
niterations
|
||||
nmonitor
|
||||
NONCLIENTMETRICS
|
||||
nonvle
|
||||
nredraw
|
||||
nstop
|
||||
nsubpixel
|
||||
ntorn
|
||||
nvw
|
||||
osc
|
||||
OWNERDRAW
|
||||
PBGRA
|
||||
periodictrap
|
||||
pfdc
|
||||
playhead
|
||||
pointerreuse
|
||||
pwfx
|
||||
Qpc
|
||||
quantums
|
||||
RCZOOMITSCR
|
||||
realcapture
|
||||
REFKNOWNFOLDERID
|
||||
reposted
|
||||
SCREENSAVE
|
||||
SCRNSAVE
|
||||
SCRNSAVECONFIGURE
|
||||
scrnsavw
|
||||
Scrnsavw
|
||||
scrollramp
|
||||
SCROLLSIZEGRIP
|
||||
selftest
|
||||
SETBARCOLOR
|
||||
SETBKCOLOR
|
||||
SETDEFID
|
||||
SETRECT
|
||||
SETSCREENSAVETIMEOUT
|
||||
SHAREMODE
|
||||
SHAREVIOLATION
|
||||
shortlist
|
||||
slowthenfast
|
||||
smallstart
|
||||
SNIPOCR
|
||||
ssi
|
||||
startuprecovery
|
||||
stf
|
||||
stopafter
|
||||
STREAMFLAGS
|
||||
submix
|
||||
sxx
|
||||
sxy
|
||||
syy
|
||||
tallportal
|
||||
tci
|
||||
tcsicmp
|
||||
TEXTMETRIC
|
||||
tinystep
|
||||
tme
|
||||
toolbars
|
||||
TRACKMOUSEEVENT
|
||||
Unadvise
|
||||
vaddq
|
||||
vaddvq
|
||||
vandq
|
||||
vcgeq
|
||||
vdup
|
||||
vld
|
||||
vle
|
||||
Vle
|
||||
VLE
|
||||
vminq
|
||||
vmlal
|
||||
vmull
|
||||
vqaddq
|
||||
vshrn
|
||||
vsntprintf
|
||||
vsnwprintf
|
||||
vsync
|
||||
WASAPI
|
||||
WAVEFORMATEX
|
||||
WAVEFORMATEXTENSIBLE
|
||||
wfopen
|
||||
wideportal
|
||||
wil
|
||||
WMU
|
||||
wrapjump
|
||||
wtol
|
||||
WTSSESSION
|
||||
WTSUn
|
||||
XEnd
|
||||
XStart
|
||||
XStep
|
||||
YInternal
|
||||
ZMBS
|
||||
zncc
|
||||
Zncc
|
||||
ZNCC
|
||||
|
||||
@@ -427,7 +427,7 @@
|
||||
</Project>
|
||||
</Folder>
|
||||
<Folder Name="/modules/FileLocksmith/">
|
||||
<Project Path="src/modules/FileLocksmith/FileLocksmithCLI/FileLocksmithCLI.vcxproj" Id="49D456D3-F485-45AF-8875-45B44F193DDC" />
|
||||
<Project Path="src/modules/FileLocksmith/FileLocksmithCLI/FileLocksmithCLI.vcxproj" Id="49d456d3-f485-45af-8875-45b44f193ddc" />
|
||||
<Project Path="src/modules/FileLocksmith/FileLocksmithContextMenu/FileLocksmithContextMenu.vcxproj" Id="799a50d8-de89-4ed1-8ff8-ad5a9ed8c0ca" />
|
||||
<Project Path="src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj" Id="57175ec7-92a5-4c1e-8244-e3fbca2a81de" />
|
||||
<Project Path="src/modules/FileLocksmith/FileLocksmithLib/FileLocksmithLib.vcxproj" Id="9d52fd25-ef90-4f9a-a015-91efc5daf54f" />
|
||||
@@ -438,7 +438,7 @@
|
||||
</Project>
|
||||
</Folder>
|
||||
<Folder Name="/modules/FileLocksmith/Tests/">
|
||||
<Project Path="src/modules/FileLocksmith/FileLocksmithCLI/tests/FileLocksmithCLIUnitTests.vcxproj" Id="A1B2C3D4-E5F6-7890-1234-567890ABCDEF" />
|
||||
<Project Path="src/modules/FileLocksmith/FileLocksmithCLI/tests/FileLocksmithCLIUnitTests.vcxproj" Id="a1b2c3d4-e5f6-7890-1234-567890abcdef" />
|
||||
</Folder>
|
||||
<Folder Name="/modules/Hosts/">
|
||||
<Project Path="src/modules/Hosts/Hosts/Hosts.csproj">
|
||||
@@ -464,13 +464,13 @@
|
||||
</Folder>
|
||||
<Folder Name="/modules/imageresizer/">
|
||||
<Project Path="src/modules/imageresizer/dll/ImageResizerExt.vcxproj" Id="0b43679e-edfa-4da0-ad30-f4628b308b1b" />
|
||||
<Project Path="src/modules/imageresizer/ImageResizerContextMenu/ImageResizerContextMenu.vcxproj" Id="93b72a06-c8bd-484f-a6f7-c9f280b150bf" />
|
||||
<Project Path="src/modules/imageresizer/ImageResizerLib/ImageResizerLib.vcxproj" Id="18b3db45-4ffe-4d01-97d6-5223feee1853" />
|
||||
<Project Path="src/modules/imageresizer/ui/ImageResizerUI.csproj">
|
||||
<Project Path="src/modules/imageresizer/ImageResizerCLI/ImageResizerCLI.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/modules/imageresizer/ImageResizerCLI/ImageResizerCLI.csproj">
|
||||
<Project Path="src/modules/imageresizer/ImageResizerContextMenu/ImageResizerContextMenu.vcxproj" Id="93b72a06-c8bd-484f-a6f7-c9f280b150bf" />
|
||||
<Project Path="src/modules/imageresizer/ImageResizerLib/ImageResizerLib.vcxproj" Id="18b3db45-4ffe-4d01-97d6-5223feee1853" />
|
||||
<Project Path="src/modules/imageresizer/ui/ImageResizerUI.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
@@ -1027,7 +1027,10 @@
|
||||
<File Path="src/modules/Workspaces/workspaces-common/WindowUtils.h" />
|
||||
</Folder>
|
||||
<Folder Name="/modules/ZoomIt/">
|
||||
<Project Path="src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj" Id="0a84f764-3a88-44cd-aa96-41bdbd48627b" />
|
||||
<Project Path="src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj" Id="0a84f764-3a88-44cd-aa96-41bdbd48627b">
|
||||
<BuildDependency Project="src/modules/ZoomIt/ZoomItBreak/ZoomItBreak.vcxproj" />
|
||||
</Project>
|
||||
<Project Path="src/modules/ZoomIt/ZoomItBreak/ZoomItBreak.vcxproj" Id="94ba3051-c8d7-454a-9d46-1a7c78e228a3" />
|
||||
<Project Path="src/modules/ZoomIt/ZoomItModuleInterface/ZoomItModuleInterface.vcxproj" Id="e4585179-2ac1-4d5f-a3ff-cfc5392f694c" />
|
||||
<Project Path="src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettingsInterop.vcxproj" Id="ca7d8106-30b9-4aec-9d05-b69b31b8c461" />
|
||||
</Folder>
|
||||
|
||||
18
README.md
18
README.md
@@ -53,17 +53,17 @@ Go to the <a href="https://aka.ms/installPowerToys">PowerToys GitHub releases</a
|
||||
<!-- items that need to be updated release to release -->
|
||||
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.99%22
|
||||
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.98%22
|
||||
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.0/PowerToysUserSetup-0.98.0-x64.exe
|
||||
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.0/PowerToysUserSetup-0.98.0-arm64.exe
|
||||
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.0/PowerToysSetup-0.98.0-x64.exe
|
||||
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.0/PowerToysSetup-0.98.0-arm64.exe
|
||||
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.1/PowerToysUserSetup-0.98.1-x64.exe
|
||||
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.1/PowerToysUserSetup-0.98.1-arm64.exe
|
||||
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.1/PowerToysSetup-0.98.1-x64.exe
|
||||
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.1/PowerToysSetup-0.98.1-arm64.exe
|
||||
|
||||
| Description | Filename |
|
||||
|----------------|----------|
|
||||
| Per user - x64 | [PowerToysUserSetup-0.98.0-x64.exe][ptUserX64] |
|
||||
| Per user - ARM64 | [PowerToysUserSetup-0.98.0-arm64.exe][ptUserArm64] |
|
||||
| Machine wide - x64 | [PowerToysSetup-0.98.0-x64.exe][ptMachineX64] |
|
||||
| Machine wide - ARM64 | [PowerToysSetup-0.98.0-arm64.exe][ptMachineArm64] |
|
||||
| Per user - x64 | [PowerToysUserSetup-0.98.1-x64.exe][ptUserX64] |
|
||||
| Per user - ARM64 | [PowerToysUserSetup-0.98.1-arm64.exe][ptUserArm64] |
|
||||
| Machine wide - x64 | [PowerToysSetup-0.98.1-x64.exe][ptMachineX64] |
|
||||
| Machine wide - ARM64 | [PowerToysSetup-0.98.1-arm64.exe][ptMachineArm64] |
|
||||
|
||||
</details>
|
||||
|
||||
@@ -106,7 +106,7 @@ There are <a href="https://learn.microsoft.com/windows/powertoys/install#communi
|
||||
|
||||
[](https://github.com/microsoft/PowerToys/releases)
|
||||
|
||||
To see what's new, check out the [release notes](https://github.com/microsoft/PowerToys/releases/tag/v0.98.0).
|
||||
To see what's new, check out the [release notes](https://github.com/microsoft/PowerToys/releases/tag/v0.98.1).
|
||||
|
||||
## 🛣️ Roadmap
|
||||
We are planning some nice new features and improvements for the next releases – PowerDisplay, Command Palette improvements and a brand-new Shortcut Guide experience! Stay tuned for [v0.99][github-next-release-work]!
|
||||
|
||||
@@ -300,10 +300,6 @@ namespace winrt::PowerToys::Interop::implementation
|
||||
{
|
||||
return CommonSharedConstants::OPEN_NEW_KEYBOARD_MANAGER_EVENT;
|
||||
}
|
||||
hstring Constants::ToggleKeyboardManagerActiveEvent()
|
||||
{
|
||||
return CommonSharedConstants::TOGGLE_KEYBOARD_MANAGER_ACTIVE_EVENT;
|
||||
}
|
||||
hstring Constants::KeyboardManagerEngineInstanceMutex()
|
||||
{
|
||||
return CommonSharedConstants::KEYBOARD_MANAGER_ENGINE_INSTANCE_MUTEX;
|
||||
|
||||
@@ -78,7 +78,6 @@ namespace winrt::PowerToys::Interop::implementation
|
||||
static hstring MWBToggleEasyMouseEvent();
|
||||
static hstring MWBReconnectEvent();
|
||||
static hstring OpenNewKeyboardManagerEvent();
|
||||
static hstring ToggleKeyboardManagerActiveEvent();
|
||||
static hstring KeyboardManagerEngineInstanceMutex();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -75,7 +75,6 @@ namespace PowerToys
|
||||
static String MWBToggleEasyMouseEvent();
|
||||
static String MWBReconnectEvent();
|
||||
static String OpenNewKeyboardManagerEvent();
|
||||
static String ToggleKeyboardManagerActiveEvent();
|
||||
static String KeyboardManagerEngineInstanceMutex();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +151,7 @@ namespace CommonSharedConstants
|
||||
const wchar_t ZOOMIT_BREAK_EVENT[] = L"Local\\PowerToysZoomIt-BreakEvent-17f2e63c-4c56-41dd-90a0-2d12f9f50c6b";
|
||||
const wchar_t ZOOMIT_LIVEZOOM_EVENT[] = L"Local\\PowerToysZoomIt-LiveZoomEvent-390bf0c7-616f-47dc-bafe-a2d228add20d";
|
||||
const wchar_t ZOOMIT_SNIP_EVENT[] = L"Local\\PowerToysZoomIt-SnipEvent-2fd9c211-436d-4f17-a902-2528aaae3e30";
|
||||
const wchar_t ZOOMIT_SNIPOCR_EVENT[] = L"Local\\PowerToysZoomIt-SnipOcrEvent-a7c3b1d2-9e4f-4a6b-8d5c-1f2e3a4b5c6d";
|
||||
const wchar_t ZOOMIT_RECORD_EVENT[] = L"Local\\PowerToysZoomIt-RecordEvent-74539344-eaad-4711-8e83-23946e424512";
|
||||
|
||||
// Path to the events used by PowerDisplay
|
||||
@@ -172,7 +173,6 @@ namespace CommonSharedConstants
|
||||
|
||||
// Path to events used by Keyboard Manager
|
||||
const wchar_t OPEN_NEW_KEYBOARD_MANAGER_EVENT[] = L"Local\\PowerToysOpenNewKeyboardManagerEvent-9c1d2e3f-4b5a-6c7d-8e9f-0a1b2c3d4e5f";
|
||||
const wchar_t TOGGLE_KEYBOARD_MANAGER_ACTIVE_EVENT[] = L"Local\\PowerToysToggleKeyboardManagerActiveEvent-7f3a1d5c-2e94-4ff4-8b6a-90fd2bc4d2a7";
|
||||
const wchar_t KEYBOARD_MANAGER_ENGINE_INSTANCE_MUTEX[] = L"Local\\PowerToys_KBMEngine_InstanceMutex";
|
||||
|
||||
// used from quick access window
|
||||
|
||||
18013
src/modules/ZoomIt/ZoomIt/PanoramaCapture.cpp
Normal file
18013
src/modules/ZoomIt/ZoomIt/PanoramaCapture.cpp
Normal file
File diff suppressed because it is too large
Load Diff
42
src/modules/ZoomIt/ZoomIt/PanoramaCapture.h
Normal file
42
src/modules/ZoomIt/ZoomIt/PanoramaCapture.h
Normal file
@@ -0,0 +1,42 @@
|
||||
//============================================================================
|
||||
//
|
||||
// PanoramaCapture.h
|
||||
//
|
||||
// Panorama (scrolling) screen capture and stitching.
|
||||
//
|
||||
// Copyright (C) Mark Russinovich
|
||||
// Sysinternals - www.sysinternals.com
|
||||
//
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
//============================================================================
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <vector>
|
||||
|
||||
// Globals shared with the main ZoomIt module.
|
||||
extern bool g_PanoramaCaptureActive;
|
||||
extern bool g_PanoramaStopRequested;
|
||||
extern bool g_PanoramaDebugMode;
|
||||
|
||||
// Run the panorama capture flow: select a region, capture frames while
|
||||
// scrolling, stitch them together, and copy the result to the clipboard.
|
||||
bool RunPanoramaCaptureToClipboard( HWND hWnd );
|
||||
|
||||
// Run the panorama capture flow and save the result to a file via a
|
||||
// Save As dialog instead of copying to the clipboard.
|
||||
bool RunPanoramaCaptureToFile( HWND hWnd );
|
||||
|
||||
// Run a synthetic, non-interactive self-test for panorama frame stitching.
|
||||
// Returns true when stitching output matches expected dimensions/content.
|
||||
#ifdef _DEBUG
|
||||
bool RunPanoramaStitchSelfTest();
|
||||
|
||||
// Re-stitch frames from a specific debug dump directory.
|
||||
bool RunPanoramaStitchDumpDirectory( const wchar_t* path );
|
||||
|
||||
// Re-stitch accepted panorama frames from the latest debug dump session and
|
||||
// save output into that same session directory.
|
||||
bool RunPanoramaStitchLatestDebugDump();
|
||||
#endif
|
||||
@@ -11,6 +11,23 @@
|
||||
#include "Utility.h"
|
||||
#include "WindowsVersions.h"
|
||||
|
||||
static void SelectRectangleDebugLog( const wchar_t* format, ... )
|
||||
{
|
||||
#if _DEBUG
|
||||
wchar_t message[1024]{};
|
||||
va_list args;
|
||||
#pragma warning( push )
|
||||
#pragma warning( disable : 26492 )
|
||||
va_start( args, format );
|
||||
#pragma warning( pop )
|
||||
vswprintf_s( message, format, args );
|
||||
va_end( args );
|
||||
OutputDebugStringW( message );
|
||||
#else
|
||||
UNREFERENCED_PARAMETER( format );
|
||||
#endif
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// SelectRectangle::Start
|
||||
@@ -18,6 +35,12 @@
|
||||
//----------------------------------------------------------------------------
|
||||
bool SelectRectangle::Start( HWND ownerWindow, bool fullMonitor )
|
||||
{
|
||||
m_stopping = false;
|
||||
SelectRectangleDebugLog( L"[SelectRectangle] Start owner=%p fullMonitor=%d minSize=%d alpha=%u\n",
|
||||
ownerWindow,
|
||||
fullMonitor ? 1 : 0,
|
||||
MinSize(),
|
||||
Alpha() );
|
||||
WNDCLASSW windowClass{};
|
||||
windowClass.lpfnWndProc = []( HWND window, UINT message, WPARAM wordParam, LPARAM longParam ) -> LRESULT
|
||||
{
|
||||
@@ -46,10 +69,16 @@ bool SelectRectangle::Start( HWND ownerWindow, bool fullMonitor )
|
||||
|
||||
m_cancel = false;
|
||||
auto rect = GetMonitorRectFromCursor();
|
||||
SelectRectangleDebugLog( L"[SelectRectangle] Monitor rect=(%ld,%ld)-(%ld,%ld)\n",
|
||||
rect.left,
|
||||
rect.top,
|
||||
rect.right,
|
||||
rect.bottom );
|
||||
m_window = wil::unique_hwnd( CreateWindowExW( WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TOPMOST, m_className, nullptr, WS_POPUP,
|
||||
rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, ownerWindow,
|
||||
nullptr, nullptr, this ) );
|
||||
THROW_LAST_ERROR_IF_NULL( m_window.get() );
|
||||
SelectRectangleDebugLog( L"[SelectRectangle] Window created hwnd=%p\n", m_window.get() );
|
||||
|
||||
if( fullMonitor )
|
||||
{
|
||||
@@ -58,7 +87,11 @@ bool SelectRectangle::Start( HWND ownerWindow, bool fullMonitor )
|
||||
}
|
||||
else
|
||||
{
|
||||
SetLayeredWindowAttributes( m_window.get(), 0, Alpha(), LWA_ALPHA );
|
||||
const BOOL layered = SetLayeredWindowAttributes( m_window.get(), 0, Alpha(), LWA_ALPHA );
|
||||
SelectRectangleDebugLog( L"[SelectRectangle] SetLayeredWindowAttributes(alpha=%u) success=%d err=%lu\n",
|
||||
Alpha(),
|
||||
layered ? 1 : 0,
|
||||
layered ? 0 : GetLastError() );
|
||||
}
|
||||
|
||||
ShowWindow( m_window.get(), SW_SHOW );
|
||||
@@ -69,6 +102,7 @@ bool SelectRectangle::Start( HWND ownerWindow, bool fullMonitor )
|
||||
GetClipCursor( &m_oldClipRect );
|
||||
ClipCursor( &rect );
|
||||
m_setClip = true;
|
||||
SelectRectangleDebugLog( L"[SelectRectangle] Cursor clipped to monitor bounds\n" );
|
||||
}
|
||||
|
||||
MSG message;
|
||||
@@ -78,13 +112,20 @@ bool SelectRectangle::Start( HWND ownerWindow, bool fullMonitor )
|
||||
DispatchMessageW( &message );
|
||||
if( m_cancel )
|
||||
{
|
||||
SelectRectangleDebugLog( L"[SelectRectangle] Start cancelled via Stop()\n" );
|
||||
return false;
|
||||
}
|
||||
if( m_selected )
|
||||
{
|
||||
SelectRectangleDebugLog( L"[SelectRectangle] Selection finalized rect=(%ld,%ld)-(%ld,%ld)\n",
|
||||
m_selectedRect.left,
|
||||
m_selectedRect.top,
|
||||
m_selectedRect.right,
|
||||
m_selectedRect.bottom );
|
||||
break;
|
||||
}
|
||||
}
|
||||
SelectRectangleDebugLog( L"[SelectRectangle] Start complete selected=%d cancel=%d\n", m_selected ? 1 : 0, m_cancel ? 1 : 0 );
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -95,15 +136,38 @@ bool SelectRectangle::Start( HWND ownerWindow, bool fullMonitor )
|
||||
//----------------------------------------------------------------------------
|
||||
void SelectRectangle::Stop()
|
||||
{
|
||||
if( m_stopping )
|
||||
{
|
||||
SelectRectangleDebugLog( L"[SelectRectangle] Stop ignored due to reentrancy\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
m_stopping = true;
|
||||
SelectRectangleDebugLog( L"[SelectRectangle] Stop hwnd=%p selected=%d cancel=%d clip=%d rect=(%ld,%ld)-(%ld,%ld)\n",
|
||||
m_window.get(),
|
||||
m_selected ? 1 : 0,
|
||||
m_cancel ? 1 : 0,
|
||||
m_setClip ? 1 : 0,
|
||||
m_selectedRect.left,
|
||||
m_selectedRect.top,
|
||||
m_selectedRect.right,
|
||||
m_selectedRect.bottom );
|
||||
if( m_setClip )
|
||||
{
|
||||
ClipCursor( &m_oldClipRect );
|
||||
m_setClip = false;
|
||||
}
|
||||
m_window.reset();
|
||||
|
||||
HWND window = m_window.release();
|
||||
if( window != nullptr && IsWindow( window ) )
|
||||
{
|
||||
DestroyWindow( window );
|
||||
}
|
||||
|
||||
m_selected = false;
|
||||
m_selectedRect = {};
|
||||
m_cancel = true;
|
||||
m_stopping = false;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -114,11 +178,20 @@ void SelectRectangle::Stop()
|
||||
void SelectRectangle::ShowSelected()
|
||||
{
|
||||
m_selected = true;
|
||||
SelectRectangleDebugLog( L"[SelectRectangle] ShowSelected rect=(%ld,%ld)-(%ld,%ld) dpi=%u\n",
|
||||
m_selectedRect.left,
|
||||
m_selectedRect.top,
|
||||
m_selectedRect.right,
|
||||
m_selectedRect.bottom,
|
||||
m_dpi );
|
||||
|
||||
// Set the alpha to match the Windows graphics capture API yellow border
|
||||
// and set the window to be transparent and disabled, so it will be skipped
|
||||
// for hit testing and as a candidate for the next foreground window.
|
||||
SetLayeredWindowAttributes( m_window.get(), 0, 191, LWA_ALPHA );
|
||||
const BOOL layered = SetLayeredWindowAttributes( m_window.get(), 0, 191, LWA_ALPHA );
|
||||
SelectRectangleDebugLog( L"[SelectRectangle] ShowSelected SetLayeredWindowAttributes(alpha=191) success=%d err=%lu\n",
|
||||
layered ? 1 : 0,
|
||||
layered ? 0 : GetLastError() );
|
||||
SetWindowLong( m_window.get(), GWL_EXSTYLE, GetWindowLong( m_window.get(), GWL_EXSTYLE ) | WS_EX_TRANSPARENT );
|
||||
EnableWindow( m_window.get(), FALSE );
|
||||
|
||||
@@ -144,6 +217,12 @@ void SelectRectangle::ShowSelected()
|
||||
point.x += windowRect.left;
|
||||
point.y += windowRect.top;
|
||||
MoveWindow( m_window.get(), point.x, point.y, rect.right, rect.bottom, true );
|
||||
SelectRectangleDebugLog( L"[SelectRectangle] Border window moved to (%ld,%ld) size=%ldx%ld borderWidth=%d\n",
|
||||
point.x,
|
||||
point.y,
|
||||
rect.right,
|
||||
rect.bottom,
|
||||
width );
|
||||
|
||||
// Use a region to keep everything but the border transparent.
|
||||
wil::unique_hrgn region{CreateRectRgnIndirect( &rect )};
|
||||
@@ -151,6 +230,11 @@ void SelectRectangle::ShowSelected()
|
||||
wil::unique_hrgn insideRegion{CreateRectRgnIndirect( &rect )};
|
||||
CombineRgn( region.get(), region.get(), insideRegion.get(), RGN_XOR );
|
||||
SetWindowRgn( m_window.get(), region.release(), true );
|
||||
SelectRectangleDebugLog( L"[SelectRectangle] Border window region applied\n" );
|
||||
|
||||
// Force immediate paint so the yellow border is visible instead of a
|
||||
// transient black frame from the class background brush.
|
||||
RedrawWindow( m_window.get(), nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW | RDW_FRAME );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -162,6 +246,7 @@ void SelectRectangle::UpdateOwner( HWND window )
|
||||
{
|
||||
if( m_window != nullptr )
|
||||
{
|
||||
SelectRectangleDebugLog( L"[SelectRectangle] UpdateOwner hwnd=%p newOwner=%p\n", m_window.get(), window );
|
||||
SetWindowLongPtr( m_window.get(), GWLP_HWNDPARENT, reinterpret_cast<LONG_PTR>(window) );
|
||||
SetWindowPos( m_window.get(), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE );
|
||||
}
|
||||
@@ -179,10 +264,24 @@ LRESULT SelectRectangle::WindowProc( HWND window, UINT message, WPARAM wordParam
|
||||
case WM_CREATE:
|
||||
m_dpi = GetDpiForWindowHelper( window );
|
||||
SetWindowDisplayAffinity( window, WDA_EXCLUDEFROMCAPTURE );
|
||||
SelectRectangleDebugLog( L"[SelectRectangle] WM_CREATE hwnd=%p dpi=%u\n", window, m_dpi );
|
||||
return 0;
|
||||
|
||||
case WM_DESTROY:
|
||||
Stop();
|
||||
SelectRectangleDebugLog( L"[SelectRectangle] WM_DESTROY hwnd=%p\n", window );
|
||||
if( m_window.get() == window )
|
||||
{
|
||||
m_window.release();
|
||||
}
|
||||
if( m_setClip )
|
||||
{
|
||||
ClipCursor( &m_oldClipRect );
|
||||
m_setClip = false;
|
||||
}
|
||||
m_selected = false;
|
||||
m_selectedRect = {};
|
||||
m_cancel = true;
|
||||
m_stopping = false;
|
||||
return 0;
|
||||
|
||||
case WM_LBUTTONDOWN:
|
||||
@@ -190,6 +289,7 @@ LRESULT SelectRectangle::WindowProc( HWND window, UINT message, WPARAM wordParam
|
||||
SetCapture( window );
|
||||
|
||||
m_startPoint = { GET_X_LPARAM( longParam ), GET_Y_LPARAM( longParam ) };
|
||||
SelectRectangleDebugLog( L"[SelectRectangle] WM_LBUTTONDOWN startPoint=(%ld,%ld)\n", m_startPoint.x, m_startPoint.y );
|
||||
[[fallthrough]];
|
||||
}
|
||||
case WM_MOUSEMOVE:
|
||||
@@ -199,6 +299,11 @@ LRESULT SelectRectangle::WindowProc( HWND window, UINT message, WPARAM wordParam
|
||||
GetClientRect( window, &rect );
|
||||
POINT point{ GET_X_LPARAM( longParam ), GET_Y_LPARAM( longParam ) };
|
||||
m_selectedRect = ForceRectInBounds( RectFromPointsMinSize( m_startPoint, point, MinSize() ), rect );
|
||||
SelectRectangleDebugLog( L"[SelectRectangle] Drag rect=(%ld,%ld)-(%ld,%ld)\n",
|
||||
m_selectedRect.left,
|
||||
m_selectedRect.top,
|
||||
m_selectedRect.right,
|
||||
m_selectedRect.bottom );
|
||||
|
||||
// Use a region to carve out the selected rectangle.
|
||||
wil::unique_hrgn region{CreateRectRgnIndirect( &m_selectedRect )};
|
||||
@@ -211,6 +316,7 @@ LRESULT SelectRectangle::WindowProc( HWND window, UINT message, WPARAM wordParam
|
||||
case WM_KEYDOWN:
|
||||
if( wordParam == VK_ESCAPE )
|
||||
{
|
||||
SelectRectangleDebugLog( L"[SelectRectangle] WM_KEYDOWN Escape pressed\n" );
|
||||
Stop();
|
||||
}
|
||||
return 0;
|
||||
@@ -218,12 +324,18 @@ LRESULT SelectRectangle::WindowProc( HWND window, UINT message, WPARAM wordParam
|
||||
case WM_KILLFOCUS:
|
||||
if( !m_selected )
|
||||
{
|
||||
SelectRectangleDebugLog( L"[SelectRectangle] WM_KILLFOCUS before selection complete\n" );
|
||||
Stop();
|
||||
}
|
||||
return 0;
|
||||
|
||||
case WM_LBUTTONUP:
|
||||
{
|
||||
SelectRectangleDebugLog( L"[SelectRectangle] WM_LBUTTONUP selectedRect=(%ld,%ld)-(%ld,%ld)\n",
|
||||
m_selectedRect.left,
|
||||
m_selectedRect.top,
|
||||
m_selectedRect.right,
|
||||
m_selectedRect.bottom );
|
||||
if( m_setClip )
|
||||
{
|
||||
ClipCursor( &m_oldClipRect );
|
||||
@@ -249,6 +361,11 @@ LRESULT SelectRectangle::WindowProc( HWND window, UINT message, WPARAM wordParam
|
||||
|
||||
RECT rect;
|
||||
GetClientRect( window, &rect );
|
||||
SelectRectangleDebugLog( L"[SelectRectangle] WM_PAINT selected border rect=(%ld,%ld)-(%ld,%ld)\n",
|
||||
rect.left,
|
||||
rect.top,
|
||||
rect.right,
|
||||
rect.bottom );
|
||||
|
||||
// Draw a border matching the Windows graphics capture API border.
|
||||
// The outer frame is yellow and two logical pixels wide, while the
|
||||
|
||||
@@ -20,10 +20,14 @@ public:
|
||||
void MinSize( int minSize ) { m_minSize = minSize; }
|
||||
int MinSize() const { return m_minSize; }
|
||||
RECT SelectedRect() const { return m_selectedRect; }
|
||||
bool IsActive() const { return m_window != nullptr; }
|
||||
|
||||
bool Start( HWND ownerWindow = nullptr, bool fullMonitor = false );
|
||||
void Stop();
|
||||
void UpdateOwner( HWND window );
|
||||
void Hide() { if( m_window ) ShowWindow( m_window.get(), SW_HIDE ); }
|
||||
void Show() { if( m_window ) ShowWindow( m_window.get(), SW_SHOWNA ); }
|
||||
void SetExcludeFromCapture( bool exclude ) { if( m_window ) SetWindowDisplayAffinity( m_window.get(), exclude ? WDA_EXCLUDEFROMCAPTURE : WDA_NONE ); }
|
||||
|
||||
private:
|
||||
BYTE m_alpha = 176;
|
||||
@@ -36,6 +40,7 @@ private:
|
||||
RECT m_oldClipRect{};
|
||||
bool m_selected{ false };
|
||||
bool m_setClip{ false };
|
||||
bool m_stopping{ false };
|
||||
POINT m_startPoint{};
|
||||
wil::unique_hwnd m_window;
|
||||
|
||||
|
||||
@@ -406,7 +406,10 @@ static bool LoadGifFrames(const std::wstring& gifPath, VideoRecordingSession::Tr
|
||||
|
||||
const auto& lastFrame = pData->gifFrames.back();
|
||||
pData->videoDuration = winrt::TimeSpan{ lastFrame.start.count() + lastFrame.duration.count() };
|
||||
pData->trimEnd = pData->videoDuration;
|
||||
if( pData->trimEnd.count() <= 0 )
|
||||
{
|
||||
pData->trimEnd = pData->videoDuration;
|
||||
}
|
||||
pData->gifFramesLoaded = true;
|
||||
pData->gifLastFrameIndex = 0;
|
||||
|
||||
@@ -721,13 +724,9 @@ namespace
|
||||
SetDlgItemText(hDlg, IDC_TRIM_DURATION_LABEL, durationText.c_str());
|
||||
}
|
||||
|
||||
// Enable OK when trimming is active (even if unchanged since dialog opened),
|
||||
// or when the user changed the selection (including reverting to full length).
|
||||
const bool trimChanged = (pData->trimStart.count() != pData->originalTrimStart.count()) ||
|
||||
(pData->trimEnd.count() != pData->originalTrimEnd.count());
|
||||
const bool trimIsActive = (pData->trimStart.count() > 0) ||
|
||||
(pData->videoDuration.count() > 0 && pData->trimEnd.count() < pData->videoDuration.count());
|
||||
EnableWindow(GetDlgItem(hDlg, IDOK), trimChanged || trimIsActive);
|
||||
// Always enable OK so users can close the dialog after previewing
|
||||
// without being forced to use Cancel.
|
||||
EnableWindow(GetDlgItem(hDlg, IDOK), TRUE);
|
||||
}
|
||||
|
||||
RECT GetTimelineTrackRect(const RECT& clientRect, UINT dpi)
|
||||
@@ -1345,7 +1344,10 @@ public:
|
||||
auto trimResult = VideoRecordingSession::ShowTrimDialog(hParent, m_videoPath, *m_pTrimStart, *m_pTrimEnd);
|
||||
if (trimResult == IDOK)
|
||||
{
|
||||
*m_pShouldTrim = true;
|
||||
// Trim values are only written back when the user actually
|
||||
// changed the selection, so a non-zero trimEnd means a
|
||||
// real trim is requested.
|
||||
*m_pShouldTrim = (m_pTrimEnd->count() > 0);
|
||||
}
|
||||
else if( trimResult == IDCANCEL )
|
||||
{
|
||||
@@ -1502,12 +1504,13 @@ INT_PTR VideoRecordingSession::ShowTrimDialog(
|
||||
HWND hParent,
|
||||
const std::wstring& videoPath,
|
||||
winrt::TimeSpan& trimStart,
|
||||
winrt::TimeSpan& trimEnd)
|
||||
winrt::TimeSpan& trimEnd,
|
||||
bool standaloneMode)
|
||||
{
|
||||
std::promise<INT_PTR> resultPromise;
|
||||
auto resultFuture = resultPromise.get_future();
|
||||
|
||||
std::thread staThread([hParent, videoPath, &trimStart, &trimEnd, promise = std::move(resultPromise)]() mutable
|
||||
std::thread staThread([hParent, videoPath, &trimStart, &trimEnd, standaloneMode, promise = std::move(resultPromise)]() mutable
|
||||
{
|
||||
bool coInitialized = false;
|
||||
try
|
||||
@@ -1525,7 +1528,7 @@ INT_PTR VideoRecordingSession::ShowTrimDialog(
|
||||
|
||||
try
|
||||
{
|
||||
INT_PTR dlgResult = ShowTrimDialogInternal(hParent, videoPath, trimStart, trimEnd);
|
||||
INT_PTR dlgResult = ShowTrimDialogInternal(hParent, videoPath, trimStart, trimEnd, standaloneMode);
|
||||
promise.set_value(dlgResult);
|
||||
}
|
||||
catch (const winrt::hresult_error& e)
|
||||
@@ -1584,7 +1587,8 @@ INT_PTR VideoRecordingSession::ShowTrimDialogInternal(
|
||||
HWND hParent,
|
||||
const std::wstring& videoPath,
|
||||
winrt::TimeSpan& trimStart,
|
||||
winrt::TimeSpan& trimEnd)
|
||||
winrt::TimeSpan& trimEnd,
|
||||
bool standaloneMode)
|
||||
{
|
||||
TrimDialogData data;
|
||||
data.videoPath = videoPath;
|
||||
@@ -1592,6 +1596,7 @@ INT_PTR VideoRecordingSession::ShowTrimDialogInternal(
|
||||
data.trimStart = trimStart;
|
||||
data.trimEnd = trimEnd;
|
||||
data.isGif = IsGifPath(videoPath);
|
||||
data.standaloneMode = standaloneMode;
|
||||
|
||||
if (data.isGif)
|
||||
{
|
||||
@@ -1786,8 +1791,17 @@ INT_PTR VideoRecordingSession::ShowTrimDialogInternal(
|
||||
|
||||
if (result == IDOK)
|
||||
{
|
||||
trimStart = data.trimStart;
|
||||
trimEnd = data.trimEnd;
|
||||
// Only write back trim values when the user actually changed the
|
||||
// selection. This lets the caller distinguish "confirmed without
|
||||
// trimming" (preview-only) from a real trim operation.
|
||||
const bool selectionChanged =
|
||||
(data.trimStart.count() != data.originalTrimStart.count()) ||
|
||||
(data.trimEnd.count() != data.originalTrimEnd.count());
|
||||
if (selectionChanged)
|
||||
{
|
||||
trimStart = data.trimStart;
|
||||
trimEnd = data.trimEnd;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -3890,6 +3904,12 @@ INT_PTR CALLBACK VideoRecordingSession::TrimDialogProc(HWND hDlg, UINT message,
|
||||
// Make OK the default button
|
||||
SendMessage(hDlg, DM_SETDEFID, IDOK, 0);
|
||||
|
||||
// In standalone mode, change OK button text to "Save As"
|
||||
if (pData->standaloneMode)
|
||||
{
|
||||
SetDlgItemText(hDlg, IDOK, L"Save As");
|
||||
}
|
||||
|
||||
// Subclass the dialog to handle resize grip hit testing
|
||||
SetWindowSubclass(hDlg, TrimDialogSubclassProc, 0, reinterpret_cast<DWORD_PTR>(pData));
|
||||
|
||||
@@ -5029,6 +5049,115 @@ INT_PTR CALLBACK VideoRecordingSession::TrimDialogProc(HWND hDlg, UINT message,
|
||||
case IDOK:
|
||||
pData = reinterpret_cast<TrimDialogData*>(GetWindowLongPtr(hDlg, DWLP_USER));
|
||||
StopPlayback(hDlg, pData);
|
||||
|
||||
if (pData->standaloneMode)
|
||||
{
|
||||
// In standalone mode, "Save As" shows a save dialog and performs the trim
|
||||
auto saveDialog = wil::CoCreateInstance<::IFileSaveDialog>(CLSID_FileSaveDialog);
|
||||
|
||||
FILEOPENDIALOGOPTIONS options;
|
||||
if (SUCCEEDED(saveDialog->GetOptions(&options)))
|
||||
saveDialog->SetOptions(options | FOS_FORCEFILESYSTEM);
|
||||
|
||||
wil::com_ptr<::IShellItem> videosItem;
|
||||
if (SUCCEEDED(SHGetKnownFolderItem(FOLDERID_Videos, KF_FLAG_DEFAULT, nullptr,
|
||||
IID_IShellItem, (void**)videosItem.put())))
|
||||
saveDialog->SetDefaultFolder(videosItem.get());
|
||||
|
||||
// Derive suggested filename from source
|
||||
std::wstring suggestedName;
|
||||
{
|
||||
auto pos = pData->videoPath.find_last_of(L"\\/");
|
||||
suggestedName = (pos != std::wstring::npos) ? pData->videoPath.substr(pos + 1) : pData->videoPath;
|
||||
auto dot = suggestedName.find_last_of(L'.');
|
||||
if (dot != std::wstring::npos)
|
||||
suggestedName.insert(dot, L"_trimmed");
|
||||
else
|
||||
suggestedName += L"_trimmed";
|
||||
}
|
||||
|
||||
if (pData->isGif)
|
||||
{
|
||||
saveDialog->SetDefaultExtension(L".gif");
|
||||
COMDLG_FILTERSPEC fileTypes[] = { { L"GIF Animation", L"*.gif" } };
|
||||
saveDialog->SetFileTypes(_countof(fileTypes), fileTypes);
|
||||
}
|
||||
else
|
||||
{
|
||||
saveDialog->SetDefaultExtension(L".mp4");
|
||||
COMDLG_FILTERSPEC fileTypes[] = { { L"MP4 Video", L"*.mp4" } };
|
||||
saveDialog->SetFileTypes(_countof(fileTypes), fileTypes);
|
||||
}
|
||||
saveDialog->SetFileName(suggestedName.c_str());
|
||||
saveDialog->SetTitle(L"ZoomIt: Save Trimmed Video As...");
|
||||
|
||||
HRESULT hr = saveDialog->Show(hDlg);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
// User cancelled save dialog — return to trim editor
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
wil::com_ptr<::IShellItem> resultItem;
|
||||
THROW_IF_FAILED(saveDialog->GetResult(resultItem.put()));
|
||||
wil::unique_cotaskmem_string savePath;
|
||||
THROW_IF_FAILED(resultItem->GetDisplayName(SIGDN_FILESYSPATH, savePath.put()));
|
||||
|
||||
// Capture what we need before closing the dialog
|
||||
std::wstring videoPath = pData->videoPath;
|
||||
bool isGif = pData->isGif;
|
||||
auto trimStart = pData->trimStart;
|
||||
auto trimEnd = pData->trimEnd;
|
||||
std::wstring savePathStr(savePath.get());
|
||||
|
||||
// Close the trim dialog immediately
|
||||
EndDialog(hDlg, IDOK);
|
||||
|
||||
// Perform the trim after the dialog is closed
|
||||
try
|
||||
{
|
||||
auto trimOp = isGif
|
||||
? TrimGifAsync(videoPath, trimStart, trimEnd)
|
||||
: TrimVideoAsync(videoPath, trimStart, trimEnd);
|
||||
|
||||
// Pump messages while waiting for async operation
|
||||
while (trimOp.Status() == winrt::AsyncStatus::Started)
|
||||
{
|
||||
MSG msg;
|
||||
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
|
||||
{
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
Sleep(10);
|
||||
}
|
||||
|
||||
auto trimmedPath = std::wstring(trimOp.GetResults());
|
||||
if (trimmedPath.empty())
|
||||
{
|
||||
MessageBox(nullptr, L"Failed to trim video.", L"Error", MB_OK | MB_ICONERROR);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Copy trimmed file to the user-chosen save location
|
||||
if (!CopyFile(trimmedPath.c_str(), savePathStr.c_str(), FALSE))
|
||||
{
|
||||
MessageBox(nullptr, L"Failed to save the trimmed file.", L"Error", MB_OK | MB_ICONERROR);
|
||||
DeleteFile(trimmedPath.c_str());
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Clean up temp file
|
||||
DeleteFile(trimmedPath.c_str());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
MessageBox(nullptr, L"Failed to trim video.", L"Error", MB_OK | MB_ICONERROR);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Trim times are already set by mouse dragging
|
||||
EndDialog(hDlg, IDOK);
|
||||
return TRUE;
|
||||
|
||||
@@ -132,6 +132,7 @@ public:
|
||||
bool isDragging{ false };
|
||||
int lastPlayheadX{ -1 }; // Track last playhead pixel position for efficient invalidation
|
||||
MMRESULT mmTimerId{ 0 }; // Multimedia timer for smooth MP4 playback
|
||||
bool standaloneMode{ false }; // When true, OK becomes "Save As" and handles file saving directly
|
||||
|
||||
// Helper to convert time to pixel position
|
||||
int TimeToPixel(winrt::Windows::Foundation::TimeSpan time, int timelineWidth) const
|
||||
@@ -162,7 +163,8 @@ public:
|
||||
HWND hParent,
|
||||
const std::wstring& videoPath,
|
||||
winrt::Windows::Foundation::TimeSpan& trimStart,
|
||||
winrt::Windows::Foundation::TimeSpan& trimEnd);
|
||||
winrt::Windows::Foundation::TimeSpan& trimEnd,
|
||||
bool standaloneMode = false);
|
||||
|
||||
private:
|
||||
static INT_PTR CALLBACK TrimDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
|
||||
@@ -179,7 +181,8 @@ private:
|
||||
HWND hParent,
|
||||
const std::wstring& videoPath,
|
||||
winrt::Windows::Foundation::TimeSpan& trimStart,
|
||||
winrt::Windows::Foundation::TimeSpan& trimEnd);
|
||||
winrt::Windows::Foundation::TimeSpan& trimEnd,
|
||||
bool standaloneMode = false);
|
||||
|
||||
private:
|
||||
VideoRecordingSession(
|
||||
|
||||
@@ -113,26 +113,26 @@ END
|
||||
// Dialog
|
||||
//
|
||||
|
||||
OPTIONS DIALOGEX 0, 0, 299, 325
|
||||
OPTIONS DIALOGEX 0, 0, 299, 331
|
||||
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CLIPSIBLINGS | WS_CAPTION | WS_SYSMENU
|
||||
EXSTYLE WS_EX_CONTROLPARENT
|
||||
CAPTION "ZoomIt - Sysinternals: www.sysinternals.com"
|
||||
FONT 8, "MS Shell Dlg", 0, 0, 0x0
|
||||
BEGIN
|
||||
DEFPUSHBUTTON "OK",IDOK,186,306,50,14
|
||||
PUSHBUTTON "Cancel",IDCANCEL,243,306,50,14
|
||||
LTEXT "ZoomIt v10.1",IDC_VERSION,42,7,73,10
|
||||
DEFPUSHBUTTON "OK",IDOK,184,308,50,14
|
||||
PUSHBUTTON "Cancel",IDCANCEL,241,308,50,14
|
||||
LTEXT "ZoomIt v11.0",IDC_VERSION,42,7,73,10
|
||||
LTEXT "Copyright \251 2006-2026 Mark Russinovich",IDC_COPYRIGHT,42,17,251,8
|
||||
CONTROL "<a HREF=""https://www.sysinternals.com"">Sysinternals - www.sysinternals.com</a>",IDC_LINK,
|
||||
"SysLink",WS_TABSTOP,42,26,150,9
|
||||
ICON "APPICON",IDC_STATIC,12,9,20,20
|
||||
CONTROL "Show tray icon",IDC_SHOW_TRAY_ICON,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,295,105,10
|
||||
CONTROL "",IDC_TAB,"SysTabControl32",TCS_MULTILINE | WS_TABSTOP,8,46,285,247
|
||||
CONTROL "Run ZoomIt when Windows starts",IDC_AUTOSTART,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,309,122,10
|
||||
CONTROL "Show tray icon",IDC_SHOW_TRAY_ICON,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,302,105,10
|
||||
CONTROL "",IDC_TAB,"SysTabControl32",TCS_MULTILINE | WS_TABSTOP,8,45,285,255
|
||||
CONTROL "Run ZoomIt when Windows starts",IDC_AUTOSTART,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,316,122,10
|
||||
END
|
||||
|
||||
ADVANCED_BREAK DIALOGEX 0, 0, 209, 225
|
||||
STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
|
||||
ADVANCED_BREAK DIALOGEX 0, 0, 209, 223
|
||||
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
|
||||
CAPTION "Advanced Break Options"
|
||||
FONT 8, "MS Shell Dlg", 400, 0, 0x1
|
||||
BEGIN
|
||||
@@ -158,8 +158,8 @@ BEGIN
|
||||
EDITTEXT IDC_BACKGROUND_FILE,62,164,125,12,ES_AUTOHSCROLL | ES_READONLY
|
||||
PUSHBUTTON "&...",IDC_BACKGROUND_BROWSE,188,164,13,11
|
||||
CONTROL "Scale to screen:",IDC_CHECK_BACKGROUND_STRETCH,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,58,180,67,10,WS_EX_RIGHT
|
||||
DEFPUSHBUTTON "OK",IDOK,97,199,50,14
|
||||
PUSHBUTTON "Cancel",IDCANCEL,150,199,50,14
|
||||
DEFPUSHBUTTON "OK",IDOK,97,202,50,14
|
||||
PUSHBUTTON "Cancel",IDCANCEL,150,202,50,14
|
||||
LTEXT "Alarm Sound File:",IDC_STATIC_SOUND_FILE,61,26,56,8
|
||||
LTEXT "Timer Opacity:",IDC_STATIC,8,59,48,8
|
||||
LTEXT "Timer Position:",IDC_STATIC,8,77,48,8
|
||||
@@ -215,21 +215,23 @@ BEGIN
|
||||
GROUPBOX "Sample",IDC_TEXT_FONT,8,61,99,28
|
||||
END
|
||||
|
||||
BREAK DIALOGEX 0, 0, 260, 123
|
||||
STYLE DS_SETFONT | DS_CONTROL | WS_CHILD | WS_SYSMENU
|
||||
BREAK DIALOGEX 0, 0, 260, 159
|
||||
STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU
|
||||
FONT 8, "MS Shell Dlg", 400, 0, 0x1
|
||||
BEGIN
|
||||
CONTROL "",IDC_BREAK_HOTKEY,"msctls_hotkey32",WS_BORDER | WS_TABSTOP,52,67,80,12
|
||||
EDITTEXT IDC_TIMER,52,86,31,13,ES_RIGHT | ES_AUTOHSCROLL | ES_NUMBER
|
||||
CONTROL "",IDC_SPIN_TIMER,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS | UDS_NOTHOUSANDS,66,86,11,12
|
||||
LTEXT "minutes",IDC_STATIC,88,88,25,8
|
||||
PUSHBUTTON "&Advanced",IDC_ADVANCED_BREAK,192,102,41,14
|
||||
LTEXT "Enter timer mode by using the ZoomIt tray icon's Break menu item. Increase and decrease time with the arrow keys. If you Alt-Tab away from the timer window, reactivate it by left-clicking on the ZoomIt tray icon. Exit timer mode with Escape. ",IDC_STATIC,7,7,230,33
|
||||
LTEXT "Start Timer:",IDC_STATIC,7,70,39,8
|
||||
LTEXT "Timer:",IDC_STATIC,7,88,20,8
|
||||
LTEXT "Change the break timer color using the same keys that the drawing color. The break timer font is the same as text font.",IDC_STATIC,7,45,230,20
|
||||
CONTROL "",IDC_BREAK_HOTKEY,"msctls_hotkey32",WS_BORDER | WS_TABSTOP,52,74,80,12
|
||||
EDITTEXT IDC_TIMER,52,93,31,13,ES_RIGHT | ES_AUTOHSCROLL | ES_NUMBER
|
||||
CONTROL "",IDC_SPIN_TIMER,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS | UDS_NOTHOUSANDS,66,93,11,12
|
||||
LTEXT "minutes",IDC_STATIC,88,95,25,8
|
||||
PUSHBUTTON "&Advanced",IDC_ADVANCED_BREAK,213,140,41,14
|
||||
LTEXT "Enter timer mode by using the ZoomIt tray icon's Break menu item. Increase and decrease time with the arrow keys. If you Alt-Tab away from the timer window, reactivate it by left-clicking on the ZoomIt tray icon. Exit timer mode with Escape. ",IDC_STATIC,7,7,242,33
|
||||
LTEXT "Start Timer:",IDC_STATIC,7,77,39,8
|
||||
LTEXT "Timer:",IDC_STATIC,7,95,20,8
|
||||
LTEXT "Change the break timer color using the same keys that the drawing color, including background color. The break timer font is the same as text font.",IDC_STATIC,7,45,241,26
|
||||
CONTROL "Show Time Elapsed After Expiration:",IDC_CHECK_SHOW_EXPIRED,
|
||||
"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,8,104,132,10
|
||||
"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,7,111,130,10
|
||||
CONTROL "Lock Workstation During Break:",IDC_CHECK_LOCK_WORKSTATION,
|
||||
"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,7,126,113,10
|
||||
END
|
||||
|
||||
1543 DIALOGEX 100, 50, 216, 131
|
||||
@@ -249,19 +251,19 @@ BEGIN
|
||||
CTEXT "AaBbYyZz",1092,16,88,127,31,SS_NOPREFIX | NOT WS_VISIBLE
|
||||
END
|
||||
|
||||
LIVEZOOM DIALOGEX 0, 0, 260, 134
|
||||
STYLE DS_SETFONT | DS_CONTROL | WS_CHILD | WS_SYSMENU
|
||||
LIVEZOOM DIALOGEX 0, 0, 317, 136
|
||||
STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU
|
||||
FONT 8, "MS Shell Dlg", 400, 0, 0x1
|
||||
BEGIN
|
||||
CONTROL "",IDC_LIVE_HOTKEY,"msctls_hotkey32",WS_BORDER | WS_TABSTOP,69,108,80,12
|
||||
LTEXT "LiveZoom mode is supported on Windows 7 and higher where window updates show while zoomed. ",IDC_STATIC,7,7,230,18
|
||||
LTEXT "LiveZoom mode is supported on Windows 7 and higher where window updates show while zoomed. ",IDC_STATIC,7,7,255,18
|
||||
LTEXT "LiveZoom Toggle:",IDC_STATIC,7,110,62,8
|
||||
LTEXT "To enter and exit LiveZoom, enter the hotkey specified below.",IDC_STATIC,7,94,230,13
|
||||
LTEXT "Note that in LiveZoom you must use Ctrl+Up and Ctrl+Down to control the zoom level. To enter drawing mode, use the standard zoom-without-draw hotkey and then escape to go back to LiveZoom.",IDC_STATIC,7,30,230,27
|
||||
LTEXT "Use LiveDraw to draw and annotate the live desktop. To activate LiveDraw, enter the hotkey with the Shift key in the opposite mode. You can remove LiveDraw annotations by activating LiveDraw and enter the escape key",IDC_STATIC,7,62,230,32
|
||||
LTEXT "Use LiveDraw to draw and annotate the live desktop. To activate LiveDraw, enter the hotkey with the Shift key in the opposite mode. You can remove LiveDraw annotations by activating LiveDraw and enter the escape key",IDC_STATIC,7,62,249,32
|
||||
LTEXT "Note that in LiveZoom you must use Ctrl+Up and Ctrl+Down to control the zoom level. To enter drawing mode, use the standard zoom-without-draw hotkey and then escape to go back to LiveZoom.",IDC_STATIC,7,30,255,27
|
||||
END
|
||||
|
||||
RECORD DIALOGEX 0, 0, 260, 181
|
||||
RECORD DIALOGEX 0, 0, 263, 224
|
||||
STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU
|
||||
FONT 8, "MS Shell Dlg", 400, 0, 0x1
|
||||
BEGIN
|
||||
@@ -282,19 +284,33 @@ BEGIN
|
||||
CONTROL "Mono",IDC_MIC_MONO_MIX,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,98,161,30,10
|
||||
COMBOBOX IDC_MICROPHONE,81,176,152,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
|
||||
LTEXT "Microphone:",IDC_MICROPHONE_LABEL,32,178,47,8
|
||||
PUSHBUTTON "&Trim",IDC_TRIM_FILE,207,209,53,14
|
||||
END
|
||||
|
||||
SNIP DIALOGEX 0, 0, 260, 68
|
||||
STYLE DS_SETFONT | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_SYSMENU
|
||||
SNIP DIALOGEX 0, 0, 260, 80
|
||||
STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_SYSMENU
|
||||
FONT 8, "MS Shell Dlg", 400, 0, 0x1
|
||||
BEGIN
|
||||
CONTROL "",IDC_SNIP_HOTKEY,"msctls_hotkey32",WS_BORDER | WS_TABSTOP,55,32,80,12
|
||||
LTEXT "Copy a region of the screen to the clipboard or enter the hotkey with the Shift key in the opposite mode to save it to a file.",IDC_STATIC,7,7,230,19
|
||||
LTEXT "Snip Toggle:",IDC_STATIC,7,33,45,8
|
||||
LTEXT "Copy a region of the screen to the clipboard or enter the hotkey with the Shift key in the opposite mode to save it to a file. ",IDC_STATIC,7,7,230,19
|
||||
CONTROL "",IDC_SNIP_HOTKEY,"msctls_hotkey32",WS_BORDER | WS_TABSTOP,67,32,80,12
|
||||
LTEXT "Copy text from the selected region to the clipboard:",IDC_STATIC,7,50,230,10
|
||||
LTEXT "Text Toggle:",IDC_STATIC,7,65,55,8
|
||||
CONTROL "",IDC_SNIP_OCR_HOTKEY,"msctls_hotkey32",WS_BORDER | WS_TABSTOP,67,63,80,12
|
||||
END
|
||||
|
||||
PANORAMA DIALOGEX 0, 0, 260, 105
|
||||
STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_SYSMENU
|
||||
FONT 8, "MS Shell Dlg", 400, 0, 0x1
|
||||
BEGIN
|
||||
LTEXT "Capture a scrolling panorama of a selected screen region. Select the area, then scroll the content. Move slowly and consistently, and do not rewind to previously covered areas. Press the hotkey again or with Shift to save to a file.",IDC_STATIC,7,7,245,33
|
||||
LTEXT "Panorama Toggle:",IDC_STATIC,7,74,63,8
|
||||
CONTROL "",IDC_SNIP_PANORAMA_HOTKEY,"msctls_hotkey32",WS_BORDER | WS_TABSTOP,73,72,80,12
|
||||
LTEXT "For the best results, scroll slowly and at a constant rate, do not include stationary content (like scrollbars) in the capture area, and avoid content that is changing (e.g., animations or videos). ",IDC_STATIC,7,41,245,30
|
||||
END
|
||||
|
||||
DEMOTYPE DIALOGEX 0, 0, 260, 249
|
||||
STYLE DS_SETFONT | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_SYSMENU
|
||||
STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_SYSMENU
|
||||
FONT 8, "MS Shell Dlg", 400, 0, 0x1
|
||||
BEGIN
|
||||
CONTROL "",IDC_DEMOTYPE_HOTKEY,"msctls_hotkey32",WS_BORDER | WS_TABSTOP,74,154,80,12
|
||||
@@ -308,11 +324,11 @@ BEGIN
|
||||
LTEXT "Fast",IDC_DEMOTYPE_STATIC2,186,213,17,8
|
||||
EDITTEXT IDC_DEMOTYPE_FILE,44,137,167,12,ES_AUTOHSCROLL | ES_READONLY
|
||||
LTEXT "Input file:",IDC_STATIC,7,139,32,8
|
||||
LTEXT "When you reach the end of the file, ZoomIt will reload the file and start at the beginning. Enter the hotkey with the Shift key in the opposite mode to step back to the last [end].",IDC_STATIC,7,108,230,24
|
||||
LTEXT "DemoType has ZoomIt type text specified in the input file when you enter the DemoType toggle. Simply separate snippets with the [end] keyword, or you can insert text from the clipboard if it is prefixed with the [start].",IDC_STATIC,7,7,230,24
|
||||
LTEXT "When you reach the end of the file, ZoomIt will reload the file and start at the beginning. Enter the hotkey with the Shift key in the opposite mode to step back to the last [end].",IDC_STATIC,7,108,249,24
|
||||
LTEXT "DemoType has ZoomIt type text specified in the input file when you enter the DemoType toggle. Simply separate snippets with the [end] keyword, or you can insert text from the clipboard if it is prefixed with the [start].",IDC_STATIC,7,7,247,24
|
||||
LTEXT " - Insert pauses with the [pause:n] keyword where 'n' is seconds. ",IDC_STATIC,19,34,218,11
|
||||
LTEXT "You can have ZoomIt send text automatically, or select the option to drive input with typing. ZoomIt will block keyboard input while sending output.",IDC_STATIC,7,68,230,16
|
||||
LTEXT "When driving input, hit the space bar to unblock keyboard input at the end of a snippet. In auto mode, control will be returned upon completion.",IDC_STATIC,7,88,230,16
|
||||
LTEXT "You can have ZoomIt send text automatically, or select the option to drive input with typing. ZoomIt will block keyboard input while sending output.",IDC_STATIC,7,68,245,16
|
||||
LTEXT "When driving input, hit the space bar to unblock keyboard input at the end of a snippet. In auto mode, control will be returned upon completion.",IDC_STATIC,7,88,243,16
|
||||
LTEXT "- Send text via the clipboard with [paste] and [/paste]. ",IDC_STATIC,23,45,210,8
|
||||
LTEXT "- Send keystrokes with [enter], [up], [down], [left], and [right].",IDC_STATIC,23,56,210,8
|
||||
END
|
||||
@@ -349,13 +365,13 @@ BEGIN
|
||||
"OPTIONS", DIALOG
|
||||
BEGIN
|
||||
RIGHTMARGIN, 293
|
||||
BOTTOMMARGIN, 320
|
||||
BOTTOMMARGIN, 326
|
||||
END
|
||||
|
||||
"ADVANCED_BREAK", DIALOG
|
||||
BEGIN
|
||||
RIGHTMARGIN, 207
|
||||
BOTTOMMARGIN, 215
|
||||
BOTTOMMARGIN, 214
|
||||
END
|
||||
|
||||
"ZOOM", DIALOG
|
||||
@@ -383,7 +399,7 @@ BEGIN
|
||||
BEGIN
|
||||
LEFTMARGIN, 7
|
||||
TOPMARGIN, 7
|
||||
BOTTOMMARGIN, 116
|
||||
BOTTOMMARGIN, 154
|
||||
END
|
||||
|
||||
1543, DIALOG
|
||||
@@ -395,22 +411,27 @@ BEGIN
|
||||
"LIVEZOOM", DIALOG
|
||||
BEGIN
|
||||
LEFTMARGIN, 7
|
||||
RIGHTMARGIN, 181
|
||||
TOPMARGIN, 7
|
||||
BOTTOMMARGIN, 127
|
||||
BOTTOMMARGIN, 89
|
||||
END
|
||||
|
||||
"RECORD", DIALOG
|
||||
BEGIN
|
||||
LEFTMARGIN, 7
|
||||
RIGHTMARGIN, 260
|
||||
TOPMARGIN, 7
|
||||
BOTTOMMARGIN, 164
|
||||
BOTTOMMARGIN, 223
|
||||
END
|
||||
|
||||
"SNIP", DIALOG
|
||||
BEGIN
|
||||
LEFTMARGIN, 7
|
||||
TOPMARGIN, 7
|
||||
BOTTOMMARGIN, 61
|
||||
END
|
||||
|
||||
"PANORAMA", DIALOG
|
||||
BEGIN
|
||||
END
|
||||
|
||||
"DEMOTYPE", DIALOG
|
||||
@@ -496,6 +517,16 @@ BEGIN
|
||||
0
|
||||
END
|
||||
|
||||
ADVANCED_BREAK AFX_DIALOG_LAYOUT
|
||||
BEGIN
|
||||
0
|
||||
END
|
||||
|
||||
PANORAMA AFX_DIALOG_LAYOUT
|
||||
BEGIN
|
||||
0
|
||||
END
|
||||
|
||||
#endif // English (United States) resources
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@@ -68,8 +68,8 @@
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<DisableSpecificWarnings>4100;4091;4245</DisableSpecificWarnings>
|
||||
<AdditionalIncludeDirectories>..\..\..\;$(MSBuildThisFileDirectory)..\..\..\common\sysinternals;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
|
||||
<DisableSpecificWarnings>26451;4100;4091;4245</DisableSpecificWarnings>
|
||||
<AdditionalIncludeDirectories>..\..\..\;$(MSBuildThisFileDirectory)..\..\..\common\sysinternals;..\ZoomItBreak;$(MSBuildThisFileDirectory);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
@@ -90,7 +90,7 @@
|
||||
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)..\..\..\common\version;$(MSBuildThisFileDirectory)PowerToys;$(InterPlatformDir)</AdditionalIncludeDirectories>
|
||||
</ResourceCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;Wtsapi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<RandomizedBaseAddress>true</RandomizedBaseAddress>
|
||||
@@ -109,10 +109,10 @@
|
||||
<ResourceCompile>
|
||||
<PreprocessorDefinitions>NDEBUG;_M_X64;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Culture>0x0409</Culture>
|
||||
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)..\..\..\common\version;$(MSBuildThisFileDirectory)PowerToys;</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)..\..\..\common\version;$(MSBuildThisFileDirectory)PowerToys;$(MSBuildThisFileDirectory)..\ZoomItBreak\$(Platform)\$(Configuration)\</AdditionalIncludeDirectories>
|
||||
</ResourceCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;Wtsapi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<RandomizedBaseAddress>true</RandomizedBaseAddress>
|
||||
@@ -132,10 +132,10 @@
|
||||
<ResourceCompile>
|
||||
<PreprocessorDefinitions>NDEBUG;_M_ARM64;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Culture>0x0409</Culture>
|
||||
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)..\..\..\common\version;$(MSBuildThisFileDirectory)PowerToys;</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)..\..\..\common\version;$(MSBuildThisFileDirectory)PowerToys;$(MSBuildThisFileDirectory)..\ZoomItBreak\$(Platform)\$(Configuration)\</AdditionalIncludeDirectories>
|
||||
</ResourceCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;Wtsapi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<FixedBaseAddress>
|
||||
@@ -156,7 +156,7 @@
|
||||
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)..\..\..\common\version;$(MSBuildThisFileDirectory)PowerToys;$(InterPlatformDir)</AdditionalIncludeDirectories>
|
||||
</ResourceCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;Wtsapi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<RandomizedBaseAddress>false</RandomizedBaseAddress>
|
||||
@@ -174,10 +174,10 @@
|
||||
<ResourceCompile>
|
||||
<PreprocessorDefinitions>_DEBUG;_M_X64;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Culture>0x0409</Culture>
|
||||
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)..\..\..\common\version;$(MSBuildThisFileDirectory)PowerToys;</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)..\..\..\common\version;$(MSBuildThisFileDirectory)PowerToys;$(MSBuildThisFileDirectory)..\ZoomItBreak\$(Platform)\$(Configuration)\</AdditionalIncludeDirectories>
|
||||
</ResourceCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;Wtsapi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<UACUIAccess>true</UACUIAccess>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
@@ -196,10 +196,10 @@
|
||||
<ResourceCompile>
|
||||
<PreprocessorDefinitions>_DEBUG;_M_ARM64;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Culture>0x0409</Culture>
|
||||
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)..\..\..\common\version;$(MSBuildThisFileDirectory)PowerToys;</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)..\..\..\common\version;$(MSBuildThisFileDirectory)PowerToys;$(MSBuildThisFileDirectory)..\ZoomItBreak\$(Platform)\$(Configuration)\</AdditionalIncludeDirectories>
|
||||
</ResourceCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>Shlwapi.lib;comctl32.lib;odbc32.lib;odbccp32.lib;version.lib;Winmm.lib;gdiplus.lib;Msimg32.lib;Wtsapi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<UACUIAccess>true</UACUIAccess>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
@@ -208,6 +208,14 @@
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\ZoomItBreak\BreakTimer.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="AudioSampleGenerator.cpp">
|
||||
<MultiProcessorCompilation Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</MultiProcessorCompilation>
|
||||
<MultiProcessorCompilation Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</MultiProcessorCompilation>
|
||||
@@ -249,6 +257,14 @@
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GifRecordingSession.cpp" />
|
||||
<ClCompile Include="PanoramaCapture.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Use</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">Use</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Use</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Use</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">Use</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Use</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="pch.cpp" />
|
||||
<ClCompile Include="SelectRectangle.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Use</PrecompiledHeader>
|
||||
@@ -300,11 +316,13 @@
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\ZoomItBreak\BreakTimer.h" />
|
||||
<ClInclude Include="AudioSampleGenerator.h" />
|
||||
<ClInclude Include="LoopbackCapture.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)..\..\..\common\sysinternals\Eula\Eula.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)..\ZoomItModuleInterface\Trace.h" />
|
||||
<ClInclude Include="GifRecordingSession.h" />
|
||||
<ClInclude Include="PanoramaCapture.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="Registry.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
@@ -378,4 +396,4 @@
|
||||
<Import Project="..\..\..\..\packages\robmikh.common.0.0.23-beta\build\native\robmikh.common.targets" Condition="Exists('..\..\..\..\packages\robmikh.common.0.0.23-beta\build\native\robmikh.common.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -60,6 +60,12 @@
|
||||
<ClCompile Include="GifRecordingSession.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="PanoramaCapture.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\ZoomItBreak\BreakTimer.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Registry.h">
|
||||
@@ -107,6 +113,12 @@
|
||||
<ClInclude Include="GifRecordingSession.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="PanoramaCapture.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\ZoomItBreak\BreakTimer.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="appicon.ico">
|
||||
|
||||
@@ -17,6 +17,8 @@ DWORD g_BreakToggleKey = ((HOTKEYF_CONTROL) << 8)| '3';
|
||||
DWORD g_DemoTypeToggleKey = ((HOTKEYF_CONTROL) << 8) | '7';
|
||||
DWORD g_RecordToggleKey = ((HOTKEYF_CONTROL) << 8) | '5';
|
||||
DWORD g_SnipToggleKey = ((HOTKEYF_CONTROL) << 8) | '6';
|
||||
DWORD g_SnipPanoramaToggleKey = ((HOTKEYF_CONTROL) << 8) | '8';
|
||||
DWORD g_SnipOcrToggleKey = ((HOTKEYF_CONTROL | HOTKEYF_ALT) << 8) | '6';
|
||||
|
||||
DWORD g_ShowExpiredTime = 1;
|
||||
DWORD g_SliderZoomLevel = 3;
|
||||
@@ -24,6 +26,7 @@ BOOLEAN g_AnimateZoom = TRUE;
|
||||
BOOLEAN g_SmoothImage = TRUE;
|
||||
DWORD g_PenColor = COLOR_RED;
|
||||
DWORD g_BreakPenColor = COLOR_RED;
|
||||
DWORD g_BreakBackgroundColor = 0;
|
||||
DWORD g_RootPenWidth = PEN_WIDTH;
|
||||
int g_FontScale = 10;
|
||||
DWORD g_BreakTimeout = 10;
|
||||
@@ -40,6 +43,7 @@ BOOLEAN g_ShowTrayIcon = TRUE;
|
||||
BOOLEAN g_SnapToGrid = TRUE;
|
||||
BOOLEAN g_TelescopeZoomOut = TRUE;
|
||||
BOOLEAN g_BreakOnSecondary = FALSE;
|
||||
BOOLEAN g_BreakLockWorkstation = FALSE;
|
||||
LOGFONT g_LogFont;
|
||||
BOOLEAN g_DemoTypeUserDriven = false;
|
||||
TCHAR g_DemoTypeFile[MAX_PATH] = {0};
|
||||
@@ -66,10 +70,13 @@ REG_SETTING RegSettings[] = {
|
||||
{ L"DrawToggleKey", SETTING_TYPE_DWORD, 0, &g_DrawToggleKey, static_cast<DOUBLE>(g_DrawToggleKey) },
|
||||
{ L"RecordToggleKey", SETTING_TYPE_DWORD, 0, &g_RecordToggleKey, static_cast<DOUBLE>(g_RecordToggleKey) },
|
||||
{ L"SnipToggleKey", SETTING_TYPE_DWORD, 0, &g_SnipToggleKey, static_cast<DOUBLE>(g_SnipToggleKey) },
|
||||
{ L"SnipPanoramaToggleKey", SETTING_TYPE_DWORD, 0, &g_SnipPanoramaToggleKey, static_cast<DOUBLE>(g_SnipPanoramaToggleKey) },
|
||||
{ L"SnipOcrToggleKey", SETTING_TYPE_DWORD, 0, &g_SnipOcrToggleKey, static_cast<DOUBLE>(g_SnipOcrToggleKey) },
|
||||
{ L"PenColor", SETTING_TYPE_DWORD, 0, &g_PenColor, static_cast<DOUBLE>(g_PenColor) },
|
||||
{ L"PenWidth", SETTING_TYPE_DWORD, 0, &g_RootPenWidth, static_cast<DOUBLE>(g_RootPenWidth) },
|
||||
{ L"OptionsShown", SETTING_TYPE_BOOLEAN, 0, &g_OptionsShown, static_cast<DOUBLE>(g_OptionsShown) },
|
||||
{ L"BreakPenColor", SETTING_TYPE_DWORD, 0, &g_BreakPenColor, static_cast<DOUBLE>(g_BreakPenColor) },
|
||||
{ L"BreakBackgroundColor", SETTING_TYPE_DWORD, 0, &g_BreakBackgroundColor, static_cast<DOUBLE>(g_BreakBackgroundColor) },
|
||||
{ L"BreakTimerKey", SETTING_TYPE_DWORD, 0, &g_BreakToggleKey, static_cast<DOUBLE>(g_BreakToggleKey) },
|
||||
{ L"DemoTypeToggleKey", SETTING_TYPE_DWORD, 0, &g_DemoTypeToggleKey, static_cast<DOUBLE>(g_DemoTypeToggleKey) },
|
||||
{ L"DemoTypeFile", SETTING_TYPE_STRING, sizeof( g_DemoTypeFile ), g_DemoTypeFile, static_cast<DOUBLE>(0) },
|
||||
@@ -85,6 +92,7 @@ REG_SETTING RegSettings[] = {
|
||||
{ L"BreakTimerPosition", SETTING_TYPE_DWORD, 0, &g_BreakTimerPosition, static_cast<DOUBLE>(g_BreakTimerPosition) },
|
||||
{ L"BreakShowDesktop", SETTING_TYPE_BOOLEAN, 0, &g_BreakShowDesktop, static_cast<DOUBLE>(g_BreakShowDesktop) },
|
||||
{ L"BreakOnSecondary", SETTING_TYPE_BOOLEAN, 0, &g_BreakOnSecondary,static_cast<DOUBLE>(g_BreakOnSecondary) },
|
||||
{ L"BreakLockWorkstation", SETTING_TYPE_BOOLEAN, 0, &g_BreakLockWorkstation, static_cast<DOUBLE>(g_BreakLockWorkstation) },
|
||||
{ L"FontScale", SETTING_TYPE_DWORD, 0, &g_FontScale, static_cast<DOUBLE>(g_FontScale) },
|
||||
{ L"ShowExpiredTime", SETTING_TYPE_BOOLEAN, 0, &g_ShowExpiredTime, static_cast<DOUBLE>(g_ShowExpiredTime) },
|
||||
{ L"ShowTrayIcon", SETTING_TYPE_BOOLEAN, 0, &g_ShowTrayIcon, static_cast<DOUBLE>(g_ShowTrayIcon) },
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,4 +15,14 @@ RCZOOMIT64 BINRES MOVEABLE PURE RCZOOMIT_x64_path
|
||||
|
||||
#endif
|
||||
|
||||
// Embed the break timer screensaver for the current platform.
|
||||
// The .scr is built by the ZoomItBreak project into the shared output directory.
|
||||
#ifdef _M_IX86
|
||||
RCZOOMITSCR BINRES MOVEABLE PURE "ZoomItBreak.scr"
|
||||
#elif defined(_M_X64)
|
||||
RCZOOMITSCR BINRES MOVEABLE PURE "ZoomItBreak64.scr"
|
||||
#elif defined(_M_ARM64)
|
||||
RCZOOMITSCR BINRES MOVEABLE PURE "ZoomItBreak64a.scr"
|
||||
#endif
|
||||
|
||||
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "ZoomIt.exe.manifest"
|
||||
|
||||
@@ -53,6 +53,9 @@
|
||||
#include <winrt/Windows.Storage.Pickers.h>
|
||||
#include <winrt/Windows.Storage.FileProperties.h>
|
||||
#include <winrt/Windows.Devices.Enumeration.h>
|
||||
#include <winrt/Windows.Media.Ocr.h>
|
||||
|
||||
#include <Windows.Graphics.Imaging.Interop.h>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
|
||||
@@ -78,6 +78,8 @@
|
||||
#define IDC_RECORD_FRAME_RATE2 1059
|
||||
#define IDC_RECORD_SCALING 1059
|
||||
#define IDC_SNIP_HOTKEY 1060
|
||||
#define IDC_SNIP_OCR_HOTKEY 1112
|
||||
#define IDC_SNIP_PANORAMA_HOTKEY 1114
|
||||
#define IDC_CAPTURE_AUDIO 1061
|
||||
#define IDC_MICROPHONE 1062
|
||||
#define IDC_PEN_CONTROL 1063
|
||||
@@ -111,12 +113,15 @@
|
||||
#define IDC_SMOOTH_IMAGE 1107
|
||||
#define IDC_CAPTURE_SYSTEM_AUDIO 1108
|
||||
#define IDC_MICROPHONE_LABEL 1109
|
||||
#define IDC_MIC_MONO_MIX 1110
|
||||
#define IDC_TRIM_FILE 1110
|
||||
#define IDC_MIC_MONO_MIX 1111
|
||||
#define IDC_CHECK_LOCK_WORKSTATION 1112
|
||||
#define IDC_SAVE 40002
|
||||
#define IDC_COPY 40004
|
||||
#define IDC_RECORD 40006
|
||||
#define IDC_RECORD_HOTKEY 40007
|
||||
#define IDC_COPY_CROP 40008
|
||||
#define IDC_COPY_OCR 40014
|
||||
#define IDC_SAVE_CROP 40009
|
||||
#define IDC_DEMOTYPE_HOTKEY 40011
|
||||
|
||||
@@ -125,8 +130,8 @@
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||
#define _APS_NEXT_RESOURCE_VALUE 120
|
||||
#define _APS_NEXT_COMMAND_VALUE 40013
|
||||
#define _APS_NEXT_CONTROL_VALUE 1099
|
||||
#define _APS_NEXT_COMMAND_VALUE 40015
|
||||
#define _APS_NEXT_CONTROL_VALUE 1113
|
||||
#define _APS_NEXT_SYMED_VALUE 101
|
||||
#endif
|
||||
#endif
|
||||
|
||||
520
src/modules/ZoomIt/ZoomItBreak/BreakTimer.cpp
Normal file
520
src/modules/ZoomIt/ZoomItBreak/BreakTimer.cpp
Normal file
@@ -0,0 +1,520 @@
|
||||
//============================================================================
|
||||
//
|
||||
// BreakTimer.cpp
|
||||
//
|
||||
// Shared break timer rendering module used by both ZoomIt and the
|
||||
// ZoomItBreak screensaver (.scr).
|
||||
//
|
||||
// Copyright (C) Mark Russinovich
|
||||
// Sysinternals - www.sysinternals.com
|
||||
//
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
//============================================================================
|
||||
|
||||
// When built inside ZoomIt (with PCH), pch.h is included automatically.
|
||||
// When built for the screensaver project, we include the headers we need.
|
||||
#ifndef __ZOOMIT_SCREENSAVER__
|
||||
#include "pch.h"
|
||||
#endif
|
||||
|
||||
#include "BreakTimer.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#pragma comment(lib, "gdiplus.lib")
|
||||
#pragma comment(lib, "Msimg32.lib")
|
||||
#pragma comment(lib, "Winmm.lib")
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// BreakTimer_UpdateMonitorInfo
|
||||
//
|
||||
// Determine monitor geometry for the given screen point.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
void BreakTimer_UpdateMonitorInfo( POINT point, MONITORINFO* monInfo )
|
||||
{
|
||||
HMONITOR hMon = MonitorFromPoint( point, MONITOR_DEFAULTTONEAREST );
|
||||
if( hMon != nullptr )
|
||||
{
|
||||
monInfo->cbSize = sizeof *monInfo;
|
||||
GetMonitorInfo( hMon, monInfo );
|
||||
}
|
||||
else
|
||||
{
|
||||
*monInfo = {};
|
||||
HDC hdcScreen = CreateDC( L"DISPLAY", nullptr, nullptr, nullptr );
|
||||
if( hdcScreen != nullptr )
|
||||
{
|
||||
monInfo->rcMonitor.right = GetDeviceCaps( hdcScreen, HORZRES );
|
||||
monInfo->rcMonitor.bottom = GetDeviceCaps( hdcScreen, VERTRES );
|
||||
DeleteDC( hdcScreen );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// BreakTimer_LoadImageFile
|
||||
//
|
||||
// Use GDI+ to load an image file and return an HBITMAP.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
HBITMAP BreakTimer_LoadImageFile( PTCHAR Filename )
|
||||
{
|
||||
HBITMAP hBmp;
|
||||
Gdiplus::Bitmap* bitmap = Gdiplus::Bitmap::FromFile( Filename );
|
||||
if( bitmap == nullptr || bitmap->GetHBITMAP( NULL, &hBmp ) != Gdiplus::Ok )
|
||||
{
|
||||
delete bitmap;
|
||||
return NULL;
|
||||
}
|
||||
delete bitmap;
|
||||
return hBmp;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// BreakTimer_CreateFadedDesktopBackground
|
||||
//
|
||||
// Creates a snapshot of the desktop that is faded and alpha-blended
|
||||
// with black.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
HBITMAP BreakTimer_CreateFadedDesktopBackground( HDC hdc, LPRECT rcScreen, LPRECT rcCrop )
|
||||
{
|
||||
int width = rcScreen->right - rcScreen->left;
|
||||
int height = rcScreen->bottom - rcScreen->top;
|
||||
HDC hdcScreen = hdc;
|
||||
HDC hdcMem = CreateCompatibleDC( hdcScreen );
|
||||
HBITMAP hBitmap = CreateCompatibleBitmap( hdcScreen, width, height );
|
||||
HBITMAP hOld = static_cast<HBITMAP>( SelectObject( hdcMem, hBitmap ) );
|
||||
HBRUSH hBrush = CreateSolidBrush( RGB( 0, 0, 0 ) );
|
||||
|
||||
// Start with black background.
|
||||
FillRect( hdcMem, rcScreen, hBrush );
|
||||
if( rcCrop != NULL && rcCrop->left != -1 )
|
||||
{
|
||||
// Copy screen contents that are not cropped.
|
||||
BitBlt( hdcMem, rcCrop->left, rcCrop->top,
|
||||
rcCrop->right - rcCrop->left,
|
||||
rcCrop->bottom - rcCrop->top,
|
||||
hdcScreen, rcCrop->left, rcCrop->top, SRCCOPY );
|
||||
}
|
||||
|
||||
// Blend screen contents into the black background.
|
||||
BLENDFUNCTION blend = { 0 };
|
||||
blend.BlendOp = AC_SRC_OVER;
|
||||
blend.BlendFlags = 0;
|
||||
blend.SourceConstantAlpha = 0x4F;
|
||||
blend.AlphaFormat = 0;
|
||||
AlphaBlend( hdcMem, 0, 0, width, height,
|
||||
hdcScreen, rcScreen->left, rcScreen->top,
|
||||
width, height, blend );
|
||||
|
||||
SelectObject( hdcMem, hOld );
|
||||
DeleteDC( hdcMem );
|
||||
DeleteObject( hBrush );
|
||||
|
||||
return hBitmap;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// BreakTimer_Init
|
||||
//
|
||||
// Create fonts, backing bitmap, and optionally load background.
|
||||
// Returns TRUE on success.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
BOOLEAN BreakTimer_Init(
|
||||
HWND hWnd,
|
||||
BreakTimerState* state,
|
||||
const BreakTimerSettings* settings,
|
||||
int timeoutSeconds,
|
||||
HBITMAP hExistingBackground,
|
||||
HDC hExistingBackgroundDC )
|
||||
{
|
||||
state->active = TRUE;
|
||||
state->timeoutSeconds = timeoutSeconds;
|
||||
|
||||
// Get screen DC.
|
||||
state->hdcScreen = CreateDC( L"DISPLAY", static_cast<PTCHAR>( NULL ),
|
||||
static_cast<PTCHAR>( NULL ),
|
||||
static_cast<CONST DEVMODE*>( NULL ) );
|
||||
if( !state->hdcScreen )
|
||||
return FALSE;
|
||||
|
||||
// Determine monitor.
|
||||
POINT cursorPos;
|
||||
GetCursorPos( &cursorPos );
|
||||
BreakTimer_UpdateMonitorInfo( cursorPos, &state->monInfo );
|
||||
state->width = state->monInfo.rcMonitor.right - state->monInfo.rcMonitor.left;
|
||||
state->height = state->monInfo.rcMonitor.bottom - state->monInfo.rcMonitor.top;
|
||||
|
||||
// Manage background bitmap.
|
||||
if( hExistingBackground )
|
||||
{
|
||||
// Caller supplied a pre-captured background (e.g. from command line).
|
||||
state->hBackgroundBmp = hExistingBackground;
|
||||
state->hDcBackgroundFile = hExistingBackgroundDC;
|
||||
}
|
||||
else if( settings->showBackgroundFile && !settings->showDesktop )
|
||||
{
|
||||
// Load image file.
|
||||
state->hBackgroundBmp = BreakTimer_LoadImageFile(
|
||||
const_cast<PTCHAR>( settings->backgroundFile ) );
|
||||
if( !state->hBackgroundBmp )
|
||||
return FALSE;
|
||||
state->hDcBackgroundFile = CreateCompatibleDC( state->hdcScreen );
|
||||
SelectObject( state->hDcBackgroundFile, state->hBackgroundBmp );
|
||||
}
|
||||
else if( settings->showBackgroundFile && settings->showDesktop )
|
||||
{
|
||||
// Faded desktop screenshot.
|
||||
HDC hDcDesktop = GetDC( NULL );
|
||||
state->hBackgroundBmp = BreakTimer_CreateFadedDesktopBackground(
|
||||
hDcDesktop, &state->monInfo.rcMonitor, NULL );
|
||||
ReleaseDC( NULL, hDcDesktop );
|
||||
state->hDcBackgroundFile = CreateCompatibleDC( state->hdcScreen );
|
||||
SelectObject( state->hDcBackgroundFile, state->hBackgroundBmp );
|
||||
}
|
||||
else
|
||||
{
|
||||
state->hBackgroundBmp = NULL;
|
||||
state->hDcBackgroundFile = NULL;
|
||||
}
|
||||
|
||||
// Create fonts.
|
||||
LOGFONT lf = settings->logFont;
|
||||
lf.lfHeight = state->height / 5;
|
||||
state->hTimerFont = CreateFontIndirect( &lf );
|
||||
lf.lfHeight = state->height / 8;
|
||||
state->hNegativeTimerFont = CreateFontIndirect( &lf );
|
||||
|
||||
// Create backing bitmap for double buffering.
|
||||
state->hdcScreenCompat = CreateCompatibleDC( state->hdcScreen );
|
||||
state->bmp.bmBitsPixel = static_cast<BYTE>( GetDeviceCaps( state->hdcScreen, BITSPIXEL ) );
|
||||
state->bmp.bmPlanes = static_cast<BYTE>( GetDeviceCaps( state->hdcScreen, PLANES ) );
|
||||
state->bmp.bmWidth = state->width;
|
||||
state->bmp.bmHeight = state->height;
|
||||
state->bmp.bmWidthBytes = ( ( state->bmp.bmWidth + 15 ) & ~15 ) / 8;
|
||||
state->hbmpCompat = CreateBitmap( state->bmp.bmWidth, state->bmp.bmHeight,
|
||||
state->bmp.bmPlanes, state->bmp.bmBitsPixel, static_cast<CONST VOID*>( NULL ) );
|
||||
SelectObject( state->hdcScreenCompat, state->hbmpCompat );
|
||||
|
||||
SetTextColor( state->hdcScreenCompat, settings->penColor );
|
||||
SetBkMode( state->hdcScreenCompat, TRANSPARENT );
|
||||
SelectObject( state->hdcScreenCompat, state->hTimerFont );
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// BreakTimer_Tick
|
||||
//
|
||||
// Decrement counter, invalidate window, play sound at zero.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
void BreakTimer_Tick(
|
||||
HWND hWnd,
|
||||
BreakTimerState* state,
|
||||
const BreakTimerSettings* settings )
|
||||
{
|
||||
state->timeoutSeconds -= 1;
|
||||
InvalidateRect( hWnd, NULL, FALSE );
|
||||
|
||||
if( state->timeoutSeconds == 0 && settings->playSound )
|
||||
{
|
||||
PlaySound( settings->soundFile, NULL, SND_FILENAME | SND_ASYNC );
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// BreakTimer_Paint
|
||||
//
|
||||
// Render the break timer into the back buffer and blit to the paint DC.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
void BreakTimer_Paint(
|
||||
HDC hdc,
|
||||
BreakTimerState* state,
|
||||
const BreakTimerSettings* settings )
|
||||
{
|
||||
RECT rc, rc1;
|
||||
TCHAR timerText[16];
|
||||
TCHAR negativeTimerText[16];
|
||||
|
||||
// Fill background (white by default, black if backgroundColor == 1).
|
||||
rc.top = rc.left = 0;
|
||||
rc.bottom = state->height;
|
||||
rc.right = state->width;
|
||||
if( settings->backgroundColor )
|
||||
{
|
||||
HBRUSH hBrush = CreateSolidBrush( RGB( 0, 0, 0 ) );
|
||||
FillRect( state->hdcScreenCompat, &rc, hBrush );
|
||||
DeleteObject( hBrush );
|
||||
}
|
||||
else
|
||||
{
|
||||
FillRect( state->hdcScreenCompat, &rc, GetSysColorBrush( COLOR_WINDOW ) );
|
||||
}
|
||||
|
||||
// Draw background bitmap if present.
|
||||
if( state->hBackgroundBmp )
|
||||
{
|
||||
BITMAP local_bmp;
|
||||
GetObject( state->hBackgroundBmp, sizeof( local_bmp ), &local_bmp );
|
||||
SetStretchBltMode( state->hdcScreenCompat,
|
||||
settings->smoothImage ? HALFTONE : COLORONCOLOR );
|
||||
if( settings->backgroundStretch )
|
||||
{
|
||||
StretchBlt( state->hdcScreenCompat, 0, 0, state->width, state->height,
|
||||
state->hDcBackgroundFile, 0, 0,
|
||||
local_bmp.bmWidth, local_bmp.bmHeight, SRCCOPY | CAPTUREBLT );
|
||||
}
|
||||
else
|
||||
{
|
||||
BitBlt( state->hdcScreenCompat,
|
||||
state->width / 2 - local_bmp.bmWidth / 2,
|
||||
state->height / 2 - local_bmp.bmHeight / 2,
|
||||
local_bmp.bmWidth, local_bmp.bmHeight,
|
||||
state->hDcBackgroundFile, 0, 0, SRCCOPY | CAPTUREBLT );
|
||||
}
|
||||
}
|
||||
|
||||
// Format timer text.
|
||||
if( state->timeoutSeconds > 0 )
|
||||
{
|
||||
_stprintf( timerText, L"% 2d:%02d",
|
||||
state->timeoutSeconds / 60, state->timeoutSeconds % 60 );
|
||||
}
|
||||
else
|
||||
{
|
||||
_tcscpy( timerText, L"0:00" );
|
||||
}
|
||||
|
||||
// Measure timer text.
|
||||
rc.left = rc.top = 0;
|
||||
DrawText( state->hdcScreenCompat, timerText, -1, &rc,
|
||||
DT_NOCLIP | DT_LEFT | DT_NOPREFIX | DT_CALCRECT );
|
||||
|
||||
// Measure expired text if needed.
|
||||
rc1.left = rc1.right = rc1.bottom = rc1.top = 0;
|
||||
if( settings->showExpiredTime && state->timeoutSeconds < 0 )
|
||||
{
|
||||
_stprintf( negativeTimerText, L"(-% 2d:%02d)",
|
||||
-state->timeoutSeconds / 60, -state->timeoutSeconds % 60 );
|
||||
HFONT prevFont = static_cast<HFONT>(
|
||||
SelectObject( state->hdcScreenCompat, state->hNegativeTimerFont ) );
|
||||
DrawText( state->hdcScreenCompat, negativeTimerText, -1, &rc1,
|
||||
DT_NOCLIP | DT_LEFT | DT_NOPREFIX | DT_CALCRECT );
|
||||
SelectObject( state->hdcScreenCompat, prevFont );
|
||||
}
|
||||
|
||||
// Position vertically.
|
||||
switch( settings->timerPosition )
|
||||
{
|
||||
case 0: case 1: case 2:
|
||||
rc.top = 50;
|
||||
break;
|
||||
case 3: case 4: case 5:
|
||||
rc.top = ( state->height - ( rc.bottom - rc.top ) ) / 2;
|
||||
break;
|
||||
case 6: case 7: case 8:
|
||||
rc.top = state->height - rc.bottom - 50 - rc1.bottom;
|
||||
break;
|
||||
}
|
||||
|
||||
// Position horizontally.
|
||||
switch( settings->timerPosition )
|
||||
{
|
||||
case 0: case 3: case 6:
|
||||
rc.left = 50;
|
||||
break;
|
||||
case 1: case 4: case 7:
|
||||
rc.left = ( state->width - ( rc.right - rc.left ) ) / 2;
|
||||
break;
|
||||
case 2: case 5: case 8:
|
||||
rc.left = state->width - rc.right - 50;
|
||||
break;
|
||||
}
|
||||
rc.bottom += rc.top;
|
||||
rc.right += rc.left;
|
||||
|
||||
// Draw timer text.
|
||||
DrawText( state->hdcScreenCompat, timerText, -1, &rc,
|
||||
DT_NOCLIP | DT_LEFT | DT_NOPREFIX );
|
||||
|
||||
// Draw expired text below the timer.
|
||||
if( settings->showExpiredTime && state->timeoutSeconds < 0 )
|
||||
{
|
||||
rc1.top = rc.bottom + 10;
|
||||
rc1.left = rc.left + ( ( rc.right - rc.left ) - ( rc1.right - rc1.left ) ) / 2;
|
||||
HFONT prevFont = static_cast<HFONT>(
|
||||
SelectObject( state->hdcScreenCompat, state->hNegativeTimerFont ) );
|
||||
DrawText( state->hdcScreenCompat, negativeTimerText, -1, &rc1,
|
||||
DT_NOCLIP | DT_LEFT | DT_NOPREFIX );
|
||||
SelectObject( state->hdcScreenCompat, prevFont );
|
||||
}
|
||||
|
||||
// Copy to screen.
|
||||
BitBlt( hdc, 0, 0, state->width, state->height,
|
||||
state->hdcScreenCompat, 0, 0, SRCCOPY | CAPTUREBLT );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// BreakTimer_Cleanup
|
||||
//
|
||||
// Free the GDI resources used by the break timer.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
void BreakTimer_Cleanup(
|
||||
BreakTimerState* state,
|
||||
BOOLEAN freeBackground )
|
||||
{
|
||||
if( freeBackground && state->hBackgroundBmp )
|
||||
{
|
||||
DeleteObject( state->hBackgroundBmp );
|
||||
DeleteDC( state->hDcBackgroundFile );
|
||||
state->hBackgroundBmp = NULL;
|
||||
state->hDcBackgroundFile = NULL;
|
||||
}
|
||||
|
||||
if( state->hTimerFont )
|
||||
{
|
||||
DeleteObject( state->hTimerFont );
|
||||
state->hTimerFont = NULL;
|
||||
}
|
||||
if( state->hNegativeTimerFont )
|
||||
{
|
||||
DeleteObject( state->hNegativeTimerFont );
|
||||
state->hNegativeTimerFont = NULL;
|
||||
}
|
||||
if( state->hdcScreen )
|
||||
{
|
||||
DeleteDC( state->hdcScreen );
|
||||
state->hdcScreen = NULL;
|
||||
}
|
||||
if( state->hdcScreenCompat )
|
||||
{
|
||||
DeleteDC( state->hdcScreenCompat );
|
||||
state->hdcScreenCompat = NULL;
|
||||
}
|
||||
if( state->hbmpCompat )
|
||||
{
|
||||
DeleteObject( state->hbmpCompat );
|
||||
state->hbmpCompat = NULL;
|
||||
}
|
||||
|
||||
state->active = FALSE;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// BreakTimer_AdjustTime
|
||||
//
|
||||
// Round to the nearest minute boundary and adjust by deltaMinutes.
|
||||
// Resets the 1-second timer on the window.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
void BreakTimer_AdjustTime(
|
||||
HWND hWnd,
|
||||
BreakTimerState* state,
|
||||
int deltaMinutes )
|
||||
{
|
||||
int breakTimeout = state->timeoutSeconds;
|
||||
|
||||
if( deltaMinutes > 0 )
|
||||
{
|
||||
if( breakTimeout < 0 ) breakTimeout = 0;
|
||||
if( breakTimeout % 60 )
|
||||
{
|
||||
breakTimeout += ( 60 - breakTimeout % 60 );
|
||||
deltaMinutes--;
|
||||
}
|
||||
breakTimeout += deltaMinutes * 60;
|
||||
}
|
||||
else
|
||||
{
|
||||
int absDelta = -deltaMinutes;
|
||||
if( breakTimeout % 60 )
|
||||
{
|
||||
breakTimeout -= breakTimeout % 60;
|
||||
absDelta--;
|
||||
}
|
||||
breakTimeout -= absDelta * 60;
|
||||
}
|
||||
|
||||
if( breakTimeout < 0 ) breakTimeout = 0;
|
||||
state->timeoutSeconds = breakTimeout;
|
||||
|
||||
KillTimer( hWnd, 0 );
|
||||
SetTimer( hWnd, 0, 1000, NULL );
|
||||
InvalidateRect( hWnd, NULL, TRUE );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// BreakScrConfig_GetPath
|
||||
//
|
||||
// Build the full path to the config file in %TEMP%.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
static void BreakScrConfig_GetPath( TCHAR* path, size_t cch )
|
||||
{
|
||||
GetTempPath( static_cast<DWORD>( cch ), path );
|
||||
_tcscat( path, BREAKSCR_CONFIG_FILE );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// BreakScrConfig_Write
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
BOOLEAN BreakScrConfig_Write( const BreakScrConfig* config )
|
||||
{
|
||||
TCHAR path[MAX_PATH];
|
||||
BreakScrConfig_GetPath( path, MAX_PATH );
|
||||
|
||||
HANDLE hFile = CreateFile( path, GENERIC_WRITE, 0, NULL,
|
||||
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
|
||||
if( hFile == INVALID_HANDLE_VALUE )
|
||||
return FALSE;
|
||||
|
||||
DWORD written;
|
||||
BOOL ok = WriteFile( hFile, config, sizeof( *config ), &written, NULL );
|
||||
CloseHandle( hFile );
|
||||
return ok && written == sizeof( *config );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// BreakScrConfig_Read
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
BOOLEAN BreakScrConfig_Read( BreakScrConfig* config )
|
||||
{
|
||||
TCHAR path[MAX_PATH];
|
||||
BreakScrConfig_GetPath( path, MAX_PATH );
|
||||
|
||||
HANDLE hFile = CreateFile( path, GENERIC_READ, FILE_SHARE_READ, NULL,
|
||||
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
|
||||
if( hFile == INVALID_HANDLE_VALUE )
|
||||
return FALSE;
|
||||
|
||||
DWORD bytesRead;
|
||||
BOOL ok = ReadFile( hFile, config, sizeof( *config ), &bytesRead, NULL );
|
||||
CloseHandle( hFile );
|
||||
|
||||
if( !ok || bytesRead != sizeof( *config ) )
|
||||
return FALSE;
|
||||
if( config->magic != BREAKSCR_CONFIG_MAGIC )
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
141
src/modules/ZoomIt/ZoomItBreak/BreakTimer.h
Normal file
141
src/modules/ZoomIt/ZoomItBreak/BreakTimer.h
Normal file
@@ -0,0 +1,141 @@
|
||||
//============================================================================
|
||||
//
|
||||
// BreakTimer.h
|
||||
//
|
||||
// Shared break timer rendering module used by both ZoomIt and the
|
||||
// ZoomItBreak screensaver (.scr).
|
||||
//
|
||||
// Copyright (C) Mark Russinovich
|
||||
// Sysinternals - www.sysinternals.com
|
||||
//
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
//============================================================================
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <tchar.h>
|
||||
#define GDIPVER 0x0110
|
||||
#include <gdiplus.h>
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// BreakTimerSettings — read-only configuration, populated from globals
|
||||
// or from command-line arguments in the screensaver.
|
||||
//----------------------------------------------------------------------------
|
||||
struct BreakTimerSettings
|
||||
{
|
||||
DWORD penColor;
|
||||
DWORD backgroundColor; // 0 = white, 1 = black
|
||||
DWORD timerPosition; // 0–8 (3×3 grid)
|
||||
DWORD opacity; // 0–100
|
||||
DWORD showExpiredTime; // 0 or 1
|
||||
BOOLEAN smoothImage;
|
||||
BOOLEAN backgroundStretch;
|
||||
BOOLEAN playSound;
|
||||
TCHAR soundFile[MAX_PATH];
|
||||
BOOLEAN showDesktop;
|
||||
BOOLEAN showBackgroundFile;
|
||||
TCHAR backgroundFile[MAX_PATH];
|
||||
LOGFONT logFont;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// BreakTimerState — runtime state for an active break timer.
|
||||
//----------------------------------------------------------------------------
|
||||
struct BreakTimerState
|
||||
{
|
||||
BOOLEAN active;
|
||||
int timeoutSeconds; // counts down; goes negative if expired
|
||||
HFONT hTimerFont;
|
||||
HFONT hNegativeTimerFont;
|
||||
HBITMAP hBackgroundBmp;
|
||||
HDC hDcBackgroundFile;
|
||||
HDC hdcScreen;
|
||||
HDC hdcScreenCompat;
|
||||
HBITMAP hbmpCompat;
|
||||
BITMAP bmp;
|
||||
int width;
|
||||
int height;
|
||||
MONITORINFO monInfo;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Shared utility functions
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
// Determine monitor geometry for the given screen point.
|
||||
void BreakTimer_UpdateMonitorInfo( POINT point, MONITORINFO* monInfo );
|
||||
|
||||
// Load an image file via GDI+; returns an HBITMAP or NULL on failure.
|
||||
HBITMAP BreakTimer_LoadImageFile( PTCHAR Filename );
|
||||
|
||||
// Capture a faded (alpha-blended with black) screenshot of the desktop.
|
||||
HBITMAP BreakTimer_CreateFadedDesktopBackground( HDC hdc, LPRECT rcScreen, LPRECT rcCrop );
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Break timer lifecycle
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
// Create fonts, backing bitmap, and load background.
|
||||
// The caller is responsible for creating/showing the window itself.
|
||||
// |timeoutSeconds| is already in seconds (e.g. g_BreakTimeout * 60 + 1).
|
||||
BOOLEAN BreakTimer_Init(
|
||||
HWND hWnd,
|
||||
BreakTimerState* state,
|
||||
const BreakTimerSettings* settings,
|
||||
int timeoutSeconds,
|
||||
HBITMAP hExistingBackground, // optional pre-captured background
|
||||
HDC hExistingBackgroundDC // optional DC for above
|
||||
);
|
||||
|
||||
// Called every second; decrements the counter and invalidates the window.
|
||||
void BreakTimer_Tick(
|
||||
HWND hWnd,
|
||||
BreakTimerState* state,
|
||||
const BreakTimerSettings* settings
|
||||
);
|
||||
|
||||
// Render the timer into hdcScreenCompat then BitBlt to hdc (from BeginPaint).
|
||||
void BreakTimer_Paint(
|
||||
HDC hdc,
|
||||
BreakTimerState* state,
|
||||
const BreakTimerSettings* settings
|
||||
);
|
||||
|
||||
// Free fonts, DCs, bitmaps. If |freeBackground| is false the background
|
||||
// bitmap/DC are left for the caller to manage (e.g. shallow destroy).
|
||||
void BreakTimer_Cleanup(
|
||||
BreakTimerState* state,
|
||||
BOOLEAN freeBackground
|
||||
);
|
||||
|
||||
// Adjust the remaining time by |deltaMinutes| (positive = add time).
|
||||
// Resets the 1-second timer on hWnd.
|
||||
void BreakTimer_AdjustTime(
|
||||
HWND hWnd,
|
||||
BreakTimerState* state,
|
||||
int deltaMinutes
|
||||
);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// BreakScrConfig — binary blob written to a temp file by ZoomIt and
|
||||
// read by the screensaver on startup. This avoids command-line arg
|
||||
// issues since Windows launches screensavers with only /s.
|
||||
//----------------------------------------------------------------------------
|
||||
#define BREAKSCR_CONFIG_MAGIC 0x5A4D4253 // 'ZMBS'
|
||||
#define BREAKSCR_CONFIG_FILE L"ZoomItBreakConfig.dat"
|
||||
|
||||
struct BreakScrConfig
|
||||
{
|
||||
DWORD magic; // must be BREAKSCR_CONFIG_MAGIC
|
||||
int timeoutSeconds;
|
||||
BOOL resumed; // set TRUE by screensaver on first launch
|
||||
BreakTimerSettings settings;
|
||||
TCHAR screenshotPath[MAX_PATH];
|
||||
};
|
||||
|
||||
// Write config to %TEMP%\BREAKSCR_CONFIG_FILE.
|
||||
BOOLEAN BreakScrConfig_Write( const BreakScrConfig* config );
|
||||
|
||||
// Read config from %TEMP%\BREAKSCR_CONFIG_FILE.
|
||||
BOOLEAN BreakScrConfig_Read( BreakScrConfig* config );
|
||||
10
src/modules/ZoomIt/ZoomItBreak/ZoomItBreak.manifest
Normal file
10
src/modules/ZoomIt/ZoomItBreak/ZoomItBreak.manifest
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"
|
||||
xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||
<asmv3:application>
|
||||
<asmv3:windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,PerMonitor</dpiAwareness>
|
||||
</asmv3:windowsSettings>
|
||||
</asmv3:application>
|
||||
</assembly>
|
||||
27
src/modules/ZoomIt/ZoomItBreak/ZoomItBreak.rc
Normal file
27
src/modules/ZoomIt/ZoomItBreak/ZoomItBreak.rc
Normal file
@@ -0,0 +1,27 @@
|
||||
//============================================================================
|
||||
//
|
||||
// ZoomItBreak.rc
|
||||
//
|
||||
// Minimal resources required by Scrnsavw.lib.
|
||||
//
|
||||
//============================================================================
|
||||
#include <windows.h>
|
||||
#include <scrnsave.h>
|
||||
|
||||
// Embed DPI-awareness manifest so the screensaver sees native resolution.
|
||||
// This ensures the pre-captured desktop screenshot (saved at physical pixels
|
||||
// by the DPI-aware ZoomIt process) matches the screensaver window dimensions.
|
||||
1 RT_MANIFEST "ZoomItBreak.manifest"
|
||||
|
||||
// IDS_DESCRIPTION is used by scrnsavw.lib as the window class name.
|
||||
STRINGTABLE
|
||||
BEGIN
|
||||
IDS_DESCRIPTION, "ZoomIt Break Timer"
|
||||
END
|
||||
|
||||
// Stub configuration dialog - never shown (ScreenSaverConfigureDialog returns FALSE).
|
||||
DLG_SCRNSAVECONFIGURE DIALOG 0, 0, 200, 60
|
||||
STYLE WS_DLGFRAME | WS_POPUP | WS_VISIBLE | DS_MODALFRAME | WS_CAPTION
|
||||
CAPTION "ZoomIt Break Timer"
|
||||
BEGIN
|
||||
END
|
||||
230
src/modules/ZoomIt/ZoomItBreak/ZoomItBreak.vcxproj
Normal file
230
src/modules/ZoomIt/ZoomItBreak/ZoomItBreak.vcxproj
Normal file
@@ -0,0 +1,230 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>18.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{94ba3051-c8d7-454a-9d46-1a7c78e228a3}</ProjectGuid>
|
||||
<RootNamespace>ZoomItBreak</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0.26100.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<!--
|
||||
Output .scr instead of .exe. A screensaver is a renamed executable.
|
||||
Use a separate intermediate dir to avoid colliding with ZoomIt.
|
||||
-->
|
||||
<PropertyGroup>
|
||||
<TargetExt>.scr</TargetExt>
|
||||
<GenerateManifest>false</GenerateManifest>
|
||||
<IntDir>$(Platform)\$(Configuration)\ZoomItBreak\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<OutDir>$(MsBuildProjectDirectory)\$(Platform)\$(Configuration)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<OutDir>$(MsBuildProjectDirectory)\$(Platform)\$(Configuration)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<OutDir>$(MsBuildProjectDirectory)\$(Platform)\$(Configuration)\</OutDir>
|
||||
<TargetName>$(ProjectName)64</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<OutDir>$(MsBuildProjectDirectory)\$(Platform)\$(Configuration)\</OutDir>
|
||||
<TargetName>$(ProjectName)64a</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<OutDir>$(MsBuildProjectDirectory)\$(Platform)\$(Configuration)\</OutDir>
|
||||
<TargetName>$(ProjectName)64</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<OutDir>$(MsBuildProjectDirectory)\$(Platform)\$(Configuration)\</OutDir>
|
||||
<TargetName>$(ProjectName)64a</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v145</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v145</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v145</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v145</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v145</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v145</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<DisableSpecificWarnings>4100;4091;4245</DisableSpecificWarnings>
|
||||
<AdditionalIncludeDirectories>..\..\..\;$(MSBuildThisFileDirectory)..\..\..\common\sysinternals;..\ZoomIt;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
<LanguageStandard_C>stdc17</LanguageStandard_C>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>__ZOOMIT_SCREENSAVER__;_UNICODE;UNICODE;WINVER=0x0602;_DEBUG;_WIN32_WINNT=0x602;_WIN32_WINDOWS=0x600;WIN32;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>__ZOOMIT_SCREENSAVER__;_UNICODE;UNICODE;WINVER=0x602;NDEBUG;_WIN32_WINNT=0x602;_WIN32_WINDOWS=0x501;WIN32;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>__ZOOMIT_SCREENSAVER__;_UNICODE;UNICODE;WINVER=0x0602;_DEBUG;_WIN32_WINNT=0x602;_WIN32_WINDOWS=0x600;WIN32;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions>_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>__ZOOMIT_SCREENSAVER__;_UNICODE;UNICODE;WINVER=0x0602;_DEBUG;_WIN32_WINNT=0x602;_WIN32_WINDOWS=0x600;WIN32;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions>_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>__ZOOMIT_SCREENSAVER__;_UNICODE;UNICODE;WINVER=0x602;NDEBUG;_WIN32_WINNT=0x602;_WIN32_WINDOWS=0x501;WIN32;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>__ZOOMIT_SCREENSAVER__;_UNICODE;UNICODE;WINVER=0x602;NDEBUG;_WIN32_WINNT=0x602;_WIN32_WINDOWS=0x501;WIN32;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="BreakTimer.cpp" />
|
||||
<ClCompile Include="ZoomItBreakScr.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="ZoomItBreak.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="BreakTimer.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
|
||||
</Target>
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
|
||||
</Project>
|
||||
38
src/modules/ZoomIt/ZoomItBreak/ZoomItBreak.vcxproj.filters
Normal file
38
src/modules/ZoomIt/ZoomItBreak/ZoomItBreak.vcxproj.filters
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="BreakTimer.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ZoomItBreakScr.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="BreakTimer.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="ZoomItBreak.rc">
|
||||
<Filter>Resource Files</Filter>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
283
src/modules/ZoomIt/ZoomItBreak/ZoomItBreakScr.cpp
Normal file
283
src/modules/ZoomIt/ZoomItBreak/ZoomItBreakScr.cpp
Normal file
@@ -0,0 +1,283 @@
|
||||
//============================================================================
|
||||
//
|
||||
// ZoomItBreakScr.cpp
|
||||
//
|
||||
// ZoomIt break timer screensaver (.scr). When launched by Winlogon on the
|
||||
// Screen-saver desktop with password protection, the user must authenticate
|
||||
// to dismiss it. The break timer countdown and rendering are provided by
|
||||
// the shared BreakTimer module.
|
||||
//
|
||||
// Copyright (C) Mark Russinovich
|
||||
// Sysinternals - www.sysinternals.com
|
||||
//
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
//============================================================================
|
||||
|
||||
#include <windows.h>
|
||||
#include <windowsx.h>
|
||||
#include <tchar.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <scrnsave.h>
|
||||
#define GDIPVER 0x0110
|
||||
#include <gdiplus.h>
|
||||
|
||||
#include "BreakTimer.h"
|
||||
|
||||
static void DbgPrint( LPCTSTR fmt, ... )
|
||||
{
|
||||
TCHAR buf[512];
|
||||
va_list ap;
|
||||
#pragma warning( push )
|
||||
#pragma warning( disable : 26492 )
|
||||
va_start( ap, fmt );
|
||||
#pragma warning( pop )
|
||||
_vsntprintf( buf, _countof(buf), fmt, ap );
|
||||
va_end( ap );
|
||||
buf[_countof(buf)-1] = 0;
|
||||
OutputDebugString( buf );
|
||||
}
|
||||
|
||||
#pragma comment(lib, "scrnsavw.lib")
|
||||
#pragma comment(lib, "comctl32.lib")
|
||||
#pragma comment(lib, "gdiplus.lib")
|
||||
#pragma comment(lib, "Msimg32.lib")
|
||||
#pragma comment(lib, "Winmm.lib")
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Globals
|
||||
//----------------------------------------------------------------------------
|
||||
static BreakTimerSettings g_Settings;
|
||||
static BreakTimerState g_State;
|
||||
static ULONG_PTR g_GdiplusToken;
|
||||
static TCHAR g_ScreenshotPath[MAX_PATH] = { 0 };
|
||||
static int g_LastSavedTimeout = 0; // For state persistence
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Load settings from the binary config file written by ZoomIt,
|
||||
// falling back to hard-coded defaults if the file is missing.
|
||||
//----------------------------------------------------------------------------
|
||||
static void LoadSettings( void )
|
||||
{
|
||||
BreakScrConfig config;
|
||||
if( BreakScrConfig_Read( &config ) )
|
||||
{
|
||||
g_Settings = config.settings;
|
||||
g_State.timeoutSeconds = config.timeoutSeconds;
|
||||
_tcscpy( g_ScreenshotPath, config.screenshotPath );
|
||||
DbgPrint( L"[BreakScr] Config loaded: timeout=%d, bgFile=%d, showDesktop=%d, screenshot=%s\n",
|
||||
config.timeoutSeconds, config.settings.showBackgroundFile,
|
||||
config.settings.showDesktop, config.screenshotPath );
|
||||
return;
|
||||
}
|
||||
|
||||
DbgPrint( L"[BreakScr] Config file not found, using fallback defaults\n" );
|
||||
// Fallback defaults (for testing the .scr directly).
|
||||
memset( &g_Settings, 0, sizeof( g_Settings ) );
|
||||
g_Settings.penColor = RGB( 255, 0, 0 );
|
||||
g_Settings.timerPosition = 4;
|
||||
g_Settings.opacity = 100;
|
||||
g_Settings.showExpiredTime = 1;
|
||||
g_Settings.smoothImage = TRUE;
|
||||
g_Settings.backgroundStretch = FALSE;
|
||||
g_Settings.showDesktop = TRUE;
|
||||
g_Settings.showBackgroundFile = FALSE;
|
||||
g_State.timeoutSeconds = 600;
|
||||
|
||||
NONCLIENTMETRICS ncm = { sizeof( ncm ) };
|
||||
SystemParametersInfo( SPI_GETNONCLIENTMETRICS, sizeof( ncm ), &ncm, 0 );
|
||||
g_Settings.logFont = ncm.lfMessageFont;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// ScreenSaverProc
|
||||
//
|
||||
// Main window procedure for the screensaver, called by Scrnsavw.lib.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
LRESULT WINAPI ScreenSaverProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
|
||||
{
|
||||
switch( msg )
|
||||
{
|
||||
case WM_CREATE:
|
||||
{
|
||||
DbgPrint( L"[BreakScr] WM_CREATE: hwnd=%p\n", hWnd );
|
||||
|
||||
// Initialize GDI+.
|
||||
Gdiplus::GdiplusStartupInput startupIn;
|
||||
Gdiplus::GdiplusStartup( &g_GdiplusToken, &startupIn, NULL );
|
||||
|
||||
LoadSettings();
|
||||
|
||||
// Check if a previous screensaver instance already ran (resumed == TRUE).
|
||||
// On first launch, ZoomIt sets resumed = FALSE, so we skip the deduction.
|
||||
BreakScrConfig resumeConfig;
|
||||
if( BreakScrConfig_Read( &resumeConfig ) && resumeConfig.resumed )
|
||||
{
|
||||
// Subtract the screensaver idle timeout to compensate for
|
||||
// the time the screensaver wasn't running on the lock screen.
|
||||
UINT scrTimeout = 0;
|
||||
SystemParametersInfo( SPI_GETSCREENSAVETIMEOUT, 0, &scrTimeout, 0 );
|
||||
g_State.timeoutSeconds -= static_cast<int>( scrTimeout );
|
||||
if( g_State.timeoutSeconds < 0 && !g_Settings.showExpiredTime )
|
||||
g_State.timeoutSeconds = 0;
|
||||
DbgPrint( L"[BreakScr] Resumption: subtracted %u sec idle, timeout=%d\n",
|
||||
scrTimeout, g_State.timeoutSeconds );
|
||||
}
|
||||
|
||||
// Mark as resumed so subsequent screensaver launches know to deduct idle time.
|
||||
{
|
||||
BreakScrConfig markConfig;
|
||||
if( BreakScrConfig_Read( &markConfig ) )
|
||||
{
|
||||
markConfig.resumed = TRUE;
|
||||
BreakScrConfig_Write( &markConfig );
|
||||
}
|
||||
}
|
||||
|
||||
// Load pre-captured screenshot if provided.
|
||||
HBITMAP hBgBmp = NULL;
|
||||
HDC hBgDC = NULL;
|
||||
if( g_ScreenshotPath[0] )
|
||||
{
|
||||
hBgBmp = BreakTimer_LoadImageFile( g_ScreenshotPath );
|
||||
DbgPrint( L"[BreakScr] LoadImageFile(%s) => %p\n", g_ScreenshotPath, hBgBmp );
|
||||
if( hBgBmp )
|
||||
{
|
||||
HDC hdcScreen = CreateDC( L"DISPLAY", NULL, NULL, NULL );
|
||||
hBgDC = CreateCompatibleDC( hdcScreen );
|
||||
SelectObject( hBgDC, hBgBmp );
|
||||
DeleteDC( hdcScreen );
|
||||
}
|
||||
}
|
||||
|
||||
int timeout = g_State.timeoutSeconds;
|
||||
memset( &g_State, 0, sizeof( g_State ) );
|
||||
|
||||
DbgPrint( L"[BreakScr] Calling BreakTimer_Init, timeout=%d\n", timeout );
|
||||
if( !BreakTimer_Init( hWnd, &g_State, &g_Settings, timeout, hBgBmp, hBgDC ) )
|
||||
{
|
||||
DbgPrint( L"[BreakScr] BreakTimer_Init FAILED\n" );
|
||||
PostMessage( hWnd, WM_CLOSE, 0, 0 );
|
||||
return 0;
|
||||
}
|
||||
DbgPrint( L"[BreakScr] BreakTimer_Init OK, active=%d\n", g_State.active );
|
||||
|
||||
// Prevent the monitor from blanking due to power management.
|
||||
SetThreadExecutionState( ES_CONTINUOUS | ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED );
|
||||
|
||||
// Kick off the first tick and start the 1-second timer.
|
||||
SendMessage( hWnd, WM_TIMER, 1, 0 );
|
||||
SetTimer( hWnd, 1, 1000, NULL );
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_TIMER:
|
||||
if( wParam == 1 )
|
||||
{
|
||||
BreakTimer_Tick( hWnd, &g_State, &g_Settings );
|
||||
|
||||
// Periodically save state (every 5 seconds) for resumption after
|
||||
// credential provider timeout. This allows the screensaver to continue
|
||||
// from where it left off if a student triggers the login screen but
|
||||
// doesn't authenticate.
|
||||
if( g_State.timeoutSeconds != g_LastSavedTimeout &&
|
||||
g_State.timeoutSeconds % 5 == 0 )
|
||||
{
|
||||
BreakScrConfig config;
|
||||
if( BreakScrConfig_Read( &config ) )
|
||||
{
|
||||
config.timeoutSeconds = g_State.timeoutSeconds;
|
||||
if( BreakScrConfig_Write( &config ) )
|
||||
{
|
||||
g_LastSavedTimeout = g_State.timeoutSeconds;
|
||||
DbgPrint( L"[BreakScr] Saved state: %d seconds remaining\n",
|
||||
g_State.timeoutSeconds );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
case WM_PAINT:
|
||||
{
|
||||
PAINTSTRUCT ps;
|
||||
HDC hdc = BeginPaint( hWnd, &ps );
|
||||
if( g_State.active )
|
||||
{
|
||||
BreakTimer_Paint( hdc, &g_State, &g_Settings );
|
||||
}
|
||||
EndPaint( hWnd, &ps );
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_DESTROY:
|
||||
DbgPrint( L"[BreakScr] WM_DESTROY\n" );
|
||||
SetThreadExecutionState( ES_CONTINUOUS ); // Restore default power behavior
|
||||
KillTimer( hWnd, 1 );
|
||||
BreakTimer_Cleanup( &g_State, TRUE );
|
||||
Gdiplus::GdiplusShutdown( g_GdiplusToken );
|
||||
return 0;
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Prevent DefScreenSaverProc from auto-closing on user input.
|
||||
// The screensaver must stay up until the break timer expires or
|
||||
// the user authenticates via Ctrl+Alt+Del. DefScreenSaverProc
|
||||
// would close the window on mouse movement, clicks, keyboard,
|
||||
// or deactivation.
|
||||
//------------------------------------------------------------------
|
||||
case WM_MOUSEMOVE:
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_RBUTTONDOWN:
|
||||
case WM_MBUTTONDOWN:
|
||||
case WM_KEYDOWN:
|
||||
if( wParam == 'W' || wParam == 'K' )
|
||||
{
|
||||
g_Settings.backgroundColor = ( wParam == 'K' ) ? 1 : 0;
|
||||
InvalidateRect( hWnd, NULL, FALSE );
|
||||
}
|
||||
return 0;
|
||||
case WM_KEYUP:
|
||||
case WM_SYSKEYDOWN:
|
||||
return 0;
|
||||
|
||||
case WM_ACTIVATE:
|
||||
case WM_ACTIVATEAPP:
|
||||
// Don't close on deactivation (e.g. LockWorkStation switches desktop).
|
||||
return 0;
|
||||
|
||||
case WM_SYSCOMMAND:
|
||||
// Block SC_CLOSE from Alt+F4 etc.
|
||||
if( ( wParam & 0xFFF0 ) == SC_CLOSE )
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return DefScreenSaverProc( hWnd, msg, wParam, lParam );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// ScreenSaverConfigureDialog
|
||||
//
|
||||
// No configuration — ZoomIt handles all settings.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
BOOL WINAPI ScreenSaverConfigureDialog( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// RegisterDialogClasses
|
||||
//
|
||||
// Nothing to register.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
BOOL WINAPI RegisterDialogClasses( HANDLE hInst )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
@@ -91,3 +91,12 @@ void Trace::ZoomItActivateSnip() noexcept
|
||||
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
|
||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
|
||||
}
|
||||
|
||||
void Trace::ZoomItActivateSnipOcr() noexcept
|
||||
{
|
||||
TraceLoggingWriteWrapper(
|
||||
g_hProvider,
|
||||
"ZoomIt_ActivateSnipOcr",
|
||||
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
|
||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
|
||||
}
|
||||
|
||||
@@ -14,4 +14,5 @@ public:
|
||||
static void ZoomItActivateDemoType() noexcept;
|
||||
static void ZoomItActivateRecord() noexcept;
|
||||
static void ZoomItActivateSnip() noexcept;
|
||||
static void ZoomItActivateSnipOcr() noexcept;
|
||||
};
|
||||
|
||||
@@ -70,6 +70,8 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
|
||||
{ L"DrawToggleKey", SPECIAL_SEMANTICS_SHORTCUT },
|
||||
{ L"RecordToggleKey", SPECIAL_SEMANTICS_SHORTCUT },
|
||||
{ L"SnipToggleKey", SPECIAL_SEMANTICS_SHORTCUT },
|
||||
{ L"SnipOcrToggleKey", SPECIAL_SEMANTICS_SHORTCUT },
|
||||
{ L"SnipPanoramaToggleKey", SPECIAL_SEMANTICS_SHORTCUT },
|
||||
{ L"BreakTimerKey", SPECIAL_SEMANTICS_SHORTCUT },
|
||||
{ L"DemoTypeToggleKey", SPECIAL_SEMANTICS_SHORTCUT },
|
||||
{ L"PenColor", SPECIAL_SEMANTICS_COLOR },
|
||||
|
||||
@@ -76,7 +76,7 @@ bool isExcluded(HWND window)
|
||||
}
|
||||
|
||||
AlwaysOnTop::AlwaysOnTop(bool useLLKH, DWORD mainThreadId) :
|
||||
SettingsObserver({SettingId::FrameEnabled, SettingId::Hotkey, SettingId::ExcludeApps, SettingId::ShowInSystemMenu}),
|
||||
SettingsObserver({ SettingId::FrameEnabled, SettingId::Hotkey, SettingId::IncreaseOpacityHotkey, SettingId::DecreaseOpacityHotkey, SettingId::ExcludeApps, SettingId::ShowInSystemMenu }),
|
||||
m_hinstance(reinterpret_cast<HINSTANCE>(&__ImageBase)),
|
||||
m_useCentralizedLLKH(useLLKH),
|
||||
m_mainThreadId(mainThreadId),
|
||||
@@ -150,6 +150,8 @@ void AlwaysOnTop::SettingsUpdate(SettingId id)
|
||||
switch (id)
|
||||
{
|
||||
case SettingId::Hotkey:
|
||||
case SettingId::IncreaseOpacityHotkey:
|
||||
case SettingId::DecreaseOpacityHotkey:
|
||||
{
|
||||
RegisterHotkey();
|
||||
}
|
||||
@@ -360,10 +362,8 @@ void AlwaysOnTop::RegisterHotkey() const
|
||||
// Register pin hotkey
|
||||
RegisterHotKey(m_window, static_cast<int>(HotkeyId::Pin), settings->hotkey.get_modifiers(), settings->hotkey.get_code());
|
||||
|
||||
// Register transparency hotkeys using the same modifiers as the pin hotkey
|
||||
UINT modifiers = settings->hotkey.get_modifiers();
|
||||
RegisterHotKey(m_window, static_cast<int>(HotkeyId::IncreaseOpacity), modifiers, VK_OEM_PLUS);
|
||||
RegisterHotKey(m_window, static_cast<int>(HotkeyId::DecreaseOpacity), modifiers, VK_OEM_MINUS);
|
||||
RegisterHotKey(m_window, static_cast<int>(HotkeyId::IncreaseOpacity), settings->increaseOpacityHotkey.get_modifiers(), settings->increaseOpacityHotkey.get_code());
|
||||
RegisterHotKey(m_window, static_cast<int>(HotkeyId::DecreaseOpacity), settings->decreaseOpacityHotkey.get_modifiers(), settings->decreaseOpacityHotkey.get_code());
|
||||
}
|
||||
|
||||
void AlwaysOnTop::RegisterLLKH()
|
||||
|
||||
@@ -13,6 +13,8 @@ namespace NonLocalizable
|
||||
const static wchar_t* SettingsFileName = L"settings.json";
|
||||
|
||||
const static wchar_t* HotkeyID = L"hotkey";
|
||||
const static wchar_t* IncreaseOpacityHotkeyID = L"increase-opacity-hotkey";
|
||||
const static wchar_t* DecreaseOpacityHotkeyID = L"decrease-opacity-hotkey";
|
||||
const static wchar_t* SoundEnabledID = L"sound-enabled";
|
||||
const static wchar_t* ShowInSystemMenuID = L"show-in-system-menu";
|
||||
const static wchar_t* FrameEnabledID = L"frame-enabled";
|
||||
@@ -100,16 +102,21 @@ void AlwaysOnTopSettings::LoadSettings()
|
||||
const auto currentSettings = AlwaysOnTopSettings::settings();
|
||||
auto updatedSettings = std::make_shared<Settings>(*currentSettings);
|
||||
std::vector<SettingId> changedSettings;
|
||||
|
||||
if (const auto jsonVal = values.get_json(NonLocalizable::HotkeyID))
|
||||
{
|
||||
auto val = PowerToysSettings::HotkeyObject::from_json(*jsonVal);
|
||||
if (updatedSettings->hotkey.get_modifiers() != val.get_modifiers() || updatedSettings->hotkey.get_key() != val.get_key() || updatedSettings->hotkey.get_code() != val.get_code())
|
||||
const auto updateHotkeySetting = [&](const wchar_t* hotkeyName, auto& currentHotkey, SettingId settingId) {
|
||||
if (const auto jsonVal = values.get_json(hotkeyName))
|
||||
{
|
||||
updatedSettings->hotkey = val;
|
||||
changedSettings.push_back(SettingId::Hotkey);
|
||||
auto val = PowerToysSettings::HotkeyObject::from_json(*jsonVal);
|
||||
if (currentHotkey.get_modifiers() != val.get_modifiers() || currentHotkey.get_key() != val.get_key() || currentHotkey.get_code() != val.get_code())
|
||||
{
|
||||
currentHotkey = val;
|
||||
changedSettings.push_back(settingId);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
updateHotkeySetting(NonLocalizable::HotkeyID, updatedSettings->hotkey, SettingId::Hotkey);
|
||||
updateHotkeySetting(NonLocalizable::IncreaseOpacityHotkeyID, updatedSettings->increaseOpacityHotkey, SettingId::IncreaseOpacityHotkey);
|
||||
updateHotkeySetting(NonLocalizable::DecreaseOpacityHotkeyID, updatedSettings->decreaseOpacityHotkey, SettingId::DecreaseOpacityHotkey);
|
||||
|
||||
if (const auto jsonVal = values.get_bool_value(NonLocalizable::SoundEnabledID))
|
||||
{
|
||||
|
||||
@@ -18,6 +18,8 @@ class SettingsObserver;
|
||||
struct Settings
|
||||
{
|
||||
PowerToysSettings::HotkeyObject hotkey = PowerToysSettings::HotkeyObject::from_settings(true, true, false, false, 84); // win + ctrl + T
|
||||
PowerToysSettings::HotkeyObject increaseOpacityHotkey = PowerToysSettings::HotkeyObject::from_settings(true, true, false, false, VK_OEM_PLUS); // win + ctrl + '+'
|
||||
PowerToysSettings::HotkeyObject decreaseOpacityHotkey = PowerToysSettings::HotkeyObject::from_settings(true, true, false, false, VK_OEM_MINUS); // win + ctrl + '-'
|
||||
static constexpr int minTransparencyPercentage = 20; // minimum transparency (can't go below 20%)
|
||||
static constexpr int maxTransparencyPercentage = 100; // maximum (fully opaque)
|
||||
static constexpr int transparencyStep = 10; // step size for +/- adjustment
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
enum class SettingId
|
||||
{
|
||||
Hotkey = 0,
|
||||
IncreaseOpacityHotkey,
|
||||
DecreaseOpacityHotkey,
|
||||
SoundEnabled,
|
||||
ShowInSystemMenu,
|
||||
FrameEnabled,
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
namespace NonLocalizable
|
||||
{
|
||||
const wchar_t ModulePath[] = L"PowerToys.AlwaysOnTop.exe";
|
||||
// Keep in sync with src\modules\alwaysontop\AlwaysOnTop\AlwaysOnTop.cpp
|
||||
const wchar_t PinnedWindowProp[] = L"AlwaysOnTop_Pinned";
|
||||
}
|
||||
|
||||
namespace
|
||||
@@ -27,6 +29,8 @@ namespace
|
||||
const wchar_t JSON_KEY_SHIFT[] = L"shift";
|
||||
const wchar_t JSON_KEY_CODE[] = L"code";
|
||||
const wchar_t JSON_KEY_HOTKEY[] = L"hotkey";
|
||||
const wchar_t JSON_KEY_INCREASE_OPACITY_HOTKEY[] = L"increase-opacity-hotkey";
|
||||
const wchar_t JSON_KEY_DECREASE_OPACITY_HOTKEY[] = L"decrease-opacity-hotkey";
|
||||
const wchar_t JSON_KEY_VALUE[] = L"value";
|
||||
}
|
||||
|
||||
@@ -107,27 +111,38 @@ public:
|
||||
|
||||
virtual bool on_hotkey(size_t hotkeyId) override
|
||||
{
|
||||
if (m_enabled)
|
||||
if (!m_enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Logger::trace(L"AlwaysOnTop hotkey pressed, id={}", hotkeyId);
|
||||
|
||||
if (hotkeyId == 0)
|
||||
{
|
||||
Logger::trace(L"AlwaysOnTop hotkey pressed, id={}", hotkeyId);
|
||||
if (!is_process_running())
|
||||
{
|
||||
Enable();
|
||||
}
|
||||
|
||||
if (hotkeyId == 0)
|
||||
SetEvent(m_hPinEvent);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hotkeyId == 1 || hotkeyId == 2)
|
||||
{
|
||||
const HWND foregroundWindow = GetForegroundWindow();
|
||||
if (!foregroundWindow || !IsWindow(foregroundWindow) || !GetPropW(foregroundWindow, NonLocalizable::PinnedWindowProp))
|
||||
{
|
||||
SetEvent(m_hPinEvent);
|
||||
}
|
||||
else if (hotkeyId == 1)
|
||||
{
|
||||
SetEvent(m_hIncreaseOpacityEvent);
|
||||
}
|
||||
else if (hotkeyId == 2)
|
||||
{
|
||||
SetEvent(m_hDecreaseOpacityEvent);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_process_running())
|
||||
{
|
||||
Enable();
|
||||
}
|
||||
|
||||
SetEvent(hotkeyId == 1 ? m_hIncreaseOpacityEvent : m_hDecreaseOpacityEvent);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -136,48 +151,48 @@ public:
|
||||
|
||||
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
|
||||
{
|
||||
size_t count = 0;
|
||||
|
||||
// Hotkey 0: Pin/Unpin (e.g., Win+Ctrl+T)
|
||||
if (m_hotkey.key)
|
||||
constexpr size_t hotkeyCount = 3;
|
||||
Hotkey configuredHotkeys[hotkeyCount] = { m_hotkey, m_increaseOpacityHotkey, m_decreaseOpacityHotkey };
|
||||
|
||||
for (size_t i = 0; i < hotkeyCount; ++i)
|
||||
{
|
||||
if (hotkeys && buffer_size > count)
|
||||
{
|
||||
hotkeys[count] = m_hotkey;
|
||||
Logger::trace(L"AlwaysOnTop hotkey[0]: win={}, ctrl={}, shift={}, alt={}, key={}",
|
||||
m_hotkey.win, m_hotkey.ctrl, m_hotkey.shift, m_hotkey.alt, m_hotkey.key);
|
||||
}
|
||||
count++;
|
||||
configuredHotkeys[i].id = static_cast<int>(i);
|
||||
configuredHotkeys[i].isShown = configuredHotkeys[i].key != 0;
|
||||
}
|
||||
|
||||
// Hotkey 1: Increase opacity (same modifiers + VK_OEM_PLUS '=')
|
||||
if (m_hotkey.key)
|
||||
if (hotkeys)
|
||||
{
|
||||
if (hotkeys && buffer_size > count)
|
||||
const size_t countToCopy = (buffer_size < hotkeyCount) ? buffer_size : hotkeyCount;
|
||||
for (size_t i = 0; i < countToCopy; ++i)
|
||||
{
|
||||
hotkeys[count] = m_hotkey;
|
||||
hotkeys[count].key = VK_OEM_PLUS; // '=' key
|
||||
Logger::trace(L"AlwaysOnTop hotkey[1] (increase opacity): win={}, ctrl={}, shift={}, alt={}, key={}",
|
||||
hotkeys[count].win, hotkeys[count].ctrl, hotkeys[count].shift, hotkeys[count].alt, hotkeys[count].key);
|
||||
hotkeys[i] = configuredHotkeys[i];
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
// Hotkey 2: Decrease opacity (same modifiers + VK_OEM_MINUS '-')
|
||||
if (m_hotkey.key)
|
||||
{
|
||||
if (hotkeys && buffer_size > count)
|
||||
{
|
||||
hotkeys[count] = m_hotkey;
|
||||
hotkeys[count].key = VK_OEM_MINUS; // '-' key
|
||||
Logger::trace(L"AlwaysOnTop hotkey[2] (decrease opacity): win={}, ctrl={}, shift={}, alt={}, key={}",
|
||||
hotkeys[count].win, hotkeys[count].ctrl, hotkeys[count].shift, hotkeys[count].alt, hotkeys[count].key);
|
||||
}
|
||||
count++;
|
||||
}
|
||||
Logger::trace(L"AlwaysOnTop hotkey[0]: win={}, ctrl={}, shift={}, alt={}, key={}, shown={}",
|
||||
configuredHotkeys[0].win,
|
||||
configuredHotkeys[0].ctrl,
|
||||
configuredHotkeys[0].shift,
|
||||
configuredHotkeys[0].alt,
|
||||
configuredHotkeys[0].key,
|
||||
configuredHotkeys[0].isShown);
|
||||
Logger::trace(L"AlwaysOnTop hotkey[1] (increase opacity): win={}, ctrl={}, shift={}, alt={}, key={}, shown={}",
|
||||
configuredHotkeys[1].win,
|
||||
configuredHotkeys[1].ctrl,
|
||||
configuredHotkeys[1].shift,
|
||||
configuredHotkeys[1].alt,
|
||||
configuredHotkeys[1].key,
|
||||
configuredHotkeys[1].isShown);
|
||||
Logger::trace(L"AlwaysOnTop hotkey[2] (decrease opacity): win={}, ctrl={}, shift={}, alt={}, key={}, shown={}",
|
||||
configuredHotkeys[2].win,
|
||||
configuredHotkeys[2].ctrl,
|
||||
configuredHotkeys[2].shift,
|
||||
configuredHotkeys[2].alt,
|
||||
configuredHotkeys[2].key,
|
||||
configuredHotkeys[2].isShown);
|
||||
|
||||
Logger::trace(L"AlwaysOnTop get_hotkeys returning count={}", count);
|
||||
return count;
|
||||
Logger::trace(L"AlwaysOnTop get_hotkeys returning count={}", hotkeyCount);
|
||||
return hotkeyCount;
|
||||
}
|
||||
|
||||
// Enable the powertoy
|
||||
@@ -279,21 +294,34 @@ private:
|
||||
|
||||
void parse_hotkey(PowerToysSettings::PowerToyValues& settings)
|
||||
{
|
||||
const auto parseSingleHotkey = [](const winrt::Windows::Data::Json::JsonObject& propertiesObject, const wchar_t* hotkeyName, Hotkey& hotkey) {
|
||||
try
|
||||
{
|
||||
auto jsonHotkeyObject = propertiesObject.GetNamedObject(hotkeyName).GetNamedObject(JSON_KEY_VALUE);
|
||||
hotkey.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
|
||||
hotkey.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
|
||||
hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
|
||||
hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
|
||||
hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
auto settingsObject = settings.get_raw_json();
|
||||
if (settingsObject.GetView().Size())
|
||||
{
|
||||
try
|
||||
{
|
||||
auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_HOTKEY).GetNamedObject(JSON_KEY_VALUE);
|
||||
m_hotkey.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
|
||||
m_hotkey.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
|
||||
m_hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
|
||||
m_hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
|
||||
m_hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
|
||||
auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES);
|
||||
parseSingleHotkey(propertiesObject, JSON_KEY_HOTKEY, m_hotkey);
|
||||
parseSingleHotkey(propertiesObject, JSON_KEY_INCREASE_OPACITY_HOTKEY, m_increaseOpacityHotkey);
|
||||
parseSingleHotkey(propertiesObject, JSON_KEY_DECREASE_OPACITY_HOTKEY, m_decreaseOpacityHotkey);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::error("Failed to initialize AlwaysOnTop start shortcut");
|
||||
Logger::error("Failed to initialize AlwaysOnTop shortcuts");
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -329,7 +357,9 @@ private:
|
||||
|
||||
bool m_enabled = false;
|
||||
HANDLE m_hProcess = nullptr;
|
||||
Hotkey m_hotkey;
|
||||
Hotkey m_hotkey{ .win = true, .ctrl = true, .shift = false, .alt = false, .key = 'T' };
|
||||
Hotkey m_increaseOpacityHotkey{ .win = true, .ctrl = true, .shift = false, .alt = false, .key = VK_OEM_PLUS };
|
||||
Hotkey m_decreaseOpacityHotkey{ .win = true, .ctrl = true, .shift = false, .alt = false, .key = VK_OEM_MINUS };
|
||||
|
||||
// Handle to event used to pin/unpin windows
|
||||
HANDLE m_hPinEvent;
|
||||
|
||||
@@ -213,22 +213,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
|
||||
BuildAndInitMoreCommands();
|
||||
|
||||
if (!string.IsNullOrEmpty(model.Command?.Name))
|
||||
{
|
||||
_defaultCommandContextItemViewModel = new CommandContextItemViewModel(new CommandContextItem(model.Command!), PageContext)
|
||||
{
|
||||
_itemTitle = Name,
|
||||
Subtitle = Subtitle,
|
||||
Command = Command,
|
||||
|
||||
// TODO this probably should just be a CommandContextItemViewModel(CommandItemViewModel) ctor, or a copy ctor or whatever
|
||||
// Anything we set manually here must stay in sync with the corresponding properties on CommandItemViewModel.
|
||||
};
|
||||
|
||||
// Only set the icon on the context item for us if our command didn't
|
||||
// have its own icon
|
||||
UpdateDefaultContextItemIcon();
|
||||
}
|
||||
TryCreateDefaultCommandContextItem(model);
|
||||
|
||||
lock (_moreCommandsLock)
|
||||
{
|
||||
@@ -238,6 +223,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
Initialized |= InitializedState.SelectionInitialized;
|
||||
UpdateProperty(nameof(MoreCommands));
|
||||
UpdateProperty(nameof(AllCommands));
|
||||
UpdateProperty(nameof(SecondaryCommand), nameof(SecondaryCommandName), nameof(HasMoreCommands));
|
||||
UpdateProperty(nameof(IsSelectedInitialized));
|
||||
}
|
||||
|
||||
@@ -335,9 +321,16 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
// or Command.Name change. This is a workaround to ensure that the Title is always up-to-date for extensions with old SDK.
|
||||
_itemTitle = model.Title;
|
||||
|
||||
_defaultCommandContextItemViewModel?.Command = Command;
|
||||
_defaultCommandContextItemViewModel?.UpdateTitle(_itemTitle);
|
||||
UpdateDefaultContextItemIcon();
|
||||
if (_defaultCommandContextItemViewModel is not null)
|
||||
{
|
||||
_defaultCommandContextItemViewModel.Command = Command;
|
||||
_defaultCommandContextItemViewModel.UpdateTitle(_itemTitle);
|
||||
UpdateDefaultContextItemIcon();
|
||||
}
|
||||
else
|
||||
{
|
||||
TryCreateDefaultCommandContextItem(model);
|
||||
}
|
||||
|
||||
UpdateProperty(nameof(Name));
|
||||
UpdateProperty(nameof(Title));
|
||||
@@ -403,7 +396,15 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
_titleCache.Invalidate();
|
||||
UpdateProperty(nameof(Title), nameof(Name));
|
||||
|
||||
_defaultCommandContextItemViewModel?.UpdateTitle(model.Command.Name);
|
||||
if (_defaultCommandContextItemViewModel is not null)
|
||||
{
|
||||
_defaultCommandContextItemViewModel.UpdateTitle(model.Command.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
TryCreateDefaultCommandContextItem(model);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case nameof(Command.Icon):
|
||||
@@ -413,6 +414,46 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates <see cref="_defaultCommandContextItemViewModel"/> when it does not exist
|
||||
/// yet and the current command has a non-empty name. This covers the case
|
||||
/// where an extension initially exposes a <c>NoOpCommand</c> (empty name)
|
||||
/// and later switches to a concrete command after <see cref="SlowInitializeProperties"/> has already run.
|
||||
/// When a new instance is created, the snapshot is refreshed and
|
||||
/// <see cref="AllCommands"/> is notified.
|
||||
/// </summary>
|
||||
private void TryCreateDefaultCommandContextItem(ICommandItem model)
|
||||
{
|
||||
if (_defaultCommandContextItemViewModel is not null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(model.Command?.Name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_defaultCommandContextItemViewModel = new CommandContextItemViewModel(new CommandContextItem(model.Command!), PageContext)
|
||||
{
|
||||
_itemTitle = Name,
|
||||
Subtitle = Subtitle,
|
||||
Command = Command,
|
||||
|
||||
// TODO this probably should just be a CommandContextItemViewModel(CommandItemViewModel) ctor, or a copy ctor or whatever
|
||||
// Anything we set manually here must stay in sync with the corresponding properties on CommandItemViewModel.
|
||||
};
|
||||
|
||||
UpdateDefaultContextItemIcon();
|
||||
|
||||
lock (_moreCommandsLock)
|
||||
{
|
||||
RefreshMoreCommandStateUnsafe();
|
||||
}
|
||||
|
||||
UpdateProperty(nameof(AllCommands));
|
||||
}
|
||||
|
||||
private void UpdateDefaultContextItemIcon() =>
|
||||
|
||||
// Command icon takes precedence over our icon on the primary command
|
||||
|
||||
@@ -301,8 +301,19 @@ public sealed class CommandProviderWrapper : ICommandProviderContext
|
||||
var dockSettings = settings.DockSettings;
|
||||
var allPinnedCommands = dockSettings.AllPinnedCommands;
|
||||
var pinnedBandsForThisProvider = allPinnedCommands.Where(c => c.ProviderId == ProviderId);
|
||||
|
||||
// Track which command IDs we've already added to avoid duplicates
|
||||
// from settings that were pinned multiple times.
|
||||
HashSet<string> seenCommandIds = new(bands.Select(b => b.Id));
|
||||
|
||||
foreach (var (providerId, commandId) in pinnedBandsForThisProvider)
|
||||
{
|
||||
if (!seenCommandIds.Add(commandId))
|
||||
{
|
||||
Logger.LogWarning($"Skipping duplicate pinned dock band command {commandId} for provider {providerId}");
|
||||
continue;
|
||||
}
|
||||
|
||||
Logger.LogDebug($"Looking for pinned dock band command {commandId} for provider {providerId}");
|
||||
|
||||
// First, try to lookup the command as one of this provider's
|
||||
@@ -436,16 +447,42 @@ public sealed class CommandProviderWrapper : ICommandProviderContext
|
||||
}
|
||||
}
|
||||
|
||||
public void PinDockBand(string commandId, IServiceProvider serviceProvider)
|
||||
public void PinDockBand(string commandId, IServiceProvider serviceProvider, Dock.DockPinSide side = Dock.DockPinSide.Start, bool? showTitles = null, bool? showSubtitles = null)
|
||||
{
|
||||
var settingsService = serviceProvider.GetRequiredService<ISettingsService>();
|
||||
var settings = settingsService.Settings;
|
||||
var dockSettings = settings.DockSettings;
|
||||
|
||||
// Prevent duplicate pins — check all sections
|
||||
if (dockSettings.StartBands.Any(b => b.CommandId == commandId && b.ProviderId == this.ProviderId) ||
|
||||
dockSettings.CenterBands.Any(b => b.CommandId == commandId && b.ProviderId == this.ProviderId) ||
|
||||
dockSettings.EndBands.Any(b => b.CommandId == commandId && b.ProviderId == this.ProviderId))
|
||||
{
|
||||
Logger.LogDebug($"Dock band '{commandId}' from provider '{this.ProviderId}' is already pinned; skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
var bandSettings = new DockBandSettings
|
||||
{
|
||||
CommandId = commandId,
|
||||
ProviderId = this.ProviderId,
|
||||
ShowTitles = showTitles,
|
||||
ShowSubtitles = showSubtitles,
|
||||
};
|
||||
settings.DockSettings.StartBands.Add(bandSettings);
|
||||
|
||||
switch (side)
|
||||
{
|
||||
case Dock.DockPinSide.Center:
|
||||
settings.DockSettings.CenterBands.Add(bandSettings);
|
||||
break;
|
||||
case Dock.DockPinSide.End:
|
||||
settings.DockSettings.EndBands.Add(bandSettings);
|
||||
break;
|
||||
case Dock.DockPinSide.Start:
|
||||
default:
|
||||
settings.DockSettings.StartBands.Add(bandSettings);
|
||||
break;
|
||||
}
|
||||
|
||||
// Raise CommandsChanged so the TopLevelCommandManager reloads our commands
|
||||
this.CommandsChanged?.Invoke(this, new ItemsChangedEventArgs(-1));
|
||||
|
||||
@@ -365,9 +365,9 @@ public sealed partial class MainListPage : DynamicListPage,
|
||||
}
|
||||
|
||||
// prefilter fallbacks
|
||||
var globalFallbacks = _settingsService.Settings.GetGlobalFallbacks();
|
||||
var specialFallbacks = new List<TopLevelViewModel>(globalFallbacks.Length);
|
||||
var commonFallbacks = new List<TopLevelViewModel>(commands.Count - globalFallbacks.Length);
|
||||
var configuredGlobalFallbackIds = _settingsService.Settings.GetGlobalFallbacks();
|
||||
var specialFallbacks = new List<TopLevelViewModel>(configuredGlobalFallbackIds.Length);
|
||||
var commonFallbacks = new List<TopLevelViewModel>(Math.Max(commands.Count - configuredGlobalFallbackIds.Length, 0));
|
||||
|
||||
foreach (var s in commands)
|
||||
{
|
||||
@@ -376,7 +376,7 @@ public sealed partial class MainListPage : DynamicListPage,
|
||||
continue;
|
||||
}
|
||||
|
||||
if (globalFallbacks.Contains(s.Id))
|
||||
if (configuredGlobalFallbackIds.Contains(s.Id))
|
||||
{
|
||||
specialFallbacks.Add(s);
|
||||
}
|
||||
@@ -509,7 +509,7 @@ public sealed partial class MainListPage : DynamicListPage,
|
||||
return;
|
||||
}
|
||||
|
||||
IEnumerable<IListItem> newFallbacksForScoring = commands.Where(s => s.IsFallback && globalFallbacks.Contains(s.Id));
|
||||
IEnumerable<IListItem> newFallbacksForScoring = commands.Where(s => s.IsFallback && configuredGlobalFallbackIds.Contains(s.Id));
|
||||
_scoredFallbackItems = InternalListHelpers.FilterListWithScores(newFallbacksForScoring, searchQuery, _scoringFunction);
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
|
||||
@@ -21,6 +21,7 @@ public sealed partial class DockViewModel
|
||||
private readonly IContextMenuFactory _contextMenuFactory;
|
||||
|
||||
private DockSettings _settings;
|
||||
private bool _isEditing;
|
||||
|
||||
public TaskScheduler Scheduler { get; }
|
||||
|
||||
@@ -52,6 +53,12 @@ public sealed partial class DockViewModel
|
||||
|
||||
private void DockBands_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (_isEditing)
|
||||
{
|
||||
Logger.LogDebug("Skipping DockBands_CollectionChanged during edit mode");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogDebug("Starting DockBands_CollectionChanged");
|
||||
SetupBands();
|
||||
Logger.LogDebug("Ended DockBands_CollectionChanged");
|
||||
@@ -302,7 +309,12 @@ public sealed partial class DockViewModel
|
||||
_snapshotCenterBands = null;
|
||||
_snapshotEndBands = null;
|
||||
_snapshotBandViewModels = null;
|
||||
_settingsService.Save();
|
||||
|
||||
// Save without hotReload to avoid triggering SettingsChanged → SetupBands,
|
||||
// which could race with stale DockBands_CollectionChanged work items and
|
||||
// re-add bands that were just unpinned.
|
||||
_settingsService.Save(hotReload: false);
|
||||
_isEditing = false;
|
||||
Logger.LogDebug("Saved band order to settings");
|
||||
}
|
||||
|
||||
@@ -317,6 +329,8 @@ public sealed partial class DockViewModel
|
||||
/// </summary>
|
||||
public void SnapshotBandOrder()
|
||||
{
|
||||
_isEditing = true;
|
||||
|
||||
var dockSettings = _settingsService.Settings.DockSettings;
|
||||
_snapshotStartBands = dockSettings.StartBands.Select(b => b.Clone()).ToList();
|
||||
_snapshotCenterBands = dockSettings.CenterBands.Select(b => b.Clone()).ToList();
|
||||
@@ -391,6 +405,7 @@ public sealed partial class DockViewModel
|
||||
_snapshotCenterBands = null;
|
||||
_snapshotEndBands = null;
|
||||
_snapshotBandViewModels = null;
|
||||
_isEditing = false;
|
||||
Logger.LogDebug("Restored band order from snapshot");
|
||||
}
|
||||
|
||||
|
||||
@@ -214,6 +214,7 @@ public partial class ListItemViewModel : CommandItemViewModel
|
||||
if (addedCommand)
|
||||
{
|
||||
UpdateProperty(nameof(MoreCommands), nameof(AllCommands));
|
||||
UpdateProperty(nameof(SecondaryCommand), nameof(SecondaryCommandName), nameof(HasMoreCommands));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -252,6 +253,7 @@ public partial class ListItemViewModel : CommandItemViewModel
|
||||
oldCommand?.SafeCleanup();
|
||||
|
||||
UpdateProperty(nameof(MoreCommands), nameof(AllCommands));
|
||||
UpdateProperty(nameof(SecondaryCommand), nameof(SecondaryCommandName), nameof(HasMoreCommands));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
// 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.CmdPal.UI.ViewModels.Dock;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
|
||||
public record PinToDockMessage(string ProviderId, string CommandId, bool Pin);
|
||||
public record PinToDockMessage(
|
||||
string ProviderId,
|
||||
string CommandId,
|
||||
bool Pin,
|
||||
DockPinSide Side = DockPinSide.Start,
|
||||
bool? ShowTitles = null,
|
||||
bool? ShowSubtitles = null);
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// 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.CmdPal.UI.ViewModels.Settings;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
|
||||
public record ShowPinToDockDialogMessage(
|
||||
string ProviderId,
|
||||
string CommandId,
|
||||
string Title,
|
||||
string Subtitle,
|
||||
IconInfoViewModel? Icon,
|
||||
DockSide DockSide);
|
||||
@@ -261,8 +261,20 @@ public partial class ShellViewModel : ObservableObject,
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine whether this is the root/home page navigation BEFORE
|
||||
// computing providerContext. When navigating back to the root page we
|
||||
// must use an empty provider context so that home-page list items don't
|
||||
// inherit a pinning-capable context left over from the previous sub-page
|
||||
// (which can happen e.g. when the window is hidden while on a sub-page).
|
||||
// isMainPage must be evaluated here; if it were moved inside the
|
||||
// "if (command is IPage)" block below, it would be too late to affect
|
||||
// the providerContext that is passed to the new page view-model.
|
||||
var isMainPage = command == _rootPage;
|
||||
|
||||
var host = _appHostService.GetHostForCommand(message.Context, CurrentPage.ExtensionHost);
|
||||
var providerContext = _appHostService.GetProviderContextForCommand(message.Context, CurrentPage.ProviderContext);
|
||||
var providerContext = isMainPage
|
||||
? CommandProviderContext.Empty
|
||||
: _appHostService.GetProviderContextForCommand(message.Context, CurrentPage.ProviderContext);
|
||||
|
||||
_rootPageService.OnPerformCommand(message.Context, CurrentPage.IsRootPage, host);
|
||||
|
||||
@@ -272,7 +284,6 @@ public partial class ShellViewModel : ObservableObject,
|
||||
{
|
||||
CoreLogger.LogDebug($"Navigating to page");
|
||||
|
||||
var isMainPage = command == _rootPage;
|
||||
_isNested = !isMainPage;
|
||||
_currentlyTransient = message.TransientPage;
|
||||
|
||||
|
||||
@@ -707,7 +707,7 @@ public sealed partial class TopLevelCommandManager : ObservableObject,
|
||||
{
|
||||
if (message.Pin)
|
||||
{
|
||||
wrapper?.PinDockBand(message.CommandId, _serviceProvider);
|
||||
wrapper?.PinDockBand(message.CommandId, _serviceProvider, message.Side, message.ShowTitles, message.ShowSubtitles);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Ext.Apps;
|
||||
using Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
@@ -56,8 +58,32 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
|
||||
List<IContextItem> moreCommands = [];
|
||||
var itemId = commandItem.Command.Id;
|
||||
var providerContext = page.ProviderContext;
|
||||
|
||||
// AppListItems can be surfaced on the main page even though they still
|
||||
// belong to the All Apps provider.
|
||||
// MainListPage only returns our in-proc wrappers/items.
|
||||
if (providerContext.ProviderId != AllAppsCommandProvider.WellKnownId &&
|
||||
page is ListViewModel { IsMainPage: true } &&
|
||||
commandItem.Model.Unsafe is AppListItem)
|
||||
{
|
||||
providerContext = _topLevelCommandManager.LookupProvider(AllAppsCommandProvider.WellKnownId)?.GetProviderContext() ?? providerContext;
|
||||
}
|
||||
|
||||
var supportsPinning = providerContext.SupportsPinning;
|
||||
|
||||
// Also bail early for ListItemViewModels that wrap a TopLevelViewModel.
|
||||
// For those items, TopLevelViewModel.BuildContextMenu() already includes
|
||||
// the correct pin commands by calling AddMoreCommandsToTopLevel with the
|
||||
// item's own provider context. Adding them again here (using the page's
|
||||
// potentially incorrect provider context) would produce duplicate pin
|
||||
// entries such as two "Pin to Dock" buttons.
|
||||
// Check SupportsPinning first to avoid the .Unsafe type-check in the
|
||||
// common non-pinning case.
|
||||
if (supportsPinning && commandItem.Model.Unsafe is TopLevelViewModel)
|
||||
{
|
||||
return results;
|
||||
}
|
||||
|
||||
if (supportsPinning &&
|
||||
!string.IsNullOrEmpty(itemId))
|
||||
{
|
||||
@@ -185,7 +211,8 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
|
||||
pin: !alreadyPinned,
|
||||
PinLocation.Dock,
|
||||
_settingsService,
|
||||
_topLevelCommandManager);
|
||||
_topLevelCommandManager,
|
||||
commandItemViewModel: commandItem);
|
||||
|
||||
var contextItem = new PinToContextItem(pinToTopLevelCommand, commandItem);
|
||||
moreCommands.Add(contextItem);
|
||||
@@ -236,6 +263,7 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
|
||||
private readonly TopLevelCommandManager _topLevelCommandManager;
|
||||
private readonly bool _pin;
|
||||
private readonly PinLocation _pinLocation;
|
||||
private readonly CommandItemViewModel? _commandItemViewModel;
|
||||
|
||||
private bool IsPinToDock => _pinLocation == PinLocation.Dock;
|
||||
|
||||
@@ -253,7 +281,8 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
|
||||
bool pin,
|
||||
PinLocation pinLocation,
|
||||
ISettingsService settingsService,
|
||||
TopLevelCommandManager topLevelCommandManager)
|
||||
TopLevelCommandManager topLevelCommandManager,
|
||||
CommandItemViewModel? commandItemViewModel = null)
|
||||
{
|
||||
_commandId = commandId;
|
||||
_providerId = providerId;
|
||||
@@ -261,6 +290,7 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
|
||||
_settingsService = settingsService;
|
||||
_topLevelCommandManager = topLevelCommandManager;
|
||||
_pin = pin;
|
||||
_commandItemViewModel = commandItemViewModel;
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
@@ -312,7 +342,11 @@ internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFac
|
||||
|
||||
private void PinToDock()
|
||||
{
|
||||
PinToDockMessage message = new(_providerId, _commandId, true);
|
||||
var title = _commandItemViewModel?.Title ?? string.Empty;
|
||||
var subtitle = _commandItemViewModel?.Subtitle ?? string.Empty;
|
||||
var icon = _commandItemViewModel?.Icon;
|
||||
var dockSide = _settingsService.Settings.DockSettings.Side;
|
||||
ShowPinToDockDialogMessage message = new(_providerId, _commandId, title, subtitle, icon, dockSide);
|
||||
WeakReferenceMessenger.Default.Send(message);
|
||||
}
|
||||
|
||||
|
||||
@@ -189,7 +189,7 @@
|
||||
<VisualStateGroup x:Name="OrientationStates">
|
||||
<VisualState x:Name="HorizontalState">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="scroller.HorizontalScrollBarVisibility" Value="Disabled" />
|
||||
<Setter Target="scroller.HorizontalScrollBarVisibility" Value="Hidden" />
|
||||
<Setter Target="scroller.HorizontalScrollMode" Value="Enabled" />
|
||||
<Setter Target="scroller.VerticalScrollBarVisibility" Value="Disabled" />
|
||||
<Setter Target="scroller.VerticalScrollMode" Value="Disabled" />
|
||||
@@ -213,7 +213,7 @@
|
||||
<VisualState.Setters>
|
||||
<Setter Target="scroller.HorizontalScrollBarVisibility" Value="Disabled" />
|
||||
<Setter Target="scroller.HorizontalScrollMode" Value="Disabled" />
|
||||
<Setter Target="scroller.VerticalScrollBarVisibility" Value="Disabled" />
|
||||
<Setter Target="scroller.VerticalScrollBarVisibility" Value="Hidden" />
|
||||
<Setter Target="scroller.VerticalScrollMode" Value="Enabled" />
|
||||
<Setter Target="ScrollBackBtn.Padding" Value="12,4,12,4" />
|
||||
<Setter Target="ScrollBackBtn.Margin" Value="0,8,0,0" />
|
||||
@@ -228,7 +228,7 @@
|
||||
<Setter Target="ScrollForwardBtn.VerticalAlignment" Value="Bottom" />
|
||||
<Setter Target="ScrollForwardBtn.(AutomationProperties.Name)" Value="Scroll down" />
|
||||
<Setter Target="ScrollForwardBtn.(ToolTipService.ToolTip)" Value="Scroll down" />
|
||||
<Setter Target="ScrollForwardIcon.Glyph" Value="" />
|
||||
<Setter Target="ScrollForwardIcon.Glyph" Value="" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
|
||||
@@ -67,15 +67,52 @@ public sealed partial class DockControl : UserControl, IRecipient<CloseContextMe
|
||||
{
|
||||
_viewModel = viewModel;
|
||||
InitializeComponent();
|
||||
WeakReferenceMessenger.Default.Register<CloseContextMenuMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<EnterDockEditModeMessage>(this);
|
||||
|
||||
ViewModel.CenterItems.CollectionChanged += CenterItems_CollectionChanged;
|
||||
Loaded += DockControl_Loaded;
|
||||
Unloaded += DockControl_Unloaded;
|
||||
|
||||
// Start with edit mode disabled - normal click behavior
|
||||
UpdateEditMode(false);
|
||||
}
|
||||
|
||||
private void DockControl_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WeakReferenceMessenger.Default.UnregisterAll(this);
|
||||
WeakReferenceMessenger.Default.Register<CloseContextMenuMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<EnterDockEditModeMessage>(this);
|
||||
|
||||
ViewModel.CenterItems.CollectionChanged -= CenterItems_CollectionChanged;
|
||||
ViewModel.CenterItems.CollectionChanged += CenterItems_CollectionChanged;
|
||||
|
||||
UpdateEditModeTeachingTip();
|
||||
}
|
||||
|
||||
private void DockControl_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WeakReferenceMessenger.Default.UnregisterAll(this);
|
||||
|
||||
ViewModel.CenterItems.CollectionChanged -= CenterItems_CollectionChanged;
|
||||
|
||||
if (EditButtonsTeachingTip.IsOpen)
|
||||
{
|
||||
EditButtonsTeachingTip.IsOpen = false;
|
||||
}
|
||||
|
||||
if (ContextMenuFlyout.IsOpen)
|
||||
{
|
||||
ContextMenuFlyout.Hide();
|
||||
}
|
||||
|
||||
if (AddBandFlyout.IsOpen)
|
||||
{
|
||||
AddBandFlyout.Hide();
|
||||
}
|
||||
|
||||
if (EditModeContextMenu.IsOpen)
|
||||
{
|
||||
EditModeContextMenu.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
private void CenterItems_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
UpdateCenterVisibility();
|
||||
@@ -125,7 +162,38 @@ public sealed partial class DockControl : UserControl, IRecipient<CloseContextMe
|
||||
};
|
||||
}
|
||||
|
||||
EditButtonsTeachingTip.IsOpen = isEditMode;
|
||||
UpdateEditModeTeachingTip();
|
||||
}
|
||||
|
||||
private void UpdateEditModeTeachingTip()
|
||||
{
|
||||
if (XamlRoot is null || ContentGrid.XamlRoot is null || EditButtonsTeachingTip.Parent is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsEditMode)
|
||||
{
|
||||
if (EditButtonsTeachingTip.IsOpen)
|
||||
{
|
||||
EditButtonsTeachingTip.IsOpen = false;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EditButtonsTeachingTip.IsOpen)
|
||||
{
|
||||
EditButtonsTeachingTip.IsOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void PreparePopupForShow(FlyoutBase popup, FrameworkElement placementTarget)
|
||||
{
|
||||
if (placementTarget.XamlRoot is not null && popup.XamlRoot != placementTarget.XamlRoot)
|
||||
{
|
||||
popup.XamlRoot = placementTarget.XamlRoot;
|
||||
}
|
||||
}
|
||||
|
||||
internal void EnterEditMode()
|
||||
@@ -214,6 +282,7 @@ public sealed partial class DockControl : UserControl, IRecipient<CloseContextMe
|
||||
ShowTitlesMenuItem.IsChecked = _editModeContextBand.ShowTitles;
|
||||
ShowSubtitlesMenuItem.IsChecked = _editModeContextBand.ShowSubtitles;
|
||||
|
||||
PreparePopupForShow(EditModeContextMenu, dockItem);
|
||||
EditModeContextMenu.ShowAt(
|
||||
dockItem,
|
||||
new FlyoutShowOptions()
|
||||
@@ -232,6 +301,7 @@ public sealed partial class DockControl : UserControl, IRecipient<CloseContextMe
|
||||
{
|
||||
ContextControl.ViewModel.SelectedItem = item;
|
||||
ContextControl.ShowFilterBox = true;
|
||||
PreparePopupForShow(ContextMenuFlyout, dockItem);
|
||||
ContextMenuFlyout.ShowAt(
|
||||
dockItem,
|
||||
new FlyoutShowOptions()
|
||||
@@ -320,6 +390,7 @@ public sealed partial class DockControl : UserControl, IRecipient<CloseContextMe
|
||||
{
|
||||
ContextControl.ViewModel.SelectedItem = item;
|
||||
ContextControl.ShowFilterBox = false;
|
||||
PreparePopupForShow(ContextMenuFlyout, RootGrid);
|
||||
ContextMenuFlyout.ShowAt(
|
||||
this.RootGrid,
|
||||
new FlyoutShowOptions()
|
||||
@@ -516,6 +587,7 @@ public sealed partial class DockControl : UserControl, IRecipient<CloseContextMe
|
||||
AddBandListView.Visibility = hasAvailableBands ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
// Show the flyout
|
||||
PreparePopupForShow(AddBandFlyout, button);
|
||||
AddBandFlyout.ShowAt(button);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,10 +35,7 @@ public sealed partial class DockItemControl : Control
|
||||
{
|
||||
if (d is DockItemControl control)
|
||||
{
|
||||
// Collapse the tooltip when the string is null or empty so an
|
||||
// empty tooltip bubble doesn't appear on hover.
|
||||
var text = e.NewValue as string;
|
||||
ToolTipService.SetToolTip(control, string.IsNullOrEmpty(text) ? null : text);
|
||||
control.UpdateToolTip();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +88,7 @@ public sealed partial class DockItemControl : Control
|
||||
|
||||
private FrameworkElement? _iconPresenter;
|
||||
private DockControl? _parentDock;
|
||||
private ToolTip? _toolTip;
|
||||
private long _dockSideCallbackToken = -1;
|
||||
|
||||
private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
@@ -184,9 +182,33 @@ public sealed partial class DockItemControl : Control
|
||||
{
|
||||
UpdateTextVisibility();
|
||||
UpdateIconVisibility();
|
||||
UpdateToolTip();
|
||||
UpdateAlignment();
|
||||
}
|
||||
|
||||
private void UpdateToolTip()
|
||||
{
|
||||
var text = ToolTip;
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
ToolTipService.SetToolTip(this, null);
|
||||
_toolTip = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait until the control is connected to a XamlRoot before creating
|
||||
// the tooltip popup; dock items are materialized very early in startup.
|
||||
if (XamlRoot is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_toolTip ??= new ToolTip();
|
||||
_toolTip.Content = text;
|
||||
_toolTip.XamlRoot = XamlRoot;
|
||||
ToolTipService.SetToolTip(this, _toolTip);
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
@@ -232,6 +254,8 @@ public sealed partial class DockItemControl : Control
|
||||
DockControl.DockSideProperty,
|
||||
OnParentDockSideChanged);
|
||||
}
|
||||
|
||||
UpdateToolTip();
|
||||
}
|
||||
|
||||
private void DockItemControl_ActualThemeChanged(FrameworkElement sender, object args)
|
||||
@@ -250,6 +274,9 @@ public sealed partial class DockItemControl : Control
|
||||
_dockSideCallbackToken = -1;
|
||||
_parentDock = null;
|
||||
}
|
||||
|
||||
ToolTipService.SetToolTip(this, null);
|
||||
_toolTip = null;
|
||||
}
|
||||
|
||||
private void OnParentDockSideChanged(DependencyObject sender, DependencyProperty dp)
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
using Microsoft.CmdPal.UI.ViewModels.Settings;
|
||||
using Windows.Win32;
|
||||
using WinUIEx;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Dock;
|
||||
|
||||
@@ -32,16 +31,6 @@ internal static class DockSettingsToViews
|
||||
};
|
||||
}
|
||||
|
||||
public static Microsoft.UI.Xaml.Media.SystemBackdrop? GetSystemBackdrop(DockBackdrop backdrop)
|
||||
{
|
||||
return backdrop switch
|
||||
{
|
||||
DockBackdrop.Transparent => new TransparentTintBackdrop(),
|
||||
DockBackdrop.Acrylic => null,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
public static uint GetAppBarEdge(DockSide side)
|
||||
{
|
||||
return side switch
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.UI.Helpers;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Dock;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
@@ -27,7 +26,6 @@ using Windows.Win32.UI.WindowsAndMessaging;
|
||||
using WinRT;
|
||||
using WinRT.Interop;
|
||||
using WinUIEx;
|
||||
using WindowExtensions = Microsoft.CmdPal.UI.Helpers.WindowExtensions;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Dock;
|
||||
|
||||
@@ -59,7 +57,10 @@ public sealed partial class DockWindow : WindowEx,
|
||||
private DockControl _dock;
|
||||
private DesktopAcrylicController? _acrylicController;
|
||||
private SystemBackdropConfiguration? _configurationSource;
|
||||
private bool _isUpdatingBackdrop;
|
||||
private BackdropParameters? _lastAppliedAcrylicBackdrop;
|
||||
private DockSize _lastSize;
|
||||
private bool _isDisposed;
|
||||
|
||||
// Store the original WndProc
|
||||
private WNDPROC? _originalWndProc;
|
||||
@@ -78,6 +79,7 @@ public sealed partial class DockWindow : WindowEx,
|
||||
viewModel = serviceProvider.GetService<DockViewModel>()!;
|
||||
_themeService = serviceProvider.GetRequiredService<IThemeService>();
|
||||
_themeService.ThemeChanged += ThemeService_ThemeChanged;
|
||||
InitializeBackdropSupport();
|
||||
_windowViewModel = new DockWindowViewModel(_themeService);
|
||||
_dock = new DockControl(viewModel);
|
||||
|
||||
@@ -156,14 +158,7 @@ public sealed partial class DockWindow : WindowEx,
|
||||
private void UpdateSettingsOnUiThread()
|
||||
{
|
||||
this.viewModel.UpdateSettings(_settings);
|
||||
|
||||
SystemBackdrop = DockSettingsToViews.GetSystemBackdrop(_settings.Backdrop);
|
||||
|
||||
// If the backdrop is acrylic, things are more complicated
|
||||
if (_settings.Backdrop == DockBackdrop.Acrylic)
|
||||
{
|
||||
SetAcrylic();
|
||||
}
|
||||
UpdateBackdrop();
|
||||
|
||||
_dock.UpdateSettings(_settings);
|
||||
var side = DockSettingsToViews.GetAppBarEdge(_settings.Side);
|
||||
@@ -183,31 +178,98 @@ public sealed partial class DockWindow : WindowEx,
|
||||
CreateAppBar(_hwnd);
|
||||
}
|
||||
|
||||
// We want to use DesktopAcrylicKind.Thin and custom colors as this is the default material
|
||||
// other Shell surfaces are using, this cannot be set in XAML however.
|
||||
private void SetAcrylic()
|
||||
private void InitializeBackdropSupport()
|
||||
{
|
||||
if (DesktopAcrylicController.IsSupported())
|
||||
{
|
||||
// Hooking up the policy object.
|
||||
_configurationSource = new SystemBackdropConfiguration
|
||||
{
|
||||
// Initial configuration state.
|
||||
IsInputActive = true,
|
||||
};
|
||||
UpdateAcrylic();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAcrylic()
|
||||
private void UpdateBackdrop()
|
||||
{
|
||||
if (_acrylicController != null)
|
||||
// Prevent re-entrance when backdrop changes trigger theme refresh work.
|
||||
if (_isUpdatingBackdrop)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isUpdatingBackdrop = true;
|
||||
|
||||
try
|
||||
{
|
||||
switch (_settings.Backdrop)
|
||||
{
|
||||
case DockBackdrop.Transparent:
|
||||
if (SystemBackdrop is not TransparentTintBackdrop)
|
||||
{
|
||||
CleanupBackdropControllers();
|
||||
SetupTransparentBackdrop();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case DockBackdrop.Acrylic:
|
||||
default:
|
||||
SetupDesktopAcrylic(_themeService.CurrentDockTheme.BackdropParameters);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to update dock backdrop", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isUpdatingBackdrop = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupTransparentBackdrop()
|
||||
{
|
||||
if (SystemBackdrop is not TransparentTintBackdrop)
|
||||
{
|
||||
SystemBackdrop = new TransparentTintBackdrop();
|
||||
}
|
||||
|
||||
_lastAppliedAcrylicBackdrop = null;
|
||||
}
|
||||
|
||||
private void CleanupBackdropControllers()
|
||||
{
|
||||
if (_acrylicController is not null)
|
||||
{
|
||||
_acrylicController.RemoveAllSystemBackdropTargets();
|
||||
_acrylicController.Dispose();
|
||||
_acrylicController = null;
|
||||
}
|
||||
|
||||
var backdrop = _themeService.CurrentDockTheme.BackdropParameters;
|
||||
_lastAppliedAcrylicBackdrop = null;
|
||||
}
|
||||
|
||||
private void SetupDesktopAcrylic(BackdropParameters backdrop)
|
||||
{
|
||||
var needsAcrylicUpdate = _acrylicController is null || _lastAppliedAcrylicBackdrop != backdrop;
|
||||
if (!needsAcrylicUpdate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CleanupBackdropControllers();
|
||||
|
||||
// Fall back to the transparent backdrop if acrylic is not supported.
|
||||
if (_configurationSource is null || !DesktopAcrylicController.IsSupported())
|
||||
{
|
||||
SetupTransparentBackdrop();
|
||||
return;
|
||||
}
|
||||
|
||||
// DesktopAcrylicController and SystemBackdrop can't be active simultaneously.
|
||||
SystemBackdrop = null;
|
||||
|
||||
_acrylicController = new DesktopAcrylicController
|
||||
{
|
||||
Kind = DesktopAcrylicKind.Thin,
|
||||
@@ -221,29 +283,20 @@ public sealed partial class DockWindow : WindowEx,
|
||||
// Note: Be sure to have "using WinRT;" to support the Window.As<...>() call.
|
||||
_acrylicController.AddSystemBackdropTarget(this.As<ICompositionSupportsSystemBackdrop>());
|
||||
_acrylicController.SetSystemBackdropConfiguration(_configurationSource);
|
||||
_lastAppliedAcrylicBackdrop = backdrop;
|
||||
}
|
||||
|
||||
private void DisposeAcrylic()
|
||||
{
|
||||
if (_acrylicController is not null)
|
||||
{
|
||||
_acrylicController.Dispose();
|
||||
_acrylicController = null!;
|
||||
_configurationSource = null!;
|
||||
}
|
||||
CleanupBackdropControllers();
|
||||
_configurationSource = null;
|
||||
}
|
||||
|
||||
private void ThemeService_ThemeChanged(object? sender, ThemeChangedEventArgs e)
|
||||
{
|
||||
DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
// We only need to handle acrylic here.
|
||||
// Transparent background is handled directly in XAML by binding to
|
||||
// the DockWindowViewModel's ColorizationColor properties.
|
||||
if (_settings.Backdrop == DockBackdrop.Acrylic)
|
||||
{
|
||||
UpdateAcrylic();
|
||||
}
|
||||
UpdateBackdrop();
|
||||
|
||||
// ActualTheme / RequestedTheme sync,
|
||||
// as pilfered from WindowThemeSynchronizer
|
||||
@@ -617,18 +670,38 @@ public sealed partial class DockWindow : WindowEx,
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DisposeAcrylic();
|
||||
_windowViewModel.Dispose();
|
||||
Cleanup();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void DockWindow_Closed(object sender, WindowEventArgs args)
|
||||
{
|
||||
_settingsService.SettingsChanged -= SettingsChangedHandler;
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void Cleanup()
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
|
||||
_settingsService?.SettingsChanged -= SettingsChangedHandler;
|
||||
|
||||
Activated -= DockWindow_Activated;
|
||||
_themeService.ThemeChanged -= ThemeService_ThemeChanged;
|
||||
WeakReferenceMessenger.Default.UnregisterAll(this);
|
||||
|
||||
DisposeAcrylic();
|
||||
_windowViewModel.Dispose();
|
||||
|
||||
// Remove our app bar registration
|
||||
DestroyAppBar(_hwnd);
|
||||
if (_appBarData.hWnd != IntPtr.Zero)
|
||||
{
|
||||
DestroyAppBar(_hwnd);
|
||||
}
|
||||
|
||||
// Unhook the window procedure
|
||||
ShowDesktop.RemoveHook();
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="Microsoft.CmdPal.UI.Dock.PinToDockDialogContent"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cpcontrols="using:Microsoft.CmdPal.UI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:help="using:Microsoft.CmdPal.UI.Helpers"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.Segmented/Segmented/Segmented.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- Top dock: horizontal bar at top (left/center/right) -->
|
||||
<x:String x:Key="TopStartPath">M52.25 8C53.8125 8 55.3021 8.3125 56.7188 8.9375C58.1354 9.5625 59.3854 10.4167 60.4688 11.5C61.5521 12.5833 62.4062 13.8333 63.0312 15.25C63.6562 16.6667 63.9792 18.1667 64 19.75V44.25C64 45.8125 63.6875 47.3021 63.0625 48.7188C62.4375 50.1354 61.5833 51.3854 60.5 52.4688C59.4167 53.5521 58.1667 54.4062 56.75 55.0312C55.3333 55.6562 53.8333 55.9792 52.25 56H11.75C10.1875 56 8.69792 55.6875 7.28125 55.0625C5.86458 54.4375 4.61458 53.5833 3.53125 52.5C2.44792 51.4167 1.59375 50.1667 0.96875 48.75C0.34375 47.3333 0.0208333 45.8333 0 44.25V19.75C0 18.1875 0.3125 16.6979 0.9375 15.2812C1.5625 13.8646 2.41667 12.6146 3.5 11.5312C4.58333 10.4479 5.83333 9.59375 7.25 8.96875C8.66667 8.34375 10.1667 8.02083 11.75 8H52.25ZM11.8438 12C10.8021 12 9.8125 12.2083 8.875 12.625C7.9375 13.0417 7.10417 13.6146 6.375 14.3438C5.64583 15.0729 5.07292 15.9062 4.65625 16.8438C4.23958 17.7812 4.02083 18.7812 4 19.8438V44.1562C4 45.1979 4.20833 46.1875 4.625 47.125C5.04167 48.0625 5.61458 48.8958 6.34375 49.625C7.07292 50.3542 7.90625 50.9271 8.84375 51.3438C9.78125 51.7604 10.7812 51.9792 11.8438 52H52.1562C53.1979 52 54.1875 51.7917 55.125 51.375C56.0625 50.9583 56.8958 50.3854 57.625 49.6562C58.3542 48.9271 58.9271 48.0938 59.3438 47.1562C59.7604 46.2188 59.9792 45.2188 60 44.1562V19.8438C60 18.8021 59.7917 17.8125 59.375 16.875C58.9583 15.9375 58.3854 15.1042 57.6562 14.375C56.9271 13.6458 56.0938 13.0729 55.1562 12.6562C54.2188 12.2396 53.2188 12.0208 52.1562 12H11.8438ZM24 18C25.1046 18 26 18.8954 26 20C26 21.1046 25.1046 22 24 22H12C10.8954 22 10 21.1046 10 20C10 18.8954 10.8954 18 12 18H24Z</x:String>
|
||||
<x:String x:Key="TopCenterPath">M52.25 8C53.8125 8 55.3021 8.3125 56.7188 8.9375C58.1354 9.5625 59.3854 10.4167 60.4688 11.5C61.5521 12.5833 62.4062 13.8333 63.0312 15.25C63.6562 16.6667 63.9792 18.1667 64 19.75V44.25C64 45.8125 63.6875 47.3021 63.0625 48.7188C62.4375 50.1354 61.5833 51.3854 60.5 52.4688C59.4167 53.5521 58.1667 54.4062 56.75 55.0312C55.3333 55.6562 53.8333 55.9792 52.25 56H11.75C10.1875 56 8.69792 55.6875 7.28125 55.0625C5.86458 54.4375 4.61458 53.5833 3.53125 52.5C2.44792 51.4167 1.59375 50.1667 0.96875 48.75C0.34375 47.3333 0.0208333 45.8333 0 44.25V19.75C0 18.1875 0.3125 16.6979 0.9375 15.2812C1.5625 13.8646 2.41667 12.6146 3.5 11.5312C4.58333 10.4479 5.83333 9.59375 7.25 8.96875C8.66667 8.34375 10.1667 8.02083 11.75 8H52.25ZM11.8438 12C10.8021 12 9.8125 12.2083 8.875 12.625C7.9375 13.0417 7.10417 13.6146 6.375 14.3438C5.64583 15.0729 5.07292 15.9062 4.65625 16.8438C4.23958 17.7812 4.02083 18.7812 4 19.8438V44.1562C4 45.1979 4.20833 46.1875 4.625 47.125C5.04167 48.0625 5.61458 48.8958 6.34375 49.625C7.07292 50.3542 7.90625 50.9271 8.84375 51.3438C9.78125 51.7604 10.7812 51.9792 11.8438 52H52.1562C53.1979 52 54.1875 51.7917 55.125 51.375C56.0625 50.9583 56.8958 50.3854 57.625 49.6562C58.3542 48.9271 58.9271 48.0938 59.3438 47.1562C59.7604 46.2188 59.9792 45.2188 60 44.1562V19.8438C60 18.8021 59.7917 17.8125 59.375 16.875C58.9583 15.9375 58.3854 15.1042 57.6562 14.375C56.9271 13.6458 56.0938 13.0729 55.1562 12.6562C54.2188 12.2396 53.2188 12.0208 52.1562 12H11.8438ZM38 18C39.1046 18 40 18.8954 40 20C40 21.1046 39.1046 22 38 22H26C24.8954 22 24 21.1046 24 20C24 18.8954 24.8954 18 26 18H38Z</x:String>
|
||||
<x:String x:Key="TopEndPath">M52.25 8C53.8125 8 55.3021 8.3125 56.7188 8.9375C58.1354 9.5625 59.3854 10.4167 60.4688 11.5C61.5521 12.5833 62.4062 13.8333 63.0312 15.25C63.6562 16.6667 63.9792 18.1667 64 19.75V44.25C64 45.8125 63.6875 47.3021 63.0625 48.7188C62.4375 50.1354 61.5833 51.3854 60.5 52.4688C59.4167 53.5521 58.1667 54.4062 56.75 55.0312C55.3333 55.6562 53.8333 55.9792 52.25 56H11.75C10.1875 56 8.69792 55.6875 7.28125 55.0625C5.86458 54.4375 4.61458 53.5833 3.53125 52.5C2.44792 51.4167 1.59375 50.1667 0.96875 48.75C0.34375 47.3333 0.0208333 45.8333 0 44.25V19.75C0 18.1875 0.3125 16.6979 0.9375 15.2812C1.5625 13.8646 2.41667 12.6146 3.5 11.5312C4.58333 10.4479 5.83333 9.59375 7.25 8.96875C8.66667 8.34375 10.1667 8.02083 11.75 8H52.25ZM11.8438 12C10.8021 12 9.8125 12.2083 8.875 12.625C7.9375 13.0417 7.10417 13.6146 6.375 14.3438C5.64583 15.0729 5.07292 15.9062 4.65625 16.8438C4.23958 17.7812 4.02083 18.7812 4 19.8438V44.1562C4 45.1979 4.20833 46.1875 4.625 47.125C5.04167 48.0625 5.61458 48.8958 6.34375 49.625C7.07292 50.3542 7.90625 50.9271 8.84375 51.3438C9.78125 51.7604 10.7812 51.9792 11.8438 52H52.1562C53.1979 52 54.1875 51.7917 55.125 51.375C56.0625 50.9583 56.8958 50.3854 57.625 49.6562C58.3542 48.9271 58.9271 48.0938 59.3438 47.1562C59.7604 46.2188 59.9792 45.2188 60 44.1562V19.8438C60 18.8021 59.7917 17.8125 59.375 16.875C58.9583 15.9375 58.3854 15.1042 57.6562 14.375C56.9271 13.6458 56.0938 13.0729 55.1562 12.6562C54.2188 12.2396 53.2188 12.0208 52.1562 12H11.8438ZM52 18C53.1046 18 54 18.8954 54 20C54 21.1046 53.1046 22 52 22H40C38.8954 22 38 21.1046 38 20C38 18.8954 38.8954 18 40 18H52Z</x:String>
|
||||
|
||||
<!-- Bottom dock: horizontal bar at bottom (left/center/right) -->
|
||||
<x:String x:Key="BottomStartPath">M52.25 8C53.8125 8 55.3021 8.3125 56.7188 8.9375C58.1354 9.5625 59.3854 10.4167 60.4688 11.5C61.5521 12.5833 62.4062 13.8333 63.0312 15.25C63.6562 16.6667 63.9792 18.1667 64 19.75V44.25C64 45.8125 63.6875 47.3021 63.0625 48.7188C62.4375 50.1354 61.5833 51.3854 60.5 52.4688C59.4167 53.5521 58.1667 54.4062 56.75 55.0312C55.3333 55.6562 53.8333 55.9792 52.25 56H11.75C10.1875 56 8.69792 55.6875 7.28125 55.0625C5.86458 54.4375 4.61458 53.5833 3.53125 52.5C2.44792 51.4167 1.59375 50.1667 0.96875 48.75C0.34375 47.3333 0.0208333 45.8333 0 44.25V19.75C0 18.1875 0.3125 16.6979 0.9375 15.2812C1.5625 13.8646 2.41667 12.6146 3.5 11.5312C4.58333 10.4479 5.83333 9.59375 7.25 8.96875C8.66667 8.34375 10.1667 8.02083 11.75 8H52.25ZM11.8438 12C10.8021 12 9.8125 12.2083 8.875 12.625C7.9375 13.0417 7.10417 13.6146 6.375 14.3438C5.64583 15.0729 5.07292 15.9062 4.65625 16.8438C4.23958 17.7812 4.02083 18.7812 4 19.8438V44.1562C4 45.1979 4.20833 46.1875 4.625 47.125C5.04167 48.0625 5.61458 48.8958 6.34375 49.625C7.07292 50.3542 7.90625 50.9271 8.84375 51.3438C9.78125 51.7604 10.7812 51.9792 11.8438 52H52.1562C53.1979 52 54.1875 51.7917 55.125 51.375C56.0625 50.9583 56.8958 50.3854 57.625 49.6562C58.3542 48.9271 58.9271 48.0938 59.3438 47.1562C59.7604 46.2188 59.9792 45.2188 60 44.1562V19.8438C60 18.8021 59.7917 17.8125 59.375 16.875C58.9583 15.9375 58.3854 15.1042 57.6562 14.375C56.9271 13.6458 56.0938 13.0729 55.1562 12.6562C54.2188 12.2396 53.2188 12.0208 52.1562 12H11.8438ZM24 42C25.1046 42 26 42.8954 26 44C26 45.1046 25.1046 46 24 46H12C10.8954 46 10 45.1046 10 44C10 42.8954 10.8954 42 12 42H24Z</x:String>
|
||||
<x:String x:Key="BottomCenterPath">M52.25 8C53.8125 8 55.3021 8.3125 56.7188 8.9375C58.1354 9.5625 59.3854 10.4167 60.4688 11.5C61.5521 12.5833 62.4062 13.8333 63.0312 15.25C63.6562 16.6667 63.9792 18.1667 64 19.75V44.25C64 45.8125 63.6875 47.3021 63.0625 48.7188C62.4375 50.1354 61.5833 51.3854 60.5 52.4688C59.4167 53.5521 58.1667 54.4062 56.75 55.0312C55.3333 55.6562 53.8333 55.9792 52.25 56H11.75C10.1875 56 8.69792 55.6875 7.28125 55.0625C5.86458 54.4375 4.61458 53.5833 3.53125 52.5C2.44792 51.4167 1.59375 50.1667 0.96875 48.75C0.34375 47.3333 0.0208333 45.8333 0 44.25V19.75C0 18.1875 0.3125 16.6979 0.9375 15.2812C1.5625 13.8646 2.41667 12.6146 3.5 11.5312C4.58333 10.4479 5.83333 9.59375 7.25 8.96875C8.66667 8.34375 10.1667 8.02083 11.75 8H52.25ZM11.8438 12C10.8021 12 9.8125 12.2083 8.875 12.625C7.9375 13.0417 7.10417 13.6146 6.375 14.3438C5.64583 15.0729 5.07292 15.9062 4.65625 16.8438C4.23958 17.7812 4.02083 18.7812 4 19.8438V44.1562C4 45.1979 4.20833 46.1875 4.625 47.125C5.04167 48.0625 5.61458 48.8958 6.34375 49.625C7.07292 50.3542 7.90625 50.9271 8.84375 51.3438C9.78125 51.7604 10.7812 51.9792 11.8438 52H52.1562C53.1979 52 54.1875 51.7917 55.125 51.375C56.0625 50.9583 56.8958 50.3854 57.625 49.6562C58.3542 48.9271 58.9271 48.0938 59.3438 47.1562C59.7604 46.2188 59.9792 45.2188 60 44.1562V19.8438C60 18.8021 59.7917 17.8125 59.375 16.875C58.9583 15.9375 58.3854 15.1042 57.6562 14.375C56.9271 13.6458 56.0938 13.0729 55.1562 12.6562C54.2188 12.2396 53.2188 12.0208 52.1562 12H11.8438ZM38 42C39.1046 42 40 42.8954 40 44C40 45.1046 39.1046 46 38 46H26C24.8954 46 24 45.1046 24 44C24 42.8954 24.8954 42 26 42H38Z</x:String>
|
||||
<x:String x:Key="BottomEndPath">M52.25 8C53.8125 8 55.3021 8.3125 56.7188 8.9375C58.1354 9.5625 59.3854 10.4167 60.4688 11.5C61.5521 12.5833 62.4062 13.8333 63.0312 15.25C63.6562 16.6667 63.9792 18.1667 64 19.75V44.25C64 45.8125 63.6875 47.3021 63.0625 48.7188C62.4375 50.1354 61.5833 51.3854 60.5 52.4688C59.4167 53.5521 58.1667 54.4062 56.75 55.0312C55.3333 55.6562 53.8333 55.9792 52.25 56H11.75C10.1875 56 8.69792 55.6875 7.28125 55.0625C5.86458 54.4375 4.61458 53.5833 3.53125 52.5C2.44792 51.4167 1.59375 50.1667 0.96875 48.75C0.34375 47.3333 0.0208333 45.8333 0 44.25V19.75C0 18.1875 0.3125 16.6979 0.9375 15.2812C1.5625 13.8646 2.41667 12.6146 3.5 11.5312C4.58333 10.4479 5.83333 9.59375 7.25 8.96875C8.66667 8.34375 10.1667 8.02083 11.75 8H52.25ZM11.8438 12C10.8021 12 9.8125 12.2083 8.875 12.625C7.9375 13.0417 7.10417 13.6146 6.375 14.3438C5.64583 15.0729 5.07292 15.9062 4.65625 16.8438C4.23958 17.7812 4.02083 18.7812 4 19.8438V44.1562C4 45.1979 4.20833 46.1875 4.625 47.125C5.04167 48.0625 5.61458 48.8958 6.34375 49.625C7.07292 50.3542 7.90625 50.9271 8.84375 51.3438C9.78125 51.7604 10.7812 51.9792 11.8438 52H52.1562C53.1979 52 54.1875 51.7917 55.125 51.375C56.0625 50.9583 56.8958 50.3854 57.625 49.6562C58.3542 48.9271 58.9271 48.0938 59.3438 47.1562C59.7604 46.2188 59.9792 45.2188 60 44.1562V19.8438C60 18.8021 59.7917 17.8125 59.375 16.875C58.9583 15.9375 58.3854 15.1042 57.6562 14.375C56.9271 13.6458 56.0938 13.0729 55.1562 12.6562C54.2188 12.2396 53.2188 12.0208 52.1562 12H11.8438ZM52 42C53.1046 42 54 42.8954 54 44C54 45.1046 53.1046 46 52 46H40C38.8954 46 38 45.1046 38 44C38 42.8954 38.8954 42 40 42H52Z</x:String>
|
||||
|
||||
<!-- Left dock: vertical bar at left (top/center/bottom) -->
|
||||
<x:String x:Key="LeftStartPath">M52.25 8C53.8125 8 55.3021 8.3125 56.7188 8.9375C58.1354 9.5625 59.3854 10.4167 60.4688 11.5C61.5521 12.5833 62.4062 13.8333 63.0312 15.25C63.6562 16.6667 63.9792 18.1667 64 19.75V44.25C64 45.8125 63.6875 47.3021 63.0625 48.7188C62.4375 50.1354 61.5833 51.3854 60.5 52.4688C59.4167 53.5521 58.1667 54.4062 56.75 55.0312C55.3333 55.6562 53.8333 55.9792 52.25 56H11.75C10.1875 56 8.69792 55.6875 7.28125 55.0625C5.86458 54.4375 4.61458 53.5833 3.53125 52.5C2.44792 51.4167 1.59375 50.1667 0.96875 48.75C0.34375 47.3333 0.0208333 45.8333 0 44.25V19.75C0 18.1875 0.3125 16.6979 0.9375 15.2812C1.5625 13.8646 2.41667 12.6146 3.5 11.5312C4.58333 10.4479 5.83333 9.59375 7.25 8.96875C8.66667 8.34375 10.1667 8.02083 11.75 8H52.25ZM11.8438 12C10.8021 12 9.8125 12.2083 8.875 12.625C7.9375 13.0417 7.10417 13.6146 6.375 14.3438C5.64583 15.0729 5.07292 15.9062 4.65625 16.8438C4.23958 17.7812 4.02083 18.7812 4 19.8438V44.1562C4 45.1979 4.20833 46.1875 4.625 47.125C5.04167 48.0625 5.61458 48.8958 6.34375 49.625C7.07292 50.3542 7.90625 50.9271 8.84375 51.3438C9.78125 51.7604 10.7812 51.9792 11.8438 52H52.1562C53.1979 52 54.1875 51.7917 55.125 51.375C56.0625 50.9583 56.8958 50.3854 57.625 49.6562C58.3542 48.9271 58.9271 48.0938 59.3438 47.1562C59.7604 46.2188 59.9792 45.2188 60 44.1562V19.8438C60 18.8021 59.7917 17.8125 59.375 16.875C58.9583 15.9375 58.3854 15.1042 57.6562 14.375C56.9271 13.6458 56.0938 13.0729 55.1562 12.6562C54.2188 12.2396 53.2188 12.0208 52.1562 12H11.8438ZM12 14C13.1046 14 14 14.8954 14 16V28C14 29.1046 13.1046 30 12 30C10.8954 30 10 29.1046 10 28V16C10 14.8954 10.8954 14 12 14Z</x:String>
|
||||
<x:String x:Key="LeftCenterPath">M52.25 8C53.8125 8 55.3021 8.3125 56.7188 8.9375C58.1354 9.5625 59.3854 10.4167 60.4688 11.5C61.5521 12.5833 62.4062 13.8333 63.0312 15.25C63.6562 16.6667 63.9792 18.1667 64 19.75V44.25C64 45.8125 63.6875 47.3021 63.0625 48.7188C62.4375 50.1354 61.5833 51.3854 60.5 52.4688C59.4167 53.5521 58.1667 54.4062 56.75 55.0312C55.3333 55.6562 53.8333 55.9792 52.25 56H11.75C10.1875 56 8.69792 55.6875 7.28125 55.0625C5.86458 54.4375 4.61458 53.5833 3.53125 52.5C2.44792 51.4167 1.59375 50.1667 0.96875 48.75C0.34375 47.3333 0.0208333 45.8333 0 44.25V19.75C0 18.1875 0.3125 16.6979 0.9375 15.2812C1.5625 13.8646 2.41667 12.6146 3.5 11.5312C4.58333 10.4479 5.83333 9.59375 7.25 8.96875C8.66667 8.34375 10.1667 8.02083 11.75 8H52.25ZM11.8438 12C10.8021 12 9.8125 12.2083 8.875 12.625C7.9375 13.0417 7.10417 13.6146 6.375 14.3438C5.64583 15.0729 5.07292 15.9062 4.65625 16.8438C4.23958 17.7812 4.02083 18.7812 4 19.8438V44.1562C4 45.1979 4.20833 46.1875 4.625 47.125C5.04167 48.0625 5.61458 48.8958 6.34375 49.625C7.07292 50.3542 7.90625 50.9271 8.84375 51.3438C9.78125 51.7604 10.7812 51.9792 11.8438 52H52.1562C53.1979 52 54.1875 51.7917 55.125 51.375C56.0625 50.9583 56.8958 50.3854 57.625 49.6562C58.3542 48.9271 58.9271 48.0938 59.3438 47.1562C59.7604 46.2188 59.9792 45.2188 60 44.1562V19.8438C60 18.8021 59.7917 17.8125 59.375 16.875C58.9583 15.9375 58.3854 15.1042 57.6562 14.375C56.9271 13.6458 56.0938 13.0729 55.1562 12.6562C54.2188 12.2396 53.2188 12.0208 52.1562 12H11.8438ZM12 24C13.1046 24 14 24.8954 14 26V38C14 39.1046 13.1046 40 12 40C10.8954 40 10 39.1046 10 38V26C10 24.8954 10.8954 24 12 24Z</x:String>
|
||||
<x:String x:Key="LeftEndPath">M52.25 8C53.8125 8 55.3021 8.3125 56.7188 8.9375C58.1354 9.5625 59.3854 10.4167 60.4688 11.5C61.5521 12.5833 62.4062 13.8333 63.0312 15.25C63.6562 16.6667 63.9792 18.1667 64 19.75V44.25C64 45.8125 63.6875 47.3021 63.0625 48.7188C62.4375 50.1354 61.5833 51.3854 60.5 52.4688C59.4167 53.5521 58.1667 54.4062 56.75 55.0312C55.3333 55.6562 53.8333 55.9792 52.25 56H11.75C10.1875 56 8.69792 55.6875 7.28125 55.0625C5.86458 54.4375 4.61458 53.5833 3.53125 52.5C2.44792 51.4167 1.59375 50.1667 0.96875 48.75C0.34375 47.3333 0.0208333 45.8333 0 44.25V19.75C0 18.1875 0.3125 16.6979 0.9375 15.2812C1.5625 13.8646 2.41667 12.6146 3.5 11.5312C4.58333 10.4479 5.83333 9.59375 7.25 8.96875C8.66667 8.34375 10.1667 8.02083 11.75 8H52.25ZM11.8438 12C10.8021 12 9.8125 12.2083 8.875 12.625C7.9375 13.0417 7.10417 13.6146 6.375 14.3438C5.64583 15.0729 5.07292 15.9062 4.65625 16.8438C4.23958 17.7812 4.02083 18.7812 4 19.8438V44.1562C4 45.1979 4.20833 46.1875 4.625 47.125C5.04167 48.0625 5.61458 48.8958 6.34375 49.625C7.07292 50.3542 7.90625 50.9271 8.84375 51.3438C9.78125 51.7604 10.7812 51.9792 11.8438 52H52.1562C53.1979 52 54.1875 51.7917 55.125 51.375C56.0625 50.9583 56.8958 50.3854 57.625 49.6562C58.3542 48.9271 58.9271 48.0938 59.3438 47.1562C59.7604 46.2188 59.9792 45.2188 60 44.1562V19.8438C60 18.8021 59.7917 17.8125 59.375 16.875C58.9583 15.9375 58.3854 15.1042 57.6562 14.375C56.9271 13.6458 56.0938 13.0729 55.1562 12.6562C54.2188 12.2396 53.2188 12.0208 52.1562 12H11.8438ZM12 34C13.1046 34 14 34.8954 14 36V48C14 49.1046 13.1046 50 12 50C10.8954 50 10 49.1046 10 48V36C10 34.8954 10.8954 34 12 34Z</x:String>
|
||||
|
||||
<!-- Right dock: vertical bar at right (top/center/bottom) -->
|
||||
<x:String x:Key="RightStartPath">M52.25 8C53.8125 8 55.3021 8.3125 56.7188 8.9375C58.1354 9.5625 59.3854 10.4167 60.4688 11.5C61.5521 12.5833 62.4062 13.8333 63.0312 15.25C63.6562 16.6667 63.9792 18.1667 64 19.75V44.25C64 45.8125 63.6875 47.3021 63.0625 48.7188C62.4375 50.1354 61.5833 51.3854 60.5 52.4688C59.4167 53.5521 58.1667 54.4062 56.75 55.0312C55.3333 55.6562 53.8333 55.9792 52.25 56H11.75C10.1875 56 8.69792 55.6875 7.28125 55.0625C5.86458 54.4375 4.61458 53.5833 3.53125 52.5C2.44792 51.4167 1.59375 50.1667 0.96875 48.75C0.34375 47.3333 0.0208333 45.8333 0 44.25V19.75C0 18.1875 0.3125 16.6979 0.9375 15.2812C1.5625 13.8646 2.41667 12.6146 3.5 11.5312C4.58333 10.4479 5.83333 9.59375 7.25 8.96875C8.66667 8.34375 10.1667 8.02083 11.75 8H52.25ZM11.8438 12C10.8021 12 9.8125 12.2083 8.875 12.625C7.9375 13.0417 7.10417 13.6146 6.375 14.3438C5.64583 15.0729 5.07292 15.9062 4.65625 16.8438C4.23958 17.7812 4.02083 18.7812 4 19.8438V44.1562C4 45.1979 4.20833 46.1875 4.625 47.125C5.04167 48.0625 5.61458 48.8958 6.34375 49.625C7.07292 50.3542 7.90625 50.9271 8.84375 51.3438C9.78125 51.7604 10.7812 51.9792 11.8438 52H52.1562C53.1979 52 54.1875 51.7917 55.125 51.375C56.0625 50.9583 56.8958 50.3854 57.625 49.6562C58.3542 48.9271 58.9271 48.0938 59.3438 47.1562C59.7604 46.2188 59.9792 45.2188 60 44.1562V19.8438C60 18.8021 59.7917 17.8125 59.375 16.875C58.9583 15.9375 58.3854 15.1042 57.6562 14.375C56.9271 13.6458 56.0938 13.0729 55.1562 12.6562C54.2188 12.2396 53.2188 12.0208 52.1562 12H11.8438ZM52 14C53.1046 14 54 14.8954 54 16V28C54 29.1046 53.1046 30 52 30C50.8954 30 50 29.1046 50 28V16C50 14.8954 50.8954 14 52 14Z</x:String>
|
||||
<x:String x:Key="RightCenterPath">M52.25 8C53.8125 8 55.3021 8.3125 56.7188 8.9375C58.1354 9.5625 59.3854 10.4167 60.4688 11.5C61.5521 12.5833 62.4062 13.8333 63.0312 15.25C63.6562 16.6667 63.9792 18.1667 64 19.75V44.25C64 45.8125 63.6875 47.3021 63.0625 48.7188C62.4375 50.1354 61.5833 51.3854 60.5 52.4688C59.4167 53.5521 58.1667 54.4062 56.75 55.0312C55.3333 55.6562 53.8333 55.9792 52.25 56H11.75C10.1875 56 8.69792 55.6875 7.28125 55.0625C5.86458 54.4375 4.61458 53.5833 3.53125 52.5C2.44792 51.4167 1.59375 50.1667 0.96875 48.75C0.34375 47.3333 0.0208333 45.8333 0 44.25V19.75C0 18.1875 0.3125 16.6979 0.9375 15.2812C1.5625 13.8646 2.41667 12.6146 3.5 11.5312C4.58333 10.4479 5.83333 9.59375 7.25 8.96875C8.66667 8.34375 10.1667 8.02083 11.75 8H52.25ZM11.8438 12C10.8021 12 9.8125 12.2083 8.875 12.625C7.9375 13.0417 7.10417 13.6146 6.375 14.3438C5.64583 15.0729 5.07292 15.9062 4.65625 16.8438C4.23958 17.7812 4.02083 18.7812 4 19.8438V44.1562C4 45.1979 4.20833 46.1875 4.625 47.125C5.04167 48.0625 5.61458 48.8958 6.34375 49.625C7.07292 50.3542 7.90625 50.9271 8.84375 51.3438C9.78125 51.7604 10.7812 51.9792 11.8438 52H52.1562C53.1979 52 54.1875 51.7917 55.125 51.375C56.0625 50.9583 56.8958 50.3854 57.625 49.6562C58.3542 48.9271 58.9271 48.0938 59.3438 47.1562C59.7604 46.2188 59.9792 45.2188 60 44.1562V19.8438C60 18.8021 59.7917 17.8125 59.375 16.875C58.9583 15.9375 58.3854 15.1042 57.6562 14.375C56.9271 13.6458 56.0938 13.0729 55.1562 12.6562C54.2188 12.2396 53.2188 12.0208 52.1562 12H11.8438ZM52 24C53.1046 24 54 24.8954 54 26V38C54 39.1046 53.1046 40 52 40C50.8954 40 50 39.1046 50 38V26C50 24.8954 50.8954 24 52 24Z</x:String>
|
||||
<x:String x:Key="RightEndPath">M52.25 8C53.8125 8 55.3021 8.3125 56.7188 8.9375C58.1354 9.5625 59.3854 10.4167 60.4688 11.5C61.5521 12.5833 62.4062 13.8333 63.0312 15.25C63.6562 16.6667 63.9792 18.1667 64 19.75V44.25C64 45.8125 63.6875 47.3021 63.0625 48.7188C62.4375 50.1354 61.5833 51.3854 60.5 52.4688C59.4167 53.5521 58.1667 54.4062 56.75 55.0312C55.3333 55.6562 53.8333 55.9792 52.25 56H11.75C10.1875 56 8.69792 55.6875 7.28125 55.0625C5.86458 54.4375 4.61458 53.5833 3.53125 52.5C2.44792 51.4167 1.59375 50.1667 0.96875 48.75C0.34375 47.3333 0.0208333 45.8333 0 44.25V19.75C0 18.1875 0.3125 16.6979 0.9375 15.2812C1.5625 13.8646 2.41667 12.6146 3.5 11.5312C4.58333 10.4479 5.83333 9.59375 7.25 8.96875C8.66667 8.34375 10.1667 8.02083 11.75 8H52.25ZM11.8438 12C10.8021 12 9.8125 12.2083 8.875 12.625C7.9375 13.0417 7.10417 13.6146 6.375 14.3438C5.64583 15.0729 5.07292 15.9062 4.65625 16.8438C4.23958 17.7812 4.02083 18.7812 4 19.8438V44.1562C4 45.1979 4.20833 46.1875 4.625 47.125C5.04167 48.0625 5.61458 48.8958 6.34375 49.625C7.07292 50.3542 7.90625 50.9271 8.84375 51.3438C9.78125 51.7604 10.7812 51.9792 11.8438 52H52.1562C53.1979 52 54.1875 51.7917 55.125 51.375C56.0625 50.9583 56.8958 50.3854 57.625 49.6562C58.3542 48.9271 58.9271 48.0938 59.3438 47.1562C59.7604 46.2188 59.9792 45.2188 60 44.1562V19.8438C60 18.8021 59.7917 17.8125 59.375 16.875C58.9583 15.9375 58.3854 15.1042 57.6562 14.375C56.9271 13.6458 56.0938 13.0729 55.1562 12.6562C54.2188 12.2396 53.2188 12.0208 52.1562 12H11.8438ZM52 34C53.1046 34 54 34.8954 54 36V48C54 49.1046 53.1046 50 52 50C50.8954 50 50 49.1046 50 48V36C50 34.8954 50.8954 34 52 34Z</x:String>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<ScrollViewer>
|
||||
<StackPanel Width="420" Spacing="16">
|
||||
<!-- Preview -->
|
||||
<StackPanel Spacing="8">
|
||||
<Border
|
||||
Padding="12,8"
|
||||
HorizontalAlignment="Left"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}">
|
||||
<Grid ColumnSpacing="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Icon -->
|
||||
<cpcontrols:IconBox
|
||||
x:Name="PreviewIcon"
|
||||
Width="16"
|
||||
Height="16"
|
||||
VerticalAlignment="Center"
|
||||
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested20}" />
|
||||
|
||||
<!-- Text -->
|
||||
<StackPanel
|
||||
x:Name="PreviewTextPanel"
|
||||
Grid.Column="1"
|
||||
Margin="8,0,0,0"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock
|
||||
x:Name="PreviewTitleText"
|
||||
FontSize="12"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
<TextBlock
|
||||
x:Name="PreviewSubtitleText"
|
||||
FontSize="10"
|
||||
Foreground="{ThemeResource TextFillColorTertiary}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<Rectangle
|
||||
Height="1"
|
||||
Margin="-24,0,-24,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Fill="{ThemeResource DividerStrokeColorDefaultBrush}" />
|
||||
|
||||
<!-- Dock Section Selector -->
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock
|
||||
x:Uid="PinToDock_SectionHeader"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<tkcontrols:Segmented
|
||||
x:Name="SectionSegmented"
|
||||
HorizontalAlignment="Stretch"
|
||||
SelectedIndex="0"
|
||||
SelectionMode="Single">
|
||||
<tkcontrols:SegmentedItem x:Name="StartSegmentedItem" x:Uid="PinToDock_Start" />
|
||||
<tkcontrols:SegmentedItem x:Name="CenterSegmentedItem" x:Uid="PinToDock_Center" />
|
||||
<tkcontrols:SegmentedItem x:Name="EndSegmentedItem" x:Uid="PinToDock_End" />
|
||||
</tkcontrols:Segmented>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Label Options -->
|
||||
<StackPanel Spacing="4">
|
||||
<CheckBox
|
||||
x:Name="ShowTitleCheckBox"
|
||||
x:Uid="PinToDock_ShowTitle"
|
||||
Checked="OnLabelOptionChanged"
|
||||
IsChecked="True"
|
||||
Unchecked="OnLabelOptionChanged" />
|
||||
<CheckBox
|
||||
x:Name="ShowSubtitleCheckBox"
|
||||
x:Uid="PinToDock_ShowSubtitle"
|
||||
Checked="OnLabelOptionChanged"
|
||||
IsChecked="True"
|
||||
Unchecked="OnLabelOptionChanged" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,171 @@
|
||||
// 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.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Dock;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Settings;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Windows.System;
|
||||
using RS_ = Microsoft.CmdPal.UI.Helpers.ResourceLoaderInstance;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Dock;
|
||||
|
||||
public sealed partial class PinToDockDialogContent : UserControl
|
||||
{
|
||||
private string _title = string.Empty;
|
||||
private string _subtitle = string.Empty;
|
||||
|
||||
public DockPinSide SelectedSide => SectionSegmented.SelectedIndex switch
|
||||
{
|
||||
0 => DockPinSide.Start,
|
||||
1 => DockPinSide.Center,
|
||||
2 => DockPinSide.End,
|
||||
_ => DockPinSide.Start,
|
||||
};
|
||||
|
||||
public bool? ShowTitles => ShowTitleCheckBox.IsChecked;
|
||||
|
||||
public bool? ShowSubtitles => ShowSubtitleCheckBox.IsChecked;
|
||||
|
||||
public PinToDockDialogContent()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void Configure(string title, string subtitle, IconInfoViewModel? icon, DockSide dockSide)
|
||||
{
|
||||
_title = title;
|
||||
_subtitle = subtitle;
|
||||
|
||||
var hasTitle = !string.IsNullOrEmpty(title);
|
||||
var hasSubtitle = !string.IsNullOrEmpty(subtitle);
|
||||
|
||||
PreviewTitleText.Text = title;
|
||||
PreviewTitleText.Visibility = hasTitle ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
PreviewSubtitleText.Text = subtitle;
|
||||
PreviewSubtitleText.Visibility = hasSubtitle ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
PreviewTextPanel.Visibility = (hasTitle || hasSubtitle) ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
ShowTitleCheckBox.Visibility = hasTitle ? Visibility.Visible : Visibility.Collapsed;
|
||||
ShowTitleCheckBox.IsChecked = hasTitle;
|
||||
|
||||
ShowSubtitleCheckBox.Visibility = hasSubtitle ? Visibility.Visible : Visibility.Collapsed;
|
||||
ShowSubtitleCheckBox.IsChecked = hasSubtitle;
|
||||
|
||||
if (icon is not null)
|
||||
{
|
||||
PreviewIcon.SourceKey = icon;
|
||||
}
|
||||
|
||||
ApplyDockOrientation(dockSide);
|
||||
}
|
||||
|
||||
public static async System.Threading.Tasks.Task<(ContentDialogResult Result, PinToDockDialogContent Content)> ShowAsync(
|
||||
XamlRoot xamlRoot,
|
||||
string title,
|
||||
string subtitle,
|
||||
IconInfoViewModel? icon,
|
||||
DockSide dockSide)
|
||||
{
|
||||
var content = new PinToDockDialogContent();
|
||||
content.Configure(title, subtitle, icon, dockSide);
|
||||
|
||||
var dialog = new ContentDialog
|
||||
{
|
||||
Title = RS_.GetString("PinToDock_DialogTitle"),
|
||||
Content = content,
|
||||
PrimaryButtonText = RS_.GetString("PinToDock_PinButton"),
|
||||
CloseButtonText = RS_.GetString("PinToDock_CancelButton"),
|
||||
DefaultButton = ContentDialogButton.Primary,
|
||||
XamlRoot = xamlRoot,
|
||||
};
|
||||
|
||||
// Inner controls (Segmented, CheckBox) may consume the Enter key event,
|
||||
// preventing DefaultButton from activating. Handle it explicitly.
|
||||
var enterPressed = false;
|
||||
dialog.AddHandler(
|
||||
UIElement.KeyDownEvent,
|
||||
new KeyEventHandler((s, e) =>
|
||||
{
|
||||
if (e.Key == VirtualKey.Enter)
|
||||
{
|
||||
enterPressed = true;
|
||||
dialog.Hide();
|
||||
}
|
||||
}),
|
||||
true);
|
||||
|
||||
var result = await dialog.ShowAsync();
|
||||
|
||||
if (result == ContentDialogResult.None && enterPressed)
|
||||
{
|
||||
result = ContentDialogResult.Primary;
|
||||
}
|
||||
|
||||
return (result, content);
|
||||
}
|
||||
|
||||
private void ApplyDockOrientation(DockSide dockSide)
|
||||
{
|
||||
var isVertical = dockSide is DockSide.Left or DockSide.Right;
|
||||
|
||||
if (isVertical)
|
||||
{
|
||||
StartSegmentedItem.Content = RS_.GetString("PinToDock_Top");
|
||||
CenterSegmentedItem.Content = RS_.GetString("PinToDock_CenterLabel");
|
||||
EndSegmentedItem.Content = RS_.GetString("PinToDock_Bottom");
|
||||
}
|
||||
else
|
||||
{
|
||||
StartSegmentedItem.Content = RS_.GetString("PinToDock_Left");
|
||||
CenterSegmentedItem.Content = RS_.GetString("PinToDock_CenterLabel");
|
||||
EndSegmentedItem.Content = RS_.GetString("PinToDock_RightLabel");
|
||||
}
|
||||
|
||||
// Pick the 3 icon path strings based on dock orientation
|
||||
var (startKey, centerKey, endKey) = dockSide switch
|
||||
{
|
||||
DockSide.Top => ("TopStartPath", "TopCenterPath", "TopEndPath"),
|
||||
DockSide.Bottom => ("BottomStartPath", "BottomCenterPath", "BottomEndPath"),
|
||||
DockSide.Left => ("LeftStartPath", "LeftCenterPath", "LeftEndPath"),
|
||||
DockSide.Right => ("RightStartPath", "RightCenterPath", "RightEndPath"),
|
||||
_ => ("TopStartPath", "TopCenterPath", "TopEndPath"),
|
||||
};
|
||||
|
||||
StartSegmentedItem.Icon = CreatePathIcon((string)Resources[startKey]);
|
||||
CenterSegmentedItem.Icon = CreatePathIcon((string)Resources[centerKey]);
|
||||
EndSegmentedItem.Icon = CreatePathIcon((string)Resources[endKey]);
|
||||
}
|
||||
|
||||
private static PathIcon CreatePathIcon(string pathData)
|
||||
{
|
||||
var geometry = (Microsoft.UI.Xaml.Media.Geometry)Microsoft.UI.Xaml.Markup.XamlBindingHelper.ConvertValue(
|
||||
typeof(Microsoft.UI.Xaml.Media.Geometry), pathData);
|
||||
return new PathIcon { Data = geometry };
|
||||
}
|
||||
|
||||
private void OnLabelOptionChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (PreviewTitleText is null || PreviewSubtitleText is null ||
|
||||
PreviewTextPanel is null || ShowTitleCheckBox is null || ShowSubtitleCheckBox is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var showTitle = ShowTitleCheckBox.IsChecked == true;
|
||||
var showSubtitle = ShowSubtitleCheckBox.IsChecked == true;
|
||||
|
||||
PreviewTitleText.Text = showTitle ? _title : string.Empty;
|
||||
PreviewTitleText.Visibility = showTitle ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
PreviewSubtitleText.Text = showSubtitle ? _subtitle : string.Empty;
|
||||
PreviewSubtitleText.Visibility = showSubtitle ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
PreviewTextPanel.Visibility = (showTitle || showSubtitle) ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
@@ -156,21 +156,7 @@ public sealed partial class ListPage : Page,
|
||||
/// </summary>
|
||||
private int GetFirstSelectableIndex()
|
||||
{
|
||||
var items = ItemView.Items;
|
||||
if (items is null || items.Count == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (var i = 0; i < items.Count; i++)
|
||||
{
|
||||
if (!IsSeparator(items[i]))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
return FindSelectableIndex(0, 1);
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "VS is too aggressive at pruning methods bound in XAML")]
|
||||
@@ -604,9 +590,44 @@ public sealed partial class ListPage : Page,
|
||||
? Math.Min(itemCount - 1, currentIndex + Math.Max(1, itemsPerPage))
|
||||
: Math.Max(0, currentIndex - Math.Max(1, itemsPerPage));
|
||||
|
||||
targetIndex = FindSelectableIndexForPageNavigation(targetIndex, isPageDown);
|
||||
if (targetIndex == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (currentIndex, targetIndex);
|
||||
}
|
||||
|
||||
private int FindSelectableIndex(int startIndex, int step)
|
||||
{
|
||||
var items = ItemView.Items;
|
||||
var count = items.Count;
|
||||
if (count == 0 || step == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
startIndex = Math.Clamp(startIndex, 0, count - 1);
|
||||
|
||||
for (var i = startIndex; i >= 0 && i < count; i += step)
|
||||
{
|
||||
if (!IsSeparator(items[i]))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int FindSelectableIndexForPageNavigation(int targetIndex, bool isPageDown)
|
||||
{
|
||||
var step = isPageDown ? 1 : -1;
|
||||
var index = FindSelectableIndex(targetIndex, step);
|
||||
return index != -1 ? index : FindSelectableIndex(targetIndex - step, -step);
|
||||
}
|
||||
|
||||
private static void OnViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is ListPage @this)
|
||||
@@ -1045,39 +1066,24 @@ public sealed partial class ListPage : Page,
|
||||
/// </summary>
|
||||
private void NavigateUp()
|
||||
{
|
||||
var newIndex = ItemView.SelectedIndex;
|
||||
|
||||
if (ItemView.SelectedIndex > 0)
|
||||
if (ItemView.Items.Count == 0)
|
||||
{
|
||||
newIndex--;
|
||||
|
||||
while (
|
||||
newIndex >= 0 &&
|
||||
IsSeparator(ItemView.Items[newIndex]) &&
|
||||
newIndex != ItemView.SelectedIndex)
|
||||
{
|
||||
newIndex--;
|
||||
}
|
||||
|
||||
if (newIndex < 0)
|
||||
{
|
||||
newIndex = ItemView.Items.Count - 1;
|
||||
|
||||
while (
|
||||
newIndex >= 0 &&
|
||||
IsSeparator(ItemView.Items[newIndex]) &&
|
||||
newIndex != ItemView.SelectedIndex)
|
||||
{
|
||||
newIndex--;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newIndex = ItemView.Items.Count - 1;
|
||||
return;
|
||||
}
|
||||
|
||||
ItemView.SelectedIndex = newIndex;
|
||||
var newIndex = ItemView.SelectedIndex > 0
|
||||
? FindSelectableIndex(ItemView.SelectedIndex - 1, -1)
|
||||
: -1;
|
||||
|
||||
if (newIndex == -1)
|
||||
{
|
||||
newIndex = FindSelectableIndex(ItemView.Items.Count - 1, -1);
|
||||
}
|
||||
|
||||
if (newIndex != -1)
|
||||
{
|
||||
ItemView.SelectedIndex = newIndex;
|
||||
}
|
||||
}
|
||||
|
||||
private void Items_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
|
||||
@@ -1168,50 +1174,24 @@ public sealed partial class ListPage : Page,
|
||||
/// </summary>
|
||||
private void NavigateDown()
|
||||
{
|
||||
var newIndex = ItemView.SelectedIndex;
|
||||
|
||||
if (ItemView.SelectedIndex == ItemView.Items.Count - 1)
|
||||
if (ItemView.Items.Count == 0)
|
||||
{
|
||||
newIndex = 0;
|
||||
while (
|
||||
newIndex < ItemView.Items.Count &&
|
||||
IsSeparator(ItemView.Items[newIndex]))
|
||||
{
|
||||
newIndex++;
|
||||
}
|
||||
|
||||
if (newIndex >= ItemView.Items.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newIndex++;
|
||||
|
||||
while (
|
||||
newIndex < ItemView.Items.Count &&
|
||||
IsSeparator(ItemView.Items[newIndex]) &&
|
||||
newIndex != ItemView.SelectedIndex)
|
||||
{
|
||||
newIndex++;
|
||||
}
|
||||
|
||||
if (newIndex >= ItemView.Items.Count)
|
||||
{
|
||||
newIndex = 0;
|
||||
|
||||
while (
|
||||
newIndex < ItemView.Items.Count &&
|
||||
IsSeparator(ItemView.Items[newIndex]) &&
|
||||
newIndex != ItemView.SelectedIndex)
|
||||
{
|
||||
newIndex++;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ItemView.SelectedIndex = newIndex;
|
||||
var newIndex = ItemView.SelectedIndex < ItemView.Items.Count - 1
|
||||
? FindSelectableIndex(ItemView.SelectedIndex + 1, 1)
|
||||
: -1;
|
||||
|
||||
if (newIndex == -1)
|
||||
{
|
||||
newIndex = FindSelectableIndex(0, 1);
|
||||
}
|
||||
|
||||
if (newIndex != -1)
|
||||
{
|
||||
ItemView.SelectedIndex = newIndex;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsSeparator(object? item) => item is ListItemViewModel li && !li.IsInteractive;
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
xmlns:winuiex="using:WinUIEx"
|
||||
Width="800"
|
||||
Height="480"
|
||||
MinWidth="320"
|
||||
MinHeight="240"
|
||||
MinWidth="640"
|
||||
MinHeight="420"
|
||||
Activated="MainWindow_Activated"
|
||||
Closed="MainWindow_Closed"
|
||||
mc:Ignorable="d">
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Animations" />
|
||||
|
||||
@@ -26,7 +26,6 @@ using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using Windows.UI.Core;
|
||||
using WinUIEx;
|
||||
using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue;
|
||||
using VirtualKey = Windows.System.VirtualKey;
|
||||
@@ -51,6 +50,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
IRecipient<ShowToastMessage>,
|
||||
IRecipient<NavigateToPageMessage>,
|
||||
IRecipient<ShowHideDockMessage>,
|
||||
IRecipient<ShowPinToDockDialogMessage>,
|
||||
INotifyPropertyChanged,
|
||||
IDisposable
|
||||
{
|
||||
@@ -72,6 +72,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
|
||||
private CancellationTokenSource? _focusAfterLoadedCts;
|
||||
private WeakReference<Page>? _lastNavigatedPageRef;
|
||||
private bool _isDisposed;
|
||||
|
||||
public ShellViewModel ViewModel { get; private set; } = App.Current.Services.GetService<ShellViewModel>()!;
|
||||
|
||||
@@ -102,6 +103,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
WeakReferenceMessenger.Default.Register<NavigateToPageMessage>(this);
|
||||
|
||||
WeakReferenceMessenger.Default.Register<ShowHideDockMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<ShowPinToDockDialogMessage>(this);
|
||||
|
||||
AddHandler(PreviewKeyDownEvent, new KeyEventHandler(ShellPage_OnPreviewKeyDown), true);
|
||||
AddHandler(KeyDownEvent, new KeyEventHandler(ShellPage_OnKeyDown), false);
|
||||
@@ -210,6 +212,43 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
});
|
||||
}
|
||||
|
||||
public void Receive(ShowPinToDockDialogMessage message)
|
||||
{
|
||||
DispatcherQueue.TryEnqueue(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await HandlePinToDockDialogOnUiThread(message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex.ToString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async Task HandlePinToDockDialogOnUiThread(ShowPinToDockDialogMessage message)
|
||||
{
|
||||
var (result, content) = await PinToDockDialogContent.ShowAsync(
|
||||
this.XamlRoot,
|
||||
message.Title,
|
||||
message.Subtitle,
|
||||
message.Icon,
|
||||
message.DockSide);
|
||||
|
||||
if (result == ContentDialogResult.Primary)
|
||||
{
|
||||
var pinMessage = new PinToDockMessage(
|
||||
message.ProviderId,
|
||||
message.CommandId,
|
||||
Pin: true,
|
||||
Side: content.SelectedSide,
|
||||
ShowTitles: content.ShowTitles,
|
||||
ShowSubtitles: content.ShowSubtitles);
|
||||
WeakReferenceMessenger.Default.Send(pinMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// This gets called from the UI thread
|
||||
private async Task HandleConfirmArgsOnUiThread(IConfirmationArgs? args)
|
||||
{
|
||||
@@ -269,6 +308,11 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
{
|
||||
_ = DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OpenSettings(message.SettingsPageTag);
|
||||
});
|
||||
}
|
||||
@@ -440,20 +484,23 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
// However, then we have more fine-grained control on the back stack, managing the VM cache, and not
|
||||
// having that all be a black box, though then we wouldn't cache the XAML page itself, but sometimes that is a drawback.
|
||||
// However, we do a good job here, see ForwardStack.Clear below, and BackStack.Clear above about managing that.
|
||||
if (withAnimation)
|
||||
if (RootFrame.CanGoBack)
|
||||
{
|
||||
RootFrame.GoBack();
|
||||
}
|
||||
else
|
||||
{
|
||||
RootFrame.GoBack(_noAnimation);
|
||||
}
|
||||
if (withAnimation)
|
||||
{
|
||||
RootFrame.GoBack();
|
||||
}
|
||||
else
|
||||
{
|
||||
RootFrame.GoBack(_noAnimation);
|
||||
}
|
||||
|
||||
// Don't store pages we're navigating away from in the Frame cache
|
||||
// TODO: In the future we probably want a short cache (3-5?) of recent VMs in case the user re-navigates
|
||||
// back to a recent page they visited (like the Pokedex) so we don't have to reload it from scratch.
|
||||
// That'd be retrieved as we re-navigate in the PerformCommandMessage logic above
|
||||
RootFrame.ForwardStack.Clear();
|
||||
// Don't store pages we're navigating away from in the Frame cache
|
||||
// TODO: In the future we probably want a short cache (3-5?) of recent VMs in case the user re-navigates
|
||||
// back to a recent page they visited (like the Pokedex) so we don't have to reload it from scratch.
|
||||
// That'd be retrieved as we re-navigate in the PerformCommandMessage logic above
|
||||
RootFrame.ForwardStack.Clear();
|
||||
}
|
||||
|
||||
if (!RootFrame.CanGoBack)
|
||||
{
|
||||
@@ -489,6 +536,11 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
{
|
||||
_ = DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.ShowDock)
|
||||
{
|
||||
if (_dockWindow is null)
|
||||
@@ -790,10 +842,33 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
WeakReferenceMessenger.Default.UnregisterAll(this);
|
||||
|
||||
_focusAfterLoadedCts?.Cancel();
|
||||
_focusAfterLoadedCts?.Dispose();
|
||||
_focusAfterLoadedCts = null;
|
||||
|
||||
_dockWindow?.Dispose();
|
||||
var dockWindow = _dockWindow;
|
||||
_dockWindow = null;
|
||||
|
||||
if (dockWindow is not null)
|
||||
{
|
||||
if (DispatcherQueue.HasThreadAccess)
|
||||
{
|
||||
dockWindow.Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
DispatcherQueue.TryEnqueue(dockWindow.Close);
|
||||
}
|
||||
}
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,6 +216,7 @@
|
||||
<ItemsRepeater
|
||||
x:Name="ProvidersRepeater"
|
||||
x:Load="{x:Bind viewModel.Extensions.HasResults, Mode=OneWay}"
|
||||
GettingFocus="ProvidersRepeater_GettingFocus"
|
||||
ItemsSource="{x:Bind viewModel.Extensions.FilteredProviders, Mode=OneWay}"
|
||||
Layout="{StaticResource VerticalStackLayout}">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
@@ -224,6 +225,7 @@
|
||||
Click="SettingsCard_Click"
|
||||
DataContext="{x:Bind}"
|
||||
Description="{x:Bind ExtensionSubtext, Mode=OneWay}"
|
||||
GotFocus="SettingsCard_GotFocus"
|
||||
Header="{x:Bind DisplayName, Mode=OneWay}"
|
||||
IsClickEnabled="True">
|
||||
<controls:SettingsCard.HeaderIcon>
|
||||
|
||||
@@ -19,6 +19,7 @@ public sealed partial class ExtensionsPage : Page
|
||||
private readonly TaskScheduler _mainTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
|
||||
|
||||
private readonly SettingsViewModel? viewModel;
|
||||
private int _lastFocusedIndex;
|
||||
|
||||
public ExtensionsPage()
|
||||
{
|
||||
@@ -58,4 +59,157 @@ public sealed partial class ExtensionsPage : Page
|
||||
Logger.LogError("Error when showing FallbackRankerDialog", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void SettingsCard_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Track focus whenever any part of the card gets focus (including children like ToggleSwitch)
|
||||
if (sender is SettingsCard card && viewModel is not null)
|
||||
{
|
||||
var dataContext = card.DataContext as ProviderSettingsViewModel;
|
||||
if (dataContext is not null)
|
||||
{
|
||||
var filteredProviders = viewModel.Extensions.FilteredProviders;
|
||||
var index = filteredProviders.IndexOf(dataContext);
|
||||
if (index >= 0)
|
||||
{
|
||||
_lastFocusedIndex = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProvidersRepeater_GettingFocus(UIElement sender, GettingFocusEventArgs args)
|
||||
{
|
||||
if (viewModel is null || ProvidersRepeater is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Only intervene when focus is coming into the ItemsRepeater from outside
|
||||
if (args.OldFocusedElement != null && IsElementInsideRepeater(args.OldFocusedElement))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var filteredProviders = viewModel.Extensions.FilteredProviders;
|
||||
|
||||
if (filteredProviders.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the last focused index, defaulting to 0
|
||||
var index = _lastFocusedIndex;
|
||||
if (index < 0 || index >= filteredProviders.Count)
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
|
||||
// Check if WinUI is trying to focus something other than our target
|
||||
var shouldIntervene = false;
|
||||
|
||||
// If direction is Previous (Shift+Tab), we need to intervene
|
||||
if (args.Direction == FocusNavigationDirection.Previous || args.Direction == FocusNavigationDirection.Up)
|
||||
{
|
||||
shouldIntervene = true;
|
||||
}
|
||||
|
||||
// Also intervene if the NewFocusedElement is not at our target index
|
||||
else if (args.NewFocusedElement is DependencyObject newFocus)
|
||||
{
|
||||
// Check if the new focus element is inside our target card
|
||||
var targetCard = ProvidersRepeater.TryGetElement(index) as SettingsCard;
|
||||
if (targetCard != null && !IsElementInsideCard(newFocus, targetCard))
|
||||
{
|
||||
shouldIntervene = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldIntervene)
|
||||
{
|
||||
// Ensure the target element is realized before trying to focus it
|
||||
ProvidersRepeater.GetOrCreateElement(index);
|
||||
|
||||
// Get the target card
|
||||
var targetCard = ProvidersRepeater.TryGetElement(index) as SettingsCard;
|
||||
|
||||
if (targetCard != null)
|
||||
{
|
||||
// For shift-tab or wrong target, cancel and manually set focus
|
||||
args.TryCancel();
|
||||
args.Handled = true;
|
||||
|
||||
// Set focus asynchronously to the target card and scroll it into view
|
||||
_ = targetCard.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal, () =>
|
||||
{
|
||||
targetCard.Focus(FocusState.Keyboard);
|
||||
BringCardIntoView(targetCard);
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// For normal Tab forward, just redirect
|
||||
ProvidersRepeater.GetOrCreateElement(index);
|
||||
var targetCard = ProvidersRepeater.TryGetElement(index) as SettingsCard;
|
||||
|
||||
if (targetCard != null)
|
||||
{
|
||||
args.TrySetNewFocusedElement(targetCard);
|
||||
args.Handled = true;
|
||||
|
||||
// Set focus asynchronously to the target card and scroll it into view
|
||||
_ = targetCard.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
BringCardIntoView(targetCard);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void BringCardIntoView(SettingsCard card)
|
||||
{
|
||||
card.StartBringIntoView(new BringIntoViewOptions
|
||||
{
|
||||
AnimationDesired = true,
|
||||
VerticalAlignmentRatio = 0.5, // Center vertically
|
||||
});
|
||||
}
|
||||
|
||||
private bool IsElementInsideCard(DependencyObject element, SettingsCard card)
|
||||
{
|
||||
var parent = element;
|
||||
while (parent != null)
|
||||
{
|
||||
if (ReferenceEquals(parent, card))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
parent = Microsoft.UI.Xaml.Media.VisualTreeHelper.GetParent(parent);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsElementInsideRepeater(object element)
|
||||
{
|
||||
if (element is not DependencyObject depObj)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var parent = depObj;
|
||||
while (parent != null)
|
||||
{
|
||||
if (ReferenceEquals(parent, ProvidersRepeater))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
parent = Microsoft.UI.Xaml.Media.VisualTreeHelper.GetParent(parent);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -989,4 +989,68 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
||||
<value>NEW</value>
|
||||
<comment>Must be all caps</comment>
|
||||
</data>
|
||||
<data name="PinToDock_DialogTitle" xml:space="preserve">
|
||||
<value>Pin to Dock</value>
|
||||
<comment>Title for the pin to dock configuration dialog</comment>
|
||||
</data>
|
||||
<data name="PinToDock_PreviewHeader.Text" xml:space="preserve">
|
||||
<value>Preview</value>
|
||||
<comment>Header for the preview section in the pin to dock dialog</comment>
|
||||
</data>
|
||||
<data name="PinToDock_SectionHeader.Text" xml:space="preserve">
|
||||
<value>Choose where to pin this command</value>
|
||||
<comment>Header for the dock section selector in the pin to dock dialog</comment>
|
||||
</data>
|
||||
<data name="PinToDock_Start.Content" xml:space="preserve">
|
||||
<value>Start</value>
|
||||
<comment>Start section option in pin to dock dialog</comment>
|
||||
</data>
|
||||
<data name="PinToDock_Center.Content" xml:space="preserve">
|
||||
<value>Center</value>
|
||||
<comment>Center section option in pin to dock dialog</comment>
|
||||
</data>
|
||||
<data name="PinToDock_End.Content" xml:space="preserve">
|
||||
<value>End</value>
|
||||
<comment>End section option in pin to dock dialog</comment>
|
||||
</data>
|
||||
<data name="PinToDock_ShowTitle.Content" xml:space="preserve">
|
||||
<value>Show title</value>
|
||||
<comment>Checkbox label to show the title of a pinned dock item</comment>
|
||||
</data>
|
||||
<data name="PinToDock_ShowSubtitle.Content" xml:space="preserve">
|
||||
<value>Show subtitle</value>
|
||||
<comment>Checkbox label to show the subtitle of a pinned dock item</comment>
|
||||
</data>
|
||||
<data name="PinToDock_PinButton" xml:space="preserve">
|
||||
<value>Pin</value>
|
||||
<comment>Button text to confirm pinning an item to the dock</comment>
|
||||
</data>
|
||||
<data name="PinToDock_CancelButton" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
<comment>Button text to cancel pinning an item to the dock</comment>
|
||||
</data>
|
||||
<data name="PinToDock_Left" xml:space="preserve">
|
||||
<value>Left</value>
|
||||
<comment>Left section option in pin to dock dialog (horizontal dock)</comment>
|
||||
</data>
|
||||
<data name="PinToDock_Right" xml:space="preserve">
|
||||
<value>Right</value>
|
||||
<comment>Right section option in pin to dock dialog (horizontal dock)</comment>
|
||||
</data>
|
||||
<data name="PinToDock_Top" xml:space="preserve">
|
||||
<value>Top</value>
|
||||
<comment>Top section option in pin to dock dialog (vertical dock)</comment>
|
||||
</data>
|
||||
<data name="PinToDock_Bottom" xml:space="preserve">
|
||||
<value>Bottom</value>
|
||||
<comment>Bottom section option in pin to dock dialog (vertical dock)</comment>
|
||||
</data>
|
||||
<data name="PinToDock_CenterLabel" xml:space="preserve">
|
||||
<value>Center</value>
|
||||
<comment>Center section label in pin to dock dialog (code access)</comment>
|
||||
</data>
|
||||
<data name="PinToDock_RightLabel" xml:space="preserve">
|
||||
<value>Right</value>
|
||||
<comment>Right section label in pin to dock dialog (code access, horizontal end)</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -68,7 +68,7 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
{
|
||||
var settings = new Settings();
|
||||
var page = new TimeDateExtensionPage(settings);
|
||||
page.UpdateSearchText(string.Empty, input);
|
||||
page.SearchText = input;
|
||||
var resultLists = page.GetItems();
|
||||
|
||||
var result = Query(input, resultLists);
|
||||
@@ -117,7 +117,7 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
{
|
||||
var settings = new Settings();
|
||||
var page = new TimeDateExtensionPage(settings);
|
||||
page.UpdateSearchText(string.Empty, input);
|
||||
page.SearchText = input;
|
||||
var resultLists = page.GetItems();
|
||||
|
||||
var firstItem = resultLists.FirstOrDefault();
|
||||
@@ -158,7 +158,7 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
{
|
||||
var settings = new Settings();
|
||||
var page = new TimeDateExtensionPage(settings);
|
||||
page.UpdateSearchText(string.Empty, query);
|
||||
page.SearchText = query;
|
||||
var results = page.GetItems();
|
||||
|
||||
// Assert
|
||||
@@ -176,7 +176,8 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
{
|
||||
var settings = new Settings();
|
||||
var page = new TimeDateExtensionPage(settings);
|
||||
page.UpdateSearchText("abc", input);
|
||||
page.SearchText = "abc";
|
||||
page.SearchText = input;
|
||||
var results = page.GetItems();
|
||||
|
||||
// Assert
|
||||
@@ -193,7 +194,7 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
{
|
||||
var settings = new Settings();
|
||||
var page = new TimeDateExtensionPage(settings);
|
||||
page.UpdateSearchText(string.Empty, query);
|
||||
page.SearchText = query;
|
||||
var resultsList = page.GetItems();
|
||||
var results = Query(query, resultsList);
|
||||
|
||||
@@ -211,7 +212,7 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
{
|
||||
var settings = new Settings();
|
||||
var page = new TimeDateExtensionPage(settings);
|
||||
page.UpdateSearchText(string.Empty, query);
|
||||
page.SearchText = query;
|
||||
var resultsList = page.GetItems();
|
||||
|
||||
// Assert
|
||||
@@ -219,4 +220,28 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
var firstResult = resultsList.FirstOrDefault();
|
||||
Assert.IsTrue(firstResult.Title.Contains(expectedResult, StringComparison.CurrentCulture), $"Delimiter query '{query}' result not match {expectedResult} current result {firstResult.Title}");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdateSearchTextMatchesSearchTextSetter()
|
||||
{
|
||||
var settings = new Settings();
|
||||
var pageUsingSetter = new TimeDateExtensionPage(settings);
|
||||
var pageUsingMethod = new TimeDateExtensionPage(settings);
|
||||
const string query = "time::12:30:45";
|
||||
|
||||
pageUsingSetter.SearchText = query;
|
||||
pageUsingMethod.UpdateSearchText(string.Empty, query);
|
||||
|
||||
var setterResults = pageUsingSetter.GetItems();
|
||||
var methodResults = pageUsingMethod.GetItems();
|
||||
|
||||
Assert.AreEqual(setterResults.Length, methodResults.Length, "UpdateSearchText should produce the same number of results as setting SearchText.");
|
||||
|
||||
var setterFirstItem = setterResults.FirstOrDefault();
|
||||
var methodFirstItem = methodResults.FirstOrDefault();
|
||||
Assert.IsNotNull(setterFirstItem);
|
||||
Assert.IsNotNull(methodFirstItem);
|
||||
Assert.AreEqual(setterFirstItem.Title, methodFirstItem.Title, "UpdateSearchText should keep the stored query in sync with SearchText.");
|
||||
Assert.AreEqual(setterFirstItem.Subtitle, methodFirstItem.Subtitle, "UpdateSearchText should keep the stored query in sync with SearchText.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Common.Text;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
@@ -76,4 +77,64 @@ public class CommandItemViewModelTests
|
||||
Assert.IsNotNull(viewModel.SecondaryCommand);
|
||||
Assert.AreEqual("Secondary", viewModel.SecondaryCommand.Name);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void LatePrimaryCommandCreation_AddsPrimaryToAllCommands()
|
||||
{
|
||||
// Reproduces issue where SlowInitializeProperties runs before a real primary command exists.
|
||||
// The late-arriving command should still create the synthetic primary context item and prepend it to AllCommands.
|
||||
var pageContext = new TestPageContext();
|
||||
var item = new CommandItem()
|
||||
{
|
||||
Command = null,
|
||||
MoreCommands =
|
||||
[
|
||||
new CommandContextItem(new NoOpCommand { Name = "Secondary" }),
|
||||
],
|
||||
};
|
||||
|
||||
var viewModel = new CommandItemViewModel(new(item), new(pageContext), DefaultContextMenuFactory.Instance);
|
||||
viewModel.SlowInitializeProperties();
|
||||
|
||||
Assert.AreEqual(1, viewModel.AllCommands.Count);
|
||||
Assert.AreEqual("Secondary", ((CommandContextItemViewModel)viewModel.AllCommands[0]).Name);
|
||||
|
||||
item.Command = new NoOpCommand { Name = "Primary" };
|
||||
|
||||
Assert.AreEqual(2, viewModel.AllCommands.Count);
|
||||
Assert.AreEqual("Primary", ((CommandContextItemViewModel)viewModel.AllCommands[0]).Name);
|
||||
Assert.AreEqual("Secondary", ((CommandContextItemViewModel)viewModel.AllCommands[1]).Name);
|
||||
Assert.IsTrue(viewModel.HasMoreCommands);
|
||||
Assert.AreEqual("Secondary", viewModel.SecondaryCommand?.Name);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SyntheticPrimaryContextItem_UpdatesSubtitleAndCachedSubtitleTarget()
|
||||
{
|
||||
// The synthetic primary context item copies subtitle state from the parent CommandItemViewModel.
|
||||
// When subtitle changes later, both the exposed subtitle and its cached fuzzy-search target must refresh.
|
||||
var pageContext = new TestPageContext();
|
||||
var item = new CommandItem(new NoOpCommand { Name = "Primary" })
|
||||
{
|
||||
Subtitle = "before",
|
||||
MoreCommands =
|
||||
[
|
||||
new CommandContextItem(new NoOpCommand { Name = "Secondary" }),
|
||||
],
|
||||
};
|
||||
|
||||
var viewModel = new CommandItemViewModel(new(item), new(pageContext), DefaultContextMenuFactory.Instance);
|
||||
viewModel.SlowInitializeProperties();
|
||||
|
||||
var primaryContextItem = (CommandContextItemViewModel)viewModel.AllCommands[0];
|
||||
var matcher = new PrecomputedFuzzyMatcher(new PrecomputedFuzzyMatcherOptions());
|
||||
|
||||
Assert.AreEqual("before", primaryContextItem.Subtitle);
|
||||
Assert.AreEqual("before", primaryContextItem.GetSubtitleTarget(matcher).Original);
|
||||
|
||||
item.Subtitle = "after unique";
|
||||
|
||||
Assert.AreEqual("after unique", primaryContextItem.Subtitle);
|
||||
Assert.AreEqual("after unique", primaryContextItem.GetSubtitleTarget(matcher).Original);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
// 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.CommandPalette.Extensions.Toolkit;
|
||||
using PowerToysExtension.Helpers;
|
||||
using PowerToysExtension.Properties;
|
||||
|
||||
namespace PowerToysExtension.Commands;
|
||||
|
||||
internal sealed partial class ToggleKeyboardManagerListeningCommand : InvokableCommand
|
||||
{
|
||||
public ToggleKeyboardManagerListeningCommand()
|
||||
{
|
||||
Name = "Toggle Keyboard Manager active state";
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
return KeyboardManagerStateService.TryToggleListening()
|
||||
? CommandResult.KeepOpen()
|
||||
: CommandResult.ShowToast(Resources.ResourceManager.GetString("KeyboardManager_ToggleListening_Error", Resources.Culture) ?? "Keyboard Manager is unavailable. Try enabling it in PowerToys settings.");
|
||||
}
|
||||
}
|
||||
@@ -43,21 +43,6 @@ internal static class KeyboardManagerStateService
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool TryToggleListening()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var evt = EventWaitHandle.OpenExisting(Constants.ToggleKeyboardManagerActiveEvent());
|
||||
var signaled = evt.Set();
|
||||
PollStatus();
|
||||
return signaled;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void PollStatus()
|
||||
{
|
||||
var isListening = IsListening();
|
||||
|
||||
@@ -14,11 +14,6 @@ internal static class PowerToysResourcesHelper
|
||||
|
||||
internal static IconInfo IconFromSettingsIcon(string fileName) => IconHelpers.FromRelativePath($"{SettingsIconRoot}{fileName}");
|
||||
|
||||
internal static IconInfo KeyboardManagerListeningIcon(bool isListening) => IconHelpers.FromRelativePath(
|
||||
isListening
|
||||
? $"{AssetsRoot}KeyboardManager\\KeyboardManagerListeningOn.svg"
|
||||
: $"{AssetsRoot}KeyboardManager\\KeyboardManagerListeningOff.svg");
|
||||
|
||||
#if DEBUG
|
||||
public static IconInfo ProviderIcon() => IconFromSettingsIcon("PowerToys.dark.png");
|
||||
#else
|
||||
|
||||
@@ -22,22 +22,9 @@ internal sealed class KeyboardManagerModuleCommandProvider : ModuleCommandProvid
|
||||
var title = module.ModuleDisplayName();
|
||||
var icon = module.ModuleIcon();
|
||||
|
||||
if (ModuleEnablementService.IsModuleEnabled(module))
|
||||
{
|
||||
var isListening = KeyboardManagerStateService.IsListening();
|
||||
yield return new ListItem(new ToggleKeyboardManagerListeningCommand() { Id = "com.microsoft.powertoys.keyboardManager.toggleListening" })
|
||||
{
|
||||
Title = GetResourceString("KeyboardManager_ToggleListening_Title", "Keyboard Manager: Toggle active state"),
|
||||
Subtitle = isListening
|
||||
? GetResourceString("KeyboardManager_ToggleListening_On_Subtitle", "Keyboard Manager is active. Invoke to stop listening.")
|
||||
: GetResourceString("KeyboardManager_ToggleListening_Off_Subtitle", "Keyboard Manager is paused. Invoke to start listening."),
|
||||
Icon = PowerToysResourcesHelper.KeyboardManagerListeningIcon(isListening),
|
||||
};
|
||||
}
|
||||
|
||||
if (IsUseNewEditorEnabled())
|
||||
{
|
||||
yield return new ListItem(new OpenNewKeyboardManagerEditorCommand())
|
||||
yield return new ListItem(new OpenNewKeyboardManagerEditorCommand() { Id = "com.microsoft.powertoys.keyboardManager.openNewEditor" })
|
||||
{
|
||||
Title = Resources.KeyboardManager_OpenNewEditor_Title,
|
||||
Subtitle = Resources.KeyboardManager_OpenNewEditor_Subtitle,
|
||||
|
||||
@@ -414,18 +414,6 @@
|
||||
<data name="KeyboardManager_OpenNewEditor_Subtitle" xml:space="preserve">
|
||||
<value>Open the Keyboard Manager remap editor</value>
|
||||
</data>
|
||||
<data name="KeyboardManager_ToggleListening_Title" xml:space="preserve">
|
||||
<value>Keyboard Manager: Toggle active state</value>
|
||||
</data>
|
||||
<data name="KeyboardManager_ToggleListening_On_Subtitle" xml:space="preserve">
|
||||
<value>Keyboard Manager is active. Invoke to stop listening.</value>
|
||||
</data>
|
||||
<data name="KeyboardManager_ToggleListening_Off_Subtitle" xml:space="preserve">
|
||||
<value>Keyboard Manager is paused. Invoke to start listening.</value>
|
||||
</data>
|
||||
<data name="KeyboardManager_ToggleListening_Error" xml:space="preserve">
|
||||
<value>Keyboard Manager is unavailable. Try enabling it in PowerToys settings.</value>
|
||||
</data>
|
||||
<!-- Light Switch Module -->
|
||||
<data name="LightSwitch_Toggle_Title" xml:space="preserve">
|
||||
<value>Light Switch: Toggle theme</value>
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.CmdPal.Ext.TimeDate.Helpers;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
@@ -14,12 +11,7 @@ namespace Microsoft.CmdPal.Ext.TimeDate.Pages;
|
||||
|
||||
internal sealed partial class TimeDateExtensionPage : DynamicListPage
|
||||
{
|
||||
private readonly Lock _resultsLock = new();
|
||||
|
||||
private IList<ListItem> _results = new List<ListItem>();
|
||||
private bool _dataLoaded;
|
||||
|
||||
private ISettingsInterface _settingsManager;
|
||||
private readonly ISettingsInterface _settingsManager;
|
||||
|
||||
public TimeDateExtensionPage(ISettingsInterface settingsManager)
|
||||
{
|
||||
@@ -33,39 +25,10 @@ internal sealed partial class TimeDateExtensionPage : DynamicListPage
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
ListItem[] results;
|
||||
lock (_resultsLock)
|
||||
{
|
||||
if (_dataLoaded)
|
||||
{
|
||||
results = _results.ToArray();
|
||||
_dataLoaded = false;
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
DoExecuteSearch(string.Empty);
|
||||
|
||||
lock (_resultsLock)
|
||||
{
|
||||
results = _results.ToArray();
|
||||
_dataLoaded = false;
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateSearchText(string oldSearch, string newSearch)
|
||||
{
|
||||
DoExecuteSearch(newSearch);
|
||||
}
|
||||
|
||||
private void DoExecuteSearch(string query)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = TimeDateCalculator.ExecuteSearch(_settingsManager, query);
|
||||
UpdateResult(result);
|
||||
return [.. TimeDateCalculator.ExecuteSearch(_settingsManager, SearchText)];
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@@ -74,23 +37,13 @@ internal sealed partial class TimeDateExtensionPage : DynamicListPage
|
||||
// So, we need to clean the result.
|
||||
// But in that time, empty result may cause exception.
|
||||
// So, we need to add at least on item to user.
|
||||
var items = new List<ListItem>
|
||||
{
|
||||
ResultHelper.CreateInvalidInputErrorResult(),
|
||||
};
|
||||
|
||||
UpdateResult(items);
|
||||
return [ResultHelper.CreateInvalidInputErrorResult()];
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateResult(IList<ListItem> result)
|
||||
public override void UpdateSearchText(string oldSearch, string newSearch)
|
||||
{
|
||||
lock (_resultsLock)
|
||||
{
|
||||
this._results = result;
|
||||
_dataLoaded = true;
|
||||
}
|
||||
|
||||
RaiseItemsChanged(this._results.Count);
|
||||
SetSearchNoUpdate(newSearch);
|
||||
RaiseItemsChanged(-2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -564,7 +564,7 @@ namespace KeyboardManagerEditorUI.Controls
|
||||
return CurrentActionType switch
|
||||
{
|
||||
ActionType.KeyOrShortcut => _actionKeys.Count > 0,
|
||||
ActionType.Text => !string.IsNullOrWhiteSpace(TextContentBox?.Text),
|
||||
ActionType.Text => !string.IsNullOrEmpty(TextContentBox?.Text),
|
||||
ActionType.OpenUrl => !string.IsNullOrWhiteSpace(UrlPathInput?.Text),
|
||||
ActionType.OpenApp => !string.IsNullOrWhiteSpace(ProgramPathInput?.Text),
|
||||
_ => false,
|
||||
|
||||
@@ -90,7 +90,7 @@ namespace KeyboardManagerEditorUI.Helpers
|
||||
return ValidationErrorType.EmptyOriginalKeys;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(textContent))
|
||||
if (string.IsNullOrEmpty(textContent))
|
||||
{
|
||||
return ValidationErrorType.EmptyTargetText;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ namespace
|
||||
const wchar_t JSON_KEY_CTRL[] = L"ctrl";
|
||||
const wchar_t JSON_KEY_SHIFT[] = L"shift";
|
||||
const wchar_t JSON_KEY_CODE[] = L"code";
|
||||
const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"ToggleShortcut";
|
||||
const wchar_t JSON_KEY_EDITOR_SHORTCUT[] = L"EditorShortcut";
|
||||
const wchar_t JSON_KEY_USE_NEW_EDITOR[] = L"useNewEditor";
|
||||
}
|
||||
@@ -57,25 +56,21 @@ private:
|
||||
//contains the non localized key of the powertoy
|
||||
std::wstring app_key = KeyboardManagerConstants::ModuleName;
|
||||
|
||||
// Hotkey for toggling the module
|
||||
Hotkey m_hotkey = { .key = 0 };
|
||||
|
||||
// Hotkey for opening the editor
|
||||
Hotkey m_editorHotkey = { .key = 0 };
|
||||
|
||||
// Whether to use the new WinUI3 editor
|
||||
bool m_useNewEditor = false;
|
||||
|
||||
ULONGLONG m_lastHotkeyToggleTime = 0;
|
||||
ULONGLONG m_lastHotkeyTime = 0;
|
||||
|
||||
HANDLE m_hProcess = nullptr;
|
||||
HANDLE m_hEditorProcess = nullptr;
|
||||
|
||||
HANDLE m_hTerminateEngineEvent = nullptr;
|
||||
HANDLE m_open_new_editor_event_handle{ nullptr };
|
||||
HANDLE m_toggle_active_event_handle{ nullptr };
|
||||
std::thread m_toggle_thread;
|
||||
std::atomic<bool> m_toggle_thread_running{ false };
|
||||
std::thread m_editor_listener_thread;
|
||||
std::atomic<bool> m_editor_listener_running{ false };
|
||||
|
||||
|
||||
void refresh_process_state()
|
||||
@@ -88,19 +83,6 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void toggle_engine()
|
||||
{
|
||||
refresh_process_state();
|
||||
if (m_active)
|
||||
{
|
||||
stop_engine();
|
||||
}
|
||||
else
|
||||
{
|
||||
start_engine();
|
||||
}
|
||||
}
|
||||
|
||||
bool start_engine()
|
||||
{
|
||||
refresh_process_state();
|
||||
@@ -176,35 +158,9 @@ private:
|
||||
void parse_hotkey(PowerToysSettings::PowerToyValues& settings)
|
||||
{
|
||||
auto settingsObject = settings.get_raw_json();
|
||||
if (settingsObject.GetView().Size())
|
||||
{
|
||||
try
|
||||
{
|
||||
auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES)
|
||||
.GetNamedObject(JSON_KEY_ACTIVATION_SHORTCUT);
|
||||
m_hotkey.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
|
||||
m_hotkey.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
|
||||
m_hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
|
||||
m_hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
|
||||
m_hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::error("Failed to initialize Keyboard Manager toggle shortcut");
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_hotkey.key)
|
||||
{
|
||||
// Set default: Win+Shift+K
|
||||
m_hotkey.win = true;
|
||||
m_hotkey.shift = true;
|
||||
m_hotkey.ctrl = false;
|
||||
m_hotkey.alt = false;
|
||||
m_hotkey.key = 'K';
|
||||
}
|
||||
|
||||
// Parse editor shortcut
|
||||
bool editorShortcutParsed = false;
|
||||
if (settingsObject.GetView().Size())
|
||||
{
|
||||
try
|
||||
@@ -216,6 +172,7 @@ private:
|
||||
m_editorHotkey.shift = jsonEditorHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
|
||||
m_editorHotkey.ctrl = jsonEditorHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
|
||||
m_editorHotkey.key = static_cast<unsigned char>(jsonEditorHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
|
||||
editorShortcutParsed = true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@@ -223,9 +180,9 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_editorHotkey.key)
|
||||
if (!editorShortcutParsed && !m_editorHotkey.key)
|
||||
{
|
||||
// Set default: Win+Shift+Q
|
||||
// Set default: Win+Shift+Q (only when no setting exists)
|
||||
m_editorHotkey.win = true;
|
||||
m_editorHotkey.shift = true;
|
||||
m_editorHotkey.ctrl = false;
|
||||
@@ -287,7 +244,6 @@ public:
|
||||
}
|
||||
|
||||
m_open_new_editor_event_handle = CreateDefaultEvent(CommonSharedConstants::OPEN_NEW_KEYBOARD_MANAGER_EVENT);
|
||||
m_toggle_active_event_handle = CreateDefaultEvent(CommonSharedConstants::TOGGLE_KEYBOARD_MANAGER_ACTIVE_EVENT);
|
||||
|
||||
init_settings();
|
||||
};
|
||||
@@ -306,11 +262,6 @@ public:
|
||||
CloseHandle(m_open_new_editor_event_handle);
|
||||
m_open_new_editor_event_handle = nullptr;
|
||||
}
|
||||
if (m_toggle_active_event_handle)
|
||||
{
|
||||
CloseHandle(m_toggle_active_event_handle);
|
||||
m_toggle_active_event_handle = nullptr;
|
||||
}
|
||||
if (m_hEditorProcess)
|
||||
{
|
||||
CloseHandle(m_hEditorProcess);
|
||||
@@ -412,22 +363,12 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return the invocation hotkeys for toggling and opening the editor
|
||||
// Return the invocation hotkey for opening the editor
|
||||
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
|
||||
{
|
||||
size_t count = 0;
|
||||
|
||||
// Hotkey 0: toggle engine
|
||||
if (m_hotkey.key)
|
||||
{
|
||||
if (hotkeys && buffer_size > count)
|
||||
{
|
||||
hotkeys[count] = m_hotkey;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
// Hotkey 1: open editor (only when using new editor)
|
||||
// Hotkey 0: open editor (only when using new editor)
|
||||
if (m_useNewEditor && m_editorHotkey.key)
|
||||
{
|
||||
if (hotkeys && buffer_size > count)
|
||||
@@ -442,69 +383,44 @@ public:
|
||||
|
||||
void StartOpenEditorListener()
|
||||
{
|
||||
if (m_toggle_thread_running || (!m_open_new_editor_event_handle && !m_toggle_active_event_handle))
|
||||
if (m_editor_listener_running || !m_open_new_editor_event_handle)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_toggle_thread_running = true;
|
||||
m_toggle_thread = std::thread([this]() {
|
||||
HANDLE handles[2]{};
|
||||
DWORD handle_count = 0;
|
||||
DWORD open_editor_index = MAXDWORD;
|
||||
DWORD toggle_active_index = MAXDWORD;
|
||||
|
||||
if (m_open_new_editor_event_handle)
|
||||
m_editor_listener_running = true;
|
||||
m_editor_listener_thread = std::thread([this]() {
|
||||
while (m_editor_listener_running)
|
||||
{
|
||||
open_editor_index = handle_count;
|
||||
handles[handle_count++] = m_open_new_editor_event_handle;
|
||||
}
|
||||
|
||||
if (m_toggle_active_event_handle)
|
||||
{
|
||||
toggle_active_index = handle_count;
|
||||
handles[handle_count++] = m_toggle_active_event_handle;
|
||||
}
|
||||
|
||||
while (m_toggle_thread_running)
|
||||
{
|
||||
const DWORD wait_result = WaitForMultipleObjects(handle_count, handles, FALSE, 500);
|
||||
if (!m_toggle_thread_running)
|
||||
const DWORD wait_result = WaitForSingleObject(m_open_new_editor_event_handle, 500);
|
||||
if (!m_editor_listener_running)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (open_editor_index != MAXDWORD && wait_result == (WAIT_OBJECT_0 + open_editor_index))
|
||||
if (wait_result == WAIT_OBJECT_0)
|
||||
{
|
||||
launch_editor();
|
||||
}
|
||||
else if (toggle_active_index != MAXDWORD && wait_result == (WAIT_OBJECT_0 + toggle_active_index))
|
||||
{
|
||||
toggle_engine();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void StopOpenEditorListener()
|
||||
{
|
||||
if (!m_toggle_thread_running)
|
||||
if (!m_editor_listener_running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_toggle_thread_running = false;
|
||||
m_editor_listener_running = false;
|
||||
if (m_open_new_editor_event_handle)
|
||||
{
|
||||
SetEvent(m_open_new_editor_event_handle);
|
||||
}
|
||||
if (m_toggle_active_event_handle)
|
||||
if (m_editor_listener_thread.joinable())
|
||||
{
|
||||
SetEvent(m_toggle_active_event_handle);
|
||||
}
|
||||
if (m_toggle_thread.joinable())
|
||||
{
|
||||
m_toggle_thread.join();
|
||||
m_editor_listener_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -584,22 +500,17 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr ULONGLONG hotkeyToggleDebounceMs = 500;
|
||||
constexpr ULONGLONG hotkeyDebounceMs = 500;
|
||||
const auto now = GetTickCount64();
|
||||
if (now - m_lastHotkeyToggleTime < hotkeyToggleDebounceMs)
|
||||
if (now - m_lastHotkeyTime < hotkeyDebounceMs)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
m_lastHotkeyToggleTime = now;
|
||||
m_lastHotkeyTime = now;
|
||||
|
||||
if (hotkeyId == 0)
|
||||
{
|
||||
// Toggle engine on/off
|
||||
toggle_engine();
|
||||
}
|
||||
else if (hotkeyId == 1)
|
||||
{
|
||||
// Open the new editor (only in new editor mode)
|
||||
// Open the editor
|
||||
launch_editor();
|
||||
}
|
||||
|
||||
|
||||
@@ -376,6 +376,7 @@ namespace PowerAccent.Core
|
||||
LetterKey.VK_A => new[] { "á", "æ" },
|
||||
LetterKey.VK_D => new[] { "ð" },
|
||||
LetterKey.VK_E => new[] { "é" },
|
||||
LetterKey.VK_I => new[] { "í" },
|
||||
LetterKey.VK_O => new[] { "ó", "ö" },
|
||||
LetterKey.VK_U => new[] { "ú" },
|
||||
LetterKey.VK_Y => new[] { "ý" },
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
public class AlwaysOnTopProperties
|
||||
{
|
||||
public static readonly HotkeySettings DefaultHotkeyValue = new HotkeySettings(true, true, false, false, 0x54);
|
||||
public static readonly HotkeySettings DefaultIncreaseOpacityHotkeyValue = new HotkeySettings(true, true, false, false, 0xBB);
|
||||
public static readonly HotkeySettings DefaultDecreaseOpacityHotkeyValue = new HotkeySettings(true, true, false, false, 0xBD);
|
||||
public const bool DefaultFrameEnabled = true;
|
||||
public const bool DefaultShowInSystemMenu = false;
|
||||
public const int DefaultFrameThickness = 15;
|
||||
@@ -24,6 +26,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
public AlwaysOnTopProperties()
|
||||
{
|
||||
Hotkey = new KeyboardKeysProperty(DefaultHotkeyValue);
|
||||
IncreaseOpacityHotkey = new KeyboardKeysProperty(DefaultIncreaseOpacityHotkeyValue);
|
||||
DecreaseOpacityHotkey = new KeyboardKeysProperty(DefaultDecreaseOpacityHotkeyValue);
|
||||
ShowInSystemMenu = new BoolProperty(DefaultShowInSystemMenu);
|
||||
FrameEnabled = new BoolProperty(DefaultFrameEnabled);
|
||||
FrameThickness = new IntProperty(DefaultFrameThickness);
|
||||
@@ -39,6 +43,12 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
[JsonPropertyName("hotkey")]
|
||||
public KeyboardKeysProperty Hotkey { get; set; }
|
||||
|
||||
[JsonPropertyName("increase-opacity-hotkey")]
|
||||
public KeyboardKeysProperty IncreaseOpacityHotkey { get; set; }
|
||||
|
||||
[JsonPropertyName("decrease-opacity-hotkey")]
|
||||
public KeyboardKeysProperty DecreaseOpacityHotkey { get; set; }
|
||||
|
||||
[JsonPropertyName("frame-enabled")]
|
||||
public BoolProperty FrameEnabled { get; set; }
|
||||
|
||||
|
||||
@@ -40,6 +40,14 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
() => Properties.Hotkey.Value,
|
||||
value => Properties.Hotkey.Value = value ?? AlwaysOnTopProperties.DefaultHotkeyValue,
|
||||
"AlwaysOnTop_ActivationShortcut"),
|
||||
new HotkeyAccessor(
|
||||
() => Properties.IncreaseOpacityHotkey.Value,
|
||||
value => Properties.IncreaseOpacityHotkey.Value = value ?? AlwaysOnTopProperties.DefaultIncreaseOpacityHotkeyValue,
|
||||
"AlwaysOnTop_IncreaseOpacityShortcut"),
|
||||
new HotkeyAccessor(
|
||||
() => Properties.DecreaseOpacityHotkey.Value,
|
||||
value => Properties.DecreaseOpacityHotkey.Value = value ?? AlwaysOnTopProperties.DefaultDecreaseOpacityHotkeyValue,
|
||||
"AlwaysOnTop_DecreaseOpacityShortcut"),
|
||||
};
|
||||
|
||||
return hotkeyAccessors.ToArray();
|
||||
|
||||
@@ -21,20 +21,15 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
[CmdConfigureIgnoreAttribute]
|
||||
public GenericProperty<List<string>> KeyboardConfigurations { get; set; }
|
||||
|
||||
public HotkeySettings DefaultToggleShortcut => new HotkeySettings(true, false, false, true, 0x4B);
|
||||
|
||||
public HotkeySettings DefaultEditorShortcut => new HotkeySettings(true, false, false, true, 0x51);
|
||||
|
||||
public KeyboardManagerProperties()
|
||||
{
|
||||
ToggleShortcut = DefaultToggleShortcut;
|
||||
EditorShortcut = DefaultEditorShortcut;
|
||||
KeyboardConfigurations = new GenericProperty<List<string>>(new List<string> { "default", });
|
||||
ActiveConfiguration = new GenericProperty<string>("default");
|
||||
}
|
||||
|
||||
public HotkeySettings ToggleShortcut { get; set; }
|
||||
|
||||
public HotkeySettings EditorShortcut { get; set; }
|
||||
|
||||
[JsonPropertyName("useNewEditor")]
|
||||
|
||||
@@ -38,10 +38,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
var hotkeyAccessors = new List<HotkeyAccessor>
|
||||
{
|
||||
new HotkeyAccessor(
|
||||
() => Properties.ToggleShortcut,
|
||||
value => Properties.ToggleShortcut = value ?? Properties.DefaultToggleShortcut,
|
||||
"Toggle_Shortcut"),
|
||||
new HotkeyAccessor(
|
||||
() => Properties.EditorShortcut,
|
||||
value => Properties.EditorShortcut = value ?? Properties.DefaultEditorShortcut,
|
||||
|
||||
@@ -28,6 +28,12 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
[CmdConfigureIgnore]
|
||||
public static HotkeySettings DefaultSnipToggleKey => new HotkeySettings(false, true, false, false, '6'); // Ctrl+6
|
||||
|
||||
[CmdConfigureIgnore]
|
||||
public static HotkeySettings DefaultSnipOcrToggleKey => new HotkeySettings(false, true, true, false, '6'); // Ctrl+Alt+6
|
||||
|
||||
[CmdConfigureIgnore]
|
||||
public static HotkeySettings DefaultSnipPanoramaToggleKey => new HotkeySettings(false, true, false, false, '8'); // Ctrl+8
|
||||
|
||||
[CmdConfigureIgnore]
|
||||
public static HotkeySettings DefaultBreakTimerKey => new HotkeySettings(false, true, false, false, '3'); // Ctrl+3
|
||||
|
||||
@@ -44,6 +50,10 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
|
||||
public KeyboardKeysProperty SnipToggleKey { get; set; }
|
||||
|
||||
public KeyboardKeysProperty SnipOcrToggleKey { get; set; }
|
||||
|
||||
public KeyboardKeysProperty SnipPanoramaToggleKey { get; set; }
|
||||
|
||||
public KeyboardKeysProperty BreakTimerKey { get; set; }
|
||||
|
||||
public StringProperty Font { get; set; }
|
||||
@@ -96,5 +106,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
public BoolProperty MicMonoMix { get; set; }
|
||||
|
||||
public StringProperty MicrophoneDeviceId { get; set; }
|
||||
|
||||
public BoolProperty BreakLockWorkstation { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
|
||||
private void OpenSettingsItem_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e)
|
||||
{
|
||||
App.OpenSettingsWindow();
|
||||
App.OpenSettingsWindow(ensurePageIsSelected: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,13 +34,13 @@
|
||||
IsExpanded="True">
|
||||
<controls:ShortcutControl HotkeySettings="{x:Bind Path=ViewModel.Hotkey, Mode=TwoWay}" />
|
||||
<tkcontrols:SettingsExpander.Items>
|
||||
<tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard.Description>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<tkcontrols:MarkdownTextBlock Config="{StaticResource DescriptionTextMarkdownConfig}" Text="{x:Bind ViewModel.IncreaseOpacityShortcut, Mode=OneWay}" />
|
||||
<tkcontrols:MarkdownTextBlock Config="{StaticResource DescriptionTextMarkdownConfig}" Text="{x:Bind ViewModel.DecreaseOpacityShortcut, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</tkcontrols:SettingsCard.Description>
|
||||
<!-- HACK: For some weird reason, a ShortcutControl does not work correctly if it's the first or last item in the expander, so we add an invisible card. -->
|
||||
<tkcontrols:SettingsCard Visibility="Collapsed" />
|
||||
<tkcontrols:SettingsCard x:Uid="AlwaysOnTop_IncreaseOpacityShortcut">
|
||||
<controls:ShortcutControl HotkeySettings="{x:Bind Path=ViewModel.IncreaseOpacityHotkey, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard x:Uid="AlwaysOnTop_DecreaseOpacityShortcut">
|
||||
<controls:ShortcutControl HotkeySettings="{x:Bind Path=ViewModel.DecreaseOpacityHotkey, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left">
|
||||
<CheckBox x:Uid="AlwaysOnTop_GameMode" IsChecked="{x:Bind ViewModel.DoNotActivateOnGameMode, Mode=TwoWay}" />
|
||||
|
||||
@@ -62,28 +62,12 @@
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsExpander
|
||||
<tkcontrols:SettingsCard
|
||||
Name="KeyboardManagerEnableToggle"
|
||||
x:Uid="KeyboardManager_EnableToggle"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/KeyboardManager.png}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.Enabled, Mode=TwoWay}" />
|
||||
<tkcontrols:SettingsExpander.Description>
|
||||
<HyperlinkButton NavigateUri="https://aka.ms/powerToysCannotRemapKeys">
|
||||
<TextBlock x:Uid="KBM_KeysCannotBeRemapped" FontWeight="SemiBold" />
|
||||
</HyperlinkButton>
|
||||
</tkcontrols:SettingsExpander.Description>
|
||||
<tkcontrols:SettingsExpander.Items>
|
||||
<tkcontrols:SettingsCard
|
||||
Name="ToggleShortcut"
|
||||
x:Uid="KeyboardManager_Toggle_Shortcut"
|
||||
IsEnabled="{x:Bind ViewModel.Enabled, Mode=OneWay}">
|
||||
<controls:ShortcutControl
|
||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||
AllowDisable="True"
|
||||
HotkeySettings="{x:Bind Path=ViewModel.ToggleShortcut, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
</tkcontrols:SettingsExpander>
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
|
||||
<tkcontrols:SwitchPresenter TargetType="x:Boolean" Value="{x:Bind ViewModel.UseNewEditor, Mode=OneWay}">
|
||||
|
||||
@@ -240,6 +240,9 @@
|
||||
Visibility="{x:Bind ViewModel.BreakShowBackgroundFile, Mode=OneWay}">
|
||||
<CheckBox x:Uid="ZoomIt_Break_BackgroundStretch" IsChecked="{x:Bind ViewModel.BreakBackgroundStretch, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard Name="ZoomItBreakLockWorkstation" ContentAlignment="Left">
|
||||
<CheckBox x:Uid="ZoomIt_Break_LockWorkstation" IsChecked="{x:Bind ViewModel.BreakLockWorkstation, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard.Description>
|
||||
<tkcontrols:MarkdownTextBlock x:Uid="ZoomIt_BreakFAQ" Config="{StaticResource DescriptionTextMarkdownConfig}" />
|
||||
@@ -325,6 +328,22 @@
|
||||
</tkcontrols:SettingsCard>
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
</tkcontrols:SettingsExpander>
|
||||
<tkcontrols:SettingsExpander
|
||||
Name="ZoomItSnipOcrShortcut"
|
||||
x:Uid="ZoomIt_SnipOcr_Shortcut"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsExpanded="True">
|
||||
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind ViewModel.SnipOcrToggleKey, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsExpander>
|
||||
</controls:SettingsGroup>
|
||||
<controls:SettingsGroup x:Uid="ZoomIt_PanoramaGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsExpander
|
||||
Name="ZoomItPanoramaShortcut"
|
||||
x:Uid="ZoomIt_Panorama_Shortcut"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsExpanded="True">
|
||||
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind ViewModel.SnipPanoramaToggleKey, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsExpander>
|
||||
</controls:SettingsGroup>
|
||||
</StackPanel>
|
||||
</controls:SettingsPageControl.ModuleContent>
|
||||
|
||||
@@ -1909,12 +1909,6 @@ Made with 💗 by Microsoft and the PowerToys community.</value>
|
||||
</data>
|
||||
<data name="Activation_Shortcut.Description" xml:space="preserve">
|
||||
<value>Customize the shortcut to activate this module</value>
|
||||
</data>
|
||||
<data name="KeyboardManager_Toggle_Shortcut.Header" xml:space="preserve">
|
||||
<value>Shortcut</value>
|
||||
</data>
|
||||
<data name="KeyboardManager_Toggle_Shortcut.Description" xml:space="preserve">
|
||||
<value>Enable or disable this module (Note: the Settings UI will not update)</value>
|
||||
</data>
|
||||
<data name="KeyboardManager_Editor_Shortcut.Header" xml:space="preserve">
|
||||
<value>Editor shortcut</value>
|
||||
@@ -3054,11 +3048,17 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
||||
<data name="AlwaysOnTop_ActivationShortcut.Description" xml:space="preserve">
|
||||
<value>Customize the shortcut to pin or unpin an app window</value>
|
||||
</data>
|
||||
<data name="AlwaysOnTop_IncreaseOpacity" xml:space="preserve">
|
||||
<value>Press **{0}** to increase the opacity of the window</value>
|
||||
<data name="AlwaysOnTop_IncreaseOpacityShortcut.Header" xml:space="preserve">
|
||||
<value>Increase opacity</value>
|
||||
</data>
|
||||
<data name="AlwaysOnTop_DecreaseOpacity" xml:space="preserve">
|
||||
<value>Press **{0}** to decrease the opacity of the window</value>
|
||||
<data name="AlwaysOnTop_IncreaseOpacityShortcut.Description" xml:space="preserve">
|
||||
<value>Customize the shortcut to increase the opacity of a pinned window</value>
|
||||
</data>
|
||||
<data name="AlwaysOnTop_DecreaseOpacityShortcut.Header" xml:space="preserve">
|
||||
<value>Decrease opacity</value>
|
||||
</data>
|
||||
<data name="AlwaysOnTop_DecreaseOpacityShortcut.Description" xml:space="preserve">
|
||||
<value>Customize the shortcut to decrease the opacity of a pinned window</value>
|
||||
</data>
|
||||
<data name="Oobe_AlwaysOnTop.Title" xml:space="preserve">
|
||||
<value>Always On Top</value>
|
||||
@@ -4807,6 +4807,9 @@ The break timer font matches the text font.</value>
|
||||
<data name="ZoomIt_Record_Microphones_Default_Name" xml:space="preserve">
|
||||
<value>Default</value>
|
||||
</data>
|
||||
<data name="ZoomIt_Break_LockWorkstation.Content" xml:space="preserve">
|
||||
<value>Lock workstation during break</value>
|
||||
</data>
|
||||
<data name="ZoomIt_SnipGroup.Header" xml:space="preserve">
|
||||
<value>Snip</value>
|
||||
</data>
|
||||
@@ -4819,6 +4822,24 @@ The break timer font matches the text font.</value>
|
||||
<data name="ZoomIt_Snip_Shortcut_Save" xml:space="preserve">
|
||||
<value>Press **{0}** to save the snip to a file instead of the clipboard.</value>
|
||||
</data>
|
||||
<data name="ZoomIt_SnipOcr_Shortcut.Header" xml:space="preserve">
|
||||
<value>Snip OCR activation</value>
|
||||
</data>
|
||||
<data name="ZoomIt_SnipOcr_Shortcut.Description" xml:space="preserve">
|
||||
<value>Copy text from the selected region to the clipboard.</value>
|
||||
</data>
|
||||
<data name="ZoomIt_PanoramaGroup.Header" xml:space="preserve">
|
||||
<value>Panorama</value>
|
||||
</data>
|
||||
<data name="ZoomIt_PanoramaGroup.Description" xml:space="preserve">
|
||||
<value>Capture a scrolling panorama of a selected screen region.</value>
|
||||
</data>
|
||||
<data name="ZoomIt_Panorama_Shortcut.Header" xml:space="preserve">
|
||||
<value>Panorama activation</value>
|
||||
</data>
|
||||
<data name="ZoomIt_Panorama_Shortcut.Description" xml:space="preserve">
|
||||
<value>Select the area, then scroll the content. Move slowly and consistently, and do not rewind to previously covered areas. Press the hotkey again or with Shift to save to a file.</value>
|
||||
</data>
|
||||
<data name="Oobe_ZoomIt.Description" xml:space="preserve">
|
||||
<value>ZoomIt is a screen zoom, annotation, and recording tool for technical presentations and demos. You can also use ZoomIt to snip screenshots to the clipboard or to a file.</value>
|
||||
<comment>{Locked="ZoomIt"}</comment>
|
||||
@@ -5877,7 +5898,7 @@ Text uses the current drawing color.</value>
|
||||
<value>Welcome to PowerToys</value>
|
||||
</data>
|
||||
<data name="ShortcutConflictWindow_IgnoreShortcut.Content" xml:space="preserve">
|
||||
<value>Ignore shortcut</value>
|
||||
<value>Ignore conflict</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_AddModelButton.Content" xml:space="preserve">
|
||||
<value>Add model</value>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using global::PowerToys.GPOWrapper;
|
||||
@@ -50,6 +49,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
Settings = moduleSettingsRepository.SettingsConfig;
|
||||
|
||||
_hotkey = Settings.Properties.Hotkey.Value;
|
||||
_increaseOpacityHotkey = Settings.Properties.IncreaseOpacityHotkey.Value;
|
||||
_decreaseOpacityHotkey = Settings.Properties.DecreaseOpacityHotkey.Value;
|
||||
_showInSystemMenu = Settings.Properties.ShowInSystemMenu.Value;
|
||||
_frameEnabled = Settings.Properties.FrameEnabled.Value;
|
||||
_frameThickness = Settings.Properties.FrameThickness.Value;
|
||||
@@ -85,7 +86,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
var hotkeysDict = new Dictionary<string, HotkeySettings[]>
|
||||
{
|
||||
[ModuleName] = [Hotkey],
|
||||
[ModuleName] = [Hotkey, IncreaseOpacityHotkey, DecreaseOpacityHotkey],
|
||||
};
|
||||
|
||||
return hotkeysDict;
|
||||
@@ -135,10 +136,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
Settings.Properties.Hotkey.Value = _hotkey;
|
||||
NotifyPropertyChanged();
|
||||
|
||||
// Also notify that transparency shortcut strings have changed
|
||||
OnPropertyChanged(nameof(IncreaseOpacityShortcut));
|
||||
OnPropertyChanged(nameof(DecreaseOpacityShortcut));
|
||||
|
||||
// Using InvariantCulture as this is an IPC message
|
||||
SendConfigMSG(
|
||||
string.Format(
|
||||
@@ -150,6 +147,52 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public HotkeySettings IncreaseOpacityHotkey
|
||||
{
|
||||
get => _increaseOpacityHotkey;
|
||||
|
||||
set
|
||||
{
|
||||
if (value != _increaseOpacityHotkey)
|
||||
{
|
||||
_increaseOpacityHotkey = value ?? AlwaysOnTopProperties.DefaultIncreaseOpacityHotkeyValue;
|
||||
|
||||
Settings.Properties.IncreaseOpacityHotkey.Value = _increaseOpacityHotkey;
|
||||
NotifyPropertyChanged();
|
||||
|
||||
SendConfigMSG(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{{ \"powertoys\": {{ \"{0}\": {1} }} }}",
|
||||
AlwaysOnTopSettings.ModuleName,
|
||||
JsonSerializer.Serialize(Settings, SourceGenerationContextContext.Default.AlwaysOnTopSettings)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public HotkeySettings DecreaseOpacityHotkey
|
||||
{
|
||||
get => _decreaseOpacityHotkey;
|
||||
|
||||
set
|
||||
{
|
||||
if (value != _decreaseOpacityHotkey)
|
||||
{
|
||||
_decreaseOpacityHotkey = value ?? AlwaysOnTopProperties.DefaultDecreaseOpacityHotkeyValue;
|
||||
|
||||
Settings.Properties.DecreaseOpacityHotkey.Value = _decreaseOpacityHotkey;
|
||||
NotifyPropertyChanged();
|
||||
|
||||
SendConfigMSG(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{{ \"powertoys\": {{ \"{0}\": {1} }} }}",
|
||||
AlwaysOnTopSettings.ModuleName,
|
||||
JsonSerializer.Serialize(Settings, SourceGenerationContextContext.Default.AlwaysOnTopSettings)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool FrameEnabled
|
||||
{
|
||||
get => _frameEnabled;
|
||||
@@ -310,32 +353,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the formatted shortcut string for increasing window opacity (modifier keys + "+").
|
||||
/// </summary>
|
||||
public string IncreaseOpacityShortcut
|
||||
{
|
||||
get
|
||||
{
|
||||
var modifiers = new HotkeySettings(_hotkey.Win, _hotkey.Ctrl, _hotkey.Alt, _hotkey.Shift, 0).ToString();
|
||||
var shortcut = string.IsNullOrEmpty(modifiers) ? "+" : modifiers + " + +";
|
||||
return string.Format(CultureInfo.CurrentCulture, ResourceLoaderInstance.ResourceLoader.GetString("AlwaysOnTop_IncreaseOpacity"), shortcut);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the formatted shortcut string for decreasing window opacity (modifier keys + "-").
|
||||
/// </summary>
|
||||
public string DecreaseOpacityShortcut
|
||||
{
|
||||
get
|
||||
{
|
||||
var modifiers = new HotkeySettings(_hotkey.Win, _hotkey.Ctrl, _hotkey.Alt, _hotkey.Shift, 0).ToString();
|
||||
var shortcut = string.IsNullOrEmpty(modifiers) ? "-" : modifiers + " + -";
|
||||
return string.Format(CultureInfo.CurrentCulture, ResourceLoaderInstance.ResourceLoader.GetString("AlwaysOnTop_DecreaseOpacity"), shortcut);
|
||||
}
|
||||
}
|
||||
|
||||
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
OnPropertyChanged(propertyName);
|
||||
@@ -352,6 +369,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
private bool _enabledStateIsGPOConfigured;
|
||||
private bool _isEnabled;
|
||||
private HotkeySettings _hotkey;
|
||||
private HotkeySettings _increaseOpacityHotkey;
|
||||
private HotkeySettings _decreaseOpacityHotkey;
|
||||
private bool _showInSystemMenu;
|
||||
private bool _frameEnabled;
|
||||
private int _frameThickness;
|
||||
|
||||
@@ -521,7 +521,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
ISettingsRepository<AlwaysOnTopSettings> moduleSettingsRepository = SettingsRepository<AlwaysOnTopSettings>.GetInstance(SettingsUtils.Default);
|
||||
var list = new List<DashboardModuleItem>
|
||||
{
|
||||
new DashboardModuleShortcutItem() { Label = resourceLoader.GetString("AlwaysOnTop_ShortDescription"), Shortcut = moduleSettingsRepository.SettingsConfig.Properties.Hotkey.Value.GetKeysList() },
|
||||
new DashboardModuleShortcutItem() { Label = resourceLoader.GetString("AlwaysOnTop_ActivationShortcut/Header"), Shortcut = moduleSettingsRepository.SettingsConfig.Properties.Hotkey.Value.GetKeysList() },
|
||||
new DashboardModuleShortcutItem() { Label = resourceLoader.GetString("AlwaysOnTop_IncreaseOpacityShortcut/Header"), Shortcut = moduleSettingsRepository.SettingsConfig.Properties.IncreaseOpacityHotkey.Value.GetKeysList() },
|
||||
new DashboardModuleShortcutItem() { Label = resourceLoader.GetString("AlwaysOnTop_DecreaseOpacityShortcut/Header"), Shortcut = moduleSettingsRepository.SettingsConfig.Properties.DecreaseOpacityHotkey.Value.GetKeysList() },
|
||||
};
|
||||
return new ObservableCollection<DashboardModuleItem>(list);
|
||||
}
|
||||
|
||||
@@ -181,34 +181,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
var hotkeysDict = new Dictionary<string, HotkeySettings[]>
|
||||
{
|
||||
[ModuleName] = [ToggleShortcut, EditorShortcut],
|
||||
[ModuleName] = [EditorShortcut],
|
||||
};
|
||||
|
||||
return hotkeysDict;
|
||||
}
|
||||
|
||||
public HotkeySettings ToggleShortcut
|
||||
{
|
||||
get => Settings.Properties.ToggleShortcut;
|
||||
set
|
||||
{
|
||||
if (value != Settings.Properties.ToggleShortcut)
|
||||
{
|
||||
Settings.Properties.ToggleShortcut = value == null ? Settings.Properties.DefaultToggleShortcut : value;
|
||||
|
||||
OnPropertyChanged(nameof(ToggleShortcut));
|
||||
NotifySettingsChanged();
|
||||
|
||||
SendConfigMSG(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{{ \"powertoys\": {{ \"{0}\": {1} }} }}",
|
||||
KeyboardManagerSettings.ModuleName,
|
||||
JsonSerializer.Serialize(Settings, SourceGenerationContextContext.Default.KeyboardManagerSettings)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool UseNewEditor
|
||||
{
|
||||
get => Settings.Properties.UseNewEditor;
|
||||
|
||||
@@ -367,6 +367,34 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public HotkeySettings SnipOcrToggleKey
|
||||
{
|
||||
get => _zoomItSettings.Properties.SnipOcrToggleKey.Value;
|
||||
set
|
||||
{
|
||||
if (_zoomItSettings.Properties.SnipOcrToggleKey.Value != value)
|
||||
{
|
||||
_zoomItSettings.Properties.SnipOcrToggleKey.Value = value ?? ZoomItProperties.DefaultSnipOcrToggleKey;
|
||||
OnPropertyChanged(nameof(SnipOcrToggleKey));
|
||||
NotifySettingsChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public HotkeySettings SnipPanoramaToggleKey
|
||||
{
|
||||
get => _zoomItSettings.Properties.SnipPanoramaToggleKey.Value;
|
||||
set
|
||||
{
|
||||
if (_zoomItSettings.Properties.SnipPanoramaToggleKey.Value != value)
|
||||
{
|
||||
_zoomItSettings.Properties.SnipPanoramaToggleKey.Value = value ?? ZoomItProperties.DefaultSnipPanoramaToggleKey;
|
||||
OnPropertyChanged(nameof(SnipPanoramaToggleKey));
|
||||
NotifySettingsChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public HotkeySettings BreakTimerKey
|
||||
{
|
||||
get => _zoomItSettings.Properties.BreakTimerKey.Value;
|
||||
@@ -783,6 +811,20 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public bool BreakLockWorkstation
|
||||
{
|
||||
get => _zoomItSettings.Properties.BreakLockWorkstation.Value;
|
||||
set
|
||||
{
|
||||
if (_zoomItSettings.Properties.BreakLockWorkstation.Value != value)
|
||||
{
|
||||
_zoomItSettings.Properties.BreakLockWorkstation.Value = value;
|
||||
OnPropertyChanged(nameof(BreakLockWorkstation));
|
||||
NotifySettingsChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double RecordScaling
|
||||
{
|
||||
get
|
||||
|
||||
Reference in New Issue
Block a user