def queryCheck(icon):
    global queryThread
    print("Querying API")
    response = get("https://ddda.lennardf1989.com/api/v2/grace/Steam")
    if response.status_code == 200:
        responseData = response.json()
        print(responseData)
        if responseData["grace"] == "yes":
            time = responseData["timestamp"]
            icon.notify(
                "The Ur-Dragon is in grace period, go kill him Arisen!\nLast update: "
                + str(time.split("T")[1][0:-4]) + ", " +
                str(time.split("T")[0]),
                "Ur-Dragon in grace period! (" +
                str(responseData["platform"]) + ", Generation " +
                str(responseData["generation"]) + ")",
            )
    queryThread = Timer(customQueryInterval * 60, queryCheck, [icon])
    queryThread.start()
def checkGrace(icon, item):
    response = get("https://ddda.lennardf1989.com/api/v2/grace/Steam")
    print(response)
    if response.status_code == 200:
        responseData = response.json()
        print(responseData)
        if responseData["grace"] == "yes":
            time = responseData["timestamp"]
            icon.notify(
                "The Ur-Dragon is in grace period, go kill him Arisen!\nLast update: "
                + str(time.split("T")[1][0:-4]) + ", " +
                str(time.split("T")[0]),
                "Ur-Dragon in grace period! (" +
                str(responseData["platform"]) + ", Generation " +
                str(responseData["generation"]) + ")",
            )
        else:
            icon.notify(
                "The Ur-Dragon is not in grace period, keep hacking away!",
                "Nothing yet...",
            )
    else:
        icon.notify(
            "Failed to request the Ur-Dragon's grace state",
            "Request failed.",
        )
