예제 #1
0
def track_data(name, data):
    """ Track arbitrary data.

        Calling this function will generate a data tracking event.

        :param name: The identifier of the data.
        :type name: str

        :param data: Arbitrary data, must be compatible with JSON.
        :type data: dict, list, str, int, float, None
    """

    event = {
        "type": "data",
        "time": int(time.time()),
        "timezone_offset": get_utc_offset(),
        "os_version": OS_VERSION,
        "cpu_id": CPU_ID,
        "token": TOKEN,

        "name": str(name),
        "data": data
    }

    try:
        af = open_locked(tracker_events_file, "a")
    except IOError as e:
        logger.error('Error opening tracker events file {}'.format(e))
    else:
        with af:
            af.write(json.dumps(event) + "\n")
        if 'SUDO_USER' in os.environ:
            chown_path(tracker_events_file)
예제 #2
0
def set_locale_param(param, locale, skip_check=False):
    if not skip_check and not is_locale_installed(locale):
        install_locale(locale)

    param_found = False
    new_param_line = 'export {}={}'.format(param, locale)
    new_config_file = []

    if os.path.exists(XSESSION_RC_FILE):
        xsession_file = read_file_contents_as_lines(XSESSION_RC_FILE)

        for line in xsession_file:
            if param in line:
                line = new_param_line
                param_found = True

            new_config_file.append(line)

    if not param_found:
        new_config_file.append(new_param_line)

    with open(XSESSION_RC_FILE, 'w') as conf_file:
        conf_file.write('\n'.join(new_config_file))

    chown_path(XSESSION_RC_FILE)
예제 #3
0
def session_end(session_file):
    if not os.path.exists(session_file):
        msg = "Someone removed the tracker file, the runtime of this " + \
            "app will not be logged"
        logger.warn(msg)
        return

    try:
        rf = open_locked(session_file, "r")
    except IOError as e:
        logger.error('Error opening the tracker session file {}'.format(e))
    else:
        with rf:
            data = json.load(rf)

            data["elapsed"] = int(time.time()) - data["started"]
            data["finished"] = True

            try:
                wf = open(session_file, "w")
            except IOError as e:
                logger.error(
                    'Error opening the tracker session file {}'.format(e))
            else:
                with wf:
                    json.dump(data, wf)
                if 'SUDO_USER' in os.environ:
                    chown_path(data)
예제 #4
0
def session_log(name, started, length):
    """ Log a session that was tracked outside of the tracker.

        :param name: The identifier of the session.
        :type name: str

        :param started: When was the session started (UTC unix timestamp).
        :type started: int

        :param length: Length of the session in seconds.
        :param started: int
    """

    try:
        af = open_locked(tracker_events_file, 'a')
    except IOError as e:
        logger.error('Error while opening events file'.format(e))
    else:
        with af:
            session = {
                "name": name,
                "started": int(started),
                "elapsed": int(length)
            }

            event = get_session_event(session)
            af.write(json.dumps(event) + "\n")
        if 'SUDO_USER' in os.environ:
            chown_path(tracker_events_file)
예제 #5
0
def generate_tracker_token():
    """
        Generating the token is a simple md5hash of the current time.

        The token is saved to the `tracker_token_file`.

        :returns: The token.
        :rtype: str
    """

    token = hashlib.md5(str(time.time())).hexdigest()

    ensure_dir(tracker_dir)
    try:
        f = open_locked(tracker_token_file, "w")
    except IOError as e:
        logger.error(
            'Error opening tracker token file (generate) {}'.format(e))
    else:
        with f:
            f.write(token)
        if 'SUDO_USER' in os.environ:
            chown_path(tracker_token_file)

    # Make sure that the events file exist
    try:
        f = open(tracker_events_file, 'a')
    except IOError as e:
        logger.error('Error opening tracker events file {}'.format(e))
    else:
        f.close()
        if 'SUDO_USER' in os.environ:
            chown_path(tracker_events_file)

    return token
예제 #6
0
def session_start(name, pid=None):
    if not pid:
        pid = os.getpid()
    pid = int(pid)

    data = {
        "pid": pid,
        "name": name,
        "started": int(time.time()),
        "elapsed": 0,
        "finished": False
    }

    path = get_session_file_path(data['name'], data['pid'])

    try:
        f = open_locked(path, "w")
    except IOError as e:
        logger.error('Error opening tracker session file {}'.format(e))
    else:
        with f:
            json.dump(data, f)
        if 'SUDO_USER' in os.environ:
            chown_path(path)

    return path
