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
Exemple #2
0
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
Exemple #3
0
    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
Exemple #4
0
    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
Exemple #5
0
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
Exemple #6
0
    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
Exemple #7
0
 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()
Exemple #8
0
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]
Exemple #9
0
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'])
Exemple #10
0
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
Exemple #11
0
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
Exemple #13
0
    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'
Exemple #14
0
 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
Exemple #15
0
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
                    }
Exemple #16
0
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)
Exemple #17
0
    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)
Exemple #18
0
    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
Exemple #19
0
    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
Exemple #20
0
 def __iter__(self):
     """Show only values when converting to dict."""
     for k, v in get_items(self):
         yield k, _ConfigDict(v)
Exemple #21
0
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()
Exemple #22
0
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
Exemple #23
0
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
Exemple #24
0
 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)
Exemple #25
0
 def __iter__(self):
     for k, v in get_items(self):
         if self.hidden and k.startswith('_'):
             continue
         yield k, v['value']
Exemple #26
0
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