mintmenu/usr/lib/linuxmint/mintMenu/plugins/easybuttons.py
Clement Lefebvre 29d14c01d1 Fix launching of pkexec applications
When launching mintsources, timeshift or an app which Exec field (in the desktop file)
starts with pkexec, nothing happens and the output states:

"Refusing to render service to dead parents."

For some reason this does not happen on fresh LMDE 4 and Mint 19.3 installations,
but it happens on LMDE 3 -> LMDE 4 and 19.1 -> 19.3 upgrades.

Similar bugs were fixed in nemo and cinnamon. Pkexec is known to cause issues
depending on how it's launched.

This introduces a workaround to wrap the pkexec call within an "sh -c".
2020-03-30 14:40:06 +01:00

526 lines
19 KiB
Python
Executable File

#!/usr/bin/python2
import os.path
import shutil
import xdg.DesktopEntry
import xdg.Menu
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("MateDesktop", "2.0")
from gi.repository import Gtk, Gdk, GdkPixbuf, GLib, GObject, Pango, MateDesktop
from plugins.execute import Execute
from plugins.filemonitor import monitor as filemonitor
class IconManager(GObject.GObject):
__gsignals__ = {
"changed" : (GObject.SignalFlags.RUN_LAST, None, ())
}
def __init__(self):
GObject.GObject.__init__(self)
self.icons = {}
self.count = 0
# Some apps don't put a default icon in the default theme folder, so we will search all themes
# def createTheme(d):
# theme = Gtk.IconTheme()
# theme.set_custom_theme(d)
# return theme
# This takes to much time and there are only a very few applications that use icons from different themes
#self.themes = map( createTheme, [d for d in os.listdir("/usr/share/icons") if os.path.isdir(os.path.join("/usr/share/icons", d))])
self.defaultTheme = Gtk.IconTheme.get_default()
#defaultKdeTheme = createTheme("kde.default")
# Setup and clean up the temp icon dir
configDir = GLib.get_user_config_dir()
self.iconDir = os.path.join(configDir, "mintmenu")
if not os.path.exists(self.iconDir):
os.makedirs(self.iconDir)
contents = os.listdir(self.iconDir)
for fn in contents:
os.remove(os.path.join(self.iconDir, fn))
self.defaultTheme.append_search_path(self.iconDir)
# Themes with the same content as the default them aren't needed
#self.themes = [theme for theme in self.themes if theme.list_icons() != defaultTheme.list_icons()]
#self.themes = [self.defaultTheme, defaultKdeTheme]
self.themes = [self.defaultTheme]
# Listen for changes in the themes
# for theme in self.themes:
# theme.connect("changed", self.themeChanged)
def getIcon(self, iconName, iconSize):
if not iconName:
return None
try:
iconFileName = ""
realIconName = ""
needTempFile = False
#[iconWidth, iconHeight] = self.getIconSize(iconSize)
if iconSize <= 0:
return None
elif os.path.isabs(iconName):
iconFileName = iconName
needTempFile = True
else:
if iconName[-4:] in [".png", ".xpm", ".svg", ".gif"]:
realIconName = iconName[:-4]
else:
realIconName = iconName
if iconFileName and needTempFile and os.path.exists(iconFileName):
tmpIconName = iconFileName.replace("/", "-")
realIconName = tmpIconName[:-4]
if not os.path.exists(os.path.join(self.iconDir, tmpIconName)):
shutil.copyfile(iconFileName, os.path.join(self.iconDir, tmpIconName))
self.defaultTheme.append_search_path(self.iconDir)
image = Gtk.Image.new_from_icon_name(realIconName, Gtk.IconSize.DND)
image.set_pixel_size(iconSize)
return image
except Exception as e:
print("Exception %s: %s" % (e.__class__.__name__, e))
return None
def themeChanged(self, theme):
self.emit("changed")
GObject.type_register(IconManager)
class easyButton(Gtk.Button):
def __init__(self, iconName, iconSize, labels=None, buttonWidth=-1, buttonHeight=-1, ellipsis=True):
GObject.GObject.__init__(self)
self.connections = []
self.iconName = iconName
self.iconSize = iconSize
self.showIcon = True
self.ellipsis = ellipsis
self.set_relief(Gtk.ReliefStyle.NONE)
self.set_size_request(buttonWidth, buttonHeight)
Align1 = Gtk.Alignment.new(0, 0.5, 1.0, 0)
HBox1 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
self.labelBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)
self.buttonImage = self.getIcon(self.iconSize)
if not self.buttonImage:
self.buttonImage = Gtk.Image()
self.buttonImage.set_size_request(self.iconSize, self.iconSize)
self.image_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
self.image_box.pack_start(self.buttonImage, False, False, 5)
self.image_box.show_all()
HBox1.pack_start(self.image_box, False, False, 0)
if labels:
for label in labels:
if isinstance(label, basestring):
self.addLabel(label)
elif isinstance(label, list):
self.addLabel(label[0], label[1])
self.labelBox.show()
HBox1.pack_start(self.labelBox , True, True, 0)
HBox1.show()
Align1.add(HBox1)
Align1.show()
self.add(Align1)
self.connectSelf("destroy", self.onDestroy)
self.connect("released", self.onRelease)
# Reload icons when the theme changed
# self.themeChangedHandlerId = iconManager.connect("changed", self.themeChanged)
def connectSelf(self, event, callback):
self.connections.append(self.connect(event, callback))
def onRelease(self, widget):
widget.get_style_context().set_state(Gtk.StateFlags.NORMAL)
def onDestroy(self, widget):
self.buttonImage.clear()
# iconManager.disconnect(self.themeChangedHandlerId)
for connection in self.connections:
self.disconnect(connection)
del self.connections
def addLabel(self, text, styles = None):
label = Gtk.Label()
if "<b>" in text or "<span" in text:
label.set_markup(text.replace('&', '&amp;')) # don't remove our pango
else:
label.set_text(text)
if styles:
labelStyle = Pango.AttrList()
for attr in styles:
labelStyle.insert(attr)
label.set_attributes(labelStyle)
if self.ellipsis:
label.set_ellipsize(Pango.EllipsizeMode.END)
label.set_alignment(0.0, 1.0)
label.set_max_width_chars(0)
label.show()
self.labelBox.pack_start(label , True, True, 0)
def getIcon(self, iconSize):
if not self.iconName:
return None
icon = iconManager.getIcon(self.iconName, iconSize)
return icon
def setIcon(self, iconName):
self.iconName = iconName
self.iconChanged()
# IconTheme changed, setup new button icons
def themeChanged(self, theme):
self.iconChanged()
def iconChanged(self):
icon = self.getIcon(self.iconSize)
self.buttonImage.destroy()
if icon:
self.buttonImage = icon
self.image_box.pack_start(self.buttonImage, False, False, 5)
self.image_box.show_all()
else:
#[iW, iH] = iconManager.getIconSize(self.iconSize)
self.buttonImage.set_size_request(self.iconSize, self.iconSize )
def setIconSize(self, size):
self.iconSize = size
icon = self.getIcon(self.iconSize)
self.buttonImage.destroy()
if icon:
self.buttonImage = icon
self.image_box.pack_start(self.buttonImage, False, False, 5)
self.image_box.show_all()
elif self.iconSize:
#[iW, iH] = iconManager.getIconSize(self.iconSize)
self.buttonImage.set_size_request(self.iconSize, self.iconSize )
class ApplicationLauncher(easyButton):
def __init__(self, desktopFile, iconSize):
if isinstance(desktopFile, xdg.Menu.MenuEntry):
desktopItem = desktopFile.DesktopEntry
desktopFile = desktopItem.filename
self.appDirs = desktopFile.AppDirs
elif isinstance(desktopFile, xdg.Menu.DesktopEntry):
desktopItem = desktopFile
desktopFile = desktopItem.filename
self.appDirs = [os.path.dirname(desktopItem.filename)]
else:
desktopItem = xdg.DesktopEntry.DesktopEntry(desktopFile)
self.appDirs = [os.path.dirname(desktopFile)]
self.desktopFile = desktopFile
self.startupMonitorId = 0
self.loadDesktopEntry(desktopItem)
self.desktopEntryMonitors = []
base = os.path.basename(self.desktopFile)
for directory in self.appDirs:
self.desktopEntryMonitors.append(filemonitor.addMonitor(os.path.join(directory, base) , self.desktopEntryFileChangedCallback))
easyButton.__init__(self, self.appIconName, iconSize)
self.setupLabels()
# Drag and Drop
self.connectSelf("drag-data-get", self.dragDataGet)
targets = (Gtk.TargetEntry.new("text/plain", 0, 100), Gtk.TargetEntry.new("text/uri-list", 0, 101))
self.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, targets, Gdk.DragAction.COPY)
self.drag_source_set_icon_name(self.iconName)
self.connectSelf("focus-in-event", self.onFocusIn)
self.connectSelf("focus-out-event", self.onFocusOut)
self.connectSelf("clicked", self.execute)
def loadDesktopEntry(self, desktopItem):
try:
self.appName = self.strip_accents(desktopItem.getName())
self.appGenericName = self.strip_accents(desktopItem.getGenericName())
self.appComment = self.strip_accents(desktopItem.getComment())
self.appExec = self.strip_accents(desktopItem.getExec().replace('\\\\', '\\'))
self.appIconName = desktopItem.getIcon()
self.appCategories = desktopItem.getCategories()
self.appMateDocPath = desktopItem.get("X-MATE-DocPath") or ""
self.useTerminal = desktopItem.getTerminal()
self.appPath = desktopItem.getPath()
if not self.appMateDocPath:
self.appKdeDocPath = desktopItem.getDocPath() or ""
self.appName = self.appName.strip()
self.appGenericName = self.appGenericName.strip()
self.appComment = self.appComment.strip()
basename = os.path.basename(self.desktopFile)
self.startupFilePath = os.path.join(os.path.expanduser("~"), ".config/autostart", basename)
if self.startupMonitorId:
filemonitor.removeMonitor(self.startupMonitorId )
if os.path.exists(self.startupFilePath):
self.startupMonitorId = filemonitor.addMonitor(self.startupFilePath, self.startupFileChanged)
except Exception as e:
print(e)
self.appName = ""
self.appGenericName = ""
self.appComment = ""
self.appExec = ""
self.appIconName = ""
self.appCategories = ""
self.appDocPath = ""
self.startupMonitorId = 0
def onFocusIn(self, widget, event):
self.set_relief(Gtk.ReliefStyle.HALF)
def onFocusOut(self, widget, event):
self.set_relief(Gtk.ReliefStyle.NONE)
def setupLabels(self):
self.addLabel(self.appName)
def filterText(self, text):
keywords = text.lower().split()
appName = self.appName.lower()
appGenericName = self.appGenericName.lower()
appComment = self.appComment.lower()
appExec = self.appExec.lower()
for keyword in keywords:
keyw = self.strip_accents(keyword)
if keyw != "" and appName.find(keyw) == -1 and appGenericName.find(keyw) == -1 and appComment.find(keyw) == -1 and appExec.find(keyw) == -1:
self.hide()
return False
self.show()
return True
def strip_accents(self, string):
value = string
if isinstance(string, unicode):
try:
value = string.encode('UTF8', 'ignore')
except:
pass
return value
def getTooltip(self):
tooltip = self.appName
if self.appComment != "" and self.appComment != self.appName:
tooltip = tooltip + "\n" + self.appComment
return tooltip
def dragDataGet(self, widget, context, selection, targetType, eventTime):
if targetType == 100: # text/plain
selection.set_text("'" + self.desktopFile + "'", -1)
elif targetType == 101: # text/uri-list
if self.desktopFile[0:7] == "file://":
selection.set_uris([self.desktopFile])
else:
selection.set_uris(["file://" + self.desktopFile])
def execute(self, *args):
if self.appExec:
if self.useTerminal:
cmd = "x-terminal-emulator -e \"" + self.appExec + "\""
if os.path.exists("/usr/bin/mate-terminal"):
cmd = "mate-terminal -e \"" + self.appExec + "\""
Execute(cmd, self.appPath)
else:
if self.appExec.startswith("pkexec "):
print ("Using pkexec workaround...")
cmd = "sh -c \"" + self.appExec + "\""
Execute(cmd, self.appPath)
else:
Execute(None, desktopFile=self.desktopFile)
def uninstall(self, *args):
Execute("mint-remove-application " + self.desktopFile)
# IconTheme changed, setup new icons for button and drag 'n drop
def iconChanged(self):
easyButton.iconChanged(self)
icon = self.getIcon(Gtk.IconSize.DND)
if icon:
iconName, size = icon.get_icon_name()
self.drag_source_set_icon_name(iconName)
def startupFileChanged(self, *args):
self.inStartup = os.path.exists(self.startupFilePath)
def addToStartup(self):
startupDir = os.path.join(os.path.dirname(self.startupFilePath))
if not os.path.exists(startupDir):
os.makedirs(startupDir)
shutil.copyfile(self.desktopFile, self.startupFilePath)
# Remove %u, etc. from Exec entry, because MATE will not replace them when it starts the app
item = MateDesktop.DesktopItem.new_from_uri(self.startupFilePath, MateDesktop.DesktopItemLoadFlags.ONLY_IF_EXISTS)
if item:
r = re.compile("%[A-Za-z]")
tmp = r.sub("", item.get_string(MateDesktop.DESKTOP_ITEM_EXEC)).strip()
item.set_string(MateDesktop.DESKTOP_ITEM_EXEC, tmp)
item.save(self.startupFilePath, 0)
def removeFromStartup(self):
if os.path.exists(self.startupFilePath):
os.remove(self.startupFilePath)
def addToFavourites(self):
favouritesDir = os.path.join(os.path.expanduser("~"), ".linuxmint/mintMenu/applications")
if not os.path.exists(favouritesDir):
os.makedirs(favouritesDir)
shutil.copyfile(self.desktopFile, self.favouritesFilePath)
def removeFromFavourites(self):
if os.path.exists(self.favouritesFilePath):
os.remove(self.favouritesFilePath)
def isInStartup(self):
#return self.inStartup
return os.path.exists(self.startupFilePath)
def onDestroy(self, widget):
easyButton.onDestroy(self, widget)
if self.startupMonitorId:
filemonitor.removeMonitor(self.startupMonitorId)
for id in self.desktopEntryMonitors:
filemonitor.removeMonitor(id)
def desktopEntryFileChangedCallback(self):
GLib.timeout_add(200, self.onDesktopEntryFileChanged)
def onDesktopEntryFileChanged(self):
exists = False
base = os.path.basename(self.desktopFile)
for directory in self.appDirs:
if os.path.exists(os.path.join(directory, base)):
# print os.path.join(dir, base), self.desktopFile
self.loadDesktopEntry(xdg.DesktopEntry.DesktopEntry(os.path.join(directory, base)))
for child in self.labelBox:
child.destroy()
self.iconName = self.appIconName
self.setupLabels()
self.iconChanged()
exists = True
break
if not exists:
# FIXME: What to do in this case?
self.destroy()
return False
class MenuApplicationLauncher(ApplicationLauncher):
def __init__(self, desktopFile, iconSize, category, showComment, highlight=False):
self.showComment = showComment
self.appCategory = category
self.highlight = highlight
ApplicationLauncher.__init__(self, desktopFile, iconSize)
def filterCategory(self, category):
if self.appCategory == category or category == "":
self.show()
else:
self.hide()
def setupLabels(self):
appName = self.appName
appComment = self.appComment
if self.highlight:
try:
appName = "<b>%s</b>" % appName
appComment = "<b>%s</b>" % appComment
except Exception as e:
print(e)
if self.showComment and self.appComment != "":
if self.iconSize <= 2:
self.addLabel('<span size="small">%s</span>\n<span size="x-small">%s</span>' %
(appName, appComment))
else:
self.addLabel('%s\n<span size="small">%s</span>' %
(appName, appComment))
else:
self.addLabel(appName)
def execute(self, *args):
self.highlight = False
for child in self.labelBox:
child.destroy()
self.setupLabels()
return super(MenuApplicationLauncher, self).execute(*args)
def setShowComment(self, showComment):
self.showComment = showComment
for child in self.labelBox:
child.destroy()
self.setupLabels()
class FavApplicationLauncher(ApplicationLauncher):
def __init__(self, desktopFile, iconSize, swapGeneric = False):
self.swapGeneric = swapGeneric
ApplicationLauncher.__init__(self, desktopFile, iconSize)
def setupLabels(self):
if self.appGenericName:
if self.swapGeneric:
self.addLabel('<span weight="bold">%s</span>' % self.appName)
self.addLabel(self.appGenericName)
else:
self.addLabel('<span weight="bold">%s</span>' % self.appGenericName)
self.addLabel(self.appName)
else:
self.addLabel('<span weight="bold">%s</span>' % self.appName)
if self.appComment != "":
self.addLabel(self.appComment)
else:
self.addLabel("")
def setSwapGeneric(self, swapGeneric):
self.swapGeneric = swapGeneric
for child in self.labelBox:
child.destroy()
self.setupLabels()
class CategoryButton(easyButton):
def __init__(self, iconName, iconSize, labels , f):
easyButton.__init__(self, iconName, iconSize, labels, ellipsis=False)
self.filter = f
iconManager = IconManager()