1#!/usr/local/bin/python3.8 2 3from gi.repository import Gio, GLib 4import dbus, dbus.service, dbus.glib 5from dbus.mainloop.glib import DBusGMainLoop 6import random 7import os, locale 8from xml.etree import ElementTree 9from setproctitle import setproctitle 10 11SLIDESHOW_DBUS_NAME = "org.Cinnamon.Slideshow" 12SLIDESHOW_DBUS_PATH = "/org/Cinnamon/Slideshow" 13 14BACKGROUND_COLLECTION_TYPE_DIRECTORY = "directory" 15BACKGROUND_COLLECTION_TYPE_XML = "xml" 16 17class CinnamonSlideshow(dbus.service.Object): 18 def __init__(self): 19 bus_name = dbus.service.BusName(SLIDESHOW_DBUS_NAME, bus=dbus.SessionBus()) 20 dbus.service.Object.__init__(self, bus_name, SLIDESHOW_DBUS_PATH) 21 22 self.slideshow_settings = Gio.Settings(schema="org.cinnamon.desktop.background.slideshow") 23 self.background_settings = Gio.Settings(schema="org.cinnamon.desktop.background") 24 25 if self.slideshow_settings.get_boolean("slideshow-paused"): 26 self.slideshow_settings.set_boolean("slideshow-paused", False) 27 28 self.image_playlist = [] 29 self.used_image_playlist = [] 30 self.images_ready = False 31 self.update_in_progress = False 32 self.current_image = self.background_settings.get_string("picture-uri") 33 34 self.update_id = 0 35 self.loop_counter = self.slideshow_settings.get_int("delay") 36 37 self.folder_monitor = None 38 self.folder_monitor_id = 0 39 40 @dbus.service.method(SLIDESHOW_DBUS_NAME, in_signature='', out_signature='') 41 def begin(self): 42 self.setup_slideshow() 43 44 @dbus.service.method(SLIDESHOW_DBUS_NAME, in_signature='', out_signature='') 45 def end(self): 46 if self.update_id > 0: 47 GLib.source_remove(self.update_id) 48 self.update_id = 0 49 50 ml.quit() 51 52 @dbus.service.method(SLIDESHOW_DBUS_NAME, in_signature='', out_signature='') 53 def getNextImage(self): 54 if self.update_id > 0: 55 GLib.source_remove(self.update_id) 56 self.update_id = 0 57 58 self.loop_counter = self.slideshow_settings.get_int("delay") 59 self.start_mainloop() 60 61 def setup_slideshow(self): 62 self.load_settings() 63 self.connect_signals() 64 self.gather_images() 65 if self.collection_type == BACKGROUND_COLLECTION_TYPE_DIRECTORY: 66 self.connect_folder_monitor() 67 self.start_mainloop() 68 69 def format_source(self, type, path): 70 # returns 'type://path' 71 return ("%s://%s" % (type, path)) 72 73 def load_settings(self): 74 self.random_order = self.slideshow_settings.get_boolean("random-order") 75 self.collection = self.slideshow_settings.get_string("image-source") 76 self.collection_path = "" 77 self.collection_type = None 78 if self.collection != "" and "://" in self.collection: 79 (self.collection_type, self.collection_path) = self.collection.split("://") 80 self.collection_path = os.path.expanduser(self.collection_path) 81 82 def connect_signals(self): 83 self.slideshow_settings.connect("changed::image-source", self.on_slideshow_source_changed) 84 self.slideshow_settings.connect("changed::random-order", self.on_random_order_changed) 85 self.background_settings.connect("changed::picture-uri", self.on_picture_uri_changed) 86 87 def connect_folder_monitor(self): 88 folder_path = Gio.file_new_for_path(self.collection_path) 89 self.folder_monitor = folder_path.monitor_directory(0, None) 90 self.folder_monitor_id = self.folder_monitor.connect("changed", self.on_monitored_folder_changed) 91 92 def disconnect_folder_monitor(self): 93 if self.folder_monitor_id > 0: 94 self.folder_monitor.disconnect(self.folder_monitor_id) 95 self.folder_monitor_id = 0 96 97 def gather_images(self): 98 if self.collection_type == BACKGROUND_COLLECTION_TYPE_DIRECTORY: 99 folder_at_path = Gio.file_new_for_path(self.collection_path) 100 101 if folder_at_path.query_exists(None): 102 folder_at_path.enumerate_children_async("standard::name,standard::type,standard::content-type", 103 Gio.FileQueryInfoFlags.NONE, 104 GLib.PRIORITY_LOW, 105 None, 106 self.gather_images_cb, 107 None) 108 109 elif self.collection_type == BACKGROUND_COLLECTION_TYPE_XML: 110 pictures = self.parse_xml_backgrounds_list(self.collection_path) 111 for picture in pictures: 112 filename = picture["filename"] 113 self.add_image_to_playlist(filename) 114 115 def gather_images_cb(self, obj, res, user_data): 116 all_files = [] 117 enumerator = obj.enumerate_children_finish(res) 118 def on_next_file_complete(obj, res, user_data=all_files): 119 files = obj.next_files_finish(res) 120 file_list = all_files 121 if len(files) != 0: 122 file_list = file_list.extend(files) 123 enumerator.next_files_async(100, GLib.PRIORITY_LOW, None, on_next_file_complete, None) 124 else: 125 enumerator.close(None) 126 self.ensure_file_is_image(file_list) 127 128 enumerator.next_files_async(100, GLib.PRIORITY_LOW, None, on_next_file_complete, all_files) 129 130 def ensure_file_is_image(self, file_list): 131 for item in file_list: 132 file_type = item.get_file_type(); 133 if file_type is not Gio.FileType.DIRECTORY: 134 file_contents = item.get_content_type(); 135 if file_contents.startswith("image"): 136 self.add_image_to_playlist(self.collection_path + "/" + item.get_name()) 137 138 def add_image_to_playlist(self, file_path): 139 image = Gio.file_new_for_path(file_path) 140 image_uri = image.get_uri(); 141 self.image_playlist.append(image_uri) 142 if self.collection_type == BACKGROUND_COLLECTION_TYPE_DIRECTORY: 143 self.image_playlist.sort() 144 self.images_ready = True 145 146 def on_slideshow_source_changed(self, settings, key): 147 if self.update_id > 0: 148 GLib.source_remove(self.update_id) 149 self.update_id = 0 150 self.disconnect_folder_monitor() 151 self.image_playlist = [] 152 self.used_image_playlist = [] 153 self.images_ready = False 154 self.collection = self.slideshow_settings.get_string("image-source") 155 self.collection_path = "" 156 self.collection_type = None 157 if self.collection != "" and "://" in self.collection: 158 (self.collection_type, self.collection_path) = self.collection.split("://") 159 self.collection_path = os.path.expanduser(self.collection_path) 160 if self.collection_type == BACKGROUND_COLLECTION_TYPE_DIRECTORY: 161 self.connect_folder_monitor() 162 self.gather_images() 163 self.loop_counter = self.slideshow_settings.get_int("delay") 164 self.start_mainloop() 165 166 def on_monitored_folder_changed(self, monitor, file1, file2, event_type): 167 try: 168 if event_type == Gio.FileMonitorEvent.DELETED: 169 file_uri = file1.get_uri(); 170 if self.image_playlist.count(file_uri) > 0: 171 index_to_remove = self.image_playlist.index(file_uri) 172 del self.image_playlist[index_to_remove] 173 elif self.used_image_playlist.count(file_uri) > 0: 174 index_to_remove = self.used_image_playlist.index(file_uri) 175 del self.used_image_playlist[index_to_remove] 176 177 if event_type == Gio.FileMonitorEvent.CREATED: 178 file_path = file1.get_path() 179 file_info = file1.query_info("standard::type,standard::content-type", Gio.FileQueryInfoFlags.NONE, None) 180 file_type = file_info.get_file_type() 181 if file_type is not Gio.FileType.DIRECTORY: 182 file_contents = file_info.get_content_type(); 183 if file_contents.startswith("image"): 184 self.add_image_to_playlist(file_path) 185 except: 186 pass 187 188 def on_random_order_changed(self, settings, key): 189 self.random_order = self.slideshow_settings.get_boolean("random-order") 190 191 def on_picture_uri_changed(self, settings, key): 192 if self.update_in_progress: 193 return 194 else: 195 if self.background_settings.get_string("picture-uri") != self.current_image: 196 self.slideshow_settings.set_boolean("slideshow-enabled", False) 197 198 def start_mainloop(self): 199 if self.update_id > 0: 200 GLib.source_remove(self.update_id) 201 self.update_id = 0 202 203 if not self.images_ready: 204 self.update_id = GLib.timeout_add_seconds(1, self.start_mainloop) 205 else: 206 if self.loop_counter >= self.slideshow_settings.get_int("delay") and not self.slideshow_settings.get_boolean("slideshow-paused"): 207 self.loop_counter = 1 208 self.update_background() 209 self.update_id = GLib.timeout_add_seconds(60, self.start_mainloop) 210 else: 211 self.loop_counter = self.loop_counter + 1 212 self.update_id = GLib.timeout_add_seconds(60, self.start_mainloop) 213 214 def update_background(self): 215 if self.update_in_progress: 216 return 217 218 self.update_in_progress = True 219 220 if len(self.image_playlist) == 0: 221 self.move_used_images_to_original_playlist() 222 223 next_image = self.get_next_image_from_list() 224 if next_image is not None: 225 self.background_settings.set_string("picture-uri", next_image) 226 self.current_image = next_image 227 228 self.update_in_progress = False 229 230 def get_next_image_from_list(self): 231 if self.random_order: 232 index = random.randint(0, len(self.image_playlist) - 1) 233 image = self.image_playlist[index] 234 else: 235 index = 0 236 image = self.image_playlist[index] 237 238 self.move_image_to_used_playlist(index, image) 239 240 return image 241 242 def move_image_to_used_playlist(self, index, image): 243 self.image_playlist.pop(index) 244 self.used_image_playlist.append(image) 245 246 def move_used_images_to_original_playlist(self): 247 self.image_playlist = self.used_image_playlist 248 if self.collection_type == BACKGROUND_COLLECTION_TYPE_DIRECTORY: 249 self.image_playlist.sort() 250 self.used_image_playlist = [] 251 252 253########### TAKEN FROM CS_BACKGROUND 254 def splitLocaleCode(self, localeCode): 255 loc = localeCode.partition("_") 256 loc = (loc[0], loc[2]) 257 return loc 258 259 def getLocalWallpaperName(self, names, loc): 260 result = "" 261 mainLocFound = False 262 for wp in names: 263 wpLoc = wp[0] 264 wpName = wp[1] 265 if wpLoc == ("", ""): 266 if not mainLocFound: 267 result = wpName 268 elif wpLoc[0] == loc[0]: 269 if wpLoc[1] == loc[1]: 270 return wpName 271 elif wpLoc[1] == "": 272 result = wpName 273 mainLocFound = True 274 return result 275 276 def parse_xml_backgrounds_list(self, filename): 277 try: 278 locAttrName = "{http://www.w3.org/XML/1998/namespace}lang" 279 loc = self.splitLocaleCode(locale.getdefaultlocale()[0]) 280 res = [] 281 subLocaleFound = False 282 f = open(filename) 283 rootNode = ElementTree.fromstring(f.read()) 284 f.close() 285 if rootNode.tag == "wallpapers": 286 for wallpaperNode in rootNode: 287 if wallpaperNode.tag == "wallpaper" and wallpaperNode.get("deleted") != "true": 288 wallpaperData = {"metadataFile": filename} 289 names = [] 290 for prop in wallpaperNode: 291 if type(prop.tag) == str: 292 if prop.tag != "name": 293 wallpaperData[prop.tag] = prop.text 294 else: 295 propAttr = prop.attrib 296 wpName = prop.text 297 locName = self.splitLocaleCode(propAttr.get(locAttrName)) if locAttrName in propAttr else ("", "") 298 names.append((locName, wpName)) 299 wallpaperData["name"] = self.getLocalWallpaperName(names, loc) 300 301 if "filename" in wallpaperData and wallpaperData["filename"] != "" and os.path.exists(wallpaperData["filename"]) and os.access(wallpaperData["filename"], os.R_OK): 302 if wallpaperData["name"] == "": 303 wallpaperData["name"] = os.path.basename(wallpaperData["filename"]) 304 res.append(wallpaperData) 305 return res 306 except Exception as detail: 307 print(detail) 308 return [] 309############### 310 311if __name__ == "__main__": 312 setproctitle("cinnamon-slideshow") 313 DBusGMainLoop(set_as_default=True) 314 315 sessionBus = dbus.SessionBus () 316 request = sessionBus.request_name(SLIDESHOW_DBUS_NAME, dbus.bus.NAME_FLAG_DO_NOT_QUEUE) 317 if request != dbus.bus.REQUEST_NAME_REPLY_EXISTS: 318 slideshow = CinnamonSlideshow() 319 else: 320 print("cinnamon-slideshow already running.") 321 quit() 322 323 ml = GLib.MainLoop.new(None, True) 324 ml.run() 325