예제 #7
0
def set_locale_param(param, locale, skip_check=False):
    # FIXME: Don't use the .xsessionrc file to set the locale
    XSESSION_RC_FILE = os.path.join(get_home_by_username(get_user_unsudoed()),
                                    '.xsessionrc')
    if not skip_check and not is_locale_installed(locale):
        install_locale(locale)

    param_found = False
    new_param_line = 'export {}={}'.format(param, locale)
    new_config_file = []

    if os.path.exists(XSESSION_RC_FILE):
        xsession_file = read_file_contents_as_lines(XSESSION_RC_FILE)

        for line in xsession_file:
            if param in line:
                line = new_param_line
                param_found = True

            new_config_file.append(line)

    if not param_found:
        new_config_file.append(new_param_line)

    with open(XSESSION_RC_FILE, 'w') as conf_file:
        conf_file.write('\n'.join(new_config_file))

    chown_path(XSESSION_RC_FILE)
예제 #8
0
def set_locale_param(param, locale, skip_check=False):
    # FIXME: Don't use the .xsessionrc file to set the locale
    XSESSION_RC_FILE = os.path.join(get_home_by_username(get_user_unsudoed()),
                                    '.xsessionrc')
    if not skip_check and not is_locale_installed(locale):
        install_locale(locale)

    param_found = False
    new_param_line = 'export {}={}'.format(param, locale)
    new_config_file = []

    if os.path.exists(XSESSION_RC_FILE):
        xsession_file = read_file_contents_as_lines(XSESSION_RC_FILE)

        for line in xsession_file:
            if param in line:
                line = new_param_line
                param_found = True

            new_config_file.append(line)

    if not param_found:
        new_config_file.append(new_param_line)

    with open(XSESSION_RC_FILE, 'w') as conf_file:
        conf_file.write('\n'.join(new_config_file))

    chown_path(XSESSION_RC_FILE)
예제 #9
0
def session_start(name, pid=None):
    if not pid:
        pid = os.getpid()
    pid = int(pid)

    data = {
        'pid': pid,
        'name': name,
        'started': int(time.time()),
        'elapsed': 0,
        'app_session_id': str(uuid5(uuid1(), name + str(pid))),
        'finished': False,
        'token-system': TOKEN
    }

    path = get_session_file_path(data['name'], data['pid'])

    try:
        f = open_locked(path, 'w')
    except IOError as e:
        logger.error("Error opening tracker session file {}".format(e))
    else:
        with f:
            json.dump(data, f)
        if 'SUDO_USER' in os.environ:
            chown_path(path)

    return path
예제 #10
0
def clear_tracker_events(old_only=True):
    """ Truncate the events file, removing all the cached data.

        :param old_only: Don't remove data from the current boot.
        :type old_only: boolean
    """
    try:
        rf = open_locked(tracker_events_file, "r")
    except IOError as e:
        logger.error('Error opening tracking events file {}'.format(e))
    else:
        with rf:
            events = []
            for event_line in rf.readlines():
                try:
                    event = json.loads(event_line)
                    if 'token' in event and event['token'] == TOKEN:
                        events.append(event_line)
                except:
                    logger.warn("Found a corrupted event, skipping.")

            with open(tracker_events_file, "w") as wf:
                for event_line in events:
                    wf.write(event_line)
            if 'SUDO_USER' in os.environ:
                chown_path(tracker_events_file)
