713 lines
23 KiB
Python
713 lines
23 KiB
Python
import os
|
|
import sys
|
|
import copy
|
|
import tempfile
|
|
from threading import Lock
|
|
|
|
sys.path.insert(0, "../lib")
|
|
sys.path.insert(1, "../client")
|
|
sys.path.insert(2, "./")
|
|
sys.path.insert(3, "/usr/lib/entropy/lib")
|
|
sys.path.insert(4, "/usr/lib/entropy/client")
|
|
sys.path.insert(5, "/usr/lib/entropy/rigo")
|
|
|
|
|
|
from gi.repository import Gtk, Gdk, Gio, GLib, GObject, GdkPixbuf
|
|
|
|
from rigo.paths import DATA_DIR
|
|
from rigo.enums import Icons
|
|
from rigo.models.application import Application, ApplicationMetadata
|
|
from rigo.ui.gtk3.widgets.apptreeview import AppTreeView
|
|
from rigo.ui.gtk3.utils import init_sc_css_provider, get_sc_icon_theme, \
|
|
resize_image
|
|
|
|
from entropy.const import etpUi, const_debug_write, const_debug_enabled
|
|
from entropy.exceptions import RepositoryError
|
|
from entropy.client.interfaces import Client
|
|
from entropy.services.client import WebService
|
|
from entropy.misc import TimeScheduled, ParallelTask
|
|
from entropy.i18n import _, ngettext
|
|
|
|
import entropy.tools
|
|
|
|
class EntropyWebService(object):
|
|
|
|
def __init__(self, entropy_client, tx_callback=None):
|
|
# Install custom CACHE_DIR pointing it to our
|
|
# home directory. This way we don't need to mess
|
|
# with privileges, resulting in documents not
|
|
# downloadable.
|
|
home_dir = os.getenv("HOME")
|
|
if home_dir is None:
|
|
home_dir = tempfile.mkdtemp(prefix="EntropyWebService")
|
|
ws_cache_dir = os.path.join(home_dir, ".entropy", "ws_cache")
|
|
WebService.CACHE_DIR = ws_cache_dir
|
|
self._entropy = entropy_client
|
|
self._webserv_map = {}
|
|
self._tx_callback = tx_callback
|
|
self._mutex = Lock()
|
|
|
|
def get(self, repository_id):
|
|
"""
|
|
Get Entropy Web Services service object (ClientWebService).
|
|
|
|
@param repository_id: repository identifier
|
|
@type repository_id: string
|
|
@return: the ClientWebService instance
|
|
@rtype: entropy.client.services.interfaces.ClientWebService
|
|
@raise WebService.UnsupportedService: if service is unsupported by
|
|
repository
|
|
"""
|
|
webserv = self._webserv_map.get(repository_id)
|
|
if webserv == -1:
|
|
# not available
|
|
return None
|
|
if webserv is not None:
|
|
return webserv
|
|
|
|
with self._mutex:
|
|
webserv = self._webserv_map.get(repository_id)
|
|
if webserv == -1:
|
|
# not available
|
|
return None
|
|
if webserv is not None:
|
|
return webserv
|
|
|
|
try:
|
|
webserv = self._get(self._entropy, repository_id)
|
|
except WebService.UnsupportedService as err:
|
|
webserv = None
|
|
|
|
if webserv is None:
|
|
self._webserv_map[repository_id] = -1
|
|
# not available
|
|
return None
|
|
|
|
try:
|
|
available = webserv.service_available()
|
|
except WebService.WebServiceException:
|
|
available = False
|
|
|
|
if not available:
|
|
with self._mutex:
|
|
if repository_id not in self._webserv_map:
|
|
self._webserv_map[repository_id] = -1
|
|
return
|
|
|
|
with self._mutex:
|
|
if repository_id not in self._webserv_map:
|
|
self._webserv_map[repository_id] = webserv
|
|
return webserv
|
|
|
|
def _get(self, entropy_client, repository_id):
|
|
"""
|
|
Get Entropy Web Services service object (ClientWebService).
|
|
|
|
@param entropy_client: Entropy Client interface
|
|
@type entropy_client: entropy.client.interfaces.Client
|
|
@param repository_id: repository identifier
|
|
@type repository_id: string
|
|
@return: the ClientWebService instance
|
|
@rtype: entropy.client.services.interfaces.ClientWebService
|
|
@raise WebService.UnsupportedService: if service is unsupported by
|
|
repository
|
|
"""
|
|
factory = entropy_client.WebServices()
|
|
webserv = factory.new(repository_id)
|
|
if self._tx_callback is not None:
|
|
webserv._set_transfer_callback(self._tx_callback)
|
|
return webserv
|
|
|
|
|
|
class AppListStore(Gtk.ListStore):
|
|
|
|
# column types
|
|
COL_TYPES = (GObject.TYPE_PYOBJECT,)
|
|
|
|
# column id
|
|
COL_ROW_DATA = 0
|
|
|
|
# default icon size returned by Application.get_icon()
|
|
ICON_SIZE = 48
|
|
_MISSING_ICON = None
|
|
_MISSING_ICON_MUTEX = Lock()
|
|
_ICON_CACHE = {}
|
|
|
|
def __init__(self, entropy_client, entropy_ws, view, icons):
|
|
Gtk.ListStore.__init__(self)
|
|
self._view = view
|
|
self._entropy = entropy_client
|
|
self._entropy_ws = entropy_ws
|
|
self._icons = icons
|
|
self.set_column_types(self.COL_TYPES)
|
|
|
|
# Startup Entropy Package Metadata daemon
|
|
ApplicationMetadata.start()
|
|
|
|
def clear(self):
|
|
"""
|
|
Clear ListStore content (and Icon Cache).
|
|
"""
|
|
outcome = Gtk.ListStore.clear(self)
|
|
AppListStore._ICON_CACHE.clear()
|
|
return outcome
|
|
|
|
def _ui_redraw_callback(self, *args):
|
|
if const_debug_enabled():
|
|
const_debug_write(__name__,
|
|
"_ui_redraw_callback()")
|
|
GLib.idle_add(self._view.queue_draw)
|
|
|
|
@property
|
|
def _missing_icon(self):
|
|
"""
|
|
Return the missing icon Gtk.Image() if needed.
|
|
"""
|
|
if AppListStore._MISSING_ICON is not None:
|
|
return AppListStore._MISSING_ICON
|
|
with AppListStore._MISSING_ICON_MUTEX:
|
|
if AppListStore._MISSING_ICON is not None:
|
|
return AppListStore._MISSING_ICON
|
|
_missing_icon = self._icons.load_icon(
|
|
Icons.MISSING_APP, AppListStore.ICON_SIZE, 0)
|
|
AppListStore._MISSING_ICON = _missing_icon
|
|
return _missing_icon
|
|
|
|
def get_icon(self, pkg_match):
|
|
cached = AppListStore._ICON_CACHE.get(pkg_match)
|
|
if cached is not None:
|
|
return cached
|
|
|
|
app = Application(self._entropy, self._entropy_ws, pkg_match,
|
|
redraw_callback=self._ui_redraw_callback)
|
|
icon, cache_hit = app.get_details().icon
|
|
if icon is None:
|
|
if cache_hit:
|
|
# this means that there is no icon for package
|
|
# and so we should not keep bugging underlying
|
|
# layers with requests
|
|
AppListStore._ICON_CACHE[pkg_match] = self._missing_icon
|
|
return self._missing_icon
|
|
|
|
icon_path = icon.local_document()
|
|
if not os.path.isfile(icon_path):
|
|
return self._missing_icon
|
|
|
|
img = Gtk.Image()
|
|
img.set_from_file(icon_path)
|
|
img_buf = img.get_pixbuf()
|
|
if img_buf is None:
|
|
# wth, invalid crap
|
|
return self._missing_icon
|
|
w, h = img_buf.get_width(), img_buf.get_height()
|
|
del img_buf
|
|
del img
|
|
if w < 1:
|
|
# not legit
|
|
return self._missing_icon
|
|
width = AppListStore.ICON_SIZE
|
|
height = width * h / w
|
|
|
|
try:
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
|
|
icon_path, width, height)
|
|
except GObject.GError:
|
|
try:
|
|
os.remove(icon_path)
|
|
except OSError:
|
|
pass
|
|
return self._missing_icon
|
|
|
|
AppListStore._ICON_CACHE[pkg_match] = pixbuf
|
|
return pixbuf
|
|
|
|
def is_installed(self, pkg_match):
|
|
app = Application(self._entropy, self._entropy_ws, pkg_match,
|
|
redraw_callback=self._ui_redraw_callback)
|
|
return app.is_installed()
|
|
|
|
def is_available(self, pkg_match):
|
|
app = Application(self._entropy, self._entropy_ws, pkg_match,
|
|
redraw_callback=self._ui_redraw_callback)
|
|
return app.is_available()
|
|
|
|
def get_markup(self, pkg_match):
|
|
app = Application(self._entropy, self._entropy_ws, pkg_match,
|
|
redraw_callback=self._ui_redraw_callback)
|
|
return app.get_markup()
|
|
|
|
def get_review_stats(self, pkg_match):
|
|
app = Application(self._entropy, self._entropy_ws, pkg_match,
|
|
redraw_callback=self._ui_redraw_callback)
|
|
return app.get_review_stats()
|
|
|
|
def get_application(self, pkg_match):
|
|
app = Application(self._entropy, self._entropy_ws, pkg_match,
|
|
redraw_callback=self._ui_redraw_callback)
|
|
return app
|
|
|
|
def get_transaction_progress(self, pkg_match):
|
|
# TODO: complete
|
|
# int from 0 - 100, or -1 for no transaction
|
|
return -1
|
|
|
|
|
|
class PackagesViewController(object):
|
|
|
|
def __init__(self, entropy_client, icons, entropy_ws, rigo_sm,
|
|
search_entry, view):
|
|
self._entropy = entropy_client
|
|
self._icons = icons
|
|
self._rigo_sm = rigo_sm
|
|
self._entropy_ws = entropy_ws
|
|
self._search_entry = search_entry
|
|
self._view = view
|
|
|
|
def _search_icon_release(self, search_entry, icon_pos, _other):
|
|
"""
|
|
Event associated to the Search bar icon click.
|
|
Here we catch secondary icon click to reset the search entry text.
|
|
"""
|
|
if search_entry is not self._search_entry:
|
|
return
|
|
if icon_pos != Gtk.EntryIconPosition.SECONDARY:
|
|
return
|
|
search_entry.set_text("")
|
|
self.clear()
|
|
search_entry.emit("changed")
|
|
|
|
def _search_changed(self, search_entry):
|
|
GLib.timeout_add(700, self._search, search_entry.get_text())
|
|
|
|
def _search(self, old_text):
|
|
cur_text = self._search_entry.get_text()
|
|
if cur_text == old_text and cur_text:
|
|
th = ParallelTask(self.__search_thread, copy.copy(old_text))
|
|
th.name = "SearchThread"
|
|
th.start()
|
|
|
|
def __search_thread(self, text):
|
|
matches = []
|
|
pkg_matches, rc = self._entropy.atom_match(
|
|
text, multi_match = True,
|
|
multi_repo = True, mask_filter = False)
|
|
matches.extend(pkg_matches)
|
|
search_matches = self._entropy.atom_search(
|
|
text, repositories = self._entropy.repositories())
|
|
matches.extend([x for x in search_matches if x not in matches])
|
|
self.clear_safe()
|
|
self.append_many_safe(matches)
|
|
|
|
def setup(self):
|
|
self._store = AppListStore(
|
|
self._entropy, self._entropy_ws,
|
|
self._view, self._icons)
|
|
self._view.set_model(self._store)
|
|
|
|
# setup searchEntry event
|
|
self._search_entry.connect(
|
|
"changed", self._search_changed)
|
|
# connect icon click event
|
|
self._search_entry.connect("icon-release",
|
|
self._search_icon_release)
|
|
self._view.show()
|
|
|
|
def clear(self):
|
|
self._rigo_sm.change_view_state(
|
|
Rigo.STATIC_VIEW_STATE)
|
|
self._store.clear()
|
|
ApplicationMetadata.discard()
|
|
|
|
def append(self, opaque):
|
|
self._store.append([opaque])
|
|
self._rigo_sm.change_view_state(
|
|
Rigo.BROWSER_VIEW_STATE)
|
|
|
|
def append_many(self, opaque_list):
|
|
for opaque in opaque_list:
|
|
self._store.append([opaque])
|
|
self._rigo_sm.change_view_state(
|
|
Rigo.BROWSER_VIEW_STATE)
|
|
|
|
def clear_safe(self):
|
|
ApplicationMetadata.discard()
|
|
self._rigo_sm.change_view_state_safe(
|
|
Rigo.STATIC_VIEW_STATE)
|
|
GLib.idle_add(self._store.clear)
|
|
|
|
def append_safe(self, opaque):
|
|
GLib.idle_add(self.append, opaque)
|
|
self._rigo_sm.change_view_state_safe(
|
|
Rigo.BROWSER_VIEW_STATE)
|
|
|
|
def append_many_safe(self, opaque_list):
|
|
GLib.idle_add(self.append_many, opaque_list)
|
|
self._rigo_sm.change_view_state_safe(
|
|
Rigo.BROWSER_VIEW_STATE)
|
|
|
|
class WelcomeBox(Gtk.VBox):
|
|
|
|
def __init__(self):
|
|
Gtk.VBox.__init__(self)
|
|
self._image_path = os.path.join(DATA_DIR, "ui/gtk3/art/rigo.png")
|
|
|
|
def render(self):
|
|
image = Gtk.Image.new_from_file(self._image_path)
|
|
label = Gtk.Label()
|
|
label.set_markup(_("<i>Browse <b>Applications</b> with ease</i>"))
|
|
self.pack_start(image, False, False, 0)
|
|
self.pack_start(label, False, False, 0)
|
|
label.show()
|
|
image.show()
|
|
|
|
|
|
class NotificationBox(Gtk.HBox):
|
|
|
|
"""
|
|
Generic notification widget to be used in the
|
|
Rigo notification area.
|
|
"""
|
|
|
|
def __init__(self, message, message_type=None, tooltip=None):
|
|
Gtk.HBox.__init__(self)
|
|
self._message = message
|
|
self._buttons = []
|
|
self._type = message_type
|
|
if self._type is None:
|
|
self._type = Gtk.MessageType.INFO
|
|
self._tooltip = tooltip
|
|
|
|
def add_button(self, text, clicked_callback):
|
|
"""
|
|
Add a Gtk.Button() to this container.
|
|
Return the newly created Gtk.Button().
|
|
"""
|
|
button = Gtk.Button(text)
|
|
button.set_use_underline(True)
|
|
button.connect("clicked", clicked_callback)
|
|
self._buttons.append(button)
|
|
return button
|
|
|
|
def render(self):
|
|
"""
|
|
Render the Notification box filling in the container.
|
|
"""
|
|
bar = Gtk.InfoBar()
|
|
if self._tooltip is not None:
|
|
bar.set_tooltip_markup(self._tooltip)
|
|
bar.set_message_type(self._type)
|
|
|
|
content_area = bar.get_content_area()
|
|
hbox = Gtk.HBox()
|
|
label = Gtk.Label()
|
|
label.set_markup(self._message)
|
|
label.set_property("expand", True)
|
|
label.set_alignment(0.02, 0.50)
|
|
hbox.pack_start(label, True, True, 0)
|
|
label.show()
|
|
|
|
for button in self._buttons:
|
|
hbox.pack_start(button, False, False, 3)
|
|
button.show()
|
|
|
|
content_area.set_property("expand", False)
|
|
content_area.add(hbox)
|
|
content_area.show()
|
|
hbox.show()
|
|
|
|
bar.show()
|
|
bar.get_action_area().hide()
|
|
self.pack_start(bar, True, True, 0)
|
|
|
|
|
|
class UpdatesNotificationBox(NotificationBox):
|
|
|
|
def __init__(self, entropy_client, pvc,
|
|
updates_len, security_updates_len):
|
|
self._entropy = entropy_client
|
|
self._pvc = pvc
|
|
|
|
msg = ngettext("There is <b>%d</b> update",
|
|
"There are <b>%d</b> updates",
|
|
updates_len)
|
|
msg = msg % (updates_len,)
|
|
|
|
if security_updates_len > 0:
|
|
sec_msg = ", " + ngettext("and <b>%d</b> security update",
|
|
"and <b>%d</b> security updates",
|
|
security_updates_len)
|
|
sec_msg = sec_msg % (security_updates_len,)
|
|
msg += sec_msg
|
|
|
|
msg += ". " + _("What to do?")
|
|
|
|
NotificationBox.__init__(self, msg,
|
|
tooltip=_("Updates available, how about installing them?"))
|
|
self.add_button(_("_Update System"), self._update)
|
|
self.add_button(_("_Ignore"), self._dismiss)
|
|
|
|
def _update(self, button):
|
|
"""
|
|
Update button callback from the updates notification box.
|
|
"""
|
|
# FIXME, lxnay complete
|
|
print("Update Button clicked", button)
|
|
|
|
def _dismiss(self, button):
|
|
"""
|
|
Dismiss the notification.
|
|
"""
|
|
self.destroy()
|
|
|
|
|
|
class NotificationController(object):
|
|
|
|
"""
|
|
Notification area widget controller code.
|
|
"""
|
|
|
|
def __init__(self, entropy_client, pvc, notification_box):
|
|
self._entropy = entropy_client
|
|
self._pvc = pvc
|
|
self._box = notification_box
|
|
self._updates = None
|
|
self._security_updates = None
|
|
|
|
def setup(self):
|
|
GLib.timeout_add(3000, self._calculate_updates)
|
|
|
|
def _calculate_updates(self):
|
|
th = ParallelTask(self.__calculate_updates)
|
|
th.daemon = True
|
|
th.name = "CalculateUpdates"
|
|
th.start()
|
|
|
|
def __calculate_updates(self):
|
|
updates, removal, fine, spm_fine = \
|
|
self._entropy.calculate_updates()
|
|
self._updates = updates
|
|
self._security_updates = self._entropy.calculate_security_updates()
|
|
GLib.idle_add(self._notify_updates_safe)
|
|
|
|
def _notify_updates_safe(self):
|
|
updates_len = len(self._updates)
|
|
if updates_len == 0:
|
|
# no updates, do not show anything
|
|
return
|
|
|
|
box = UpdatesNotificationBox(
|
|
self._entropy, self._pvc,
|
|
updates_len, len(self._security_updates))
|
|
self.append(box)
|
|
|
|
def append(self, box, timeout=None):
|
|
"""
|
|
Append a notification to the Notification area.
|
|
"""
|
|
box.render()
|
|
self._box.pack_start(box, False, False, 0)
|
|
box.show()
|
|
self._box.show()
|
|
if timeout is not None:
|
|
GLib.timeout_add_seconds(timeout, self.remove, box)
|
|
|
|
def append_safe(self, box, timeout=None):
|
|
"""
|
|
Thread-safe version of append().
|
|
"""
|
|
def _append():
|
|
self.append(box, timeout=timeout)
|
|
GLib.idle_add(_append)
|
|
|
|
def remove(self, box):
|
|
"""
|
|
Remove a NotificationBox from this notification
|
|
area, if there.
|
|
"""
|
|
if box in self._box.get_children():
|
|
self._box.remove(box)
|
|
|
|
def remove_safe(self, box):
|
|
"""
|
|
Thread-safe version of remove().
|
|
"""
|
|
GLib.idle_add(self.remove, box)
|
|
|
|
def clear(self):
|
|
"""
|
|
Clear all the notifications.
|
|
"""
|
|
for child in self._box.get_children():
|
|
self._box.remove(child)
|
|
|
|
def clear_safe(self):
|
|
"""
|
|
Thread-safe version of clear().
|
|
"""
|
|
GLib.idle_add(self.clear)
|
|
|
|
|
|
class Rigo(Gtk.Application):
|
|
|
|
class RigoHandler:
|
|
|
|
def onDeleteWindow(self, *args):
|
|
while True:
|
|
try:
|
|
entropy.tools.kill_threads()
|
|
Gtk.main_quit(*args)
|
|
except KeyboardInterrupt:
|
|
continue
|
|
break
|
|
|
|
def __init__(self):
|
|
self._builder = Gtk.Builder()
|
|
self._builder.add_from_file(os.path.join(DATA_DIR, "ui/gtk3/rigo.ui"))
|
|
self._builder.connect_signals(Rigo.RigoHandler())
|
|
self._window = self._builder.get_object("window1")
|
|
self._app_vbox = self._builder.get_object("appVbox")
|
|
self._search_entry = self._builder.get_object("searchEntry")
|
|
self._scrolled_view = self._builder.get_object("scrolledView")
|
|
self._static_view = self._builder.get_object("staticViewVbox")
|
|
icons = get_sc_icon_theme(DATA_DIR)
|
|
self._view = AppTreeView(self._app_vbox, icons, True,
|
|
AppListStore.ICON_SIZE, store=None)
|
|
self._scrolled_view.add(self._view)
|
|
|
|
self._notification = self._builder.get_object("notificationBox")
|
|
self._welcome_box = WelcomeBox()
|
|
|
|
settings = Gtk.Settings.get_default()
|
|
settings.set_property("gtk-error-bell", False)
|
|
# wire up the css provider to reconfigure on theme-changes
|
|
self._window.connect("style-updated",
|
|
self._on_style_updated,
|
|
init_sc_css_provider,
|
|
settings,
|
|
Gdk.Screen.get_default(),
|
|
DATA_DIR)
|
|
|
|
self._entropy = Client()
|
|
self._entropy_ws = EntropyWebService(self._entropy)
|
|
|
|
self._state_mutex = Lock()
|
|
self._current_state = Rigo.STATIC_VIEW_STATE
|
|
self._pvc = PackagesViewController(
|
|
self._entropy, icons, self._entropy_ws,
|
|
self, self._search_entry, self._view)
|
|
self._nc = NotificationController(
|
|
self._entropy, self._pvc, self._notification)
|
|
|
|
BROWSER_VIEW_STATE = 1
|
|
STATIC_VIEW_STATE = 2
|
|
|
|
def change_view_state(self, state, child_widget=None):
|
|
"""
|
|
Change Rigo Application UI state.
|
|
You can pass a custom widget that will be shown in case
|
|
of static view state.
|
|
"""
|
|
with self._state_mutex:
|
|
if state == Rigo.BROWSER_VIEW_STATE:
|
|
self._static_view.set_visible(False)
|
|
# release all the childrens of static_view
|
|
for child in self._static_view.get_children():
|
|
self._static_view.remove(child)
|
|
self._scrolled_view.set_visible(True)
|
|
self._scrolled_view.show()
|
|
|
|
elif state == Rigo.STATIC_VIEW_STATE:
|
|
self._scrolled_view.set_visible(False)
|
|
if child_widget is not None:
|
|
for child in self._static_view.get_children():
|
|
self._static_view.remove(child)
|
|
self._static_view.pack_start(child_widget,
|
|
False, False, 0)
|
|
child_widget.show()
|
|
else:
|
|
# keep the current widget if any, or add the
|
|
# welcome widget
|
|
if not self._static_view.get_children():
|
|
self._welcome_box.show()
|
|
self._static_view.pack_start(self._welcome_box,
|
|
False, False, 0)
|
|
|
|
self._static_view.set_visible(True)
|
|
self._static_view.show()
|
|
|
|
else:
|
|
raise AttributeError("wrong view state")
|
|
|
|
self._current_state = state
|
|
|
|
def change_view_state_safe(self, state, child_widget=None):
|
|
"""
|
|
Thread-safe version of change_view_state().
|
|
"""
|
|
def _do_change():
|
|
return self.change_view_state(state, child_widget=child_widget)
|
|
GLib.idle_add(_do_change)
|
|
|
|
def _on_style_updated(self, widget, init_css_callback, *args):
|
|
"""
|
|
Gtk Style callback, nothing to see here.
|
|
"""
|
|
init_css_callback(widget, *args)
|
|
|
|
def _show_ok_dialog(self, parent, title, message):
|
|
"""
|
|
Show ugly OK dialog window.
|
|
"""
|
|
dlg = Gtk.MessageDialog(parent=parent,
|
|
type=Gtk.MessageType.INFO,
|
|
buttons=Gtk.ButtonsType.OK)
|
|
dlg.set_markup(message)
|
|
dlg.set_title(title)
|
|
dlg.run()
|
|
dlg.destroy()
|
|
|
|
def _permissions_setup(self):
|
|
"""
|
|
Check execution privileges and spawn the Rigo UI.
|
|
"""
|
|
if not entropy.tools.is_user_in_entropy_group():
|
|
# otherwise the lock handling would potentially
|
|
# fail.
|
|
self._show_ok_dialog(
|
|
None,
|
|
_("Not authorized"),
|
|
_("You are not authorized to run Rigo"))
|
|
entropy.tools.kill_threads()
|
|
Gtk.main_quit()
|
|
return
|
|
|
|
acquired = entropy.tools.acquire_entropy_locks(
|
|
self._entropy, shared=True, max_tries=1)
|
|
if not acquired:
|
|
self._show_ok_dialog(
|
|
None,
|
|
_("Rigo"),
|
|
_("Another Application Manager is active"))
|
|
entropy.tools.kill_threads()
|
|
Gtk.main_quit()
|
|
return
|
|
|
|
self._pvc.setup()
|
|
self._nc.setup()
|
|
self._window.show()
|
|
|
|
def run(self):
|
|
self._welcome_box.render()
|
|
self.change_view_state(self._current_state)
|
|
GLib.idle_add(self._permissions_setup)
|
|
|
|
GLib.threads_init()
|
|
Gdk.threads_enter()
|
|
Gtk.main()
|
|
Gdk.threads_leave()
|
|
entropy.tools.kill_threads()
|
|
|
|
if __name__ == "__main__":
|
|
app = Rigo()
|
|
app.run()
|