From 65f3a5c1daa52ebeee5ca1e288793e0ce2b4817d Mon Sep 17 00:00:00 2001 From: Simon Zeyer Date: Wed, 29 Dec 2021 15:14:33 +0100 Subject: [PATCH] refactoring as gui application :# Please enter the commit message for your changes. Lines starting --- __main__.py | 156 +---- __main__.spec | 16 +- favicon.ico | Bin 0 -> 67646 bytes gui.py | 147 +++++ ledcontroll.py | 607 ++++++++++++++++++ mediacontrol.py | 119 ++++ requirements.txt | 3 +- resources.py | 100 --- swyx-media-control.spec | 44 ++ swyxcontrol.py | 221 +++++++ ...event_listener.py => workstationcontrol.py | 39 +- 11 files changed, 1177 insertions(+), 275 deletions(-) create mode 100644 favicon.ico create mode 100644 gui.py create mode 100644 ledcontroll.py create mode 100644 mediacontrol.py delete mode 100644 resources.py create mode 100644 swyx-media-control.spec create mode 100644 swyxcontrol.py rename session_event_listener.py => workstationcontrol.py (79%) diff --git a/__main__.py b/__main__.py index 59e31ff..c899bee 100644 --- a/__main__.py +++ b/__main__.py @@ -1,155 +1,5 @@ -import asyncio -DEBUG=False -try: - from winrt.windows.media.control import GlobalSystemMediaTransportControlsSessionManager as MediaManager -except: - print("DEBUG; winrt disabled") - DEBUG = True -import win32com.client -import time -import pythoncom +from gui import Gui -from resources import LineState, CLMgrMessage -import serial -from serial import SerialException -import session_event_listener as sel -import ctypes - -portName = 'COM3' - -global_inactive = True - -# Lock Screen detection -user32 = ctypes.windll.User32 -OpenDesktop = user32.OpenDesktopA -SwitchDesktop = user32.SwitchDesktop -DESKTOP_SWITCHDESKTOP = 0x0100 - -global_screen_locked = False - -def check_screen_locked(): - hDesktop = OpenDesktop ("default", 0, False, DESKTOP_SWITCHDESKTOP) - result = SwitchDesktop (hDesktop) - if result: - return True - else: - return False - -def sendSerial(byte): - try: - ser = serial.Serial(port=portName, baudrate=115200) - ser.write(byte) - except SerialException: - print('port already open') - -class PhoneLineEventHandler(): - phone_mgr = None - lines = [] - line_selected = None - connected = False - - def OnDispOnLineMgrNotification(self, msg, param, returns=""): - - global global_inactive, global_screen_locked - if not self.phone_mgr: - self.phone_mgr = win32com.client.Dispatch("CLMgr.ClientLineMgr") - if not self.phone_mgr: - "Swyx not connected!" - else: - print("Swyx connected!") - if self.phone_mgr: - if msg == CLMgrMessage.CLMgrLineStateChangedMessage: - line = self.phone_mgr.DispGetLine(param) - print("Line: {} {}".format( - param, - LineState.s[line.DispState]) - ) - if line.DispState != LineState.Inactive: - if line.DispState == LineState.Ringing: - sendSerial(b'shc000255000') - else: - sendSerial(b'shc255000000') - - if not global_screen_locked: - asyncio.run(try_pause()) - global_inactive = False - for linenum in range(self.phone_mgr.DispNumberOfLines): - line = self.phone_mgr.DispGetLine(linenum) - if line.DispState != LineState.Inactive: - return True - - if not global_screen_locked: - asyncio.run(try_play()) - global_inactive = True - sendSerial(b'shc000000000') - return True - -# async def get_media_info(): -# sessions = await MediaManager.request_async() - -# # This source_app_user_model_id check and if statement is optional -# # Use it if you want to only get a certain player/program's media -# # (e.g. only chrome.exe's media not any other program's). - -# # To get the ID, use a breakpoint() to run sessions.get_current_session() -# # while the media you want to get is playing. -# # Then set TARGET_ID to the string this call returns. - -# current_session = sessions.get_current_session() -# if current_session: # there needs to be a media session running -# #if current_session.source_app_user_model_id == TARGET_ID: -# info = await current_session.try_get_media_properties_async() -# # song_attr[0] != '_' ignores system attributes -# info_dict = {song_attr: info.__getattribute__(song_attr) for song_attr in dir(info) if song_attr[0] != '_'} - -# # converts winrt vector to list -# info_dict['genres'] = list(info_dict['genres']) - -# return info_dict - -# # It could be possible to select a program from a list of current -# # available ones. I just haven't implemented this here for my use case. -# # See references for more information. -# raise Exception('TARGET_PROGRAM is not the current media session') - -async def try_play(): - print("try_play") - if not DEBUG: - sessions = await MediaManager.request_async() - current_session = sessions.get_current_session() - if current_session: - await current_session.try_play_async() - -async def try_pause(): - print("try_pause") - if not DEBUG: - sessions = await MediaManager.request_async() - current_session = sessions.get_current_session() - if current_session: - await current_session.try_pause_async() - -def OnSessionEvent(event: sel.SessionEvent): - global global_inactive, global_screen_locked - if event == sel.SessionEvent.SESSION_LOCK: - global_screen_locked = True - print("locked, try_pause") - asyncio.run(try_pause()) - - if event == sel.SessionEvent.SESSION_UNLOCK: - global_screen_locked = False - if global_inactive: - print("unlocked, global inactive, tryplay") - asyncio.run(try_play()) - -if __name__ == '__main__': - win32com.client.WithEvents("CLMgr.ClientLineMgr", PhoneLineEventHandler) - m = sel.WorkstationMonitor() - m.register_handler(sel.SessionEvent.ANY, handler=OnSessionEvent) - try: - while True: - pythoncom.PumpWaitingMessages() - m.listen() - time.sleep(0.1) # Don't use up all our CPU checking constantly - except KeyboardInterrupt: - print("exit") \ No newline at end of file +if __name__ == "__main__": + gui = Gui() \ No newline at end of file diff --git a/__main__.spec b/__main__.spec index 91d0550..2d7dfb3 100644 --- a/__main__.spec +++ b/__main__.spec @@ -21,24 +21,20 @@ pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) exe = EXE(pyz, - a.scripts, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, [], - exclude_binaries=True, name='__main__', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, + upx_exclude=[], + runtime_tmpdir=None, console=True, disable_windowed_traceback=False, target_arch=None, codesign_identity=None, entitlements_file=None ) -coll = COLLECT(exe, - a.binaries, - a.zipfiles, - a.datas, - strip=False, - upx=True, - upx_exclude=[], - name='__main__') diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..0282a46ad3b9c9d83d640f7a13621261a7bddb36 GIT binary patch literal 67646 zcmeI*3A}Gp-9PYiyBQh{4W{CvA|V>2RFV>nN=ZZ_MWr+lDXt=tf5s-Ic#-l{RA^9% zqM3Lq9u2RkG!OFPDIV)Z*c$n-~ZC(mRqh{VTBdC)mLA=n>TM>_kagH;J;U1dF5ZN zw9-o7tK*-k-9sPx&|a#(d&ol`(mnXW5AN1nbIopzHP)Em#wx3!L^#1~WXB5RV{8+TT- zvww{S;`ixMtl#_nN}pD?egpj3f7yGF{W|Nc)3t&VDX+cu+Fi?y@M5*qR_lrvw^SQ{ zU2(+~e_q`9Mz#6znokbD``z#Us^Z3T>V6xStXcAp@}gaf4CfNuwnjBpGW)kc1F`+Y z`|OS1U+KR0z3(aT3;u=Q1pe^@aEE!zfp8(IV=XWEjR!sGLA}o~myFLRH&y$8F28X_ z#Tn-nH%>0Uad5>NyH#JFUa~>SypmDzMe8rJ9x;~?ag6y4AMhIwW;sww82_USI^jzv z{C@fMZAxy04g8m1etC2Lgq_1S9Dr>&kbH}MxRJCy7GE)(OYqLU`Hk`!Hx@5`Uq0hU zHJ^O3=AsXl&vk^W634eyye4BC3a-ft@f3f}eeeo4$P)m2v&uU=og_*M1)yTyycYHagL__+80@xV;|8~S~7=!!Mx7EF)6`f-nYTt7e4 zEBwp0dV6D^BpA28Jou=CJ>2ED+;YqAwXc0`clhClcgG!fT=$;$yr)l(J@(j+9COSu zeJbzxd*A!sUXDKc=|6YU`B49<&zuhT{r1~$0&{pCb<|Pakw+fc zz5Cto?%wsTclDgW3;DKu#1TjI=i z;jVYR>t^F)hJWIK%FdX-U~lYVV)oGc{qmdJlJ-^1=#(^`<2ef`ad%WW2RUh$)M_jn^ z#v6A82h5|v-yFanwD`+M@Bkj>Y(AUKw)W1pKk$JMbYJ+w7rIL?y|laHiYvMw{NM-O zcfb4H?(?7jeD~>3f4ay0f(tI_e)z*5c0c*aPkOoJl1ut}*z*(G)D9o=!WX`P+>1JP;Z?w@y69Rvjvi}zU*!&Rt%{Sk? z-b4Ez$Nxn&UOtZxz=1Xnpr6I{&)8!7@%#38 zR}%A;&%XLGk9o`l_Hc;(!=8u*tl3Dgm*?}J{NyM1{vGygl)&kuAN^?e?Qeg(_w_G* z=}Wuy*I&P1M`{}$<#T#p^Z3UA1%IQ{g~yUQ-Sto!-Tf8L#S)>-|% z+~b2E{9t#$0SELx3kP7YZ{b2xb=ocae_@STyu$~u0c(Ml165os|6==D>t~O5<_dPuetcJ!w&21KG^fmpZw$}yRU!!>)q>L|N0*5>>oS-DNlJy zx7Ai#^|sF5pY^O~bz5(}b@zf7yrB0D+i$;pe=K~#i@o>WyF2sDGrKFVys|Sd@CEz~ z4!q+X@924i1F(({(7*UAJRQd$@9hQPfVDvO0~Xg83sYFMjch`+0uD4L9ulhBbxQLJYwdeB>h^>F`rF9`N6{`_)_f9<36b1dv(PUw@J9)JAt{rb#0o;{v= z>Zv{cV!_&j>GjWk`4;%X+1$_W@q;|~xzFvk*=CzQ&o4OcJbe>axX*K*^PGN8aBnfD zwZboc@r&-1Q%>pcd%_7PbZ>p@Tl>Djedq)5p!NlKsd0;WtaKkH69>7>37AO7%%yH~&Z)e|-! zKY+LTywp-l?OvE$@8E!WKrDa*i)$!JYk^i}?e%h{Lo2S8D@y|HLOgvA<58d0;wmUdsdAag93o z;sZ{a3(h_F+ z;lr`@r1Tk4M_0jL><@psjvn{gYp>qUg1x;M&+*tjo6EXh3<�wfRx_r%oH%GM8vy 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 W&Qm^b(BEPC%E9s8*Z%+TzyAd+s+TnY literal 0 HcmV?d00001 diff --git a/gui.py b/gui.py new file mode 100644 index 0000000..59ce74c --- /dev/null +++ b/gui.py @@ -0,0 +1,147 @@ +# Import the required libraries +import logging +from tkinter import * +from pystray import MenuItem as item +import pystray +from PIL import Image, ImageTk +import threading +from functools import partial +import time + +from mediacontrol import MediaControl +from ledcontroll import BLACK, LedControl, RED1, GREEN +from swyxcontrol import SwyxControl, LineState +from workstationcontrol import LockscreenMonitor, SessionEvent + +class Gui(): + + win: Tk + t_icon: threading.Thread + + strvar_swyx_connected_status: StringVar + strvar_screen_locked: StringVar + strvar_line_inactive: StringVar + strvar_play_state: StringVar + + on_quit = None + + def set_swyx_status(self, string): + self.strvar_swyx_connected_status.set(string) + + # Define a function for quit the window + def quit_window(self, icon, item = None): + self.sc.stop() + self.wm.stop() + self.mc.stop() + icon.stop() + self.win.deiconify() + self.win.quit() + + # Define a function to show the window again + def show_window(self, icon, item): + self.win.deiconify() + + # Hide the window and show on the system taskbar + def hide_window(self): + self.win.withdraw() + + def handle_lockscreen(self, event: SessionEvent): + if event == SessionEvent.SESSION_LOCK: + logging.debug("locked, pause") + self.mc.pause() + self.strvar_screen_locked.set("Locked") + + if event == SessionEvent.SESSION_UNLOCK: + if self.sc.all_lines_inactive: + logging.debug("unlocked, global inactive, play") + time.sleep(3) + self.mc.play() + self.strvar_screen_locked.set("Unlocked") + + def handle_line_state_changed(self, line): + if not line.DispState == LineState.Inactive: + if line.DispState == LineState.Ringing: + self.lc.show_all(GREEN) + else: + self.lc.show_all(RED1) + self.mc.pause() + self.strvar_play_state.set("try pause") + self.strvar_line_inactive.set("False") + if self.sc.all_lines_inactive: + self.strvar_line_inactive.set("True") + self.lc.show_all(BLACK) + if not self.wm.screen_locked(): + self.mc.play() + self.strvar_play_state.set("try play") + + def handle_phone_mgr_connected(self): + if self.sc.all_lines_inactive: + self.strvar_line_inactive.set("True") + self.lc.show_all(BLACK) + return + self.strvar_line_inactive.set("False") + + def handle_media_control_init(self): + playback_info = self.mc.get_playback_info() + if playback_info is None: + print(playback_info) + self.strvar_play_state.set("no session active") + + def __init__(self): + + self.load_gui() + + self.sc = SwyxControl() + self.sc.name = "SwyxControl" + self.sc.add_phone_mgr_connected_handler(self.handle_phone_mgr_connected) + self.sc.add_line_state_changed_handler(self.handle_line_state_changed) + self.sc.start() + + self.lc = LedControl('COM3') + + self.mc = MediaControl() + self.mc.name = "MediaControl" + self.mc.add_init_handler(self.handle_media_control_init) + self.mc.start() + + self.wm = LockscreenMonitor() + self.wm.name = "LockscreenMonitor" + self.wm.register_handler(self.handle_lockscreen) + self.strvar_screen_locked.set("Locked" if self.wm.screen_locked() else "Unlocked") + self.wm.start() + + self.win.protocol('WM_DELETE_WINDOW', self.hide_window) + image=Image.open("favicon.ico") + menu=(item('Beenden', self.quit_window), item('Anzeigen', self.show_window)) + self.icon=pystray.Icon("name", image, "Swyx Media Controll", menu) + self.t_icon = threading.Thread(target=self.icon.run) + self.t_icon.name = "icon_thread" + self.t_icon.start() + self.win.mainloop() + + + def load_gui(self): + self.win=Tk() + # Create an instance of tkinter frame or window + self.win.title("Swyx Media Controll") + # Set the size of the window + self.win.geometry("350x150") + self.win.resizable(False, False) + + ## Screen Lock + Label(self.win, text="Screen Lock: ", font=("Helvetica", 10)).place(x=10, y=10) + self.strvar_screen_locked = StringVar(self.win) + Label(self.win, textvariable=self.strvar_screen_locked, font=("Helvetica", 10)).place(x=100, y=10) + + ## Line Inactive + Label(self.win, text="Line Inactive: ", font=("Helvetica", 10)).place(x=10, y=30) + self.strvar_line_inactive = StringVar(self.win) + Label(self.win, textvariable=self.strvar_line_inactive, font=("Helvetica", 10)).place(x=100, y=30) + + ## Play State + Label(self.win, text="Play State: ", font=("Helvetica", 10)).place(x=10, y=50) + self.strvar_play_state = StringVar(self.win) + Label(self.win, textvariable=self.strvar_play_state, font=("Helvetica", 10)).place(x=100, y=50) + +if __name__ == "__main__": + gui = Gui() diff --git a/ledcontroll.py b/ledcontroll.py new file mode 100644 index 0000000..8921fc6 --- /dev/null +++ b/ledcontroll.py @@ -0,0 +1,607 @@ +import serial + +class LedControl(): + portName = '' + + def __init__(self, portName: str) -> None: + '''portname is the com port of the ledstrip''' + self.portName = portName + pass + + def show_all(self, color:'RGB'): + '''shows all leds in a specified color''' + self.__sendSerial(b"shc"+color.hex_format()) + + def set_one(self, id, color: 'RGB'): + '''sets a led to a specified color''' + self.__sendSerial("dta{:03}".format(id).encode() + color.hex_format()) + + def show(self): + '''shows the colors set by set_one''' + self.__sendSerial(b'shw') + + def __sendSerial(self, byte): + try: + ser = serial.Serial(port=self.portName, baudrate=115200) + ser.write(byte) + for line in ser.readline(): + pass + except serial.SerialException: + print('port already open') + +"""Provide RGB color constants and a colors dictionary with +elements formatted: colors[colorname] = CONSTANT""" +from collections import namedtuple, OrderedDict +Color = namedtuple('RGB','red, green, blue') +colors = {} #dict of colors +class RGB(Color): + def hex_format(self) -> bytes: + '''Returns color in hex format''' + return '{:03}{:03}{:03}'.format(self.red,self.green,self.blue).encode() +#Color Contants +ALICEBLUE = RGB(240, 248, 255) +ANTIQUEWHITE = RGB(250, 235, 215) +ANTIQUEWHITE1 = RGB(255, 239, 219) +ANTIQUEWHITE2 = RGB(238, 223, 204) +ANTIQUEWHITE3 = RGB(205, 192, 176) +ANTIQUEWHITE4 = RGB(139, 131, 120) +AQUA = RGB(0, 255, 255) +AQUAMARINE1 = RGB(127, 255, 212) +AQUAMARINE2 = RGB(118, 238, 198) +AQUAMARINE3 = RGB(102, 205, 170) +AQUAMARINE4 = RGB(69, 139, 116) +AZURE1 = RGB(240, 255, 255) +AZURE2 = RGB(224, 238, 238) +AZURE3 = RGB(193, 205, 205) +AZURE4 = RGB(131, 139, 139) +BANANA = RGB(227, 207, 87) +BEIGE = RGB(245, 245, 220) +BISQUE1 = RGB(255, 228, 196) +BISQUE2 = RGB(238, 213, 183) +BISQUE3 = RGB(205, 183, 158) +BISQUE4 = RGB(139, 125, 107) +BLACK = RGB(0, 0, 0) +BLANCHEDALMOND = RGB(255, 235, 205) +BLUE = RGB(0, 0, 255) +BLUE2 = RGB(0, 0, 238) +BLUE3 = RGB(0, 0, 205) +BLUE4 = RGB(0, 0, 139) +BLUEVIOLET = RGB(138, 43, 226) +BRICK = RGB(156, 102, 31) +BROWN = RGB(165, 42, 42) +BROWN1 = RGB(255, 64, 64) +BROWN2 = RGB(238, 59, 59) +BROWN3 = RGB(205, 51, 51) +BROWN4 = RGB(139, 35, 35) +BURLYWOOD = RGB(222, 184, 135) +BURLYWOOD1 = RGB(255, 211, 155) +BURLYWOOD2 = RGB(238, 197, 145) +BURLYWOOD3 = RGB(205, 170, 125) +BURLYWOOD4 = RGB(139, 115, 85) +BURNTSIENNA = RGB(138, 54, 15) +BURNTUMBER = RGB(138, 51, 36) +CADETBLUE = RGB(95, 158, 160) +CADETBLUE1 = RGB(152, 245, 255) +CADETBLUE2 = RGB(142, 229, 238) +CADETBLUE3 = RGB(122, 197, 205) +CADETBLUE4 = RGB(83, 134, 139) +CADMIUMORANGE = RGB(255, 97, 3) +CADMIUMYELLOW = RGB(255, 153, 18) +CARROT = RGB(237, 145, 33) +CHARTREUSE1 = RGB(127, 255, 0) +CHARTREUSE2 = RGB(118, 238, 0) +CHARTREUSE3 = RGB(102, 205, 0) +CHARTREUSE4 = RGB(69, 139, 0) +CHOCOLATE = RGB(210, 105, 30) +CHOCOLATE1 = RGB(255, 127, 36) +CHOCOLATE2 = RGB(238, 118, 33) +CHOCOLATE3 = RGB(205, 102, 29) +CHOCOLATE4 = RGB(139, 69, 19) +COBALT = RGB(61, 89, 171) +COBALTGREEN = RGB(61, 145, 64) +COLDGREY = RGB(128, 138, 135) +CORAL = RGB(255, 127, 80) +CORAL1 = RGB(255, 114, 86) +CORAL2 = RGB(238, 106, 80) +CORAL3 = RGB(205, 91, 69) +CORAL4 = RGB(139, 62, 47) +CORNFLOWERBLUE = RGB(100, 149, 237) +CORNSILK1 = RGB(255, 248, 220) +CORNSILK2 = RGB(238, 232, 205) +CORNSILK3 = RGB(205, 200, 177) +CORNSILK4 = RGB(139, 136, 120) +CRIMSON = RGB(220, 20, 60) +CYAN2 = RGB(0, 238, 238) +CYAN3 = RGB(0, 205, 205) +CYAN4 = RGB(0, 139, 139) +DARKGOLDENROD = RGB(184, 134, 11) +DARKGOLDENROD1 = RGB(255, 185, 15) +DARKGOLDENROD2 = RGB(238, 173, 14) +DARKGOLDENROD3 = RGB(205, 149, 12) +DARKGOLDENROD4 = RGB(139, 101, 8) +DARKGRAY = RGB(169, 169, 169) +DARKGREEN = RGB(0, 100, 0) +DARKKHAKI = RGB(189, 183, 107) +DARKOLIVEGREEN = RGB(85, 107, 47) +DARKOLIVEGREEN1 = RGB(202, 255, 112) +DARKOLIVEGREEN2 = RGB(188, 238, 104) +DARKOLIVEGREEN3 = RGB(162, 205, 90) +DARKOLIVEGREEN4 = RGB(110, 139, 61) +DARKORANGE = RGB(255, 140, 0) +DARKORANGE1 = RGB(255, 127, 0) +DARKORANGE2 = RGB(238, 118, 0) +DARKORANGE3 = RGB(205, 102, 0) +DARKORANGE4 = RGB(139, 69, 0) +DARKORCHID = RGB(153, 50, 204) +DARKORCHID1 = RGB(191, 62, 255) +DARKORCHID2 = RGB(178, 58, 238) +DARKORCHID3 = RGB(154, 50, 205) +DARKORCHID4 = RGB(104, 34, 139) +DARKSALMON = RGB(233, 150, 122) +DARKSEAGREEN = RGB(143, 188, 143) +DARKSEAGREEN1 = RGB(193, 255, 193) +DARKSEAGREEN2 = RGB(180, 238, 180) +DARKSEAGREEN3 = RGB(155, 205, 155) +DARKSEAGREEN4 = RGB(105, 139, 105) +DARKSLATEBLUE = RGB(72, 61, 139) +DARKSLATEGRAY = RGB(47, 79, 79) +DARKSLATEGRAY1 = RGB(151, 255, 255) +DARKSLATEGRAY2 = RGB(141, 238, 238) +DARKSLATEGRAY3 = RGB(121, 205, 205) +DARKSLATEGRAY4 = RGB(82, 139, 139) +DARKTURQUOISE = RGB(0, 206, 209) +DARKVIOLET = RGB(148, 0, 211) +DEEPPINK1 = RGB(255, 20, 147) +DEEPPINK2 = RGB(238, 18, 137) +DEEPPINK3 = RGB(205, 16, 118) +DEEPPINK4 = RGB(139, 10, 80) +DEEPSKYBLUE1 = RGB(0, 191, 255) +DEEPSKYBLUE2 = RGB(0, 178, 238) +DEEPSKYBLUE3 = RGB(0, 154, 205) +DEEPSKYBLUE4 = RGB(0, 104, 139) +DIMGRAY = RGB(105, 105, 105) +DIMGRAY = RGB(105, 105, 105) +DODGERBLUE1 = RGB(30, 144, 255) +DODGERBLUE2 = RGB(28, 134, 238) +DODGERBLUE3 = RGB(24, 116, 205) +DODGERBLUE4 = RGB(16, 78, 139) +EGGSHELL = RGB(252, 230, 201) +EMERALDGREEN = RGB(0, 201, 87) +FIREBRICK = RGB(178, 34, 34) +FIREBRICK1 = RGB(255, 48, 48) +FIREBRICK2 = RGB(238, 44, 44) +FIREBRICK3 = RGB(205, 38, 38) +FIREBRICK4 = RGB(139, 26, 26) +FLESH = RGB(255, 125, 64) +FLORALWHITE = RGB(255, 250, 240) +FORESTGREEN = RGB(34, 139, 34) +GAINSBORO = RGB(220, 220, 220) +GHOSTWHITE = RGB(248, 248, 255) +GOLD1 = RGB(255, 215, 0) +GOLD2 = RGB(238, 201, 0) +GOLD3 = RGB(205, 173, 0) +GOLD4 = RGB(139, 117, 0) +GOLDENROD = RGB(218, 165, 32) +GOLDENROD1 = RGB(255, 193, 37) +GOLDENROD2 = RGB(238, 180, 34) +GOLDENROD3 = RGB(205, 155, 29) +GOLDENROD4 = RGB(139, 105, 20) +GRAY = RGB(128, 128, 128) +GRAY1 = RGB(3, 3, 3) +GRAY10 = RGB(26, 26, 26) +GRAY11 = RGB(28, 28, 28) +GRAY12 = RGB(31, 31, 31) +GRAY13 = RGB(33, 33, 33) +GRAY14 = RGB(36, 36, 36) +GRAY15 = RGB(38, 38, 38) +GRAY16 = RGB(41, 41, 41) +GRAY17 = RGB(43, 43, 43) +GRAY18 = RGB(46, 46, 46) +GRAY19 = RGB(48, 48, 48) +GRAY2 = RGB(5, 5, 5) +GRAY20 = RGB(51, 51, 51) +GRAY21 = RGB(54, 54, 54) +GRAY22 = RGB(56, 56, 56) +GRAY23 = RGB(59, 59, 59) +GRAY24 = RGB(61, 61, 61) +GRAY25 = RGB(64, 64, 64) +GRAY26 = RGB(66, 66, 66) +GRAY27 = RGB(69, 69, 69) +GRAY28 = RGB(71, 71, 71) +GRAY29 = RGB(74, 74, 74) +GRAY3 = RGB(8, 8, 8) +GRAY30 = RGB(77, 77, 77) +GRAY31 = RGB(79, 79, 79) +GRAY32 = RGB(82, 82, 82) +GRAY33 = RGB(84, 84, 84) +GRAY34 = RGB(87, 87, 87) +GRAY35 = RGB(89, 89, 89) +GRAY36 = RGB(92, 92, 92) +GRAY37 = RGB(94, 94, 94) +GRAY38 = RGB(97, 97, 97) +GRAY39 = RGB(99, 99, 99) +GRAY4 = RGB(10, 10, 10) +GRAY40 = RGB(102, 102, 102) +GRAY42 = RGB(107, 107, 107) +GRAY43 = RGB(110, 110, 110) +GRAY44 = RGB(112, 112, 112) +GRAY45 = RGB(115, 115, 115) +GRAY46 = RGB(117, 117, 117) +GRAY47 = RGB(120, 120, 120) +GRAY48 = RGB(122, 122, 122) +GRAY49 = RGB(125, 125, 125) +GRAY5 = RGB(13, 13, 13) +GRAY50 = RGB(127, 127, 127) +GRAY51 = RGB(130, 130, 130) +GRAY52 = RGB(133, 133, 133) +GRAY53 = RGB(135, 135, 135) +GRAY54 = RGB(138, 138, 138) +GRAY55 = RGB(140, 140, 140) +GRAY56 = RGB(143, 143, 143) +GRAY57 = RGB(145, 145, 145) +GRAY58 = RGB(148, 148, 148) +GRAY59 = RGB(150, 150, 150) +GRAY6 = RGB(15, 15, 15) +GRAY60 = RGB(153, 153, 153) +GRAY61 = RGB(156, 156, 156) +GRAY62 = RGB(158, 158, 158) +GRAY63 = RGB(161, 161, 161) +GRAY64 = RGB(163, 163, 163) +GRAY65 = RGB(166, 166, 166) +GRAY66 = RGB(168, 168, 168) +GRAY67 = RGB(171, 171, 171) +GRAY68 = RGB(173, 173, 173) +GRAY69 = RGB(176, 176, 176) +GRAY7 = RGB(18, 18, 18) +GRAY70 = RGB(179, 179, 179) +GRAY71 = RGB(181, 181, 181) +GRAY72 = RGB(184, 184, 184) +GRAY73 = RGB(186, 186, 186) +GRAY74 = RGB(189, 189, 189) +GRAY75 = RGB(191, 191, 191) +GRAY76 = RGB(194, 194, 194) +GRAY77 = RGB(196, 196, 196) +GRAY78 = RGB(199, 199, 199) +GRAY79 = RGB(201, 201, 201) +GRAY8 = RGB(20, 20, 20) +GRAY80 = RGB(204, 204, 204) +GRAY81 = RGB(207, 207, 207) +GRAY82 = RGB(209, 209, 209) +GRAY83 = RGB(212, 212, 212) +GRAY84 = RGB(214, 214, 214) +GRAY85 = RGB(217, 217, 217) +GRAY86 = RGB(219, 219, 219) +GRAY87 = RGB(222, 222, 222) +GRAY88 = RGB(224, 224, 224) +GRAY89 = RGB(227, 227, 227) +GRAY9 = RGB(23, 23, 23) +GRAY90 = RGB(229, 229, 229) +GRAY91 = RGB(232, 232, 232) +GRAY92 = RGB(235, 235, 235) +GRAY93 = RGB(237, 237, 237) +GRAY94 = RGB(240, 240, 240) +GRAY95 = RGB(242, 242, 242) +GRAY97 = RGB(247, 247, 247) +GRAY98 = RGB(250, 250, 250) +GRAY99 = RGB(252, 252, 252) +GREEN = RGB(0, 128, 0) +GREEN1 = RGB(0, 255, 0) +GREEN2 = RGB(0, 238, 0) +GREEN3 = RGB(0, 205, 0) +GREEN4 = RGB(0, 139, 0) +GREENYELLOW = RGB(173, 255, 47) +HONEYDEW1 = RGB(240, 255, 240) +HONEYDEW2 = RGB(224, 238, 224) +HONEYDEW3 = RGB(193, 205, 193) +HONEYDEW4 = RGB(131, 139, 131) +HOTPINK = RGB(255, 105, 180) +HOTPINK1 = RGB(255, 110, 180) +HOTPINK2 = RGB(238, 106, 167) +HOTPINK3 = RGB(205, 96, 144) +HOTPINK4 = RGB(139, 58, 98) +INDIANRED = RGB(176, 23, 31) +INDIANRED = RGB(205, 92, 92) +INDIANRED1 = RGB(255, 106, 106) +INDIANRED2 = RGB(238, 99, 99) +INDIANRED3 = RGB(205, 85, 85) +INDIANRED4 = RGB(139, 58, 58) +INDIGO = RGB(75, 0, 130) +IVORY1 = RGB(255, 255, 240) +IVORY2 = RGB(238, 238, 224) +IVORY3 = RGB(205, 205, 193) +IVORY4 = RGB(139, 139, 131) +IVORYBLACK = RGB(41, 36, 33) +KHAKI = RGB(240, 230, 140) +KHAKI1 = RGB(255, 246, 143) +KHAKI2 = RGB(238, 230, 133) +KHAKI3 = RGB(205, 198, 115) +KHAKI4 = RGB(139, 134, 78) +LAVENDER = RGB(230, 230, 250) +LAVENDERBLUSH1 = RGB(255, 240, 245) +LAVENDERBLUSH2 = RGB(238, 224, 229) +LAVENDERBLUSH3 = RGB(205, 193, 197) +LAVENDERBLUSH4 = RGB(139, 131, 134) +LAWNGREEN = RGB(124, 252, 0) +LEMONCHIFFON1 = RGB(255, 250, 205) +LEMONCHIFFON2 = RGB(238, 233, 191) +LEMONCHIFFON3 = RGB(205, 201, 165) +LEMONCHIFFON4 = RGB(139, 137, 112) +LIGHTBLUE = RGB(173, 216, 230) +LIGHTBLUE1 = RGB(191, 239, 255) +LIGHTBLUE2 = RGB(178, 223, 238) +LIGHTBLUE3 = RGB(154, 192, 205) +LIGHTBLUE4 = RGB(104, 131, 139) +LIGHTCORAL = RGB(240, 128, 128) +LIGHTCYAN1 = RGB(224, 255, 255) +LIGHTCYAN2 = RGB(209, 238, 238) +LIGHTCYAN3 = RGB(180, 205, 205) +LIGHTCYAN4 = RGB(122, 139, 139) +LIGHTGOLDENROD1 = RGB(255, 236, 139) +LIGHTGOLDENROD2 = RGB(238, 220, 130) +LIGHTGOLDENROD3 = RGB(205, 190, 112) +LIGHTGOLDENROD4 = RGB(139, 129, 76) +LIGHTGOLDENRODYELLOW = RGB(250, 250, 210) +LIGHTGREY = RGB(211, 211, 211) +LIGHTPINK = RGB(255, 182, 193) +LIGHTPINK1 = RGB(255, 174, 185) +LIGHTPINK2 = RGB(238, 162, 173) +LIGHTPINK3 = RGB(205, 140, 149) +LIGHTPINK4 = RGB(139, 95, 101) +LIGHTSALMON1 = RGB(255, 160, 122) +LIGHTSALMON2 = RGB(238, 149, 114) +LIGHTSALMON3 = RGB(205, 129, 98) +LIGHTSALMON4 = RGB(139, 87, 66) +LIGHTSEAGREEN = RGB(32, 178, 170) +LIGHTSKYBLUE = RGB(135, 206, 250) +LIGHTSKYBLUE1 = RGB(176, 226, 255) +LIGHTSKYBLUE2 = RGB(164, 211, 238) +LIGHTSKYBLUE3 = RGB(141, 182, 205) +LIGHTSKYBLUE4 = RGB(96, 123, 139) +LIGHTSLATEBLUE = RGB(132, 112, 255) +LIGHTSLATEGRAY = RGB(119, 136, 153) +LIGHTSTEELBLUE = RGB(176, 196, 222) +LIGHTSTEELBLUE1 = RGB(202, 225, 255) +LIGHTSTEELBLUE2 = RGB(188, 210, 238) +LIGHTSTEELBLUE3 = RGB(162, 181, 205) +LIGHTSTEELBLUE4 = RGB(110, 123, 139) +LIGHTYELLOW1 = RGB(255, 255, 224) +LIGHTYELLOW2 = RGB(238, 238, 209) +LIGHTYELLOW3 = RGB(205, 205, 180) +LIGHTYELLOW4 = RGB(139, 139, 122) +LIMEGREEN = RGB(50, 205, 50) +LINEN = RGB(250, 240, 230) +MAGENTA = RGB(255, 0, 255) +MAGENTA2 = RGB(238, 0, 238) +MAGENTA3 = RGB(205, 0, 205) +MAGENTA4 = RGB(139, 0, 139) +MANGANESEBLUE = RGB(3, 168, 158) +MAROON = RGB(128, 0, 0) +MAROON1 = RGB(255, 52, 179) +MAROON2 = RGB(238, 48, 167) +MAROON3 = RGB(205, 41, 144) +MAROON4 = RGB(139, 28, 98) +MEDIUMORCHID = RGB(186, 85, 211) +MEDIUMORCHID1 = RGB(224, 102, 255) +MEDIUMORCHID2 = RGB(209, 95, 238) +MEDIUMORCHID3 = RGB(180, 82, 205) +MEDIUMORCHID4 = RGB(122, 55, 139) +MEDIUMPURPLE = RGB(147, 112, 219) +MEDIUMPURPLE1 = RGB(171, 130, 255) +MEDIUMPURPLE2 = RGB(159, 121, 238) +MEDIUMPURPLE3 = RGB(137, 104, 205) +MEDIUMPURPLE4 = RGB(93, 71, 139) +MEDIUMSEAGREEN = RGB(60, 179, 113) +MEDIUMSLATEBLUE = RGB(123, 104, 238) +MEDIUMSPRINGGREEN = RGB(0, 250, 154) +MEDIUMTURQUOISE = RGB(72, 209, 204) +MEDIUMVIOLETRED = RGB(199, 21, 133) +MELON = RGB(227, 168, 105) +MIDNIGHTBLUE = RGB(25, 25, 112) +MINT = RGB(189, 252, 201) +MINTCREAM = RGB(245, 255, 250) +MISTYROSE1 = RGB(255, 228, 225) +MISTYROSE2 = RGB(238, 213, 210) +MISTYROSE3 = RGB(205, 183, 181) +MISTYROSE4 = RGB(139, 125, 123) +MOCCASIN = RGB(255, 228, 181) +NAVAJOWHITE1 = RGB(255, 222, 173) +NAVAJOWHITE2 = RGB(238, 207, 161) +NAVAJOWHITE3 = RGB(205, 179, 139) +NAVAJOWHITE4 = RGB(139, 121, 94) +NAVY = RGB(0, 0, 128) +OLDLACE = RGB(253, 245, 230) +OLIVE = RGB(128, 128, 0) +OLIVEDRAB = RGB(107, 142, 35) +OLIVEDRAB1 = RGB(192, 255, 62) +OLIVEDRAB2 = RGB(179, 238, 58) +OLIVEDRAB3 = RGB(154, 205, 50) +OLIVEDRAB4 = RGB(105, 139, 34) +ORANGE = RGB(255, 128, 0) +ORANGE1 = RGB(255, 165, 0) +ORANGE2 = RGB(238, 154, 0) +ORANGE3 = RGB(205, 133, 0) +ORANGE4 = RGB(139, 90, 0) +ORANGERED1 = RGB(255, 69, 0) +ORANGERED2 = RGB(238, 64, 0) +ORANGERED3 = RGB(205, 55, 0) +ORANGERED4 = RGB(139, 37, 0) +ORCHID = RGB(218, 112, 214) +ORCHID1 = RGB(255, 131, 250) +ORCHID2 = RGB(238, 122, 233) +ORCHID3 = RGB(205, 105, 201) +ORCHID4 = RGB(139, 71, 137) +PALEGOLDENROD = RGB(238, 232, 170) +PALEGREEN = RGB(152, 251, 152) +PALEGREEN1 = RGB(154, 255, 154) +PALEGREEN2 = RGB(144, 238, 144) +PALEGREEN3 = RGB(124, 205, 124) +PALEGREEN4 = RGB(84, 139, 84) +PALETURQUOISE1 = RGB(187, 255, 255) +PALETURQUOISE2 = RGB(174, 238, 238) +PALETURQUOISE3 = RGB(150, 205, 205) +PALETURQUOISE4 = RGB(102, 139, 139) +PALEVIOLETRED = RGB(219, 112, 147) +PALEVIOLETRED1 = RGB(255, 130, 171) +PALEVIOLETRED2 = RGB(238, 121, 159) +PALEVIOLETRED3 = RGB(205, 104, 137) +PALEVIOLETRED4 = RGB(139, 71, 93) +PAPAYAWHIP = RGB(255, 239, 213) +PEACHPUFF1 = RGB(255, 218, 185) +PEACHPUFF2 = RGB(238, 203, 173) +PEACHPUFF3 = RGB(205, 175, 149) +PEACHPUFF4 = RGB(139, 119, 101) +PEACOCK = RGB(51, 161, 201) +PINK = RGB(255, 192, 203) +PINK1 = RGB(255, 181, 197) +PINK2 = RGB(238, 169, 184) +PINK3 = RGB(205, 145, 158) +PINK4 = RGB(139, 99, 108) +PLUM = RGB(221, 160, 221) +PLUM1 = RGB(255, 187, 255) +PLUM2 = RGB(238, 174, 238) +PLUM3 = RGB(205, 150, 205) +PLUM4 = RGB(139, 102, 139) +POWDERBLUE = RGB(176, 224, 230) +PURPLE = RGB(128, 0, 128) +PURPLE1 = RGB(155, 48, 255) +PURPLE2 = RGB(145, 44, 238) +PURPLE3 = RGB(125, 38, 205) +PURPLE4 = RGB(85, 26, 139) +RASPBERRY = RGB(135, 38, 87) +RAWSIENNA = RGB(199, 97, 20) +RED1 = RGB(255, 0, 0) +RED2 = RGB(238, 0, 0) +RED3 = RGB(205, 0, 0) +RED4 = RGB(139, 0, 0) +ROSYBROWN = RGB(188, 143, 143) +ROSYBROWN1 = RGB(255, 193, 193) +ROSYBROWN2 = RGB(238, 180, 180) +ROSYBROWN3 = RGB(205, 155, 155) +ROSYBROWN4 = RGB(139, 105, 105) +ROYALBLUE = RGB(65, 105, 225) +ROYALBLUE1 = RGB(72, 118, 255) +ROYALBLUE2 = RGB(67, 110, 238) +ROYALBLUE3 = RGB(58, 95, 205) +ROYALBLUE4 = RGB(39, 64, 139) +SALMON = RGB(250, 128, 114) +SALMON1 = RGB(255, 140, 105) +SALMON2 = RGB(238, 130, 98) +SALMON3 = RGB(205, 112, 84) +SALMON4 = RGB(139, 76, 57) +SANDYBROWN = RGB(244, 164, 96) +SAPGREEN = RGB(48, 128, 20) +SEAGREEN1 = RGB(84, 255, 159) +SEAGREEN2 = RGB(78, 238, 148) +SEAGREEN3 = RGB(67, 205, 128) +SEAGREEN4 = RGB(46, 139, 87) +SEASHELL1 = RGB(255, 245, 238) +SEASHELL2 = RGB(238, 229, 222) +SEASHELL3 = RGB(205, 197, 191) +SEASHELL4 = RGB(139, 134, 130) +SEPIA = RGB(94, 38, 18) +SGIBEET = RGB(142, 56, 142) +SGIBRIGHTGRAY = RGB(197, 193, 170) +SGICHARTREUSE = RGB(113, 198, 113) +SGIDARKGRAY = RGB(85, 85, 85) +SGIGRAY12 = RGB(30, 30, 30) +SGIGRAY16 = RGB(40, 40, 40) +SGIGRAY32 = RGB(81, 81, 81) +SGIGRAY36 = RGB(91, 91, 91) +SGIGRAY52 = RGB(132, 132, 132) +SGIGRAY56 = RGB(142, 142, 142) +SGIGRAY72 = RGB(183, 183, 183) +SGIGRAY76 = RGB(193, 193, 193) +SGIGRAY92 = RGB(234, 234, 234) +SGIGRAY96 = RGB(244, 244, 244) +SGILIGHTBLUE = RGB(125, 158, 192) +SGILIGHTGRAY = RGB(170, 170, 170) +SGIOLIVEDRAB = RGB(142, 142, 56) +SGISALMON = RGB(198, 113, 113) +SGISLATEBLUE = RGB(113, 113, 198) +SGITEAL = RGB(56, 142, 142) +SIENNA = RGB(160, 82, 45) +SIENNA1 = RGB(255, 130, 71) +SIENNA2 = RGB(238, 121, 66) +SIENNA3 = RGB(205, 104, 57) +SIENNA4 = RGB(139, 71, 38) +SILVER = RGB(192, 192, 192) +SKYBLUE = RGB(135, 206, 235) +SKYBLUE1 = RGB(135, 206, 255) +SKYBLUE2 = RGB(126, 192, 238) +SKYBLUE3 = RGB(108, 166, 205) +SKYBLUE4 = RGB(74, 112, 139) +SLATEBLUE = RGB(106, 90, 205) +SLATEBLUE1 = RGB(131, 111, 255) +SLATEBLUE2 = RGB(122, 103, 238) +SLATEBLUE3 = RGB(105, 89, 205) +SLATEBLUE4 = RGB(71, 60, 139) +SLATEGRAY = RGB(112, 128, 144) +SLATEGRAY1 = RGB(198, 226, 255) +SLATEGRAY2 = RGB(185, 211, 238) +SLATEGRAY3 = RGB(159, 182, 205) +SLATEGRAY4 = RGB(108, 123, 139) +SNOW1 = RGB(255, 250, 250) +SNOW2 = RGB(238, 233, 233) +SNOW3 = RGB(205, 201, 201) +SNOW4 = RGB(139, 137, 137) +SPRINGGREEN = RGB(0, 255, 127) +SPRINGGREEN1 = RGB(0, 238, 118) +SPRINGGREEN2 = RGB(0, 205, 102) +SPRINGGREEN3 = RGB(0, 139, 69) +STEELBLUE = RGB(70, 130, 180) +STEELBLUE1 = RGB(99, 184, 255) +STEELBLUE2 = RGB(92, 172, 238) +STEELBLUE3 = RGB(79, 148, 205) +STEELBLUE4 = RGB(54, 100, 139) +TAN = RGB(210, 180, 140) +TAN1 = RGB(255, 165, 79) +TAN2 = RGB(238, 154, 73) +TAN3 = RGB(205, 133, 63) +TAN4 = RGB(139, 90, 43) +TEAL = RGB(0, 128, 128) +THISTLE = RGB(216, 191, 216) +THISTLE1 = RGB(255, 225, 255) +THISTLE2 = RGB(238, 210, 238) +THISTLE3 = RGB(205, 181, 205) +THISTLE4 = RGB(139, 123, 139) +TOMATO1 = RGB(255, 99, 71) +TOMATO2 = RGB(238, 92, 66) +TOMATO3 = RGB(205, 79, 57) +TOMATO4 = RGB(139, 54, 38) +TURQUOISE = RGB(64, 224, 208) +TURQUOISE1 = RGB(0, 245, 255) +TURQUOISE2 = RGB(0, 229, 238) +TURQUOISE3 = RGB(0, 197, 205) +TURQUOISE4 = RGB(0, 134, 139) +TURQUOISEBLUE = RGB(0, 199, 140) +VIOLET = RGB(238, 130, 238) +VIOLETRED = RGB(208, 32, 144) +VIOLETRED1 = RGB(255, 62, 150) +VIOLETRED2 = RGB(238, 58, 140) +VIOLETRED3 = RGB(205, 50, 120) +VIOLETRED4 = RGB(139, 34, 82) +WARMGREY = RGB(128, 128, 105) +WHEAT = RGB(245, 222, 179) +WHEAT1 = RGB(255, 231, 186) +WHEAT2 = RGB(238, 216, 174) +WHEAT3 = RGB(205, 186, 150) +WHEAT4 = RGB(139, 126, 102) +WHITE = RGB(255, 255, 255) +WHITESMOKE = RGB(245, 245, 245) +WHITESMOKE = RGB(245, 245, 245) +YELLOW1 = RGB(255, 255, 0) +YELLOW2 = RGB(238, 238, 0) +YELLOW3 = RGB(205, 205, 0) +YELLOW4 = RGB(139, 139, 0) + +if __name__ == "__main__": + import time + ledcount = 9 + lc = LedControl('COM3') + while(True): + for led in range(ledcount): + lc.set_one(led, BLUEVIOLET) + lc.show() + time.sleep(0.1) + lc.show_all(BLACK) + time.sleep(0.1) \ No newline at end of file diff --git a/mediacontrol.py b/mediacontrol.py new file mode 100644 index 0000000..a3ac310 --- /dev/null +++ b/mediacontrol.py @@ -0,0 +1,119 @@ +import asyncio +from threading import Thread +import logging +import time + + +class SessionPlaybackStatus: + + Changing: 2 # Das Medium ändert sich. + Closed: 0 # Das Medium ist geschlossen. + Opened: 1 # Das Medium wird geöffnet. + Paused: 5 # Das Medium wird angehalten. + Playing: 4 # Die Medien werden abspielt. + Stopped: 3 # Das Medium wird beendet. + + + +class MediaControl(Thread): + + __init_handler = [] + __session_changed_handler = [] + __running = True + + + def run(self) -> None: + from winrt.windows.media.control import GlobalSystemMediaTransportControlsSessionManager as MediaManager, CurrentSessionChangedEventArgs + + self.MediaManager = MediaManager + # MediaManager.add_current_session_changed(Event) + for handler in self.__init_handler: + handler() + while self.__running: + time.sleep(1) + return super().run() + + def __init__(self) -> None: + super().__init__() + + def __current_session_changed(self, **kwargs): + print(kwargs) + for handler in self.__session_changed_handler: + handler() + + def add_init_handler(self, handler): + self.__init_handler.append(handler) + + # def add_session_changed_handler(self, handler): + # self.__session_changed_handler.append(handler) + + def play(self): + asyncio.run(self.__try_play()) + + def pause(self): + asyncio.run(self.__try_pause()) + + def get_media_info(self): + return asyncio.run(self.__get_media_info()) + + def get_playback_info(self): + return asyncio.run(self.__get_playback_info()) + + def stop(self): + self.__running = False + + async def __try_play(self): + logging.debug("__try_play") + sessions = await self.MediaManager.request_async() + current_session = sessions.get_current_session() + if current_session: + print("try_play!!!!!!!") + await current_session.try_play_async() + + async def __try_pause(self): + logging.debug("__try_pause") + sessions = await self.MediaManager.request_async() + current_session = sessions.get_current_session() + if current_session: + await current_session.try_pause_async() + + async def __get_media_info(self): + sessions = await self.MediaManager.request_async() + + current_session = sessions.get_current_session() + if current_session: # there needs to be a media session running + info = await current_session.try_get_media_properties_async() + + # song_attr[0] != '_' ignores system attributes + info_dict = {song_attr: info.__getattribute__(song_attr) for song_attr in dir(info) if song_attr[0] != '_'} + + # converts winrt vector to list + info_dict['genres'] = list(info_dict['genres']) + + return info_dict + + async def __get_playback_info(self): + sessions = await self.MediaManager.request_async() + + current_session = sessions.get_current_session() + if current_session: # there needs to be a media session running + info = current_session.get_playback_info() + + # song_attr[0] != '_' ignores system attributes + info_dict = {playback_attr: info.__getattribute__(playback_attr) for playback_attr in dir(info) if playback_attr[0] != '_'} + + # converts winrt vector to list + info_dict['controls'] = {control_attr: info.controls.__getattribute__(control_attr) for control_attr in dir(info.controls) if control_attr[0] != '_'} + + return info_dict + +if __name__ == "__main__": + def handle_init(): + print(mc.get_media_info()) + print(mc.get_playback_info()) + pass + + logging.getLogger().setLevel(logging.DEBUG) + mc = MediaControl() + mc.start() + mc.add_init_handler(handle_init) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 82e7afd..72ff95a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ winrt pywin32 -python3-tk \ No newline at end of file +python3-tk +pystray \ No newline at end of file diff --git a/resources.py b/resources.py deleted file mode 100644 index 426c548..0000000 --- a/resources.py +++ /dev/null @@ -1,100 +0,0 @@ - -class CLMgrMessage(): - CLMgrLineStateChangedMessage = 0 # state of at least one line has changed - CLMgrLineSelectionChangedMessage = 1 # line in focus has changed - CLMgrLineDetailsChangedMessage = 2 # details of at least one line have changed - CLMgrCallDetailsMessage = 4 # details of last call are available, post mortem for logging purpose - CLMgrServerDownMessage = 5 # server goes down, keep line manager, wait for ServerUp message - CLMgrServerUpMessage = 6 # server is up again, keep interfaces to line manger - CLMgrWaveDeviceChanged = 7 # speaker / micro has been switched on / off - CLMgrGroupCallNotificationMessage = 8 # notification about group call - CLMgrNumberOfLinesChangedMessage = 10 # the number of lines has changed - CLMgrClientShutDownRequest = 11 # Client Line Manager requests client to shutdown and release all interfaces - CLMgrLineStateChangedMessageEx = 28 # state of certain line has changed, lParam: LOWORD: line index of line that changed its state (starting with 0) HIWORD: new state of this line - - s = [ - "CLMgrLineStateChangedMessage", - "CLMgrLineSelectionChangedMessage", - "CLMgrLineDetailsChangedMessage", - "3", - "CLMgrCallDetailsMessage", - "CLMgrServerDownMessage", - "CLMgrServerUpMessage", - "CLMgrWaveDeviceChanged", - "CLMgrGroupCallNotificationMessage", - "9", - "CLMgrNumberOfLinesChangedMessage", - "CLMgrClientShutDownRequest", - "12", - "13", - "14", - "15", - "16", - "17", - "18", - "19", - "20", - "21", - "22", - "23", - "24", - "25", - "26", - "27", - "CLMgrLineStateChangedMessageEx" - ] - -class LineState(): - Inactive = 0 # line is inactive - HookOffInternal = 1 # off hook, internal dialtone - HookOffExternal = 2 # off hook, external dialtone - Ringing = 3 # incoming call, ringing - Dialing = 4 # outgoing call, we are dialing, no sound - Alerting = 5 # outgoing call, alerting = ringing on destination - Knocking = 6 # outgoing call, knocking = second call ringing on destination - Busy = 7 # outgoing call, destination is busy - Active = 8 # incoming / outgoing call, logical and physical connection is established - OnHold = 9 # incoming / outgoing call, logical connection is established, destination gets music on hold - ConferenceActive = 10 # incoming / outgoing conference, logical and physical connection is established - ConferenceOnHold = 11 # incoming / outgoing conference, logical connection is established, not physcically connected - Terminated = 12 # incoming / outgoing connection / call has been disconnected - Transferring = 13 # special LSOnHold, call is awaiting to be transferred, peer gets special music on hold - Disabled = 14 # special LSInactive: wrap up time - - s = [ - "Inactive", - "HookOffInternal", - "HookOffExternal", - "Ringing", - "Dialing", - "Alerting", - "Knocking", - "Busy", - "Active", - "OnHold", - "ConferenceActive", - "ConferenceOnHold", - "Terminated", - "Transferring", - "Disabled" - ] - -class DisconnectReason(): - Normal = 0 - Busy = 1 - Rejected = 2 - Cancelled = 3 - Transferred = 4 - JoinedConference = 5 - NoAnswer = 6 - TooLate = 7 - DirectCallImpossible = 8 - WrongNumber = 9 - Unreachable = 10 - CallDiverted = 11 - CallRoutingFailed = 12 - PermissionDenied = 13 - NetworkCongestion = 14 - NoChannelAvailable = 15 - NumberChanged = 16 - IncompatibleDestination = 17 \ No newline at end of file diff --git a/swyx-media-control.spec b/swyx-media-control.spec new file mode 100644 index 0000000..c7f6964 --- /dev/null +++ b/swyx-media-control.spec @@ -0,0 +1,44 @@ +# -*- mode: python ; coding: utf-8 -*- + + +block_cipher = None + + +a = Analysis(['gui.py'], + pathex=['X:\\Documents\\Projekte\\Simon\\python media controll'], + binaries=[], + datas=[('favicon.ico', '.')], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False) +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) + +exe = EXE(pyz, + a.scripts, + [], + exclude_binaries=True, + name='swyx-media-control', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=False, + disable_windowed_traceback=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None , icon='favicon.ico') +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=True, + upx_exclude=[], + name='swyx-media-control') diff --git a/swyxcontrol.py b/swyxcontrol.py new file mode 100644 index 0000000..f214515 --- /dev/null +++ b/swyxcontrol.py @@ -0,0 +1,221 @@ +import asyncio +from threading import Thread +import logging +import time +import logging +import pythoncom + +class CLMgrMessage(): + CLMgrLineStateChangedMessage = 0 # state of at least one line has changed + CLMgrLineSelectionChangedMessage = 1 # line in focus has changed + CLMgrLineDetailsChangedMessage = 2 # details of at least one line have changed + CLMgrCallDetailsMessage = 4 # details of last call are available, post mortem for logging purpose + CLMgrServerDownMessage = 5 # server goes down, keep line manager, wait for ServerUp message + CLMgrServerUpMessage = 6 # server is up again, keep interfaces to line manger + CLMgrWaveDeviceChanged = 7 # speaker / micro has been switched on / off + CLMgrGroupCallNotificationMessage = 8 # notification about group call + CLMgrNumberOfLinesChangedMessage = 10 # the number of lines has changed + CLMgrClientShutDownRequest = 11 # Client Line Manager requests client to shutdown and release all interfaces + CLMgrLineStateChangedMessageEx = 28 # state of certain line has changed, lParam: LOWORD: line index of line that changed its state (starting with 0) HIWORD: new state of this line + + s = [ + "CLMgrLineStateChangedMessage", + "CLMgrLineSelectionChangedMessage", + "CLMgrLineDetailsChangedMessage", + "3", + "CLMgrCallDetailsMessage", + "CLMgrServerDownMessage", + "CLMgrServerUpMessage", + "CLMgrWaveDeviceChanged", + "CLMgrGroupCallNotificationMessage", + "9", + "CLMgrNumberOfLinesChangedMessage", + "CLMgrClientShutDownRequest", + "12", + "13", + "14", + "15", + "16", + "17", + "18", + "19", + "20", + "21", + "22", + "23", + "24", + "25", + "26", + "27", + "CLMgrLineStateChangedMessageEx" + ] + +class LineState(): + Inactive = 0 # line is inactive + HookOffInternal = 1 # off hook, internal dialtone + HookOffExternal = 2 # off hook, external dialtone + Ringing = 3 # incoming call, ringing + Dialing = 4 # outgoing call, we are dialing, no sound + Alerting = 5 # outgoing call, alerting = ringing on destination + Knocking = 6 # outgoing call, knocking = second call ringing on destination + Busy = 7 # outgoing call, destination is busy + Active = 8 # incoming / outgoing call, logical and physical connection is established + OnHold = 9 # incoming / outgoing call, logical connection is established, destination gets music on hold + ConferenceActive = 10 # incoming / outgoing conference, logical and physical connection is established + ConferenceOnHold = 11 # incoming / outgoing conference, logical connection is established, not physcically connected + Terminated = 12 # incoming / outgoing connection / call has been disconnected + Transferring = 13 # special LSOnHold, call is awaiting to be transferred, peer gets special music on hold + Disabled = 14 # special LSInactive: wrap up time + + s = [ + "Inactive", + "HookOffInternal", + "HookOffExternal", + "Ringing", + "Dialing", + "Alerting", + "Knocking", + "Busy", + "Active", + "OnHold", + "ConferenceActive", + "ConferenceOnHold", + "Terminated", + "Transferring", + "Disabled" + ] + +class DisconnectReason(): + Normal = 0 + Busy = 1 + Rejected = 2 + Cancelled = 3 + Transferred = 4 + JoinedConference = 5 + NoAnswer = 6 + TooLate = 7 + DirectCallImpossible = 8 + WrongNumber = 9 + Unreachable = 10 + CallDiverted = 11 + CallRoutingFailed = 12 + PermissionDenied = 13 + NetworkCongestion = 14 + NoChannelAvailable = 15 + NumberChanged = 16 + IncompatibleDestination = 17 + +class SwyxControlException(Exception): + + def __init__(self, msg, *args: object) -> None: + self.msg = msg + super().__init__(*args) + +class SwyxControl(Thread): + + __running = True + phone_mgr = None + __line_state_changed_hander = [] + __phone_mgr_connected_hander = [] + all_lines_inactive = False + + class __PhoneLineEventHandler(): + outer: 'SwyxControl' = None + + def OnDispOnLineMgrNotification(self, msg, param, returns=""): + if not self.outer: + logging.debug("self.outer not set") + return True + logging.debug("msg: {}\tparam: {}\treturns: {}".format(msg, param,returns)) + if msg == CLMgrMessage.CLMgrLineStateChangedMessage: + self.outer._handle_line_state_changed(msg, param) + return True + + def setOuter(self, outer: 'SwyxControl'): + logging.debug("set outer class") + self.outer = outer + + def _handle_line_state_changed(self, msg, param): + line = self.phone_mgr.DispGetLine(param) + logging.debug("Line: {} {} / {}".format( + param, + LineState.s[line.DispState], + LineState.s[msg] + ) + ) + self.all_lines_inactive = self.__all_lines_inactive() + for handler in self.__line_state_changed_hander: + handler(line) + + def __init__(self) -> None: + super().__init__() + pass + + def __all_lines_inactive(self) -> bool: + '''returns true if all lines are inactive''' + for linenum in range(self.phone_mgr.DispNumberOfLines): + line = self.phone_mgr.DispGetLine(linenum) + if line.DispState != LineState.Inactive: + return False + return True + + def add_line_state_changed_handler(self, handler): + '''handler called when CLMgrMessage.CLMgrLineStateChangedMessage + is fired + + def handler(line): + if line.DispState == LineState.Ringing: + print("Ringing") + if line.DispState == LineState.HookOffInternal: + print("HookOffInternal") + sc.add_line_state_changed_handler(handler) + ''' + self.__line_state_changed_hander.append(handler) + + def add_phone_mgr_connected_handler(self, handler): + ''' + handler called when CLMgr.ClientLineMgr dispatched + + def handler(): + print("connected") + sc.add_phone_mgr_connected_handler(handler) + ''' + self.__phone_mgr_connected_hander.append(handler) + + def stop(self): + '''stops the running thread''' + self.__running = False + + def run(self): + # Initialize + import win32com.client + #pythoncom.CoInitialize(pythoncom.COINIT_MULTITHREADED) + #try: + self.phone_mgr = win32com.client.Dispatch("CLMgr.ClientLineMgr") + self.all_lines_inactive = self.__all_lines_inactive() + for handler in self.__phone_mgr_connected_hander: + handler() + # except Exception: + # raise SwyxControlException("Swyx Client not installed") + self.e = win32com.client.WithEvents("CLMgr.ClientLineMgr", self.__PhoneLineEventHandler) + self.e.setOuter(self) + while self.__running: + pythoncom.PumpWaitingMessages() + time.sleep(0.1) + +if __name__ == "__main__": + logging.getLogger().setLevel(logging.DEBUG) + def line_change_handler(line): + if line.DispState == LineState.Ringing: + print("Ringing") + if line.DispState == LineState.HookOffInternal: + print("HookOffInternal") + + def phone_mgr_connected_handler(): + print(sc.all_lines_inactive) + + sc = SwyxControl() + sc.add_line_state_changed_handler(line_change_handler) + sc.add_phone_mgr_connected_handler(phone_mgr_connected_handler) + sc.start() + pass \ No newline at end of file diff --git a/session_event_listener.py b/workstationcontrol.py similarity index 79% rename from session_event_listener.py rename to workstationcontrol.py index 7470e1f..56ac489 100644 --- a/session_event_listener.py +++ b/workstationcontrol.py @@ -1,9 +1,12 @@ from collections import defaultdict from enum import Enum +from threading import Thread import win32api import win32con import win32gui import win32ts +import time +import ctypes class SessionEvent(Enum): @@ -22,14 +25,32 @@ class SessionEvent(Enum): SESSION_REMOTE_CONTROL = 0x9 # WTS_SESSION_REMOTE_CONTROL -class WorkstationMonitor: - CLASS_NAME = "WorkstationMonitor" - WINDOW_TITLE = "Workstation Event Monitor" +class LockscreenMonitor(Thread): + CLASS_NAME = "LockscreenMonitor" + WINDOW_TITLE = "Lockscreen Event Monitor" + __running = True def __init__(self): self.window_handle = None self.event_handlers = defaultdict(list) self._register_listener() + super().__init__() + + def run(self) -> None: + while self.__running: + self.listen() + time.sleep(0.1) + + def stop(self): + self.__running = False + exit_code = 0 + win32gui.PostQuitMessage(exit_code) + + def screen_locked(self) -> bool: + if (ctypes.windll.User32.GetForegroundWindow() % 10 == 0): + return True + else: + return False def _register_listener(self): wc = win32gui.WNDCLASS() @@ -51,11 +72,7 @@ class WorkstationMonitor: win32ts.WTSRegisterSessionNotification(self.window_handle, scope) def listen(self): - win32gui.PumpMessages() - - def stop(self): - exit_code = 0 - win32gui.PostQuitMessage(exit_code) + win32gui.PumpWaitingMessages() def _window_procedure(self, window_handle: int, message: int, event_id, session_id): """ @@ -78,11 +95,11 @@ class WorkstationMonitor: for handler in self.event_handlers[SessionEvent.ANY]: handler(event) - def register_handler(self, event: SessionEvent, handler: callable): - self.event_handlers[event].append(handler) + def register_handler(self, handler: callable): + self.event_handlers[SessionEvent.ANY].append(handler) if __name__ == '__main__': - m = WorkstationMonitor() + m = LockscreenMonitor() m.register_handler(SessionEvent.ANY, handler=print) m.listen()