예제 #11
0
    def download_online_badges(self):
        profile = load_profile()
        if "kanoworld_id" in profile:
            user_id = profile["kanoworld_id"]
        else:
            return False, "Profile not registered!"

        success, text, data = request_wrapper("get", "/users/{}".format(user_id), session=self.session)

        if not success:
            return False, text

        if "user" not in data:
            return False, "Corrupt response (the 'user' key not found)"

        if "profile" not in data["user"]:
            return False, "Corrupt response (the 'user.profile' key not found)"

        if "badges" not in data["user"]["profile"]:
            msg = "Corrupt response (the 'user.profile.badges' key not found)"
            return False, msg

        online_badges_data = {}

        ensure_dir(online_badges_dir)

        badges = data["user"]["profile"]["badges"]
        for badge in badges:
            if "assigned" not in badge or not badge["assigned"]:
                continue

            if "image_url" not in badge:
                return False, "Couldn't find an image for the badge"

            image_loc = os.path.join(online_badges_dir, "{}.png".format(badge["id"]))
            download_url(badge["image_url"], image_loc)

            online_badges_data[badge["id"]] = {
                "achieved": True,
                "bg_color": badge["bg_color"].replace("#", ""),
                "desc_locked": badge["desc_locked"],
                "desc_unlocked": badge["desc_unlocked"],
                "title": badge["title"],
            }

        try:
            may_write = True
            txt = None
            f = open(online_badges_file, "w")
        except IOError as e:
            may_write = False
            txt = "Error opening badges file {}".format(str(e))
        else:
            with f:
                f.write(json.dumps(online_badges_data))
            if "SUDO_USER" in os.environ:
                chown_path(online_badges_dir)
                chown_path(online_badges_file)

        return may_write, txt
예제 #12
0
def set_setting(variable, value):

    if username == 'root':
        return

    logger.debug('config_file / set_setting: {} {}'.format(variable, value))

    data = read_json(settings_file)
    if not data:
        data = dict()

    data[variable] = value
    write_json(settings_file, data)
    chown_path(settings_file)
예제 #13
0
def set_user_cookies(enabled=None, username=None):
    if enabled is None:
        enabled = get_parental_level() >= 2
    if username is None:
        return

    # The cookie enables/disables safety mode in YouTube (Midori)
    # The .db files are located in /usr/share/kano-video
    homedir = "/home/{}".format(username)
    if not os.path.isdir(homedir):
        logger.error("Could not access user home dir: {}".format(homedir))
        return

    # Browser: Cookie needs to be copied to /home/USERNAME/.config/midori
    midori_cookie_path = '{}/{}'.format(homedir, midori_cookie)
    if os.path.exists(browser_safe_cookie) and \
            os.path.exists(browser_nosafe_cookie) and \
            os.path.exists(midori_cookie_path):

        if enabled:
            logger.debug('Enabling Browser Safety mode for browser on user {}'.format(username))
            browser_cookie = browser_safe_cookie
        else:
            logger.debug('Disabling Browser Safety mode for browser on user {}'.format(username))
            browser_cookie = browser_nosafe_cookie

        # Copy cookie for this user
        shutil.copy(browser_cookie, midori_cookie_path)

        # Set correct permissions on file
        chown_path('{}/cookies.db'.format(midori_cookie_path), username, username)

    # YT: copy yo /home/USERNAME/.config/midori/youtube (kano-video-browser)
    youtube_cookie_path = '{}/{}'.format(homedir, youtube_cookie)
    if os.path.exists(youtube_safe_cookie) and \
       os.path.exists(youtube_nosafe_cookie) and \
       os.path.exists(youtube_cookie_path):

        if enabled:
            logger.debug('Enabling YouTube Safety mode for kano-video-browser on user {}'.format(username))
            yt_cookie = youtube_safe_cookie
        else:
            logger.debug('Disabling YT Safety mode for kano-video-browser on user {}'.format(username))
            yt_cookie = youtube_nosafe_cookie

        # Copy cookie for this user
        shutil.copy(yt_cookie, youtube_cookie_path)

        # Set correct permissions on file
        chown_path('{}/cookies.db'.format(youtube_cookie_path), username, username)
예제 #14
0
def track_action(name):
    """ Trigger an action tracking event.

        :param name: The identifier of the action.
        :type name: str
    """

    try:
        af = open_locked(tracker_events_file, 'a')
    except IOError as e:
        logger.error('Error opening tracker events file {}'.format(e))
    else:
        with af:
            event = get_action_event(name)
            af.write(json.dumps(event) + "\n")
        if 'SUDO_USER' in os.environ:
            chown_path(tracker_events_file)
예제 #15
0
def save_app_state_variable_all_users(app, variable, value):
    if os.environ['LOGNAME'] != 'root':
        logger.error(
            "Error: save_app_state_variable_all_users must be executed with root privileges"
        )
        return

    users = get_all_users()

    for user in users:
        dir_path = os.path.join("/home", user, ".kanoprofile", "apps", app)
        if not os.path.exists(dir_path):
            os.makedirs(dir_path)
            chown_path(dir_path, user, user)

        state_path = os.path.join(dir_path, "state.json")
        data = {variable: value}
        data['save_date'] = get_date_now()
        write_json(state_path, data)
        chown_path(state_path, user, user)
