Esempio n. 1
0
async def api_call(obj_name, obj_id, method='', errcount=0, **params):
    r = await request_get(f'{api_url}/{obj_name}/{obj_id}/{method}',
                          params=params)
    obj = AttrDict(await r.json())
    if obj.error:
        if errcount > 2:
            raise ValueError(f'error getting object {obj}_{obj_id}'
                             f'\n{r.url}')

        url = f'https://deezer.com/{obj_name}/{obj_id}/'
        r = await request_get(url)
        new_id = str(r.url).split('/')[-1]
        return await api_call(obj_name, new_id, method, errcount + 1, **params)

    if obj.get('data'):
        obj = obj['data']
        if not len(obj):
            return []
    if isinstance(obj, list):
        return obj
    return AttrDict(obj)
class VarietySlideshow(Gtk.Window):
    def __init__(self):
        super(Gtk.Window, self).__init__()

    def current_monitors_help(self):
        result = 'Your current monitors are: '
        screen = Gdk.Screen.get_default()
        for i in range(0, screen.get_n_monitors()):
            geo = screen.get_monitor_geometry(i)
            result += (', ' if i > 0 else '') + '%d - %s, %dx%d' % (
                i + 1, screen.get_monitor_plug_name(i), geo.width, geo.height)
        return result

    def load_options(self):
        if '--defaults' in sys.argv:
            self.options = AttrDict()
            return

        try:
            with io.open(os.path.expanduser(u'~/.config/variety/variety_slideshow.json'), encoding='utf8') as f:
                self.options = AttrDict(json.loads(f.read()))
        except:
            self.options = AttrDict()

    def save_options(self):
        try:
            try:
                os.makedirs(os.path.expanduser(u'~/.config/variety/'))
            except:
                pass
            with io.open(os.path.expanduser(u'~/.config/variety/variety_slideshow.json'), 'w', encoding='utf8') as f:
                f.write(json.dumps(self.options, indent=4, ensure_ascii=False, encoding='utf8'))
        except:
            logging.exception(u'Could not save options:')

    def parse_options(self):
        """Support for command line options"""
        usage = """%prog [options] [list of images and/or image folders]
Starts a slideshow using the given images and/or image folders. Options are automatically saved, and reused next time you start the slideshow."""
        parser = optparse.OptionParser(usage=usage)

        parser.add_option("-s", "--seconds", action="store", type="float", dest="seconds",
                          default=self.options.get("seconds", SECONDS),
                          help="Interval in seconds between image changes.\n"
                               "Default is %s.\n"
                               "Float, at least 0.1." % SECONDS)
        parser.add_option("--fade", action="store", type="float", dest="fade",
                          default=self.options.get("fade", FADE),
                          help="Fade duration, as a fraction of the interval.\n"
                               "Default is 0.4, i.e. 0.4 * 6 = 2.4 seconds.\n"
                               "Float, between 0 and 1.\n"
                               "0 disables fade.")
        parser.add_option("--zoom", action="store", type="float", dest="zoom",
                          default=self.options.get("zoom", ZOOM),
                          help="How much to zoom in or out images, as a ratio of their size.\n"
                               "Default is %s.\n"
                               "Float, at least 0.\n"
                               "0 disables zoom." % ZOOM)
        parser.add_option("--pan", action="store", type="float", dest="pan",
                          default=self.options.get("pan", PAN),
                          help="How much to pan images sideways, as a ratio of screen size.\n"
                               "Default is %s.\n"
                               "Float, at least 0.\n"
                               "0 disables pan." % PAN)

        parser.add_option("--sort", action="store", type="string", dest="sort",
                          default=self.options.get("sort", "random"),
                          help="""
In what order to cycle the files. Possible values are:
random - random order (Default);
keep - keep order, specified on the commandline (only useful when specifying files, not folders);
name - sort by folder name, then by filename;
date - sort by file date;""")

        parser.add_option("--order", action="store", dest="sort_order",
                          default=self.options.get("sort_order", "asc"),
                          help="Sort order: asc/ascending (this is the default), or desc/descending")

        parser.add_option("--monitor", action="store", type="int", dest="monitor",
                          default=self.options.get("monitor", 1),
                          help="On which monitor to run - 1, 2, etc. up to the number of monitors.\n" + self.current_monitors_help())

        parser.add_option("--mode", action="store", dest="mode",
                          default=self.options.get("mode", "fullscreen"),
                          help="Window mode: possible values are 'fullscreen', 'maximized', 'desktop', 'window' and 'undecorated'. "
                               "Default is fullscreen.")

        parser.add_option("--title", action="store", type="string", dest="title",
                          default=self.options.get("title", "Variety Slideshow"),
                          help="Window title")

        parser.add_option("--defaults", action="store_true", dest="defaults",
                          help="Do not load saved options, use defaults instead. "
                               "You can still specify commandline parameters to override them.")

        parser.add_option("--quit-on-motion", action="store_true", dest="quit_on_motion",
                          help="Should mouse motion stop the slideshow, like a screensaver?")


        cmd_options, args = parser.parse_args(sys.argv)
        self.options.update(vars(cmd_options))
        if 'defaults' in self.options:
            del self.options['defaults']
        if len(args) > 1:
            self.options.files_and_folders = args[1:]
        if 'files_and_folders' not in self.options:
            self.options.files_and_folders = ['/usr/share/backgrounds/']

        if self.options.seconds < 0.1:
            parser.error("Seconds should be at least 0.1")
        self.interval = self.options.seconds * 1000

        if self.options.fade < 0 or self.options.fade > 1:
            parser.error("Fade should be between 0 and 1")
        self.fade_time = self.interval * self.options.fade

        if self.options.zoom < 0:
            parser.error("Zoom should be at least 0")

        if self.options.pan < 0:
            parser.error("Pan should be at least 0")

        self.options.mode = self.options.mode.lower()
        if self.options.mode not in ('fullscreen', 'maximized', 'desktop', 'window', 'undecorated', 'desktop'):
            parser.error("Window mode: possible values are "
                         "'fullscreen', 'maximized', 'desktop', 'window' and 'undecorated'")

        self.parser = parser

    def prepare_file_queues(self):
        self.queued = []
        self.files = []
        self.error_files = set()
        self.cursor = 0

        for arg in self.options.files_and_folders:
            path = os.path.abspath(os.path.expanduser(arg))
            if is_image(path):
                self.files.append(path)

            elif os.path.isdir(path):
                for root, dirnames, filenames in os.walk(path):
                    for filename in filenames:
                        full_path = os.path.join(root, filename)
                        if is_image(full_path):
                            self.files.append(full_path)
                        if len(self.files) > 2000:
                            break
                    else:
                        continue
                    break
                else:
                    continue
                break

        if not self.files:
            self.parser.error('You should specify some files or folders')

        sort = self.options.sort.lower()
        if sort == 'keep':
            pass
        elif sort == 'name':
            self.files.sort()
        elif sort == 'date':
            self.files.sort(key=os.path.getmtime)
        else:
            random.shuffle(self.files)

        if self.options.sort_order.lower().startswith('desc'):
            self.files.reverse()

    def get_next_file(self):
        if not self.running:
            return None
        if len(self.queued):
            return self.queued.pop(0)
        else:
            if self.error_files == set(self.files):
                logging.error('Could not find any non-corrupt images, exiting.')
                self.quit()
                return None

            f = self.files[self.cursor]
            self.cursor = (self.cursor + 1) % len(self.files)
            if f in self.error_files:
                return self.get_next_file()
            else:
                return f

    def queue(self, filename):
        self.queued.append(filename)

    def connect_signals(self):
        # Connect signals
        def on_button_press(*args):
            if self.current_mode == 'fullscreen' and not self.mode_was_changed:
                self.quit()

        def on_motion(*args):
            if self.options.quit_on_motion and self.current_mode == 'fullscreen' and not self.mode_was_changed:
                self.quit()

        def on_key_press(widget, event):
            if self.current_mode == 'fullscreen' and not self.mode_was_changed:
                self.quit()
                return

            key = Gdk.keyval_name(event.keyval)

            if key == 'Escape':
                self.quit()

            elif key in ('f', 'F', 'F11'):
                if self.current_mode == 'desktop':
                    return
                if self.current_mode == 'fullscreen':
                    self.current_mode = 'window'
                    self.unfullscreen()
                else:
                    self.current_mode = 'fullscreen'
                    self.fullscreen()
                self.mode_was_changed = True
                GObject.timeout_add(200, self.next)

            elif key in ('d', 'D'):
                if self.current_mode == 'undecorated':
                    self.current_mode = 'window'
                    self.set_decorated(True)
                else:
                    self.current_mode = 'undecorated'
                    self.set_decorated(False)

        self.connect("delete-event", self.quit)
        self.stage.connect('destroy', self.quit)
        self.stage.connect('key-press-event', on_key_press)
        self.stage.connect('button-press-event', on_button_press)
        self.stage.connect('motion-event', on_motion)

    def run(self):
        self.running = True

        self.load_options()  # loads from config file
        self.parse_options()    # parses the command-line arguments, these take precedence over the saved config
        self.save_options()
        self.prepare_file_queues()

        self.set_title(self.options.title)
        self.screen = self.get_screen()

        self.embed = GtkClutter.Embed()
        self.add(self.embed)
        self.embed.set_visible(True)

        self.stage = self.embed.get_stage()
        self.stage.set_color(Clutter.Color.get_static(Clutter.StaticColor.BLACK))
        if self.options.mode == 'fullscreen':
            self.stage.hide_cursor()

        self.texture = Clutter.Texture.new()
        self.next_texture = None
        self.prev_texture = None
        self.data_queue = Queue()

        self.connect_signals()

        self.will_enlarge = random.choice((True, False))

        self.resize(600, 400)
        self.move_to_monitor(self.options.monitor)

        self.current_mode = self.options.mode
        self.mode_was_changed = False
        if self.options.mode == 'fullscreen':
            self.fullscreen()
            self.set_skip_taskbar_hint(True)
        elif self.options.mode == 'maximized':
            self.maximize()
        elif self.options.mode == 'desktop':
            self.maximize()
            self.set_decorated(False)
            self.set_keep_below(True)
        elif self.options.mode == 'undecorated':
            self.set_decorated(False)

        def after_show(*args):
            def f():
                self.move_to_monitor(self.options.monitor)
                self.prepare_next_data()
                self.next()
            GObject.timeout_add(200, f)

        self.connect('show', lambda *args: GObject.idle_add(after_show))

        self.show()
        Gtk.main()

    def quit(self, *args):
        logging.info('Exiting...')
        self.running = False
        Gtk.main_quit()

    def move_to_monitor(self, i):
        i = max(1, min(i, self.screen.get_n_monitors()))
        rect = self.screen.get_monitor_geometry(i - 1)
        self.move(rect.x + (rect.width - self.get_size()[0]) / 2, rect.y + (rect.height - self.get_size()[1]) / 2)

    def next(self, *args):
        if not self.running:
            return
        try:
            if hasattr(self, 'next_timeout'):
                GObject.source_remove(self.next_timeout)
                delattr(self, 'next_timeout')

            image_data = self.data_queue.get(True, timeout=1)
            if not isinstance(image_data, tuple):
                if image_data:
                    logging.info('Error in %s, skipping it' % image_data)
                    self.error_files.add(image_data)
                self.prepare_next_data()
                return self.next()

            self.next_texture = self.create_texture(image_data)
            target_size, target_position = self.initialize_pan_and_zoom(self.next_texture)

            self.stage.add_actor(self.next_texture)
            self.toggle(self.texture, False)
            self.toggle(self.next_texture, True)

            self.start_pan_and_zoom(self.next_texture, target_size, target_position)

            if self.prev_texture:
                self.prev_texture.destroy()
            self.prev_texture = self.texture
            self.texture = self.next_texture

            self.next_timeout = GObject.timeout_add(int(self.interval), self.next, priority=GLib.PRIORITY_HIGH)
            self.prepare_next_data()
        except:
            logging.exception('Oops, exception in next, rescheduling:')
            self.next_timeout = GObject.timeout_add(100, self.next, priority=GLib.PRIORITY_HIGH)

    def get_ratio_to_screen(self, texture):
        return max(self.stage.get_width() / texture.get_width(), self.stage.get_height() / texture.get_height())

    def prepare_next_data(self):
        filename = self.get_next_file()
        if not filename:
            self.data_queue.put(None)
            return

        def _prepare(filename):
            os.nice(20)
            max_w = self.stage.get_width() * (1 + 2 * self.options.zoom)
            max_h = self.stage.get_height() * (1 + 2 * self.options.zoom)

            try:
                pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(filename, max_w, max_h, True)
                data = (
                    pixbuf.get_pixels(),
                    pixbuf.get_has_alpha(),
                    pixbuf.get_width(),
                    pixbuf.get_height(),
                    pixbuf.get_rowstride(),
                    4 if pixbuf.get_has_alpha() else 3)
                self.data_queue.put(data)
            except:
                logging.exception('Could not open file %s' % filename)
                self.data_queue.put(filename)

        p = Process(target=_prepare, args=(filename,))
        p.daemon = True
        p.start()

    def create_texture(self, image_data):
        data = tuple(image_data) + (Clutter.TextureFlags.NONE,)
        texture = Clutter.Texture.new()
        texture.set_from_rgb_data(*data)
        texture.set_opacity(0)
        texture.set_keep_aspect_ratio(True)
        return texture

    def initialize_pan_and_zoom(self, texture):
        self.will_enlarge = not self.will_enlarge

        pan_px = max(self.stage.get_width(), self.stage.get_height()) * self.options.pan
        rand_pan = lambda: random.choice((-1, 1)) * (pan_px + pan_px * random.random())
        zoom_factor = (1 + self.options.zoom) * (1 + self.options.zoom * random.random())

        scale = self.get_ratio_to_screen(texture)
        base_w, base_h = texture.get_width() * scale, texture.get_height() * scale

        safety_zoom = 1 + self.options.pan / 2 if self.options.zoom > 0 else 1

        small_size = base_w * safety_zoom, base_h * safety_zoom
        big_size = base_w * safety_zoom * zoom_factor, base_h * safety_zoom * zoom_factor
        small_position = (-(small_size[0] - self.stage.get_width()) / 2,
                          -(small_size[1] - self.stage.get_height()) / 2)
        big_position = (-(big_size[0] - self.stage.get_width()) / 2 + rand_pan(),
                        -(big_size[1] - self.stage.get_height()) / 2 + rand_pan())

        if self.will_enlarge:
            initial_size, initial_position = small_size, small_position
            target_size, target_position = big_size, big_position
        else:
            initial_size, initial_position = big_size, big_position
            target_size, target_position = small_size, small_position

        # set initial size
        texture.set_size(*initial_size)
        texture.set_position(*initial_position)

        return target_size, target_position

    def start_pan_and_zoom(self, texture, target_size, target_position):
        # start animating to target size
        texture.save_easing_state()
        texture.set_easing_mode(Clutter.AnimationMode.LINEAR)
        texture.set_easing_duration(self.interval + self.fade_time)
        texture.set_size(*target_size)
        texture.set_position(*target_position)

    def toggle(self, texture, visible):
        texture.set_reactive(visible)
        texture.save_easing_state()
        texture.set_easing_mode(Clutter.AnimationMode.EASE_OUT_SINE if visible else Clutter.AnimationMode.EASE_IN_SINE)
        texture.set_easing_duration(self.fade_time)
        texture.set_opacity(255 if visible else 0)
        if visible:
            self.stage.raise_child(texture, None)
