def save(self, path=None): """Save the sorted application list.""" result = [] for executable, names in get_items(self.data): for ext in self.extensions: if executable.lower().endswith(ext): for window_name, app_name in get_items(names): if window_name is None: if app_name == executable[:-self.extensions[ext]['len']]: result.append(executable) else: result.append('{}: {}'.format(executable, app_name)) else: if window_name == app_name: result.append('{}[{}]'.format(executable, window_name)) else: result.append('{}[{}]: {}'.format(executable, window_name, app_name)) break result = '\n'.join(_DEFAULT_TEXT + [''] + sorted(result, key=str.lower)) if path is None: path = self.path with open(path, 'w') as f: f.write(result) return result
def config_to_dict(conf): new_dict = {} for header, variables in get_items(conf): new_dict[header] = {} for variable, info in get_items(variables): new_dict[header][variable] = info['type'](info['value']) return new_dict
def _get_track_map(self, track_type, session=False): """Return dictionary of tracks along with top resolution and range of values. TODO: Test sum of arrays vs length of arrays to get top resolution """ start_time = self['Ticks']['Session'][track_type] if session else 0 top_resolution = None max_records = 0 min_value = float('inf') max_value = -float('inf') result = {} for resolution, maps in get_items(self['Resolution']): array = numpy.max(maps[track_type] - start_time, 0) num_records = numpy.count(array) if num_records: result[resolution] = array #Find resolution with most data if num_records > max_records: max_records = num_records top_resolution = resolution #Find the highest and lowest recorded values min_value = min(min_value, numpy.min(array)) max_value = max(max_value, numpy.max(array)) if not result: return None return top_resolution, (int(min_value), int(max_value)), result
def _iterate(self, maps, command, extra=None, _legacy=False): for key, value in get_items(maps): #Old format where resolution was separate for each map if _legacy and isinstance(key, (str, unicode)): self._iterate(value, command, extra, _legacy=_legacy) #New format when each resolution contains all the maps elif not _legacy and isinstance(value, dict): self._iterate(value, command, extra, _legacy=_legacy) #Separate the numpy arrays from the data elif command == 'separate': array = maps[key] maps[key] = len(self._map_list) self._map_list.append(array) #Rejoin the numpy arrays with the data elif command == 'join': maps[key] = extra[value] #Convert dicts to numpy arrays (only used on old files) elif command == 'convert' and _legacy: width, height = key numpy_array = numpy.array((width, height), create=True, dtype='int64') for x, y in value: numpy_array[y][x] = value[(x, y)] maps[key] = numpy_array
def _get_priority_order(values, key='__priority__', default=None): """Use the __priority__ key to build a sorted list of config values. The list starts from the lowest value, and by default, the first gap will be filled with anything without a value. Changing default will instead assign all those values to a particular priority """ #Build dict of values grouped by priority priorities = {} for k, v in get_items(values): if not k.startswith('_'): priority = v.get(key, default) try: priorities[priority].append(k) except KeyError: priorities[priority] = [k] #Build list of sorted values try: current = min(i for i in priorities if i is not None) except ValueError: current = 0 order = [] while priorities: try: order += sorted(priorities.pop(current)) except KeyError: try: order += sorted(priorities.pop(None)) except KeyError: pass current += 1 return order
def _load_from_dict(self, config_dict): """Read data from the default dictionary.""" for heading, var_data in get_items(config_dict): self._data[heading] = {} for var, info in get_items(var_data): if not isinstance(info, dict): continue #Fill in type or value if not set if 'type' not in info: info['type'] = type(info['value']) elif 'value' not in info: info['value'] = self._DEFAULT_VALUES[info['type']] info['default'] = info['value'] self._data[heading][var] = info
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 list_data_files(): """List the name of every saved profile in the data folder. The extension is checked, but removed in the output list. """ all_files = list_directory(DATA_FOLDER) if all_files is None: return [] date_modified = {f: get_modified_time(os.path.join(DATA_FOLDER, f)) for f in all_files} date_sort = sorted(get_items(date_modified), key=itemgetter(1)) return [k.replace(DATA_EXTENSION, '') for k, v in date_sort if k.endswith(DATA_EXTENSION)][::-1]
def compress_tracks(store, multiplier): for resolution, maps in get_items(store['Data']['Resolution']): maps['Tracks'] = numpy.divide(maps['Tracks'], multiplier, as_int=True) store['Data']['Ticks']['Tracks'] //= multiplier store['Data']['Ticks']['Tracks'] = int(store['Data']['Ticks']['Tracks']) store['Data']['Ticks']['Session']['Tracks'] //= multiplier store['Data']['Ticks']['Session']['Tracks'] = int( store['Data']['Ticks']['Session']['Tracks'])
def upscale_arrays_to_resolution(arrays, target_resolution, skip=[]): """Upscale a dict of arrays to a certain resolution. The dictionary key must be a resolution, and the values can either be an array or list of arrays. Use skip to ignore array indexes in the list. """ if isinstance(skip, int): skip = [skip] skip = set(skip) #Count number of arrays num_arrays = 0 for resolution, array_list in get_items(arrays): if isinstance(array_list, (list, tuple)): array_len = len(array_list) num_arrays += array_len - len( [i for i in range(array_len) if i in skip]) elif 0 not in skip: num_arrays += 1 #Upscale each array Message('Upscaling arrays to {}x{}...'.format(target_resolution[0], target_resolution[1])) processed = 0 output = [] for resolution, array_list in get_items(arrays): if not isinstance(array_list, (list, tuple)): array_list = [array_list] for i, array in enumerate(array_list): if i in skip: continue processed += 1 Message('Processing array for {}x{} ({}/{})'.format( resolution[0], resolution[1], processed, num_arrays)) zoom_factor = (target_resolution[1] / resolution[1], target_resolution[0] / resolution[0]) upscaled = upscale(array, zoom_factor) output.append(upscaled) return output
def gradient_preview(folder, width=720, height=80): """Save each colour map as a gradient in a folder.""" from core.os import join_path for map_lowercase, data in get_items(parse_colour_file()['Maps']): colours = calculate_colour_map(map_lowercase) image = ColourRange(0, 1, colours)._preview_gradient(width, height) if data['Type']['tracks']: image.save(join_path(folder, 'Tracks', '{}.png'.format(data['UpperCase']), True)) if data['Type']['clicks']: image.save(join_path(folder, 'Clicks', '{}.png'.format(data['UpperCase']), True)) if data['Type']['keyboard']: image.save(join_path(folder, 'Keyboard', '{}.png'.format(data['UpperCase']), True))
def update(self, url=APP_LIST_URL): """Update application list from URL. Does not overwrite any values. """ url_data = self._read(url=url) if not url_data: return False for executable, names in get_items(url_data): if executable not in self.data: self.data[executable] = url_data[executable] else: for name in names: if name not in self.data[executable]: self.data[executable][name] = names[name] return True
def reload(self): g_im = CONFIG['GenerateImages'] g_hm = CONFIG['GenerateHeatmap'] g_t = CONFIG['GenerateTracks'] g_s = CONFIG['GenerateSpeed'] g_kb = CONFIG['GenerateKeyboard'] self.width = str(g_im['_OutputResolutionX']) self.height = str(g_im['_OutputResolutionY']) self.uwidth = str(g_im['_UpscaleResolutionX']) self.uheight = str(g_im['_UpscaleResolutionY']) self.high_precision = 'High Detail' if g_im[ 'HighPrecision'] else 'Normal' self.heatmap_colours = str(g_hm['ColourProfile']) self.heatmap_buttons = { 'LMB': g_hm['_MouseButtonLeft'], 'MMB': g_hm['_MouseButtonMiddle'], 'RMB': g_hm['_MouseButtonRight'] } selected_buttons = [k for k, v in get_items(self.heatmap_buttons) if v] if len(selected_buttons) == 3: self.heatmap_button_group = 'Combined' elif len(selected_buttons) == 2: self.heatmap_button_group = '+'.join(selected_buttons) elif len(selected_buttons) == 1: self.heatmap_button_group = selected_buttons[0] else: self.heatmap_button_group = 'Empty' self.heatmap_gaussian_actual = str( gaussian_size(g_im['_UpscaleResolutionX'], g_im['_UpscaleResolutionY'])) self.heatmap_gaussian = str(g_hm['GaussianBlurMultiplier']) self.track_colour = str(g_t['ColourProfile']) self.speed_colour = str(g_s['ColourProfile']) self.keyboard_colour = str(g_kb['ColourProfile']) self.keyboard_set = g_kb['DataSet'][0].upper( ) + g_kb['DataSet'][1:].lower() self.keyboard_exponential = str(g_kb['LinearPower']) self.keyboard_size_mult = str(g_kb['SizeMultiplier']) self.keyboard_extended = 'Extended' if g_kb[ 'ExtendedKeyboard'] else 'Compact'
def keys_per_hour(self, session=False): """Detect if the game has keyboard tracking or not. Based on my own tracks, a game may range from 100 to 4000 normally. Without keyboard tracking, it's generally between 0.01 and 5. Check if this number is above 10 or 20 to get an idea. """ if session: all_clicks = self.data['Keys']['Session']['Held'] ticks = self.data['Ticks']['Session']['Total'] else: all_clicks = self.data['Keys']['All']['Held'] ticks = self.data['Ticks']['Total'] include = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ' total_presses = sum(v for k, v in get_items(all_clicks) if k in include) return 3600 * total_presses / ticks
def record_gamepad_axis(store, received_data): for controller_axis in received_data: for axis, amount in get_items(controller_axis): try: store['Data']['Gamepad']['All']['Axis'][axis][amount] += 1 except KeyError: try: store['Data']['Gamepad']['All']['Axis'][axis][amount] = 1 except KeyError: store['Data']['Gamepad']['All']['Axis'][axis] = {amount: 1} try: store['Data']['Gamepad']['Session']['Axis'][axis][amount] += 1 except KeyError: try: store['Data']['Gamepad']['Session']['Axis'][axis][ amount] = 1 except KeyError: store['Data']['Gamepad']['Session']['Axis'][axis] = { amount: 1 }
def config_controls(heading=None, variable=None, property=None): #Set config value if heading is not None and variable is not None: for k, v in get_items(request.args): if k == 'set': app.config['PIPE_CONTROL_SEND'].send(CONFIG_SET) app.config['PIPE_CONFIG_UPDATE_SEND'].send( (heading, variable, v)) #Return config _config = _get_config() config = config_to_dict(_config) if heading is None: return jsonify(config) try: if variable is None: return jsonify(config[heading]) else: if property is not None: property = property.lower() try: if property == 'min': return jsonify(_config[heading][variable].min) elif property == 'max': return jsonify(_config[heading][variable].max) elif property == 'default': if _config[heading][variable].type == bool: return jsonify( bool(_config[heading][variable].default)) return jsonify(_config[heading][variable].default) elif property == 'valid': return jsonify(_config[heading][variable].valid) elif property == 'type': return jsonify( _config[heading][variable].type.__name__) except AttributeError: abort(404) return jsonify(config[heading][variable]) except KeyError: abort(404)
def clicks(self, image_name): mouse_buttons = ['LMB', 'MMB', 'RMB'] _print('Generating CSV from clicks...') for i, mouse_button in enumerate(self.data['Maps']['Click']['Single']): CONFIG['GenerateHeatmap']['_MouseButtonLeft'] = i == 0 CONFIG['GenerateHeatmap']['_MouseButtonMiddle'] = i == 1 CONFIG['GenerateHeatmap']['_MouseButtonRight'] = i == 2 for resolution, array in get_items( self.data['Maps']['Click']['Single'][mouse_button]): CONFIG['GenerateImages']['_TempResolutionX'], CONFIG[ 'GenerateImages']['_TempResolutionY'] = resolution result = self._generate(resolution, array) if result is not None: file_name = image_name.generate('csv-clicks', reload=True) create_folder(file_name) with open(file_name, 'w') as f: f.write(result)
def get_clicks(self, double_click=False, session=False): session = 'Session' if session else 'All' click_type = 'Double' if double_click else 'Single' top_resolution = None max_records = 0 min_value = float('inf') max_value = -float('inf') result = {} for resolution, maps in get_items(self['Resolution']): click_maps = (maps['Clicks'][session][click_type]['Left'], maps['Clicks'][session][click_type]['Middle'], maps['Clicks'][session][click_type]['Right']) #Get information on array contains_data = False for array in click_maps: num_records = numpy.count(array) if num_records: contains_data = True #Find resolution with most data if num_records > max_records: max_records = num_records top_resolution = resolution #Find the highest and lowest recorded values min_value = min(min_value, numpy.min(array)) max_value = max(max_value, numpy.max(array)) if contains_data: result[resolution] = click_maps if not result: return None return top_resolution, (int(min_value), int(max_value)), result
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].strip() #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 __iter__(self): """Show only values when converting to dict.""" for k, v in get_items(self): yield k, _ConfigDict(v)
def _start_tracking(): _background_process = None no_detection_wait = 2 try: q_feedback = Queue() #Setup message server if CONFIG['API']['RunServer']: q_msg = Queue() message = PrintFormat(MessageWithQueue(q_msg).send) server_port = get_free_port() local_message_server(port=server_port, q_main=q_msg, q_feedback=q_feedback) else: message = PrintFormat(MessageWithQueue().send) server_port = None #message(NOTIFY.get_output()) #Setup web server if CONFIG['API']['RunWeb']: app.config.update(create_pipe('REQUEST', duplex=False)) app.config.update(create_pipe('CONTROL', duplex=False)) app.config.update(create_pipe('STATUS', duplex=False)) app.config.update(create_pipe('PORT', duplex=False)) app.config.update(create_pipe('CONFIG', duplex=False)) app.config.update(create_pipe('CONFIG_UPDATE', duplex=False)) web_port = get_free_port() web_port = 65043 local_web_server(app=app, port=web_port, q_feedback=q_feedback) else: web_port = None message(NOTIFY.get_output()) #Start main script NOTIFY(MT_PATH) message(NOTIFY.get_output()) CONFIG.save() #Adjust timings to account for tick rate timer = { 'UpdateScreen': CONFIG['Advanced']['CheckResolution'], 'UpdatePrograms': CONFIG['Advanced']['CheckRunningApplications'], 'Save': CONFIG['Save']['Frequency'] * UPDATES_PER_SECOND, 'ReloadProgramList': CONFIG['Advanced']['ReloadApplicationList'], 'UpdateQueuedCommands': CONFIG['Advanced']['ShowQueuedCommands'], 'RefreshGamepads': CONFIG['Advanced']['RefreshGamepads'], 'HistoryCheck': CONFIG['Advanced']['HistoryCheck'] } store = { 'Resolution': { 'Current': monitor_info(), 'Previous': None, 'Boundaries': None }, 'Mouse': { 'Position': { 'Current': None, 'Previous': None }, 'NotMoved': 0, 'Inactive': False, 'Clicked': {}, 'LastClick': None, 'LastClickTime': 0, 'OffScreen': False, 'DoubleClickTime': get_double_click_time() / 1000 * UPDATES_PER_SECOND }, 'Keyboard': { 'KeysPressed': {k: False for k in KEYS.keys()}, 'KeysInvalid': set() }, 'LastActivity': 0, 'LastSent': 0, 'Save': { 'Finished': True, 'Next': timer['Save'] }, 'Gamepad': { 'ButtonsPressed': {} }, 'Flask': { 'App': app if web_port is not None else None, 'Port': { 'Web': web_port, 'Server': server_port } } } mouse_pos = store['Mouse']['Position'] #Start background processes q_bg_recv = Queue() q_bg_send = Queue() _background_process = Process(target=background_process, args=(q_bg_send, q_bg_recv)) _background_process.daemon = True _background_process.start() q_rp_recv = Queue() q_rp_send = Queue() _running_programs = Thread(target=running_processes, args=(q_rp_send, q_rp_recv, q_bg_send)) _running_programs.daemon = True _running_programs.start() ticks = 0 NOTIFY(START_MAIN) message(NOTIFY.get_output()) script_status = STATUS_RUNNING while script_status != STATUS_TERMINATED: with RefreshRateLimiter(UPDATES_PER_SECOND) as limiter: #Handle web server API requests if store['Flask']['App'] is not None: #Control state of script if store['Flask']['App'].config['PIPE_CONTROL_RECV'].poll( ): api_control = store['Flask']['App'].config[ 'PIPE_CONTROL_RECV'].recv() #Change if running or paused, or exit script if api_control in (STATUS_RUNNING, STATUS_PAUSED, STATUS_TERMINATED): script_status = api_control #Set config values if api_control == CONFIG_SET: config_header, config_var, config_val = store[ 'Flask']['App'].config[ 'PIPE_CONFIG_UPDATE_RECV'].recv() CONFIG[config_header][config_var] = config_val print('Set {}.{} to {}'.format( config_header, config_var, CONFIG[config_header][config_var])) #Requests that require response if store['Flask']['App'].config['PIPE_REQUEST_RECV'].poll( ): request_id = store['Flask']['App'].config[ 'PIPE_REQUEST_RECV'].recv() if request_id == FEEDBACK_STATUS: store['Flask']['App'].config[ 'PIPE_STATUS_SEND'].send(script_status) elif request_id == FEEDBACK_PORT: store['Flask']['App'].config[ 'PIPE_PORT_SEND'].send({ 'server': store['Flask']['Port']['Server'], 'web': store['Flask']['Port']['Web'] }) elif request_id == FEEDBACK_CONFIG: store['Flask']['App'].config[ 'PIPE_CONFIG_SEND'].send(CONFIG) if script_status != STATUS_RUNNING: continue #Send data to thread try: if frame_data or frame_data_rp: last_sent = ticks - store['LastSent'] if frame_data: if last_sent: frame_data['Ticks'] = last_sent q_bg_send.put(frame_data) if frame_data_rp: q_rp_send.put(frame_data_rp) store['LastSent'] = ticks except NameError: pass #Get messages from running program thread while not q_rp_recv.empty(): received = q_rp_recv.get() if isinstance(received, str): message(received, limiter.time) #Do not continue tracking held down keys #This behaviour is sometimes abused for games #but it will continue well after the game is quit elif isinstance(received, dict): if 'Program' in received: store['Keyboard']['KeysInvalid'] |= set([ k for k, v in get_items(store['Keyboard'] ['KeysPressed']) if v ]) #Print any messages from previous loop notify_extra = '' received_data = [] while not q_bg_recv.empty(): received_message = q_bg_recv.get() #Receive text messages try: if received_message.startswith( 'Traceback (most recent call last)'): q_bg_send.put({'Quit': True}) handle_error(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'] = ticks + timer['Save'] output_list = [received_data] #Add on output from Notify class output_list.append(NOTIFY.get_output()) #Add output from server if CONFIG['API']['RunServer']: received_data = [] while not q_feedback.empty(): received_data.append(q_feedback.get()) output_list.append(received_data) #Join all valid outputs together output = u' | '.join(u' | '.join(msg_group) if isinstance( msg_group, (list, tuple)) else msg_group for msg_group in output_list if msg_group) if output: message(output, limiter.time) frame_data = {} frame_data_rp = {} #Mouse Movement 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(no_detection_wait) 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'] = ticks #Mouse clicks click_repeat = CONFIG['Advanced']['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'] = ticks #First click if not mb_clicked: #Double click double_click = False if (store['Mouse']['LastClickTime'] > ticks - store['Mouse']['DoubleClickTime'] and store['Mouse']['LastClick'] == mb_data): store['Mouse']['LastClickTime'] = 0 store['Mouse']['LastClick'] = None double_click = True try: frame_data['DoubleClick'].append(mb_data) except KeyError: frame_data['DoubleClick'] = [mb_data] else: store['Mouse']['LastClickTime'] = ticks #Single click store['Mouse']['Clicked'][mouse_button] = ticks if not store['Mouse']['OffScreen']: if double_click: NOTIFY(MOUSE_CLICKED_DOUBLE, mouse_button, mouse_pos['Current']) else: NOTIFY(MOUSE_CLICKED, mouse_button, mouse_pos['Current']) try: frame_data['MouseClick'].append(mb_data) except KeyError: frame_data['MouseClick'] = [mb_data] frame_data['MouseHeld'] = False else: if double_click: NOTIFY(MOUSE_CLICKED_DOUBLE, mouse_button) else: NOTIFY(MOUSE_CLICKED, mouse_button) #Held clicks elif click_repeat and mb_clicked < ticks - click_repeat: store['Mouse']['Clicked'][mouse_button] = ticks if not store['Mouse']['OffScreen']: NOTIFY(MOUSE_CLICKED_HELD, mouse_button, mouse_pos['Current']) try: frame_data['MouseClick'].append(mb_data) except KeyError: frame_data['MouseClick'] = [mb_data] frame_data['MouseHeld'] = True else: NOTIFY(MOUSE_CLICKED_HELD, mouse_button) store['Mouse']['LastClick'] = mb_data elif mb_clicked: NOTIFY(MOUSE_UNCLICKED) del store['Mouse']['Clicked'][mouse_button] store['LastActivity'] = ticks #Key presses keys_pressed = [] keys_held = [] key_status = store['Keyboard']['KeysPressed'] key_press_repeat = CONFIG['Advanced']['RepeatKeyPress'] key_invalid = store['Keyboard']['KeysInvalid'] _keys_held = [] _keys_pressed = [] _keys_released = [] for k in KEYS: if get_key_press(KEYS[k]): #Ignore if held down from last profile if k in key_invalid: continue keys_held.append(k) #If key is currently being held down if key_status[k]: if key_press_repeat and key_status[ k] < ticks - key_press_repeat: keys_pressed.append(k) _keys_held.append(k) key_status[k] = ticks #If key has been pressed else: keys_pressed.append(k) _keys_pressed.append(k) key_status[k] = ticks notify_key_press = list(keys_pressed) #If key has been released elif key_status[k]: key_status[k] = False _keys_released.append(k) #Mark key as valid again try: key_invalid.remove(k) except KeyError: pass if keys_pressed: frame_data['KeyPress'] = keys_pressed store['LastActivity'] = ticks if keys_held: frame_data['KeyHeld'] = keys_held store['LastActivity'] = ticks if _keys_pressed: NOTIFY(KEYBOARD_PRESSES, *_keys_pressed) if _keys_held: NOTIFY(KEYBOARD_PRESSES_HELD, *_keys_held) if _keys_released: NOTIFY(KEYBOARD_RELEASED, *_keys_released) if CONFIG['Main']['_TrackGamepads']: #Reload list of gamepads (in case one was plugged in) if timer['RefreshGamepads'] and not ticks % timer[ 'RefreshGamepads']: try: old_gamepads = set(gamepads) except UnboundLocalError: old_gamepads = set() gamepads = { gamepad.device_number: gamepad for gamepad in Gamepad.list_gamepads() } difference = set(gamepads) - old_gamepads for i, id in enumerate(difference): NOTIFY(GAMEPAD_FOUND, id) store['Gamepad']['ButtonsPressed'][id] = {} #Gamepad tracking (multiple controllers not tested yet) button_repeat = CONFIG['Advanced']['RepeatButtonPress'] invalid_ids = [] buttons_held = {} _buttons_pressed = {} _buttons_released = {} for id, gamepad in get_items(gamepads): #Repeat presses if button_repeat: for button_id, last_update in get_items( store['Gamepad']['ButtonsPressed'][id]): if last_update < ticks - button_repeat: try: buttons_held[id].append(button_id) except KeyError: buttons_held[id] = [button_id] store['Gamepad']['ButtonsPressed'][id][ button_id] = ticks with gamepad as gamepad_input: #Break the connection if controller can't be found if not gamepad.connected: NOTIFY(GAMEPAD_LOST, id) invalid_ids.append(id) continue #Axis events (thumbsticks, triggers, etc) #Send an update every tick, but only print the changes #The dead zone can be tracked now and ignored later printable = {} axis_updates = gamepad_input.get_axis( printable=printable) if axis_updates: store['LastActivity'] = ticks try: frame_data['GamepadAxis'].append( axis_updates) except KeyError: frame_data['GamepadAxis'] = [axis_updates] for axis, value in get_items(printable): NOTIFY(GAMEPAD_AXIS, id, axis, value) #Button events button_presses = gamepad_input.get_button() if button_presses: for button_id, state in get_items( button_presses): #Button pressed if state: try: frame_data[ 'GamepadButtonPress'].append( button_id) except KeyError: frame_data[ 'GamepadButtonPress'] = [ button_id ] store['Gamepad']['ButtonsPressed'][id][ button_id] = ticks try: _buttons_pressed[id].append( button_id) except KeyError: _buttons_pressed[id] = [button_id] #Button has been released elif button_id in store['Gamepad'][ 'ButtonsPressed'][id]: held_length = ticks - store['Gamepad'][ 'ButtonsPressed'][id][button_id] del store['Gamepad']['ButtonsPressed'][ id][button_id] try: _buttons_released[id].append( button_id) except KeyError: _buttons_released[id] = [button_id] #Send held buttons each frame for id, held_buttons in get_items( store['Gamepad']['ButtonsPressed']): if held_buttons: try: frame_data['GamepadButtonHeld'].add( held_buttons) except KeyError: frame_data['GamepadButtonHeld'] = set( held_buttons) #Cleanup disconnected controllers for id in invalid_ids: del gamepads[id] del store['Gamepad']['ButtonsPressed'][id] if buttons_held: try: frame_data['GamepadButtonPress'] += buttons_held except KeyError: frame_data['GamepadButtonPress'] = buttons_held store['LastActivity'] = ticks for id, buttons in get_items(buttons_held): NOTIFY(GAMEPAD_BUTTON_HELD, id, buttons) if _buttons_pressed: store['LastActivity'] = ticks for id, buttons in get_items(_buttons_pressed): NOTIFY(GAMEPAD_BUTTON_PRESS, id, buttons) if _buttons_released: store['LastActivity'] = ticks for id, buttons in get_items(_buttons_released): NOTIFY(GAMEPAD_BUTTON_RELEASED, id, buttons) #Resolution recalculate_mouse = False check_resolution = timer[ 'UpdateScreen'] and not ticks % timer['UpdateScreen'] #Check if resolution has changed if check_resolution: if MULTI_MONITOR: old_resolution = store['Resolution']['Boundaries'] store['Resolution']['Boundaries'] = monitor_info() if old_resolution != store['Resolution']['Boundaries']: frame_data['MonitorLimits'] = store['Resolution'][ 'Boundaries'] recalculate_mouse = True 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'] = store['Resolution'][ 'Current'] store['Resolution']['Previous'] = store[ 'Resolution']['Current'] #Display message that mouse has switched monitors if MULTI_MONITOR: try: current_mouse_pos = frame_data['MouseMove'][1] except KeyError: current_mouse_pos = mouse_pos['Current'] else: recalculate_mouse = True if recalculate_mouse: try: #Calculate which monitor the mouse is on try: current_screen_resolution = monitor_offset( current_mouse_pos, store['Resolution']['Boundaries'])[0] except TypeError: if check_resolution: raise TypeError #Send to background process if the monitor list changes old_resolution = store['Resolution'][ 'Boundaries'] store['Resolution'][ 'Boundaries'] = monitor_info() if old_resolution != store['Resolution'][ 'Boundaries']: frame_data['MonitorLimits'] = store[ 'Resolution']['Boundaries'] current_screen_resolution = monitor_offset( current_mouse_pos, store['Resolution']['Boundaries'])[0] except TypeError: pass else: if current_screen_resolution != store[ 'Resolution']['Previous']: if store['Resolution']['Previous'] is not None: NOTIFY(MONITOR_CHANGED, store['Resolution']['Previous'], current_screen_resolution) store['Resolution'][ 'Previous'] = current_screen_resolution #Send request to check history list if timer['HistoryCheck'] and not ticks % timer['HistoryCheck']: frame_data['HistoryCheck'] = True #Send request to update programs if timer['UpdatePrograms'] and not ticks % timer[ 'UpdatePrograms']: frame_data_rp['Update'] = True #Send request to reload program list if timer['ReloadProgramList'] and not ticks % timer[ 'ReloadProgramList']: frame_data_rp['Reload'] = True #Update user about the queue size if (timer['UpdateQueuedCommands'] and not ticks % timer['UpdateQueuedCommands'] and timer['Save'] and store['LastActivity'] > ticks - timer['Save']): try: NOTIFY(QUEUE_SIZE, q_bg_send.qsize()) except NotImplementedError: pass #Send save request if store['Save']['Finished'] and ticks and not ticks % 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'] ticks += 1 except Exception as e: if _background_process is not None: try: q_bg_send.put({'Quit': True}) except IOError: pass handle_error(traceback.format_exc()) except KeyboardInterrupt: if _background_process is not None: try: q_bg_send.put({'Quit': True}) except IOError: pass NOTIFY(THREAD_EXIT) NOTIFY(PROCESS_EXIT) message(NOTIFY.get_output()) handle_error()
def parse_colour_text(colour_string): """Convert text into a colour map. It could probably do with a rewrite to make it more efficient, as it was first written to only use capitals. Mixed Colour: Combine multiple colours. Examples: BlueRed, BlackYellowGreen Hexadecimal Colours: As well as typing in words, you may also use hex. All the same effects can apply to these. Supported formats are #RGB, #RGBA, #RRGGBB, #RRGGBBAA. Modified Colour: Apply a modification to a colour. If multiple ones are applied, they will work in reverse order. Light and dark are not opposites so will not cancel each other out. Examples: LightBlue, DarkLightYellow Transition: This ends the current colour mix and starts a new one. Examples: BlackToWhite, RedToGreenToBlue Duplicate: Avoid having to type out multiple versions of the same word. Be careful as it has different effects based on its position. It basically multiplies the next word, see below for usage. Examples: Before colour: DarkDoubleRed = DarkRedDarkRed Before modifier: TripleDarkLightRed = DarkDarkDarkLightRed Before transition: BlueDoubleToDarkRed = BlueToDarkRedToDarkRed Any number of these features can be combined together to create different effects. As an example, here are the values that would result in the heatmap: BlackToDarkBlueToBlueToCyanTripleBlueToCyanBlueTo + TripleCyanBlueToTripleCyanYellowToCyanYellowTo + CyanTripleYellowToYellowToOrangeToRedOrangeToRed """ colour_string = format_name(colour_string, '#') colour_data = parse_colour_file()['Colours'] current_mix = [[]] current_colour = {'Mod': [], 'Dup': 1} while colour_string: edited = False #Check for colours colour_selection = None for colour, data in get_items(colour_data): if colour_string.startswith(colour): colour_string = colour_string[len(colour):] colour_selection = data['Colour'] break #Check for hex codes if colour_string.startswith('#'): length, colour_selection = hex_to_colour(colour_string[1:9]) if colour_selection and length: colour_string = colour_string[1 + length:] #Process colour with stored modifiers/duplicates colour = None if colour_selection: edited = True #Apply modifiers in reverse order colour = list(colour_selection) for modifier in current_colour['Mod']: colour_offset = modifier.get('ColourOffset', 0) colour_shift = modifier.get('ColourShift', 0) alpha_offset = modifier.get('AlphaOffset', 0) alpha_shift = modifier.get('AlphaShift', 0) colour = [(colour[0] >> colour_shift) + colour_offset, (colour[1] >> colour_shift) + colour_offset, (colour[2] >> colour_shift) + colour_offset, (colour[3] >> alpha_shift) + alpha_offset] current_colour['Mod'] = [] current_mix[-1] += [colour] * current_colour['Dup'] current_colour['Dup'] = 1 continue #Check for modifiers (dark, light, transparent etc) for i in MODIFIERS: if colour_string.startswith(i): colour_string = colour_string[len(i):] edited = True current_colour['Mod'] += [MODIFIERS[i]] * current_colour['Dup'] current_colour['Dup'] = 1 if edited: continue #Check for duplicates (double, triple, etc) for i in DUPLICATES: if colour_string.startswith(i): colour_string = colour_string[len(i):] edited = True current_colour['Dup'] *= DUPLICATES[i] if edited: continue #Start a new groups of colours for i in SEPERATORS: if colour_string.startswith(i): colour_string = colour_string[len(i):] edited = True #Handle putting a duplicate before 'to' new_list = [] list_len = current_colour['Dup'] if not current_mix[-1]: new_list = current_mix[-1] list_len -= 1 #Start the ew list current_mix += [new_list] * list_len current_colour['Dup'] = 1 break if edited: continue #Remove the first letter and try again colour_string = colour_string[1:] if not current_mix[0]: raise ValueError('invalid colour map') #Merge colours together final_mix = [] for colours in current_mix: result = colours[0] for colour in colours[1:]: result = [i + j for i, j in zip(result, colour)] num_colours = len(colours) final_mix.append(tuple(i / num_colours for i in result)) return final_mix
def upgrade_version(data={}, reset_sessions=True, update_metadata=True): """Files from an older version will be run through this function. It will always be compatible between any two versions. """ #Convert from old versions to new try: file_version = data['FileVersion'] except KeyError: legacy_history = ['-1', '2.0'] + [ '2.0.{}'.format(i) for i in (1, '1b', 2, 3, 4, 5, '5b', 6, '6b', '6c', 7, 8, 9, '9b', '9c', '9d', '9e', 10, '10b', '10c', '10d', 11, 12, 13) ] try: file_version = legacy_history.index(data['Version']) except KeyError: file_version = 0 original_version = file_version current_time = time.time() #Base format if file_version < 1: data['Count'] = 0 data['Tracks'] = {} data['Clicks'] = {} data['Keys'] = {} data['Ticks'] = 0 data['LastSave'] = current_time data['TimesLoaded'] = 0 data['Version'] = '2.0' #Add acceleration tracking if file_version < 2: data['Acceleration'] = {} #Rename acceleration to speed, change tracking method if file_version < 3: del data['Acceleration'] data['Speed'] = {} #Experimenting with combined speed and position tracks if file_version < 4: data['Combined'] = {} #Separate click maps, record both keys pressed and how long if file_version < 5: if update_metadata: data['Clicks'] = {} else: for resolution in data['Clicks']: data['Clicks'][resolution] = [ data['Clicks'][resolution], {}, {} ] data['Keys'] = {'Pressed': {}, 'Held': {}} data['Ticks'] = { 'Current': data['Count'], 'Total': data['Ticks'], 'Recorded': data['Count'] } del data['Count'] #Save creation date and rename modified date if file_version < 6: data['Time'] = { 'Created': data['LastSave'], 'Modified': data['LastSave'] } del data['LastSave'] #Group maps and add extras for experimenting on if file_version < 7: data['Maps'] = { 'Tracks': data['Tracks'], 'Clicks': data['Clicks'], 'Speed': data['Speed'], 'Combined': data['Combined'], 'Temp1': {}, 'Temp2': {}, 'Temp3': {}, 'Temp4': {}, 'Temp5': {}, 'Temp6': {}, 'Temp7': {}, 'Temp8': {} } del data['Tracks'] del data['Clicks'] del data['Speed'] del data['Combined'] #Separate tick counts for different maps if file_version < 8: data['Ticks']['Current'] = { 'Tracks': data['Ticks']['Current'], 'Speed': data['Ticks']['Current'] } #Remove speed and combined maps as they don't look very interesting if file_version < 9: del data['Maps']['Speed'] del data['Maps']['Combined'] del data['Ticks']['Current']['Speed'] #Record when session started if file_version < 10: data['Ticks']['Session'] = { 'Current': data['Ticks']['Current']['Tracks'], 'Total': data['Ticks']['Total'] } #Record keystokes per session if file_version < 11: data['Keys'] = { 'All': data['Keys'], 'Session': { 'Pressed': {}, 'Held': {} } } #Fixed some incorrect key names if file_version < 12: changes = { 'UNDERSCORE': 'HYPHEN', 'MULTIPLY': 'ASTERISK', 'AT': 'APOSTROPHE', 'HASH': 'NUMBER' } for old, new in get_items(changes): try: data['Keys']['All']['Pressed'][new] = data['Keys']['All'][ 'Pressed'].pop(old) data['Keys']['All']['Held'][new] = data['Keys']['All'][ 'Held'].pop(old) except KeyError: pass try: data['Keys']['Session']['Pressed'][new] = data['Keys'][ 'Session']['Pressed'].pop(old) data['Keys']['Session']['Held'][new] = data['Keys']['Session'][ 'Held'].pop(old) except KeyError: pass #Store each session start if file_version < 13: data['SessionStarts'] = [] #Remove invalid track coordinates if file_version < 14: for resolution in data['Maps']['Tracks']: for k in data['Maps']['Tracks'][resolution].keys(): if not 0 < k[0] < resolution[0] or not 0 < k[1] < resolution[1]: del data['Maps']['Tracks'][resolution][k] #Matched format of session and total ticks, converted all back to integers if file_version < 15: data['Ticks']['Tracks'] = int(data['Ticks']['Current']['Tracks']) data['Ticks']['Session']['Tracks'] = int( data['Ticks']['Session']['Current']) del data['Ticks']['Current'] del data['Ticks']['Session']['Current'] for resolution in data['Maps']['Tracks']: for k in data['Maps']['Tracks'][resolution]: if isinstance(data['Maps']['Tracks'][resolution][k], float): data['Maps']['Tracks'][resolution][k] = int( data['Maps']['Tracks'][resolution][k]) #Created separate map for clicks this session if file_version < 16: data['Maps']['Session'] = {'Clicks': {}} #Remove temporary maps as they were messy, add double clicks if file_version < 17: del data['Maps']['Temp1'] del data['Maps']['Temp2'] del data['Maps']['Temp3'] del data['Maps']['Temp4'] del data['Maps']['Temp5'] del data['Maps']['Temp6'] del data['Maps']['Temp7'] del data['Maps']['Temp8'] data['Maps']['DoubleClicks'] = {} data['Maps']['Session']['DoubleClicks'] = {} #Maintainence to remove invalid resolutions (the last update caused a few) if file_version < 18: def _test_resolution(aspects, x, y): for ax, ay in aspects: dx = x / ax if not dx % 1 and dx * ay == y: return True return False aspects = [ (4, 3), (16, 9), (16, 10), (18, 9), (21, 9), ] #Reverse and check for multi monitor setups aspects += [(y, x) for x, y in aspects] aspects += [(x * 2, y) for x, y in aspects] + [ (x * 3, y) for x, y in aspects ] + [(x * 5, y) for x, y in aspects] maps = ('Tracks', 'Clicks', 'DoubleClicks') for map in maps: for resolution in data['Maps'][map].keys(): if not _test_resolution(aspects, *resolution): del data['Maps'][map][resolution] #Rearrange some maps and convert to numpy arrays if file_version < 19: for maps in (data['Maps'], data['Maps']['Session']): maps['Click'] = { 'Single': { 'Left': {}, 'Middle': {}, 'Right': {} }, 'Double': { 'Left': {}, 'Middle': {}, 'Right': {} } } for resolution in maps['Clicks']: maps['Click']['Single']['Left'][resolution] = maps['Clicks'][ resolution][0] maps['Click']['Single']['Middle'][resolution] = maps['Clicks'][ resolution][1] maps['Click']['Single']['Right'][resolution] = maps['Clicks'][ resolution][2] del maps['Clicks'] for resolution in maps['DoubleClicks']: maps['Click']['Double']['Left'][resolution] = maps[ 'DoubleClicks'][resolution][0] maps['Click']['Double']['Middle'][resolution] = maps[ 'DoubleClicks'][resolution][1] maps['Click']['Double']['Right'][resolution] = maps[ 'DoubleClicks'][resolution][2] del maps['DoubleClicks'] IterateMaps(data['Maps']).convert() #Reset double click maps for code update if file_version < 20: data['Maps']['Click']['Double'] = { 'Left': {}, 'Middle': {}, 'Right': {} } #Track time between key presses and mistakes if file_version < 21: data['Keys']['All']['Intervals'] = {} data['Keys']['Session']['Intervals'] = {} data['Keys']['All']['Mistakes'] = {} data['Keys']['Session']['Mistakes'] = {} #Record more accurate intervals for each keystroke if file_version < 22: data['Keys']['All']['Intervals'] = { 'Total': data['Keys']['All']['Intervals'], 'Individual': {} } data['Keys']['Session']['Intervals'] = { 'Total': data['Keys']['Session']['Intervals'], 'Individual': {} } #Gamepad tracking if file_version < 23: data['Gamepad'] = { 'All': { 'Buttons': { 'Pressed': {}, 'Held': {} }, 'Axis': {} } } #Change resolutions to major keys if file_version < 24: data['Resolution'] = {} resolutions = data['Maps']['Tracks'].keys() for resolution in resolutions: data['Resolution'][resolution] = {} data['Resolution'][resolution]['Tracks'] = data['Maps'][ 'Tracks'].pop(resolution) data['Resolution'][resolution]['Clicks'] = {} try: click_s_l = data['Maps']['Click']['Single']['Left'].pop( resolution) except KeyError: click_s_l = numpy.array(resolution, create=True) try: click_s_m = data['Maps']['Click']['Single']['Middle'].pop( resolution) except KeyError: click_s_m = numpy.array(resolution, create=True) try: click_s_r = data['Maps']['Click']['Single']['Right'].pop( resolution) except KeyError: click_s_r = numpy.array(resolution, create=True) try: click_d_l = data['Maps']['Click']['Double']['Left'].pop( resolution) except KeyError: click_d_l = numpy.array(resolution, create=True) try: click_d_m = data['Maps']['Click']['Double']['Middle'].pop( resolution) except KeyError: click_d_m = numpy.array(resolution, create=True) try: click_d_r = data['Maps']['Click']['Double']['Right'].pop( resolution) except KeyError: click_d_r = numpy.array(resolution, create=True) clicks = { 'Single': { 'Left': click_s_l, 'Middle': click_s_m, 'Right': click_s_r }, 'Double': { 'Left': click_d_l, 'Middle': click_d_m, 'Right': click_d_r } } data['Resolution'][resolution]['Clicks']['All'] = clicks del data['Maps'] #Record history for later animation if file_version < 25: data['HistoryAnimation'] = {'Tracks': [], 'Clicks': [], 'Keyboard': []} #Add version update history and distance travelled if file_version < 27: data['VersionHistory'] = {} data['Distance'] = {'Tracks': 0.0} #Add speed maps if file_version < 28: for resolution in data['Resolution']: data['Resolution'][resolution]['Speed'] = numpy.array(resolution, create=True) version_update = data.get('FileVersion', '0') != FILE_VERSION #Track when the updates happen if version_update: start_version = max(original_version, 27) for i in range(start_version, FILE_VERSION + 1): data['VersionHistory'][i] = current_time if update_metadata: if update_metadata: data['Version'] = VERSION data['FileVersion'] = FILE_VERSION #Only count as new session if updated or last save was over an hour ago new_session = reset_sessions and ( not data['SessionStarts'] or current_time - 3600 > data['Time']['Modified']) if new_session or version_update and original_version < 27: data['Ticks']['Session']['Tracks'] = data['Ticks']['Tracks'] data['Ticks']['Session']['Total'] = data['Ticks']['Total'] data['Keys']['Session']['Pressed'] = {} data['Keys']['Session']['Held'] = {} data['Keys']['Session']['Intervals'] = { 'Total': {}, 'Individual': {} } data['Keys']['Session']['Mistakes'] = {} #Empty session arrays for resolution, values in get_items(data['Resolution']): if 'Session' not in values['Clicks']: values['Clicks']['Session'] = { 'Single': { 'Left': numpy.array(resolution, create=True), 'Middle': numpy.array(resolution, create=True), 'Right': numpy.array(resolution, create=True) }, 'Double': { 'Left': numpy.array(resolution, create=True), 'Middle': numpy.array(resolution, create=True), 'Right': numpy.array(resolution, create=True) } } else: try: values['Clicks']['Session']['Single'][ 'Left'] = numpy.fill( values['Clicks']['Session']['Single']['Left'], 0) except AttributeError: values['Clicks']['Session']['Single'][ 'Left'] = numpy.array(resolution, create=True) try: values['Clicks']['Session']['Single'][ 'Middle'] = numpy.fill( values['Clicks']['Session']['Single'] ['Middle'], 0) except AttributeError: values['Clicks']['Session']['Single'][ 'Middle'] = numpy.array(resolution, create=True) try: values['Clicks']['Session']['Single'][ 'Right'] = numpy.fill( values['Clicks']['Session']['Single']['Right'], 0) except AttributeError: values['Clicks']['Session']['Single'][ 'Right'] = numpy.array(resolution, create=True) try: values['Clicks']['Session']['Double'][ 'Left'] = numpy.fill( values['Clicks']['Session']['Double']['Left'], 0) except AttributeError: values['Clicks']['Session']['Double'][ 'Left'] = numpy.array(resolution, create=True) try: values['Clicks']['Session']['Double'][ 'Middle'] = numpy.fill( values['Clicks']['Session']['Double'] ['Middle'], 0) except AttributeError: values['Clicks']['Session']['Double'][ 'Middle'] = numpy.array(resolution, create=True) try: values['Clicks']['Session']['Double'][ 'Right'] = numpy.fill( values['Clicks']['Session']['Double']['Right'], 0) except AttributeError: values['Clicks']['Session']['Double'][ 'Right'] = numpy.array(resolution, create=True) data['Gamepad']['Session'] = { 'Buttons': { 'Pressed': {}, 'Held': {} }, 'Axis': {} } data['TimesLoaded'] += 1 data['SessionStarts'].append(current_time) return data
def generate(self, image_type, reload=False): image_type = image_type.lower() if reload: self.reload() lookup = {'clicks': 'GenerateHeatmap', 'tracks': 'GenerateTracks', 'keyboard': 'GenerateKeyboard', 'csv-tracks': 'NameFormatTracks', 'csv-clicks': 'NameFormatClicks', 'csv-keyboard': 'NameFormatKeyboard'} try: name = CONFIG[lookup[image_type]]['NameFormat'] except KeyError: try: name = CONFIG['GenerateCSV'][lookup[image_type]] except KeyError: raise ValueError('incorred image type: {}'.format(image_type)) #Rename alternative variables for k, v in get_items(self.ALTERNATIVES): k = '[{}]'.format(k) for i in v: i = '[{}]'.format(i) name = name.replace(i, k) #General Options name = name.replace('[Name]', self.name) name = name.replace('[FileName]', self.file_name) name = name.replace('[Width]', self.width) name = name.replace('[Height]', self.height) name = name.replace('[UpscaleWidth]', self.uwidth) name = name.replace('[UpscaleHeight]', self.uheight) name = name.replace('[Version]', VERSION) name = name.replace('[HighPrecision]', self.high_precision) if self.data is not None: name = name.replace('[FirstSave]', str(round_int(self.data['Time']['Created']))) name = name.replace('[LatestSave]', str(round_int(self.data['Time']['Modified']))) name = name.replace('[FileVersion]', str(self.data['Version'])) name = name.replace('[TimesLoaded]', str(self.data['TimesLoaded'])) name = name.replace('[Sessions]', str(len(self.data['SessionStarts']))) ticks = self.data['Ticks']['Total'] name = name.replace('[Ticks]', str(int(ticks))) name = name.replace('[RunningTimeSeconds]', str(round_int(ticks / UPDATES_PER_SECOND))) name = name.replace('[RunningTimeMinutes]', str(round(ticks / (UPDATES_PER_SECOND * 60), 2))) name = name.replace('[RunningTimeHours]', str(round(ticks / (UPDATES_PER_SECOND * 60 * 60), 2))) name = name.replace('[RunningTimeDays]', str(round(ticks / (UPDATES_PER_SECOND * 60 * 60 * 24), 2))) #Specific options if image_type == 'clicks': name = name.replace('[Colours]', self.heatmap_colours) name = name.replace('[MouseButton]', self.heatmap_button_group) name = name.replace('[GaussianBlur]', self.heatmap_gaussian) name = name.replace('[GaussianSigma]', self.heatmap_gaussian_actual) elif image_type == 'tracks': name = name.replace('[Colours]', self.track_colour) elif image_type.lower() == 'keyboard': name = name.replace('[Exponential]', self.keyboard_exponential) name = name.replace('[Colours]', self.keyboard_colour) name = name.replace('[DataSet]', self.keyboard_set) name = name.replace('[Size]', self.keyboard_size_mult) name = name.replace('[Extended]', self.keyboard_extended) elif image_type.startswith('csv'): if image_type == 'csv-clicks': #Using the heatmap mouse buttons saves rewriting parts of the function, #but the config will need edited first to only have one button selected. name = name.replace('[MouseButton]', self.heatmap_button_group) else: raise ValueError('incorred image type: {}'.format(image_type)) #Replace invalid characters invalid_chars = ':*?"<>|' for char in invalid_chars: if char in name: name = name.replace(char, '') if image_type.startswith('csv'): ext = 'csv' else: ext = CONFIG['GenerateImages']['FileType'] return '{}.{}'.format(format_file_path(name), ext)
def __iter__(self): for k, v in get_items(self): if self.hidden and k.startswith('_'): continue yield k, v['value']
def dumps(x, _sign=True): item_type = type(x) try: item_type = DUPLICATES[item_type] except KeyError: pass try: type_id = _TYPES_INDEXED[item_type] except KeyError: raise ValueError('invalid type: {}'.format(item_type)) current = int_to_bin(type_id, TYPE_LEN) if item_type == _NONETYPE: pass elif item_type == bool: item_binary = '1' if x else '0' current += item_binary elif item_type in (tuple, list, set): length_bytes = int_to_bin(len(x)) current += '0' * (len(length_bytes) - 1) + '1' + length_bytes for item in x: current += dumps(item) elif item_type == int: item_binary = int_to_bin(x, signed=_sign) length_bytes = int_to_bin(len(item_binary)) current += '0' * (len(length_bytes) - 1) + '1' + length_bytes current += item_binary elif item_type == float: if math.isnan(x): current += '10' elif math.isinf(x): current += '11' else: sign = ('1' if x < 0 else '0') parts = str(x).split('.') if parts[1] == '0': current += '00' + sign + dumps(int(parts[0]), _sign=False) else: current += '01' + sign + dumps(int( parts[0]), _sign=False) + dumps(int(parts[1]), _sign=False) elif item_type == str: length_bytes = int_to_bin(len(x)) current += '0' * (len(length_bytes) - 1) + '1' + length_bytes for integer in str_to_ints(x): current += dumps(integer, _sign=False) elif item_type == complex: real = dumps(int(x.real)) imaginary = dumps(int(x.imag)) current += real + imaginary elif item_type == dict: length_bytes = int_to_bin(len(x)) current += '0' * (len(length_bytes) - 1) + '1' + length_bytes for k, v in get_items(x): current += dumps(k) + dumps(v) return current