| Server IP : 107.13.46.68 / Your IP : 216.73.216.232 Web Server : Apache/2.4.58 (Ubuntu) System : Linux mariOS 6.8.0-51-generic #52-Ubuntu SMP PREEMPT_DYNAMIC Thu Dec 5 13:09:44 UTC 2024 x86_64 User : www-data ( 33) PHP Version : 8.3.6 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /lib/linuxmint/mintinstall/ |
Upload File : |
#!/usr/bin/python3
# encoding=utf-8
# -*- coding: UTF-8 -*-
import sys
import os
import gettext
import threading
import locale
import urllib.request, urllib.parse, urllib.error
import random
from datetime import datetime
import subprocess
import platform
import time
import json
import re
import math
from pathlib import Path
import traceback
from operator import attrgetter
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('XApp', '1.0')
from gi.repository import Gtk, Gdk, GdkPixbuf, GObject, GLib, Gio, XApp, Pango
import cairo
from mintcommon.installer import installer
from mintcommon.installer import dialogs
import prefs
import reviews
import housekeeping
from misc import print_timing, networking_available
import imaging
from screenshot_window import ScreenshotWindow
from math import pi
DEGREES = pi / 180
#Hardcoded mouse back button key for button-press-event
#May not work on all mice
MOUSE_BACK_BUTTON = 8
#How many milliseconds between banner slides
BANNER_TIMER = 500
# package type combobox columns
# index, label, icon-name, tooltip, pkginfo
PACKAGE_TYPE_COMBO_INDEX = 0
PACKAGE_TYPE_COMBO_LABEL = 1
PACKAGE_TYPE_COMBO_SUMMARY = 2
PACKAGE_TYPE_COMBO_ICON_NAME = 3
PACKAGE_TYPE_COMBO_PKGINFO = 4
# Don't let mintinstall run as root
if os.getuid() == 0:
print("The software manager should not be run as root. Please run it in user mode.")
sys.exit(1)
# i18n
APP = 'mintinstall'
LOCALE_DIR = "/usr/share/linuxmint/locale"
locale.bindtextdomain(APP, LOCALE_DIR)
gettext.bindtextdomain(APP, LOCALE_DIR)
gettext.textdomain(APP)
_ = gettext.gettext
import setproctitle
setproctitle.setproctitle("mintinstall")
Gtk.IconTheme.get_default().append_search_path("/usr/share/linuxmint/mintinstall")
# List of aliases
ALIASES = {}
ALIASES['spotify-client'] = "Spotify"
ALIASES['steam-installer'] = "Steam"
ALIASES['minecraft-launcher'] = "Minecraft"
ALIASES['virtualbox-qt'] = "Virtualbox " # Added a space to force alias
ALIASES['virtualbox'] = "Virtualbox (base)"
ALIASES['sublime-text'] = "Sublime"
ALIASES['mint-meta-codecs'] = _("Multimedia Codecs")
ALIASES['mint-meta-codecs-kde'] = _("Multimedia Codecs for KDE")
ALIASES['mint-meta-debian-codecs'] = _("Multimedia Codecs")
ALIASES['firefox'] = "Firefox"
ALIASES['vlc'] = "VLC"
ALIASES['mpv'] = "Mpv"
ALIASES['gimp'] = "Gimp"
ALIASES['gnome-maps'] = "GNOME Maps"
ALIASES['thunderbird'] = "Thunderbird"
ALIASES['pia-manager'] = "PIA Manager"
ALIASES['skypeforlinux'] = "Skype"
ALIASES['google-earth-pro-stable'] = "Google Earth"
ALIASES['whatsapp-desktop'] = "WhatsApp"
ALIASES['wine-installer'] = "Wine"
libdir = os.path.join("/usr/lib/linuxmint/mintinstall")
with open(os.path.join(libdir, "apt_flatpak_match_data.info")) as f:
match_data = json.load(f)
FLATPAK_EQUIVS = match_data["apt_flatpak_matches"]
DEB_EQUIVS = dict((v, k) for k,v in FLATPAK_EQUIVS.items())
pkg_tile_ui = "/usr/share/linuxmint/mintinstall/mintinstall.gresource"
UI_RESOURCES = Gio.Resource.load(pkg_tile_ui)
UI_RESOURCES._register()
KB = 1000
MB = KB * 1000
def get_size_for_display(size):
if size == 0:
return ""
if size > (5 * MB):
size = (size // MB) * MB
elif size > KB:
size = (size // KB) * KB
formatted = GLib.format_size(size).replace(".0", "")
return formatted
class NonScrollingComboBox(Gtk.ComboBox):
def __init__(self, area):
Gtk.ComboBox.__init__(self, cell_area=area, height_request=36)
def do_scroll_event(self, event, data=None):
# Skip Gtk.ComboBox's default handler.
#
# Connecting to a Gtk.ComboBox and stopping a scroll-event
# prevents unintentional combobox changes, but also breaks
# any scrollable parents when passing over the combobox.
Gtk.Widget.do_scroll_event(self, event)
class HeadingMenuItem(Gtk.MenuItem):
def __init__(self, *args, **kargs):
Gtk.MenuItem.__init__(self, *args, **kargs)
label = self.get_child()
if (isinstance(label, Gtk.Label)):
label.set_use_markup(True)
label.set_markup("<b>%s</b>" % label.get_label())
def do_button_press_event(self, event):
return Gdk.EVENT_STOP
def do_button_release_event(self, event):
return Gdk.EVENT_STOP
def do_key_press_event(self, event):
return Gdk.EVENT_STOP
def do_key_release_event(self, event):
return Gdk.EVENT_STOP
def do_enter_notify_event(self, event):
return Gdk.EVENT_STOP
class FlatpakAddonRow(Gtk.ListBoxRow):
def __init__(self, app, parent_pkginfo, addon_pkginfo, name_size_group, button_size_group):
Gtk.ListBoxRow.__init__(self)
self.box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=4, margin_start=10, margin_end=10, margin_top=4, margin_bottom=4)
self.add(self.box)
self.app = app
self.parent_pkginfo = parent_pkginfo
self.addon_pkginfo = addon_pkginfo
self.spinner = Gtk.Spinner(active=True, no_show_all=True, visible=True)
self.box.pack_start(self.spinner, False, False, 0)
self.action = Gtk.Button(label="",
sensitive=False,
image=self.spinner,
always_show_image=True,
valign=Gtk.Align.CENTER,
no_show_all=True)
self.action.set_size_request(100, -1)
button_size_group.add_widget(self.action)
self.action.connect("clicked", self.action_clicked)
self.box.pack_end(self.action, False, False, 0)
label_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4)
self.box.pack_start(label_box, False, False, 0)
name = Gtk.Label(label="<b>%s</b>" % addon_pkginfo.get_display_name(), use_markup=True, xalign=0.0, selectable=True)
name_size_group.add_widget(name)
label_box.pack_start(name, False, False, 0)
summary = Gtk.Label(label=addon_pkginfo.get_summary(), xalign=0.0, wrap=True, max_width_chars=60, selectable=True)
label_box.pack_start(summary, False, False, 0)
if not self.app.installer.pkginfo_is_installed(self.parent_pkginfo):
self.action.hide()
self.set_sensitive(False)
return
self.action.show()
self.update_button()
def update_button(self):
if not self.app.installer.pkginfo_is_installed(self.addon_pkginfo):
self.action.set_label(_("Add"))
self.action.set_sensitive(True)
self.action.get_style_context().add_class("suggested-action")
self.action.get_style_context().remove_class("destructive-action")
self.spinner.hide()
else:
self.action.set_label(_("Remove"))
self.action.set_sensitive(True)
self.action.get_style_context().add_class("destructive-action")
self.action.get_style_context().remove_class("suggested-action")
self.spinner.hide()
def info_ready(self, task):
self.app.installer.execute_task(task)
self.action.set_label("")
self.app.update_activity_widgets()
def info_error(self, task):
self.task = task
self.spinner.hide()
self.action.set_sensitive(False)
self.action.set_label(_("Unavailable"))
self.action.get_style_context().remove_class("suggested-action")
self.action.get_style_context().remove_class("destructive-action")
def action_clicked(self, widget):
self.app.installer.select_pkginfo(self.addon_pkginfo,
self.info_ready, self.info_error,
self.installer_finished, self.installer_progress,
use_mainloop=True)
def installer_finished(self, task):
self.app.update_activity_widgets()
self.update_button()
def installer_progress(self, pkginfo, progress, estimating, status_text=None):
self.spinner.show()
class SaneProgressBar(Gtk.DrawingArea):
def __init__(self):
super(Gtk.DrawingArea, self).__init__(width_request=-1,
height_request=8,
margin_top=1, #??? to align better with the stars and count
hexpand=True,
valign=Gtk.Align.CENTER,
visible=True)
self.connect("draw", self.draw_bar)
self.fraction = 0
def update_colors(self):
context = self.get_style_context()
ret, self.fill_color = context.lookup_color("fg_color")
if not ret:
self.fill_color = Gdk.RGBA()
self.fill_color.parse("grey")
ret, self.trough_color = context.lookup_color("bg_color")
if not ret:
self.trough_color = Gdk.RGBA()
self.trough_color.parse("white")
ret, self.border_color = context.lookup_color("borders_color")
if not ret:
self.border_color = Gdk.RGBA()
self.border_color.parse("grey")
def draw_bar(self, widget, cr):
self.update_colors()
allocation = self.get_allocation()
cr.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
cr.save()
cr.set_line_width(1)
self.rounded_rect(cr, 1, 1, allocation.width - 2, allocation.height - 2)
cr.clip()
Gdk.cairo_set_source_rgba(cr, self.trough_color)
self.rounded_rect(cr, 1, 1, allocation.width - 2, allocation.height - 2)
cr.fill()
Gdk.cairo_set_source_rgba(cr, self.fill_color)
self.rounded_rect(cr, -20, 1, allocation.width * self.fraction + 20, allocation.height - 2)
cr.fill()
Gdk.cairo_set_source_rgba(cr, self.border_color)
self.rounded_rect(cr, 1, 1, allocation.width - 2, allocation.height - 2)
cr.stroke()
cr.restore()
return True
def rounded_rect(self, cr, x, y, width, height):
radius = 3
cr.new_sub_path()
cr.arc(x + radius, y + radius, radius, 180 * DEGREES, 270 * DEGREES)
cr.arc(x + width - radius, y + radius, radius, -90 * DEGREES, 0 * DEGREES)
cr.arc(x + width - radius, y + height - radius, radius, 0 * DEGREES, 90 * DEGREES)
cr.arc(x + radius, y + height - radius, radius, 90 * DEGREES, 180 * DEGREES)
cr.close_path()
def set_fraction(self, fraction):
self.fraction = fraction
self.queue_draw()
class BannerTile(Gtk.FlowBoxChild):
def __init__(self, pkginfo, installer, name, background, color, is_flatpak, app_json, on_clicked_action):
super(Gtk.FlowBoxChild, self).__init__()
self.pkginfo = pkginfo
self.installer = installer
self.is_flatpak = is_flatpak
self.init_name = name
self.background = background
self.color = color
self.image_uri = (f"/usr/share/linuxmint/mintinstall/featured/{name}.svg")
css = """
#BannerTile {
background: %(background)s;
color: %(color)s;
padding: 12px;
border-radius: 5px;
}
#BannerTitle {
color: %(color)s;
font-weight: bold;
font-size: 48px;
padding-top: 12px;
}
#BannerSummary {
color: %(color)s;
font-weight: normal;
font-size: 16px;
padding-top: 10px;
}
#BannerFlatpakLabel {
font-weight: normal;
font-size: 12px;
}
""" % {'background':background, 'color':color}
self.set_name("BannerTile")
style_provider = Gtk.CssProvider()
style_provider.load_from_data(str.encode(css))
""" Have to reuse .add_provider(style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) for every widget because
add_provider doesn't cascade to children, and can't set the whole screen context for multiple tiles
without making them uniform """
self.get_style_context().add_provider(style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
label_name = Gtk.Label(xalign=0)
label_name.set_label(pkginfo.get_display_name())
label_name.set_name("BannerTitle")
label_name.get_style_context().add_provider(style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
label_summary = Gtk.Label(xalign=0)
label_summary.set_label(pkginfo.get_summary())
label_summary.set_name("BannerSummary")
label_summary.get_style_context().add_provider(style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
image = Gtk.Image.new_from_file(self.image_uri)
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12, halign=Gtk.Align.START)
vbox.get_style_context().add_provider(style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
vbox.set_border_width(6)
vbox.pack_start(label_name, False, False, 0)
vbox.pack_start(label_summary, False, False, 0)
if self.is_flatpak:
box_flatpak = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
box_flatpak.get_style_context().add_provider(style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
box_flatpak.pack_start(Gtk.Image.new_from_icon_name("mintinstall-package-flatpak-symbolic", Gtk.IconSize.MENU), False, False, 0)
label_flatpak = Gtk.Label(label="Flathub")
label_flatpak.set_name("BannerFlatpakLabel")
label_flatpak.get_style_context().add_provider(style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
box_flatpak.pack_start(label_flatpak, False, False, 0)
vbox.pack_start(box_flatpak, False, False, 0)
hbox = Gtk.Box(spacing=24)
hbox.pack_start(image, False, False, 0)
hbox.pack_start(vbox, True, True, 0)
hbox.show_all()
self.add(hbox)
self.box = hbox
class PackageTile(Gtk.FlowBoxChild):
def __init__(self, pkginfo, installer, show_package_type=False, review_info=None):
super(PackageTile, self).__init__()
self.button = Gtk.Button();
self.button.connect("clicked", self._activate_fb_child)
self.button.set_can_focus(False)
self.add(self.button)
self.pkginfo = pkginfo
self.installer = installer
self.review_info = review_info
self.show_package_type = show_package_type
self.pkg_category = ''
if len(pkginfo.categories) > 0:
if len(pkginfo.categories) == 1:
self.pkg_category = pkginfo.categories[0]
else:
self.pkg_category = pkginfo.categories[1]
self.builder = Gtk.Builder.new_from_resource("/com/linuxmint/mintinstall/package-tile.glade")
self.overlay = self.builder.get_object("vertical_package_tile")
self.button.add(self.overlay)
self.icon_holder = self.builder.get_object("icon_holder")
self.package_label = self.builder.get_object("package_label")
self.package_summary = self.builder.get_object("package_summary")
self.package_type_box = self.builder.get_object("package_type_box")
self.package_type_emblem = self.builder.get_object("package_type_emblem")
self.package_type_name = self.builder.get_object("package_type_name")
self.installed_mark = self.builder.get_object("installed_mark")
self.verified_mark = self.builder.get_object("verified_mark")
self.icon = None
self.repopulate_tile()
def repopulate_tile(self):
if self.icon is not None:
self.icon.destroy()
icon_string = self.pkginfo.get_icon(imaging.FEATURED_ICON_SIZE)
if not icon_string:
icon_string = imaging.FALLBACK_PACKAGE_ICON_PATH
self.icon = imaging.get_icon(icon_string, imaging.FEATURED_ICON_SIZE)
self.icon_holder.add(self.icon)
display_name = self.pkginfo.get_display_name()
self.package_label.set_label(display_name)
summary = self.pkginfo.get_summary()
self.package_summary.set_label(summary)
if self.show_package_type:
if self.pkginfo.pkg_hash.startswith("f"):
remote_info = None
try:
remote_info = self.installer.get_remote_info_for_name(self.pkginfo.remote)
if remote_info:
self.package_type_name.set_label(remote_info.title)
except:
pass
if remote_info is None:
self.package_type_name.set_label(self.pkginfo.remote.capitalize())
self.package_type_emblem.set_from_icon_name("mintinstall-package-flatpak-symbolic", Gtk.IconSize.MENU)
self.package_type_box.show()
self.package_type_box.set_tooltip_text(_("This package is a Flatpak"))
else:
self.package_type_name.hide()
self.package_type_emblem.hide()
if self.pkginfo.verified:
self.builder.get_object("review_info_box").show()
if self.review_info:
self.fill_rating_widget(self.review_info)
else:
self.builder.get_object("unsafe_box").show()
self.show_all()
self.refresh_state()
def _activate_fb_child(self, widget):
self.activate()
def refresh_state(self):
self.installed = self.installer.pkginfo_is_installed(self.pkginfo)
if self.installed:
self.installed_mark.set_from_icon_name("mintinstall-installed", Gtk.IconSize.MENU)
else:
self.installed_mark.clear()
def fill_rating_widget(self, review_info):
rating = str(review_info.avg_rating)
num_reviews_label = self.builder.get_object("num_reviews_label")
num_reviews_label.set_label(rating)
class ReviewTile(Gtk.ListBoxRow):
def __init__(self, username, date, comment, rating):
super(Gtk.ListBoxRow, self).__init__()
main_box = Gtk.Box()
main_box.set_margin_start(12)
main_box.set_margin_end(12)
main_box.set_margin_top(12)
main_box.set_margin_bottom(12)
ratings_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
stars_box = Gtk.Box()
for i in range(rating):
stars_box.pack_start(Gtk.Image.new_from_icon_name("starred-symbolic", Gtk.IconSize.MENU), False, False, 0)
for i in range(5 - rating):
stars_box.pack_start(Gtk.Image.new_from_icon_name("non-starred-symbolic", Gtk.IconSize.MENU), False, False, 0)
ratings_box.pack_start(stars_box, False, False, 0)
label_name = Gtk.Label(xalign=0.0)
label_name.set_markup("<small>%s</small>" % GLib.markup_escape_text(username))
ratings_box.pack_start(label_name, False, False, 0)
label_date = Gtk.Label(xalign=0.0)
label_date.set_markup("<small>%s</small>" % GLib.markup_escape_text(date))
ratings_box.pack_start(label_date, False, False, 0)
label_comment = Gtk.Label(xalign=0.0, selectable=True)
label_comment.set_label(comment)
label_comment.set_line_wrap(True)
comment_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
comment_box.set_margin_start(12)
comment_box.pack_start(label_comment, False, False, 0)
main_box.pack_start(ratings_box, False, False, 0)
main_box.pack_start(comment_box, True, True, 0)
self.add(main_box)
class Category:
def __init__(self, name, parent, categories, icon_name=""):
self.name = name
self.parent = parent
self.icon_name = icon_name
self.subcategories = []
self.pkginfos = []
self.matchingPackages = []
if parent is not None:
parent.subcategories.append(self)
if categories is not None:
categories.append(self)
cat = self
while cat.parent is not None:
cat = cat.parent
class SortPackage:
def __init__(self, pkg):
self.pkg = pkg
self.name = pkg.name
self.unverified = not pkg.verified
self.installed = False
self.score_desc = 0
self.search_tier = pkg.search_tier if hasattr(pkg, "search_tier") else 0
class SubcategoryFlowboxChild(Gtk.FlowBoxChild):
def __init__(self, category, is_all=False, active=False):
super(Gtk.FlowBoxChild, self).__init__()
self.category = category
cat_name = category.name
cat_icon = category.icon_name
if is_all:
cat_name = _("All")
cat_icon = "mintinstall-all-symbolic"
self.button = Gtk.ToggleButton(active=active)
self.button.connect("clicked", self._activate_fb_child)
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6, valign=Gtk.Align.CENTER)
image = Gtk.Image(icon_name=cat_icon, icon_size=Gtk.IconSize.MENU)
label = Gtk.Label(label=cat_name)
box.pack_start(image, False, False, 0)
box.pack_start(label, False, False, 0)
box.show_all()
self.button.add(box)
self.add(self.button)
def _activate_fb_child(self, widget):
self.activate()
class CategoryButton(Gtk.Button):
def __init__(self, category):
super(Gtk.Button, self).__init__()
self.category = category
self.set_can_focus(False)
self.set_hexpand(True)
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6, valign=Gtk.Align.CENTER)
image = Gtk.Image(icon_name=category.icon_name, icon_size=Gtk.IconSize.MENU)
label = Gtk.Label(label=category.name)
box.pack_start(image, False, False, 0)
box.pack_start(label, False, False, 0)
box.show_all()
self.add(box)
class Application(Gtk.Application):
(ACTION_TAB, PROGRESS_TAB, SPINNER_TAB) = list(range(3))
PAGE_LANDING = "landing"
PAGE_LIST = "list"
PAGE_DETAILS = "details"
PAGE_LOADING = "loading"
PAGE_SEARCHING = "searching"
PAGE_GENERATING_CACHE = "generating_cache"
PAGE_PREFS = "prefs"
def __init__(self):
super(Application, self).__init__(application_id='com.linuxmint.mintinstall',
flags=Gio.ApplicationFlags.HANDLES_OPEN | Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
self.gui_ready = False
self.start_time = time.time()
self.low_res = self.get_low_res_screen()
self.settings = Gio.Settings(schema_id="com.linuxmint.install")
self.arch = platform.machine()
print("MintInstall: Detected system architecture: '%s'" % self.arch)
self.locale = os.getenv('LANGUAGE')
if self.locale is None:
self.locale = "C"
else:
self.locale = self.locale.split("_")[0]
self.installer = installer.Installer()
self.installer.connect("appstream-changed", self.on_appstream_changed)
self.task_cancellable = None
self.current_task = None
self.recursion_buster = False
self.install_on_startup_file = None
self.review_cache = None
self.current_pkginfo = None
self.current_category = None
self.flatpak_remote_categories = {}
self.picks_tiles = []
self.category_tiles = []
self.one_package_idle_timer = 0
self.installer_pulse_timer = 0
self.search_changed_timer = 0
self.search_idle_timer = 0
self.action_button_signal_id = 0
self.launch_button_signal_id = 0
self.banner_app_name = None
self.featured_app_names = []
self.add_categories()
self.main_window = None
@print_timing
def do_activate(self):
if self.main_window is None:
self.create_window(self.PAGE_LOADING)
self.add_window(self.main_window)
self.update_conditional_widgets()
t = threading.Thread(target=self._init_installer_thread, args=[])
t.start()
self.main_window.present()
def _init_installer_thread(self):
if self.installer.init_sync():
GLib.idle_add(self.on_installer_ready)
else:
self.page_stack.set_visible_child_name(self.PAGE_GENERATING_CACHE)
self.installer.init(self.on_installer_ready)
def do_command_line(self, command_line, data=None):
Gtk.Application.do_command_line(self, command_line)
args = command_line.get_arguments()
num = len(args)
if num > 1 and args[1] == "list":
sys.exit(self.export_listing(flatpak_only=False))
elif num > 1 and args[1] == "list-flatpak":
sys.exit(self.export_listing(flatpak_only=True))
elif num == 3 and args[1] == "install":
for try_method in (Gio.File.new_for_path, Gio.File.new_for_uri):
file = try_method(args[2])
if file.query_exists(None):
self.open([file], "")
self.activate()
return 0
print("MintInstall: file not found", args[2])
sys.exit(1)
elif num > 1:
print("MintInstall: Unknown arguments", args[1:])
sys.exit(1)
self.activate()
return 0
def do_open(self, files, num, hint):
if self.gui_ready:
self.handle_command_line_install(files[0])
else:
self.install_on_startup_file = files[0]
def handle_command_line_install(self, file):
if file.get_path().endswith(".flatpakrepo"):
if self.installer.is_busy():
dialog = Gtk.MessageDialog(self.main_window,
Gtk.DialogFlags.MODAL,
Gtk.MessageType.WARNING,
Gtk.ButtonsType.OK,
_("Cannot process this file while there are active operations.\nPlease try again after they finish."))
res = dialog.run()
dialog.destroy()
return
self.start_add_new_flatpak_remote(file)
elif file.get_path().endswith(".flatpakref"):
self.installer.get_pkginfo_from_ref_file(file, self.on_pkginfo_from_uri_complete)
def start_add_new_flatpak_remote(self, file):
self.page_stack.set_visible_child_name(self.PAGE_GENERATING_CACHE)
self.installer.add_remote_from_repo_file(file, self.add_new_flatpak_remote_finished)
def add_new_flatpak_remote_finished(self, file=None, error=None):
if error:
if error == "exists":
dialog = Gtk.MessageDialog(self.main_window,
Gtk.DialogFlags.MODAL,
Gtk.MessageType.WARNING,
Gtk.ButtonsType.OK,
_("The Flatpak repo you are trying to add already exists."))
res = dialog.run()
dialog.destroy()
elif error == "error":
dialog = Gtk.MessageDialog(self.main_window,
Gtk.DialogFlags.MODAL,
Gtk.MessageType.ERROR,
Gtk.ButtonsType.OK,
_("An error occurred attempting to add the Flatpak repo."))
res = dialog.run()
dialog.destroy()
elif error == "no-flatpak-support":
dialog = Gtk.MessageDialog(self.main_window,
Gtk.DialogFlags.MODAL,
Gtk.MessageType.ERROR,
Gtk.ButtonsType.OK,
_("Flatpak support is not currently available. Try installing flatpak."))
res = dialog.run()
dialog.destroy()
elif error == "cancel":
pass
self.finish_loading_visual()
return
self.add_categories()
self.installer.init(self.on_installer_ready)
def on_pkginfo_from_uri_complete(self, pkginfo, error=None):
if error:
if error == "no-flatpak-support":
dialog = Gtk.MessageDialog(self.main_window,
Gtk.DialogFlags.MODAL,
Gtk.MessageType.ERROR,
Gtk.ButtonsType.OK,
_("Flatpak support is not currently available. Try installing flatpak and gir1.2-flatpak-1.0."))
res = dialog.run()
dialog.destroy()
self.finish_loading_visual()
return
if pkginfo:
self.show_package(pkginfo, self.PAGE_LANDING)
def get_low_res_screen(self):
display = Gdk.Display.get_default()
pointer = display.get_default_seat().get_pointer()
height = 9999
if pointer:
position = pointer.get_position()
monitor = display.get_monitor_at_point(position.x, position.y)
height = monitor.get_geometry().height
# If it's less than our threshold than consider us 'low res'. The workarea being used is in
# app pixels, so hidpi will also be affected here regardless of device resolution.
if height < 800:
print("MintInstall: low resolution detected (%dpx height), limiting window height." % (height))
return True
return False
def create_window(self, starting_page):
if self.main_window is not None:
print("MintInstall: create_window called, but we already had one!")
return
# Build the GUI
glade_file = "/usr/share/linuxmint/mintinstall/mintinstall.glade"
self.builder = Gtk.Builder.new_from_resource("/com/linuxmint/mintinstall/mintinstall.glade")
self.main_window = self.builder.get_object("main_window")
self.main_window.set_title(_("Software Manager"))
GLib.set_application_name(_("Software Manager"))
self.main_window.set_icon_name("mintinstall")
self.main_window.connect("delete_event", self.close_application)
self.main_window.connect("key-press-event", self.on_keypress)
self.main_window.connect("button-press-event", self.on_buttonpress)
self.detail_view_icon = imaging.AsyncImage()
self.detail_view_icon.show()
self.builder.get_object("application_icon_holder").add(self.detail_view_icon)
self.status_label = self.builder.get_object("label_ongoing")
self.progressbar = self.builder.get_object("progressbar1")
self.progress_box = self.builder.get_object("progress_box")
self.action_button = self.builder.get_object("action_button")
self.launch_button = self.builder.get_object("launch_button")
self.unsafe_box = self.builder.get_object("unsafe_box")
self.active_tasks_button = self.builder.get_object("active_tasks_button")
self.active_tasks_spinner = self.builder.get_object("active_tasks_spinner")
self.no_packages_found_label = self.builder.get_object("no_packages_found_label")
self.no_packages_found_refresh_button = self.builder.get_object("no_packages_found_refresh_button")
self.no_packages_found_refresh_button.connect("clicked", self.on_refresh_cache_clicked)
self.progress_label = DottedProgressLabel()
self.progress_box.pack_start(self.progress_label, False, False, 0)
self.progress_label.show()
box_reviews = self.builder.get_object("box_reviews")
def list_header_func(row, before, user_data=None):
if before and not row.get_header():
row.set_header(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL))
box_reviews.set_header_func(list_header_func, None)
# Build the menu
submenu = Gtk.Menu()
self.installed_menuitem = Gtk.MenuItem(label=_("Show installed applications"))
self.installed_menuitem.connect("activate", self.show_installed_apps)
self.installed_menuitem.show()
submenu.append(self.installed_menuitem)
separator = Gtk.SeparatorMenuItem()
separator.show()
submenu.append(separator)
self.refresh_cache_menuitem = Gtk.MenuItem(label=_("Refresh the list of packages"))
self.refresh_cache_menuitem.connect("activate", self.on_refresh_cache_clicked)
self.refresh_cache_menuitem.show()
self.refresh_cache_menuitem.set_sensitive(False)
submenu.append(self.refresh_cache_menuitem)
software_sources_menuitem = Gtk.MenuItem(label=_("Software sources"))
software_sources_menuitem.connect("activate", self.open_software_sources)
software_sources_menuitem.show()
submenu.append(software_sources_menuitem)
self.prefs_menuitem = Gtk.MenuItem(label=_("Preferences"))
self.prefs_menuitem.connect("activate", self.on_prefs_clicked)
self.prefs_menuitem.show()
self.prefs_menuitem.set_sensitive(True)
submenu.append(self.prefs_menuitem)
separator = Gtk.SeparatorMenuItem()
separator.show()
submenu.append(separator)
about_menuitem = Gtk.MenuItem(label=_("About"))
about_menuitem.connect("activate", self.open_about)
about_menuitem.show()
submenu.append(about_menuitem)
menu_button = self.builder.get_object("menu_button")
menu_button.connect("clicked", self.on_menu_button_clicked, submenu)
flowbox = Gtk.FlowBox()
flowbox.set_min_children_per_line(3)
flowbox.set_max_children_per_line(10)
flowbox.set_row_spacing(0)
flowbox.set_column_spacing(0)
flowbox.set_homogeneous(True)
flowbox.connect("child-activated", self.on_app_row_activated, self.PAGE_LIST)
flowbox.connect("selected-children-changed", self.navigate_flowbox, self.builder.get_object("scrolledwindow_applications"))
self.flowbox_applications = flowbox
box = self.builder.get_object("box_prefs")
warning_box = self.builder.get_object("box_unverified_warning")
box.pack_start(prefs.PrefsWidget(warning_box), True, True, 0)
box = self.builder.get_object("box_cat_page")
box.add(self.flowbox_applications)
self.back_button = self.builder.get_object("back_button")
self.back_button.connect("clicked", self.on_back_button_clicked)
self.previous_page = self.PAGE_LANDING
self.back_button.set_sensitive(False)
self.searchentry = self.builder.get_object("search_entry")
self.searchentry.connect("changed", self.on_entry_text_changed)
self.searchentry.connect("activate", self.on_search_entry_activated)
self.subsearch_toggle = self.builder.get_object("subsearch_toggle")
self.subsearch_toggle.set_active(self.settings.get_boolean(prefs.SEARCH_IN_CATEGORY))
self.subsearch_toggle.connect("toggled", self.on_subsearch_toggled)
self.active_tasks_button.connect("clicked", self.on_active_tasks_button_clicked)
self.update_activity_widgets()
self.page_stack = self.builder.get_object("page_stack")
self.page_stack.set_visible_child_name(starting_page)
self.addons_listbox = self.builder.get_object("box_addons")
self.addons_listbox.set_header_func(list_header_func, None)
self.package_details_listbox = self.builder.get_object("package_details_listbox")
self.app_list_stack = self.builder.get_object("app_list_stack")
self.flatpak_details_vgroup = XApp.VisibilityGroup.new(True, True,
(
self.builder.get_object("branch_row"),
self.builder.get_object("remote_row")
)
)
self.screenshot_controls_vgroup = XApp.VisibilityGroup.new(False, True,
[
self.builder.get_object("previous_ss_button"),
self.builder.get_object("next_ss_button"),
]
)
self.search_tool_item = self.builder.get_object("search_tool_item")
self.flowbox_featured = None
self.flowbox_top_rated = None
self.banner_tile = None
self.banner_dot_box = None
self.banner_stack = None
self.banner_slideshow_timeout_id = 0
self.package_type_store = Gtk.ListStore(int, str, str, str, object) # index, label, summary, icon-name, remotename, pkginfo
box = Gtk.CellAreaBox(orientation=Gtk.Orientation.HORIZONTAL, spacing=4)
cell = Gtk.CellRendererPixbuf(stock_size=Gtk.IconSize.BUTTON)
box.pack_start(cell, False, False, True)
box.add_attribute(cell, "icon-name", PACKAGE_TYPE_COMBO_ICON_NAME)
cell = Gtk.CellRendererText(xalign=0.0)
box.pack_start(cell, False, False, True)
box.add_attribute(cell, "text", PACKAGE_TYPE_COMBO_LABEL)
self.package_type_combo = NonScrollingComboBox(box)
self.package_type_combo.set_model(self.package_type_store)
self.package_type_combo.show_all()
self.package_type_combo_container = self.builder.get_object("package_type_combo_container")
self.package_type_combo_container.pack_start(self.package_type_combo, True, True, 0)
self.single_version_package_type_box = self.builder.get_object("single_version_package_type_box")
self.single_version_package_type_icon = self.builder.get_object("single_version_package_type_icon")
self.single_version_package_type_label = self.builder.get_object("single_version_package_type_label")
review_breakdown_grid = self.builder.get_object("review_breakdown_grid")
self.star_bars = []
for i in range(5, 0, -1):
bar = SaneProgressBar()
review_breakdown_grid.attach(bar, 1, i - 1, 1, 1)
self.star_bars.append(bar)
self.screenshot_stack = self.builder.get_object("screenshot_stack")
self.screenshot_stack.connect("notify::visible-child", self.on_screenshot_shown)
self.select_cursor = Gdk.Cursor.new_from_name(Gdk.Display.get_default(), "pointer")
self.screenshot_window = None
self.builder.get_object("previous_ss_button").connect("clicked", self.navigate_screenshot, Gtk.DirectionType.TAB_BACKWARD)
self.builder.get_object("next_ss_button").connect("clicked", self.navigate_screenshot, Gtk.DirectionType.TAB_FORWARD)
self.ss_swipe_handler = Gtk.GestureSwipe.new(self.screenshot_stack)
self.ss_swipe_handler.connect("swipe", self.screenshot_stack_swiped, self.screenshot_stack)
self.ss_swipe_handler.set_propagation_phase(Gtk.PropagationPhase.NONE)
# This is only for the screenshot stack at the moment, as I can't get key events from the stack, even
# with an event controller.
self.main_window.connect("key-press-event", self.on_window_key_press)
self.searchentry.grab_focus()
box = self.builder.get_object("box_subcategories")
self.subcat_flowbox = Gtk.FlowBox(homogeneous=True)
self.subcat_flowbox.set_min_children_per_line(1)
self.subcat_flowbox.set_max_children_per_line(4)
self.subcat_flowbox.set_row_spacing(0)
self.subcat_flowbox.set_column_spacing(0)
box.pack_start(self.subcat_flowbox, True, True, 0)
self.subcat_flowbox.connect("child-activated", self.on_subcategory_selected)
def refresh_cache(self):
self.refresh_cache_menuitem.set_sensitive(False)
self.page_stack.set_visible_child_name(self.PAGE_GENERATING_CACHE)
self.installer.force_new_cache(self._on_refresh_cache_complete)
def _on_refresh_cache_complete(self):
self.add_categories()
self.installer.init(self.on_installer_ready)
def on_prefs_clicked(self, widget, data=None):
self.previous_page = self.PAGE_LANDING
self.search_tool_item.set_sensitive(False)
self.back_button.set_sensitive(True)
self.page_stack.set_visible_child_name(self.PAGE_PREFS)
def on_refresh_cache_clicked(self, widget, data=None):
self.refresh_cache()
def on_appstream_changed(self, installer):
for tile in self.picks_tiles:
tile.repopulate_tile()
if self.banner_tile is not None:
self.banner_tile.repopulate_tile()
def on_installer_ready(self):
self.page_stack.set_visible_child_name(self.PAGE_LOADING)
try:
self.process_matching_packages()
self.apply_aliases()
self.review_cache = reviews.ReviewCache()
self.review_cache.connect("reviews-updated", self.load_landing_apps)
self.load_landing_apps()
self.load_categories_on_landing()
self.sync_installed_apps()
self.update_conditional_widgets()
GLib.idle_add(self.finished_loading_packages)
# Can take some time, don't block for it (these are categorizing packages based on apt info, not our listings)
GLib.idle_add(self.process_unmatched_packages)
housekeeping.run()
self.refresh_cache_menuitem.set_sensitive(True)
self.print_startup_time()
except Exception as e:
print("Loading error: %s" % e)
traceback.print_tb(e.__traceback__)
GLib.idle_add(self.refresh_cache)
def print_startup_time(self):
end_time = time.time()
print('Mintinstall startup took %0.3f ms' % ((end_time - self.start_time) * 1000.0,))
@print_timing
def load_banner(self):
box = self.builder.get_object("box_banner")
if self.low_res:
box.hide()
# This overrides the glade 800x600 defaults. 300 is excessively small so the window works
# out its own minimum height.
self.main_window.set_default_size(800, 500)
return
for child in box.get_children():
child.destroy()
overlay = Gtk.Overlay()
box.pack_start(overlay, True, True, 0)
self.banner_stack = Gtk.Stack()
self.banner_stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
self.banner_stack.set_transition_duration(BANNER_TIMER)
overlay.add(self.banner_stack)
self.banner_dot_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,
halign=Gtk.Align.CENTER,
valign=Gtk.Align.END)
overlay.add_overlay(self.banner_dot_box)
json_array = json.load(open("/usr/share/linuxmint/mintinstall/featured/featured.json", "r"))
random.shuffle(json_array)
selected_apps = set()
num_selected = 0
for app_json in json_array:
if num_selected >= 5:
break
name = app_json["name"]
background = app_json["background"]
color = app_json["text_color"]
if name in selected_apps:
continue
if name.startswith("flatpak:"):
name = name.replace("flatpak:", "")
pkginfo = self.installer.find_pkginfo(name, installer.PKG_TYPE_FLATPAK)
if pkginfo is None or not pkginfo.verified:
continue
is_flatpak = True
else:
pkginfo = self.installer.find_pkginfo(name, installer.PKG_TYPE_APT)
is_flatpak = False
if pkginfo is None:
continue
selected_apps.add(name)
num_selected += 1
flowbox = Gtk.FlowBox()
flowbox.set_min_children_per_line(1)
flowbox.set_max_children_per_line(1)
flowbox.set_row_spacing(0)
flowbox.set_column_spacing(0)
flowbox.set_homogeneous(True)
flowbox.connect("child-activated", self.on_flowbox_child_activated, self.PAGE_LANDING)
tile = BannerTile(pkginfo, self.installer, name, background, color, is_flatpak, app_json, self.on_banner_clicked)
flowbox.insert(tile, -1)
flowbox.show_all()
self.banner_stack.add_named(flowbox, str(len(self.banner_stack.get_children())))
icon = Gtk.Image.new_from_icon_name("mintinstall-banner-dot", Gtk.IconSize.MENU)
icon.set_pixel_size(5)
button_class_override = """
#BannerDotOverlay {
background-color: rgba(0, 0, 0, 0);
background-image: none;
border-color: rgba(0, 0, 0, 0);
min-height: 12px;
min-width: 22px;
-gtk-icon-shadow: none;
-gtk-icon-effect: none;
box-shadow: none;
}
"""
provider = Gtk.CssProvider()
provider.load_from_data(str.encode(button_class_override))
dot_button = Gtk.Button(
halign=Gtk.Align.CENTER,
valign=Gtk.Align.END,
name="BannerDotOverlay",
relief=Gtk.ReliefStyle.NONE,
can_focus=False,
image=icon
)
dot_button.get_style_context().add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
dot_button.connect("clicked", self.on_dot_clicked, len(self.banner_stack.get_children()) - 1)
self.banner_dot_box.pack_start(dot_button, False, False, 0)
self.update_dot_buttons(0)
box.show_all()
def on_dot_clicked(self, button, index):
self.start_slideshow_timer()
self.banner_stack.set_visible_child_name(str(index))
self.update_dot_buttons(index)
def start_slideshow_timer(self):
if self.low_res:
return
self.stop_slideshow_timer()
self.banner_slideshow_timeout_id = GLib.timeout_add_seconds(5, self.on_slideshow_timeout)
def stop_slideshow_timer(self):
if self.low_res:
return
if self.banner_slideshow_timeout_id > 0:
GLib.source_remove(self.banner_slideshow_timeout_id)
self.banner_slideshow_timeout_id = 0
def on_slideshow_timeout(self):
visible_child = self.banner_stack.get_visible_child()
index = self.banner_stack.get_children().index(visible_child)
new_index = (index + 1) % len(self.banner_stack.get_children())
self.banner_stack.set_visible_child_name(str(new_index))
self.update_dot_buttons(new_index)
return True
def update_dot_buttons(self, current_index):
for i, button in enumerate(self.banner_dot_box.get_children()):
if i == current_index: #Bigger do if current slide
icon = Gtk.Image.new_from_icon_name("mintinstall-banner-dot", Gtk.IconSize.MENU)
icon.set_pixel_size(10)
button.set_image(icon)
else:
icon = Gtk.Image.new_from_icon_name("mintinstall-banner-dot", Gtk.IconSize.MENU)
icon.set_pixel_size(5)
button.set_image(icon)
def on_banner_clicked(self, button, pkginfo):
self.show_package(pkginfo, self.PAGE_LANDING)
@print_timing
def load_top_rated(self):
box = self.builder.get_object("box_top_rated")
label = self.builder.get_object("label_top_rated")
label.set_text(_("Top Rated"))
label.show()
if self.flowbox_top_rated is None:
flowbox = Gtk.FlowBox()
flowbox.set_min_children_per_line(3)
flowbox.set_max_children_per_line(3)
flowbox.set_row_spacing(0)
flowbox.set_column_spacing(0)
flowbox.set_homogeneous(False)
flowbox.connect("child-activated", self.on_flowbox_child_activated, self.PAGE_LANDING)
flowbox.connect("selected-children-changed", self.navigate_flowbox, self.builder.get_object("scrolledwindow_landing"))
self.flowbox_top_rated = flowbox
box.add(flowbox)
for child in self.flowbox_top_rated:
child.destroy()
apps = []
for info in (self.all_category.pkginfos + self.flatpak_category.pkginfos):
if info.refid == "" or info.refid.startswith("app"):
if not info.verified:
continue
if info.name != self.banner_app_name and info.name not in self.featured_app_names:
if info.get_icon(imaging.FEATURED_ICON_SIZE) is not None:
apps.append(info)
apps = self.sort_packages(apps, attrgetter("installed", "score_desc", "name"))
apps = apps[0:30]
random.shuffle(apps)
size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
for pkginfo in apps:
if self.review_cache and pkginfo.verified:
review_info = self.review_cache[pkginfo.name]
else:
review_info = None
tile = PackageTile(pkginfo, self.installer, show_package_type=True, review_info=review_info)
size_group.add_widget(tile)
self.flowbox_top_rated.insert(tile, -1)
self.picks_tiles.append(tile)
box.show_all()
@print_timing
def load_featured(self):
box = self.builder.get_object("box_featured")
label = self.builder.get_object("label_featured")
label.set_text(_("Featured"))
label.show()
if self.flowbox_featured is None:
flowbox = Gtk.FlowBox()
flowbox.set_min_children_per_line(3)
flowbox.set_max_children_per_line(3)
flowbox.set_row_spacing(0)
flowbox.set_column_spacing(0)
flowbox.set_homogeneous(False)
flowbox.connect("child-activated", self.on_flowbox_child_activated, self.PAGE_LANDING)
flowbox.connect("selected-children-changed", self.navigate_flowbox, self.builder.get_object("scrolledwindow_landing"))
self.flowbox_featured = flowbox
box.add(flowbox)
for child in self.flowbox_featured:
child.destroy()
apps = []
featured_list = self.file_to_array("/usr/share/linuxmint/mintinstall/categories/picks.list")
for name in featured_list:
if name.startswith("flatpak:"):
name = name.replace("flatpak:", "")
pkginfo = self.installer.find_pkginfo(name, installer.PKG_TYPE_FLATPAK)
if pkginfo is None or not pkginfo.verified:
continue
else:
pkginfo = self.installer.find_pkginfo(name, installer.PKG_TYPE_APT)
if pkginfo is None:
continue
if pkginfo.name == self.banner_app_name:
continue
if self.installer.pkginfo_is_installed(pkginfo):
continue
if pkginfo.refid == "" or pkginfo.refid.startswith("app"):
apps.append(pkginfo)
random.shuffle(apps)
apps = apps[0:9]
size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
self.featured_app_names = []
for pkginfo in apps:
if self.review_cache and pkginfo.verified:
review_info = self.review_cache[pkginfo.name]
else:
review_info = None
tile = PackageTile(pkginfo, self.installer, show_package_type=True, review_info=review_info)
size_group.add_widget(tile)
self.flowbox_featured.insert(tile, -1)
self.picks_tiles.append(tile)
self.featured_app_names.append(pkginfo.name)
box.show_all()
@print_timing
def load_categories_on_landing(self):
box = self.builder.get_object("box_categories")
for child in box.get_children():
child.destroy()
label = self.builder.get_object("label_categories_landing")
label.set_text(_("Categories"))
label.show()
flowbox = Gtk.FlowBox()
flowbox.set_min_children_per_line(4)
flowbox.set_max_children_per_line(4)
flowbox.set_row_spacing(0)
flowbox.set_column_spacing(0)
flowbox.set_homogeneous(True)
flowbox.connect("selected-children-changed", self.navigate_flowbox, self.builder.get_object("scrolledwindow_landing"))
for name in sorted(self.root_categories.keys()):
category = self.root_categories[name]
button = CategoryButton(category)
button.connect("clicked", self.category_button_clicked, category)
flowbox.insert(button, -1)
if self.installer.list_flatpak_remotes():
# Add flatpaks
button = CategoryButton(self.flatpak_category)
button.connect("clicked", self.category_button_clicked, self.flatpak_category)
flowbox.insert(button, -1)
button = CategoryButton(self.all_category)
button.connect("clicked", self.category_button_clicked, self.all_category)
flowbox.insert(button, -1)
box.pack_start(flowbox, True, True, 0)
box.show_all()
def load_landing_apps(self, rcache=None):
self.picks_tiles = []
self.load_banner()
self.load_featured()
self.load_top_rated()
def open_software_sources(self,_):
# Opens Mint's Software Sources and refreshes the cache afterwards
def on_process_exited(proc, result):
proc.wait_finish(result)
self.refresh_cache()
p = Gio.Subprocess.new(["mintsources"], 0)
# Add a callback when we exit mintsources
p.wait_async(None, on_process_exited)
def should_show_pkginfo(self, pkginfo):
if pkginfo.pkg_hash.startswith("apt"):
return True
if not self.settings.get_boolean(prefs.ALLOW_UNVERIFIED_FLATPAKS):
return pkginfo.verified
return pkginfo.refid.startswith("app/")
def update_conditional_widgets(self):
if not self.gui_ready:
self.installed_menuitem.set_sensitive(False)
self.subsearch_toggle.set_sensitive(False)
return
sensitive = len(self.installed_category.pkginfos) > 0 \
and not ((self.page_stack.get_visible_child_name() == self.PAGE_LIST) \
and (self.current_category == self.installed_category))
self.installed_menuitem.set_sensitive(sensitive)
sensitive = self.current_category is not None \
and self.page_stack.get_visible_child_name() == self.PAGE_LIST
self.subsearch_toggle.set_sensitive(sensitive)
def update_activity_widgets(self):
num_tasks = self.installer.get_task_count()
if num_tasks > 0:
self.active_tasks_button.show()
text = gettext.ngettext("%d task running", "%d tasks running", num_tasks) % num_tasks
self.active_tasks_button.set_tooltip_text(text)
self.active_tasks_spinner.start()
else:
self.active_tasks_button.hide()
self.active_tasks_spinner.stop()
if self.current_category == self.active_tasks_category \
and self.page_stack.get_visible_child_name() == self.PAGE_LIST:
self.show_active_tasks() # Refresh the view, remove old items
def update_state(self, pkginfo):
self.update_activity_widgets()
installed_packages = self.settings.get_strv(prefs.INSTALLED_APPS)
if self.installer.pkginfo_is_installed(pkginfo):
if pkginfo.pkg_hash not in installed_packages:
installed_packages.append(pkginfo.pkg_hash)
if pkginfo not in self.installed_category.pkginfos:
self.installed_category.pkginfos.append(pkginfo)
else:
if pkginfo.pkg_hash in installed_packages:
installed_packages.remove(pkginfo.pkg_hash)
for iter_package in self.installed_category.pkginfos:
if iter_package.pkg_hash == pkginfo.pkg_hash:
self.installed_category.pkginfos.remove(iter_package)
self.settings.set_strv(prefs.INSTALLED_APPS, installed_packages)
if self.current_pkginfo is not None and self.current_pkginfo.pkg_hash == pkginfo.pkg_hash:
# flatpaks added by flatpakref files auto-remove their remotes when uninstalled
# (this doesn't apply to flatpakref files that were refs off of an already-existing
# remote - like those installed from flathub, for instance.) So, we can't assume
# the remote still exists, and will get errors if we try to display the app. We will
# just remove it from the cache at this point.
can_show = False
if pkginfo.pkg_hash.startswith("f"):
remotes = self.installer.list_flatpak_remotes()
for remote in remotes:
if remote.name == pkginfo.remote:
can_show = True
break
else:
can_show = True
if can_show:
if self.recursion_buster:
self.recursion_buster = False
return
self.show_package(self.current_pkginfo, self.previous_page)
else:
del self.installer.cache[pkginfo.pkg_hash]
self.previous_page = self.PAGE_LANDING
self.go_back_action()
for tile in (self.picks_tiles + self.category_tiles):
if tile.pkginfo == pkginfo:
tile.refresh_state()
for tile in self.flowbox_applications.get_children():
try:
tile.refresh_state()
except Exception as e:
print(e)
def modernize_installed_list(self, packages):
"""
We can not rely on just the name of a package to guarantee uniqueness.
This works for apt but not flatpaks. So we need to upgrade existing keys
to store pkg_hashes rather than simple names.
"""
return_list = []
for item in packages:
if item.startswith(("apt:", "fp:")):
# pkg_hashes start with apt: or fp:, if items have this, they're up-to-date.
return_list.append(item)
continue
# Must be an old one, look it up by name (possibly incorrectly if there have been multiple remotes added)
pkginfo = self.installer.find_pkginfo(item)
if pkginfo:
return_list.append(pkginfo.pkg_hash)
continue
return return_list
def sync_installed_apps(self):
""" garbage collect any stale packages in this list (uninstalled somewhere else) """
installed_packages = self.installer.cache.get_manually_installed_packages()
if not installed_packages:
installed_packages = self.settings.get_strv(prefs.INSTALLED_APPS)
installed_packages = self.modernize_installed_list(installed_packages)
new_installed_packages = []
for pkg_hash in installed_packages:
try:
pkginfo = self.installer.cache[pkg_hash]
except KeyError:
continue
if self.installer.pkginfo_is_installed(pkginfo):
if pkginfo not in self.installed_category.pkginfos:
self.installed_category.pkginfos.append(pkginfo)
new_installed_packages.append(pkg_hash)
else:
try:
self.installed_category.pkginfos.remove(pkginfo)
except ValueError:
pass
self.settings.set_strv(prefs.INSTALLED_APPS, new_installed_packages)
def show_installed_apps(self, menuitem):
self.show_category(self.installed_category)
def add_screenshots(self, pkginfo):
ss_dir = Path(imaging.SCREENSHOT_DIR)
n = 0
for ss_path in ss_dir.glob("%s_*.png" % pkginfo.name):
n += 1
self.add_screenshot(pkginfo, ss_path, n)
if n == 0:
downloadScreenshots = imaging.ScreenshotDownloader(self, pkginfo, self.main_window.get_scale_factor())
def add_screenshot(self, pkginfo, ss_path, n):
if pkginfo != self.current_pkginfo:
return
try:
self.screenshot_stack.get_child_by_name("spinner").destroy()
except AttributeError:
pass
if ss_path is None:
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6, valign=Gtk.Align.CENTER)
image = Gtk.Image(icon_name="face-uncertain-symbolic", icon_size=Gtk.IconSize.DIALOG)
label = Gtk.Label(label=_("No screenshots available"))
box.pack_start(image, False, False, 0)
box.pack_start(label, False, False, 0)
box.show_all()
self.screenshot_stack.add_named(box, "no-screenshots")
self.screenshot_stack.get_window().set_cursor(None)
return
screenshot = imaging.get_image_for_screenshot(str(ss_path), imaging.SCREENSHOT_WIDTH, imaging.SCREENSHOT_HEIGHT)
self.screenshot_stack.add_named(screenshot, str(n))
self.screenshot_stack.last = n
self.screenshot_stack.show_all()
self.ss_swipe_handler.set_propagation_phase(Gtk.PropagationPhase.BUBBLE)
self.screenshot_controls_vgroup.set_visible(n > 1)
self.screenshot_stack.get_window().set_cursor(self.select_cursor)
def on_screenshot_shown(self, stack, pspec):
screenshot = stack.get_visible_child()
def navigate_screenshot(self, button, direction):
new = current = int(self.screenshot_stack.get_visible_child_name())
if direction == Gtk.DirectionType.TAB_BACKWARD:
new = current - 1 if current > 1 else self.screenshot_stack.last
trans = Gtk.StackTransitionType.SLIDE_RIGHT
elif direction == Gtk.DirectionType.TAB_FORWARD:
new = current + 1 if current < self.screenshot_stack.last else 1
trans = Gtk.StackTransitionType.SLIDE_LEFT
self.screenshot_stack.set_visible_child_full(str(new), trans)
def screenshot_stack_swiped(self, handler, vx, vy, stack):
if vx == 0 and vy == 0:
if self.screenshot_window is None or not self.screenshot_window.get_visible():
self.enlarge_screenshot(stack.get_visible_child())
return
if vx < 0:
self.navigate_screenshot(None, Gtk.DirectionType.TAB_FORWARD)
elif vx > 0:
self.navigate_screenshot(None, Gtk.DirectionType.TAB_BACKWARD)
def on_window_key_press(self, stack, event, data=None):
if self.page_stack.get_visible_child_name() != self.PAGE_DETAILS:
return Gdk.EVENT_PROPAGATE
if not self.screenshot_controls_vgroup.get_visible():
return Gdk.EVENT_PROPAGATE
got_keyval, keyval = event.get_keyval()
direction = None
if got_keyval:
if keyval in (Gdk.KEY_Left, Gdk.KEY_KP_Left):
direction = Gtk.DirectionType.TAB_BACKWARD
elif keyval in (Gdk.KEY_Right, Gdk.KEY_KP_Right):
direction = Gtk.DirectionType.TAB_FORWARD
if direction is not None:
self.navigate_screenshot(None, direction)
return Gdk.EVENT_STOP
return Gdk.EVENT_PROPAGATE
def get_screenshot_source_from_metadata(self, screenshot):
file = Gio.File.new_for_path(screenshot.path)
try:
info = file.query_info("metadata::mintinstall-screenshot-source-url", Gio.FileQueryInfoFlags.NONE, None)
url = info.get_attribute_string("metadata::mintinstall-screenshot-source-url")
if url is not None:
return url
except GLib.Error as e:
print("Could not retrieve source metadata for screenshot (%s): %s" % (ss_path, e.message))
return None
def enlarge_screenshot(self, screenshot):
source_url = self.get_screenshot_source_from_metadata(screenshot)
if source_url is not None:
image_location = source_url
else:
image_location = screenshot.path
if self.screenshot_window is not None:
if self.screenshot_window.has_image(source_url):
self.screenshot_window.show_all()
self.screenshot_window.present()
return
else:
multiple_images = len(self.installer.get_screenshots(self.current_pkginfo)) > 1 or \
self.screenshot_stack.last > 1
self.screenshot_window = ScreenshotWindow(self.main_window, multiple_images)
self.screenshot_window.connect("next-image", self.next_enlarged_screenshot_requested)
self.screenshot_window.connect("destroy", self.enlarged_screenshot_window_destroyed)
monitor = Gdk.Display.get_default().get_monitor_at_window(self.main_window.get_window())
work_area = monitor.get_workarea()
enlarged = imaging.get_image_for_screenshot(image_location, work_area.width * .8, work_area.height * .8)
enlarged.connect("image-loaded", self.enlarged_image_ready)
enlarged.connect("image-failed", self.enlarged_image_failed)
return Gdk.EVENT_STOP
def enlarged_image_ready(self, image):
self.screenshot_window.add_image(image, image.path)
def enlarged_image_failed(self, image):
# AsyncImage will be trying to load a fallback image next, make sure we don't get signaled for it.
image.disconnect_by_func(self.enlarged_image_ready)
image.destroy()
self.screenshot_window.set_busy(False)
# If a screenshot failed and it's the first one, there's an empty, invisible screenshot window
# in front of the main window, so destroy it.
if not self.screenshot_window.any_images():
self.screenshot_window.destroy()
def enlarged_screenshot_window_destroyed(self, window):
self.screenshot_window = None
def next_enlarged_screenshot_requested(self, window, direction):
if len(self.screenshot_stack.get_children()) < 2:
self.screenshot_window.destroy()
return False
self.navigate_screenshot(None, direction)
screenshot = self.screenshot_stack.get_visible_child()
source_url = self.get_screenshot_source_from_metadata(screenshot)
if source_url is not None:
image_location = source_url
else:
image_location = screenshot.path
if self.screenshot_window.has_image(image_location):
self.screenshot_window.show_image(image_location)
return False
self.enlarge_screenshot(screenshot)
return True
def destroy_screenshot_window(self):
if self.screenshot_window is not None:
self.screenshot_window.destroy()
self.screenshot_window = None
def category_button_clicked(self, button, category):
self.show_category(category)
def on_menu_button_clicked(self, button, menu):
menu.popup_at_pointer(None)
def on_subsearch_toggled(self, button):
self.settings.set_boolean(prefs.SEARCH_IN_CATEGORY, button.get_active())
if button.get_active():
return
else:
self.on_search_changed(self.searchentry)
def on_search_entry_activated(self, searchentry):
terms = searchentry.get_text()
if terms != "":
self.show_search_results(terms)
def on_entry_text_changed(self, entry):
if self.search_changed_timer > 0:
GLib.source_remove(self.search_changed_timer)
self.search_changed_timer = 0
self.search_changed_timer = GLib.timeout_add(175, self.on_search_changed, entry)
def on_search_changed(self, searchentry):
terms = searchentry.get_text()
if self.subsearch_toggle.get_active() and self.current_category is not None and terms == "":
self.show_category(self.current_category)
elif terms != "" and len(terms) >= 3:
self.show_search_results(terms)
self.search_changed_timer = 0
return False
def set_search_filter(self, checkmenuitem, key):
self.settings.set_boolean(key, checkmenuitem.get_active())
terms = self.searchentry.get_text()
if (self.searchentry.get_text() != ""):
self.show_search_results(terms)
def set_package_type_preference(self, radiomenuitem, value):
if radiomenuitem.get_active():
self.settings.set_string(prefs.PACKAGE_TYPE_PREFERENCE, value)
terms = self.searchentry.get_text()
if terms != "":
self.show_search_results(terms)
def open_about(self, widget):
dlg = Gtk.AboutDialog()
dlg.set_transient_for(self.main_window)
dlg.set_title(_("About"))
dlg.set_program_name("mintinstall")
dlg.set_comments(_("Software Manager"))
try:
h = open('/usr/share/common-licenses/GPL', 'r')
s = h.readlines()
gpl = ""
for line in s:
gpl += line
h.close()
dlg.set_license(gpl)
except Exception as e:
print(e)
dlg.set_version("8.3.6")
dlg.set_icon_name("mintinstall")
dlg.set_logo_icon_name("mintinstall")
def close(w, res):
if res == Gtk.ResponseType.CANCEL or res == Gtk.ResponseType.DELETE_EVENT:
w.hide()
dlg.connect("response", close)
dlg.show()
def export_listing(self, flatpak_only=False):
if not flatpak_only:
for filename in os.listdir("/var/lib/apt/lists/"):
if "i18n" in filename and not filename.endswith("-en"):
print("Your APT cache is localized. Please remove all translations first.")
print("sudo rm -rf /var/lib/apt/lists/*Translation%s" % filename[-3:])
return 1
if (os.getenv('LANGUAGE') != "C"):
print("Please prefix this command with LANGUAGE=C, to prevent content from being translated in the host's locale.")
return 1
self.locale = "C"
self.installer = installer.Installer()
from mintcommon.installer import cache
self.installer.cache = cache.PkgCache(None, None, self.installer.have_flatpak)
self.installer.cache._generate_cache_thread()
self.installer.backend_table = {}
self.installer.initialize_appstream()
self.installer.generate_uncached_pkginfos(self.installer.cache)
self.add_categories()
self.process_matching_packages()
self.process_unmatched_packages()
if flatpak_only:
pkginfos = self.installer.cache.get_subset_of_type("f")
else:
pkginfos = self.installer.cache
for pkg_hash in pkginfos.keys():
pkginfo = self.installer.cache[pkg_hash]
description = pkginfo.get_description(pkginfo)
description = description.replace("\r\n", "<br>")
description = description.replace("\n", "<br>")
summary = pkginfo.get_summary(pkginfo)
url = ""
try:
url = self.installer.get_homepage_url(pkginfo)
except:
pass
categories = []
for category in pkginfo.categories:
categories.append(category.name)
try:
output = "#~#".join([pkginfo.name, url, summary, description, ":::".join(categories)])
except Exception as e:
print (e)
print(pkginfo.name, url, summary, description)
return 1
print(output)
return 0
def close_application(self, window, event=None):
if self.installer.is_busy():
dialog = Gtk.MessageDialog(self.main_window,
Gtk.DialogFlags.MODAL,
Gtk.MessageType.WARNING,
Gtk.ButtonsType.YES_NO,
_("There are currently active operations.\nAre you sure you want to quit?"))
res = dialog.run()
dialog.destroy()
if res == Gtk.ResponseType.NO:
return True
# kill -9 won't kill mp subprocesses, we have to do them ourselves.
housekeeping.kill()
if self.review_cache:
self.review_cache.kill()
# Not happy with Python when it comes to closing threads, so here's a radical method to get what we want.
os.system("kill -9 %s &" % os.getpid())
def on_action_button_clicked(self, button, task):
if task.info_ready_status == task.STATUS_UNKNOWN:
self.show_package(self.current_pkginfo, self.previous_page)
return
if not self.installer.confirm_task(task):
return
self.on_installer_progress(task.pkginfo, 0, True)
self.installer.execute_task(task)
self.update_activity_widgets()
def on_launch_button_clicked(self, button, task):
if task.exec_string is not None:
exec_array = task.exec_string.split()
for element in exec_array:
if element.startswith('%'):
exec_array.remove(element)
if "sh" in exec_array:
print("Launching app with OS: " % " ".join(exec_array))
os.system("%s &" % " ".join(exec_array))
else:
print("Launching app with Popen: %s" % " ".join(exec_array))
subprocess.Popen(exec_array)
def file_to_array(self, filename):
array = []
with open(filename) as f:
for line in f:
line = line.replace("\n", "").replace("\r", "").strip()
if line != "":
array.append(line)
return array
@print_timing
def add_categories(self):
self.categories = []
self.sections = {}
self.root_categories = {}
self.installed_category = Category(_("Installed Applications"), None, self.categories)
self.installed_category.matchingPackages = self.settings.get_strv(prefs.INSTALLED_APPS)
self.active_tasks_category = Category(_("Currently working on the following packages"), None, None)
self.flatpak_category = Category("Flatpak", None, self.categories, "mintinstall-package-flatpak-symbolic")
# INTERNET
category = Category(_("Internet"), None, self.categories, "mintinstall-web-symbolic")
subcat = Category(_("Web"), category, self.categories, "mintinstall-web-symbolic")
self.sections["web"] = subcat
self.sections["net"] = subcat
subcat.matchingPackages = self.file_to_array("/usr/share/linuxmint/mintinstall/categories/internet-web.list")
subcat = Category(_("Email"), category, self.categories, "mintinstall-email-symbolic")
self.sections["mail"] = subcat
subcat.matchingPackages = self.file_to_array("/usr/share/linuxmint/mintinstall/categories/internet-email.list")
subcat = Category(_("Chat"), category, self.categories, "mintinstall-chat-symbolic")
subcat.matchingPackages = self.file_to_array("/usr/share/linuxmint/mintinstall/categories/internet-chat.list")
subcat = Category(_("File sharing"), category, self.categories, "mintinstall-share-symbolic")
subcat.matchingPackages = self.file_to_array("/usr/share/linuxmint/mintinstall/categories/internet-filesharing.list")
self.root_categories[category.name] = category
# SOUND AND VIDEO
category = Category(_("Sound and video"), None, self.categories, "mintinstall-music-symbolic")
category.matchingPackages = self.file_to_array("/usr/share/linuxmint/mintinstall/categories/sound-video.list")
subcat = Category(_("Sound"), category, self.categories, "mintinstall-music-symbolic")
self.sections["sound"] = subcat
subcat = Category(_("Video"), category, self.categories, "mintinstall-video-symbolic")
self.sections["video"] = subcat
self.root_categories[category.name] = category
# GRAPHICS
category = Category(_("Graphics"), None, self.categories, "mintinstall-drawing-symbolic")
self.sections["graphics"] = category
category.matchingPackages = self.file_to_array("/usr/share/linuxmint/mintinstall/categories/graphics.list")
subcat = Category(_("3D"), category, self.categories, "mintinstall-3d-symbolic")
subcat.matchingPackages = self.file_to_array("/usr/share/linuxmint/mintinstall/categories/graphics-3d.list")
subcat = Category(_("Drawing"), category, self.categories, "mintinstall-drawing-symbolic")
subcat.matchingPackages = self.file_to_array("/usr/share/linuxmint/mintinstall/categories/graphics-drawing.list")
subcat = Category(_("Photography"), category, self.categories, "mintinstall-photo-symbolic")
subcat.matchingPackages = self.file_to_array("/usr/share/linuxmint/mintinstall/categories/graphics-photography.list")
subcat = Category(_("Publishing"), category, self.categories, "mintinstall-publishing-symbolic")
subcat.matchingPackages = self.file_to_array("/usr/share/linuxmint/mintinstall/categories/graphics-publishing.list")
subcat = Category(_("Scanning"), category, self.categories, "mintinstall-scanning-symbolic")
subcat.matchingPackages = self.file_to_array("/usr/share/linuxmint/mintinstall/categories/graphics-scanning.list")
subcat = Category(_("Viewers"), category, self.categories, "mintinstall-viewers-symbolic")
subcat.matchingPackages = self.file_to_array("/usr/share/linuxmint/mintinstall/categories/graphics-viewers.list")
self.root_categories[category.name] = category
# OFFICE
category = Category(_("Office"), None, self.categories, "mintinstall-office-symbolic")
self.sections["office"] = category
self.sections["editors"] = category
self.root_categories[category.name] = category
# GAMES
category = Category(_("Games"), None, self.categories, "mintinstall-games-symbolic")
self.sections["games"] = category
category.matchingPackages = self.file_to_array("/usr/share/linuxmint/mintinstall/categories/games.list")
subcat = Category(_("Board games"), category, self.categories, "mintinstall-board-symbolic")
subcat.matchingPackages = self.file_to_array("/usr/share/linuxmint/mintinstall/categories/games-board.list")
subcat = Category(_("First-person"), category, self.categories, "mintinstall-fps-symbolic")
subcat.matchingPackages = self.file_to_array("/usr/share/linuxmint/mintinstall/categories/games-fps.list")
subcat = Category(_("Real-time strategy"), category, self.categories, "mintinstall-rts-symbolic")
subcat.matchingPackages = self.file_to_array("/usr/share/linuxmint/mintinstall/categories/games-rts.list")
subcat = Category(_("Turn-based strategy"), category, self.categories, "mintinstall-tbs-symbolic")
subcat.matchingPackages = self.file_to_array("/usr/share/linuxmint/mintinstall/categories/games-tbs.list")
subcat = Category(_("Emulators"), category, self.categories, "mintinstall-emulator-symbolic")
subcat.matchingPackages = self.file_to_array("/usr/share/linuxmint/mintinstall/categories/games-emulators.list")
subcat = Category(_("Simulation and racing"), category, self.categories, "mintinstall-sim-symbolic")
subcat.matchingPackages = self.file_to_array("/usr/share/linuxmint/mintinstall/categories/games-simulations.list")
self.root_categories[category.name] = category
# ACCESSORIES
category = Category(_("Accessories"), None, self.categories, "mintinstall-accessories-symbolic")
self.sections["accessories"] = category
self.sections["utils"] = category
self.root_categories[category.name] = category
# SYSTEM TOOLS
category = Category(_("System tools"), None, self.categories, "mintinstall-system-symbolic")
self.sections["system"] = category
self.sections["admin"] = category
category.matchingPackages = self.file_to_array("/usr/share/linuxmint/mintinstall/categories/system-tools.list")
self.root_categories[category.name] = category
# FONTS
category = Category(_("Fonts"), None, self.categories, "mintinstall-fonts-symbolic")
self.sections["fonts"] = category
category.matchingPackages = self.file_to_array("/usr/share/linuxmint/mintinstall/categories/fonts.list")
self.root_categories[category.name] = category
# EDUCATION
category = Category(_("Science and Education"), None, self.categories, "mintinstall-science-symbolic")
subcat = Category(_("Science"), category, self.categories, "mintinstall-science-symbolic")
self.sections["science"] = subcat
subcat = Category(_("Maths"), category, self.categories, "mintinstall-maths-symbolic")
self.sections["math"] = subcat
subcat = Category(_("Education"), category, self.categories, "mintinstall-education-symbolic")
self.sections["education"] = subcat
subcat = Category(_("Electronics"), category, self.categories, "mintinstall-electronic-symbolic")
self.sections["electronics"] = subcat
category.matchingPackages = self.file_to_array("/usr/share/linuxmint/mintinstall/categories/education.list")
self.root_categories[category.name] = category
# PROGRAMMING
category = Category(_("Programming"), None, self.categories, "mintinstall-programming-symbolic")
self.sections["devel"] = category
subcat = Category(_("Java"), category, self.categories, "mintinstall-java-symbolic")
self.sections["java"] = subcat
subcat = Category(_("PHP"), category, self.categories, "mintinstall-php-symbolic")
self.sections["php"] = subcat
subcat = Category(_("Python"), category, self.categories, "mintinstall-python-symbolic")
self.sections["python"] = subcat
subcat = Category(_("Essentials"), category, self.categories, "xapp-favorites-app-symbolic")
subcat.matchingPackages = self.file_to_array("/usr/share/linuxmint/mintinstall/categories/development-essentials.list")
self.root_categories[category.name] = category
# ALL
self.all_category = Category(_("All Applications"), None, self.categories, "mintinstall-all-symbolic")
for cat in self.categories:
self.all_category.matchingPackages.extend(cat.matchingPackages)
sorted(self.all_category.matchingPackages)
def add_pkginfo_to_category(self, pkginfo, category):
try:
if category not in pkginfo.categories:
pkginfo.categories.append(category)
category.pkginfos.append(pkginfo)
if category.parent:
self.add_pkginfo_to_category(pkginfo, category.parent)
except AttributeError:
pass
def finished_loading_packages(self):
self.finish_loading_visual()
self.start_slideshow_timer()
self.gui_ready = True
self.update_conditional_widgets()
if self.install_on_startup_file is not None:
self.handle_command_line_install(self.install_on_startup_file)
return False
def get_installed_package_hashes(self):
if self.installer.have_flatpak:
installed_fp_refs = installer._flatpak.get_fp_sys().list_installed_refs(None)
fp_hashes = [installer._flatpak.make_pkg_hash(ref) for ref in installed_fp_refs]
else:
fp_hashes = []
apt_cache = installer._apt.get_apt_cache()
apt_hashes = [installer._apt.make_pkg_hash(pkg) for pkg in apt_cache if pkg.installed]
return apt_hashes + fp_hashes
@print_timing
def process_matching_packages(self):
# Process matching packages
for category in self.categories:
for package_name in category.matchingPackages:
if package_name.startswith("fp"):
continue
pkginfo = self.installer.find_pkginfo(package_name, installer.PKG_TYPE_APT)
self.add_pkginfo_to_category(pkginfo, category)
for package_name in self.installed_category.matchingPackages:
if not package_name.startswith("fp"):
continue
pkginfo = self.installer.find_pkginfo(package_name, installer.PKG_TYPE_FLATPAK)
self.add_pkginfo_to_category(pkginfo,
self.installed_category)
self.flatpak_remote_categories = {}
self.flatpak_category.subcategories = []
for key in self.installer.cache.get_subset_of_type("f"):
remote_name = self.installer.cache[key].remote
remote_info = self.installer.cache.flatpak_remote_infos[remote_name]
self.add_pkginfo_to_category(self.installer.cache[key],
self.flatpak_category)
# Remotes marked noenumerate don't get a category, their apps show only in
# the installed category and the main flatpak category. They are auto-removed
# then the app is uninstalled generally (usually they're -origin remotes, only added
# when a .flatpakref file is installed.) However, their addition/removal, purposely,
# will not trigger a rebuild of the cache, so we don't want to be caught showing an
# empty category as a reuslt.
if remote_info.noenumerate:
continue
if remote_name not in self.flatpak_remote_categories.keys():
self.flatpak_remote_categories[remote_name] = Category(remote_info.title, self.flatpak_category, None)
self.add_pkginfo_to_category(self.installer.cache[key],
self.flatpak_remote_categories[remote_name])
@print_timing
def process_unmatched_packages(self):
cache_sections = self.installer.cache.sections
for section in self.sections.keys():
if section in cache_sections.keys():
for pkg_hash in cache_sections[section]:
self.add_pkginfo_to_category(self.installer.cache[pkg_hash],
self.sections[section])
def apply_aliases(self):
for pkg_name in ALIASES.keys():
pkginfo = self.installer.cache.find_pkginfo(pkg_name, installer.PKG_TYPE_APT) # aliases currently only apply to apt
if pkginfo:
# print("Applying aliases: ", ALIASES[pkg_name], self.installer.get_display_name(pkginfo))
pkginfo.display_name = ALIASES[pkg_name]
def finish_loading_visual(self):
if self.page_stack.get_visible_child_name() != self.PAGE_LANDING:
self.page_stack.set_visible_child_name(self.PAGE_LANDING)
#Copied from the Cinnamon Project cinnamon-settings.py
#Goes back when the Backspace or Home key on the keyboard is typed
def on_keypress(self, widget, event):
if self.main_window.get_focus() != self.searchentry and (event.keyval in [Gdk.KEY_BackSpace, Gdk.KEY_Home]):
self.go_back_action()
return True
return False
#Copied from the Cinnamon Project cinnamon-settings.py
#Goes back when the back button on the mouse is clicked
def on_buttonpress(self, widget, event):
if event.button == MOUSE_BACK_BUTTON:
self.go_back_action()
return True
return False
def reset_scroll_view(self, scrolledwindow):
adjustment = scrolledwindow.get_vadjustment()
adjustment.set_value(adjustment.get_lower())
def on_active_tasks_button_clicked(self, button):
self.show_active_tasks()
def show_active_tasks(self):
self.current_pkginfo = None
self.active_tasks_category.pkginfos = self.installer.get_active_pkginfos()
self.show_category(self.active_tasks_category)
def on_back_button_clicked(self, button):
self.go_back_action()
def go_back_action(self):
XApp.set_window_progress(self.main_window, 0)
self.stop_progress_pulse()
# If we're still loading details (and simulating), there's no task yet,
# but we can cancel it via cancellable the installer gave us initially.
if self.task_cancellable is not None:
self.task_cancellable.cancel()
self.task_cancellable = None
# If we have a task, we're viewing a package and it's been 'loaded' fully.
# Cancel it directly.
elif self.current_task:
if not self.installer.task_running(self.current_task):
self.installer.cancel_task(self.current_task)
self.current_task = None
if self.page_stack.get_visible_child_name() == self.PAGE_PREFS:
self.search_tool_item.set_sensitive(True)
self.current_pkginfo = None
self.page_stack.set_visible_child_name(self.previous_page)
if self.previous_page == self.PAGE_LANDING:
self.back_button.set_sensitive(False)
self.searchentry.grab_focus()
self.searchentry.set_text("")
self.current_category = None
if self.one_package_idle_timer > 0:
GLib.source_remove(self.one_package_idle_timer)
self.one_package_idle_timer = 0
try:
tile = self.flowbox_top_rated.get_selected_children()[0]
tile.grab_focus()
except IndexError:
pass
self.start_slideshow_timer()
if self.previous_page == self.PAGE_LIST:
self.previous_page = self.PAGE_LANDING
if self.current_category == self.installed_category:
# special case, when going back to the installed-category, refresh it in case we removed something
self.show_category(self.installed_category)
elif self.current_category == self.active_tasks_category:
self.show_active_tasks()
else:
try:
tile = self.flowbox_applications.get_selected_children()[0]
tile.grab_focus()
except IndexError:
pass
if self.screenshot_stack.get_realized():
self.screenshot_stack.get_window().set_cursor(None)
self.update_conditional_widgets()
def show_category(self, category):
self.current_pkginfo = None
label = self.builder.get_object("label_cat_name")
self.current_category = category
self.page_stack.set_visible_child_name(self.PAGE_LIST)
self.previous_page = self.PAGE_LANDING
self.back_button.set_sensitive(True)
label.set_text(self.current_category.name)
label.show()
if category.parent:
self.show_subcategories(category.parent)
else:
self.show_subcategories(category)
self.show_packages(category.pkginfos, from_search=False)
self.update_conditional_widgets()
def show_subcategories(self, category):
# Load subcategories
for child in self.subcat_flowbox.get_children():
child.destroy()
if category == self.flatpak_category or len(category.subcategories) == 0:
self.subcat_flowbox.hide()
return
child = SubcategoryFlowboxChild(category, is_all=True, active=self.current_category == category)
self.subcat_flowbox.add(child)
for cat in category.subcategories:
if len(cat.pkginfos) > 0:
child = SubcategoryFlowboxChild(cat, is_all=False, active=self.current_category == cat)
self.subcat_flowbox.add(child)
self.subcat_flowbox.show_all()
def on_subcategory_selected(self, flowbox, child, data=None):
self.show_category(child.category)
def get_application_icon_string(self, pkginfo, size):
string = pkginfo.get_icon(size, self.installer.get_appstream_pkg_for_pkginfo(pkginfo))
if not string:
string = imaging.FALLBACK_PACKAGE_ICON_PATH
return string
def get_application_icon(self, pkginfo, size):
icon_string = self.get_application_icon_string(pkginfo, size)
return imaging.get_icon(icon_string, size)
@print_timing
def show_search_results(self, terms):
if not self.gui_ready:
return False
label = self.builder.get_object("label_cat_name")
label.hide()
XApp.set_window_progress(self.main_window, 0)
self.stop_progress_pulse()
self.current_pkginfo = None
for child in self.flowbox_applications.get_children():
self.flowbox_applications.remove(child)
if self.subsearch_toggle.get_active() \
and self.current_category is not None \
and self.page_stack.get_visible_child_name() == self.PAGE_LIST:
listing = self.current_category.pkginfos
else:
listing = self.installer.cache.values()
self.current_category = None
self.subcat_flowbox.hide()
self.back_button.set_sensitive(True)
self.previous_page = self.PAGE_LANDING
if self.page_stack.get_visible_child_name() != self.PAGE_SEARCHING:
self.page_stack.set_visible_child_name(self.PAGE_SEARCHING)
termsUpper = terms.upper()
termsSplit = re.split(r'\W+', termsUpper)
searched_packages = []
if self.search_idle_timer > 0:
GLib.source_remove(self.search_idle_timer)
self.search_idle_timer = 0
search_in_summary = self.settings.get_boolean(prefs.SEARCH_IN_SUMMARY)
search_in_description = self.settings.get_boolean(prefs.SEARCH_IN_DESCRIPTION)
package_type_preference = self.settings.get_string(prefs.PACKAGE_TYPE_PREFERENCE)
hidden_packages = set()
def idle_search_one_package(pkginfos):
try:
pkginfo = pkginfos.pop(0)
except IndexError:
self.search_idle_timer = 0
return False
flatpak = pkginfo.pkg_hash.startswith("f")
is_match = False
while True:
if not self.should_show_pkginfo(pkginfo):
break
if all(piece in pkginfo.name.upper() for piece in termsSplit):
is_match = True
pkginfo.search_tier = 0
break
# pkginfo.name for flatpaks is their id (org.foo.BarMaker), which
# may not actually contain the app's name. In this case their display
# names are better. The 'name' is still checked first above, because
# it's static - get_display_name() may involve a lookup with appstream.
if flatpak and all(piece in pkginfo.get_display_name().upper() for piece in termsSplit):
is_match = True
pkginfo.search_tier = 0
break
if termsUpper in pkginfo.get_keywords().upper():
is_match = True
pkginfo.search_tier = 50
break
if (search_in_summary and termsUpper in pkginfo.get_summary().upper()):
is_match = True
pkginfo.search_tier = 100
break
if(search_in_description and termsUpper in self.installer.get_description(pkginfo).upper()):
is_match = True
pkginfo.search_tier = 200
break
break
if is_match:
searched_packages.append(pkginfo)
if package_type_preference == prefs.PACKAGE_TYPE_PREFERENCE_APT and not flatpak:
hidden_packages.add(FLATPAK_EQUIVS.get(pkginfo.name))
elif package_type_preference == prefs.PACKAGE_TYPE_PREFERENCE_FLATPAK and flatpak:
hidden_packages.add(DEB_EQUIVS.get(pkginfo.name))
# Repeat until empty
if len(pkginfos) > 0:
return True
self.search_idle_timer = 0
if package_type_preference == prefs.PACKAGE_TYPE_PREFERENCE_APT:
results = [p for p in searched_packages if not (p.pkg_hash.startswith("f") and p.name in hidden_packages)]
elif package_type_preference == prefs.PACKAGE_TYPE_PREFERENCE_FLATPAK:
results = [p for p in searched_packages if not (p.pkg_hash.startswith("a") and p.name in hidden_packages)]
else:
results = searched_packages
GLib.idle_add(self.on_search_results_complete, results)
return False
self.search_idle_timer = GLib.idle_add(idle_search_one_package, list(listing))
def on_search_results_complete(self, results):
self.page_stack.set_visible_child_name(self.PAGE_LIST)
self.show_packages(results, from_search=True)
def on_app_row_activated(self, listbox, row, previous_page):
self.show_package(row.pkginfo, previous_page)
def on_flowbox_item_clicked(self, tile, data=None):
# This ties the GtkButton.clicked signal for the Tile class
# to the flowbox mechanics. Clicks would be handled by
# GtkFlowBox.child-activated if we weren't using a GtkButton
# as each flowbox entry. This could probably fixed eventually
# but we like the button styling and highlighting.
tile.get_parent().activate()
def on_flowbox_child_activated(self, flowbox, child, previous_page):
flowbox.select_child(child)
self.show_package(child.pkginfo, previous_page)
def navigate_flowbox(self, flowbox, scrolled_window):
try:
selected = flowbox.get_selected_children()[0]
except IndexError:
return
adj = scrolled_window.get_vadjustment()
current = adj.get_value()
sel_box = selected.get_allocation()
fb_box = flowbox.get_allocation()
sw_box = scrolled_window.get_allocation()
unit = sel_box.height
if (sel_box.y + sw_box.y + fb_box.y + unit) > (current + sw_box.height):
adj.set_value((sel_box.y + fb_box.y + unit) - sw_box.height)
elif sel_box.y + fb_box.y < current:
adj.set_value(sel_box.y + fb_box.y)
def capitalize(self, string):
if len(string) > 1:
return (string[0].upper() + string[1:])
else:
return (string)
@print_timing
def sort_packages(self, pkgs, key_func):
sort_pkgs = []
installed_hashes = self.get_installed_package_hashes()
for pkg in pkgs:
sort_pkg = SortPackage(pkg)
sort_pkg.installed = pkg.pkg_hash in installed_hashes
# A flatpak's 'name' may not even have the app's name in it.
# It's better to compare by their display names
if pkg.pkg_hash.startswith("f"):
sort_pkg.name = pkg.get_display_name()
if self.review_cache and pkg.name in self.review_cache:
sort_pkg.score_desc = -self.review_cache[pkg.name].score
sort_pkgs.append(sort_pkg)
sort_pkgs.sort(key=key_func)
return [pkg.pkg for pkg in sort_pkgs]
def show_packages(self, pkginfos, from_search=False):
self.stop_slideshow_timer()
if self.one_package_idle_timer > 0:
GLib.source_remove(self.one_package_idle_timer)
self.one_package_idle_timer = 0
for child in self.flowbox_applications.get_children():
self.flowbox_applications.remove(child)
self.category_tiles = []
if len(pkginfos) == 0:
self.app_list_stack.set_visible_child_name("no-results")
if self.current_category == self.active_tasks_category:
text = _("All operations complete")
self.no_packages_found_refresh_button.hide()
else:
if from_search:
text = _("No matching packages found")
self.no_packages_found_refresh_button.hide()
else:
text = _("No packages to show.\nThis may indicate a problem - try refreshing the cache.")
self.no_packages_found_refresh_button.show()
self.no_packages_found_label.set_markup("<big><b>%s</b></big>" % text)
else:
self.app_list_stack.set_visible_child_name("results")
if self.current_category == self.installed_category:
# Installed category we want to show all apps even if they're 'unverified'
apps = [info for info in pkginfos if info.refid == "" or info.refid.startswith("app")]
apps = self.sort_packages(apps, attrgetter("name"))
else:
if from_search:
apps = [info for info in pkginfos] # should_show_pkginfo was applied during search matching
apps = self.sort_packages(apps, attrgetter("unverified", "search_tier", "score_desc", "name"))
else:
apps = [info for info in pkginfos if self.should_show_pkginfo(info)]
apps = self.sort_packages(apps, attrgetter("unverified", "score_desc", "name"))
apps = apps[0:201]
# Identify name collisions (to show more info when multiple apps have the same name)
package_titles = []
collisions = []
bad_ones = []
for pkginfo in apps:
try:
title = pkginfo.get_display_name().lower()
if title in package_titles and title not in collisions:
collisions.append(title)
package_titles.append(title)
except:
bad_ones.append(pkginfo)
for bad in bad_ones:
apps.remove(bad)
self.one_package_idle_timer = GLib.idle_add(self.idle_show_one_package,
apps,
collisions)
self.flowbox_applications.show_all()
def idle_show_one_package(self, pkginfos, collisions):
try:
pkginfo = pkginfos.pop(0)
except IndexError:
self.one_package_idle_timer = 0
return False
icon = self.get_application_icon(pkginfo, imaging.LIST_ICON_SIZE)
summary = pkginfo.get_summary()
if self.review_cache:
review_info = self.review_cache[pkginfo.name]
else:
review_info = None
tile = PackageTile(pkginfo, self.installer, show_package_type=True, review_info=review_info)
self.flowbox_applications.insert(tile, -1)
self.category_tiles.append(tile)
# Repeat until empty
if len(pkginfos) > 0:
return True
self.reset_scroll_view(self.builder.get_object("scrolledwindow_applications"))
self.one_package_idle_timer = 0
return False
def on_tile_keypress(self, row, event, data=None):
if event.keyval in (Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab):
self.searchentry.grab_focus()
return Gdk.EVENT_STOP
return Gdk.EVENT_PROPAGATE
def package_type_combo_changed(self, combo):
iter = combo.get_active_iter()
if iter:
pkginfo = combo.get_model().get_value(iter, PACKAGE_TYPE_COMBO_PKGINFO)
self.show_package(pkginfo, self.previous_page)
@print_timing
def show_package(self, pkginfo, previous_page):
self.page_stack.set_visible_child_name(self.PAGE_DETAILS)
self.stop_slideshow_timer()
self.builder.get_object("details_notebook").set_current_page(0)
self.previous_page = previous_page
self.back_button.set_sensitive(True)
self.update_conditional_widgets()
# Reset the position of our scrolled window back to the top
self.reset_scroll_view(self.builder.get_object("scrolled_details"))
self.current_pkginfo = pkginfo
is_flatpak = pkginfo.pkg_hash.startswith("fp:")
# Set to busy while the installer figures out what to do
self.builder.get_object("notebook_progress").set_current_page(self.SPINNER_TAB)
# Set source-agnostic things
icon_string = self.get_application_icon_string(pkginfo, imaging.DETAILS_ICON_SIZE)
self.detail_view_icon.set_icon_string(icon_string)
self.package_type_store.clear()
self.package_type_combo.set_button_sensitivity(Gtk.SensitivityType.AUTO)
try:
self.package_type_combo.disconnect_by_func(self.package_type_combo_changed)
except TypeError:
pass
self.package_type_store.clear()
i = 0
to_use_iter = None
row_pkginfo = None
row = None
tooltip = None
# add system if this is a system package, or one exists in our match table.
if not is_flatpak:
row_pkginfo = pkginfo
else:
match = self.get_deb_for_flatpak(pkginfo)
if match is not None:
row_pkginfo = match
if row_pkginfo:
row = [i, _("System Package"), _("Your system's package manager"), "linuxmint-logo-badge-symbolic", row_pkginfo]
iter = self.package_type_store.append(row)
if pkginfo == row_pkginfo:
to_use_iter = iter
tooltip = row[PACKAGE_TYPE_COMBO_SUMMARY]
i += 1
if is_flatpak or self.get_flatpak_for_deb(pkginfo) is not None:
a_flatpak = self.get_flatpak_for_deb(pkginfo) or pkginfo
for remote in self.installer.list_flatpak_remotes():
row_pkginfo = self.installer.find_pkginfo(a_flatpak.name, installer.PKG_TYPE_FLATPAK, remote=remote.name)
if row_pkginfo:
row = [i, _("Flatpak (%s)") % remote.title, remote.summary, "mintinstall-package-flatpak-symbolic", row_pkginfo]
iter = self.package_type_store.append(row)
if pkginfo == row_pkginfo:
to_use_iter = iter
tooltip = row[PACKAGE_TYPE_COMBO_SUMMARY]
i += 1
if i == 1:
self.package_type_combo.hide()
self.single_version_package_type_box.show()
self.single_version_package_type_label.set_label(row[PACKAGE_TYPE_COMBO_LABEL])
self.single_version_package_type_icon.set_from_icon_name(row[PACKAGE_TYPE_COMBO_ICON_NAME], Gtk.IconSize.BUTTON)
self.single_version_package_type_box.set_tooltip_text(row[PACKAGE_TYPE_COMBO_SUMMARY])
else:
self.single_version_package_type_box.hide()
self.package_type_combo.show()
self.package_type_combo.set_active_iter(to_use_iter)
self.package_type_combo.set_tooltip_text(tooltip)
self.unsafe_box.hide()
self.builder.get_object("application_dev_name").set_label("")
if is_flatpak:
self.flatpak_details_vgroup.show()
# We don't know flatpak versions until the task reports back, apt we know immediately.
self.builder.get_object("application_version").set_label("")
dev_name = self.installer.get_developer(pkginfo)
if dev_name != "":
self.builder.get_object("application_dev_name").set_label(_("by %s" % dev_name))
else:
self.builder.get_object("application_dev_name").set_label(_("Unknown maintainer"))
if not pkginfo.verified:
self.unsafe_box.show()
self.builder.get_object("application_dev_name").set_label("")
else:
self.flatpak_details_vgroup.hide()
self.builder.get_object("application_version").set_label(self.installer.get_version(pkginfo))
self.package_type_combo.connect("changed", self.package_type_combo_changed)
app_name = pkginfo.get_display_name()
self.builder.get_object("application_name").set_label(app_name)
self.builder.get_object("application_summary").set_label(pkginfo.get_summary())
self.builder.get_object("application_package").set_label(pkginfo.name)
self.builder.get_object("application_size").set_markup("")
self.builder.get_object("application_remote").set_markup("")
self.builder.get_object("application_branch").set_markup("")
homepage_url = self.installer.get_homepage_url(pkginfo)
if homepage_url not in ('', None):
self.builder.get_object("application_homepage").show()
homepage_label = _("Homepage")
self.builder.get_object("application_homepage").set_markup("<a href='%s'>%s</a>" % (homepage_url, homepage_label))
self.builder.get_object("application_homepage").set_tooltip_text(homepage_url)
else:
self.builder.get_object("application_homepage").hide()
helppage_url = self.installer.get_help_url(pkginfo)
if helppage_url not in ('', None):
self.builder.get_object("application_help_page").show()
helppage_label = _("Documentation")
self.builder.get_object("application_help_page").set_markup("<a href='%s'>%s</a>" % (helppage_url, helppage_label))
self.builder.get_object("application_help_page").set_tooltip_text(helppage_url)
else:
self.builder.get_object("application_help_page").hide()
description = self.installer.get_description(pkginfo)
if self.settings.get_boolean(prefs.HAMONIKR_SCREENSHOTS):
try:
from bs4 import BeautifulSoup
hamonikrpkgname = pkginfo.name.replace("-","_")
page = BeautifulSoup(urllib.request.urlopen("https://hamonikr.org/%s" % hamonikrpkgname, timeout=5), "lxml")
texts = page.find("div","xe_content")
text = texts.get_text()
if text is not None:
description = text
except Exception as e:
pass
app_description = self.builder.get_object("application_description")
if description not in (None, ''):
app_description.set_label(description)
app_description.show()
else:
app_description.hide()
box_reviews = self.builder.get_object("box_reviews")
for child in box_reviews.get_children():
box_reviews.remove(child)
if not is_flatpak or pkginfo.verified:
review_info = self.review_cache[pkginfo.name]
label_num_reviews = self.builder.get_object("application_num_reviews")
# TRANSLATORS: showing specific number of reviews in the list view and the header of the package details.
review_text = gettext.ngettext("%d Review", "%d Reviews", review_info.num_reviews) % review_info.num_reviews
label_num_reviews.set_label(review_text)
self.builder.get_object("application_avg_rating").set_label(str(review_info.avg_rating))
box_stars = self.builder.get_object("box_stars")
for child in box_stars.get_children():
box_stars.remove(child)
rating = review_info.avg_rating
remaining_stars = 5
while rating >= 1.0:
box_stars.pack_start(Gtk.Image(icon_name="starred-symbolic", pixel_size=16), False, False, 0)
rating -= 1
remaining_stars -= 1
if rating > 0.0:
box_stars.pack_start(Gtk.Image(icon_name="semi-starred-symbolic", pixel_size=16), False, False, 0)
remaining_stars -= 1
for i in range(remaining_stars):
box_stars.pack_start(Gtk.Image(icon_name="non-starred-symbolic", pixel_size=16), False, False, 0)
box_stars.show_all()
for i in range(0, 5):
self.star_bars[i].set_fraction(0.0)
self.builder.get_object("stars_count_%d" % (i + 1)).set_label("")
reviews = review_info.reviews
reviews.sort(key=lambda x: x.date, reverse=True)
stars = [0, 0, 0, 0, 0]
n_reviews = len(reviews)
if n_reviews > 0:
# TRANSLATORS: reviews heading in package details view
# label_reviews.set_text(_("Reviews"))
i = 0
for review in reviews:
if i < 10:
comment = review.comment.strip()
comment = comment.replace("'", "\'")
comment = comment.replace('"', '\"')
comment = self.capitalize(comment)
review_date = datetime.fromtimestamp(review.date).strftime("%Y.%m.%d")
tile = ReviewTile(review.username, review_date, comment, review.rating)
box_reviews.add(tile)
i = i +1
stars[review.rating - 1] += 1
for i in range(0, 5):
widget_idx = i + 1
label = self.builder.get_object("stars_count_%s" % widget_idx)
label.set_label(str(stars[i]))
self.star_bars[i].set_fraction(stars[i] / n_reviews)
add_your_own = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
add_your_own.set_margin_start(12)
add_your_own.set_margin_end(12)
add_your_own.set_margin_top(12)
add_your_own.set_margin_bottom(12)
community_link = "https://community.linuxmint.com/software/view/%s" % pkginfo.name
label = Gtk.Label(xalign=0.0, use_markup=True, label=_("Click <a href='%s'>here</a> to add your own review.") % community_link)
add_your_own.pack_start(label, True, True, 0)
box_reviews.add(add_your_own)
box_reviews.show_all()
self.builder.get_object("reviews_page").show()
self.builder.get_object("details_review_box").show()
else:
self.builder.get_object("reviews_page").hide()
self.builder.get_object("details_review_box").hide()
# Screenshots
self.destroy_screenshot_window()
for child in self.screenshot_stack.get_children():
child.destroy()
self.ss_swipe_handler.set_propagation_phase(Gtk.PropagationPhase.NONE)
self.screenshot_stack.last = 0
self.screenshot_stack.add_named(Gtk.Spinner(active=True), "spinner")
self.screenshot_controls_vgroup.set_visible(False)
self.add_screenshots(pkginfo)
self.screenshot_stack.show_all()
self.screenshot_stack.grab_focus()
# Call the installer to get the rest of the information
self.task_cancellable = self.installer.select_pkginfo(pkginfo,
self.on_installer_info_ready, self.on_installer_info_error,
self.on_installer_finished, self.on_installer_progress,
use_mainloop=True)
self.populate_addons(pkginfo)
def on_package_type_button_clicked(self, button, pkginfo):
self.show_package(pkginfo, self.previous_page)
def get_flatpak_for_deb(self, pkginfo):
try:
fp_name = FLATPAK_EQUIVS[pkginfo.name]
flatpak_pkginfo = self.installer.find_pkginfo(fp_name, installer.PKG_TYPE_FLATPAK)
if self.should_show_pkginfo(flatpak_pkginfo):
return flatpak_pkginfo
except:
return None
def get_deb_for_flatpak(self, pkginfo):
try:
deb_name = DEB_EQUIVS[pkginfo.name]
return self.installer.find_pkginfo(deb_name, installer.PKG_TYPE_APT)
except:
return None
def on_installer_info_error(self, task):
if networking_available():
if task.info_ready_status != task.STATUS_FORBIDDEN:
dialogs.show_error(task.error_message)
else:
dialogs.show_error(_("Unable to communicate with servers. Check your Internet connection and try again."))
self.recursion_buster = True
# The error ui changes can be handled in the normal callback
self.on_installer_info_ready(task)
def on_installer_info_ready(self, task):
self.current_task = task
self.task_cancellable = None
pkginfo = task.pkginfo
if pkginfo != self.current_pkginfo and (not self.installer.task_running(task)):
return
if self.installer.task_running(task):
self.builder.get_object("notebook_progress").set_current_page(self.PROGRESS_TAB)
else:
self.builder.get_object("notebook_progress").set_current_page(self.ACTION_TAB)
task.parent_window = self.main_window
# Load package info
style_context = self.action_button.get_style_context()
action_button_description = ""
action_button_icon = None
if task.info_ready_status == task.STATUS_OK:
if task.type == "remove":
action_button_label = _("Remove")
action_button_icon = None
style_context.remove_class("suggested-action")
style_context.add_class("destructive-action")
action_button_description = _("Installed")
self.action_button.set_sensitive(True)
self.progress_label.set_text(_("Removing"))
else:
action_button_label = _("Install")
if pkginfo.pkg_hash.startswith("f"):
action_button_icon = "mintinstall-package-flatpak-symbolic"
else:
action_button_icon = "linuxmint-logo-badge-symbolic"
style_context.remove_class("destructive-action")
style_context.add_class("suggested-action")
action_button_description = _("Not installed")
self.action_button.set_sensitive(True)
self.progress_label.set_text(_("Installing"))
else:
if task.info_ready_status == task.STATUS_FORBIDDEN:
if task.type == "remove":
action_button_label = _("Cannot remove")
action_button_description = _("Removing this package could cause irreparable damage to your system.")
else:
action_button_label = _("Cannot install")
action_button_description = _("Installing this package could cause irreparable damage to your system.")
style_context.remove_class("suggested-action")
style_context.add_class("destructive-action")
self.action_button.set_sensitive(False)
self.progress_label.set_text("")
elif task.info_ready_status == task.STATUS_BROKEN:
action_button_label = _("Not available")
style_context.remove_class("destructive-action")
style_context.remove_class("suggested-action")
if task.type == "install":
action_button_description = _("Please use apt-get to install this package.")
else:
action_button_description = _("Use apt-get to remove this package.")
self.action_button.set_sensitive(False)
elif task.info_ready_status == task.STATUS_UNKNOWN:
action_button_label = _("Try again")
style_context.remove_class("destructive-action")
style_context.add_class("suggested-action")
action_button_description = _("Something went wrong. Click to try again.")
self.action_button.set_sensitive(True)
self.progress_label.set_text("")
action_button_icon = None
self.action_button.set_label(action_button_label)
self.action_button.set_tooltip_text(action_button_description)
try:
self.builder.get_object("application_remote").set_label(self.flatpak_remote_categories[task.remote].name)
except (KeyError, AttributeError):
self.builder.get_object("application_remote").set_label(task.remote)
self.builder.get_object("application_branch").set_label(task.branch)
try:
self.builder.get_object("application_version").set_label(task.version)
except TypeError:
self.builder.get_object("application_version").set_label("")
sizeinfo = ""
if self.installer.pkginfo_is_installed(pkginfo):
if task.freed_size > 0:
sizeinfo = get_size_for_display(task.freed_size)
elif task.install_size > 0:
sizeinfo = _("%(localSize)s of disk space required") \
% {'localSize': get_size_for_display(task.install_size)}
else:
if task.freed_size > 0:
sizeinfo = _("%(downloadSize)s to download, %(localSize)s of disk space freed") \
% {'downloadSize': get_size_for_display(task.download_size), 'localSize': get_size_for_display(task.freed_size)}
else:
sizeinfo = _("%(downloadSize)s to download, %(localSize)s of disk space required") \
% {'downloadSize': get_size_for_display(task.download_size), 'localSize': get_size_for_display(task.install_size)}
if task.info_ready_status != task.STATUS_UNKNOWN:
self.builder.get_object("application_size").set_label(sizeinfo)
self.flatpak_details_vgroup.set_visible(task.branch != "")
if self.action_button_signal_id > 0:
self.action_button.disconnect(self.action_button_signal_id)
self.action_button_signal_id = 0
self.action_button_signal_id = self.action_button.connect("clicked",
self.on_action_button_clicked,
task)
bin_name = pkginfo.name.replace(":i386", "")
exec_string = None
if self.installer.pkginfo_is_installed(pkginfo):
if pkginfo.pkg_hash.startswith("a"):
for desktop_file in [
# foo.desktop
"/usr/share/applications/%s.desktop" % bin_name,
# foo in foo-bar.desktop or foo_bar.desktop
"/usr/share/applications/%s.desktop" % bin_name.split("-")[0],
"/usr/share/applications/%s.desktop" % bin_name.split("_")[0],
# foo-bar package with foo_bar.desktop
"/usr/share/applications/%s.desktop" % bin_name.replace("-", "_"),
# foo in org.bar.Foo.desktop
"/usr/share/applications/%s.desktop" % bin_name.split(".")[-1],
"/usr/share/app-install/desktop/%s:%s.desktop" % (bin_name, bin_name)
]:
if os.path.exists(desktop_file):
try:
info = Gio.DesktopAppInfo.new_from_filename(desktop_file)
exec_string = info.get_commandline()
if exec_string is not None:
break
except Exception as e:
print(e)
if exec_string is None and os.path.exists("/usr/bin/%s" % bin_name):
exec_string = "/usr/bin/%s" % bin_name
else:
launchables = self.installer.get_flatpak_launchables(pkginfo)
if launchables:
for desktop_id in launchables:
desktop_file = os.path.join(self.installer.get_flatpak_root_path(), "exports/share/applications", desktop_id)
try:
info = Gio.DesktopAppInfo.new_from_filename(desktop_file)
except TypeError:
info = Gio.DesktopAppInfo.new_from_filename(desktop_file + ".desktop")
exec_string = info.get_commandline()
break
else:
desktop_file = os.path.join(self.installer.get_flatpak_root_path(), "exports/share/applications", pkginfo.name)
info = None
try:
info = Gio.DesktopAppInfo.new_from_filename(desktop_file)
except TypeError:
try:
info = Gio.DesktopAppInfo.new_from_filename(desktop_file + ".desktop")
except:
pass
if info is not None:
exec_string = info.get_commandline()
if exec_string is not None:
task.exec_string = exec_string
self.launch_button.show()
if self.launch_button_signal_id > 0:
self.launch_button.disconnect(self.launch_button_signal_id)
self.launch_button_signal_id = 0
self.launch_button_signal_id = self.launch_button.connect("clicked",
self.on_launch_button_clicked,
task)
else:
self.launch_button.hide()
def populate_addons(self, pkginfo):
for row in self.addons_listbox.get_children():
row.destroy()
addons = self.installer.get_addons(pkginfo)
if addons is None:
self.builder.get_object("addons_page").hide()
return
name_size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
button_size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
first = True
for addon in addons:
print("Discovered addon: %s" % addon.name)
first = False
row = FlatpakAddonRow(self, pkginfo, addon, name_size_group, button_size_group)
self.addons_listbox.insert(row, -1)
self.builder.get_object("addons_page").show_all()
def on_installer_progress(self, pkginfo, progress, estimating, status_text=None):
if self.current_pkginfo is not None and self.current_pkginfo.name == pkginfo.name:
self.builder.get_object("notebook_progress").set_current_page(self.PROGRESS_TAB)
if estimating:
self.start_progress_pulse()
else:
self.stop_progress_pulse()
self.builder.get_object("application_progress").set_fraction(progress / 100.0)
XApp.set_window_progress(self.main_window, progress)
self.progress_label.tick()
def on_installer_finished(self, task):
if self.current_pkginfo is not None and self.current_pkginfo.name == task.pkginfo.name:
self.stop_progress_pulse()
self.builder.get_object("notebook_progress").set_current_page(self.ACTION_TAB)
self.builder.get_object("application_progress").set_fraction(0 / 100.0)
XApp.set_window_progress(self.main_window, 0)
self.update_state(task.pkginfo)
def start_progress_pulse(self):
if self.installer_pulse_timer > 0:
return
self.builder.get_object("application_progress").pulse()
self.installer_pulse_timer = GLib.timeout_add(1050, self.installer_pulse_tick)
def installer_pulse_tick(self):
p = self.builder.get_object("application_progress")
p.pulse()
return GLib.SOURCE_CONTINUE
def stop_progress_pulse(self):
if self.installer_pulse_timer > 0:
GLib.source_remove(self.installer_pulse_timer)
self.installer_pulse_timer = 0
class DottedProgressLabel(Gtk.Fixed):
"""
Centers a label's base text, adds ... as a progress/
activity indicator, without the text getting repositioned.
"""
def __init__(self):
super(DottedProgressLabel, self).__init__()
self.real_text = ""
self.label = Gtk.Label()
self.num_dots = 0
self.add(self.label)
self.label.show()
def set_text(self, text):
self.real_text = text
self.label.set_text(text)
self._adjust_position()
def tick(self):
if self.num_dots < 5:
self.num_dots += 1
else:
self.num_dots = 0
new_string = self.real_text
i = 0
while i < self.num_dots:
new_string += "."
i += 1
self.label.set_text(new_string)
def _adjust_position(self):
layout = self.label.create_pango_layout()
layout.set_text(self.real_text, -1)
w, h = layout.get_pixel_size()
parent_width = self.get_allocated_width()
x_offset = (parent_width - w) / 2
self.move(self.label, x_offset, 0)
if __name__ == "__main__":
os.system("mkdir -p %s" % imaging.SCREENSHOT_DIR)
if os.environ.get("RAYON_NUM_THREADS") is None:
os.environ["RAYON_NUM_THREADS"] = "2"
app = Application()
app.run(sys.argv)