<?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: christianmusumeci00</title>
    <description>The latest articles on DEV Community by christianmusumeci00 (@christianmusumeci00).</description>
    <link>https://dev.to/christianmusumeci00</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%2F4223181%2F0f902252-8f59-4b78-8a44-f2f7904f2abb.png</url>
      <title>DEV Community: christianmusumeci00</title>
      <link>https://dev.to/christianmusumeci00</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/christianmusumeci00"/>
    <language>en</language>
    <item>
      <title>Why "stay awake" APIs don't keep you green on Teams</title>
      <dc:creator>christianmusumeci00</dc:creator>
      <pubDate>Mon, 02 Nov 2026 22:53:31 +0000</pubDate>
      <link>https://dev.to/christianmusumeci00/why-stay-awake-apis-dont-keep-you-green-on-teams-385e</link>
      <guid>https://dev.to/christianmusumeci00/why-stay-awake-apis-dont-keep-you-green-on-teams-385e</guid>
      <description>&lt;p&gt;You get up for a coffee, answer the door or grab something from the printer. Five minutes later Teams has switched you to &lt;strong&gt;Away&lt;/strong&gt;, even though you're back at your desk a minute after that.&lt;/p&gt;

&lt;p&gt;The obvious fix is one of the many "keep awake" tools: &lt;code&gt;caffeinate&lt;/code&gt; or Amphetamine on a Mac, PowerToys Awake on Windows. They don't help: Teams still goes yellow. So I dug into why, and ended up writing a small app, &lt;a href="https://github.com/christianmusumeci00/KeepMeOn" rel="noopener noreferrer"&gt;KeepMeOn&lt;/a&gt;, for macOS and Windows. This post is about what I learned: there are two different "idle" concepts in an operating system, and keep-awake tools only touch one of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two kinds of "idle"
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Power management idle.&lt;/strong&gt; This decides when the display dims, when the screen saver starts and when the computer goes to sleep. Apps can hold it off with a power assertion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;macOS: &lt;code&gt;IOPMAssertionCreateWithName&lt;/code&gt; (what &lt;code&gt;caffeinate&lt;/code&gt; and most keep-awake apps use) or &lt;code&gt;IOPMAssertionDeclareUserActivity&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Windows: &lt;code&gt;SetThreadExecutionState(ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Input idle time.&lt;/strong&gt; This is a plain counter: how long since the last mouse, keyboard or trackpad event. Any app can read it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;macOS: &lt;code&gt;CGEventSourceSecondsSinceLastEventType&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Windows: &lt;code&gt;GetLastInputInfo&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Teams decides you're Away after roughly five minutes in which that &lt;strong&gt;input&lt;/strong&gt; counter keeps growing. Power assertions don't reset it. They keep the screen on and the machine awake, but as far as the input counter is concerned, nobody has touched the computer.&lt;/p&gt;

&lt;p&gt;I measured it rather than trusting the docs. On a Windows 11 machine, calling &lt;code&gt;SetThreadExecutionState&lt;/code&gt; did nothing to the input idle time: it kept climbing. On macOS, &lt;code&gt;IOPMAssertionDeclareUserActivity&lt;/code&gt; postponed display sleep but left the counter Teams reads untouched.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the usual workarounds do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keep-awake apps&lt;/strong&gt; (&lt;code&gt;caffeinate&lt;/code&gt;, Amphetamine, PowerToys Awake): they hold a power assertion. Great for long downloads or presentations, but the input counter still grows, so Teams still goes Away.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mouse jigglers:&lt;/strong&gt; they move the cursor a pixel back and forth. That does reset the counter, but it moves your actual cursor, can wake hover menus and tooltips, and fights with you if you come back mid-jiggle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pressing a real key&lt;/strong&gt; (F15, Shift‚Ä¶): resets the counter too, but some apps react to it, and holding a modifier at the wrong moment turns your next click into a Shift-click.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What I wanted was an input event that resets the counter while every app ignores it.&lt;/p&gt;

&lt;h2&gt;
  
  
  macOS: an "unchanged modifiers" event
&lt;/h2&gt;

&lt;p&gt;macOS has an event type for modifier key changes, &lt;code&gt;flagsChanged&lt;/code&gt;. It fires when you press or release ‚åò, ‚å•, ‚áß, ‚åÉ or Caps Lock, and it carries the full set of modifiers that are now down.&lt;/p&gt;

&lt;p&gt;If you post a &lt;code&gt;flagsChanged&lt;/code&gt; event whose flags are &lt;em&gt;exactly the current flags&lt;/em&gt;, nothing changes for any app: no key went down, no key went up, no character was typed. But it's still an input event, so the idle counter resets.&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;func&lt;/span&gt; &lt;span class="nf"&gt;postNeutralEvent&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;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CGEventSource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flagsState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;combinedSessionState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// Skip it if the user is holding a modifier right now.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;held&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGEventFlags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maskShift&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maskControl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maskAlternate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maskCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maskSecondaryFn&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;CGPreflightPostEventAccess&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
          &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;intersection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&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;isEmpty&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;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CGEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGEventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;stateID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hidSystemState&lt;/span&gt;&lt;span class="p"&gt;))&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;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flagsChanged&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;            &lt;span class="c1"&gt;// same modifiers as now: at most Caps Lock&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setIntegerValueField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eventSourceUserData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x4B4D_4F4E&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// "KMON" tag&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cghidEventTap&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few details that matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It needs the Accessibility permission.&lt;/strong&gt; Posting events is gated by macOS; &lt;code&gt;CGPreflightPostEventAccess()&lt;/code&gt; tells you whether you have it, and &lt;code&gt;CGRequestPostEventAccess()&lt;/code&gt; adds the app to the Accessibility list. macOS checks the permission when the app launches, so after granting it you have to relaunch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It never runs while a modifier is held.&lt;/strong&gt; If you're in the middle of a ‚åò-drag, the app doesn't touch anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's tagged.&lt;/strong&gt; The &lt;code&gt;eventSourceUserData&lt;/code&gt; field carries a marker, so anyone watching with an event tap can tell these events apart from real input.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I still declare user activity with &lt;code&gt;IOPMAssertionDeclareUserActivity&lt;/code&gt; as well: on its own it doesn't keep you green, but it does stop the display from sleeping, which you want too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Windows: the key-up of a key that doesn't exist
