class EventManager(object): """ Event manager that runs event loop and calls event handlers. """ def __init__(self): """ Constructor. :return: None. """ # Create hook manager self._hook_manager = HookManager() # Add attributes `mouse_hook` and `keyboard_hook`. # Without the two attributes, the hook manager's method `__del__` # will raise AttributeError if its methods `HookKeyboard` and # `HookMouse` have not been called. self._hook_manager.mouse_hook = False self._hook_manager.keyboard_hook = False def start_event_loop(self): """ Start event loop. This method will not return until the event loop is stopped by \ calling :paramref:`stop_event_loop`. :return: None. """ # Start hooking key events self._hook_manager.HookKeyboard() # Start hooking mouse events self._hook_manager.HookMouse() # Create MSG structure msg = MSG() # Run event loop GetMessageW(byref(msg), 0, 0, 0) # Stop hooking key events self._hook_manager.UnhookKeyboard() # Stop hooking mouse events self._hook_manager.UnhookMouse() def stop_event_loop(self): """ Stop event loop. :return: None. """ # Post a WM_QUIT message to this thread's message queue PostQuitMessage(0) # Map event handler type to handler attribute name _EVENT_HANDLER_TYPE_TO_ATTR_NAME = { 'KeyDown': 'KeyDown', 'KeyUp': 'KeyUp', 'MouseDown': 'MouseAllButtonsDown', 'MouseUp': 'MouseAllButtonsUp', 'MouseMove': 'MouseMove', 'MouseWheel': 'MouseWheel', } def add_handler(self, handler_type, handler): """ Add event handler. :param handler_type: Event handler type. Allowed values: - 'KeyDown' - 'KeyUp' - 'MouseDown' - 'MouseUp' - 'MouseMove' - 'MouseWheel' :param handler: Event handler. :return: None. """ # Get handler attribute name attr_name = self._EVENT_HANDLER_TYPE_TO_ATTR_NAME.get( handler_type, None) # If handler attribute name is not found, # it means given handler type is not valid. if attr_name is None: # Get error message msg = 'Error: Invalid handler type: {0}'.format(repr(handler_type)) # Raise error raise ValueError(msg) # If handler attribute name is found. # Set the handler attribute on the hook manager setattr(self._hook_manager, attr_name, handler) def remove_handlers(self): """ Remove all event handlers. :return: None. """ # Set handler attributes on the hook manager be None self._hook_manager.KeyDown = None self._hook_manager.KeyUp = None self._hook_manager.MouseAllButtonsDown = None self._hook_manager.MouseAllButtonsUp = None self._hook_manager.MouseMove = None self._hook_manager.MouseWheel = None
class Master(QtWidgets.QDialog): def __init__(self, parent=None): super(Master, self).__init__(parent) self.old_volume = get_volume() self.program_searching = False self.fading = False self.initUI() def initUI(self): self.setWindowFlags(QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowCloseButtonHint) ba = QtCore.QByteArray.fromBase64(icon_data) pixmap = QtGui.QPixmap() pixmap.loadFromData(ba, 'PNG') icon = QtGui.QIcon() icon.addPixmap(pixmap) self.setWindowIcon(icon) self.load_settings() xspacer = QtWidgets.QSpacerItem(10, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) big_xspacer = QtWidgets.QSpacerItem(0, 40, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.v_layout = QtWidgets.QVBoxLayout(self) self.pretext_layout = QtWidgets.QHBoxLayout() self.pretext_label = QtWidgets.QLabel("Pretext:") self.pretext_input = QtWidgets.QLineEdit(self.settings["pretext"]) self.pretext_input.textChanged.connect(self.set_pretext) self.gotime_label = QtWidgets.QLabel("Go Time:") self.hour_input = QtWidgets.QComboBox() hours = [ '23', '22', '21', '20', '19', '18', '17', '16', '15', '14', '13', '12', '11', '10', '09', '08', '07', '06', '05', '04', '03', '02', '01', '00' ] self.hour_input.insertItems(0, hours) self.hour_input.setCurrentIndex( hours.index(self.settings['gotime'].split(':')[0])) self.hour_input.currentIndexChanged.connect(self.set_gotime) self.colon_label = QtWidgets.QLabel(":") self.colon_label.setFixedWidth(3) self.minute_input = QtWidgets.QComboBox() minutes = [ '59', '58', '57', '56', '55', '54', '53', '52', '51', '50', '49', '48', '47', '46', '45', '44', '43', '42', '41', '40', '39', '38', '37', '36', '35', '34', '33', '32', '31', '30', '29', '28', '27', '26', '25', '24', '23', '22', '21', '20', '19', '18', '17', '16', '15', '14', '13', '12', '11', '10', '09', '08', '07', '06', '05', '04', '03', '02', '01', '00' ] self.minute_input.insertItems(0, minutes) self.minute_input.setCurrentIndex( minutes.index(self.settings['gotime'].split(':')[1])) self.minute_input.currentIndexChanged.connect(self.set_gotime) self.pretext_layout.addWidget(self.pretext_label) self.pretext_layout.addWidget(self.pretext_input) self.pretext_layout.addWidget(self.gotime_label) self.pretext_layout.addWidget(self.hour_input) self.pretext_layout.addWidget(self.colon_label) self.pretext_layout.addWidget(self.minute_input) self.pretext_layout.addItem(xspacer) self.v_layout.addLayout(self.pretext_layout) self.settings_layout = QtWidgets.QHBoxLayout() self.font_size_label = QtWidgets.QLabel("Font Size:") font_sizes = [ "20", "24", "28", "30", "34", "38", "40", "44", "48", "52", "56", "60", "64", "68", "72" ] self.font_size_combobox = QtWidgets.QComboBox() self.font_size_combobox.insertItems(0, font_sizes) self.font_size_combobox.setCurrentIndex( font_sizes.index(self.settings['fontsize'])) self.font_size_combobox.currentIndexChanged.connect(self.set_font) self.font_type_label = QtWidgets.QLabel("Font Type:") self.font_type_combobox = QtWidgets.QFontComboBox() self.font_type_combobox.setCurrentFont( QtGui.QFont(self.settings['fonttype'])) self.font_type_combobox.currentIndexChanged.connect(self.set_font) self.font_type_combobox.setFixedWidth(128) self.fg_color_label = QtWidgets.QLabel("Fg:") self.fg_color_button = QtWidgets.QPushButton() self.fg_color_button.setFixedWidth(32) self.fg_color_button.clicked.connect(self.choose_fg_color) self.fg_color_button.setStyleSheet("background-color: " + self.settings['fgcolor'] + ';') self.fg_dialog = QtWidgets.QColorDialog() self.bg_color_label = QtWidgets.QLabel("Bg:") self.bg_color_button = QtWidgets.QPushButton() self.bg_color_button.setFixedWidth(32) self.bg_color_button.clicked.connect(self.choose_bg_color) self.bg_color_button.setStyleSheet("background-color: " + self.settings['bgcolor'] + ';') self.bg_dialog = QtWidgets.QColorDialog() self.settings_layout.addWidget(self.font_size_label) self.settings_layout.addWidget(self.font_size_combobox) self.settings_layout.addItem(xspacer) self.settings_layout.addWidget(self.font_type_label) self.settings_layout.addWidget(self.font_type_combobox) self.settings_layout.addItem(xspacer) self.settings_layout.addWidget(self.fg_color_label) self.settings_layout.addWidget(self.fg_color_button) self.settings_layout.addWidget(self.bg_color_label) self.settings_layout.addWidget(self.bg_color_button) self.settings_layout.addItem(big_xspacer) self.v_layout.addLayout(self.settings_layout) self.fade_layout = QtWidgets.QHBoxLayout() self.fade_label = QtWidgets.QLabel("Fade Audio and Close Player:") self.fade_checkbox = QtWidgets.QCheckBox() self.fade_checkbox.setChecked(bool(self.settings['fadeaudio'])) self.fade_checkbox.setEnabled(False) self.close_label = QtWidgets.QLabel("Player:") self.close_button = QtWidgets.QPushButton("") self.close_button.setFixedWidth(200) self.close_button.clicked.connect(self.start_program_search) self.fade_layout.addWidget(self.fade_label) self.fade_layout.addWidget(self.fade_checkbox) self.fade_layout.addItem(xspacer) self.fade_layout.addWidget(self.close_label) self.fade_layout.addWidget(self.close_button) self.fade_layout.addItem(big_xspacer) self.v_layout.addLayout(self.fade_layout) self.toggle_timer_button = QtWidgets.QPushButton("Toggle Timer") self.toggle_timer_button.clicked.connect(self.toggle_timer) self.v_layout.addWidget(self.toggle_timer_button) self.hookman = HookManager() self.hookman.HookMouse() if os.name == 'posix': self.hookman.buttonpressevent = self.mousedown self.hookman.start() elif os.name == 'nt': self.hookman.MouseLeftDown = self.mousedown self.timer = Timer(app) self.timer.prefix = self.pretext_input.text() self.timer.gotime = self.hour_input.currentText( ) + ':' + self.minute_input.currentText() + ':00' font = self.font_type_combobox.currentText() size = int(self.font_size_combobox.currentText()) self.timer.label.setFont(QtGui.QFont(font, size, QtGui.QFont.Bold)) self.timer.label.setStyleSheet("color: " + self.settings['fgcolor'] + ';') self.timer.setStyleSheet("background-color: " + self.settings['bgcolor'] + ';') self.timer.above_ten_signal.connect(self.get_volume) self.timer.below_ten_signal.connect(self.start_fade_thread) self.timer.zero_signal.connect(self.reset_volume) self.resize_thread = ResizeThread() self.resize_thread.reset_timer_signal.connect(self.resize_timer) self.fade_thread = FadeThread() self.player_getter = PlayerGetterThread() self.player_getter.found_signal.connect(self.set_player) def start_program_search(self): self.program_searching = True self.close_button.setText("Click Player Window") def mousedown(self, event): if self.program_searching: self.player_getter.start() if os.name == 'nt': return True def set_player(self): program = get_active_process() self.close_button.setText(program) self.program_searching = False self.fade_checkbox.setEnabled(True) def set_gotime(self, event): self.timer.gotime = self.hour_input.currentText( ) + ':' + self.minute_input.currentText() + ':00' self.timer.update_time() def set_pretext(self, event): self.timer.prefix = self.pretext_input.text() self.timer.update_time() self.resize_thread.start() def set_font(self, event): font = self.font_type_combobox.currentText() size = int(self.font_size_combobox.currentText()) self.timer.label.setFont(QtGui.QFont(font, size, QtGui.QFont.Bold)) self.resize_thread.start() def resize_timer(self): self.timer.resize(0, 0) def choose_fg_color(self, event): self.setEnabled(False) color = self.fg_dialog.getColor(QtGui.QColor(self.settings['fgcolor'])) if color.isValid(): self.fg_color_button.setStyleSheet("background-color: " + color.name() + ";") self.settings['fgcolor'] = color.name() self.timer.label.setStyleSheet("color: " + self.settings['fgcolor'] + ';') self.setEnabled(True) def choose_bg_color(self, event): self.setEnabled(False) color = self.bg_dialog.getColor(QtGui.QColor(self.settings['bgcolor'])) if color.isValid(): self.bg_color_button.setStyleSheet("background-color: " + color.name() + ";") self.settings['bgcolor'] = color.name() self.timer.setStyleSheet("background-color: " + self.settings['bgcolor'] + ';') self.setEnabled(True) def save_settings(self): parts = [ "pretext: " + self.pretext_input.text(), "gotime: " + self.hour_input.currentText() + ':' + self.minute_input.currentText(), "fontsize: " + self.font_size_combobox.currentText(), "fonttype: " + self.font_type_combobox.currentText(), "fgcolor: " + self.settings['fgcolor'], "bgcolor: " + self.settings['bgcolor'], "fadeaudio: " + str(self.fade_checkbox.isChecked()), "player: " + str(self.close_button.text()) ] with open("pt_settings.ini", "w") as f: f.write('\n'.join(parts)) def load_settings(self): self.settings = { "pretext": "Lecture Start:", "gotime": "12:00", "fontsize": "24", "fonttype": "Arial", "fgcolor": "black", "bgcolor": "white", "fadeaudio": "True", "player": "vlc" } try: with open("pt_settings.ini", 'r') as f: text = f.read() lines = text.split('\n') for line in lines: key = line.split(': ')[0].strip() val = line.split(': ')[-1].strip() self.settings[key] = val except FileNotFoundError: pass def toggle_timer(self, event): if self.timer.isVisible(): self.timer.hide() else: self.timer.show() def get_volume(self): self.old_volume = get_volume() if self.fading: self.fade_thread.exiting = True self.fading = False def start_fade_thread(self): if self.timer.isVisible( ) and self.fading == False and self.fade_checkbox.isChecked( ) and self.close_button.text().strip() != '': self.fade_thread.exiting = False self.fade_thread.start() self.fading = True def reset_volume(self): if self.fade_checkbox.isChecked() and self.timer.isVisible( ) and self.close_button.text().strip() != '': self.fading = False self.fade_thread.exiting = True player = self.close_button.text() if player.strip() != '': if os.name == 'posix': subprocess.call(['killall', player]) elif os.name == "nt": no_window = 0x08000000 subprocess.call('taskkill /F /IM ' + player, creationflags=no_window) set_volume(self.old_volume) def closeEvent(self, event): self.save_settings() self.timer.close() if os.name == 'nt': self.hookman.UnhookMouse()
class WindowsRecorder(BaseRecorder): KBFLAG_CTRL = 0x01 KBFLAG_ALT = 0x02 KBFLAG_SHIFT = 0x04 KBFLAG_CAPS = 0x08 default_radius = 25 capture_interval = 0.05 capture_maxnum = 30 def __init__(self, device=None): self.watched_hwnds = set() super(WindowsRecorder, self).__init__(device) self.kbflag = 0 self.hm = HookManager() self.hm.MouseAllButtons = self._hook_on_mouse self.hm.KeyAll = self._hook_on_keyboard def attach(self, device): if self.device is not None: print "Warning: already attached to a device." if device is not self.device: self.detach() handle = device.hwnd def callback(hwnd, extra): extra.add(hwnd) return True self.watched_hwnds.add(handle) try: # EnumChildWindows may crash for windows have no any child. # refs: https://mail.python.org/pipermail/python-win32/2005-March/003042.html win32gui.EnumChildWindows(handle, callback, self.watched_hwnds) except pywintypes.error: pass self.device = device print "attach to device", device def detach(self): print "detach from device", self.device self.device = None self.watched_hwnds = set() def hook(self): self.hm.HookMouse() self.hm.HookKeyboard() def unhook(self): self.hm.UnhookMouse() self.hm.UnhookKeyboard() def _hook_on_mouse(self, event): if self.device is None: return True if event.Window not in self.watched_hwnds: return True if event.Message == HookConstants.WM_LBUTTONUP: x, y = self.device.norm_position(event.Position) # ignore the clicks outside the rect if the window has a frame. if x < 0 or y < 0: return True self.on_click((x, y)) return True def _hook_on_keyboard(self, event): if self.device is None: return True if event.Window not in self.watched_hwnds: return True print "on_keyboard", event.MessageName, event.Key, repr( event.Ascii), event.KeyID, event.ScanCode, print event.flags, event.Extended, event.Injected, event.Alt, event.Transition return True
class CoordHandler: def _add_motionctrl_headline(self): '''Convenience function for adding label headline to the coordinate list''' # Creating widgets lbl_empty = lambda: wx.StaticText(self.coords_settings, label="") lbl_x = wx.StaticText(self.coords_settings, label="X") lbl_y = wx.StaticText(self.coords_settings, label="Y") # Changing font font = lbl_x.GetFont() font.SetWeight(wx.BOLD) lbl_x.SetFont(font) lbl_y.SetFont(font) # Adding widgets to sizer self.motionctrl.AddMany([ lbl_empty(), (lbl_x, 0, wx.ALIGN_CENTER), (lbl_y, 0, wx.ALIGN_CENTER), lbl_empty(), lbl_empty() ]) def add_coords(self, event): '''Add coordinate widgets (X and Y spinners + record button)''' count_rows = self.motionctrl.GetEffectiveRowsCount() # Creating settings widgets number = wx.StaticText(self.coords_settings, label=str(count_rows) + ")") move_x = wx.SpinCtrl(self.coords_settings, size=(60, -1), min=-self.screen_dimensions[0], max=self.screen_dimensions[0]) move_y = wx.SpinCtrl(self.coords_settings, size=(60, -1), min=-self.screen_dimensions[1], max=self.screen_dimensions[1]) record = wx.Button(self.coords_settings, label="Record", size=(60, -1)) delete = wx.Button(self.coords_settings, size=(26, -1)) delete.SetBitmap(wx.Bitmap("icons/delete.png")) # Adding widgets to sizers self.motionctrl.AddMany([(number, 0, wx.ALIGN_CENTER_VERTICAL), move_x, move_y, record, delete]) # Adding tooltips move_x.SetToolTipString(tooltip_x) move_y.SetToolTipString(tooltip_y) record.SetToolTipString(tooltip_record_coord) delete.SetToolTipString(tooltip_delete_coord) # Make number bold font = number.GetFont() font.SetWeight(wx.BOLD) number.SetFont(font) # Binding events move_x.Bind(wx.EVT_KILL_FOCUS, self.save_profile) move_y.Bind(wx.EVT_KILL_FOCUS, self.save_profile) self.Bind(wx.EVT_BUTTON, self.init_record, record) self.Bind(wx.EVT_BUTTON, self.delete_coords, delete) # Update scrolled window self.coords_settings.Layout() old_size = self.coords_settings.GetSizer().GetSize() new_size = (old_size[0] + 1, old_size[1]) self.coords_settings.SetVirtualSize(new_size) self.coords_settings.Scroll( 0, self.coords_settings.GetScrollRange(wx.VERTICAL)) # Add input widgets to list (to be used by main loop later) self.coordinates.append([move_x, move_y]) if event != None: self.save_profile(None) def delete_coords(self, event): '''Function for deleting an already existing coordinate''' if self.motionctrl.GetEffectiveRowsCount() > 2: obj = event.GetEventObject() # Collecting all widgets to be deleted delete_widgets = [ wx.FindWindowById(obj.GetId() - i) for i in range(self.motionctrl.GetCols()) ] # Updating coordinate numbers all_ids = sorted([ child.GetWindow().GetId() for child in self.motionctrl.GetChildren() ]) for x in all_ids[all_ids.index(delete_widgets[-1].GetId())::5][1:]: widget = wx.FindWindowById(x) widget.SetLabel(str(int(widget.GetLabel()[0]) - 1) + ")") # Removing coordinate from list self.coordinates.pop(int(delete_widgets[-1].GetLabel()[0]) - 1) # Destroying all widgets related to the deleted coordinate for widget in delete_widgets: widget.Destroy() self.motionctrl.Layout() # Saving settings self.save_profile(None) def clear_coords(self, event): '''Function for resetting all coordinates''' for coords in self.coordinates: for widget in coords: widget.SetValue(0) self.save_profile(None) def init_record(self, event): '''Initiate recording of cursor positions''' # Preparing variables obj = event.GetEventObject() self.coord_counter = 0 self.coord_widgets = [] if self.movement_relative: self.last_coord = None # Starting listening for mouse presses if not hasattr(self, "hook"): self.hook = HookManager() self.hook.HookMouse() # Indicating recording self.frame.statusbar.SetStatusText("Recording cursor positions...") self.frame.SetTitle("MouseMove - Recording positions") if obj in [self.record, self.frame.page_keyconfig ]: # Need the keyconfig reference to make hotkey work first_coord = self.motionctrl.GetChildren()[5].GetWindow() self.hook.MouseLeftDown = self.record_coords else: first_coord = wx.FindWindowById(obj.GetId() - 3) self.first_coord = first_coord self.hook.MouseLeftDown = self.record_single_coord first_coord.SetForegroundColour("Red") first_coord.Refresh() def stop_record(self): '''Actions to be performed when stopping recording''' self.hook.UnhookMouse() self.frame.statusbar.SetStatusText("") self.frame.SetTitle("MouseMove") self.save_profile(None)