def __init__(self, file_name, default_data, group_order=None): self.file_name = format_file_path(file_name) self._default_data = default_data self.default_data = {} self.order = list(group_order) if group_order is not None else [] for group, data in get_items(self._default_data): self.default_data[group] = self._default_data[group] self.load()
def generate(self, image_type, reload=False): if reload: self.reload() if image_type.lower() == 'clicks': name = CONFIG['GenerateHeatmap']['NameFormat'] name = name.replace('[ExpMult]', self.heatmap_exp) name = name.replace('[GaussianSize]', self.heatmap_gaussian) name = name.replace('[ColourProfile]', self.heatmap_colour) selected_buttons = [ k for k, v in get_items(self.heatmap_buttons) if v ] if all(self.heatmap_buttons.values()): name = name.replace('[MouseButtons]', 'Combined') elif len(selected_buttons) == 2: name = name.replace('[MouseButtons]', '+'.join(selected_buttons)) elif len(selected_buttons) == 1: name = name.replace('[MouseButtons]', selected_buttons[0]) else: name = name.replace('[MouseButtons]', 'Empty') elif image_type.lower() == 'tracks': name = CONFIG['GenerateTracks']['NameFormat'] name = name.replace('[ColourProfile]', self.track_colour) else: raise ValueError('incorred image type: {}, ' 'must be tracks or clicks'.format(image_type)) name = name.replace('[UResX]', self.upscale_res_x) name = name.replace('[UResY]', self.upscale_res_y) name = name.replace('[ResX]', self.output_res_x) name = name.replace('[ResY]', self.output_res_y) name = name.replace('[FriendlyName]', self.name) #Replace invalid characters invalid_chars = ':*?"<>|' for char in invalid_chars: if char in name: name = name.replace(char, '') return '{}.{}'.format(format_file_path(name), CONFIG['GenerateImages']['FileType'])
def load(self): """Open config file and validate values. Allowed formats: value, type, [comment] value, int/float, [min, [max]], [comment] value, str, [is case sensitive, item1, item2...], [comment] """ try: with open(self.file_name, 'r') as f: config_lines = [i.strip() for i in f.readlines()] except IOError: config_lines = [] #Read user values config_data = {} for line in config_lines: if not line: continue #Start new heading if line.startswith('['): current_group = line[1:].split(']', 1)[0] config_data[current_group] = {} #Skip comment elif line[0] in (';', '/', '#'): pass #Process value else: name, value = [i.strip() for i in line.split('=', 1)] value = value.replace('#', ';').replace('//', ';').split(';', 1)[0] #Compare value in file to default settings try: default_value, default_type = self.default_data[current_group][name][:2] except KeyError: pass else: #Process differently depending on variable type if default_type == bool: if value.lower() in ('0', 'false'): value = False elif value.lower() in ('1', 'true'): value = True else: value = default_value elif default_type == int: if '.' in value: value = value.split('.')[0] try: value = int(value) except ValueError: value = default_value elif default_type == str: value = str(value).rstrip() else: value = default_type(value) #Handle min/max values if default_type in (int, float): no_text = [i for i in self.default_data[current_group][name] if not isinstance(i, str)] if len(no_text) >= 3: if no_text[2] is not None and no_text[2] > value: value = no_text[2] elif len(no_text) >= 4: if no_text[3] is not None and no_text[3] < value: value = no_text[3] if default_type == str: if len(self.default_data[current_group][name]) >= 3: if isinstance(self.default_data[current_group][name][2], tuple): allowed_values = list(self.default_data[current_group][name][2]) case_sensitive = allowed_values.pop(0) if case_sensitive: if not any(value == i for i in allowed_values): value = default_value else: value_lower = value.lower() if not any(value_lower == i.lower() for i in allowed_values): value = default_value config_data[current_group][name] = value #Add any remaining values that weren't in the file for group, variables in get_items(self.default_data): for variable, defaults in get_items(variables): try: config_data[group][variable] except KeyError: try: config_data[group][variable] = defaults[0] except KeyError: config_data[group] = {variable: defaults[0]} self.data = config_data return self.data
def background_process(q_recv, q_send): """Function to handle all the data from the main thread.""" try: NOTIFY(START_THREAD) NOTIFY.send(q_send) store = {'Data': load_program(), 'LastProgram': None, 'Resolution': None, 'ResolutionTemp': None, 'ResolutionList': set(), 'Offset': (0, 0), 'LastResolution': None, 'ActivitySinceLastSave': False, 'SavesSkipped': 0, 'PingTimeout': CONFIG['Timer']['_Ping'] + 1} NOTIFY(DATA_LOADED) NOTIFY(QUEUE_SIZE, q_recv.qsize()) NOTIFY.send(q_send) while True: received_data = q_recv.get() ''' #Quit if data is not received by the ping interval #Seems buggy so disabling for now try: received_data = q_recv.get(timeout=store['PingTimeout']) except Empty: break ''' check_resolution = False if 'Save' in received_data: if store['ActivitySinceLastSave']: _save_wrapper(q_send, store['LastProgram'], store['Data'], False) NOTIFY(QUEUE_SIZE, q_recv.qsize()) store['ActivitySinceLastSave'] = False store['SavesSkipped'] = 0 else: store['SavesSkipped'] += 1 NOTIFY(SAVE_SKIP, CONFIG['Save']['Frequency'] * store['SavesSkipped'], q_recv.qsize()) q_send.put({'SaveFinished': None}) if 'Program' in received_data: current_program = received_data['Program'] if current_program != store['LastProgram']: if current_program is None: NOTIFY(APPLICATION_LOADING) else: NOTIFY(APPLICATION_LOADING, current_program) NOTIFY.send(q_send) #Save old profile _save_wrapper(q_send, store['LastProgram'], store['Data'], True) #Load new profile store['LastProgram'] = current_program store['Data'] = load_program(current_program) store['ActivitySinceLastSave'] = False #Check new resolution _check_resolution(store, store['Resolution']) store['ResolutionList'] = set() if store['Data']['Ticks']['Total']: NOTIFY(DATA_LOADED) else: NOTIFY(DATA_NOTFOUND) NOTIFY(QUEUE_SIZE, q_recv.qsize()) NOTIFY.send(q_send) if 'Resolution' in received_data: store['Resolution'] = received_data['Resolution'] _check_resolution(store, store['Resolution']) if 'MonitorLimits' in received_data: store['ResolutionTemp'] = received_data['MonitorLimits'] #Record key presses if 'KeyPress' in received_data: store['ActivitySinceLastSave'] = True for key in received_data['KeyPress']: try: store['Data']['Keys']['All']['Pressed'][key] += 1 except KeyError: store['Data']['Keys']['All']['Pressed'][key] = 1 try: store['Data']['Keys']['Session']['Pressed'][key] += 1 except KeyError: store['Data']['Keys']['Session']['Pressed'][key] = 1 #Record time keys are held down if 'KeyHeld' in received_data: store['ActivitySinceLastSave'] = True for key in received_data['KeyHeld']: try: store['Data']['Keys']['All']['Held'][key] += 1 except KeyError: store['Data']['Keys']['All']['Held'][key] = 1 try: store['Data']['Keys']['Session']['Held'][key] += 1 except KeyError: store['Data']['Keys']['Session']['Held'][key] = 1 #Calculate and track mouse movement if 'MouseMove' in received_data: store['ActivitySinceLastSave'] = True resolution = 0 _resolution = -1 start, end = received_data['MouseMove'] #distance = find_distance(end, start) #Calculate the pixels in the line if end is None: raise TypeError('debug - mouse moved without coordinates') if start is None: mouse_coordinates = [end] else: mouse_coordinates = [start, end] + calculate_line(start, end) #Don't bother calculating offset for each pixel #if both start and end are on the same monitor try: resolution, offset = monitor_offset(start, store['ResolutionTemp']) _resolution = monitor_offset(end, store['ResolutionTemp'])[0] except TypeError: pass #Write each pixel to the dictionary for pixel in mouse_coordinates: if MULTI_MONITOR: if resolution != _resolution: try: resolution, offset = monitor_offset(pixel, store['ResolutionTemp']) except TypeError: store['ResolutionTemp'] = monitor_info() try: resolution, offset = monitor_offset(pixel, store['ResolutionTemp']) except TypeError: continue pixel = (pixel[0] - offset[0], pixel[1] - offset[1]) if resolution not in store['ResolutionList']: _check_resolution(store, resolution) store['ResolutionList'].add(resolution) else: resolution = store['Resolution'] store['Data']['Maps']['Tracks'][resolution][pixel] = store['Data']['Ticks']['Current']['Tracks'] store['Data']['Ticks']['Current']['Tracks'] += 1 #Compress tracks if the count gets too high if store['Data']['Ticks']['Current']['Tracks'] > CONFIG['CompressMaps']['TrackMaximum']: compress_multplier = CONFIG['CompressMaps']['TrackReduction'] NOTIFY(TRACK_COMPRESS_START, 'track') NOTIFY.send(q_send) tracks = store['Data']['Maps']['Tracks'] for resolution in tracks.keys(): tracks[resolution] = {k: int(v // compress_multplier) for k, v in get_items(tracks[resolution])} tracks[resolution] = {k: v for k, v in get_items(tracks[resolution]) if v} if not tracks[resolution]: del tracks[resolution] NOTIFY(TRACK_COMPRESS_END, 'track') NOTIFY(QUEUE_SIZE, q_recv.qsize()) store['Data']['Ticks']['Current']['Tracks'] //= compress_multplier store['Data']['Ticks']['Session']['Current'] //= compress_multplier #Record mouse clicks if 'MouseClick' in received_data: store['ActivitySinceLastSave'] = True for mouse_button, pixel in received_data['MouseClick']: if MULTI_MONITOR: try: resolution, offset = monitor_offset(pixel, store['ResolutionTemp']) except TypeError: store['ResolutionTemp'] = monitor_info() try: resolution, offset = monitor_offset(pixel, store['ResolutionTemp']) except TypeError: continue pixel = (pixel[0] - offset[0], pixel[1] - offset[1]) else: resolution = store['Resolution'] try: store['Data']['Maps']['Clicks'][resolution][mouse_button][pixel] += 1 except KeyError: store['Data']['Maps']['Clicks'][resolution][mouse_button][pixel] = 1 #Increment the amount of time the script has been running for if 'Ticks' in received_data: store['Data']['Ticks']['Total'] += received_data['Ticks'] store['Data']['Ticks']['Recorded'] += 1 NOTIFY.send(q_send) #Exit process (this shouldn't happen for now) NOTIFY(THREAD_EXIT) NOTIFY.send(q_send) _save_wrapper(q_send, store['LastProgram'], store['Data'], False) except Exception as e: q_send.put(traceback.format_exc()) return
def start_tracking(): mouse_inactive_delay = 2 updates_per_second = CONFIG['Main']['UpdatesPerSecond'] timer = { 'UpdateScreen': CONFIG['Timer']['CheckResolution'], 'UpdatePrograms': CONFIG['Timer']['CheckPrograms'], 'Save': CONFIG['Save']['Frequency'], 'ReloadProgramList': CONFIG['Timer']['ReloadPrograms'], 'UpdateQueuedCommands': CONFIG['Timer']['_ShowQueuedCommands'] } timer = {k: v * updates_per_second for k, v in get_items(timer)} store = { 'Resolution': { 'Current': monitor_info(), 'Previous': None, 'Boundaries': None }, 'Mouse': { 'Position': { 'Current': None, 'Previous': None }, 'NotMoved': 0, 'Inactive': False, 'Clicked': {}, 'OffScreen': False }, 'Keyboard': { 'KeysPressed': {k: False for k in KEYS.keys()} }, 'LastActivity': 0, 'LastSent': 0, 'Save': { 'Finished': True, 'Next': timer['Save'] } } mouse_pos = store['Mouse']['Position'] #Start background process q_send = Queue() q_recv = Queue() p = Process(target=background_process, args=(q_send, q_recv)) #p.daemon = True p.start() q_send2 = Queue() q_recv2 = Queue() running_programs = ThreadHelper(running_processes, q_send2, q_recv2, q_send) running_programs.start() i = 0 NOTIFY(START_MAIN) while True: with RefreshRateLimiter(updates_per_second) as limiter: #Send data to thread try: if frame_data or frame_data_rp: last_sent = i - store['LastSent'] if frame_data: if last_sent: frame_data['Ticks'] = last_sent q_send.put(frame_data) if frame_data_rp: q_send2.put(frame_data_rp) store['LastSent'] = i except NameError: pass while not q_recv2.empty(): print_override('{} {}'.format(time_format(limiter.time), q_recv2.get())) #Print any messages from previous loop notify_extra = '' received_data = [] while not q_recv.empty(): received_message = q_recv.get() #Receive text messages try: if received_message.startswith( 'Traceback (most recent call last)'): return received_message except AttributeError: pass else: received_data.append(received_message) #Get notification when saving is finished try: received_message.pop('SaveFinished') except (KeyError, AttributeError): pass else: store['Save']['Finished'] = True store['Save']['Next'] = i + timer['Save'] if received_data: notify_extra = u' | '.join(received_data) notify_output = NOTIFY.get_output() if notify_extra: if notify_output: notify_output = notify_extra + ' | ' + notify_output else: notify_output = notify_extra if notify_output: print_override(u'{} {}'.format(time_format(limiter.time), notify_output)) frame_data = {} frame_data_rp = {} mouse_pos['Current'] = get_cursor_pos() #Check if mouse is inactive (such as in a screensaver) if mouse_pos['Current'] is None: if not store['Mouse']['Inactive']: NOTIFY(MOUSE_UNDETECTED) store['Mouse']['Inactive'] = True time.sleep(mouse_inactive_delay) continue #Check if mouse left the monitor elif ( not MULTI_MONITOR and (not 0 <= mouse_pos['Current'][0] < store['Resolution']['Current'][0] or not 0 <= mouse_pos['Current'][1] < store['Resolution']['Current'][1])): if not store['Mouse']['OffScreen']: NOTIFY(MOUSE_OFFSCREEN) store['Mouse']['OffScreen'] = True elif store['Mouse']['OffScreen']: NOTIFY(MOUSE_ONSCREEN) store['Mouse']['OffScreen'] = False #Notify once if mouse is no longer inactive if store['Mouse']['Inactive']: store['Mouse']['Inactive'] = False NOTIFY(MOUSE_DETECTED) #Check if mouse is in a duplicate position if mouse_pos['Current'] is None or mouse_pos[ 'Current'] == mouse_pos['Previous']: store['Mouse']['NotMoved'] += 1 elif store['Mouse']['NotMoved']: store['Mouse']['NotMoved'] = 0 if not store['Mouse']['NotMoved']: if not store['Mouse']['OffScreen']: frame_data['MouseMove'] = (mouse_pos['Previous'], mouse_pos['Current']) NOTIFY(MOUSE_POSITION, mouse_pos['Current']) store['LastActivity'] = i #Mouse clicks click_repeat = CONFIG['Main']['RepeatClicks'] for mouse_button, clicked in enumerate(get_mouse_click()): mb_clicked = store['Mouse']['Clicked'].get(mouse_button, False) mb_data = (mouse_button, mouse_pos['Current']) if clicked: store['LastActivity'] = i #First click if not mb_clicked: store['Mouse']['Clicked'][mouse_button] = limiter.time if not store['Mouse']['OffScreen']: NOTIFY(MOUSE_CLICKED, mouse_pos['Current'], mouse_button) try: frame_data['MouseClick'].append(mb_data) except KeyError: frame_data['MouseClick'] = [mb_data] else: NOTIFY(MOUSE_CLICKED_OFFSCREEN, mouse_button) #Held clicks elif click_repeat and mb_clicked < limiter.time - click_repeat: store['Mouse']['Clicked'][mouse_button] = limiter.time if not store['Mouse']['OffScreen']: NOTIFY(MOUSE_CLICKED_HELD, mouse_pos['Current'], mouse_button) try: frame_data['MouseClick'].append(mb_data) except KeyError: frame_data['MouseClick'] = [mb_data] elif mb_clicked: NOTIFY(MOUSE_UNCLICKED) del store['Mouse']['Clicked'][mouse_button] store['LastActivity'] = i #Key presses keys_pressed = [] keys_held = [] key_status = store['Keyboard']['KeysPressed'] key_press_repeat = CONFIG['Main']['RepeatKeyPress'] _keys_held = [] _keys_pressed = [] for k in KEYS: if get_key_press(KEYS[k]): keys_held.append(k) #If key is currently being held down if key_status[k]: if key_press_repeat and key_status[ k] < limiter.time - key_press_repeat: keys_pressed.append(k) _keys_held.append(k) key_status[k] = limiter.time #If key has been pressed else: keys_pressed.append(k) _keys_pressed.append(k) key_status[k] = limiter.time notify_key_press = list(keys_pressed) #If key has been released elif key_status[k]: key_status[k] = False if keys_pressed: frame_data['KeyPress'] = keys_pressed if keys_held: frame_data['KeyHeld'] = keys_held store['LastActivity'] = i if _keys_held: NOTIFY(KEYBOARD_PRESSES_HELD, _keys_held) if _keys_pressed: NOTIFY(KEYBOARD_PRESSES, _keys_pressed) #Check if resolution has changed if not i % timer['UpdateScreen']: if MULTI_MONITOR: frame_data['MonitorLimits'] = monitor_info() store['Resolution']['Boundaries'] = frame_data[ 'MonitorLimits'] else: store['Resolution']['Current'] = monitor_info() if store['Resolution']['Previous'] != store['Resolution'][ 'Current']: if store['Resolution']['Previous'] is not None: NOTIFY(RESOLUTION_CHANGED, store['Resolution']['Previous'], store['Resolution']['Current']) frame_data['Resolution'] = ['Resolution']['Current'] store['Resolution']['Previous'] = store['Resolution'][ 'Current'] #Display message that mouse has switched monitors if MULTI_MONITOR and 'MouseMove' in frame_data: try: try: res = monitor_offset( frame_data['MouseMove'][1], store['Resolution']['Boundaries'])[0] except TypeError: frame_data['MonitorLimits'] = monitor_info() store['Resolution']['Boundaries'] = frame_data[ 'MonitorLimits'] res = monitor_offset( frame_data['MouseMove'][1], store['Resolution']['Boundaries'])[0] except TypeError: pass else: store['Resolution']['Current'] = res if store['Resolution']['Previous'] is not None: if store['Resolution']['Current'] != store[ 'Resolution']['Previous']: NOTIFY(MONITOR_CHANGED, store['Resolution']['Previous'], store['Resolution']['Current']) store['Resolution']['Previous'] = store['Resolution'][ 'Current'] #Send request to update programs if not i % timer['UpdatePrograms']: frame_data_rp['Update'] = True #Send request to reload program list if not i % timer['ReloadProgramList']: frame_data_rp['Reload'] = True #Update user about the queue size if not i % timer['UpdateQueuedCommands'] and store[ 'LastActivity'] > i - timer['Save']: NOTIFY(QUEUE_SIZE, q_send.qsize()) #Send save request if store['Save'][ 'Finished'] and i and not i % store['Save']['Next']: frame_data['Save'] = True store['Save']['Finished'] = False if store['Mouse']['OffScreen']: mouse_pos['Previous'] = None else: mouse_pos['Previous'] = mouse_pos['Current'] i += 1