(q_recv, q_send)
Function to handle all the data from the main thread.
Function to handle all the data from the main thread.
Function to handle all the data from the main thread .
def background_process(q_recv, q_send):
    """Function to handle all the data from the main thread."""
    try:
        NOTIFY(LANGUAGE.strings['Tracking']['ScriptThreadStart']).put(q_send)
        set_priority('low')
        
        store = {'Data': {None: LoadData()},
                 'Applications': {
                     DEFAULT_NAME: {
                        'Data': LoadData(),
                        'ActivitySinceLastSave': False,
                        'SavesSinceLastActivity': 0,
                     },
                 },
                 'CurrentProgram': None,
                 'CurrentProgramName': DEFAULT_NAME,
                 'Resolution': None,
                 'MonitorLimits': None,
                 'Offset': (0, 0),
                 'LastResolution': None,
                 'ActivitySinceLastSave': False,
                 'ApplicationResolution': None,
                 'LastClick': None,
                 'KeyTrack': {'LastKey': None,
                              'Time': None,
                              'Backspace': False},
                 'FirstLoad': True,
                 'LastTrackUpdate': 0,
                 'LastIdle': 0,
                 'ProcessIDs': defaultdict(set)
                }
        
        NOTIFY(LANGUAGE.strings['Tracking']['ProfileLoad'])
        _notify_queue_size(q_recv)
        NOTIFY.put(q_send)
        
        while True:
            received_data = q_recv.get()
            data = store['Applications'][store['CurrentProgramName']]['Data']
            
            #Increment the amount of time the script has been running for
            if 'Ticks' in received_data:
                data['Ticks']['Total'] += received_data['Ticks']['Total']
                data['Sessions'][-1][1] += received_data['Ticks']['Total']
                
                #Increment idle time if it reaches a threshold (>10 seconds)
                if store['LastIdle'] > received_data['Ticks']['Idle'] and store['LastIdle'] > CONFIG['Advanced']['IdleTime']:
                    data['Sessions'][-1][2] += store['LastIdle'] + CONFIG['Advanced']['IdleTime']
                store['LastIdle'] = received_data['Ticks']['Idle']
            
            #Save the data
            if 'Save' in received_data:
                remove_applications = []
                for application_name, application_data in store['Applications'].items():

                    #Data has been modified
                    if application_data['ActivitySinceLastSave']:
                        _save_wrapper(q_send, application_name, application_data['Data'])
                        application_data['ActivitySinceLastSave'] = False
                        application_data['SavesSinceLastActivity'] = 0
                        _notify_queue_size(q_recv)

                    #Data hasn't been modified
                    else:
                        application_data['SavesSinceLastActivity'] += 1
                        
                        #Mark the data for deletion to free up memory
                        NOTIFY('{}: {}'.format(application_name, application_data['SavesSinceLastActivity']), 2)
                        if application_data['SavesSinceLastActivity'] > CONFIG['Save']['SavesBeforeUnload']:
                            if application_name != store['CurrentProgramName']:
                                remove_applications.append(application_name)

                        #Detect why the save was skipped
                        try:
                            queue_size = q_recv.qsize()
                        except NotImplementedError:
                            queue_size = 0
                        
                        #Only show inactivity on the current program
                        #There shouldn't be any case where the current program is inactive and any others aren't
                        if application_name == store['CurrentProgramName']:
                            #Two different save commands probably next to each other
                            if queue_size > 2:
                                NOTIFY(LANGUAGE.strings['Tracking']['SaveSkipNoChange'], APPLICATION_NAME=application_name)
                            
                            #No activity since previous save
                            else:
                                time_since_save = CONFIG['Save']['Frequency'] * application_data['SavesSinceLastActivity']
                                seconds = int(time_since_save)
                                minutes = round(time_since_save / 60, 2)
                                if not minutes % 1:
                                    minutes = int(minutes)
                                hours = round(time_since_save / 3600, 2)
                                if not hours % 1:
                                    hours = int(hours)
                                seconds_plural = LANGUAGE.strings['Words'][('TimeSecondSingle', 'TimeSecondPlural')[seconds != 1]]
                                minutes_plural = LANGUAGE.strings['Words'][('TimeMinuteSingle', 'TimeMinutePlural')[minutes != 1]]
                                hours_plural = LANGUAGE.strings['Words'][('TimeHourSingle', 'TimeHourPlural')[hours != 1]]
                                NOTIFY(LANGUAGE.strings['Tracking']['SaveSkipInactivity'], 
                                    SECONDS=seconds, SECONDS_PLURAL=seconds_plural,
                                    MINUTES=minutes, MINUTES_PLURAL=minutes_plural,
                                    HOURS=hours, HOURS_PLURAL=hours_plural,
                                    APPLICATION_NAME=application_name
                                )
                q_send.put({'SaveFinished': None})

                NOTIFY(str(remove_applications), 2)
                for application_name in remove_applications:
                    del store['Applications'][application_name]
                    NOTIFY(LANGUAGE.strings['Tracking']['ApplicationUnload'], APPLICATION_NAME=application_name)

            update_resolution = False
            
            #Check for new program loaded
            if 'Program' in received_data:
                process_id, current_program = received_data['Program']
                
                if current_program != store['CurrentProgram']:
                    update_resolution = True
                    
                    store['CurrentProgramName'] = current_program[0] if current_program is not None else DEFAULT_NAME
                    NOTIFY(LANGUAGE.strings['Tracking']['ApplicationLoad'], APPLICATION_NAME=store['CurrentProgramName']).put(q_send)

                    # Load from cache
                    if store['CurrentProgramName'] in store['Applications']:
                        store['CurrentProgram'] = current_program
                        data = store['Applications'][store['CurrentProgramName']]['Data']

                    # Load from file
                    else:
                        #Load new profile
                        allow_new_session = current_program is not None or current_program is None and store['CurrentProgram'] is None
                        try:
                            if process_id is not None and process_id in store['ProcessIDs'][current_program]:
                                allow_new_session = False
                        except KeyError:
                            pass
                        if process_id is not None:
                            store['ProcessIDs'][current_program].add(process_id)
                        data = LoadData(current_program, _reset_sessions=allow_new_session)
                        store['CurrentProgram'] = current_program
                        store['Applications'][store['CurrentProgramName']] = {
                            'Data': data,
                            'ActivitySinceLastSave': False,
                            'SavesSinceLastActivity': 0,
                        }
                        
                        #Check new resolution
                        try:
                            store['ApplicationResolution'] = received_data['ApplicationResolution']
                        except KeyError:
                            pass
                        if store['ApplicationResolution'] is None:
                            check_resolution(data, store['Resolution'])
                        else:
                            check_resolution(data, store['ApplicationResolution'][1])
                            
                        if data['Ticks']['Total']:
                            NOTIFY(LANGUAGE.strings['Tracking']['ProfileLoad'])
                        else:
                            NOTIFY(LANGUAGE.strings['Tracking']['ProfileNew'])

                    _notify_queue_size(q_recv)
                NOTIFY.put(q_send)
            
            if 'ApplicationResolution' in received_data:
                store['ApplicationResolution'] = received_data['ApplicationResolution']
                if store['ApplicationResolution'] is not None:
                    check_resolution(data, store['ApplicationResolution'][1])
                    update_resolution = True

            if 'Resolution' in received_data:
                store['Resolution'] = received_data['Resolution']
                check_resolution(data, received_data['Resolution'])
                update_resolution = True
            
            if 'MonitorLimits' in received_data:
                store['MonitorLimits'] = received_data['MonitorLimits']
                update_resolution = True
            
            #Keep the history tracking the correct resolution
            if update_resolution and CONFIG['Main']['HistoryLength']:
                if store['ApplicationResolution'] is not None:
                    history_resolution = store['ApplicationResolution']
                elif MULTI_MONITOR:
                    history_resolution = store['MonitorLimits']
                else:
                    history_resolution = store['Resolution']
                try:
                    if data['HistoryAnimation']['Tracks'][-1][0] != history_resolution:
                        raise IndexError
                except IndexError:
                    data['HistoryAnimation']['Tracks'].append([history_resolution])
            
            #Record key presses
            if 'KeyPress' in received_data:
                store['Applications'][store['CurrentProgramName']]['ActivitySinceLastSave'] = True
                record_key_press(store, received_data['KeyPress'])
            
            #Record time keys are held down
            if 'KeyHeld' in received_data:
                record_key_held(store, received_data['KeyHeld'])
                store['Applications'][store['CurrentProgramName']]['ActivitySinceLastSave'] = True
            
            #Record button presses
            if 'GamepadButtonPress' in received_data:
                store['Applications'][store['CurrentProgramName']]['ActivitySinceLastSave'] = True
                record_gamepad_pressed(store, received_data['GamepadButtonPress'])
            
            #Record how long buttons are held
            if 'GamepadButtonHeld' in received_data:
                store['Applications'][store['CurrentProgramName']]['ActivitySinceLastSave'] = True
                record_gamepad_held(store, received_data['GamepadButtonHeld'])
                        
            #Axis updates
            if 'GamepadAxis' in received_data:
                store['Applications'][store['CurrentProgramName']]['ActivitySinceLastSave'] = True
                record_gamepad_axis(store, received_data['GamepadAxis'])
            
            #Calculate and track mouse movement
            if 'MouseMove' in received_data:
                store['Applications'][store['CurrentProgramName']]['ActivitySinceLastSave'] = True
                record_mouse_move(store, received_data['MouseMove'])
                
                #Add to history if set
                if CONFIG['Main']['HistoryLength']:
                    data['HistoryAnimation']['Tracks'][-1].append(received_data['MouseMove'][1])
                
                #Compress tracks if the count gets too high
                max_track_value = CONFIG['Advanced']['CompressTrackMax']
                if not max_track_value:
                    max_track_value = MAX_INT
                
                if data['Ticks']['Tracks'] > max_track_value:
                    NOTIFY(LANGUAGE.strings['Tracking']['CompressStart'], TRACK_TYPE='tracks').put(q_send)
                    
                    compress_tracks(store, CONFIG['Advanced']['CompressTrackAmount'])
                    
                    NOTIFY(LANGUAGE.strings['Tracking']['CompressEnd'], TRACK_TYPE='tracks')
                    _notify_queue_size(q_recv)
                
            #Record mouse clicks
            if 'MouseClick' in received_data:
                store['Applications'][store['CurrentProgramName']]['ActivitySinceLastSave'] = True
                record_click_single(store, received_data['MouseClick'])
                    
            #Record double clicks
            if 'DoubleClick' in received_data:
                store['Applications'][store['CurrentProgramName']]['ActivitySinceLastSave'] = True
                record_click_double(store, received_data['DoubleClick'])
            
            #Trim the history list if too long
            if 'HistoryCheck' in received_data:
                max_length = CONFIG['Main']['HistoryLength'] * UPDATES_PER_SECOND
                history_trim(store, max_length)
                        
            data['Ticks']['Recorded'] += 1
            
            if 'Quit' in received_data or 'Exit' in received_data:
                return

            NOTIFY.put(q_send)
        
        #Exit process (this shouldn't happen for now)
        NOTIFY(LANGUAGE.strings['Tracking']['ScriptThreadEnd']).put(q_send)
        _save_wrapper(q_send, store['CurrentProgramName'], data)
            
    except Exception:
        q_send.put(traceback.format_exc())
        
    except KeyboardInterrupt:
        pass
