<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mohamed-Zakaria</title>
    <description>The latest articles on DEV Community by Mohamed-Zakaria (@mohamedelshesheny).</description>
    <link>https://dev.to/mohamedelshesheny</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1433928%2F1af02ce0-b723-4e66-bcf8-dc88bde54951.jpeg</url>
      <title>DEV Community: Mohamed-Zakaria</title>
      <link>https://dev.to/mohamedelshesheny</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mohamedelshesheny"/>
    <language>en</language>
    <item>
      <title>How sigstop knows you are on a call, with zero permissions</title>
      <dc:creator>Mohamed-Zakaria</dc:creator>
      <pubDate>Fri, 02 Oct 2026 19:59:36 +0000</pubDate>
      <link>https://dev.to/mohamedelshesheny/how-sigstop-knows-you-are-on-a-call-with-zero-permissions-1kfj</link>
      <guid>https://dev.to/mohamedelshesheny/how-sigstop-knows-you-are-on-a-call-with-zero-permissions-1kfj</guid>
      <description>&lt;p&gt;A break reminder has one job it must never get wrong: it must not fire in the middle of a call. So when I wrote sigstop, a menu bar break reminder for macOS, the first question was how an app can know a call is happening without the Microphone permission, which would not help anyway: that permission is for capturing audio, not for knowing whether somebody else is. This is what the code does, with the caveats the docs state next to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fact macOS will tell anyone
&lt;/h2&gt;

&lt;p&gt;CoreAudio exposes a property on every audio device called &lt;code&gt;kAudioDevicePropertyDeviceIsRunningSomewhere&lt;/code&gt;. It answers one question: is some process on this machine running I/O on this device right now. Reading it needs no permission, because it is a property read on a device object, not a capture session, in the same way that &lt;code&gt;ps&lt;/code&gt; is different from attaching a debugger.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;AudioDeviceCollector&lt;/code&gt; enumerates &lt;code&gt;kAudioHardwarePropertyDevices&lt;/code&gt;, keeps the devices whose input-scope &lt;code&gt;kAudioDevicePropertyStreamConfiguration&lt;/code&gt; has at least one channel, and reads the bit on each:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;deviceIsRunningSomewhere&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;device&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AudioObjectID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;AudioObjectPropertyAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;mSelector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kAudioDevicePropertyDeviceIsRunningSomewhere&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;mScope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kAudioObjectPropertyScopeGlobal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;mElement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kAudioObjectPropertyElementMain&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UInt32&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UInt32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;MemoryLayout&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;UInt32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="kt"&gt;AudioObjectGetPropertyData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;noErr&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The return type is &lt;code&gt;Bool?&lt;/code&gt; on purpose. A read that fails is &lt;code&gt;nil&lt;/code&gt;, never &lt;code&gt;false&lt;/code&gt;. "I could not read it" and "nothing is running" are different facts, and collapsing them is how an app ends up interrupting a call it could not see.&lt;/p&gt;

&lt;p&gt;The collector registers &lt;code&gt;AudioObjectAddPropertyListenerBlock&lt;/code&gt; on each device for that property, plus one on the device list for hot-plug, so it is event-driven rather than polled.&lt;/p&gt;

&lt;p&gt;The camera is the same shape in CoreMediaIO. &lt;code&gt;CameraDeviceCollector&lt;/code&gt; enumerates &lt;code&gt;kCMIOHardwarePropertyDevices&lt;/code&gt;, reads &lt;code&gt;kCMIODevicePropertyDeviceIsRunningSomewhere&lt;/code&gt; per device through &lt;code&gt;CMIOObjectGetPropertyData&lt;/code&gt;, registers CMIO listeners, and returns &lt;code&gt;nil&lt;/code&gt; on a failed read. Probed on the machine the docs were written on: three devices, no Camera permission, no prompt, no entry in &lt;code&gt;tccd&lt;/code&gt;'s log. Check it with &lt;code&gt;log show --last 5m --predicate 'process == "tccd"'&lt;/code&gt; after a run.&lt;/p&gt;

