added program menu
@ -8,7 +8,8 @@ Pytop is a Python + Gtk+ application to have a custom desktop interface.
|
|||||||
n/a
|
n/a
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
* Add a program menu dropdown button. (Can use much of Shellmen's code.)
|
n/a
|
||||||
|
|
||||||
# Images
|
# 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)
|
||||||
|
BIN
images/pic1.png
Before Width: | Height: | Size: 836 KiB After Width: | Height: | Size: 879 KiB |
BIN
images/pic2.png
Normal file
After Width: | Height: | Size: 594 KiB |
@ -7,6 +7,26 @@
|
|||||||
<mime-type>inode/directory</mime-type>
|
<mime-type>inode/directory</mime-type>
|
||||||
</mime-types>
|
</mime-types>
|
||||||
</object>
|
</object>
|
||||||
|
<object class="GtkAdjustment" id="brushSizeProp">
|
||||||
|
<property name="lower">1</property>
|
||||||
|
<property name="upper">100</property>
|
||||||
|
<property name="value">1</property>
|
||||||
|
<property name="step_increment">1</property>
|
||||||
|
<property name="page_increment">10</property>
|
||||||
|
<signal name="value-changed" handler="onBrushSizeChange" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<object class="GtkImage" id="createImage">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="stock">gtk-new</property>
|
||||||
|
</object>
|
||||||
|
<object class="GtkImage" id="menuImage">
|
||||||
|
<property name="width_request">64</property>
|
||||||
|
<property name="height_request">64</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="pixbuf">start_menu_icons/start_menu_icon2_32x32.png</property>
|
||||||
|
</object>
|
||||||
<object class="GtkWindow" id="Window">
|
<object class="GtkWindow" id="Window">
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="default_width">800</property>
|
<property name="default_width">800</property>
|
||||||
@ -28,7 +48,22 @@
|
|||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<child>
|
<child>
|
||||||
<placeholder/>
|
<object class="GtkButton" id="progMenu">
|
||||||
|
<property name="label" translatable="yes">Menu</property>
|
||||||
|
<property name="width_request">64</property>
|
||||||
|
<property name="height_request">64</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="image">menuImage</property>
|
||||||
|
<property name="always_show_image">True</property>
|
||||||
|
<signal name="clicked" handler="toggleProgramMenu" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<placeholder/>
|
<placeholder/>
|
||||||
@ -252,14 +287,6 @@
|
|||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
<object class="GtkAdjustment" id="brushSizeProp">
|
|
||||||
<property name="lower">1</property>
|
|
||||||
<property name="upper">100</property>
|
|
||||||
<property name="value">1</property>
|
|
||||||
<property name="step_increment">1</property>
|
|
||||||
<property name="page_increment">10</property>
|
|
||||||
<signal name="value-changed" handler="onBrushSizeChange" swapped="no"/>
|
|
||||||
</object>
|
|
||||||
<object class="GtkPopover" id="systemStats">
|
<object class="GtkPopover" id="systemStats">
|
||||||
<property name="width_request">500</property>
|
<property name="width_request">500</property>
|
||||||
<property name="height_request">0</property>
|
<property name="height_request">0</property>
|
||||||
@ -314,10 +341,255 @@
|
|||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
<object class="GtkImage" id="createImage">
|
<object class="GtkApplicationWindow" id="menuWindow">
|
||||||
<property name="visible">True</property>
|
<property name="width_request">600</property>
|
||||||
|
<property name="height_request">400</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="stock">gtk-new</property>
|
<property name="border_width">4</property>
|
||||||
|
<property name="window_position">mouse</property>
|
||||||
|
<property name="destroy_with_parent">True</property>
|
||||||
|
<property name="skip_taskbar_hint">True</property>
|
||||||
|
<property name="skip_pager_hint">True</property>
|
||||||
|
<property name="decorated">False</property>
|
||||||
|
<property name="deletable">False</property>
|
||||||
|
<property name="gravity">center</property>
|
||||||
|
<property name="has_resize_grip">True</property>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSearchEntry" id="searchProgramsEntry">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="has_focus">True</property>
|
||||||
|
<property name="has_default">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="primary_icon_name">edit-find-symbolic</property>
|
||||||
|
<property name="primary_icon_activatable">False</property>
|
||||||
|
<property name="primary_icon_sensitive">False</property>
|
||||||
|
<signal name="search-changed" handler="searchForEntry" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButtonBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="layout_style">start</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton">
|
||||||
|
<property name="label" translatable="yes">Accessories</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<signal name="clicked" handler="setListGroup" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton">
|
||||||
|
<property name="label" translatable="yes">Multimedia</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<signal name="clicked" handler="setListGroup" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton">
|
||||||
|
<property name="label" translatable="yes">Graphics</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<signal name="clicked" handler="setListGroup" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton">
|
||||||
|
<property name="label" translatable="yes">Game</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<signal name="clicked" handler="setListGroup" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton">
|
||||||
|
<property name="label" translatable="yes">Office</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<signal name="clicked" handler="setListGroup" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">4</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton">
|
||||||
|
<property name="label" translatable="yes">Development</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<signal name="clicked" handler="setListGroup" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">5</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton">
|
||||||
|
<property name="label" translatable="yes">Internet</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<signal name="clicked" handler="setListGroup" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">6</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton">
|
||||||
|
<property name="label" translatable="yes">Settings</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<signal name="clicked" handler="setListGroup" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">7</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton">
|
||||||
|
<property name="label" translatable="yes">System</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<signal name="clicked" handler="setListGroup" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">8</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton">
|
||||||
|
<property name="label" translatable="yes">Wine</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<signal name="clicked" handler="setListGroup" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">9</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton">
|
||||||
|
<property name="label" translatable="yes">Other</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<signal name="clicked" handler="setListGroup" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">10</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScrolledWindow">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="hscrollbar_policy">never</property>
|
||||||
|
<property name="shadow_type">in</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkViewport">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="programListBttns">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
<object class="GtkImage" id="trashImage">
|
<object class="GtkImage" id="trashImage">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 88 KiB |
BIN
src/Pytop/resources/start_menu_icons/start_menu_icon2_32x32.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
src/Pytop/resources/start_menu_icons/start_menu_icon2_64x64.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
src/Pytop/resources/start_menu_icons/start_menu_icon2_72x72.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
src/Pytop/resources/start_menu_icons/start_menu_icon2_96x96.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
src/Pytop/resources/start_menu_icons/start_menu_icon_128x128.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
src/Pytop/resources/start_menu_icons/start_menu_icon_256x256.png
Normal file
After Width: | Height: | Size: 65 KiB |
BIN
src/Pytop/resources/start_menu_icons/start_menu_icon_32x32.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
src/Pytop/resources/start_menu_icons/start_menu_icon_64x64.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
src/Pytop/resources/start_menu_icons/start_menu_icon_72x72.png
Normal file
After Width: | Height: | Size: 9.3 KiB |
BIN
src/Pytop/resources/start_menu_icons/start_menu_icon_96x96.png
Normal file
After Width: | Height: | Size: 15 KiB |
@ -1,17 +1,19 @@
|
|||||||
# Python imports
|
# Python imports
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import os
|
||||||
|
|
||||||
# Gtk imports
|
# Gtk imports
|
||||||
|
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from .mixins import CPUDrawMixin, TaskbarMixin, GridMixin
|
from .mixins import CPUDrawMixin, MainMenuMixin, TaskbarMixin, GridMixin
|
||||||
from widgets import Grid
|
from widgets import Grid
|
||||||
|
from widgets import Icon
|
||||||
from utils import FileHandler
|
from utils import FileHandler
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Signals(CPUDrawMixin, TaskbarMixin, GridMixin):
|
class Signals(CPUDrawMixin, MainMenuMixin, TaskbarMixin, GridMixin):
|
||||||
def __init__(self, settings):
|
def __init__(self, settings):
|
||||||
self.settings = settings
|
self.settings = settings
|
||||||
self.builder = self.settings.returnBuilder()
|
self.builder = self.settings.returnBuilder()
|
||||||
@ -69,3 +71,18 @@ class Signals(CPUDrawMixin, TaskbarMixin, GridMixin):
|
|||||||
selectDirDialog.add_filter(filefilter)
|
selectDirDialog.add_filter(filefilter)
|
||||||
selectDirDialog.set_filename(self.currentPath)
|
selectDirDialog.set_filename(self.currentPath)
|
||||||
self.setNewDirectory(selectDirDialog)
|
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()
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
|
|
||||||
class GridMixin:
|
class GridMixin:
|
||||||
# different from the grid widget class'
|
# Calls the Grid widget class' method
|
||||||
def setNewDirectory(self, widget, data=None):
|
def setNewDirectory(self, widget, data=None):
|
||||||
newPath = widget.get_filename()
|
newPath = widget.get_filename()
|
||||||
self.gridClss.setNewDirectory(newPath)
|
self.gridClss.setNewDirectory(newPath)
|
||||||
|
248
src/Pytop/signal_classes/mixins/MainMenuMixin.py
Normal file
@ -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
|
@ -1,3 +1,4 @@
|
|||||||
|
from .MainMenuMixin import MainMenuMixin
|
||||||
from .TaskbarMixin import TaskbarMixin
|
from .TaskbarMixin import TaskbarMixin
|
||||||
from .CPUDrawMixin import CPUDrawMixin
|
from .CPUDrawMixin import CPUDrawMixin
|
||||||
from .GridMixin import GridMixin
|
from .GridMixin import GridMixin
|
||||||
|
@ -46,7 +46,6 @@ class Grid:
|
|||||||
self.grid.set_model(self.store)
|
self.grid.set_model(self.store)
|
||||||
self.grid.set_pixbuf_column(0)
|
self.grid.set_pixbuf_column(0)
|
||||||
self.grid.set_text_column(1)
|
self.grid.set_text_column(1)
|
||||||
print("Grid generated...")
|
|
||||||
self.grid.connect("item-activated", self.iconDblLeftClick)
|
self.grid.connect("item-activated", self.iconDblLeftClick)
|
||||||
self.grid.connect("button_release_event", self.iconSingleClick, (self.grid,))
|
self.grid.connect("button_release_event", self.iconSingleClick, (self.grid,))
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ from xdg.DesktopEntry import DesktopEntry
|
|||||||
|
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from .icon_manager import easybuttons
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -113,15 +113,6 @@ class Icon:
|
|||||||
execStr = xdgObj.getExec()
|
execStr = xdgObj.getExec()
|
||||||
parts = execStr.split("steam://rungameid/")
|
parts = execStr.split("steam://rungameid/")
|
||||||
id = parts[len(parts) - 1]
|
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"
|
imageLink = "https://steamcdn-a.akamaihd.net/steam/apps/" + id + "/header.jpg"
|
||||||
proc = subprocess.Popen(["wget", "-O", hashImgPth, imageLink])
|
proc = subprocess.Popen(["wget", "-O", hashImgPth, imageLink])
|
||||||
proc.wait()
|
proc.wait()
|
||||||
@ -131,14 +122,13 @@ class Icon:
|
|||||||
elif os.path.exists(icon):
|
elif os.path.exists(icon):
|
||||||
return self.createScaledImage(icon, self.systemIconImageWH)
|
return self.createScaledImage(icon, self.systemIconImageWH)
|
||||||
else:
|
else:
|
||||||
# return easybuttons.IconManager().getIcon(icon, 64)
|
iconsDirs = ["/usr/share/pixmaps", self.usrHome + "/.icons", "/usr/share/icons" ,]
|
||||||
iconsDirs = "/usr/share/icons"
|
altIconPath = ""
|
||||||
for (dirpath, dirnames, filenames) in os.walk(iconsDirs):
|
|
||||||
for file in filenames:
|
for iconsDir in iconsDirs:
|
||||||
appNM = "application-x-" + icon
|
altIconPath = self.traverseIconsFolder(iconsDir, icon)
|
||||||
if appNM in file:
|
if altIconPath is not "":
|
||||||
altIconPath = dirpath + "/" + file
|
break
|
||||||
break
|
|
||||||
|
|
||||||
return self.createScaledImage(altIconPath, self.systemIconImageWH)
|
return self.createScaledImage(altIconPath, self.systemIconImageWH)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -147,6 +137,19 @@ class Icon:
|
|||||||
return None
|
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):
|
def getSystemThumbnail(self, filename, size):
|
||||||
try:
|
try:
|
||||||
if os.path.exists(filename):
|
if os.path.exists(filename):
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
from . import easybuttons
|
|
||||||
from . import execute
|
|
||||||
from . import filemonitor
|
|
@ -1,613 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright (C) 2007-2014 Clement Lefebvre <root@linuxmint.com>
|
|
||||||
# Copyright (C) 2015 Martin Wimpress <code@ubuntu-mate.org>
|
|
||||||
#
|
|
||||||
# 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 "<b>" in text or "<span" in text:
|
|
||||||
label.set_markup(text.replace('&', '&')) # 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 )
|
|
||||||
|
|
||||||
label.set_ellipsize( Pango.EllipsizeMode.END )
|
|
||||||
if (Gtk.MAJOR_VERSION, Gtk.MINOR_VERSION) >= (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 = "<span foreground=\"%s\"><b>%s</b></span>" % (color, appName);
|
|
||||||
#appComment = "<span foreground=\"%s\"><b>%s</b></span>" % (color, appComment);
|
|
||||||
#appName = "<b>%s</b>" % (appName);
|
|
||||||
#appComment = "<b>%s</b>" % (appComment);
|
|
||||||
#else:
|
|
||||||
#appName = "<b>%s</b>" % (appName);
|
|
||||||
#appComment = "<b>%s</b>" % (appComment);
|
|
||||||
appName = "<b>%s</b>" % (appName);
|
|
||||||
appComment = "<b>%s</b>" % (appComment);
|
|
||||||
except Exception as detail:
|
|
||||||
print(detail)
|
|
||||||
pass
|
|
||||||
|
|
||||||
if self.showComment and self.appComment != "":
|
|
||||||
if self.iconSize <= 2:
|
|
||||||
self.addLabel( '<span size="small">%s</span>' % appName)
|
|
||||||
self.addLabel( '<span size="x-small">%s</span>' % appComment)
|
|
||||||
else:
|
|
||||||
self.addLabel( appName )
|
|
||||||
self.addLabel( '<span size="small">%s</span>' % 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 )
|
|
||||||
self.filter = f
|
|
||||||
self.set_focus_on_click(False)
|
|
||||||
|
|
||||||
|
|
||||||
iconManager = IconManager()
|
|
@ -1,74 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright (C) 2007-2014 Clement Lefebvre <root@linuxmint.com>
|
|
||||||
# Copyright (C) 2015 Martin Wimpress <code@ubuntu-mate.org>
|
|
||||||
#
|
|
||||||
# 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()
|
|
@ -1,136 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright (C) 2007-2014 Clement Lefebvre <root@linuxmint.com>
|
|
||||||
# Copyright (C) 2015 Martin Wimpress <code@ubuntu-mate.org>
|
|
||||||
#
|
|
||||||
# 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()
|
|