feat: exclusion-aware frame peek for filter-sensitive consumers#10
Open
divanshu-go wants to merge 1 commit into
Open
feat: exclusion-aware frame peek for filter-sensitive consumers#10divanshu-go wants to merge 1 commit into
divanshu-go wants to merge 1 commit into
Conversation
Add a peek API that makes the persistent stream's latched frame safely consumable by filter-sensitive callers (screenpipe's visual-change checks), and fix a filter-update staleness bug that also affects capture(). ## Peek API - peek_latest_frame_matching(monitor_id, width, height, ids): returns the latched frame only when the running stream matches what a capture call with those parameters would use — same resolution (capture recreates the stream on any width/height change) and same exclusion filter (the sorted comparison capture() uses to decide on a filter update). On mismatch the caller falls back to a capture call, which installs the requested parameters. A returned frame is therefore always interchangeable with a captured one: no stream creation, no filter churn, no blocking-thread hop, same ≤1-frame-interval latch staleness. - Monitor::peek_image_excluding / peek_image_scaled_excluding compute the target dimensions with the same scaled_dims logic as the corresponding capture_image* methods, so callers can't drift. - Each stream carries a filter epoch (bumped on every filter change). The matching peek validates under the manager lock, clones the frame after releasing it, and rejects the frame if the epoch moved — a concurrent capture swapping the filter mid-read can't hand back pixels the validation never saw. ## Filter-update correctness (also hardens capture()) update_exclusions() now drops the latched frame after installing the new ContentFilter: the latch was composited under the OLD filter, so serving it would hand callers pixels the new exclusion list claims are filtered out (e.g. a freshly-ignored password manager). Consumers wait ≤1 frame interval for the first post-update delivery instead. ## Lock hygiene All latch reads (peeks and capture()'s fast paths) clone the multi-MB frame buffer OUTSIDE the global manager lock, so a copy on one monitor no longer stalls captures, filter updates, or peeks on others. Removes the now-dead MonitorStream::latest_frame / wait_first_frame. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
a6beca7 to
c29e363
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a peek API that makes the persistent stream's latched frame safely consumable by filter-sensitive callers (screenpipe's visual-change checks), and fixes a filter-update staleness bug that also affects
capture().Peek API
peek_latest_frame_matching(monitor_id, width, height, ids)— returns the latch only when the running stream matches what a capture call with those parameters would use: same resolution (capture recreates on any width/height change) and same exclusion filter (the sorted comparisoncapture()uses). On mismatch the caller falls back to a capture, which installs the requested parameters. A returned frame is therefore always interchangeable with a captured one — no stream creation, no filter churn, no blocking-thread hop, same ≤1-frame-interval latch staleness.Monitor::peek_image_excluding/peek_image_scaled_excludingcompute target dims with the samescaled_dimslogic as the correspondingcapture_image*methods, so callers can't drift.Filter-update correctness (pre-existing bug, also hardens
capture())update_exclusions()installed the new ContentFilter but left the latched frame — composited under the old filter — in place, so for up to one frame interval bothcapture()and peeks served pixels the new exclusion list claims are filtered out (e.g. a freshly-ignored password manager). The latch is now dropped after a successful filter install; consumers wait ≤1 frame interval for the first post-update delivery.Lock hygiene
All latch reads (peeks and
capture()'s fast paths) clone the multi-MB frame buffer outside the global manager lock — a copy on one monitor no longer stalls captures, filter updates, or peeks on other monitors.