&lt;/h2&gt;

&lt;p&gt;Windows has a range of virtual key codes that aren't assigned to anything: &lt;code&gt;0x97&lt;/code&gt; to &lt;code&gt;0x9F&lt;/code&gt;. If you send a &lt;strong&gt;key-up&lt;/strong&gt; for one of them with &lt;code&gt;SendInput&lt;/code&gt;, no app reacts, because there's no such key and it was never pressed. For Windows it still counts as input, so &lt;code&gt;GetLastInputInfo&lt;/code&gt; resets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;ushort&lt;/span&gt; &lt;span class="n"&gt;UnassignedKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0x97&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;INPUT&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;INPUT_KEYBOARD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;InputUnion&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ki&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;KEYBDINPUT&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;wVk&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UnassignedKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;dwFlags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;KEYEVENTF_KEYUP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;dwExtraInfo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0x4B4D4F4E&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// "KMON" tag&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nf"&gt;SendInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&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="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SizeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows, no permission is needed. On a Windows 11 test machine this brought the idle time from 144 s to 0.3 s without moving the cursor.&lt;/p&gt;

&lt;p&gt;I also tried a zero-pixel mouse move (&lt;code&gt;MOUSEEVENTF_MOVE&lt;/code&gt; with &lt;code&gt;dx = dy = 0&lt;/code&gt;). It works too, but I went with the unassigned key-up. One limitation: if an app running as administrator is in the foreground, Windows' UIPI protection can block injected input from a normal-privilege process.&lt;/p&gt;

&lt;p&gt;To make sure this keeps working, the GitHub Actions workflow runs a small probe on a real Windows runner. It sends each candidate signal and checks that the idle counter resets and the cursor doesn't move.&lt;/p&gt;

&lt;h2&gt;
  
  
  Only when you're actually away
&lt;/h2&gt;

&lt;p&gt;Resetting the counter every minute would be easy and wrong: it would hide the fact that you're actually gone for an hour. So the app has a few rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It only acts after 2 minutes without real input.&lt;/strong&gt; While you work, it does nothing at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It checks every 60 seconds.&lt;/strong&gt; With these defaults, the idle counter peaks at about 3 minutes, comfortably under Teams' 5. (In a 7.5-minute run without input on a Mac, the highest value I saw was 181 s.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It pauses&lt;/strong&gt; when the screen is locked, when the computer or display is asleep, and optionally outside work hours and during a lunch break.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If you're leaving for longer, lock the screen.&lt;/strong&gt; KeepMeOn stops, and Teams correctly shows you as Away.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point is the whole idea: it's for the coffee break, not for pretending to be at your desk all afternoon. If your computer is managed by your company, check that this kind of tool is allowed. KeepMeOn stays visible on purpose: running process, login item and, on a Mac, an entry in the Accessibility list.&lt;/p&gt;

&lt;h2&gt;
  
  
  The app
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/christianmusumeci00/KeepMeOn" rel="noopener noreferrer"&gt;KeepMeOn&lt;/a&gt; is free and open source (MIT):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mac:&lt;/strong&gt; native Swift and SwiftUI menu bar app, under 1 MB, macOS 13 or later, signed and notarized.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows:&lt;/strong&gt; C# WinForms tray app on .NET Framework 4.8, which ships with Windows 10 and 11. The installer is about 2 MB.&lt;/li&gt;
&lt;li&gt;No dependencies, no account, no analytics. It only contacts GitHub to check for updates, and you can turn that off.&lt;/li&gt;
&lt;li&gt;English and Italian.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've found a cleaner way to reset the input idle time, or a case where these signals don't work, I'd love to hear about it in the comments.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I used AI coding tools while building the app and writing this post. The measurements and the code are from the project's repository.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>macos</category>
      <category>windows</category>
      <category>swift</category>
      <category>csharp</category>
    </item>
  </channel>
</rss>