Beispiel #3
0
class SysTray:
    """
    systray icon using pystray package
    """
    def __init__(self, main_window):
        self.main_window = main_window
        self.tray_icon_path = os.path.join(config.sett_folder, 'systray.png')  # path to icon
        self.icon = None
        self._hover_text = None
        self.Gtk = None
        self.active = False

    def show_main_window(self, *args):
        # unhide and bring on top
        self.main_window.focus()

    def minimize_to_systray(self, *args):
        self.main_window.hide()

    @property
    def tray_icon(self):
        """return pillow image"""
        try:
            img = atk.create_pil_image(b64=APP_ICON, size=48)

            return img
        except Exception as e:
            log('systray: tray_icon', e)
            if config.test_mode:
                raise e

    def run(self):
        # not supported on mac
        if config.operating_system == 'Darwin':
            log('Systray is not supported on mac yet')
            return

        options_map = {'Show': self.show_main_window,
                       'Minimize to Systray': self.minimize_to_systray,
                       'Quit': self.quit}

        # make our own Gtk statusIcon, since pystray failed to run icon properly on Gtk 3.0 from a thread
        if config.operating_system == 'Linux':
            try:
                import gi
                gi.require_version('Gtk', '3.0')
                from gi.repository import Gtk
                self.Gtk = Gtk

                # delete previous icon file (it might contains an icon file for old firedm versions)
                delete_file(self.tray_icon_path)

                # save file to settings folder
                self.tray_icon.save(self.tray_icon_path, format='png')

                # creating menu
                menu = Gtk.Menu()
                for option, callback in options_map.items():
                    item = Gtk.MenuItem(label=option)
                    item.connect('activate', callback)
                    menu.append(item)
                menu.show_all()

                APPINDICATOR_ID = config.APP_NAME

                # setup notify system, will be used in self.notify()
                gi.require_version('Notify', '0.7')
                from gi.repository import Notify as notify
                self.Gtk_notify = notify  # get reference for later deinitialize when quit systray
                self.Gtk_notify.init(APPINDICATOR_ID)  # initialize first

                # try appindicator
                try:
                    gi.require_version('AppIndicator3', '0.1')
                    from gi.repository import AppIndicator3 as appindicator

                    indicator = appindicator.Indicator.new(APPINDICATOR_ID, self.tray_icon_path, appindicator.IndicatorCategory.APPLICATION_STATUS)
                    indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
                    indicator.set_menu(menu)

                    # use .set_name to prevent error, Gdk-CRITICAL **: gdk_window_thaw_toplevel_updates: assertion 'window->update_and_descendants_freeze_count > 0' failed
                    indicator.set_name = APPINDICATOR_ID

                    # can set label beside systray icon
                    # indicator.set_label('1.2 MB/s', '')

                    self.active = True
                    log('Systray active backend: Gtk.AppIndicator')
                    Gtk.main()
                    return
                except:
                    pass

                # try GTK StatusIcon
                def icon_right_click(icon, button, time):
                    menu.popup(None, None, None, icon, button, time)

                icon = Gtk.StatusIcon()
                icon.set_from_file(self.tray_icon_path)  # DeprecationWarning: Gtk.StatusIcon.set_from_file is deprecated
                icon.connect("popup-menu", icon_right_click)  # right click
                icon.connect('activate', self.show_main_window)  # left click
                icon.set_name = APPINDICATOR_ID

                self.active = True
                log('Systray active backend: Gtk.StatusIcon')
                Gtk.main()
                return
            except Exception as e:
                log('Systray Gtk 3.0:', e, log_level=2)
                self.active = False
        else:
            # let pystray run for other platforms, basically windows
            try:
                from pystray import Icon, Menu, MenuItem
                items = []
                for option, callback in options_map.items():
                    items.append(MenuItem(option, callback, default=True if option == 'Show' else False))

                menu = Menu(*items)
                self.icon = Icon(config.APP_NAME, self.tray_icon, menu=menu)
                self.active = True
                self.icon.run()
            except Exception as e:
                log('systray: - run() - ', e)
                self.active = False

    def shutdown(self):
        try:
            self.active = False
            self.icon.stop()  # must be called from main thread
        except:
            pass

        try:
            # if we use Gtk notify we should deinitialize
            self.Gtk_notify.uninit()
        except:
            pass

        try:
            # Gtk.main_quit(), if called from a thread might raise 
            # (Gtk-CRITICAL **:gtk_main_quit: assertion 'main_loops != NULL' failed)
            # should call this from main thread
            self.Gtk.main_quit()
        except:
            pass

    def notify(self, msg, title=None):
        """show os notifications, e.g. balloon pop up at systray icon on windows"""
        if getattr(self.icon, 'HAS_NOTIFICATION', False):
            self.icon.notify(msg, title=title)

        else:
            try:
                self.Gtk_notify.Notification.new(title, msg, self.tray_icon_path).show()  # to show notification
            except:
                try:
                    # fallback to plyer
                    import plyer
                    plyer.notification.notify(message=msg, title=title, app_name=config.APP_NAME,
                                              app_icon=self.tray_icon_path, timeout=5)
                except:
                    pass

    def quit(self, *args):
        """callback when selecting quit from systray menu"""
        # thread safe call for main window close
        self.main_window.run_method(self.main_window.quit)
