def __init__(self): sg.theme('GreenTan') self.data = uData.gui_setting() self.layout = self.create_layout() self.window = sg.Window(f'kirafan-bot v{uData.setting["version"]}', self.layout, finalize=True) self.update_tab_selected(self.find_tab_by_name(self.data['questList']['quest_selector']).id) self.update_tabs_bind() self.update_adb_bind() self.battle_job = self.visit_room_job = self.cork_shop_job = Job() self.hotkey = Hotkey('s', mode='gui', window=self.window) if self.data['adb']['use']: self.hotkey.remove_all_hotkey() logger.propagate = False logger.addHandler(GUI_Handler(self.window, '_log_box_', self.blocked)) self._open_re = re.compile(r'^_(\d+|button|update|tab_group|adb|timer|log_level|sleep)_.*$')
def __init__(self, g_pool, annotation_definitions=None): super().__init__(g_pool) self.menu = None self._annotation_list_menu = None if annotation_definitions is None: annotation_definitions = [[ "My annotation", Hotkey.ANNOTATION_EVENT_DEFAULT_HOTKEY() ]] self._initial_annotation_definitions = annotation_definitions self._definition_to_buttons = {} self._new_annotation_label = "new annotation label" self._new_annotation_hotkey = Hotkey.ANNOTATION_EVENT_DEFAULT_HOTKEY()
class HotkeyThread(QThread): fire = Signal() def __init__(self): QThread.__init__(self) from hotkey import Hotkey self.hotkey = Hotkey(self.handle_hotkey) def handle_hotkey(self): self.fire.emit() def stop(self): self.hotkey.stop() def run(self): self.hotkey.start()
def init_ui(self): self.add_menu() self.menu.label = self.pretty_class_name self.add_button = ui.Thumb( "add_surface", setter=self.on_add_surface_click, getter=lambda: False, label="A", hotkey=Hotkey.SURFACE_TRACKER_ADD_SURFACE_CAPTURE_AND_PLAYER_HOTKEY(), ) self.g_pool.quickbar.append(self.add_button) self._update_ui()
def __init__(self): hCheck = win32gui.FindWindow(None, 'kcon') if hCheck > 0: echo('Program has been running') else: self.root = Tk() self.root.title('kcon') hwnd = self.root.winfo_id() self.box = self.TaskList(self.root) self.edit = self.EditLine(self.root, self.box) self.root.bind_all('<Control-Key-a>', self.onCtrlA) self.root.bind_all("<Key>", self.onPress) self.root.bind_all('<Return>', self.onReturn) self.hotkey = Hotkey(self.root) self.root.mainloop()
def main(): if uData.setting['mode'].lower() == 'gui': kirafanbot_GUI().open() elif uData.setting['mode'].lower() == 'hotkey': try: hotkey = Hotkey('rslmptcoxkvei') logger.info("kirafan-bot: hotkey setting finish...") logger.info(f'kirafan-bot: region = {list(kirafan.region)}') logger.info( f'kirafan-bot: adb use = {uData.setting["adb"]["use"]}') logger.info( f'kirafan-bot: quest setting = \x1b[41m{kirafan.quest_name}\x1b[0m' ) check_basic_information() logger.info('please press \'f3\' button to exit...') hotkey.wait('f3') hotkey.safe_exit() except KeyboardInterrupt: hotkey.safe_exit()
def main(): all_bots = ", ".join(get_all_bots()) parser = argparse.ArgumentParser() parser.add_argument("-b", "--bot", help=f"Available bots: {all_bots}") args = parser.parse_args() h = Hotkey() if args.bot: try: bot_module = __import__(f"bots.{args.bot}", fromlist=('PressKey', )) bot = bot_module.PressKey() h.load_bot(bot) except ModuleNotFoundError as e: logger.error(e) h.run()
class kirafanbot_GUI(): def __init__(self): sg.theme('GreenTan') self.data = uData.gui_setting() self.layout = self.create_layout() self.window = sg.Window(f'kirafan-bot v{uData.setting["version"]}', self.layout, finalize=True) self.update_tab_selected(self.find_tab_by_name(self.data['questList']['quest_selector']).id) self.update_tabs_bind() self.update_adb_bind() self.battle_job = self.visit_room_job = self.cork_shop_job = Job() self.hotkey = Hotkey('s', mode='gui', window=self.window) if self.data['adb']['use']: self.hotkey.remove_all_hotkey() logger.propagate = False logger.addHandler(GUI_Handler(self.window, '_log_box_', self.blocked)) self._open_re = re.compile(r'^_(\d+|button|update|tab_group|adb|timer|log_level|sleep)_.*$') def open(self): _map = { 'tab': lambda event, values: self.handle_tab_event(self.find_tab_by_key(event), event, values), 'tab_group': lambda event, values: self.handle_tab_group_event(values[event]), 'sleep': lambda event, values: self.handle_sleep_event(event, values[event]), 'timer': lambda event, values: self.handle_timer_event(event, values[event]), 'adb': lambda event, values: self.handle_adb_event(event, values[event]), 'button': lambda event, values: self.handle_button_event(event), 'log_level': lambda event, values: (self.data.update({'loglevel': values[event]}), logger.setLevel(values[event])), 'update': lambda event, values: self.handle_update_event(event, values[event]) } while True: event, values = self.window.read() if event in (sg.WIN_CLOSED, '_button_Exit_'): self.__save() self.stop_all_safe() break # print(f'event={event}, ' # f'(value, type)={(values[event], type(values[event])) if event in values else ("none", "none_type")}') matchresult = self._open_re.match(event) if matchresult: _cls = 'tab' if matchresult[1].isdigit() else matchresult[1] _map[_cls](event, values) self.window.close() def find_tabskey_by_id(self, id: str) -> Optional[str]: for k, v in self.tabs.items(): if v.id == id: return k return None def find_tab_by_key(self, key: str) -> Optional[Tab_Frame]: try: id = key[key.index('_')+1:key.index('_', 1)] return self.tabs[self.find_tabskey_by_id(id)] if id.isdigit() else None except ValueError: return None def find_tab_by_name(self, name: str) -> Optional[Tab_Frame]: for tab in self.tabs.values(): if tab.name == name: return tab return None def update_tab_selected(self, tab_id: str): self.window[tab_id].select() def update_tabs_bind(self): for _, tab in self.tabs.items(): tab.update_all_bind(self.window) def update_adb_bind(self): if self.data['adb']['use']: self.window['_adb_serial_'].bind('<Button-1>', '') else: self.window['_adb_serial_'].unbind('<Button-1>') def check_configure_and_status(self): if (self.data['questList']['quest_selector'] != self.tabs[self.window['_tab_group_'].get()].name or self.tabs[self.window['_tab_group_'].get()].is_modified() or not data_compare(self.__original_timer, self.data['set_timer']) or not data_compare(self.__original_sleep, self.data['sleep'])): self.data['questList']['quest_selector'] = self.tabs[self.window['_tab_group_'].get()].name self.bt_reset_event() logger.info(f'kirafan-bot: region = {list(kirafan.region)}') logger.debug(f'kirafan-bot: adb use = \x1b[35m{uData.setting["adb"]["use"]}\x1b[0m') logger.debug(f'kirafan-bot: quest setting = \x1b[41m{kirafan.quest_name}\x1b[0m') def handle_tab_event(self, tab: Tab_Frame, event: str, values: Optional[Dict]): if event.endswith('_rename_'): old_name = tab.name exclude = [t.name for _, t in self.tabs.items()] + ['quest_selector'] # magic: tab key will be modified by window[tab_key].Update() after executing tab.hide() # therefore, window['_tab_group_'].get() will confused. o_key = self.window['_tab_group_'].get() new_name = tab.rename_title(self.window, exclude) n_key = self.window['_tab_group_'].get() # update key value, avoid tab key being modified. if n_key != o_key: self.tabs[n_key] = self.tabs.pop(o_key) if new_name: self.data['questList'] = {new_name if k == old_name else k: v for k, v in self.data['questList'].items()} if self.data['questList']['quest_selector'] == old_name: self.data['questList']['quest_selector'] = new_name elif event.endswith('_delete_'): return_button = sg.popup_ok_cancel(f"Are you sure you want to delete '{tab.name}' tab?", title='Confirm delete') if return_button == 'OK': del self.data['questList'][tab.name] del self.tabs[self.find_tabskey_by_id(tab.id)] self.window['_tab_group_'].Widget.hide(int(tab.id)) if self.data['questList']['quest_selector'] == tab.name: self.data['questList']['quest_selector'] = self.tabs[self.window['_tab_group_'].get()].name if self.window['_tab_group_'].get() else '' # noqa: E501 if self.window['_tab_group_'].get() == self.find_tab_by_name('+').id: k = self.window['_tab_group_'].get() self.window[k].update(disabled=True) self.window[k].update(disabled=False) else: tab.handle(self.window, event, values) def handle_tab_group_event(self, tab_id: Optional[str]): def __gen_tab_name(): i = 1 while True: n = f'new tab {i}' if n not in [t.name for _, t in self.tabs.items()]: return n i += 1 self.window['_tab_group_'].set_focus() if tab_id is not None and int(tab_id) == int(self.next_id) - 1: tab = self.find_tab_by_name('+') self.data['questList']['+'] = tab.quest new_name = __gen_tab_name() self.window[f'_{tab.id}_{tab.name}_title_'].update(new_name) self.handle_tab_event(tab, '_rename_', None) self.window['_tab_group_'].add_tab(self.__tab_plus()[0]) def handle_sleep_event(self, key: str, value: str): key = key[7:-1] if value.replace('.', '', 1).isdigit(): self.data['sleep'][key] = float(value) elif value == '': self.data['sleep'][key] = 0 def handle_timer_event(self, key: str, value): if key == '_timer_use_': for tk in ['_timer_hour_start_', '_timer_min_start_', '_timer_sec_start_', '_timer_hour_end_', '_timer_min_end_', '_timer_sec_end_']: self.window[tk].update(disabled=(not value)) self.data['set_timer']['use'] = value elif key == '_timer_countdown_': self.window[key].update(value) elif key in ['_timer_hour_start_', '_timer_min_start_', '_timer_sec_start_', '_timer_hour_end_', '_timer_min_end_', '_timer_sec_end_']: self.data['set_timer']['pause_range'] = '{}:{}:{}-{}:{}:{}'.format( self.window['_timer_hour_start_'].get(), self.window['_timer_min_start_'].get(), self.window['_timer_sec_start_'].get(), self.window['_timer_hour_end_'].get(), self.window['_timer_min_end_'].get(), self.window['_timer_sec_end_'].get() ) def handle_adb_event(self, key: str, value): key = key[5:-1] if key == 'use': if value: self.hotkey.remove_all_hotkey() else: self.hotkey.add_hotkey() self.data['adb'][key] = value self.window['_adb_serial_'].Widget.config(readonlybackground=('white' if value else 'gray')) self.window['_adb_path_'].Widget.config(readonlybackground=('white' if value else 'gray')) self.window['_adb_browse_'].update(disabled=(not value)) self.window['_button_Game region_'].update(disabled=value) self.update_tips_information() self.update_adb_bind() uData.adb_mode_switch() adb.reload() kirafan.adb_mode_switch() elif key == 'path' and self.data['adb'][key] != value: self.data['adb'][key] = value self.__reload() elif key == 'serial': new_serial = sg.popup_get_text('new serial:', title='modify serial', default_text=self.data['adb'][key], size=30) if new_serial and self.data['adb'][key] != new_serial: self.data['adb'][key] = new_serial self.window[f'_adb_{key}_'].update(new_serial) self.__reload() def handle_button_event(self, key: str): button_event_map = { 'Start': lambda k: self.bt_start_event(k), 'Reset': lambda k: self.bt_reset_event(), 'Stop once': lambda k: self.bt_stop_once_event(), 'Visit Room': lambda k: self.bt_visit_room_event(k), 'Cork Shop': lambda k: self.bt_cork_shop_event(k), 'Game region': lambda k: self.bt_game_region_event(), 'ScreenShot': lambda k: self.bt_screenshot_event(), 'Log': lambda k: self.window['_log_area_'].update(visible=(not self.window['_log_area_'].visible)), 'More settings': lambda k: self.window['_more_settings_area_'].update(visible=(not self.window['_more_settings_area_'].visible)), # noqa: E501 } button_str = key[len('_button_'):-1] button_event_map[button_str](key) def bt_start_event(self, key: str): bt_name = self.window[key].GetText() if bt_name == 'Start': self.check_configure_and_status() if not self.battle_job.is_alive(): self.battle_job = Job(target=battle, args=(self.window,)) self.battle_job.start() elif self.battle_job.is_pausing(): self.battle_job.resume() self.update_button_status(key, 'Stop') elif bt_name == 'Stop': if self.battle_job.is_alive(): self.update_button_status(key, bt_name) self.stop_safe(self.battle_job) self.update_button_status(key, 'Start') def bt_reset_event(self): self.__save() if self.data['questList']['quest_selector'] == self.tabs[self.window['_tab_group_'].get()].name: self.__reload() self.update_stop_once_status() self.tabs[self.window['_tab_group_'].get()].reset(self.window) logger.info(f'kirafan-bot: reset {self.tabs[self.window["_tab_group_"].get()].name} quest finish') def bt_stop_once_event(self): kirafan.stop_once = not kirafan.stop_once self.update_stop_once_status() logger.info(f'({str(kirafan.stop_once):>5}) kirafan-bot stop after current battle is completed') def bt_screenshot_event(self): kirafan.screenshot() def bt_game_region_event(self): new = list(game_region()) if self.data['location'] != new: self.data['location'] = new self.__reload() def bt_visit_room_event(self, key: str): bt_name = self.window[key].GetText() if bt_name == 'Visit Room': if not self.visit_room_job.is_alive(): self.visit_room_job = Job(target=visit_friend_room, args=(self.window,)) self.visit_room_job.start() elif self.visit_room_job.is_pausing(): self.visit_room_job.resume() self.update_button_status(key, 'Stop Visit') elif bt_name == 'Stop Visit': if self.visit_room_job.is_alive(): self.update_button_status(key, bt_name) self.stop_safe(self.visit_room_job) self.update_button_status(key, 'Visit Room') def bt_cork_shop_event(self, key: str): bt_name = self.window[key].GetText() if bt_name == 'Cork Shop': if not self.cork_shop_job.is_alive(): self.cork_shop_job = Job(target=cork_shop_exchange, args=(self.window,)) self.cork_shop_job.start() elif self.cork_shop_job.is_pausing(): self.cork_shop_job.resume() self.update_button_status(key, 'Stop Exchange') elif bt_name == 'Stop Exchange': if self.cork_shop_job.is_alive(): self.update_button_status(key, bt_name) self.stop_safe(self.cork_shop_job) self.update_button_status(key, 'Cork Shop') def handle_update_event(self, key: str, value): update_event_map = { '_update_wave_id_': lambda v: self.find_tab_by_name(self.data['questList']['quest_selector']).update_wave_id_status(self.window, v), # noqa: E501 '_update_loop_count_': lambda v: self.find_tab_by_name(self.data['questList']['quest_selector']).update_loop_count_status(self.window, v), # noqa: E501 '_update_stop_once_': lambda v: self.update_stop_once_status(), '_update_button_start_': lambda v: self.update_button_status('_button_Start_', v), '_update_button_visit_room_': lambda v: self.update_button_status('_button_Visit Room_', v), '_update_button_cork_shop_': lambda v: self.update_button_status('_button_Cork Shop_', v) } update_event_map[key](value) def update_stop_once_status(self): self.window['_button_Stop once_'].Update('Cancel' if kirafan.stop_once else 'Stop once') def update_button_status(self, key: str, new_button: str): old_button = self.window[key].GetText() if old_button == new_button: self.window[key].Update(disabled=True) self.window['_tips_'].Update('Tips: Please wait for a while') self.window.Refresh() return self.window[key].Update(new_button, disabled=False) self.toggle_other_buttons(old_button) if key == '_button_Start_': self.window['_running_status_'].Update('' if new_button == 'Start' else self.tabs[self.window['_tab_group_'].get()].name) # noqa: E501 self.update_tips_information() def update_tips_information(self): if self.blocked(): return if not self.data['adb']['use'] and (self.battle_job.is_alive() or self.visit_room_job.is_alive() or self.cork_shop_job.is_alive()): self.window['_tips_'].update('Tips: press hotkey(z+s) to stop bot') else: self.window['_tips_'].update('') self.window['_timer_countdown_'].update('') def toggle_other_buttons(self, current_button: str): toggle_other_buttons_map = { 'Start': lambda: (self.window['_button_Visit Room_'].Update(disabled=True), self.window['_button_Cork Shop_'].Update(disabled=True), self.window['_button_Game region_'].Update(disabled=True)), 'Visit Room': lambda: (self.window['_button_Start_'].Update(disabled=True), self.window['_button_Stop once_'].Update(disabled=True), self.window['_button_Cork Shop_'].Update(disabled=True), self.window['_button_Game region_'].Update(disabled=True)), 'Cork Shop': lambda: (self.window['_button_Start_'].Update(disabled=True), self.window['_button_Stop once_'].Update(disabled=True), self.window['_button_Visit Room_'].Update(disabled=True), self.window['_button_Game region_'].Update(disabled=True)), 'Stop': lambda: (self.window['_button_Visit Room_'].Update(disabled=False), self.window['_button_Cork Shop_'].Update(disabled=False), self.data['adb']['use'] or self.window['_button_Game region_'].Update(disabled=False)), 'Stop Visit': lambda: (self.window['_button_Start_'].Update(disabled=False), self.window['_button_Stop once_'].Update(disabled=False), self.window['_button_Cork Shop_'].Update(disabled=False), self.data['adb']['use'] or self.window['_button_Game region_'].Update(disabled=False)), 'Stop Exchange': lambda: (self.window['_button_Start_'].Update(disabled=False), self.window['_button_Stop once_'].Update(disabled=False), self.window['_button_Visit Room_'].Update(disabled=False), self.data['adb']['use'] or self.window['_button_Game region_'].Update(disabled=False)), } toggle_other_buttons_map[current_button]() def create_layout(self) -> List: return [ self.__tab_group_area(), self.__more_settings_area(), self.__information_area(), self.__button_area(), self.__log_level_area() ] def __tab_group_area(self) -> List: self.tabs = {str(i): Tab_Frame(str(i), name, self.data['questList'][name]) for i, name in enumerate(filter(lambda x: x != 'quest_selector', self.data['questList'].keys()))} self.next_id = str(len(self.tabs)) return [ [sg.TabGroup([ [sg.Tab(tab.name, tab.create_layout(), key=id) for id, tab in self.tabs.items()], self.__tab_plus() ], key='_tab_group_', selected_title_color='red2', focus_color='Any', enable_events=True)] ] def __tab_plus(self) -> List: tab = self.tabs[self.next_id] = Tab_Frame(self.next_id, '+', uData.create_default_quest()) self.next_id = str(int(self.next_id) + 1) return [sg.Tab(tab.name, tab.create_layout(), key=tab.id)] def __more_settings_area(self) -> List: return [sg.pin( sg.Column([ self.__sleep_area() + self.__set_timer_area(), self.__adb_area() ], k='_more_settings_area_', visible=False) )] def __sleep_area(self) -> List: self.__original_sleep = deepcopy(self.data['sleep']) delay = self.data['sleep'] k = ['_sleep_click_', '_sleep_sp_', '_sleep_loadding_', '_sleep_wave_transitions_'] layout = [[ sg.Text('click:', pad=((5, 0), 5)), sg.Input(delay['click'], size=3, pad=((0, 10), 5), key=k[0], enable_events=True), sg.Text('sp:', pad=((5, 0), 5)), sg.Input(delay['sp'], size=4, pad=((0, 10), 5), key=k[1], enable_events=True), sg.Text('loading:', pad=((5, 0), 5)), sg.Input(delay['loading'], size=4, pad=((0, 10), 5), key=k[2], enable_events=True), sg.Text('wave transition:', pad=((5, 0), 5)), sg.Input(delay['wave_transitions'], size=3, pad=((0, 10), 5), key=k[3], enable_events=True) ]] return [sg.Frame('delay (s)', layout, pad=((0, 5), 5))] def __set_timer_area(self) -> List: self.__original_timer = deepcopy(self.data['set_timer']) timer = self.data['set_timer'] k = ['_timer_use_', '_timer_hour_start_', '_timer_min_start_', '_timer_sec_start_', '_timer_hour_end_', '_timer_min_end_', '_timer_sec_end_'] frame_layout = [[ sg.Checkbox('use', default=timer['use'], key=k[0], enable_events=True), sg.Text('pause range(h:m:s):', pad=((5, 0), 5)), sg.Spin([f'{("0" + str(i))[-2:]}' for i in range(0, 24)], size=2, key=k[1], readonly=True, initial_value=timer['pause_range'][:2], disabled=(not timer['use']), enable_events=True), sg.Text(':', pad=(0, 0)), # noqa: E501 sg.Spin([f'{("0" + str(i))[-2:]}' for i in range(0, 60)], size=2, key=k[2], readonly=True, initial_value=timer['pause_range'][3:5], disabled=(not timer['use']), enable_events=True), sg.Text(':', pad=(0, 0)), # noqa: E501 sg.Spin([f'{("0" + str(i))[-2:]}' for i in range(0, 60)], size=2, key=k[3], readonly=True, initial_value=timer['pause_range'][6:8], disabled=(not timer['use']), enable_events=True), sg.Text(' - ', pad=(0, 0)), # noqa: E501 sg.Spin([f'{("0" + str(i))[-2:]}' for i in range(0, 24)], size=2, key=k[4], readonly=True, initial_value=timer['pause_range'][9:11], disabled=(not timer['use']), enable_events=True), sg.Text(':', pad=(0, 0)), # noqa: E501 sg.Spin([f'{("0" + str(i))[-2:]}' for i in range(0, 60)], size=2, key=k[5], readonly=True, initial_value=timer['pause_range'][12:14], disabled=(not timer['use']), enable_events=True), sg.Text(':', pad=(0, 0)), # noqa: E501 sg.Spin([f'{("0" + str(i))[-2:]}' for i in range(0, 60)], size=2, key=k[6], readonly=True, initial_value=timer['pause_range'][15:17], disabled=(not timer['use']), enable_events=True) # noqa: E501 ]] return [sg.Frame('timer', frame_layout)] def __adb_area(self) -> List: adb = self.data['adb'] frame_layout = [[ sg.Checkbox('use', default=adb['use'], key='_adb_use_', enable_events=True), sg.Text('serial:', pad=((5, 0), 5)), sg.Input(adb['serial'], key='_adb_serial_', size=13, disabled_readonly_background_color=('white' if adb['use'] else 'gray'), disabled=True), # noqa: E501 sg.Text('path:', pad=((5, 0), 5)), sg.Input(adb['path'], key='_adb_path_', size=63, enable_events=True, disabled_readonly_background_color=('white' if adb['use'] else 'gray'), disabled=True), # noqa: E501 sg.FileBrowse(key='_adb_browse_', size=7, disabled=not adb['use']) ]] return [sg.Frame('adb.exe', frame_layout, pad=((0, 5), 5))] def __information_area(self) -> List: return [ sg.Text('Running:', pad=((5, 0), 5)), sg.Text('', font=('Any', 11, 'bold'), size=40, key='_running_status_'), sg.Column([[sg.Text('', size=30, key='_timer_countdown_')]], expand_x=True, element_justification='center'), sg.Column([[sg.Text('', size=30, text_color='red2', justification='right', font=('Any', 11, 'bold'), key='_tips_') ]], expand_x=True, element_justification='right') ] def __button_area(self) -> List: button_list = ['Start', 'Reset', 'Stop once', 'Visit Room', 'Cork Shop', 'Game region', 'ScreenShot', 'Log', 'More settings', 'Exit'] return [ sg.Column([[ sg.Button(bt, key=f'_button_{bt}_', mouseover_colors=None, size=12, focus=True if bt == 'Reset' else None, disabled=True if bt == 'Game region' and self.data['adb']['use'] else False) for bt in button_list ]], element_justification='right', expand_x=True) ] def __log_level_area(self) -> List: level = [ sg.Column([[ sg.Text('Log level:', pad=(0, 0)), sg.InputCombo([e.name for e in loglevel], default_value=self.data['loglevel'].upper(), key='_log_level_', enable_events=True) ]], vertical_alignment='top') ] box = [sg.Multiline(size=(None, 10), key='_log_box_', pad=(0, 0), expand_x=True)] return [sg.pin(sg.Column([box + level], expand_x=True, k='_log_area_', visible=False), expand_x=True)] def stop_all_safe(self): self.stop_safe(self.battle_job) self.stop_safe(self.visit_room_job) def stop_safe(self, job: Job): if job.is_alive(): job.gui_button_stop() job.join() job.gui_button_stop_finish() def blocked(self): if not (self.battle_job.is_not_gui_button_stop() and self.visit_room_job.is_not_gui_button_stop() and self.cork_shop_job.is_not_gui_button_stop()): return True return False def __save(self): uData.save_gui_setting() self.__original_sleep = deepcopy(self.data['sleep']) self.__original_timer = deepcopy(self.data['set_timer']) if self.window['_tab_group_'].get(): # is None if event == sg.exit self.tabs[self.window['_tab_group_'].get()].update_original_quest() def __reload(self): uData.reload() adb.reload() kirafan.reload() logger.info('kirafan-bot: reload configuration finish')
def test_hotkey(): hotkey = Hotkey('rslmptcoxkvei') assert hotkey.safe_exit() is None assert hotkey._Hotkey__user_command('1') is None assert hotkey._Hotkey__user_command('r') is None assert hotkey._Hotkey__user_command('s') is None assert hotkey._Hotkey__user_command('l') is None # assert hotkey._Hotkey__user_command('m') is None assert hotkey._Hotkey__user_command('t') is None assert hotkey._Hotkey__user_command('c') is None assert hotkey._Hotkey__user_command('o') is None # assert hotkey._Hotkey__user_command('x') is None assert hotkey._Hotkey__user_command('k') is None assert hotkey._Hotkey__user_command('k') is None # switch back assert hotkey._Hotkey__user_command('v') is None assert hotkey._Hotkey__user_command('s') is None assert hotkey._Hotkey__user_command('e') is None assert hotkey._Hotkey__user_command('s') is None assert hotkey._Hotkey__user_command('i') is None assert os.path.exists('screenshot0.png') is True assert hotkey.remove_all_hotkey() is None
def player(rec_dir, ipc_pub_url, ipc_sub_url, ipc_push_url, user_dir, app_version, debug): # general imports from time import sleep import logging from glob import glob from time import time, strftime, localtime # networking import zmq import zmq_tools import numpy as np # zmq ipc setup zmq_ctx = zmq.Context() ipc_pub = zmq_tools.Msg_Dispatcher(zmq_ctx, ipc_push_url) notify_sub = zmq_tools.Msg_Receiver(zmq_ctx, ipc_sub_url, topics=("notify", )) # log setup logging.getLogger("OpenGL").setLevel(logging.ERROR) logger = logging.getLogger() logger.handlers = [] logger.setLevel(logging.NOTSET) logger.addHandler(zmq_tools.ZMQ_handler(zmq_ctx, ipc_push_url)) # create logger for the context of this function logger = logging.getLogger(__name__) try: from background_helper import IPC_Logging_Task_Proxy IPC_Logging_Task_Proxy.push_url = ipc_push_url from tasklib.background.patches import IPCLoggingPatch IPCLoggingPatch.ipc_push_url = ipc_push_url # imports from file_methods import Persistent_Dict, next_export_sub_dir from OpenGL.GL import GL_COLOR_BUFFER_BIT # display import glfw from gl_utils import GLFWErrorReporting GLFWErrorReporting.set_default() # check versions for our own depedencies as they are fast-changing from pyglui import __version__ as pyglui_version from pyglui import ui, cygl from pyglui.cygl.utils import Named_Texture, RGBA import gl_utils # capture from video_capture import File_Source # helpers/utils from version_utils import parse_version from methods import normalize, denormalize, delta_t, get_system_info import player_methods as pm from pupil_recording import PupilRecording from csv_utils import write_key_value_file from hotkey import Hotkey # Plug-ins from plugin import Plugin, Plugin_List, import_runtime_plugins from plugin_manager import Plugin_Manager from vis_circle import Vis_Circle from vis_cross import Vis_Cross from vis_polyline import Vis_Polyline from vis_light_points import Vis_Light_Points from vis_watermark import Vis_Watermark from vis_fixation import Vis_Fixation from seek_control import Seek_Control from surface_tracker import Surface_Tracker_Offline # from marker_auto_trim_marks import Marker_Auto_Trim_Marks from fixation_detector import Offline_Fixation_Detector from log_display import Log_Display from annotations import Annotation_Player from raw_data_exporter import Raw_Data_Exporter from log_history import Log_History from pupil_producers import ( DisabledPupilProducer, Pupil_From_Recording, Offline_Pupil_Detection, ) from gaze_producer.gaze_from_recording import GazeFromRecording from gaze_producer.gaze_from_offline_calibration import ( GazeFromOfflineCalibration, ) from pupil_detector_plugins.detector_base_plugin import PupilDetectorPlugin from system_graphs import System_Graphs from system_timelines import System_Timelines from blink_detection import Offline_Blink_Detection from audio_playback import Audio_Playback from video_export.plugins.imotions_exporter import iMotions_Exporter from video_export.plugins.eye_video_exporter import Eye_Video_Exporter from video_export.plugins.world_video_exporter import World_Video_Exporter from head_pose_tracker.offline_head_pose_tracker import ( Offline_Head_Pose_Tracker, ) from video_capture import File_Source from video_overlay.plugins import Video_Overlay, Eye_Overlay from pupil_recording import ( assert_valid_recording_type, InvalidRecordingException, ) assert parse_version(pyglui_version) >= parse_version( "1.29"), "pyglui out of date, please upgrade to newest version" process_was_interrupted = False def interrupt_handler(sig, frame): import traceback trace = traceback.format_stack(f=frame) logger.debug(f"Caught signal {sig} in:\n" + "".join(trace)) nonlocal process_was_interrupted process_was_interrupted = True signal.signal(signal.SIGINT, interrupt_handler) runtime_plugins = import_runtime_plugins( os.path.join(user_dir, "plugins")) runtime_plugins = [ p for p in runtime_plugins if not issubclass(p, PupilDetectorPlugin) ] system_plugins = [ Log_Display, Seek_Control, Plugin_Manager, System_Graphs, System_Timelines, Audio_Playback, ] user_plugins = [ Vis_Circle, Vis_Fixation, Vis_Polyline, Vis_Light_Points, Vis_Cross, Vis_Watermark, Eye_Overlay, Video_Overlay, Offline_Fixation_Detector, Offline_Blink_Detection, Surface_Tracker_Offline, Raw_Data_Exporter, Annotation_Player, Log_History, DisabledPupilProducer, Pupil_From_Recording, Offline_Pupil_Detection, GazeFromRecording, GazeFromOfflineCalibration, World_Video_Exporter, iMotions_Exporter, Eye_Video_Exporter, Offline_Head_Pose_Tracker, ] + runtime_plugins plugins = system_plugins + user_plugins def consume_events_and_render_buffer(): gl_utils.glViewport(0, 0, *g_pool.camera_render_size) g_pool.capture.gl_display() for p in g_pool.plugins: p.gl_display() gl_utils.glViewport(0, 0, *window_size) try: clipboard = glfw.get_clipboard_string(main_window).decode() except (AttributeError, glfw.GLFWError): # clipbaord is None, might happen on startup clipboard = "" g_pool.gui.update_clipboard(clipboard) user_input = g_pool.gui.update() if user_input.clipboard and user_input.clipboard != clipboard: # only write to clipboard if content changed glfw.set_clipboard_string(main_window, user_input.clipboard) for b in user_input.buttons: button, action, mods = b x, y = glfw.get_cursor_pos(main_window) pos = gl_utils.window_coordinate_to_framebuffer_coordinate( main_window, x, y, cached_scale=None) pos = normalize(pos, g_pool.camera_render_size) pos = denormalize(pos, g_pool.capture.frame_size) for plugin in g_pool.plugins: if plugin.on_click(pos, button, action): break for key, scancode, action, mods in user_input.keys: for plugin in g_pool.plugins: if plugin.on_key(key, scancode, action, mods): break for char_ in user_input.chars: for plugin in g_pool.plugins: if plugin.on_char(char_): break glfw.swap_buffers(main_window) # Callback functions def on_resize(window, w, h): nonlocal window_size nonlocal content_scale if w == 0 or h == 0: return # Always clear buffers on resize to make sure that there are no overlapping # artifacts from previous frames. gl_utils.glClear(GL_COLOR_BUFFER_BIT) gl_utils.glClearColor(0, 0, 0, 1) content_scale = gl_utils.get_content_scale(window) framebuffer_scale = gl_utils.get_framebuffer_scale(window) g_pool.gui.scale = content_scale window_size = w, h g_pool.camera_render_size = w - int( icon_bar_width * g_pool.gui.scale), h g_pool.gui.update_window(*window_size) g_pool.gui.collect_menus() for p in g_pool.plugins: p.on_window_resize(window, *g_pool.camera_render_size) # Minimum window size required, otherwise parts of the UI can cause openGL # issues with permanent effects. Depends on the content scale, which can # potentially be dynamically modified, so we re-adjust the size limits every # time here. min_size = int(2 * icon_bar_width * g_pool.gui.scale / framebuffer_scale) glfw.set_window_size_limits( window, min_size, min_size, glfw.DONT_CARE, glfw.DONT_CARE, ) # Needed, to update the window buffer while resizing consume_events_and_render_buffer() def on_window_key(window, key, scancode, action, mods): g_pool.gui.update_key(key, scancode, action, mods) def on_window_char(window, char): g_pool.gui.update_char(char) def on_window_mouse_button(window, button, action, mods): g_pool.gui.update_button(button, action, mods) def on_pos(window, x, y): x, y = gl_utils.window_coordinate_to_framebuffer_coordinate( window, x, y, cached_scale=None) g_pool.gui.update_mouse(x, y) pos = x, y pos = normalize(pos, g_pool.camera_render_size) # Position in img pixels pos = denormalize(pos, g_pool.capture.frame_size) for p in g_pool.plugins: p.on_pos(pos) def on_scroll(window, x, y): g_pool.gui.update_scroll(x, y * scroll_factor) def on_drop(window, paths): for path in paths: try: assert_valid_recording_type(path) _restart_with_recording(path) return except InvalidRecordingException as err: logger.debug(str(err)) for plugin in g_pool.plugins: if plugin.on_drop(paths): break def _restart_with_recording(rec_dir): logger.debug("Starting new session with '{}'".format(rec_dir)) ipc_pub.notify({ "subject": "player_drop_process.should_start", "rec_dir": rec_dir }) glfw.set_window_should_close(g_pool.main_window, True) tick = delta_t() def get_dt(): return next(tick) recording = PupilRecording(rec_dir) meta_info = recording.meta_info # log info about Pupil Platform and Platform in player.log logger.info("Application Version: {}".format(app_version)) logger.info("System Info: {}".format(get_system_info())) logger.debug(f"Debug flag: {debug}") icon_bar_width = 50 window_size = None content_scale = 1.0 # create container for globally scoped vars g_pool = SimpleNamespace() g_pool.app = "player" g_pool.process = "player" g_pool.zmq_ctx = zmq_ctx g_pool.ipc_pub = ipc_pub g_pool.ipc_pub_url = ipc_pub_url g_pool.ipc_sub_url = ipc_sub_url g_pool.ipc_push_url = ipc_push_url g_pool.plugin_by_name = {p.__name__: p for p in plugins} g_pool.camera_render_size = None video_path = recording.files().core().world().videos()[0].resolve() File_Source( g_pool, timing="external", source_path=video_path, buffered_decoding=True, fill_gaps=True, ) # load session persistent settings session_settings = Persistent_Dict( os.path.join(user_dir, "user_settings_player")) if parse_version(session_settings.get("version", "0.0")) != app_version: logger.info( "Session setting are a different version of this app. I will not use those." ) session_settings.clear() width, height = g_pool.capture.frame_size width += icon_bar_width width, height = session_settings.get("window_size", (width, height)) window_name = f"Pupil Player: {meta_info.recording_name} - {rec_dir}" glfw.init() glfw.window_hint(glfw.SCALE_TO_MONITOR, glfw.TRUE) main_window = glfw.create_window(width, height, window_name, None, None) window_position_manager = gl_utils.WindowPositionManager() window_pos = window_position_manager.new_window_position( window=main_window, default_position=window_position_default, previous_position=session_settings.get("window_position", None), ) glfw.set_window_pos(main_window, window_pos[0], window_pos[1]) glfw.make_context_current(main_window) cygl.utils.init() g_pool.main_window = main_window g_pool.version = app_version g_pool.timestamps = g_pool.capture.timestamps g_pool.get_timestamp = lambda: 0.0 g_pool.user_dir = user_dir g_pool.rec_dir = rec_dir g_pool.meta_info = meta_info g_pool.min_data_confidence = session_settings.get( "min_data_confidence", MIN_DATA_CONFIDENCE_DEFAULT) g_pool.min_calibration_confidence = session_settings.get( "min_calibration_confidence", MIN_CALIBRATION_CONFIDENCE_DEFAULT) # populated by producers g_pool.pupil_positions = pm.PupilDataBisector() g_pool.gaze_positions = pm.Bisector() g_pool.fixations = pm.Affiliator() g_pool.eye_movements = pm.Affiliator() def set_data_confidence(new_confidence): g_pool.min_data_confidence = new_confidence notification = {"subject": "min_data_confidence_changed"} notification["_notify_time_"] = time() + 0.8 g_pool.ipc_pub.notify(notification) def do_export(_): left_idx = g_pool.seek_control.trim_left right_idx = g_pool.seek_control.trim_right export_range = left_idx, right_idx + 1 # exclusive range.stop export_ts_window = pm.exact_window(g_pool.timestamps, (left_idx, right_idx)) export_dir = os.path.join(g_pool.rec_dir, "exports") export_dir = next_export_sub_dir(export_dir) os.makedirs(export_dir) logger.info('Created export dir at "{}"'.format(export_dir)) export_info = { "Player Software Version": str(g_pool.version), "Data Format Version": meta_info.min_player_version, "Export Date": strftime("%d.%m.%Y", localtime()), "Export Time": strftime("%H:%M:%S", localtime()), "Frame Index Range:": g_pool.seek_control.get_frame_index_trim_range_string(), "Relative Time Range": g_pool.seek_control.get_rel_time_trim_range_string(), "Absolute Time Range": g_pool.seek_control.get_abs_time_trim_range_string(), } with open(os.path.join(export_dir, "export_info.csv"), "w") as csv: write_key_value_file(csv, export_info) notification = { "subject": "should_export", "range": export_range, "ts_window": export_ts_window, "export_dir": export_dir, } g_pool.ipc_pub.notify(notification) def reset_restart(): logger.warning("Resetting all settings and restarting Player.") glfw.set_window_should_close(main_window, True) ipc_pub.notify({"subject": "clear_settings_process.should_start"}) ipc_pub.notify({ "subject": "player_process.should_start", "rec_dir": rec_dir, "delay": 2.0, }) def toggle_general_settings(collapsed): # this is the menu toggle logic. # Only one menu can be open. # If no menu is open the menubar should collapse. g_pool.menubar.collapsed = collapsed for m in g_pool.menubar.elements: m.collapsed = True general_settings.collapsed = collapsed g_pool.gui = ui.UI() g_pool.menubar = ui.Scrolling_Menu("Settings", pos=(-500, 0), size=(-icon_bar_width, 0), header_pos="left") g_pool.iconbar = ui.Scrolling_Menu("Icons", pos=(-icon_bar_width, 0), size=(0, 0), header_pos="hidden") g_pool.timelines = ui.Container((0, 0), (0, 0), (0, 0)) g_pool.timelines.horizontal_constraint = g_pool.menubar g_pool.user_timelines = ui.Timeline_Menu("User Timelines", pos=(0.0, -150.0), size=(0.0, 0.0), header_pos="headline") g_pool.user_timelines.color = RGBA(a=0.0) g_pool.user_timelines.collapsed = True # add container that constaints itself to the seekbar height vert_constr = ui.Container((0, 0), (0, -50.0), (0, 0)) vert_constr.append(g_pool.user_timelines) g_pool.timelines.append(vert_constr) def set_window_size(): # Get current capture frame size f_width, f_height = g_pool.capture.frame_size # Get current display scale factor content_scale = gl_utils.get_content_scale(main_window) framebuffer_scale = gl_utils.get_framebuffer_scale(main_window) display_scale_factor = content_scale / framebuffer_scale # Scale the capture frame size by display scale factor f_width *= display_scale_factor f_height *= display_scale_factor # Increas the width to account for the added scaled icon bar width f_width += icon_bar_width * display_scale_factor # Set the newly calculated size (scaled capture frame size + scaled icon bar width) glfw.set_window_size(main_window, int(f_width), int(f_height)) general_settings = ui.Growing_Menu("General", header_pos="headline") general_settings.append(ui.Button("Reset window size", set_window_size)) general_settings.append( ui.Info_Text( f"Minimum Player Version: {meta_info.min_player_version}")) general_settings.append( ui.Info_Text(f"Player Version: {g_pool.version}")) general_settings.append( ui.Info_Text( f"Recording Software: {meta_info.recording_software_name}")) general_settings.append( ui.Info_Text( f"Recording Software Version: {meta_info.recording_software_version}" )) general_settings.append( ui.Info_Text( "High level data, e.g. fixations, or visualizations only consider gaze data that has an equal or higher confidence than the minimum data confidence." )) general_settings.append( ui.Slider( "min_data_confidence", g_pool, setter=set_data_confidence, step=0.05, min=0.0, max=1.0, label="Minimum data confidence", )) general_settings.append( ui.Button("Restart with default settings", reset_restart)) g_pool.menubar.append(general_settings) icon = ui.Icon( "collapsed", general_settings, label=chr(0xE8B8), on_val=False, off_val=True, setter=toggle_general_settings, label_font="pupil_icons", ) icon.tooltip = "General Settings" g_pool.iconbar.append(icon) user_plugin_separator = ui.Separator() user_plugin_separator.order = 0.35 g_pool.iconbar.append(user_plugin_separator) g_pool.quickbar = ui.Stretching_Menu("Quick Bar", (0, 100), (100, -100)) g_pool.export_button = ui.Thumb( "export", label=chr(0xE2C5), getter=lambda: False, setter=do_export, hotkey=Hotkey.EXPORT_START_PLAYER_HOTKEY(), label_font="pupil_icons", ) g_pool.quickbar.extend([g_pool.export_button]) g_pool.gui.append(g_pool.menubar) g_pool.gui.append(g_pool.timelines) g_pool.gui.append(g_pool.iconbar) g_pool.gui.append(g_pool.quickbar) # we always load these plugins _pupil_producer_plugins = [ # In priority order (first is default) ("Pupil_From_Recording", {}), ("Offline_Pupil_Detection", {}), ("DisabledPupilProducer", {}), ] _pupil_producer_plugins = list(reversed(_pupil_producer_plugins)) _gaze_producer_plugins = [ # In priority order (first is default) ("GazeFromRecording", {}), ("GazeFromOfflineCalibration", {}), ] _gaze_producer_plugins = list(reversed(_gaze_producer_plugins)) default_plugins = [ ("Plugin_Manager", {}), ("Seek_Control", {}), ("Log_Display", {}), ("Raw_Data_Exporter", {}), ("Vis_Polyline", {}), ("Vis_Circle", {}), ("System_Graphs", {}), ("System_Timelines", {}), ("World_Video_Exporter", {}), *_pupil_producer_plugins, *_gaze_producer_plugins, ("Audio_Playback", {}), ] _plugins_to_load = session_settings.get("loaded_plugins", None) if _plugins_to_load is None: # If no plugins are available from a previous session, # then use the default plugin list _plugins_to_load = default_plugins else: # If there are plugins available from a previous session, # then prepend plugins that are required, but might have not been available before _plugins_to_load = [ *_pupil_producer_plugins, *_gaze_producer_plugins, *_plugins_to_load, ] g_pool.plugins = Plugin_List(g_pool, _plugins_to_load) # Manually add g_pool.capture to the plugin list g_pool.plugins._plugins.append(g_pool.capture) g_pool.plugins._plugins.sort(key=lambda p: p.order) g_pool.capture.init_ui() general_settings.insert( -1, ui.Text_Input( "rel_time_trim_section", getter=g_pool.seek_control.get_rel_time_trim_range_string, setter=g_pool.seek_control.set_rel_time_trim_range_string, label="Relative time range to export", ), ) general_settings.insert( -1, ui.Text_Input( "frame_idx_trim_section", getter=g_pool.seek_control.get_frame_index_trim_range_string, setter=g_pool.seek_control.set_frame_index_trim_range_string, label="Frame index range to export", ), ) # Register callbacks main_window glfw.set_framebuffer_size_callback(main_window, on_resize) glfw.set_key_callback(main_window, on_window_key) glfw.set_char_callback(main_window, on_window_char) glfw.set_mouse_button_callback(main_window, on_window_mouse_button) glfw.set_cursor_pos_callback(main_window, on_pos) glfw.set_scroll_callback(main_window, on_scroll) glfw.set_drop_callback(main_window, on_drop) toggle_general_settings(True) g_pool.gui.configuration = session_settings.get("ui_config", {}) # If previously selected plugin was not loaded this time, we will have an # expanded menubar without any menu selected. We need to ensure the menubar is # collapsed in this case. if all(submenu.collapsed for submenu in g_pool.menubar.elements): g_pool.menubar.collapsed = True # gl_state settings gl_utils.basic_gl_setup() g_pool.image_tex = Named_Texture() # trigger on_resize on_resize(main_window, *glfw.get_framebuffer_size(main_window)) def handle_notifications(n): subject = n["subject"] if subject == "start_plugin": g_pool.plugins.add(g_pool.plugin_by_name[n["name"]], args=n.get("args", {})) elif subject.startswith("meta.should_doc"): ipc_pub.notify({ "subject": "meta.doc", "actor": g_pool.app, "doc": player.__doc__ }) for p in g_pool.plugins: if (p.on_notify.__doc__ and p.__class__.on_notify != Plugin.on_notify): ipc_pub.notify({ "subject": "meta.doc", "actor": p.class_name, "doc": p.on_notify.__doc__, }) while not glfw.window_should_close( main_window) and not process_was_interrupted: # fetch newest notifications new_notifications = [] while notify_sub.new_data: t, n = notify_sub.recv() new_notifications.append(n) # notify each plugin if there are new notifications: for n in new_notifications: handle_notifications(n) for p in g_pool.plugins: p.on_notify(n) events = {} # report time between now and the last loop interation events["dt"] = get_dt() # pupil and gaze positions are added by their respective producer plugins events["pupil"] = [] events["gaze"] = [] # allow each Plugin to do its work. for p in g_pool.plugins: p.recent_events(events) # check if a plugin need to be destroyed g_pool.plugins.clean() glfw.make_context_current(main_window) glfw.poll_events() # render visual feedback from loaded plugins if gl_utils.is_window_visible(main_window): gl_utils.glViewport(0, 0, *g_pool.camera_render_size) g_pool.capture.gl_display() for p in g_pool.plugins: p.gl_display() gl_utils.glViewport(0, 0, *window_size) try: clipboard = glfw.get_clipboard_string(main_window).decode() except (AttributeError, glfw.GLFWError): # clipbaord is None, might happen on startup clipboard = "" g_pool.gui.update_clipboard(clipboard) user_input = g_pool.gui.update() if user_input.clipboard and user_input.clipboard != clipboard: # only write to clipboard if content changed glfw.set_clipboard_string(main_window, user_input.clipboard) for b in user_input.buttons: button, action, mods = b x, y = glfw.get_cursor_pos(main_window) pos = gl_utils.window_coordinate_to_framebuffer_coordinate( main_window, x, y, cached_scale=None) pos = normalize(pos, g_pool.camera_render_size) pos = denormalize(pos, g_pool.capture.frame_size) for plugin in g_pool.plugins: if plugin.on_click(pos, button, action): break for key, scancode, action, mods in user_input.keys: for plugin in g_pool.plugins: if plugin.on_key(key, scancode, action, mods): break for char_ in user_input.chars: for plugin in g_pool.plugins: if plugin.on_char(char_): break # present frames at appropriate speed g_pool.seek_control.wait(events["frame"].timestamp) glfw.swap_buffers(main_window) session_settings["loaded_plugins"] = g_pool.plugins.get_initializers() session_settings["min_data_confidence"] = g_pool.min_data_confidence session_settings[ "min_calibration_confidence"] = g_pool.min_calibration_confidence session_settings["ui_config"] = g_pool.gui.configuration session_settings["window_position"] = glfw.get_window_pos(main_window) session_settings["version"] = str(g_pool.version) session_window_size = glfw.get_window_size(main_window) if 0 not in session_window_size: f_width, f_height = session_window_size if platform.system() in ("Windows", "Linux"): f_width, f_height = ( f_width / content_scale, f_height / content_scale, ) session_settings["window_size"] = int(f_width), int(f_height) session_settings.close() # de-init all running plugins for p in g_pool.plugins: p.alive = False g_pool.plugins.clean() g_pool.gui.terminate() glfw.destroy_window(main_window) except Exception: import traceback trace = traceback.format_exc() logger.error("Process Player crashed with trace:\n{}".format(trace)) finally: logger.info("Process shutting down.") ipc_pub.notify({"subject": "player_process.stopped"}) sleep(1.0)
def init_ui(self): self.add_menu() self.menu.label = "Recorder" self.menu_icon.order = 0.29 self.menu.append( ui.Info_Text( 'Pupil recordings are saved like this: "path_to_recordings/recording_session_name/nnn" where "nnn" is an increasing number to avoid overwrites. You can use "/" in your session name to create subdirectories.' )) self.menu.append( ui.Info_Text( 'Recordings are saved to "~/pupil_recordings". You can change the path here but note that invalid input will be ignored.' )) self.menu.append( ui.Text_Input( "rec_root_dir", self, setter=self.set_rec_root_dir, label="Path to recordings", )) self.menu.append( ui.Text_Input( "session_name", self, setter=self.set_session_name, label="Recording session name", )) self.menu.append( ui.Switch( "show_info_menu", self, on_val=True, off_val=False, label="Request additional user info", )) self.menu.append( ui.Selector( "raw_jpeg", self, selection=[True, False], labels=["bigger file, less CPU", "smaller file, more CPU"], label="Compression", )) self.menu.append( ui.Info_Text( "Recording the raw eye video is optional. We use it for debugging." )) self.menu.append( ui.Switch("record_eye", self, on_val=True, off_val=False, label="Record eye")) self.button = ui.Thumb( "running", self, setter=self.toggle, label="R", hotkey=Hotkey.RECORDER_RUNNING_TOGGLE_CAPTURE_HOTKEY(), ) self.button.on_color[:] = (1, 0.0, 0.0, 0.8) self.g_pool.quickbar.insert(2, self.button) self.low_disk_space_thumb = ui.Thumb("low_disk_warn", label="!", getter=lambda: True, setter=lambda x: None) self.low_disk_space_thumb.on_color[:] = (1, 0.0, 0.0, 0.8) self.low_disk_space_thumb.status_text = "Low disk space"
def __init__(self): QThread.__init__(self) from hotkey import Hotkey self.hotkey = Hotkey(self.handle_hotkey)
def init_ui(self): desc_text = ui.Info_Text(self._choreography_description_text()) self.__ui_selector_choreography = ui.Selector( "selected_choreography_class", self, label="Choreography", selection_getter=self.__choreography_selection_getter, ) self.__ui_selector_gazer = ui.Selector( "selected_gazer_class", self, label="Gaze Mapping", labels=[g.label for g in self.user_selectable_gazer_classes()], selection=self.user_selectable_gazer_classes(), ) self.__ui_gazer_description_text = ui.Info_Text("") self._update_gazer_description_ui_text() best_practices_text = ui.Info_Text( "Read more about best practices at docs.pupil-labs.com" ) custom_ui_elements = self._init_custom_menu_ui_elements() super().init_ui() self.add_menu() self.menu.label = self.label self.menu_icon.order = self.order self.menu_icon.tooltip = "Calibration" # Construct menu UI self.menu.append(self.__ui_selector_choreography) self.menu.append(desc_text) if len(custom_ui_elements) > 0: self.menu.append(ui.Separator()) for ui_elem in custom_ui_elements: self.menu.append(ui_elem) self.menu.append(ui.Separator()) else: self.menu.append(ui.Separator()) self.menu.append(self.__ui_selector_gazer) self.menu.append(self.__ui_gazer_description_text) self.menu.append(best_practices_text) if self.shows_action_buttons: def calibration_setter(should_be_on): self.__signal_should_toggle_processing( should_be_on=should_be_on, mode=ChoreographyMode.CALIBRATION ) def validation_setter(should_be_on): self.__signal_should_toggle_processing( should_be_on=should_be_on, mode=ChoreographyMode.VALIDATION ) self.__ui_button_calibration = ui.Thumb( "is_active", self, label="C", hotkey=Hotkey.GAZE_CALIBRATION_CAPTURE_HOTKEY(), setter=calibration_setter, on_color=self._THUMBNAIL_COLOR_ON, ) self.__ui_button_validation = ui.Thumb( "is_active", self, label="T", hotkey=Hotkey.GAZE_VALIDATION_CAPTURE_HOTKEY(), setter=validation_setter, on_color=self._THUMBNAIL_COLOR_ON, ) self.__toggle_mode_button_visibility( is_visible=True, mode=ChoreographyMode.CALIBRATION ) self.__toggle_mode_button_visibility( is_visible=True, mode=ChoreographyMode.VALIDATION )
def init_ui(self): self.add_menu() self.menu.label = "Fixation Detector" def set_max_dispersion(new_value): self.max_dispersion = new_value self.notify_all( {"subject": "fixation_detector.should_recalculate", "delay": 1.0} ) def set_min_duration(new_value): self.min_duration = min(new_value, self.max_duration) self.notify_all( {"subject": "fixation_detector.should_recalculate", "delay": 1.0} ) def set_max_duration(new_value): self.max_duration = max(new_value, self.min_duration) self.notify_all( {"subject": "fixation_detector.should_recalculate", "delay": 1.0} ) def jump_next_fixation(_): cur_idx = self.last_frame_idx all_idc = [f["mid_frame_index"] for f in self.g_pool.fixations] if not all_idc: logger.warning("No fixations available") return # wrap-around index tar_fix = bisect_right(all_idc, cur_idx) % len(all_idc) self.notify_all( { "subject": "seek_control.should_seek", "index": int(self.g_pool.fixations[tar_fix]["mid_frame_index"]), } ) def jump_prev_fixation(_): cur_idx = self.last_frame_idx all_idc = [f["mid_frame_index"] for f in self.g_pool.fixations] if not all_idc: logger.warning("No fixations available") return # wrap-around index tar_fix = (bisect_left(all_idc, cur_idx) - 1) % len(all_idc) self.notify_all( { "subject": "seek_control.should_seek", "index": int(self.g_pool.fixations[tar_fix]["mid_frame_index"]), } ) for help_block in self.__doc__.split("\n\n"): help_str = help_block.replace("\n", " ").replace(" ", "").strip() self.menu.append(ui.Info_Text(help_str)) self.menu.append( ui.Info_Text( "To start the export, wait until the detection has finished and press " "the export button or type 'e'." ) ) self.menu.append( ui.Info_Text( "Note: This plugin does not process fixations that have been calculated" " and recorded in real time." ) ) self.menu.append( ui.Slider( "max_dispersion", self, min=0.01, step=0.1, max=5.0, label="Maximum Dispersion [degrees]", setter=set_max_dispersion, ) ) self.menu.append( ui.Slider( "min_duration", self, min=10, step=10, max=4000, label="Minimum Duration [milliseconds]", setter=set_min_duration, ) ) self.menu.append( ui.Slider( "max_duration", self, min=10, step=10, max=4000, label="Maximum Duration [milliseconds]", setter=set_max_duration, ) ) self.menu.append( ui.Text_Input( "status", self, label="Detection progress:", setter=lambda x: None ) ) self.menu.append(ui.Switch("show_fixations", self, label="Show fixations")) self.current_fixation_details = ui.Info_Text("") self.menu.append(self.current_fixation_details) self.next_fix_button = ui.Thumb( "jump_next_fixation", setter=jump_next_fixation, getter=lambda: False, label=chr(0xE044), hotkey=Hotkey.FIXATION_NEXT_PLAYER_HOTKEY(), label_font="pupil_icons", ) self.next_fix_button.status_text = "Next Fixation" self.g_pool.quickbar.append(self.next_fix_button) self.prev_fix_button = ui.Thumb( "jump_prev_fixation", setter=jump_prev_fixation, getter=lambda: False, label=chr(0xE045), hotkey=Hotkey.FIXATION_PREV_PLAYER_HOTKEY(), label_font="pupil_icons", ) self.prev_fix_button.status_text = "Previous Fixation" self.g_pool.quickbar.append(self.prev_fix_button)