&lt;p&gt;There is a stronger guarantee than "it does not ask". The app's &lt;code&gt;Info.plist&lt;/code&gt; carries no &lt;code&gt;NSMicrophoneUsageDescription&lt;/code&gt; and no &lt;code&gt;NSCameraUsageDescription&lt;/code&gt;, so if any future code path tried to open a capture session, macOS would terminate the process with &lt;code&gt;TCC_CRASHING_DUE_TO_PRIVACY_VIOLATION&lt;/code&gt; rather than show a prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the bit does not say
&lt;/h2&gt;

&lt;p&gt;It does not say who. It does not say why: dictation, Voice Control, a voice memo, a browser tab and a game all trip it. And some things hold an input device open permanently; the docs name Krisp, Loopback, BlackHole, some headset daemons and certain audio interfaces. On such a Mac the bit is a constant &lt;code&gt;true&lt;/code&gt; and worthless as a call signal.&lt;/p&gt;

&lt;p&gt;So the state is not a &lt;code&gt;Bool&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;AudioInputState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Sendable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Codable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Hashable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;running&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;notRunning&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;noInputDevice&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;unreliable&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;contributesToMeeting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;.unreliable&lt;/code&gt; comes from a calibration guard in the collector. If the OR'd bit has been continuously &lt;code&gt;true&lt;/code&gt; for more than four hours, or &lt;code&gt;true&lt;/code&gt; for more than 85 percent of the last 24 hours of awake time after at least an hour of observation, the collector reports &lt;code&gt;.unreliable&lt;/code&gt; and says why in a sentence the menu bar and &lt;code&gt;--doctor&lt;/code&gt; both show. The docs are plain that those thresholds were never validated against a real virtual device; they are the long-run judgement about signal quality, and two faster mechanisms sit in front of them.&lt;/p&gt;

&lt;p&gt;One more edge: Zoom holds the device briefly after a call and in a waiting room, so the docs budget about 30 seconds of edge error and never bill meeting time to the second off this signal alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who has the microphone
&lt;/h2&gt;

&lt;p&gt;Since macOS 14, CoreAudio also exposes a list of process objects. &lt;code&gt;AudioProcessCollector&lt;/code&gt; reads &lt;code&gt;kAudioHardwarePropertyProcessObjectList&lt;/code&gt; on the system object, then &lt;code&gt;kAudioProcessPropertyBundleID&lt;/code&gt; and &lt;code&gt;kAudioProcessPropertyIsRunningInput&lt;/code&gt; per object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;isRunningInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;object&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AudioObjectID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inputAddress&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UInt32&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UInt32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;MemoryLayout&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;UInt32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="kt"&gt;AudioObjectGetPropertyData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;noErr&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No Microphone permission is required, none is requested, and a probe of it generated no &lt;code&gt;tccd&lt;/code&gt; activity. Each bundle identifier is matched against a fixed list and dropped; nothing else about the process is read on this path.&lt;/p&gt;

&lt;p&gt;This buys three things. It names the holder, so the hold can say "Slack has the microphone" instead of "something does". It repairs &lt;code&gt;.unreliable&lt;/code&gt;: a driver holding the device does not make Slack's process object report input, so an attributed call-capable holder still counts. And it stops the app being fooled: a readable and empty table is evidence of absence, and a table whose only holders are the speech services is not a call. &lt;code&gt;CallCapableApps.neverAMeetingPrefixes&lt;/code&gt; lists those: &lt;code&gt;com.apple.CoreSpeech&lt;/code&gt;, &lt;code&gt;com.apple.Siri&lt;/code&gt;, &lt;code&gt;com.apple.assistantd&lt;/code&gt;, the &lt;code&gt;com.apple.speech.&lt;/code&gt; prefix and a few more.&lt;/p&gt;

&lt;p&gt;The caveats are printed by &lt;code&gt;--doctor&lt;/code&gt; rather than smoothed over. Audio attributes to helper processes: Chrome's is &lt;code&gt;com.google.Chrome.helper&lt;/code&gt;, and Teams' media path is &lt;code&gt;com.microsoft.vcxpc&lt;/code&gt;, not even under the Teams prefix. Matching is by prefix against a hand-checked list that will age when a vendor reshuffles helpers, and it degrades to the unattributed device bit, visibly. Safari routes page audio through &lt;code&gt;com.apple.WebKit.GPU&lt;/code&gt;, which serves every WebKit client and names no app, so it is deliberately not on the list, or any Safari tab playing a podcast could anchor a call.&lt;/p&gt;

