class sys_tray(): def __init__(self, _fnc): self.fnc = _fnc def pas_thq(self, sysTrayIcon): global var_thq var_thq = "exit" def stray_dst(self, sysTrayIcon): self.systrayicon.shutdown() # def setng(self, sysTrayIcon): # pass def stray_strt(self, sysTrayIcon): menu_options = () # (('Settings', None, self.setng),) self.systrayicon = SysTrayIcon("va.ico", "Virtual Assistant", menu_options, on_quit=self.pas_thq) self.systrayicon.start()
class Trayer(): def __init__(self, mouse_tracker): self.mouse_tracker = mouse_tracker self.activated = True self.menu_options = ( ("Activate", None, self.activate), ("Deactivate", None, self.deactivate), ) self.systray = SysTrayIcon("bogazici.ico", "BUICS Security Lab", self.menu_options) self.mouse_tracker.start_listening() def start(self): self.systray.start() def activate(self, systray): self.activated = True self.mouse_tracker.start_listening() print("Activated") def deactivate(self, systray): self.activated = False self.mouse_tracker.stop_listening() print("Deactivated")
class SnuClipboardManager(NormalApp): icon = 'data/icon_small.png' tray = None tray_thread = None window_minimized = BooleanProperty(False) shift_pressed = BooleanProperty(False) ctrl_pressed = BooleanProperty(False) alt_pressed = BooleanProperty(False) window_leave_timeout = ObjectProperty(allownone=True) on_top = BooleanProperty(False) window_min_width = NumericProperty(130) window_title = 'Snu Clipboard Manager' window_width_target = NumericProperty(200) modify_mode = BooleanProperty(False) clipboard_updater = ObjectProperty(allownone=True) current_clipboard = StringProperty('') clipboard_folder = StringProperty('') clipboard_folders = ListProperty() clipboard_data = ListProperty() clipboard_data_display = ListProperty() clipboard_history = ListProperty() hiddens = ListProperty() main_history_length = NumericProperty(1) data_directory = StringProperty('') show_undo = BooleanProperty(True) show_strip = BooleanProperty(False) max_history = NumericProperty(20) preset_type = StringProperty() preset_element = StringProperty() preset_second_element = StringProperty() edit_type = StringProperty() edit_path = StringProperty() edit_name = StringProperty() edit_name_original = StringProperty() edit_section = StringProperty() edit_content = StringProperty() search_text = StringProperty() search_results = ListProperty() search_content = BooleanProperty(True) def search(self, *_): data = [] if self.search_text: search_text = self.search_text.lower() for clip in self.clipboard_data: if clip['viewtype'] == 'item': if (search_text in clip['text'].lower()) or ( self.search_content and search_text in clip['clipboard'].lower()): new_clip = clip.copy() new_clip['hidden'] = False new_clip['show_folder'] = True data.append(new_clip) self.search_results = data def dismiss_popup(self, *_): if self.popup is not None: try: self.popup.dismiss() except: pass self.popup = None def rescale_interface(self, *_, force=False): """Called when the window changes resolution, calculates variables dependent on screen size""" self.store_window_size() if Window.width != self.last_width: self.last_width = Window.width self.popup_x = min(Window.width, 640) if (Window.height != self.last_height) or force: self.last_height = Window.height self.button_scale = int( float(cm(0.85)) * (int(self.config.get("Settings", "buttonsize")) / 100)) self.text_scale = int( (self.button_scale / 3) * int(self.config.get("Settings", "textsize")) / 100) self.display_border = self.button_scale / 3 self.display_padding = self.button_scale / 4 def get_application_config(self, **kwargs): if platform == 'win': self.data_directory = os.getenv( 'APPDATA') + os.path.sep + "Snu Clipboard Manager" if not os.path.isdir(self.data_directory): os.makedirs(self.data_directory) elif platform == 'linux': self.data_directory = os.path.expanduser( '~') + os.path.sep + ".snuclipboardmanager" if not os.path.isdir(self.data_directory): os.makedirs(self.data_directory) elif platform == 'macosx': self.data_directory = os.path.expanduser( '~') + os.path.sep + ".snuclipboardmanager" if not os.path.isdir(self.data_directory): os.makedirs(self.data_directory) elif platform == 'android': self.data_directory = self.user_data_dir else: self.data_directory = os.path.sep config_file = os.path.realpath( os.path.join(self.data_directory, "snuclipboardmanager.ini")) return config_file def on_config_change(self, config, section, key, value): self.load_config_values() self.rescale_interface(force=True) def load_config_values(self): self.show_undo = self.config.getboolean("Settings", 'showundo') self.show_strip = self.config.getboolean("Settings", 'showstrip') max_history = int(self.config.get("Settings", 'max_history')) if max_history < 1: self.max_history = 1 else: self.max_history = max_history main_history_length = int( self.config.get("Settings", "main_history_length")) if main_history_length > self.max_history: self.main_history_length = self.max_history elif main_history_length < 1: self.main_history_length = 1 else: self.main_history_length = main_history_length def load_clipboards(self, edit=False): self.load_hiddens() self.clipboard_data = [] if edit: self.clipboard_data.append({ 'text': 'Add Section', 'viewtype': 'adder-heading', 'hidden': False, 'section': self.clipboard_folder, 'path': self.clipboard_folder, 'show_folder': False }) if os.path.isdir(self.clipboard_folder): sections = os.listdir(self.clipboard_folder) for section in sections: if section in self.hiddens and not edit: hidden = True else: hidden = False section_folder = os.path.join(self.clipboard_folder, section) if os.path.isdir(section_folder): self.clipboard_data.append({ 'text': section, 'viewtype': 'heading', 'hidden': hidden, 'section': section, 'path': section_folder, 'section_folder': self.clipboard_folder, 'show_folder': False }) if edit: self.clipboard_data.append({ 'text': 'Add Item', 'viewtype': 'adder-item', 'hidden': hidden, 'section': section, 'path': section_folder, 'section_folder': self.clipboard_folder, 'show_folder': False }) for file in os.listdir(section_folder): fullpath = os.path.join(section_folder, file) if os.path.isfile(fullpath): with open(fullpath, 'r') as datafile: try: data = datafile.read() except: continue self.clipboard_data.append({ 'text': os.path.splitext(file)[0], 'viewtype': 'item', 'clipboard': data, 'hidden': hidden, 'section': section, 'path': fullpath, 'section_folder': section_folder, 'show_folder': False }) self.update_display_clipboards() def toggle_hide_section(self, section, hide_others=False): for data in self.clipboard_data: if data['section'] == section: if hide_others: data['hidden'] = False else: data['hidden'] = not data['hidden'] elif hide_others: data['hidden'] = True self.save_hiddens() self.root.ids.presets.refresh_from_data() self.update_display_clipboards() def update_display_clipboards(self, *_): data = [] for clip in self.clipboard_data: if not clip['hidden'] or clip['viewtype'] == 'heading': data.append(clip) self.clipboard_data_display = data def clear_history(self): self.clipboard_history = self.clipboard_history[:1] def remove_history_item(self, index): if index > 0: self.clipboard_history.pop(index) def remove_history_matches(self, clipboard): for clip in reversed(self.clipboard_history): if clip['text'] == clipboard: self.clipboard_history.remove(clip) def set_history_item(self, index): try: history_item = self.clipboard_history.pop(index) except: return self.clipboard_history.insert(0, history_item) self.set_clipboard(history_item['text'], skip_history=True) def set_clipboard_from_widget(self, widget): if widget.focus: self.set_clipboard(widget.text, overwrite_history=True) def set_clipboard(self, text, skip_history=False, overwrite_history=False): #Sets a preset to the clipboard if text and text != self.current_clipboard: Clipboard.copy(text) if overwrite_history: if len(self.clipboard_history) == 0: self.clipboard_history.append({'text': text}) else: self.clipboard_history[0]['text'] = text self.current_clipboard = text self.refresh_history_areas() if skip_history: self.current_clipboard = text def refresh_history_areas(self): self.root.ids.historyFull.refresh_from_data() self.root.ids.historyShort.refresh_from_data() def update_current_clipboard(self, *_): #Sets the current clipboard to a local variable clipboard = Clipboard.paste() if clipboard and self.current_clipboard != clipboard: self.current_clipboard = clipboard if self.config.getboolean('Settings', 'no_duplicates'): self.remove_history_matches(clipboard) self.clipboard_history.insert(0, {'text': clipboard}) self.clipboard_history = self.clipboard_history[:self.max_history] def undo_clipboard(self, *_): self.set_history_item(1) def strip_clipboard(self, *_): Clipboard.copy(self.current_clipboard.strip()) def settings_mode(self, heading=''): self.close_settings() if self.modify_mode: self.modify_mode = False if self.root.ids.editArea.current == 'edit': self.load_clipboards() else: self.modify_mode = True if self.root.ids.editArea.current == 'edit': self.load_clipboards(edit=True) self.size_window() def delete_preset(self, preset): self.preset_type = preset.viewtype self.preset_element = preset.path if preset.viewtype == 'heading': confirm_text = "This entire section of presets will be deleted permanently" else: confirm_text = "This single preset will be deleted permanently" self.dismiss_popup() content = ConfirmPopupContent(text=confirm_text, yes_text='Delete', no_text="Don't Delete", warn_yes=True) content.bind(on_answer=self.delete_preset_answer) self.popup = NormalPopup(title="Confirm Delete ", content=content, size_hint=(1, None), size=(1000, self.button_scale * 4)) self.popup.bind(on_dismiss=self.dismiss_popup) self.popup.open() def delete_preset_answer(self, instance, answer): self.dismiss_popup() if answer == 'yes': if self.preset_type == 'item': if os.path.isfile(self.preset_element): os.remove(self.preset_element) self.load_clipboards(edit=True) elif self.preset_type == 'heading': if os.path.isdir(self.preset_element): shutil.rmtree(self.preset_element) self.load_clipboards(edit=True) def edit_preset(self, preset): if preset.viewtype == 'item': self.edit_type = preset.viewtype self.edit_path = preset.path self.edit_name = preset.text self.edit_name_original = preset.text self.edit_section = preset.section_folder self.edit_content = preset.clipboard self.root.ids.editContentArea.focus = True elif preset.viewtype == 'heading': self.preset_type = 'heading' self.preset_element = preset.section_folder self.preset_second_element = preset.text content = InputPopupContent(text="Rename folder to:", input_text=preset.text, hint='Folder Name') content.bind(on_answer=self.rename_folder_answer) self.popup = NormalPopup(title="Rename Folder", content=content, size_hint=(1, None), size=(1000, self.button_scale * 5)) self.popup.bind(on_dismiss=self.dismiss_popup) self.popup.open() def rename_folder_answer(self, instance, answer): new_name = instance.ids["input"].text.strip(' ') self.dismiss_popup() if not new_name: return if answer == 'yes': old_path = os.path.join(self.preset_element, self.preset_second_element) new_path = os.path.join(self.preset_element, new_name) if old_path != new_path: try: os.rename(old_path, new_path) if self.edit_section == old_path: self.edit_path = os.path.join( new_path, self.edit_name_original) + '.txt' self.edit_section = new_path self.load_clipboards(edit=True) except Exception as e: app.message_popup(text="Unable to rename folder: " + str(e), title="Warning") def save_edit(self): if not self.edit_path or not self.edit_name or not self.edit_section: return new_file = os.path.join(self.edit_section, self.edit_name) + '.txt' if new_file != self.edit_path: #rename file try: os.rename(self.edit_path, new_file) self.edit_path = new_file except Exception as e: app.message_popup(text="Unable to save edit: " + str(e), title="Warning") return try: file = open(self.edit_path, "w") file.write(self.edit_content) file.close() self.load_clipboards(edit=True) except Exception as e: app.message_popup(text="Unable to write to file: " + str(e), title="Warning") def clear_edit(self, *_): self.edit_content = '' self.edit_name = '' self.edit_name_original = '' self.edit_path = '' self.edit_section = '' def scroll_presets_to(self, section, item): scroll_to_index = None preset_area = self.root.ids['presets'] for index, data in enumerate(preset_area.data): if not item: if data['viewtype'] == 'heading' and data['section'] == section: scroll_to_index = index + 2 break else: if data['section'] == section and data[ 'viewtype'] == 'item' and data['path'] == item: scroll_to_index = index break if scroll_to_index is not None: preset_area.scroll_to_index(scroll_to_index) def instant_add(self, path, section): self.dismiss_popup() if not self.modify_mode: self.settings_mode() self.root.ids.editArea.current = 'edit' self.load_clipboards(edit=True) self.edit_type = 'item' self.edit_path = path self.edit_name = '' self.edit_name_original = '' self.edit_section = section self.edit_content = self.current_clipboard self.scroll_presets_to(section, '') content = InstantAddPresetContent() content.bind(on_answer=self.instant_add_preset_answer) self.popup = NormalPopup(title='Create File', content=content, size_hint=(1, 1), size=(1000, 2000)) self.popup.bind(on_dismiss=self.dismiss_popup) self.popup.bind(on_dismiss=self.clear_edit) self.popup.open() def instant_add_preset_answer(self, instance, answer): self.dismiss_popup() if answer == 'yes': filename = os.path.join(self.edit_path, self.edit_name) + '.txt' if os.path.exists(filename): self.message_popup(text="File already exists!", title="Warning") self.clear_edit() return file = open(filename, 'w') file.write(self.edit_content) file.close() self.load_clipboards() self.clear_edit() def add_preset(self, preset_type, preset_location): self.preset_type = preset_type self.preset_element = preset_location if preset_type == 'heading': input_text = "Add a folder with the name:" hint_text = "Folder Name" title_text = "Create Folder" else: input_text = "Add a preset with the name:" hint_text = "Preset Name" title_text = "Create File" self.dismiss_popup() content = InputPopupContent(text=input_text, hint=hint_text) content.bind(on_answer=self.add_preset_answer) self.popup = NormalPopup(title=title_text, content=content, size_hint=(1, None), size=(1000, self.button_scale * 5)) self.popup.bind(on_dismiss=self.dismiss_popup) self.popup.open() def add_preset_answer(self, instance, answer): preset_name = instance.ids['input'].text.strip(' ') self.dismiss_popup() if not preset_name: return if answer == 'yes': preset_type = self.preset_type preset_location = self.preset_element if preset_type == 'heading': path = os.path.join(preset_location, preset_name) try: os.makedirs(path) except Exception as e: app.message_popup(text="Unable to create folder: " + str(e), title="Warning") self.load_clipboards(edit=True) else: filename = os.path.join(preset_location, preset_name) + '.txt' if not os.path.isfile(filename): try: file = open(filename, 'w+') file.close() self.edit_type = preset_type self.edit_path = filename self.edit_name = preset_name self.edit_name_original = preset_name self.edit_section = preset_location self.edit_content = '' self.root.ids.editContentArea.focus = True except Exception as e: app.message_popup(text="Unable to create file: " + str(e), title="Warning") self.load_clipboards(edit=True) else: app.message_popup(text="File already exists!", title="Warning") def load_clipboard_folders(self): folders = [self.clipboard_folder] folder_items = self.config.items("PresetsPaths") for key, folder in folder_items: if folder not in folders: folders.append(folder) folders_data = [] for folder in folders: if folder: folders_data.append({'path': folder}) self.clipboard_folders = folders_data def save_clipboard_folders(self): self.config.remove_section("PresetsPaths") self.config.add_section("PresetsPaths") for index, folder_item in enumerate(self.clipboard_folders): folder = folder_item['path'] self.config.set("PresetsPaths", str(index), folder) def remove_presets_folder(self, path): for index, preset in enumerate(self.clipboard_folders): if preset['path'] == path: self.clipboard_folders.pop(index) self.save_clipboard_folders() def set_presets_folder(self, path): self.save_hiddens() self.clipboard_folder = path self.config.set("Settings", "presetsfolder", path) self.load_clipboards() def add_presets_folder(self, *_): self.not_on_top() from plyer import filechooser try: filechooser.choose_dir(on_selection=self.add_presets_folder_finish, title='Select A Presets Directory') except: #Filechooser can fail if user selects root if self.config.getboolean("Settings", "alwaysontop"): self.always_on_top() pass def add_presets_folder_finish(self, path): if self.config.getboolean("Settings", "alwaysontop"): self.always_on_top() path = path[0] if path: if path not in self.clipboard_folders: self.clipboard_folders.append({'path': path}) self.save_clipboard_folders() self.set_presets_folder(path) else: self.message("Path Already Added") else: self.message("No Path Found") def browse_presets(self): try: import webbrowser webbrowser.open(self.clipboard_folder) except: pass def size_window(self, *_): Window.left = self.window_left Window.top = self.window_top height = self.window_height width = int(self.button_scale * 4 * (float(self.config.get("Settings", "normalwidth")) / 100)) if width < self.window_min_width: width = self.window_min_width if self.modify_mode: self.window_width_target = width width = int( self.button_scale * 17 * (float(self.config.get("Settings", "expandedwidth")) / 100)) Window.size = width, height else: Window.size = width, height self.window_width_target = Window.width def on_button_scale(self, *_): self.size_window() def save_hiddens(self): if not self.clipboard_folder: return hiddens = [] for data in self.clipboard_data: if data['hidden'] and data['viewtype'] == 'heading': section = data['section'] if section not in hiddens: hiddens.append(section) hiddens_filename = os.path.join(self.clipboard_folder, 'hiddens.txt') try: hiddens_file = open(hiddens_filename, 'w') except: return for hidden in hiddens: hiddens_file.write(hidden + '\n') hiddens_file.close() def load_hiddens(self): hiddens = [] hiddens_filename = os.path.join(self.clipboard_folder, 'hiddens.txt') try: hiddens_file = open(hiddens_filename, 'r') except: self.hiddens = [] return lines = hiddens_file.readlines() hiddens_file.close() for line in lines: line = line.strip('\n') if line: hiddens.append(line) self.hiddens = hiddens def build_config(self, config): """Setup config file if it is not found""" window_height, window_width, window_top, window_left = self.get_window_size( ) config.setdefaults( 'Settings', { 'buttonsize': 100, 'textsize': 100, 'expandedwidth': 100, 'normalwidth': 100, 'alwaysontop': 1, 'minimizestart': 0, 'minimizetray': 0, 'showtray': 1, 'auto_shrink': 0, 'auto_expand': 0, 'no_duplicates': 0, 'showundo': 1, 'showstrip': 0, 'presetsfolder': '', 'max_history': 20, 'main_history_length': 1, 'window_height': window_height, 'window_top': window_top, 'window_left': window_left, 'window_width': window_width }) config.setdefaults('PresetsPaths', {}) def build_settings(self, settings): """Kivy settings dialog panel settings types: title, bool, numeric, options, string, path""" settingspanel = [] settingspanel.append({ "type": "label", "title": " Basic Behavior" }) settingspanel.append({ "type": "numeric", "title": "Max Clipboard History", "desc": "Number of entries to keep in clipboard history. Defaluts to 20.", "section": "Settings", "key": "max_history" }) settingspanel.append({ "type": "bool", "title": "Auto-Shrink Window", "desc": "Automatically shrink the window when the mouse leaves.", "section": "Settings", "key": "auto_shrink" }) settingspanel.append({ "type": "bool", "title": "Auto-Expand Window", "desc": "Automatically expand the window when the mouse enters.", "section": "Settings", "key": "auto_expand" }) settingspanel.append({ "type": "bool", "title": "Minimize On Startup", "desc": "Automatically minimize the program as soon as it starts.", "section": "Settings", "key": "minimizestart" }) if platform == 'win': settingspanel.append({ "type": "bool", "title": "Always-On-Top", "desc": "Sets the window to always-on-top mode on startup. Changing requires a program restart.", "section": "Settings", "key": "alwaysontop" }) settingspanel.append({ "type": "bool", "title": "Show Tray Icon", "desc": "A tray icon will be shown with options. Changing requires program restart.", "section": "Settings", "key": "showtray" }) settingspanel.append({ "type": "bool", "title": "Minimize To Tray", "desc": "Minimize to tray when minimizing the window. Only available when tray icon is enabled.", "section": "Settings", "key": "minimizetray" }) settingspanel.append({ "type": "bool", "title": "Disallow Duplicates", "desc": "Prevent duplicates from being added to clipboard history, removes the previous item when a new duplicate is added.", "section": "Settings", "key": "no_duplicates" }) settingspanel.append({ "type": "label", "title": " Main Window Appearance" }) settingspanel.append({ "type": "bool", "title": "Show Undo Clipboard Button", "desc": "Shows a button to undo the clipboard to the previous state.", "section": "Settings", "key": "showundo" }) settingspanel.append({ "type": "bool", "title": "Show Strip Clipboard Button", "desc": "Shows a button to remove all formatting and extra spaces from clipboard text.", "section": "Settings", "key": "showstrip" }) settingspanel.append({ "type": "numeric", "title": "History In Main Window", "desc": "Number of history elements to show in main window. Defaults to 1.", "section": "Settings", "key": "main_history_length" }) settingspanel.append({ "type": "label", "title": " Interface Scaling" }) settingspanel.append({ "type": "numeric", "title": "Shrunk Width Scale", "desc": "Scale of the window when it is in normal mode. Expects a percentage, defaults to 100", "section": "Settings", "key": "normalwidth" }) settingspanel.append({ "type": "numeric", "title": "Expanded Width Scale", "desc": "Scale of the window when it is in expanded mode. Expects a percentage, defaults to 100", "section": "Settings", "key": "expandedwidth" }) settingspanel.append({ "type": "numeric", "title": "Button Scale", "desc": "Button scale, expects a percentage, defaults to 100.", "section": "Settings", "key": "buttonsize" }) settingspanel.append({ "type": "numeric", "title": "Text Scale", "desc": "Font scale, expects a percentage, defaults to 100.", "section": "Settings", "key": "textsize" }) settings.add_json_panel('Settings', self.config, data=json.dumps(settingspanel)) def build(self): global app app = self if platform == 'win' and self.config.getboolean( "Settings", "showtray"): #setup system tray from infi.systray import SysTrayIcon tray_menu = ( ("Toggle Minimize", None, self.tray_click), ("Undo Clipboard", None, self.undo_clipboard), ("Strip Clipboard", None, self.strip_clipboard), ) icon_file = kivy.resources.resource_find('icon.ico') self.tray = SysTrayIcon(icon_file, 'Snu Clipboard Manager', tray_menu, on_quit=self.tray_quit) self.tray.start() else: self.tray = None self.clipboard_folder = self.config.get("Settings", "presetsfolder") self.load_clipboard_folders() self.load_clipboards() Window.bind(on_key_down=self.key_down) Window.bind(on_key_up=self.key_up) Window.bind(on_cursor_leave=self.window_exited) Window.bind(on_cursor_enter=self.window_entered) Window.bind(on_minimize=self.on_minimize) Window.bind(on_restore=self.on_restore) return MainScreen() def tray_quit(self, tray): Clock.schedule_once(self.stop) def tray_click(self, tray): if self.window_minimized: Clock.schedule_once(self.restore) else: Clock.schedule_once(self.minimize) def restore(self, *_): Window.restore() def minimize(self, *_): Window.minimize() def on_restore(self, window): self.window_minimized = False Window.show() def on_minimize(self, window): self.window_minimized = True if self.config.getboolean("Settings", "minimizetray") and self.tray is not None: Window.hide() def window_exited(self, window): self.window_leave_timeout = Clock.schedule_once( self.window_exited_finish, 1) def window_entered(self, window): self.window_entered_finish() if self.window_leave_timeout is not None: self.window_leave_timeout.cancel() self.window_leave_timeout = None def window_exited_finish(self, *_): if self.config.getboolean("Settings", 'auto_shrink'): if self.modify_mode and self.popup is None and self.root.ids.editArea.current != 'edit': self.settings_mode() def window_entered_finish(self, *_): if self.config.getboolean("Settings", 'auto_expand'): if not self.modify_mode: self.settings_mode() def on_start(self): EventLoop.window.bind(on_keyboard=self.hook_keyboard) self.load_config_values() Window.set_title(self.window_title) if self.config.getboolean("Settings", "alwaysontop"): self.always_on_top() self.load_window_size() self.clipboard_updater = Clock.schedule_interval( self.update_current_clipboard, 0.1) if not self.clipboard_folder: self.root.ids.editArea.current = 'select' self.modify_mode = True Clock.schedule_once(self.add_presets_folder) else: self.modify_mode = False self.size_window() if self.config.getboolean("Settings", "minimizestart"): self.minimize() def always_on_top(self, *_): #Set on top mode self.on_top = True from KivyOnTop import register_topmost register_topmost(Window, self.window_title) def not_on_top(self, *_): self.on_top = False from KivyOnTop import unregister_topmost unregister_topmost(Window, self.window_title) def toggle_on_top(self): if self.on_top: self.not_on_top() else: self.always_on_top() def get_window_size(self): #Get window size if platform == 'win': SPI_GETWORKAREA = 48 SM_CXSCREEN = 0 SM_CYSCREEN = 1 #The height of the screen of the primary display monitor, in pixels. SM_CYCAPTION = 4 #The height of a caption area, in pixels. SM_CYBORDER = 6 #The height of a window border, in pixels. SM_CYFIXEDFRAME = 8 #The thickness of the frame around the perimeter of a window that has a caption but is not sizable, in pixels. from win32api import GetSystemMetrics taskbar_height = GetSystemMetrics(SPI_GETWORKAREA) screen_height = GetSystemMetrics(SM_CYSCREEN) screen_width = GetSystemMetrics(SM_CXSCREEN) window_frame_size = GetSystemMetrics( SM_CYFIXEDFRAME) + GetSystemMetrics(SM_CYBORDER) window_left_offset = window_frame_size window_top_offset = GetSystemMetrics( SM_CYCAPTION) + window_frame_size window_height = screen_height - window_top_offset - window_left_offset - taskbar_height window_width = screen_width - window_left_offset - window_left_offset return window_height, window_width, window_top_offset, window_left_offset else: return 680, self.window_min_width, 30, 5 def on_pause(self): self.save_hiddens() self.config.write() return True def on_stop(self): if not (self.modify_mode and self.root.ids.editArea.current == 'edit'): self.save_hiddens() self.config.write() if self.tray is not None: self.tray.shutdown() def toggle_area(self, area): self.close_settings() if not self.modify_mode: self.root.ids.editArea.current = area self.modify_mode = True if area == 'edit': self.load_clipboards(edit=True) self.size_window() else: if self.root.ids.editArea.current == area: self.modify_mode = False self.load_clipboards() self.size_window() else: self.root.ids.editArea.current = area if area == 'edit': self.load_clipboards(edit=True) def text_input_active(self): """Checks if any 'NormalInput' or 'FloatInput' widgets are currently active (being typed in). Returns: True or False """ input_active = False for widget in self.root.walk(restrict=True): if widget.__class__.__name__ == 'NormalInput' or widget.__class__.__name__ == 'FloatInput' or widget.__class__.__name__ == 'IntegerInput': if widget.focus: input_active = True break return input_active def check_for_active_input(self, root): #Walks widget tree and checks for an active multiline text input, returns that input if found, otherwise returns None has_input = None for widget in root.walk(restrict=True): if isinstance(widget, TextInput): if widget.focus: return widget return has_input def hook_keyboard(self, window, scancode, *_): """This function receives keyboard events""" #print(scancode) if scancode == 282: #f1 key if not self.modify_mode: self.settings_mode() if scancode == 27: #escape key if self.popup: self.popup.content.dispatch('on_answer', 'no') return True if scancode == 13: #enter key if self.popup: has_input = self.check_for_active_input(self.popup) if has_input is not None: self.popup.content.dispatch('on_answer', 'yes') if scancode == 96: #tilde key if self.alt_pressed or self.ctrl_pressed or not self.text_input_active( ): self.settings_mode() if scancode == 283: #f2 key self.toggle_area('history') if scancode == 284: #f3 key self.toggle_area('edit') if scancode == 285: #f4 key self.toggle_area('select') if scancode == 97: #a key if self.alt_pressed: self.toggle_on_top() if scancode == 109: #m key if self.alt_pressed: self.minimize() def key_down(self, key, scancode=None, *_): """Intercepts various key presses and sends commands to the current screen.""" del key if scancode == 307 or scancode == 308: #alt keys self.alt_pressed = True if scancode == 305 or scancode == 306: #ctrl keys self.ctrl_pressed = True if scancode == 303 or scancode == 304: #shift keys self.shift_pressed = True def key_up(self, key, scancode=None, *_): """Checks for the shift key released.""" del key if scancode == 307 or scancode == 308: self.alt_pressed = False if scancode == 305 or scancode == 306: self.ctrl_pressed = False if scancode == 303 or scancode == 304: self.shift_pressed = False
def trayer(ops, mode, n): # try: def notify(a="0", title='Now Runnng at', tail=" mode"): def work(): n.send((title, a+tail)) go(work) def noti(a): if mode[3] == 0: try: notify(a) except: pass def caller(a): def calling(sysTrayIcon, a=a): if 0 <= a < len(ops): mode[0] = a if mode[1] == -1: mode[1] = 0 if a < 2 and mode[2] == 1: mode[2] = 0 noti("OCR only") else: if mode[2] != 1: noti(ops[a]) elif a == len(ops): if mode[2] == 1: noti("Translate only") mode[2] = 0 elif mode[2] == 0: noti("OCR&Translate") mode[2] = 1 elif a == len(ops) + 1: if mode[1] != -1: mode[1] = -1 noti("Listening Paused") else: mode[1] = 0 noti("Listening Recoverd") elif a == len(ops) + 2: mode[3] = (mode[3] + 1) % 2 elif a == -1: mode[1] = 1 n.send(("Info", "Exiting")) #print(mode[0], mode[1], mode[2], mode[3]) return lambda sysTrayIcon: go(calling, args=(sysTrayIcon,)) menu = () trans = () for i, ix in enumerate(ops): now = (ix, None, caller(i)) if i <= 1: menu += (now,) else: trans += (now,) menu += (("Translate", None, trans,), ("OCR&Translate", None, caller(i + 1)), ("Pause", None, caller(i + 2)), ("Disable Notify", None, caller(i + 3))) hover_text = "PowerClipboard" tray = SysTrayIcon("main.ico", hover_text, menu_options=menu, on_quit=caller(-1), default_menu_index=1) # noti("!!!!") tray.start() return tray
def notify_tray(): menu_options = (("Open GUI", None, vp_start_gui()),) systray = SysTrayIcon("icon/icon.ico", "Treye", menu_options) systray.start()
idle_icon = current_dir + 'check-circle.ico' idle_text = 'rsync is not running' syncing_icon = current_dir + 'arrow-circle-up.ico' syncing_text = 'rsync is running' # (delays are in seconds) check_delay = 5 start_delay = 1 ############## def rsync_checker(systray_icon): while True: cmd_status, cmd_output = getstatusoutput('tasklist | find /c /i "rsync"') if cmd_output == '0': systray_icon.update(icon=idle_icon, hover_text=idle_text) else: systray_icon.update(icon=syncing_icon, hover_text=syncing_text) time.sleep(check_delay) systray_icon = SysTrayIcon(idle_icon, idle_text) systray_icon.start() rsync_checker_thread = threading.Timer( start_delay, rsync_checker, args=[systray_icon]) rsync_checker_thread.daemon = True rsync_checker_thread.start()
class WindowsSystemTray(object): def __init__(self): self.image_dir = os.path.join(plexpy.PROG_DIR, 'data/interfaces/', plexpy.CONFIG.INTERFACE, 'images') self.icon = os.path.join(self.image_dir, 'logo-circle.ico') if plexpy.UPDATE_AVAILABLE: self.hover_text = common.PRODUCT + ' - Update Available!' self.update_title = 'Check for Updates - Update Available!' else: self.hover_text = common.PRODUCT self.update_title = 'Check for Updates' if plexpy.CONFIG.LAUNCH_STARTUP: launch_start_icon = os.path.join(self.image_dir, 'check-solid.ico') else: launch_start_icon = None if plexpy.CONFIG.LAUNCH_BROWSER: launch_browser_icon = os.path.join(self.image_dir, 'check-solid.ico') else: launch_browser_icon = None self.menu = [['Open Tautulli', None, self.tray_open, 'default'], ['', None, 'separator', None], [ 'Start Tautulli at Login', launch_start_icon, self.tray_startup, None ], [ 'Open Browser when Tautulli Starts', launch_browser_icon, self.tray_browser, None ], ['', None, 'separator', None], [self.update_title, None, self.tray_check_update, None], ['Restart', None, self.tray_restart, None]] if not plexpy.FROZEN: self.menu.insert(6, ['Update', None, self.tray_update, None]) self.tray_icon = SysTrayIcon(self.icon, self.hover_text, self.menu, on_quit=self.tray_quit) def start(self): logger.info("Launching Windows system tray icon.") try: self.tray_icon.start() except Exception as e: logger.error("Unable to launch system tray icon: %s." % e) def shutdown(self): self.tray_icon.shutdown() def update(self, **kwargs): self.tray_icon.update(**kwargs) def tray_open(self, tray_icon): plexpy.launch_browser(plexpy.CONFIG.HTTP_HOST, plexpy.HTTP_PORT, plexpy.HTTP_ROOT) def tray_startup(self, tray_icon): plexpy.CONFIG.LAUNCH_STARTUP = not plexpy.CONFIG.LAUNCH_STARTUP set_startup() def tray_browser(self, tray_icon): plexpy.CONFIG.LAUNCH_BROWSER = not plexpy.CONFIG.LAUNCH_BROWSER set_startup() def tray_check_update(self, tray_icon): versioncheck.check_update() def tray_update(self, tray_icon): if plexpy.UPDATE_AVAILABLE: plexpy.SIGNAL = 'update' else: self.hover_text = common.PRODUCT + ' - No Update Available' self.update_title = 'Check for Updates - No Update Available' self.menu[5][0] = self.update_title self.update(hover_text=self.hover_text, menu_options=self.menu) def tray_restart(self, tray_icon): plexpy.SIGNAL = 'restart' def tray_quit(self, tray_icon): plexpy.SIGNAL = 'shutdown' def change_tray_update_icon(self): if plexpy.UPDATE_AVAILABLE: self.hover_text = common.PRODUCT + ' - Update Available!' self.update_title = 'Check for Updates - Update Available!' else: self.hover_text = common.PRODUCT + ' - No Update Available' self.update_title = 'Check for Updates' self.menu[5][0] = self.update_title self.update(hover_text=self.hover_text, menu_options=self.menu) def change_tray_icons(self): if plexpy.CONFIG.LAUNCH_STARTUP: launch_start_icon = os.path.join(self.image_dir, 'check-solid.ico') else: launch_start_icon = None if plexpy.CONFIG.LAUNCH_BROWSER: launch_browser_icon = os.path.join(self.image_dir, 'check-solid.ico') else: launch_browser_icon = None self.menu[2][1] = launch_start_icon self.menu[3][1] = launch_browser_icon self.update(menu_options=self.menu)
class TaskTray(): def __init__(self, wallcord): global credit_text self.window = wallcord self.root = self.window.root self.version = self.window.version with open("data.json", "r") as f: self.data = load(f) credit_text = credit_text.replace("!version!", self.version) if self.version in ["1.1.1", "1.1.0", "1.0.0"]: messagebox.showwarning( "FreedomWall", "バージョンとFreedomWallのプログラムがあいませんでした。\n再ダウンロードしてください。") self.window.onoff = False self.window.q.append(self.exit) # メインスレッドじゃないとTkinterメソッドを実行できない。 # だからメインスレッドのFreedomWallクラスのself.qに実行したいのを追加する。 # そしてメインスレッドから実行する。 # だから lambda がある。 self.icon = SysTrayIcon( "icon.ico", "FreedomWall", (("FreedomWall", None, lambda sysTrayIcon: self.window.q.append(self.credit)), ("Set", None, lambda sysTrayIcon: self.window.q.append(self.setting)), ("Del", None, lambda sysTrayIcon: self.window.q.append(self.delete)), ("List", None, lambda sysTrayIcon: self.window.q.append(self.setting_list))), on_quit=lambda sysTrayIcon: self.window.q.append(self.exit)) # 壁紙の設定。 def setting(self): # simpledialog.askstringで入力ボックスを表示する。 # それを使い設定をする。 title = simpledialog.askstring("FreedomWall", "設定したいウィンドウのタイトルにある文字を入力してください。") if not title: return alpha = simpledialog.askstring( "FreedomWall", "壁紙の透明度を入力してください。\nデフォルトは0.2です。\n元の背景が白の場合は0.3あたりの数値が良いです。\n元の背景が黒の場合は0.1あたりの数値が良いです。" ) if not alpha: return try: alpha = float(alpha) except: messagebox.showwarning("FreedomWall", "0.1 ~ 1 の間を設定してください。") return exception = simpledialog.askstring( "FreedomWall", "例外ウィンドウのタイトルにある文字を入力してください。\nコンマ( , )を使うことで複数追加できます。\n\n例えばLINEを登録したあとにLINEについてのウェブサイトを閲覧したとします。\nするとブラウザのタイトルにLINEが入り被りが発生します。\nそれを防ぐためにブラウザのタイトルを入れることをおすすめします。" ) exception = exception.split(",") if exception else [] # 壁紙ファイルを取得する。 file_path = filedialog.askopenfilename( filetypes=[("壁紙ファイル", "*.png;*.jpg;*.mp4")]) if not file_path: return # 存在確認をする。 if not GetWindow(title, "bubun"): messagebox.showwarning("FreedomWall", f"{title}があるタイトルのウィンドウが見つかりませんでした。") return self.data["windows"][title] = { "path": file_path, "alpha": alpha, "exception": exception } with open("data.json", "w") as f: dump(self.data, f, indent=4) # メインスレッドのdataを再読み込みさせる。 self.window.reload() self.window.now = {"path": "", "alpha": 0, "exception": []} messagebox.showinfo("FreedomWall", "設定しました。") # 壁紙の削除。 def delete(self): title = simpledialog.askstring("FreedomWall", "削除したい設定名を入力してください。") if not title: return if not title in self.data["windows"]: messagebox.showwarning("FreedomWall", "その設定が見つかりませんでした。") return del self.data["windows"][title] with open("data.json", "w") as f: dump(self.data, f, indent=4) # メインスレッドのdataを再読み込みさせる。 self.window.reload() self.window.now = {"path": "", "alpha": 0, "exception": []} messagebox.showinfo("FreedomWall", "その設定を削除しました。") # 壁紙一覧。 def setting_list(self): desc = ", ".join(data for data in self.data["windows"].keys()) messagebox.showinfo("FreedomWall", desc) # クレジット。 def credit(self): messagebox.showinfo("FreedomWall", credit_text) # 終了。 def exit(self): self.root.quit() self.window.video = None self.icon.shutdown() # 実行。 def run(self): self.icon.start()
os.startfile("CryptExcel.config") def trading_sheet(systray): os.startfile("trading.xlsx") def open_folder(systray): os.startfile(os.getcwd()) options_menu = (("Open folder", None, open_folder), ("Config file", None, config_file), ("Trading sheet", None, trading_sheet), ("Close", None, left)) tray = SysTrayIcon("assets/icon.ico", "CryptExcel", options_menu) tray.start() #Other warnings.filterwarnings("ignore", category=DeprecationWarning) run = True def modify(): #Binance API client = Client(api_key, secret_key) balances = client.get_account().get('balances') prices = client.get_all_tickers() trades = [] for pair in pairs: trade = (client.get_all_orders(symbol=pair, limit=1000)) for order in trade:
""" assistantCommands.hide_or_show_input(True) def quit_(systray): """ Função para fechar o programa. """ assistant.stop() # Define o menu do ícone de bandeja. menu = ( ("Change Language",None,changeLanguage), ("Enable Sounds",None,enable_sounds), ("Disable Sounds",None,disable_sounds), ("Hide Input",None,hideInput), ("Show Input",None,showInput), ("Help",None,help_) ) # Cria um ícone de bandeja. trayIcon = SysTrayIcon(icon,trayIcon_title+" (%s)"%assistant.getLanguage().upper(),menu,on_quit=quit_) trayIcon.start() # Inicia o assistente. assistant.run() # Fecha o programa por completo. time.sleep(1) os._exit(0)
volume_after = AVR.volume print("Volume turned down from %i to %i." % (volume_before + DB_OFFSET, volume_after + DB_OFFSET)) return tray # initialize connection AVR = denonavr.DenonAVR(RECEIVER_ADDRESS) # create source selection menu options SOURCE_SELECTORS = [] for source in AVR.input_func_list: source_short = "".join(x for x in source if x.isalpha()) exec("def select_%s(systray): AVR.input_func = '%s'" % (source_short, source)) SOURCE_SELECTORS += [("Select %s" % source, None, eval("select_%s" % source_short))] # build up full set of tray icon menu options MENU_OPTIONS = [ ("Power On", None, power_on), ("Power Off", None, power_off), ("Volume Up", None, volume_up), ("Volume Down", None, volume_down), ] + SOURCE_SELECTORS # create tray icon and start main loop SYSTRAY = SysTrayIcon(os.path.join(WORK_DIR, "denon.ico"), "denontray", tuple(MENU_OPTIONS)) SYSTRAY.start()
class PopUpWindow(object): windowCount = 0 counter = -1 def counterLabel(self, label): self.resetBtn['state'] = 'normal' self.stopBtn['state'] = 'normal' def count(): if running: # To manager the intial delay if PopUpWindow.counter == -1: display = "Starting..." else: display = str( datetime.timedelta(seconds=PopUpWindow.counter)) label.config(text=display) label.after(1000, count) PopUpWindow.counter += 1 # Triggering the start of the counter. count() # Start the function of the stopwatch def Start(self): global running running = True self.counterLabel(self.timerLbl) self.breakBtn['state'] = 'disabled' self.stopBtn['state'] = 'normal' self.resetBtn['state'] = 'normal' # Stop function of the stopwatch def Stop(self): global running running = False self.breakBtn['state'] = 'normal' self.stopBtn['state'] = 'disabled' self.resetBtn['state'] = 'normal' # Reset function of the stopwatch def Reset(self): PopUpWindow.counter = -1 # If reset is pressed after pressing stop if running == False: self.resetBtn['state'] = 'disabled' self.timerLbl['text'] = 'Welcome!' # If reset is pressed while hte stopwatch is running else: self.timerLbl['text'] = 'Starting ...' def getStatus(): return programContinue def exitApp(self): Flag.setFlag(1) self.systray.shutdown() self.window.destroy() def closeWindow(self): PopUpWindow.windowCount = PopUpWindow.windowCount + 1 self.window.destroy() def increasePopUpCount(self): global popUpCount popUpCount = popUpCount + 1 self.popUpTimer.config(text=str(popUpCount) + " Minutes") def decreasePopUpCount(self): global popUpCount popUpCount = popUpCount - 1 self.popUpTimer.config(text=str(popUpCount) + " Minutes") def getPopUpCount(): global popUpCount return popUpCount def say_hello(systray): print("Hello") def __init__(self, time): # Create a blank window self.window = Tk() self.window.title("Break Timer") # Create a label widget lbl = Label(self.window, text="The time is", font=("Arial Bold", 20)) lbl.grid(column=0, row=0) # Create a label to display the current time self.timeLbl = Label(self.window, text=time, font=("Arial Bold", 20)) self.timeLbl.grid(column=0, row=1) # Create a timer label if (PopUpWindow.counter == -1): self.timerLbl = Label(self.window, text="00:00", font=("Arial Bold", 20)) else: self.timerLbl = Label( self.window, text=str(datetime.timedelta(seconds=PopUpWindow.counter)), font=("Arial Bold", 20)) self.timerLbl.grid(column=1, row=1) # Create a button to take a break self.breakBtn = Button(self.window, text="Take a break", bg="grey80", fg="black", command=lambda: self.Start(), font=("Arial", 20)) self.breakBtn.grid(column=0, row=2) # Create a reset timer button self.resetBtn = Button(self.window, text="Reset timer", bg="grey80", fg="black", state="disabled", command=lambda: self.Reset(), font=("Arial", 20)) self.resetBtn.grid(column=1, row=2) # Create a stop timer button self.stopBtn = Button(self.window, text="Stop timer", bg="grey80", fg="black", state="disabled", command=lambda: self.Stop(), font=("Arial", 20)) self.stopBtn.grid(column=2, row=2) separator = Frame(height=30, bd=1, relief=SUNKEN) separator.grid(column=0, row=3) # Create a close button closeBtn = Button(self.window, text="Close Window", bg="grey80", fg="black", command=lambda: self.closeWindow(), font=("Arial", 20)) closeBtn.grid(column=0, row=4) separator2 = Frame(height=30, bd=1, relief=SUNKEN) separator2.grid(column=0, row=5) # Create a exit button exitBtn = Button(self.window, text="Exit App", bg="grey80", fg="black", command=lambda: self.exitApp(), font=("Arial", 20)) exitBtn.grid(column=0, row=6) # Create a window count label counterLbl = Label(self.window, text="Pop up count: " + str(PopUpWindow.windowCount), font=("Arial Bold", 20)) counterLbl.grid(column=1, row=0) # Create a label to display the amount of time until the pop-up indow pop ups self.popUpTimer = Label(self.window, text=str(popUpCount) + " Minutes", font=("Arial Bold", 20)) self.popUpTimer.grid(column=2, row=5) self.popUpTimerUpButton = Button( self.window, text="+1", bg="grey80", fg="black", command=lambda: self.increasePopUpCount(), font=("Arial Bold", 20), width=3) self.popUpTimerUpButton.grid(column=2, row=4) self.popUpTimerDownButton = Button( self.window, text="-1", bg="grey80", fg="black", command=lambda: self.decreasePopUpCount(), font=("Arial Bold", 20), width=3) self.popUpTimerDownButton.grid(column=2, row=6) # set the window size self.window.geometry('600x350') # Create a message box to display the current time #messagebox.showinfo("Current Time", time) # Bring the Window in front of every other window self.window.attributes("-topmost", True) # Create a system tray icon menu_options = (("Reset Timer", None, self.Reset), ) self.systray = SysTrayIcon("icon.ico", "Example tray icon", menu_options) self.systray.start() # Start the window self.window.mainloop()
# Place here the code to close the reg object that reads the registry wr.CloseKey(openkey) print("Program finished") finally: # Place here the code to close the reg object that reads the registry wr.CloseKey(openkey) print("Logic thread finished") # this is the main thread if __name__ == "__main__": menu_options = ( ("Options...", None, call_opts_tray), ("About AutoTheme-19", None, call_about_tray), ) icon = SysTrayIcon(resource_path("icons\\16\\B16sun.ico"), "AutoTheme-19", menu_options, on_quit=make_me_stop) # Start the logic thread after defining the icon, because the logic thread needs to update the icon. mylogic = threading.Thread(target=logic_thread) mylogic.start() # now we start the tray icon icon.start()
def main(): sessions = AudioUtilities.GetAllSessions mixer = Mixer() # SysTrayIcon menu items menu_options = () # SysTrayIcon object systray = SysTrayIcon(None, "NK2Mixer", menu_options, on_quit=partial(exit_program, mixer)) # Start SysTray systray.start() # Map physical faders to Voicemeeter faders - mappings subject to personal preference with voicemeeter.remote('banana', 0.0005) as vmr: mixer.vb_map = { 4: vmr.inputs[3], 5: vmr.inputs[4], 6: vmr.inputs[1], 7: vmr.outputs[0] } for session in sessions(): if session.Process: print(session.Process.name) while mixer.running: # Receive midi message (non-blocking) msg = mixer.nk2_in.poll() # If no message exists, end current loop if not msg: continue # Check for select button press if msg.control in mixer.select_range and msg.value: group = mixer.groups[msg.control - mixer.select_fader_diff] # If program is not bound bound if not group.program: # Get active program name active_program = get_active_program() # Find audio session with matching name session = next( (s for s in sessions() if s.Process and s.Process.name() == active_program), None) # If audio session does not exist end current loop if not session: continue # Assign session to control group group.program = session # Turn on select button light mixer.enable_light(group.select) # If program is muted turn on mute button light if group.program.SimpleAudioVolume.GetMute(): mixer.enable_light(group.mute) print( f"{group.program.Process.name()} bound to fader {group.fader}" ) else: print( f"{group.program.Process.name()} unbound from fader {group.fader}" ) # Unassign session from fader group.program = None # Turn off select button light mixer.disable_light(group.select) # Turn off mute button light mixer.disable_light(group.mute) # Check for mute button press elif msg.control in mixer.mute_range and msg.value and mixer.groups[ msg.control - mixer.mute_fader_diff].program: group = mixer.groups[msg.control - mixer.mute_fader_diff] # Check if program is muted if group.program.SimpleAudioVolume.GetMute(): # Unmute program group.program.SimpleAudioVolume.SetMute(0, None) # Turn off mute button light mixer.disable_light(group.mute) print( f"{group.program.Process.name()} unmuted (fader {group.fader})" ) # If program is not muted else: # Mute program group.program.SimpleAudioVolume.SetMute(1, None) # Turn on mute button light mixer.enable_light(group.mute) print( f"{group.program.Process.name()} muted (fader {group.fader})" ) # Check for fader input elif msg.control in mixer.fader_range: group = mixer.groups[msg.control] # If fader does not have assigned program end current loop if not group.program: continue # Get volume control object from session volume = group.program._ctl.QueryInterface(ISimpleAudioVolume) # Convert midi value to percentage and set volume volume.SetMasterVolume(msg.value / 127, None) print( f"{group.program.Process.name()} set to {volume.GetMasterVolume() * 100}%" ) # Check for Voicemeeter fader input elif msg.control in mixer.vb_fader_range: # Map midi value (0-127) to VB appropriate gain value (-60-0) level = ((127 - msg.value) / 127) * -60 # Set VB fader gain mixer.vb_map[msg.control].gain = level print(f"fader {msg.control} (VoiceMeeter) gain set to {level}") elif msg.control in mixer.vb_mute_range and msg.value: fader = msg.control - mixer.mute_fader_diff control = mixer.vb_map[fader] # ISSUE: inconsistent mute/unmute if control.mute: # Unmute VB control control.mute = False # Turn off mute button light mixer.disable_light(msg.control) print(f"fader {fader} (VoiceMeeter) unmuted") else: # Mute FB control control.mute = True # Turn on mute button light mixer.enable_light(msg.control) print(f"fader {fader} (VoiceMeeter) muted") # After input is processed delete message to prevent unnecessary looping msg = None
class SysTrayService: def __init__(self, mainService): logger.info('Run : ini') self.app = None self.aboutUI = None self.settingUI = None self.mainService = mainService def start(self): # TODO notification anpassen (kein ToolTip) logger.info('Run: iniSystray') menu_options = ( ("Settings", None, self.openSettings), ("Restart", None, self.restart), ("About", None, self.about), ) self.systray = SysTrayIcon(CHECK_ICO, STATUS_OK, menu_options, on_quit=self.on_quit_callback) logger.info('systray start') self.systray.start() def about(self, systray): # TODO About Anpassen self.iniAboutUI() aboutText = '<html><head/><body><p>Utility to get a notification to commit and/or push the repository</p><p><br/>Developed by Christian Beigelbeck \ </p><p>\ Licensed under the <a href="https://www.gnu.org/licenses/gpl-3.0-standalone.html"><span style=" text-decoration:\ underline; color:#2980b9;">GPL v3 license</span></a></p><p>Project home: \ <a href="https://overmindstudios.github.io/BlenderUpdater/"><span style=" text-decoration:\ underline; color:#2980b9;">https://www.github.io/VCSReminder/</a></p> \ Application version: ' + '0.0.1' + '</body></html> ' QtWidgets.QMessageBox.about(self.aboutUI, 'About', aboutText) def on_quit_callback(self, systray): logger.info('Run: on_quit_callback') self.mainService.stop() def updateSystrayInfo(self, ico, status): self.systray.update(ico, status) def restart(self, systray): logger.info('Run: restart') self.mainService.restart() def openSettings(self, systray): logger.info('Run: openSettings') self.iniSettingUI() logger.info('exec settingUI') self.settingUI.exec() self.app.exec_() def iniAboutUI(self): if self.app == None: logger.info('INI Application') self.app = QtWidgets.QApplication(sys.argv) logger.info('set Style') self.app.setStyle("Fusion") logger.info('set Palette') self.app.setPalette(setPalette()) if self.aboutUI == None: self.aboutUI = AboutUIService() def iniSettingUI(self): if self.app == None: logger.info('INI Application') self.app = QtWidgets.QApplication(sys.argv) logger.info('set Style') self.app.setStyle("Fusion") logger.info('set Palette') self.app.setPalette(setPalette()) if self.settingUI == None: self.settingUI = SettingsUIService(self.mainService)
class TrayIcon(Pipeable): """ Non-blocking tray icon. Tray icons require blocking due to having to wait for button clicks. To prevent blocking on the main thread, a tread is spawned for the tray icon and click events are send through `Pipeable` pipes. """ MenuOptionCallback = Callable[[SysTrayIcon], None] MenuOption = tuple[str, Optional[str], MenuOptionCallback] _thread: Thread _icon: SysTrayIcon _menu_options: list[MenuOption] = [] _stop_event: Event _icon_image: str _hover_text: str def __init__(self, icon_image: str = "icon.ico", hover_text: str = "Hover text", stop_event: Event = Event()): """ Initialize system tray icon. Args: icon_image (str, optional): Name of image to use as tray icon. Defaults to "icon.ico". hover_text (str, optional): Text displayed when the tray icon is hovered. Defaults to "Hover text". stop_event (Event, optional): Flag is set when the tray icon process exits. Defaults to Event(). """ super().__init__() self._stop_event = stop_event self._icon_image = icon_image self._hover_text = hover_text self._menu_options = [ ("Show QR code", None, lambda icon: self._pipe.send("show_qr_code")), ("Show debug logs", None, lambda icon: self._pipe.send("show_debug_logs")) ] def run(self): """ Display the tray icon in the system tray. Tray icon thread is spawned and the tray loop is executed. """ self._icon = SysTrayIcon( self._icon_image, self._hover_text, tuple(self._menu_options), on_quit=self.handle_quit) self._thread = Thread(target=self._loop) self._thread.start() def handle_quit(self, icon: SysTrayIcon): """ Set the stop flag. Called when the quit option is selected from the icons context menu. Args: icon (SysTrayIcon): SysTrayIcon reference """ self._stop_event.set() def join(self): """Join the thread that runs the TrayIcon.""" self._thread.join() def _loop(self): """ Listen to events on the system tray icon. @NOTE This should not be run directly and will block the main thread. It is automatically executed on a spawned worker thread internally. """ self._icon.start() while True: if self._stop_event.is_set(): self._icon.shutdown() return sleep(SLEEP_INTERVAL)
def AskDir(systray): global dirname Dir = Tk() # Set tkinter root Dir.withdraw() # IDK stackoverflow said some Dir.iconbitmap('KSrcIcon.ico') # Set icon tempdir = filedialog.askdirectory( initialdir=dirname, title='Please select a directory') # Do some stuff if tempdir != "": # Check if nothing was done dirname = tempdir # They did something? Brilliant! dirname += "\\" # Add this cuz python don't like me print("Dir set to: " + dirname) with open("Dir_Save", "w+") as f: # Set the dir back to save file f.write(dirname) # I don't really understand this. Just copied it from stackoverflow def on_exit(systray): exit() menu_options = ( ("Screenshot", None, Screenshot), ("Open Folder", None, OpenFolder), ("Set Directory", None, AskDir)) # Some systray stuff idk systray = SysTrayIcon("KSrcIcon.ico", "KSrc", menu_options, on_quit=on_exit) # Even more systray stuff systray.start() # Start them systray
os.startfile(path) try: with open("config.ini", "r") as config: for line in config: line = ''.join(line) start_dsc = line.find('##') start_file = line.rfind('/') filename = line[start_file + 1: start_dsc].replace(" ", "") path = line[:start_dsc - 1] description = line[start_dsc + 3:] new_callback_function = partial(callback_function, path=path) callback = new_callback_function iconextract(path, filename) if len(description) > 1: sep = ' || ' else: sep = ' ' softlist.append((filename + sep + description, filename + ".ico", callback)) except: pass softlist.append(('Add/Del Software', 'add.ico', popupmsg)) for i in range(0, len(softlist)): menu_options += (softlist[i],) sysTrayIcon = SysTrayIcon('tray.ico', hover_text, menu_options, default_menu_index=len(softlist)-1) sysTrayIcon.start()
class MainService: def __init__(self, notificationService, gitService): self.CHECK_ICO = StringService.getIcons('success') self.ERROR_ICO = StringService.getIcons('error') self.CHANGE_ICO = StringService.getIcons('warning') self.notificationService = notificationService self.gitService = gitService self.isGitReminderStarted = False self.threads = [] self.countStatusDirty = 0 self.countStatusError = 0 self.countStatusOk = 0 def startSystray(self): a = StringService.getIcons('success') try: menu_options = ( (APP_NAME, self.CHECK_ICO, ( ('Start', None, self.startGitReminderFromSystray), ('Stop', None, self.stopGitReminderFromSystray), ('Restart', None, self.restartGitReminderFromSystray), )), ("Status", None, self.status), ("About", None, self.about), ) self.systray = SysTrayIcon(self.CHECK_ICO, STATUS_OK, menu_options, on_quit=self.on_quit_callback) self.systray.start() except: self.notificationService.showToastNotification( APP_NAME, "Start: FAILED", self.ERROR_ICO) sys.exit() def startGitReminderFromSystray(self, systray): self.startGitReminder() def startGitReminder(self): if self.isGitReminderStarted: self.notificationService.showToastNotification( APP_NAME, "is already started", self.CHECK_ICO) else: configService = ConfigService(self.notificationService) self.noGitRepos = False self.dirtyGitRepos = False self.countStatusDirty = 0 self.countStatusError = 0 self.countStatusOk = 0 for repo in configService.readConf(): if self.gitService.isGitRepo(repo): if self.gitService.isRepoDirty(repo): self.countStatusDirty += 1 self.updateSystrayInfo() self.startThreadwithRepo(repo) else: self.countStatusOk += 1 self.startThreadwithRepo(repo) self.updateSystrayInfo() else: self.countStatusError += 1 self.updateSystrayInfo() self.notificationService.showToastNotification( APP_NAME, "is started", self.CHECK_ICO) self.isGitReminderStarted = True def startThreadwithRepo(self, repo): thread = Thread(target=self.profileThreads, args=(repo, )) self.threads += [thread] thread.start() def profileThreads(self, repo): mins = int(repo.sleeptime) * 60 currentTime = 0 while self.isGitReminderStarted: if currentTime == mins: nowTime = datetime.now() targetTime = datetime(nowTime.year, nowTime.month, nowTime.day, int(profile.reminderTimeHour), int(profile.reminderTimeMin)) if targetTime < nowTime: self.startCheckProfile(profile) currentTime = 0 time.sleep(1) currentTime += 1 def stopGitReminderFromSystray(self, systray): self.stopGitReminder() def stopGitReminder(self): if not self.isGitReminderStarted: self.notificationService.showToastNotification( APP_NAME, "is already stopped", self.CHECK_ICO) else: self.isGitReminderStarted = False for x in self.threads: x.join() self.threads = [] self.notificationService.showToastNotification( APP_NAME, "is stopped", self.CHECK_ICO) self.systrayUpdate(CHECK_ICO, STATUS_NOT_RUNNING) def restartGitReminder(self): self.stopGitReminder() self.startGitReminder() def restartGitReminderFromSystray(self, systray): self.restartGitReminder() def status(self, systray): if self.isGitReminderStarted: self.notificationService.showToastNotification( APP_NAME, "is started", self.CHECK_ICO) else: self.notificationService.showToastNotification( APP_NAME, "is stopped", self.CHECK_ICO) def updateSystrayInfo(self): if self.countStatusError > 0: self.systrayUpdate( self.ERROR_ICO, StringService.getMessages('systray_status_error').format( self.countStatusError)) elif self.countStatusDirty > 0: self.systrayUpdate( self.CHANGE_ICO, StringService.getMessages('systray_status_dirty').format( self.countStatusDirty)) else: self.systrayUpdate( self.CHECK_ICO, StringService.getMessages('systray_status_ok').format( self.countStatusOk)) def systrayUpdate(self, ico, status): self.systray._create_window self.systray.update(ico, status) def about(self, systray): webbrowser.open(StringService.getMetaInfos('about')) def on_quit_callback(self, systray): pass
class Interface(Frame): def __init__(self, master=None, data={}): Frame.__init__(self, master) self.version = "1.2.0" self.master.title(data["window_name"]) self.data = data self._main = Main(self.data["directory_name"]) self.default = StringVar() self.default.set(self._main.language) self.load_config() self.init_elements() self.fill_from_config() menu_options = (("Open Window", None, self.back), ("Start", None, self.start_service), ("Stop", None, self.stop_service)) self.systray = SysTrayIcon("./{0}/icon.ico".format( self.data["directory_name"]), "SIVA", menu_options, on_quit=self.quit_service) self.systray.start() self.master.protocol("WM_DELETE_WINDOW", self.vanish) def quit_service(self, e=None): exit(0) def vanish(self): self.master.withdraw() def back(self, e=None): self.master.update() self.master.deiconify() def load_config(self): if path.isfile("./{0}/config.json".format( self.data["directory_name"])): with open("./{0}/config.json".format(self.data["directory_name"]), "r") as out: self.config = json.load(out) else: with open("./{0}/config.json".format(self.data["directory_name"]), "w+") as out: self.config = { "api_token": "", "platform": "Playstation", "username": "", "language": "en", "autostart": False, "id_search": False } json.dump(self.config, out, indent=4) def fill_from_config(self): self.token_box.delete(0, END) self.token_box.insert(0, self.config["api_token"]) self.option_menu_default.set(self.config["platform"]) self.username_box.delete(0, END) self.username_box.insert(0, self.config["username"]) if self.config.get("autostart", None) == None: self.config["autostart"] = False if self.config.get("autostart", None) == True: self.start_service() if self.config.get("id_search", None) == None: self.config["id_search"] = False self.search_with_id.set(self.config["id_search"]) def init_elements(self): self.menubar = Menu(self.master) self.master.config(menu=self.menubar) self.menu_dropdown_siva = Menu(self.menubar) self.menu_dropdown_themes = Menu(self.menubar) self.menu_dropdown_links = Menu(self.menubar) self.menu_dropdown_help = Menu(self.menubar) self.menu_dropdown_language = Menu(self.menubar) self.menu_dropdown_siva.add_command( label="Start", command=lambda: self.start_service()) self.menu_dropdown_siva.add_command( label="Stop", command=lambda: self.stop_service()) self.auto_start = BooleanVar() self.auto_start.set(self.config.get("autostart", False)) self.menu_dropdown_siva.add_checkbutton( label="Autostart", onvalue=True, offvalue=False, variable=self.auto_start, command=lambda: self.start_service()) self.search_with_id = BooleanVar() self.search_with_id.set(self.config.get("id_search", False)) self.menu_dropdown_siva.add_checkbutton(label="Login With ID", onvalue=True, offvalue=False, variable=self.search_with_id) self.menu_dropdown_themes.add_command( label="Light Theme", command=lambda: self.light_mode()) self.menu_dropdown_themes.add_command(label="Dark Theme", command=lambda: self.dark_mode()) self.menu_dropdown_links.add_command( label="Get A Token", command=lambda: open_new_tab( "https://www.bungie.net/en/Application")) self.menu_dropdown_links.add_command( label="Message Me On Reddit", command=lambda: open_new_tab( "https://www.reddit.com/message/compose?to=TheTimebike&subject=SIVA" )) self.menu_dropdown_links.add_command( label="Github", command=lambda: open_new_tab("https://github.com/TheTimebike/SIVA" )) self.menu_dropdown_links.add_command( label="Report An Issue", command=lambda: open_new_tab( "https://github.com/TheTimebike/SIVA/issues")) self.menu_dropdown_help.add_command( label="About", command=lambda: messagebox.showinfo( "SIVA", "SIVA:\nVersion: {0}\nCreator: u/TheTimebike".format( self.version))) language_conversion_table = self.get_conversion_table("language") for lang, key in language_conversion_table.items(): self.add_language(lang, key) self.menubar.add_cascade(label="SIVA", menu=self.menu_dropdown_siva) self.menubar.add_cascade(label="Themes", menu=self.menu_dropdown_themes) self.menubar.add_cascade(label="Links", menu=self.menu_dropdown_links) self.menubar.add_cascade(label="Help", menu=self.menu_dropdown_help) self.menubar.add_cascade(label="Languages", menu=self.menu_dropdown_language) if self.data["version"] != self.version: self.menubar.add_command(label="Update", command=lambda: update(self)) self.label_1 = Label(self.master, text="API Token") self.label_1.place(x=315, y=10) self.token_box = Entry(self.master, width=50) self.token_box.place(x=10, y=10) self.token_button = Button( self.master, width=20, height=1, text="How Do I Find This?", command=lambda: open_new_tab( "https://www.bungie.net/en/Application")) self.token_button.place(x=375, y=9) self.start_button = Button(self.master, width=72, height=5, text="Start!", command=lambda: self.start_service()) self.start_button.place(x=10, y=70) self.option_menu_default = StringVar() self.option_menu_default.set("Playstation") self.option_menu = OptionMenu(self.master, self.option_menu_default, "Playstation", "Xbox", "Steam", "Stadia") self.option_menu.configure(highlightthickness=0) self.option_menu.place(x=8, y=35) self.label_2 = Label(self.master, text="Select Platform") self.label_2.place(x=110, y=40) self.username_box = Entry(self.master, width=40) self.username_box.place(x=220, y=40) self.label_3 = Label(self.master, text="Username") self.label_3.place(x=460, y=40) self.elements = { "labels": [self.label_1, self.label_2, self.label_3], "entrys": [self.username_box, self.token_box], "optionmenus": [self.option_menu], "buttons": [self.start_button, self.token_button] } def add_language(self, lang, key): self.menu_dropdown_language.add_radiobutton( label=lang, value=key, variable=self.default, command=lambda: self.change_language(key)) def change_language(self, key): self._main.language = key self._main.configurator.save({ "api_token": self.token_box.get(), "platform": self.option_menu_default.get(), "username": self.username_box.get(), "language": self._main.language, "autostart": self.auto_start.get() }) def light_mode(self): text_colour = "black" box_colour = "white" background_colour = "#f0f0f0" self.master.configure(background=background_colour) for element in self.elements["labels"]: element.configure(background=background_colour, foreground=text_colour) for element in self.elements["buttons"]: element.configure(background=background_colour, foreground=text_colour) for element in self.elements["entrys"]: element.configure(background=box_colour, foreground=text_colour) for element in self.elements["optionmenus"]: element.configure(highlightthickness=0, background=background_colour, foreground=text_colour, activebackground=background_colour, activeforeground=text_colour) def dark_mode(self): text_colour = "white" box_colour = "#484b52" background_colour = "#36393f" self.master.configure(background=background_colour) for element in self.elements["labels"]: element.configure(background=background_colour, foreground=text_colour) for element in self.elements["buttons"]: element.configure(background=background_colour, foreground=text_colour) for element in self.elements["entrys"]: element.configure(background=box_colour, foreground=text_colour) for element in self.elements["optionmenus"]: element.configure(highlightthickness=0, background=background_colour, foreground=text_colour, activebackground=background_colour, activeforeground=text_colour) def start_service(self, e=None): self._main.configurator.save({ "api_token": self.token_box.get(), "platform": self.option_menu_default.get(), "username": self.username_box.get(), "language": self._main.language, "autostart": self.auto_start.get(), "id_search": self.search_with_id.get() }) self.thread = Thread(target=self._main.start_siva, args=(self, )) self.thread.daemon = True self.thread.start() self.start_button.config(text="Stop!", command=lambda: self.stop_service()) def stop_service(self, e=None): self._main.run = False self.start_button.config(text="Start!", command=lambda: self.start_service()) def get_conversion_table(self, table): _index = get( "https://raw.githubusercontent.com/TheTimebike/SIVA/master/conversion_tables/index.json" ) _data_url = _index.json()[table] _data = get(_data_url) return _data.json() def error(self, error_enum): self.stop_service() error_conversion_table = self.get_conversion_table("error") messagebox.showinfo(error_conversion_table["error_window_name"], error_conversion_table["errors"][error_enum]) return None def create_pick_account_interface(self, acc_list): self.select_acc_window = SubWindow(self.master, acc_list, self._main) self.select_acc_window.master.iconbitmap("./{0}/icon.ico".format( self.data["directory_name"]))
class TrayIcon: def __init__(self): menuOptions = (('Audio', None, (('Select Alert Sound', None, self._onSelectAlertSound), ('Play', None, self._onPlayAlert), ('Stop', None, self._onStopAlert))), ('Open Maps File', None, self._onOpenMapsFile), ('Select Path of Exile folder', None, self._onSelectPathOfExileDirectory)) self._icon = SysTrayIcon(iconPath, 'Map Alert', menu_options=menuOptions, on_quit=self._onQuit) self._callbackQueue = queue.Queue() async def show(self): self._icon.start() while True: try: callback = self._callbackQueue.get(False) callback() except queue.Empty: await asyncio.sleep(0.1) def close(self): self._icon.shutdown() def onSelectAlertSound(self): pass def onPlayAlert(self): pass def onStopAlert(self): pass def onOpenMapsFile(self): pass def onSelectPathOfExileDirectory(self): pass def onQuit(self): pass def _onSelectAlertSound(self, sysTray): self._callbackQueue.put(self.onSelectAlertSound) def _onPlayAlert(self, sysTray): self._callbackQueue.put(self.onPlayAlert) def _onStopAlert(self, sysTray): self._callbackQueue.put(self.onStopAlert) def _onOpenMapsFile(self, sysTray): self._callbackQueue.put(self.onOpenMapsFile) def _onSelectPathOfExileDirectory(self, sysTray): self._callbackQueue.put(self.onSelectPathOfExileDirectory) def _onQuit(self, sysTray): self._callbackQueue.put(sysTray.shutdown) self._callbackQueue.put(self.onQuit)
class GW2RPC: def __init__(self): def fetch_registry(): url = GW2RPC_BASE_URL + "registry" res = requests.get(url) if res.status_code != 200: log.error("Could not fetch the web registry") return None return res.json() def icon_path(): try: return os.path.join(sys._MEIPASS, "icon.ico") except: return "icon.ico" def fetch_support_invite(): try: return requests.get(GW2RPC_BASE_URL + "support").json()["support"] except: return None self.rpc = DiscordRPC(GW2RPC_APP_ID) self.game = MumbleData() self.registry = fetch_registry() self.support_invite = fetch_support_invite() menu_options = (("About", None, self.about), ) if self.support_invite: menu_options += (("Join support server", None, self.join_guild), ) self.systray = SysTrayIcon( icon_path(), "Guild Wars 2 with Discord", menu_options, on_quit=self.shutdown) self.systray.start() self.process = None self.last_map_info = None self.last_continent_info = None self.last_boss = None self.boss_timestamp = None self.no_pois = set() self.check_for_updates() def shutdown(self, _=None): os._exit(0) # Nuclear option def about(self, _): message = ( "Version: {}\n\nhttps://gw2rpc.info\n\nBy Maselkov & " "N1TR0\nIcons by Zebban\nWebsite by Penemue".format(VERSION)) threading.Thread(target=create_msgbox, args=[message]).start() def join_guild(self, _): try: webbrowser.open(self.support_invite) except webbrowser.Error: pass def check_for_updates(self): def get_build(): url = GW2RPC_BASE_URL + "build" r = requests.get(url) try: return r.json()["build"] except: return None build = get_build() if not build: log.error("Could not retrieve build!") create_msgbox( "Could not check for updates - check your connection!") return if build > VERSION: log.info("New version found! Current: {} New: {}".format( VERSION, build)) res = create_msgbox( "There is a new update for GW2 Rich Presence available. " "Would you like to be taken to the download page now?", code=68) if res == 6: webbrowser.open("https://gw2rpc.info/") def get_map_asset(self, map_info): map_id = map_info["id"] map_name = map_info["name"] region = map_info.get("region_name", "thanks_anet") if self.registry: if map_name == "Fractales des Brumes": for fractal in self.registry["fractals"]: if fractal["id"] == map_id: state = fractal["name"] + " fractal" image = "fotm" break else: image = "fotm" state = "Fractales des Brumes" name = "Fractales des Brumes" else: if map_name in self.registry["special"]: image = self.registry["special"][map_name] elif map_id in self.registry["valid"]: image = map_id elif region in self.registry["regions"]: image = self.registry["regions"][region] else: image = "default" name = map_name state = name else: special = { "Fractals of the Mists": "fotm", "Refuge de Chassevent": "gh_haven", "Caverne dorée": "gh_hollow", "Précipice perdu": "gh_precipice" }.get(map_info["name"]) if special: return special if map_info["type"] == "Public": image = map_id else: valid_ids = [1062, 1149, 1156, 38, 1264] if map_id in valid_ids: image = map_id else: image = "default" name = map_name state = name return "dans " + state, {"large_image": str(image), "large_text": name} def get_raid_assets(self, map_info): def readable_id(_id): _id = _id.split("_") dont_capitalize = ("of", "the", "in") return " ".join([ x.capitalize() if x not in dont_capitalize else x for x in _id ]) boss = self.find_closest_boss(map_info) if not boss: self.boss_timestamp = None return self.get_map_asset(map_info) if boss["type"] == "boss": state = "en combat " else: state = "combat terminé " name = readable_id(boss["id"]) state += name if self.last_boss != boss["id"]: self.boss_timestamp = int(time.time()) self.last_boss = boss["id"] return state, { "large_image": boss["id"], "large_text": name + " - {}".format(map_info["name"]) } def get_activity(self): def get_region(): world = api.world if world: for k, v in worlds.items(): if world in v: return " [{}]".format(k) return "" def get_closest_poi(map_info, continent_info): region = map_info.get("region_name") if config.disable_pois: return None if config.disable_pois_in_wvw and region == "World vs. World": return None return self.find_closest_point(map_info, continent_info) data = self.game.get_mumble_data() if not data: return None map_id = data["map_id"] try: if self.last_map_info and map_id == self.last_map_info["id"]: map_info = self.last_map_info else: map_info = api.get_map_info(map_id) self.last_map_info = map_info character = Character(data) except APIError: log.exception("API Erreur!") self.last_map_info = None return None state, map_asset = self.get_map_asset(map_info) tag = character.guild_tag if config.display_tag else "" try: if map_id in self.no_pois or "continent_id" not in map_info: raise APIError(404) if (self.last_continent_info and map_id == self.last_continent_info["id"]): continent_info = self.last_continent_info else: continent_info = api.get_continent_info(map_info) self.last_continent_info = continent_info except APIError: self.last_continent_info = None self.no_pois.add(map_id) details = character.name + tag timestamp = self.game.last_timestamp if self.registry and str(map_id) in self.registry.get("raids", {}): state, map_asset = self.get_raid_assets(map_info) timestamp = self.boss_timestamp or self.game.last_timestamp else: self.last_boss = None if self.last_continent_info: point = get_closest_poi(map_info, continent_info) if point: map_asset["large_text"] += " proche de " + point["name"] map_asset["large_text"] += get_region() activiy = { "state": state, "details": details, "timestamps": { 'start': timestamp }, "assets": { **map_asset, "small_image": character.profession_icon, "small_text": "{0.race} {0.profession}".format(character, tag) } } return activiy def in_character_selection(self): activity = { "state": "sélection de personnage", "assets": { "large_image": "default", "large_text": "Sélection de personnage", "small_image": "gw2rpclogo", "small_text": "GW2RPC Version {}\nhttps://gw2rpc.info".format(VERSION) } } return activity def convert_mumble_coordinates(self, map_info, position): crect = map_info["continent_rect"] mrect = map_info["map_rect"] x = crect[0][0] + (position.x - mrect[0][0]) / 24 y = crect[0][1] + (mrect[1][1] - position.y) / 24 return x, y def find_closest_point(self, map_info, continent_info): position = self.game.get_position() x_coord, y_coord = self.convert_mumble_coordinates(map_info, position) lowest_distance = float("inf") point = None for item in continent_info["points_of_interest"].values(): if "name" not in item: continue distance = (item["coord"][0] - x_coord)**2 + ( item["coord"][1] - y_coord)**2 if distance < lowest_distance: lowest_distance = distance point = item return point def find_closest_boss(self, map_info): position = self.game.get_position() x_coord, y_coord = self.convert_mumble_coordinates(map_info, position) closest = None for boss in self.registry["raids"][str(map_info["id"])]: distance = math.sqrt((boss["coord"][0] - x_coord)**2 + (boss["coord"][1] - y_coord)**2) if "radius" in boss and distance < boss["radius"]: if "height" in boss: if position.z < boss["height"]: closest = boss else: closest = boss return closest def main_loop(self): def update_gw2_process(): shutdown = False if self.process: if self.process.is_running(): return else: if config.close_with_gw2: shutdown = True for process in psutil.process_iter(attrs=['name']): name = process.info['name'] if name in ("Gw2-64.exe", "Gw2.exe"): self.process = process return if shutdown: self.shutdown() self.process = None raise GameNotRunningError def start_rpc(): while True: try: self.rpc.start() break except (FileNotFoundError, PermissionError) as e: time.sleep(10) try: while True: try: update_gw2_process() if not self.game.memfile: self.game.create_map() if not self.rpc.running: start_rpc() log.debug("Lancement self.rpc") try: data = self.get_activity() except requests.exceptions.ConnectionError: raise GameNotRunningError if not data: data = self.in_character_selection() log.debug(data) try: self.rpc.send_rich_presence(data, self.process.pid) except BrokenPipeError: raise GameNotRunningError # To start a new connection except GameNotRunningError: # TODO self.game.close_map() if self.rpc.running: self.rpc.close() log.debug("Killing RPC") time.sleep(15) except Exception as e: log.critical("GW2RPC a crashé", exc_info=e) create_msgbox( "GW2 Rich Presence a crashé.\nVérifiez le fichier " "log et envoyez son contenu au créateur!", code=16) self.shutdown()
class SysTrayPlugin(plugins.SimplePlugin): ''' CherryPy plugin that creates a system tray icon for Windows. Because SysTrayIcon always fires off on_quit, we can't have on_quit execute cherrypy.engine.exit() if cherrypy.engine.exit() is what triggered SysTrayIcon to close. So conditions are set to only fire on_quit when the quit_method == 'men'. This way, when the menu option is called, it destroys SysTrayIcon then closes cherrypy. Cherrypy will try to close SysTrayIcon by calling stop(), so _on_quit() gets reassigned to None. If the app is closed by cherrypy (whether catching a kb interrupt or the GUI shutdown button), cherrypy stops the plugin by calling stop(). Stop() reassigns SysTrayIcon._on_quit to None and calls SysTrayIcon.shutdown(). SysTrayIcon is then destroyed (twice for reasons I can't figure out), then cherrypy finishes up the engine.stop() and engine.exit(). The chain is as such: Trigger == systray menu 'Quit': SysTrayIcon._destroy() > SysTrayIcon._on_quit() > set SysTrayPlugin.quit_method = 'men' cherrypy.engine.exit() > SysTrayPlugin.stop() > does nothing sys.exit() Trigger == KBInterrupt or GUI Shutdown: cherrypy.engine.stop() > SysTrayPlugin.stop() > disable SysTrayIcon._on_quit() SysTrayIcon.shutdown() > SysTrayIcon._destroy() > SysTrayIcon._destroy() > cherrypy.engine.exit() > sys.exit() ''' def __init__(self, bus, icon, name, menu_options, on_quit=lambda: None): if not callable(on_quit): raise TypeError('on_quit not a callable object.') self.on_quit = on_quit plugins.SimplePlugin.__init__(self, bus) self.systray = SysTrayIcon(icon, name, menu_options, on_quit=self._on_quit) self.quit_method = None return def start(self): self.systray.start() return def stop(self): if self.quit_method == 'men': return else: self.systray._on_quit = None self.systray.shutdown() return def _on_quit(self, systray): self.on_quit() self.quit_method = 'men' cherrypy.engine.exit() sys.exit(0)
class ProjectApp(App): def build(self): # delete unnecessary default config file to avoid overloading App.get_application_config try: remove('project.ini') except: pass # check if config file in app dir exists self.custom_config_name = 'pppm.ini' self.app_folder = dirname(realpath(__file__)) self.app_folder_config_fn = join(self.app_folder, self.custom_config_name) if isfile(self.app_folder_config_fn): # config is in app dir self.config.filename = self.app_folder_config_fn else: # config is in user dir self.config.filename = join(self.user_data_dir, self.custom_config_name) # title self.title = '3PM' # initialize settings self.use_kivy_settings = False self.settings_cls = SettingsWithTabbedPanel self.icon = join('data', 'icon.ico') # read configuration file self.config.read(self.config.filename) # initialize projects self.projects = Projects(name='projects', config=self.config) self.load_projects() # initialize ebs history self.velocity_history = [] self.load_velocity_history() # initialize timer self.timer = Timer(self.config) self.simulation_string = StringProperty() # screen management and transition self.transition = SlideTransition(duration=.35) root = ScreenManager(transition=self.transition) root.add_widget(self.projects) return root def build_config(self, config): if platform in ['android', 'ios']: # smartphone defaults config.setdefaults( 'timer', { 'start_sound': 1, 'end_sound': 1, 'vibrate': 1, 'notification': 0, 'notification_timeout': 10, 'session_length': 25, 'use_notepad': 0 }) config.setdefaults('ebs', { 'use_ebs': 1, 'number_history': 30, 'log_activity': 0 }) config.setdefaults('system', { 'enable_tray': 0, 'hide_window': 0, 'store_in_app': 0 }) else: # defaults for desktop computers config.setdefaults( 'timer', { 'start_sound': 1, 'end_sound': 1, 'vibrate': 0, 'notification': 1, 'notification_timeout': 10, 'session_length': 25, 'use_notepad': 1 }) config.setdefaults('ebs', { 'use_ebs': 1, 'number_history': 30, 'log_activity': 1 }) config.setdefaults('system', { 'enable_tray': 1, 'hide_window': 0, 'store_in_app': 0 }) def build_settings(self, settings): settings.add_json_panel('Timer', self.config, data=settings_info.timer_settings_json) settings.add_json_panel('Simulation', self.config, data=settings_info.ebs_settings_json) settings.add_json_panel('System', self.config, data=settings_info.system_settings_json) def on_config_change(self, config, section, key, value): # react on store_in_app config change if section == 'system' and key == 'store_in_app': if value == '0': try: # move data to user folder move_if_exists(join(self.app_folder, 'projects.json'), join(self.user_data_dir, 'projects.json')) move_if_exists( join(self.app_folder, 'velocity_history.json'), join(self.user_data_dir, 'velocity_history.json')) move_if_exists( join(self.app_folder, 'daily_activity.txt'), join(self.user_data_dir, 'daily_activity.txt')) # write config to user folder self.config.filename = join(self.user_data_dir, self.custom_config_name) self.config.write() # remove old config file remove(self.app_folder_config_fn) except: # failed to move to new dir, undo config change self.config.set('system', 'store_in_app', '1') else: try: # move data to app folder move_if_exists(join(self.user_data_dir, 'projects.json'), join(self.app_folder, 'projects.json')) move_if_exists( join(self.user_data_dir, 'velocity_history.json'), join(self.app_folder, 'velocity_history.json')) move_if_exists( join(self.user_data_dir, 'daily_activity.txt'), join(self.app_folder, 'daily_activity.txt')) # write config to app folder self.config.filename = join(self.app_folder, self.custom_config_name) self.config.write() # remove old config file remove(join(self.user_data_dir, self.custom_config_name)) except: # failed to move to new dir, undo config change self.config.set('system', 'store_in_app', '0') # react on enable_tray config change if section == 'system' and key == 'enable_tray' and platform == 'win': if value == '0': # shutdown tray icon try: self.systray.shutdown() except: pass else: # initialize system tray - todo: encapsulate menu_options = (("Show Session Info", "Show current task and remaining time.", self.systray_show_info), ) hover_text = "Personal Project Productivity Manager - 3PM" self.systray = SysTrayIcon("data/icon.ico", hover_text, menu_options, default_menu_index=0, on_quit=self.systray_close_window) self.systray.start() # reinitialize timer self.timer.init(config) # reinitialize projects self.projects = Projects(name='projects', config=config) self.load_projects() # update projects view config self.projects.use_ebs = config.get('ebs', 'use_ebs') == '1' self.root.remove_widget(self.root.get_screen('projects')) self.root.add_widget(self.projects) self.root.current = 'projects' def on_start(self): # no current project index self.current_project_index = -1 if platform == 'win' and self.config.get('system', 'enable_tray') == '1': # initialize system tray - todo: encapsulate menu_options = (("Show Session Info", "Show current task and remaining time.", self.systray_show_info), ) hover_text = "Personal Project Productivity Manager - 3PM" self.systray = SysTrayIcon("data/icon.ico", hover_text, menu_options, default_menu_index=0, on_quit=self.systray_close_window) self.systray.start() def on_stop(self): self.config.write() if platform == 'win': # shutdown tray icon try: self.systray.shutdown() except: pass def systray_show_info(self, sysTrayIcon): # remaining time if self.timer.running_down: message_time = '%s remaining.' % self.timer.time_string elif self.timer.running_up: message_time = 'On break since %s.' % self.timer.time_string else: message_time = 'No session running.' # project string if self.current_project_index == -1: project_title = 'Quick Session' else: project_title = self.projects.data[ self.current_project_index]['title'] # show notification self.timer.notification_wrapper.notify( title=project_title, message=message_time, timeout=self.timer.notification_timeout) def systray_close_window(self, sysTrayIcon): # stop app if not called from traybar menu (not settings change) if self.config.get('system', 'enable_tray') == '1': try: App.get_running_app().stop() except: exit(0) def load_velocity_history(self): # load velocity history from file if isfile(self.velocity_history_fn): with open(self.velocity_history_fn) as fd: self.velocity_history = json.load(fd) else: # assume bad estimation self.velocity_history = [ 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9 ] def save_velocity_history(self): # save velocity history to file with open(self.velocity_history_fn, 'w') as fd: json.dump(self.velocity_history, fd) def load_projects(self): # load projects from file if not isfile(self.projects_fn): return with open(self.projects_fn) as fd: data = json.load(fd) self.projects.data = data def save_projects(self): # save projects to file with open(self.projects_fn, 'w') as fd: json.dump(self.projects.data, fd) def delete_project(self, project_index): # go to project list self.go_projects(project_index) # delete project del self.projects.data[project_index] self.save_projects() self.refresh_projects() def finish_project(self, project_index): # go to project list self.go_projects(project_index) if self.config.get('ebs', 'use_ebs') == '1': # get velocity rating vel_rating = self.projects.data[project_index][ 'logged'] / self.projects.data[project_index]['estimated'] if vel_rating > 0: # save velocity rating to history self.velocity_history.append(vel_rating) self.save_velocity_history() # delete project del self.projects.data[project_index] self.save_projects() self.refresh_projects() def edit_project(self, project_index): self.current_project_index = project_index project = self.projects.data[project_index] name = 'project{}'.format(project_index) if self.root.has_screen(name): self.root.remove_widget(self.root.get_screen(name)) if self.config.get('ebs', 'use_ebs') == '1': if self.config.get('timer', 'use_notepad') == '0': view = ProjectViewWithoutNotepad( name=name, project_index=project_index, project_title=project.get('title'), project_estimated=project.get('estimated'), project_logged=project.get('logged')) else: view = ProjectView(name=name, project_index=project_index, project_title=project.get('title'), project_content=project.get('content'), project_estimated=project.get('estimated'), project_logged=project.get('logged')) else: if self.config.get('timer', 'use_notepad') == '0': view = ProjectViewSimpleWithoutNotepad( name=name, project_index=project_index, project_title=project.get('title')) else: view = ProjectViewSimple( name=name, project_index=project_index, project_title=project.get('title'), project_content=project.get('content')) # add widget to root self.root.add_widget(view) self.transition.direction = 'left' self.root.current = view.name # update timer logged view self.timer.update_logged_string(project.get('logged')) # update simulation string self.update_simulation_string(project_index) def quick_session(self): if not self.root.current == '': # remove previous quick view screen if self.root.has_screen(''): self.root.remove_widget(self.root.get_screen('')) view = QuickView(project_content='') self.root.add_widget(view) self.transition.direction = 'left' self.root.current = view.name self.current_project_index = -1 self.start_timer() def add_project(self): self.projects.data.append({ 'title': 'Unnamed Task', 'content': '', 'logged': 0, 'estimated': 1 }) project_index = len(self.projects.data) - 1 self.edit_project(project_index) def set_project_content(self, project_index, project_content): self.projects.data[project_index]['content'] = project_content data = self.projects.data self.projects.data = [] self.projects.data = data self.save_projects() self.refresh_projects() def set_project_title(self, project_index, project_title): self.projects.data[project_index]['title'] = project_title self.save_projects() self.refresh_projects() def set_project_estimated(self, project_index, estimated): new_estimate = float(estimated) # sanity check if not new_estimate > 0: new_estimate = 1.0 # save new estimate to project self.projects.data[project_index]['estimated'] = new_estimate self.save_projects() self.refresh_projects() # update simulation string self.update_simulation_string(project_index) def set_project_logged(self, project_index, logged): self.projects.data[project_index]['logged'] = float(logged) self.save_projects() self.refresh_projects() def refresh_projects(self): data = self.projects.data self.projects.data = [] self.projects.data = data def go_projects(self, project_index): # stop in- or decrementing time Clock.unschedule(self.increment_time) Clock.unschedule(self.decrement_time) if project_index == -1: # quick session view; stop timer self.stop_timer() else: # project view; log work and stop timer if self.timer.running_down: self.stop_work(project_index) # go to project view self.transition.direction = 'right' self.root.current = 'projects' self.current_project_index = -1 def start_work(self, project_index): # start new session if timer completely stopped if not self.timer.running_down and not self.timer.running_up: # start timer self.start_timer() # set current project index self.current_project_index = project_index # or reset and start new session if timer runs up if self.timer.running_up: # stop counting up self.stop_timer() # start timer self.start_timer() def stop_work(self, project_index): # only log work when timer running down if self.timer.running_down: if not project_index == -1: # log work self.log_work(project_index) # save log self.refresh_projects() self.save_projects() # stop timer self.stop_timer() def log_work(self, project_index): # get logged fractional unit: (full session time - remaining time) / full session time full_session_time = float(self.config.get('timer', 'session_length')) logged_new = (full_session_time * 60. - (self.timer.minutes * 60. + self.timer.seconds)) / \ (full_session_time * 60.) logged_total = self.projects.data[project_index]['logged'] + logged_new # update logged self.set_project_logged(project_index, logged_total) # update logged view self.timer.update_logged_string(logged_total) # update simulation view self.update_simulation_string(project_index) def start_timer(self): # stop in- or decrementing time Clock.unschedule(self.increment_time) Clock.unschedule(self.decrement_time) # start decrementing time Clock.schedule_interval(self.decrement_time, 1) # store flags that timer is running down self.timer.running_up = False self.timer.running_down = True # play start sound if file found if self.timer.start_sound_activated and self.timer.start_sound: self.timer.start_sound.play() # hide main window if option activated if platform == 'win' and self.config.get('system', 'hide_window') == '1': self.root_window.hide() def stop_timer(self): # stop in- or decrementing time Clock.unschedule(self.increment_time) Clock.unschedule(self.decrement_time) # store flag that timer is not running down or up self.timer.running_down = False self.timer.running_up = False # reinitialize timer self.timer.minutes = self.timer.session_length self.timer.seconds = 0 self.timer.update_time_string() def decrement_time(self, interval): # decrement time of timer self.timer.seconds -= 1 if self.timer.seconds < 0: self.timer.minutes -= 1 self.timer.seconds = 59 if self.timer.minutes < 0: self.timer_alarm() self.timer.update_time_string() def increment_time(self, interval): # increment time of timer self.timer.seconds += 1 if self.timer.seconds > 60: self.timer.minutes += 1 self.timer.seconds = 0 self.timer.update_time_string() def timer_alarm(self): # stop decrementing time Clock.unschedule(self.decrement_time) # set timer to 0:00 self.timer.minutes = 0 self.timer.seconds = 0 # update time string self.timer.update_time_string() # log work if not self.current_project_index == -1: self.log_work(self.current_project_index) # save log self.refresh_projects() self.save_projects() if platform == 'win' and self.config.get('system', 'hide_window') == '1': # show main window again self.root_window.show() # log date of completed session to file if self.config.get('ebs', 'log_activity') == '1': # date and count for this session date_today = datetime.datetime.today().strftime('%Y-%m-%d') count_today = 1 # read old file if isfile(self.activity_fn): with open(self.activity_fn, 'r') as f: # read lines lines = f.readlines() # get date and count date_file, count_file = lines[-1].split() if date_file == date_today: # add count from earlier sessions today count_today += int(count_file) # remove last line with current log for today lines = lines[:-1] else: lines = [] # append new last line lines.append("%s\t%s\n" % (date_today, str(count_today))) # write new file with open(self.activity_fn, 'w') as f: f.writelines(lines) # store flags that timer is not running down but up self.timer.running_down = False self.timer.running_up = True # start incrementing time Clock.schedule_interval(self.increment_time, 1) # show notification if self.timer.notification_activated: self.timer.notification_wrapper.notify( title="3PM", message="Session finished!", timeout=self.timer.notification_timeout) # play alarm sound if file found if self.timer.start_sound_activated and self.timer.alarm_sound: self.timer.alarm_sound.play() # vibrate on smartphone if self.timer.vibration_activated and platform in ['android', 'ios']: vibrator.vibrate(2) def simulate_completion(self, project_index): # only use most recent histories number_history = int(self.config.get('ebs', 'number_history')) if len(self.velocity_history) > number_history: velocity_history = self.velocity_history[-number_history:] else: velocity_history = self.velocity_history # MC simulate completion of project sessions_needed = [] for i in range(0, 100): # randomly choose a velocity rating vel = random.choice(velocity_history) # simulate necessity sessions sessions_needed.append( vel * self.projects.data[project_index]['estimated']) # sort sessions_needed = sorted(sessions_needed) # pick quartiles quartiles = [sessions_needed[i] for i in [24, 49, 74, 99]] # calc completion logged = float(self.projects.data[project_index]['logged']) completion = [logged * 100 / quartiles[i] for i in [0, 1, 2, 3]] return quartiles, completion def update_simulation_string(self, project_index): # simulate completion of project quartiles, completion = self.simulate_completion(project_index) simulation_string = "%i/%i/%i/%i\n%i%%/%i%%/%i%%/%i%%" % tuple( quartiles + completion) self.timer.update_simulation_string(simulation_string) @property def data_dir(self): # get settings and data dir if self.config.get('system', 'store_in_app') == '1': return dirname(realpath(__file__)) else: return self.user_data_dir @property def projects_fn(self): return join(self.data_dir, 'projects.json') @property def velocity_history_fn(self): return join(self.data_dir, 'velocity_history.json') @property def activity_fn(self): return join(self.data_dir, 'daily_activity.txt')
class GW2RPC: def __init__(self): def fetch_registry(): # First one only for building # Only used for debugging without the web based API #registry_path = resource_path('./data/registry.json') #registry_path = resource_path('../data/registry.json') #registry = json.loads(open(registry_path).read()) #return registry url = GW2RPC_BASE_URL + "registry" res = requests.get(url) if res.status_code != 200: log.error("Could not fetch the web registry") return None return res.json() def icon_path(): try: return os.path.join(sys._MEIPASS, "icon.ico") except: return "icon.ico" def fetch_support_invite(): try: return requests.get(GW2RPC_BASE_URL + "support").json()["support"] except: return None self.rpc = DiscordRPC(GW2RPC_APP_ID) self.game = MumbleData() self.registry = fetch_registry() self.support_invite = fetch_support_invite() menu_options = ((_("About"), None, self.about), ) if self.support_invite: menu_options += ((_("Join support server"), None, self.join_guild), ) self.systray = SysTrayIcon(icon_path(), _("Guild Wars 2 with Discord"), menu_options, on_quit=self.shutdown) self.systray.start() self.process = None self.last_map_info = None self.last_continent_info = None self.last_boss = None self.boss_timestamp = None self.no_pois = set() self.check_for_updates() def shutdown(self, _=None): os._exit(0) # Nuclear option def about(self, _): message = ( "Version: {}\n\nhttps://gw2rpc.info\n\nBy Maselkov & " "N1tR0\nIcons by Zebban\nWebsite by Penemue\nTranslations by Seshu (de), TheRaytheone (es), z0n3g (fr)" .format(VERSION)) threading.Thread(target=create_msgbox, args=[message]).start() def join_guild(self, _): try: webbrowser.open(self.support_invite) except webbrowser.Error: pass def check_for_updates(self): def get_build(): url = GW2RPC_BASE_URL + "build" r = requests.get(url) try: return r.json()["build"] except: return None build = get_build() if not build: log.error("Could not retrieve build!") create_msgbox( _("Could not check for updates - check your connection!")) return if build > VERSION: log.info("New version found! Current: {} New: {}".format( VERSION, build)) res = create_msgbox(_( "There is a new update for GW2 Rich Presence available. " "Would you like to be taken to the download page now?"), code=68) if res == 6: webbrowser.open("https://gw2rpc.info/") def get_map_asset(self, map_info): map_id = map_info["id"] map_name = map_info["name"] region = str(map_info.get("region_id", "thanks_anet")) position = self.game.get_position() #print("{} {}".format(position.x, position.y)) #print("{} {}".format(map_id, map_name)) #print("{} {}".format(position.x, position.y)) if self.registry: if region == "26": # Fractals of the Mists image = "fotm" for fractal in self.registry["fractals"]: state = self.find_fractal_boss(map_id, fractal, position) if state: break if fractal["id"] == map_id: state = _("in ") + _("fractal") + ": " + _( fractal["name"]) break else: if not state: state = _("in ") + _("Fractals of the Mists") name = "Fractals of the Mists" else: if map_name in self.registry["special"]: image = self.registry["special"][map_name] elif map_id in self.registry["valid"]: image = map_id elif region in self.registry["regions"]: image = self.registry["regions"][region] else: image = "default" name = map_name state = _("in ") + name else: # Fallback for api special = { "1068": "gh_hollow", "1101": "gh_hollow", "1107": "gh_hollow", "1108": "gh_hollow", "1121": "gh_hollow", "1069": "gh_precipice", "1076": "gh_precipice", "1071": "gh_precipice", "1104": "gh_precipice", "1124": "gh_precipice", "882": "wintersday_snowball", "877": "wintersday_snowball", "1155": "1155", "1214": "gh_haven", "1215": "gh_haven", "1232": "gh_haven", "1224": "gh_haven", "1243": "gh_haven", "1250": "gh_haven" }.get(map_info["id"]) if special: return special if map_info["type"] == "Public": image = map_id else: valid_ids = [1062, 1149, 1156, 38, 1264] if map_id in valid_ids: image = map_id else: image = "default" name = map_name state = _("in ") + name return state, {"large_image": str(image), "large_text": _(name)} def get_raid_assets(self, map_info): def readable_id(_id): _id = _id.split("_") dont_capitalize = ("of", "the", "in") return " ".join([ x.capitalize() if x not in dont_capitalize else x for x in _id ]) boss = self.find_closest_boss(map_info) if not boss: self.boss_timestamp = None return self.get_map_asset(map_info) if boss["type"] == "boss": state = _("fighting ") else: state = _("completing ") name = _(readable_id(boss["id"])) state += name if self.last_boss != boss["id"]: self.boss_timestamp = int(time.time()) self.last_boss = boss["id"] return state, { "large_image": boss["id"], "large_text": name + " - {}".format(map_info["name"]) } def get_activity(self): def get_region(): world = api.world if world: for k, v in worlds.items(): if world in v: return " [{}]".format(k) return "" def get_closest_poi(map_info, continent_info): #region = map_info.get("region_name") region = map_info.get("region_id") if config.disable_pois: return None if config.disable_pois_in_wvw and region == 7: #TODO change to region_Id return None return self.find_closest_point(map_info, continent_info) data = self.game.get_mumble_data() if not data: return None map_id = data["map_id"] is_commander = data["commander"] try: if self.last_map_info and map_id == self.last_map_info["id"]: map_info = self.last_map_info else: map_info = api.get_map_info(map_id) self.last_map_info = map_info character = Character(data) except APIError: log.exception("API Error!") self.last_map_info = None return None state, map_asset = self.get_map_asset(map_info) tag = character.guild_tag if config.display_tag else "" try: if map_id in self.no_pois or "continent_id" not in map_info: raise APIError(404) if (self.last_continent_info and map_id == self.last_continent_info["id"]): continent_info = self.last_continent_info else: continent_info = api.get_continent_info(map_info) self.last_continent_info = continent_info except APIError: self.last_continent_info = None self.no_pois.add(map_id) details = character.name + tag timestamp = self.game.last_timestamp if self.registry and str(map_id) in self.registry.get("raids", {}): state, map_asset = self.get_raid_assets(map_info) timestamp = self.boss_timestamp or self.game.last_timestamp else: self.last_boss = None if self.last_continent_info: point = get_closest_poi(map_info, continent_info) if point: map_asset["large_text"] += _(" near ") + point["name"] map_asset["large_text"] += get_region() if not config.hide_commander_tag and is_commander: small_image = "commander_tag" details = "{}: {}".format(_("Commander"), details) else: small_image = character.profession_icon small_text = "{} {} {}".format(_(character.race), _(character.profession), tag) activity = { "state": _(state), "details": details, "timestamps": { 'start': timestamp }, "assets": { **map_asset, "small_image": small_image, "small_text": small_text } } return activity def in_character_selection(self): activity = { "state": _("in character selection"), "assets": { "large_image": "default", "large_text": _("Character Selection"), "small_image": "gw2rpclogo", "small_text": "GW2RPC Version {}\nhttps://gw2rpc.info".format(VERSION) } } return activity def convert_mumble_coordinates(self, map_info, position): crect = map_info["continent_rect"] mrect = map_info["map_rect"] x = crect[0][0] + (position.x - mrect[0][0]) / 24 y = crect[0][1] + (mrect[1][1] - position.y) / 24 return x, y def find_closest_point(self, map_info, continent_info): position = self.game.get_position() x_coord, y_coord = self.convert_mumble_coordinates(map_info, position) lowest_distance = float("inf") point = None for item in continent_info["points_of_interest"].values(): if "name" not in item: continue distance = (item["coord"][0] - x_coord)**2 + (item["coord"][1] - y_coord)**2 if distance < lowest_distance: lowest_distance = distance point = item return point def find_closest_boss(self, map_info): position = self.game.get_position() x_coord, y_coord = self.convert_mumble_coordinates(map_info, position) closest = None for boss in self.registry["raids"][str(map_info["id"])]: distance = math.sqrt((boss["coord"][0] - x_coord)**2 + (boss["coord"][1] - y_coord)**2) if "radius" in boss and distance < boss["radius"]: if "height" in boss: if position.z < boss["height"]: closest = boss else: closest = boss return closest def find_fractal_boss(self, map_id, fractal, position): state = None if map_id in [1177, 1205, 1384]: if fractal["id"] == map_id: for boss in fractal["bosses"]: distance = math.sqrt((boss["coord"][0] - position.x)**2 + (boss["coord"][1] - position.y)**2) if distance <= boss["radius"]: state = _("fighting ") + _( boss["name"]) + " " + _("in ") + _(fractal["name"]) return state else: state = _("in ") + _("fractal") + ": " + _( fractal["name"]) else: return None return state def main_loop(self): def update_gw2_process(): shutdown = False if self.process: if self.process.is_running(): return else: if config.close_with_gw2: shutdown = True try: for process in psutil.process_iter(attrs=['name']): name = process.info['name'] if name in ("Gw2-64.exe", "Gw2.exe"): self.process = process return except psutil.NoSuchProcess: log.debug( "A process exited while iterating over the process list.") pass if shutdown: self.shutdown() self.process = None raise GameNotRunningError def start_rpc(): while True: try: self.rpc.start() break except (FileNotFoundError, PermissionError) as e: time.sleep(10) try: while True: try: update_gw2_process() if not self.game.memfile: self.game.create_map() if not self.rpc.running: start_rpc() log.debug("starting self.rpc") try: data = self.get_activity() except requests.exceptions.ConnectionError: raise GameNotRunningError if not data: data = self.in_character_selection() log.debug(data) try: self.rpc.send_rich_presence(data, self.process.pid) except BrokenPipeError: raise GameNotRunningError # To start a new connection except GameNotRunningError: # TODO self.game.close_map() if self.rpc.running: self.rpc.close() log.debug("Killing RPC") time.sleep(15) except Exception as e: log.critical("GW2RPC has crashed", exc_info=e) create_msgbox( "GW2 Rich Presence has crashed.\nPlease check your " "log file and report this to the author!", code=16) self.shutdown()
def systray_with_infisystray(): from infi.systray import SysTrayIcon menu_options = (("Say Hello", None, say_hello),) systray = SysTrayIcon("icon.ico", "Example tray icon", menu_options) systray.start()
from __future__ import print_function from infi.systray import SysTrayIcon import os import ctypes icon_path = os.path.join(os.path.dirname(__file__), "test.ico") shutdown_called = False def on_quit(systray): print("Bye") def do_example(systray): print("Example") def on_about(systray): ctypes.windll.user32.MessageBoxW(None, u"This is a test of infi.systray", u"About", 0) menu_options = (("Example", None, do_example), ("About", None, on_about)) systray = SysTrayIcon(icon_path, "Systray Test", menu_options, on_quit) systray.start()
class DesktopUtils: VERSION = "1.0" def __init__(self): self.widgets = {} self.running_gui = False self.refresh_thread = threading.Thread(target=self.refresh_widgets) menu = ( ("Open", "./img/icon.ico", self._open_gui), ("Exit", "./img/x.ico", self.__shutdown), ) self.stray = SysTrayIcon("./img/icon.ico", "DeskopUtils", menu_options=menu) def __shutdown(self, *_): def stop(): DestroyWindow(self.stray._hwnd) for widget in self.widgets: instance = self.widgets[widget]["instance"] try: instance.root.destroy() except RuntimeError: pass t = threading.Thread(target=stop) t.start() self.refresh_thread.join() sys.exit(0) def _run_stray(self): self.stray.start() def _open_gui(self, _): if self.running_gui: return self.running_gui = True root = tk.Tk() root.geometry("500x500") root.title("DesktopUtils V" + self.VERSION) def close_win(): root.destroy() self.running_gui = False self._run_stray() root.protocol("WM_DELETE_WINDOW", close_win) root.mainloop() def create_root(self, widget) -> tk.Tk: root = tk.Tk() root.overrideredirect(True) root.wm_resizable(*widget.RESIZE) root.geometry(f"{widget.SIZE[0]}x{widget.SIZE[1]}+{widget.START_POS[0]}+{widget.START_POS[1]}") return root def add_widget(self, info, widget): generalInfo = info.copy() generalInfo.pop("NAME") self.widgets[info["NAME"]] = { "information": generalInfo, "plugin": widget } def refresh_widgets(self): time.sleep(10) while True: for widget in self.widgets: if self.widgets[widget]["instance"].REFRESH: self.widgets[widget]["instance"].refresh() time.sleep(20) def _create_bar(self, root, w_): top_bar = tk.Frame(root, bg=w_.BAR_COLOR, height=25, width=200) xImage = tk.PhotoImage(file="./img/x.png") x = tk.Button(top_bar, image=xImage, borderwidth=0, highlightthickness=0, command=w_.quit) x.photo = xImage x.place(x=180, y=2) top_bar.bind("<ButtonRelease-1>", getattr(w_, "_Widget__button_release")) top_bar.bind("<ButtonPress-1>", getattr(w_, "_Widget__button_press")) top_bar.bind("<B1-Motion>", getattr(w_, "_Widget__move_window")) top_bar.pack(side=tk.TOP, anchor=tk.E) return top_bar def run(self): pluginPaths = next(os.walk("./plugins/"))[1] for folderName in pluginPaths: path = "./plugins/" + folderName try: with open(path + "/info.meda", "r") as fh: infoMeDa = fh.readlines() except FileNotFoundError: print("Could not load " + folderName + ". info.meda is missing!") continue pluginInfo = { "NAME": None, "AUTHOR": None, "DESCRIPTION": None, "VERSION": None, "MAIN": None, "CLASS": None } for line in infoMeDa: key, value = line.split("=") if key in pluginInfo: pluginInfo[key] = value.replace("\n", "").replace(" ", "_") assert pluginInfo["MAIN"] is not None path = path[2:].replace("/", ".") main_filename = pluginInfo["MAIN"].replace(".py", "") main_ = importlib.import_module(".." + main_filename, path) class_ = getattr(main_, pluginInfo["CLASS"]) self.add_widget(pluginInfo, class_) self._run_stray() self.refresh_thread.start() for widget in self.widgets: root = self.create_root(self.widgets[widget]["plugin"]) w = self.widgets[widget]["plugin"](root) self.widgets[widget]["instance"] = w self._create_bar(root, w) w.run()
class SysTrayPlugin(plugins.SimplePlugin): ''' CherryPy plugin that creates a system tray icon for Windows. Because SysTrayIcon always fires off on_quit, we can't have on_quit execute cherrypy.engine.exit() if the exit command is what triggered SysTrayIcon to close. So conditions are set to only fire on_quit when the quit_method == 'menu'. This way, when the menu option is called, it destroys SysTrayIcon then closes cherrypy. Cherrypy will try to close SysTrayIcon by calling stop(), so stop() gets reassigned to None. If the app is closed by cherrypy (whether catching a kb interrupt or the GUI shutdown button), cherrypy stops the plugin by calling stop(). Stop() reassigns SysTrayIcon._on_quit to None and calls SysTrayIcon.shutdown(). SysTrayIcon is then destroyed (twice for reasons I can't figure out), then cherrypy finishes up the engine.stop() and engine.exit(). The chain is as such: Trigger == systray menu 'Quit': SysTrayIcon._destroy() > SysTrayIcon._on_quit() > set SysTrayPlugin.quit_method = 'menu' cherrypy.engine.exit() > SysTrayPlugin.stop() > does nothing sys.exit() Trigger == KBInterrupt or GUI Shutdown: cherrypy.engine.stop() > SysTrayPlugin.stop() > disable SysTrayIcon._on_quit() SysTrayIcon.shutdown() > SysTrayIcon._destroy() > SysTrayIcon._destroy() > cherrypy.engine.exit() > sys.exit() ''' def __init__(self, bus): plugins.SimplePlugin.__init__(self, bus) menu_options = (('Open Browser', None, self.open),) self.systray = SysTrayIcon('core/favicon.ico', 'Watcher', menu_options, on_quit=self.on_quit) self.quit_method = None return def start(self): self.systray.start() return def stop(self): if self.quit_method == 'menu': return else: self.systray._on_quit = None self.systray.shutdown() return def on_quit(self, systray): self.quit_method = 'menu' cherrypy.engine.exit() sys.exit(0) # sys tray functions: def open(self, systray): webbrowser.open(u'http://{}:{}{}'.format( core.SERVER_ADDRESS, core.SERVER_PORT, core.URL_BASE)) return
if Var_name.strip()==variable.strip(): return Var_path return "" if __name__ == "__main__": menu_options = (("Buona notte", None, say_hello),) # creazione menu icona systray = SysTrayIcon(".\icon.ico", "Polito Materiale", menu_options) # creazione icona applicazione checkConfig() print() # Creo la sessione. systray.start() printy("PoliTo Materiale - v 1.2.0\n", "b") sess = PolitoWeb() # Imposto la cartella di download di default home = os.path.expanduser('~') if sys.platform.startswith('win'): path_materiale=getVar("Percorso-Materiale") sess.set_dl_folder(path_materiale) else: sess.set_dl_folder(home + "/polito-materiale") # Togliere il commento dalla riga seguente e modificarlo nel caso si volesse settare # una cartella per il download diversa da quella di default
class GW2RPC: def __init__(self): def fetch_registry(): url = GW2RPC_BASE_URL + "registry" res = requests.get(url) if res.status_code != 200: log.error("Could not fetch the web registry") return None return res.json() def icon_path(): try: return os.path.join(sys._MEIPASS, "icon.ico") except: return "icon.ico" def fetch_support_invite(): try: return requests.get(GW2RPC_BASE_URL + "support").json()["support"] except: return None self.rpc = DiscordRPC(GW2RPC_APP_ID) self.game = MumbleData() self.registry = fetch_registry() self.support_invite = fetch_support_invite() menu_options = (("About", None, self.about), ) if self.support_invite: menu_options += (("Join support server", None, self.join_guild), ) self.systray = SysTrayIcon(icon_path(), "Guild Wars 2 with Discord", menu_options, on_quit=self.shutdown) self.systray.start() self.process = None self.check_for_updates() def shutdown(self, _=None): os._exit(0) # Nuclear option def about(self, _): message = ( "Version: {}\n\nhttps://gw2rpc.info\n\nBy Maselkov & " "N1TR0\nIcons by Zebban\nWebsite by Penemue".format(VERSION)) threading.Thread(target=create_msgbox, args=[message]).start() def join_guild(self, _): try: webbrowser.open(self.support_invite) except: pass def check_for_updates(self): def get_build(): url = GW2RPC_BASE_URL + "build" r = requests.get(url) try: return r.json()["build"] except: return None build = get_build() if not build: log.error("Could not retrieve build!") create_msgbox( "Could not check for updates - check your connection!") return if build > VERSION: log.info("New version found! Current: {} New: {}".format( VERSION, build)) res = create_msgbox( "There is a new update for GW2 Rich Presence available. " "Would you like to be taken to the download page now?", code=68) if res == 6: webbrowser.open("https://gw2rpc.info/") def update_gw2_process(self): if self.process: if self.process.is_running(): return else: if config.close_with_gw2: self.shutdown() for pid in psutil.pids(): try: p = psutil.Process(pid) pname = p.name() if pname == "Gw2-64.exe" or pname == "Gw2.exe": self.process = p return except: pass self.process = None def get_map_asset(self, map_info): def get_region(): world = api.world if world: for k, v in worlds.items(): if world in v: return " [{}]".format(k) return "" map_id = map_info["id"] map_name = map_info["name"] region = map_info.get("region_name", "thanks_anet") if self.registry: if map_name == "Fractals of the Mists": for fractal in self.registry["fractals"]: if fractal["id"] == map_id: state = fractal["name"] + " fractal" image = "fotm" break else: image = "fotm" state = "Fractals of the Mists" name = "Fractals of the Mists" else: if map_name in self.registry["special"]: image = self.registry["special"][map_name] elif map_id in self.registry["valid"]: image = map_id elif region in self.registry["regions"]: image = self.registry["regions"][region] else: image = "default" name = map_name state = name else: special = { "Fractals of the Mists": "fotm", "Windswept Haven": "gh_haven", "Gilded Hollow": "gh_hollow", "Lost Precipice": "gh_precipice" }.get(map_info["name"]) if special: return special if map_info["type"] == "Public": image = map_id else: valid_ids = [1062, 1149, 1156, 38, 1264] if map_id in valid_ids: image = map_id else: image = "default" name = map_name state = name return "in " + state, { "large_image": str(image), "large_text": name + get_region() } def get_activity(self): data = self.game.get_mumble_data() if not data: return None current_time = time.time() map_id = data["map_id"] try: map_info = api.get_map_info(map_id) character = Character(data) except APIError: log.exception("API Error!") return None state, map_asset = self.get_map_asset(map_info) if config.display_tag: tag = character.guild_tag else: tag = "" activiy = { "state": state, "details": character.name + tag, "timestamps": { 'start': int(current_time) }, "assets": { **map_asset, "small_image": character.profession_icon, "small_text": "{0.race} {0.profession}".format(character) } } return activiy def in_character_selection(self): current_time = time.time() activity = { "state": "in character selection", "timestamps": { 'start': int(current_time) }, "assets": { "large_image": "default", "large_text": "Character Selection", "small_image": "gw2rpclogo", "small_text": "GW2RPC Version {}\nhttps://gw2rpc.info".format(VERSION) } } return activity def main_loop(self): def gw2_running(): if not self.process: return False if not self.process.is_running(): return False return True def start_rpc(): while True: try: self.rpc.start() break except (FileNotFoundError, PermissionError) as e: time.sleep(10) try: while True: try: self.update_gw2_process() if not gw2_running(): raise GameNotRunningError if not self.rpc.running: start_rpc() log.debug("starting self.rpc") data = self.get_activity() if not data: data = self.in_character_selection() log.debug(data) try: self.rpc.send_rich_presence(data, self.process.pid) except BrokenPipeError: raise GameNotRunningError # To start a new connection except GameNotRunningError: # TODO if self.rpc.running: self.rpc.last_payload = {} self.rpc.last_pid = None self.rpc.last_update = time.time() self.game.last_character = None self.game.last_map_id = None self.rpc.close() log.debug("Killing RPC") except DataUnchangedError: pass time.sleep(config.update_frequency) except Exception as e: log.critical("GW2RPC has crashed", exc_info=e) create_msgbox( "GW2 Rich Presence has crashed.\nPlease check your " "log file and report this to the author!", code=16) self.shutdown()