예제 #16
0
def save_app_state_variable_all_users(app, variable, value):
    if os.environ['LOGNAME'] != 'root':
        logger.error("Error: save_app_state_variable_all_users must be executed with root privileges")
        return

    users = get_all_users()

    for user in users:
        dir_path = os.path.join(
            "/home", user, ".kanoprofile", "apps", app
        )
        if not os.path.exists(dir_path):
            os.makedirs(dir_path)
            chown_path(dir_path, user, user)

        state_path = os.path.join(dir_path, "state.json")
        data = {variable: value}
        data['save_date'] = get_date_now()
        write_json(state_path, data)
        chown_path(state_path, user, user)
예제 #17
0
def save_app_state(app_name, data):
    """ Save a state of an application to the user's Kano profile.

        :param app_name: The application that this data are associated with.
        :type app_name: str

        :param data: The data to be stored.
        :type data: dict
    """

    logger.debug('save_app_state {}'.format(app_name))

    app_state_file = get_app_state_file(app_name)
    data['save_date'] = get_date_now()
    ensure_dir(get_app_dir(app_name))
    write_json(app_state_file, data)
    if 'SUDO_USER' in os.environ:
        chown_path(kanoprofile_dir)
        chown_path(apps_dir)
        chown_path(get_app_dir(app_name))
        chown_path(app_state_file)
예제 #18
0
def save_app_state(app_name, data):
    """ Save a state of an application to the user's Kano profile.

        :param app_name: The application that this data are associated with.
        :type app_name: str

        :param data: The data to be stored.
        :type data: dict
    """

    logger.debug("save_app_state {}".format(app_name))

    app_state_file = get_app_state_file(app_name)
    data['save_date'] = get_date_now()
    ensure_dir(get_app_dir(app_name))
    write_json(app_state_file, data)
    if 'SUDO_USER' in os.environ:
        chown_path(kanoprofile_dir)
        chown_path(apps_dir)
        chown_path(get_app_dir(app_name))
        chown_path(app_state_file)
예제 #19
0
def save_profile(data):
    ''' Write profile data to file
    :param data: JSON serialisable data about the profile
    '''
    logger.debug('save_profile')

    data.pop('cpu_id', None)
    data.pop('mac_addr', None)
    data['save_date'] = get_date_now()
    ensure_dir(profile_dir)
    write_json(profile_file, data)

    if 'SUDO_USER' in os.environ:
        chown_path(kanoprofile_dir)
        chown_path(profile_dir)
        chown_path(profile_file)

    if os.path.exists('/usr/bin/kdesk') and not is_running('kano-sync'):
        logger.info('refreshing kdesk from save_profile')
        run_bg('kdesk -a profile')
예제 #20
0
def save_profile(data, skip_kdesk_refresh=False):
    ''' Write profile data to file
    :param data: JSON serialisable data about the profile
    '''
    logger.debug('save_profile')

    data.pop('cpu_id', None)
    data.pop('mac_addr', None)
    data['save_date'] = get_date_now()
    ensure_dir(profile_dir)
    write_json(profile_file, data)

    if 'SUDO_USER' in os.environ:
        chown_path(kanoprofile_dir)
        chown_path(profile_dir)
        chown_path(profile_file)

    if (not skip_kdesk_refresh and
            os.path.exists('/usr/bin/kdesk') and
            not is_running('kano-sync')):
        logger.info("refreshing kdesk from save_profile")
        run_bg('kdesk -a profile')
