Compare commits
1
Commits
release-v0.6.57
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0387904a0 |
No files matched your search
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
cmake_minimum_required(VERSION 3.24)
|
cmake_minimum_required(VERSION 3.24)
|
||||||
project(KSwitcher VERSION 0.6.57 LANGUAGES CXX RC)
|
project(KSwitcher VERSION 0.6.58 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()
|
||||||
|
|||||||
+6
-2
@@ -1,4 +1,4 @@
|
|||||||
# LAN mouse, keyboard and clipboard — v0.6.57 (experimental)
|
# LAN mouse, keyboard and clipboard — v0.6.58 (experimental)
|
||||||
|
|
||||||
KSwitcher discovers other enabled KSwitcher instances, pairs them with the same
|
KSwitcher discovers other enabled KSwitcher instances, pairs them with the same
|
||||||
code on both computers, and shares mouse and keyboard through a screen edge.
|
code on both computers, and shares mouse and keyboard through a screen edge.
|
||||||
@@ -10,7 +10,7 @@ is not implemented by clipboard synchronization.
|
|||||||
|
|
||||||
## Set up multiple computers
|
## Set up multiple computers
|
||||||
|
|
||||||
1. Install the v0.6.57 or newer on every participating PC. Input protocol v2 requires all participants to update; earlier one-pair builds cannot connect. Preserve local data; do not copy pairing identities between PCs.
|
1. Install the v0.6.58 or newer on every participating PC. Input protocol v2 requires all participants to update; earlier one-pair builds cannot connect. Preserve local data; do not copy pairing identities between PCs.
|
||||||
2. Open Settings → Tools → KSwitcher computers and enable discovery on both PCs.
|
2. Open Settings → Tools → KSwitcher computers and enable discovery on both PCs.
|
||||||
Only running LAN-enabled KSwitcher instances appear, not generic network devices.
|
Only running LAN-enabled KSwitcher instances appear, not generic network devices.
|
||||||
3. Match the computer name and instance-ID prefix displayed on the other PC.
|
3. Match the computer name and instance-ID prefix displayed on the other PC.
|
||||||
@@ -116,3 +116,7 @@ Three physical computers still need an end-to-end trial for their adapters,
|
|||||||
firewalls, displays and application privilege levels. The automated TCP tests
|
firewalls, displays and application privilege levels. The automated TCP tests
|
||||||
use local sockets, and input-state tests use simulated desktop environments;
|
use local sockets, and input-state tests use simulated desktop environments;
|
||||||
they do not claim a completed three-machine hardware test.
|
they do not claim a completed three-machine hardware test.
|
||||||
|
|
||||||
|
## v0.6.58 connection fixes
|
||||||
|
|
||||||
|
Saved trust keeps TCP listeners available even when discovery announcements are missed. Working authenticated input sessions no longer depend on discovery presence. Pair saved is distinct from input connected; the list refreshes every second. A changed instance ID requires a new pairing. Keep each PC's own data folder during upgrades. Connection state changes and listener Windows errors are written to the diagnostic log.
|
||||||
@@ -10,14 +10,18 @@ The original [Typlo](https://github.com/bitcodepro/Typlo) project and its data r
|
|||||||
|
|
||||||
[Donate via Monobank](https://send.monobank.ua/jar/88h2TddKfS) · Thank you for supporting KSwitcher.
|
[Donate via Monobank](https://send.monobank.ua/jar/88h2TddKfS) · Thank you for supporting KSwitcher.
|
||||||
|
|
||||||
## KSwitcher computers — v0.6.57 (experimental)
|
## KSwitcher computers — v0.6.58 (experimental)
|
||||||
|
|
||||||
|
TCP input listeners and authenticated connections survive missed discovery announcements.
|
||||||
|
The list distinguishes saved pairing from connected input and refreshes every second.
|
||||||
|
Keep each PC’s own data folder during updates; a different instance ID requires new pairing.
|
||||||
|
|
||||||
Arrange multiple paired PCs on a monitor grid. Mouse and keyboard follow shared
|
Arrange multiple paired PCs on a monitor grid. Mouse and keyboard follow shared
|
||||||
edges, including A → B → C and back. New text and images go to every online paired
|
edges, including A → B → C and back. New text and images go to every online paired
|
||||||
PC's history and active clipboard. The history Computers menu filters by multiple
|
PC's history and active clipboard. The history Computers menu filters by multiple
|
||||||
sources using checkboxes. See [LAN-PREVIEW.md](LAN-PREVIEW.md).
|
sources using checkboxes. See [LAN-PREVIEW.md](LAN-PREVIEW.md).
|
||||||
|
|
||||||
**Update every participating PC to v0.6.57:** input protocol v2 is incompatible
|
**Update every participating PC to v0.6.58:** input protocol v2 is incompatible
|
||||||
with older one-pair builds. Keep each PC's own data directory. Pair every input
|
with older one-pair builds. Keep each PC's own data directory. Pair every input
|
||||||
source with all destinations, arrange the same physical layout on each PC and
|
source with all destinations, arrange the same physical layout on each PC and
|
||||||
enable input sharing. Empty cells and offline neighbors block passage.
|
enable input sharing. Empty cells and offline neighbors block passage.
|
||||||
@@ -25,7 +29,7 @@ enable input sharing. Empty cells and offline neighbors block passage.
|
|||||||
**Українською.** Розставте кілька спарованих ПК по сітці: миша й клавіатура
|
**Українською.** Розставте кілька спарованих ПК по сітці: миша й клавіатура
|
||||||
переходять через спільний край, зокрема A → B → C і назад. Нові текст та зображення
|
переходять через спільний край, зокрема A → B → C і назад. Нові текст та зображення
|
||||||
передаються всім доступним спарованим ПК в історію й активний буфер. Фільтр
|
передаються всім доступним спарованим ПК в історію й активний буфер. Фільтр
|
||||||
Computers в історії має чекбокси. Оновіть усі ПК до v0.6.57, залишивши кожному
|
Computers в історії має чекбокси. Оновіть усі ПК до v0.6.58, залишивши кожному
|
||||||
власну папку data. Спаруйте джерело вводу з усіма потрібними ПК й задайте
|
власну папку data. Спаруйте джерело вводу з усіма потрібними ПК й задайте
|
||||||
однакове фізичне розташування на кожному. Передача файлів не реалізована.
|
однакове фізичне розташування на кожному. Передача файлів не реалізована.
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
+6
-9
@@ -1,19 +1,16 @@
|
|||||||
# Latest Windows release
|
# Latest Windows release
|
||||||
|
|
||||||
## v0.6.57 — 2026-11-06
|
## v0.6.58 — 2026-11-06
|
||||||
|
|
||||||
## Changes
|
## Changes
|
||||||
- Settings Save merges independent edits with newer resident settings. Same-field conflicts offer a choice; declining keeps the window and draft open.
|
- Keep trusted TCP input listeners and connections alive across missing discovery announcements. Retain the last known address for reconnection; authenticate incoming trusted peers even before discovery sees their address.
|
||||||
- Multiple paired PCs on a draggable monitor grid; mouse and keyboard follow neighboring edges, including A → B → C and back. Connections reconnect independently.
|
- Show saved pairing separately from actual input connectivity: connected, disabled here, offline or not connected. Background identity verification does not flash the list.
|
||||||
- New text, formatted text and screenshots synchronize to all online paired PCs: history and active clipboard. Newer local copies are preserved during incoming transfers.
|
- Log TCP listener failures with the Windows error and peer connection state changes for troubleshooting.
|
||||||
- Clipboard history has a persistent Computers checkbox filter and source labels. Lossless PNG transport avoids duplicate image payloads.
|
|
||||||
- LAN modifier chords, DPI-scaled motion, cursor parking/return, automatic conversion ownership and queued wheel reversal fixes. Local shortcut latches reset on ownership changes.
|
|
||||||
- Stable paired status, pairing/input button states, persistent selection and a fixed-size LAN window with scrolling where needed.
|
|
||||||
|
|
||||||
## Validation
|
## Validation
|
||||||
Passed: 5 settings-save merge checks, 203 LAN input/three-peer TCP, 289 clipboard transfer, 43 pairing, 828 settings/chat UI, 570 input, 341 native and 126 sync checks; dictionary equivalence for 140,939 words; wheel, StayHere, Utilities, screenshot UI/save/registration, 54 updater checks and updater progress. Three physical PCs have not been verified together. LAN remains experimental; follow LAN-PREVIEW.md.
|
Passed: 205 LAN input checks (including missed discovery and authentication before discovery), 289 clipboard transfer, 43 pairing, 829 settings/chat UI, 570 input, 341 native, 126 sync and 5 settings-save merge checks; dictionary equivalence for 140,939 words; wheel, StayHere, Utilities, screenshot UI/save/registration, 54 updater checks and updater progress. LAN remains experimental; three physical PCs have not been verified together.
|
||||||
|
|
||||||
**Update ALL LAN participants to v0.6.57. Input protocol v2 cannot connect to older one-pair builds.** Pair every physical input source with each destination. Configure the same physical arrangement on each PC; layouts are not automatically synchronized. File transfer, offline clipboard backfill, cross-PC dragging and secure desktops remain unsupported.
|
Update both PCs to receive these fixes. Preserve each PC's own data directory and pairing identity. Input protocol v2 remains compatible with v0.6.57. A changed instance ID requires pairing again. The computer list refreshes automatically once per second while open.
|
||||||
|
|
||||||
## Upgrade and compatibility
|
## Upgrade and compatibility
|
||||||
Choose **Update** in the tray menu. Legacy v0.3.0–v0.4.1 clients first receive the v0.4.2 compatibility bridge, then the current full release. v0.2.3 and earlier require manual installation.
|
Choose **Update** in the tray menu. Legacy v0.3.0–v0.4.1 clients first receive the v0.4.2 compatibility bridge, then the current full release. v0.2.3 and earlier require manual installation.
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
f3ec4f7a62b4479929d94d6d6f7f68edb8b4a47b84edac7258afb64dbe07aef2 KSwitcher-win-x64.zip
|
16d8138c63442931bf947707b28820d9b89943169ac9f25db3cb50d23c0ff319 KSwitcher-win-x64.zip
|
||||||
+35
-35
@@ -1,86 +1,86 @@
|
|||||||
{
|
{
|
||||||
"version": "0.6.57",
|
|
||||||
"schema": 1,
|
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"size": 2106368,
|
"sha256": "a7be2f749082bb6417b4cf7e8c8ea221ef4f7ba63dabc3cbbe8694ab1a3d942c",
|
||||||
"path": "KSwitcher.exe",
|
"path": "KSwitcher.exe",
|
||||||
"sha256": "3b7315e1545f70739fd1a7c099ddeab28362b323521d93879fa43fdd76133f8e"
|
"size": 2108416
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": 2003456,
|
"sha256": "c8a88364c9967ce5fd150519d632b14460e225a0cfae51a8de50f39782d8010c",
|
||||||
"path": "KSwitcher.Config.exe",
|
"path": "KSwitcher.Config.exe",
|
||||||
"sha256": "a819ec42030990725326730396680388cbcb870dd00c0a97856cb1615f9db27b"
|
"size": 2005504
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": 3151360,
|
"sha256": "68eed5abefed0208ef7bdd431094c7357c53bd3ca03dfd91aaeb7dbb4f291e06",
|
||||||
"path": "KSwitcher.Screenshot.exe",
|
"path": "KSwitcher.Screenshot.exe",
|
||||||
"sha256": "8473697245c2c961496135eaaff6de9e9a19e917f2fd7299a16068210223d396"
|
"size": 3153408
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": 4401,
|
"sha256": "d3c81140cce01c26bf6cbf52aa40e0825e1fa35b9a91d1387170a65d2597082c",
|
||||||
"path": "LICENSE",
|
"path": "LICENSE",
|
||||||
"sha256": "d3c81140cce01c26bf6cbf52aa40e0825e1fa35b9a91d1387170a65d2597082c"
|
"size": 4401
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": 1096,
|
"sha256": "61e08da79c44061654015ce6b0c4061c23ee1dd18237fbf09e9bfda3c0ea2831",
|
||||||
"path": "json-LICENSE.txt",
|
"path": "json-LICENSE.txt",
|
||||||
"sha256": "61e08da79c44061654015ce6b0c4061c23ee1dd18237fbf09e9bfda3c0ea2831"
|
"size": 1096
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": 79896,
|
"sha256": "87f396a6bf988725b14bf5b6eb7c2b60ce33ff51828290ce40e136c66942cc09",
|
||||||
"path": "README.md",
|
"path": "README.md",
|
||||||
"sha256": "8b4b1e905a2f0a39d532e61136ec7eca473492a39a538eb073ed829307adefae"
|
"size": 80168
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": 7921,
|
"sha256": "72e131bdf46d32a8a17fb352292bc8983c0e5a7ed84812218b11cf6b69c9afc4",
|
||||||
"path": "LAN-PREVIEW.md",
|
"path": "LAN-PREVIEW.md",
|
||||||
"sha256": "2467b86bbad7d7dce1e0c65c8c0faf2320e4804e90a85784a058afa357127b6b"
|
"size": 8376
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": 732485,
|
"sha256": "d0c022b27b9fbc2ff7b6b8bec723cc1adc4eef517202f9ff77eed5d8bea3acb9",
|
||||||
"path": "dictionaries/en.txt",
|
"path": "dictionaries/en.txt",
|
||||||
"sha256": "d0c022b27b9fbc2ff7b6b8bec723cc1adc4eef517202f9ff77eed5d8bea3acb9"
|
"size": 732485
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": 20566,
|
"sha256": "0bdfb0201d1803634f268cdff0993b8349d2c70e956a06dbb667c8dd65254ec5",
|
||||||
"path": "dictionaries/LICENSE-CC-BY-SA-4.0.txt",
|
"path": "dictionaries/LICENSE-CC-BY-SA-4.0.txt",
|
||||||
"sha256": "0bdfb0201d1803634f268cdff0993b8349d2c70e956a06dbb667c8dd65254ec5"
|
"size": 20566
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": 2265,
|
"sha256": "3017bf44d3be9d9dfab21bd6778aa52b7947e61acf9ce2bc86b41d6e949ac311",
|
||||||
"path": "dictionaries/README.md",
|
"path": "dictionaries/README.md",
|
||||||
"sha256": "3017bf44d3be9d9dfab21bd6778aa52b7947e61acf9ce2bc86b41d6e949ac311"
|
"size": 2265
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": 1109134,
|
"sha256": "80e6b6a27815973535f7facb636c597eb845dfee45cfabfc4e9b3341b9332849",
|
||||||
"path": "dictionaries/ru.txt",
|
"path": "dictionaries/ru.txt",
|
||||||
"sha256": "80e6b6a27815973535f7facb636c597eb845dfee45cfabfc4e9b3341b9332849"
|
"size": 1109134
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": 1280,
|
"sha256": "98e5b3fe7304e37705c9e5ff48b0b399ccb16e806736d21f09921b99e5f2a408",
|
||||||
"path": "dictionaries/sources.json",
|
"path": "dictionaries/sources.json",
|
||||||
"sha256": "98e5b3fe7304e37705c9e5ff48b0b399ccb16e806736d21f09921b99e5f2a408"
|
"size": 1280
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": 940799,
|
"sha256": "d30e990d6ec2983c04f3644a3c61c6c668e8c542323f5931916fdd55b916eebf",
|
||||||
"path": "dictionaries/uk.txt",
|
"path": "dictionaries/uk.txt",
|
||||||
"sha256": "d30e990d6ec2983c04f3644a3c61c6c668e8c542323f5931916fdd55b916eebf"
|
"size": 940799
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": 993,
|
"sha256": "a34b5161a8315af037e1ec71d816bfc688f7c364141d4ee2347cf0515ef0c82a",
|
||||||
"path": "tools/README.txt",
|
"path": "tools/README.txt",
|
||||||
"sha256": "a34b5161a8315af037e1ec71d816bfc688f7c364141d4ee2347cf0515ef0c82a"
|
"size": 993
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": 134656,
|
"sha256": "6ef5cf64708d62b7fc63c3c793cc99188a513433ff4774cae01dc014f6664376",
|
||||||
"path": "tools/StayHere.exe",
|
"path": "tools/StayHere.exe",
|
||||||
"sha256": "6ef5cf64708d62b7fc63c3c793cc99188a513433ff4774cae01dc014f6664376"
|
"size": 134656
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": 2796,
|
"sha256": "ba214e2aca7405e698b3ab21ceb1f41ce088edd996b3e30e4a9d58ff496f9413",
|
||||||
"path": "tools/StayHere-README.md",
|
"path": "tools/StayHere-README.md",
|
||||||
"sha256": "ba214e2aca7405e698b3ab21ceb1f41ce088edd996b3e30e4a9d58ff496f9413"
|
"size": 2796
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"schema": 1,
|
||||||
|
"version": "0.6.58"
|
||||||
}
|
}
|
||||||
+3
-3
@@ -9,8 +9,8 @@ IDC_SCREENSHOT_PENCIL CURSOR "icons/lucide/pencil.cur"
|
|||||||
1 RT_MANIFEST "app.manifest"
|
1 RT_MANIFEST "app.manifest"
|
||||||
201 RCDATA "update.ps1"
|
201 RCDATA "update.ps1"
|
||||||
1 VERSIONINFO
|
1 VERSIONINFO
|
||||||
FILEVERSION 0,6,57,0
|
FILEVERSION 0,6,58,0
|
||||||
PRODUCTVERSION 0,6,57,0
|
PRODUCTVERSION 0,6,58,0
|
||||||
FILEOS VOS_NT_WINDOWS32
|
FILEOS VOS_NT_WINDOWS32
|
||||||
FILETYPE VFT_APP
|
FILETYPE VFT_APP
|
||||||
BEGIN
|
BEGIN
|
||||||
@@ -20,7 +20,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.6.57"
|
VALUE "FileVersion", "0.6.58"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
|||||||
+2
-2
@@ -242,8 +242,8 @@ struct LanService::Impl
|
|||||||
if(peers.contains(target))send(socket,peers.at(target).address,packet);
|
if(peers.contains(target))send(socket,peers.at(target).address,packet);
|
||||||
});
|
});
|
||||||
clipboardActive=clipboard->active();
|
clipboardActive=clipboard->active();
|
||||||
if(shareInput)for(auto &[peer,entry]:trusted.items())if(peers.contains(peer))
|
if(shareInput)for(auto &[peer,entry]:trusted.items())
|
||||||
endpoints.push_back({peer,entry.at("key"),peers.at(peer).address.sin_addr.s_addr});
|
endpoints.push_back({peer,entry.at("key"),peers.contains(peer)?peers.at(peer).address.sin_addr.s_addr:0});
|
||||||
if(now>=nextHello)
|
if(now>=nextHello)
|
||||||
{
|
{
|
||||||
epoch=lan::randomHex(16); // Fresh challenge prevents replayed presence from staying online.
|
epoch=lan::randomHex(16); // Fresh challenge prevents replayed presence from staying online.
|
||||||
|
|||||||
+16
-5
@@ -207,7 +207,7 @@ std::string InputChannel::status()const{return impl->state;}
|
|||||||
void InputChannel::disconnect(){impl->reset(GetTickCount64(),"Input disconnected; local control restored.");}
|
void InputChannel::disconnect(){impl->reset(GetTickCount64(),"Input disconnected; local control restored.");}
|
||||||
struct InputHub::Impl
|
struct InputHub::Impl
|
||||||
{
|
{
|
||||||
struct Link { InputEndpoint endpoint;std::unique_ptr<InputChannel> channel;uint64_t generation{},epoch{}; };
|
struct Link { InputEndpoint endpoint;std::unique_ptr<InputChannel> channel;uint64_t generation{},epoch{};std::string lastState; };
|
||||||
struct Pending {SOCKET socket;uint32_t address;uint64_t since;};
|
struct Pending {SOCKET socket;uint32_t address;uint64_t since;};
|
||||||
std::string local;uint16_t port;SOCKET listener=INVALID_SOCKET;
|
std::string local;uint16_t port;SOCKET listener=INVALID_SOCKET;
|
||||||
std::map<std::string,Link> links;std::vector<Pending> pending;uint64_t epoch{},retry{};
|
std::map<std::string,Link> links;std::vector<Pending> pending;uint64_t epoch{},retry{};
|
||||||
@@ -217,19 +217,29 @@ struct InputHub::Impl
|
|||||||
void poll(const std::vector<InputEndpoint> &endpoints,const std::vector<RoutedInput> &out,std::vector<RoutedInput> &in,uint64_t now)
|
void poll(const std::vector<InputEndpoint> &endpoints,const std::vector<RoutedInput> &out,std::vector<RoutedInput> &in,uint64_t now)
|
||||||
{
|
{
|
||||||
for(auto it=links.begin();it!=links.end();){
|
for(auto it=links.begin();it!=links.end();){
|
||||||
auto match=std::find_if(endpoints.begin(),endpoints.end(),[&](auto &e){return e.id==it->first&&e.key==it->second.endpoint.key&&e.port==it->second.endpoint.port&&e.address==it->second.endpoint.address;});
|
auto match=std::find_if(endpoints.begin(),endpoints.end(),[&](auto &e){return e.id==it->first&&e.key==it->second.endpoint.key&&e.port==it->second.endpoint.port;});
|
||||||
if(match==endpoints.end())it=links.erase(it);else ++it;
|
if(match==endpoints.end())it=links.erase(it);else ++it;
|
||||||
}
|
}
|
||||||
for(auto &e:endpoints)if(e.id!=local&&!links.contains(e.id)){
|
for(auto &e:endpoints)if(e.id!=local&&!links.contains(e.id)){
|
||||||
Link link;link.endpoint=e;link.channel=std::make_unique<InputChannel>(local,e.id,e.key,0,e.port,true);link.epoch=++epoch;links.emplace(e.id,std::move(link));
|
Link link;link.endpoint=e;link.channel=std::make_unique<InputChannel>(local,e.id,e.key,0,e.port,true);link.epoch=++epoch;links.emplace(e.id,std::move(link));
|
||||||
}
|
}
|
||||||
|
// Discovery is a hint, not the lifetime of authenticated input sessions.
|
||||||
|
// Keep the last usable address across missed announcements. A new address
|
||||||
|
// is used only once a channel is down, never to tear down working input.
|
||||||
|
for(auto &e:endpoints){if(e.id==local)continue;auto &link=links.at(e.id);
|
||||||
|
if(e.address&&!link.channel->ready()&&e.address!=link.endpoint.address){
|
||||||
|
link.channel->disconnect();link.endpoint.address=e.address;
|
||||||
|
}
|
||||||
|
}
|
||||||
bool listenNeeded=std::any_of(endpoints.begin(),endpoints.end(),[&](auto &e){return e.id<local;});
|
bool listenNeeded=std::any_of(endpoints.begin(),endpoints.end(),[&](auto &e){return e.id<local;});
|
||||||
if(!listenNeeded){closeSocket(listener);for(auto &p:pending)closeSocket(p.socket);pending.clear();}
|
if(!listenNeeded){closeSocket(listener);for(auto &p:pending)closeSocket(p.socket);pending.clear();}
|
||||||
if(listenNeeded&&listener==INVALID_SOCKET&&now>=retry){
|
if(listenNeeded&&listener==INVALID_SOCKET&&now>=retry){
|
||||||
listener=::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);BOOL yes=TRUE;sockaddr_in a{};a.sin_family=AF_INET;a.sin_port=htons(port);
|
listener=::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);BOOL yes=TRUE;sockaddr_in a{};a.sin_family=AF_INET;a.sin_port=htons(port);
|
||||||
if(listener==INVALID_SOCKET||setsockopt(listener,SOL_SOCKET,SO_EXCLUSIVEADDRUSE,(char*)&yes,sizeof(yes))||!nonblocking(listener)||bind(listener,(sockaddr*)&a,sizeof(a))||listen(listener,SOMAXCONN)){
|
if(listener==INVALID_SOCKET||setsockopt(listener,SOL_SOCKET,SO_EXCLUSIVEADDRUSE,(char*)&yes,sizeof(yes))||!nonblocking(listener)||bind(listener,(sockaddr*)&a,sizeof(a))||listen(listener,SOMAXCONN)){
|
||||||
closeSocket(listener);retry=now+1500;error="TCP input port is unavailable.";
|
auto detail="TCP input port "+std::to_string(port)+" is unavailable (Windows error "+std::to_string(WSAGetLastError())+").";
|
||||||
}else error.clear();
|
if(error!=detail)log(L"LAN input: "+wide(detail));
|
||||||
|
closeSocket(listener);retry=now+1500;error=std::move(detail);
|
||||||
|
}else {error.clear();log(L"LAN input listener ready on TCP "+std::to_wstring(port));}
|
||||||
}
|
}
|
||||||
if(listener!=INVALID_SOCKET)for(int n=0;n<16&&pending.size()<64;++n){
|
if(listener!=INVALID_SOCKET)for(int n=0;n<16&&pending.size()<64;++n){
|
||||||
sockaddr_in a{};int size=sizeof(a);SOCKET sock=accept(listener,(sockaddr*)&a,&size);if(sock==INVALID_SOCKET)break;
|
sockaddr_in a{};int size=sizeof(a);SOCKET sock=accept(listener,(sockaddr*)&a,&size);if(sock==INVALID_SOCKET)break;
|
||||||
@@ -243,7 +253,7 @@ struct InputHub::Impl
|
|||||||
else if(count>=int(length+4)){
|
else if(count>=int(length+4)){
|
||||||
done=true;
|
done=true;
|
||||||
try{auto hello=Json::parse(bytes+4,bytes+4+length);auto id=hello.value("id","");auto link=links.find(id);
|
try{auto hello=Json::parse(bytes+4,bytes+4+length);auto id=hello.value("id","");auto link=links.find(id);
|
||||||
if(id<local&&hello.value("to","")==local&&hello.value("version",0)==2&&link!=links.end()&&link->second.endpoint.address==it->address&&link->second.channel->adopt(uintptr_t(it->socket),now))it->socket=INVALID_SOCKET;
|
if(id<local&&hello.value("to","")==local&&hello.value("version",0)==2&&link!=links.end()&&link->second.channel->adopt(uintptr_t(it->socket),now))it->socket=INVALID_SOCKET;
|
||||||
}catch(const std::exception &){}
|
}catch(const std::exception &){}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -252,6 +262,7 @@ struct InputHub::Impl
|
|||||||
for(auto &[id,link]:links){
|
for(auto &[id,link]:links){
|
||||||
std::vector<InputFrame> frames,received;for(auto &f:out)if(f.peer==id&&(!f.epoch||f.epoch==link.epoch))enqueueInput(frames,f.frame);
|
std::vector<InputFrame> frames,received;for(auto &f:out)if(f.peer==id&&(!f.epoch||f.epoch==link.epoch))enqueueInput(frames,f.frame);
|
||||||
link.channel->poll(link.endpoint.address,frames,received,now);
|
link.channel->poll(link.endpoint.address,frames,received,now);
|
||||||
|
auto state=link.channel->status();if(state!=link.lastState){link.lastState=state;log(L"LAN input peer "+wide(id.substr(0,8))+L": "+wide(state));}
|
||||||
if(link.generation!=link.channel->generation()){link.generation=link.channel->generation();link.epoch=++epoch;}
|
if(link.generation!=link.channel->generation()){link.generation=link.channel->generation();link.epoch=++epoch;}
|
||||||
for(auto &f:received)in.push_back({id,f,link.epoch});
|
for(auto &f:received)in.push_back({id,f,link.epoch});
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-1
@@ -36,7 +36,13 @@ void refreshLan(HWND w,LanView &v)
|
|||||||
for(const auto &peer:next["peers"])
|
for(const auto &peer:next["peers"])
|
||||||
{
|
{
|
||||||
auto id=peer.at("id").get<std::string>();ids.push_back(id);
|
auto id=peer.at("id").get<std::string>();ids.push_back(id);
|
||||||
auto state=peer.at("paired").get<bool>() ? L"Paired" : L"Not paired";
|
std::wstring state=L"Not paired";
|
||||||
|
if(peer.at("paired").get<bool>()){
|
||||||
|
bool connected=next.value("inputReady",nlohmann::json::object()).contains(id);
|
||||||
|
state=connected?L"Pair saved — input connected":
|
||||||
|
!next.value("shareInput",false)?L"Pair saved — input disabled here":
|
||||||
|
!peer.value("online",false)?L"Pair saved — computer offline":L"Pair saved — input not connected";
|
||||||
|
}
|
||||||
rows.push_back(wide(peer.at("name").get<std::string>())+L" ["+wide(id.substr(0,8))+L"] — "+state);
|
rows.push_back(wide(peer.at("name").get<std::string>())+L" ["+wide(id.substr(0,8))+L"] — "+state);
|
||||||
}
|
}
|
||||||
// Presence/challenge refreshes must not repaint or reset the pairing list.
|
// Presence/challenge refreshes must not repaint or reset the pairing list.
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
namespace ks
|
namespace ks
|
||||||
{
|
{
|
||||||
inline constexpr wchar_t AppVersion[] = L"0.6.57";
|
inline constexpr wchar_t AppVersion[] = L"0.6.58";
|
||||||
inline constexpr UINT UpdateMessage = WM_APP + 7;
|
inline constexpr UINT UpdateMessage = WM_APP + 7;
|
||||||
inline constexpr UINT UpdateCheckIntervalMs = 6 * 60 * 60 * 1000;
|
inline constexpr UINT UpdateCheckIntervalMs = 6 * 60 * 60 * 1000;
|
||||||
inline constexpr UINT UpdateNotificationRetryMs = 5 * 60 * 1000;
|
inline constexpr UINT UpdateNotificationRetryMs = 5 * 60 * 1000;
|
||||||
|
|||||||
+22
-11
@@ -7,6 +7,15 @@
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
HWND findTestWindow(LPCWSTR className, LPCWSTR title)
|
||||||
|
{
|
||||||
|
HWND window=nullptr;
|
||||||
|
while((window=FindWindowExW(nullptr,window,className,title))) {
|
||||||
|
DWORD process{}; GetWindowThreadProcessId(window,&process);
|
||||||
|
if(process==GetCurrentProcessId()) return window;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
int passed{};
|
int passed{};
|
||||||
void require(bool ok, const char *message)
|
void require(bool ok, const char *message)
|
||||||
{
|
{
|
||||||
@@ -141,31 +150,31 @@ int main()
|
|||||||
SetDlgItemTextW(parent,240,originalLimit);
|
SetDlgItemTextW(parent,240,originalLimit);
|
||||||
require(!saveEnabled(),"restoring valid original text clears dirty state");
|
require(!saveEnabled(),"restoring valid original text clears dirty state");
|
||||||
SendMessageW(parent,WM_COMMAND,257,0);
|
SendMessageW(parent,WM_COMMAND,257,0);
|
||||||
HWND ocr=FindWindowW(L"KSwitcher.OcrOptions",nullptr);
|
HWND ocr=findTestWindow(L"KSwitcher.OcrOptions",nullptr);
|
||||||
require(ocr!=nullptr,"OCR preferences open");
|
require(ocr!=nullptr,"OCR preferences open");
|
||||||
wchar_t languages[128]{}; GetDlgItemTextW(ocr,503,languages,128);
|
wchar_t languages[128]{}; GetDlgItemTextW(ocr,503,languages,128);
|
||||||
SetDlgItemTextW(ocr,503,L"eng");
|
SetDlgItemTextW(ocr,503,L"eng");
|
||||||
SendMessageW(ocr,WM_COMMAND,IDCANCEL,0);
|
SendMessageW(ocr,WM_COMMAND,IDCANCEL,0);
|
||||||
require(!saveEnabled(),"cancelled child changes leave Settings clean");
|
require(!saveEnabled(),"cancelled child changes leave Settings clean");
|
||||||
SendMessageW(parent,WM_COMMAND,257,0);
|
SendMessageW(parent,WM_COMMAND,257,0);
|
||||||
ocr=FindWindowW(L"KSwitcher.OcrOptions",nullptr);
|
ocr=findTestWindow(L"KSwitcher.OcrOptions",nullptr);
|
||||||
SetDlgItemTextW(ocr,503,std::wstring(languages)==L"eng"?L"ukr":L"eng");
|
SetDlgItemTextW(ocr,503,std::wstring(languages)==L"eng"?L"ukr":L"eng");
|
||||||
SendMessageW(ocr,WM_COMMAND,IDOK,0);
|
SendMessageW(ocr,WM_COMMAND,IDOK,0);
|
||||||
require(!IsWindow(ocr) && saveEnabled(),"accepted OCR edits enable parent Save");
|
require(!IsWindow(ocr) && saveEnabled(),"accepted OCR edits enable parent Save");
|
||||||
SendMessageW(parent,WM_COMMAND,257,0);
|
SendMessageW(parent,WM_COMMAND,257,0);
|
||||||
ocr=FindWindowW(L"KSwitcher.OcrOptions",nullptr);
|
ocr=findTestWindow(L"KSwitcher.OcrOptions",nullptr);
|
||||||
SetDlgItemTextW(ocr,503,languages);
|
SetDlgItemTextW(ocr,503,languages);
|
||||||
SendMessageW(ocr,WM_COMMAND,IDOK,0);
|
SendMessageW(ocr,WM_COMMAND,IDOK,0);
|
||||||
require(!saveEnabled(),"reverting OCR draft in child clears parent dirty state");
|
require(!saveEnabled(),"reverting OCR draft in child clears parent dirty state");
|
||||||
SendMessageW(parent,WM_COMMAND,255,0);
|
SendMessageW(parent,WM_COMMAND,255,0);
|
||||||
HWND automatic=FindWindowW(L"KSwitcher.AutoOptions",nullptr);
|
HWND automatic=findTestWindow(L"KSwitcher.AutoOptions",nullptr);
|
||||||
require(automatic!=nullptr,"automatic correction preferences open");
|
require(automatic!=nullptr,"automatic correction preferences open");
|
||||||
bool replace=IsDlgButtonChecked(automatic,414)==BST_CHECKED;
|
bool replace=IsDlgButtonChecked(automatic,414)==BST_CHECKED;
|
||||||
CheckDlgButton(automatic,414,replace?BST_UNCHECKED:BST_CHECKED);
|
CheckDlgButton(automatic,414,replace?BST_UNCHECKED:BST_CHECKED);
|
||||||
SendMessageW(automatic,WM_COMMAND,IDOK,0);
|
SendMessageW(automatic,WM_COMMAND,IDOK,0);
|
||||||
require(!IsWindow(automatic) && saveEnabled(),"accepted auto-correction edits enable Save");
|
require(!IsWindow(automatic) && saveEnabled(),"accepted auto-correction edits enable Save");
|
||||||
SendMessageW(parent,WM_COMMAND,255,0);
|
SendMessageW(parent,WM_COMMAND,255,0);
|
||||||
automatic=FindWindowW(L"KSwitcher.AutoOptions",nullptr);
|
automatic=findTestWindow(L"KSwitcher.AutoOptions",nullptr);
|
||||||
CheckDlgButton(automatic,414,replace?BST_CHECKED:BST_UNCHECKED);
|
CheckDlgButton(automatic,414,replace?BST_CHECKED:BST_UNCHECKED);
|
||||||
SendMessageW(automatic,WM_COMMAND,IDOK,0);
|
SendMessageW(automatic,WM_COMMAND,IDOK,0);
|
||||||
require(!saveEnabled(),"reverting auto-correction draft disables Save");
|
require(!saveEnabled(),"reverting auto-correction draft disables Save");
|
||||||
@@ -201,7 +210,7 @@ int main()
|
|||||||
SendMessageW(parent, WM_NOTIFY, 260, (LPARAM)¬ification);
|
SendMessageW(parent, WM_NOTIFY, 260, (LPARAM)¬ification);
|
||||||
require(!saveEnabled(),"changing tabs leaves Settings clean");
|
require(!saveEnabled(),"changing tabs leaves Settings clean");
|
||||||
SendMessageW(parent, WM_COMMAND, 259, 0);
|
SendMessageW(parent, WM_COMMAND, 259, 0);
|
||||||
HWND dialog = FindWindowW(L"KSwitcher.ChatLanguages", nullptr);
|
HWND dialog = findTestWindow(L"KSwitcher.ChatLanguages", nullptr);
|
||||||
require(dialog && !IsWindowEnabled(parent), "chat dialog opens with disabled parent");
|
require(dialog && !IsWindowEnabled(parent), "chat dialog opens with disabled parent");
|
||||||
int index = (int)SendDlgItemMessageW(dialog, 710, LB_FINDSTRINGEXACT, -1, (LPARAM)L"Telegram: ! Fixture chat");
|
int index = (int)SendDlgItemMessageW(dialog, 710, LB_FINDSTRINGEXACT, -1, (LPARAM)L"Telegram: ! Fixture chat");
|
||||||
require(index >= 0, "saved chat listed");
|
require(index >= 0, "saved chat listed");
|
||||||
@@ -220,7 +229,7 @@ int main()
|
|||||||
require(!IsWindow(dialog) && IsWindowEnabled(parent), "cancel closes dialog");
|
require(!IsWindow(dialog) && IsWindowEnabled(parent), "cancel closes dialog");
|
||||||
require(!saveEnabled(),"cancelled chat edits do not enable Save");
|
require(!saveEnabled(),"cancelled chat edits do not enable Save");
|
||||||
SendMessageW(parent, WM_COMMAND, 259, 0);
|
SendMessageW(parent, WM_COMMAND, 259, 0);
|
||||||
dialog = FindWindowW(L"KSwitcher.ChatLanguages", nullptr);
|
dialog = findTestWindow(L"KSwitcher.ChatLanguages", nullptr);
|
||||||
index = (int)SendDlgItemMessageW(dialog, 710, LB_FINDSTRINGEXACT, -1, (LPARAM)L"Telegram: ! Fixture chat");
|
index = (int)SendDlgItemMessageW(dialog, 710, LB_FINDSTRINGEXACT, -1, (LPARAM)L"Telegram: ! Fixture chat");
|
||||||
SendDlgItemMessageW(dialog, 710, LB_SETCURSEL, index, 0);
|
SendDlgItemMessageW(dialog, 710, LB_SETCURSEL, index, 0);
|
||||||
SendMessageW(dialog, WM_COMMAND, MAKEWPARAM(710, LBN_SELCHANGE), 0);
|
SendMessageW(dialog, WM_COMMAND, MAKEWPARAM(710, LBN_SELCHANGE), 0);
|
||||||
@@ -236,7 +245,7 @@ int main()
|
|||||||
parent = app.settingsWindow;
|
parent = app.settingsWindow;
|
||||||
require(!saveEnabled(),"reopened saved Settings is clean");
|
require(!saveEnabled(),"reopened saved Settings is clean");
|
||||||
SendMessageW(parent, WM_COMMAND, 259, 0);
|
SendMessageW(parent, WM_COMMAND, 259, 0);
|
||||||
dialog = FindWindowW(L"KSwitcher.ChatLanguages", nullptr);
|
dialog = findTestWindow(L"KSwitcher.ChatLanguages", nullptr);
|
||||||
index = (int)SendDlgItemMessageW(dialog, 710, LB_FINDSTRINGEXACT, -1, (LPARAM)L"Telegram: ! Fixture chat");
|
index = (int)SendDlgItemMessageW(dialog, 710, LB_FINDSTRINGEXACT, -1, (LPARAM)L"Telegram: ! Fixture chat");
|
||||||
SendDlgItemMessageW(dialog, 710, LB_SETCURSEL, index, 0);
|
SendDlgItemMessageW(dialog, 710, LB_SETCURSEL, index, 0);
|
||||||
SendMessageW(dialog, WM_COMMAND, MAKEWPARAM(710, LBN_SELCHANGE), 0);
|
SendMessageW(dialog, WM_COMMAND, MAKEWPARAM(710, LBN_SELCHANGE), 0);
|
||||||
@@ -286,7 +295,7 @@ int main()
|
|||||||
require(app.settings.snippets[0].apps==std::vector<std::wstring>{L"notepad",L"ms-teams"}, "cancel preserves existing scope");
|
require(app.settings.snippets[0].apps==std::vector<std::wstring>{L"notepad",L"ms-teams"}, "cancel preserves existing scope");
|
||||||
ks::showSettings(app);parent=app.settingsWindow;
|
ks::showSettings(app);parent=app.settingsWindow;
|
||||||
SendMessageW(parent,WM_COMMAND,262,0);
|
SendMessageW(parent,WM_COMMAND,262,0);
|
||||||
HWND lan=FindWindowW(L"KSwitcher.LAN",nullptr);
|
HWND lan=findTestWindow(L"KSwitcher.LAN",nullptr);
|
||||||
require(lan && !IsWindowEnabled(parent),"LAN pairing opens a native child window");
|
require(lan && !IsWindowEnabled(parent),"LAN pairing opens a native child window");
|
||||||
require(!IsWindowEnabled(GetDlgItem(lan,705)),"pairing disabled without discovered peer");
|
require(!IsWindowEnabled(GetDlgItem(lan,705)),"pairing disabled without discovered peer");
|
||||||
require(!IsWindowEnabled(GetDlgItem(lan,706)),"no cancellation without pending pairing");
|
require(!IsWindowEnabled(GetDlgItem(lan,706)),"no cancellation without pending pairing");
|
||||||
@@ -312,8 +321,10 @@ int main()
|
|||||||
auto text=fixture.dump();ks::atomicWrite(root/L"lan-pairing.bin",ks::protect(ks::Bytes(text.begin(),text.end())));
|
auto text=fixture.dump();ks::atomicWrite(root/L"lan-pairing.bin",ks::protect(ks::Bytes(text.begin(),text.end())));
|
||||||
ks::App pairedApp(GetModuleHandleW(nullptr),true,root);ks::showSettings(pairedApp);
|
ks::App pairedApp(GetModuleHandleW(nullptr),true,root);ks::showSettings(pairedApp);
|
||||||
SendMessageW(pairedApp.settingsWindow,WM_COMMAND,262,0);
|
SendMessageW(pairedApp.settingsWindow,WM_COMMAND,262,0);
|
||||||
HWND lan=FindWindowW(L"KSwitcher.LAN",nullptr);
|
HWND lan=findTestWindow(L"KSwitcher.LAN",nullptr);
|
||||||
require(SendDlgItemMessageW(lan,702,LB_GETCURSEL,0,0)==0,"single saved pair selected automatically when opening LAN settings");
|
require(SendDlgItemMessageW(lan,702,LB_GETCURSEL,0,0)==0,"single saved pair selected automatically when opening LAN settings");
|
||||||
|
wchar_t pairState[512]{};SendDlgItemMessageW(lan,702,LB_GETTEXT,0,(LPARAM)pairState);
|
||||||
|
require(std::wstring(pairState).find(L"Pair saved — input disabled here")!=std::wstring::npos,"saved pairing does not imply an active input connection");
|
||||||
require(!IsWindowEnabled(GetDlgItem(lan,705))&&!IsWindowEnabled(GetDlgItem(lan,703))&&!IsWindowEnabled(GetDlgItem(lan,704)),"saved pair disables pairing and code controls");
|
require(!IsWindowEnabled(GetDlgItem(lan,705))&&!IsWindowEnabled(GetDlgItem(lan,703))&&!IsWindowEnabled(GetDlgItem(lan,704)),"saved pair disables pairing and code controls");
|
||||||
SendDlgItemMessageW(lan,702,LB_SETCURSEL,-1,0);
|
SendDlgItemMessageW(lan,702,LB_SETCURSEL,-1,0);
|
||||||
SendMessageW(lan,WM_TIMER,1,0);
|
SendMessageW(lan,WM_TIMER,1,0);
|
||||||
@@ -346,7 +357,7 @@ int main()
|
|||||||
{"positions",{{b,J::array({-1,0})},{c,J::array({1,0})}}}};
|
{"positions",{{b,J::array({-1,0})},{c,J::array({1,0})}}}};
|
||||||
auto text=fixture.dump();ks::atomicWrite(root/L"lan-pairing.bin",ks::protect(ks::Bytes(text.begin(),text.end())));
|
auto text=fixture.dump();ks::atomicWrite(root/L"lan-pairing.bin",ks::protect(ks::Bytes(text.begin(),text.end())));
|
||||||
ks::App layoutApp(GetModuleHandleW(nullptr),true,root);ks::showSettings(layoutApp);
|
ks::App layoutApp(GetModuleHandleW(nullptr),true,root);ks::showSettings(layoutApp);
|
||||||
SendMessageW(layoutApp.settingsWindow,WM_COMMAND,262,0);auto lan=FindWindowW(L"KSwitcher.LAN",nullptr);auto grid=GetDlgItem(lan,721);
|
SendMessageW(layoutApp.settingsWindow,WM_COMMAND,262,0);auto lan=findTestWindow(L"KSwitcher.LAN",nullptr);auto grid=GetDlgItem(lan,721);
|
||||||
RECT r{};GetClientRect(grid,&r);
|
RECT r{};GetClientRect(grid,&r);
|
||||||
auto point=[&](int column,int row){return MAKELPARAM((2*column+1)*r.right/10,(2*row+1)*r.bottom/6);};
|
auto point=[&](int column,int row){return MAKELPARAM((2*column+1)*r.right/10,(2*row+1)*r.bottom/6);};
|
||||||
SendMessageW(grid,WM_LBUTTONDOWN,MK_LBUTTON,point(1,1));SendMessageW(grid,WM_MOUSEMOVE,MK_LBUTTON,point(1,0));SendMessageW(grid,WM_LBUTTONUP,0,point(1,0));
|
SendMessageW(grid,WM_LBUTTONDOWN,MK_LBUTTON,point(1,1));SendMessageW(grid,WM_MOUSEMOVE,MK_LBUTTON,point(1,0));SendMessageW(grid,WM_LBUTTONUP,0,point(1,0));
|
||||||
|
|||||||
@@ -317,6 +317,11 @@ void hubChecks()
|
|||||||
auto deadline=GetTickCount64()+5000;
|
auto deadline=GetTickCount64()+5000;
|
||||||
while((hubs[0]->ready().size()!=2||hubs[1]->ready().size()!=2||hubs[2]->ready().size()!=2)&&GetTickCount64()<deadline)pump();
|
while((hubs[0]->ready().size()!=2||hubs[1]->ready().size()!=2||hubs[2]->ready().size()!=2)&&GetTickCount64()<deadline)pump();
|
||||||
check(hubs[0]->ready().size()==2&&hubs[1]->ready().size()==2&&hubs[2]->ready().size()==2,"three TCP hubs authenticate all independent pair channels on one listener per PC");
|
check(hubs[0]->ready().size()==2&&hubs[1]->ready().size()==2&&hubs[2]->ready().size()==2,"three TCP hubs authenticate all independent pair channels on one listener per PC");
|
||||||
|
auto generation=hubs[0]->ready();
|
||||||
|
for(auto &list:endpoints)for(auto &e:list)e.address=0;
|
||||||
|
for(int n=0;n<20;++n)pump();
|
||||||
|
check(hubs[0]->ready()==generation&&hubs[1]->ready().size()==2&&hubs[2]->ready().size()==2,"expired discovery does not destroy authenticated channels");
|
||||||
|
for(auto &list:endpoints)for(auto &e:list)e.address=htonl(INADDR_LOOPBACK);
|
||||||
InputFrame keyFrame{InputType::Key,7,'A',0,1},cross{InputType::Cross,8,2,30000};
|
InputFrame keyFrame{InputType::Key,7,'A',0,1},cross{InputType::Cross,8,2,30000};
|
||||||
hubs[0]->poll(endpoints[0],{{ids[1],keyFrame},{ids[2],cross}},incoming[0],GetTickCount64());
|
hubs[0]->poll(endpoints[0],{{ids[1],keyFrame},{ids[2],cross}},incoming[0],GetTickCount64());
|
||||||
for(int n=0;n<100&&(incoming[1].empty()||incoming[2].empty());++n)pump();
|
for(int n=0;n<100&&(incoming[1].empty()||incoming[2].empty());++n)pump();
|
||||||
@@ -327,6 +332,15 @@ void hubChecks()
|
|||||||
check(hubs[0]->ready().contains(ids[2])&&hubs[0]->ready().at(ids[2])==survivor,"one computer disconnect leaves other authenticated channel intact");
|
check(hubs[0]->ready().contains(ids[2])&&hubs[0]->ready().at(ids[2])==survivor,"one computer disconnect leaves other authenticated channel intact");
|
||||||
for(int n=0;n<300&&(hubs[0]->ready().size()!=2||hubs[1]->ready().size()!=2);++n)pump(1800);
|
for(int n=0;n<300&&(hubs[0]->ready().size()!=2||hubs[1]->ready().size()!=2);++n)pump(1800);
|
||||||
check(hubs[0]->ready().size()==2&&hubs[0]->ready().at(ids[1])!=old,"mesh reconnect restores channel with fresh ownership generation");
|
check(hubs[0]->ready().size()==2&&hubs[0]->ready().at(ids[1])!=old,"mesh reconnect restores channel with fresh ownership generation");
|
||||||
|
{
|
||||||
|
auto serverPort=freePort();InputHub server(ids[2],serverPort),client(ids[0],freePort());
|
||||||
|
std::vector<InputEndpoint> unknown{{ids[0],secret,0}},known{{ids[2],secret,htonl(INADDR_LOOPBACK),serverPort}};
|
||||||
|
std::vector<RoutedInput> a,b;
|
||||||
|
for(int n=0;n<300&&(server.ready().empty()||client.ready().empty());++n){
|
||||||
|
server.poll(unknown,{},a,GetTickCount64());client.poll(known,{},b,GetTickCount64());Sleep(1);
|
||||||
|
}
|
||||||
|
check(server.ready().size()==1&&client.ready().size()==1,"saved pair listens and authenticates without receiving a discovery announcement");
|
||||||
|
}
|
||||||
endpoints[0].erase(endpoints[0].begin());hubs[0]->poll(endpoints[0],{},incoming[0],GetTickCount64()+1800);
|
endpoints[0].erase(endpoints[0].begin());hubs[0]->poll(endpoints[0],{},incoming[0],GetTickCount64()+1800);
|
||||||
check(!hubs[0]->ready().contains(ids[1])&&hubs[0]->ready().contains(ids[2]),"forgetting one pair revokes only its channel");
|
check(!hubs[0]->ready().contains(ids[1])&&hubs[0]->ready().contains(ids[2]),"forgetting one pair revokes only its channel");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user