diff --git a/README.md b/README.md index 19889c3..24c1543 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ Pytop is a Python + Gtk+ application to have a custom desktop interface. n/a # TODO -* Add a program menu dropdown button. (Can use much of Shellmen's code.) +n/a # Images -![1 Pytop Desktop showing Home directory. ](images/pic1.png) +![1 Pytop Desktop showing Desktop directory. ](images/pic1.png) +![2 Pytop Desktop showing main menu. ](images/pic2.png) diff --git a/bin/pytop-0-0-1-x64.deb b/bin/pytop-0-0-1-x64.deb index e142205..b725096 100644 Binary files a/bin/pytop-0-0-1-x64.deb and b/bin/pytop-0-0-1-x64.deb differ diff --git a/images/pic1.png b/images/pic1.png index ceb72c0..bfbcb18 100644 Binary files a/images/pic1.png and b/images/pic1.png differ diff --git a/images/pic2.png b/images/pic2.png new file mode 100644 index 0000000..f0f5cd3 Binary files /dev/null and b/images/pic2.png differ diff --git a/src/Pytop/resources/Main_Window.glade b/src/Pytop/resources/Main_Window.glade index 2a958f2..a8631ef 100644 --- a/src/Pytop/resources/Main_Window.glade +++ b/src/Pytop/resources/Main_Window.glade @@ -7,6 +7,26 @@ inode/directory + + 1 + 100 + 1 + 1 + 10 + + + + True + False + gtk-new + + + 64 + 64 + True + False + start_menu_icons/start_menu_icon2_32x32.png + False 800 @@ -28,7 +48,22 @@ True False - + + Menu + 64 + 64 + True + True + True + menuImage + True + + + + False + True + 0 + @@ -252,14 +287,6 @@ - - 1 - 100 - 1 - 1 - 10 - - 500 0 @@ -314,10 +341,255 @@ - - True + + 600 + 400 False - gtk-new + 4 + mouse + True + True + True + False + False + center + True + + + + + + True + False + vertical + + + True + True + True + True + True + edit-find-symbolic + False + False + + + + False + True + 0 + + + + + True + False + + + True + False + vertical + start + + + Accessories + True + True + True + + + + True + True + 0 + + + + + Multimedia + True + True + True + + + + True + True + 1 + + + + + Graphics + True + True + True + + + + True + True + 2 + + + + + Game + True + True + True + + + + True + True + 3 + + + + + Office + True + True + True + + + + True + True + 4 + + + + + Development + True + True + True + + + + True + True + 5 + + + + + Internet + True + True + True + + + + True + True + 6 + + + + + Settings + True + True + True + + + + True + True + 7 + + + + + System + True + True + True + + + + True + True + 8 + + + + + Wine + True + True + True + + + + True + True + 9 + + + + + Other + True + True + True + + + + True + True + 10 + + + + + False + True + 0 + + + + + True + True + True + never + in + + + True + False + + + True + False + vertical + + + + + + + + + + True + True + 1 + + + + + True + True + 1 + + + + True diff --git a/src/Pytop/resources/start_menu_icons/start_menu_icon2_128x128.png b/src/Pytop/resources/start_menu_icons/start_menu_icon2_128x128.png new file mode 100644 index 0000000..d565a6a Binary files /dev/null and b/src/Pytop/resources/start_menu_icons/start_menu_icon2_128x128.png differ diff --git a/src/Pytop/resources/start_menu_icons/start_menu_icon2_256x256.png b/src/Pytop/resources/start_menu_icons/start_menu_icon2_256x256.png new file mode 100644 index 0000000..29810bb Binary files /dev/null and b/src/Pytop/resources/start_menu_icons/start_menu_icon2_256x256.png differ diff --git a/src/Pytop/resources/start_menu_icons/start_menu_icon2_32x32.png b/src/Pytop/resources/start_menu_icons/start_menu_icon2_32x32.png new file mode 100644 index 0000000..ba53a44 Binary files /dev/null and b/src/Pytop/resources/start_menu_icons/start_menu_icon2_32x32.png differ diff --git a/src/Pytop/resources/start_menu_icons/start_menu_icon2_64x64.png b/src/Pytop/resources/start_menu_icons/start_menu_icon2_64x64.png new file mode 100644 index 0000000..07eae0a Binary files /dev/null and b/src/Pytop/resources/start_menu_icons/start_menu_icon2_64x64.png differ diff --git a/src/Pytop/resources/start_menu_icons/start_menu_icon2_72x72.png b/src/Pytop/resources/start_menu_icons/start_menu_icon2_72x72.png new file mode 100644 index 0000000..221cca5 Binary files /dev/null and b/src/Pytop/resources/start_menu_icons/start_menu_icon2_72x72.png differ diff --git a/src/Pytop/resources/start_menu_icons/start_menu_icon2_96x96.png b/src/Pytop/resources/start_menu_icons/start_menu_icon2_96x96.png new file mode 100644 index 0000000..74020a7 Binary files /dev/null and b/src/Pytop/resources/start_menu_icons/start_menu_icon2_96x96.png differ diff --git a/src/Pytop/resources/start_menu_icons/start_menu_icon_128x128.png b/src/Pytop/resources/start_menu_icons/start_menu_icon_128x128.png new file mode 100644 index 0000000..f11fb1b Binary files /dev/null and b/src/Pytop/resources/start_menu_icons/start_menu_icon_128x128.png differ diff --git a/src/Pytop/resources/start_menu_icons/start_menu_icon_256x256.png b/src/Pytop/resources/start_menu_icons/start_menu_icon_256x256.png new file mode 100644 index 0000000..f7b5320 Binary files /dev/null and b/src/Pytop/resources/start_menu_icons/start_menu_icon_256x256.png differ diff --git a/src/Pytop/resources/start_menu_icons/start_menu_icon_32x32.png b/src/Pytop/resources/start_menu_icons/start_menu_icon_32x32.png new file mode 100644 index 0000000..f5abcf9 Binary files /dev/null and b/src/Pytop/resources/start_menu_icons/start_menu_icon_32x32.png differ diff --git a/src/Pytop/resources/start_menu_icons/start_menu_icon_64x64.png b/src/Pytop/resources/start_menu_icons/start_menu_icon_64x64.png new file mode 100644 index 0000000..7250d7c Binary files /dev/null and b/src/Pytop/resources/start_menu_icons/start_menu_icon_64x64.png differ diff --git a/src/Pytop/resources/start_menu_icons/start_menu_icon_72x72.png b/src/Pytop/resources/start_menu_icons/start_menu_icon_72x72.png new file mode 100644 index 0000000..2d4e7c1 Binary files /dev/null and b/src/Pytop/resources/start_menu_icons/start_menu_icon_72x72.png differ diff --git a/src/Pytop/resources/start_menu_icons/start_menu_icon_96x96.png b/src/Pytop/resources/start_menu_icons/start_menu_icon_96x96.png new file mode 100644 index 0000000..9ea489d Binary files /dev/null and b/src/Pytop/resources/start_menu_icons/start_menu_icon_96x96.png differ diff --git a/src/Pytop/signal_classes/Signals.py b/src/Pytop/signal_classes/Signals.py index f902683..cda13bb 100644 --- a/src/Pytop/signal_classes/Signals.py +++ b/src/Pytop/signal_classes/Signals.py @@ -1,17 +1,19 @@ # Python imports from datetime import datetime +import os # Gtk imports # Application imports -from .mixins import CPUDrawMixin, TaskbarMixin, GridMixin +from .mixins import CPUDrawMixin, MainMenuMixin, TaskbarMixin, GridMixin from widgets import Grid +from widgets import Icon from utils import FileHandler -class Signals(CPUDrawMixin, TaskbarMixin, GridMixin): +class Signals(CPUDrawMixin, MainMenuMixin, TaskbarMixin, GridMixin): def __init__(self, settings): self.settings = settings self.builder = self.settings.returnBuilder() @@ -69,3 +71,18 @@ class Signals(CPUDrawMixin, TaskbarMixin, GridMixin): selectDirDialog.add_filter(filefilter) selectDirDialog.set_filename(self.currentPath) self.setNewDirectory(selectDirDialog) + + + # Program Menu Parts + self.menuWindow = self.builder.get_object("menuWindow") + self.menuWindow.set_keep_above(True); + + self.iconFactory = Icon(self.settings) + self.grpDefault = "Accessories" + self.progGroup = self.grpDefault + HOME_APPS = os.path.expanduser('~') + "/.local/share/applications/" + paths = ["/usr/share/applications/", HOME_APPS] + self.menuData = self.getDesktopFilesInfo(paths) + self.desktopObjs = [] + self.getSubgroup() + self.generateListView() diff --git a/src/Pytop/signal_classes/mixins/GridMixin.py b/src/Pytop/signal_classes/mixins/GridMixin.py index 4003ea7..a10cf42 100644 --- a/src/Pytop/signal_classes/mixins/GridMixin.py +++ b/src/Pytop/signal_classes/mixins/GridMixin.py @@ -6,7 +6,7 @@ class GridMixin: - # different from the grid widget class' + # Calls the Grid widget class' method def setNewDirectory(self, widget, data=None): newPath = widget.get_filename() self.gridClss.setNewDirectory(newPath) diff --git a/src/Pytop/signal_classes/mixins/MainMenuMixin.py b/src/Pytop/signal_classes/mixins/MainMenuMixin.py new file mode 100644 index 0000000..9110b8c --- /dev/null +++ b/src/Pytop/signal_classes/mixins/MainMenuMixin.py @@ -0,0 +1,248 @@ +# Python imports +import subprocess +import threading +import os +import json + +from os.path import isdir, isfile, join +from os import listdir + + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') + +from gi.repository import Gtk as gtk +from gi.repository import GLib as glib +from xdg.DesktopEntry import DesktopEntry + + +# Application imports + + + + +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs).start() + return wrapper + + +class MainMenuMixin: + def toggleProgramMenu(self, widget): + pos = self.menuWindow.get_position() + posX = pos[0] + 32 + posY = pos[1] + 72 + if self.menuWindow.get_visible() == False: + self.menuWindow.move(posX, posY) + glib.idle_add(self.menuWindow.show_all) + else: + glib.idle_add(self.menuWindow.hide) + + + def setListGroup(self, widget): + self.progGroup = widget.get_label().strip() + self.getSubgroup(self.progGroup) + self.generateListView() + + + def searchForEntry(self, widget, data=None): + self.progGroup = "[ Search ]" + query = widget.get_text().strip() + if not query: + self.progGroup = self.grpDefault + self.getSubgroup() + self.generateListView() + return + + self.getSubgroup(query) + self.generateListView() + + + @threaded + def generateListView(self): + widget = self.builder.get_object("programListBttns") + + # Should have this as a useful method...But, I don't want to import Glib everywhere + children = widget.get_children() + for child in children: + glib.idle_add(widget.remove, (child)) + + for obj in self.desktopObjs: + title = obj[0] + dirPath = obj[1] + image = self.iconFactory.parseDesktopFiles(dirPath) # .get_pixbuf() + self.addToProgramListView(widget, title, image) + + + @threaded + def addToProgramListView(self, widget, title, icon): + button = gtk.Button(label=title) + button.set_image(icon) + button.connect("clicked", self.executeProgram) + button.show_all() + glib.idle_add(widget.add, (button)) + + + def executeProgram(self, widget): + """ + Need to refactor and pull out the sub loop that is used in both cases... + """ + entry = widget.get_label().strip() + group = self.progGroup + + parts = entry.split("||") + program = parts[0].strip() + comment = parts[1].strip() + + if "[ Search ]" in group: + gkeys = self.menuData.keys() + for gkey in gkeys: + for opt in self.menuData[gkey]: + if program in opt["title"]: + keys = opt.keys() + if comment in opt["comment"] or comment in opt["fileName"]: + DEVNULL = open(os.devnull, 'w') + execFailed = False + try: + command = opt["tryExec"].split("%")[0] + # self.logger.debug(command) + subprocess.Popen(command.split(), start_new_session=True, stdout=DEVNULL, stderr=DEVNULL) + break + except Exception as e: + execFailed = True + + if execFailed: + try: + if "exec" in keys and len(opt["exec"]): + command = opt["exec"].split("%")[0] + # self.logger.debug(command) + subprocess.Popen(command.split(), start_new_session=True, stdout=DEVNULL, stderr=DEVNULL) + break + except Exception as e: + print( repr(e) ) + # self.logger.debug(e) + else: + for opt in self.menuData[group]: + if program in opt["title"]: + keys = opt.keys() + if comment in opt["comment"] or comment in opt["fileName"]: + DEVNULL = open(os.devnull, 'w') + execFailed = False + try: + command = opt["tryExec"].split("%")[0] + # self.logger.debug(command) + subprocess.Popen(command.split(), start_new_session=True, stdout=DEVNULL, stderr=DEVNULL) + except Exception as e: + execFailed = True + + if execFailed: + try: + if "exec" in keys and len(opt["exec"]): + command = opt["exec"].split("%")[0] + # self.logger.debug(command) + subprocess.Popen(command.split(), start_new_session=True, stdout=DEVNULL, stderr=DEVNULL) + except Exception as e: + print( repr(e) ) + # self.logger.debug(e) + + + # Supoport methods + def getDesktopFilesInfo(self, paths): + menuObjs = { + "Accessories": [], + "Multimedia": [], + "Graphics": [], + "Game": [], + "Office": [], + "Development": [], + "Internet": [], + "Settings": [], + "System": [], + "Wine": [], + "Other": [] + } + + for path in paths: + for f in listdir(path): + fPath = path + f + flags = ["mimeinfo.cache", "defaults.list"] + if not f in flags and isfile(fPath): + xdgObj = DesktopEntry(fPath) + + title = xdgObj.getName() + groups = xdgObj.getCategories() + comment = xdgObj.getComment() + icon = xdgObj.getIcon() + mainExec = xdgObj.getExec() + tryExec = xdgObj.getTryExec() + + group = "" + if "Accessories" in groups or "Utility" in groups: + group = "Accessories" + elif "Multimedia" in groups or "Video" in groups or "Audio" in groups: + group = "Multimedia" + elif "Development" in groups: + group = "Development" + elif "Game" in groups: + group = "Game" + elif "Internet" in groups or "Network" in groups: + group = "Internet" + elif "Graphics" in groups: + group = "Graphics" + elif "Office" in groups: + group = "Office" + elif "System" in groups: + group = "System" + elif "Settings" in groups: + group = "Settings" + elif "Wine" in groups: + group = "Wine" + else: + group = "Other" + + menuObjs[group].append( {"title": title, "groups": groups, + "comment": comment, "exec": mainExec, + "tryExec": tryExec, "fileName": f, + "filePath": fPath, "icon": icon}) + + return menuObjs + + + def getSubgroup(self, query = ""): + """ + Need to refactor and pull out the sub logic that is used in both cases... + """ + group = self.progGroup + self.desktopObjs.clear() + if "[ Search ]" in group: + gkeys = self.menuData.keys() + for gkey in gkeys: + for opt in self.menuData[gkey]: + keys = opt.keys() + + if "comment" in keys and len(opt["comment"]) > 0 : + if query.lower() in opt["comment"].lower(): + title = opt["title"] + " || " + opt["comment"] + fPath = opt["filePath"] + self.desktopObjs.append([title, fPath]) + continue + + if query.lower() in opt["title"].lower() or \ + query.lower() in opt["fileName"].lower(): + title = opt["title"] + " || " + opt["fileName"].replace(".desktop", "") + fPath = opt["filePath"] + self.desktopObjs.append([title, fPath]) + else: + for opt in self.menuData[group]: + keys = opt.keys() + if "comment" in keys and len(opt["comment"]) > 0 : + title = opt["title"] + " || " + opt["comment"] + fPath = opt["filePath"] + self.desktopObjs.append([title, fPath]) + else: + title = opt["title"] + " || " + opt["fileName"].replace(".desktop", "") + fPath = opt["filePath"] + self.desktopObjs.append([title, fPath]) + + return self.desktopObjs diff --git a/src/Pytop/signal_classes/mixins/__init__.py b/src/Pytop/signal_classes/mixins/__init__.py index 4f38600..7df5562 100644 --- a/src/Pytop/signal_classes/mixins/__init__.py +++ b/src/Pytop/signal_classes/mixins/__init__.py @@ -1,3 +1,4 @@ +from .MainMenuMixin import MainMenuMixin from .TaskbarMixin import TaskbarMixin from .CPUDrawMixin import CPUDrawMixin from .GridMixin import GridMixin diff --git a/src/Pytop/widgets/Grid.py b/src/Pytop/widgets/Grid.py index 6e08d21..ae67fb7 100644 --- a/src/Pytop/widgets/Grid.py +++ b/src/Pytop/widgets/Grid.py @@ -46,7 +46,6 @@ class Grid: self.grid.set_model(self.store) self.grid.set_pixbuf_column(0) self.grid.set_text_column(1) - print("Grid generated...") self.grid.connect("item-activated", self.iconDblLeftClick) self.grid.connect("button_release_event", self.iconSingleClick, (self.grid,)) diff --git a/src/Pytop/widgets/Icon.py b/src/Pytop/widgets/Icon.py index c18fc8f..4083b1a 100644 --- a/src/Pytop/widgets/Icon.py +++ b/src/Pytop/widgets/Icon.py @@ -14,7 +14,7 @@ from xdg.DesktopEntry import DesktopEntry # Application imports -from .icon_manager import easybuttons + @@ -113,15 +113,6 @@ class Icon: execStr = xdgObj.getExec() parts = execStr.split("steam://rungameid/") id = parts[len(parts) - 1] - - # NOTE: Can try this logic instead... - # if command exists use it instead of header image - # if "steamcmd app_info_print id": - # proc = subprocess.Popen(["steamcmd", "app_info_print", id]) - # proc.wait() - # else: - # use the bottom logic - imageLink = "https://steamcdn-a.akamaihd.net/steam/apps/" + id + "/header.jpg" proc = subprocess.Popen(["wget", "-O", hashImgPth, imageLink]) proc.wait() @@ -131,14 +122,13 @@ class Icon: elif os.path.exists(icon): return self.createScaledImage(icon, self.systemIconImageWH) else: - # return easybuttons.IconManager().getIcon(icon, 64) - iconsDirs = "/usr/share/icons" - for (dirpath, dirnames, filenames) in os.walk(iconsDirs): - for file in filenames: - appNM = "application-x-" + icon - if appNM in file: - altIconPath = dirpath + "/" + file - break + iconsDirs = ["/usr/share/pixmaps", self.usrHome + "/.icons", "/usr/share/icons" ,] + altIconPath = "" + + for iconsDir in iconsDirs: + altIconPath = self.traverseIconsFolder(iconsDir, icon) + if altIconPath is not "": + break return self.createScaledImage(altIconPath, self.systemIconImageWH) except Exception as e: @@ -147,6 +137,19 @@ class Icon: return None + def traverseIconsFolder(self, path, icon): + altIconPath = "" + + for (dirpath, dirnames, filenames) in os.walk(path): + for file in filenames: + appNM = "application-x-" + icon + if icon in file or appNM in file: + altIconPath = dirpath + "/" + file + break + + return altIconPath + + def getSystemThumbnail(self, filename, size): try: if os.path.exists(filename): diff --git a/src/Pytop/widgets/icon_manager/__init__.py b/src/Pytop/widgets/icon_manager/__init__.py deleted file mode 100644 index c797f4b..0000000 --- a/src/Pytop/widgets/icon_manager/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from . import easybuttons -from . import execute -from . import filemonitor diff --git a/src/Pytop/widgets/icon_manager/easybuttons.py b/src/Pytop/widgets/icon_manager/easybuttons.py deleted file mode 100644 index 9b215dc..0000000 --- a/src/Pytop/widgets/icon_manager/easybuttons.py +++ /dev/null @@ -1,613 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2007-2014 Clement Lefebvre -# Copyright (C) 2015 Martin Wimpress -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the -# Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -import os -import re -import shutil -import xdg.DesktopEntry -import xdg.Menu -import gi - -gi.require_version('Gtk', '3.0') - -from .execute import * -from .filemonitor import monitor as filemonitor -from gi.repository import Gtk, Gdk, GLib -from gi.repository import Pango -from gi.repository import GObject - -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() - - # Setup and clean up the temp icon dir - configDir = GLib.get_user_config_dir() - self.iconDir = os.path.join(configDir, "mate-menu") - if not os.path.exists(self.iconDir): - os.makedirs(self.iconDir) - # Skip over files and dirs belonging to the applications plugin - contents = frozenset(os.listdir(self.iconDir)) - frozenset(('applications', 'applications.list')) - for fn in contents: - if os.path.isfile(os.path.join(self.iconDir, fn)): - print("Removing file : " + os.path.join(self.iconDir, fn)) - os.remove(os.path.join(self.iconDir, fn)) - else: - print(os.path.join(self.iconDir, fn) + " is not a file, skipping delete.") - - 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 ] - - # 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() - icon_found = False - for theme in self.themes: - if theme.lookup_icon( realIconName, 0, Gtk.IconLookupFlags.FORCE_REGULAR ): - icon_found = True - break - - if icon_found: - image.set_from_icon_name(realIconName, Gtk.IconSize.DND) - image.set_pixel_size(iconSize) - else: - image = None - - return image - except Exception as e: - print("Exception " + e.__class__.__name__ + ": " + e.message) - 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 ): - GObject.GObject.__init__( self ) - self.connections = [ ] - self.iconName = iconName - self.iconSize = iconSize - self.showIcon = True - - self.set_relief( Gtk.ReliefStyle.NONE ) - self.set_size_request( buttonWidth, buttonHeight ) - - HBox1 = Gtk.Box( orientation=Gtk.Orientation.HORIZONTAL ) - HBox1.set_valign(Gtk.Align.CENTER) - HBox1.set_hexpand(True) - self.labelBox = Gtk.Box( orientation=Gtk.Orientation.VERTICAL, spacing=2 ) - - - self.buttonImage = Gtk.Image() - icon = self.getIcon( self.iconSize ) - if icon: - self.buttonImage = icon - else: - #[ iW, iH ] = iconManager.getIconSize( self.iconSize ) - 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, str ): - 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() - self.add( HBox1 ) - - self.set_events(Gdk.EventMask.POINTER_MOTION_MASK) - self.connectSelf( "motion-notify-event", self.onMotion ) - self.connectSelf( "enter-notify-event", self.onEnter ) - self.connectSelf( "focus-in-event", self.onFocusIn ) - self.connectSelf( "focus-out-event", self.onFocusOut ) - 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 onMotion( self, widget, event ): - # Only grab if mouse is actually hovering - if self.mouse_entered: - self.grab_focus() - self.mouse_entered = False - - def onEnter( self, widget, event ): - # Prevent false "enter" notifications by determining - # whether the mouse is actually hovering on the button. - self.mouse_entered = True - - def onFocusIn( self, widget, event ): - self.set_state_flags( Gtk.StateFlags.PRELIGHT, False ) - - def onFocusOut( self, widget, event ): - self.unset_state_flags( Gtk.StateFlags.PRELIGHT ) - - 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 "" in text or "= (3, 16): - label.set_xalign(0.0) - label.set_yalign(1.0) - else: - 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 ) - if icon is None: - icon = iconManager.getIcon( "gtk-missing-image", 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 = desktop.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.relevance = 0 - - self.loadDesktopEntry( desktopItem ) - - self.desktopEntryMonitors = [] - - base = os.path.basename( self.desktopFile ) - for dir in self.appDirs: - self.desktopEntryMonitors.append( filemonitor.addMonitor( os.path.join(dir, 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 ) - - icon = self.getIcon( Gtk.IconSize.DND ) - if icon: - iconName, s = icon.get_icon_name() - self.drag_source_set_icon_name( 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 = desktopItem.getName() - self.appGenericName = desktopItem.getGenericName() - self.appComment = desktopItem.getComment() - self.appExec = 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() - self.appName = self.appName.strip() - self.appGenericName = self.appGenericName.strip() - self.appComment = self.appComment.strip() - - configPath = os.environ.get( "XDG_CONFIG_HOME", - os.path.join( os.environ["HOME"], ".config" ) ) - basename = os.path.basename( self.desktopFile ) - self.startupFilePath = os.path.join( configPath, "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 ): - super(ApplicationLauncher, self).onFocusIn( widget, event ) - self.set_relief( Gtk.ReliefStyle.HALF ) - - def onFocusOut( self, widget, event ): - super(ApplicationLauncher, self).onFocusOut( widget, event ) - self.set_relief( Gtk.ReliefStyle.NONE ) - - def setupLabels( self ): - self.addLabel( self.appName ) - - def filterText( self, text ): - keywords = text.lower().split() - self.relevance = 0 - appName = self.appName.lower() - appGenericName = self.appGenericName.lower() - appComment = self.appComment.lower() - appExec = self.appExec.lower() - for keyword in keywords: - keyw = keyword - - # Hide if the term does not match - 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 - - # Give better ranking to the actual app name - if appName == keyw: - self.relevance += 32 - elif appName.find( keyw ) == 0: - self.relevance += 16 - elif appName.find( keyw ) != -1: - self.relevance += 8 - - if appExec.find( keyw ) != -1: - self.relevance += 4 - if appComment.find( keyw ) != -1: - self.relevance += 2 - if appGenericName.find( keyw ) != -1: - self.relevance += 1 - - self.show() - return True - - 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 ): - - def pathExists(file): - if os.path.exists(file): - return True - for path in os.environ["PATH"].split(os.pathsep): - if os.path.exists(os.path.join(path, file)): - return True - - if self.appExec: - if self.useTerminal: - if pathExists("mate-terminal"): - cmd = "mate-terminal -e \"" + self.appExec + "\"" - elif pathExists("x-terminal-emulator"): - cmd = "x-terminal-emulator -e \"" + self.appExec + "\"" - else: - cmd = "xterm -e \"" + self.appExec + "\"" - Execute(cmd, self.appPath) - else: - Execute(self.appExec, self.appPath) - - # 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 removeFromStartup( self ): - if os.path.exists( self.startupFilePath ): - os.remove( self.startupFilePath ) - - def addToFavourites( self ): - configPath = os.environ.get( "XDG_CONFIG_HOME", - os.path.join( os.environ["HOME"], ".config" ) ) - favouritesDir = os.path.join( configPath, "mate-menu", "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 dir in self.appDirs: - if os.path.exists( os.path.join( dir, base ) ): - # print(os.path.join( dir, base ), self.desktopFile) - self.loadDesktopEntry( xdg.DesktopEntry.DesktopEntry( os.path.join( dir, 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: - #color = self.labelBox.get_style_context().get_color( Gtk.StateFlags.SELECTED ).to_string() - #if len(color) > 0 and color[0] == "#": - #appName = "%s" % (color, appName); - #appComment = "%s" % (color, appComment); - #appName = "%s" % (appName); - #appComment = "%s" % (appComment); - #else: - #appName = "%s" % (appName); - #appComment = "%s" % (appComment); - appName = "%s" % (appName); - appComment = "%s" % (appComment); - except Exception as detail: - print(detail) - pass - - if self.showComment and self.appComment != "": - if self.iconSize <= 2: - self.addLabel( '%s' % appName) - self.addLabel( '%s' % appComment) - else: - self.addLabel( appName ) - self.addLabel( '%s' % 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( '%s' % self.appName ) - self.addLabel( self.appGenericName ) - else: - self.addLabel( '%s' % self.appGenericName ) - self.addLabel( self.appName ) - else: - self.addLabel( '%s' % 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 ) - self.filter = f - self.set_focus_on_click(False) - - -iconManager = IconManager() diff --git a/src/Pytop/widgets/icon_manager/execute.py b/src/Pytop/widgets/icon_manager/execute.py deleted file mode 100644 index d39c900..0000000 --- a/src/Pytop/widgets/icon_manager/execute.py +++ /dev/null @@ -1,74 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2007-2014 Clement Lefebvre -# Copyright (C) 2015 Martin Wimpress -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the -# Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -import os -import shlex -import subprocess - -def RemoveArgs(Execline): - NewExecline = [] - Specials=["\"%c\"", "%f","%F","%u","%U","%d","%D","%n","%N","%i","%c","%k","%v","%m","%M", "-caption", "/bin/sh", "sh", "-c", "STARTED_FROM_MENU=yes"] - for elem in Execline: - elem = elem.replace("'","") - elem = elem.replace("\"", "") - if elem not in Specials: - print(elem) - NewExecline.append(elem) - return NewExecline - -# Actually execute the command -def ExecuteCommand(cmd , commandCwd=None): - if not commandCwd: - cwd = os.path.expanduser( "~" ); - else: - tmpCwd = os.path.expanduser( commandCwd ); - if (os.path.exists(tmpCwd)): - cwd = tmpCwd - - if isinstance( cmd, str ): - if (cmd.find("/home/") >= 0) or (cmd.find("xdg-su") >= 0) or (cmd.find("\"") >= 0): - print("running manually...") - try: - os.chdir(cwd) - subprocess.Popen(shlex.split(cmd)) - return True - except Exception as detail: - print(detail) - return False - cmd = cmd.split() - cmd = RemoveArgs(cmd) - - try: - os.chdir( cwd ) - string = ' '.join(cmd) - subprocess.Popen(shlex.split(string)) - return True - except Exception as detail: - print(detail) - return False - -# Execute cmd using the double fork method -def Execute(cmd, commandCwd=None): - child_pid = os.fork() - if child_pid == 0: - ExecuteCommand(cmd, commandCwd) - os._exit(0) - else: - os.wait() diff --git a/src/Pytop/widgets/icon_manager/filemonitor.py b/src/Pytop/widgets/icon_manager/filemonitor.py deleted file mode 100644 index 6ac5919..0000000 --- a/src/Pytop/widgets/icon_manager/filemonitor.py +++ /dev/null @@ -1,136 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2007-2014 Clement Lefebvre -# Copyright (C) 2015 Martin Wimpress -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the -# Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - -import os -import os.path -import threading -import time -from gi.repository import GLib - -try: - import pyinotify - hasInotify = True -except ImportError: - hasInotify = False - -if hasInotify: - class FileMonitor(object): - def __init__( self ): - self.monitorId = 0 - self.wm = pyinotify.WatchManager() - self.wdds = {} - self.callbacks = {} - self.notifier = pyinotify.ThreadedNotifier(self.wm, self.fileChanged) - self.notifier.setDaemon( True ) - self.notifier.start() - - - def addMonitor( self, filename, callback, args = None ): - try: - mask = pyinotify.IN_DELETE | pyinotify.IN_CREATE | pyinotify.IN_MODIFY - mId = self.wm.add_watch( filename, mask, rec = True)[filename] - if mId >= 0: - self.callbacks[mId] = ( callback, args ) - except Exception as detail: - mId = 0 - return mId - - def removeMonitor( self, monitorId ): - if monitorId in self.callbacks: - self.wm.rm_watch( monitorId ) - del self.callbacks[monitorId] - - def fileChanged(self, event ): - if event.wd in self.callbacks: - callback = self.callbacks[event.wd] - if callback[1]: - GLib.idle_add( callback[0], callback[1] ) - else: - GLib.idle_add( callback[0] ) -else: - - class _MonitoredFile( object ): - def __init__( self, filename, callback, monitorId, args ): - self.filename = filename - self.callback = callback - self.monitorId = monitorId - self.args = args - self.exists = os.path.exists( self.filename ) - if self.exists: - self.mtime = os.stat( filename ).st_mtime - else: - self.mtime = 0 - - def hasChanged( self ): - if os.path.exists( self.filename ): - if not self.exists: - self.exists = True - self.mtime = os.stat( self.filename ).st_mtime - return True - else: - mtime = os.stat( self.filename ).st_mtime - if mtime != self.mtime: - self.mtime = mtime - return True - else: - if self.exists: - self.exists = False - return True - - return False - - class MonitorThread(threading.Thread): - def __init__(self, monitor): - threading.Thread.__init__ ( self ) - self.monitor = monitor - - def run(self): - while(1): - self.monitor.checkFiles() - time.sleep(1) - - class FileMonitor(object): - def __init__( self ): - self.monitorId = 0 - self.monitoredFiles = [] - self.monitorThread = MonitorThread( self ) - self.monitorThread.setDaemon( True ) - self.monitorThread.start() - - def addMonitor( self, filename, callback, args = None ): - self.monitorId += 1 - self.monitoredFiles.append( _MonitoredFile( filename, callback, self.monitorId, args ) ) - return self.monitorId - - def removeMonitor( self, monitorId ): - for monitored in self.monitoredFiles: - if monitorId == monitored.monitorId: - self.monitoredFiles.remove( monitored ) - break - - def checkFiles( self ): - for monitored in self.monitoredFiles: - if monitored.hasChanged(): - if monitored.args: - GLib.idle_add( monitored.callback, monitored.args ) - else: - GLib.idle_add( monitored.callback ) - -monitor = FileMonitor()