예제 #21
0
def update_folder_from_skel(user_name):
    logger.info("Updating home folder of user: {}".format(user_name))
    src_dir = '/etc/skel'
    dst_dir = os.path.join('/home', user_name)

    dirlinks = []
    filelinks = []
    files = []

    for root, dirs, filenames in os.walk(src_dir):
        for d in dirs:
            path_full = os.path.join(root, d)
            if os.path.islink(path_full):
                dirlinks.append(path_full)

        for f in filenames:
            path_full = os.path.join(root, f)
            if os.path.islink(path_full):
                filelinks.append(path_full)
            else:
                files.append(path_full)

    for path_full in dirlinks + filelinks + files:
        path_rel = os.path.relpath(path_full, src_dir)
        dir_path_rel = os.path.dirname(path_rel)

        dst_path = os.path.join(dst_dir, path_rel)
        dir_dst_path = os.path.join(dst_dir, dir_path_rel)

        # print 'path_full', path_full
        # print 'path_rel', path_rel
        # print 'dir_path_rel', dir_path_rel
        # print 'dst_path', dst_path
        # print 'dir_dst_path', dir_dst_path
        # print

        if os.path.exists(dst_path):
            if os.path.islink(dst_path):
                logger.info("removing link: {}".format(dst_path))
                os.unlink(dst_path)

            elif os.path.isdir(dst_path):
                logger.info("removing dir: {}".format(dst_path))
                shutil.rmtree(dst_path)

            elif os.path.isfile(dst_path):
                logger.info("removing file: {}".format(dst_path))
                os.remove(dst_path)

        # make sure that destination directory exists
        if os.path.exists(dir_dst_path):
            if not os.path.isdir(dir_dst_path):
                os.remove(dir_dst_path)
        else:
            logger.info("making needed dir: {}".format(dir_dst_path))
            os.makedirs(dir_dst_path)
            chown_path(dir_dst_path, user=user_name, group=user_name)

        # creating links
        if os.path.islink(path_full):
            linkto = os.readlink(path_full)
            msg = "creating link {} -> {}".format(dst_path, linkto)
            logger.info(msg)
            os.symlink(linkto, dst_path)
            chown_path(dst_path, user=user_name, group=user_name)

        elif os.path.isfile(path_full):
            msg = "copying file {} -> {}".format(path_full, dst_path)
            logger.info(msg)
            shutil.copy(path_full, dst_path)
            chown_path(dst_path, user=user_name, group=user_name)
예제 #22
0
def update_folder_from_skel(user_name):
    logger.info("Updating home folder of user: {}".format(user_name))
    src_dir = '/etc/skel'
    dst_dir = os.path.join('/home', user_name)

    dirlinks = []
    filelinks = []
    files = []

    for root, dirs, filenames in os.walk(src_dir):
        for d in dirs:
            path_full = os.path.join(root, d)
            if os.path.islink(path_full):
                dirlinks.append(path_full)

        for f in filenames:
            path_full = os.path.join(root, f)
            if os.path.islink(path_full):
                filelinks.append(path_full)
            else:
                files.append(path_full)

    for path_full in dirlinks + filelinks + files:
        path_rel = os.path.relpath(path_full, src_dir)
        dir_path_rel = os.path.dirname(path_rel)

        dst_path = os.path.join(dst_dir, path_rel)
        dir_dst_path = os.path.join(dst_dir, dir_path_rel)

        # print 'path_full', path_full
        # print 'path_rel', path_rel
        # print 'dir_path_rel', dir_path_rel
        # print 'dst_path', dst_path
        # print 'dir_dst_path', dir_dst_path
        # print

        if os.path.exists(dst_path):
            if os.path.islink(dst_path):
                logger.info("removing link: {}".format(dst_path))
                os.unlink(dst_path)

            elif os.path.isdir(dst_path):
                logger.info("removing dir: {}".format(dst_path))
                shutil.rmtree(dst_path)

            elif os.path.isfile(dst_path):
                logger.info("removing file: {}".format(dst_path))
                os.remove(dst_path)

        # make sure that destination directory exists
        if os.path.exists(dir_dst_path):
            if not os.path.isdir(dir_dst_path):
                os.remove(dir_dst_path)
        else:
            logger.info("making needed dir: {}".format(dir_dst_path))
            os.makedirs(dir_dst_path)
            chown_path(dir_dst_path, user=user_name, group=user_name)

        # creating links
        if os.path.islink(path_full):
            linkto = os.readlink(path_full)
            msg = "creating link {} -> {}".format(dst_path, linkto)
            logger.info(msg)
            os.symlink(linkto, dst_path)
            chown_path(dst_path, user=user_name, group=user_name)

        elif os.path.isfile(path_full):
            msg = "copying file {} -> {}".format(path_full, dst_path)
            logger.info(msg)
            shutil.copy(path_full, dst_path)
            chown_path(dst_path, user=user_name, group=user_name)
