From f88d10eaefc6f489029133d800c7c3edafe2ef8e Mon Sep 17 00:00:00 2001 From: Greg Neagle Date: Thu, 6 Feb 2025 17:17:18 -0800 Subject: [PATCH] Skip user notification if a process has made a display sleep assertion, which may mean the user is 'presenting' or in a meeting --- code/client/managedsoftwareupdate.py | 21 +++++++++++++++++++++ code/client/munkilib/powermgr.py | 18 +++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/code/client/managedsoftwareupdate.py b/code/client/managedsoftwareupdate.py index a370d143..fad621dd 100644 --- a/code/client/managedsoftwareupdate.py +++ b/code/client/managedsoftwareupdate.py @@ -73,6 +73,7 @@ else: from munkilib import munkistatus from munkilib import osinstaller from munkilib import osutils + from munkilib import powermgr from munkilib import prefs from munkilib import processes from munkilib import reports @@ -528,6 +529,19 @@ def sendEndNotification(): userInfo) +def activeDisplaySleepAssertion(): + """Returns a boolean to indicate we have an active assertion preventing + display sleep. Idea borrowed from Installomator.""" + for processName, assertions in powermgr.getIOPMAssertions().items(): + if (processName != "coreaudiod" and + ("PreventUserIdleDisplaySleep" in assertions or + "NoDisplaySleepAssertion" in assertions) + ): + munkilog.log("%s has an assertion preventing display sleep") + return True + return False + + def notifyUserOfUpdates(force=False): """Notify the logged-in user of available updates. @@ -562,6 +576,13 @@ def notifyUserOfUpdates(force=False): # subtract 6 hours interval = interval - (6 * 60 * 60) nextNotifyDate = lastNotifiedDate.dateByAddingTimeInterval_(interval) + if activeDisplaySleepAssertion(): + # display sleep assertions are made by Zoom during a meeting, + # PowerPoint and Keynote when presenting, and Chrome when playing + # a movie. Other apps may make these assertions as well, If we see + # such an assertion, don't notify this time. + munkilog.log("Skipping user notification.") + return False if force or now.timeIntervalSinceDate_(nextNotifyDate) >= 0: # record current notification date prefs.set_pref('LastNotifiedDate', now) diff --git a/code/client/munkilib/powermgr.py b/code/client/munkilib/powermgr.py index 1790bc2e..ceeb3575 100644 --- a/code/client/munkilib/powermgr.py +++ b/code/client/munkilib/powermgr.py @@ -45,6 +45,7 @@ functions = [("IOPMAssertionCreateWithName", b"i@i@o^i"), ("IOPSCopyPowerSourcesInfo", b"@"), ("IOPSCopyPowerSourcesList", b"@@"), ("IOPSGetProvidingPowerSourceType", b"@@"), + ("IOPMCopyAssertionsByProcess", b"io^@") ] # No idea why PyLint complains about objc.loadBundleFunctions @@ -89,6 +90,22 @@ def hasInternalBattery(): return True return False +def getIOPMAssertions(): + """Returns a dict containing PowerManager assertions keyed by process name""" + (result, assertions) = IOPMCopyAssertionsByProcess(None) + if result != 0: + return None + pm_assertions = {} + for assertion_list in assertions.values(): + for assertion in assertion_list: + process_name = assertion.get("Process Name") + assertionType = assertion.get("AssertionTrueType") + if process_name and assertionType: + if not process_name in pm_assertions: + pm_assertions[process_name] = [] + pm_assertions[process_name].append(assertionType) + return pm_assertions + def assertNoIdleSleep(reason=None): """Uses IOKit functions to prevent idle sleep.""" kIOPMAssertionTypeNoIdleSleep = "NoIdleSleepAssertion" @@ -106,7 +123,6 @@ def assertNoIdleSleep(reason=None): return None return assertID - def removeNoIdleSleepAssertion(assertion_id): """Uses IOKit functions to remove a "no idle sleep" assertion.""" if assertion_id: