def __init__(self, profile_file): if not wpproc.RESOLUTION_ARRAY: msg = "Cannot parse profile, monitor resolution data is missing." show_message_dialog(msg) sp_logging.G_LOGGER(msg) exit() self.file = profile_file self.name = "default_profile" self.spanmode = "single" # single / multi self.slideshow = True self.delay_list = [600] self.sortmode = "shuffle" # shuffle ( random , sorted? ) self.ppimode = False self.ppi_array = wpproc.NUM_DISPLAYS * [100] self.ppi_array_relative_density = [] self.inches = [] self.manual_offsets = wpproc.NUM_DISPLAYS * [(0, 0)] self.manual_offsets_useronly = [] self.bezels = [] self.bezel_px_offsets = [] self.hk_binding = None self.paths_array = [] self.parse_profile(self.file) if self.ppimode is True: self.compute_relative_densities() if self.bezels: self.compute_bezel_px_offsets() self.file_handler = self.Filehandler(self.paths_array, self.sortmode)
def __init__(self, paths_array, sortmode): # A list of lists if there is more than one monitor with distinct # input paths. self.all_files_in_paths = [] self.paths_array = paths_array self.sortmode = sortmode for paths_list in paths_array: list_of_images = [] for path in paths_list: # Add list items to the end of the list instead of # appending the list to the list. if not os.path.exists(path): message = "A path was not found: '{}'.\n\ Use absolute paths for best reliabilty.".format(path) sp_logging.G_LOGGER.info(message) show_message_dialog(message, "Error") continue else: # List only images that are of supported type. list_of_images += [ os.path.join(path, f) for f in os.listdir(path) if f.endswith(wpproc.G_SUPPORTED_IMAGE_EXTENSIONS) ] # Append the list of monitor_i specific files to the list of # lists of images. self.all_files_in_paths.append(list_of_images) self.iterators = [] for diplay_image_list in self.all_files_in_paths: self.iterators.append( self.ImageList(diplay_image_list, self.sortmode))
def read_general_settings(self): """Refreshes general settings from file and applies hotkey bindings.""" self.g_settings = GeneralSettingsData() self.register_hotkeys() msg = "New settings are applied after an application restart. \ New hotkeys are registered." show_message_dialog(msg, "Info")
def read_general_settings(self): """Refreshes general settings from file and applies hotkey bindings.""" self.g_settings = GeneralSettingsData() try: self.seen_binding except NameError: self.seen_binding = set() self.register_hotkeys() if self.g_settings.logging: msg = "Logging is enabled after an application restart." show_message_dialog(msg, "Info")
def is_list_valid_paths(self, input_list): """Verifies that input list contains paths and that they're valid.""" if input_list == [""]: msg = "At least one path for wallpapers must be given." show_message_dialog(msg, "Error") return False if "" in input_list: msg = "Take care not to save a profile with an empty display paths field." show_message_dialog(msg, "Error") return False for path_list_str in input_list: path_list = path_list_str.split(";") for path in path_list: if os.path.isdir(path) is True: supported_files = [ f for f in os.listdir(path) if f.endswith(wpproc.G_SUPPORTED_IMAGE_EXTENSIONS) ] if supported_files: continue else: msg = "Path '{}' does not contain supported image files.".format( path) show_message_dialog(msg, "Error") return False else: msg = "Path '{}' was not recognized as a directory.".format( path) show_message_dialog(msg, "Error") return False valid_pathsarray = True return valid_pathsarray
def list_profiles(): """Lists profiles as initiated objects from the sp_paths.PROFILES_PATH.""" files = sorted(os.listdir(sp_paths.PROFILES_PATH)) profile_list = [] for pfle in files: try: if pfle.endswith(".profile"): profile_list.append( ProfileData(os.path.join(sp_paths.PROFILES_PATH, pfle))) except Exception as exep: # TODO implement proper error catching for ProfileData init msg = ( "There was an error when loading profile '{}'.\n".format(pfle) + "Would you like to delete it? Choosing 'No' will just ignore the profile." ) sp_logging.G_LOGGER.info(msg) sp_logging.G_LOGGER.info(exep) res = show_message_dialog(msg, "Error", style="YES_NO") if res: # remove pfle print("removing:", os.path.join(sp_paths.PROFILES_PATH, pfle)) os.remove(os.path.join(sp_paths.PROFILES_PATH, pfle)) continue else: continue return profile_list
def onDeleteProfile(self, event): """Deletes the currently selected profile after getting confirmation.""" profname = self.tc_name.GetLineText(0) fname = PROFILES_PATH + profname + ".profile" file_exists = os.path.isfile(fname) if not file_exists: msg = "Selected profile is not saved." show_message_dialog(msg, "Error") return # Open confirmation dialog dlg = wx.MessageDialog( None, "Do you want to delete profile:" + profname + "?", 'Confirm Delete', wx.YES_NO | wx.ICON_QUESTION) result = dlg.ShowModal() if result == wx.ID_YES and file_exists: os.remove(fname) else: pass
def list_profiles(): """Lists profiles as initiated objects from the sp_paths.PROFILES_PATH.""" files = sorted(os.listdir(sp_paths.PROFILES_PATH)) profile_list = [] for i in range(len(files)): try: profile_list.append( ProfileData(os.path.join(sp_paths.PROFILES_PATH, files[i]))) except Exception as exep: # TODO implement proper error catching for ProfileData init msg = "There was an error when loading profile '{}'. Exiting.".format( files[i]) sp_logging.G_LOGGER.info(msg) sp_logging.G_LOGGER.info(exep) show_message_dialog(msg, "Error") exit() if sp_logging.DEBUG: sp_logging.G_LOGGER.info("Listed profile: %s", profile_list[i].name) return profile_list
def save(self): """Saves the TempProfile into a file.""" if self.name is not None: fname = os.path.join(PROFILES_PATH, self.name + ".profile") try: tpfile = open(fname, "w") except IOError: msg = "Cannot write to file {}".format(fname) show_message_dialog(msg, "Error") return None tpfile.write("name=" + str(self.name) + "\n") if self.spanmode: tpfile.write("spanmode=" + str(self.spanmode) + "\n") if self.spangroups: tpfile.write("spangroups=" + str(self.spangroups) + "\n") if self.slideshow is not None: tpfile.write("slideshow=" + str(self.slideshow) + "\n") if self.delay: tpfile.write("delay=" + str(self.delay) + "\n") if self.sortmode: tpfile.write("sortmode=" + str(self.sortmode) + "\n") if self.inches: tpfile.write("diagonal_inches=" + str(self.inches) + "\n") if self.manual_offsets: tpfile.write("offsets=" + str(self.manual_offsets) + "\n") if self.bezels: tpfile.write("bezels=" + str(self.bezels) + "\n") if self.hk_binding: tpfile.write("hotkey=" + str(self.hk_binding) + "\n") if self.perspective: tpfile.write("perspective=" + str(self.perspective) + "\n") if self.paths_array: for paths in self.paths_array: tpfile.write("display" + str(self.paths_array.index(paths)) + "paths=" + paths + "\n") tpfile.close() return fname else: print("tmp.Save(): name is not set.") return None
def onAlignTest(self, event): """Align test, takes alignment settings from open profile and sets a test image wp.""" # Use the settings currently written out in the fields! testimage = [os.path.join(PATH, "superpaper/resources/test.png")] if not os.path.isfile(testimage[0]): print(testimage) msg = "Test image not found in {}.".format(testimage) show_message_dialog(msg, "Error") ppi = None inches = self.tc_inches.GetLineText(0).split(";") if (inches == "") or (len(inches) < NUM_DISPLAYS): msg = "You must enter a diagonal inch value for every \ display, serparated by a semicolon ';'." show_message_dialog(msg, "Error") # print(inches) inches = [float(i) for i in inches] bezels = self.tc_bez.GetLineText(0).split(";") bezels = [float(b) for b in bezels] offsets = self.tc_offsets.GetLineText(0).split(";") offsets = [[int(i.split(",")[0]), int(i.split(",")[1])] for i in offsets] flat_offsets = [] for off in offsets: for pix in off: flat_offsets.append(pix) # print("flat_offsets= ", flat_offsets) # Use the simplified CLI profile class get_display_data() profile = CLIProfileData( testimage, ppi, inches, bezels, flat_offsets, ) change_wallpaper_job(profile)
def open_config(self, event): """Opens Superpaper config folder, CONFIG_PATH.""" if platform.system() == "Windows": try: os.startfile(sp_paths.CONFIG_PATH) except BaseException: show_message_dialog( "There was an error trying to open the config folder.") elif platform.system() == "Darwin": try: subprocess.check_call(["open", sp_paths.CONFIG_PATH]) except subprocess.CalledProcessError: show_message_dialog( "There was an error trying to open the config folder.") else: try: subprocess.check_call(['xdg-open', sp_paths.CONFIG_PATH]) except subprocess.CalledProcessError: show_message_dialog( "There was an error trying to open the config folder.")
def set_wallpaper_linux(outputfile): """ Wallpaper setter for Linux hosts. Functionality is based on the DESKTOP_SESSION environment variable, if it is not set, like often on window managers such as i3, the default behavior is to attempt to use feh as the communication layer with the desktop. On systems where the variable is set, a native way of setting the wallpaper can be used. These are DE specific. """ file = "file://" + outputfile set_command = G_SET_COMMAND_STRING if sp_logging.DEBUG: sp_logging.G_LOGGER.info(file) desk_env = os.environ.get("DESKTOP_SESSION") if sp_logging.DEBUG: sp_logging.G_LOGGER.info("DESKTOP_SESSION is: '%s'", desk_env) if desk_env: if set_command != "": if set_command == "feh": subprocess.run(["feh", "--bg-scale", "--no-xinerama", outputfile]) else: command_string_list = set_command.split() formatted_command = [] for term in command_string_list: formatted_command.append(term.format(image=outputfile)) sp_logging.G_LOGGER.info("Formatted custom command is: '%s'", formatted_command) subprocess.run(formatted_command) elif desk_env in ["gnome", "gnome-wayland", "unity", "ubuntu", "pantheon", "budgie-desktop"]: subprocess.run(["gsettings", "set", "org.gnome.desktop.background", "picture-uri", file]) elif desk_env in ["cinnamon"]: subprocess.run(["gsettings", "set", "org.cinnamon.desktop.background", "picture-uri", file]) elif desk_env in ["mate"]: subprocess.run(["gsettings", "set", "org.mate.background", "picture-filename", outputfile]) elif desk_env in ["xfce", "xubuntu"]: xfce_actions(outputfile) elif desk_env in ["lubuntu", "Lubuntu"]: try: subprocess.run("pcmanfm", "-w", outputfile) except OSError: try: subprocess.run("pcmanfm-qt", "-w", outputfile) except OSError: sp_logging.G_LOGGER.info("Exception: failure to find either command \ 'pcmanfm' or 'pcmanfm-qt'. Exiting.") sys.exit(1) elif desk_env in ["/usr/share/xsessions/plasma", "plasma"]: kdeplasma_actions(outputfile) elif "i3" in desk_env or desk_env in ["/usr/share/xsessions/bspwm"]: subprocess.run(["feh", "--bg-scale", "--no-xinerama", outputfile]) else: if set_command == "": message = "Your DE could not be detected to set the wallpaper. \ You need to set the 'set_command' option in your \ settings file superpaper/general_settings. Exiting." sp_logging.G_LOGGER.info(message) show_message_dialog(message, "Error") sys.exit(1) else: os.system(set_command.format(image=outputfile)) else: sp_logging.G_LOGGER.info("DESKTOP_SESSION variable is empty, \ attempting to use feh to set the wallpaper.") subprocess.run(["feh", "--bg-scale", "--no-xinerama", outputfile])
def register_hotkeys(self): """Registers system-wide hotkeys for profiles and application interaction.""" if self.g_settings.use_hotkeys is True: try: # import keyboard # https://github.com/boppreh/keyboard # This import allows access to the specific errors in this method. from system_hotkey import (SystemHotkey, SystemHotkeyError, SystemRegisterError, UnregisterError, InvalidKeyError) except ImportError as import_e: sp_logging.G_LOGGER.info( "WARNING: Could not import keyboard hotkey hook library, \ hotkeys will not work. Exception: %s", import_e) if "system_hotkey" in sys.modules: try: # Keyboard bindings: https://github.com/boppreh/keyboard # # Alternative KB bindings for X11 systems and Windows: # system_hotkey https://github.com/timeyyy/system_hotkey # seen_binding = set() # self.hk = SystemHotkey(check_queue_interval=0.05) # self.hk2 = SystemHotkey( # consumer=self.profile_consumer, # check_queue_interval=0.05) # Unregister previous hotkeys if self.seen_binding: for binding in self.seen_binding: try: self.hk.unregister(binding) if sp_logging.DEBUG: sp_logging.G_LOGGER.info( "Unreg hotkey %s", binding) except (SystemHotkeyError, UnregisterError, InvalidKeyError): try: self.hk2.unregister(binding) if sp_logging.DEBUG: sp_logging.G_LOGGER.info( "Unreg hotkey %s", binding) except (SystemHotkeyError, UnregisterError, InvalidKeyError): if sp_logging.DEBUG: sp_logging.G_LOGGER.info( "Could not unreg hotkey '%s'", binding) self.seen_binding = set() # register general bindings if self.g_settings.hk_binding_next not in self.seen_binding: try: self.hk.register(self.g_settings.hk_binding_next, callback=lambda x: self. next_wallpaper(wx.EVT_MENU), overwrite=False) self.seen_binding.add( self.g_settings.hk_binding_next) except (SystemHotkeyError, SystemRegisterError, InvalidKeyError): msg = "Error: could not register hotkey {}. \ Check that it is formatted properly and valid keys.".format( self.g_settings.hk_binding_next) sp_logging.G_LOGGER.info(msg) sp_logging.G_LOGGER.info(sys.exc_info()[0]) show_message_dialog(msg, "Error") if self.g_settings.hk_binding_pause not in self.seen_binding: try: self.hk.register(self.g_settings.hk_binding_pause, callback=lambda x: self. pause_timer(wx.EVT_MENU), overwrite=False) self.seen_binding.add( self.g_settings.hk_binding_pause) except (SystemHotkeyError, SystemRegisterError, InvalidKeyError): msg = "Error: could not register hotkey {}. \ Check that it is formatted properly and valid keys.".format( self.g_settings.hk_binding_pause) sp_logging.G_LOGGER.info(msg) sp_logging.G_LOGGER.info(sys.exc_info()[0]) show_message_dialog(msg, "Error") # try: # self.hk.register(('control', 'super', 'shift', 'q'), # callback=lambda x: self.on_exit(wx.EVT_MENU)) # except (SystemHotkeyError, SystemRegisterError, InvalidKeyError): # pass # register profile specific bindings self.list_of_profiles = list_profiles() for profile in self.list_of_profiles: if sp_logging.DEBUG: sp_logging.G_LOGGER.info( "Registering binding: \ %s for profile: %s", profile.hk_binding, profile.name) if (profile.hk_binding is not None and profile.hk_binding not in self.seen_binding): try: self.hk2.register(profile.hk_binding, profile, overwrite=False) self.seen_binding.add(profile.hk_binding) except (SystemHotkeyError, SystemRegisterError, InvalidKeyError): msg = "Error: could not register hotkey {}. \ Check that it is formatted properly and valid keys.".format(profile.hk_binding) sp_logging.G_LOGGER.info(msg) sp_logging.G_LOGGER.info(sys.exc_info()[0]) show_message_dialog(msg, "Error") elif profile.hk_binding in self.seen_binding: msg = "Could not register hotkey: '{}' for profile: '{}'.\n\ It is already registered for another action.".format(profile.hk_binding, profile.name) sp_logging.G_LOGGER.info(msg) show_message_dialog(msg, "Error") except (SystemHotkeyError, SystemRegisterError, UnregisterError, InvalidKeyError): if sp_logging.DEBUG: sp_logging.G_LOGGER.info( "Coulnd't register hotkeys, exception:") sp_logging.G_LOGGER.info(sys.exc_info()[0])
def test_save(self): """Tests whether the user input for profile settings is valid.""" valid_profile = False if self.name is not None and self.name.strip() is not "": fname = os.path.join(PROFILES_PATH, self.name + ".deleteme") try: testfile = open(fname, "w") testfile.close() os.remove(fname) except IOError: msg = "Cannot write to file {}".format(fname) show_message_dialog(msg, "Error") return False if self.spanmode == "single": if len(self.paths_array) > 1: msg = "When spanning a single image across all monitors, \ only one paths field is needed." show_message_dialog(msg, "Error") return False if self.spanmode == "multi": if len(self.paths_array) < 2: msg = "When setting a different image on every display, \ each display needs its own paths field." show_message_dialog(msg, "Error") return False if self.slideshow is True and not self.delay: msg = "When using slideshow you need to enter a delay." show_message_dialog(msg, "Info") return False if self.delay: try: val = int(self.delay) if val < 20: msg = "It is advisable to set the slideshow delay to \ be at least 20 seconds due to the time the image processing takes." show_message_dialog(msg, "Info") return False except ValueError: msg = "Slideshow delay must be an integer of seconds." show_message_dialog(msg, "Error") return False # if self.sortmode: # No test needed if self.inches: if self.is_list_float(self.inches): pass else: msg = "Display diagonals must be given in numeric values \ using decimal point and separated by semicolon ';'." show_message_dialog(msg, "Error") return False if self.manual_offsets: if self.is_list_offsets(self.manual_offsets): pass else: msg = "Display offsets must be given in width,height pixel \ pairs and separated by semicolon ';'." show_message_dialog(msg, "Error") return False if self.bezels: if self.is_list_float(self.bezels): if self.manual_offsets: if len(self.manual_offsets.split(";")) < len( self.bezels.split(";")): msg = "When using both offset and bezel \ corrections, take care to enter an offset for each display that you \ enter a bezel thickness." show_message_dialog(msg, "Error") return False else: pass else: pass else: msg = "Display bezels must be given in millimeters using \ decimal point and separated by semicolon ';'." show_message_dialog(msg, "Error") return False if self.hk_binding: if self.is_valid_hotkey(self.hk_binding): pass else: msg = "Hotkey must be given as 'mod1+mod2+mod3+key'. \ Valid modifiers are 'control', 'super', 'alt', 'shift'." show_message_dialog(msg, "Error") return False if self.paths_array: if self.is_list_valid_paths(self.paths_array): pass else: # msg = "Paths must be separated by a semicolon ';'." # show_message_dialog(msg, "Error") return False else: msg = "You must enter at least one path for images." show_message_dialog(msg, "Error") return False # Passed all tests. valid_profile = True return valid_profile else: print("tmp.Save(): name is not set.") msg = "You must enter a name for the profile." show_message_dialog(msg, "Error") return False
def is_list_valid_paths(self, input_list): """Verifies that input list contains paths and that they're valid.""" if input_list == [""]: msg = "At least one path for wallpapers must be given." show_message_dialog(msg, "Error") return False if "" in input_list: msg = "Add an image source for every display present." show_message_dialog(msg, "Error") return False if self.spangroups: num_groups = len(self.spangroups.split(',')) if len(input_list) < num_groups: msg = "Add an image source for every span group." show_message_dialog(msg, "Error") return False for path_list_str in input_list: path_list = path_list_str.split(";") for path in path_list: if os.path.isdir(path) is True: supported_files = [ f for f in os.listdir(path) if f.endswith(wpproc.G_SUPPORTED_IMAGE_EXTENSIONS) ] if supported_files: continue else: msg = "Path '{}' does not contain supported image files.".format( path) show_message_dialog(msg, "Error") return False elif os.path.isfile(path) is True: if path.endswith(wpproc.G_SUPPORTED_IMAGE_EXTENSIONS): continue else: msg = "Image '{}' is not a supported image file.".format( path) show_message_dialog(msg, "Error") return False else: msg = "Path '{}' was not recognized as a directory.".format( path) show_message_dialog(msg, "Error") return False valid_pathsarray = True return valid_pathsarray