licenses.ini was hit on every heartbeat -- 5s x clients x ~8 SetStr per
auth -- with no concurrency protection. Two consequences:
1. 100 concurrent online would saturate the file (~160 writes/sec,
full-file rewrite each via WritePrivateProfileString).
2. Concurrent SetPendingRenewal / DecrementPendingQuota with no lock
occasionally clobbered freshly-set renewal quotas (reported by
user as "preset renewal silently disappears").
Add LicensesIniMutex() (Meyers singleton recursive_mutex, exposed in
CPasswordDlg.h so both CPasswordDlg.cpp and CLicenseDlg.cpp share it)
and wrap all 15 functions that touch licenses.ini.
Rewrite UpdateLicenseActivity around g_activityCache (in-memory state
keyed by "SN|IP|machine"): skip the entire write path when nothing
changed and the 30s LastActiveTime throttle window hasn't expired.
Passcode/HMAC are only flushed on actual change (renewal path); IP
list is only rewritten when the yyMMdd timestamp would roll a day.
Measured impact (local 2-client baseline):
before: 0.60 writes/sec (4 writes per heartbeat cluster)
after: 0.07 writes/sec (one write per client per 30s throttle)
Extrapolated to the 100-online target:
before: ~160 writes/sec (saturation)
after: ~3.3 writes/sec (100 clients / 30s throttle window)
Race elimination is the more important win: PendingQuota's
read-modify-write is now atomic, so the "preset renewal disappears"
race is closed.
Notes from audit (these landed during the same iteration):
- Cache key is (SN, IP, machine), not SN alone. A single SN can be
shared by 100+ end machines in bulk-license deployments, so a
per-SN cache flips on every heartbeat and defeats suppression.
Per-(SN, IP, machine) throttling is what makes the 100/30 model
actually hold; an SN-only key reproduced the original ~0.7 writes/s.
- DeleteLicense invalidates the per-SN activity cache via
InvalidateLicenseActivityCache() (prefix scan since one SN maps to
many cache entries). Without this, cache hits after delete would
skip the auto-recreate path and leave the section permanently
missing.
- OnLicenseViewIPs: m_ListLicense.SetItemText moved outside the lock
so the critical section only covers disk I/O.
GitHub mirror is no longer maintained; v1.3.4+ releases land on Gitea only.
Repoint the Release-version badge and Download-Latest button (href + shields
endpoint + logo) at git.simpleremoter.com so visitors don't end up on a stale
GitHub release page.
Stars/forks badges stay on GitHub — vanity counters reflecting historical
accumulation, not navigation targets.
Also: server/go/README.md yama-issue-token link and the line-294 Markdown
"Releases" link in all three READMEs now point at Gitea.
- Server: clamp web session adaptive quality to H264-only levels (>=Good) in EvaluateQuality and ApplyQualityLevel; Ultra/High (DIFF/RGB565) caused the browser to freeze ~1 min into a session
- Server: move session-type detection to the top of ScreenSpyDlg::OnInitDialog and skip SetWindowPlacement/EnterFullScreen for hidden web sessions, eliminating the MFC dialog flash on web-triggered opens
- Linux client: default QualityLevel from QUALITY_ADAPTIVE to QUALITY_GOOD to match Windows/macOS so the server's adaptive controller doesn't auto-upgrade to non-H264 algorithms
- Web: clear the floating quick-action toolbar on fullscreen exit so its row of buttons (RDP reset / Mouse / Close) doesn't stay pinned to the top of the page
- Web: route F11 to the remote in control mode instead of toggling local fullscreen
- Web: route Esc to the remote in control mode via the Keyboard Lock API instead of exiting native fullscreen
Server sends COMMAND_SCREEN_PREVIEW_REQ when user double-clicks an
active (non-Locked/Inactive) host that advertises CLIENT_CAP_SCREEN_PREVIEW.
Client BitBlts primary screen, encodes to JPEG via GDI+ and replies. The
existing STATIC tooltip is replaced with a self-drawn CPreviewTipWnd
showing the thumbnail above the host info text, with wide-character
rendering so the popup also works on non-Chinese servers.
- Quality tiers reuse QualityProfile pattern: PreviewProfile + 6 levels
driven by GetTargetQualityLevel (FRP-aware), with 4K/ultrawide auto
upscale on Ultra/High tiers up to min(screenWidth/4, 1280).
- Client limits to 1 in-flight capture via atomic counter to defend
against flood/DoS; Send2Server is already mutex-serialized.
- Server validates responses by reqId only (single in-flight tip);
4s arrival timeout marks "preview unavailable" without blocking the
text fallback path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ON_NOTIFY(HDN_ITEMCLICK, 0, ...) matches the inner header control's ID,
which is 0 for both m_CList_Online and m_CList_Message. So clicks on
either list's header reach OnHdnItemclickList, which always sorts the
host list by the clicked column index.
The cross-talk has existed since the initial migration commit (5a325a2).
It went unnoticed because pre-0aa7588 both lists' headers triggered the
handler in A mode and the columns happened to align (host list cols 0..2
== IP/Addr/Location, log list also has 3 cols), so log-header clicks
appeared to "sort plausibly". After 0aa7588 only the log list's A-mode
header reached the handler, surfacing the strange "click log header
re-sorts hosts" behavior.
Guard the handler by checking pNMHDR->hwndFrom against the online list's
header HWND. Log header clicks now have no effect on the host list.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The i18n commit (0aa7588) enabled LVM_SETUNICODEFORMAT(TRUE) on the online
list. That flag also flips the embedded header control to Unicode mode, so
header notifications switch from HDN_ITEMCLICKA (= HDN_ITEMCLICK in MBCS
build) to HDN_ITEMCLICKW. The existing ON_NOTIFY mapping only handles the
A version, so clicking the column header silently does nothing.
Add a parallel ON_NOTIFY for HDN_ITEMCLICKW dispatching to the same handler.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>