def load_config_file(self): if not os.path.isfile(mf_config_path): self.build_config_file(self.default_config()) parser = configparser.ConfigParser(comment_prefixes='# ', allow_no_value=True) with open(mf_config_path) as fi: parser.read_file(fi) self.merge_config_default(parser) # Check if any binds in config file is already used by the system, and remove them in case used = system_hotkey.check_used_hotkeys() for key, bind in parser['KEYBINDS'].items(): valid_entry = bind is not None and len(bind) > 0 and bind[0] in [ "[", "(" ] if valid_entry and tuple( str(x).lower() for x in other_utils.safe_eval(bind)) in used: parser['KEYBINDS'][key] = str( [other_utils.safe_eval(bind)[0], 'NO_BIND']) messagebox.showerror( 'Used keybind', 'Configured keybind for %s (%s) is already in use by the system.\nUnbinding "%s" - please set a new bind in options.' % (key, bind, key)) return parser
def add_flag(self, flag_name, comment=None, pack=True, config_section='OPTIONS'): lf = tkd.LabelFrame(self, height=LAB_HEIGHT, width=LAB_WIDTH) lf.propagate(False) if pack: lf.pack(expand=False, fill=tk.X) lab = tkd.Label(lf, text=flag_name) lab.pack(side=tk.LEFT) if comment is not None: tkd.create_tooltip(lab, comment) flag_attr = flag_name.lower().replace(' ', '_').replace('-', '_') setattr(self, flag_attr, tk.StringVar(lf)) sv = getattr(self, flag_attr) off_button = tkd.Radiobutton(lf, text='Off', variable=sv, indicatoron=False, value=False, width=4, padx=4) on_button = tkd.Radiobutton(lf, text='On', variable=sv, indicatoron=False, value=True, width=4, padx=3) if other_utils.safe_eval(self.main_frame.cfg[config_section][flag_attr]): on_button.invoke() setattr(self, flag_attr + '_invoked', True) else: off_button.invoke() setattr(self, flag_attr + '_invoked', False) off_button.config(command=lambda: self.toggle_button(flag_attr)) on_button.config(command=lambda: self.toggle_button(flag_attr)) on_button.pack(side=tk.RIGHT) off_button.pack(side=tk.RIGHT) return lf
def toggle_button(self, attr): val = other_utils.safe_eval(getattr(self, attr).get()) if bool(val) == getattr(self, attr + '_invoked'): return setattr(self, attr + '_invoked', bool(val)) setattr(self.main_frame, attr, val) if attr.lower() == 'always_on_top': self.main_frame.root.wm_attributes("-topmost", self.main_frame.always_on_top) elif attr.lower() == 'tab_switch_keys_global': self.main_frame.toggle_tab_keys_global()
def load_config_file(self): if not os.path.isfile(mf_config_path): self.build_config_file(self.default_config()) parser = configparser.ConfigParser(comment_prefixes='# ', allow_no_value=True) with open(mf_config_path) as fi: parser.read_file(fi) if 'automode' in parser['DEFAULT'] and other_utils.safe_eval( parser['DEFAULT'] ['automode']) is True and 'game_path' not in parser['DEFAULT']: parser['DEFAULT']['game_path'] = self.find_SP_game_path() try: ver = parser.get('VERSION', 'version') except: ver = 0 if ver != version: self.delete_config_file() parser = self.load_config_file() messagebox.showinfo( 'Config file recreated', 'You downloaded a new version. To ensure compatibility, config file has been recreated with default options.' ) # Check if any binds in config file is already used by the system, and remove them in case used = system_hotkey.check_used_hotkeys() for key, bind in parser['KEYBINDS'].items(): if len(bind) > 0 and bind[0] in ["[", "("] and tuple( str(x).lower() for x in other_utils.safe_eval(bind)) in used: parser['KEYBINDS'][key] = str( [other_utils.safe_eval(bind)[0], 'NO_BIND']) messagebox.showerror( 'Used keybind', 'Configured keybind for %s (%s) is already in use by the system.\nUnbinding "%s" - please set a new bind in options.' % (key, bind, key)) return parser
def add_automode_flag(self): lf = tkd.LabelFrame(self, height=LAB_HEIGHT, width=LAB_WIDTH) lf.propagate(False) lf.pack(expand=False, fill=tk.X) lab = tkd.Label( lf, text='Automode', tooltip= 'Enable automode for monitoring when you enter and exit games') lab.pack(side=tk.LEFT) self.automode_var = tk.StringVar(lf) off_btn = tkd.Radiobutton(lf, text='Off', variable=self.automode_var, indicatoron=False, value=0, width=5, padx=4) simple_btn = tkd.Radiobutton(lf, text='Simple', variable=self.automode_var, indicatoron=False, value=1, width=5, padx=3) adv_btn = tkd.Radiobutton(lf, text='Advanced', variable=self.automode_var, indicatoron=False, value=2, width=7, padx=3) cfg_mode = other_utils.safe_eval( self.main_frame.cfg['AUTOMODE']['automode']) if cfg_mode == 2: adv_btn.invoke() elif cfg_mode == 1: simple_btn.invoke() else: off_btn.invoke() off_btn.config(command=self.toggle_automode_btn) simple_btn.config(command=self.toggle_automode_btn) adv_btn.config(command=self.toggle_automode_btn) adv_btn.pack(side=tk.RIGHT) simple_btn.pack(side=tk.RIGHT) off_btn.pack(side=tk.RIGHT) return off_btn, simple_btn, adv_btn
def __init__(self, main_frame, timer_frame, drop_frame, parent=None, **kw): tkd.Frame.__init__(self, parent, kw) self.main_frame = main_frame self.modifier_options = system_hotkey.modifier_options self.character_options = system_hotkey.character_options self.hk = system_hotkey.SystemHotkey() lf = tkd.Frame(self, height=20, width=179) lf.pack(expand=True, fill=tk.BOTH) lf.propagate(False) tkd.Label(lf, text='Action', font='Helvetica 11 bold', justify=tk.LEFT).pack(side=tk.LEFT) tkd.Label(lf, text='Key ', font='Helvetica 11 bold', justify=tk.LEFT, width=9).pack(side=tk.RIGHT) tkd.Label(lf, text=' Modifier', font='Helvetica 11 bold', justify=tk.LEFT, width=7).pack(side=tk.RIGHT) self.add_hotkey(label_name='Start new run', keys=other_utils.safe_eval( main_frame.cfg['KEYBINDS']['start_key']), func=timer_frame.stop_start) self.add_hotkey(label_name='End run', keys=other_utils.safe_eval( main_frame.cfg['KEYBINDS']['end_key']), func=timer_frame.stop) self.add_hotkey(label_name='Delete prev', keys=other_utils.safe_eval( main_frame.cfg['KEYBINDS']['delete_prev_key']), func=timer_frame.delete_prev) self.add_hotkey(label_name='Pause', keys=other_utils.safe_eval( main_frame.cfg['KEYBINDS']['pause_key']), func=timer_frame.pause) self.add_hotkey(label_name='Add drop', keys=other_utils.safe_eval( main_frame.cfg['KEYBINDS']['drop_key']), func=drop_frame.add_drop) self.add_hotkey(label_name='Reset lap', keys=other_utils.safe_eval( main_frame.cfg['KEYBINDS']['reset_key']), func=timer_frame.reset_lap) self.add_hotkey(label_name='Make unclickable', keys=other_utils.safe_eval( main_frame.cfg['KEYBINDS']['make_unclickable']), func=main_frame.set_clickthrough)
def add_num_entry(self, flag_name, comment=None): lf = tkd.LabelFrame(self, height=LAB_HEIGHT, width=LAB_WIDTH) lf.propagate(False) lf.pack(expand=False, fill=tk.X) lab = tkd.Label(lf, text=flag_name) lab.pack(side=tk.LEFT) if comment is not None: tkd.create_tooltip(lab, comment) flag_attr = flag_name.lower().replace(' ', '_').replace('-', '_').replace('(', '').replace(')', '') setattr(self, flag_attr + '_sv', tk.StringVar()) sv = getattr(self, flag_attr + '_sv') sv.set(other_utils.safe_eval(self.main_frame.cfg['OPTIONS'][flag_attr])) tkd.RestrictedEntry(lf, textvariable=sv, num_only=True, width=13).pack(side=tk.RIGHT, padx=3) sv.trace_add('write', lambda name, index, mode: setattr(self.main_frame, flag_attr, float('0' + sv.get())))
def toggle_button(self, attr, first=False): val = other_utils.safe_eval(getattr(self, attr).get()) if not first and bool(val) == getattr(self, attr + '_invoked'): return setattr(self, attr + '_invoked', bool(val)) setattr(self.main_frame, attr, val) show_drops = other_utils.safe_eval(getattr(self, 'show_drops_section').get()) if hasattr(self, 'show_drops_section') else 0 show_advanced = other_utils.safe_eval(getattr(self, 'show_advanced_tracker').get()) if hasattr(self, 'show_advanced_tracker') else 0 if attr == 'show_buttons': btn_height = 30 if val: self.main_frame.root.update() self.main_frame.root.config(height=self.main_frame.root.winfo_height() + btn_height) self.main_frame.btn_frame.pack(expand=True, fill=tk.BOTH, side=tk.TOP) else: if not first: self.main_frame.root.update() self.main_frame.root.config(height=self.main_frame.root.winfo_height() - btn_height) self.main_frame.btn_frame.forget() elif attr == 'show_drops_section': btn_height = 22 if val: self.main_frame.root.update() self.main_frame.root.config(height=self.main_frame.root.winfo_height() + btn_height) self.main_frame.drops_frame.pack(fill=tk.BOTH, expand=True, side=tk.TOP) else: if hasattr(self.main_frame, 'drops_caret') and self.main_frame.drops_caret.active: self.main_frame.drops_caret.invoke() if not first: self.main_frame.root.update() self.main_frame.root.config(height=self.main_frame.root.winfo_height() - btn_height) self.main_frame.drops_frame.forget() elif attr == 'show_advanced_tracker': btn_height = 22 if val: self.main_frame.root.update() self.main_frame.root.config(height=self.main_frame.root.winfo_height() + btn_height) self.main_frame.adv_stats_frame.pack(fill=tk.BOTH, expand=True, side=tk.BOTTOM) else: if hasattr(self.main_frame, 'advanced_stats_caret') and self.main_frame.advanced_stats_caret.active: self.main_frame.advanced_stats_caret.invoke() if not first: self.main_frame.root.update() self.main_frame.root.config(height=self.main_frame.root.winfo_height() - btn_height) self.main_frame.adv_stats_frame.forget() elif attr == 'show_xp_tracker': if not first and hasattr(self.main_frame.advanced_stats_tracker, 'after_updater'): # self.main_frame.toggle_advanced_stats_frame(show=False) self.main_frame.advanced_stats_caret.invoke() self.main_frame.root.update() self.main_frame.advanced_stats_caret.invoke() if show_drops or show_advanced: self.main_frame.caret_frame.pack(fill=tk.BOTH, expand=True, side=tk.BOTTOM) else: self.main_frame.caret_frame.forget()
def __init__(self): # Check if application is already open self.title = 'MF run counter' # Create error logger lh = logging.FileHandler(filename='mf_timer.log', mode='w', delay=True) logging.basicConfig(handlers=[lh], format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s', datefmt='%H:%M:%S', level=logging.WARNING) # Check OS self.os_platform = platform.system() self.os_release = platform.release() if not self.os_platform == 'Windows': raise SystemError("MF Run Counter only supports windows") # Create root self.root = tkd.Tk() # Ensure errors are handled with an exception pop-up if encountered self.root.report_callback_exception = self.report_callback_exception # Build/load config file self.cfg = self.load_config_file() if hasattr(logging, self.cfg['DEFAULT']['logging_level']): logging.getLogger().setLevel(getattr(logging, self.cfg['DEFAULT']['logging_level'])) self.SP_game_path = self.cfg['DEFAULT']['SP_game_path'] self.MP_game_path = self.cfg['DEFAULT']['MP_game_path'] self.herokuapp_username = self.cfg['DEFAULT']['herokuapp_username'] self.herokuapp_password = base64.b64decode(self.cfg['DEFAULT']['herokuapp_password']).decode('utf-8') self.webproxies = other_utils.safe_eval(self.cfg['DEFAULT']['webproxies']) self.automode = other_utils.safe_eval(self.cfg['AUTOMODE']['automode']) self.end_run_in_menu = other_utils.safe_eval(self.cfg['AUTOMODE']['end_run_in_menu']) self.pause_on_esc_menu = other_utils.safe_eval(self.cfg['AUTOMODE']['pause_on_esc_menu']) self.always_on_top = other_utils.safe_eval(self.cfg['OPTIONS']['always_on_top']) self.tab_switch_keys_global = other_utils.safe_eval(self.cfg['OPTIONS']['tab_switch_keys_global']) self.check_for_new_version = other_utils.safe_eval(self.cfg['OPTIONS']['check_for_new_version']) self.enable_sound_effects = other_utils.safe_eval(self.cfg['OPTIONS']['enable_sound_effects']) self.start_run_delay_seconds = other_utils.safe_eval(self.cfg['OPTIONS']['start_run_delay_seconds']) self.show_drops_tab_below = other_utils.safe_eval(self.cfg['OPTIONS']['show_drops_tab_below']) self.active_theme = self.cfg['OPTIONS']['active_theme'].lower() self.auto_upload_herokuapp = other_utils.safe_eval(self.cfg['OPTIONS']['auto_upload_herokuapp']) self.auto_archive_hours = other_utils.safe_eval(self.cfg['OPTIONS']['auto_archive_hours']) self.autocompletion_unids = other_utils.safe_eval(self.cfg['OPTIONS']['autocompletion_unids']) self.add_to_last_run = other_utils.safe_eval(self.cfg['OPTIONS']['add_to_last_run']) self.disable_scaling = other_utils.safe_eval(self.cfg['OPTIONS']['disable_dpi_scaling']) # UI config self.show_buttons = other_utils.safe_eval(self.cfg['UI']['show_buttons']) self.show_drops_section = other_utils.safe_eval(self.cfg['UI']['show_drops_section']) self.show_advanced_tracker = other_utils.safe_eval(self.cfg['UI']['show_advanced_tracker']) self.show_xp_tracker = other_utils.safe_eval(self.cfg['UI']['show_xp_tracker']) # Initiate variables for memory reading self.is_user_admin = reader_utils.is_user_admin() self.advanced_error_thrown = False self.d2_reader = None # Load theme if self.active_theme not in available_themes: self.active_theme = 'vista' self.theme = Theme(used_theme=self.active_theme) # Create hotkey queue and initiate process for monitoring the queue self.queue = queue.Queue(maxsize=1) self.process_queue() # Check for version update if self.check_for_new_version: self.dl_count = github_releases.check_newest_version(release_repo) else: self.dl_count = '' # Load profile info self.make_profile_folder() self.profiles = [x[:-5] for x in os.listdir('Profiles') if x.endswith('.json') and not x == 'grail.json'] self.active_profile = self.cfg['DEFAULT']['active_profile'] if len(self.profiles) == 0: self.active_profile = '' elif len(self.profiles) > 0 and self.active_profile not in self.profiles: self.active_profile = self.profiles[0] self.profiles = self.sorted_profiles() # Modify root window self.root.title(self.title) self.clickable = True self.root.resizable(False, False) self.root.geometry('+%d+%d' % other_utils.safe_eval(self.cfg['DEFAULT']['window_start_position'])) self.root.config(borderwidth=2, height=365, width=240, relief='raised') # self.root.wm_attributes("-transparentcolor", "purple") self.root.wm_attributes("-topmost", self.always_on_top) self.root.focus_get() self.root.protocol("WM_DELETE_WINDOW", self.Quit) self.root.iconbitmap(media_path + 'icon.ico') self.root.pack_propagate(False) # Build banner image and make window draggable on the banner d2banner = media_path + 'd2icon.png' img = tk.PhotoImage(file=d2banner) self.img_panel = tkd.Label(self.root, image=img, borderwidth=0) self.img_panel.pack() self.img_panel.bind("<ButtonPress-1>", self.root.start_move) self.img_panel.bind("<ButtonRelease-1>", self.root.stop_move) self.img_panel.bind("<B1-Motion>", self.root.on_motion) self.root.bind("<Delete>", self.delete_selection) self.root.bind("<Left>", self.root.moveleft) self.root.bind("<Right>", self.root.moveright) self.root.bind("<Up>", self.root.moveup) self.root.bind("<Down>", self.root.movedown) # Add buttons to main widget self.btn_frame = tkd.Frame(self.root) tkd.Button(self.btn_frame, text='Delete selection', command=self.delete_selection).pack(side=tk.LEFT, expand=True, fill=tk.BOTH, padx=[2, 1], pady=1) tkd.Button(self.btn_frame, text='Archive session', command=self.ArchiveReset).pack(side=tk.LEFT, expand=True, fill=tk.BOTH, padx=[0, 1], pady=1) # Build tabs self.caret_frame = tkd.Frame(self.root) self.drops_frame = tkd.Frame(self.caret_frame) self.adv_stats_frame = tkd.Frame(self.caret_frame) self.tabcontrol = tkd.Notebook(self.root) self.tabcontrol.pack(expand=False, fill=tk.BOTH) self.profile_tab = Profile(self, parent=self.tabcontrol) self.timer_tab = MFRunTimer(self, parent=self.tabcontrol) self.drops_tab = Drops(self, parent=self.drops_frame) self.options_tab = Options(self, self.timer_tab, self.drops_tab, parent=self.tabcontrol) self.grail_tab = Grail(self, parent=self.tabcontrol) self.about_tab = About(self, parent=self.tabcontrol) self.tabcontrol.add(self.timer_tab, text='Timer') self.tabcontrol.add(self.options_tab, text='Options') self.tabcontrol.add(self.profile_tab, text='Profile') self.tabcontrol.add(self.grail_tab, text='Grail') self.tabcontrol.add(self.about_tab, text='About') self.root.bind("<<NotebookTabChanged>>", lambda _e: self.notebook_tab_change()) self.profile_tab.update_descriptive_statistics() self.toggle_drops_frame(show=self.show_drops_tab_below) self.drops_caret = tkd.CaretButton(self.drops_frame, active=self.show_drops_tab_below, command=self.toggle_drops_frame, text='Drops', compound=tk.RIGHT, height=13) self.drops_caret.propagate(False) self.drops_caret.pack(side=tk.BOTTOM, fill=tk.X, expand=True, padx=[2, 1], pady=[0, 1]) tracker_is_active = other_utils.safe_eval(self.cfg['AUTOMODE']['advanced_tracker_open']) and self.automode == 2 and self.is_user_admin self.advanced_stats_tracker = StatsTracker(self, self.adv_stats_frame) self.advanced_stats_caret = tkd.CaretButton(self.adv_stats_frame, active=tracker_is_active, text='Advanced stats', compound=tk.RIGHT, height=13, command=self.toggle_advanced_stats_frame) self.advanced_stats_caret.propagate(False) self.advanced_stats_caret.pack(side=tk.BOTTOM, fill=tk.X, expand=True, padx=[2, 1], pady=[0, 1]) # Register binds for changing tabs if self.tab_switch_keys_global: self.options_tab.tab2.hk.register(['control', 'shift', 'next'], callback=lambda event: self.queue.put(self.tabcontrol.next_tab)) self.options_tab.tab2.hk.register(['control', 'shift', 'prior'], callback=lambda event: self.queue.put(self.tabcontrol.prev_tab)) else: self.root.bind_all('<Control-Shift-Next>', lambda event: self.tabcontrol.next_tab()) self.root.bind_all('<Control-Shift-Prior>', lambda event: self.tabcontrol.prev_tab()) # Load save state and start autosave process active_state = self.load_state_file() self.LoadActiveState(active_state) self.root.after(30000, self._autosave_state) # Apply styling options self.theme.apply_theme_style() self.theme.update_colors() # Automode and advanced stats loop self.am_lab = tk.Text(self.root, height=1, width=13, wrap=tk.NONE, bg="black", font=('Segoe UI', 9), cursor='', borderwidth=0) self.am_lab.tag_configure("am", foreground="white", background="black") self.am_lab.tag_configure("on", foreground="lawn green", background="black") self.am_lab.tag_configure("off", foreground="red", background="black") self.am_lab.place(x=1, y=0.4) self.toggle_automode() self.toggle_advanced_stats_frame(show=tracker_is_active) # A trick to disable windows DPI scaling - the app doesnt work well with scaling, unfortunately if self.os_release == '10' and self.disable_scaling: ctypes.windll.shcore.SetProcessDpiAwareness(2) # Used if "auto archive session" is activated self.profile_tab.auto_reset_session() # Pressing ALT_L paused UI updates when in focus, disable (probably hooked to opening menus) self.root.unbind_all('<Alt_L>') # Start the program self.root.mainloop()
def toggle_automode_btn(self, first=False, show_error=True): got_val = other_utils.safe_eval(self.automode_var.get()) if first is False and self.main_frame.automode == got_val: return self.main_frame.automode = got_val if got_val == 1: self.gamemode_frame.pack(expand=False, fill=tk.X) self.gamemode_lab.pack(side=tk.LEFT) self.gamemode_cb.pack(side=tk.RIGHT) self.charname_frame.pack(expand=False, fill=tk.X) self.charname_text_lab.pack(side=tk.LEFT) self.charname_val_lab.pack(side=tk.RIGHT) self.sp_path_lab.pack(pady=[0, 0]) self.sp_path_entry.pack(fill=tk.BOTH, padx=4) self.sp_path_frame.pack() self.sp_path_get.pack(side=tk.LEFT, padx=1) self.sp_path_apply.pack(side=tk.LEFT) self.mp_path_lab.pack(pady=[0, 0]) self.mp_path_entry.pack(fill=tk.BOTH, padx=4) self.mp_path_frame.pack() self.mp_path_get.pack(side=tk.LEFT, padx=1) self.mp_path_apply.pack(side=tk.LEFT) else: self.gamemode_frame.forget() self.gamemode_lab.forget() self.gamemode_cb.forget() self.charname_frame.forget() self.charname_text_lab.forget() self.charname_val_lab.forget() self.sp_path_lab.forget() self.sp_path_entry.forget() self.sp_path_frame.forget() self.sp_path_get.forget() self.sp_path_apply.forget() self.mp_path_lab.forget() self.mp_path_entry.forget() self.mp_path_frame.forget() self.mp_path_get.forget() self.mp_path_apply.forget() if got_val == 2: if first is False and not tk_utils.mbox( msg= 'Activating "Advanced automode" is highly discouraged when playing multiplayer, as it might result in a ban.\n\n' 'Explanation: Advanced automode utilizes "memory reading" of the D2 process\n' 'to discover information about the current game state, and this could be deemed cheating\n\n' 'If you still wish to continue, click "OK"'): self.automode_var.set('0') return self.toggle_automode_btn(first=first, show_error=show_error) else: self.main_frame.load_memory_reader(show_err=show_error) if self.main_frame.advanced_error_thrown: self.automode_var.set('0') return self.toggle_automode_btn(first=first, show_error=show_error) else: self.advanced_mode_stop.pack(expand=False, fill=tk.X, pady=[4, 0]) self.advanced_pause_on_esc_menu.pack(expand=False, fill=tk.X) self.advanced_automode_warning.pack(pady=6) else: if not first and self.main_frame.advanced_stats_caret.active: self.main_frame.advanced_stats_caret.invoke() self.advanced_automode_warning.forget() self.advanced_mode_stop.forget() self.advanced_pause_on_esc_menu.forget() self.main_frame.d2_reader = None if not first: self.main_frame.toggle_automode()