def background_process ( q_recv , q_send ) : try : NOTIFY ( LANGUAGE . strings [ 'Tracking' ] [ 'ScriptThreadStart' ] ) . put ( q_send ) set_priority ( 'low' ) store = { 'Data' : { None : LoadData ( ) } , 'Applications' : { DEFAULT_NAME : { 'Data' : LoadData ( ) , 'ActivitySinceLastSave' : False , 'SavesSinceLastActivity' : 0 , } , } , 'CurrentProgram' : None , 'CurrentProgramName' : DEFAULT_NAME , 'Resolution' : None , 'MonitorLimits' : None , 'Offset' : ( 0 , 0 ) , 'LastResolution' : None , 'ActivitySinceLastSave' : False , 'ApplicationResolution' : None , 'LastClick' : None , 'KeyTrack' : { 'LastKey' : None , 'Time' : None , 'Backspace' : False } , 'FirstLoad' : True , 'LastTrackUpdate' : 0 , 'LastIdle' : 0 , 'ProcessIDs' : defaultdict ( set ) } NOTIFY ( LANGUAGE . strings [ 'Tracking' ] [ 'ProfileLoad' ] ) _notify_queue_size ( q_recv ) NOTIFY . put ( q_send ) while True : received_data = q_recv . get ( ) data = store [ 'Applications' ] [ store [ 'CurrentProgramName' ] ] [ 'Data' ] #Increment the amount of time the script has been running for if 'Ticks' in received_data : data [ 'Ticks' ] [ 'Total' ] += received_data [ 'Ticks' ] [ 'Total' ] data [ 'Sessions' ] [ - 1 ] [ 1 ] += received_data [ 'Ticks' ] [ 'Total' ] #Increment idle time if it reaches a threshold (>10 seconds) if store [ 'LastIdle' ] > received_data [ 'Ticks' ] [ 'Idle' ] and store [ 'LastIdle' ] > CONFIG [ 'Advanced' ] [ 'IdleTime' ] : data [ 'Sessions' ] [ - 1 ] [ 2 ] += store [ 'LastIdle' ] + CONFIG [ 'Advanced' ] [ 'IdleTime' ] store [ 'LastIdle' ] = received_data [ 'Ticks' ] [ 'Idle' ] #Save the data if 'Save' in received_data : remove_applications = [ ] for application_name , application_data in store [ 'Applications' ] . items ( ) : #Data has been modified if application_data [ 'ActivitySinceLastSave' ] : _save_wrapper ( q_send , application_name , application_data [ 'Data' ] ) application_data [ 'ActivitySinceLastSave' ] = False application_data [ 'SavesSinceLastActivity' ] = 0 _notify_queue_size ( q_recv ) #Data hasn't been modified else : application_data [ 'SavesSinceLastActivity' ] += 1 #Mark the data for deletion to free up memory NOTIFY ( '{}: {}' . format ( application_name , application_data [ 'SavesSinceLastActivity' ] ) , 2 ) if application_data [ 'SavesSinceLastActivity' ] > CONFIG [ 'Save' ] [ 'SavesBeforeUnload' ] : if application_name != store [ 'CurrentProgramName' ] : remove_applications . append ( application_name ) #Detect why the save was skipped try : queue_size = q_recv . qsize ( ) except NotImplementedError : queue_size = 0 #Only show inactivity on the current program #There shouldn't be any case where the current program is inactive and any others aren't if application_name == store [ 'CurrentProgramName' ] : #Two different save commands probably next to each other if queue_size > 2 : NOTIFY ( LANGUAGE . strings [ 'Tracking' ] [ 'SaveSkipNoChange' ] , APPLICATION_NAME = application_name ) #No activity since previous save else : time_since_save = CONFIG [ 'Save' ] [ 'Frequency' ] * application_data [ 'SavesSinceLastActivity' ] seconds = int ( time_since_save ) minutes = round ( time_since_save / 60 , 2 ) if not minutes % 1 : minutes = int ( minutes ) hours = round ( time_since_save / 3600 , 2 ) if not hours % 1 : hours = int ( hours ) seconds_plural = LANGUAGE . strings [ 'Words' ] [ ( 'TimeSecondSingle' , 'TimeSecondPlural' ) [ seconds != 1 ] ] minutes_plural = LANGUAGE . strings [ 'Words' ] [ ( 'TimeMinuteSingle' , 'TimeMinutePlural' ) [ minutes != 1 ] ] hours_plural = LANGUAGE . strings [ 'Words' ] [ ( 'TimeHourSingle' , 'TimeHourPlural' ) [ hours != 1 ] ] NOTIFY ( LANGUAGE . strings [ 'Tracking' ] [ 'SaveSkipInactivity' ] , SECONDS = seconds , SECONDS_PLURAL = seconds_plural , MINUTES = minutes , MINUTES_PLURAL = minutes_plural , HOURS = hours , HOURS_PLURAL = hours_plural , APPLICATION_NAME = application_name ) q_send . put ( { 'SaveFinished' : None } ) NOTIFY ( str ( remove_applications ) , 2 ) for application_name in remove_applications : del store [ 'Applications' ] [ application_name ] NOTIFY ( LANGUAGE . strings [ 'Tracking' ] [ 'ApplicationUnload' ] , APPLICATION_NAME = application_name ) update_resolution = False #Check for new program loaded if 'Program' in received_data : process_id , current_program = received_data [ 'Program' ] if current_program != store [ 'CurrentProgram' ] : update_resolution = True store [ 'CurrentProgramName' ] = current_program [ 0 ] if current_program is not None else DEFAULT_NAME NOTIFY ( LANGUAGE . strings [ 'Tracking' ] [ 'ApplicationLoad' ] , APPLICATION_NAME = store [ 'CurrentProgramName' ] ) . put ( q_send ) # Load from cache if store [ 'CurrentProgramName' ] in store [ 'Applications' ] : store [ 'CurrentProgram' ] = current_program data = store [ 'Applications' ] [ store [ 'CurrentProgramName' ] ] [ 'Data' ] # Load from file else : #Load new profile allow_new_session = current_program is not None or current_program is None and store [ 'CurrentProgram' ] is None try : if process_id is not None and process_id in store [ 'ProcessIDs' ] [ current_program ] : allow_new_session = False except KeyError : pass if process_id is not None : store [ 'ProcessIDs' ] [ current_program ] . add ( process_id ) data = LoadData ( current_program , _reset_sessions = allow_new_session ) store [ 'CurrentProgram' ] = current_program store [ 'Applications' ] [ store [ 'CurrentProgramName' ] ] = { 'Data' : data , 'ActivitySinceLastSave' : False , 'SavesSinceLastActivity' : 0 , } #Check new resolution try : store [ 'ApplicationResolution' ] = received_data [ 'ApplicationResolution' ] except KeyError : pass if store [ 'ApplicationResolution' ] is None : check_resolution ( data , store [ 'Resolution' ] ) else : check_resolution ( data , store [ 'ApplicationResolution' ] [ 1 ] ) if data [ 'Ticks' ] [ 'Total' ] : NOTIFY ( LANGUAGE . strings [ 'Tracking' ] [ 'ProfileLoad' ] ) else : NOTIFY ( LANGUAGE . strings [ 'Tracking' ] [ 'ProfileNew' ] ) _notify_queue_size ( q_recv ) NOTIFY . put ( q_send ) if 'ApplicationResolution' in received_data : store [ 'ApplicationResolution' ] = received_data [ 'ApplicationResolution' ] if store [ 'ApplicationResolution' ] is not None : check_resolution ( data , store [ 'ApplicationResolution' ] [ 1 ] ) update_resolution = True if 'Resolution' in received_data : store [ 'Resolution' ] = received_data [ 'Resolution' ] check_resolution ( data , received_data [ 'Resolution' ] ) update_resolution = True if 'MonitorLimits' in received_data : store [ 'MonitorLimits' ] = received_data [ 'MonitorLimits' ] update_resolution = True #Keep the history tracking the correct resolution if update_resolution and CONFIG [ 'Main' ] [ 'HistoryLength' ] : if store [ 'ApplicationResolution' ] is not None : history_resolution = store [ 'ApplicationResolution' ] elif MULTI_MONITOR : history_resolution = store [ 'MonitorLimits' ] else : history_resolution = store [ 'Resolution' ] try : if data [ 'HistoryAnimation' ] [ 'Tracks' ] [ - 1 ] [ 0 ] != history_resolution : raise IndexError except IndexError : data [ 'HistoryAnimation' ] [ 'Tracks' ] . append ( [ history_resolution ] ) #Record key presses if 'KeyPress' in received_data : store [ 'Applications' ] [ store [ 'CurrentProgramName' ] ] [ 'ActivitySinceLastSave' ] = True record_key_press ( store , received_data [ 'KeyPress' ] ) #Record time keys are held down if 'KeyHeld' in received_data : record_key_held ( store , received_data [ 'KeyHeld' ] ) store [ 'Applications' ] [ store [ 'CurrentProgramName' ] ] [ 'ActivitySinceLastSave' ] = True #Record button presses if 'GamepadButtonPress' in received_data : store [ 'Applications' ] [ store [ 'CurrentProgramName' ] ] [ 'ActivitySinceLastSave' ] = True record_gamepad_pressed ( store , received_data [ 'GamepadButtonPress' ] ) #Record how long buttons are held if 'GamepadButtonHeld' in received_data : store [ 'Applications' ] [ store [ 'CurrentProgramName' ] ] [ 'ActivitySinceLastSave' ] = True record_gamepad_held ( store , received_data [ 'GamepadButtonHeld' ] ) #Axis updates if 'GamepadAxis' in received_data : store [ 'Applications' ] [ store [ 'CurrentProgramName' ] ] [ 'ActivitySinceLastSave' ] = True record_gamepad_axis ( store , received_data [ 'GamepadAxis' ] ) #Calculate and track mouse movement if 'MouseMove' in received_data : store [ 'Applications' ] [ store [ 'CurrentProgramName' ] ] [ 'ActivitySinceLastSave' ] = True record_mouse_move ( store , received_data [ 'MouseMove' ] ) #Add to history if set if CONFIG [ 'Main' ] [ 'HistoryLength' ] : data [ 'HistoryAnimation' ] [ 'Tracks' ] [ - 1 ] . append ( received_data [ 'MouseMove' ] [ 1 ] ) #Compress tracks if the count gets too high max_track_value = CONFIG [ 'Advanced' ] [ 'CompressTrackMax' ] if not max_track_value : max_track_value = MAX_INT if data [ 'Ticks' ] [ 'Tracks' ] > max_track_value : NOTIFY ( LANGUAGE . strings [ 'Tracking' ] [ 'CompressStart' ] , TRACK_TYPE = 'tracks' ) . put ( q_send ) compress_tracks ( store , CONFIG [ 'Advanced' ] [ 'CompressTrackAmount' ] ) NOTIFY ( LANGUAGE . strings [ 'Tracking' ] [ 'CompressEnd' ] , TRACK_TYPE = 'tracks' ) _notify_queue_size ( q_recv ) #Record mouse clicks if 'MouseClick' in received_data : store [ 'Applications' ] [ store [ 'CurrentProgramName' ] ] [ 'ActivitySinceLastSave' ] = True record_click_single ( store , received_data [ 'MouseClick' ] ) #Record double clicks if 'DoubleClick' in received_data : store [ 'Applications' ] [ store [ 'CurrentProgramName' ] ] [ 'ActivitySinceLastSave' ] = True record_click_double ( store , received_data [ 'DoubleClick' ] ) #Trim the history list if too long if 'HistoryCheck' in received_data : max_length = CONFIG [ 'Main' ] [ 'HistoryLength' ] * UPDATES_PER_SECOND history_trim ( store , max_length ) data [ 'Ticks' ] [ 'Recorded' ] += 1 if 'Quit' in received_data or 'Exit' in received_data : return NOTIFY . put ( q_send ) #Exit process (this shouldn't happen for now) NOTIFY ( LANGUAGE . strings [ 'Tracking' ] [ 'ScriptThreadEnd' ] ) . put ( q_send ) _save_wrapper ( q_send , store [ 'CurrentProgramName' ] , data ) except Exception : q_send . put ( traceback . format_exc ( ) ) except KeyboardInterrupt : pass