Let me know how it works for you - I'd be happy to get improvements that I can use. In case it is helpful, I am using Python 3.11.
I've started experimenting with this for Google Meet also. The following new version contains some slightly improved error handling, and also will detect a meeting in Google Meet, but so far I don't see a way to tell the difference between watching a video in Chrome and using Google Meet, so it triggers on both. This isn't a problem for me, as I don't tend to use Chrome for browsing, but might be for others. If you find a solution to this please let me know.
# TODO add shebang
# meetingMonitor.py
# code to check whether we are in a Teams or Webex or Zoom Meeting
import re, argparse, subprocess, time, traceback
sleep_CRE = re.compile('sleep prevented by ([^()]+)')
# return the meeting app preventing sleep or None
def check_status():
#output = subprocess.check_output(['pmset', '-g']
# pmset -g for Teams: sleep 0 (sleep prevented by MSTeams, ...
# pmset -g for zoom : displaysleep 180 (display sleep prevented by zoom.us)
# pmset -g for Google Meet : 180 (display sleep prevented by Google Chrome, ...
# also 0 (sleep prevented by ..., Google Chrome)
# Note, this is a separate line from ' sleep' (zoom only shows up in this line on Sonoma 14.6)
# Google Meet is a bit difficult if you are also trying to use Chrome for browsing videos (ex: youtube)
# because this also shows up as 'Google Chrome' in the sleep prevented by list.
# So far I don't see a way to figure out if it is a GoogleMeet meeting or a video.
# Note, this command will return several lines
pmsetCmd = 'pmset -g | grep "^ [[:alpha:]]*sleep"'
output = subprocess.check_output(pmsetCmd, shell=True, encoding='UTF-8')
# The output of this will be more than one line
for line in output.splitlines():
m = sleep_CRE.search(line)
if m is not None:
# m.group(1) will be the list of things preventing sleep, i.e. : 'coreaudiod, WebexHelper'
unsleepers = m.group(1)
for meetingApp in ['MSTeams', 'Webex', 'zoom.us', 'Google Chrome']:
if meetingApp in unsleepers:
# special case, unclear how to differentiate
if meetingApp.startswith('Google'):
return 'GoogleMeet' # special case to remove the space
return meetingApp
return None
# TODO maybe notify btt of the app the first time through the loop
# returns False if there was an exception notifying btt, True otherwise
def notify_btt(meetingApp='', verbosity=0):
if meetingApp is None:
meetingApp = ''
btt_args = ['open']
btt_url = f'"btt://set_persistent_string_variable/?variableName=meeting_app&to={meetingApp}"'
btt_args.append(btt_url)
if verbosity > 0:
print(f'[notify btt subproc args] {btt_args}')
try:
btt_args = " ".join(btt_args)
subprocess.check_call(btt_args, shell=True)
except:
# TODO, what to do on fail, for instance, if btt not open? TEST?
print('unable to update btt with meeting status')
return False
return True
def monitor_status(interval_sec=10, print_updates=True, verbosity=0):
was_meeting = False
while True:
meetingApp = check_status()
is_meeting = meetingApp is not None
if was_meeting != is_meeting:
if print_updates:
print(f'Now Meeting [{is_meeting}] in app {meetingApp}')
was_meeting = is_meeting
notify_btt(meetingApp, verbosity=verbosity)
time.sleep(interval_sec)
if __name__ == '__main__':
parser = argparse.ArgumentParser(usage='MacOS monitor status of MSTeams and Webex and Zoom meetings')
# TODO maybe add print/noprint option
parser.add_argument('-i', '--interval', help='polling interval in sec', default=5, type=int)
parser.add_argument('-v', help='increase verbosity', default=0, action='count')
args = parser.parse_args()
interval_sec = args.interval
if interval_sec < 1:
interval_sec = 1
user_interrupt = False
while not user_interrupt:
try:
monitor_status(interval_sec, verbosity=args.v)
except KeyboardInterrupt:
# OK, user pressed ctrl-c, clean exit
print(' Done.')
user_interrupt = True
except RuntimeError as err:
print('RuntimeError: ', err)
traceback.print_tb(err.__traceback__)
except:
raise