Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c61ae156b4 |
+7
-15
@@ -1,5 +1,5 @@
|
|||||||
cmake_minimum_required(VERSION 3.24)
|
cmake_minimum_required(VERSION 3.24)
|
||||||
project(KSwitcher VERSION 0.5.0 LANGUAGES CXX RC)
|
project(KSwitcher VERSION 0.4.2 LANGUAGES CXX RC)
|
||||||
if(NOT WIN32)
|
if(NOT WIN32)
|
||||||
message(FATAL_ERROR "KSwitcher uses native Windows APIs")
|
message(FATAL_ERROR "KSwitcher uses native Windows APIs")
|
||||||
endif()
|
endif()
|
||||||
@@ -9,7 +9,7 @@ add_library(kscore src/core.cpp src/platform.cpp src/autostart.cpp src/autocorre
|
|||||||
target_include_directories(kscore PUBLIC src PRIVATE vendor)
|
target_include_directories(kscore PUBLIC src PRIVATE vendor)
|
||||||
target_compile_definitions(kscore PUBLIC UNICODE _UNICODE WIN32_LEAN_AND_MEAN NOMINMAX _WIN32_WINNT=0x0A00)
|
target_compile_definitions(kscore PUBLIC UNICODE _UNICODE WIN32_LEAN_AND_MEAN NOMINMAX _WIN32_WINNT=0x0A00)
|
||||||
target_link_libraries(kscore PUBLIC user32 shell32 advapi32 crypt32 imm32 comctl32 gdi32 ole32 oleaut32 taskschd comdlg32 uuid windowscodecs runtimeobject uiautomationcore)
|
target_link_libraries(kscore PUBLIC user32 shell32 advapi32 crypt32 imm32 comctl32 gdi32 ole32 oleaut32 taskschd comdlg32 uuid windowscodecs runtimeobject uiautomationcore)
|
||||||
add_executable(KSwitcher WIN32 src/main.cpp src/app.cpp src/screenshot_launcher.cpp src/configuration.cpp src/ui.cpp resources/app.rc)
|
add_executable(KSwitcher WIN32 src/main.cpp src/app.cpp src/screenshot.cpp src/configuration.cpp src/ui.cpp resources/app.rc)
|
||||||
target_compile_definitions(KSwitcher PRIVATE KS_RESIDENT_APP)
|
target_compile_definitions(KSwitcher PRIVATE KS_RESIDENT_APP)
|
||||||
target_link_libraries(KSwitcher PRIVATE kscore)
|
target_link_libraries(KSwitcher PRIVATE kscore)
|
||||||
add_executable(KSwitcherConfig WIN32 src/config_main.cpp src/configuration.cpp src/ui.cpp resources/app.rc)
|
add_executable(KSwitcherConfig WIN32 src/config_main.cpp src/configuration.cpp src/ui.cpp resources/app.rc)
|
||||||
@@ -18,18 +18,12 @@ target_compile_definitions(KSwitcherConfig PRIVATE KS_CONFIG_APP)
|
|||||||
target_link_libraries(KSwitcherConfig PRIVATE kscore)
|
target_link_libraries(KSwitcherConfig PRIVATE kscore)
|
||||||
add_executable(KSwitcherTests tests/tests.cpp)
|
add_executable(KSwitcherTests tests/tests.cpp)
|
||||||
target_link_libraries(KSwitcherTests PRIVATE kscore)
|
target_link_libraries(KSwitcherTests PRIVATE kscore)
|
||||||
add_executable(KSwitcherIntegration tests/integration.cpp src/screenshot_launcher.cpp src/app.cpp src/screenshot.cpp src/configuration.cpp src/ui.cpp resources/app.rc)
|
add_executable(KSwitcherIntegration tests/integration.cpp src/app.cpp src/screenshot.cpp src/configuration.cpp src/ui.cpp resources/app.rc)
|
||||||
target_link_libraries(KSwitcherIntegration PRIVATE kscore)
|
target_link_libraries(KSwitcherIntegration PRIVATE kscore)
|
||||||
add_executable(KSwitcherInputTests tests/input.cpp src/screenshot_launcher.cpp src/app.cpp src/screenshot.cpp src/configuration.cpp src/ui.cpp resources/app.rc)
|
add_executable(KSwitcherInputTests tests/input.cpp src/app.cpp src/screenshot.cpp src/configuration.cpp src/ui.cpp resources/app.rc)
|
||||||
target_link_libraries(KSwitcherInputTests PRIVATE kscore)
|
target_link_libraries(KSwitcherInputTests PRIVATE kscore)
|
||||||
target_compile_definitions(KSwitcherInputTests PRIVATE KS_SCREENSHOT_RESOURCES)
|
add_executable(KSwitcherScreenshotUITests tests/screenshot_ui.cpp src/app.cpp src/configuration.cpp src/ui.cpp resources/app.rc)
|
||||||
add_executable(KSwitcherScreenshotUITests tests/screenshot_ui.cpp resources/app.rc)
|
|
||||||
target_link_libraries(KSwitcherScreenshotUITests PRIVATE kscore)
|
target_link_libraries(KSwitcherScreenshotUITests PRIVATE kscore)
|
||||||
target_compile_definitions(KSwitcherScreenshotUITests PRIVATE KS_SCREENSHOT_RESOURCES)
|
|
||||||
add_executable(KSwitcherScreenshot WIN32 src/screenshot_main.cpp src/screenshot.cpp resources/app.rc)
|
|
||||||
set_target_properties(KSwitcherScreenshot PROPERTIES OUTPUT_NAME "KSwitcher.Screenshot")
|
|
||||||
target_compile_definitions(KSwitcherScreenshot PRIVATE KS_SCREENSHOT_RESOURCES)
|
|
||||||
target_link_libraries(KSwitcherScreenshot PRIVATE kscore)
|
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
target_compile_options(KSwitcherScreenshotUITests PRIVATE /utf-8)
|
target_compile_options(KSwitcherScreenshotUITests PRIVATE /utf-8)
|
||||||
set_property(TARGET KSwitcherScreenshotUITests PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
set_property(TARGET KSwitcherScreenshotUITests PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||||
@@ -39,15 +33,13 @@ endif()
|
|||||||
add_executable(KSwitcherOcrTests tests/ocr.cpp)
|
add_executable(KSwitcherOcrTests tests/ocr.cpp)
|
||||||
target_link_libraries(KSwitcherOcrTests PRIVATE kscore)
|
target_link_libraries(KSwitcherOcrTests PRIVATE kscore)
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
set_property(TARGET kscore KSwitcher KSwitcherConfig KSwitcherScreenshot KSwitcherTests KSwitcherIntegration KSwitcherInputTests KSwitcherOcrTests PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
set_property(TARGET kscore KSwitcher KSwitcherConfig KSwitcherTests KSwitcherIntegration KSwitcherInputTests KSwitcherOcrTests PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||||
target_compile_options(kscore PRIVATE /W4 /utf-8)
|
target_compile_options(kscore PRIVATE /W4 /utf-8)
|
||||||
target_compile_options(KSwitcher PRIVATE /W4 /utf-8)
|
target_compile_options(KSwitcher PRIVATE /W4 /utf-8)
|
||||||
target_compile_options(KSwitcherConfig PRIVATE /W4 /utf-8)
|
target_compile_options(KSwitcherConfig PRIVATE /W4 /utf-8)
|
||||||
target_compile_options(KSwitcherScreenshot PRIVATE /W4 /utf-8)
|
|
||||||
else()
|
else()
|
||||||
target_link_options(KSwitcher PRIVATE -municode -static -static-libgcc -static-libstdc++)
|
target_link_options(KSwitcher PRIVATE -municode -static -static-libgcc -static-libstdc++)
|
||||||
target_link_options(KSwitcherConfig PRIVATE -municode -static -static-libgcc -static-libstdc++)
|
target_link_options(KSwitcherConfig PRIVATE -municode -static -static-libgcc -static-libstdc++)
|
||||||
target_link_options(KSwitcherScreenshot PRIVATE -municode -static -static-libgcc -static-libstdc++)
|
|
||||||
target_link_options(KSwitcherTests PRIVATE -static -static-libgcc -static-libstdc++)
|
target_link_options(KSwitcherTests PRIVATE -static -static-libgcc -static-libstdc++)
|
||||||
target_link_options(KSwitcherIntegration PRIVATE -static -static-libgcc -static-libstdc++)
|
target_link_options(KSwitcherIntegration PRIVATE -static -static-libgcc -static-libstdc++)
|
||||||
target_link_options(KSwitcherInputTests PRIVATE -static -static-libgcc -static-libstdc++)
|
target_link_options(KSwitcherInputTests PRIVATE -static -static-libgcc -static-libstdc++)
|
||||||
@@ -59,7 +51,7 @@ add_test(NAME input-regressions COMMAND KSwitcherInputTests)
|
|||||||
add_test(NAME screenshot-ui-regressions COMMAND KSwitcherScreenshotUITests)
|
add_test(NAME screenshot-ui-regressions COMMAND KSwitcherScreenshotUITests)
|
||||||
add_test(NAME updater-regressions COMMAND powershell.exe -NoProfile -ExecutionPolicy Bypass -File "${CMAKE_CURRENT_SOURCE_DIR}/tests/update.ps1" -Executable "$<TARGET_FILE:KSwitcher>")
|
add_test(NAME updater-regressions COMMAND powershell.exe -NoProfile -ExecutionPolicy Bypass -File "${CMAKE_CURRENT_SOURCE_DIR}/tests/update.ps1" -Executable "$<TARGET_FILE:KSwitcher>")
|
||||||
set_tests_properties(native-regressions PROPERTIES WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
|
set_tests_properties(native-regressions PROPERTIES WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
install(TARGETS KSwitcher KSwitcherConfig KSwitcherScreenshot RUNTIME DESTINATION .)
|
install(TARGETS KSwitcher KSwitcherConfig RUNTIME DESTINATION .)
|
||||||
install(FILES LICENSE vendor/json-LICENSE.txt DESTINATION .)
|
install(FILES LICENSE vendor/json-LICENSE.txt DESTINATION .)
|
||||||
|
|
||||||
add_custom_command(TARGET KSwitcher POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/dictionaries" "$<TARGET_FILE_DIR:KSwitcher>/dictionaries")
|
add_custom_command(TARGET KSwitcher POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/dictionaries" "$<TARGET_FILE_DIR:KSwitcher>/dictionaries")
|
||||||
|
|||||||
@@ -16,25 +16,12 @@ Settings and the snippet editor run on demand in **KSwitcher.Config.exe**, besid
|
|||||||
Repeated launches activate the existing configurator; closing its last window exits that process.
|
Repeated launches activate the existing configurator; closing its last window exits that process.
|
||||||
Saving applies changes immediately through the resident KSwitcher process. The configurator does not
|
Saving applies changes immediately through the resident KSwitcher process. The configurator does not
|
||||||
load clipboard history, dictionaries or global input hooks. Clipboard history and the log viewer remain
|
load clipboard history, dictionaries or global input hooks. Clipboard history and the log viewer remain
|
||||||
in the resident app. Screen capture and all screenshot editors run on demand in
|
in the resident app. Keep both executables together when copying or updating the application.
|
||||||
**KSwitcher.Screenshot.exe**. The capture hotkey starts it; subsequent captures reuse the same
|
|
||||||
process. Closing the last editor (or cancelling the only selection) exits that process. It does not
|
|
||||||
load resident settings, dictionaries, clipboard history or keyboard hooks. Editor windows have
|
|
||||||
stable numbered titles within that process. Keep all three executables together when copying or
|
|
||||||
updating the application. Close screenshot editors before installing an update.
|
|
||||||
If settings change in the tray while the configurator is open, a conflicting Save is rejected; reopen
|
If settings change in the tray while the configurator is open, a conflicting Save is rejected; reopen
|
||||||
the configurator to load the latest settings.
|
the configurator to load the latest settings.
|
||||||
Updates from v0.3.x–v0.4.1 first install the compatible **v0.4.2 bridge** through
|
The first upgrade from v0.2.3 or earlier to this two-executable build requires manual extraction:
|
||||||
Update in the tray menu. Select Update again to install the full current release;
|
the older updater rejects additional executable files. Exit KSwitcher, replace both executables,
|
||||||
manual extraction is not required. Future full releases use stable `release-vX.Y.Z`
|
and retain `data/` and customized dictionaries. Subsequent updates support both executables.
|
||||||
tags, which the bridge and newer clients recognize. Older clients ignore those tags
|
|
||||||
and continue to find the bridge instead of skipping it. Versions v0.2.3 and earlier
|
|
||||||
still require the previously documented initial configurator migration.
|
|
||||||
|
|
||||||
Modern ZIPs contain `release-files.json` (schema, version, file paths, sizes and SHA-256
|
|
||||||
hashes). The updater validates that every packaged file is declared, all hashes match,
|
|
||||||
and paths stay within the installation directory. `data/` is protected; existing
|
|
||||||
dictionaries are preserved. Adding a new program file no longer needs a new updater.
|
|
||||||
In Settings, enable **Start with Windows** and **Start with Windows as administrator**
|
In Settings, enable **Start with Windows** and **Start with Windows as administrator**
|
||||||
to use a per-user Task Scheduler logon task with highest privileges. Enabling or changing
|
to use a per-user Task Scheduler logon task with highest privileges. Enabling or changing
|
||||||
the elevated task requests UAC approval if the app is not already elevated. No password is stored;
|
the elevated task requests UAC approval if the app is not already elevated. No password is stored;
|
||||||
@@ -131,10 +118,10 @@ Dash-dot; **Ends** supports an arrow at the end, start, both ends, or no arrows.
|
|||||||
apply to selected straight/freehand lines and set defaults for subsequent lines. Both are disabled
|
apply to selected straight/freehand lines and set defaults for subsequent lines. Both are disabled
|
||||||
for numbered markers and highlights unless a line drawing tool is active. Undo/Redo includes style changes.
|
for numbered markers and highlights unless a line drawing tool is active. Undo/Redo includes style changes.
|
||||||
The window close button asks for confirmation before discarding an uncopied screenshot.
|
The window close button asks for confirmation before discarding an uncopied screenshot.
|
||||||
**Escape** cancels region selection or inline text edits; it does not close the editor. Copy keeps the editor open and publishes the image
|
**Escape** cancels region selection or inline text edits; it does not close the editor. Copy closes the editor and publishes the image
|
||||||
at its original pixel size, ready to paste. The preview fits the window without changing export size.
|
at its original pixel size, ready to paste. The preview fits the window without changing export size.
|
||||||
The shortcut is configurable in Settings under **Screen capture**.
|
The shortcut is configurable in Settings under **Screen capture**.
|
||||||
Each capture opens an independent editor available through Alt+Tab; use the shortcut again to take another screenshot while
|
Each capture opens an independent editor; use the shortcut again to take another screenshot while
|
||||||
keeping earlier editors and their annotations open.
|
keeping earlier editors and their annotations open.
|
||||||
Captures use ordinary clipboard history settings and limits. Protected video and secure desktops cannot be captured.
|
Captures use ordinary clipboard history settings and limits. Protected video and secure desktops cannot be captured.
|
||||||
|
|
||||||
@@ -360,14 +347,12 @@ It still uses the normal instance lock; close a running KSwitcher first if measu
|
|||||||
Application code is MIT licensed; preserves the original [Typlo](https://github.com/bitcodepro/Typlo) attribution in `LICENSE`.
|
Application code is MIT licensed; preserves the original [Typlo](https://github.com/bitcodepro/Typlo) attribution in `LICENSE`.
|
||||||
Dictionary data has its own CC BY-SA 4.0 license as described above.
|
Dictionary data has its own CC BY-SA 4.0 license as described above.
|
||||||
|
|
||||||
The screenshot process lifecycle can be checked interactively with `tests/screenshot_process.ps1`
|
## Compatibility update bridge (v0.4.2)
|
||||||
against a staged build. It briefly opens real selection/editor windows without changing the clipboard.
|
|
||||||
|
|
||||||
### Release channel rule
|
This two-executable release can be installed by the v0.3.x–v0.4.1 updater.
|
||||||
|
It upgrades the updater to discover stable `release-vX.Y.Z` tags and validate
|
||||||
Keep `v0.4.2` as the last legacy `vX.Y.Z` release. Publish full packages as
|
manifest-based packages. Use Update again after this version starts to install
|
||||||
`release-vX.Y.Z` (stable, not prerelease), including `release-files.json`,
|
the full current release, including the standalone screenshot editor.
|
||||||
`KSwitcher-win-x64.zip` and `SHA256SUMS.txt`. Never publish a manifest package under a
|
The screenshot editor remains embedded only in this transitional release.
|
||||||
legacy version tag: clients that have not installed the bridge would select it.
|
Future full releases must use the `release-v` prefix so unmigrated clients
|
||||||
Keep the compatibility bridge in the first 100 releases returned by Gitea while
|
continue to discover v0.4.2 first rather than skip this bridge.
|
||||||
supporting old clients; their immutable update code only reads that page.
|
|
||||||
|
|||||||
Binary file not shown.
@@ -1 +1 @@
|
|||||||
7b9f07168023d2ad5c71672d0e699144bc15b11175eb05a7149b72b8be4f90b4 KSwitcher-win-x64.zip
|
7f0ee55eb5b56a6227431c1951ac2751208d3d0c7c34e4d1d616e180c37b1d9f KSwitcher-win-x64.zip
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Push-Location $root
|
|||||||
try {
|
try {
|
||||||
$common = @('-std=c++20','-Os','-DNDEBUG','-DUNICODE','-D_UNICODE','-DWIN32_LEAN_AND_MEAN','-DNOMINMAX','-D_WIN32_WINNT=0x0A00','-Wall','-Wextra','-Wno-missing-field-initializers','-Isrc','-Ivendor','-ffunction-sections','-fdata-sections')
|
$common = @('-std=c++20','-Os','-DNDEBUG','-DUNICODE','-D_UNICODE','-DWIN32_LEAN_AND_MEAN','-DNOMINMAX','-D_WIN32_WINNT=0x0A00','-Wall','-Wextra','-Wno-missing-field-initializers','-Isrc','-Ivendor','-ffunction-sections','-fdata-sections')
|
||||||
$libs = @('-luser32','-lshell32','-ladvapi32','-lcrypt32','-limm32','-lcomctl32','-lgdi32','-lole32','-loleaut32','-ltaskschd','-lcomdlg32','-luuid','-lwindowscodecs','-lruntimeobject','-luiautomationcore')
|
$libs = @('-luser32','-lshell32','-ladvapi32','-lcrypt32','-limm32','-lcomctl32','-lgdi32','-lole32','-loleaut32','-ltaskschd','-lcomdlg32','-luuid','-lwindowscodecs','-lruntimeobject','-luiautomationcore')
|
||||||
foreach ($name in @('core','platform','autostart','autocorrect','storage','ocr','richtext','update','screenshot','screenshot_main','screenshot_launcher','app','ui','main','configuration','config_main')) {
|
foreach ($name in @('core','platform','autostart','autocorrect','storage','ocr','richtext','update','screenshot','app','ui','main','configuration','config_main')) {
|
||||||
& $compiler @common -c "src/$name.cpp" -o "build/$name.o"
|
& $compiler @common -c "src/$name.cpp" -o "build/$name.o"
|
||||||
if ($LASTEXITCODE) { throw "Compilation failed: $name" }
|
if ($LASTEXITCODE) { throw "Compilation failed: $name" }
|
||||||
}
|
}
|
||||||
@@ -24,30 +24,24 @@ try {
|
|||||||
if ($LASTEXITCODE) { throw 'Configurator UI compilation failed' }
|
if ($LASTEXITCODE) { throw 'Configurator UI compilation failed' }
|
||||||
& windres --codepage=65001 resources/app.rc -O coff -o build/app-res.o
|
& windres --codepage=65001 resources/app.rc -O coff -o build/app-res.o
|
||||||
if ($LASTEXITCODE) { throw 'Resource compilation failed' }
|
if ($LASTEXITCODE) { throw 'Resource compilation failed' }
|
||||||
& windres --codepage=65001 -DKS_SCREENSHOT_RESOURCES resources/app.rc -O coff -o build/screenshot-res.o
|
|
||||||
if ($LASTEXITCODE) { throw 'Screenshot resource compilation failed' }
|
|
||||||
New-Item -ItemType Directory -Force build/release | Out-Null
|
New-Item -ItemType Directory -Force build/release | Out-Null
|
||||||
& $compiler -mwindows -municode -static -s '-Wl,--gc-sections' build/core.o build/platform.o build/autostart.o build/autocorrect.o build/storage.o build/ocr.o build/richtext.o build/update.o build/screenshot_launcher.o build/app.o build/configuration.o build/ui-resident.o build/main.o build/app-res.o @libs -o $executablePath
|
& $compiler -mwindows -municode -static -s '-Wl,--gc-sections' build/core.o build/platform.o build/autostart.o build/autocorrect.o build/storage.o build/ocr.o build/richtext.o build/update.o build/screenshot.o build/app.o build/configuration.o build/ui-resident.o build/main.o build/app-res.o @libs -o $executablePath
|
||||||
if ($LASTEXITCODE) { throw 'Link failed' }
|
if ($LASTEXITCODE) { throw 'Link failed' }
|
||||||
Copy-Item -LiteralPath $executablePath -Destination build/release/KSwitcher.exe -Force
|
Copy-Item -LiteralPath $executablePath -Destination build/release/KSwitcher.exe -Force
|
||||||
$configPath = Join-Path (Split-Path $executablePath) 'KSwitcher.Config.exe'
|
$configPath = Join-Path (Split-Path $executablePath) 'KSwitcher.Config.exe'
|
||||||
& $compiler -mwindows -municode -static -s '-Wl,--gc-sections' build/core.o build/platform.o build/storage.o build/ocr.o build/configuration.o build/config_main.o build/ui-config.o build/app-res.o @libs -o $configPath
|
& $compiler -mwindows -municode -static -s '-Wl,--gc-sections' build/core.o build/platform.o build/storage.o build/ocr.o build/configuration.o build/config_main.o build/ui-config.o build/app-res.o @libs -o $configPath
|
||||||
if ($LASTEXITCODE) { throw 'Configurator link failed' }
|
if ($LASTEXITCODE) { throw 'Configurator link failed' }
|
||||||
Copy-Item -LiteralPath $configPath -Destination build/release/KSwitcher.Config.exe -Force
|
Copy-Item -LiteralPath $configPath -Destination build/release/KSwitcher.Config.exe -Force
|
||||||
$screenshotPath = Join-Path (Split-Path $executablePath) 'KSwitcher.Screenshot.exe'
|
|
||||||
& $compiler -mwindows -municode -static -s '-Wl,--gc-sections' build/core.o build/platform.o build/screenshot.o build/screenshot_main.o build/screenshot-res.o @libs -o $screenshotPath
|
|
||||||
if ($LASTEXITCODE) { throw 'Screenshot link failed' }
|
|
||||||
Copy-Item -LiteralPath $screenshotPath -Destination build/release/KSwitcher.Screenshot.exe -Force
|
|
||||||
if ($Test) {
|
if ($Test) {
|
||||||
& $compiler @common tests/tests.cpp build/core.o build/platform.o build/autostart.o build/autocorrect.o build/storage.o build/ocr.o build/richtext.o build/update.o @libs -static -s -o build/KSwitcherTests.exe
|
& $compiler @common tests/tests.cpp build/core.o build/platform.o build/autostart.o build/autocorrect.o build/storage.o build/ocr.o build/richtext.o build/update.o @libs -static -s -o build/KSwitcherTests.exe
|
||||||
if ($LASTEXITCODE) { throw 'Test build failed' }
|
if ($LASTEXITCODE) { throw 'Test build failed' }
|
||||||
& .\build\KSwitcherTests.exe
|
& .\build\KSwitcherTests.exe
|
||||||
if ($LASTEXITCODE) { throw 'Tests failed' }
|
if ($LASTEXITCODE) { throw 'Tests failed' }
|
||||||
& $compiler @common tests/input.cpp build/core.o build/platform.o build/autostart.o build/autocorrect.o build/storage.o build/ocr.o build/richtext.o build/update.o build/screenshot.o build/screenshot_launcher.o build/app.o build/configuration.o build/ui.o build/screenshot-res.o @libs -static -s -o build/KSwitcherInputTests.exe
|
& $compiler @common tests/input.cpp build/core.o build/platform.o build/autostart.o build/autocorrect.o build/storage.o build/ocr.o build/richtext.o build/update.o build/screenshot.o build/app.o build/configuration.o build/ui.o build/app-res.o @libs -static -s -o build/KSwitcherInputTests.exe
|
||||||
if ($LASTEXITCODE) { throw 'Input test build failed' }
|
if ($LASTEXITCODE) { throw 'Input test build failed' }
|
||||||
& .\build\KSwitcherInputTests.exe
|
& .\build\KSwitcherInputTests.exe
|
||||||
if ($LASTEXITCODE) { throw 'Input tests failed' }
|
if ($LASTEXITCODE) { throw 'Input tests failed' }
|
||||||
& $compiler @common tests/screenshot_ui.cpp build/core.o build/platform.o build/screenshot-res.o @libs -static -s -o build/KSwitcherScreenshotUITests.exe
|
& $compiler @common tests/screenshot_ui.cpp build/core.o build/platform.o build/autostart.o build/autocorrect.o build/storage.o build/ocr.o build/richtext.o build/update.o build/app.o build/configuration.o build/ui.o build/app-res.o @libs -static -s -o build/KSwitcherScreenshotUITests.exe
|
||||||
if ($LASTEXITCODE) { throw 'Screenshot UI test build failed' }
|
if ($LASTEXITCODE) { throw 'Screenshot UI test build failed' }
|
||||||
& .\build\KSwitcherScreenshotUITests.exe
|
& .\build\KSwitcherScreenshotUITests.exe
|
||||||
if ($LASTEXITCODE) { throw 'Screenshot UI tests failed' }
|
if ($LASTEXITCODE) { throw 'Screenshot UI tests failed' }
|
||||||
@@ -55,7 +49,7 @@ try {
|
|||||||
if ($LASTEXITCODE) { throw 'Updater tests failed' }
|
if ($LASTEXITCODE) { throw 'Updater tests failed' }
|
||||||
}
|
}
|
||||||
if ($Integration) {
|
if ($Integration) {
|
||||||
& $compiler @common tests/integration.cpp build/core.o build/platform.o build/autostart.o build/autocorrect.o build/storage.o build/ocr.o build/richtext.o build/update.o build/screenshot.o build/screenshot_launcher.o build/app.o build/configuration.o build/ui.o build/screenshot-res.o @libs -static -s -o build/KSwitcherIntegration.exe
|
& $compiler @common tests/integration.cpp build/core.o build/platform.o build/autostart.o build/autocorrect.o build/storage.o build/ocr.o build/richtext.o build/update.o build/screenshot.o build/app.o build/configuration.o build/ui.o build/app-res.o @libs -static -s -o build/KSwitcherIntegration.exe
|
||||||
if ($LASTEXITCODE) { throw 'Integration test build failed' }
|
if ($LASTEXITCODE) { throw 'Integration test build failed' }
|
||||||
& .\build\KSwitcherIntegration.exe
|
& .\build\KSwitcherIntegration.exe
|
||||||
if ($LASTEXITCODE) { throw 'Integration tests failed' }
|
if ($LASTEXITCODE) { throw 'Integration tests failed' }
|
||||||
|
|||||||
+1
-11
@@ -5,22 +5,12 @@ $destination = Join-Path $root 'dist\KSwitcher'
|
|||||||
New-Item -ItemType Directory -Force $destination | Out-Null
|
New-Item -ItemType Directory -Force $destination | Out-Null
|
||||||
Copy-Item -LiteralPath (Join-Path $root 'build\release\KSwitcher.exe') -Destination $destination
|
Copy-Item -LiteralPath (Join-Path $root 'build\release\KSwitcher.exe') -Destination $destination
|
||||||
Copy-Item -LiteralPath (Join-Path $root 'build\release\KSwitcher.Config.exe') -Destination $destination
|
Copy-Item -LiteralPath (Join-Path $root 'build\release\KSwitcher.Config.exe') -Destination $destination
|
||||||
Copy-Item -LiteralPath (Join-Path $root 'build\release\KSwitcher.Screenshot.exe') -Destination $destination
|
|
||||||
Copy-Item -LiteralPath (Join-Path $root 'LICENSE') -Destination $destination
|
Copy-Item -LiteralPath (Join-Path $root 'LICENSE') -Destination $destination
|
||||||
Copy-Item -LiteralPath (Join-Path $root 'vendor\json-LICENSE.txt') -Destination $destination
|
Copy-Item -LiteralPath (Join-Path $root 'vendor\json-LICENSE.txt') -Destination $destination
|
||||||
Copy-Item -LiteralPath (Join-Path $root 'README.md') -Destination $destination
|
Copy-Item -LiteralPath (Join-Path $root 'README.md') -Destination $destination
|
||||||
Copy-Item -LiteralPath (Join-Path $root 'dictionaries') -Destination $destination -Recurse -Force
|
Copy-Item -LiteralPath (Join-Path $root 'dictionaries') -Destination $destination -Recurse -Force
|
||||||
# Enumerate the distributable files explicitly; do not include a user's data directory.
|
# Enumerate the distributable files explicitly; do not include a user's data directory.
|
||||||
$files = @('KSwitcher.exe','KSwitcher.Config.exe','KSwitcher.Screenshot.exe','LICENSE','json-LICENSE.txt','README.md','dictionaries') | ForEach-Object { Join-Path $destination $_ }
|
$files = @('KSwitcher.exe','KSwitcher.Config.exe','LICENSE','json-LICENSE.txt','README.md','dictionaries') | ForEach-Object { Join-Path $destination $_ }
|
||||||
$entries = foreach ($path in $files) {
|
|
||||||
foreach ($file in @(Get-ChildItem -LiteralPath $path -File -Recurse)) {
|
|
||||||
@{path=$file.FullName.Substring($destination.Length+1).Replace('\','/'); size=$file.Length; sha256=(Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash.ToLowerInvariant()}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$version=[Diagnostics.FileVersionInfo]::GetVersionInfo((Join-Path $destination 'KSwitcher.exe')).FileVersion
|
|
||||||
$manifest=Join-Path $destination 'release-files.json'
|
|
||||||
@{schema=1; version=$version; files=@($entries)} | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $manifest -Encoding utf8
|
|
||||||
$files += $manifest
|
|
||||||
$archive = Join-Path $root 'dist\KSwitcher-win-x64.zip'
|
$archive = Join-Path $root 'dist\KSwitcher-win-x64.zip'
|
||||||
Compress-Archive -LiteralPath $files -DestinationPath $archive -Force
|
Compress-Archive -LiteralPath $files -DestinationPath $archive -Force
|
||||||
Get-FileHash -LiteralPath $archive -Algorithm SHA256
|
Get-FileHash -LiteralPath $archive -Algorithm SHA256
|
||||||
|
|||||||
+3
-9
@@ -2,15 +2,12 @@
|
|||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include "resource.h"
|
#include "resource.h"
|
||||||
IDI_KSWITCHER ICON "kswitcher.ico"
|
IDI_KSWITCHER ICON "kswitcher.ico"
|
||||||
#ifdef KS_SCREENSHOT_RESOURCES
|
|
||||||
IDI_SCREENSHOT_QUESTION ICON "icons/question.ico"
|
|
||||||
IDC_SCREENSHOT_PENCIL CURSOR "icons/lucide/pencil.cur"
|
IDC_SCREENSHOT_PENCIL CURSOR "icons/lucide/pencil.cur"
|
||||||
#endif
|
|
||||||
1 RT_MANIFEST "app.manifest"
|
1 RT_MANIFEST "app.manifest"
|
||||||
201 RCDATA "update.ps1"
|
201 RCDATA "update.ps1"
|
||||||
1 VERSIONINFO
|
1 VERSIONINFO
|
||||||
FILEVERSION 0,5,0,0
|
FILEVERSION 0,4,2,0
|
||||||
PRODUCTVERSION 0,5,0,0
|
PRODUCTVERSION 0,4,2,0
|
||||||
FILEOS VOS_NT_WINDOWS32
|
FILEOS VOS_NT_WINDOWS32
|
||||||
FILETYPE VFT_APP
|
FILETYPE VFT_APP
|
||||||
BEGIN
|
BEGIN
|
||||||
@@ -20,7 +17,7 @@ BEGIN
|
|||||||
BEGIN
|
BEGIN
|
||||||
VALUE "FileDescription", "KSwitcher — native keyboard and clipboard utility"
|
VALUE "FileDescription", "KSwitcher — native keyboard and clipboard utility"
|
||||||
VALUE "ProductName", "KSwitcher"
|
VALUE "ProductName", "KSwitcher"
|
||||||
VALUE "FileVersion", "0.5.0"
|
VALUE "FileVersion", "0.4.2"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
@@ -28,7 +25,6 @@ BEGIN
|
|||||||
VALUE "Translation", 0x409, 1200
|
VALUE "Translation", 0x409, 1200
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
#ifdef KS_SCREENSHOT_RESOURCES
|
|
||||||
300 ICON "icons/lucide/circle-dot-normal.ico"
|
300 ICON "icons/lucide/circle-dot-normal.ico"
|
||||||
310 ICON "icons/lucide/circle-dot-active.ico"
|
310 ICON "icons/lucide/circle-dot-active.ico"
|
||||||
320 ICON "icons/lucide/circle-dot-disabled.ico"
|
320 ICON "icons/lucide/circle-dot-disabled.ico"
|
||||||
@@ -77,5 +73,3 @@ END
|
|||||||
405 ICON "icons/lucide/a-large-small-normal.ico"
|
405 ICON "icons/lucide/a-large-small-normal.ico"
|
||||||
415 ICON "icons/lucide/a-large-small-active.ico"
|
415 ICON "icons/lucide/a-large-small-active.ico"
|
||||||
425 ICON "icons/lucide/a-large-small-disabled.ico"
|
425 ICON "icons/lucide/a-large-small-disabled.ico"
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 56 KiB |
@@ -1,14 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="blue" x1="8" y1="4" x2="36" y2="44" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop stop-color="#38bdf8"/>
|
|
||||||
<stop offset=".55" stop-color="#1687e8"/>
|
|
||||||
<stop offset="1" stop-color="#2563eb"/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<circle cx="24" cy="25" r="21" fill="#1e40af" opacity=".12"/>
|
|
||||||
<circle cx="24" cy="23" r="21" fill="url(#blue)"/>
|
|
||||||
<circle cx="24" cy="23" r="20.5" fill="none" stroke="white" stroke-opacity=".25"/>
|
|
||||||
<path d="M18 17.5c0-3.1 2.5-5 6-5s6 2 6 5c0 4.8-6 4.8-6 9" fill="none" stroke="white" stroke-width="3.5" stroke-linecap="round"/>
|
|
||||||
<circle cx="24" cy="33" r="2" fill="white"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 711 B |
@@ -1,4 +1,3 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#define IDI_KSWITCHER 101
|
#define IDI_KSWITCHER 101
|
||||||
#define IDI_SCREENSHOT_QUESTION 102
|
|
||||||
#define IDC_SCREENSHOT_PENCIL 500
|
#define IDC_SCREENSHOT_PENCIL 500
|
||||||
|
|||||||
+1
-1
@@ -589,7 +589,7 @@ void App::dispatch(int command)
|
|||||||
{
|
{
|
||||||
if (command == Screenshot)
|
if (command == Screenshot)
|
||||||
{
|
{
|
||||||
if (!busy) { resetInput(); launchScreenshot(*this); }
|
if (!busy) showScreenshot(*this);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (command == ShowHistory)
|
if (command == ShowHistory)
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ class App
|
|||||||
static LRESULT CALLBACK mouseProc(int, WPARAM, LPARAM);
|
static LRESULT CALLBACK mouseProc(int, WPARAM, LPARAM);
|
||||||
};
|
};
|
||||||
void showHistory(App &);
|
void showHistory(App &);
|
||||||
void launchScreenshot(App &);
|
void showScreenshot(App &);
|
||||||
void refreshHistory(App &);
|
void refreshHistory(App &);
|
||||||
void showSettings(App &, bool visible = true);
|
void showSettings(App &, bool visible = true);
|
||||||
void showLogs(App &);
|
void showLogs(App &);
|
||||||
|
|||||||
+15
-30
@@ -1,3 +1,4 @@
|
|||||||
|
#include "app.h"
|
||||||
#include "screenshot.h"
|
#include "screenshot.h"
|
||||||
#include "../resources/resource.h"
|
#include "../resources/resource.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -12,21 +13,14 @@ namespace
|
|||||||
{
|
{
|
||||||
// Only selection is exclusive; each completed capture owns an independent editor.
|
// Only selection is exclusive; each completed capture owns an independent editor.
|
||||||
HWND activeCapture{};
|
HWND activeCapture{};
|
||||||
// Keep titles stable and distinct for every editor opened during this session.
|
|
||||||
unsigned long long nextEditorIndex=1;
|
|
||||||
bool confirmScreenshotClose(HWND owner)
|
bool confirmScreenshotClose(HWND owner)
|
||||||
{
|
{
|
||||||
TASKDIALOGCONFIG dialog{sizeof(dialog)};
|
TASKDIALOGCONFIG dialog{sizeof(dialog)};
|
||||||
dialog.hwndParent=owner;
|
dialog.hwndParent=owner;
|
||||||
dialog.dwFlags=TDF_POSITION_RELATIVE_TO_WINDOW|TDF_ALLOW_DIALOG_CANCELLATION|TDF_USE_HICON_MAIN;
|
dialog.dwFlags=TDF_POSITION_RELATIVE_TO_WINDOW|TDF_ALLOW_DIALOG_CANCELLATION;
|
||||||
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.dwCommonButtons=TDCBF_YES_BUTTON|TDCBF_NO_BUTTON;
|
||||||
dialog.pszWindowTitle=L"KSwitcher";
|
dialog.pszWindowTitle=L"KSwitcher";
|
||||||
dialog.pszMainInstruction=L"Close without copying?";
|
dialog.pszContent=L"Close without copying this screenshot to the clipboard?";
|
||||||
dialog.pszContent=L"Your latest screenshot changes have not been copied to the clipboard.";
|
|
||||||
dialog.nDefaultButton=IDNO;
|
dialog.nDefaultButton=IDNO;
|
||||||
dialog.lpCallbackData=(LONG_PTR)owner;
|
dialog.lpCallbackData=(LONG_PTR)owner;
|
||||||
dialog.pfCallback=[](HWND window,UINT notification,WPARAM,LPARAM,LONG_PTR data)->HRESULT {
|
dialog.pfCallback=[](HWND window,UINT notification,WPARAM,LPARAM,LONG_PTR data)->HRESULT {
|
||||||
@@ -46,7 +40,6 @@ bool confirmScreenshotClose(HWND owner)
|
|||||||
};
|
};
|
||||||
int button=IDNO;
|
int button=IDNO;
|
||||||
auto result=TaskDialogIndirect(&dialog,&button,nullptr,nullptr);
|
auto result=TaskDialogIndirect(&dialog,&button,nullptr,nullptr);
|
||||||
if(icon) DestroyIcon(icon);
|
|
||||||
// If Windows cannot display the confirmation, keep the screenshot open.
|
// If Windows cannot display the confirmation, keep the screenshot open.
|
||||||
return SUCCEEDED(result) && button==IDYES;
|
return SUCCEEDED(result) && button==IDYES;
|
||||||
}
|
}
|
||||||
@@ -122,7 +115,7 @@ bool chooseTextFont(HWND owner,Mark &m)
|
|||||||
}
|
}
|
||||||
struct Capture
|
struct Capture
|
||||||
{
|
{
|
||||||
ScreenshotHost &app;
|
App &app;
|
||||||
std::unique_ptr<Surface> image;
|
std::unique_ptr<Surface> image;
|
||||||
std::unique_ptr<Surface> dimmed, frame;
|
std::unique_ptr<Surface> dimmed, frame;
|
||||||
bool editing{}, dragging{}, arrow{}, owned{}, copied{};
|
bool editing{}, dragging{}, arrow{}, owned{}, copied{};
|
||||||
@@ -160,7 +153,7 @@ struct Capture
|
|||||||
int hoveredTool=0;
|
int hoveredTool=0;
|
||||||
MarkPart hoveredPart=MarkPart::None;
|
MarkPart hoveredPart=MarkPart::None;
|
||||||
std::wstring statusText;
|
std::wstring statusText;
|
||||||
explicit Capture(ScreenshotHost &a) : app(a) {}
|
explicit Capture(App &a) : app(a) {}
|
||||||
~Capture() {
|
~Capture() {
|
||||||
if(pencilCursor) DestroyCursor(pencilCursor);
|
if(pencilCursor) DestroyCursor(pencilCursor);
|
||||||
if (font) DeleteObject(font);
|
if (font) DeleteObject(font);
|
||||||
@@ -169,7 +162,6 @@ struct Capture
|
|||||||
}
|
}
|
||||||
void remember(const std::vector<Mark> &previous)
|
void remember(const std::vector<Mark> &previous)
|
||||||
{
|
{
|
||||||
copied=false;
|
|
||||||
history.remember(previous);
|
history.remember(previous);
|
||||||
syncHistory();
|
syncHistory();
|
||||||
}
|
}
|
||||||
@@ -728,13 +720,14 @@ LRESULT CALLBACK toolHover(HWND w, UINT msg, WPARAM wp, LPARAM lp, UINT_PTR, DWO
|
|||||||
if(msg==WM_MOUSELEAVE) InvalidateRect(w,nullptr,FALSE);
|
if(msg==WM_MOUSELEAVE) InvalidateRect(w,nullptr,FALSE);
|
||||||
return DefSubclassProc(w,msg,wp,lp);
|
return DefSubclassProc(w,msg,wp,lp);
|
||||||
}
|
}
|
||||||
void copy(HWND w, Capture &c, DWORD (*publish)(HWND,const Clip &)=writeClipboard)
|
void copy(HWND w, Capture &c)
|
||||||
{
|
{
|
||||||
finishInlineText(w,c,true);
|
finishInlineText(w,c,true);
|
||||||
// The resident message window keeps ownership if the editor is closed later.
|
// The resident message window owns the clipboard after this editor closes.
|
||||||
auto clip=screenshotClip(c.image->dc,c.image->width,c.image->height,c.marks);
|
auto clip=screenshotClip(c.image->dc,c.image->width,c.image->height,c.marks);
|
||||||
publish(c.app.window, clip);
|
writeClipboard(c.app.window, clip);
|
||||||
c.copied=true;
|
c.copied=true;
|
||||||
|
DestroyWindow(w);
|
||||||
}
|
}
|
||||||
void edit(HWND w, Capture &c)
|
void edit(HWND w, Capture &c)
|
||||||
{
|
{
|
||||||
@@ -752,11 +745,8 @@ void edit(HWND w, Capture &c)
|
|||||||
POINT cursor; GetCursorPos(&cursor);
|
POINT cursor; GetCursorPos(&cursor);
|
||||||
GetMonitorInfoW(MonitorFromPoint(cursor,MONITOR_DEFAULTTOPRIMARY), &monitor);
|
GetMonitorInfoW(MonitorFromPoint(cursor,MONITOR_DEFAULTTOPRIMARY), &monitor);
|
||||||
ShowWindow(w,SW_HIDE);
|
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);
|
SetWindowLongPtrW(w,GWL_STYLE,WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN);
|
||||||
auto title=L"KSwitcher — Screenshot #"+std::to_wstring(nextEditorIndex++);
|
SetWindowTextW(w,L"KSwitcher — Screenshot");
|
||||||
SetWindowTextW(w,title.c_str());
|
|
||||||
INITCOMMONCONTROLSEX statusControls{sizeof(statusControls),ICC_BAR_CLASSES};
|
INITCOMMONCONTROLSEX statusControls{sizeof(statusControls),ICC_BAR_CLASSES};
|
||||||
InitCommonControlsEx(&statusControls);
|
InitCommonControlsEx(&statusControls);
|
||||||
CreateWindowExW(0,STATUSCLASSNAMEW,L"",WS_CHILD|WS_VISIBLE|SBARS_TOOLTIPS,
|
CreateWindowExW(0,STATUSCLASSNAMEW,L"",WS_CHILD|WS_VISIBLE|SBARS_TOOLTIPS,
|
||||||
@@ -816,8 +806,7 @@ LRESULT CALLBACK captureProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
|
|||||||
switch (msg)
|
switch (msg)
|
||||||
{
|
{
|
||||||
case WM_CLOSE:
|
case WM_CLOSE:
|
||||||
finishInlineText(w,*c,true);
|
if(c->editing && !c->copied && !confirmScreenshotClose(w)) return 0;
|
||||||
if(c->editing && (!c->copied || c->dragging) && !confirmScreenshotClose(w)) return 0;
|
|
||||||
DestroyWindow(w); return 0;
|
DestroyWindow(w); return 0;
|
||||||
case FinishTextMessage:
|
case FinishTextMessage:
|
||||||
if(c->inlineText && (HWND)lp==c->inlineText && (wp!=0 || GetFocus()!=c->inlineText)) {
|
if(c->inlineText && (HWND)lp==c->inlineText && (wp!=0 || GetFocus()!=c->inlineText)) {
|
||||||
@@ -919,7 +908,7 @@ LRESULT CALLBACK captureProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
|
|||||||
if (LOWORD(wp)==12 || LOWORD(wp)==18)
|
if (LOWORD(wp)==12 || LOWORD(wp)==18)
|
||||||
{
|
{
|
||||||
bool changed=LOWORD(wp)==12?c->history.undo(c->marks):c->history.redo(c->marks);
|
bool changed=LOWORD(wp)==12?c->history.undo(c->marks):c->history.redo(c->marks);
|
||||||
if(changed) { c->copied=false; c->selected=-1; c->selectedLabel=false; c->wheelDelta=0; c->wheelTarget=-1; }
|
if(changed) { c->selected=-1; c->selectedLabel=false; c->wheelDelta=0; c->wheelTarget=-1; }
|
||||||
c->syncHistory();
|
c->syncHistory();
|
||||||
}
|
}
|
||||||
if (LOWORD(wp)==10 || LOWORD(wp)==11 || LOWORD(wp)==19 || (LOWORD(wp)>=21 && LOWORD(wp)<=26))
|
if (LOWORD(wp)==10 || LOWORD(wp)==11 || LOWORD(wp)==19 || (LOWORD(wp)>=21 && LOWORD(wp)<=26))
|
||||||
@@ -1237,11 +1226,7 @@ LRESULT CALLBACK captureProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
|
|||||||
case WM_NCDESTROY:
|
case WM_NCDESTROY:
|
||||||
if (activeCapture == w) activeCapture=nullptr;
|
if (activeCapture == w) activeCapture=nullptr;
|
||||||
SetWindowLongPtrW(w,GWLP_USERDATA,0);
|
SetWindowLongPtrW(w,GWLP_USERDATA,0);
|
||||||
if (c->owned) {
|
if (c->owned) delete c;
|
||||||
auto &host=c->app;
|
|
||||||
if (--host.windows==0) PostMessageW(host.window,ScreenshotIdle,0,0);
|
|
||||||
delete c;
|
|
||||||
}
|
|
||||||
return DefWindowProcW(w,msg,wp,lp);
|
return DefWindowProcW(w,msg,wp,lp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1513,9 +1498,10 @@ Clip screenshotClip(HDC source, int width, int height, const std::vector<Screens
|
|||||||
memcpy(bytes.data()+sizeof(header),output.pixels,bytes.size()-sizeof(header));
|
memcpy(bytes.data()+sizeof(header),output.pixels,bytes.size()-sizeof(header));
|
||||||
return Clip{{{CF_DIB,L"",std::move(bytes)}}};
|
return Clip{{{CF_DIB,L"",std::move(bytes)}}};
|
||||||
}
|
}
|
||||||
void showScreenshot(ScreenshotHost &app)
|
void showScreenshot(App &app)
|
||||||
{
|
{
|
||||||
if (activeCapture) { showRequestedWindow(activeCapture); SetForegroundWindow(activeCapture); return; }
|
if (activeCapture) { showRequestedWindow(activeCapture); SetForegroundWindow(activeCapture); return; }
|
||||||
|
app.resetInput();
|
||||||
auto c=std::make_unique<Capture>(app);
|
auto c=std::make_unique<Capture>(app);
|
||||||
int x=GetSystemMetrics(SM_XVIRTUALSCREEN),y=GetSystemMetrics(SM_YVIRTUALSCREEN);
|
int x=GetSystemMetrics(SM_XVIRTUALSCREEN),y=GetSystemMetrics(SM_YVIRTUALSCREEN);
|
||||||
int width=GetSystemMetrics(SM_CXVIRTUALSCREEN),height=GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
int width=GetSystemMetrics(SM_CXVIRTUALSCREEN),height=GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
||||||
@@ -1542,7 +1528,6 @@ void showScreenshot(ScreenshotHost &app)
|
|||||||
WS_POPUP,x,y,width,height,app.window,nullptr,app.instance,c.get());
|
WS_POPUP,x,y,width,height,app.window,nullptr,app.instance,c.get());
|
||||||
if (!activeCapture) throw std::runtime_error("Cannot open screen capture");
|
if (!activeCapture) throw std::runtime_error("Cannot open screen capture");
|
||||||
c->owned=true;
|
c->owned=true;
|
||||||
++app.windows;
|
|
||||||
c.release();
|
c.release();
|
||||||
showRequestedWindow(activeCapture); SetForegroundWindow(activeCapture); SetFocus(activeCapture);
|
showRequestedWindow(activeCapture); SetForegroundWindow(activeCapture); SetFocus(activeCapture);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,6 @@
|
|||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
namespace ks
|
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 LineStyle { Solid, Dotted, Dashed, DashDot };
|
||||||
enum class LineEnds { End, Start, Both, None };
|
enum class LineEnds { End, Start, Both, None };
|
||||||
enum class ScreenshotShape { None, Rectangle, Circle, Ellipse };
|
enum class ScreenshotShape { None, Rectangle, Circle, Ellipse };
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
#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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
#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"
|
#include "platform.h"
|
||||||
namespace ks
|
namespace ks
|
||||||
{
|
{
|
||||||
inline constexpr wchar_t AppVersion[] = L"0.5.0";
|
inline constexpr wchar_t AppVersion[] = L"0.4.2";
|
||||||
inline constexpr UINT UpdateMessage = WM_APP + 7;
|
inline constexpr UINT UpdateMessage = WM_APP + 7;
|
||||||
struct UpdateResult
|
struct UpdateResult
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
param([string]$Executable = "$PSScriptRoot\..\build\staged\KSwitcher.Screenshot.exe")
|
|
||||||
$ErrorActionPreference='Stop'
|
|
||||||
if (Get-Process KSwitcher.Screenshot -ErrorAction SilentlyContinue) { throw 'Close existing screenshot editors before this test.' }
|
|
||||||
Add-Type @'
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
public static class ScreenshotProcessTest {
|
|
||||||
public delegate bool EnumProc(IntPtr w, IntPtr p);
|
|
||||||
[DllImport("user32.dll")] static extern bool EnumWindows(EnumProc callback, IntPtr param);
|
|
||||||
[DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr w, out uint pid);
|
|
||||||
[DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern int GetClassName(IntPtr w, StringBuilder name, int count);
|
|
||||||
[DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern int GetWindowText(IntPtr w, StringBuilder name, int count);
|
|
||||||
[DllImport("user32.dll")] public static extern bool PostMessage(IntPtr w, uint msg, IntPtr wp, IntPtr lp);
|
|
||||||
public static IntPtr[] Windows(int process, string cls) {
|
|
||||||
var windows=new List<IntPtr>();
|
|
||||||
EnumWindows((w,p)=>{ uint pid; GetWindowThreadProcessId(w,out pid);
|
|
||||||
var name=new StringBuilder(256); GetClassName(w,name,256);
|
|
||||||
if(pid==process && name.ToString()==cls) windows.Add(w); return true;
|
|
||||||
},IntPtr.Zero); return windows.ToArray();
|
|
||||||
}
|
|
||||||
public static string Title(IntPtr w) { var s=new StringBuilder(256); GetWindowText(w,s,256); return s.ToString(); }
|
|
||||||
}
|
|
||||||
'@
|
|
||||||
function Wait-For([scriptblock]$Check,[string]$Message) {
|
|
||||||
for($attempt=0;$attempt -lt 100;$attempt++) { if (& $Check) { return }; Start-Sleep -Milliseconds 50 }
|
|
||||||
throw $Message
|
|
||||||
}
|
|
||||||
function Send($Window,$Message,$Wparam=0,$Lparam=0) {
|
|
||||||
[ScreenshotProcessTest]::PostMessage($Window,$Message,[IntPtr]$Wparam,[IntPtr]$Lparam) | Out-Null
|
|
||||||
}
|
|
||||||
function Capture-Windows { @([ScreenshotProcessTest]::Windows($script:process.Id,'KSwitcher.Capture')) }
|
|
||||||
function Select-Region($Window) {
|
|
||||||
Send $Window 0x201 1 ((40 -shl 16) -bor 40)
|
|
||||||
Send $Window 0x200 1 ((240 -shl 16) -bor 360)
|
|
||||||
Send $Window 0x202 0 ((240 -shl 16) -bor 360)
|
|
||||||
Wait-For { [ScreenshotProcessTest]::Title($Window) -match 'Screenshot #' } 'Selection did not become an editor.'
|
|
||||||
}
|
|
||||||
function Close-Editor($Window) {
|
|
||||||
Send $Window 0x10
|
|
||||||
Wait-For { @([ScreenshotProcessTest]::Windows($script:process.Id,'#32770')).Count -eq 1 } 'Missing close confirmation.'
|
|
||||||
$dialog=[ScreenshotProcessTest]::Windows($script:process.Id,'#32770')[0]
|
|
||||||
Send $dialog (0x400+102) 6 # TDM_CLICK_BUTTON / IDYES; no clipboard writes.
|
|
||||||
Wait-For { (Capture-Windows) -notcontains $Window } 'Editor did not close.'
|
|
||||||
}
|
|
||||||
$process=$null
|
|
||||||
try {
|
|
||||||
$process=Start-Process -FilePath $Executable -WindowStyle Hidden -PassThru
|
|
||||||
Wait-For { (Capture-Windows).Count -eq 1 } 'First selection did not open.'
|
|
||||||
$first=(Capture-Windows)[0]; Select-Region $first
|
|
||||||
$secondLaunch=Start-Process -FilePath $Executable -WindowStyle Hidden -PassThru
|
|
||||||
if (!$secondLaunch.WaitForExit(10000) -or $secondLaunch.ExitCode -ne 0) { throw 'Second launch did not hand off.' }
|
|
||||||
Wait-For { (Capture-Windows).Count -eq 2 } 'Second selection did not open in same process.'
|
|
||||||
$second=(Capture-Windows | Where-Object { $_ -ne $first })[0]; Select-Region $second
|
|
||||||
if ([ScreenshotProcessTest]::Title($first) -eq [ScreenshotProcessTest]::Title($second)) { throw 'Editor indices collide.' }
|
|
||||||
Close-Editor $first
|
|
||||||
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
|
|
||||||
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.' }
|
|
||||||
'Passed screenshot process lifecycle checks'
|
|
||||||
} finally {
|
|
||||||
if ($process -and !$process.HasExited) { Stop-Process -Id $process.Id }
|
|
||||||
}
|
|
||||||
+4
-16
@@ -4,9 +4,10 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
|
auto temp=std::filesystem::temp_directory_path()/(L"KSwitcher-editor-test-"+std::to_wstring(GetCurrentProcessId()));
|
||||||
try {
|
try {
|
||||||
INITCOMMONCONTROLSEX controls{sizeof(controls),ICC_STANDARD_CLASSES}; InitCommonControlsEx(&controls);
|
INITCOMMONCONTROLSEX controls{sizeof(controls),ICC_STANDARD_CLASSES}; InitCommonControlsEx(&controls);
|
||||||
ks::ScreenshotHost app{GetModuleHandleW(nullptr)};
|
ks::App app(GetModuleHandleW(nullptr),true,temp);
|
||||||
app.window=CreateWindowW(L"STATIC",L"",0,0,0,0,0,HWND_MESSAGE,nullptr,app.instance,nullptr);
|
app.window=CreateWindowW(L"STATIC",L"",0,0,0,0,0,HWND_MESSAGE,nullptr,app.instance,nullptr);
|
||||||
ks::Capture c(app);
|
ks::Capture c(app);
|
||||||
c.image=std::make_unique<ks::Surface>(320,240);
|
c.image=std::make_unique<ks::Surface>(320,240);
|
||||||
@@ -17,8 +18,6 @@ int main()
|
|||||||
auto require=[](bool ok,const char *message){ if(!ok) throw std::runtime_error(message); };
|
auto require=[](bool ok,const char *message){ if(!ok) throw std::runtime_error(message); };
|
||||||
require(window!=nullptr,"editor creation");
|
require(window!=nullptr,"editor creation");
|
||||||
ks::edit(window,c); ShowWindow(window,SW_HIDE);
|
ks::edit(window,c); ShowWindow(window,SW_HIDE);
|
||||||
require(GetWindow(window,GW_OWNER)==nullptr && (GetWindowLongPtrW(window,GWL_EXSTYLE)&WS_EX_APPWINDOW),
|
|
||||||
"editor is an independent Alt+Tab application window");
|
|
||||||
require(GetDlgItem(window,50)!=nullptr && c.statusHeight>0,"status bar is created");
|
require(GetDlgItem(window,50)!=nullptr && c.statusHeight>0,"status bar is created");
|
||||||
require(c.pencilCursor!=nullptr,"pencil drawing cursor loads");
|
require(c.pencilCursor!=nullptr,"pencil drawing cursor loads");
|
||||||
for(int size:{32,48,64,96}) {
|
for(int size:{32,48,64,96}) {
|
||||||
@@ -31,7 +30,7 @@ int main()
|
|||||||
DestroyCursor(cursor);
|
DestroyCursor(cursor);
|
||||||
}
|
}
|
||||||
wchar_t windowTitle[128]{}; GetWindowTextW(window,windowTitle,128);
|
wchar_t windowTitle[128]{}; GetWindowTextW(window,windowTitle,128);
|
||||||
require(std::wstring(windowTitle)==L"KSwitcher — Screenshot #1","title includes editor index without usage hints");
|
require(std::wstring(windowTitle)==L"KSwitcher — Screenshot","title has no usage hints");
|
||||||
RECT client{}; GetClientRect(window,&client);
|
RECT client{}; GetClientRect(window,&client);
|
||||||
require(c.view.bottom<=client.bottom-c.statusHeight,"image does not overlap status bar");
|
require(c.view.bottom<=client.bottom-c.statusHeight,"image does not overlap status bar");
|
||||||
ks::updateStatus(c);
|
ks::updateStatus(c);
|
||||||
@@ -125,18 +124,6 @@ int main()
|
|||||||
SendMessageW(window,WM_COMMAND,18,0);
|
SendMessageW(window,WM_COMMAND,18,0);
|
||||||
require(ks::screenshotTextBounds(ks::screenshotArrowLabel(c.marks[0])).left==beforeNudge.left+5,"redo restores one nudge");
|
require(ks::screenshotTextBounds(ks::screenshotArrowLabel(c.marks[0])).left==beforeNudge.left+5,"redo restores one nudge");
|
||||||
SetWindowPos(window,nullptr,80,60,720,500,SWP_NOZORDER|SWP_SHOWWINDOW);
|
SetWindowPos(window,nullptr,80,60,720,500,SWP_NOZORDER|SWP_SHOWWINDOW);
|
||||||
ks::copy(window,c,[](HWND,const ks::Clip &clip)->DWORD {
|
|
||||||
if(clip.formats.empty()) throw std::runtime_error("copy produced no image");
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
require(IsWindow(window) && c.copied,"copy publishes screenshot and keeps editor open");
|
|
||||||
c.selected=0; c.selectedLabel=false;
|
|
||||||
SendMessageW(window,WM_KEYDOWN,VK_RIGHT,0);
|
|
||||||
require(!c.copied,"editing after copy requires confirmation again");
|
|
||||||
c.copied=true; SendMessageW(window,WM_COMMAND,12,0);
|
|
||||||
require(!c.copied,"undo after copy requires confirmation again");
|
|
||||||
c.copied=true; SendMessageW(window,WM_COMMAND,18,0);
|
|
||||||
require(!c.copied,"redo after copy requires confirmation again");
|
|
||||||
SetPropW(window,L"ConfirmationCentered",nullptr);
|
SetPropW(window,L"ConfirmationCentered",nullptr);
|
||||||
SetTimer(window,987,10,[](HWND owner,UINT,UINT_PTR id,DWORD) {
|
SetTimer(window,987,10,[](HWND owner,UINT,UINT_PTR id,DWORD) {
|
||||||
EnumThreadWindows(GetCurrentThreadId(),[](HWND popup,LPARAM parent)->BOOL {
|
EnumThreadWindows(GetCurrentThreadId(),[](HWND popup,LPARAM parent)->BOOL {
|
||||||
@@ -163,6 +150,7 @@ int main()
|
|||||||
SendMessageW(window,WM_CLOSE,0,0);
|
SendMessageW(window,WM_CLOSE,0,0);
|
||||||
require(!IsWindow(window),"copied screenshot closes without confirmation");
|
require(!IsWindow(window),"copied screenshot closes without confirmation");
|
||||||
DestroyWindow(app.window); app.window=nullptr;
|
DestroyWindow(app.window); app.window=nullptr;
|
||||||
|
std::filesystem::remove_all(temp);
|
||||||
std::cout<<"Passed screenshot editor UI integration checks\n";
|
std::cout<<"Passed screenshot editor UI integration checks\n";
|
||||||
} catch(const std::exception &e) { std::cerr<<e.what()<<'\n'; return 1; }
|
} catch(const std::exception &e) { std::cerr<<e.what()<<'\n'; return 1; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
param([Parameter(Mandatory)][string]$BridgeArchive,[Parameter(Mandatory)][string]$FullArchive)
|
|
||||||
$ErrorActionPreference='Stop'
|
|
||||||
Add-Type @'
|
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
public static class UpdateResource {
|
|
||||||
[DllImport("kernel32.dll", CharSet=CharSet.Unicode)] static extern IntPtr LoadLibraryEx(string path, IntPtr file, uint flags);
|
|
||||||
[DllImport("kernel32.dll")] static extern IntPtr FindResource(IntPtr module, IntPtr name, IntPtr type);
|
|
||||||
[DllImport("kernel32.dll")] static extern IntPtr LoadResource(IntPtr module, IntPtr resource);
|
|
||||||
[DllImport("kernel32.dll")] static extern IntPtr LockResource(IntPtr resource);
|
|
||||||
[DllImport("kernel32.dll")] static extern uint SizeofResource(IntPtr module, IntPtr resource);
|
|
||||||
[DllImport("kernel32.dll")] static extern bool FreeLibrary(IntPtr module);
|
|
||||||
public static byte[] Read(string path) {
|
|
||||||
var module=LoadLibraryEx(path,IntPtr.Zero,2|32);
|
|
||||||
if(module==IntPtr.Zero) throw new Exception("Cannot load bridge resources");
|
|
||||||
try {
|
|
||||||
var resource=FindResource(module,(IntPtr)201,(IntPtr)10);
|
|
||||||
var size=SizeofResource(module,resource);
|
|
||||||
if(size==0 || size>1024*1024) throw new Exception("Invalid bridge updater resource");
|
|
||||||
var bytes=new byte[size];
|
|
||||||
Marshal.Copy(LockResource(LoadResource(module,resource)),bytes,0,(int)size);
|
|
||||||
return bytes;
|
|
||||||
} finally { FreeLibrary(module); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'@
|
|
||||||
$root=Join-Path ([IO.Path]::GetTempPath()) ('KSwitcher-transition-'+[guid]::NewGuid().ToString('N'))
|
|
||||||
[IO.Directory]::CreateDirectory($root) | Out-Null
|
|
||||||
try {
|
|
||||||
$legacy=Join-Path $root 'legacy.ps1'
|
|
||||||
$oldSource=git -C (Join-Path $PSScriptRoot '..') show v0.4.1:resources/update.ps1
|
|
||||||
if ($LASTEXITCODE) { throw 'Cannot read the released v0.4.1 updater.' }
|
|
||||||
$oldSource | Set-Content -LiteralPath $legacy -Encoding utf8
|
|
||||||
$releases=@('v0.4.2','release-v0.5.0') | ForEach-Object {
|
|
||||||
@{tag_name=$_;draft=$false;prerelease=$false;assets=@(
|
|
||||||
@{name='KSwitcher-win-x64.zip';browser_download_url="https://gitea.hrynco.com/hrynco/hrynco-kswitcher/releases/download/$_/KSwitcher-win-x64.zip"},
|
|
||||||
@{name='SHA256SUMS.txt';browser_download_url="https://gitea.hrynco.com/hrynco/hrynco-kswitcher/releases/download/$_/SHA256SUMS.txt"})}
|
|
||||||
}
|
|
||||||
$install=Join-Path $root 'install'
|
|
||||||
[IO.Directory]::CreateDirectory((Join-Path $install 'data')) | Out-Null
|
|
||||||
[IO.Directory]::CreateDirectory((Join-Path $install 'dictionaries')) | Out-Null
|
|
||||||
[IO.File]::WriteAllText((Join-Path $install 'data/settings.json'),'personal settings')
|
|
||||||
[IO.File]::WriteAllText((Join-Path $install 'dictionaries/en.txt'),'personal dictionary')
|
|
||||||
& {
|
|
||||||
. $legacy -Library
|
|
||||||
$selected=Select-Release $releases ([version]'0.4.1')
|
|
||||||
if ($selected.version -ne 'v0.4.2') { throw 'Legacy client skipped the bridge.' }
|
|
||||||
$stage=Join-Path $root 'bridge-stage'
|
|
||||||
Expand-Release $BridgeArchive $stage ([version]'0.4.2')
|
|
||||||
$changed=[Collections.Generic.List[string]]::new()
|
|
||||||
Install-Files $stage $install (Join-Path $root 'bridge-backup') $changed
|
|
||||||
}
|
|
||||||
$bridgeHelper=Join-Path $root 'bridge-helper.ps1'
|
|
||||||
[IO.File]::WriteAllBytes($bridgeHelper,[UpdateResource]::Read((Join-Path $install 'KSwitcher.exe')))
|
|
||||||
. $bridgeHelper -Library
|
|
||||||
$selected=Select-Release $releases ([version]'0.4.2')
|
|
||||||
if ($selected.version -ne 'release-v0.5.0') { throw 'Bridge did not find the full release.' }
|
|
||||||
$stage=Join-Path $root 'full-stage'
|
|
||||||
Expand-Release $FullArchive $stage ([version]'0.5.0')
|
|
||||||
$changed=[Collections.Generic.List[string]]::new()
|
|
||||||
Install-Files $stage $install (Join-Path $root 'full-backup') $changed
|
|
||||||
foreach ($name in @('KSwitcher.exe','KSwitcher.Config.exe','KSwitcher.Screenshot.exe')) {
|
|
||||||
$version=[Diagnostics.FileVersionInfo]::GetVersionInfo((Join-Path $install $name)).FileVersion
|
|
||||||
if ((Get-StableVersion $version) -ne [version]'0.5.0') { throw "Incorrect installed version: $name" }
|
|
||||||
}
|
|
||||||
if ([IO.File]::ReadAllText((Join-Path $install 'data/settings.json')) -ne 'personal settings' -or
|
|
||||||
[IO.File]::ReadAllText((Join-Path $install 'dictionaries/en.txt')) -ne 'personal dictionary') { throw 'User data changed during transition.' }
|
|
||||||
if (Select-Release $releases ([version]'0.5.0')) { throw 'Current version offers another update.' }
|
|
||||||
Undo-Files $install (Join-Path $root 'full-backup') $changed
|
|
||||||
if ((Get-StableVersion ([Diagnostics.FileVersionInfo]::GetVersionInfo((Join-Path $install 'KSwitcher.exe')).FileVersion)) -ne [version]'0.4.2' -or
|
|
||||||
(Test-Path -LiteralPath (Join-Path $install 'KSwitcher.Screenshot.exe'))) { throw 'Full release rollback did not restore the bridge.' }
|
|
||||||
'Passed real-package transition: v0.4.1 updater -> v0.4.2 bridge -> release-v0.5.0; user data preserved; rollback verified.'
|
|
||||||
} finally {
|
|
||||||
if ([IO.Path]::GetDirectoryName($root) -eq [IO.Path]::GetTempPath().TrimEnd('\') -and (Split-Path -Leaf $root) -like 'KSwitcher-transition-*') {
|
|
||||||
Remove-Item -LiteralPath $root -Recurse -Force
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -40,9 +40,3 @@ for index in range(struct.unpack_from('<H', cursor, 4)[0]):
|
|||||||
# Drawing point is the tip of the pencil, not the center of its image.
|
# Drawing point is the tip of the pencil, not the center of its image.
|
||||||
struct.pack_into('<HH', cursor, entry+4, round(size*4/28), round(size*24/28))
|
struct.pack_into('<HH', cursor, entry+4, round(size*4/28), round(size*24/28))
|
||||||
(root / 'pencil.cur').write_bytes(cursor)
|
(root / 'pencil.cur').write_bytes(cursor)
|
||||||
|
|
||||||
# Original confirmation artwork, with native-resolution variants for display scaling.
|
|
||||||
question = root.parent / 'question.svg'
|
|
||||||
question_png = resvg_py.svg_to_bytes(svg_string=question.read_text(encoding='utf-8'), width=256, height=256)
|
|
||||||
Image.open(BytesIO(question_png)).convert('RGBA').save(
|
|
||||||
question.with_suffix('.ico'), sizes=[(s,s) for s in (16,20,24,32,40,48,64,96,128,256)])
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
$root = Split-Path $PSScriptRoot
|
$root = Split-Path $PSScriptRoot
|
||||||
try {
|
try {
|
||||||
if (Get-Process 'KSwitcher.Screenshot' -ErrorAction SilentlyContinue | Where-Object { $_.Path -eq (Join-Path $root 'build\KSwitcher.Screenshot.exe') }) { throw 'Close screenshot editors before updating the development build.' }
|
|
||||||
$exe = Join-Path $root 'build\KSwitcher.exe'
|
$exe = Join-Path $root 'build\KSwitcher.exe'
|
||||||
$running = @(Get-Process KSwitcher -ErrorAction SilentlyContinue | Where-Object { $_.Path -eq $exe })
|
$running = @(Get-Process KSwitcher -ErrorAction SilentlyContinue | Where-Object { $_.Path -eq $exe })
|
||||||
$stop = Start-Process -FilePath $exe -ArgumentList '--exit' -WindowStyle Hidden -PassThru
|
$stop = Start-Process -FilePath $exe -ArgumentList '--exit' -WindowStyle Hidden -PassThru
|
||||||
@@ -11,7 +10,6 @@ try {
|
|||||||
foreach ($process in $config) { if (-not $process.WaitForExit(10000)) { throw 'Close the configurator before updating.' } }
|
foreach ($process in $config) { if (-not $process.WaitForExit(10000)) { throw 'Close the configurator before updating.' } }
|
||||||
Copy-Item -LiteralPath (Join-Path $root 'build\staged\KSwitcher.Config.exe') -Destination (Join-Path $root 'build\KSwitcher.Config.exe') -Force
|
Copy-Item -LiteralPath (Join-Path $root 'build\staged\KSwitcher.Config.exe') -Destination (Join-Path $root 'build\KSwitcher.Config.exe') -Force
|
||||||
Copy-Item -LiteralPath (Join-Path $root 'build\staged\KSwitcher.exe') -Destination $exe -Force
|
Copy-Item -LiteralPath (Join-Path $root 'build\staged\KSwitcher.exe') -Destination $exe -Force
|
||||||
Copy-Item -LiteralPath (Join-Path $root 'build\staged\KSwitcher.Screenshot.exe') -Destination (Join-Path $root 'build\KSwitcher.Screenshot.exe') -Force
|
|
||||||
$eventName = 'Local\KSwitcher.Update.Started.' + [guid]::NewGuid().ToString('N')
|
$eventName = 'Local\KSwitcher.Update.Started.' + [guid]::NewGuid().ToString('N')
|
||||||
$started = [System.Threading.EventWaitHandle]::new($false, [System.Threading.EventResetMode]::ManualReset, $eventName)
|
$started = [System.Threading.EventWaitHandle]::new($false, [System.Threading.EventResetMode]::ManualReset, $eventName)
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user