&lt;h2&gt;
  
  
  From a fact to a hold
&lt;/h2&gt;

&lt;p&gt;The engine keys on the microphone, not on a list of conferencing bundle ids it would always be behind on. A live device is a hard block: no prompt is delivered on any channel, and a prompt already on screen is withdrawn. Three steps.&lt;/p&gt;

&lt;p&gt;First, the sensor layer decides whether the device bit is backed by a holder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;audioDeviceHold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AudioDeviceHold&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contributesToMeeting&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notRunning&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;any&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;audioProcesses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;anyInputRunning&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;held&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;any&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;held&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runningButUnheld&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three answers. The bit is false, so no hold. The process table could not be read, so the bare bit holds. The table was read and nothing has input open, named or not, which is evidence of absence and drops the block. That last reading has to hold still for 30 seconds first (&lt;code&gt;unheldDeviceDwell&lt;/code&gt; in &lt;code&gt;AppModel&lt;/code&gt;), because the per-object listener's latency against the device property has never been measured, and a transient disagreement at the start of every real call would be worse than the bug.&lt;/p&gt;

&lt;p&gt;Second, the policy in Core bounds how long a bare microphone may mean "call":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;audioInputRunning&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calendar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eventInProgress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inProgressIsBusy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inProgressHasVideoLink&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;videoEventInProgress&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;audioIsCorroborated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uncorroboratedAudioElapsed&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uncorroboratedAudioCeiling&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;audioInputInUse&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cameraRunning&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cameraInUse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meetingLatch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isHolding&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recentCallContinuing&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;uncorroboratedAudioCeiling&lt;/code&gt; is 20 minutes, and it is not a new number: it is the latch's fact hold plus its anchor extension, 8 plus 12, the repo's own written answer to how long a capture fact alone may mean a call. Corroboration is anything independent of that bit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;audioIsCorroborated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;EngineInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signals&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cameraRunning&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meetingLatch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basis&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;manual&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meetingLatch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;anchorName&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calendar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eventInProgress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inProgressIsBusy&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A camera running, a call app the latch adopted from the process table, a manual "I'm in a meeting", or a busy calendar event. Any one of them and the block is unbounded. The latch arming on the same microphone is not corroboration; that would be the same evidence counted twice. Past the ceiling with nothing agreeing, the block downgrades to a soft deferral, &lt;code&gt;liveCaptureUnattributed&lt;/code&gt;, which the seam budget then bounds.&lt;/p&gt;

&lt;p&gt;The cost is stated in the docs rather than discovered: a long podcast take with no camera and no calendar entry can now be interrupted after 20 minutes, as a seam wait with the sound channel suppressed. That is the trade against an app that is invisible forever on a Krisp Mac, and only the ceiling covers that case: Krisp is an app and holds the device through its own process, so the table is not empty and attribution buys nothing.&lt;/p&gt;

&lt;p&gt;Third, the call does not end when the bit drops, because that is exactly what pressing mute does. A latch asserts something narrower than "you are in a meeting": a capture device ran continuously for at least 45 seconds and stopped less than the hold budget ago. The budget is 8 minutes unconditionally, plus 12 if the app adopted as the anchor is still running, so the longest single hold is 20 minutes. No confidence score and no window title go into it; the guess is not representable in its input. The block is named &lt;code&gt;recentCallContinuing&lt;/code&gt;, after what was observed, and the menu bar shows a held state with the closing time and "Not in a meeting" one click away. Ceilings: 90 minutes of hold per call, three hours per day.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it still misses
&lt;/h2&gt;

&lt;p&gt;Said plainly, because &lt;code&gt;--doctor&lt;/code&gt; says it too. A call you joined muted with the camera off and never unmuted produces no capture fact, so the latch cannot open. Screen sharing is not detectable without the Screen Recording grant, which the app does not request. Google Meet in Safari can open the latch from the unattributed bit but cannot name the app and gets the shorter hold. The manual "I'm in a meeting" hold in the menu, time-boxed to two hours, is the answer to all three.&lt;/p&gt;

