Release v0.5.0: isolate screenshot editor and support manifest-based updates
This commit is contained in:
+1
-1
@@ -589,7 +589,7 @@ void App::dispatch(int command)
|
||||
{
|
||||
if (command == Screenshot)
|
||||
{
|
||||
if (!busy) showScreenshot(*this);
|
||||
if (!busy) { resetInput(); launchScreenshot(*this); }
|
||||
return;
|
||||
}
|
||||
if (command == ShowHistory)
|
||||
|
||||
@@ -100,7 +100,7 @@ class App
|
||||
static LRESULT CALLBACK mouseProc(int, WPARAM, LPARAM);
|
||||
};
|
||||
void showHistory(App &);
|
||||
void showScreenshot(App &);
|
||||
void launchScreenshot(App &);
|
||||
void refreshHistory(App &);
|
||||
void showSettings(App &, bool visible = true);
|
||||
void showLogs(App &);
|
||||
|
||||
+30
-15
@@ -1,4 +1,3 @@
|
||||
#include "app.h"
|
||||
#include "screenshot.h"
|
||||
#include "../resources/resource.h"
|
||||
#include <algorithm>
|
||||
@@ -13,14 +12,21 @@ namespace
|
||||
{
|
||||
// Only selection is exclusive; each completed capture owns an independent editor.
|
||||
HWND activeCapture{};
|
||||
// Keep titles stable and distinct for every editor opened during this session.
|
||||
unsigned long long nextEditorIndex=1;
|
||||
bool confirmScreenshotClose(HWND owner)
|
||||
{
|
||||
TASKDIALOGCONFIG dialog{sizeof(dialog)};
|
||||
dialog.hwndParent=owner;
|
||||
dialog.dwFlags=TDF_POSITION_RELATIVE_TO_WINDOW|TDF_ALLOW_DIALOG_CANCELLATION;
|
||||
dialog.dwFlags=TDF_POSITION_RELATIVE_TO_WINDOW|TDF_ALLOW_DIALOG_CANCELLATION|TDF_USE_HICON_MAIN;
|
||||
const int iconSize=MulDiv(32,GetDpiForWindow(owner),96);
|
||||
auto icon=(HICON)LoadImageW(GetModuleHandleW(nullptr),MAKEINTRESOURCEW(IDI_SCREENSHOT_QUESTION),
|
||||
IMAGE_ICON,iconSize,iconSize,0);
|
||||
dialog.hMainIcon=icon;
|
||||
dialog.dwCommonButtons=TDCBF_YES_BUTTON|TDCBF_NO_BUTTON;
|
||||
dialog.pszWindowTitle=L"KSwitcher";
|
||||
dialog.pszContent=L"Close without copying this screenshot to the clipboard?";
|
||||
dialog.pszMainInstruction=L"Close without copying?";
|
||||
dialog.pszContent=L"Your latest screenshot changes have not been copied to the clipboard.";
|
||||
dialog.nDefaultButton=IDNO;
|
||||
dialog.lpCallbackData=(LONG_PTR)owner;
|
||||
dialog.pfCallback=[](HWND window,UINT notification,WPARAM,LPARAM,LONG_PTR data)->HRESULT {
|
||||
@@ -40,6 +46,7 @@ bool confirmScreenshotClose(HWND owner)
|
||||
};
|
||||
int button=IDNO;
|
||||
auto result=TaskDialogIndirect(&dialog,&button,nullptr,nullptr);
|
||||
if(icon) DestroyIcon(icon);
|
||||
// If Windows cannot display the confirmation, keep the screenshot open.
|
||||
return SUCCEEDED(result) && button==IDYES;
|
||||
}
|
||||
@@ -115,7 +122,7 @@ bool chooseTextFont(HWND owner,Mark &m)
|
||||
}
|
||||
struct Capture
|
||||
{
|
||||
App &app;
|
||||
ScreenshotHost &app;
|
||||
std::unique_ptr<Surface> image;
|
||||
std::unique_ptr<Surface> dimmed, frame;
|
||||
bool editing{}, dragging{}, arrow{}, owned{}, copied{};
|
||||
@@ -153,7 +160,7 @@ struct Capture
|
||||
int hoveredTool=0;
|
||||
MarkPart hoveredPart=MarkPart::None;
|
||||
std::wstring statusText;
|
||||
explicit Capture(App &a) : app(a) {}
|
||||
explicit Capture(ScreenshotHost &a) : app(a) {}
|
||||
~Capture() {
|
||||
if(pencilCursor) DestroyCursor(pencilCursor);
|
||||
if (font) DeleteObject(font);
|
||||
@@ -162,6 +169,7 @@ struct Capture
|
||||
}
|
||||
void remember(const std::vector<Mark> &previous)
|
||||
{
|
||||
copied=false;
|
||||
history.remember(previous);
|
||||
syncHistory();
|
||||
}
|
||||
@@ -720,14 +728,13 @@ LRESULT CALLBACK toolHover(HWND w, UINT msg, WPARAM wp, LPARAM lp, UINT_PTR, DWO
|
||||
if(msg==WM_MOUSELEAVE) InvalidateRect(w,nullptr,FALSE);
|
||||
return DefSubclassProc(w,msg,wp,lp);
|
||||
}
|
||||
void copy(HWND w, Capture &c)
|
||||
void copy(HWND w, Capture &c, DWORD (*publish)(HWND,const Clip &)=writeClipboard)
|
||||
{
|
||||
finishInlineText(w,c,true);
|
||||
// The resident message window owns the clipboard after this editor closes.
|
||||
// The resident message window keeps ownership if the editor is closed later.
|
||||
auto clip=screenshotClip(c.image->dc,c.image->width,c.image->height,c.marks);
|
||||
writeClipboard(c.app.window, clip);
|
||||
publish(c.app.window, clip);
|
||||
c.copied=true;
|
||||
DestroyWindow(w);
|
||||
}
|
||||
void edit(HWND w, Capture &c)
|
||||
{
|
||||
@@ -745,8 +752,11 @@ void edit(HWND w, Capture &c)
|
||||
POINT cursor; GetCursorPos(&cursor);
|
||||
GetMonitorInfoW(MonitorFromPoint(cursor,MONITOR_DEFAULTTOPRIMARY), &monitor);
|
||||
ShowWindow(w,SW_HIDE);
|
||||
SetWindowLongPtrW(w,GWLP_HWNDPARENT,0);
|
||||
SetWindowLongPtrW(w,GWL_EXSTYLE,(GetWindowLongPtrW(w,GWL_EXSTYLE)|WS_EX_APPWINDOW)&~WS_EX_TOOLWINDOW);
|
||||
SetWindowLongPtrW(w,GWL_STYLE,WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN);
|
||||
SetWindowTextW(w,L"KSwitcher — Screenshot");
|
||||
auto title=L"KSwitcher — Screenshot #"+std::to_wstring(nextEditorIndex++);
|
||||
SetWindowTextW(w,title.c_str());
|
||||
INITCOMMONCONTROLSEX statusControls{sizeof(statusControls),ICC_BAR_CLASSES};
|
||||
InitCommonControlsEx(&statusControls);
|
||||
CreateWindowExW(0,STATUSCLASSNAMEW,L"",WS_CHILD|WS_VISIBLE|SBARS_TOOLTIPS,
|
||||
@@ -806,7 +816,8 @@ LRESULT CALLBACK captureProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
|
||||
switch (msg)
|
||||
{
|
||||
case WM_CLOSE:
|
||||
if(c->editing && !c->copied && !confirmScreenshotClose(w)) return 0;
|
||||
finishInlineText(w,*c,true);
|
||||
if(c->editing && (!c->copied || c->dragging) && !confirmScreenshotClose(w)) return 0;
|
||||
DestroyWindow(w); return 0;
|
||||
case FinishTextMessage:
|
||||
if(c->inlineText && (HWND)lp==c->inlineText && (wp!=0 || GetFocus()!=c->inlineText)) {
|
||||
@@ -908,7 +919,7 @@ LRESULT CALLBACK captureProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
|
||||
if (LOWORD(wp)==12 || LOWORD(wp)==18)
|
||||
{
|
||||
bool changed=LOWORD(wp)==12?c->history.undo(c->marks):c->history.redo(c->marks);
|
||||
if(changed) { c->selected=-1; c->selectedLabel=false; c->wheelDelta=0; c->wheelTarget=-1; }
|
||||
if(changed) { c->copied=false; c->selected=-1; c->selectedLabel=false; c->wheelDelta=0; c->wheelTarget=-1; }
|
||||
c->syncHistory();
|
||||
}
|
||||
if (LOWORD(wp)==10 || LOWORD(wp)==11 || LOWORD(wp)==19 || (LOWORD(wp)>=21 && LOWORD(wp)<=26))
|
||||
@@ -1226,7 +1237,11 @@ LRESULT CALLBACK captureProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
|
||||
case WM_NCDESTROY:
|
||||
if (activeCapture == w) activeCapture=nullptr;
|
||||
SetWindowLongPtrW(w,GWLP_USERDATA,0);
|
||||
if (c->owned) delete c;
|
||||
if (c->owned) {
|
||||
auto &host=c->app;
|
||||
if (--host.windows==0) PostMessageW(host.window,ScreenshotIdle,0,0);
|
||||
delete c;
|
||||
}
|
||||
return DefWindowProcW(w,msg,wp,lp);
|
||||
}
|
||||
}
|
||||
@@ -1498,10 +1513,9 @@ Clip screenshotClip(HDC source, int width, int height, const std::vector<Screens
|
||||
memcpy(bytes.data()+sizeof(header),output.pixels,bytes.size()-sizeof(header));
|
||||
return Clip{{{CF_DIB,L"",std::move(bytes)}}};
|
||||
}
|
||||
void showScreenshot(App &app)
|
||||
void showScreenshot(ScreenshotHost &app)
|
||||
{
|
||||
if (activeCapture) { showRequestedWindow(activeCapture); SetForegroundWindow(activeCapture); return; }
|
||||
app.resetInput();
|
||||
auto c=std::make_unique<Capture>(app);
|
||||
int x=GetSystemMetrics(SM_XVIRTUALSCREEN),y=GetSystemMetrics(SM_YVIRTUALSCREEN);
|
||||
int width=GetSystemMetrics(SM_CXVIRTUALSCREEN),height=GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
||||
@@ -1528,6 +1542,7 @@ void showScreenshot(App &app)
|
||||
WS_POPUP,x,y,width,height,app.window,nullptr,app.instance,c.get());
|
||||
if (!activeCapture) throw std::runtime_error("Cannot open screen capture");
|
||||
c->owned=true;
|
||||
++app.windows;
|
||||
c.release();
|
||||
showRequestedWindow(activeCapture); SetForegroundWindow(activeCapture); SetFocus(activeCapture);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
#include "platform.h"
|
||||
namespace ks
|
||||
{
|
||||
// Only the on-demand screenshot process owns this state; no resident App dependency.
|
||||
struct ScreenshotHost {
|
||||
HINSTANCE instance{};
|
||||
HWND window{};
|
||||
unsigned windows{};
|
||||
};
|
||||
inline constexpr UINT ScreenshotRequest=WM_APP+71, ScreenshotIdle=WM_APP+72;
|
||||
void showScreenshot(ScreenshotHost &);
|
||||
enum class LineStyle { Solid, Dotted, Dashed, DashDot };
|
||||
enum class LineEnds { End, Start, Both, None };
|
||||
enum class ScreenshotShape { None, Rectangle, Circle, Ellipse };
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#include "app.h"
|
||||
|
||||
namespace ks {
|
||||
void launchScreenshot(App &)
|
||||
{
|
||||
auto path=std::filesystem::path(executable()).parent_path()/L"KSwitcher.Screenshot.exe";
|
||||
if (!std::filesystem::exists(path))
|
||||
throw std::runtime_error("KSwitcher.Screenshot.exe is missing. Extract the complete application package.");
|
||||
auto command=L"\""+path.wstring()+L"\"";
|
||||
STARTUPINFOW startup{sizeof(startup)};
|
||||
PROCESS_INFORMATION process{};
|
||||
// Grant foreground rights before the child's first selection window opens.
|
||||
if (!CreateProcessW(path.c_str(),command.data(),nullptr,nullptr,FALSE,CREATE_SUSPENDED,
|
||||
nullptr,path.parent_path().c_str(),&startup,&process))
|
||||
throw std::runtime_error(utf8(lastError(L"Cannot start screenshot editor")));
|
||||
AllowSetForegroundWindow(process.dwProcessId);
|
||||
ResumeThread(process.hThread);
|
||||
CloseHandle(process.hThread);
|
||||
CloseHandle(process.hProcess);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
#include "screenshot.h"
|
||||
#include <commctrl.h>
|
||||
|
||||
namespace {
|
||||
constexpr wchar_t ControllerClass[]=L"KSwitcher.Screenshot.Controller";
|
||||
LRESULT CALLBACK controller(HWND window,UINT message,WPARAM wp,LPARAM lp)
|
||||
{
|
||||
auto host=(ks::ScreenshotHost *)GetWindowLongPtrW(window,GWLP_USERDATA);
|
||||
if(message==WM_NCCREATE) {
|
||||
host=(ks::ScreenshotHost *)((CREATESTRUCTW *)lp)->lpCreateParams;
|
||||
SetWindowLongPtrW(window,GWLP_USERDATA,(LONG_PTR)host);
|
||||
}
|
||||
if(host && message==ks::ScreenshotRequest) {
|
||||
try { ks::showScreenshot(*host); }
|
||||
catch(const std::exception &e) {
|
||||
MessageBoxW(window,ks::wide(e.what()).c_str(),L"Screen capture",MB_OK|MB_ICONERROR);
|
||||
PostMessageW(window,ks::ScreenshotIdle,0,0);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
if(host && message==ks::ScreenshotIdle && !host->windows) {
|
||||
DestroyWindow(window);
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
}
|
||||
return DefWindowProcW(window,message,wp,lp);
|
||||
}
|
||||
}
|
||||
|
||||
int WINAPI wWinMain(HINSTANCE instance,HINSTANCE,PWSTR,int)
|
||||
{
|
||||
HANDLE mutex=CreateMutexW(nullptr,FALSE,L"Local\\KSwitcher.Screenshot.Singleton");
|
||||
if(!mutex) return 1;
|
||||
// Wait through startup/shutdown races; the controller accepts requests only while alive.
|
||||
bool acquired=false;
|
||||
for(int attempt=0;attempt<100;++attempt) {
|
||||
auto wait=WaitForSingleObject(mutex,0);
|
||||
if(wait==WAIT_OBJECT_0 || wait==WAIT_ABANDONED) { acquired=true; break; }
|
||||
if(auto existing=FindWindowW(ControllerClass,nullptr)) {
|
||||
DWORD pid{}; GetWindowThreadProcessId(existing,&pid);
|
||||
AllowSetForegroundWindow(pid);
|
||||
DWORD_PTR accepted{};
|
||||
if(SendMessageTimeoutW(existing,ks::ScreenshotRequest,0,0,SMTO_ABORTIFHUNG,2000,&accepted) && accepted) {
|
||||
CloseHandle(mutex); return 0;
|
||||
}
|
||||
}
|
||||
Sleep(50);
|
||||
}
|
||||
if(!acquired) { CloseHandle(mutex); return 1; }
|
||||
CoInitializeEx(nullptr,COINIT_APARTMENTTHREADED);
|
||||
INITCOMMONCONTROLSEX controls{sizeof(controls),ICC_STANDARD_CLASSES|ICC_BAR_CLASSES};
|
||||
InitCommonControlsEx(&controls);
|
||||
ks::ScreenshotHost host{instance};
|
||||
WNDCLASSW cls{}; cls.hInstance=instance; cls.lpfnWndProc=controller; cls.lpszClassName=ControllerClass;
|
||||
RegisterClassW(&cls);
|
||||
host.window=CreateWindowW(ControllerClass,L"KSwitcher Screenshot",0,0,0,0,0,nullptr,nullptr,instance,&host);
|
||||
int result=1;
|
||||
if(host.window) {
|
||||
PostMessageW(host.window,ks::ScreenshotRequest,0,0);
|
||||
MSG message{};
|
||||
while((result=GetMessageW(&message,nullptr,0,0))>0) {
|
||||
TranslateMessage(&message); DispatchMessageW(&message);
|
||||
}
|
||||
result=result<0?1:0;
|
||||
}
|
||||
CoUninitialize();
|
||||
ReleaseMutex(mutex); CloseHandle(mutex);
|
||||
return result;
|
||||
}
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
#include "platform.h"
|
||||
namespace ks
|
||||
{
|
||||
inline constexpr wchar_t AppVersion[] = L"0.4.1";
|
||||
inline constexpr wchar_t AppVersion[] = L"0.5.0";
|
||||
inline constexpr UINT UpdateMessage = WM_APP + 7;
|
||||
struct UpdateResult
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user