diff --git a/bin/pytop-0-0-1-x64.deb b/bin/pytop-0-0-1-x64.deb index 4823e22..fe7e716 100644 Binary files a/bin/pytop-0-0-1-x64.deb and b/bin/pytop-0-0-1-x64.deb differ diff --git a/src/Pytop/resources/PyTop.glade b/src/Pytop/resources/PyTop.glade index e1124db..f416ce2 100644 --- a/src/Pytop/resources/PyTop.glade +++ b/src/Pytop/resources/PyTop.glade @@ -30,6 +30,9 @@ + + + True @@ -45,6 +48,22 @@ 1 + + + True + False + start + + + + + + False + True + end + 0 + + False @@ -53,23 +72,66 @@ - + True - True - in + False - + True - False + True + in - + True - True - 6 - multiple + False + + + True + True + 6 + multiple + + + + True + True + 0 + + + + + 180 + 64 + True + True + never + in + 225 + + + True + False + + + True + False + vertical + + + + + + + + + + False + True + 1 + @@ -94,10 +156,9 @@ True False - + True False - start @@ -113,7 +174,7 @@ - + True False start @@ -124,7 +185,7 @@ False True - 2 + 1 diff --git a/src/Pytop/signal_classes/GridSignals.py b/src/Pytop/signal_classes/GridSignals.py index 1062118..1ed18ad 100644 --- a/src/Pytop/signal_classes/GridSignals.py +++ b/src/Pytop/signal_classes/GridSignals.py @@ -27,7 +27,6 @@ class GridSignals: # Add filter to allow only folders to be selected selectDirDialog.add_filter(filefilter) selectDirDialog.set_filename(self.currentPath) - print(selectDirDialog.get_filename()) self.setNewDirectory(selectDirDialog) @@ -35,9 +34,7 @@ class GridSignals: newPath = widget.get_filename() self.gridClss = Grid(self.gridObj, self.settings) self.gridClss.setNewDirectory(newPath) - - if not "~/Desktop/" in newPath: - self.settings.saveSettings(newPath) + self.settings.saveSettings(newPath) # File control events diff --git a/src/Pytop/signal_classes/TaskbarSignals.py b/src/Pytop/signal_classes/TaskbarSignals.py index 6fca296..7dd3c8a 100644 --- a/src/Pytop/signal_classes/TaskbarSignals.py +++ b/src/Pytop/signal_classes/TaskbarSignals.py @@ -19,16 +19,29 @@ class TaskbarSignals: def __init__(self, settings): self.settings = settings self.builder = self.settings.returnBuilder() + self.orientation = 1 # 0 = horizontal, 1 = vertical self.setPagerWidget() self.setTasklistWidget() def setPagerWidget(self): pager = wnck.Pager() - self.builder.get_object('taskBarWorkspaces').add(pager) + + pager.set_orientation(self.orientation) + if self.orientation == 0: + self.builder.get_object('taskBarWorkspacesHor').add(pager) + else: + self.builder.get_object('taskBarWorkspacesVer').add(pager) + def setTasklistWidget(self): barBtns = wnck.Tasklist() + barBtns.set_scroll_enabled(False) barBtns.set_button_relief(2) # 0 = normal relief, 2 = no relief - barBtns.set_grouping(1) # 0 = mever group, 1 auto group, 2 = always group - self.builder.get_object('taskBarButtons').add(barBtns) + barBtns.set_grouping(1) # 0 = mever group, 1 auto group, 2 = always group + + barBtns.set_orientation(self.orientation) + if self.orientation == 0: + self.builder.get_object('taskBarButtonsHor').add(barBtns) + else: + self.builder.get_object('taskBarButtonsVer').add(barBtns) diff --git a/src/Pytop/widgets/Icon.py b/src/Pytop/widgets/Icon.py index e443d42..7b97926 100644 --- a/src/Pytop/widgets/Icon.py +++ b/src/Pytop/widgets/Icon.py @@ -12,6 +12,8 @@ import os, subprocess, hashlib, threading from os.path import isdir, isfile, join # Application imports +from .icon_manager import easybuttons + def threaded(fn): @@ -91,7 +93,6 @@ class Icon: try: xdgObj = DesktopEntry(fullPath) icon = xdgObj.getIcon() - iconsDirs = "/usr/share/icons" altIconPath = "" if "steam" in icon: @@ -128,6 +129,8 @@ 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 diff --git a/src/Pytop/widgets/icon_manager/__init__.py b/src/Pytop/widgets/icon_manager/__init__.py new file mode 100644 index 0000000..c797f4b --- /dev/null +++ b/src/Pytop/widgets/icon_manager/__init__.py @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000..9b215dc --- /dev/null +++ b/src/Pytop/widgets/icon_manager/easybuttons.py @@ -0,0 +1,613 @@ +# -*- 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 new file mode 100644 index 0000000..d39c900 --- /dev/null +++ b/src/Pytop/widgets/icon_manager/execute.py @@ -0,0 +1,74 @@ +# -*- 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 new file mode 100644 index 0000000..6ac5919 --- /dev/null +++ b/src/Pytop/widgets/icon_manager/filemonitor.py @@ -0,0 +1,136 @@ +# -*- 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()