&lt;h2&gt;
  
  
  What --doctor shows
&lt;/h2&gt;

&lt;p&gt;Run the binary with &lt;code&gt;--doctor&lt;/code&gt;. From my machine, with nothing on a call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  0     audio input           no input device is running
  0     audio input, by app   nobody. 36 processes are known to
                              CoreAudio and none of them is running input.
                              A device running with nobody holding it is a virtual
                              device, and it does not hold your break.
  0     camera in use         no camera device is running
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the hold section under it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;  &lt;span class="n"&gt;enabled&lt;/span&gt;              &lt;span class="n"&gt;yes&lt;/span&gt;
  &lt;span class="n"&gt;arms&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt;           &lt;span class="m"&gt;45&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;continuous&lt;/span&gt; &lt;span class="n"&gt;microphone&lt;/span&gt; &lt;span class="n"&gt;or&lt;/span&gt; &lt;span class="n"&gt;camera&lt;/span&gt; &lt;span class="n"&gt;use&lt;/span&gt;
  &lt;span class="n"&gt;capture&lt;/span&gt; &lt;span class="n"&gt;live&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;     &lt;span class="n"&gt;no&lt;/span&gt;
  &lt;span class="n"&gt;live&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;blocked&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="n"&gt;audioInputInUse&lt;/span&gt; / &lt;span class="n"&gt;cameraInUse&lt;/span&gt;, &lt;span class="n"&gt;which&lt;/span&gt; &lt;span class="n"&gt;are&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="n"&gt;facts&lt;/span&gt;.
                       &lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;latch&lt;/span&gt; &lt;span class="n"&gt;stays&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;way&lt;/span&gt; &lt;span class="n"&gt;until&lt;/span&gt; &lt;span class="n"&gt;capture&lt;/span&gt; &lt;span class="n"&gt;stops&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything above is a property read plus arithmetic on an injected clock, which is why it is unit-tested without a GUI session, and why the app needs no permission for the one thing it must never get wrong.&lt;/p&gt;

&lt;p&gt;sigstop is an open-source, GPL-3.0 break reminder for macOS, and the code in this article is at &lt;a href="https://github.com/Mohamed-Elshesheny/sigstop" rel="noopener noreferrer"&gt;https://github.com/Mohamed-Elshesheny/sigstop&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>macos</category>
      <category>swift</category>
      <category>opensource</category>
      <category>privacy</category>
    </item>
    <item>
      <title>Open sourcing WordyMe, a lightweight note-taking app for students built with React and TypeScript. Supports rich text, math, and diagrams. Fully self-hostable and developer-friendly. 

GitHub Repo:
https://github.com/TeamCoderz/WordyMe</title>
      <dc:creator>Mohamed-Zakaria</dc:creator>
      <pubDate>Tue, 06 Jan 2026 13:28:42 +0000</pubDate>
      <link>https://dev.to/mohamedelshesheny/open-sourcing-wordyme-a-lightweight-note-taking-app-for-students-built-with-react-and-typescript-5g68</link>
      <guid>https://dev.to/mohamedelshesheny/open-sourcing-wordyme-a-lightweight-note-taking-app-for-students-built-with-react-and-typescript-5g68</guid>
      <description>&lt;p&gt;

&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://github.com/TeamCoderz/WordyMe" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopengraph.githubassets.com%2F025fa1c41e83baa936d0e5b55053ac309819cc5db89830e2141e742495559714%2FTeamCoderz%2FWordyMe" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://github.com/TeamCoderz/WordyMe" rel="noopener noreferrer" class="c-link"&gt;
            GitHub - TeamCoderz/WordyMe: Lightweight Note-Taking App for you to selfhost anywhere. Even on your Raspberry Pi. · GitHub
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Lightweight Note-Taking App for you to selfhost anywhere. Even on your Raspberry Pi. - TeamCoderz/WordyMe
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.githubassets.com%2Ffavicons%2Ffavicon.svg"&gt;
          github.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;




</description>
      <category>opensource</category>
      <category>react</category>
      <category>showdev</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
