def test_listeners(): """`listeners()` returns a copied list of listeners.""" call_me = Mock() ee = BaseEventEmitter() @ee.on('event') def event_handler(): pass @ee.once('event') def once_handler(): pass listeners = ee.listeners('event') assert listeners[0] == event_handler assert listeners[1] == once_handler # listeners is a copy, you can't mutate the innards this way listeners[0] = call_me ee.emit('event') call_me.assert_not_called()
def test_properties_preserved(): """Test that the properties of decorated functions are preserved.""" call_me = Mock() call_me_also = Mock() ee = BaseEventEmitter() @ee.on('always') def always_event_handler(): """An event handler.""" call_me() @ee.once('once') def once_event_handler(): """Another event handler.""" call_me_also() assert always_event_handler.__doc__ == 'An event handler.' assert once_event_handler.__doc__ == 'Another event handler.' always_event_handler() call_me.assert_called_once() once_event_handler() call_me_also.assert_called_once() call_me_also.reset_mock() # Calling the event handler directly doesn't clear the handler ee.emit('once') call_me_also.assert_called_once()
def test_new_listener_event(): """The 'new_listener' event fires whenever a new listerner is added.""" call_me = Mock() ee = BaseEventEmitter() ee.on('new_listener', call_me) # Should fire new_listener event @ee.on('event') def event_handler(data): pass call_me.assert_called_once()
def test_emit_return(): """Emit returns True when handlers are registered on an event, and false otherwise. """ call_me = Mock() ee = BaseEventEmitter() # make sure emitting without a callback returns False assert not ee.emit('data') # add a callback ee.on('data')(call_me) # should return True now assert ee.emit('data')
def test_once_removal(): """Removal of once functions works """ ee = BaseEventEmitter() def once_handler(data): pass handle = ee.once('event', once_handler) assert handle == once_handler ee.remove_listener('event', handle) assert ee._events['event'] == OrderedDict()
def test_once(): """Test that `once()` method works propers. """ # very similar to "test_emit" but also makes sure that the event # gets removed afterwards call_me = Mock() ee = BaseEventEmitter() def once_handler(data): assert data == 'emitter is emitted!' call_me() # Tests to make sure that after event is emitted that it's gone. ee.once('event', once_handler) ee.emit('event', 'emitter is emitted!') call_me.assert_called_once() assert ee._events['event'] == OrderedDict()
def test_listener_removal_on_emit(): """Test that a listener removed during an emit is called inside the current emit cycle. """ call_me = Mock() ee = BaseEventEmitter() def should_remove(): ee.remove_listener('remove', call_me) ee.on('remove', should_remove) ee.on('remove', call_me) ee.emit('remove') call_me.assert_called_once() call_me.reset_mock() # Also test with the listeners added in the opposite order ee = BaseEventEmitter() ee.on('remove', call_me) ee.on('remove', should_remove) ee.emit('remove') call_me.assert_called_once()
class SpeakingEyeApp(Gtk.Application): def __init__( self, app_id: str, # TODO: replace with config_reader config: Dict, config_reader: ConfigReader, logger: logging.Logger, application_info_matcher: ApplicationInfoMatcher, activity_reader: ActivityReader, files_provider: FilesProvider) -> None: super().__init__() self.logger = logger self.config_reader = config_reader self.connection = Gio.bus_get_sync(Gio.BusType.SESSION, None) self.screen_saver_bus_names = self.__dbus_get_screen_saver_bus_names() self.files_provider = files_provider language = get(config, 'language') or 'en' self.localizator = Localizator(self.files_provider.i18n_dir, language) self.theme = get(config, 'theme') or 'dark' self.active_icon = self.get_icon(IconState.ACTIVE) self.disabled_icon = self.get_icon(IconState.DISABLED) self.tray_icon = TrayIcon(app_id, self.disabled_icon, self.create_tray_menu()) self.screen: Optional[Wnck.Screen] = None self.main_loop: Optional[GObject.MainLoop] = None self.name_changed_handler_id = None self.previous_active_window_name: Optional[str] = None self.previous_wm_class: Optional[str] = None self.overtime_timer = \ Timer('overtime_timer', handler=self.overtime_timer_handler, interval_ms=1 * 60 * 1000, repeat=True) self.break_timer = \ Timer('break_timer', handler=self.break_timer_handler, interval_ms=1 * 60 * 1000, repeat=True) self.distracting_app_timer = \ Timer('distracting_app_timer', handler=self.distracting_app_timer_handler, interval_ms=1 * 60 * 1000, repeat=True) self.event = BaseEventEmitter() self.event.on(ApplicationEvent.DISTRACTING_APP_OVERTIME.value, self.show_distracting_app_overtime_notification) self.start_time = datetime.now() self.is_work_time = False self.is_work_time_update_time = self.start_time self.last_overtime_notification: Optional[Notification] = None self.last_break_notification: Optional[Notification] = None self.last_lock_screen_time: Optional[datetime] = None self.is_lock_screen_activated = False self.has_distracting_app_overtime_notification_shown = False self.is_overtime_notification_allowed_to_show = True self.is_break_notification_allowed_to_show = True self.user_work_time_hour_limit = config_reader.get_work_time_limit() self.user_breaks_interval_hours = get( config, 'time_limits.breaks_interval_hours') or 3 self.user_distracting_apps_mins = config_reader.get_distracting_apps_mins( ) self.writer = ActivityWriter(self.files_provider) self.app_info_matcher = application_info_matcher today_raw_data_file_path = self.files_provider.get_raw_data_file_path( date.today()) self.holder = ActivityStatHolder( activity_reader.read(today_raw_data_file_path)) self.holder.initialize_stats(self.app_info_matcher.detailed_app_infos) self.holder.initialize_stats( self.app_info_matcher.distracting_app_infos) self.current_activity: Optional[Activity] = None self.writer.event.on(ActivityWriter.NEW_DAY_EVENT, self.on_new_day_started) self.logger.debug( f'Set user work time limit to [{self.user_work_time_hour_limit}] hours' ) self.logger.debug( f'Set user user breaks interval to [{self.user_breaks_interval_hours}] hours' ) Notify.init(app_id) self.__dbus_subscribe_to_screen_saver_signals() start_msg = self.localizator.get( 'notification.start', start_time=self.start_time.strftime("%H:%M:%S")) self.logger.debug(start_msg) self.new_notification(msg=start_msg).show() def __dbus_method_call(self, bus_name: str, object_path: str, interface_name: str, method_name: str) -> Any: if not self.connection: raise Exception('self.connection should be set!') no_parameters = None default_reply_type = None default_call_timeout = -1 not_cancellable = None raw_result = self.connection.call_sync( bus_name, object_path, interface_name, method_name, no_parameters, default_reply_type, Gio.DBusCallFlags.NONE, default_call_timeout, not_cancellable) if raw_result: result, = raw_result return result return None def __dbus_get_all_bus_names(self) -> List[str]: return self.__dbus_method_call('org.freedesktop.DBus', '/org/freedesktop/DBus', 'org.freedesktop.DBus', 'ListNames') def __dbus_lock_screen(self) -> None: for bus in self.screen_saver_bus_names: if bus == 'org.freedesktop.ScreenSaver': # NOTE: that server is just interface without implementation continue interface_name = f'/{bus.replace(".", "/")}' try: self.__dbus_method_call(bus, interface_name, bus, 'Lock') except Exception as e: self.logger.warning( f'Please ignore it if lock screen works well. Lock screen error: [{e}]' ) def __on_screen_saver_active_changed(self, connection: Gio.DBusConnection, sender_name: str, object_path: str, interface_name: str, signal_name: str, parameters: GLib.Variant) -> None: is_activated, = parameters self.is_lock_screen_activated = is_activated now = datetime.now() if self.is_lock_screen_activated: current_activity = Value.get_or_raise(self.current_activity, 'current_activity') self.previous_wm_class = current_activity.wm_class self.previous_active_window_name = current_activity.window_name wm_class = SpecialWmClass.LOCK_SCREEN.value window_name = '' else: if self.is_work_time: self.last_lock_screen_time = now wm_class = Value.get_or_raise(self.previous_wm_class, 'previous_wm_class') window_name = Value.get_or_raise(self.previous_active_window_name, 'previous_active_window_name') self.on_open_window(wm_class, window_name, now) def __dbus_get_screen_saver_bus_names(self) -> List[str]: bus_names = self.__dbus_get_all_bus_names() screen_saver_re = re.compile(r'^org\..*\.ScreenSaver$') return list(filter(screen_saver_re.match, bus_names)) def __dbus_subscribe_to_screen_saver_signals(self) -> None: if not self.screen_saver_bus_names: raise Exception('self.screen_saver_bus_names should be set!') for bus_name in self.screen_saver_bus_names: self.connection.signal_subscribe( None, bus_name, 'ActiveChanged', None, None, Gio.DBusSignalFlags.NONE, self.__on_screen_saver_active_changed) def do_activate(self) -> None: signal.signal(signal.SIGTERM, self.handle_sigterm) self.screen = Wnck.Screen.get_default() self.screen.connect('active-window-changed', self.on_active_window_changed) self.main_loop = GObject.MainLoop() self.overtime_timer.start() self.break_timer.start() self.distracting_app_timer.start() def on_active_window_changed(self, screen: Wnck.Screen, previously_active_window: Gtk.Window) -> None: now = datetime.now() # to prevent double handler connections if previously_active_window and self.name_changed_handler_id: previously_active_window.disconnect(self.name_changed_handler_id) active_window = screen.get_active_window() if active_window: self.name_changed_handler_id = active_window.connect( 'name-changed', self.on_name_changed) wm_class = get_wm_class(active_window.get_xid()) window_name = get_window_name(active_window) else: wm_class = SpecialWmClass.DESKTOP.value window_name = '' self.on_open_window(wm_class, window_name, now) def on_open_window(self, wm_class: str, window_name: str, now: datetime) -> None: new_activity = Activity(wm_class, window_name, now, self.is_work_time) self.__on_activity_changed(self.current_activity, new_activity) def on_name_changed(self, window: Wnck.Window) -> None: now = datetime.now() current_activity = Value.get_or_raise(self.current_activity, 'current_activity') window_name = get_window_name(window) new_activity = Activity(current_activity.wm_class, window_name, now, current_activity.is_work_time) self.__on_activity_changed(current_activity, new_activity) def __on_activity_changed(self, previous_activity: Optional[Activity], next_activity: Activity) -> None: now = datetime.now() if previous_activity is not None: previous_activity.set_end_time(now) self.writer.write(previous_activity) self.holder.update_stat(previous_activity) # NOTE: previous_activity is None when it is the first activity after starting previous_activity_app_name = \ '' if previous_activity is None else f'{previous_activity.wm_class}|{previous_activity.window_name}' self.logger.debug( f'{now}: {previous_activity_app_name} -> ' f'{next_activity.wm_class}|{next_activity.window_name}') self.app_info_matcher.set_if_matched(next_activity) self.current_activity = next_activity # NOTE: for reshowing notification when open distracting app once more time self.has_distracting_app_overtime_notification_shown = False def start_main_loop(self) -> None: try: self.main_loop.run() # type: ignore[union-attr] except KeyboardInterrupt: self.stop() def stop(self) -> None: now = datetime.now() finish_time = now work_time = finish_time - self.start_time work_time -= timedelta(microseconds=work_time.microseconds) finish_msg = self.localizator.get( 'notification.finish', finish_time=finish_time.strftime("%H:%M:%S")) work_time_msg = self.localizator.get('notification.work_time', work_time=work_time) self.logger.debug(f'{finish_msg}\n{work_time_msg}') self.logger.info( ' title | work_time | off_time') self.logger.info( '--------------------------------------------------------------') for holder_item in self.holder.items(): # cast untyped title and stat to str and ActivityStat title, stat = cast(Tuple[str, ActivityStat], holder_item) padded_title = title.rjust(19, ' ') padded_work_time = f'{stat.work_time}'.rjust(19, ' ') padded_off_time = f'{stat.off_time}'.rjust(20, ' ') self.logger.info( f'{padded_title} |{padded_work_time} |{padded_off_time}') self.new_notification(msg=f'{finish_msg}; {work_time_msg}').show() Notify.uninit() self.main_loop.quit() # type: ignore[union-attr] def on_close_item_click(self, menu_item: Gtk.MenuItem) -> None: self.stop() def on_open_report_item_click(self, menu_item: Gtk.MenuItem) -> None: browser = webbrowser.get( self.config_reader.get_report_server_browser()) host = self.config_reader.get_report_server_host() port = self.config_reader.get_report_server_port() url = f'http://{host}:{port}' browser.open_new_tab(url) def set_work_time_state(self, value: bool) -> None: if value == self.is_work_time: self.logger.debug( 'Trying to change is_work_time to the same value') return self.is_work_time = value current_activity = Value.get_or_raise(self.current_activity, 'current_activity') now = datetime.now() new_activity = Activity(current_activity.wm_class, current_activity.window_name, now, self.is_work_time) self.__on_activity_changed(current_activity, new_activity) self.is_work_time_update_time = now self.logger.debug(f'Set Work Time to [{self.is_work_time}]') icon = self.active_icon if self.is_work_time else self.disabled_icon self.tray_icon.set_icon_if_exist(icon) if self.is_work_time: self.last_lock_screen_time = now self.last_break_notification = None self.last_overtime_notification = None def on_work_state_checkbox_item_click(self, menu_item: Gtk.MenuItem) -> None: self.set_work_time_state(not self.is_work_time) def create_tray_menu(self) -> Gtk.Menu: menu = Gtk.Menu() work_state_checkbox_item = Gtk.CheckMenuItem( self.localizator.get('tray.work_time')) work_state_checkbox_item.connect( 'activate', self.on_work_state_checkbox_item_click) menu.append(work_state_checkbox_item) open_report_item = Gtk.MenuItem( self.localizator.get('tray.open_report')) open_report_item.connect('activate', self.on_open_report_item_click) menu.append(open_report_item) menu.append(Gtk.SeparatorMenuItem()) close_item = Gtk.MenuItem(self.localizator.get('tray.close')) close_item.connect('activate', self.on_close_item_click) menu.append(close_item) menu.show_all() return menu def on_overtime_notification_closed(self) -> None: self.is_overtime_notification_allowed_to_show = True def on_finish_work_action_clicked(self) -> None: self.set_work_time_state(False) def on_take_break_clicked(self) -> None: self.__dbus_lock_screen() def new_notification(self, msg: str, urgency: Optional[Notify.Urgency] = None, listen_closed_event: bool = False) -> Notification: # TODO: replace with NotificationFactory (Factory Pattern) return Notification('Speaking Eye', msg, self.active_icon, urgency, listen_closed_event) def show_overtime_notification(self) -> None: msg = self.localizator.get('notification.overtime.text', hours=self.user_work_time_hour_limit) notification = self.new_notification(msg, Notify.Urgency.CRITICAL, listen_closed_event=True) notification.add_buttons(( self.localizator.get( 'notification.overtime.left_button'), # finish_work self.localizator.get( 'notification.overtime.right_button') # remind_later )) notification.event.on(NotificationEvent.CLOSED.value, self.on_overtime_notification_closed) notification.event.on(NotificationEvent.LEFT_BUTTON_CLICKED.value, self.on_finish_work_action_clicked) notification.show() self.last_overtime_notification = notification self.is_overtime_notification_allowed_to_show = False def show_distracting_app_overtime_notification( self, title: str, total_time: timedelta) -> None: distracting_minutes = total_time.total_seconds() // 60 text = self.localizator.get_random('notification.distracting_texts', 10) emoji = choice(DISTRACTING_NOTIFICATION_EMOJIS) msg = self.localizator.get( 'notification.distracting', app_title=title, distracting_minutes=int(distracting_minutes), text=text, emoji=emoji) self.new_notification(msg).show() def __on_break_notification_closed(self) -> None: self.is_break_notification_allowed_to_show = True def show_break_notification(self) -> None: emoji = choice(BREAK_TIME_EMOJIS) msg = self.localizator.get('notification.break.text', emoji=emoji) notification = self.new_notification(msg, Notify.Urgency.CRITICAL, listen_closed_event=True) notification.add_buttons(( self.localizator.get( 'notification.break.left_button'), # take_break self.localizator.get( 'notification.break.right_button') # remind_later )) notification.event.on(NotificationEvent.CLOSED.value, self.__on_break_notification_closed) notification.event.on(NotificationEvent.LEFT_BUTTON_CLICKED.value, self.on_take_break_clicked) notification.show() self.last_break_notification = notification self.is_break_notification_allowed_to_show = False def on_new_day_started(self) -> None: open_new_file_msg = self.localizator.get('notification.new_day') self.logger.debug(open_new_file_msg) self.new_notification(msg=open_new_file_msg).show() self.set_work_time_state(False) def handle_sigterm(self, signal_number: int, frame: FrameType) -> None: self.stop() def __need_to_show_overtime_notification(self) -> bool: if not self.is_work_time: return False if self.is_lock_screen_activated: return False now = datetime.now() current_activity = Value.get_or_raise(self.current_activity, 'current_activity') seconds_in_current_activity = ( now - current_activity.start_time).total_seconds() total_work_time_seconds = seconds_in_current_activity + self.holder.total_work_time.total_seconds( ) is_overtime_started = total_work_time_seconds >= self.user_work_time_hour_limit * 60 * 60 if not is_overtime_started: return False if self.last_overtime_notification is None: return True if self.last_overtime_notification.last_shown is None: return True if not self.is_overtime_notification_allowed_to_show: return False seconds_from_last_notification = ( now - self.last_overtime_notification.last_shown).total_seconds() interval_seconds = 15 * 60 return seconds_from_last_notification >= interval_seconds def overtime_timer_handler(self) -> None: if not self.__need_to_show_overtime_notification(): return self.show_overtime_notification() def __need_to_show_break_notification(self) -> bool: if not self.is_work_time: return False if self.is_lock_screen_activated: return False if not self.is_break_notification_allowed_to_show: return False now = datetime.now() start_work_time = self.is_work_time_update_time last_break_time = self.last_lock_screen_time if self.last_lock_screen_time else start_work_time if (now - last_break_time ).total_seconds() < self.user_breaks_interval_hours * 60 * 60: return False last_break_reminder_time = Value.get_or_default( lambda: self.last_break_notification. last_shown, # type: ignore[union-attr] start_work_time) if (now - last_break_reminder_time).total_seconds() < 15 * 60: return False return True def break_timer_handler(self) -> None: if not self.__need_to_show_break_notification(): return self.show_break_notification() def distracting_app_timer_handler(self) -> None: # TODO: ⚡️ run this timer only if work started if not self.is_work_time: return if self.has_distracting_app_overtime_notification_shown: return current_activity = Value.get_or_raise(self.current_activity, 'current_activity') application_info = current_activity.application_info if application_info is None: # NOTE: It is None if detailed/distracting lists do not contain such activity # See more in ApplicationInfoMatcher.set_if_matched() return if not application_info.is_distracting: return now = datetime.now() current_stats = self.holder[application_info.title] total_distracting_time = now - current_activity.start_time + current_stats.work_time if total_distracting_time.total_seconds( ) < self.user_distracting_apps_mins * 60: return self.event.emit(ApplicationEvent.DISTRACTING_APP_OVERTIME.value, application_info.title, total_distracting_time) self.has_distracting_app_overtime_notification_shown = True def get_icon(self, icon_state: IconState) -> Path: if not self.theme: raise Exception('self.theme should be set!') path = self.files_provider.get_icon_file_path(self.theme, icon_state) if not path.exists(): self.logger.warning(f'Icon [{path}] not found') return path
def __init__(self, bus): BaseEventEmitter.__init__(self) self.path = '/' self.services = [] dbus.service.Object.__init__(self, bus, self.path)
def __init__(self, files_provider: FilesProvider) -> None: self.__files_provider = files_provider self.__current_file: Optional[TextIO] = None self.__current_file_path: Optional[Path] = None self.event = BaseEventEmitter()
def jpg_bytes(to_jpg): _, jpg = cv2.imencode('.jpg', to_jpg) return jpg.tobytes() vs = VideoCaptureAsync(0) time.sleep(2.0) someState = State() pt = ProcessingThread(vs, someState) wt = WebsocketThread(someState) sc = ServoControl(someState) ee = BaseEventEmitter() sendee = AsyncIOEventEmitter(loop=wt.sendLoop) @ee.on('command_received') def command_received(cmd): someState.handle_command(cmd) sc.handle_command(cmd) @ee.on('ball_found') def ball_found(): sc.update_ball_camera() app = Flask(__name__)
def test_uplift_emit(): call_me = Mock() base_ee = BaseEventEmitter() @base_ee.on('base_event') def base_handler(): call_me('base event on base emitter') @base_ee.on('shared_event') def shared_base_handler(): call_me('shared event on base emitter') uplifted_ee = uplift(UpliftedEventEmitter, base_ee) assert isinstance(uplifted_ee, UpliftedEventEmitter), 'Returns an uplifted emitter' @uplifted_ee.on('uplifted_event') def uplifted_handler(): call_me('uplifted event on uplifted emitter') @uplifted_ee.on('shared_event') def shared_uplifted_handler(): call_me('shared event on uplifted emitter') # Events on uplifted proxy correctly assert uplifted_ee.emit('base_event') assert uplifted_ee.emit('shared_event') assert uplifted_ee.emit('uplifted_event') call_me.assert_has_calls([ call('base event on base emitter'), call('shared event on uplifted emitter'), call('shared event on base emitter'), call('uplifted event on uplifted emitter') ]) call_me.reset_mock() # Events on underlying proxy correctly assert base_ee.emit('base_event') assert base_ee.emit('shared_event') assert base_ee.emit('uplifted_event') call_me.assert_has_calls([ call('base event on base emitter'), call('shared event on base emitter'), call('shared event on uplifted emitter'), call('uplifted event on uplifted emitter') ]) call_me.reset_mock() # Quick check for unwrap uplifted_ee.unwrap() with pytest.raises(AttributeError): getattr(uplifted_ee, 'unwrap') with pytest.raises(AttributeError): getattr(base_ee, 'unwrap') assert not uplifted_ee.emit('base_event') assert uplifted_ee.emit('shared_event') assert uplifted_ee.emit('uplifted_event') assert base_ee.emit('base_event') assert base_ee.emit('shared_event') assert not base_ee.emit('uplifted_event') call_me.assert_has_calls([ # No listener for base event on uplifted call('shared event on uplifted emitter'), call('uplifted event on uplifted emitter'), call('base event on base emitter'), call('shared event on base emitter') # No listener for uplifted event on uplifted ])
def test_listener_removal(): """Removing listeners removes the correct listener from an event.""" ee = BaseEventEmitter() # Some functions to pass to the EE def first(): return 1 ee.on('event', first) @ee.on('event') def second(): return 2 @ee.on('event') def third(): return 3 def fourth(): return 4 ee.on('event', fourth) assert ee._events['event'] == OrderedDict([ (first, first), (second, second), (third, third), (fourth, fourth) ]) ee.remove_listener('event', second) assert ee._events['event'] == OrderedDict([(first, first), (third, third), (fourth, fourth)]) ee.remove_listener('event', first) assert ee._events['event'] == OrderedDict([(third, third), (fourth, fourth)]) ee.remove_all_listeners('event') assert ee._events['event'] == OrderedDict()
def __init__( self, app_id: str, # TODO: replace with config_reader config: Dict, config_reader: ConfigReader, logger: logging.Logger, application_info_matcher: ApplicationInfoMatcher, activity_reader: ActivityReader, files_provider: FilesProvider) -> None: super().__init__() self.logger = logger self.config_reader = config_reader self.connection = Gio.bus_get_sync(Gio.BusType.SESSION, None) self.screen_saver_bus_names = self.__dbus_get_screen_saver_bus_names() self.files_provider = files_provider language = get(config, 'language') or 'en' self.localizator = Localizator(self.files_provider.i18n_dir, language) self.theme = get(config, 'theme') or 'dark' self.active_icon = self.get_icon(IconState.ACTIVE) self.disabled_icon = self.get_icon(IconState.DISABLED) self.tray_icon = TrayIcon(app_id, self.disabled_icon, self.create_tray_menu()) self.screen: Optional[Wnck.Screen] = None self.main_loop: Optional[GObject.MainLoop] = None self.name_changed_handler_id = None self.previous_active_window_name: Optional[str] = None self.previous_wm_class: Optional[str] = None self.overtime_timer = \ Timer('overtime_timer', handler=self.overtime_timer_handler, interval_ms=1 * 60 * 1000, repeat=True) self.break_timer = \ Timer('break_timer', handler=self.break_timer_handler, interval_ms=1 * 60 * 1000, repeat=True) self.distracting_app_timer = \ Timer('distracting_app_timer', handler=self.distracting_app_timer_handler, interval_ms=1 * 60 * 1000, repeat=True) self.event = BaseEventEmitter() self.event.on(ApplicationEvent.DISTRACTING_APP_OVERTIME.value, self.show_distracting_app_overtime_notification) self.start_time = datetime.now() self.is_work_time = False self.is_work_time_update_time = self.start_time self.last_overtime_notification: Optional[Notification] = None self.last_break_notification: Optional[Notification] = None self.last_lock_screen_time: Optional[datetime] = None self.is_lock_screen_activated = False self.has_distracting_app_overtime_notification_shown = False self.is_overtime_notification_allowed_to_show = True self.is_break_notification_allowed_to_show = True self.user_work_time_hour_limit = config_reader.get_work_time_limit() self.user_breaks_interval_hours = get( config, 'time_limits.breaks_interval_hours') or 3 self.user_distracting_apps_mins = config_reader.get_distracting_apps_mins( ) self.writer = ActivityWriter(self.files_provider) self.app_info_matcher = application_info_matcher today_raw_data_file_path = self.files_provider.get_raw_data_file_path( date.today()) self.holder = ActivityStatHolder( activity_reader.read(today_raw_data_file_path)) self.holder.initialize_stats(self.app_info_matcher.detailed_app_infos) self.holder.initialize_stats( self.app_info_matcher.distracting_app_infos) self.current_activity: Optional[Activity] = None self.writer.event.on(ActivityWriter.NEW_DAY_EVENT, self.on_new_day_started) self.logger.debug( f'Set user work time limit to [{self.user_work_time_hour_limit}] hours' ) self.logger.debug( f'Set user user breaks interval to [{self.user_breaks_interval_hours}] hours' ) Notify.init(app_id) self.__dbus_subscribe_to_screen_saver_signals() start_msg = self.localizator.get( 'notification.start', start_time=self.start_time.strftime("%H:%M:%S")) self.logger.debug(start_msg) self.new_notification(msg=start_msg).show()
from functools import partial from pyee import BaseEventEmitter event_emitter = BaseEventEmitter() class BasePlugin: def __init__(self, event_e): self._emitter = event_e self._listeners: list = [] self.getListenersList() def getListenersList(self): self._listeners = [ # means get all the methods that qualify as a listener method for item in dir(self) if callable((method:=getattr(self, item)) and not item == "getMethodsList" and not item.startswith("_") and item.endswith("_listener")) ] def subscribeEvent(self, event_name, listener): self._emitter.on(event_name, listener) event_emitter.remove_listener return partial(self._emitter.remove_listener, event_name, listener) def loadPlugin(self): raise NotImplementedError( "this method should be implemented by a subclass" )
def test_listener_removal(): """Removing listeners removes the correct listener from an event.""" ee = BaseEventEmitter() # Some functions to pass to the EE def first(): return 1 ee.on('event', first) @ee.on('event') def second(): return 2 @ee.on('event') def third(): return 3 def fourth(): return 4 ee.on('event', fourth) assert ee._events['event'] == OrderedDict([ (first, first), (second, second), (third, third), (fourth, fourth) ]) ee.remove_listener('event', second) assert ee._events['event'] == OrderedDict([ (first, first), (third, third), (fourth, fourth) ]) ee.remove_listener('event', first) assert ee._events['event'] == OrderedDict([ (third, third), (fourth, fourth) ]) ee.remove_all_listeners('event') assert ee._events['event'] == OrderedDict()
class Frame(ChannelOwner): def __init__(self, parent: ChannelOwner, type: str, guid: str, initializer: Dict) -> None: super().__init__(parent, type, guid, initializer) self._parent_frame = from_nullable_channel( initializer.get("parentFrame")) if self._parent_frame: self._parent_frame._child_frames.append(self) self._name = initializer["name"] self._url = initializer["url"] self._detached = False self._child_frames: List[Frame] = [] self._page: "Page" self._load_states: Set[str] = set(initializer["loadStates"]) self._event_emitter = BaseEventEmitter() self._channel.on( "loadstate", lambda params: self._on_load_state(params.get("add"), params.get("remove")), ) self._channel.on( "navigated", lambda params: self._on_frame_navigated(params), ) def _on_load_state(self, add: DocumentLoadState = None, remove: DocumentLoadState = None) -> None: if add: self._load_states.add(add) self._event_emitter.emit("loadstate", add) elif remove and remove in self._load_states: self._load_states.remove(remove) def _on_frame_navigated(self, event: FrameNavigatedEvent) -> None: self._url = event["url"] self._name = event["name"] self._event_emitter.emit("navigated", event) if "error" not in event and hasattr(self, "_page") and self._page: self._page.emit("framenavigated", self) async def goto( self, url: str, timeout: int = None, waitUntil: DocumentLoadState = None, referer: str = None, ) -> Optional[Response]: return cast( Optional[Response], from_nullable_channel(await self._channel.send( "goto", locals_to_params(locals()))), ) def _setup_navigation_wait_helper(self, timeout: int = None) -> WaitHelper: wait_helper = WaitHelper(self._loop) wait_helper.reject_on_event( self._page, "close", Error("Navigation failed because page was closed!")) wait_helper.reject_on_event( self._page, "crash", Error("Navigation failed because page crashed!")) wait_helper.reject_on_event( self._page, "framedetached", Error("Navigating frame was detached!"), lambda frame: frame == self, ) if timeout is None: timeout = self._page._timeout_settings.navigation_timeout() wait_helper.reject_on_timeout(timeout, f"Timeout {timeout}ms exceeded.") return wait_helper async def waitForNavigation( self, url: URLMatch = None, waitUntil: DocumentLoadState = None, timeout: int = None, ) -> Optional[Response]: if not waitUntil: waitUntil = "load" if timeout is None: timeout = self._page._timeout_settings.navigation_timeout() deadline = monotonic_time() + timeout wait_helper = self._setup_navigation_wait_helper(timeout) matcher = URLMatcher(url) if url else None def predicate(event: Any) -> bool: # Any failed navigation results in a rejection. if event.get("error"): return True return not matcher or matcher.matches(event["url"]) event = await wait_helper.wait_for_event( self._event_emitter, "navigated", predicate=predicate, ) if "error" in event: raise Error(event["error"]) if waitUntil not in self._load_states: timeout = deadline - monotonic_time() if timeout > 0: await self.waitForLoadState(state=waitUntil, timeout=timeout) if "newDocument" in event and "request" in event["newDocument"]: request = from_channel(event["newDocument"]["request"]) return await request.response() return None async def waitForLoadState(self, state: DocumentLoadState = None, timeout: int = None) -> None: if not state: state = "load" if state not in ("load", "domcontentloaded", "networkidle"): raise Error( "state: expected one of (load|domcontentloaded|networkidle)") if state in self._load_states: return wait_helper = self._setup_navigation_wait_helper(timeout) await wait_helper.wait_for_event(self._event_emitter, "loadstate", lambda s: s == state) async def frameElement(self) -> ElementHandle: return from_channel(await self._channel.send("frameElement")) async def evaluate(self, expression: str, arg: Serializable = None, force_expr: bool = None) -> Any: if not is_function_body(expression): force_expr = True return parse_result(await self._channel.send( "evaluateExpression", dict( expression=expression, isFunction=not (force_expr), arg=serialize_argument(arg), ), )) async def evaluateHandle(self, expression: str, arg: Serializable = None, force_expr: bool = None) -> JSHandle: if not is_function_body(expression): force_expr = True return from_channel(await self._channel.send( "evaluateExpressionHandle", dict( expression=expression, isFunction=not (force_expr), arg=serialize_argument(arg), ), )) async def querySelector(self, selector: str) -> Optional[ElementHandle]: return from_nullable_channel(await self._channel.send( "querySelector", dict(selector=selector))) async def querySelectorAll(self, selector: str) -> List[ElementHandle]: return list( map( cast(ElementHandle, from_channel), await self._channel.send("querySelectorAll", dict(selector=selector)), )) async def waitForSelector( self, selector: str, timeout: int = None, state: Literal["attached", "detached", "visible", "hidden"] = None, ) -> Optional[ElementHandle]: return from_nullable_channel(await self._channel.send( "waitForSelector", locals_to_params(locals()))) async def dispatchEvent(self, selector: str, type: str, eventInit: Dict = None, timeout: int = None) -> None: await self._channel.send( "dispatchEvent", dict(selector=selector, type=type, eventInit=serialize_argument(eventInit)), ) async def evalOnSelector( self, selector: str, expression: str, arg: Serializable = None, force_expr: bool = None, ) -> Any: return parse_result(await self._channel.send( "evalOnSelector", dict( selector=selector, expression=expression, isFunction=not (force_expr), arg=serialize_argument(arg), ), )) async def evalOnSelectorAll( self, selector: str, expression: str, arg: Serializable = None, force_expr: bool = None, ) -> Any: return parse_result(await self._channel.send( "evalOnSelectorAll", dict( selector=selector, expression=expression, isFunction=not (force_expr), arg=serialize_argument(arg), ), )) async def content(self) -> str: return await self._channel.send("content") async def setContent( self, html: str, timeout: int = None, waitUntil: DocumentLoadState = None, ) -> None: await self._channel.send("setContent", locals_to_params(locals())) @property def name(self) -> str: return self._name or "" @property def url(self) -> str: return self._url or "" @property def parentFrame(self) -> Optional["Frame"]: return self._parent_frame @property def childFrames(self) -> List["Frame"]: return self._child_frames.copy() def isDetached(self) -> bool: return self._detached async def addScriptTag( self, url: str = None, path: str = None, content: str = None, type: str = None, ) -> ElementHandle: params = locals_to_params(locals()) if path: with open(path, "r") as file: params["content"] = file.read() + "\n//# sourceURL=" + str( Path(path)) del params["path"] return from_channel(await self._channel.send("addScriptTag", params)) async def addStyleTag(self, url: str = None, path: str = None, content: str = None) -> ElementHandle: params = locals_to_params(locals()) if path: with open(path, "r") as file: params["content"] = (file.read() + "\n/*# sourceURL=" + str(Path(path)) + "*/") del params["path"] return from_channel(await self._channel.send("addStyleTag", params)) async def click( self, selector: str, modifiers: List[KeyboardModifier] = None, position: MousePosition = None, delay: int = None, button: MouseButton = None, clickCount: int = None, timeout: int = None, force: bool = None, noWaitAfter: bool = None, ) -> None: await self._channel.send("click", locals_to_params(locals())) async def dblclick( self, selector: str, modifiers: List[KeyboardModifier] = None, position: MousePosition = None, delay: int = None, button: MouseButton = None, timeout: int = None, force: bool = None, ) -> None: await self._channel.send("dblclick", locals_to_params(locals())) async def fill(self, selector: str, value: str, timeout: int = None, noWaitAfter: bool = None) -> None: await self._channel.send("fill", locals_to_params(locals())) async def focus(self, selector: str, timeout: int = None) -> None: await self._channel.send("focus", locals_to_params(locals())) async def textContent(self, selector: str, timeout: int = None) -> Optional[str]: return await self._channel.send("textContent", locals_to_params(locals())) async def innerText(self, selector: str, timeout: int = None) -> str: return await self._channel.send("innerText", locals_to_params(locals())) async def innerHTML(self, selector: str, timeout: int = None) -> str: return await self._channel.send("innerHTML", locals_to_params(locals())) async def getAttribute(self, selector: str, name: str, timeout: int = None) -> Optional[str]: return await self._channel.send("getAttribute", locals_to_params(locals())) async def hover( self, selector: str, modifiers: List[KeyboardModifier] = None, position: MousePosition = None, timeout: int = None, force: bool = None, ) -> None: await self._channel.send("hover", locals_to_params(locals())) async def selectOption( self, selector: str, values: ValuesToSelect, timeout: int = None, noWaitAfter: bool = None, ) -> List[str]: params = locals_to_params(locals()) if "values" in params: values = params.pop("values") params = dict(**params, **convert_select_option_values(values)) return await self._channel.send("selectOption", params) async def setInputFiles( self, selector: str, files: Union[str, Path, FilePayload, List[str], List[Path], List[FilePayload]], timeout: int = None, noWaitAfter: bool = None, ) -> None: params = locals_to_params(locals()) params["files"] = normalize_file_payloads(files) await self._channel.send("setInputFiles", params) async def type( self, selector: str, text: str, delay: int = None, timeout: int = None, noWaitAfter: bool = None, ) -> None: await self._channel.send("type", locals_to_params(locals())) async def press( self, selector: str, key: str, delay: int = None, timeout: int = None, noWaitAfter: bool = None, ) -> None: await self._channel.send("press", locals_to_params(locals())) async def check( self, selector: str, timeout: int = None, force: bool = None, noWaitAfter: bool = None, ) -> None: await self._channel.send("check", locals_to_params(locals())) async def uncheck( self, selector: str, timeout: int = None, force: bool = None, noWaitAfter: bool = None, ) -> None: await self._channel.send("uncheck", locals_to_params(locals())) async def waitForTimeout(self, timeout: int) -> None: await self._connection._loop.create_task(asyncio.sleep(timeout / 1000)) async def waitForFunction( self, expression: str, arg: Serializable = None, force_expr: bool = None, timeout: int = None, polling: Union[int, Literal["raf"]] = None, ) -> JSHandle: if not is_function_body(expression): force_expr = True params = locals_to_params(locals()) params["isFunction"] = not (force_expr) params["arg"] = serialize_argument(arg) return from_channel(await self._channel.send("waitForFunction", params)) async def title(self) -> str: return await self._channel.send("title") def expect_load_state( self, state: DocumentLoadState = None, timeout: int = None, ) -> EventContextManagerImpl[Optional[Response]]: return EventContextManagerImpl(self.waitForLoadState(state, timeout)) def expect_navigation( self, url: URLMatch = None, waitUntil: DocumentLoadState = None, timeout: int = None, ) -> EventContextManagerImpl[Optional[Response]]: return EventContextManagerImpl( self.waitForNavigation(url, waitUntil, timeout))