예제 #23
0
    def download_online_badges(self):
        profile = load_profile()
        if 'kanoworld_id' in profile:
            user_id = profile['kanoworld_id']
        else:
            return False, 'Profile not registered!'

        success, text, data = request_wrapper('get',
                                              '/users/{}'.format(user_id),
                                              session=self.session)

        if not success:
            return False, text

        if 'user' not in data:
            return False, _("Corrupt response (the 'user' key not found)")

        if 'profile' not in data['user']:
            return False, _(
                "Corrupt response (the 'user.profile' key not found)")

        if 'badges' not in data['user']['profile']:
            return False, _(
                "Corrupt response (the 'user.profile.badges' key not found)")

        online_badges_data = {}

        ensure_dir(online_badges_dir)

        badges = data['user']['profile']['badges']
        for badge in badges:
            if 'assigned' not in badge or not badge['assigned']:
                continue

            if 'image_url' not in badge:
                return False, _("Couldn't find an image for the badge")

            image_loc = os.path.join(online_badges_dir,
                                     "{}.png".format(badge['id']))
            download_url(badge['image_url'], image_loc)

            online_badges_data[badge['id']] = {
                'achieved': True,
                'bg_color': badge['bg_color'].replace("#", ""),
                'desc_locked': badge['desc_locked'],
                'desc_unlocked': badge['desc_unlocked'],
                'title': badge['title']
            }

        try:
            may_write = True
            txt = None
            f = open(online_badges_file, 'w')
        except IOError as e:
            may_write = False
            txt = 'Error opening badges file {}'.format(str(e))
        else:
            with f:
                f.write(json.dumps(online_badges_data))
            if 'SUDO_USER' in os.environ:
                chown_path(online_badges_dir)
                chown_path(online_badges_file)

        return may_write, txt
예제 #24
0
    def download_online_badges(self):
        profile = load_profile()
        if 'kanoworld_id' in profile:
            user_id = profile['kanoworld_id']
        else:
            return False, 'Profile not registered!'

        success, text, data = request_wrapper(
            'get', '/users/{}'.format(user_id),
            session=self.session
        )

        if not success:
            return False, text

        if 'user' not in data:
            return False, _("Corrupt response (the 'user' key not found)")

        if 'profile' not in data['user']:
            return False, _("Corrupt response (the 'user.profile' key not found)")

        if 'badges' not in data['user']['profile']:
            return False, _("Corrupt response (the 'user.profile.badges' key not found)")

        online_badges_data = {}

        ensure_dir(online_badges_dir)

        badges = data['user']['profile']['badges']
        for badge in badges:
            if 'assigned' not in badge or not badge['assigned']:
                continue

            if 'image_url' not in badge:
                return False, _("Couldn't find an image for the badge")

            image_loc = os.path.join(online_badges_dir,
                                     "{}.png".format(badge['id']))
            download_url(badge['image_url'], image_loc)

            online_badges_data[badge['id']] = {
                'achieved': True,
                'bg_color': badge['bg_color'].replace("#", ""),
                'desc_locked': badge['desc_locked'],
                'desc_unlocked': badge['desc_unlocked'],
                'title': badge['title']
            }

        try:
            may_write = True
            txt = None
            f = open(online_badges_file, 'w')
        except IOError as e:
            may_write = False
            txt = 'Error opening badges file {}'.format(str(e))
        else:
            with f:
                f.write(json.dumps(online_badges_data))
            if 'SUDO_USER' in os.environ:
                chown_path(online_badges_dir)
                chown_path(online_badges_file)

        return may_write, txt
예제 #25
0
import re
import shutil
from kano.utils import ensure_dir, get_user_unsudoed, read_json, write_json, chown_path
from kano.logging import logger
from kano_settings.common import settings_dir
from kano.utils import is_model_2_b

USER = None
USER_ID = None

username = get_user_unsudoed()
if username != 'root':
    if os.path.exists(settings_dir) and os.path.isfile(settings_dir):
        os.rename(settings_dir, settings_dir + '.bak')
    ensure_dir(settings_dir)
    chown_path(settings_dir)
    settings_file = os.path.join(settings_dir, 'settings')

defaults = {
    'pi1': {
        'Keyboard-continent-index': 1,
        'Keyboard-country-index': 21,
        'Keyboard-variant-index': 0,
        'Keyboard-continent-human': 'america',
        'Keyboard-country-human': 'United States',
        'Keyboard-variant-human': 'Generic',
        'Audio': 'Analogue',
        'Wifi': '',
        'Wifi-connection-attempted': False,
        'Overclocking': 'High',
        'Mouse': 'Normal',