Beispiel #4
0
class Updater:
    def __init__(self, options={}):
        self.__main_gui = None

        self.__config = config.AppConfig('dnfdragora')
        self.__updateInterval = 180
        self.__update_count = -1

        self.__log_enabled = False
        self.__log_directory = None
        self.__level_debug = False

        self.__hide_menu = True

        if self.__config.userPreferences:
            if 'settings' in self.__config.userPreferences.keys():
                settings = self.__config.userPreferences['settings']
                if 'interval for checking updates' in settings.keys():
                    self.__updateInterval = int(
                        settings['interval for checking updates'])
                self.__hide_menu = settings['hide_update_menu'] if 'hide_update_menu' in settings.keys() \
                  else False

                #### Logging
                if 'log' in settings.keys():
                    log = settings['log']
                    if 'enabled' in log.keys():
                        self.__log_enabled = log['enabled']
                    if self.__log_enabled:
                        if 'directory' in log.keys():
                            self.__log_directory = log['directory']
                        if 'level_debug' in log.keys():
                            self.__level_debug = log['level_debug']

        if self.__log_enabled:
            if self.__log_directory:
                log_filename = os.path.join(self.__log_directory,
                                            "dnfdragora-updater.log")
                if self.__level_debug:
                    misc.logger_setup(log_filename, loglvl=logging.DEBUG)
                else:
                    misc.logger_setup(log_filename)
                print("Logging into %s, debug mode is %s" %
                      (self.__log_directory,
                       ("enabled" if self.__level_debug else "disabled")))
                logger.info("dnfdragora-updater started")
        else:
            print("Logging disabled")

        # if missing gets the default icon from our folder (same as dnfdragora)
        icon_path = '/usr/share/dnfdragora/images/'

        if 'icon-path' in options.keys():
            icon_path = options['icon-path']
        if icon_path.endswith('/'):
            icon_path = icon_path + 'dnfdragora.svg' if (os.path.exists(
                icon_path +
                'dnfdragora.svg')) else icon_path + 'dnfdragora.png'
        else:
            icon_path = icon_path + '/dnfdragora.svg' if (os.path.exists(
                icon_path +
                '/dnfdragora.svg')) else icon_path + '/dnfdragora.png'

        theme_icon_pathname = icon_path if 'icon-path' in options.keys(
        ) else self.__get_theme_icon_pathname() or icon_path

        logger.debug("Icon: %s" % (theme_icon_pathname))
        #empty icon as last chance
        self.__icon = Image.Image()
        try:
            if theme_icon_pathname.endswith('.svg'):
                with open(theme_icon_pathname, 'rb') as svg:
                    self.__icon = self.__svg_to_Image(svg.read())
            else:
                self.__icon = Image.open(theme_icon_pathname)
        except Exception as e:
            logger.error(e)
            logger.error("Cannot open theme icon using default one %s" %
                         (icon_path))
            self.__icon = Image.open(icon_path)

        # resetting icon_path to default value
        icon_path = '/usr/share/dnfdragora/images/'
        if 'icon-path' in options.keys():
            icon_path = options['icon-path']
        if icon_path.endswith('/'):
            icon_path = icon_path + 'dnfdragora-update.svg' if (
                os.path.exists(icon_path + 'dnfdragora-update.svg')
            ) else icon_path + 'dnfdragora-update.png'
        else:
            icon_path = icon_path + '/dnfdragora-update.svg' if (
                os.path.exists(icon_path + '/dnfdragora-update.svg')
            ) else icon_path + '/dnfdragora-update.png'

        theme_icon_pathname = icon_path if 'icon-path' in options.keys(
        ) else self.__get_theme_icon_pathname(
            name="dnfdragora-update") or icon_path

        self.__icon_update = Image.Image()
        try:
            if theme_icon_pathname.endswith('.svg'):
                with open(theme_icon_pathname, 'rb') as svg:
                    self.__icon_update = self.__svg_to_Image(svg.read())
            else:
                self.__icon_update = Image.open(theme_icon_pathname)
        except Exception as e:
            logger.error(e)
            logger.error("Cannot open theme icon using default one %s" %
                         (icon_path))
            self.__icon_update = Image.open(icon_path)

        try:
            self.__backend = dnfd_client.Client()
        except dnfdaemon.client.DaemonError as error:
            logger.error(
                _('Error starting dnfdaemon service: [%s]') % (str(error)))
            return
        except Exception as e:
            logger.error(
                _('Error starting dnfdaemon service: [%s]') % (str(e)))
            return

        self.__running = True
        self.__updater = threading.Thread(target=self.__update_loop)
        self.__scheduler = sched.scheduler(time.time, time.sleep)
        self.__getUpdatesRequested = False

        self.__menu = Menu(
            MenuItem(_('Update'), self.__run_update),
            MenuItem(_('Open dnfdragora dialog'), self.__run_dnfdragora),
            MenuItem(_('Check for updates'), self.__check_updates),
            MenuItem(_('Exit'), self.__shutdown))
        self.__name = 'dnfdragora-updater'
        self.__tray = Tray(self.__name,
                           icon=self.__icon,
                           title=self.__name,
                           menu=self.__menu)

    def __get_theme_icon_pathname(self, name='dnfdragora'):
        '''
        return theme icon pathname or None if missing
      '''
        try:
            import xdg.IconTheme
        except ImportError:
            logger.error("Error: module xdg.IconTheme is missing")
            return None
        else:
            pathname = xdg.IconTheme.getIconPath(name, 256)
            return pathname
        return None

    def __svg_to_Image(self, svg_string):
        '''
        gets svg content and returns a PIL.Image object
      '''
        import cairosvg
        import io
        in_mem_file = io.BytesIO()
        cairosvg.svg2png(bytestring=svg_string, write_to=in_mem_file)
        return Image.open(io.BytesIO(in_mem_file.getvalue()))

    def __shutdown(self, *kwargs):
        logger.info("shutdown")
        if self.__main_gui:
            logger.warning("Cannot exit dnfdragora is not deleted %s" %
                           ("and RUNNING"
                            if self.__main_gui.running else "but NOT RUNNING"))
            return
        try:
            self.__running = False
            self.__updater.join()
            try:
                if self.__backend:
                    self.__backend.Exit()
                    self.__backend = None
            except:
                pass
            yui.YDialog.deleteAllDialogs()
            yui.YUILoader.deleteUI()

        except:
            pass

        finally:
            if not self.__scheduler.empty():
                for task in self.__scheduler.queue:
                    try:
                        self.__scheduler.cancel(task)
                    except:
                        pass

            if self.__tray != None:
                self.__tray.stop()
            if self.__backend:
                self.__backend.Exit(False)
                time.sleep(0.5)
                self.__backend = None

    def __reschedule_update_in(self, minutes):
        '''
      clean up scheduler and schedule update in 'minutes'
      '''
        logger.debug("rescheduling")
        if not self.__scheduler.empty():
            logger.debug("Reset scheduler")
            for task in self.__scheduler.queue:
                try:
                    self.__scheduler.cancel(task)
                except:
                    pass
        if self.__scheduler.empty():
            self.__scheduler.enter(minutes * 60, 1, self.__get_updates)
            logger.info("Scheduled check for updates in %d %s",
                        minutes if minutes >= 1 else minutes * 60,
                        "minutes" if minutes >= 1 else "seconds")
            return True

        return False

    def __run_dialog(self, args, *kwargs):
        if self.__tray != None and self.__main_gui == None and self.__tray.visible:
            if self.__hide_menu:
                self.__tray.visible = False
            time.sleep(0.5)
            try:
                self.__main_gui = ui.mainGui(args)
            except Exception as e:
                logger.error(
                    "Exception on running dnfdragora with args %s - %s",
                    str(args), str(e))
                dialogs.warningMsgBox({
                    'title': _("Running dnfdragora failure"),
                    "text": str(e),
                    "richtext": True
                })
                yui.YDialog.deleteAllDialogs()
                time.sleep(0.5)
                self.__main_gui = None
                return
            #self.__tray.icon = None
            self.__main_gui.handleevent()

            logger.debug("Closing dnfdragora")
            while self.__main_gui.loop_has_finished != True:
                time.sleep(1)
            logger.info("Closed dnfdragora")
            yui.YDialog.deleteAllDialogs()
            time.sleep(1)
            self.__main_gui = None
            logger.debug("Look for remaining updates")
            # Let's delay a bit the check, otherwise Lock will fail
            done = self.__reschedule_update_in(0.5)
            logger.debug("Scheduled %s", "done" if done else "skipped")
        else:
            if self.__main_gui:
                logger.warning(
                    "Cannot run dnfdragora because it is already running")
            else:
                logger.warning("Cannot run dnfdragora")

    def __run_dnfdragora(self, *kwargs):
        logger.debug("Menu visibility is %s", str(self.__tray.visible))
        return self.__run_dialog({})

    def __run_update(self, *kwargs):
        logger.debug("Menu visibility is %s", str(self.__tray.visible))
        return self.__run_dialog({'update_only': True})

    def __check_updates(self, *kwargs):
        '''
      Start get updates by simply locking the DB
      '''
        logger.debug("Start checking for updates, by menu command")
        if self.__hide_menu:
            self.__tray.visible = False
        try:
            self.__backend.Lock()
            self.__getUpdatesRequested = True
        except Exception as e:
            logger.error(_('Exception caught: [%s]') % (str(e)))

    def __get_updates(self, *kwargs):
        '''
      Start get updates by simply locking the DB
      '''
        logger.debug("Start getting updates")
        try:
            self.__backend.Lock()
        except Exception as e:
            logger.error(_('Exception caught: [%s]') % (str(e)))

    def __OnRepoMetaDataProgress(self, name, frac):
        '''Repository Metadata Download progress.'''
        values = (name, frac)
        #print('on_RepoMetaDataProgress (root): %s', repr(values))
        if frac == 0.0 or frac == 1.0:
            logger.debug('OnRepoMetaDataProgress: %s', repr(values))

    def __update_loop(self):
        self.__get_updates()
        backend_locked = False

        while self.__running == True:
            update_next = self.__updateInterval
            add_to_schedule = False
            try:
                counter = 0
                count_max = 1000

                #if dnfdragora is running we receive transaction/rpm progress/download etc events
                #let's dequeue them as quick as possible
                while counter < count_max:
                    counter = counter + 1

                    item = self.__backend.eventQueue.get_nowait()
                    event = item['event']
                    info = item['value']

                    if (event == 'Lock'):
                        logger.info("Event received %s - info %s", event,
                                    str(info))
                        backend_locked = info['result']
                        if backend_locked:
                            self.__backend.GetPackages('updates_all')
                            logger.debug("Getting update packages")
                        else:
                            # no locked try again in a minute
                            update_next = 1
                            add_to_schedule = True
                    elif (event == 'OnRepoMetaDataProgress'):
                        #let's log metadata since slows down the Lock requests
                        self.__OnRepoMetaDataProgress(info['name'],
                                                      info['frac'])
                    elif (event == 'GetPackages'):
                        logger.debug(
                            "Got GetPackages event menu visibility is %s",
                            str(self.__tray.visible))
                        #if not self.__tray.visible :
                        # ugly workaround to show icon if hidden, set empty icon and show it
                        self.__tray.icon = Image.Image()
                        self.__tray.visible = True
                        logger.debug("Event received %s", event)
                        if not info['error']:
                            po_list = info['result']
                            self.__update_count = len(po_list)
                            logger.info("Found %d updates" %
                                        (self.__update_count))

                            if (self.__update_count >= 1):
                                self.__tray.icon = self.__icon_update
                                self.__tray.visible = True
                                self.__tray.notify(
                                    title='dnfdragora-update',
                                    message=_('%d updates available.') %
                                    self.__update_count)
                            elif self.__getUpdatesRequested:
                                # __update_count == 0 but get updates has been requested by user command
                                # Let's give a feed back anyway
                                logger.debug(
                                    "No updates found after user request")
                                self.__tray.icon = self.__icon
                                self.__tray.notify(
                                    title='dnfdragora-update',
                                    message=_('No updates available'))
                                self.__tray.visible = not self.__hide_menu
                            else:
                                self.__tray.icon = self.__icon
                                self.__tray.visible = not self.__hide_menu
                                logger.debug("No updates found")
                            self.__getUpdatesRequested = False
                            logger.debug("Menu visibility is %s",
                                         str(self.__tray.visible))
                        else:
                            # error
                            logger.error("GetPackages error %s",
                                         str(info['error']))
                        #force scheduling again
                        add_to_schedule = True
                        # Let's release the db
                        self.__backend.Unlock(sync=True)
                        backend_locked = False
                        logger.debug("RPM DB unlocked")
                    #elif (event == xxx)
                    else:
                        if backend_locked:
                            logger.warning(
                                "Unmanaged event received %s - info %s", event,
                                str(info))

            except Empty as e:
                pass

            if add_to_schedule:
                self.__reschedule_update_in(update_next)
            elif self.__scheduler.empty():
                # if the scheduler is empty we schedule a check according
                # to configuration file anyway
                self.__scheduler.enter(update_next * 60, 1, self.__get_updates)
                logger.info("Scheduled check for updates in %d minutes",
                            update_next)
            self.__scheduler.run(blocking=False)
            time.sleep(0.5)

        logger.info("Update loop end")

    def __main_loop(self):
        def setup(tray):
            # False to start without icon
            tray.visible = True

        self.__updater.start()
        time.sleep(1)
        self.__tray.run(setup=setup)
        logger.info("dnfdragora-updater termination")

    def main(self):
        self.__main_loop()
Beispiel #5
0
def copy_to_clipboard(icon: pystray.Icon, port: int) -> None:
    text_to_copy = f"/connect localhost:{port}"
    pyperclip.copy(text_to_copy)

    icon.notify("Copied to clipboard!")
    log.debug("Copy to clipboard: " + text_to_copy)