Release v0.6.2: configurable screenshot editor and DPI fixes
This commit is contained in:
+11
-1
@@ -499,9 +499,19 @@ int main(int argc, char **argv) {
|
||||
require(buttonAfter.top-buttonBefore.top==180 && buttonAfter.bottom-listAfter.bottom==buttonBefore.bottom-listBefore.bottom,"history actions stay below list after resize");
|
||||
SetWindowPos(history,nullptr,0,0,historyFrame.right-historyFrame.left,historyFrame.bottom-historyFrame.top,SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE);
|
||||
require(EqualRect(&listBefore,&(listAfter=childRect(102))),"history resize roundtrip restores geometry");
|
||||
// Reproduce a row height left over from another DPI without changing desktop settings.
|
||||
UINT historyDpi=GetDpiForWindow(history);
|
||||
for (int staleDpi : {96, 144, 192, 240}) {
|
||||
SendDlgItemMessageW(history,102,LB_SETITEMHEIGHT,0,MulDiv(88,staleDpi,96));
|
||||
SendMessageW(history,WM_DPICHANGED,MAKELONG(historyDpi,historyDpi),(LPARAM)&historyFrame);
|
||||
require(SendDlgItemMessageW(history,102,LB_GETITEMHEIGHT,0,0)==MulDiv(88,historyDpi,96),
|
||||
"DPI change updates owner-drawn history row height through layout subclass");
|
||||
require(EqualRect(&listBefore,&(listAfter=childRect(102))),"DPI refresh preserves history list geometry");
|
||||
}
|
||||
auto settings=app.settingsWindow;
|
||||
require(SendDlgItemMessageW(settings,209,BM_GETCHECK,0,0)==BST_CHECKED,"automatic screenshot editor checkbox defaults on");
|
||||
require(settings&&!IsWindowVisible(settings),"settings layout tested without changing user focus");
|
||||
for(auto pair:{std::pair{207,215},std::pair{207,232},std::pair{207,220},std::pair{232,230},std::pair{230,231},std::pair{231,253}}) {
|
||||
for(auto pair:{std::pair{209,215},std::pair{209,207},std::pair{209,220},std::pair{209,232},std::pair{207,215},std::pair{207,232},std::pair{207,220},std::pair{232,230},std::pair{230,231},std::pair{231,253}}) {
|
||||
RECT first{},second{},overlap{};
|
||||
GetWindowRect(GetDlgItem(settings,pair.first),&first); GetWindowRect(GetDlgItem(settings,pair.second),&second);
|
||||
require(!IntersectRect(&overlap,&first,&second),"capture hotkey and layout controls do not overlap");
|
||||
|
||||
@@ -46,7 +46,7 @@ function Latest-Token {
|
||||
}
|
||||
$process=$null
|
||||
try {
|
||||
$process=Start-Process -FilePath $Executable -WindowStyle Hidden -PassThru
|
||||
$process=Start-Process -FilePath $Executable -ArgumentList @("--auto-open-editor=0") -WindowStyle Hidden -PassThru
|
||||
Wait-For { (Capture-Windows).Count -eq 1 } 'First selection did not open.'
|
||||
Select-Region (Capture-Windows)[0]
|
||||
if (!$process.WaitForExit(5000)) { throw 'Process remained alive after notification.' }
|
||||
@@ -63,7 +63,7 @@ try {
|
||||
if ($process.HasExited) { throw 'Process exited with an editor open.' }
|
||||
Close-Editor $second
|
||||
if (!$process.WaitForExit(5000) -or $process.ExitCode -ne 0) { throw 'Last editor did not exit process.' }
|
||||
$process=Start-Process -FilePath $Executable -WindowStyle Hidden -PassThru
|
||||
$process=Start-Process -FilePath $Executable -ArgumentList @("--auto-open-editor=0") -WindowStyle Hidden -PassThru
|
||||
Wait-For { (Capture-Windows).Count -eq 1 } 'New process did not open selection.'
|
||||
Send (Capture-Windows)[0] 0x100 27 # Esc cancels a selection only.
|
||||
if (!$process.WaitForExit(5000) -or $process.ExitCode -ne 0) { throw 'Cancelling selection did not exit process.' }
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// Redirect HKCU and the shortcut destination; never replace the user's registration.
|
||||
#include "../src/screenshot_notifications.h"
|
||||
#include <shlobj.h>
|
||||
#include <iostream>
|
||||
static std::wstring programs;
|
||||
static HRESULT testKnownFolder(REFKNOWNFOLDERID, DWORD, HANDLE, PWSTR *out)
|
||||
{
|
||||
*out=(PWSTR)CoTaskMemAlloc((programs.size()+1)*sizeof(wchar_t));
|
||||
if(!*out) return E_OUTOFMEMORY;
|
||||
wcscpy(*out,programs.c_str()); return S_OK;
|
||||
}
|
||||
#define SHGetKnownFolderPath testKnownFolder
|
||||
#include "../src/screenshot_activation.cpp"
|
||||
#undef SHGetKnownFolderPath
|
||||
int main()
|
||||
{
|
||||
CoInitializeEx(nullptr,COINIT_APARTMENTTHREADED);
|
||||
auto suffix=std::to_wstring(GetCurrentProcessId());
|
||||
auto keyPath=L"Software\\KSwitcherRegistrationTest-"+suffix;
|
||||
auto directory=std::filesystem::temp_directory_path()/(L"KSwitcherRegistrationTest-"+suffix);
|
||||
std::filesystem::create_directories(directory); programs=directory.wstring();
|
||||
HKEY isolated{}; int result=0;
|
||||
try {
|
||||
if(RegCreateKeyExW(HKEY_CURRENT_USER,keyPath.c_str(),0,nullptr,0,KEY_ALL_ACCESS,nullptr,&isolated,nullptr)!=ERROR_SUCCESS ||
|
||||
RegOverridePredefKey(HKEY_CURRENT_USER,isolated)!=ERROR_SUCCESS) throw std::runtime_error("registry isolation failed");
|
||||
ks::registerScreenshotNotifications();
|
||||
wchar_t command[32768]{}; DWORD size=sizeof(command);
|
||||
auto path=L"Software\\Classes\\CLSID\\{e3f78047-f0ad-49b6-9074-411931706167}\\LocalServer32";
|
||||
if(RegGetValueW(HKEY_CURRENT_USER,path,nullptr,RRF_RT_REG_SZ,nullptr,command,&size)!=ERROR_SUCCESS)
|
||||
throw std::runtime_error("COM server was not registered");
|
||||
auto helper=std::filesystem::path(ks::executable()).parent_path()/L"KSwitcher.Screenshot.exe";
|
||||
if(command!=L"\""+helper.wstring()+L"\" --toast-activated") throw std::runtime_error("COM server targets wrong executable");
|
||||
Microsoft::WRL::ComPtr<IShellLinkW> link;
|
||||
if(FAILED(CoCreateInstance(CLSID_ShellLink,nullptr,CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&link)))) throw std::runtime_error("read shortcut");
|
||||
Microsoft::WRL::ComPtr<IPersistFile> file; link.As(&file);
|
||||
if(FAILED(file->Load((directory/L"KSwitcher Screenshot.lnk").c_str(),STGM_READ))) throw std::runtime_error("shortcut missing");
|
||||
wchar_t target[32768]{}; link->GetPath(target,32768,nullptr,SLGP_RAWPATH);
|
||||
if(target!=helper.wstring()) throw std::runtime_error("shortcut targets wrong executable");
|
||||
Microsoft::WRL::ComPtr<IPropertyStore> properties; link.As(&properties);
|
||||
PROPVARIANT id{}; properties->GetValue(PKEY_AppUserModel_ID,&id);
|
||||
bool valid=id.vt==VT_LPWSTR && std::wstring(id.pwszVal)==L"HrynCo.KSwitcher.Screenshot";
|
||||
PropVariantClear(&id);
|
||||
if(!valid) throw std::runtime_error("shortcut identity missing");
|
||||
properties->GetValue(PKEY_AppUserModel_ToastActivatorCLSID,&id);
|
||||
valid=id.vt==VT_CLSID && *id.puuid==ks::ActivatorId; PropVariantClear(&id);
|
||||
if(!valid) throw std::runtime_error("shortcut activator missing");
|
||||
ks::registerScreenshotNotifications(); // Registration is safe to repeat.
|
||||
std::cout<<"Passed isolated screenshot registration checks\n";
|
||||
} catch(const std::exception &e) { std::cerr<<e.what()<<'\n'; result=1; }
|
||||
RegOverridePredefKey(HKEY_CURRENT_USER,nullptr);
|
||||
if(isolated) RegCloseKey(isolated);
|
||||
RegDeleteTreeW(HKEY_CURRENT_USER,keyPath.c_str());
|
||||
std::filesystem::remove_all(directory);
|
||||
CoUninitialize(); return result;
|
||||
}
|
||||
+30
-12
@@ -164,18 +164,36 @@ int main()
|
||||
SendMessageW(window,WM_CLOSE,0,0);
|
||||
require(!IsWindow(window),"copied screenshot closes without confirmation");
|
||||
static bool published=false;
|
||||
app.notify=[](const ks::Clip &) { return published; };
|
||||
ks::Capture selection(app);
|
||||
selection.image=std::make_unique<ks::Surface>(100,100);
|
||||
PatBlt(selection.image->dc,0,0,100,100,WHITENESS);
|
||||
selection.start={10,20}; selection.end={70,60};
|
||||
auto overlay=CreateWindowW(cls.lpszClassName,L"Selection test",WS_POPUP,0,0,100,100,app.window,nullptr,app.instance,&selection);
|
||||
ks::finishSelection(overlay,selection,[](HWND,const ks::Clip &clip)->DWORD {
|
||||
auto pixels=ks::decodeImage(clip);
|
||||
if(pixels.width!=60 || pixels.height!=40) throw std::runtime_error("automatic copy did not crop selection");
|
||||
published=true; return 0;
|
||||
});
|
||||
require(published && selection.copied && !IsWindow(overlay),"selection copies before notification and closes without editor");
|
||||
static bool notificationAccepted=false;
|
||||
app.notify=[](const ks::Clip &) {
|
||||
if(!published) throw std::runtime_error("notification preceded clipboard publication");
|
||||
return notificationAccepted;
|
||||
};
|
||||
for(bool autoOpen : {true,false}) for (bool accepted : {true, false}) {
|
||||
app.autoOpenEditor=autoOpen;
|
||||
published=false;
|
||||
notificationAccepted=accepted;
|
||||
ks::Capture selection(app);
|
||||
selection.image=std::make_unique<ks::Surface>(100,100);
|
||||
PatBlt(selection.image->dc,0,0,100,100,WHITENESS);
|
||||
selection.start={10,20}; selection.end={70,60};
|
||||
auto overlay=CreateWindowW(cls.lpszClassName,L"Selection test",WS_POPUP,0,0,100,100,app.window,nullptr,app.instance,&selection);
|
||||
ks::finishSelection(overlay,selection,[](HWND,const ks::Clip &clip)->DWORD {
|
||||
auto pixels=ks::decodeImage(clip);
|
||||
if(pixels.width!=60 || pixels.height!=40) throw std::runtime_error("automatic copy did not crop selection");
|
||||
published=true; return 0;
|
||||
});
|
||||
if(!autoOpen && accepted) {
|
||||
require(published && !IsWindow(overlay),"notification-first closes selection after successful submission");
|
||||
continue;
|
||||
}
|
||||
require(published && selection.copied && IsWindowVisible(overlay) && selection.editing,
|
||||
"selection opens editor whether notification is accepted or rejected");
|
||||
require(selection.image->width==60 && selection.image->height==40,
|
||||
"automatically opened editor contains the selected region");
|
||||
SendMessageW(overlay,WM_CLOSE,0,0);
|
||||
require(!IsWindow(overlay),"automatically copied screenshot closes without confirmation");
|
||||
}
|
||||
DestroyWindow(app.window); app.window=nullptr;
|
||||
CoUninitialize();
|
||||
std::cout<<"Passed screenshot editor UI integration checks\n";
|
||||
|
||||
@@ -430,6 +430,10 @@ int main(int argc, char **argv)
|
||||
auto invalid = settings;
|
||||
auto captureSettings = settings;
|
||||
captureSettings.screenshot = L"Ctrl+Shift+F9";
|
||||
require(captureSettings.screenshotAutoOpenEditor,"editor opens by default");
|
||||
captureSettings.screenshotAutoOpenEditor=false;
|
||||
require(!ks::decodeSettings(ks::encodeSettings(captureSettings), {}).screenshotAutoOpenEditor,
|
||||
"notification-first preference survives settings roundtrip");
|
||||
require(ks::decodeSettings(ks::encodeSettings(captureSettings), {}).screenshot == captureSettings.screenshot,
|
||||
"screen capture shortcut roundtrip");
|
||||
captureSettings.screenshot = captureSettings.clipboard;
|
||||
|
||||
Reference in New Issue
Block a user