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