class VarietySlideshow(Gtk.Window):
    def __init__(self):
        super(Gtk.Window, self).__init__()

    def current_monitors_help(self):
        result = 'Your current monitors are: '
        screen = Gdk.Screen.get_default()
        for i in range(0, screen.get_n_monitors()):
            geo = screen.get_monitor_geometry(i)
            result += (', ' if i > 0 else '') + '%d - %s, %dx%d' % (
                i + 1, screen.get_monitor_plug_name(i), geo.width, geo.height)
        return result

    def load_options(self):
        if '--defaults' in sys.argv:
            self.options = AttrDict()
            return

        try:
            with io.open(os.path.expanduser(
                    u'~/.config/variety/variety_slideshow.json'),
                         encoding='utf8') as f:
                self.options = AttrDict(json.loads(f.read()))
        except:
            self.options = AttrDict()

    def save_options(self):
        try:
            try:
                os.makedirs(os.path.expanduser(u'~/.config/variety/'))
            except:
                pass
            with io.open(os.path.expanduser(
                    u'~/.config/variety/variety_slideshow.json'),
                         'w',
                         encoding='utf8') as f:
                f.write(
                    json.dumps(self.options,
                               indent=4,
                               ensure_ascii=False,
                               encoding='utf8'))
        except:
            logging.exception(u'Could not save options:')

    def parse_options(self):
        """Support for command line options"""
        usage = """%prog [options] [list of images and/or image folders]
Starts a slideshow using the given images and/or image folders. Options are automatically saved, and reused next time you start the slideshow."""
        parser = optparse.OptionParser(usage=usage)

        parser.add_option("-s",
                          "--seconds",
                          action="store",
                          type="float",
                          dest="seconds",
                          default=self.options.get("seconds", SECONDS),
                          help="Interval in seconds between image changes.\n"
                          "Default is %s.\n"
                          "Float, at least 0.1." % SECONDS)
        parser.add_option(
            "--fade",
            action="store",
            type="float",
            dest="fade",
            default=self.options.get("fade", FADE),
            help="Fade duration, as a fraction of the interval.\n"
            "Default is 0.4, i.e. 0.4 * 6 = 2.4 seconds.\n"
            "Float, between 0 and 1.\n"
            "0 disables fade.")
        parser.add_option(
            "--zoom",
            action="store",
            type="float",
            dest="zoom",
            default=self.options.get("zoom", ZOOM),
            help="How much to zoom in or out images, as a ratio of their size.\n"
            "Default is %s.\n"
            "Float, at least 0.\n"
            "0 disables zoom." % ZOOM)
        parser.add_option(
            "--pan",
            action="store",
            type="float",
            dest="pan",
            default=self.options.get("pan", PAN),
            help="How much to pan images sideways, as a ratio of screen size.\n"
            "Default is %s.\n"
            "Float, at least 0.\n"
            "0 disables pan." % PAN)

        parser.add_option("--sort",
                          action="store",
                          type="string",
                          dest="sort",
                          default=self.options.get("sort", "random"),
                          help="""
In what order to cycle the files. Possible values are:
random - random order (Default);
keep - keep order, specified on the commandline (only useful when specifying files, not folders);
name - sort by folder name, then by filename;
date - sort by file date;""")

        parser.add_option(
            "--order",
            action="store",
            dest="sort_order",
            default=self.options.get("sort_order", "asc"),
            help=
            "Sort order: asc/ascending (this is the default), or desc/descending"
        )

        parser.add_option(
            "--monitor",
            action="store",
            type="int",
            dest="monitor",
            default=self.options.get("monitor", 1),
            help=
            "On which monitor to run - 1, 2, etc. up to the number of monitors.\n"
            + self.current_monitors_help())

        parser.add_option(
            "--mode",
            action="store",
            dest="mode",
            default=self.options.get("mode", "fullscreen"),
            help=
            "Window mode: possible values are 'fullscreen', 'maximized', 'desktop', 'window' and 'undecorated'. "
            "Default is fullscreen.")

        parser.add_option("--title",
                          action="store",
                          type="string",
                          dest="title",
                          default=self.options.get("title",
                                                   "Variety Slideshow"),
                          help="Window title")

        parser.add_option(
            "--defaults",
            action="store_true",
            dest="defaults",
            help="Do not load saved options, use defaults instead. "
            "You can still specify commandline parameters to override them.")

        parser.add_option(
            "--quit-on-motion",
            action="store_true",
            dest="quit_on_motion",
            help="Should mouse motion stop the slideshow, like a screensaver?")

        cmd_options, args = parser.parse_args(sys.argv)
        self.options.update(vars(cmd_options))
        if 'defaults' in self.options:
            del self.options['defaults']
        if len(args) > 1:
            self.options.files_and_folders = args[1:]
        if 'files_and_folders' not in self.options:
            self.options.files_and_folders = ['/usr/share/backgrounds/']

        if self.options.seconds < 0.1:
            parser.error("Seconds should be at least 0.1")
        self.interval = self.options.seconds * 1000

        if self.options.fade < 0 or self.options.fade > 1:
            parser.error("Fade should be between 0 and 1")
        self.fade_time = self.interval * self.options.fade

        if self.options.zoom < 0:
            parser.error("Zoom should be at least 0")

        if self.options.pan < 0:
            parser.error("Pan should be at least 0")

        self.options.mode = self.options.mode.lower()
        if self.options.mode not in ('fullscreen', 'maximized', 'desktop',
                                     'window', 'undecorated', 'desktop'):
            parser.error(
                "Window mode: possible values are "
                "'fullscreen', 'maximized', 'desktop', 'window' and 'undecorated'"
            )

        self.parser = parser

    def prepare_file_queues(self):
        self.queued = []
        self.files = []
        self.error_files = set()
        self.cursor = 0

        for arg in self.options.files_and_folders:
            path = os.path.abspath(os.path.expanduser(arg))
            if is_image(path):
                self.files.append(path)

            elif os.path.isdir(path):
                for root, dirnames, filenames in os.walk(path):
                    for filename in filenames:
                        full_path = os.path.join(root, filename)
                        if is_image(full_path):
                            self.files.append(full_path)
                        if len(self.files) > 2000:
                            break
                    else:
                        continue
                    break
                else:
                    continue
                break

        if not self.files:
            self.parser.error('You should specify some files or folders')

        sort = self.options.sort.lower()
        if sort == 'keep':
            pass
        elif sort == 'name':
            self.files.sort()
        elif sort == 'date':
            self.files.sort(key=os.path.getmtime)
        else:
            random.shuffle(self.files)

        if self.options.sort_order.lower().startswith('desc'):
            self.files.reverse()

    def get_next_file(self):
        if not self.running:
            return None
        if len(self.queued):
            return self.queued.pop(0)
        else:
            if self.error_files == set(self.files):
                logging.error(
                    'Could not find any non-corrupt images, exiting.')
                self.quit()
                return None

            f = self.files[self.cursor]
            self.cursor = (self.cursor + 1) % len(self.files)
            if f in self.error_files:
                return self.get_next_file()
            else:
                return f

    def queue(self, filename):
        self.queued.append(filename)

    def connect_signals(self):
        # Connect signals
        def on_button_press(*args):
            if self.current_mode == 'fullscreen' and not self.mode_was_changed:
                self.quit()

        def on_motion(*args):
            if self.options.quit_on_motion and self.current_mode == 'fullscreen' and not self.mode_was_changed:
                self.quit()

        def on_key_press(widget, event):
            if self.current_mode == 'fullscreen' and not self.mode_was_changed:
                self.quit()
                return

            key = Gdk.keyval_name(event.keyval)

            if key == 'Escape':
                self.quit()

            elif key in ('f', 'F', 'F11'):
                if self.current_mode == 'desktop':
                    return
                if self.current_mode == 'fullscreen':
                    self.current_mode = 'window'
                    self.unfullscreen()
                else:
                    self.current_mode = 'fullscreen'
                    self.fullscreen()
                self.mode_was_changed = True
                GObject.timeout_add(200, self.next)

            elif key in ('d', 'D'):
                if self.current_mode == 'undecorated':
                    self.current_mode = 'window'
                    self.set_decorated(True)
                else:
                    self.current_mode = 'undecorated'
                    self.set_decorated(False)

        self.connect("delete-event", self.quit)
        self.stage.connect('destroy', self.quit)
        self.stage.connect('key-press-event', on_key_press)
        self.stage.connect('button-press-event', on_button_press)
        self.stage.connect('motion-event', on_motion)

    def run(self):
        self.running = True

        self.load_options()  # loads from config file
        self.parse_options(
        )  # parses the command-line arguments, these take precedence over the saved config
        self.save_options()
        self.prepare_file_queues()

        self.set_title(self.options.title)
        self.screen = self.get_screen()

        self.embed = GtkClutter.Embed()
        self.add(self.embed)
        self.embed.set_visible(True)

        self.stage = self.embed.get_stage()
        self.stage.set_color(
            Clutter.Color.get_static(Clutter.StaticColor.BLACK))
        if self.options.mode == 'fullscreen':
            self.stage.hide_cursor()

        self.texture = Clutter.Texture.new()
        self.next_texture = None
        self.prev_texture = None
        self.data_queue = Queue()

        self.connect_signals()

        self.will_enlarge = random.choice((True, False))

        self.resize(600, 400)
        self.move_to_monitor(self.options.monitor)

        self.current_mode = self.options.mode
        self.mode_was_changed = False
        if self.options.mode == 'fullscreen':
            self.fullscreen()
            self.set_skip_taskbar_hint(True)
        elif self.options.mode == 'maximized':
            self.maximize()
        elif self.options.mode == 'desktop':
            self.maximize()
            self.set_decorated(False)
            self.set_keep_below(True)
        elif self.options.mode == 'undecorated':
            self.set_decorated(False)

        def after_show(*args):
            def f():
                self.move_to_monitor(self.options.monitor)
                self.prepare_next_data()
                self.next()

            GObject.timeout_add(200, f)

        self.connect('show', lambda *args: GObject.idle_add(after_show))

        self.show()
        Gtk.main()

    def quit(self, *args):
        logging.info('Exiting...')
        self.running = False
        Gtk.main_quit()

    def move_to_monitor(self, i):
        i = max(1, min(i, self.screen.get_n_monitors()))
        rect = self.screen.get_monitor_geometry(i - 1)
        self.move(rect.x + (rect.width - self.get_size()[0]) / 2,
                  rect.y + (rect.height - self.get_size()[1]) / 2)

    def next(self, *args):
        if not self.running:
            return
        try:
            if hasattr(self, 'next_timeout'):
                GObject.source_remove(self.next_timeout)
                delattr(self, 'next_timeout')

            image_data = self.data_queue.get(True, timeout=1)
            if not isinstance(image_data, tuple):
                if image_data:
                    logging.info('Error in %s, skipping it' % image_data)
                    self.error_files.add(image_data)
                self.prepare_next_data()
                return self.next()

            self.next_texture = self.create_texture(image_data)
            target_size, target_position = self.initialize_pan_and_zoom(
                self.next_texture)

            self.stage.add_actor(self.next_texture)
            self.toggle(self.texture, False)
            self.toggle(self.next_texture, True)

            self.start_pan_and_zoom(self.next_texture, target_size,
                                    target_position)

            if self.prev_texture:
                self.prev_texture.destroy()
            self.prev_texture = self.texture
            self.texture = self.next_texture

            self.next_timeout = GObject.timeout_add(
                int(self.interval), self.next, priority=GLib.PRIORITY_HIGH)
            self.prepare_next_data()
        except:
            logging.exception('Oops, exception in next, rescheduling:')
            self.next_timeout = GObject.timeout_add(
                100, self.next, priority=GLib.PRIORITY_HIGH)

    def get_ratio_to_screen(self, texture):
        return max(self.stage.get_width() / texture.get_width(),
                   self.stage.get_height() / texture.get_height())

    def prepare_next_data(self):
        filename = self.get_next_file()
        if not filename:
            self.data_queue.put(None)
            return

        def _prepare(filename):
            os.nice(20)
            max_w = self.stage.get_width() * (1 + 2 * self.options.zoom)
            max_h = self.stage.get_height() * (1 + 2 * self.options.zoom)

            try:
                pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
                    filename, max_w, max_h, True)
                data = (pixbuf.get_pixels(), pixbuf.get_has_alpha(),
                        pixbuf.get_width(), pixbuf.get_height(),
                        pixbuf.get_rowstride(),
                        4 if pixbuf.get_has_alpha() else 3)
                self.data_queue.put(data)
            except:
                logging.exception('Could not open file %s' % filename)
                self.data_queue.put(filename)

        p = Process(target=_prepare, args=(filename, ))
        p.daemon = True
        p.start()

    def create_texture(self, image_data):
        data = tuple(image_data) + (Clutter.TextureFlags.NONE, )
        texture = Clutter.Texture.new()
        texture.set_from_rgb_data(*data)
        texture.set_opacity(0)
        texture.set_keep_aspect_ratio(True)
        return texture

    def initialize_pan_and_zoom(self, texture):
        self.will_enlarge = not self.will_enlarge

        pan_px = max(self.stage.get_width(),
                     self.stage.get_height()) * self.options.pan
        rand_pan = lambda: random.choice(
            (-1, 1)) * (pan_px + pan_px * random.random())
        zoom_factor = (1 + self.options.zoom) * (
            1 + self.options.zoom * random.random())

        scale = self.get_ratio_to_screen(texture)
        base_w, base_h = texture.get_width() * scale, texture.get_height(
        ) * scale

        safety_zoom = 1 + self.options.pan / 2 if self.options.zoom > 0 else 1

        small_size = base_w * safety_zoom, base_h * safety_zoom
        big_size = base_w * safety_zoom * zoom_factor, base_h * safety_zoom * zoom_factor
        small_position = (-(small_size[0] - self.stage.get_width()) / 2,
                          -(small_size[1] - self.stage.get_height()) / 2)
        big_position = (-(big_size[0] - self.stage.get_width()) / 2 +
                        rand_pan(),
                        -(big_size[1] - self.stage.get_height()) / 2 +
                        rand_pan())

        if self.will_enlarge:
            initial_size, initial_position = small_size, small_position
            target_size, target_position = big_size, big_position
        else:
            initial_size, initial_position = big_size, big_position
            target_size, target_position = small_size, small_position

        # set initial size
        texture.set_size(*initial_size)
        texture.set_position(*initial_position)

        return target_size, target_position

    def start_pan_and_zoom(self, texture, target_size, target_position):
        # start animating to target size
        texture.save_easing_state()
        texture.set_easing_mode(Clutter.AnimationMode.LINEAR)
        texture.set_easing_duration(self.interval + self.fade_time)
        texture.set_size(*target_size)
        texture.set_position(*target_position)

    def toggle(self, texture, visible):
        texture.set_reactive(visible)
        texture.save_easing_state()
        texture.set_easing_mode(Clutter.AnimationMode.EASE_OUT_SINE if visible
                                else Clutter.AnimationMode.EASE_IN_SINE)
        texture.set_easing_duration(self.fade_time)
        texture.set_opacity(255 if visible else 0)
        if visible:
            self.stage.raise_child(texture, None)